From 1931bc096d8a2ca4d53fef9dee2921f86c0648d3 Mon Sep 17 00:00:00 2001 From: Apertis CI <devel@lists.apertis.org> Date: Mon, 10 Mar 2025 12:32:46 +0000 Subject: [PATCH 1/5] Import Upstream version 1.4.0 --- .gitlab-ci.yml | 33 +- Makefile.in | 2 +- NEWS | 422 ++- doc/dox/config/pipewire-client.conf.5.md | 19 +- doc/dox/config/pipewire-jack.conf.5.md | 10 +- doc/dox/config/pipewire-props.7.md | 63 +- doc/dox/config/xref.md | 2 +- doc/dox/index.dox | 3 +- doc/dox/internals/midi.dox | 20 +- doc/dox/modules.dox | 4 + doc/dox/programs/pipewire.1.md | 4 + doc/dox/programs/pw-loopback.1.md | 4 +- doc/dox/programs/spa-acp-tool.1.md | 16 +- meson.build | 85 +- meson_options.txt | 12 + pipewire-alsa/alsa-plugins/ctl_pipewire.c | 28 +- pipewire-alsa/alsa-plugins/pcm_pipewire.c | 196 +- pipewire-jack/examples/ump-source.c | 114 + pipewire-jack/examples/video-dsp-play.c | 2 + pipewire-jack/jack/types.h | 23 + pipewire-jack/src/control.c | 4 +- pipewire-jack/src/meson.build | 17 +- pipewire-jack/src/pipewire-jack-extensions.h | 7 + pipewire-jack/src/pipewire-jack.c | 423 ++- pipewire-v4l2/src/pipewire-v4l2.c | 2 +- po/LINGUAS | 1 + po/POTFILES.skip | 1 + po/de.po | 144 +- po/fi.po | 409 +-- po/id.po | 425 +-- po/nl.po | 2 +- po/pl.po | 211 +- po/sl.po | 766 +++++ po/sv.po | 212 +- po/zh_CN.po | 410 +-- spa/examples/local-libcamera.c | 52 +- spa/examples/local-v4l2.c | 53 +- spa/examples/local-videotestsrc.c | 535 ++++ spa/examples/meson.build | 2 + spa/include/spa/buffer/alloc.h | 16 +- spa/include/spa/buffer/buffer.h | 12 +- spa/include/spa/buffer/meta.h | 37 +- spa/include/spa/control/control.h | 9 +- spa/include/spa/control/type-info.h | 3 +- spa/include/spa/control/ump-utils.h | 230 ++ spa/include/spa/debug/buffer.h | 12 +- spa/include/spa/debug/context.h | 11 +- spa/include/spa/debug/dict.h | 12 +- spa/include/spa/debug/file.h | 10 +- spa/include/spa/debug/format.h | 17 +- spa/include/spa/debug/log.h | 10 +- spa/include/spa/debug/mem.h | 12 +- spa/include/spa/debug/node.h | 12 +- spa/include/spa/debug/pod.h | 20 +- spa/include/spa/debug/types.h | 28 +- spa/include/spa/filter-graph/filter-graph.h | 150 + spa/include/spa/graph/graph.h | 55 +- spa/include/spa/interfaces/audio/aec.h | 91 +- spa/include/spa/monitor/device.h | 52 +- spa/include/spa/monitor/utils.h | 12 +- spa/include/spa/node/io.h | 49 +- spa/include/spa/node/node.h | 160 +- spa/include/spa/node/utils.h | 14 +- spa/include/spa/param/audio/aac-utils.h | 12 +- spa/include/spa/param/audio/alac-utils.h | 13 +- spa/include/spa/param/audio/amr-utils.h | 13 +- spa/include/spa/param/audio/ape-utils.h | 12 +- spa/include/spa/param/audio/dsd-utils.h | 12 +- spa/include/spa/param/audio/dsp-utils.h | 12 +- spa/include/spa/param/audio/flac-utils.h | 12 +- spa/include/spa/param/audio/format-utils.h | 12 +- spa/include/spa/param/audio/iec958-types.h | 16 + spa/include/spa/param/audio/iec958-utils.h | 12 +- spa/include/spa/param/audio/layout.h | 4 +- spa/include/spa/param/audio/mp3-utils.h | 12 +- spa/include/spa/param/audio/ra-utils.h | 12 +- spa/include/spa/param/audio/raw-json.h | 105 + spa/include/spa/param/audio/raw-types.h | 28 + spa/include/spa/param/audio/raw-utils.h | 12 +- spa/include/spa/param/audio/raw.h | 4 +- spa/include/spa/param/audio/vorbis-utils.h | 12 +- spa/include/spa/param/audio/wma-utils.h | 12 +- spa/include/spa/param/bluetooth/audio.h | 3 + spa/include/spa/param/bluetooth/type-info.h | 2 + spa/include/spa/param/format-types.h | 6 + spa/include/spa/param/format-utils.h | 10 +- spa/include/spa/param/format.h | 2 + spa/include/spa/param/latency-utils.h | 57 +- spa/include/spa/param/latency.h | 12 +- spa/include/spa/param/param-types.h | 5 + spa/include/spa/param/profiler-types.h | 1 + spa/include/spa/param/profiler.h | 17 +- spa/include/spa/param/props-types.h | 10 +- spa/include/spa/param/route-types.h | 4 +- spa/include/spa/param/tag-utils.h | 22 +- spa/include/spa/param/video/dsp-utils.h | 12 +- spa/include/spa/param/video/format-utils.h | 12 +- spa/include/spa/param/video/h264-utils.h | 12 +- spa/include/spa/param/video/mjpg-utils.h | 12 +- spa/include/spa/param/video/raw-types.h | 20 +- spa/include/spa/param/video/raw-utils.h | 12 +- spa/include/spa/pod/builder.h | 103 +- spa/include/spa/pod/compare.h | 12 +- spa/include/spa/pod/dynamic.h | 12 +- spa/include/spa/pod/filter.h | 20 +- spa/include/spa/pod/iter.h | 114 +- spa/include/spa/pod/parser.h | 68 +- spa/include/spa/support/cpu.h | 53 +- spa/include/spa/support/dbus.h | 49 +- spa/include/spa/support/i18n.h | 28 +- spa/include/spa/support/log.h | 45 +- spa/include/spa/support/loop.h | 298 +- spa/include/spa/support/plugin-loader.h | 28 +- spa/include/spa/support/plugin.h | 54 +- spa/include/spa/support/system.h | 144 +- spa/include/spa/support/thread.h | 48 +- spa/include/spa/utils/defs.h | 29 +- spa/include/spa/utils/dict.h | 27 +- spa/include/spa/utils/dll.h | 16 +- spa/include/spa/utils/endian.h | 26 + spa/include/spa/utils/hook.h | 99 +- spa/include/spa/utils/json-core.h | 635 ++++ spa/include/spa/utils/json-pod.h | 41 +- spa/include/spa/utils/json.h | 693 +---- spa/include/spa/utils/keys.h | 17 + spa/include/spa/utils/list.h | 20 +- spa/include/spa/utils/names.h | 2 + spa/include/spa/utils/ratelimit.h | 12 +- spa/include/spa/utils/result.h | 26 +- spa/include/spa/utils/ringbuffer.h | 24 +- spa/include/spa/utils/string.h | 44 +- spa/include/spa/utils/type-info.h | 4 - spa/include/spa/utils/type.h | 50 + spa/lib/lib.c | 161 + spa/lib/meson.build | 6 + spa/meson.build | 17 +- spa/plugins/aec/aec-webrtc.cpp | 60 +- spa/plugins/alsa/90-pipewire-alsa.rules | 2 +- spa/plugins/alsa/acp-tool.c | 48 +- spa/plugins/alsa/acp/acp.c | 132 +- spa/plugins/alsa/acp/acp.h | 9 + spa/plugins/alsa/acp/alsa-mixer.c | 2 + spa/plugins/alsa/acp/alsa-mixer.h | 2 + spa/plugins/alsa/acp/alsa-ucm.c | 391 ++- spa/plugins/alsa/acp/alsa-ucm.h | 14 + spa/plugins/alsa/acp/alsa-util.c | 53 +- spa/plugins/alsa/acp/alsa-util.h | 1 + spa/plugins/alsa/acp/card.h | 1 + spa/plugins/alsa/acp/compat.h | 8 + spa/plugins/alsa/alsa-acp-device.c | 37 +- spa/plugins/alsa/alsa-pcm-sink.c | 4 +- spa/plugins/alsa/alsa-pcm-source.c | 4 +- spa/plugins/alsa/alsa-pcm.c | 168 +- spa/plugins/alsa/alsa-pcm.h | 66 +- spa/plugins/alsa/alsa-seq-bridge.c | 79 +- spa/plugins/alsa/alsa-seq.c | 99 +- spa/plugins/alsa/alsa-seq.h | 1 + .../alsa/mixer/profile-sets/hdmi-ac3.conf | 110 + ...ixer => USB Device 0x46d_0x9a4--USB Mixer} | 0 spa/plugins/audioconvert/audioadapter.c | 529 ++-- spa/plugins/audioconvert/audioconvert.c | 1011 +++++-- spa/plugins/audioconvert/benchmark-fmt-ops.c | 22 + spa/plugins/audioconvert/biquad.c | 369 ++- spa/plugins/audioconvert/biquad.h | 27 +- spa/plugins/audioconvert/channelmix-ops-c.c | 93 +- spa/plugins/audioconvert/channelmix-ops-sse.c | 211 +- spa/plugins/audioconvert/channelmix-ops.c | 17 +- spa/plugins/audioconvert/channelmix-ops.h | 13 +- spa/plugins/audioconvert/crossover.c | 49 +- spa/plugins/audioconvert/crossover.h | 3 - spa/plugins/audioconvert/delay.h | 52 - spa/plugins/audioconvert/fmt-ops-avx2.c | 265 +- spa/plugins/audioconvert/fmt-ops-rvv.c | 259 ++ spa/plugins/audioconvert/fmt-ops-sse2.c | 291 +- spa/plugins/audioconvert/fmt-ops.c | 26 + spa/plugins/audioconvert/fmt-ops.h | 52 +- spa/plugins/audioconvert/hilbert.h | 7 + spa/plugins/audioconvert/meson.build | 42 + spa/plugins/audioconvert/resample-native.c | 50 +- spa/plugins/audioconvert/resample-peaks.c | 6 + spa/plugins/audioconvert/resample.h | 5 + .../audioconvert/spa-resample-dump-coeffs.c | 200 ++ spa/plugins/audioconvert/spa-resample.c | 30 +- spa/plugins/audioconvert/test-fmt-ops.c | 137 +- .../audioconvert/test-resample-delay.c | 456 +++ spa/plugins/audiomixer/mix-ops.h | 2 +- spa/plugins/avb/avb-pcm.c | 5 +- spa/plugins/avb/avb-pcm.h | 46 +- spa/plugins/bluez5/README-Telephony.md | 345 +++ spa/plugins/bluez5/a2dp-codec-aac.c | 41 +- spa/plugins/bluez5/a2dp-codec-aptx.c | 15 +- spa/plugins/bluez5/a2dp-codec-caps.h | 2 + spa/plugins/bluez5/a2dp-codec-faststream.c | 17 +- spa/plugins/bluez5/a2dp-codec-lc3plus.c | 6 +- spa/plugins/bluez5/a2dp-codec-ldac.c | 23 +- spa/plugins/bluez5/a2dp-codec-opus-g.c | 24 +- spa/plugins/bluez5/a2dp-codec-opus.c | 26 +- spa/plugins/bluez5/a2dp-codec-sbc.c | 18 +- spa/plugins/bluez5/asha-codec-g722.c | 176 ++ spa/plugins/bluez5/backend-native.c | 1166 +++++++- spa/plugins/bluez5/bap-codec-caps.h | 27 + spa/plugins/bluez5/bap-codec-lc3.c | 172 +- spa/plugins/bluez5/bluez5-dbus.c | 342 ++- spa/plugins/bluez5/bluez5-device.c | 446 ++- spa/plugins/bluez5/codec-loader.c | 4 +- spa/plugins/bluez5/decode-buffer.h | 242 +- spa/plugins/bluez5/defs.h | 60 +- spa/plugins/bluez5/g722/g722_enc_dec.h | 148 + spa/plugins/bluez5/g722/g722_encode.c | 387 +++ spa/plugins/bluez5/media-codecs.h | 16 +- spa/plugins/bluez5/media-sink.c | 37 +- spa/plugins/bluez5/media-source.c | 171 +- spa/plugins/bluez5/meson.build | 12 +- spa/plugins/bluez5/midi-node.c | 46 +- spa/plugins/bluez5/quirks.c | 66 +- spa/plugins/bluez5/sco-source.c | 16 +- spa/plugins/bluez5/telephony.c | 1870 ++++++++++++ spa/plugins/bluez5/telephony.h | 132 + spa/plugins/control/mixer.c | 82 +- spa/plugins/filter-graph/audio-dsp-avx.c | 326 ++ spa/plugins/filter-graph/audio-dsp-c.c | 345 +++ spa/plugins/filter-graph/audio-dsp-impl.h | 94 + spa/plugins/filter-graph/audio-dsp-sse.c | 744 +++++ .../plugins/filter-graph/audio-dsp.c | 63 +- spa/plugins/filter-graph/audio-dsp.h | 168 ++ spa/plugins/filter-graph/audio-plugin.h | 95 + .../plugins/filter-graph}/biquad.h | 29 +- spa/plugins/filter-graph/builtin_plugin.c | 2647 +++++++++++++++++ .../plugins/filter-graph}/convolver.c | 246 +- .../plugins/filter-graph}/convolver.h | 4 +- spa/plugins/filter-graph/ebur128_plugin.c | 631 ++++ spa/plugins/filter-graph/filter-graph.c | 2170 ++++++++++++++ .../plugins/filter-graph}/ladspa.h | 0 spa/plugins/filter-graph/ladspa_plugin.c | 385 +++ .../plugins/filter-graph}/lv2_plugin.c | 268 +- spa/plugins/filter-graph/meson.build | 117 + .../plugins/filter-graph}/pffft.c | 0 .../plugins/filter-graph}/pffft.h | 0 .../plugins/filter-graph}/sofa_plugin.c | 216 +- spa/plugins/libcamera/libcamera-device.cpp | 65 +- spa/plugins/libcamera/libcamera-source.cpp | 8 +- spa/plugins/libcamera/libcamera-utils.cpp | 75 +- spa/plugins/meson.build | 1 + spa/plugins/support/cpu-riscv.c | 29 + spa/plugins/support/cpu.c | 6 + spa/plugins/support/logger.c | 39 +- spa/plugins/support/loop.c | 312 +- spa/plugins/support/meson.build | 1 + spa/plugins/support/null-audio-sink.c | 41 +- spa/plugins/v4l2/meson.build | 2 +- spa/plugins/v4l2/v4l2-source.c | 29 +- spa/plugins/v4l2/v4l2-utils.c | 70 +- spa/plugins/videoconvert/meson.build | 19 +- spa/plugins/videoconvert/plugin.c | 12 + spa/plugins/videoconvert/videoadapter.c | 406 ++- spa/plugins/videoconvert/videoconvert-dummy.c | 720 +++++ .../videoconvert/videoconvert-ffmpeg.c | 2082 +++++++++++++ spa/plugins/videotestsrc/videotestsrc.c | 46 +- spa/plugins/vulkan/vulkan-utils.c | 7 +- spa/tools/spa-json-dump.c | 7 +- .../client-rt.conf.avail/20-upmix.conf.in | 8 - src/daemon/client-rt.conf.avail/meson.build | 12 - src/daemon/client-rt.conf.in | 136 - src/daemon/client.conf.in | 72 +- src/daemon/filter-chain/35-ebur128.conf | 63 + src/daemon/filter-chain/36-dcblock.conf | 59 + src/daemon/filter-chain/meson.build | 1 + .../filter-chain/sink-upmix-5.1-filter.conf | 151 + src/daemon/jack.conf.in | 1 + src/daemon/meson.build | 2 - src/daemon/minimal.conf.in | 4 +- src/daemon/pipewire-aes67.conf.in | 4 + src/daemon/pipewire-pulse.conf.in | 15 +- src/daemon/pipewire-vulkan.conf.in | 4 +- src/daemon/pipewire.conf.in | 75 +- src/daemon/systemd/system/meson.build | 8 +- .../systemd/system/pipewire-pulse.service.in | 24 + .../systemd/system/pipewire-pulse.socket | 12 + src/examples/audio-capture.c | 1 - src/examples/audio-src-ring.c | 226 ++ src/examples/audio-src-ring2.c | 269 ++ src/examples/gmain.c | 101 + src/examples/internal.c | 18 +- src/examples/local-v4l2.c | 20 +- src/examples/meson.build | 4 + src/examples/midi-src.c | 14 +- src/examples/video-src.c | 6 +- src/gst/gstpipewireclock.c | 2 +- src/gst/gstpipewirecore.c | 3 +- src/gst/gstpipewiredeviceprovider.c | 206 +- src/gst/gstpipewiredeviceprovider.h | 11 +- src/gst/gstpipewireformat.c | 20 + src/gst/gstpipewirepool.c | 62 +- src/gst/gstpipewirepool.h | 9 + src/gst/gstpipewiresink.c | 345 ++- src/gst/gstpipewiresink.h | 21 + src/gst/gstpipewiresrc.c | 70 +- src/gst/gstpipewiresrc.h | 2 +- src/gst/gstpipewirestream.c | 1 + src/gst/gstpipewirestream.h | 8 + src/gst/meson.build | 2 +- src/modules/meson.build | 131 +- src/modules/module-access.c | 56 +- src/modules/module-adapter.c | 22 + src/modules/module-avb/adp.c | 14 +- src/modules/module-avb/maap.c | 18 +- src/modules/module-client-device.c | 53 + src/modules/module-client-node.c | 68 + src/modules/module-combine-stream.c | 135 +- src/modules/module-echo-cancel.c | 63 +- src/modules/module-example-filter.c | 56 +- src/modules/module-example-sink.c | 64 +- src/modules/module-example-source.c | 64 +- src/modules/module-ffado-driver.c | 113 +- src/modules/module-filter-chain.c | 2276 ++------------ src/modules/module-filter-chain/biquad.c | 364 --- .../module-filter-chain/builtin_plugin.c | 1771 ----------- src/modules/module-filter-chain/dsp-ops-avx.c | 65 - src/modules/module-filter-chain/dsp-ops-c.c | 196 -- src/modules/module-filter-chain/dsp-ops-sse.c | 122 - src/modules/module-filter-chain/dsp-ops.h | 136 - .../module-filter-chain/ladspa_plugin.c | 256 -- src/modules/module-filter-chain/plugin.h | 86 - src/modules/module-jack-tunnel.c | 93 +- src/modules/module-jackdbus-detect.c | 16 + src/modules/module-link-factory.c | 114 +- src/modules/module-loopback.c | 216 +- src/modules/module-metadata.c | 101 +- src/modules/module-netjack2-driver.c | 85 +- src/modules/module-netjack2-manager.c | 122 +- src/modules/module-netjack2/peer.c | 86 +- src/modules/module-parametric-equalizer.c | 247 +- src/modules/module-pipe-tunnel.c | 84 +- src/modules/module-profiler.c | 80 +- src/modules/module-protocol-native.c | 89 +- .../module-protocol-native/local-socket.c | 31 +- .../module-protocol-native/security-context.c | 2 + src/modules/module-protocol-pulse.c | 20 +- src/modules/module-protocol-pulse/client.c | 37 +- src/modules/module-protocol-pulse/client.h | 2 +- src/modules/module-protocol-pulse/cmd.c | 42 +- src/modules/module-protocol-pulse/defs.h | 77 +- src/modules/module-protocol-pulse/format.c | 82 +- src/modules/module-protocol-pulse/format.h | 3 +- src/modules/module-protocol-pulse/internal.h | 2 +- src/modules/module-protocol-pulse/module.c | 4 +- .../modules/module-stream-restore.c | 26 +- .../module-protocol-pulse/pulse-server.c | 137 +- src/modules/module-protocol-pulse/quirks.c | 2 + src/modules/module-protocol-pulse/quirks.h | 2 + src/modules/module-protocol-pulse/server.c | 32 +- src/modules/module-protocol-pulse/stream.c | 10 + src/modules/module-protocol-pulse/stream.h | 1 + src/modules/module-protocol-pulse/utils.c | 45 +- src/modules/module-protocol-pulse/utils.h | 1 + src/modules/module-protocol-simple.c | 71 +- src/modules/module-pulse-tunnel.c | 97 +- src/modules/module-raop-sink.c | 91 +- src/modules/module-raop/rtsp-client.c | 4 +- src/modules/module-roc-source.c | 57 +- src/modules/module-roc/common.h | 45 +- src/modules/module-rt.c | 16 +- src/modules/module-rtp-sap.c | 410 ++- src/modules/module-rtp-sink.c | 59 +- src/modules/module-rtp-source.c | 70 +- src/modules/module-rtp/audio.c | 278 +- src/modules/module-rtp/midi.c | 44 +- src/modules/module-rtp/opus.c | 22 +- src/modules/module-rtp/stream.c | 145 +- src/modules/module-rtp/stream.h | 8 +- src/modules/module-snapcast-discover.c | 110 +- ...-factory.c => module-spa-device-factory.c} | 6 +- .../module-device.c => module-spa-device.c} | 6 +- ...de-factory.c => module-spa-node-factory.c} | 6 +- .../{spa/module-node.c => module-spa-node.c} | 6 +- src/modules/module-vban-recv.c | 433 ++- src/modules/module-vban-send.c | 13 +- src/modules/module-vban/audio.c | 30 +- src/modules/module-vban/midi.c | 51 +- src/modules/module-vban/stream.c | 104 +- src/modules/module-vban/vban.h | 116 +- src/modules/network-utils.h | 30 +- src/modules/spa/meson.build | 31 - src/modules/spa/spa-node.c | 81 - src/pipewire/array.h | 19 +- src/pipewire/client.h | 58 +- src/pipewire/conf.c | 145 +- src/pipewire/conf.h | 4 + src/pipewire/context.c | 103 +- src/pipewire/core.c | 25 + src/pipewire/core.h | 150 +- src/pipewire/device.h | 55 +- src/pipewire/extensions/client-node.h | 71 +- src/pipewire/extensions/metadata.h | 50 +- src/pipewire/extensions/profiler.h | 25 +- src/pipewire/extensions/security-context.h | 37 +- src/pipewire/factory.h | 25 +- src/pipewire/filter.c | 73 +- src/pipewire/filter.h | 36 +- src/pipewire/impl-client.c | 4 + src/pipewire/impl-device.c | 1 + src/pipewire/impl-factory.c | 2 + src/pipewire/impl-link.c | 109 +- src/pipewire/impl-module.c | 1 + src/pipewire/impl-node.c | 149 +- src/pipewire/impl-port.c | 19 +- src/pipewire/keys.h | 6 +- src/pipewire/link.h | 26 +- src/pipewire/loop.c | 1 + src/pipewire/loop.h | 122 +- src/pipewire/map.h | 20 +- src/pipewire/mem.c | 1 + src/pipewire/mem.h | 8 +- src/pipewire/module.h | 25 +- src/pipewire/node.h | 65 +- src/pipewire/pipewire.c | 5 +- src/pipewire/port.h | 45 +- src/pipewire/private.h | 47 +- src/pipewire/properties.c | 32 +- src/pipewire/properties.h | 26 +- src/pipewire/settings.c | 9 +- src/pipewire/stream.c | 171 +- src/pipewire/stream.h | 97 +- src/pipewire/thread-loop.h | 5 + src/pipewire/thread.c | 12 +- src/pipewire/thread.h | 30 +- src/pipewire/utils.c | 13 +- src/tests/test-security-context.c | 2 +- src/tools/dfffile.c | 136 +- src/tools/dsffile.c | 138 +- src/tools/midifile.c | 532 +++- src/tools/midifile.h | 3 + src/tools/pw-cat.c | 159 +- src/tools/pw-cli.c | 51 +- src/tools/pw-config.c | 3 +- src/tools/pw-container.c | 10 +- src/tools/pw-dot.c | 10 +- src/tools/pw-dump.c | 12 +- src/tools/pw-loopback.c | 4 +- src/tools/pw-mididump.c | 10 +- src/tools/pw-mon.c | 4 +- src/tools/pw-profiler.c | 216 +- src/tools/pw-top.c | 11 +- subprojects/webrtc-audio-processing.wrap | 4 +- subprojects/wireplumber.wrap | 2 +- test/meson.build | 1 + test/test-example.c | 4 + test/test-spa-control.c | 173 ++ test/test-spa-json.c | 33 + 449 files changed, 36389 insertions(+), 13169 deletions(-) create mode 100644 pipewire-jack/examples/ump-source.c create mode 100644 po/sl.po create mode 100644 spa/examples/local-videotestsrc.c create mode 100644 spa/include/spa/control/ump-utils.h create mode 100644 spa/include/spa/filter-graph/filter-graph.h create mode 100644 spa/include/spa/param/audio/raw-json.h create mode 100644 spa/include/spa/utils/endian.h create mode 100644 spa/include/spa/utils/json-core.h create mode 100644 spa/lib/lib.c create mode 100644 spa/lib/meson.build create mode 100644 spa/plugins/alsa/mixer/profile-sets/hdmi-ac3.conf rename spa/plugins/alsa/mixer/samples/{USB Device 0x46d:0x9a4--USB Mixer => USB Device 0x46d_0x9a4--USB Mixer} (100%) delete mode 100644 spa/plugins/audioconvert/delay.h create mode 100644 spa/plugins/audioconvert/fmt-ops-rvv.c create mode 100644 spa/plugins/audioconvert/spa-resample-dump-coeffs.c create mode 100644 spa/plugins/audioconvert/test-resample-delay.c create mode 100644 spa/plugins/bluez5/README-Telephony.md create mode 100644 spa/plugins/bluez5/asha-codec-g722.c create mode 100644 spa/plugins/bluez5/g722/g722_enc_dec.h create mode 100644 spa/plugins/bluez5/g722/g722_encode.c create mode 100644 spa/plugins/bluez5/telephony.c create mode 100644 spa/plugins/bluez5/telephony.h create mode 100644 spa/plugins/filter-graph/audio-dsp-avx.c create mode 100644 spa/plugins/filter-graph/audio-dsp-c.c create mode 100644 spa/plugins/filter-graph/audio-dsp-impl.h create mode 100644 spa/plugins/filter-graph/audio-dsp-sse.c rename src/modules/module-filter-chain/dsp-ops.c => spa/plugins/filter-graph/audio-dsp.c (55%) create mode 100644 spa/plugins/filter-graph/audio-dsp.h create mode 100644 spa/plugins/filter-graph/audio-plugin.h rename {src/modules/module-filter-chain => spa/plugins/filter-graph}/biquad.h (96%) create mode 100644 spa/plugins/filter-graph/builtin_plugin.c rename {src/modules/module-filter-chain => spa/plugins/filter-graph}/convolver.c (51%) rename {src/modules/module-filter-chain => spa/plugins/filter-graph}/convolver.h (72%) create mode 100644 spa/plugins/filter-graph/ebur128_plugin.c create mode 100644 spa/plugins/filter-graph/filter-graph.c rename {src/modules/module-filter-chain => spa/plugins/filter-graph}/ladspa.h (100%) create mode 100644 spa/plugins/filter-graph/ladspa_plugin.c rename {src/modules/module-filter-chain => spa/plugins/filter-graph}/lv2_plugin.c (70%) create mode 100644 spa/plugins/filter-graph/meson.build rename {src/modules/module-filter-chain => spa/plugins/filter-graph}/pffft.c (100%) rename {src/modules/module-filter-chain => spa/plugins/filter-graph}/pffft.h (100%) rename {src/modules/module-filter-chain => spa/plugins/filter-graph}/sofa_plugin.c (62%) create mode 100644 spa/plugins/support/cpu-riscv.c create mode 100644 spa/plugins/videoconvert/videoconvert-dummy.c create mode 100644 spa/plugins/videoconvert/videoconvert-ffmpeg.c delete mode 100644 src/daemon/client-rt.conf.avail/20-upmix.conf.in delete mode 100644 src/daemon/client-rt.conf.avail/meson.build delete mode 100644 src/daemon/client-rt.conf.in create mode 100644 src/daemon/filter-chain/35-ebur128.conf create mode 100644 src/daemon/filter-chain/36-dcblock.conf create mode 100644 src/daemon/filter-chain/sink-upmix-5.1-filter.conf create mode 100644 src/daemon/systemd/system/pipewire-pulse.service.in create mode 100644 src/daemon/systemd/system/pipewire-pulse.socket create mode 100644 src/examples/audio-src-ring.c create mode 100644 src/examples/audio-src-ring2.c create mode 100644 src/examples/gmain.c delete mode 100644 src/modules/module-filter-chain/biquad.c delete mode 100644 src/modules/module-filter-chain/builtin_plugin.c delete mode 100644 src/modules/module-filter-chain/dsp-ops-avx.c delete mode 100644 src/modules/module-filter-chain/dsp-ops-c.c delete mode 100644 src/modules/module-filter-chain/dsp-ops-sse.c delete mode 100644 src/modules/module-filter-chain/dsp-ops.h delete mode 100644 src/modules/module-filter-chain/ladspa_plugin.c delete mode 100644 src/modules/module-filter-chain/plugin.h rename src/modules/{spa/module-device-factory.c => module-spa-device-factory.c} (97%) rename src/modules/{spa/module-device.c => module-spa-device.c} (95%) rename src/modules/{spa/module-node-factory.c => module-spa-node-factory.c} (98%) rename src/modules/{spa/module-node.c => module-spa-node.c} (95%) delete mode 100644 src/modules/spa/meson.build create mode 100644 test/test-spa-control.c diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b671db36..3953445e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,13 @@ +# Create merge request pipelines for open merge requests, branch pipelines +# otherwise. This allows MRs for new users to run CI, and prevents duplicate +# pipelines for branches with open MRs. +workflow: + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS + when: never + - if: $CI_COMMIT_BRANCH + stages: - container - container_coverity @@ -25,7 +35,7 @@ include: .fedora: variables: # Update this tag when you want to trigger a rebuild - FDO_DISTRIBUTION_TAG: '2024-05-30.0' + FDO_DISTRIBUTION_TAG: '2024-12-10.0' FDO_DISTRIBUTION_VERSION: '40' FDO_DISTRIBUTION_PACKAGES: >- alsa-lib-devel @@ -46,6 +56,7 @@ include: jack-audio-connection-kit-devel libasan libcanberra-devel + libebur128-devel libffado-devel libldac-devel libmysofa-devel @@ -307,17 +318,24 @@ build_on_fedora_html_docs: -Dsndfile=enabled -Dsession-managers=[] before_script: - - git fetch origin 1.0 master + - git fetch origin 1.0 1.2 master - git branch -f 1.0 origin/1.0 - - git branch -f master origin/master - git clone -b 1.0 . branch-1.0 + - git branch -f 1.2 origin/1.2 + - git clone -b 1.2 . branch-1.2 + - git branch -f master origin/master - git clone -b master . branch-master - !reference [.build, before_script] script: - cd branch-1.0 - meson setup builddir $MESON_OPTIONS - meson compile -C builddir doc/pipewire-docs - - cd ../branch-master + - cd .. + - cd branch-1.2 + - meson setup builddir $MESON_OPTIONS + - meson compile -C builddir doc/pipewire-docs + - cd .. + - cd branch-master - meson setup builddir $MESON_OPTIONS - meson compile -C builddir doc/pipewire-docs artifacts: @@ -558,12 +576,15 @@ pages: dependencies: - build_on_fedora_html_docs script: - - mkdir public public/devel - - cp -R branch-1.0/builddir/doc/html/* public/ + - mkdir public public/1.0 public/1.2 public/devel + - cp -R branch-1.0/builddir/doc/html/* public/1.0/ + - cp -R branch-1.2/builddir/doc/html/* public/1.2/ - cp -R branch-master/builddir/doc/html/* public/devel/ + - (cd public && ln -s 1.2/* .) artifacts: paths: - public rules: - if: $CI_COMMIT_BRANCH == 'master' - if: $CI_COMMIT_BRANCH == '1.0' + - if: $CI_COMMIT_BRANCH == '1.2' diff --git a/Makefile.in b/Makefile.in index 67b199bb..10461931 100644 --- a/Makefile.in +++ b/Makefile.in @@ -38,7 +38,7 @@ gdb: $(MAKE) run DBG=gdb valgrind: - $(MAKE) run DBG="DISABLE_RTKIT=1 PIPEWIRE_DLCLOSE=false valgrind --trace-children=yes" + $(MAKE) run DBG="DISABLE_RTKIT=1 PIPEWIRE_DLCLOSE=false valgrind --trace-children=yes --leak-check=full" test: all ninja -C $(BUILD_ROOT) test diff --git a/NEWS b/NEWS index ea6fab48..66ce22fc 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,343 @@ +# PipeWire 1.4.0 (2025-03-06) + +This is the 1.4 release that is API and ABI compatible with previous +1.2.x and 1.0.x releases. + +This release contains some of the bigger changes that happened since +the 1.2 release last year, including: + + * client-rt.conf was removed, all clients now use client.conf and + are given RT priority in the data threads. + * UMP (aka MIDI2) support was added and is now the default format + to carry MIDI1 and MIDI2 around in PipeWire. There are helper + functions to convert between legacy MIDI and UMP. + * The resampler can now precompute (at compile time) some common + conversion filters. Delay reporting in the resampler was fixed and + improved. + * Bluetooth support for BAP broadcast links and support for hearing aids + using ASHA was added. A new G722 codec was also added. + Delay reporting and configuration in Bluetooth was improved. + * The ALSA plugin now supports DSD playback when explicitly allowed + with the alsa.formats property. + * A PipeWire JACK control API was added. + * A system service was added for pipewire-pulse. + * Many documentation and translation updates. + * Many of the SPA macros are converted to inline functions. All SPA + inline functions are now also compiled into a libspa.so library to + make it easier to access them from bindings. + * The module-filter-chain graph code was moved to a separate + filter-graph SPA plugin so that it becomes usable in more places. + EBUR128, param_eq and dcblock plugins were added to filter-graph. + The filter graph can now also use fftw for doing convolutions. + The audioconvert plugin was optimized and support was added to + audioconvert to insert extra filter-graphs in the processing pipeline. + * New helper functions were added to parse JSON format descriptions. + * The profiler now also includes the clock of the followers. + * RISCV CPU support and assembler optimisations were added. + * The clock used for logging timestamps can be configured now. + * The JSON parser was split into core functions and helper. + * Support for UCM split PCMs was added. Instead of alsa-lib splitting + up PCMs, PipeWire can mark the PCMs with the correct metadata so that + the session manager can use native PipeWire features to do this. + * Support for webrtc2 was added to echo-cancel. + * IEC958 codecs are now detected from the HDMI ELD data. + * Conversion between floating point and 32 bits now preserve 25 bits of + precision instead of 24 bits. + * A new Telephony D-BUS API compatible with ofono was added. + * The invoke queues are now more efficient and can be called from multiple + threads concurrently. + * Clock information in v4l2 was improved. + * An ffmpeg based videoconvert plugin was added that can be used with the + videoadapter. + * The GStreamer elements have improved buffer pool handling and rate + matching. + * The combine-stream module can now also mix streams. + * link-factory now checks that the port and node belong together. + * The netjack-manager module has support for autoconnecting streams. + * The native-protocol has support for abstract sockets. + * The pulse server has support for blocking playback and capture in + pulse.rules. + * The corked state of stream is now reported correctly in pulse-server. + * Fix backwards jumps in pulse-server. + * Latency configuration support was added in loopback and raop-sink. + * The ROC module has more configuration options. + * The SAP module now only send updated SDP when something changed. + * RTP source now has a standby mode where it idles when there is no + data received. + * Support for PTP clocking was added the RTP streams. + * The VBAN receiver can now dynamically create streams when they are + detected. + * Error reporting when making links was improved. + * Support for returning (canceling) a dequeued buffer in pw-stream. + * Support for emiting events in pw-stream was added. + * pw-cat now support stdin and stdout. + + +## Highlights (since the previous 1.3.83 release) + - Small fixes and improvements. + +## PipeWire + - Fix some missing includes in metadata.h + - Pass the current error in errno when a stream is in error (#4574) + + +## modules + - Evaluate node rules before loading adapter follower to ensure + properties are set correctly. (#4562) + +## SPA + - Avoid a use after free when building PODs. (#4445) + - Take headroom into account when calculating resync. + +## Bluetooth + - Fix +CLCC parsing. + +## GStreamer + - Notify about default device changes in deviceprovider. + - Copy frames between pools and avoid splitting video buffers. + +## JACK + - Add an option to disable the MIDI2 port flags. (#4584) + + +Older versions: + +# PipeWire 1.3.83 (2025-02-20) + +This is the third and hopefully last 1.4 release candidate that +is (almost) API and (entirely) ABI compatible with previous 1.2.x +and 1.0.x releases. + +We note that in the 1.3.x series, the API is slighty not backwards +compatible because some methods previously used to accept void* as +a parameter while they now require the correct type. We think this +is however a good kind of API breakage and expect projects to patch +their code to get things compiled with newer version (which will also +compile for older versions). Note also that this is not an ABI break. + + +## Highlights + - Handle JACK transport updates in a better way. + - Fix a SAP regression when starting. + - Fix regression in rate scaling. + - Improve bluetooth source rate handling. + - More small bugfixes and improvements. + + +## PipeWire + - Handle JACK transport updates in a better way. (#4543) + +## Modules + - Check that the link factory port and nodes match. Deprecate the + port.id when making links. + - Improve profiler output by scaling the quantum with the node + rate so that we don't end up with confusing information. (#4555) + - Fix sending of the SAP SDP. Handle some SDP parsing errors. + - Add some more options to the ROC source module. (#4516) + +## SPA + - Fix firewire quirks in udev rules. (#4528) + - Fix a bug in the rate scaling in some cases that would make things + run with the wrong samplerate. + - Improve introspection of control types. + +## Bluetooth + - Use the G722 codec from Android instead of FFmpeg for ASHA. + - Use the A2DP source rate as the graph rate. (#4555) + - Specify the bluetooth source latency property in the rate of the + stream to avoid conversions and rounding errors. + +# PipeWire 1.3.82 (2025-02-06) + +This is the second 1.4 release candidate that is API and ABI +compatible with previous 1.2.x and 1.0.x releases. + +## Highlights + - Various pw-stream improvements: timing information fixes, + avoid locking buffers in some cases and an improved drain + event. + - A new Telephony D-BUS API compatible with ofono. + - Documentation fixes and updates. + - More small fixes and improvements. + + +## PipeWire + - Improve timing information when rate is unknown. + - Avoid locked buffers in pw_stream in some cases. + - Improve pw_stream drain event emission. + - Improve manager socket handling. Applications can avoid hardcoding + the sockets so that they will respect the config settings. + +## modules + - Fix header size calculation when using ipv6. (#4524) + +## SPA + - Optimize byteswapped s16 conversions. + - Improve event handling for internal events. + - Optimize negiotiation when in convert mode, prefer the format + of the follower in adapter. + - Fix EnumPortConfig for videoadapter without converter. + - Fix libcamera property buffer size. + +## Pulse-server + - Add systemwide systemd files. + +## JACK + - Add a UMP example. + - Use the new JackPortMIDI2 flag to mark UMP ports to JACK. + +## Bluetooth + - Support BAP hardware volume. + - Add a Telephony DBUS API. + +## GStreamer + - Disable buffer pools for audio by default. + +## Docs + - Improve the module documentation. + +# PipeWire 1.3.81 (2025-01-23) + +This is the first 1.4 release candidate that is API and ABI +compatible with previous 1.2.x and 1.0.x releases. + +In addition to all the changes backported to 1.2.x, this release +also contains some new features: + +## Highlights + - UMP support was added with MIDI 1.0 and MIDI 2.0 support in the ALSA + sequencer plugin. By default PipeWire will now use MIDI 2.0 in UMP + messages to transport MIDI in the graph, with conversions to/from legacy + MIDI where required. This requires UMP support in the kernel. + - client-rt.conf is no longer supported. Custom changes made to this + config should be moved to client.conf. Clients that try to load the + client-rt.conf will emit a warning and be directed to client.conf + automatically for backwards compatibility. + - The module-filter-chain code was moved to a new filter-graph plugin. This + made it possible to add filter-graph support directly in audioconvert. It + is now possible to run up to 8 run-time swappable filter-graphs inside + streams and nodes. This should make it easier to add effects to streams + and device nodes. + - Bluetooth support for BAP broadcast links and support for hearing aids + using ASHA was added. + - Many more bugfixes and improvements. + +## PipeWire + - Nodes are now only scheduled when ready to signal the driver. + - Add slovenian translation. (#4156) + - Link errors are handled better. + - The videoadapter is now enabled by default but no videoconverter + is loaded yet by default. + - Streams now have support for ProcessLatency. + - Streams now have a method to emit events. + - The RequestProcess event and command can now pass around extra + properties. + - Local timestamps are now used for logging. + - client-rt.conf is no longer supported. Custom changes made to this + config should be moved to client.conf. Clients that try to load the + client-rt.conf will emit a warning and be directed to cliert.conf + automatically to preserve backwards compatibility. + - pw_stream now has an API to return unused buffers. + +## modules + - module-combine-stream can now mix streams. + - Links in error are now destroyed by link-factory. + - The netjack2 driver can now also create streams that autoconnect when + specified. (#4125) + - Many updates and bugfixes to the RTP modules. + - The netjack2 driver can now bind to a custom IP and port pair. (#4144) + - The loopback module and module-raop have support for ProcessLatency, which + can be used to query and update the latency. + - The profiler module can now reduce the sampling rate. + - The filter-chain was optimized some more. + - The filter-chain gained some more plugins: param_eq, ebur128, dcblock. + - Support for fftw based convolver was added. + - Some module arguments can now be overridden. + - The VBAN receiver now creates new streams per stream name. (#4400) + - The RTP SAP module is now smarter with generating new SAP messages. + - The RTP source can now be paused when no data is received. (#4456) + +## tools + - pw-cat can now stream most formats from stdin/stdout. + - pw-profiler has a JSON dump option to dump the raw profiler data. + - pw-cli now supports unload-module. (#4276) + +## SPA + - The resampler can precompute some common coeficients now at compile + time. + - UMP support was added with MIDI 1.0 and MIDI 2.0 support in the ALSA + sequencer plugin. By default PipeWire will now use MIDI 2.0 in UMP + messages to transport MIDI in the graph, with conversions to/from legacy + MIDI where required. + - Control types can now be negotiated. + - Support for writing ALSA bind controls was added. + - The ALSA sequencer now has better names for the ports. + - The F32 to S32 conversion now uses 25 bits for an extra bit of + precision. + - libcamera controls can now be set in all cases. + - The videoadapter has been improved and a dummy and ffmpeg based + videoconverter plugin was added. + - Negotiation was improved in audioadapter. First a passthrough format + is tried. + - Some JSON helper functions were added and some duplicate code removed + or simplified. + - Add support for RISC V CPU detection and add many optimizations in + the audio converters. + - Add an option to disable ALSA mixer path select. (#4311) + - Fix a potential bug with the cleanup of the loop queues. + - ALSA nodes now dynamically adjust the DLL bandwidth based on average + measured variance. + - The loop invoke queue was made more efficient and make it possible to + invoke from multiple threads. + - The filter-chain code was moved to a new filter-graph plugin. + - Most function macros are now static inlined and can also be built into a + libspa.so file. This should improve language bindings. + - V4l2 clock information was improved. + - Supported IEC958 codecs are now autodetected via ELD info. + - Audioconvert was optimized some more. + - Audioconvert can now include filter-graphs in its processing. + - webrtc-audio-processing-2 is now supported in AEC. + - The resampler now reports the delay and subsample delay. Also the + delay is reported in the samplerate of the input. + - The ALSA sequencer now handle kernels without UMP support. (#4507) + +## Pulse-server + - Add quirk to block clients from making record and playback streams. + - The corked state is now set on stream to always report this state + correctly to other clients. + - Readiness notification was added to the pulse server with the + PIPEWIRE_PULSE_NOTIFICATION_FD environment variable. (#4347) + - The pulse.cmd config now supports conditions. + - A bug in clearing the ringbuffer was fixed. (#4464) + +## GStreamer + - Support for the default devices was added to the deviceprovider. (#4268) + - The graph clock is now used as the source for the GStreamer clock. + - The sink now does some rate control. + +## ALSA + - The ALSA plugin now supports DSD when explicitly enabled. + +## JACK + - JACK now supports 2 new extension formats for OSC and UMP. + - JACK clients can receive UMP MIDI1 or MIDI2 messages when using + the new UMP port format extension. + - JACK now reports the PipeWire version in the minor/micro/proto. + - Implement more jackserver functions. + +## Bluetooth + - Support BAP broadcast links. + - Support for ASHA was added. + - Delay reporting in A2DP sources was improved. + +## Examples + - 2 new examples of pw-stream using spa_ringbuffer were added. + +## Docs + - Many updates to the man pages. + - More documentation about thread safety of functions in stream + and filters. (#4521) + # PipeWire 1.2.7 (2024-11-26) This is a bugfix release that is API and ABI compatible with the previous @@ -48,9 +388,6 @@ This is a bugfix release that is API and ABI compatible with the previous ## Tools - Fix pw-dot link labels. -Older versions: - - # PipeWire 1.2.6 (2024-10-23) This is a bugfix release that is API and ABI compatible with the previous @@ -93,6 +430,40 @@ This is a bugfix release that is API and ABI compatible with the previous ## Docs - Backport docs from master. +# PipeWire 1.0.9 (2024-10-22) + +This is a bugfix release that is API and ABI compatible with previous +1.0.x releases. + +## Highlights + - Fix an fd leak and confusion in the protocol that would cause leaks and + wrong memory to be used. + - Fix bug where the mixer would not be synced correctly after selecting + a port, leaving the audio muted. (#4084) + - Backport v4l2 systemd-logind support to avoid races when starting. + (#3539 and #3960). + - Other small fixed and improvements. + + +## PipeWire + - Fix a bug where renegotiation would sometimes fail to deactivate a link. + - Fix an fd leaks and confusion in the protocol. + +## modules + - Fix a use-after-free in the rt module when stopping a thread. + +## SPA + - Fix bug where the mixer would not be synced correctly after selecting + a port, leaving the audio muted. (#4084) + - Fix a compilation issue with empty initializers. (#4317) + - Backport v4l2 systemd-logind support to avoid races when starting. + (#3539 and #3960). + - Fix a potential crash when cleaning ALSA nodes. + +## JACK + - align buffers to the max cpu alignment in order to allow more + optimizations. + # PipeWire 1.2.5 (2024-09-27) This is an important bugfix release that is API and ABI compatible with the @@ -138,7 +509,6 @@ previous 1.2.x and 1.0.x releases. ## Doc - Some small doc updates. (#4272) - # PipeWire 1.2.4 (2024-09-19) This is a bugfix release that is API and ABI compatible with the @@ -167,6 +537,50 @@ previous 1.2.x and 1.0.x releases. - Emit buffer_size callback in jack_activate() to improve compatibility with GStreamer. (#4260) +# PipeWire 1.0.8 (2024-09-19) + +This is a small bugfix release that is API and ABI compatible with previous +1.0.x releases. + +## Highlights + - Backport support for explicit sync. + - FFADO backport fixes. + - Fix some races in JACK. + - More small fixes and improvements. + + +## PipeWire + - Add support for mandatory metadata and explicit sync metadata. + - Fix RequestProcess again. + - Include config.h to use malloc_trim() when cleaning nodes. + - Avoid crash when destroying a global. (#4250) + +## Modules + - FFADO fixes: improve timing reporting, avoid some xruns, improve + samplerate and period size handling, implement freewheeling. + - Decrease memory usage of the profiler. + +## Tools + - Fix pw-dump metadata changes fix. (#4053) + - Support large params in pw-cli. (#4166) + +## SPA + - Improve libcamera devices reporting to properly filter out duplicates in + all cases. + - Improve property reporting in v4l2. + - Fix lost buffer in v4l2. + +## Bluetooth + - Improve compatibility with some devices. + +## JACK + - Fix some races when shutting down. + - Fix rt-priority on the main thread when using custom thread create + function. (#4099) + +## ALSA + - Handle format renegotiation. (#3856) + # PipeWire 1.2.3 (2024-08-22) This is a bugfix release that is API and ABI compatible with the diff --git a/doc/dox/config/pipewire-client.conf.5.md b/doc/dox/config/pipewire-client.conf.5.md index 11a849d3..66bf3d2e 100644 --- a/doc/dox/config/pipewire-client.conf.5.md +++ b/doc/dox/config/pipewire-client.conf.5.md @@ -18,18 +18,6 @@ The PipeWire client configuration file. *$XDG_CONFIG_HOME/pipewire/client.conf.d/* -*$XDG_CONFIG_HOME/pipewire/client-rt.conf* - -*$(PIPEWIRE_CONFIG_DIR)/client-rt.conf* - -*$(PIPEWIRE_CONFDATADIR)/client-rt.conf* - -*$(PIPEWIRE_CONFDATADIR)/client-rt.conf.d/* - -*$(PIPEWIRE_CONFIG_DIR)/client-rt.conf.d/* - -*$XDG_CONFIG_HOME/pipewire/client-rt.conf.d/* - # DESCRIPTION Configuration for PipeWire native clients, and for PipeWire's ALSA @@ -38,9 +26,6 @@ plugin. A PipeWire native client program selects the default config to load, and if nothing is specified, it usually loads `client.conf`. -The ALSA plugin uses the `client-rt.conf` file, as do some PipeWire -native clients such as \ref page_man_pw-cat_1 "pw-cat(1)". - The configuration file format and lookup logic is the same as for \ref page_man_pipewire_conf_5 "pipewire.conf(5)". Drop-in configuration files `client.conf.d/*.conf` can be used, and are recommended. @@ -148,7 +133,7 @@ An `alsa.properties` section can be added to configure client applications that connect via the PipeWire ALSA plugin. ```css -# ~/.config/pipewire/client-rt.conf.d/custom.conf +# ~/.config/pipewire/client.conf.d/custom.conf alsa.properties = { #alsa.deny = false @@ -193,7 +178,7 @@ set any of the above ALSA properties or any of the `stream.properties`. ### Example ```css -# ~/.config/pipewire/client-rt.conf.d/custom.conf +# ~/.config/pipewire/client.conf.d/custom.conf alsa.rules = [ { matches = [ { application.process.binary = "resolve" } ] diff --git a/doc/dox/config/pipewire-jack.conf.5.md b/doc/dox/config/pipewire-jack.conf.5.md index a2e2f6e3..b8a4a5cf 100644 --- a/doc/dox/config/pipewire-jack.conf.5.md +++ b/doc/dox/config/pipewire-jack.conf.5.md @@ -70,7 +70,7 @@ jack.properties = { #jack.max-client-ports = 768 #jack.fill-aliases = false #jack.writable-input = false - + #jack.flag-midi2 = false } ``` @@ -198,6 +198,14 @@ from the buffer. Set this to true to avoid buffer corruption if you are only dealing with non-buggy clients. \endparblock +@PAR@ jack.conf jack.flag-midi2 +\parblock +Use the new JACK MIDI2 port flag on MIDI2 (UMP) ports. This is disabled by default because most +JACK apps don't know about this flag yet and refuse to show the port. + +Set this to true for applications that know how to handle MIDI2 ports. +\endparblock + # MATCH RULES @IDX@ jack.conf `jack.rules` provides an `update-props` action that takes an object with properties that are updated diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md index 9822cce9..d7e6af82 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -664,8 +664,19 @@ This option does nothing if `api.alsa.use-acp` is set to `false`. @PAR@ device-prop api.alsa.soft-mixer = false # boolean Setting this option to `true` will disable the hardware mixer for volume control and mute. All volume handling will then use software volume and mute, -leaving the hardware mixer untouched. The hardware mixer will still be used -to mute unused audio paths in the device. +leaving the hardware mixer untouched. This can be interesting to work around +bugs in the mixer detection or decibel reporting. The hardware mixer will still +be used to mute unused audio paths in the device. Use `api.alsa.disable-mixer-path` +to also disable mixer path selection. + +@PAR@ device-prop api.alsa.disable-mixer-path = false # boolean +Setting this option to `true` will disable the hardware mixer path selection. +The hardware mixer path is the configuration of the mixer depending on the +jacks that are inserted in the card. If this is disabled, you will have to +manually enable and disable mixer controls but it can be used to work around +bugs in the mixer. The hardware mixer will still be used for +volume and mute. Use `api.alsa.soft-mixer` to also disable hardware volume +and mute. @PAR@ device-prop api.alsa.ignore-dB = false # boolean Setting this option to `true` will ignore the decibel setting configured by @@ -702,6 +713,11 @@ Normally, the maximum amount of channels will be used but with this setting this can be reduced, which can make it possible to use other samplerates on some devices. +@PAR@ device-prop api.alsa.split-enable # boolean +\parblock +\copydoc SPA_KEY_API_ALSA_SPLIT_ENABLE +\endparblock + ## Node properties @PAR@ node-prop audio.channels # integer @@ -785,6 +801,24 @@ UNDOCUMENTED Enable only specific IEC958 codecs. This can be used to disable some codecs the hardware supports. Available values: PCM, AC3, DTS, MPEG, MPEG2-AAC, EAC3, TRUEHD, DTSHD +@PAR@ device-prop api.alsa.split.parent # boolean +\parblock +\copydoc SPA_KEY_API_ALSA_SPLIT_PARENT +\endparblock + +@PAR@ node-prop api.alsa.split.position # JSON +\parblock +\copybrief SPA_KEY_API_ALSA_SPLIT_POSITION +Informative property. +\endparblock + +@PAR@ node-prop api.alsa.split.hw-position # JSON +\parblock +\copybrief SPA_KEY_API_ALSA_SPLIT_HW_POSITION +Informative property. +\endparblock + + # BLUETOOTH PROPERTIES @IDX@ props ## Monitor properties @@ -890,6 +924,7 @@ bluez5.bcast_source.config = [ { "broadcast_code": "Børne House", "encryption: false, + "sync_factor": 2, "bis": [ { # BIS configuration "qos_preset": "16_2_1", # QOS preset name from table Table 6.4 from BAP_v1.0.1. @@ -904,6 +939,30 @@ bluez5.bcast_source.config = [ ``` \endparblock +@PAR@ monitor-prop bluez5.bap-server-capabilities.rates # Array of integers +Supported sampling frequencies for the LC3 codec (default: all). +Possible values: +`8000`, `16000`, `24000`, `32000`, `44100`, `48000` + +@PAR@ monitor-prop bluez5.bap-server-capabilities.durations # Array of doubles +Supported frame durations for the LC3 codec (default: all). +Possible values: +`7.5`, `10` + +@PAR@ monitor-prop bluez5.bap-server-capabilities.channels # Array of integers +Supported audio channel counts for the LC3 codec (default: [1, 2]). +Possible values: +`1`, `2`, `3`, `4`, `5`, `6`, `7`, `8` + +@PAR@ monitor-prop bluez5.bap-server-capabilities.framelen_min # integer +Minimum number of octets supported per codec frame for the LC3 codec (default: 20). + +@PAR@ monitor-prop bluez5.bap-server-capabilities.framelen_max # integer +Maximum number of octets supported per codec frame for the LC3 codec (default: 400). + +@PAR@ monitor-prop bluez5.bap-server-capabilities.max_frames # integer +Maximum number of codec frames supported per SDU for the LC3 codec (default: 2). + ## Device properties @PAR@ device-prop bluez5.auto-connect # boolean diff --git a/doc/dox/config/xref.md b/doc/dox/config/xref.md index e5652fd8..507d09bf 100644 --- a/doc/dox/config/xref.md +++ b/doc/dox/config/xref.md @@ -8,7 +8,7 @@ @SECREF@ pipewire-pulse.conf -\ref page_man_pipewire-client_conf_5 "client.conf, client-rt.conf" +\ref page_man_pipewire-client_conf_5 "client.conf" @SECREF@ client.conf diff --git a/doc/dox/index.dox b/doc/dox/index.dox index 58a2b48b..6688ca16 100644 --- a/doc/dox/index.dox +++ b/doc/dox/index.dox @@ -40,7 +40,8 @@ See \ref page_api. # Resources - [PipeWire and AGL](https://wiki.automotivelinux.org/_media/pipewire_agl_20181206.pdf) -- [LAC 2020 Paper](https://lac2020.sciencesconf.org/307881/document) +- [LAC 2020 Paper](https://lac2020.sciencesconf.org//data/proceedings.pdf) and + [Video](https://tube.aquilenet.fr/w/uy8PJyMnBrpBFNEZ9D48Uu) - [PipeWire Under The Hood](https://venam.nixers.net/blog/unix/2021/06/23/pipewire-under-the-hood.html) - [PipeWire: The Linux audio/video bus (LWN)](https://lwn.net/Articles/847412) - [PipeWire Wikipedia](https://en.wikipedia.org/wiki/PipeWire) diff --git a/doc/dox/internals/midi.dox b/doc/dox/internals/midi.dox index 77c2b27a..12283004 100644 --- a/doc/dox/internals/midi.dox +++ b/doc/dox/internals/midi.dox @@ -58,6 +58,10 @@ Since the MIDI events are embedded in the generic control stream, they can be interleaved with other control message types, such as property updates or OSC messages. +As of 1.4, SPA_CONTROL_UMP (Universal Midi Packet) is the prefered format +for the MIDI 1.0 and 2.0 messages in the \ref spa_pod_sequence. Conversion +to SPA_CONTROL_Midi is performed for legacy applications. + ## The PipeWire Daemon Nothing special is implemented for MIDI. Negotiation of formats @@ -78,9 +82,9 @@ in order to route MIDI streams to them from applications that want this. # Implementation -## PipeWire Media Session +## Session manager (Wireplumber) -PipeWire media session uses the \ref SPA_NAME_API_ALSA_SEQ_BRIDGE plugin for +The session manager uses the \ref SPA_NAME_API_ALSA_SEQ_BRIDGE plugin for the MIDI features. This creates a single SPA Node with ports per MIDI client/stream. @@ -93,8 +97,16 @@ until the sequencer device node is accessible. JACK assumes all `"application/control"` ports are MIDI ports. The control messages are converted to the JACK event format by -filtering out the \ref SPA_CONTROL_Midi types. On output ports, the JACK -event stream is converted to control messages in a similar way. +filtering out the \ref SPA_CONTROL_Midi, \ref SPA_CONTROL_OSC and +\ref SPA_CONTROL_UMP types. On output ports, the JACK event stream is +converted to control messages in a similar way. + +Normally, all MIDI and UMP messages are converted to MIDI1 jack events unless +the JACK port was created with an explcit "32 bits raw UMP" format, in which +case the raw UMP is passed to the JACK application directly. For output ports, +the JACK events are assumed to be MIDI1 and converted to UMP unless the port +has the "32 bit raw UMP" format, in which case the UMP messages are simply +passed on. There is a 1 to 1 mapping between the JACK events and control messages so there is no information loss or need for complicated diff --git a/doc/dox/modules.dox b/doc/dox/modules.dox index cef6100b..4e935819 100644 --- a/doc/dox/modules.dox +++ b/doc/dox/modules.dox @@ -86,6 +86,10 @@ List of known modules: - \subpage page_module_rtp_source - \subpage page_module_rtp_session - \subpage page_module_rt +- \subpage page_module_spa_node +- \subpage page_module_spa_node_factory +- \subpage page_module_spa_device +- \subpage page_module_spa_device_factory - \subpage page_module_session_manager - \subpage page_module_snapcast_discover - \subpage page_module_vban_recv diff --git a/doc/dox/programs/pipewire.1.md b/doc/dox/programs/pipewire.1.md index aaf089e4..43f03e3f 100644 --- a/doc/dox/programs/pipewire.1.md +++ b/doc/dox/programs/pipewire.1.md @@ -154,6 +154,10 @@ systemd. @PAR@ pipewire-env PIPEWIRE_LOG_LINE Enables the logging of line numbers. Default true. +@PAR@ pipewire-env PIPEWIRE_LOG_TIMESTAMP +Logging timestamp type: "local", "monotonic", "realtime", "none". +Default "local". + @PAR@ pipewire-env PIPEWIRE_LOG Specifies a log file to use instead of the default logger. diff --git a/doc/dox/programs/pw-loopback.1.md b/doc/dox/programs/pw-loopback.1.md index 98293430..dea34339 100644 --- a/doc/dox/programs/pw-loopback.1.md +++ b/doc/dox/programs/pw-loopback.1.md @@ -45,10 +45,10 @@ Target device to capture from \par -P | \--playback=TARGET Target device to play to -\par \--capture-props=PROPS +\par -i | \--capture-props=PROPS Wanted properties of capture node (in JSON) -\par \--playback-props=PROPS +\par -o | \--playback-props=PROPS Wanted properties of capture node (in JSON) # AUTHORS diff --git a/doc/dox/programs/spa-acp-tool.1.md b/doc/dox/programs/spa-acp-tool.1.md index b77f144d..402a606f 100644 --- a/doc/dox/programs/spa-acp-tool.1.md +++ b/doc/dox/programs/spa-acp-tool.1.md @@ -4,7 +4,7 @@ The PipeWire ALSA profile debugging utility # SYNOPSIS -**spa-acp-tool** \[*COMMAND*\] +**spa-acp-tool** \[*OPTIONS*\] \[*COMMAND*\] # DESCRIPTION @@ -14,6 +14,20 @@ running PipeWire. May be used to debug problems where PipeWire has incorrectly functioning ALSA card profiles. +# OPTIONS + +\par -h | \--help +Show help + +\par -v | \--verbose +Increase verbosity by one level + +\par -c NUMBER | \--card NUMBER +Select which card to probe + +\par -p | \--properties +Additional properties to pass to ACP, e.g. `key=value ...`. + # COMMANDS \par help | h diff --git a/meson.build b/meson.build index be8ce5d1..3dfdc184 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('pipewire', ['c' ], - version : '1.2.7', + version : '1.4.0', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.61.1', default_options : [ 'warning_level=3', @@ -33,6 +33,9 @@ jack_version_minor = libversion_minor # libjack[server] version has 0 for major (for compatibility with other implementations), # 3 for minor, and "1000*major + 100*minor + micro" as micro version (the minor libpipewire soversion number) libjackversion = '@0@.@1@.@2@'.format(soversion, jack_version_major, jack_version_minor) +# jack[server] version has 3 for major +# and pipewire's "1000*major + 100*minor + micro" as minor version +jackversion = '@0@.@1@.@2@'.format(jack_version_major, jack_version_minor, 0) pipewire_name = 'pipewire-@0@'.format(apiversion) spa_name = 'spa-@0@'.format(spaversion) @@ -50,6 +53,7 @@ pipewire_confdatadir = pipewire_datadir / 'pipewire' modules_install_dir = pipewire_libdir / pipewire_name cc = meson.get_compiler('c') +cc_native = meson.get_compiler('c', native: true) if cc.has_header('features.h') and cc.get_define('__GLIBC__', prefix: '#include <features.h>') != '' # glibc ld.so interprets ${LIB} in a library loading path with an @@ -81,7 +85,8 @@ common_flags = [ '-Wsign-compare', '-Wpointer-arith', '-Wpointer-sign', - '-Wformat', + '-Werror=format', + '-Wno-error=format-overflow', # avoid some "‘%s’ directive argument is null" '-Wformat-security', '-Wimplicit-fallthrough', '-Wmissing-braces', @@ -95,6 +100,7 @@ common_flags = [ '-Wunused-result', '-Werror=return-type', '-Werror=float-conversion', + '-Werror=constant-conversion', ] cc_flags = common_flags + [ @@ -111,6 +117,8 @@ cc_flags = common_flags + [ ] add_project_arguments(cc.get_supported_arguments(cc_flags), language: 'c') +cc_flags_native = cc_native.get_supported_arguments(cc_flags) + have_cpp = add_languages('cpp', native: false, required : false) if have_cpp @@ -170,6 +178,20 @@ elif cc.has_argument('-mfpu=neon') endif endif +have_rvv = false +if host_machine.cpu_family() == 'riscv64' + if cc.compiles(''' + int main() { + __asm__ __volatile__ ( + ".option arch, +v\nvsetivli zero, 0, e8, m1, ta, ma" + ); + } + ''', + name : 'riscv64 V Support') + have_rvv = true + endif +endif + libatomic = cc.find_library('atomic', required : false) test_8_byte_atomic = ''' @@ -230,6 +252,7 @@ if host_machine.endian() == 'big' endif check_headers = [ + ['sys/auxv.h', 'HAVE_SYS_AUXV_H'], ['sys/mount.h', 'HAVE_SYS_MOUNT_H'], ['sys/param.h', 'HAVE_SYS_PARAM_H'], ['sys/random.h', 'HAVE_SYS_RANDOM_H'], @@ -279,6 +302,7 @@ configure_file(input : 'Makefile.in', # Find dependencies mathlib = cc.find_library('m', required : false) +mathlib_native = cc_native.find_library('m', required : false) rt_lib = cc.find_library('rt', required : false) # clock_gettime dl_lib = cc.find_library('dl', required : false) pthread_lib = dependency('threads') @@ -288,6 +312,9 @@ cdata.set('HAVE_DBUS', dbus_dep.found()) sdl_dep = dependency('sdl2', required : get_option('sdl2')) summary({'SDL2 (video examples)': sdl_dep.found()}, bool_yn: true, section: 'Misc dependencies') drm_dep = dependency('libdrm', required : false) +fftw_dep = dependency('fftw3f', required : false) +summary({'fftw3f (filter-chain convolver)': fftw_dep.found()}, bool_yn: true, section: 'Misc dependencies') +cdata.set('HAVE_FFTW', fftw_dep.found()) if get_option('readline').disabled() readline_dep = dependency('', required: false) @@ -306,7 +333,8 @@ ffmpeg = get_option('ffmpeg') if pw_cat_ffmpeg.allowed() or ffmpeg.allowed() avcodec_dep = dependency('libavcodec', required: pw_cat_ffmpeg.enabled() or ffmpeg.enabled()) avformat_dep = dependency('libavformat', required: pw_cat_ffmpeg.enabled()) - avutil_dep = dependency('libavutil', required: pw_cat_ffmpeg.enabled()) + avutil_dep = dependency('libavutil', required: pw_cat_ffmpeg.enabled() or ffmpeg.enabled()) + swscale_dep = dependency('libswscale', required: pw_cat_ffmpeg.enabled() or ffmpeg.enabled()) else avcodec_dep = dependency('', required: false) endif @@ -322,8 +350,6 @@ 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') cdata.set('HAVE_SNDFILE', sndfile_dep.found()) -libmysofa_dep = dependency('libmysofa', required : get_option('libmysofa')) -summary({'libmysofa': libmysofa_dep.found()}, bool_yn: true, section: 'filter-chain') pulseaudio_dep = dependency('libpulse', required : get_option('libpulse')) summary({'libpulse': pulseaudio_dep.found()}, bool_yn: true, section: 'Streaming between daemons') avahi_dep = dependency('avahi-client', required : get_option('avahi')) @@ -404,20 +430,37 @@ cdata.set('HAVE_GSTREAMER_DMA_DRM', gst_dma_drm_found) if get_option('echo-cancel-webrtc').disabled() webrtc_dep = dependency('', required: false) - summary({'WebRTC Echo Canceling >= 1.2': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies') + summary({'WebRTC Echo Canceling': webrtc_dep.found()}, bool_yn: false, section: 'Misc dependencies') else - webrtc_dep = dependency('webrtc-audio-processing-1', - version : ['>= 1.2' ], + webrtc_dep = dependency('webrtc-audio-processing-2', + version : ['>= 2.0' ], required : false) - cdata.set('HAVE_WEBRTC1', webrtc_dep.found()) + cdata.set('HAVE_WEBRTC2', webrtc_dep.found()) if webrtc_dep.found() - summary({'WebRTC Echo Canceling >= 1.2': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies') + summary({'WebRTC Echo Canceling >= 2.0': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies') else - webrtc_dep = dependency('webrtc-audio-processing', - version : ['>= 0.2', '< 1.0'], - required : get_option('echo-cancel-webrtc')) - cdata.set('HAVE_WEBRTC', webrtc_dep.found()) - summary({'WebRTC Echo Canceling < 1.0': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies') + webrtc_dep = dependency('webrtc-audio-processing-1', + version : ['>= 1.2' ], + required : false) + cdata.set('HAVE_WEBRTC1', webrtc_dep.found()) + if webrtc_dep.found() + summary({'WebRTC Echo Canceling >= 1.2': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies') + else + webrtc_dep = dependency('webrtc-audio-processing', + version : ['>= 0.2', '< 1.0'], + required : false) + cdata.set('HAVE_WEBRTC', webrtc_dep.found()) + if webrtc_dep.found() + summary({'WebRTC Echo Canceling < 1.0': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies') + else + # If deps are not found on the system but it's enabled, try to fallback to the subproject + webrtc_dep = dependency('webrtc-audio-processing-2', + version : ['>= 2.0' ], + required : get_option('echo-cancel-webrtc')) + cdata.set('HAVE_WEBRTC2', webrtc_dep.found()) + summary({'WebRTC Echo Canceling > 2.0': webrtc_dep.found()}, bool_yn: true, section: 'Misc dependencies') + endif + endif endif endif @@ -438,7 +481,7 @@ endif summary({'intl support': libintl_dep.found()}, bool_yn: true) need_alsa = get_option('pipewire-alsa').enabled() or 'media-session' in get_option('session-managers') -alsa_dep = dependency('alsa', version : '>=1.1.7', required: need_alsa) +alsa_dep = dependency('alsa', version : '>=1.2.10', required: need_alsa) summary({'pipewire-alsa': alsa_dep.found()}, bool_yn: true) if host_machine.system() == 'freebsd' or host_machine.system() == 'midnightbsd' @@ -453,9 +496,6 @@ else endif summary({'OpenSSL (for raop-sink)': openssl_lib.found()}, bool_yn: true) -lilv_lib = dependency('lilv-0', required: get_option('lv2')) -summary({'lilv (for lv2 plugins)': lilv_lib.found()}, bool_yn: true) - libffado_dep = dependency('libffado', required: get_option('libffado')) summary({'ffado': libffado_dep.found()}, bool_yn: true) glib2_snap_dep = dependency('glib-2.0', required : get_option('snap')) @@ -565,13 +605,12 @@ devenv.set('PIPEWIRE_MODULE_DIR', pipewire_dep.get_variable('moduledir')) devenv.set('SPA_PLUGIN_DIR', spa_dep.get_variable('plugindir')) devenv.set('SPA_DATA_DIR', spa_dep.get_variable('datadir')) -devenv.set('GST_PLUGIN_PATH', builddir / 'src'/ 'gst') - -devenv.set('ALSA_PLUGIN_DIR', builddir / 'pipewire-alsa' / 'alsa-plugins') devenv.set('ACP_PATHS_DIR', srcdir / 'spa' / 'plugins' / 'alsa' / 'mixer' / 'paths') devenv.set('ACP_PROFILES_DIR', srcdir / 'spa' / 'plugins' / 'alsa' / 'mixer' / 'profile-sets') -devenv.set('LD_LIBRARY_PATH', builddir / 'pipewire-jack' / 'src') +devenv.prepend('GST_PLUGIN_PATH', builddir / 'src'/ 'gst') +devenv.prepend('ALSA_PLUGIN_DIR', builddir / 'pipewire-alsa' / 'alsa-plugins') +devenv.prepend('LD_LIBRARY_PATH', builddir / 'pipewire-jack' / 'src') devenv.set('PW_UNINSTALLED', '1') diff --git a/meson_options.txt b/meson_options.txt index 94761f14..dc1b339f 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -93,6 +93,10 @@ option('audioconvert', description: 'Enable audioconvert spa plugin integration', type: 'feature', value: 'enabled') +option('resampler-precomp-tuples', + description: 'Array of "inrate,outrate[,quality]" tuples to precompute resampler coefficients for', + type: 'array', + value: [ '32000,44100', '32000,48000', '48000,44100', '44100,48000' ]) option('bluez5', description: 'Enable bluez5 spa plugin integration', type: 'feature', @@ -141,6 +145,10 @@ option('bluez5-codec-lc3', description: 'Enable LC3 open source codec implementation', type: 'feature', value: 'auto') +option('bluez5-codec-g722', + description: 'Enable G722 open source codec implementation', + type: 'feature', + value: 'auto') option('control', description: 'Enable control spa plugin integration', type: 'feature', @@ -367,3 +375,7 @@ option('doc-sysconfdir-value', description : 'Sysconf data directory to show in documentation instead of the actual value.', type : 'string', value : '') +option('ebur128', + description: 'Enable code that depends on ebur128', + type: 'feature', + value: 'auto') diff --git a/pipewire-alsa/alsa-plugins/ctl_pipewire.c b/pipewire-alsa/alsa-plugins/ctl_pipewire.c index 2a54a0dd..7fcfd572 100644 --- a/pipewire-alsa/alsa-plugins/ctl_pipewire.c +++ b/pipewire-alsa/alsa-plugins/ctl_pipewire.c @@ -1019,29 +1019,6 @@ static const struct global_info node_info = { }; /** metadata */ -static int json_object_find(const char *obj, const char *key, char *value, size_t len) -{ - struct spa_json it[2]; - const char *v; - char k[128]; - - spa_json_init(&it[0], obj, strlen(obj)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) - return -EINVAL; - - while (spa_json_get_string(&it[1], k, sizeof(k)) > 0) { - if (spa_streq(k, key)) { - if (spa_json_get_string(&it[1], value, len) <= 0) - continue; - return 0; - } else { - if (spa_json_next(&it[1], &v) <= 0) - break; - } - } - return -ENOENT; -} - static int metadata_property(void *data, uint32_t subject, const char *key, @@ -1054,14 +1031,14 @@ static int metadata_property(void *data, if (subject == PW_ID_CORE) { if (key == NULL || spa_streq(key, "default.audio.sink")) { if (value == NULL || - json_object_find(value, "name", + spa_json_str_object_find(value, strlen(value), "name", ctl->default_sink, sizeof(ctl->default_sink)) < 0) ctl->default_sink[0] = '\0'; pw_log_debug("found default sink: %s", ctl->default_sink); } if (key == NULL || spa_streq(key, "default.audio.source")) { if (value == NULL || - json_object_find(value, "name", + spa_json_str_object_find(value, strlen(value), "name", ctl->default_source, sizeof(ctl->default_source)) < 0) ctl->default_source[0] = '\0'; pw_log_debug("found default source: %s", ctl->default_source); @@ -1367,7 +1344,6 @@ SND_CTL_PLUGIN_DEFINE_FUNC(pipewire) ctl->context = pw_context_new(loop, pw_properties_new( PW_KEY_CLIENT_API, "alsa", - PW_KEY_CONFIG_NAME, "client-rt.conf", NULL), 0); if (ctl->context == NULL) { diff --git a/pipewire-alsa/alsa-plugins/pcm_pipewire.c b/pipewire-alsa/alsa-plugins/pcm_pipewire.c index 4ba25ee6..bd6836e5 100644 --- a/pipewire-alsa/alsa-plugins/pcm_pipewire.c +++ b/pipewire-alsa/alsa-plugins/pcm_pipewire.c @@ -5,9 +5,6 @@ #define __USE_GNU #include <limits.h> -#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) -#include <byteswap.h> -#endif #include <sys/shm.h> #include <sys/types.h> #include <sys/socket.h> @@ -20,6 +17,7 @@ #include <spa/debug/types.h> #include <spa/param/props.h> #include <spa/utils/atomic.h> +#include <spa/utils/endian.h> #include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/utils/json.h> @@ -83,7 +81,8 @@ typedef struct { int64_t now; uintptr_t seq; - struct spa_audio_info_raw format; + struct spa_audio_info requested; + struct spa_audio_info format; } snd_pcm_pipewire_t; static int snd_pcm_pipewire_stop(snd_pcm_ioplug_t *io); @@ -346,6 +345,25 @@ static void on_stream_param_changed(void *data, uint32_t id, const struct spa_po if (param == NULL || id != SPA_PARAM_Format) return; + if (spa_format_audio_parse(param, &pw->format) < 0) { + pw->error = -EINVAL; + } else { + switch (pw->format.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + break; + case SPA_MEDIA_SUBTYPE_dsd: + if (pw->format.info.dsd.interleave != pw->requested.info.dsd.interleave || + pw->format.info.dsd.bitorder != pw->requested.info.dsd.bitorder) { + pw->error = -EINVAL; + } + break; + } + } + if (pw->error < 0) { + pw_thread_loop_signal(pw->main_loop, false); + return; + } + io->period_size = pw->min_avail; buffers = SPA_CLAMP(io->buffer_size / io->period_size, MIN_BUFFERS, MAX_BUFFERS); @@ -373,7 +391,7 @@ static void on_stream_state_changed(void *data, enum pw_stream_state old, enum p if (state == PW_STREAM_STATE_ERROR) { pw_log_warn("%s", error); - pw->error = -EIO; + pw->error = -errno; update_active(&pw->io); } } @@ -527,7 +545,7 @@ static int snd_pcm_pipewire_prepare(snd_pcm_ioplug_t *io) pw_properties_setf(pw->props, PW_KEY_NODE_LATENCY, "%lu/%u", pw->min_avail, io->rate); pw_properties_setf(pw->props, PW_KEY_NODE_RATE, "1/%u", io->rate); - params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &pw->format); + params[0] = spa_format_audio_build(&b, SPA_PARAM_EnumFormat, &pw->format); if (pw->stream != NULL) { pw_stream_set_active(pw->stream, false); @@ -624,29 +642,29 @@ static int snd_pcm_pipewire_pause(snd_pcm_ioplug_t * io, int enable) #define _FORMAT_BE(p, fmt) p ? SPA_AUDIO_FORMAT_UNKNOWN : SPA_AUDIO_FORMAT_ ## fmt ## _OE #endif -static int set_default_channels(struct spa_audio_info_raw *info) +static int set_default_channels(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS]) { - switch (info->channels) { + switch (channels) { case 8: - info->position[6] = SPA_AUDIO_CHANNEL_SL; - info->position[7] = SPA_AUDIO_CHANNEL_SR; + position[6] = SPA_AUDIO_CHANNEL_SL; + position[7] = SPA_AUDIO_CHANNEL_SR; SPA_FALLTHROUGH case 6: - info->position[5] = SPA_AUDIO_CHANNEL_LFE; + position[5] = SPA_AUDIO_CHANNEL_LFE; SPA_FALLTHROUGH case 5: - info->position[4] = SPA_AUDIO_CHANNEL_FC; + position[4] = SPA_AUDIO_CHANNEL_FC; SPA_FALLTHROUGH case 4: - info->position[2] = SPA_AUDIO_CHANNEL_RL; - info->position[3] = SPA_AUDIO_CHANNEL_RR; + position[2] = SPA_AUDIO_CHANNEL_RL; + position[3] = SPA_AUDIO_CHANNEL_RR; SPA_FALLTHROUGH case 2: - info->position[0] = SPA_AUDIO_CHANNEL_FL; - info->position[1] = SPA_AUDIO_CHANNEL_FR; + position[0] = SPA_AUDIO_CHANNEL_FL; + position[1] = SPA_AUDIO_CHANNEL_FR; return 1; case 1: - info->position[0] = SPA_AUDIO_CHANNEL_MONO; + position[0] = SPA_AUDIO_CHANNEL_MONO; return 1; default: return 0; @@ -658,6 +676,7 @@ static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io, { snd_pcm_pipewire_t *pw = io->private_data; bool planar; + const char *fmt_str = NULL; snd_pcm_hw_params_dump(params, pw->output); fflush(pw->log_file); @@ -678,48 +697,98 @@ static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io, return -EINVAL; } + pw->requested.media_type = SPA_MEDIA_TYPE_audio; switch(io->format) { case SND_PCM_FORMAT_U8: - pw->format.format = planar ? SPA_AUDIO_FORMAT_U8P : SPA_AUDIO_FORMAT_U8; + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; + pw->requested.info.raw.format = planar ? SPA_AUDIO_FORMAT_U8P : SPA_AUDIO_FORMAT_U8; break; case SND_PCM_FORMAT_S16_LE: - pw->format.format = _FORMAT_LE(planar, S16); + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; + pw->requested.info.raw.format = _FORMAT_LE(planar, S16); break; case SND_PCM_FORMAT_S16_BE: - pw->format.format = _FORMAT_BE(planar, S16); + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; + pw->requested.info.raw.format = _FORMAT_BE(planar, S16); break; case SND_PCM_FORMAT_S24_LE: - pw->format.format = _FORMAT_LE(planar, S24_32); + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; + pw->requested.info.raw.format = _FORMAT_LE(planar, S24_32); break; case SND_PCM_FORMAT_S24_BE: - pw->format.format = _FORMAT_BE(planar, S24_32); + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; + pw->requested.info.raw.format = _FORMAT_BE(planar, S24_32); break; case SND_PCM_FORMAT_S32_LE: - pw->format.format = _FORMAT_LE(planar, S32); + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; + pw->requested.info.raw.format = _FORMAT_LE(planar, S32); break; case SND_PCM_FORMAT_S32_BE: - pw->format.format = _FORMAT_BE(planar, S32); + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; + pw->requested.info.raw.format = _FORMAT_BE(planar, S32); break; case SND_PCM_FORMAT_S24_3LE: - pw->format.format = _FORMAT_LE(planar, S24); + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; + pw->requested.info.raw.format = _FORMAT_LE(planar, S24); break; case SND_PCM_FORMAT_S24_3BE: - pw->format.format = _FORMAT_BE(planar, S24); + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; + pw->requested.info.raw.format = _FORMAT_BE(planar, S24); break; case SND_PCM_FORMAT_FLOAT_LE: - pw->format.format = _FORMAT_LE(planar, F32); + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; + pw->requested.info.raw.format = _FORMAT_LE(planar, F32); break; case SND_PCM_FORMAT_FLOAT_BE: - pw->format.format = _FORMAT_BE(planar, F32); + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_raw; + pw->requested.info.raw.format = _FORMAT_BE(planar, F32); + break; + case SND_PCM_FORMAT_DSD_U32_BE: + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd; + pw->requested.info.dsd.interleave = 4; + break; + case SND_PCM_FORMAT_DSD_U32_LE: + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd; + pw->requested.info.dsd.interleave = -4; + break; + case SND_PCM_FORMAT_DSD_U16_BE: + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd; + pw->requested.info.dsd.interleave = 2; + break; + case SND_PCM_FORMAT_DSD_U16_LE: + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd; + pw->requested.info.dsd.interleave = -2; + break; + case SND_PCM_FORMAT_DSD_U8: + pw->requested.media_subtype = SPA_MEDIA_SUBTYPE_dsd; + pw->requested.info.dsd.interleave = 1; break; default: SNDERR("PipeWire: invalid format: %d\n", io->format); return -EINVAL; } - pw->format.channels = io->channels; - pw->format.rate = io->rate; - - set_default_channels(&pw->format); + switch (pw->requested.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + pw->requested.info.raw.channels = io->channels; + pw->requested.info.raw.rate = io->rate; + set_default_channels(io->channels, pw->requested.info.raw.position); + fmt_str = spa_type_audio_format_to_short_name(pw->requested.info.raw.format); + pw->format = pw->requested; + break; + case SPA_MEDIA_SUBTYPE_dsd: + pw->requested.info.dsd.bitorder = SPA_PARAM_BITORDER_msb; + pw->requested.info.dsd.channels = io->channels; + pw->requested.info.dsd.rate = io->rate * SPA_ABS(pw->requested.info.dsd.interleave); + set_default_channels(io->channels, pw->requested.info.dsd.position); + pw->format = pw->requested; + /* we need to let the server decide these values */ + pw->format.info.dsd.bitorder = 0; + pw->format.info.dsd.interleave = 0; + fmt_str = "DSD"; + break; + default: + return -EIO; + } pw->sample_bits = snd_pcm_format_physical_width(io->format); if (planar) { @@ -730,8 +799,7 @@ static int snd_pcm_pipewire_hw_params(snd_pcm_ioplug_t * io, pw->stride = (io->channels * pw->sample_bits) / 8; } pw->hw_params_changed = true; - pw_log_info("%p: format:%s channels:%d rate:%d stride:%d blocks:%d", pw, - spa_debug_type_find_name(spa_type_audio_format, pw->format.format), + pw_log_info("%p: format:%s channels:%d rate:%d stride:%d blocks:%d", pw, fmt_str, io->channels, io->rate, pw->stride, pw->blocks); return 0; @@ -833,14 +901,26 @@ static int snd_pcm_pipewire_set_chmap(snd_pcm_ioplug_t * io, { snd_pcm_pipewire_t *pw = io->private_data; unsigned int i; + uint32_t *position; - pw->format.channels = map->channels; + switch (pw->requested.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + pw->requested.info.raw.channels = map->channels; + position = pw->requested.info.raw.position; + break; + case SPA_MEDIA_SUBTYPE_dsd: + pw->requested.info.dsd.channels = map->channels; + position = pw->requested.info.dsd.position; + break; + default: + return -EINVAL; + } for (i = 0; i < map->channels; i++) { - pw->format.position[i] = chmap_to_channel(map->pos[i]); + position[i] = chmap_to_channel(map->pos[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])); + position[i])); } return 1; } @@ -849,13 +929,26 @@ static snd_pcm_chmap_t * snd_pcm_pipewire_get_chmap(snd_pcm_ioplug_t * io) { snd_pcm_pipewire_t *pw = io->private_data; snd_pcm_chmap_t *map; - uint32_t i; + uint32_t i, channels, *position; + + switch (pw->requested.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + channels = pw->requested.info.raw.channels; + position = pw->requested.info.raw.position; + break; + case SPA_MEDIA_SUBTYPE_dsd: + channels = pw->requested.info.dsd.channels; + position = pw->requested.info.dsd.position; + break; + default: + return NULL; + } map = calloc(1, sizeof(snd_pcm_chmap_t) + - pw->format.channels * sizeof(unsigned int)); - map->channels = pw->format.channels; - for (i = 0; i < pw->format.channels; i++) - map->pos[i] = channel_to_chmap(pw->format.position[i]); + channels * sizeof(unsigned int)); + map->channels = channels; + for (i = 0; i < channels; i++) + map->pos[i] = channel_to_chmap(position[i]); return map; } @@ -991,7 +1084,16 @@ struct param_info infos[] = { SND_PCM_FORMAT_S24_3BE, SND_PCM_FORMAT_S16_BE, #endif - SND_PCM_FORMAT_U8 }, 7, collect_format }, + SND_PCM_FORMAT_U8, + /* we don't add DSD formats here, use alsa.formats to + * force this. Because we can't convert to/from DSD, enabling this + * might fail when the system has no native DSD + * SND_PCM_FORMAT_DSD_U32_BE, + * SND_PCM_FORMAT_DSD_U32_LE, + * SND_PCM_FORMAT_DSD_U16_BE, + * SND_PCM_FORMAT_DSD_U16_LE, + * SND_PCM_FORMAT_DSD_U8 */ + }, 7, collect_format }, { "alsa.rate", SND_PCM_IOPLUG_HW_RATE, TYPE_MIN_MAX, { 1, MAX_RATE }, 2, collect_int }, { "alsa.channels", SND_PCM_IOPLUG_HW_CHANNELS, TYPE_MIN_MAX, @@ -1020,8 +1122,7 @@ static int parse_value(const char *str, struct param_info *info) const char *val; int len; - spa_json_init(&it[0], str, strlen(str)); - if ((len = spa_json_next(&it[0], &val)) <= 0) + if ((len = spa_json_begin(&it[0], str, strlen(str), &val)) <= 0) return -EINVAL; if (spa_json_is_array(val, len)) { @@ -1039,9 +1140,7 @@ static int parse_value(const char *str, struct param_info *info) info->type = TYPE_MIN_MAX; info->n_vals = 2; spa_json_enter(&it[0], &it[1]); - while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { - if ((len = spa_json_next(&it[1], &val)) <= 0) - break; + while ((len = spa_json_object_next(&it[1], key, sizeof(key), &val)) > 0) { if (info->collect(val, len, &v) < 0) continue; if (spa_streq(key, "min")) @@ -1189,7 +1288,6 @@ static int snd_pcm_pipewire_open(snd_pcm_t **pcmp, pw->system = loop->system; if ((pw->context = pw_context_new(loop, pw_properties_new( - PW_KEY_CONFIG_NAME, "client-rt.conf", PW_KEY_CLIENT_API, "alsa", NULL), 0)) == NULL) { diff --git a/pipewire-jack/examples/ump-source.c b/pipewire-jack/examples/ump-source.c new file mode 100644 index 00000000..274d7248 --- /dev/null +++ b/pipewire-jack/examples/ump-source.c @@ -0,0 +1,114 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/mman.h> + +#include <jack/jack.h> +#include <jack/midiport.h> + +#include <pipewire-jack-extensions.h> + +#define MAX_BUFFERS 64 + +struct data { + const char *path; + + jack_client_t *client; + const char *client_name; + jack_port_t *out_port; + + int cycle; + uint64_t position; + uint64_t next_sample; + uint64_t period; +}; + +static int +process (jack_nframes_t nframes, void *arg) +{ + struct data *d = (struct data*)arg; + void *buf; + uint32_t event[2]; + + buf = jack_port_get_buffer (d->out_port, nframes); + jack_midi_clear_buffer(buf); + + while (d->position >= d->next_sample && d->position + nframes > d->next_sample) { + uint64_t pos = d->position - d->next_sample; + + if (d->cycle == 0) { + /* MIDI 2.0 note on, channel 0, middle C, max velocity, no attribute */ + event[0] = 0x40903c00; + event[1] = 0xffff0000; + } else { + /* MIDI 2.0 note off, channel 0, middle C, max velocity, no attribute */ + event[0] = 0x40803c00; + event[1] = 0xffff0000; + } + + d->cycle ^= 1; + + jack_midi_event_write(buf, pos, (const jack_midi_data_t *) event, sizeof(event)); + + d->next_sample += d->period; + } + d->position += nframes; + return 0; +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0, }; + jack_options_t options = JackNullOption; + jack_status_t status; + + data.client = jack_client_open ("ump-source", options, &status); + if (data.client == NULL) { + fprintf (stderr, "jack_client_open() failed, " + "status = 0x%2.0x\n", status); + if (status & JackServerFailed) { + fprintf (stderr, "Unable to connect to JACK server\n"); + } + exit (1); + } + if (status & JackServerStarted) { + fprintf (stderr, "JACK server started\n"); + } + if (status & JackNameNotUnique) { + data.client_name = jack_get_client_name(data.client); + fprintf (stderr, "unique name `%s' assigned\n", data.client_name); + } + + /* send 2 events per second */ + data.period = jack_get_sample_rate(data.client) / 2; + + jack_set_process_callback (data.client, process, &data); + + /* the UMP port type allows both sending and receiving of UMP + * messages, which can contain MIDI 1.0 and MIDI 2.0 messages. */ + data.out_port = jack_port_register (data.client, "output", + JACK_DEFAULT_MIDI_TYPE, + JackPortIsOutput | JackPortIsMIDI2, 0); + + if (data.out_port == NULL) { + fprintf(stderr, "no more JACK ports available\n"); + exit (1); + } + + if (jack_activate (data.client)) { + fprintf (stderr, "cannot activate client"); + exit (1); + } + + while (1) { + sleep (1); + } + + jack_client_close (data.client); + + return 0; +} diff --git a/pipewire-jack/examples/video-dsp-play.c b/pipewire-jack/examples/video-dsp-play.c index 52891115..5bca72a4 100644 --- a/pipewire-jack/examples/video-dsp-play.c +++ b/pipewire-jack/examples/video-dsp-play.c @@ -2,7 +2,9 @@ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include <math.h> #include <stdio.h> +#include <string.h> #include <unistd.h> #include <sys/mman.h> diff --git a/pipewire-jack/jack/types.h b/pipewire-jack/jack/types.h index 00606438..206d7270 100644 --- a/pipewire-jack/jack/types.h +++ b/pipewire-jack/jack/types.h @@ -515,6 +515,29 @@ enum JackPortFlags { */ JackPortIsTerminal = 0x10, + /** + * if JackPortIsCV is set, then the port buffer represents audio-rate + * control data rather than audio. + * + * Clients SHOULD prevent connections between Audio and CV ports. + * + * To make the ports more meaningful, clients can add meta-data to them. + * It is recommended to set these 2 in particular: + * - http://lv2plug.in/ns/lv2core#minimum + * - http://lv2plug.in/ns/lv2core#maximum + */ + JackPortIsCV = 0x20, + + /** + * if JackPortIsMIDI2 is set, then the port expects to receive MIDI2 data. + * + * JACK will automatically convert MIDI1 data into MIDI2 for this port. + * + * for ports without this flag JACK will convert MIDI2 into MIDI1 + * as much possible, some events might be skipped. + */ + JackPortIsMIDI2 = 0x20, + }; /** diff --git a/pipewire-jack/src/control.c b/pipewire-jack/src/control.c index f86b0b55..8299e8a0 100644 --- a/pipewire-jack/src/control.c +++ b/pipewire-jack/src/control.c @@ -124,7 +124,7 @@ bool jackctl_server_stop(jackctl_server_t * server) { // stub pw_log_warn("%p: not implemented", server); - return false; + return true; } SPA_EXPORT @@ -132,7 +132,7 @@ bool jackctl_server_close(jackctl_server_t * server) { // stub pw_log_warn("%p: not implemented", server); - return false; + return true; } SPA_EXPORT diff --git a/pipewire-jack/src/meson.build b/pipewire-jack/src/meson.build index def7746a..0630d96a 100644 --- a/pipewire-jack/src/meson.build +++ b/pipewire-jack/src/meson.build @@ -88,9 +88,16 @@ if get_option('jack-devel') == true libraries : [pipewire_jack], name : 'jack', description : 'PipeWire JACK API', - version : '1.9.17', + version : jackversion, extra_cflags : '-D_REENTRANT', unescaped_variables: ['server_libs=-L${libdir} -ljackserver', 'jack_implementation=pipewire']) + + pkgconfig.generate(filebase : 'jackserver', + libraries : [pipewire_jackserver], + name : 'jackserver', + description : 'PipeWire JACK Control API', + version : jackversion, + unescaped_variables: ['jack_implementation=pipewire']) endif if sdl_dep.found() @@ -103,3 +110,11 @@ if sdl_dep.found() link_with: pipewire_jack, ) endif +executable('ump-source', + '../examples/ump-source.c', + include_directories : [jack_inc], + install : installed_tests_enabled, + install_dir : installed_tests_execdir / 'examples' / 'jack', + dependencies : [mathlib], + link_with: pipewire_jack, +) diff --git a/pipewire-jack/src/pipewire-jack-extensions.h b/pipewire-jack/src/pipewire-jack-extensions.h index 0caec40c..dc3b9f94 100644 --- a/pipewire-jack/src/pipewire-jack-extensions.h +++ b/pipewire-jack/src/pipewire-jack-extensions.h @@ -25,6 +25,13 @@ int jack_get_video_image_size(jack_client_t *client, jack_image_size_t *size); int jack_set_sample_rate (jack_client_t *client, jack_nframes_t nframes); +/* raw OSC message */ +#define JACK_DEFAULT_OSC_TYPE "8 bit raw OSC" + +/* MIDI 2.0 UMP type. This contains raw UMP data, which can have MIDI 1.0 or + * MIDI 2.0 packets. The data is an array of 32 bit ints. */ +#define JACK_DEFAULT_UMP_TYPE "32 bit raw UMP" + #ifdef __cplusplus } #endif diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 55c49425..839a9fc2 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -1,5 +1,6 @@ /* PipeWire */ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ +/* SPDX-FileCopyrightText: Copyright © 2024 Nedko Arnaudov */ /* SPDX-License-Identifier: MIT */ #include "config.h" @@ -29,6 +30,7 @@ #include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/utils/ringbuffer.h> +#include <spa/control/ump-utils.h> #include <pipewire/pipewire.h> #include <pipewire/private.h> @@ -39,8 +41,6 @@ #include "pipewire/extensions/metadata.h" #include "pipewire-jack-extensions.h" -#define JACK_DEFAULT_VIDEO_TYPE "32 bit float RGBA video" - /* use 512KB stack per thread - the default is way too high to be feasible * with mlockall() on many systems */ #define THREAD_STACK 524288 @@ -65,9 +65,16 @@ PW_LOG_TOPIC_STATIC(jack_log_topic, "jack"); #define PW_LOG_TOPIC_DEFAULT jack_log_topic #define TYPE_ID_AUDIO 0 -#define TYPE_ID_MIDI 1 -#define TYPE_ID_VIDEO 2 -#define TYPE_ID_OTHER 3 +#define TYPE_ID_VIDEO 1 +#define TYPE_ID_MIDI 2 +#define TYPE_ID_OSC 3 +#define TYPE_ID_UMP 4 +#define TYPE_ID_OTHER 5 + +#define TYPE_ID_IS_EVENT(t) ((t) >= TYPE_ID_MIDI && (t) <= TYPE_ID_UMP) +#define TYPE_ID_CAN_OSC(t) ((t) == TYPE_ID_MIDI || (t) == TYPE_ID_OSC) +#define TYPE_ID_IS_HIDDEN(t) ((t) >= TYPE_ID_OTHER) +#define TYPE_ID_IS_COMPATIBLE(a,b)(((a) == (b)) || (TYPE_ID_IS_EVENT(a) && TYPE_ID_IS_EVENT(b))) #define SELF_CONNECT_ALLOW 0 #define SELF_CONNECT_FAIL_EXT -1 @@ -134,11 +141,16 @@ struct object { #define INTERFACE_Port 1 #define INTERFACE_Node 2 #define INTERFACE_Link 3 +#define INTERFACE_Client 4 uint32_t type; uint32_t id; uint32_t serial; union { + struct { + char name[1024]; + int32_t pid; + } pwclient; struct { char name[JACK_CLIENT_NAME_SIZE+1]; char node_name[512]; @@ -460,6 +472,7 @@ struct client { unsigned int fill_aliases:1; unsigned int writable_input:1; unsigned int async:1; + unsigned int flag_midi2:1; uint32_t max_frames; uint32_t max_align; @@ -647,7 +660,7 @@ static struct mix *find_port_peer(struct port *port, uint32_t peer_id) { struct mix *mix; spa_list_for_each(mix, &port->mix, port_link) { - pw_log_info("%p %d %d", port, mix->peer_id, peer_id); + pw_log_trace("%p %d %d", port, mix->peer_id, peer_id); if (mix->peer_id == peer_id) return mix; } @@ -884,6 +897,11 @@ static struct object *find_type(struct client *c, uint32_t id, uint32_t type, bo return NULL; } +static struct object *find_client(struct client *c, uint32_t client_id) +{ + return find_type(c, client_id, INTERFACE_Client, false); +} + static struct object *find_link(struct client *c, uint32_t src, uint32_t dst) { struct object *l; @@ -943,11 +961,11 @@ void jack_get_version(int *major_ptr, int *minor_ptr, int *micro_ptr, int *proto if (major_ptr) *major_ptr = 3; if (minor_ptr) - *minor_ptr = 0; + *minor_ptr = PW_MAJOR; if (micro_ptr) - *micro_ptr = 0; + *micro_ptr = PW_MINOR; if (proto_ptr) - *proto_ptr = 0; + *proto_ptr = PW_MICRO; } #define do_callback_expr(c,expr,callback,do_emit,...) \ @@ -994,7 +1012,10 @@ const char * jack_get_version_string(void) { static char name[1024]; - snprintf(name, sizeof(name), "3.0.0.0 (using PipeWire %s)", pw_get_library_version()); + int major, minor, micro, proto; + jack_get_version(&major, &minor, µ, &proto); + snprintf(name, sizeof(name), "%d.%d.%d.%d (using PipeWire %s)", + major, minor, micro, proto, pw_get_library_version()); return name; } @@ -1078,7 +1099,7 @@ static void on_notify_event(void *data, uint64_t count) do_recompute_capture = do_recompute_playback = true; break; case NOTIFY_TYPE_BUFFER_FRAMES: - pw_log_debug("%p: buffer frames %d", c, notify->arg1); + pw_log_debug("%p: buffer frames %d -> %d", c, c->buffer_frames, notify->arg1); if (c->buffer_frames != (uint32_t)notify->arg1) { do_callback_expr(c, c->buffer_frames = notify->arg1, bufsize_callback, c->active, @@ -1087,7 +1108,7 @@ static void on_notify_event(void *data, uint64_t count) } break; case NOTIFY_TYPE_SAMPLE_RATE: - pw_log_debug("%p: sample rate %d", c, notify->arg1); + pw_log_debug("%p: sample rate %d -> %d", c, c->sample_rate, notify->arg1); if (c->sample_rate != (uint32_t)notify->arg1) { do_callback_expr(c, c->sample_rate = notify->arg1, srate_callback, c->active, @@ -1392,12 +1413,25 @@ static inline bool is_osc(jack_midi_event_t *ev) return ev->size >= 1 && (ev->buffer[0] == '#' || ev->buffer[0] == '/'); } -static size_t convert_from_midi(void *midi, void *buffer, size_t size) +static size_t convert_from_event(void *midi, void *buffer, size_t size, uint32_t type) { struct spa_pod_builder b = { 0, }; uint32_t i, count; struct spa_pod_frame f; + uint32_t event_type; + switch (type) { + case TYPE_ID_MIDI: + case TYPE_ID_OSC: + /* we handle MIDI as OSC, check below */ + event_type = SPA_CONTROL_OSC; + break; + case TYPE_ID_UMP: + event_type = SPA_CONTROL_UMP; + break; + default: + return 0; + } count = jack_midi_get_event_count(midi); spa_pod_builder_init(&b, buffer, size); @@ -1406,14 +1440,43 @@ static size_t convert_from_midi(void *midi, void *buffer, size_t size) for (i = 0; i < count; i++) { jack_midi_event_t ev; jack_midi_event_get(&ev, midi, i); - spa_pod_builder_control(&b, ev.time, - is_osc(&ev) ? SPA_CONTROL_OSC : SPA_CONTROL_Midi); - spa_pod_builder_bytes(&b, ev.buffer, ev.size); + + if (type != TYPE_ID_MIDI || is_osc(&ev)) { + /* no midi port or it's OSC */ + spa_pod_builder_control(&b, ev.time, event_type); + spa_pod_builder_bytes(&b, ev.buffer, ev.size); + } else { + /* midi port and it's not OSC, convert to UMP */ + uint8_t *data = ev.buffer; + size_t size = ev.size; + uint64_t state = 0; + + while (size > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&data, &size, + ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + break; + spa_pod_builder_control(&b, ev.time, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ump, ump_size); + } + } } spa_pod_builder_pop(&b, &f); return b.state.offset; } +static inline int event_compare(uint8_t s1, uint8_t s2) +{ + /* 11 (controller) > 12 (program change) > + * 8 (note off) > 9 (note on) > 10 (aftertouch) > + * 13 (channel pressure) > 14 (pitch bend) */ + static int priotab[] = { 5,4,3,7,6,2,1,0 }; + if ((s1 & 0xf) != (s2 & 0xf)) + return 0; + return priotab[(s2>>4) & 7] - priotab[(s1>>4) & 7]; +} + static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control *b) { if (a->offset < b->offset) @@ -1425,21 +1488,20 @@ static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control * switch(a->type) { case SPA_CONTROL_Midi: { - /* 11 (controller) > 12 (program change) > - * 8 (note off) > 9 (note on) > 10 (aftertouch) > - * 13 (channel pressure) > 14 (pitch bend) */ - static int priotab[] = { 5,4,3,7,6,2,1,0 }; - uint8_t *da, *db; - - if (SPA_POD_BODY_SIZE(&a->value) < 1 || - SPA_POD_BODY_SIZE(&b->value) < 1) + uint8_t *sa = SPA_POD_BODY(&a->value), *sb = SPA_POD_BODY(&b->value); + if (SPA_POD_BODY_SIZE(&a->value) < 1 || SPA_POD_BODY_SIZE(&b->value) < 1) return 0; - - da = SPA_POD_BODY(&a->value); - db = SPA_POD_BODY(&b->value); - if ((da[0] & 0xf) != (db[0] & 0xf)) + return event_compare(sa[0], sb[0]); + } + case SPA_CONTROL_UMP: + { + uint32_t *sa = SPA_POD_BODY(&a->value), *sb = SPA_POD_BODY(&b->value); + if (SPA_POD_BODY_SIZE(&a->value) < 4 || SPA_POD_BODY_SIZE(&b->value) < 4) return 0; - return priotab[(db[0]>>4) & 7] - priotab[(da[0]>>4) & 7]; + if ((sa[0] >> 28) != 2 || (sa[0] >> 28) != 4 || + (sb[0] >> 28) != 2 || (sb[0] >> 28) != 4) + return 0; + return event_compare(sa[0] >> 16, sb[0] >> 16); } default: return 0; @@ -1498,11 +1560,12 @@ static inline int midi_event_write(void *port_buffer, return 0; } -static void convert_to_midi(struct spa_pod_sequence **seq, uint32_t n_seq, void *midi, bool fix) +static void convert_to_event(struct spa_pod_sequence **seq, uint32_t n_seq, void *midi, bool fix, uint32_t type) { struct spa_pod_control *c[n_seq]; + uint64_t state = 0; uint32_t i; - int res; + int res = 0; for (i = 0; i < n_seq; i++) c[i] = spa_pod_control_first(&seq[i]->body); @@ -1526,16 +1589,51 @@ static void convert_to_midi(struct spa_pod_sequence **seq, uint32_t n_seq, void switch(next->type) { case SPA_CONTROL_OSC: + if (!TYPE_ID_CAN_OSC(type)) + break; + SPA_FALLTHROUGH; case SPA_CONTROL_Midi: { uint8_t *data = SPA_POD_BODY(&next->value); size_t size = SPA_POD_BODY_SIZE(&next->value); - if ((res = midi_event_write(midi, next->offset, data, size, fix)) < 0) + if (type == TYPE_ID_UMP) { + while (size > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&data, &size, ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + break; + if ((res = midi_event_write(midi, next->offset, + (uint8_t*)ump, ump_size, false)) < 0) + break; + } + } else { + res = midi_event_write(midi, next->offset, data, size, fix); + } + if (res < 0) pw_log_warn("midi %p: can't write event: %s", midi, spa_strerror(res)); break; } + case SPA_CONTROL_UMP: + { + void *data = SPA_POD_BODY(&next->value); + size_t size = SPA_POD_BODY_SIZE(&next->value); + uint8_t ev[32]; + + if (type == TYPE_ID_MIDI) { + int ev_size = spa_ump_to_midi(data, size, ev, sizeof(ev)); + if (ev_size <= 0) + break; + size = ev_size; + data = ev; + } else if (type != TYPE_ID_UMP) + break; + + if ((res = midi_event_write(midi, next->offset, data, size, fix)) < 0) + pw_log_warn("midi %p: can't write event: %s", midi, + spa_strerror(res)); + } } c[next_index] = spa_pod_control_next(c[next_index]); } @@ -1602,19 +1700,22 @@ static inline void process_empty(struct port *p, uint32_t frames) struct client *c = p->client; void *ptr, *src = p->emptyptr; struct port *tied = p->tied; + uint32_t type = p->object->port.type_id; if (SPA_UNLIKELY(tied != NULL)) { if ((src = tied->get_buffer(tied, frames)) == NULL) src = p->emptyptr; } - switch (p->object->port.type_id) { + switch (type) { case TYPE_ID_AUDIO: ptr = get_buffer_output(p, frames, sizeof(float), NULL); if (SPA_LIKELY(ptr != NULL)) memcpy(ptr, src, frames * sizeof(float)); break; case TYPE_ID_MIDI: + case TYPE_ID_OSC: + case TYPE_ID_UMP: { struct buffer *b; ptr = get_buffer_output(p, c->max_frames, 1, &b); @@ -1622,8 +1723,8 @@ static inline void process_empty(struct port *p, uint32_t frames) /* first build the complete pod in scratch memory, then copy it * to the target buffer. This makes it possible for multiple threads * to do this concurrently */ - b->datas[0].chunk->size = convert_from_midi(src, - midi_scratch, MIDI_SCRATCH_FRAMES * sizeof(float)); + b->datas[0].chunk->size = convert_from_event(src, midi_scratch, + MIDI_SCRATCH_FRAMES * sizeof(float), type); memcpy(ptr, midi_scratch, b->datas[0].chunk->size); } break; @@ -2132,7 +2233,7 @@ static int client_node_set_param(void *data, const struct spa_pod *param) { struct client *c = (struct client *) data; - pw_proxy_error((struct pw_proxy*)c->node, -ENOTSUP, "set_param: not supported"); + pw_proxy_error((struct pw_proxy*)c->node, -ENOTSUP, "not supported"); return -ENOTSUP; } @@ -2408,6 +2509,8 @@ static int param_enum_format(struct client *c, struct port *p, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp), SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_DSP_F32)); break; + case TYPE_ID_UMP: + case TYPE_ID_OSC: case TYPE_ID_MIDI: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, @@ -2439,6 +2542,8 @@ static int param_format(struct client *c, struct port *p, SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_DSP_F32)); break; case TYPE_ID_MIDI: + case TYPE_ID_OSC: + case TYPE_ID_UMP: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_Format, SPA_PARAM_Format, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), @@ -2463,6 +2568,8 @@ static int param_buffers(struct client *c, struct port *p, switch (p->object->port.type_id) { case TYPE_ID_AUDIO: case TYPE_ID_MIDI: + case TYPE_ID_OSC: + case TYPE_ID_UMP: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, MAX_BUFFERS), @@ -2584,7 +2691,7 @@ static int port_set_format(struct client *c, struct port *p, p->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE); } - pw_log_info("port %s: update", p->object->port.name); + pw_log_debug("port %s: update", p->object->port.name); p->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; @@ -2624,7 +2731,7 @@ static void port_update_latency(struct port *p) param_latency(c, p, ¶ms[5], &b); param_latency_other(c, p, ¶ms[6], &b); - pw_log_info("port %s: update", p->object->port.name); + pw_log_debug("port %s: update", p->object->port.name); p->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; p->params[IDX_Latency].flags ^= SPA_PARAM_INFO_SERIAL; @@ -2796,7 +2903,7 @@ static inline void *init_buffer(struct port *p, uint32_t nframes) if (p->zeroed) return data; - if (p->object->port.type_id == TYPE_ID_MIDI) { + if (TYPE_ID_IS_EVENT(p->object->port.type_id)) { struct midi_buffer *mb = data; midi_init_buffer(data, c->max_frames, nframes); pw_log_debug("port %p: init midi buffer size:%d frames:%d", p, @@ -2837,7 +2944,8 @@ static int client_node_port_use_buffers(void *data, if (n_buffers > MAX_BUFFERS) { pw_log_error("%p: too many buffers %u > %u", c, n_buffers, MAX_BUFFERS); - return -ENOSPC; + res = -ENOSPC; + goto done; } fl = PW_MEMMAP_FLAG_READ; @@ -2951,9 +3059,11 @@ static int client_node_port_use_buffers(void *data, mix->n_buffers = n_buffers; res = 0; - done: +done: if (res < 0) - pw_proxy_errorf((struct pw_proxy*)c->node, res, "port_use_buffers: %s", spa_strerror(res)); + pw_proxy_errorf((struct pw_proxy*)c->node, res, + "port_use_buffers(%u:%u:%u): %s", direction, port_id, + mix_id, spa_strerror(res)); return res; } @@ -3018,7 +3128,9 @@ exit_free: pw_memmap_free(old); exit: if (res < 0) - pw_proxy_errorf((struct pw_proxy*)c->node, res, "port_set_io: %s", spa_strerror(res)); + pw_proxy_errorf((struct pw_proxy*)c->node, res, + "port_set_io(%u:%u:%u %u): %s", direction, port_id, + mix_id, id, spa_strerror(res)); return res; } @@ -3151,7 +3263,8 @@ static int client_node_set_activation(void *data, exit: if (res < 0) - pw_proxy_errorf((struct pw_proxy*)c->node, res, "set_activation: %s", spa_strerror(res)); + pw_proxy_errorf((struct pw_proxy*)c->node, res, + "set_activation(%u): %s", node_id, spa_strerror(res)); return res; } @@ -3168,7 +3281,7 @@ static int client_node_port_set_mix_info(void *data, int res = 0; if (p == NULL || !p->valid) { - res = -EINVAL; + res = peer_id == SPA_ID_INVALID ? 0 : -EINVAL; goto exit; } @@ -3192,7 +3305,9 @@ static int client_node_port_set_mix_info(void *data, } exit: if (res < 0) - pw_proxy_errorf((struct pw_proxy*)c->node, res, "set_mix_info: %s", spa_strerror(res)); + pw_proxy_errorf((struct pw_proxy*)c->node, res, + "set_mix_info(%u:%u:%u %u): %s", direction, port_id, + mix_id, peer_id, spa_strerror(res)); return res; } @@ -3234,7 +3349,7 @@ static struct spa_thread *impl_create(void *object, if (globals.creator != NULL) { uint32_t i, n_items = props ? props->n_items : 0; - items = alloca((n_items) + 1 * sizeof(*items)); + items = alloca((n_items + 1) * sizeof(*items)); for (i = 0; i < n_items; i++) items[i] = props->items[i]; @@ -3281,10 +3396,14 @@ static jack_port_type_id_t string_to_type(const char *port_type) { if (spa_streq(JACK_DEFAULT_AUDIO_TYPE, port_type)) return TYPE_ID_AUDIO; - else if (spa_streq(JACK_DEFAULT_MIDI_TYPE, port_type)) - return TYPE_ID_MIDI; else if (spa_streq(JACK_DEFAULT_VIDEO_TYPE, port_type)) return TYPE_ID_VIDEO; + else if (spa_streq(JACK_DEFAULT_MIDI_TYPE, port_type)) + return TYPE_ID_MIDI; + else if (spa_streq(JACK_DEFAULT_OSC_TYPE, port_type)) + return TYPE_ID_OSC; + else if (spa_streq(JACK_DEFAULT_UMP_TYPE, port_type)) + return TYPE_ID_UMP; else if (spa_streq("other", port_type)) return TYPE_ID_OTHER; else @@ -3296,22 +3415,46 @@ static const char* type_to_string(jack_port_type_id_t type_id) switch(type_id) { case TYPE_ID_AUDIO: return JACK_DEFAULT_AUDIO_TYPE; - case TYPE_ID_MIDI: - return JACK_DEFAULT_MIDI_TYPE; case TYPE_ID_VIDEO: return JACK_DEFAULT_VIDEO_TYPE; + case TYPE_ID_MIDI: + case TYPE_ID_OSC: + case TYPE_ID_UMP: + /* all returned as MIDI */ + return JACK_DEFAULT_MIDI_TYPE; case TYPE_ID_OTHER: return "other"; default: return NULL; } } + +static const char* type_to_format_dsp(jack_port_type_id_t type_id) +{ + switch(type_id) { + case TYPE_ID_AUDIO: + return JACK_DEFAULT_AUDIO_TYPE; + case TYPE_ID_VIDEO: + return JACK_DEFAULT_VIDEO_TYPE; + case TYPE_ID_OSC: + return JACK_DEFAULT_OSC_TYPE; + case TYPE_ID_MIDI: + return JACK_DEFAULT_MIDI_TYPE; + case TYPE_ID_UMP: + return JACK_DEFAULT_UMP_TYPE; + default: + return NULL; + } +} + static bool type_is_dsp(jack_port_type_id_t type_id) { switch(type_id) { case TYPE_ID_AUDIO: case TYPE_ID_MIDI: case TYPE_ID_VIDEO: + case TYPE_ID_OSC: + case TYPE_ID_UMP: return true; default: return false; @@ -3328,29 +3471,6 @@ static jack_uuid_t client_make_uuid(uint32_t id, bool monitor) return uuid; } -static int json_object_find(const char *obj, const char *key, char *value, size_t len) -{ - struct spa_json it[2]; - const char *v; - char k[128]; - - spa_json_init(&it[0], obj, strlen(obj)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) - return -EINVAL; - - while (spa_json_get_string(&it[1], k, sizeof(k)) > 0) { - if (spa_streq(k, key)) { - if (spa_json_get_string(&it[1], value, len) <= 0) - continue; - return 0; - } else { - if (spa_json_next(&it[1], &v) <= 0) - break; - } - } - return -ENOENT; -} - static int metadata_property(void *data, uint32_t id, const char *key, const char *type, const char *value) { @@ -3363,7 +3483,7 @@ static int metadata_property(void *data, uint32_t id, if (id == PW_ID_CORE) { if (key == NULL || spa_streq(key, "default.audio.sink")) { if (value != NULL) { - if (json_object_find(value, "name", + if (spa_json_str_object_find(value, strlen(value), "name", c->metadata->default_audio_sink, sizeof(c->metadata->default_audio_sink)) < 0) value = NULL; @@ -3373,7 +3493,7 @@ static int metadata_property(void *data, uint32_t id, } if (key == NULL || spa_streq(key, "default.audio.source")) { if (value != NULL) { - if (json_object_find(value, "name", + if (spa_json_str_object_find(value, strlen(value), "name", c->metadata->default_audio_source, sizeof(c->metadata->default_audio_source)) < 0) value = NULL; @@ -3564,6 +3684,7 @@ static void registry_event_global(void *data, uint32_t id, const char *str; bool do_emit = true, do_sync = false; uint32_t serial; + const char *app; if (props == NULL) return; @@ -3574,8 +3695,30 @@ static void registry_event_global(void *data, uint32_t id, pw_log_debug("new %s id:%u serial:%u", type, id, serial); - if (spa_streq(type, PW_TYPE_INTERFACE_Node)) { - const char *app, *node_name; + if (spa_streq(type, PW_TYPE_INTERFACE_Client)) { + app = spa_dict_lookup(props, PW_KEY_APP_NAME); + + if ((str = spa_dict_lookup(props, PW_KEY_SEC_PID)) != NULL) { + pw_log_debug("%p: pid of \"%s\" is \"%s\"", c, app, str); + } else { + pw_log_debug("%p: pid of \"%s\" is unknown", c, app); + } + + o = alloc_object(c, INTERFACE_Client); + if (o == NULL) + goto exit; + + o->pwclient.pid = (int32_t)atoi(str); + snprintf(o->pwclient.name, sizeof(o->pwclient.name), "%s", app); + + pw_log_debug("%p: add pw client %d (%s) pid %llu", c, id, app, (unsigned long long)o->pwclient.pid); + + pthread_mutex_lock(&c->context.lock); + spa_list_append(&c->context.objects, &o->link); + pthread_mutex_unlock(&c->context.lock); + } + else if (spa_streq(type, PW_TYPE_INTERFACE_Node)) { + const char *node_name; char tmp[JACK_CLIENT_NAME_SIZE+1]; o = alloc_object(c, INTERFACE_Node); @@ -3676,6 +3819,9 @@ static void registry_event_global(void *data, uint32_t id, if ((str = spa_dict_lookup(props, PW_KEY_PORT_NAME)) == NULL) goto exit; + if (type_id == TYPE_ID_UMP && c->flag_midi2) + flags |= JackPortIsMIDI2; + spa_dict_for_each(item, props) { if (spa_streq(item->key, PW_KEY_PORT_DIRECTION)) { if (spa_streq(item->value, "in")) @@ -3701,7 +3847,7 @@ static void registry_event_global(void *data, uint32_t id, } if (is_monitor && !c->show_monitor) goto exit; - if (type_id == TYPE_ID_MIDI && !c->show_midi) + if (TYPE_ID_IS_EVENT(type_id) && !c->show_midi) goto exit; o = NULL; @@ -3762,6 +3908,8 @@ static void registry_event_global(void *data, uint32_t id, (int)(sizeof(tmp)-11), tmp, serial); else snprintf(o->port.name, sizeof(o->port.name), "%s", tmp); + + o->port.type_id = type_id; } if (c->fill_aliases) { @@ -3781,7 +3929,6 @@ static void registry_event_global(void *data, uint32_t id, } o->port.flags = flags; - o->port.type_id = type_id; o->port.node_id = node_id; o->port.is_monitor = is_monitor; @@ -3933,6 +4080,9 @@ static void registry_event_global_remove(void *data, uint32_t id) o->removing = true; switch (o->type) { + case INTERFACE_Client: + free_object(c, o); + break; case INTERFACE_Node: if (c->metadata) { if (spa_streq(o->node.node_name, c->metadata->default_audio_sink)) @@ -4003,10 +4153,12 @@ static int execute_match(void *data, const char *location, const char *action, return 1; } +static struct client * g_first_client; + SPA_EXPORT jack_client_t * jack_client_open (const char *client_name, jack_options_t options, - jack_status_t *status, ...) + jack_status_t *status_ptr, ...) { struct client *client; const struct spa_support *support; @@ -4015,7 +4167,7 @@ jack_client_t * jack_client_open (const char *client_name, struct spa_cpu *cpu_iface; const struct pw_properties *props; va_list ap; - + jack_status_t status; if (getenv("PIPEWIRE_NOJACK") != NULL || getenv("PIPEWIRE_INTERNAL") != NULL || spa_strstartswith(pw_get_library_version(), "0.2")) @@ -4029,7 +4181,7 @@ jack_client_t * jack_client_open (const char *client_name, pw_log_info("%p: open '%s' options:%d", client, client_name, options); - va_start(ap, status); + va_start(ap, status_ptr); varargs_parse(client, options, ap); va_end(ap); @@ -4249,6 +4401,7 @@ jack_client_t * jack_client_open (const char *client_name, client->fill_aliases = pw_properties_get_bool(client->props, "jack.fill-aliases", false); client->writable_input = pw_properties_get_bool(client->props, "jack.writable-input", true); client->async = pw_properties_get_bool(client->props, PW_KEY_NODE_ASYNC, false); + client->flag_midi2 = pw_properties_get_bool(client->props, "jack.flag-midi2", false); client->self_connect_mode = SELF_CONNECT_ALLOW; if ((str = pw_properties_get(client->props, "jack.self-connect-mode")) != NULL) { @@ -4271,8 +4424,9 @@ jack_client_t * jack_client_open (const char *client_name, client->rt_max = pw_properties_get_int32(client->props, "rt.prio", DEFAULT_RT_MAX); - if (status) - *status = 0; + status = 0; + if (status_ptr) + *status_ptr = status; client->pending_sync = pw_proxy_sync((struct pw_proxy*)client->core, client->pending_sync); @@ -4287,40 +4441,48 @@ jack_client_t * jack_client_open (const char *client_name, } if (!spa_streq(client->name, client_name)) { - if (status) - *status |= JackNameNotUnique; + status |= JackNameNotUnique; + if (status_ptr) + *status_ptr = status; if (options & JackUseExactName) goto exit_unlock; } pw_thread_loop_unlock(client->context.loop); + if (g_first_client == NULL) + g_first_client = client; + pw_thread_loop_start(client->context.notify); pw_log_info("%p: opened", client); return (jack_client_t *)client; no_props: - if (status) - *status = JackFailure | JackInitFailure; + status = JackFailure | JackInitFailure; + if (status_ptr) + *status_ptr = status; goto exit; init_failed: - if (status) - *status = JackFailure | JackInitFailure; + status = JackFailure | JackInitFailure; + if (status_ptr) + *status_ptr = status; goto exit_unlock; server_failed: - if (status) - *status = JackFailure | JackServerFailed; + status = JackFailure | JackServerFailed; + if (status_ptr) + *status_ptr = status; goto exit_unlock; exit_unlock: pw_thread_loop_unlock(client->context.loop); exit: - pw_log_info("%p: error %d", client, *status); + pw_log_info("%p: error %d", client, status); jack_client_close((jack_client_t *) client); return NULL; disabled: pw_log_warn("JACK is disabled"); - if (status) - *status = JackFailure | JackInitFailure; + status = JackFailure | JackInitFailure; + if (status_ptr) + *status_ptr = status; return NULL; } @@ -4350,6 +4512,9 @@ int jack_client_close (jack_client_t *client) pw_log_info("%p: close", client); + if (g_first_client == c) + g_first_client = NULL; + c->destroyed = true; res = jack_deactivate(client); @@ -4703,8 +4868,25 @@ int jack_deactivate (jack_client_t *client) SPA_EXPORT int jack_get_client_pid (const char *name) { - pw_log_error("not implemented on library side"); - return 0; + struct object *on, *oc; + + if (g_first_client == NULL) return 0; + + on = find_node(g_first_client, name); + if (on == NULL) { + pw_log_warn("unknown (jack-client) node \"%s\"", name); + return 0; + } + + oc = find_client(g_first_client, on->node.client_id); + if (oc == NULL) { + pw_log_warn("unknown (pw) client %d", (int)on->node.client_id); + return 0; + } + + pw_log_info("pid %d (%s)", (int)oc->pwclient.pid, oc->pwclient.name); + + return (int)oc->pwclient.pid; } SPA_EXPORT @@ -5162,7 +5344,7 @@ jack_nframes_t jack_get_sample_rate (jack_client_t *client) } } c->sample_rate = res; - pw_log_debug("sample_rate: %u", res); + pw_log_trace_fp("sample_rate: %u", res); return res; } @@ -5260,6 +5442,9 @@ jack_port_t * jack_port_register (jack_client_t *client, pw_log_warn("unknown port type %s", port_type); return NULL; } + if (type_id == TYPE_ID_MIDI && (flags & JackPortIsMIDI2)) + type_id = TYPE_ID_UMP; + len = snprintf(name, sizeof(name), "%s:%s", c->name, port_name); if (len < 0 || (size_t)len >= sizeof(name)) { pw_log_warn("%p: name \"%s:%s\" too long", c, @@ -5293,6 +5478,8 @@ jack_port_t * jack_port_register (jack_client_t *client, p->get_buffer = get_buffer_input_float; break; case TYPE_ID_MIDI: + case TYPE_ID_OSC: + case TYPE_ID_UMP: p->get_buffer = get_buffer_input_midi; break; default: @@ -5306,6 +5493,8 @@ jack_port_t * jack_port_register (jack_client_t *client, p->get_buffer = get_buffer_output_float; break; case TYPE_ID_MIDI: + case TYPE_ID_OSC: + case TYPE_ID_UMP: p->get_buffer = get_buffer_output_midi; break; default: @@ -5318,7 +5507,7 @@ jack_port_t * jack_port_register (jack_client_t *client, spa_list_init(&p->mix); - pw_properties_set(p->props, PW_KEY_FORMAT_DSP, port_type); + pw_properties_set(p->props, PW_KEY_FORMAT_DSP, type_to_format_dsp(type_id)); pw_properties_set(p->props, PW_KEY_PORT_NAME, port_name); if (flags > 0x1f) { pw_properties_setf(p->props, PW_KEY_PORT_EXTRA, @@ -5562,7 +5751,7 @@ static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames) /* first convert to a thread local scratch buffer, then memcpy into * the per port buffer. This makes it possible to call this function concurrently * but also have different pointers per port */ - convert_to_midi(seq, n_seq, mb, p->client->fix_midi_events); + convert_to_event(seq, n_seq, mb, p->client->fix_midi_events, p->object->port.type_id); memcpy(ptr, mb, sizeof(struct midi_buffer) + (mb->event_count * sizeof(struct midi_event))); if (mb->write_pos) { @@ -5628,7 +5817,7 @@ void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t frames) if ((b = get_mix_buffer(c, mix, frames)) == NULL) goto done; - if (o->port.type_id == TYPE_ID_MIDI) { + if (TYPE_ID_IS_EVENT(o->port.type_id)) { struct spa_pod_sequence *seq[1]; struct spa_data *d; void *pod; @@ -5643,7 +5832,7 @@ void * jack_port_get_buffer (jack_port_t *port, jack_nframes_t frames) if (!spa_pod_is_sequence(pod)) goto done; seq[0] = pod; - convert_to_midi(seq, 1, ptr, c->fix_midi_events); + convert_to_event(seq, 1, ptr, c->fix_midi_events, o->port.type_id); } else { ptr = get_buffer_data(b, frames); } @@ -6207,7 +6396,7 @@ int jack_connect (jack_client_t *client, if (src == NULL || dst == NULL || !(src->port.flags & JackPortIsOutput) || !(dst->port.flags & JackPortIsInput) || - src->port.type_id != dst->port.type_id) { + !TYPE_ID_IS_COMPATIBLE(src->port.type_id, dst->port.type_id)) { res = -EINVAL; goto exit; } @@ -6364,6 +6553,10 @@ size_t jack_port_type_get_buffer_size (jack_client_t *client, const char *port_t return jack_get_buffer_size(client) * sizeof(float); else if (spa_streq(JACK_DEFAULT_MIDI_TYPE, port_type)) return c->max_frames * sizeof(float); + else if (spa_streq(JACK_DEFAULT_OSC_TYPE, port_type)) + return c->max_frames * sizeof(float); + else if (spa_streq(JACK_DEFAULT_UMP_TYPE, port_type)) + return c->max_frames * sizeof(float); else if (spa_streq(JACK_DEFAULT_VIDEO_TYPE, port_type)) return 320 * 240 * 4 * sizeof(float); else @@ -6398,6 +6591,7 @@ void jack_port_get_latency_range (jack_port_t *port, jack_latency_callback_mode_ jack_nframes_t nframes, rate; int direction; struct spa_latency_info *info; + int64_t min, max; return_if_fail(o != NULL); c = o->client; @@ -6416,10 +6610,15 @@ void jack_port_get_latency_range (jack_port_t *port, jack_latency_callback_mode_ rate = jack_get_sample_rate((jack_client_t*)c); info = &o->port.latency[direction]; - range->min = (jack_nframes_t)((info->min_quantum * nframes) + - info->min_rate + (info->min_ns * rate) / SPA_NSEC_PER_SEC); - range->max = (jack_nframes_t)((info->max_quantum * nframes) + - info->max_rate + (info->max_ns * rate) / SPA_NSEC_PER_SEC); + min = (int64_t)(info->min_quantum * nframes) + + info->min_rate + + (info->min_ns * (int64_t)rate) / (int64_t)SPA_NSEC_PER_SEC; + max = (int64_t)(info->max_quantum * nframes) + + info->max_rate + + (info->max_ns * (int64_t)rate) / (int64_t)SPA_NSEC_PER_SEC; + + range->min = SPA_MAX(min, 0); + range->max = SPA_MAX(max, 0); pw_log_debug("%p: %s get %d latency range %d %d", c, o->port.name, mode, range->min, range->max); @@ -6464,13 +6663,13 @@ void jack_port_set_latency_range (jack_port_t *port, jack_latency_callback_mode_ nframes = 1; latency.min_rate = range->min; - if (latency.min_rate >= nframes) { + if (latency.min_rate >= (int32_t)nframes) { latency.min_quantum = latency.min_rate / nframes; latency.min_rate %= nframes; } latency.max_rate = range->max; - if (latency.max_rate >= nframes) { + if (latency.max_rate >= (int32_t)nframes) { latency.max_quantum = latency.max_rate / nframes; latency.max_rate %= nframes; } @@ -6626,7 +6825,7 @@ const char ** jack_get_ports (jack_client_t *client, continue; pw_log_debug("%p: check port type:%d flags:%08lx name:\"%s\"", c, o->port.type_id, o->port.flags, o->port.name); - if (o->port.type_id > TYPE_ID_VIDEO) + if (TYPE_ID_IS_HIDDEN(o->port.type_id)) continue; if (!SPA_FLAG_IS_SET(o->port.flags, flags)) continue; @@ -7100,6 +7299,8 @@ static int transport_update(struct client* c, int active) PW_CLIENT_NODE_UPDATE_INFO, 0, NULL, &c->info); c->info.change_mask = 0; + + pw_properties_set(c->props, PW_KEY_NODE_TRANSPORT, NULL); pw_thread_loop_unlock(c->context.loop); return 0; diff --git a/pipewire-v4l2/src/pipewire-v4l2.c b/pipewire-v4l2/src/pipewire-v4l2.c index 2e5a4045..691c8ec3 100644 --- a/pipewire-v4l2/src/pipewire-v4l2.c +++ b/pipewire-v4l2/src/pipewire-v4l2.c @@ -1696,7 +1696,7 @@ static int connect_stream(struct file *file) break; if (state == PW_STREAM_STATE_ERROR) { - res = -EIO; + res = -errno; goto exit; } if (file->error < 0) { diff --git a/po/LINGUAS b/po/LINGUAS index 2532833e..a6b999f4 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -40,6 +40,7 @@ pt ro ru sk +sl sr@latin sr sv diff --git a/po/POTFILES.skip b/po/POTFILES.skip index d6b76925..686d91a3 100644 --- a/po/POTFILES.skip +++ b/po/POTFILES.skip @@ -1 +1,2 @@ +src/daemon/systemd/system/pipewire-pulse.service.in src/daemon/systemd/system/pipewire.service.in diff --git a/po/de.po b/po/de.po index 6c1b0882..5ac68fea 100644 --- a/po/de.po +++ b/po/de.po @@ -9,15 +9,16 @@ # Hedda Peters <hpeters@redhat.com>, 2009, 2012. # Mario Blättermann <mario.blaettermann@gmail.com>, 2016. # Jürgen Benvenuti <gastornis@posteo.org>, 2024. +# Christian Kirbach <christian.kirbach@gmail.com>, 2024. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx.de\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2024-01-28 15:27+0000\n" -"PO-Revision-Date: 2024-01-28 19:19+0100\n" -"Last-Translator: Jürgen Benvenuti <gastornis@posteo.org>\n" +"POT-Creation-Date: 2024-09-27 03:27+0000\n" +"PO-Revision-Date: 2024-09-27 11:25+0200\n" +"Last-Translator: Christian Kirbach <christian.kirbach@gmail.com>\n" "Language-Team: German <https://translate.fedoraproject.org/projects/pipewire/" "pipewire/de/>\n" "Language: de\n" @@ -25,21 +26,25 @@ 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.4.2\n" +"X-Generator: Poedit 3.4.4\n" -#: src/daemon/pipewire.c:26 +#: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" +" -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" +" -P --properties Set context properties\n" msgstr "" "%s [Optionen]\n" " -h, --help Diese Hilfe ausgeben\n" +" -v, --verbose Ausführlichere Ausgaben\n" " --version Version anzeigen\n" " -c, --config Konfiguration laden (Voreinstellung " "%s)\n" +" -P --properties Kontext-Eigenschaften festlegen\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" @@ -59,26 +64,26 @@ msgstr "Tunnel zu %s%s%s" msgid "Dummy Output" msgstr "Schein-Ausgabe" -#: src/modules/module-pulse-tunnel.c:774 +#: src/modules/module-pulse-tunnel.c:777 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunnel für %s@%s" -#: src/modules/module-zeroconf-discover.c:315 +#: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Unbekanntes Gerät" -#: src/modules/module-zeroconf-discover.c:327 +#: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%s auf %s@%s" -#: src/modules/module-zeroconf-discover.c:331 +#: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%s auf %s" -#: src/tools/pw-cat.c:991 +#: src/tools/pw-cat.c:973 #, c-format msgid "" "%s [options] [<file>|-]\n" @@ -94,7 +99,7 @@ msgstr "" "\n" "\n" -#: src/tools/pw-cat.c:998 +#: src/tools/pw-cat.c:980 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -128,7 +133,7 @@ msgstr "" " -P --properties Knoteneigenschaften festlegen\n" "\n" -#: src/tools/pw-cat.c:1016 +#: src/tools/pw-cat.c:998 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -145,6 +150,7 @@ msgid "" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" +" -a, --raw RAW mode\n" "\n" msgstr "" " --rate Abtastrate (notw. für Aufzeichn.) " @@ -162,9 +168,10 @@ msgstr "" "%.3f)\n" " -q --quality Qualität der Neu-Abtastung (0 - 15) " "(Vorgabe %d)\n" +" -a, --raw RAW-Modus\n" "\n" -#: src/tools/pw-cat.c:1033 +#: src/tools/pw-cat.c:1016 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" @@ -180,7 +187,7 @@ msgstr "" " -o, --encoded Codieren-Modus\n" "\n" -#: src/tools/pw-cli.c:2248 +#: src/tools/pw-cli.c:2318 #, c-format msgid "" "%s [options] [command]\n" @@ -203,8 +210,8 @@ msgstr "" msgid "Pro Audio" msgstr "Pro Audio" -#: spa/plugins/alsa/acp/acp.c:488 spa/plugins/alsa/acp/alsa-mixer.c:4633 -#: spa/plugins/bluez5/bluez5-device.c:1725 +#: spa/plugins/alsa/acp/acp.c:487 spa/plugins/alsa/acp/alsa-mixer.c:4633 +#: spa/plugins/bluez5/bluez5-device.c:1696 msgid "Off" msgstr "Aus" @@ -231,7 +238,7 @@ msgstr "Line-Eingang" #: spa/plugins/alsa/acp/alsa-mixer.c:2657 #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:2004 +#: spa/plugins/bluez5/bluez5-device.c:1984 msgid "Microphone" msgstr "Mikrofon" @@ -297,7 +304,7 @@ msgid "No Bass Boost" msgstr "Keine Bassverstärkung" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:2010 +#: spa/plugins/bluez5/bluez5-device.c:1990 msgid "Speaker" msgstr "Lautsprecher" @@ -316,7 +323,7 @@ msgstr "Mikrofon der Docking-Station" #: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" -msgstr "Mikrofon am Kopfhörer" +msgstr "Mikrofon am Sprechkopfhörer" #: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" @@ -360,11 +367,11 @@ msgstr "Mehrkanal-Eingang" #: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" -msgstr "Mehrkanal-Ausgang" +msgstr "Mehrkanal-Wiedergabe" #: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" -msgstr "Spiel-Ausgabe" +msgstr "Spiel-Ausgang" #: spa/plugins/alsa/acp/alsa-mixer.c:2763 #: spa/plugins/alsa/acp/alsa-mixer.c:2764 @@ -381,7 +388,7 @@ msgstr "Virtuelles 7.1 Surround" #: spa/plugins/alsa/acp/alsa-mixer.c:4456 msgid "Analog Mono" -msgstr "Analog Mono" +msgstr "Analoges Mono" #: spa/plugins/alsa/acp/alsa-mixer.c:4457 msgid "Analog Mono (Left)" @@ -400,7 +407,7 @@ msgstr "Analoges Mono (rechts)" #: spa/plugins/alsa/acp/alsa-mixer.c:4467 #: spa/plugins/alsa/acp/alsa-mixer.c:4468 msgid "Analog Stereo" -msgstr "Analog Stereo" +msgstr "Analoges Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Mono" @@ -412,9 +419,9 @@ msgstr "Stereo" #: spa/plugins/alsa/acp/alsa-mixer.c:4469 #: spa/plugins/alsa/acp/alsa-mixer.c:4627 -#: spa/plugins/bluez5/bluez5-device.c:1992 +#: spa/plugins/bluez5/bluez5-device.c:1972 msgid "Headset" -msgstr "Headset" +msgstr "Sprechkopfhörer" #: spa/plugins/alsa/acp/alsa-mixer.c:4470 #: spa/plugins/alsa/acp/alsa-mixer.c:4628 @@ -472,7 +479,7 @@ msgstr "Analog Surround 7.1" #: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Digital Stereo (IEC958)" -msgstr "Digital Stereo (IEC958)" +msgstr "Digitales Stereo (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Digital Surround 4.0 (IEC958/AC3)" @@ -488,7 +495,7 @@ msgstr "Digital Surround 5.1 (IEC958/DTS)" #: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Stereo (HDMI)" -msgstr "Digital Stereo (HDMI)" +msgstr "Digitales Stereo (HDMI)" #: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (HDMI)" @@ -504,15 +511,15 @@ msgstr "Spiel" #: spa/plugins/alsa/acp/alsa-mixer.c:4625 msgid "Analog Mono Duplex" -msgstr "Analog Mono Duplex" +msgstr "Analoges Mono Duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4626 msgid "Analog Stereo Duplex" -msgstr "Analog Stereo Duplex" +msgstr "Analoges Stereo Duplex" #: spa/plugins/alsa/acp/alsa-mixer.c:4629 msgid "Digital Stereo Duplex (IEC958)" -msgstr "Digital Stereo Duplex (IEC958)" +msgstr "Digitales Stereo Duplex (IEC958)" #: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Multichannel Duplex" @@ -536,7 +543,7 @@ msgstr "%s-Ausgabe" msgid "%s Input" msgstr "%s-Eingang" -#: spa/plugins/alsa/acp/alsa-util.c:1211 spa/plugins/alsa/acp/alsa-util.c:1305 +#: spa/plugins/alsa/acp/alsa-util.c:1231 spa/plugins/alsa/acp/alsa-util.c:1325 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -559,7 +566,7 @@ msgstr[1] "" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber »%s«. Bitte melden Sie " "dieses Problem den ALSA-Entwicklern." -#: spa/plugins/alsa/acp/alsa-util.c:1277 +#: spa/plugins/alsa/acp/alsa-util.c:1297 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " @@ -582,7 +589,7 @@ msgstr[1] "" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber »%s«. Bitte melden Sie " "dieses Problem den ALSA-Entwicklern." -#: spa/plugins/alsa/acp/alsa-util.c:1324 +#: spa/plugins/alsa/acp/alsa-util.c:1344 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -595,7 +602,7 @@ msgstr "" "Dies ist wahrscheinlich ein Fehler im ALSA-Treiber »%s«. Bitte melden Sie " "dieses Problem den ALSA-Entwicklern." -#: spa/plugins/alsa/acp/alsa-util.c:1367 +#: spa/plugins/alsa/acp/alsa-util.c:1387 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -630,104 +637,103 @@ msgstr "Internes Audio" msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1736 +#: spa/plugins/bluez5/bluez5-device.c:1707 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Audio-Gateway (A2DP-Quelle und HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1784 +#: spa/plugins/bluez5/bluez5-device.c:1755 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "High Fidelity-Wiedergabe (A2DP-Senke, Codec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1787 +#: spa/plugins/bluez5/bluez5-device.c:1758 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "High Fidelity Duplex (A2DP-Quelle/-Senke, Codec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1795 +#: spa/plugins/bluez5/bluez5-device.c:1766 msgid "High Fidelity Playback (A2DP Sink)" msgstr "High Fidelity-Wiedergabe (A2DP-Senke)" -#: spa/plugins/bluez5/bluez5-device.c:1797 +#: spa/plugins/bluez5/bluez5-device.c:1768 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "High Fidelity Duplex (A2DP-Quelle/-Senke)" -#: spa/plugins/bluez5/bluez5-device.c:1847 +#: spa/plugins/bluez5/bluez5-device.c:1818 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "High Fidelity-Wiedergabe (BAP-Senke, Codec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1852 +#: spa/plugins/bluez5/bluez5-device.c:1823 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "High Fidelity-Eingang (BAP-Quelle, Codec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1856 +#: spa/plugins/bluez5/bluez5-device.c:1827 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "High Fidelity Duplex (BAP-Quelle/-Senke, Codec %s)" -#: spa/plugins/bluez5/bluez5-device.c:1865 +#: spa/plugins/bluez5/bluez5-device.c:1836 msgid "High Fidelity Playback (BAP Sink)" msgstr "High Fidelity-Wiedergabe (BAP-Senke)" -#: spa/plugins/bluez5/bluez5-device.c:1869 +#: spa/plugins/bluez5/bluez5-device.c:1840 msgid "High Fidelity Input (BAP Source)" msgstr "High Fidelity-Eingang (BAP-Quelle)" -#: spa/plugins/bluez5/bluez5-device.c:1872 +#: spa/plugins/bluez5/bluez5-device.c:1843 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "High Fidelity Duplex (BAP-Quelle/-Senke)" -#: spa/plugins/bluez5/bluez5-device.c:1908 +#: spa/plugins/bluez5/bluez5-device.c:1892 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" -msgstr "Kopfhörer-Garnitur (HSP/HFP, Codec %s)" - -#: spa/plugins/bluez5/bluez5-device.c:1913 -msgid "Headset Head Unit (HSP/HFP)" -msgstr "Kopfhörer-Garnitur (HSP/HFP)" - -#: spa/plugins/bluez5/bluez5-device.c:1993 -#: spa/plugins/bluez5/bluez5-device.c:1998 -#: spa/plugins/bluez5/bluez5-device.c:2005 -#: spa/plugins/bluez5/bluez5-device.c:2011 -#: spa/plugins/bluez5/bluez5-device.c:2017 -#: spa/plugins/bluez5/bluez5-device.c:2023 -#: spa/plugins/bluez5/bluez5-device.c:2029 -#: spa/plugins/bluez5/bluez5-device.c:2035 -#: spa/plugins/bluez5/bluez5-device.c:2041 +msgstr "Sprechkopfhörer-Einheit (HSP/HFP, Codec %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1973 +#: spa/plugins/bluez5/bluez5-device.c:1978 +#: spa/plugins/bluez5/bluez5-device.c:1985 +#: spa/plugins/bluez5/bluez5-device.c:1991 +#: spa/plugins/bluez5/bluez5-device.c:1997 +#: spa/plugins/bluez5/bluez5-device.c:2003 +#: spa/plugins/bluez5/bluez5-device.c:2009 +#: spa/plugins/bluez5/bluez5-device.c:2015 +#: spa/plugins/bluez5/bluez5-device.c:2021 msgid "Handsfree" msgstr "Freisprech" -#: spa/plugins/bluez5/bluez5-device.c:1999 +#: spa/plugins/bluez5/bluez5-device.c:1979 msgid "Handsfree (HFP)" msgstr "Freisprech (HFP)" -#: spa/plugins/bluez5/bluez5-device.c:2016 +#: spa/plugins/bluez5/bluez5-device.c:1996 msgid "Headphone" msgstr "Kopfhörer" -#: spa/plugins/bluez5/bluez5-device.c:2022 +#: spa/plugins/bluez5/bluez5-device.c:2002 msgid "Portable" msgstr "Tragbar" -#: spa/plugins/bluez5/bluez5-device.c:2028 +#: spa/plugins/bluez5/bluez5-device.c:2008 msgid "Car" msgstr "Auto" -#: spa/plugins/bluez5/bluez5-device.c:2034 +#: spa/plugins/bluez5/bluez5-device.c:2014 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:2040 +#: spa/plugins/bluez5/bluez5-device.c:2020 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:2047 +#: spa/plugins/bluez5/bluez5-device.c:2027 msgid "Bluetooth" msgstr "Bluetooth" -#: spa/plugins/bluez5/bluez5-device.c:2048 +#: spa/plugins/bluez5/bluez5-device.c:2028 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" + +#~ msgid "Headset Head Unit (HSP/HFP)" +#~ msgstr "Kopfhörer-Garnitur (HSP/HFP)" \ No newline at end of file diff --git a/po/fi.po b/po/fi.po index 0a0bb148..a9cba413 100644 --- a/po/fi.po +++ b/po/fi.po @@ -7,13 +7,11 @@ msgid "" msgstr "" "Project-Id-Version: git trunk\n" -"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" -"issues/new\n" -"POT-Creation-Date: 2021-04-18 16:31+0300\n" -"PO-Revision-Date: 2021-04-18 16:38+0300\n" -"Last-Translator: Ricky Tigg <ricky.tigg@gmail.com>\n" -"Language-Team: Finnish <https://translate.fedoraproject.org/projects/" -"pipewire/pipewire/fi/>\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/issues/new\n" +"POT-Creation-Date: 2024-10-12 11:50+0300\n" +"PO-Revision-Date: 2024-10-12 12:04+0300\n" +"Last-Translator: Pauli Virtanen <pav@iki.fi>\n" +"Language-Team: Finnish <https://translate.fedoraproject.org/projects/pipewire/pipewire/fi/>\n" "Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -21,18 +19,22 @@ msgstr "" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.5.1\n" -#: src/daemon/pipewire.c:43 +#: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" +" -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" +" -P --properties Set context properties\n" msgstr "" "%s [valinnat]\n" " -h, --help Näytä tämä ohje\n" +" -v, --verbose Lisää viestien yksityiskohtaisuutta\n" " --version Näytä versio\n" " -c, --config Lataa asetukset (oletus %s)\n" +" -P, --properties Aseta kontekstin ominaisuudet\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" @@ -42,68 +44,82 @@ msgstr "PipeWire-mediajärjestelmä" msgid "Start the PipeWire Media System" msgstr "Käynnistä PipeWire-mediajärjestelmä" -#: src/examples/media-session/alsa-monitor.c:526 -#: spa/plugins/alsa/acp/compat.c:187 -msgid "Built-in Audio" -msgstr "Sisäinen äänentoisto" +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 +#, c-format +msgid "Tunnel to %s%s%s" +msgstr "Tunneli: %s%s%s" -#: src/examples/media-session/alsa-monitor.c:530 -#: spa/plugins/alsa/acp/compat.c:192 -msgid "Modem" -msgstr "Modeemi" +#: src/modules/module-fallback-sink.c:40 +msgid "Dummy Output" +msgstr "Valeulostulo" -#: src/examples/media-session/alsa-monitor.c:539 +#: src/modules/module-pulse-tunnel.c:777 +#, c-format +msgid "Tunnel for %s@%s" +msgstr "Tunneli: %s@%s" + +#: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Tuntematon laite" -#: src/tools/pw-cat.c:991 +#: src/modules/module-zeroconf-discover.c:332 +#, c-format +msgid "%s on %s@%s" +msgstr "%s koneella %s@%s" + +#: src/modules/module-zeroconf-discover.c:336 +#, c-format +msgid "%s on %s" +msgstr "%s koneella %s" + +#: src/tools/pw-cat.c:973 #, 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 [valinnat] <tiedosto>\n" +"%s [valinnat] [<tiedosto>|-]\n" " -h, --help Näytä tämä ohje\n" " --version Näytä versio\n" " -v, --verbose Näytä lisää tietoja\n" "\n" -#: src/tools/pw-cat.c:998 +#: src/tools/pw-cat.c:980 #, 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" +" --target Set node target serial or name " +"(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" -" --list-targets List available targets for --target\n" +" -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Vastapään taustaprosessin nimi\n" " --media-type Aseta mediatyyppi (oletus %s)\n" " --media-category Aseta medialuokka (oletus %s)\n" " --media-role Aseta mediarooli (oletus %s)\n" -" --target Aseta kohdesolmu (oletus %s)\n" +" --target Aseta kohteen numero/nimi (oletus %s)\n" " 0 tarkoittaa: ei linkkiä\n" " --latency Aseta solmun viive (oletus %s)\n" -" Xyksikkö (yksikkö = s, ms, us, " -"ns)\n" +" Xyksikkö (yksikkö = s, ms, us, ns)\n" " tai näytteiden lukumäärä (256)\n" -" näytetaajuus on lähteen mukaan\n" -" --list-targets Näytä --target:n mahdolliset " -"kohteet\n" +" näytetaajuus on tiedoston mukainen\n" +" -P --properties Aseta solmun ominaisuudet\n" "\n" -#: src/tools/pw-cat.c:1016 +#: src/tools/pw-cat.c:998 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -120,38 +136,37 @@ msgid "" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" +" -a, --raw RAW mode\n" "\n" msgstr "" -" --rate Näytetaajuus (pakoll. nauhoit.) " -"(oletus %u)\n" -" --channels Kanavien määrä (pakoll. nauhoit.) " -"(oletus %u)\n" +" --rate Näytetaajuus (pakoll. nauhoit.) (oletus %u)\n" +" --channels Kanavien määrä (pakoll. nauhoit.) (oletus %u)\n" " --channel-map Kanavakartta\n" -" vaihtoehdot: \"stereo\", " -"\"surround-51\",... tai\n" -" pilkulla erotetut kanavien " -"nimet: esim. \"FL,FR\"\n" -" --format Näytemuoto %s (pakoll. nauhoit.) " -"(oletus %s)\n" -" --volume Vuon äänenvoimakkuus 0-1.0 (oletus " -"%.3f)\n" -" -q --quality Resamplerin laatu (0 - 15) (oletus " -"%d)\n" +" vaihtoehdot: \"stereo\", \"surround-51\",... tai\n" +" pilkulla erotetut kanavien nimet: esim. \"FL,FR\"\n" +" --format Näytemuoto %s (pakoll. nauhoit.) (oletus %s)\n" +" --volume Vuon äänenvoimakkuus 0-1.0 (oletus %.3f)\n" +" -q --quality Resamplerin laatu (0 - 15) (oletus %d)\n" +" -a --raw Muotoilemattoman äänidatan tila\n" "\n" -#: src/tools/pw-cat.c:1033 +#: src/tools/pw-cat.c:1016 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" +" -d, --dsd DSD mode\n" +" -o, --encoded Encoded mode\n" "\n" msgstr "" " -p, --playback Toisto\n" " -r, --record Nauhoitus\n" " -m, --midi MIDI-tila\n" +" -d, --dsd DSD-tila\n" +" -o, --encoded Koodatun audion tila\n" "\n" -#: src/tools/pw-cli.c:2932 +#: src/tools/pw-cli.c:2318 #, c-format msgid "" "%s [options] [command]\n" @@ -159,209 +174,206 @@ msgid "" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" +" -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [valinnat] [komento]\n" " -h, --help Näytä tämä ohje\n" " --version Näytä versio\n" -" -d, --daemon Käynnistä taustaprosessina (oletus: " -"ei)\n" +" -d, --daemon Käynnistä taustaprosessina (oletus: ei)\n" " -r, --remote Taustaprosessin nimi\n" +" -m, --monitor Seuraa tapahtumia\n" "\n" -#: spa/plugins/alsa/acp/acp.c:290 +#: spa/plugins/alsa/acp/acp.c:327 msgid "Pro Audio" 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:487 spa/plugins/alsa/acp/alsa-mixer.c:4633 +#: spa/plugins/bluez5/bluez5-device.c:1696 msgid "Off" msgstr "Poissa" -#: spa/plugins/alsa/acp/channelmap.h:466 -msgid "(invalid)" -msgstr "(virheellinen)" - -#: spa/plugins/alsa/acp/alsa-mixer.c:2709 +#: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Sisääntulo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2710 +#: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Telakan sisääntulo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2711 +#: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Telakan mikrofoni" -#: spa/plugins/alsa/acp/alsa-mixer.c:2712 +#: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Telakan linjasisääntulo" -#: 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 "Linjasisääntulo" -#: 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:1984 msgid "Microphone" msgstr "Mikrofoni" -#: 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 "Etumikrofoni" -#: 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 "Takamikrofoni" -#: spa/plugins/alsa/acp/alsa-mixer.c:2717 +#: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Ulkoinen mikrofoni" -#: 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 "Sisäinen mikrofoni" -#: 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 "Video" -#: spa/plugins/alsa/acp/alsa-mixer.c:2721 +#: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Automaattinen äänenvoimakkuuden säätö" -#: spa/plugins/alsa/acp/alsa-mixer.c:2722 +#: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Ei automaattista äänenvoimakkuuden säätöä" -#: spa/plugins/alsa/acp/alsa-mixer.c:2723 +#: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Vahvistus" -#: spa/plugins/alsa/acp/alsa-mixer.c:2724 +#: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Ei vahvistusta" -#: spa/plugins/alsa/acp/alsa-mixer.c:2725 +#: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Vahvistin" -#: spa/plugins/alsa/acp/alsa-mixer.c:2726 +#: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Ei vahvistinta" -#: spa/plugins/alsa/acp/alsa-mixer.c:2727 +#: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Bassonvahvistus" -#: spa/plugins/alsa/acp/alsa-mixer.c:2728 +#: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Ei basson vahvistusta" -#: 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:1990 msgid "Speaker" msgstr "Kaiutin" -#: 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 "Kuulokkeet" -#: spa/plugins/alsa/acp/alsa-mixer.c:2797 +#: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Analoginen sisääntulo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2801 +#: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Telakan mikrofoni" -#: spa/plugins/alsa/acp/alsa-mixer.c:2803 +#: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Kuulokkeiden mikrofoni" -#: spa/plugins/alsa/acp/alsa-mixer.c:2807 +#: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Analoginen ulostulo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Kuulokkeet 2" -#: spa/plugins/alsa/acp/alsa-mixer.c:2810 +#: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Kuulokkeiden monoulostulo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Linjaulostulo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2812 +#: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Analoginen monoulostulo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Kaiuttimet" -#: 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 "Digitaalinen ulostulo (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Digitaalinen sisääntulo (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2817 +#: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Monikanavainen sisääntulo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Monikanavainen ulostulo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2819 +#: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Peli-ulostulo" -#: 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 "Puhe-ulostulo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Puhe-sisääntulo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Virtuaalinen tilaääni 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4527 +#: spa/plugins/alsa/acp/alsa-mixer.c:4456 msgid "Analog Mono" msgstr "Analoginen mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4528 +#: spa/plugins/alsa/acp/alsa-mixer.c:4457 msgid "Analog Mono (Left)" msgstr "Analoginen mono (vasen)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4529 +#: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono (Right)" msgstr "Analoginen mono (oikea)" @@ -370,147 +382,147 @@ msgstr "Analoginen mono (oikea)" #. * 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:4459 +#: spa/plugins/alsa/acp/alsa-mixer.c:4467 +#: spa/plugins/alsa/acp/alsa-mixer.c:4468 msgid "Analog Stereo" msgstr "Analoginen stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4531 +#: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Mono" msgstr "Mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4532 +#: spa/plugins/alsa/acp/alsa-mixer.c:4461 msgid "Stereo" msgstr "Stereo" -#: 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:4469 +#: spa/plugins/alsa/acp/alsa-mixer.c:4627 +#: spa/plugins/bluez5/bluez5-device.c:1972 msgid "Headset" msgstr "Kuulokkeet" -#: spa/plugins/alsa/acp/alsa-mixer.c:4541 -#: spa/plugins/alsa/acp/alsa-mixer.c:4699 +#: spa/plugins/alsa/acp/alsa-mixer.c:4470 +#: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Speakerphone" msgstr "Kaiutinpuhelin" -#: spa/plugins/alsa/acp/alsa-mixer.c:4542 -#: spa/plugins/alsa/acp/alsa-mixer.c:4543 +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Multichannel" msgstr "Monikanavainen" -#: spa/plugins/alsa/acp/alsa-mixer.c:4544 +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Surround 2.1" msgstr "Analoginen tilaääni 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4545 +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Analog Surround 3.0" msgstr "Analoginen tilaääni 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4546 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 3.1" msgstr "Analoginen tilaääni 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4547 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 4.0" msgstr "Analoginen tilaääni 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4548 +#: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 4.1" msgstr "Analoginen tilaääni 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4549 +#: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 5.0" msgstr "Analoginen tilaääni 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4550 +#: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 5.1" msgstr "Analoginen tilaääni 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4551 +#: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 6.0" msgstr "Analoginen tilaääni 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4552 +#: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 6.1" msgstr "Analoginen tilaääni 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4553 +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 7.0" msgstr "Analoginen tilaääni 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4554 +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 7.1" msgstr "Analoginen tilaääni 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4555 +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Digital Stereo (IEC958)" msgstr "Digitaalinen stereo (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4556 +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digitaalinen tilaääni 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4557 +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digitaalinen tilaääni 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4558 +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digitaalinen tilaääni 5.1 (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4559 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Stereo (HDMI)" msgstr "Digitaalinen stereo (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4560 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digitaalinen tilaääni 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4561 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Chat" msgstr "Puhe" -#: spa/plugins/alsa/acp/alsa-mixer.c:4562 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Game" msgstr "Peli" -#: spa/plugins/alsa/acp/alsa-mixer.c:4696 +#: spa/plugins/alsa/acp/alsa-mixer.c:4625 msgid "Analog Mono Duplex" msgstr "Analoginen mono, molempisuuntainen" -#: spa/plugins/alsa/acp/alsa-mixer.c:4697 +#: spa/plugins/alsa/acp/alsa-mixer.c:4626 msgid "Analog Stereo Duplex" msgstr "Analoginen stereo, molempisuuntainen" -#: spa/plugins/alsa/acp/alsa-mixer.c:4700 +#: spa/plugins/alsa/acp/alsa-mixer.c:4629 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digitaalinen stereo, molempisuuntainen (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4701 +#: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Multichannel Duplex" msgstr "Monikanavainen, molempisuuntainen" -#: spa/plugins/alsa/acp/alsa-mixer.c:4702 +#: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Stereo Duplex" msgstr "Stereo, molempisuuntainen" -#: spa/plugins/alsa/acp/alsa-mixer.c:4703 +#: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Mono Chat + 7.1 Surround" msgstr "Mono-puhe + 7.1 tilaääni" -#: spa/plugins/alsa/acp/alsa-mixer.c:4806 +#: spa/plugins/alsa/acp/alsa-mixer.c:4733 #, c-format msgid "%s Output" msgstr "%s, ulostulo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4813 +#: spa/plugins/alsa/acp/alsa-mixer.c:4741 #, c-format msgid "%s Input" msgstr "%s, sisääntulo" -#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 +#: spa/plugins/alsa/acp/alsa-util.c:1231 spa/plugins/alsa/acp/alsa-util.c:1325 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -531,16 +543,16 @@ msgstr[1] "" "Tämä on todennäköisesti ohjelmavirhe ALSA-ajurissa â€%sâ€. Ilmoita tästä " "ongelmasta ALSA-kehittäjille." -#: spa/plugins/alsa/acp/alsa-util.c:1241 +#: spa/plugins/alsa/acp/alsa-util.c:1297 #, 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] "" @@ -554,7 +566,7 @@ msgstr[1] "" "Tämä on todennäköisesti ohjelmavirhe ALSA-ajurissa â€%sâ€. Ilmoita tästä " "ongelmasta ALSA-kehittäjille." -#: spa/plugins/alsa/acp/alsa-util.c:1288 +#: spa/plugins/alsa/acp/alsa-util.c:1344 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -567,7 +579,7 @@ msgstr "" "Tämä on todennäköisesti ohjelmavirhe ALSA-ajurissa â€%sâ€. Ilmoita tästä " "ongelmasta ALSA-kehittäjille." -#: spa/plugins/alsa/acp/alsa-util.c:1331 +#: spa/plugins/alsa/acp/alsa-util.c:1387 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -590,61 +602,112 @@ msgstr[1] "" "Tämä on todennäköisesti ohjelmavirhe ALSA-ajurissa â€%sâ€. Ilmoita tästä " "ongelmasta ALSA-kehittäjille." -#: spa/plugins/bluez5/bluez5-device.c:1010 +#: spa/plugins/alsa/acp/channelmap.h:457 +msgid "(invalid)" +msgstr "(virheellinen)" + +#: spa/plugins/alsa/acp/compat.c:193 +msgid "Built-in Audio" +msgstr "Sisäinen äänentoisto" + +#: spa/plugins/alsa/acp/compat.c:198 +msgid "Modem" +msgstr "Modeemi" + +#: spa/plugins/bluez5/bluez5-device.c:1707 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Ääniyhdyskäytävä (A2DP-lähde & HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1033 +#: spa/plugins/bluez5/bluez5-device.c:1755 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Korkealaatuinen toisto (A2DP-kohde, %s-koodekki)" -#: spa/plugins/bluez5/bluez5-device.c:1035 +#: spa/plugins/bluez5/bluez5-device.c:1758 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Korkealaatuinen molempisuuntainen (A2DP-lähde/kohde, %s-koodekki)" -#: spa/plugins/bluez5/bluez5-device.c:1041 +#: spa/plugins/bluez5/bluez5-device.c:1766 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Korkealaatuinen toisto (A2DP-kohde)" -#: spa/plugins/bluez5/bluez5-device.c:1043 +#: spa/plugins/bluez5/bluez5-device.c:1768 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Korkealaatuinen molempisuuntainen (A2DP-lähde/kohde)" -#: spa/plugins/bluez5/bluez5-device.c:1070 +#: spa/plugins/bluez5/bluez5-device.c:1818 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "Korkealaatuinen toisto (BAP-kohde, %s-koodekki)" + +#: spa/plugins/bluez5/bluez5-device.c:1823 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "Korkealaatuinen sisääntulo (BAP-lähde, %s-koodekki)" + +#: spa/plugins/bluez5/bluez5-device.c:1827 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "Korkealaatuinen molempisuuntainen (BAP-lähde/kohde, %s-koodekki)" + +#: spa/plugins/bluez5/bluez5-device.c:1836 +msgid "High Fidelity Playback (BAP Sink)" +msgstr "Korkealaatuinen toisto (BAP-kohde)" + +#: spa/plugins/bluez5/bluez5-device.c:1840 +msgid "High Fidelity Input (BAP Source)" +msgstr "Korkealaatuinen sisääntulo (BAP-lähde)" + +#: spa/plugins/bluez5/bluez5-device.c:1843 +msgid "High Fidelity Duplex (BAP Source/Sink)" +msgstr "Korkealaatuinen molempisuuntainen (BAP-lähde/kohde)" + +#: spa/plugins/bluez5/bluez5-device.c:1892 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Kuulokemikrofoni (HSP/HFP, %s-koodekki)" -#: spa/plugins/bluez5/bluez5-device.c:1074 -msgid "Headset Head Unit (HSP/HFP)" -msgstr "Kuulokemikrofoni (HSP/HFP)" - -#: spa/plugins/bluez5/bluez5-device.c:1140 +#: spa/plugins/bluez5/bluez5-device.c:1973 +#: spa/plugins/bluez5/bluez5-device.c:1978 +#: spa/plugins/bluez5/bluez5-device.c:1985 +#: spa/plugins/bluez5/bluez5-device.c:1991 +#: spa/plugins/bluez5/bluez5-device.c:1997 +#: spa/plugins/bluez5/bluez5-device.c:2003 +#: spa/plugins/bluez5/bluez5-device.c:2009 +#: spa/plugins/bluez5/bluez5-device.c:2015 +#: spa/plugins/bluez5/bluez5-device.c:2021 msgid "Handsfree" msgstr "Kuulokemikrofoni" -#: spa/plugins/bluez5/bluez5-device.c:1155 +#: spa/plugins/bluez5/bluez5-device.c:1979 +msgid "Handsfree (HFP)" +msgstr "Kuulokemikrofoni (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:1996 msgid "Headphone" msgstr "Kuulokkeet" -#: spa/plugins/bluez5/bluez5-device.c:1160 +#: spa/plugins/bluez5/bluez5-device.c:2002 msgid "Portable" msgstr "Kannettava" -#: spa/plugins/bluez5/bluez5-device.c:1165 +#: spa/plugins/bluez5/bluez5-device.c:2008 msgid "Car" msgstr "Auto" -#: spa/plugins/bluez5/bluez5-device.c:1170 +#: spa/plugins/bluez5/bluez5-device.c:2014 msgid "HiFi" msgstr "Hi-Fi" -#: spa/plugins/bluez5/bluez5-device.c:1175 +#: spa/plugins/bluez5/bluez5-device.c:2020 msgid "Phone" msgstr "Puhelin" -#: spa/plugins/bluez5/bluez5-device.c:1181 +#: spa/plugins/bluez5/bluez5-device.c:2027 msgid "Bluetooth" msgstr "Bluetooth" + +#: spa/plugins/bluez5/bluez5-device.c:2028 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)" diff --git a/po/id.po b/po/id.po index f6700a8d..f4767be5 100644 --- a/po/id.po +++ b/po/id.po @@ -3,24 +3,24 @@ # This file is distributed under the same license as the pipewire package. # # Translators: -# Andika Triwidada <andika@gmail.com>, 2011, 2012, 2018. +# Andika Triwidada <andika@gmail.com>, 2011, 2012, 2018, 2021, 2024. msgid "" msgstr "" "Project-Id-Version: PipeWire master\n" -"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" -"issues\n" -"POT-Creation-Date: 2021-05-16 13:13+0000\n" -"PO-Revision-Date: 2021-08-11 10:50+0700\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" +"issues/new\n" +"POT-Creation-Date: 2024-02-25 03:43+0300\n" +"PO-Revision-Date: 2024-11-03 14:23+0700\n" "Last-Translator: Andika Triwidada <andika@gmail.com>\n" "Language-Team: Indonesia <id@li.org>\n" "Language: id\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 2.2.1\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Poedit 3.5\n" -#: src/daemon/pipewire.c:45 +#: src/daemon/pipewire.c:26 #, c-format msgid "" "%s [options]\n" @@ -41,38 +41,31 @@ msgstr "Sistem Media PipeWire" msgid "Start the PipeWire Media System" msgstr "Memulai Sistem Media PipeWire" -#: src/examples/media-session/alsa-monitor.c:585 -#: spa/plugins/alsa/acp/compat.c:187 -msgid "Built-in Audio" -msgstr "Audio Bawaan" - -#: src/examples/media-session/alsa-monitor.c:589 -#: spa/plugins/alsa/acp/compat.c:192 -msgid "Modem" -msgstr "Modem" - -#: src/examples/media-session/alsa-monitor.c:598 -#: src/modules/module-zeroconf-discover.c:290 -msgid "Unknown device" -msgstr "Perangkat tak dikenal" - -#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:182 -#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:182 +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format -msgid "Tunnel to %s/%s" -msgstr "Tunnel ke %s/%s" +msgid "Tunnel to %s%s%s" +msgstr "Tunnel ke %s%s%s" -#: src/modules/module-pulse-tunnel.c:511 +#: src/modules/module-fallback-sink.c:40 +msgid "Dummy Output" +msgstr "Keluaran Dummy" + +#: src/modules/module-pulse-tunnel.c:774 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunnel untuk %s@%s" -#: src/modules/module-zeroconf-discover.c:302 +#: src/modules/module-zeroconf-discover.c:315 +msgid "Unknown device" +msgstr "Perangkat tak dikenal" + +#: src/modules/module-zeroconf-discover.c:327 #, c-format msgid "%s on %s@%s" msgstr "%s pada %s@%s" -#: src/modules/module-zeroconf-discover.c:306 +#: src/modules/module-zeroconf-discover.c:331 #, c-format msgid "%s on %s" msgstr "%s pada %s" @@ -80,16 +73,16 @@ msgstr "%s pada %s" #: src/tools/pw-cat.c:991 #, 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 [opsi] <berkas>\n" -" -h, --help Menampilkan bantuan ini\n" -" --version Menampilkan versi\n" -" -v, --verbose Memfungsikan pesan rinci\n" +"%s [opsi] [<berkas>|-]\n" +" -h, --help Tampilkan bantuan ini\n" +" --version Tampilkan versi\n" +" -v, --verbose Fungsikan pesan rinci\n" "\n" #: src/tools/pw-cat.c:998 @@ -99,14 +92,15 @@ msgid "" " --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" +" --target Set node target serial or name " +"(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" -" --list-targets List available targets for --target\n" +" -P --properties Set node properties\n" "\n" msgstr "" " -R, --remote Nama daemon remote\n" @@ -120,8 +114,7 @@ msgstr "" " or cuplikan langsung (256)\n" " laju adalah satu dari berkas " "sumber\n" -" --list-targets Daftar target yang tersedia untuk --" -"target\n" +" -P --properties Atur properti simpul\n" "\n" #: src/tools/pw-cat.c:1016 @@ -149,11 +142,11 @@ msgstr "" "%u)\n" " --channel-map Peta kanal\n" " satu dari: \"stereo\", " -"\"surround-51\",... or\n" +"\"surround-51\",... atau\n" " daftar dipisah koma dari nama " "kanal: mis. \"FL,FR\"\n" -" --format Format cuplikan %s (req. for rec) " -"(baku %s)\n" +" --format Format cuplikan %s (perlu untuk " +"rekam) (baku %s)\n" " --volume Volume stream 0-1.0 (baku %.3f)\n" " -q --quality Kualitas resampler (0 - 15) (baku " "%d)\n" @@ -164,14 +157,18 @@ msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" +" -d, --dsd DSD mode\n" +" -o, --encoded Encoded mode\n" "\n" msgstr "" " -p, --playback Mode main ulang\n" " -r, --record Mode perekaman\n" " -m, --midi Mode midi\n" +" -d, --dsd Mode DSD\n" +" -o, --encoded Mode di-enkode\n" "\n" -#: src/tools/pw-cli.c:2959 +#: src/tools/pw-cli.c:2252 #, c-format msgid "" "%s [options] [command]\n" @@ -179,6 +176,7 @@ msgid "" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" +" -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [opsi] [perintah]\n" @@ -186,197 +184,198 @@ msgstr "" " --version Tampilkan versi\n" " -d, --daemon Mulai sebagai daemon (Baku = false)\n" " -r, --remote Nama daemon remote\n" +" -m, --monitor Monitor activitas\n" "\n" -#: spa/plugins/alsa/acp/acp.c:291 +#: spa/plugins/alsa/acp/acp.c:327 msgid "Pro Audio" msgstr "Pro Audio" -#: spa/plugins/alsa/acp/acp.c:412 spa/plugins/alsa/acp/alsa-mixer.c:4704 -#: spa/plugins/bluez5/bluez5-device.c:1020 +#: spa/plugins/alsa/acp/acp.c:488 spa/plugins/alsa/acp/alsa-mixer.c:4633 +#: spa/plugins/bluez5/bluez5-device.c:1701 msgid "Off" msgstr "Mati" -#: spa/plugins/alsa/acp/alsa-mixer.c:2709 +#: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Masukan" -#: spa/plugins/alsa/acp/alsa-mixer.c:2710 +#: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Masukan Docking Station" -#: spa/plugins/alsa/acp/alsa-mixer.c:2711 +#: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Mikrofon Docking Station" -#: spa/plugins/alsa/acp/alsa-mixer.c:2712 +#: 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: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 "Line In" -#: spa/plugins/alsa/acp/alsa-mixer.c:2714 -#: spa/plugins/alsa/acp/alsa-mixer.c:2798 -#: spa/plugins/bluez5/bluez5-device.c:1175 +#: spa/plugins/alsa/acp/alsa-mixer.c:2657 +#: spa/plugins/alsa/acp/alsa-mixer.c:2741 +#: spa/plugins/bluez5/bluez5-device.c:1989 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 "Mikrofon Depan" -#: 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 "Mikrofon Belakang" -#: spa/plugins/alsa/acp/alsa-mixer.c:2717 +#: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Mikrofon Eksternal" -#: 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 "Mikrofon Internal" -#: 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 "Video" -#: spa/plugins/alsa/acp/alsa-mixer.c:2721 +#: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Kendali Penguatan Otomatis (AGC)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2722 +#: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Tanpa Kendali Penguatan Otomatis (AGC)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2723 +#: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Boost" -#: spa/plugins/alsa/acp/alsa-mixer.c:2724 +#: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Tanpa Boost" -#: spa/plugins/alsa/acp/alsa-mixer.c:2725 +#: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Penguat" -#: spa/plugins/alsa/acp/alsa-mixer.c:2726 +#: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Tanpa Penguat" -#: spa/plugins/alsa/acp/alsa-mixer.c:2727 +#: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Boost Bass" -#: spa/plugins/alsa/acp/alsa-mixer.c:2728 +#: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Tanpa Boost Bass" -#: spa/plugins/alsa/acp/alsa-mixer.c:2729 -#: spa/plugins/bluez5/bluez5-device.c:1180 +#: spa/plugins/alsa/acp/alsa-mixer.c:2672 +#: spa/plugins/bluez5/bluez5-device.c:1995 msgid "Speaker" msgstr "Speaker" -#: 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 "Headphone" -#: spa/plugins/alsa/acp/alsa-mixer.c:2797 +#: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Masukan Analog" -#: spa/plugins/alsa/acp/alsa-mixer.c:2801 +#: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Mikrofon Dok" -#: spa/plugins/alsa/acp/alsa-mixer.c:2803 +#: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Mikrofon Headset" -#: spa/plugins/alsa/acp/alsa-mixer.c:2807 +#: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Keluaran Analog" -#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Headphone 2" -#: spa/plugins/alsa/acp/alsa-mixer.c:2810 +#: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Keluaran Mono Headphone" -#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Line Out" -#: spa/plugins/alsa/acp/alsa-mixer.c:2812 +#: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Keluaran Mono Analog" -#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Speaker" -#: 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 "Keluaran Digital (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Masukan Digital (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2817 +#: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Masukan Multikanal" -#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Keluaran Multikanal" -#: spa/plugins/alsa/acp/alsa-mixer.c:2819 +#: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Keluaran Permainan" -#: 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 "Keluaran Obrolan" -#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Masukan Obrolan" -#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Virtual Surround 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4527 +#: spa/plugins/alsa/acp/alsa-mixer.c:4456 msgid "Analog Mono" msgstr "Analog Mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4528 +#: spa/plugins/alsa/acp/alsa-mixer.c:4457 msgid "Analog Mono (Left)" msgstr "Analog Mono (Kiri)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4529 +#: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono (Right)" msgstr "Analog Mono (Kanan)" @@ -385,147 +384,147 @@ msgstr "Analog Mono (Kanan)" #. * 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:4459 +#: spa/plugins/alsa/acp/alsa-mixer.c:4467 +#: spa/plugins/alsa/acp/alsa-mixer.c:4468 msgid "Analog Stereo" msgstr "Analog Stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4531 +#: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Mono" msgstr "Mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4532 +#: spa/plugins/alsa/acp/alsa-mixer.c:4461 msgid "Stereo" msgstr "Stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4540 -#: spa/plugins/alsa/acp/alsa-mixer.c:4698 -#: spa/plugins/bluez5/bluez5-device.c:1165 +#: spa/plugins/alsa/acp/alsa-mixer.c:4469 +#: spa/plugins/alsa/acp/alsa-mixer.c:4627 +#: spa/plugins/bluez5/bluez5-device.c:1977 msgid "Headset" msgstr "Headset" -#: spa/plugins/alsa/acp/alsa-mixer.c:4541 -#: spa/plugins/alsa/acp/alsa-mixer.c:4699 +#: spa/plugins/alsa/acp/alsa-mixer.c:4470 +#: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Speakerphone" msgstr "Speakerphone" -#: spa/plugins/alsa/acp/alsa-mixer.c:4542 -#: spa/plugins/alsa/acp/alsa-mixer.c:4543 +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Multichannel" msgstr "Multikanal" -#: spa/plugins/alsa/acp/alsa-mixer.c:4544 +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Surround 2.1" msgstr "Analog Surround 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4545 +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Analog Surround 3.0" msgstr "Analog Surround 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4546 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 3.1" msgstr "Analog Surround 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4547 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 4.0" msgstr "Analog Surround 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4548 +#: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 4.1" msgstr "Analog Surround 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4549 +#: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 5.0" msgstr "Analog Surround 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4550 +#: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 5.1" msgstr "Analog Surround 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4551 +#: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 6.0" msgstr "Analog Surround 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4552 +#: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 6.1" msgstr "Analog Surround 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4553 +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 7.0" msgstr "Analog Surround 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4554 +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 7.1" msgstr "Analog Surround 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4555 +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Digital Stereo (IEC958)" msgstr "Digital Stereo (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4556 +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digital Surround 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4557 +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digital Surround 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4558 +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Surround 5.1 Digital (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4559 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Stereo (HDMI)" msgstr "Digital Stereo (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4560 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (HDMI)" msgstr "Surround 5.1 Digital (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4561 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Chat" msgstr "Obrolan" -#: spa/plugins/alsa/acp/alsa-mixer.c:4562 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Game" msgstr "Permainan" -#: spa/plugins/alsa/acp/alsa-mixer.c:4696 +#: spa/plugins/alsa/acp/alsa-mixer.c:4625 msgid "Analog Mono Duplex" -msgstr "Analog Mono Duplex" +msgstr "Dupleks Mono Analog" -#: spa/plugins/alsa/acp/alsa-mixer.c:4697 +#: spa/plugins/alsa/acp/alsa-mixer.c:4626 msgid "Analog Stereo Duplex" -msgstr "Analog Stereo Duplex" +msgstr "Dupleks Stereo Analog" -#: spa/plugins/alsa/acp/alsa-mixer.c:4700 +#: spa/plugins/alsa/acp/alsa-mixer.c:4629 msgid "Digital Stereo Duplex (IEC958)" -msgstr "Digital Stereo Duplex (IEC958)" +msgstr "Dupleks Stereo Digital (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4701 +#: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Multichannel Duplex" msgstr "Dupleks Multikanal" -#: spa/plugins/alsa/acp/alsa-mixer.c:4702 +#: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Stereo Duplex" msgstr "Dupleks Stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4703 +#: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Mono Chat + 7.1 Surround" msgstr "Mono Chat + 7.1 Surround" -#: spa/plugins/alsa/acp/alsa-mixer.c:4806 +#: spa/plugins/alsa/acp/alsa-mixer.c:4733 #, c-format msgid "%s Output" msgstr "Keluaran %s" -#: spa/plugins/alsa/acp/alsa-mixer.c:4813 +#: spa/plugins/alsa/acp/alsa-mixer.c:4741 #, c-format msgid "%s Input" msgstr "Masukan %s" -#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 +#: spa/plugins/alsa/acp/alsa-util.c:1220 spa/plugins/alsa/acp/alsa-util.c:1314 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -542,22 +541,17 @@ msgstr[0] "" "ms).\n" "Sangat mungkin ini adalah kutu pada driver ALSA '%s'. Silakan laporkan hal " "ini ke para pengembang ALSA." -msgstr[1] "" -"snd_pcm_avail() mengembalikan nilai yang luar biasa besar: %lu byte (%lu " -"ms).\n" -"Sangat mungkin ini adalah kutu pada driver ALSA '%s'. Silakan laporkan hal " -"ini ke para pengembang ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1241 +#: spa/plugins/alsa/acp/alsa-util.c:1286 #, 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] "" @@ -565,13 +559,8 @@ msgstr[0] "" "ms).\n" "Sangat mungkin ini adalah kutu pada driver ALSA '%s'. Silakan laporkan hal " "ini ke para pengembang ALSA." -msgstr[1] "" -"snd_pcm_delay() mengembalikan nilai yang luar biasa besar: %li byte (%s%lu " -"ms).\n" -"Sangat mungkin ini adalah kutu pada driver ALSA '%s'. Silakan laporkan hal " -"ini ke para pengembang ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1288 +#: spa/plugins/alsa/acp/alsa-util.c:1333 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -584,7 +573,7 @@ msgstr "" "Paling mungkin ini adalah kutu dalam penggerak ALSA '%s'. Harap laporkan " "kasus ini ke para pengembang ALSA." -#: spa/plugins/alsa/acp/alsa-util.c:1331 +#: spa/plugins/alsa/acp/alsa-util.c:1376 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -601,71 +590,129 @@ msgstr[0] "" "(%lu ms).\n" "Sangat mungkin ini adalah kutu pada driver ALSA '%s'. Silakan laporkan hal " "ini ke para pengembang ALSA." -msgstr[1] "" -"snd_pcm_mmap_begin() mengembalikan nilai yang luar biasa besar: %lu byte " -"(%lu ms).\n" -"Sangat mungkin ini adalah kutu pada driver ALSA '%s'. Silakan laporkan hal " -"ini ke para pengembang ALSA." -#: spa/plugins/alsa/acp/channelmap.h:466 +#: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(tak valid)" -#: spa/plugins/bluez5/bluez5-device.c:1030 +#: spa/plugins/alsa/acp/compat.c:193 +msgid "Built-in Audio" +msgstr "Audio Bawaan" + +#: spa/plugins/alsa/acp/compat.c:198 +msgid "Modem" +msgstr "Modem" + +#: spa/plugins/bluez5/bluez5-device.c:1712 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" -msgstr "Audio Gateway (A2DP Source & HSP/HFP AG)" +msgstr "Audio Gateway (Sumber A2DP & HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1053 +#: spa/plugins/bluez5/bluez5-device.c:1760 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" -msgstr "High Fidelity Playback (A2DP Sink, codec %s)" +msgstr "Putar High Fidelity (Muara A2DP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1055 +#: spa/plugins/bluez5/bluez5-device.c:1763 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" -msgstr "High Fidelity Duplex (A2DP Source/Sink, codec %s)" +msgstr "Dupleks High Fidelity (Sumber/Muara A2DP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1061 +#: spa/plugins/bluez5/bluez5-device.c:1771 msgid "High Fidelity Playback (A2DP Sink)" -msgstr "High Fidelity Playback (A2DP Sink)" +msgstr "Putar High Fidelity (Muara A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1063 +#: spa/plugins/bluez5/bluez5-device.c:1773 msgid "High Fidelity Duplex (A2DP Source/Sink)" -msgstr "High Fidelity Duplex (A2DP Source/Sink)" +msgstr "Dupleks High Fidelity (Sumber/Muara A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1090 +#: spa/plugins/bluez5/bluez5-device.c:1823 #, c-format -msgid "Headset Head Unit (HSP/HFP, codec %s)" -msgstr "Headset Head Unit (HSP/HFP, codec %s)" +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "Putar High Fidelity (Muara BAP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1094 -msgid "Headset Head Unit (HSP/HFP)" -msgstr "Headset Head Unit (HSP/HFP)" +#: spa/plugins/bluez5/bluez5-device.c:1828 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "Masukan High Fidelity (Sumber BAP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1832 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "Dupleks High Fidelity (Sumber/Muara BAP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1841 +msgid "High Fidelity Playback (BAP Sink)" +msgstr "Putar High Fidelity (Muara BAP)" + +#: spa/plugins/bluez5/bluez5-device.c:1845 +msgid "High Fidelity Input (BAP Source)" +msgstr "Masukan High Fidelity (Sumber BAP)" + +#: spa/plugins/bluez5/bluez5-device.c:1848 +msgid "High Fidelity Duplex (BAP Source/Sink)" +msgstr "Dupleks High Fidelity (Sumber/Muara BAP)" -#: spa/plugins/bluez5/bluez5-device.c:1170 +#: spa/plugins/bluez5/bluez5-device.c:1897 +#, c-format +msgid "Headset Head Unit (HSP/HFP, codec %s)" +msgstr "Headset Head Unit (HSP/HFP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1978 +#: spa/plugins/bluez5/bluez5-device.c:1983 +#: spa/plugins/bluez5/bluez5-device.c:1990 +#: spa/plugins/bluez5/bluez5-device.c:1996 +#: spa/plugins/bluez5/bluez5-device.c:2002 +#: spa/plugins/bluez5/bluez5-device.c:2008 +#: spa/plugins/bluez5/bluez5-device.c:2014 +#: spa/plugins/bluez5/bluez5-device.c:2020 +#: spa/plugins/bluez5/bluez5-device.c:2026 msgid "Handsfree" msgstr "Handsfree" -#: spa/plugins/bluez5/bluez5-device.c:1185 +#: spa/plugins/bluez5/bluez5-device.c:1984 +msgid "Handsfree (HFP)" +msgstr "Handsfree (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:2001 msgid "Headphone" msgstr "Headphone" -#: spa/plugins/bluez5/bluez5-device.c:1190 +#: spa/plugins/bluez5/bluez5-device.c:2007 msgid "Portable" msgstr "Portabel" -#: spa/plugins/bluez5/bluez5-device.c:1195 +#: spa/plugins/bluez5/bluez5-device.c:2013 msgid "Car" msgstr "Mobil" -#: spa/plugins/bluez5/bluez5-device.c:1200 +#: spa/plugins/bluez5/bluez5-device.c:2019 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1205 +#: spa/plugins/bluez5/bluez5-device.c:2025 msgid "Phone" msgstr "Telepon" -#: spa/plugins/bluez5/bluez5-device.c:1211 +#: spa/plugins/bluez5/bluez5-device.c:2032 msgid "Bluetooth" msgstr "Bluetooth" + +#: spa/plugins/bluez5/bluez5-device.c:2033 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)" + +#, c-format +#~ msgid "" +#~ "%s [options]\n" +#~ " -h, --help Show this help\n" +#~ " -v, --verbose Increase verbosity by one level\n" +#~ " --version Show version\n" +#~ " -c, --config Load config (Default %s)\n" +#~ " -P --properties Set context properties\n" +#~ msgstr "" +#~ "%s [opsi]\n" +#~ " -h, --help Tampilkan bantuan ini\n" +#~ " -v, --verbose Tingkatkan kerincian satu aras\n" +#~ " --version Tampilkan versi\n" +#~ " -c, --config Muat konfig (Baku %s)\n" +#~ " -P --properties Atur properti konteks\n" diff --git a/po/nl.po b/po/nl.po index 733b7eec..b1e5695a 100644 --- a/po/nl.po +++ b/po/nl.po @@ -257,7 +257,7 @@ msgstr "Analoge mono-uitvoer" #: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" -msgstr "Lijn-in" +msgstr "Lijn-uit" #: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" diff --git a/po/pl.po b/po/pl.po index 297e2fb7..6d6572a3 100644 --- a/po/pl.po +++ b/po/pl.po @@ -1,15 +1,15 @@ # Polish translation for pipewire. -# Copyright © 2008-2023 the pipewire authors. +# Copyright © 2008-2025 the pipewire authors. # This file is distributed under the same license as the pipewire package. -# Piotr DrÄ…g <piotrdrag@gmail.com>, 2008, 2012-2023. +# Piotr DrÄ…g <piotrdrag@gmail.com>, 2008, 2012-2025. # msgid "" msgstr "" "Project-Id-Version: pipewire\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2023-05-28 10:45+0000\n" -"PO-Revision-Date: 2023-05-28 12:48+0200\n" +"POT-Creation-Date: 2025-01-09 15:25+0000\n" +"PO-Revision-Date: 2025-02-09 14:55+0100\n" "Last-Translator: Piotr DrÄ…g <piotrdrag@gmail.com>\n" "Language-Team: Polish <community-poland@mozilla.org>\n" "Language: pl\n" @@ -19,19 +19,24 @@ msgstr "" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" -#: src/daemon/pipewire.c:26 +#: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" +" -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" +" -P --properties Set context properties\n" msgstr "" "%s [opcje]\n" " -h, --help WyÅ›wietla tÄ™ pomoc\n" +" -v, --verbose ZwiÄ™ksza liczbÄ™ wyÅ›wietlanych\n" +" komunikatów o jeden poziom\n" " --version WyÅ›wietla wersjÄ™\n" " -c, --config Wczytuje konfiguracjÄ™ (domyÅ›lnie " "%s)\n" +" -P --properties Ustawia wÅ‚aÅ›ciwoÅ›ci kontekstu\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" @@ -41,36 +46,36 @@ 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:141 -#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:141 +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "Tunel do %s%s%s" -#: src/modules/module-fallback-sink.c:31 +#: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "GÅ‚uche wyjÅ›cie" -#: src/modules/module-pulse-tunnel.c:844 +#: src/modules/module-pulse-tunnel.c:760 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunel dla %s@%s" -#: src/modules/module-zeroconf-discover.c:315 +#: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Nieznane urzÄ…dzenie" -#: src/modules/module-zeroconf-discover.c:327 +#: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%s na %s@%s" -#: src/modules/module-zeroconf-discover.c:331 +#: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%s na %s" -#: src/tools/pw-cat.c:974 +#: src/tools/pw-cat.c:973 #, c-format msgid "" "%s [options] [<file>|-]\n" @@ -85,7 +90,7 @@ msgstr "" " -v, --verbose WyÅ›wietla wiÄ™cej komunikatów\n" "\n" -#: src/tools/pw-cat.c:981 +#: src/tools/pw-cat.c:980 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -123,7 +128,7 @@ msgstr "" " -P --properties Ustawia wÅ‚aÅ›ciwoÅ›ci wÄ™zÅ‚a\n" "\n" -#: src/tools/pw-cat.c:999 +#: src/tools/pw-cat.c:998 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -140,6 +145,7 @@ msgid "" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" +" -a, --raw RAW mode\n" "\n" msgstr "" " --rate CzÄ™stotliwość próbki (wymagane do " @@ -157,6 +163,7 @@ msgstr "" "(domyÅ›lnie %.3f)\n" " -q --quality Jakość resamplera od 0 do 15 " "(domyÅ›lnie %d)\n" +" -a, --raw Tryb RAW\n" "\n" #: src/tools/pw-cat.c:1016 @@ -175,7 +182,7 @@ msgstr "" " -o, --encoded Tryb zakodowany\n" "\n" -#: src/tools/pw-cli.c:2220 +#: src/tools/pw-cli.c:2306 #, c-format msgid "" "%s [options] [command]\n" @@ -195,12 +202,12 @@ msgstr "" " -m, --monitor Monitoruje aktywność\n" "\n" -#: spa/plugins/alsa/acp/acp.c:303 +#: spa/plugins/alsa/acp/acp.c:347 msgid "Pro Audio" msgstr "DźwiÄ™k w zastosowaniach profesjonalnych" -#: spa/plugins/alsa/acp/acp.c:427 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1586 +#: spa/plugins/alsa/acp/acp.c:507 spa/plugins/alsa/acp/alsa-mixer.c:4635 +#: spa/plugins/bluez5/bluez5-device.c:1795 msgid "Off" msgstr "WyÅ‚Ä…czone" @@ -227,7 +234,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:1831 +#: spa/plugins/bluez5/bluez5-device.c:2139 msgid "Microphone" msgstr "Mikrofon" @@ -293,7 +300,7 @@ msgid "No Bass Boost" msgstr "Brak podbicia basów" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1837 +#: spa/plugins/bluez5/bluez5-device.c:2145 msgid "Speaker" msgstr "GÅ‚oÅ›nik" @@ -375,15 +382,15 @@ msgstr "WejÅ›cie rozmowy" msgid "Virtual Surround 7.1" msgstr "Wirtualne przestrzenne 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4471 +#: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono" msgstr "Analogowe mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4472 +#: spa/plugins/alsa/acp/alsa-mixer.c:4459 msgid "Analog Mono (Left)" msgstr "Analogowe mono (lewy)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4473 +#: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Analog Mono (Right)" msgstr "Analogowe mono (prawy)" @@ -392,147 +399,147 @@ msgstr "Analogowe mono (prawy)" #. * 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 +#: spa/plugins/alsa/acp/alsa-mixer.c:4461 +#: spa/plugins/alsa/acp/alsa-mixer.c:4469 +#: spa/plugins/alsa/acp/alsa-mixer.c:4470 msgid "Analog Stereo" msgstr "Analogowe stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4475 +#: spa/plugins/alsa/acp/alsa-mixer.c:4462 msgid "Mono" msgstr "Mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4476 +#: spa/plugins/alsa/acp/alsa-mixer.c:4463 msgid "Stereo" msgstr "Stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4484 -#: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1819 +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 +#: spa/plugins/alsa/acp/alsa-mixer.c:4629 +#: spa/plugins/bluez5/bluez5-device.c:2127 msgid "Headset" msgstr "SÅ‚uchawki z mikrofonem" -#: spa/plugins/alsa/acp/alsa-mixer.c:4485 -#: spa/plugins/alsa/acp/alsa-mixer.c:4643 +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 +#: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Speakerphone" msgstr "Telefon gÅ‚oÅ›nomówiÄ…cy" -#: spa/plugins/alsa/acp/alsa-mixer.c:4486 -#: spa/plugins/alsa/acp/alsa-mixer.c:4487 +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Multichannel" msgstr "WielokanaÅ‚owe" -#: spa/plugins/alsa/acp/alsa-mixer.c:4488 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 2.1" msgstr "Analogowe przestrzenne 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4489 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 3.0" msgstr "Analogowe przestrzenne 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4490 +#: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 3.1" msgstr "Analogowe przestrzenne 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4491 +#: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 4.0" msgstr "Analogowe przestrzenne 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4492 +#: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 4.1" msgstr "Analogowe przestrzenne 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4493 +#: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 5.0" msgstr "Analogowe przestrzenne 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4494 +#: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 5.1" msgstr "Analogowe przestrzenne 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4495 +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 6.0" msgstr "Analogowe przestrzenne 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4496 +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 6.1" msgstr "Analogowe przestrzenne 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4497 +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Analog Surround 7.0" msgstr "Analogowe przestrzenne 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4498 +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Analog Surround 7.1" msgstr "Analogowe przestrzenne 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4499 +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Stereo (IEC958)" msgstr "Cyfrowe stereo (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4500 +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Cyfrowe przestrzenne 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4501 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Cyfrowe przestrzenne 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4502 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Cyfrowe przestrzenne 5.1 (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4503 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Digital Stereo (HDMI)" msgstr "Cyfrowe stereo (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4504 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Digital Surround 5.1 (HDMI)" msgstr "Cyfrowe przestrzenne 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4505 +#: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Chat" msgstr "Rozmowa" -#: spa/plugins/alsa/acp/alsa-mixer.c:4506 +#: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Game" msgstr "Gra" -#: spa/plugins/alsa/acp/alsa-mixer.c:4640 +#: spa/plugins/alsa/acp/alsa-mixer.c:4627 msgid "Analog Mono Duplex" msgstr "Analogowy dupleks mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4641 +#: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Analog Stereo Duplex" msgstr "Analogowy dupleks stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4644 +#: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Digital Stereo Duplex (IEC958)" msgstr "Cyfrowy dupleks stereo (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4645 +#: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Multichannel Duplex" msgstr "Dupleks wielokanaÅ‚owy" -#: spa/plugins/alsa/acp/alsa-mixer.c:4646 +#: spa/plugins/alsa/acp/alsa-mixer.c:4633 msgid "Stereo Duplex" msgstr "Dupleks stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4647 +#: spa/plugins/alsa/acp/alsa-mixer.c:4634 msgid "Mono Chat + 7.1 Surround" msgstr "Rozmowa mono + przestrzenne 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4748 +#: spa/plugins/alsa/acp/alsa-mixer.c:4735 #, c-format msgid "%s Output" msgstr "WyjÅ›cie %s" -#: spa/plugins/alsa/acp/alsa-mixer.c:4756 +#: spa/plugins/alsa/acp/alsa-mixer.c:4743 #, c-format msgid "%s Input" msgstr "WejÅ›cie %s" -#: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 +#: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -557,7 +564,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:1253 +#: spa/plugins/alsa/acp/alsa-util.c:1299 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " @@ -582,7 +589,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:1300 +#: spa/plugins/alsa/acp/alsa-util.c:1346 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -595,7 +602,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:1343 +#: spa/plugins/alsa/acp/alsa-util.c:1389 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -624,112 +631,112 @@ msgstr[2] "" msgid "(invalid)" msgstr "(nieprawidÅ‚owe)" -#: spa/plugins/alsa/acp/compat.c:189 +#: spa/plugins/alsa/acp/compat.c:193 msgid "Built-in Audio" msgstr "Wbudowany dźwiÄ™k" -#: spa/plugins/alsa/acp/compat.c:194 +#: spa/plugins/alsa/acp/compat.c:198 msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1597 +#: spa/plugins/bluez5/bluez5-device.c:1806 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:1622 +#: spa/plugins/bluez5/bluez5-device.c:1834 +msgid "Audio Streaming for Hearing Aids (ASHA Sink)" +msgstr "PrzesyÅ‚anie dźwiÄ™ku do aparatów sÅ‚uchowych (odpÅ‚yw ASHA)" + +#: spa/plugins/bluez5/bluez5-device.c:1874 #, 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:1625 +#: spa/plugins/bluez5/bluez5-device.c:1877 #, 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:1633 +#: spa/plugins/bluez5/bluez5-device.c:1885 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Odtwarzanie o wysokiej dokÅ‚adnoÅ›ci (odpÅ‚yw A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:1635 +#: spa/plugins/bluez5/bluez5-device.c:1887 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:1677 +#: spa/plugins/bluez5/bluez5-device.c:1937 #, 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:1681 +#: spa/plugins/bluez5/bluez5-device.c:1942 #, 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:1685 +#: spa/plugins/bluez5/bluez5-device.c:1946 #, 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:1693 +#: spa/plugins/bluez5/bluez5-device.c:1955 msgid "High Fidelity Playback (BAP Sink)" msgstr "Odtwarzanie o wysokiej dokÅ‚adnoÅ›ci (odpÅ‚yw BAP)" -#: spa/plugins/bluez5/bluez5-device.c:1696 +#: spa/plugins/bluez5/bluez5-device.c:1959 msgid "High Fidelity Input (BAP Source)" msgstr "WejÅ›cie o wysokiej dokÅ‚adnoÅ›ci (źródÅ‚o BAP)" -#: spa/plugins/bluez5/bluez5-device.c:1699 +#: spa/plugins/bluez5/bluez5-device.c:1962 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Dupleks o wysokiej dokÅ‚adnoÅ›ci (źródÅ‚o/odpÅ‚yw BAP)" -#: spa/plugins/bluez5/bluez5-device.c:1735 +#: spa/plugins/bluez5/bluez5-device.c:2008 #, 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:1740 -msgid "Headset Head Unit (HSP/HFP)" -msgstr "Jednostka główna sÅ‚uchawek z mikrofonem (HSP/HFP)" - -#: spa/plugins/bluez5/bluez5-device.c:1820 -#: spa/plugins/bluez5/bluez5-device.c:1825 -#: spa/plugins/bluez5/bluez5-device.c:1832 -#: spa/plugins/bluez5/bluez5-device.c:1838 -#: spa/plugins/bluez5/bluez5-device.c:1844 -#: spa/plugins/bluez5/bluez5-device.c:1850 -#: spa/plugins/bluez5/bluez5-device.c:1856 -#: spa/plugins/bluez5/bluez5-device.c:1862 -#: spa/plugins/bluez5/bluez5-device.c:1868 +#: spa/plugins/bluez5/bluez5-device.c:2128 +#: spa/plugins/bluez5/bluez5-device.c:2133 +#: spa/plugins/bluez5/bluez5-device.c:2140 +#: spa/plugins/bluez5/bluez5-device.c:2146 +#: spa/plugins/bluez5/bluez5-device.c:2152 +#: spa/plugins/bluez5/bluez5-device.c:2158 +#: spa/plugins/bluez5/bluez5-device.c:2164 +#: spa/plugins/bluez5/bluez5-device.c:2170 +#: spa/plugins/bluez5/bluez5-device.c:2176 msgid "Handsfree" msgstr "Zestaw gÅ‚oÅ›nomówiÄ…cy" -#: spa/plugins/bluez5/bluez5-device.c:1826 +#: spa/plugins/bluez5/bluez5-device.c:2134 msgid "Handsfree (HFP)" msgstr "Zestaw gÅ‚oÅ›nomówiÄ…cy (HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1843 +#: spa/plugins/bluez5/bluez5-device.c:2151 msgid "Headphone" msgstr "SÅ‚uchawki" -#: spa/plugins/bluez5/bluez5-device.c:1849 +#: spa/plugins/bluez5/bluez5-device.c:2157 msgid "Portable" msgstr "PrzenoÅ›ne" -#: spa/plugins/bluez5/bluez5-device.c:1855 +#: spa/plugins/bluez5/bluez5-device.c:2163 msgid "Car" msgstr "Samochód" -#: spa/plugins/bluez5/bluez5-device.c:1861 +#: spa/plugins/bluez5/bluez5-device.c:2169 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1867 +#: spa/plugins/bluez5/bluez5-device.c:2175 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:1874 +#: spa/plugins/bluez5/bluez5-device.c:2182 msgid "Bluetooth" msgstr "Bluetooth" -#: spa/plugins/bluez5/bluez5-device.c:1875 +#: spa/plugins/bluez5/bluez5-device.c:2183 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" diff --git a/po/sl.po b/po/sl.po new file mode 100644 index 00000000..de3f9452 --- /dev/null +++ b/po/sl.po @@ -0,0 +1,766 @@ +# Slovenian translation for PipeWire. +# Copyright (C) 2024 PipeWire's COPYRIGHT HOLDER +# This file is distributed under the same license as the PipeWire package. +# +# Martin <miles@filmsi.net>, 2024, 2025. +# +msgid "" +msgstr "" +"Project-Id-Version: PipeWire master\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" +"issues\n" +"POT-Creation-Date: 2025-01-09 15:25+0000\n" +"PO-Revision-Date: 2025-01-23 09:23+0100\n" +"Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n" +"Language-Team: Slovenian <gnome-si@googlegroups.com>\n" +"Language: sl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100>=3 && n" +"%100<=4 ? 2 : 3);\n" +"X-Generator: Poedit 2.2.1\n" + +#: src/daemon/pipewire.c:29 +#, c-format +msgid "" +"%s [options]\n" +" -h, --help Show this help\n" +" -v, --verbose Increase verbosity by one level\n" +" --version Show version\n" +" -c, --config Load config (Default %s)\n" +" -P --properties Set context properties\n" +msgstr "" +"%s [možnosti]\n" +" -h, --help Pokaži to pomoÄ\n" +" -v, --verbose PoveÄaj opisnost za eno raven\n" +" --version Pokaži razliÄico\n" +" -c, --config Naloži prilagoditev config (privzeto " +"%s)\n" +" -P —properties DoloÄi lastnosti konteksta\n" + +#: src/daemon/pipewire.desktop.in:4 +msgid "PipeWire Media System" +msgstr "Medijski sistem PipeWire" + +#: src/daemon/pipewire.desktop.in:5 +msgid "Start the PipeWire Media System" +msgstr "Zaženite medijski sistem PipeWire" + +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 +#, c-format +msgid "Tunnel to %s%s%s" +msgstr "Prehod do %s%s%s" + +#: src/modules/module-fallback-sink.c:40 +msgid "Dummy Output" +msgstr "Lažni izhod" + +#: src/modules/module-pulse-tunnel.c:760 +#, c-format +msgid "Tunnel for %s@%s" +msgstr "Prehod za %s@%s" + +#: src/modules/module-zeroconf-discover.c:320 +msgid "Unknown device" +msgstr "Neznana naprava" + +#: src/modules/module-zeroconf-discover.c:332 +#, c-format +msgid "%s on %s@%s" +msgstr "%s na %s@%s" + +#: src/modules/module-zeroconf-discover.c:336 +#, c-format +msgid "%s on %s" +msgstr "%s na %s" + +#: src/tools/pw-cat.c:973 +#, 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 [možnosti] [<datoteka>|-]\n" +" -h, --help Pokaži to pomoÄ\n" +" --version Pokaži razliÄico\n" +" -v, --verbose OmogoÄi podrobno opisane operacije\n" +"\n" +"</file>\n" + +#: src/tools/pw-cat.c:980 +#, 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 serial or name " +"(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 Ime oddaljenega demona\n" +" --media-type Nastavitev vrste medija (privzeto " +"%s)\n" +" --media-category Nastavi kategorijo predstavnosti " +"(privzeto %s)\n" +" --media-role Nastavi vlogo predstavnosti " +"(privzeto %s)\n" +" --target Nastavi serijsko ali ime ciljnega " +"vozliÅ¡Äa (privzeto %s)\n" +" 0 pomeni, da se ne poveže\n" +" --latency Nastavi zakasnitev vozliÅ¡Äa " +"(privzeto %s)\n" +" Xunit (enota = s, ms, us, ns)\n" +" ali neposredni vzorci (256)\n" +" Hitrost je enaka tisti v izvornih " +"datotekah\n" +" -P --properties Nastavi lastnosti vozliÅ¡Äa\n" +"\n" + +#: src/tools/pw-cat.c:998 +#, 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" +" -a, --raw RAW mode\n" +"\n" +msgstr "" +" --rate Mera vzorÄenja (zahtevano za rec) " +"(privzeto %u)\n" +" --channels Å tevilo kanalov (zahteva za " +"snemanje) (privzeto %u)\n" +" --channel-map Preslikava kanalov\n" +" Ena izmed: \"Stereo\", " +"\"surround-51\",... ali\n" +" seznam imen kanalov, loÄen z " +"vejico: npr. \"FL,FR\"\n" +" --format VzorÄne oblike zapisa %s (zahtevano " +"za rec) (privzeto %s)\n" +" --volume Glasnost toka 0-1.0 (privzeto %.3f)\n" +" -q --quality Kakovost prevzorÄenja (0 - 15) " +"(privzeto %d)\n" +" -a, --raw neobdelan naÄin (RAW)\n" +"\n" +"\n" + +#: src/tools/pw-cat.c:1016 +msgid "" +" -p, --playback Playback mode\n" +" -r, --record Recording mode\n" +" -m, --midi Midi mode\n" +" -d, --dsd DSD mode\n" +" -o, --encoded Encoded mode\n" +"\n" +msgstr "" +" -p, --playback NaÄin predvajanja\n" +" -r, --record NaÄin snemanja\n" +" -m, --midi Midi naÄin\n" +" -d, --dsd NaÄin DSD\n" +" -o, --encoded Kodiran naÄin\n" +"\n" + +#: src/tools/pw-cli.c:2306 +#, 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" +" -m, --monitor Monitor activity\n" +"\n" +msgstr "" +"%s [možnosti] [ukaz]\n" +" -h, --help Pokaži to pomoÄ\n" +" --version Pokaži razliÄico\n" +" -d, --daemon ZaÄni kot zaledni proces (privzeto " +"false)\n" +" -r, --remote Ime oddaljenega zalednega procesa\n" +" -m, --monitor Spremljaj dejavnosti\n" +"\n" + +#: spa/plugins/alsa/acp/acp.c:347 +msgid "Pro Audio" +msgstr "Profesionalni zvok" + +#: spa/plugins/alsa/acp/acp.c:507 spa/plugins/alsa/acp/alsa-mixer.c:4635 +#: spa/plugins/bluez5/bluez5-device.c:1795 +msgid "Off" +msgstr "Izklopljeno" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2652 +msgid "Input" +msgstr "Vhod" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2653 +msgid "Docking Station Input" +msgstr "Vhod priklopne postaje" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2654 +msgid "Docking Station Microphone" +msgstr "Mikrofon priklopne postaje" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2655 +msgid "Docking Station Line In" +msgstr "Linijski vhod priklopne postaje" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2656 +#: spa/plugins/alsa/acp/alsa-mixer.c:2747 +msgid "Line In" +msgstr "Linijski vhod" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2657 +#: spa/plugins/alsa/acp/alsa-mixer.c:2741 +#: spa/plugins/bluez5/bluez5-device.c:2139 +msgid "Microphone" +msgstr "Mikrofon" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2658 +#: spa/plugins/alsa/acp/alsa-mixer.c:2742 +msgid "Front Microphone" +msgstr "Sprednji mikrofon" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2659 +#: spa/plugins/alsa/acp/alsa-mixer.c:2743 +msgid "Rear Microphone" +msgstr "Zadnji mikrofon" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2660 +msgid "External Microphone" +msgstr "Zunanji mikrofon" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2661 +#: spa/plugins/alsa/acp/alsa-mixer.c:2745 +msgid "Internal Microphone" +msgstr "Notranji mikrofon" + +#: 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:2663 +#: spa/plugins/alsa/acp/alsa-mixer.c:2749 +msgid "Video" +msgstr "Video" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2664 +msgid "Automatic Gain Control" +msgstr "Samodejni nadzor ojaÄanja" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2665 +msgid "No Automatic Gain Control" +msgstr "Brez samodejnega nadzora ojaÄanja" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2666 +msgid "Boost" +msgstr "OjaÄitev" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2667 +msgid "No Boost" +msgstr "Brez ojaÄitve" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2668 +msgid "Amplifier" +msgstr "OjaÄevalnik" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2669 +msgid "No Amplifier" +msgstr "Brez ojaÄevalnika" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2670 +msgid "Bass Boost" +msgstr "OjaÄitev nizkih tonov" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2671 +msgid "No Bass Boost" +msgstr "Brez ojaÄitve nizkih tonov" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2672 +#: spa/plugins/bluez5/bluez5-device.c:2145 +msgid "Speaker" +msgstr "ZvoÄnik" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2673 +#: spa/plugins/alsa/acp/alsa-mixer.c:2751 +msgid "Headphones" +msgstr "SluÅ¡alke" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2740 +msgid "Analog Input" +msgstr "Analogni vhod" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2744 +msgid "Dock Microphone" +msgstr "Priklopni mikrofon" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2746 +msgid "Headset Microphone" +msgstr "Mikrofon s sluÅ¡alkami" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2750 +msgid "Analog Output" +msgstr "Analogni izhod" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2752 +msgid "Headphones 2" +msgstr "SluÅ¡alke 2" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2753 +msgid "Headphones Mono Output" +msgstr "Mono izhod sluÅ¡alk" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2754 +msgid "Line Out" +msgstr "Linijsk izhod" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2755 +msgid "Analog Mono Output" +msgstr "Analogni mono izhod" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2756 +msgid "Speakers" +msgstr "Govorniki" + +#: 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 "Digitalni izhod (S/PDIF)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2759 +msgid "Digital Input (S/PDIF)" +msgstr "Digitalni vhod (S/PDIF)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2760 +msgid "Multichannel Input" +msgstr "VeÄkanalni vhod" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2761 +msgid "Multichannel Output" +msgstr "VeÄkanalni izhod" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2762 +msgid "Game Output" +msgstr "Vhod igre" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2763 +#: spa/plugins/alsa/acp/alsa-mixer.c:2764 +msgid "Chat Output" +msgstr "Izhod klepeta" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2765 +msgid "Chat Input" +msgstr "Vhod klepeta" + +#: spa/plugins/alsa/acp/alsa-mixer.c:2766 +msgid "Virtual Surround 7.1" +msgstr "Navidezni prostorski zvok 7.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4458 +msgid "Analog Mono" +msgstr "Analogni mono" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4459 +msgid "Analog Mono (Left)" +msgstr "Analogni mono (levo)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4460 +msgid "Analog Mono (Right)" +msgstr "Analogni mono (desno)" + +#. 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:4461 +#: spa/plugins/alsa/acp/alsa-mixer.c:4469 +#: spa/plugins/alsa/acp/alsa-mixer.c:4470 +msgid "Analog Stereo" +msgstr "Analogni stereo" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4462 +msgid "Mono" +msgstr "Mono" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4463 +msgid "Stereo" +msgstr "Stereo" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 +#: spa/plugins/alsa/acp/alsa-mixer.c:4629 +#: spa/plugins/bluez5/bluez5-device.c:2127 +msgid "Headset" +msgstr "SluÅ¡alka" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 +#: spa/plugins/alsa/acp/alsa-mixer.c:4630 +msgid "Speakerphone" +msgstr "ZvoÄnik telefona" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 +msgid "Multichannel" +msgstr "VeÄkanalno" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 +msgid "Analog Surround 2.1" +msgstr "Analogni prostorski zvok 2.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 +msgid "Analog Surround 3.0" +msgstr "Analogni prostorski zvok 3.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4477 +msgid "Analog Surround 3.1" +msgstr "Analogni prostorski zvok 3.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4478 +msgid "Analog Surround 4.0" +msgstr "Analogni prostorski zvok 4.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4479 +msgid "Analog Surround 4.1" +msgstr "Analogni prostorski zvok 4.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4480 +msgid "Analog Surround 5.0" +msgstr "Analogni prostorski zvok 5.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4481 +msgid "Analog Surround 5.1" +msgstr "Analogni prostorski zvok 5.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 +msgid "Analog Surround 6.0" +msgstr "Analogni prostorski zvok 6.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 +msgid "Analog Surround 6.1" +msgstr "Analogni prostorski zvok 6.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 +msgid "Analog Surround 7.0" +msgstr "Analogni prostorski zvok 7.0" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 +msgid "Analog Surround 7.1" +msgstr "Analogni prostorski zvok 7.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 +msgid "Digital Stereo (IEC958)" +msgstr "Digitalni stereo (IEC958)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 +msgid "Digital Surround 4.0 (IEC958/AC3)" +msgstr "Digitalni prostorski zvok 4.0 (IEC958/AC3)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 +msgid "Digital Surround 5.1 (IEC958/AC3)" +msgstr "Digitalni prostorski zvok 5.1 (IEC958/AC3)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 +msgid "Digital Surround 5.1 (IEC958/DTS)" +msgstr "Digitalni prostorski zvok 5.1 (IEC958/DTS)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 +msgid "Digital Stereo (HDMI)" +msgstr "Digitalni stereo (HDMI)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 +msgid "Digital Surround 5.1 (HDMI)" +msgstr "Digitalni prostorski zvok 5.1 (HDMI)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4492 +msgid "Chat" +msgstr "Klepet" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4493 +msgid "Game" +msgstr "Igra" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4627 +msgid "Analog Mono Duplex" +msgstr "Analogni mono dupleks" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4628 +msgid "Analog Stereo Duplex" +msgstr "Analogni stereo dupleks" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4631 +msgid "Digital Stereo Duplex (IEC958)" +msgstr "Digitalni stereo dupleks (IEC958)" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4632 +msgid "Multichannel Duplex" +msgstr "VeÄkanalni dupleks" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4633 +msgid "Stereo Duplex" +msgstr "Stereo dupleks" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4634 +msgid "Mono Chat + 7.1 Surround" +msgstr "Mono klepet + prostorski zvok 7.1" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4735 +#, c-format +msgid "%s Output" +msgstr "Izhod %s" + +#: spa/plugins/alsa/acp/alsa-mixer.c:4743 +#, c-format +msgid "%s Input" +msgstr "Vhod %s" + +#: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 +#, 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() je vrnil vrednost, ki je izjemno velika: %lu bajt (%lu ms).\n" +"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " +"razvijalce ALSA." +msgstr[1] "" +"snd_pcm_avail() je vrnil vrednost, ki je izjemno velika: %lu bajta (%lu " +"ms).\n" +"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " +"razvijalce ALSA." +msgstr[2] "" +"snd_pcm_avail() je vrnil vrednost, ki je izjemno velika: %lu bajti (%lu " +"ms).\n" +"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " +"razvijalce ALSA." +msgstr[3] "" +"snd_pcm_avail() je vrnil vrednost, ki je izjemno velika: %lu bajtov (%lu " +"ms).\n" +"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " +"razvijalce ALSA." + +#: spa/plugins/alsa/acp/alsa-util.c:1299 +#, 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() je vrnil vrednost, ki je izjemno velika: %li bajt (%s%lu " +"ms).\n" +"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " +"razvijalce ALSA." +msgstr[1] "" +"snd_pcm_delay() je vrnil vrednost, ki je izjemno velika: %li bajta (%s%lu " +"ms).\n" +"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " +"razvijalce ALSA." +msgstr[2] "" +"snd_pcm_delay() je vrnil vrednost, ki je izjemno velika: %li bajti (%s%lu " +"ms).\n" +"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " +"razvijalce ALSA." +msgstr[3] "" +"snd_pcm_delay() je vrnil vrednost, ki je izjemno velika: %li bajtov (%s%lu " +"ms).\n" +"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " +"razvijalce ALSA." + +#: spa/plugins/alsa/acp/alsa-util.c:1346 +#, 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() je vrnil nenavadne vrednosti: zakasnitev %lu je manjÅ¡a " +"kot razpoložljiva %lu.\n" +"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " +"razvijalce ALSA." + +#: spa/plugins/alsa/acp/alsa-util.c:1389 +#, 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() je vrnil vrednost, ki je izjemno velika: %lu bajt (%lu " +"ms).\n" +"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " +"razvijalce ALSA." +msgstr[1] "" +"snd_pcm_mmap_begin() je vrnil vrednost, ki je izjemno velika: %lu bajta (%lu " +"ms).\n" +"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " +"razvijalce ALSA." +msgstr[2] "" +"snd_pcm_mmap_begin() je vrnil vrednost, ki je izjemno velika: %lu bajti (%lu " +"ms).\n" +"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " +"razvijalce ALSA." +msgstr[3] "" +"snd_pcm_mmap_begin() je vrnil vrednost, ki je izjemno velika: %lu bajtov " +"(%lu ms).\n" +"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite " +"razvijalce ALSA." + +#: spa/plugins/alsa/acp/channelmap.h:457 +msgid "(invalid)" +msgstr "(neveljavno)" + +#: spa/plugins/alsa/acp/compat.c:193 +msgid "Built-in Audio" +msgstr "Vgrajen zvok" + +#: spa/plugins/alsa/acp/compat.c:198 +msgid "Modem" +msgstr "Modem" + +#: spa/plugins/bluez5/bluez5-device.c:1806 +msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" +msgstr "Zvožni prehod (vir A2DP in HSP/HFP AG)" + +#: spa/plugins/bluez5/bluez5-device.c:1834 +msgid "Audio Streaming for Hearing Aids (ASHA Sink)" +msgstr "Pretakanje zvoka za sluÅ¡ne aparate (ponor ASHA)" + +#: spa/plugins/bluez5/bluez5-device.c:1874 +#, c-format +msgid "High Fidelity Playback (A2DP Sink, codec %s)" +msgstr "Predvajanje visoke loÄljivosti (ponor A2DP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1877 +#, c-format +msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" +msgstr "Dupleks visoke loÄljivosti (vir/ponor A2DP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1885 +msgid "High Fidelity Playback (A2DP Sink)" +msgstr "Predvajanje visoke loÄljivosti (ponor A2DP)" + +#: spa/plugins/bluez5/bluez5-device.c:1887 +msgid "High Fidelity Duplex (A2DP Source/Sink)" +msgstr "Dupleks visoke loÄljivosti (vir/ponor A2DP)" + +#: spa/plugins/bluez5/bluez5-device.c:1937 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "Predvajanje visoke loÄljivosti (ponor BAP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1942 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "Vhod visoke loÄljivosti (vir BAP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1946 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "Dupleks visoke loÄljivosti (vir/ponor BAP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1955 +msgid "High Fidelity Playback (BAP Sink)" +msgstr "Predvajanje visoke loÄljivosti (ponor BAP)" + +#: spa/plugins/bluez5/bluez5-device.c:1959 +msgid "High Fidelity Input (BAP Source)" +msgstr "Vhod visoke loÄljivosti (vir BAP)" + +#: spa/plugins/bluez5/bluez5-device.c:1962 +msgid "High Fidelity Duplex (BAP Source/Sink)" +msgstr "Dupleks visoke loÄljivosti (vir/ponor BAP)" + +#: spa/plugins/bluez5/bluez5-device.c:2008 +#, c-format +msgid "Headset Head Unit (HSP/HFP, codec %s)" +msgstr "Naglavna enota sluÅ¡alk (HSP/HFP, kodek %s)" + +#: spa/plugins/bluez5/bluez5-device.c:2128 +#: spa/plugins/bluez5/bluez5-device.c:2133 +#: spa/plugins/bluez5/bluez5-device.c:2140 +#: spa/plugins/bluez5/bluez5-device.c:2146 +#: spa/plugins/bluez5/bluez5-device.c:2152 +#: spa/plugins/bluez5/bluez5-device.c:2158 +#: spa/plugins/bluez5/bluez5-device.c:2164 +#: spa/plugins/bluez5/bluez5-device.c:2170 +#: spa/plugins/bluez5/bluez5-device.c:2176 +msgid "Handsfree" +msgstr "ProstoroÄno telefoniranje" + +#: spa/plugins/bluez5/bluez5-device.c:2134 +msgid "Handsfree (HFP)" +msgstr "ProstoroÄno telefoniranje (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:2151 +msgid "Headphone" +msgstr "SluÅ¡alke" + +#: spa/plugins/bluez5/bluez5-device.c:2157 +msgid "Portable" +msgstr "Prenosna naprava" + +#: spa/plugins/bluez5/bluez5-device.c:2163 +msgid "Car" +msgstr "Avtomobil" + +#: spa/plugins/bluez5/bluez5-device.c:2169 +msgid "HiFi" +msgstr "HiFi" + +#: spa/plugins/bluez5/bluez5-device.c:2175 +msgid "Phone" +msgstr "Telefon" + +#: spa/plugins/bluez5/bluez5-device.c:2182 +msgid "Bluetooth" +msgstr "Bluetooth" + +#: spa/plugins/bluez5/bluez5-device.c:2183 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)" diff --git a/po/sv.po b/po/sv.po index 01e97ad1..b867795c 100644 --- a/po/sv.po +++ b/po/sv.po @@ -1,9 +1,9 @@ # Swedish translation for pipewire. -# Copyright © 2008-2023 Free Software Foundation, Inc. +# Copyright © 2008-2024 Free Software Foundation, Inc. # This file is distributed under the same license as the pipewire package. # Daniel Nylander <po@danielnylander.se>, 2008, 2012. # Josef Andersson <josef.andersson@fripost.org>, 2014, 2017. -# Anders Jonsson <anders.jonsson@norsjovallen.se>, 2021, 2022, 2023. +# Anders Jonsson <anders.jonsson@norsjovallen.se>, 2021, 2022, 2023, 2024. # # Termer: # input/output: ingÃ¥ng/utgÃ¥ng (det handlar om ljud) @@ -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: 2023-08-07 15:27+0000\n" -"PO-Revision-Date: 2023-05-12 18:46+0200\n" +"POT-Creation-Date: 2024-11-05 03:27+0000\n" +"PO-Revision-Date: 2024-11-07 21:52+0100\n" "Last-Translator: Anders Jonsson <anders.jonsson@norsjovallen.se>\n" "Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n" "Language: sv\n" @@ -28,20 +28,24 @@ 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.2.2\n" +"X-Generator: Poedit 3.5\n" -#: src/daemon/pipewire.c:26 +#: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" +" -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" +" -P --properties Set context properties\n" msgstr "" "%s [flaggor]\n" " -h, --help Visa denna hjälp\n" +" -v, --verbose Öka utförligheten en nivÃ¥\n" " --version Visa version\n" -" -c, --config Läs in konfig (Standard %s)\n" +" -c, --config Läs in konfig (standard %s)\n" +" -P --properties Ställ in kontextegenskaper\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" @@ -51,36 +55,36 @@ msgstr "PipeWire mediasystem" msgid "Start the PipeWire Media System" msgstr "Starta mediasystemet PipeWire" -#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:141 -#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:141 +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 #, c-format msgid "Tunnel to %s%s%s" msgstr "Tunnel till %s%s%s" -#: src/modules/module-fallback-sink.c:31 +#: src/modules/module-fallback-sink.c:40 msgid "Dummy Output" msgstr "AttrapputgÃ¥ng" -#: src/modules/module-pulse-tunnel.c:847 +#: src/modules/module-pulse-tunnel.c:777 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunnel för %s@%s" -#: src/modules/module-zeroconf-discover.c:311 +#: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Okänd enhet" -#: src/modules/module-zeroconf-discover.c:323 +#: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%s pÃ¥ %s@%s" -#: src/modules/module-zeroconf-discover.c:327 +#: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%s pÃ¥ %s" -#: src/tools/pw-cat.c:979 +#: src/tools/pw-cat.c:973 #, c-format msgid "" "%s [options] [<file>|-]\n" @@ -95,7 +99,7 @@ msgstr "" " -v, --verbose Aktivera utförliga operationer\n" "\n" -#: src/tools/pw-cat.c:986 +#: src/tools/pw-cat.c:980 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -127,7 +131,7 @@ msgstr "" " -P --properties Sätt nodegenskaper\n" "\n" -#: src/tools/pw-cat.c:1004 +#: src/tools/pw-cat.c:998 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -144,6 +148,7 @@ msgid "" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" +" -a, --raw RAW mode\n" "\n" msgstr "" " --rate Samplingsfrekvens (krävs för insp.) " @@ -160,9 +165,10 @@ msgstr "" " --volume Strömvolym 0-1.0 (standard %.3f)\n" " -q --quality Omsamplarkvalitet (0 - 15) (standard " "%d)\n" +" -a, --raw RAW-läge\n" "\n" -#: src/tools/pw-cat.c:1021 +#: src/tools/pw-cat.c:1016 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" @@ -178,7 +184,7 @@ msgstr "" " -o, --encoded Kodat läge\n" "\n" -#: src/tools/pw-cli.c:2220 +#: src/tools/pw-cli.c:2318 #, c-format msgid "" "%s [options] [command]\n" @@ -197,12 +203,12 @@ msgstr "" " -m, --monitor Övervaka aktivitet\n" "\n" -#: spa/plugins/alsa/acp/acp.c:325 +#: spa/plugins/alsa/acp/acp.c:327 msgid "Pro Audio" msgstr "Professionellt ljud" -#: spa/plugins/alsa/acp/acp.c:449 spa/plugins/alsa/acp/alsa-mixer.c:4648 -#: spa/plugins/bluez5/bluez5-device.c:1586 +#: spa/plugins/alsa/acp/acp.c:487 spa/plugins/alsa/acp/alsa-mixer.c:4633 +#: spa/plugins/bluez5/bluez5-device.c:1705 msgid "Off" msgstr "Av" @@ -229,7 +235,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:1831 +#: spa/plugins/bluez5/bluez5-device.c:2026 msgid "Microphone" msgstr "Mikrofon" @@ -295,7 +301,7 @@ msgid "No Bass Boost" msgstr "Ingen basökning" #: spa/plugins/alsa/acp/alsa-mixer.c:2672 -#: spa/plugins/bluez5/bluez5-device.c:1837 +#: spa/plugins/bluez5/bluez5-device.c:2032 msgid "Speaker" msgstr "Högtalare" @@ -377,15 +383,15 @@ msgstr "Chatt-ingÃ¥ng" msgid "Virtual Surround 7.1" msgstr "Virtual surround 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4471 +#: spa/plugins/alsa/acp/alsa-mixer.c:4456 msgid "Analog Mono" msgstr "Analog mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4472 +#: spa/plugins/alsa/acp/alsa-mixer.c:4457 msgid "Analog Mono (Left)" msgstr "Analog mono (vänster)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4473 +#: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono (Right)" msgstr "Analog mono (höger)" @@ -394,147 +400,147 @@ msgstr "Analog mono (höger)" #. * 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 +#: spa/plugins/alsa/acp/alsa-mixer.c:4459 +#: spa/plugins/alsa/acp/alsa-mixer.c:4467 +#: spa/plugins/alsa/acp/alsa-mixer.c:4468 msgid "Analog Stereo" msgstr "Analog stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4475 +#: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Mono" msgstr "Mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4476 +#: spa/plugins/alsa/acp/alsa-mixer.c:4461 msgid "Stereo" msgstr "Stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4484 -#: spa/plugins/alsa/acp/alsa-mixer.c:4642 -#: spa/plugins/bluez5/bluez5-device.c:1819 +#: spa/plugins/alsa/acp/alsa-mixer.c:4469 +#: spa/plugins/alsa/acp/alsa-mixer.c:4627 +#: spa/plugins/bluez5/bluez5-device.c:2014 msgid "Headset" msgstr "Headset" -#: spa/plugins/alsa/acp/alsa-mixer.c:4485 -#: spa/plugins/alsa/acp/alsa-mixer.c:4643 +#: spa/plugins/alsa/acp/alsa-mixer.c:4470 +#: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Speakerphone" msgstr "Högtalartelefon" -#: spa/plugins/alsa/acp/alsa-mixer.c:4486 -#: spa/plugins/alsa/acp/alsa-mixer.c:4487 +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Multichannel" msgstr "Multikanal" -#: spa/plugins/alsa/acp/alsa-mixer.c:4488 +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Surround 2.1" msgstr "Analog surround 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4489 +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Analog Surround 3.0" msgstr "Analog surround 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4490 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 3.1" msgstr "Analog surround 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4491 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 4.0" msgstr "Analog surround 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4492 +#: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 4.1" msgstr "Analog surround 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4493 +#: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 5.0" msgstr "Analog surround 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4494 +#: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 5.1" msgstr "Analog surround 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4495 +#: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 6.0" msgstr "Analog surround 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4496 +#: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 6.1" msgstr "Analog surround 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4497 +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 7.0" msgstr "Analog surround 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4498 +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 7.1" msgstr "Analog surround 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4499 +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Digital Stereo (IEC958)" msgstr "Digital stereo (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4500 +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digital surround 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4501 +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digital surround 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4502 +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digital surround 5.1 (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4503 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Stereo (HDMI)" msgstr "Digital stereo (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4504 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digital surround 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4505 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Chat" msgstr "Chatt" -#: spa/plugins/alsa/acp/alsa-mixer.c:4506 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Game" msgstr "Spel" -#: spa/plugins/alsa/acp/alsa-mixer.c:4640 +#: spa/plugins/alsa/acp/alsa-mixer.c:4625 msgid "Analog Mono Duplex" msgstr "Analog mono duplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4641 +#: spa/plugins/alsa/acp/alsa-mixer.c:4626 msgid "Analog Stereo Duplex" msgstr "Analog stereo duplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4644 +#: spa/plugins/alsa/acp/alsa-mixer.c:4629 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digital stereo duplex (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4645 +#: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Multichannel Duplex" msgstr "Multikanalduplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4646 +#: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Stereo Duplex" msgstr "Stereo duplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4647 +#: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Mono Chat + 7.1 Surround" msgstr "Mono Chatt + 7.1 Surround" -#: spa/plugins/alsa/acp/alsa-mixer.c:4748 +#: spa/plugins/alsa/acp/alsa-mixer.c:4733 #, c-format msgid "%s Output" msgstr "%s-utgÃ¥ng" -#: spa/plugins/alsa/acp/alsa-mixer.c:4756 +#: spa/plugins/alsa/acp/alsa-mixer.c:4741 #, c-format msgid "%s Input" msgstr "%s-ingÃ¥ng" -#: spa/plugins/alsa/acp/alsa-util.c:1187 spa/plugins/alsa/acp/alsa-util.c:1281 +#: spa/plugins/alsa/acp/alsa-util.c:1231 spa/plugins/alsa/acp/alsa-util.c:1325 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -557,7 +563,7 @@ msgstr[1] "" "Förmodligen är detta ett fel i ALSA-drivrutinen â€%sâ€. Vänligen rapportera " "problemet till ALSA-utvecklarna." -#: spa/plugins/alsa/acp/alsa-util.c:1253 +#: spa/plugins/alsa/acp/alsa-util.c:1297 #, c-format msgid "" "snd_pcm_delay() returned a value that is exceptionally large: %li byte " @@ -580,7 +586,7 @@ msgstr[1] "" "Förmodligen är detta ett fel i ALSA-drivrutinen â€%sâ€. Vänligen rapportera " "problemet till ALSA-utvecklarna." -#: spa/plugins/alsa/acp/alsa-util.c:1300 +#: spa/plugins/alsa/acp/alsa-util.c:1344 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -593,7 +599,7 @@ msgstr "" "Förmodligen är detta ett fel i ALSA-drivrutinen â€%sâ€. Vänligen rapportera " "problemet till ALSA-utvecklarna." -#: spa/plugins/alsa/acp/alsa-util.c:1343 +#: spa/plugins/alsa/acp/alsa-util.c:1387 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -620,112 +626,108 @@ msgstr[1] "" msgid "(invalid)" msgstr "(ogiltig)" -#: spa/plugins/alsa/acp/compat.c:189 +#: spa/plugins/alsa/acp/compat.c:193 msgid "Built-in Audio" msgstr "Inbyggt ljud" -#: spa/plugins/alsa/acp/compat.c:194 +#: spa/plugins/alsa/acp/compat.c:198 msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:1597 +#: spa/plugins/bluez5/bluez5-device.c:1716 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Audio gateway (A2DP-källa & HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:1622 +#: spa/plugins/bluez5/bluez5-device.c:1764 #, 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:1625 +#: spa/plugins/bluez5/bluez5-device.c:1767 #, 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:1633 +#: spa/plugins/bluez5/bluez5-device.c:1775 msgid "High Fidelity Playback (A2DP Sink)" msgstr "High fidelity-uppspelning (A2DP-utgÃ¥ng)" -#: spa/plugins/bluez5/bluez5-device.c:1635 +#: spa/plugins/bluez5/bluez5-device.c:1777 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "High fidelity duplex (A2DP-källa/utgÃ¥ng)" -#: spa/plugins/bluez5/bluez5-device.c:1677 +#: spa/plugins/bluez5/bluez5-device.c:1827 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "High fidelity-uppspelning (BAP-utgÃ¥ng, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1681 +#: spa/plugins/bluez5/bluez5-device.c:1832 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "High fidelity-ingÃ¥ng (BAP-källa, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1685 +#: spa/plugins/bluez5/bluez5-device.c:1836 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "High fidelity duplex (BAP-källa/utgÃ¥ng, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1693 +#: spa/plugins/bluez5/bluez5-device.c:1845 msgid "High Fidelity Playback (BAP Sink)" msgstr "High fidelity-uppspelning (BAP-utgÃ¥ng)" -#: spa/plugins/bluez5/bluez5-device.c:1696 +#: spa/plugins/bluez5/bluez5-device.c:1849 msgid "High Fidelity Input (BAP Source)" msgstr "High fidelity-ingÃ¥ng (BAP-källa)" -#: spa/plugins/bluez5/bluez5-device.c:1699 +#: spa/plugins/bluez5/bluez5-device.c:1852 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "High fidelity duplex (BAP-källa/utgÃ¥ng)" -#: spa/plugins/bluez5/bluez5-device.c:1735 +#: spa/plugins/bluez5/bluez5-device.c:1901 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Headset-huvudenhet (HSP/HFP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:1740 -msgid "Headset Head Unit (HSP/HFP)" -msgstr "Headset-huvudenhet (HSP/HFP)" - -#: spa/plugins/bluez5/bluez5-device.c:1820 -#: spa/plugins/bluez5/bluez5-device.c:1825 -#: spa/plugins/bluez5/bluez5-device.c:1832 -#: spa/plugins/bluez5/bluez5-device.c:1838 -#: spa/plugins/bluez5/bluez5-device.c:1844 -#: spa/plugins/bluez5/bluez5-device.c:1850 -#: spa/plugins/bluez5/bluez5-device.c:1856 -#: spa/plugins/bluez5/bluez5-device.c:1862 -#: spa/plugins/bluez5/bluez5-device.c:1868 +#: spa/plugins/bluez5/bluez5-device.c:2015 +#: spa/plugins/bluez5/bluez5-device.c:2020 +#: spa/plugins/bluez5/bluez5-device.c:2027 +#: spa/plugins/bluez5/bluez5-device.c:2033 +#: spa/plugins/bluez5/bluez5-device.c:2039 +#: spa/plugins/bluez5/bluez5-device.c:2045 +#: spa/plugins/bluez5/bluez5-device.c:2051 +#: spa/plugins/bluez5/bluez5-device.c:2057 +#: spa/plugins/bluez5/bluez5-device.c:2063 msgid "Handsfree" msgstr "Handsfree" -#: spa/plugins/bluez5/bluez5-device.c:1826 +#: spa/plugins/bluez5/bluez5-device.c:2021 msgid "Handsfree (HFP)" msgstr "Handsfree (HFP)" -#: spa/plugins/bluez5/bluez5-device.c:1843 +#: spa/plugins/bluez5/bluez5-device.c:2038 msgid "Headphone" msgstr "Hörlurar" -#: spa/plugins/bluez5/bluez5-device.c:1849 +#: spa/plugins/bluez5/bluez5-device.c:2044 msgid "Portable" msgstr "Bärbar" -#: spa/plugins/bluez5/bluez5-device.c:1855 +#: spa/plugins/bluez5/bluez5-device.c:2050 msgid "Car" msgstr "Bil" -#: spa/plugins/bluez5/bluez5-device.c:1861 +#: spa/plugins/bluez5/bluez5-device.c:2056 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:1867 +#: spa/plugins/bluez5/bluez5-device.c:2062 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:1874 +#: spa/plugins/bluez5/bluez5-device.c:2069 msgid "Bluetooth" msgstr "Bluetooth" -#: spa/plugins/bluez5/bluez5-device.c:1875 +#: spa/plugins/bluez5/bluez5-device.c:2070 msgid "Bluetooth (HFP)" msgstr "Bluetooth (HFP)" diff --git a/po/zh_CN.po b/po/zh_CN.po index 53ce6e8d..1022f5d6 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -6,106 +6,127 @@ # Cheng-Chia Tseng <pswo10680@gmail.com>, 2010, 2012. # Frank Hill <hxf.prc@gmail.com>, 2015. # Mingye Wang (Arthur2e5) <arthur200126@gmail.com>, 2015. +# lumingzh <lumingzh@qq.com>, 2024. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx\n" -"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" -"issues/new\n" -"POT-Creation-Date: 2021-04-18 10:47+0800\n" -"PO-Revision-Date: 2021-04-18 10:56+0800\n" -"Last-Translator: Huang-Huang Bao <i@eh5.me>\n" -"Language-Team: Huang-Huang Bao <i@eh5.me>\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" +"issues\n" +"POT-Creation-Date: 2024-09-09 16:36+0000\n" +"PO-Revision-Date: 2024-10-08 09:41+0800\n" +"Last-Translator: lumingzh <lumingzh@qq.com>\n" +"Language-Team: Chinese (China) <i18n-zh@googlegroups.com>\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Launchpad-Export-Date: 2016-03-22 13:23+0000\n" -"X-Generator: Poedit 2.4.1\n" +"X-Generator: Gtranslator 47.0\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: src/daemon/pipewire.c:43 +#: src/daemon/pipewire.c:29 #, c-format msgid "" "%s [options]\n" " -h, --help Show this help\n" +" -v, --verbose Increase verbosity by one level\n" " --version Show version\n" " -c, --config Load config (Default %s)\n" +" -P --properties Set context properties\n" msgstr "" "%s [选项]\n" " -h, --help 显示æ¤å¸®åŠ©ä¿¡æ¯\n" +" -v, --verbose å¢žåŠ ä¸€çº§çš„è¯¦å°½ç¨‹åº¦\n" " --version 显示版本\n" -" -c, --config åŠ è½½é…置文件 (默认 %s)\n" +" -c, --config åŠ è½½é…ç½® (默认 %s)\n" +" -P --properties 设置上下文属性\n" #: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" -msgstr "PipeWire多媒体系统" +msgstr "PipeWire 多媒体系统" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" -msgstr "å¯åŠ¨PipeWire多媒体系统" +msgstr "å¯åŠ¨ PipeWire 多媒体系统" -#: src/examples/media-session/alsa-monitor.c:526 -#: spa/plugins/alsa/acp/compat.c:187 -msgid "Built-in Audio" -msgstr "内置音频" +#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159 +#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159 +#, c-format +msgid "Tunnel to %s%s%s" +msgstr "至 %s%s%s 的隧é“" -#: src/examples/media-session/alsa-monitor.c:530 -#: spa/plugins/alsa/acp/compat.c:192 -msgid "Modem" -msgstr "调制解调器" +#: src/modules/module-fallback-sink.c:40 +msgid "Dummy Output" +msgstr "虚拟输出" -#: src/examples/media-session/alsa-monitor.c:539 +#: src/modules/module-pulse-tunnel.c:774 +#, c-format +msgid "Tunnel for %s@%s" +msgstr "用于 %s@%s 的隧é“" + +#: src/modules/module-zeroconf-discover.c:318 msgid "Unknown device" msgstr "未知设备" -#: src/tools/pw-cat.c:991 +#: src/modules/module-zeroconf-discover.c:330 +#, c-format +msgid "%s on %s@%s" +msgstr "%2$s@%3$s 上的 %1$s" + +#: src/modules/module-zeroconf-discover.c:334 +#, c-format +msgid "%s on %s" +msgstr "%2$s 上的 %1$s" + +#: src/tools/pw-cat.c:996 #, 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 [选项] <文件>\n" +"%s [选项] [<文件>|-]\n" " -h, --help 显示æ¤å¸®åŠ©ä¿¡æ¯\n" " --version 显示版本\n" " -v, --verbose 输出详细æ“作\n" "\n" -#: src/tools/pw-cat.c:998 +#: src/tools/pw-cat.c:1003 #, 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" +" --target Set node target serial or name " +"(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" -" --list-targets List available targets for --target\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" +" --target è®¾ç½®èŠ‚ç‚¹ç›®æ ‡åºåˆ—或å称 (默认 %s)\n" +" 设为 0 则ä¸é“¾æŽ¥èŠ‚点\n" " --latency 设置节点延迟 (默认 %s)\n" " 时间 (å•ä½å¯ä¸º s, ms, us, ns)\n" " æˆ–æ ·æœ¬æ•° (如256)\n" " å¯¹åº”çš„é‡‡æ ·çŽ‡åˆ™æ˜¯åª’ä½“æºæ–‡ä»¶é‡‡æ ·çŽ‡çš„" "其一\n" -" --list-targets 列出å¯ç”¨çš„ --target ç›®æ ‡\n" +" -P --properties 设置节点属性\n" "\n" -#: src/tools/pw-cat.c:1016 +#: src/tools/pw-cat.c:1021 #, c-format msgid "" " --rate Sample rate (req. for rec) (default " @@ -122,6 +143,7 @@ msgid "" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" +" -a, --raw RAW mode\n" "\n" msgstr "" " --rate é‡‡æ ·çŽ‡ (录制模å¼éœ€è¦) (默认 %u)\n" @@ -135,21 +157,26 @@ msgstr "" "%s)\n" " --volume 媒体æµéŸ³é‡ 0-1.0 (默认 %.3f)\n" " -q --quality é‡é‡‡æ ·è´¨é‡ (0 - 15) (默认 %d)\n" +" -a, --raw 原生模å¼\n" "\n" -#: src/tools/pw-cat.c:1033 +#: src/tools/pw-cat.c:1039 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" +" -d, --dsd DSD mode\n" +" -o, --encoded Encoded mode\n" "\n" msgstr "" " -p, --playback 回放模å¼\n" " -r, --record 录制模å¼\n" -" -m, --midi Midi模å¼\n" +" -m, --midi Midi 模å¼\n" +" -d, --dsd DSD 模å¼\n" +" -o, --encoded ç¼–ç 模å¼\n" "\n" -#: src/tools/pw-cli.c:2932 +#: src/tools/pw-cli.c:2285 #, c-format msgid "" "%s [options] [command]\n" @@ -157,208 +184,205 @@ msgid "" " --version Show version\n" " -d, --daemon Start as daemon (Default false)\n" " -r, --remote Remote daemon name\n" +" -m, --monitor Monitor activity\n" "\n" msgstr "" "%s [选项] [命令]\n" " -h, --help 显示æ¤å¸®åŠ©ä¿¡æ¯\n" " --version 显示版本\n" " -d, --daemon 以守护程åºæ–¹å¼å¯åŠ¨ (默认关é—)\n" -" -r, --remote 远程守护程åºå\n" +" -m, --monitor 监视器活动\n" "\n" -#: spa/plugins/alsa/acp/acp.c:290 +#: spa/plugins/alsa/acp/acp.c:327 msgid "Pro Audio" msgstr "专业音频" -#: 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:488 spa/plugins/alsa/acp/alsa-mixer.c:4633 +#: spa/plugins/bluez5/bluez5-device.c:1701 msgid "Off" msgstr "å…³" -#: spa/plugins/alsa/acp/channelmap.h:466 -msgid "(invalid)" -msgstr "(æ— æ•ˆ)" - -#: spa/plugins/alsa/acp/alsa-mixer.c:2709 +#: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "输入" -#: spa/plugins/alsa/acp/alsa-mixer.c:2710 +#: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "扩展åžè¾“å…¥" -#: spa/plugins/alsa/acp/alsa-mixer.c:2711 +#: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "扩展åžè¯ç’" -#: spa/plugins/alsa/acp/alsa-mixer.c:2712 +#: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "扩展åžçº¿è¾“å…¥" -#: 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 "输入æ’å”" -#: 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:1989 msgid "Microphone" msgstr "è¯ç’" -#: 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 "å‰éº¦å…‹é£Ž" -#: 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 "åŽéº¦å…‹é£Ž" -#: spa/plugins/alsa/acp/alsa-mixer.c:2717 +#: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "外部è¯ç’" -#: 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 "内部è¯ç’" -#: 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 "æ— çº¿ç”µ" -#: 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 "视频" -#: spa/plugins/alsa/acp/alsa-mixer.c:2721 +#: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "自动增益控制" -#: spa/plugins/alsa/acp/alsa-mixer.c:2722 +#: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "æ— è‡ªåŠ¨å¢žç›ŠæŽ§åˆ¶" -#: spa/plugins/alsa/acp/alsa-mixer.c:2723 +#: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "增强" -#: spa/plugins/alsa/acp/alsa-mixer.c:2724 +#: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "æ— å¢žå¼º" -#: spa/plugins/alsa/acp/alsa-mixer.c:2725 +#: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "功放" -#: spa/plugins/alsa/acp/alsa-mixer.c:2726 +#: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "æ— åŠŸæ”¾" -#: spa/plugins/alsa/acp/alsa-mixer.c:2727 +#: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "é‡ä½ŽéŸ³å¢žå¼º" -#: spa/plugins/alsa/acp/alsa-mixer.c:2728 +#: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "æ— é‡ä½ŽéŸ³å¢žå¼º" -#: 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:1995 msgid "Speaker" msgstr "扬声器" -#: 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 "模拟耳机" -#: spa/plugins/alsa/acp/alsa-mixer.c:2797 +#: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "模拟输入" -#: spa/plugins/alsa/acp/alsa-mixer.c:2801 +#: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "扩展åžéº¦å…‹é£Ž" -#: spa/plugins/alsa/acp/alsa-mixer.c:2803 +#: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "头挂麦克风" -#: spa/plugins/alsa/acp/alsa-mixer.c:2807 +#: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "模拟输出" -#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "模拟耳机 2" -#: spa/plugins/alsa/acp/alsa-mixer.c:2810 +#: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "模拟å•å£°é“输出" -#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "线缆输出" -#: spa/plugins/alsa/acp/alsa-mixer.c:2812 +#: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "模拟å•å£°é“输出" -#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "扬声器" -#: 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 "æ•°å—输出 (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "æ•°å—输入 (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2817 +#: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "多声é“输入" -#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "多声é“输出" -#: spa/plugins/alsa/acp/alsa-mixer.c:2819 +#: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "游æˆè¾“出" -#: 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 "è¯éŸ³è¾“出" -#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "è¯éŸ³è¾“å…¥" -#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "虚拟环绕 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4527 +#: spa/plugins/alsa/acp/alsa-mixer.c:4456 msgid "Analog Mono" msgstr "模拟å•å£°é“" -#: spa/plugins/alsa/acp/alsa-mixer.c:4528 +#: spa/plugins/alsa/acp/alsa-mixer.c:4457 msgid "Analog Mono (Left)" msgstr "模拟å•å£°é“ (左声é“)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4529 +#: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono (Right)" msgstr "模拟å•å£°é“ (å³å£°é“)" @@ -367,147 +391,147 @@ msgstr "模拟å•å£°é“ (å³å£°é“)" #. * 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:4459 +#: spa/plugins/alsa/acp/alsa-mixer.c:4467 +#: spa/plugins/alsa/acp/alsa-mixer.c:4468 msgid "Analog Stereo" msgstr "模拟立体声" -#: spa/plugins/alsa/acp/alsa-mixer.c:4531 +#: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Mono" msgstr "å•å£°é“" -#: spa/plugins/alsa/acp/alsa-mixer.c:4532 +#: spa/plugins/alsa/acp/alsa-mixer.c:4461 msgid "Stereo" msgstr "立体声" -#: 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:4469 +#: spa/plugins/alsa/acp/alsa-mixer.c:4627 +#: spa/plugins/bluez5/bluez5-device.c:1977 msgid "Headset" msgstr "耳机" -#: spa/plugins/alsa/acp/alsa-mixer.c:4541 -#: spa/plugins/alsa/acp/alsa-mixer.c:4699 +#: spa/plugins/alsa/acp/alsa-mixer.c:4470 +#: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Speakerphone" msgstr "扬声麦克风" -#: spa/plugins/alsa/acp/alsa-mixer.c:4542 -#: spa/plugins/alsa/acp/alsa-mixer.c:4543 +#: spa/plugins/alsa/acp/alsa-mixer.c:4471 +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 msgid "Multichannel" msgstr "多声é“" -#: spa/plugins/alsa/acp/alsa-mixer.c:4544 +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 msgid "Analog Surround 2.1" msgstr "模拟环绕 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4545 +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Analog Surround 3.0" msgstr "模拟环绕 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4546 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 3.1" msgstr "模拟环绕 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4547 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 4.0" msgstr "模拟环绕 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4548 +#: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 4.1" msgstr "模拟环绕 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4549 +#: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 5.0" msgstr "模拟环绕 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4550 +#: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 5.1" msgstr "模拟环绕 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4551 +#: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 6.0" msgstr "模拟环绕 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4552 +#: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 6.1" msgstr "模拟环绕 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4553 +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 7.0" msgstr "模拟环绕 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4554 +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 7.1" msgstr "模拟环绕 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4555 +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Digital Stereo (IEC958)" -msgstr "æ•°å—立体声(IEC958)" +msgstr "æ•°å—立体声 (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4556 +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Digital Surround 4.0 (IEC958/AC3)" -msgstr "æ•°å—环绕 4.0(IEC958/AC3)" +msgstr "æ•°å—环绕 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4557 +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Surround 5.1 (IEC958/AC3)" -msgstr "æ•°å—环绕 5.1(IEC958/AC3)" +msgstr "æ•°å—环绕 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4558 +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 5.1 (IEC958/DTS)" -msgstr "æ•°å—环绕 5.1(IEC958/DTS)" +msgstr "æ•°å—环绕 5.1 (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4559 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Stereo (HDMI)" -msgstr "æ•°å—立体声(HDMI)" +msgstr "æ•°å—立体声 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4560 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (HDMI)" -msgstr "æ•°å—环绕 5.1(HDMI)" +msgstr "æ•°å—环绕 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4561 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Chat" msgstr "è¯éŸ³" -#: spa/plugins/alsa/acp/alsa-mixer.c:4562 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Game" msgstr "游æˆ" -#: spa/plugins/alsa/acp/alsa-mixer.c:4696 +#: spa/plugins/alsa/acp/alsa-mixer.c:4625 msgid "Analog Mono Duplex" msgstr "模拟å•å£°é“åŒå·¥" -#: spa/plugins/alsa/acp/alsa-mixer.c:4697 +#: spa/plugins/alsa/acp/alsa-mixer.c:4626 msgid "Analog Stereo Duplex" msgstr "模拟立体声åŒå·¥" -#: spa/plugins/alsa/acp/alsa-mixer.c:4700 +#: spa/plugins/alsa/acp/alsa-mixer.c:4629 msgid "Digital Stereo Duplex (IEC958)" -msgstr "æ•°å—立体声åŒå·¥(IEC958)" +msgstr "æ•°å—立体声åŒå·¥ (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4701 +#: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Multichannel Duplex" msgstr "多声é“åŒå·¥" -#: spa/plugins/alsa/acp/alsa-mixer.c:4702 +#: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Stereo Duplex" msgstr "模拟立体声åŒå·¥" -#: spa/plugins/alsa/acp/alsa-mixer.c:4703 +#: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Mono Chat + 7.1 Surround" msgstr "å•å£°é“è¯éŸ³ + 7.1 环绕声" -#: spa/plugins/alsa/acp/alsa-mixer.c:4806 +#: spa/plugins/alsa/acp/alsa-mixer.c:4733 #, c-format msgid "%s Output" msgstr "%s 输出" -#: spa/plugins/alsa/acp/alsa-mixer.c:4813 +#: spa/plugins/alsa/acp/alsa-mixer.c:4741 #, c-format msgid "%s Input" msgstr "%s 输入" -#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 +#: spa/plugins/alsa/acp/alsa-util.c:1231 spa/plugins/alsa/acp/alsa-util.c:1325 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -523,23 +547,23 @@ msgstr[0] "" "snd_pcm_avail() 返回的值éžå¸¸å¤§ï¼š%lu å—节(%lu 毫秒)。\n" "这很å¯èƒ½æ˜¯ç”± ALSA é©±åŠ¨ç¨‹åº %s çš„ç¼ºé™·å¯¼è‡´çš„ã€‚è¯·å‘ ALSA å¼€å‘者报告这个问题。" -#: spa/plugins/alsa/acp/alsa-util.c:1241 +#: spa/plugins/alsa/acp/alsa-util.c:1297 #, 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] "" "snd_pcm_delay() 返回的值éžå¸¸å¤§ï¼š%li å—节(%s%lu 毫秒)。\n" "这很å¯èƒ½æ˜¯ç”± ALSA é©±åŠ¨ç¨‹åº %s çš„ç¼ºé™·å¯¼è‡´çš„ã€‚è¯·å‘ ALSA å¼€å‘者报告这个问题。" -#: spa/plugins/alsa/acp/alsa-util.c:1288 +#: spa/plugins/alsa/acp/alsa-util.c:1344 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -550,7 +574,7 @@ msgstr "" "snd_pcm_avail_delay() 返回的值éžå¸¸å¾ˆå¥‡æ€ªï¼šå»¶è¿Ÿ %lu å°äºŽå¯ç”¨ (avail) %lu。\n" "这很å¯èƒ½æ˜¯ç”± ALSA é©±åŠ¨ç¨‹åº %s çš„ç¼ºé™·å¯¼è‡´çš„ã€‚è¯·å‘ ALSA å¼€å‘者报告这个问题。" -#: spa/plugins/alsa/acp/alsa-util.c:1331 +#: spa/plugins/alsa/acp/alsa-util.c:1387 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -566,61 +590,115 @@ msgstr[0] "" "snd_pcm_mmap_begin() 返回的值éžå¸¸å¤§ï¼š%lu å—节(%lu ms)。\n" "这很å¯èƒ½æ˜¯ç”± ALSA é©±åŠ¨ç¨‹åº %s çš„ç¼ºé™·å¯¼è‡´çš„ã€‚è¯·å‘ ALSA å¼€å‘者报告这个问题。" -#: spa/plugins/bluez5/bluez5-device.c:1010 +#: spa/plugins/alsa/acp/channelmap.h:457 +msgid "(invalid)" +msgstr "(æ— æ•ˆ)" + +#: spa/plugins/alsa/acp/compat.c:193 +msgid "Built-in Audio" +msgstr "内置音频" + +#: spa/plugins/alsa/acp/compat.c:198 +msgid "Modem" +msgstr "调制解调器" + +#: spa/plugins/bluez5/bluez5-device.c:1712 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "音频网关 (A2DP ä¿¡æº æˆ– HSP/HFP 网关)" -#: spa/plugins/bluez5/bluez5-device.c:1033 +#: spa/plugins/bluez5/bluez5-device.c:1760 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "高ä¿çœŸå›žæ”¾ (A2DP 信宿, ç¼–ç %s)" -#: spa/plugins/bluez5/bluez5-device.c:1035 +#: spa/plugins/bluez5/bluez5-device.c:1763 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "高ä¿çœŸåŒå·¥ (A2DP ä¿¡æº/信宿, ç¼–ç %s)" -#: spa/plugins/bluez5/bluez5-device.c:1041 +#: spa/plugins/bluez5/bluez5-device.c:1771 msgid "High Fidelity Playback (A2DP Sink)" msgstr "高ä¿çœŸå›žæ”¾ (A2DP 信宿)" -#: spa/plugins/bluez5/bluez5-device.c:1043 +#: spa/plugins/bluez5/bluez5-device.c:1773 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "高ä¿çœŸåŒå·¥ (A2DP ä¿¡æº/信宿)" -#: spa/plugins/bluez5/bluez5-device.c:1070 +#: spa/plugins/bluez5/bluez5-device.c:1823 +#, c-format +msgid "High Fidelity Playback (BAP Sink, codec %s)" +msgstr "高ä¿çœŸå›žæ”¾ (BAP 信宿, ç¼–ç %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1828 +#, c-format +msgid "High Fidelity Input (BAP Source, codec %s)" +msgstr "高ä¿çœŸè¾“å…¥ (BAP ä¿¡æº, ç¼–ç %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1832 +#, c-format +msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" +msgstr "高ä¿çœŸåŒå·¥ (BAP ä¿¡æº/信宿, ç¼–ç %s)" + +#: spa/plugins/bluez5/bluez5-device.c:1841 +msgid "High Fidelity Playback (BAP Sink)" +msgstr "高ä¿çœŸå›žæ”¾ (BAP 信宿)" + +#: spa/plugins/bluez5/bluez5-device.c:1845 +msgid "High Fidelity Input (BAP Source)" +msgstr "高ä¿çœŸè¾“å…¥ (BAP ä¿¡æº)" + +#: spa/plugins/bluez5/bluez5-device.c:1848 +msgid "High Fidelity Duplex (BAP Source/Sink)" +msgstr "高ä¿çœŸåŒå·¥ (BAP ä¿¡æº/信宿)" + +#: spa/plugins/bluez5/bluez5-device.c:1897 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "头戴å¼è€³æœºå•å…ƒ (HSP/HFP, ç¼–ç %s)" -#: spa/plugins/bluez5/bluez5-device.c:1074 -msgid "Headset Head Unit (HSP/HFP)" -msgstr "头戴å¼è€³æœºå•å…ƒ (HSP/HFP)" - -#: spa/plugins/bluez5/bluez5-device.c:1140 +#: spa/plugins/bluez5/bluez5-device.c:1978 +#: spa/plugins/bluez5/bluez5-device.c:1983 +#: spa/plugins/bluez5/bluez5-device.c:1990 +#: spa/plugins/bluez5/bluez5-device.c:1996 +#: spa/plugins/bluez5/bluez5-device.c:2002 +#: spa/plugins/bluez5/bluez5-device.c:2008 +#: spa/plugins/bluez5/bluez5-device.c:2014 +#: spa/plugins/bluez5/bluez5-device.c:2020 +#: spa/plugins/bluez5/bluez5-device.c:2026 msgid "Handsfree" msgstr "å…手æ“作" -#: spa/plugins/bluez5/bluez5-device.c:1155 +#: spa/plugins/bluez5/bluez5-device.c:1984 +msgid "Handsfree (HFP)" +msgstr "å…手æ“作 (HFP)" + +#: spa/plugins/bluez5/bluez5-device.c:2001 msgid "Headphone" msgstr "头戴耳机" -#: spa/plugins/bluez5/bluez5-device.c:1160 +#: spa/plugins/bluez5/bluez5-device.c:2007 msgid "Portable" msgstr "便æºå¼" -#: spa/plugins/bluez5/bluez5-device.c:1165 +#: spa/plugins/bluez5/bluez5-device.c:2013 msgid "Car" msgstr "车内" -#: spa/plugins/bluez5/bluez5-device.c:1170 +#: spa/plugins/bluez5/bluez5-device.c:2019 msgid "HiFi" msgstr "高ä¿çœŸ" -#: spa/plugins/bluez5/bluez5-device.c:1175 +#: spa/plugins/bluez5/bluez5-device.c:2025 msgid "Phone" msgstr "电è¯" -#: spa/plugins/bluez5/bluez5-device.c:1181 +#: spa/plugins/bluez5/bluez5-device.c:2032 msgid "Bluetooth" msgstr "è“牙" + +#: spa/plugins/bluez5/bluez5-device.c:2033 +msgid "Bluetooth (HFP)" +msgstr "è“牙 (HFP)" + +#~ msgid "Headset Head Unit (HSP/HFP)" +#~ msgstr "头戴å¼è€³æœºå•å…ƒ (HSP/HFP)" diff --git a/spa/examples/local-libcamera.c b/spa/examples/local-libcamera.c index 2a4d8283..d6e9f80d 100644 --- a/spa/examples/local-libcamera.c +++ b/spa/examples/local-libcamera.c @@ -43,7 +43,7 @@ static SPA_LOG_IMPL(default_log); #define MAX_BUFFERS 8 - +#define LOOP_TIMEOUT_MS 100 #define USE_BUFFER false struct buffer { @@ -401,57 +401,33 @@ static int negotiate_formats(struct data *data) return 0; } -static void *loop(void *user_data) -{ - struct data *data = user_data; - - printf("enter thread\n"); - spa_loop_control_enter(data->control); - - while (data->running) { - spa_loop_control_iterate(data->control, -1); - } - - printf("leave thread\n"); - spa_loop_control_leave(data->control); - return NULL; -} - -static void run_async_source(struct data *data) +static void loop(struct data *data) { - int res, err; + int res; struct spa_command cmd; SDL_Event event; - bool running = true; printf("starting...\n\n"); cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start); if ((res = spa_node_send_command(data->source, &cmd)) < 0) printf("got error %d\n", res); - spa_loop_control_leave(data->control); - data->running = true; - if ((err = pthread_create(&data->thread, NULL, loop, data)) != 0) { - printf("can't create thread: %d %s", err, strerror(err)); - data->running = false; - } - while (running && SDL_WaitEvent(&event)) { - switch (event.type) { - case SDL_QUIT: - running = false; - break; + while (data->running) { + // must be called from the thread that created the renderer + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + data->running = false; + break; + } } - } - if (data->running) { - data->running = false; - pthread_join(data->thread, NULL); + // small timeout to make sure we don't starve the SDL loop + spa_loop_control_iterate(data->control, LOOP_TIMEOUT_MS); } - spa_loop_control_enter(data->control); - printf("pausing...\n\n"); cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause); if ((res = spa_node_send_command(data->source, &cmd)) < 0) @@ -531,7 +507,7 @@ int main(int argc, char *argv[]) } spa_loop_control_enter(data.control); - run_async_source(&data); + loop(&data); spa_loop_control_leave(data.control); SDL_DestroyRenderer(data.renderer); diff --git a/spa/examples/local-v4l2.c b/spa/examples/local-v4l2.c index 4e7000da..46f224ee 100644 --- a/spa/examples/local-v4l2.c +++ b/spa/examples/local-v4l2.c @@ -39,6 +39,7 @@ static SPA_LOG_IMPL(default_log); #define MAX_BUFFERS 8 +#define LOOP_TIMEOUT_MS 100 struct buffer { struct spa_buffer buffer; @@ -396,56 +397,34 @@ static int negotiate_formats(struct data *data) return 0; } -static void *loop(void *user_data) +static void loop(struct data *data) { - struct data *data = user_data; - - printf("enter thread\n"); - spa_loop_control_enter(data->control); - - while (data->running) { - spa_loop_control_iterate(data->control, -1); - } - - printf("leave thread\n"); - spa_loop_control_leave(data->control); - return NULL; -} - -static void run_async_source(struct data *data) -{ - int res, err; + int res; struct spa_command cmd; SDL_Event event; - bool running = true; + printf("starting...\n\n"); cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start); if ((res = spa_node_send_command(data->source, &cmd)) < 0) printf("got error %d\n", res); - spa_loop_control_leave(data->control); - data->running = true; - if ((err = pthread_create(&data->thread, NULL, loop, data)) != 0) { - printf("can't create thread: %d %s", err, strerror(err)); - data->running = false; - } - while (running && SDL_WaitEvent(&event)) { - switch (event.type) { - case SDL_QUIT: - running = false; - break; + while (data->running) { + // must be called from the thread that created the renderer + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + data->running = false; + break; + } } - } - if (data->running) { - data->running = false; - pthread_join(data->thread, NULL); + // small timeout to make sure we don't starve the SDL loop + spa_loop_control_iterate(data->control, LOOP_TIMEOUT_MS); } - spa_loop_control_enter(data->control); - + printf("pausing...\n\n"); cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause); if ((res = spa_node_send_command(data->source, &cmd)) < 0) printf("got error %d\n", res); @@ -524,7 +503,7 @@ int main(int argc, char *argv[]) } spa_loop_control_enter(data.control); - run_async_source(&data); + loop(&data); spa_loop_control_leave(data.control); SDL_DestroyRenderer(data.renderer); diff --git a/spa/examples/local-videotestsrc.c b/spa/examples/local-videotestsrc.c new file mode 100644 index 00000000..3743e24d --- /dev/null +++ b/spa/examples/local-videotestsrc.c @@ -0,0 +1,535 @@ +/* Spa */ +/* SPDX-FileCopyrightText: Copyright © 2020 Collabora Ltd. */ +/* @author Raghavendra Rao Sidlagatta <raghavendra.rao@collabora.com> */ +/* SPDX-License-Identifier: MIT */ + +/* + [title] + Example using libspa-videotestsrc, with only \ref api_spa + [title] + */ + +#include "config.h" + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <dlfcn.h> +#include <poll.h> +#include <pthread.h> +#include <errno.h> +#include <sys/mman.h> + +#include <SDL2/SDL.h> + +#include <spa/support/plugin.h> +#include <spa/utils/names.h> +#include <spa/utils/result.h> +#include <spa/utils/string.h> +#include <spa/support/log-impl.h> +#include <spa/support/loop.h> +#include <spa/node/node.h> +#include <spa/node/io.h> +#include <spa/node/utils.h> +#include <spa/param/param.h> +#include <spa/param/props.h> +#include <spa/param/video/format-utils.h> +#include <spa/debug/pod.h> + +#define WIDTH 640 +#define HEIGHT 480 + +static SPA_LOG_IMPL(default_log); + +#define MAX_BUFFERS 8 +#define LOOP_TIMEOUT_MS 100 +#define USE_BUFFER true + +struct buffer { + struct spa_buffer buffer; + struct spa_meta metas[1]; + struct spa_meta_header header; + struct spa_data datas[1]; + struct spa_chunk chunks[1]; + SDL_Texture *texture; +}; + +struct data { + const char *plugin_dir; + struct spa_log *log; + struct spa_system *system; + struct spa_loop *loop; + struct spa_loop_control *control; + struct spa_loop_utils *loop_utils; + + struct spa_support support[6]; + uint32_t n_support; + + struct spa_node *source; + struct spa_hook listener; + struct spa_io_buffers source_output[1]; + + SDL_Renderer *renderer; + SDL_Window *window; + SDL_Texture *texture; + + bool use_buffer; + + bool running; + pthread_t thread; + + struct spa_buffer *bp[MAX_BUFFERS]; + struct buffer buffers[MAX_BUFFERS]; + unsigned int n_buffers; +}; + +static int load_handle(struct data *data, struct spa_handle **handle, const char *lib, const char *name) +{ + int res; + void *hnd; + spa_handle_factory_enum_func_t enum_func; + uint32_t i; + + char *path = NULL; + + if ((path = spa_aprintf("%s/%s", data->plugin_dir, lib)) == NULL) { + return -ENOMEM; + } + if ((hnd = dlopen(path, RTLD_NOW)) == NULL) { + printf("can't load %s: %s\n", path, dlerror()); + free(path); + return -errno; + } + free(path); + if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) { + printf("can't find enum function\n"); + return -errno; + } + + for (i = 0;;) { + const struct spa_handle_factory *factory; + + if ((res = enum_func(&factory, &i)) <= 0) { + if (res != 0) + printf("can't enumerate factories: %s\n", spa_strerror(res)); + break; + } + if (!spa_streq(factory->name, name)) + continue; + + *handle = calloc(1, spa_handle_factory_get_size(factory, NULL)); + 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 res; + } + return 0; + } + return -EBADF; +} + +static int make_node(struct data *data, struct spa_node **node, const char *lib, const char *name) +{ + struct spa_handle *handle = NULL; + void *iface; + int res; + + if ((res = load_handle(data, &handle, lib, name)) < 0) + return res; + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Node, &iface)) < 0) { + printf("can't get interface %d\n", res); + return res; + } + *node = iface; + return 0; +} + +static int on_source_ready(void *_data, int status) +{ + struct data *data = _data; + int res; + struct buffer *b; + void *sdata, *ddata; + int sstride, dstride; + int i; + uint8_t *src, *dst; + struct spa_data *datas; + struct spa_io_buffers *io = &data->source_output[0]; + + if (io->status != SPA_STATUS_HAVE_DATA || + io->buffer_id >= MAX_BUFFERS) + return -EINVAL; + + b = &data->buffers[io->buffer_id]; + io->status = SPA_STATUS_NEED_DATA; + + datas = b->buffer.datas; + + if (b->texture) { + SDL_Texture *texture = b->texture; + + SDL_UnlockTexture(texture); + if (SDL_RenderClear(data->renderer) < 0) { + fprintf(stderr, "Couldn't render clear: %s\n", SDL_GetError()); + return -EIO; + } + if (SDL_RenderCopy(data->renderer, texture, NULL, NULL) < 0) { + fprintf(stderr, "Couldn't render copy: %s\n", SDL_GetError()); + return -EIO; + } + SDL_RenderPresent(data->renderer); + + if (SDL_LockTexture(texture, NULL, &sdata, &sstride) < 0) { + fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + return -EIO; + } + + datas[0].data = sdata; + } else { + uint8_t *map; + + if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) { + fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + return -EIO; + } + sdata = datas[0].data; + if (datas[0].type == SPA_DATA_MemFd || + datas[0].type == SPA_DATA_DmaBuf) { + map = mmap(NULL, datas[0].maxsize, PROT_READ, + MAP_PRIVATE, datas[0].fd, datas[0].mapoffset); + if (map == MAP_FAILED) + return -errno; + sdata = map; + } else if (datas[0].type == SPA_DATA_MemPtr) { + map = NULL; + sdata = datas[0].data; + } else + return -EIO; + + sstride = datas[0].chunk->stride; + + for (i = 0; i < HEIGHT; i++) { + src = ((uint8_t *) sdata + i * sstride); + dst = ((uint8_t *) ddata + i * dstride); + memcpy(dst, src, SPA_MIN(sstride, dstride)); + } + SDL_UnlockTexture(data->texture); + + SDL_RenderClear(data->renderer); + SDL_RenderCopy(data->renderer, data->texture, NULL, NULL); + SDL_RenderPresent(data->renderer); + + if (map) + munmap(map, datas[0].maxsize); + } + + if ((res = spa_node_process(data->source)) < 0) + printf("got process error %d\n", res); + + return 0; +} + +static const struct spa_node_callbacks source_callbacks = { + SPA_VERSION_NODE_CALLBACKS, + .ready = on_source_ready, +}; + +static int make_nodes(struct data *data, uint32_t pattern) +{ + int res; + struct spa_pod *props; + struct spa_pod_builder b = { 0 }; + uint8_t buffer[256]; + uint32_t index; + + if ((res = + make_node(data, &data->source, + "videotestsrc/libspa-videotestsrc.so", + "videotestsrc")) < 0) { + printf("can't create videotestsrc: %d\n", res); + return res; + } + + spa_node_set_callbacks(data->source, &source_callbacks, data); + + index = 0; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + if ((res = spa_node_enum_params_sync(data->source, SPA_PARAM_Props, + &index, NULL, &props, &b)) == 1) { + spa_debug_pod(0, NULL, props); + } + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + props = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Props, 0, + SPA_PROP_patternType, SPA_POD_Int(pattern)); + + if ((res = spa_node_set_param(data->source, SPA_PARAM_Props, 0, props)) < 0) + printf("got set_props error %d\n", res); + + return res; +} + +static int setup_buffers(struct data *data) +{ + int i; + + for (i = 0; i < MAX_BUFFERS; i++) { + struct buffer *b = &data->buffers[i]; + + data->bp[i] = &b->buffer; + + b->texture = NULL; + + b->buffer.metas = b->metas; + b->buffer.n_metas = 1; + b->buffer.datas = b->datas; + b->buffer.n_datas = 1; + + b->header.flags = 0; + b->header.seq = 0; + b->header.pts = 0; + b->header.dts_offset = 0; + b->metas[0].type = SPA_META_Header; + b->metas[0].data = &b->header; + b->metas[0].size = sizeof(b->header); + + b->datas[0].type = SPA_DATA_DmaBuf; + b->datas[0].flags = 0; + b->datas[0].fd = -1; + b->datas[0].mapoffset = 0; + b->datas[0].maxsize = 0; + b->datas[0].data = NULL; + b->datas[0].chunk = &b->chunks[0]; + b->datas[0].chunk->offset = 0; + b->datas[0].chunk->size = 0; + b->datas[0].chunk->stride = 0; + } + data->n_buffers = MAX_BUFFERS; + return 0; +} + +static int sdl_alloc_buffers(struct data *data) +{ + int i; + + for (i = 0; i < MAX_BUFFERS; i++) { + struct buffer *b = &data->buffers[i]; + SDL_Texture *texture; + void *ptr; + int stride; + + texture = SDL_CreateTexture(data->renderer, + SDL_PIXELFORMAT_RGB24, + SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT); + if (!texture) { + printf("can't create texture: %s\n", SDL_GetError()); + return -ENOMEM; + } + if (SDL_LockTexture(texture, NULL, &ptr, &stride) < 0) { + fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError()); + return -EIO; + } + b->texture = texture; + + b->datas[0].type = SPA_DATA_DmaBuf; + b->datas[0].maxsize = stride * HEIGHT; + b->datas[0].data = ptr; + b->datas[0].chunk->offset = 0; + b->datas[0].chunk->size = stride * HEIGHT; + b->datas[0].chunk->stride = stride; + } + return 0; +} + +static int negotiate_formats(struct data *data) +{ + int res; + struct spa_pod *format; + uint8_t buffer[256]; + struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); + + data->source_output[0] = SPA_IO_BUFFERS_INIT; + + if ((res = + spa_node_port_set_io(data->source, + SPA_DIRECTION_OUTPUT, 0, + SPA_IO_Buffers, + &data->source_output[0], sizeof(data->source_output[0]))) < 0) + return res; + + format = spa_format_video_raw_build(&b, 0, + &SPA_VIDEO_INFO_RAW_INIT( + .format = SPA_VIDEO_FORMAT_RGB, + .size = SPA_RECTANGLE(WIDTH, HEIGHT), + .framerate = SPA_FRACTION(25,1))); + + if ((res = spa_node_port_set_param(data->source, + SPA_DIRECTION_OUTPUT, 0, + SPA_PARAM_Format, 0, + format)) < 0) + return res; + + + setup_buffers(data); + + if (data->use_buffer) { + if ((res = sdl_alloc_buffers(data)) < 0) + return res; + + if ((res = spa_node_port_use_buffers(data->source, + SPA_DIRECTION_OUTPUT, 0, 0, + data->bp, data->n_buffers)) < 0) { + printf("can't allocate buffers: %s\n", spa_strerror(res)); + return -1; + } + } else { + unsigned int n_buffers; + + data->texture = SDL_CreateTexture(data->renderer, + SDL_PIXELFORMAT_RGB24, + SDL_TEXTUREACCESS_STREAMING, WIDTH, HEIGHT); + if (!data->texture) { + printf("can't create texture: %s\n", SDL_GetError()); + return -1; + } + n_buffers = MAX_BUFFERS; + if ((res = spa_node_port_use_buffers(data->source, + SPA_DIRECTION_OUTPUT, 0, + SPA_NODE_BUFFERS_FLAG_ALLOC, + data->bp, n_buffers)) < 0) { + printf("can't allocate buffers: %s\n", spa_strerror(res)); + return -1; + } + data->n_buffers = n_buffers; + } + return 0; +} + +static void loop(struct data *data) +{ + int res; + struct spa_command cmd; + SDL_Event event; + + printf("starting...\n\n"); + cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start); + if ((res = spa_node_send_command(data->source, &cmd)) < 0) + printf("got error %d\n", res); + + data->running = true; + + while (data->running) { + // must be called from the thread that created the renderer + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: + data->running = false; + break; + } + } + + // small timeout to make sure we don't starve the SDL loop + spa_loop_control_iterate(data->control, LOOP_TIMEOUT_MS); + } + + printf("pausing...\n\n"); + cmd = SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause); + if ((res = spa_node_send_command(data->source, &cmd)) < 0) + printf("got error %d\n", res); +} + +int main(int argc, char *argv[]) +{ + struct data data = { 0 }; + int res; + const char *str; + struct spa_handle *handle = NULL; + void *iface; + uint32_t pattern = 0; + + if ((str = getenv("SPA_PLUGIN_DIR")) == NULL) + str = PLUGINDIR; + data.plugin_dir = str; + + if ((res = load_handle(&data, &handle, + "support/libspa-support.so", + SPA_NAME_SUPPORT_SYSTEM)) < 0) + return res; + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_System, &iface)) < 0) { + printf("can't get System interface %d\n", res); + return res; + } + data.system = iface; + data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, data.system); + data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataSystem, data.system); + + if ((res = load_handle(&data, &handle, + "support/libspa-support.so", + SPA_NAME_SUPPORT_LOOP)) < 0) + return res; + + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Loop, &iface)) < 0) { + printf("can't get interface %d\n", res); + return res; + } + data.loop = iface; + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_LoopControl, &iface)) < 0) { + printf("can't get interface %d\n", res); + return res; + } + data.control = iface; + if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_LoopUtils, &iface)) < 0) { + printf("can't get interface %d\n", res); + return res; + } + data.loop_utils = iface; + + data.use_buffer = USE_BUFFER; + + data.log = &default_log.log; + + if ((str = getenv("SPA_DEBUG"))) + data.log->level = atoi(str); + + data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, data.log); + data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, data.loop); + data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataLoop, data.loop); + data.support[data.n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_LoopUtils, data.loop_utils); + + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + printf("can't initialize SDL: %s\n", SDL_GetError()); + return -1; + } + + if (SDL_CreateWindowAndRenderer + (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) { + printf("can't create window: %s\n", SDL_GetError()); + return -1; + } + + if (argv[1] != NULL) + pattern = atoi(argv[1]); + + if ((res = make_nodes(&data, pattern)) < 0) { + printf("can't make nodes: %d\n", res); + return -1; + } + + if ((res = negotiate_formats(&data)) < 0) { + printf("can't negotiate nodes: %d\n", res); + return -1; + } + + spa_loop_control_enter(data.control); + loop(&data); + spa_loop_control_leave(data.control); + + SDL_DestroyRenderer(data.renderer); + + return 0; +} diff --git a/spa/examples/meson.build b/spa/examples/meson.build index fdd54fdc..8d0fc492 100644 --- a/spa/examples/meson.build +++ b/spa/examples/meson.build @@ -4,6 +4,7 @@ spa_examples = [ 'example-control', 'local-libcamera', 'local-v4l2', + 'local-videotestsrc', ] if not get_option('examples').allowed() or not get_option('spa-plugins').allowed() @@ -12,6 +13,7 @@ endif spa_examples_extra_deps = { 'local-v4l2': [sdl_dep], + 'local-videotestsrc': [sdl_dep], 'local-libcamera': [sdl_dep, libcamera_dep], } diff --git a/spa/include/spa/buffer/alloc.h b/spa/include/spa/buffer/alloc.h index 8b9e55e5..dc8f4cc6 100644 --- a/spa/include/spa/buffer/alloc.h +++ b/spa/include/spa/buffer/alloc.h @@ -11,6 +11,14 @@ extern "C" { #include <spa/buffer/buffer.h> +#ifndef SPA_API_BUFFER_ALLOC + #ifdef SPA_API_IMPL + #define SPA_API_BUFFER_ALLOC SPA_API_IMPL + #else + #define SPA_API_BUFFER_ALLOC static inline + #endif +#endif + /** * \addtogroup spa_buffer * \{ @@ -58,7 +66,7 @@ struct spa_buffer_alloc_info { * \param data_aligns \a n_datas alignments * \return 0 on success. * */ -static inline int spa_buffer_alloc_fill_info(struct spa_buffer_alloc_info *info, +SPA_API_BUFFER_ALLOC int spa_buffer_alloc_fill_info(struct spa_buffer_alloc_info *info, uint32_t n_metas, struct spa_meta metas[], uint32_t n_datas, struct spa_data datas[], uint32_t data_aligns[]) @@ -179,7 +187,7 @@ static inline int spa_buffer_alloc_fill_info(struct spa_buffer_alloc_info *info, * \param data_mem memory to hold the meta, chunk and memory * \return a struct \ref spa_buffer in \a skel_mem */ -static inline struct spa_buffer * +SPA_API_BUFFER_ALLOC struct spa_buffer * spa_buffer_alloc_layout(struct spa_buffer_alloc_info *info, void *skel_mem, void *data_mem) { @@ -257,7 +265,7 @@ spa_buffer_alloc_layout(struct spa_buffer_alloc_info *info, * \return 0 on success. * */ -static inline int +SPA_API_BUFFER_ALLOC int spa_buffer_alloc_layout_array(struct spa_buffer_alloc_info *info, uint32_t n_buffers, struct spa_buffer *buffers[], void *skel_mem, void *data_mem) @@ -292,7 +300,7 @@ spa_buffer_alloc_layout_array(struct spa_buffer_alloc_info *info, * allocation failed. * */ -static inline struct spa_buffer ** +SPA_API_BUFFER_ALLOC struct spa_buffer ** spa_buffer_alloc_array(uint32_t n_buffers, uint32_t flags, uint32_t n_metas, struct spa_meta metas[], uint32_t n_datas, struct spa_data datas[], diff --git a/spa/include/spa/buffer/buffer.h b/spa/include/spa/buffer/buffer.h index 7b3b7c00..03fd13b2 100644 --- a/spa/include/spa/buffer/buffer.h +++ b/spa/include/spa/buffer/buffer.h @@ -12,6 +12,14 @@ extern "C" { #include <spa/utils/defs.h> #include <spa/buffer/meta.h> +#ifndef SPA_API_BUFFER + #ifdef SPA_API_IMPL + #define SPA_API_BUFFER SPA_API_IMPL + #else + #define SPA_API_BUFFER static inline + #endif +#endif + /** \defgroup spa_buffer Buffers * * Buffers describe the data and metadata that is exchanged between @@ -91,7 +99,7 @@ struct spa_buffer { }; /** Find metadata in a buffer */ -static inline struct spa_meta *spa_buffer_find_meta(const struct spa_buffer *b, uint32_t type) +SPA_API_BUFFER struct spa_meta *spa_buffer_find_meta(const struct spa_buffer *b, uint32_t type) { uint32_t i; @@ -102,7 +110,7 @@ static inline struct spa_meta *spa_buffer_find_meta(const struct spa_buffer *b, return NULL; } -static inline void *spa_buffer_find_meta_data(const struct spa_buffer *b, uint32_t type, size_t size) +SPA_API_BUFFER void *spa_buffer_find_meta_data(const struct spa_buffer *b, uint32_t type, size_t size) { struct spa_meta *m; if ((m = spa_buffer_find_meta(b, type)) && m->size >= size) diff --git a/spa/include/spa/buffer/meta.h b/spa/include/spa/buffer/meta.h index 844a8a25..b484cfb0 100644 --- a/spa/include/spa/buffer/meta.h +++ b/spa/include/spa/buffer/meta.h @@ -12,6 +12,14 @@ extern "C" { #include <spa/utils/defs.h> #include <spa/pod/pod.h> +#ifndef SPA_API_META + #ifdef SPA_API_IMPL + #define SPA_API_META SPA_API_IMPL + #else + #define SPA_API_META static inline + #endif +#endif + /** * \addtogroup spa_buffer * \{ @@ -46,14 +54,13 @@ struct spa_meta { void *data; /**< pointer to metadata */ }; -static inline void *spa_meta_first(const struct spa_meta *m) { +SPA_API_META 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) { + +SPA_API_META 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)) /** @@ -80,19 +87,16 @@ struct spa_meta_region { struct spa_region region; }; -static inline bool spa_meta_region_is_valid(const struct spa_meta_region *m) { +SPA_API_META 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)++) -#define spa_meta_bitmap_is_valid(m) ((m)->format != 0) - /** * Bitmap information * @@ -112,7 +116,9 @@ struct spa_meta_bitmap { * info. */ }; -#define spa_meta_cursor_is_valid(m) ((m)->id != 0) +SPA_API_META bool spa_meta_bitmap_is_valid(const struct spa_meta_bitmap *m) { + return m->format != 0; +} /** * Cursor information @@ -132,6 +138,10 @@ struct spa_meta_cursor { * struct spa_meta_bitmap at the offset. */ }; +SPA_API_META bool spa_meta_cursor_is_valid(const struct spa_meta_cursor *m) { + return m->id != 0; +} + /** a timed set of events associated with the buffer */ struct spa_meta_control { struct spa_pod_sequence sequence; @@ -168,9 +178,10 @@ struct spa_meta_videotransform { * Metadata to describe the time on the timeline when the buffer * can be acquired and when it can be reused. * - * This metadata will usually also require negotiation of 2 extra - * buffer datas of type SPA_DATA_SyncObj with 2 fds for the acquire - * and release timelines respectively. + * This metadata will require negotiation of 2 extra fds for the acquire + * and release timelines respectively. One way to achieve this is to place + * this metadata as SPA_PARAM_BUFFERS_metaType when negotiating a buffer + * layout with 2 extra fds. */ struct spa_meta_sync_timeline { uint32_t flags; diff --git a/spa/include/spa/control/control.h b/spa/include/spa/control/control.h index 1d955d3e..1c1ec81f 100644 --- a/spa/include/spa/control/control.h +++ b/spa/include/spa/control/control.h @@ -24,9 +24,12 @@ extern "C" { /** Different Control types */ enum spa_control_type { SPA_CONTROL_Invalid, - SPA_CONTROL_Properties, /**< data contains a SPA_TYPE_OBJECT_Props */ - SPA_CONTROL_Midi, /**< data contains a spa_pod_bytes with raw midi data */ - SPA_CONTROL_OSC, /**< data contains a spa_pod_bytes with an OSC packet */ + SPA_CONTROL_Properties, /**< SPA_TYPE_OBJECT_Props */ + SPA_CONTROL_Midi, /**< spa_pod_bytes with raw midi data (deprecated, use SPA_CONTROL_UMP) */ + SPA_CONTROL_OSC, /**< spa_pod_bytes with an OSC packet */ + SPA_CONTROL_UMP, /**< spa_pod_bytes with raw UMP (universal MIDI packet) + * data. The UMP 32 bit words are stored in native endian + * format. */ _SPA_CONTROL_LAST, /**< not part of ABI */ }; diff --git a/spa/include/spa/control/type-info.h b/spa/include/spa/control/type-info.h index 2ff3ce18..6c7bd6a9 100644 --- a/spa/include/spa/control/type-info.h +++ b/spa/include/spa/control/type-info.h @@ -15,7 +15,7 @@ extern "C" { */ #include <spa/utils/defs.h> -#include <spa/utils/type-info.h> +#include <spa/utils/type.h> #include <spa/control/control.h> /* base for parameter object enumerations */ @@ -27,6 +27,7 @@ static const struct spa_type_info spa_type_control[] = { { SPA_CONTROL_Properties, SPA_TYPE_Int, SPA_TYPE_INFO_CONTROL_BASE "Properties", NULL }, { SPA_CONTROL_Midi, SPA_TYPE_Int, SPA_TYPE_INFO_CONTROL_BASE "Midi", NULL }, { SPA_CONTROL_OSC, SPA_TYPE_Int, SPA_TYPE_INFO_CONTROL_BASE "OSC", NULL }, + { SPA_CONTROL_UMP, SPA_TYPE_Int, SPA_TYPE_INFO_CONTROL_BASE "UMP", NULL }, { 0, 0, NULL, NULL }, }; diff --git a/spa/include/spa/control/ump-utils.h b/spa/include/spa/control/ump-utils.h new file mode 100644 index 00000000..9f57efb9 --- /dev/null +++ b/spa/include/spa/control/ump-utils.h @@ -0,0 +1,230 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + + +#ifndef SPA_CONTROL_UMP_UTILS_H +#define SPA_CONTROL_UMP_UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <errno.h> +#include <spa/utils/defs.h> + +#ifndef SPA_API_CONTROL_UMP_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_CONTROL_UMP_UTILS SPA_API_IMPL + #else + #define SPA_API_CONTROL_UMP_UTILS static inline + #endif +#endif +/** + * \addtogroup spa_control + * \{ + */ + +SPA_API_CONTROL_UMP_UTILS size_t spa_ump_message_size(uint8_t message_type) +{ + static const uint32_t ump_sizes[] = { + [0x0] = 1, /* Utility messages */ + [0x1] = 1, /* System messages */ + [0x2] = 1, /* MIDI 1.0 messages */ + [0x3] = 2, /* 7bit SysEx messages */ + [0x4] = 2, /* MIDI 2.0 messages */ + [0x5] = 4, /* 8bit data message */ + [0x6] = 1, + [0x7] = 1, + [0x8] = 2, + [0x9] = 2, + [0xa] = 2, + [0xb] = 3, + [0xc] = 3, + [0xd] = 4, /* Flexible data messages */ + [0xe] = 4, + [0xf] = 4, /* Stream messages */ + }; + return ump_sizes[message_type & 0xf]; +} + +SPA_API_CONTROL_UMP_UTILS int spa_ump_to_midi(uint32_t *ump, size_t ump_size, + uint8_t *midi, size_t midi_maxsize) +{ + int size = 0; + + if (ump_size < 4) + return 0; + if (midi_maxsize < 8) + return -ENOSPC; + + switch (ump[0] >> 28) { + case 0x1: /* System Real Time and System Common Messages (except System Exclusive) */ + midi[size++] = (ump[0] >> 16) & 0xff; + if (midi[0] >= 0xf1 && midi[0] <= 0xf3) { + midi[size++] = (ump[0] >> 8) & 0x7f; + if (midi[0] == 0xf2) + midi[size++] = ump[0] & 0x7f; + } + break; + case 0x2: /* MIDI 1.0 Channel Voice Messages */ + midi[size++] = (ump[0] >> 16); + midi[size++] = (ump[0] >> 8); + if (midi[0] < 0xc0 || midi[0] > 0xdf) + midi[size++] = (ump[0]); + break; + case 0x3: /* Data Messages (including System Exclusive) */ + { + uint8_t status, i, bytes; + + if (ump_size < 8) + return 0; + + status = (ump[0] >> 20) & 0xf; + bytes = SPA_CLAMP((ump[0] >> 16) & 0xf, 0u, 6u); + + if (status == 0 || status == 1) + midi[size++] = 0xf0; + for (i = 0 ; i < bytes; i++) + /* ump[0] >> 8 | ump[0] | ump[1] >> 24 | ump[1] >>16 ... */ + midi[size++] = ump[(i+2)/4] >> ((5-i)%4 * 8); + if (status == 0 || status == 3) + midi[size++] = 0xf7; + break; + } + case 0x4: /* MIDI 2.0 Channel Voice Messages */ + if (ump_size < 8) + return 0; + midi[size++] = (ump[0] >> 16) | 0x80; + if (midi[0] < 0xc0 || midi[0] > 0xdf) + midi[size++] = (ump[0] >> 8) & 0x7f; + midi[size++] = (ump[1] >> 25); + break; + + case 0x0: /* Utility Messages */ + case 0x5: /* Data Messages */ + default: + return 0; + } + return size; +} + +SPA_API_CONTROL_UMP_UTILS int spa_ump_from_midi(uint8_t **midi, size_t *midi_size, + uint32_t *ump, size_t ump_maxsize, uint8_t group, uint64_t *state) +{ + int size = 0; + uint32_t i, prefix = group << 24, to_consume = 0, bytes; + uint8_t status, *m = (*midi), end; + + if (*midi_size < 1) + return 0; + if (ump_maxsize < 16) + return -ENOSPC; + + status = m[0]; + + /* SysEx */ + if (*state == 0) { + if (status == 0xf0) + *state = 1; /* sysex start */ + else if (status == 0xf7) + *state = 2; /* sysex continue */ + } + if (*state & 3) { + prefix |= 0x30000000; + if (status & 0x80) { + m++; + to_consume++; + } + bytes = SPA_CLAMP(*midi_size - to_consume, 0u, 7u); + if (bytes > 0) { + end = m[bytes-1]; + if (end & 0x80) { + bytes--; /* skip terminator */ + to_consume++; + } + else + end = 0xf0; /* pretend there is a continue terminator */ + + bytes = SPA_CLAMP(bytes, 0u, 6u); + to_consume += bytes; + + if (end == 0xf7) { + if (*state == 2) { + /* continue and done */ + prefix |= 0x3 << 20; + *state = 0; + } + } else if (*state == 1) { + /* first packet but not finished */ + prefix |= 0x1 << 20; + *state = 2; /* sysex continue */ + } else { + /* continue and not finished */ + prefix |= 0x2 << 20; + } + ump[size++] = prefix | bytes << 16; + ump[size++] = 0; + for (i = 0 ; i < bytes; i++) + /* ump[0] |= (m[0] & 0x7f) << 8 + * ump[0] |= (m[1] & 0x7f) + * ump[1] |= (m[2] & 0x7f) << 24 + * ... */ + ump[(i+2)/4] |= (m[i] & 0x7f) << ((5-i)%4 * 8); + } + } else { + /* regular messages */ + switch (status) { + case 0x80 ... 0x8f: + case 0x90 ... 0x9f: + case 0xa0 ... 0xaf: + case 0xb0 ... 0xbf: + case 0xe0 ... 0xef: + to_consume = 3; + prefix |= 0x20000000; + break; + case 0xc0 ... 0xdf: + to_consume = 2; + prefix |= 0x20000000; + break; + case 0xf2: + to_consume = 3; + prefix = 0x10000000; + break; + case 0xf1: case 0xf3: + to_consume = 2; + prefix = 0x10000000; + break; + case 0xf4 ... 0xff: + to_consume = 1; + prefix = 0x10000000; + break; + default: + return -EIO; + } + if (*midi_size < to_consume) { + to_consume = *midi_size; + } else { + prefix |= status << 16; + if (to_consume > 1) + prefix |= (m[1] & 0x7f) << 8; + if (to_consume > 2) + prefix |= (m[2] & 0x7f); + ump[size++] = prefix; + } + } + (*midi_size) -= to_consume; + (*midi) += to_consume; + + return size * 4; +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_CONTROL_UMP_UTILS_H */ diff --git a/spa/include/spa/debug/buffer.h b/spa/include/spa/debug/buffer.h index 8efa411c..eea48ae4 100644 --- a/spa/include/spa/debug/buffer.h +++ b/spa/include/spa/debug/buffer.h @@ -23,7 +23,15 @@ extern "C" { #include <spa/debug/types.h> #include <spa/buffer/type-info.h> -static inline int spa_debugc_buffer(struct spa_debug_context *ctx, int indent, const struct spa_buffer *buffer) +#ifndef SPA_API_DEBUG_BUFFER + #ifdef SPA_API_IMPL + #define SPA_API_DEBUG_BUFFER SPA_API_IMPL + #else + #define SPA_API_DEBUG_BUFFER static inline + #endif +#endif + +SPA_API_DEBUG_BUFFER int spa_debugc_buffer(struct spa_debug_context *ctx, int indent, const struct spa_buffer *buffer) { uint32_t i; @@ -98,7 +106,7 @@ static inline int spa_debugc_buffer(struct spa_debug_context *ctx, int indent, c return 0; } -static inline int spa_debug_buffer(int indent, const struct spa_buffer *buffer) +SPA_API_DEBUG_BUFFER int spa_debug_buffer(int indent, const struct spa_buffer *buffer) { return spa_debugc_buffer(NULL, indent, buffer); } diff --git a/spa/include/spa/debug/context.h b/spa/include/spa/debug/context.h index bd0186b0..13002f66 100644 --- a/spa/include/spa/debug/context.h +++ b/spa/include/spa/debug/context.h @@ -26,13 +26,22 @@ extern "C" { #define spa_debug(_fmt,...) spa_debugn(_fmt"\n", ## __VA_ARGS__) #endif + +#ifndef SPA_API_DEBUG_CONTEXT + #ifdef SPA_API_IMPL + #define SPA_API_DEBUG_CONTEXT SPA_API_IMPL + #else + #define SPA_API_DEBUG_CONTEXT static inline + #endif +#endif + struct spa_debug_context { void (*log) (struct spa_debug_context *ctx, const char *fmt, ...) SPA_PRINTF_FUNC(2, 3); }; #define spa_debugc(_c,_fmt,...) (_c)?((_c)->log((_c),_fmt, ## __VA_ARGS__)):(void)spa_debug(_fmt, ## __VA_ARGS__) -static inline void spa_debugc_error_location(struct spa_debug_context *c, +SPA_API_DEBUG_CONTEXT void spa_debugc_error_location(struct spa_debug_context *c, struct spa_error_location *loc) { int i, skip = loc->col > 80 ? loc->col - 40 : 0, lc = loc->col-skip-1; diff --git a/spa/include/spa/debug/dict.h b/spa/include/spa/debug/dict.h index 2eb3672a..5657b2d9 100644 --- a/spa/include/spa/debug/dict.h +++ b/spa/include/spa/debug/dict.h @@ -17,7 +17,15 @@ extern "C" { #include <spa/debug/context.h> #include <spa/utils/dict.h> -static inline int spa_debugc_dict(struct spa_debug_context *ctx, int indent, const struct spa_dict *dict) +#ifndef SPA_API_DEBUG_DICT + #ifdef SPA_API_IMPL + #define SPA_API_DEBUG_DICT SPA_API_IMPL + #else + #define SPA_API_DEBUG_DICT static inline + #endif +#endif + +SPA_API_DEBUG_DICT int spa_debugc_dict(struct spa_debug_context *ctx, int indent, const struct spa_dict *dict) { const struct spa_dict_item *item; spa_debugc(ctx, "%*sflags:%08x n_items:%d", indent, "", dict->flags, dict->n_items); @@ -27,7 +35,7 @@ static inline int spa_debugc_dict(struct spa_debug_context *ctx, int indent, con return 0; } -static inline int spa_debug_dict(int indent, const struct spa_dict *dict) +SPA_API_DEBUG_DICT int spa_debug_dict(int indent, const struct spa_dict *dict) { return spa_debugc_dict(NULL, indent, dict); } diff --git a/spa/include/spa/debug/file.h b/spa/include/spa/debug/file.h index 115264f9..17ce46b7 100644 --- a/spa/include/spa/debug/file.h +++ b/spa/include/spa/debug/file.h @@ -26,13 +26,21 @@ extern "C" { * \{ */ +#ifndef SPA_API_DEBUG_FILE + #ifdef SPA_API_IMPL + #define SPA_API_DEBUG_FILE SPA_API_IMPL + #else + #define SPA_API_DEBUG_FILE static inline + #endif +#endif + struct spa_debug_file_ctx { struct spa_debug_context ctx; FILE *f; }; SPA_PRINTF_FUNC(2,3) -static inline void spa_debug_file_log(struct spa_debug_context *ctx, const char *fmt, ...) +SPA_API_DEBUG_FILE void spa_debug_file_log(struct spa_debug_context *ctx, const char *fmt, ...) { struct spa_debug_file_ctx *c = SPA_CONTAINER_OF(ctx, struct spa_debug_file_ctx, ctx); va_list args; diff --git a/spa/include/spa/debug/format.h b/spa/include/spa/debug/format.h index c92b43e7..ad7f2048 100644 --- a/spa/include/spa/debug/format.h +++ b/spa/include/spa/debug/format.h @@ -21,7 +21,16 @@ extern "C" { #include <spa/param/type-info.h> #include <spa/param/format-utils.h> -static inline int +#ifndef SPA_API_DEBUG_FORMAT + #ifdef SPA_API_IMPL + #define SPA_API_DEBUG_FORMAT SPA_API_IMPL + #else + #define SPA_API_DEBUG_FORMAT static inline + #endif +#endif + + +SPA_API_DEBUG_FORMAT int spa_debug_strbuf_format_value(struct spa_strbuf *buffer, const struct spa_type_info *info, uint32_t type, void *body, uint32_t size) { @@ -96,7 +105,7 @@ spa_debug_strbuf_format_value(struct spa_strbuf *buffer, const struct spa_type_i return 0; } -static inline int +SPA_API_DEBUG_FORMAT int spa_debug_format_value(const struct spa_type_info *info, uint32_t type, void *body, uint32_t size) { @@ -108,7 +117,7 @@ spa_debug_format_value(const struct spa_type_info *info, return 0; } -static inline int spa_debugc_format(struct spa_debug_context *ctx, int indent, +SPA_API_DEBUG_FORMAT int spa_debugc_format(struct spa_debug_context *ctx, int indent, const struct spa_type_info *info, const struct spa_pod *format) { const char *media_type; @@ -198,7 +207,7 @@ static inline int spa_debugc_format(struct spa_debug_context *ctx, int indent, return 0; } -static inline int spa_debug_format(int indent, +SPA_API_DEBUG_FORMAT int spa_debug_format(int indent, const struct spa_type_info *info, const struct spa_pod *format) { return spa_debugc_format(NULL, indent, info, format); diff --git a/spa/include/spa/debug/log.h b/spa/include/spa/debug/log.h index 89eaee10..05c3bd50 100644 --- a/spa/include/spa/debug/log.h +++ b/spa/include/spa/debug/log.h @@ -20,6 +20,14 @@ extern "C" { #include <spa/debug/mem.h> #include <spa/debug/pod.h> +#ifndef SPA_API_DEBUG_LOG + #ifdef SPA_API_IMPL + #define SPA_API_DEBUG_LOG SPA_API_IMPL + #else + #define SPA_API_DEBUG_LOG static inline + #endif +#endif + /** * \addtogroup spa_debug * \{ @@ -36,7 +44,7 @@ struct spa_debug_log_ctx { }; SPA_PRINTF_FUNC(2,3) -static inline void spa_debug_log_log(struct spa_debug_context *ctx, const char *fmt, ...) +SPA_API_DEBUG_LOG void spa_debug_log_log(struct spa_debug_context *ctx, const char *fmt, ...) { struct spa_debug_log_ctx *c = SPA_CONTAINER_OF(ctx, struct spa_debug_log_ctx, ctx); va_list args; diff --git a/spa/include/spa/debug/mem.h b/spa/include/spa/debug/mem.h index 462a5f93..96662948 100644 --- a/spa/include/spa/debug/mem.h +++ b/spa/include/spa/debug/mem.h @@ -18,7 +18,15 @@ extern "C" { #include <spa/debug/context.h> -static inline int spa_debugc_mem(struct spa_debug_context *ctx, int indent, const void *data, size_t size) +#ifndef SPA_API_DEBUG_MEM + #ifdef SPA_API_IMPL + #define SPA_API_DEBUG_MEM SPA_API_IMPL + #else + #define SPA_API_DEBUG_MEM static inline + #endif +#endif + +SPA_API_DEBUG_MEM int spa_debugc_mem(struct spa_debug_context *ctx, int indent, const void *data, size_t size) { const uint8_t *t = (const uint8_t*)data; char buffer[512]; @@ -36,7 +44,7 @@ static inline int spa_debugc_mem(struct spa_debug_context *ctx, int indent, cons return 0; } -static inline int spa_debug_mem(int indent, const void *data, size_t size) +SPA_API_DEBUG_MEM int spa_debug_mem(int indent, const void *data, size_t size) { return spa_debugc_mem(NULL, indent, data, size); } diff --git a/spa/include/spa/debug/node.h b/spa/include/spa/debug/node.h index 592d6346..baa273ff 100644 --- a/spa/include/spa/debug/node.h +++ b/spa/include/spa/debug/node.h @@ -18,7 +18,15 @@ extern "C" { #include <spa/debug/context.h> #include <spa/debug/dict.h> -static inline int spa_debugc_port_info(struct spa_debug_context *ctx, int indent, const struct spa_port_info *info) +#ifndef SPA_API_DEBUG_NODE + #ifdef SPA_API_IMPL + #define SPA_API_DEBUG_NODE SPA_API_IMPL + #else + #define SPA_API_DEBUG_NODE static inline + #endif +#endif + +SPA_API_DEBUG_NODE int spa_debugc_port_info(struct spa_debug_context *ctx, int indent, const struct spa_port_info *info) { spa_debugc(ctx, "%*s" "struct spa_port_info %p:", indent, "", info); spa_debugc(ctx, "%*s" " flags: \t%08" PRIx64, indent, "", info->flags); @@ -31,7 +39,7 @@ static inline int spa_debugc_port_info(struct spa_debug_context *ctx, int indent return 0; } -static inline int spa_debug_port_info(int indent, const struct spa_port_info *info) +SPA_API_DEBUG_NODE int spa_debug_port_info(int indent, const struct spa_port_info *info) { return spa_debugc_port_info(NULL, indent, info); } diff --git a/spa/include/spa/debug/pod.h b/spa/include/spa/debug/pod.h index a31b78e3..9db6f4b0 100644 --- a/spa/include/spa/debug/pod.h +++ b/spa/include/spa/debug/pod.h @@ -20,7 +20,15 @@ extern "C" { #include <spa/pod/pod.h> #include <spa/pod/iter.h> -static inline int +#ifndef SPA_API_DEBUG_POD + #ifdef SPA_API_IMPL + #define SPA_API_DEBUG_POD SPA_API_IMPL + #else + #define SPA_API_DEBUG_POD static inline + #endif +#endif + +SPA_API_DEBUG_POD int spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa_type_info *info, uint32_t type, void *body, uint32_t size) { @@ -60,13 +68,13 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa case SPA_TYPE_Rectangle: { struct spa_rectangle *r = (struct spa_rectangle *)body; - spa_debugc(ctx, "%*s" "Rectangle %dx%d", indent, "", r->width, r->height); + spa_debugc(ctx, "%*s" "Rectangle %" PRIu32 "x%" PRIu32 "", indent, "", r->width, r->height); break; } case SPA_TYPE_Fraction: { struct spa_fraction *f = (struct spa_fraction *)body; - spa_debugc(ctx, "%*s" "Fraction %d/%d", indent, "", f->num, f->denom); + spa_debugc(ctx, "%*s" "Fraction %" PRIu32 "/%" PRIu32 "", indent, "", f->num, f->denom); break; } case SPA_TYPE_Bitmap: @@ -174,7 +182,7 @@ spa_debugc_pod_value(struct spa_debug_context *ctx, int indent, const struct spa return 0; } -static inline int spa_debugc_pod(struct spa_debug_context *ctx, int indent, +SPA_API_DEBUG_POD int spa_debugc_pod(struct spa_debug_context *ctx, int indent, const struct spa_type_info *info, const struct spa_pod *pod) { return spa_debugc_pod_value(ctx, indent, info ? info : SPA_TYPE_ROOT, @@ -183,14 +191,14 @@ static inline int spa_debugc_pod(struct spa_debug_context *ctx, int indent, SPA_POD_BODY_SIZE(pod)); } -static inline int +SPA_API_DEBUG_POD int spa_debug_pod_value(int indent, const struct spa_type_info *info, uint32_t type, void *body, uint32_t size) { return spa_debugc_pod_value(NULL, indent, info, type, body, size); } -static inline int spa_debug_pod(int indent, +SPA_API_DEBUG_POD int spa_debug_pod(int indent, const struct spa_type_info *info, const struct spa_pod *pod) { return spa_debugc_pod(NULL, indent, info, pod); diff --git a/spa/include/spa/debug/types.h b/spa/include/spa/debug/types.h index 6e53a547..d7ca8366 100644 --- a/spa/include/spa/debug/types.h +++ b/spa/include/spa/debug/types.h @@ -18,7 +18,16 @@ extern "C" { #include <string.h> -static inline const struct spa_type_info *spa_debug_type_find(const struct spa_type_info *info, uint32_t type) +#ifndef SPA_API_DEBUG_TYPES + #ifdef SPA_API_IMPL + #define SPA_API_DEBUG_TYPES SPA_API_IMPL + #else + #define SPA_API_DEBUG_TYPES static inline + #endif +#endif + + +SPA_API_DEBUG_TYPES const struct spa_type_info *spa_debug_type_find(const struct spa_type_info *info, uint32_t type) { const struct spa_type_info *res; @@ -37,22 +46,19 @@ static inline const struct spa_type_info *spa_debug_type_find(const struct spa_t return NULL; } -static inline const char *spa_debug_type_short_name(const char *name) +SPA_API_DEBUG_TYPES const char *spa_debug_type_short_name(const char *name) { - const char *h; - if ((h = strrchr(name, ':')) != NULL) - name = h + 1; - return name; + return spa_type_short_name(name); } -static inline const char *spa_debug_type_find_name(const struct spa_type_info *info, uint32_t type) +SPA_API_DEBUG_TYPES const char *spa_debug_type_find_name(const struct spa_type_info *info, uint32_t type) { if ((info = spa_debug_type_find(info, type)) == NULL) return NULL; return info->name; } -static inline const char *spa_debug_type_find_short_name(const struct spa_type_info *info, uint32_t type) +SPA_API_DEBUG_TYPES const char *spa_debug_type_find_short_name(const struct spa_type_info *info, uint32_t type) { const char *str; if ((str = spa_debug_type_find_name(info, type)) == NULL) @@ -60,7 +66,7 @@ static inline const char *spa_debug_type_find_short_name(const struct spa_type_i return spa_debug_type_short_name(str); } -static inline uint32_t spa_debug_type_find_type(const struct spa_type_info *info, const char *name) +SPA_API_DEBUG_TYPES uint32_t spa_debug_type_find_type(const struct spa_type_info *info, const char *name) { if (info == NULL) info = SPA_TYPE_ROOT; @@ -76,7 +82,7 @@ static inline uint32_t spa_debug_type_find_type(const struct spa_type_info *info return SPA_ID_INVALID; } -static inline const struct spa_type_info *spa_debug_type_find_short(const struct spa_type_info *info, const char *name) +SPA_API_DEBUG_TYPES const struct spa_type_info *spa_debug_type_find_short(const struct spa_type_info *info, const char *name) { while (info && info->name) { if (strcmp(spa_debug_type_short_name(info->name), name) == 0) @@ -90,7 +96,7 @@ static inline const struct spa_type_info *spa_debug_type_find_short(const struct return NULL; } -static inline uint32_t spa_debug_type_find_type_short(const struct spa_type_info *info, const char *name) +SPA_API_DEBUG_TYPES uint32_t spa_debug_type_find_type_short(const struct spa_type_info *info, const char *name) { if ((info = spa_debug_type_find_short(info, name)) == NULL) return SPA_ID_INVALID; diff --git a/spa/include/spa/filter-graph/filter-graph.h b/spa/include/spa/filter-graph/filter-graph.h new file mode 100644 index 00000000..05904c7f --- /dev/null +++ b/spa/include/spa/filter-graph/filter-graph.h @@ -0,0 +1,150 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_FILTER_GRAPH_H +#define SPA_FILTER_GRAPH_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> + +#include <spa/utils/defs.h> +#include <spa/utils/hook.h> +#include <spa/pod/builder.h> + +#ifndef SPA_API_FILTER_GRAPH + #ifdef SPA_API_IMPL + #define SPA_API_FILTER_GRAPH SPA_API_IMPL + #else + #define SPA_API_FILTER_GRAPH static inline + #endif +#endif + +/** \defgroup spa_filter_graph Filter Graph + * a graph of filters + */ + +/** + * \addtogroup spa_filter_graph + * \{ + */ + +/** + * A graph of filters + */ +#define SPA_TYPE_INTERFACE_FilterGraph SPA_TYPE_INFO_INTERFACE_BASE "FilterGraph" + +#define SPA_VERSION_FILTER_GRAPH 0 +struct spa_filter_graph { struct spa_interface iface; }; + +struct spa_filter_graph_info { + uint32_t n_inputs; + uint32_t n_outputs; + +#define SPA_FILTER_GRAPH_CHANGE_MASK_FLAGS (1u<<0) +#define SPA_FILTER_GRAPH_CHANGE_MASK_PROPS (1u<<1) + uint64_t change_mask; + + uint64_t flags; + struct spa_dict *props; +}; + +struct spa_filter_graph_events { +#define SPA_VERSION_FILTER_GRAPH_EVENTS 0 + uint32_t version; + + void (*info) (void *object, const struct spa_filter_graph_info *info); + + void (*apply_props) (void *object, enum spa_direction direction, const struct spa_pod *props); + + void (*props_changed) (void *object, enum spa_direction direction); +}; + +struct spa_filter_graph_methods { +#define SPA_VERSION_FILTER_GRAPH_METHODS 0 + uint32_t version; + + int (*add_listener) (void *object, + struct spa_hook *listener, + const struct spa_filter_graph_events *events, + void *data); + + int (*enum_prop_info) (void *object, uint32_t idx, struct spa_pod_builder *b, + struct spa_pod **param); + int (*get_props) (void *object, struct spa_pod_builder *b, struct spa_pod **props); + int (*set_props) (void *object, enum spa_direction direction, const struct spa_pod *props); + + int (*activate) (void *object, const struct spa_dict *props); + int (*deactivate) (void *object); + + int (*reset) (void *object); + + int (*process) (void *object, const void *in[], void *out[], uint32_t n_samples); +}; + +SPA_API_FILTER_GRAPH int spa_filter_graph_add_listener(struct spa_filter_graph *object, + struct spa_hook *listener, + const struct spa_filter_graph_events *events, void *data) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_filter_graph, &object->iface, add_listener, 0, listener, + events, data); +} + +SPA_API_FILTER_GRAPH int spa_filter_graph_enum_prop_info(struct spa_filter_graph *object, + uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_filter_graph, &object->iface, enum_prop_info, 0, idx, b, param); +} +SPA_API_FILTER_GRAPH int spa_filter_graph_get_props(struct spa_filter_graph *object, + struct spa_pod_builder *b, struct spa_pod **props) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_filter_graph, &object->iface, get_props, 0, b, props); +} + +SPA_API_FILTER_GRAPH int spa_filter_graph_set_props(struct spa_filter_graph *object, + enum spa_direction direction, const struct spa_pod *props) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_filter_graph, &object->iface, set_props, 0, direction, props); +} + +SPA_API_FILTER_GRAPH int spa_filter_graph_activate(struct spa_filter_graph *object, const struct spa_dict *props) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_filter_graph, &object->iface, activate, 0, props); +} +SPA_API_FILTER_GRAPH int spa_filter_graph_deactivate(struct spa_filter_graph *object) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_filter_graph, &object->iface, deactivate, 0); +} + +SPA_API_FILTER_GRAPH int spa_filter_graph_reset(struct spa_filter_graph *object) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_filter_graph, &object->iface, reset, 0); +} + +SPA_API_FILTER_GRAPH int spa_filter_graph_process(struct spa_filter_graph *object, + const void *in[], void *out[], uint32_t n_samples) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_filter_graph, &object->iface, process, 0, in, out, n_samples); +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_FILTER_GRAPH_H */ + diff --git a/spa/include/spa/graph/graph.h b/spa/include/spa/graph/graph.h index 117f6eef..537e6e75 100644 --- a/spa/include/spa/graph/graph.h +++ b/spa/include/spa/graph/graph.h @@ -25,6 +25,15 @@ extern "C" { #include <spa/node/node.h> #include <spa/node/io.h> +#ifndef SPA_API_GRAPH + #ifdef SPA_API_IMPL + #define SPA_API_GRAPH SPA_API_IMPL + #else + #define SPA_API_GRAPH static inline + #endif +#endif + + #ifndef spa_debug #define spa_debug(...) #endif @@ -40,7 +49,7 @@ struct spa_graph_state { int32_t pending; /**< number of pending signals */ }; -static inline void spa_graph_state_reset(struct spa_graph_state *state) +SPA_API_GRAPH void spa_graph_state_reset(struct spa_graph_state *state) { state->pending = state->required; } @@ -56,7 +65,7 @@ struct spa_graph_link { #define spa_graph_state_dec(s) (SPA_ATOMIC_DEC(s->pending) == 0) -static inline int spa_graph_link_trigger(struct spa_graph_link *link) +SPA_API_GRAPH int spa_graph_link_trigger(struct spa_graph_link *link) { struct spa_graph_state *state = link->state; @@ -118,7 +127,7 @@ struct spa_graph_port { struct spa_graph_port *peer; /**< peer */ }; -static inline int spa_graph_node_trigger(struct spa_graph_node *node) +SPA_API_GRAPH int spa_graph_node_trigger(struct spa_graph_node *node) { struct spa_graph_link *l; spa_debug("node %p trigger", node); @@ -127,7 +136,7 @@ static inline int spa_graph_node_trigger(struct spa_graph_node *node) return 0; } -static inline int spa_graph_run(struct spa_graph *graph) +SPA_API_GRAPH int spa_graph_run(struct spa_graph *graph) { struct spa_graph_node *n, *t; struct spa_list pending; @@ -152,27 +161,27 @@ static inline int spa_graph_run(struct spa_graph *graph) return 0; } -static inline int spa_graph_finish(struct spa_graph *graph) +SPA_API_GRAPH int spa_graph_finish(struct spa_graph *graph) { spa_debug("graph %p finish", graph); if (graph->parent) return spa_graph_node_trigger(graph->parent); return 0; } -static inline int spa_graph_link_signal_node(void *data) +SPA_API_GRAPH int spa_graph_link_signal_node(void *data) { struct spa_graph_node *node = (struct spa_graph_node *)data; spa_debug("node %p call process", node); return spa_graph_node_process(node); } -static inline int spa_graph_link_signal_graph(void *data) +SPA_API_GRAPH int spa_graph_link_signal_graph(void *data) { struct spa_graph_node *node = (struct spa_graph_node *)data; return spa_graph_finish(node->graph); } -static inline void spa_graph_init(struct spa_graph *graph, struct spa_graph_state *state) +SPA_API_GRAPH void spa_graph_init(struct spa_graph *graph, struct spa_graph_state *state) { spa_list_init(&graph->nodes); graph->flags = 0; @@ -180,7 +189,7 @@ static inline void spa_graph_init(struct spa_graph *graph, struct spa_graph_stat spa_debug("graph %p init state %p", graph, state); } -static inline void +SPA_API_GRAPH void spa_graph_link_add(struct spa_graph_node *out, struct spa_graph_state *state, struct spa_graph_link *link) @@ -191,14 +200,14 @@ spa_graph_link_add(struct spa_graph_node *out, spa_list_append(&out->links, &link->link); } -static inline void spa_graph_link_remove(struct spa_graph_link *link) +SPA_API_GRAPH void spa_graph_link_remove(struct spa_graph_link *link) { link->state->required--; spa_debug("link %p state %p remove %d", link, link->state, link->state->required); spa_list_remove(&link->link); } -static inline void +SPA_API_GRAPH void spa_graph_node_init(struct spa_graph_node *node, struct spa_graph_state *state) { spa_list_init(&node->ports[SPA_DIRECTION_INPUT]); @@ -215,7 +224,7 @@ spa_graph_node_init(struct spa_graph_node *node, struct spa_graph_state *state) } -static inline int spa_graph_node_impl_sub_process(void *data SPA_UNUSED, struct spa_graph_node *node) +SPA_API_GRAPH int spa_graph_node_impl_sub_process(void *data SPA_UNUSED, struct spa_graph_node *node) { struct spa_graph *graph = node->subgraph; spa_debug("node %p: sub process %p", node, graph); @@ -227,7 +236,7 @@ static const struct spa_graph_node_callbacks spa_graph_node_sub_impl_default = { .process = spa_graph_node_impl_sub_process, }; -static inline void spa_graph_node_set_subgraph(struct spa_graph_node *node, +SPA_API_GRAPH void spa_graph_node_set_subgraph(struct spa_graph_node *node, struct spa_graph *subgraph) { node->subgraph = subgraph; @@ -235,7 +244,7 @@ static inline void spa_graph_node_set_subgraph(struct spa_graph_node *node, spa_debug("node %p set subgraph %p", node, subgraph); } -static inline void +SPA_API_GRAPH void spa_graph_node_set_callbacks(struct spa_graph_node *node, const struct spa_graph_node_callbacks *callbacks, void *data) @@ -243,7 +252,7 @@ spa_graph_node_set_callbacks(struct spa_graph_node *node, node->callbacks = SPA_CALLBACKS_INIT(callbacks, data); } -static inline void +SPA_API_GRAPH void spa_graph_node_add(struct spa_graph *graph, struct spa_graph_node *node) { @@ -255,7 +264,7 @@ spa_graph_node_add(struct spa_graph *graph, spa_graph_link_add(node, graph->state, &node->graph_link); } -static inline void spa_graph_node_remove(struct spa_graph_node *node) +SPA_API_GRAPH void spa_graph_node_remove(struct spa_graph_node *node) { spa_debug("node %p remove from graph %p, state %p required %d", node, node->graph, node->state, node->state->required); @@ -265,7 +274,7 @@ static inline void spa_graph_node_remove(struct spa_graph_node *node) } -static inline void +SPA_API_GRAPH void spa_graph_port_init(struct spa_graph_port *port, enum spa_direction direction, uint32_t port_id, @@ -277,7 +286,7 @@ spa_graph_port_init(struct spa_graph_port *port, port->flags = flags; } -static inline void +SPA_API_GRAPH void spa_graph_port_add(struct spa_graph_node *node, struct spa_graph_port *port) { @@ -286,13 +295,13 @@ spa_graph_port_add(struct spa_graph_node *node, spa_list_append(&node->ports[port->direction], &port->link); } -static inline void spa_graph_port_remove(struct spa_graph_port *port) +SPA_API_GRAPH void spa_graph_port_remove(struct spa_graph_port *port) { spa_debug("port %p remove", port); spa_list_remove(&port->link); } -static inline void +SPA_API_GRAPH void spa_graph_port_link(struct spa_graph_port *out, struct spa_graph_port *in) { spa_debug("port %p link to %p %p %p", out, in, in->node, in->node->state); @@ -300,7 +309,7 @@ spa_graph_port_link(struct spa_graph_port *out, struct spa_graph_port *in) in->peer = out; } -static inline void +SPA_API_GRAPH void spa_graph_port_unlink(struct spa_graph_port *port) { spa_debug("port %p unlink from %p", port, port->peer); @@ -310,7 +319,7 @@ spa_graph_port_unlink(struct spa_graph_port *port) } } -static inline int spa_graph_node_impl_process(void *data, struct spa_graph_node *node) +SPA_API_GRAPH int spa_graph_node_impl_process(void *data, struct spa_graph_node *node) { struct spa_node *n = (struct spa_node *)data; struct spa_graph_state *state = node->state; @@ -322,7 +331,7 @@ static inline int spa_graph_node_impl_process(void *data, struct spa_graph_node return state->status; } -static inline int spa_graph_node_impl_reuse_buffer(void *data, struct spa_graph_node *node SPA_UNUSED, +SPA_API_GRAPH int spa_graph_node_impl_reuse_buffer(void *data, struct spa_graph_node *node SPA_UNUSED, uint32_t port_id, uint32_t buffer_id) { struct spa_node *n = (struct spa_node *)data; diff --git a/spa/include/spa/interfaces/audio/aec.h b/spa/include/spa/interfaces/audio/aec.h index 67319f1d..48626a23 100644 --- a/spa/include/spa/interfaces/audio/aec.h +++ b/spa/include/spa/interfaces/audio/aec.h @@ -14,6 +14,14 @@ extern "C" { #endif +#ifndef SPA_API_AUDIO_AEC + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_AEC SPA_API_IMPL + #else + #define SPA_API_AUDIO_AEC static inline + #endif +#endif + #define SPA_TYPE_INTERFACE_AUDIO_AEC SPA_TYPE_INFO_INTERFACE_BASE "Audio:AEC" #define SPA_VERSION_AUDIO_AEC 1 @@ -68,26 +76,69 @@ struct spa_audio_aec_methods { struct spa_audio_info_raw *out_info); }; -#define spa_audio_aec_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - struct spa_audio_aec *_o = (o); \ - spa_interface_call_res(&_o->iface, \ - struct spa_audio_aec_methods, _res, \ - method, (version), ##__VA_ARGS__); \ - _res; \ -}) - -#define spa_audio_aec_add_listener(o,...) spa_audio_aec_method(o, add_listener, 0, __VA_ARGS__) -#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) -#define spa_audio_aec_enum_props(o,...) spa_audio_aec_method(o, enum_props, 2, __VA_ARGS__) -#define spa_audio_aec_get_params(o,...) spa_audio_aec_method(o, get_params, 2, __VA_ARGS__) -#define spa_audio_aec_set_params(o,...) spa_audio_aec_method(o, set_params, 2, __VA_ARGS__) -#define spa_audio_aec_init2(o,...) spa_audio_aec_method(o, init2, 3, __VA_ARGS__) +SPA_API_AUDIO_AEC int spa_audio_aec_add_listener(struct spa_audio_aec *object, + struct spa_hook *listener, + const struct spa_audio_aec_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_audio_aec, &object->iface, add_listener, 0, listener, events, data); +} + +SPA_API_AUDIO_AEC int spa_audio_aec_init(struct spa_audio_aec *object, + const struct spa_dict *args, const struct spa_audio_info_raw *info) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_audio_aec, &object->iface, init, 0, args, info); +} +SPA_API_AUDIO_AEC int spa_audio_aec_run(struct spa_audio_aec *object, + const float *rec[], const float *play[], float *out[], uint32_t n_samples) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_audio_aec, &object->iface, run, 0, rec, play, out, n_samples); +} +SPA_API_AUDIO_AEC int spa_audio_aec_set_props(struct spa_audio_aec *object, const struct spa_dict *args) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_audio_aec, &object->iface, set_props, 0, args); +} +SPA_API_AUDIO_AEC int spa_audio_aec_activate(struct spa_audio_aec *object) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_audio_aec, &object->iface, activate, 1); +} +SPA_API_AUDIO_AEC int spa_audio_aec_deactivate(struct spa_audio_aec *object) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_audio_aec, &object->iface, deactivate, 1); +} +SPA_API_AUDIO_AEC int spa_audio_aec_enum_props(struct spa_audio_aec *object, + int index, struct spa_pod_builder* builder) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_audio_aec, &object->iface, enum_props, 2, index, builder); +} +SPA_API_AUDIO_AEC int spa_audio_aec_get_params(struct spa_audio_aec *object, + struct spa_pod_builder* builder) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_audio_aec, &object->iface, get_params, 2, builder); +} +SPA_API_AUDIO_AEC int spa_audio_aec_set_params(struct spa_audio_aec *object, + const struct spa_pod *args) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_audio_aec, &object->iface, set_params, 2, args); +} +SPA_API_AUDIO_AEC int spa_audio_aec_init2(struct spa_audio_aec *object, + const struct spa_dict *args, + struct spa_audio_info_raw *play_info, + struct spa_audio_info_raw *rec_info, + struct spa_audio_info_raw *out_info) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_audio_aec, &object->iface, init2, 3, args, play_info, rec_info, out_info); +} #ifdef __cplusplus } /* extern "C" */ diff --git a/spa/include/spa/monitor/device.h b/spa/include/spa/monitor/device.h index 2201ffdb..73b4a94f 100644 --- a/spa/include/spa/monitor/device.h +++ b/spa/include/spa/monitor/device.h @@ -14,6 +14,14 @@ extern "C" { #include <spa/utils/dict.h> #include <spa/pod/event.h> +#ifndef SPA_API_DEVICE + #ifdef SPA_API_IMPL + #define SPA_API_DEVICE SPA_API_IMPL + #else + #define SPA_API_DEVICE static inline + #endif +#endif + /** * \defgroup spa_device Device * @@ -220,20 +228,34 @@ struct spa_device_methods { const struct spa_pod *param); }; -#define spa_device_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - struct spa_device *_o = (o); \ - spa_interface_call_res(&_o->iface, \ - struct spa_device_methods, _res, \ - method, (version), ##__VA_ARGS__); \ - _res; \ -}) - -#define spa_device_add_listener(d,...) spa_device_method(d, add_listener, 0, __VA_ARGS__) -#define spa_device_sync(d,...) spa_device_method(d, sync, 0, __VA_ARGS__) -#define spa_device_enum_params(d,...) spa_device_method(d, enum_params, 0, __VA_ARGS__) -#define spa_device_set_param(d,...) spa_device_method(d, set_param, 0, __VA_ARGS__) +SPA_API_DEVICE int spa_device_add_listener(struct spa_device *object, + struct spa_hook *listener, + const struct spa_device_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, spa_device, &object->iface, add_listener, 0, + listener, events, data); + +} +SPA_API_DEVICE int spa_device_sync(struct spa_device *object, int seq) +{ + return spa_api_method_r(int, -ENOTSUP, spa_device, &object->iface, sync, 0, + seq); +} +SPA_API_DEVICE int spa_device_enum_params(struct spa_device *object, int seq, + uint32_t id, uint32_t index, uint32_t max, + const struct spa_pod *filter) +{ + return spa_api_method_r(int, -ENOTSUP, spa_device, &object->iface, enum_params, 0, + seq, id, index, max, filter); +} +SPA_API_DEVICE int spa_device_set_param(struct spa_device *object, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + return spa_api_method_r(int, -ENOTSUP, spa_device, &object->iface, set_param, 0, + id, flags, param); +} #define SPA_KEY_DEVICE_ENUM_API "device.enum.api" /**< the api used to discover this * device */ @@ -271,7 +293,7 @@ struct spa_device_methods { * "webcam", "microphone", "headset", * "headphone", "hands-free", "car", "hifi", * "computer", "portable" */ -#define SPA_KEY_DEVICE_PROFILE "device.profile " /**< profile for the device */ +#define SPA_KEY_DEVICE_PROFILE "device.profile" /**< profile for the device */ #define SPA_KEY_DEVICE_PROFILE_SET "device.profile-set" /**< profile set for the device */ #define SPA_KEY_DEVICE_STRING "device.string" /**< device string in the underlying * layer's format. E.g. "surround51:0" */ diff --git a/spa/include/spa/monitor/utils.h b/spa/include/spa/monitor/utils.h index 93f8f41b..4337eceb 100644 --- a/spa/include/spa/monitor/utils.h +++ b/spa/include/spa/monitor/utils.h @@ -12,6 +12,14 @@ extern "C" { #include <spa/pod/builder.h> #include <spa/monitor/device.h> +#ifndef SPA_API_DEVICE_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_DEVICE_UTILS SPA_API_IMPL + #else + #define SPA_API_DEVICE_UTILS static inline + #endif +#endif + /** * \addtogroup spa_device * \{ @@ -22,7 +30,7 @@ struct spa_result_device_params_data { struct spa_result_device_params data; }; -static inline void spa_result_func_device_params(void *data, int seq SPA_UNUSED, int res SPA_UNUSED, +SPA_API_DEVICE_UTILS void spa_result_func_device_params(void *data, int seq SPA_UNUSED, int res SPA_UNUSED, uint32_t type SPA_UNUSED, const void *result) { struct spa_result_device_params_data *d = @@ -36,7 +44,7 @@ static inline void spa_result_func_device_params(void *data, int seq SPA_UNUSED, d->data.param = spa_pod_builder_deref(d->builder, offset); } -static inline int spa_device_enum_params_sync(struct spa_device *device, +SPA_API_DEVICE_UTILS int spa_device_enum_params_sync(struct spa_device *device, uint32_t id, uint32_t *index, const struct spa_pod *filter, struct spa_pod **param, diff --git a/spa/include/spa/node/io.h b/spa/include/spa/node/io.h index 409fdde1..c1c725eb 100644 --- a/spa/include/spa/node/io.h +++ b/spa/include/spa/node/io.h @@ -128,6 +128,9 @@ struct spa_io_clock { #define SPA_IO_CLOCK_FLAG_FREEWHEEL (1u<<0) /* graph is freewheeling */ #define SPA_IO_CLOCK_FLAG_XRUN_RECOVER (1u<<1) /* recovering from xrun */ #define SPA_IO_CLOCK_FLAG_LAZY (1u<<2) /* lazy scheduling */ +#define SPA_IO_CLOCK_FLAG_NO_RATE (1u<<3) /* the rate of the clock is only approximately. + * it is recommended to use the nsec as a clock source. + * The rate_diff contains the measured inaccuracy. */ uint32_t flags; /**< Clock flags */ uint32_t id; /**< Unique clock id, set by host application */ char name[64]; /**< Clock name prefixed with API, set by node when it receives @@ -302,14 +305,50 @@ struct spa_io_position { struct spa_io_segment segments[SPA_IO_POSITION_MAX_SEGMENTS]; /**< segments */ }; -/** rate matching */ +/** + * Rate matching. + * + * It is usually set on the nodes that process resampled data, by + * the component (audioadapter) that handles resampling between graph + * and node rates. The \a flags and \a rate fields may be modified by the node. + * + * The node can request a correction to the resampling rate in its process(), by setting + * \ref SPA_IO_RATE_MATCH_ACTIVE on \a flags, and setting \a rate to the desired rate + * correction. Usually the rate is obtained from DLL or other adaptive mechanism that + * e.g. drives the node buffer fill level toward a specific value. + * + * When resampling to (graph->node) direction, the number of samples produced + * by the resampler varies on each cycle, as the rates are not commensurate. + * + * When resampling to (node->graph) direction, the number of samples consumed by the + * resampler varies. Node output ports in process() should produce \a size number of + * samples to match what the resampler needs to produce one graph quantum of output + * samples. + * + * Resampling filters introduce processing delay, given by \a delay and \a delay_frac, in + * samples at node rate. The delay varies on each cycle e.g. when resampling between + * noncommensurate rates. + * + * The first sample output (graph->node) or consumed (node->graph) by the resampler is + * offset by \a delay + \a delay_frac / 1e9 node samples relative to the nominal graph + * cycle start position: + * + * \code{.unparsed} + * first_resampled_sample_nsec = + * first_original_sample_nsec + * - (rate_match->delay * SPA_NSEC_PER_SEC + rate_match->delay_frac) / node_rate + * \endcode + */ struct spa_io_rate_match { - uint32_t delay; /**< extra delay in samples for resampler */ + uint32_t delay; /**< resampling delay, in samples at + * node rate */ uint32_t size; /**< requested input size for resampler */ - double rate; /**< rate for resampler */ + double rate; /**< rate for resampler (set by node) */ #define SPA_IO_RATE_MATCH_FLAG_ACTIVE (1 << 0) - uint32_t flags; /**< extra flags */ - uint32_t padding[7]; + uint32_t flags; /**< extra flags (set by node) */ + int32_t delay_frac; /**< resampling delay fractional part, + * in units of nanosamples (1/10^9 sample) at node rate */ + uint32_t padding[6]; }; /** async buffers */ diff --git a/spa/include/spa/node/node.h b/spa/include/spa/node/node.h index 44e45da8..ae9f6354 100644 --- a/spa/include/spa/node/node.h +++ b/spa/include/spa/node/node.h @@ -27,6 +27,14 @@ extern "C" { #include <spa/node/event.h> #include <spa/node/command.h> +#ifndef SPA_API_NODE + #ifdef SPA_API_IMPL + #define SPA_API_NODE SPA_API_IMPL + #else + #define SPA_API_NODE static inline + #endif +#endif + #define SPA_TYPE_INTERFACE_Node SPA_TYPE_INFO_INTERFACE_BASE "Node" @@ -633,44 +641,120 @@ struct spa_node_methods { int (*process) (void *object); }; -#define spa_node_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - struct spa_node *_n = o; \ - spa_interface_call_res(&_n->iface, \ - struct spa_node_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define spa_node_method_fast(o,method,version,...) \ -({ \ - int _res; \ - struct spa_node *_n = o; \ - spa_interface_call_fast_res(&_n->iface, \ - struct spa_node_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define spa_node_add_listener(n,...) spa_node_method(n, add_listener, 0, __VA_ARGS__) -#define spa_node_set_callbacks(n,...) spa_node_method(n, set_callbacks, 0, __VA_ARGS__) -#define spa_node_sync(n,...) spa_node_method(n, sync, 0, __VA_ARGS__) -#define spa_node_enum_params(n,...) spa_node_method(n, enum_params, 0, __VA_ARGS__) -#define spa_node_set_param(n,...) spa_node_method(n, set_param, 0, __VA_ARGS__) -#define spa_node_set_io(n,...) spa_node_method(n, set_io, 0, __VA_ARGS__) -#define spa_node_send_command(n,...) spa_node_method(n, send_command, 0, __VA_ARGS__) -#define spa_node_add_port(n,...) spa_node_method(n, add_port, 0, __VA_ARGS__) -#define spa_node_remove_port(n,...) spa_node_method(n, remove_port, 0, __VA_ARGS__) -#define spa_node_port_enum_params(n,...) spa_node_method(n, port_enum_params, 0, __VA_ARGS__) -#define spa_node_port_set_param(n,...) spa_node_method(n, port_set_param, 0, __VA_ARGS__) -#define spa_node_port_use_buffers(n,...) spa_node_method(n, port_use_buffers, 0, __VA_ARGS__) -#define spa_node_port_set_io(n,...) spa_node_method(n, port_set_io, 0, __VA_ARGS__) - -#define spa_node_port_reuse_buffer(n,...) spa_node_method(n, port_reuse_buffer, 0, __VA_ARGS__) -#define spa_node_port_reuse_buffer_fast(n,...) spa_node_method_fast(n, port_reuse_buffer, 0, __VA_ARGS__) -#define spa_node_process(n) spa_node_method(n, process, 0) -#define spa_node_process_fast(n) spa_node_method_fast(n, process, 0) + +SPA_API_NODE int spa_node_add_listener(struct spa_node *object, + struct spa_hook *listener, + const struct spa_node_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, add_listener, 0, + listener, events, data); +} +SPA_API_NODE int spa_node_set_callbacks(struct spa_node *object, + const struct spa_node_callbacks *callbacks, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, set_callbacks, 0, + callbacks, data); +} +SPA_API_NODE int spa_node_sync(struct spa_node *object, int seq) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, sync, 0, + seq); +} +SPA_API_NODE int spa_node_enum_params(struct spa_node *object, int seq, + uint32_t id, uint32_t start, uint32_t max, + const struct spa_pod *filter) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, enum_params, 0, + seq, id, start, max, filter); +} +SPA_API_NODE int spa_node_set_param(struct spa_node *object, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, set_param, 0, + id, flags, param); +} +SPA_API_NODE int spa_node_set_io(struct spa_node *object, + uint32_t id, void *data, size_t size) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, set_io, 0, + id, data, size); +} +SPA_API_NODE int spa_node_send_command(struct spa_node *object, + const struct spa_command *command) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, send_command, 0, + command); +} +SPA_API_NODE int spa_node_add_port(struct spa_node *object, + enum spa_direction direction, uint32_t port_id, + const struct spa_dict *props) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, add_port, 0, + direction, port_id, props); +} +SPA_API_NODE int spa_node_remove_port(struct spa_node *object, + enum spa_direction direction, uint32_t port_id) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, remove_port, 0, + direction, port_id); +} +SPA_API_NODE int spa_node_port_enum_params(struct spa_node *object, int seq, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t start, uint32_t max, + const struct spa_pod *filter) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, port_enum_params, 0, + seq, direction, port_id, id, start, max, filter); +} +SPA_API_NODE int spa_node_port_set_param(struct spa_node *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, port_set_param, 0, + direction, port_id, id, flags, param); +} +SPA_API_NODE int spa_node_port_use_buffers(struct spa_node *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + struct spa_buffer **buffers, + uint32_t n_buffers) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, port_use_buffers, 0, + direction, port_id, flags, buffers, n_buffers); +} +SPA_API_NODE int spa_node_port_set_io(struct spa_node *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t id, void *data, size_t size) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, port_set_io, 0, + direction, port_id, id, data, size); +} + +SPA_API_NODE int spa_node_port_reuse_buffer(struct spa_node *object, uint32_t port_id, uint32_t buffer_id) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, port_reuse_buffer, 0, + port_id, buffer_id); +} +SPA_API_NODE int spa_node_port_reuse_buffer_fast(struct spa_node *object, uint32_t port_id, uint32_t buffer_id) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_node, &object->iface, port_reuse_buffer, 0, + port_id, buffer_id); +} +SPA_API_NODE int spa_node_process(struct spa_node *object) +{ + return spa_api_method_r(int, -ENOTSUP, spa_node, &object->iface, process, 0); +} +SPA_API_NODE int spa_node_process_fast(struct spa_node *object) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_node, &object->iface, process, 0); +} /** * \} diff --git a/spa/include/spa/node/utils.h b/spa/include/spa/node/utils.h index 01d249ab..b7724e92 100644 --- a/spa/include/spa/node/utils.h +++ b/spa/include/spa/node/utils.h @@ -18,12 +18,20 @@ extern "C" { #include <spa/node/node.h> +#ifndef SPA_API_NODE_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_NODE_UTILS SPA_API_IMPL + #else + #define SPA_API_NODE_UTILS static inline + #endif +#endif + struct spa_result_node_params_data { struct spa_pod_builder *builder; struct spa_result_node_params data; }; -static inline void spa_result_func_node_params(void *data, +SPA_API_NODE_UTILS void spa_result_func_node_params(void *data, int seq SPA_UNUSED, int res SPA_UNUSED, uint32_t type SPA_UNUSED, const void *result) { struct spa_result_node_params_data *d = @@ -37,7 +45,7 @@ static inline void spa_result_func_node_params(void *data, d->data.param = spa_pod_builder_deref(d->builder, offset); } -static inline int spa_node_enum_params_sync(struct spa_node *node, +SPA_API_NODE_UTILS int spa_node_enum_params_sync(struct spa_node *node, uint32_t id, uint32_t *index, const struct spa_pod *filter, struct spa_pod **param, @@ -70,7 +78,7 @@ static inline int spa_node_enum_params_sync(struct spa_node *node, return res; } -static inline int spa_node_port_enum_params_sync(struct spa_node *node, +SPA_API_NODE_UTILS int spa_node_port_enum_params_sync(struct spa_node *node, enum spa_direction direction, uint32_t port_id, uint32_t id, uint32_t *index, const struct spa_pod *filter, diff --git a/spa/include/spa/param/audio/aac-utils.h b/spa/include/spa/param/audio/aac-utils.h index 07d436ac..01f226bb 100644 --- a/spa/include/spa/param/audio/aac-utils.h +++ b/spa/include/spa/param/audio/aac-utils.h @@ -19,7 +19,15 @@ extern "C" { #include <spa/param/audio/format.h> #include <spa/param/format-utils.h> -static inline int +#ifndef SPA_API_AUDIO_AAC_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_AAC_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_AAC_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_AAC_UTILS int spa_format_audio_aac_parse(const struct spa_pod *format, struct spa_audio_info_aac *info) { int res; @@ -33,7 +41,7 @@ spa_format_audio_aac_parse(const struct spa_pod *format, struct spa_audio_info_a return res; } -static inline struct spa_pod * +SPA_API_AUDIO_AAC_UTILS struct spa_pod * spa_format_audio_aac_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_aac *info) { diff --git a/spa/include/spa/param/audio/alac-utils.h b/spa/include/spa/param/audio/alac-utils.h index 262c2af3..898a84e5 100644 --- a/spa/include/spa/param/audio/alac-utils.h +++ b/spa/include/spa/param/audio/alac-utils.h @@ -19,7 +19,16 @@ extern "C" { #include <spa/param/audio/format.h> #include <spa/param/format-utils.h> -static inline int +#ifndef SPA_API_AUDIO_ALAC_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_ALAC_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_ALAC_UTILS static inline + #endif +#endif + + +SPA_API_AUDIO_ALAC_UTILS int spa_format_audio_alac_parse(const struct spa_pod *format, struct spa_audio_info_alac *info) { int res; @@ -30,7 +39,7 @@ spa_format_audio_alac_parse(const struct spa_pod *format, struct spa_audio_info_ return res; } -static inline struct spa_pod * +SPA_API_AUDIO_ALAC_UTILS struct spa_pod * spa_format_audio_alac_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_alac *info) { diff --git a/spa/include/spa/param/audio/amr-utils.h b/spa/include/spa/param/audio/amr-utils.h index 3ecbda8f..cfe6aa5d 100644 --- a/spa/include/spa/param/audio/amr-utils.h +++ b/spa/include/spa/param/audio/amr-utils.h @@ -19,7 +19,16 @@ extern "C" { #include <spa/param/audio/format.h> #include <spa/param/format-utils.h> -static inline int +#ifndef SPA_API_AUDIO_AMR_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_AMR_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_AMR_UTILS static inline + #endif +#endif + + +SPA_API_AUDIO_AMR_UTILS int spa_format_audio_amr_parse(const struct spa_pod *format, struct spa_audio_info_amr *info) { int res; @@ -31,7 +40,7 @@ spa_format_audio_amr_parse(const struct spa_pod *format, struct spa_audio_info_a return res; } -static inline struct spa_pod * +SPA_API_AUDIO_AMR_UTILS struct spa_pod * spa_format_audio_amr_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_amr *info) { diff --git a/spa/include/spa/param/audio/ape-utils.h b/spa/include/spa/param/audio/ape-utils.h index 76ae6d89..d05c596c 100644 --- a/spa/include/spa/param/audio/ape-utils.h +++ b/spa/include/spa/param/audio/ape-utils.h @@ -19,7 +19,15 @@ extern "C" { #include <spa/param/audio/format.h> #include <spa/param/format-utils.h> -static inline int +#ifndef SPA_API_AUDIO_APE_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_APE_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_APE_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_APE_UTILS int spa_format_audio_ape_parse(const struct spa_pod *format, struct spa_audio_info_ape *info) { int res; @@ -30,7 +38,7 @@ spa_format_audio_ape_parse(const struct spa_pod *format, struct spa_audio_info_a return res; } -static inline struct spa_pod * +SPA_API_AUDIO_APE_UTILS struct spa_pod * spa_format_audio_ape_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_ape *info) { diff --git a/spa/include/spa/param/audio/dsd-utils.h b/spa/include/spa/param/audio/dsd-utils.h index 5f4585aa..3f7065b2 100644 --- a/spa/include/spa/param/audio/dsd-utils.h +++ b/spa/include/spa/param/audio/dsd-utils.h @@ -19,7 +19,15 @@ extern "C" { #include <spa/param/format-utils.h> #include <spa/param/audio/format.h> -static inline int +#ifndef SPA_API_AUDIO_DSD_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_DSD_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_DSD_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_DSD_UTILS int spa_format_audio_dsd_parse(const struct spa_pod *format, struct spa_audio_info_dsd *info) { struct spa_pod *position = NULL; @@ -39,7 +47,7 @@ spa_format_audio_dsd_parse(const struct spa_pod *format, struct spa_audio_info_d return res; } -static inline struct spa_pod * +SPA_API_AUDIO_DSD_UTILS struct spa_pod * spa_format_audio_dsd_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_dsd *info) { diff --git a/spa/include/spa/param/audio/dsp-utils.h b/spa/include/spa/param/audio/dsp-utils.h index 621c370c..af107f1e 100644 --- a/spa/include/spa/param/audio/dsp-utils.h +++ b/spa/include/spa/param/audio/dsp-utils.h @@ -19,7 +19,15 @@ extern "C" { #include <spa/param/audio/format.h> #include <spa/param/format-utils.h> -static inline int +#ifndef SPA_API_AUDIO_DSP_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_DSP_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_DSP_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_DSP_UTILS int spa_format_audio_dsp_parse(const struct spa_pod *format, struct spa_audio_info_dsp *info) { int res; @@ -29,7 +37,7 @@ spa_format_audio_dsp_parse(const struct spa_pod *format, struct spa_audio_info_d return res; } -static inline struct spa_pod * +SPA_API_AUDIO_DSP_UTILS struct spa_pod * spa_format_audio_dsp_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_dsp *info) { diff --git a/spa/include/spa/param/audio/flac-utils.h b/spa/include/spa/param/audio/flac-utils.h index 007436c9..bc3d8afc 100644 --- a/spa/include/spa/param/audio/flac-utils.h +++ b/spa/include/spa/param/audio/flac-utils.h @@ -19,7 +19,15 @@ extern "C" { #include <spa/param/audio/format.h> #include <spa/param/format-utils.h> -static inline int +#ifndef SPA_API_AUDIO_FLAC_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_FLAC_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_FLAC_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_FLAC_UTILS int spa_format_audio_flac_parse(const struct spa_pod *format, struct spa_audio_info_flac *info) { int res; @@ -30,7 +38,7 @@ spa_format_audio_flac_parse(const struct spa_pod *format, struct spa_audio_info_ return res; } -static inline struct spa_pod * +SPA_API_AUDIO_FLAC_UTILS struct spa_pod * spa_format_audio_flac_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_flac *info) { diff --git a/spa/include/spa/param/audio/format-utils.h b/spa/include/spa/param/audio/format-utils.h index 1fad6292..4ff40faa 100644 --- a/spa/include/spa/param/audio/format-utils.h +++ b/spa/include/spa/param/audio/format-utils.h @@ -34,7 +34,15 @@ extern "C" { * \{ */ -static inline int +#ifndef SPA_API_AUDIO_FORMAT_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_FORMAT_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_FORMAT_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_FORMAT_UTILS int spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info) { int res; @@ -76,7 +84,7 @@ spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info return -ENOTSUP; } -static inline struct spa_pod * +SPA_API_AUDIO_FORMAT_UTILS struct spa_pod * spa_format_audio_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info *info) { diff --git a/spa/include/spa/param/audio/iec958-types.h b/spa/include/spa/param/audio/iec958-types.h index 6388a24f..adcffdc9 100644 --- a/spa/include/spa/param/audio/iec958-types.h +++ b/spa/include/spa/param/audio/iec958-types.h @@ -17,6 +17,14 @@ extern "C" { * \{ */ +#ifndef SPA_API_AUDIO_IEC958_TYPES + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_IEC958_TYPES SPA_API_IMPL + #else + #define SPA_API_AUDIO_IEC958_TYPES static inline + #endif +#endif + #define SPA_TYPE_INFO_AudioIEC958Codec SPA_TYPE_INFO_ENUM_BASE "AudioIEC958Codec" #define SPA_TYPE_INFO_AUDIO_IEC958_CODEC_BASE SPA_TYPE_INFO_AudioIEC958Codec ":" @@ -33,6 +41,14 @@ static const struct spa_type_info spa_type_audio_iec958_codec[] = { { 0, 0, NULL, NULL }, }; +SPA_API_AUDIO_IEC958_TYPES uint32_t spa_type_audio_iec958_codec_from_short_name(const char *name) +{ + return spa_type_from_short_name(name, spa_type_audio_iec958_codec, SPA_AUDIO_IEC958_CODEC_UNKNOWN); +} +SPA_API_AUDIO_IEC958_TYPES const char * spa_type_audio_iec958_codec_to_short_name(uint32_t type) +{ + return spa_type_to_short_name(type, spa_type_audio_iec958_codec, "UNKNOWN"); +} /** * \} */ diff --git a/spa/include/spa/param/audio/iec958-utils.h b/spa/include/spa/param/audio/iec958-utils.h index 56bade23..1c4ec105 100644 --- a/spa/include/spa/param/audio/iec958-utils.h +++ b/spa/include/spa/param/audio/iec958-utils.h @@ -19,7 +19,15 @@ extern "C" { #include <spa/param/audio/format.h> #include <spa/param/format-utils.h> -static inline int +#ifndef SPA_API_AUDIO_IEC958_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_IEC958_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_IEC958_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_IEC958_UTILS int spa_format_audio_iec958_parse(const struct spa_pod *format, struct spa_audio_info_iec958 *info) { int res; @@ -30,7 +38,7 @@ spa_format_audio_iec958_parse(const struct spa_pod *format, struct spa_audio_inf return res; } -static inline struct spa_pod * +SPA_API_AUDIO_IEC958_UTILS struct spa_pod * spa_format_audio_iec958_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_iec958 *info) { diff --git a/spa/include/spa/param/audio/layout.h b/spa/include/spa/param/audio/layout.h index 8b05ba03..545ceae3 100644 --- a/spa/include/spa/param/audio/layout.h +++ b/spa/include/spa/param/audio/layout.h @@ -9,9 +9,7 @@ extern "C" { #endif -#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) -#include <endian.h> -#endif +#include <spa/utils/endian.h> /** * \addtogroup spa_param diff --git a/spa/include/spa/param/audio/mp3-utils.h b/spa/include/spa/param/audio/mp3-utils.h index 4c1ace26..481000e2 100644 --- a/spa/include/spa/param/audio/mp3-utils.h +++ b/spa/include/spa/param/audio/mp3-utils.h @@ -19,7 +19,15 @@ extern "C" { #include <spa/param/audio/format.h> #include <spa/param/format-utils.h> -static inline int +#ifndef SPA_API_AUDIO_MP3_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_MP3_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_MP3_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_MP3_UTILS int spa_format_audio_mp3_parse(const struct spa_pod *format, struct spa_audio_info_mp3 *info) { int res; @@ -30,7 +38,7 @@ spa_format_audio_mp3_parse(const struct spa_pod *format, struct spa_audio_info_m return res; } -static inline struct spa_pod * +SPA_API_AUDIO_MP3_UTILS struct spa_pod * spa_format_audio_mp3_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_mp3 *info) { diff --git a/spa/include/spa/param/audio/ra-utils.h b/spa/include/spa/param/audio/ra-utils.h index 0f267fb1..79e96514 100644 --- a/spa/include/spa/param/audio/ra-utils.h +++ b/spa/include/spa/param/audio/ra-utils.h @@ -19,7 +19,15 @@ extern "C" { #include <spa/param/audio/format.h> #include <spa/param/format-utils.h> -static inline int +#ifndef SPA_API_AUDIO_RA_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_RA_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_RA_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_RA_UTILS int spa_format_audio_ra_parse(const struct spa_pod *format, struct spa_audio_info_ra *info) { int res; @@ -30,7 +38,7 @@ spa_format_audio_ra_parse(const struct spa_pod *format, struct spa_audio_info_ra return res; } -static inline struct spa_pod * +SPA_API_AUDIO_RA_UTILS struct spa_pod * spa_format_audio_ra_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_ra *info) { diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h new file mode 100644 index 00000000..07f0e0c4 --- /dev/null +++ b/spa/include/spa/param/audio/raw-json.h @@ -0,0 +1,105 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_AUDIO_RAW_JSON_H +#define SPA_AUDIO_RAW_JSON_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \addtogroup spa_param + * \{ + */ + +#include <spa/utils/dict.h> +#include <spa/utils/json.h> +#include <spa/param/audio/raw.h> +#include <spa/param/audio/raw-types.h> + +#ifndef SPA_API_AUDIO_RAW_JSON + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_RAW_JSON SPA_API_IMPL + #else + #define SPA_API_AUDIO_RAW_JSON static inline + #endif +#endif + +SPA_API_AUDIO_RAW_JSON int +spa_audio_parse_position(const char *str, size_t len, + uint32_t *position, uint32_t *n_channels) +{ + struct spa_json iter; + char v[256]; + uint32_t channels = 0; + + if (spa_json_begin_array_relax(&iter, str, len) <= 0) + return 0; + + while (spa_json_get_string(&iter, v, sizeof(v)) > 0 && + channels < SPA_AUDIO_MAX_CHANNELS) { + position[channels++] = spa_type_audio_channel_from_short_name(v); + } + *n_channels = channels; + return channels; +} + +SPA_API_AUDIO_RAW_JSON int +spa_audio_info_raw_update(struct spa_audio_info_raw *info, const char *key, const char *val, bool force) +{ + uint32_t v; + if (spa_streq(key, SPA_KEY_AUDIO_FORMAT)) { + if (force || info->format == 0) + info->format = (enum spa_audio_format)spa_type_audio_format_from_short_name(val); + } else if (spa_streq(key, SPA_KEY_AUDIO_RATE)) { + if (spa_atou32(val, &v, 0) && (force || info->rate == 0)) + info->rate = v; + } else if (spa_streq(key, SPA_KEY_AUDIO_CHANNELS)) { + if (spa_atou32(val, &v, 0) && (force || info->channels == 0)) + info->channels = SPA_MIN(v, SPA_AUDIO_MAX_CHANNELS); + } else if (spa_streq(key, SPA_KEY_AUDIO_POSITION)) { + if (force || info->channels == 0) { + if (spa_audio_parse_position(val, strlen(val), info->position, &info->channels) > 0) + SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); + } + } + return 0; +} + +SPA_API_AUDIO_RAW_JSON int SPA_SENTINEL +spa_audio_info_raw_init_dict_keys(struct spa_audio_info_raw *info, + const struct spa_dict *defaults, + const struct spa_dict *dict, ...) +{ + spa_zero(*info); + SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED); + if (dict) { + const char *val, *key; + va_list args; + va_start(args, dict); + while ((key = va_arg(args, const char *))) { + if ((val = spa_dict_lookup(dict, key)) == NULL) + continue; + spa_audio_info_raw_update(info, key, val, true); + } + va_end(args); + } + if (defaults) { + const struct spa_dict_item *it; + spa_dict_for_each(it, defaults) + spa_audio_info_raw_update(info, it->key, it->value, false); + } + return 0; +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_AUDIO_RAW_JSON_H */ diff --git a/spa/include/spa/param/audio/raw-types.h b/spa/include/spa/param/audio/raw-types.h index 5dd65d77..9aa9591c 100644 --- a/spa/include/spa/param/audio/raw-types.h +++ b/spa/include/spa/param/audio/raw-types.h @@ -15,8 +15,17 @@ extern "C" { */ #include <spa/utils/type.h> +#include <spa/utils/string.h> #include <spa/param/audio/raw.h> +#ifndef SPA_API_AUDIO_RAW_TYPES + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_RAW_TYPES SPA_API_IMPL + #else + #define SPA_API_AUDIO_RAW_TYPES static inline + #endif +#endif + #define SPA_TYPE_INFO_AudioFormat SPA_TYPE_INFO_ENUM_BASE "AudioFormat" #define SPA_TYPE_INFO_AUDIO_FORMAT_BASE SPA_TYPE_INFO_AudioFormat ":" @@ -128,6 +137,15 @@ static const struct spa_type_info spa_type_audio_format[] = { { 0, 0, NULL, NULL }, }; +SPA_API_AUDIO_RAW_TYPES uint32_t spa_type_audio_format_from_short_name(const char *name) +{ + return spa_type_from_short_name(name, spa_type_audio_format, SPA_AUDIO_FORMAT_UNKNOWN); +} +SPA_API_AUDIO_RAW_TYPES const char * spa_type_audio_format_to_short_name(uint32_t type) +{ + return spa_type_to_short_name(type, spa_type_audio_format, "UNKNOWN"); +} + #define SPA_TYPE_INFO_AudioFlags SPA_TYPE_INFO_FLAGS_BASE "AudioFlags" #define SPA_TYPE_INFO_AUDIO_FLAGS_BASE SPA_TYPE_INFO_AudioFlags ":" @@ -247,6 +265,16 @@ static const struct spa_type_info spa_type_audio_channel[] = { { 0, 0, NULL, NULL }, }; +SPA_API_AUDIO_RAW_TYPES uint32_t spa_type_audio_channel_from_short_name(const char *name) +{ + return spa_type_from_short_name(name, spa_type_audio_channel, SPA_AUDIO_CHANNEL_UNKNOWN); +} +SPA_API_AUDIO_RAW_TYPES const char * spa_type_audio_channel_to_short_name(uint32_t type) +{ + return spa_type_to_short_name(type, spa_type_audio_channel, "UNK"); +} + + /** * \} */ diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h index 8790c452..178e3dd1 100644 --- a/spa/include/spa/param/audio/raw-utils.h +++ b/spa/include/spa/param/audio/raw-utils.h @@ -19,7 +19,15 @@ extern "C" { #include <spa/param/audio/format.h> #include <spa/param/format-utils.h> -static inline int +#ifndef SPA_API_AUDIO_RAW_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_RAW_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_RAW_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_RAW_UTILS int spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_raw *info) { struct spa_pod *position = NULL; @@ -38,7 +46,7 @@ spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_r return res; } -static inline struct spa_pod * +SPA_API_AUDIO_RAW_UTILS struct spa_pod * spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_raw *info) { diff --git a/spa/include/spa/param/audio/raw.h b/spa/include/spa/param/audio/raw.h index a357d559..8bed3f8a 100644 --- a/spa/include/spa/param/audio/raw.h +++ b/spa/include/spa/param/audio/raw.h @@ -11,9 +11,7 @@ extern "C" { #include <stdint.h> -#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) -#include <endian.h> -#endif +#include <spa/utils/endian.h> /** * \addtogroup spa_param diff --git a/spa/include/spa/param/audio/vorbis-utils.h b/spa/include/spa/param/audio/vorbis-utils.h index 9f3fee1d..bc901e61 100644 --- a/spa/include/spa/param/audio/vorbis-utils.h +++ b/spa/include/spa/param/audio/vorbis-utils.h @@ -19,7 +19,15 @@ extern "C" { #include <spa/param/audio/format.h> #include <spa/param/format-utils.h> -static inline int +#ifndef SPA_API_AUDIO_VORBIS_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_VORBIS_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_VORBIS_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_VORBIS_UTILS int spa_format_audio_vorbis_parse(const struct spa_pod *format, struct spa_audio_info_vorbis *info) { int res; @@ -30,7 +38,7 @@ spa_format_audio_vorbis_parse(const struct spa_pod *format, struct spa_audio_inf return res; } -static inline struct spa_pod * +SPA_API_AUDIO_VORBIS_UTILS struct spa_pod * spa_format_audio_vorbis_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_vorbis *info) { diff --git a/spa/include/spa/param/audio/wma-utils.h b/spa/include/spa/param/audio/wma-utils.h index 9517e122..ca15f7d0 100644 --- a/spa/include/spa/param/audio/wma-utils.h +++ b/spa/include/spa/param/audio/wma-utils.h @@ -19,7 +19,15 @@ extern "C" { #include <spa/param/audio/format.h> #include <spa/param/format-utils.h> -static inline int +#ifndef SPA_API_AUDIO_WMA_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_AUDIO_WMA_UTILS SPA_API_IMPL + #else + #define SPA_API_AUDIO_WMA_UTILS static inline + #endif +#endif + +SPA_API_AUDIO_WMA_UTILS int spa_format_audio_wma_parse(const struct spa_pod *format, struct spa_audio_info_wma *info) { int res; @@ -33,7 +41,7 @@ spa_format_audio_wma_parse(const struct spa_pod *format, struct spa_audio_info_w return res; } -static inline struct spa_pod * +SPA_API_AUDIO_WMA_UTILS struct spa_pod * spa_format_audio_wma_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_audio_info_wma *info) { diff --git a/spa/include/spa/param/bluetooth/audio.h b/spa/include/spa/param/bluetooth/audio.h index abdf7ccb..c95e22d6 100644 --- a/spa/include/spa/param/bluetooth/audio.h +++ b/spa/include/spa/param/bluetooth/audio.h @@ -44,6 +44,9 @@ enum spa_bluetooth_audio_codec { /* BAP */ SPA_BLUETOOTH_AUDIO_CODEC_LC3 = 0x200, + + /* ASHA */ + SPA_BLUETOOTH_AUDIO_CODEC_G722 = 0x300, }; /** diff --git a/spa/include/spa/param/bluetooth/type-info.h b/spa/include/spa/param/bluetooth/type-info.h index 878c5b9b..7d9cd365 100644 --- a/spa/include/spa/param/bluetooth/type-info.h +++ b/spa/include/spa/param/bluetooth/type-info.h @@ -47,6 +47,8 @@ static const struct spa_type_info spa_type_bluetooth_audio_codec[] = { { SPA_BLUETOOTH_AUDIO_CODEC_LC3, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "lc3", NULL }, + { SPA_BLUETOOTH_AUDIO_CODEC_G722, SPA_TYPE_Int, SPA_TYPE_INFO_BLUETOOTH_AUDIO_CODEC_BASE "g722", NULL }, + { 0, 0, NULL, NULL }, }; diff --git a/spa/include/spa/param/format-types.h b/spa/include/spa/param/format-types.h index b7f52d71..8daaa103 100644 --- a/spa/include/spa/param/format-types.h +++ b/spa/include/spa/param/format-types.h @@ -19,6 +19,7 @@ extern "C" { #include <spa/param/audio/type-info.h> #include <spa/param/video/type-info.h> +#include <spa/control/type-info.h> #define SPA_TYPE_INFO_Format SPA_TYPE_INFO_PARAM_BASE "Format" #define SPA_TYPE_INFO_FORMAT_BASE SPA_TYPE_INFO_Format ":" @@ -103,6 +104,9 @@ static const struct spa_type_info spa_type_media_subtype[] = { #define SPA_TYPE_INFO_FORMAT_VIDEO_H264 SPA_TYPE_INFO_FORMAT_VIDEO_BASE "H264" #define SPA_TYPE_INFO_FORMAT_VIDEO_H264_BASE SPA_TYPE_INFO_FORMAT_VIDEO_H264 ":" +#define SPA_TYPE_INFO_FormatControl SPA_TYPE_INFO_FORMAT_BASE "Control" +#define SPA_TYPE_INFO_FORMAT_CONTROL_BASE SPA_TYPE_INFO_FormatControl ":" + static const struct spa_type_info spa_type_format[] = { { SPA_FORMAT_START, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_BASE, spa_type_param, }, @@ -158,6 +162,8 @@ static const struct spa_type_info spa_type_format[] = { { SPA_FORMAT_VIDEO_H264_streamFormat, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_H264_BASE "streamFormat", NULL }, { SPA_FORMAT_VIDEO_H264_alignment, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_VIDEO_H264_BASE "alignment", NULL }, + + { SPA_FORMAT_CONTROL_types, SPA_TYPE_Id, SPA_TYPE_INFO_FORMAT_CONTROL_BASE "types", spa_type_control }, { 0, 0, NULL, NULL }, }; diff --git a/spa/include/spa/param/format-utils.h b/spa/include/spa/param/format-utils.h index fb5cb39a..27fc5f58 100644 --- a/spa/include/spa/param/format-utils.h +++ b/spa/include/spa/param/format-utils.h @@ -18,7 +18,15 @@ extern "C" { #include <spa/pod/parser.h> #include <spa/param/format.h> -static inline int +#ifndef SPA_API_FORMAT_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_FORMAT_UTILS SPA_API_IMPL + #else + #define SPA_API_FORMAT_UTILS static inline + #endif +#endif + +SPA_API_FORMAT_UTILS int spa_format_parse(const struct spa_pod *format, uint32_t *media_type, uint32_t *media_subtype) { return spa_pod_parse_object(format, diff --git a/spa/include/spa/param/format.h b/spa/include/spa/param/format.h index 3f075fd3..eb3b851b 100644 --- a/spa/include/spa/param/format.h +++ b/spa/include/spa/param/format.h @@ -141,6 +141,8 @@ enum spa_format { SPA_FORMAT_START_Stream = 0x50000, /* Application Format keys */ SPA_FORMAT_START_Application = 0x60000, + SPA_FORMAT_CONTROL_types, /**< possible control types (flags choice Int, + * mask of enum spa_control_type) */ }; #define SPA_KEY_FORMAT_DSP "format.dsp" /**< a predefined DSP format, diff --git a/spa/include/spa/param/latency-utils.h b/spa/include/spa/param/latency-utils.h index 83df433f..45f817eb 100644 --- a/spa/include/spa/param/latency-utils.h +++ b/spa/include/spa/param/latency-utils.h @@ -20,7 +20,15 @@ extern "C" { #include <spa/pod/parser.h> #include <spa/param/latency.h> -static inline int +#ifndef SPA_API_LATENCY_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_LATENCY_UTILS SPA_API_IMPL + #else + #define SPA_API_LATENCY_UTILS static inline + #endif +#endif + +SPA_API_LATENCY_UTILS int spa_latency_info_compare(const struct spa_latency_info *a, const struct spa_latency_info *b) { if (a->min_quantum == b->min_quantum && @@ -33,29 +41,35 @@ spa_latency_info_compare(const struct spa_latency_info *a, const struct spa_late return 1; } -static inline void +SPA_API_LATENCY_UTILS void spa_latency_info_combine_start(struct spa_latency_info *info, enum spa_direction direction) { *info = SPA_LATENCY_INFO(direction, .min_quantum = FLT_MAX, - .max_quantum = 0.0f, - .min_rate = UINT32_MAX, - .max_rate = 0, - .min_ns = UINT64_MAX, - .max_ns = 0); + .max_quantum = FLT_MIN, + .min_rate = INT32_MAX, + .max_rate = INT32_MIN, + .min_ns = INT64_MAX, + .max_ns = INT64_MIN); } -static inline void +SPA_API_LATENCY_UTILS void spa_latency_info_combine_finish(struct spa_latency_info *info) { if (info->min_quantum == FLT_MAX) info->min_quantum = 0; - if (info->min_rate == UINT32_MAX) + if (info->max_quantum == FLT_MIN) + info->max_quantum = 0; + if (info->min_rate == INT32_MAX) info->min_rate = 0; - if (info->min_ns == UINT64_MAX) + if (info->max_rate == INT32_MIN) + info->max_rate = 0; + if (info->min_ns == INT64_MAX) info->min_ns = 0; + if (info->max_ns == INT64_MIN) + info->max_ns = 0; } -static inline int +SPA_API_LATENCY_UTILS int spa_latency_info_combine(struct spa_latency_info *info, const struct spa_latency_info *other) { if (info->direction != other->direction) @@ -75,7 +89,7 @@ spa_latency_info_combine(struct spa_latency_info *info, const struct spa_latency return 0; } -static inline int +SPA_API_LATENCY_UTILS int spa_latency_parse(const struct spa_pod *latency, struct spa_latency_info *info) { int res; @@ -94,7 +108,7 @@ spa_latency_parse(const struct spa_pod *latency, struct spa_latency_info *info) return 0; } -static inline struct spa_pod * +SPA_API_LATENCY_UTILS struct spa_pod * spa_latency_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_latency_info *info) { return (struct spa_pod *)spa_pod_builder_add_object(builder, @@ -108,7 +122,7 @@ spa_latency_build(struct spa_pod_builder *builder, uint32_t id, const struct spa SPA_PARAM_LATENCY_maxNs, SPA_POD_Long(info->max_ns)); } -static inline int +SPA_API_LATENCY_UTILS int spa_process_latency_parse(const struct spa_pod *latency, struct spa_process_latency_info *info) { int res; @@ -122,7 +136,7 @@ spa_process_latency_parse(const struct spa_pod *latency, struct spa_process_late return 0; } -static inline struct spa_pod * +SPA_API_LATENCY_UTILS struct spa_pod * spa_process_latency_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_process_latency_info *info) { @@ -133,7 +147,7 @@ spa_process_latency_build(struct spa_pod_builder *builder, uint32_t id, SPA_PARAM_PROCESS_LATENCY_ns, SPA_POD_Long(info->ns)); } -static inline int +SPA_API_LATENCY_UTILS int spa_process_latency_info_add(const struct spa_process_latency_info *process, struct spa_latency_info *info) { @@ -146,6 +160,17 @@ spa_process_latency_info_add(const struct spa_process_latency_info *process, return 0; } +SPA_API_LATENCY_UTILS int +spa_process_latency_info_compare(const struct spa_process_latency_info *a, + const struct spa_process_latency_info *b) +{ + if (a->quantum == b->quantum && + a->rate == b->rate && + a->ns == b->ns) + return 0; + return 1; +} + /** * \} */ diff --git a/spa/include/spa/param/latency.h b/spa/include/spa/param/latency.h index 456c8b6c..4087941c 100644 --- a/spa/include/spa/param/latency.h +++ b/spa/include/spa/param/latency.h @@ -50,10 +50,10 @@ struct spa_latency_info { enum spa_direction direction; float min_quantum; float max_quantum; - uint32_t min_rate; - uint32_t max_rate; - uint64_t min_ns; - uint64_t max_ns; + int32_t min_rate; + int32_t max_rate; + int64_t min_ns; + int64_t max_ns; }; #define SPA_LATENCY_INFO(dir,...) ((struct spa_latency_info) { .direction = (dir), ## __VA_ARGS__ }) @@ -74,8 +74,8 @@ enum spa_param_process_latency { /** Helper structure for managing process latency objects */ struct spa_process_latency_info { float quantum; - uint32_t rate; - uint64_t ns; + int32_t rate; + int64_t ns; }; #define SPA_PROCESS_LATENCY_INFO_INIT(...) ((struct spa_process_latency_info) { __VA_ARGS__ }) diff --git a/spa/include/spa/param/param-types.h b/spa/include/spa/param/param-types.h index 4bed3651..ebb8d988 100644 --- a/spa/include/spa/param/param-types.h +++ b/spa/include/spa/param/param-types.h @@ -55,6 +55,11 @@ static const struct spa_type_info spa_type_prop_float_array[] = { { 0, 0, NULL, NULL }, }; +static const struct spa_type_info spa_type_prop_int_array[] = { + { SPA_PROP_START, SPA_TYPE_Int, SPA_TYPE_INFO_BASE "intArray", NULL, }, + { 0, 0, NULL, NULL }, +}; + static const struct spa_type_info spa_type_prop_channel_map[] = { { SPA_PROP_START, SPA_TYPE_Id, SPA_TYPE_INFO_BASE "channelMap", spa_type_audio_channel, }, { 0, 0, NULL, NULL }, diff --git a/spa/include/spa/param/profiler-types.h b/spa/include/spa/param/profiler-types.h index 91cbf112..57f3f369 100644 --- a/spa/include/spa/param/profiler-types.h +++ b/spa/include/spa/param/profiler-types.h @@ -26,6 +26,7 @@ static const struct spa_type_info spa_type_profiler[] = { { SPA_PROFILER_clock, SPA_TYPE_Struct, SPA_TYPE_INFO_PROFILER_BASE "clock", NULL, }, { SPA_PROFILER_driverBlock, SPA_TYPE_Struct, SPA_TYPE_INFO_PROFILER_BASE "driverBlock", NULL, }, { SPA_PROFILER_followerBlock, SPA_TYPE_Struct, SPA_TYPE_INFO_PROFILER_BASE "followerBlock", NULL, }, + { SPA_PROFILER_followerClock, SPA_TYPE_Struct, SPA_TYPE_INFO_PROFILER_BASE "followerClock", NULL, }, { 0, 0, NULL, NULL }, }; diff --git a/spa/include/spa/param/profiler.h b/spa/include/spa/param/profiler.h index 5d1c9453..36af0fc2 100644 --- a/spa/include/spa/param/profiler.h +++ b/spa/include/spa/param/profiler.h @@ -40,7 +40,9 @@ enum spa_profiler { * Long : clock delay, * Double : clock rate_diff, * Long : clock next_nsec, - * Int : transport_state)) */ + * Int : transport_state, + * Int : clock cycle, + * Long : xrun duration)) */ SPA_PROFILER_driverBlock, /**< generic driver info block * (Struct( * Int : driver_id, @@ -65,7 +67,18 @@ enum spa_profiler { * Int : status, * Fraction : latency, * Int : xrun_count)) */ - + SPA_PROFILER_followerClock, /**< follower clock information + * (Struct( + * Int : clock id, + * String: clock name, + * Long : clock nsec, + * Fraction : clock rate, + * Long : clock position, + * Long : clock duration, + * Long : clock delay, + * Double : clock rate_diff, + * Long : clock next_nsec, + * Long : xrun duration)) */ SPA_PROFILER_START_CUSTOM = 0x1000000, }; diff --git a/spa/include/spa/param/props-types.h b/spa/include/spa/param/props-types.h index e00f2f87..e6612599 100644 --- a/spa/include/spa/param/props-types.h +++ b/spa/include/spa/param/props-types.h @@ -64,14 +64,14 @@ static const struct spa_type_info spa_type_props[] = { { SPA_PROP_volumeRampStepTime, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "volumeRampStepTime", NULL }, { SPA_PROP_volumeRampScale, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "volumeRampScale", NULL }, - { SPA_PROP_brightness, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "brightness", NULL }, - { SPA_PROP_contrast, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "contrast", NULL }, - { SPA_PROP_saturation, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "saturation", NULL }, + { SPA_PROP_brightness, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "brightness", NULL }, + { SPA_PROP_contrast, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "contrast", NULL }, + { SPA_PROP_saturation, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "saturation", NULL }, { SPA_PROP_hue, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "hue", NULL }, { SPA_PROP_gamma, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "gamma", NULL }, { SPA_PROP_exposure, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "exposure", NULL }, - { SPA_PROP_gain, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "gain", NULL }, - { SPA_PROP_sharpness, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "sharpness", NULL }, + { SPA_PROP_gain, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "gain", NULL }, + { SPA_PROP_sharpness, SPA_TYPE_Float, SPA_TYPE_INFO_PROPS_BASE "sharpness", NULL }, { SPA_PROP_params, SPA_TYPE_Struct, SPA_TYPE_INFO_PROPS_BASE "params", NULL }, { 0, 0, NULL, NULL }, diff --git a/spa/include/spa/param/route-types.h b/spa/include/spa/param/route-types.h index 619a9e2e..78ced495 100644 --- a/spa/include/spa/param/route-types.h +++ b/spa/include/spa/param/route-types.h @@ -32,9 +32,9 @@ static const struct spa_type_info spa_type_param_route[] = { { SPA_PARAM_ROUTE_priority, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ROUTE_BASE "priority", NULL, }, { SPA_PARAM_ROUTE_available, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_ROUTE_BASE "available", spa_type_param_availability, }, { SPA_PARAM_ROUTE_info, SPA_TYPE_Struct, SPA_TYPE_INFO_PARAM_ROUTE_BASE "info", NULL, }, - { SPA_PARAM_ROUTE_profiles, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ROUTE_BASE "profiles", NULL, }, + { SPA_PARAM_ROUTE_profiles, SPA_TYPE_Array, SPA_TYPE_INFO_PARAM_ROUTE_BASE "profiles", spa_type_prop_int_array, }, { SPA_PARAM_ROUTE_props, SPA_TYPE_OBJECT_Props, SPA_TYPE_INFO_PARAM_ROUTE_BASE "props", NULL, }, - { SPA_PARAM_ROUTE_devices, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ROUTE_BASE "devices", NULL, }, + { SPA_PARAM_ROUTE_devices, SPA_TYPE_Array, SPA_TYPE_INFO_PARAM_ROUTE_BASE "devices", spa_type_prop_int_array, }, { SPA_PARAM_ROUTE_profile, SPA_TYPE_Int, SPA_TYPE_INFO_PARAM_ROUTE_BASE "profile", NULL, }, { SPA_PARAM_ROUTE_save, SPA_TYPE_Bool, SPA_TYPE_INFO_PARAM_ROUTE_BASE "save", NULL, }, { 0, 0, NULL, NULL }, diff --git a/spa/include/spa/param/tag-utils.h b/spa/include/spa/param/tag-utils.h index 2bce7b19..ba8a952c 100644 --- a/spa/include/spa/param/tag-utils.h +++ b/spa/include/spa/param/tag-utils.h @@ -21,14 +21,22 @@ extern "C" { #include <spa/pod/parser.h> #include <spa/param/tag.h> -static inline int +#ifndef SPA_API_TAG_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_TAG_UTILS SPA_API_IMPL + #else + #define SPA_API_TAG_UTILS static inline + #endif +#endif + +SPA_API_TAG_UTILS int spa_tag_compare(const struct spa_pod *a, const struct spa_pod *b) { return ((a == b) || (a && b && SPA_POD_SIZE(a) == SPA_POD_SIZE(b) && memcmp(a, b, SPA_POD_SIZE(b)) == 0)) ? 0 : 1; } -static inline int +SPA_API_TAG_UTILS int spa_tag_parse(const struct spa_pod *tag, struct spa_tag_info *info, void **state) { int res; @@ -57,7 +65,7 @@ spa_tag_parse(const struct spa_pod *tag, struct spa_tag_info *info, void **state return 0; } -static inline int +SPA_API_TAG_UTILS int spa_tag_info_parse(const struct spa_tag_info *info, struct spa_dict *dict, struct spa_dict_item *items) { struct spa_pod_parser prs; @@ -90,7 +98,7 @@ spa_tag_info_parse(const struct spa_tag_info *info, struct spa_dict *dict, struc return 0; } -static inline void +SPA_API_TAG_UTILS void spa_tag_build_start(struct spa_pod_builder *builder, struct spa_pod_frame *f, uint32_t id, enum spa_direction direction) { @@ -100,7 +108,7 @@ spa_tag_build_start(struct spa_pod_builder *builder, struct spa_pod_frame *f, 0); } -static inline void +SPA_API_TAG_UTILS void spa_tag_build_add_info(struct spa_pod_builder *builder, const struct spa_pod *info) { spa_pod_builder_add(builder, @@ -108,7 +116,7 @@ spa_tag_build_add_info(struct spa_pod_builder *builder, const struct spa_pod *in 0); } -static inline void +SPA_API_TAG_UTILS void spa_tag_build_add_dict(struct spa_pod_builder *builder, const struct spa_dict *dict) { uint32_t i, n_items; @@ -126,7 +134,7 @@ spa_tag_build_add_dict(struct spa_pod_builder *builder, const struct spa_dict *d spa_pod_builder_pop(builder, &f); } -static inline struct spa_pod * +SPA_API_TAG_UTILS struct spa_pod * spa_tag_build_end(struct spa_pod_builder *builder, struct spa_pod_frame *f) { return (struct spa_pod*)spa_pod_builder_pop(builder, f); diff --git a/spa/include/spa/param/video/dsp-utils.h b/spa/include/spa/param/video/dsp-utils.h index 193e3194..6e76309b 100644 --- a/spa/include/spa/param/video/dsp-utils.h +++ b/spa/include/spa/param/video/dsp-utils.h @@ -18,7 +18,15 @@ extern "C" { #include <spa/pod/builder.h> #include <spa/param/video/dsp.h> -static inline int +#ifndef SPA_API_VIDEO_DSP_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_VIDEO_DSP_UTILS SPA_API_IMPL + #else + #define SPA_API_VIDEO_DSP_UTILS static inline + #endif +#endif + +SPA_API_VIDEO_DSP_UTILS int spa_format_video_dsp_parse(const struct spa_pod *format, struct spa_video_info_dsp *info) { @@ -36,7 +44,7 @@ spa_format_video_dsp_parse(const struct spa_pod *format, SPA_FORMAT_VIDEO_modifier, SPA_POD_OPT_Long(&info->modifier)); } -static inline struct spa_pod * +SPA_API_VIDEO_DSP_UTILS struct spa_pod * spa_format_video_dsp_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_video_info_dsp *info) { diff --git a/spa/include/spa/param/video/format-utils.h b/spa/include/spa/param/video/format-utils.h index a04b67b2..9efbef61 100644 --- a/spa/include/spa/param/video/format-utils.h +++ b/spa/include/spa/param/video/format-utils.h @@ -16,7 +16,15 @@ extern "C" { #include <spa/param/video/h264-utils.h> #include <spa/param/video/mjpg-utils.h> -static inline int +#ifndef SPA_API_VIDEO_FORMAT_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_VIDEO_FORMAT_UTILS SPA_API_IMPL + #else + #define SPA_API_VIDEO_FORMAT_UTILS static inline + #endif +#endif + +SPA_API_VIDEO_FORMAT_UTILS int spa_format_video_parse(const struct spa_pod *format, struct spa_video_info *info) { int res; @@ -40,7 +48,7 @@ spa_format_video_parse(const struct spa_pod *format, struct spa_video_info *info return -ENOTSUP; } -static inline struct spa_pod * +SPA_API_VIDEO_FORMAT_UTILS struct spa_pod * spa_format_video_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_video_info *info) { diff --git a/spa/include/spa/param/video/h264-utils.h b/spa/include/spa/param/video/h264-utils.h index fbf22c95..fa693329 100644 --- a/spa/include/spa/param/video/h264-utils.h +++ b/spa/include/spa/param/video/h264-utils.h @@ -18,7 +18,15 @@ extern "C" { #include <spa/pod/builder.h> #include <spa/param/video/h264.h> -static inline int +#ifndef SPA_API_VIDEO_H264_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_VIDEO_H264_UTILS SPA_API_IMPL + #else + #define SPA_API_VIDEO_H264_UTILS static inline + #endif +#endif + +SPA_API_VIDEO_H264_UTILS int spa_format_video_h264_parse(const struct spa_pod *format, struct spa_video_info_h264 *info) { @@ -31,7 +39,7 @@ spa_format_video_h264_parse(const struct spa_pod *format, SPA_FORMAT_VIDEO_H264_alignment, SPA_POD_OPT_Id(&info->alignment)); } -static inline struct spa_pod * +SPA_API_VIDEO_H264_UTILS struct spa_pod * spa_format_video_h264_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_video_info_h264 *info) { diff --git a/spa/include/spa/param/video/mjpg-utils.h b/spa/include/spa/param/video/mjpg-utils.h index b6bec003..f1aa27af 100644 --- a/spa/include/spa/param/video/mjpg-utils.h +++ b/spa/include/spa/param/video/mjpg-utils.h @@ -18,7 +18,15 @@ extern "C" { #include <spa/pod/builder.h> #include <spa/param/video/mjpg.h> -static inline int +#ifndef SPA_API_VIDEO_MJPG_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_VIDEO_MJPG_UTILS SPA_API_IMPL + #else + #define SPA_API_VIDEO_MJPG_UTILS static inline + #endif +#endif + +SPA_API_VIDEO_MJPG_UTILS int spa_format_video_mjpg_parse(const struct spa_pod *format, struct spa_video_info_mjpg *info) { @@ -29,7 +37,7 @@ spa_format_video_mjpg_parse(const struct spa_pod *format, SPA_FORMAT_VIDEO_maxFramerate, SPA_POD_OPT_Fraction(&info->max_framerate)); } -static inline struct spa_pod * +SPA_API_VIDEO_MJPG_UTILS struct spa_pod * spa_format_video_mjpg_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_video_info_mjpg *info) { diff --git a/spa/include/spa/param/video/raw-types.h b/spa/include/spa/param/video/raw-types.h index 30d144c7..bca0c8d4 100644 --- a/spa/include/spa/param/video/raw-types.h +++ b/spa/include/spa/param/video/raw-types.h @@ -16,11 +16,20 @@ extern "C" { #include <spa/utils/type.h> #include <spa/param/video/raw.h> +#ifndef SPA_API_VIDEO_RAW_TYPES + #ifdef SPA_API_IMPL + #define SPA_API_VIDEO_RAW_TYPES SPA_API_IMPL + #else + #define SPA_API_VIDEO_RAW_TYPES static inline + #endif +#endif + #define SPA_TYPE_INFO_VideoFormat SPA_TYPE_INFO_ENUM_BASE "VideoFormat" #define SPA_TYPE_INFO_VIDEO_FORMAT_BASE SPA_TYPE_INFO_VideoFormat ":" static const struct spa_type_info spa_type_video_format[] = { - { SPA_VIDEO_FORMAT_ENCODED, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "encoded", NULL }, + { SPA_VIDEO_FORMAT_UNKNOWN, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "UNKNOWN", NULL }, + { SPA_VIDEO_FORMAT_ENCODED, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ENCODED", NULL }, { SPA_VIDEO_FORMAT_I420, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420", NULL }, { SPA_VIDEO_FORMAT_YV12, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YV12", NULL }, { SPA_VIDEO_FORMAT_YUY2, SPA_TYPE_Int, SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YUY2", NULL }, @@ -110,6 +119,15 @@ static const struct spa_type_info spa_type_video_format[] = { { 0, 0, NULL, NULL }, }; +SPA_API_VIDEO_RAW_TYPES uint32_t spa_type_video_format_from_short_name(const char *name) +{ + return spa_type_from_short_name(name, spa_type_video_format, SPA_VIDEO_FORMAT_UNKNOWN); +} +SPA_API_VIDEO_RAW_TYPES const char * spa_type_video_format_to_short_name(uint32_t type) +{ + return spa_type_to_short_name(type, spa_type_video_format, "UNKNOWN"); +} + #define SPA_TYPE_INFO_VideoFlags SPA_TYPE_INFO_FLAGS_BASE "VideoFlags" #define SPA_TYPE_INFO_VIDEO_FLAGS_BASE SPA_TYPE_INFO_VideoFlags ":" diff --git a/spa/include/spa/param/video/raw-utils.h b/spa/include/spa/param/video/raw-utils.h index 41d9cc1e..8a5a2778 100644 --- a/spa/include/spa/param/video/raw-utils.h +++ b/spa/include/spa/param/video/raw-utils.h @@ -18,7 +18,15 @@ extern "C" { #include <spa/pod/builder.h> #include <spa/param/video/raw.h> -static inline int +#ifndef SPA_API_VIDEO_RAW_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_VIDEO_RAW_UTILS SPA_API_IMPL + #else + #define SPA_API_VIDEO_RAW_UTILS static inline + #endif +#endif + +SPA_API_VIDEO_RAW_UTILS int spa_format_video_raw_parse(const struct spa_pod *format, struct spa_video_info_raw *info) { @@ -49,7 +57,7 @@ spa_format_video_raw_parse(const struct spa_pod *format, SPA_FORMAT_VIDEO_colorPrimaries, SPA_POD_OPT_Id(&info->color_primaries)); } -static inline struct spa_pod * +SPA_API_VIDEO_RAW_UTILS struct spa_pod * spa_format_video_raw_build(struct spa_pod_builder *builder, uint32_t id, const struct spa_video_info_raw *info) { diff --git a/spa/include/spa/pod/builder.h b/spa/include/spa/pod/builder.h index 0564d94e..553f7551 100644 --- a/spa/include/spa/pod/builder.h +++ b/spa/include/spa/pod/builder.h @@ -24,6 +24,14 @@ extern "C" { #include <spa/pod/iter.h> #include <spa/pod/vararg.h> +#ifndef SPA_API_POD_BUILDER + #ifdef SPA_API_IMPL + #define SPA_API_POD_BUILDER SPA_API_IMPL + #else + #define SPA_API_POD_BUILDER static inline + #endif +#endif + struct spa_pod_builder_state { uint32_t offset; #define SPA_POD_BUILDER_FLAG_BODY (1<<0) @@ -49,22 +57,22 @@ struct spa_pod_builder { struct spa_callbacks callbacks; }; -#define SPA_POD_BUILDER_INIT(buffer,size) ((struct spa_pod_builder){ (buffer), (size), 0, {0}, {0} }) +#define SPA_POD_BUILDER_INIT(buffer,size) ((struct spa_pod_builder){ (buffer), (size), 0, {0,0,NULL},{NULL,NULL}}) -static inline void +SPA_API_POD_BUILDER void spa_pod_builder_get_state(struct spa_pod_builder *builder, struct spa_pod_builder_state *state) { *state = builder->state; } -static inline void +SPA_API_POD_BUILDER void spa_pod_builder_set_callbacks(struct spa_pod_builder *builder, const struct spa_pod_builder_callbacks *callbacks, void *data) { builder->callbacks = SPA_CALLBACKS_INIT(callbacks, data); } -static inline void +SPA_API_POD_BUILDER void spa_pod_builder_reset(struct spa_pod_builder *builder, struct spa_pod_builder_state *state) { struct spa_pod_frame *f; @@ -74,12 +82,12 @@ spa_pod_builder_reset(struct spa_pod_builder *builder, struct spa_pod_builder_st f->pod.size -= size; } -static inline void spa_pod_builder_init(struct spa_pod_builder *builder, void *data, uint32_t size) +SPA_API_POD_BUILDER void spa_pod_builder_init(struct spa_pod_builder *builder, void *data, uint32_t size) { *builder = SPA_POD_BUILDER_INIT(data, size); } -static inline struct spa_pod * +SPA_API_POD_BUILDER struct spa_pod * spa_pod_builder_deref(struct spa_pod_builder *builder, uint32_t offset) { uint32_t size = builder->size; @@ -91,7 +99,7 @@ spa_pod_builder_deref(struct spa_pod_builder *builder, uint32_t offset) return NULL; } -static inline struct spa_pod * +SPA_API_POD_BUILDER struct spa_pod * spa_pod_builder_frame(struct spa_pod_builder *builder, struct spa_pod_frame *frame) { if (frame->offset + SPA_POD_SIZE(&frame->pod) <= builder->size) @@ -99,7 +107,7 @@ spa_pod_builder_frame(struct spa_pod_builder *builder, struct spa_pod_frame *fra return NULL; } -static inline void +SPA_API_POD_BUILDER void spa_pod_builder_push(struct spa_pod_builder *builder, struct spa_pod_frame *frame, const struct spa_pod *pod, @@ -115,21 +123,30 @@ spa_pod_builder_push(struct spa_pod_builder *builder, builder->state.flags = SPA_POD_BUILDER_FLAG_FIRST | SPA_POD_BUILDER_FLAG_BODY; } -static inline int spa_pod_builder_raw(struct spa_pod_builder *builder, const void *data, uint32_t size) +SPA_API_POD_BUILDER int spa_pod_builder_raw(struct spa_pod_builder *builder, const void *data, uint32_t size) { int res = 0; struct spa_pod_frame *f; uint32_t offset = builder->state.offset; + size_t data_offset = -1; if (offset + size > builder->size) { + /* data could be inside the data we will realloc */ + if (spa_ptrinside(builder->data, builder->size, data, size, NULL)) + data_offset = SPA_PTRDIFF(data, builder->data); + res = -ENOSPC; if (offset <= builder->size) spa_callbacks_call_res(&builder->callbacks, struct spa_pod_builder_callbacks, res, overflow, 0, offset + size); } - if (res == 0 && data) + if (res == 0 && data) { + if (data_offset != (size_t) -1) + data = SPA_PTROFF(builder->data, data_offset, const void); + memcpy(SPA_PTROFF(builder->data, offset, void), data, size); + } builder->state.offset += size; @@ -139,14 +156,14 @@ static inline int spa_pod_builder_raw(struct spa_pod_builder *builder, const voi return res; } -static inline int spa_pod_builder_pad(struct spa_pod_builder *builder, uint32_t size) +SPA_API_POD_BUILDER int spa_pod_builder_pad(struct spa_pod_builder *builder, uint32_t size) { uint64_t zeroes = 0; size = SPA_ROUND_UP_N(size, 8) - size; return size ? spa_pod_builder_raw(builder, &zeroes, size) : 0; } -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_raw_padded(struct spa_pod_builder *builder, const void *data, uint32_t size) { int r, res = spa_pod_builder_raw(builder, data, size); @@ -155,7 +172,7 @@ spa_pod_builder_raw_padded(struct spa_pod_builder *builder, const void *data, ui return res; } -static inline void *spa_pod_builder_pop(struct spa_pod_builder *builder, struct spa_pod_frame *frame) +SPA_API_POD_BUILDER void *spa_pod_builder_pop(struct spa_pod_builder *builder, struct spa_pod_frame *frame) { struct spa_pod *pod; @@ -172,7 +189,7 @@ static inline void *spa_pod_builder_pop(struct spa_pod_builder *builder, struct return pod; } -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_primitive(struct spa_pod_builder *builder, const struct spa_pod *p) { const void *data; @@ -198,13 +215,13 @@ spa_pod_builder_primitive(struct spa_pod_builder *builder, const struct spa_pod #define SPA_POD_INIT_None() SPA_POD_INIT(0, SPA_TYPE_None) -static inline int spa_pod_builder_none(struct spa_pod_builder *builder) +SPA_API_POD_BUILDER int spa_pod_builder_none(struct spa_pod_builder *builder) { const struct spa_pod p = SPA_POD_INIT_None(); return spa_pod_builder_primitive(builder, &p); } -static inline int spa_pod_builder_child(struct spa_pod_builder *builder, uint32_t size, uint32_t type) +SPA_API_POD_BUILDER int spa_pod_builder_child(struct spa_pod_builder *builder, uint32_t size, uint32_t type) { const struct spa_pod p = SPA_POD_INIT(size,type); SPA_FLAG_CLEAR(builder->state.flags, SPA_POD_BUILDER_FLAG_FIRST); @@ -213,7 +230,7 @@ static inline int spa_pod_builder_child(struct spa_pod_builder *builder, uint32_ #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) +SPA_API_POD_BUILDER int spa_pod_builder_bool(struct spa_pod_builder *builder, bool val) { const struct spa_pod_bool p = SPA_POD_INIT_Bool(val); return spa_pod_builder_primitive(builder, &p.pod); @@ -221,7 +238,7 @@ static inline int spa_pod_builder_bool(struct spa_pod_builder *builder, bool val #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) +SPA_API_POD_BUILDER int spa_pod_builder_id(struct spa_pod_builder *builder, uint32_t val) { const struct spa_pod_id p = SPA_POD_INIT_Id(val); return spa_pod_builder_primitive(builder, &p.pod); @@ -229,7 +246,7 @@ static inline int spa_pod_builder_id(struct spa_pod_builder *builder, uint32_t v #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) +SPA_API_POD_BUILDER int spa_pod_builder_int(struct spa_pod_builder *builder, int32_t val) { const struct spa_pod_int p = SPA_POD_INIT_Int(val); return spa_pod_builder_primitive(builder, &p.pod); @@ -237,7 +254,7 @@ static inline int spa_pod_builder_int(struct spa_pod_builder *builder, int32_t v #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) +SPA_API_POD_BUILDER int spa_pod_builder_long(struct spa_pod_builder *builder, int64_t val) { const struct spa_pod_long p = SPA_POD_INIT_Long(val); return spa_pod_builder_primitive(builder, &p.pod); @@ -245,7 +262,7 @@ static inline int spa_pod_builder_long(struct spa_pod_builder *builder, int64_t #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) +SPA_API_POD_BUILDER int spa_pod_builder_float(struct spa_pod_builder *builder, float val) { const struct spa_pod_float p = SPA_POD_INIT_Float(val); return spa_pod_builder_primitive(builder, &p.pod); @@ -253,7 +270,7 @@ static inline int spa_pod_builder_float(struct spa_pod_builder *builder, float v #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) +SPA_API_POD_BUILDER int spa_pod_builder_double(struct spa_pod_builder *builder, double val) { const struct spa_pod_double p = SPA_POD_INIT_Double(val); return spa_pod_builder_primitive(builder, &p.pod); @@ -261,7 +278,7 @@ static inline int spa_pod_builder_double(struct spa_pod_builder *builder, double #define SPA_POD_INIT_String(len) ((struct spa_pod_string){ { (len), SPA_TYPE_String } }) -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_write_string(struct spa_pod_builder *builder, const char *str, uint32_t len) { int r, res; @@ -273,7 +290,7 @@ spa_pod_builder_write_string(struct spa_pod_builder *builder, const char *str, u return res; } -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_string_len(struct spa_pod_builder *builder, const char *str, uint32_t len) { const struct spa_pod_string p = SPA_POD_INIT_String(len+1); @@ -283,7 +300,7 @@ spa_pod_builder_string_len(struct spa_pod_builder *builder, const char *str, uin return res; } -static inline int spa_pod_builder_string(struct spa_pod_builder *builder, const char *str) +SPA_API_POD_BUILDER int spa_pod_builder_string(struct spa_pod_builder *builder, const char *str) { uint32_t len = str ? strlen(str) : 0; return spa_pod_builder_string_len(builder, str ? str : "", len); @@ -291,7 +308,7 @@ static inline int spa_pod_builder_string(struct spa_pod_builder *builder, const #define SPA_POD_INIT_Bytes(len) ((struct spa_pod_bytes){ { (len), SPA_TYPE_Bytes } }) -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_bytes(struct spa_pod_builder *builder, const void *bytes, uint32_t len) { const struct spa_pod_bytes p = SPA_POD_INIT_Bytes(len); @@ -300,7 +317,7 @@ spa_pod_builder_bytes(struct spa_pod_builder *builder, const void *bytes, uint32 res = r; return res; } -static inline void * +SPA_API_POD_BUILDER void * spa_pod_builder_reserve_bytes(struct spa_pod_builder *builder, uint32_t len) { uint32_t offset = builder->state.offset; @@ -311,7 +328,7 @@ spa_pod_builder_reserve_bytes(struct spa_pod_builder *builder, uint32_t len) #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_API_POD_BUILDER int spa_pod_builder_pointer(struct spa_pod_builder *builder, uint32_t type, const void *val) { const struct spa_pod_pointer p = SPA_POD_INIT_Pointer(type, val); @@ -320,7 +337,7 @@ spa_pod_builder_pointer(struct spa_pod_builder *builder, uint32_t type, const vo #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) +SPA_API_POD_BUILDER int spa_pod_builder_fd(struct spa_pod_builder *builder, int64_t fd) { const struct spa_pod_fd p = SPA_POD_INIT_Fd(fd); return spa_pod_builder_primitive(builder, &p.pod); @@ -328,7 +345,7 @@ static inline int spa_pod_builder_fd(struct spa_pod_builder *builder, int64_t fd #define SPA_POD_INIT_Rectangle(val) ((struct spa_pod_rectangle){ { sizeof(struct spa_rectangle), SPA_TYPE_Rectangle }, (val) }) -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_rectangle(struct spa_pod_builder *builder, uint32_t width, uint32_t height) { const struct spa_pod_rectangle p = SPA_POD_INIT_Rectangle(SPA_RECTANGLE(width, height)); @@ -337,14 +354,14 @@ spa_pod_builder_rectangle(struct spa_pod_builder *builder, uint32_t width, uint3 #define SPA_POD_INIT_Fraction(val) ((struct spa_pod_fraction){ { sizeof(struct spa_fraction), SPA_TYPE_Fraction }, (val) }) -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_fraction(struct spa_pod_builder *builder, uint32_t num, uint32_t denom) { const struct spa_pod_fraction p = SPA_POD_INIT_Fraction(SPA_FRACTION(num, denom)); return spa_pod_builder_primitive(builder, &p.pod); } -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_push_array(struct spa_pod_builder *builder, struct spa_pod_frame *frame) { const struct spa_pod_array p = @@ -356,7 +373,7 @@ spa_pod_builder_push_array(struct spa_pod_builder *builder, struct spa_pod_frame return res; } -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_array(struct spa_pod_builder *builder, uint32_t child_size, uint32_t child_type, uint32_t n_elems, const void *elems) { @@ -378,7 +395,7 @@ spa_pod_builder_array(struct spa_pod_builder *builder, { { { (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_API_POD_BUILDER int spa_pod_builder_push_choice(struct spa_pod_builder *builder, struct spa_pod_frame *frame, uint32_t type, uint32_t flags) { @@ -393,7 +410,7 @@ spa_pod_builder_push_choice(struct spa_pod_builder *builder, struct spa_pod_fram #define SPA_POD_INIT_Struct(size) ((struct spa_pod_struct){ { (size), SPA_TYPE_Struct } }) -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_push_struct(struct spa_pod_builder *builder, struct spa_pod_frame *frame) { const struct spa_pod_struct p = SPA_POD_INIT_Struct(0); @@ -405,7 +422,7 @@ spa_pod_builder_push_struct(struct spa_pod_builder *builder, struct spa_pod_fram #define SPA_POD_INIT_Object(size,type,id,...) ((struct spa_pod_object){ { (size), SPA_TYPE_Object }, { (type), (id) }, ##__VA_ARGS__ }) -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_push_object(struct spa_pod_builder *builder, struct spa_pod_frame *frame, uint32_t type, uint32_t id) { @@ -420,7 +437,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) } }) -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_prop(struct spa_pod_builder *builder, uint32_t key, uint32_t flags) { const struct { uint32_t key; uint32_t flags; } p = { key, flags }; @@ -430,7 +447,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 } }) -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_push_sequence(struct spa_pod_builder *builder, struct spa_pod_frame *frame, uint32_t unit) { const struct spa_pod_sequence p = @@ -441,14 +458,14 @@ spa_pod_builder_push_sequence(struct spa_pod_builder *builder, struct spa_pod_fr return res; } -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_control(struct spa_pod_builder *builder, uint32_t offset, uint32_t type) { const struct { uint32_t offset; uint32_t type; } p = { offset, type }; return spa_pod_builder_raw(builder, &p, sizeof(p)); } -static inline uint32_t spa_choice_from_id(char id) +SPA_API_POD_BUILDER uint32_t spa_choice_from_id(char id) { switch (id) { case 'r': @@ -560,7 +577,7 @@ do { \ } \ } while(false) -static inline int +SPA_API_POD_BUILDER int spa_pod_builder_addv(struct spa_pod_builder *builder, va_list args) { int res = 0; @@ -618,7 +635,7 @@ spa_pod_builder_addv(struct spa_pod_builder *builder, va_list args) return res; } -static inline int spa_pod_builder_add(struct spa_pod_builder *builder, ...) +SPA_API_POD_BUILDER int spa_pod_builder_add(struct spa_pod_builder *builder, ...) { int res; va_list args; @@ -658,7 +675,7 @@ static inline int spa_pod_builder_add(struct spa_pod_builder *builder, ...) }) /** Copy a pod structure */ -static inline struct spa_pod * +SPA_API_POD_BUILDER struct spa_pod * spa_pod_copy(const struct spa_pod *pod) { size_t size; diff --git a/spa/include/spa/pod/compare.h b/spa/include/spa/pod/compare.h index f41e4811..50898756 100644 --- a/spa/include/spa/pod/compare.h +++ b/spa/include/spa/pod/compare.h @@ -20,12 +20,20 @@ extern "C" { #include <spa/pod/iter.h> #include <spa/pod/builder.h> +#ifndef SPA_API_POD_COMPARE + #ifdef SPA_API_IMPL + #define SPA_API_POD_COMPARE SPA_API_IMPL + #else + #define SPA_API_POD_COMPARE static inline + #endif +#endif + /** * \addtogroup spa_pod * \{ */ -static inline int spa_pod_compare_value(uint32_t type, const void *r1, const void *r2, uint32_t size) +SPA_API_POD_COMPARE int spa_pod_compare_value(uint32_t type, const void *r1, const void *r2, uint32_t size) { switch (type) { case SPA_TYPE_None: @@ -72,7 +80,7 @@ static inline int spa_pod_compare_value(uint32_t type, const void *r1, const voi return 0; } -static inline int spa_pod_compare(const struct spa_pod *pod1, +SPA_API_POD_COMPARE int spa_pod_compare(const struct spa_pod *pod1, const struct spa_pod *pod2) { int res = 0; diff --git a/spa/include/spa/pod/dynamic.h b/spa/include/spa/pod/dynamic.h index 9edff2d0..e9998cdb 100644 --- a/spa/include/spa/pod/dynamic.h +++ b/spa/include/spa/pod/dynamic.h @@ -12,6 +12,14 @@ extern "C" { #include <spa/pod/builder.h> #include <spa/utils/cleanup.h> +#ifndef SPA_API_POD_DYNAMIC + #ifdef SPA_API_IMPL + #define SPA_API_POD_DYNAMIC SPA_API_IMPL + #else + #define SPA_API_POD_DYNAMIC static inline + #endif +#endif + struct spa_pod_dynamic_builder { struct spa_pod_builder b; void *data; @@ -37,7 +45,7 @@ static int spa_pod_dynamic_builder_overflow(void *data, uint32_t size) return 0; } -static inline void spa_pod_dynamic_builder_init(struct spa_pod_dynamic_builder *builder, +SPA_API_POD_DYNAMIC void spa_pod_dynamic_builder_init(struct spa_pod_dynamic_builder *builder, void *data, uint32_t size, uint32_t extend) { static const struct spa_pod_builder_callbacks spa_pod_dynamic_builder_callbacks = { @@ -50,7 +58,7 @@ static inline void spa_pod_dynamic_builder_init(struct spa_pod_dynamic_builder * builder->data = data; } -static inline void spa_pod_dynamic_builder_clean(struct spa_pod_dynamic_builder *builder) +SPA_API_POD_DYNAMIC void spa_pod_dynamic_builder_clean(struct spa_pod_dynamic_builder *builder) { if (builder->data != builder->b.data) free(builder->b.data); diff --git a/spa/include/spa/pod/filter.h b/spa/include/spa/pod/filter.h index 3a682e1a..6a47472e 100644 --- a/spa/include/spa/pod/filter.h +++ b/spa/include/spa/pod/filter.h @@ -20,12 +20,20 @@ extern "C" { #include <spa/pod/builder.h> #include <spa/pod/compare.h> +#ifndef SPA_API_POD_FILTER + #ifdef SPA_API_IMPL + #define SPA_API_POD_FILTER SPA_API_IMPL + #else + #define SPA_API_POD_FILTER static inline + #endif +#endif + /** * \addtogroup spa_pod * \{ */ -static inline int spa_pod_choice_fix_default(struct spa_pod_choice *choice) +SPA_API_POD_FILTER int spa_pod_choice_fix_default(struct spa_pod_choice *choice) { void *val, *alt; int i, nvals; @@ -77,7 +85,7 @@ static inline int spa_pod_choice_fix_default(struct spa_pod_choice *choice) return 0; } -static inline int spa_pod_filter_flags_value(struct spa_pod_builder *b, +SPA_API_POD_FILTER int spa_pod_filter_flags_value(struct spa_pod_builder *b, uint32_t type, const void *r1, const void *r2, uint32_t size SPA_UNUSED) { switch (type) { @@ -103,7 +111,7 @@ static inline int spa_pod_filter_flags_value(struct spa_pod_builder *b, return 1; } -static inline int spa_pod_filter_is_step_of(uint32_t type, const void *r1, +SPA_API_POD_FILTER int spa_pod_filter_is_step_of(uint32_t type, const void *r1, const void *r2, uint32_t size SPA_UNUSED) { switch (type) { @@ -125,7 +133,7 @@ static inline int spa_pod_filter_is_step_of(uint32_t type, const void *r1, return 0; } -static inline int +SPA_API_POD_FILTER int spa_pod_filter_prop(struct spa_pod_builder *b, const struct spa_pod_prop *p1, const struct spa_pod_prop *p2) @@ -322,7 +330,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b, return 0; } -static inline int spa_pod_filter_part(struct spa_pod_builder *b, +SPA_API_POD_FILTER int spa_pod_filter_part(struct spa_pod_builder *b, const struct spa_pod *pod, uint32_t pod_size, const struct spa_pod *filter, uint32_t filter_size) { @@ -422,7 +430,7 @@ static inline int spa_pod_filter_part(struct spa_pod_builder *b, return res; } -static inline int +SPA_API_POD_FILTER int spa_pod_filter(struct spa_pod_builder *b, struct spa_pod **result, const struct spa_pod *pod, diff --git a/spa/include/spa/pod/iter.h b/spa/include/spa/pod/iter.h index 93a23ad9..1bd56996 100644 --- a/spa/include/spa/pod/iter.h +++ b/spa/include/spa/pod/iter.h @@ -14,6 +14,14 @@ extern "C" { #include <spa/pod/pod.h> +#ifndef SPA_API_POD_ITER + #ifdef SPA_API_IMPL + #define SPA_API_POD_ITER SPA_API_IMPL + #else + #define SPA_API_POD_ITER static inline + #endif +#endif + /** * \addtogroup spa_pod * \{ @@ -26,7 +34,7 @@ struct spa_pod_frame { uint32_t flags; }; -static inline bool spa_pod_is_inside(const void *pod, uint32_t size, const void *iter) +SPA_API_POD_ITER bool spa_pod_is_inside(const void *pod, uint32_t size, const void *iter) { size_t remaining; @@ -34,17 +42,17 @@ static inline bool spa_pod_is_inside(const void *pod, uint32_t size, const void remaining >= SPA_POD_BODY_SIZE(iter); } -static inline void *spa_pod_next(const void *iter) +SPA_API_POD_ITER void *spa_pod_next(const void *iter) { return SPA_PTROFF(iter, SPA_ROUND_UP_N(SPA_POD_SIZE(iter), 8), void); } -static inline struct spa_pod_prop *spa_pod_prop_first(const struct spa_pod_object_body *body) +SPA_API_POD_ITER struct spa_pod_prop *spa_pod_prop_first(const struct spa_pod_object_body *body) { return SPA_PTROFF(body, sizeof(struct spa_pod_object_body), struct spa_pod_prop); } -static inline bool spa_pod_prop_is_inside(const struct spa_pod_object_body *body, +SPA_API_POD_ITER bool spa_pod_prop_is_inside(const struct spa_pod_object_body *body, uint32_t size, const struct spa_pod_prop *iter) { size_t remaining; @@ -53,17 +61,17 @@ static inline bool spa_pod_prop_is_inside(const struct spa_pod_object_body *body remaining >= iter->value.size; } -static inline struct spa_pod_prop *spa_pod_prop_next(const struct spa_pod_prop *iter) +SPA_API_POD_ITER struct spa_pod_prop *spa_pod_prop_next(const struct spa_pod_prop *iter) { return SPA_PTROFF(iter, SPA_ROUND_UP_N(SPA_POD_PROP_SIZE(iter), 8), struct spa_pod_prop); } -static inline struct spa_pod_control *spa_pod_control_first(const struct spa_pod_sequence_body *body) +SPA_API_POD_ITER struct spa_pod_control *spa_pod_control_first(const struct spa_pod_sequence_body *body) { return SPA_PTROFF(body, sizeof(struct spa_pod_sequence_body), struct spa_pod_control); } -static inline bool spa_pod_control_is_inside(const struct spa_pod_sequence_body *body, +SPA_API_POD_ITER bool spa_pod_control_is_inside(const struct spa_pod_sequence_body *body, uint32_t size, const struct spa_pod_control *iter) { size_t remaining; @@ -72,7 +80,7 @@ static inline bool spa_pod_control_is_inside(const struct spa_pod_sequence_body remaining >= iter->value.size; } -static inline struct spa_pod_control *spa_pod_control_next(const struct spa_pod_control *iter) +SPA_API_POD_ITER struct spa_pod_control *spa_pod_control_next(const struct spa_pod_control *iter) { return SPA_PTROFF(iter, SPA_ROUND_UP_N(SPA_POD_CONTROL_SIZE(iter), 8), struct spa_pod_control); } @@ -118,7 +126,7 @@ static inline struct spa_pod_control *spa_pod_control_next(const struct spa_pod_ SPA_POD_SEQUENCE_BODY_FOREACH(&(seq)->body, SPA_POD_BODY_SIZE(seq), iter) -static inline void *spa_pod_from_data(void *data, size_t maxsize, off_t offset, size_t size) +SPA_API_POD_ITER void *spa_pod_from_data(void *data, size_t maxsize, off_t offset, size_t size) { void *pod; if (size < sizeof(struct spa_pod) || offset + size > maxsize) @@ -129,17 +137,17 @@ static inline void *spa_pod_from_data(void *data, size_t maxsize, off_t offset, return pod; } -static inline int spa_pod_is_none(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_none(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_None); } -static inline int spa_pod_is_bool(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_bool(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Bool && SPA_POD_BODY_SIZE(pod) >= sizeof(int32_t)); } -static inline int spa_pod_get_bool(const struct spa_pod *pod, bool *value) +SPA_API_POD_ITER int spa_pod_get_bool(const struct spa_pod *pod, bool *value) { if (!spa_pod_is_bool(pod)) return -EINVAL; @@ -147,12 +155,12 @@ static inline int spa_pod_get_bool(const struct spa_pod *pod, bool *value) return 0; } -static inline int spa_pod_is_id(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_id(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Id && SPA_POD_BODY_SIZE(pod) >= sizeof(uint32_t)); } -static inline int spa_pod_get_id(const struct spa_pod *pod, uint32_t *value) +SPA_API_POD_ITER int spa_pod_get_id(const struct spa_pod *pod, uint32_t *value) { if (!spa_pod_is_id(pod)) return -EINVAL; @@ -160,12 +168,12 @@ static inline int spa_pod_get_id(const struct spa_pod *pod, uint32_t *value) return 0; } -static inline int spa_pod_is_int(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_int(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Int && SPA_POD_BODY_SIZE(pod) >= sizeof(int32_t)); } -static inline int spa_pod_get_int(const struct spa_pod *pod, int32_t *value) +SPA_API_POD_ITER int spa_pod_get_int(const struct spa_pod *pod, int32_t *value) { if (!spa_pod_is_int(pod)) return -EINVAL; @@ -173,12 +181,12 @@ static inline int spa_pod_get_int(const struct spa_pod *pod, int32_t *value) return 0; } -static inline int spa_pod_is_long(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_long(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Long && SPA_POD_BODY_SIZE(pod) >= sizeof(int64_t)); } -static inline int spa_pod_get_long(const struct spa_pod *pod, int64_t *value) +SPA_API_POD_ITER int spa_pod_get_long(const struct spa_pod *pod, int64_t *value) { if (!spa_pod_is_long(pod)) return -EINVAL; @@ -186,12 +194,12 @@ static inline int spa_pod_get_long(const struct spa_pod *pod, int64_t *value) return 0; } -static inline int spa_pod_is_float(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_float(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Float && SPA_POD_BODY_SIZE(pod) >= sizeof(float)); } -static inline int spa_pod_get_float(const struct spa_pod *pod, float *value) +SPA_API_POD_ITER int spa_pod_get_float(const struct spa_pod *pod, float *value) { if (!spa_pod_is_float(pod)) return -EINVAL; @@ -199,12 +207,12 @@ static inline int spa_pod_get_float(const struct spa_pod *pod, float *value) return 0; } -static inline int spa_pod_is_double(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_double(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Double && SPA_POD_BODY_SIZE(pod) >= sizeof(double)); } -static inline int spa_pod_get_double(const struct spa_pod *pod, double *value) +SPA_API_POD_ITER int spa_pod_get_double(const struct spa_pod *pod, double *value) { if (!spa_pod_is_double(pod)) return -EINVAL; @@ -212,7 +220,7 @@ static inline int spa_pod_get_double(const struct spa_pod *pod, double *value) return 0; } -static inline int spa_pod_is_string(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_string(const struct spa_pod *pod) { const char *s = (const char *)SPA_POD_CONTENTS(struct spa_pod_string, pod); return (SPA_POD_TYPE(pod) == SPA_TYPE_String && @@ -220,7 +228,7 @@ static inline int spa_pod_is_string(const struct spa_pod *pod) s[SPA_POD_BODY_SIZE(pod)-1] == '\0'); } -static inline int spa_pod_get_string(const struct spa_pod *pod, const char **value) +SPA_API_POD_ITER int spa_pod_get_string(const struct spa_pod *pod, const char **value) { if (!spa_pod_is_string(pod)) return -EINVAL; @@ -228,7 +236,7 @@ static inline int spa_pod_get_string(const struct spa_pod *pod, const char **val return 0; } -static inline int spa_pod_copy_string(const struct spa_pod *pod, size_t maxlen, char *dest) +SPA_API_POD_ITER int spa_pod_copy_string(const struct spa_pod *pod, size_t maxlen, char *dest) { const char *s = (const char *)SPA_POD_CONTENTS(struct spa_pod_string, pod); if (!spa_pod_is_string(pod) || maxlen < 1) @@ -238,12 +246,12 @@ static inline int spa_pod_copy_string(const struct spa_pod *pod, size_t maxlen, return 0; } -static inline int spa_pod_is_bytes(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_bytes(const struct spa_pod *pod) { return SPA_POD_TYPE(pod) == SPA_TYPE_Bytes; } -static inline int spa_pod_get_bytes(const struct spa_pod *pod, const void **value, uint32_t *len) +SPA_API_POD_ITER int spa_pod_get_bytes(const struct spa_pod *pod, const void **value, uint32_t *len) { if (!spa_pod_is_bytes(pod)) return -EINVAL; @@ -252,13 +260,13 @@ static inline int spa_pod_get_bytes(const struct spa_pod *pod, const void **valu return 0; } -static inline int spa_pod_is_pointer(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_pointer(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Pointer && SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_pointer_body)); } -static inline int spa_pod_get_pointer(const struct spa_pod *pod, uint32_t *type, const void **value) +SPA_API_POD_ITER int spa_pod_get_pointer(const struct spa_pod *pod, uint32_t *type, const void **value) { if (!spa_pod_is_pointer(pod)) return -EINVAL; @@ -267,13 +275,13 @@ static inline int spa_pod_get_pointer(const struct spa_pod *pod, uint32_t *type, return 0; } -static inline int spa_pod_is_fd(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_fd(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Fd && SPA_POD_BODY_SIZE(pod) >= sizeof(int64_t)); } -static inline int spa_pod_get_fd(const struct spa_pod *pod, int64_t *value) +SPA_API_POD_ITER int spa_pod_get_fd(const struct spa_pod *pod, int64_t *value) { if (!spa_pod_is_fd(pod)) return -EINVAL; @@ -281,13 +289,13 @@ static inline int spa_pod_get_fd(const struct spa_pod *pod, int64_t *value) return 0; } -static inline int spa_pod_is_rectangle(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_rectangle(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Rectangle && SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_rectangle)); } -static inline int spa_pod_get_rectangle(const struct spa_pod *pod, struct spa_rectangle *value) +SPA_API_POD_ITER int spa_pod_get_rectangle(const struct spa_pod *pod, struct spa_rectangle *value) { if (!spa_pod_is_rectangle(pod)) return -EINVAL; @@ -295,39 +303,39 @@ static inline int spa_pod_get_rectangle(const struct spa_pod *pod, struct spa_re return 0; } -static inline int spa_pod_is_fraction(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_fraction(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Fraction && SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_fraction)); } -static inline int spa_pod_get_fraction(const struct spa_pod *pod, struct spa_fraction *value) +SPA_API_POD_ITER int spa_pod_get_fraction(const struct spa_pod *pod, struct spa_fraction *value) { spa_return_val_if_fail(spa_pod_is_fraction(pod), -EINVAL); *value = SPA_POD_VALUE(struct spa_pod_fraction, pod); return 0; } -static inline int spa_pod_is_bitmap(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_bitmap(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Bitmap && SPA_POD_BODY_SIZE(pod) >= sizeof(uint8_t)); } -static inline int spa_pod_is_array(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_array(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Array && SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_array_body)); } -static inline void *spa_pod_get_array(const struct spa_pod *pod, uint32_t *n_values) +SPA_API_POD_ITER void *spa_pod_get_array(const struct spa_pod *pod, uint32_t *n_values) { spa_return_val_if_fail(spa_pod_is_array(pod), NULL); *n_values = SPA_POD_ARRAY_N_VALUES(pod); return SPA_POD_ARRAY_VALUES(pod); } -static inline uint32_t spa_pod_copy_array(const struct spa_pod *pod, uint32_t type, +SPA_API_POD_ITER uint32_t spa_pod_copy_array(const struct spa_pod *pod, uint32_t type, void *values, uint32_t max_values) { uint32_t n_values; @@ -339,13 +347,13 @@ static inline uint32_t spa_pod_copy_array(const struct spa_pod *pod, uint32_t ty return n_values; } -static inline int spa_pod_is_choice(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_choice(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Choice && SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_choice_body)); } -static inline struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, uint32_t *n_vals, uint32_t *choice) +SPA_API_POD_ITER struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, uint32_t *n_vals, uint32_t *choice) { if (pod->type == SPA_TYPE_Choice) { *n_vals = SPA_POD_CHOICE_N_VALUES(pod); @@ -359,34 +367,34 @@ static inline struct spa_pod *spa_pod_get_values(const struct spa_pod *pod, uint } } -static inline int spa_pod_is_struct(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_struct(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Struct); } -static inline int spa_pod_is_object(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_object(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Object && SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_object_body)); } -static inline bool spa_pod_is_object_type(const struct spa_pod *pod, uint32_t type) +SPA_API_POD_ITER bool spa_pod_is_object_type(const struct spa_pod *pod, uint32_t type) { return (pod && spa_pod_is_object(pod) && SPA_POD_OBJECT_TYPE(pod) == type); } -static inline bool spa_pod_is_object_id(const struct spa_pod *pod, uint32_t id) +SPA_API_POD_ITER bool spa_pod_is_object_id(const struct spa_pod *pod, uint32_t id) { return (pod && spa_pod_is_object(pod) && SPA_POD_OBJECT_ID(pod) == id); } -static inline int spa_pod_is_sequence(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_sequence(const struct spa_pod *pod) { return (SPA_POD_TYPE(pod) == SPA_TYPE_Sequence && SPA_POD_BODY_SIZE(pod) >= sizeof(struct spa_pod_sequence_body)); } -static inline const struct spa_pod_prop *spa_pod_object_find_prop(const struct spa_pod_object *pod, +SPA_API_POD_ITER const struct spa_pod_prop *spa_pod_object_find_prop(const struct spa_pod_object *pod, const struct spa_pod_prop *start, uint32_t key) { const struct spa_pod_prop *first, *res; @@ -406,7 +414,7 @@ static inline const struct spa_pod_prop *spa_pod_object_find_prop(const struct s return NULL; } -static inline const struct spa_pod_prop *spa_pod_find_prop(const struct spa_pod *pod, +SPA_API_POD_ITER const struct spa_pod_prop *spa_pod_find_prop(const struct spa_pod *pod, const struct spa_pod_prop *start, uint32_t key) { if (!spa_pod_is_object(pod)) @@ -414,7 +422,7 @@ static inline const struct spa_pod_prop *spa_pod_find_prop(const struct spa_pod return spa_pod_object_find_prop((const struct spa_pod_object *)pod, start, key); } -static inline int spa_pod_object_fixate(struct spa_pod_object *pod) +SPA_API_POD_ITER int spa_pod_object_fixate(struct spa_pod_object *pod) { struct spa_pod_prop *res; SPA_POD_OBJECT_FOREACH(pod, res) { @@ -425,14 +433,14 @@ static inline int spa_pod_object_fixate(struct spa_pod_object *pod) return 0; } -static inline int spa_pod_fixate(struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_fixate(struct spa_pod *pod) { if (!spa_pod_is_object(pod)) return -EINVAL; return spa_pod_object_fixate((struct spa_pod_object *)pod); } -static inline int spa_pod_object_is_fixated(const struct spa_pod_object *pod) +SPA_API_POD_ITER int spa_pod_object_is_fixated(const struct spa_pod_object *pod) { struct spa_pod_prop *res; SPA_POD_OBJECT_FOREACH(pod, res) { @@ -443,7 +451,7 @@ static inline int spa_pod_object_is_fixated(const struct spa_pod_object *pod) return 1; } -static inline int spa_pod_object_has_props(const struct spa_pod_object *pod) +SPA_API_POD_ITER int spa_pod_object_has_props(const struct spa_pod_object *pod) { struct spa_pod_prop *res; SPA_POD_OBJECT_FOREACH(pod, res) @@ -451,7 +459,7 @@ static inline int spa_pod_object_has_props(const struct spa_pod_object *pod) return 0; } -static inline int spa_pod_is_fixated(const struct spa_pod *pod) +SPA_API_POD_ITER int spa_pod_is_fixated(const struct spa_pod *pod) { if (!spa_pod_is_object(pod)) return -EINVAL; diff --git a/spa/include/spa/pod/parser.h b/spa/include/spa/pod/parser.h index 083f9117..d2aa206b 100644 --- a/spa/include/spa/pod/parser.h +++ b/spa/include/spa/pod/parser.h @@ -15,6 +15,14 @@ extern "C" { #include <spa/pod/iter.h> #include <spa/pod/vararg.h> +#ifndef SPA_API_POD_PARSER + #ifdef SPA_API_IMPL + #define SPA_API_POD_PARSER SPA_API_IMPL + #else + #define SPA_API_POD_PARSER static inline + #endif +#endif + /** * \addtogroup spa_pod * \{ @@ -33,33 +41,33 @@ struct spa_pod_parser { struct spa_pod_parser_state state; }; -#define SPA_POD_PARSER_INIT(buffer,size) ((struct spa_pod_parser){ (buffer), (size), 0, {0} }) +#define SPA_POD_PARSER_INIT(buffer,size) ((struct spa_pod_parser){ (buffer), (size), 0, {0,0,NULL}}) -static inline void spa_pod_parser_init(struct spa_pod_parser *parser, +SPA_API_POD_PARSER void spa_pod_parser_init(struct spa_pod_parser *parser, const void *data, uint32_t size) { *parser = SPA_POD_PARSER_INIT(data, size); } -static inline void spa_pod_parser_pod(struct spa_pod_parser *parser, +SPA_API_POD_PARSER void spa_pod_parser_pod(struct spa_pod_parser *parser, const struct spa_pod *pod) { spa_pod_parser_init(parser, pod, SPA_POD_SIZE(pod)); } -static inline void +SPA_API_POD_PARSER void spa_pod_parser_get_state(struct spa_pod_parser *parser, struct spa_pod_parser_state *state) { *state = parser->state; } -static inline void +SPA_API_POD_PARSER void spa_pod_parser_reset(struct spa_pod_parser *parser, struct spa_pod_parser_state *state) { parser->state = *state; } -static inline struct spa_pod * +SPA_API_POD_PARSER struct spa_pod * spa_pod_parser_deref(struct spa_pod_parser *parser, uint32_t offset, uint32_t size) { /* Cast to uint64_t to avoid wraparound. Add 8 for the pod itself. */ @@ -78,12 +86,12 @@ spa_pod_parser_deref(struct spa_pod_parser *parser, uint32_t offset, uint32_t si return NULL; } -static inline struct spa_pod *spa_pod_parser_frame(struct spa_pod_parser *parser, struct spa_pod_frame *frame) +SPA_API_POD_PARSER struct spa_pod *spa_pod_parser_frame(struct spa_pod_parser *parser, struct spa_pod_frame *frame) { return SPA_PTROFF(parser->data, frame->offset, struct spa_pod); } -static inline void spa_pod_parser_push(struct spa_pod_parser *parser, +SPA_API_POD_PARSER void spa_pod_parser_push(struct spa_pod_parser *parser, struct spa_pod_frame *frame, const struct spa_pod *pod, uint32_t offset) { frame->pod = *pod; @@ -93,19 +101,19 @@ static inline void spa_pod_parser_push(struct spa_pod_parser *parser, parser->state.frame = frame; } -static inline struct spa_pod *spa_pod_parser_current(struct spa_pod_parser *parser) +SPA_API_POD_PARSER struct spa_pod *spa_pod_parser_current(struct spa_pod_parser *parser) { struct spa_pod_frame *f = parser->state.frame; uint32_t size = f ? f->offset + SPA_POD_SIZE(&f->pod) : parser->size; return spa_pod_parser_deref(parser, parser->state.offset, size); } -static inline void spa_pod_parser_advance(struct spa_pod_parser *parser, const struct spa_pod *pod) +SPA_API_POD_PARSER void spa_pod_parser_advance(struct spa_pod_parser *parser, const struct spa_pod *pod) { parser->state.offset += SPA_ROUND_UP_N(SPA_POD_SIZE(pod), 8); } -static inline struct spa_pod *spa_pod_parser_next(struct spa_pod_parser *parser) +SPA_API_POD_PARSER struct spa_pod *spa_pod_parser_next(struct spa_pod_parser *parser) { struct spa_pod *pod = spa_pod_parser_current(parser); if (pod) @@ -113,7 +121,7 @@ static inline struct spa_pod *spa_pod_parser_next(struct spa_pod_parser *parser) return pod; } -static inline int spa_pod_parser_pop(struct spa_pod_parser *parser, +SPA_API_POD_PARSER int spa_pod_parser_pop(struct spa_pod_parser *parser, struct spa_pod_frame *frame) { parser->state.frame = frame->parent; @@ -121,7 +129,7 @@ static inline int spa_pod_parser_pop(struct spa_pod_parser *parser, return 0; } -static inline int spa_pod_parser_get_bool(struct spa_pod_parser *parser, bool *value) +SPA_API_POD_PARSER int spa_pod_parser_get_bool(struct spa_pod_parser *parser, bool *value) { int res = -EPIPE; const struct spa_pod *pod = spa_pod_parser_current(parser); @@ -130,7 +138,7 @@ static inline int spa_pod_parser_get_bool(struct spa_pod_parser *parser, bool *v return res; } -static inline int spa_pod_parser_get_id(struct spa_pod_parser *parser, uint32_t *value) +SPA_API_POD_PARSER int spa_pod_parser_get_id(struct spa_pod_parser *parser, uint32_t *value) { int res = -EPIPE; const struct spa_pod *pod = spa_pod_parser_current(parser); @@ -139,7 +147,7 @@ static inline int spa_pod_parser_get_id(struct spa_pod_parser *parser, uint32_t return res; } -static inline int spa_pod_parser_get_int(struct spa_pod_parser *parser, int32_t *value) +SPA_API_POD_PARSER int spa_pod_parser_get_int(struct spa_pod_parser *parser, int32_t *value) { int res = -EPIPE; const struct spa_pod *pod = spa_pod_parser_current(parser); @@ -148,7 +156,7 @@ static inline int spa_pod_parser_get_int(struct spa_pod_parser *parser, int32_t return res; } -static inline int spa_pod_parser_get_long(struct spa_pod_parser *parser, int64_t *value) +SPA_API_POD_PARSER int spa_pod_parser_get_long(struct spa_pod_parser *parser, int64_t *value) { int res = -EPIPE; const struct spa_pod *pod = spa_pod_parser_current(parser); @@ -157,7 +165,7 @@ static inline int spa_pod_parser_get_long(struct spa_pod_parser *parser, int64_t return res; } -static inline int spa_pod_parser_get_float(struct spa_pod_parser *parser, float *value) +SPA_API_POD_PARSER int spa_pod_parser_get_float(struct spa_pod_parser *parser, float *value) { int res = -EPIPE; const struct spa_pod *pod = spa_pod_parser_current(parser); @@ -166,7 +174,7 @@ static inline int spa_pod_parser_get_float(struct spa_pod_parser *parser, float return res; } -static inline int spa_pod_parser_get_double(struct spa_pod_parser *parser, double *value) +SPA_API_POD_PARSER int spa_pod_parser_get_double(struct spa_pod_parser *parser, double *value) { int res = -EPIPE; const struct spa_pod *pod = spa_pod_parser_current(parser); @@ -175,7 +183,7 @@ static inline int spa_pod_parser_get_double(struct spa_pod_parser *parser, doubl return res; } -static inline int spa_pod_parser_get_string(struct spa_pod_parser *parser, const char **value) +SPA_API_POD_PARSER int spa_pod_parser_get_string(struct spa_pod_parser *parser, const char **value) { int res = -EPIPE; const struct spa_pod *pod = spa_pod_parser_current(parser); @@ -184,7 +192,7 @@ static inline int spa_pod_parser_get_string(struct spa_pod_parser *parser, const return res; } -static inline int spa_pod_parser_get_bytes(struct spa_pod_parser *parser, const void **value, uint32_t *len) +SPA_API_POD_PARSER int spa_pod_parser_get_bytes(struct spa_pod_parser *parser, const void **value, uint32_t *len) { int res = -EPIPE; const struct spa_pod *pod = spa_pod_parser_current(parser); @@ -193,7 +201,7 @@ static inline int spa_pod_parser_get_bytes(struct spa_pod_parser *parser, const return res; } -static inline int spa_pod_parser_get_pointer(struct spa_pod_parser *parser, uint32_t *type, const void **value) +SPA_API_POD_PARSER int spa_pod_parser_get_pointer(struct spa_pod_parser *parser, uint32_t *type, const void **value) { int res = -EPIPE; const struct spa_pod *pod = spa_pod_parser_current(parser); @@ -202,7 +210,7 @@ static inline int spa_pod_parser_get_pointer(struct spa_pod_parser *parser, uint return res; } -static inline int spa_pod_parser_get_fd(struct spa_pod_parser *parser, int64_t *value) +SPA_API_POD_PARSER int spa_pod_parser_get_fd(struct spa_pod_parser *parser, int64_t *value) { int res = -EPIPE; const struct spa_pod *pod = spa_pod_parser_current(parser); @@ -211,7 +219,7 @@ static inline int spa_pod_parser_get_fd(struct spa_pod_parser *parser, int64_t * return res; } -static inline int spa_pod_parser_get_rectangle(struct spa_pod_parser *parser, struct spa_rectangle *value) +SPA_API_POD_PARSER int spa_pod_parser_get_rectangle(struct spa_pod_parser *parser, struct spa_rectangle *value) { int res = -EPIPE; const struct spa_pod *pod = spa_pod_parser_current(parser); @@ -220,7 +228,7 @@ static inline int spa_pod_parser_get_rectangle(struct spa_pod_parser *parser, st return res; } -static inline int spa_pod_parser_get_fraction(struct spa_pod_parser *parser, struct spa_fraction *value) +SPA_API_POD_PARSER int spa_pod_parser_get_fraction(struct spa_pod_parser *parser, struct spa_fraction *value) { int res = -EPIPE; const struct spa_pod *pod = spa_pod_parser_current(parser); @@ -229,7 +237,7 @@ static inline int spa_pod_parser_get_fraction(struct spa_pod_parser *parser, str return res; } -static inline int spa_pod_parser_get_pod(struct spa_pod_parser *parser, struct spa_pod **value) +SPA_API_POD_PARSER int spa_pod_parser_get_pod(struct spa_pod_parser *parser, struct spa_pod **value) { struct spa_pod *pod = spa_pod_parser_current(parser); if (pod == NULL) @@ -238,7 +246,7 @@ static inline int spa_pod_parser_get_pod(struct spa_pod_parser *parser, struct s spa_pod_parser_advance(parser, pod); return 0; } -static inline int spa_pod_parser_push_struct(struct spa_pod_parser *parser, +SPA_API_POD_PARSER int spa_pod_parser_push_struct(struct spa_pod_parser *parser, struct spa_pod_frame *frame) { const struct spa_pod *pod = spa_pod_parser_current(parser); @@ -251,7 +259,7 @@ static inline int spa_pod_parser_push_struct(struct spa_pod_parser *parser, return 0; } -static inline int spa_pod_parser_push_object(struct spa_pod_parser *parser, +SPA_API_POD_PARSER int spa_pod_parser_push_object(struct spa_pod_parser *parser, struct spa_pod_frame *frame, uint32_t type, uint32_t *id) { const struct spa_pod *pod = spa_pod_parser_current(parser); @@ -268,7 +276,7 @@ static inline int spa_pod_parser_push_object(struct spa_pod_parser *parser, return 0; } -static inline bool spa_pod_parser_can_collect(const struct spa_pod *pod, char type) +SPA_API_POD_PARSER bool spa_pod_parser_can_collect(const struct spa_pod *pod, char type) { if (pod == NULL) return false; @@ -443,7 +451,7 @@ do { \ } \ } while(false) -static inline int spa_pod_parser_getv(struct spa_pod_parser *parser, va_list args) +SPA_API_POD_PARSER int spa_pod_parser_getv(struct spa_pod_parser *parser, va_list args) { struct spa_pod_frame *f = parser->state.frame; uint32_t ftype = f ? f->pod.type : (uint32_t)SPA_TYPE_Struct; @@ -496,7 +504,7 @@ static inline int spa_pod_parser_getv(struct spa_pod_parser *parser, va_list arg return count; } -static inline int spa_pod_parser_get(struct spa_pod_parser *parser, ...) +SPA_API_POD_PARSER int spa_pod_parser_get(struct spa_pod_parser *parser, ...) { int res; va_list args; diff --git a/spa/include/spa/support/cpu.h b/spa/include/spa/support/cpu.h index 350dee78..ce8551e7 100644 --- a/spa/include/spa/support/cpu.h +++ b/spa/include/spa/support/cpu.h @@ -10,10 +10,19 @@ extern "C" { #endif #include <stdarg.h> +#include <errno.h> #include <spa/utils/defs.h> #include <spa/utils/hook.h> +#ifndef SPA_API_CPU + #ifdef SPA_API_IMPL + #define SPA_API_CPU SPA_API_IMPL + #else + #define SPA_API_CPU static inline + #endif +#endif + /** \defgroup spa_cpu CPU * Querying CPU properties */ @@ -68,6 +77,9 @@ struct spa_cpu { struct spa_interface iface; }; #define SPA_CPU_FLAG_NEON (1 << 5) #define SPA_CPU_FLAG_ARMV8 (1 << 6) +/* RISCV specific */ +#define SPA_CPU_FLAG_RISCV_V (1 << 0) + #define SPA_CPU_FORCE_AUTODETECT ((uint32_t)-1) #define SPA_CPU_VM_NONE (0) @@ -87,7 +99,7 @@ struct spa_cpu { struct spa_interface iface; }; #define SPA_CPU_VM_ACRN (1 << 13) #define SPA_CPU_VM_POWERVM (1 << 14) -static inline const char *spa_cpu_vm_type_to_string(uint32_t vm_type) +SPA_API_CPU const char *spa_cpu_vm_type_to_string(uint32_t vm_type) { switch(vm_type) { case SPA_CPU_VM_NONE: @@ -156,21 +168,30 @@ struct spa_cpu_methods { int (*zero_denormals) (void *object, bool enable); }; -#define spa_cpu_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - struct spa_cpu *_c = o; \ - spa_interface_call_res(&_c->iface, \ - struct spa_cpu_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) -#define spa_cpu_get_flags(c) spa_cpu_method(c, get_flags, 0) -#define spa_cpu_force_flags(c,f) spa_cpu_method(c, force_flags, 0, f) -#define spa_cpu_get_count(c) spa_cpu_method(c, get_count, 0) -#define spa_cpu_get_max_align(c) spa_cpu_method(c, get_max_align, 0) -#define spa_cpu_get_vm_type(c) spa_cpu_method(c, get_vm_type, 1) -#define spa_cpu_zero_denormals(c,e) spa_cpu_method(c, zero_denormals, 2, e) +SPA_API_CPU uint32_t spa_cpu_get_flags(struct spa_cpu *c) +{ + return spa_api_method_r(uint32_t, 0, spa_cpu, &c->iface, get_flags, 0); +} +SPA_API_CPU int spa_cpu_force_flags(struct spa_cpu *c, uint32_t flags) +{ + return spa_api_method_r(int, -ENOTSUP, spa_cpu, &c->iface, force_flags, 0, flags); +} +SPA_API_CPU uint32_t spa_cpu_get_count(struct spa_cpu *c) +{ + return spa_api_method_r(uint32_t, 0, spa_cpu, &c->iface, get_count, 0); +} +SPA_API_CPU uint32_t spa_cpu_get_max_align(struct spa_cpu *c) +{ + return spa_api_method_r(uint32_t, 0, spa_cpu, &c->iface, get_max_align, 0); +} +SPA_API_CPU uint32_t spa_cpu_get_vm_type(struct spa_cpu *c) +{ + return spa_api_method_r(uint32_t, 0, spa_cpu, &c->iface, get_vm_type, 1); +} +SPA_API_CPU int spa_cpu_zero_denormals(struct spa_cpu *c, bool enable) +{ + return spa_api_method_r(int, -ENOTSUP, spa_cpu, &c->iface, zero_denormals, 2, enable); +} /** keys can be given when initializing the cpu handle */ #define SPA_KEY_CPU_FORCE "cpu.force" /**< force cpu flags */ diff --git a/spa/include/spa/support/dbus.h b/spa/include/spa/support/dbus.h index 83deb1d6..3908bfe5 100644 --- a/spa/include/spa/support/dbus.h +++ b/spa/include/spa/support/dbus.h @@ -11,6 +11,14 @@ extern "C" { #include <spa/support/loop.h> +#ifndef SPA_API_DBUS + #ifdef SPA_API_IMPL + #define SPA_API_DBUS SPA_API_IMPL + #else + #define SPA_API_DBUS static inline + #endif +#endif + /** \defgroup spa_dbus DBus * DBus communication */ @@ -79,29 +87,27 @@ struct spa_dbus_connection { void *data); }; -#define spa_dbus_connection_call(c,method,vers,...) \ -({ \ - if (SPA_LIKELY(SPA_CALLBACK_CHECK(c,method,vers))) \ - c->method((c), ## __VA_ARGS__); \ -}) - -#define spa_dbus_connection_call_vp(c,method,vers,...) \ -({ \ - void *_res = NULL; \ - if (SPA_LIKELY(SPA_CALLBACK_CHECK(c,method,vers))) \ - _res = c->method((c), ## __VA_ARGS__); \ - _res; \ -}) - /** \copydoc spa_dbus_connection.get * \sa spa_dbus_connection.get */ -#define spa_dbus_connection_get(c) spa_dbus_connection_call_vp(c,get,0) +SPA_API_DBUS void *spa_dbus_connection_get(struct spa_dbus_connection *conn) +{ + return spa_api_func_r(void *, NULL, conn, get, 0); +} /** \copydoc spa_dbus_connection.destroy * \sa spa_dbus_connection.destroy */ -#define spa_dbus_connection_destroy(c) spa_dbus_connection_call(c,destroy,0) +SPA_API_DBUS void spa_dbus_connection_destroy(struct spa_dbus_connection *conn) +{ + spa_api_func_v(conn, destroy, 0); +} /** \copydoc spa_dbus_connection.add_listener * \sa spa_dbus_connection.add_listener */ -#define spa_dbus_connection_add_listener(c,...) spa_dbus_connection_call(c,add_listener,1,__VA_ARGS__) +SPA_API_DBUS void spa_dbus_connection_add_listener(struct spa_dbus_connection *conn, + struct spa_hook *listener, + const struct spa_dbus_connection_events *events, + void *data) +{ + spa_api_func_v(conn, add_listener, 1, listener, events, data); +} struct spa_dbus_methods { #define SPA_VERSION_DBUS_METHODS 0 @@ -126,14 +132,11 @@ struct spa_dbus_methods { /** \copydoc spa_dbus_methods.get_connection * \sa spa_dbus_methods.get_connection */ -static inline struct spa_dbus_connection * +SPA_API_DBUS struct spa_dbus_connection * spa_dbus_get_connection(struct spa_dbus *dbus, enum spa_dbus_type type) { - struct spa_dbus_connection *res = NULL; - spa_interface_call_res(&dbus->iface, - struct spa_dbus_methods, res, - get_connection, 0, type); - return res; + return spa_api_method_r(struct spa_dbus_connection *, NULL, + spa_dbus, &dbus->iface, get_connection, 0, type); } /** diff --git a/spa/include/spa/support/i18n.h b/spa/include/spa/support/i18n.h index 56660e68..3b258873 100644 --- a/spa/include/spa/support/i18n.h +++ b/spa/include/spa/support/i18n.h @@ -12,6 +12,14 @@ extern "C" { #include <spa/utils/hook.h> #include <spa/utils/defs.h> +#ifndef SPA_API_I18N + #ifdef SPA_API_IMPL + #define SPA_API_I18N SPA_API_IMPL + #else + #define SPA_API_I18N static inline + #endif +#endif + /** \defgroup spa_i18n I18N * Gettext interface */ @@ -53,27 +61,19 @@ struct spa_i18n_methods { }; SPA_FORMAT_ARG_FUNC(2) -static inline const char * +SPA_API_I18N const char * spa_i18n_text(struct spa_i18n *i18n, const char *msgid) { - const char *res = msgid; - if (SPA_LIKELY(i18n != NULL)) - spa_interface_call_res(&i18n->iface, - struct spa_i18n_methods, res, - text, 0, msgid); - return res; + return spa_api_method_null_r(const char *, msgid, spa_i18n, i18n, &i18n->iface, + text, 0, msgid); } -static inline const char * +SPA_API_I18N const char * spa_i18n_ntext(struct spa_i18n *i18n, const char *msgid, const char *msgid_plural, unsigned long int n) { - const char *res = n == 1 ? msgid : msgid_plural; - if (SPA_LIKELY(i18n != NULL)) - spa_interface_call_res(&i18n->iface, - struct spa_i18n_methods, res, - ntext, 0, msgid, msgid_plural, n); - return res; + return spa_api_method_null_r(const char *, n == 1 ? msgid : msgid_plural, + spa_i18n, i18n, &i18n->iface, ntext, 0, msgid, msgid_plural, n); } /** diff --git a/spa/include/spa/support/log.h b/spa/include/spa/support/log.h index fba50e75..e1441440 100644 --- a/spa/include/spa/support/log.h +++ b/spa/include/spa/support/log.h @@ -15,6 +15,14 @@ extern "C" { #include <spa/utils/defs.h> #include <spa/utils/hook.h> +#ifndef SPA_API_LOG + #ifdef SPA_API_IMPL + #define SPA_API_LOG SPA_API_IMPL + #else + #define SPA_API_LOG static inline + #endif +#endif + /** \defgroup spa_log Log * Logging interface */ @@ -213,7 +221,7 @@ struct spa_log_methods { #define SPA_LOG_TOPIC(v, t) \ (struct spa_log_topic){ .version = (v), .topic = (t)} -static inline void spa_log_topic_init(struct spa_log *log, struct spa_log_topic *topic) +SPA_API_LOG void spa_log_topic_init(struct spa_log *log, struct spa_log_topic *topic) { if (SPA_UNLIKELY(!log)) return; @@ -221,7 +229,7 @@ static inline void spa_log_topic_init(struct spa_log *log, struct spa_log_topic spa_interface_call(&log->iface, struct spa_log_methods, topic_init, 1, topic); } -static inline bool spa_log_level_topic_enabled(const struct spa_log *log, +SPA_API_LOG bool spa_log_level_topic_enabled(const struct spa_log *log, const struct spa_log_topic *topic, enum spa_log_level level) { @@ -255,20 +263,22 @@ static inline bool spa_log_level_topic_enabled(const struct spa_log *log, }) /* Transparently calls to version 0 logv if v1 is not supported */ -#define spa_log_logtv(l,lev,topic,...) \ -({ \ - struct spa_log *_l = l; \ - if (SPA_UNLIKELY(spa_log_level_topic_enabled(_l, topic, lev))) { \ - struct spa_interface *_if = &_l->iface; \ - if (!spa_interface_call(_if, \ - struct spa_log_methods, logtv, 1, \ - lev, topic, \ - __VA_ARGS__)) \ - spa_interface_call(_if, \ - struct spa_log_methods, logv, 0, \ - lev, __VA_ARGS__); \ - } \ -}) +SPA_PRINTF_FUNC(7, 0) +SPA_API_LOG void spa_log_logtv(struct spa_log *l, enum spa_log_level level, + const struct spa_log_topic *topic, const char *file, int line, + const char *func, const char *fmt, va_list args) +{ + if (SPA_UNLIKELY(spa_log_level_topic_enabled(l, topic, level))) { + struct spa_interface *i = &l->iface; + if (!spa_interface_call(i, + struct spa_log_methods, logtv, 1, + level, topic, + file, line, func, fmt, args)) + spa_interface_call(i, + struct spa_log_methods, logv, 0, + level, file, line, func, fmt, args); + } +} #define spa_logt_lev(l,lev,t,...) \ spa_log_logt(l,lev,t,__FILE__,__LINE__,__func__,__VA_ARGS__) @@ -369,7 +379,8 @@ static inline bool spa_log_level_topic_enabled(const struct spa_log *log, * colors even when not logging to a terminal */ #define SPA_KEY_LOG_FILE "log.file" /**< log to the specified file instead of * stderr. */ -#define SPA_KEY_LOG_TIMESTAMP "log.timestamp" /**< log timestamps */ +#define SPA_KEY_LOG_TIMESTAMP "log.timestamp" /**< log timestamp type (local, realtime, monotonic, monotonic-raw). + * boolean true means local. */ #define SPA_KEY_LOG_LINE "log.line" /**< log file and line numbers */ #define SPA_KEY_LOG_PATTERNS "log.patterns" /**< Spa:String:JSON array of [ {"pattern" : level}, ... ] */ diff --git a/spa/include/spa/support/loop.h b/spa/include/spa/support/loop.h index 7dc55f3a..520a465d 100644 --- a/spa/include/spa/support/loop.h +++ b/spa/include/spa/support/loop.h @@ -9,10 +9,20 @@ extern "C" { #endif +#include <errno.h> + #include <spa/utils/defs.h> #include <spa/utils/hook.h> #include <spa/support/system.h> +#ifndef SPA_API_LOOP + #ifdef SPA_API_IMPL + #define SPA_API_LOOP SPA_API_IMPL + #else + #define SPA_API_LOOP static inline + #endif +#endif + /** \defgroup spa_loop Loop * Event loop interface */ @@ -125,21 +135,29 @@ struct spa_loop_methods { void *user_data); }; -#define spa_loop_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - struct spa_loop *_o = o; \ - spa_interface_call_res(&_o->iface, \ - struct spa_loop_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define spa_loop_add_source(l,...) spa_loop_method(l,add_source,0,##__VA_ARGS__) -#define spa_loop_update_source(l,...) spa_loop_method(l,update_source,0,##__VA_ARGS__) -#define spa_loop_remove_source(l,...) spa_loop_method(l,remove_source,0,##__VA_ARGS__) -#define spa_loop_invoke(l,...) spa_loop_method(l,invoke,0,##__VA_ARGS__) - +SPA_API_LOOP int spa_loop_add_source(struct spa_loop *object, struct spa_source *source) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop, &object->iface, add_source, 0, source); +} +SPA_API_LOOP int spa_loop_update_source(struct spa_loop *object, struct spa_source *source) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop, &object->iface, update_source, 0, source); +} +SPA_API_LOOP int spa_loop_remove_source(struct spa_loop *object, struct spa_source *source) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop, &object->iface, remove_source, 0, source); +} +SPA_API_LOOP int spa_loop_invoke(struct spa_loop *object, + spa_invoke_func_t func, uint32_t seq, const void *data, + size_t size, bool block, void *user_data) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop, &object->iface, invoke, 0, func, seq, data, + size, block, user_data); +} /** Control hooks. These hooks can't be removed from their * callbacks and must be removed from a safe place (when the loop @@ -155,24 +173,42 @@ struct spa_loop_control_hooks { void (*after) (void *data); }; -#define spa_loop_control_hook_before(l) \ -({ \ - struct spa_hook_list *_l = l; \ - struct spa_hook *_h; \ - spa_list_for_each_reverse(_h, &_l->list, link) \ - spa_callbacks_call_fast(&_h->cb, struct spa_loop_control_hooks, before, 0); \ -}) - -#define spa_loop_control_hook_after(l) \ -({ \ - struct spa_hook_list *_l = l; \ - struct spa_hook *_h; \ - spa_list_for_each(_h, &_l->list, link) \ - spa_callbacks_call_fast(&_h->cb, struct spa_loop_control_hooks, after, 0); \ -}) +SPA_API_LOOP void spa_loop_control_hook_before(struct spa_hook_list *l) +{ + struct spa_hook *h; + spa_list_for_each_reverse(h, &l->list, link) + spa_callbacks_call_fast(&h->cb, struct spa_loop_control_hooks, before, 0); +} + +SPA_API_LOOP void spa_loop_control_hook_after(struct spa_hook_list *l) +{ + struct spa_hook *h; + spa_list_for_each(h, &l->list, link) + spa_callbacks_call_fast(&h->cb, struct spa_loop_control_hooks, after, 0); +} /** * Control an event loop + * + * The event loop control function provide API to run the event loop. + * + * The below (pseudo)code is a minimal example outlining the use of the loop + * control: + * \code{.c} + * spa_loop_control_enter(loop); + * while (running) { + * spa_loop_control_iterate(loop, -1); + * } + * spa_loop_control_leave(loop); + * \endcode + * + * It is also possible to add the loop to an existing event loop by using the + * spa_loop_control_get_fd() call. This fd will become readable when activity + * has been detected on the sources in the loop. spa_loop_control_iterate() with + * a 0 timeout should be called to process the pending sources. + * + * spa_loop_control_enter() and spa_loop_control_leave() should be called once + * from the thread that will run the iterate() function. */ struct spa_loop_control_methods { /* the version of this structure. This can be used to expand this @@ -180,10 +216,19 @@ struct spa_loop_control_methods { #define SPA_VERSION_LOOP_CONTROL_METHODS 1 uint32_t version; + /** get the loop fd + * \param object the control to query + * + * Get the fd of this loop control. This fd will be readable when a + * source in the loop has activity. The user should call iterate() + * with a 0 timeout to schedule one iteration of the loop and dispatch + * the sources. + * \return the fd of the loop + */ int (*get_fd) (void *object); /** Add a hook - * \param ctrl the control to change + * \param object the control to change * \param hooks the hooks to add * * Adds hooks to the loop controlled by \a ctrl. @@ -194,18 +239,19 @@ struct spa_loop_control_methods { void *data); /** Enter a loop - * \param ctrl the control + * \param object the control * - * Start an iteration of the loop. This function should be called - * before calling iterate and is typically used to capture the thread - * that this loop will run in. + * This function should be called before calling iterate and is + * typically used to capture the thread that this loop will run in. + * It should ideally be called once from the thread that will run + * the loop. */ void (*enter) (void *object); /** Leave a loop - * \param ctrl the control + * \param object the control * - * Ends the iteration of a loop. This should be called after calling - * iterate. + * It should ideally be called once after calling iterate when the loop + * will no longer be iterated from the thread that called enter(). */ void (*leave) (void *object); @@ -231,42 +277,43 @@ struct spa_loop_control_methods { int (*check) (void *object); }; -#define spa_loop_control_method_v(o,method,version,...) \ -({ \ - struct spa_loop_control *_o = o; \ - spa_interface_call(&_o->iface, \ - struct spa_loop_control_methods, \ - method, version, ##__VA_ARGS__); \ -}) - -#define spa_loop_control_method_r(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - struct spa_loop_control *_o = o; \ - spa_interface_call_res(&_o->iface, \ - struct spa_loop_control_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define spa_loop_control_method_fast_r(o,method,version,...) \ -({ \ - int _res; \ - struct spa_loop_control *_o = o; \ - spa_interface_call_fast_res(&_o->iface, \ - struct spa_loop_control_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define spa_loop_control_get_fd(l) spa_loop_control_method_r(l,get_fd,0) -#define spa_loop_control_add_hook(l,...) spa_loop_control_method_v(l,add_hook,0,__VA_ARGS__) -#define spa_loop_control_enter(l) spa_loop_control_method_v(l,enter,0) -#define spa_loop_control_leave(l) spa_loop_control_method_v(l,leave,0) -#define spa_loop_control_iterate(l,...) spa_loop_control_method_r(l,iterate,0,__VA_ARGS__) -#define spa_loop_control_check(l) spa_loop_control_method_r(l,check,1) - -#define spa_loop_control_iterate_fast(l,...) spa_loop_control_method_fast_r(l,iterate,0,__VA_ARGS__) +SPA_API_LOOP int spa_loop_control_get_fd(struct spa_loop_control *object) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop_control, &object->iface, get_fd, 0); +} +SPA_API_LOOP void spa_loop_control_add_hook(struct spa_loop_control *object, + struct spa_hook *hook, const struct spa_loop_control_hooks *hooks, + void *data) +{ + spa_api_method_v(spa_loop_control, &object->iface, add_hook, 0, + hook, hooks, data); +} +SPA_API_LOOP void spa_loop_control_enter(struct spa_loop_control *object) +{ + spa_api_method_v(spa_loop_control, &object->iface, enter, 0); +} +SPA_API_LOOP void spa_loop_control_leave(struct spa_loop_control *object) +{ + spa_api_method_v(spa_loop_control, &object->iface, leave, 0); +} +SPA_API_LOOP int spa_loop_control_iterate(struct spa_loop_control *object, + int timeout) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop_control, &object->iface, iterate, 0, timeout); +} +SPA_API_LOOP int spa_loop_control_iterate_fast(struct spa_loop_control *object, + int timeout) +{ + return spa_api_method_fast_r(int, -ENOTSUP, + spa_loop_control, &object->iface, iterate, 0, timeout); +} +SPA_API_LOOP int spa_loop_control_check(struct spa_loop_control *object) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop_control, &object->iface, check, 1); +} typedef void (*spa_source_io_func_t) (void *data, int fd, uint32_t mask); typedef void (*spa_source_idle_func_t) (void *data); @@ -317,44 +364,71 @@ struct spa_loop_utils_methods { void (*destroy_source) (void *object, struct spa_source *source); }; -#define spa_loop_utils_method_v(o,method,version,...) \ -({ \ - struct spa_loop_utils *_o = o; \ - spa_interface_call(&_o->iface, \ - struct spa_loop_utils_methods, \ - method, version, ##__VA_ARGS__); \ -}) - -#define spa_loop_utils_method_r(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - struct spa_loop_utils *_o = o; \ - spa_interface_call_res(&_o->iface, \ - struct spa_loop_utils_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) -#define spa_loop_utils_method_s(o,method,version,...) \ -({ \ - struct spa_source *_res = NULL; \ - struct spa_loop_utils *_o = o; \ - spa_interface_call_res(&_o->iface, \ - struct spa_loop_utils_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - - -#define spa_loop_utils_add_io(l,...) spa_loop_utils_method_s(l,add_io,0,__VA_ARGS__) -#define spa_loop_utils_update_io(l,...) spa_loop_utils_method_r(l,update_io,0,__VA_ARGS__) -#define spa_loop_utils_add_idle(l,...) spa_loop_utils_method_s(l,add_idle,0,__VA_ARGS__) -#define spa_loop_utils_enable_idle(l,...) spa_loop_utils_method_r(l,enable_idle,0,__VA_ARGS__) -#define spa_loop_utils_add_event(l,...) spa_loop_utils_method_s(l,add_event,0,__VA_ARGS__) -#define spa_loop_utils_signal_event(l,...) spa_loop_utils_method_r(l,signal_event,0,__VA_ARGS__) -#define spa_loop_utils_add_timer(l,...) spa_loop_utils_method_s(l,add_timer,0,__VA_ARGS__) -#define spa_loop_utils_update_timer(l,...) spa_loop_utils_method_r(l,update_timer,0,__VA_ARGS__) -#define spa_loop_utils_add_signal(l,...) spa_loop_utils_method_s(l,add_signal,0,__VA_ARGS__) -#define spa_loop_utils_destroy_source(l,...) spa_loop_utils_method_v(l,destroy_source,0,__VA_ARGS__) +SPA_API_LOOP struct spa_source * +spa_loop_utils_add_io(struct spa_loop_utils *object, int fd, uint32_t mask, + bool close, spa_source_io_func_t func, void *data) +{ + return spa_api_method_r(struct spa_source *, NULL, + spa_loop_utils, &object->iface, add_io, 0, fd, mask, close, func, data); +} +SPA_API_LOOP int spa_loop_utils_update_io(struct spa_loop_utils *object, + struct spa_source *source, uint32_t mask) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop_utils, &object->iface, update_io, 0, source, mask); +} +SPA_API_LOOP struct spa_source * +spa_loop_utils_add_idle(struct spa_loop_utils *object, bool enabled, + spa_source_idle_func_t func, void *data) +{ + return spa_api_method_r(struct spa_source *, NULL, + spa_loop_utils, &object->iface, add_idle, 0, enabled, func, data); +} +SPA_API_LOOP int spa_loop_utils_enable_idle(struct spa_loop_utils *object, + struct spa_source *source, bool enabled) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop_utils, &object->iface, enable_idle, 0, source, enabled); +} +SPA_API_LOOP struct spa_source * +spa_loop_utils_add_event(struct spa_loop_utils *object, spa_source_event_func_t func, void *data) +{ + return spa_api_method_r(struct spa_source *, NULL, + spa_loop_utils, &object->iface, add_event, 0, func, data); +} +SPA_API_LOOP int spa_loop_utils_signal_event(struct spa_loop_utils *object, + struct spa_source *source) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop_utils, &object->iface, signal_event, 0, source); +} +SPA_API_LOOP struct spa_source * +spa_loop_utils_add_timer(struct spa_loop_utils *object, spa_source_timer_func_t func, void *data) +{ + return spa_api_method_r(struct spa_source *, NULL, + spa_loop_utils, &object->iface, add_timer, 0, func, data); +} +SPA_API_LOOP int spa_loop_utils_update_timer(struct spa_loop_utils *object, + struct spa_source *source, struct timespec *value, + struct timespec *interval, bool absolute) +{ + return spa_api_method_r(int, -ENOTSUP, + spa_loop_utils, &object->iface, update_timer, 0, source, + value, interval, absolute); +} +SPA_API_LOOP struct spa_source * +spa_loop_utils_add_signal(struct spa_loop_utils *object, int signal_number, + spa_source_signal_func_t func, void *data) +{ + return spa_api_method_r(struct spa_source *, NULL, + spa_loop_utils, &object->iface, add_signal, 0, + signal_number, func, data); +} +SPA_API_LOOP void spa_loop_utils_destroy_source(struct spa_loop_utils *object, + struct spa_source *source) +{ + spa_api_method_v(spa_loop_utils, &object->iface, destroy_source, 0, source); +} /** * \} diff --git a/spa/include/spa/support/plugin-loader.h b/spa/include/spa/support/plugin-loader.h index 9b32ebe1..9540853c 100644 --- a/spa/include/spa/support/plugin-loader.h +++ b/spa/include/spa/support/plugin-loader.h @@ -12,6 +12,14 @@ extern "C" { #include <spa/utils/hook.h> #include <spa/utils/dict.h> +#ifndef SPA_API_PLUGIN_LOADER + #ifdef SPA_API_IMPL + #define SPA_API_PLUGIN_LOADER SPA_API_IMPL + #else + #define SPA_API_PLUGIN_LOADER static inline + #endif +#endif + /** \defgroup spa_plugin_loader Plugin Loader * SPA plugin loader */ @@ -48,26 +56,18 @@ struct spa_plugin_loader_methods { int (*unload)(void *object, struct spa_handle *handle); }; -static inline struct spa_handle * +SPA_API_PLUGIN_LOADER struct spa_handle * spa_plugin_loader_load(struct spa_plugin_loader *loader, const char *factory_name, const struct spa_dict *info) { - struct spa_handle *res = NULL; - if (SPA_LIKELY(loader != NULL)) - spa_interface_call_res(&loader->iface, - struct spa_plugin_loader_methods, res, - load, 0, factory_name, info); - return res; + return spa_api_method_null_r(struct spa_handle *, NULL, spa_plugin_loader, loader, &loader->iface, + load, 0, factory_name, info); } -static inline int +SPA_API_PLUGIN_LOADER int spa_plugin_loader_unload(struct spa_plugin_loader *loader, struct spa_handle *handle) { - int res = -1; - if (SPA_LIKELY(loader != NULL)) - spa_interface_call_res(&loader->iface, - struct spa_plugin_loader_methods, res, - unload, 0, handle); - return res; + return spa_api_method_null_r(int, -1, spa_plugin_loader, loader, &loader->iface, + unload, 0, handle); } /** diff --git a/spa/include/spa/support/plugin.h b/spa/include/spa/support/plugin.h index e2e6d469..576c1950 100644 --- a/spa/include/spa/support/plugin.h +++ b/spa/include/spa/support/plugin.h @@ -9,9 +9,20 @@ extern "C" { #endif +#include <errno.h> + #include <spa/utils/defs.h> +#include <spa/utils/hook.h> #include <spa/utils/dict.h> +#ifndef SPA_API_PLUGIN + #ifdef SPA_API_IMPL + #define SPA_API_PLUGIN SPA_API_IMPL + #else + #define SPA_API_PLUGIN static inline + #endif +#endif + /** * \defgroup spa_handle Plugin Handle * SPA plugin handle and factory interfaces @@ -35,12 +46,12 @@ struct spa_handle { * * \param handle a spa_handle * \param type the interface type - * \param interface result to hold the interface. + * \param iface result to hold the interface. * \return 0 on success * -ENOTSUP when there are no interfaces * -EINVAL when handle or info is NULL */ - int (*get_interface) (struct spa_handle *handle, const char *type, void **interface); + int (*get_interface) (struct spa_handle *handle, const char *type, void **iface); /** * Clean up the memory of \a handle. After this, \a handle should not be used * anymore. @@ -51,8 +62,17 @@ struct spa_handle { int (*clear) (struct spa_handle *handle); }; -#define spa_handle_get_interface(h,...) (h)->get_interface((h),__VA_ARGS__) -#define spa_handle_clear(h) (h)->clear((h)) +SPA_API_PLUGIN int +spa_handle_get_interface(struct spa_handle *object, + const char *type, void **iface) +{ + return spa_api_func_r(int, -ENOTSUP, object, get_interface, 0, type, iface); +} +SPA_API_PLUGIN int +spa_handle_clear(struct spa_handle *object) +{ + return spa_api_func_r(int, -ENOTSUP, object, clear, 0); +} /** * This structure lists the information about available interfaces on @@ -73,7 +93,7 @@ struct spa_support { }; /** Find a support item of the given type */ -static inline void *spa_support_find(const struct spa_support *support, +SPA_API_PLUGIN void *spa_support_find(const struct spa_support *support, uint32_t n_support, const char *type) { @@ -158,9 +178,27 @@ struct spa_handle_factory { uint32_t *index); }; -#define spa_handle_factory_get_size(h,...) (h)->get_size((h),__VA_ARGS__) -#define spa_handle_factory_init(h,...) (h)->init((h),__VA_ARGS__) -#define spa_handle_factory_enum_interface_info(h,...) (h)->enum_interface_info((h),__VA_ARGS__) +SPA_API_PLUGIN size_t +spa_handle_factory_get_size(const struct spa_handle_factory *object, + const struct spa_dict *params) +{ + return spa_api_func_r(size_t, 0, object, get_size, 1, params); +} +SPA_API_PLUGIN int +spa_handle_factory_init(const struct spa_handle_factory *object, + struct spa_handle *handle, const struct spa_dict *info, + const struct spa_support *support, uint32_t n_support) +{ + return spa_api_func_r(int, -ENOTSUP, object, init, 1, handle, info, + support, n_support); +} +SPA_API_PLUGIN int +spa_handle_factory_enum_interface_info(const struct spa_handle_factory *object, + const struct spa_interface_info **info, uint32_t *index) +{ + return spa_api_func_r(int, -ENOTSUP, object, enum_interface_info, 1, + info, index); +} /** * The function signature of the entry point in a plugin. diff --git a/spa/include/spa/support/system.h b/spa/include/spa/support/system.h index 9ea41bce..aa140c95 100644 --- a/spa/include/spa/support/system.h +++ b/spa/include/spa/support/system.h @@ -12,11 +12,20 @@ extern "C" { struct itimerspec; #include <time.h> +#include <errno.h> #include <sys/types.h> #include <spa/utils/defs.h> #include <spa/utils/hook.h> +#ifndef SPA_API_SYSTEM + #ifdef SPA_API_IMPL + #define SPA_API_SYSTEM SPA_API_IMPL + #else + #define SPA_API_SYSTEM static inline + #endif +#endif + /** \defgroup spa_system System * I/O, clock, polling, timer, and signal interfaces */ @@ -97,41 +106,106 @@ struct spa_system_methods { int (*signalfd_read) (void *object, int fd, int *signal); }; -#define spa_system_method_r(o,method,version,...) \ -({ \ - volatile int _res = -ENOTSUP; \ - struct spa_system *_o = o; \ - spa_interface_call_fast_res(&_o->iface, \ - struct spa_system_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define spa_system_read(s,...) spa_system_method_r(s,read,0,__VA_ARGS__) -#define spa_system_write(s,...) spa_system_method_r(s,write,0,__VA_ARGS__) -#define spa_system_ioctl(s,...) spa_system_method_r(s,ioctl,0,__VA_ARGS__) -#define spa_system_close(s,...) spa_system_method_r(s,close,0,__VA_ARGS__) - -#define spa_system_clock_gettime(s,...) spa_system_method_r(s,clock_gettime,0,__VA_ARGS__) -#define spa_system_clock_getres(s,...) spa_system_method_r(s,clock_getres,0,__VA_ARGS__) - -#define spa_system_pollfd_create(s,...) spa_system_method_r(s,pollfd_create,0,__VA_ARGS__) -#define spa_system_pollfd_add(s,...) spa_system_method_r(s,pollfd_add,0,__VA_ARGS__) -#define spa_system_pollfd_mod(s,...) spa_system_method_r(s,pollfd_mod,0,__VA_ARGS__) -#define spa_system_pollfd_del(s,...) spa_system_method_r(s,pollfd_del,0,__VA_ARGS__) -#define spa_system_pollfd_wait(s,...) spa_system_method_r(s,pollfd_wait,0,__VA_ARGS__) - -#define spa_system_timerfd_create(s,...) spa_system_method_r(s,timerfd_create,0,__VA_ARGS__) -#define spa_system_timerfd_settime(s,...) spa_system_method_r(s,timerfd_settime,0,__VA_ARGS__) -#define spa_system_timerfd_gettime(s,...) spa_system_method_r(s,timerfd_gettime,0,__VA_ARGS__) -#define spa_system_timerfd_read(s,...) spa_system_method_r(s,timerfd_read,0,__VA_ARGS__) - -#define spa_system_eventfd_create(s,...) spa_system_method_r(s,eventfd_create,0,__VA_ARGS__) -#define spa_system_eventfd_write(s,...) spa_system_method_r(s,eventfd_write,0,__VA_ARGS__) -#define spa_system_eventfd_read(s,...) spa_system_method_r(s,eventfd_read,0,__VA_ARGS__) - -#define spa_system_signalfd_create(s,...) spa_system_method_r(s,signalfd_create,0,__VA_ARGS__) -#define spa_system_signalfd_read(s,...) spa_system_method_r(s,signalfd_read,0,__VA_ARGS__) +SPA_API_SYSTEM ssize_t spa_system_read(struct spa_system *object, int fd, void *buf, size_t count) +{ + return spa_api_method_fast_r(ssize_t, -ENOTSUP, spa_system, &object->iface, read, 0, fd, buf, count); +} +SPA_API_SYSTEM ssize_t spa_system_write(struct spa_system *object, int fd, const void *buf, size_t count) +{ + return spa_api_method_fast_r(ssize_t, -ENOTSUP, spa_system, &object->iface, write, 0, fd, buf, count); +} +#define spa_system_ioctl(object,fd,request,...) \ + spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, ioctl, 0, fd, request, ##__VA_ARGS__) + +SPA_API_SYSTEM int spa_system_close(struct spa_system *object, int fd) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, close, 0, fd); +} +SPA_API_SYSTEM int spa_system_clock_gettime(struct spa_system *object, + int clockid, struct timespec *value) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, clock_gettime, 0, clockid, value); +} +SPA_API_SYSTEM int spa_system_clock_getres(struct spa_system *object, + int clockid, struct timespec *res) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, clock_getres, 0, clockid, res); +} + +SPA_API_SYSTEM int spa_system_pollfd_create(struct spa_system *object, int flags) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, pollfd_create, 0, flags); +} +SPA_API_SYSTEM int spa_system_pollfd_add(struct spa_system *object, int pfd, int fd, uint32_t events, void *data) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, pollfd_add, 0, pfd, fd, events, data); +} +SPA_API_SYSTEM int spa_system_pollfd_mod(struct spa_system *object, int pfd, int fd, uint32_t events, void *data) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, pollfd_mod, 0, pfd, fd, events, data); +} +SPA_API_SYSTEM int spa_system_pollfd_del(struct spa_system *object, int pfd, int fd) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, pollfd_del, 0, pfd, fd); +} +SPA_API_SYSTEM int spa_system_pollfd_wait(struct spa_system *object, int pfd, + struct spa_poll_event *ev, int n_ev, int timeout) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, pollfd_wait, 0, pfd, ev, n_ev, timeout); +} + +SPA_API_SYSTEM int spa_system_timerfd_create(struct spa_system *object, int clockid, int flags) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, timerfd_create, 0, clockid, flags); +} + +SPA_API_SYSTEM int spa_system_timerfd_settime(struct spa_system *object, + int fd, int flags, + const struct itimerspec *new_value, + struct itimerspec *old_value) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, timerfd_settime, 0, + fd, flags, new_value, old_value); +} + +SPA_API_SYSTEM int spa_system_timerfd_gettime(struct spa_system *object, + int fd, struct itimerspec *curr_value) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, timerfd_gettime, 0, + fd, curr_value); +} +SPA_API_SYSTEM int spa_system_timerfd_read(struct spa_system *object, int fd, uint64_t *expirations) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, timerfd_read, 0, + fd, expirations); +} + +SPA_API_SYSTEM int spa_system_eventfd_create(struct spa_system *object, int flags) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, eventfd_create, 0, flags); +} +SPA_API_SYSTEM int spa_system_eventfd_write(struct spa_system *object, int fd, uint64_t count) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, eventfd_write, 0, + fd, count); +} +SPA_API_SYSTEM int spa_system_eventfd_read(struct spa_system *object, int fd, uint64_t *count) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, eventfd_read, 0, + fd, count); +} + +SPA_API_SYSTEM int spa_system_signalfd_create(struct spa_system *object, int signal, int flags) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, signalfd_create, 0, + signal, flags); +} + +SPA_API_SYSTEM int spa_system_signalfd_read(struct spa_system *object, int fd, int *signal) +{ + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, signalfd_read, 0, + fd, signal); +} /** * \} diff --git a/spa/include/spa/support/thread.h b/spa/include/spa/support/thread.h index f691e3a3..b69cb688 100644 --- a/spa/include/spa/support/thread.h +++ b/spa/include/spa/support/thread.h @@ -16,6 +16,14 @@ extern "C" { #include <spa/utils/hook.h> #include <spa/utils/dict.h> +#ifndef SPA_API_THREAD + #ifdef SPA_API_IMPL + #define SPA_API_THREAD SPA_API_IMPL + #else + #define SPA_API_THREAD static inline + #endif +#endif + /** \defgroup spa_thread Thread * Threading utility interfaces */ @@ -58,61 +66,51 @@ struct spa_thread_utils_methods { /** \copydoc spa_thread_utils_methods.create * \sa spa_thread_utils_methods.create */ -static inline struct spa_thread *spa_thread_utils_create(struct spa_thread_utils *o, +SPA_API_THREAD struct spa_thread *spa_thread_utils_create(struct spa_thread_utils *o, const struct spa_dict *props, void *(*start_routine)(void*), void *arg) { - struct spa_thread *res = NULL; - spa_interface_call_res(&o->iface, - struct spa_thread_utils_methods, res, create, 0, + return spa_api_method_r(struct spa_thread *, NULL, + spa_thread_utils, &o->iface, create, 0, props, start_routine, arg); - return res; } /** \copydoc spa_thread_utils_methods.join * \sa spa_thread_utils_methods.join */ -static inline int spa_thread_utils_join(struct spa_thread_utils *o, +SPA_API_THREAD int spa_thread_utils_join(struct spa_thread_utils *o, struct spa_thread *thread, void **retval) { - int res = -ENOTSUP; - spa_interface_call_res(&o->iface, - struct spa_thread_utils_methods, res, join, 0, + return spa_api_method_r(int, -ENOTSUP, + spa_thread_utils, &o->iface, join, 0, thread, retval); - return res; } /** \copydoc spa_thread_utils_methods.get_rt_range * \sa spa_thread_utils_methods.get_rt_range */ -static inline int spa_thread_utils_get_rt_range(struct spa_thread_utils *o, +SPA_API_THREAD int spa_thread_utils_get_rt_range(struct spa_thread_utils *o, const struct spa_dict *props, int *min, int *max) { - int res = -ENOTSUP; - spa_interface_call_res(&o->iface, - struct spa_thread_utils_methods, res, get_rt_range, 0, + return spa_api_method_r(int, -ENOTSUP, + spa_thread_utils, &o->iface, get_rt_range, 0, props, min, max); - return res; } /** \copydoc spa_thread_utils_methods.acquire_rt * \sa spa_thread_utils_methods.acquire_rt */ -static inline int spa_thread_utils_acquire_rt(struct spa_thread_utils *o, +SPA_API_THREAD int spa_thread_utils_acquire_rt(struct spa_thread_utils *o, struct spa_thread *thread, int priority) { - int res = -ENOTSUP; - spa_interface_call_res(&o->iface, - struct spa_thread_utils_methods, res, acquire_rt, 0, + return spa_api_method_r(int, -ENOTSUP, + spa_thread_utils, &o->iface, acquire_rt, 0, thread, priority); - return res; } /** \copydoc spa_thread_utils_methods.drop_rt * \sa spa_thread_utils_methods.drop_rt */ -static inline int spa_thread_utils_drop_rt(struct spa_thread_utils *o, +SPA_API_THREAD int spa_thread_utils_drop_rt(struct spa_thread_utils *o, struct spa_thread *thread) { - int res = -ENOTSUP; - spa_interface_call_res(&o->iface, - struct spa_thread_utils_methods, res, drop_rt, 0, thread); - return res; + return spa_api_method_r(int, -ENOTSUP, + spa_thread_utils, &o->iface, drop_rt, 0, thread); } #define SPA_KEY_THREAD_NAME "thread.name" /* the thread name */ diff --git a/spa/include/spa/utils/defs.h b/spa/include/spa/utils/defs.h index 474073af..1c1a73ab 100644 --- a/spa/include/spa/utils/defs.h +++ b/spa/include/spa/utils/defs.h @@ -133,10 +133,10 @@ 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; (ptr) < (arr) + SPA_N_ELEMENTS(arr); (ptr)++) #define SPA_FOR_EACH_ELEMENT_VAR(arr, var) \ - for (__typeof__((arr)[0])* var = arr; (void*)(var) < SPA_PTROFF(arr, sizeof(arr), void); (var)++) + for (__typeof__((arr)[0])* var = arr; (var) < (arr) + SPA_N_ELEMENTS(arr); (var)++) #define SPA_ABS(a) \ ({ \ @@ -254,6 +254,20 @@ struct spa_fraction { #define SPA_WARN_UNUSED_RESULT #endif +#ifndef SPA_API_IMPL +#define SPA_API_PROTO static inline +#define SPA_API_IMPL static inline +#endif + +#ifndef SPA_API_UTILS_DEFS + #ifdef SPA_API_IMPL + #define SPA_API_UTILS_DEFS SPA_API_IMPL + #else + #define SPA_API_UTILS_DEFS static inline + #endif +#endif + + #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L #define SPA_RESTRICT restrict #elif defined(__GNUC__) && __GNUC__ >= 4 @@ -278,6 +292,13 @@ struct spa_fraction { #define SPA_ROUND_DOWN_N(num,align) ((num) & ~SPA_ROUND_MASK(num, align)) #define SPA_ROUND_UP_N(num,align) ((((num)-1) | SPA_ROUND_MASK(num, align))+1) +#define SPA_SCALE32(val,num,denom) \ +({ \ + uint64_t _val = (val); \ + uint64_t _denom = (denom); \ + (uint32_t)(((_val) * (num)) / (_denom)); \ +}) + #define SPA_SCALE32_UP(val,num,denom) \ ({ \ uint64_t _val = (val); \ @@ -300,7 +321,7 @@ struct spa_fraction { #endif #endif -static inline bool spa_ptrinside(const void *p1, size_t s1, const void *p2, size_t s2, +SPA_API_UTILS_DEFS bool spa_ptrinside(const void *p1, size_t s1, const void *p2, size_t s2, size_t *remaining) { if (SPA_LIKELY((uintptr_t)p1 <= (uintptr_t)p2 && s2 <= s1 && @@ -315,7 +336,7 @@ static inline bool spa_ptrinside(const void *p1, size_t s1, const void *p2, size } } -static inline bool spa_ptr_inside_and_aligned(const void *p1, size_t s1, +SPA_API_UTILS_DEFS bool spa_ptr_inside_and_aligned(const void *p1, size_t s1, const void *p2, size_t s2, size_t align, size_t *remaining) { diff --git a/spa/include/spa/utils/dict.h b/spa/include/spa/utils/dict.h index 126f469e..c88a833f 100644 --- a/spa/include/spa/utils/dict.h +++ b/spa/include/spa/utils/dict.h @@ -13,6 +13,14 @@ extern "C" { #include <spa/utils/defs.h> +#ifndef SPA_API_DICT + #ifdef SPA_API_IMPL + #define SPA_API_DICT SPA_API_IMPL + #else + #define SPA_API_DICT static inline + #endif +#endif + /** * \defgroup spa_dict Dictionary * Dictionary data structure @@ -28,7 +36,8 @@ struct spa_dict_item { const char *value; }; -#define SPA_DICT_ITEM_INIT(key,value) ((struct spa_dict_item) { (key), (value) }) +#define SPA_DICT_ITEM(key,value) ((struct spa_dict_item) { (key), (value) }) +#define SPA_DICT_ITEM_INIT(key,value) SPA_DICT_ITEM(key,value) struct spa_dict { #define SPA_DICT_FLAG_SORTED (1<<0) /**< items are sorted */ @@ -37,22 +46,26 @@ 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(items,n_items) ((struct spa_dict) { 0, (n_items), (items) }) +#define SPA_DICT_ARRAY(items) SPA_DICT((items),SPA_N_ELEMENTS(items)) +#define SPA_DICT_ITEMS(...) SPA_DICT_ARRAY(((struct spa_dict_item[]) { __VA_ARGS__})) + +#define SPA_DICT_INIT(items,n_items) SPA_DICT(items,n_items) +#define SPA_DICT_INIT_ARRAY(items) SPA_DICT_ARRAY(items) #define spa_dict_for_each(item, dict) \ for ((item) = (dict)->items; \ (item) < &(dict)->items[(dict)->n_items]; \ (item)++) -static inline int spa_dict_item_compare(const void *i1, const void *i2) +SPA_API_DICT int spa_dict_item_compare(const void *i1, const void *i2) { const struct spa_dict_item *it1 = (const struct spa_dict_item *)i1, *it2 = (const struct spa_dict_item *)i2; return strcmp(it1->key, it2->key); } -static inline void spa_dict_qsort(struct spa_dict *dict) +SPA_API_DICT void spa_dict_qsort(struct spa_dict *dict) { if (dict->n_items > 0) qsort((void*)dict->items, dict->n_items, sizeof(struct spa_dict_item), @@ -60,7 +73,7 @@ static inline void spa_dict_qsort(struct spa_dict *dict) SPA_FLAG_SET(dict->flags, SPA_DICT_FLAG_SORTED); } -static inline const struct spa_dict_item *spa_dict_lookup_item(const struct spa_dict *dict, +SPA_API_DICT const struct spa_dict_item *spa_dict_lookup_item(const struct spa_dict *dict, const char *key) { const struct spa_dict_item *item; @@ -83,7 +96,7 @@ static inline const struct spa_dict_item *spa_dict_lookup_item(const struct spa_ return NULL; } -static inline const char *spa_dict_lookup(const struct spa_dict *dict, const char *key) +SPA_API_DICT const char *spa_dict_lookup(const struct spa_dict *dict, const char *key) { const struct spa_dict_item *item = spa_dict_lookup_item(dict, key); return item ? item->value : NULL; diff --git a/spa/include/spa/utils/dll.h b/spa/include/spa/utils/dll.h index 0372d63e..7b8fd207 100644 --- a/spa/include/spa/utils/dll.h +++ b/spa/include/spa/utils/dll.h @@ -12,6 +12,16 @@ extern "C" { #include <stddef.h> #include <math.h> +#include <spa/utils/defs.h> + +#ifndef SPA_API_DLL + #ifdef SPA_API_IMPL + #define SPA_API_DLL SPA_API_IMPL + #else + #define SPA_API_DLL static inline + #endif +#endif + #define SPA_DLL_BW_MAX 0.128 #define SPA_DLL_BW_MIN 0.016 @@ -21,13 +31,13 @@ struct spa_dll { double w0, w1, w2; }; -static inline void spa_dll_init(struct spa_dll *dll) +SPA_API_DLL void spa_dll_init(struct spa_dll *dll) { dll->bw = 0.0; dll->z1 = dll->z2 = dll->z3 = 0.0; } -static inline void spa_dll_set_bw(struct spa_dll *dll, double bw, unsigned period, unsigned rate) +SPA_API_DLL void spa_dll_set_bw(struct spa_dll *dll, double bw, unsigned period, unsigned rate) { double w = 2 * M_PI * bw * period / rate; dll->w0 = 1.0 - exp (-20.0 * w); @@ -36,7 +46,7 @@ static inline void spa_dll_set_bw(struct spa_dll *dll, double bw, unsigned perio dll->bw = bw; } -static inline double spa_dll_update(struct spa_dll *dll, double err) +SPA_API_DLL double spa_dll_update(struct spa_dll *dll, double err) { dll->z1 += dll->w0 * (dll->w1 * err - dll->z1); dll->z2 += dll->w0 * (dll->z1 - dll->z2); diff --git a/spa/include/spa/utils/endian.h b/spa/include/spa/utils/endian.h new file mode 100644 index 00000000..2d002d45 --- /dev/null +++ b/spa/include/spa/utils/endian.h @@ -0,0 +1,26 @@ +/* Spa */ +/* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_ENDIAN_H +#define SPA_ENDIAN_H + +#if defined(__FreeBSD__) || defined(__MidnightBSD__) +#include <sys/endian.h> +#define bswap_16 bswap16 +#define bswap_32 bswap32 +#define bswap_64 bswap64 +#elif defined(_MSC_VER) && defined(_WIN32) +#include <stdlib.h> +#define __LITTLE_ENDIAN 1234 +#define __BIG_ENDIAN 4321 +#define __BYTE_ORDER __LITTLE_ENDIAN +#define bswap_16 _byteswap_ushort +#define bswap_32 _byteswap_ulong +#define bswap_64 _byteswap_uint64 +#else +#include <endian.h> +#include <byteswap.h> +#endif + +#endif /* SPA_ENDIAN_H */ diff --git a/spa/include/spa/utils/hook.h b/spa/include/spa/utils/hook.h index aea18d28..dbbb0197 100644 --- a/spa/include/spa/utils/hook.h +++ b/spa/include/spa/utils/hook.h @@ -12,6 +12,14 @@ extern "C" { #include <spa/utils/defs.h> #include <spa/utils/list.h> +#ifndef SPA_API_HOOK + #ifdef SPA_API_IMPL + #define SPA_API_HOOK SPA_API_IMPL + #else + #define SPA_API_HOOK static inline + #endif +#endif + /** \defgroup spa_interfaces Interfaces * * \brief Generic implementation of implementation-independent interfaces @@ -158,14 +166,14 @@ struct spa_interface { const type *_f = (const type *) (callbacks)->funcs; \ bool _res = SPA_CALLBACK_CHECK(_f,method,vers); \ if (SPA_LIKELY(_res)) \ - _f->method((callbacks)->data, ## __VA_ARGS__); \ + (_f->method)((callbacks)->data, ## __VA_ARGS__); \ _res; \ }) #define spa_callbacks_call_fast(callbacks,type,method,vers,...) \ ({ \ const type *_f = (const type *) (callbacks)->funcs; \ - _f->method((callbacks)->data, ## __VA_ARGS__); \ + (_f->method)((callbacks)->data, ## __VA_ARGS__); \ true; \ }) @@ -199,13 +207,13 @@ struct spa_interface { ({ \ const type *_f = (const type *) (callbacks)->funcs; \ if (SPA_LIKELY(SPA_CALLBACK_CHECK(_f,method,vers))) \ - res = _f->method((callbacks)->data, ## __VA_ARGS__); \ + res = (_f->method)((callbacks)->data, ## __VA_ARGS__); \ res; \ }) #define spa_callbacks_call_fast_res(callbacks,type,res,method,vers,...) \ ({ \ const type *_f = (const type *) (callbacks)->funcs; \ - res = _f->method((callbacks)->data, ## __VA_ARGS__); \ + res = (_f->method)((callbacks)->data, ## __VA_ARGS__); \ }) /** @@ -245,6 +253,73 @@ struct spa_interface { #define spa_interface_call_fast_res(iface,method_type,res,method,vers,...) \ spa_callbacks_call_fast_res(&(iface)->cb,method_type,res,method,vers,##__VA_ARGS__) + +#define spa_api_func_v(o,method,version,...) \ +({ \ + if (SPA_LIKELY(SPA_CALLBACK_CHECK(o,method,version))) \ + ((o)->method)(o, ##__VA_ARGS__); \ +}) +#define spa_api_func_r(rtype,def,o,method,version,...) \ +({ \ + rtype _res = def; \ + if (SPA_LIKELY(SPA_CALLBACK_CHECK(o,method,version))) \ + _res = ((o)->method)(o, ##__VA_ARGS__); \ + _res; \ +}) +#define spa_api_func_fast(o,method,...) \ +({ \ + ((o)->method)(o, ##__VA_ARGS__); \ +}) + +#define spa_api_method_v(type,o,method,version,...) \ +({ \ + struct spa_interface *_i = o; \ + spa_interface_call(_i, struct type ##_methods, \ + method, version, ##__VA_ARGS__); \ +}) +#define spa_api_method_r(rtype,def,type,o,method,version,...) \ +({ \ + rtype _res = def; \ + struct spa_interface *_i = o; \ + spa_interface_call_res(_i, struct type ##_methods, \ + _res, method, version, ##__VA_ARGS__); \ + _res; \ +}) +#define spa_api_method_null_v(type,co,o,method,version,...) \ +({ \ + struct type *_co = co; \ + if (SPA_LIKELY(_co != NULL)) { \ + struct spa_interface *_i = o; \ + spa_interface_call(_i, struct type ##_methods, \ + method, version, ##__VA_ARGS__); \ + } \ +}) +#define spa_api_method_null_r(rtype,def,type,co,o,method,version,...) \ +({ \ + rtype _res = def; \ + struct type *_co = co; \ + if (SPA_LIKELY(_co != NULL)) { \ + struct spa_interface *_i = o; \ + spa_interface_call_res(_i, struct type ##_methods, \ + _res, method, version, ##__VA_ARGS__); \ + } \ + _res; \ +}) +#define spa_api_method_fast_v(type,o,method,version,...) \ +({ \ + struct spa_interface *_i = o; \ + spa_interface_call_fast(_i, struct type ##_methods, \ + method, version, ##__VA_ARGS__); \ +}) +#define spa_api_method_fast_r(rtype,def,type,o,method,version,...) \ +({ \ + rtype _res = def; \ + struct spa_interface *_i = o; \ + spa_interface_call_fast_res(_i, struct type ##_methods, \ + _res, method, version, ##__VA_ARGS__); \ + _res; \ +}) + /** * \} */ @@ -348,18 +423,18 @@ struct spa_hook { }; /** Initialize a hook list to the empty list*/ -static inline void spa_hook_list_init(struct spa_hook_list *list) +SPA_API_HOOK void spa_hook_list_init(struct spa_hook_list *list) { spa_list_init(&list->list); } -static inline bool spa_hook_list_is_empty(struct spa_hook_list *list) +SPA_API_HOOK bool spa_hook_list_is_empty(struct spa_hook_list *list) { return spa_list_is_empty(&list->list); } /** Append a hook. */ -static inline void spa_hook_list_append(struct spa_hook_list *list, +SPA_API_HOOK void spa_hook_list_append(struct spa_hook_list *list, struct spa_hook *hook, const void *funcs, void *data) { @@ -369,7 +444,7 @@ static inline void spa_hook_list_append(struct spa_hook_list *list, } /** Prepend a hook */ -static inline void spa_hook_list_prepend(struct spa_hook_list *list, +SPA_API_HOOK void spa_hook_list_prepend(struct spa_hook_list *list, struct spa_hook *hook, const void *funcs, void *data) { @@ -379,7 +454,7 @@ 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_API_HOOK void spa_hook_remove(struct spa_hook *hook) { if (spa_list_is_initialized(&hook->link)) spa_list_remove(&hook->link); @@ -388,14 +463,14 @@ static inline void spa_hook_remove(struct spa_hook *hook) } /** Remove all hooks from the list */ -static inline void spa_hook_list_clean(struct spa_hook_list *list) +SPA_API_HOOK void spa_hook_list_clean(struct spa_hook_list *list) { struct spa_hook *h; spa_list_consume(h, &list->list, link) spa_hook_remove(h); } -static inline void +SPA_API_HOOK void spa_hook_list_isolate(struct spa_hook_list *list, struct spa_hook_list *save, struct spa_hook *hook, @@ -409,7 +484,7 @@ spa_hook_list_isolate(struct spa_hook_list *list, spa_hook_list_append(list, hook, funcs, data); } -static inline void +SPA_API_HOOK void spa_hook_list_join(struct spa_hook_list *list, struct spa_hook_list *save) { diff --git a/spa/include/spa/utils/json-core.h b/spa/include/spa/utils/json-core.h new file mode 100644 index 00000000..31bf772f --- /dev/null +++ b/spa/include/spa/utils/json-core.h @@ -0,0 +1,635 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_UTILS_JSON_H +#define SPA_UTILS_JSON_H + +#ifdef __cplusplus +extern "C" { +#else +#include <stdbool.h> +#endif +#include <stddef.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <math.h> +#include <float.h> + +#include <spa/utils/defs.h> +#include <spa/utils/string.h> + +#ifndef SPA_API_JSON + #ifdef SPA_API_IMPL + #define SPA_API_JSON SPA_API_IMPL + #else + #define SPA_API_JSON static inline + #endif +#endif + +/** \defgroup spa_json JSON + * Relaxed JSON variant parsing + */ + +/** + * \addtogroup spa_json + * \{ + */ + +/* a simple JSON compatible tokenizer */ +struct spa_json { + const char *cur; + const char *end; + struct spa_json *parent; +#define SPA_JSON_ERROR_FLAG 0x100 + uint32_t state; + uint32_t depth; +}; + +#define SPA_JSON_INIT(data,size) ((struct spa_json) { (data), (data)+(size), NULL, 0, 0 }) + +SPA_API_JSON 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), (iter)->state & 0xff0, 0 }) + +SPA_API_JSON 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, NULL, (iter)->state, 0 }) + +SPA_API_JSON void spa_json_save(struct spa_json * iter, struct spa_json * save) +{ + *save = SPA_JSON_SAVE(iter); +} + +#define SPA_JSON_START(iter,p) ((struct spa_json) { (p), (iter)->end, NULL, 0, 0 }) + +SPA_API_JSON void spa_json_start(struct spa_json * iter, struct spa_json * sub, const char *pos) +{ + *sub = SPA_JSON_START(iter,pos); +} + +/** Get the next token. \a value points to the token and the return value + * is the length. Returns -1 on parse error, 0 on end of input. */ +SPA_API_JSON int spa_json_next(struct spa_json * iter, const char **value) +{ + int utf8_remain = 0, err = 0; + enum { + __NONE, __STRUCT, __BARE, __STRING, __UTF8, __ESC, __COMMENT, + __ARRAY_FLAG = 0x10, /* in array context */ + __PREV_ARRAY_FLAG = 0x20, /* depth=0 array context flag */ + __KEY_FLAG = 0x40, /* inside object key */ + __SUB_FLAG = 0x80, /* not at top-level */ + __FLAGS = 0xff0, + __ERROR_SYSTEM = SPA_JSON_ERROR_FLAG, + __ERROR_INVALID_ARRAY_SEPARATOR, + __ERROR_EXPECTED_OBJECT_KEY, + __ERROR_EXPECTED_OBJECT_VALUE, + __ERROR_TOO_DEEP_NESTING, + __ERROR_EXPECTED_ARRAY_CLOSE, + __ERROR_EXPECTED_OBJECT_CLOSE, + __ERROR_MISMATCHED_BRACKET, + __ERROR_ESCAPE_NOT_ALLOWED, + __ERROR_CHARACTERS_NOT_ALLOWED, + __ERROR_INVALID_ESCAPE, + __ERROR_INVALID_STATE, + __ERROR_UNFINISHED_STRING, + }; + uint64_t array_stack[8] = {0}; /* array context flags of depths 1...512 */ + + *value = iter->cur; + + if (iter->state & SPA_JSON_ERROR_FLAG) + return -1; + + for (; iter->cur < iter->end; iter->cur++) { + unsigned char cur = (unsigned char)*iter->cur; + uint32_t flag; + +#define _SPA_ERROR(reason) { err = __ERROR_ ## reason; goto error; } + again: + flag = iter->state & __FLAGS; + switch (iter->state & ~__FLAGS) { + case __NONE: + flag &= ~(__KEY_FLAG | __PREV_ARRAY_FLAG); + iter->state = __STRUCT | flag; + iter->depth = 0; + goto again; + case __STRUCT: + switch (cur) { + case '\0': case '\t': case ' ': case '\r': case '\n': case ',': + continue; + case ':': case '=': + if (flag & __ARRAY_FLAG) + _SPA_ERROR(INVALID_ARRAY_SEPARATOR); + if (!(flag & __KEY_FLAG)) + _SPA_ERROR(EXPECTED_OBJECT_KEY); + iter->state |= __SUB_FLAG; + continue; + case '#': + iter->state = __COMMENT | flag; + continue; + case '"': + if (flag & __KEY_FLAG) + flag |= __SUB_FLAG; + if (!(flag & __ARRAY_FLAG)) + SPA_FLAG_UPDATE(flag, __KEY_FLAG, !(flag & __KEY_FLAG)); + *value = iter->cur; + iter->state = __STRING | flag; + continue; + case '[': case '{': + if (!(flag & __ARRAY_FLAG)) { + /* At top-level we may be either in object context + * or in single-item context, and then we need to + * accept array/object here. + */ + if ((iter->state & __SUB_FLAG) && !(flag & __KEY_FLAG)) + _SPA_ERROR(EXPECTED_OBJECT_KEY); + SPA_FLAG_CLEAR(flag, __KEY_FLAG); + } + iter->state = __STRUCT | __SUB_FLAG | flag; + SPA_FLAG_UPDATE(iter->state, __ARRAY_FLAG, cur == '['); + + /* We need to remember previous array state across calls + * for depth=0, so store that in state. Others bits go to + * temporary stack. + */ + if (iter->depth == 0) { + SPA_FLAG_UPDATE(iter->state, __PREV_ARRAY_FLAG, flag & __ARRAY_FLAG); + } else if (((iter->depth-1) >> 6) < SPA_N_ELEMENTS(array_stack)) { + uint64_t mask = 1ULL << ((iter->depth-1) & 0x3f); + SPA_FLAG_UPDATE(array_stack[(iter->depth-1) >> 6], mask, flag & __ARRAY_FLAG); + } else { + /* too deep */ + _SPA_ERROR(TOO_DEEP_NESTING); + } + + *value = iter->cur; + if (++iter->depth > 1) + continue; + iter->cur++; + return 1; + case '}': case ']': + if ((flag & __ARRAY_FLAG) && cur != ']') + _SPA_ERROR(EXPECTED_ARRAY_CLOSE); + if (!(flag & __ARRAY_FLAG) && cur != '}') + _SPA_ERROR(EXPECTED_OBJECT_CLOSE); + if (flag & __KEY_FLAG) { + /* incomplete key-value pair */ + _SPA_ERROR(EXPECTED_OBJECT_VALUE); + } + iter->state = __STRUCT | __SUB_FLAG | flag; + if (iter->depth == 0) { + if (iter->parent) + iter->parent->cur = iter->cur; + else + _SPA_ERROR(MISMATCHED_BRACKET); + return 0; + } + --iter->depth; + if (iter->depth == 0) { + SPA_FLAG_UPDATE(iter->state, __ARRAY_FLAG, flag & __PREV_ARRAY_FLAG); + } else if (((iter->depth-1) >> 6) < SPA_N_ELEMENTS(array_stack)) { + uint64_t mask = 1ULL << ((iter->depth-1) & 0x3f); + SPA_FLAG_UPDATE(iter->state, __ARRAY_FLAG, + SPA_FLAG_IS_SET(array_stack[(iter->depth-1) >> 6], mask)); + } else { + /* too deep */ + _SPA_ERROR(TOO_DEEP_NESTING); + } + continue; + case '\\': + /* disallow bare escape */ + _SPA_ERROR(ESCAPE_NOT_ALLOWED); + default: + /* allow bare ascii */ + if (!(cur >= 32 && cur <= 126)) + _SPA_ERROR(CHARACTERS_NOT_ALLOWED); + if (flag & __KEY_FLAG) + flag |= __SUB_FLAG; + if (!(flag & __ARRAY_FLAG)) + SPA_FLAG_UPDATE(flag, __KEY_FLAG, !(flag & __KEY_FLAG)); + *value = iter->cur; + iter->state = __BARE | flag; + } + continue; + case __BARE: + switch (cur) { + case '\0': + case '\t': case ' ': case '\r': case '\n': + case '"': case '#': + case ':': case ',': case '=': case ']': case '}': + iter->state = __STRUCT | flag; + if (iter->depth > 0) + goto again; + return iter->cur - *value; + case '\\': + /* disallow bare escape */ + _SPA_ERROR(ESCAPE_NOT_ALLOWED); + default: + /* allow bare ascii */ + if (cur >= 32 && cur <= 126) + continue; + } + _SPA_ERROR(CHARACTERS_NOT_ALLOWED); + case __STRING: + switch (cur) { + case '\\': + iter->state = __ESC | flag; + continue; + case '"': + iter->state = __STRUCT | flag; + if (iter->depth > 0) + continue; + return ++iter->cur - *value; + case 240 ... 247: + utf8_remain++; + SPA_FALLTHROUGH; + case 224 ... 239: + utf8_remain++; + SPA_FALLTHROUGH; + case 192 ... 223: + utf8_remain++; + iter->state = __UTF8 | flag; + continue; + default: + if (cur >= 32 && cur <= 127) + continue; + } + _SPA_ERROR(CHARACTERS_NOT_ALLOWED); + case __UTF8: + switch (cur) { + case 128 ... 191: + if (--utf8_remain == 0) + iter->state = __STRING | flag; + continue; + } + _SPA_ERROR(CHARACTERS_NOT_ALLOWED); + case __ESC: + switch (cur) { + case '"': case '\\': case '/': case 'b': case 'f': + case 'n': case 'r': case 't': case 'u': + iter->state = __STRING | flag; + continue; + } + _SPA_ERROR(INVALID_ESCAPE); + case __COMMENT: + switch (cur) { + case '\n': case '\r': + iter->state = __STRUCT | flag; + } + break; + default: + _SPA_ERROR(INVALID_STATE); + } + + } + if (iter->depth != 0 || iter->parent) + _SPA_ERROR(MISMATCHED_BRACKET); + + switch (iter->state & ~__FLAGS) { + case __STRING: case __UTF8: case __ESC: + /* string/escape not closed */ + _SPA_ERROR(UNFINISHED_STRING); + case __COMMENT: + /* trailing comment */ + return 0; + } + + if ((iter->state & __SUB_FLAG) && (iter->state & __KEY_FLAG)) { + /* incomplete key-value pair */ + _SPA_ERROR(EXPECTED_OBJECT_VALUE); + } + + if ((iter->state & ~__FLAGS) != __STRUCT) { + iter->state = __STRUCT | (iter->state & __FLAGS); + return iter->cur - *value; + } + return 0; +#undef _SPA_ERROR + +error: + iter->state = err; + while (iter->parent) { + if (iter->parent->state & SPA_JSON_ERROR_FLAG) + break; + iter->parent->state = err; + iter->parent->cur = iter->cur; + iter = iter->parent; + } + return -1; +} + +/** + * Return if there was a parse error, and its possible location. + * + * \since 1.1.0 + */ +SPA_API_JSON bool spa_json_get_error(struct spa_json *iter, const char *start, + struct spa_error_location *loc) +{ + static const char *reasons[] = { + "System error", + "Invalid array separator", + "Expected object key", + "Expected object value", + "Too deep nesting", + "Expected array close bracket", + "Expected object close brace", + "Mismatched bracket", + "Escape not allowed", + "Character not allowed", + "Invalid escape", + "Invalid state", + "Unfinished string", + "Expected key separator", + }; + + if (!(iter->state & SPA_JSON_ERROR_FLAG)) + return false; + + if (loc) { + int linepos = 1, colpos = 1, code; + const char *p, *l; + + for (l = p = start; p && p != iter->cur; ++p) { + if (*p == '\n') { + linepos++; + colpos = 1; + l = p+1; + } else { + colpos++; + } + } + code = SPA_CLAMP(iter->state & 0xff, 0u, SPA_N_ELEMENTS(reasons)-1); + loc->line = linepos; + loc->col = colpos; + loc->location = l; + loc->len = SPA_PTRDIFF(iter->end, loc->location) / sizeof(char); + loc->reason = code == 0 ? strerror(errno) : reasons[code]; + } + return true; +} + +SPA_API_JSON int spa_json_is_container(const char *val, int len) +{ + return len > 0 && (*val == '{' || *val == '['); +} + +/* object */ +SPA_API_JSON int spa_json_is_object(const char *val, int len) +{ + return len > 0 && *val == '{'; +} + +/* array */ +SPA_API_JSON bool spa_json_is_array(const char *val, int len) +{ + return len > 0 && *val == '['; +} + +/* null */ +SPA_API_JSON bool spa_json_is_null(const char *val, int len) +{ + return len == 4 && strncmp(val, "null", 4) == 0; +} + +/* float */ +SPA_API_JSON int spa_json_parse_float(const char *val, int len, float *result) +{ + char buf[96]; + char *end; + int pos; + + if (len <= 0 || len >= (int)sizeof(buf)) + return 0; + + for (pos = 0; pos < len; ++pos) { + switch (val[pos]) { + case '+': case '-': case '0' ... '9': case '.': case 'e': case 'E': break; + default: return 0; + } + } + + memcpy(buf, val, len); + buf[len] = '\0'; + + *result = spa_strtof(buf, &end); + return len > 0 && end == buf + len; +} + +SPA_API_JSON bool spa_json_is_float(const char *val, int len) +{ + float dummy; + return spa_json_parse_float(val, len, &dummy); +} + +SPA_API_JSON char *spa_json_format_float(char *str, int size, float val) +{ + if (SPA_UNLIKELY(!isnormal(val))) { + if (isinf(val)) + val = signbit(val) ? FLT_MIN : FLT_MAX; + else + val = 0.0f; + } + return spa_dtoa(str, size, val); +} + +/* int */ +SPA_API_JSON int spa_json_parse_int(const char *val, int len, int *result) +{ + char buf[64]; + char *end; + + if (len <= 0 || len >= (int)sizeof(buf)) + return 0; + + memcpy(buf, val, len); + buf[len] = '\0'; + + *result = strtol(buf, &end, 0); + return len > 0 && end == buf + len; +} +SPA_API_JSON bool spa_json_is_int(const char *val, int len) +{ + int dummy; + return spa_json_parse_int(val, len, &dummy); +} + +/* bool */ +SPA_API_JSON bool spa_json_is_true(const char *val, int len) +{ + return len == 4 && strncmp(val, "true", 4) == 0; +} + +SPA_API_JSON bool spa_json_is_false(const char *val, int len) +{ + return len == 5 && strncmp(val, "false", 5) == 0; +} + +SPA_API_JSON bool spa_json_is_bool(const char *val, int len) +{ + return spa_json_is_true(val, len) || spa_json_is_false(val, len); +} + +SPA_API_JSON int spa_json_parse_bool(const char *val, int len, bool *result) +{ + if ((*result = spa_json_is_true(val, len))) + return 1; + if (!(*result = !spa_json_is_false(val, len))) + return 1; + return -1; +} + +/* string */ +SPA_API_JSON bool spa_json_is_string(const char *val, int len) +{ + return len > 1 && *val == '"'; +} + +SPA_API_JSON int spa_json_parse_hex(const char *p, int num, uint32_t *res) +{ + int i; + *res = 0; + for (i = 0; i < num; i++) { + char v = p[i]; + if (v >= '0' && v <= '9') + v = v - '0'; + else if (v >= 'a' && v <= 'f') + v = v - 'a' + 10; + else if (v >= 'A' && v <= 'F') + v = v - 'A' + 10; + else + return -1; + *res = (*res << 4) | v; + } + return 1; +} + +SPA_API_JSON int spa_json_parse_stringn(const char *val, int len, char *result, int maxlen) +{ + const char *p; + if (maxlen <= len) + return -ENOSPC; + if (!spa_json_is_string(val, len)) { + if (result != val) + memmove(result, val, len); + result += len; + } else { + for (p = val+1; p < val + len; p++) { + if (*p == '\\') { + p++; + if (*p == 'n') + *result++ = '\n'; + else if (*p == 'r') + *result++ = '\r'; + else if (*p == 'b') + *result++ = '\b'; + else if (*p == 't') + *result++ = '\t'; + else if (*p == 'f') + *result++ = '\f'; + else if (*p == 'u') { + uint8_t prefix[] = { 0, 0xc0, 0xe0, 0xf0 }; + uint32_t idx, n, v, cp, enc[] = { 0x80, 0x800, 0x10000 }; + if (val + len - p < 5 || + spa_json_parse_hex(p+1, 4, &cp) < 0) { + *result++ = *p; + continue; + } + p += 4; + + if (cp >= 0xd800 && cp <= 0xdbff) { + if (val + len - p < 7 || + p[1] != '\\' || p[2] != 'u' || + spa_json_parse_hex(p+3, 4, &v) < 0 || + v < 0xdc00 || v > 0xdfff) + continue; + p += 6; + cp = 0x010000 + (((cp & 0x3ff) << 10) | (v & 0x3ff)); + } else if (cp >= 0xdc00 && cp <= 0xdfff) + continue; + + for (idx = 0; idx < 3; idx++) + if (cp < enc[idx]) + break; + for (n = idx; n > 0; n--, cp >>= 6) + result[n] = (cp | 0x80) & 0xbf; + *result++ = (cp | prefix[idx]) & 0xff; + result += idx; + } else + *result++ = *p; + } else if (*p == '\"') { + break; + } else + *result++ = *p; + } + } + *result = '\0'; + return 1; +} + +SPA_API_JSON int spa_json_parse_string(const char *val, int len, char *result) +{ + return spa_json_parse_stringn(val, len, result, len+1); +} + +SPA_API_JSON int spa_json_encode_string(char *str, int size, const char *val) +{ + int len = 0; + static const char hex[] = { "0123456789abcdef" }; +#define __PUT(c) { if (len < size) *str++ = c; len++; } + __PUT('"'); + while (*val) { + switch (*val) { + case '\n': + __PUT('\\'); __PUT('n'); + break; + case '\r': + __PUT('\\'); __PUT('r'); + break; + case '\b': + __PUT('\\'); __PUT('b'); + break; + case '\t': + __PUT('\\'); __PUT('t'); + break; + case '\f': + __PUT('\\'); __PUT('f'); + break; + case '\\': + case '"': + __PUT('\\'); __PUT(*val); + break; + default: + if (*val > 0 && *val < 0x20) { + __PUT('\\'); __PUT('u'); + __PUT('0'); __PUT('0'); + __PUT(hex[((*val)>>4)&0xf]); __PUT(hex[(*val)&0xf]); + } else { + __PUT(*val); + } + break; + } + val++; + } + __PUT('"'); + __PUT('\0'); +#undef __PUT + return len-1; +} + +/** + * \} + */ + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* SPA_UTILS_JSON_H */ diff --git a/spa/include/spa/utils/json-pod.h b/spa/include/spa/utils/json-pod.h index 0c9bdb8a..22f2b281 100644 --- a/spa/include/spa/utils/json-pod.h +++ b/spa/include/spa/utils/json-pod.h @@ -15,6 +15,14 @@ extern "C" { #include <spa/pod/builder.h> #include <spa/debug/types.h> +#ifndef SPA_API_JSON_POD + #ifdef SPA_API_IMPL + #define SPA_API_JSON_POD SPA_API_IMPL + #else + #define SPA_API_JSON_POD static inline + #endif +#endif + /** \defgroup spa_json_pod JSON to POD * JSON to POD conversion */ @@ -24,7 +32,7 @@ extern "C" { * \{ */ -static inline int spa_json_to_pod_part(struct spa_pod_builder *b, uint32_t flags, uint32_t id, +SPA_API_JSON_POD int spa_json_to_pod_part(struct spa_pod_builder *b, uint32_t flags, uint32_t id, const struct spa_type_info *info, struct spa_json *iter, const char *value, int len) { const struct spa_type_info *ti; @@ -42,10 +50,8 @@ static inline int spa_json_to_pod_part(struct spa_pod_builder *b, uint32_t flags spa_pod_builder_push_object(b, &f[0], info->parent, id); spa_json_enter(iter, &it[0]); - while (spa_json_get_string(&it[0], key, sizeof(key)) > 0) { + while ((l = spa_json_object_next(&it[0], key, sizeof(key), &v)) > 0) { const struct spa_type_info *pi; - if ((l = spa_json_next(&it[0], &v)) <= 0) - break; if ((pi = spa_debug_type_find_short(ti->values, key)) != NULL) type = pi->type; else if (!spa_atou32(key, &type, 0)) @@ -137,17 +143,32 @@ static inline int spa_json_to_pod_part(struct spa_pod_builder *b, uint32_t flags return 0; } -static inline int spa_json_to_pod(struct spa_pod_builder *b, uint32_t flags, - const struct spa_type_info *info, const char *value, int len) +SPA_API_JSON_POD int spa_json_to_pod_checked(struct spa_pod_builder *b, uint32_t flags, + const struct spa_type_info *info, const char *value, int len, + struct spa_error_location *loc) { struct spa_json iter; const char *val; + int res; + + if (loc) + spa_zero(*loc); + + if ((res = spa_json_begin(&iter, value, len, &val)) <= 0) + goto error; - spa_json_init(&iter, value, len); - if ((len = spa_json_next(&iter, &val)) <= 0) - return -EINVAL; + res = spa_json_to_pod_part(b, flags, info->type, info, &iter, val, len); - return spa_json_to_pod_part(b, flags, info->type, info, &iter, val, len); +error: + if (res < 0 && loc) + spa_json_get_error(&iter, value, loc); + return res; +} + +SPA_API_JSON_POD int spa_json_to_pod(struct spa_pod_builder *b, uint32_t flags, + const struct spa_type_info *info, const char *value, int len) +{ + return spa_json_to_pod_checked(b, flags, info, value, len, NULL); } /** diff --git a/spa/include/spa/utils/json.h b/spa/include/spa/utils/json.h index 0cca1710..a36554d1 100644 --- a/spa/include/spa/utils/json.h +++ b/spa/include/spa/utils/json.h @@ -2,8 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2020 Wim Taymans */ /* SPDX-License-Identifier: MIT */ -#ifndef SPA_UTILS_JSON_H -#define SPA_UTILS_JSON_H +#ifndef SPA_UTILS_JSON_UTILS_H +#define SPA_UTILS_JSON_UTILS_H #ifdef __cplusplus extern "C" { @@ -17,11 +17,18 @@ extern "C" { #include <math.h> #include <float.h> -#include <spa/utils/defs.h> -#include <spa/utils/string.h> +#include <spa/utils/json-core.h> -/** \defgroup spa_json JSON - * Relaxed JSON variant parsing +#ifndef SPA_API_JSON_UTILS + #ifdef SPA_API_IMPL + #define SPA_API_JSON_UTILS SPA_API_IMPL + #else + #define SPA_API_JSON_UTILS static inline + #endif +#endif + +/** \defgroup spa_json_utils JSON Utils + * Relaxed JSON variant parsing Utils */ /** @@ -29,356 +36,85 @@ extern "C" { * \{ */ -/* a simple JSON compatible tokenizer */ -struct spa_json { - const char *cur; - const char *end; - struct spa_json *parent; -#define SPA_JSON_ERROR_FLAG 0x100 - uint32_t state; - uint32_t depth; -}; - -#define SPA_JSON_INIT(data,size) ((struct spa_json) { (data), (data)+(size), 0, 0, 0 }) - -static inline void spa_json_init(struct spa_json * iter, const char *data, size_t size) +SPA_API_JSON_UTILS int spa_json_begin(struct spa_json * iter, const char *data, size_t size, const char **val) { - *iter = SPA_JSON_INIT(data, size); + spa_json_init(iter, data, size); + return spa_json_next(iter, val); } -#define SPA_JSON_ENTER(iter) ((struct spa_json) { (iter)->cur, (iter)->end, (iter), (iter)->state & 0xff0, 0 }) -static inline void spa_json_enter(struct spa_json * iter, struct spa_json * sub) +/* float */ +SPA_API_JSON_UTILS int spa_json_get_float(struct spa_json *iter, float *res) { - *sub = SPA_JSON_ENTER(iter); + const char *value; + int len; + if ((len = spa_json_next(iter, &value)) <= 0) + return len; + return spa_json_parse_float(value, len, res); } -#define SPA_JSON_SAVE(iter) ((struct spa_json) { (iter)->cur, (iter)->end, NULL, (iter)->state, 0 }) - -/** Get the next token. \a value points to the token and the return value - * is the length. Returns -1 on parse error, 0 on end of input. */ -static inline int spa_json_next(struct spa_json * iter, const char **value) +/* int */ +SPA_API_JSON_UTILS int spa_json_get_int(struct spa_json *iter, int *res) { - int utf8_remain = 0, err = 0; - enum { - __NONE, __STRUCT, __BARE, __STRING, __UTF8, __ESC, __COMMENT, - __ARRAY_FLAG = 0x10, /* in array context */ - __PREV_ARRAY_FLAG = 0x20, /* depth=0 array context flag */ - __KEY_FLAG = 0x40, /* inside object key */ - __SUB_FLAG = 0x80, /* not at top-level */ - __FLAGS = 0xff0, - __ERROR_SYSTEM = SPA_JSON_ERROR_FLAG, - __ERROR_INVALID_ARRAY_SEPARATOR, - __ERROR_EXPECTED_OBJECT_KEY, - __ERROR_EXPECTED_OBJECT_VALUE, - __ERROR_TOO_DEEP_NESTING, - __ERROR_EXPECTED_ARRAY_CLOSE, - __ERROR_EXPECTED_OBJECT_CLOSE, - __ERROR_MISMATCHED_BRACKET, - __ERROR_ESCAPE_NOT_ALLOWED, - __ERROR_CHARACTERS_NOT_ALLOWED, - __ERROR_INVALID_ESCAPE, - __ERROR_INVALID_STATE, - __ERROR_UNFINISHED_STRING, - }; - uint64_t array_stack[8] = {0}; /* array context flags of depths 1...512 */ - - *value = iter->cur; - - if (iter->state & SPA_JSON_ERROR_FLAG) - return -1; - - for (; iter->cur < iter->end; iter->cur++) { - unsigned char cur = (unsigned char)*iter->cur; - uint32_t flag; - -#define _SPA_ERROR(reason) { err = __ERROR_ ## reason; goto error; } - again: - flag = iter->state & __FLAGS; - switch (iter->state & ~__FLAGS) { - case __NONE: - flag &= ~(__KEY_FLAG | __PREV_ARRAY_FLAG); - iter->state = __STRUCT | flag; - iter->depth = 0; - goto again; - case __STRUCT: - switch (cur) { - case '\0': case '\t': case ' ': case '\r': case '\n': case ',': - continue; - case ':': case '=': - if (flag & __ARRAY_FLAG) - _SPA_ERROR(INVALID_ARRAY_SEPARATOR); - if (!(flag & __KEY_FLAG)) - _SPA_ERROR(EXPECTED_OBJECT_KEY); - iter->state |= __SUB_FLAG; - continue; - case '#': - iter->state = __COMMENT | flag; - continue; - case '"': - if (flag & __KEY_FLAG) - flag |= __SUB_FLAG; - if (!(flag & __ARRAY_FLAG)) - SPA_FLAG_UPDATE(flag, __KEY_FLAG, !(flag & __KEY_FLAG)); - *value = iter->cur; - iter->state = __STRING | flag; - continue; - case '[': case '{': - if (!(flag & __ARRAY_FLAG)) { - /* At top-level we may be either in object context - * or in single-item context, and then we need to - * accept array/object here. - */ - if ((iter->state & __SUB_FLAG) && !(flag & __KEY_FLAG)) - _SPA_ERROR(EXPECTED_OBJECT_KEY); - SPA_FLAG_CLEAR(flag, __KEY_FLAG); - } - iter->state = __STRUCT | __SUB_FLAG | flag; - SPA_FLAG_UPDATE(iter->state, __ARRAY_FLAG, cur == '['); - - /* We need to remember previous array state across calls - * for depth=0, so store that in state. Others bits go to - * temporary stack. - */ - if (iter->depth == 0) { - SPA_FLAG_UPDATE(iter->state, __PREV_ARRAY_FLAG, flag & __ARRAY_FLAG); - } else if (((iter->depth-1) >> 6) < SPA_N_ELEMENTS(array_stack)) { - uint64_t mask = 1ULL << ((iter->depth-1) & 0x3f); - SPA_FLAG_UPDATE(array_stack[(iter->depth-1) >> 6], mask, flag & __ARRAY_FLAG); - } else { - /* too deep */ - _SPA_ERROR(TOO_DEEP_NESTING); - } - - *value = iter->cur; - if (++iter->depth > 1) - continue; - iter->cur++; - return 1; - case '}': case ']': - if ((flag & __ARRAY_FLAG) && cur != ']') - _SPA_ERROR(EXPECTED_ARRAY_CLOSE); - if (!(flag & __ARRAY_FLAG) && cur != '}') - _SPA_ERROR(EXPECTED_OBJECT_CLOSE); - if (flag & __KEY_FLAG) { - /* incomplete key-value pair */ - _SPA_ERROR(EXPECTED_OBJECT_VALUE); - } - iter->state = __STRUCT | __SUB_FLAG | flag; - if (iter->depth == 0) { - if (iter->parent) - iter->parent->cur = iter->cur; - else - _SPA_ERROR(MISMATCHED_BRACKET); - return 0; - } - --iter->depth; - if (iter->depth == 0) { - SPA_FLAG_UPDATE(iter->state, __ARRAY_FLAG, flag & __PREV_ARRAY_FLAG); - } else if (((iter->depth-1) >> 6) < SPA_N_ELEMENTS(array_stack)) { - uint64_t mask = 1ULL << ((iter->depth-1) & 0x3f); - SPA_FLAG_UPDATE(iter->state, __ARRAY_FLAG, - SPA_FLAG_IS_SET(array_stack[(iter->depth-1) >> 6], mask)); - } else { - /* too deep */ - _SPA_ERROR(TOO_DEEP_NESTING); - } - continue; - case '\\': - /* disallow bare escape */ - _SPA_ERROR(ESCAPE_NOT_ALLOWED); - default: - /* allow bare ascii */ - if (!(cur >= 32 && cur <= 126)) - _SPA_ERROR(CHARACTERS_NOT_ALLOWED); - if (flag & __KEY_FLAG) - flag |= __SUB_FLAG; - if (!(flag & __ARRAY_FLAG)) - SPA_FLAG_UPDATE(flag, __KEY_FLAG, !(flag & __KEY_FLAG)); - *value = iter->cur; - iter->state = __BARE | flag; - } - continue; - case __BARE: - switch (cur) { - case '\0': - case '\t': case ' ': case '\r': case '\n': - case '"': case '#': - case ':': case ',': case '=': case ']': case '}': - iter->state = __STRUCT | flag; - if (iter->depth > 0) - goto again; - return iter->cur - *value; - case '\\': - /* disallow bare escape */ - _SPA_ERROR(ESCAPE_NOT_ALLOWED); - default: - /* allow bare ascii */ - if (cur >= 32 && cur <= 126) - continue; - } - _SPA_ERROR(CHARACTERS_NOT_ALLOWED); - case __STRING: - switch (cur) { - case '\\': - iter->state = __ESC | flag; - continue; - case '"': - iter->state = __STRUCT | flag; - if (iter->depth > 0) - continue; - return ++iter->cur - *value; - case 240 ... 247: - utf8_remain++; - SPA_FALLTHROUGH; - case 224 ... 239: - utf8_remain++; - SPA_FALLTHROUGH; - case 192 ... 223: - utf8_remain++; - iter->state = __UTF8 | flag; - continue; - default: - if (cur >= 32 && cur <= 127) - continue; - } - _SPA_ERROR(CHARACTERS_NOT_ALLOWED); - case __UTF8: - switch (cur) { - case 128 ... 191: - if (--utf8_remain == 0) - iter->state = __STRING | flag; - continue; - } - _SPA_ERROR(CHARACTERS_NOT_ALLOWED); - case __ESC: - switch (cur) { - case '"': case '\\': case '/': case 'b': case 'f': - case 'n': case 'r': case 't': case 'u': - iter->state = __STRING | flag; - continue; - } - _SPA_ERROR(INVALID_ESCAPE); - case __COMMENT: - switch (cur) { - case '\n': case '\r': - iter->state = __STRUCT | flag; - } - break; - default: - _SPA_ERROR(INVALID_STATE); - } - - } - if (iter->depth != 0 || iter->parent) - _SPA_ERROR(MISMATCHED_BRACKET); - - switch (iter->state & ~__FLAGS) { - case __STRING: case __UTF8: case __ESC: - /* string/escape not closed */ - _SPA_ERROR(UNFINISHED_STRING); - case __COMMENT: - /* trailing comment */ - return 0; - } - - if ((iter->state & __SUB_FLAG) && (iter->state & __KEY_FLAG)) { - /* incomplete key-value pair */ - _SPA_ERROR(EXPECTED_OBJECT_VALUE); - } - - if ((iter->state & ~__FLAGS) != __STRUCT) { - iter->state = __STRUCT | (iter->state & __FLAGS); - return iter->cur - *value; - } - return 0; -#undef _SPA_ERROR - -error: - iter->state = err; - while (iter->parent) { - if (iter->parent->state & SPA_JSON_ERROR_FLAG) - break; - iter->parent->state = err; - iter->parent->cur = iter->cur; - iter = iter->parent; - } - return -1; + const char *value; + int len; + if ((len = spa_json_next(iter, &value)) <= 0) + return len; + return spa_json_parse_int(value, len, res); } -/** - * Return it there was a parse error, and its possible location. - * - * \since 1.1.0 - */ -static inline bool spa_json_get_error(struct spa_json *iter, const char *start, - struct spa_error_location *loc) +/* bool */ +SPA_API_JSON_UTILS int spa_json_get_bool(struct spa_json *iter, bool *res) { - static const char *reasons[] = { - "System error", - "Invalid array separator", - "Expected object key", - "Expected object value", - "Too deep nesting", - "Expected array close bracket", - "Expected object close brace", - "Mismatched bracket", - "Escape not allowed", - "Character not allowed", - "Invalid escape", - "Invalid state", - "Unfinished string", - "Expected key separator", - }; - - if (!(iter->state & SPA_JSON_ERROR_FLAG)) - return false; - - if (loc) { - int linepos = 1, colpos = 1, code; - const char *p, *l; + const char *value; + int len; + if ((len = spa_json_next(iter, &value)) <= 0) + return len; + return spa_json_parse_bool(value, len, res); +} - for (l = p = start; p && p != iter->cur; ++p) { - if (*p == '\n') { - linepos++; - colpos = 1; - l = p+1; - } else { - colpos++; - } - } - code = SPA_CLAMP(iter->state & 0xff, 0u, SPA_N_ELEMENTS(reasons)-1); - loc->line = linepos; - loc->col = colpos; - loc->location = l; - loc->len = SPA_PTRDIFF(iter->end, loc->location) / sizeof(char); - loc->reason = code == 0 ? strerror(errno) : reasons[code]; - } - return true; +/* string */ +SPA_API_JSON_UTILS int spa_json_get_string(struct spa_json *iter, char *res, int maxlen) +{ + const char *value; + int len; + if ((len = spa_json_next(iter, &value)) <= 0) + return len; + return spa_json_parse_stringn(value, len, res, maxlen); } -static inline int spa_json_enter_container(struct spa_json *iter, struct spa_json *sub, char type) + +SPA_API_JSON_UTILS int spa_json_enter_container(struct spa_json *iter, struct spa_json *sub, char type) { const char *value; int len; if ((len = spa_json_next(iter, &value)) <= 0) return len; + if (!spa_json_is_container(value, len)) + return -EPROTO; if (*value != type) - return -1; + return -EINVAL; spa_json_enter(iter, sub); return 1; } -static inline int spa_json_is_container(const char *val, int len) +SPA_API_JSON_UTILS int spa_json_begin_container(struct spa_json * iter, + const char *data, size_t size, char type, bool relax) { - return len > 0 && (*val == '{' || *val == '['); + int res; + spa_json_init(iter, data, size); + res = spa_json_enter_container(iter, iter, type); + if (res == -EPROTO && relax) + spa_json_init(iter, data, size); + else if (res <= 0) + return res; + return 1; } - /** * Return length of container at current position, starting at \a value. * * \return Length of container including {} or [], or 0 on error. */ -static inline int spa_json_container_len(struct spa_json *iter, const char *value, int len SPA_UNUSED) +SPA_API_JSON_UTILS int spa_json_container_len(struct spa_json *iter, const char *value, int len SPA_UNUSED) { const char *val; struct spa_json sub; @@ -391,287 +127,88 @@ static inline int spa_json_container_len(struct spa_json *iter, const char *valu } /* object */ -static inline int spa_json_is_object(const char *val, int len) -{ - return len > 0 && *val == '{'; -} -static inline int spa_json_enter_object(struct spa_json *iter, struct spa_json *sub) +SPA_API_JSON_UTILS int spa_json_enter_object(struct spa_json *iter, struct spa_json *sub) { return spa_json_enter_container(iter, sub, '{'); } - -/* array */ -static inline bool spa_json_is_array(const char *val, int len) -{ - return len > 0 && *val == '['; -} -static inline int spa_json_enter_array(struct spa_json *iter, struct spa_json *sub) -{ - return spa_json_enter_container(iter, sub, '['); -} - -/* null */ -static inline bool spa_json_is_null(const char *val, int len) -{ - return len == 4 && strncmp(val, "null", 4) == 0; -} - -/* float */ -static inline int spa_json_parse_float(const char *val, int len, float *result) +SPA_API_JSON_UTILS int spa_json_begin_object_relax(struct spa_json * iter, const char *data, size_t size) { - char buf[96]; - char *end; - int pos; - - if (len <= 0 || len >= (int)sizeof(buf)) - return 0; - - for (pos = 0; pos < len; ++pos) { - switch (val[pos]) { - case '+': case '-': case '0' ... '9': case '.': case 'e': case 'E': break; - default: return 0; - } - } - - memcpy(buf, val, len); - buf[len] = '\0'; - - *result = spa_strtof(buf, &end); - return len > 0 && end == buf + len; -} - -static inline bool spa_json_is_float(const char *val, int len) -{ - float dummy; - return spa_json_parse_float(val, len, &dummy); + return spa_json_begin_container(iter, data, size, '{', true); } -static inline int spa_json_get_float(struct spa_json *iter, float *res) +SPA_API_JSON_UTILS int spa_json_begin_object(struct spa_json * iter, const char *data, size_t size) { - const char *value; - int len; - if ((len = spa_json_next(iter, &value)) <= 0) - return len; - return spa_json_parse_float(value, len, res); + return spa_json_begin_container(iter, data, size, '{', false); } -static inline char *spa_json_format_float(char *str, int size, float val) +SPA_API_JSON_UTILS int spa_json_object_next(struct spa_json *iter, char *key, int maxkeylen, const char **value) { - if (SPA_UNLIKELY(!isnormal(val))) { - if (isinf(val)) - val = signbit(val) ? FLT_MIN : FLT_MAX; - else - val = 0.0f; + int res1, res2; + while (true) { + res1 = spa_json_get_string(iter, key, maxkeylen); + if (res1 <= 0 && res1 != -ENOSPC) + return res1; + res2 = spa_json_next(iter, value); + if (res2 <= 0 || res1 != -ENOSPC) + return res2; } - return spa_dtoa(str, size, val); -} - -/* int */ -static inline int spa_json_parse_int(const char *val, int len, int *result) -{ - char buf[64]; - char *end; - - if (len <= 0 || len >= (int)sizeof(buf)) - return 0; - - memcpy(buf, val, len); - buf[len] = '\0'; - - *result = strtol(buf, &end, 0); - return len > 0 && end == buf + len; -} -static inline bool spa_json_is_int(const char *val, int len) -{ - int dummy; - return spa_json_parse_int(val, len, &dummy); -} -static inline int spa_json_get_int(struct spa_json *iter, int *res) -{ - const char *value; - int len; - if ((len = spa_json_next(iter, &value)) <= 0) - return len; - return spa_json_parse_int(value, len, res); } -/* bool */ -static inline bool spa_json_is_true(const char *val, int len) +SPA_API_JSON_UTILS int spa_json_object_find(struct spa_json *iter, const char *key, const char **value) { - return len == 4 && strncmp(val, "true", 4) == 0; -} + struct spa_json obj = SPA_JSON_SAVE(iter); + int res, len = strlen(key) + 3; + char k[len]; -static inline bool spa_json_is_false(const char *val, int len) -{ - return len == 5 && strncmp(val, "false", 5) == 0; + while ((res = spa_json_object_next(&obj, k, len, value)) > 0) + if (spa_streq(k, key)) + return res; + return -ENOENT; } -static inline bool spa_json_is_bool(const char *val, int len) +SPA_API_JSON_UTILS int spa_json_str_object_find(const char *obj, size_t obj_len, + const char *key, char *value, size_t maxlen) { - return spa_json_is_true(val, len) || spa_json_is_false(val, len); -} + struct spa_json iter; + int l; + const char *v; -static inline int spa_json_parse_bool(const char *val, int len, bool *result) -{ - if ((*result = spa_json_is_true(val, len))) - return 1; - if (!(*result = !spa_json_is_false(val, len))) - return 1; - return -1; -} -static inline int spa_json_get_bool(struct spa_json *iter, bool *res) -{ - const char *value; - int len; - if ((len = spa_json_next(iter, &value)) <= 0) - return len; - return spa_json_parse_bool(value, len, res); -} - -/* string */ -static inline bool spa_json_is_string(const char *val, int len) -{ - return len > 1 && *val == '"'; + if (spa_json_begin_object(&iter, obj, obj_len) <= 0) + return -EINVAL; + if ((l = spa_json_object_find(&iter, key, &v)) <= 0) + return l; + return spa_json_parse_stringn(v, l, value, maxlen); } -static inline int spa_json_parse_hex(const char *p, int num, uint32_t *res) +/* array */ +SPA_API_JSON_UTILS int spa_json_enter_array(struct spa_json *iter, struct spa_json *sub) { - int i; - *res = 0; - for (i = 0; i < num; i++) { - char v = p[i]; - if (v >= '0' && v <= '9') - v = v - '0'; - else if (v >= 'a' && v <= 'f') - v = v - 'a' + 10; - else if (v >= 'A' && v <= 'F') - v = v - 'A' + 10; - else - return -1; - *res = (*res << 4) | v; - } - return 1; + return spa_json_enter_container(iter, sub, '['); } - -static inline int spa_json_parse_stringn(const char *val, int len, char *result, int maxlen) +SPA_API_JSON_UTILS int spa_json_begin_array_relax(struct spa_json * iter, const char *data, size_t size) { - const char *p; - if (maxlen <= len) - return -1; - if (!spa_json_is_string(val, len)) { - if (result != val) - memmove(result, val, len); - result += len; - } else { - for (p = val+1; p < val + len; p++) { - if (*p == '\\') { - p++; - if (*p == 'n') - *result++ = '\n'; - else if (*p == 'r') - *result++ = '\r'; - else if (*p == 'b') - *result++ = '\b'; - else if (*p == 't') - *result++ = '\t'; - else if (*p == 'f') - *result++ = '\f'; - else if (*p == 'u') { - uint8_t prefix[] = { 0, 0xc0, 0xe0, 0xf0 }; - uint32_t idx, n, v, cp, enc[] = { 0x80, 0x800, 0x10000 }; - if (val + len - p < 5 || - spa_json_parse_hex(p+1, 4, &cp) < 0) { - *result++ = *p; - continue; - } - p += 4; - - if (cp >= 0xd800 && cp <= 0xdbff) { - if (val + len - p < 7 || - p[1] != '\\' || p[2] != 'u' || - spa_json_parse_hex(p+3, 4, &v) < 0 || - v < 0xdc00 || v > 0xdfff) - continue; - p += 6; - cp = 0x010000 + (((cp & 0x3ff) << 10) | (v & 0x3ff)); - } else if (cp >= 0xdc00 && cp <= 0xdfff) - continue; - - for (idx = 0; idx < 3; idx++) - if (cp < enc[idx]) - break; - for (n = idx; n > 0; n--, cp >>= 6) - result[n] = (cp | 0x80) & 0xbf; - *result++ = (cp | prefix[idx]) & 0xff; - result += idx; - } else - *result++ = *p; - } else if (*p == '\"') { - break; - } else - *result++ = *p; - } - } - *result = '\0'; - return 1; + return spa_json_begin_container(iter, data, size, '[', true); } - -static inline int spa_json_parse_string(const char *val, int len, char *result) +SPA_API_JSON_UTILS int spa_json_begin_array(struct spa_json * iter, const char *data, size_t size) { - return spa_json_parse_stringn(val, len, result, len+1); + return spa_json_begin_container(iter, data, size, '[', false); } -static inline int spa_json_get_string(struct spa_json *iter, char *res, int maxlen) -{ - const char *value; - int len; - if ((len = spa_json_next(iter, &value)) <= 0) - return len; - return spa_json_parse_stringn(value, len, res, maxlen); +#define spa_json_make_str_array_unpack(maxlen,type,conv) \ +{ \ + struct spa_json iter; \ + char v[maxlen]; \ + uint32_t count = 0; \ + if (spa_json_begin_array_relax(&iter, arr, arr_len) <= 0) \ + return -EINVAL; \ + while (spa_json_get_string(&iter, v, sizeof(v)) > 0 && count < max) \ + values[count++] = conv(v); \ + return count; \ } -static inline int spa_json_encode_string(char *str, int size, const char *val) +SPA_API_JSON_UTILS int spa_json_str_array_uint32(const char *arr, size_t arr_len, + uint32_t *values, size_t max) { - int len = 0; - static const char hex[] = { "0123456789abcdef" }; -#define __PUT(c) { if (len < size) *str++ = c; len++; } - __PUT('"'); - while (*val) { - switch (*val) { - case '\n': - __PUT('\\'); __PUT('n'); - break; - case '\r': - __PUT('\\'); __PUT('r'); - break; - case '\b': - __PUT('\\'); __PUT('b'); - break; - case '\t': - __PUT('\\'); __PUT('t'); - break; - case '\f': - __PUT('\\'); __PUT('f'); - break; - case '\\': - case '"': - __PUT('\\'); __PUT(*val); - break; - default: - if (*val > 0 && *val < 0x20) { - __PUT('\\'); __PUT('u'); - __PUT('0'); __PUT('0'); - __PUT(hex[((*val)>>4)&0xf]); __PUT(hex[(*val)&0xf]); - } else { - __PUT(*val); - } - break; - } - val++; - } - __PUT('"'); - __PUT('\0'); -#undef __PUT - return len-1; + spa_json_make_str_array_unpack(32,uint32_t, atoi); } /** @@ -682,4 +219,4 @@ static inline int spa_json_encode_string(char *str, int size, const char *val) } /* extern "C" */ #endif -#endif /* SPA_UTILS_JSON_H */ +#endif /* SPA_UTILS_JSON_UTILS_H */ diff --git a/spa/include/spa/utils/keys.h b/spa/include/spa/utils/keys.h index 48b94c50..12a8cfab 100644 --- a/spa/include/spa/utils/keys.h +++ b/spa/include/spa/utils/keys.h @@ -45,6 +45,14 @@ extern "C" { #define SPA_KEY_API_ALSA_DISABLE_LONGNAME \ "api.alsa.disable-longname" /**< if card long name should not be passed to MIDI port */ #define SPA_KEY_API_ALSA_BIND_CTLS "api.alsa.bind-ctls" /**< alsa controls to bind as params */ +#define SPA_KEY_API_ALSA_SPLIT_ENABLE "api.alsa.split-enable" /**< For UCM devices with split PCMs, don't split to + * multiple PCMs using alsa-lib plugins, but instead + * add api.alsa.split properties to emitted nodes + * with PCM splitting information. + */ +#define SPA_KEY_API_ALSA_SPLIT_PARENT "api.alsa.split.parent" /**< PCM is UCM SplitPCM parent PCM, + * to be opened with SplitPCM set. + */ /** info from alsa card_info */ #define SPA_KEY_API_ALSA_CARD_ID "api.alsa.card.id" /**< id from card_info */ @@ -67,6 +75,15 @@ extern "C" { #define SPA_KEY_API_ALSA_PCM_SUBCLASS "api.alsa.pcm.subclass" /**< subclass from pcm_info as string */ #define SPA_KEY_API_ALSA_PCM_SYNC_ID "api.alsa.pcm.sync-id" /**< sync id */ +#define SPA_KEY_API_ALSA_SPLIT_POSITION "api.alsa.split.position" /**< (SPA JSON list) If present, this is a + * virtual device corresponding to a subset of + * channels in an underlying PCM, listed in this + * property. The \ref SPA_KEY_API_ALSA_PATH + * contains the underlying split PCM. */ +#define SPA_KEY_API_ALSA_SPLIT_HW_POSITION \ + "api.alsa.split.hw-position" /**< (SPA JSON list) Channel map of the + * underlying split PCM. */ + /** keys for v4l2 api */ #define SPA_KEY_API_V4L2 "api.v4l2" /**< key for the v4l2 api */ #define SPA_KEY_API_V4L2_PATH "api.v4l2.path" /**< v4l2 device path as can be diff --git a/spa/include/spa/utils/list.h b/spa/include/spa/utils/list.h index 0c03854b..60c89f23 100644 --- a/spa/include/spa/utils/list.h +++ b/spa/include/spa/utils/list.h @@ -9,6 +9,16 @@ extern "C" { #endif +#include <spa/utils/defs.h> + +#ifndef SPA_API_LIST + #ifdef SPA_API_IMPL + #define SPA_API_LIST SPA_API_IMPL + #else + #define SPA_API_LIST static inline + #endif +#endif + /** * \defgroup spa_list List * Doubly linked list data structure @@ -26,19 +36,19 @@ struct spa_list { #define SPA_LIST_INIT(list) ((struct spa_list){ (list), (list) }) -static inline void spa_list_init(struct spa_list *list) +SPA_API_LIST void spa_list_init(struct spa_list *list) { *list = SPA_LIST_INIT(list); } -static inline int spa_list_is_initialized(struct spa_list *list) +SPA_API_LIST 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) +SPA_API_LIST void spa_list_insert(struct spa_list *list, struct spa_list *elem) { elem->prev = list; elem->next = list->next; @@ -46,7 +56,7 @@ static inline void spa_list_insert(struct spa_list *list, struct spa_list *elem) elem->next->prev = elem; } -static inline void spa_list_insert_list(struct spa_list *list, struct spa_list *other) +SPA_API_LIST void spa_list_insert_list(struct spa_list *list, struct spa_list *other) { if (spa_list_is_empty(other)) return; @@ -56,7 +66,7 @@ static inline void spa_list_insert_list(struct spa_list *list, struct spa_list * list->next = other->next; } -static inline void spa_list_remove(struct spa_list *elem) +SPA_API_LIST void spa_list_remove(struct spa_list *elem) { elem->prev->next = elem->next; elem->next->prev = elem->prev; diff --git a/spa/include/spa/utils/names.h b/spa/include/spa/utils/names.h index f2f73bb4..a3f51901 100644 --- a/spa/include/spa/utils/names.h +++ b/spa/include/spa/utils/names.h @@ -73,6 +73,8 @@ extern "C" { #define SPA_NAME_VIDEO_CONVERT "video.convert" /**< converts raw video from one format * to another. Must include at least * format and scaling */ +#define SPA_NAME_VIDEO_CONVERT_DUMMY "video.convert.dummy" /**< a dummy converter as fallback for the + * videoadapter node */ #define SPA_NAME_VIDEO_ADAPT "video.adapt" /**< combination of a node and a * video.convert. */ /** keys for alsa factory names */ diff --git a/spa/include/spa/utils/ratelimit.h b/spa/include/spa/utils/ratelimit.h index ef1b9412..2af2c26b 100644 --- a/spa/include/spa/utils/ratelimit.h +++ b/spa/include/spa/utils/ratelimit.h @@ -12,6 +12,16 @@ extern "C" { #include <inttypes.h> #include <stddef.h> +#include <spa/utils/defs.h> + +#ifndef SPA_API_RATELIMIT + #ifdef SPA_API_IMPL + #define SPA_API_RATELIMIT SPA_API_IMPL + #else + #define SPA_API_RATELIMIT static inline + #endif +#endif + struct spa_ratelimit { uint64_t interval; uint64_t begin; @@ -20,7 +30,7 @@ struct spa_ratelimit { unsigned n_suppressed; }; -static inline int spa_ratelimit_test(struct spa_ratelimit *r, uint64_t now) +SPA_API_RATELIMIT int spa_ratelimit_test(struct spa_ratelimit *r, uint64_t now) { unsigned suppressed = 0; if (r->begin + r->interval < now) { diff --git a/spa/include/spa/utils/result.h b/spa/include/spa/utils/result.h index 7f389c2c..312a6bb0 100644 --- a/spa/include/spa/utils/result.h +++ b/spa/include/spa/utils/result.h @@ -19,8 +19,18 @@ extern "C" { * \{ */ +#include <errno.h> + #include <spa/utils/defs.h> -#include <spa/utils/list.h> + +#ifndef SPA_API_RESULT + #ifdef SPA_API_IMPL + #define SPA_API_RESULT SPA_API_IMPL + #else + #define SPA_API_RESULT static inline + #endif +#endif + #define SPA_ASYNC_BIT (1 << 30) #define SPA_ASYNC_SEQ_MASK (SPA_ASYNC_BIT - 1) @@ -33,13 +43,13 @@ extern "C" { #define SPA_RESULT_ASYNC_SEQ(res) ((res) & SPA_ASYNC_SEQ_MASK) #define SPA_RESULT_RETURN_ASYNC(seq) (SPA_ASYNC_BIT | SPA_RESULT_ASYNC_SEQ(seq)) -#define spa_strerror(err) \ -({ \ - int _err = -(err); \ - if (SPA_RESULT_IS_ASYNC(err)) \ - _err = EINPROGRESS; \ - strerror(_err); \ -}) +SPA_API_RESULT const char *spa_strerror(int err) +{ + int _err = -(err); + if (SPA_RESULT_IS_ASYNC(err)) + _err = EINPROGRESS; + return strerror(_err); +} /** * \} diff --git a/spa/include/spa/utils/ringbuffer.h b/spa/include/spa/utils/ringbuffer.h index 51b502d7..e8c5d625 100644 --- a/spa/include/spa/utils/ringbuffer.h +++ b/spa/include/spa/utils/ringbuffer.h @@ -25,6 +25,14 @@ struct spa_ringbuffer; #include <spa/utils/defs.h> +#ifndef SPA_API_RINGBUFFER + #ifdef SPA_API_IMPL + #define SPA_API_RINGBUFFER SPA_API_IMPL + #else + #define SPA_API_RINGBUFFER static inline + #endif +#endif + /** * A ringbuffer type. */ @@ -40,7 +48,7 @@ struct spa_ringbuffer { * * \param rbuf a spa_ringbuffer */ -static inline void spa_ringbuffer_init(struct spa_ringbuffer *rbuf) +SPA_API_RINGBUFFER void spa_ringbuffer_init(struct spa_ringbuffer *rbuf) { *rbuf = SPA_RINGBUFFER_INIT(); } @@ -51,7 +59,7 @@ static inline void spa_ringbuffer_init(struct spa_ringbuffer *rbuf) * \param rbuf a spa_ringbuffer * \param size the target size of \a rbuf */ -static inline void spa_ringbuffer_set_avail(struct spa_ringbuffer *rbuf, uint32_t size) +SPA_API_RINGBUFFER void spa_ringbuffer_set_avail(struct spa_ringbuffer *rbuf, uint32_t size) { rbuf->readindex = 0; rbuf->writeindex = size; @@ -67,7 +75,7 @@ static inline void spa_ringbuffer_set_avail(struct spa_ringbuffer *rbuf, uint32_ * there was an underrun. values > rbuf->size means there * was an overrun. */ -static inline int32_t spa_ringbuffer_get_read_index(struct spa_ringbuffer *rbuf, uint32_t *index) +SPA_API_RINGBUFFER int32_t spa_ringbuffer_get_read_index(struct spa_ringbuffer *rbuf, uint32_t *index) { *index = __atomic_load_n(&rbuf->readindex, __ATOMIC_RELAXED); return (int32_t) (__atomic_load_n(&rbuf->writeindex, __ATOMIC_ACQUIRE) - *index); @@ -84,7 +92,7 @@ static inline int32_t spa_ringbuffer_get_read_index(struct spa_ringbuffer *rbuf, * \param data destination memory * \param len number of bytes to read */ -static inline void +SPA_API_RINGBUFFER void spa_ringbuffer_read_data(struct spa_ringbuffer *rbuf SPA_UNUSED, const void *buffer, uint32_t size, uint32_t offset, void *data, uint32_t len) @@ -101,7 +109,7 @@ spa_ringbuffer_read_data(struct spa_ringbuffer *rbuf SPA_UNUSED, * \param rbuf a spa_ringbuffer * \param index new index */ -static inline void spa_ringbuffer_read_update(struct spa_ringbuffer *rbuf, int32_t index) +SPA_API_RINGBUFFER void spa_ringbuffer_read_update(struct spa_ringbuffer *rbuf, int32_t index) { __atomic_store_n(&rbuf->readindex, index, __ATOMIC_RELEASE); } @@ -117,7 +125,7 @@ static inline void spa_ringbuffer_read_update(struct spa_ringbuffer *rbuf, int32 * was an overrun. Subtract from the buffer size to get * the number of bytes available for writing. */ -static inline int32_t spa_ringbuffer_get_write_index(struct spa_ringbuffer *rbuf, uint32_t *index) +SPA_API_RINGBUFFER int32_t spa_ringbuffer_get_write_index(struct spa_ringbuffer *rbuf, uint32_t *index) { *index = __atomic_load_n(&rbuf->writeindex, __ATOMIC_RELAXED); return (int32_t) (*index - __atomic_load_n(&rbuf->readindex, __ATOMIC_ACQUIRE)); @@ -134,7 +142,7 @@ static inline int32_t spa_ringbuffer_get_write_index(struct spa_ringbuffer *rbuf * \param data source memory * \param len number of bytes to write */ -static inline void +SPA_API_RINGBUFFER void spa_ringbuffer_write_data(struct spa_ringbuffer *rbuf SPA_UNUSED, void *buffer, uint32_t size, uint32_t offset, const void *data, uint32_t len) @@ -151,7 +159,7 @@ spa_ringbuffer_write_data(struct spa_ringbuffer *rbuf SPA_UNUSED, * \param rbuf a spa_ringbuffer * \param index new index */ -static inline void spa_ringbuffer_write_update(struct spa_ringbuffer *rbuf, int32_t index) +SPA_API_RINGBUFFER void spa_ringbuffer_write_update(struct spa_ringbuffer *rbuf, int32_t index) { __atomic_store_n(&rbuf->writeindex, index, __ATOMIC_RELEASE); } diff --git a/spa/include/spa/utils/string.h b/spa/include/spa/utils/string.h index 6ee9e2cd..060ef7d6 100644 --- a/spa/include/spa/utils/string.h +++ b/spa/include/spa/utils/string.h @@ -17,6 +17,14 @@ extern "C" { #include <spa/utils/defs.h> +#ifndef SPA_API_STRING + #ifdef SPA_API_IMPL + #define SPA_API_STRING SPA_API_IMPL + #else + #define SPA_API_STRING static inline + #endif +#endif + /** * \defgroup spa_string String handling * String handling utilities @@ -33,7 +41,7 @@ extern "C" { * If both \a a and \a b are NULL, the two are considered equal. * */ -static inline bool spa_streq(const char *s1, const char *s2) +SPA_API_STRING bool spa_streq(const char *s1, const char *s2) { return SPA_LIKELY(s1 && s2) ? strcmp(s1, s2) == 0 : s1 == s2; } @@ -43,7 +51,7 @@ static inline bool spa_streq(const char *s1, const char *s2) * * If both \a a and \a b are NULL, the two are considered equal. */ -static inline bool spa_strneq(const char *s1, const char *s2, size_t len) +SPA_API_STRING bool spa_strneq(const char *s1, const char *s2, size_t len) { return SPA_LIKELY(s1 && s2) ? strncmp(s1, s2, len) == 0 : s1 == s2; } @@ -54,7 +62,7 @@ static inline bool spa_strneq(const char *s1, const char *s2, size_t len) * A \a s is NULL, it never starts with the given \a prefix. A \a prefix of * NULL is a bug in the caller. */ -static inline bool spa_strstartswith(const char *s, const char *prefix) +SPA_API_STRING bool spa_strstartswith(const char *s, const char *prefix) { if (SPA_UNLIKELY(s == NULL)) return false; @@ -70,7 +78,7 @@ static inline bool spa_strstartswith(const char *s, const char *prefix) * A \a s is NULL, it never ends with the given \a suffix. A \a suffix of * NULL is a bug in the caller. */ -static inline bool spa_strendswith(const char *s, const char *suffix) +SPA_API_STRING bool spa_strendswith(const char *s, const char *suffix) { size_t l1, l2; @@ -92,7 +100,7 @@ static inline bool spa_strendswith(const char *s, const char *suffix) * * \return true on success, false otherwise */ -static inline bool spa_atoi32(const char *str, int32_t *val, int base) +SPA_API_STRING bool spa_atoi32(const char *str, int32_t *val, int base) { char *endptr; long v; @@ -120,7 +128,7 @@ static inline bool spa_atoi32(const char *str, int32_t *val, int base) * * \return true on success, false otherwise */ -static inline bool spa_atou32(const char *str, uint32_t *val, int base) +SPA_API_STRING bool spa_atou32(const char *str, uint32_t *val, int base) { char *endptr; unsigned long long v; @@ -148,7 +156,7 @@ static inline bool spa_atou32(const char *str, uint32_t *val, int base) * * \return true on success, false otherwise */ -static inline bool spa_atoi64(const char *str, int64_t *val, int base) +SPA_API_STRING bool spa_atoi64(const char *str, int64_t *val, int base) { char *endptr; long long v; @@ -173,7 +181,7 @@ static inline bool spa_atoi64(const char *str, int64_t *val, int base) * * \return true on success, false otherwise */ -static inline bool spa_atou64(const char *str, uint64_t *val, int base) +SPA_API_STRING bool spa_atou64(const char *str, uint64_t *val, int base) { char *endptr; unsigned long long v; @@ -196,7 +204,7 @@ static inline bool spa_atou64(const char *str, uint64_t *val, int base) * * \return true on success, false otherwise */ -static inline bool spa_atob(const char *str) +SPA_API_STRING bool spa_atob(const char *str) { return spa_streq(str, "true") || spa_streq(str, "1"); } @@ -210,7 +218,7 @@ static inline bool spa_atob(const char *str) * number on error. */ SPA_PRINTF_FUNC(3, 0) -static inline int spa_vscnprintf(char *buffer, size_t size, const char *format, va_list args) +SPA_API_STRING int spa_vscnprintf(char *buffer, size_t size, const char *format, va_list args) { int r; @@ -233,7 +241,7 @@ static inline int spa_vscnprintf(char *buffer, size_t size, const char *format, * number on error. */ SPA_PRINTF_FUNC(3, 4) -static inline int spa_scnprintf(char *buffer, size_t size, const char *format, ...) +SPA_API_STRING int spa_scnprintf(char *buffer, size_t size, const char *format, ...) { int r; va_list args; @@ -253,7 +261,7 @@ static inline int spa_scnprintf(char *buffer, size_t size, const char *format, . * * \return the result float. */ -static inline float spa_strtof(const char *str, char **endptr) +SPA_API_STRING float spa_strtof(const char *str, char **endptr) { #ifndef __LOCALE_C_ONLY static locale_t locale = NULL; @@ -279,7 +287,7 @@ static inline float spa_strtof(const char *str, char **endptr) * * \return true on success, false otherwise */ -static inline bool spa_atof(const char *str, float *val) +SPA_API_STRING bool spa_atof(const char *str, float *val) { char *endptr; float v; @@ -303,7 +311,7 @@ static inline bool spa_atof(const char *str, float *val) * * \return the result float. */ -static inline double spa_strtod(const char *str, char **endptr) +SPA_API_STRING double spa_strtod(const char *str, char **endptr) { #ifndef __LOCALE_C_ONLY static locale_t locale = NULL; @@ -329,7 +337,7 @@ static inline double spa_strtod(const char *str, char **endptr) * * \return true on success, false otherwise */ -static inline bool spa_atod(const char *str, double *val) +SPA_API_STRING bool spa_atod(const char *str, double *val) { char *endptr; double v; @@ -346,7 +354,7 @@ static inline bool spa_atod(const char *str, double *val) return true; } -static inline char *spa_dtoa(char *str, size_t size, double val) +SPA_API_STRING char *spa_dtoa(char *str, size_t size, double val) { int i, l; l = spa_scnprintf(str, size, "%f", val); @@ -362,7 +370,7 @@ struct spa_strbuf { size_t pos; }; -static inline void spa_strbuf_init(struct spa_strbuf *buf, char *buffer, size_t maxsize) +SPA_API_STRING void spa_strbuf_init(struct spa_strbuf *buf, char *buffer, size_t maxsize) { buf->buffer = buffer; buf->maxsize = maxsize; @@ -372,7 +380,7 @@ static inline void spa_strbuf_init(struct spa_strbuf *buf, char *buffer, size_t } SPA_PRINTF_FUNC(2, 3) -static inline int spa_strbuf_append(struct spa_strbuf *buf, const char *fmt, ...) +SPA_API_STRING int spa_strbuf_append(struct spa_strbuf *buf, const char *fmt, ...) { size_t remain = buf->maxsize - buf->pos; ssize_t written; diff --git a/spa/include/spa/utils/type-info.h b/spa/include/spa/utils/type-info.h index 1d9cd29b..9ee2f3ab 100644 --- a/spa/include/spa/utils/type-info.h +++ b/spa/include/spa/utils/type-info.h @@ -20,10 +20,6 @@ extern "C" { #define SPA_TYPE_ROOT spa_types #endif -static inline bool spa_type_is_a(const char *type, const char *parent) -{ - return type != NULL && parent != NULL && strncmp(type, parent, strlen(parent)) == 0; -} #include <spa/utils/type.h> #include <spa/utils/enum-types.h> diff --git a/spa/include/spa/utils/type.h b/spa/include/spa/utils/type.h index 65610c11..88de2c62 100644 --- a/spa/include/spa/utils/type.h +++ b/spa/include/spa/utils/type.h @@ -10,6 +10,15 @@ extern "C" { #endif #include <spa/utils/defs.h> +#include <spa/utils/string.h> + +#ifndef SPA_API_TYPE + #ifdef SPA_API_IMPL + #define SPA_API_TYPE SPA_API_IMPL + #else + #define SPA_API_TYPE static inline + #endif +#endif /** \defgroup spa_types Types * Data type information enumerations @@ -123,6 +132,47 @@ struct spa_type_info { const struct spa_type_info *values; }; +SPA_API_TYPE bool spa_type_is_a(const char *type, const char *parent) +{ + return type != NULL && parent != NULL && strncmp(type, parent, strlen(parent)) == 0; +} + +SPA_API_TYPE const char *spa_type_short_name(const char *name) +{ + const char *h; + if ((h = strrchr(name, ':')) != NULL) + name = h + 1; + return name; +} + +SPA_API_TYPE uint32_t spa_type_from_short_name(const char *name, + const struct spa_type_info *info, uint32_t unknown) +{ + int i; + for (i = 0; info[i].name; i++) { + if (spa_streq(name, spa_type_short_name(info[i].name))) + return info[i].type; + } + return unknown; +} +SPA_API_TYPE const char * spa_type_to_name(uint32_t type, + const struct spa_type_info *info, const char *unknown) +{ + int i; + for (i = 0; info[i].name; i++) { + if (info[i].type == type) + return info[i].name; + } + return unknown; +} + +SPA_API_TYPE const char * spa_type_to_short_name(uint32_t type, + const struct spa_type_info *info, const char *unknown) +{ + const char *n = spa_type_to_name(type, info, unknown); + return n ? spa_type_short_name(n) : NULL; +} + /** * \} */ diff --git a/spa/lib/lib.c b/spa/lib/lib.c new file mode 100644 index 00000000..e2acb9cc --- /dev/null +++ b/spa/lib/lib.c @@ -0,0 +1,161 @@ + +#define SPA_API_IMPL SPA_EXPORT +#include <spa/utils/defs.h> +#include <spa/buffer/alloc.h> +#include <spa/buffer/buffer.h> +#include <spa/buffer/type-info.h> +#include <spa/control/control.h> +#include <spa/control/type-info.h> +#include <spa/control/ump-utils.h> +#include <spa/debug/buffer.h> +#include <spa/debug/context.h> +#include <spa/debug/dict.h> +#include <spa/debug/file.h> +#include <spa/debug/format.h> +#include <spa/debug/log.h> +#include <spa/debug/mem.h> +#include <spa/debug/node.h> +#include <spa/debug/pod.h> +#include <spa/debug/types.h> +#include <spa/filter-graph/filter-graph.h> +#include <spa/graph/graph.h> +#include <spa/interfaces/audio/aec.h> +#include <spa/monitor/device.h> +#include <spa/monitor/event.h> +#include <spa/monitor/type-info.h> +#include <spa/monitor/utils.h> +#include <spa/node/command.h> +#include <spa/node/event.h> +#include <spa/node/io.h> +#include <spa/node/keys.h> +#include <spa/node/node.h> +#include <spa/node/type-info.h> +#include <spa/node/utils.h> +#include <spa/param/audio/aac.h> +#include <spa/param/audio/aac-types.h> +#include <spa/param/audio/aac-utils.h> +#include <spa/param/audio/alac.h> +#include <spa/param/audio/alac-utils.h> +#include <spa/param/audio/amr.h> +#include <spa/param/audio/amr-types.h> +#include <spa/param/audio/amr-utils.h> +#include <spa/param/audio/ape.h> +#include <spa/param/audio/ape-utils.h> +#include <spa/param/audio/compressed.h> +#include <spa/param/audio/dsd.h> +#include <spa/param/audio/dsd-utils.h> +#include <spa/param/audio/dsp.h> +#include <spa/param/audio/dsp-utils.h> +#include <spa/param/audio/flac.h> +#include <spa/param/audio/flac-utils.h> +#include <spa/param/audio/format.h> +#include <spa/param/audio/format-utils.h> +#include <spa/param/audio/iec958.h> +#include <spa/param/audio/iec958-types.h> +#include <spa/param/audio/iec958-utils.h> +#include <spa/param/audio/layout.h> +#include <spa/param/audio/mp3.h> +#include <spa/param/audio/mp3-types.h> +#include <spa/param/audio/mp3-utils.h> +#include <spa/param/audio/opus.h> +#include <spa/param/audio/ra.h> +#include <spa/param/audio/ra-utils.h> +#include <spa/param/audio/raw.h> +#include <spa/param/audio/raw-json.h> +#include <spa/param/audio/raw-types.h> +#include <spa/param/audio/raw-utils.h> +#include <spa/param/audio/type-info.h> +#include <spa/param/audio/vorbis.h> +#include <spa/param/audio/vorbis-utils.h> +#include <spa/param/audio/wma.h> +#include <spa/param/audio/wma-types.h> +#include <spa/param/audio/wma-utils.h> +#include <spa/param/bluetooth/audio.h> +#include <spa/param/bluetooth/type-info.h> +#include <spa/param/buffers.h> +#include <spa/param/buffers-types.h> +#include <spa/param/format.h> +#include <spa/param/format-types.h> +#include <spa/param/format-utils.h> +#include <spa/param/latency.h> +#include <spa/param/latency-types.h> +#include <spa/param/latency-utils.h> +#include <spa/param/param.h> +#include <spa/param/param-types.h> +#include <spa/param/port-config.h> +#include <spa/param/port-config-types.h> +#include <spa/param/profile.h> +#include <spa/param/profiler.h> +#include <spa/param/profiler-types.h> +#include <spa/param/profile-types.h> +#include <spa/param/props.h> +#include <spa/param/props-types.h> +#include <spa/param/route.h> +#include <spa/param/route-types.h> +#include <spa/param/tag.h> +#include <spa/param/tag-types.h> +#include <spa/param/tag-utils.h> +#include <spa/param/type-info.h> +#include <spa/param/video/chroma.h> +#include <spa/param/video/color.h> +#include <spa/param/video/dsp.h> +#include <spa/param/video/dsp-utils.h> +#include <spa/param/video/encoded.h> +#include <spa/param/video/format.h> +#include <spa/param/video/format-utils.h> +#include <spa/param/video/h264.h> +#include <spa/param/video/h264-utils.h> +#include <spa/param/video/mjpg.h> +#include <spa/param/video/mjpg-utils.h> +#include <spa/param/video/multiview.h> +#include <spa/param/video/raw.h> +#include <spa/param/video/raw-types.h> +#include <spa/param/video/raw-utils.h> +#include <spa/param/video/type-info.h> +#include <spa/pod/builder.h> +#include <spa/pod/command.h> +#include <spa/pod/compare.h> +#include <spa/pod/dynamic.h> +#include <spa/pod/event.h> +#include <spa/pod/filter.h> +#include <spa/pod/iter.h> +#include <spa/pod/parser.h> +#include <spa/pod/pod.h> +#include <spa/pod/vararg.h> +#include <spa/support/cpu.h> +#include <spa/support/dbus.h> +#include <spa/support/i18n.h> +#include <spa/support/log.h> +#include <spa/support/log-impl.h> +#include <spa/support/loop.h> +#include <spa/support/plugin.h> +#include <spa/support/plugin-loader.h> +#include <spa/support/system.h> +#include <spa/support/thread.h> +#include <spa/utils/ansi.h> +#include <spa/utils/atomic.h> +#include <spa/utils/cleanup.h> +#include <spa/utils/defs.h> +#include <spa/utils/dict.h> +#include <spa/utils/dll.h> +#include <spa/utils/endian.h> +#include <spa/utils/enum-types.h> +#include <spa/utils/hook.h> +#include <spa/utils/json-core.h> +#include <spa/utils/json.h> +#include <spa/utils/json-pod.h> +#include <spa/utils/keys.h> +#include <spa/utils/list.h> +#include <spa/utils/names.h> +#include <spa/utils/ratelimit.h> +#include <spa/utils/result.h> +#include <spa/utils/ringbuffer.h> +#include <spa/utils/string.h> +#include <spa/utils/type.h> +#include <spa/utils/type-info.h> + + + + + + diff --git a/spa/lib/meson.build b/spa/lib/meson.build new file mode 100644 index 00000000..a12c3043 --- /dev/null +++ b/spa/lib/meson.build @@ -0,0 +1,6 @@ +spa_lib = shared_library('spa', + [ 'lib.c' ], + include_directories : [ configinc ], + dependencies : [ spa_dep, pthread_lib, mathlib ], + install : true, + install_dir : spa_plugindir ) diff --git a/spa/meson.build b/spa/meson.build index cf25609d..9b5f8960 100644 --- a/spa/meson.build +++ b/spa/meson.build @@ -43,7 +43,7 @@ if get_option('spa-plugins').allowed() endif # plugin-specific dependencies - alsa_dep = dependency('alsa', required: get_option('alsa')) + alsa_dep = dependency('alsa', version : '>=1.2.10', required: get_option('alsa')) summary({'ALSA': alsa_dep.found()}, bool_yn: true, section: 'Backend') bluez_dep = dependency('bluez', version : '>= 4.101', required: get_option('bluez5')) @@ -89,6 +89,9 @@ if get_option('spa-plugins').allowed() summary({'ModemManager': mm_dep.found()}, bool_yn: true, section: 'Bluetooth backends') endif cdata.set('HAVE_LC3', get_option('bluez5-codec-lc3').allowed() and lc3_dep.found()) + g722_codec_option = get_option('bluez5-codec-g722') + summary({'G722': g722_codec_option.allowed()}, bool_yn: true, section: 'Bluetooth audio codecs') + cdata.set('HAVE_G722', g722_codec_option.allowed()) endif have_vulkan = false @@ -111,10 +114,20 @@ if get_option('spa-plugins').allowed() cdata.set('HAVE_LIBUDEV', libudev_dep.found()) summary({'Udev': libudev_dep.found()}, bool_yn: true, section: 'Backend') - cdata.set('HAVE_SPA_PLUGINS', '1') + libmysofa_dep = dependency('libmysofa', required : get_option('libmysofa')) + summary({'libmysofa': libmysofa_dep.found()}, bool_yn: true, section: 'filter-graph') + + lilv_lib = dependency('lilv-0', required: get_option('lv2')) + summary({'lilv (for lv2 plugins)': lilv_lib.found()}, bool_yn: true, section: 'filter-graph') + + ebur128_lib = dependency('libebur128', required: get_option('ebur128').enabled()) + summary({'EBUR128': ebur128_lib.found()}, bool_yn: true, section: 'filter-graph') + + cdata.set('HAVE_SPA_PLUGINS', true) subdir('plugins') endif subdir('tools') subdir('tests') subdir('examples') +subdir('lib') diff --git a/spa/plugins/aec/aec-webrtc.cpp b/spa/plugins/aec/aec-webrtc.cpp index 354ad940..74255aae 100644 --- a/spa/plugins/aec/aec-webrtc.cpp +++ b/spa/plugins/aec/aec-webrtc.cpp @@ -28,7 +28,11 @@ struct impl_data { struct spa_audio_aec aec; struct spa_log *log; +#if defined(HAVE_WEBRTC) || defined(HAVE_WEBRTC1) std::unique_ptr<webrtc::AudioProcessing> apm; +#elif defined(HAVE_WEBRTC2) + rtc::scoped_refptr<webrtc::AudioProcessing> apm; +#endif spa_audio_info_raw rec_info; spa_audio_info_raw out_info; spa_audio_info_raw play_info; @@ -70,10 +74,9 @@ static int parse_mic_geometry(struct impl_data *impl, const char *mic_geometry, { int res; size_t i; - struct spa_json it[2]; + struct spa_json it[1]; - spa_json_init(&it[0], mic_geometry, strlen(mic_geometry)); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) { + if (spa_json_begin_array(&it[0], mic_geometry, strlen(mic_geometry)) <= 0) { spa_log_error(impl->log, "Error: webrtc.mic-geometry expects an array"); return -EINVAL; } @@ -81,7 +84,7 @@ static int parse_mic_geometry(struct impl_data *impl, const char *mic_geometry, for (i = 0; i < geometry.size(); i++) { float f[3]; - if ((res = parse_point(&it[1], f)) < 0) { + if ((res = parse_point(&it[0], f)) < 0) { spa_log_error(impl->log, "Error: can't parse webrtc.mic-geometry points: %d", res); return res; } @@ -104,16 +107,17 @@ static int webrtc_init2(void *object, const struct spa_dict *args, bool high_pass_filter = webrtc_get_spa_bool(args, "webrtc.high_pass_filter", true); bool noise_suppression = webrtc_get_spa_bool(args, "webrtc.noise_suppression", true); - bool voice_detection = webrtc_get_spa_bool(args, "webrtc.voice_detection", true); -#ifdef HAVE_WEBRTC +#if defined(HAVE_WEBRTC) bool extended_filter = webrtc_get_spa_bool(args, "webrtc.extended_filter", true); bool delay_agnostic = webrtc_get_spa_bool(args, "webrtc.delay_agnostic", true); // Disable experimental flags by default bool experimental_agc = webrtc_get_spa_bool(args, "webrtc.experimental_agc", false); bool experimental_ns = webrtc_get_spa_bool(args, "webrtc.experimental_ns", false); + bool voice_detection = webrtc_get_spa_bool(args, "webrtc.voice_detection", true); bool beamforming = webrtc_get_spa_bool(args, "webrtc.beamforming", false); -#else +#elif defined(HAVE_WEBRTC1) + bool voice_detection = webrtc_get_spa_bool(args, "webrtc.voice_detection", true); bool transient_suppression = webrtc_get_spa_bool(args, "webrtc.transient_suppression", true); #endif // Note: AGC seems to mess up with Agnostic Delay Detection, especially with speech, @@ -124,7 +128,7 @@ static int webrtc_init2(void *object, const struct spa_dict *args, // This filter will modify playback buffer (when calling ProcessReverseStream), but now // playback buffer modifications are discarded. -#ifdef HAVE_WEBRTC +#if defined(HAVE_WEBRTC) webrtc::Config config; config.Set<webrtc::ExtendedFilter>(new webrtc::ExtendedFilter(extended_filter)); config.Set<webrtc::DelayAgnostic>(new webrtc::DelayAgnostic(delay_agnostic)); @@ -168,7 +172,7 @@ static int webrtc_init2(void *object, const struct spa_dict *args, config.Set<webrtc::Beamforming>(new webrtc::Beamforming(true, geometry)); } } -#else +#elif defined(HAVE_WEBRTC1) webrtc::AudioProcessing::Config config; config.echo_canceller.enabled = true; config.pipeline.multi_channel_capture = rec_info->channels > 1; @@ -185,20 +189,43 @@ static int webrtc_init2(void *object, const struct spa_dict *args, // FIXME: expose pre/postamp gain config.transient_suppression.enabled = transient_suppression; config.voice_detection.enabled = voice_detection; +#elif defined(HAVE_WEBRTC2) + webrtc::AudioProcessing::Config config; + config.pipeline.multi_channel_capture = rec_info->channels > 1; + config.pipeline.multi_channel_render = play_info->channels > 1; + // FIXME: Example code enables both gain controllers, but that seems sus + config.gain_controller1.enabled = gain_control; + config.gain_controller1.mode = webrtc::AudioProcessing::Config::GainController1::Mode::kAdaptiveDigital; + config.gain_controller2.enabled = gain_control; + config.high_pass_filter.enabled = high_pass_filter; + config.noise_suppression.enabled = noise_suppression; + config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kHigh; + // FIXME: expose pre/postamp gain #endif +#if defined(HAVE_WEBRTC) || defined(HAVE_WEBRTC1) webrtc::ProcessingConfig pconfig = {{ webrtc::StreamConfig(rec_info->rate, rec_info->channels, false), /* input stream */ webrtc::StreamConfig(out_info->rate, out_info->channels, false), /* output stream */ webrtc::StreamConfig(play_info->rate, play_info->channels, false), /* reverse input stream */ webrtc::StreamConfig(play_info->rate, play_info->channels, false), /* reverse output stream */ }}; +#elif defined(HAVE_WEBRTC2) + webrtc::ProcessingConfig pconfig = {{ + webrtc::StreamConfig(rec_info->rate, rec_info->channels), /* input stream */ + webrtc::StreamConfig(out_info->rate, out_info->channels), /* output stream */ + webrtc::StreamConfig(play_info->rate, play_info->channels), /* reverse input stream */ + webrtc::StreamConfig(play_info->rate, play_info->channels), /* reverse output stream */ + }}; +#endif -#ifdef HAVE_WEBRTC +#if defined(HAVE_WEBRTC) auto apm = std::unique_ptr<webrtc::AudioProcessing>(webrtc::AudioProcessing::Create(config)); -#else +#elif defined(HAVE_WEBRTC1) auto apm = std::unique_ptr<webrtc::AudioProcessing>(webrtc::AudioProcessingBuilder().Create()); - + apm->ApplyConfig(config); +#elif defined(HAVE_WEBRTC2) + auto apm = webrtc::AudioProcessingBuilder().Create(); apm->ApplyConfig(config); #endif @@ -251,12 +278,21 @@ static int webrtc_run(void *object, const float *rec[], const float *play[], flo auto impl = static_cast<struct impl_data*>(object); int res; +#if defined(HAVE_WEBRTC) || defined(HAVE_WEBRTC1) webrtc::StreamConfig play_config = webrtc::StreamConfig(impl->play_info.rate, impl->play_info.channels, false); webrtc::StreamConfig rec_config = webrtc::StreamConfig(impl->rec_info.rate, impl->rec_info.channels, false); webrtc::StreamConfig out_config = webrtc::StreamConfig(impl->out_info.rate, impl->out_info.channels, false); +#elif defined(HAVE_WEBRTC2) + webrtc::StreamConfig play_config = + webrtc::StreamConfig(impl->play_info.rate, impl->play_info.channels); + webrtc::StreamConfig rec_config = + webrtc::StreamConfig(impl->rec_info.rate, impl->rec_info.channels); + webrtc::StreamConfig out_config = + webrtc::StreamConfig(impl->out_info.rate, impl->out_info.channels); +#endif unsigned int num_blocks = n_samples * 1000 / impl->play_info.rate / 10; if (n_samples * 1000 / impl->play_info.rate % 10 != 0) { diff --git a/spa/plugins/alsa/90-pipewire-alsa.rules b/spa/plugins/alsa/90-pipewire-alsa.rules index e19a0951..9ef3d533 100644 --- a/spa/plugins/alsa/90-pipewire-alsa.rules +++ b/spa/plugins/alsa/90-pipewire-alsa.rules @@ -19,8 +19,8 @@ SUBSYSTEM!="sound", GOTO="pipewire_end" ACTION!="change", GOTO="pipewire_end" KERNEL!="card*", GOTO="pipewire_end" SUBSYSTEMS=="usb", GOTO="pipewire_check_usb" -SUBSYSTEMS=="pci", GOTO="pipewire_check_pci" SUBSYSTEMS=="firewire", GOTO="pipewire_firewire_quirk" +SUBSYSTEMS=="pci", GOTO="pipewire_check_pci" SUBSYSTEMS=="platform", DRIVERS=="thinkpad_acpi", ENV{ACP_IGNORE}="1" diff --git a/spa/plugins/alsa/acp-tool.c b/spa/plugins/alsa/acp-tool.c index 0f72a4b6..cff9edb8 100644 --- a/spa/plugins/alsa/acp-tool.c +++ b/spa/plugins/alsa/acp-tool.c @@ -10,8 +10,11 @@ #include <time.h> #include <stdbool.h> #include <getopt.h> +#include <alloca.h> +#include <spa/debug/context.h> #include <spa/utils/string.h> +#include <spa/utils/json.h> #include <acp/acp.h> @@ -587,40 +590,34 @@ static int do_probe(struct data *data) uint32_t n_items = 0; struct acp_dict_item items[64]; struct acp_dict props; + struct spa_json it; acp_set_log_func(log_func, data); acp_set_log_level(data->verbose); items[n_items++] = ACP_DICT_ITEM_INIT("use-ucm", "true"); items[n_items++] = ACP_DICT_ITEM_INIT("verbose", data->verbose ? "true" : "false"); - if (data->properties != NULL) { - char *p = data->properties, *e, f; - while (*p) { - const char *k, *v; - - if ((e = strchr(p, '=')) == NULL) - break; - *e = '\0'; - k = p; - p = e+1; - - if (*p == '\"') { - p++; - f = '\"'; - } else { - f = ' '; - } - if ((e = strchr(p, f)) == NULL && - (e = strchr(p, '\0')) == NULL) - break; - *e = '\0'; - v = p; - p = e+1; + if (spa_json_begin_object_relax(&it, data->properties, strlen(data->properties)) > 0) { + char key[1024]; + const char *value; + int len; + struct spa_error_location loc; + while ((len = spa_json_object_next(&it, key, sizeof(key), &value)) > 0) { + char *k = alloca(strlen(key) + 1); + char *v = alloca(len + 1); + memcpy(k, key, strlen(key) + 1); + spa_json_parse_stringn(value, len, v, len + 1); items[n_items++] = ACP_DICT_ITEM_INIT(k, v); - if (n_items == 64) + if (n_items >= SPA_N_ELEMENTS(items)) break; } + if (spa_json_get_error(&it, data->properties, &loc)) { + struct spa_debug_context *c = NULL; + spa_debugc(c, "invalid --properties: %s", loc.reason); + spa_debugc_error_location(c, &loc); + return -EINVAL; + } } props = ACP_DICT_INIT(items, n_items); @@ -714,7 +711,7 @@ int main(int argc, char *argv[]) { int c, res; int longopt_index = 0, ret; - struct data data = { 0, }; + struct data data = { .properties = strdup("") }; data.verbose = 1; @@ -735,6 +732,7 @@ int main(int argc, char *argv[]) data.card_index = ret; break; case 'p': + free(data.properties); data.properties = strdup(optarg); break; default: diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c index 505681d5..7b49c8be 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -8,6 +8,7 @@ #include <spa/utils/string.h> #include <spa/utils/json.h> +#include <spa/param/audio/iec958-types.h> int _acp_log_level = 1; acp_log_func _acp_log_func; @@ -229,6 +230,25 @@ static void init_device(pa_card *impl, pa_alsa_device *dev, pa_alsa_direction_t dev->device.direction = ACP_DIRECTION_CAPTURE; pa_proplist_update(dev->proplist, PA_UPDATE_REPLACE, m->input_proplist); } + if (m->split) { + char pos[2048]; + struct spa_strbuf b; + int i; + + spa_strbuf_init(&b, pos, sizeof(pos)); + spa_strbuf_append(&b, "["); + for (i = 0; i < m->split->channels; ++i) + spa_strbuf_append(&b, "%sAUX%d", ((i == 0) ? "" : ","), m->split->idx[i]); + spa_strbuf_append(&b, "]"); + pa_proplist_sets(dev->proplist, "api.alsa.split.position", pos); + + spa_strbuf_init(&b, pos, sizeof(pos)); + spa_strbuf_append(&b, "["); + for (i = 0; i < m->split->hw_channels; ++i) + spa_strbuf_append(&b, "%sAUX%d", ((i == 0) ? "" : ","), i); + spa_strbuf_append(&b, "]"); + pa_proplist_sets(dev->proplist, "api.alsa.split.hw-position", pos); + } pa_proplist_sets(dev->proplist, PA_PROP_DEVICE_PROFILE_NAME, m->name); pa_proplist_sets(dev->proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, m->description); pa_proplist_setf(dev->proplist, "card.profile.device", "%u", index); @@ -452,17 +472,16 @@ static int add_pro_profile(pa_card *impl, uint32_t index) static bool contains_string(const char *arr, const char *str) { - struct spa_json it[2]; + struct spa_json it[1]; char v[256]; if (arr == NULL || str == NULL) return false; - spa_json_init(&it[0], arr, strlen(arr)); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], arr, strlen(arr)); + if (spa_json_begin_array_relax(&it[0], arr, strlen(arr)) <= 0) + return false; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0) { + while (spa_json_get_string(&it[0], v, sizeof(v)) > 0) { if (spa_streq(v, str)) return true; } @@ -952,12 +971,58 @@ static pa_device_port* find_port_with_eld_device(pa_card *impl, int device) return NULL; } +static void acp_iec958_codec_mask_to_json(uint64_t codecs, char *buf, size_t maxsize) +{ + struct spa_strbuf b; + const struct spa_type_info *info; + + spa_strbuf_init(&b, buf, maxsize); + for (info = spa_type_audio_iec958_codec; info->name; ++info) + if ((codecs & (1ULL << info->type)) && info->type != SPA_AUDIO_IEC958_CODEC_UNKNOWN) + spa_strbuf_append(&b, "%s\"%s\"", (b.pos ? "," : "["), + spa_type_audio_iec958_codec_to_short_name(info->type)); + if (b.pos) + spa_strbuf_append(&b, "]"); +} + +void acp_iec958_codecs_to_json(const uint32_t *codecs, size_t n_codecs, char *buf, size_t maxsize) +{ + struct spa_strbuf b; + + spa_strbuf_init(&b, buf, maxsize); + spa_strbuf_append(&b, "["); + for (size_t i = 0; i < n_codecs; ++i) + spa_strbuf_append(&b, "%s\"%s\"", (i ? "," : ""), + spa_type_audio_iec958_codec_to_short_name(codecs[i])); + spa_strbuf_append(&b, "]"); +} + +size_t acp_iec958_codecs_from_json(const char *str, uint32_t *codecs, size_t max_codecs) +{ + struct spa_json it; + char v[256]; + size_t n_codecs = 0; + + if (spa_json_begin_array_relax(&it, str, strlen(str)) <= 0) + return 0; + + while (spa_json_get_string(&it, v, sizeof(v)) > 0) { + uint32_t type = spa_type_audio_iec958_codec_from_short_name(v); + if (type != SPA_AUDIO_IEC958_CODEC_UNKNOWN) + codecs[n_codecs++] = type; + if (n_codecs >= max_codecs) + break; + } + + return n_codecs; +} + static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) { pa_card *impl = snd_mixer_elem_get_callback_private(melem); snd_hctl_elem_t **_elem = snd_mixer_elem_get_private(melem), *elem; int device, i; - const char *old_monitor_name; + const char *old_monitor_name, *old_iec958_codec_list; pa_device_port *p; pa_hdmi_eld eld; bool changed = false; @@ -995,6 +1060,18 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) changed |= (old_monitor_name == NULL) || (!spa_streq(old_monitor_name, eld.monitor_name)); pa_proplist_sets(p->proplist, PA_PROP_DEVICE_PRODUCT_NAME, eld.monitor_name); } + + old_iec958_codec_list = pa_proplist_gets(p->proplist, ACP_KEY_IEC958_CODECS_DETECTED); + if (eld.iec958_codecs == 0) { + changed |= old_iec958_codec_list != NULL; + pa_proplist_unset(p->proplist, ACP_KEY_IEC958_CODECS_DETECTED); + } else { + char codecs[512]; + acp_iec958_codec_mask_to_json(eld.iec958_codecs, codecs, sizeof(codecs)); + changed |= (old_iec958_codec_list == NULL) || (!spa_streq(old_iec958_codec_list, codecs)); + pa_proplist_sets(p->proplist, ACP_KEY_IEC958_CODECS_DETECTED, codecs); + } + pa_proplist_as_dict(p->proplist, &p->port.props); if (changed && mask != 0 && impl->events && impl->events->props_changed) @@ -1387,7 +1464,8 @@ static int setup_mixer(pa_card *impl, pa_alsa_device *dev, bool ignore_dB) data = PA_DEVICE_PORT_DATA(dev->active_port); dev->mixer_path = data->path; - pa_alsa_path_select(data->path, data->setting, dev->mixer_handle, dev->muted); + if (!impl->disable_mixer_path) + pa_alsa_path_select(data->path, data->setting, dev->mixer_handle, dev->muted); } else { pa_alsa_ucm_port_data *data; @@ -1396,7 +1474,8 @@ static int setup_mixer(pa_card *impl, pa_alsa_device *dev, bool ignore_dB) /* Now activate volume controls, if any */ if (data->path) { dev->mixer_path = data->path; - pa_alsa_path_select(dev->mixer_path, NULL, dev->mixer_handle, dev->muted); + if (!impl->disable_mixer_path) + pa_alsa_path_select(dev->mixer_path, NULL, dev->mixer_handle, dev->muted); } } } else { @@ -1405,8 +1484,9 @@ static int setup_mixer(pa_card *impl, pa_alsa_device *dev, bool ignore_dB) if (dev->mixer_path) { /* Hmm, we have only a single path, then let's activate it */ - pa_alsa_path_select(dev->mixer_path, dev->mixer_path->settings, - dev->mixer_handle, dev->muted); + if (!impl->disable_mixer_path) + pa_alsa_path_select(dev->mixer_path, dev->mixer_path->settings, + dev->mixer_handle, dev->muted); } else return 0; } @@ -1450,6 +1530,9 @@ static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device { const char *mod_name; uint32_t i, port_index; + const char *codecs; + pa_device_port *p; + void *state = NULL; int res; if (impl->use_ucm && @@ -1469,7 +1552,7 @@ static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device /* Synchronize priority values, as it may have changed when setting the profile */ for (i = 0; i < impl->card.n_ports; i++) { - pa_device_port *p = (pa_device_port *)impl->card.ports[i]; + p = (pa_device_port *)impl->card.ports[i]; p->port.priority = p->priority; } @@ -1500,6 +1583,15 @@ static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device else dev->muted = false; + while ((p = pa_hashmap_iterate(dev->ports, &state, NULL))) { + codecs = pa_proplist_gets(p->proplist, ACP_KEY_IEC958_CODECS_DETECTED); + if (codecs) { + dev->device.n_codecs = acp_iec958_codecs_from_json(codecs, dev->device.codecs, + ACP_N_ELEMENTS(dev->device.codecs)); + break; + } + } + return 0; } @@ -1669,6 +1761,8 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) impl->use_ucm = spa_atob(s); if ((s = acp_dict_lookup(props, "api.alsa.soft-mixer")) != NULL) impl->soft_mixer = spa_atob(s); + if ((s = acp_dict_lookup(props, "api.alsa.disable-mixer-path")) != NULL) + impl->disable_mixer_path = spa_atob(s); if ((s = acp_dict_lookup(props, "api.alsa.ignore-dB")) != NULL) impl->ignore_dB = spa_atob(s); if ((s = acp_dict_lookup(props, "device.profile-set")) != NULL) @@ -1683,8 +1777,17 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) impl->rate = atoi(s); if ((s = acp_dict_lookup(props, "api.acp.pro-channels")) != NULL) impl->pro_channels = atoi(s); + if ((s = acp_dict_lookup(props, "api.alsa.split-enable")) != NULL) + impl->ucm.split_enable = spa_atob(s); } +#if SND_LIB_VERSION < 0x10207 + if (impl->ucm.split_enable) + pa_log_info("alsa-lib too old for PipeWire-side UCM SplitPCM"); + + impl->ucm.split_enable = false; /* API addition in 1.2.7 */ +#endif + impl->ucm.default_sample_spec.format = PA_SAMPLE_S16NE; impl->ucm.default_sample_spec.rate = impl->rate; impl->ucm.default_sample_spec.channels = 2; @@ -1753,11 +1856,11 @@ struct acp_card *acp_card_new(uint32_t index, const struct acp_dict *props) if (!impl->auto_profile && profile == NULL) profile = "off"; + init_eld_ctls(impl); + profile_index = acp_card_find_best_profile_index(&impl->card, profile); acp_card_set_profile(&impl->card, profile_index, 0); - init_eld_ctls(impl); - return &impl->card; error: pa_alsa_refcnt_dec(); @@ -1874,6 +1977,7 @@ int acp_card_handle_events(struct acp_card *card) static void sync_mixer(pa_alsa_device *d, pa_device_port *port) { pa_alsa_setting *setting = NULL; + pa_card *impl = d->card; if (!d->mixer_path) return; @@ -1886,7 +1990,7 @@ static void sync_mixer(pa_alsa_device *d, pa_device_port *port) setting = data->setting; } - if (d->mixer_handle) + if (d->mixer_handle && !impl->disable_mixer_path) pa_alsa_path_select(d->mixer_path, setting, d->mixer_handle, d->muted); if (d->set_mute) diff --git a/spa/plugins/alsa/acp/acp.h b/spa/plugins/alsa/acp/acp.h index fadf7853..4b9f2c49 100644 --- a/spa/plugins/alsa/acp/acp.h +++ b/spa/plugins/alsa/acp/acp.h @@ -148,6 +148,12 @@ const char *acp_available_str(enum acp_available status); * like an ALSA control name, but applications must not assume any such relationship. * The group naming scheme can change without a warning. */ +#define ACP_KEY_IEC958_CODECS_DETECTED "iec958.codecs.detected" + /**< A list of IEC958 passthrough formats which have been auto-detected as being + * supported by a given node. This only serves as a hint, as the auto-detected + * values may be incorrect and/or might change, e.g. when external devices such + * as receivers are powered on or off. + */ struct acp_device; @@ -294,6 +300,9 @@ typedef void (*acp_log_func) (void *data, void acp_set_log_func(acp_log_func, void *data); void acp_set_log_level(int level); +void acp_iec958_codecs_to_json(const uint32_t *codecs, size_t n_codecs, char *buf, size_t maxsize); +size_t acp_iec958_codecs_from_json(const char *str, uint32_t *codecs, size_t max_codecs); + #ifdef __cplusplus } #endif diff --git a/spa/plugins/alsa/acp/alsa-mixer.c b/spa/plugins/alsa/acp/alsa-mixer.c index bd4942c7..1f494ee0 100644 --- a/spa/plugins/alsa/acp/alsa-mixer.c +++ b/spa/plugins/alsa/acp/alsa-mixer.c @@ -3787,6 +3787,8 @@ void pa_alsa_mapping_free(pa_alsa_mapping *m) { pa_proplist_free(m->input_proplist); pa_proplist_free(m->output_proplist); + pa_xfree(m->split); + pa_assert(!m->input_pcm); pa_assert(!m->output_pcm); diff --git a/spa/plugins/alsa/acp/alsa-mixer.h b/spa/plugins/alsa/acp/alsa-mixer.h index 687e8b53..cbfac4ab 100644 --- a/spa/plugins/alsa/acp/alsa-mixer.h +++ b/spa/plugins/alsa/acp/alsa-mixer.h @@ -327,6 +327,8 @@ struct pa_alsa_mapping { pa_sample_spec sample_spec; pa_channel_map channel_map; + pa_alsa_ucm_split *split; + char **device_strings; char **input_path_names; diff --git a/spa/plugins/alsa/acp/alsa-ucm.c b/spa/plugins/alsa/acp/alsa-ucm.c index 3d3f19bb..9061a1f2 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.c +++ b/spa/plugins/alsa/acp/alsa-ucm.c @@ -326,6 +326,90 @@ static const char *get_jack_mixer_device(pa_alsa_ucm_device *dev, bool is_sink) return dev_name; } +static PA_PRINTF_FUNC(2,3) const char *ucm_get_string(snd_use_case_mgr_t *uc_mgr, const char *fmt, ...) +{ + char *id; + const char *value; + va_list args; + int err; + + va_start(args, fmt); + id = pa_vsprintf_malloc(fmt, args); + va_end(args); + err = snd_use_case_get(uc_mgr, id, &value); + if (err >= 0) + pa_log_debug("Got %s: %s", id, value); + pa_xfree(id); + if (err < 0) { + errno = -err; + return NULL; + } + return value; +} + +static pa_alsa_ucm_split *ucm_get_split_channels(pa_alsa_ucm_device *device, snd_use_case_mgr_t *uc_mgr, const char *prefix) { + pa_alsa_ucm_split *split; + const char *value; + const char *device_name; + int i; + uint32_t hw_channels; + + device_name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME); + if (!device_name) + return NULL; + + value = ucm_get_string(uc_mgr, "%sChannels/%s", prefix, device_name); + if (pa_atou(value, &hw_channels) < 0) + return NULL; + + split = pa_xnew0(pa_alsa_ucm_split, 1); + + for (i = 0; i < PA_CHANNELS_MAX; i++) { + uint32_t idx; + snd_pcm_chmap_t *map; + + value = ucm_get_string(uc_mgr, "%sChannel%d/%s", prefix, i, device_name); + if (pa_atou(value, &idx) < 0) + break; + + if (idx >= hw_channels) + goto fail; + + value = ucm_get_string(uc_mgr, "%sChannelPos%d/%s", prefix, i, device_name); + if (!value) + goto fail; + + map = snd_pcm_chmap_parse_string(value); + if (!map) + goto fail; + + if (map->channels == 1) { + pa_log_debug("Split %s channel %d -> device %s channel %d: %s (%d)", + prefix, (int)idx, device_name, i, value, map->pos[0]); + split->idx[i] = idx; + split->pos[i] = map->pos[0]; + free(map); + } else { + free(map); + goto fail; + } + } + + if (i == 0) { + pa_xfree(split); + return NULL; + } + + split->channels = i; + split->hw_channels = hw_channels; + return split; + +fail: + pa_log_warn("Invalid SplitPCM ALSA UCM rule for device %s", device_name); + pa_xfree(split); + return NULL; +} + /* Create a property list for this ucm device */ static int ucm_get_device_property( pa_alsa_ucm_device *device, @@ -470,6 +554,9 @@ static int ucm_get_device_property( pa_hashmap_put(device->capture_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), vol); } + device->playback_split = ucm_get_split_channels(device, uc_mgr, "Playback"); + device->capture_split = ucm_get_split_channels(device, uc_mgr, "Capture"); + if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) { /* get priority from static table */ for (i = 0; dev_info[i].id; i++) { @@ -868,21 +955,30 @@ int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) { char *card_name; const char **verb_list, *value; int num_verbs, i, err = 0; + const char *split_prefix = ucm->split_enable ? "<<<SplitPCM=1>>>" : ""; /* support multiple card instances, address card directly by index */ - card_name = pa_sprintf_malloc("hw:%i", card_index); + card_name = pa_sprintf_malloc("%shw:%i", split_prefix, card_index); if (card_name == NULL) return -PA_ALSA_ERR_UNSPECIFIED; err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name); if (err < 0) { + char *ucm_card_name; + /* fallback longname: is UCM available for this card ? */ pa_xfree(card_name); - err = snd_card_get_name(card_index, &card_name); + err = snd_card_get_name(card_index, &ucm_card_name); if (err < 0) { pa_log("Card can't get card_name from card_index %d", card_index); err = -PA_ALSA_ERR_UNSPECIFIED; goto name_fail; } + card_name = pa_sprintf_malloc("%s%s", split_prefix, ucm_card_name); + free(ucm_card_name); + if (card_name == NULL) { + err = -PA_ALSA_ERR_UNSPECIFIED; + goto name_fail; + } err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name); if (err < 0) { @@ -955,6 +1051,54 @@ name_fail: return err; } +static void ucm_verb_set_split_leaders(pa_alsa_ucm_verb *verb) { + pa_alsa_ucm_device *d, *d2; + + /* Set first virtual device in each split HW PCM as the split leader */ + + PA_LLIST_FOREACH(d, verb->devices) { + if (d->playback_split) + d->playback_split->leader = true; + if (d->capture_split) + d->capture_split->leader = true; + } + + PA_LLIST_FOREACH(d, verb->devices) { + const char *sink = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SINK); + const char *source = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SOURCE); + + if (d->playback_split) { + if (!sink) + d->playback_split->leader = false; + + if (d->playback_split->leader) { + PA_LLIST_FOREACH(d2, verb->devices) { + const char *sink2 = pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_SINK); + + if (d == d2 || !d2->playback_split || !sink || !sink2 || !pa_streq(sink, sink2)) + continue; + d2->playback_split->leader = false; + } + } + } + + if (d->capture_split) { + if (!source) + d->capture_split->leader = false; + + if (d->capture_split->leader) { + PA_LLIST_FOREACH(d2, verb->devices) { + const char *source2 = pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_SOURCE); + + if (d == d2 || !d2->capture_split || !source || !source2 || !pa_streq(source, source2)) + continue; + d2->capture_split->leader = false; + } + } + } + } +} + int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb) { pa_alsa_ucm_device *d; pa_alsa_ucm_modifier *mod; @@ -994,6 +1138,9 @@ int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, cons /* Devices properties */ ucm_get_device_property(d, uc_mgr, verb, dev_name); } + + ucm_verb_set_split_leaders(verb); + /* make conflicting or supported device mutual */ PA_LLIST_FOREACH(d, verb->devices) append_lost_relationship(d); @@ -1092,14 +1239,14 @@ fail: } } -static void ucm_add_port_props( - pa_device_port *port, - bool is_sink) -{ +static void proplist_set_icon_name( + pa_proplist *proplist, + pa_device_port_type_t type, + bool is_sink) { const char *icon; if (is_sink) { - switch (port->type) { + switch (type) { case PA_DEVICE_PORT_TYPE_HEADPHONES: icon = "audio-headphones"; break; @@ -1112,7 +1259,7 @@ static void ucm_add_port_props( break; } } else { - switch (port->type) { + switch (type) { case PA_DEVICE_PORT_TYPE_HEADSET: icon = "audio-headset"; break; @@ -1123,7 +1270,7 @@ static void ucm_add_port_props( } } - pa_proplist_sets(port->proplist, "device.icon_name", icon); + pa_proplist_sets(proplist, "device.icon_name", icon); } static char *devset_name(pa_idxset *devices, const char *sep) { @@ -1229,6 +1376,13 @@ static unsigned devset_capture_priority(pa_idxset *devices, bool invert) { return (unsigned) priority; } +static void ucm_add_port_props( + pa_device_port *port, + bool is_sink) +{ + proplist_set_icon_name(port->proplist, port->type, is_sink); +} + void pa_alsa_ucm_add_port( pa_hashmap *hash, pa_alsa_ucm_mapping_context *context, @@ -1283,28 +1437,30 @@ void pa_alsa_ucm_add_port( pa_hashmap_put(ports, port->name, port); pa_log_debug("Add port %s: %s", port->name, port->description); ucm_add_port_props(port, is_sink); + } - PA_HASHMAP_FOREACH_KV(verb_name, vol, is_sink ? dev->playback_volumes : dev->capture_volumes, state) { - pa_alsa_path *path = pa_alsa_path_synthesize(vol->mixer_elem, - is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT); - - if (!path) - pa_log_warn("Failed to set up volume control: %s", vol->mixer_elem); - else { - if (vol->master_elem) { - pa_alsa_element *e = pa_alsa_element_get(path, vol->master_elem, false); - e->switch_use = PA_ALSA_SWITCH_MUTE; - e->volume_use = PA_ALSA_VOLUME_MERGE; - } + data = PA_DEVICE_PORT_DATA(port); + PA_HASHMAP_FOREACH_KV(verb_name, vol, is_sink ? dev->playback_volumes : dev->capture_volumes, state) { + if (pa_hashmap_get(data->paths, verb_name)) + continue; + pa_alsa_path *path = pa_alsa_path_synthesize(vol->mixer_elem, + is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT); + if (!path) + pa_log_warn("Failed to set up volume control: %s", vol->mixer_elem); + else { + if (vol->master_elem) { + pa_alsa_element *e = pa_alsa_element_get(path, vol->master_elem, false); + e->switch_use = PA_ALSA_SWITCH_MUTE; + e->volume_use = PA_ALSA_VOLUME_MERGE; + } - pa_hashmap_put(data->paths, pa_xstrdup(verb_name), path); + pa_hashmap_put(data->paths, pa_xstrdup(verb_name), path); - /* Add path also to already created empty path set */ - if (is_sink) - pa_hashmap_put(dev->playback_mapping->output_path_set->paths, pa_xstrdup(vol->mixer_elem), path); - else - pa_hashmap_put(dev->capture_mapping->input_path_set->paths, pa_xstrdup(vol->mixer_elem), path); - } + /* Add path also to already created empty path set */ + if (is_sink) + pa_hashmap_put(dev->playback_mapping->output_path_set->paths, pa_xstrdup(vol->mixer_elem), path); + else + pa_hashmap_put(dev->capture_mapping->input_path_set->paths, pa_xstrdup(vol->mixer_elem), path); } } @@ -1363,15 +1519,19 @@ static bool devset_supports_device(pa_idxset *devices, pa_alsa_ucm_device *dev) if (!pa_idxset_contains(d->supported_devices, dev)) return false; - /* PlaybackPCM must not be the same as any selected device */ + /* PlaybackPCM must not be the same as any selected device, except when both split */ sink2 = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SINK); - if (sink && sink2 && pa_streq(sink, sink2)) - return false; + if (sink && sink2 && pa_streq(sink, sink2)) { + if (!(dev->playback_split && d->playback_split)) + return false; + } - /* CapturePCM must not be the same as any selected device */ + /* CapturePCM must not be the same as any selected device, except when both split */ source2 = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_SOURCE); - if (source && source2 && pa_streq(source, source2)) - return false; + if (source && source2 && pa_streq(source, source2)) { + if (!(dev->capture_split && d->capture_split)) + return false; + } } return true; @@ -1425,22 +1585,20 @@ static pa_idxset *iterate_device_subsets(pa_idxset *devices, void **state) { static pa_idxset *iterate_maximal_device_subsets(pa_idxset *devices, void **state) { uint32_t idx; pa_alsa_ucm_device *dev; - pa_idxset *subset; + pa_idxset *subset = NULL; pa_assert(devices); pa_assert(state); - subset = iterate_device_subsets(devices, state); - if (!subset) - return subset; - - /* Skip this group if it's incomplete, by checking if we can add any - * other device. If we can, this iteration is a subset of another - * group that we already returned or eventually return. */ - PA_IDXSET_FOREACH(dev, devices, idx) { - if (!pa_idxset_contains(subset, dev) && devset_supports_device(subset, dev)) { - pa_idxset_free(subset, NULL); - return iterate_maximal_device_subsets(devices, state); + while (subset == NULL && (subset = iterate_device_subsets(devices, state))) { + /* Skip this group if it's incomplete, by checking if we can add any + * other device. If we can, this iteration is a subset of another + * group that we already returned or eventually return. */ + PA_IDXSET_FOREACH(dev, devices, idx) { + if (subset && !pa_idxset_contains(subset, dev) && devset_supports_device(subset, dev)) { + pa_idxset_free(subset, NULL); + subset = NULL; + } } } @@ -1677,6 +1835,8 @@ static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m, pa_alsa_ucm_device * else device->capture_mapping = m; + proplist_set_icon_name(m->proplist, device->type, is_sink); + mdev = get_mixer_device(device, is_sink); if (mdev) pa_proplist_sets(m->proplist, "alsa.mixer_device", mdev); @@ -1744,6 +1904,69 @@ static pa_alsa_mapping* ucm_alsa_mapping_get(pa_alsa_ucm_config *ucm, pa_alsa_pr return m; } +static const struct { + enum snd_pcm_chmap_position pos; + pa_channel_position_t channel; +} chmap_info[] = { + [SND_CHMAP_MONO] = { SND_CHMAP_MONO, PA_CHANNEL_POSITION_MONO }, + [SND_CHMAP_FL] = { SND_CHMAP_FL, PA_CHANNEL_POSITION_FRONT_LEFT }, + [SND_CHMAP_FR] = { SND_CHMAP_FR, PA_CHANNEL_POSITION_FRONT_RIGHT }, + [SND_CHMAP_RL] = { SND_CHMAP_RL, PA_CHANNEL_POSITION_REAR_LEFT }, + [SND_CHMAP_RR] = { SND_CHMAP_RR, PA_CHANNEL_POSITION_REAR_RIGHT }, + [SND_CHMAP_FC] = { SND_CHMAP_FC, PA_CHANNEL_POSITION_FRONT_CENTER }, + [SND_CHMAP_LFE] = { SND_CHMAP_LFE, PA_CHANNEL_POSITION_LFE }, + [SND_CHMAP_SL] = { SND_CHMAP_SL, PA_CHANNEL_POSITION_SIDE_LEFT }, + [SND_CHMAP_SR] = { SND_CHMAP_SR, PA_CHANNEL_POSITION_SIDE_RIGHT }, + [SND_CHMAP_RC] = { SND_CHMAP_RC, PA_CHANNEL_POSITION_REAR_CENTER }, + [SND_CHMAP_FLC] = { SND_CHMAP_FLC, PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER }, + [SND_CHMAP_FRC] = { SND_CHMAP_FRC, PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER }, + /* XXX: missing channel positions, mapped to aux... */ + /* [SND_CHMAP_RLC] = { SND_CHMAP_RLC, PA_CHANNEL_POSITION_REAR_LEFT_OF_CENTER }, */ + /* [SND_CHMAP_RRC] = { SND_CHMAP_RRC, PA_CHANNEL_POSITION_REAR_RIGHT_OF_CENTER }, */ + /* [SND_CHMAP_FLW] = { SND_CHMAP_FLW, PA_CHANNEL_POSITION_FRONT_LEFT_WIDE }, */ + /* [SND_CHMAP_FRW] = { SND_CHMAP_FRW, PA_CHANNEL_POSITION_FRONT_RIGHT_WIDE }, */ + /* [SND_CHMAP_FLH] = { SND_CHMAP_FLH, PA_CHANNEL_POSITION_FRONT_LEFT_HIGH }, */ + /* [SND_CHMAP_FCH] = { SND_CHMAP_FCH, PA_CHANNEL_POSITION_FRONT_CENTER_HIGH }, */ + /* [SND_CHMAP_FRH] = { SND_CHMAP_FRH, PA_CHANNEL_POSITION_FRONT_RIGHT_HIGH }, */ + [SND_CHMAP_TC] = { SND_CHMAP_TC, PA_CHANNEL_POSITION_TOP_CENTER }, + [SND_CHMAP_TFL] = { SND_CHMAP_TFL, PA_CHANNEL_POSITION_TOP_FRONT_LEFT }, + [SND_CHMAP_TFR] = { SND_CHMAP_TFR, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT }, + [SND_CHMAP_TFC] = { SND_CHMAP_TFC, PA_CHANNEL_POSITION_TOP_FRONT_CENTER }, + [SND_CHMAP_TRL] = { SND_CHMAP_TRL, PA_CHANNEL_POSITION_TOP_REAR_LEFT }, + [SND_CHMAP_TRR] = { SND_CHMAP_TRR, PA_CHANNEL_POSITION_TOP_REAR_RIGHT }, + [SND_CHMAP_TRC] = { SND_CHMAP_TRC, PA_CHANNEL_POSITION_TOP_REAR_CENTER }, + /* [SND_CHMAP_TFLC] = { SND_CHMAP_TFLC, PA_CHANNEL_POSITION_TOP_FRONT_LEFT_OF_CENTER }, */ + /* [SND_CHMAP_TFRC] = { SND_CHMAP_TFRC, PA_CHANNEL_POSITION_TOP_FRONT_RIGHT_OF_CENTER }, */ + /* [SND_CHMAP_TSL] = { SND_CHMAP_TSL, PA_CHANNEL_POSITION_TOP_SIDE_LEFT }, */ + /* [SND_CHMAP_TSR] = { SND_CHMAP_TSR, PA_CHANNEL_POSITION_TOP_SIDE_RIGHT }, */ + /* [SND_CHMAP_LLFE] = { SND_CHMAP_LLFE, PA_CHANNEL_POSITION_LEFT_LFE }, */ + /* [SND_CHMAP_RLFE] = { SND_CHMAP_RLFE, PA_CHANNEL_POSITION_RIGHT_LFE }, */ + /* [SND_CHMAP_BC] = { SND_CHMAP_BC, PA_CHANNEL_POSITION_BOTTOM_CENTER }, */ + /* [SND_CHMAP_BLC] = { SND_CHMAP_BLC, PA_CHANNEL_POSITION_BOTTOM_LEFT_OF_CENTER }, */ + /* [SND_CHMAP_BRC] = { SND_CHMAP_BRC, PA_CHANNEL_POSITION_BOTTOM_RIGHT_OF_CENTER }, */ +}; + +static void ucm_split_to_channel_map(pa_channel_map *m, const pa_alsa_ucm_split *s) +{ + const int n = sizeof(chmap_info) / sizeof(chmap_info[0]); + int i; + int aux = 0; + + for (i = 0; i < s->channels; ++i) { + int p = s->pos[i]; + + if (p >= 0 && p < n && (int)chmap_info[p].pos == p) + m->map[i] = chmap_info[p].channel; + else + m->map[i] = PA_CHANNEL_POSITION_AUX0 + aux++; + + if (aux >= 32) + break; + } + + m->channels = i; +} + static int ucm_create_mapping_direction( pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps, @@ -1788,6 +2011,14 @@ static int ucm_create_mapping_direction( if (channels < m->channel_map.channels) pa_channel_map_init_extend(&m->channel_map, channels, PA_CHANNEL_MAP_ALSA); + if (is_sink && device->playback_split) { + m->split = pa_xmemdup(device->playback_split, sizeof(*m->split)); + ucm_split_to_channel_map(&m->channel_map, m->split); + } else if (!is_sink && device->capture_split) { + m->split = pa_xmemdup(device->capture_split, sizeof(*m->split)); + ucm_split_to_channel_map(&m->channel_map, m->split); + } + alsa_mapping_add_ucm_device(m, device); return 0; @@ -2159,11 +2390,22 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, snd_pcm_uframes_t try_period_size, try_buffer_size; bool exact_channels = m->channel_map.channels > 0; - if (exact_channels) { - try_map = m->channel_map; - try_ss.channels = try_map.channels; - } else - pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_ALSA); + if (!m->split) { + if (exact_channels) { + try_map = m->channel_map; + try_ss.channels = try_map.channels; + } else + pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_ALSA); + } else { + if (!m->split->leader) { + errno = EINVAL; + return NULL; + } + + exact_channels = true; + try_ss.channels = m->split->hw_channels; + pa_channel_map_init_extend(&try_map, try_ss.channels, PA_CHANNEL_MAP_AUX); + } try_period_size = pa_usec_to_bytes(ucm->default_fragment_size_msec * PA_USEC_PER_MSEC, &try_ss) / @@ -2182,6 +2424,32 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, return pcm; } +static void pa_alsa_init_proplist_split_pcm(pa_idxset *mappings, pa_alsa_mapping *leader, pa_direction_t direction) +{ + pa_proplist *props = pa_proplist_new(); + uint32_t idx; + pa_alsa_mapping *m; + + if (direction == PA_DIRECTION_OUTPUT) + pa_alsa_init_proplist_pcm(NULL, props, leader->output_pcm); + else + pa_alsa_init_proplist_pcm(NULL, props, leader->input_pcm); + + PA_IDXSET_FOREACH(m, mappings, idx) { + if (!m->split) + continue; + if (!pa_streq(m->device_strings[0], leader->device_strings[0])) + continue; + + if (direction == PA_DIRECTION_OUTPUT) + pa_proplist_update(m->output_proplist, PA_UPDATE_REPLACE, props); + else + pa_proplist_update(m->input_proplist, PA_UPDATE_REPLACE, props); + } + + pa_proplist_free(props); +} + static void profile_finalize_probing(pa_alsa_profile *p) { pa_alsa_mapping *m; uint32_t idx; @@ -2193,7 +2461,11 @@ static void profile_finalize_probing(pa_alsa_profile *p) { if (!m->output_pcm) continue; - pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm); + if (!m->split) + pa_alsa_init_proplist_pcm(NULL, m->output_proplist, m->output_pcm); + else + pa_alsa_init_proplist_split_pcm(p->output_mappings, m, PA_DIRECTION_OUTPUT); + pa_alsa_close(&m->output_pcm); } @@ -2204,7 +2476,11 @@ static void profile_finalize_probing(pa_alsa_profile *p) { if (!m->input_pcm) continue; - pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm); + if (!m->split) + pa_alsa_init_proplist_pcm(NULL, m->input_proplist, m->input_pcm); + else + pa_alsa_init_proplist_split_pcm(p->input_mappings, m, PA_DIRECTION_INPUT); + pa_alsa_close(&m->input_pcm); } } @@ -2257,6 +2533,9 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * continue; } + if (m->split && !m->split->leader) + continue; + m->output_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_PLAYBACK); if (!m->output_pcm) { p->supported = false; @@ -2272,6 +2551,9 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * continue; } + if (m->split && !m->split->leader) + continue; + m->input_pcm = mapping_open_pcm(ucm, m, SND_PCM_STREAM_CAPTURE); if (!m->input_pcm) { p->supported = false; @@ -2361,6 +2643,9 @@ static void free_verb(pa_alsa_ucm_verb *verb) { pa_xfree(di->eld_mixer_device_name); + pa_xfree(di->playback_split); + pa_xfree(di->capture_split); + pa_xfree(di); } diff --git a/spa/plugins/alsa/acp/alsa-ucm.h b/spa/plugins/alsa/acp/alsa-ucm.h index b03e7311..74dbe821 100644 --- a/spa/plugins/alsa/acp/alsa-ucm.h +++ b/spa/plugins/alsa/acp/alsa-ucm.h @@ -145,6 +145,7 @@ typedef struct pa_alsa_ucm_mapping_context pa_alsa_ucm_mapping_context; typedef struct pa_alsa_ucm_profile_context pa_alsa_ucm_profile_context; typedef struct pa_alsa_ucm_port_data pa_alsa_ucm_port_data; typedef struct pa_alsa_ucm_volume pa_alsa_ucm_volume; +typedef struct pa_alsa_ucm_split pa_alsa_ucm_split; int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index); pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map); @@ -177,6 +178,15 @@ void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_ /* UCM - Use Case Manager is available on some audio cards */ +struct pa_alsa_ucm_split { + /* UCM SplitPCM channel remapping */ + bool leader; + int hw_channels; + int channels; + int idx[PA_CHANNELS_MAX]; + enum snd_pcm_chmap_position pos[PA_CHANNELS_MAX]; +}; + struct pa_alsa_ucm_device { PA_LLIST_FIELDS(pa_alsa_ucm_device); @@ -215,6 +225,9 @@ struct pa_alsa_ucm_device { char *eld_mixer_device_name; int eld_device; + + pa_alsa_ucm_split *playback_split; + pa_alsa_ucm_split *capture_split; }; void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device); @@ -254,6 +267,7 @@ struct pa_alsa_ucm_config { pa_channel_map default_channel_map; unsigned default_fragment_size_msec; unsigned default_n_fragments; + bool split_enable; snd_use_case_mgr_t *ucm_mgr; pa_alsa_ucm_verb *active_verb; diff --git a/spa/plugins/alsa/acp/alsa-util.c b/spa/plugins/alsa/acp/alsa-util.c index 9c4638f1..96d6020c 100644 --- a/spa/plugins/alsa/acp/alsa-util.c +++ b/spa/plugins/alsa/acp/alsa-util.c @@ -26,6 +26,8 @@ #include "alsa-util.h" #include "alsa-mixer.h" +#include <spa/param/audio/format.h> + #ifdef HAVE_UDEV #include <modules/udev-util.h> #endif @@ -1972,7 +1974,7 @@ int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld) { snd_ctl_elem_info_t *info; snd_ctl_elem_value_t *value; uint8_t *elddata; - unsigned int eldsize, mnl; + unsigned int eldsize, mnl, sad_count; unsigned int device; pa_assert(eld != NULL); @@ -2010,5 +2012,54 @@ int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld) { if (mnl) pa_log_debug("Monitor name in ELD info is '%s' (for device=%d)", eld->monitor_name, device); + /* Fetch Short Audio Descriptors */ + sad_count = (elddata[5] & 0xf0) >> 4; + pa_log_debug("SAD count in ELD info is %u (for device=%d)", sad_count, device); + if (20 + mnl + 3 * sad_count > eldsize) { + pa_log_debug("Invalid SAD count (%u) in ELD info (for device=%d)", sad_count, device); + sad_count = 0; + } + + eld->iec958_codecs = 0; + for (unsigned i = 0; i < sad_count; i++) { + uint8_t *sad = &elddata[20 + mnl + 3 * i]; + + /* https://en.wikipedia.org/wiki/Extended_Display_Identification_Data#Audio_Data_Blocks */ + switch ((sad[0] & 0x78) >> 3) { + case 1: + eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_PCM; + break; + case 2: + eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_AC3; + break; + case 3: + eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_MPEG; + break; + case 4: + eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_MPEG; + break; + case 5: + eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_MPEG; + break; + case 6: + eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_MPEG2_AAC; + break; + case 7: + eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_DTS; + break; + case 10: + eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_EAC3; + break; + case 11: + eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_DTSHD; + break; + case 12: + eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_TRUEHD; + break; + default: + eld->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_UNKNOWN; + break; + } + } return 0; } diff --git a/spa/plugins/alsa/acp/alsa-util.h b/spa/plugins/alsa/acp/alsa-util.h index e576dc9b..26c2698c 100644 --- a/spa/plugins/alsa/acp/alsa-util.h +++ b/spa/plugins/alsa/acp/alsa-util.h @@ -175,6 +175,7 @@ void pa_alsa_mixer_free(pa_alsa_mixer *mixer); typedef struct pa_hdmi_eld pa_hdmi_eld; struct pa_hdmi_eld { char monitor_name[17]; + uint64_t iec958_codecs; }; int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld); diff --git a/spa/plugins/alsa/acp/card.h b/spa/plugins/alsa/acp/card.h index c58f89ab..c1126fe2 100644 --- a/spa/plugins/alsa/acp/card.h +++ b/spa/plugins/alsa/acp/card.h @@ -44,6 +44,7 @@ struct pa_card { bool use_ucm; bool soft_mixer; + bool disable_mixer_path; bool auto_profile; bool auto_port; bool ignore_dB; diff --git a/spa/plugins/alsa/acp/compat.h b/spa/plugins/alsa/acp/compat.h index 356ef074..7660e1c2 100644 --- a/spa/plugins/alsa/acp/compat.h +++ b/spa/plugins/alsa/acp/compat.h @@ -414,6 +414,14 @@ static PA_PRINTF_FUNC(1,2) inline char *pa_sprintf_malloc(const char *fmt, ...) return res; } +static PA_PRINTF_FUNC(1,0) inline char *pa_vsprintf_malloc(const char *fmt, va_list args) +{ + char *res; + if (vasprintf(&res, fmt, args) < 0) + res = NULL; + return res; +} + #define pa_fopen_cloexec(f,m) fopen(f,m"e") static inline char *pa_path_get_filename(const char *p) diff --git a/spa/plugins/alsa/alsa-acp-device.c b/spa/plugins/alsa/alsa-acp-device.c index ee8e6f15..5d46feed 100644 --- a/spa/plugins/alsa/alsa-acp-device.c +++ b/spa/plugins/alsa/alsa-acp-device.c @@ -157,6 +157,7 @@ static int emit_node(struct impl *this, struct acp_device *dev) char device_name[128], path[210], channels[16], ch[12], routes[16]; char card_index[16], card_name[64], *p; char positions[SPA_AUDIO_MAX_CHANNELS * 12]; + char codecs[512]; struct spa_device_object_info info; struct acp_card *card = this->card; const char *stream, *card_id; @@ -174,7 +175,7 @@ static int emit_node(struct impl *this, struct acp_device *dev) info.change_mask = SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS; - items = alloca((dev->props.n_items + 9) * sizeof(*items)); + items = alloca((dev->props.n_items + 11) * sizeof(*items)); n_items = 0; snprintf(card_index, sizeof(card_index), "%d", card->index); @@ -191,6 +192,7 @@ static int emit_node(struct impl *this, struct acp_device *dev) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_CARD, card_index); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_PCM_STREAM, stream); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, stream); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_ICON_NAME, "audio-card-analog"); snprintf(channels, sizeof(channels), "%d", dev->format.channels); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_CHANNELS, channels); @@ -202,6 +204,11 @@ static int emit_node(struct impl *this, struct acp_device *dev) } items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_POSITION, positions); + if (dev->n_codecs > 0) { + acp_iec958_codecs_to_json(dev->codecs, dev->n_codecs, codecs, sizeof(codecs)); + items[n_items++] = SPA_DICT_ITEM_INIT("iec958.codecs", codecs); + } + snprintf(routes, sizeof(routes), "%d", dev->n_ports); items[n_items++] = SPA_DICT_ITEM_INIT("device.routes", routes); @@ -754,6 +761,32 @@ static uint32_t find_route_by_name(struct acp_card *card, const char *name) return SPA_ID_INVALID; } +static bool check_active_profile_port(struct impl *this, uint32_t device, uint32_t port_index) +{ + struct acp_port *p; + uint32_t i; + + if (port_index >= this->card->n_ports) + return false; + p = this->card->ports[port_index]; + + /* Port must be in active profile */ + for (i = 0; i < p->n_profiles; i++) + if (p->profiles[i]->index == this->card->active_profile_index) + break; + if (i == p->n_profiles) + return false; + + /* Port must correspond to the device */ + for (i = 0; i< p->n_devices; i++) + if (p->devices[i]->index == device) + break; + if (i == p->n_devices) + return false; + + return true; +} + static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param) @@ -831,6 +864,8 @@ static int impl_set_param(void *object, idx = find_route_by_name(this->card, name); if (idx == SPA_ID_INVALID) return -EINVAL; + if (!check_active_profile_port(this, device, idx)) + return -EINVAL; acp_device_set_port(dev, idx, save ? ACP_PORT_SAVE : 0); if (props) diff --git a/spa/plugins/alsa/alsa-pcm-sink.c b/spa/plugins/alsa/alsa-pcm-sink.c index 49e6ee55..6c340cc6 100644 --- a/spa/plugins/alsa/alsa-pcm-sink.c +++ b/spa/plugins/alsa/alsa-pcm-sink.c @@ -27,6 +27,7 @@ static void reset_props(struct props *props) { strncpy(props->device, default_device, 64); props->use_chmap = DEFAULT_USE_CHMAP; + spa_scnprintf(props->media_class, sizeof(props->media_class), "%s", "Audio/Sink"); } static int impl_node_enum_params(void *object, int seq, @@ -757,7 +758,8 @@ impl_node_port_set_io(void *object, break; case SPA_IO_RateMatch: this->rate_match = data; - spa_alsa_update_rate_match(this); + if (this->rate_match) + spa_alsa_update_rate_match(this); break; default: return -ENOENT; diff --git a/spa/plugins/alsa/alsa-pcm-source.c b/spa/plugins/alsa/alsa-pcm-source.c index 5ee9a4c9..a86e8aa0 100644 --- a/spa/plugins/alsa/alsa-pcm-source.c +++ b/spa/plugins/alsa/alsa-pcm-source.c @@ -28,6 +28,7 @@ static void reset_props(struct props *props) { strncpy(props->device, default_device, 64); props->use_chmap = DEFAULT_USE_CHMAP; + spa_scnprintf(props->media_class, sizeof(props->media_class), "%s", "Audio/Source"); } static int impl_node_enum_params(void *object, int seq, @@ -687,7 +688,8 @@ impl_node_port_set_io(void *object, break; case SPA_IO_RateMatch: this->rate_match = data; - spa_alsa_update_rate_match(this); + if (this->rate_match) + spa_alsa_update_rate_match(this); break; default: return -ENOENT; diff --git a/spa/plugins/alsa/alsa-pcm.c b/spa/plugins/alsa/alsa-pcm.c index b0789793..36834eae 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -33,13 +33,16 @@ static struct card *find_card(uint32_t index) return NULL; } -static struct card *ensure_card(uint32_t index, bool ucm) +static struct card *ensure_card(uint32_t index, bool ucm, bool ucm_split) { struct card *c; - char card_name[64]; + char card_name[128]; const char *alibpref = NULL; int err; + if (index == SPA_ID_INVALID) + return NULL; + if ((c = find_card(index)) != NULL) return c; @@ -48,7 +51,9 @@ static struct card *ensure_card(uint32_t index, bool ucm) c->index = index; if (ucm) { - snprintf(card_name, sizeof(card_name), "hw:%i", index); + const char *split_prefix = ucm_split ? "<<<SplitPCM=1>>>" : ""; + + snprintf(card_name, sizeof(card_name), "%shw:%i", split_prefix, index); err = snd_use_case_mgr_open(&c->ucm, card_name); if (err < 0) { char *name; @@ -56,7 +61,7 @@ static struct card *ensure_card(uint32_t index, bool ucm) if (err < 0) goto error; - snprintf(card_name, sizeof(card_name), "%s", name); + snprintf(card_name, sizeof(card_name), "%s%s", split_prefix, name); free(name); err = snd_use_case_mgr_open(&c->ucm, card_name); @@ -78,6 +83,9 @@ error: static void release_card(struct card *c) { + if (!c) + return; + spa_assert(c->ref > 0); if (--c->ref > 0) @@ -91,6 +99,64 @@ static void release_card(struct card *c) free(c); } +#define CHECK(s,msg,...) if ((err = (s)) < 0) { spa_log_error(state->log, msg ": %s", ##__VA_ARGS__, snd_strerror(err)); return err; } + +static int write_bind_ctl_param(struct state *state, const char *name, const char *param) { + int err; + unsigned int count, idx; + char _name[1024]; + + for (unsigned int i = 0; i < state->num_bind_ctls; i++) { + snd_ctl_elem_info_t *info = state->bound_ctls[i].info; + bool changed = false; + int type; + + if(!state->bound_ctls[i].value || !info) + continue; + + snprintf(_name, sizeof(_name), "api.alsa.bind-ctl.%s", + snd_ctl_elem_info_get_name(info)); + + if (!spa_streq(name, _name)) + continue; + + type = snd_ctl_elem_info_get_type(info); + count = snd_ctl_elem_info_get_count(info); + + switch (type) { + case SND_CTL_ELEM_TYPE_BOOLEAN: { + bool b = spa_atob(param); + + for (idx = 0; idx < count; idx++) + snd_ctl_elem_value_set_boolean(state->bound_ctls[i].value, idx, b); + changed = true; + } + break; + + case SND_CTL_ELEM_TYPE_INTEGER: { + long l = (long) atoi(param); + + for (idx = 0; idx < count; idx++) + snd_ctl_elem_value_set_integer(state->bound_ctls[i].value, idx, l); + changed = true; + } + break; + + default: + spa_log_warn(state->log, "%s ctl '%s' not supported", + snd_ctl_elem_type_name(snd_ctl_elem_info_get_type(info)), + snd_ctl_elem_info_get_name(info)); + break; + } + + if(changed) + CHECK(snd_ctl_elem_write(state->ctl, state->bound_ctls[i].value), "snd_ctl_elem_write"); + return 0; + } + + return 0; +} + static int alsa_set_param(struct state *state, const char *k, const char *s) { int fmt_change = 0; @@ -101,7 +167,7 @@ static int alsa_set_param(struct state *state, const char *k, const char *s) state->default_rate = atoi(s); fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_FORMAT)) { - state->default_format = spa_alsa_format_from_name(s, strlen(s)); + state->default_format = spa_type_audio_format_from_short_name(s); fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { spa_alsa_parse_position(&state->default_pos, s, strlen(s)); @@ -144,6 +210,13 @@ static int alsa_set_param(struct state *state, const char *k, const char *s) } else if (spa_streq(k, "clock.name")) { spa_scnprintf(state->clock_name, sizeof(state->clock_name), "%s", s); + } else if (spa_strstartswith(k, "api.alsa.bind-ctl.")) { + write_bind_ctl_param(state, k, s); + fmt_change++; + } else if (spa_streq(k, SPA_KEY_MEDIA_CLASS)) { + spa_scnprintf(state->props.media_class, sizeof(state->props.media_class), "%s", s); + } else if (spa_streq(k, "api.alsa.split.parent")) { + state->is_split_parent = true; } else return 0; @@ -628,7 +701,6 @@ 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) { @@ -657,7 +729,7 @@ static void silence_error_handler(const char *file, int line, static void fill_device_name(struct state *state, const char *params, char device_name[], size_t len) { spa_scnprintf(device_name, len, "%s%s%s", - state->card->ucm_prefix ? state->card->ucm_prefix : "", + state->card && state->card->ucm_prefix ? state->card->ucm_prefix : "", state->props.device, params ? params : ""); } @@ -911,16 +983,15 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info) } else if (spa_streq(k, "clock.quantum-limit")) { spa_atou32(s, &state->quantum_limit, 0); } else if (spa_streq(k, SPA_KEY_API_ALSA_BIND_CTLS)) { - struct spa_json it[2]; + struct spa_json it[1]; char v[256]; unsigned int i = 0; /* Read a list of ALSA control names to bind as params */ - spa_json_init(&it[0], s, strlen(s)); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], s, strlen(s)); + if (spa_json_begin_array_relax(&it[0], s, strlen(s)) <= 0) + continue; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && + while (spa_json_get_string(&it[0], v, sizeof(v)) > 0 && i < SPA_N_ELEMENTS(state->bound_ctls)) { snprintf(state->bound_ctls[i].name, sizeof(state->bound_ctls[i].name), "%s", v); @@ -938,13 +1009,12 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info) /* If we don't have a card index, see if we have a *:<idx> string */ sscanf(state->props.device, "%*[^:]:%u", &state->card_index); if (state->card_index == SPA_ID_INVALID) { - spa_log_error(state->log, "Could not determine card index, maybe set %s", - SPA_KEY_API_ALSA_CARD); - return -EINVAL; + spa_log_info(state->log, "Could not determine card index. %s and/or clock.name " + "may need to be configured manually", SPA_KEY_API_ALSA_PCM_CARD); } } - if (state->clock_name[0] == '\0') + if (state->clock_name[0] == '\0' && state->card_index != SPA_ID_INVALID) snprintf(state->clock_name, sizeof(state->clock_name), "api.alsa.%s-%u", state->stream == SND_PCM_STREAM_PLAYBACK ? "p" : "c", @@ -956,11 +1026,8 @@ int spa_alsa_init(struct state *state, const struct spa_dict *info) state->iec958_codecs |= 1ULL << SPA_AUDIO_IEC958_CODEC_PCM; } - state->card = ensure_card(state->card_index, state->open_ucm); - if (state->card == NULL) { - spa_log_error(state->log, "can't create card %u", state->card_index); - return -errno; - } + state->card = ensure_card(state->card_index, state->open_ucm, state->is_split_parent); + state->log_file = fopencookie(state, "w", io_funcs); if (state->log_file == NULL) { spa_log_error(state->log, "can't create log file"); @@ -1203,7 +1270,7 @@ int spa_alsa_close(struct state *state) else state->n_fds = 0; - if (state->have_format) + if (state->have_format && state->card) state->card->format_ref--; state->have_format = false; @@ -1423,7 +1490,7 @@ static int add_rate(struct state *state, uint32_t scale, uint32_t interleave, bo if (max < min) return 0; - if (!state->multi_rate && state->card->format_ref > 0) + if (!state->multi_rate && state->card && state->card->format_ref > 0) rate = state->card->rate; else rate = state->default_rate; @@ -1439,8 +1506,8 @@ static int add_rate(struct state *state, uint32_t scale, uint32_t interleave, bo 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_log_debug(state->log, "rate:%u multi:%d card:%u def:%d", + rate, state->multi_rate, state->card ? state->card->rate : 0, state->default_rate); spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0); @@ -2014,7 +2081,7 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ unsigned aes3; spa_log_info(state->log, "using IEC958 Codec:%s rate:%d", - spa_debug_type_find_short_name(spa_type_audio_iec958_codec, f->codec), + spa_type_audio_iec958_codec_to_short_name(f->codec), f->rate); rformat = SND_PCM_FORMAT_S16_LE; @@ -2172,6 +2239,7 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ } if (!state->multi_rate && + state->card && state->card->format_ref > 0 && state->card->rate != rrate) { spa_log_error(state->log, "%p: card already opened at rate:%i", @@ -2217,7 +2285,7 @@ int spa_alsa_set_format(struct state *state, struct spa_audio_info *fmt, uint32_ state->driver_rate.denom = 0; state->have_format = true; - if (state->card->format_ref++ == 0) + if (state->card && state->card->format_ref++ == 0) state->card->rate = rrate; dir = 0; @@ -2716,7 +2784,7 @@ static int get_status(struct state *state, uint64_t current_time, snd_pcm_uframe static int update_time(struct state *state, uint64_t current_time, snd_pcm_sframes_t delay, snd_pcm_sframes_t target, bool follower) { - double err, corr; + double err, corr, avg; int32_t diff; if (state->disable_tsched && !follower) { @@ -2754,22 +2822,36 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram err = -state->max_error; } - if (!follower || state->matching) + if (!follower || state->matching) { corr = spa_dll_update(&state->dll, err); - else + + avg = (state->err_avg * state->err_wdw + (err - state->err_avg)) / (state->err_wdw + 1.0); + state->err_var = (state->err_var * state->err_wdw + + (err - state->err_avg) * (err - avg)) / (state->err_wdw + 1.0); + state->err_avg = avg; + } else { corr = 1.0; + } if (diff < 0) state->next_time += (uint64_t)(diff / corr * 1e9 / state->rate); if (SPA_UNLIKELY((state->next_time - state->base_time) > BW_PERIOD)) { + double bw; + state->base_time = state->next_time; + bw = (fabs(state->err_avg) + sqrt(fabs(state->err_var)))/1000.0; + spa_log_debug(state->log, "%s: follower:%d match:%d rate:%f " - "bw:%f thr:%u del:%ld target:%ld err:%f max:%f", + "bw:%f thr:%u del:%ld target:%ld err:%f max_err:%f max_resync: %f var:%f:%f:%f", state->name, follower, state->matching, corr, state->dll.bw, state->threshold, delay, target, - err, state->max_error); + err, state->max_error, state->max_resync, state->err_avg, state->err_var, bw); + + spa_dll_set_bw(&state->dll, + SPA_CLAMPD(bw, 0.001, SPA_DLL_BW_MAX), + state->threshold, state->rate); } if (state->rate_match) { @@ -2786,7 +2868,7 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram state->next_time += (uint64_t)(state->threshold / corr * 1e9 / state->rate); - if (SPA_LIKELY(!follower && state->clock)) { + if (SPA_LIKELY(state->clock)) { state->clock->nsec = current_time; state->clock->rate = state->driver_rate; state->clock->position += state->clock->duration; @@ -2872,8 +2954,9 @@ static inline int check_position_config(struct state *state, bool starting) state->driver_duration = target_duration; state->driver_rate = target_rate; state->threshold = SPA_SCALE32_UP(state->driver_duration, state->rate, state->driver_rate.denom); - state->max_error = SPA_MAX(256.0f, state->threshold / 2.0f); - state->max_resync = SPA_MIN(state->threshold, state->max_error); + state->max_error = SPA_MAX(256.0f, (state->threshold + state->headroom) / 2.0f); + state->max_resync = SPA_MIN(state->threshold + state->headroom, state->max_error); + state->err_wdw = (double)state->driver_rate.denom/state->driver_duration; state->resample = !state->pitch_elem && (((uint32_t)state->rate != state->driver_rate.denom) || state->matching); state->alsa_sync = true; @@ -3024,10 +3107,14 @@ again: if (state->use_mmap && written > 0) { if (SPA_UNLIKELY((commitres = snd_pcm_mmap_commit(hndl, offset, written)) < 0)) { - spa_log_error(state->log, "%s: snd_pcm_mmap_commit error: %s", - state->name, snd_strerror(commitres)); - if (commitres != -EPIPE && commitres != -ESTRPIPE) + if (commitres == -EPIPE || commitres == -ESTRPIPE) { + spa_log_warn(state->log, "%s: snd_pcm_mmap_commit error: %s", + state->name, snd_strerror(commitres)); + } else { + spa_log_error(state->log, "%s: snd_pcm_mmap_commit error: %s", + state->name, snd_strerror(commitres)); return res; + } } if (commitres > 0 && written != (snd_pcm_uframes_t) commitres) { spa_log_warn(state->log, "%s: mmap_commit wrote %ld instead of %ld", @@ -3617,7 +3704,7 @@ int spa_alsa_start(struct state *state) } /* We only add the source to the data loop if we're driving. - * This is done in setup_sources() */ + * This is done in add_sources() */ for (int i = 0; i < state->n_fds; i++) { state->source[i].func = alsa_irq_wakeup_event; state->source[i].data = state; @@ -3744,8 +3831,7 @@ void spa_alsa_emit_node_info(struct state *state, bool full) 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, - state->stream == SND_PCM_STREAM_PLAYBACK ? "Audio/Sink" : "Audio/Source"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, state->props.media_class); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_NODE_DRIVER, "true"); if (state->have_format) diff --git a/spa/plugins/alsa/alsa-pcm.h b/spa/plugins/alsa/alsa-pcm.h index 3dc1ec9d..bd77abc2 100644 --- a/spa/plugins/alsa/alsa-pcm.h +++ b/spa/plugins/alsa/alsa-pcm.h @@ -30,6 +30,7 @@ extern "C" { #include <spa/param/param.h> #include <spa/param/latency-utils.h> #include <spa/param/audio/format-utils.h> +#include <spa/param/audio/raw-json.h> #include <spa/param/tag-utils.h> #include "alsa.h" @@ -49,6 +50,7 @@ struct props { char device[64]; char device_name[128]; char card_name[128]; + char media_class[128]; bool use_chmap; }; @@ -152,6 +154,7 @@ struct state { unsigned int disable_mmap:1; unsigned int disable_batch:1; unsigned int disable_tsched:1; + unsigned int is_split_parent:1; char clock_name[64]; uint32_t quantum_limit; @@ -243,6 +246,7 @@ struct state { struct spa_dll dll; double max_error; double max_resync; + double err_avg, err_var, err_wdw; struct spa_latency_info latency[2]; struct spa_process_latency_info process_latency; @@ -303,79 +307,31 @@ void spa_alsa_recycle_buffer(struct state *state, uint32_t buffer_id); void spa_alsa_emit_node_info(struct state *state, bool full); void spa_alsa_emit_port_info(struct state *state, bool full); -static inline uint32_t spa_alsa_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_alsa_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_alsa_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_alsa_channel_from_name(v); - } + spa_audio_parse_position(val, len, map->pos, &map->channels); } static inline uint32_t spa_alsa_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; + return spa_json_str_array_uint32(val, len, rates, max); } static inline uint32_t spa_alsa_iec958_codec_from_name(const char *name) { - int i; - for (i = 0; spa_type_audio_iec958_codec[i].name; i++) { - if (strcmp(name, spa_debug_type_short_name(spa_type_audio_iec958_codec[i].name)) == 0) - return spa_type_audio_iec958_codec[i].type; - } - return SPA_AUDIO_IEC958_CODEC_UNKNOWN; + return spa_type_audio_iec958_codec_from_short_name(name); } static inline void spa_alsa_parse_iec958_codecs(uint64_t *codecs, const char *val, size_t len) { - struct spa_json it[2]; + struct spa_json it[1]; 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); + if (spa_json_begin_array_relax(&it[0], val, len) <= 0) + return; *codecs = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0) + while (spa_json_get_string(&it[0], v, sizeof(v)) > 0) *codecs |= 1ULL << spa_alsa_iec958_codec_from_name(v); } diff --git a/spa/plugins/alsa/alsa-seq-bridge.c b/spa/plugins/alsa/alsa-seq-bridge.c index 3592fbd7..f85b41e2 100644 --- a/spa/plugins/alsa/alsa-seq-bridge.c +++ b/spa/plugins/alsa/alsa-seq-bridge.c @@ -17,18 +17,20 @@ #include <spa/monitor/device.h> #include <spa/param/audio/format.h> #include <spa/param/latency-utils.h> +#include <spa/control/control.h> #include <spa/pod/filter.h> #include "alsa-seq.h" #define DEFAULT_DEVICE "default" #define DEFAULT_CLOCK_NAME "clock.system.monotonic" +#define DEFAULT_DISABLE_LONGNAME true 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; + props->disable_longname = DEFAULT_DISABLE_LONGNAME; } static int impl_node_enum_params(void *object, int seq, @@ -227,9 +229,11 @@ static void emit_port_info(struct seq_state *this, struct seq_port *port, bool f if (port->info.change_mask) { struct spa_dict_item items[6]; uint32_t n_items = 0; - int id; + int card_id; snd_seq_port_info_t *info; snd_seq_client_info_t *client_info; + const char *client_name, *port_name, *dir, *pn; + char prefix[32] = ""; char card[8]; char name[256]; char path[128]; @@ -244,59 +248,40 @@ 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); - int card_id; + card_id = snd_seq_client_info_get_card(client_info); + client_name = snd_seq_client_info_get_name(client_info); + port_name = snd_seq_port_info_get_name(info); + dir = port->direction == SPA_DIRECTION_OUTPUT ? "capture" : "playback"; - // 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)); - } - } + if (!this->props.disable_longname) + snprintf(prefix, sizeof(prefix), "[%d:%d] ", + port->addr.client, port->addr.port); + + pn = port_name; + if (spa_strstartswith(pn, client_name)) + pn += strlen(client_name); + + snprintf(name, sizeof(name), "%s%s%s (%s)", prefix, + client_name, pn, dir); clean_name(name); snprintf(stream, sizeof(stream), "client_%d", port->addr.client); clean_name(stream); snprintf(path, sizeof(path), "alsa:seq:%s:%s:%s_%d", - this->props.device, - stream, - port->direction == SPA_DIRECTION_OUTPUT ? "capture" : "playback", - port->addr.port); + this->props.device, stream, dir, port->addr.port); clean_name(path); - snprintf(alias, sizeof(alias), "%s:%s", - snd_seq_client_info_get_name(client_info), - snd_seq_port_info_get_name(info)); + snprintf(alias, sizeof(alias), "%s:%s", client_name, port_name); clean_name(alias); - - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_OBJECT_PATH, path); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, name); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, alias); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, stream); - if ((id = snd_seq_client_info_get_card(client_info)) != -1) { - snprintf(card, sizeof(card), "%d", id); + if (card_id != -1) { + snprintf(card, sizeof(card), "%d", card_id); items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_CARD, card); } port->info.props = &SPA_DICT_INIT(items, n_items); @@ -544,7 +529,8 @@ impl_node_port_enum_params(void *object, int seq, 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)); + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), + SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int(1u<<SPA_CONTROL_UMP)); break; case SPA_PARAM_Format: @@ -555,7 +541,8 @@ impl_node_port_enum_params(void *object, int seq, 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)); + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), + SPA_FORMAT_CONTROL_types, SPA_POD_Int(1u<<SPA_CONTROL_UMP)); break; case SPA_PARAM_Buffers: @@ -648,6 +635,7 @@ static int port_set_format(void *object, struct seq_port *port, port->have_format = false; } else { struct spa_audio_info info = { 0 }; + uint32_t types; if ((err = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) return err; @@ -656,6 +644,13 @@ static int port_set_format(void *object, struct seq_port *port, info.media_subtype != SPA_MEDIA_SUBTYPE_control) return -EINVAL; + if ((err = spa_pod_parse_object(format, + SPA_TYPE_OBJECT_Format, NULL, + SPA_FORMAT_CONTROL_types, SPA_POD_Int(&types))) < 0) + return err; + if (types != 1u << SPA_CONTROL_UMP) + return -EINVAL; + port->current_format = info; port->have_format = true; } diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c index 95947dfc..2a4ebd2c 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -16,6 +16,7 @@ #include <spa/pod/filter.h> #include <spa/support/system.h> #include <spa/control/control.h> +#include <spa/control/ump-utils.h> #include "alsa.h" @@ -33,9 +34,16 @@ static int seq_open(struct seq_state *state, struct seq_conn *conn, bool with_qu if ((res = snd_seq_open(&conn->hndl, props->device, SND_SEQ_OPEN_DUPLEX, - 0)) < 0) { + 0)) < 0) + return res; + + if ((res = snd_seq_set_client_midi_version(conn->hndl, SND_SEQ_CLIENT_UMP_MIDI_2_0)) < 0) { + snd_seq_close(conn->hndl); + spa_log_info(state->log, "%p: ALSA failed to enable UMP MIDI: %s", + state, snd_strerror(res)); return res; } + return 0; } @@ -164,7 +172,7 @@ static void init_ports(struct seq_state *state) } } -static void debug_event(struct seq_state *state, snd_seq_event_t *ev) +static void debug_event(struct seq_state *state, snd_seq_ump_event_t *ev) { if (SPA_LIKELY(!spa_log_level_topic_enabled(state->log, SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_TRACE))) return; @@ -191,10 +199,10 @@ static void debug_event(struct seq_state *state, snd_seq_event_t *ev) static void alsa_seq_on_sys(struct spa_source *source) { struct seq_state *state = source->data; - snd_seq_event_t *ev; + snd_seq_ump_event_t *ev; int res; - while (snd_seq_event_input(state->sys.hndl, &ev) > 0) { + while (snd_seq_ump_event_input(state->sys.hndl, &ev) > 0) { const snd_seq_addr_t *addr = &ev->data.addr; if (addr->client == state->event.addr.client) @@ -240,7 +248,6 @@ static void alsa_seq_on_sys(struct spa_source *source) break; } - snd_seq_free_event(ev); } } @@ -522,15 +529,15 @@ static int process_recycle(struct seq_state *state) static int process_read(struct seq_state *state) { - snd_seq_event_t *ev; + snd_seq_ump_event_t *ev; struct seq_stream *stream = &state->streams[SPA_DIRECTION_OUTPUT]; uint32_t i; + uint32_t *data; long size; - uint8_t data[MAX_EVENT_SIZE]; int res; /* copy all new midi events into their port buffers */ - while ((res = snd_seq_event_input(state->event.hndl, &ev)) > 0) { + while ((res = snd_seq_ump_event_input(state->event.hndl, &ev)) > 0) { const snd_seq_addr_t *addr = &ev->source; struct seq_port *port; uint64_t ev_time, diff; @@ -552,11 +559,8 @@ static int process_read(struct seq_state *state) continue; } - snd_midi_event_reset_decode(stream->codec); - if ((size = snd_midi_event_decode(stream->codec, data, MAX_EVENT_SIZE, ev)) < 0) { - spa_log_warn(state->log, "decode failed: %s", snd_strerror(size)); - continue; - } + data = (uint32_t*)&ev->ump[0]; + size = spa_ump_message_size(snd_ump_msg_hdr_type(ev->ump[0])) * 4; /* queue_time is the estimated current time of the queue as calculated by * the DLL. Calculate the age of the event. */ @@ -576,11 +580,9 @@ static int process_read(struct seq_state *state) spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d", ev->type, ev_time, offset, size, addr->client, addr->port); - spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_Midi); + spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP); spa_pod_builder_bytes(&port->builder, data, size); - snd_seq_free_event(ev); - /* make sure we can fit at least one control event of max size otherwise * we keep the event in the queue and try to copy it in the next cycle */ if (port->builder.state.offset + @@ -659,10 +661,9 @@ static int process_write(struct seq_state *state) struct spa_pod_sequence *pod; struct spa_data *d; struct spa_pod_control *c; - snd_seq_event_t ev; + snd_seq_ump_event_t ev; uint64_t out_time; snd_seq_real_time_t out_rt; - long size = 0; if (!port->valid || io == NULL) continue; @@ -686,53 +687,33 @@ static int process_write(struct seq_state *state) } SPA_POD_SEQUENCE_FOREACH(pod, c) { - long s, body_size; + size_t body_size; uint8_t *body; - if (c->type != SPA_CONTROL_Midi) + if (c->type != SPA_CONTROL_UMP) continue; body = SPA_POD_BODY(&c->value); body_size = SPA_POD_BODY_SIZE(&c->value); + spa_zero(ev); + + memcpy(ev.ump, body, SPA_MIN(sizeof(ev.ump), (size_t)body_size)); + + snd_seq_ev_set_source(&ev, state->event.addr.port); + snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); + + out_time = state->queue_time + NSEC_FROM_CLOCK(&state->rate, c->offset); + + out_rt.tv_nsec = out_time % SPA_NSEC_PER_SEC; + out_rt.tv_sec = out_time / SPA_NSEC_PER_SEC; + snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt); + + spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%zd port:%d.%d", + ev.type, out_time, c->offset, body_size, port->addr.client, port->addr.port); - while (body_size > 0) { - if (size == 0) - /* only reset when we start decoding a new message */ - snd_seq_ev_clear(&ev); - - if ((s = snd_midi_event_encode(stream->codec, - body, body_size, &ev)) < 0) { - spa_log_warn(state->log, "failed to encode event: %s", - snd_strerror(s)); - snd_midi_event_reset_encode(stream->codec); - size = 0; - break; - } - body += s; - body_size -= s; - size += s; - if (ev.type == SND_SEQ_EVENT_NONE) - /* this can happen when the event is not complete yet, like - * a sysex message and we need to encode some more data. */ - break; - - snd_seq_ev_set_source(&ev, state->event.addr.port); - snd_seq_ev_set_dest(&ev, port->addr.client, port->addr.port); - - out_time = state->queue_time + NSEC_FROM_CLOCK(&state->rate, c->offset); - - out_rt.tv_nsec = out_time % SPA_NSEC_PER_SEC; - out_rt.tv_sec = out_time / SPA_NSEC_PER_SEC; - snd_seq_ev_schedule_real(&ev, state->event.queue_id, 0, &out_rt); - - spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d", - ev.type, out_time, c->offset, size, port->addr.client, port->addr.port); - - if ((err = snd_seq_event_output(state->event.hndl, &ev)) < 0) { - spa_log_warn(state->log, "failed to output event: %s", - snd_strerror(err)); - } - size = 0; + if ((err = snd_seq_ump_event_output(state->event.hndl, &ev)) < 0) { + spa_log_warn(state->log, "failed to output event: %s", + snd_strerror(err)); } } } @@ -809,7 +790,7 @@ static int update_time(struct seq_state *state, uint64_t nsec, bool follower) } state->next_time += (uint64_t)(state->threshold / corr * 1e9 / state->rate.denom); - if (!follower && state->clock) { + if (SPA_LIKELY(state->clock)) { state->clock->nsec = nsec; state->clock->rate = state->rate; state->clock->position += state->clock->duration; diff --git a/spa/plugins/alsa/alsa-seq.h b/spa/plugins/alsa/alsa-seq.h index 23f71d63..0f2c192c 100644 --- a/spa/plugins/alsa/alsa-seq.h +++ b/spa/plugins/alsa/alsa-seq.h @@ -13,6 +13,7 @@ extern "C" { #include <math.h> #include <alsa/asoundlib.h> +#include <alsa/ump_msg.h> #include <spa/support/plugin.h> #include <spa/support/loop.h> diff --git a/spa/plugins/alsa/mixer/profile-sets/hdmi-ac3.conf b/spa/plugins/alsa/mixer/profile-sets/hdmi-ac3.conf new file mode 100644 index 00000000..ea6cc9e7 --- /dev/null +++ b/spa/plugins/alsa/mixer/profile-sets/hdmi-ac3.conf @@ -0,0 +1,110 @@ +# 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/>. + +; Profile set with HDMI/AC3 profiles. +; +; You can use udev rules to enable these, for example: +; +; ATTRS{subsystem_vendor}=="0x1849", ATTRS{subsystem_device}=="0xaaf0", ENV{ACP_PROFILE_SET}="hdmi-ac3.conf" + +.include default.conf + +[Mapping hdmi-ac3-surround] +description = Digital Surround 5.1 (HDMI/AC3) +device-strings = plug:{SLAVE="a52:%f,'hw:%f,3'"} +paths-output = hdmi-output-0 +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +priority = 1 +direction = output + +[Mapping hdmi-ac3-surround-extra1] +description = Digital Surround 5.1 (HDMI 2/AC3) +device-strings = plug:{SLAVE="a52:%f,'hw:%f,7'"} +paths-output = hdmi-output-1 +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +priority = 1 +direction = output + +[Mapping hdmi-ac3-surround-extra2] +description = Digital Surround 5.1 (HDMI 3/AC3) +device-strings = plug:{SLAVE="a52:%f,'hw:%f,8'"} +paths-output = hdmi-output-2 +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +priority = 1 +direction = output + +[Mapping hdmi-ac3-surround-extra3] +description = Digital Surround 5.1 (HDMI 4/AC3) +device-strings = plug:{SLAVE="a52:%f,'hw:%f,9'"} +paths-output = hdmi-output-3 +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +priority = 1 +direction = output + +[Mapping hdmi-ac3-surround-extra4] +description = Digital Surround 5.1 (HDMI 5/AC3) +device-strings = plug:{SLAVE="a52:%f,'hw:%f,10'"} +paths-output = hdmi-output-4 +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +priority = 1 +direction = output + +[Mapping hdmi-ac3-surround-extra5] +description = Digital Surround 5.1 (HDMI 6/AC3) +device-strings = plug:{SLAVE="a52:%f,'hw:%f,11'"} +paths-output = hdmi-output-5 +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +priority = 1 +direction = output + +[Mapping hdmi-ac3-surround-extra6] +description = Digital Surround 5.1 (HDMI 7/AC3) +device-strings = plug:{SLAVE="a52:%f,'hw:%f,12'"} +paths-output = hdmi-output-6 +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +priority = 1 +direction = output + +[Mapping hdmi-ac3-surround-extra7] +description = Digital Surround 5.1 (HDMI 8/AC3) +device-strings = plug:{SLAVE="a52:%f,'hw:%f,13'"} +paths-output = hdmi-output-7 +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +priority = 1 +direction = output + +[Mapping hdmi-ac3-surround-extra8] +description = Digital Surround 5.1 (HDMI 9/AC3) +device-strings = plug:{SLAVE="a52:%f,'hw:%f,14'"} +paths-output = hdmi-output-8 +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +priority = 1 +direction = output + +[Mapping hdmi-ac3-surround-extra9] +description = Digital Surround 5.1 (HDMI 10/AC3) +device-strings = plug:{SLAVE="a52:%f,'hw:%f,15'"} +paths-output = hdmi-output-9 +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +priority = 1 +direction = output + +[Mapping hdmi-ac3-surround-extra10] +description = Digital Surround 5.1 (HDMI 11/AC3) +device-strings = plug:{SLAVE="a52:%f,'hw:%f,16'"} +paths-output = hdmi-output-10 +channel-map = front-left,front-right,rear-left,rear-right,front-center,lfe +priority = 1 +direction = output diff --git a/spa/plugins/alsa/mixer/samples/USB Device 0x46d:0x9a4--USB Mixer b/spa/plugins/alsa/mixer/samples/USB Device 0x46d_0x9a4--USB Mixer similarity index 100% rename from spa/plugins/alsa/mixer/samples/USB Device 0x46d:0x9a4--USB Mixer rename to spa/plugins/alsa/mixer/samples/USB Device 0x46d_0x9a4--USB Mixer diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 5e91f36f..6e393bd0 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -3,6 +3,7 @@ /* SPDX-License-Identifier: MIT */ #include <spa/support/plugin.h> +#include <spa/support/plugin-loader.h> #include <spa/support/log.h> #include <spa/support/cpu.h> @@ -43,6 +44,7 @@ struct impl { struct spa_log *log; struct spa_cpu *cpu; + struct spa_plugin_loader *ploader; uint32_t max_align; enum spa_direction direction; @@ -57,9 +59,11 @@ struct impl { int in_set_param; struct spa_handle *hnd_convert; + bool unload_handle; struct spa_node *convert; struct spa_hook convert_listener; - uint64_t convert_flags; + uint64_t convert_port_flags; + char *convertname; uint32_t n_buffers; struct spa_buffer **buffers; @@ -94,16 +98,42 @@ struct impl { unsigned int started:1; unsigned int ready:1; unsigned int async:1; - unsigned int passthrough:1; + enum spa_param_port_config_mode mode; unsigned int follower_removing:1; unsigned int in_recalc; unsigned int warned:1; unsigned int driver:1; + + int in_enum_sync; }; /** \endcond */ +static int node_enum_params_sync(struct impl *impl, struct spa_node *node, + uint32_t id, uint32_t *index, const struct spa_pod *filter, + struct spa_pod **param, struct spa_pod_builder *builder) +{ + int res; + impl->in_enum_sync++; + res = spa_node_enum_params_sync(node, id, index, filter, param, builder); + impl->in_enum_sync--; + return res; +} + +static int node_port_enum_params_sync(struct impl *impl, struct spa_node *node, + enum spa_direction direction, uint32_t port_id, + uint32_t id, uint32_t *index, const struct spa_pod *filter, + struct spa_pod **param, struct spa_pod_builder *builder) +{ + int res; + impl->in_enum_sync++; + res = spa_node_port_enum_params_sync(node, direction, port_id, id, index, + filter, param, builder); + impl->in_enum_sync--; + return res; +} + static int follower_enum_params(struct impl *this, uint32_t id, uint32_t idx, @@ -112,15 +142,16 @@ static int follower_enum_params(struct impl *this, struct spa_pod_builder *builder) { int res; - if (result->next < 0x100000) { - if ((res = spa_node_enum_params_sync(this->convert, + if (result->next < 0x100000 && + this->follower != this->target) { + if ((res = node_enum_params_sync(this, this->target, 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, + if ((res = node_enum_params_sync(this, this->follower, id, &result->next, filter, &result->param, builder)) == 1) { result->next |= 0x100000; return res; @@ -137,6 +168,9 @@ static int convert_enum_port_config(struct impl *this, struct spa_pod *f1, *f2 = NULL; int res; + if (this->convert == NULL) + return 0; + f1 = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_ParamPortConfig, id, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction)); @@ -180,9 +214,8 @@ next: switch (id) { case SPA_PARAM_EnumPortConfig: - return convert_enum_port_config(this, seq, id, start, num, filter, &b.b); case SPA_PARAM_PortConfig: - if (this->passthrough) { + if (this->mode == SPA_PARAM_PORT_CONFIG_MODE_passthrough) { switch (result.index) { case 0: result.param = spa_pod_builder_add_object(&b.b, @@ -216,7 +249,7 @@ next: case SPA_PARAM_Format: case SPA_PARAM_Latency: case SPA_PARAM_Tag: - res = spa_node_port_enum_params_sync(this->follower, + res = node_port_enum_params_sync(this, this->follower, this->direction, 0, id, &result.next, filter, &result.param, &b.b); break; @@ -261,14 +294,15 @@ static int link_io(struct impl *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, - rate_match, rate_match_size)) < 0) { - spa_log_warn(this->log, "%p: set RateMatch on convert failed %d %s", this, - res, spa_strerror(res)); + else if (this->follower != this->target) { + if ((res = spa_node_port_set_io(this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_IO_RateMatch, + rate_match, rate_match_size)) < 0) { + spa_log_warn(this->log, "%p: set RateMatch on target failed %d %s", this, + res, spa_strerror(res)); + } } - return 0; } @@ -291,7 +325,7 @@ static int activate_io(struct impl *this, bool active) res, spa_strerror(res)); return res; } - else if ((res = spa_node_port_set_io(this->convert, + else if ((res = spa_node_port_set_io(this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_IO_Buffers, data, size)) < 0) { spa_log_warn(this->log, "%p: set Buffers on convert failed %d %s", this, @@ -366,7 +400,7 @@ static int debug_params(struct impl *this, struct spa_node *node, state = 0; while (true) { spa_pod_builder_init(&b, buffer, sizeof(buffer)); - res = spa_node_port_enum_params_sync(node, + res = node_port_enum_params_sync(this, node, direction, port_id, id, &state, NULL, ¶m, &b); @@ -400,12 +434,15 @@ static int negotiate_buffers(struct impl *this) spa_log_debug(this->log, "%p: n_buffers:%d", this, this->n_buffers); + if (this->follower == this->target) + return 0; + if (this->n_buffers > 0) return 0; state = 0; param = NULL; - if ((res = spa_node_port_enum_params_sync(this->follower, + if ((res = node_port_enum_params_sync(this, this->follower, this->direction, 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) < 0) { @@ -419,11 +456,11 @@ static int negotiate_buffers(struct impl *this) } state = 0; - if ((res = spa_node_port_enum_params_sync(this->convert, + if ((res = node_port_enum_params_sync(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) != 1) { - debug_params(this, this->convert, + debug_params(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Buffers, param, "convert buffers", res); return -ENOTSUP; @@ -433,8 +470,8 @@ static int negotiate_buffers(struct impl *this) spa_pod_fixate(param); - follower_flags = this->follower_flags; - conv_flags = this->convert_flags; + follower_flags = this->follower_port_flags; + conv_flags = this->convert_port_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); @@ -481,7 +518,7 @@ static int negotiate_buffers(struct impl *this) return -errno; this->n_buffers = buffers; - if ((res = spa_node_port_use_buffers(this->convert, + if ((res = spa_node_port_use_buffers(this->target, SPA_DIRECTION_REVERSE(this->direction), 0, conv_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, this->buffers, this->n_buffers)) < 0) @@ -512,10 +549,6 @@ static int configure_format(struct impl *this, uint32_t flags, const struct spa_ spa_log_debug(this->log, "%p: configure format:", this); - if (format == NULL && !this->have_format) - return 0; - - if (format == NULL) { if (!this->have_format) return 0; @@ -538,7 +571,7 @@ static int configure_format(struct impl *this, uint32_t flags, const struct spa_ /* format was changed to nearest compatible format */ - if ((res = spa_node_port_enum_params_sync(this->follower, + if ((res = node_port_enum_params_sync(this, this->follower, this->direction, 0, SPA_PARAM_Format, &state, NULL, &fmt, &b)) != 1) @@ -548,7 +581,7 @@ static int configure_format(struct impl *this, uint32_t flags, const struct spa_ } if (this->target != this->follower) { - if ((res = spa_node_port_set_param(this->convert, + if ((res = spa_node_port_set_param(this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Format, flags, format)) < 0) @@ -558,7 +591,7 @@ static int configure_format(struct impl *this, uint32_t flags, const struct spa_ this->have_format = format != NULL; clear_buffers(this); - if (format != NULL && this->target != this->follower) + if (format != NULL) res = negotiate_buffers(this); return res; @@ -570,6 +603,9 @@ static int configure_convert(struct impl *this, uint32_t mode) 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); @@ -603,7 +639,7 @@ static int recalc_latency(struct impl *this, struct spa_node *src, enum spa_dire while (true) { spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if ((res = spa_node_port_enum_params_sync(src, + if ((res = node_port_enum_params_sync(this, src, direction, port_id, SPA_PARAM_Latency, &index, NULL, ¶m, &b)) != 1) { param = NULL; @@ -644,7 +680,7 @@ static int recalc_tag(struct impl *this, struct spa_node *src, enum spa_directio while (true) { void *tag_state = NULL; spa_pod_builder_reset(&b.b, &state); - if ((res = spa_node_port_enum_params_sync(src, + if ((res = node_port_enum_params_sync(this, src, direction, port_id, SPA_PARAM_Tag, &index, NULL, ¶m, &b.b)) != 1) { param = NULL; @@ -660,15 +696,20 @@ static int recalc_tag(struct impl *this, struct spa_node *src, enum spa_directio } -static int reconfigure_mode(struct impl *this, bool passthrough, +static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode mode, enum spa_direction direction, struct spa_pod *format) { int res = 0; struct spa_hook l; + bool passthrough = mode == SPA_PARAM_PORT_CONFIG_MODE_passthrough; + bool old_passthrough = this->mode == SPA_PARAM_PORT_CONFIG_MODE_passthrough; spa_log_debug(this->log, "%p: passthrough mode %d", this, passthrough); - if (this->passthrough != passthrough) { + if (!passthrough && this->convert == NULL) + return -ENOTSUP; + + if (old_passthrough != passthrough) { if (passthrough) { /* remove converter split/merge ports */ configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_none); @@ -688,8 +729,9 @@ static int reconfigure_mode(struct impl *this, bool passthrough, if ((res = configure_format(this, SPA_NODE_PARAM_FLAG_NEAREST, format)) < 0) return res; - if (this->passthrough != passthrough) { - this->passthrough = passthrough; + this->mode = mode; + + if (old_passthrough != passthrough) { if (passthrough) { /* add follower ports */ spa_zero(l); @@ -697,7 +739,7 @@ static int reconfigure_mode(struct impl *this, bool passthrough, spa_hook_remove(&l); } else { /* add converter ports */ - configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_dsp); + configure_convert(this, mode); } link_io(this); } @@ -730,12 +772,9 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, 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_audio || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -EINVAL; - if (spa_format_audio_raw_parse(param, &info.info.raw) < 0) + if (spa_format_audio_parse(param, &info) < 0) + return -EINVAL; + if (info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; this->follower_current_format = info; @@ -763,28 +802,27 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, struct spa_audio_info info; spa_zero(info); - if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) + if ((res = spa_format_audio_parse(format, &info)) < 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) { + if (info.media_subtype == SPA_MEDIA_SUBTYPE_raw) info.info.raw.rate = 0; - this->default_format = info; - } + else + return -ENOTSUP; + + 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) + if ((res = reconfigure_mode(this, mode, dir, format)) < 0) return res; break; case SPA_PARAM_PORT_CONFIG_MODE_convert: case SPA_PARAM_PORT_CONFIG_MODE_dsp: - if ((res = reconfigure_mode(this, false, dir, NULL)) < 0) + if ((res = reconfigure_mode(this, mode, dir, NULL)) < 0) return res; break; default: @@ -795,7 +833,7 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, if ((res = spa_node_set_param(this->target, id, flags, param)) < 0) return res; - res = recalc_latency(this, this->follower, this->direction, 0, this->convert); + res = recalc_latency(this, this->follower, this->direction, 0, this->target); } break; } @@ -881,15 +919,18 @@ static struct spa_pod *merge_objects(struct impl *this, struct spa_pod_builder * static int negotiate_format(struct impl *this) { - uint32_t state; + uint32_t fstate, tstate; struct spa_pod *format, *def; uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; - int res; + int res, fres; spa_log_debug(this->log, "%p: have_format:%d recheck:%d", this, this->have_format, this->recheck_format); + if (this->target == this->follower) + return 0; + if (this->have_format && !this->recheck_format) return 0; @@ -900,38 +941,58 @@ static int negotiate_format(struct impl *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) { + /* first try the ideal converter format, which is likely passthrough */ + tstate = 0; + fres = node_port_enum_params_sync(this, this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_EnumFormat, &tstate, + NULL, &format, &b); + if (fres == 1) { + fstate = 0; + res = node_port_enum_params_sync(this, this->follower, + this->direction, 0, + SPA_PARAM_EnumFormat, &fstate, + format, &format, &b); + if (res == 1) + goto found; + } + + /* then try something the follower can accept */ + for (fstate = 0;;) { + format = NULL; + res = node_port_enum_params_sync(this, this->follower, + this->direction, 0, + SPA_PARAM_EnumFormat, &fstate, + NULL, &format, &b); + if (res == -ENOENT) format = NULL; - else { - debug_params(this, this->follower, this->direction, 0, - SPA_PARAM_EnumFormat, format, "follower format", res); - goto done; - } + else if (res <= 0) + break; + + tstate = 0; + fres = node_port_enum_params_sync(this, this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_EnumFormat, &tstate, + format, &format, &b); + if (fres == 0 && res == 1) + continue; + + res = fres; + break; } - 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, +found: + if (format == NULL) { + debug_params(this, this->follower, this->direction, 0, + SPA_PARAM_EnumFormat, format, "follower format", res); + debug_params(this, this->target, 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_audio_raw_build(&b, - SPA_PARAM_Format, &this->default_format.info.raw); + def = spa_format_audio_build(&b, + SPA_PARAM_Format, &this->default_format); format = merge_objects(this, &b, SPA_PARAM_Format, (struct spa_pod_object*)format, @@ -961,12 +1022,10 @@ static int impl_node_send_command(void *object, const struct spa_command *comman switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: spa_log_debug(this->log, "%p: starting %d", this, this->started); - if (this->target != this->follower) { - if (this->started) - return 0; - if ((res = negotiate_format(this)) < 0) - return res; - } + if (this->started) + return 0; + if ((res = negotiate_format(this)) < 0) + return res; this->ready = true; this->warned = false; break; @@ -1091,6 +1150,8 @@ static void follower_convert_port_info(void *data, this->direction == SPA_DIRECTION_INPUT ? "Input" : "Output", info, info->change_mask); + this->convert_port_flags = info->flags; + if (info->change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { for (i = 0; i < info->n_params; i++) { uint32_t idx; @@ -1117,14 +1178,14 @@ static void follower_convert_port_info(void *data, if (idx == IDX_Latency) { this->in_recalc++; - res = recalc_latency(this, this->convert, direction, port_id, this->follower); + res = recalc_latency(this, this->target, direction, port_id, this->follower); this->in_recalc--; spa_log_debug(this->log, "latency: %d (%s)", res, spa_strerror(res)); } if (idx == IDX_Tag) { this->in_recalc++; - res = recalc_tag(this, this->convert, direction, port_id, this->follower); + res = recalc_tag(this, this->target, direction, port_id, this->follower); this->in_recalc--; spa_log_debug(this->log, "tag: %d (%s)", res, spa_strerror(res)); @@ -1151,7 +1212,10 @@ static void convert_port_info(void *data, port_id--; } else if (info) { pi = *info; - pi.flags = this->follower_port_flags; + pi.flags = this->follower_port_flags & + (SPA_PORT_FLAG_LIVE | + SPA_PORT_FLAG_PHYSICAL | + SPA_PORT_FLAG_TERMINAL); info = π } @@ -1166,7 +1230,7 @@ static void convert_result(void *data, int seq, int res, uint32_t type, const vo { struct impl *this = data; - if (this->target == this->follower) + if (this->target == this->follower || this->in_enum_sync) return; spa_log_trace(this->log, "%p: result %d %d", this, seq, res); @@ -1195,7 +1259,7 @@ static void follower_info(void *data, const struct spa_node_info *info) if (info->max_input_ports > 0) this->direction = SPA_DIRECTION_INPUT; - else + else this->direction = SPA_DIRECTION_OUTPUT; if (this->direction == SPA_DIRECTION_INPUT) { @@ -1272,10 +1336,7 @@ static void follower_port_info(void *data, return; } - this->follower_port_flags = info->flags & - (SPA_PORT_FLAG_LIVE | - SPA_PORT_FLAG_PHYSICAL | - SPA_PORT_FLAG_TERMINAL); + this->follower_port_flags = info->flags; spa_log_debug(this->log, "%p: follower port info %s %p %08"PRIx64" recalc:%u", this, this->direction == SPA_DIRECTION_INPUT ? @@ -1346,7 +1407,7 @@ static void follower_result(void *data, int seq, int res, uint32_t type, const v { struct impl *this = data; - if (this->target != this->follower) + if (this->target != this->follower || this->in_enum_sync) return; spa_log_trace(this->log, "%p: result %d %d", this, seq, res); @@ -1379,6 +1440,20 @@ static const struct spa_node_events follower_node_events = { .event = follower_event, }; +static void follower_probe_info(void *data, const struct spa_node_info *info) +{ + struct impl *this = data; + if (info->max_input_ports > 0) + this->direction = SPA_DIRECTION_INPUT; + else + this->direction = SPA_DIRECTION_OUTPUT; +} + +static const struct spa_node_events follower_probe_events = { + SPA_VERSION_NODE_EVENTS, + .info = follower_probe_info, +}; + static int follower_ready(void *data, int status) { struct impl *this = data; @@ -1396,7 +1471,7 @@ static int follower_ready(void *data, int status) if (this->direction == SPA_DIRECTION_OUTPUT) { int retry = MAX_RETRY; while (retry--) { - status = spa_node_process_fast(this->convert); + status = spa_node_process_fast(this->target); if (status & SPA_STATUS_HAVE_DATA) break; @@ -1419,7 +1494,7 @@ static int follower_reuse_buffer(void *data, uint32_t port_id, uint32_t buffer_i struct impl *this = data; if (this->target != this->follower) - res = spa_node_port_reuse_buffer(this->convert, port_id, buffer_id); + res = spa_node_port_reuse_buffer(this->target, port_id, buffer_id); else res = spa_node_call_reuse_buffer(&this->callbacks, port_id, buffer_id); @@ -1461,10 +1536,11 @@ static int impl_node_add_listener(void *object, spa_node_add_listener(this->follower, &l, &follower_node_events, this); spa_hook_remove(&l); - spa_zero(l); - spa_node_add_listener(this->convert, &l, &convert_node_events, this); - spa_hook_remove(&l); - + if (this->follower != this->target) { + spa_zero(l); + spa_node_add_listener(this->target, &l, &convert_node_events, this); + spa_hook_remove(&l); + } this->add_listener = false; emit_node_info(this, true); @@ -1525,6 +1601,55 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_ return spa_node_remove_port(this->target, direction, port_id); } +static int +port_enum_formats_for_convert(struct impl *this, int seq, enum spa_direction direction, + uint32_t port_id, uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + uint8_t buffer[4096]; + struct spa_pod_builder b = { 0 }; + int res; + uint32_t count = 0; + struct spa_result_node_params result; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + result.id = id; + result.next = start; +next: + result.index = result.next; + + if (result.next < 0x100000) { + /* Enumerate follower formats first, until we have enough or we run out */ + if ((res = node_port_enum_params_sync(this, this->follower, direction, port_id, id, + &result.next, filter, &result.param, &b)) != 1) { + if (res == 0 || res == -ENOENT) { + result.next = 0x100000; + goto next; + } else { + spa_log_error(this->log, "could not enum follower format: %s", spa_strerror(res)); + return res; + } + } + } else if (result.next < 0x200000) { + /* Then enumerate converter formats */ + result.next &= 0xfffff; + if ((res = node_port_enum_params_sync(this, this->convert, direction, port_id, id, + &result.next, filter, &result.param, &b)) != 1) { + return res; + } else { + result.next |= 0x100000; + } + } + + 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_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, @@ -1539,10 +1664,15 @@ impl_node_port_enum_params(void *object, int seq, if (direction != this->direction) port_id++; - spa_log_debug(this->log, "%p: %d %u", this, seq, id); + spa_log_debug(this->log, "%p: %d %u %u %u", this, seq, id, start, num); - return spa_node_port_enum_params(this->target, seq, direction, port_id, id, - start, num, filter); + /* We only need special handling for EnumFormat in convert mode */ + if (id == SPA_PARAM_EnumFormat && this->mode == SPA_PARAM_PORT_CONFIG_MODE_convert) + return port_enum_formats_for_convert(this, seq, direction, port_id, id, + start, num, filter); + else + return spa_node_port_enum_params(this->target, seq, direction, port_id, id, + start, num, filter); } static int @@ -1645,7 +1775,7 @@ static int impl_node_process(void *object) * First we run the converter to process the input for the follower * then if it produced data, we run the follower. */ while (retry--) { - status = spa_node_process_fast(this->convert); + status = spa_node_process_fast(this->target); /* schedule the follower when the converter needed * a recycled buffer */ if (status == -EPIPE || status == 0) @@ -1677,7 +1807,7 @@ static int impl_node_process(void *object) /* 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 = spa_node_process_fast(this->convert); + status = spa_node_process_fast(this->target); if (status == 0) status = SPA_STATUS_NEED_DATA; else if (status < 0) @@ -1733,6 +1863,70 @@ static const struct spa_node_methods impl_node = { .process = impl_node_process, }; +static int load_converter(struct impl *this, const struct spa_dict *info, + const struct spa_support *support, uint32_t n_support) +{ + const char* factory_name = NULL; + struct spa_handle *hnd_convert = NULL; + void *iface_conv = NULL; + bool unload_handle = false; + struct spa_dict_item *items; + struct spa_dict cinfo; + char direction[16]; + uint32_t i; + + items = alloca((info->n_items + 1) * sizeof(struct spa_dict_item)); + cinfo = SPA_DICT(items, 0); + for (i = 0; i < info->n_items; i++) + items[cinfo.n_items++] = info->items[i]; + + snprintf(direction, sizeof(direction), "%s", + SPA_DIRECTION_REVERSE(this->direction) == SPA_DIRECTION_INPUT ? + "input" : "output"); + items[cinfo.n_items++] = SPA_DICT_ITEM("convert.direction", direction); + + factory_name = spa_dict_lookup(&cinfo, "audio.adapt.converter"); + if (factory_name == NULL) + factory_name = SPA_NAME_AUDIO_CONVERT; + + if (spa_streq(factory_name, SPA_NAME_AUDIO_CONVERT)) { + size_t size = spa_handle_factory_get_size(&spa_audioconvert_factory, &cinfo); + + hnd_convert = calloc(1, size); + if (hnd_convert == NULL) + return -errno; + + spa_handle_factory_init(&spa_audioconvert_factory, + hnd_convert, &cinfo, support, n_support); + } else if (this->ploader) { + hnd_convert = spa_plugin_loader_load(this->ploader, factory_name, &cinfo); + if (!hnd_convert) + return -EINVAL; + unload_handle = true; + } else { + return -ENOTSUP; + } + + spa_handle_get_interface(hnd_convert, SPA_TYPE_INTERFACE_Node, &iface_conv); + if (iface_conv == NULL) { + if (unload_handle) + spa_plugin_loader_unload(this->ploader, hnd_convert); + else { + spa_handle_clear(hnd_convert); + free(hnd_convert); + } + return -EINVAL; + } + + this->hnd_convert = hnd_convert; + this->convert = iface_conv; + this->unload_handle = unload_handle; + this->convertname = strdup(factory_name); + + return 0; +} + + static int do_auto_port_config(struct impl *this, const char *str) { uint32_t state = 0, i; @@ -1741,22 +1935,21 @@ static int do_auto_port_config(struct impl *this, const char *str) #define POSITION_PRESERVE 0 #define POSITION_AUX 1 #define POSITION_UNKNOWN 2 - int res, position = POSITION_PRESERVE; + int l, res, position = POSITION_PRESERVE; struct spa_pod *param; - uint32_t media_type, media_subtype; 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]; + struct spa_json it[1]; char key[1024], val[256]; + const char *v; - spa_json_init(&it[0], str, strlen(str)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) + if (spa_json_begin_object(&it[0], str, strlen(str)) <= 0) return -EINVAL; - while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { - if (spa_json_get_string(&it[1], val, sizeof(val)) <= 0) - break; + while ((l = spa_json_object_next(&it[0], key, sizeof(key), &v)) > 0) { + if (spa_json_parse_stringn(v, l, val, sizeof(val)) <= 0) + continue; if (spa_streq(key, "mode")) { mode = spa_debug_type_find_type_short(spa_type_param_port_config_mode, val); @@ -1778,40 +1971,22 @@ static int do_auto_port_config(struct impl *this, const char *str) while (true) { struct spa_audio_info info = { 0, }; - struct spa_pod *position = NULL; - uint32_t n_position = 0; spa_pod_builder_init(&b, buffer, sizeof(buffer)); - if ((res = spa_node_port_enum_params_sync(this->follower, + if ((res = node_port_enum_params_sync(this, this->follower, this->direction, 0, SPA_PARAM_EnumFormat, &state, NULL, ¶m, &b)) != 1) break; - if ((res = spa_format_parse(param, &media_type, &media_subtype)) < 0) - continue; - - if (media_type != SPA_MEDIA_TYPE_audio || - media_subtype != SPA_MEDIA_SUBTYPE_raw) + if ((res = spa_format_audio_parse(param, &info)) < 0) continue; spa_pod_object_fixate((struct spa_pod_object*)param); - if (spa_pod_parse_object(param, - SPA_TYPE_OBJECT_Format, NULL, - SPA_FORMAT_AUDIO_format, SPA_POD_Id(&info.info.raw.format), - SPA_FORMAT_AUDIO_rate, SPA_POD_Int(&info.info.raw.rate), - SPA_FORMAT_AUDIO_channels, SPA_POD_Int(&info.info.raw.channels), - SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position)) < 0) - continue; - - if (position != NULL) - n_position = spa_pod_copy_array(position, SPA_TYPE_Id, - info.info.raw.position, SPA_AUDIO_MAX_CHANNELS); - if (n_position == 0 || n_position != info.info.raw.channels) - SPA_FLAG_SET(info.info.raw.flags, SPA_AUDIO_FLAG_UNPOSITIONED); - - if (format.info.raw.channels >= info.info.raw.channels) + if (info.media_subtype == SPA_MEDIA_SUBTYPE_raw && + format.media_subtype == SPA_MEDIA_SUBTYPE_raw && + format.info.raw.channels >= info.info.raw.channels) continue; format = info; @@ -1820,16 +1995,18 @@ static int do_auto_port_config(struct impl *this, const char *str) if (!have_format) return -ENOENT; - if (position == POSITION_AUX) { - for (i = 0; i < format.info.raw.channels; i++) - format.info.raw.position[i] = SPA_AUDIO_CHANNEL_START_Aux + i; - } else if (position == POSITION_UNKNOWN) { - for (i = 0; i < format.info.raw.channels; i++) - format.info.raw.position[i] = SPA_AUDIO_CHANNEL_UNKNOWN; + if (format.media_subtype == SPA_MEDIA_SUBTYPE_raw) { + if (position == POSITION_AUX) { + for (i = 0; i < format.info.raw.channels; i++) + format.info.raw.position[i] = SPA_AUDIO_CHANNEL_START_Aux + i; + } else if (position == POSITION_UNKNOWN) { + for (i = 0; i < format.info.raw.channels; i++) + format.info.raw.position[i] = SPA_AUDIO_CHANNEL_UNKNOWN; + } } spa_pod_builder_init(&b, buffer, sizeof(buffer)); - param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &format.info.raw); + param = spa_format_audio_build(&b, SPA_PARAM_Format, &format); param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction), @@ -1870,7 +2047,15 @@ static int impl_clear(struct spa_handle *handle) spa_hook_remove(&this->follower_listener); spa_node_set_callbacks(this->follower, NULL, NULL); - spa_handle_clear(this->hnd_convert); + if (this->hnd_convert) { + if (this->unload_handle) + spa_plugin_loader_unload(this->ploader, this->hnd_convert); + else { + spa_handle_clear(this->hnd_convert); + free(this->hnd_convert); + } + free(this->convertname); + } clear_buffers(this); return 0; @@ -1881,10 +2066,7 @@ static size_t impl_get_size(const struct spa_handle_factory *factory, const struct spa_dict *params) { - size_t size; - - size = spa_handle_factory_get_size(&spa_audioconvert_factory, params); - size += sizeof(struct impl); + size_t size = sizeof(struct impl); return size; } @@ -1897,8 +2079,9 @@ impl_init(const struct spa_handle_factory *factory, uint32_t n_support) { struct impl *this; - void *iface; const char *str; + int ret; + struct spa_hook probe_listener; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); @@ -1913,6 +2096,8 @@ impl_init(const struct spa_handle_factory *factory, this->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); + this->ploader = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_PluginLoader); + if (info == NULL || (str = spa_dict_lookup(info, "audio.adapt.follower")) == NULL) return -EINVAL; @@ -1931,17 +2116,25 @@ impl_init(const struct spa_handle_factory *factory, SPA_VERSION_NODE, &impl_node, this); - this->hnd_convert = SPA_PTROFF(this, sizeof(struct impl), struct spa_handle); - spa_handle_factory_init(&spa_audioconvert_factory, - this->hnd_convert, - info, support, n_support); + /* just probe the ports to get the direction */ + spa_zero(probe_listener); + spa_node_add_listener(this->follower, &probe_listener, &follower_probe_events, this); + spa_hook_remove(&probe_listener); - spa_handle_get_interface(this->hnd_convert, SPA_TYPE_INTERFACE_Node, &iface); - if (iface == NULL) - return -EINVAL; + ret = load_converter(this, info, support, n_support); + spa_log_info(this->log, "%p: loaded converter %s, hnd %p, convert %p", this, + this->convertname, this->hnd_convert, this->convert); + if (ret < 0) + return ret; - this->convert = iface; - this->target = this->convert; + if (this->convert == NULL) { + this->target = this->follower; + this->mode = SPA_PARAM_PORT_CONFIG_MODE_passthrough; + } else { + this->target = this->convert; + /* the actual mode is selected below */ + this->mode = SPA_PARAM_PORT_CONFIG_MODE_none; + } this->info_all = SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PROPS | @@ -1965,14 +2158,16 @@ impl_init(const struct spa_handle_factory *factory, &this->follower_listener, &follower_node_events, this); spa_node_set_callbacks(this->follower, &follower_node_callbacks, this); - spa_node_add_listener(this->convert, - &this->convert_listener, &convert_node_events, this); - - if (info && (str = spa_dict_lookup(info, "adapter.auto-port-config")) != NULL) - do_auto_port_config(this, str); - else - configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_dsp); - + if (this->convert) { + spa_node_add_listener(this->convert, + &this->convert_listener, &convert_node_events, this); + if (info && (str = spa_dict_lookup(info, "adapter.auto-port-config")) != NULL) + do_auto_port_config(this, str); + else + configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_dsp); + } else { + reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_passthrough, this->direction, NULL); + } link_io(this); return 0; diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 897cdf8f..47859e65 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -11,6 +11,7 @@ #include <spa/support/cpu.h> #include <spa/support/loop.h> #include <spa/support/log.h> +#include <spa/support/plugin-loader.h> #include <spa/utils/result.h> #include <spa/utils/list.h> #include <spa/utils/json.h> @@ -22,12 +23,15 @@ #include <spa/node/utils.h> #include <spa/node/keys.h> #include <spa/param/audio/format-utils.h> +#include <spa/param/audio/raw-json.h> #include <spa/param/param.h> #include <spa/param/latency-utils.h> #include <spa/param/tag-utils.h> #include <spa/pod/filter.h> #include <spa/pod/dynamic.h> #include <spa/debug/types.h> +#include <spa/control/ump-utils.h> +#include <spa/filter-graph/filter-graph.h> #include "volume-ops.h" #include "fmt-ops.h" @@ -46,6 +50,8 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.audioconvert"); #define MAX_BUFFERS 32 #define MAX_DATAS SPA_AUDIO_MAX_CHANNELS #define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1) +#define MAX_STAGES 64 +#define MAX_GRAPH 9 /* 8 active + 1 replacement slot */ #define DEFAULT_MUTE false #define DEFAULT_VOLUME VOLUME_NORM @@ -93,6 +99,7 @@ struct props { double rate; char wav_path[512]; unsigned int lock_volumes:1; + unsigned int filter_graph_disabled:1; }; static void props_reset(struct props *props) @@ -114,6 +121,7 @@ static void props_reset(struct props *props) props->rate = 1.0; spa_zero(props->wav_path); props->lock_volumes = false; + props->filter_graph_disabled = false; } struct buffer { @@ -187,6 +195,47 @@ struct dir { unsigned int control:1; }; +struct stage_context { +#define CTX_DATA_SRC 0 +#define CTX_DATA_DST 1 +#define CTX_DATA_REMAP_DST 2 +#define CTX_DATA_REMAP_SRC 3 +#define CTX_DATA_TMP_0 4 +#define CTX_DATA_TMP_1 5 +#define CTX_DATA_MAX 6 + void **datas[CTX_DATA_MAX]; + uint32_t in_samples; + uint32_t n_samples; + uint32_t n_out; + uint32_t src_idx; + uint32_t dst_idx; + uint32_t final_idx; + uint32_t n_datas; + struct port *ctrlport; +}; + +struct stage { + struct impl *impl; + bool passthrough; + uint32_t in_idx; + uint32_t out_idx; + uint32_t n_in; + uint32_t n_out; + void *data; + void (*run) (struct stage *stage, struct stage_context *c); +}; + +struct filter_graph { + struct impl *impl; + int order; + struct spa_handle *handle; + struct spa_filter_graph *graph; + struct spa_hook listener; + uint32_t n_inputs; + uint32_t n_outputs; + bool active; +}; + struct impl { struct spa_handle handle; struct spa_node node; @@ -194,6 +243,17 @@ struct impl { struct spa_log *log; struct spa_cpu *cpu; struct spa_loop *data_loop; + struct spa_plugin_loader *loader; + + uint32_t n_graph; + uint32_t graph_index[MAX_GRAPH]; + + struct filter_graph filter_graph[MAX_GRAPH]; + int in_filter_props; + int filter_props_count; + + struct stage stages[MAX_STAGES]; + uint32_t n_stages; uint32_t cpu_flags; uint32_t max_align; @@ -240,6 +300,9 @@ struct impl { unsigned int rate_adjust:1; unsigned int port_ignore_latency:1; unsigned int monitor_passthrough:1; + unsigned int resample_passthrough:1; + + bool recalc; char group_name[128]; @@ -302,7 +365,7 @@ static void emit_port_info(struct impl *this, struct port *port, bool full) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_IGNORE_LATENCY, "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"); + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"); } if (this->group_name[0] != '\0') items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, this->group_name); @@ -408,6 +471,7 @@ 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); @@ -738,8 +802,30 @@ static int impl_node_enum_params(void *object, int seq, SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->lock_volumes), SPA_PROP_INFO_params, SPA_POD_Bool(true)); break; + case 28: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph.disable"), + SPA_PROP_INFO_description, SPA_POD_String("Disable Filter graph updates"), + SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->filter_graph_disabled), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + case 29: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph"), + SPA_PROP_INFO_description, SPA_POD_String("A filter graph to load"), + SPA_PROP_INFO_type, SPA_POD_String(""), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; default: - return 0; + if (this->filter_graph[0].graph) { + res = spa_filter_graph_enum_prop_info(this->filter_graph[0].graph, + result.index - 30, &b, ¶m); + if (res <= 0) + return res; + } else + return 0; } break; } @@ -818,11 +904,27 @@ static int impl_node_enum_params(void *object, int seq, spa_pod_builder_string(&b, p->wav_path); spa_pod_builder_string(&b, "channelmix.lock-volumes"); spa_pod_builder_bool(&b, p->lock_volumes); + spa_pod_builder_string(&b, "audioconvert.filter-graph.disable"); + spa_pod_builder_bool(&b, p->filter_graph_disabled); + spa_pod_builder_string(&b, "audioconvert.filter-graph"); + spa_pod_builder_string(&b, ""); spa_pod_builder_pop(&b, &f[1]); param = spa_pod_builder_pop(&b, &f[0]); break; default: - return 0; + if (result.index > MAX_GRAPH) + return 0; + + if (this->filter_graph[result.index-1].graph == NULL) + goto next; + + res = spa_filter_graph_get_props(this->filter_graph[result.index-1].graph, + &b, ¶m); + if (res < 0) + return res; + if (res == 0) + goto next; + break; } break; } @@ -859,8 +961,197 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) return 0; } +static void graph_info(void *object, const struct spa_filter_graph_info *info) +{ + struct filter_graph *g = object; + if (!g->active) + return; + g->n_inputs = info->n_inputs; + g->n_outputs = info->n_outputs; +} + +static int apply_props(struct impl *impl, const struct spa_pod *props); + +static void graph_apply_props(void *object, enum spa_direction direction, const struct spa_pod *props) +{ + struct filter_graph *g = object; + struct impl *impl = g->impl; + if (!g->active) + return; + if (apply_props(impl, props) > 0) + emit_node_info(impl, false); +} + +static void graph_props_changed(void *object, enum spa_direction direction) +{ + struct filter_graph *g = object; + struct impl *impl = g->impl; + if (!g->active) + return; + impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + impl->params[IDX_Props].user++; +} + +struct spa_filter_graph_events graph_events = { + SPA_VERSION_FILTER_GRAPH_EVENTS, + .info = graph_info, + .apply_props = graph_apply_props, + .props_changed = graph_props_changed, +}; + +static int setup_filter_graph(struct impl *this, struct spa_filter_graph *graph) +{ + int res; + char rate_str[64]; + struct dir *in; + + if (graph == NULL) + return 0; + + in = &this->dir[SPA_DIRECTION_INPUT]; + snprintf(rate_str, sizeof(rate_str), "%d", in->format.info.raw.rate); + + spa_filter_graph_deactivate(graph); + res = spa_filter_graph_activate(graph, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate_str))); + return res; +} + +static int do_sync_filter_graph(struct spa_loop *loop, bool async, uint32_t seq, + const void *data, size_t size, void *user_data) +{ + struct impl *impl = user_data; + uint32_t i, j; + impl->n_graph = 0; + for (i = 0; i < MAX_GRAPH; i++) { + struct filter_graph *g = &impl->filter_graph[i]; + if (g->graph == NULL || !g->active) + continue; + impl->graph_index[impl->n_graph++] = i; + + for (j = impl->n_graph-1; j > 0; j--) { + if (impl->filter_graph[impl->graph_index[j]].order >= + impl->filter_graph[impl->graph_index[j-1]].order) + break; + SPA_SWAP(impl->graph_index[j], impl->graph_index[j-1]); + } + } + impl->recalc = true; + return 0; +} + +static void clean_filter_handles(struct impl *impl, bool force) +{ + uint32_t i; + for (i = 0; i < MAX_GRAPH; i++) { + struct filter_graph *g = &impl->filter_graph[i]; + if (!g->active || force) { + if (g->graph) + spa_hook_remove(&g->listener); + if (g->handle) + spa_plugin_loader_unload(impl->loader, g->handle); + spa_zero(*g); + } + } +} + +static int load_filter_graph(struct impl *impl, const char *graph, int order) +{ + char qlimit[64]; + int res; + void *iface; + struct spa_handle *new_handle = NULL; + uint32_t i, idx, n_graph; + struct filter_graph *pending, *old_active = NULL; + + if (impl->props.filter_graph_disabled) + return -EPERM; + + /* find graph spot */ + idx = SPA_ID_INVALID; + n_graph = 0; + for (i = 0; i < MAX_GRAPH; i++) { + pending = &impl->filter_graph[i]; + /* find the first free spot for our new filter */ + if (!pending->active && idx == SPA_ID_INVALID) + idx = i; + /* deactivate an existing filter of the same order */ + if (pending->active) { + if (pending->order == order) + old_active = pending; + else + n_graph++; + } + } + /* we can at most have MAX_GRAPH-1 active filters */ + if (n_graph >= MAX_GRAPH-1) + return -ENOSPC; + + pending = &impl->filter_graph[idx]; + pending->impl = impl; + pending->order = order; + + if (graph != NULL && graph[0] != '\0') { + snprintf(qlimit, sizeof(qlimit), "%u", impl->quantum_limit); + + new_handle = spa_plugin_loader_load(impl->loader, "filter.graph", + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_LIBRARY_NAME, "filter-graph/libspa-filter-graph"), + SPA_DICT_ITEM("clock.quantum-limit", qlimit), + SPA_DICT_ITEM("filter.graph", graph))); + if (new_handle == NULL) + goto error; + + res = spa_handle_get_interface(new_handle, SPA_TYPE_INTERFACE_FilterGraph, &iface); + if (res < 0 || iface == NULL) + goto error; + + /* prepare new filter and swap it */ + res = setup_filter_graph(impl, iface); + if (res < 0) + goto error; + pending->graph = iface; + pending->active = true; + spa_log_info(impl->log, "loading filter-graph order:%d in %d active:%d", + order, idx, n_graph + 1); + } else { + pending->active = false; + spa_log_info(impl->log, "removing filter-graph order:%d active:%d", + order, n_graph); + } + if (old_active) + old_active->active = false; + + /* we call this here on the pending_graph so that the n_input/n_output is updated + * before we switch */ + if (pending->active) + spa_filter_graph_add_listener(pending->graph, + &pending->listener, &graph_events, pending); + + spa_loop_invoke(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, true, impl); + + if (pending->active) + pending->handle = new_handle; + + if (impl->in_filter_props == 0) + clean_filter_handles(impl, false); + + impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS; + impl->params[IDX_PropInfo].user++; + impl->params[IDX_Props].user++; + + return 0; +error: + if (new_handle != NULL) + spa_plugin_loader_unload(impl->loader, new_handle); + return -ENOTSUP; +} + static int audioconvert_set_param(struct impl *this, const char *k, const char *s) { + int res; + if (spa_streq(k, "monitor.channel-volumes")) this->monitor_channel_volumes = spa_atob(s); else if (spa_streq(k, "channelmix.disable")) @@ -901,6 +1192,13 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char * } else if (spa_streq(k, "channelmix.lock-volumes")) this->props.lock_volumes = spa_atob(s); + else if (spa_strstartswith(k, "audioconvert.filter-graph")) { + int order = atoi(k+ strlen("audioconvert.filter-graph.")); + if ((res = load_filter_graph(this, s, order)) < 0) { + spa_log_warn(this->log, "Can't load filter-graph %d: %s", + order, spa_strerror(res)); + } + } else return 0; return 1; @@ -1224,7 +1522,8 @@ static int apply_props(struct impl *this, const struct spa_pod *param) } break; case SPA_PROP_params: - changed += parse_prop_params(this, &prop->value); + if (this->filter_props_count == 0) + changed += parse_prop_params(this, &prop->value); break; default: break; @@ -1237,6 +1536,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param) p->have_soft_volume = false; set_volume(this); + this->recalc = true; } if (!p->lock_volumes && vol_ramp_params_changed) { @@ -1253,23 +1553,26 @@ static int apply_props(struct impl *this, const struct spa_pod *param) this->vol_ramp_sequence = (struct spa_pod_sequence *) sequence; this->vol_ramp_offset = 0; + this->recalc = true; } 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; + uint8_t data[8]; + int size; + size = spa_ump_to_midi(SPA_POD_BODY(value), SPA_POD_BODY_SIZE(value), + data, sizeof(data)); if (size < 3) return -EINVAL; - if ((val[0] & 0xf0) != 0xb0 || val[1] != 7) + if ((data[0] & 0xf0) != 0xb0 || data[1] != 7) return 0; - p->volume = val[2] / 127.0f; + p->volume = data[2] / 127.0f; set_volume(this); return 1; } @@ -1343,11 +1646,6 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m i = dir->n_ports++; init_port(this, direction, i, 0, false, false, true); } - /* when output is convert mode, we are in OUTPUT (merge) mode, we always output all - * the incoming data to output. When output is DSP, we need to output quantum size - * chunks. */ - this->direction = this->dir[SPA_DIRECTION_OUTPUT].mode == SPA_PARAM_PORT_CONFIG_MODE_convert ? - SPA_DIRECTION_OUTPUT : SPA_DIRECTION_INPUT; this->info.change_mask |= SPA_NODE_CHANGE_MASK_FLAGS | SPA_NODE_CHANGE_MASK_PARAMS; this->info.flags &= ~SPA_NODE_FLAG_NEED_CONFIGURE; @@ -1415,9 +1713,29 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, break; } case SPA_PARAM_Props: - if (apply_props(this, param) > 0) + { + uint32_t i; + bool have_graph = false; + this->filter_props_count = 0; + for (i = 0; i < MAX_GRAPH; i++) { + struct filter_graph *g = &this->filter_graph[i]; + if (!g->active) + continue; + + have_graph = true; + + this->in_filter_props++; + spa_filter_graph_set_props(g->graph, + SPA_DIRECTION_INPUT, param); + this->filter_props_count++; + this->in_filter_props--; + } + if (!have_graph && apply_props(this, param) > 0) emit_node_info(this, false); + + clean_filter_handles(this, false); break; + } default: return -ENOENT; } @@ -1839,21 +2157,33 @@ static int ensure_tmp(struct impl *this, uint32_t maxsize, uint32_t maxports) static uint32_t resample_update_rate_match(struct impl *this, bool passthrough, uint32_t size, uint32_t queued) { uint32_t delay, match_size; + int32_t delay_frac; if (passthrough) { delay = 0; + delay_frac = 0; match_size = size; } else { - double rate = this->rate_scale / this->props.rate; + /* Only apply rate_scale if we're working in DSP mode (i.e. in driver rate) */ + double scale = this->dir[SPA_DIRECTION_REVERSE(this->direction)].mode == SPA_PARAM_PORT_CONFIG_MODE_dsp ? + this->rate_scale : 1.0; + double rate = scale / this->props.rate; + double fdelay; + 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); - if (this->direction == SPA_DIRECTION_INPUT) + fdelay = resample_delay(&this->resample) + resample_phase(&this->resample); + if (this->direction == SPA_DIRECTION_INPUT) { match_size = resample_in_len(&this->resample, size); - else + } else { + fdelay *= rate * this->resample.o_rate / this->resample.i_rate; match_size = resample_out_len(&this->resample, size); + } + + delay = (uint32_t)round(fdelay); + delay_frac = (int32_t)((fdelay - delay) * 1e9); } match_size -= SPA_MIN(match_size, queued); @@ -1861,6 +2191,7 @@ static uint32_t resample_update_rate_match(struct impl *this, bool passthrough, if (this->io_rate_match) { this->io_rate_match->delay = delay + queued; + this->io_rate_match->delay_frac = delay_frac; this->io_rate_match->size = match_size; } return match_size; @@ -1934,6 +2265,13 @@ static int setup_convert(struct impl *this) if ((res = setup_in_convert(this)) < 0) return res; + for (i = 0; i < MAX_GRAPH; i++) { + struct filter_graph *g = &this->filter_graph[i]; + if (!g->active) + continue; + if ((res = setup_filter_graph(this, g->graph)) < 0) + return res; + } if ((res = setup_channelmix(this)) < 0) return res; if ((res = setup_resample(this)) < 0) @@ -1957,6 +2295,7 @@ static int setup_convert(struct impl *this) resample_update_rate_match(this, resample_is_passthrough(this), duration, 0); this->setup = true; + this->recalc = true; emit_node_info(this, false); @@ -1965,6 +2304,12 @@ static int setup_convert(struct impl *this) static void reset_node(struct impl *this) { + uint32_t i; + for (i = 0; i < MAX_GRAPH; i++) { + struct filter_graph *g = &this->filter_graph[i]; + if (g->graph) + spa_filter_graph_deactivate(g->graph); + } if (this->resample.reset) resample_reset(&this->resample); this->in_offset = 0; @@ -2071,7 +2416,9 @@ static int port_enum_formats(void *object, *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)); + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), + SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int( + (1u<<SPA_CONTROL_UMP) | (1u<<SPA_CONTROL_Properties))); } else { struct spa_pod_frame f[1]; uint32_t rate = this->io_position ? @@ -2177,7 +2524,9 @@ impl_node_port_enum_params(void *object, int seq, 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)); + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), + SPA_FORMAT_CONTROL_types, SPA_POD_Int( + (1u<<SPA_CONTROL_UMP) | (1u<<SPA_CONTROL_Properties))); else param = spa_format_audio_raw_build(&b, id, &port->format.info.raw); break; @@ -2190,29 +2539,30 @@ impl_node_port_enum_params(void *object, int seq, 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 { + size = this->quantum_limit; + + if (!PORT_IS_DSP(this, direction, port_id)) { uint32_t irate, orate; struct dir *dir = &this->dir[direction]; /* Convert ports are scaled so that they can always - * provide one quantum of data */ + * provide one quantum of data. irate is the rate of the + * data before it goes into the resampler. */ irate = dir->format.info.raw.rate; + /* scale the size for adaptive resampling */ + size += size/2; - /* collect the other port rate */ + /* collect the other port rate. This is the output of the resampler + * and is usually one quantum. */ dir = &this->dir[SPA_DIRECTION_REVERSE(direction)]; if (dir->mode == SPA_PARAM_PORT_CONFIG_MODE_dsp) - orate = this->io_position ? this->io_position->clock.target_rate.denom : DEFAULT_RATE; + orate = this->io_position ? this->io_position->clock.target_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) + /* scale the buffer size when we can. Only do this when we downsample because + * then we need to ask more input data for one quantum. */ + if (irate != 0 && orate != 0 && irate > orate) size = SPA_SCALE32_UP(size, irate, orate); } @@ -2741,30 +3091,6 @@ static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t return 0; } -static void handle_wav(struct impl *this, const void **src, uint32_t n_samples) -{ - if (SPA_UNLIKELY(this->props.wav_path[0])) { - if (this->wav_file == NULL) { - struct wav_file_info info; - - info.info = this->dir[this->direction].format; - - this->wav_file = wav_file_open(this->props.wav_path, - "w", &info); - if (this->wav_file == NULL) - spa_log_warn(this->log, "can't open wav path: %m"); - } - if (this->wav_file) { - wav_file_write(this->wav_file, src, n_samples); - } else { - spa_zero(this->props.wav_path); - } - } else if (this->wav_file != NULL) { - wav_file_close(this->wav_file); - this->wav_file = NULL; - } -} - static int channelmix_process_apply_sequence(struct impl *this, const struct spa_pod_sequence *sequence, uint32_t *processed_offset, void *SPA_RESTRICT dst[], const void *SPA_RESTRICT src[], @@ -2808,7 +3134,7 @@ static int channelmix_process_apply_sequence(struct impl *this, if (prev) { switch (prev->type) { - case SPA_CONTROL_Midi: + case SPA_CONTROL_UMP: apply_midi(this, &prev->value); break; case SPA_CONTROL_Properties: @@ -2856,25 +3182,385 @@ static uint64_t get_time_ns(struct impl *impl) return SPA_TIMESPEC_TO_NSEC(&now); } +static void run_wav_stage(struct stage *stage, struct stage_context *c) +{ + struct impl *impl = stage->impl; + const void **src = (const void **)c->datas[stage->in_idx]; + + if (SPA_UNLIKELY(impl->props.wav_path[0])) { + if (impl->wav_file == NULL) { + struct wav_file_info info; + + info.info = impl->dir[impl->direction].format; + + impl->wav_file = wav_file_open(impl->props.wav_path, + "w", &info); + if (impl->wav_file == NULL) + spa_log_warn(impl->log, "can't open wav path: %m"); + } + if (impl->wav_file) { + wav_file_write(impl->wav_file, src, c->n_samples); + } else { + spa_zero(impl->props.wav_path); + } + } else if (impl->wav_file != NULL) { + wav_file_close(impl->wav_file); + impl->wav_file = NULL; + impl->recalc = true; + } +} + +static void add_wav_stage(struct impl *impl, struct stage_context *ctx) +{ + struct stage *s = &impl->stages[impl->n_stages]; + s->impl = impl; + s->passthrough = false; + s->in_idx = ctx->src_idx; + s->out_idx = ctx->src_idx; + s->n_in = ctx->n_datas; + s->n_out = ctx->n_datas; + s->data = NULL; + s->run = run_wav_stage; + spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); + impl->n_stages++; +} + +static void run_dst_remap_stage(struct stage *s, struct stage_context *c) +{ + struct impl *impl = s->impl; + struct dir *dir = &impl->dir[SPA_DIRECTION_OUTPUT]; + uint32_t i; + for (i = 0; i < s->n_in; i++) { + c->datas[s->out_idx][i] = c->datas[s->in_idx][dir->remap[i]]; + spa_log_trace_fp(impl->log, "%p: output remap %d -> %d", impl, i, dir->remap[i]); + } +} +static void add_dst_remap_stage(struct impl *impl, struct stage_context *ctx) +{ + struct stage *s = &impl->stages[impl->n_stages]; + s->impl = impl; + s->passthrough = false; + s->in_idx = ctx->dst_idx; + s->out_idx = CTX_DATA_REMAP_DST; + s->n_in = ctx->n_datas; + s->n_out = ctx->n_datas; + s->data = NULL; + s->run = run_dst_remap_stage; + spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); + impl->n_stages++; + ctx->dst_idx = CTX_DATA_REMAP_DST; + ctx->final_idx = CTX_DATA_REMAP_DST; +} + +static void run_src_remap_stage(struct stage *s, struct stage_context *c) +{ + struct impl *impl = s->impl; + struct dir *dir = &impl->dir[SPA_DIRECTION_INPUT]; + uint32_t i; + for (i = 0; i < dir->conv.n_channels; i++) { + c->datas[s->out_idx][dir->remap[i]] = c->datas[s->in_idx][i]; + spa_log_trace_fp(impl->log, "%p: input remap %d -> %d", impl, dir->remap[i], i); + } +} +static void add_src_remap_stage(struct impl *impl, struct stage_context *ctx) +{ + struct stage *s = &impl->stages[impl->n_stages]; + s->impl = impl; + s->passthrough = false; + s->in_idx = ctx->src_idx; + s->out_idx = CTX_DATA_REMAP_SRC; + s->n_in = ctx->n_datas; + s->n_out = ctx->n_datas; + s->data = NULL; + s->run = run_src_remap_stage; + spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); + impl->n_stages++; + ctx->src_idx = CTX_DATA_REMAP_SRC; +} + +static void run_src_convert_stage(struct stage *s, struct stage_context *c) +{ + struct impl *impl = s->impl; + struct dir *dir = &impl->dir[SPA_DIRECTION_INPUT]; + void *remap_src_datas[MAX_PORTS], **dst; + + spa_log_trace_fp(impl->log, "%p: input convert %d", impl, c->n_samples); + if (dir->need_remap) { + uint32_t i; + for (i = 0; i < dir->conv.n_channels; i++) { + remap_src_datas[i] = c->datas[s->out_idx][dir->remap[i]]; + spa_log_trace_fp(impl->log, "%p: input remap %d -> %d", impl, dir->remap[i], i); + } + dst = remap_src_datas; + } else { + dst = c->datas[s->out_idx]; + } + convert_process(&dir->conv, dst, (const void**)c->datas[s->in_idx], c->n_samples); +} +static void add_src_convert_stage(struct impl *impl, struct stage_context *ctx) +{ + struct stage *s = &impl->stages[impl->n_stages]; + s->impl = impl; + s->passthrough = false; + s->in_idx = ctx->src_idx; + s->out_idx = ctx->dst_idx; + s->n_in = ctx->n_datas; + s->n_out = ctx->n_datas; + s->data = NULL; + s->run = run_src_convert_stage; + spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); + impl->n_stages++; + ctx->src_idx = ctx->dst_idx; +} + +static void run_resample_stage(struct stage *s, struct stage_context *c) +{ + struct impl *impl = s->impl; + uint32_t in_len = c->n_samples; + uint32_t out_len = c->n_out; + + resample_process(&impl->resample, (const void**)c->datas[s->in_idx], &in_len, + c->datas[s->out_idx], &out_len); + + spa_log_trace_fp(impl->log, "%p: resample %d/%d -> %d/%d", impl, + c->n_samples, in_len, c->n_out, out_len); + c->in_samples = in_len; + c->n_samples = out_len; +} +static void add_resample_stage(struct impl *impl, struct stage_context *ctx) +{ + struct stage *s = &impl->stages[impl->n_stages]; + s->impl = impl; + s->passthrough = false; + s->in_idx = ctx->src_idx; + s->out_idx = ctx->dst_idx; + s->n_in = ctx->n_datas; + s->n_out = ctx->n_datas; + s->data = NULL; + s->run = run_resample_stage; + spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); + impl->n_stages++; + ctx->src_idx = ctx->dst_idx; +} + +static void run_channelmix_stage(struct stage *s, struct stage_context *c) +{ + struct impl *impl = s->impl; + void **out_datas = c->datas[s->out_idx]; + const void **in_datas = (const void**)c->datas[s->in_idx]; + struct port *ctrlport = c->ctrlport; + + spa_log_trace_fp(impl->log, "%p: channelmix %d", impl, c->n_samples); + if (ctrlport != NULL && ctrlport->ctrl != NULL) { + if (channelmix_process_apply_sequence(impl, ctrlport->ctrl, + &ctrlport->ctrl_offset, out_datas, in_datas, c->n_samples) == 1) { + ctrlport->io->status = SPA_STATUS_OK; + ctrlport->ctrl = NULL; + } + } else if (impl->vol_ramp_sequence) { + if (channelmix_process_apply_sequence(impl, impl->vol_ramp_sequence, + &impl->vol_ramp_offset, out_datas, in_datas, c->n_samples) == 1) { + free(impl->vol_ramp_sequence); + impl->vol_ramp_sequence = NULL; + } + } else { + channelmix_process(&impl->mix, out_datas, in_datas, c->n_samples); + } +} + +static void run_filter_stage(struct stage *s, struct stage_context *c) +{ + struct filter_graph *fg = s->data; + + spa_log_trace_fp(s->impl->log, "%p: filter-graph %d", s->impl, c->n_samples); + spa_filter_graph_process(fg->graph, (const void **)c->datas[s->in_idx], + c->datas[s->out_idx], c->n_samples); +} +static void add_filter_stage(struct impl *impl, uint32_t i, struct filter_graph *fg, struct stage_context *ctx) +{ + struct stage *s = &impl->stages[impl->n_stages]; + s->impl = impl; + s->passthrough = false; + s->in_idx = ctx->src_idx; + s->out_idx = ctx->dst_idx; + s->n_in = ctx->n_datas; + s->n_out = ctx->n_datas; + s->data = fg; + s->run = run_filter_stage; + spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); + impl->n_stages++; + ctx->src_idx = ctx->dst_idx; +} + +static void add_channelmix_stage(struct impl *impl, struct stage_context *ctx) +{ + struct stage *s = &impl->stages[impl->n_stages]; + s->impl = impl; + s->passthrough = false; + s->in_idx = ctx->src_idx; + s->out_idx = ctx->dst_idx; + s->n_in = ctx->n_datas; + s->n_out = ctx->n_datas; + s->data = NULL; + s->run = run_channelmix_stage; + spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); + impl->n_stages++; + ctx->src_idx = ctx->dst_idx; +} + +static void run_dst_convert_stage(struct stage *s, struct stage_context *c) +{ + struct impl *impl = s->impl; + struct dir *dir = &impl->dir[SPA_DIRECTION_OUTPUT]; + void *remap_datas[MAX_PORTS], **src; + + spa_log_trace_fp(impl->log, "%p: output convert %d", impl, c->n_samples); + if (dir->need_remap) { + uint32_t i; + for (i = 0; i < dir->conv.n_channels; i++) { + remap_datas[dir->remap[i]] = c->datas[s->in_idx][i]; + spa_log_trace_fp(impl->log, "%p: output remap %d -> %d", impl, i, dir->remap[i]); + } + src = remap_datas; + } else { + src = c->datas[s->in_idx]; + } + convert_process(&dir->conv, c->datas[s->out_idx], (const void **)src, c->n_samples); +} +static void add_dst_convert_stage(struct impl *impl, struct stage_context *ctx) +{ + struct stage *s = &impl->stages[impl->n_stages]; + s->impl = impl; + s->passthrough = false; + s->in_idx = ctx->src_idx; + s->out_idx = ctx->final_idx; + s->n_in = ctx->n_datas; + s->n_out = ctx->n_datas; + s->data = NULL; + s->run = run_dst_convert_stage; + spa_log_trace(impl->log, "%p: stage %d", impl, impl->n_stages); + impl->n_stages++; + ctx->src_idx = s->out_idx; +} + +static void recalc_stages(struct impl *this, struct stage_context *ctx) +{ + struct dir *dir; + bool filter_passthrough, in_passthrough, mix_passthrough, resample_passthrough, out_passthrough; + int tmp = 0; + struct port *ctrlport = ctx->ctrlport; + bool in_need_remap, out_need_remap; + uint32_t i; + + this->recalc = false; + this->n_stages = 0; + + dir = &this->dir[SPA_DIRECTION_INPUT]; + in_passthrough = dir->conv.is_passthrough; + in_need_remap = dir->need_remap; + + dir = &this->dir[SPA_DIRECTION_OUTPUT]; + out_passthrough = dir->conv.is_passthrough; + out_need_remap = dir->need_remap; + + resample_passthrough = resample_is_passthrough(this); + filter_passthrough = this->n_graph == 0; + this->resample_passthrough = resample_passthrough; + mix_passthrough = SPA_FLAG_IS_SET(this->mix.flags, CHANNELMIX_FLAG_IDENTITY) && + (ctrlport == NULL || ctrlport->ctrl == NULL) && (this->vol_ramp_sequence == NULL); + + if (in_passthrough && filter_passthrough && mix_passthrough && resample_passthrough) + out_passthrough = false; + + if (out_passthrough && out_need_remap) + add_dst_remap_stage(this, ctx); + + if (this->direction == SPA_DIRECTION_INPUT && + (this->props.wav_path[0] || this->wav_file != NULL)) + add_wav_stage(this, ctx); + + if (!in_passthrough) { + if (filter_passthrough && mix_passthrough && resample_passthrough && out_passthrough) + ctx->dst_idx = ctx->final_idx; + else + ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1); + + add_src_convert_stage(this, ctx); + } else { + if (in_need_remap) + add_src_remap_stage(this, ctx); + } + + if (this->direction == SPA_DIRECTION_INPUT) { + if (!resample_passthrough) { + if (filter_passthrough && mix_passthrough && out_passthrough) + ctx->dst_idx = ctx->final_idx; + else + ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1); + + add_resample_stage(this, ctx); + resample_passthrough = true; + } + } + if (!filter_passthrough) { + for (i = 0; i < this->n_graph; i++) { + struct filter_graph *fg = &this->filter_graph[this->graph_index[i]]; + + if (mix_passthrough && resample_passthrough && out_passthrough && + i + 1 == this->n_graph) + ctx->dst_idx = ctx->final_idx; + else + ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1); + + add_filter_stage(this, i, fg, ctx); + } + } + if (!mix_passthrough) { + if (resample_passthrough && out_passthrough) + ctx->dst_idx = ctx->final_idx; + else + ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1); + + add_channelmix_stage(this, ctx); + } + if (this->direction == SPA_DIRECTION_OUTPUT) { + if (!resample_passthrough) { + if (out_passthrough) + ctx->dst_idx = ctx->final_idx; + else + ctx->dst_idx = CTX_DATA_TMP_0 + ((tmp++) & 1); + + add_resample_stage(this, ctx); + } + } + if (!out_passthrough) { + add_dst_convert_stage(this, ctx); + } + if (this->direction == SPA_DIRECTION_OUTPUT && + (this->props.wav_path[0] || this->wav_file != NULL)) + add_wav_stage(this, ctx); + + spa_log_trace(this->log, "got %u processing stages", this->n_stages); +} + static int impl_node_process(void *object) { struct impl *this = object; - const void *src_datas[MAX_PORTS], **in_datas; + const void *src_datas[MAX_PORTS]; 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, suppressed; - bool in_passthrough, mix_passthrough, resample_passthrough, out_passthrough; + int res = 0, suppressed; bool in_avail = false, flush_in = false, flush_out = false; bool draining = false, in_empty = this->out_offset == 0; - struct spa_io_buffers *io, *ctrlio = NULL; + struct spa_io_buffers *io; const struct spa_pod_sequence *ctrl = NULL; uint64_t current_time; + struct stage_context ctx; /* 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 @@ -2910,7 +3596,6 @@ static int impl_node_process(void *object) } dir = &this->dir[SPA_DIRECTION_INPUT]; - in_passthrough = dir->conv.is_passthrough; max_in = UINT32_MAX; /* collect input port data */ @@ -2974,7 +3659,6 @@ static int impl_node_process(void *object) 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)) @@ -2982,6 +3666,7 @@ static int impl_node_process(void *object) if (ctrl != ctrlport->ctrl) { ctrlport->ctrl = ctrl; ctrlport->ctrl_offset = 0; + this->recalc = true; } } else { max_in = SPA_MIN(max_in, size / port->stride); @@ -2997,8 +3682,9 @@ static int impl_node_process(void *object) } } } - - resample_passthrough = resample_is_passthrough(this); + bool resample_passthrough = resample_is_passthrough(this); + if (this->resample_passthrough != resample_passthrough) + this->recalc = true; /* calculate how many samples we are going to produce. */ if (this->direction == SPA_DIRECTION_INPUT) { @@ -3133,122 +3819,30 @@ static int impl_node_process(void *object) flush_in = true; } - mix_passthrough = SPA_FLAG_IS_SET(this->mix.flags, CHANNELMIX_FLAG_IDENTITY) && - (ctrlport == NULL || ctrlport->ctrl == NULL) && (this->vol_ramp_sequence == 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; + ctx.datas[CTX_DATA_SRC] = (void **)src_datas; + ctx.datas[CTX_DATA_DST] = dst_datas; + ctx.datas[CTX_DATA_REMAP_DST] = remap_dst_datas; + ctx.datas[CTX_DATA_REMAP_SRC] = remap_src_datas; + ctx.datas[CTX_DATA_TMP_0] = (void**)this->tmp_datas[0]; + ctx.datas[CTX_DATA_TMP_1] = (void**)this->tmp_datas[1]; + ctx.in_samples = n_samples; + ctx.n_samples = n_samples; + ctx.n_out = n_out; + ctx.src_idx = CTX_DATA_SRC; + ctx.dst_idx = CTX_DATA_DST; + ctx.final_idx = CTX_DATA_DST; + ctx.n_datas = dir->conv.n_channels; + ctx.ctrlport = ctrlport; + + if (this->recalc) + recalc_stages(this, &ctx); + + for (i = 0; i < this->n_stages; i++) { + struct stage *s = &this->stages[i]; + s->run(s, &ctx); } - - if (this->direction == SPA_DIRECTION_INPUT) - handle_wav(this, src_datas, n_samples); - - 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_apply_sequence(this, ctrlport->ctrl, - &ctrlport->ctrl_offset, out_datas, in_datas, n_samples) == 1) { - ctrlio->status = SPA_STATUS_OK; - ctrlport->ctrl = NULL; - } - } else if (this->vol_ramp_sequence) { - if (channelmix_process_apply_sequence(this, this->vol_ramp_sequence, - &this->vol_ramp_offset, out_datas, in_datas, n_samples) == 1) { - free(this->vol_ramp_sequence); - this->vol_ramp_sequence = 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); - } - if (this->direction == SPA_DIRECTION_OUTPUT) - handle_wav(this, (const void**)dst_datas, n_samples); + this->in_offset += ctx.in_samples; + this->out_offset += ctx.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); @@ -3391,6 +3985,8 @@ static int impl_clear(struct spa_handle *handle) free_tmp(this); + clean_filter_handles(this, true); + if (this->resample.free) resample_free(&this->resample); if (this->wav_file != NULL) @@ -3406,34 +4002,6 @@ impl_get_size(const struct spa_handle_factory *factory, return sizeof(struct impl); } -static uint32_t channel_from_name(const char *name) -{ - int i; - for (i = 0; spa_type_audio_channel[i].name; i++) { - if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channel[i].name))) - return spa_type_audio_channel[i].type; - } - return SPA_AUDIO_CHANNEL_UNKNOWN; -} - -static inline uint32_t parse_position(uint32_t *pos, const char *val, size_t len) -{ - struct spa_json it[2]; - char v[256]; - uint32_t i = 0; - - spa_json_init(&it[0], val, len); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], val, len); - - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - i < SPA_AUDIO_MAX_CHANNELS) { - pos[i++] = channel_from_name(v); - } - return i; -} - - static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, @@ -3443,6 +4011,8 @@ impl_init(const struct spa_handle_factory *factory, { struct impl *this; uint32_t i; + const char *str; + bool filter_graph_disabled; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); @@ -3461,7 +4031,10 @@ impl_init(const struct spa_handle_factory *factory, this->cpu_flags = spa_cpu_get_flags(this->cpu); this->max_align = SPA_MIN(MAX_ALIGN, spa_cpu_get_max_align(this->cpu)); } + this->loader = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_PluginLoader); + props_reset(&this->props); + filter_graph_disabled = this->props.filter_graph_disabled; this->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; this->rate_limit.burst = 1; @@ -3474,19 +4047,27 @@ impl_init(const struct spa_handle_factory *factory, this->mix.rear_delay = 0.0f; this->mix.widen = 0.0f; + if (info && (str = spa_dict_lookup(info, "clock.quantum-limit")) != NULL) + spa_atou32(str, &this->quantum_limit, 0); + 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")) + 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, "convert.direction")) { + if (spa_streq(s, "output")) + 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)); + spa_audio_parse_position(s, strlen(s), this->props.channel_map, + &this->props.n_channels); } else if (spa_streq(k, SPA_KEY_PORT_IGNORE_LATENCY)) this->port_ignore_latency = spa_atob(s); @@ -3494,10 +4075,12 @@ impl_init(const struct spa_handle_factory *factory, spa_scnprintf(this->group_name, sizeof(this->group_name), "%s", s); else if (spa_streq(k, "monitor.passthrough")) this->monitor_passthrough = spa_atob(s); + else if (spa_streq(k, "audioconvert.filter-graph.disable")) + filter_graph_disabled = spa_atob(s); else audioconvert_set_param(this, k, s); } - + this->props.filter_graph_disabled = filter_graph_disabled; 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; diff --git a/spa/plugins/audioconvert/benchmark-fmt-ops.c b/spa/plugins/audioconvert/benchmark-fmt-ops.c index 556c8227..9ea43ec6 100644 --- a/spa/plugins/audioconvert/benchmark-fmt-ops.c +++ b/spa/plugins/audioconvert/benchmark-fmt-ops.c @@ -132,6 +132,13 @@ static void test_f32_s16(void) run_testc("test_f32d_s16_2", "avx2", false, true, conv_f32d_to_s16_2_avx2, 2); run_testc("test_f32d_s16_4", "avx2", false, true, conv_f32d_to_s16_4_avx2, 4); } +#endif +#if defined (HAVE_RVV) + if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { + run_test("test_f32_s16", "rvv", true, true, conv_f32_to_s16_rvv); + run_test("test_f32d_s16d", "rvv", false, false, conv_f32d_to_s16d_rvv); + run_test("test_f32d_s16", "rvv", false, true, conv_f32d_to_s16_rvv); + } #endif run_test("test_f32_s16d", "c", true, false, conv_f32_to_s16d_c); run_test("test_f32d_s16d", "c", false, false, conv_f32d_to_s16d_c); @@ -153,6 +160,11 @@ static void test_s16_f32(void) run_test("test_s16_f32d", "avx2", true, false, conv_s16_to_f32d_avx2); run_testc("test_s16_f32d_2", "avx2", true, false, conv_s16_to_f32d_2_avx2, 2); } +#endif +#if defined (HAVE_RVV) + if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { + run_test("test_s16_f32d", "rvv", true, false, conv_s16_to_f32d_rvv); + } #endif run_test("test_s16d_f32d", "c", false, false, conv_s16d_to_f32d_c); } @@ -170,6 +182,11 @@ static void test_f32_s32(void) if (cpu_flags & SPA_CPU_FLAG_AVX2) { run_test("test_f32d_s32", "avx2", false, true, conv_f32d_to_s32_avx2); } +#endif +#if defined (HAVE_RVV) + if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { + run_test("test_f32d_s32", "rvv", false, true, conv_f32d_to_s32_rvv); + } #endif run_test("test_f32_s32d", "c", true, false, conv_f32_to_s32d_c); run_test("test_f32d_s32d", "c", false, false, conv_f32d_to_s32d_c); @@ -188,6 +205,11 @@ static void test_s32_f32(void) if (cpu_flags & SPA_CPU_FLAG_AVX2) { run_test("test_s32_f32d", "avx2", true, false, conv_s32_to_f32d_avx2); } +#endif +#if defined (HAVE_RVV) + if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { + run_test("test_s32_f32d", "rvv", true, false, conv_s32_to_f32d_rvv); + } #endif run_test("test_s32_f32d", "c", true, false, conv_s32_to_f32d_c); run_test("test_s32d_f32d", "c", false, false, conv_s32d_to_f32d_c); diff --git a/spa/plugins/audioconvert/biquad.c b/spa/plugins/audioconvert/biquad.c index 9e8bae5f..d9bb7e3d 100644 --- a/spa/plugins/audioconvert/biquad.c +++ b/spa/plugins/audioconvert/biquad.c @@ -8,9 +8,6 @@ * found in the LICENSE.WEBKIT file. */ - -#include <spa/utils/defs.h> - #include <math.h> #include "biquad.h" @@ -18,9 +15,8 @@ #define M_PI 3.14159265358979323846 #endif -#ifndef M_SQRT2 -#define M_SQRT2 1.41421356237309504880 -#endif +/* Q = 1 / sqrt(2), also resulting Q value when S = 1 */ +#define BIQUAD_DEFAULT_Q 0.707106781186548 static void set_coefficient(struct biquad *bq, double b0, double b1, double b2, double a0, double a1, double a2) @@ -33,79 +29,346 @@ static void set_coefficient(struct biquad *bq, double b0, double b1, double b2, bq->a2 = (float)(a2 * a0_inv); } -static void biquad_lowpass(struct biquad *bq, double cutoff) +static void biquad_lowpass(struct biquad *bq, double cutoff, double Q) { /* Limit cutoff to 0 to 1. */ - cutoff = SPA_CLAMP(cutoff, 0.0, 1.0); + cutoff = fmax(0.0, fmin(cutoff, 1.0)); - if (cutoff >= 1.0) { - /* When cutoff is 1, the z-transform is 1. */ - set_coefficient(bq, 1, 0, 0, 1, 0, 0); - } else if (cutoff > 0) { - /* Compute biquad coefficients for lowpass filter */ - double theta = M_PI * cutoff; - double sn = 0.5 * M_SQRT2 * sin(theta); - double beta = 0.5 * (1 - sn) / (1 + sn); - double gamma_coeff = (0.5 + beta) * cos(theta); - double alpha = 0.25 * (0.5 + beta - gamma_coeff); - - double b0 = 2 * alpha; - double b1 = 2 * 2 * alpha; - double b2 = 2 * alpha; - double a1 = 2 * -gamma_coeff; - double a2 = 2 * beta; - - set_coefficient(bq, b0, b1, b2, 1, a1, a2); - } else { - /* When cutoff is zero, nothing gets through the filter, so set + if (cutoff == 1 || cutoff == 0) { + /* When cutoff is 1, the z-transform is 1. + * When cutoff is zero, nothing gets through the filter, so set * coefficients up correctly. */ - set_coefficient(bq, 0, 0, 0, 1, 0, 0); + set_coefficient(bq, cutoff, 0, 0, 1, 0, 0); + return; } + + /* Set Q to a sane default value if not set */ + if (Q <= 0) + Q = BIQUAD_DEFAULT_Q; + + /* Compute biquad coefficients for lowpass filter */ + /* H(s) = 1 / (s^2 + s/Q + 1) */ + double w0 = M_PI * cutoff; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + + double b0 = (1 - k) / 2; + double b1 = 1 - k; + double b2 = (1 - k) / 2; + double a0 = 1 + alpha; + double a1 = -2 * k; + double a2 = 1 - alpha; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); } -static void biquad_highpass(struct biquad *bq, double cutoff) +static void biquad_highpass(struct biquad *bq, double cutoff, double Q) { /* Limit cutoff to 0 to 1. */ - cutoff = SPA_CLAMP(cutoff, 0.0, 1.0); + cutoff = fmax(0.0, fmin(cutoff, 1.0)); - if (cutoff >= 1.0) { - /* The z-transform is 0. */ - set_coefficient(bq, 0, 0, 0, 1, 0, 0); - } else if (cutoff > 0) { - /* Compute biquad coefficients for highpass filter */ - double theta = M_PI * cutoff; - double sn = 0.5 * M_SQRT2 * sin(theta); - double beta = 0.5 * (1 - sn) / (1 + sn); - double gamma_coeff = (0.5 + beta) * cos(theta); - double alpha = 0.25 * (0.5 + beta + gamma_coeff); - - double b0 = 2 * alpha; - double b1 = 2 * -2 * alpha; - double b2 = 2 * alpha; - double a1 = 2 * -gamma_coeff; - double a2 = 2 * beta; - - set_coefficient(bq, b0, b1, b2, 1, a1, a2); - } else { + if (cutoff == 1 || cutoff == 0) { + /* When cutoff is one, the z-transform is 0. */ /* When cutoff is zero, we need to be careful because the above * gives a quadratic divided by the same quadratic, with poles * and zeros on the unit circle in the same place. When cutoff * is zero, the z-transform is 1. */ + set_coefficient(bq, 1 - cutoff, 0, 0, 1, 0, 0); + return; + } + + /* Set Q to a sane default value if not set */ + if (Q <= 0) + Q = BIQUAD_DEFAULT_Q; + + /* Compute biquad coefficients for highpass filter */ + /* H(s) = s^2 / (s^2 + s/Q + 1) */ + double w0 = M_PI * cutoff; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + + double b0 = (1 + k) / 2; + double b1 = -(1 + k); + double b2 = (1 + k) / 2; + double a0 = 1 + alpha; + double a1 = -2 * k; + double a2 = 1 - alpha; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); +} + +static void biquad_bandpass(struct biquad *bq, double frequency, double Q) +{ + /* No negative frequencies allowed. */ + frequency = fmax(0.0, frequency); + + /* Don't let Q go negative, which causes an unstable filter. */ + Q = fmax(0.0, Q); + + if (frequency <= 0 || frequency >= 1) { + /* When the cutoff is zero, the z-transform approaches 0, if Q + * > 0. When both Q and cutoff are zero, the z-transform is + * pretty much undefined. What should we do in this case? + * For now, just make the filter 0. When the cutoff is 1, the + * z-transform also approaches 0. + */ + set_coefficient(bq, 0, 0, 0, 1, 0, 0); + return; + } + if (Q <= 0) { + /* When Q = 0, the above formulas have problems. If we + * look at the z-transform, we can see that the limit + * as Q->0 is 1, so set the filter that way. + */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + return; + } + + double w0 = M_PI * frequency; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + + double b0 = alpha; + double b1 = 0; + double b2 = -alpha; + double a0 = 1 + alpha; + double a1 = -2 * k; + double a2 = 1 - alpha; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); +} + +static void biquad_lowshelf(struct biquad *bq, double frequency, double Q, + double db_gain) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = fmax(0.0, fmin(frequency, 1.0)); + + double A = pow(10.0, db_gain / 40); + + if (frequency == 1) { + /* The z-transform is a constant gain. */ + set_coefficient(bq, A * A, 0, 0, 1, 0, 0); + return; + } + if (frequency <= 0) { + /* When frequency is 0, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + return; + } + + /* Set Q to an equivalent value to S = 1 if not specified */ + if (Q <= 0) + Q = BIQUAD_DEFAULT_Q; + + double w0 = M_PI * frequency; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + double k2 = 2 * sqrt(A) * alpha; + double a_plus_one = A + 1; + double a_minus_one = A - 1; + + double b0 = A * (a_plus_one - a_minus_one * k + k2); + double b1 = 2 * A * (a_minus_one - a_plus_one * k); + double b2 = A * (a_plus_one - a_minus_one * k - k2); + double a0 = a_plus_one + a_minus_one * k + k2; + double a1 = -2 * (a_minus_one + a_plus_one * k); + double a2 = a_plus_one + a_minus_one * k - k2; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); +} + +static void biquad_highshelf(struct biquad *bq, double frequency, double Q, + double db_gain) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = fmax(0.0, fmin(frequency, 1.0)); + + double A = pow(10.0, db_gain / 40); + + if (frequency == 1) { + /* The z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + return; + } + if (frequency <= 0) { + /* When frequency = 0, the filter is just a gain, A^2. */ + set_coefficient(bq, A * A, 0, 0, 1, 0, 0); + return; + } + + /* Set Q to an equivalent value to S = 1 if not specified */ + if (Q <= 0) + Q = BIQUAD_DEFAULT_Q; + + double w0 = M_PI * frequency; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + double k2 = 2 * sqrt(A) * alpha; + double a_plus_one = A + 1; + double a_minus_one = A - 1; + + double b0 = A * (a_plus_one + a_minus_one * k + k2); + double b1 = -2 * A * (a_minus_one + a_plus_one * k); + double b2 = A * (a_plus_one + a_minus_one * k - k2); + double a0 = a_plus_one - a_minus_one * k + k2; + double a1 = 2 * (a_minus_one - a_plus_one * k); + double a2 = a_plus_one - a_minus_one * k - k2; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); +} + +static void biquad_peaking(struct biquad *bq, double frequency, double Q, + double db_gain) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = fmax(0.0, fmin(frequency, 1.0)); + + /* Don't let Q go negative, which causes an unstable filter. */ + Q = fmax(0.0, Q); + + double A = pow(10.0, db_gain / 40); + + if (frequency <= 0 || frequency >= 1) { + /* When frequency is 0 or 1, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + return; + } + if (Q <= 0) { + /* When Q = 0, the above formulas have problems. If we + * look at the z-transform, we can see that the limit + * as Q->0 is A^2, so set the filter that way. + */ + set_coefficient(bq, A * A, 0, 0, 1, 0, 0); + return; + } + + double w0 = M_PI * frequency; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + + double b0 = 1 + alpha * A; + double b1 = -2 * k; + double b2 = 1 - alpha * A; + double a0 = 1 + alpha / A; + double a1 = -2 * k; + double a2 = 1 - alpha / A; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); +} + +static void biquad_notch(struct biquad *bq, double frequency, double Q) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = fmax(0.0, fmin(frequency, 1.0)); + + /* Don't let Q go negative, which causes an unstable filter. */ + Q = fmax(0.0, Q); + + if (frequency <= 0 || frequency >= 1) { + /* When frequency is 0 or 1, the z-transform is 1. */ set_coefficient(bq, 1, 0, 0, 1, 0, 0); + return; + } + if (Q <= 0) { + /* When Q = 0, the above formulas have problems. If we + * look at the z-transform, we can see that the limit + * as Q->0 is 0, so set the filter that way. + */ + set_coefficient(bq, 0, 0, 0, 1, 0, 0); + return; } + + double w0 = M_PI * frequency; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + + double b0 = 1; + double b1 = -2 * k; + double b2 = 1; + double a0 = 1 + alpha; + double a1 = -2 * k; + double a2 = 1 - alpha; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); +} + +static void biquad_allpass(struct biquad *bq, double frequency, double Q) +{ + /* Clip frequencies to between 0 and 1, inclusive. */ + frequency = fmax(0.0, fmin(frequency, 1.0)); + + /* Don't let Q go negative, which causes an unstable filter. */ + Q = fmax(0.0, Q); + + if (frequency <= 0 || frequency >= 1) { + /* When frequency is 0 or 1, the z-transform is 1. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); + return; + } + + if (Q <= 0) { + /* When Q = 0, the above formulas have problems. If we + * look at the z-transform, we can see that the limit + * as Q->0 is -1, so set the filter that way. + */ + set_coefficient(bq, -1, 0, 0, 1, 0, 0); + return; + } + + double w0 = M_PI * frequency; + double alpha = sin(w0) / (2 * Q); + double k = cos(w0); + + double b0 = 1 - alpha; + double b1 = -2 * k; + double b2 = 1 + alpha; + double a0 = 1 + alpha; + double a1 = -2 * k; + double a2 = 1 - alpha; + + set_coefficient(bq, b0, b1, b2, a0, a1, a2); } -void biquad_set(struct biquad *bq, enum biquad_type type, double freq) +void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q, + double gain) { + /* Clear history values. */ + bq->type = type; + bq->x1 = 0; + bq->x2 = 0; switch (type) { case BQ_LOWPASS: - biquad_lowpass(bq, freq); + biquad_lowpass(bq, freq, Q); break; case BQ_HIGHPASS: - biquad_highpass(bq, freq); + biquad_highpass(bq, freq, Q); + break; + case BQ_BANDPASS: + biquad_bandpass(bq, freq, Q); + break; + case BQ_LOWSHELF: + biquad_lowshelf(bq, freq, Q, gain); + break; + case BQ_HIGHSHELF: + biquad_highshelf(bq, freq, Q, gain); + break; + case BQ_PEAKING: + biquad_peaking(bq, freq, Q, gain); + break; + case BQ_NOTCH: + biquad_notch(bq, freq, Q); + break; + case BQ_ALLPASS: + biquad_allpass(bq, freq, Q); + break; + case BQ_NONE: + case BQ_RAW: + /* Default is an identity filter. */ + set_coefficient(bq, 1, 0, 0, 1, 0, 0); break; } } diff --git a/spa/plugins/audioconvert/biquad.h b/spa/plugins/audioconvert/biquad.h index 8b7eccca..3344598e 100644 --- a/spa/plugins/audioconvert/biquad.h +++ b/spa/plugins/audioconvert/biquad.h @@ -10,6 +10,20 @@ extern "C" { #endif +/* The type of the biquad filters */ +enum biquad_type { + BQ_NONE, + BQ_LOWPASS, + BQ_HIGHPASS, + BQ_BANDPASS, + BQ_LOWSHELF, + BQ_HIGHSHELF, + BQ_PEAKING, + BQ_NOTCH, + BQ_ALLPASS, + BQ_RAW, +}; + /* The biquad filter parameters. The transfer function H(z) is (b0 + b1 * z^(-1) * + b2 * z^(-2)) / (1 + a1 * z^(-1) + a2 * z^(-2)). The previous two inputs * are stored in x1 and x2, and the previous two outputs are stored in y1 and @@ -19,14 +33,10 @@ extern "C" { * float is used during the actual filtering for faster computation. */ struct biquad { + enum biquad_type type; float b0, b1, b2; float a1, a2; -}; - -/* The type of the biquad filters */ -enum biquad_type { - BQ_LOWPASS, - BQ_HIGHPASS, + float x1, x2; }; /* Initialize a biquad filter parameters from its type and parameters. @@ -35,8 +45,11 @@ enum biquad_type { * type - The type of the biquad filter. * frequency - The value should be in the range [0, 1]. It is relative to * half of the sampling rate. + * Q - Quality factor. See Web Audio API for details. + * gain - The value is in dB. See Web Audio API for details. */ -void biquad_set(struct biquad *bq, enum biquad_type type, double freq); +void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q, + double gain); #ifdef __cplusplus } /* extern "C" */ diff --git a/spa/plugins/audioconvert/channelmix-ops-c.c b/spa/plugins/audioconvert/channelmix-ops-c.c index 49613e35..2f03df84 100644 --- a/spa/plugins/audioconvert/channelmix-ops-c.c +++ b/spa/plugins/audioconvert/channelmix-ops-c.c @@ -2,6 +2,9 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include <float.h> +#include <math.h> + #include "channelmix-ops.h" static inline void clear_c(float *d, uint32_t n_samples) @@ -11,7 +14,8 @@ static inline void clear_c(float *d, uint32_t n_samples) static inline void copy_c(float *d, const float *s, uint32_t n_samples) { - spa_memcpy(d, s, n_samples * sizeof(float)); + if (d != s) + spa_memcpy(d, s, n_samples * sizeof(float)); } static inline void vol_c(float *d, const float *s, float vol, uint32_t n_samples) @@ -62,6 +66,73 @@ channelmix_copy_c(struct channelmix *mix, void * SPA_RESTRICT dst[], vol_c(d[i], s[i], mix->matrix[i][i], n_samples); } +static void lr4_process_c(struct lr4 *lr4, float *dst, const float *src, const float vol, int samples) +{ + float x1 = lr4->x1; + float x2 = lr4->x2; + float y1 = lr4->y1; + float y2 = lr4->y2; + float b0 = lr4->bq.b0; + float b1 = lr4->bq.b1; + float b2 = lr4->bq.b2; + float a1 = lr4->bq.a1; + float a2 = lr4->bq.a2; + float x, y, z; + int i; + + if (vol == 0.0f || !lr4->active) { + vol_c(dst, src, vol, samples); + return; + } + + for (i = 0; i < samples; i++) { + x = src[i]; + y = b0 * x + x1; + x1 = b1 * x - a1 * y + x2; + x2 = b2 * x - a2 * y; + z = b0 * y + y1; + y1 = b1 * y - a1 * z + y2; + y2 = b2 * y - a2 * z; + dst[i] = z * vol; + } +#define F(x) (isnormal(x) ? (x) : 0.0f) + lr4->x1 = F(x1); + lr4->x2 = F(x2); + lr4->y1 = F(y1); + lr4->y2 = F(y2); +#undef F +} + +static inline void delay_convolve_run_c(float *buffer, uint32_t *pos, + uint32_t n_buffer, uint32_t delay, + const float *taps, uint32_t n_taps, + float *dst, const float *src, const float vol, uint32_t n_samples) +{ + uint32_t i, j; + uint32_t w = *pos; + uint32_t o = n_buffer - delay - n_taps-1; + + if (n_taps == 1) { + for (i = 0; i < n_samples; i++) { + buffer[w] = buffer[w + n_buffer] = src[i]; + dst[i] = buffer[w + o] * vol; + w = w + 1 >= n_buffer ? 0 : w + 1; + } + } else { + for (i = 0; i < n_samples; i++) { + float sum = 0.0f; + + buffer[w] = buffer[w + n_buffer] = src[i]; + for (j = 0; j < n_taps; j++) + sum += taps[j] * buffer[w+o+j]; + dst[i] = sum * vol; + + w = w + 1 >= n_buffer ? 0 : w + 1; + } + } + *pos = w; +} + #define _M(ch) (1UL << SPA_AUDIO_CHANNEL_ ## ch) void @@ -99,10 +170,10 @@ channelmix_f32_n_m_c(struct channelmix *mix, void * SPA_RESTRICT dst[], 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); + lr4_process_c(&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); + lr4_process_c(&mix->lr4[i], di, di, 1.0f, n_samples); } } } @@ -199,9 +270,9 @@ channelmix_f32_2_4_c(struct channelmix *mix, void * SPA_RESTRICT dst[], } else { sub_c(d[2], s[0], s[1], n_samples); - delay_convolve_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + delay_convolve_run_c(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, + delay_convolve_run_c(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[2], d[2], v2, n_samples); } } @@ -238,8 +309,8 @@ channelmix_f32_2_3p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], d[2][n] = c * 0.5f; } } - lr4_process(&mix->lr4[3], d[3], d[2], v3, n_samples); - lr4_process(&mix->lr4[2], d[2], d[2], v2, n_samples); + lr4_process_c(&mix->lr4[3], d[3], d[2], v3, n_samples); + lr4_process_c(&mix->lr4[2], d[2], d[2], v2, n_samples); } } @@ -267,9 +338,9 @@ channelmix_f32_2_5p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], } else { sub_c(d[4], s[0], s[1], n_samples); - delay_convolve_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + delay_convolve_run_c(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[5], d[4], -v5, n_samples); - delay_convolve_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + delay_convolve_run_c(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[4], d[4], v4, n_samples); } } @@ -303,9 +374,9 @@ channelmix_f32_2_7p1_c(struct channelmix *mix, void * SPA_RESTRICT dst[], } else { sub_c(d[6], s[0], s[1], n_samples); - delay_convolve_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + delay_convolve_run_c(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[7], d[6], -v7, n_samples); - delay_convolve_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + delay_convolve_run_c(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[6], d[6], v6, n_samples); } } diff --git a/spa/plugins/audioconvert/channelmix-ops-sse.c b/spa/plugins/audioconvert/channelmix-ops-sse.c index 36840286..1058959c 100644 --- a/spa/plugins/audioconvert/channelmix-ops-sse.c +++ b/spa/plugins/audioconvert/channelmix-ops-sse.c @@ -5,6 +5,8 @@ #include "channelmix-ops.h" #include <xmmintrin.h> +#include <float.h> +#include <math.h> static inline void clear_sse(float *d, uint32_t n_samples) { @@ -149,6 +151,197 @@ void channelmix_copy_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], vol_sse(d[i], s[i], mix->matrix[i][i], n_samples); } +static void lr4_process_sse(struct lr4 *lr4, float *dst, const float *src, const float vol, int samples) +{ + __m128 x, y, z; + __m128 b012; + __m128 a12; + __m128 x12, y12, v; + int i; + + if (vol == 0.0f || !lr4->active) { + vol_sse(dst, src, vol, samples); + return; + } + + b012 = _mm_setr_ps(lr4->bq.b0, lr4->bq.b1, lr4->bq.b2, 0.0f); /* b0 b1 b2 0 */ + a12 = _mm_setr_ps(0.0f, lr4->bq.a1, lr4->bq.a2, 0.0f); /* 0 a1 a2 0 */ + x12 = _mm_setr_ps(lr4->x1, lr4->x2, 0.0f, 0.0f); /* x1 x2 0 0 */ + y12 = _mm_setr_ps(lr4->y1, lr4->y2, 0.0f, 0.0f); /* y1 y2 0 0 */ + v = _mm_setr_ps(vol, vol, 0.0f, 0.0f); + + for (i = 0; i < samples; i++) { + x = _mm_load1_ps(&src[i]); /* x x x x */ + + z = _mm_mul_ps(x, b012); /* b0*x b1*x b2*x 0 */ + z = _mm_add_ps(z, x12); /* b0*x+x1 b1*x+x2 b2*x 0 */ + y = _mm_shuffle_ps(z, z, _MM_SHUFFLE(0,0,0,0)); /* b0*x+x1 b0*x+x1 b0*x+x1 b0*x+x1 = y*/ + x = _mm_mul_ps(y, a12); /* 0 a1*y a2*y 0 */ + x = _mm_sub_ps(z, x); /* y x1 x2 0 */ + x12 = _mm_shuffle_ps(x, x, _MM_SHUFFLE(3,3,2,1)); /* x1 x2 0 0*/ + + z = _mm_mul_ps(y, b012); + z = _mm_add_ps(z, y12); + x = _mm_shuffle_ps(z, z, _MM_SHUFFLE(0,0,0,0)); + y = _mm_mul_ps(x, a12); + y = _mm_sub_ps(z, y); + y12 = _mm_shuffle_ps(y, y, _MM_SHUFFLE(3,3,2,1)); + + x = _mm_mul_ps(x, v); + _mm_store_ss(&dst[i], x); + } +#define F(x) (isnormal(x) ? (x) : 0.0f) + lr4->x1 = F(x12[0]); + lr4->x2 = F(x12[1]); + lr4->y1 = F(y12[0]); + lr4->y2 = F(y12[1]); +#undef F +} + +static void lr4_process_2_sse(struct lr4 *lr40, struct lr4 *lr41, float *dst0, float *dst1, + const float *src0, const float *src1, const float vol0, const float vol1, uint32_t samples) +{ + __m128 x, y, z; + __m128 b0, b1, b2; + __m128 a1, a2; + __m128 x1, x2; + __m128 y1, y2, v; + uint32_t i; + + b0 = _mm_setr_ps(lr40->bq.b0, lr41->bq.b0, 0.0f, 0.0f); + b1 = _mm_setr_ps(lr40->bq.b1, lr41->bq.b1, 0.0f, 0.0f); + b2 = _mm_setr_ps(lr40->bq.b2, lr41->bq.b2, 0.0f, 0.0f); + a1 = _mm_setr_ps(lr40->bq.a1, lr41->bq.a1, 0.0f, 0.0f); + a2 = _mm_setr_ps(lr40->bq.a2, lr41->bq.a2, 0.0f, 0.0f); + x1 = _mm_setr_ps(lr40->x1, lr41->x1, 0.0f, 0.0f); + x2 = _mm_setr_ps(lr40->x2, lr41->x2, 0.0f, 0.0f); + y1 = _mm_setr_ps(lr40->y1, lr41->y1, 0.0f, 0.0f); + y2 = _mm_setr_ps(lr40->y2, lr41->y2, 0.0f, 0.0f); + v = _mm_setr_ps(vol0, vol1, 0.0f, 0.0f); + + for (i = 0; i < samples; i++) { + x = _mm_setr_ps(src0[i], src1[i], 0.0f, 0.0f); + + y = _mm_mul_ps(x, b0); /* y = x * b0 */ + y = _mm_add_ps(y, x1); /* y = x * b0 + x1*/ + z = _mm_mul_ps(y, a1); /* z = a1 * y */ + x1 = _mm_mul_ps(x, b1); /* x1 = x * b1 */ + x1 = _mm_add_ps(x1, x2); /* x1 = x * b1 + x2*/ + x1 = _mm_sub_ps(x1, z); /* x1 = x * b1 + x2 - a1 * y*/ + z = _mm_mul_ps(y, a2); /* z = a2 * y */ + x2 = _mm_mul_ps(x, b2); /* x2 = x * b2 */ + x2 = _mm_sub_ps(x2, z); /* x2 = x * b2 - a2 * y*/ + + x = _mm_mul_ps(y, b0); /* y = x * b0 */ + x = _mm_add_ps(x, y1); /* y = x * b0 + x1*/ + z = _mm_mul_ps(x, a1); /* z = a1 * y */ + y1 = _mm_mul_ps(y, b1); /* x1 = x * b1 */ + y1 = _mm_add_ps(y1, y2); /* x1 = x * b1 + x2*/ + y1 = _mm_sub_ps(y1, z); /* x1 = x * b1 + x2 - a1 * y*/ + z = _mm_mul_ps(x, a2); /* z = a2 * y */ + y2 = _mm_mul_ps(y, b2); /* x2 = x * b2 */ + y2 = _mm_sub_ps(y2, z); /* x2 = x * b2 - a2 * y*/ + + x = _mm_mul_ps(x, v); + dst0[i] = x[0]; + dst1[i] = x[1]; + } +#define F(x) (isnormal(x) ? (x) : 0.0f) + lr40->x1 = F(x1[0]); + lr40->x2 = F(x2[0]); + lr40->y1 = F(y1[0]); + lr40->y2 = F(y2[0]); + lr41->x1 = F(x1[1]); + lr41->x2 = F(x2[1]); + lr41->y1 = F(y1[1]); + lr41->y2 = F(y2[1]); +#undef F +} + +static inline void convolver_run(const float *src, float *dst, + const float *taps, uint32_t n_taps, const __m128 vol) +{ + __m128 t[1], sum[4]; + uint32_t i; + + sum[0] = _mm_setzero_ps(); + for(i = 0; i < n_taps; i+=4) { + t[0] = _mm_loadu_ps(&src[i]); + sum[0] = _mm_add_ps(sum[0], _mm_mul_ps(_mm_load_ps(&taps[i]), t[0])); + } + sum[0] = _mm_add_ps(sum[0], _mm_movehl_ps(sum[0], sum[0])); + sum[0] = _mm_add_ss(sum[0], _mm_shuffle_ps(sum[0], sum[0], 0x55)); + t[0] = _mm_mul_ss(sum[0], vol); + _mm_store_ss(dst, t[0]); +} + +static inline void delay_convolve_run_sse(float *buffer, uint32_t *pos, + uint32_t n_buffer, uint32_t delay, + const float *taps, uint32_t n_taps, + float *dst, const float *src, const float vol, uint32_t n_samples) +{ + __m128 t[1]; + const __m128 v = _mm_set1_ps(vol); + uint32_t i; + uint32_t w = *pos; + uint32_t o = n_buffer - delay - n_taps-1; + uint32_t n, unrolled; + + if (SPA_IS_ALIGNED(src, 16) && + SPA_IS_ALIGNED(dst, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + if (n_taps == 1) { + for(n = 0; n < unrolled; n += 4) { + t[0] = _mm_load_ps(&src[n]); + _mm_storeu_ps(&buffer[w], t[0]); + _mm_storeu_ps(&buffer[w+n_buffer], t[0]); + t[0] = _mm_loadu_ps(&buffer[w+o]); + t[0] = _mm_mul_ps(t[0], v); + _mm_store_ps(&dst[n], t[0]); + w += 4; + if (w >= n_buffer) { + w -= n_buffer; + t[0] = _mm_load_ps(&buffer[n_buffer]); + _mm_store_ps(&buffer[0], t[0]); + } + } + for(; n < n_samples; n++) { + t[0] = _mm_load_ss(&src[n]); + _mm_store_ss(&buffer[w], t[0]); + _mm_store_ss(&buffer[w+n_buffer], t[0]); + t[0] = _mm_load_ss(&buffer[w+o]); + t[0] = _mm_mul_ss(t[0], v); + _mm_store_ss(&dst[n], t[0]); + w = w + 1 >= n_buffer ? 0 : w + 1; + } + } else { + for(n = 0; n < unrolled; n += 4) { + t[0] = _mm_load_ps(&src[n]); + _mm_storeu_ps(&buffer[w], t[0]); + _mm_storeu_ps(&buffer[w+n_buffer], t[0]); + for(i = 0; i < 4; i++) + convolver_run(&buffer[w+o+i], &dst[n+i], taps, n_taps, v); + w += 4; + if (w >= n_buffer) { + w -= n_buffer; + t[0] = _mm_load_ps(&buffer[n_buffer]); + _mm_store_ps(&buffer[0], t[0]); + } + } + for(; n < n_samples; n++) { + t[0] = _mm_load_ss(&src[n]); + _mm_store_ss(&buffer[w], t[0]); + _mm_store_ss(&buffer[w+n_buffer], t[0]); + convolver_run(&buffer[w+o], &dst[n], taps, n_taps, v); + w = w + 1 >= n_buffer ? 0 : w + 1; + } + } + *pos = w; +} + void channelmix_f32_n_m_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) @@ -172,13 +365,10 @@ channelmix_f32_n_m_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], 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); + lr4_process_sse(&mix->lr4[i], 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); + lr4_process_sse(&mix->lr4[i], di, di, 1.0f, n_samples); } } } @@ -239,8 +429,7 @@ channelmix_f32_2_3p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], _mm_store_ss(&d[2][n], _mm_mul_ss(c[0], mh)); } } - lr4_process(&mix->lr4[3], d[3], d[2], v3, n_samples); - lr4_process(&mix->lr4[2], d[2], d[2], v2, n_samples); + lr4_process_2_sse(&mix->lr4[3], &mix->lr4[2], d[3], d[2], d[2], d[2], v3, v2, n_samples); } } @@ -267,9 +456,9 @@ channelmix_f32_2_5p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], } else { sub_sse(d[4], s[0], s[1], n_samples); - delay_convolve_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + delay_convolve_run_sse(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[5], d[4], -v5, n_samples); - delay_convolve_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + delay_convolve_run_sse(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[4], d[4], v4, n_samples); } } @@ -303,9 +492,9 @@ channelmix_f32_2_7p1_sse(struct channelmix *mix, void * SPA_RESTRICT dst[], } else { sub_sse(d[6], s[0], s[1], n_samples); - delay_convolve_run(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, + delay_convolve_run_sse(mix->buffer[1], &mix->pos[1], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[7], d[6], -v7, n_samples); - delay_convolve_run(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, + delay_convolve_run_sse(mix->buffer[0], &mix->pos[0], BUFFER_SIZE, mix->delay, mix->taps, mix->n_taps, d[6], d[6], v6, n_samples); } } diff --git a/spa/plugins/audioconvert/channelmix-ops.c b/spa/plugins/audioconvert/channelmix-ops.c index 86cd049f..d774112b 100644 --- a/spa/plugins/audioconvert/channelmix-ops.c +++ b/spa/plugins/audioconvert/channelmix-ops.c @@ -673,7 +673,7 @@ done: spa_log_info(mix->log, "channel %d is FC cutoff:%f", ic, mix->fc_cutoff); lr4_set(&mix->lr4[ic], BQ_LOWPASS, mix->fc_cutoff / mix->freq); } else { - mix->lr4[ic].active = false; + lr4_set(&mix->lr4[ic], BQ_NONE, mix->fc_cutoff / mix->freq); } ic++; } @@ -775,16 +775,25 @@ int channelmix_init(struct channelmix *mix) mix->delay = (uint32_t)(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); + spa_zero(mix->taps_mem); + mix->taps = SPA_PTR_ALIGN(mix->taps_mem, CHANNELMIX_OPS_MAX_ALIGN, float); + mix->buffer[0] = SPA_PTR_ALIGN(&mix->buffer_mem[0], CHANNELMIX_OPS_MAX_ALIGN, float); + mix->buffer[1] = SPA_PTR_ALIGN(&mix->buffer_mem[2*BUFFER_SIZE], CHANNELMIX_OPS_MAX_ALIGN, float); if (mix->hilbert_taps > 0) { - mix->n_taps = SPA_CLAMP(mix->hilbert_taps, 15u, 255u) | 1; + mix->n_taps = SPA_CLAMP(mix->hilbert_taps, 15u, MAX_TAPS) | 1; blackman_window(mix->taps, mix->n_taps); hilbert_generate(mix->taps, mix->n_taps); + reverse_taps(mix->taps, mix->n_taps); } else { mix->n_taps = 1; mix->taps[0] = 1.0f; } + if (mix->delay + mix->n_taps > BUFFER_SIZE) + mix->delay = BUFFER_SIZE - mix->n_taps; + + spa_log_debug(mix->log, "selected %s delay:%d options:%08x", info->name, mix->delay, + mix->options); + return make_matrix(mix); } diff --git a/spa/plugins/audioconvert/channelmix-ops.h b/spa/plugins/audioconvert/channelmix-ops.h index 8fb2e005..26e2efc3 100644 --- a/spa/plugins/audioconvert/channelmix-ops.h +++ b/spa/plugins/audioconvert/channelmix-ops.h @@ -10,7 +10,6 @@ #include <spa/param/audio/raw.h> #include "crossover.h" -#include "delay.h" #define VOLUME_MIN 0.0f #define VOLUME_NORM 1.0f @@ -24,7 +23,9 @@ #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 MAX_TAPS 255 +#define MAX_TAPS 255u + +#define CHANNELMIX_OPS_MAX_ALIGN 16 struct channelmix { uint32_t src_chan; @@ -60,10 +61,12 @@ struct channelmix { uint32_t hilbert_taps; /* to phase shift, 0 disabled */ struct lr4 lr4[SPA_AUDIO_MAX_CHANNELS]; - float buffer[2][BUFFER_SIZE]; + float buffer_mem[2 * BUFFER_SIZE*2 + CHANNELMIX_OPS_MAX_ALIGN/4]; + float *buffer[2]; uint32_t pos[2]; uint32_t delay; - float taps[MAX_TAPS]; + float taps_mem[MAX_TAPS + CHANNELMIX_OPS_MAX_ALIGN/4]; + float *taps; uint32_t n_taps; void (*process) (struct channelmix *mix, void * SPA_RESTRICT dst[], @@ -105,8 +108,6 @@ void channelmix_##name##_##arch(struct channelmix *mix, \ void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], \ uint32_t n_samples); -#define CHANNELMIX_OPS_MAX_ALIGN 16 - DEFINE_FUNCTION(copy, c); DEFINE_FUNCTION(f32_n_m, c); DEFINE_FUNCTION(f32_1_2, c); diff --git a/spa/plugins/audioconvert/crossover.c b/spa/plugins/audioconvert/crossover.c index 177cf610..537d4937 100644 --- a/spa/plugins/audioconvert/crossover.c +++ b/spa/plugins/audioconvert/crossover.c @@ -10,55 +10,10 @@ void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq) { - biquad_set(&lr4->bq, type, freq); + biquad_set(&lr4->bq, type, freq, 0, 0); lr4->x1 = 0; lr4->x2 = 0; lr4->y1 = 0; lr4->y2 = 0; - lr4->z1 = 0; - lr4->z2 = 0; - lr4->active = true; -} - -void lr4_process(struct lr4 *lr4, float *dst, const float *src, const float vol, int samples) -{ - float x1 = lr4->x1; - float x2 = lr4->x2; - float y1 = lr4->y1; - float y2 = lr4->y2; - float b0 = lr4->bq.b0; - float b1 = lr4->bq.b1; - float b2 = lr4->bq.b2; - float a1 = lr4->bq.a1; - float a2 = lr4->bq.a2; - float x, y, z; - int i; - - if (vol == 0.0f) { - memset(dst, 0, samples * sizeof(float)); - return; - } else if (!lr4->active) { - if (src != dst || vol != 1.0f) { - for (i = 0; i < samples; i++) - dst[i] = src[i] * vol; - } - return; - } - - for (i = 0; i < samples; i++) { - x = src[i]; - y = b0 * x + x1; - x1 = b1 * x - a1 * y + x2; - x2 = b2 * x - a2 * y; - z = b0 * y + y1; - y1 = b1 * y - a1 * z + y2; - y2 = b2 * y - a2 * z; - dst[i] = z * vol; - } -#define F(x) (-FLT_MIN < (x) && (x) < FLT_MIN ? 0.0f : (x)) - lr4->x1 = F(x1); - lr4->x2 = F(x2); - lr4->y1 = F(y1); - lr4->y2 = F(y2); -#undef F + lr4->active = type != BQ_NONE; } diff --git a/spa/plugins/audioconvert/crossover.h b/spa/plugins/audioconvert/crossover.h index b6f458ba..159d9590 100644 --- a/spa/plugins/audioconvert/crossover.h +++ b/spa/plugins/audioconvert/crossover.h @@ -20,12 +20,9 @@ struct lr4 { struct biquad bq; float x1, x2; float y1, y2; - float z1, z2; bool active; }; void lr4_set(struct lr4 *lr4, enum biquad_type type, float freq); -void lr4_process(struct lr4 *lr4, float *dst, const float *src, const float vol, int samples); - #endif /* CROSSOVER_H_ */ diff --git a/spa/plugins/audioconvert/delay.h b/spa/plugins/audioconvert/delay.h deleted file mode 100644 index 61b028bc..00000000 --- a/spa/plugins/audioconvert/delay.h +++ /dev/null @@ -1,52 +0,0 @@ -/* Spa */ -/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#ifndef DELAY_H -#define DELAY_H - -#ifdef __cplusplus -extern "C" { -#endif - -static inline void delay_run(float *buffer, uint32_t *pos, - uint32_t n_buffer, uint32_t delay, - float *dst, const float *src, const float vol, uint32_t n_samples) -{ - uint32_t i; - uint32_t p = *pos; - - for (i = 0; i < n_samples; i++) { - buffer[p] = src[i]; - dst[i] = buffer[(p - delay) & (n_buffer-1)] * vol; - p = (p + 1) & (n_buffer-1); - } - *pos = p; -} - -static inline void delay_convolve_run(float *buffer, uint32_t *pos, - uint32_t n_buffer, uint32_t delay, - const float *taps, uint32_t n_taps, - float *dst, const float *src, const float vol, uint32_t n_samples) -{ - uint32_t i, j; - uint32_t p = *pos; - - for (i = 0; i < n_samples; i++) { - float sum = 0.0f; - - buffer[p] = src[i]; - for (j = 0; j < n_taps; j++) - sum += (taps[j] * buffer[((p - delay) - j) & (n_buffer-1)]); - dst[i] = sum * vol; - - p = (p + 1) & (n_buffer-1); - } - *pos = p; -} - -#ifdef __cplusplus -} -#endif - -#endif /* DELAY_H */ diff --git a/spa/plugins/audioconvert/fmt-ops-avx2.c b/spa/plugins/audioconvert/fmt-ops-avx2.c index 69970d6e..a939da45 100644 --- a/spa/plugins/audioconvert/fmt-ops-avx2.c +++ b/spa/plugins/audioconvert/fmt-ops-avx2.c @@ -23,6 +23,13 @@ #define _MM_CLAMP_SS(r,min,max) \ _mm_min_ss(_mm_max_ss(r, min), max) +#define _MM256_BSWAP_EPI16(x) \ +({ \ + _mm256_or_si256( \ + _mm256_slli_epi16(x, 8), \ + _mm256_srli_epi16(x, 8)); \ +}) + 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) @@ -74,6 +81,59 @@ conv_s16_to_f32d_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const voi conv_s16_to_f32d_1s_avx2(conv, &dst[i], &s[i], n_channels, n_samples); } + +static void +conv_s16s_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const uint16_t *s = src; + float *d0 = dst[0]; + uint32_t n, unrolled; + __m256i in = _mm256_setzero_si256(); + __m256 out, factor = _mm256_set1_ps(1.0f / S16_SCALE); + + if (SPA_LIKELY(SPA_IS_ALIGNED(d0, 32))) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in = _mm256_insert_epi16(in, s[0*n_channels], 1); + in = _mm256_insert_epi16(in, s[1*n_channels], 3); + in = _mm256_insert_epi16(in, s[2*n_channels], 5); + in = _mm256_insert_epi16(in, s[3*n_channels], 7); + in = _mm256_insert_epi16(in, s[4*n_channels], 9); + in = _mm256_insert_epi16(in, s[5*n_channels], 11); + in = _mm256_insert_epi16(in, s[6*n_channels], 13); + in = _mm256_insert_epi16(in, s[7*n_channels], 15); + in = _MM256_BSWAP_EPI16(in); + + in = _mm256_srai_epi32(in, 16); + out = _mm256_cvtepi32_ps(in); + out = _mm256_mul_ps(out, factor); + _mm256_store_ps(&d0[n], out); + s += 8*n_channels; + } + for(; n < n_samples; n++) { + __m128 out, factor = _mm_set1_ps(1.0f / S16_SCALE); + out = _mm_cvtsi32_ss(factor, (int16_t)bswap_16(s[0])); + out = _mm_mul_ss(out, factor); + _mm_store_ss(&d0[n], out); + s += n_channels; + } +} + +void +conv_s16s_to_f32d_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const uint16_t *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i < n_channels; i++) + conv_s16s_to_f32d_1s_avx2(conv, &dst[i], &s[i], n_channels, n_samples); +} + void conv_s16_to_f32d_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) @@ -132,6 +192,66 @@ conv_s16_to_f32d_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const v } } +void +conv_s16s_to_f32d_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const uint16_t *s = src[0]; + float *d0 = dst[0], *d1 = dst[1]; + uint32_t n, unrolled; + __m256i in[2], t[4]; + __m256 out[4], factor = _mm256_set1_ps(1.0f / S16_SCALE); + + if (SPA_IS_ALIGNED(s, 32) && + SPA_IS_ALIGNED(d0, 32) && + SPA_IS_ALIGNED(d1, 32)) + unrolled = n_samples & ~15; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 16) { + in[0] = _mm256_load_si256((__m256i*)(s + 0)); + in[1] = _mm256_load_si256((__m256i*)(s + 16)); + in[0] = _MM256_BSWAP_EPI16(in[0]); + in[1] = _MM256_BSWAP_EPI16(in[1]); + + t[0] = _mm256_slli_epi32(in[0], 16); + t[0] = _mm256_srai_epi32(t[0], 16); + out[0] = _mm256_cvtepi32_ps(t[0]); + out[0] = _mm256_mul_ps(out[0], factor); + + t[1] = _mm256_srai_epi32(in[0], 16); + out[1] = _mm256_cvtepi32_ps(t[1]); + out[1] = _mm256_mul_ps(out[1], factor); + + t[2] = _mm256_slli_epi32(in[1], 16); + t[2] = _mm256_srai_epi32(t[2], 16); + out[2] = _mm256_cvtepi32_ps(t[2]); + out[2] = _mm256_mul_ps(out[2], factor); + + t[3] = _mm256_srai_epi32(in[1], 16); + out[3] = _mm256_cvtepi32_ps(t[3]); + out[3] = _mm256_mul_ps(out[3], factor); + + _mm256_store_ps(&d0[n + 0], out[0]); + _mm256_store_ps(&d1[n + 0], out[1]); + _mm256_store_ps(&d0[n + 8], out[2]); + _mm256_store_ps(&d1[n + 8], out[3]); + + s += 32; + } + for(; n < n_samples; n++) { + __m128 out[4], factor = _mm_set1_ps(1.0f / S16_SCALE); + out[0] = _mm_cvtsi32_ss(factor, (int16_t)bswap_16(s[0])); + out[0] = _mm_mul_ss(out[0], factor); + out[1] = _mm_cvtsi32_ss(factor, (int16_t)bswap_16(s[1])); + out[1] = _mm_mul_ss(out[1], factor); + _mm_store_ss(&d0[n], out[0]); + _mm_store_ss(&d1[n], out[1]); + s += 2; + } +} + static 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) @@ -316,7 +436,7 @@ conv_s32_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3]; uint32_t n, unrolled; __m256i in[4]; - __m256 out[4], factor = _mm256_set1_ps(1.0f / S24_SCALE); + __m256 out[4], factor = _mm256_set1_ps(1.0f / S32_SCALE_I2F); __m256i mask1 = _mm256_setr_epi32(0*n_channels, 1*n_channels, 2*n_channels, 3*n_channels, 4*n_channels, 5*n_channels, 6*n_channels, 7*n_channels); @@ -334,11 +454,6 @@ conv_s32_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA in[2] = _mm256_i32gather_epi32((int*)&s[2], mask1, 4); in[3] = _mm256_i32gather_epi32((int*)&s[3], mask1, 4); - in[0] = _mm256_srai_epi32(in[0], 8); - in[1] = _mm256_srai_epi32(in[1], 8); - 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]); @@ -357,11 +472,11 @@ conv_s32_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA s += 8*n_channels; } for(; n < n_samples; n++) { - __m128 out[4], factor = _mm_set1_ps(1.0f / S24_SCALE); - 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); + __m128 out[4], factor = _mm_set1_ps(1.0f / S32_SCALE_I2F); + out[0] = _mm_cvtsi32_ss(factor, s[0]); + out[1] = _mm_cvtsi32_ss(factor, s[1]); + out[2] = _mm_cvtsi32_ss(factor, s[2]); + out[3] = _mm_cvtsi32_ss(factor, 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); @@ -382,7 +497,7 @@ conv_s32_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA float *d0 = dst[0], *d1 = dst[1]; uint32_t n, unrolled; __m256i in[4]; - __m256 out[4], factor = _mm256_set1_ps(1.0f / S24_SCALE); + __m256 out[4], factor = _mm256_set1_ps(1.0f / S32_SCALE_I2F); __m256i mask1 = _mm256_setr_epi32(0*n_channels, 1*n_channels, 2*n_channels, 3*n_channels, 4*n_channels, 5*n_channels, 6*n_channels, 7*n_channels); @@ -396,9 +511,6 @@ conv_s32_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA in[0] = _mm256_i32gather_epi32((int*)&s[0], mask1, 4); in[1] = _mm256_i32gather_epi32((int*)&s[1], mask1, 4); - in[0] = _mm256_srai_epi32(in[0], 8); - in[1] = _mm256_srai_epi32(in[1], 8); - out[0] = _mm256_cvtepi32_ps(in[0]); out[1] = _mm256_cvtepi32_ps(in[1]); @@ -411,9 +523,9 @@ conv_s32_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA s += 8*n_channels; } for(; n < n_samples; n++) { - __m128 out[2], factor = _mm_set1_ps(1.0f / S24_SCALE); - out[0] = _mm_cvtsi32_ss(factor, s[0] >> 8); - out[1] = _mm_cvtsi32_ss(factor, s[1] >> 8); + __m128 out[2], factor = _mm_set1_ps(1.0f / S32_SCALE_I2F); + out[0] = _mm_cvtsi32_ss(factor, s[0]); + out[1] = _mm_cvtsi32_ss(factor, 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]); @@ -430,7 +542,7 @@ conv_s32_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA float *d0 = dst[0]; uint32_t n, unrolled; __m256i in[2]; - __m256 out[2], factor = _mm256_set1_ps(1.0f / S24_SCALE); + __m256 out[2], factor = _mm256_set1_ps(1.0f / S32_SCALE_I2F); __m256i mask1 = _mm256_setr_epi32(0*n_channels, 1*n_channels, 2*n_channels, 3*n_channels, 4*n_channels, 5*n_channels, 6*n_channels, 7*n_channels); @@ -443,9 +555,6 @@ conv_s32_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA in[0] = _mm256_i32gather_epi32(&s[0*n_channels], mask1, 4); in[1] = _mm256_i32gather_epi32(&s[8*n_channels], mask1, 4); - in[0] = _mm256_srai_epi32(in[0], 8); - in[1] = _mm256_srai_epi32(in[1], 8); - out[0] = _mm256_cvtepi32_ps(in[0]); out[1] = _mm256_cvtepi32_ps(in[1]); @@ -458,8 +567,8 @@ conv_s32_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA s += 16*n_channels; } for(; n < n_samples; n++) { - __m128 out, factor = _mm_set1_ps(1.0f / S24_SCALE); - out = _mm_cvtsi32_ss(factor, s[0] >> 8); + __m128 out, factor = _mm_set1_ps(1.0f / S32_SCALE_I2F); + out = _mm_cvtsi32_ss(factor, s[0]); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += n_channels; @@ -490,9 +599,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(S24_SCALE); - __m128 int_max = _mm_set1_ps(S24_MAX); - __m128 int_min = _mm_set1_ps(S24_MIN); + __m128 scale = _mm_set1_ps(S32_SCALE_F2I); + __m128 int_min = _mm_set1_ps(S32_MIN_F2I); + __m128 int_max = _mm_set1_ps(S32_MAX_F2I); if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~3; @@ -503,7 +612,6 @@ conv_f32d_to_s32_1s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale); 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)); @@ -518,7 +626,7 @@ conv_f32d_to_s32_1s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[0] = _mm_load_ss(&s0[n]); in[0] = _mm_mul_ss(in[0], scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); - *d = _mm_cvtss_si32(in[0]) << 8; + *d = _mm_cvtss_si32(in[0]); d += n_channels; } } @@ -538,12 +646,12 @@ 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(S24_SCALE); - __m256 int_min = _mm256_set1_ps(S24_MIN); - __m256 int_max = _mm256_set1_ps(S24_MAX); + __m256 scale = _mm256_set1_ps(S32_SCALE_F2I); + __m256 int_min = _mm256_set1_ps(S32_MIN_F2I); + __m256 int_max = _mm256_set1_ps(S32_MAX_F2I); if (SPA_IS_ALIGNED(s0, 32) && - SPA_IS_ALIGNED(s1, 32)) + SPA_IS_ALIGNED(s1, 32)) unrolled = n_samples & ~7; else unrolled = 0; @@ -557,8 +665,6 @@ conv_f32d_to_s32_2s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R 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 */ @@ -587,9 +693,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(S24_SCALE); - __m128 int_min = _mm_set1_ps(S24_MIN); - __m128 int_max = _mm_set1_ps(S24_MAX); + __m128 scale = _mm_set1_ps(S32_SCALE_F2I); + __m128 int_min = _mm_set1_ps(S32_MIN_F2I); + __m128 int_max = _mm_set1_ps(S32_MAX_F2I); in[0] = _mm_load_ss(&s0[n]); in[1] = _mm_load_ss(&s1[n]); @@ -599,7 +705,6 @@ conv_f32d_to_s32_2s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[0] = _mm_mul_ps(in[0], scale); 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; } @@ -614,14 +719,14 @@ 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(S24_SCALE); - __m256 int_min = _mm256_set1_ps(S24_MIN); - __m256 int_max = _mm256_set1_ps(S24_MAX); + __m256 scale = _mm256_set1_ps(S32_SCALE_F2I); + __m256 int_min = _mm256_set1_ps(S32_MIN_F2I); + __m256 int_max = _mm256_set1_ps(S32_MAX_F2I); if (SPA_IS_ALIGNED(s0, 32) && - SPA_IS_ALIGNED(s1, 32) && - SPA_IS_ALIGNED(s2, 32) && - SPA_IS_ALIGNED(s3, 32)) + SPA_IS_ALIGNED(s1, 32) && + SPA_IS_ALIGNED(s2, 32) && + SPA_IS_ALIGNED(s3, 32)) unrolled = n_samples & ~7; else unrolled = 0; @@ -641,10 +746,6 @@ conv_f32d_to_s32_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R 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 */ @@ -669,9 +770,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(S24_SCALE); - __m128 int_min = _mm_set1_ps(S24_MIN); - __m128 int_max = _mm_set1_ps(S24_MAX); + __m128 scale = _mm_set1_ps(S32_SCALE_F2I); + __m128 int_min = _mm_set1_ps(S32_MIN_F2I); + __m128 int_max = _mm_set1_ps(S32_MAX_F2I); in[0] = _mm_load_ss(&s0[n]); in[1] = _mm_load_ss(&s1[n]); @@ -685,7 +786,6 @@ conv_f32d_to_s32_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[0] = _mm_mul_ps(in[0], scale); 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; } @@ -1026,3 +1126,62 @@ conv_f32d_to_s16_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const v d += 2; } } + +void +conv_f32d_to_s16s_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1]; + uint16_t *d = dst[0]; + uint32_t n, unrolled; + __m256 in[4]; + __m256i out[4], t[4]; + __m256 int_scale = _mm256_set1_ps(S16_SCALE); + + if (SPA_IS_ALIGNED(s0, 32) && + SPA_IS_ALIGNED(s1, 32)) + unrolled = n_samples & ~15; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 16) { + 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 */ + out[2] = _mm256_cvtps_epi32(in[2]); /* a0 a1 a2 a3 a4 a5 a6 a7 */ + out[3] = _mm256_cvtps_epi32(in[3]); /* b0 b1 b2 b3 b4 b5 b6 b7 */ + + 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 */ + t[2] = _mm256_unpacklo_epi32(out[2], out[3]); /* a0 b0 a1 b1 a4 b4 a5 b5 */ + t[3] = _mm256_unpackhi_epi32(out[2], out[3]); /* a2 b2 a3 b3 a6 b6 a7 b7 */ + + out[0] = _mm256_packs_epi32(t[0], t[1]); /* a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 */ + out[1] = _mm256_packs_epi32(t[2], t[3]); /* a0 b0 a1 b1 a2 b2 a3 b3 a4 b4 a5 b5 a6 b6 a7 b7 */ + out[0] = _MM256_BSWAP_EPI16(out[0]); + out[1] = _MM256_BSWAP_EPI16(out[1]); + + _mm256_store_si256((__m256i*)(d+0), out[0]); + _mm256_store_si256((__m256i*)(d+16), out[1]); + + d += 32; + } + for(; n < n_samples; n++) { + __m128 in[4]; + __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] = bswap_16((uint16_t)_mm_cvtss_si32(in[0])); + d[1] = bswap_16((uint16_t)_mm_cvtss_si32(in[1])); + d += 2; + } +} diff --git a/spa/plugins/audioconvert/fmt-ops-rvv.c b/spa/plugins/audioconvert/fmt-ops-rvv.c new file mode 100644 index 00000000..02b01720 --- /dev/null +++ b/spa/plugins/audioconvert/fmt-ops-rvv.c @@ -0,0 +1,259 @@ +/* Spa */ +/* SPDX-FileCopyrightText: Copyright (c) 2023 Institue of Software Chinese Academy of Sciences (ISCAS). */ +/* SPDX-License-Identifier: MIT */ + +#include "fmt-ops.h" + +#if HAVE_RVV +void +f32_to_s16(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_samples) +{ + asm __volatile__ ( + ".option arch, +v \n\t" + "li t0, 1191182336 \n\t" + "fmv.w.x fa5, t0 \n\t" + "1: \n\t" + "vsetvli t0, %[n_samples], e32, m8, ta, ma \n\t" + "vle32.v v8, (%[src]) \n\t" + "sub %[n_samples], %[n_samples], t0 \n\t" + "vfmul.vf v8, v8, fa5 \n\t" + "vsetvli zero, zero, e16, m4, ta, ma \n\t" + "vfncvt.x.f.w v8, v8 \n\t" + "slli t0, t0, 1 \n\t" + "vse16.v v8, (%[dst]) \n\t" + "add %[src], %[src], t0 \n\t" + "add %[dst], %[dst], t0 \n\t" + "add %[src], %[src], t0 \n\t" + "bnez %[n_samples], 1b \n\t" + : [n_samples] "+r" (n_samples), + [src] "+r" (src), + [dst] "+r" (dst) + : + : "cc", "memory" + ); +} + +void +conv_f32_to_s16_rvv(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + if (n_samples * conv->n_channels <= 4) { + conv_f32_to_s16_c(conv, dst, src, n_samples); + return; + } + + f32_to_s16(conv, *dst, *src, n_samples * conv -> n_channels); +} + +void +conv_f32d_to_s16d_rvv(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + if (n_samples <= 4) { + conv_f32d_to_s16d_c(conv, dst, src, n_samples); + return; + } + + uint32_t i = 0, n_channels = conv->n_channels; + for(i = 0; i < n_channels; i++) { + f32_to_s16(conv, dst[i], src[i], n_samples); + } +} + +static void +f32d_to_s16(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s = src[0]; + uint32_t stride = n_channels << 1; + + asm __volatile__ ( + ".option arch, +v \n\t" + "li t0, 1191182336 \n\t" + "fmv.w.x fa5, t0 \n\t" + "1: \n\t" + "vsetvli t0, %[n_samples], e32, m8, ta, ma \n\t" + "vle32.v v8, (%[s]) \n\t" + "sub %[n_samples], %[n_samples], t0 \n\t" + "vfmul.vf v8, v8, fa5 \n\t" + "vsetvli zero, zero, e16, m4, ta, ma \n\t" + "vfncvt.x.f.w v8, v8 \n\t" + "slli t2, t0, 2 \n\t" + "mul t3, t0, %[stride] \n\t" + "vsse16.v v8, (%[dst]), %[stride] \n\t" + "add %[s], %[s], t2 \n\t" + "add %[dst], %[dst], t3 \n\t" + "bnez %[n_samples], 1b \n\t" + : [n_samples] "+r" (n_samples), + [s] "+r" (s), + [dst] "+r" (dst) + : [stride] "r" (stride) + : "cc", "memory" + ); +} + +void +conv_f32d_to_s16_rvv(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + if (n_samples <= 4) { + conv_f32d_to_s16_c(conv, dst, src, n_samples); + return; + } + + int16_t *d = dst[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(i = 0; i < n_channels; i++) + f32d_to_s16(conv, &d[i], &src[i], n_channels, n_samples); +} + +static void +s16_to_f32d(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + float *d = dst[0]; + uint32_t stride = n_channels << 1; + + asm __volatile__ ( + ".option arch, +v \n\t" + "li t0, 939524096 \n\t" + "fmv.w.x fa5, t0 \n\t" + "1: \n\t" + "vsetvli t0, %[n_samples], e16, m4, ta, ma \n\t" + "vlse16.v v8, (%[src]), %[stride] \n\t" + "sub %[n_samples], %[n_samples], t0 \n\t" + "vfwcvt.f.x.v v16, v8 \n\t" + "vsetvli zero, zero, e32, m8, ta, ma \n\t" + "mul t4, t0, %[stride] \n\t" + "vfmul.vf v8, v16, fa5 \n\t" + "slli t3, t0, 2 \n\t" + "vse32.v v8, (%[d]) \n\t" + "add %[src], %[src], t4 \n\t" + "add %[d], %[d], t3 \n\t" + "bnez %[n_samples], 1b \n\t" + : [n_samples] "+r" (n_samples), + [src] "+r" (src), + [d] "+r" (d) + : [stride] "r" (stride) + : "cc", "memory" + ); + +} + +void +conv_s16_to_f32d_rvv(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + if (n_samples <= 4) { + conv_s16_to_f32d_c(conv, dst, src, n_samples); + return; + } + + const int16_t *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(i = 0; i < n_channels; i++) + s16_to_f32d(conv, &dst[i], &s[i], n_channels, n_samples); +} + +static void +s32_to_f32d(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + float *d = dst[0]; + uint32_t stride = n_channels << 2; + + asm __volatile__ ( + ".option arch, +v \n\t" + "li t0, 805306368 \n\t" + "fmv.w.x fa5, t0 \n\t" + "1: \n\t" + "vsetvli t0, %[n_samples], e32, m8, ta, ma \n\t" + "vlse32.v v8, (%[src]), %[stride] \n\t" + "sub %[n_samples], %[n_samples], t0 \n\t" + "vfcvt.f.x.v v8, v8 \n\t" + "mul t4, t0, %[stride] \n\t" + "vfmul.vf v8, v8, fa5 \n\t" + "slli t3, t0, 2 \n\t" + "vse32.v v8, (%[d]) \n\t" + "add %[src], %[src], t4 \n\t" + "add %[d], %[d], t3 \n\t" + "bnez %[n_samples], 1b \n\t" + : [n_samples] "+r" (n_samples), + [src] "+r" (src), + [d] "+r" (d) + : [stride] "r" (stride) + : "cc", "memory" + ); + +} + +void +conv_s32_to_f32d_rvv(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + if (n_samples <= 4) { + conv_s32_to_f32d_c(conv, dst, src, n_samples); + return; + } + + const int32_t *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(i = 0; i < n_channels; i++) + s32_to_f32d(conv, &dst[i], &s[i], n_channels, n_samples); + return; +} + +static void +f32d_to_s32(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s = src[0]; + uint32_t stride = n_channels << 2; + + asm __volatile__ ( + ".option arch, +v \n\t" + "li t0, 1325400064 \n\t" + "li t2, 1325400063 \n\t" + "fmv.w.x fa5, t0 \n\t" + "fmv.w.x fa4, t2 \n\t" + "1: \n\t" + "vsetvli t0, %[n_samples], e32, m8, ta, ma \n\t" + "vle32.v v8, (%[s]) \n\t" + "sub %[n_samples], %[n_samples], t0 \n\t" + "vfmul.vf v8, v8, fa5 \n\t" + "vfmin.vf v8, v8, fa4 \n\t" + "vfcvt.x.f.v v8, v8 \n\t" + "slli t2, t0, 2 \n\t" + "mul t3, t0, %[stride] \n\t" + "vsse32.v v8, (%[dst]), %[stride] \n\t" + "add %[s], %[s], t2 \n\t" + "add %[dst], %[dst], t3 \n\t" + "bnez %[n_samples], 1b \n\t" + : [n_samples] "+r" (n_samples), + [s] "+r" (s), + [dst] "+r" (dst) + : [stride] "r" (stride) + : "cc", "memory" + ); +} + +void +conv_f32d_to_s32_rvv(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + if (n_samples <= 4) { + conv_f32d_to_s32_c(conv, dst, src, n_samples); + return; + } + + int32_t *d = dst[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(i = 0; i < n_channels; i++) + f32d_to_s32(conv, &d[i], &src[i], n_channels, n_samples); +} +#endif diff --git a/spa/plugins/audioconvert/fmt-ops-sse2.c b/spa/plugins/audioconvert/fmt-ops-sse2.c index 4aca0e9c..fe6af66c 100644 --- a/spa/plugins/audioconvert/fmt-ops-sse2.c +++ b/spa/plugins/audioconvert/fmt-ops-sse2.c @@ -12,6 +12,20 @@ #define _MM_CLAMP_SS(r,min,max) \ _mm_min_ss(_mm_max_ss(r, min), max) +#define _MM_BSWAP_EPI16(x) \ +({ \ + _mm_or_si128( \ + _mm_slli_epi16(x, 8), \ + _mm_srli_epi16(x, 8)); \ +}) + +#define _MM_BSWAP_EPI32(x) \ +({ \ + __m128i a = _MM_BSWAP_EPI16(x); \ + a = _mm_shufflelo_epi16(a, _MM_SHUFFLE(2, 3, 0, 1)); \ + a = _mm_shufflehi_epi16(a, _MM_SHUFFLE(2, 3, 0, 1)); \ +}) + 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) @@ -57,6 +71,52 @@ conv_s16_to_f32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const voi conv_s16_to_f32d_1s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); } +static void +conv_s16s_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, + uint32_t n_channels, uint32_t n_samples) +{ + const uint16_t *s = src; + float *d0 = dst[0]; + uint32_t n, unrolled; + __m128i in = _mm_setzero_si128(); + __m128 out, factor = _mm_set1_ps(1.0f / S16_SCALE); + + if (SPA_LIKELY(SPA_IS_ALIGNED(d0, 16))) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + in = _mm_insert_epi16(in, s[0*n_channels], 1); + in = _mm_insert_epi16(in, s[1*n_channels], 3); + in = _mm_insert_epi16(in, s[2*n_channels], 5); + in = _mm_insert_epi16(in, s[3*n_channels], 7); + in = _MM_BSWAP_EPI16(in); + in = _mm_srai_epi32(in, 16); + out = _mm_cvtepi32_ps(in); + out = _mm_mul_ps(out, factor); + _mm_store_ps(&d0[n], out); + s += 4*n_channels; + } + for(; n < n_samples; n++) { + out = _mm_cvtsi32_ss(factor, (int16_t)bswap_16(s[0])); + out = _mm_mul_ss(out, factor); + _mm_store_ss(&d0[n], out); + s += n_channels; + } +} + +void +conv_s16s_to_f32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const uint16_t *s = src[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i < n_channels; i++) + conv_s16s_to_f32d_1s_sse2(conv, &dst[i], &s[i], n_channels, n_samples); +} + void conv_s16_to_f32d_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], uint32_t n_samples) @@ -114,6 +174,65 @@ conv_s16_to_f32d_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const v } } +void +conv_s16s_to_f32d_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const uint16_t *s = src[0]; + float *d0 = dst[0], *d1 = dst[1]; + uint32_t n, unrolled; + __m128i in[2], t[4]; + __m128 out[4], factor = _mm_set1_ps(1.0f / S16_SCALE); + + if (SPA_IS_ALIGNED(s, 16) && + SPA_IS_ALIGNED(d0, 16) && + SPA_IS_ALIGNED(d1, 16)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + in[0] = _mm_load_si128((__m128i*)(s + 0)); + in[1] = _mm_load_si128((__m128i*)(s + 8)); + in[0] = _MM_BSWAP_EPI16(in[0]); + in[1] = _MM_BSWAP_EPI16(in[1]); + + t[0] = _mm_slli_epi32(in[0], 16); + t[0] = _mm_srai_epi32(t[0], 16); + out[0] = _mm_cvtepi32_ps(t[0]); + out[0] = _mm_mul_ps(out[0], factor); + + t[1] = _mm_srai_epi32(in[0], 16); + out[1] = _mm_cvtepi32_ps(t[1]); + out[1] = _mm_mul_ps(out[1], factor); + + t[2] = _mm_slli_epi32(in[1], 16); + t[2] = _mm_srai_epi32(t[2], 16); + out[2] = _mm_cvtepi32_ps(t[2]); + out[2] = _mm_mul_ps(out[2], factor); + + t[3] = _mm_srai_epi32(in[1], 16); + out[3] = _mm_cvtepi32_ps(t[3]); + out[3] = _mm_mul_ps(out[3], factor); + + _mm_store_ps(&d0[n + 0], out[0]); + _mm_store_ps(&d1[n + 0], out[1]); + _mm_store_ps(&d0[n + 4], out[2]); + _mm_store_ps(&d1[n + 4], out[3]); + + s += 16; + } + for(; n < n_samples; n++) { + out[0] = _mm_cvtsi32_ss(factor, (int16_t)bswap_16(s[0])); + out[0] = _mm_mul_ss(out[0], factor); + out[1] = _mm_cvtsi32_ss(factor, (int16_t)bswap_16(s[1])); + out[1] = _mm_mul_ss(out[1], factor); + _mm_store_ss(&d0[n], out[0]); + _mm_store_ss(&d1[n], out[1]); + s += 2; + } +} + #define spa_read_unaligned(ptr, type) \ __extension__ ({ \ __typeof__(type) _val; \ @@ -335,7 +454,7 @@ conv_s32_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA float *d0 = dst[0]; uint32_t n, unrolled; __m128i in; - __m128 out, factor = _mm_set1_ps(1.0f / S24_SCALE); + __m128 out, factor = _mm_set1_ps(1.0f / S32_SCALE_I2F); if (SPA_IS_ALIGNED(d0, 16)) unrolled = n_samples & ~3; @@ -347,14 +466,13 @@ conv_s32_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA s[1*n_channels], s[2*n_channels], s[3*n_channels]); - in = _mm_srai_epi32(in, 8); out = _mm_cvtepi32_ps(in); out = _mm_mul_ps(out, factor); _mm_store_ps(&d0[n], out); s += 4*n_channels; } for(; n < n_samples; n++) { - out = _mm_cvtsi32_ss(factor, s[0]>>8); + out = _mm_cvtsi32_ss(factor, s[0]); out = _mm_mul_ss(out, factor); _mm_store_ss(&d0[n], out); s += n_channels; @@ -381,9 +499,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(S24_SCALE); - __m128 int_min = _mm_set1_ps(S24_MIN); - __m128 int_max = _mm_set1_ps(S24_MAX); + __m128 scale = _mm_set1_ps(S32_SCALE_F2I); + __m128 int_min = _mm_set1_ps(S32_MIN_F2I); + __m128 int_max = _mm_set1_ps(S32_MAX_F2I); if (SPA_IS_ALIGNED(s0, 16)) unrolled = n_samples & ~3; @@ -394,7 +512,6 @@ conv_f32d_to_s32_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale); 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,7 +526,7 @@ conv_f32d_to_s32_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[0] = _mm_load_ss(&s0[n]); in[0] = _mm_mul_ss(in[0], scale); in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); - *d = _mm_cvtss_si32(in[0]) << 8; + *d = _mm_cvtss_si32(in[0]); d += n_channels; } } @@ -423,12 +540,12 @@ 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(S24_SCALE); - __m128 int_min = _mm_set1_ps(S24_MIN); - __m128 int_max = _mm_set1_ps(S24_MAX); + __m128 scale = _mm_set1_ps(S32_SCALE_F2I); + __m128 int_min = _mm_set1_ps(S32_MIN_F2I); + __m128 int_max = _mm_set1_ps(S32_MAX_F2I); if (SPA_IS_ALIGNED(s0, 16) && - SPA_IS_ALIGNED(s1, 16)) + SPA_IS_ALIGNED(s1, 16)) unrolled = n_samples & ~3; else unrolled = 0; @@ -442,8 +559,6 @@ conv_f32d_to_s32_2s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R 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]); @@ -463,7 +578,6 @@ conv_f32d_to_s32_2s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[0] = _mm_mul_ps(in[0], scale); 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; } @@ -478,14 +592,14 @@ 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(S24_SCALE); - __m128 int_min = _mm_set1_ps(S24_MIN); - __m128 int_max = _mm_set1_ps(S24_MAX); + __m128 scale = _mm_set1_ps(S32_SCALE_F2I); + __m128 int_min = _mm_set1_ps(S32_MIN_F2I); + __m128 int_max = _mm_set1_ps(S32_MAX_F2I); if (SPA_IS_ALIGNED(s0, 16) && - SPA_IS_ALIGNED(s1, 16) && - SPA_IS_ALIGNED(s2, 16) && - SPA_IS_ALIGNED(s3, 16)) + SPA_IS_ALIGNED(s1, 16) && + SPA_IS_ALIGNED(s2, 16) && + SPA_IS_ALIGNED(s3, 16)) unrolled = n_samples & ~3; else unrolled = 0; @@ -507,10 +621,6 @@ 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]); @@ -531,7 +641,6 @@ conv_f32d_to_s32_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R in[0] = _mm_mul_ps(in[0], scale); 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; } @@ -620,6 +729,7 @@ void conv_noise_tri_hf_sse2(struct convert *conv, float *noise, uint32_t n_sampl _mm_store_si128((__m128i*)p, old[0]); } +// FIXME: this function is not covered with tests. 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) @@ -629,9 +739,9 @@ conv_f32d_to_s32_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, co 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); + __m128 scale = _mm_set1_ps(S32_SCALE_F2I); + __m128 int_min = _mm_set1_ps(S32_MIN_F2I); + __m128 int_max = _mm_set1_ps(S32_MAX_F2I); if (SPA_IS_ALIGNED(s, 16)) unrolled = n_samples & ~3; @@ -643,7 +753,6 @@ conv_f32d_to_s32_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, co 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)); @@ -659,7 +768,7 @@ conv_f32d_to_s32_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, co 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 = _mm_cvtss_si32(in[0]); d += n_channels; } } @@ -766,15 +875,6 @@ conv_32d_to_32_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void conv_interleave_32_1s_sse2(conv, &d[i], &src[i], n_channels, n_samples); } -#define _MM_BSWAP_EPI32(x) \ -({ \ - __m128i a = _mm_or_si128( \ - _mm_slli_epi16(x, 8), \ - _mm_srli_epi16(x, 8)); \ - a = _mm_shufflelo_epi16(a, _MM_SHUFFLE(2, 3, 0, 1)); \ - a = _mm_shufflehi_epi16(a, _MM_SHUFFLE(2, 3, 0, 1)); \ -}) - static void conv_interleave_32s_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], uint32_t n_channels, uint32_t n_samples) @@ -1256,6 +1356,62 @@ 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_s16s_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_RESTRICT src[], + uint32_t n_channels, uint32_t n_samples) +{ + const float *s0 = src[0]; + uint16_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); + out[0] = _mm_cvtps_epi32(in[0]); + out[1] = _mm_cvtps_epi32(in[1]); + out[0] = _mm_packs_epi32(out[0], out[1]); + out[0] = _MM_BSWAP_EPI16(out[0]); + + 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_CLAMP_SS(in[0], int_min, int_max); + *d = bswap_16((uint16_t)_mm_cvtss_si32(in[0])); + d += n_channels; + } +} + +void +conv_f32d_to_s16s_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + uint16_t *d = dst[0]; + uint32_t i = 0, n_channels = conv->n_channels; + + for(; i < n_channels; i++) + conv_f32d_to_s16s_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) @@ -1428,3 +1584,58 @@ conv_f32d_to_s16_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const v d += 2; } } + +void +conv_f32d_to_s16s_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[], + uint32_t n_samples) +{ + const float *s0 = src[0], *s1 = src[1]; + uint16_t *d = dst[0]; + uint32_t n, unrolled; + __m128 in[4]; + __m128i out[4]; + __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)) + unrolled = n_samples & ~7; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 8) { + 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]); + out[2] = _mm_cvtps_epi32(in[2]); + out[3] = _mm_cvtps_epi32(in[3]); + + out[0] = _mm_packs_epi32(out[0], out[2]); + out[1] = _mm_packs_epi32(out[1], out[3]); + + out[2] = _mm_unpacklo_epi16(out[0], out[1]); + out[3] = _mm_unpackhi_epi16(out[0], out[1]); + + out[2] = _MM_BSWAP_EPI16(out[2]); + out[3] = _MM_BSWAP_EPI16(out[3]); + + _mm_storeu_si128((__m128i*)(d+0), out[2]); + _mm_storeu_si128((__m128i*)(d+8), out[3]); + + d += 16; + } + for(; n < n_samples; n++) { + 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] = bswap_16((uint16_t)_mm_cvtss_si32(in[0])); + d[1] = bswap_16((uint16_t)_mm_cvtss_si32(in[1])); + d += 2; + } +} diff --git a/spa/plugins/audioconvert/fmt-ops.c b/spa/plugins/audioconvert/fmt-ops.c index 748c2231..4df31323 100644 --- a/spa/plugins/audioconvert/fmt-ops.c +++ b/spa/plugins/audioconvert/fmt-ops.c @@ -68,10 +68,21 @@ static struct conv_info conv_table[] = #if defined (HAVE_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 +#if defined (HAVE_RVV) + MAKE(S16, F32P, 0, conv_s16_to_f32d_rvv, SPA_CPU_FLAG_RISCV_V), #endif MAKE(S16, F32P, 0, conv_s16_to_f32d_c), MAKE(S16P, F32, 0, conv_s16d_to_f32_c), +#if defined (HAVE_AVX2) + MAKE(S16_OE, F32P, 2, conv_s16s_to_f32d_2_avx2, SPA_CPU_FLAG_AVX2), + MAKE(S16_OE, F32P, 0, conv_s16s_to_f32d_avx2, SPA_CPU_FLAG_AVX2), +#endif +#if defined (HAVE_SSE2) + MAKE(S16_OE, F32P, 2, conv_s16s_to_f32d_2_sse2, SPA_CPU_FLAG_SSE2), + MAKE(S16_OE, F32P, 0, conv_s16s_to_f32d_sse2, SPA_CPU_FLAG_SSE2), +#endif MAKE(S16_OE, F32P, 0, conv_s16s_to_f32d_c), MAKE(F32, F32, 0, conv_copy32_c), @@ -102,6 +113,9 @@ static struct conv_info conv_table[] = #endif #if defined (HAVE_SSE2) MAKE(S32, F32P, 0, conv_s32_to_f32d_sse2, SPA_CPU_FLAG_SSE2), +#endif +#if defined (HAVE_RVV) + MAKE(S32, F32P, 0, conv_s32_to_f32d_rvv, SPA_CPU_FLAG_RISCV_V), #endif MAKE(S32, F32, 0, conv_s32_to_f32_c), MAKE(S32P, F32P, 0, conv_s32d_to_f32d_c), @@ -176,6 +190,11 @@ static struct conv_info conv_table[] = #if defined (HAVE_SSE2) MAKE(F32, S16, 0, conv_f32_to_s16_sse2, SPA_CPU_FLAG_SSE2), +#endif +#if defined (HAVE_RVV) + MAKE(F32, S16, 0, conv_f32_to_s16_rvv, SPA_CPU_FLAG_RISCV_V), + MAKE(F32P, S16P, 0, conv_f32d_to_s16d_rvv, SPA_CPU_FLAG_RISCV_V), + MAKE(F32P, S16, 0, conv_f32d_to_s16_rvv, SPA_CPU_FLAG_RISCV_V), #endif MAKE(F32, S16, 0, conv_f32_to_s16_c), @@ -212,6 +231,10 @@ static struct conv_info conv_table[] = 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), +#if defined (HAVE_SSE2) + MAKE(F32P, S16_OE, 2, conv_f32d_to_s16s_2_sse2, SPA_CPU_FLAG_SSE2), + MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_sse2, SPA_CPU_FLAG_SSE2), +#endif MAKE(F32P, S16_OE, 0, conv_f32d_to_s16s_c), MAKE(F32, U32, 0, conv_f32_to_u32_c), @@ -232,6 +255,9 @@ static struct conv_info conv_table[] = #endif #if defined (HAVE_SSE2) MAKE(F32P, S32, 0, conv_f32d_to_s32_sse2, SPA_CPU_FLAG_SSE2), +#endif +#if defined (HAVE_RVV) + MAKE(F32P, S32, 0, conv_f32d_to_s32_rvv, SPA_CPU_FLAG_RISCV_V), #endif MAKE(F32P, S32, 0, conv_f32d_to_s32_c), diff --git a/spa/plugins/audioconvert/fmt-ops.h b/spa/plugins/audioconvert/fmt-ops.h index c8d1d526..7aed0bc6 100644 --- a/spa/plugins/audioconvert/fmt-ops.h +++ b/spa/plugins/audioconvert/fmt-ops.h @@ -3,16 +3,9 @@ /* SPDX-License-Identifier: MIT */ #include <math.h> -#if defined(__FreeBSD__) || defined(__MidnightBSD__) -#include <sys/endian.h> -#define bswap_16 bswap16 -#define bswap_32 bswap32 -#define bswap_64 bswap64 -#else -#include <byteswap.h> -#endif #include <spa/utils/defs.h> +#include <spa/utils/endian.h> #include <spa/utils/string.h> #define f32_round(a) lrintf(a) @@ -22,7 +15,7 @@ #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 FMT_OPS_MAX_ALIGN 32u #define U8_MIN 0u #define U8_MAX 255u @@ -88,6 +81,18 @@ #define U32_TO_U24_32(v) (((uint32_t)(v)) >> 8) +#define S25_MIN -16777216 +#define S25_MAX 16777215 +#define S25_SCALE 16777216.0f +#define S25_32_TO_F32(v) ITOF(int32_t, v, S25_SCALE, 0.0f) +#define S25_32S_TO_F32(v) S25_32_TO_F32(bswap_32(v)) +#define F32_TO_S25_32_D(v,d) FTOI(int32_t, v, S25_SCALE, 0.0f, d, S25_MIN, S25_MAX) +#define F32_TO_S25_32(v) F32_TO_S25_32_D(v, 0.0f) +#define F32_TO_S25_32S(v) bswap_32(F32_TO_S25_32(v)) +#define F32_TO_S25_32S_D(v,d) bswap_32(F32_TO_S25_32_D(v,d)) +#define S25_32_TO_S32(v) ((int32_t)(((uint32_t)(v)) << 7)) +#define S32_TO_S25_32(v) (((int32_t)(v)) >> 7) + #define U32_MIN 0u #define U32_MAX 4294967295u #define U32_SCALE 2147483648.f @@ -107,12 +112,17 @@ #define S32_TO_S24_32(v) (((int32_t)(v)) >> 8) -#define S32_MIN (S24_MIN * 256) -#define S32_MAX (S24_MAX * 256) -#define S32_TO_F32(v) ITOF(int32_t, S32_TO_S24_32(v), S24_SCALE, 0.0f) +#define S32_MIN -2147483648 +#define S32_MAX 2147483647 +#define S32_SCALE_I2F 2147483648.0f +#define S32_TO_F32(v) ITOF(int32_t, v, S32_SCALE_I2F, 0.0f) #define S32S_TO_F32(v) S32_TO_F32(bswap_32(v)) -#define F32_TO_S32(v) S24_32_TO_S32(F32_TO_S24_32(v)) -#define F32_TO_S32_D(v,d) S24_32_TO_S32(F32_TO_S24_32_D(v,d)) + +#define S32_MIN_F2I ((int32_t)(((uint32_t)(S25_MIN)) << 7)) +#define S32_MAX_F2I ((S25_MAX) << 7) +#define S32_SCALE_F2I (-((float)(S32_MIN_F2I))) +#define F32_TO_S32_D(v,d) FTOI(int32_t, v, S32_SCALE_F2I, 0.0f, d, S32_MIN_F2I, S32_MAX_F2I) +#define F32_TO_S32(v) F32_TO_S32_D(v, 0.0f) #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)) @@ -430,9 +440,19 @@ DEFINE_FUNCTION(s16_to_f32d_2, neon); DEFINE_FUNCTION(s16_to_f32d, neon); DEFINE_FUNCTION(f32d_to_s16, neon); #endif +#if defined(HAVE_RVV) +DEFINE_FUNCTION(f32d_to_s32, rvv); +DEFINE_FUNCTION(f32_to_s16, rvv); +DEFINE_FUNCTION(f32d_to_s16d, rvv); +DEFINE_FUNCTION(f32d_to_s16, rvv); +DEFINE_FUNCTION(s16_to_f32d, rvv); +DEFINE_FUNCTION(s32_to_f32d, rvv); +#endif #if defined(HAVE_SSE2) DEFINE_FUNCTION(s16_to_f32d_2, sse2); DEFINE_FUNCTION(s16_to_f32d, sse2); +DEFINE_FUNCTION(s16s_to_f32d, sse2); +DEFINE_FUNCTION(s16s_to_f32d_2, sse2); DEFINE_FUNCTION(s24_to_f32d, sse2); DEFINE_FUNCTION(s32_to_f32d, sse2); DEFINE_FUNCTION(f32d_to_s32, sse2); @@ -440,6 +460,8 @@ 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_s16s_2, sse2); +DEFINE_FUNCTION(f32d_to_s16s, sse2); DEFINE_FUNCTION(f32d_to_s16_noise, sse2); DEFINE_FUNCTION(f32d_to_s16d, sse2); DEFINE_FUNCTION(f32d_to_s16d_noise, sse2); @@ -457,6 +479,8 @@ DEFINE_FUNCTION(s24_to_f32d, sse41); #if defined(HAVE_AVX2) DEFINE_FUNCTION(s16_to_f32d_2, avx2); DEFINE_FUNCTION(s16_to_f32d, avx2); +DEFINE_FUNCTION(s16s_to_f32d, avx2); +DEFINE_FUNCTION(s16s_to_f32d_2, avx2); DEFINE_FUNCTION(s24_to_f32d, avx2); DEFINE_FUNCTION(s32_to_f32d, avx2); DEFINE_FUNCTION(f32d_to_s32, avx2); diff --git a/spa/plugins/audioconvert/hilbert.h b/spa/plugins/audioconvert/hilbert.h index 7abef708..aa00940b 100644 --- a/spa/plugins/audioconvert/hilbert.h +++ b/spa/plugins/audioconvert/hilbert.h @@ -43,6 +43,13 @@ static inline int hilbert_generate(float *taps, int n_taps) return 0; } +static inline void reverse_taps(float *taps, int n_taps) +{ + int i; + for (i = 0; i < n_taps/2; i++) + SPA_SWAP(taps[i], taps[n_taps-1-i]); +} + #ifdef __cplusplus } /* extern "C" */ #endif diff --git a/spa/plugins/audioconvert/meson.build b/spa/plugins/audioconvert/meson.build index d18f64b5..394bc11e 100644 --- a/spa/plugins/audioconvert/meson.build +++ b/spa/plugins/audioconvert/meson.build @@ -105,10 +105,51 @@ if have_neon simd_dependencies += audioconvert_neon endif +if have_rvv + audioconvert_rvv = static_library('audioconvert_rvv', + ['fmt-ops-rvv.c' ], + c_args : ['-O3', '-DHAVE_RVV'], + dependencies : [ spa_dep ], + install : false + ) + simd_cargs += ['-DHAVE_RVV'] + simd_dependencies += audioconvert_rvv +endif + +sparesampledumpcoeffs_sources = [ + 'resample-native.c', + 'resample-native-c.c', + 'spa-resample-dump-coeffs.c', +] + +sparesampledumpcoeffs = executable( + 'spa-resample-dump-coeffs', + sparesampledumpcoeffs_sources, + c_args : [ cc_flags_native, '-DRESAMPLE_DISABLE_PRECOMP' ], + dependencies : [ spa_dep, mathlib_native ], + install : false, + native : true, +) + +precomptuples = [] +foreach tuple : get_option('resampler-precomp-tuples') + precomptuples += '-t ' + tuple +endforeach + +resample_native_precomp_h = custom_target( + 'resample-native-precomp.h', + output : 'resample-native-precomp.h', + capture : true, + command : [ + sparesampledumpcoeffs, + ] + precomptuples +) + audioconvert_lib = static_library('audioconvert', ['fmt-ops.c', 'channelmix-ops.c', 'peaks-ops.c', + resample_native_precomp_h, 'resample-native.c', 'resample-peaks.c', 'wavfile.c', @@ -144,6 +185,7 @@ test_apps = [ 'test-fmt-ops', 'test-peaks', 'test-resample', + 'test-resample-delay', ] foreach a : test_apps diff --git a/spa/plugins/audioconvert/resample-native.c b/spa/plugins/audioconvert/resample-native.c index 0183d368..f393e3dc 100644 --- a/spa/plugins/audioconvert/resample-native.c +++ b/spa/plugins/audioconvert/resample-native.c @@ -7,6 +7,9 @@ #include <spa/param/audio/format.h> #include "resample-native-impl.h" +#ifndef RESAMPLE_DISABLE_PRECOMP +#include "resample-native-precomp.h" +#endif struct quality { uint32_t n_taps; @@ -302,14 +305,36 @@ static void impl_native_reset (struct resample *r) if (r->options & RESAMPLE_OPTION_PREFILL) d->hist = d->n_taps - 1; else - d->hist = (d->n_taps / 2) - 1; + d->hist = d->n_taps / 2; d->phase = 0; } static uint32_t impl_native_delay (struct resample *r) { struct native_data *d = r->data; - return d->n_taps / 2; + return d->n_taps / 2 - 1; +} + +static float impl_native_phase (struct resample *r) +{ + struct native_data *d = r->data; + float pho = 0; + + if (d->func == d->info->process_full) { + pho = -(float)((int32_t)d->phase) / d->out_rate; + + /* XXX: this is how it seems to behave, but not clear why */ + if (d->hist >= d->n_taps - 1) + pho += 1.0f; + } else if (d->func == d->info->process_inter) { + pho = -d->phase / d->out_rate; + + /* XXX: this is how it seems to behave, but not clear why */ + if (d->hist >= d->n_taps - 1) + pho += 1.0f; + } + + return pho; } int resample_native_init(struct resample *r) @@ -328,6 +353,7 @@ int resample_native_init(struct resample *r) r->process = impl_native_process; r->reset = impl_native_reset; r->delay = impl_native_delay; + r->phase = impl_native_phase; q = &window_qualities[r->quality]; @@ -375,7 +401,25 @@ int resample_native_init(struct resample *r) for (c = 0; c < r->channels; c++) d->history[c] = SPA_PTROFF(d->hist_mem, c * history_stride, float); - build_filter(d->filter, d->filter_stride, n_taps, n_phases, scale); +#ifndef RESAMPLE_DISABLE_PRECOMP + /* See if we have precomputed coefficients */ + for (c = 0; precomp_coeffs[c].filter; c++) { + if (precomp_coeffs[c].in_rate == r->i_rate && + precomp_coeffs[c].out_rate == r->o_rate && + precomp_coeffs[c].quality == r->quality) + break; + } + + if (precomp_coeffs[c].filter) { + spa_log_debug(r->log, "using precomputed filter for %u->%u(%u)", + r->i_rate, r->o_rate, r->quality); + spa_memcpy(d->filter, precomp_coeffs[c].filter, filter_size); + } else { +#endif + build_filter(d->filter, d->filter_stride, n_taps, n_phases, scale); +#ifndef RESAMPLE_DISABLE_PRECOMP + } +#endif d->info = find_resample_info(SPA_AUDIO_FORMAT_F32, r->cpu_flags); if (SPA_UNLIKELY(d->info == NULL)) { diff --git a/spa/plugins/audioconvert/resample-peaks.c b/spa/plugins/audioconvert/resample-peaks.c index 51c28e7b..aade7711 100644 --- a/spa/plugins/audioconvert/resample-peaks.c +++ b/spa/plugins/audioconvert/resample-peaks.c @@ -100,6 +100,11 @@ static void impl_peaks_reset (struct resample *r) d->i_count = d->o_count = 0; } +static float impl_peaks_phase (struct resample *r) +{ + return 0; +} + int resample_peaks_init(struct resample *r) { struct peaks_data *d; @@ -125,6 +130,7 @@ int resample_peaks_init(struct resample *r) r->delay = impl_peaks_delay; r->in_len = impl_peaks_in_len; r->out_len = impl_peaks_out_len; + r->phase = impl_peaks_phase; spa_log_debug(r->log, "peaks %p: in:%d out:%d features:%08x:%08x", r, r->i_rate, r->o_rate, r->cpu_flags, d->peaks.cpu_flags); diff --git a/spa/plugins/audioconvert/resample.h b/spa/plugins/audioconvert/resample.h index a07b559c..5308fa82 100644 --- a/spa/plugins/audioconvert/resample.h +++ b/spa/plugins/audioconvert/resample.h @@ -32,6 +32,10 @@ struct resample { void * SPA_RESTRICT dst[], uint32_t *out_len); void (*reset) (struct resample *r); uint32_t (*delay) (struct resample *r); + + /** Fractional part of delay (in input samples) */ + float (*phase) (struct resample *r); + void *data; }; @@ -42,6 +46,7 @@ struct resample { #define resample_process(r,...) (r)->process(r,__VA_ARGS__) #define resample_reset(r) (r)->reset(r) #define resample_delay(r) (r)->delay(r) +#define resample_phase(r) (r)->phase(r) int resample_native_init(struct resample *r); int resample_peaks_init(struct resample *r); diff --git a/spa/plugins/audioconvert/spa-resample-dump-coeffs.c b/spa/plugins/audioconvert/spa-resample-dump-coeffs.c new file mode 100644 index 00000000..154792fc --- /dev/null +++ b/spa/plugins/audioconvert/spa-resample-dump-coeffs.c @@ -0,0 +1,200 @@ +/* Spa */ +/* SPDX-FileCopyrightText: Copyright © 2024 Arun Raghavan <arun@asymptotic.io> */ +/* SPDX-License-Identifier: MIT */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <getopt.h> + +#include <spa/support/log-impl.h> +#include <spa/utils/result.h> +#include <spa/utils/string.h> + +SPA_LOG_IMPL(logger); + +#include "resample.h" +#include "resample-native-impl.h" + +#define OPTIONS "ht:" +static const struct option long_options[] = { + { "help", no_argument, NULL, 'h'}, + + { "tuple", required_argument, NULL, 't' }, + + { NULL, 0, NULL, 0 } +}; + +static void show_usage(const char *name, bool is_error) +{ + FILE *fp; + + fp = is_error ? stderr : stdout; + + fprintf(fp, "%s [options]\n", name); + fprintf(fp, + " -h, --help Show this help\n" + "\n" + " -t --tuple Sample rate tuple (as \"in_rate,out_rate[,quality]\")\n" + "\n"); +} + +static void parse_tuple(const char *arg, int *in, int *out, int *quality) +{ + char tuple[256]; + char *token; + + strncpy(tuple, arg, sizeof(tuple) - 1); + *in = 0; + *out = 0; + + token = strtok(tuple, ","); + if (!token || !spa_atoi32(token, in, 10)) + return; + + token = strtok(NULL, ","); + if (!token || !spa_atoi32(token, out, 10)) + return; + + token = strtok(NULL, ","); + if (!token) { + *quality = RESAMPLE_DEFAULT_QUALITY; + } else if (!spa_atoi32(token, quality, 10)) { + *quality = -1; + return; + } + + /* first, second now contain zeroes on error, or the numbers on success, + * third contains a quality or -1 on error, default value if unspecified */ +} + +#define PREFIX "__precomp_coeff" + +static void dump_header(void) +{ + printf("/* This is a generated file, see spa-resample-dump-coeffs.c */"); + printf("\n#include <stdint.h>\n"); + printf("\n#include <stdlib.h>\n"); + printf("\n"); + printf("struct resample_coeffs {\n"); + printf("\tuint32_t in_rate;\n"); + printf("\tuint32_t out_rate;\n"); + printf("\tint quality;\n"); + printf("\tconst float *filter;\n"); + printf("};\n"); +} + +static void dump_footer(const uint32_t *ins, const uint32_t *outs, const int *qualities) +{ + printf("\n"); + printf("static const struct resample_coeffs precomp_coeffs[] = {\n"); + while (*ins && *outs) { + printf("\t{ .in_rate = %u, .out_rate = %u, .quality = %u, " + ".filter = %s_%u_%u_%u },\n", + *ins, *outs, *qualities, PREFIX, *ins, *outs, *qualities); + ins++; + outs++; + qualities++; + } + printf("\t{ .in_rate = 0, .out_rate = 0, .quality = 0, .filter = NULL },\n"); + printf("};\n"); +} + +static void dump_coeffs(unsigned int in_rate, unsigned int out_rate, int quality) +{ + struct resample r = { 0, }; + struct native_data *d; + unsigned int i, filter_size; + int ret; + + r.log = &logger.log; + r.i_rate = in_rate; + r.o_rate = out_rate; + r.quality = quality; + r.channels = 1; /* irrelevant for generated taps */ + + if ((ret = resample_native_init(&r)) < 0) { + fprintf(stderr, "can't init converter: %s\n", spa_strerror(ret)); + return; + } + + d = r.data; + filter_size = d->filter_stride * (d->n_phases + 1); + + printf("\n"); + printf("static const float %s_%u_%u_%u[] = {", PREFIX, in_rate, out_rate, quality); + for (i = 0; i < filter_size; i++) { + printf("%a", d->filter[i]); + if (i != filter_size - 1) + printf(","); + } + printf("};\n"); + + if (r.free) + r.free(&r); +} + +int main(int argc, char* argv[]) +{ + unsigned int ins[256] = { 0, }, outs[256] = { 0, }; + int qualities[256] = { 0, }; + int in_rate = 0, out_rate = 0, quality = 0; + int c, longopt_index = 0, i = 0; + + while ((c = getopt_long(argc, argv, OPTIONS, long_options, &longopt_index)) != -1) { + switch (c) { + case 'h': + show_usage(argv[0], false); + return EXIT_SUCCESS; + case 't': + parse_tuple(optarg, &in_rate, &out_rate, &quality); + if (in_rate <= 0) { + fprintf(stderr, "error: bad input rate %d\n", in_rate); + goto error; + } + if (out_rate <= 0) { + fprintf(stderr, "error: bad output rate %d\n", out_rate); + goto error; + } + if (quality < 0 || quality > 14) { + fprintf(stderr, "error: bad quality value %s\n", optarg); + goto error; + } + ins[i] = in_rate; + outs[i] = out_rate; + qualities[i] = quality; + i++; + break; + default: + fprintf(stderr, "error: unknown option\n"); + goto error_usage; + } + } + + if (optind != argc) { + fprintf(stderr, "error: got %d extra argument(s))\n", + optind - argc); + goto error_usage; + } + if (in_rate == 0) { + fprintf(stderr, "error: input rate must be specified\n"); + goto error; + } + if (out_rate == 0) { + fprintf(stderr, "error: input rate must be specified\n"); + goto error; + } + + dump_header(); + while (i--) { + dump_coeffs(ins[i], outs[i], qualities[i]); + } + dump_footer(ins, outs, qualities); + + return EXIT_SUCCESS; + +error_usage: + show_usage(argv[0], true); +error: + return EXIT_FAILURE; +} diff --git a/spa/plugins/audioconvert/spa-resample.c b/spa/plugins/audioconvert/spa-resample.c index 1c90a713..ec752c65 100644 --- a/spa/plugins/audioconvert/spa-resample.c +++ b/spa/plugins/audioconvert/spa-resample.c @@ -119,6 +119,8 @@ sf_str_to_fmt(const char *str) static int open_files(struct data *d) { + int i, count = 0, format = -1; + d->ifile = sf_open(d->iname, SFM_READ, &d->iinfo); if (d->ifile == NULL) { fprintf(stderr, "error: failed to open input file \"%s\": %s\n", @@ -128,8 +130,32 @@ static int open_files(struct data *d) d->oinfo.channels = d->iinfo.channels; d->oinfo.samplerate = d->rate > 0 ? d->rate : d->iinfo.samplerate; - d->oinfo.format = d->format > 0 ? d->format : d->iinfo.format; - d->oinfo.format |= SF_FORMAT_WAV; + d->oinfo.format = d->format > 0 ? d->format : (d->iinfo.format & SF_FORMAT_SUBMASK); + + /* try to guess the format from the extension */ + if (sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) != 0) + count = 0; + + for (i = 0; i < count; i++) { + SF_FORMAT_INFO fi; + + spa_zero(fi); + fi.format = i; + if (sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) != 0) + continue; + + if (spa_strendswith(d->oname, fi.extension)) { + format = fi.format; + break; + } + } + if (format == -1) + /* use the same format as the input file otherwise */ + format = d->iinfo.format & ~SF_FORMAT_SUBMASK; + if (format == SF_FORMAT_WAV && d->oinfo.channels > 2) + format = SF_FORMAT_WAVEX; + + d->oinfo.format |= format; d->ofile = sf_open(d->oname, SFM_WRITE, &d->oinfo); if (d->ofile == NULL) { diff --git a/spa/plugins/audioconvert/test-fmt-ops.c b/spa/plugins/audioconvert/test-fmt-ops.c index b14da3a5..17a26a35 100644 --- a/spa/plugins/audioconvert/test-fmt-ops.c +++ b/spa/plugins/audioconvert/test-fmt-ops.c @@ -228,6 +228,16 @@ static void test_f32_s16(void) false, true, conv_f32d_to_s16_neon); } #endif +#if defined(HAVE_RVV) + if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { + run_test("test_f32_s16_rvv", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, true, conv_f32_to_s16_rvv); + run_test("test_f32d_s16d_rvv", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, false, conv_f32d_to_s16d_rvv); + run_test("test_f32d_s16_rvv", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s16_rvv); + } +#endif } static void test_s16_f32(void) @@ -261,6 +271,12 @@ static void test_s16_f32(void) true, false, conv_s16_to_f32d_neon); } #endif +#if defined(HAVE_RVV) + if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { + run_test("test_s16_f32d_rvv", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s16_to_f32d_rvv); + } +#endif } static void test_f32_u32(void) @@ -291,10 +307,21 @@ 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, - 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 }; + 1.0f/0xa00000, -1.0f/0xa00000, 1.0f/0x800000, -1.0f/0x800000, + 1.0f/0x1000000, -1.0f/0x1000000, 1.0f/0x2000000, -1.0f/0x2000000, + 1.0f/0x4000000, -1.0f/0x4000000, 1.0f/0x8000000, -1.0f/0x8000000, + 1.0f/0x10000000, -1.0f/0x10000000, 1.0f/0x20000000, -1.0f/0x20000000, + 1.0f/0x40000000, -1.0f/0x40000000, 1.0f/0x80000000, -1.0f/0x80000000, + 1.0f/0x100000000, -1.0f/0x100000000, 1.0f/0x200000000, -1.0f/0x200000000, + }; + static const int32_t out[] = { 0x00000000, 0x7fffff80, 0x80000000, + 0x40000000, 0xc0000000, 0x7fffff80, 0x80000000, 0x000000cd, + 0xffffff33, 0x00000100, 0xffffff00, 0x00000080, 0xffffff80, + 0x00000040, 0xffffffc0, 0x00000020, 0xffffffe0, 0x00000010, + 0xfffffff0, 0x00000008, 0xfffffff8, 0x00000004, 0xfffffffc, + 0x00000002, 0xfffffffe, 0x00000001, 0xffffffff, 0x00000000, + 0x00000000, 0x00000000, 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); @@ -316,12 +343,27 @@ static void test_f32_s32(void) false, true, conv_f32d_to_s32_avx2); } #endif +#if defined(HAVE_RVV) + if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { + run_test("test_f32d_s32_rvv", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + false, true, conv_f32d_to_s32_rvv); + } +#endif } static void test_s32_f32(void) { - static const int32_t in[] = { 0, 0x7fffff00, 0x80000000, 0x40000000, 0xc0000000 }; - static const float out[] = { 0.0f, 0.999999880791f, -1.0f, 0.5, -0.5, }; + static const int32_t in[] = { 0, 0x7FFFFFFF, 0x80000000, 0x7fffff00, + 0x80000100, 0x40000000, 0xc0000000, 0x0080, 0xFFFFFF80, 0x0100, + 0xFFFFFF00, 0x0200, 0xFFFFFE00 + }; + + static const float out[] = { 0.e+00f, 1.e+00f, -1.e+00f, + 9.9999988079071044921875e-01f, -9.9999988079071044921875e-01f, 5.e-01f, + -5.e-01f, 5.9604644775390625e-08f, -5.9604644775390625e-08f, + 1.1920928955078125e-07f, -1.1920928955078125e-07f, + 2.384185791015625e-07f, -2.384185791015625e-07f + }; run_test("test_s32_f32d", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), true, false, conv_s32_to_f32d_c); @@ -343,6 +385,12 @@ static void test_s32_f32(void) true, false, conv_s32_to_f32d_avx2); } #endif +#if defined(HAVE_RVV) + if (cpu_flags & SPA_CPU_FLAG_RISCV_V) { + run_test("test_s32_f32d_rvv", in, sizeof(in[0]), out, sizeof(out[0]), SPA_N_ELEMENTS(out), + true, false, conv_s32_to_f32d_rvv); + } +#endif } static void test_f32_u24(void) @@ -600,15 +648,81 @@ static void test_lossless_u24(void) } } -static void test_lossless_s32(void) +static void test_lossless_s25_32_to_f32_to_s25_32(void) +{ + int32_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = S25_MIN; i <= S25_MAX; i+=11) { + float v = S25_32_TO_F32(i); + int32_t t = F32_TO_S25_32(v); + spa_assert_se(i == t); + } +} + +static void test_lossless_s25_32_to_s32_to_f32_to_s25_32(void) { int32_t i; fprintf(stderr, "test %s:\n", __func__); - for (i = S32_MIN; i < S32_MAX; i+=255) { + for (i = S25_MIN; i <= S25_MAX; i+=13) { + float v = S32_TO_F32(S25_32_TO_S32(i)); + int32_t t = F32_TO_S25_32(v); + spa_assert_se(i == t); + } +} + +static void test_lossless_s25_32_to_s32_to_f32_to_s32_to_s25_32(void) +{ + int32_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = S25_MIN; i <= S25_MAX; i+=11) { + float v = S32_TO_F32(S25_32_TO_S32(i)); + int32_t t = S32_TO_S25_32(F32_TO_S32(v)); + spa_assert_se(i == t); + } +} + +static void test_lossless_s25_32_to_f32_to_s32_to_s25_32(void) +{ + int32_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = S25_MIN; i <= S25_MAX; i+=11) { + float v = S25_32_TO_F32(i); + int32_t t = S32_TO_S25_32(F32_TO_S32(v)); + spa_assert_se(i == t); + } +} + +static void test_lossless_s32(void) +{ + int64_t i; + + fprintf(stderr, "test %s:\n", __func__); + for (i = S32_MIN; i < S32_MAX; i += 63) { float v = S32_TO_F32(i); int32_t t = F32_TO_S32(v); - spa_assert_se(SPA_ABS(i - t) <= 256); + spa_assert_se(SPA_ABS(i - t) <= 126); + // NOTE: 126 is the maximal absolute error given step=1, + // for wider steps it may (errneously) be lower, + // because we may not check some integer that would bump it. + } +} + +static void test_lossless_s32_lossless_subset(void) +{ + int32_t i, j; + + fprintf(stderr, "test %s:\n", __func__); + for (i = S25_MIN; i <= S25_MAX; i+=11) { + for(j = 0; j < 8; ++j) { + int32_t s = i * (1<<j); + float v = S32_TO_F32(s); + int32_t t = F32_TO_S32(v); + spa_assert_se(s == t); + } } } @@ -775,7 +889,12 @@ int main(int argc, char *argv[]) test_lossless_u16(); test_lossless_s24(); test_lossless_u24(); + test_lossless_s25_32_to_f32_to_s25_32(); + test_lossless_s25_32_to_s32_to_f32_to_s25_32(); + test_lossless_s25_32_to_s32_to_f32_to_s32_to_s25_32(); + test_lossless_s25_32_to_f32_to_s32_to_s25_32(); test_lossless_s32(); + test_lossless_s32_lossless_subset(); test_lossless_u32(); test_swaps(); diff --git a/spa/plugins/audioconvert/test-resample-delay.c b/spa/plugins/audioconvert/test-resample-delay.c new file mode 100644 index 00000000..91a04a5a --- /dev/null +++ b/spa/plugins/audioconvert/test-resample-delay.c @@ -0,0 +1,456 @@ +/* Spa */ +/* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> +#include <errno.h> +#include <time.h> +#include <math.h> + +#include <spa/support/log-impl.h> +#include <spa/debug/mem.h> + +SPA_LOG_IMPL(logger); + +#include "resample.h" + + +static float samp_in[65536]; +static float samp_out[65536]; +static bool force_print; + +static void assert_test(bool check) +{ + if (!check) + fprintf(stderr, "FAIL\n\n"); +#if 1 + spa_assert_se(check); +#endif +} + +static double difference(double delay, const float *a, size_t len_a, const float *b, size_t len_b, double b_rate) +{ + size_t i; + float c = 0, wa = 0, wb = 0; + + /* Difference measure: sum((a-b)^2) / sqrt(sum a^2 sum b^2); restricted to overlap */ + for (i = 0; i < len_a; ++i) { + double jf = (i + delay) * b_rate; + int j; + double x; + float bv; + + j = (int)floor(jf); + if (j < 0 || j + 1 >= (int)len_b) + continue; + + x = jf - j; + bv = (float)((1 - x) * b[j] + x * b[j + 1]); + + c += (a[i] - bv) * (a[i] - bv); + wa += a[i]*a[i]; + wb = bv*bv; + } + + if (wa == 0 || wb == 0) + return 1e30; + + return c / sqrt(wa * wb); +} + +struct find_delay_data { + const float *a; + const float *b; + size_t len_a; + size_t len_b; + double b_rate; +}; + +static double find_delay_func(double x, void *user_data) +{ + const struct find_delay_data *data = user_data; + + return difference((float)x, data->a, data->len_a, data->b, data->len_b, data->b_rate); +} + +static double minimum(double x1, double x4, double (*func)(double, void *), void *user_data, double tol) +{ + /* Find minimum with golden section search */ + const double phi = (1 + sqrt(5)) / 2; + double x2, x3; + double f2, f3; + + spa_assert(x4 >= x1); + + x2 = x4 - (x4 - x1) / phi; + x3 = x1 + (x4 - x1) / phi; + + f2 = func(x2, user_data); + f3 = func(x3, user_data); + + while (x4 - x1 > tol) { + if (f2 > f3) { + x1 = x2; + x2 = x3; + x3 = x1 + (x4 - x1) / phi; + f2 = f3; + f3 = func(x3, user_data); + } else { + x4 = x3; + x3 = x2; + x2 = x4 - (x4 - x1) / phi; + f3 = f2; + f2 = func(x2, user_data); + } + } + + return (f2 < f3) ? x2 : x3; +} + +static double find_delay(const float *a, size_t len_a, const float *b, size_t len_b, double b_rate, int maxdelay, double tol) +{ + struct find_delay_data data = { .a = a, .len_a = len_a, .b = b, .len_b = len_b, .b_rate = b_rate }; + double best_x, best_f; + int i; + + best_x = 0; + best_f = find_delay_func(best_x, &data); + + for (i = -maxdelay; i <= maxdelay; ++i) { + double f = find_delay_func(i, &data); + + if (f < best_f) { + best_x = i; + best_f = f; + } + } + + return minimum(best_x - 2, best_x + 2, find_delay_func, &data, tol); +} + +static void test_find_delay(void) +{ + float v1[1024]; + float v2[1024]; + const double tol = 0.001; + double delay, expect; + int i; + + fprintf(stderr, "\n\n-- test_find_delay\n\n"); + + for (i = 0; i < 1024; ++i) { + v1[i] = sinf(0.1f * i); + v2[i] = sinf(0.1f * (i - 3.1234f)); + } + + delay = find_delay(v1, SPA_N_ELEMENTS(v1), v2, SPA_N_ELEMENTS(v2), 1, 50, tol); + expect = 3.1234f; + fprintf(stderr, "find_delay = %g (exact %g)\n", delay, expect); + assert_test(expect - 2*tol < delay && delay < expect + 2*tol); + + for (i = 0; i < 1024; ++i) { + v1[i] = sinf(0.1f * i); + v2[i] = sinf(0.1f * (i*3.0f/4 - 3.1234f)); + } + + delay = find_delay(v1, SPA_N_ELEMENTS(v1), v2, SPA_N_ELEMENTS(v2), 4.0/3.0, 50, tol); + expect = 3.1234f; + fprintf(stderr, "find_delay = %g (exact %g)\n", delay, expect); + assert_test(expect - 2*tol < delay && delay < expect + 2*tol); +} + +static uint32_t feed_sine(struct resample *r, uint32_t in, uint32_t *inp, uint32_t *phase, bool print) +{ + uint32_t i, out; + const void *src[1]; + void *dst[1]; + + for (i = 0; i < in; ++i) + samp_in[i] = sinf(0.01f * (*phase + i)) * expf(-0.001f * (*phase + i)); + + src[0] = samp_in; + dst[0] = samp_out; + out = SPA_N_ELEMENTS(samp_out); + + *inp = in; + resample_process(r, src, inp, dst, &out); + spa_assert_se(*inp == in); + + if (print || force_print) { + fprintf(stderr, "inp(%u) = ", in); + for (uint32_t i = 0; i < in; ++i) + fprintf(stderr, "%g, ", samp_in[i]); + fprintf(stderr, "\n\n"); + + fprintf(stderr, "out(%u) = ", out); + for (uint32_t i = 0; i < out; ++i) + fprintf(stderr, "%g, ", samp_out[i]); + fprintf(stderr, "\n\n"); + } else { + fprintf(stderr, "inp(%u) = ...\n", in); + fprintf(stderr, "out(%u) = ...\n", out); + } + + *phase += in; + *inp = in; + return out; +} + +static void check_delay(double rate, uint32_t out_rate, uint32_t options) +{ + const double tol = 0.001; + struct resample r; + uint32_t in_phase = 0; + uint32_t in, out; + double expect, got; + + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = 48000; + r.o_rate = out_rate; + r.quality = RESAMPLE_DEFAULT_QUALITY; + r.options = options; + resample_native_init(&r); + + resample_update_rate(&r, rate); + + feed_sine(&r, 512, &in, &in_phase, false); + + /* Test delay */ + expect = resample_delay(&r) + (double)resample_phase(&r); + out = feed_sine(&r, 256, &in, &in_phase, true); + got = find_delay(samp_in, in, samp_out, out, out_rate / 48000.0, 100, tol); + + fprintf(stderr, "delay: expect = %g, got = %g\n", expect, got); + assert_test(expect - 4*tol < got && got < expect + 4*tol); + + resample_free(&r); +} + +static void test_delay_copy(void) +{ + fprintf(stderr, "\n\n-- test_delay_copy (no prefill)\n\n"); + check_delay(1, 48000, 0); + + fprintf(stderr, "\n\n-- test_delay_copy (prefill)\n\n"); + check_delay(1, 48000, RESAMPLE_OPTION_PREFILL); +} + +static void test_delay_full(void) +{ + const uint32_t rates[] = { 16000, 32000, 44100, 48000, 88200, 96000, 144000, 192000 }; + unsigned int i; + + for (i = 0; i < SPA_N_ELEMENTS(rates); ++i) { + fprintf(stderr, "\n\n-- test_delay_full(%u, no prefill)\n\n", rates[i]); + check_delay(1, rates[i], 0); + fprintf(stderr, "\n\n-- test_delay_full(%u, prefill)\n\n", rates[i]); + check_delay(1, rates[i], RESAMPLE_OPTION_PREFILL); + } +} + +static void test_delay_interp(void) +{ + fprintf(stderr, "\n\n-- test_delay_interp(no prefill)\n\n"); + check_delay(1 + 1e-12, 48000, 0); + + fprintf(stderr, "\n\n-- test_delay_interp(prefill)\n\n"); + check_delay(1 + 1e-12, 48000, RESAMPLE_OPTION_PREFILL); +} + +static void check_delay_vary_rate(double rate, double end_rate, uint32_t out_rate, uint32_t options) +{ + const double tol = 0.001; + struct resample r; + uint32_t in_phase = 0; + uint32_t in, out; + double expect, got; + + fprintf(stderr, "\n\n-- check_delay_vary_rate(%g, %.14g, %u, %s)\n\n", rate, end_rate, out_rate, + (options & RESAMPLE_OPTION_PREFILL) ? "prefill" : "no prefill"); + + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = 48000; + r.o_rate = out_rate; + r.quality = RESAMPLE_DEFAULT_QUALITY; + r.options = options; + resample_native_init(&r); + + /* Cause nonzero resampler phase */ + resample_update_rate(&r, rate); + feed_sine(&r, 128, &in, &in_phase, false); + + resample_update_rate(&r, 1.7); + feed_sine(&r, 128, &in, &in_phase, false); + + resample_update_rate(&r, end_rate); + feed_sine(&r, 128, &in, &in_phase, false); + feed_sine(&r, 255, &in, &in_phase, false); + + /* Test delay */ + expect = (double)resample_delay(&r) + (double)resample_phase(&r); + out = feed_sine(&r, 256, &in, &in_phase, true); + got = find_delay(samp_in, in, samp_out, out, out_rate/48000.0, 100, tol); + + fprintf(stderr, "delay: expect = %g, got = %g\n", expect, got); + assert_test(expect - 4*tol < got && got < expect + 4*tol); + + resample_free(&r); +} + + +static void test_delay_interp_vary_rate(void) +{ + const uint32_t rates[] = { 32000, 44100, 48000, 88200, 96000 }; + const double factors[] = { 1.0123456789, 1.123456789, 1.203883, 1.23456789, 1.3456789 }; + unsigned int i, j; + + for (i = 0; i < SPA_N_ELEMENTS(rates); ++i) { + for (j = 0; j < SPA_N_ELEMENTS(factors); ++j) { + /* Interp at end */ + check_delay_vary_rate(factors[j], 1 + 1e-12, rates[i], 0); + + /* Copy/full at end */ + check_delay_vary_rate(factors[j], 1, rates[i], 0); + + /* Interp at end */ + check_delay_vary_rate(factors[j], 1 + 1e-12, rates[i], RESAMPLE_OPTION_PREFILL); + + /* Copy/full at end */ + check_delay_vary_rate(factors[j], 1, rates[i], RESAMPLE_OPTION_PREFILL); + } + } +} + +static void run(uint32_t in_rate, uint32_t out_rate, double end_rate, double mid_rate, uint32_t options) +{ + const double tol = 0.001; + struct resample r; + uint32_t in_phase = 0; + uint32_t in, out; + double expect, got; + + spa_zero(r); + r.log = &logger.log; + r.channels = 1; + r.i_rate = in_rate; + r.o_rate = out_rate; + r.quality = RESAMPLE_DEFAULT_QUALITY; + r.options = options; + resample_native_init(&r); + + /* Cause nonzero resampler phase */ + if (mid_rate != 0.0) { + resample_update_rate(&r, mid_rate); + feed_sine(&r, 128, &in, &in_phase, true); + + resample_update_rate(&r, 1.7); + feed_sine(&r, 128, &in, &in_phase, true); + } + + resample_update_rate(&r, end_rate); + feed_sine(&r, 128, &in, &in_phase, true); + feed_sine(&r, 255, &in, &in_phase, true); + + /* Test delay */ + expect = (double)resample_delay(&r) + (double)resample_phase(&r); + out = feed_sine(&r, 256, &in, &in_phase, true); + got = find_delay(samp_in, in, samp_out, out, ((double)out_rate)/in_rate, 100, tol); + + fprintf(stderr, "delay: expect = %g, got = %g\n", expect, got); + if (!(expect - 4*tol < got && got < expect + 4*tol)) + fprintf(stderr, "FAIL\n\n"); + + resample_free(&r); +} + +int main(int argc, char *argv[]) +{ + static const struct option long_options[] = { + { "in-rate", required_argument, NULL, 'i' }, + { "out-rate", required_argument, NULL, 'o' }, + { "end-full", no_argument, NULL, 'f' }, + { "end-interp", no_argument, NULL, 'p' }, + { "mid-rate", required_argument, NULL, 'm' }, + { "prefill", no_argument, NULL, 'r' }, + { "print", no_argument, NULL, 'P' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0} + }; + const char *help = "%s [options]\n" + "\n" + "Check resampler delay. If no arguments, run tests.\n" + "\n" + "-i | --in-rate INRATE input rate\n" + "-o | --out-rate OUTRATE output rate\n" + "-f | --end-full force full (or copy) resampler\n" + "-p | --end-interp force interp resampler\n" + "-m | --mid-rate RELRATE force rate adjustment in the middle\n" + "-r | --prefill enable prefill\n" + "-P | --print force printing\n" + "\n"; + uint32_t in_rate = 0, out_rate = 0; + double end_rate = 1, mid_rate = 0; + uint32_t options = 0; + int c; + + logger.log.level = SPA_LOG_LEVEL_TRACE; + + while ((c = getopt_long(argc, argv, "i:o:fpm:rPh", long_options, NULL)) != -1) { + switch (c) { + case 'h': + fprintf(stderr, help, argv[0]); + return 0; + case 'i': + if (!spa_atou32(optarg, &in_rate, 0)) + goto error_arg; + break; + case 'o': + if (!spa_atou32(optarg, &out_rate, 0)) + goto error_arg; + break; + case 'f': + end_rate = 1; + break; + case 'p': + end_rate = 1 + 1e-12; + break; + case 'm': + if (!spa_atod(optarg, &mid_rate)) + goto error_arg; + break; + case 'r': + options = RESAMPLE_OPTION_PREFILL; + break; + case 'P': + force_print = true; + break; + default: + goto error_arg; + } + } + + if (in_rate && out_rate) { + run(in_rate, out_rate, end_rate, mid_rate, options); + return 0; + } + + test_find_delay(); + test_delay_copy(); + test_delay_full(); + test_delay_interp(); + test_delay_interp_vary_rate(); + + return 0; + +error_arg: + fprintf(stderr, "Invalid arguments\n"); + return 1; +} diff --git a/spa/plugins/audiomixer/mix-ops.h b/spa/plugins/audiomixer/mix-ops.h index 22147404..0d955963 100644 --- a/spa/plugins/audiomixer/mix-ops.h +++ b/spa/plugins/audiomixer/mix-ops.h @@ -123,7 +123,7 @@ void mix_##name##_##arch(struct mix_ops *ops, void * SPA_RESTRICT dst, \ const void * SPA_RESTRICT src[], uint32_t n_src, \ uint32_t n_samples) \ -#define MIX_OPS_MAX_ALIGN 32 +#define MIX_OPS_MAX_ALIGN 32u DEFINE_FUNCTION(s8, c); DEFINE_FUNCTION(u8, c); diff --git a/spa/plugins/avb/avb-pcm.c b/spa/plugins/avb/avb-pcm.c index ffe1c748..3f608d7f 100644 --- a/spa/plugins/avb/avb-pcm.c +++ b/spa/plugins/avb/avb-pcm.c @@ -37,10 +37,11 @@ static int avb_set_param(struct state *state, const char *k, const char *s) 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)); + state->default_format = spa_type_audio_format_from_short_name(s); fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { - spa_avb_parse_position(&state->default_pos, s, strlen(s)); + spa_audio_parse_position(s, strlen(s), state->default_pos.pos, + &state->default_pos.channels); fmt_change++; } else if (spa_streq(k, SPA_KEY_AUDIO_ALLOWED_RATES)) { state->n_allowed_rates = spa_avb_parse_rates(state->allowed_rates, diff --git a/spa/plugins/avb/avb-pcm.h b/spa/plugins/avb/avb-pcm.h index fb2591ff..d4dfa03f 100644 --- a/spa/plugins/avb/avb-pcm.h +++ b/spa/plugins/avb/avb-pcm.h @@ -33,6 +33,7 @@ extern "C" { #include <spa/param/param.h> #include <spa/param/latency-utils.h> #include <spa/param/audio/format-utils.h> +#include <spa/param/audio/raw-json.h> #include "avb.h" @@ -264,54 +265,17 @@ 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]; + struct spa_json it[1]; 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); + if (spa_json_begin_array_relax(&it[0], val, len) <= 0) + return 0; count = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && count < max) + while (spa_json_get_string(&it[0], v, sizeof(v)) > 0 && count < max) rates[count++] = atoi(v); return count; } diff --git a/spa/plugins/bluez5/README-Telephony.md b/spa/plugins/bluez5/README-Telephony.md new file mode 100644 index 00000000..93910b05 --- /dev/null +++ b/spa/plugins/bluez5/README-Telephony.md @@ -0,0 +1,345 @@ +# PipeWire Bluetooth Telephony service + +The Telephony service is a D-Bus service that allows applications to communicate +with the HFP native backend in order to control phone calls. Phone call features +are a core part of the HFP specification and are available when a mobile phone +is paired (therefore, PipeWire acts as the Hands-Free and the phone is the Audio +Gateway). + +The service is exposed on the user session bus by default, but there is an +option to make it available on the system bus instead. + +The service implements its own interfaces alongside the standard DBus +Introspectable, ObjectManager and Properties interfaces, where needed. +These interfaces are mostly compatible with the ofono Manager, VoiceCallManager +and VoiceCall interfaces. For compatibility, the `org.ofono.Manager`, +`org.ofono.VoiceCallManager` and `org.ofono.VoiceCall` are also implemented +with any additional compatibility methods & signals are necessary to allow +ofono-based applications to be able to work just by modifying the service name, +the manager object path and the operating bus (session vs system). + +In addition, to the compatibility interfaces, there is a runtime option to +also register the service as `org.ofono` on the system bus, making it a drop-in +replacement for ofono. Note, however, that this service is not a full replacement, +but only for the Bluetooth-based voice calls. + +## Manager Object + +``` +Service org.pipewire.Telephony + or org.ofono +Object path /org/pipewire/Telephony + or / +Implements org.ofono.Manager + org.freedesktop.DBus.Introspectable + org.freedesktop.DBus.ObjectManager +``` + +The manager object is always available and allows applications to get access to +the connected audio gateways. + +The object path is set to `/` when ofono service compatibility is enabled, +in which case the service name `org.ofono` is also registered instead of +`org.pipewire.Telephony`. + +The methods and signals below are made available on the `org.ofono.Manager` +interface, for compatibility. AudioGateway objects are normally announced via +the standard DBus ObjectManager interface. + +### Methods + +`array{object,dict} GetModems()` + +Get an array of AudioGateway objects and properties that represents the +currently connected audio gateways. + +### Signals + +`ModemAdded(object path, dict properties)` + +Signal that is sent when a new audio gateway is added. It contains the object +path of the new audio gateway and also its properties. + +`ModemRemoved(object path)` + +Signal that is sent when an audio gateway has been removed. The object path is +no longer accessible after this signal and only emitted for reference. + +## AudioGateway Object + +``` +Service org.pipewire.Telephony + or org.ofono +Object path /org/pipewire/Telephony/{ag0,ag1,...} +Implements org.pipewire.Telephony.AudioGateway1 + org.ofono.VoiceCallManager + org.freedesktop.DBus.Introspectable + org.freedesktop.DBus.ObjectManager +``` + +Audio gateway objects represent the currently connected AG devices (typically +mobile phones). + +The methods, signals and properties listed below are made available on both +`org.pipewire.Telephony.AudioGateway1` and +`org.ofono.VoiceCallManager` interfaces, unless explicitly documented otherwise. + +Call objects are announced via both the standard DBus ObjectManager interface +and via the `org.ofono.VoiceCallManager` interface, for compatibility. + +### Methods + +`array{object,dict} GetCalls()` + +Get an array of call object paths and properties that represents the currently +present calls. + +This method call should only be used once when an application starts up. +Further call additions and removal shall be monitored via CallAdded and +CallRemoved signals. + +NOTE: This method is implemented only on the `org.ofono.VoiceCallManager` +interface, for compatibility. Call announcements are normally made available via +the standard `org.freedesktop.DBus.ObjectManager` interface. + +`object Dial(string number)` + +Initiates a new outgoing call. Returns the object path to the newly created +call. + +The number must be a string containing the following characters: +`[0-9+*#,ABCD]{1,80}` In other words, it must be a non-empty string consisting +of 1 to 80 characters. The character set can contain numbers, `+`, `*`, `#`, `,` +and the letters `A` to `D`. Besides this sanity checking no further number +validation is performed. It is assumed that the gateway and/or the network will +perform further validation. + +NOTE: If an active call (single or multiparty) exists, then it is automatically +put on hold if the dial procedure is successful. + +Possible Errors: + * org.pipewire.Telephony.Error.InvalidState + * org.freedesktop.DBus.Error.InvalidArgs + * org.freedesktop.DBus.Error.Failed + +`void SwapCalls()` + +Swaps Active and Held calls. The effect of this is that all calls (0 or more +including calls in a multi-party conversation) that were Active are now Held, +and all calls (0 or more) that were Held are now Active. + +GSM specification does not allow calls to be swapped in the case where Held, +Active and Waiting calls exist. Some modems implement this anyway, thus it is +manufacturer specific whether this method will succeed in the case of Held, +Active and Waiting calls. + +Possible Errors: + * org.pipewire.Telephony.Error.InvalidState + * org.freedesktop.DBus.Error.Failed + +`void ReleaseAndAnswer()` + +Releases currently active call (0 or more) and answers the currently waiting +call. Please note that if the current call is a multiparty call, then all +parties in the multi-party call will be released. + +Possible Errors: + * org.pipewire.Telephony.Error.InvalidState + * org.freedesktop.DBus.Error.Failed + +`void ReleaseAndSwap()` + +Releases currently active call (0 or more) and activates any currently held +calls. Please note that if the current call is a multiparty call, then all +parties in the multi-party call will be released. + +Possible Errors: + * org.pipewire.Telephony.Error.InvalidState + * org.freedesktop.DBus.Error.Failed + +`void HoldAndAnswer()` + +Puts the current call (including multi-party calls) on hold and answers the +currently waiting call. Calling this function when a user already has a both +Active and Held calls is invalid, since in GSM a user can have only a single +Held call at a time. + +Possible Errors: + * org.pipewire.Telephony.Error.InvalidState + * org.freedesktop.DBus.Error.Failed + +`void HangupAll()` + +Releases all calls except waiting calls. This includes multiparty calls. + +Possible Errors: + * org.pipewire.Telephony.Error.InvalidState + * org.freedesktop.DBus.Error.Failed + +`array{object} CreateMultiparty()` + +Joins active and held calls together into a multi-party call. If one of the +calls is already a multi-party call, then the other call is added to the +multiparty conversation. Returns the new list of calls participating in the +multiparty call. + +There can only be one subscriber controlled multi-party call according to the +GSM specification. + +Possible Errors: + * org.pipewire.Telephony.Error.InvalidState + * org.freedesktop.DBus.Error.Failed + +`void SendTones(string tones)` + +Sends the DTMF tones to the network. The tones have a fixed duration. Tones +can be one of: '0' - '9', '*', '#', 'A', 'B', 'C', 'D'. The last four are +typically not used in normal circumstances. + +Possible Errors: + * org.pipewire.Telephony.Error.InvalidState + * org.freedesktop.DBus.Error.InvalidArgs + * org.freedesktop.DBus.Error.Failed + +### Signals + +`CallAdded(object path, dict properties)` + +Signal that is sent when a new call is added. It contains the object path of +the new voice call and also its properties. + +NOTE: This method is implemented only on the `org.ofono.VoiceCallManager` +interface, for compatibility. Call announcements are normally made available via +the standard `org.freedesktop.DBus.ObjectManager` interface. + +`CallRemoved(object path)` + +Signal that is sent when a voice call has been released. The object path is no +longer accessible after this signal and only emitted for reference. + +NOTE: This method is implemented only on the `org.ofono.VoiceCallManager` +interface, for compatibility. Call announcements are normally made available via +the standard `org.freedesktop.DBus.ObjectManager` interface. + +## Call Object + +``` +Service org.pipewire.Telephony + or org.ofono +Object path /org/pipewire/Telephony/{ag0,ag1,...}/{call0,call1,...} +Implements org.pipewire.Telephony.Call1 + org.ofono.VoiceCall + org.freedesktop.DBus.Introspectable + org.freedesktop.DBus.Properties +``` + +Call objects represent active calls and allow managing them. + +The methods, signals and properties listed below are made available on both +`org.pipewire.Telephony.Call1` and `org.ofono.VoiceCall` +interfaces, unless explicitly documented otherwise. + +### Methods + +`dict GetProperties()` + +Returns all properties for this object. See the properties section for available +properties. + +NOTE: This method is implemented only on the `org.ofono.VoiceCall` interface, +for compatibility. Properties are normally made available via the standard +`org.freedesktop.DBus.Properties` interface. + +`void Answer()` + +Answers an incoming call. Only valid if the state of the call is "incoming". + +Possible Errors: + * org.pipewire.Telephony.Error.InvalidState + * org.freedesktop.DBus.Error.Failed + +`void Hangup()` + +Hangs up the call. + +For an incoming call, the call is hung up using ATH or equivalent. For a +waiting call, the remote party is notified by using the User Determined User +Busy (UDUB) condition. This is generally implemented using CHLD=0. + +Please note that the GSM specification does not allow the release of a held call +when a waiting call exists. This is because 27.007 allows CHLD=1X to operate +only on active calls. Hence a held call cannot be hung up without affecting the +state of the incoming call (e.g. using other CHLD alternatives). Most +manufacturers provide vendor extensions that do allow the state of the held call +to be modified using CHLD=1X or equivalent. It should be noted that Bluetooth +HFP specifies the classic 27.007 behavior and does not allow CHLD=1X to modify +the state of held calls. + +Based on the discussion above, it should also be noted that releasing a +particular party of a held multiparty call might not be possible on some +implementations. It is recommended for the applications to structure their UI +accordingly. + +NOTE: Releasing active calls does not produce side-effects. That is the state +of held or waiting calls is not affected. As an exception, in the case where a +single active call and a waiting call are present, releasing the active call +will result in the waiting call transitioning to the 'incoming' state. + +Possible Errors: + * org.pipewire.Telephony.Error.InvalidState + * org.freedesktop.DBus.Error.Failed + +### Signals + +`PropertyChanged(string property, variant value)` + +Signal is emitted whenever a property has changed. The new value is passed as +the signal argument. + +NOTE: This method is implemented only on the `org.ofono.VoiceCall` interface, +for compatibility. Properties are normally made available via the standard +`org.freedesktop.DBus.Properties` interface. + +### Properties + +`string LineIdentification [readonly]` + +Contains the Line Identification information returned by the network, if +present. For incoming calls this is effectively the CLIP. For outgoing calls +this attribute will hold the dialed number, or the COLP if received by the +audio gateway. + +Please note that COLP may be different from the dialed number. A special +"withheld" value means the remote party refused to provide caller ID and the +"override category" option was not provisioned for the current subscriber. + +`string IncomingLine [readonly, optional]` + +Contains the Called Line Identification information returned by the network. +This is only available for incoming calls and indicates the local subscriber +number which was dialed by the remote party. This is useful for subscribers +which have a multiple line service with their network provider and would like to +know what line the call is coming in on. + +`string Name [readonly]` + +Contains the Name Identification information returned by the network, if +present. + +`boolean Multiparty [readonly]` + +Contains the indication if the call is part of a multiparty call or not. + +Notifications if a call becomes part or leaves a multiparty call are sent. + +`string State [readonly]` + +Contains the state of the current call. The state can be one of: + - "active" - The call is active + - "held" - The call is on hold + - "dialing" - The call is being dialed + - "alerting" - The remote party is being alerted + - "incoming" - Incoming call in progress + - "waiting" - Call is waiting + - "disconnected" - No further use of this object is allowed, it will be + destroyed shortly diff --git a/spa/plugins/bluez5/a2dp-codec-aac.c b/spa/plugins/bluez5/a2dp-codec-aac.c index 5420e914..bf0b735f 100644 --- a/spa/plugins/bluez5/a2dp-codec-aac.c +++ b/spa/plugins/bluez5/a2dp-codec-aac.c @@ -40,6 +40,9 @@ struct impl { uint32_t rate; uint32_t channels; int samplesize; + + uint32_t enc_delay; + uint32_t dec_delay; }; static bool eld_supported(void) @@ -68,7 +71,7 @@ done: } static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, - uint8_t caps[A2DP_MAX_CAPS_SIZE]) + const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { const a2dp_aac_t a2dp_aac = { .object_type = @@ -364,7 +367,15 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, if (res != AACENC_OK) goto error; - if (conf->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_ELD) { + /* If object type has multiple bits set (invalid per spec, see above), + * assume the device usually means AAC-LC. + */ + if (conf->object_type & (AAC_OBJECT_TYPE_MPEG2_AAC_LC | + AAC_OBJECT_TYPE_MPEG4_AAC_LC)) { + res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_AAC_LC); + if (res != AACENC_OK) + goto error; + } else if (conf->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_ELD) { res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_ER_AAC_ELD); if (res != AACENC_OK) goto error; @@ -372,11 +383,6 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, res = aacEncoder_SetParam(this->aacenc, AACENC_SBR_MODE, 1); if (res != AACENC_OK) goto error; - } else if (conf->object_type & (AAC_OBJECT_TYPE_MPEG2_AAC_LC | - AAC_OBJECT_TYPE_MPEG4_AAC_LC)) { - res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_AAC_LC); - if (res != AACENC_OK) - goto error; } else { res = -EINVAL; goto error; @@ -440,6 +446,8 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, if (res != AACENC_OK) goto error; + this->enc_delay = enc_info.nDelay; + this->codesize = enc_info.frameLength * this->channels * this->samplesize; this->aacdec = aacDecoder_Open(TT_MP4_LATM_MCP1, 1); @@ -468,6 +476,8 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, } #endif + this->dec_delay = 0; + return this; error: @@ -647,6 +657,21 @@ static int codec_increase_bitpool(void *data) return codec_change_bitrate(this, (this->cur_bitrate * 4) / 3); } +static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) +{ + struct impl *this = data; + + if (encoder) + *encoder = this->enc_delay; + + if (decoder) { + CStreamInfo *info = aacDecoder_GetStreamInfo(this->aacdec); + if (info) + this->dec_delay = info->outputDelay; + *decoder = this->dec_delay; + } +} + static void codec_set_log(struct spa_log *global_log) { log = global_log; @@ -675,6 +700,7 @@ const struct media_codec a2dp_codec_aac = { .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, .set_log = codec_set_log, + .get_delay = codec_get_delay, }; const struct media_codec a2dp_codec_aac_eld = { @@ -700,6 +726,7 @@ const struct media_codec a2dp_codec_aac_eld = { .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, .set_log = codec_set_log, + .get_delay = codec_get_delay, }; MEDIA_CODEC_EXPORT_DEF( diff --git a/spa/plugins/bluez5/a2dp-codec-aptx.c b/spa/plugins/bluez5/a2dp-codec-aptx.c index 2682d9d1..e10691e7 100644 --- a/spa/plugins/bluez5/a2dp-codec-aptx.c +++ b/spa/plugins/bluez5/a2dp-codec-aptx.c @@ -74,7 +74,7 @@ static inline size_t codec_get_caps_size(const struct media_codec *codec) } static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, - uint8_t caps[A2DP_MAX_CAPS_SIZE]) + const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { size_t actual_conf_size = codec_get_caps_size(codec); const a2dp_aptx_t a2dp_aptx = { @@ -449,6 +449,14 @@ static int codec_decode(void *data, return res; } +static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) +{ + if (encoder) + *encoder = 90; + if (decoder) + *decoder = 0; +} + /* * mSBC duplex codec * @@ -627,6 +635,7 @@ const struct media_codec a2dp_codec_aptx = { .decode = codec_decode, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, + .get_delay = codec_get_delay, }; @@ -650,6 +659,7 @@ const struct media_codec a2dp_codec_aptx_hd = { .decode = codec_decode, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, + .get_delay = codec_get_delay, }; #define APTX_LL_COMMON_DEFS \ @@ -665,7 +675,8 @@ const struct media_codec a2dp_codec_aptx_hd = { .start_encode = codec_start_encode, \ .encode = codec_encode, \ .reduce_bitpool = codec_reduce_bitpool, \ - .increase_bitpool = codec_increase_bitpool + .increase_bitpool = codec_increase_bitpool, \ + .get_delay = codec_get_delay const struct media_codec a2dp_codec_aptx_ll_0 = { diff --git a/spa/plugins/bluez5/a2dp-codec-caps.h b/spa/plugins/bluez5/a2dp-codec-caps.h index 2bb883ec..d775ce10 100644 --- a/spa/plugins/bluez5/a2dp-codec-caps.h +++ b/spa/plugins/bluez5/a2dp-codec-caps.h @@ -486,4 +486,6 @@ typedef struct { uint8_t data; } __attribute__ ((packed)) a2dp_opus_g_t; +#define ASHA_CODEC_G722 0x63 + #endif diff --git a/spa/plugins/bluez5/a2dp-codec-faststream.c b/spa/plugins/bluez5/a2dp-codec-faststream.c index ce70e43f..bbf9e96a 100644 --- a/spa/plugins/bluez5/a2dp-codec-faststream.c +++ b/spa/plugins/bluez5/a2dp-codec-faststream.c @@ -7,12 +7,10 @@ #include <stddef.h> #include <errno.h> #include <arpa/inet.h> -#if __BYTE_ORDER != __LITTLE_ENDIAN -#include <byteswap.h> -#endif #include <spa/param/audio/format.h> #include <spa/param/audio/format-utils.h> +#include <spa/utils/endian.h> #include <sbc/sbc.h> @@ -32,7 +30,7 @@ struct duplex_impl { }; static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, - uint8_t caps[A2DP_MAX_CAPS_SIZE]) + const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { const a2dp_faststream_t a2dp_faststream = { .info = codec->vendor, @@ -556,6 +554,14 @@ static int duplex_decode(void *data, return res; } +static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) +{ + if (encoder) + *encoder = 73; + if (decoder) + *decoder = 0; +} + /* Voice channel SBC, not a real A2DP codec */ static const struct media_codec duplex_codec = { .codec_id = A2DP_CODEC_VENDOR, @@ -592,7 +598,8 @@ static const struct media_codec duplex_codec = { .start_encode = codec_start_encode, \ .encode = codec_encode, \ .reduce_bitpool = codec_reduce_bitpool, \ - .increase_bitpool = codec_increase_bitpool + .increase_bitpool = codec_increase_bitpool, \ + .get_delay = codec_get_delay const struct media_codec a2dp_codec_faststream = { FASTSTREAM_COMMON_DEFS, diff --git a/spa/plugins/bluez5/a2dp-codec-lc3plus.c b/spa/plugins/bluez5/a2dp-codec-lc3plus.c index 98868899..05a3aa20 100644 --- a/spa/plugins/bluez5/a2dp-codec-lc3plus.c +++ b/spa/plugins/bluez5/a2dp-codec-lc3plus.c @@ -7,12 +7,10 @@ #include <stddef.h> #include <errno.h> #include <arpa/inet.h> -#if __BYTE_ORDER != __LITTLE_ENDIAN -#include <byteswap.h> -#endif #include <spa/param/audio/format.h> #include <spa/param/audio/format-utils.h> +#include <spa/utils/endian.h> #ifdef HAVE_LC3PLUS_H #include <lc3plus.h> @@ -67,7 +65,7 @@ struct impl { }; static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, - uint8_t caps[A2DP_MAX_CAPS_SIZE]) + const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { const a2dp_lc3plus_hr_t a2dp_lc3plus_hr = { .info = codec->vendor, diff --git a/spa/plugins/bluez5/a2dp-codec-ldac.c b/spa/plugins/bluez5/a2dp-codec-ldac.c index 3ec20b72..f5d0b155 100644 --- a/spa/plugins/bluez5/a2dp-codec-ldac.c +++ b/spa/plugins/bluez5/a2dp-codec-ldac.c @@ -59,7 +59,8 @@ struct impl { int frame_count; }; -static int codec_fill_caps(const struct media_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, + const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { static const a2dp_ldac_t a2dp_ldac = { .info.vendor_id = LDAC_VENDOR_ID, @@ -551,6 +552,25 @@ static int codec_encode(void *data, return src_used; } +static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) +{ + struct impl *this = data; + + if (encoder) { + switch (this->frequency) { + case 96000: + case 88200: + *encoder = 256; + break; + default: + *encoder = 128; + break; + } + } + if (decoder) + *decoder = 0; +} + const struct media_codec a2dp_codec_ldac = { .id = SPA_BLUETOOTH_AUDIO_CODEC_LDAC, .codec_id = A2DP_CODEC_VENDOR, @@ -577,6 +597,7 @@ const struct media_codec a2dp_codec_ldac = { .encode = codec_encode, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, + .get_delay = codec_get_delay, }; MEDIA_CODEC_EXPORT_DEF( diff --git a/spa/plugins/bluez5/a2dp-codec-opus-g.c b/spa/plugins/bluez5/a2dp-codec-opus-g.c index df1ad776..85a36bf8 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus-g.c +++ b/spa/plugins/bluez5/a2dp-codec-opus-g.c @@ -8,15 +8,13 @@ #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/utils/endian.h> #include <spa/param/audio/format.h> #include <spa/param/audio/format-utils.h> @@ -28,6 +26,7 @@ static struct spa_log *log; struct dec_data { + int32_t delay; }; struct enc_data { @@ -39,6 +38,8 @@ struct enc_data { int frame_dms; int bitrate; int packet_size; + + int32_t delay; }; struct impl { @@ -55,7 +56,7 @@ struct impl { }; static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, - uint8_t caps[A2DP_MAX_CAPS_SIZE]) + const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { a2dp_opus_g_t conf = { .info = codec->vendor, @@ -336,6 +337,8 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, opus_encoder_ctl(this->enc, OPUS_SET_BITRATE(this->e.bitrate)); + opus_encoder_ctl(this->enc, OPUS_GET_LOOKAHEAD(&this->e.delay)); + /* * Setup decoder */ @@ -345,6 +348,8 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, goto error; } + opus_decoder_ctl(this->dec, OPUS_GET_LOOKAHEAD(&this->d.delay)); + return this; error_errno: @@ -489,6 +494,16 @@ static int codec_increase_bitpool(void *data) return 0; } +static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) +{ + struct impl *this = data; + + if (encoder) + *encoder = this->e.delay; + if (decoder) + *decoder = this->d.delay; +} + static void codec_set_log(struct spa_log *global_log) { log = global_log; @@ -518,6 +533,7 @@ const struct media_codec a2dp_codec_opus_g = { .name = "opus_g", .description = "Opus", .fill_caps = codec_fill_caps, + .get_delay = codec_get_delay, }; MEDIA_CODEC_EXPORT_DEF( diff --git a/spa/plugins/bluez5/a2dp-codec-opus.c b/spa/plugins/bluez5/a2dp-codec-opus.c index 651d82d5..e65b62fa 100644 --- a/spa/plugins/bluez5/a2dp-codec-opus.c +++ b/spa/plugins/bluez5/a2dp-codec-opus.c @@ -8,13 +8,11 @@ #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/endian.h> #include <spa/utils/string.h> #include <spa/utils/dict.h> #include <spa/param/audio/format.h> @@ -82,6 +80,8 @@ struct dec_data { int fragment_size; int fragment_count; uint8_t fragment[OPUS_05_MAX_BYTES]; + + int32_t delay; }; struct abr { @@ -121,6 +121,8 @@ struct enc_data { int frame_dms; int application; + + int32_t delay; }; struct impl { @@ -554,7 +556,7 @@ static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direc } static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, - uint8_t caps[A2DP_MAX_CAPS_SIZE]) + const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { a2dp_opus_05_t a2dp_opus_05 = { .info = codec->vendor, @@ -1007,6 +1009,7 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, this->e.samples = this->e.frame_dms * this->samplerate / 10000; this->e.codesize = this->e.samples * (int)this->channels * sizeof(float); + opus_multistream_encoder_ctl(this->enc, OPUS_GET_LOOKAHEAD(&this->e.delay)); /* * Setup decoder @@ -1022,6 +1025,8 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, goto error; } + opus_multistream_decoder_ctl(this->dec, OPUS_GET_LOOKAHEAD(&this->d.delay)); + return this; error_errno: @@ -1327,6 +1332,16 @@ static int codec_increase_bitpool(void *data) return 0; } +static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) +{ + struct impl *this = data; + + if (encoder) + *encoder = this->e.delay; + if (decoder) + *decoder = this->d.delay; +} + static void codec_set_log(struct spa_log *global_log) { log = global_log; @@ -1349,7 +1364,8 @@ static void codec_set_log(struct spa_log *global_log) .encode = codec_encode, \ .reduce_bitpool = codec_reduce_bitpool, \ .increase_bitpool = codec_increase_bitpool, \ - .set_log = codec_set_log + .set_log = codec_set_log, \ + .get_delay = codec_get_delay #define OPUS_05_COMMON_FULL_DEFS \ OPUS_05_COMMON_DEFS, \ diff --git a/spa/plugins/bluez5/a2dp-codec-sbc.c b/spa/plugins/bluez5/a2dp-codec-sbc.c index 3397a8f5..fc55a031 100644 --- a/spa/plugins/bluez5/a2dp-codec-sbc.c +++ b/spa/plugins/bluez5/a2dp-codec-sbc.c @@ -29,10 +29,12 @@ struct impl { int min_bitpool; int max_bitpool; + + uint32_t enc_delay; }; static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, - uint8_t caps[A2DP_MAX_CAPS_SIZE]) + const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]) { static const a2dp_sbc_t a2dp_sbc = { .frequency = @@ -497,9 +499,11 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, switch (conf->subbands) { case SBC_SUBBANDS_4: this->sbc.subbands = SBC_SB_4; + this->enc_delay = 37; break; case SBC_SUBBANDS_8: this->sbc.subbands = SBC_SB_8; + this->enc_delay = 73; break; default: res = -EINVAL; @@ -618,6 +622,16 @@ static int codec_decode(void *data, return res; } +static void codec_get_delay(void *data, uint32_t *encoder, uint32_t *decoder) +{ + struct impl *this = data; + + if (encoder) + *encoder = this->enc_delay; + if (decoder) + *decoder = 0; +} + const struct media_codec a2dp_codec_sbc = { .id = SPA_BLUETOOTH_AUDIO_CODEC_SBC, .codec_id = A2DP_CODEC_SBC, @@ -638,6 +652,7 @@ const struct media_codec a2dp_codec_sbc = { .decode = codec_decode, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, + .get_delay = codec_get_delay, }; const struct media_codec a2dp_codec_sbc_xq = { @@ -661,6 +676,7 @@ const struct media_codec a2dp_codec_sbc_xq = { .decode = codec_decode, .reduce_bitpool = codec_reduce_bitpool, .increase_bitpool = codec_increase_bitpool, + .get_delay = codec_get_delay, }; MEDIA_CODEC_EXPORT_DEF( diff --git a/spa/plugins/bluez5/asha-codec-g722.c b/spa/plugins/bluez5/asha-codec-g722.c new file mode 100644 index 00000000..1043b4fe --- /dev/null +++ b/spa/plugins/bluez5/asha-codec-g722.c @@ -0,0 +1,176 @@ +/* Spa ASHA G722 codec */ +/* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include <spa/param/audio/format.h> +#include <spa/utils/dict.h> +#include <spa/debug/log.h> + +#include "rtp.h" +#include "media-codecs.h" +#include "g722/g722_enc_dec.h" + +#define ASHA_HEADER_SZ 1 /* 1 byte sequence number */ +#define ASHA_ENCODED_PKT_SZ 160 + +static struct spa_log *spalog; + +struct impl { + g722_encode_state_t encode; + unsigned int codesize; +}; + +static int codec_reduce_bitpool(void *data) +{ + return -ENOTSUP; +} + +static int codec_increase_bitpool(void *data) +{ + return -ENOTSUP; +} + +static int codec_abr_process (void *data, size_t unsent) +{ + return -ENOTSUP; +} + +static int codec_get_block_size(void *data) +{ + struct impl *this = data; + return this->codesize; +} + +static int codec_start_encode (void *data, + void *dst, size_t dst_size, uint16_t seqnum, uint32_t timestamp) +{ + /* Payload for ASHA must be preceded by 1-byte sequence number */ + *(uint8_t *)dst = seqnum % 256; + + return 1; +} + +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) +{ + struct spa_pod_frame f[1]; + uint32_t position[1]; + + 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_S16), + 0); + spa_pod_builder_add(b, + SPA_FORMAT_AUDIO_rate, SPA_POD_Int(16000), + 0); + + spa_pod_builder_add(b, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1), + 0); + position[0] = SPA_AUDIO_CHANNEL_MONO; + spa_pod_builder_add(b, + SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1), + SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t), + SPA_TYPE_Id, 1, position), + 0); + + *param = spa_pod_builder_pop(b, &f[0]); + + return *param == NULL ? -EIO : 1; +} + +static void codec_deinit(void *data) +{ + return; +} + +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) +{ + struct impl *this; + + if ((this = calloc(1, sizeof(struct impl))) == NULL) + return NULL; + + g722_encode_init(&this->encode, 64000, G722_PACKED); + + /* + * G722 has a compression ratio of 4. Considering 160 bytes of encoded + * payload, we need 640 bytes for generating an encoded frame. + */ + this->codesize = ASHA_ENCODED_PKT_SZ * 4; + + spa_log_debug(spalog, "Codec initialized"); + + return this; +} + +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; + size_t src_sz; + int ret; + + if (src_size < this->codesize) { + spa_log_trace(spalog, "Insufficient bytes for encoding, %zd", src_size); + return 0; + } + + if (dst_size < (ASHA_HEADER_SZ + ASHA_ENCODED_PKT_SZ)) { + spa_log_trace(spalog, "No space for encoded output, %zd", dst_size); + return 0; + } + + src_sz = (src_size > this->codesize) ? this->codesize : src_size; + + ret = g722_encode(&this->encode, dst, src, src_sz / 2 /* S16LE */); + if (ret < 0) { + spa_log_error(spalog, "encode error: %d", ret); + return -EIO; + } + + *dst_out = ret; + *need_flush = NEED_FLUSH_ALL; + + return src_sz; +} + +static void codec_set_log(struct spa_log *global_log) +{ + spalog = global_log; + spa_log_topic_init(spalog, &codec_plugin_log_topic); +} + +const struct media_codec asha_codec_g722 = { + .id = SPA_BLUETOOTH_AUDIO_CODEC_G722, + .codec_id = ASHA_CODEC_G722, + .name = "g722", + .asha = true, + .description = "G722", + .fill_caps = NULL, + .enum_config = codec_enum_config, + .init = codec_init, + .deinit = codec_deinit, + .get_block_size = codec_get_block_size, + .start_encode = codec_start_encode, + .encode = codec_encode, + .abr_process = codec_abr_process, + .reduce_bitpool = codec_reduce_bitpool, + .increase_bitpool = codec_increase_bitpool, + .set_log = codec_set_log, +}; + +MEDIA_CODEC_EXPORT_DEF( + "g722", + &asha_codec_g722 +); diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c index 33deda7c..35969690 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -4,6 +4,9 @@ /* SPDX-License-Identifier: MIT */ #include <errno.h> +#include <stdbool.h> +#include <string.h> +#include <sys/poll.h> #include <unistd.h> #include <stdarg.h> #include <sys/types.h> @@ -36,6 +39,7 @@ #include "modemmanager.h" #include "upower.h" +#include "telephony.h" SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.native"); #undef SPA_LOG_TOPIC_DEFAULT @@ -43,6 +47,7 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.native"); #define PROP_KEY_ROLES "bluez5.roles" #define PROP_KEY_HEADSET_ROLES "bluez5.headset-roles" +#define PROP_KEY_HFP_DISABLE_NREC "bluez5.hfp-hf.disable-nrec" #define HFP_CODEC_SWITCH_INITIAL_TIMEOUT_MSEC 5000 #define HFP_CODEC_SWITCH_TIMEOUT_MSEC 20000 @@ -94,6 +99,7 @@ struct impl { #define DEFAULT_ENABLED_PROFILES (SPA_BT_PROFILE_HFP_HF | SPA_BT_PROFILE_HFP_AG) enum spa_bt_profile enabled_profiles; + bool hfp_disable_nrec; struct spa_source sco; @@ -108,6 +114,7 @@ struct impl { void *modemmanager; struct spa_source *ring_timer; void *upower; + struct spa_bt_telephony *telephony; }; struct transport_data { @@ -123,6 +130,11 @@ enum hfp_hf_state { hfp_hf_cind1, hfp_hf_cind2, hfp_hf_cmer, + hfp_hf_chld, + hfp_hf_clip, + hfp_hf_ccwa, + hfp_hf_cmee, + hfp_hf_nrec, hfp_hf_slc1, hfp_hf_slc2, hfp_hf_vgs, @@ -142,6 +154,16 @@ struct rfcomm_volume { int hw_volume; }; +struct rfcomm_call_data { + struct rfcomm *rfcomm; + struct spa_bt_telephony_call *call; +}; + +struct rfcomm_cmd { + struct spa_list link; + char* cmd; +}; + struct rfcomm { struct spa_list link; struct spa_source source; @@ -168,11 +190,20 @@ struct rfcomm { unsigned int cind_call_notify:1; unsigned int extended_error_reporting:1; unsigned int clip_notify:1; + unsigned int hfp_hf_3way:1; + unsigned int hfp_hf_nrec:1; + unsigned int hfp_hf_clcc:1; + unsigned int hfp_hf_cme:1; + unsigned int hfp_hf_cmd_in_progress:1; + unsigned int hfp_hf_in_progress:1; + unsigned int chld_supported:1; enum hfp_hf_state hf_state; enum hsp_hs_state hs_state; unsigned int codec; uint32_t cind_enabled_indicators; char *hf_indicators[MAX_HF_INDICATORS]; + struct spa_bt_telephony_ag *telephony_ag; + struct spa_list hfp_hf_commands; #endif }; @@ -193,14 +224,25 @@ static void transport_destroy(void *data) rfcomm->transport = NULL; } +static void transport_state_changed (void *data, enum spa_bt_transport_state old, + enum spa_bt_transport_state state) +{ + struct rfcomm *rfcomm = data; + if (rfcomm->telephony_ag) { + rfcomm->telephony_ag->transport.state = state; + telephony_ag_transport_notify_updated_props(rfcomm->telephony_ag); + } +} + static const struct spa_bt_transport_events transport_events = { SPA_VERSION_BT_TRANSPORT_EVENTS, .destroy = transport_destroy, + .state_changed = transport_state_changed, }; static const struct spa_bt_transport_implementation sco_transport_impl; -static int rfcomm_new_transport(struct rfcomm *rfcomm) +static int rfcomm_new_transport(struct rfcomm *rfcomm, int codec) { struct impl *backend = rfcomm->backend; struct spa_bt_transport *t = NULL; @@ -229,7 +271,7 @@ static int rfcomm_new_transport(struct rfcomm *rfcomm) t->backend = &backend->this; t->n_channels = 1; t->channels[0] = SPA_AUDIO_CHANNEL_MONO; - t->codec = HFP_AUDIO_CODEC_CVSD; + t->codec = codec; td = t->user_data; td->rfcomm = rfcomm; @@ -252,6 +294,12 @@ static int rfcomm_new_transport(struct rfcomm *rfcomm) spa_bt_transport_add_listener(t, &rfcomm->transport_listener, &transport_events, rfcomm); + if (rfcomm->telephony_ag) { + rfcomm->telephony_ag->transport.codec = codec; + rfcomm->telephony_ag->transport.state = SPA_BT_TRANSPORT_STATE_IDLE; + telephony_ag_transport_notify_updated_props(rfcomm->telephony_ag); + } + rfcomm->transport = t; return 0; @@ -267,6 +315,10 @@ static void volume_sync_stop_timer(struct rfcomm *rfcomm); static void rfcomm_free(struct rfcomm *rfcomm) { codec_switch_stop_timer(rfcomm); + if (rfcomm->telephony_ag) { + telephony_ag_destroy(rfcomm->telephony_ag); + rfcomm->telephony_ag = NULL; + } for (int i = 0; i < MAX_HF_INDICATORS; i++) { if (rfcomm->hf_indicators[i]) { free(rfcomm->hf_indicators[i]); @@ -300,7 +352,7 @@ static void rfcomm_free(struct rfcomm *rfcomm) /* from HF/HS to AG */ SPA_PRINTF_FUNC(2, 3) -static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format, ...) +static ssize_t rfcomm_send_cmd(struct rfcomm *rfcomm, const char *format, ...) { struct impl *backend = rfcomm->backend; char message[RFCOMM_MESSAGE_MAX_LENGTH + 1]; @@ -317,6 +369,14 @@ static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format, if (len > RFCOMM_MESSAGE_MAX_LENGTH) return -E2BIG; + if (rfcomm->hfp_hf_cmd_in_progress) { + spa_log_debug(backend->log, "Command in progress, postponing: %s", message); + struct rfcomm_cmd *cmd = calloc(1, sizeof(struct rfcomm_cmd)); + cmd->cmd = strndup(message, len); + spa_list_append(&rfcomm->hfp_hf_commands, &cmd->link); + return 0; + } + spa_log_debug(backend->log, "RFCOMM >> %s", message); /* @@ -337,6 +397,8 @@ static ssize_t rfcomm_send_cmd(const struct rfcomm *rfcomm, const char *format, spa_log_error(backend->log, "RFCOMM write error: %s", strerror(errno)); } + rfcomm->hfp_hf_cmd_in_progress = true; + return len; } @@ -851,7 +913,7 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) /* send reply to HF with the features supported by Audio Gateway (=computer) */ ag_features |= mm_supported_features(); - ag_features |= SPA_BT_HFP_AG_FEATURE_HF_INDICATORS; + ag_features |= SPA_BT_HFP_AG_FEATURE_HF_INDICATORS | SPA_BT_HFP_AG_FEATURE_ESCO_S4; rfcomm_send_reply(rfcomm, "+BRSF: %u", ag_features); rfcomm_send_reply(rfcomm, "OK"); } else if (spa_strstartswith(buf, "AT+BAC=")) { @@ -916,10 +978,9 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) rfcomm_send_reply(rfcomm, "+BCS: 2"); codec_switch_start_timer(rfcomm, HFP_CODEC_SWITCH_INITIAL_TIMEOUT_MSEC); } else { - if (rfcomm_new_transport(rfcomm) < 0) { + if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) { // TODO: We should manage the missing transport } else { - rfcomm->transport->codec = HFP_AUDIO_CODEC_CVSD; spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); rfcomm_emit_volume_changed(rfcomm, -1, SPA_BT_VOLUME_INVALID); } @@ -958,14 +1019,13 @@ static bool rfcomm_hfp_ag(struct rfcomm *rfcomm, char* buf) spa_log_debug(backend->log, "RFCOMM selected_codec = %i", selected_codec); /* Recreate transport, since previous connection may now be invalid */ - if (rfcomm_new_transport(rfcomm) < 0) { + if (rfcomm_new_transport(rfcomm, selected_codec) < 0) { // TODO: We should manage the missing transport rfcomm_send_error(rfcomm, CMEE_AG_FAILURE); if (was_switching_codec) spa_bt_device_emit_codec_switched(rfcomm->device, -ENOMEM); return true; } - rfcomm->transport->codec = selected_codec; spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); rfcomm_emit_volume_changed(rfcomm, -1, SPA_BT_VOLUME_INVALID); @@ -1221,15 +1281,606 @@ next_indicator: return true; } +static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token); + +static bool hfp_hf_wait_for_reply(struct rfcomm *rfcomm, char *buf, size_t len) +{ + struct impl *backend = rfcomm->backend; + struct pollfd fds[1]; + bool reply_found = false; + + fds[0].fd = rfcomm->source.fd; + fds[0].events = POLLIN; + while (!reply_found) { + int ret; + char tmp_buf[512]; + ssize_t tmp_len; + char *ptr, *token; + + ret = poll(fds, 1, 2000); + if (ret < 0) { + spa_log_error(backend->log, "RFCOMM poll error: %s", strerror(errno)); + goto done; + } else if (ret == 0) { + spa_log_error(backend->log, "RFCOMM poll timeout"); + goto done; + } + + if (fds[0].revents & (POLLHUP | POLLERR)) { + spa_log_info(backend->log, "lost RFCOMM connection."); + rfcomm_free(rfcomm); + return false; + } + + if (fds[0].revents & POLLIN) { + tmp_len = read(rfcomm->source.fd, tmp_buf, sizeof(tmp_buf) - 1); + if (tmp_len < 0) { + spa_log_error(backend->log, "RFCOMM read error: %s", strerror(errno)); + goto done; + } + tmp_buf[tmp_len] = '\0'; + + /* Relaxed parsing of \r\n<REPLY>\r\n */ + ptr = tmp_buf; + while ((token = strsep(&ptr, "\r"))) { + size_t ptr_len; + + /* Skip leading and trailing \n */ + while (*token == '\n') + ++token; + for (ptr_len = strlen(token); ptr_len > 0 && token[ptr_len - 1] == '\n'; --ptr_len) + token[ptr_len - 1] = '\0'; + + /* Skip empty */ + if (*token == '\0' /*&& buf == NULL*/) + continue; + + spa_log_debug(backend->log, "RFCOMM event: %s", token); + if (spa_strstartswith(token, "OK") || spa_strstartswith(token, "ERROR") || + spa_strstartswith(token, "+CME ERROR:")) { + spa_log_debug(backend->log, "RFCOMM reply found: %s", token); + reply_found = true; + strncpy(buf, token, len); + buf[len-1] = '\0'; + } else if (!rfcomm_hfp_hf(rfcomm, token)) { + spa_log_debug(backend->log, "RFCOMM received unsupported event: %s", token); + } + } + } + } + +done: + rfcomm->hfp_hf_cmd_in_progress = false; + if (!spa_list_is_empty(&rfcomm->hfp_hf_commands)) { + struct rfcomm_cmd *cmd; + cmd = spa_list_first(&rfcomm->hfp_hf_commands, struct rfcomm_cmd, link); + spa_list_remove(&cmd->link); + spa_log_debug(backend->log, "Sending postponed command: %s", cmd->cmd); + rfcomm_send_cmd(rfcomm, "%s", cmd->cmd); + free(cmd->cmd); + free(cmd); + } + + return reply_found; +} + +static void hfp_hf_get_error_from_reply(char *reply, enum spa_bt_telephony_error *err, uint8_t *cme_error) +{ + if (spa_strstartswith(reply, "+CME ERROR:")) { + *cme_error = atoi(reply + strlen("+CME ERROR:")); + *err = BT_TELEPHONY_ERROR_CME; + } else { + *err = BT_TELEPHONY_ERROR_FAILED; + } +} + +static void hfp_hf_answer(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +{ + struct rfcomm_call_data *call_data = data; + struct rfcomm *rfcomm = call_data->rfcomm; + struct impl *backend = rfcomm->backend; + char reply[20]; + bool res; + + if (call_data->call->state != CALL_STATE_INCOMING) { + *err = BT_TELEPHONY_ERROR_INVALID_STATE; + return; + } + + rfcomm_send_cmd(rfcomm, "ATA"); + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (!res || !spa_strstartswith(reply, "OK")) { + spa_log_info(backend->log, "Failed to answer call"); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + return; + } + + *err = BT_TELEPHONY_ERROR_NONE; +} + +static void hfp_hf_hangup(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +{ + struct rfcomm_call_data *call_data = data; + struct rfcomm *rfcomm = call_data->rfcomm; + struct impl *backend = rfcomm->backend; + char reply[20]; + bool res; + + switch (call_data->call->state) { + case CALL_STATE_ACTIVE: + case CALL_STATE_DIALING: + case CALL_STATE_ALERTING: + case CALL_STATE_INCOMING: + rfcomm_send_cmd(rfcomm, "AT+CHUP"); + break; + case CALL_STATE_WAITING: + rfcomm_send_cmd(rfcomm, "AT+CHLD=0"); + break; + default: + spa_log_info(backend->log, "Call not incoming, waiting or active: skip hangup"); + *err = BT_TELEPHONY_ERROR_INVALID_STATE; + return; + } + + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (!res || !spa_strstartswith(reply, "OK")) { + spa_log_info(backend->log, "Failed to hangup call"); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + return; + } + + *err = BT_TELEPHONY_ERROR_NONE; +} + +static const struct spa_bt_telephony_call_callbacks telephony_call_callbacks = { + SPA_VERSION_BT_TELEPHONY_AG_CALLBACKS, + .answer = hfp_hf_answer, + .hangup = hfp_hf_hangup, +}; + +static struct spa_bt_telephony_call *hfp_hf_add_call(struct rfcomm *rfcomm, struct spa_bt_telephony_ag *ag, enum spa_bt_telephony_call_state state, + const char *number) +{ + struct spa_bt_telephony_call *call; + struct rfcomm_call_data *data; + + call = telephony_call_new(ag, sizeof(*data)); + if (!call) + return NULL; + call->state = state; + if (number) + call->line_identification = strdup(number); + data = telephony_call_get_user_data(call); + data->rfcomm = rfcomm; + data->call = call; + telephony_call_set_callbacks(call, &telephony_call_callbacks, data); + telephony_call_register(call); + + return call; +} + +static void hfp_hf_dial(void *data, const char *number, enum spa_bt_telephony_error *err, uint8_t *cme_error) +{ + struct rfcomm *rfcomm = data; + struct impl *backend = rfcomm->backend; + char reply[20]; + bool res; + + spa_log_info(backend->log, "Dialing: \"%s\"", number); + rfcomm_send_cmd(rfcomm, "ATD%s;", number); + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (res && spa_strstartswith(reply, "OK")) { + struct spa_bt_telephony_call *call; + call = hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, CALL_STATE_DIALING, number); + *err = call ? BT_TELEPHONY_ERROR_NONE : BT_TELEPHONY_ERROR_FAILED; + } else { + spa_log_info(backend->log, "Failed to dial: \"%s\"", number); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + } +} + +static void hfp_hf_swap_calls(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +{ + struct rfcomm *rfcomm = data; + struct impl *backend = rfcomm->backend; + struct spa_bt_telephony_call *call; + bool found_active = false; + bool found_held = false; + char reply[20]; + bool res; + + if (!rfcomm->chld_supported) { + *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + return; + } else if (rfcomm->hfp_hf_in_progress) { + *err = BT_TELEPHONY_ERROR_IN_PROGRESS; + return; + } + + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_WAITING) { + spa_log_debug(backend->log, "call waiting before swapping"); + *err = BT_TELEPHONY_ERROR_INVALID_STATE; + return; + } else if (call->state == CALL_STATE_ACTIVE) + found_active = true; + else if (call->state == CALL_STATE_HELD) + found_held = true; + } + + if (!found_active || !found_held) { + spa_log_debug(backend->log, "no active and held calls"); + *err = BT_TELEPHONY_ERROR_INVALID_STATE; + return; + } + + rfcomm_send_cmd(rfcomm, "AT+CHLD=2"); + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (!res || !spa_strstartswith(reply, "OK")) { + spa_log_info(backend->log, "Failed to swap calls"); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + return; + } + + rfcomm->hfp_hf_in_progress = true; + *err = BT_TELEPHONY_ERROR_NONE; +} + +static void hfp_hf_release_and_answer(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +{ + struct rfcomm *rfcomm = data; + struct impl *backend = rfcomm->backend; + struct spa_bt_telephony_call *call; + bool found_active = false; + bool found_waiting = false; + char reply[20]; + bool res; + + if (!rfcomm->chld_supported) { + *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + return; + } else if (rfcomm->hfp_hf_in_progress) { + *err = BT_TELEPHONY_ERROR_IN_PROGRESS; + return; + } + + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_ACTIVE) + found_active = true; + else if (call->state == CALL_STATE_WAITING) + found_waiting = true; + } + + if (!found_active || !found_waiting) { + spa_log_debug(backend->log, "no active and waiting calls"); + *err = BT_TELEPHONY_ERROR_INVALID_STATE; + return; + } + + rfcomm_send_cmd(rfcomm, "AT+CHLD=1"); + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (!res || !spa_strstartswith(reply, "OK")) { + spa_log_info(backend->log, "Failed to release and answer calls"); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + return; + } + + rfcomm->hfp_hf_in_progress = true; + *err = BT_TELEPHONY_ERROR_NONE; +} + +static void hfp_hf_release_and_swap(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +{ + struct rfcomm *rfcomm = data; + struct impl *backend = rfcomm->backend; + struct spa_bt_telephony_call *call; + bool found_active = false; + bool found_held = false; + char reply[20]; + bool res; + + if (!rfcomm->chld_supported) { + *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + return; + } else if (rfcomm->hfp_hf_in_progress) { + *err = BT_TELEPHONY_ERROR_IN_PROGRESS; + return; + } + + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_WAITING) { + spa_log_debug(backend->log, "call waiting before release and swap"); + *err = BT_TELEPHONY_ERROR_INVALID_STATE; + return; + } else if (call->state == CALL_STATE_ACTIVE) + found_active = true; + else if (call->state == CALL_STATE_HELD) + found_held = true; + } + + if (!found_active || !found_held) { + spa_log_debug(backend->log, "no active and held calls"); + *err = BT_TELEPHONY_ERROR_INVALID_STATE; + return; + } + + rfcomm_send_cmd(rfcomm, "AT+CHLD=1"); + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (!res || !spa_strstartswith(reply, "OK")) { + spa_log_info(backend->log, "Failed to release and swap calls"); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + return; + } + + rfcomm->hfp_hf_in_progress = true; + *err = BT_TELEPHONY_ERROR_NONE; +} + +static void hfp_hf_hold_and_answer(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +{ + struct rfcomm *rfcomm = data; + struct impl *backend = rfcomm->backend; + struct spa_bt_telephony_call *call; + bool found_active = false; + bool found_waiting = false; + char reply[20]; + bool res; + + if (!rfcomm->chld_supported) { + *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + return; + } else if (rfcomm->hfp_hf_in_progress) { + *err = BT_TELEPHONY_ERROR_IN_PROGRESS; + return; + } + + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_ACTIVE) + found_active = true; + else if (call->state == CALL_STATE_WAITING) + found_waiting = true; + } + + if (!found_active || !found_waiting) { + spa_log_debug(backend->log, "no active and waiting calls"); + *err = BT_TELEPHONY_ERROR_INVALID_STATE; + return; + } + + rfcomm_send_cmd(rfcomm, "AT+CHLD=2"); + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (!res || !spa_strstartswith(reply, "OK")) { + spa_log_info(backend->log, "Failed to hold and answer calls"); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + return; + } + + rfcomm->hfp_hf_in_progress = true; + *err = BT_TELEPHONY_ERROR_NONE; +} + +static void hfp_hf_hangup_all(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +{ + struct rfcomm *rfcomm = data; + struct impl *backend = rfcomm->backend; + struct spa_bt_telephony_call *call; + bool found_active = false; + bool found_held = false; + char reply[20]; + bool res; + + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + switch (call->state) { + case CALL_STATE_ACTIVE: + case CALL_STATE_DIALING: + case CALL_STATE_ALERTING: + case CALL_STATE_INCOMING: + found_active = true; + break; + case CALL_STATE_HELD: + case CALL_STATE_WAITING: + found_held = true; + break; + default: + break; + } + } + + *err = BT_TELEPHONY_ERROR_NONE; + + /* Hangup held calls */ + if (found_held) { + rfcomm_send_cmd(rfcomm, "AT+CHLD=0"); + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (!res || !spa_strstartswith(reply, "OK")) { + spa_log_info(backend->log, "Failed to hangup held calls"); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + } + } + + /* Hangup active calls */ + if (found_active) { + rfcomm_send_cmd(rfcomm, "AT+CHUP"); + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (!res || !spa_strstartswith(reply, "OK")) { + spa_log_info(backend->log, "Failed to hangup active calls"); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + } + } +} + +static void hfp_hf_create_multiparty(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +{ + struct rfcomm *rfcomm = data; + struct impl *backend = rfcomm->backend; + struct spa_bt_telephony_call *call; + bool found_active = false; + bool found_held = false; + char reply[20]; + bool res; + + if (!rfcomm->chld_supported) { + *err = BT_TELEPHONY_ERROR_NOT_SUPPORTED; + return; + } else if (rfcomm->hfp_hf_in_progress) { + *err = BT_TELEPHONY_ERROR_IN_PROGRESS; + return; + } + + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_WAITING) { + spa_log_debug(backend->log, "call waiting before creating multiparty"); + *err = BT_TELEPHONY_ERROR_INVALID_STATE; + return; + } else if (call->state == CALL_STATE_ACTIVE) + found_active = true; + else if (call->state == CALL_STATE_HELD) + found_held = true; + } + + if (!found_active || !found_held) { + spa_log_debug(backend->log, "no active and held calls"); + *err = BT_TELEPHONY_ERROR_INVALID_STATE; + return; + } + + rfcomm_send_cmd(rfcomm, "AT+CHLD=3"); + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (!res || !spa_strstartswith(reply, "OK")) { + spa_log_info(backend->log, "Failed to create multiparty"); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + return; + } + + rfcomm->hfp_hf_in_progress = true; + *err = BT_TELEPHONY_ERROR_NONE; +} + +static void hfp_hf_send_tones(void *data, const char *tones, enum spa_bt_telephony_error *err, uint8_t *cme_error) +{ + struct rfcomm *rfcomm = data; + struct impl *backend = rfcomm->backend; + struct spa_bt_telephony_call *call; + bool found = false; + char reply[20]; + bool res; + + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_ACTIVE) { + found = true; + break; + } + } + + if (!found) { + spa_log_debug(backend->log, "no active call"); + *err = BT_TELEPHONY_ERROR_INVALID_STATE; + return; + } + + rfcomm_send_cmd(rfcomm, "AT+VTS=%s", tones); + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (!res || !spa_strstartswith(reply, "OK")) { + spa_log_info(backend->log, "Failed to send tones: %s", tones); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + return; + } + + *err = BT_TELEPHONY_ERROR_NONE; +} + +static void hfp_hf_transport_activate(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error) +{ + struct rfcomm *rfcomm = data; + struct impl *backend = rfcomm->backend; + char reply[20]; + bool res; + + if (spa_list_is_empty(&rfcomm->telephony_ag->call_list)) { + spa_log_debug(backend->log, "no ongoing call"); + *err = BT_TELEPHONY_ERROR_INVALID_STATE; + return; + } + if (rfcomm->transport->fd > 0) { + spa_log_debug(backend->log, "transport is already active; SCO socket exists"); + *err = BT_TELEPHONY_ERROR_INVALID_STATE; + return; + } + + rfcomm_send_cmd(rfcomm, "AT+BCC"); + res = hfp_hf_wait_for_reply(rfcomm, reply, sizeof(reply)); + if (!res || !spa_strstartswith(reply, "OK")) { + spa_log_info(backend->log, "Failed to send AT+BCC"); + if (res) + hfp_hf_get_error_from_reply(reply, err, cme_error); + else + *err = BT_TELEPHONY_ERROR_FAILED; + return; + } + + *err = BT_TELEPHONY_ERROR_NONE; +} + +static const struct spa_bt_telephony_ag_callbacks telephony_ag_callbacks = { + SPA_VERSION_BT_TELEPHONY_AG_CALLBACKS, + .dial = hfp_hf_dial, + .swap_calls = hfp_hf_swap_calls, + .release_and_answer = hfp_hf_release_and_answer, + .release_and_swap = hfp_hf_release_and_swap, + .hold_and_answer = hfp_hf_hold_and_answer, + .hangup_all = hfp_hf_hangup_all, + .create_multiparty = hfp_hf_create_multiparty, + .send_tones = hfp_hf_send_tones, + .transport_activate = hfp_hf_transport_activate, +}; + static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) { struct impl *backend = rfcomm->backend; - unsigned int features, gain, selected_codec, indicator, value; + unsigned int features, gain, selected_codec, indicator, value, type; + char number[17]; if (sscanf(token, "+BRSF:%u", &features) == 1) { if (((features & (SPA_BT_HFP_AG_FEATURE_CODEC_NEGOTIATION)) != 0) && (rfcomm->msbc_supported_by_hfp || rfcomm->lc3_supported_by_hfp)) rfcomm->codec_negotiation_supported = true; + rfcomm->hfp_hf_3way = (features & SPA_BT_HFP_AG_FEATURE_3WAY) != 0; + rfcomm->hfp_hf_nrec = (features & SPA_BT_HFP_AG_FEATURE_ECNR) != 0; + rfcomm->hfp_hf_clcc = (features & SPA_BT_HFP_AG_FEATURE_ENHANCED_CALL_STATUS) != 0; + rfcomm->hfp_hf_cme = (features & SPA_BT_HFP_AG_FEATURE_EXTENDED_RES_CODE) != 0; } 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 && selected_codec != HFP_AUDIO_CODEC_LC3_SWB) { @@ -1243,10 +1894,9 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) rfcomm->hf_state = hfp_hf_bcs; if (!rfcomm->transport || (rfcomm->transport->codec != selected_codec) ) { - if (rfcomm_new_transport(rfcomm) < 0) { + if (rfcomm_new_transport(rfcomm, selected_codec) < 0) { // TODO: We should manage the missing transport } else { - rfcomm->transport->codec = selected_codec; spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); } } @@ -1294,6 +1944,26 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) token += strcspn(token, "\0") + 1; i++; } + } else if (spa_strstartswith(token, "+CHLD: (")) { + int chlds = 0; + token[strcspn(token, "\r")] = 0; + token[strcspn(token, "\n")] = 0; + token[strcspn(token, ")")] = 0; + token += strlen("+CHLD: ("); + while (strlen(token)) { + token[strcspn(token, ",")] = 0; + if (spa_streq(token, "0")) + chlds |= 1 << 0; + else if (spa_streq(token, "1")) + chlds |= 1 << 1; + else if (spa_streq(token, "2")) + chlds |= 1 << 2; + else if (spa_streq(token, "3")) + chlds |= 1 << 3; + token += strcspn(token, "\0") + 1; + } + rfcomm->chld_supported = (chlds == 0x0F); + spa_log_debug(backend->log, "AT+CHLD supported: %d (0x%X)", rfcomm->chld_supported, chlds); } else if (sscanf(token, "+CIEV: %u,%u", &indicator, &value) == 2) { if (indicator >= MAX_HF_INDICATORS || !rfcomm->hf_indicators[indicator]) { spa_log_warn(backend->log, "indicator %u has not been registered, ignoring", indicator); @@ -1302,67 +1972,388 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) if (spa_streq(rfcomm->hf_indicators[indicator], "battchg")) { spa_bt_device_report_battery_level(rfcomm->device, value * 100 / 5); + } else if (spa_streq(rfcomm->hf_indicators[indicator], "callsetup")) { + if (value == CIND_CALLSETUP_NONE) { + struct spa_bt_telephony_call *call, *tcall; + spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_DIALING || call->state == CALL_STATE_ALERTING || + call->state == CALL_STATE_INCOMING) { + call->state = CALL_STATE_DISCONNECTED; + telephony_call_notify_updated_props(call); + telephony_call_destroy(call); + } + } + } else if (value == CIND_CALLSETUP_INCOMING) { + struct spa_bt_telephony_call *call; + bool found = false; + + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_INCOMING || call->state == CALL_STATE_WAITING) { + spa_log_info(backend->log, "incoming call already in progress (%d)", call->state); + found = true; + break; + } + } + + if (!found && !rfcomm->hfp_hf_clcc) { + spa_log_info(backend->log, "Incoming call"); + if (hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, CALL_STATE_INCOMING, NULL) == NULL) + spa_log_warn(backend->log, "failed to create incoming call"); + } + } else if (value == CIND_CALLSETUP_DIALING) { + struct spa_bt_telephony_call *call; + bool found = false; + + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_DIALING || call->state == CALL_STATE_ALERTING) { + spa_log_info(backend->log, "dialing call already in progress (%d)", call->state); + found = true; + break; + } + } + + if (!found && !rfcomm->hfp_hf_clcc) { + spa_log_info(backend->log, "Dialing call"); + if (hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, CALL_STATE_DIALING, NULL) == NULL) + spa_log_warn(backend->log, "failed to create dialing call"); + } + } else if (value == CIND_CALLSETUP_ALERTING) { + struct spa_bt_telephony_call *call; + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_DIALING) { + call->state = CALL_STATE_ALERTING; + telephony_call_notify_updated_props(call); + } + } + } + + if (rfcomm->hfp_hf_clcc) + rfcomm_send_cmd(rfcomm, "AT+CLCC"); + else + rfcomm->hfp_hf_in_progress = false; + } else if (spa_streq(rfcomm->hf_indicators[indicator], "call")) { + if (value == 0) { + struct spa_bt_telephony_call *call, *tcall; + spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_ACTIVE) { + call->state = CALL_STATE_DISCONNECTED; + telephony_call_notify_updated_props(call); + telephony_call_destroy(call); + } + } + } else if (value == 1) { + struct spa_bt_telephony_call *call; + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_DIALING || call->state == CALL_STATE_ALERTING || + call->state == CALL_STATE_INCOMING) { + call->state = CALL_STATE_ACTIVE; + telephony_call_notify_updated_props(call); + } + } + } + + if (rfcomm->hfp_hf_clcc) + rfcomm_send_cmd(rfcomm, "AT+CLCC"); + else + rfcomm->hfp_hf_in_progress = false; + } else if (spa_streq(rfcomm->hf_indicators[indicator], "callheld")) { + if (value == 0) { /* Reject waiting call or no held calls */ + struct spa_bt_telephony_call *call, *tcall; + bool found_waiting = false; + spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_WAITING) { + call->state = CALL_STATE_DISCONNECTED; + telephony_call_notify_updated_props(call); + telephony_call_destroy(call); + found_waiting = true; + break; + } + } + if (!found_waiting) { + spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_HELD) { + call->state = CALL_STATE_DISCONNECTED; + telephony_call_notify_updated_props(call); + telephony_call_destroy(call); + } + } + } + } else if (value == 1) { /* Swap calls */ + struct spa_bt_telephony_call *call; + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + bool changed = false; + if (call->state == CALL_STATE_ACTIVE) { + call->state = CALL_STATE_HELD; + changed = true; + } else if (call->state == CALL_STATE_HELD) { + call->state = CALL_STATE_ACTIVE; + changed = true; + } + + if (changed) + telephony_call_notify_updated_props(call); + } + } else if (value == 2) { /* No active calls, place waiting on hold */ + struct spa_bt_telephony_call *call; + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + bool changed = false; + if (call->state == CALL_STATE_ACTIVE || call->state == CALL_STATE_WAITING) { + call->state = CALL_STATE_HELD; + changed = true; + } + + if (changed) + telephony_call_notify_updated_props(call); + } + } + + if (rfcomm->hfp_hf_clcc) + rfcomm_send_cmd(rfcomm, "AT+CLCC"); + else + rfcomm->hfp_hf_in_progress = false; } } - } else if (spa_strstartswith(token, "OK")) { - switch(rfcomm->hf_state) { - case hfp_hf_brsf: - if (rfcomm->codec_negotiation_supported) { - char buf[64]; - struct spa_strbuf str; - - spa_strbuf_init(&str, buf, sizeof(buf)); - spa_strbuf_append(&str, "1"); - if (rfcomm->msbc_supported_by_hfp) - spa_strbuf_append(&str, ",2"); - if (rfcomm->lc3_supported_by_hfp) - spa_strbuf_append(&str, ",3"); - - rfcomm_send_cmd(rfcomm, "AT+BAC=%s", buf); - rfcomm->hf_state = hfp_hf_bac; - } else { + } else if (sscanf(token, "+CLIP: \"%16[^\"]\",%u", number, &type) == 2) { + struct spa_bt_telephony_call *call; + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_INCOMING && !spa_streq(number, call->line_identification)) { + if (call->line_identification) + free(call->line_identification); + call->line_identification = strdup(number); + telephony_call_notify_updated_props(call); + break; + } + } + } else if (sscanf(token, "+CCWA: \"%16[^\"]\",%u", number, &type) == 2) { + struct spa_bt_telephony_call *call; + bool found = false; + + spa_log_info(backend->log, "Waiting call"); + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->state == CALL_STATE_WAITING) { + spa_log_info(backend->log, "waiting call already in progress (id: %d)", call->id); + found = true; + break; + } + } + if (!found) { + call = hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, CALL_STATE_WAITING, number); + if (call == NULL) + spa_log_warn(backend->log, "failed to create waiting call"); + } + } else if (spa_strstartswith(token, "+CLCC:")) { + struct spa_bt_telephony_call *call; + size_t pos; + char *token_end; + int idx; + unsigned int status, mpty; + bool parsed = false, found = false; + + number[0] = '\0'; + + token[strcspn(token, "\r")] = 0; + token[strcspn(token, "\n")] = 0; + token_end = token + strlen(token); + token += strlen("+CLCC:"); + + if (token < token_end) { + pos = strcspn(token, ","); + token[pos] = '\0'; + idx = atoi(token); + token += pos + 1; + } + if (token < token_end) { + // Skip direction + pos = strcspn(token, ","); + token += pos + 1; + } + if (token < token_end) { + pos = strcspn(token, ","); + token[pos] = '\0'; + status = atoi(token); + token += pos + 1; + } + if (token < token_end) { + // Skip mode + pos = strcspn(token, ","); + token += pos + 1; + } + if (token < token_end) { + pos = strcspn(token, ","); + token[pos] = '\0'; + mpty = atoi(token); + token += pos + 1; + parsed = true; + } + if (token < token_end) { + if (sscanf(token, "\"%16[^\"]\",%u", number, &type) != 2) { + spa_log_warn(backend->log, "Failed to parse number: %s", token); + number[0] = '\0'; + } + } + + if (SPA_LIKELY (parsed)) { + spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { + if (call->id == idx) { + bool changed = false; + + found = true; + + if (call->state != status) { + call->state =status; + changed = true; + } + if (call->multiparty != mpty) { + call->multiparty = mpty; + changed = true; + } + if (strlen(number) && !spa_streq(number, call->line_identification)) { + if (call->line_identification) + free(call->line_identification); + call->line_identification = strdup(number); + changed = true; + } + + if (changed) + telephony_call_notify_updated_props(call); + } + } + + if (!found) { + spa_log_info(backend->log, "New call, initial state: %u", status); + call = hfp_hf_add_call(rfcomm, rfcomm->telephony_ag, status, strlen(number) ? number : NULL); + if (call == NULL) + spa_log_warn(backend->log, "failed to create call"); + else if (call->id != idx) + spa_log_warn(backend->log, "wrong call index: %d, expected: %d", call->id, idx); + } + } else { + spa_log_warn(backend->log, "malformed +CLCC command received from AG"); + } + + rfcomm->hfp_hf_in_progress = false; + } else if (spa_strstartswith(token, "OK") || spa_strstartswith(token, "ERROR") || + spa_strstartswith(token, "+CME ERROR:")) { + rfcomm->hfp_hf_cmd_in_progress = false; + if (!spa_list_is_empty(&rfcomm->hfp_hf_commands)) { + struct rfcomm_cmd *cmd; + cmd = spa_list_first(&rfcomm->hfp_hf_commands, struct rfcomm_cmd, link); + spa_list_remove(&cmd->link); + spa_log_debug(backend->log, "Sending postponed command: %s", cmd->cmd); + rfcomm_send_cmd(rfcomm, "%s", cmd->cmd); + free(cmd->cmd); + free(cmd); + } + + if (spa_strstartswith(token, "OK")) { + switch(rfcomm->hf_state) { + case hfp_hf_brsf: + if (rfcomm->codec_negotiation_supported) { + char buf[64]; + struct spa_strbuf str; + + spa_strbuf_init(&str, buf, sizeof(buf)); + spa_strbuf_append(&str, "1"); + if (rfcomm->msbc_supported_by_hfp) + spa_strbuf_append(&str, ",2"); + if (rfcomm->lc3_supported_by_hfp) + spa_strbuf_append(&str, ",3"); + + rfcomm_send_cmd(rfcomm, "AT+BAC=%s", buf); + rfcomm->hf_state = hfp_hf_bac; + } else { + rfcomm_send_cmd(rfcomm, "AT+CIND=?"); + rfcomm->hf_state = hfp_hf_cind1; + } + break; + case hfp_hf_bac: rfcomm_send_cmd(rfcomm, "AT+CIND=?"); rfcomm->hf_state = hfp_hf_cind1; - } - break; - case hfp_hf_bac: - rfcomm_send_cmd(rfcomm, "AT+CIND=?"); - rfcomm->hf_state = hfp_hf_cind1; - break; - case hfp_hf_cind1: - rfcomm_send_cmd(rfcomm, "AT+CIND?"); - rfcomm->hf_state = hfp_hf_cind2; - break; - case hfp_hf_cind2: - rfcomm_send_cmd(rfcomm, "AT+CMER=3,0,0,1"); - rfcomm->hf_state = hfp_hf_cmer; - break; - case hfp_hf_cmer: - rfcomm->hf_state = hfp_hf_slc1; - rfcomm->slc_configured = true; - if (!rfcomm->codec_negotiation_supported) { - if (rfcomm_new_transport(rfcomm) < 0) { - // TODO: We should manage the missing transport + break; + case hfp_hf_cind1: + rfcomm_send_cmd(rfcomm, "AT+CIND?"); + rfcomm->hf_state = hfp_hf_cind2; + break; + case hfp_hf_cind2: + rfcomm_send_cmd(rfcomm, "AT+CMER=3,0,0,1"); + rfcomm->hf_state = hfp_hf_cmer; + break; + case hfp_hf_cmer: + if (rfcomm->hfp_hf_3way) { + rfcomm_send_cmd(rfcomm, "AT+CHLD=?"); + rfcomm->hf_state = hfp_hf_chld; + break; + } + SPA_FALLTHROUGH; + case hfp_hf_chld: + rfcomm_send_cmd(rfcomm, "AT+CLIP=1"); + rfcomm->hf_state = hfp_hf_clip; + break; + case hfp_hf_clip: + if (rfcomm->chld_supported) { + rfcomm_send_cmd(rfcomm, "AT+CCWA=1"); + rfcomm->hf_state = hfp_hf_ccwa; + break; + } + SPA_FALLTHROUGH; + case hfp_hf_ccwa: + if (rfcomm->hfp_hf_cme) { + rfcomm_send_cmd(rfcomm, "AT+CMEE=1"); + rfcomm->hf_state = hfp_hf_cmee; + break; + } + SPA_FALLTHROUGH; + case hfp_hf_cmee: + if (backend->hfp_disable_nrec && rfcomm->hfp_hf_nrec) { + rfcomm_send_cmd(rfcomm, "AT+NREC=0"); + rfcomm->hf_state = hfp_hf_nrec; + break; + } + SPA_FALLTHROUGH; + case hfp_hf_nrec: + rfcomm->hf_state = hfp_hf_slc1; + rfcomm->slc_configured = true; + + if (!rfcomm->codec_negotiation_supported) { + if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) { + // TODO: We should manage the missing transport + } else { + spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); + } + } + + rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0); + rfcomm->telephony_ag->address = strdup(rfcomm->device->address); + telephony_ag_set_callbacks(rfcomm->telephony_ag, + &telephony_ag_callbacks, rfcomm); + if (rfcomm->transport) { + rfcomm->telephony_ag->transport.codec = rfcomm->transport->codec; + rfcomm->telephony_ag->transport.state = rfcomm->transport->state; + } + telephony_ag_register(rfcomm->telephony_ag); + + if (rfcomm->hfp_hf_clcc) { + rfcomm_send_cmd(rfcomm, "AT+CLCC"); + rfcomm->hf_state = hfp_hf_slc2; + break; } else { - rfcomm->transport->codec = HFP_AUDIO_CODEC_CVSD; - spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); + // TODO: Create calls if CIND reports one during SLC setup } + + /* Report volume on SLC establishment */ + SPA_FALLTHROUGH; + case hfp_hf_slc2: + if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX)) + rfcomm->hf_state = hfp_hf_vgs; + break; + case hfp_hf_vgs: + rfcomm->hf_state = hfp_hf_slc1; + if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX)) + rfcomm->hf_state = hfp_hf_vgm; + break; + default: + break; } - /* Report volume on SLC establishment */ - if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX)) - rfcomm->hf_state = hfp_hf_vgs; - break; - case hfp_hf_slc2: - if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_RX)) - rfcomm->hf_state = hfp_hf_vgs; - break; - case hfp_hf_vgs: - rfcomm->hf_state = hfp_hf_slc1; - if (rfcomm_send_volume_cmd(rfcomm, SPA_BT_VOLUME_ID_TX)) - rfcomm->hf_state = hfp_hf_vgm; - break; - default: - break; } } @@ -1835,6 +2826,11 @@ static void sco_listen_event(struct spa_source *source) spa_assert(t->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY); + if (rfcomm->telephony_ag && rfcomm->telephony_ag->transport.rejectSCO) { + spa_log_info(backend->log, "rejecting SCO, AudioGatewayTransport1.RejectSCO=true"); + return; + } + if (t->fd >= 0) { spa_log_debug(backend->log, "transport %p: Rejecting, audio already connected", t); return; @@ -2130,8 +3126,7 @@ static void codec_switch_timer_event(struct spa_source *source) /* Failure, try falling back to CVSD. */ rfcomm->hfp_ag_initial_codec_setup = HFP_AG_INITIAL_CODEC_SETUP_NONE; if (rfcomm->transport == NULL) { - if (rfcomm_new_transport(rfcomm) == 0) { - rfcomm->transport->codec = HFP_AUDIO_CODEC_CVSD; + if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) == 0) { spa_bt_device_connect_profile(rfcomm->device, rfcomm->profile); } } @@ -2291,6 +3286,7 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag rfcomm->source.fd = spa_steal_fd(fd); rfcomm->source.mask = SPA_IO_IN; rfcomm->source.rmask = 0; + spa_list_init(&rfcomm->hfp_hf_commands); /* By default all indicators are enabled */ rfcomm->cind_enabled_indicators = 0xFFFFFFFF; memset(rfcomm->hf_indicators, 0, sizeof rfcomm->hf_indicators); @@ -2306,10 +3302,9 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag spa_list_append(&backend->rfcomm_list, &rfcomm->link); if (profile == SPA_BT_PROFILE_HSP_HS || profile == SPA_BT_PROFILE_HSP_AG) { - if (rfcomm_new_transport(rfcomm) < 0) + if (rfcomm_new_transport(rfcomm, HFP_AUDIO_CODEC_CVSD) < 0) goto fail_need_memory; - rfcomm->transport->codec = HFP_AUDIO_CODEC_CVSD; rfcomm->has_volume = rfcomm_volume_enabled(rfcomm); if (profile == SPA_BT_PROFILE_HSP_AG) { @@ -2322,7 +3317,9 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag rfcomm->transport->path, handler); } else if (profile == SPA_BT_PROFILE_HFP_AG) { /* Start SLC connection */ - unsigned int hf_features = SPA_BT_HFP_HF_FEATURE_NONE; + unsigned int hf_features = SPA_BT_HFP_HF_FEATURE_CLIP | SPA_BT_HFP_HF_FEATURE_3WAY | + SPA_BT_HFP_HF_FEATURE_ENHANCED_CALL_STATUS | + SPA_BT_HFP_HF_FEATURE_ESCO_S4; bool has_msbc = device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_MSBC); bool has_lc3 = device_supports_codec(backend, rfcomm->device, HFP_AUDIO_CODEC_LC3_SWB); @@ -2538,6 +3535,9 @@ static int register_profile(struct impl *backend, const char *profile, const cha /* We announce wideband speech support anyway */ features = SPA_BT_HFP_SDP_AG_FEATURE_WIDEBAND_SPEECH; +#ifdef HAVE_LC3 + features |= SPA_BT_HFP_SDP_AG_FEATURE_SUPER_WIDEBAND_SPEECH; +#endif dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]); @@ -2545,9 +3545,9 @@ static int register_profile(struct impl *backend, const char *profile, const cha dbus_message_iter_close_container(&it[2], &it[3]); dbus_message_iter_close_container(&it[1], &it[2]); - /* HFP version 1.7 */ + /* HFP version 1.9 */ str = "Version"; - version = 0x0107; + version = 0x0109; dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]); @@ -2559,6 +3559,9 @@ static int register_profile(struct impl *backend, const char *profile, const cha /* We announce wideband speech support anyway */ features = SPA_BT_HFP_SDP_HF_FEATURE_WIDEBAND_SPEECH; +#ifdef HAVE_LC3 + features |= SPA_BT_HFP_SDP_HF_FEATURE_SUPER_WIDEBAND_SPEECH; +#endif dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]); @@ -2566,9 +3569,9 @@ static int register_profile(struct impl *backend, const char *profile, const cha dbus_message_iter_close_container(&it[2], &it[3]); dbus_message_iter_close_container(&it[1], &it[2]); - /* HFP version 1.7 */ + /* HFP version 1.9 */ str = "Version"; - version = 0x0107; + version = 0x0109; dbus_message_iter_open_container(&it[1], DBUS_TYPE_DICT_ENTRY, NULL, &it[2]); dbus_message_iter_append_basic(&it[2], DBUS_TYPE_STRING, &str); dbus_message_iter_open_container(&it[2], DBUS_TYPE_VARIANT, "q", &it[3]); @@ -2837,6 +3840,8 @@ static int backend_native_free(void *data) backend->upower = NULL; } + spa_clear_ptr(backend->telephony, telephony_free); + if (backend->ring_timer) spa_loop_utils_destroy_source(backend->loop_utils, backend->ring_timer); @@ -2883,6 +3888,16 @@ fallback: return 0; } +static void parse_hfp_disable_nrec(struct impl *backend, const struct spa_dict *info) +{ + const char *str; + + if ((str = spa_dict_lookup(info, PROP_KEY_HFP_DISABLE_NREC)) != NULL) + backend->hfp_disable_nrec = spa_atob(str); + else + backend->hfp_disable_nrec = false; +} + static const struct spa_bt_backend_implementation backend_impl = { SPA_VERSION_BT_BACKEND_IMPLEMENTATION, .free = backend_native_free, @@ -2940,6 +3955,8 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor, if (parse_headset_roles(backend, info) < 0) goto fail; + parse_hfp_disable_nrec(backend, info); + #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE if (!dbus_connection_register_object_path(backend->conn, PROFILE_HSP_AG, @@ -2970,6 +3987,7 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor, backend->modemmanager = mm_register(backend->log, backend->conn, info, &mm_ops, backend); backend->upower = upower_register(backend->log, backend->conn, set_battery_level, backend); + backend->telephony = telephony_new(backend->log, backend->dbus, info); return &backend->this; diff --git a/spa/plugins/bluez5/bap-codec-caps.h b/spa/plugins/bluez5/bap-codec-caps.h index 981b0015..9d1da42b 100644 --- a/spa/plugins/bluez5/bap-codec-caps.h +++ b/spa/plugins/bluez5/bap-codec-caps.h @@ -25,15 +25,42 @@ LC3_FREQ_44KHZ | \ LC3_FREQ_48KHZ) +#define LC3_VAL_FREQ_8KHZ 8000 +#define LC3_VAL_FREQ_11KHZ 11025 +#define LC3_VAL_FREQ_16KHZ 16000 +#define LC3_VAL_FREQ_22KHZ 22050 +#define LC3_VAL_FREQ_24KHZ 24000 +#define LC3_VAL_FREQ_32KHZ 32000 +#define LC3_VAL_FREQ_44KHZ 44100 +#define LC3_VAL_FREQ_48KHZ 48000 + #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_VAL_DUR_7_5 7.5 +#define LC3_VAL_DUR_10 10 + #define LC3_TYPE_CHAN 0x03 #define LC3_CHAN_1 (1 << 0) #define LC3_CHAN_2 (1 << 1) +#define LC3_CHAN_3 (1 << 2) +#define LC3_CHAN_4 (1 << 3) +#define LC3_CHAN_5 (1 << 4) +#define LC3_CHAN_6 (1 << 5) +#define LC3_CHAN_7 (1 << 6) +#define LC3_CHAN_8 (1 << 7) + +#define LC3_VAL_CHAN_1 1 +#define LC3_VAL_CHAN_2 2 +#define LC3_VAL_CHAN_3 3 +#define LC3_VAL_CHAN_4 4 +#define LC3_VAL_CHAN_5 5 +#define LC3_VAL_CHAN_6 6 +#define LC3_VAL_CHAN_7 7 +#define LC3_VAL_CHAN_8 8 #define LC3_TYPE_FRAMELEN 0x04 #define LC3_TYPE_BLKS 0x05 diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index f628bd48..e9b77232 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -14,6 +14,7 @@ #include <spa/param/audio/format.h> #include <spa/param/audio/format-utils.h> #include <spa/utils/string.h> +#include <spa/utils/json.h> #include <spa/debug/log.h> #include <lc3.h> @@ -23,7 +24,7 @@ #define MAX_PACS 64 -static struct spa_log *log; +static struct spa_log *log_; struct impl { lc3_encoder_t enc[LC3_MAX_CHANNELS]; @@ -238,20 +239,153 @@ static int write_ltv_uint32(uint8_t *dest, uint8_t type, uint32_t value) return write_ltv(dest, type, &value, sizeof(value)); } +static uint16_t parse_rates(const char *str) +{ + struct spa_json it; + uint16_t rate_mask = 0; + int value; + + if (spa_json_begin_array_relax(&it, str, strlen(str)) <= 0) + return rate_mask; + + while (spa_json_get_int(&it, &value) > 0) { + switch (value) { + case LC3_VAL_FREQ_8KHZ: + rate_mask |= LC3_FREQ_8KHZ; + break; + case LC3_VAL_FREQ_16KHZ: + rate_mask |= LC3_FREQ_16KHZ; + break; + case LC3_VAL_FREQ_24KHZ: + rate_mask |= LC3_FREQ_24KHZ; + break; + case LC3_VAL_FREQ_32KHZ: + rate_mask |= LC3_FREQ_32KHZ; + break; + case LC3_VAL_FREQ_44KHZ: + rate_mask |= LC3_FREQ_44KHZ; + break; + case LC3_VAL_FREQ_48KHZ: + rate_mask |= LC3_FREQ_48KHZ; + break; + default: + break; + } + } + + return rate_mask; +} + +static uint8_t parse_durations(const char *str) +{ + struct spa_json it; + uint8_t duration_mask = 0; + float value; + + if (spa_json_begin_array_relax(&it, str, strlen(str)) <= 0) + return duration_mask; + + while (spa_json_get_float(&it, &value) > 0) { + if (value == (float)LC3_VAL_DUR_7_5) + duration_mask |= LC3_DUR_7_5; + else if (value == (float)LC3_VAL_DUR_10) + duration_mask |= LC3_DUR_10; + } + + return duration_mask; +} + +static uint8_t parse_channel_counts(const char *str) +{ + struct spa_json it; + uint8_t channel_counts = 0; + int value; + + if (spa_json_begin_array_relax(&it, str, strlen(str)) <= 0) + return channel_counts; + + while (spa_json_get_int(&it, &value) > 0) { + switch (value) { + case LC3_VAL_CHAN_1: + channel_counts |= LC3_CHAN_1; + break; + case LC3_VAL_CHAN_2: + channel_counts |= LC3_CHAN_2; + break; + case LC3_VAL_CHAN_3: + channel_counts |= LC3_CHAN_3; + break; + case LC3_VAL_CHAN_4: + channel_counts |= LC3_CHAN_4; + break; + case LC3_VAL_CHAN_5: + channel_counts |= LC3_CHAN_5; + break; + case LC3_VAL_CHAN_6: + channel_counts |= LC3_CHAN_6; + break; + case LC3_VAL_CHAN_7: + channel_counts |= LC3_CHAN_7; + break; + case LC3_VAL_CHAN_8: + channel_counts |= LC3_CHAN_8; + break; + default: + break; + } + } + + return channel_counts; +} + static int codec_fill_caps(const struct media_codec *codec, uint32_t flags, - uint8_t caps[A2DP_MAX_CAPS_SIZE]) + const struct spa_dict *settings, 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_32KHZ | \ - 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); + const char *str; + uint16_t framelen[2]; + uint16_t rate_mask = LC3_FREQ_48KHZ | LC3_FREQ_32KHZ | \ + LC3_FREQ_24KHZ | LC3_FREQ_16KHZ | LC3_FREQ_8KHZ; + uint8_t duration_mask = LC3_DUR_ANY; + uint8_t channel_counts = LC3_CHAN_1 | LC3_CHAN_2; + uint16_t framelen_min = LC3_MIN_FRAME_BYTES; + uint16_t framelen_max = LC3_MAX_FRAME_BYTES; + uint8_t max_frames = 2; + uint32_t value; + + if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.rates"))) + rate_mask = parse_rates(str); + + if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.durations"))) + duration_mask = parse_durations(str); + + if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.channels"))) + channel_counts = parse_channel_counts(str); + + if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.framelen_min"))) + if (spa_atou32(str, &value, 0)) + framelen_min = value; + + if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.framelen_max"))) + if (spa_atou32(str, &value, 0)) + framelen_max = value; + + if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.max_frames"))) + if (spa_atou32(str, &value, 0)) + max_frames = value; + + framelen[0] = htobs(framelen_min); + framelen[1] = htobs(framelen_max); + + data += write_ltv_uint16(data, LC3_TYPE_FREQ, htobs(rate_mask)); + data += write_ltv_uint8(data, LC3_TYPE_DUR, duration_mask); + data += write_ltv_uint8(data, LC3_TYPE_CHAN, channel_counts); data += write_ltv(data, LC3_TYPE_FRAMELEN, framelen, sizeof(framelen)); /* XXX: we support only one frame block -> max 2 frames per SDU */ - data += write_ltv_uint8(data, LC3_TYPE_BLKS, 2); + if (max_frames > 2) + max_frames = 2; + + data += write_ltv_uint8(data, LC3_TYPE_BLKS, max_frames); return data - caps; } @@ -633,7 +767,7 @@ static int pac_cmp(const void *p1, const void *p2) { const struct pac_data *pac1 = p1; const struct pac_data *pac2 = p2; - struct spa_debug_log_ctx debug_ctx = SPA_LOG_DEBUG_INIT(log, SPA_LOG_LEVEL_TRACE); + struct spa_debug_log_ctx debug_ctx = SPA_LOG_DEBUG_INIT(log_, SPA_LOG_LEVEL_TRACE); bap_lc3_t conf1, conf2; int res1, res2; @@ -655,7 +789,7 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags, uint32_t locations = 0; uint32_t channel_allocation = 0; bool sink = false, duplex = false; - struct spa_debug_log_ctx debug_ctx = SPA_LOG_DEBUG_INIT(log, SPA_LOG_LEVEL_TRACE); + struct spa_debug_log_ctx debug_ctx = SPA_LOG_DEBUG_INIT(log_, SPA_LOG_LEVEL_TRACE); int i; if (caps == NULL) @@ -670,7 +804,7 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags, } if (spa_atob(spa_dict_lookup(settings, "bluez5.bap.debug"))) - debug_ctx = SPA_LOG_DEBUG_INIT(log, SPA_LOG_LEVEL_DEBUG); + debug_ctx = SPA_LOG_DEBUG_INIT(log_, SPA_LOG_LEVEL_DEBUG); /* Is remote endpoint sink or source */ sink = spa_atob(spa_dict_lookup(settings, "bluez5.bap.sink")); @@ -897,7 +1031,7 @@ static int codec_get_qos(const struct media_codec *codec, conf.framelen, conf.framelen); if (!bap_qos) { /* shouldn't happen: select_config should pick existing one */ - spa_log_error(log, "no QoS settings found"); + spa_log_error(log_, "no QoS settings found"); return -EINVAL; } @@ -955,7 +1089,7 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, goto error; if (!parse_conf(&conf, config, config_len)) { - spa_log_error(log, "invalid LC3 config"); + spa_log_error(log_, "invalid LC3 config"); res = -ENOTSUP; goto error; } @@ -976,12 +1110,12 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags, goto error; } - spa_log_info(log, "LC3 rate:%d frame_duration:%d channels:%d framelen:%d nblks:%d", + spa_log_info(log_, "LC3 rate:%d frame_duration:%d channels:%d framelen:%d nblks:%d", this->samplerate, this->frame_dus, this->channels, this->framelen, conf.n_blks); res = lc3_frame_samples(this->frame_dus, this->samplerate); if (res < 0) { - spa_log_error(log, "invalid LC3 frame samples"); + spa_log_error(log_, "invalid LC3 frame samples"); res = -EINVAL; goto error; } @@ -1146,8 +1280,8 @@ static int codec_increase_bitpool(void *data) static void codec_set_log(struct spa_log *global_log) { - log = global_log; - spa_log_topic_init(log, &codec_plugin_log_topic); + log_ = global_log; + spa_log_topic_init(log_, &codec_plugin_log_topic); } static int codec_get_bis_config(const struct media_codec *codec, uint8_t *caps, diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c index 19d365c4..e4495f0e 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -138,6 +138,7 @@ struct spa_bt_remote_endpoint { struct spa_list device_link; struct spa_bt_monitor *monitor; char *path; + char *transport_path; char *uuid; unsigned int codec; @@ -180,6 +181,7 @@ struct spa_bt_big { int presentation_delay; struct spa_list bis_list; int big_id; + int sync_factor; }; /* @@ -218,7 +220,8 @@ struct spa_bt_media_codec_switch { #define DEFAULT_RECONNECT_PROFILES SPA_BT_PROFILE_NULL #define DEFAULT_HW_VOLUME_PROFILES (SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY | SPA_BT_PROFILE_HEADSET_HEAD_UNIT | \ - SPA_BT_PROFILE_A2DP_SOURCE | SPA_BT_PROFILE_A2DP_SINK) + SPA_BT_PROFILE_A2DP_SOURCE | SPA_BT_PROFILE_A2DP_SINK | \ + SPA_BT_PROFILE_BAP_AUDIO) #define BT_DEVICE_DISCONNECTED 0 #define BT_DEVICE_CONNECTED 1 @@ -555,6 +558,8 @@ static enum spa_bt_profile get_codec_profile(const struct media_codec *codec, case SPA_BT_MEDIA_SOURCE: return codec->bap ? SPA_BT_PROFILE_BAP_SOURCE : SPA_BT_PROFILE_A2DP_SOURCE; case SPA_BT_MEDIA_SINK: + if (codec->asha) + return SPA_BT_PROFILE_ASHA_SINK; return codec->bap ? SPA_BT_PROFILE_BAP_SINK : SPA_BT_PROFILE_A2DP_SINK; case SPA_BT_MEDIA_SOURCE_BROADCAST: return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; @@ -2019,10 +2024,11 @@ int spa_bt_device_check_profiles(struct spa_bt_device *device, bool force) uint32_t connected_profiles = device->connected_profiles; uint32_t connectable_profiles = device->adapter ? adapter_connectable_profiles(device->adapter) : 0; - uint32_t direction_masks[3] = { + uint32_t direction_masks[4] = { SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_HEADSET_HEAD_UNIT, SPA_BT_PROFILE_MEDIA_SOURCE, SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY, + SPA_BT_PROFILE_ASHA_SINK, }; bool direction_connected = false; bool set_connected = true; @@ -2075,9 +2081,10 @@ static void device_set_connected(struct spa_bt_device *device, int connected) if (device->connected && !connected) device->connected_profiles = 0; - if (connected) + if (connected) { + spa_bt_quirks_log_features(monitor->quirks, device->adapter, device); spa_bt_device_check_profiles(device, false); - else { + } else { /* Stop codec switch on disconnect */ struct spa_bt_media_codec_switch *sw; spa_list_consume(sw, &device->codec_switch_list, device_link) @@ -2093,13 +2100,11 @@ static void device_update_set_status(struct spa_bt_device *device, bool force, c int spa_bt_device_connect_profile(struct spa_bt_device *device, enum spa_bt_profile profile) { - uint32_t prev_connected = device->connected_profiles; device->connected_profiles |= profile; - if ((prev_connected ^ device->connected_profiles) & SPA_BT_PROFILE_BAP_DUPLEX) + if (profile & SPA_BT_PROFILE_BAP_DUPLEX) device_update_set_status(device, true, NULL); spa_bt_device_check_profiles(device, false); - if (device->connected_profiles != prev_connected) - spa_bt_device_emit_profiles_changed(device, device->profiles, prev_connected); + spa_bt_device_emit_profiles_changed(device, profile); return 0; } @@ -2457,8 +2462,7 @@ static int device_update_props(struct spa_bt_device *device, } if (device->profiles != prev_profiles) - spa_bt_device_emit_profiles_changed( - device, prev_profiles, device->connected_profiles); + spa_bt_device_emit_profiles_changed(device, 0); } else if (spa_streq(key, "Sets")) { device_update_device_sets_prop(device, &it[1]); @@ -2494,12 +2498,13 @@ bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const stru { SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX, SPA_BT_FEATURE_A2DP_DUPLEX }, { SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, SPA_BT_FEATURE_A2DP_DUPLEX }, }; + bool is_a2dp = !codec->bap && !codec->asha; size_t i; if (!is_media_codec_enabled(device->monitor, codec)) return false; - if (!device->adapter->a2dp_application_registered && !codec->bap) { + if (!device->adapter->a2dp_application_registered && is_a2dp) { /* Codec switching not supported: only plain SBC allowed */ return (codec->codec_id == A2DP_CODEC_SBC && spa_streq(codec->name, "sbc") && device->adapter->legacy_endpoints_registered); @@ -2646,6 +2651,8 @@ static struct spa_bt_device *create_bcast_device(struct spa_bt_monitor *monitor, return d; } +static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, struct spa_bt_monitor *monitor); + static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_endpoint, DBusMessageIter *props_iter, DBusMessageIter *invalidated_iter) @@ -2699,6 +2706,11 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en spa_list_append(&device->remote_endpoint_list, &remote_endpoint->device_link); } } + /* For ASHA */ + else if (spa_streq(key, "Transport")) { + free(remote_endpoint->transport_path); + remote_endpoint->transport_path = strdup(value); + } } else if (type == DBUS_TYPE_BOOLEAN) { int value; @@ -2722,6 +2734,16 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en remote_endpoint->codec = value; } } + /* Codecs property is present for ASHA */ + else if (type == DBUS_TYPE_UINT16) { + uint16_t value; + + dbus_message_iter_get_basic(&it[1], &value); + + if (spa_streq(key, "Codecs")) { + spa_log_debug(monitor->log, "remote_endpoint %p: %s=%02x", remote_endpoint, key, value); + } + } else if (spa_streq(key, "Capabilities")) { DBusMessageIter iter; uint8_t *value; @@ -2745,6 +2767,23 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en remote_endpoint->capabilities_len = len; } } + /* HiSyncId property is present for ASHA */ + else if (spa_streq(key, "HiSyncId")) { + /* + * TODO: Required for Stereo support in ASHA, for now just log. + */ + DBusMessageIter iter; + uint8_t *value; + int len; + + if (!check_iter_signature(&it[1], "ay")) + goto next; + + dbus_message_iter_recurse(&it[1], &iter); + dbus_message_iter_get_fixed_array(&iter, &value, &len); + + spa_log_debug(monitor->log, "remote_endpoint %p: %s=%d", remote_endpoint, key, len); + } else spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key); @@ -2762,6 +2801,11 @@ next: profile = spa_bt_profile_from_uuid(remote_endpoint->uuid); if (profile & SPA_BT_PROFILE_BAP_AUDIO) spa_bt_device_add_profile(remote_endpoint->device, profile); + + if (spa_streq(remote_endpoint->uuid, SPA_BT_UUID_ASHA_SINK)) { + if (profile & SPA_BT_PROFILE_ASHA_SINK) + setup_asha_transport(remote_endpoint, monitor); + } } return 0; @@ -2795,6 +2839,7 @@ static void remote_endpoint_free(struct spa_bt_remote_endpoint *remote_endpoint) spa_list_remove(&remote_endpoint->link); free(remote_endpoint->path); + free(remote_endpoint->transport_path); free(remote_endpoint->uuid); free(remote_endpoint->capabilities); free(remote_endpoint); @@ -2904,7 +2949,6 @@ void spa_bt_transport_free(struct spa_bt_transport *transport) { struct spa_bt_monitor *monitor = transport->monitor; struct spa_bt_device *device = transport->device; - uint32_t prev_connected = 0; spa_log_debug(monitor->log, "transport %p: free %s", transport, transport->path); @@ -2931,7 +2975,8 @@ void spa_bt_transport_free(struct spa_bt_transport *transport) cancel_and_unref(&transport->volume_call); if (transport->fd >= 0) { - spa_bt_player_set_state(transport->device->adapter->dummy_player, SPA_BT_PLAYER_STOPPED); + if (device) + spa_bt_player_set_state(device->adapter->dummy_player, SPA_BT_PLAYER_STOPPED); shutdown(transport->fd, SHUT_RDWR); close(transport->fd); @@ -2939,16 +2984,20 @@ void spa_bt_transport_free(struct spa_bt_transport *transport) } spa_list_remove(&transport->link); - if (transport->device) { - prev_connected = transport->device->connected_profiles; - transport->device->connected_profiles &= ~transport->profile; + if (device) { + struct spa_bt_transport *t; + uint32_t disconnected = transport->profile; + spa_list_remove(&transport->device_link); - } - if (device && device->connected_profiles != prev_connected) { - if ((prev_connected ^ device->connected_profiles) & SPA_BT_PROFILE_BAP_DUPLEX) + spa_list_for_each(t, &device->transport_list, device_link) + disconnected &= ~t->profile; + device->connected_profiles &= ~disconnected; + + if (transport->profile & SPA_BT_PROFILE_BAP_DUPLEX) device_update_set_status(device, true, NULL); - spa_bt_device_emit_profiles_changed(device, device->profiles, prev_connected); + + spa_bt_device_emit_profiles_changed(device, transport->profile); } spa_list_remove(&transport->bap_transport_linked); @@ -3165,6 +3214,8 @@ static void spa_bt_transport_volume_changed(struct spa_bt_transport *transport) volume_id = SPA_BT_VOLUME_ID_TX; else if (transport->profile & SPA_BT_PROFILE_A2DP_SOURCE) volume_id = SPA_BT_VOLUME_ID_RX; + else if (transport->profile & SPA_BT_PROFILE_ASHA_SINK) + volume_id = SPA_BT_VOLUME_ID_TX; else return; @@ -3406,6 +3457,12 @@ static int transport_update_props(struct spa_bt_transport *transport, t_volume = &transport->volumes[SPA_BT_VOLUME_ID_TX]; else if (transport->profile & SPA_BT_PROFILE_A2DP_SOURCE) t_volume = &transport->volumes[SPA_BT_VOLUME_ID_RX]; + else if (transport->profile & SPA_BT_PROFILE_ASHA_SINK) + t_volume = &transport->volumes[SPA_BT_VOLUME_ID_TX]; + else if (transport->profile & SPA_BT_PROFILE_BAP_SINK) + t_volume = &transport->volumes[SPA_BT_VOLUME_ID_TX]; + else if (transport->profile & SPA_BT_PROFILE_BAP_SOURCE) + t_volume = &transport->volumes[SPA_BT_VOLUME_ID_RX]; else goto next; @@ -3455,6 +3512,9 @@ static int transport_update_props(struct spa_bt_transport *transport, if (!check_iter_signature(&it[1], "ao")) goto next; + spa_list_remove(&transport->bap_transport_linked); + spa_list_init(&transport->bap_transport_linked); + dbus_message_iter_recurse(&it[1], &iter); while (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_INVALID) { const char *transport_path; @@ -3554,7 +3614,7 @@ static int transport_set_volume(void *data, int id, float volume) if (!t_volume->active || !spa_bt_transport_volume_enabled(transport)) return -ENOTSUP; - value = spa_bt_volume_linear_to_hw(volume, 127); + value = spa_bt_volume_linear_to_hw(volume, t_volume->hw_volume_max); t_volume->volume = volume; /* AVRCP volume would not applied on remote sink device @@ -3684,21 +3744,32 @@ static void transport_acquire_reply(DBusPendingCall *pending, void *user_data) transport_sync_volume(transport); finish: - if (ret < 0) + if (ret < 0) { spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ERROR); - else { + + /* For broadcast, skip handling links. Each link acquire + * is handled separately. + */ + if ((transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) || + (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) + return; + } else { if (transport_create_iso_io(transport) < 0) spa_log_error(monitor->log, "transport %p: transport_create_iso_io failed", transport); - /* For broadcast the initiator moves the transport state to SPA_BT_TRANSPORT_STATE_ACTIVE */ + /* For broadcast, each transport has a different fd, so it needs to be + * acquired independently from others. Each transport moves to + * SPA_BT_TRANSPORT_STATE_ACTIVE after acquire is completed. + */ /* TODO: handling multiple BIGs support */ if ((transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) || (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) { spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ACTIVE); - } else { - if (!transport->bap_initiator) - spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ACTIVE); + return; } + + if (!transport->bap_initiator) + spa_bt_transport_set_state(transport, SPA_BT_TRANSPORT_STATE_ACTIVE); } /* For LE Audio, multiple transport from the same device may share the same @@ -3758,6 +3829,13 @@ static int do_transport_acquire(struct spa_bt_transport *transport) spa_autoptr(DBusMessage) m = NULL; struct spa_bt_transport *t_linked; + if ((transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) || + (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) + /* For Broadcast, all linked transports need to be + * acquired independently, since they have different fds. + */ + goto acquire; + spa_list_for_each(t_linked, &transport->bap_transport_linked, bap_transport_linked) { /* If a linked transport has been acquired, it will do all the work */ if (t_linked->acquire_call || t_linked->acquired) { @@ -3768,6 +3846,7 @@ static int do_transport_acquire(struct spa_bt_transport *transport) } } +acquire: if (transport->acquire_call) return -EBUSY; @@ -3862,10 +3941,17 @@ static int do_transport_release(struct spa_bt_transport *transport) transport->iso_io = NULL; } - /* For LE Audio, multiple transport stream (CIS) can be linked together (CIG). + /* For Unicast LE Audio, multiple transport stream (CIS) can be linked together (CIG). * If they are part of the same device they reuse the same fd, and call to * release should be done for the last one only. + * + * For Broadcast LE Audio, since linked transports have different fds, they + * should be released independently. */ + if ((transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SINK) || + (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) + goto release; + spa_list_for_each(t_linked, &transport->bap_transport_linked, bap_transport_linked) { if (t_linked->acquire_call || t_linked->acquired) { linked = true; @@ -3878,6 +3964,7 @@ static int do_transport_release(struct spa_bt_transport *transport) return 0; } +release: if (transport->fd >= 0) { close(transport->fd); transport->fd = -1; @@ -3951,14 +4038,117 @@ static int transport_release(void *data) return do_transport_release(data); } +static int transport_set_delay(void *data, int64_t delay_nsec) +{ + struct spa_bt_transport *transport = data; + struct spa_bt_monitor *monitor = transport->monitor; + DBusMessageIter it[2]; + spa_autoptr(DBusMessage) m = NULL; + uint16_t value; + const char *property = "Delay", *interface = BLUEZ_MEDIA_TRANSPORT_INTERFACE; + + if (!(transport->profile & SPA_BT_PROFILE_A2DP_DUPLEX)) + return -ENOTSUP; + + value = SPA_CLAMP(delay_nsec / (100 * SPA_NSEC_PER_USEC), 0, 10 * UINT16_MAX); + + if (transport->delay_us == 100 * value) + return 0; + transport->delay_us = 100 * value; + + m = dbus_message_new_method_call(BLUEZ_SERVICE, transport->path, DBUS_INTERFACE_PROPERTIES, "Set"); + if (m == NULL) + return -ENOMEM; + + dbus_message_iter_init_append(m, &it[0]); + dbus_message_iter_append_basic(&it[0], DBUS_TYPE_STRING, &interface); + dbus_message_iter_append_basic(&it[0], DBUS_TYPE_STRING, &property); + dbus_message_iter_open_container(&it[0], DBUS_TYPE_VARIANT, DBUS_TYPE_UINT16_AS_STRING, &it[1]); + dbus_message_iter_append_basic(&it[1], DBUS_TYPE_UINT16, &value); + dbus_message_iter_close_container(&it[0], &it[1]); + + if (!dbus_connection_send(monitor->conn, m, NULL)) + return -EIO; + + spa_log_debug(monitor->log, "transport %p: set delay %d us", transport, 100 * value); + return 0; +} static const struct spa_bt_transport_implementation transport_impl = { SPA_VERSION_BT_TRANSPORT_IMPLEMENTATION, .acquire = transport_acquire, .release = transport_release, .set_volume = transport_set_volume, + .set_delay = transport_set_delay, }; +static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, struct spa_bt_monitor *monitor) +{ + const struct media_codec * const * const media_codecs = monitor->media_codecs; + const struct media_codec *codec = NULL; + struct spa_bt_transport *transport; + char *tpath; + + if (!remote_endpoint->transport_path) { + spa_log_error(monitor->log, "Missing ASHA transport path"); + return -EINVAL; + } + + transport = spa_bt_transport_find(monitor, remote_endpoint->transport_path); + if (transport != NULL) { + spa_log_debug(monitor->log, "transport %p: free %s", + transport, transport->path); + spa_bt_transport_free(transport); + } + + tpath = strdup(remote_endpoint->transport_path); + transport = spa_bt_transport_create(monitor, tpath, 0); + if (transport == NULL) { + spa_log_error(monitor->log, "Failed to create transport for %s", + remote_endpoint->transport_path); + free(tpath); + return -EINVAL; + } + + spa_bt_transport_set_implementation(transport, &transport_impl, transport); + + spa_log_debug(monitor->log, "Created ASHA transport for %s", remote_endpoint->transport_path); + + for (int i = 0; media_codecs[i]; i++) { + const struct media_codec *mcodec = media_codecs[i]; + if (!mcodec->asha) + continue; + if (!spa_streq(mcodec->name, "g722")) + continue; + codec = mcodec; + spa_log_debug(monitor->log, "Setting ASHA codec: %s", mcodec->name); + } + + free(transport->endpoint_path); + transport->endpoint_path = strdup(remote_endpoint->path); + transport->profile = SPA_BT_PROFILE_ASHA_SINK; + transport->media_codec = codec; + transport->device = remote_endpoint->device; + + spa_list_append(&remote_endpoint->device->transport_list, &transport->device_link); + + spa_bt_device_update_last_bluez_action_time(transport->device); + + transport->volumes[SPA_BT_VOLUME_ID_TX].active = true; + transport->volumes[SPA_BT_VOLUME_ID_TX].volume = DEFAULT_TX_VOLUME; + transport->n_channels = 1; + transport->channels[0] = SPA_AUDIO_CHANNEL_MONO; + + spa_bt_device_add_profile(transport->device, transport->profile); + spa_bt_device_connect_profile(transport->device, transport->profile); + + transport_sync_volume(transport); + + spa_log_debug(monitor->log, "ASHA transport setup complete"); + + return 0; +} + static void media_codec_switch_reply(DBusPendingCall *pending, void *userdata); static int media_codec_switch_cmp(const void *a, const void *b); @@ -4506,7 +4696,10 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn, for (int i = 0; i < SPA_BT_VOLUME_ID_TERM; ++i) { transport->volumes[i].hw_volume = SPA_BT_VOLUME_INVALID; - transport->volumes[i].hw_volume_max = SPA_BT_VOLUME_A2DP_MAX; + if (profile & SPA_BT_PROFILE_BAP_AUDIO) + transport->volumes[i].hw_volume_max = SPA_BT_VOLUME_BAP_MAX; + else + transport->volumes[i].hw_volume_max = SPA_BT_VOLUME_A2DP_MAX; } free(transport->endpoint_path); @@ -4722,7 +4915,7 @@ static int bluez_register_endpoint_legacy(struct spa_bt_adapter *adapter, if (ret < 0) return ret; - ret = caps_size = codec->fill_caps(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, caps); + ret = caps_size = codec->fill_caps(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, &monitor->global_settings, caps); if (ret < 0) return ret; @@ -4828,7 +5021,7 @@ static void append_media_object(DBusMessageIter *iter, const char *endpoint, 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) { + if (spa_bt_profile_from_uuid(uuid) & (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_A2DP_SOURCE)) { dbus_bool_t delay_reporting = TRUE; append_basic_variant_dict_entry(&dict, "DelayReporting", DBUS_TYPE_BOOLEAN, "b", &delay_reporting); @@ -4914,12 +5107,14 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * if (codec->bap != is_bap) continue; + if (codec->asha) + continue; if (!is_media_codec_enabled(monitor, codec)) continue; if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK)) { - caps_size = codec->fill_caps(codec, MEDIA_CODEC_FLAG_SINK, caps); + caps_size = codec->fill_caps(codec, MEDIA_CODEC_FLAG_SINK, &monitor->global_settings, caps); if (caps_size < 0) continue; @@ -4934,7 +5129,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * } if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE)) { - caps_size = codec->fill_caps(codec, 0, caps); + caps_size = codec->fill_caps(codec, 0, &monitor->global_settings, caps); if (caps_size < 0) continue; @@ -4950,7 +5145,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * if (codec->bap && register_bcast) { if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SOURCE_BROADCAST)) { - caps_size = codec->fill_caps(codec, 0, caps); + caps_size = codec->fill_caps(codec, 0, &monitor->global_settings, caps); if (caps_size < 0) continue; @@ -4965,7 +5160,7 @@ static DBusHandlerResult object_manager_handler(DBusConnection *c, DBusMessage * } if (endpoint_should_be_registered(monitor, codec, SPA_BT_MEDIA_SINK_BROADCAST)) { - caps_size = codec->fill_caps(codec, MEDIA_CODEC_FLAG_SINK, caps); + caps_size = codec->fill_caps(codec, MEDIA_CODEC_FLAG_SINK, &monitor->global_settings, caps); if (caps_size < 0) continue; @@ -5310,8 +5505,6 @@ static void configure_bis(struct spa_bt_monitor *monitor, int options = 0; int skip = 0; int sync_cte_type = 0; - /* sync_factor should be >=2 to avoid invalid extended advertising interval value */ - int sync_factor = 2; int sync_timeout = 2000; int timeout = 2000; @@ -5364,7 +5557,12 @@ static void configure_bis(struct spa_bt_monitor *monitor, append_basic_variant_dict_entry(&qos_dict, "BIG", DBUS_TYPE_BYTE, "y", &big->big_id); append_basic_variant_dict_entry(&qos_dict, "BIS", DBUS_TYPE_BYTE, "y", &bis_id); - append_basic_variant_dict_entry(&qos_dict, "SyncFactor", DBUS_TYPE_BYTE, "y", &sync_factor); + + /* sync_factor should be >=2 to avoid invalid extended advertising interval value */ + if (big->sync_factor < 2) + big->sync_factor = 2; + + append_basic_variant_dict_entry(&qos_dict, "SyncFactor", DBUS_TYPE_BYTE, "y", &big->sync_factor); append_basic_variant_dict_entry(&qos_dict, "Options", DBUS_TYPE_BYTE, "y", &options); append_basic_variant_dict_entry(&qos_dict, "Skip", DBUS_TYPE_UINT16, "q", &skip); append_basic_variant_dict_entry(&qos_dict, "SyncTimeout", DBUS_TYPE_UINT16, "q", &sync_timeout); @@ -5462,6 +5660,8 @@ static void interface_added(struct spa_bt_monitor *monitor, object_path); return; } + spa_log_info(monitor->log, "Created Bluetooth device %s", + object_path); } device_update_props(d, props_iter, NULL); @@ -5496,7 +5696,7 @@ static void interface_added(struct spa_bt_monitor *monitor, d = ep->device; if (d) - spa_bt_device_emit_profiles_changed(d, d->profiles, d->connected_profiles); + spa_bt_device_emit_profiles_changed(d, 0); if (spa_streq(ep->uuid, SPA_BT_UUID_BAP_BROADCAST_SINK)) { int ret, i; @@ -5590,7 +5790,7 @@ static void interfaces_removed(struct spa_bt_monitor *monitor, DBusMessageIter * struct spa_bt_device *d = ep->device; remote_endpoint_free(ep); if (d) - spa_bt_device_emit_profiles_changed(d, d->profiles, d->connected_profiles); + spa_bt_device_emit_profiles_changed(d, 0); } } else if (spa_streq(interface_name, BLUEZ_MEDIA_TRANSPORT_INTERFACE)) { struct spa_bt_transport *transport; @@ -5600,7 +5800,7 @@ static void interfaces_removed(struct spa_bt_monitor *monitor, DBusMessageIter * struct spa_bt_device *d = transport->device; if (d != NULL){ device_free(d); - } + } } else if (transport->profile == SPA_BT_PROFILE_BAP_BROADCAST_SOURCE) { /* * For each transport that has a broadcast source profile, @@ -5610,7 +5810,7 @@ static void interfaces_removed(struct spa_bt_monitor *monitor, DBusMessageIter * * for this case will have the scanned device to the transport * "/fd0" and create new devices for the other transports from this device * that appear only in case of multiple BISes per BIG. - * + * * Here we delete the created devices. */ char *pos = strstr(transport->path, "/fd0"); @@ -5858,7 +6058,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us d = ep->device; if (d) - spa_bt_device_emit_profiles_changed(d, d->profiles, d->connected_profiles); + spa_bt_device_emit_profiles_changed(d, 0); } else if (spa_streq(iface, BLUEZ_MEDIA_TRANSPORT_INTERFACE)) { struct spa_bt_transport *transport; @@ -6075,13 +6275,11 @@ impl_get_size(const struct spa_handle_factory *factory, int spa_bt_profiles_from_json_array(const char *str) { - struct spa_json it, it_array; + struct spa_json it_array; char role_name[256]; enum spa_bt_profile profiles = SPA_BT_PROFILE_NULL; - spa_json_init(&it, str, strlen(str)); - - if (spa_json_enter_array(&it, &it_array) <= 0) + if (spa_json_begin_array(&it_array, str, strlen(str)) <= 0) return -EINVAL; while (spa_json_get_string(&it_array, role_name, sizeof(role_name)) > 0) { @@ -6105,6 +6303,8 @@ int spa_bt_profiles_from_json_array(const char *str) profiles |= SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; } else if (spa_streq(role_name, "bap_bcast_sink")) { profiles |= SPA_BT_PROFILE_BAP_BROADCAST_SINK; + } else if (spa_streq(role_name, "asha_sink")) { + profiles |= SPA_BT_PROFILE_ASHA_SINK; } } @@ -6115,7 +6315,7 @@ static int parse_roles(struct spa_bt_monitor *monitor, const struct spa_dict *in { const char *str; int res = 0; - int profiles = SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_MEDIA_SOURCE; + int profiles = SPA_BT_PROFILE_MEDIA_SINK | SPA_BT_PROFILE_MEDIA_SOURCE | SPA_BT_PROFILE_ASHA_SINK; /* HSP/HFP backends parse this property separately */ if (info && (str = spa_dict_lookup(info, "bluez5.roles"))) { @@ -6144,7 +6344,7 @@ static void parse_broadcast_source_config(struct spa_bt_monitor *monitor, const char bcode[BROADCAST_CODE_LEN + 3]; int cursor; int big_id = 0; - struct spa_json it[4], it_array[4]; + struct spa_json it[3], it_array[4]; struct spa_list big_list = SPA_LIST_INIT(&big_list); struct spa_error_location loc; struct spa_bt_big *big; @@ -6153,14 +6353,12 @@ static void parse_broadcast_source_config(struct spa_bt_monitor *monitor, const if (!(info && (str = spa_dict_lookup(info, "bluez5.bcast_source.config")))) return; - spa_json_init(&it[0], str, strlen(str)); - /* Verify is an array of BIGS */ - if (spa_json_enter_array(&it[0], &it_array[0]) <= 0) + if (spa_json_begin_array(&it_array[0], str, strlen(str)) <= 0) goto parse_failed; /* Iterate on all BIG objects */ - while (spa_json_enter_object(&it_array[0], &it[1]) > 0) { + while (spa_json_enter_object(&it_array[0], &it[0]) > 0) { struct spa_bt_big *big_entry = calloc(1, sizeof(struct spa_bt_big)); if (!big_entry) @@ -6171,22 +6369,26 @@ static void parse_broadcast_source_config(struct spa_bt_monitor *monitor, const spa_list_append(&big_list, &big_entry->link); /* Iterate on all BIG values */ - while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { + while (spa_json_get_string(&it[0], key, sizeof(key)) > 0) { if (spa_streq(key, "broadcast_code")) { - if (spa_json_get_string(&it[1], bcode, sizeof(bcode)) <= 0) + if (spa_json_get_string(&it[0], bcode, sizeof(bcode)) <= 0) goto parse_failed; if (strlen(bcode) > BROADCAST_CODE_LEN) goto parse_failed; memcpy(big_entry->broadcast_code, bcode, strlen(bcode)); spa_log_debug(monitor->log, "big_entry->broadcast_code %s", big_entry->broadcast_code); } else if (spa_streq(key, "encryption")) { - if (spa_json_get_bool(&it[1], &big_entry->encryption) <= 0) + if (spa_json_get_bool(&it[0], &big_entry->encryption) <= 0) goto parse_failed; spa_log_debug(monitor->log, "big_entry->encryption %d", big_entry->encryption); + } else if (spa_streq(key, "sync_factor")) { + if (spa_json_get_int(&it[0], &big_entry->sync_factor) <= 0) + goto parse_failed; + spa_log_debug(monitor->log, "big_entry->sync_factor %d", big_entry->sync_factor); } else if (spa_streq(key, "bis")) { - if (spa_json_enter_array(&it[1], &it_array[1]) <= 0) + if (spa_json_enter_array(&it[0], &it_array[1]) <= 0) goto parse_failed; - while (spa_json_enter_object(&it_array[1], &it[2]) > 0) { + while (spa_json_enter_object(&it_array[1], &it[1]) > 0) { /* Iterate on all BIS values */ struct spa_bt_bis *bis_entry = calloc(1, sizeof(struct spa_bt_bis)); @@ -6196,19 +6398,19 @@ static void parse_broadcast_source_config(struct spa_bt_monitor *monitor, const spa_list_init(&bis_entry->metadata_list); spa_list_append(&big_entry->bis_list, &bis_entry->link); - while (spa_json_get_string(&it[2], bis_key, sizeof(bis_key)) > 0) { + while (spa_json_get_string(&it[1], bis_key, sizeof(bis_key)) > 0) { if (spa_streq(bis_key, "qos_preset")) { - if (spa_json_get_string(&it[2], bis_entry->qos_preset, sizeof(bis_entry->qos_preset)) <= 0) + if (spa_json_get_string(&it[1], bis_entry->qos_preset, sizeof(bis_entry->qos_preset)) <= 0) goto parse_failed; spa_log_debug(monitor->log, "bis_entry->qos_preset %s", bis_entry->qos_preset); } else if (spa_streq(bis_key, "audio_channel_allocation")) { - if (spa_json_get_int(&it[2], &bis_entry->channel_allocation) <= 0) + if (spa_json_get_int(&it[1], &bis_entry->channel_allocation) <= 0) goto parse_failed; spa_log_debug(monitor->log, "bis_entry->channel_allocation %d", bis_entry->channel_allocation); } else if (spa_streq(bis_key, "metadata")) { - if (spa_json_enter_array(&it[2], &it_array[2]) <= 0) + if (spa_json_enter_array(&it[1], &it_array[2]) <= 0) goto parse_failed; - while (spa_json_enter_object(&it_array[2], &it[3]) > 0) { + while (spa_json_enter_object(&it_array[2], &it[2]) > 0) { struct spa_bt_metadata *metadata_entry = calloc(1, sizeof(struct spa_bt_metadata)); if (!metadata_entry) @@ -6216,13 +6418,13 @@ static void parse_broadcast_source_config(struct spa_bt_monitor *monitor, const spa_list_append(&bis_entry->metadata_list, &metadata_entry->link); - while (spa_json_get_string(&it[3], qos_key, sizeof(qos_key)) > 0) { + while (spa_json_get_string(&it[2], qos_key, sizeof(qos_key)) > 0) { if (spa_streq(qos_key, "type")) { - if (spa_json_get_int(&it[3], &metadata_entry->type) <= 0) + if (spa_json_get_int(&it[2], &metadata_entry->type) <= 0) goto parse_failed; spa_log_debug(monitor->log, "metadata_entry->type %d", metadata_entry->type); } else if (spa_streq(qos_key, "value")) { - if (spa_json_enter_array(&it[3], &it_array[3]) <= 0) + if (spa_json_enter_array(&it[2], &it_array[3]) <= 0) goto parse_failed; for (cursor = 0; cursor < METADATA_MAX_LEN - 1; cursor++) { int temp_val = 0; @@ -6254,7 +6456,7 @@ errno_failed: parse_failed: str = spa_dict_lookup(info, "bluez5.bcast_source.config"); - if (spa_json_get_error(&it[0], str, &loc)) { + if (spa_json_get_error(&it_array[0], str, &loc)) { spa_debug_log_error_location(monitor->log, SPA_LOG_LEVEL_WARN, &loc, "malformed bluez5.bcast_source.config: %s", loc.reason); } else { @@ -6272,7 +6474,7 @@ static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict 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; + struct spa_json it_array; char codec_name[256]; size_t num_codecs; int i; @@ -6290,9 +6492,7 @@ static int parse_codec_array(struct spa_bt_monitor *this, const struct spa_dict if (info == NULL || (str = spa_dict_lookup(info, "bluez5.codecs")) == NULL) goto fallback; - spa_json_init(&it, str, strlen(str)); - - if (spa_json_enter_array(&it, &it_array) <= 0) { + if (spa_json_begin_array(&it_array, str, strlen(str)) <= 0) { spa_log_error(this->log, "property bluez5.codecs '%s' is not an array", str); goto fallback; } diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c index 009d1586..2095ba82 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -59,9 +59,19 @@ enum { DEVICE_PROFILE_A2DP = 2, DEVICE_PROFILE_HSP_HFP = 3, DEVICE_PROFILE_BAP = 4, + DEVICE_PROFILE_ASHA = 5, DEVICE_PROFILE_LAST, }; +enum { + ROUTE_INPUT = 0, + ROUTE_OUTPUT, + ROUTE_HF_OUTPUT, + ROUTE_SET_INPUT, + ROUTE_SET_OUTPUT, + ROUTE_LAST, +}; + struct props { enum spa_bluetooth_audio_codec codec; bool offload_active; @@ -112,6 +122,8 @@ struct device_set_member { struct device_set { struct impl *impl; char *path; + bool sink_enabled; + bool source_enabled; bool leader; uint32_t sinks; uint32_t sources; @@ -502,12 +514,7 @@ static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t static const char *get_channel_name(uint32_t channel) { - int i; - for (i = 0; spa_type_audio_channel[i].name; i++) { - if (spa_type_audio_channel[i].type == channel) - return spa_debug_type_short_name(spa_type_audio_channel[i].name); - } - return NULL; + return spa_type_to_short_name(channel, spa_type_audio_channel, NULL); } static int channel_position_cmp(const void *pa, const void *pb) @@ -521,7 +528,7 @@ static void emit_device_set_node(struct impl *this, uint32_t id) struct spa_bt_device *device = this->bt_dev; struct node *node = &this->nodes[id]; struct spa_device_object_info info; - struct spa_dict_item items[7]; + struct spa_dict_item items[8]; char str_id[32], members_json[8192], channels_json[512]; struct device_set_member *members; uint32_t n_members; @@ -534,6 +541,7 @@ static void emit_device_set_node(struct impl *this, uint32_t id) items[n_items++] = SPA_DICT_ITEM_INIT("api.bluez5.set.leader", "true"); snprintf(str_id, sizeof(str_id), "%d", id); items[n_items++] = SPA_DICT_ITEM_INIT("card.profile.device", str_id); + items[n_items++] = SPA_DICT_ITEM_INIT("device.routes", "1"); if (id == DEVICE_ID_SOURCE_SET) { items[n_items++] = SPA_DICT_ITEM_INIT("media.class", "Audio/Source"); @@ -646,9 +654,9 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t, spa_log_debug(this->log, "%p: node, transport:%p id:%08x factory:%s", this, t, id, factory_name); if (id & SINK_ID_FLAG) - in_device_set = this->device_set.path && (this->device_set.sinks > 1); + in_device_set = this->device_set.sink_enabled; else - in_device_set = this->device_set.path && (this->device_set.sources > 1); + in_device_set = this->device_set.source_enabled; snprintf(transport, sizeof(transport), "pointer:%p", t); items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_API_BLUEZ5_TRANSPORT, transport); @@ -694,7 +702,7 @@ static void emit_node(struct impl *this, struct spa_bt_transport *t, spa_device_emit_object_info(&this->hooks, id, &info); - if (this->device_set.path) { + if (in_device_set) { /* Device set member nodes don't have their own routes */ this->nodes[id].impl = this; this->nodes[id].active = false; @@ -917,9 +925,8 @@ static void remove_dynamic_node(struct dynamic_node *this) this->factory_name = NULL; } -static void device_set_clear(struct impl *impl) +static void device_set_clear(struct impl *impl, struct device_set *set) { - struct device_set *set = &impl->device_set; unsigned int i; for (i = 0; i < SPA_N_ELEMENTS(set->sink); ++i) @@ -953,10 +960,9 @@ static const struct spa_bt_transport_events device_set_transport_events = { .destroy = device_set_transport_destroy, }; -static void device_set_update(struct impl *this) +static void device_set_update(struct impl *this, struct device_set *dset) { struct spa_bt_device *device = this->bt_dev; - struct device_set *dset = &this->device_set; struct spa_bt_set_membership *set; struct spa_bt_set_membership tmp_set = { .device = device, @@ -981,7 +987,7 @@ static void device_set_update(struct impl *this) struct spa_bt_set_membership *s; int num_devices = 0; - device_set_clear(this); + device_set_clear(this, dset); spa_bt_for_each_set_member(s, set) { struct spa_bt_transport *t; @@ -1049,6 +1055,26 @@ static void device_set_update(struct impl *this) if (num_devices > 1) break; } + + dset->sink_enabled = dset->path && (dset->sinks > 1); + dset->source_enabled = dset->path && (dset->sources > 1); +} + +static bool device_set_equal(struct device_set *a, struct device_set *b) +{ + unsigned int i; + + if (!spa_streq(a->path, b->path) || a->sink_enabled != b->sink_enabled || + a->source_enabled != b->source_enabled || a->leader != b->leader || + a->sinks != b->sinks || a->sources != b->sources) + return false; + for (i = 0; i < a->sinks; ++i) + if (a->sink[i].transport != b->sink[i].transport) + return false; + for (i = 0; i < a->sources; ++i) + if (a->source[i].transport != b->source[i].transport) + return false; + return true; } static int emit_nodes(struct impl *this) @@ -1057,7 +1083,7 @@ static int emit_nodes(struct impl *this) this->props.codec = 0; - device_set_update(this); + device_set_update(this, &this->device_set); switch (this->profile) { case DEVICE_PROFILE_OFF: @@ -1084,6 +1110,17 @@ static int emit_nodes(struct impl *this) } } break; + case DEVICE_PROFILE_ASHA: + if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_ASHA_SINK) { + t = find_transport(this, SPA_BT_PROFILE_ASHA_SINK); + if (t) { + this->props.codec = t->media_codec->id; + emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_MEDIA_SINK, false); + } else { + spa_log_warn(this->log, "Unable to find transport for ASHA"); + } + } + break; case DEVICE_PROFILE_A2DP: if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) { t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE); @@ -1139,7 +1176,7 @@ static int emit_nodes(struct impl *this) emit_dynamic_node(this, t, id, SPA_NAME_API_BLUEZ5_MEDIA_SOURCE, false); } - if (set->path && set->leader && set->sources > 1) + if (set->source_enabled && set->leader) emit_device_set_node(this, DEVICE_ID_SOURCE_SET); for (i = 0; i < set->sinks; ++i) { @@ -1158,7 +1195,7 @@ static int emit_nodes(struct impl *this) emit_dynamic_node(this, t, id, SPA_NAME_API_BLUEZ5_MEDIA_SINK, false); } - if (set->path && set->leader && set->sinks > 1) + if (set->sink_enabled && set->leader) emit_device_set_node(this, DEVICE_ID_SINK_SET); if (this->bt_dev->connected_profiles & (SPA_BT_PROFILE_BAP_BROADCAST_SINK)) { @@ -1167,9 +1204,6 @@ static int emit_nodes(struct impl *this) this->props.codec = t->media_codec->id; emit_node(this, t, DEVICE_ID_SINK, SPA_NAME_API_BLUEZ5_MEDIA_SINK, false); } - - if (this->device_set.leader && this->device_set.sinks > 0) - emit_device_set_node(this, DEVICE_ID_SINK_SET); } if (this->bt_dev->connected_profiles & (SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)) { @@ -1263,8 +1297,9 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a this->save_profile = save; if (this->profile == profile && + (this->profile != DEVICE_PROFILE_ASHA || codec == this->props.codec) && (this->profile != DEVICE_PROFILE_A2DP || codec == this->props.codec) && - (this->profile != DEVICE_PROFILE_BAP || codec == this->props.codec || this->device_set.path) && + (this->profile != DEVICE_PROFILE_BAP || codec == this->props.codec) && (this->profile != DEVICE_PROFILE_HSP_HFP || codec == this->props.codec)) return 0; @@ -1355,20 +1390,29 @@ static void codec_switched(void *userdata, int status) emit_info(this, false); } -static void profiles_changed(void *userdata, uint32_t prev_profiles, uint32_t prev_connected_profiles) +static bool device_set_needs_update(struct impl *this) +{ + struct device_set dset = { .impl = this }; + bool changed; + + if (this->profile != DEVICE_PROFILE_BAP) + return false; + + device_set_update(this, &dset); + changed = !device_set_equal(&dset, &this->device_set); + device_set_clear(this, &dset); + return changed; +} + +static void profiles_changed(void *userdata, uint32_t connected_change) { struct impl *this = userdata; - uint32_t connected_change; bool nodes_changed = false; - connected_change = (this->bt_dev->connected_profiles ^ prev_connected_profiles); - /* Profiles changed. We have to re-emit device information. */ - spa_log_info(this->log, "profiles changed to %08x %08x (prev %08x %08x, change %08x)" - " switching_codec:%d", - this->bt_dev->profiles, this->bt_dev->connected_profiles, - prev_profiles, prev_connected_profiles, connected_change, - this->switching_codec); + spa_log_info(this->log, "profiles changed to %08x %08x (change %08x) switching_codec:%d", + this->bt_dev->profiles, this->bt_dev->connected_profiles, + connected_change, this->switching_codec); if (this->switching_codec) return; @@ -1384,15 +1428,26 @@ 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_MEDIA_SOURCE)); + SPA_BT_PROFILE_A2DP_SOURCE)); spa_log_debug(this->log, "profiles changed: AG nodes changed: %d", nodes_changed); break; + case DEVICE_PROFILE_ASHA: + nodes_changed = (connected_change & SPA_BT_PROFILE_ASHA_SINK); + spa_log_debug(this->log, "profiles changed: ASHA nodes changed: %d", + nodes_changed); + break; case DEVICE_PROFILE_A2DP: + nodes_changed = (connected_change & SPA_BT_PROFILE_A2DP_DUPLEX); + spa_log_debug(this->log, "profiles changed: A2DP nodes changed: %d", + nodes_changed); + break; case DEVICE_PROFILE_BAP: - 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 = ((connected_change & SPA_BT_PROFILE_BAP_DUPLEX) + && device_set_needs_update(this)) + || (connected_change & (SPA_BT_PROFILE_BAP_BROADCAST_SINK | + SPA_BT_PROFILE_BAP_BROADCAST_SOURCE)); + spa_log_debug(this->log, "profiles changed: BAP nodes changed: %d", nodes_changed); break; case DEVICE_PROFILE_HSP_HFP: @@ -1424,6 +1479,11 @@ static void device_set_changed(void *userdata) if (this->profile != DEVICE_PROFILE_BAP) return; + if (!device_set_needs_update(this)) { + spa_log_debug(this->log, "%p: device set not changed", this); + return; + } + spa_log_debug(this->log, "%p: device set changed", this); emit_remove_nodes(this); @@ -1545,6 +1605,10 @@ static uint32_t profile_direction_mask(struct impl *this, uint32_t index, enum s if (device->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT) have_output = have_input = true; break; + case DEVICE_PROFILE_ASHA: + if (device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK) + have_input = true; + break; default: break; } @@ -1559,33 +1623,37 @@ static uint32_t profile_direction_mask(struct impl *this, uint32_t index, enum s static uint32_t get_profile_from_index(struct impl *this, uint32_t index, uint32_t *next, enum spa_bluetooth_audio_codec *codec) { - if (index < DEVICE_PROFILE_LAST) { - *codec = 0; - *next = index + 1; - return index; - } else if (index != SPA_ID_INVALID) { - const struct spa_type_info *info; - uint32_t profile; + uint32_t profile = (index >> 16); + const struct spa_type_info *info; - *codec = index - DEVICE_PROFILE_LAST; - *next = SPA_ID_INVALID; + switch (profile) { + case DEVICE_PROFILE_OFF: + case DEVICE_PROFILE_AG: + *codec = 0; + *next = (profile + 1) << 16; + return profile; + case DEVICE_PROFILE_ASHA: + *codec = SPA_BLUETOOTH_AUDIO_CODEC_G722; + *next = (profile + 1) << 16; + return profile; + case DEVICE_PROFILE_A2DP: + case DEVICE_PROFILE_HSP_HFP: + case DEVICE_PROFILE_BAP: + *codec = (index & 0xffff); + *next = (profile + 1) << 16; for (info = spa_type_bluetooth_audio_codec; info->type; ++info) if (info->type > *codec) - *next = SPA_MIN(info->type + DEVICE_PROFILE_LAST, *next); - - 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; - + *next = SPA_MIN(*next, (profile << 16) | (info->type & 0xffff)); return profile; + default: + *codec = 0; + *next = SPA_ID_INVALID; + profile = SPA_ID_INVALID; + break; } - *next = SPA_ID_INVALID; - return SPA_ID_INVALID; + return profile; } static uint32_t get_index_from_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_audio_codec codec) @@ -1593,19 +1661,41 @@ static uint32_t get_index_from_profile(struct impl *this, uint32_t profile, enum switch (profile) { case DEVICE_PROFILE_OFF: case DEVICE_PROFILE_AG: - return profile; + return (profile << 16); + + case DEVICE_PROFILE_ASHA: + return (profile << 16) | (SPA_BLUETOOTH_AUDIO_CODEC_G722 & 0xffff); case DEVICE_PROFILE_A2DP: case DEVICE_PROFILE_BAP: case DEVICE_PROFILE_HSP_HFP: if (!codec) return SPA_ID_INVALID; - return codec + DEVICE_PROFILE_LAST; + return (profile << 16) | (codec & 0xffff); } return SPA_ID_INVALID; } +static bool set_initial_asha_profile(struct impl *this) +{ + struct spa_bt_transport *t; + if (!(this->bt_dev->connected_profiles & SPA_BT_PROFILE_ASHA_SINK)) + return false; + + t = find_transport(this, SPA_BT_PROFILE_ASHA_SINK); + if (t) { + this->profile = DEVICE_PROFILE_ASHA; + this->props.codec = SPA_BLUETOOTH_AUDIO_CODEC_G722; + + spa_log_debug(this->log, "initial ASHA profile:%d codec:%d", + this->profile, this->props.codec); + return true; + } + + return false; +} + static bool set_initial_hsp_hfp_profile(struct impl *this) { struct spa_bt_transport *t; @@ -1647,13 +1737,16 @@ static void set_initial_profile(struct impl *this) /* If default profile is set to HSP/HFP, first try those and exit if found. */ if (this->bt_dev->settings != NULL) { const char *str = spa_dict_lookup(this->bt_dev->settings, "bluez5.profile"); + + if (spa_streq(str, "asha-sink") && set_initial_asha_profile(this)) + return; if (spa_streq(str, "off")) goto off; if (spa_streq(str, "headset-head-unit") && set_initial_hsp_hfp_profile(this)) return; } - for (i = SPA_BT_PROFILE_BAP_SINK; i <= SPA_BT_PROFILE_A2DP_SOURCE; i <<= 1) { + for (i = SPA_BT_PROFILE_BAP_SINK; i <= SPA_BT_PROFILE_ASHA_SINK; i <<= 1) { if (!(this->bt_dev->connected_profiles & i)) continue; @@ -1663,6 +1756,8 @@ static void set_initial_profile(struct impl *this) this->profile = DEVICE_PROFILE_AG; else if (i == SPA_BT_PROFILE_BAP_SINK) this->profile = DEVICE_PROFILE_BAP; + else if (i == SPA_BT_PROFILE_ASHA_SINK) + this->profile = DEVICE_PROFILE_ASHA; else this->profile = DEVICE_PROFILE_A2DP; this->props.codec = t->media_codec->id; @@ -1724,6 +1819,26 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * priority = 256; break; } + case DEVICE_PROFILE_ASHA: + { + uint32_t profile = device->connected_profiles & SPA_BT_PROFILE_ASHA_SINK; + + if (codec == 0) + return NULL; + if (profile == 0) + return NULL; + if (!(profile & SPA_BT_PROFILE_ASHA_SINK)) { + return NULL; + } + + name = spa_bt_profile_name(profile); + desc = _("Audio Streaming for Hearing Aids (ASHA Sink)"); + + n_sink++; + priority = 1; + + break; + } case DEVICE_PROFILE_A2DP: { /* make this device profile visible only if there is an A2DP sink */ @@ -1850,13 +1965,10 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder * priority = 128; } - if (this->device_set.leader) { - n_sink = this->device_set.sinks ? 1 : 0; - n_source = this->device_set.sinks ? 1 : 0; - } else if (this->device_set.path) { - n_sink = 0; - n_source = 0; - } + if (this->device_set.sink_enabled) + n_sink = this->device_set.leader ? 1 : 0; + if (this->device_set.source_enabled) + n_source = this->device_set.leader ? 1 : 0; break; } case DEVICE_PROFILE_HSP_HFP: @@ -1955,8 +2067,47 @@ static bool validate_profile(struct impl *this, uint32_t profile, return (build_profile(this, &b, 0, 0, profile, codec, false) != NULL); } +static bool profile_has_route(uint32_t profile, uint32_t route) +{ + switch (profile) { + case DEVICE_PROFILE_OFF: + case DEVICE_PROFILE_AG: + break; + case DEVICE_PROFILE_A2DP: + switch (route) { + case ROUTE_INPUT: + case ROUTE_OUTPUT: + return true; + } + break; + case DEVICE_PROFILE_HSP_HFP: + switch (route) { + case ROUTE_INPUT: + case ROUTE_HF_OUTPUT: + return true; + } + break; + case DEVICE_PROFILE_BAP: + switch (route) { + case ROUTE_INPUT: + case ROUTE_OUTPUT: + case ROUTE_SET_INPUT: + case ROUTE_SET_OUTPUT: + return true; + } + break; + case DEVICE_PROFILE_ASHA: + switch (route) { + case ROUTE_OUTPUT: + return true; + } + break; + } + return false; +} + static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, - uint32_t id, uint32_t port, uint32_t profile) + uint32_t id, uint32_t route, uint32_t profile) { struct spa_bt_device *device = this->bt_dev; struct spa_pod_frame f[2]; @@ -1967,7 +2118,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, enum spa_param_availability available; char name[128]; uint32_t i, j, mask, next; - uint32_t dev = SPA_ID_INVALID, enum_dev; + uint32_t dev; ff = spa_bt_form_factor_from_class(device->bluetooth_class); @@ -2035,86 +2186,60 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, break; } - switch (port) { - case 0: + switch (route) { + case ROUTE_INPUT: direction = SPA_DIRECTION_INPUT; snprintf(name, sizeof(name), "%s-input", name_prefix); - enum_dev = DEVICE_ID_SOURCE; + dev = DEVICE_ID_SOURCE; + available = this->device_set.source_enabled ? + SPA_PARAM_AVAILABILITY_no : SPA_PARAM_AVAILABILITY_yes; if ((this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SINK) && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) && !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_BAP_AUDIO) && (this->bt_dev->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT)) description = hfp_description; - - if (profile == DEVICE_PROFILE_A2DP || profile == DEVICE_PROFILE_BAP || - profile == DEVICE_PROFILE_HSP_HFP) - dev = enum_dev; - else if (profile != SPA_ID_INVALID) - enum_dev = SPA_ID_INVALID; break; - case 1: + case ROUTE_OUTPUT: direction = SPA_DIRECTION_OUTPUT; snprintf(name, sizeof(name), "%s-output", name_prefix); - enum_dev = DEVICE_ID_SINK; - if (profile == DEVICE_PROFILE_A2DP || profile == DEVICE_PROFILE_BAP) - dev = enum_dev; - else if (profile != SPA_ID_INVALID) - enum_dev = SPA_ID_INVALID; + dev = DEVICE_ID_SINK; + available = this->device_set.sink_enabled ? + SPA_PARAM_AVAILABILITY_no : SPA_PARAM_AVAILABILITY_yes; break; - case 2: + case ROUTE_HF_OUTPUT: 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; + dev = DEVICE_ID_SINK; + available = SPA_PARAM_AVAILABILITY_yes; break; - case 3: - if (!this->device_set.leader) { - errno = EINVAL; + case ROUTE_SET_INPUT: + if (!(this->device_set.source_enabled && this->device_set.leader)) return NULL; - } direction = SPA_DIRECTION_INPUT; snprintf(name, sizeof(name), "%s-set-input", name_prefix); - enum_dev = DEVICE_ID_SOURCE_SET; - if (profile == DEVICE_PROFILE_BAP) - dev = enum_dev; - else if (profile != SPA_ID_INVALID) - enum_dev = SPA_ID_INVALID; + dev = DEVICE_ID_SOURCE_SET; + available = SPA_PARAM_AVAILABILITY_yes; break; - case 4: - if (!this->device_set.leader) { - errno = EINVAL; + case ROUTE_SET_OUTPUT: + if (!(this->device_set.sink_enabled && this->device_set.leader)) return NULL; - } direction = SPA_DIRECTION_OUTPUT; snprintf(name, sizeof(name), "%s-set-output", name_prefix); - enum_dev = DEVICE_ID_SINK_SET; - if (profile == DEVICE_PROFILE_BAP) - dev = enum_dev; - else if (profile != SPA_ID_INVALID) - enum_dev = SPA_ID_INVALID; + dev = DEVICE_ID_SINK_SET; + available = SPA_PARAM_AVAILABILITY_yes; break; default: - errno = EINVAL; return NULL; } - if (enum_dev == SPA_ID_INVALID) { - errno = EINVAL; + if (profile != SPA_ID_INVALID && !profile_has_route(profile, route)) return NULL; - } - - available = SPA_PARAM_AVAILABILITY_yes; - if (this->device_set.path && !(port == 4 || port == 5)) - available = SPA_PARAM_AVAILABILITY_no; 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), + SPA_PARAM_ROUTE_index, SPA_POD_Int(route), SPA_PARAM_ROUTE_direction, SPA_POD_Id(direction), SPA_PARAM_ROUTE_name, SPA_POD_String(name), SPA_PARAM_ROUTE_description, SPA_POD_String(description), @@ -2133,14 +2258,10 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, spa_pod_builder_push_array(b, &f[1]); mask = 0; - for (i = 1; (j = get_profile_from_index(this, i, &next, &codec)) != SPA_ID_INVALID; i = next) { + for (i = 0; (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_BAP && !(port == 0 || port == 1 || port == 3 || port == 4)) - continue; - if (j == DEVICE_PROFILE_HSP_HFP && !(port == 0 || port == 2)) + if (!profile_has_route(j, route)) continue; profile_mask = profile_direction_mask(this, j, codec, false); @@ -2161,7 +2282,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, return NULL; } - if (dev != SPA_ID_INVALID) { + if (profile != SPA_ID_INVALID) { struct node *node = &this->nodes[dev]; struct spa_bt_transport_volume *t_volume; @@ -2206,17 +2327,16 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b, spa_pod_builder_prop(b, SPA_PARAM_ROUTE_save, 0); spa_pod_builder_bool(b, node->save); + + spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profile, 0); + spa_pod_builder_int(b, profile); } spa_pod_builder_prop(b, SPA_PARAM_ROUTE_devices, 0); spa_pod_builder_push_array(b, &f[1]); - spa_pod_builder_int(b, enum_dev); + spa_pod_builder_int(b, dev); spa_pod_builder_pop(b, &f[1]); - if (profile != SPA_ID_INVALID) { - spa_pod_builder_prop(b, SPA_PARAM_ROUTE_profile, 0); - spa_pod_builder_int(b, profile); - } return spa_pod_builder_pop(b, &f[0]); } @@ -2288,7 +2408,7 @@ static struct spa_pod *build_prop_info_codec(struct impl *this, struct spa_pod_b 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 || this->profile == DEVICE_PROFILE_BAP) { + if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP || this->profile == DEVICE_PROFILE_ASHA) { FOR_EACH_MEDIA_CODEC(j, codec) { spa_pod_builder_int(b, codec->id); spa_pod_builder_string(b, codec->description); @@ -2344,20 +2464,12 @@ static int impl_enum_params(void *object, int seq, enum spa_bluetooth_audio_codec codec; profile = get_profile_from_index(this, result.index, &result.next, &codec); - - switch (profile) { - 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) - goto next; - break; - default: + if (profile == SPA_ID_INVALID) return 0; - } + + param = build_profile(this, &b, id, result.index, profile, codec, false); + if (param == NULL) + goto next; break; } case SPA_PARAM_Profile: @@ -2378,26 +2490,23 @@ static int impl_enum_params(void *object, int seq, } case SPA_PARAM_EnumRoute: { - switch (result.index) { - case 0: case 1: case 2: case 3: case 4: + if (result.index < ROUTE_LAST) { param = build_route(this, &b, id, result.index, SPA_ID_INVALID); if (param == NULL) goto next; - break; - default: + } else { return 0; } break; } case SPA_PARAM_Route: { - switch (result.index) { - case 0: case 1: case 2: case 3: case 4: case 5: + if (result.index < ROUTE_LAST) { param = build_route(this, &b, id, result.index, this->profile); if (param == NULL) goto next; break; - default: + } else { return 0; } break; @@ -2447,6 +2556,41 @@ static int impl_enum_params(void *object, int seq, return 0; } +static void device_set_update_volumes(struct node *node) +{ + struct impl *impl = node->impl; + struct device_set *dset = &impl->device_set; + float hw_volume = node_get_hw_volume(node); + bool sink = (node->id == DEVICE_ID_SINK_SET); + struct device_set_member *members = sink ? dset->sink : dset->source; + uint32_t n_members = sink ? dset->sinks : dset->sources; + uint32_t i; + + /* Check if all sub-devices have HW volume */ + if ((sink && !dset->sink_enabled) || (!sink && !dset->source_enabled)) + goto soft_volume; + + for (i = 0; i < n_members; ++i) { + struct spa_bt_transport *t = members[i].transport; + struct spa_bt_transport_volume *t_volume = t ? &t->volumes[members[i].id] : NULL; + + if (!t_volume || !t_volume->active) + goto soft_volume; + } + + node_update_soft_volumes(node, hw_volume); + for (i = 0; i < n_members; ++i) + spa_bt_transport_set_volume(members[i].transport, members[i].id, hw_volume); + return; + +soft_volume: + /* Soft volume fallback */ + for (i = 0; i < n_members; ++i) + spa_bt_transport_set_volume(members[i].transport, members[i].id, 1.0f); + node_update_soft_volumes(node, 1.0f); + return; +} + static int node_set_volume(struct impl *this, struct node *node, float volumes[], uint32_t n_volumes) { uint32_t i; @@ -2474,6 +2618,8 @@ static int node_set_volume(struct impl *this, struct node *node, float volumes[] node_update_soft_volumes(node, hw_volume); spa_bt_transport_set_volume(node->transport, node->id, hw_volume); + } else if (node->id == DEVICE_ID_SOURCE_SET || node->id == DEVICE_ID_SINK_SET) { + device_set_update_volumes(node); } else { float boost = get_soft_volume_boost(node); for (uint32_t i = 0; i < node->n_channels; ++i) @@ -2718,7 +2864,7 @@ static int impl_set_param(void *object, if (codec_id == SPA_ID_INVALID) return 0; - if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP) { + if (this->profile == DEVICE_PROFILE_A2DP || this->profile == DEVICE_PROFILE_BAP || this->profile == DEVICE_PROFILE_ASHA) { size_t j; for (j = 0; j < this->supported_codec_count; ++j) { if (this->supported_codecs[j]->id == codec_id) { @@ -2790,7 +2936,7 @@ static int impl_clear(struct spa_handle *handle) free((void *)it->value); } - device_set_clear(this); + device_set_clear(this, &this->device_set); return 0; } diff --git a/spa/plugins/bluez5/codec-loader.c b/spa/plugins/bluez5/codec-loader.c index 3b908779..6fd1d043 100644 --- a/spa/plugins/bluez5/codec-loader.c +++ b/spa/plugins/bluez5/codec-loader.c @@ -51,6 +51,7 @@ static int codec_order(const struct media_codec *c) SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_DUPLEX, SPA_BLUETOOTH_AUDIO_CODEC_OPUS_05_PRO, SPA_BLUETOOTH_AUDIO_CODEC_AAC_ELD, + SPA_BLUETOOTH_AUDIO_CODEC_G722, }; size_t i; for (i = 0; i < SPA_N_ELEMENTS(order); ++i) @@ -172,7 +173,8 @@ const struct media_codec * const *load_media_codecs(struct spa_plugin_loader *lo MEDIA_CODEC_FACTORY_LIB("lc3plus"), MEDIA_CODEC_FACTORY_LIB("opus"), MEDIA_CODEC_FACTORY_LIB("opus-g"), - MEDIA_CODEC_FACTORY_LIB("lc3") + MEDIA_CODEC_FACTORY_LIB("lc3"), + MEDIA_CODEC_FACTORY_LIB("g722") #undef MEDIA_CODEC_FACTORY_LIB }; diff --git a/spa/plugins/bluez5/decode-buffer.h b/spa/plugins/bluez5/decode-buffer.h index 2eab4645..0441f041 100644 --- a/spa/plugins/bluez5/decode-buffer.h +++ b/spa/plugins/bluez5/decode-buffer.h @@ -21,12 +21,8 @@ * 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 buffer level is the position of last received sample, relative to the current + * playback position. If it is larger than duration, there is no underrun. * * The rate correction aims to maintain the average level at a safety margin. */ @@ -45,16 +41,6 @@ #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,max_buf) \ - SPA_CLAMP((spike)*3/2, (packet_size), (max_buf) - 2*(packet_size)) - - struct spa_bt_decode_buffer { struct spa_log *log; @@ -74,20 +60,20 @@ struct spa_bt_decode_buffer 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 duration; uint32_t pos; int32_t target; /**< target buffer (0: automatic) */ - int32_t max_target; + int32_t max_extra; + + int32_t level; + uint64_t next_nsec; + double rate_diff; - uint8_t received:1; uint8_t buffering:1; }; -static int spa_bt_decode_buffer_init(struct spa_bt_decode_buffer *this, struct spa_log *log, +static inline 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); @@ -100,7 +86,7 @@ static int spa_bt_decode_buffer_init(struct spa_bt_decode_buffer *this, struct s this->corr = 1.0; this->target = 0; this->buffering = true; - this->max_target = INT32_MAX; + this->max_extra = INT32_MAX; spa_bt_rate_control_init(&this->ctl, 0); @@ -114,13 +100,13 @@ static int spa_bt_decode_buffer_init(struct spa_bt_decode_buffer *this, struct s return 0; } -static void spa_bt_decode_buffer_clear(struct spa_bt_decode_buffer *this) +static inline 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) +static inline void spa_bt_decode_buffer_compact(struct spa_bt_decode_buffer *this) { uint32_t avail; @@ -153,7 +139,23 @@ 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) +static inline 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 inline 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 inline 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); @@ -161,69 +163,85 @@ static void *spa_bt_decode_buffer_get_write(struct spa_bt_decode_buffer *this, u 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) +static inline void spa_bt_decode_buffer_write_packet(struct spa_bt_decode_buffer *this, uint32_t size, uint64_t nsec) { + int32_t remain; + uint32_t avail; + 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); -} + if (nsec && this->next_nsec && this->rate_diff != 0.0) { + int64_t dt = (this->next_nsec >= nsec) ? + (int64_t)(this->next_nsec - nsec) : -(int64_t)(nsec - this->next_nsec); + remain = (int32_t)SPA_CLAMP(dt * this->rate_diff * this->rate / SPA_NSEC_PER_SEC, + -(int32_t)this->duration, this->duration); + } else { + remain = 0; + } -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; + spa_bt_decode_buffer_get_read(this, &avail); + this->level = avail / this->frame_size + remain; } -static void spa_bt_decode_buffer_recover(struct spa_bt_decode_buffer *this) +static inline 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->level = size; this->corr = 1.0; - - spa_bt_rate_control_init(&this->ctl, level); + spa_bt_rate_control_init(&this->ctl, size); } -static SPA_UNUSED -void spa_bt_decode_buffer_set_target_latency(struct spa_bt_decode_buffer *this, int32_t samples) +static inline void spa_bt_decode_buffer_set_target_latency(struct spa_bt_decode_buffer *this, int32_t samples) { this->target = samples; } -static SPA_UNUSED -void spa_bt_decode_buffer_set_max_latency(struct spa_bt_decode_buffer *this, int32_t samples) +static inline void spa_bt_decode_buffer_set_max_extra_latency(struct spa_bt_decode_buffer *this, int32_t samples) { - this->max_target = samples; + this->max_extra = samples; } -static void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *this, uint32_t samples, uint32_t duration) +static inline int32_t spa_bt_decode_buffer_get_target_latency(struct spa_bt_decode_buffer *this) +{ + const int32_t duration = this->duration; + const int32_t packet_size = SPA_CLAMP(this->packet_size.max, 0, INT32_MAX/8); + const int32_t max_buf = (this->buffer_size - this->buffer_reserve) / this->frame_size; + const int32_t spike = SPA_CLAMP(this->spike.max, 0, max_buf); + int32_t target; + + if (this->target) + target = this->target; + else + target = SPA_CLAMP(SPA_ROUND_UP(SPA_MAX(spike * 3/2, duration), + SPA_CLAMP((int)this->rate / 50, 1, INT32_MAX)), + duration, max_buf - 2*packet_size); + + return SPA_MIN(target, duration + SPA_CLAMP(this->max_extra, 0, INT32_MAX - duration)); +} + +static inline void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *this, uint32_t samples, uint32_t duration, + double rate_diff, uint64_t next_nsec) { 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); + const uint32_t avg_period = (uint64_t)this->rate * BUFFERING_SHORT_MSEC / 1000; + int32_t target; uint32_t avail; - if (SPA_UNLIKELY(duration != this->prev_duration)) { - this->prev_duration = duration; + this->rate_diff = rate_diff; + this->next_nsec = next_nsec; + + if (SPA_UNLIKELY(duration != this->duration)) { + this->duration = duration; spa_bt_decode_buffer_recover(this); } + target = spa_bt_decode_buffer_get_target_latency(this); + if (SPA_UNLIKELY(this->buffering)) { int32_t size = (this->write_index - this->read_index) / this->frame_size; @@ -231,89 +249,63 @@ static void spa_bt_decode_buffer_process(struct spa_bt_decode_buffer *this, uint 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)) + if (size >= SPA_MAX((int)duration, target)) this->buffering = false; else return; + spa_bt_ptp_update(&this->spike, packet_size, duration); 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; - const int32_t max_buf = (this->buffer_size - this->buffer_reserve) / this->frame_size; - int32_t level, target; + /* Track buffer level */ + this->level = SPA_MAX(this->level, -max_level); - /* 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, (int32_t)this->ctl.avg - this->level, duration); - spa_bt_ptp_update(&this->spike, (int32_t)(this->ctl.avg - level), this->prev_consumed); + if (this->level > SPA_MAX(4 * target, 3*(int32_t)duration) && + avail > data_size) { + /* Lagging too much: drop data */ + uint32_t size = SPA_MIN(avail - data_size, + (this->level - target) * this->frame_size); - /* Update target level */ - if (this->target) - target = this->target; - else - target = BUFFERING_TARGET(this->spike.max, packet_size, max_buf); - - target = SPA_MIN(target, this->max_target); - - 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) * 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, - BUFFERING_RATE_DIFF_MAX); - - spa_bt_decode_buffer_get_read(this, &avail); - - this->prev_consumed = 0; - this->prev_avail = avail; - this->underrun = 0; - this->received = false; + 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)this->level, (int)target); + + spa_bt_decode_buffer_recover(this); + } + + this->pos += duration; + 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)this->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, + this->level, target, duration, avg_period, + BUFFERING_RATE_DIFF_MAX); + + this->level -= duration; + + spa_bt_decode_buffer_get_read(this, &avail); 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->buffering = true; + spa_bt_ptp_update(&this->spike, (int32_t)this->ctl.avg - this->level, duration); } - - this->prev_consumed += samples; } #endif diff --git a/spa/plugins/bluez5/defs.h b/spa/plugins/bluez5/defs.h index ba9e54ac..23094580 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -127,6 +127,7 @@ extern "C" { #define SPA_BT_UUID_BAP_SOURCE "00002bcb-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_BAP_BROADCAST_SOURCE "00001852-0000-1000-8000-00805f9b34fb" #define SPA_BT_UUID_BAP_BROADCAST_SINK "00001851-0000-1000-8000-00805f9b34fb" +#define SPA_BT_UUID_ASHA_SINK "0000FDF0-0000-1000-8000-00805f9b34fb" #define PROFILE_HSP_AG "/Profile/HSPAG" #define PROFILE_HSP_HS "/Profile/HSPHS" @@ -181,12 +182,13 @@ enum spa_bt_profile { 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_BAP_BROADCAST_SOURCE = (1 << 8), - SPA_BT_PROFILE_BAP_BROADCAST_SINK = (1 << 9), + SPA_BT_PROFILE_ASHA_SINK = (1 << 4), + SPA_BT_PROFILE_HSP_HS = (1 << 5), + SPA_BT_PROFILE_HSP_AG = (1 << 6), + SPA_BT_PROFILE_HFP_HF = (1 << 7), + SPA_BT_PROFILE_HFP_AG = (1 << 8), + SPA_BT_PROFILE_BAP_BROADCAST_SOURCE = (1 << 9), + SPA_BT_PROFILE_BAP_BROADCAST_SINK = (1 << 10), 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), @@ -226,6 +228,8 @@ static inline enum spa_bt_profile spa_bt_profile_from_uuid(const char *uuid) return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; else if (strcasecmp(uuid, SPA_BT_UUID_BAP_BROADCAST_SINK) == 0) return SPA_BT_PROFILE_BAP_BROADCAST_SINK; + else if (strcasecmp(uuid, SPA_BT_UUID_ASHA_SINK) == 0) + return SPA_BT_PROFILE_ASHA_SINK; else return 0; } @@ -252,13 +256,16 @@ enum spa_bt_hfp_ag_feature { }; enum spa_bt_hfp_sdp_ag_features { - SPA_BT_HFP_SDP_AG_FEATURE_NONE = (0), - SPA_BT_HFP_SDP_AG_FEATURE_3WAY = (1 << 0), - SPA_BT_HFP_SDP_AG_FEATURE_ECNR = (1 << 1), - SPA_BT_HFP_SDP_AG_FEATURE_VOICE_RECOG = (1 << 2), - SPA_BT_HFP_SDP_AG_FEATURE_IN_BAND_RING_TONE = (1 << 3), - SPA_BT_HFP_SDP_AG_FEATURE_ATTACH_VOICE_TAG = (1 << 4), - SPA_BT_HFP_SDP_AG_FEATURE_WIDEBAND_SPEECH = (1 << 5), + SPA_BT_HFP_SDP_AG_FEATURE_NONE = (0), + SPA_BT_HFP_SDP_AG_FEATURE_3WAY = (1 << 0), + SPA_BT_HFP_SDP_AG_FEATURE_ECNR = (1 << 1), + SPA_BT_HFP_SDP_AG_FEATURE_VOICE_RECOG = (1 << 2), + SPA_BT_HFP_SDP_AG_FEATURE_IN_BAND_RING_TONE = (1 << 3), + SPA_BT_HFP_SDP_AG_FEATURE_ATTACH_VOICE_TAG = (1 << 4), + SPA_BT_HFP_SDP_AG_FEATURE_WIDEBAND_SPEECH = (1 << 5), + SPA_BT_HFP_SDP_AG_FEATURE_ENH_VOICE_RECOG_STATUS = (1 << 6), + SPA_BT_HFP_SDP_AG_FEATURE_VOICE_RECOG_TEXT = (1 << 7), + SPA_BT_HFP_SDP_AG_FEATURE_SUPER_WIDEBAND_SPEECH = (1 << 8), }; enum spa_bt_hfp_hf_feature { @@ -299,17 +306,22 @@ enum spa_bt_hfp_hf_xapl_features { }; enum spa_bt_hfp_sdp_hf_features { - SPA_BT_HFP_SDP_HF_FEATURE_NONE = (0), - SPA_BT_HFP_SDP_HF_FEATURE_ECNR = (1 << 0), - SPA_BT_HFP_SDP_HF_FEATURE_3WAY = (1 << 1), - SPA_BT_HFP_SDP_HF_FEATURE_CLIP = (1 << 2), - SPA_BT_HFP_SDP_HF_FEATURE_VOICE_RECOGNITION = (1 << 3), + SPA_BT_HFP_SDP_HF_FEATURE_NONE = (0), + SPA_BT_HFP_SDP_HF_FEATURE_ECNR = (1 << 0), + SPA_BT_HFP_SDP_HF_FEATURE_3WAY = (1 << 1), + SPA_BT_HFP_SDP_HF_FEATURE_CLIP = (1 << 2), + SPA_BT_HFP_SDP_HF_FEATURE_VOICE_RECOGNITION = (1 << 3), SPA_BT_HFP_SDP_HF_FEATURE_REMOTE_VOLUME_CONTROL = (1 << 4), - SPA_BT_HFP_SDP_HF_FEATURE_WIDEBAND_SPEECH = (1 << 5), + SPA_BT_HFP_SDP_HF_FEATURE_WIDEBAND_SPEECH = (1 << 5), + SPA_BT_HFP_SDP_HF_FEATURE_ENH_VOICE_RECOG_STATUS = (1 << 6), + SPA_BT_HFP_SDP_HF_FEATURE_VOICE_RECOG_TEXT = (1 << 7), + SPA_BT_HFP_SDP_HF_FEATURE_SUPER_WIDEBAND_SPEECH = (1 << 8), }; static inline const char *spa_bt_profile_name (enum spa_bt_profile profile) { switch (profile) { + case SPA_BT_PROFILE_ASHA_SINK: + return "asha-sink"; case SPA_BT_PROFILE_A2DP_SOURCE: return "a2dp-source"; case SPA_BT_PROFILE_A2DP_SINK: @@ -462,7 +474,7 @@ struct spa_bt_device_events { void (*codec_switched) (void *data, int status); /** Profile configuration changed */ - void (*profiles_changed) (void *data, uint32_t prev_profiles, uint32_t prev_connected); + void (*profiles_changed) (void *data, uint32_t connected_change); /** Device set configuration changed */ void (*device_set_changed) (void *data); @@ -584,6 +596,7 @@ int spa_bt_sco_io_write(struct spa_bt_sco_io *io, uint8_t *data, int size); #define SPA_BT_VOLUME_INVALID -1 #define SPA_BT_VOLUME_HS_MAX 15 #define SPA_BT_VOLUME_A2DP_MAX 127 +#define SPA_BT_VOLUME_BAP_MAX 255 enum spa_bt_transport_state { SPA_BT_TRANSPORT_STATE_ERROR = -1, @@ -610,6 +623,7 @@ struct spa_bt_transport_implementation { int (*acquire) (void *data, bool optional); int (*release) (void *data); int (*set_volume) (void *data, int id, float volume); + int (*set_delay) (void *data, int64_t delay_nsec); int (*destroy) (void *data); }; @@ -715,12 +729,13 @@ int spa_bt_transport_ensure_sco_io(struct spa_bt_transport *t, struct spa_loop * #define spa_bt_transport_destroy(t) spa_bt_transport_impl(t, destroy, 0) #define spa_bt_transport_set_volume(t,...) spa_bt_transport_impl(t, set_volume, 0, __VA_ARGS__) +#define spa_bt_transport_set_delay(t,...) spa_bt_transport_impl(t, set_delay, 0, __VA_ARGS__) static inline enum spa_bt_transport_state spa_bt_transport_state_from_string(const char *value) { if (strcasecmp("idle", value) == 0) return SPA_BT_TRANSPORT_STATE_IDLE; - else if (strcasecmp("pending", value) == 0) + else if ((strcasecmp("pending", value) == 0) || (strcasecmp("broadcasting", value) == 0)) return SPA_BT_TRANSPORT_STATE_PENDING; else if (strcasecmp("active", value) == 0) return SPA_BT_TRANSPORT_STATE_ACTIVE; @@ -772,6 +787,9 @@ int spa_bt_quirks_get_features(const struct spa_bt_quirks *quirks, const struct spa_bt_adapter *adapter, const struct spa_bt_device *device, uint32_t *features); +void spa_bt_quirks_log_features(const struct spa_bt_quirks *this, + const struct spa_bt_adapter *adapter, + const struct spa_bt_device *device); void spa_bt_quirks_destroy(struct spa_bt_quirks *quirks); int spa_bt_adapter_has_msbc(struct spa_bt_adapter *adapter); diff --git a/spa/plugins/bluez5/g722/g722_enc_dec.h b/spa/plugins/bluez5/g722/g722_enc_dec.h new file mode 100644 index 00000000..0da6060d --- /dev/null +++ b/spa/plugins/bluez5/g722/g722_enc_dec.h @@ -0,0 +1,148 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * g722.h - The ITU G.722 codec. + * + * Written by Steve Underwood <steveu@coppice.org> + * + * Copyright (C) 2005 Steve Underwood + * + * Despite my general liking of the GPL, I place my own contributions + * to this code in the public domain for the benefit of all mankind - + * even the slimy ones who might try to proprietize my work and use it + * to my detriment. + * + * Based on a single channel G.722 codec which is: + * + ***** Copyright (c) CMU 1993 ***** + * Computer Science, Speech Group + * Chengxiang Lu and Alex Hauptmann + * + * $Id: g722.h 48959 2006-12-25 06:42:15Z rizzo $ + */ + +/*! \file */ + +#if !defined(_G722_H_) +#define _G722_H_ + +#include <stdint.h> + +/*! \page g722_page G.722 encoding and decoding +\section g722_page_sec_1 What does it do? +The G.722 module is a bit exact implementation of the ITU G.722 specification for all three +specified bit rates - 64000bps, 56000bps and 48000bps. It passes the ITU tests. + +To allow fast and flexible interworking with narrow band telephony, the encoder and decoder +support an option for the linear audio to be an 8k samples/second stream. In this mode the +codec is considerably faster, and still fully compatible with wideband terminals using G.722. + +\section g722_page_sec_2 How does it work? +???. +*/ + +/* Format DAC12 is added to decode directly into samples suitable for + a 12-bit DAC using offset binary representation. */ + +enum { + G722_SAMPLE_RATE_8000 = 0x0001, + G722_PACKED = 0x0002, + G722_FORMAT_DAC12 = 0x0004, +}; + +#ifdef BUILD_FEATURE_DAC +#define NLDECOMPRESS_APPLY_GAIN(s, g) (((s) * (int32_t)(g)) >> 16) +// Equivalent to shift 16, add 0x8000, shift 4 +#define NLDECOMPRESS_APPLY_GAIN_CONVERTED_DAC(s, g) \ + (uint16_t)((uint16_t)(((s) * (int32_t)(g)) >> 20) + 0x800) +#else +#define NLDECOMPRESS_APPLY_GAIN(s, g) (((int32_t)(s) * (int32_t)(g)) >> 16) +#endif + +#ifdef BUILD_FEATURE_DAC +#define NLDECOMPRESS_PREPROCESS_PCM_SAMPLE_WITH_GAIN(s, g) \ + NLDECOMPRESS_APPLY_GAIN_CONVERTED_DAC((s), (g)) +#define NLDECOMPRESS_PREPROCESS_SAMPLE_WITH_GAIN(s, g) ((int16_t)NLDECOMPRESS_APPLY_GAIN((s), (g))) +#else +#define NLDECOMPRESS_PREPROCESS_PCM_SAMPLE_WITH_GAIN NLDECOMPRESS_PREPROCESS_SAMPLE_WITH_GAIN +#define NLDECOMPRESS_PREPROCESS_SAMPLE_WITH_GAIN(s, g) \ + ((int16_t)(NLDECOMPRESS_APPLY_GAIN((s), (g)))) +#endif + +typedef struct { + int s; + int sp; + int sz; + int r[3]; + int a[3]; + int ap[3]; + int p[3]; + int d[7]; + int b[7]; + int bp[7]; + int nb; + int det; +} g722_band_t; + +typedef struct { + /*! TRUE if the operating in the special ITU test mode, with the band split filters disabled. */ + int itu_test_mode; + /*! TRUE if the G.722 data is packed */ + int packed; + /*! TRUE if encode from 8k samples/second */ + int eight_k; + /*! 6 for 48000kbps, 7 for 56000kbps, or 8 for 64000kbps. */ + int bits_per_sample; + + /*! Signal history for the QMF */ + int x[24]; + + g722_band_t band[2]; + + unsigned int in_buffer; + int in_bits; + unsigned int out_buffer; + int out_bits; +} g722_encode_state_t; + +typedef struct { + /*! TRUE if the operating in the special ITU test mode, with the band split filters disabled. */ + int itu_test_mode; + /*! TRUE if the G.722 data is packed */ + int packed; + /*! TRUE if decode to 8k samples/second */ + int eight_k; + /*! 6 for 48000kbps, 7 for 56000kbps, or 8 for 64000kbps. */ + int bits_per_sample; + /*! TRUE if offset binary for a 12-bit DAC */ + int dac_pcm; + + /*! Signal history for the QMF */ + int x[24]; + + g722_band_t band[2]; + + unsigned int in_buffer; + int in_bits; + unsigned int out_buffer; + int out_bits; +} g722_decode_state_t; + +#ifdef __cplusplus +extern "C" { +#endif + +g722_encode_state_t *g722_encode_init(g722_encode_state_t *s, unsigned int rate, int options); +int g722_encode_release(g722_encode_state_t *s); +int g722_encode(g722_encode_state_t *s, uint8_t g722_data[], const int16_t amp[], int len); + +g722_decode_state_t *g722_decode_init(g722_decode_state_t *s, unsigned int rate, int options); +int g722_decode_release(g722_decode_state_t *s); +uint32_t g722_decode(g722_decode_state_t *s, int16_t amp[], const uint8_t g722_data[], int len, + uint16_t aGain); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/spa/plugins/bluez5/g722/g722_encode.c b/spa/plugins/bluez5/g722/g722_encode.c new file mode 100644 index 00000000..a185e259 --- /dev/null +++ b/spa/plugins/bluez5/g722/g722_encode.c @@ -0,0 +1,387 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * g722_encode.c - The ITU G.722 codec, encode part. + * + * Written by Steve Underwood <steveu@coppice.org> + * + * Copyright (C) 2005 Steve Underwood + * + * All rights reserved. + * + * Despite my general liking of the GPL, I place my own contributions + * to this code in the public domain for the benefit of all mankind - + * even the slimy ones who might try to proprietize my work and use it + * to my detriment. + * + * Based on a single channel 64kbps only G.722 codec which is: + * + ***** Copyright (c) CMU 1993 ***** + * Computer Science, Speech Group + * Chengxiang Lu and Alex Hauptmann + * + * $Id: g722_encode.c,v 1.14 2006/07/07 16:37:49 steveu Exp $ + */ + +/*! \file */ + +#include <stdlib.h> +#include <string.h> + +#include "g722_enc_dec.h" + +#if !defined(FALSE) +#define FALSE 0 +#endif +#if !defined(TRUE) +#define TRUE (!FALSE) +#endif + +#define PACKED_OUTPUT (0) +#define BITS_PER_SAMPLE (8) + +#ifndef BUILD_FEATURE_G722_USE_INTRINSIC_SAT +static __inline int16_t saturate(int32_t amp) { + int16_t amp16; + + /* Hopefully this is optimised for the common case - not clipping */ + amp16 = (int16_t)amp; + if (amp == amp16) { + return amp16; + } + if (amp > 0x7FFF) { + return 0x7FFF; + } + return 0x8000; +} +#else +static __inline int16_t saturate(int32_t val) { + register int32_t res; + __asm volatile("SSAT %0, #16, %1\n\t" : "=r"(res) : "r"(val) :); + return (int16_t)res; +} +#endif +/*- End of function --------------------------------------------------------*/ + +static void block4(g722_band_t *band, int d) { + int wd1; + int wd2; + int wd3; + int i; + int sg[7]; + int ap1, ap2; + int sg0, sgi; + int sz; + + /* Block 4, RECONS */ + band->d[0] = d; + band->r[0] = saturate(band->s + d); + + /* Block 4, PARREC */ + band->p[0] = saturate(band->sz + d); + + /* Block 4, UPPOL2 */ + for (i = 0; i < 3; i++) { + sg[i] = band->p[i] >> 15; + } + wd1 = saturate(band->a[1] << 2); + + wd2 = (sg[0] == sg[1]) ? -wd1 : wd1; + if (wd2 > 32767) { + wd2 = 32767; + } + + ap2 = (wd2 >> 7) + ((sg[0] == sg[2]) ? 128 : -128); + ap2 += (band->a[2] * 32512) >> 15; + if (ap2 > 12288) { + ap2 = 12288; + } else if (ap2 < -12288) { + ap2 = -12288; + } + band->ap[2] = ap2; + + /* Block 4, UPPOL1 */ + sg[0] = band->p[0] >> 15; + sg[1] = band->p[1] >> 15; + wd1 = (sg[0] == sg[1]) ? 192 : -192; + wd2 = (band->a[1] * 32640) >> 15; + + ap1 = saturate(wd1 + wd2); + wd3 = saturate(15360 - band->ap[2]); + if (ap1 > wd3) { + ap1 = wd3; + } else if (ap1 < -wd3) { + ap1 = -wd3; + } + band->ap[1] = ap1; + + /* Block 4, UPZERO */ + /* Block 4, FILTEZ */ + wd1 = (d == 0) ? 0 : 128; + + sg0 = sg[0] = d >> 15; + for (i = 1; i < 7; i++) { + sgi = band->d[i] >> 15; + wd2 = (sgi == sg0) ? wd1 : -wd1; + wd3 = (band->b[i] * 32640) >> 15; + band->bp[i] = saturate(wd2 + wd3); + } + + /* Block 4, DELAYA */ + sz = 0; + for (i = 6; i > 0; i--) { + int bi; + + band->d[i] = band->d[i - 1]; + bi = band->b[i] = band->bp[i]; + wd1 = saturate(band->d[i] + band->d[i]); + sz += (bi * wd1) >> 15; + } + band->sz = sz; + + for (i = 2; i > 0; i--) { + band->r[i] = band->r[i - 1]; + band->p[i] = band->p[i - 1]; + band->a[i] = band->ap[i]; + } + + /* Block 4, FILTEP */ + wd1 = saturate(band->r[1] + band->r[1]); + wd1 = (band->a[1] * wd1) >> 15; + wd2 = saturate(band->r[2] + band->r[2]); + wd2 = (band->a[2] * wd2) >> 15; + band->sp = saturate(wd1 + wd2); + + /* Block 4, PREDIC */ + band->s = saturate(band->sp + band->sz); +} +/*- End of function --------------------------------------------------------*/ + +g722_encode_state_t *g722_encode_init(g722_encode_state_t *s, unsigned int rate, int options) { + if (s == NULL) { +#ifdef G722_SUPPORT_MALLOC + if ((s = (g722_encode_state_t *)malloc(sizeof(*s))) == NULL) +#endif + return NULL; + } + memset(s, 0, sizeof(*s)); + if (rate == 48000) { + s->bits_per_sample = 6; + } else if (rate == 56000) { + s->bits_per_sample = 7; + } else { + s->bits_per_sample = 8; + } + s->band[0].det = 32; + s->band[1].det = 8; + return s; +} +/*- End of function --------------------------------------------------------*/ + +int g722_encode_release(g722_encode_state_t *s) { + free(s); + return 0; +} +/*- End of function --------------------------------------------------------*/ + +/* WebRtc, tlegrand: + * Only define the following if bit-exactness with reference implementation + * is needed. Will only have any effect if input signal is saturated. + */ +// #define RUN_LIKE_REFERENCE_G722 +#ifdef RUN_LIKE_REFERENCE_G722 +int16_t limitValues(int16_t rl) { + int16_t yl; + + yl = (rl > 16383) ? 16383 : ((rl < -16384) ? -16384 : rl); + + return yl; +} +/*- End of function --------------------------------------------------------*/ +#endif + +static int16_t q6[32] = {0, 35, 72, 110, 150, 190, 233, 276, 323, 370, 422, + 473, 530, 587, 650, 714, 786, 858, 940, 1023, 1121, 1219, + 1339, 1458, 1612, 1765, 1980, 2195, 2557, 2919, 0, 0}; +static int16_t iln[32] = {0, 63, 62, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, + 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 0}; +static int16_t ilp[32] = {0, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, + 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 0}; +static int16_t wl[8] = {-60, -30, 58, 172, 334, 538, 1198, 3042}; +static int16_t rl42[16] = {0, 7, 6, 5, 4, 3, 2, 1, 7, 6, 5, 4, 3, 2, 1, 0}; +static int16_t ilb[32] = {2048, 2093, 2139, 2186, 2233, 2282, 2332, 2383, 2435, 2489, 2543, + 2599, 2656, 2714, 2774, 2834, 2896, 2960, 3025, 3091, 3158, 3228, + 3298, 3371, 3444, 3520, 3597, 3676, 3756, 3838, 3922, 4008}; +static int16_t qm4[16] = {0, -20456, -12896, -8968, -6288, -4240, -2584, -1200, + 20456, 12896, 8968, 6288, 4240, 2584, 1200, 0}; +static int16_t qm2[4] = {-7408, -1616, 7408, 1616}; +static int16_t qmf_coeffs[12] = { + 3, -11, 12, 32, -210, 951, 3876, -805, 362, -156, 53, -11, +}; +static int16_t ihn[3] = {0, 1, 0}; +static int16_t ihp[3] = {0, 3, 2}; +static int16_t wh[3] = {0, -214, 798}; +static int16_t rh2[4] = {2, 1, 2, 1}; + +int g722_encode(g722_encode_state_t *s, uint8_t g722_data[], const int16_t amp[], int len) { + int dlow; + int dhigh; + int el; + int wd; + int wd1; + int ril; + int wd2; + int il4; + int ih2; + int wd3; + int eh; + int mih; + int i; + int j; + /* Low and high band PCM from the QMF */ + int xlow; + int xhigh; + int g722_bytes; + /* Even and odd tap accumulators */ + int sumeven; + int sumodd; + int ihigh; + int ilow; + int code; + + g722_bytes = 0; + xhigh = 0; + for (j = 0; j < len;) { + if (s->itu_test_mode) { + xlow = xhigh = amp[j++] >> 1; + } else { + { + /* Apply the transmit QMF */ + /* Shuffle the buffer down */ + for (i = 0; i < 22; i++) { + s->x[i] = s->x[i + 2]; + } + // TODO: if len is odd, then this can be a buffer overrun + s->x[22] = amp[j++]; + s->x[23] = amp[j++]; + + /* Discard every other QMF output */ + sumeven = 0; + sumodd = 0; + for (i = 0; i < 12; i++) { + sumodd += s->x[2 * i] * qmf_coeffs[i]; + sumeven += s->x[2 * i + 1] * qmf_coeffs[11 - i]; + } + /* We shift by 12 to allow for the QMF filters (DC gain = 4096), plus 1 + to allow for us summing two filters, plus 1 to allow for the 15 bit + input to the G.722 algorithm. */ + xlow = (sumeven + sumodd) >> 14; + xhigh = (sumeven - sumodd) >> 14; + +#ifdef RUN_LIKE_REFERENCE_G722 + /* The following lines are only used to verify bit-exactness + * with reference implementation of G.722. Higher precision + * is achieved without limiting the values. + */ + xlow = limitValues(xlow); + xhigh = limitValues(xhigh); +#endif + } + } + /* Block 1L, SUBTRA */ + el = saturate(xlow - s->band[0].s); + + /* Block 1L, QUANTL */ + wd = (el >= 0) ? el : -(el + 1); + + for (i = 1; i < 30; i++) { + wd1 = (q6[i] * s->band[0].det) >> 12; + if (wd < wd1) { + break; + } + } + ilow = (el < 0) ? iln[i] : ilp[i]; + + /* Block 2L, INVQAL */ + ril = ilow >> 2; + wd2 = qm4[ril]; + dlow = (s->band[0].det * wd2) >> 15; + + /* Block 3L, LOGSCL */ + il4 = rl42[ril]; + wd = (s->band[0].nb * 127) >> 7; + s->band[0].nb = wd + wl[il4]; + if (s->band[0].nb < 0) { + s->band[0].nb = 0; + } else if (s->band[0].nb > 18432) { + s->band[0].nb = 18432; + } + + /* Block 3L, SCALEL */ + wd1 = (s->band[0].nb >> 6) & 31; + wd2 = 8 - (s->band[0].nb >> 11); + wd3 = (wd2 < 0) ? (ilb[wd1] << -wd2) : (ilb[wd1] >> wd2); + s->band[0].det = wd3 << 2; + + block4(&s->band[0], dlow); + { + int nb; + + /* Block 1H, SUBTRA */ + eh = saturate(xhigh - s->band[1].s); + + /* Block 1H, QUANTH */ + wd = (eh >= 0) ? eh : -(eh + 1); + wd1 = (564 * s->band[1].det) >> 12; + mih = (wd >= wd1) ? 2 : 1; + ihigh = (eh < 0) ? ihn[mih] : ihp[mih]; + + /* Block 2H, INVQAH */ + wd2 = qm2[ihigh]; + dhigh = (s->band[1].det * wd2) >> 15; + + /* Block 3H, LOGSCH */ + ih2 = rh2[ihigh]; + wd = (s->band[1].nb * 127) >> 7; + + nb = wd + wh[ih2]; + if (nb < 0) { + nb = 0; + } else if (nb > 22528) { + nb = 22528; + } + s->band[1].nb = nb; + + /* Block 3H, SCALEH */ + wd1 = (s->band[1].nb >> 6) & 31; + wd2 = 10 - (s->band[1].nb >> 11); + wd3 = (wd2 < 0) ? (ilb[wd1] << -wd2) : (ilb[wd1] >> wd2); + s->band[1].det = wd3 << 2; + + block4(&s->band[1], dhigh); +#if BITS_PER_SAMPLE == 8 + code = ((ihigh << 6) | ilow); +#elif BITS_PER_SAMPLE == 7 + code = ((ihigh << 6) | ilow) >> 1; +#elif BITS_PER_SAMPLE == 6 + code = ((ihigh << 6) | ilow) >> 2; +#endif + } + +#if PACKED_OUTPUT == 1 + /* Pack the code bits */ + s->out_buffer |= (code << s->out_bits); + s->out_bits += s->bits_per_sample; + if (s->out_bits >= 8) { + g722_data[g722_bytes++] = (uint8_t)(s->out_buffer & 0xFF); + s->out_bits -= 8; + s->out_buffer >>= 8; + } +#else + g722_data[g722_bytes++] = (uint8_t)code; +#endif + } + return g722_bytes; +} +/*- End of function --------------------------------------------------------*/ +/*- End of file ------------------------------------------------------------*/ diff --git a/spa/plugins/bluez5/media-codecs.h b/spa/plugins/bluez5/media-codecs.h index a1b0a9f2..c9a00f7b 100644 --- a/spa/plugins/bluez5/media-codecs.h +++ b/spa/plugins/bluez5/media-codecs.h @@ -26,7 +26,7 @@ #define SPA_TYPE_INTERFACE_Bluez5CodecMedia SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:Media:Private" -#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 9 +#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 12 struct spa_bluez5_codec_a2dp { struct spa_interface iface; @@ -71,6 +71,7 @@ struct media_codec { a2dp_vendor_codec_t vendor; bool bap; + bool asha; const char *name; const char *description; @@ -87,7 +88,7 @@ struct media_codec { /** If fill_caps is NULL, no endpoint is registered (for sharing with another codec). */ int (*fill_caps) (const struct media_codec *codec, uint32_t flags, - uint8_t caps[A2DP_MAX_CAPS_SIZE]); + const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE]); int (*select_config) (const struct media_codec *codec, uint32_t flags, const void *caps, size_t caps_size, @@ -199,6 +200,17 @@ struct media_codec { int (*increase_bitpool) (void *data); void (*set_log) (struct spa_log *global_log); + + /** + * Get codec internal delays, in samples at input/output rates. + * + * The delay does not include the duration of the PCM input/output + * audio data, but is that internal to the codec. + * + * \param[out] encoder Encoder delay in samples, or NULL + * \param[out] decoder Decoder delay in samples, or NULL + */ + void (*get_delay) (void *data, uint32_t *encoder, uint32_t *decoder); }; struct media_codec_config { diff --git a/spa/plugins/bluez5/media-sink.c b/spa/plugins/bluez5/media-sink.c index 1e822f77..49dc5de7 100644 --- a/spa/plugins/bluez5/media-sink.c +++ b/spa/plugins/bluez5/media-sink.c @@ -165,6 +165,8 @@ struct impl { uint64_t packet_delay_ns; struct spa_source *update_delay_event; + uint32_t encoder_delay; + const struct media_codec *codec; bool codec_props_changed; void *codec_props; @@ -380,7 +382,7 @@ static void set_latency(struct impl *this, bool emit_latency) /* in main loop */ - if (this->transport == NULL) + if (this->transport == NULL || !port->have_format) return; /* @@ -388,12 +390,13 @@ static void set_latency(struct impl *this, bool emit_latency) * * (packet delay) + (codec internal delay) + (transport delay) + (latency offset) * - * and doesn't depend on the quantum. The codec internal delay is neglected. - * Kernel knows the latency due to socket/controller queue, but doesn't - * tell us, so not included but hopefully in < 20 ms range. + * and doesn't depend on the quantum. Kernel knows the latency due to + * socket/controller queue, but doesn't tell us, so not included but + * hopefully in < 10 ms range. */ delay = __atomic_load_n(&this->packet_delay_ns, __ATOMIC_RELAXED); + delay += (int64_t)this->encoder_delay * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate; delay += spa_bt_transport_get_delay_nsec(this->transport); delay += SPA_CLAMP(this->props.latency_offset, -delay, INT64_MAX / 2); delay = SPA_MAX(delay, 0); @@ -539,9 +542,8 @@ static uint64_t get_reference_time(struct impl *this, uint64_t *duration_ns_ret) /* Account for resampling delay */ resampling = (port->current_format.info.raw.rate != this->process_rate) || this->following; if (port->rate_match && this->position && resampling) { - t -= (uint64_t)port->rate_match->delay * SPA_NSEC_PER_SEC - / this->position->clock.rate.denom; - t += SPA_NSEC_PER_SEC / port->current_format.info.raw.rate; + t -= (port->rate_match->delay * SPA_NSEC_PER_SEC + port->rate_match->delay_frac) + / port->current_format.info.raw.rate; } return t; @@ -716,8 +718,8 @@ static int encode_fragment(struct impl *this) static int flush_buffer(struct impl *this) { - spa_log_trace(this->log, "%p: used:%d block_size:%d", this, - this->buffer_used, this->block_size); + spa_log_trace(this->log, "%p: used:%d block_size:%d need_flush:%d", this, + this->buffer_used, this->block_size, this->need_flush); if (this->need_flush) return send_buffer(this); @@ -1069,7 +1071,7 @@ static void media_iso_pull(struct spa_bt_iso_io *iso_io) } else { spa_bt_rate_control_update(&port->ratectl, err, 0, iso_io->duration, period, RATE_CTL_DIFF_MAX); - spa_log_trace(this->log, "%p: ISO sync err:%+.3f value:%.3f target:%.3f (ms) corr:%g", + spa_log_trace(this->log, "%p: ISO sync err:%+.3g value:%.6f target:%.6f (ms) corr:%g", this, port->ratectl.avg / SPA_NSEC_PER_MSEC, value / SPA_NSEC_PER_MSEC, @@ -1250,9 +1252,15 @@ static int transport_start(struct impl *this) this->codec_props_changed = true; } - 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->encoder_delay = 0; + if (this->codec->get_delay) + this->codec->get_delay(this->codec_data, &this->encoder_delay, NULL); + + const char *codec_profile = this->codec->asha ? "ASHA" : (this->codec->bap ? "BAP" : "A2DP"); + spa_log_info(this->log, "%p: using %s codec %s, delay:%.2f ms, codec-delay:%.2f ms", this, + codec_profile, this->codec->description, + (double)spa_bt_transport_get_delay_nsec(this->transport) / SPA_NSEC_PER_MSEC, + (double)this->encoder_delay * SPA_MSEC_PER_SEC / port->current_format.info.raw.rate); this->seqnum = UINT16_MAX; @@ -1490,12 +1498,13 @@ static void emit_node_info(struct impl *this, bool full) node_group = node_group_buf; } + const char *codec_profile = this->codec->asha ? "ASHA" : (this->codec->bap ? "BAP" : "A2DP"); struct spa_dict_item node_info_items[] = { { SPA_KEY_DEVICE_API, "bluez5" }, { SPA_KEY_MEDIA_CLASS, this->is_internal ? "Audio/Sink/Internal" : 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" ) }, + this->transport->device->name : codec_profile ) }, { SPA_KEY_NODE_DRIVER, this->is_output ? "true" : "false" }, { "node.group", node_group }, }; diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c index 488cf497..00559749 100644 --- a/spa/plugins/bluez5/media-source.c +++ b/spa/plugins/bluez5/media-source.c @@ -68,7 +68,7 @@ struct port { struct spa_port_info info; struct spa_io_buffers *io; struct spa_io_rate_match *rate_match; - struct spa_latency_info latency; + struct spa_latency_info latency[2]; #define IDX_EnumFormat 0 #define IDX_Meta 1 #define IDX_IO 2 @@ -87,6 +87,18 @@ struct port { struct spa_bt_decode_buffer buffer; }; +struct delay_info { + union { + struct { + int32_t buffer; + uint32_t duration; + }; + uint64_t v; + }; +}; + +SPA_STATIC_ASSERT(sizeof(struct delay_info) == sizeof(uint64_t)); + struct impl { struct spa_handle handle; struct spa_node node; @@ -94,6 +106,7 @@ struct impl { struct spa_log *log; struct spa_loop *data_loop; struct spa_system *data_system; + struct spa_loop_utils *loop_utils; struct spa_hook_list hooks; struct spa_callbacks callbacks; @@ -150,6 +163,10 @@ struct impl { uint64_t sample_count; uint32_t errqueue_count; + + struct delay_info delay; + int64_t delay_sink; + struct spa_source *update_delay_event; }; #define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) == 0) @@ -307,8 +324,10 @@ static void set_latency(struct impl *this, bool emit_latency) { if (this->codec->bap && !this->is_input && this->transport && this->transport->delay_us != SPA_BT_UNKNOWN_DELAY) { + struct port *port = &this->port; unsigned int node_latency = 2048; - unsigned int target = this->transport->delay_us*48000ll/SPA_USEC_PER_SEC * 1/2; + uint64_t rate = port->current_format.info.raw.rate; + unsigned int target = this->transport->delay_us*rate/SPA_USEC_PER_SEC * 1/2; /* Adjust requested node latency to be somewhat (~1/2) smaller * than presentation delay. The difference functions as room @@ -323,8 +342,9 @@ static void set_latency(struct impl *this, bool emit_latency) emit_node_info(this, false); } - spa_log_info(this->log, "BAP presentation delay %d us, node latency %u/48000", - (int)this->transport->delay_us, node_latency); + spa_log_info(this->log, "BAP presentation delay %d us, node latency %u/%u", + (int)this->transport->delay_us, node_latency, + (unsigned int)rate); } } @@ -504,6 +524,9 @@ static void media_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, &now); + /* read */ size_read = read_data (this); if (size_read == 0) @@ -513,9 +536,6 @@ static void media_on_ready_read(struct spa_source *source) goto stop; } - /* 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) { this->codec->update_props(this->codec_data, this->codec_props); @@ -539,7 +559,7 @@ static void media_on_ready_read(struct spa_source *source) if (!this->started) return; - spa_bt_decode_buffer_write_packet(&port->buffer, decoded); + spa_bt_decode_buffer_write_packet(&port->buffer, decoded, SPA_TIMESPEC_TO_NSEC(&now)); dt = SPA_TIMESPEC_TO_NSEC(&this->now); this->now = now; @@ -648,6 +668,52 @@ static void media_iso_pull(struct spa_bt_iso_io *iso_io) * iso-io whether this source is running or not. */ } +static void emit_port_info(struct impl *this, struct port *port, bool full); + +static void update_transport_delay(struct impl *this) +{ + struct port *port = &this->port; + struct delay_info info; + float latency; + int64_t latency_nsec; + int64_t delay_sink; + + if (!this->transport || !port->have_format) + return; + + info.v = __atomic_load_n(&this->delay.v, __ATOMIC_RELAXED); + + /* Latency to sink */ + latency = info.buffer + + port->latency[SPA_DIRECTION_INPUT].min_rate + + port->latency[SPA_DIRECTION_INPUT].min_quantum * info.duration; + + latency_nsec = port->latency[SPA_DIRECTION_INPUT].min_ns + + (int64_t)(latency * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate); + + spa_bt_transport_set_delay(this->transport, latency_nsec); + + delay_sink = + port->latency[SPA_DIRECTION_INPUT].min_ns + + (int64_t)((port->latency[SPA_DIRECTION_INPUT].min_rate + + port->latency[SPA_DIRECTION_INPUT].min_quantum * info.duration) + * SPA_NSEC_PER_SEC / port->current_format.info.raw.rate); + __atomic_store_n(&this->delay_sink, delay_sink, __ATOMIC_RELAXED); + + /* Latency from source */ + port->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT, + .min_rate = info.buffer, .max_rate = info.buffer); + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->params[IDX_Latency].user++; + emit_port_info(this, port, false); +} + +static void update_delay_event(void *data, uint64_t count) +{ + /* in main loop */ + update_transport_delay(data); +} + static int do_start_iso_io(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { @@ -709,11 +775,15 @@ static int transport_start(struct impl *this) return res; if (this->is_duplex) { - /* 80 ms max buffer */ - spa_bt_decode_buffer_set_max_latency(&port->buffer, + /* 80 ms max extra buffer */ + spa_bt_decode_buffer_set_max_extra_latency(&port->buffer, port->current_format.info.raw.rate * 80 / 1000); } + this->delay.buffer = -1; + this->delay.duration = 0; + this->update_delay_event = spa_loop_utils_add_event(this->loop_utils, update_delay_event, this); + this->sample_count = 0; this->errqueue_count = 0; @@ -790,6 +860,11 @@ static int do_remove_source(struct spa_loop *loop, spa_bt_iso_io_set_cb(this->transport->iso_io, NULL, NULL); set_timeout(this, 0); + if (this->update_delay_event) { + spa_loop_utils_destroy_source(this->loop_utils, this->update_delay_event); + this->update_delay_event = NULL; + } + return 0; } @@ -898,7 +973,9 @@ static void emit_node_info(struct impl *this, bool full) { uint64_t old = full ? this->info.change_mask : 0; char latency[64]; + char rate[64]; char media_name[256]; + struct port *port = &this->port; spa_scnprintf( media_name, @@ -915,10 +992,12 @@ static void emit_node_info(struct impl *this, bool full) this->is_input ? "Audio/Source" : "Stream/Output/Audio" }, { SPA_KEY_NODE_LATENCY, this->is_input ? "" : latency }, { "media.name", media_name }, + { "node.rate", this->is_input ? "" : rate }, { SPA_KEY_NODE_DRIVER, this->is_input ? "true" : "false" }, }; - spa_scnprintf(latency, sizeof(latency), "%d/48000", this->node_latency); + spa_scnprintf(latency, sizeof(latency), "%u/%u", this->node_latency, port->current_format.info.raw.rate); + spa_scnprintf(rate, sizeof(rate), "1/%u", port->current_format.info.raw.rate); if (full) this->info.change_mask = this->info_all; @@ -1104,8 +1183,8 @@ impl_node_port_enum_params(void *object, int seq, case SPA_PARAM_Latency: switch (result.index) { - case 0: - param = spa_latency_build(&b, id, &port->latency); + case 0: case 1: + param = spa_latency_build(&b, id, &port->latency[result.index]); break; default: return 0; @@ -1186,6 +1265,8 @@ static int port_set_format(struct impl *this, struct port *port, port->current_format = info; port->have_format = true; + + set_latency(this, false); } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; @@ -1201,6 +1282,9 @@ static int port_set_format(struct impl *this, struct port *port, } emit_port_info(this, port, false); + this->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS; + emit_node_info(this, false); + return 0; } @@ -1224,8 +1308,28 @@ impl_node_port_set_param(void *object, res = port_set_format(this, port, flags, param); break; case SPA_PARAM_Latency: + { + enum spa_direction other = SPA_DIRECTION_REVERSE(direction); + struct spa_latency_info info; + + if (param == NULL) + info = SPA_LATENCY_INFO(other); + else if ((res = spa_latency_parse(param, &info)) < 0) + return res; + if (info.direction != other) + return -EINVAL; + if (memcmp(&port->latency[info.direction], &info, sizeof(info)) == 0) + return 0; + + port->latency[info.direction] = info; + this->port.info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + this->port.params[IDX_Latency].user++; + + update_transport_delay(this); + emit_port_info(this, port, false); res = 0; break; + } default: res = -ENOENT; break; @@ -1354,7 +1458,8 @@ static uint32_t get_samples(struct impl *this, uint32_t *result_duration) static void update_target_latency(struct impl *this) { struct port *port = &this->port; - uint32_t samples, duration; + uint32_t samples, duration, latency; + int64_t delay_sink; if (this->transport == NULL || !port->have_format) return; @@ -1380,8 +1485,11 @@ static void update_target_latency(struct impl *this) samples = (uint64_t)this->transport->delay_us * port->current_format.info.raw.rate / SPA_USEC_PER_SEC; - if (samples > duration) - samples -= duration; + delay_sink = __atomic_load_n(&this->delay_sink, __ATOMIC_RELAXED); + latency = delay_sink * port->current_format.info.raw.rate / SPA_NSEC_PER_SEC; + + if (samples > latency) + samples -= latency; else samples = 1; @@ -1406,7 +1514,9 @@ static void process_buffering(struct impl *this) update_target_latency(this); - spa_bt_decode_buffer_process(&port->buffer, samples, duration); + spa_bt_decode_buffer_process(&port->buffer, samples, duration, + this->position ? this->position->clock.rate_diff : 1.0, + this->position ? this->position->clock.next_nsec : 0); setup_matching(this); @@ -1459,6 +1569,23 @@ static void process_buffering(struct impl *this) spa_log_trace(this->log, "queue %d frames:%d", buffer->id, (int)samples); spa_list_append(&port->ready, &buffer->link); } + + if (this->update_delay_event) { + int32_t target = spa_bt_decode_buffer_get_target_latency(&port->buffer); + uint32_t decoder_delay = 0; + + if (this->codec->get_delay) + this->codec->get_delay(this->codec_data, NULL, &decoder_delay); + + target += decoder_delay; + + if (target != this->delay.buffer || duration != this->delay.duration) { + struct delay_info info = { .buffer = target, .duration = duration }; + + __atomic_store_n(&this->delay.v, info.v, __ATOMIC_RELAXED); + spa_loop_utils_signal_event(this->loop_utils, this->update_delay_event); + } + } } static int produce_buffer(struct impl *this) @@ -1679,6 +1806,7 @@ impl_init(const struct spa_handle_factory *factory, this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); + this->loop_utils = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils); spa_log_topic_init(this->log, &log_topic); @@ -1690,6 +1818,10 @@ impl_init(const struct spa_handle_factory *factory, spa_log_error(this->log, "a data system is needed"); return -EINVAL; } + if (this->loop_utils == NULL) { + spa_log_error(this->log, "loop utils are needed"); + return -EINVAL; + } this->node.iface = SPA_INTERFACE_INIT( SPA_TYPE_INTERFACE_Node, @@ -1731,9 +1863,8 @@ impl_init(const struct spa_handle_factory *factory, port->info.params = port->params; port->info.n_params = N_PORT_PARAMS; - port->latency = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); - port->latency.min_quantum = 1.0f; - port->latency.max_quantum = 1.0f; + port->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + port->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); /* Init the buffer lists */ spa_list_init(&port->ready); diff --git a/spa/plugins/bluez5/meson.build b/spa/plugins/bluez5/meson.build index 035e4b7a..b0990eaf 100644 --- a/spa/plugins/bluez5/meson.build +++ b/spa/plugins/bluez5/meson.build @@ -56,7 +56,7 @@ if get_option('bluez5-backend-hsp-native').allowed() or get_option('bluez5-backe bluez5_deps += mm_dep bluez5_sources += ['modemmanager.c'] endif - bluez5_sources += ['backend-native.c', 'upower.c'] + bluez5_sources += ['backend-native.c', 'upower.c', 'telephony.c'] endif if get_option('bluez5-backend-ofono').allowed() @@ -173,6 +173,16 @@ if get_option('bluez5-codec-lc3').allowed() and lc3_dep.found() install_dir : spa_plugindir / 'bluez5') endif +if get_option('bluez5-codec-g722').allowed() + bluez_codec_g722 = shared_library('spa-codec-bluez5-g722', + [ 'g722/g722_encode.c', 'asha-codec-g722.c', 'media-codecs.c' ], + include_directories : [ configinc ], + c_args : codec_args, + dependencies : [ spa_dep ], + install : true, + install_dir : spa_plugindir / 'bluez5') +endif + test_apps = [ 'test-midi', ] diff --git a/spa/plugins/bluez5/midi-node.c b/spa/plugins/bluez5/midi-node.c index af8c9aa6..b5fd179e 100644 --- a/spa/plugins/bluez5/midi-node.c +++ b/spa/plugins/bluez5/midi-node.c @@ -23,6 +23,7 @@ #include <spa/utils/ringbuffer.h> #include <spa/monitor/device.h> #include <spa/control/control.h> +#include <spa/control/ump-utils.h> #include <spa/node/node.h> #include <spa/node/utils.h> @@ -449,7 +450,7 @@ static void midi_event_recv(void *user_data, uint16_t timestamp, uint8_t *data, struct impl *this = user_data; struct port *port = &this->ports[PORT_OUT]; struct time_sync *sync = &port->sync; - uint64_t time; + uint64_t time, state = 0; int res; spa_assert(size > 0); @@ -459,11 +460,19 @@ static void midi_event_recv(void *user_data, uint16_t timestamp, uint8_t *data, spa_log_trace(this->log, "%p: event:0x%x size:%d timestamp:%d time:%"PRIu64"", this, (int)data[0], (int)size, (int)timestamp, (uint64_t)time); - res = midi_event_ringbuffer_push(&this->event_rbuf, time, data, size); - if (res < 0) { - midi_event_ringbuffer_init(&this->event_rbuf); - spa_log_warn(this->log, "%p: MIDI receive buffer overflow: %s", - this, spa_strerror(res)); + while (size > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&data, &size, + ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + break; + + res = midi_event_ringbuffer_push(&this->event_rbuf, time, (uint8_t*)ump, ump_size); + if (res < 0) { + midi_event_ringbuffer_init(&this->event_rbuf); + spa_log_warn(this->log, "%p: MIDI receive buffer overflow: %s", + this, spa_strerror(res)); + } } } @@ -704,7 +713,7 @@ static int process_output(struct impl *this) offset = time * this->rate / SPA_NSEC_PER_SEC; offset = SPA_CLAMP(offset, 0u, this->duration - 1); - spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_Midi); + spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP); buf = spa_pod_builder_reserve_bytes(&port->builder, size); if (buf) { midi_event_ringbuffer_pop(&this->event_rbuf, buf, size); @@ -773,15 +782,18 @@ static int write_data(struct impl *this, struct spa_data *d) time = 0; SPA_POD_SEQUENCE_FOREACH(pod, c) { - uint8_t *event; - size_t size; + int size; + uint8_t event[32]; - if (c->type != SPA_CONTROL_Midi) + if (c->type != SPA_CONTROL_UMP) continue; time = SPA_MAX(time, this->current_time + c->offset * SPA_NSEC_PER_SEC / this->rate); - event = SPA_POD_BODY(&c->value); - size = SPA_POD_BODY_SIZE(&c->value); + + size = spa_ump_to_midi(SPA_POD_BODY(&c->value), + SPA_POD_BODY_SIZE(&c->value), event, sizeof(event)); + if (size <= 0) + continue; spa_log_trace(this->log, "%p: output event:0x%x time:%"PRIu64, this, (size > 0) ? event[0] : 0, time); @@ -1555,7 +1567,8 @@ next: 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)); + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), + SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int(1u<<SPA_CONTROL_UMP)); break; case SPA_PARAM_Format: @@ -1567,7 +1580,8 @@ next: 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)); + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), + SPA_FORMAT_CONTROL_types, SPA_POD_Int(1u<<SPA_CONTROL_UMP)); break; case SPA_PARAM_Buffers: @@ -2010,13 +2024,13 @@ impl_init(const struct spa_handle_factory *factory, for (i = 0; i < N_PORTS; ++i) { struct port *port = &this->ports[i]; static const struct spa_dict_item in_port_items[] = { - SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"), + SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "in"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, "in"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, "group.0"), }; static const struct spa_dict_item out_port_items[] = { - SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "8 bit raw midi"), + SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit raw UMP"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_NAME, "out"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_ALIAS, "out"), SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, "group.0"), diff --git a/spa/plugins/bluez5/quirks.c b/spa/plugins/bluez5/quirks.c index e204822c..c4b293e6 100644 --- a/spa/plugins/bluez5/quirks.c +++ b/spa/plugins/bluez5/quirks.c @@ -87,26 +87,24 @@ static int do_match(const char *rules, struct spa_dict *dict, uint32_t *no_featu while (spa_json_enter_object(&rules_arr, &it[0]) > 0) { char key[256]; - int match = true; + int match = true, len; uint32_t no_features_cur = 0; + const char *value; - while (spa_json_get_string(&it[0], key, sizeof(key)) > 0) { + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &value)) > 0) { char val[4096]; - const char *str, *value; - int len; + const char *str; bool success = false; if (spa_streq(key, "no-features")) { - if (spa_json_enter_array(&it[0], &it[1]) > 0) { + if (spa_json_is_array(value, len) > 0) { + spa_json_enter(&it[0], &it[1]); while (spa_json_get_string(&it[1], val, sizeof(val)) > 0) no_features_cur |= parse_feature(val); } continue; } - if ((len = spa_json_next(&it[0], &value)) <= 0) - break; - if (spa_json_is_null(value, len)) { value = NULL; } else { @@ -161,17 +159,13 @@ static void load_quirks(struct spa_bt_quirks *this, const char *str, size_t len) struct spa_json rules; char key[1024]; struct spa_error_location loc; + int sz; + const char *value; if (spa_json_enter_object(&data, &rules) <= 0) spa_json_init(&rules, str, len); - while (spa_json_get_string(&rules, key, sizeof(key)) > 0) { - int sz; - const char *value; - - if ((sz = spa_json_next(&rules, &value)) <= 0) - break; - + while ((sz = spa_json_object_next(&rules, key, sizeof(key), &value)) > 0) { if (!spa_json_is_container(value, sz)) continue; @@ -284,10 +278,11 @@ static void strtolower(char *src, char *dst, int maxsize) *dst = '\0'; } -int spa_bt_quirks_get_features(const struct spa_bt_quirks *this, +static int get_features(const struct spa_bt_quirks *this, const struct spa_bt_adapter *adapter, const struct spa_bt_device *device, - uint32_t *features) + uint32_t *features, + bool debug) { struct spa_dict props; struct spa_dict_item items[5]; @@ -300,15 +295,18 @@ int spa_bt_quirks_get_features(const struct spa_bt_quirks *this, uint32_t no_features = 0; int nitems = 0; struct utsname name; + if ((res = uname(&name)) < 0) return res; items[nitems++] = SPA_DICT_ITEM_INIT("sysname", name.sysname); items[nitems++] = SPA_DICT_ITEM_INIT("release", name.release); items[nitems++] = SPA_DICT_ITEM_INIT("version", name.version); props = SPA_DICT_INIT(items, nitems); - log_props(this->log, &props); + if (debug) + log_props(this->log, &props); do_match(this->kernel_rules, &props, &no_features); - spa_log_debug(this->log, "kernel quirks:%08x", no_features); + if (debug) + spa_log_debug(this->log, "kernel quirks:%08x", no_features); *features &= ~no_features; } @@ -331,9 +329,11 @@ int spa_bt_quirks_get_features(const struct spa_bt_quirks *this, items[nitems++] = SPA_DICT_ITEM_INIT("address", address); } props = SPA_DICT_INIT(items, nitems); - log_props(this->log, &props); + if (debug) + log_props(this->log, &props); do_match(this->adapter_rules, &props, &no_features); - spa_log_debug(this->log, "adapter quirks:%08x", no_features); + if (debug) + spa_log_debug(this->log, "adapter quirks:%08x", no_features); *features &= ~no_features; } @@ -358,9 +358,11 @@ int spa_bt_quirks_get_features(const struct spa_bt_quirks *this, items[nitems++] = SPA_DICT_ITEM_INIT("address", address); } props = SPA_DICT_INIT(items, nitems); - log_props(this->log, &props); + if (debug) + log_props(this->log, &props); do_match(this->device_rules, &props, &no_features); - spa_log_debug(this->log, "device quirks:%08x", no_features); + if (debug) + spa_log_debug(this->log, "device quirks:%08x", no_features); *features &= ~no_features; } @@ -385,3 +387,21 @@ int spa_bt_quirks_get_features(const struct spa_bt_quirks *this, return 0; } + +int spa_bt_quirks_get_features(const struct spa_bt_quirks *this, + const struct spa_bt_adapter *adapter, + const struct spa_bt_device *device, + uint32_t *features) +{ + return get_features(this, adapter, device, features, false); +} + +void spa_bt_quirks_log_features(const struct spa_bt_quirks *this, + const struct spa_bt_adapter *adapter, + const struct spa_bt_device *device) +{ + uint32_t features = 0; + + get_features(this, adapter, device, &features, true); + spa_log_debug(this->log, "features:%08x", features); +} diff --git a/spa/plugins/bluez5/sco-source.c b/spa/plugins/bluez5/sco-source.c index 71725e49..dc2e1f07 100644 --- a/spa/plugins/bluez5/sco-source.c +++ b/spa/plugins/bluez5/sco-source.c @@ -458,7 +458,7 @@ static int lc3_decode_frame(struct impl *this, const void *src, size_t src_size, #endif } -static uint32_t preprocess_and_decode_codec_data(void *userdata, uint8_t *read_data, int size_read) +static uint32_t preprocess_and_decode_codec_data(void *userdata, uint8_t *read_data, int size_read, uint64_t now) { struct impl *this = userdata; struct port *port = &this->port; @@ -531,7 +531,7 @@ static uint32_t preprocess_and_decode_codec_data(void *userdata, uint8_t *read_d continue; } - spa_bt_decode_buffer_write_packet(&port->buffer, written); + spa_bt_decode_buffer_write_packet(&port->buffer, written, now); decoded += written; } @@ -566,7 +566,7 @@ static int sco_source_cb(void *userdata, uint8_t *read_data, int size_read) if (this->transport->codec == HFP_AUDIO_CODEC_MSBC || this->transport->codec == HFP_AUDIO_CODEC_LC3_SWB) { - decoded = preprocess_and_decode_codec_data(userdata, read_data, size_read); + decoded = preprocess_and_decode_codec_data(userdata, read_data, size_read, SPA_TIMESPEC_TO_NSEC(&this->now)); } else { uint32_t avail; uint8_t *packet; @@ -591,7 +591,7 @@ static int sco_source_cb(void *userdata, uint8_t *read_data, int size_read) 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); + spa_bt_decode_buffer_write_packet(&port->buffer, avail, SPA_TIMESPEC_TO_NSEC(&this->now)); decoded = avail; } @@ -728,8 +728,8 @@ static int transport_start(struct impl *this) this->quantum_limit, this->quantum_limit)) < 0) return res; - /* 40 ms max buffer */ - spa_bt_decode_buffer_set_max_latency(&port->buffer, + /* 40 ms max buffer (on top of duration) */ + spa_bt_decode_buffer_set_max_extra_latency(&port->buffer, port->current_format.info.raw.rate * 40 / 1000); /* Init mSBC/LC3 if needed */ @@ -1402,7 +1402,9 @@ static void process_buffering(struct impl *this) void *buf; uint32_t avail; - spa_bt_decode_buffer_process(&port->buffer, samples, duration); + spa_bt_decode_buffer_process(&port->buffer, samples, duration, + this->position ? this->position->clock.rate_diff : 1.0, + this->position ? this->position->clock.next_nsec : 0); setup_matching(this); diff --git a/spa/plugins/bluez5/telephony.c b/spa/plugins/bluez5/telephony.c new file mode 100644 index 00000000..c2f9d674 --- /dev/null +++ b/spa/plugins/bluez5/telephony.c @@ -0,0 +1,1870 @@ +/* Spa Bluez5 Telephony D-Bus service */ +/* SPDX-FileCopyrightText: Copyright © 2024 Collabora Ltd. */ +/* SPDX-License-Identifier: MIT */ + +#include "telephony.h" + +#include <errno.h> +#include <stdbool.h> +#include <string.h> +#include <dbus/dbus.h> +#include <spa-private/dbus-helpers.h> + +#include <spa/utils/list.h> +#include <spa/utils/string.h> + +#define PW_TELEPHONY_SERVICE "org.pipewire.Telephony" + +#define PW_TELEPHONY_OBJECT_PATH "/org/pipewire/Telephony" + +#define PW_TELEPHONY_AG_IFACE "org.pipewire.Telephony.AudioGateway1" +#define PW_TELEPHONY_AG_TRANSPORT_IFACE "org.pipewire.Telephony.AudioGatewayTransport1" +#define PW_TELEPHONY_CALL_IFACE "org.pipewire.Telephony.Call1" + +#define OFONO_MANAGER_IFACE "org.ofono.Manager" +#define OFONO_VOICE_CALL_MANAGER_IFACE "org.ofono.VoiceCallManager" +#define OFONO_VOICE_CALL_IFACE "org.ofono.VoiceCall" + +#define DBUS_OBJECT_MANAGER_IFACE_INTROSPECT_XML \ + " <interface name='" DBUS_INTERFACE_OBJECT_MANAGER "'>" \ + " <method name='GetManagedObjects'>" \ + " <arg name='objects' direction='out' type='a{oa{sa{sv}}}'/>" \ + " </method>" \ + " <signal name='InterfacesAdded'>" \ + " <arg name='object' type='o'/>" \ + " <arg name='interfaces' type='a{sa{sv}}'/>" \ + " </signal>" \ + " <signal name='InterfacesRemoved'>" \ + " <arg name='object' type='o'/>" \ + " <arg name='interfaces' type='as'/>" \ + " </signal>" \ + " </interface>" + +#define DBUS_PROPERTIES_IFACE_INTROSPECT_XML \ + " <interface name='" DBUS_INTERFACE_PROPERTIES "'>" \ + " <method name='Get'>" \ + " <arg name='interface' type='s' direction='in' />" \ + " <arg name='name' type='s' direction='in' />" \ + " <arg name='value' type='v' direction='out' />" \ + " </method>" \ + " <method name='Set'>" \ + " <arg name='interface' type='s' direction='in' />" \ + " <arg name='name' type='s' direction='in' />" \ + " <arg name='value' type='v' direction='in' />" \ + " </method>" \ + " <method name='GetAll'>" \ + " <arg name='interface' type='s' direction='in' />" \ + " <arg name='properties' type='a{sv}' direction='out' />" \ + " </method>" \ + " <signal name='PropertiesChanged'>" \ + " <arg name='interface' type='s' />" \ + " <arg name='changed_properties' type='a{sv}' />" \ + " <arg name='invalidated_properties' type='as' />" \ + " </signal>" \ + " </interface>" + +#define DBUS_INTROSPECTABLE_IFACE_INTROSPECT_XML \ + " <interface name='" DBUS_INTERFACE_INTROSPECTABLE "'>" \ + " <method name='Introspect'>" \ + " <arg name='xml' type='s' direction='out'/>" \ + " </method>" \ + " </interface>" + +#define PW_TELEPHONY_MANAGER_INTROSPECT_XML \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>" \ + " <interface name='" OFONO_MANAGER_IFACE "'>" \ + " <method name='GetModems'>" \ + " <arg name='objects' direction='out' type='a{oa{sv}}'/>" \ + " </method>" \ + " <signal name='ModemAdded'>" \ + " <arg name='path' type='o'/>" \ + " <arg name='properties' type='a{sv}'/>" \ + " </signal>" \ + " <signal name='ModemRemoved'>" \ + " <arg name='path' type='o'/>" \ + " </signal>" \ + " </interface>" \ + DBUS_OBJECT_MANAGER_IFACE_INTROSPECT_XML \ + DBUS_INTROSPECTABLE_IFACE_INTROSPECT_XML \ + "</node>" + +#define PW_TELEPHONY_AG_COMMON_INTROSPECT_XML \ + " <method name='Dial'>" \ + " <arg name='number' direction='in' type='s'/>" \ + " </method>" \ + " <method name='SwapCalls'>" \ + " </method>" \ + " <method name='ReleaseAndAnswer'>" \ + " </method>" \ + " <method name='ReleaseAndSwap'>" \ + " </method>" \ + " <method name='HoldAndAnswer'>" \ + " </method>" \ + " <method name='HangupAll'>" \ + " </method>" \ + " <method name='CreateMultiparty'>" \ + " </method>" \ + " <method name='SendTones'>" \ + " <arg name='tones' direction='in' type='s'/>" \ + " </method>" + +#define PW_TELEPHONY_AG_INTROSPECT_XML \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>" \ + " <interface name='" PW_TELEPHONY_AG_IFACE "'>" \ + PW_TELEPHONY_AG_COMMON_INTROSPECT_XML \ + " <property name='Address' type='s' access='read'>" \ + " <annotation name='org.freedesktop.DBus.Property.EmitsChangedSignal' value='const'/>" \ + " </property>" \ + " </interface>" \ + " <interface name='" PW_TELEPHONY_AG_TRANSPORT_IFACE "'>" \ + " <property name='State' type='s' access='read'/>" \ + " <property name='Codec' type='y' access='read'/>" \ + " <property name='RejectSCO' type='b' access='readwrite'/>" \ + " <method name='Activate'/>" \ + " </interface>" \ + " <interface name='" OFONO_VOICE_CALL_MANAGER_IFACE "'>" \ + PW_TELEPHONY_AG_COMMON_INTROSPECT_XML \ + " <method name='GetCalls'>" \ + " <arg name='objects' direction='out' type='a{oa{sv}}'/>" \ + " </method>" \ + " <signal name='CallAdded'>" \ + " <arg name='path' type='o'/>" \ + " <arg name='properties' type='a{sv}'/>" \ + " </signal>" \ + " <signal name='CallRemoved'>" \ + " <arg name='path' type='o'/>" \ + " </signal>" \ + " </interface>" \ + DBUS_OBJECT_MANAGER_IFACE_INTROSPECT_XML \ + DBUS_PROPERTIES_IFACE_INTROSPECT_XML \ + DBUS_INTROSPECTABLE_IFACE_INTROSPECT_XML \ + "</node>" + +#define PW_TELEPHONY_CALL_COMMON_INTROSPECT_XML \ + " <method name='Answer'>" \ + " </method>" \ + " <method name='Hangup'>" \ + " </method>" + +#define PW_TELEPHONY_CALL_INTROSPECT_XML \ + DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE \ + "<node>" \ + " <interface name='" PW_TELEPHONY_CALL_IFACE "'>" \ + PW_TELEPHONY_CALL_COMMON_INTROSPECT_XML \ + " <property name='LineIdentification' type='s' access='read'/>" \ + " <property name='IncomingLine' type='s' access='read'/>" \ + " <property name='Name' type='s' access='read'/>" \ + " <property name='Multiparty' type='b' access='read'/>" \ + " <property name='State' type='s' access='read'/>" \ + " </interface>" \ + " <interface name='" OFONO_VOICE_CALL_IFACE "'>" \ + PW_TELEPHONY_CALL_COMMON_INTROSPECT_XML \ + " <method name='GetProperties'>" \ + " <arg name='properties' type='a{sv}' direction='out' />" \ + " </method>" \ + " <signal name='PropertyChanged'>" \ + " <arg name='property' type='s' />" \ + " <arg name='value' type='v' />" \ + " </signal>" \ + " </interface>" \ + DBUS_PROPERTIES_IFACE_INTROSPECT_XML \ + DBUS_INTROSPECTABLE_IFACE_INTROSPECT_XML \ + "</node>" + +SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.telephony"); +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &log_topic + +struct callimpl; + +struct impl { + struct spa_bt_telephony this; + + struct spa_log *log; + struct spa_dbus *dbus; + + struct spa_dbus_connection *dbus_connection; + DBusConnection *conn; + + const char *path; + struct spa_list ag_list; + + bool default_reject_sco; +}; + +struct agimpl { + struct spa_bt_telephony_ag this; + struct spa_list link; + char *path; + struct spa_callbacks callbacks; + void *user_data; + + bool dial_in_progress; + struct callimpl *dial_return; + + struct { + struct spa_bt_telephony_ag_transport transport; + } prev; +}; + +struct callimpl { + struct spa_bt_telephony_call this; + char *path; + struct spa_callbacks callbacks; + void *user_data; + + /* previous values of properties */ + struct { + char *line_identification; + char *incoming_line; + char *name; + bool multiparty; + enum spa_bt_telephony_call_state state; + } prev; +}; + +#define ag_emit(ag,m,v,...) spa_callbacks_call(&ag->callbacks, struct spa_bt_telephony_ag_callbacks, m, v, ##__VA_ARGS__) +#define ag_emit_dial(s,n,e,cme) ag_emit(s,dial,0,n,e,cme) +#define ag_emit_swap_calls(s,e,cme) ag_emit(s,swap_calls,0,e,cme) +#define ag_emit_release_and_answer(s,e,cme) ag_emit(s,release_and_answer,0,e,cme) +#define ag_emit_release_and_swap(s,e,cme) ag_emit(s,release_and_swap,0,e,cme) +#define ag_emit_hold_and_answer(s,e,cme) ag_emit(s,hold_and_answer,0,e,cme) +#define ag_emit_hangup_all(s,e,cme) ag_emit(s,hangup_all,0,e,cme) +#define ag_emit_create_multiparty(s,e,cme) ag_emit(s,create_multiparty,0,e,cme) +#define ag_emit_send_tones(s,t,e,cme) ag_emit(s,send_tones,0,t,e,cme) +#define ag_emit_transport_activate(s,e,cme) ag_emit(s,transport_activate,0,e,cme) + +#define call_emit(c,m,v,...) spa_callbacks_call(&c->callbacks, struct spa_bt_telephony_call_callbacks, m, v, ##__VA_ARGS__) +#define call_emit_answer(s,e,cme) call_emit(s,answer,0,e,cme) +#define call_emit_hangup(s,e,cme) call_emit(s,hangup,0,e,cme) + +static void dbus_iter_append_ag_interfaces(DBusMessageIter *i, struct spa_bt_telephony_ag *ag); +static void dbus_iter_append_call_properties(DBusMessageIter *i, struct spa_bt_telephony_call *call, bool all); + +#define PW_TELEPHONY_ERROR_FAILED "org.pipewire.Telephony.Error.Failed" +#define PW_TELEPHONY_ERROR_NOT_SUPPORTED "org.pipewire.Telephony.Error.NotSupported" +#define PW_TELEPHONY_ERROR_INVALID_FORMAT "org.pipewire.Telephony.Error.InvalidFormat" +#define PW_TELEPHONY_ERROR_INVALID_STATE "org.pipewire.Telephony.Error.InvalidState" +#define PW_TELEPHONY_ERROR_IN_PROGRESS "org.pipewire.Telephony.Error.InProgress" +#define PW_TELEPHONY_ERROR_CME "org.pipewire.Telephony.Error.CME" + +static const char *telephony_error_to_dbus (enum spa_bt_telephony_error err) +{ + switch (err) { + case BT_TELEPHONY_ERROR_FAILED: + return PW_TELEPHONY_ERROR_FAILED; + case BT_TELEPHONY_ERROR_NOT_SUPPORTED: + return PW_TELEPHONY_ERROR_NOT_SUPPORTED; + case BT_TELEPHONY_ERROR_INVALID_FORMAT: + return PW_TELEPHONY_ERROR_INVALID_FORMAT; + case BT_TELEPHONY_ERROR_INVALID_STATE: + return PW_TELEPHONY_ERROR_INVALID_STATE; + case BT_TELEPHONY_ERROR_IN_PROGRESS: + return PW_TELEPHONY_ERROR_IN_PROGRESS; + case BT_TELEPHONY_ERROR_CME: + return PW_TELEPHONY_ERROR_CME; + default: + return ""; + } +} + +static const char *telephony_error_to_description (enum spa_bt_telephony_error err, uint8_t cme_error) +{ + switch (err) { + case BT_TELEPHONY_ERROR_FAILED: + return "Method call failed"; + case BT_TELEPHONY_ERROR_NOT_SUPPORTED: + return "Method is not supported on this Audio Gateway"; + case BT_TELEPHONY_ERROR_INVALID_FORMAT: + return "Invalid phone number or tones"; + case BT_TELEPHONY_ERROR_INVALID_STATE: + return "The current state does not allow this method call"; + case BT_TELEPHONY_ERROR_IN_PROGRESS: + return "Command already in progress"; + case BT_TELEPHONY_ERROR_CME: + switch (cme_error) { + case 0: return "AG failure"; + case 1: return "no connection to phone"; + case 3: return "operation not allowed"; + case 4: return "operation not supported"; + case 5: return "PH-SIM PIN required"; + case 10: return "SIM not inserted"; + case 11: return "SIM PIN required"; + case 12: return "SIM PUK required"; + case 13: return "SIM failure"; + case 14: return "SIM busy"; + case 16: return "incorrect password"; + case 17: return "SIM PIN2 required"; + case 18: return "SIM PUK2 required"; + case 20: return "memory full"; + case 21: return "invalid index"; + case 23: return "memory failure"; + case 24: return "text string too long"; + case 25: return "invalid characters in text string"; + case 26: return "dial string too long"; + case 27: return "invalid characters in dial string"; + case 30: return "no network service"; + case 31: return "network Timeout"; + case 32: return "network not allowed - Emergency calls only"; + default: return "Unknown CME error"; + } + default: + return ""; + } +} + +#define find_free_object_id(list, obj_type, link) \ +({ \ + int id = 1; \ + obj_type *object; \ + spa_list_for_each(object, list, link) { \ + if (object->this.id <= id) \ + id = object->this.id + 1; \ + } \ + id; \ +}) + +static DBusMessage *manager_introspect(struct impl *impl, DBusMessage *m) +{ + const char *xml = PW_TELEPHONY_MANAGER_INTROSPECT_XML; + spa_autoptr(DBusMessage) r = NULL; + if ((r = dbus_message_new_method_return(m)) == NULL) + return NULL; + if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) + return NULL; + return spa_steal_ptr(r); +} + +static DBusMessage *manager_get_managed_objects(struct impl *impl, DBusMessage *m, bool ofono_compat) +{ + struct agimpl *agimpl; + spa_autoptr(DBusMessage) r = NULL; + DBusMessageIter iter, array1, entry1, props_dict; + + if ((r = dbus_message_new_method_return(m)) == NULL) + return NULL; + + dbus_message_iter_init_append(r, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + ofono_compat ? "{oa{sv}}" : "{oa{sa{sv}}}", &array1); + + spa_list_for_each (agimpl, &impl->ag_list, link) { + if (agimpl->path) { + dbus_message_iter_open_container(&array1, DBUS_TYPE_DICT_ENTRY, NULL, &entry1); + if (ofono_compat) { + dbus_message_iter_append_basic(&entry1, DBUS_TYPE_OBJECT_PATH, &agimpl->path); + dbus_message_iter_open_container(&entry1, DBUS_TYPE_ARRAY, "{sv}", &props_dict); + dbus_message_iter_close_container(&entry1, &props_dict); + } else { + dbus_iter_append_ag_interfaces(&entry1, &agimpl->this); + } + dbus_message_iter_close_container(&array1, &entry1); + } + } + dbus_message_iter_close_container(&iter, &array1); + + return spa_steal_ptr(r); +} + +static DBusHandlerResult manager_handler(DBusConnection *c, DBusMessage *m, void *userdata) +{ + struct impl *impl = userdata; + + spa_autoptr(DBusMessage) r = NULL; + const char *path, *interface, *member; + + path = dbus_message_get_path(m); + interface = dbus_message_get_interface(m); + member = dbus_message_get_member(m); + + spa_log_debug(impl->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member); + + if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { + r = manager_introspect(impl, m); + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_OBJECT_MANAGER, "GetManagedObjects")) { + r = manager_get_managed_objects(impl, m, false); + } else if (dbus_message_is_method_call(m, OFONO_MANAGER_IFACE, "GetModems")) { + r = manager_get_managed_objects(impl, m, true); + } else { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (r == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + if (!dbus_connection_send(impl->conn, r, NULL)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + return DBUS_HANDLER_RESULT_HANDLED; +} + +struct spa_bt_telephony * +telephony_new(struct spa_log *log, struct spa_dbus *dbus, const struct spa_dict *info) +{ + struct impl *impl = NULL; + spa_auto(DBusError) err = DBUS_ERROR_INIT; + bool service_enabled = true; + bool ofono_service_compat = false; + enum spa_dbus_type bus_type = SPA_DBUS_TYPE_SESSION; + int res; + + static const DBusObjectPathVTable vtable_manager = { + .message_function = manager_handler, + }; + + spa_assert(log); + spa_assert(dbus); + + spa_log_topic_init(log, &log_topic); + + if (info) { + const char *str; + if ((str = spa_dict_lookup(info, "bluez5.telephony-dbus-service")) != NULL) { + service_enabled = spa_atob(str); + } + if ((str = spa_dict_lookup(info, "bluez5.telephony.use-system-bus")) != NULL) { + bus_type = spa_atob(str) ? SPA_DBUS_TYPE_SYSTEM : SPA_DBUS_TYPE_SESSION; + } + if ((str = spa_dict_lookup(info, "bluez5.telephony.provide-ofono")) != NULL) { + ofono_service_compat = spa_atob(str); + bus_type = SPA_DBUS_TYPE_SYSTEM; + } + } + + if (!service_enabled) { + spa_log_info(log, "Bluetooth Telephony service disabled by configuration"); + return NULL; + } + + impl = calloc(1, sizeof(*impl)); + if (impl == NULL) + return NULL; + + impl->log = log; + impl->dbus = dbus; + impl->ag_list = SPA_LIST_INIT(&impl->ag_list); + + impl->dbus_connection = spa_dbus_get_connection(impl->dbus, bus_type); + if (impl->dbus_connection == NULL) { + spa_log_warn(impl->log, "no session dbus connection"); + goto fail; + } + impl->conn = spa_dbus_connection_get(impl->dbus_connection); + if (impl->conn == NULL) { + spa_log_warn(impl->log, "failed to get session dbus connection"); + goto fail; + } + + impl->default_reject_sco = false; + if (info) { + const char *str; + if ((str = spa_dict_lookup(info, "bluez5.telephony.default-reject-sco")) != NULL) { + impl->default_reject_sco = spa_atob(str); + } + } + + /* XXX: We should handle spa_dbus reconnecting, but we don't, so ref + * XXX: the handle so that we can keep it if spa_dbus unrefs it. + */ + dbus_connection_ref(impl->conn); + + res = dbus_bus_request_name(impl->conn, + ofono_service_compat ? OFONO_SERVICE : PW_TELEPHONY_SERVICE, + DBUS_NAME_FLAG_DO_NOT_QUEUE, &err); + if (res < 0) { + spa_log_warn(impl->log, "D-Bus RequestName() error: %s", err.message); + goto fail; + } + if (res == DBUS_REQUEST_NAME_REPLY_EXISTS) { + spa_log_warn(impl->log, "Bluetooth Telephony service is already registered by another connection"); + goto fail; + } + + impl->path = ofono_service_compat ? "/" : PW_TELEPHONY_OBJECT_PATH; + + if (!dbus_connection_register_object_path(impl->conn, impl->path, + &vtable_manager, impl)) { + goto fail; + } + + return &impl->this; + +fail: + spa_log_info(impl->log, "Bluetooth Telephony service disabled due to failure"); + if (impl->conn) + dbus_connection_unref(impl->conn); + if (impl->dbus_connection) + spa_dbus_connection_destroy(impl->dbus_connection); + free(impl); + return NULL; +} + +void telephony_free(struct spa_bt_telephony *telephony) +{ + struct impl *impl = SPA_CONTAINER_OF(telephony, struct impl, this); + struct agimpl *agimpl; + + spa_list_consume (agimpl, &impl->ag_list, link) { + telephony_ag_destroy(&agimpl->this); + } + + dbus_connection_unref(impl->conn); + spa_dbus_connection_destroy(impl->dbus_connection); + impl->dbus_connection = NULL; + impl->conn = NULL; + + free(impl); +} + +static void telephony_ag_transport_commit_properties(struct spa_bt_telephony_ag *ag) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + agimpl->prev.transport = ag->transport; +} + +static const char * const * transport_state_to_string(int state) +{ + static const char * const state_str[] = { + "error", + "idle", + "pending", + "active", + }; + if (state < -1 || state > 2) + state = -1; + return &state_str[state + 1]; +} + +static bool +dbus_iter_append_ag_properties(DBusMessageIter *i, struct spa_bt_telephony_ag *ag, bool all) +{ + DBusMessageIter dict, entry, variant; + bool changed = false; + + dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "{sv}", &dict); + + /* Address must be set before registering and never changes, + so there is no need to check for changes here */ + if (all) { + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + const char *name = "Address"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, &ag->address); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + changed = true; + } + + dbus_message_iter_close_container(i, &dict); + return changed; +} + +static bool +dbus_iter_append_ag_transport_properties(DBusMessageIter *i, struct spa_bt_telephony_ag *ag, bool all) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + DBusMessageIter dict, entry, variant; + bool changed = false; + + dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "{sv}", &dict); + + if (all || ag->transport.codec != agimpl->prev.transport.codec) { + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + const char *name = "Codec"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_BYTE_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_BYTE, &ag->transport.codec); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + changed = true; + } + + if (all || ag->transport.state != agimpl->prev.transport.state) { + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + const char *name = "State"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, + transport_state_to_string(ag->transport.state)); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + changed = true; + } + + if (all || ag->transport.rejectSCO != agimpl->prev.transport.rejectSCO) { + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + const char *name = "RejectSCO"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_BOOLEAN_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, + &ag->transport.rejectSCO); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + changed = true; + } + + dbus_message_iter_close_container(i, &dict); + return changed; +} + +static void +dbus_iter_append_ag_interfaces(DBusMessageIter *i, struct spa_bt_telephony_ag *ag) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + DBusMessageIter entry, dict; + + dbus_message_iter_append_basic(i, DBUS_TYPE_OBJECT_PATH, &agimpl->path); + dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "{sa{sv}}", &dict); + + const char *interface = PW_TELEPHONY_AG_IFACE; + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface); + dbus_iter_append_ag_properties(&entry, ag, true); + dbus_message_iter_close_container(&dict, &entry); + + const char *interface2 = PW_TELEPHONY_AG_TRANSPORT_IFACE; + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface2); + dbus_iter_append_ag_transport_properties(&entry, ag, true); + dbus_message_iter_close_container(&dict, &entry); + + dbus_message_iter_close_container(i, &dict); +} + +static DBusMessage *ag_introspect(struct agimpl *agimpl, DBusMessage *m) +{ + const char *xml = PW_TELEPHONY_AG_INTROSPECT_XML; + spa_autoptr(DBusMessage) r = NULL; + if ((r = dbus_message_new_method_return(m)) == NULL) + return NULL; + if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) + return NULL; + return spa_steal_ptr(r); +} + +static DBusMessage *ag_get_managed_objects(struct agimpl *agimpl, DBusMessage *m, bool ofono_compat) +{ + struct callimpl *callimpl; + spa_autoptr(DBusMessage) r = NULL; + DBusMessageIter iter, array1, entry1, array2, entry2; + const char *interface = PW_TELEPHONY_CALL_IFACE; + + if ((r = dbus_message_new_method_return(m)) == NULL) + return NULL; + + dbus_message_iter_init_append(r, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + ofono_compat ? "{oa{sv}}" : "{oa{sa{sv}}}", &array1); + + spa_list_for_each (callimpl, &agimpl->this.call_list, this.link) { + dbus_message_iter_open_container(&array1, DBUS_TYPE_DICT_ENTRY, NULL, &entry1); + dbus_message_iter_append_basic(&entry1, DBUS_TYPE_OBJECT_PATH, &callimpl->path); + if (ofono_compat) { + dbus_iter_append_call_properties(&entry1, &callimpl->this, true); + } else { + dbus_message_iter_open_container(&entry1, DBUS_TYPE_ARRAY, "{sa{sv}}", &array2); + dbus_message_iter_open_container(&array2, DBUS_TYPE_DICT_ENTRY, NULL, &entry2); + dbus_message_iter_append_basic(&entry2, DBUS_TYPE_STRING, &interface); + dbus_iter_append_call_properties(&entry2, &callimpl->this, true); + dbus_message_iter_close_container(&array2, &entry2); + dbus_message_iter_close_container(&entry1, &array2); + } + dbus_message_iter_close_container(&array1, &entry1); + } + dbus_message_iter_close_container(&iter, &array1); + + return spa_steal_ptr(r); +} + +static DBusMessage *ag_properties_get(struct agimpl *agimpl, DBusMessage *m) +{ + const char *iface, *name; + DBusMessage *r; + DBusMessageIter i, v; + + if (!dbus_message_get_args(m, NULL, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return NULL; + + if (spa_streq(iface, PW_TELEPHONY_AG_IFACE)) { + if (spa_streq(name, "Address")) { + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + dbus_message_iter_init_append(r, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, &v); + dbus_message_iter_append_basic(&v, DBUS_TYPE_STRING, + &agimpl->this.address); + dbus_message_iter_close_container(&i, &v); + return r; + } + } else if (spa_streq(iface, PW_TELEPHONY_AG_TRANSPORT_IFACE)) { + if (spa_streq(name, "Codec")) { + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + dbus_message_iter_init_append(r, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, + DBUS_TYPE_BYTE_AS_STRING, &v); + dbus_message_iter_append_basic(&v, DBUS_TYPE_BYTE, + &agimpl->this.transport.codec); + dbus_message_iter_close_container(&i, &v); + return r; + } else if (spa_streq(name, "State")) { + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + dbus_message_iter_init_append(r, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, &v); + dbus_message_iter_append_basic(&v, DBUS_TYPE_STRING, + transport_state_to_string(agimpl->this.transport.state)); + dbus_message_iter_close_container(&i, &v); + return r; + } else if (spa_streq(name, "RejectSCO")) { + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + dbus_message_iter_init_append(r, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, + DBUS_TYPE_BOOLEAN_AS_STRING, &v); + dbus_message_iter_append_basic(&v, DBUS_TYPE_BOOLEAN, + &agimpl->this.transport.rejectSCO); + dbus_message_iter_close_container(&i, &v); + return r; + } + } else { + return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_INTERFACE, + "No such interface"); + } + + return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_PROPERTY, + "No such property"); +} + +static DBusMessage *ag_properties_get_all(struct agimpl *agimpl, DBusMessage *m) +{ + DBusMessage *r; + DBusMessageIter i; + const char *iface; + + if (!dbus_message_get_args(m, NULL, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_INVALID)) + return NULL; + + if (spa_streq(iface, PW_TELEPHONY_AG_IFACE)) { + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + dbus_message_iter_init_append(r, &i); + dbus_iter_append_ag_properties(&i, &agimpl->this, true); + return r; + } else if (spa_streq(iface, PW_TELEPHONY_AG_TRANSPORT_IFACE)) { + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + dbus_message_iter_init_append(r, &i); + dbus_iter_append_ag_transport_properties(&i, &agimpl->this, true); + return r; + } else { + return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_INTERFACE, + "No such interface"); + } +} + +static DBusMessage *ag_properties_set(struct agimpl *agimpl, DBusMessage *m) +{ + const char *iface, *name; + DBusMessageIter i, variant; + + if (!dbus_message_get_args(m, NULL, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return NULL; + + if (spa_streq(iface, PW_TELEPHONY_AG_TRANSPORT_IFACE)) { + if (spa_streq(name, "RejectSCO")) { + dbus_message_iter_init(m, &i); + dbus_message_iter_next(&i); /* skip iface */ + dbus_message_iter_next(&i); /* skip name */ + dbus_message_iter_recurse(&i, &variant); /* value */ + dbus_message_iter_get_basic(&variant, &agimpl->this.transport.rejectSCO); + return dbus_message_new_method_return(m); + } + } + + return dbus_message_new_error(m, DBUS_ERROR_PROPERTY_READ_ONLY, + "Property not writable"); +} + +static bool validate_phone_number(const char *number) +{ + const char *c; + int count = 0; + + if (!number) + return false; + for (c = number; *c != '\0'; c++) { + if (!(*c >= '0' && *c <= '9') && !(*c >= 'A' && *c <= 'D') && + *c != '#' && *c != '*' && *c != '+' && *c != ',' ) + return false; + count++; + } + if (count < 1 || count > 80) + return false; + return true; +} + +static bool validate_tones(const char *tones) +{ + const char *c; + if (!tones) + return false; + for (c = tones; *c != '\0'; c++) { + if (!(*c >= '0' && *c <= '9') && !(*c >= 'A' && *c <= 'D') && + *c != '#' && *c != '*') + return false; + } + return true; +} + +static DBusMessage *ag_dial(struct agimpl *agimpl, DBusMessage *m) +{ + const char *number = NULL; + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; + uint8_t cme_error; + spa_autoptr(DBusMessage) r = NULL; + + if (!dbus_message_get_args(m, NULL, + DBUS_TYPE_STRING, &number, + DBUS_TYPE_INVALID)) + return NULL; + + if (!validate_phone_number(number)) { + err = BT_TELEPHONY_ERROR_INVALID_FORMAT; + goto failed; + } + + agimpl->dial_in_progress = true; + if (!ag_emit_dial(agimpl, number, &err, &cme_error)) { + agimpl->dial_in_progress = false; + goto failed; + } + agimpl->dial_in_progress = false; + + if (!agimpl->dial_return || !agimpl->dial_return->path) + err = BT_TELEPHONY_ERROR_FAILED; + + if (err != BT_TELEPHONY_ERROR_NONE) + goto failed; + + if ((r = dbus_message_new_method_return(m)) == NULL) + return NULL; + if (!dbus_message_append_args(r, DBUS_TYPE_OBJECT_PATH, + &agimpl->dial_return->path, DBUS_TYPE_INVALID)) + return NULL; + + agimpl->dial_return = NULL; + + return spa_steal_ptr(r); + +failed: + return dbus_message_new_error(m, telephony_error_to_dbus (err), + telephony_error_to_description (err, cme_error)); +} + +static DBusMessage *ag_swap_calls(struct agimpl *agimpl, DBusMessage *m) +{ + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; + uint8_t cme_error; + + if (ag_emit_swap_calls(agimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) + return dbus_message_new_method_return(m); + + return dbus_message_new_error(m, telephony_error_to_dbus (err), + telephony_error_to_description (err, cme_error)); +} + +static DBusMessage *ag_release_and_answer(struct agimpl *agimpl, DBusMessage *m) +{ + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; + uint8_t cme_error; + + if (ag_emit_release_and_answer(agimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) + return dbus_message_new_method_return(m); + + return dbus_message_new_error(m, telephony_error_to_dbus (err), + telephony_error_to_description (err, cme_error)); +} + +static DBusMessage *ag_release_and_swap(struct agimpl *agimpl, DBusMessage *m) +{ + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; + uint8_t cme_error; + + if (ag_emit_release_and_swap(agimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) + return dbus_message_new_method_return(m); + + return dbus_message_new_error(m, telephony_error_to_dbus (err), + telephony_error_to_description (err, cme_error)); +} + +static DBusMessage *ag_hold_and_answer(struct agimpl *agimpl, DBusMessage *m) +{ + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; + uint8_t cme_error; + + if (ag_emit_hold_and_answer(agimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) + return dbus_message_new_method_return(m); + + return dbus_message_new_error(m, telephony_error_to_dbus (err), + telephony_error_to_description (err, cme_error)); +} + +static DBusMessage *ag_hangup_all(struct agimpl *agimpl, DBusMessage *m) +{ + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; + uint8_t cme_error; + + if (ag_emit_hangup_all(agimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) + return dbus_message_new_method_return(m); + + return dbus_message_new_error(m, telephony_error_to_dbus (err), + telephony_error_to_description (err, cme_error)); +} + +static DBusMessage *ag_create_multiparty(struct agimpl *agimpl, DBusMessage *m) +{ + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; + uint8_t cme_error; + + if (ag_emit_create_multiparty(agimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) + return dbus_message_new_method_return(m); + + return dbus_message_new_error(m, telephony_error_to_dbus (err), + telephony_error_to_description (err, cme_error)); +} + +static DBusMessage *ag_send_tones(struct agimpl *agimpl, DBusMessage *m) +{ + const char *tones = NULL; + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; + uint8_t cme_error; + + if (!dbus_message_get_args(m, NULL, + DBUS_TYPE_STRING, &tones, + DBUS_TYPE_INVALID)) + return NULL; + + if (!validate_tones(tones)) { + err = BT_TELEPHONY_ERROR_INVALID_FORMAT; + goto failed; + } + + if (ag_emit_send_tones(agimpl, tones, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) + return dbus_message_new_method_return(m); + +failed: + return dbus_message_new_error(m, telephony_error_to_dbus (err), + telephony_error_to_description (err, cme_error)); +} + +static DBusMessage *ag_transport_activate(struct agimpl *agimpl, DBusMessage *m) +{ + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; + uint8_t cme_error; + + if (ag_emit_transport_activate(agimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) + return dbus_message_new_method_return(m); + + return dbus_message_new_error(m, telephony_error_to_dbus (err), + telephony_error_to_description (err, cme_error)); +} + +static DBusHandlerResult ag_handler(DBusConnection *c, DBusMessage *m, void *userdata) +{ + struct agimpl *agimpl = userdata; + struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); + + spa_autoptr(DBusMessage) r = NULL; + const char *path, *interface, *member; + + path = dbus_message_get_path(m); + interface = dbus_message_get_interface(m); + member = dbus_message_get_member(m); + + spa_log_debug(impl->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member); + + if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { + r = ag_introspect(agimpl, m); + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_OBJECT_MANAGER, "GetManagedObjects")) { + r = ag_get_managed_objects(agimpl, m, false); + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Get")) { + r = ag_properties_get(agimpl, m); + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "GetAll")) { + r = ag_properties_get_all(agimpl, m); + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Set")) { + r = ag_properties_set(agimpl, m); + } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "Dial") || + dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "Dial")) { + r = ag_dial(agimpl, m); + } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "SwapCalls") || + dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "SwapCalls")) { + r = ag_swap_calls(agimpl, m); + } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "ReleaseAndAnswer") || + dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "ReleaseAndAnswer")) { + r = ag_release_and_answer(agimpl, m); + } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "ReleaseAndSwap") || + dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "ReleaseAndSwap")) { + r = ag_release_and_swap(agimpl, m); + } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "HoldAndAnswer") || + dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "HoldAndAnswer")) { + r = ag_hold_and_answer(agimpl, m); + } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "HangupAll") || + dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "HangupAll")) { + r = ag_hangup_all(agimpl, m); + } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "CreateMultiparty") || + dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "CreateMultiparty")) { + r = ag_create_multiparty(agimpl, m); + } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_IFACE, "SendTones") || + dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "SendTones")) { + r = ag_send_tones(agimpl, m); + } else if (dbus_message_is_method_call(m, OFONO_VOICE_CALL_MANAGER_IFACE, "GetCalls")) { + r = ag_get_managed_objects(agimpl, m, true); + } else if (dbus_message_is_method_call(m, PW_TELEPHONY_AG_TRANSPORT_IFACE, "Activate")) { + r = ag_transport_activate(agimpl, m); + } else { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (r == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + if (!dbus_connection_send(impl->conn, r, NULL)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + return DBUS_HANDLER_RESULT_HANDLED; +} + +struct spa_bt_telephony_ag * +telephony_ag_new(struct spa_bt_telephony *telephony, size_t user_data_size) +{ + struct impl *impl = SPA_CONTAINER_OF(telephony, struct impl, this); + struct agimpl *agimpl; + + spa_assert(user_data_size < SIZE_MAX - sizeof(*agimpl)); + + agimpl = calloc(1, sizeof(*agimpl) + user_data_size); + if (agimpl == NULL) + return NULL; + + agimpl->this.telephony = telephony; + agimpl->this.id = find_free_object_id(&impl->ag_list, struct agimpl, link); + spa_list_init(&agimpl->this.call_list); + + spa_list_append(&impl->ag_list, &agimpl->link); + + if (user_data_size > 0) + agimpl->user_data = SPA_PTROFF(agimpl, sizeof(struct agimpl), void); + + agimpl->this.transport.rejectSCO = impl->default_reject_sco; + + return &agimpl->this; +} + +void telephony_ag_destroy(struct spa_bt_telephony_ag *ag) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + struct callimpl *callimpl; + + spa_list_consume (callimpl, &agimpl->this.call_list, this.link) { + telephony_call_destroy(&callimpl->this); + } + + telephony_ag_unregister(ag); + spa_list_remove(&agimpl->link); + + free(ag->address); + + free(agimpl); +} + +void *telephony_ag_get_user_data(struct spa_bt_telephony_ag *ag) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + return agimpl->user_data; +} + +int telephony_ag_register(struct spa_bt_telephony_ag *ag) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); + char *path; + + const DBusObjectPathVTable vtable = { + .message_function = ag_handler, + }; + + path = spa_aprintf (PW_TELEPHONY_OBJECT_PATH "/ag%d", agimpl->this.id); + + /* register object */ + if (!dbus_connection_register_object_path(impl->conn, path, &vtable, agimpl)) { + spa_log_error(impl->log, "failed to register %s", path); + return -EIO; + } + agimpl->path = strdup(path); + + /* notify on ObjectManager of the Manager object */ + { + spa_autoptr(DBusMessage) msg = NULL; + DBusMessageIter iter; + + msg = dbus_message_new_signal(impl->path, DBUS_INTERFACE_OBJECT_MANAGER, + "InterfacesAdded"); + dbus_message_iter_init_append(msg, &iter); + dbus_iter_append_ag_interfaces(&iter, ag); + + if (!dbus_connection_send(impl->conn, msg, NULL)) { + spa_log_error(impl->log, "failed to send InterfacesAdded for %s", path); + telephony_ag_unregister(ag); + return -EIO; + } + } + + /* emit ModemAdded on the Manager object */ + { + spa_autoptr(DBusMessage) msg = NULL; + DBusMessageIter iter, props_dict; + + msg = dbus_message_new_signal(impl->path, OFONO_MANAGER_IFACE, + "ModemAdded"); + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &props_dict); + dbus_message_iter_close_container(&iter, &props_dict); + + if (!dbus_connection_send(impl->conn, msg, NULL)) { + spa_log_error(impl->log, "failed to send ModemAdded for %s", path); + telephony_ag_unregister(ag); + return -EIO; + } + } + + spa_log_debug(impl->log, "registered AudioGateway: %s", path); + + return 0; +} + +void telephony_ag_unregister(struct spa_bt_telephony_ag *ag) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); + + if (!agimpl->path) + return; + + spa_log_debug(impl->log, "removing AudioGateway: %s", agimpl->path); + + { + spa_autoptr(DBusMessage) msg = NULL; + DBusMessageIter iter, entry; + const char *interface = PW_TELEPHONY_AG_IFACE; + const char *interface2 = PW_TELEPHONY_AG_TRANSPORT_IFACE; + + msg = dbus_message_new_signal(impl->path, DBUS_INTERFACE_OBJECT_MANAGER, + "InterfacesRemoved"); + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &agimpl->path); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface2); + dbus_message_iter_close_container(&iter, &entry); + + if (!dbus_connection_send(impl->conn, msg, NULL)) { + spa_log_warn(impl->log, "sending InterfacesRemoved failed"); + } + } + { + spa_autoptr(DBusMessage) msg = NULL; + DBusMessageIter iter; + + msg = dbus_message_new_signal(impl->path, OFONO_MANAGER_IFACE, + "ModemRemoved"); + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &agimpl->path); + + if (!dbus_connection_send(impl->conn, msg, NULL)) { + spa_log_warn(impl->log, "sending ModemRemoved failed"); + } + } + + if (!dbus_connection_unregister_object_path(impl->conn, agimpl->path)) { + spa_log_warn(impl->log, "failed to unregister %s", agimpl->path); + } + + free(agimpl->path); + agimpl->path = NULL; +} + +/* send message to notify about property changes */ +void telephony_ag_transport_notify_updated_props(struct spa_bt_telephony_ag *ag) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); + + spa_autoptr(DBusMessage) msg = NULL; + const char *interface = PW_TELEPHONY_AG_TRANSPORT_IFACE; + DBusMessageIter i, a; + + msg = dbus_message_new_signal(agimpl->path, + DBUS_INTERFACE_PROPERTIES, + "PropertiesChanged"); + + dbus_message_iter_init_append(msg, &i); + dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &interface); + + if (!dbus_iter_append_ag_transport_properties(&i, ag, false)) + return; + + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &a); + dbus_message_iter_close_container(&i, &a); + + if (!dbus_connection_send(impl->conn, msg, NULL)){ + spa_log_warn(impl->log, "sending PropertiesChanged failed"); + } + + telephony_ag_transport_commit_properties(ag); +} + +void telephony_ag_set_callbacks(struct spa_bt_telephony_ag *ag, + const struct spa_bt_telephony_ag_callbacks *cbs, + void *data) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + agimpl->callbacks.funcs = cbs; + agimpl->callbacks.data = data; +} + +struct spa_bt_telephony_call * +telephony_call_new(struct spa_bt_telephony_ag *ag, size_t user_data_size) +{ + struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this); + struct callimpl *callimpl; + + spa_assert(user_data_size < SIZE_MAX - sizeof(*callimpl)); + + callimpl = calloc(1, sizeof(*callimpl) + user_data_size); + if (callimpl == NULL) + return NULL; + + callimpl->this.ag = ag; + callimpl->this.id = find_free_object_id(&ag->call_list, struct callimpl, this.link); + + spa_list_append(&ag->call_list, &callimpl->this.link); + + if (user_data_size > 0) + callimpl->user_data = SPA_PTROFF(callimpl, sizeof(struct callimpl), void); + + /* mark this object as the return value of the Dial method */ + if (agimpl->dial_in_progress) + agimpl->dial_return = callimpl; + + return &callimpl->this; +} + +void telephony_call_destroy(struct spa_bt_telephony_call *call) +{ + struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); + + telephony_call_unregister(call); + spa_list_remove(&call->link); + + free(callimpl->prev.line_identification); + free(callimpl->prev.incoming_line); + free(callimpl->prev.name); + + free(call->line_identification); + free(call->incoming_line); + free(call->name); + + free(callimpl); +} + +void *telephony_call_get_user_data(struct spa_bt_telephony_call *call) +{ + struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); + return callimpl->user_data; +} + +static void telephony_call_commit_properties(struct spa_bt_telephony_call *call) +{ + struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); + + if (!spa_streq (call->line_identification, callimpl->prev.line_identification)) { + free(callimpl->prev.line_identification); + callimpl->prev.line_identification = call->line_identification ? + strdup (call->line_identification) : NULL; + } + if (!spa_streq (call->incoming_line, callimpl->prev.incoming_line)) { + free(callimpl->prev.incoming_line); + callimpl->prev.incoming_line = call->incoming_line ? + strdup (call->incoming_line) : NULL; + } + if (!spa_streq (call->name, callimpl->prev.name)) { + free(callimpl->prev.name); + callimpl->prev.name = call->name ? strdup (call->name) : NULL; + } + callimpl->prev.multiparty = call->multiparty; + callimpl->prev.state = call->state; +} + +static const char * const call_state_to_string[] = { + "active", + "held", + "dialing", + "alerting", + "incoming", + "waiting", + "disconnected", +}; + +static inline const void *safe_string(char **str) +{ + static const char *empty_string = ""; + return *str ? (const char **) str : &empty_string; +} + +static void +dbus_iter_append_call_properties(DBusMessageIter *i, struct spa_bt_telephony_call *call, bool all) +{ + struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); + DBusMessageIter dict, entry, variant; + + dbus_message_iter_open_container(i, DBUS_TYPE_ARRAY, "{sv}", &dict); + + if (all || !spa_streq (call->line_identification, callimpl->prev.line_identification)) { + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, + &entry); + const char *line_identification = "LineIdentification"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &line_identification); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, + safe_string (&call->line_identification)); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + } + + if (all || !spa_streq (call->incoming_line, callimpl->prev.incoming_line)) { + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + const char *incoming_line = "IncomingLine"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &incoming_line); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, + safe_string (&call->incoming_line)); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + } + + if (all || !spa_streq (call->name, callimpl->prev.name)) { + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + const char *name = "Name"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, + safe_string (&call->name)); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + } + + if (all || call->multiparty != callimpl->prev.multiparty) { + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + const char *multiparty = "Multiparty"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &multiparty); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_BOOLEAN_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, &call->multiparty); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + } + + if (all || call->state != callimpl->prev.state) { + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + const char *state = "State"; + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &state); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, + &call_state_to_string[call->state]); + dbus_message_iter_close_container(&entry, &variant); + dbus_message_iter_close_container(&dict, &entry); + } + + dbus_message_iter_close_container(i, &dict); +} + +static DBusMessage *call_introspect(struct callimpl *callimpl, DBusMessage *m) +{ + const char *xml = PW_TELEPHONY_CALL_INTROSPECT_XML; + spa_autoptr(DBusMessage) r = NULL; + if ((r = dbus_message_new_method_return(m)) == NULL) + return NULL; + if (!dbus_message_append_args(r, DBUS_TYPE_STRING, &xml, DBUS_TYPE_INVALID)) + return NULL; + return spa_steal_ptr(r); +} + +static DBusMessage *call_properties_get(struct callimpl *callimpl, DBusMessage *m) +{ + const char *iface, *name; + DBusMessage *r; + DBusMessageIter i, v; + + if (!dbus_message_get_args(m, NULL, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) + return NULL; + + if (spa_streq(iface, PW_TELEPHONY_CALL_IFACE)) + return dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, + "No such interface"); + + if (spa_streq(name, "Multiparty")) { + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + dbus_message_iter_init_append(r, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, + DBUS_TYPE_BOOLEAN_AS_STRING, &v); + dbus_message_iter_append_basic(&v, DBUS_TYPE_BOOLEAN, + &callimpl->this.multiparty); + dbus_message_iter_close_container(&i, &v); + return r; + } else { + const char * const *property = NULL; + if (spa_streq(name, "LineIdentification")) { + property = (const char * const *) &callimpl->this.line_identification; + } else if (spa_streq(name, "IncomingLine")) { + property = (const char * const *) &callimpl->this.incoming_line; + } else if (spa_streq(name, "Name")) { + property = (const char * const *) &callimpl->this.name; + } else if (spa_streq(name, "State")) { + property = &call_state_to_string[callimpl->this.state]; + } + + if (property) { + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + dbus_message_iter_init_append(r, &i); + dbus_message_iter_open_container(&i, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, &v); + dbus_message_iter_append_basic(&v, DBUS_TYPE_STRING, + property); + dbus_message_iter_close_container(&i, &v); + return r; + } + } + + return dbus_message_new_error(m, DBUS_ERROR_INVALID_ARGS, + "No such property"); +} + +static DBusMessage *call_properties_get_all(struct callimpl *callimpl, DBusMessage *m, bool ofono_compat) +{ + DBusMessage *r; + DBusMessageIter i; + + if (!ofono_compat) { + const char *iface; + + if (!dbus_message_get_args(m, NULL, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_INVALID)) + return NULL; + + if (!spa_streq(iface, PW_TELEPHONY_CALL_IFACE)) + return dbus_message_new_error(m, DBUS_ERROR_UNKNOWN_INTERFACE, + "No such interface"); + } + + r = dbus_message_new_method_return(m); + if (r == NULL) + return NULL; + + dbus_message_iter_init_append(r, &i); + dbus_iter_append_call_properties(&i, &callimpl->this, true); + return r; +} + +static DBusMessage *call_properties_set(struct callimpl *callimpl, DBusMessage *m) +{ + return dbus_message_new_error(m, DBUS_ERROR_PROPERTY_READ_ONLY, + "Property not writable"); +} + +static DBusMessage *call_answer(struct callimpl *callimpl, DBusMessage *m) +{ + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; + uint8_t cme_error; + + if (call_emit_answer(callimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) + return dbus_message_new_method_return(m); + + return dbus_message_new_error(m, telephony_error_to_dbus (err), + telephony_error_to_description (err, cme_error)); +} + +static DBusMessage *call_hangup(struct callimpl *callimpl, DBusMessage *m) +{ + enum spa_bt_telephony_error err = BT_TELEPHONY_ERROR_FAILED; + uint8_t cme_error; + + if (call_emit_hangup(callimpl, &err, &cme_error) && err == BT_TELEPHONY_ERROR_NONE) + return dbus_message_new_method_return(m); + + return dbus_message_new_error(m, telephony_error_to_dbus (err), + telephony_error_to_description (err, cme_error)); +} + +static DBusHandlerResult call_handler(DBusConnection *c, DBusMessage *m, void *userdata) +{ + struct callimpl *callimpl = userdata; + struct agimpl *agimpl = SPA_CONTAINER_OF(callimpl->this.ag, struct agimpl, this); + struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); + + spa_autoptr(DBusMessage) r = NULL; + const char *path, *interface, *member; + + path = dbus_message_get_path(m); + interface = dbus_message_get_interface(m); + member = dbus_message_get_member(m); + + spa_log_debug(impl->log, "dbus: path=%s, interface=%s, member=%s", path, interface, member); + + if (dbus_message_is_method_call(m, DBUS_INTERFACE_INTROSPECTABLE, "Introspect")) { + r = call_introspect(callimpl, m); + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Get")) { + r = call_properties_get(callimpl, m); + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "GetAll")) { + r = call_properties_get_all(callimpl, m, false); + } else if (dbus_message_is_method_call(m, DBUS_INTERFACE_PROPERTIES, "Set")) { + r = call_properties_set(callimpl, m); + } else if (dbus_message_is_method_call(m, PW_TELEPHONY_CALL_IFACE, "Answer") || + dbus_message_is_method_call(m, OFONO_VOICE_CALL_IFACE, "Answer")) { + r = call_answer(callimpl, m); + } else if (dbus_message_is_method_call(m, PW_TELEPHONY_CALL_IFACE, "Hangup") || + dbus_message_is_method_call(m, OFONO_VOICE_CALL_IFACE, "Hangup")) { + r = call_hangup(callimpl, m); + } else if (dbus_message_is_method_call(m, OFONO_VOICE_CALL_IFACE, "GetProperties")) { + r = call_properties_get_all(callimpl, m, true); + } else { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (r == NULL) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + if (!dbus_connection_send(impl->conn, r, NULL)) + return DBUS_HANDLER_RESULT_NEED_MEMORY; + return DBUS_HANDLER_RESULT_HANDLED; +} + +int telephony_call_register(struct spa_bt_telephony_call *call) +{ + struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); + struct agimpl *agimpl = SPA_CONTAINER_OF(callimpl->this.ag, struct agimpl, this); + struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); + char *path; + + const DBusObjectPathVTable vtable = { + .message_function = call_handler, + }; + + path = spa_aprintf ("%s/call%d", agimpl->path, callimpl->this.id); + + /* register object */ + if (!dbus_connection_register_object_path(impl->conn, path, &vtable, callimpl)) { + spa_log_error(impl->log, "failed to register %s", path); + return -EIO; + } + callimpl->path = strdup(path); + + /* notify on ObjectManager of the AudioGateway object */ + { + spa_autoptr(DBusMessage) msg = NULL; + DBusMessageIter iter, entry, dict; + const char *interface = PW_TELEPHONY_CALL_IFACE; + + msg = dbus_message_new_signal(agimpl->path, + DBUS_INTERFACE_OBJECT_MANAGER, + "InterfacesAdded"); + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sa{sv}}", &dict); + dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface); + dbus_iter_append_call_properties(&entry, call, true); + dbus_message_iter_close_container(&dict, &entry); + dbus_message_iter_close_container(&iter, &dict); + + if (!dbus_connection_send(impl->conn, msg, NULL)) { + spa_log_error(impl->log, "failed to send InterfacesAdded for %s", path); + telephony_call_unregister(call); + return -EIO; + } + } + + /* emit CallAdded on the AudioGateway object */ + { + spa_autoptr(DBusMessage) msg = NULL; + DBusMessageIter iter; + + msg = dbus_message_new_signal(agimpl->path, + OFONO_VOICE_CALL_MANAGER_IFACE, + "CallAdded"); + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path); + dbus_iter_append_call_properties(&iter, call, true); + + if (!dbus_connection_send(impl->conn, msg, NULL)) { + spa_log_error(impl->log, "failed to send CallAdded for %s", path); + telephony_call_unregister(call); + return -EIO; + } + } + + telephony_call_commit_properties(call); + + spa_log_debug(impl->log, "registered Call: %s", path); + + return 0; +} + +void telephony_call_unregister(struct spa_bt_telephony_call *call) +{ + struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); + struct agimpl *agimpl = SPA_CONTAINER_OF(callimpl->this.ag, struct agimpl, this); + struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); + + if (!callimpl->path) + return; + + spa_log_debug(impl->log, "removing Call: %s", callimpl->path); + + { + spa_autoptr(DBusMessage) msg = NULL; + DBusMessageIter iter, entry; + const char *interface = PW_TELEPHONY_CALL_IFACE; + + msg = dbus_message_new_signal(agimpl->path, + DBUS_INTERFACE_OBJECT_MANAGER, + "InterfacesRemoved"); + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &callimpl->path); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface); + dbus_message_iter_close_container(&iter, &entry); + + if (!dbus_connection_send(impl->conn, msg, NULL)) { + spa_log_warn(impl->log, "sending InterfacesRemoved failed"); + } + } + { + spa_autoptr(DBusMessage) msg = NULL; + DBusMessageIter iter; + + msg = dbus_message_new_signal(agimpl->path, + OFONO_VOICE_CALL_MANAGER_IFACE, + "CallRemoved"); + dbus_message_iter_init_append(msg, &iter); + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &callimpl->path); + + if (!dbus_connection_send(impl->conn, msg, NULL)) { + spa_log_warn(impl->log, "sending CallRemoved failed"); + } + } + + if (!dbus_connection_unregister_object_path(impl->conn, callimpl->path)) { + spa_log_warn(impl->log, "failed to unregister %s", callimpl->path); + } + + free(callimpl->path); + callimpl->path = NULL; +} + +/* send message to notify about property changes */ +void telephony_call_notify_updated_props(struct spa_bt_telephony_call *call) +{ + struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); + struct agimpl *agimpl = SPA_CONTAINER_OF(callimpl->this.ag, struct agimpl, this); + struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this); + + { + spa_autoptr(DBusMessage) msg = NULL; + const char *interface = PW_TELEPHONY_CALL_IFACE; + DBusMessageIter i, a; + + msg = dbus_message_new_signal(callimpl->path, + DBUS_INTERFACE_PROPERTIES, + "PropertiesChanged"); + + dbus_message_iter_init_append(msg, &i); + dbus_message_iter_append_basic(&i, DBUS_TYPE_STRING, &interface); + + dbus_iter_append_call_properties(&i, call, false); + + dbus_message_iter_open_container(&i, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &a); + dbus_message_iter_close_container(&i, &a); + + if (!dbus_connection_send(impl->conn, msg, NULL)){ + spa_log_warn(impl->log, "sending PropertiesChanged failed"); + } + } + + if (!spa_streq (call->line_identification, callimpl->prev.line_identification)) { + spa_autoptr(DBusMessage) msg = NULL; + DBusMessageIter entry, variant; + + msg = dbus_message_new_signal(callimpl->path, + OFONO_VOICE_CALL_IFACE, + "PropertyChanged"); + + const char *line_identification = "LineIdentification"; + dbus_message_iter_init_append(msg, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &line_identification); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, + safe_string (&call->line_identification)); + dbus_message_iter_close_container(&entry, &variant); + + if (!dbus_connection_send(impl->conn, msg, NULL)){ + spa_log_warn(impl->log, "sending PropertyChanged failed"); + } + } + + if (!spa_streq (call->incoming_line, callimpl->prev.incoming_line)) { + spa_autoptr(DBusMessage) msg = NULL; + DBusMessageIter entry, variant; + + msg = dbus_message_new_signal(callimpl->path, + OFONO_VOICE_CALL_IFACE, + "PropertyChanged"); + + const char *incoming_line = "IncomingLine"; + dbus_message_iter_init_append(msg, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &incoming_line); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, + safe_string (&call->incoming_line)); + dbus_message_iter_close_container(&entry, &variant); + + if (!dbus_connection_send(impl->conn, msg, NULL)){ + spa_log_warn(impl->log, "sending PropertyChanged failed"); + } + } + + if (!spa_streq (call->name, callimpl->prev.name)) { + spa_autoptr(DBusMessage) msg = NULL; + DBusMessageIter entry, variant; + + msg = dbus_message_new_signal(callimpl->path, + OFONO_VOICE_CALL_IFACE, + "PropertyChanged"); + + const char *name = "Name"; + dbus_message_iter_init_append(msg, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &name); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, + safe_string (&call->name)); + dbus_message_iter_close_container(&entry, &variant); + + if (!dbus_connection_send(impl->conn, msg, NULL)){ + spa_log_warn(impl->log, "sending PropertyChanged failed"); + } + } + + if (call->multiparty != callimpl->prev.multiparty) { + spa_autoptr(DBusMessage) msg = NULL; + DBusMessageIter entry, variant; + + msg = dbus_message_new_signal(callimpl->path, + OFONO_VOICE_CALL_IFACE, + "PropertyChanged"); + + const char *multiparty = "Multiparty"; + dbus_message_iter_init_append(msg, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &multiparty); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_BOOLEAN_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_BOOLEAN, &call->multiparty); + dbus_message_iter_close_container(&entry, &variant); + + if (!dbus_connection_send(impl->conn, msg, NULL)){ + spa_log_warn(impl->log, "sending PropertyChanged failed"); + } + } + + if (call->state != callimpl->prev.state) { + spa_autoptr(DBusMessage) msg = NULL; + DBusMessageIter entry, variant; + + msg = dbus_message_new_signal(callimpl->path, + OFONO_VOICE_CALL_IFACE, + "PropertyChanged"); + + const char *state = "State"; + dbus_message_iter_init_append(msg, &entry); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &state); + dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, + DBUS_TYPE_STRING_AS_STRING, + &variant); + dbus_message_iter_append_basic(&variant, DBUS_TYPE_STRING, + &call_state_to_string[call->state]); + dbus_message_iter_close_container(&entry, &variant); + + if (!dbus_connection_send(impl->conn, msg, NULL)){ + spa_log_warn(impl->log, "sending PropertyChanged failed"); + } + } + + telephony_call_commit_properties(call); +} + +void telephony_call_set_callbacks(struct spa_bt_telephony_call *call, + const struct spa_bt_telephony_call_callbacks *cbs, + void *data) +{ + struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this); + callimpl->callbacks.funcs = cbs; + callimpl->callbacks.data = data; +} diff --git a/spa/plugins/bluez5/telephony.h b/spa/plugins/bluez5/telephony.h new file mode 100644 index 00000000..8ba85ec4 --- /dev/null +++ b/spa/plugins/bluez5/telephony.h @@ -0,0 +1,132 @@ +/* Spa Bluez5 Telephony D-Bus service */ +/* SPDX-FileCopyrightText: Copyright © 2024 Collabora Ltd. */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_BLUEZ5_TELEPHONY_H +#define SPA_BLUEZ5_TELEPHONY_H + +#include "defs.h" + +enum spa_bt_telephony_error { + BT_TELEPHONY_ERROR_NONE = 0, + BT_TELEPHONY_ERROR_FAILED, + BT_TELEPHONY_ERROR_NOT_SUPPORTED, + BT_TELEPHONY_ERROR_INVALID_FORMAT, + BT_TELEPHONY_ERROR_INVALID_STATE, + BT_TELEPHONY_ERROR_IN_PROGRESS, + BT_TELEPHONY_ERROR_CME, +}; + +enum spa_bt_telephony_call_state { + CALL_STATE_ACTIVE, + CALL_STATE_HELD, + CALL_STATE_DIALING, + CALL_STATE_ALERTING, + CALL_STATE_INCOMING, + CALL_STATE_WAITING, + CALL_STATE_DISCONNECTED, +}; + +struct spa_bt_telephony { + +}; + +struct spa_bt_telephony_ag_transport { + int8_t codec; + enum spa_bt_transport_state state; + dbus_bool_t rejectSCO; +}; + +struct spa_bt_telephony_ag { + struct spa_bt_telephony *telephony; + struct spa_list call_list; + + int id; + + /* D-Bus properties */ + char *address; + struct spa_bt_telephony_ag_transport transport; +}; + +struct spa_bt_telephony_call { + struct spa_bt_telephony_ag *ag; + struct spa_list link; /* link in ag->call_list */ + + int id; + + /* D-Bus properties */ + char *line_identification; + char *incoming_line; + char *name; + bool multiparty; + enum spa_bt_telephony_call_state state; +}; + +struct spa_bt_telephony_ag_callbacks { +#define SPA_VERSION_BT_TELEPHONY_AG_CALLBACKS 0 + uint32_t version; + + void (*dial)(void *data, const char *number, enum spa_bt_telephony_error *err, uint8_t *cme_error); + void (*swap_calls)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); + void (*release_and_answer)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); + void (*release_and_swap)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); + void (*hold_and_answer)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); + void (*hangup_all)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); + void (*create_multiparty)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); + void (*send_tones)(void *data, const char *tones, enum spa_bt_telephony_error *err, uint8_t *cme_error); + + void (*transport_activate)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); +}; + +struct spa_bt_telephony_call_callbacks { +#define SPA_VERSION_BT_TELEPHONY_CALL_CALLBACKS 0 + uint32_t version; + + void (*answer)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); + void (*hangup)(void *data, enum spa_bt_telephony_error *err, uint8_t *cme_error); +}; + +struct spa_bt_telephony *telephony_new(struct spa_log *log, struct spa_dbus *dbus, + const struct spa_dict *info); +void telephony_free(struct spa_bt_telephony *telephony); + + +/* create/destroy the ag object */ +struct spa_bt_telephony_ag * telephony_ag_new(struct spa_bt_telephony *telephony, + size_t user_data_size); +void telephony_ag_destroy(struct spa_bt_telephony_ag *ag); + +/* get the user data structure; struct size is set when creating the AG */ +void *telephony_ag_get_user_data(struct spa_bt_telephony_ag *ag); + +void telephony_ag_set_callbacks(struct spa_bt_telephony_ag *ag, + const struct spa_bt_telephony_ag_callbacks *cbs, + void *data); + +void telephony_ag_transport_notify_updated_props(struct spa_bt_telephony_ag *ag); + +/* register/unregister AudioGateway object on the bus */ +int telephony_ag_register(struct spa_bt_telephony_ag *ag); +void telephony_ag_unregister(struct spa_bt_telephony_ag *ag); + + +/* create/destroy the call object */ +struct spa_bt_telephony_call * telephony_call_new(struct spa_bt_telephony_ag *ag, + size_t user_data_size); +void telephony_call_destroy(struct spa_bt_telephony_call *call); + +/* get the user data structure; struct size is set when creating the Call */ +void *telephony_call_get_user_data(struct spa_bt_telephony_call *call); + +/* register/unregister Call object on the bus */ +int telephony_call_register(struct spa_bt_telephony_call *call); +void telephony_call_unregister(struct spa_bt_telephony_call *call); + +/* send message to notify about property changes */ +void telephony_call_notify_updated_props(struct spa_bt_telephony_call *call); + +void telephony_call_set_callbacks(struct spa_bt_telephony_call *call, + const struct spa_bt_telephony_call_callbacks *cbs, + void *data); + +#endif diff --git a/spa/plugins/control/mixer.c b/spa/plugins/control/mixer.c index 390a4dab..439bea76 100644 --- a/spa/plugins/control/mixer.c +++ b/spa/plugins/control/mixer.c @@ -19,6 +19,7 @@ #include <spa/param/audio/format-utils.h> #include <spa/param/param.h> #include <spa/control/control.h> +#include <spa/control/ump-utils.h> #include <spa/pod/filter.h> #undef SPA_LOG_TOPIC_DEFAULT @@ -50,6 +51,8 @@ struct port { unsigned int valid:1; unsigned int have_format:1; + uint32_t types; + struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers; @@ -449,7 +452,7 @@ static int port_set_format(void *object, clear_buffers(this, port); } } else { - uint32_t media_type, media_subtype; + uint32_t media_type, media_subtype, types = 0; if ((res = spa_format_parse(format, &media_type, &media_subtype)) < 0) return res; @@ -457,11 +460,17 @@ static int port_set_format(void *object, media_subtype != SPA_MEDIA_SUBTYPE_control) return -EINVAL; + if ((res = spa_pod_parse_object(format, + SPA_TYPE_OBJECT_Format, NULL, + SPA_FORMAT_CONTROL_types, SPA_POD_OPT_Int(&types))) < 0) + return res; + this->have_format = true; if (!port->have_format) { this->n_formats++; port->have_format = true; + port->types = types; spa_log_debug(this->log, "%p: set format on port %d:%d", this, direction, port_id); } @@ -620,6 +629,17 @@ static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t return queue_buffer(this, port, &port->buffers[buffer_id]); } +static inline int event_compare(uint8_t s1, uint8_t s2) +{ + /* 11 (controller) > 12 (program change) > + * 8 (note off) > 9 (note on) > 10 (aftertouch) > + * 13 (channel pressure) > 14 (pitch bend) */ + static int priotab[] = { 5,4,3,7,6,2,1,0 }; + if ((s1 & 0xf) != (s2 & 0xf)) + return 0; + return priotab[(s2>>4) & 7] - priotab[(s1>>4) & 7]; +} + static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control *b) { if (a->offset < b->offset) @@ -631,21 +651,20 @@ static inline int event_sort(struct spa_pod_control *a, struct spa_pod_control * switch(a->type) { case SPA_CONTROL_Midi: { - /* 11 (controller) > 12 (program change) > - * 8 (note off) > 9 (note on) > 10 (aftertouch) > - * 13 (channel pressure) > 14 (pitch bend) */ - static int priotab[] = { 5,4,3,7,6,2,1,0 }; - uint8_t *da, *db; - - if (SPA_POD_BODY_SIZE(&a->value) < 1 || - SPA_POD_BODY_SIZE(&b->value) < 1) + uint8_t *da = SPA_POD_BODY(&a->value), *db = SPA_POD_BODY(&b->value); + if (SPA_POD_BODY_SIZE(&a->value) < 1 || SPA_POD_BODY_SIZE(&b->value) < 1) return 0; - - da = SPA_POD_BODY(&a->value); - db = SPA_POD_BODY(&b->value); - if ((da[0] & 0xf) != (db[0] & 0xf)) + return event_compare(da[0], db[0]); + } + case SPA_CONTROL_UMP: + { + uint32_t *da = SPA_POD_BODY(&a->value), *db = SPA_POD_BODY(&b->value); + if (SPA_POD_BODY_SIZE(&a->value) < 4 || SPA_POD_BODY_SIZE(&b->value) < 4) + return 0; + if ((da[0] >> 28) != 2 || (da[0] >> 28) != 4 || + (db[0] >> 28) != 2 || (db[0] >> 28) != 4) return 0; - return priotab[(db[0]>>4) & 7] - priotab[(da[0]>>4) & 7]; + return event_compare(da[0] >> 16, db[0] >> 16); } default: return 0; @@ -763,8 +782,39 @@ static int impl_node_process(void *object) if (next == NULL) break; - spa_pod_builder_control(&builder, next->offset, next->type); - spa_pod_builder_primitive(&builder, &next->value); + if (outport->types && (outport->types & (1u << next->type)) == 0) { + uint8_t *data = SPA_POD_BODY(&next->value); + size_t size = SPA_POD_BODY_SIZE(&next->value); + + switch (next->type) { + case SPA_CONTROL_Midi: + { + uint32_t ump[4]; + uint64_t state = 0; + while (size > 0) { + int ump_size = spa_ump_from_midi(&data, &size, ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + break; + spa_pod_builder_control(&builder, next->offset, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&builder, ump, ump_size); + } + break; + } + case SPA_CONTROL_UMP: + { + uint8_t ev[8]; + int ev_size = spa_ump_to_midi((uint32_t*)data, size, ev, sizeof(ev)); + if (ev_size <= 0) + break; + spa_pod_builder_control(&builder, next->offset, SPA_CONTROL_Midi); + spa_pod_builder_bytes(&builder, ev, ev_size); + break; + } + } + } else { + spa_pod_builder_control(&builder, next->offset, next->type); + spa_pod_builder_primitive(&builder, &next->value); + } ctrl[next_index] = spa_pod_control_next(ctrl[next_index]); } diff --git a/spa/plugins/filter-graph/audio-dsp-avx.c b/spa/plugins/filter-graph/audio-dsp-avx.c new file mode 100644 index 00000000..1509284c --- /dev/null +++ b/spa/plugins/filter-graph/audio-dsp-avx.c @@ -0,0 +1,326 @@ +/* Spa */ +/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include <string.h> +#include <stdio.h> +#include <math.h> + +#include <spa/utils/defs.h> + +#include "config.h" +#ifndef HAVE_FFTW +#include "pffft.h" +#endif +#include "audio-dsp-impl.h" + +#include <immintrin.h> + +static void dsp_add_avx(void *obj, float *dst, const float * SPA_RESTRICT src[], + uint32_t n_src, uint32_t n_samples) +{ + uint32_t n, i, unrolled; + __m256 in[4]; + 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) { + 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]); + } +} + +static void dsp_add_1_gain_avx(void *obj, float *dst, const float * SPA_RESTRICT src[], + uint32_t n_src, float gain, uint32_t n_samples) +{ + uint32_t n, i, unrolled; + __m256 in[4], g; + const float **s = (const float **)src; + float *d = dst; + __m128 g1; + + 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; + + g = _mm256_set1_ps(gain); + g1 = _mm_set_ss(gain); + + for (n = 0; n < unrolled; n += 32) { + 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], _mm256_mul_ps(g, in[0])); + _mm256_store_ps(&d[n+ 8], _mm256_mul_ps(g, in[1])); + _mm256_store_ps(&d[n+16], _mm256_mul_ps(g, in[2])); + _mm256_store_ps(&d[n+24], _mm256_mul_ps(g, 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], _mm_mul_ss(g1, in[0])); + } +} + +static void dsp_add_n_gain_avx(void *obj, float *dst, + const float * SPA_RESTRICT src[], uint32_t n_src, + float gain[], uint32_t n_gain, uint32_t n_samples) +{ + uint32_t n, i, unrolled; + __m256 in[4], g; + 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) { + g = _mm256_set1_ps(gain[0]); + in[0] = _mm256_mul_ps(g, _mm256_load_ps(&s[0][n+ 0])); + in[1] = _mm256_mul_ps(g, _mm256_load_ps(&s[0][n+ 8])); + in[2] = _mm256_mul_ps(g, _mm256_load_ps(&s[0][n+16])); + in[3] = _mm256_mul_ps(g, _mm256_load_ps(&s[0][n+24])); + + for (i = 1; i < n_src; i++) { + g = _mm256_set1_ps(gain[i]); + in[0] = _mm256_add_ps(in[0], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+ 0]))); + in[1] = _mm256_add_ps(in[1], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+ 8]))); + in[2] = _mm256_add_ps(in[2], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+16]))); + in[3] = _mm256_add_ps(in[3], _mm256_mul_ps(g, _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], g; + g = _mm_set_ss(gain[0]); + in[0] = _mm_mul_ss(g, _mm_load_ss(&s[0][n])); + for (i = 1; i < n_src; i++) { + g = _mm_set_ss(gain[i]); + in[0] = _mm_add_ss(in[0], _mm_mul_ss(g, _mm_load_ss(&s[i][n]))); + } + _mm_store_ss(&d[n], in[0]); + } +} + + +void dsp_mix_gain_avx(void *obj, + float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src[], uint32_t n_src, + float gain[], uint32_t n_gain, uint32_t n_samples) +{ + if (n_src == 0) { + memset(dst, 0, n_samples * sizeof(float)); + } else if (n_src == 1 && gain[0] == 1.0f) { + if (dst != src[0]) + spa_memcpy(dst, src[0], n_samples * sizeof(float)); + } else { + if (n_gain == 0) + dsp_add_avx(obj, dst, src, n_src, n_samples); + else if (n_gain < n_src) + dsp_add_1_gain_avx(obj, dst, src, n_src, gain[0], n_samples); + else + dsp_add_n_gain_avx(obj, dst, src, n_src, gain, n_gain, n_samples); + } +} + +void dsp_sum_avx(void *obj, float *r, const float *a, const float *b, uint32_t n_samples) +{ + uint32_t n, unrolled; + __m256 in[4]; + + unrolled = n_samples & ~31; + + if (SPA_LIKELY(SPA_IS_ALIGNED(r, 32)) && + SPA_LIKELY(SPA_IS_ALIGNED(a, 32)) && + SPA_LIKELY(SPA_IS_ALIGNED(b, 32))) { + for (n = 0; n < unrolled; n += 32) { + in[0] = _mm256_load_ps(&a[n+ 0]); + in[1] = _mm256_load_ps(&a[n+ 8]); + in[2] = _mm256_load_ps(&a[n+16]); + in[3] = _mm256_load_ps(&a[n+24]); + + in[0] = _mm256_add_ps(in[0], _mm256_load_ps(&b[n+ 0])); + in[1] = _mm256_add_ps(in[1], _mm256_load_ps(&b[n+ 8])); + in[2] = _mm256_add_ps(in[2], _mm256_load_ps(&b[n+16])); + in[3] = _mm256_add_ps(in[3], _mm256_load_ps(&b[n+24])); + + _mm256_store_ps(&r[n+ 0], in[0]); + _mm256_store_ps(&r[n+ 8], in[1]); + _mm256_store_ps(&r[n+16], in[2]); + _mm256_store_ps(&r[n+24], in[3]); + } + } else { + for (n = 0; n < unrolled; n += 32) { + in[0] = _mm256_loadu_ps(&a[n+ 0]); + in[1] = _mm256_loadu_ps(&a[n+ 8]); + in[2] = _mm256_loadu_ps(&a[n+16]); + in[3] = _mm256_loadu_ps(&a[n+24]); + + in[0] = _mm256_add_ps(in[0], _mm256_loadu_ps(&b[n+ 0])); + in[1] = _mm256_add_ps(in[1], _mm256_loadu_ps(&b[n+ 8])); + in[2] = _mm256_add_ps(in[2], _mm256_loadu_ps(&b[n+16])); + in[3] = _mm256_add_ps(in[3], _mm256_loadu_ps(&b[n+24])); + + _mm256_storeu_ps(&r[n+ 0], in[0]); + _mm256_storeu_ps(&r[n+ 8], in[1]); + _mm256_storeu_ps(&r[n+16], in[2]); + _mm256_storeu_ps(&r[n+24], in[3]); + } + } + for (; n < n_samples; n++) { + __m128 in[1]; + in[0] = _mm_load_ss(&a[n]); + in[0] = _mm_add_ss(in[0], _mm_load_ss(&b[n])); + _mm_store_ss(&r[n], in[0]); + } +} + +inline static __m256 _mm256_mul_pz(__m256 ab, __m256 cd) +{ + __m256 aa, bb, dc, x0, x1; + aa = _mm256_moveldup_ps(ab); + bb = _mm256_movehdup_ps(ab); + x0 = _mm256_mul_ps(aa, cd); + dc = _mm256_shuffle_ps(cd, cd, _MM_SHUFFLE(2,3,0,1)); + x1 = _mm256_mul_ps(bb, dc); + return _mm256_addsub_ps(x0, x1); +} + +void dsp_fft_cmul_avx(void *obj, void *fft, + float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, + const float * SPA_RESTRICT b, uint32_t len, const float scale) +{ +#ifdef HAVE_FFTW + __m256 s = _mm256_set1_ps(scale); + __m256 aa[2], bb[2], dd[2]; + uint32_t i, unrolled; + + if (SPA_IS_ALIGNED(a, 32) && + SPA_IS_ALIGNED(b, 32) && + SPA_IS_ALIGNED(dst, 32)) + unrolled = len & ~7; + else + unrolled = 0; + + for (i = 0; i < unrolled; i+=8) { + aa[0] = _mm256_load_ps(&a[2*i]); /* ar0 ai0 ar1 ai1 */ + aa[1] = _mm256_load_ps(&a[2*i+8]); /* ar1 ai1 ar2 ai2 */ + bb[0] = _mm256_load_ps(&b[2*i]); /* br0 bi0 br1 bi1 */ + bb[1] = _mm256_load_ps(&b[2*i+8]); /* br2 bi2 br3 bi3 */ + dd[0] = _mm256_mul_pz(aa[0], bb[0]); + dd[1] = _mm256_mul_pz(aa[1], bb[1]); + dd[0] = _mm256_mul_ps(dd[0], s); + dd[1] = _mm256_mul_ps(dd[1], s); + _mm256_store_ps(&dst[2*i], dd[0]); + _mm256_store_ps(&dst[2*i+8], dd[1]); + } + for (; i < len; i++) { + dst[2*i ] = (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale; + dst[2*i+1] = (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale; + } +#else + pffft_zconvolve(fft, a, b, dst, scale); +#endif +} + +void dsp_fft_cmuladd_avx(void *obj, void *fft, + float * SPA_RESTRICT dst, const float * SPA_RESTRICT src, + const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, + uint32_t len, const float scale) +{ +#ifdef HAVE_FFTW + __m256 s = _mm256_set1_ps(scale); + __m256 aa[2], bb[2], dd[2], t[2]; + uint32_t i, unrolled; + + if (SPA_IS_ALIGNED(a, 32) && + SPA_IS_ALIGNED(b, 32) && + SPA_IS_ALIGNED(src, 32) && + SPA_IS_ALIGNED(dst, 32)) + unrolled = len & ~7; + else + unrolled = 0; + + for (i = 0; i < unrolled; i+=8) { + aa[0] = _mm256_load_ps(&a[2*i]); /* ar0 ai0 ar1 ai1 */ + aa[1] = _mm256_load_ps(&a[2*i+8]); /* ar1 ai1 ar2 ai2 */ + bb[0] = _mm256_load_ps(&b[2*i]); /* br0 bi0 br1 bi1 */ + bb[1] = _mm256_load_ps(&b[2*i+8]); /* br2 bi2 br3 bi3 */ + dd[0] = _mm256_mul_pz(aa[0], bb[0]); + dd[1] = _mm256_mul_pz(aa[1], bb[1]); + dd[0] = _mm256_mul_ps(dd[0], s); + dd[1] = _mm256_mul_ps(dd[1], s); + t[0] = _mm256_load_ps(&src[2*i]); + t[1] = _mm256_load_ps(&src[2*i+8]); + t[0] = _mm256_add_ps(t[0], dd[0]); + t[1] = _mm256_add_ps(t[1], dd[1]); + _mm256_store_ps(&dst[2*i], t[0]); + _mm256_store_ps(&dst[2*i+8], t[1]); + } + for (; i < len; i++) { + dst[2*i ] = src[2*i ] + (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale; + dst[2*i+1] = src[2*i+1] + (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale; + } +#else + pffft_zconvolve_accumulate(fft, a, b, src, dst, scale); +#endif +} diff --git a/spa/plugins/filter-graph/audio-dsp-c.c b/spa/plugins/filter-graph/audio-dsp-c.c new file mode 100644 index 00000000..f2a509a7 --- /dev/null +++ b/spa/plugins/filter-graph/audio-dsp-c.c @@ -0,0 +1,345 @@ +/* Spa */ +/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include <string.h> +#include <stdio.h> +#include <math.h> +#include <float.h> +#include <errno.h> + +#include <spa/utils/defs.h> + +#include "config.h" +#ifdef HAVE_FFTW +#include <fftw3.h> +#else +#include "pffft.h" +#endif +#include "audio-dsp-impl.h" + +void dsp_clear_c(void *obj, float * SPA_RESTRICT dst, uint32_t n_samples) +{ + memset(dst, 0, sizeof(float) * n_samples); +} + +void dsp_copy_c(void *obj, float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src, uint32_t n_samples) +{ + if (dst != src) + spa_memcpy(dst, src, sizeof(float) * n_samples); +} + +static inline void dsp_add_c(void *obj, float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src, uint32_t n_samples) +{ + uint32_t i; + const float *s = src; + float *d = dst; + for (i = 0; i < n_samples; i++) + d[i] += s[i]; +} + +static inline void dsp_gain_c(void *obj, float * dst, + const float * src, float gain, uint32_t n_samples) +{ + uint32_t i; + const float *s = src; + float *d = dst; + if (gain == 0.0f) + dsp_clear_c(obj, dst, n_samples); + else if (gain == 1.0f) + dsp_copy_c(obj, dst, src, n_samples); + else { + for (i = 0; i < n_samples; i++) + d[i] = s[i] * gain; + } +} + +static inline void dsp_gain_add_c(void *obj, float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src, float gain, uint32_t n_samples) +{ + uint32_t i; + const float *s = src; + float *d = dst; + + if (gain == 0.0f) + return; + else if (gain == 1.0f) + dsp_add_c(obj, dst, src, n_samples); + else { + for (i = 0; i < n_samples; i++) + d[i] += s[i] * gain; + } +} + + +void dsp_mix_gain_c(void *obj, + float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src[], uint32_t n_src, + float gain[], uint32_t n_gain, uint32_t n_samples) +{ + uint32_t i; + if (n_src == 0) { + dsp_clear_c(obj, dst, n_samples); + } else { + if (n_gain < n_src) { + dsp_copy_c(obj, dst, src[0], n_samples); + for (i = 1; i < n_src; i++) + dsp_add_c(obj, dst, src[i], n_samples); + if (n_gain > 0) + dsp_gain_c(obj, dst, dst, gain[0], n_samples); + } else { + dsp_gain_c(obj, dst, src[0], gain[0], n_samples); + for (i = 1; i < n_src; i++) + dsp_gain_add_c(obj, dst, src[i], gain[i], n_samples); + } + } +} + +static inline void dsp_mult1_c(void *obj, float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src, uint32_t n_samples) +{ + uint32_t i; + const float *s = src; + float *d = dst; + for (i = 0; i < n_samples; i++) + d[i] *= s[i]; +} + +void dsp_mult_c(void *obj, + float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src[], + uint32_t n_src, uint32_t n_samples) +{ + uint32_t i; + if (n_src == 0) { + dsp_clear_c(obj, dst, n_samples); + } else { + dsp_copy_c(obj, dst, src[0], n_samples); + for (i = 1; i < n_src; i++) + dsp_mult1_c(obj, dst, src[i], n_samples); + } +} + +static void biquad_run_c(void *obj, struct biquad *bq, + float *out, const float *in, uint32_t n_samples) +{ + float x, y, x1, x2; + float b0, b1, b2, a1, a2; + uint32_t i; + + if (bq->type == BQ_NONE) { + dsp_copy_c(obj, out, in, n_samples); + return; + } + + x1 = bq->x1; + x2 = bq->x2; + b0 = bq->b0; + b1 = bq->b1; + b2 = bq->b2; + a1 = bq->a1; + a2 = bq->a2; + for (i = 0; i < n_samples; i++) { + x = in[i]; + y = b0 * x + x1; + x1 = b1 * x - a1 * y + x2; + x2 = b2 * x - a2 * y; + out[i] = y; + } +#define F(x) (isnormal(x) ? (x) : 0.0f) + bq->x1 = F(x1); + bq->x2 = F(x2); +#undef F +} + +void dsp_biquad_run_c(void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq_stride, + float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], + uint32_t n_src, uint32_t n_samples) +{ + uint32_t i, j; + const float *s; + float *d; + for (i = 0; i < n_src; i++, bq+=bq_stride) { + s = in[i]; + d = out[i]; + if (s == NULL || d == NULL) + continue; + if (n_bq > 0) + biquad_run_c(obj, &bq[0], d, s, n_samples); + for (j = 1; j < n_bq; j++) + biquad_run_c(obj, &bq[j], d, d, n_samples); + } +} + +void dsp_sum_c(void *obj, float * dst, + const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t n_samples) +{ + uint32_t i; + for (i = 0; i < n_samples; i++) + dst[i] = a[i] + b[i]; +} + +void dsp_linear_c(void *obj, float * dst, + const float * SPA_RESTRICT src, const float mult, + const float add, uint32_t n_samples) +{ + uint32_t i; + if (add == 0.0f) { + dsp_gain_c(obj, dst, src, mult, n_samples); + } else { + if (mult == 0.0f) { + for (i = 0; i < n_samples; i++) + dst[i] = add; + } else if (mult == 1.0f) { + for (i = 0; i < n_samples; i++) + dst[i] = src[i] + add; + } else { + for (i = 0; i < n_samples; i++) + dst[i] = mult * src[i] + add; + } + } +} + + +void dsp_delay_c(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, + uint32_t delay, float *dst, const float *src, uint32_t n_samples) +{ + if (delay == 0) { + dsp_copy_c(obj, dst, src, n_samples); + } else { + uint32_t w, o, i; + + w = *pos; + o = n_buffer - delay; + + for (i = 0; i < n_samples; i++) { + buffer[w] = buffer[w + n_buffer] = src[i]; + dst[i] = buffer[w + o]; + w = w + 1 > n_buffer ? 0 : w + 1; + } + *pos = w; + } +} + +#ifdef HAVE_FFTW +struct fft_info { + fftwf_plan plan_r2c; + fftwf_plan plan_c2r; +}; +#endif + +void *dsp_fft_new_c(void *obj, uint32_t size, bool real) +{ +#ifdef HAVE_FFTW + struct fft_info *info = calloc(1, sizeof(struct fft_info)); + float *rdata; + fftwf_complex *cdata; + + if (info == NULL) + return NULL; + + rdata = fftwf_alloc_real(size * 2); + cdata = fftwf_alloc_complex(size + 1); + + info->plan_r2c = fftwf_plan_dft_r2c_1d(size, rdata, cdata, FFTW_ESTIMATE); + info->plan_c2r = fftwf_plan_dft_c2r_1d(size, cdata, rdata, FFTW_ESTIMATE); + + fftwf_free(rdata); + fftwf_free(cdata); + + return info; +#else + return pffft_new_setup(size, real ? PFFFT_REAL : PFFFT_COMPLEX); +#endif +} + +void dsp_fft_free_c(void *obj, void *fft) +{ +#ifdef HAVE_FFTW + struct fft_info *info = fft; + fftwf_destroy_plan(info->plan_r2c); + fftwf_destroy_plan(info->plan_c2r); + free(info); +#else + pffft_destroy_setup(fft); +#endif +} + +void *dsp_fft_memalloc_c(void *obj, uint32_t size, bool real) +{ +#ifdef HAVE_FFTW + if (real) + return fftwf_alloc_real(size); + else + return fftwf_alloc_complex(size); +#else + if (real) + return pffft_aligned_malloc(size * sizeof(float)); + else + return pffft_aligned_malloc(size * 2 * sizeof(float)); +#endif +} + +void dsp_fft_memfree_c(void *obj, void *data) +{ +#ifdef HAVE_FFTW + fftwf_free(data); +#else + pffft_aligned_free(data); +#endif +} + +void dsp_fft_memclear_c(void *obj, void *data, uint32_t size, bool real) +{ +#ifdef HAVE_FFTW + spa_fga_dsp_clear(obj, data, real ? size : size * 2); +#else + spa_fga_dsp_clear(obj, data, real ? size : size * 2); +#endif +} + +void dsp_fft_run_c(void *obj, void *fft, int direction, + const float * SPA_RESTRICT src, float * SPA_RESTRICT dst) +{ +#ifdef HAVE_FFTW + struct fft_info *info = fft; + if (direction > 0) + fftwf_execute_dft_r2c (info->plan_r2c, (float*)src, (fftwf_complex*)dst); + else + fftwf_execute_dft_c2r (info->plan_c2r, (fftwf_complex*)src, dst); +#else + pffft_transform(fft, src, dst, NULL, direction < 0 ? PFFFT_BACKWARD : PFFFT_FORWARD); +#endif +} + +void dsp_fft_cmul_c(void *obj, void *fft, + float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, + const float * SPA_RESTRICT b, uint32_t len, const float scale) +{ +#ifdef HAVE_FFTW + for (uint32_t i = 0; i < len; i++) { + dst[2*i ] = (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale; + dst[2*i+1] = (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale; + } +#else + pffft_zconvolve(fft, a, b, dst, scale); +#endif +} + +void dsp_fft_cmuladd_c(void *obj, void *fft, + float * SPA_RESTRICT dst, const float * SPA_RESTRICT src, + const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, + uint32_t len, const float scale) +{ +#ifdef HAVE_FFTW + for (uint32_t i = 0; i < len; i++) { + dst[2*i ] = src[2*i ] + (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale; + dst[2*i+1] = src[2*i+1] + (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale; + } +#else + pffft_zconvolve_accumulate(fft, a, b, src, dst, scale); +#endif +} + diff --git a/spa/plugins/filter-graph/audio-dsp-impl.h b/spa/plugins/filter-graph/audio-dsp-impl.h new file mode 100644 index 00000000..388a7453 --- /dev/null +++ b/spa/plugins/filter-graph/audio-dsp-impl.h @@ -0,0 +1,94 @@ +/* Spa */ +/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#ifndef DSP_IMPL_H +#define DSP_IMPL_H + +#include "audio-dsp.h" + +struct spa_fga_dsp * spa_fga_dsp_new(uint32_t cpu_flags); +void spa_fga_dsp_free(struct spa_fga_dsp *dsp); + +#define MAKE_CLEAR_FUNC(arch) \ +void dsp_clear_##arch(void *obj, float * SPA_RESTRICT dst, uint32_t n_samples) +#define MAKE_COPY_FUNC(arch) \ +void dsp_copy_##arch(void *obj, float * SPA_RESTRICT dst, \ + const float * SPA_RESTRICT src, uint32_t n_samples) +#define MAKE_MIX_GAIN_FUNC(arch) \ +void dsp_mix_gain_##arch(void *obj, float * SPA_RESTRICT dst, \ + const float * SPA_RESTRICT src[], uint32_t n_src, float gain[], uint32_t n_gain, uint32_t n_samples) +#define MAKE_SUM_FUNC(arch) \ +void dsp_sum_##arch (void *obj, float * SPA_RESTRICT dst, \ + const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t n_samples) +#define MAKE_LINEAR_FUNC(arch) \ +void dsp_linear_##arch (void *obj, float * SPA_RESTRICT dst, \ + const float * SPA_RESTRICT src, const float mult, const float add, uint32_t n_samples) +#define MAKE_MULT_FUNC(arch) \ +void dsp_mult_##arch(void *obj, float * SPA_RESTRICT dst, \ + const float * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) +#define MAKE_BIQUAD_RUN_FUNC(arch) \ +void dsp_biquad_run_##arch (void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq_stride, \ + float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], uint32_t n_src, uint32_t n_samples) +#define MAKE_DELAY_FUNC(arch) \ +void dsp_delay_##arch (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, \ + uint32_t delay, float *dst, const float *src, uint32_t n_samples) + +#define MAKE_FFT_NEW_FUNC(arch) \ +void *dsp_fft_new_##arch(void *obj, uint32_t size, bool real) +#define MAKE_FFT_FREE_FUNC(arch) \ +void dsp_fft_free_##arch(void *obj, void *fft) +#define MAKE_FFT_MEMALLOC_FUNC(arch) \ +void *dsp_fft_memalloc_##arch(void *obj, uint32_t size, bool real) +#define MAKE_FFT_MEMFREE_FUNC(arch) \ +void dsp_fft_memfree_##arch(void *obj, void *mem) +#define MAKE_FFT_MEMCLEAR_FUNC(arch) \ +void dsp_fft_memclear_##arch(void *obj, void *mem, uint32_t size, bool real) +#define MAKE_FFT_RUN_FUNC(arch) \ +void dsp_fft_run_##arch(void *obj, void *fft, int direction, \ + const float * SPA_RESTRICT src, float * SPA_RESTRICT dst) +#define MAKE_FFT_CMUL_FUNC(arch) \ +void dsp_fft_cmul_##arch(void *obj, void *fft, \ + float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, \ + const float * SPA_RESTRICT b, uint32_t len, const float scale) +#define MAKE_FFT_CMULADD_FUNC(arch) \ +void dsp_fft_cmuladd_##arch(void *obj, void *fft, \ + float * dst, const float * src, \ + const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, \ + uint32_t len, const float scale) + + +MAKE_CLEAR_FUNC(c); +MAKE_COPY_FUNC(c); +MAKE_MIX_GAIN_FUNC(c); +MAKE_SUM_FUNC(c); +MAKE_LINEAR_FUNC(c); +MAKE_MULT_FUNC(c); +MAKE_BIQUAD_RUN_FUNC(c); +MAKE_DELAY_FUNC(c); + +MAKE_FFT_NEW_FUNC(c); +MAKE_FFT_FREE_FUNC(c); +MAKE_FFT_MEMALLOC_FUNC(c); +MAKE_FFT_MEMFREE_FUNC(c); +MAKE_FFT_MEMCLEAR_FUNC(c); +MAKE_FFT_RUN_FUNC(c); +MAKE_FFT_CMUL_FUNC(c); +MAKE_FFT_CMULADD_FUNC(c); + +#if defined (HAVE_SSE) +MAKE_MIX_GAIN_FUNC(sse); +MAKE_SUM_FUNC(sse); +MAKE_BIQUAD_RUN_FUNC(sse); +MAKE_DELAY_FUNC(sse); +MAKE_FFT_CMUL_FUNC(sse); +MAKE_FFT_CMULADD_FUNC(sse); +#endif +#if defined (HAVE_AVX) +MAKE_MIX_GAIN_FUNC(avx); +MAKE_SUM_FUNC(avx); +MAKE_FFT_CMUL_FUNC(avx); +MAKE_FFT_CMULADD_FUNC(avx); +#endif + +#endif /* DSP_OPS_IMPL_H */ diff --git a/spa/plugins/filter-graph/audio-dsp-sse.c b/spa/plugins/filter-graph/audio-dsp-sse.c new file mode 100644 index 00000000..8c2ffa8e --- /dev/null +++ b/spa/plugins/filter-graph/audio-dsp-sse.c @@ -0,0 +1,744 @@ +/* Spa */ +/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include <string.h> +#include <stdio.h> +#include <math.h> +#include <float.h> +#include <complex.h> + +#include <spa/utils/defs.h> + +#include "config.h" +#ifndef HAVE_FFTW +#include "pffft.h" +#endif + +#include "audio-dsp-impl.h" + +#include <xmmintrin.h> + +static void dsp_add_sse(void *obj, float *dst, const float * SPA_RESTRICT src[], + uint32_t n_src, uint32_t n_samples) +{ + 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; + + 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++) { + 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]); + } +} + +static void dsp_add_1_gain_sse(void *obj, + float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src[], uint32_t n_src, + float gain, uint32_t n_samples) +{ + uint32_t n, i, unrolled; + __m128 in[4], g; + 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; + + g = _mm_set1_ps(gain); + + 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++) { + 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], _mm_mul_ps(in[0], g)); + _mm_store_ps(&d[n+ 4], _mm_mul_ps(in[1], g)); + _mm_store_ps(&d[n+ 8], _mm_mul_ps(in[2], g)); + _mm_store_ps(&d[n+12], _mm_mul_ps(in[3], g)); + } + 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], _mm_mul_ss(in[0], g)); + } +} + +static void dsp_add_n_gain_sse(void *obj, + float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src[], uint32_t n_src, + float gain[], uint32_t n_gain, uint32_t n_samples) +{ + uint32_t n, i, unrolled; + __m128 in[4], g; + 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; + + for (n = 0; n < unrolled; n += 16) { + g = _mm_set1_ps(gain[0]); + in[0] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 0])); + in[1] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 4])); + in[2] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 8])); + in[3] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+12])); + + for (i = 1; i < n_src; i++) { + g = _mm_set1_ps(gain[i]); + in[0] = _mm_add_ps(in[0], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 0]))); + in[1] = _mm_add_ps(in[1], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 4]))); + in[2] = _mm_add_ps(in[2], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 8]))); + in[3] = _mm_add_ps(in[3], _mm_mul_ps(g, _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++) { + g = _mm_set_ss(gain[0]); + in[0] = _mm_mul_ss(g, _mm_load_ss(&s[0][n])); + for (i = 1; i < n_src; i++) { + g = _mm_set_ss(gain[i]); + in[0] = _mm_add_ss(in[0], _mm_mul_ss(g, _mm_load_ss(&s[i][n]))); + } + _mm_store_ss(&d[n], in[0]); + } +} + +void dsp_mix_gain_sse(void *obj, + float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src[], uint32_t n_src, + float gain[], uint32_t n_gain, uint32_t n_samples) +{ + if (n_src == 0) { + memset(dst, 0, n_samples * sizeof(float)); + } else if (n_src == 1 && gain[0] == 1.0f) { + if (dst != src[0]) + spa_memcpy(dst, src[0], n_samples * sizeof(float)); + } else { + if (n_gain == 0) + dsp_add_sse(obj, dst, src, n_src, n_samples); + else if (n_gain < n_src) + dsp_add_1_gain_sse(obj, dst, src, n_src, gain[0], n_samples); + else + dsp_add_n_gain_sse(obj, dst, src, n_src, gain, n_gain, n_samples); + } +} + +void dsp_sum_sse(void *obj, float *r, const float *a, const float *b, uint32_t n_samples) +{ + uint32_t n, unrolled; + __m128 in[4]; + + unrolled = n_samples & ~15; + + if (SPA_LIKELY(SPA_IS_ALIGNED(r, 16)) && + SPA_LIKELY(SPA_IS_ALIGNED(a, 16)) && + SPA_LIKELY(SPA_IS_ALIGNED(b, 16))) { + for (n = 0; n < unrolled; n += 16) { + in[0] = _mm_load_ps(&a[n+ 0]); + in[1] = _mm_load_ps(&a[n+ 4]); + in[2] = _mm_load_ps(&a[n+ 8]); + in[3] = _mm_load_ps(&a[n+12]); + + in[0] = _mm_add_ps(in[0], _mm_load_ps(&b[n+ 0])); + in[1] = _mm_add_ps(in[1], _mm_load_ps(&b[n+ 4])); + in[2] = _mm_add_ps(in[2], _mm_load_ps(&b[n+ 8])); + in[3] = _mm_add_ps(in[3], _mm_load_ps(&b[n+12])); + + _mm_store_ps(&r[n+ 0], in[0]); + _mm_store_ps(&r[n+ 4], in[1]); + _mm_store_ps(&r[n+ 8], in[2]); + _mm_store_ps(&r[n+12], in[3]); + } + } else { + for (n = 0; n < unrolled; n += 16) { + in[0] = _mm_loadu_ps(&a[n+ 0]); + in[1] = _mm_loadu_ps(&a[n+ 4]); + in[2] = _mm_loadu_ps(&a[n+ 8]); + in[3] = _mm_loadu_ps(&a[n+12]); + + in[0] = _mm_add_ps(in[0], _mm_loadu_ps(&b[n+ 0])); + in[1] = _mm_add_ps(in[1], _mm_loadu_ps(&b[n+ 4])); + in[2] = _mm_add_ps(in[2], _mm_loadu_ps(&b[n+ 8])); + in[3] = _mm_add_ps(in[3], _mm_loadu_ps(&b[n+12])); + + _mm_storeu_ps(&r[n+ 0], in[0]); + _mm_storeu_ps(&r[n+ 4], in[1]); + _mm_storeu_ps(&r[n+ 8], in[2]); + _mm_storeu_ps(&r[n+12], in[3]); + } + } + for (; n < n_samples; n++) { + in[0] = _mm_load_ss(&a[n]); + in[0] = _mm_add_ss(in[0], _mm_load_ss(&b[n])); + _mm_store_ss(&r[n], in[0]); + } +} + +static void dsp_biquad_run1_sse(void *obj, struct biquad *bq, + float *out, const float *in, uint32_t n_samples) +{ + __m128 x, y, z; + __m128 b012; + __m128 a12; + __m128 x12; + uint32_t i; + + b012 = _mm_setr_ps(bq->b0, bq->b1, bq->b2, 0.0f); /* b0 b1 b2 0 */ + a12 = _mm_setr_ps(0.0f, bq->a1, bq->a2, 0.0f); /* 0 a1 a2 0 */ + x12 = _mm_setr_ps(bq->x1, bq->x2, 0.0f, 0.0f); /* x1 x2 0 0 */ + + for (i = 0; i < n_samples; i++) { + x = _mm_load1_ps(&in[i]); /* x x x x */ + z = _mm_mul_ps(x, b012); /* b0*x b1*x b2*x 0 */ + z = _mm_add_ps(z, x12); /* b0*x+x1 b1*x+x2 b2*x 0 */ + _mm_store_ss(&out[i], z); /* out[i] = b0*x+x1 */ + y = _mm_shuffle_ps(z, z, _MM_SHUFFLE(0,0,0,0)); /* b0*x+x1 b0*x+x1 b0*x+x1 b0*x+x1 = y*/ + y = _mm_mul_ps(y, a12); /* 0 a1*y a2*y 0 */ + y = _mm_sub_ps(z, y); /* y x1 x2 0 */ + x12 = _mm_shuffle_ps(y, y, _MM_SHUFFLE(3,3,2,1)); /* x1 x2 0 0*/ + } +#define F(x) (isnormal(x) ? (x) : 0.0f) + bq->x1 = F(x12[0]); + bq->x2 = F(x12[1]); +#undef F +} + +static void dsp_biquad2_run_sse(void *obj, struct biquad *bq, + float *out, const float *in, uint32_t n_samples) +{ + __m128 x, y, z; + __m128 b0, b1; + __m128 a0, a1; + __m128 x0, x1; + uint32_t i; + + b0 = _mm_setr_ps(bq[0].b0, bq[0].b1, bq[0].b2, 0.0f); /* b0 b1 b2 0 */ + a0 = _mm_setr_ps(0.0f, bq[0].a1, bq[0].a2, 0.0f); /* 0 a1 a2 0 */ + x0 = _mm_setr_ps(bq[0].x1, bq[0].x2, 0.0f, 0.0f); /* x1 x2 0 0 */ + + b1 = _mm_setr_ps(bq[1].b0, bq[1].b1, bq[1].b2, 0.0f); /* b0 b1 b2 0 */ + a1 = _mm_setr_ps(0.0f, bq[1].a1, bq[1].a2, 0.0f); /* 0 a1 a2 0 */ + x1 = _mm_setr_ps(bq[1].x1, bq[1].x2, 0.0f, 0.0f); /* x1 x2 0 0 */ + + for (i = 0; i < n_samples; i++) { + x = _mm_load1_ps(&in[i]); /* x x x x */ + + z = _mm_mul_ps(x, b0); /* b0*x b1*x b2*x 0 */ + z = _mm_add_ps(z, x0); /* b0*x+x1 b1*x+x2 b2*x 0 */ + y = _mm_shuffle_ps(z, z, _MM_SHUFFLE(0,0,0,0)); /* b0*x+x1 b0*x+x1 b0*x+x1 b0*x+x1 = y*/ + x = _mm_mul_ps(y, a0); /* 0 a1*y a2*y 0 */ + x = _mm_sub_ps(z, x); /* y x1 x2 0 */ + x0 = _mm_shuffle_ps(x, x, _MM_SHUFFLE(3,3,2,1)); /* x1 x2 0 0*/ + + z = _mm_mul_ps(y, b1); /* b0*x b1*x b2*x 0 */ + z = _mm_add_ps(z, x1); /* b0*x+x1 b1*x+x2 b2*x 0 */ + x = _mm_shuffle_ps(z, z, _MM_SHUFFLE(0,0,0,0)); /* b0*x+x1 b0*x+x1 b0*x+x1 b0*x+x1 = y*/ + y = _mm_mul_ps(x, a1); /* 0 a1*y a2*y 0 */ + y = _mm_sub_ps(z, y); /* y x1 x2 0 */ + x1 = _mm_shuffle_ps(y, y, _MM_SHUFFLE(3,3,2,1)); /* x1 x2 0 0*/ + + _mm_store_ss(&out[i], x); /* out[i] = b0*x+x1 */ + } +#define F(x) (isnormal(x) ? (x) : 0.0f) + bq[0].x1 = F(x0[0]); + bq[0].x2 = F(x0[1]); + bq[1].x1 = F(x1[0]); + bq[1].x2 = F(x1[1]); +#undef F +} + +static void dsp_biquad_run2_sse(void *obj, struct biquad *bq, uint32_t bq_stride, + float **out, const float **in, uint32_t n_samples) +{ + __m128 x, y, z; + __m128 b0, b1, b2; + __m128 a1, a2; + __m128 x1, x2; + uint32_t i; + + b0 = _mm_setr_ps(bq[0].b0, bq[bq_stride].b0, 0.0f, 0.0f); /* b00 b10 0 0 */ + b1 = _mm_setr_ps(bq[0].b1, bq[bq_stride].b1, 0.0f, 0.0f); /* b01 b11 0 0 */ + b2 = _mm_setr_ps(bq[0].b2, bq[bq_stride].b2, 0.0f, 0.0f); /* b02 b12 0 0 */ + a1 = _mm_setr_ps(bq[0].a1, bq[bq_stride].a1, 0.0f, 0.0f); /* b00 b10 0 0 */ + a2 = _mm_setr_ps(bq[0].a2, bq[bq_stride].a2, 0.0f, 0.0f); /* b01 b11 0 0 */ + x1 = _mm_setr_ps(bq[0].x1, bq[bq_stride].x1, 0.0f, 0.0f); /* b00 b10 0 0 */ + x2 = _mm_setr_ps(bq[0].x2, bq[bq_stride].x2, 0.0f, 0.0f); /* b01 b11 0 0 */ + + for (i = 0; i < n_samples; i++) { + x = _mm_setr_ps(in[0][i], in[1][i], 0.0f, 0.0f); + + y = _mm_mul_ps(x, b0); /* y = x * b0 */ + y = _mm_add_ps(y, x1); /* y = x * b0 + x1*/ + z = _mm_mul_ps(y, a1); /* z = a1 * y */ + x1 = _mm_mul_ps(x, b1); /* x1 = x * b1 */ + x1 = _mm_add_ps(x1, x2); /* x1 = x * b1 + x2*/ + x1 = _mm_sub_ps(x1, z); /* x1 = x * b1 + x2 - a1 * y*/ + z = _mm_mul_ps(y, a2); /* z = a2 * y */ + x2 = _mm_mul_ps(x, b2); /* x2 = x * b2 */ + x2 = _mm_sub_ps(x2, z); /* x2 = x * b2 - a2 * y*/ + + out[0][i] = y[0]; + out[1][i] = y[1]; + } +#define F(x) (isnormal(x) ? (x) : 0.0f) + bq[0*bq_stride].x1 = F(x1[0]); + bq[0*bq_stride].x2 = F(x2[0]); + bq[1*bq_stride].x1 = F(x1[1]); + bq[1*bq_stride].x2 = F(x2[1]); +#undef F +} + + +static void dsp_biquad2_run2_sse(void *obj, struct biquad *bq, uint32_t bq_stride, + float **out, const float **in, uint32_t n_samples) +{ + __m128 x, y, z; + __m128 b00, b01, b02, b10, b11, b12; + __m128 a01, a02, a11, a12; + __m128 x01, x02, x11, x12; + uint32_t i; + + b00 = _mm_setr_ps(bq[0].b0, bq[bq_stride].b0, 0.0f, 0.0f); /* b00 b10 0 0 */ + b01 = _mm_setr_ps(bq[0].b1, bq[bq_stride].b1, 0.0f, 0.0f); /* b01 b11 0 0 */ + b02 = _mm_setr_ps(bq[0].b2, bq[bq_stride].b2, 0.0f, 0.0f); /* b02 b12 0 0 */ + a01 = _mm_setr_ps(bq[0].a1, bq[bq_stride].a1, 0.0f, 0.0f); /* b00 b10 0 0 */ + a02 = _mm_setr_ps(bq[0].a2, bq[bq_stride].a2, 0.0f, 0.0f); /* b01 b11 0 0 */ + x01 = _mm_setr_ps(bq[0].x1, bq[bq_stride].x1, 0.0f, 0.0f); /* b00 b10 0 0 */ + x02 = _mm_setr_ps(bq[0].x2, bq[bq_stride].x2, 0.0f, 0.0f); /* b01 b11 0 0 */ + + b10 = _mm_setr_ps(bq[1].b0, bq[bq_stride+1].b0, 0.0f, 0.0f); /* b00 b10 0 0 */ + b11 = _mm_setr_ps(bq[1].b1, bq[bq_stride+1].b1, 0.0f, 0.0f); /* b01 b11 0 0 */ + b12 = _mm_setr_ps(bq[1].b2, bq[bq_stride+1].b2, 0.0f, 0.0f); /* b02 b12 0 0 */ + a11 = _mm_setr_ps(bq[1].a1, bq[bq_stride+1].a1, 0.0f, 0.0f); /* b00 b10 0 0 */ + a12 = _mm_setr_ps(bq[1].a2, bq[bq_stride+1].a2, 0.0f, 0.0f); /* b01 b11 0 0 */ + x11 = _mm_setr_ps(bq[1].x1, bq[bq_stride+1].x1, 0.0f, 0.0f); /* b00 b10 0 0 */ + x12 = _mm_setr_ps(bq[1].x2, bq[bq_stride+1].x2, 0.0f, 0.0f); /* b01 b11 0 0 */ + + for (i = 0; i < n_samples; i++) { + x = _mm_setr_ps(in[0][i], in[1][i], 0.0f, 0.0f); + + y = _mm_mul_ps(x, b00); /* y = x * b0 */ + y = _mm_add_ps(y, x01); /* y = x * b0 + x1*/ + z = _mm_mul_ps(y, a01); /* z = a1 * y */ + x01 = _mm_mul_ps(x, b01); /* x1 = x * b1 */ + x01 = _mm_add_ps(x01, x02); /* x1 = x * b1 + x2*/ + x01 = _mm_sub_ps(x01, z); /* x1 = x * b1 + x2 - a1 * y*/ + z = _mm_mul_ps(y, a02); /* z = a2 * y */ + x02 = _mm_mul_ps(x, b02); /* x2 = x * b2 */ + x02 = _mm_sub_ps(x02, z); /* x2 = x * b2 - a2 * y*/ + + x = y; + + y = _mm_mul_ps(x, b10); /* y = x * b0 */ + y = _mm_add_ps(y, x11); /* y = x * b0 + x1*/ + z = _mm_mul_ps(y, a11); /* z = a1 * y */ + x11 = _mm_mul_ps(x, b11); /* x1 = x * b1 */ + x11 = _mm_add_ps(x11, x12); /* x1 = x * b1 + x2*/ + x11 = _mm_sub_ps(x11, z); /* x1 = x * b1 + x2 - a1 * y*/ + z = _mm_mul_ps(y, a12); /* z = a2 * y*/ + x12 = _mm_mul_ps(x, b12); /* x2 = x * b2 */ + x12 = _mm_sub_ps(x12, z); /* x2 = x * b2 - a2 * y*/ + + out[0][i] = y[0]; + out[1][i] = y[1]; + } +#define F(x) (isnormal(x) ? (x) : 0.0f) + bq[0*bq_stride+0].x1 = F(x01[0]); + bq[0*bq_stride+0].x2 = F(x02[0]); + bq[1*bq_stride+0].x1 = F(x01[1]); + bq[1*bq_stride+0].x2 = F(x02[1]); + + bq[0*bq_stride+1].x1 = F(x11[0]); + bq[0*bq_stride+1].x2 = F(x12[0]); + bq[1*bq_stride+1].x1 = F(x11[1]); + bq[1*bq_stride+1].x2 = F(x12[1]); +#undef F +} + +static void dsp_biquad_run4_sse(void *obj, struct biquad *bq, uint32_t bq_stride, + float **out, const float **in, uint32_t n_samples) +{ + __m128 x, y, z; + __m128 b0, b1, b2; + __m128 a1, a2; + __m128 x1, x2; + uint32_t i; + + b0 = _mm_setr_ps(bq[0].b0, bq[bq_stride].b0, bq[2*bq_stride].b0, bq[3*bq_stride].b0); + b1 = _mm_setr_ps(bq[0].b1, bq[bq_stride].b1, bq[2*bq_stride].b1, bq[3*bq_stride].b1); + b2 = _mm_setr_ps(bq[0].b2, bq[bq_stride].b2, bq[2*bq_stride].b2, bq[3*bq_stride].b2); + a1 = _mm_setr_ps(bq[0].a1, bq[bq_stride].a1, bq[2*bq_stride].a1, bq[3*bq_stride].a1); + a2 = _mm_setr_ps(bq[0].a2, bq[bq_stride].a2, bq[2*bq_stride].a2, bq[3*bq_stride].a2); + x1 = _mm_setr_ps(bq[0].x1, bq[bq_stride].x1, bq[2*bq_stride].x1, bq[3*bq_stride].x1); + x2 = _mm_setr_ps(bq[0].x2, bq[bq_stride].x2, bq[2*bq_stride].x2, bq[3*bq_stride].x2); + + for (i = 0; i < n_samples; i++) { + x = _mm_setr_ps(in[0][i], in[1][i], in[2][i], in[3][i]); + + y = _mm_mul_ps(x, b0); /* y = x * b0 */ + y = _mm_add_ps(y, x1); /* y = x * b0 + x1*/ + z = _mm_mul_ps(y, a1); /* z = a1 * y */ + x1 = _mm_mul_ps(x, b1); /* x1 = x * b1 */ + x1 = _mm_add_ps(x1, x2); /* x1 = x * b1 + x2*/ + x1 = _mm_sub_ps(x1, z); /* x1 = x * b1 + x2 - a1 * y*/ + z = _mm_mul_ps(y, a2); /* z = a2 * y */ + x2 = _mm_mul_ps(x, b2); /* x2 = x * b2 */ + x2 = _mm_sub_ps(x2, z); /* x2 = x * b2 - a2 * y*/ + + out[0][i] = y[0]; + out[1][i] = y[1]; + out[2][i] = y[2]; + out[3][i] = y[3]; + } +#define F(x) (isnormal(x) ? (x) : 0.0f) + bq[0*bq_stride].x1 = F(x1[0]); + bq[0*bq_stride].x2 = F(x2[0]); + bq[1*bq_stride].x1 = F(x1[1]); + bq[1*bq_stride].x2 = F(x2[1]); + bq[2*bq_stride].x1 = F(x1[2]); + bq[2*bq_stride].x2 = F(x2[2]); + bq[3*bq_stride].x1 = F(x1[3]); + bq[3*bq_stride].x2 = F(x2[3]); +#undef F +} + +static void dsp_biquad2_run4_sse(void *obj, struct biquad *bq, uint32_t bq_stride, + float **out, const float **in, uint32_t n_samples) +{ + __m128 x, y, z; + __m128 b00, b01, b02, b10, b11, b12; + __m128 a01, a02, a11, a12; + __m128 x01, x02, x11, x12; + uint32_t i; + + b00 = _mm_setr_ps(bq[0].b0, bq[bq_stride].b0, bq[2*bq_stride].b0, bq[3*bq_stride].b0); + b01 = _mm_setr_ps(bq[0].b1, bq[bq_stride].b1, bq[2*bq_stride].b1, bq[3*bq_stride].b1); + b02 = _mm_setr_ps(bq[0].b2, bq[bq_stride].b2, bq[2*bq_stride].b2, bq[3*bq_stride].b2); + a01 = _mm_setr_ps(bq[0].a1, bq[bq_stride].a1, bq[2*bq_stride].a1, bq[3*bq_stride].a1); + a02 = _mm_setr_ps(bq[0].a2, bq[bq_stride].a2, bq[2*bq_stride].a2, bq[3*bq_stride].a2); + x01 = _mm_setr_ps(bq[0].x1, bq[bq_stride].x1, bq[2*bq_stride].x1, bq[3*bq_stride].x1); + x02 = _mm_setr_ps(bq[0].x2, bq[bq_stride].x2, bq[2*bq_stride].x2, bq[3*bq_stride].x2); + + b10 = _mm_setr_ps(bq[1].b0, bq[bq_stride+1].b0, bq[2*bq_stride+1].b0, bq[3*bq_stride+1].b0); + b11 = _mm_setr_ps(bq[1].b1, bq[bq_stride+1].b1, bq[2*bq_stride+1].b1, bq[3*bq_stride+1].b1); + b12 = _mm_setr_ps(bq[1].b2, bq[bq_stride+1].b2, bq[2*bq_stride+1].b2, bq[3*bq_stride+1].b2); + a11 = _mm_setr_ps(bq[1].a1, bq[bq_stride+1].a1, bq[2*bq_stride+1].a1, bq[3*bq_stride+1].a1); + a12 = _mm_setr_ps(bq[1].a2, bq[bq_stride+1].a2, bq[2*bq_stride+1].a2, bq[3*bq_stride+1].a2); + x11 = _mm_setr_ps(bq[1].x1, bq[bq_stride+1].x1, bq[2*bq_stride+1].x1, bq[3*bq_stride+1].x1); + x12 = _mm_setr_ps(bq[1].x2, bq[bq_stride+1].x2, bq[2*bq_stride+1].x2, bq[3*bq_stride+1].x2); + + for (i = 0; i < n_samples; i++) { + x = _mm_setr_ps(in[0][i], in[1][i], in[2][i], in[3][i]); + + y = _mm_mul_ps(x, b00); /* y = x * b0 */ + y = _mm_add_ps(y, x01); /* y = x * b0 + x1*/ + z = _mm_mul_ps(y, a01); /* z = a1 * y */ + x01 = _mm_mul_ps(x, b01); /* x1 = x * b1 */ + x01 = _mm_add_ps(x01, x02); /* x1 = x * b1 + x2*/ + x01 = _mm_sub_ps(x01, z); /* x1 = x * b1 + x2 - a1 * y*/ + z = _mm_mul_ps(y, a02); /* z = a2 * y */ + x02 = _mm_mul_ps(x, b02); /* x2 = x * b2 */ + x02 = _mm_sub_ps(x02, z); /* x2 = x * b2 - a2 * y*/ + + x = y; + + y = _mm_mul_ps(x, b10); /* y = x * b0 */ + y = _mm_add_ps(y, x11); /* y = x * b0 + x1*/ + z = _mm_mul_ps(y, a11); /* z = a1 * y */ + x11 = _mm_mul_ps(x, b11); /* x1 = x * b1 */ + x11 = _mm_add_ps(x11, x12); /* x1 = x * b1 + x2*/ + x11 = _mm_sub_ps(x11, z); /* x1 = x * b1 + x2 - a1 * y*/ + z = _mm_mul_ps(y, a12); /* z = a2 * y*/ + x12 = _mm_mul_ps(x, b12); /* x2 = x * b2 */ + x12 = _mm_sub_ps(x12, z); /* x2 = x * b2 - a2 * y*/ + + out[0][i] = y[0]; + out[1][i] = y[1]; + out[2][i] = y[2]; + out[3][i] = y[3]; + } +#define F(x) (isnormal(x) ? (x) : 0.0f) + bq[0*bq_stride+0].x1 = F(x01[0]); + bq[0*bq_stride+0].x2 = F(x02[0]); + bq[1*bq_stride+0].x1 = F(x01[1]); + bq[1*bq_stride+0].x2 = F(x02[1]); + bq[2*bq_stride+0].x1 = F(x01[2]); + bq[2*bq_stride+0].x2 = F(x02[2]); + bq[3*bq_stride+0].x1 = F(x01[3]); + bq[3*bq_stride+0].x2 = F(x02[3]); + + bq[0*bq_stride+1].x1 = F(x11[0]); + bq[0*bq_stride+1].x2 = F(x12[0]); + bq[1*bq_stride+1].x1 = F(x11[1]); + bq[1*bq_stride+1].x2 = F(x12[1]); + bq[2*bq_stride+1].x1 = F(x11[2]); + bq[2*bq_stride+1].x2 = F(x12[2]); + bq[3*bq_stride+1].x1 = F(x11[3]); + bq[3*bq_stride+1].x2 = F(x12[3]); +#undef F +} + +void dsp_biquad_run_sse(void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq_stride, + float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], + uint32_t n_src, uint32_t n_samples) +{ + uint32_t i, j, bqs2 = bq_stride*2, bqs4 = bqs2*2; + uint32_t iunrolled4 = n_src & ~3; + uint32_t iunrolled2 = n_src & ~1; + uint32_t junrolled2 = n_bq & ~1; + + for (i = 0; i < iunrolled4; i+=4, bq+=bqs4) { + const float *s[4] = { in[i], in[i+1], in[i+2], in[i+3] }; + float *d[4] = { out[i], out[i+1], out[i+2], out[i+3] }; + + if (s[0] == NULL || s[1] == NULL || s[2] == NULL || s[3] == NULL || + d[0] == NULL || d[1] == NULL || d[2] == NULL || d[3] == NULL) + break; + + j = 0; + if (j < junrolled2) { + dsp_biquad2_run4_sse(obj, &bq[j], bq_stride, d, s, n_samples); + s[0] = d[0]; + s[1] = d[1]; + s[2] = d[2]; + s[3] = d[3]; + j+=2; + } + for (; j < junrolled2; j+=2) { + dsp_biquad2_run4_sse(obj, &bq[j], bq_stride, d, s, n_samples); + } + if (j < n_bq) { + dsp_biquad_run4_sse(obj, &bq[j], bq_stride, d, s, n_samples); + } + } + for (; i < iunrolled2; i+=2, bq+=bqs2) { + const float *s[2] = { in[i], in[i+1] }; + float *d[2] = { out[i], out[i+1] }; + + if (s[0] == NULL || s[1] == NULL || d[0] == NULL || d[1] == NULL) + break; + + j = 0; + if (j < junrolled2) { + dsp_biquad2_run2_sse(obj, &bq[j], bq_stride, d, s, n_samples); + s[0] = d[0]; + s[1] = d[1]; + j+=2; + } + for (; j < junrolled2; j+=2) { + dsp_biquad2_run2_sse(obj, &bq[j], bq_stride, d, s, n_samples); + } + if (j < n_bq) { + dsp_biquad_run2_sse(obj, &bq[j], bq_stride, d, s, n_samples); + } + } + for (; i < n_src; i++, bq+=bq_stride) { + const float *s = in[i]; + float *d = out[i]; + if (s == NULL || d == NULL) + continue; + + j = 0; + if (j < junrolled2) { + dsp_biquad2_run_sse(obj, &bq[j], d, s, n_samples); + s = d; + j+=2; + } + for (; j < junrolled2; j+=2) { + dsp_biquad2_run_sse(obj, &bq[j], d, s, n_samples); + } + if (j < n_bq) { + dsp_biquad_run1_sse(obj, &bq[j], d, s, n_samples); + } + } +} + +void dsp_delay_sse(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay, + float *dst, const float *src, uint32_t n_samples) +{ + __m128 t[1]; + uint32_t w = *pos; + uint32_t o = n_buffer - delay; + uint32_t n, unrolled; + + if (SPA_IS_ALIGNED(src, 16) && + SPA_IS_ALIGNED(dst, 16)) + unrolled = n_samples & ~3; + else + unrolled = 0; + + for(n = 0; n < unrolled; n += 4) { + t[0] = _mm_load_ps(&src[n]); + _mm_storeu_ps(&buffer[w], t[0]); + _mm_storeu_ps(&buffer[w+n_buffer], t[0]); + t[0] = _mm_loadu_ps(&buffer[w+o]); + _mm_store_ps(&dst[n], t[0]); + w = w + 4 >= n_buffer ? 0 : w + 4; + } + for(; n < n_samples; n++) { + t[0] = _mm_load_ss(&src[n]); + _mm_store_ss(&buffer[w], t[0]); + _mm_store_ss(&buffer[w+n_buffer], t[0]); + t[0] = _mm_load_ss(&buffer[w+o]); + _mm_store_ss(&dst[n], t[0]); + w = w + 1 >= n_buffer ? 0 : w + 1; + } + *pos = w; +} + +inline static void _mm_mul_pz(__m128 *a, __m128 *b, __m128 *d) +{ + __m128 ar, ai, br, bi, arbr, arbi, aibi, aibr, dr, di; + ar = _mm_shuffle_ps(a[0], a[1], _MM_SHUFFLE(2,0,2,0)); /* ar0 ar1 ar2 ar3 */ + ai = _mm_shuffle_ps(a[0], a[1], _MM_SHUFFLE(3,1,3,1)); /* ai0 ai1 ai2 ai3 */ + br = _mm_shuffle_ps(b[0], b[1], _MM_SHUFFLE(2,0,2,0)); /* br0 br1 br2 br3 */ + bi = _mm_shuffle_ps(b[0], b[1], _MM_SHUFFLE(3,1,3,1)) /* bi0 bi1 bi2 bi3 */; + + arbr = _mm_mul_ps(ar, br); /* ar * br */ + arbi = _mm_mul_ps(ar, bi); /* ar * bi */ + + aibi = _mm_mul_ps(ai, bi); /* ai * bi */ + aibr = _mm_mul_ps(ai, br); /* ai * br */ + + dr = _mm_sub_ps(arbr, aibi); /* ar * br - ai * bi */ + di = _mm_add_ps(arbi, aibr); /* ar * bi + ai * br */ + d[0] = _mm_unpacklo_ps(dr, di); + d[1] = _mm_unpackhi_ps(dr, di); +} + +void dsp_fft_cmul_sse(void *obj, void *fft, + float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, + const float * SPA_RESTRICT b, uint32_t len, const float scale) +{ +#ifdef HAVE_FFTW + __m128 s = _mm_set1_ps(scale); + __m128 aa[2], bb[2], dd[2]; + uint32_t i, unrolled; + + if (SPA_IS_ALIGNED(a, 16) && + SPA_IS_ALIGNED(b, 16) && + SPA_IS_ALIGNED(dst, 16)) + unrolled = len & ~3; + else + unrolled = 0; + + for (i = 0; i < unrolled; i+=4) { + aa[0] = _mm_load_ps(&a[2*i]); /* ar0 ai0 ar1 ai1 */ + aa[1] = _mm_load_ps(&a[2*i+4]); /* ar1 ai1 ar2 ai2 */ + bb[0] = _mm_load_ps(&b[2*i]); /* br0 bi0 br1 bi1 */ + bb[1] = _mm_load_ps(&b[2*i+4]); /* br2 bi2 br3 bi3 */ + _mm_mul_pz(aa, bb, dd); + dd[0] = _mm_mul_ps(dd[0], s); + dd[1] = _mm_mul_ps(dd[1], s); + _mm_store_ps(&dst[2*i], dd[0]); + _mm_store_ps(&dst[2*i+4], dd[1]); + } + for (; i < len; i++) { + dst[2*i ] = (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale; + dst[2*i+1] = (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale; + } +#else + pffft_zconvolve(fft, a, b, dst, scale); +#endif +} + +void dsp_fft_cmuladd_sse(void *obj, void *fft, + float * SPA_RESTRICT dst, const float * SPA_RESTRICT src, + const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, + uint32_t len, const float scale) +{ +#ifdef HAVE_FFTW + __m128 s = _mm_set1_ps(scale); + __m128 aa[2], bb[2], dd[2], t[2]; + uint32_t i, unrolled; + + if (SPA_IS_ALIGNED(a, 16) && + SPA_IS_ALIGNED(b, 16) && + SPA_IS_ALIGNED(src, 16) && + SPA_IS_ALIGNED(dst, 16)) + unrolled = len & ~3; + else + unrolled = 0; + + for (i = 0; i < unrolled; i+=4) { + aa[0] = _mm_load_ps(&a[2*i]); /* ar0 ai0 ar1 ai1 */ + aa[1] = _mm_load_ps(&a[2*i+4]); /* ar1 ai1 ar2 ai2 */ + bb[0] = _mm_load_ps(&b[2*i]); /* br0 bi0 br1 bi1 */ + bb[1] = _mm_load_ps(&b[2*i+4]); /* br2 bi2 br3 bi3 */ + _mm_mul_pz(aa, bb, dd); + dd[0] = _mm_mul_ps(dd[0], s); + dd[1] = _mm_mul_ps(dd[1], s); + t[0] = _mm_load_ps(&src[2*i]); + t[1] = _mm_load_ps(&src[2*i+4]); + t[0] = _mm_add_ps(t[0], dd[0]); + t[1] = _mm_add_ps(t[1], dd[1]); + _mm_store_ps(&dst[2*i], t[0]); + _mm_store_ps(&dst[2*i+4], t[1]); + } + for (; i < len; i++) { + dst[2*i ] = src[2*i ] + (a[2*i] * b[2*i ] - a[2*i+1] * b[2*i+1]) * scale; + dst[2*i+1] = src[2*i+1] + (a[2*i] * b[2*i+1] + a[2*i+1] * b[2*i ]) * scale; + } +#else + pffft_zconvolve_accumulate(fft, a, b, src, dst, scale); +#endif +} diff --git a/src/modules/module-filter-chain/dsp-ops.c b/spa/plugins/filter-graph/audio-dsp.c similarity index 55% rename from src/modules/module-filter-chain/dsp-ops.c rename to spa/plugins/filter-graph/audio-dsp.c index 7a21177b..fadd8534 100644 --- a/src/modules/module-filter-chain/dsp-ops.c +++ b/spa/plugins/filter-graph/audio-dsp.c @@ -5,35 +5,42 @@ #include <string.h> #include <stdio.h> #include <math.h> +#include <time.h> #include <spa/support/cpu.h> #include <spa/utils/defs.h> #include <spa/param/audio/format-utils.h> -#include "dsp-ops.h" +#include "pffft.h" + +#include "audio-dsp-impl.h" struct dsp_info { uint32_t cpu_flags; - struct dsp_ops_funcs funcs; + struct spa_fga_dsp_methods funcs; }; -static struct dsp_info dsp_table[] = +static const struct dsp_info dsp_table[] = { #if defined (HAVE_AVX) { SPA_CPU_FLAG_AVX, .funcs.clear = dsp_clear_c, .funcs.copy = dsp_copy_c, - .funcs.mix_gain = dsp_mix_gain_sse, - .funcs.biquad_run = dsp_biquad_run_c, + .funcs.mix_gain = dsp_mix_gain_avx, + .funcs.biquad_run = dsp_biquad_run_sse, .funcs.sum = dsp_sum_avx, .funcs.linear = dsp_linear_c, .funcs.mult = dsp_mult_c, .funcs.fft_new = dsp_fft_new_c, .funcs.fft_free = dsp_fft_free_c, + .funcs.fft_memalloc = dsp_fft_memalloc_c, + .funcs.fft_memfree = dsp_fft_memfree_c, + .funcs.fft_memclear = dsp_fft_memclear_c, .funcs.fft_run = dsp_fft_run_c, - .funcs.fft_cmul = dsp_fft_cmul_c, - .funcs.fft_cmuladd = dsp_fft_cmuladd_c, + .funcs.fft_cmul = dsp_fft_cmul_avx, + .funcs.fft_cmuladd = dsp_fft_cmuladd_avx, + .funcs.delay = dsp_delay_sse, }, #endif #if defined (HAVE_SSE) @@ -41,15 +48,19 @@ static struct dsp_info dsp_table[] = .funcs.clear = dsp_clear_c, .funcs.copy = dsp_copy_c, .funcs.mix_gain = dsp_mix_gain_sse, - .funcs.biquad_run = dsp_biquad_run_c, + .funcs.biquad_run = dsp_biquad_run_sse, .funcs.sum = dsp_sum_sse, .funcs.linear = dsp_linear_c, .funcs.mult = dsp_mult_c, .funcs.fft_new = dsp_fft_new_c, .funcs.fft_free = dsp_fft_free_c, + .funcs.fft_memalloc = dsp_fft_memalloc_c, + .funcs.fft_memfree = dsp_fft_memfree_c, + .funcs.fft_memclear = dsp_fft_memclear_c, .funcs.fft_run = dsp_fft_run_c, - .funcs.fft_cmul = dsp_fft_cmul_c, - .funcs.fft_cmuladd = dsp_fft_cmuladd_c, + .funcs.fft_cmul = dsp_fft_cmul_sse, + .funcs.fft_cmuladd = dsp_fft_cmuladd_sse, + .funcs.delay = dsp_delay_sse, }, #endif { 0, @@ -62,9 +73,13 @@ static struct dsp_info dsp_table[] = .funcs.mult = dsp_mult_c, .funcs.fft_new = dsp_fft_new_c, .funcs.fft_free = dsp_fft_free_c, + .funcs.fft_memalloc = dsp_fft_memalloc_c, + .funcs.fft_memfree = dsp_fft_memfree_c, + .funcs.fft_memclear = dsp_fft_memclear_c, .funcs.fft_run = dsp_fft_run_c, .funcs.fft_cmul = dsp_fft_cmul_c, .funcs.fft_cmuladd = dsp_fft_cmuladd_c, + .funcs.delay = dsp_delay_c, }, }; @@ -79,23 +94,31 @@ static const struct dsp_info *find_dsp_info(uint32_t cpu_flags) return NULL; } -static void impl_dsp_ops_free(struct dsp_ops *ops) +void spa_fga_dsp_free(struct spa_fga_dsp *dsp) { - spa_zero(*ops); + free(dsp); } -int dsp_ops_init(struct dsp_ops *ops, uint32_t cpu_flags) +struct spa_fga_dsp * spa_fga_dsp_new(uint32_t cpu_flags) { const struct dsp_info *info; + struct spa_fga_dsp *dsp; info = find_dsp_info(cpu_flags); - if (info == NULL) - return -ENOTSUP; + if (info == NULL) { + errno = ENOTSUP; + return NULL; + } + dsp = calloc(1, sizeof(*dsp)); + if (dsp == NULL) + return NULL; - ops->cpu_flags = cpu_flags; - ops->priv = info; - ops->free = impl_dsp_ops_free; - ops->funcs = info->funcs; + pffft_select_cpu(cpu_flags); + dsp->cpu_flags = cpu_flags; + dsp->iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioDSP, + SPA_VERSION_FGA_DSP, + &info->funcs, dsp); - return 0; + return dsp; } diff --git a/spa/plugins/filter-graph/audio-dsp.h b/spa/plugins/filter-graph/audio-dsp.h new file mode 100644 index 00000000..4fc06eb7 --- /dev/null +++ b/spa/plugins/filter-graph/audio-dsp.h @@ -0,0 +1,168 @@ +/* Spa */ +/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_FGA_DSP_H +#define SPA_FGA_DSP_H + +#include <spa/utils/defs.h> +#include <spa/utils/hook.h> + +#include "biquad.h" + +#define SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioDSP SPA_TYPE_INFO_INTERFACE_BASE "FilterGraph:AudioDSP" + +#define SPA_VERSION_FGA_DSP 0 +struct spa_fga_dsp { + struct spa_interface iface; + uint32_t cpu_flags; +}; + +struct spa_fga_dsp_methods { +#define SPA_VERSION_FGA_DSP_METHODS 0 + uint32_t version; + + void (*clear) (void *obj, float * SPA_RESTRICT dst, uint32_t n_samples); + void (*copy) (void *obj, + float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src, uint32_t n_samples); + void (*mix_gain) (void *obj, + float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src[], uint32_t n_src, + float gain[], uint32_t n_gain, uint32_t n_samples); + void (*sum) (void *obj, + float * dst, const float * SPA_RESTRICT a, + const float * SPA_RESTRICT b, uint32_t n_samples); + + void *(*fft_new) (void *obj, uint32_t size, bool real); + void (*fft_free) (void *obj, void *fft); + void *(*fft_memalloc) (void *obj, uint32_t size, bool real); + void (*fft_memfree) (void *obj, void *mem); + void (*fft_memclear) (void *obj, void *mem, uint32_t size, bool real); + void (*fft_run) (void *obj, void *fft, int direction, + const float * SPA_RESTRICT src, float * SPA_RESTRICT dst); + void (*fft_cmul) (void *obj, void *fft, + float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, + const float * SPA_RESTRICT b, uint32_t len, const float scale); + void (*fft_cmuladd) (void *obj, void *fft, + float * dst, const float * src, + const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, + uint32_t len, const float scale); + void (*linear) (void *obj, + float * dst, const float * SPA_RESTRICT src, + const float mult, const float add, uint32_t n_samples); + void (*mult) (void *obj, + float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples); + void (*biquad_run) (void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq_stride, + float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], + uint32_t n_src, uint32_t n_samples); + void (*delay) (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay, + float *dst, const float *src, uint32_t n_samples); +}; + +static inline void spa_fga_dsp_clear(struct spa_fga_dsp *obj, float * SPA_RESTRICT dst, uint32_t n_samples) +{ + spa_api_method_v(spa_fga_dsp, &obj->iface, clear, 0, + dst, n_samples); +} +static inline void spa_fga_dsp_copy(struct spa_fga_dsp *obj, + float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src, uint32_t n_samples) +{ + spa_api_method_v(spa_fga_dsp, &obj->iface, copy, 0, + dst, src, n_samples); +} +static inline void spa_fga_dsp_mix_gain(struct spa_fga_dsp *obj, + float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src[], uint32_t n_src, + float gain[], uint32_t n_gain, uint32_t n_samples) +{ + spa_api_method_v(spa_fga_dsp, &obj->iface, mix_gain, 0, + dst, src, n_src, gain, n_gain, n_samples); +} +static inline void spa_fga_dsp_sum(struct spa_fga_dsp *obj, + float * dst, const float * SPA_RESTRICT a, + const float * SPA_RESTRICT b, uint32_t n_samples) +{ + spa_api_method_v(spa_fga_dsp, &obj->iface, sum, 0, + dst, a, b, n_samples); +} + +static inline void *spa_fga_dsp_fft_new(struct spa_fga_dsp *obj, uint32_t size, bool real) +{ + return spa_api_method_r(void *, NULL, spa_fga_dsp, &obj->iface, fft_new, 0, + size, real); +} +static inline void spa_fga_dsp_fft_free(struct spa_fga_dsp *obj, void *fft) +{ + spa_api_method_v(spa_fga_dsp, &obj->iface, fft_free, 0, + fft); +} +static inline void *spa_fga_dsp_fft_memalloc(struct spa_fga_dsp *obj, uint32_t size, bool real) +{ + return spa_api_method_r(void *, NULL, spa_fga_dsp, &obj->iface, fft_memalloc, 0, + size, real); +} +static inline void spa_fga_dsp_fft_memfree(struct spa_fga_dsp *obj, void *mem) +{ + spa_api_method_v(spa_fga_dsp, &obj->iface, fft_memfree, 0, + mem); +} +static inline void spa_fga_dsp_fft_memclear(struct spa_fga_dsp *obj, void *mem, uint32_t size, bool real) +{ + spa_api_method_v(spa_fga_dsp, &obj->iface, fft_memclear, 0, + mem, size, real); +} +static inline void spa_fga_dsp_fft_run(struct spa_fga_dsp *obj, void *fft, int direction, + const float * SPA_RESTRICT src, float * SPA_RESTRICT dst) +{ + spa_api_method_v(spa_fga_dsp, &obj->iface, fft_run, 0, + fft, direction, src, dst); +} +static inline void spa_fga_dsp_fft_cmul(struct spa_fga_dsp *obj, void *fft, + float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, + const float * SPA_RESTRICT b, uint32_t len, const float scale) +{ + spa_api_method_v(spa_fga_dsp, &obj->iface, fft_cmul, 0, + fft, dst, a, b, len, scale); +} +static inline void spa_fga_dsp_fft_cmuladd(struct spa_fga_dsp *obj, void *fft, + float * dst, const float * src, + const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, + uint32_t len, const float scale) +{ + spa_api_method_v(spa_fga_dsp, &obj->iface, fft_cmuladd, 0, + fft, dst, src, a, b, len, scale); +} +static inline void spa_fga_dsp_linear(struct spa_fga_dsp *obj, + float * dst, const float * SPA_RESTRICT src, + const float mult, const float add, uint32_t n_samples) +{ + spa_api_method_v(spa_fga_dsp, &obj->iface, linear, 0, + dst, src, mult, add, n_samples); +} +static inline void spa_fga_dsp_mult(struct spa_fga_dsp *obj, + float * SPA_RESTRICT dst, + const float * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) +{ + spa_api_method_v(spa_fga_dsp, &obj->iface, mult, 0, + dst, src, n_src, n_samples); +} +static inline void spa_fga_dsp_biquad_run(struct spa_fga_dsp *obj, + struct biquad *bq, uint32_t n_bq, uint32_t bq_stride, + float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], + uint32_t n_src, uint32_t n_samples) +{ + spa_api_method_v(spa_fga_dsp, &obj->iface, biquad_run, 0, + bq, n_bq, bq_stride, out, in, n_src, n_samples); +} +static inline void spa_fga_dsp_delay(struct spa_fga_dsp *obj, + float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay, + float *dst, const float *src, uint32_t n_samples) +{ + spa_api_method_v(spa_fga_dsp, &obj->iface, delay, 0, + buffer, pos, n_buffer, delay, dst, src, n_samples); +} + +#endif /* SPA_FGA_DSP_H */ diff --git a/spa/plugins/filter-graph/audio-plugin.h b/spa/plugins/filter-graph/audio-plugin.h new file mode 100644 index 00000000..e05d7a80 --- /dev/null +++ b/spa/plugins/filter-graph/audio-plugin.h @@ -0,0 +1,95 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#ifndef SPA_FGA_PLUGIN_H +#define SPA_FGA_PLUGIN_H + +#include <stdint.h> +#include <stddef.h> + +#include <spa/utils/defs.h> +#include <spa/utils/hook.h> +#include <spa/support/plugin.h> + +#define SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin SPA_TYPE_INFO_INTERFACE_BASE "FilterGraph:AudioPlugin" + +#define SPA_VERSION_FGA_PLUGIN 0 +struct spa_fga_plugin { struct spa_interface iface; }; + +struct spa_fga_plugin_methods { +#define SPA_VERSION_FGA_PLUGIN_METHODS 0 + uint32_t version; + + const struct spa_fga_descriptor *(*make_desc) (void *plugin, const char *name); +}; + +struct spa_fga_port { + uint32_t index; + const char *name; +#define SPA_FGA_PORT_INPUT (1ULL << 0) +#define SPA_FGA_PORT_OUTPUT (1ULL << 1) +#define SPA_FGA_PORT_CONTROL (1ULL << 2) +#define SPA_FGA_PORT_AUDIO (1ULL << 3) + uint64_t flags; + +#define SPA_FGA_HINT_BOOLEAN (1ULL << 2) +#define SPA_FGA_HINT_SAMPLE_RATE (1ULL << 3) +#define SPA_FGA_HINT_INTEGER (1ULL << 5) + uint64_t hint; + float def; + float min; + float max; +}; + +#define SPA_FGA_IS_PORT_INPUT(x) ((x) & SPA_FGA_PORT_INPUT) +#define SPA_FGA_IS_PORT_OUTPUT(x) ((x) & SPA_FGA_PORT_OUTPUT) +#define SPA_FGA_IS_PORT_CONTROL(x) ((x) & SPA_FGA_PORT_CONTROL) +#define SPA_FGA_IS_PORT_AUDIO(x) ((x) & SPA_FGA_PORT_AUDIO) + +struct spa_fga_descriptor { + const char *name; +#define SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA (1ULL << 0) +#define SPA_FGA_DESCRIPTOR_COPY (1ULL << 1) + uint64_t flags; + + void (*free) (const struct spa_fga_descriptor *desc); + + uint32_t n_ports; + struct spa_fga_port *ports; + + void *(*instantiate) (const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc, + unsigned long SampleRate, int index, const char *config); + + void (*cleanup) (void *instance); + + void (*connect_port) (void *instance, unsigned long port, float *data); + void (*control_changed) (void *instance); + + void (*activate) (void *instance); + void (*deactivate) (void *instance); + + void (*run) (void *instance, unsigned long SampleCount); +}; + +static inline void spa_fga_descriptor_free(const struct spa_fga_descriptor *desc) +{ + if (desc->free) + desc->free(desc); +} + +static inline const struct spa_fga_descriptor * +spa_fga_plugin_make_desc(struct spa_fga_plugin *plugin, const char *name) +{ + return spa_api_method_r(const struct spa_fga_descriptor *, NULL, + spa_fga_plugin, &plugin->iface, make_desc, 0, name); +} + +typedef struct spa_fga_plugin *(spa_filter_graph_audio_plugin_load_func_t)(const struct spa_support *support, + uint32_t n_support, const char *path, const struct spa_dict *info); + +#define SPA_FILTER_GRAPH_AUDIO_PLUGIN_LOAD_FUNC_NAME "spa_filter_graph_audio_plugin_load" + + + +#endif /* PLUGIN_H */ diff --git a/src/modules/module-filter-chain/biquad.h b/spa/plugins/filter-graph/biquad.h similarity index 96% rename from src/modules/module-filter-chain/biquad.h rename to spa/plugins/filter-graph/biquad.h index 650b2639..3344598e 100644 --- a/src/modules/module-filter-chain/biquad.h +++ b/spa/plugins/filter-graph/biquad.h @@ -10,6 +10,20 @@ extern "C" { #endif +/* The type of the biquad filters */ +enum biquad_type { + BQ_NONE, + BQ_LOWPASS, + BQ_HIGHPASS, + BQ_BANDPASS, + BQ_LOWSHELF, + BQ_HIGHSHELF, + BQ_PEAKING, + BQ_NOTCH, + BQ_ALLPASS, + BQ_RAW, +}; + /* The biquad filter parameters. The transfer function H(z) is (b0 + b1 * z^(-1) * + b2 * z^(-2)) / (1 + a1 * z^(-1) + a2 * z^(-2)). The previous two inputs * are stored in x1 and x2, and the previous two outputs are stored in y1 and @@ -19,23 +33,10 @@ extern "C" { * float is used during the actual filtering for faster computation. */ struct biquad { + enum biquad_type type; float b0, b1, b2; float a1, a2; float x1, x2; - float y1, y2; -}; - -/* The type of the biquad filters */ -enum biquad_type { - BQ_NONE, - BQ_LOWPASS, - BQ_HIGHPASS, - BQ_BANDPASS, - BQ_LOWSHELF, - BQ_HIGHSHELF, - BQ_PEAKING, - BQ_NOTCH, - BQ_ALLPASS }; /* Initialize a biquad filter parameters from its type and parameters. diff --git a/spa/plugins/filter-graph/builtin_plugin.c b/spa/plugins/filter-graph/builtin_plugin.c new file mode 100644 index 00000000..f50ffd77 --- /dev/null +++ b/spa/plugins/filter-graph/builtin_plugin.c @@ -0,0 +1,2647 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include "config.h" + +#include <float.h> +#include <math.h> +#ifdef HAVE_SNDFILE +#include <sndfile.h> +#endif +#include <unistd.h> +#include <limits.h> + +#include <spa/utils/json.h> +#include <spa/utils/result.h> +#include <spa/support/cpu.h> +#include <spa/support/log.h> +#include <spa/plugins/audioconvert/resample.h> + +#include "audio-plugin.h" + +#include "biquad.h" +#include "convolver.h" +#include "audio-dsp.h" + +#define MAX_RATES 32u + +struct plugin { + struct spa_handle handle; + struct spa_fga_plugin plugin; + + struct spa_fga_dsp *dsp; + struct spa_log *log; +}; + +struct builtin { + struct plugin *plugin; + + struct spa_fga_dsp *dsp; + struct spa_log *log; + + unsigned long rate; + float *port[64]; + + int type; + struct biquad bq; + float freq; + float Q; + float gain; + float b0, b1, b2; + float a0, a1, a2; + float accum; +}; + +static void *builtin_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, + unsigned long SampleRate, int index, const char *config) +{ + struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); + struct builtin *impl; + + impl = calloc(1, sizeof(*impl)); + if (impl == NULL) + return NULL; + + impl->plugin = pl; + impl->rate = SampleRate; + impl->dsp = impl->plugin->dsp; + impl->log = impl->plugin->log; + + return impl; +} + +static void builtin_connect_port(void *Instance, unsigned long Port, float * DataLocation) +{ + struct builtin *impl = Instance; + impl->port[Port] = DataLocation; +} + +static void builtin_cleanup(void * Instance) +{ + struct builtin *impl = Instance; + free(impl); +} + +/** copy */ +static void copy_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float *in = impl->port[1], *out = impl->port[0]; + spa_fga_dsp_copy(impl->dsp, out, in, SampleCount); +} + +static struct spa_fga_port copy_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + } +}; + +static const struct spa_fga_descriptor copy_desc = { + .name = "copy", + .flags = SPA_FGA_DESCRIPTOR_COPY, + + .n_ports = 2, + .ports = copy_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = copy_run, + .cleanup = builtin_cleanup, +}; + +/** mixer */ +static void mixer_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + int i, n_src = 0; + float *out = impl->port[0]; + const float *src[8]; + float gains[8]; + bool eq_gain = true; + + if (out == NULL) + return; + + for (i = 0; i < 8; i++) { + float *in = impl->port[1+i]; + float gain = impl->port[9+i][0]; + + if (in == NULL || gain == 0.0f) + continue; + + src[n_src] = in; + gains[n_src++] = gain; + if (gain != gains[0]) + eq_gain = false; + } + if (eq_gain) + spa_fga_dsp_mix_gain(impl->dsp, out, src, n_src, gains, 1, SampleCount); + else + spa_fga_dsp_mix_gain(impl->dsp, out, src, n_src, gains, n_src, SampleCount); +} + +static struct spa_fga_port mixer_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + + { .index = 1, + .name = "In 1", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "In 2", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 3, + .name = "In 3", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 4, + .name = "In 4", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 5, + .name = "In 5", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 6, + .name = "In 6", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 7, + .name = "In 7", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 8, + .name = "In 8", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + + { .index = 9, + .name = "Gain 1", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0f, .min = 0.0f, .max = 10.0f + }, + { .index = 10, + .name = "Gain 2", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0f, .min = 0.0f, .max = 10.0f + }, + { .index = 11, + .name = "Gain 3", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0f, .min = 0.0f, .max = 10.0f + }, + { .index = 12, + .name = "Gain 4", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0f, .min = 0.0f, .max = 10.0f + }, + { .index = 13, + .name = "Gain 5", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0f, .min = 0.0f, .max = 10.0f + }, + { .index = 14, + .name = "Gain 6", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0f, .min = 0.0f, .max = 10.0f + }, + { .index = 15, + .name = "Gain 7", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0f, .min = 0.0f, .max = 10.0f + }, + { .index = 16, + .name = "Gain 8", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0f, .min = 0.0f, .max = 10.0f + }, +}; + +static const struct spa_fga_descriptor mixer_desc = { + .name = "mixer", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = 17, + .ports = mixer_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = mixer_run, + .cleanup = builtin_cleanup, +}; + +/** biquads */ +static int bq_type_from_name(const char *name) +{ + if (spa_streq(name, "bq_lowpass")) + return BQ_LOWPASS; + if (spa_streq(name, "bq_highpass")) + return BQ_HIGHPASS; + if (spa_streq(name, "bq_bandpass")) + return BQ_BANDPASS; + if (spa_streq(name, "bq_lowshelf")) + return BQ_LOWSHELF; + if (spa_streq(name, "bq_highshelf")) + return BQ_HIGHSHELF; + if (spa_streq(name, "bq_peaking")) + return BQ_PEAKING; + if (spa_streq(name, "bq_notch")) + return BQ_NOTCH; + if (spa_streq(name, "bq_allpass")) + return BQ_ALLPASS; + if (spa_streq(name, "bq_raw")) + return BQ_NONE; + return BQ_NONE; +} + +static const char *bq_name_from_type(int type) +{ + switch (type) { + case BQ_LOWPASS: + return "lowpass"; + case BQ_HIGHPASS: + return "highpass"; + case BQ_BANDPASS: + return "bandpass"; + case BQ_LOWSHELF: + return "lowshelf"; + case BQ_HIGHSHELF: + return "highshelf"; + case BQ_PEAKING: + return "peaking"; + case BQ_NOTCH: + return "notch"; + case BQ_ALLPASS: + return "allpass"; + case BQ_NONE: + return "raw"; + } + return "unknown"; +} + +static void bq_raw_update(struct builtin *impl, float b0, float b1, float b2, + float a0, float a1, float a2) +{ + struct biquad *bq = &impl->bq; + impl->b0 = b0; + impl->b1 = b1; + impl->b2 = b2; + impl->a0 = a0; + impl->a1 = a1; + impl->a2 = a2; + if (a0 != 0.0f) + a0 = 1.0f / a0; + bq->b0 = impl->b0 * a0; + bq->b1 = impl->b1 * a0; + bq->b2 = impl->b2 * a0; + bq->a1 = impl->a1 * a0; + bq->a2 = impl->a2 * a0; + bq->x1 = bq->x2 = 0.0f; + bq->type = BQ_RAW; +} + +/* + * config = { + * coefficients = [ + * { rate = 44100, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. }, + * { rate = 48000, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. }, + * { rate = 192000, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. } + * ] + * } + */ +static void *bq_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, + unsigned long SampleRate, int index, const char *config) +{ + struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); + struct builtin *impl; + struct spa_json it[3]; + const char *val; + char key[256]; + uint32_t best_rate = 0; + int len; + + impl = calloc(1, sizeof(*impl)); + if (impl == NULL) + return NULL; + + impl->plugin = pl; + impl->log = impl->plugin->log; + impl->dsp = impl->plugin->dsp; + impl->rate = SampleRate; + impl->b0 = impl->a0 = 1.0f; + impl->type = bq_type_from_name(Descriptor->name); + if (impl->type != BQ_NONE) + return impl; + + if (config == NULL) { + spa_log_error(impl->log, "biquads:bq_raw requires a config section"); + goto error; + } + + if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { + spa_log_error(impl->log, "biquads:config section must be an object"); + goto error; + } + + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { + if (spa_streq(key, "coefficients")) { + if (!spa_json_is_array(val, len)) { + spa_log_error(impl->log, "biquads:coefficients require an array"); + goto error; + } + spa_json_enter(&it[0], &it[1]); + while (spa_json_enter_object(&it[1], &it[2]) > 0) { + int32_t rate = 0; + float b0 = 1.0f, b1 = 0.0f, b2 = 0.0f; + float a0 = 1.0f, a1 = 0.0f, a2 = 0.0f; + + while ((len = spa_json_object_next(&it[2], key, sizeof(key), &val)) > 0) { + if (spa_streq(key, "rate")) { + if (spa_json_parse_int(val, len, &rate) <= 0) { + spa_log_error(impl->log, "biquads:rate requires a number"); + goto error; + } + } + else if (spa_streq(key, "b0")) { + if (spa_json_parse_float(val, len, &b0) <= 0) { + spa_log_error(impl->log, "biquads:b0 requires a float"); + goto error; + } + } + else if (spa_streq(key, "b1")) { + if (spa_json_parse_float(val, len, &b1) <= 0) { + spa_log_error(impl->log, "biquads:b1 requires a float"); + goto error; + } + } + else if (spa_streq(key, "b2")) { + if (spa_json_parse_float(val, len, &b2) <= 0) { + spa_log_error(impl->log, "biquads:b2 requires a float"); + goto error; + } + } + else if (spa_streq(key, "a0")) { + if (spa_json_parse_float(val, len, &a0) <= 0) { + spa_log_error(impl->log, "biquads:a0 requires a float"); + goto error; + } + } + else if (spa_streq(key, "a1")) { + if (spa_json_parse_float(val, len, &a1) <= 0) { + spa_log_error(impl->log, "biquads:a1 requires a float"); + goto error; + } + } + else if (spa_streq(key, "a2")) { + if (spa_json_parse_float(val, len, &a2) <= 0) { + spa_log_error(impl->log, "biquads:a0 requires a float"); + goto error; + } + } + else { + spa_log_warn(impl->log, "biquads: ignoring coefficients key: '%s'", key); + } + } + if (labs((long)rate - (long)SampleRate) < + labs((long)best_rate - (long)SampleRate)) { + best_rate = rate; + bq_raw_update(impl, b0, b1, b2, a0, a1, a2); + } + } + } + else { + spa_log_warn(impl->log, "biquads: ignoring config key: '%s'", key); + } + } + + return impl; +error: + free(impl); + errno = EINVAL; + return NULL; +} + +#define BQ_NUM_PORTS 11 +static struct spa_fga_port bq_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "Freq", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .hint = SPA_FGA_HINT_SAMPLE_RATE, + .def = 0.0f, .min = 0.0f, .max = 1.0f, + }, + { .index = 3, + .name = "Q", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.0f, .min = 0.0f, .max = 10.0f, + }, + { .index = 4, + .name = "Gain", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.0f, .min = -120.0f, .max = 20.0f, + }, + { .index = 5, + .name = "b0", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0f, .min = -10.0f, .max = 10.0f, + }, + { .index = 6, + .name = "b1", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.0f, .min = -10.0f, .max = 10.0f, + }, + { .index = 7, + .name = "b2", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.0f, .min = -10.0f, .max = 10.0f, + }, + { .index = 8, + .name = "a0", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0f, .min = -10.0f, .max = 10.0f, + }, + { .index = 9, + .name = "a1", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.0f, .min = -10.0f, .max = 10.0f, + }, + { .index = 10, + .name = "a2", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.0f, .min = -10.0f, .max = 10.0f, + }, + +}; + +static void bq_freq_update(struct builtin *impl, int type, float freq, float Q, float gain) +{ + struct biquad *bq = &impl->bq; + impl->freq = freq; + impl->Q = Q; + impl->gain = gain; + biquad_set(bq, type, freq * 2 / impl->rate, Q, gain); + impl->port[5][0] = impl->b0 = bq->b0; + impl->port[6][0] = impl->b1 = bq->b1; + impl->port[7][0] = impl->b2 = bq->b2; + impl->port[8][0] = impl->a0 = 1.0f; + impl->port[9][0] = impl->a1 = bq->a1; + impl->port[10][0] = impl->a2 = bq->a2; +} + +static void bq_activate(void * Instance) +{ + struct builtin *impl = Instance; + if (impl->type == BQ_NONE) { + impl->port[5][0] = impl->b0; + impl->port[6][0] = impl->b1; + impl->port[7][0] = impl->b2; + impl->port[8][0] = impl->a0; + impl->port[9][0] = impl->a1; + impl->port[10][0] = impl->a2; + } else { + float freq = impl->port[2][0]; + float Q = impl->port[3][0]; + float gain = impl->port[4][0]; + bq_freq_update(impl, impl->type, freq, Q, gain); + } +} + +static void bq_run(void *Instance, unsigned long samples) +{ + struct builtin *impl = Instance; + struct biquad *bq = &impl->bq; + float *out = impl->port[0]; + float *in = impl->port[1]; + + if (impl->type == BQ_NONE) { + float b0, b1, b2, a0, a1, a2; + b0 = impl->port[5][0]; + b1 = impl->port[6][0]; + b2 = impl->port[7][0]; + a0 = impl->port[8][0]; + a1 = impl->port[9][0]; + a2 = impl->port[10][0]; + if (impl->b0 != b0 || impl->b1 != b1 || impl->b2 != b2 || + impl->a0 != a0 || impl->a1 != a1 || impl->a2 != a2) { + bq_raw_update(impl, b0, b1, b2, a0, a1, a2); + } + } else { + float freq = impl->port[2][0]; + float Q = impl->port[3][0]; + float gain = impl->port[4][0]; + if (impl->freq != freq || impl->Q != Q || impl->gain != gain) + bq_freq_update(impl, impl->type, freq, Q, gain); + } + spa_fga_dsp_biquad_run(impl->dsp, bq, 1, 0, &out, (const float **)&in, 1, samples); +} + +/** bq_lowpass */ +static const struct spa_fga_descriptor bq_lowpass_desc = { + .name = "bq_lowpass", + + .n_ports = BQ_NUM_PORTS, + .ports = bq_ports, + + .instantiate = bq_instantiate, + .connect_port = builtin_connect_port, + .activate = bq_activate, + .run = bq_run, + .cleanup = builtin_cleanup, +}; + +/** bq_highpass */ +static const struct spa_fga_descriptor bq_highpass_desc = { + .name = "bq_highpass", + + .n_ports = BQ_NUM_PORTS, + .ports = bq_ports, + + .instantiate = bq_instantiate, + .connect_port = builtin_connect_port, + .activate = bq_activate, + .run = bq_run, + .cleanup = builtin_cleanup, +}; + +/** bq_bandpass */ +static const struct spa_fga_descriptor bq_bandpass_desc = { + .name = "bq_bandpass", + + .n_ports = BQ_NUM_PORTS, + .ports = bq_ports, + + .instantiate = bq_instantiate, + .connect_port = builtin_connect_port, + .activate = bq_activate, + .run = bq_run, + .cleanup = builtin_cleanup, +}; + +/** bq_lowshelf */ +static const struct spa_fga_descriptor bq_lowshelf_desc = { + .name = "bq_lowshelf", + + .n_ports = BQ_NUM_PORTS, + .ports = bq_ports, + + .instantiate = bq_instantiate, + .connect_port = builtin_connect_port, + .activate = bq_activate, + .run = bq_run, + .cleanup = builtin_cleanup, +}; + +/** bq_highshelf */ +static const struct spa_fga_descriptor bq_highshelf_desc = { + .name = "bq_highshelf", + + .n_ports = BQ_NUM_PORTS, + .ports = bq_ports, + + .instantiate = bq_instantiate, + .connect_port = builtin_connect_port, + .activate = bq_activate, + .run = bq_run, + .cleanup = builtin_cleanup, +}; + +/** bq_peaking */ +static const struct spa_fga_descriptor bq_peaking_desc = { + .name = "bq_peaking", + + .n_ports = BQ_NUM_PORTS, + .ports = bq_ports, + + .instantiate = bq_instantiate, + .connect_port = builtin_connect_port, + .activate = bq_activate, + .run = bq_run, + .cleanup = builtin_cleanup, +}; + +/** bq_notch */ +static const struct spa_fga_descriptor bq_notch_desc = { + .name = "bq_notch", + + .n_ports = BQ_NUM_PORTS, + .ports = bq_ports, + + .instantiate = bq_instantiate, + .connect_port = builtin_connect_port, + .activate = bq_activate, + .run = bq_run, + .cleanup = builtin_cleanup, +}; + + +/** bq_allpass */ +static const struct spa_fga_descriptor bq_allpass_desc = { + .name = "bq_allpass", + + .n_ports = BQ_NUM_PORTS, + .ports = bq_ports, + + .instantiate = bq_instantiate, + .connect_port = builtin_connect_port, + .activate = bq_activate, + .run = bq_run, + .cleanup = builtin_cleanup, +}; + +/* bq_raw */ +static const struct spa_fga_descriptor bq_raw_desc = { + .name = "bq_raw", + + .n_ports = BQ_NUM_PORTS, + .ports = bq_ports, + + .instantiate = bq_instantiate, + .connect_port = builtin_connect_port, + .activate = bq_activate, + .run = bq_run, + .cleanup = builtin_cleanup, +}; + +/** convolve */ +struct convolver_impl { + struct plugin *plugin; + + struct spa_log *log; + struct spa_fga_dsp *dsp; + unsigned long rate; + float *port[2]; + + struct convolver *conv; +}; + +#ifdef HAVE_SNDFILE +static float *read_samples_from_sf(SNDFILE *f, const SF_INFO *info, float gain, int delay, + int offset, int length, int channel, long unsigned *rate, int *n_samples) { + float *samples; + int i, n; + + if (length <= 0) + length = info->frames; + else + length = SPA_MIN(length, info->frames); + + length -= SPA_MIN(offset, length); + + n = delay + length; + if (n == 0) + return NULL; + + samples = calloc(n * info->channels, sizeof(float)); + if (samples == NULL) + return NULL; + + if (offset > 0) + sf_seek(f, offset, SEEK_SET); + sf_readf_float(f, samples + (delay * info->channels), length); + + channel = channel % info->channels; + + for (i = 0; i < n; i++) + samples[i] = samples[info->channels * i + channel] * gain; + + *n_samples = n; + *rate = info->samplerate; + return samples; +} +#endif + +static float *read_closest(struct plugin *pl, char **filenames, float gain, float delay_sec, int offset, + int length, int channel, long unsigned *rate, int *n_samples) +{ +#ifdef HAVE_SNDFILE + SF_INFO infos[MAX_RATES]; + SNDFILE *fs[MAX_RATES]; + + spa_zero(infos); + spa_zero(fs); + + int diff = INT_MAX; + uint32_t best = 0, i; + float *samples = NULL; + + for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) { + fs[i] = sf_open(filenames[i], SFM_READ, &infos[i]); + if (fs[i] == NULL) + continue; + + if (labs((long)infos[i].samplerate - (long)*rate) < diff) { + best = i; + diff = labs((long)infos[i].samplerate - (long)*rate); + spa_log_debug(pl->log, "new closest match: %d", infos[i].samplerate); + } + } + if (fs[best] != NULL) { + spa_log_info(pl->log, "loading best rate:%u %s", infos[best].samplerate, filenames[best]); + samples = read_samples_from_sf(fs[best], &infos[best], gain, + (int) (delay_sec * infos[best].samplerate), offset, length, + channel, rate, n_samples); + } else { + char buf[PATH_MAX]; + spa_log_error(pl->log, "Can't open any sample file (CWD %s):", + getcwd(buf, sizeof(buf))); + for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) { + fs[i] = sf_open(filenames[i], SFM_READ, &infos[i]); + if (fs[i] == NULL) + spa_log_error(pl->log, " failed file %s: %s", filenames[i], sf_strerror(fs[i])); + else + spa_log_warn(pl->log, " unexpectedly opened file %s", filenames[i]); + } + } + for (i = 0; i < MAX_RATES; i++) + if (fs[i] != NULL) + sf_close(fs[i]); + + return samples; +#else + spa_log_error(pl->log, "compiled without sndfile support, can't load samples: " + "using dirac impulse"); + float *samples = calloc(1, sizeof(float)); + samples[0] = gain; + *n_samples = 1; + return samples; +#endif +} + +static float *create_hilbert(struct plugin *pl, const char *filename, float gain, int rate, float delay_sec, int offset, + int length, int *n_samples) +{ + float *samples, v; + int i, n, h; + int delay = (int) (delay_sec * rate); + + if (length <= 0) + length = 1024; + + length -= SPA_MIN(offset, length); + + n = delay + length; + if (n == 0) + return NULL; + + samples = calloc(n, sizeof(float)); + if (samples == NULL) + return NULL; + + gain *= 2 / (float)M_PI; + h = length / 2; + for (i = 1; i < h; i += 2) { + v = (gain / i) * (0.43f + 0.57f * cosf(i * (float)M_PI / h)); + samples[delay + h + i] = -v; + samples[delay + h - i] = v; + } + *n_samples = n; + return samples; +} + +static float *create_dirac(struct plugin *pl, const char *filename, float gain, int rate, float delay_sec, int offset, + int length, int *n_samples) +{ + float *samples; + int delay = (int) (delay_sec * rate); + int n; + + n = delay + 1; + + samples = calloc(n, sizeof(float)); + if (samples == NULL) + return NULL; + + samples[delay] = gain; + + *n_samples = n; + return samples; +} + +static float *resample_buffer(struct plugin *pl, float *samples, int *n_samples, + unsigned long in_rate, unsigned long out_rate, uint32_t quality) +{ +#ifdef HAVE_SPA_PLUGINS + uint32_t in_len, out_len, total_out = 0; + int out_n_samples; + float *out_samples, *out_buf, *in_buf; + struct resample r; + int res; + + spa_zero(r); + r.channels = 1; + r.i_rate = in_rate; + r.o_rate = out_rate; + r.cpu_flags = pl->dsp->cpu_flags; + r.quality = quality; + if ((res = resample_native_init(&r)) < 0) { + spa_log_error(pl->log, "resampling failed: %s", spa_strerror(res)); + errno = -res; + return NULL; + } + + out_n_samples = SPA_ROUND_UP(*n_samples * out_rate, in_rate) / in_rate; + out_samples = calloc(out_n_samples, sizeof(float)); + if (out_samples == NULL) + goto error; + + in_len = *n_samples; + in_buf = samples; + out_len = out_n_samples; + out_buf = out_samples; + + spa_log_info(pl->log, "Resampling filter: rate: %lu => %lu, n_samples: %u => %u, q:%u", + in_rate, out_rate, in_len, out_len, quality); + + resample_process(&r, (void*)&in_buf, &in_len, (void*)&out_buf, &out_len); + spa_log_debug(pl->log, "resampled: %u -> %u samples", in_len, out_len); + total_out += out_len; + + in_len = resample_delay(&r); + in_buf = calloc(in_len, sizeof(float)); + if (in_buf == NULL) + goto error; + + out_buf = out_samples + total_out; + out_len = out_n_samples - total_out; + + spa_log_debug(pl->log, "flushing resampler: %u in %u out", in_len, out_len); + resample_process(&r, (void*)&in_buf, &in_len, (void*)&out_buf, &out_len); + spa_log_debug(pl->log, "flushed: %u -> %u samples", in_len, out_len); + total_out += out_len; + + free(in_buf); + free(samples); + resample_free(&r); + + *n_samples = total_out; + + float gain = (float)in_rate / (float)out_rate; + for (uint32_t i = 0; i < total_out; i++) + out_samples[i] = out_samples[i] * gain; + + return out_samples; + +error: + resample_free(&r); + free(samples); + free(out_samples); + return NULL; +#else + spa_log_error(impl->log, "compiled without spa-plugins support, can't resample"); + float *out_samples = calloc(*n_samples, sizeof(float)); + memcpy(out_samples, samples, *n_samples * sizeof(float)); + return out_samples; +#endif +} + +static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, + unsigned long SampleRate, int index, const char *config) +{ + struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); + struct convolver_impl *impl; + float *samples; + int offset = 0, length = 0, channel = index, n_samples = 0, len; + uint32_t i = 0; + struct spa_json it[2]; + const char *val; + char key[256], v[256]; + char *filenames[MAX_RATES] = { 0 }; + int blocksize = 0, tailsize = 0; + int resample_quality = RESAMPLE_DEFAULT_QUALITY; + float gain = 1.0f, delay = 0.0f; + unsigned long rate; + + errno = EINVAL; + if (config == NULL) { + spa_log_error(pl->log, "convolver: requires a config section"); + return NULL; + } + + if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { + spa_log_error(pl->log, "convolver:config must be an object"); + return NULL; + } + + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { + if (spa_streq(key, "blocksize")) { + if (spa_json_parse_int(val, len, &blocksize) <= 0) { + spa_log_error(pl->log, "convolver:blocksize requires a number"); + return NULL; + } + } + else if (spa_streq(key, "tailsize")) { + if (spa_json_parse_int(val, len, &tailsize) <= 0) { + spa_log_error(pl->log, "convolver:tailsize requires a number"); + return NULL; + } + } + else if (spa_streq(key, "gain")) { + if (spa_json_parse_float(val, len, &gain) <= 0) { + spa_log_error(pl->log, "convolver:gain requires a number"); + return NULL; + } + } + else if (spa_streq(key, "delay")) { + int delay_i; + if (spa_json_parse_int(val, len, &delay_i) > 0) { + delay = delay_i / (float)SampleRate; + } else if (spa_json_parse_float(val, len, &delay) <= 0) { + spa_log_error(pl->log, "convolver:delay requires a number"); + return NULL; + } + } + else if (spa_streq(key, "filename")) { + if (spa_json_is_array(val, len)) { + spa_json_enter(&it[0], &it[1]); + while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && + i < SPA_N_ELEMENTS(filenames)) { + filenames[i] = strdup(v); + i++; + } + } + else if (spa_json_parse_stringn(val, len, v, sizeof(v)) <= 0) { + spa_log_error(pl->log, "convolver:filename requires a string or an array"); + return NULL; + } else { + filenames[0] = strdup(v); + } + } + else if (spa_streq(key, "offset")) { + if (spa_json_parse_int(val, len, &offset) <= 0) { + spa_log_error(pl->log, "convolver:offset requires a number"); + return NULL; + } + } + else if (spa_streq(key, "length")) { + if (spa_json_parse_int(val, len, &length) <= 0) { + spa_log_error(pl->log, "convolver:length requires a number"); + return NULL; + } + } + else if (spa_streq(key, "channel")) { + if (spa_json_parse_int(val, len, &channel) <= 0) { + spa_log_error(pl->log, "convolver:channel requires a number"); + return NULL; + } + } + else if (spa_streq(key, "resample_quality")) { + if (spa_json_parse_int(val, len, &resample_quality) <= 0) { + spa_log_error(pl->log, "convolver:resample_quality requires a number"); + return NULL; + } + } + else { + spa_log_warn(pl->log, "convolver: ignoring config key: '%s'", key); + } + } + if (filenames[0] == NULL) { + spa_log_error(pl->log, "convolver:filename was not given"); + return NULL; + } + + if (delay < 0.0f) + delay = 0.0f; + if (offset < 0) + offset = 0; + + if (spa_streq(filenames[0], "/hilbert")) { + samples = create_hilbert(pl, filenames[0], gain, SampleRate, delay, offset, + length, &n_samples); + } else if (spa_streq(filenames[0], "/dirac")) { + samples = create_dirac(pl, filenames[0], gain, SampleRate, delay, offset, + length, &n_samples); + } else { + rate = SampleRate; + samples = read_closest(pl, filenames, gain, delay, offset, + length, channel, &rate, &n_samples); + if (samples != NULL && rate != SampleRate) { + samples = resample_buffer(pl, samples, &n_samples, + rate, SampleRate, resample_quality); + } + } + + for (i = 0; i < MAX_RATES; i++) + if (filenames[i]) + free(filenames[i]); + + if (samples == NULL) { + errno = ENOENT; + return NULL; + } + + if (blocksize <= 0) + blocksize = SPA_CLAMP(n_samples, 64, 256); + if (tailsize <= 0) + tailsize = SPA_CLAMP(4096, blocksize, 32768); + + spa_log_info(pl->log, "using n_samples:%u %d:%d blocksize delay:%f", n_samples, + blocksize, tailsize, delay); + + impl = calloc(1, sizeof(*impl)); + if (impl == NULL) + goto error; + + impl->plugin = pl; + impl->log = pl->log; + impl->dsp = pl->dsp; + impl->rate = SampleRate; + + impl->conv = convolver_new(impl->dsp, blocksize, tailsize, samples, n_samples); + if (impl->conv == NULL) + goto error; + + free(samples); + + return impl; +error: + free(samples); + free(impl); + return NULL; +} + +static void convolver_connect_port(void * Instance, unsigned long Port, + float * DataLocation) +{ + struct convolver_impl *impl = Instance; + impl->port[Port] = DataLocation; +} + +static void convolver_cleanup(void * Instance) +{ + struct convolver_impl *impl = Instance; + if (impl->conv) + convolver_free(impl->conv); + free(impl); +} + +static struct spa_fga_port convolve_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, +}; + +static void convolver_deactivate(void * Instance) +{ + struct convolver_impl *impl = Instance; + convolver_reset(impl->conv); +} + +static void convolve_run(void * Instance, unsigned long SampleCount) +{ + struct convolver_impl *impl = Instance; + if (impl->port[1] != NULL && impl->port[0] != NULL) + convolver_run(impl->conv, impl->port[1], impl->port[0], SampleCount); +} + +static const struct spa_fga_descriptor convolve_desc = { + .name = "convolver", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = 2, + .ports = convolve_ports, + + .instantiate = convolver_instantiate, + .connect_port = convolver_connect_port, + .deactivate = convolver_deactivate, + .run = convolve_run, + .cleanup = convolver_cleanup, +}; + +/** delay */ +struct delay_impl { + struct plugin *plugin; + + struct spa_fga_dsp *dsp; + struct spa_log *log; + + unsigned long rate; + float *port[4]; + + float delay; + uint32_t delay_samples; + uint32_t buffer_samples; + float *buffer; + uint32_t ptr; +}; + +static void delay_cleanup(void * Instance) +{ + struct delay_impl *impl = Instance; + free(impl->buffer); + free(impl); +} + +static void *delay_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, + unsigned long SampleRate, int index, const char *config) +{ + struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); + struct delay_impl *impl; + struct spa_json it[1]; + const char *val; + char key[256]; + float max_delay = 1.0f; + int len; + + if (config == NULL) { + spa_log_error(pl->log, "delay: requires a config section"); + errno = EINVAL; + return NULL; + } + + if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { + spa_log_error(pl->log, "delay:config must be an object"); + return NULL; + } + + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { + if (spa_streq(key, "max-delay")) { + if (spa_json_parse_float(val, len, &max_delay) <= 0) { + spa_log_error(pl->log, "delay:max-delay requires a number"); + return NULL; + } + } else { + spa_log_warn(pl->log, "delay: ignoring config key: '%s'", key); + } + } + if (max_delay <= 0.0f) + max_delay = 1.0f; + + impl = calloc(1, sizeof(*impl)); + if (impl == NULL) + return NULL; + + impl->plugin = pl; + impl->dsp = pl->dsp; + impl->log = pl->log; + impl->rate = SampleRate; + impl->buffer_samples = SPA_ROUND_UP_N((uint32_t)(max_delay * impl->rate), 64); + spa_log_info(impl->log, "max-delay:%f seconds rate:%lu samples:%d", max_delay, impl->rate, impl->buffer_samples); + + impl->buffer = calloc(impl->buffer_samples * 2 + 64, sizeof(float)); + if (impl->buffer == NULL) { + delay_cleanup(impl); + return NULL; + } + return impl; +} + +static void delay_connect_port(void * Instance, unsigned long Port, + float * DataLocation) +{ + struct delay_impl *impl = Instance; + if (Port > 2) + return; + impl->port[Port] = DataLocation; +} + +static void delay_run(void * Instance, unsigned long SampleCount) +{ + struct delay_impl *impl = Instance; + float *in = impl->port[1], *out = impl->port[0]; + float delay = impl->port[2][0]; + + if (in == NULL || out == NULL) + return; + + if (delay != impl->delay) { + impl->delay_samples = SPA_CLAMP((uint32_t)(delay * impl->rate), 0u, impl->buffer_samples-1); + impl->delay = delay; + } + spa_fga_dsp_delay(impl->dsp, impl->buffer, &impl->ptr, impl->buffer_samples, + impl->delay_samples, out, in, SampleCount); +} + +static struct spa_fga_port delay_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "Delay (s)", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.0f, .min = 0.0f, .max = 100.0f + }, +}; + +static const struct spa_fga_descriptor delay_desc = { + .name = "delay", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = 3, + .ports = delay_ports, + + .instantiate = delay_instantiate, + .connect_port = delay_connect_port, + .run = delay_run, + .cleanup = delay_cleanup, +}; + +/* invert */ +static void invert_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float *in = impl->port[1], *out = impl->port[0]; + unsigned long n; + for (n = 0; n < SampleCount; n++) + out[n] = -in[n]; +} + +static struct spa_fga_port invert_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, +}; + +static const struct spa_fga_descriptor invert_desc = { + .name = "invert", + + .n_ports = 2, + .ports = invert_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = invert_run, + .cleanup = builtin_cleanup, +}; + +/* clamp */ +static void clamp_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float min = impl->port[4][0], max = impl->port[5][0]; + float *in = impl->port[1], *out = impl->port[0]; + float *ctrl = impl->port[3], *notify = impl->port[2]; + + if (in != NULL && out != NULL) { + unsigned long n; + for (n = 0; n < SampleCount; n++) + out[n] = SPA_CLAMPF(in[n], min, max); + } + if (ctrl != NULL && notify != NULL) + notify[0] = SPA_CLAMPF(ctrl[0], min, max); +} + +static struct spa_fga_port clamp_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "Notify", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 3, + .name = "Control", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 4, + .name = "Min", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.0f, .min = -100.0f, .max = 100.0f + }, + { .index = 5, + .name = "Max", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0f, .min = -100.0f, .max = 100.0f + }, +}; + +static const struct spa_fga_descriptor clamp_desc = { + .name = "clamp", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(clamp_ports), + .ports = clamp_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = clamp_run, + .cleanup = builtin_cleanup, +}; + +/* linear */ +static void linear_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float mult = impl->port[4][0], add = impl->port[5][0]; + float *in = impl->port[1], *out = impl->port[0]; + float *ctrl = impl->port[3], *notify = impl->port[2]; + + if (in != NULL && out != NULL) + spa_fga_dsp_linear(impl->dsp, out, in, mult, add, SampleCount); + + if (ctrl != NULL && notify != NULL) + notify[0] = ctrl[0] * mult + add; +} + +static struct spa_fga_port linear_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "Notify", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 3, + .name = "Control", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 4, + .name = "Mult", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0f, .min = -10.0f, .max = 10.0f + }, + { .index = 5, + .name = "Add", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.0f, .min = -10.0f, .max = 10.0f + }, +}; + +static const struct spa_fga_descriptor linear_desc = { + .name = "linear", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(linear_ports), + .ports = linear_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = linear_run, + .cleanup = builtin_cleanup, +}; + + +/* reciprocal */ +static void recip_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float *in = impl->port[1], *out = impl->port[0]; + float *ctrl = impl->port[3], *notify = impl->port[2]; + + if (in != NULL && out != NULL) { + unsigned long n; + for (n = 0; n < SampleCount; n++) { + if (in[0] == 0.0f) + out[n] = 0.0f; + else + out[n] = 1.0f / in[n]; + } + } + if (ctrl != NULL && notify != NULL) { + if (ctrl[0] == 0.0f) + notify[0] = 0.0f; + else + notify[0] = 1.0f / ctrl[0]; + } +} + +static struct spa_fga_port recip_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "Notify", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 3, + .name = "Control", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + }, +}; + +static const struct spa_fga_descriptor recip_desc = { + .name = "recip", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(recip_ports), + .ports = recip_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = recip_run, + .cleanup = builtin_cleanup, +}; + +/* exp */ +static void exp_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float base = impl->port[4][0]; + float *in = impl->port[1], *out = impl->port[0]; + float *ctrl = impl->port[3], *notify = impl->port[2]; + + if (in != NULL && out != NULL) { + unsigned long n; + for (n = 0; n < SampleCount; n++) + out[n] = powf(base, in[n]); + } + if (ctrl != NULL && notify != NULL) + notify[0] = powf(base, ctrl[0]); +} + +static struct spa_fga_port exp_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "Notify", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 3, + .name = "Control", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 4, + .name = "Base", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = (float)M_E, .min = -10.0f, .max = 10.0f + }, +}; + +static const struct spa_fga_descriptor exp_desc = { + .name = "exp", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(exp_ports), + .ports = exp_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = exp_run, + .cleanup = builtin_cleanup, +}; + +/* log */ +static void log_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float base = impl->port[4][0]; + float m1 = impl->port[5][0]; + float m2 = impl->port[6][0]; + float *in = impl->port[1], *out = impl->port[0]; + float *ctrl = impl->port[3], *notify = impl->port[2]; + float lb = log2f(base); + + if (in != NULL && out != NULL) { + unsigned long n; + for (n = 0; n < SampleCount; n++) + out[n] = m2 * log2f(fabsf(in[n] * m1)) / lb; + } + if (ctrl != NULL && notify != NULL) + notify[0] = m2 * log2f(fabsf(ctrl[0] * m1)) / lb; +} + +static struct spa_fga_port log_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "Notify", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 3, + .name = "Control", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 4, + .name = "Base", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = (float)M_E, .min = 2.0f, .max = 100.0f + }, + { .index = 5, + .name = "M1", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0f, .min = -10.0f, .max = 10.0f + }, + { .index = 6, + .name = "M2", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0f, .min = -10.0f, .max = 10.0f + }, +}; + +static const struct spa_fga_descriptor log_desc = { + .name = "log", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(log_ports), + .ports = log_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = log_run, + .cleanup = builtin_cleanup, +}; + +/* mult */ +static void mult_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + int i, n_src = 0; + float *out = impl->port[0]; + const float *src[8]; + + if (out == NULL) + return; + + for (i = 0; i < 8; i++) { + float *in = impl->port[1+i]; + + if (in == NULL) + continue; + + src[n_src++] = in; + } + spa_fga_dsp_mult(impl->dsp, out, src, n_src, SampleCount); +} + +static struct spa_fga_port mult_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In 1", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "In 2", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 3, + .name = "In 3", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 4, + .name = "In 4", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 5, + .name = "In 5", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 6, + .name = "In 6", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 7, + .name = "In 7", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 8, + .name = "In 8", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, +}; + +static const struct spa_fga_descriptor mult_desc = { + .name = "mult", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(mult_ports), + .ports = mult_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = mult_run, + .cleanup = builtin_cleanup, +}; + +#define M_PI_M2f (float)(M_PI+M_PI) + +/* sine */ +static void sine_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float *out = impl->port[0]; + float *notify = impl->port[1]; + float freq = impl->port[2][0]; + float ampl = impl->port[3][0]; + float offs = impl->port[5][0]; + unsigned long n; + + for (n = 0; n < SampleCount; n++) { + if (out != NULL) + out[n] = sinf(impl->accum) * ampl + offs; + if (notify != NULL && n == 0) + notify[0] = sinf(impl->accum) * ampl + offs; + + impl->accum += M_PI_M2f * freq / impl->rate; + if (impl->accum >= M_PI_M2f) + impl->accum -= M_PI_M2f; + } +} + +static struct spa_fga_port sine_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "Notify", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 2, + .name = "Freq", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 440.0f, .min = 0.0f, .max = 1000000.0f + }, + { .index = 3, + .name = "Ampl", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 1.0, .min = 0.0f, .max = 10.0f + }, + { .index = 4, + .name = "Phase", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.0f, .min = (float)-M_PI, .max = (float)M_PI + }, + { .index = 5, + .name = "Offset", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.0f, .min = -10.0f, .max = 10.0f + }, +}; + +static const struct spa_fga_descriptor sine_desc = { + .name = "sine", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(sine_ports), + .ports = sine_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = sine_run, + .cleanup = builtin_cleanup, +}; + +#define PARAM_EQ_MAX 64 +struct param_eq_impl { + struct plugin *plugin; + + struct spa_fga_dsp *dsp; + struct spa_log *log; + + unsigned long rate; + float *port[8*2]; + + uint32_t n_bq; + struct biquad bq[PARAM_EQ_MAX * 8]; +}; + +static int load_eq_bands(struct plugin *pl, const char *filename, int rate, + struct biquad *bq, uint32_t max_bq, uint32_t *n_bq) +{ + FILE *f = NULL; + char *line = NULL; + ssize_t nread; + size_t linelen; + uint32_t n = 0; + char filter_type[4]; + char filter[4]; + char freq[9], q[7], gain[7]; + float vf, vg, vq; + int res = 0; + + if ((f = fopen(filename, "r")) == NULL) { + res = -errno; + spa_log_error(pl->log, "failed to open param_eq file '%s': %m", filename); + goto exit; + } + /* + * Read the Preamp gain line. + * Example: Preamp: -6.8 dB + * + * When a pre-amp gain is required, which is usually the case when + * applying EQ, we need to modify the first EQ band to apply a + * bq_highshelf filter at frequency 0 Hz with the provided negative + * gain. + * + * Pre-amp gain is always negative to offset the effect of possible + * clipping introduced by the amplification resulting from EQ. + */ + nread = getline(&line, &linelen, f); + if (nread != -1 && sscanf(line, "%*s %6s %*s", gain) == 1) { + if (spa_json_parse_float(gain, strlen(gain), &vg)) { + spa_log_info(pl->log, "%d %s freq:0 q:1.0 gain:%f", n, + bq_name_from_type(BQ_HIGHSHELF), vg); + biquad_set(&bq[n++], BQ_HIGHSHELF, 0.0f, 1.0f, vg); + } + } + /* Read the filter bands */ + while ((nread = getline(&line, &linelen, f)) != -1) { + if (n == PARAM_EQ_MAX) { + res = -ENOSPC; + goto exit; + } + /* + * On field widths: + * - filter can be ON or OFF + * - filter type can be PK, LSC, HSC + * - freq can be at most 5 decimal digits + * - gain can be -xy.z + * - Q can be x.y00 + * + * Use a field width of 6 for gain and Q to account for any + * possible zeros. + */ + if (sscanf(line, "%*s %*d: %3s %3s %*s %8s %*s %*s %6s %*s %*c %6s", + filter, filter_type, freq, gain, q) == 5) { + if (strcmp(filter, "ON") == 0) { + int type; + + if (spa_streq(filter_type, "PK")) + type = BQ_PEAKING; + else if (spa_streq(filter_type, "LSC")) + type = BQ_LOWSHELF; + else if (spa_streq(filter_type, "HSC")) + type = BQ_HIGHSHELF; + else + continue; + + if (spa_json_parse_float(freq, strlen(freq), &vf) && + spa_json_parse_float(gain, strlen(gain), &vg) && + spa_json_parse_float(q, strlen(q), &vq)) { + spa_log_info(pl->log, "%d %s freq:%f q:%f gain:%f", n, + bq_name_from_type(type), vf, vq, vg); + biquad_set(&bq[n++], type, vf * 2.0f / rate, vq, vg); + } + } + } + } + *n_bq = n; +exit: + if (f) + fclose(f); + return res; +} + + + +/* + * [ + * { type=bq_peaking freq=21 gain=6.7 q=1.100 } + * { type=bq_peaking freq=85 gain=6.9 q=3.000 } + * { type=bq_peaking freq=110 gain=-2.6 q=2.700 } + * { type=bq_peaking freq=210 gain=5.9 q=2.100 } + * { type=bq_peaking freq=710 gain=-1.0 q=0.600 } + * { type=bq_peaking freq=1600 gain=2.3 q=2.700 } + * ] + */ +static int parse_filters(struct plugin *pl, struct spa_json *iter, int rate, + struct biquad *bq, uint32_t max_bq, uint32_t *n_bq) +{ + struct spa_json it[1]; + const char *val; + char key[256]; + char type_str[17]; + int len; + uint32_t n = 0; + + while (spa_json_enter_object(iter, &it[0]) > 0) { + float freq = 0.0f, gain = 0.0f, q = 1.0f; + int type = BQ_NONE; + + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { + if (spa_streq(key, "type")) { + if (spa_json_parse_stringn(val, len, type_str, sizeof(type_str)) <= 0) { + spa_log_error(pl->log, "param_eq:type requires a string"); + return -EINVAL; + } + type = bq_type_from_name(type_str); + } + else if (spa_streq(key, "freq")) { + if (spa_json_parse_float(val, len, &freq) <= 0) { + spa_log_error(pl->log, "param_eq:rate requires a number"); + return -EINVAL; + } + } + else if (spa_streq(key, "q")) { + if (spa_json_parse_float(val, len, &q) <= 0) { + spa_log_error(pl->log, "param_eq:q requires a float"); + return -EINVAL; + } + } + else if (spa_streq(key, "gain")) { + if (spa_json_parse_float(val, len, &gain) <= 0) { + spa_log_error(pl->log, "param_eq:gain requires a float"); + return -EINVAL; + } + } + else { + spa_log_warn(pl->log, "param_eq: ignoring filter key: '%s'", key); + } + } + if (n == max_bq) + return -ENOSPC; + + spa_log_info(pl->log, "%d %s freq:%f q:%f gain:%f", n, + bq_name_from_type(type), freq, q, gain); + biquad_set(&bq[n++], type, freq * 2 / rate, q, gain); + } + *n_bq = n; + return 0; +} + +/* + * { + * filename = "...", + * filenameX = "...", # to load channel X + * filters = [ ... ] + * filtersX = [ ... ] # to load channel X + * } + */ +static void *param_eq_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, + unsigned long SampleRate, int index, const char *config) +{ + struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); + struct spa_json it[3]; + const char *val; + char key[256], filename[PATH_MAX]; + int len, res; + struct param_eq_impl *impl; + uint32_t i, n_bq = 0; + + if (config == NULL) { + spa_log_error(pl->log, "param_eq: requires a config section"); + errno = EINVAL; + return NULL; + } + + if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { + spa_log_error(pl->log, "param_eq: config must be an object"); + return NULL; + } + + impl = calloc(1, sizeof(*impl)); + if (impl == NULL) + return NULL; + + impl->plugin = pl; + impl->dsp = pl->dsp; + impl->log = pl->log; + impl->rate = SampleRate; + for (i = 0; i < SPA_N_ELEMENTS(impl->bq); i++) + biquad_set(&impl->bq[i], BQ_NONE, 0.0f, 0.0f, 0.0f); + + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { + int32_t idx = 0; + struct biquad *bq = impl->bq; + + if (spa_strstartswith(key, "filename")) { + if (spa_json_parse_stringn(val, len, filename, sizeof(filename)) <= 0) { + spa_log_error(impl->log, "param_eq: filename requires a string"); + goto error; + } + if (spa_atoi32(key+8, &idx, 0)) + bq = &impl->bq[(SPA_CLAMP(idx, 1, 8) - 1) * PARAM_EQ_MAX]; + + res = load_eq_bands(pl, filename, impl->rate, bq, PARAM_EQ_MAX, &n_bq); + if (res < 0) { + spa_log_error(impl->log, "param_eq: failed to parse configuration from '%s'", filename); + goto error; + } + spa_log_info(impl->log, "loaded %d biquads for channel %d from %s", n_bq, idx, filename); + impl->n_bq = SPA_MAX(impl->n_bq, n_bq); + } + else if (spa_strstartswith(key, "filters")) { + if (!spa_json_is_array(val, len)) { + spa_log_error(impl->log, "param_eq:filters require an array"); + goto error; + } + spa_json_enter(&it[0], &it[1]); + + if (spa_atoi32(key+7, &idx, 0)) + bq = &impl->bq[(SPA_CLAMP(idx, 1, 8) - 1) * PARAM_EQ_MAX]; + + res = parse_filters(pl, &it[1], impl->rate, bq, PARAM_EQ_MAX, &n_bq); + if (res < 0) { + spa_log_error(impl->log, "param_eq: failed to parse configuration"); + goto error; + } + spa_log_info(impl->log, "parsed %d biquads for channel %d", n_bq, idx); + impl->n_bq = SPA_MAX(impl->n_bq, n_bq); + } else { + spa_log_warn(impl->log, "param_eq: ignoring config key: '%s'", key); + } + if (idx == 0) { + for (i = 1; i < 8; i++) + memcpy(&impl->bq[i*PARAM_EQ_MAX], impl->bq, + sizeof(struct biquad) * PARAM_EQ_MAX); + } + } + return impl; +error: + free(impl); + return NULL; +} + +static void param_eq_connect_port(void * Instance, unsigned long Port, + float * DataLocation) +{ + struct param_eq_impl *impl = Instance; + impl->port[Port] = DataLocation; +} + +static void param_eq_run(void * Instance, unsigned long SampleCount) +{ + struct param_eq_impl *impl = Instance; + spa_fga_dsp_biquad_run(impl->dsp, impl->bq, impl->n_bq, PARAM_EQ_MAX, + &impl->port[8], (const float**)impl->port, 8, SampleCount); +} + +static struct spa_fga_port param_eq_ports[] = { + { .index = 0, + .name = "In 1", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In 2", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "In 3", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 3, + .name = "In 4", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 4, + .name = "In 5", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 5, + .name = "In 6", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 6, + .name = "In 7", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 7, + .name = "In 8", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + + { .index = 8, + .name = "Out 1", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 9, + .name = "Out 2", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 10, + .name = "Out 3", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 11, + .name = "Out 4", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 12, + .name = "Out 5", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 13, + .name = "Out 6", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 14, + .name = "Out 7", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 15, + .name = "Out 8", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, +}; + +static const struct spa_fga_descriptor param_eq_desc = { + .name = "param_eq", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(param_eq_ports), + .ports = param_eq_ports, + + .instantiate = param_eq_instantiate, + .connect_port = param_eq_connect_port, + .run = param_eq_run, + .cleanup = free, +}; + +/** max */ +static void max_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float *out = impl->port[0], *in1 = impl->port[1], *in2 = impl->port[2]; + unsigned long n; + + if (out == NULL) + return; + + if (in1 != NULL && in2 != NULL) { + for (n = 0; n < SampleCount; n++) + out[n] = SPA_MAX(in1[n], in2[n]); + } else if (in1 != NULL) { + for (n = 0; n < SampleCount; n++) + out[n] = in1[n]; + } else if (in2 != NULL) { + for (n = 0; n < SampleCount; n++) + out[n] = in2[n]; + } else { + for (n = 0; n < SampleCount; n++) + out[n] = 0.0f; + } +} + +static struct spa_fga_port max_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + + { .index = 1, + .name = "In 1", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "In 2", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + } +}; + +static const struct spa_fga_descriptor max_desc = { + .name = "max", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(max_ports), + .ports = max_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = max_run, + .cleanup = builtin_cleanup, +}; + +/* DC blocking */ +struct dcblock { + float xm1; + float ym1; +}; + +struct dcblock_impl { + struct plugin *plugin; + + struct spa_fga_dsp *dsp; + struct spa_log *log; + + unsigned long rate; + float *port[17]; + + struct dcblock dc[8]; +}; + +static void *dcblock_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, + unsigned long SampleRate, int index, const char *config) +{ + struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); + struct dcblock_impl *impl; + + impl = calloc(1, sizeof(*impl)); + if (impl == NULL) + return NULL; + + impl->plugin = pl; + impl->dsp = pl->dsp; + impl->log = pl->log; + impl->rate = SampleRate; + return impl; +} + +static void dcblock_run_n(struct dcblock dc[], float *dst[], const float *src[], + uint32_t n_src, float R, uint32_t n_samples) +{ + float x, y; + uint32_t i, n; + + for (i = 0; i < n_src; i++) { + const float *in = src[i]; + float *out = dst[i]; + float xm1 = dc[i].xm1; + float ym1 = dc[i].ym1; + + if (out == NULL || in == NULL) + continue; + + for (n = 0; n < n_samples; n++) { + x = in[n]; + y = x - xm1 + R * ym1; + xm1 = x; + ym1 = y; + out[n] = y; + } + dc[i].xm1 = xm1; + dc[i].ym1 = ym1; + } +} + +static void dcblock_run(void * Instance, unsigned long SampleCount) +{ + struct dcblock_impl *impl = Instance; + float R = impl->port[16][0]; + dcblock_run_n(impl->dc, &impl->port[8], (const float**)&impl->port[0], 8, + R, SampleCount); +} + +static void dcblock_connect_port(void * Instance, unsigned long Port, + float * DataLocation) +{ + struct dcblock_impl *impl = Instance; + impl->port[Port] = DataLocation; +} + +static struct spa_fga_port dcblock_ports[] = { + { .index = 0, + .name = "In 1", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In 2", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 2, + .name = "In 3", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 3, + .name = "In 4", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 4, + .name = "In 5", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 5, + .name = "In 6", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 6, + .name = "In 7", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 7, + .name = "In 8", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + + { .index = 8, + .name = "Out 1", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 9, + .name = "Out 2", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 10, + .name = "Out 3", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 11, + .name = "Out 4", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 12, + .name = "Out 5", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 13, + .name = "Out 6", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 14, + .name = "Out 7", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 15, + .name = "Out 8", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 16, + .name = "R", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = 0.995f, .min = 0.0f, .max = 1.0f + }, +}; + +static const struct spa_fga_descriptor dcblock_desc = { + .name = "dcblock", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(dcblock_ports), + .ports = dcblock_ports, + + .instantiate = dcblock_instantiate, + .connect_port = dcblock_connect_port, + .run = dcblock_run, + .cleanup = free, +}; + +/* ramp */ +static struct spa_fga_port ramp_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "Start", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 2, + .name = "Stop", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 3, + .name = "Current", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 4, + .name = "Duration (s)", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + }, +}; + +static void ramp_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float *out = impl->port[0]; + float start = impl->port[1][0]; + float stop = impl->port[2][0], last; + float *current = impl->port[3]; + float duration = impl->port[4][0]; + float inc = (stop - start) / (duration * impl->rate); + uint32_t n; + + last = stop; + if (inc < 0.f) + SPA_SWAP(start, stop); + + if (out != NULL) { + if (impl->accum == last) { + for (n = 0; n < SampleCount; n++) + out[n] = last; + } else { + for (n = 0; n < SampleCount; n++) { + out[n] = impl->accum; + impl->accum = SPA_CLAMP(impl->accum + inc, start, stop); + } + } + } else { + impl->accum = SPA_CLAMP(impl->accum + SampleCount * inc, start, stop); + } + if (current) + current[0] = impl->accum; +} + +static const struct spa_fga_descriptor ramp_desc = { + .name = "ramp", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(ramp_ports), + .ports = ramp_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = ramp_run, + .cleanup = builtin_cleanup, +}; + +/* abs */ +static void abs_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float *in = impl->port[1], *out = impl->port[0]; + + if (in != NULL && out != NULL) { + unsigned long n; + for (n = 0; n < SampleCount; n++) { + out[n] = SPA_ABS(in[n]); + } + } +} + +static struct spa_fga_port abs_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, +}; + +static const struct spa_fga_descriptor abs_desc = { + .name = "abs", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(abs_ports), + .ports = abs_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = abs_run, + .cleanup = builtin_cleanup, +}; + +/* sqrt */ +static void sqrt_run(void * Instance, unsigned long SampleCount) +{ + struct builtin *impl = Instance; + float *in = impl->port[1], *out = impl->port[0]; + + if (in != NULL && out != NULL) { + unsigned long n; + for (n = 0; n < SampleCount; n++) { + if (in[n] <= 0.0f) + out[n] = 0.0f; + else + out[n] = sqrtf(in[n]); + } + } +} + +static struct spa_fga_port sqrt_ports[] = { + { .index = 0, + .name = "Out", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = 1, + .name = "In", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, +}; + +static const struct spa_fga_descriptor sqrt_desc = { + .name = "sqrt", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .n_ports = SPA_N_ELEMENTS(sqrt_ports), + .ports = sqrt_ports, + + .instantiate = builtin_instantiate, + .connect_port = builtin_connect_port, + .run = sqrt_run, + .cleanup = builtin_cleanup, +}; + +static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) +{ + switch(Index) { + case 0: + return &mixer_desc; + case 1: + return &bq_lowpass_desc; + case 2: + return &bq_highpass_desc; + case 3: + return &bq_bandpass_desc; + case 4: + return &bq_lowshelf_desc; + case 5: + return &bq_highshelf_desc; + case 6: + return &bq_peaking_desc; + case 7: + return &bq_notch_desc; + case 8: + return &bq_allpass_desc; + case 9: + return ©_desc; + case 10: + return &convolve_desc; + case 11: + return &delay_desc; + case 12: + return &invert_desc; + case 13: + return &bq_raw_desc; + case 14: + return &clamp_desc; + case 15: + return &linear_desc; + case 16: + return &recip_desc; + case 17: + return &exp_desc; + case 18: + return &log_desc; + case 19: + return &mult_desc; + case 20: + return &sine_desc; + case 21: + return ¶m_eq_desc; + case 22: + return &max_desc; + case 23: + return &dcblock_desc; + case 24: + return &ramp_desc; + case 25: + return &abs_desc; + case 26: + return &sqrt_desc; + } + return NULL; +} + +static const struct spa_fga_descriptor *builtin_plugin_make_desc(void *plugin, const char *name) +{ + unsigned long i; + for (i = 0; ;i++) { + const struct spa_fga_descriptor *d = builtin_descriptor(i); + if (d == NULL) + break; + if (spa_streq(d->name, name)) + return d; + } + return NULL; +} + +static struct spa_fga_plugin_methods impl_plugin = { + SPA_VERSION_FGA_PLUGIN_METHODS, + .make_desc = builtin_plugin_make_desc, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct plugin *impl; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + impl = (struct plugin *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin)) + *interface = &impl->plugin; + 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 plugin); +} + +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 plugin *impl; + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + impl = (struct plugin *) handle; + + impl->plugin.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, + SPA_VERSION_FGA_PLUGIN, + &impl_plugin, impl); + + impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + impl->dsp = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioDSP); + + for (uint32_t 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, "filter.graph.audio.dsp")) + sscanf(s, "pointer:%p", &impl->dsp); + } + if (impl->dsp == NULL) { + spa_log_error(impl->log, "%p: could not find DSP functions", impl); + return -EINVAL; + } + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin,}, +}; + +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 struct spa_handle_factory spa_fga_plugin_builtin_factory = { + SPA_VERSION_HANDLE_FACTORY, + "filter.graph.plugin.builtin", + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; + +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_fga_plugin_builtin_factory; + break; + default: + return 0; + } + (*index)++; + return 1; +} diff --git a/src/modules/module-filter-chain/convolver.c b/spa/plugins/filter-graph/convolver.c similarity index 51% rename from src/modules/module-filter-chain/convolver.c rename to spa/plugins/filter-graph/convolver.c index 3aa7230c..e5bd47b3 100644 --- a/src/modules/module-filter-chain/convolver.c +++ b/spa/plugins/filter-graph/convolver.c @@ -11,8 +11,6 @@ #include <math.h> struct convolver1 { - struct dsp_ops *dsp; - int blockSize; int segSize; int segCount; @@ -37,37 +35,6 @@ struct convolver1 { float scale; }; -static void *fft_alloc(int size) -{ - size_t nb_bytes = size * sizeof(float); -#define ALIGNMENT 64 - void *p, *p0 = malloc(nb_bytes + ALIGNMENT); - if (!p0) - return (void *)0; - p = (void *)(((size_t)p0 + ALIGNMENT) & (~((size_t)(ALIGNMENT - 1)))); - *((void **)p - 1) = p0; - return p; -} -static void fft_free(void *p) -{ - if (p) - free(*((void **)p - 1)); -} - -static inline void fft_cpx_clear(struct dsp_ops *dsp, float *v, int size) -{ - dsp_ops_clear(dsp, v, size * 2); -} -static float *fft_cpx_alloc(int size) -{ - return fft_alloc(size * 2); -} - -static void fft_cpx_free(float *cpx) -{ - fft_free(cpx); -} - static int next_power_of_two(int val) { int r = 1; @@ -76,20 +43,44 @@ static int next_power_of_two(int val) return r; } -static void convolver1_reset(struct convolver1 *conv) +static void convolver1_reset(struct spa_fga_dsp *dsp, struct convolver1 *conv) { int i; for (i = 0; i < conv->segCount; i++) - fft_cpx_clear(conv->dsp, conv->segments[i], conv->fftComplexSize); - dsp_ops_clear(conv->dsp, conv->overlap, conv->blockSize); - dsp_ops_clear(conv->dsp, conv->inputBuffer, conv->segSize); - fft_cpx_clear(conv->dsp, conv->pre_mult, conv->fftComplexSize); - fft_cpx_clear(conv->dsp, conv->conv, conv->fftComplexSize); + spa_fga_dsp_fft_memclear(dsp, conv->segments[i], conv->fftComplexSize, false); + spa_fga_dsp_fft_memclear(dsp, conv->overlap, conv->blockSize, true); + spa_fga_dsp_fft_memclear(dsp, conv->inputBuffer, conv->segSize, true); + spa_fga_dsp_fft_memclear(dsp, conv->pre_mult, conv->fftComplexSize, false); + spa_fga_dsp_fft_memclear(dsp, conv->conv, conv->fftComplexSize, false); conv->inputBufferFill = 0; conv->current = 0; } -static struct convolver1 *convolver1_new(struct dsp_ops *dsp, int block, const float *ir, int irlen) +static void convolver1_free(struct spa_fga_dsp *dsp, struct convolver1 *conv) +{ + int i; + for (i = 0; i < conv->segCount; i++) { + if (conv->segments) + spa_fga_dsp_fft_memfree(dsp, conv->segments[i]); + if (conv->segmentsIr) + spa_fga_dsp_fft_memfree(dsp, conv->segmentsIr[i]); + } + if (conv->fft) + spa_fga_dsp_fft_free(dsp, conv->fft); + if (conv->ifft) + spa_fga_dsp_fft_free(dsp, conv->ifft); + if (conv->fft_buffer) + spa_fga_dsp_fft_memfree(dsp, conv->fft_buffer); + free(conv->segments); + free(conv->segmentsIr); + spa_fga_dsp_fft_memfree(dsp, conv->pre_mult); + spa_fga_dsp_fft_memfree(dsp, conv->conv); + spa_fga_dsp_fft_memfree(dsp, conv->overlap); + spa_fga_dsp_fft_memfree(dsp, conv->inputBuffer); + free(conv); +} + +static struct convolver1 *convolver1_new(struct spa_fga_dsp *dsp, int block, const float *ir, int irlen) { struct convolver1 *conv; int i; @@ -107,86 +98,64 @@ static struct convolver1 *convolver1_new(struct dsp_ops *dsp, int block, const f if (irlen == 0) return conv; - conv->dsp = dsp; conv->blockSize = next_power_of_two(block); conv->segSize = 2 * conv->blockSize; conv->segCount = (irlen + conv->blockSize-1) / conv->blockSize; conv->fftComplexSize = (conv->segSize / 2) + 1; - conv->fft = dsp_ops_fft_new(conv->dsp, conv->segSize, true); + conv->fft = spa_fga_dsp_fft_new(dsp, conv->segSize, true); if (conv->fft == NULL) goto error; - conv->ifft = dsp_ops_fft_new(conv->dsp, conv->segSize, true); + conv->ifft = spa_fga_dsp_fft_new(dsp, conv->segSize, true); if (conv->ifft == NULL) goto error; - conv->fft_buffer = fft_alloc(conv->segSize); + conv->fft_buffer = spa_fga_dsp_fft_memalloc(dsp, conv->segSize, true); if (conv->fft_buffer == NULL) goto error; conv->segments = calloc(conv->segCount, sizeof(float*)); conv->segmentsIr = calloc(conv->segCount, sizeof(float*)); + if (conv->segments == NULL || conv->segmentsIr == NULL) + goto error; for (i = 0; i < conv->segCount; i++) { int left = irlen - (i * conv->blockSize); int copy = SPA_MIN(conv->blockSize, left); - conv->segments[i] = fft_cpx_alloc(conv->fftComplexSize); - conv->segmentsIr[i] = fft_cpx_alloc(conv->fftComplexSize); + conv->segments[i] = spa_fga_dsp_fft_memalloc(dsp, conv->fftComplexSize, false); + conv->segmentsIr[i] = spa_fga_dsp_fft_memalloc(dsp, conv->fftComplexSize, false); + if (conv->segments[i] == NULL || conv->segmentsIr[i] == NULL) + goto error; - dsp_ops_copy(conv->dsp, conv->fft_buffer, &ir[i * conv->blockSize], copy); + spa_fga_dsp_copy(dsp, conv->fft_buffer, &ir[i * conv->blockSize], copy); if (copy < conv->segSize) - dsp_ops_clear(conv->dsp, conv->fft_buffer + copy, conv->segSize - copy); + spa_fga_dsp_fft_memclear(dsp, conv->fft_buffer + copy, conv->segSize - copy, true); - dsp_ops_fft_run(conv->dsp, conv->fft, 1, conv->fft_buffer, conv->segmentsIr[i]); + spa_fga_dsp_fft_run(dsp, conv->fft, 1, conv->fft_buffer, conv->segmentsIr[i]); } - conv->pre_mult = fft_cpx_alloc(conv->fftComplexSize); - conv->conv = fft_cpx_alloc(conv->fftComplexSize); - conv->overlap = fft_alloc(conv->blockSize); - conv->inputBuffer = fft_alloc(conv->segSize); + conv->pre_mult = spa_fga_dsp_fft_memalloc(dsp, conv->fftComplexSize, false); + conv->conv = spa_fga_dsp_fft_memalloc(dsp, conv->fftComplexSize, false); + conv->overlap = spa_fga_dsp_fft_memalloc(dsp, conv->blockSize, true); + conv->inputBuffer = spa_fga_dsp_fft_memalloc(dsp, conv->segSize, true); + if (conv->pre_mult == NULL || conv->conv == NULL || conv->overlap == NULL || + conv->inputBuffer == NULL) + goto error; conv->scale = 1.0f / conv->segSize; - convolver1_reset(conv); + convolver1_reset(dsp, conv); return conv; error: - if (conv->fft) - dsp_ops_fft_free(dsp, conv->fft); - if (conv->ifft) - dsp_ops_fft_free(dsp, conv->ifft); - if (conv->fft_buffer) - fft_free(conv->fft_buffer); - free(conv); + convolver1_free(dsp, conv); return NULL; } -static void convolver1_free(struct convolver1 *conv) -{ - int i; - for (i = 0; i < conv->segCount; i++) { - fft_cpx_free(conv->segments[i]); - fft_cpx_free(conv->segmentsIr[i]); - } - if (conv->fft) - dsp_ops_fft_free(conv->dsp, conv->fft); - if (conv->ifft) - dsp_ops_fft_free(conv->dsp, conv->ifft); - if (conv->fft_buffer) - fft_free(conv->fft_buffer); - free(conv->segments); - free(conv->segmentsIr); - fft_cpx_free(conv->pre_mult); - fft_cpx_free(conv->conv); - fft_free(conv->overlap); - fft_free(conv->inputBuffer); - free(conv); -} - -static int convolver1_run(struct convolver1 *conv, const float *input, float *output, int len) +static int convolver1_run(struct spa_fga_dsp *dsp, struct convolver1 *conv, const float *input, float *output, int len) { int i, processed = 0; if (conv == NULL || conv->segCount == 0) { - dsp_ops_clear(conv->dsp, output, len); + spa_fga_dsp_fft_memclear(dsp, output, len, true); return len; } @@ -194,17 +163,17 @@ static int convolver1_run(struct convolver1 *conv, const float *input, float *ou const int processing = SPA_MIN(len - processed, conv->blockSize - conv->inputBufferFill); const int inputBufferPos = conv->inputBufferFill; - dsp_ops_copy(conv->dsp, conv->inputBuffer + inputBufferPos, input + processed, processing); + spa_fga_dsp_copy(dsp, conv->inputBuffer + inputBufferPos, input + processed, processing); if (inputBufferPos == 0 && processing < conv->blockSize) - dsp_ops_clear(conv->dsp, conv->inputBuffer + processing, conv->blockSize - processing); + spa_fga_dsp_fft_memclear(dsp, conv->inputBuffer + processing, conv->blockSize - processing, true); - dsp_ops_fft_run(conv->dsp, conv->fft, 1, conv->inputBuffer, conv->segments[conv->current]); + spa_fga_dsp_fft_run(dsp, conv->fft, 1, conv->inputBuffer, conv->segments[conv->current]); if (conv->segCount > 1) { if (conv->inputBufferFill == 0) { int indexAudio = (conv->current + 1) % conv->segCount; - dsp_ops_fft_cmul(conv->dsp, conv->fft, conv->pre_mult, + spa_fga_dsp_fft_cmul(dsp, conv->fft, conv->pre_mult, conv->segmentsIr[1], conv->segments[indexAudio], conv->fftComplexSize, conv->scale); @@ -212,7 +181,7 @@ static int convolver1_run(struct convolver1 *conv, const float *input, float *ou for (i = 2; i < conv->segCount; i++) { indexAudio = (conv->current + i) % conv->segCount; - dsp_ops_fft_cmuladd(conv->dsp, conv->fft, + spa_fga_dsp_fft_cmuladd(dsp, conv->fft, conv->pre_mult, conv->pre_mult, conv->segmentsIr[i], @@ -220,30 +189,30 @@ static int convolver1_run(struct convolver1 *conv, const float *input, float *ou conv->fftComplexSize, conv->scale); } } - dsp_ops_fft_cmuladd(conv->dsp, conv->fft, + spa_fga_dsp_fft_cmuladd(dsp, conv->fft, conv->conv, conv->pre_mult, conv->segments[conv->current], conv->segmentsIr[0], conv->fftComplexSize, conv->scale); } else { - dsp_ops_fft_cmul(conv->dsp, conv->fft, + spa_fga_dsp_fft_cmul(dsp, conv->fft, conv->conv, conv->segments[conv->current], conv->segmentsIr[0], conv->fftComplexSize, conv->scale); } - dsp_ops_fft_run(conv->dsp, conv->ifft, -1, conv->conv, conv->fft_buffer); + spa_fga_dsp_fft_run(dsp, conv->ifft, -1, conv->conv, conv->fft_buffer); - dsp_ops_sum(conv->dsp, output + processed, conv->fft_buffer + inputBufferPos, + spa_fga_dsp_sum(dsp, output + processed, conv->fft_buffer + inputBufferPos, conv->overlap + inputBufferPos, processing); conv->inputBufferFill += processing; if (conv->inputBufferFill == conv->blockSize) { conv->inputBufferFill = 0; - dsp_ops_copy(conv->dsp, conv->overlap, conv->fft_buffer + conv->blockSize, conv->blockSize); + spa_fga_dsp_copy(dsp, conv->overlap, conv->fft_buffer + conv->blockSize, conv->blockSize); conv->current = (conv->current > 0) ? (conv->current - 1) : (conv->segCount - 1); } @@ -255,7 +224,7 @@ static int convolver1_run(struct convolver1 *conv, const float *input, float *ou struct convolver { - struct dsp_ops *dsp; + struct spa_fga_dsp *dsp; int headBlockSize; int tailBlockSize; struct convolver1 *headConvolver; @@ -272,23 +241,25 @@ struct convolver void convolver_reset(struct convolver *conv) { + struct spa_fga_dsp *dsp = conv->dsp; + if (conv->headConvolver) - convolver1_reset(conv->headConvolver); + convolver1_reset(dsp, conv->headConvolver); if (conv->tailConvolver0) { - convolver1_reset(conv->tailConvolver0); - dsp_ops_clear(conv->dsp, conv->tailOutput0, conv->tailBlockSize); - dsp_ops_clear(conv->dsp, conv->tailPrecalculated0, conv->tailBlockSize); + convolver1_reset(dsp, conv->tailConvolver0); + spa_fga_dsp_fft_memclear(dsp, conv->tailOutput0, conv->tailBlockSize, true); + spa_fga_dsp_fft_memclear(dsp, conv->tailPrecalculated0, conv->tailBlockSize, true); } if (conv->tailConvolver) { - convolver1_reset(conv->tailConvolver); - dsp_ops_clear(conv->dsp, conv->tailOutput, conv->tailBlockSize); - dsp_ops_clear(conv->dsp, conv->tailPrecalculated, conv->tailBlockSize); + convolver1_reset(dsp, conv->tailConvolver); + spa_fga_dsp_fft_memclear(dsp, conv->tailOutput, conv->tailBlockSize, true); + spa_fga_dsp_fft_memclear(dsp, conv->tailPrecalculated, conv->tailBlockSize, true); } conv->tailInputFill = 0; conv->precalculatedPos = 0; } -struct convolver *convolver_new(struct dsp_ops *dsp_ops, int head_block, int tail_block, const float *ir, int irlen) +struct convolver *convolver_new(struct spa_fga_dsp *dsp, int head_block, int tail_block, const float *ir, int irlen) { struct convolver *conv; int head_ir_len; @@ -307,57 +278,76 @@ struct convolver *convolver_new(struct dsp_ops *dsp_ops, int head_block, int tai if (conv == NULL) return NULL; + conv->dsp = dsp; + if (irlen == 0) return conv; - conv->dsp = dsp_ops; conv->headBlockSize = next_power_of_two(head_block); conv->tailBlockSize = next_power_of_two(tail_block); head_ir_len = SPA_MIN(irlen, conv->tailBlockSize); - conv->headConvolver = convolver1_new(dsp_ops, conv->headBlockSize, ir, head_ir_len); + conv->headConvolver = convolver1_new(dsp, conv->headBlockSize, ir, head_ir_len); + if (conv->headConvolver == NULL) + goto error; if (irlen > conv->tailBlockSize) { int conv1IrLen = SPA_MIN(irlen - conv->tailBlockSize, conv->tailBlockSize); - conv->tailConvolver0 = convolver1_new(dsp_ops, conv->headBlockSize, ir + conv->tailBlockSize, conv1IrLen); - conv->tailOutput0 = fft_alloc(conv->tailBlockSize); - conv->tailPrecalculated0 = fft_alloc(conv->tailBlockSize); + conv->tailConvolver0 = convolver1_new(dsp, conv->headBlockSize, ir + conv->tailBlockSize, conv1IrLen); + conv->tailOutput0 = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); + conv->tailPrecalculated0 = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); + if (conv->tailConvolver0 == NULL || conv->tailOutput0 == NULL || + conv->tailPrecalculated0 == NULL) + goto error; } if (irlen > 2 * conv->tailBlockSize) { int tailIrLen = irlen - (2 * conv->tailBlockSize); - conv->tailConvolver = convolver1_new(dsp_ops, conv->tailBlockSize, ir + (2 * conv->tailBlockSize), tailIrLen); - conv->tailOutput = fft_alloc(conv->tailBlockSize); - conv->tailPrecalculated = fft_alloc(conv->tailBlockSize); + conv->tailConvolver = convolver1_new(dsp, conv->tailBlockSize, ir + (2 * conv->tailBlockSize), tailIrLen); + conv->tailOutput = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); + conv->tailPrecalculated = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); + if (conv->tailConvolver == NULL || conv->tailOutput == NULL || + conv->tailPrecalculated == NULL) + goto error; } - if (conv->tailConvolver0 || conv->tailConvolver) - conv->tailInput = fft_alloc(conv->tailBlockSize); + if (conv->tailConvolver0 || conv->tailConvolver) { + conv->tailInput = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); + if (conv->tailInput == NULL) + goto error; + } convolver_reset(conv); return conv; +error: + convolver_free(conv); + return NULL; } void convolver_free(struct convolver *conv) { + struct spa_fga_dsp *dsp = conv->dsp; + if (conv->headConvolver) - convolver1_free(conv->headConvolver); + convolver1_free(dsp, conv->headConvolver); if (conv->tailConvolver0) - convolver1_free(conv->tailConvolver0); + convolver1_free(dsp, conv->tailConvolver0); if (conv->tailConvolver) - convolver1_free(conv->tailConvolver); - fft_free(conv->tailOutput0); - fft_free(conv->tailPrecalculated0); - fft_free(conv->tailOutput); - fft_free(conv->tailPrecalculated); - fft_free(conv->tailInput); + convolver1_free(dsp, conv->tailConvolver); + spa_fga_dsp_fft_memfree(dsp, conv->tailOutput0); + spa_fga_dsp_fft_memfree(dsp, conv->tailPrecalculated0); + spa_fga_dsp_fft_memfree(dsp, conv->tailOutput); + spa_fga_dsp_fft_memfree(dsp, conv->tailPrecalculated); + spa_fga_dsp_fft_memfree(dsp, conv->tailInput); free(conv); } int convolver_run(struct convolver *conv, const float *input, float *output, int length) { - convolver1_run(conv->headConvolver, input, output, length); + struct spa_fga_dsp *dsp = conv->dsp; + + convolver1_run(dsp, conv->headConvolver, input, output, length); if (conv->tailInput) { int processed = 0; @@ -367,21 +357,21 @@ int convolver_run(struct convolver *conv, const float *input, float *output, int int processing = SPA_MIN(remaining, conv->headBlockSize - (conv->tailInputFill % conv->headBlockSize)); if (conv->tailPrecalculated0) - dsp_ops_sum(conv->dsp, &output[processed], &output[processed], + spa_fga_dsp_sum(dsp, &output[processed], &output[processed], &conv->tailPrecalculated0[conv->precalculatedPos], processing); if (conv->tailPrecalculated) - dsp_ops_sum(conv->dsp, &output[processed], &output[processed], + spa_fga_dsp_sum(dsp, &output[processed], &output[processed], &conv->tailPrecalculated[conv->precalculatedPos], processing); conv->precalculatedPos += processing; - dsp_ops_copy(conv->dsp, conv->tailInput + conv->tailInputFill, input + processed, processing); + spa_fga_dsp_copy(dsp, conv->tailInput + conv->tailInputFill, input + processed, processing); conv->tailInputFill += processing; if (conv->tailPrecalculated0 && (conv->tailInputFill % conv->headBlockSize == 0)) { int blockOffset = conv->tailInputFill - conv->headBlockSize; - convolver1_run(conv->tailConvolver0, + convolver1_run(dsp, conv->tailConvolver0, conv->tailInput + blockOffset, conv->tailOutput0 + blockOffset, conv->headBlockSize); @@ -392,7 +382,7 @@ int convolver_run(struct convolver *conv, const float *input, float *output, int if (conv->tailPrecalculated && conv->tailInputFill == conv->tailBlockSize) { SPA_SWAP(conv->tailPrecalculated, conv->tailOutput); - convolver1_run(conv->tailConvolver, conv->tailInput, + convolver1_run(dsp, conv->tailConvolver, conv->tailInput, conv->tailOutput, conv->tailBlockSize); } if (conv->tailInputFill == conv->tailBlockSize) { diff --git a/src/modules/module-filter-chain/convolver.h b/spa/plugins/filter-graph/convolver.h similarity index 72% rename from src/modules/module-filter-chain/convolver.h rename to spa/plugins/filter-graph/convolver.h index e8749d7b..ad6139a3 100644 --- a/src/modules/module-filter-chain/convolver.h +++ b/spa/plugins/filter-graph/convolver.h @@ -5,9 +5,9 @@ #include <stdint.h> #include <stddef.h> -#include "dsp-ops.h" +#include "audio-dsp.h" -struct convolver *convolver_new(struct dsp_ops *dsp, int block, int tail, const float *ir, int irlen); +struct convolver *convolver_new(struct spa_fga_dsp *dsp, int block, int tail, const float *ir, int irlen); void convolver_free(struct convolver *conv); void convolver_reset(struct convolver *conv); diff --git a/spa/plugins/filter-graph/ebur128_plugin.c b/spa/plugins/filter-graph/ebur128_plugin.c new file mode 100644 index 00000000..22bf5f51 --- /dev/null +++ b/spa/plugins/filter-graph/ebur128_plugin.c @@ -0,0 +1,631 @@ +#include "config.h" + +#include <limits.h> + +#include <spa/utils/json.h> +#include <spa/support/log.h> + +#include "audio-plugin.h" +#include "audio-dsp.h" + +#include <ebur128.h> + +struct plugin { + struct spa_handle handle; + struct spa_fga_plugin plugin; + + struct spa_fga_dsp *dsp; + struct spa_log *log; + uint32_t quantum_limit; +}; + +enum { + PORT_IN_FL, + PORT_IN_FR, + PORT_IN_FC, + PORT_IN_UNUSED, + PORT_IN_SL, + PORT_IN_SR, + PORT_IN_DUAL_MONO, + + PORT_OUT_FL, + PORT_OUT_FR, + PORT_OUT_FC, + PORT_OUT_UNUSED, + PORT_OUT_SL, + PORT_OUT_SR, + PORT_OUT_DUAL_MONO, + + PORT_OUT_MOMENTARY, + PORT_OUT_SHORTTERM, + PORT_OUT_GLOBAL, + PORT_OUT_WINDOW, + PORT_OUT_RANGE, + PORT_OUT_PEAK, + PORT_OUT_TRUE_PEAK, + + PORT_MAX, + + PORT_IN_START = PORT_IN_FL, + PORT_OUT_START = PORT_OUT_FL, + PORT_NOTIFY_START = PORT_OUT_MOMENTARY, +}; + + +struct ebur128_impl { + struct plugin *plugin; + + struct spa_fga_dsp *dsp; + struct spa_log *log; + + unsigned long rate; + float *port[PORT_MAX]; + + unsigned int max_history; + unsigned int max_window; + bool use_histogram; + + ebur128_state *st[7]; +}; + +static void * ebur128_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, + unsigned long SampleRate, int index, const char *config) +{ + struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); + struct ebur128_impl *impl; + struct spa_json it[1]; + const char *val; + char key[256]; + int len; + float f; + + impl = calloc(1, sizeof(*impl)); + if (impl == NULL) { + errno = ENOMEM; + return NULL; + } + + impl->plugin = pl; + impl->dsp = pl->dsp; + impl->log = pl->log; + impl->max_history = 10000; + impl->max_window = 0; + impl->rate = SampleRate; + + if (config == NULL) + return impl; + + if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { + spa_log_error(pl->log, "ebur128: expected object in config"); + errno = EINVAL; + goto error; + } + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { + if (spa_streq(key, "max-history")) { + if (spa_json_parse_float(val, len, &f) <= 0) { + spa_log_error(impl->log, "ebur128:max-history requires a number"); + errno = EINVAL; + goto error; + } + impl->max_history = (unsigned int) (f * 1000.0f); + } + else if (spa_streq(key, "max-window")) { + if (spa_json_parse_float(val, len, &f) <= 0) { + spa_log_error(impl->log, "ebur128:max-window requires a number"); + errno = EINVAL; + goto error; + } + impl->max_window = (unsigned int) (f * 1000.0f); + } + else if (spa_streq(key, "use-histogram")) { + if (spa_json_parse_bool(val, len, &impl->use_histogram) <= 0) { + spa_log_error(impl->log, "ebur128:use-histogram requires a boolean"); + errno = EINVAL; + goto error; + } + } else { + spa_log_warn(impl->log, "ebur128: unknown key %s", key); + } + } + return impl; +error: + free(impl); + return NULL; +} + +static void ebur128_run(void * Instance, unsigned long SampleCount) +{ + struct ebur128_impl *impl = Instance; + int i, c; + double value; + ebur128_state *st[7]; + + for (i = 0; i < 7; i++) { + float *in = impl->port[PORT_IN_START + i]; + float *out = impl->port[PORT_OUT_START + i]; + + st[i] = NULL; + if (in == NULL) + continue; + + st[i] = impl->st[i]; + if (st[i] != NULL) + ebur128_add_frames_float(st[i], in, SampleCount); + + if (out != NULL) + memcpy(out, in, SampleCount * sizeof(float)); + } + if (impl->port[PORT_OUT_MOMENTARY] != NULL) { + double sum = 0.0; + for (i = 0, c = 0; i < 7; i++) { + if (st[i] != NULL) { + ebur128_loudness_momentary(st[i], &value); + sum += value; + c++; + } + } + impl->port[PORT_OUT_MOMENTARY][0] = (float) (sum / c); + } + if (impl->port[PORT_OUT_SHORTTERM] != NULL) { + double sum = 0.0; + for (i = 0, c = 0; i < 7; i++) { + if (st[i] != NULL) { + ebur128_loudness_shortterm(st[i], &value); + sum += value; + c++; + } + } + impl->port[PORT_OUT_SHORTTERM][0] = (float) (sum / c); + } + if (impl->port[PORT_OUT_GLOBAL] != NULL) { + ebur128_loudness_global_multiple(st, 7, &value); + impl->port[PORT_OUT_GLOBAL][0] = (float)value; + } + if (impl->port[PORT_OUT_WINDOW] != NULL) { + double sum = 0.0; + for (i = 0, c = 0; i < 7; i++) { + if (st[i] != NULL) { + ebur128_loudness_window(st[i], impl->max_window, &value); + sum += value; + c++; + } + } + impl->port[PORT_OUT_WINDOW][0] = (float) (sum / c); + } + if (impl->port[PORT_OUT_RANGE] != NULL) { + ebur128_loudness_range_multiple(st, 7, &value); + impl->port[PORT_OUT_RANGE][0] = (float)value; + } + if (impl->port[PORT_OUT_PEAK] != NULL) { + double max = 0.0; + for (i = 0; i < 7; i++) { + if (st[i] != NULL) { + ebur128_sample_peak(st[i], i, &value); + max = SPA_MAX(max, value); + } + } + impl->port[PORT_OUT_PEAK][0] = (float) max; + } + if (impl->port[PORT_OUT_TRUE_PEAK] != NULL) { + double max = 0.0; + for (i = 0; i < 7; i++) { + if (st[i] != NULL) { + ebur128_true_peak(st[i], i, &value); + max = SPA_MAX(max, value); + } + } + impl->port[PORT_OUT_TRUE_PEAK][0] = (float) max; + } +} + +static void ebur128_connect_port(void * Instance, unsigned long Port, + float * DataLocation) +{ + struct ebur128_impl *impl = Instance; + if (Port < PORT_MAX) + impl->port[Port] = DataLocation; +} + +static void ebur128_cleanup(void * Instance) +{ + struct ebur128_impl *impl = Instance; + free(impl); +} + +static void ebur128_activate(void * Instance) +{ + struct ebur128_impl *impl = Instance; + int mode = 0, i; + int modes[] = { + EBUR128_MODE_M, + EBUR128_MODE_S, + EBUR128_MODE_I, + 0, + EBUR128_MODE_LRA, + EBUR128_MODE_SAMPLE_PEAK, + EBUR128_MODE_TRUE_PEAK, + }; + enum channel channels[] = { + EBUR128_LEFT, + EBUR128_RIGHT, + EBUR128_CENTER, + EBUR128_UNUSED, + EBUR128_LEFT_SURROUND, + EBUR128_RIGHT_SURROUND, + EBUR128_DUAL_MONO, + }; + + if (impl->use_histogram) + mode |= EBUR128_MODE_HISTOGRAM; + + /* check modes */ + for (i = 0; i < 7; i++) { + if (impl->port[PORT_NOTIFY_START + i] != NULL) + mode |= modes[i]; + } + + for (i = 0; i < 7; i++) { + impl->st[i] = ebur128_init(1, impl->rate, mode); + if (impl->st[i]) { + ebur128_set_channel(impl->st[i], i, channels[i]); + ebur128_set_max_history(impl->st[i], impl->max_history); + ebur128_set_max_window(impl->st[i], impl->max_window); + } + } +} + +static void ebur128_deactivate(void * Instance) +{ + struct ebur128_impl *impl = Instance; + int i; + + for (i = 0; i < 7; i++) { + if (impl->st[i] != NULL) + ebur128_destroy(&impl->st[i]); + } +} + +static struct spa_fga_port ebur128_ports[] = { + { .index = PORT_IN_FL, + .name = "In FL", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = PORT_IN_FR, + .name = "In FR", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = PORT_IN_FC, + .name = "In FC", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = PORT_IN_UNUSED, + .name = "In UNUSED", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = PORT_IN_SL, + .name = "In SL", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = PORT_IN_SR, + .name = "In SR", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = PORT_IN_DUAL_MONO, + .name = "In DUAL MONO", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, + }, + + { .index = PORT_OUT_FL, + .name = "Out FL", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = PORT_OUT_FR, + .name = "Out FR", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = PORT_OUT_FC, + .name = "Out FC", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = PORT_OUT_UNUSED, + .name = "Out UNUSED", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = PORT_OUT_SL, + .name = "Out SL", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = PORT_OUT_SR, + .name = "Out SR", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + { .index = PORT_OUT_DUAL_MONO, + .name = "Out DUAL MONO", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, + }, + + { .index = PORT_OUT_MOMENTARY, + .name = "Momentary LUFS", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = PORT_OUT_SHORTTERM, + .name = "Shorttem LUFS", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = PORT_OUT_GLOBAL, + .name = "Global LUFS", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = PORT_OUT_WINDOW, + .name = "Window LUFS", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = PORT_OUT_RANGE, + .name = "Range LU", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = PORT_OUT_PEAK, + .name = "Peak", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = PORT_OUT_TRUE_PEAK, + .name = "True Peak", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, +}; + +static const struct spa_fga_descriptor ebur128_desc = { + .name = "ebur128", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .ports = ebur128_ports, + .n_ports = SPA_N_ELEMENTS(ebur128_ports), + + .instantiate = ebur128_instantiate, + .connect_port = ebur128_connect_port, + .activate = ebur128_activate, + .deactivate = ebur128_deactivate, + .run = ebur128_run, + .cleanup = ebur128_cleanup, +}; + +static struct spa_fga_port lufs2gain_ports[] = { + { .index = 0, + .name = "LUFS", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 1, + .name = "Gain", + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, + }, + { .index = 2, + .name = "Target LUFS", + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, + .def = -23.0f, .min = -70.0f, .max = 0.0f + }, +}; + +struct lufs2gain_impl { + struct plugin *plugin; + + struct spa_fga_dsp *dsp; + struct spa_log *log; + + unsigned long rate; + float *port[3]; +}; + +static void * lufs2gain_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, + unsigned long SampleRate, int index, const char *config) +{ + struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); + struct lufs2gain_impl *impl; + + impl = calloc(1, sizeof(*impl)); + if (impl == NULL) { + errno = ENOMEM; + return NULL; + } + + impl->plugin = pl; + impl->dsp = pl->dsp; + impl->log = pl->log; + impl->rate = SampleRate; + + return impl; +} + +static void lufs2gain_connect_port(void * Instance, unsigned long Port, + float * DataLocation) +{ + struct lufs2gain_impl *impl = Instance; + if (Port < 3) + impl->port[Port] = DataLocation; +} + +static void lufs2gain_run(void * Instance, unsigned long SampleCount) +{ + struct lufs2gain_impl *impl = Instance; + float *in = impl->port[0]; + float *out = impl->port[1]; + float *target = impl->port[2]; + float gain; + + if (in == NULL || out == NULL || target == NULL) + return; + + if (isfinite(in[0])) { + float gaindB = target[0] - in[0]; + gain = powf(10.0f, gaindB / 20.0f); + } else { + gain = 1.0f; + } + out[0] = gain; +} + +static void lufs2gain_cleanup(void * Instance) +{ + struct lufs2gain_impl *impl = Instance; + free(impl); +} + +static const struct spa_fga_descriptor lufs2gain_desc = { + .name = "lufs2gain", + .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, + + .ports = lufs2gain_ports, + .n_ports = SPA_N_ELEMENTS(lufs2gain_ports), + + .instantiate = lufs2gain_instantiate, + .connect_port = lufs2gain_connect_port, + .run = lufs2gain_run, + .cleanup = lufs2gain_cleanup, +}; + +static const struct spa_fga_descriptor * ebur128_descriptor(unsigned long Index) +{ + switch(Index) { + case 0: + return &ebur128_desc; + case 1: + return &lufs2gain_desc; + } + return NULL; +} + + +static const struct spa_fga_descriptor *ebur128_plugin_make_desc(void *plugin, const char *name) +{ + unsigned long i; + for (i = 0; ;i++) { + const struct spa_fga_descriptor *d = ebur128_descriptor(i); + if (d == NULL) + break; + if (spa_streq(d->name, name)) + return d; + } + return NULL; +} + +static struct spa_fga_plugin_methods impl_plugin = { + SPA_VERSION_FGA_PLUGIN_METHODS, + .make_desc = ebur128_plugin_make_desc, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct plugin *impl; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + impl = (struct plugin *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin)) + *interface = &impl->plugin; + 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 plugin); +} + +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 plugin *impl; + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + impl = (struct plugin *) handle; + + impl->plugin.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, + SPA_VERSION_FGA_PLUGIN, + &impl_plugin, impl); + + impl->quantum_limit = 8192u; + + impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + impl->dsp = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioDSP); + + for (uint32_t 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, &impl->quantum_limit, 0); + if (spa_streq(k, "filter.graph.audio.dsp")) + sscanf(s, "pointer:%p", &impl->dsp); + } + if (impl->dsp == NULL) { + spa_log_error(impl->log, "%p: could not find DSP functions", impl); + return -EINVAL; + } + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin,}, +}; + +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 struct spa_handle_factory spa_fga_ebur128_plugin_factory = { + SPA_VERSION_HANDLE_FACTORY, + "filter.graph.plugin.ebur128", + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; + +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_fga_ebur128_plugin_factory; + break; + default: + return 0; + } + (*index)++; + return 1; +} diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c new file mode 100644 index 00000000..bc9ff154 --- /dev/null +++ b/spa/plugins/filter-graph/filter-graph.c @@ -0,0 +1,2170 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include <string.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dlfcn.h> +#include <unistd.h> +#include <limits.h> +#include <math.h> + +#include "config.h" + +#include <spa/utils/result.h> +#include <spa/utils/string.h> +#include <spa/utils/json.h> +#include <spa/support/cpu.h> +#include <spa/support/plugin-loader.h> +#include <spa/param/latency-utils.h> +#include <spa/param/tag-utils.h> +#include <spa/param/audio/raw.h> +#include <spa/param/audio/raw-json.h> +#include <spa/param/audio/format-utils.h> +#include <spa/pod/dynamic.h> +#include <spa/pod/builder.h> +#include <spa/debug/types.h> +#include <spa/debug/log.h> +#include <spa/filter-graph/filter-graph.h> + +#include "audio-plugin.h" +#include "audio-dsp-impl.h" + +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &log_topic +SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.filter-graph"); + +#define MAX_HNDL 64 + +#define DEFAULT_RATE 48000 + +#define spa_filter_graph_emit(hooks,method,version,...) \ + spa_hook_list_call_simple(hooks, struct spa_filter_graph_events, \ + method, version, ##__VA_ARGS__) + +#define spa_filter_graph_emit_info(hooks,...) spa_filter_graph_emit(hooks,info, 0, __VA_ARGS__) +#define spa_filter_graph_emit_apply_props(hooks,...) spa_filter_graph_emit(hooks,apply_props, 0, __VA_ARGS__) +#define spa_filter_graph_emit_props_changed(hooks,...) spa_filter_graph_emit(hooks,props_changed, 0, __VA_ARGS__) + +struct plugin { + struct spa_list link; + struct impl *impl; + + int ref; + char type[256]; + char path[PATH_MAX]; + + struct spa_handle *hndl; + struct spa_fga_plugin *plugin; + struct spa_list descriptor_list; +}; + +struct descriptor { + struct spa_list link; + int ref; + struct plugin *plugin; + char label[256]; + + const struct spa_fga_descriptor *desc; + + uint32_t n_input; + uint32_t n_output; + uint32_t n_control; + uint32_t n_notify; + unsigned long *input; + unsigned long *output; + unsigned long *control; + unsigned long *notify; + float *default_control; +}; + +struct port { + struct spa_list link; + struct node *node; + + uint32_t idx; + unsigned long p; + + struct spa_list link_list; + uint32_t n_links; + uint32_t external; + + float control_data[MAX_HNDL]; + float *audio_data[MAX_HNDL]; + void *audio_mem[MAX_HNDL]; +}; + +struct node { + struct spa_list link; + struct graph *graph; + + struct descriptor *desc; + + char name[256]; + char *config; + + struct port *input_port; + struct port *output_port; + struct port *control_port; + struct port *notify_port; + + uint32_t n_hndl; + void *hndl[MAX_HNDL]; + + unsigned int n_deps; + unsigned int visited:1; + unsigned int disabled:1; + unsigned int control_changed:1; +}; + +struct link { + struct spa_list link; + + struct spa_list input_link; + struct spa_list output_link; + + struct port *output; + struct port *input; +}; + +struct graph_port { + const struct spa_fga_descriptor *desc; + void **hndl; + uint32_t port; + unsigned next:1; +}; + +struct graph_hndl { + const struct spa_fga_descriptor *desc; + void **hndl; +}; + +struct volume { + bool mute; + uint32_t n_volumes; + float volumes[SPA_AUDIO_MAX_CHANNELS]; + + uint32_t n_ports; + struct port *ports[SPA_AUDIO_MAX_CHANNELS]; + float min[SPA_AUDIO_MAX_CHANNELS]; + float max[SPA_AUDIO_MAX_CHANNELS]; +#define SCALE_LINEAR 0 +#define SCALE_CUBIC 1 + int scale[SPA_AUDIO_MAX_CHANNELS]; +}; + +struct graph { + struct impl *impl; + + struct spa_list node_list; + struct spa_list link_list; + + uint32_t n_input; + struct graph_port *input; + + uint32_t n_output; + struct graph_port *output; + + uint32_t n_hndl; + struct graph_hndl *hndl; + + uint32_t n_control; + struct port **control_port; + + struct volume volume[2]; + + unsigned activated:1; +}; + +struct impl { + struct spa_handle handle; + struct spa_filter_graph filter_graph; + struct spa_hook_list hooks; + + struct spa_log *log; + struct spa_cpu *cpu; + struct spa_fga_dsp *dsp; + struct spa_plugin_loader *loader; + + uint64_t info_all; + struct spa_filter_graph_info info; + + struct graph graph; + + uint32_t quantum_limit; + uint32_t max_align; + long unsigned rate; + + struct spa_list plugin_list; + + float *silence_data; + float *discard_data; +}; + +static void emit_filter_graph_info(struct impl *impl, bool full) +{ + uint64_t old = full ? impl->info.change_mask : 0; + + if (full) + impl->info.change_mask = impl->info_all; + if (impl->info.change_mask || full) { + spa_filter_graph_emit_info(&impl->hooks, &impl->info); + impl->info.change_mask = old; + } +} +static int +impl_add_listener(void *object, + struct spa_hook *listener, + const struct spa_filter_graph_events *events, + void *data) +{ + struct impl *impl = object; + struct spa_hook_list save; + + spa_log_trace(impl->log, "%p: add listener %p", impl, listener); + spa_hook_list_isolate(&impl->hooks, &save, listener, events, data); + + emit_filter_graph_info(impl, true); + + spa_hook_list_join(&impl->hooks, &save); + + return 0; +} + +static int impl_process(void *object, + const void *in[], void *out[], uint32_t n_samples) +{ + struct impl *impl = object; + struct graph *graph = &impl->graph; + uint32_t i, j, n_hndl = graph->n_hndl; + struct graph_port *port; + + for (i = 0, j = 0; i < impl->info.n_inputs; i++) { + while (j < graph->n_input) { + port = &graph->input[j++]; + if (port->desc && in[i]) + port->desc->connect_port(*port->hndl, port->port, (float*)in[i]); + if (!port->next) + break; + } + } + for (i = 0; i < impl->info.n_outputs; i++) { + if (out[i] == NULL) + continue; + + port = i < graph->n_output ? &graph->output[i] : NULL; + + if (port && port->desc) + port->desc->connect_port(*port->hndl, port->port, out[i]); + else + memset(out[i], 0, n_samples * sizeof(float)); + } + for (i = 0; i < n_hndl; i++) { + struct graph_hndl *hndl = &graph->hndl[i]; + hndl->desc->run(*hndl->hndl, n_samples); + } + return 0; +} + +static float get_default(struct impl *impl, struct descriptor *desc, uint32_t p) +{ + struct spa_fga_port *port = &desc->desc->ports[p]; + return port->def; +} + +static struct node *find_node(struct graph *graph, const char *name) +{ + struct node *node; + spa_list_for_each(node, &graph->node_list, link) { + if (spa_streq(node->name, name)) + return node; + } + return NULL; +} + +/* find a port by name. Valid syntax is: + * "<node_name>:<port_name>" + * "<node_name>:<port_id>" + * "<port_name>" + * "<port_id>" + * When no node_name is given, the port is assumed in the current node. */ +static struct port *find_port(struct node *node, const char *name, int descriptor) +{ + char *col, *node_name, *port_name, *str; + struct port *ports; + const struct spa_fga_descriptor *d; + uint32_t i, n_ports, port_id = SPA_ID_INVALID; + + str = strdupa(name); + col = strchr(str, ':'); + if (col != NULL) { + struct node *find; + node_name = str; + port_name = col + 1; + *col = '\0'; + 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; + } + if (node == NULL) + return NULL; + + if (!spa_atou32(port_name, &port_id, 0)) + port_id = SPA_ID_INVALID; + + if (SPA_FGA_IS_PORT_INPUT(descriptor)) { + if (SPA_FGA_IS_PORT_CONTROL(descriptor)) { + ports = node->control_port; + n_ports = node->desc->n_control; + } else { + ports = node->input_port; + n_ports = node->desc->n_input; + } + } else if (SPA_FGA_IS_PORT_OUTPUT(descriptor)) { + if (SPA_FGA_IS_PORT_CONTROL(descriptor)) { + ports = node->notify_port; + n_ports = node->desc->n_notify; + } else { + ports = node->output_port; + n_ports = node->desc->n_output; + } + } else + return NULL; + + d = node->desc->desc; + for (i = 0; i < n_ports; i++) { + struct port *port = &ports[i]; + if (i == port_id || + spa_streq(d->ports[port->p].name, port_name)) + return port; + } + return NULL; +} + +static int impl_enum_prop_info(void *object, uint32_t idx, struct spa_pod_builder *b, + struct spa_pod **param) +{ + struct impl *impl = object; + struct graph *graph = &impl->graph; + struct spa_pod *pod; + struct spa_pod_frame f[2]; + struct port *port; + struct node *node; + struct descriptor *desc; + const struct spa_fga_descriptor *d; + struct spa_fga_port *p; + float def, min, max; + char name[512]; + uint32_t rate = impl->rate ? impl->rate : DEFAULT_RATE; + + if (idx >= graph->n_control) + return 0; + + port = graph->control_port[idx]; + node = port->node; + desc = node->desc; + d = desc->desc; + p = &d->ports[port->p]; + + if (p->hint & SPA_FGA_HINT_SAMPLE_RATE) { + def = p->def * rate; + min = p->min * rate; + max = p->max * rate; + } else { + def = p->def; + min = p->min; + max = p->max; + } + + if (node->name[0] != '\0') + snprintf(name, sizeof(name), "%s:%s", node->name, p->name); + else + snprintf(name, sizeof(name), "%s", p->name); + + spa_pod_builder_push_object(b, &f[0], + SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo); + spa_pod_builder_add (b, + SPA_PROP_INFO_name, SPA_POD_String(name), + 0); + spa_pod_builder_prop(b, SPA_PROP_INFO_type, 0); + if (p->hint & SPA_FGA_HINT_BOOLEAN) { + if (min == max) { + spa_pod_builder_bool(b, def <= 0.0f ? false : true); + } else { + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); + spa_pod_builder_bool(b, def <= 0.0f ? false : true); + spa_pod_builder_bool(b, false); + spa_pod_builder_bool(b, true); + spa_pod_builder_pop(b, &f[1]); + } + } else if (p->hint & SPA_FGA_HINT_INTEGER) { + if (min == max) { + spa_pod_builder_int(b, (int32_t)def); + } else { + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Range, 0); + spa_pod_builder_int(b, (int32_t)def); + spa_pod_builder_int(b, (int32_t)min); + spa_pod_builder_int(b, (int32_t)max); + spa_pod_builder_pop(b, &f[1]); + } + } else { + if (min == max) { + spa_pod_builder_float(b, def); + } else { + spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Range, 0); + spa_pod_builder_float(b, def); + spa_pod_builder_float(b, min); + spa_pod_builder_float(b, max); + spa_pod_builder_pop(b, &f[1]); + } + } + spa_pod_builder_prop(b, SPA_PROP_INFO_params, 0); + spa_pod_builder_bool(b, true); + pod = spa_pod_builder_pop(b, &f[0]); + if (pod == NULL) + return -ENOSPC; + if (param) + *param = pod; + + return 1; +} + +static int impl_get_props(void *object, struct spa_pod_builder *b, struct spa_pod **props) +{ + struct impl *impl = object; + struct graph *graph = &impl->graph; + struct spa_pod_frame f[2]; + uint32_t i; + char name[512]; + struct spa_pod *res; + + spa_pod_builder_push_object(b, &f[0], + SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); + spa_pod_builder_prop(b, SPA_PROP_params, 0); + spa_pod_builder_push_struct(b, &f[1]); + + for (i = 0; i < graph->n_control; i++) { + struct port *port = graph->control_port[i]; + struct node *node = port->node; + struct descriptor *desc = node->desc; + const struct spa_fga_descriptor *d = desc->desc; + struct spa_fga_port *p = &d->ports[port->p]; + + if (node->name[0] != '\0') + snprintf(name, sizeof(name), "%s:%s", node->name, p->name); + else + snprintf(name, sizeof(name), "%s", p->name); + + spa_pod_builder_string(b, name); + if (p->hint & SPA_FGA_HINT_BOOLEAN) { + spa_pod_builder_bool(b, port->control_data[0] <= 0.0f ? false : true); + } else if (p->hint & SPA_FGA_HINT_INTEGER) { + spa_pod_builder_int(b, (int32_t)port->control_data[0]); + } else { + spa_pod_builder_float(b, port->control_data[0]); + } + } + spa_pod_builder_pop(b, &f[1]); + res = spa_pod_builder_pop(b, &f[0]); + if (res == NULL) + return -ENOSPC; + if (props) + *props = res; + return 1; +} + +static int port_set_control_value(struct port *port, float *value, uint32_t id) +{ + struct node *node = port->node; + struct impl *impl = node->graph->impl; + + struct descriptor *desc = node->desc; + float old; + bool changed; + + old = port->control_data[id]; + port->control_data[id] = value ? *value : desc->default_control[port->idx]; + spa_log_info(impl->log, "control %d %d ('%s') from %f to %f", port->idx, id, + desc->desc->ports[port->p].name, old, port->control_data[id]); + changed = old != port->control_data[id]; + node->control_changed |= changed; + return changed ? 1 : 0; +} + +static int set_control_value(struct node *node, const char *name, float *value) +{ + struct port *port; + int count = 0; + uint32_t i, n_hndl; + + port = find_port(node, name, SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL); + if (port == NULL) + return -ENOENT; + + /* if we don't have any instances yet, set the first control value, we will + * copy to other instances later */ + n_hndl = SPA_MAX(1u, port->node->n_hndl); + for (i = 0; i < n_hndl; i++) + count += port_set_control_value(port, value, i); + + return count; +} + +static int parse_params(struct graph *graph, const struct spa_pod *pod) +{ + struct spa_pod_parser prs; + struct spa_pod_frame f; + int res, changed = 0; + struct node *def_node; + + def_node = spa_list_first(&graph->node_list, struct node, link); + + spa_pod_parser_pod(&prs, pod); + if (spa_pod_parser_push_struct(&prs, &f) < 0) + return 0; + + while (true) { + const char *name; + float value, *val = NULL; + double dbl_val; + bool bool_val; + int32_t int_val; + + if (spa_pod_parser_get_string(&prs, &name) < 0) + break; + if (spa_pod_parser_get_float(&prs, &value) >= 0) { + val = &value; + } else if (spa_pod_parser_get_double(&prs, &dbl_val) >= 0) { + value = (float)dbl_val; + val = &value; + } else if (spa_pod_parser_get_int(&prs, &int_val) >= 0) { + value = int_val; + val = &value; + } else if (spa_pod_parser_get_bool(&prs, &bool_val) >= 0) { + value = bool_val ? 1.0f : 0.0f; + val = &value; + } else { + struct spa_pod *pod; + spa_pod_parser_get_pod(&prs, &pod); + } + if ((res = set_control_value(def_node, name, val)) > 0) + changed += res; + } + return changed; +} + +static int impl_reset(void *object) +{ + struct impl *impl = object; + struct graph *graph = &impl->graph; + uint32_t i; + for (i = 0; i < graph->n_hndl; i++) { + struct graph_hndl *hndl = &graph->hndl[i]; + const struct spa_fga_descriptor *d = hndl->desc; + if (hndl->hndl == NULL || *hndl->hndl == NULL) + continue; + if (d->deactivate) + d->deactivate(*hndl->hndl); + if (d->activate) + d->activate(*hndl->hndl); + } + return 0; +} + +static void node_control_changed(struct node *node) +{ + const struct spa_fga_descriptor *d = node->desc->desc; + uint32_t i; + + if (!node->control_changed) + return; + + for (i = 0; i < node->n_hndl; i++) { + if (node->hndl[i] == NULL) + continue; + if (d->control_changed) + d->control_changed(node->hndl[i]); + } + node->control_changed = false; +} + +static int sync_volume(struct graph *graph, struct volume *vol) +{ + uint32_t i; + int res = 0; + + if (vol->n_ports == 0) + return 0; + for (i = 0; i < vol->n_volumes; i++) { + uint32_t n_port = i % vol->n_ports, n_hndl; + struct port *p = vol->ports[n_port]; + float v = vol->mute ? 0.0f : vol->volumes[i]; + switch (vol->scale[n_port]) { + case SCALE_CUBIC: + v = cbrtf(v); + break; + } + v = v * (vol->max[n_port] - vol->min[n_port]) + vol->min[n_port]; + + n_hndl = SPA_MAX(1u, p->node->n_hndl); + res += port_set_control_value(p, &v, i % n_hndl); + } + return res; +} + +static int impl_set_props(void *object, enum spa_direction direction, const struct spa_pod *props) +{ + struct impl *impl = object; + struct spa_pod_object *obj = (struct spa_pod_object *) props; + struct spa_pod_frame f[1]; + const struct spa_pod_prop *prop; + struct graph *graph = &impl->graph; + int changed = 0; + char buf[1024]; + struct spa_pod_dynamic_builder b; + struct volume *vol = &graph->volume[direction]; + bool do_volume = false; + + spa_pod_dynamic_builder_init(&b, buf, sizeof(buf), 1024); + spa_pod_builder_push_object(&b.b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); + + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_PROP_params: + changed += parse_params(graph, &prop->value); + spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop)); + break; + case SPA_PROP_mute: + { + bool mute; + if (spa_pod_get_bool(&prop->value, &mute) == 0) { + if (vol->mute != mute) { + vol->mute = mute; + do_volume = true; + } + } + spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop)); + break; + } + case SPA_PROP_channelVolumes: + { + uint32_t i, n_vols; + float vols[SPA_AUDIO_MAX_CHANNELS]; + + if ((n_vols = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vols, + SPA_AUDIO_MAX_CHANNELS)) > 0) { + if (vol->n_volumes != n_vols) + do_volume = true; + vol->n_volumes = n_vols; + for (i = 0; i < n_vols; i++) { + float v = vols[i]; + if (v != vol->volumes[i]) { + vol->volumes[i] = v; + do_volume = true; + } + } + } + spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop)); + break; + } + case SPA_PROP_softVolumes: + case SPA_PROP_softMute: + break; + default: + spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop)); + break; + } + } + if (do_volume && vol->n_ports != 0) { + float soft_vols[SPA_AUDIO_MAX_CHANNELS]; + uint32_t i; + + for (i = 0; i < vol->n_volumes; i++) + soft_vols[i] = (vol->mute || vol->volumes[i] == 0.0f) ? 0.0f : 1.0f; + + spa_pod_builder_prop(&b.b, SPA_PROP_softMute, 0); + spa_pod_builder_bool(&b.b, vol->mute); + spa_pod_builder_prop(&b.b, SPA_PROP_softVolumes, 0); + spa_pod_builder_array(&b.b, sizeof(float), SPA_TYPE_Float, + vol->n_volumes, soft_vols); + props = spa_pod_builder_pop(&b.b, &f[0]); + + sync_volume(graph, vol); + + } else { + props = spa_pod_builder_pop(&b.b, &f[0]); + } + spa_filter_graph_emit_apply_props(&impl->hooks, direction, props); + + spa_pod_dynamic_builder_clean(&b); + + if (changed > 0) { + struct node *node; + + spa_list_for_each(node, &graph->node_list, link) + node_control_changed(node); + + spa_filter_graph_emit_props_changed(&impl->hooks, SPA_DIRECTION_INPUT); + } + return 0; +} + +static uint32_t count_array(struct spa_json *json) +{ + struct spa_json it = *json; + char v[256]; + uint32_t count = 0; + while (spa_json_get_string(&it, v, sizeof(v)) > 0) + count++; + return count; +} + +static void plugin_unref(struct plugin *hndl) +{ + struct impl *impl = hndl->impl; + + if (--hndl->ref > 0) + return; + + spa_list_remove(&hndl->link); + if (hndl->hndl) + spa_plugin_loader_unload(impl->loader, hndl->hndl); + free(hndl); +} + +static inline const char *split_walk(const char *str, const char *delimiter, size_t * len, const char **state) +{ + const char *s = *state ? *state : str; + + s += strspn(s, delimiter); + if (*s == '\0') + return NULL; + + *len = strcspn(s, delimiter); + *state = s + *len; + + return s; +} + +static struct plugin *plugin_load(struct impl *impl, const char *type, const char *path) +{ + struct spa_handle *hndl = NULL; + struct plugin *plugin; + char module[PATH_MAX]; + char factory_name[256], dsp_ptr[256]; + void *iface; + int res; + + spa_list_for_each(plugin, &impl->plugin_list, link) { + if (spa_streq(plugin->type, type) && + spa_streq(plugin->path, path)) { + plugin->ref++; + return plugin; + } + } + + spa_scnprintf(module, sizeof(module), + "filter-graph/libspa-filter-graph-plugin-%s", type); + spa_scnprintf(factory_name, sizeof(factory_name), + "filter.graph.plugin.%s", type); + spa_scnprintf(dsp_ptr, sizeof(dsp_ptr), + "pointer:%p", impl->dsp); + + hndl = spa_plugin_loader_load(impl->loader, factory_name, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_LIBRARY_NAME, module), + SPA_DICT_ITEM("filter.graph.path", path), + SPA_DICT_ITEM("filter.graph.audio.dsp", dsp_ptr))); + + if (hndl == NULL) { + res = -errno; + spa_log_error(impl->log, "can't load plugin type '%s': %m", type); + goto exit; + } + if ((res = spa_handle_get_interface(hndl, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, &iface)) < 0) { + spa_log_error(impl->log, "can't find iface '%s': %s", + SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, spa_strerror(res)); + goto exit; + } + plugin = calloc(1, sizeof(*plugin)); + if (!plugin) { + res = -errno; + goto exit; + } + + plugin->ref = 1; + snprintf(plugin->type, sizeof(plugin->type), "%s", type); + snprintf(plugin->path, sizeof(plugin->path), "%s", path); + + spa_log_info(impl->log, "successfully opened '%s':'%s'", type, path); + + plugin->impl = impl; + plugin->hndl = hndl; + plugin->plugin = iface; + + spa_list_init(&plugin->descriptor_list); + spa_list_append(&impl->plugin_list, &plugin->link); + + return plugin; +exit: + if (hndl) + spa_plugin_loader_unload(impl->loader, hndl); + errno = -res; + return NULL; +} + +static void descriptor_unref(struct descriptor *desc) +{ + if (--desc->ref > 0) + return; + + spa_list_remove(&desc->link); + if (desc->desc) + spa_fga_descriptor_free(desc->desc); + plugin_unref(desc->plugin); + free(desc->input); + free(desc->output); + free(desc->control); + free(desc->default_control); + free(desc->notify); + free(desc); +} + +static struct descriptor *descriptor_load(struct impl *impl, const char *type, + const char *plugin, const char *label) +{ + struct plugin *pl; + struct descriptor *desc; + const struct spa_fga_descriptor *d; + uint32_t i, n_input, n_output, n_control, n_notify; + unsigned long p; + int res; + + if ((pl = plugin_load(impl, type, plugin)) == NULL) + return NULL; + + spa_list_for_each(desc, &pl->descriptor_list, link) { + if (spa_streq(desc->label, label)) { + desc->ref++; + + /* + * since ladspa_handle_load() increments the reference count of the handle, + * if the descriptor is found, then the handle's reference count + * has already been incremented to account for the descriptor, + * so we need to unref handle here since we're merely reusing + * thedescriptor, not creating a new one + */ + plugin_unref(pl); + return desc; + } + } + + desc = calloc(1, sizeof(*desc)); + desc->ref = 1; + desc->plugin = pl; + spa_list_init(&desc->link); + + if ((d = spa_fga_plugin_make_desc(pl->plugin, label)) == NULL) { + spa_log_error(impl->log, "cannot find label %s", label); + res = -ENOENT; + goto exit; + } + desc->desc = d; + snprintf(desc->label, sizeof(desc->label), "%s", label); + + n_input = n_output = n_control = n_notify = 0; + for (p = 0; p < d->n_ports; p++) { + struct spa_fga_port *fp = &d->ports[p]; + if (SPA_FGA_IS_PORT_AUDIO(fp->flags)) { + if (SPA_FGA_IS_PORT_INPUT(fp->flags)) + n_input++; + else if (SPA_FGA_IS_PORT_OUTPUT(fp->flags)) + n_output++; + } else if (SPA_FGA_IS_PORT_CONTROL(fp->flags)) { + if (SPA_FGA_IS_PORT_INPUT(fp->flags)) + n_control++; + else if (SPA_FGA_IS_PORT_OUTPUT(fp->flags)) + n_notify++; + } + } + desc->input = calloc(n_input, sizeof(unsigned long)); + desc->output = calloc(n_output, sizeof(unsigned long)); + desc->control = calloc(n_control, sizeof(unsigned long)); + desc->default_control = calloc(n_control, sizeof(float)); + desc->notify = calloc(n_notify, sizeof(unsigned long)); + + for (p = 0; p < d->n_ports; p++) { + struct spa_fga_port *fp = &d->ports[p]; + + if (SPA_FGA_IS_PORT_AUDIO(fp->flags)) { + if (SPA_FGA_IS_PORT_INPUT(fp->flags)) { + spa_log_info(impl->log, "using port %lu ('%s') as input %d", p, + fp->name, desc->n_input); + desc->input[desc->n_input++] = p; + } + else if (SPA_FGA_IS_PORT_OUTPUT(fp->flags)) { + spa_log_info(impl->log, "using port %lu ('%s') as output %d", p, + fp->name, desc->n_output); + desc->output[desc->n_output++] = p; + } + } else if (SPA_FGA_IS_PORT_CONTROL(fp->flags)) { + if (SPA_FGA_IS_PORT_INPUT(fp->flags)) { + spa_log_info(impl->log, "using port %lu ('%s') as control %d", p, + fp->name, desc->n_control); + desc->control[desc->n_control++] = p; + } + else if (SPA_FGA_IS_PORT_OUTPUT(fp->flags)) { + spa_log_info(impl->log, "using port %lu ('%s') as notify %d", p, + fp->name, desc->n_notify); + desc->notify[desc->n_notify++] = p; + } + } + } + if (desc->n_input == 0 && desc->n_output == 0 && desc->n_control == 0 && desc->n_notify == 0) { + spa_log_error(impl->log, "plugin has no input and no output ports"); + res = -ENOTSUP; + goto exit; + } + for (i = 0; i < desc->n_control; i++) { + p = desc->control[i]; + desc->default_control[i] = get_default(impl, desc, p); + spa_log_info(impl->log, "control %d ('%s') default to %f", i, + d->ports[p].name, desc->default_control[i]); + } + spa_list_append(&pl->descriptor_list, &desc->link); + + return desc; + +exit: + descriptor_unref(desc); + errno = -res; + return NULL; +} + +/** + * { + * ... + * } + */ +static int parse_config(struct node *node, struct spa_json *config) +{ + const char *val, *s = config->cur; + struct impl *impl = node->graph->impl; + int res = 0, len; + struct spa_error_location loc; + + if ((len = spa_json_next(config, &val)) <= 0) { + res = -EINVAL; + goto done; + } + if (spa_json_is_null(val, len)) + goto done; + + if (spa_json_is_container(val, len)) { + len = spa_json_container_len(config, val, len); + if (len == 0) { + res = -EINVAL; + goto done; + } + } + if ((node->config = malloc(len+1)) == NULL) { + res = -errno; + goto done; + } + + spa_json_parse_stringn(val, len, node->config, len+1); +done: + if (spa_json_get_error(config, s, &loc)) + spa_debug_log_error_location(impl->log, SPA_LOG_LEVEL_WARN, + &loc, "error: %s", loc.reason); + return res; +} + +/** + * { + * "Reverb tail" = 2.0 + * ... + * } + */ +static int parse_control(struct node *node, struct spa_json *control) +{ + struct impl *impl = node->graph->impl; + char key[256]; + const char *val; + int len; + + while ((len = spa_json_object_next(control, key, sizeof(key), &val)) > 0) { + float fl; + int res; + + if (spa_json_parse_float(val, len, &fl) <= 0) { + spa_log_warn(impl->log, "control '%s' expects a number, ignoring", key); + } + else if ((res = set_control_value(node, key, &fl)) < 0) { + spa_log_warn(impl->log, "control '%s' can not be set: %s", key, spa_strerror(res)); + } + } + return 0; +} + +/** + * output = [name:][portname] + * input = [name:][portname] + * ... + */ +static int parse_link(struct graph *graph, struct spa_json *json) +{ + struct impl *impl = graph->impl; + char key[256]; + char output[256] = ""; + char input[256] = ""; + const char *val; + struct node *def_in_node, *def_out_node; + struct port *in_port, *out_port; + struct link *link; + int len; + + if (spa_list_is_empty(&graph->node_list)) { + spa_log_error(impl->log, "can't make links in graph without nodes"); + return -EINVAL; + } + + while ((len = spa_json_object_next(json, key, sizeof(key), &val)) > 0) { + if (spa_streq(key, "output")) { + if (spa_json_parse_stringn(val, len, output, sizeof(output)) <= 0) { + spa_log_error(impl->log, "output expects a string"); + return -EINVAL; + } + } + else if (spa_streq(key, "input")) { + if (spa_json_parse_stringn(val, len, input, sizeof(input)) <= 0) { + spa_log_error(impl->log, "input expects a string"); + return -EINVAL; + } + } + else { + spa_log_error(impl->log, "unexpected link key '%s'", key); + } + } + def_out_node = spa_list_first(&graph->node_list, struct node, link); + def_in_node = spa_list_last(&graph->node_list, struct node, link); + + out_port = find_port(def_out_node, output, SPA_FGA_PORT_OUTPUT); + in_port = find_port(def_in_node, input, SPA_FGA_PORT_INPUT); + + if (out_port == NULL && out_port == NULL) { + /* try control ports */ + out_port = find_port(def_out_node, output, SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL); + in_port = find_port(def_in_node, input, SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL); + } + if (in_port == NULL || out_port == NULL) { + if (out_port == NULL) + spa_log_error(impl->log, "unknown output port %s", output); + if (in_port == NULL) + spa_log_error(impl->log, "unknown input port %s", input); + return -ENOENT; + } + + if (in_port->n_links > 0) { + spa_log_info(impl->log, "Can't have more than 1 link to %s, use a mixer", input); + return -ENOTSUP; + } + + if ((link = calloc(1, sizeof(*link))) == NULL) + return -errno; + + link->output = out_port; + link->input = in_port; + + spa_log_info(impl->log, "linking %s:%s -> %s:%s", + out_port->node->name, + out_port->node->desc->desc->ports[out_port->p].name, + in_port->node->name, + in_port->node->desc->desc->ports[in_port->p].name); + + spa_list_append(&out_port->link_list, &link->output_link); + out_port->n_links++; + spa_list_append(&in_port->link_list, &link->input_link); + in_port->n_links++; + + in_port->node->n_deps++; + + spa_list_append(&graph->link_list, &link->link); + + return 0; +} + +static void link_free(struct link *link) +{ + spa_list_remove(&link->input_link); + link->input->n_links--; + link->input->node->n_deps--; + spa_list_remove(&link->output_link); + link->output->n_links--; + spa_list_remove(&link->link); + free(link); +} + +/** + * { + * control = [name:][portname] + * min = <float, default 0.0> + * max = <float, default 1.0> + * scale = <string, default "linear", options "linear","cubic"> + * } + */ +static int parse_volume(struct graph *graph, struct spa_json *json, enum spa_direction direction) +{ + struct impl *impl = graph->impl; + char key[256]; + char control[256] = ""; + char scale[64] = "linear"; + float min = 0.0f, max = 1.0f; + const char *val; + struct node *def_control; + struct port *port; + struct volume *vol = &graph->volume[direction]; + int len; + + if (spa_list_is_empty(&graph->node_list)) { + spa_log_error(impl->log, "can't set volume in graph without nodes"); + return -EINVAL; + } + while ((len = spa_json_object_next(json, key, sizeof(key), &val)) > 0) { + if (spa_streq(key, "control")) { + if (spa_json_parse_stringn(val, len, control, sizeof(control)) <= 0) { + spa_log_error(impl->log, "control expects a string"); + return -EINVAL; + } + } + else if (spa_streq(key, "min")) { + if (spa_json_parse_float(val, len, &min) <= 0) { + spa_log_error(impl->log, "min expects a float"); + return -EINVAL; + } + } + else if (spa_streq(key, "max")) { + if (spa_json_parse_float(val, len, &max) <= 0) { + spa_log_error(impl->log, "max expects a float"); + return -EINVAL; + } + } + else if (spa_streq(key, "scale")) { + if (spa_json_parse_stringn(val, len, scale, sizeof(scale)) <= 0) { + spa_log_error(impl->log, "scale expects a string"); + return -EINVAL; + } + } + else { + spa_log_error(impl->log, "unexpected volume key '%s'", key); + } + } + if (direction == SPA_DIRECTION_INPUT) + def_control = spa_list_first(&graph->node_list, struct node, link); + else + def_control = spa_list_last(&graph->node_list, struct node, link); + + port = find_port(def_control, control, SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL); + if (port == NULL) { + spa_log_error(impl->log, "unknown control port %s", control); + return -ENOENT; + } + if (vol->n_ports >= SPA_AUDIO_MAX_CHANNELS) { + spa_log_error(impl->log, "too many volume controls"); + return -ENOSPC; + } + if (spa_streq(scale, "linear")) { + vol->scale[vol->n_ports] = SCALE_LINEAR; + } else if (spa_streq(scale, "cubic")) { + vol->scale[vol->n_ports] = SCALE_CUBIC; + } else { + spa_log_error(impl->log, "Invalid scale value '%s', use one of linear or cubic", scale); + return -EINVAL; + } + spa_log_info(impl->log, "volume %d: \"%s:%s\" min:%f max:%f scale:%s", vol->n_ports, port->node->name, + port->node->desc->desc->ports[port->p].name, min, max, scale); + + vol->ports[vol->n_ports] = port; + vol->min[vol->n_ports] = min; + vol->max[vol->n_ports] = max; + vol->n_ports++; + + return 0; +} + +/** + * type = ladspa + * name = rev + * plugin = g2reverb + * label = G2reverb + * config = { + * ... + * } + * control = { + * ... + * } + */ +static int load_node(struct graph *graph, struct spa_json *json) +{ + struct impl *impl = graph->impl; + struct spa_json control, config; + struct descriptor *desc; + struct node *node; + const char *val; + char key[256]; + char type[256] = ""; + char name[256] = ""; + char plugin[256] = ""; + char label[256] = ""; + bool have_control = false; + bool have_config = false; + uint32_t i; + int res, len; + + while ((len = spa_json_object_next(json, key, sizeof(key), &val)) > 0) { + if (spa_streq("type", key)) { + if (spa_json_parse_stringn(val, len, type, sizeof(type)) <= 0) { + spa_log_error(impl->log, "type expects a string"); + return -EINVAL; + } + } else if (spa_streq("name", key)) { + if (spa_json_parse_stringn(val, len, name, sizeof(name)) <= 0) { + spa_log_error(impl->log, "name expects a string"); + return -EINVAL; + } + } else if (spa_streq("plugin", key)) { + if (spa_json_parse_stringn(val, len, plugin, sizeof(plugin)) <= 0) { + spa_log_error(impl->log, "plugin expects a string"); + return -EINVAL; + } + } else if (spa_streq("label", key)) { + if (spa_json_parse_stringn(val, len, label, sizeof(label)) <= 0) { + spa_log_error(impl->log, "label expects a string"); + return -EINVAL; + } + } else if (spa_streq("control", key)) { + if (!spa_json_is_object(val, len)) { + spa_log_error(impl->log, "control expects an object"); + return -EINVAL; + } + spa_json_enter(json, &control); + have_control = true; + } else if (spa_streq("config", key)) { + config = SPA_JSON_START(json, val); + have_config = true; + } else { + spa_log_warn(impl->log, "unexpected node key '%s'", key); + } + } + if (spa_streq(type, "builtin")) + snprintf(plugin, sizeof(plugin), "%s", "builtin"); + else if (spa_streq(type, "")) { + spa_log_error(impl->log, "missing plugin type"); + return -EINVAL; + } + + spa_log_info(impl->log, "loading type:%s plugin:%s label:%s", type, plugin, label); + + if ((desc = descriptor_load(graph->impl, type, plugin, label)) == NULL) + return -errno; + + node = calloc(1, sizeof(*node)); + if (node == NULL) + return -errno; + + node->graph = graph; + node->desc = desc; + snprintf(node->name, sizeof(node->name), "%s", name); + + node->input_port = calloc(desc->n_input, sizeof(struct port)); + node->output_port = calloc(desc->n_output, sizeof(struct port)); + node->control_port = calloc(desc->n_control, sizeof(struct port)); + node->notify_port = calloc(desc->n_notify, sizeof(struct port)); + + spa_log_info(impl->log, "loaded n_input:%d n_output:%d n_control:%d n_notify:%d", + desc->n_input, desc->n_output, + desc->n_control, desc->n_notify); + + for (i = 0; i < desc->n_input; i++) { + struct port *port = &node->input_port[i]; + port->node = node; + port->idx = i; + port->external = SPA_ID_INVALID; + port->p = desc->input[i]; + spa_list_init(&port->link_list); + } + for (i = 0; i < desc->n_output; i++) { + struct port *port = &node->output_port[i]; + port->node = node; + port->idx = i; + port->external = SPA_ID_INVALID; + port->p = desc->output[i]; + spa_list_init(&port->link_list); + } + for (i = 0; i < desc->n_control; i++) { + struct port *port = &node->control_port[i]; + port->node = node; + port->idx = i; + port->external = SPA_ID_INVALID; + port->p = desc->control[i]; + spa_list_init(&port->link_list); + port->control_data[0] = desc->default_control[i]; + } + for (i = 0; i < desc->n_notify; i++) { + struct port *port = &node->notify_port[i]; + port->node = node; + port->idx = i; + port->external = SPA_ID_INVALID; + port->p = desc->notify[i]; + spa_list_init(&port->link_list); + } + if (have_config) + if ((res = parse_config(node, &config)) < 0) + spa_log_warn(impl->log, "error parsing config: %s", spa_strerror(res)); + if (have_control) + parse_control(node, &control); + + spa_list_append(&graph->node_list, &node->link); + + return 0; +} + +static void node_cleanup(struct node *node) +{ + const struct spa_fga_descriptor *d = node->desc->desc; + struct impl *impl = node->graph->impl; + uint32_t i; + + for (i = 0; i < node->n_hndl; i++) { + if (node->hndl[i] == NULL) + continue; + spa_log_info(impl->log, "cleanup %s %s[%d]", d->name, node->name, i); + if (d->deactivate) + d->deactivate(node->hndl[i]); + d->cleanup(node->hndl[i]); + node->hndl[i] = NULL; + } +} + +static int port_ensure_data(struct port *port, uint32_t i, uint32_t max_samples) +{ + float *data; + struct node *node = port->node; + const struct spa_fga_descriptor *d = node->desc->desc; + struct impl *impl = node->graph->impl; + + if ((data = port->audio_mem[i]) == NULL) { + data = calloc(max_samples, sizeof(float) + impl->max_align); + if (data == NULL) { + spa_log_error(impl->log, "cannot create port data: %m"); + return -errno; + } + port->audio_mem[i] = data; + port->audio_data[i] = SPA_PTR_ALIGN(data, impl->max_align, void); + } + spa_log_info(impl->log, "connect output port %s[%d]:%s %p", + node->name, i, d->ports[port->p].name, port->audio_data[i]); + d->connect_port(port->node->hndl[i], port->p, port->audio_data[i]); + return 0; +} + +static void port_free_data(struct port *port, uint32_t i) +{ + free(port->audio_mem[i]); + port->audio_mem[i] = NULL; + port->audio_data[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++) + port_free_data(&node->output_port[j], i); + } + node_cleanup(node); + descriptor_unref(node->desc); + free(node->input_port); + free(node->output_port); + free(node->control_port); + free(node->notify_port); + free(node->config); + free(node); +} + +static int impl_deactivate(void *object) +{ + struct impl *impl = object; + struct graph *graph = &impl->graph; + struct node *node; + + if (!graph->activated) + return 0; + + graph->activated = false; + spa_list_for_each(node, &graph->node_list, link) + node_cleanup(node); + return 0; +} + +static int impl_activate(void *object, const struct spa_dict *props) +{ + struct impl *impl = object; + struct graph *graph = &impl->graph; + struct node *node; + struct port *port; + struct link *link; + struct descriptor *desc; + const struct spa_fga_descriptor *d; + const struct spa_fga_plugin *p; + uint32_t i, j, max_samples = impl->quantum_limit; + int res; + float *sd, *dd, *data; + const char *rate; + + if (graph->activated) + return 0; + + graph->activated = true; + + rate = spa_dict_lookup(props, SPA_KEY_AUDIO_RATE); + impl->rate = rate ? atoi(rate) : DEFAULT_RATE; + + /* first make instances */ + spa_list_for_each(node, &graph->node_list, link) { + node_cleanup(node); + + desc = node->desc; + d = desc->desc; + p = desc->plugin->plugin; + + for (i = 0; i < node->n_hndl; i++) { + spa_log_info(impl->log, "instantiate %s %s[%d] rate:%lu", d->name, node->name, i, impl->rate); + errno = EINVAL; + if ((node->hndl[i] = d->instantiate(p, d, impl->rate, i, node->config)) == NULL) { + spa_log_error(impl->log, "cannot create plugin instance %d rate:%lu: %m", i, impl->rate); + res = -errno; + goto error; + } + } + } + + /* then link ports */ + spa_list_for_each(node, &graph->node_list, link) { + desc = node->desc; + d = desc->desc; + if (d->flags & SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA) { + sd = dd = NULL; + } + else { + sd = impl->silence_data; + dd = impl->discard_data; + } + for (i = 0; i < node->n_hndl; i++) { + for (j = 0; j < desc->n_input; j++) { + port = &node->input_port[j]; + if (!spa_list_is_empty(&port->link_list)) { + link = spa_list_first(&port->link_list, struct link, input_link); + if ((res = port_ensure_data(link->output, i, max_samples)) < 0) + goto error; + data = link->output->audio_data[i]; + } else { + data = sd; + } + spa_log_info(impl->log, "connect input port %s[%d]:%s %p", + node->name, i, d->ports[port->p].name, data); + d->connect_port(node->hndl[i], port->p, data); + } + for (j = 0; j < desc->n_output; j++) { + port = &node->output_port[j]; + if (port->audio_data[i] == NULL) { + spa_log_info(impl->log, "connect output port %s[%d]:%s %p", + node->name, i, d->ports[port->p].name, dd); + d->connect_port(node->hndl[i], port->p, dd); + } + } + for (j = 0; j < desc->n_control; j++) { + port = &node->control_port[j]; + + if (!spa_list_is_empty(&port->link_list)) { + link = spa_list_first(&port->link_list, struct link, input_link); + data = &link->output->control_data[i]; + } else { + data = &port->control_data[i]; + } + spa_log_info(impl->log, "connect control port %s[%d]:%s %p", + node->name, i, d->ports[port->p].name, data); + d->connect_port(node->hndl[i], port->p, data); + } + for (j = 0; j < desc->n_notify; j++) { + port = &node->notify_port[j]; + spa_log_info(impl->log, "connect notify port %s[%d]:%s %p", + node->name, i, d->ports[port->p].name, + &port->control_data[i]); + d->connect_port(node->hndl[i], port->p, &port->control_data[i]); + } + } + } + + /* now activate */ + spa_list_for_each(node, &graph->node_list, link) { + desc = node->desc; + d = desc->desc; + + for (i = 0; i < node->n_hndl; i++) { + if (d->activate) + d->activate(node->hndl[i]); + if (node->control_changed && d->control_changed) + d->control_changed(node->hndl[i]); + } + } + + spa_filter_graph_emit_props_changed(&impl->hooks, SPA_DIRECTION_INPUT); + return 0; +error: + impl_deactivate(impl); + return res; +} + +/* any default values for the controls are set in the first instance + * of the control data. Duplicate this to the other instances now. */ +static void setup_node_controls(struct node *node) +{ + uint32_t i, j; + uint32_t n_hndl = node->n_hndl; + uint32_t n_ports = node->desc->n_control; + struct port *ports = node->control_port; + + for (i = 0; i < n_ports; i++) { + struct port *port = &ports[i]; + for (j = 1; j < n_hndl; j++) + port->control_data[j] = port->control_data[0]; + } +} + +static struct node *find_next_node(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; +} + +static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_json *outputs) +{ + 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; + struct descriptor *desc; + const struct spa_fga_descriptor *d; + char v[256]; + bool allow_unused; + + first = spa_list_first(&graph->node_list, struct node, link); + last = spa_list_last(&graph->node_list, struct node, link); + + /* calculate the number of inputs and outputs into the graph. + * If we have a list of inputs/outputs, just count them. Otherwise + * we count all input ports of the first node and all output + * ports of the last node */ + if (inputs != NULL) + n_input = count_array(inputs); + else + n_input = first->desc->n_input; + + if (outputs != NULL) + n_output = count_array(outputs); + else + n_output = last->desc->n_output; + + /* we allow unconnected ports when not explicitly given and the nodes support + * NULL data */ + allow_unused = inputs == NULL && outputs == NULL && + SPA_FLAG_IS_SET(first->desc->desc->flags, SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA) && + SPA_FLAG_IS_SET(last->desc->desc->flags, SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA); + + if (n_input == 0) { + spa_log_error(impl->log, "no inputs"); + res = -EINVAL; + goto error; + } + if (n_output == 0) { + spa_log_error(impl->log, "no outputs"); + res = -EINVAL; + goto error; + } + + if (impl->info.n_inputs == 0) + impl->info.n_inputs = n_input; + + /* compare to the requested number of inputs and duplicate the + * graph n_hndl times when needed. */ + n_hndl = impl->info.n_inputs / n_input; + + if (impl->info.n_outputs == 0) + impl->info.n_outputs = n_output * n_hndl; + + if (n_hndl != impl->info.n_outputs / n_output) { + spa_log_error(impl->log, "invalid ports. The input stream has %1$d ports and " + "the filter has %2$d inputs. The output stream has %3$d ports " + "and the filter has %4$d outputs. input:%1$d / input:%2$d != " + "output:%3$d / output:%4$d. Check inputs and outputs objects.", + impl->info.n_inputs, n_input, + impl->info.n_outputs, n_output); + res = -EINVAL; + goto error; + } + if (n_hndl > MAX_HNDL) { + spa_log_error(impl->log, "too many ports. %d > %d", n_hndl, MAX_HNDL); + res = -EINVAL; + goto error; + } + if (n_hndl == 0) { + n_hndl = 1; + if (!allow_unused) + spa_log_warn(impl->log, "The input stream has %1$d ports and " + "the filter has %2$d inputs. The output stream has %3$d ports " + "and the filter has %4$d outputs. Some filter ports will be " + "unconnected..", + impl->info.n_inputs, n_input, + impl->info.n_outputs, n_output); + + if (impl->info.n_outputs == 0) + impl->info.n_outputs = n_output * n_hndl; + } + spa_log_info(impl->log, "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) { + node->n_hndl = n_hndl; + desc = node->desc; + n_control += desc->n_control; + n_nodes++; + setup_node_controls(node); + } + graph->n_input = 0; + graph->input = calloc(n_input * 16 * n_hndl, sizeof(struct graph_port)); + graph->n_output = 0; + graph->output = calloc(n_output * n_hndl, sizeof(struct graph_port)); + + /* now collect all input and output ports for all the handles. */ + for (i = 0; i < n_hndl; i++) { + if (inputs == NULL) { + desc = first->desc; + d = desc->desc; + for (j = 0; j < desc->n_input; j++) { + gp = &graph->input[graph->n_input++]; + spa_log_info(impl->log, "input port %s[%d]:%s", + first->name, i, d->ports[desc->input[j]].name); + gp->desc = d; + gp->hndl = &first->hndl[i]; + gp->port = desc->input[j]; + } + } else { + struct spa_json it = *inputs; + while (spa_json_get_string(&it, v, sizeof(v)) > 0) { + if (spa_streq(v, "null")) { + gp = &graph->input[graph->n_input++]; + gp->desc = NULL; + spa_log_info(impl->log, "ignore input port %d", graph->n_input); + } else if ((port = find_port(first, v, SPA_FGA_PORT_INPUT)) == NULL) { + res = -ENOENT; + spa_log_error(impl->log, "input port %s not found", v); + goto error; + } else { + bool disabled = false; + + desc = port->node->desc; + d = desc->desc; + if (i == 0 && port->external != SPA_ID_INVALID) { + spa_log_error(impl->log, "input port %s[%d]:%s already used as input %d, use mixer", + port->node->name, i, d->ports[port->p].name, + port->external); + res = -EBUSY; + goto error; + } + if (port->n_links > 0) { + spa_log_error(impl->log, "input port %s[%d]:%s already used by link, use mixer", + port->node->name, i, d->ports[port->p].name); + res = -EBUSY; + goto error; + } + + if (d->flags & SPA_FGA_DESCRIPTOR_COPY) { + for (j = 0; j < desc->n_output; j++) { + struct port *p = &port->node->output_port[j]; + struct link *link; + + gp = NULL; + spa_list_for_each(link, &p->link_list, output_link) { + struct port *peer = link->input; + + spa_log_info(impl->log, "copy input port %s[%d]:%s", + port->node->name, i, + d->ports[port->p].name); + peer->external = graph->n_input; + gp = &graph->input[graph->n_input++]; + gp->desc = peer->node->desc->desc; + gp->hndl = &peer->node->hndl[i]; + gp->port = peer->p; + gp->next = true; + disabled = true; + } + if (gp != NULL) + gp->next = false; + } + port->node->disabled = disabled; + } + if (!disabled) { + spa_log_info(impl->log, "input port %s[%d]:%s", + port->node->name, i, d->ports[port->p].name); + port->external = graph->n_input; + gp = &graph->input[graph->n_input++]; + gp->desc = d; + gp->hndl = &port->node->hndl[i]; + gp->port = port->p; + gp->next = false; + } + } + } + } + if (outputs == NULL) { + desc = last->desc; + d = desc->desc; + for (j = 0; j < desc->n_output; j++) { + gp = &graph->output[graph->n_output++]; + spa_log_info(impl->log, "output port %s[%d]:%s", + last->name, i, d->ports[desc->output[j]].name); + gp->desc = d; + gp->hndl = &last->hndl[i]; + gp->port = desc->output[j]; + } + } else { + struct spa_json it = *outputs; + while (spa_json_get_string(&it, v, sizeof(v)) > 0) { + gp = &graph->output[graph->n_output]; + if (spa_streq(v, "null")) { + gp->desc = NULL; + spa_log_info(impl->log, "silence output port %d", graph->n_output); + } else if ((port = find_port(last, v, SPA_FGA_PORT_OUTPUT)) == NULL) { + res = -ENOENT; + spa_log_error(impl->log, "output port %s not found", v); + goto error; + } else { + desc = port->node->desc; + d = desc->desc; + if (i == 0 && port->external != SPA_ID_INVALID) { + spa_log_error(impl->log, "output port %s[%d]:%s already used as output %d, use copy", + port->node->name, i, d->ports[port->p].name, + port->external); + res = -EBUSY; + goto error; + } + if (port->n_links > 0) { + spa_log_error(impl->log, "output port %s[%d]:%s already used by link, use copy", + port->node->name, i, d->ports[port->p].name); + res = -EBUSY; + goto error; + } + spa_log_info(impl->log, "output port %s[%d]:%s", + port->node->name, i, d->ports[port->p].name); + port->external = graph->n_output; + gp->desc = d; + gp->hndl = &port->node->hndl[i]; + gp->port = port->p; + } + graph->n_output++; + } + } + } + + /* order all nodes based on dependencies */ + graph->n_hndl = 0; + graph->hndl = calloc(n_nodes * n_hndl, sizeof(struct graph_hndl)); + graph->n_control = 0; + graph->control_port = calloc(n_control, sizeof(struct port *)); + while (true) { + if ((node = find_next_node(graph)) == NULL) + break; + + desc = node->desc; + d = desc->desc; + + if (!node->disabled) { + for (i = 0; i < n_hndl; i++) { + gh = &graph->hndl[graph->n_hndl++]; + gh->hndl = &node->hndl[i]; + gh->desc = d; + } + } + for (i = 0; i < desc->n_output; i++) { + spa_list_for_each(link, &node->output_port[i].link_list, output_link) + link->input->node->n_deps--; + } + for (i = 0; i < desc->n_notify; i++) { + spa_list_for_each(link, &node->notify_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++) { + graph->control_port[graph->n_control] = &node->control_port[i]; + graph->n_control++; + } + } + res = 0; +error: + return res; +} + +/** + * filter.graph = { + * nodes = [ + * { ... } ... + * ] + * links = [ + * { ... } ... + * ] + * inputs = [ ] + * outputs = [ ] + * input.volumes = [ + * ... + * ] + * output.volumes = [ + * ... + * ] + * } + */ +static int load_graph(struct graph *graph, const struct spa_dict *props) +{ + struct impl *impl = graph->impl; + struct spa_json it[2]; + struct spa_json inputs, outputs, *pinputs = NULL, *poutputs = NULL; + struct spa_json ivolumes, ovolumes, *pivolumes = NULL, *povolumes = NULL; + struct spa_json nodes, *pnodes = NULL, links, *plinks = NULL; + const char *json, *val; + char key[256]; + int res, len; + + spa_list_init(&graph->node_list); + spa_list_init(&graph->link_list); + + if ((json = spa_dict_lookup(props, "filter.graph")) == NULL) { + spa_log_error(impl->log, "missing filter.graph property"); + return -EINVAL; + } + + if (spa_json_begin_object(&it[0], json, strlen(json)) <= 0) { + spa_log_error(impl->log, "filter.graph must be an object"); + return -EINVAL; + } + + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { + if (spa_streq("n_inputs", key)) { + if (spa_json_parse_int(val, len, &res) <= 0) { + spa_log_error(impl->log, "%s expects an integer", key); + return -EINVAL; + } + impl->info.n_inputs = res; + } + else if (spa_streq("n_outputs", key)) { + if (spa_json_parse_int(val, len, &res) <= 0) { + spa_log_error(impl->log, "%s expects an integer", key); + return -EINVAL; + } + impl->info.n_outputs = res; + } + else if (spa_streq("nodes", key)) { + if (!spa_json_is_array(val, len)) { + spa_log_error(impl->log, "%s expects an array", key); + return -EINVAL; + } + spa_json_enter(&it[0], &nodes); + pnodes = &nodes; + } + else if (spa_streq("links", key)) { + if (!spa_json_is_array(val, len)) { + spa_log_error(impl->log, "%s expects an array", key); + return -EINVAL; + } + spa_json_enter(&it[0], &links); + plinks = &links; + } + else if (spa_streq("inputs", key)) { + if (!spa_json_is_array(val, len)) { + spa_log_error(impl->log, "%s expects an array", key); + return -EINVAL; + } + spa_json_enter(&it[0], &inputs); + pinputs = &inputs; + } + else if (spa_streq("outputs", key)) { + if (!spa_json_is_array(val, len)) { + spa_log_error(impl->log, "%s expects an array", key); + return -EINVAL; + } + spa_json_enter(&it[0], &outputs); + poutputs = &outputs; + } + else if (spa_streq("capture.volumes", key) || + spa_streq("input.volumes", key)) { + if (!spa_json_is_array(val, len)) { + spa_log_error(impl->log, "%s expects an array", key); + return -EINVAL; + } + spa_json_enter(&it[0], &ivolumes); + pivolumes = &ivolumes; + } + else if (spa_streq("playback.volumes", key) || + spa_streq("output.volumes", key)) { + if (!spa_json_is_array(val, len)) { + spa_log_error(impl->log, "%s expects an array", key); + return -EINVAL; + } + spa_json_enter(&it[0], &ovolumes); + povolumes = &ovolumes; + } else { + spa_log_warn(impl->log, "unexpected graph key '%s'", key); + } + } + if (pnodes == NULL) { + spa_log_error(impl->log, "filter.graph is missing a nodes array"); + return -EINVAL; + } + while (spa_json_enter_object(pnodes, &it[1]) > 0) { + if ((res = load_node(graph, &it[1])) < 0) + return res; + } + if (plinks != NULL) { + while (spa_json_enter_object(plinks, &it[1]) > 0) { + if ((res = parse_link(graph, &it[1])) < 0) + return res; + } + } + if (pivolumes != NULL) { + while (spa_json_enter_object(pivolumes, &it[1]) > 0) { + if ((res = parse_volume(graph, &it[1], SPA_DIRECTION_INPUT)) < 0) + return res; + } + } + if (povolumes != NULL) { + while (spa_json_enter_object(povolumes, &it[1]) > 0) { + if ((res = parse_volume(graph, &it[1], SPA_DIRECTION_OUTPUT)) < 0) + return res; + } + } + return setup_graph(graph, pinputs, poutputs); +} + +static void graph_free(struct graph *graph) +{ + struct link *link; + struct node *node; + spa_list_consume(link, &graph->link_list, link) + link_free(link); + spa_list_consume(node, &graph->node_list, link) + node_free(node); + free(graph->input); + free(graph->output); + free(graph->hndl); + free(graph->control_port); +} + +static const struct spa_filter_graph_methods impl_filter_graph = { + SPA_VERSION_FILTER_GRAPH_METHODS, + .add_listener = impl_add_listener, + .enum_prop_info = impl_enum_prop_info, + .get_props = impl_get_props, + .set_props = impl_set_props, + .activate = impl_activate, + .deactivate = impl_deactivate, + .reset = impl_reset, + .process = impl_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_FilterGraph)) + *interface = &this->filter_graph; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct impl *impl = (struct impl *) handle; + + graph_free(&impl->graph); + + if (impl->dsp) + spa_fga_dsp_free(impl->dsp); + + free(impl->silence_data); + free(impl->discard_data); + 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 *impl; + uint32_t i; + int res; + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + impl = (struct impl *) handle; + impl->graph.impl = impl; + + impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + spa_log_topic_init(impl->log, &log_topic); + + impl->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); + impl->max_align = spa_cpu_get_max_align(impl->cpu); + + impl->dsp = spa_fga_dsp_new(impl->cpu ? spa_cpu_get_flags(impl->cpu) : 0); + + impl->loader = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_PluginLoader); + + spa_list_init(&impl->plugin_list); + + 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, &impl->quantum_limit, 0); + if (spa_streq(k, "filter-graph.n_inputs")) + spa_atou32(s, &impl->info.n_inputs, 0); + if (spa_streq(k, "filter-graph.n_outputs")) + spa_atou32(s, &impl->info.n_outputs, 0); + } + if (impl->quantum_limit == 0) + return -EINVAL; + + impl->silence_data = calloc(impl->quantum_limit, sizeof(float)); + if (impl->silence_data == NULL) { + res = -errno; + goto error; + } + + impl->discard_data = calloc(impl->quantum_limit, sizeof(float)); + if (impl->discard_data == NULL) { + res = -errno; + goto error; + } + + if ((res = load_graph(&impl->graph, info)) < 0) { + spa_log_error(impl->log, "can't load graph: %s", spa_strerror(res)); + goto error; + } + + impl->filter_graph.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_FilterGraph, + SPA_VERSION_FILTER_GRAPH, + &impl_filter_graph, impl); + spa_hook_list_init(&impl->hooks); + + return 0; +error: + free(impl->silence_data); + free(impl->discard_data); + return res; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_FilterGraph,}, +}; + +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 struct spa_handle_factory spa_filter_graph_factory = { + SPA_VERSION_HANDLE_FACTORY, + "filter.graph", + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; + +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_filter_graph_factory; + break; + default: + return 0; + } + (*index)++; + return 1; +} diff --git a/src/modules/module-filter-chain/ladspa.h b/spa/plugins/filter-graph/ladspa.h similarity index 100% rename from src/modules/module-filter-chain/ladspa.h rename to spa/plugins/filter-graph/ladspa.h diff --git a/spa/plugins/filter-graph/ladspa_plugin.c b/spa/plugins/filter-graph/ladspa_plugin.c new file mode 100644 index 00000000..45026c8e --- /dev/null +++ b/spa/plugins/filter-graph/ladspa_plugin.c @@ -0,0 +1,385 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include "config.h" + +#include <dlfcn.h> +#include <math.h> +#include <limits.h> + +#include <spa/utils/result.h> +#include <spa/utils/defs.h> +#include <spa/utils/list.h> +#include <spa/utils/string.h> +#include <spa/support/log.h> + +#include "audio-plugin.h" +#include "ladspa.h" + +struct plugin { + struct spa_handle handle; + struct spa_fga_plugin plugin; + + struct spa_log *log; + + void *hndl; + LADSPA_Descriptor_Function desc_func; +}; + +struct descriptor { + struct spa_fga_descriptor desc; + const LADSPA_Descriptor *d; +}; + +static void *ladspa_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc, + unsigned long SampleRate, int index, const char *config) +{ + struct descriptor *d = (struct descriptor *)desc; + return d->d->instantiate(d->d, SampleRate); +} + +static const LADSPA_Descriptor *find_desc(LADSPA_Descriptor_Function desc_func, const char *name) +{ + unsigned long i; + for (i = 0; ;i++) { + const LADSPA_Descriptor *d = desc_func(i); + if (d == NULL) + break; + if (spa_streq(d->Label, name)) + return d; + } + return NULL; +} + +static float get_default(struct spa_fga_port *port, LADSPA_PortRangeHintDescriptor hint, + LADSPA_Data lower, LADSPA_Data upper) +{ + LADSPA_Data def; + + switch (hint & LADSPA_HINT_DEFAULT_MASK) { + case LADSPA_HINT_DEFAULT_MINIMUM: + def = lower; + break; + case LADSPA_HINT_DEFAULT_MAXIMUM: + def = upper; + break; + case LADSPA_HINT_DEFAULT_LOW: + if (LADSPA_IS_HINT_LOGARITHMIC(hint)) + def = (LADSPA_Data) expf(logf(lower) * 0.75f + logf(upper) * 0.25f); + else + def = (LADSPA_Data) (lower * 0.75f + upper * 0.25f); + break; + case LADSPA_HINT_DEFAULT_MIDDLE: + if (LADSPA_IS_HINT_LOGARITHMIC(hint)) + def = (LADSPA_Data) expf(logf(lower) * 0.5f + logf(upper) * 0.5f); + else + def = (LADSPA_Data) (lower * 0.5f + upper * 0.5f); + break; + case LADSPA_HINT_DEFAULT_HIGH: + if (LADSPA_IS_HINT_LOGARITHMIC(hint)) + def = (LADSPA_Data) expf(logf(lower) * 0.25f + logf(upper) * 0.75f); + else + def = (LADSPA_Data) (lower * 0.25f + upper * 0.75f); + break; + case LADSPA_HINT_DEFAULT_0: + def = 0.0f; + break; + case LADSPA_HINT_DEFAULT_1: + def = 1.0f; + break; + case LADSPA_HINT_DEFAULT_100: + def = 100.0f; + break; + case LADSPA_HINT_DEFAULT_440: + def = 440.0f; + break; + default: + if (upper == lower) + def = upper; + else + def = SPA_CLAMPF(0.5f * upper, lower, upper); + break; + } + if (LADSPA_IS_HINT_INTEGER(hint)) + def = roundf(def); + return def; +} + +static void ladspa_port_update_ranges(struct descriptor *dd, struct spa_fga_port *port) +{ + const LADSPA_Descriptor *d = dd->d; + unsigned long p = port->index; + LADSPA_PortRangeHintDescriptor hint = d->PortRangeHints[p].HintDescriptor; + LADSPA_Data lower, upper; + + lower = d->PortRangeHints[p].LowerBound; + upper = d->PortRangeHints[p].UpperBound; + + port->hint = hint; + port->def = get_default(port, hint, lower, upper); + port->min = lower; + port->max = upper; +} + +static void ladspa_free(const struct spa_fga_descriptor *desc) +{ + struct descriptor *d = (struct descriptor*)desc; + free(d->desc.ports); + free(d); +} + +static const struct spa_fga_descriptor *ladspa_plugin_make_desc(void *plugin, const char *name) +{ + struct plugin *p = (struct plugin *)plugin; + struct descriptor *desc; + const LADSPA_Descriptor *d; + uint32_t i; + + d = find_desc(p->desc_func, name); + if (d == NULL) + return NULL; + + desc = calloc(1, sizeof(*desc)); + desc->d = d; + + desc->desc.instantiate = ladspa_instantiate; + desc->desc.cleanup = d->cleanup; + desc->desc.connect_port = d->connect_port; + desc->desc.activate = d->activate; + desc->desc.deactivate = d->deactivate; + desc->desc.run = d->run; + + desc->desc.free = ladspa_free; + + desc->desc.name = d->Label; + desc->desc.flags = 0; + + desc->desc.n_ports = d->PortCount; + desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct spa_fga_port)); + + for (i = 0; i < desc->desc.n_ports; i++) { + desc->desc.ports[i].index = i; + desc->desc.ports[i].name = d->PortNames[i]; + desc->desc.ports[i].flags = d->PortDescriptors[i]; + ladspa_port_update_ranges(desc, &desc->desc.ports[i]); + } + return &desc->desc; +} + +static struct spa_fga_plugin_methods impl_plugin = { + SPA_VERSION_FGA_PLUGIN_METHODS, + .make_desc = ladspa_plugin_make_desc, +}; + +static int ladspa_handle_load_by_path(struct plugin *impl, const char *path) +{ + int res; + void *handle = NULL; + LADSPA_Descriptor_Function desc_func; + + handle = dlopen(path, RTLD_NOW); + if (handle == NULL) { + spa_log_debug(impl->log, "failed to open '%s': %s", path, dlerror()); + res = -ENOENT; + goto exit; + } + + spa_log_info(impl->log, "successfully opened '%s'", path); + + desc_func = (LADSPA_Descriptor_Function) dlsym(handle, "ladspa_descriptor"); + if (desc_func == NULL) { + spa_log_warn(impl->log, "cannot find descriptor function in '%s': %s", path, dlerror()); + res = -ENOSYS; + goto exit; + } + + impl->hndl = handle; + impl->desc_func = desc_func; + return 0; + +exit: + if (handle) + dlclose(handle); + return res; +} + +static inline const char *split_walk(const char *str, const char *delimiter, size_t * len, const char **state) +{ + const char *s = *state ? *state : str; + + s += strspn(s, delimiter); + if (*s == '\0') + return NULL; + + *len = strcspn(s, delimiter); + *state = s + *len; + + return s; +} + +static int load_ladspa_plugin(struct plugin *impl, const char *path) +{ + int res = -ENOENT; + + if (path[0] != '/') { + const char *search_dirs, *p, *state = NULL; + char filename[PATH_MAX]; + size_t len; + + search_dirs = getenv("LADSPA_PATH"); + if (!search_dirs) + search_dirs = "/usr/lib64/ladspa:/usr/lib/ladspa:" LIBDIR; + + /* + * set the errno for the case when `ladspa_handle_load_by_path()` + * is never called, which can only happen if the supplied + * LADSPA_PATH contains too long paths + */ + res = -ENAMETOOLONG; + + while ((p = split_walk(search_dirs, ":", &len, &state))) { + int namelen; + + if (len >= sizeof(filename)) + continue; + + namelen = snprintf(filename, sizeof(filename), "%.*s/%s.so", (int) len, p, path); + if (namelen < 0 || (size_t) namelen >= sizeof(filename)) + continue; + + res = ladspa_handle_load_by_path(impl, filename); + if (res >= 0) + break; + } + } + else { + res = ladspa_handle_load_by_path(impl, path); + } + return res; +} + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) +{ + struct plugin *impl; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + impl = (struct plugin *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin)) + *interface = &impl->plugin; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct plugin *impl = (struct plugin *)handle; + if (impl->hndl) + dlclose(impl->hndl); + impl->hndl = NULL; + return 0; +} + +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) +{ + return sizeof(struct plugin); +} + +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 plugin *impl; + uint32_t i; + int res; + const char *path = NULL; + + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + impl = (struct plugin *) handle; + + impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + + 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, "filter.graph.path")) + path = s; + } + if (path == NULL) + return -EINVAL; + + if ((res = load_ladspa_plugin(impl, path)) < 0) { + spa_log_error(impl->log, "failed to load plugin '%s': %s", + path, spa_strerror(res)); + return res; + } + + impl->plugin.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, + SPA_VERSION_FGA_PLUGIN, + &impl_plugin, impl); + + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + { SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin }, +}; + +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 struct spa_handle_factory spa_fga_plugin_ladspa_factory = { + SPA_VERSION_HANDLE_FACTORY, + "filter.graph.plugin.ladspa", + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; + +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_fga_plugin_ladspa_factory; + break; + default: + return 0; + } + (*index)++; + return 1; +} diff --git a/src/modules/module-filter-chain/lv2_plugin.c b/spa/plugins/filter-graph/lv2_plugin.c similarity index 70% rename from src/modules/module-filter-chain/lv2_plugin.c rename to spa/plugins/filter-graph/lv2_plugin.c index ffa35d73..a2af22d6 100644 --- a/src/modules/module-filter-chain/lv2_plugin.c +++ b/spa/plugins/filter-graph/lv2_plugin.c @@ -5,16 +5,13 @@ #include <dlfcn.h> #include <math.h> +#include <lilv/lilv.h> + #include <spa/utils/defs.h> #include <spa/utils/list.h> #include <spa/utils/string.h> #include <spa/support/loop.h> - -#include <pipewire/log.h> -#include <pipewire/utils.h> -#include <pipewire/array.h> - -#include <lilv/lilv.h> +#include <spa/support/log.h> #if defined __has_include # if __has_include (<lv2/atom/atom.h>) @@ -37,50 +34,53 @@ #endif -#include "plugin.h" +#include "audio-plugin.h" static struct context *_context; typedef struct URITable { - struct pw_array array; + char **data; + size_t alloc; + size_t len; } URITable; static void uri_table_init(URITable *table) { - pw_array_init(&table->array, 1024); + table->data = NULL; + table->len = table->alloc = 0; } static void uri_table_destroy(URITable *table) { - char **p; - pw_array_for_each(p, &table->array) - free(*p); - pw_array_clear(&table->array); + size_t i; + for (i = 0; i < table->len; i++) + free(table->data[i]); + free(table->data); + uri_table_init(table); } static LV2_URID uri_table_map(LV2_URID_Map_Handle handle, const char *uri) { URITable *table = (URITable*)handle; - char **p; - size_t i = 0; - - pw_array_for_each(p, &table->array) { - i++; - if (spa_streq(*p, uri)) - goto done; - } - pw_array_add_ptr(&table->array, strdup(uri)); - i = pw_array_get_len(&table->array, char*); -done: - return i; + size_t i; + + for (i = 0; i < table->len; i++) + if (spa_streq(table->data[i], uri)) + return i+1; + + if (table->len == table->alloc) { + table->alloc += 64; + table->data = realloc(table->data, table->alloc * sizeof(char *)); + } + table->data[table->len++] = strdup(uri); + return table->len; } static const char *uri_table_unmap(LV2_URID_Map_Handle handle, LV2_URID urid) { URITable *table = (URITable*)handle; - - if (urid > 0 && urid <= pw_array_get_len(&table->array, char*)) - return *pw_array_get_unchecked(&table->array, urid - 1, char*); + if (urid > 0 && urid <= table->len) + return table->data[urid-1]; return NULL; } @@ -88,9 +88,6 @@ struct context { int ref; LilvWorld *world; - struct spa_loop *data_loop; - struct spa_loop *main_loop; - LilvNode *lv2_InputPort; LilvNode *lv2_OutputPort; LilvNode *lv2_AudioPort; @@ -144,7 +141,7 @@ static const LV2_Feature buf_size_features[3] = { { LV2_BUF_SIZE__boundedBlockLength, NULL }, }; -static struct context *context_new(const struct spa_support *support, uint32_t n_support) +static struct context *context_new(void) { struct context *c; @@ -185,19 +182,16 @@ static struct context *context_new(const struct spa_support *support, uint32_t n c->atom_Int = context_map(c, LV2_ATOM__Int); c->atom_Float = context_map(c, LV2_ATOM__Float); - c->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); - c->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); - return c; error: context_free(c); return NULL; } -static struct context *context_ref(const struct spa_support *support, uint32_t n_support) +static struct context *context_ref(void) { if (_context == NULL) { - _context = context_new(support, n_support); + _context = context_new(); if (_context == NULL) return NULL; } @@ -214,18 +208,26 @@ static void context_unref(struct context *context) } struct plugin { - struct fc_plugin plugin; + struct spa_handle handle; + struct spa_fga_plugin plugin; + + struct spa_log *log; + struct spa_loop *data_loop; + struct spa_loop *main_loop; + struct context *c; const LilvPlugin *p; }; struct descriptor { - struct fc_descriptor desc; + struct spa_fga_descriptor desc; struct plugin *p; }; struct instance { struct descriptor *desc; + struct plugin *p; + LilvInstance *instance; LV2_Worker_Schedule work_schedule; LV2_Feature work_schedule_feature; @@ -254,8 +256,7 @@ static LV2_Worker_Status work_respond(LV2_Worker_Respond_Handle handle, uint32_t size, const void *data) { struct instance *i = (struct instance*)handle; - struct context *c = i->desc->p->c; - spa_loop_invoke(c->data_loop, do_respond, 1, data, size, false, i); + spa_loop_invoke(i->p->data_loop, do_respond, 1, data, size, false, i); return LV2_WORKER_SUCCESS; } @@ -273,12 +274,11 @@ static LV2_Worker_Status work_schedule(LV2_Worker_Schedule_Handle handle, uint32_t size, const void *data) { struct instance *i = (struct instance*)handle; - struct context *c = i->desc->p->c; - spa_loop_invoke(c->main_loop, do_schedule, 1, data, size, false, i); + spa_loop_invoke(i->p->main_loop, do_schedule, 1, data, size, false, i); return LV2_WORKER_SUCCESS; } -static void *lv2_instantiate(const struct fc_plugin *plugin, const struct fc_descriptor *desc, +static void *lv2_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor *desc, unsigned long SampleRate, int index, const char *config) { struct descriptor *d = (struct descriptor*)desc; @@ -297,6 +297,7 @@ static void *lv2_instantiate(const struct fc_plugin *plugin, const struct fc_des i->block_length = 1024; i->desc = d; + i->p = p; i->features[n_features++] = &c->map_feature; i->features[n_features++] = &c->unmap_feature; i->features[n_features++] = &buf_size_features[0]; @@ -383,7 +384,7 @@ static void lv2_run(void *instance, unsigned long SampleCount) i->work_iface->end_run(i->instance); } -static void lv2_free(const struct fc_descriptor *desc) +static void lv2_free(const struct spa_fga_descriptor *desc) { struct descriptor *d = (struct descriptor*)desc; free((char*)d->desc.name); @@ -391,7 +392,7 @@ static void lv2_free(const struct fc_descriptor *desc) free(d); } -static const struct fc_descriptor *lv2_make_desc(struct fc_plugin *plugin, const char *name) +static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const char *name) { struct plugin *p = (struct plugin *)plugin; struct context *c = p->c; @@ -417,7 +418,7 @@ static const struct fc_descriptor *lv2_make_desc(struct fc_plugin *plugin, const desc->desc.flags = 0; desc->desc.n_ports = lilv_plugin_get_num_ports(p->p); - desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct fc_port)); + desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct spa_fga_port)); mins = alloca(desc->desc.n_ports * sizeof(float)); maxes = alloca(desc->desc.n_ports * sizeof(float)); @@ -428,20 +429,20 @@ static const struct fc_descriptor *lv2_make_desc(struct fc_plugin *plugin, const for (i = 0; i < desc->desc.n_ports; i++) { const LilvPort *port = lilv_plugin_get_port_by_index(p->p, i); const LilvNode *symbol = lilv_port_get_symbol(p->p, port); - struct fc_port *fp = &desc->desc.ports[i]; + struct spa_fga_port *fp = &desc->desc.ports[i]; fp->index = i; fp->name = strdup(lilv_node_as_string(symbol)); fp->flags = 0; if (lilv_port_is_a(p->p, port, c->lv2_InputPort)) - fp->flags |= FC_PORT_INPUT; + fp->flags |= SPA_FGA_PORT_INPUT; if (lilv_port_is_a(p->p, port, c->lv2_OutputPort)) - fp->flags |= FC_PORT_OUTPUT; + fp->flags |= SPA_FGA_PORT_OUTPUT; if (lilv_port_is_a(p->p, port, c->lv2_ControlPort)) - fp->flags |= FC_PORT_CONTROL; + fp->flags |= SPA_FGA_PORT_CONTROL; if (lilv_port_is_a(p->p, port, c->lv2_AudioPort)) - fp->flags |= FC_PORT_AUDIO; + fp->flags |= SPA_FGA_PORT_AUDIO; fp->hint = 0; fp->min = mins[i]; @@ -451,60 +452,153 @@ static const struct fc_descriptor *lv2_make_desc(struct fc_plugin *plugin, const return &desc->desc; } -static void lv2_unload(struct fc_plugin *plugin) +static struct spa_fga_plugin_methods impl_plugin = { + SPA_VERSION_FGA_PLUGIN_METHODS, + .make_desc = lv2_plugin_make_desc, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { - struct plugin *p = (struct plugin *)plugin; + struct plugin *impl; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + impl = (struct plugin *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin)) + *interface = &impl->plugin; + else + return -ENOENT; + + return 0; +} + +static int impl_clear(struct spa_handle *handle) +{ + struct plugin *p = (struct plugin *)handle; context_unref(p->c); - free(p); + return 0; } -SPA_EXPORT -struct fc_plugin *pipewire__filter_chain_plugin_load(const struct spa_support *support, uint32_t n_support, - struct dsp_ops *dsp, const char *plugin_uri, const struct spa_dict *info) +static size_t +impl_get_size(const struct spa_handle_factory *factory, + const struct spa_dict *params) { - struct context *c; + return sizeof(struct plugin); +} + +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 plugin *impl; + uint32_t i; + int res; + const char *path = NULL; const LilvPlugins *plugins; - const LilvPlugin *plugin; LilvNode *uri; - int res; - struct plugin *p; - c = context_ref(support, n_support); - if (c == NULL) - return NULL; + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + impl = (struct plugin *) handle; + impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + impl->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); + impl->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); - uri = lilv_new_uri(c->world, plugin_uri); + 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, "filter.graph.path")) + path = s; + } + if (path == NULL) + return -EINVAL; + + impl->c = context_ref(); + if (impl->c == NULL) + return -EINVAL; + + uri = lilv_new_uri(impl->c->world, path); if (uri == NULL) { - pw_log_warn("invalid URI %s", plugin_uri); + spa_log_warn(impl->log, "invalid URI %s", path); res = -EINVAL; - goto error_unref; + goto error_cleanup; } - plugins = lilv_world_get_all_plugins(c->world); - plugin = lilv_plugins_get_by_uri(plugins, uri); + plugins = lilv_world_get_all_plugins(impl->c->world); + impl->p = lilv_plugins_get_by_uri(plugins, uri); lilv_node_free(uri); - if (plugin == NULL) { - pw_log_warn("can't load plugin %s", plugin_uri); + if (impl->p == NULL) { + spa_log_warn(impl->log, "can't load plugin %s", path); res = -EINVAL; - goto error_unref; + goto error_cleanup; } + impl->plugin.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, + SPA_VERSION_FGA_PLUGIN, + &impl_plugin, impl); - p = calloc(1, sizeof(*p)); - if (!p) { - res = -errno; - goto error_unref; - } - p->p = plugin; - p->c = c; + return 0; - p->plugin.make_desc = lv2_make_desc; - p->plugin.unload = lv2_unload; +error_cleanup: + if (impl->c) + context_unref(impl->c); + return res; +} - return &p->plugin; -error_unref: - context_unref(c); - errno = -res; - return NULL; +static const struct spa_interface_info impl_interfaces[] = { + { SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin }, +}; + +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 struct spa_handle_factory spa_fga_plugin_lv2_factory = { + SPA_VERSION_HANDLE_FACTORY, + "filter.graph.plugin.lv2", + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; + +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_fga_plugin_lv2_factory; + break; + default: + return 0; + } + (*index)++; + return 1; } diff --git a/spa/plugins/filter-graph/meson.build b/spa/plugins/filter-graph/meson.build new file mode 100644 index 00000000..c346a964 --- /dev/null +++ b/spa/plugins/filter-graph/meson.build @@ -0,0 +1,117 @@ +plugin_dependencies = [] +if get_option('spa-plugins').allowed() + plugin_dependencies += audioconvert_dep +endif + +simd_cargs = [] +simd_dependencies = [] + +if have_sse + filter_graph_sse = static_library('filter_graph_sse', + ['pffft.c', + 'audio-dsp-sse.c' ], + include_directories : [configinc], + c_args : [sse_args, '-O3', '-DHAVE_SSE'], + dependencies : [ spa_dep ], + install : false + ) + simd_cargs += ['-DHAVE_SSE'] + simd_dependencies += filter_graph_sse +endif +if have_avx + filter_graph_avx = static_library('filter_graph_avx', + ['audio-dsp-avx.c' ], + include_directories : [configinc], + c_args : [avx_args, fma_args,'-O3', '-DHAVE_AVX'], + dependencies : [ spa_dep ], + install : false + ) + simd_cargs += ['-DHAVE_AVX'] + simd_dependencies += filter_graph_avx +endif +if have_neon + filter_graph_neon = static_library('filter_graph_neon', + ['pffft.c' ], + c_args : [neon_args, '-O3', '-DHAVE_NEON'], + dependencies : [ spa_dep ], + install : false + ) + simd_cargs += ['-DHAVE_NEON'] + simd_dependencies += filter_graph_neon +endif + +filter_graph_c = static_library('filter_graph_c', + ['pffft.c', + 'audio-dsp.c', + 'audio-dsp-c.c' ], + include_directories : [configinc], + c_args : [simd_cargs, '-O3', '-DPFFFT_SIMD_DISABLE'], + dependencies : [ spa_dep, fftw_dep], + install : false +) +simd_dependencies += filter_graph_c + +spa_filter_graph = shared_library('spa-filter-graph', + ['filter-graph.c' ], + include_directories : [configinc], + dependencies : [ spa_dep, sndfile_dep, plugin_dependencies, mathlib ], + install : true, + install_dir : spa_plugindir / 'filter-graph', + objects : audioconvert_c.extract_objects('biquad.c'), + link_with: simd_dependencies +) + + +filter_graph_dependencies = [ + spa_dep, mathlib, sndfile_dep, plugin_dependencies +] + +spa_filter_graph_plugin_builtin = shared_library('spa-filter-graph-plugin-builtin', + [ 'builtin_plugin.c', + 'convolver.c' ], + include_directories : [configinc], + install : true, + install_dir : spa_plugindir / 'filter-graph', + dependencies : [ filter_graph_dependencies ], + objects : audioconvert_c.extract_objects('biquad.c') +) + +spa_filter_graph_plugin_ladspa = shared_library('spa-filter-graph-plugin-ladspa', + [ 'ladspa_plugin.c' ], + include_directories : [configinc], + install : true, + install_dir : spa_plugindir / 'filter-graph', + dependencies : [ filter_graph_dependencies, dl_lib ] +) + +if libmysofa_dep.found() +spa_filter_graph_plugin_sofa = shared_library('spa-filter-graph-plugin-sofa', + [ 'sofa_plugin.c', + 'convolver.c' ], + include_directories : [configinc], + install : true, + install_dir : spa_plugindir / 'filter-graph', + dependencies : [ filter_graph_dependencies, libmysofa_dep ] +) +endif + +if lilv_lib.found() +spa_filter_graph_plugin_lv2 = shared_library('spa-filter-graph-plugin-lv2', + [ 'lv2_plugin.c' ], + include_directories : [configinc], + install : true, + install_dir : spa_plugindir / 'filter-graph', + dependencies : [ filter_graph_dependencies, lilv_lib ] +) +endif + +if ebur128_lib.found() +spa_filter_graph_plugin_ebur128 = shared_library('spa-filter-graph-plugin-ebur128', + [ 'ebur128_plugin.c' ], + include_directories : [configinc], + install : true, + install_dir : spa_plugindir / 'filter-graph', + dependencies : [ filter_graph_dependencies, lilv_lib, ebur128_lib ] +) +endif + diff --git a/src/modules/module-filter-chain/pffft.c b/spa/plugins/filter-graph/pffft.c similarity index 100% rename from src/modules/module-filter-chain/pffft.c rename to spa/plugins/filter-graph/pffft.c diff --git a/src/modules/module-filter-chain/pffft.h b/spa/plugins/filter-graph/pffft.h similarity index 100% rename from src/modules/module-filter-chain/pffft.h rename to spa/plugins/filter-graph/pffft.h diff --git a/src/modules/module-filter-chain/sofa_plugin.c b/spa/plugins/filter-graph/sofa_plugin.c similarity index 62% rename from src/modules/module-filter-chain/sofa_plugin.c rename to spa/plugins/filter-graph/sofa_plugin.c index fcc30c89..d348bc97 100644 --- a/src/modules/module-filter-chain/sofa_plugin.c +++ b/spa/plugins/filter-graph/sofa_plugin.c @@ -4,19 +4,20 @@ #include <spa/utils/json.h> #include <spa/support/loop.h> +#include <spa/support/log.h> -#include <pipewire/log.h> - -#include "plugin.h" +#include "audio-plugin.h" #include "convolver.h" -#include "dsp-ops.h" -#include "pffft.h" +#include "audio-dsp.h" #include <mysofa.h> struct plugin { - struct fc_plugin plugin; - struct dsp_ops *dsp_ops; + struct spa_handle handle; + struct spa_fga_plugin plugin; + + struct spa_fga_dsp *dsp; + struct spa_log *log; struct spa_loop *data_loop; struct spa_loop *main_loop; uint32_t quantum_limit; @@ -24,6 +25,10 @@ struct plugin { struct spatializer_impl { struct plugin *plugin; + + struct spa_fga_dsp *dsp; + struct spa_log *log; + unsigned long rate; float *port[6]; int n_samples, blocksize, tailsize; @@ -35,24 +40,25 @@ struct spatializer_impl { struct convolver *r_conv[3]; }; -static void * spatializer_instantiate(const struct fc_plugin *plugin, const struct fc_descriptor * Descriptor, +static void * spatializer_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, unsigned long SampleRate, int index, const char *config) { + struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); struct spatializer_impl *impl; - struct spa_json it[2]; + struct spa_json it[1]; const char *val; char key[256]; char filename[PATH_MAX] = ""; + int len; errno = EINVAL; if (config == NULL) { - pw_log_error("spatializer: no config was given"); + spa_log_error(pl->log, "spatializer: no config was given"); return NULL; } - spa_json_init(&it[0], config, strlen(config)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) { - pw_log_error("spatializer: expected object in config"); + if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { + spa_log_error(pl->log, "spatializer: expected object in config"); return NULL; } @@ -61,35 +67,36 @@ static void * spatializer_instantiate(const struct fc_plugin *plugin, const stru errno = ENOMEM; return NULL; } - impl->plugin = (struct plugin *) plugin; - while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { + impl->plugin = pl; + impl->dsp = pl->dsp; + impl->log = pl->log; + + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "blocksize")) { - if (spa_json_get_int(&it[1], &impl->blocksize) <= 0) { - pw_log_error("spatializer:blocksize requires a number"); + if (spa_json_parse_int(val, len, &impl->blocksize) <= 0) { + spa_log_error(impl->log, "spatializer:blocksize requires a number"); errno = EINVAL; goto error; } } else if (spa_streq(key, "tailsize")) { - if (spa_json_get_int(&it[1], &impl->tailsize) <= 0) { - pw_log_error("spatializer:tailsize requires a number"); + if (spa_json_parse_int(val, len, &impl->tailsize) <= 0) { + spa_log_error(impl->log, "spatializer:tailsize requires a number"); errno = EINVAL; goto error; } } else if (spa_streq(key, "filename")) { - if (spa_json_get_string(&it[1], filename, sizeof(filename)) <= 0) { - pw_log_error("spatializer:filename requires a string"); + if (spa_json_parse_stringn(val, len, filename, sizeof(filename)) <= 0) { + spa_log_error(impl->log, "spatializer:filename requires a string"); errno = EINVAL; goto error; } } - else if (spa_json_next(&it[1], &val) < 0) - break; } if (!filename[0]) { - pw_log_error("spatializer:filename was not given"); + spa_log_error(impl->log, "spatializer:filename was not given"); errno = EINVAL; goto error; } @@ -167,7 +174,7 @@ static void * spatializer_instantiate(const struct fc_plugin *plugin, const stru reason = "Internal error"; break; } - pw_log_error("Unable to load HRTF from %s: %s (%d)", filename, reason, ret); + spa_log_error(impl->log, "Unable to load HRTF from %s: %s (%d)", filename, reason, ret); goto error; } @@ -176,7 +183,7 @@ static void * spatializer_instantiate(const struct fc_plugin *plugin, const stru if (impl->tailsize <= 0) impl->tailsize = SPA_CLAMP(4096, impl->blocksize, 32768); - pw_log_info("using n_samples:%u %d:%d blocksize sofa:%s", impl->n_samples, + spa_log_info(impl->log, "using n_samples:%u %d:%d blocksize sofa:%s", impl->n_samples, impl->blocksize, impl->tailsize, filename); impl->tmp[0] = calloc(impl->plugin->quantum_limit, sizeof(float)); @@ -220,7 +227,7 @@ static void spatializer_reload(void * Instance) for (uint8_t i = 0; i < 3; i++) coords[i] = impl->port[3 + i][0]; - pw_log_info("making spatializer with %f %f %f", coords[0], coords[2], coords[2]); + spa_log_info(impl->log, "making spatializer with %f %f %f", coords[0], coords[1], coords[2]); mysofa_s2c(coords); mysofa_getfilter_float( @@ -236,23 +243,23 @@ static void spatializer_reload(void * Instance) // TODO: make use of delay if ((left_delay != 0.0f || right_delay != 0.0f) && (!isnan(left_delay) || !isnan(right_delay))) - pw_log_warn("delay dropped l: %f, r: %f", left_delay, right_delay); + spa_log_warn(impl->log, "delay dropped l: %f, r: %f", left_delay, right_delay); if (impl->l_conv[2]) convolver_free(impl->l_conv[2]); if (impl->r_conv[2]) convolver_free(impl->r_conv[2]); - impl->l_conv[2] = convolver_new(impl->plugin->dsp_ops, impl->blocksize, impl->tailsize, + impl->l_conv[2] = convolver_new(impl->dsp, impl->blocksize, impl->tailsize, left_ir, impl->n_samples); - impl->r_conv[2] = convolver_new(impl->plugin->dsp_ops, impl->blocksize, impl->tailsize, + impl->r_conv[2] = convolver_new(impl->dsp, impl->blocksize, impl->tailsize, right_ir, impl->n_samples); free(left_ir); free(right_ir); if (impl->l_conv[2] == NULL || impl->r_conv[2] == NULL) { - pw_log_error("reloading left or right convolver failed"); + spa_log_error(impl->log, "reloading left or right convolver failed"); return; } spa_loop_invoke(impl->plugin->data_loop, do_switch, 1, NULL, 0, true, impl); @@ -349,38 +356,38 @@ static void spatializer_deactivate(void * Instance) impl->interpolate = false; } -static struct fc_port spatializer_ports[] = { +static struct spa_fga_port spatializer_ports[] = { { .index = 0, .name = "Out L", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 1, .name = "Out R", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, + .flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_AUDIO, }, { .index = 2, .name = "In", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, }, { .index = 3, .name = "Azimuth", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = 0.0f, .max = 360.0f }, { .index = 4, .name = "Elevation", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 0.0f, .min = -90.0f, .max = 90.0f }, { .index = 5, .name = "Radius", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, + .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, .def = 1.0f, .min = 0.0f, .max = 100.0f }, }; -static const struct fc_descriptor spatializer_desc = { +static const struct spa_fga_descriptor spatializer_desc = { .name = "spatializer", .n_ports = 6, @@ -394,7 +401,7 @@ static const struct fc_descriptor spatializer_desc = { .cleanup = spatializer_cleanup, }; -static const struct fc_descriptor * sofa_descriptor(unsigned long Index) +static const struct spa_fga_descriptor * sofa_descriptor(unsigned long Index) { switch(Index) { case 0: @@ -404,11 +411,11 @@ static const struct fc_descriptor * sofa_descriptor(unsigned long Index) } -static const struct fc_descriptor *sofa_make_desc(struct fc_plugin *plugin, const char *name) +static const struct spa_fga_descriptor *sofa_plugin_make_desc(void *plugin, const char *name) { unsigned long i; for (i = 0; ;i++) { - const struct fc_descriptor *d = sofa_descriptor(i); + const struct spa_fga_descriptor *d = sofa_descriptor(i); if (d == NULL) break; if (spa_streq(d->name, name)) @@ -417,33 +424,132 @@ static const struct fc_descriptor *sofa_make_desc(struct fc_plugin *plugin, cons return NULL; } -static void sofa_plugin_unload(struct fc_plugin *p) +static struct spa_fga_plugin_methods impl_plugin = { + SPA_VERSION_FGA_PLUGIN_METHODS, + .make_desc = sofa_plugin_make_desc, +}; + +static int impl_get_interface(struct spa_handle *handle, const char *type, void **interface) { - free(p); + struct plugin *impl; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + spa_return_val_if_fail(interface != NULL, -EINVAL); + + impl = (struct plugin *) handle; + + if (spa_streq(type, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin)) + *interface = &impl->plugin; + else + return -ENOENT; + + return 0; } -SPA_EXPORT -struct fc_plugin *pipewire__filter_chain_plugin_load(const struct spa_support *support, uint32_t n_support, - struct dsp_ops *dsp, const char *plugin, const struct spa_dict *info) +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 plugin); +} + +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 plugin *impl = calloc(1, sizeof (struct plugin)); + struct plugin *impl; - impl->plugin.make_desc = sofa_make_desc; - impl->plugin.unload = sofa_plugin_unload; + handle->get_interface = impl_get_interface; + handle->clear = impl_clear; + + impl = (struct plugin *) handle; + + impl->plugin.iface = SPA_INTERFACE_INIT( + SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin, + SPA_VERSION_FGA_PLUGIN, + &impl_plugin, impl); impl->quantum_limit = 8192u; + impl->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); + impl->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); + impl->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); + impl->dsp = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioDSP); + for (uint32_t 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, &impl->quantum_limit, 0); + if (spa_streq(k, "filter.graph.audio.dsp")) + sscanf(s, "pointer:%p", &impl->dsp); } - impl->dsp_ops = dsp; - pffft_select_cpu(dsp->cpu_flags); - impl->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); - impl->main_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Loop); + if (impl->data_loop == NULL || impl->main_loop == NULL) { + spa_log_error(impl->log, "%p: could not find a data/main loop", impl); + return -EINVAL; + } + if (impl->dsp == NULL) { + spa_log_error(impl->log, "%p: could not find DSP functions", impl); + return -EINVAL; + } + return 0; +} + +static const struct spa_interface_info impl_interfaces[] = { + {SPA_TYPE_INTERFACE_FILTER_GRAPH_AudioPlugin,}, +}; + +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 struct spa_handle_factory spa_fga_sofa_plugin_factory = { + SPA_VERSION_HANDLE_FACTORY, + "filter.graph.plugin.sofa", + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; + +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); - return (struct fc_plugin *) impl; + switch (*index) { + case 0: + *factory = &spa_fga_sofa_plugin_factory; + break; + default: + return 0; + } + (*index)++; + return 1; } diff --git a/spa/plugins/libcamera/libcamera-device.cpp b/spa/plugins/libcamera/libcamera-device.cpp index 25a9e2fa..5eb46a1a 100644 --- a/spa/plugins/libcamera/libcamera-device.cpp +++ b/spa/plugins/libcamera/libcamera-device.cpp @@ -8,6 +8,8 @@ #include <stddef.h> +#include <sstream> + #include <spa/support/plugin.h> #include <spa/support/log.h> #include <spa/support/loop.h> @@ -53,32 +55,25 @@ struct impl { } -static const libcamera::Span<const int64_t> cameraDevice( - const Camera *camera) +static const libcamera::Span<const int64_t> cameraDevice(const Camera& camera) { - const ControlList &props = camera->properties(); - - if (auto devices = props.get(properties::SystemDevices)) + if (auto devices = camera.properties().get(properties::SystemDevices)) return devices.value(); return {}; } -static std::string cameraModel(const Camera *camera) +static std::string cameraModel(const Camera& camera) { - const ControlList &props = camera->properties(); - - if (auto model = props.get(properties::Model)) + if (auto model = camera.properties().get(properties::Model)) return std::move(model.value()); - return camera->id(); + return camera.id(); } -static const char *cameraLoc(const Camera *camera) +static const char *cameraLoc(const Camera& camera) { - const ControlList &props = camera->properties(); - - if (auto location = props.get(properties::Location)) { + if (auto location = camera.properties().get(properties::Location)) { switch (location.value()) { case properties::CameraLocationFront: return "front"; @@ -92,11 +87,9 @@ static const char *cameraLoc(const Camera *camera) return nullptr; } -static const char *cameraRot(const Camera *camera) +static const char *cameraRot(const Camera& camera) { - const ControlList &props = camera->properties(); - - if (auto rotation = props.get(properties::Rotation)) { + if (auto rotation = camera.properties().get(properties::Rotation)) { switch (rotation.value()) { case 90: return "90"; @@ -119,44 +112,48 @@ 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], name[256], devices_str[256]; - struct spa_strbuf buf; + Camera& camera = *impl->camera; 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->device_id.c_str()); - ADD_ITEM(SPA_KEY_OBJECT_PATH, path); + + const auto path = "libcamera:" + impl->device_id; + ADD_ITEM(SPA_KEY_OBJECT_PATH, path.c_str()); + ADD_ITEM(SPA_KEY_DEVICE_API, "libcamera"); ADD_ITEM(SPA_KEY_MEDIA_CLASS, "Video/Device"); ADD_ITEM(SPA_KEY_API_LIBCAMERA_PATH, impl->device_id.c_str()); - if (auto location = cameraLoc(impl->camera.get())) + if (auto location = cameraLoc(camera)) ADD_ITEM(SPA_KEY_API_LIBCAMERA_LOCATION, location); - if (auto rotation = cameraRot(impl->camera.get())) + if (auto rotation = cameraRot(camera)) ADD_ITEM(SPA_KEY_API_LIBCAMERA_ROTATION, rotation); - const auto model = cameraModel(impl->camera.get()); + const auto model = cameraModel(camera); ADD_ITEM(SPA_KEY_DEVICE_PRODUCT_NAME, model.c_str()); ADD_ITEM(SPA_KEY_DEVICE_DESCRIPTION, model.c_str()); - snprintf(name, sizeof(name), "libcamera_device.%s", impl->device_id.c_str()); - ADD_ITEM(SPA_KEY_DEVICE_NAME, name); + const auto name = "libcamera_device." + impl->device_id; + ADD_ITEM(SPA_KEY_DEVICE_NAME, name.c_str()); - auto device_numbers = cameraDevice(impl->camera.get()); + auto device_numbers = cameraDevice(camera); + std::string devids; if (!device_numbers.empty()) { - spa_strbuf_init(&buf, devices_str, sizeof(devices_str)); + std::ostringstream s; + /* encode device numbers into a json array */ - spa_strbuf_append(&buf, "[ "); - for(int64_t device_number : device_numbers) - spa_strbuf_append(&buf, "%" PRId64 " ", device_number); + s << "[ "; + for (const auto& devid : device_numbers) + s << devid << ' '; + s << ']'; - spa_strbuf_append(&buf, "]"); - ADD_ITEM(SPA_KEY_DEVICE_DEVIDS, devices_str); + devids = std::move(s).str(); + ADD_ITEM(SPA_KEY_DEVICE_DEVIDS, devids.c_str()); } #undef ADD_ITEM diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index f9a1adbc..f570499d 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -19,6 +19,7 @@ #include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/utils/ringbuffer.h> +#include <spa/utils/dll.h> #include <spa/monitor/device.h> #include <spa/node/node.h> #include <spa/node/io.h> @@ -69,6 +70,7 @@ struct port { StreamConfiguration streamConfig; uint32_t memtype = 0; + uint32_t buffers_blocks = 1; struct buffer buffers[MAX_BUFFERS]; uint32_t n_buffers = 0; @@ -166,6 +168,8 @@ struct 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); + + struct spa_dll dll; }; } @@ -359,6 +363,8 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) switch (id) { case SPA_IO_Clock: impl->clock = (struct spa_io_clock*)data; + if (impl->clock) + SPA_FLAG_SET(impl->clock->flags, SPA_IO_CLOCK_FLAG_NO_RATE); break; case SPA_IO_Position: impl->position = (struct spa_io_position*)data; @@ -553,7 +559,7 @@ next: param = (struct spa_pod*)spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_ParamBuffers, id, SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(n_buffers, n_buffers, n_buffers), - SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1), + SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(port->buffers_blocks), SPA_PARAM_BUFFERS_size, SPA_POD_Int(port->streamConfig.frameSize), SPA_PARAM_BUFFERS_stride, SPA_POD_Int(port->streamConfig.stride)); break; diff --git a/spa/plugins/libcamera/libcamera-utils.cpp b/spa/plugins/libcamera/libcamera-utils.cpp index 4a5e0f67..92185c4b 100644 --- a/spa/plugins/libcamera/libcamera-utils.cpp +++ b/spa/plugins/libcamera/libcamera-utils.cpp @@ -109,6 +109,30 @@ static int allocBuffers(struct impl *impl, struct port *port, unsigned int count } impl->requestPool.push_back(std::move(request)); } + + /* Some devices require data for each output video frame to be + * placed in discontiguous memory buffers. In such cases, one + * video frame has to be addressed using more than one memory. + * address. Therefore, need calculate the number of discontiguous + * memory and allocate the specified amount of memory */ + Stream *stream = impl->config->at(0).stream(); + const std::vector<std::unique_ptr<FrameBuffer>> &bufs = + impl->allocator->buffers(stream); + const std::vector<libcamera::FrameBuffer::Plane> &planes = bufs[0]->planes(); + int fd = -1; + uint32_t buffers_blocks = 0; + + for (const FrameBuffer::Plane &plane : planes) { + const int current_fd = plane.fd.get(); + if (current_fd >= 0 && current_fd != fd) { + buffers_blocks += 1; + fd = current_fd; + } + } + + if (buffers_blocks > 0) { + port->buffers_blocks = buffers_blocks; + } return res; } @@ -599,7 +623,7 @@ static int do_update_ctrls(struct spa_loop *loop, impl->ctrls.set(d->id, d->f_val); break; case ControlTypeInteger32: - //impl->ctrls.set(d->id, (int32_t)d->i_val); + impl->ctrls.set(d->id, (int32_t)d->i_val); break; default: break; @@ -798,17 +822,40 @@ mmap_init(struct impl *impl, struct port *port, d[j].type = port->memtype; d[j].flags = SPA_DATA_FLAG_READABLE; d[j].mapoffset = 0; - d[j].maxsize = port->streamConfig.frameSize; - d[j].chunk->offset = 0; - d[j].chunk->size = port->streamConfig.frameSize; d[j].chunk->stride = port->streamConfig.stride; d[j].chunk->flags = 0; + /* Update parameters according to the plane information */ + unsigned int numPlanes = bufs[i]->planes().size(); + if (buffers[i]->n_datas < numPlanes) { + if (j < buffers[i]->n_datas - 1) { + d[j].maxsize = bufs[i]->planes()[j].length; + d[j].chunk->offset = bufs[i]->planes()[j].offset; + d[j].chunk->size = bufs[i]->planes()[j].length; + } else { + d[j].chunk->offset = bufs[i]->planes()[j].offset; + for (uint8_t k = j; k < numPlanes; k++) { + d[j].maxsize += bufs[i]->planes()[k].length; + d[j].chunk->size += bufs[i]->planes()[k].length; + } + } + } else if (buffers[i]->n_datas == numPlanes) { + d[j].maxsize = bufs[i]->planes()[j].length; + d[j].chunk->offset = bufs[i]->planes()[j].offset; + d[j].chunk->size = bufs[i]->planes()[j].length; + } else { + spa_log_warn(impl->log, "buffer index: i: %d, data member " + "numbers: %d is greater than plane number: %d", + i, buffers[i]->n_datas, numPlanes); + d[j].maxsize = port->streamConfig.frameSize; + d[j].chunk->offset = 0; + d[j].chunk->size = port->streamConfig.frameSize; + } if (port->memtype == SPA_DATA_DmaBuf || port->memtype == SPA_DATA_MemFd) { d[j].flags |= SPA_DATA_FLAG_MAPPABLE; d[j].fd = bufs[i]->planes()[j].fd.get(); - spa_log_debug(impl->log, "Got fd = %ld for buffer: #%d", d[j].fd, i); + spa_log_debug(impl->log, "Got fd = %" PRId64 " for buffer: #%d", d[j].fd, i); d[j].data = NULL; SPA_FLAG_SET(b->flags, BUFFER_FLAG_ALLOCATED); } @@ -884,6 +931,18 @@ void impl::requestComplete(libcamera::Request *request) const FrameMetadata &fmd = buffer->metadata(); if (impl->clock) { + double target = (double)port->info.rate.num / port->info.rate.denom; + double corr; + + if (impl->dll.bw == 0.0) { + spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MAX, port->info.rate.denom, port->info.rate.denom); + impl->clock->next_nsec = fmd.timestamp; + corr = 1.0; + } else { + double diff = ((double)impl->clock->next_nsec - (double)fmd.timestamp) / SPA_NSEC_PER_SEC; + double error = port->info.rate.denom * (diff - target); + corr = spa_dll_update(&impl->dll, SPA_CLAMPD(error, -128., 128.)); + } /* FIXME, we should follow the driver clock and target_ values. * for now we ignore and use our own. */ impl->clock->target_rate = port->rate; @@ -894,8 +953,8 @@ void impl::requestComplete(libcamera::Request *request) impl->clock->position = fmd.sequence; impl->clock->duration = 1; impl->clock->delay = 0; - impl->clock->rate_diff = 1.0; - impl->clock->next_nsec = fmd.timestamp; + impl->clock->rate_diff = corr; + impl->clock->next_nsec += (uint64_t) (target * SPA_NSEC_PER_SEC * corr); } if (b->h) { b->h->flags = 0; @@ -940,6 +999,8 @@ static int spa_libcamera_stream_on(struct impl *impl) } impl->pendingRequests.clear(); + impl->dll.bw = 0.0; + impl->source.func = libcamera_on_fd_events; impl->source.data = impl; impl->source.fd = spa_system_eventfd_create(impl->system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); diff --git a/spa/plugins/meson.build b/spa/plugins/meson.build index 97ba78af..42aec7ed 100644 --- a/spa/plugins/meson.build +++ b/spa/plugins/meson.build @@ -55,3 +55,4 @@ if libcamera_dep.found() endif subdir('aec') +subdir('filter-graph') diff --git a/spa/plugins/support/cpu-riscv.c b/spa/plugins/support/cpu-riscv.c new file mode 100644 index 00000000..e6ffa0ad --- /dev/null +++ b/spa/plugins/support/cpu-riscv.c @@ -0,0 +1,29 @@ +/* Spa */ +/* SPDX-FileCopyrightText: Copyright (c) 2023 Institue of Software Chinese Academy of Sciences (ISCAS). */ +/* SPDX-License-Identifier: MIT */ + +#ifdef HAVE_SYS_AUXV_H +#include <sys/auxv.h> +#define HWCAP_RV(letter) (1ul << ((letter) - 'A')) +#endif + +static int +riscv_init(struct impl *impl) +{ + uint32_t flags = 0; + +#ifdef HAVE_SYS_AUXV_H + const unsigned long hwcap = getauxval(AT_HWCAP); + if (hwcap & HWCAP_RV('V')) + flags |= SPA_CPU_FLAG_RISCV_V; +#endif + + impl->flags = flags; + + return 0; +} + +static int riscv_zero_denormals(void *object, bool enable) +{ + return 0; +} diff --git a/spa/plugins/support/cpu.c b/spa/plugins/support/cpu.c index 6db8278e..c2c419f7 100644 --- a/spa/plugins/support/cpu.c +++ b/spa/plugins/support/cpu.c @@ -2,6 +2,8 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ +#include "config.h" + #include <stddef.h> #include <unistd.h> #include <string.h> @@ -64,6 +66,10 @@ static char *spa_cpu_read_file(const char *name, char *buffer, size_t len) #include "cpu-arm.c" #define init(t) arm_init(t) #define impl_cpu_zero_denormals arm_zero_denormals +# elif defined (__riscv) +#include "cpu-riscv.c" +#define init(t) riscv_init(t) +#define impl_cpu_zero_denormals riscv_zero_denormals # else #define init(t) #define impl_cpu_zero_denormals NULL diff --git a/spa/plugins/support/logger.c b/spa/plugins/support/logger.c index 1df503cc..c6e6ca4b 100644 --- a/spa/plugins/support/logger.c +++ b/spa/plugins/support/logger.c @@ -22,6 +22,10 @@ #if defined(__FreeBSD__) || defined(__MidnightBSD__) #define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC +#elif defined(_MSC_VER) +static inline void setlinebuf(FILE* stream) { + setvbuf(stream, NULL, _IOLBF, 0); +} #endif #undef SPA_LOG_TOPIC_DEFAULT @@ -44,9 +48,12 @@ struct impl { struct spa_ringbuffer trace_rb; uint8_t trace_data[TRACE_BUFFER]; + clockid_t clock_id; + unsigned int have_source:1; unsigned int colors:1; unsigned int timestamp:1; + unsigned int local_timestamp:1; unsigned int line:1; }; @@ -63,7 +70,7 @@ impl_log_logtv(void *object, #define RESERVED_LENGTH 24 struct impl *impl = object; - char timestamp[15] = {0}; + char timestamp[18] = {0}; char topicstr[32] = {0}; char filename[64] = {0}; char location[1000 + RESERVED_LENGTH], *p, *s; @@ -89,9 +96,19 @@ impl_log_logtv(void *object, p = location; len = sizeof(location) - RESERVED_LENGTH; - if (impl->timestamp) { + if (impl->local_timestamp) { + char buf[64]; struct timespec now; - clock_gettime(CLOCK_MONOTONIC_RAW, &now); + struct tm now_tm; + + clock_gettime(impl->clock_id, &now); + localtime_r(&now.tv_sec, &now_tm); + strftime(buf, sizeof(buf), "%H:%M:%S", &now_tm); + spa_scnprintf(timestamp, sizeof(timestamp), "[%s.%06d]", buf, + (int)(now.tv_nsec / SPA_NSEC_PER_USEC)); + } else if (impl->timestamp) { + struct timespec now; + clock_gettime(impl->clock_id, &now); spa_scnprintf(timestamp, sizeof(timestamp), "[%05jd.%06jd]", (intmax_t) (now.tv_sec & 0x1FFFFFFF) % 100000, (intmax_t) now.tv_nsec / 1000); } @@ -315,8 +332,20 @@ impl_init(const struct spa_handle_factory *factory, } } if (info) { - if ((str = spa_dict_lookup(info, SPA_KEY_LOG_TIMESTAMP)) != NULL) - this->timestamp = spa_atob(str); + str = spa_dict_lookup(info, SPA_KEY_LOG_TIMESTAMP); + if (spa_atob(str) || spa_streq(str, "local")) { + this->clock_id = CLOCK_REALTIME; + this->local_timestamp = true; + } else if (spa_streq(str, "monotonic")) { + this->clock_id = CLOCK_MONOTONIC; + this->timestamp = true; + } else if (spa_streq(str, "monotonic-raw")) { + this->clock_id = CLOCK_MONOTONIC_RAW; + this->timestamp = true; + } else if (spa_streq(str, "realtime")) { + this->clock_id = CLOCK_REALTIME; + this->timestamp = true; + } if ((str = spa_dict_lookup(info, SPA_KEY_LOG_LINE)) != NULL) this->line = spa_atob(str); if ((str = spa_dict_lookup(info, SPA_KEY_LOG_COLORS)) != NULL) { diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index 2d2ca865..8af61e7a 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -10,6 +10,7 @@ #include <stdio.h> #include <pthread.h> #include <threads.h> +#include <stdatomic.h> #include <spa/support/loop.h> #include <spa/support/system.h> @@ -36,6 +37,12 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.loop"); #define DATAS_SIZE (4096*8) #define MAX_EP 32 +/* the number of concurrent queues for invoke. This is also the number + * of threads that can concurrently invoke. When there are more, the + * retry timeout will be used to retry. */ +#define QUEUES_MAX 128 +#define DEFAULT_RETRY (1 * SPA_USEC_PER_SEC) + /** \cond */ struct invoke_item { @@ -52,6 +59,17 @@ struct invoke_item { static int loop_signal_event(void *object, struct spa_source *source); +struct queue; + +#define IDX_INVALID ((uint16_t)0xffff) +union tag { + struct { + uint16_t idx; + uint16_t count; + } t; + uint32_t v; +}; + struct impl { struct spa_handle handle; struct spa_loop loop; @@ -63,17 +81,23 @@ struct impl { struct spa_list source_list; struct spa_list destroy_list; - struct spa_list queue_list; struct spa_hook_list hooks_list; + struct spa_ratelimit rate_limit; + int retry_timeout; + + union tag head; + + uint32_t n_queues; + struct queue *queues[QUEUES_MAX]; + pthread_mutex_t queue_lock; + int poll_fd; pthread_t thread; int enter_count; struct spa_source *wakeup; - tss_t queue_tss_id; - pthread_mutex_t queue_lock; uint32_t count; uint32_t flush_count; @@ -82,15 +106,14 @@ struct impl { struct queue { struct impl *impl; - struct spa_list link; -#define QUEUE_FLAG_NONE (0) -#define QUEUE_FLAG_ACK_FD (1<<0) - uint32_t flags; - struct queue *overflow; + uint16_t idx; + uint16_t next; int ack_fd; - struct spa_ratelimit rate_limit; + bool close_fd; + + struct queue *overflow; struct spa_ringbuffer buffer; uint8_t *buffer_data; @@ -175,7 +198,23 @@ static int loop_remove_source(void *object, struct spa_source *source) return res; } -static struct queue *loop_create_queue(void *object, uint32_t flags) +static void loop_queue_destroy(void *data) +{ + struct queue *queue = data; + struct impl *impl = queue->impl; + + if (queue->close_fd) + spa_system_close(impl->system, queue->ack_fd); + + if (queue->overflow) + loop_queue_destroy(queue->overflow); + + spa_log_info(impl->log, "%p destroyed queue %p idx:%d", impl, queue, queue->idx); + + free(queue); +} + +static struct queue *loop_create_queue(void *object, bool with_fd) { struct impl *impl = object; struct queue *queue; @@ -185,16 +224,14 @@ static struct queue *loop_create_queue(void *object, uint32_t flags) if (queue == NULL) return NULL; + queue->idx = IDX_INVALID; + queue->next = IDX_INVALID; queue->impl = impl; - queue->flags = flags; - - queue->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; - queue->rate_limit.burst = 1; queue->buffer_data = SPA_PTR_ALIGN(queue->buffer_mem, MAX_ALIGN, uint8_t); spa_ringbuffer_init(&queue->buffer); - if (flags & QUEUE_FLAG_ACK_FD) { + if (with_fd) { if ((res = spa_system_eventfd_create(impl->system, SPA_FD_EVENT_SEMAPHORE | SPA_FD_CLOEXEC)) < 0) { spa_log_error(impl->log, "%p: can't create ack event: %s", @@ -202,25 +239,82 @@ static struct queue *loop_create_queue(void *object, uint32_t flags) goto error; } queue->ack_fd = res; - } else { - queue->ack_fd = -1; + queue->close_fd = true; + + while (true) { + uint16_t idx = SPA_ATOMIC_LOAD(impl->n_queues); + if (idx >= QUEUES_MAX) { + /* this is pretty bad, there are QUEUES_MAX concurrent threads + * that are doing an invoke */ + spa_log_error(impl->log, "max queues %d exceeded!", idx); + res = -ENOSPC; + goto error; + } + queue->idx = idx; + if (SPA_ATOMIC_CAS(impl->queues[queue->idx], NULL, queue)) { + SPA_ATOMIC_INC(impl->n_queues); + break; + } + } } - - pthread_mutex_lock(&impl->queue_lock); - spa_list_append(&impl->queue_list, &queue->link); - pthread_mutex_unlock(&impl->queue_lock); - - spa_log_info(impl->log, "%p created queue %p", impl, queue); + spa_log_info(impl->log, "%p created queue %p idx:%d %p", impl, queue, queue->idx, + (void*)pthread_self()); return queue; error: - free(queue); + loop_queue_destroy(queue); errno = -res; return NULL; } +static inline struct queue *get_queue(struct impl *impl) +{ + union tag head, next; + + head.v = SPA_ATOMIC_LOAD(impl->head.v); + + while (true) { + struct queue *queue; + + if (SPA_UNLIKELY(head.t.idx == IDX_INVALID)) + return NULL; + + queue = impl->queues[head.t.idx]; + next.t.idx = queue->next; + next.t.count = head.t.count+1; + + if (SPA_LIKELY(__atomic_compare_exchange_n(&impl->head.v, &head.v, next.v, + 0, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED))) { + spa_log_trace(impl->log, "%p idx:%d %p", queue, queue->idx, (void*)pthread_self()); + return queue; + } + } + return NULL; +} + +static inline void put_queue(struct impl *impl, struct queue *queue) +{ + union tag head, next; + + spa_log_trace(impl->log, "%p idx:%d %p", queue, queue->idx, (void*)pthread_self()); + + head.v = SPA_ATOMIC_LOAD(impl->head.v); + + while (true) { + queue->next = head.t.idx; + + next.t.idx = queue->idx; + next.t.count = head.t.count+1; + + if (SPA_LIKELY(__atomic_compare_exchange_n(&impl->head.v, &head.v, next.v, + 0, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED))) + break; + } +} + + static inline int32_t item_compare(struct invoke_item *a, struct invoke_item *b) { return (int32_t)(a->count - b->count); @@ -231,25 +325,32 @@ static void flush_all_queues(struct impl *impl) uint32_t flush_count; int res; - pthread_mutex_lock(&impl->queue_lock); - flush_count = ++impl->flush_count; + flush_count = SPA_ATOMIC_INC(impl->flush_count); while (true) { struct queue *cqueue, *queue = NULL; struct invoke_item *citem, *item = NULL; uint32_t cindex, index; spa_invoke_func_t func; bool block; - - spa_list_for_each(cqueue, &impl->queue_list, link) { - if (spa_ringbuffer_get_read_index(&cqueue->buffer, &cindex) < - (int32_t)sizeof(struct invoke_item)) - continue; - citem = SPA_PTROFF(cqueue->buffer_data, cindex & (DATAS_SIZE - 1), struct invoke_item); - - if (item == NULL || item_compare(citem, item) < 0) { - item = citem; - queue = cqueue; - index = cindex; + uint32_t i, n_queues; + + n_queues = SPA_ATOMIC_LOAD(impl->n_queues); + for (i = 0; i < n_queues; i++) { + /* loop over all queues and overflow queues */ + for (cqueue = impl->queues[i]; cqueue != NULL; + cqueue = SPA_ATOMIC_LOAD(cqueue->overflow)) { + if (spa_ringbuffer_get_read_index(&cqueue->buffer, &cindex) < + (int32_t)sizeof(struct invoke_item)) + continue; + + citem = SPA_PTROFF(cqueue->buffer_data, + cindex & (DATAS_SIZE - 1), struct invoke_item); + + if (item == NULL || item_compare(citem, item) < 0) { + item = citem; + queue = cqueue; + index = cindex; + } } } if (item == NULL) @@ -262,15 +363,13 @@ static void flush_all_queues(struct impl *impl) * might get overwritten. */ func = spa_steal_ptr(item->func); if (func) { - pthread_mutex_unlock(&impl->queue_lock); item->res = func(&impl->loop, true, item->seq, item->data, item->size, item->user_data); - pthread_mutex_lock(&impl->queue_lock); } /* if this function did a recursive invoke, it now flushed the * ringbuffer and we can exit */ - if (flush_count != impl->flush_count) + if (flush_count != SPA_ATOMIC_LOAD(impl->flush_count)) break; index += item->item_size; @@ -283,7 +382,6 @@ static void flush_all_queues(struct impl *impl) queue, queue->ack_fd, spa_strerror(res)); } } - pthread_mutex_unlock(&impl->queue_lock); } static int @@ -295,26 +393,24 @@ loop_queue_invoke(void *object, bool block, void *user_data) { - struct queue *queue = object; + struct queue *queue = object, *orig = queue, *overflow; struct impl *impl = queue->impl; struct invoke_item *item; - int res, suppressed; + int res; int32_t filled; uint32_t avail, idx, offset, l0; - size_t need; - uint64_t nsec; bool in_thread; + pthread_t loop_thread, current_thread = pthread_self(); - in_thread = (impl->thread == 0 || pthread_equal(impl->thread, pthread_self())); +again: + loop_thread = impl->thread; + in_thread = (loop_thread == 0 || pthread_equal(loop_thread, current_thread)); -retry: filled = spa_ringbuffer_get_write_index(&queue->buffer, &idx); spa_assert_se(filled >= 0 && filled <= DATAS_SIZE && "queue xrun"); avail = (uint32_t)(DATAS_SIZE - filled); - if (avail < sizeof(struct invoke_item)) { - need = sizeof(struct invoke_item); + if (avail < sizeof(struct invoke_item)) goto xrun; - } offset = idx & (DATAS_SIZE - 1); /* l0 is remaining size in ringbuffer, this should always be larger than @@ -331,7 +427,7 @@ retry: item->res = 0; item->item_size = SPA_ROUND_UP_N(sizeof(struct invoke_item) + size, ITEM_ALIGN); - spa_log_trace_fp(impl->log, "%p: add item %p filled:%d", queue, item, filled); + spa_log_trace(impl->log, "%p: add item %p filled:%d block:%d", queue, item, filled, block); if (l0 >= item->item_size) { /* item + size fit in current ringbuffer idx */ @@ -347,17 +443,28 @@ retry: item->data = queue->buffer_data; item->item_size = SPA_ROUND_UP_N(l0 + size, ITEM_ALIGN); } - if (avail < item->item_size) { - need = item->item_size; + if (avail < item->item_size) goto xrun; - } + if (data && size > 0) memcpy(item->data, data, size); spa_ringbuffer_write_update(&queue->buffer, idx + item->item_size); if (in_thread) { + put_queue(impl, orig); + + /* when there is no thread running the loop we flush the queues from + * this invoking thread but we need to serialize the flushing here with + * a mutex */ + if (loop_thread == 0) + pthread_mutex_lock(&impl->queue_lock); + flush_all_queues(impl); + + if (loop_thread == 0) + pthread_mutex_unlock(&impl->queue_lock); + res = item->res; } else { loop_signal_event(impl, impl->wakeup); @@ -381,23 +488,23 @@ retry: else res = 0; } + put_queue(impl, orig); } return res; - xrun: - if (queue->overflow == NULL) { - nsec = get_time_ns(impl->system); - if ((suppressed = spa_ratelimit_test(&queue->rate_limit, nsec)) >= 0) { - spa_log_warn(impl->log, "%p: queue full %d, need %zd (%d suppressed)", - queue, avail, need, suppressed); - } - queue->overflow = loop_create_queue(impl, QUEUE_FLAG_NONE); - if (queue->overflow == NULL) + /* we overflow, make a new queue that shares the same fd + * and place it in the overflow array. We hold the queue so there + * is only ever one writer to the overflow field. */ + overflow = queue->overflow; + if (overflow == NULL) { + overflow = loop_create_queue(impl, false); + if (overflow == NULL) return -errno; - queue->overflow->ack_fd = queue->ack_fd; + overflow->ack_fd = queue->ack_fd; + SPA_ATOMIC_STORE(queue->overflow, overflow); } - queue = queue->overflow; - goto retry; + queue = overflow; + goto again; } static void wakeup_func(void *data, uint64_t count) @@ -406,33 +513,40 @@ static void wakeup_func(void *data, uint64_t count) flush_all_queues(impl); } -static void loop_queue_destroy(void *data) -{ - struct queue *queue = data; - struct impl *impl = queue->impl; - - pthread_mutex_lock(&impl->queue_lock); - spa_list_remove(&queue->link); - pthread_mutex_unlock(&impl->queue_lock); - - if (queue->flags & QUEUE_FLAG_ACK_FD) - spa_system_close(impl->system, queue->ack_fd); - free(queue); -} - static int loop_invoke(void *object, spa_invoke_func_t func, uint32_t seq, const void *data, size_t size, bool block, void *user_data) { struct impl *impl = object; - struct queue *local_queue = tss_get(impl->queue_tss_id); + struct queue *queue; + int res = 0, suppressed; + uint64_t nsec; - if (local_queue == NULL) { - local_queue = loop_create_queue(impl, QUEUE_FLAG_ACK_FD); - if (local_queue == NULL) - return -errno; - tss_set(impl->queue_tss_id, local_queue); + while (true) { + queue = get_queue(impl); + if (SPA_UNLIKELY(queue == NULL)) + queue = loop_create_queue(impl, true); + if (SPA_UNLIKELY(queue == NULL)) { + if (SPA_UNLIKELY(errno != ENOSPC)) + return -errno; + + /* there was no space for a new queue. This means QUEUE_MAX + * threads are concurrently doing an invoke. We can wait a little + * and retry to get a queue */ + if (impl->retry_timeout == 0) + return -EPIPE; + + nsec = get_time_ns(impl->system); + if ((suppressed = spa_ratelimit_test(&impl->rate_limit, nsec)) >= 0) { + spa_log_warn(impl->log, "%p: out of queues, retrying (%d suppressed)", + impl, suppressed); + } + usleep(impl->retry_timeout); + } else { + res = loop_queue_invoke(queue, func, seq, data, size, block, user_data); + break; + } } - return loop_queue_invoke(local_queue, func, seq, data, size, block, user_data); + return res; } static int loop_get_fd(void *object) @@ -1063,24 +1177,26 @@ static int impl_clear(struct spa_handle *handle) { struct impl *impl; struct source_impl *source; - struct queue *queue; + uint32_t i; spa_return_val_if_fail(handle != NULL, -EINVAL); impl = (struct impl *) handle; + spa_log_debug(impl->log, "%p: clear", impl); + if (impl->enter_count != 0 || impl->polling) spa_log_warn(impl->log, "%p: loop is entered %d times polling:%d", impl, impl->enter_count, impl->polling); spa_list_consume(source, &impl->source_list, link) loop_destroy_source(impl, &source->source); - spa_list_consume(queue, &impl->queue_list, link) - loop_queue_destroy(queue); + for (i = 0; i < impl->n_queues; i++) + loop_queue_destroy(impl->queues[i]); spa_system_close(impl->system, impl->poll_fd); + pthread_mutex_destroy(&impl->queue_lock); - tss_delete(impl->queue_tss_id); return 0; } @@ -1133,10 +1249,15 @@ impl_init(const struct spa_handle_factory *factory, SPA_VERSION_LOOP_UTILS, &impl_loop_utils, impl); + impl->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; + impl->rate_limit.burst = 1; + impl->retry_timeout = DEFAULT_RETRY; if (info) { if ((str = spa_dict_lookup(info, "loop.cancel")) != NULL && spa_atob(str)) impl->control.iface.cb.funcs = &impl_loop_control_cancel; + if ((str = spa_dict_lookup(info, "loop.retry-timeout")) != NULL) + impl->retry_timeout = atoi(str); } CHECK(pthread_mutexattr_init(&attr), error_exit); @@ -1160,7 +1281,6 @@ impl_init(const struct spa_handle_factory *factory, impl->poll_fd = res; spa_list_init(&impl->source_list); - spa_list_init(&impl->queue_list); spa_list_init(&impl->destroy_list); spa_hook_list_init(&impl->hooks_list); @@ -1171,18 +1291,12 @@ impl_init(const struct spa_handle_factory *factory, goto error_exit_free_poll; } - if (tss_create(&impl->queue_tss_id, (tss_dtor_t)loop_queue_destroy) != thrd_success) { - res = -errno; - spa_log_error(impl->log, "%p: can't create tss: %m", impl); - goto error_exit_free_wakeup; - } + impl->head.t.idx = IDX_INVALID; spa_log_debug(impl->log, "%p: initialized", impl); return 0; -error_exit_free_wakeup: - loop_destroy_source(impl, impl->wakeup); error_exit_free_poll: spa_system_close(impl->system, impl->poll_fd); error_exit_free_mutex: diff --git a/spa/plugins/support/meson.build b/spa/plugins/support/meson.build index c9824551..67884577 100644 --- a/spa/plugins/support/meson.build +++ b/spa/plugins/support/meson.build @@ -19,6 +19,7 @@ stdthreads_lib = cc.find_library('stdthreads', required: false) spa_support_lib = shared_library('spa-support', spa_support_sources, c_args : [ simd_cargs ], + include_directories : [ configinc ], dependencies : [ spa_dep, pthread_lib, epoll_shim_dep, mathlib, stdthreads_lib ], install : true, install_dir : spa_plugindir / 'support') diff --git a/spa/plugins/support/null-audio-sink.c b/spa/plugins/support/null-audio-sink.c index 91d28db5..997a6681 100644 --- a/spa/plugins/support/null-audio-sink.c +++ b/spa/plugins/support/null-audio-sink.c @@ -22,6 +22,7 @@ #include <spa/node/io.h> #include <spa/node/keys.h> #include <spa/param/audio/format-utils.h> +#include <spa/param/audio/raw-json.h> #include <spa/debug/types.h> #include <spa/debug/mem.h> #include <spa/param/audio/type-info.h> @@ -859,42 +860,6 @@ impl_get_size(const struct spa_handle_factory *factory, return sizeof(struct impl); } -static uint32_t format_from_name(const char *name) -{ - int i; - for (i = 0; spa_type_audio_format[i].name; i++) { - if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_format[i].name))) - return spa_type_audio_format[i].type; - } - return SPA_AUDIO_FORMAT_UNKNOWN; -} - -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 void parse_position(struct impl *this, 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); - - this->props.channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - this->props.channels < SPA_AUDIO_MAX_CHANNELS) { - this->props.pos[this->props.channels++] = channel_from_name(v); - } -} - static int impl_init(const struct spa_handle_factory *factory, struct spa_handle *handle, @@ -976,7 +941,7 @@ impl_init(const struct spa_handle_factory *factory, if (spa_streq(k, "clock.quantum-limit")) { spa_atou32(s, &this->quantum_limit, 0); } else if (spa_streq(k, SPA_KEY_AUDIO_FORMAT)) { - this->props.format = format_from_name(s); + this->props.format = spa_type_audio_format_from_short_name(s); } else if (spa_streq(k, SPA_KEY_AUDIO_CHANNELS)) { this->props.channels = atoi(s); } else if (spa_streq(k, SPA_KEY_AUDIO_RATE)) { @@ -984,7 +949,7 @@ impl_init(const struct spa_handle_factory *factory, } else if (spa_streq(k, SPA_KEY_NODE_DRIVER)) { this->props.driver = spa_atob(s); } else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) { - parse_position(this, s, strlen(s)); + spa_audio_parse_position(s, strlen(s), this->props.pos, &this->props.channels); } else if (spa_streq(k, "clock.name")) { spa_scnprintf(this->props.clock_name, sizeof(this->props.clock_name), diff --git a/spa/plugins/v4l2/meson.build b/spa/plugins/v4l2/meson.build index e7d09fe2..22746a16 100644 --- a/spa/plugins/v4l2/meson.build +++ b/spa/plugins/v4l2/meson.build @@ -1,7 +1,7 @@ v4l2_sources = ['v4l2.c', 'v4l2-device.c', 'v4l2-source.c'] -v4l2_dependencies = [ spa_dep, libinotify_dep ] +v4l2_dependencies = [ spa_dep, libinotify_dep, mathlib ] if libudev_dep.found() v4l2_sources += [ 'v4l2-udev.c' ] diff --git a/spa/plugins/v4l2/v4l2-source.c b/spa/plugins/v4l2/v4l2-source.c index 899a566c..f19dffde 100644 --- a/spa/plugins/v4l2/v4l2-source.c +++ b/spa/plugins/v4l2/v4l2-source.c @@ -16,6 +16,7 @@ #include <spa/utils/keys.h> #include <spa/utils/names.h> #include <spa/utils/string.h> +#include <spa/utils/dll.h> #include <spa/monitor/device.h> #include <spa/node/node.h> #include <spa/node/io.h> @@ -31,16 +32,19 @@ #include "v4l2.h" static const char default_device[] = "/dev/video0"; +static const char default_clock_name[] = "api.v4l2.unknown"; struct props { char device[64]; char device_name[128]; int device_fd; + char clock_name[64]; }; static void reset_props(struct props *props) { - strncpy(props->device, default_device, 64); + strncpy(props->device, default_device, sizeof(props->device)); + strncpy(props->clock_name, default_clock_name, sizeof(props->clock_name)); } #define MAX_BUFFERS 32 @@ -147,6 +151,8 @@ struct impl { struct spa_io_clock *clock; struct spa_latency_info latency[2]; + + struct spa_dll dll; }; #define CHECK_PORT(this,direction,port_id) ((direction) == SPA_DIRECTION_OUTPUT && (port_id) == 0) @@ -414,6 +420,11 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size) switch (id) { case SPA_IO_Clock: this->clock = data; + if (this->clock) { + SPA_FLAG_SET(this->clock->flags, SPA_IO_CLOCK_FLAG_NO_RATE); + spa_scnprintf(this->clock->name, sizeof(this->clock->name), + "%s", this->props.clock_name); + } break; case SPA_IO_Position: this->position = data; @@ -997,6 +1008,7 @@ impl_init(const struct spa_handle_factory *factory, struct port *port; uint32_t i; int res; + bool have_clock = false; spa_return_val_if_fail(factory != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL); @@ -1068,13 +1080,22 @@ impl_init(const struct spa_handle_factory *factory, const char *s = info->items[i].value; if (spa_streq(k, SPA_KEY_API_V4L2_PATH)) { strncpy(this->props.device, s, 63); - if ((res = spa_v4l2_open(&port->dev, this->props.device)) < 0) - return res; - spa_v4l2_close(&port->dev); } else if (spa_streq(k, "meta.videotransform.transform")) { this->transform = spa_debug_type_find_type_short(spa_type_meta_videotransform_type, s); + } else if (spa_streq(k, "clock.name")) { + spa_scnprintf(this->props.clock_name, + sizeof(this->props.clock_name), "%s", s); + have_clock = true; } } + if ((res = spa_v4l2_open(&port->dev, this->props.device)) < 0) + return res; + spa_v4l2_close(&port->dev); + + if (!have_clock) { + spa_scnprintf(this->props.clock_name, + sizeof(this->props.clock_name), "api.v4l2.%s", port->dev.cap.bus_info); + } return 0; } diff --git a/spa/plugins/v4l2/v4l2-utils.c b/spa/plugins/v4l2/v4l2-utils.c index c14037d1..be38ab32 100644 --- a/spa/plugins/v4l2/v4l2-utils.c +++ b/spa/plugins/v4l2/v4l2-utils.c @@ -476,17 +476,17 @@ filter_framerate(struct v4l2_frmivalenum *frmival, frmival->stepwise.step.denominator *= step->num; frmival->stepwise.step.numerator *= step->denom; - if (compare_fraction(&frmival->stepwise.max, min) < 0 || - compare_fraction(&frmival->stepwise.min, max) > 0) + if (compare_fraction(&frmival->stepwise.min, min) < 0 || + compare_fraction(&frmival->stepwise.max, max) > 0) return false; - if (compare_fraction(&frmival->stepwise.min, min) < 0) { - frmival->stepwise.min.denominator = min->num; - frmival->stepwise.min.numerator = min->denom; + if (compare_fraction(&frmival->stepwise.max, min) < 0) { + frmival->stepwise.max.denominator = min->num; + frmival->stepwise.max.numerator = min->denom; } - if (compare_fraction(&frmival->stepwise.max, max) > 0) { - frmival->stepwise.max.denominator = max->num; - frmival->stepwise.max.numerator = max->denom; + if (compare_fraction(&frmival->stepwise.min, max) > 0) { + frmival->stepwise.min.denominator = max->num; + frmival->stepwise.min.numerator = max->denom; } } else return false; @@ -778,9 +778,9 @@ do_frmsize_filter: if (errno == EINVAL || errno == ENOTTY) { if (port->frmival.index == 0) { port->frmival.type = V4L2_FRMIVAL_TYPE_CONTINUOUS; - port->frmival.stepwise.min.denominator = 1; + port->frmival.stepwise.min.denominator = 120; port->frmival.stepwise.min.numerator = 1; - port->frmival.stepwise.max.denominator = 120; + port->frmival.stepwise.max.denominator = 1; port->frmival.stepwise.max.numerator = 1; goto do_frminterval_filter; } @@ -853,14 +853,25 @@ do_frminterval_filter: n_fractions++; } else if (port->frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS || port->frmival.type == V4L2_FRMIVAL_TYPE_STEPWISE) { - if (n_fractions == 0) - spa_pod_builder_fraction(&b.b, 25, 1); - spa_pod_builder_fraction(&b.b, - port->frmival.stepwise.min.denominator, - port->frmival.stepwise.min.numerator); + if (n_fractions == 0) { + struct spa_fraction f = { 25, 1 }; + if (compare_fraction(&port->frmival.stepwise.max, &f) > 0) { + f.denom = port->frmival.stepwise.max.numerator; + f.num = port->frmival.stepwise.max.denominator; + } + if (compare_fraction(&port->frmival.stepwise.min, &f) < 0) { + f.denom = port->frmival.stepwise.min.numerator; + f.num = port->frmival.stepwise.min.denominator; + } + + spa_pod_builder_fraction(&b.b, f.num, f.denom); + } spa_pod_builder_fraction(&b.b, port->frmival.stepwise.max.denominator, port->frmival.stepwise.max.numerator); + spa_pod_builder_fraction(&b.b, + port->frmival.stepwise.min.denominator, + port->frmival.stepwise.min.numerator); if (port->frmival.type == V4L2_FRMIVAL_TYPE_CONTINUOUS) { choice->body.type = SPA_CHOICE_Range; @@ -1072,7 +1083,7 @@ static int query_ext_ctrl_ioctl(struct port *port, struct v4l2_query_ext_ctrl *q if (port->have_query_ext_ctrl) { res = xioctl(dev->fd, VIDIOC_QUERY_EXT_CTRL, qctrl); - if (errno != ENOTTY) + if (res == 0 || errno != ENOTTY) return res; port->have_query_ext_ctrl = false; } @@ -1368,6 +1379,14 @@ spa_v4l2_set_control(struct impl *this, uint32_t id, control.value = val; break; } + case SPA_TYPE_Float: + { + float val; + if ((res = spa_pod_get_float(&prop->value, &val)) < 0) + goto done; + control.value = (int32_t) val; + break; + } case SPA_TYPE_Int: { int32_t val; @@ -1421,7 +1440,21 @@ static int mmap_read(struct impl *this) pts = SPA_TIMEVAL_TO_NSEC(&buf.timestamp); + if (this->clock) { + double target = (double)port->info.rate.num / port->info.rate.denom; + double corr; + + if (this->dll.bw == 0.0) { + spa_dll_set_bw(&this->dll, SPA_DLL_BW_MAX, port->info.rate.denom, port->info.rate.denom); + this->clock->next_nsec = pts; + corr = 1.0; + } else { + double diff = ((double)this->clock->next_nsec - (double)pts) / SPA_NSEC_PER_SEC; + double error = port->info.rate.denom * (diff - target); + corr = spa_dll_update(&this->dll, SPA_CLAMPD(error, -128., 128.)); + } + /* FIXME, we should follow the driver clock and target_ values. * for now we ignore and use our own. */ this->clock->target_rate = port->info.rate; @@ -1432,8 +1465,8 @@ static int mmap_read(struct impl *this) this->clock->position = buf.sequence; this->clock->duration = 1; this->clock->delay = 0; - this->clock->rate_diff = 1.0; - this->clock->next_nsec = pts + port->info.rate.num * SPA_NSEC_PER_SEC / port->info.rate.denom; + this->clock->rate_diff = corr; + this->clock->next_nsec += (uint64_t) (target * SPA_NSEC_PER_SEC * corr); } b = &port->buffers[buf.index]; @@ -1848,6 +1881,7 @@ static int spa_v4l2_stream_on(struct impl *this) spa_log_error(this->log, "'%s' VIDIOC_STREAMON: %m", this->props.device); return -errno; } + this->dll.bw = 0.0; port->source.func = v4l2_on_fd_events; port->source.data = this; diff --git a/spa/plugins/videoconvert/meson.build b/spa/plugins/videoconvert/meson.build index 24673a54..c345257d 100644 --- a/spa/plugins/videoconvert/meson.build +++ b/spa/plugins/videoconvert/meson.build @@ -1,15 +1,26 @@ videoconvert_sources = [ 'videoadapter.c', + 'videoconvert-dummy.c', 'plugin.c' ] -simd_cargs = [] -simd_dependencies = [] +extra_cargs = [] +extra_dependencies = [] + +if avcodec_dep.found() and avutil_dep.found() and swscale_dep.found() + videoconvert_ffmpeg = static_library('videoconvert_fmmpeg', + ['videoconvert-ffmpeg.c' ], + dependencies : [ spa_dep, avcodec_dep, avutil_dep, swscale_dep ], + install : false + ) + extra_cargs += '-D HAVE_VIDEOCONVERT_FFMPEG' + extra_dependencies += videoconvert_ffmpeg +endif videoconvertlib = shared_library('spa-videoconvert', videoconvert_sources, - c_args : simd_cargs, + c_args : extra_cargs, dependencies : [ spa_dep, mathlib ], - link_with : simd_dependencies, + link_with : extra_dependencies, install : true, install_dir : spa_plugindir / 'videoconvert') diff --git a/spa/plugins/videoconvert/plugin.c b/spa/plugins/videoconvert/plugin.c index 78528141..d3d717b8 100644 --- a/spa/plugins/videoconvert/plugin.c +++ b/spa/plugins/videoconvert/plugin.c @@ -8,6 +8,10 @@ #include <spa/support/log.h> extern const struct spa_handle_factory spa_videoadapter_factory; +extern const struct spa_handle_factory spa_videoconvert_dummy_factory; +#if HAVE_VIDEOCONVERT_FFMPEG +extern const struct spa_handle_factory spa_videoconvert_ffmpeg_factory; +#endif SPA_LOG_TOPIC_ENUM_DEFINE_REGISTERED; @@ -21,6 +25,14 @@ int spa_handle_factory_enum(const struct spa_handle_factory **factory, uint32_t case 0: *factory = &spa_videoadapter_factory; break; + case 1: + *factory = &spa_videoconvert_dummy_factory; + break; +#if HAVE_VIDEOCONVERT_FFMPEG + case 2: + *factory = &spa_videoconvert_ffmpeg_factory; + break; +#endif default: return 0; } diff --git a/spa/plugins/videoconvert/videoadapter.c b/spa/plugins/videoconvert/videoadapter.c index 22cf8c4c..8c1c8de7 100644 --- a/spa/plugins/videoconvert/videoadapter.c +++ b/spa/plugins/videoconvert/videoadapter.c @@ -2,7 +2,7 @@ /* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ /* SPDX-License-Identifier: MIT */ -#include "spa/utils/dict.h" +#include <spa/utils/dict.h> #include <spa/support/plugin.h> #include <spa/support/plugin-loader.h> #include <spa/support/log.h> @@ -61,7 +61,7 @@ struct impl { struct spa_handle *hnd_convert; struct spa_node *convert; struct spa_hook convert_listener; - uint64_t convert_flags; + uint64_t convert_port_flags; char *convertname; uint32_t n_buffers; @@ -93,6 +93,7 @@ struct impl { unsigned int add_listener:1; unsigned int have_format:1; + unsigned int recheck_format:1; unsigned int started:1; unsigned int ready:1; unsigned int async:1; @@ -114,9 +115,9 @@ static int follower_enum_params(struct impl *this, struct spa_pod_builder *builder) { int res; - if (result->next < 0x100000) { - if (this->convert != NULL && - (res = spa_node_enum_params_sync(this->convert, + if (result->next < 0x100000 && + this->follower != this->target) { + if ((res = spa_node_enum_params_sync(this->target, id, &result->next, filter, &result->param, builder)) == 1) return res; result->next = 0x100000; @@ -140,6 +141,9 @@ static int convert_enum_port_config(struct impl *this, struct spa_pod *f1, *f2 = NULL; int res; + if (this->convert == NULL) + return 0; + f1 = spa_pod_builder_add_object(builder, SPA_TYPE_OBJECT_ParamPortConfig, id, SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(this->direction)); @@ -183,9 +187,6 @@ next: switch (id) { case SPA_PARAM_EnumPortConfig: - if (this->convert == NULL) - return 0; - return convert_enum_port_config(this, seq, id, start, num, filter, &b.b); case SPA_PARAM_PortConfig: if (this->passthrough) { switch (result.index) { @@ -202,8 +203,6 @@ next: return 0; } } else { - if (this->convert == NULL) - return 0; return convert_enum_port_config(this, seq, id, start, num, filter, &b.b); } break; @@ -248,9 +247,6 @@ static int link_io(struct impl *this) struct spa_io_rate_match *rate_match; size_t rate_match_size; - if (this->convert == NULL) - return 0; - spa_log_debug(this->log, "%p: controls", this); spa_zero(this->io_rate_match); @@ -271,31 +267,40 @@ static int link_io(struct impl *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, - rate_match, rate_match_size)) < 0) { - spa_log_warn(this->log, "%p: set RateMatch on convert failed %d %s", this, - res, spa_strerror(res)); + else if (this->follower != this->target) { + if ((res = spa_node_port_set_io(this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_IO_RateMatch, + rate_match, rate_match_size)) < 0) { + spa_log_warn(this->log, "%p: set RateMatch on target failed %d %s", this, + res, spa_strerror(res)); + } } + return 0; +} + +static int activate_io(struct impl *this, bool active) +{ + int res; + struct spa_io_buffers *data = active ? &this->io_buffers : NULL; + uint32_t size = active ? sizeof(this->io_buffers) : 0; if (this->follower == this->target) return 0; - this->io_buffers = SPA_IO_BUFFERS_INIT; + if (active) + this->io_buffers = SPA_IO_BUFFERS_INIT; if ((res = spa_node_port_set_io(this->follower, this->direction, 0, - SPA_IO_Buffers, - &this->io_buffers, sizeof(this->io_buffers))) < 0) { + SPA_IO_Buffers, data, size)) < 0) { spa_log_warn(this->log, "%p: set Buffers on follower failed %d %s", this, res, spa_strerror(res)); return res; } - else if ((res = spa_node_port_set_io(this->convert, + else if ((res = spa_node_port_set_io(this->target, SPA_DIRECTION_REVERSE(this->direction), 0, - SPA_IO_Buffers, - &this->io_buffers, sizeof(this->io_buffers))) < 0) { + SPA_IO_Buffers, data, size)) < 0) { spa_log_warn(this->log, "%p: set Buffers on convert failed %d %s", this, res, spa_strerror(res)); return res; @@ -314,6 +319,18 @@ static void emit_node_info(struct impl *this, bool full) if (full) this->info.change_mask = this->info_all; if (this->info.change_mask) { + struct spa_dict_item *items; + uint32_t n_items = 0; + + if (this->info.props) + n_items = this->info.props->n_items; + items = alloca((n_items + 2) * sizeof(struct spa_dict_item)); + for (i = 0; i < n_items; i++) + items[i] = this->info.props->items[i]; + items[n_items++] = SPA_DICT_ITEM_INIT("adapter.auto-port-config", NULL); + items[n_items++] = SPA_DICT_ITEM_INIT("video.adapt.follower", NULL); + 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) { @@ -326,6 +343,7 @@ static void emit_node_info(struct impl *this, bool full) } spa_node_emit_info(&this->hooks, &this->info); this->info.change_mask = old; + spa_zero(this->info.props); } } @@ -389,6 +407,9 @@ static int negotiate_buffers(struct impl *this) spa_log_debug(this->log, "%p: n_buffers:%d", this, this->n_buffers); + if (this->follower == this->target) + return 0; + if (this->n_buffers > 0) return 0; @@ -408,11 +429,11 @@ static int negotiate_buffers(struct impl *this) } state = 0; - if ((res = spa_node_port_enum_params_sync(this->convert, + if ((res = spa_node_port_enum_params_sync(this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Buffers, &state, param, ¶m, &b)) != 1) { - debug_params(this, this->convert, + debug_params(this, this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Buffers, param, "convert buffers", res); return -ENOTSUP; @@ -422,8 +443,8 @@ static int negotiate_buffers(struct impl *this) spa_pod_fixate(param); - follower_flags = this->follower_flags; - conv_flags = this->convert_flags; + follower_flags = this->follower_port_flags; + conv_flags = this->convert_port_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); @@ -470,7 +491,7 @@ static int negotiate_buffers(struct impl *this) return -errno; this->n_buffers = buffers; - if ((res = spa_node_port_use_buffers(this->convert, + if ((res = spa_node_port_use_buffers(this->target, SPA_DIRECTION_REVERSE(this->direction), 0, conv_alloc ? SPA_NODE_BUFFERS_FLAG_ALLOC : 0, this->buffers, this->n_buffers)) < 0) @@ -482,20 +503,33 @@ static int negotiate_buffers(struct impl *this) this->buffers, this->n_buffers)) < 0) return res; + activate_io(this, true); + return 0; } +static void clear_buffers(struct impl *this) +{ + free(this->buffers); + this->buffers = NULL; + this->n_buffers = 0; +} + static int configure_format(struct impl *this, uint32_t flags, const struct spa_pod *format) { uint8_t buffer[4096]; int res; - if (format == NULL && !this->have_format) - return 0; - spa_log_debug(this->log, "%p: configure format:", this); - if (format) + + if (format == NULL) { + if (!this->have_format) + return 0; + activate_io(this, false); + } + else { spa_debug_log_format(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, format); + } if ((res = spa_node_port_set_param(this->follower, this->direction, 0, @@ -519,8 +553,8 @@ static int configure_format(struct impl *this, uint32_t flags, const struct spa_ format = fmt; } - if (this->convert && this->target != this->follower) { - if ((res = spa_node_port_set_param(this->convert, + if (this->target != this->follower) { + if ((res = spa_node_port_set_param(this->target, SPA_DIRECTION_REVERSE(this->direction), 0, SPA_PARAM_Format, flags, format)) < 0) @@ -528,11 +562,11 @@ static int configure_format(struct impl *this, uint32_t flags, const struct spa_ } this->have_format = format != NULL; - if (format == NULL) { - this->n_buffers = 0; - } else if (this->target != this->follower) { + clear_buffers(this); + + if (format != NULL) res = negotiate_buffers(this); - } + return res; } @@ -547,7 +581,7 @@ static int configure_convert(struct impl *this, uint32_t mode) spa_pod_builder_init(&b, buffer, sizeof(buffer)); - spa_log_debug(this->log, "%p: configure convert %p %d", this, this->target, mode); + 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, @@ -574,9 +608,6 @@ static int recalc_latency(struct impl *this, struct spa_node *src, enum spa_dire if (this->target == this->follower) return 0; - if (dst == NULL) - return 0; - while (true) { spa_pod_builder_init(&b, buffer, sizeof(buffer)); if ((res = spa_node_port_enum_params_sync(src, @@ -614,9 +645,6 @@ static int recalc_tag(struct impl *this, struct spa_node *src, enum spa_directio if (this->target == this->follower) return 0; - if (dst == NULL) - return 0; - spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 2048); spa_pod_builder_get_state(&b.b, &state); @@ -639,14 +667,18 @@ static int recalc_tag(struct impl *this, struct spa_node *src, enum spa_directio } -static int reconfigure_mode(struct impl *this, bool passthrough, +static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode mode, enum spa_direction direction, struct spa_pod *format) { int res = 0; struct spa_hook l; + bool passthrough = mode == SPA_PARAM_PORT_CONFIG_MODE_passthrough; spa_log_debug(this->log, "%p: passthrough mode %d", this, passthrough); + if (!passthrough && this->convert == NULL) + return -ENOTSUP; + if (this->passthrough != passthrough) { if (passthrough) { /* remove converter split/merge ports */ @@ -676,7 +708,7 @@ static int reconfigure_mode(struct impl *this, bool passthrough, spa_hook_remove(&l); } else { /* add converter ports */ - configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_dsp); + configure_convert(this, mode); } link_io(this); } @@ -709,12 +741,9 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, 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) + if (spa_format_video_parse(param, &info) < 0) + return -EINVAL; + if (info.media_subtype != SPA_MEDIA_SUBTYPE_raw) return -EINVAL; this->follower_current_format = info; @@ -742,29 +771,22 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, struct spa_video_info info; spa_zero(info); - if ((res = spa_format_parse(format, &info.media_type, &info.media_subtype)) < 0) + if ((res = spa_format_video_parse(format, &info)) < 0) return res; - if (info.media_type != SPA_MEDIA_TYPE_video || - info.media_subtype != SPA_MEDIA_SUBTYPE_raw) - return -ENOTSUP; - if (spa_format_video_raw_parse(format, &info.info.raw) >= 0) { - this->default_format = info; - } + 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) + if ((res = reconfigure_mode(this, mode, 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) + if ((res = reconfigure_mode(this, mode, dir, NULL)) < 0) return res; break; default: @@ -774,6 +796,8 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, if (this->target != this->follower) { if ((res = spa_node_set_param(this->target, id, flags, param)) < 0) return res; + + res = recalc_latency(this, this->follower, this->direction, 0, this->target); } break; } @@ -859,59 +883,80 @@ static struct spa_pod *merge_objects(struct impl *this, struct spa_pod_builder * static int negotiate_format(struct impl *this) { - uint32_t state; + uint32_t fstate, tstate; struct spa_pod *format, *def; uint8_t buffer[4096]; struct spa_pod_builder b = { 0 }; - int res; + int res, fres; + + spa_log_debug(this->log, "%p: have_format:%d recheck:%d", this, this->have_format, + this->recheck_format); if (this->target == this->follower) return 0; - spa_log_debug(this->log, "%p: have_format:%d", this, this->have_format); - - if (this->have_format) + if (this->have_format && !this->recheck_format) return 0; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); + this->recheck_format = false; - spa_log_debug(this->log, "%p: negiotiate", this); + spa_pod_builder_init(&b, buffer, sizeof(buffer)); 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) { + /* first try the ideal converter format, which is likely passthrough */ + tstate = 0; + fres = spa_node_port_enum_params_sync(this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_EnumFormat, &tstate, + NULL, &format, &b); + if (fres == 1) { + fstate = 0; + res = spa_node_port_enum_params_sync(this->follower, + this->direction, 0, + SPA_PARAM_EnumFormat, &fstate, + format, &format, &b); + if (res == 1) + goto found; + } + + /* then try something the follower can accept */ + for (fstate = 0;;) { + format = NULL; + res = spa_node_port_enum_params_sync(this->follower, + this->direction, 0, + SPA_PARAM_EnumFormat, &fstate, + NULL, &format, &b); + if (res == -ENOENT) format = NULL; - else { - debug_params(this, this->follower, this->direction, 0, - SPA_PARAM_EnumFormat, format, "follower format", res); - goto done; - } + else if (res <= 0) + break; + + tstate = 0; + fres = spa_node_port_enum_params_sync(this->target, + SPA_DIRECTION_REVERSE(this->direction), 0, + SPA_PARAM_EnumFormat, &tstate, + format, &format, &b); + if (fres == 0 && res == 1) + continue; + + res = fres; + break; } - state = 0; - if (this->convert && (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, +found: + if (format == NULL) { + debug_params(this, this->follower, this->direction, 0, + SPA_PARAM_EnumFormat, format, "follower format", res); + debug_params(this, this->target, 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); + def = spa_format_video_build(&b, + SPA_PARAM_Format, &this->default_format); format = merge_objects(this, &b, SPA_PARAM_Format, (struct spa_pod_object*)format, @@ -941,12 +986,10 @@ static int impl_node_send_command(void *object, const struct spa_command *comman switch (SPA_NODE_COMMAND_ID(command)) { case SPA_NODE_COMMAND_Start: spa_log_debug(this->log, "%p: starting %d", this, this->started); - if (this->target != this->follower) { - if (this->started) - return 0; - if ((res = negotiate_format(this)) < 0) - return res; - } + if (this->started) + return 0; + if ((res = negotiate_format(this)) < 0) + return res; this->ready = true; this->warned = false; break; @@ -1064,11 +1107,16 @@ static void follower_convert_port_info(void *data, uint32_t i; int res; + if (info == NULL) + return; + spa_log_debug(this->log, "%p: convert port info %s %p %08"PRIx64, this, this->direction == SPA_DIRECTION_INPUT ? "Input" : "Output", info, info->change_mask); - if (this->convert && info->change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + this->convert_port_flags = info->flags; + + if (info->change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { for (i = 0; i < info->n_params; i++) { uint32_t idx; @@ -1094,14 +1142,14 @@ static void follower_convert_port_info(void *data, if (idx == IDX_Latency) { this->in_recalc++; - res = recalc_latency(this, this->convert, direction, port_id, this->follower); + res = recalc_latency(this, this->target, direction, port_id, this->follower); this->in_recalc--; spa_log_debug(this->log, "latency: %d (%s)", res, spa_strerror(res)); } if (idx == IDX_Tag) { this->in_recalc++; - res = recalc_tag(this, this->convert, direction, port_id, this->follower); + res = recalc_tag(this, this->target, direction, port_id, this->follower); this->in_recalc--; spa_log_debug(this->log, "tag: %d (%s)", res, spa_strerror(res)); @@ -1128,7 +1176,10 @@ static void convert_port_info(void *data, port_id--; } else if (info) { pi = *info; - pi.flags = this->follower_port_flags; + pi.flags = this->follower_port_flags & + (SPA_PORT_FLAG_LIVE | + SPA_PORT_FLAG_PHYSICAL | + SPA_PORT_FLAG_TERMINAL); info = π } @@ -1241,15 +1292,15 @@ static void follower_port_info(void *data, uint32_t i; int res; + if (info == NULL) + return; + if (this->follower_removing) { spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); return; } - this->follower_port_flags = info->flags & - (SPA_PORT_FLAG_LIVE | - SPA_PORT_FLAG_PHYSICAL | - SPA_PORT_FLAG_TERMINAL); + this->follower_port_flags = info->flags; spa_log_debug(this->log, "%p: follower port info %s %p %08"PRIx64" recalc:%u", this, this->direction == SPA_DIRECTION_INPUT ? @@ -1303,6 +1354,7 @@ static void follower_port_info(void *data, if (idx == IDX_EnumFormat) { spa_log_debug(this->log, "new formats"); /* we will renegotiate when restarting */ + this->recheck_format = true; } this->params[idx].user++; @@ -1369,12 +1421,12 @@ static int follower_ready(void *data, int status) if (this->direction == SPA_DIRECTION_OUTPUT) { int retry = MAX_RETRY; while (retry--) { - status = spa_node_process(this->convert); + status = spa_node_process_fast(this->target); if (status & SPA_STATUS_HAVE_DATA) break; if (status & SPA_STATUS_NEED_DATA) { - status = spa_node_process(this->follower); + status = spa_node_process_fast(this->follower); if (!(status & SPA_STATUS_HAVE_DATA)) break; } @@ -1391,8 +1443,8 @@ static int follower_reuse_buffer(void *data, uint32_t port_id, uint32_t buffer_i int res; struct impl *this = data; - if (this->convert && this->target != this->follower) - res = spa_node_port_reuse_buffer(this->convert, port_id, buffer_id); + if (this->target != this->follower) + res = spa_node_port_reuse_buffer(this->target, port_id, buffer_id); else res = spa_node_call_reuse_buffer(&this->callbacks, port_id, buffer_id); @@ -1434,12 +1486,11 @@ static int impl_node_add_listener(void *object, spa_node_add_listener(this->follower, &l, &follower_node_events, this); spa_hook_remove(&l); - if (this->convert) { + if (this->follower != this->target) { spa_zero(l); - spa_node_add_listener(this->convert, &l, &convert_node_events, this); + spa_node_add_listener(this->target, &l, &convert_node_events, this); spa_hook_remove(&l); } - this->add_listener = false; emit_node_info(this, true); @@ -1500,6 +1551,33 @@ impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_ return spa_node_remove_port(this->target, direction, port_id); } +static int follower_port_enum_params(struct impl *this, + enum spa_direction direction, uint32_t port_id, + 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 && this->follower_params_flags[idx] & SPA_PARAM_INFO_READ) { + if ((res = spa_node_port_enum_params_sync(this->follower, direction, port_id, + id, &result->next, filter, &result->param, builder)) == 1) + return res; + result->next = 0x100000; + } + if (result->next < 0x200000 && + this->follower != this->target) { + result->next &= 0xfffff; + if ((res = spa_node_port_enum_params_sync(this->target, direction, port_id, + id, &result->next, filter, &result->param, builder)) == 1) { + result->next |= 0x100000; + return res; + } + result->next = 0x200000; + } + return 0; +} + static int impl_node_port_enum_params(void *object, int seq, enum spa_direction direction, uint32_t port_id, @@ -1507,6 +1585,12 @@ impl_node_port_enum_params(void *object, int seq, const struct spa_pod *filter) { struct impl *this = object; + uint8_t buffer[4096]; + spa_auto(spa_pod_dynamic_builder) b = { 0 }; + struct spa_pod_builder_state state; + 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); @@ -1514,10 +1598,37 @@ impl_node_port_enum_params(void *object, int seq, if (direction != this->direction) port_id++; - spa_log_debug(this->log, "%p: %d %u", this, seq, id); + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + spa_pod_builder_get_state(&b.b, &state); + + result.id = id; + result.next = start; +next: + result.index = result.next; + + spa_log_debug(this->log, "%p: %d id:%u", this, seq, id); + + spa_pod_builder_reset(&b.b, &state); - return spa_node_port_enum_params(this->target, seq, direction, port_id, id, + switch (id) { + case SPA_PARAM_EnumFormat: + res = follower_port_enum_params(this, direction, port_id, + id, IDX_EnumFormat, &result, filter, &b.b); + break; + default: + return spa_node_port_enum_params(this->target, seq, direction, port_id, id, start, num, filter); + } + if (res != 1) + return res; + + spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result); + count++; + + if (count != num) + goto next; + + return 0; } static int @@ -1612,7 +1723,7 @@ static int impl_node_process(void *object) 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); + return spa_node_process_fast(this->follower); } if (this->direction == SPA_DIRECTION_INPUT) { @@ -1620,7 +1731,7 @@ static int impl_node_process(void *object) * 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; + status = spa_node_process_fast(this->target); /* schedule the follower when the converter needed * a recycled buffer */ if (status == -EPIPE || status == 0) @@ -1631,7 +1742,7 @@ static int impl_node_process(void *object) 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); + fstatus = spa_node_process_fast(this->follower); if (fstatus < 0) { status = fstatus; break; @@ -1652,24 +1763,20 @@ static int impl_node_process(void *object) /* 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; + status = spa_node_process_fast(this->target); 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) + if (done) break; if (status & SPA_STATUS_NEED_DATA) { /* the converter needs more data, schedule the * follower */ - fstatus = spa_node_process(this->follower); + fstatus = spa_node_process_fast(this->follower); if (fstatus < 0) { status = fstatus; break; @@ -1679,16 +1786,12 @@ static int impl_node_process(void *object) 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); } else { - status = spa_node_process(this->follower); + status = spa_node_process_fast(this->follower); } spa_log_trace_fp(this->log, "%p: process status:%d", this, status); @@ -1721,7 +1824,7 @@ static int load_plugin_from(struct impl *this, const struct spa_dict *info, { struct spa_handle *hnd_convert = NULL; void *iface_conv = NULL; - hnd_convert = spa_plugin_loader_load(this->ploader, convertname, NULL); + hnd_convert = spa_plugin_loader_load(this->ploader, convertname, info); if (!hnd_convert) return -EINVAL; @@ -1788,10 +1891,7 @@ static int impl_clear(struct spa_handle *handle) free(this->convertname); } - if (this->buffers) - free(this->buffers); - this->buffers = NULL; - + clear_buffers(this); return 0; } @@ -1850,16 +1950,26 @@ impl_init(const struct spa_handle_factory *factory, &impl_node, this); ret = load_converter(this, info); - spa_log_debug(this->log, "%p: loaded converter %s, hnd %p, convert %p", this, this->convertname, this->hnd_convert, this->convert); + spa_log_info(this->log, "%p: loaded converter %s, hnd %p, convert %p", this, + this->convertname, this->hnd_convert, this->convert); if (ret < 0) return ret; - this->target = this->convert; + + if (this->convert == NULL) { + this->target = this->follower; + this->passthrough = true; + } else { + this->target = this->convert; + this->passthrough = false; + } 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.flags = SPA_NODE_FLAG_RT | - SPA_NODE_FLAG_NEED_CONFIGURE; + 0; + //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); @@ -1881,11 +1991,15 @@ impl_init(const struct spa_handle_factory *factory, spa_node_add_listener(this->convert, &this->convert_listener, &convert_node_events, this); - configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_convert); + if (strcmp(this->convertname, "video.convert.dummy") == 0) { + configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_none); + reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_passthrough, this->direction, NULL); + } else { + configure_convert(this, SPA_PARAM_PORT_CONFIG_MODE_convert); + } } else { - reconfigure_mode(this, true, this->direction, NULL); + reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_passthrough, this->direction, NULL); } - link_io(this); return 0; diff --git a/spa/plugins/videoconvert/videoconvert-dummy.c b/spa/plugins/videoconvert/videoconvert-dummy.c new file mode 100644 index 00000000..fab51d7a --- /dev/null +++ b/spa/plugins/videoconvert/videoconvert-dummy.c @@ -0,0 +1,720 @@ +/* Spa */ +/* SPDX-FileCopyrightText: Copyright © 2019 Wim Taymans */ +/* SPDX-FileCopyrightText: Copyright © 2023 columbarius */ +/* SPDX-License-Identifier: MIT */ + +#include <errno.h> +#include <stddef.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> + +#include <spa/support/plugin.h> +#include <spa/support/loop.h> +#include <spa/support/log.h> +#include <spa/utils/result.h> +#include <spa/utils/list.h> +#include <spa/utils/keys.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/video/format-utils.h> +#include <spa/param/param.h> +#include <spa/pod/filter.h> +#include <spa/debug/types.h> + +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &log_topic +SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.videoconvert.dummy"); + +#define MAX_PORTS 1 + +struct props { +}; + +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 IDX_Tag 6 +#define N_PORT_PARAMS 7 + struct spa_param_info params[N_PORT_PARAMS]; +}; + +struct dir { + struct port ports[MAX_PORTS]; + uint32_t n_ports; + + enum spa_direction direction; + enum spa_param_port_config_mode mode; + + struct spa_video_info format; + unsigned int have_profile:1; + struct spa_pod *tag; +}; + +struct impl { + struct spa_handle handle; + struct spa_node node; + + struct spa_log *log; + + struct props props; + + struct spa_io_position *io_position; + + uint64_t info_all; + struct spa_node_info info; +#define IDX_EnumPortConfig 0 +#define IDX_PortConfig 1 +#define IDX_PropInfo 2 +#define IDX_Props 3 +#define N_NODE_PARAMS 4 + struct spa_param_info params[N_NODE_PARAMS]; + + struct spa_hook_list hooks; + struct spa_callbacks callbacks; + + struct dir dir[2]; +}; + +#define CHECK_PORT(this,d,p) ((p) < this->dir[d].n_ports) + +static const struct spa_dict_item node_info_items[] = { + { SPA_KEY_MEDIA_CLASS, "Video/Filter" }, +}; + +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) { + this->info.props = &SPA_DICT_INIT_ARRAY(node_info_items); + if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + SPA_FOR_EACH_ELEMENT_VAR(this->params, p) { + if (p->user > 0) { + p->flags ^= SPA_PARAM_INFO_SERIAL; + p->user = 0; + } + } + } + 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[1]; + + items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_FORMAT_DSP, "32 bit float RGBA video"); + port->info.props = &SPA_DICT_INIT(items, 1); + 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 impl *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; + + 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_EnumPortConfig: + { + struct dir *dir; + switch (result.index) { + case 0: + dir = &this->dir[SPA_DIRECTION_INPUT];; + break; + case 1: + dir = &this->dir[SPA_DIRECTION_OUTPUT];; + break; + default: + return 0; + } + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, id, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_none), + SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(false), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(false)); + break; + } + case SPA_PARAM_PortConfig: + { + struct dir *dir; + struct spa_pod_frame f[1]; + + switch (result.index) { + case 0: + dir = &this->dir[SPA_DIRECTION_INPUT];; + break; + case 1: + dir = &this->dir[SPA_DIRECTION_OUTPUT];; + break; + default: + return 0; + } + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_ParamPortConfig, id); + spa_pod_builder_add(&b, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(dir->mode), + SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(false), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(false), + 0); + + param = spa_pod_builder_pop(&b, &f[0]); + break; + } + case SPA_PARAM_PropInfo: + { + switch (result.index) { + case 0: + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_PropInfo, id, + SPA_PROP_INFO_name, SPA_POD_String("video.convert.converter"), + SPA_PROP_INFO_description, SPA_POD_String("Name of the used videoconverter"), + SPA_PROP_INFO_type, SPA_POD_String("dummy"), + SPA_PROP_INFO_params, SPA_POD_Bool(true)); + break; + 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/%zu", this, id, data, size); + + switch (id) { + case SPA_IO_Position: + if (size > 0 && size < sizeof(struct spa_io_position)) + return -EINVAL; + this->io_position = data; + break; + default: + return -ENOENT; + } + return 0; +} + +static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode mode, + enum spa_direction direction, struct spa_video_info *info) +{ + struct dir *dir; + uint32_t i; + + dir = &this->dir[direction]; + + if (dir->have_profile && dir->mode == mode && + (info == NULL || memcmp(&dir->format, info, sizeof(*info)) == 0)) + return 0; + + spa_log_info(this->log, "%p: port config direction:%d mode:%d %d %p", this, + direction, mode, dir->n_ports, info); + + for (i = 0; i < dir->n_ports; i++) { + spa_node_emit_port_info(&this->hooks, direction, i, NULL); + } + + dir->have_profile = true; + dir->mode = mode; + + switch (mode) { + case SPA_PARAM_PORT_CONFIG_MODE_none: + break; + default: + return -ENOTSUP; + } + + 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) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + if (param == NULL) + return 0; + + switch (id) { + case SPA_PARAM_PortConfig: + { + struct spa_video_info info = { 0, }, *infop = NULL; + 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(&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; + + if (format) { + if (!spa_pod_is_object_type(format, SPA_TYPE_OBJECT_Format)) + return -EINVAL; + + if ((res = spa_format_parse(format, &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(format, &info.info.raw) < 0) + return -EINVAL; + + if (info.info.raw.format == 0) + return -EINVAL; + + infop = &info; + } + + if ((res = reconfigure_mode(this, mode, direction, infop)) < 0) + return res; + + 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)) { + 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; + + 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, &this->dir[0].ports[0], true); + emit_port_info(this, &this->dir[1].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 impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + this->callbacks = SPA_CALLBACKS_INIT(callbacks, 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, + const struct spa_pod *filter, + struct spa_pod **param, + struct spa_pod_builder *builder) +{ + 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 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; + 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); + + 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, filter, ¶m, &b)) <= 0) + return res; + 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; + 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 port_set_format(struct impl *this, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + const struct spa_pod *format) +{ + return -ENOTSUP; +} + + +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_Format: + return port_set_format(this, direction, port_id, flags, param); + default: + 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; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL); + + return -ENOTSUP; +} + +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; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + return -ENOTSUP; + +} + +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); + + return -ENOTSUP; +} + +static int impl_node_process(void *object) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + return -ENOTSUP; +} + +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) +{ + + spa_return_val_if_fail(handle != NULL, -EINVAL); + + 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 dir *dir; + + 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); + + // props_reset(&this->props); + + 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.max_input_ports = 1; + this->info.flags = SPA_NODE_FLAG_RT | + SPA_NODE_FLAG_IN_PORT_CONFIG | + SPA_NODE_FLAG_OUT_PORT_CONFIG | + SPA_NODE_FLAG_NEED_CONFIGURE; + 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_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; + + dir = &this->dir[SPA_DIRECTION_INPUT]; + dir->direction = SPA_DIRECTION_INPUT; + + dir = &this->dir[SPA_DIRECTION_OUTPUT]; + dir->direction = SPA_DIRECTION_OUTPUT; + + reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_none, SPA_DIRECTION_INPUT, NULL); + reconfigure_mode(this, SPA_PARAM_PORT_CONFIG_MODE_none, SPA_DIRECTION_OUTPUT, NULL); + 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; +} + +static const struct spa_dict_item info_items[] = { + { SPA_KEY_FACTORY_AUTHOR, "Columbarius <co1umbarius@protonmail.com>" }, + { SPA_KEY_FACTORY_DESCRIPTION, "Dummy video convert plugin" }, +}; + +static const struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items); + +const struct spa_handle_factory spa_videoconvert_dummy_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_VIDEO_CONVERT_DUMMY, + &info, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/videoconvert/videoconvert-ffmpeg.c b/spa/plugins/videoconvert/videoconvert-ffmpeg.c new file mode 100644 index 00000000..955bfa93 --- /dev/null +++ b/spa/plugins/videoconvert/videoconvert-ffmpeg.c @@ -0,0 +1,2082 @@ +/* Spa */ +/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <limits.h> +#include <sys/mman.h> + +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libswscale/swscale.h> +#include <libavutil/pixfmt.h> + +#include <spa/support/plugin.h> +#include <spa/support/cpu.h> +#include <spa/support/loop.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/utils/ratelimit.h> +#include <spa/node/node.h> +#include <spa/node/io.h> +#include <spa/node/utils.h> +#include <spa/node/keys.h> +#include <spa/param/video/format-utils.h> +#include <spa/param/param.h> +#include <spa/param/latency-utils.h> +#include <spa/param/tag-utils.h> +#include <spa/pod/filter.h> +#include <spa/pod/dynamic.h> +#include <spa/debug/types.h> +#include <spa/debug/format.h> +#include <spa/control/ump-utils.h> + +#undef SPA_LOG_TOPIC_DEFAULT +#define SPA_LOG_TOPIC_DEFAULT &log_topic +SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.videoconvert.ffmpeg"); + +#define MAX_ALIGN 64u +#define MAX_BUFFERS 32 +#define MAX_DATAS 4 +#define MAX_PORTS (1+1) + +struct props { + unsigned int dummy:1; +}; + +static void props_reset(struct props *props) +{ + props->dummy = false; +} + +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 IDX_Tag 6 +#define N_PORT_PARAMS 7 + struct spa_param_info params[N_PORT_PARAMS]; + + struct buffer buffers[MAX_BUFFERS]; + uint32_t n_buffers; + + struct spa_latency_info latency[2]; + unsigned int have_latency:1; + + struct spa_video_info format; + unsigned int valid:1; + 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; + uint32_t maxsize; + + struct spa_list queue; +}; + +struct dir { + struct port *ports[MAX_PORTS]; + uint32_t n_ports; + + enum spa_direction direction; + enum spa_param_port_config_mode mode; + + struct spa_video_info format; + unsigned int have_format:1; + unsigned int have_profile:1; + struct spa_pod *tag; + enum AVPixelFormat pix_fmt; + int width; + int height; + + unsigned int control:1; +}; + +struct impl { + struct spa_handle handle; + struct spa_node node; + + struct spa_log *log; + struct spa_cpu *cpu; + struct spa_loop *data_loop; + + uint32_t cpu_flags; + uint32_t max_align; + uint32_t quantum_limit; + enum spa_direction direction; + + struct spa_ratelimit rate_limit; + + 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; +#define IDX_EnumPortConfig 0 +#define IDX_PortConfig 1 +#define IDX_PropInfo 2 +#define IDX_Props 3 +#define N_NODE_PARAMS 4 + struct spa_param_info params[N_NODE_PARAMS]; + + struct spa_hook_list hooks; + + unsigned int monitor:1; + + struct dir dir[2]; + + unsigned int started:1; + unsigned int setup:1; + unsigned int fmt_passthrough:1; + unsigned int drained:1; + unsigned int port_ignore_latency:1; + unsigned int monitor_passthrough:1; + + char group_name[128]; + + struct { + const AVCodec *codec; + AVCodecContext *context; + AVPacket *packet; + AVFrame *frame; + } decoder; + struct { + struct SwsContext *context; + AVFrame *frame; + } convert; + struct { + const AVCodec *codec; + AVCodecContext *context; + AVFrame *frame; + AVPacket *packet; + } encoder; +}; + +#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 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) { + if (this->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) { + SPA_FOR_EACH_ELEMENT_VAR(this->params, p) { + if (p->user > 0) { + p->flags ^= SPA_PARAM_INFO_SERIAL; + p->user = 0; + } + } + } + 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[5]; + 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 video"); + if (port->is_monitor) + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_MONITOR, "true"); + if (this->port_ignore_latency) + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_IGNORE_LATENCY, "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, "32 bit raw UMP"); + } + if (this->group_name[0] != '\0') + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_PORT_GROUP, this->group_name); + port->info.props = &SPA_DICT_INIT(items, n_items); + + if (port->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) { + SPA_FOR_EACH_ELEMENT_VAR(port->params, p) { + if (p->user > 0) { + p->flags ^= SPA_PARAM_INFO_SERIAL; + p->user = 0; + } + } + } + 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, + bool is_dsp, bool is_monitor, bool is_control) +{ + struct port *port = GET_PORT(this, direction, port_id); + + spa_assert(port_id < MAX_PORTS); + + if (port == NULL) { + port = calloc(1, sizeof(struct port)); + if (port == NULL) + return -errno; + this->dir[direction].ports[port_id] = port; + } + port->direction = direction; + port->id = port_id; + port->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + port->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); + + 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->params[IDX_Tag] = SPA_PARAM_INFO(SPA_PARAM_Tag, 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_video; + port->format.media_subtype = SPA_MEDIA_SUBTYPE_dsp; + port->format.info.dsp.format = SPA_VIDEO_FORMAT_DSP_F32; + port->blocks = 1; + port->stride = 16; + } + 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; + } + port->valid = true; + spa_list_init(&port->queue); + + spa_log_debug(this->log, "%p: add port %d:%d %d %d %d", + this, direction, port_id, is_dsp, is_monitor, is_control); + emit_port_info(this, port, true); + + return 0; +} + +static int deinit_port(struct impl *this, enum spa_direction direction, uint32_t port_id) +{ + struct port *port = GET_PORT(this, direction, port_id); + if (port == NULL || !port->valid) + return -ENOENT; + port->valid = false; + spa_node_emit_port_info(&this->hooks, direction, port_id, NULL); + 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_EnumPortConfig: + { + struct dir *dir; + switch (result.index) { + case 0: + dir = &this->dir[SPA_DIRECTION_INPUT];; + break; + case 1: + dir = &this->dir[SPA_DIRECTION_OUTPUT];; + break; + default: + return 0; + } + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamPortConfig, id, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_CHOICE_ENUM_Id(4, + SPA_PARAM_PORT_CONFIG_MODE_none, + SPA_PARAM_PORT_CONFIG_MODE_none, + SPA_PARAM_PORT_CONFIG_MODE_dsp, + SPA_PARAM_PORT_CONFIG_MODE_convert), + SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_CHOICE_Bool(false), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_CHOICE_Bool(false)); + break; + } + case SPA_PARAM_PortConfig: + { + struct dir *dir; + struct spa_pod_frame f[1]; + + switch (result.index) { + case 0: + dir = &this->dir[SPA_DIRECTION_INPUT];; + break; + case 1: + dir = &this->dir[SPA_DIRECTION_OUTPUT];; + break; + default: + return 0; + } + spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_ParamPortConfig, id); + spa_pod_builder_add(&b, + SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(dir->direction), + SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(dir->mode), + SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(this->monitor), + SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(dir->control), + 0); + + if (dir->have_format) { + spa_pod_builder_prop(&b, SPA_PARAM_PORT_CONFIG_format, 0); + spa_format_video_build(&b, SPA_PARAM_PORT_CONFIG_format, + &dir->format); + } + param = spa_pod_builder_pop(&b, &f[0]); + break; + } + case SPA_PARAM_PropInfo: + { + switch (result.index) { + default: + return 0; + } + break; + } + + case SPA_PARAM_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); + param = spa_pod_builder_pop(&b, &f[0]); + break; + 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 videoconvert_set_param(struct impl *this, const char *k, const char *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; + 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]; + + 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)) { + spa_dtoa(value, sizeof(value), + SPA_POD_VALUE(struct spa_pod_float, pod)); + } else if (spa_pod_is_double(pod)) { + spa_dtoa(value, sizeof(value), + SPA_POD_VALUE(struct spa_pod_double, 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; + + spa_log_info(this->log, "key:'%s' val:'%s'", name, value); + changed += videoconvert_set_param(this, name, value); + } + 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; + int changed = 0; + + SPA_POD_OBJECT_FOREACH(obj, prop) { + switch (prop->key) { + case SPA_PROP_params: + changed += parse_prop_params(this, &prop->value); + break; + default: + break; + } + } + return changed; +} + +static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode mode, + enum spa_direction direction, bool monitor, bool control, struct spa_video_info *info) +{ + struct dir *dir; + uint32_t i; + + dir = &this->dir[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; + + spa_log_debug(this->log, "%p: port config direction:%d monitor:%d " + "control:%d mode:%d %d", this, direction, monitor, + control, mode, dir->n_ports); + + for (i = 0; i < dir->n_ports; i++) { + deinit_port(this, direction, i); + if (this->monitor && direction == SPA_DIRECTION_INPUT) + deinit_port(this, SPA_DIRECTION_OUTPUT, i+1); + } + + this->monitor = monitor; + this->setup = false; + dir->control = control; + dir->have_profile = true; + dir->mode = mode; + + switch (mode) { + case SPA_PARAM_PORT_CONFIG_MODE_dsp: + { + if (info) { + dir->n_ports = 1; + dir->format = *info; + dir->format.info.dsp.format = SPA_VIDEO_FORMAT_DSP_F32; + dir->have_format = true; + } else { + dir->n_ports = 0; + } + + if (this->monitor && direction == SPA_DIRECTION_INPUT) + this->dir[SPA_DIRECTION_OUTPUT].n_ports = dir->n_ports + 1; + + for (i = 0; i < dir->n_ports; i++) { + init_port(this, direction, i, true, false, false); + if (this->monitor && direction == SPA_DIRECTION_INPUT) + init_port(this, SPA_DIRECTION_OUTPUT, i+1, true, true, false); + } + break; + } + case SPA_PARAM_PORT_CONFIG_MODE_convert: + { + dir->n_ports = 1; + dir->have_format = false; + init_port(this, direction, 0, false, false, false); + break; + } + case SPA_PARAM_PORT_CONFIG_MODE_none: + break; + default: + return -ENOTSUP; + } + if (direction == SPA_DIRECTION_INPUT && dir->control) { + i = dir->n_ports++; + init_port(this, direction, i, false, false, true); + } + /* when output is convert mode, we are in OUTPUT (merge) mode, we always output all + * the incoming data to output. When output is DSP, we need to output quantum size + * chunks. */ + this->direction = this->dir[SPA_DIRECTION_OUTPUT].mode == SPA_PARAM_PORT_CONFIG_MODE_convert ? + SPA_DIRECTION_OUTPUT : SPA_DIRECTION_INPUT; + + 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) +{ + struct impl *this = object; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + if (param == NULL) + return 0; + + switch (id) { + case SPA_PARAM_PortConfig: + { + struct spa_video_info info = { 0, }, *infop = NULL; + 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(&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; + + if (format) { + if (!spa_pod_is_object_type(format, SPA_TYPE_OBJECT_Format)) + return -EINVAL; + + if ((res = spa_format_video_parse(format, &info)) < 0) + return res; + + infop = &info; + } + + if ((res = reconfigure_mode(this, mode, direction, monitor, control, infop)) < 0) + return res; + + 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; +} + +static enum AVPixelFormat format_to_pix_fmt(uint32_t format) +{ + switch (format) { + case SPA_VIDEO_FORMAT_I420: + return AV_PIX_FMT_YUV420P; + case SPA_VIDEO_FORMAT_YV12: + break; + case SPA_VIDEO_FORMAT_YUY2: + return AV_PIX_FMT_YUYV422; + case SPA_VIDEO_FORMAT_UYVY: + return AV_PIX_FMT_UYVY422; + case SPA_VIDEO_FORMAT_AYUV: + break; + case SPA_VIDEO_FORMAT_RGBx: + return AV_PIX_FMT_RGB0; + case SPA_VIDEO_FORMAT_BGRx: + return AV_PIX_FMT_BGR0; + case SPA_VIDEO_FORMAT_xRGB: + return AV_PIX_FMT_0RGB; + case SPA_VIDEO_FORMAT_xBGR: + return AV_PIX_FMT_0BGR; + case SPA_VIDEO_FORMAT_RGBA: + return AV_PIX_FMT_RGBA; + case SPA_VIDEO_FORMAT_BGRA: + return AV_PIX_FMT_BGRA; + case SPA_VIDEO_FORMAT_ARGB: + return AV_PIX_FMT_ARGB; + case SPA_VIDEO_FORMAT_ABGR: + return AV_PIX_FMT_ABGR; + case SPA_VIDEO_FORMAT_RGB: + return AV_PIX_FMT_RGB24; + case SPA_VIDEO_FORMAT_BGR: + return AV_PIX_FMT_BGR24; + case SPA_VIDEO_FORMAT_Y41B: + return AV_PIX_FMT_YUV411P; + case SPA_VIDEO_FORMAT_Y42B: + return AV_PIX_FMT_YUV422P; + case SPA_VIDEO_FORMAT_YVYU: + return AV_PIX_FMT_YVYU422; + case SPA_VIDEO_FORMAT_Y444: + return AV_PIX_FMT_YUV444P; + case SPA_VIDEO_FORMAT_v210: + case SPA_VIDEO_FORMAT_v216: + break; + case SPA_VIDEO_FORMAT_NV12: + return AV_PIX_FMT_NV12; + case SPA_VIDEO_FORMAT_NV21: + return AV_PIX_FMT_NV21; + case SPA_VIDEO_FORMAT_GRAY8: + return AV_PIX_FMT_GRAY8; + case SPA_VIDEO_FORMAT_GRAY16_BE: + return AV_PIX_FMT_GRAY16BE; + case SPA_VIDEO_FORMAT_GRAY16_LE: + return AV_PIX_FMT_GRAY16LE; + case SPA_VIDEO_FORMAT_v308: + break; + case SPA_VIDEO_FORMAT_RGB16: + return AV_PIX_FMT_RGB565; + case SPA_VIDEO_FORMAT_BGR16: + break; + case SPA_VIDEO_FORMAT_RGB15: + return AV_PIX_FMT_RGB555; + case SPA_VIDEO_FORMAT_BGR15: + case SPA_VIDEO_FORMAT_UYVP: + break; + case SPA_VIDEO_FORMAT_A420: + return AV_PIX_FMT_YUVA420P; + case SPA_VIDEO_FORMAT_RGB8P: + return AV_PIX_FMT_PAL8; + case SPA_VIDEO_FORMAT_YUV9: + return AV_PIX_FMT_YUV410P; + case SPA_VIDEO_FORMAT_YVU9: + case SPA_VIDEO_FORMAT_IYU1: + case SPA_VIDEO_FORMAT_ARGB64: + case SPA_VIDEO_FORMAT_AYUV64: + case SPA_VIDEO_FORMAT_r210: + break; + case SPA_VIDEO_FORMAT_I420_10BE: + return AV_PIX_FMT_YUV420P10BE; + case SPA_VIDEO_FORMAT_I420_10LE: + return AV_PIX_FMT_YUV420P10LE; + case SPA_VIDEO_FORMAT_I422_10BE: + return AV_PIX_FMT_YUV422P10BE; + case SPA_VIDEO_FORMAT_I422_10LE: + return AV_PIX_FMT_YUV422P10LE; + case SPA_VIDEO_FORMAT_Y444_10BE: + return AV_PIX_FMT_YUV444P10BE; + case SPA_VIDEO_FORMAT_Y444_10LE: + return AV_PIX_FMT_YUV444P10LE; + case SPA_VIDEO_FORMAT_GBR: + return AV_PIX_FMT_GBRP; + case SPA_VIDEO_FORMAT_GBR_10BE: + return AV_PIX_FMT_GBRP10BE; + case SPA_VIDEO_FORMAT_GBR_10LE: + return AV_PIX_FMT_GBRP10LE; + case SPA_VIDEO_FORMAT_NV16: + case SPA_VIDEO_FORMAT_NV24: + case SPA_VIDEO_FORMAT_NV12_64Z32: + break; + case SPA_VIDEO_FORMAT_A420_10BE: + return AV_PIX_FMT_YUVA420P10BE; + case SPA_VIDEO_FORMAT_A420_10LE: + return AV_PIX_FMT_YUVA420P10LE; + case SPA_VIDEO_FORMAT_A422_10BE: + return AV_PIX_FMT_YUVA422P10BE; + case SPA_VIDEO_FORMAT_A422_10LE: + return AV_PIX_FMT_YUVA422P10LE; + case SPA_VIDEO_FORMAT_A444_10BE: + return AV_PIX_FMT_YUVA444P10BE; + case SPA_VIDEO_FORMAT_A444_10LE: + return AV_PIX_FMT_YUVA444P10LE; + case SPA_VIDEO_FORMAT_NV61: + case SPA_VIDEO_FORMAT_P010_10BE: + case SPA_VIDEO_FORMAT_P010_10LE: + case SPA_VIDEO_FORMAT_IYU2: + case SPA_VIDEO_FORMAT_VYUY: + break; + case SPA_VIDEO_FORMAT_GBRA: + return AV_PIX_FMT_GBRAP; + case SPA_VIDEO_FORMAT_GBRA_10BE: + return AV_PIX_FMT_GBRAP10BE; + case SPA_VIDEO_FORMAT_GBRA_10LE: + return AV_PIX_FMT_GBRAP10LE; + case SPA_VIDEO_FORMAT_GBR_12BE: + return AV_PIX_FMT_GBRP12BE; + case SPA_VIDEO_FORMAT_GBR_12LE: + return AV_PIX_FMT_GBRP12LE; + case SPA_VIDEO_FORMAT_GBRA_12BE: + return AV_PIX_FMT_GBRAP12BE; + case SPA_VIDEO_FORMAT_GBRA_12LE: + return AV_PIX_FMT_GBRAP12LE; + case SPA_VIDEO_FORMAT_I420_12BE: + return AV_PIX_FMT_YUV420P12BE; + case SPA_VIDEO_FORMAT_I420_12LE: + return AV_PIX_FMT_YUV420P12LE; + case SPA_VIDEO_FORMAT_I422_12BE: + return AV_PIX_FMT_YUV422P12BE; + case SPA_VIDEO_FORMAT_I422_12LE: + return AV_PIX_FMT_YUV422P12LE; + case SPA_VIDEO_FORMAT_Y444_12BE: + return AV_PIX_FMT_YUV444P12BE; + case SPA_VIDEO_FORMAT_Y444_12LE: + return AV_PIX_FMT_YUV444P12LE; + + case SPA_VIDEO_FORMAT_RGBA_F16: + case SPA_VIDEO_FORMAT_RGBA_F32: + break; + + case SPA_VIDEO_FORMAT_xRGB_210LE: + return AV_PIX_FMT_X2RGB10LE; + case SPA_VIDEO_FORMAT_xBGR_210LE: + return AV_PIX_FMT_X2BGR10LE; + + case SPA_VIDEO_FORMAT_RGBx_102LE: + case SPA_VIDEO_FORMAT_BGRx_102LE: + case SPA_VIDEO_FORMAT_ARGB_210LE: + case SPA_VIDEO_FORMAT_ABGR_210LE: + case SPA_VIDEO_FORMAT_RGBA_102LE: + case SPA_VIDEO_FORMAT_BGRA_102LE: + break; + default: + break; + } + return AV_PIX_FMT_NONE; +} + +static int get_format(struct dir *dir, int *width, int *height, uint32_t *format) +{ + if (dir->have_format) { + switch (dir->format.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + *width = dir->format.info.raw.size.width; + *height = dir->format.info.raw.size.height; + *format = dir->format.info.raw.format; + break; + case SPA_MEDIA_SUBTYPE_mjpg: + *width = dir->format.info.mjpg.size.width; + *height = dir->format.info.mjpg.size.height; + break; + case SPA_MEDIA_SUBTYPE_h264: + *width = dir->format.info.h264.size.width; + *height = dir->format.info.h264.size.height; + break; + default: + *width = *height = 0; + break; + } + } else { + *width = *height = 0; + } + return 0; +} + + +static int setup_convert(struct impl *this) +{ + struct dir *in, *out; + uint32_t format; + + 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 -EIO; + + switch (in->format.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + in->pix_fmt = format_to_pix_fmt(in->format.info.raw.format); + switch (out->format.media_subtype) { + case SPA_MEDIA_SUBTYPE_raw: + out->pix_fmt = format_to_pix_fmt(out->format.info.raw.format); + break; + case SPA_MEDIA_SUBTYPE_mjpg: + if ((this->encoder.codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG)) == NULL) { + spa_log_error(this->log, "failed to find MJPEG encoder"); + return -ENOTSUP; + } + out->format.media_subtype = SPA_MEDIA_SUBTYPE_raw; + out->format.info.raw.format = SPA_VIDEO_FORMAT_I420; + out->format.info.raw.size = in->format.info.raw.size; + out->pix_fmt = AV_PIX_FMT_YUVJ420P; + break; + case SPA_MEDIA_SUBTYPE_h264: + if ((this->encoder.codec = avcodec_find_encoder(AV_CODEC_ID_H264)) == NULL) { + spa_log_error(this->log, "failed to find H264 encoder"); + return -ENOTSUP; + } + break; + default: + return -ENOTSUP; + } + break; + case SPA_MEDIA_SUBTYPE_mjpg: + switch (out->format.media_subtype) { + case SPA_MEDIA_SUBTYPE_mjpg: + /* passthrough */ + break; + case SPA_MEDIA_SUBTYPE_raw: + out->pix_fmt = format_to_pix_fmt(out->format.info.raw.format); + if ((this->decoder.codec = avcodec_find_decoder(AV_CODEC_ID_MJPEG)) == NULL) { + spa_log_error(this->log, "failed to find MJPEG decoder"); + return -ENOTSUP; + } + break; + default: + return -ENOTSUP; + } + break; + case SPA_MEDIA_SUBTYPE_h264: + switch (out->format.media_subtype) { + case SPA_MEDIA_SUBTYPE_h264: + /* passthrough */ + break; + case SPA_MEDIA_SUBTYPE_raw: + out->pix_fmt = format_to_pix_fmt(out->format.info.raw.format); + if ((this->decoder.codec = avcodec_find_decoder(AV_CODEC_ID_H264)) == NULL) { + spa_log_error(this->log, "failed to find H264 decoder"); + return -ENOTSUP; + } + break; + default: + return -ENOTSUP; + } + break; + default: + return -ENOTSUP; + } + + get_format(in, &in->width, &in->height, &format); + get_format(out, &out->width, &out->height, &format); + + if (this->decoder.codec) { + if ((this->decoder.context = avcodec_alloc_context3(this->decoder.codec)) == NULL) + return -EIO; + + if ((this->decoder.packet = av_packet_alloc()) == NULL) + return -EIO; + + this->decoder.context->flags2 |= AV_CODEC_FLAG2_FAST; + + if (avcodec_open2(this->decoder.context, this->decoder.codec, NULL) < 0) { + spa_log_error(this->log, "failed to open decoder codec"); + return -EIO; + } + } + if ((this->decoder.frame = av_frame_alloc()) == NULL) + return -EIO; + if (this->encoder.codec) { + if ((this->encoder.context = avcodec_alloc_context3(this->encoder.codec)) == NULL) + return -EIO; + + if ((this->encoder.packet = av_packet_alloc()) == NULL) + return -EIO; + if ((this->encoder.frame = av_frame_alloc()) == NULL) + return -EIO; + + this->encoder.context->flags2 |= AV_CODEC_FLAG2_FAST; + this->encoder.context->time_base.num = 1; + this->encoder.context->width = out->width; + this->encoder.context->height = out->height; + this->encoder.context->pix_fmt = out->pix_fmt; + + if (avcodec_open2(this->encoder.context, this->encoder.codec, NULL) < 0) { + spa_log_error(this->log, "failed to open encoder codec"); + return -EIO; + } + } + if ((this->convert.frame = av_frame_alloc()) == NULL) + return -EIO; + + + this->setup = true; + + emit_node_info(this, false); + + return 0; +} + +static void reset_node(struct impl *this) +{ +} + +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); + 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; + this->started = true; + break; + case SPA_NODE_COMMAND_Suspend: + 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; + } + 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; + struct port *p; + + 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); + for (i = 0; i < this->dir[SPA_DIRECTION_INPUT].n_ports; i++) { + if ((p = GET_IN_PORT(this, i)) && p->valid) + emit_port_info(this, p, true); + } + for (i = 0; i < this->dir[SPA_DIRECTION_OUTPUT].n_ports; i++) { + if ((p = GET_OUT_PORT(this, i)) && p->valid) + emit_port_info(this, p, 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 dir *other = &this->dir[SPA_DIRECTION_REVERSE(direction)]; + struct spa_pod_frame f[1]; + int width, height; + uint32_t format = 0; + + get_format(other, &width, &height, &format); + + switch (index) { + case 0: + if (PORT_IS_DSP(this, direction, port_id)) { + struct spa_video_info_dsp info; + info.format = SPA_VIDEO_FORMAT_DSP_F32; + *param = spa_format_video_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), + SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int( + (1u<<SPA_CONTROL_UMP) | (1u<<SPA_CONTROL_Properties))); + } else { + if (other->have_format) { + *param = spa_format_video_build(builder, SPA_PARAM_EnumFormat, &other->format); + } else { + *param = NULL; + } + } + break; + case 1: + if (PORT_IS_DSP(this, direction, port_id) || + PORT_IS_CONTROL(this, direction, port_id)) + return 0; + + spa_pod_builder_push_object(builder, &f[0], + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(builder, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), + SPA_FORMAT_VIDEO_format, SPA_POD_CHOICE_ENUM_Id(7, + format, + SPA_VIDEO_FORMAT_YUY2, + SPA_VIDEO_FORMAT_I420, + SPA_VIDEO_FORMAT_UYVY, + SPA_VIDEO_FORMAT_YVYU, + SPA_VIDEO_FORMAT_RGBA, + SPA_VIDEO_FORMAT_BGRx), + 0); + if (width != 0 && height != 0) { + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( + &SPA_RECTANGLE(width, height), + &SPA_RECTANGLE(1, 1), + &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), + 0); + } + *param = spa_pod_builder_pop(builder, &f[0]); + break; + case 2: + if (PORT_IS_DSP(this, direction, port_id) || + PORT_IS_CONTROL(this, direction, port_id)) + return 0; + + spa_pod_builder_push_object(builder, &f[0], + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); + spa_pod_builder_add(builder, + SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_mjpg), + 0); + if (width != 0 && height != 0) { + spa_pod_builder_add(builder, + SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle( + &SPA_RECTANGLE(width, height), + &SPA_RECTANGLE(1, 1), + &SPA_RECTANGLE(INT32_MAX, INT32_MAX)), + 0); + } + *param = spa_pod_builder_pop(builder, &f[0]); + 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_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_video_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), + SPA_FORMAT_CONTROL_types, SPA_POD_Int( + (1u<<SPA_CONTROL_UMP) | (1u<<SPA_CONTROL_Properties))); + else + param = spa_format_video_build(&b, id, &port->format); + break; + case SPA_PARAM_Buffers: + { + uint32_t size, min, max; + + if (!port->have_format) + return -EIO; + if (result.index > 0) + return 0; + + if (PORT_IS_DSP(this, direction, port_id)) { + size = 1024 * 1024 * 16; + } else { + size = 1024 * 1024 * 4; + } + + other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); + if (other->n_buffers > 0) { + min = max = other->n_buffers; + } else { + min = 2; + max = MAX_BUFFERS; + } + + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_ParamBuffers, id, + SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, min, max), + 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: + { + uint32_t idx = result.index; + param = spa_latency_build(&b, id, &port->latency[idx]); + break; + } + default: + return 0; + } + break; + case SPA_PARAM_Tag: + switch (result.index) { + case 0: case 1: + { + uint32_t idx = result.index; + if (port->is_monitor) + idx = idx ^ 1; + param = this->dir[idx].tag; + if (param == NULL) + goto next; + break; + } + default: + return 0; + } + break; + default: + return -ENOENT; + } + + if (param == NULL || 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, *oport; + enum spa_direction other = SPA_DIRECTION_REVERSE(direction); + struct spa_latency_info info; + bool have_latency, emit = false;; + uint32_t i; + + spa_log_debug(this->log, "%p: set latency direction:%d id:%d %p", + this, direction, port_id, latency); + + port = GET_PORT(this, direction, port_id); + if (latency == NULL) { + info = SPA_LATENCY_INFO(other); + have_latency = false; + } else { + if (spa_latency_parse(latency, &info) < 0 || + info.direction != other) + return -EINVAL; + have_latency = true; + } + emit = spa_latency_info_compare(&info, &port->latency[other]) != 0 || + port->have_latency == have_latency; + + port->latency[other] = info; + port->have_latency = have_latency; + + spa_log_debug(this->log, "%p: set %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, this, + info.direction == SPA_DIRECTION_INPUT ? "input" : "output", + info.min_quantum, info.max_quantum, + info.min_rate, info.max_rate, + info.min_ns, info.max_ns); + + if (this->monitor_passthrough) { + if (port->is_monitor) + oport = GET_PORT(this, other, port_id-1); + else if (this->monitor && direction == SPA_DIRECTION_INPUT) + oport = GET_PORT(this, other, port_id+1); + else + return 0; + + if (oport != NULL && + spa_latency_info_compare(&info, &oport->latency[other]) != 0) { + oport->latency[other] = info; + oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + oport->params[IDX_Latency].user++; + emit_port_info(this, oport, false); + } + } else { + spa_latency_info_combine_start(&info, other); + for (i = 0; i < this->dir[direction].n_ports; i++) { + oport = GET_PORT(this, direction, i); + if ((oport->is_monitor) || !oport->have_latency) + continue; + spa_log_debug(this->log, "%p: combine %d", this, i); + spa_latency_info_combine(&info, &oport->latency[other]); + } + spa_latency_info_combine_finish(&info); + + spa_log_debug(this->log, "%p: combined %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, this, + info.direction == SPA_DIRECTION_INPUT ? "input" : "output", + info.min_quantum, info.max_quantum, + info.min_rate, info.max_rate, + info.min_ns, info.max_ns); + + for (i = 0; i < this->dir[other].n_ports; i++) { + oport = GET_PORT(this, other, i); + if (oport->is_monitor) + continue; + spa_log_debug(this->log, "%p: change %d", this, i); + if (spa_latency_info_compare(&info, &oport->latency[other]) != 0) { + oport->latency[other] = info; + oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + oport->params[IDX_Latency].user++; + emit_port_info(this, oport, false); + } + } + } + if (emit) { + 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_tag(void *object, + enum spa_direction direction, + uint32_t port_id, + uint32_t flags, + const struct spa_pod *tag) +{ + struct impl *this = object; + struct port *port, *oport; + enum spa_direction other = SPA_DIRECTION_REVERSE(direction); + uint32_t i; + + spa_log_debug(this->log, "%p: set tag direction:%d id:%d %p", + this, direction, port_id, tag); + + port = GET_PORT(this, direction, port_id); + if (port->is_monitor && !this->monitor_passthrough) + return 0; + + if (tag != NULL) { + struct spa_tag_info info; + void *state = NULL; + if (spa_tag_parse(tag, &info, &state) < 0 || + info.direction != other) + return -EINVAL; + } + if (spa_tag_compare(tag, this->dir[other].tag) != 0) { + free(this->dir[other].tag); + this->dir[other].tag = tag ? spa_pod_copy(tag) : NULL; + + for (i = 0; i < this->dir[other].n_ports; i++) { + oport = GET_PORT(this, other, i); + oport->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + oport->params[IDX_Tag].user++; + emit_port_info(this, oport, false); + } + } + port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; + port->params[IDX_Tag].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); + + spa_log_debug(this->log, "%p: %d:%d set format", this, direction, port_id); + + if (format == NULL) { + port->have_format = false; + clear_buffers(this, port); + } else { + struct spa_video_info info = { 0 }; + spa_debug_format(2, NULL, format); + + 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(this, direction, port_id)) { + if (info.media_type != SPA_MEDIA_TYPE_video || + 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_video_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_VIDEO_FORMAT_DSP_F32) { + spa_log_error(this->log, "unexpected format %d<->%d", + info.info.dsp.format, SPA_VIDEO_FORMAT_DSP_F32); + return -EINVAL; + } + port->blocks = 1; + port->stride = 16; + } + 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 { + struct dir *dir = &this->dir[direction]; + struct dir *odir = &this->dir[SPA_DIRECTION_REVERSE(direction)]; + + if (info.media_type != SPA_MEDIA_TYPE_video) { + spa_log_error(this->log, "unexpected types %d/%d", + info.media_type, info.media_subtype); + return -EINVAL; + } + if ((res = spa_format_video_parse(format, &info)) < 0) { + spa_log_error(this->log, "can't parse format %s", spa_strerror(res)); + return res; + } + port->stride = 2; + port->stride *= info.info.raw.size.width; + port->blocks = 1; + dir->format = info; + dir->have_format = true; + if (odir->have_format) { + if (memcmp(&odir->format, &dir->format, sizeof(dir->format)) == 0) + this->fmt_passthrough = true; + } + this->setup = false; + } + port->format = info; + port->have_format = true; + + spa_log_debug(this->log, "%p: %d %d %d", this, + port_id, port->stride, port->blocks); + } + + port->info.change_mask |= SPA_PORT_CHANGE_MASK_FLAGS; + SPA_FLAG_UPDATE(port->info.flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS, this->fmt_passthrough); + + 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_Tag: + return port_set_tag(this, direction, port_id, flags, param); + case SPA_PARAM_Format: + return port_set_format(this, direction, port_id, flags, param); + default: + return -ENOENT; + } +} + +static inline 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 inline 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/%d on port %d %u", + this, b->id, port->n_buffers, port->id, b->flags); + return b; +} + +static inline void dequeue_buffer(struct impl *this, struct port *port, struct buffer *b) +{ + spa_log_trace_fp(this->log, "%p: dequeue buffer %d on port %d %u", + this, b->id, port->id, b->flags); + if (!SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_QUEUED)) + return; + spa_list_remove(&b->link); + SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_QUEUED); +} + +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_log_debug(this->log, "%p: use buffers %d on port %d:%d", + this, n_buffers, direction, port_id); + + clear_buffers(this, port); + + if (n_buffers > 0 && !port->have_format) + return -EIO; + if (n_buffers > MAX_BUFFERS) + return -ENOSPC; + + 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; + } + if (SPA_FLAG_IS_SET(flags, SPA_NODE_BUFFERS_FLAG_ALLOC)) { + struct port *other = GET_PORT(this, SPA_DIRECTION_REVERSE(direction), port_id); + + if (other->n_buffers <= 0) + return -EIO; + *b->buf = *other->buffers[i % other->n_buffers].buf; + b->datas[0] = other->buffers[i % other->n_buffers].datas[0]; + } else { + for (j = 0; j < n_datas; j++) { + void *data = d[j].data; + if (data == NULL && SPA_FLAG_IS_SET(d[j].flags, SPA_DATA_FLAG_MAPPABLE)) { + data = mmap(NULL, d[j].maxsize, + PROT_READ, MAP_SHARED, d[j].fd, + d[j].mapoffset); + if (data == MAP_FAILED) { + spa_log_error(this->log, "%p: mmap failed %d on buffer %d %d %p: %m", + this, j, i, d[j].type, data); + return -EINVAL; + } + } + if (data != NULL && !SPA_IS_ALIGNED(data, this->max_align)) { + spa_log_warn(this->log, "%p: memory %d on buffer %d not aligned", + this, j, i); + } + b->datas[j] = data; + maxsize = SPA_MAX(maxsize, d[j].maxsize); + } + } + if (direction == SPA_DIRECTION_OUTPUT) + queue_buffer(this, port, i); + } + port->maxsize = maxsize; + port->n_buffers = n_buffers; + + return 0; +} + +struct io_data { + struct port *port; + void *data; + size_t size; +}; + +static int do_set_port_io(struct spa_loop *loop, bool async, uint32_t seq, + const void *data, size_t size, void *user_data) +{ + const struct io_data *d = user_data; + d->port->io = d->data; + 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: + if (this->data_loop) { + struct io_data d = { .port = port, .data = data, .size = size }; + spa_loop_invoke(this->data_loop, do_set_port_io, 0, NULL, 0, true, &d); + } + else + port->io = data; + break; + case SPA_IO_RateMatch: + this->io_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) +{ + 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 *in_port, *out_port; + struct spa_io_buffers *input, *output; + struct buffer *dbuf, *sbuf; + struct dir *in, *out; + struct AVFrame *f; + void *datas[8]; + uint32_t sizes[8], strides[8]; + int res; + + spa_return_val_if_fail(this != NULL, -EINVAL); + + in = &this->dir[SPA_DIRECTION_INPUT]; + out = &this->dir[SPA_DIRECTION_OUTPUT]; + + out_port = GET_OUT_PORT(this, 0); + if ((output = out_port->io) == NULL) + return -EIO; + + if (output->status == SPA_STATUS_HAVE_DATA) + return SPA_STATUS_HAVE_DATA; + + /* recycle */ + if (output->buffer_id < out_port->n_buffers) { + queue_buffer(this, out_port, output->buffer_id); + output->buffer_id = SPA_ID_INVALID; + } + + in_port = GET_IN_PORT(this, 0); + if ((input = in_port->io) == NULL) + return -EIO; + + if (input->status != SPA_STATUS_HAVE_DATA) + return SPA_STATUS_NEED_DATA; + + if (input->buffer_id >= in_port->n_buffers) { + input->status = -EINVAL; + return -EINVAL; + } + + sbuf = &in_port->buffers[input->buffer_id]; + + if ((dbuf = peek_buffer(this, out_port)) == NULL) { + spa_log_error(this->log, "%p: out of buffers", this); + return -EPIPE; + } + dbuf = &out_port->buffers[input->buffer_id]; + + spa_log_trace(this->log, "%d %p:%p %d %d %d", input->buffer_id, sbuf->buf->datas[0].chunk, + dbuf->buf->datas[0].chunk, sbuf->buf->datas[0].chunk->size, + sbuf->id, dbuf->id); + + /* do decoding */ + if (this->decoder.codec) { + this->decoder.packet->data = sbuf->datas[0]; + this->decoder.packet->size = sbuf->buf->datas[0].chunk->size; + + if ((res = avcodec_send_packet(this->decoder.context, this->decoder.packet)) < 0) { + spa_log_error(this->log, "failed to send frame to codec: %d %p:%d", + res, this->decoder.packet->data, this->decoder.packet->size); + return -EIO; + } + + f = this->decoder.frame; + if (avcodec_receive_frame(this->decoder.context, f) < 0) { + spa_log_error(this->log, "failed to receive frame from codec"); + return -EIO; + } + + in->pix_fmt = f->format; + in->width = f->width; + in->height = f->height; + } else { + f = this->decoder.frame; + f->format = in->pix_fmt; + f->width = in->width; + f->height = in->height; + f->data[0] = sbuf->datas[0]; + f->linesize[0] = sbuf->buf->datas[0].chunk->stride; + } + + /* do conversion */ + if (f->format != out->pix_fmt || + f->width != out->width || + f->height != out->height) { + if (this->convert.context == NULL) { + this->convert.context = sws_getContext( + f->width, f->height, f->format, + out->width, out->height, out->pix_fmt, + 0, NULL, NULL, NULL); + } + sws_scale_frame(this->convert.context, this->convert.frame, f); + f = this->convert.frame; + } + /* do encoding */ + if (this->encoder.codec) { + if ((res = avcodec_send_frame(this->encoder.context, f)) < 0) { + spa_log_error(this->log, "failed to send frame to codec: %d", res); + return -EIO; + } + if (avcodec_receive_packet(this->encoder.context, this->encoder.packet) < 0) { + spa_log_error(this->log, "failed to receive frame from codec"); + return -EIO; + } + datas[0] = this->encoder.packet->data; + sizes[0] = this->encoder.packet->size; + strides[0] = 1; + + } else { + datas[0] = f->data[0]; + strides[0] = f->linesize[0]; + sizes[0] = strides[0] * out->height; + } + + /* write to output */ + for (uint_fast32_t i = 0; i < dbuf->buf->n_datas; ++i) { + if (SPA_FLAG_IS_SET(dbuf->buf->datas[i].flags, SPA_DATA_FLAG_DYNAMIC)) + dbuf->buf->datas[i].data = datas[i]; + else if (datas[i] && dbuf->datas[i] && dbuf->datas[i] != datas[i]) + memcpy(dbuf->datas[i], datas[i], sizes[i]); + + if (dbuf->buf->datas[i].chunk != sbuf->buf->datas[i].chunk) { + dbuf->buf->datas[i].chunk->stride = strides[i]; + dbuf->buf->datas[i].chunk->size = sizes[i]; + } + } + + dequeue_buffer(this, out_port, dbuf); + output->buffer_id = dbuf->id; + output->status = SPA_STATUS_HAVE_DATA; + + input->status = SPA_STATUS_NEED_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, + .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 void free_dir(struct dir *dir) +{ + uint32_t i; + for (i = 0; i < MAX_PORTS; i++) + free(dir->ports[i]); + free(dir->tag); +} + +static int impl_clear(struct spa_handle *handle) +{ + struct impl *this; + + spa_return_val_if_fail(handle != NULL, -EINVAL); + + this = (struct impl *) handle; + + free_dir(&this->dir[SPA_DIRECTION_INPUT]); + free_dir(&this->dir[SPA_DIRECTION_OUTPUT]); + 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->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); + 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)); + } + props_reset(&this->props); + + this->rate_limit.interval = 2 * SPA_NSEC_PER_SEC; + this->rate_limit.burst = 1; + + 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, SPA_KEY_PORT_IGNORE_LATENCY)) + this->port_ignore_latency = spa_atob(s); + else if (spa_streq(k, SPA_KEY_PORT_GROUP)) + spa_scnprintf(this->group_name, sizeof(this->group_name), "%s", s); + else if (spa_streq(k, "monitor.passthrough")) + this->monitor_passthrough = spa_atob(s); + else + videoconvert_set_param(this, k, s); + } + + this->dir[SPA_DIRECTION_INPUT].direction = SPA_DIRECTION_INPUT; + this->dir[SPA_DIRECTION_OUTPUT].direction = 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; + this->info.flags = SPA_NODE_FLAG_RT | + SPA_NODE_FLAG_IN_PORT_CONFIG | + SPA_NODE_FLAG_OUT_PORT_CONFIG | + SPA_NODE_FLAG_NEED_CONFIGURE; + 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_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; + + 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,}, +}; + +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_videoconvert_ffmpeg_factory = { + SPA_VERSION_HANDLE_FACTORY, + SPA_NAME_VIDEO_CONVERT".ffmpeg", + NULL, + impl_get_size, + impl_init, + impl_enum_interface_info, +}; diff --git a/spa/plugins/videotestsrc/videotestsrc.c b/spa/plugins/videotestsrc/videotestsrc.c index a10c3be6..aff3f536 100644 --- a/spa/plugins/videotestsrc/videotestsrc.c +++ b/spa/plugins/videotestsrc/videotestsrc.c @@ -84,7 +84,7 @@ struct impl { struct spa_log *log; struct spa_loop *data_loop; - struct spa_system *data_system; + struct spa_loop_utils *loop_utils; uint64_t info_all; struct spa_node_info info; @@ -98,7 +98,7 @@ struct impl { struct spa_callbacks callbacks; bool async; - struct spa_source timer_source; + struct spa_source *timer_source; struct itimerspec timerspec; bool started; @@ -277,27 +277,10 @@ static void set_timer(struct impl *this, bool enabled) this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 0; } - spa_system_timerfd_settime(this->data_system, - this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL); + spa_loop_utils_update_timer(this->loop_utils, this->timer_source, &this->timerspec.it_value, &this->timerspec.it_interval, true); } } -static int read_timer(struct impl *this) -{ - uint64_t expirations; - int res = 0; - - if (this->async || this->props.live) { - if ((res = spa_system_timerfd_read(this->data_system, - this->timer_source.fd, &expirations)) < 0) { - if (res != -EAGAIN) - spa_log_error(this->log, "%p: timerfd error: %s", - this, spa_strerror(res)); - } - } - return res; -} - static int make_buffer(struct impl *this) { struct buffer *b; @@ -305,9 +288,6 @@ static int make_buffer(struct impl *this) struct spa_io_buffers *io = port->io; uint32_t n_bytes; - if (read_timer(this) < 0) - return 0; - if (spa_list_is_empty(&port->empty)) { set_timer(this, false); spa_log_error(this->log, "%p: out of buffers", this); @@ -343,9 +323,9 @@ static int make_buffer(struct impl *this) return io->status; } -static void on_output(struct spa_source *source) +static void on_output(void* data, uint64_t expirations) { - struct impl *this = source->data; + struct impl *this = data; int res; res = make_buffer(this); @@ -859,7 +839,7 @@ static int impl_get_interface(struct spa_handle *handle, const char *type, void static int do_remove_timer(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct impl *this = user_data; - spa_loop_remove_source(this->data_loop, &this->timer_source); + spa_loop_remove_source(this->data_loop, this->timer_source); return 0; } @@ -873,7 +853,7 @@ static int impl_clear(struct spa_handle *handle) if (this->data_loop) spa_loop_invoke(this->data_loop, do_remove_timer, 0, NULL, 0, true, this); - spa_system_close(this->data_system, this->timer_source.fd); + spa_loop_utils_destroy_source(this->loop_utils, this->timer_source); return 0; } @@ -905,7 +885,7 @@ impl_init(const struct spa_handle_factory *factory, this->log = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_Log); this->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop); - this->data_system = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataSystem); + this->loop_utils = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_LoopUtils); spa_hook_list_init(&this->hooks); @@ -926,20 +906,12 @@ impl_init(const struct spa_handle_factory *factory, this->info.n_params = 2; reset_props(&this->props); - this->timer_source.func = on_output; - this->timer_source.data = this; - this->timer_source.fd = spa_system_timerfd_create(this->data_system, CLOCK_MONOTONIC, - SPA_FD_CLOEXEC | SPA_FD_NONBLOCK); - this->timer_source.mask = SPA_IO_IN; - this->timer_source.rmask = 0; + this->timer_source = spa_loop_utils_add_timer(this->loop_utils, on_output, this); this->timerspec.it_value.tv_sec = 0; this->timerspec.it_value.tv_nsec = 0; this->timerspec.it_interval.tv_sec = 0; this->timerspec.it_interval.tv_nsec = 0; - if (this->data_loop) - spa_loop_add_source(this->data_loop, &this->timer_source); - port = &this->port; port->info_all = SPA_PORT_CHANGE_MASK_FLAGS | SPA_PORT_CHANGE_MASK_PARAMS; diff --git a/spa/plugins/vulkan/vulkan-utils.c b/spa/plugins/vulkan/vulkan-utils.c index cbf30f3a..64323135 100644 --- a/spa/plugins/vulkan/vulkan-utils.c +++ b/spa/plugins/vulkan/vulkan-utils.c @@ -244,11 +244,8 @@ int vulkan_write_pixels(struct vulkan_base *s, struct vulkan_write_pixels_info * void *vmap; VK_CHECK_RESULT(vkMapMemory(s->device, vk_sbuf->memory, 0, VK_WHOLE_SIZE, 0, &vmap)); - char *map = (char *)vmap; - // upload data - const char *pdata = info->data; - memcpy(map, pdata, info->stride * info->size.height); + memcpy(vmap, info->data, info->stride * info->size.height); info->copies[0] = (VkBufferImageCopy) { .imageExtent.width = info->size.width, @@ -626,7 +623,7 @@ int vulkan_create_dmabuf(struct vulkan_base *s, struct external_buffer_info *inf vkGetImageMemoryRequirements(s->device, vk_buf->image, &memoryRequirements); - spa_log_info(s->log, "export DMABUF %zd", memoryRequirements.size); + spa_log_info(s->log, "export DMABUF %" PRIu64, memoryRequirements.size); for (uint32_t i = 0; i < info->spa_buf->n_datas; i++) { VkImageSubresource subresource = { diff --git a/spa/tools/spa-json-dump.c b/spa/tools/spa-json-dump.c index 0e2e8cec..a1f2e619 100644 --- a/spa/tools/spa-json-dump.c +++ b/spa/tools/spa-json-dump.c @@ -82,14 +82,12 @@ static int dump(FILE *file, int indent, struct spa_json *it, const char *value, spa_json_enter(it, &sub); else sub = *it; - while (spa_json_get_string(&sub, key, sizeof(key)) > 0) { + while ((len = spa_json_object_next(&sub, key, sizeof(key), &value)) > 0) { fprintf(file, "%s\n%*s", count++ > 0 ? "," : "", indent+2, ""); encode_string(file, key, strlen(key)); fprintf(file, ": "); - if ((len = spa_json_next(&sub, &value)) <= 0) - break; res = dump(file, indent+2, &sub, value, len); if (res < 0) { if (toplevel) @@ -123,8 +121,7 @@ static int process_json(const char *filename, void *buf, size_t size) struct spa_json it; const char *value; - spa_json_init(&it, buf, size); - if ((len = spa_json_next(&it, &value)) <= 0) { + if ((len = spa_json_begin(&it, buf, size, &value)) <= 0) { fprintf(stderr, "not a valid file '%s': %s\n", filename, spa_strerror(len)); return -EINVAL; } diff --git a/src/daemon/client-rt.conf.avail/20-upmix.conf.in b/src/daemon/client-rt.conf.avail/20-upmix.conf.in deleted file mode 100644 index 064eba14..00000000 --- a/src/daemon/client-rt.conf.avail/20-upmix.conf.in +++ /dev/null @@ -1,8 +0,0 @@ -# Enables upmixing -stream.properties = { - channelmix.upmix = true - channelmix.upmix-method = psd # none, simple - channelmix.lfe-cutoff = 150 - channelmix.fc-cutoff = 12000 - channelmix.rear-delay = 12.0 -} diff --git a/src/daemon/client-rt.conf.avail/meson.build b/src/daemon/client-rt.conf.avail/meson.build deleted file mode 100644 index a6dc07bb..00000000 --- a/src/daemon/client-rt.conf.avail/meson.build +++ /dev/null @@ -1,12 +0,0 @@ -conf_files = [ - '20-upmix.conf', -] - -foreach c : conf_files - res = configure_file(input : '@0@.in'.format(c), - output : c, - configuration : conf_config, - install_dir : pipewire_confdatadir / 'client-rt.conf.avail') - test(f'validate-json-client-rt-@c@', spa_json_dump_exe, args : res) -endforeach - diff --git a/src/daemon/client-rt.conf.in b/src/daemon/client-rt.conf.in deleted file mode 100644 index ed08a5f1..00000000 --- a/src/daemon/client-rt.conf.in +++ /dev/null @@ -1,136 +0,0 @@ -# Real-time Client 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@/client-rt.conf.d/ for system-wide changes or in -# ~/.config/pipewire/client-rt.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 = 0 - - #default.clock.quantum-limit = 8192 -} - -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 ) ] ) - # ( condition = [ { <key> = <value> ... } ... ] ) - #} - # - # 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 = @rtprio_client@ - #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 } - - # Allows creating devices that run in the context of the - # client. Is used by the session manager. - { name = libpipewire-module-client-device } - - # Makes a factory for wrapping nodes in an adapter with a - # converter and resampler. - { name = libpipewire-module-adapter } - - # Allows applications to create metadata objects. It creates - # a factory for Metadata objects. - { name = libpipewire-module-metadata } - - # Provides factories to make session manager objects. - { name = libpipewire-module-session-manager } -] - -filter.properties = { - #node.latency = 1024/48000 -} - -stream.properties = { - #node.latency = 1024/48000 - #node.autoconnect = true - #resample.quality = 4 - #channelmix.normalize = false - #channelmix.mix-lfe = true - #channelmix.upmix = true - #channelmix.upmix-method = psd # none, simple - #channelmix.lfe-cutoff = 150 - #channelmix.fc-cutoff = 12000 - #channelmix.rear-delay = 12.0 - #channelmix.stereo-widen = 0.0 - #channelmix.hilbert-taps = 0 - #dither.noise = 0 -} - -stream.rules = [ - { matches = [ - { - # all keys must match the value. ! negates. ~ starts regex. - #application.name = "pw-cat" - #node.name = "~Google Chrome$" - } - ] - actions = { - update-props = { - #node.latency = 512/48000 - } - } - } -] - -alsa.properties = { - #alsa.deny = false - # ALSA params take a single value, an array [] of values - # or a range { min=.. max=... } - #alsa.access = [ MMAP_INTERLEAVED MMAP_NONINTERLEAVED RW_INTERLEAVED RW_NONINTERLEAVED ] - #alsa.format = [ FLOAT S32 S24 S24_3 S16 U8 ] - #alsa.rate = { min=1 max=384000 } # or [ 44100 48000 .. ] - #alsa.channels = { min=1 max=64 } # or [ 2 4 6 .. ] - #alsa.period-bytes = { min=128 max=2097152 } # or [ 128 256 1024 .. ] - #alsa.buffer-bytes = { min=256 max=4194304 } # or [ 256 512 4096 .. ] - - #alsa.volume-method = cubic # linear, cubic -} - -# client specific properties -alsa.rules = [ - { matches = [ { application.process.binary = "resolve" } ] - actions = { - update-props = { - alsa.buffer-bytes = 131072 - } - } - } -] diff --git a/src/daemon/client.conf.in b/src/daemon/client.conf.in index 896a7381..46874af9 100644 --- a/src/daemon/client.conf.in +++ b/src/daemon/client.conf.in @@ -27,6 +27,7 @@ context.spa-libs = { # audio.convert.* = audioconvert/libspa-audioconvert support.* = support/libspa-support + video.convert.* = videoconvert/libspa-videoconvert } context.modules = [ @@ -40,6 +41,16 @@ context.modules = [ # 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 = @rtprio_client@ + #rt.time.soft = -1 + #rt.time.hard = -1 + } + flags = [ ifexists nofail ] + condition = [ { module.rt = !false } ] + } # The native communication protocol. { name = libpipewire-module-protocol-native } @@ -47,22 +58,32 @@ context.modules = [ # 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 } + { name = libpipewire-module-client-node + condition = [ { module.client-node = !false } ] + } # Allows creating devices that run in the context of the # client. Is used by the session manager. - { name = libpipewire-module-client-device } + { name = libpipewire-module-client-device + condition = [ { module.client-device = !false } ] + } # Makes a factory for wrapping nodes in an adapter with a # converter and resampler. - { name = libpipewire-module-adapter } + { name = libpipewire-module-adapter + condition = [ { module.adapter = !false } ] + } # Allows applications to create metadata objects. It creates # a factory for Metadata objects. - { name = libpipewire-module-metadata } + { name = libpipewire-module-metadata + condition = [ { module.metadata = !false } ] + } # Provides factories to make session manager objects. - { name = libpipewire-module-session-manager } + { name = libpipewire-module-session-manager + condition = [ { module.session-manager = !false } ] + } ] filter.properties = { @@ -84,3 +105,44 @@ stream.properties = { #channelmix.hilbert-taps = 0 #dither.noise = 0 } + +stream.rules = [ + { matches = [ + { + # all keys must match the value. ! negates. ~ starts regex. + #application.name = "pw-cat" + #node.name = "~Google Chrome$" + } + ] + actions = { + update-props = { + #node.latency = 512/48000 + } + } + } +] + +alsa.properties = { + #alsa.deny = false + # ALSA params take a single value, an array [] of values + # or a range { min=.. max=... } + #alsa.access = [ MMAP_INTERLEAVED MMAP_NONINTERLEAVED RW_INTERLEAVED RW_NONINTERLEAVED ] + #alsa.format = [ FLOAT S32 S24 S24_3 S16 U8 ] + #alsa.rate = { min=1 max=384000 } # or [ 44100 48000 .. ] + #alsa.channels = { min=1 max=64 } # or [ 2 4 6 .. ] + #alsa.period-bytes = { min=128 max=2097152 } # or [ 128 256 1024 .. ] + #alsa.buffer-bytes = { min=256 max=4194304 } # or [ 256 512 4096 .. ] + + #alsa.volume-method = cubic # linear, cubic +} + +# client specific properties +alsa.rules = [ + { matches = [ { application.process.binary = "resolve" } ] + actions = { + update-props = { + alsa.buffer-bytes = 131072 + } + } + } +] diff --git a/src/daemon/filter-chain/35-ebur128.conf b/src/daemon/filter-chain/35-ebur128.conf new file mode 100644 index 00000000..f12c4a9c --- /dev/null +++ b/src/daemon/filter-chain/35-ebur128.conf @@ -0,0 +1,63 @@ +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "EBU R128 Normalizer" + media.name = "EBU R128 Normalizer" + filter.graph = { + nodes = [ + { + name = ebur128 + type = ebur128 + label = ebur128 + } + { + name = lufsL + type = ebur128 + label = lufs2gain + control = { + "Target LUFS" = -16.0 + } + } + { + name = lufsR + type = ebur128 + label = lufs2gain + control = { + "Target LUFS" = -16.0 + } + } + { + name = volumeL + type = builtin + label = linear + } + { + name = volumeR + type = builtin + label = linear + } + ] + links = [ + { output = "ebur128:Out FL" input = "volumeL:In" } + { output = "ebur128:Global LUFS" input = "lufsL:LUFS" } + { output = "lufsL:Gain" input = "volumeL:Mult" } + { output = "ebur128:Out FR" input = "volumeR:In" } + { output = "ebur128:Global LUFS" input = "lufsR:LUFS" } + { output = "lufsR:Gain" input = "volumeR:Mult" } + ] + inputs = [ "ebur128:In FL" "ebur128:In FR" ] + outputs = [ "volumeL:Out" "volumeR:Out" ] + } + capture.props = { + node.name = "effect_input.ebur128_normalize" + audio.position = [ FL FR ] + media.class = Audio/Sink + } + playback.props = { + node.name = "effect_output.ebur128_normalize" + audio.position = [ FL FR ] + node.passive = true + } + } + } +] diff --git a/src/daemon/filter-chain/36-dcblock.conf b/src/daemon/filter-chain/36-dcblock.conf new file mode 100644 index 00000000..b3b6feb6 --- /dev/null +++ b/src/daemon/filter-chain/36-dcblock.conf @@ -0,0 +1,59 @@ +context.modules = [ + { name = libpipewire-module-filter-chain + args = { + node.description = "DCBlock Filter" + media.name = "DCBlock Filter" + filter.graph = { + nodes = [ + { + name = dcblock + type = builtin + label = dcblock + control = { + "R" = 0.995 + } + } + { + # add a short 20ms ramp + name = ramp + type = builtin + label = ramp + control = { + "Start" = 0.0 + "Stop" = 1.0 + "Duration (s)" = 0.020 + } + } + { + name = volumeL + type = builtin + label = mult + } + { + name = volumeR + type = builtin + label = mult + } + ] + links = [ + { output = "dcblock:Out 1" input = "volumeL:In 1" } + { output = "dcblock:Out 2" input = "volumeR:In 1" } + { output = "ramp:Out" input = "volumeL:In 2" } + { output = "ramp:Out" input = "volumeR:In 2" } + ] + inputs = [ "dcblock:In 1" "dcblock:In 2" ] + outputs = [ "volumeL:Out" "volumeR:Out" ] + } + capture.props = { + node.name = "effect_input.dcblock" + audio.position = [ FL FR ] + media.class = Audio/Sink + } + playback.props = { + node.name = "effect_output.dcblock" + audio.position = [ FL FR ] + node.passive = true + } + } + } +] diff --git a/src/daemon/filter-chain/meson.build b/src/daemon/filter-chain/meson.build index 4bbcc355..0e634064 100644 --- a/src/daemon/filter-chain/meson.build +++ b/src/daemon/filter-chain/meson.build @@ -9,6 +9,7 @@ conf_files = [ [ 'sink-eq6.conf', 'sink-eq6.conf' ], [ 'sink-matrix-spatialiser.conf', 'sink-matrix-spatialiser.conf' ], [ 'source-rnnoise.conf', 'source-rnnoise.conf' ], + [ 'sink-upmix-5.1-filter.conf', 'sink-upmix-5.1-filter.conf' ], ] foreach c : conf_files diff --git a/src/daemon/filter-chain/sink-upmix-5.1-filter.conf b/src/daemon/filter-chain/sink-upmix-5.1-filter.conf new file mode 100644 index 00000000..19773304 --- /dev/null +++ b/src/daemon/filter-chain/sink-upmix-5.1-filter.conf @@ -0,0 +1,151 @@ +# Stereo to 5.1 upmix sink +# +# 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 = "Upmix Sink" + filter.graph = { + nodes = [ + { type = builtin name = copyFL label = copy } + { type = builtin name = copyFR label = copy } + { type = builtin name = copyOFL label = copy } + { type = builtin name = copyOFR label = copy } + { + # this mixes the front left and right together + # for filtering the center and subwoofer signal- + name = mixF + type = builtin + label = mixer + control = { + "Gain 1" = 0.707 + "Gain 2" = 0.707 + } + } + { + # filtering of the FC and LFE channel. We use a 2 channel + # parametric equalizer with custom filters for each channel. + # This makes it possible to run the filters in parallel. + type = builtin + name = eq_FC_LFE + label = param_eq + config = { + filters1 = [ + # FC is a crossover filter (with 2 lowpass biquads) + { type = bq_lowpass freq = 12000 }, + { type = bq_lowpass freq = 12000 }, + ] + filters2 = [ + # LFE is first a gain adjustment (with a highself) and + # then a crossover filter (with 2 lowpass biquads) + { type = bq_highshelf freq = 0 gain = -20.0 }, # gain -20dB + { type = bq_lowpass freq = 120 }, + { type = bq_lowpass freq = 120 }, + ] + } + } + { + # for the rear channels, we subtract the front channels. Do this + # with a mixer with negative gain to flip the sign. + name = subR + type = builtin + label = mixer + control = { + "Gain 1" = 0.707 + "Gain 2" = -0.707 + } + } + { + # a delay for the rear Left channel. This can be + # replaced with the convolver below. */ + type = builtin + name = delayRL + label = delay + config = { "max-delay" = 1 } + control = { "Delay (s)" = 0.012 } + } + { + # a delay for the rear Right channel. This can be + # replaced with the convolver below. */ + type = builtin + name = delayRR + label = delay + config = { "max-delay" = 1 } + control = { "Delay (s)" = 0.012 } + } + { + # an optional convolver with a hilbert curve to + # change the phase. It also has a delay, making the above + # left delay filter optional. + type = builtin + name = convRL + label = convolver + config = { + gain = 1.0 + delay = 0.012 + filename = "/hilbert" + length = 33 + } + } + { + # an optional convolver with a hilbert curve to + # change the phase. It also has a delay, making the above + # right delay filter optional. + type = builtin + name = convRR + label = convolver + config = { + gain = -1.0 + delay = 0.012 + filename = "/hilbert" + length = 33 + } + } + ] + links = [ + { output = "copyFL:Out" input="mixF:In 1" } + { output = "copyFR:Out" input="mixF:In 2" } + { output = "copyFL:Out" input="copyOFR:In" } + { output = "copyFR:Out" input="copyOFL:In" } + { output = "mixF:Out" input="eq_FC_LFE:In 1" } + { output = "mixF:Out" input="eq_FC_LFE:In 2" } + { output = "copyFL:Out" input="subR:In 1" } + { output = "copyFR:Out" input="subR:In 2" } + # here we can choose to just delay or also convolve + # + #{ output = "subR:Out" input="delayRL:In" } + #{ output = "subR:Out" input="delayRR:In" } + { output = "subR:Out" input="convRL:In" } + { output = "subR:Out" input="convRR:In" } + ] + inputs = [ "copyFL:In" "copyFR:In" ] + outputs = [ + "copyOFL:Out" + "copyOFR:Out" + "eq_FC_LFE:Out 1" + "eq_FC_LFE:Out 2" + # here we can choose to just delay or also convolve + # + #"delayRL:Out" + #"delayRR:Out" + "convRL:Out" + "convRR:Out" + ] + } + capture.props = { + node.name = "effect_input.upmix_5.1" + media.class = "Audio/Sink" + audio.position = [ FL FR ] + } + playback.props = { + node.name = "effect_output.upmix_5.1" + audio.position = [ FL FR FC LFE RL RR ] + stream.dont-remix = true + node.passive = true + } + } + } +] + diff --git a/src/daemon/jack.conf.in b/src/daemon/jack.conf.in index b3be1c68..32d8076d 100644 --- a/src/daemon/jack.conf.in +++ b/src/daemon/jack.conf.in @@ -95,6 +95,7 @@ jack.properties = { #jack.max-client-ports = 768 #jack.fill-aliases = false #jack.writable-input = true + #jack.flag-midi2 = false } # client specific properties diff --git a/src/daemon/meson.build b/src/daemon/meson.build index 6d98f4b5..b2ebb937 100644 --- a/src/daemon/meson.build +++ b/src/daemon/meson.build @@ -66,7 +66,6 @@ endif conf_files = [ 'pipewire.conf', 'client.conf', - 'client-rt.conf', 'filter-chain.conf', 'jack.conf', 'minimal.conf', @@ -95,7 +94,6 @@ test('validate-json-pipewire-uninstalled.conf', spa_json_dump_exe, args : res) conf_avail_folders = [ 'pipewire.conf.avail', 'client.conf.avail', - 'client-rt.conf.avail', 'pipewire-pulse.conf.avail', ] diff --git a/src/daemon/minimal.conf.in b/src/daemon/minimal.conf.in index 031402c7..cfaab1c1 100644 --- a/src/daemon/minimal.conf.in +++ b/src/daemon/minimal.conf.in @@ -176,11 +176,11 @@ context.objects = [ # Creates an object from a PipeWire factory with the given parameters. # If nofail is given, errors are ignored (and no object is created). # - #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc "Spa:Pod:Object:Param:Props:patternType" = 1 } } + #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc node.param.Props = { patternType = 1 } } } #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] } #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } } #{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } } - #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc } } + #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc node.param.Props = { live = false } } } #{ factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } } # Make a default metadata store diff --git a/src/daemon/pipewire-aes67.conf.in b/src/daemon/pipewire-aes67.conf.in index 34c432f6..479c12c9 100644 --- a/src/daemon/pipewire-aes67.conf.in +++ b/src/daemon/pipewire-aes67.conf.in @@ -135,6 +135,10 @@ context.modules = [ audio.channels = 2 # These channel names will be visible both to applications and AES67 receivers node.channel-names = ["CH1", "CH2"] + # Uncomment this and comment node.group in send/recv stream.props to allow + # separate drivers for the RTP sink and PTP sending (i.e. force rate matching on + # the AES67 node rather than other nodes) + #aes67.driver-group = "pipewire.ptp0" stream.props = { ### Please change the sink name, this is necessary when you create multiple sinks diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in index 0d54cf9b..affb993d 100644 --- a/src/daemon/pipewire-pulse.conf.in +++ b/src/daemon/pipewire-pulse.conf.in @@ -60,11 +60,17 @@ context.exec = [ # load-module : loads a module with args and flags # args = "<module-name> <module-args>" # ( flags = [ nofail ] ) +# ( condition = [ { <key1> = <value1>, ... } ... ] ) +# conditions will check the pulse.properties key/values. pulse.cmd = [ - { cmd = "load-module" args = "module-always-sink" flags = [ ] } - { cmd = "load-module" args = "module-device-manager" flags = [ ] } - { cmd = "load-module" args = "module-device-restore" flags = [ ] } - { cmd = "load-module" args = "module-stream-restore" flags = [ ] } + { cmd = "load-module" args = "module-always-sink" flags = [ ] + condition = [ { pulse.cmd.always-sink = !false } ] } + { cmd = "load-module" args = "module-device-manager" flags = [ ] + condition = [ { pulse.cmd.device-manager = !false } ] } + { cmd = "load-module" args = "module-device-restore" flags = [ ] + condition = [ { pulse.cmd.device-restore = !false } ] } + { cmd = "load-module" args = "module-stream-restore" flags = [ ] + condition = [ { pulse.cmd.stream-restore = !false } ] } #{ cmd = "load-module" args = "module-switch-on-connect" } #{ cmd = "load-module" args = "module-gsettings" flags = [ nofail ] } ] @@ -152,6 +158,7 @@ pulse.rules = [ matches = [ { application.process.binary = "teams" } { application.process.binary = "teams-insiders" } + { application.process.binary = "teams-for-linux" } { application.process.binary = "skypeforlinux" } ] actions = { quirks = [ force-s16-info ] } diff --git a/src/daemon/pipewire-vulkan.conf.in b/src/daemon/pipewire-vulkan.conf.in index b00c8d35..388a4fc5 100644 --- a/src/daemon/pipewire-vulkan.conf.in +++ b/src/daemon/pipewire-vulkan.conf.in @@ -87,11 +87,11 @@ context.objects = [ # If condition is given, the object is created only when the context properties # all match the match rules. # - #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc "Spa:Pod:Object:Param:Props:patternType" = 1 } } + #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc node.param.Props = { patternType = 1 } } } #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] } #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } } #{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } } - #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc } } + #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc node.param.Props = { live = false } } } { factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = vulkan-compute-source object.export = true } } { factory = spa-node-factory args = { factory.name = api.vulkan.compute.filter node.name = vulkan-compute-filter object.export = true } } { factory = spa-node-factory args = { factory.name = api.vulkan.blit.filter node.name = vulkan-blit-filter object.export = true } } diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in index 470cf960..14f7c6ea 100644 --- a/src/daemon/pipewire.conf.in +++ b/src/daemon/pipewire.conf.in @@ -54,14 +54,6 @@ context.properties = { # #settings.check-quantum = false #settings.check-rate = false - - # keys checked below to disable module loading - module.x11.bell = true - # enables autoloading of access module, when disabled an alternative - # access module needs to be loaded. - module.access = true - # enables autoloading of module-jackdbus-detect - module.jackdbus-detect = true } context.properties.rules = [ @@ -92,6 +84,7 @@ context.spa-libs = { api.jack.* = jack/libspa-jack support.* = support/libspa-support video.convert.* = videoconvert/libspa-videoconvert + #filter.graph = filter-graph/libspa-filter-graph #videotestsrc = videotestsrc/libspa-videotestsrc #audiotestsrc = audiotestsrc/libspa-audiotestsrc } @@ -114,6 +107,7 @@ context.modules = [ # RTKit if the user doesn't have permission to use regular realtime # scheduling. You can also clamp utilisation values to improve scheduling # on embedded and heterogeneous systems, e.g. Arm big.LITTLE devices. + # use module.rt.args = { ... } to override the arguments. { name = libpipewire-module-rt args = { nice.level = -11 @@ -124,6 +118,7 @@ context.modules = [ #uclamp.max = 1024 } flags = [ ifexists nofail ] + condition = [ { module.rt = !false } ] } # The native communication protocol. @@ -137,34 +132,51 @@ context.modules = [ # The profile module. Allows application to access profiler # and performance data. It provides an interface that is used # by pw-top and pw-profiler. - { name = libpipewire-module-profiler } + # use module.profiler.args = { ... } to override the arguments. + { name = libpipewire-module-profiler + args = { + #profile.interval.ms = 0 + } + condition = [ { module.profiler = !false } ] + } # Allows applications to create metadata objects. It creates # a factory for Metadata objects. - { name = libpipewire-module-metadata } + { name = libpipewire-module-metadata + condition = [ { module.metadata = !false } ] + } # Creates a factory for making devices that run in the # context of the PipeWire server. - { name = libpipewire-module-spa-device-factory } + { name = libpipewire-module-spa-device-factory + condition = [ { module.spa-device-factory = !false } ] + } # Creates a factory for making nodes that run in the # context of the PipeWire server. - { name = libpipewire-module-spa-node-factory } + { name = libpipewire-module-spa-node-factory + condition = [ { module.spa-node-factory = !false } ] + } # 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 } + { name = libpipewire-module-client-node + condition = [ { module.client-node = !false } ] + } # Allows creating devices that run in the context of the # client. Is used by the session manager. - { name = libpipewire-module-client-device } + { name = libpipewire-module-client-device + condition = [ { module.client-device = !false } ] + } # The portal module monitors the PID of the portal process # and tags connections with the same PID as portal # connections. { name = libpipewire-module-portal flags = [ ifexists nofail ] + condition = [ { module.portal = !false } ] } # The access module can perform access checks and block @@ -178,18 +190,28 @@ context.modules = [ # for now enabled by default if access.socket is not specified #access.legacy = true } - condition = [ { module.access = true } ] + condition = [ { module.access = !false } ] } # Makes a factory for wrapping nodes in an adapter with a # converter and resampler. - { name = libpipewire-module-adapter } + { name = libpipewire-module-adapter + condition = [ { module.adapter = !false } ] + } # Makes a factory for creating links between ports. - { name = libpipewire-module-link-factory } + # use module.link-factory.args = { ... } to override the arguments. + { name = libpipewire-module-link-factory + args = { + #allow.link.passive = false + } + condition = [ { module.link-factory = !false } ] + } # Provides factories to make session manager objects. - { name = libpipewire-module-session-manager } + { name = libpipewire-module-session-manager + condition = [ { module.session-manager = !false } ] + } # Use libcanberra to play X11 Bell { name = libpipewire-module-x11-bell @@ -200,8 +222,11 @@ context.modules = [ #x11.xauthority = null } flags = [ ifexists nofail ] - condition = [ { module.x11.bell = true } ] + condition = [ { module.x11.bell = !false } ] } + # The JACK DBus detection module. When jackdbus is started, this + # will automatically make PipeWire become a JACK client. + # use module.jackdbus-detect.args = { ... } to override the arguments. { name = libpipewire-module-jackdbus-detect args = { #jack.library = libjack.so.0 @@ -223,7 +248,7 @@ context.modules = [ } } flags = [ ifexists nofail ] - condition = [ { module.jackdbus-detect = true } ] + condition = [ { module.jackdbus-detect = !false } ] } ] @@ -239,11 +264,11 @@ context.objects = [ # If condition is given, the object is created only when the context properties # all match the match rules. # - #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc "Spa:Pod:Object:Param:Props:patternType" = 1 } } + #{ factory = spa-node-factory args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc node.param.Props = { patternType = 1 } } } #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] } #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } } #{ factory = spa-node-factory args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } } - #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc } } + #{ factory = adapter args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc node.param.Props = { live = false }} } #{ factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } } # A default dummy driver. This handles nodes marked with the "node.always-process" @@ -258,6 +283,7 @@ context.objects = [ #clock.id = monotonic # realtime | tai | monotonic-raw | boottime #clock.name = "clock.system.monotonic" } + condition = [ { factory.dummy-driver = !false } ] } { factory = spa-node-factory args = { @@ -269,6 +295,7 @@ context.objects = [ node.freewheel = true #freewheel.wait = 10 } + condition = [ { factory.freewheel-driver = !false } ] } # This creates a new Source node. It will have input ports @@ -332,7 +359,7 @@ context.exec = [ # Run the session manager with -h for options. # @sm_comment@{ path = "@session_manager_path@" args = "@session_manager_args@" - @sm_comment@ condition = [ { exec.session-manager = null } { exec.session-manager = true } ] } + @sm_comment@ condition = [ { exec.session-manager = !false } ] } # # You can optionally start the pulseaudio-server here as well # but it is better to start it as a systemd service. @@ -340,5 +367,5 @@ context.exec = [ # on another address with the -a option (eg. -a tcp:4713). # @pulse_comment@{ path = "@pipewire_path@" args = [ "-c" "pipewire-pulse.conf" ] - @pulse_comment@ condition = [ { exec.pipewire-pulse = null } { exec.pipewire-pulse = true } ] } + @pulse_comment@ condition = [ { exec.pipewire-pulse = !false } ] } ] diff --git a/src/daemon/systemd/system/meson.build b/src/daemon/systemd/system/meson.build index d06d3adf..0cc17670 100644 --- a/src/daemon/systemd/system/meson.build +++ b/src/daemon/systemd/system/meson.build @@ -3,13 +3,19 @@ if get_option('systemd-system-unit-dir') != '' systemd_system_services_dir = get_option('systemd-system-unit-dir') endif -install_data(sources : ['pipewire.socket', 'pipewire-manager.socket'], +install_data(sources : ['pipewire.socket', 'pipewire-manager.socket', 'pipewire-pulse.socket' ], install_dir : systemd_system_services_dir) systemd_config = configuration_data() systemd_config.set('PW_BINARY', pipewire_bindir / 'pipewire') +systemd_config.set('PW_PULSE_BINARY', pipewire_bindir / 'pipewire-pulse') configure_file(input : 'pipewire.service.in', output : 'pipewire.service', configuration : systemd_config, install_dir : systemd_system_services_dir) + +configure_file(input : 'pipewire-pulse.service.in', + output : 'pipewire-pulse.service', + configuration : systemd_config, + install_dir : systemd_system_services_dir) diff --git a/src/daemon/systemd/system/pipewire-pulse.service.in b/src/daemon/systemd/system/pipewire-pulse.service.in new file mode 100644 index 00000000..8752f785 --- /dev/null +++ b/src/daemon/systemd/system/pipewire-pulse.service.in @@ -0,0 +1,24 @@ +[Unit] +Description=PipeWire PulseAudio Service +Requires=pipewire-pulse.socket +Wants=pipewire.service pipewire-session-manager.service +After=pipewire.service pipewire-session-manager.service + +[Service] +LockPersonality=yes +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +SystemCallArchitectures=native +SystemCallFilter=@system-service +Type=simple +AmbientCapabilities=CAP_SYS_NICE +ExecStart=@PW_PULSE_BINARY@ +Restart=on-failure +User=pipewire +Environment=PIPEWIRE_RUNTIME_DIR=%t/pipewire +Environment=PULSE_RUNTIME_PATH=%t/pulse + +[Install] +Also=pipewire-pulse.socket +WantedBy=pipewire.service + diff --git a/src/daemon/systemd/system/pipewire-pulse.socket b/src/daemon/systemd/system/pipewire-pulse.socket new file mode 100644 index 00000000..0a692949 --- /dev/null +++ b/src/daemon/systemd/system/pipewire-pulse.socket @@ -0,0 +1,12 @@ +[Unit] +Description=PipeWire PulseAudio System Socket + +[Socket] +Priority=6 +ListenStream=%t/pulse/native +SocketUser=pipewire +SocketGroup=pipewire +SocketMode=0660 + +[Install] +WantedBy=sockets.target diff --git a/src/examples/audio-capture.c b/src/examples/audio-capture.c index f6a31dfa..c44f905f 100644 --- a/src/examples/audio-capture.c +++ b/src/examples/audio-capture.c @@ -143,7 +143,6 @@ int main(int argc, char *argv[]) * the data. */ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", - PW_KEY_CONFIG_NAME, "client-rt.conf", PW_KEY_MEDIA_CATEGORY, "Capture", PW_KEY_MEDIA_ROLE, "Music", NULL); diff --git a/src/examples/audio-src-ring.c b/src/examples/audio-src-ring.c new file mode 100644 index 00000000..b96e26f1 --- /dev/null +++ b/src/examples/audio-src-ring.c @@ -0,0 +1,226 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +/* + [title] + Audio source using \ref pw_stream "pw_stream" and ringbuffer. + [title] + */ + +#include <stdio.h> +#include <errno.h> +#include <math.h> +#include <signal.h> + +#include <spa/param/audio/format-utils.h> +#include <spa/utils/ringbuffer.h> + +#include <pipewire/pipewire.h> + +#define M_PI_M2f (float)(M_PI+M_PI) + +#define DEFAULT_RATE 44100 +#define DEFAULT_CHANNELS 2 +#define DEFAULT_VOLUME 0.7f + +#define BUFFER_SIZE (16*1024) + +struct data { + struct pw_main_loop *main_loop; + struct pw_loop *loop; + struct pw_stream *stream; + + float accumulator; + + struct spa_source *refill_event; + + struct spa_ringbuffer ring; + float buffer[BUFFER_SIZE * DEFAULT_CHANNELS]; +}; + +static void fill_f32(struct data *d, uint32_t offset, int n_frames) +{ + float val; + int i, c; + + for (i = 0; i < n_frames; i++) { + d->accumulator += M_PI_M2f * 440 / DEFAULT_RATE; + if (d->accumulator >= M_PI_M2f) + d->accumulator -= M_PI_M2f; + + val = sinf(d->accumulator) * DEFAULT_VOLUME; + for (c = 0; c < DEFAULT_CHANNELS; c++) + d->buffer[((offset + i) % BUFFER_SIZE) * DEFAULT_CHANNELS + c] = val; + } +} + +/* this is called from the main-thread when we need to fill up the ringbuffer + * with more data */ +static void do_refill(void *userdata, uint64_t count) +{ + struct data *data = userdata; + int32_t filled; + uint32_t index, avail; + + filled = spa_ringbuffer_get_write_index(&data->ring, &index); + /* we xrun, this can not happen because we never read more + * than what there is in the ringbuffer and we never write more than + * what is left */ + spa_assert(filled >= 0); + spa_assert(filled <= BUFFER_SIZE); + + /* this is how much samples we can write */ + avail = BUFFER_SIZE - filled; + + /* write new samples to the ringbuffer from the given index */ + fill_f32(data, index, avail); + + /* and advance the ringbuffer */ + spa_ringbuffer_write_update(&data->ring, index + avail); +} + +/* our data processing function is in general: + * + * struct pw_buffer *b; + * b = pw_stream_dequeue_buffer(stream); + * + * .. generate stuff in the buffer ... + * In this case we read samples from a ringbuffer. The ringbuffer is + * filled up by another thread. + * + * pw_stream_queue_buffer(stream, b); + */ +static void on_process(void *userdata) +{ + struct data *data = userdata; + struct pw_buffer *b; + struct spa_buffer *buf; + uint8_t *p; + uint32_t index, to_read, to_silence; + int32_t avail, n_frames, stride; + + if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { + pw_log_warn("out of buffers: %m"); + return; + } + + buf = b->buffer; + if ((p = buf->datas[0].data) == NULL) + return; + + /* the amount of space in the ringbuffer and the read index */ + avail = spa_ringbuffer_get_read_index(&data->ring, &index); + + stride = sizeof(float) * DEFAULT_CHANNELS; + n_frames = buf->datas[0].maxsize / stride; + if (b->requested) + n_frames = SPA_MIN((int32_t)b->requested, n_frames); + + /* we can read if there is something available */ + to_read = avail > 0 ? SPA_MIN(avail, n_frames) : 0; + /* and fill the remainder with silence */ + to_silence = n_frames - to_read; + + if (to_read > 0) { + /* read data into the buffer */ + spa_ringbuffer_read_data(&data->ring, + data->buffer, BUFFER_SIZE * stride, + (index % BUFFER_SIZE) * stride, + p, to_read * stride); + /* update the read pointer */ + spa_ringbuffer_read_update(&data->ring, index + to_read); + } + if (to_silence > 0) + /* set the rest of the buffer to silence */ + memset(SPA_PTROFF(p, to_read * stride, void), 0, to_silence * stride); + + buf->datas[0].chunk->offset = 0; + buf->datas[0].chunk->stride = stride; + buf->datas[0].chunk->size = n_frames * stride; + + pw_stream_queue_buffer(data->stream, b); + + /* signal the main thread to fill the ringbuffer, we can only do this, for + * example when the available ringbuffer space falls below a certain + * level. */ + pw_loop_signal_event(data->loop, data->refill_event); +} + +static const struct pw_stream_events stream_events = { + PW_VERSION_STREAM_EVENTS, + .process = on_process, +}; + +static void do_quit(void *userdata, int signal_number) +{ + struct data *data = userdata; + pw_main_loop_quit(data->main_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); + + data.main_loop = pw_main_loop_new(NULL); + data.loop = pw_main_loop_get_loop(data.main_loop); + + pw_loop_add_signal(data.loop, SIGINT, do_quit, &data); + pw_loop_add_signal(data.loop, SIGTERM, do_quit, &data); + + /* we're going to refill a ringbuffer from the main loop. Make an + * event for this. */ + spa_ringbuffer_init(&data.ring); + data.refill_event = pw_loop_add_event(data.loop, do_refill, &data); + /* prefill the ringbuffer */ + do_refill(&data, 0); + + 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( + data.loop, + "audio-src-ring", + 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). */ + params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, + &SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .channels = DEFAULT_CHANNELS, + .rate = DEFAULT_RATE )); + + /* Now connect this stream. We ask that our process function is + * called in a realtime thread. */ + pw_stream_connect(data.stream, + PW_DIRECTION_OUTPUT, + 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.main_loop); + + pw_stream_destroy(data.stream); + pw_loop_destroy_source(data.loop, data.refill_event); + pw_main_loop_destroy(data.main_loop); + pw_deinit(); + + return 0; +} diff --git a/src/examples/audio-src-ring2.c b/src/examples/audio-src-ring2.c new file mode 100644 index 00000000..19f8eb4d --- /dev/null +++ b/src/examples/audio-src-ring2.c @@ -0,0 +1,269 @@ +/* PipeWire */ +/* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans */ +/* SPDX-License-Identifier: MIT */ + +/* + [title] + Audio source using \ref pw_stream "pw_stream" and ringbuffer. + + This one uses a thread-loop and does a blocking push into a + ringbuffer. + [title] + */ + +#include <stdio.h> +#include <errno.h> +#include <math.h> +#include <signal.h> + +#include <spa/param/audio/format-utils.h> +#include <spa/utils/ringbuffer.h> + +#include <pipewire/pipewire.h> + +#define M_PI_M2f (float)(M_PI+M_PI) + +#define DEFAULT_RATE 44100 +#define DEFAULT_CHANNELS 2 +#define DEFAULT_VOLUME 0.7f + +#define BUFFER_SIZE (16*1024) + +#define MIN_SIZE 256 +#define MAX_SIZE BUFFER_SIZE + +static float samples[BUFFER_SIZE * DEFAULT_CHANNELS]; + +struct data { + struct pw_thread_loop *thread_loop; + struct pw_loop *loop; + struct pw_stream *stream; + int eventfd; + bool running; + + float accumulator; + + struct spa_ringbuffer ring; + float buffer[BUFFER_SIZE * DEFAULT_CHANNELS]; +}; + +static void fill_f32(struct data *d, float *samples, int n_frames) +{ + float val; + int i, c; + + for (i = 0; i < n_frames; i++) { + d->accumulator += M_PI_M2f * 440 / DEFAULT_RATE; + if (d->accumulator >= M_PI_M2f) + d->accumulator -= M_PI_M2f; + + val = sinf(d->accumulator) * DEFAULT_VOLUME; + for (c = 0; c < DEFAULT_CHANNELS; c++) + samples[i * DEFAULT_CHANNELS + c] = val; + } +} + +/* this can be called from any thread with a block of samples to write into + * the ringbuffer. It will block until all data has been written */ +static void push_samples(void *userdata, float *samples, uint32_t n_samples) +{ + struct data *data = userdata; + int32_t filled; + uint32_t index, avail, stride = sizeof(float) * DEFAULT_CHANNELS; + uint64_t count; + float *s = samples; + + while (n_samples > 0) { + while (true) { + filled = spa_ringbuffer_get_write_index(&data->ring, &index); + /* we xrun, this can not happen because we never read more + * than what there is in the ringbuffer and we never write more than + * what is left */ + spa_assert(filled >= 0); + spa_assert(filled <= BUFFER_SIZE); + + /* this is how much samples we can write */ + avail = BUFFER_SIZE - filled; + if (avail > 0) + break; + + /* no space.. block and wait for free space */ + spa_system_eventfd_read(data->loop->system, data->eventfd, &count); + } + if (avail > n_samples) + avail = n_samples; + + spa_ringbuffer_write_data(&data->ring, + data->buffer, BUFFER_SIZE * stride, + (index % BUFFER_SIZE) * stride, + s, avail * stride); + + s += avail * DEFAULT_CHANNELS; + n_samples -= avail; + + /* and advance the ringbuffer */ + spa_ringbuffer_write_update(&data->ring, index + avail); + } + +} + +/* our data processing function is in general: + * + * struct pw_buffer *b; + * b = pw_stream_dequeue_buffer(stream); + * + * .. generate stuff in the buffer ... + * In this case we read samples from a ringbuffer. The ringbuffer is + * filled up by another thread. + * + * pw_stream_queue_buffer(stream, b); + */ +static void on_process(void *userdata) +{ + struct data *data = userdata; + struct pw_buffer *b; + struct spa_buffer *buf; + uint8_t *p; + uint32_t index, to_read, to_silence; + int32_t avail, n_frames, stride; + + if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) { + pw_log_warn("out of buffers: %m"); + return; + } + + buf = b->buffer; + if ((p = buf->datas[0].data) == NULL) + return; + + /* the amount of space in the ringbuffer and the read index */ + avail = spa_ringbuffer_get_read_index(&data->ring, &index); + + stride = sizeof(float) * DEFAULT_CHANNELS; + n_frames = buf->datas[0].maxsize / stride; + if (b->requested) + n_frames = SPA_MIN((int32_t)b->requested, n_frames); + + /* we can read if there is something available */ + to_read = avail > 0 ? SPA_MIN(avail, n_frames) : 0; + /* and fill the remainder with silence */ + to_silence = n_frames - to_read; + + if (to_read > 0) { + /* read data into the buffer */ + spa_ringbuffer_read_data(&data->ring, + data->buffer, BUFFER_SIZE * stride, + (index % BUFFER_SIZE) * stride, + p, to_read * stride); + /* update the read pointer */ + spa_ringbuffer_read_update(&data->ring, index + to_read); + } + if (to_silence > 0) + /* set the rest of the buffer to silence */ + memset(SPA_PTROFF(p, to_read * stride, void), 0, to_silence * stride); + + buf->datas[0].chunk->offset = 0; + buf->datas[0].chunk->stride = stride; + buf->datas[0].chunk->size = n_frames * stride; + + pw_stream_queue_buffer(data->stream, b); + + /* signal the main thread to fill the ringbuffer, we can only do this, for + * example when the available ringbuffer space falls below a certain + * level. */ + spa_system_eventfd_write(data->loop->system, data->eventfd, 1); +} + +static const struct pw_stream_events stream_events = { + PW_VERSION_STREAM_EVENTS, + .process = on_process, +}; + +static void do_quit(void *userdata, int signal_number) +{ + struct data *data = userdata; + data->running = false; +} + +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); + + data.thread_loop = pw_thread_loop_new("audio-src", NULL); + data.loop = pw_thread_loop_get_loop(data.thread_loop); + data.running = true; + + pw_thread_loop_lock(data.thread_loop); + pw_loop_add_signal(data.loop, SIGINT, do_quit, &data); + pw_loop_add_signal(data.loop, SIGTERM, do_quit, &data); + + spa_ringbuffer_init(&data.ring); + if ((data.eventfd = spa_system_eventfd_create(data.loop->system, SPA_FD_CLOEXEC)) < 0) + return data.eventfd; + + pw_thread_loop_start(data.thread_loop); + + 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( + data.loop, + "audio-src-ring", + 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). */ + params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, + &SPA_AUDIO_INFO_RAW_INIT( + .format = SPA_AUDIO_FORMAT_F32, + .channels = DEFAULT_CHANNELS, + .rate = DEFAULT_RATE )); + + /* Now connect this stream. We ask that our process function is + * called in a realtime thread. */ + pw_stream_connect(data.stream, + PW_DIRECTION_OUTPUT, + PW_ID_ANY, + PW_STREAM_FLAG_AUTOCONNECT | + PW_STREAM_FLAG_MAP_BUFFERS | + PW_STREAM_FLAG_RT_PROCESS, + params, 1); + + /* prefill the ringbuffer */ + fill_f32(&data, samples, BUFFER_SIZE); + push_samples(&data, samples, BUFFER_SIZE); + + srand(time(NULL)); + + pw_thread_loop_start(data.thread_loop); + pw_thread_loop_unlock(data.thread_loop); + + while (data.running) { + uint32_t size = rand() % ((MAX_SIZE - MIN_SIZE + 1) + MIN_SIZE); + /* make new random sized block of samples and push */ + fill_f32(&data, samples, size); + push_samples(&data, samples, size); + } + + pw_thread_loop_lock(data.thread_loop); + pw_stream_destroy(data.stream); + pw_thread_loop_unlock(data.thread_loop); + pw_thread_loop_destroy(data.thread_loop); + close(data.eventfd); + pw_deinit(); + + return 0; +} diff --git a/src/examples/gmain.c b/src/examples/gmain.c new file mode 100644 index 00000000..6a13b03b --- /dev/null +++ b/src/examples/gmain.c @@ -0,0 +1,101 @@ + +#include <glib.h> + +#include <pipewire/pipewire.h> +#include <spa/utils/result.h> + +typedef struct _PipeWireSource +{ + GSource base; + + struct pw_loop *loop; +} PipeWireSource; + +static gboolean +pipewire_loop_source_dispatch (GSource *source, + GSourceFunc callback, + gpointer user_data) +{ + PipeWireSource *s = (PipeWireSource *) source; + int result; + + result = pw_loop_iterate (s->loop, 0); + if (result < 0) + g_warning ("pipewire_loop_iterate failed: %s", spa_strerror (result)); + + return TRUE; +} + +static GSourceFuncs pipewire_source_funcs = +{ + .dispatch = pipewire_loop_source_dispatch, +}; + +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); +} + +static const struct pw_registry_events registry_events = { + PW_VERSION_REGISTRY_EVENTS, + .global = registry_event_global, +}; + +int main(int argc, char *argv[]) +{ + GMainLoop *main_loop; + PipeWireSource *source; + struct pw_loop *loop; + struct pw_context *context; + struct pw_core *core; + struct pw_registry *registry; + struct spa_hook registry_listener; + + main_loop = g_main_loop_new (NULL, FALSE); + + pw_init(&argc, &argv); + + loop = pw_loop_new(NULL /* properties */); + /* wrap */ + source = (PipeWireSource *) g_source_new (&pipewire_source_funcs, + sizeof (PipeWireSource)); + source->loop = loop; + g_source_add_unix_fd (&source->base, + pw_loop_get_fd (loop), + G_IO_IN | G_IO_ERR); + g_source_attach (&source->base, NULL); + g_source_unref (&source->base); + + context = pw_context_new(loop, + 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 */); + + spa_zero(registry_listener); + pw_registry_add_listener(registry, ®istry_listener, + ®istry_events, NULL); + + /* enter and leave must be called from the same thread that runs + * the mainloop */ + pw_loop_enter(loop); + g_main_loop_run(main_loop); + pw_loop_leave(loop); + + pw_proxy_destroy((struct pw_proxy*)registry); + pw_core_disconnect(core); + pw_context_destroy(context); + pw_loop_destroy(loop); + + g_main_loop_unref(main_loop); + + return 0; +} +/* [code] */ diff --git a/src/examples/internal.c b/src/examples/internal.c index aced5283..67d39b5d 100644 --- a/src/examples/internal.c +++ b/src/examples/internal.c @@ -40,6 +40,7 @@ static void do_quit(void *userdata, int signal_number) int main(int argc, char *argv[]) { struct data data = { 0, }; + struct pw_loop *loop; struct pw_properties *props; const char *dev = "hw:0"; @@ -47,16 +48,15 @@ int main(int argc, char *argv[]) data.loop = pw_main_loop_new(NULL); + loop = pw_main_loop_get_loop(data.loop); + if (argc > 1) dev = argv[1]; - 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); + pw_loop_add_signal(loop, SIGINT, do_quit, &data); + pw_loop_add_signal(loop, SIGTERM, do_quit, &data); - data.context = pw_context_new(pw_main_loop_get_loop(data.loop), - pw_properties_new( - PW_KEY_CONFIG_NAME, "client-rt.conf", - NULL), 0); + data.context = pw_context_new(loop, NULL, 0); pw_context_load_module(data.context, "libpipewire-module-spa-node-factory", NULL, NULL); pw_context_load_module(data.context, "libpipewire-module-link-factory", NULL, NULL); @@ -72,7 +72,7 @@ int main(int argc, char *argv[]) SPA_KEY_LIBRARY_NAME, "audiotestsrc/libspa-audiotestsrc", SPA_KEY_FACTORY_NAME, "audiotestsrc", PW_KEY_NODE_NAME, "test_source", - "Spa:Pod:Object:Param:Props:live", "false", + "node.param.Props", "{ live = false }", NULL); data.source = pw_core_create_object(data.core, "spa-node-factory", @@ -94,13 +94,15 @@ int main(int argc, char *argv[]) PW_VERSION_NODE, &props->dict, 0); + pw_loop_enter(loop); while (true) { if (pw_proxy_get_bound_id(data.source) != SPA_ID_INVALID && pw_proxy_get_bound_id(data.sink) != SPA_ID_INVALID) break; - pw_loop_iterate(pw_main_loop_get_loop(data.loop), -1); + pw_loop_iterate(loop, -1); } + pw_loop_leave(loop); pw_properties_clear(props); pw_properties_setf(props, diff --git a/src/examples/local-v4l2.c b/src/examples/local-v4l2.c index b8ce37fa..2093a9b4 100644 --- a/src/examples/local-v4l2.c +++ b/src/examples/local-v4l2.c @@ -33,7 +33,8 @@ struct data { SDL_Window *window; SDL_Texture *texture; - struct pw_main_loop *loop; + struct pw_main_loop *main_loop; + struct pw_loop *loop; struct pw_context *context; struct pw_core *core; @@ -61,7 +62,7 @@ static void handle_events(struct data *data) while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: - pw_main_loop_quit(data->loop); + pw_main_loop_quit(data->main_loop); break; } } @@ -309,7 +310,7 @@ static int impl_node_process(void *object) struct data *d = object; int res; - if ((res = pw_loop_invoke(pw_main_loop_get_loop(d->loop), do_render, + if ((res = pw_loop_invoke(d->loop, do_render, SPA_ID_INVALID, NULL, 0, true, d)) < 0) return res; @@ -372,14 +373,16 @@ static int make_nodes(struct data *data) &props->dict, 0); + pw_loop_enter(data->loop); while (true) { if (pw_proxy_get_bound_id(data->out) != SPA_ID_INVALID && pw_proxy_get_bound_id(data->in) != SPA_ID_INVALID) break; - pw_loop_iterate(pw_main_loop_get_loop(data->loop), -1); + pw_loop_iterate(data->loop, -1); } + pw_loop_leave(data->loop); pw_properties_clear(props); @@ -405,9 +408,10 @@ int main(int argc, char *argv[]) pw_init(&argc, &argv); - data.loop = pw_main_loop_new(NULL); + data.main_loop = pw_main_loop_new(NULL); + data.loop = pw_main_loop_get_loop(data.main_loop); data.context = pw_context_new( - pw_main_loop_get_loop(data.loop), + data.loop, pw_properties_new( PW_KEY_CORE_DAEMON, "false", NULL), 0); @@ -436,13 +440,13 @@ int main(int argc, char *argv[]) make_nodes(&data); - pw_main_loop_run(data.loop); + pw_main_loop_run(data.main_loop); pw_proxy_destroy(data.link); pw_proxy_destroy(data.in); pw_proxy_destroy(data.out); pw_context_destroy(data.context); - pw_main_loop_destroy(data.loop); + pw_main_loop_destroy(data.main_loop); pw_deinit(); return 0; diff --git a/src/examples/meson.build b/src/examples/meson.build index 889116c8..7d45a34f 100644 --- a/src/examples/meson.build +++ b/src/examples/meson.build @@ -1,6 +1,8 @@ # Examples, in order from simple to complicated examples = [ 'audio-src', + 'audio-src-ring', + 'audio-src-ring2', 'audio-dsp-src', 'audio-dsp-filter', 'audio-capture', @@ -22,6 +24,7 @@ examples = [ 'export-spa-device', 'bluez-session', 'local-v4l2', + 'gmain', ] if not get_option('examples').allowed() @@ -37,6 +40,7 @@ examples_extra_deps = { 'video-dsp-play': [sdl_dep], 'local-v4l2': [sdl_dep], 'export-sink': [sdl_dep], + 'gmain': [glib2_dep], } foreach c : examples diff --git a/src/examples/midi-src.c b/src/examples/midi-src.c index ee5f3261..edcaa0f0 100644 --- a/src/examples/midi-src.c +++ b/src/examples/midi-src.c @@ -103,7 +103,7 @@ static void on_process(void *userdata, struct spa_io_position *position) while (sample_offset < position->clock.duration) { if (cycle % 2 == 0) { /* MIDI note on, channel 0, middle C, max velocity */ - uint8_t buf[] = { 0x90, 0x3c, 0x7f }; + uint32_t event = 0x20903c7f; /* The time position of the message in the graph cycle * is given as offset from the cycle start, in @@ -111,18 +111,18 @@ static void on_process(void *userdata, struct spa_io_position *position) * samples, and the sample offset should satisfy * 0 <= sample_offset < position->clock.duration. */ - spa_pod_builder_control(&builder, sample_offset, SPA_CONTROL_Midi); + spa_pod_builder_control(&builder, sample_offset, SPA_CONTROL_UMP); /* Raw MIDI data for the message */ - spa_pod_builder_bytes(&builder, buf, sizeof(buf)); + spa_pod_builder_bytes(&builder, &event, sizeof(event)); pw_log_info("note on at %"PRIu64, sample_position + sample_offset); } else { /* MIDI note off, channel 0, middle C, max velocity */ - uint8_t buf[] = { 0x80, 0x3c, 0x7f }; + uint32_t event = 0x20803c7f; - spa_pod_builder_control(&builder, sample_offset, SPA_CONTROL_Midi); - spa_pod_builder_bytes(&builder, buf, sizeof(buf)); + spa_pod_builder_control(&builder, sample_offset, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&builder, &event, sizeof(event)); pw_log_info("note off at %"PRIu64, sample_position + sample_offset); } @@ -213,7 +213,7 @@ int main(int argc, char *argv[]) PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( - PW_KEY_FORMAT_DSP, "8 bit raw midi", + PW_KEY_FORMAT_DSP, "32 bit raw UMP", PW_KEY_PORT_NAME, "output", NULL), NULL, 0); diff --git a/src/examples/video-src.c b/src/examples/video-src.c index 770ea596..f439f168 100644 --- a/src/examples/video-src.c +++ b/src/examples/video-src.c @@ -326,11 +326,11 @@ int main(int argc, char *argv[]) { struct spa_pod_frame f; - struct spa_dict_item items[1]; /* send a tag, output tags travel downstream */ spa_tag_build_start(&b, &f, SPA_PARAM_Tag, SPA_DIRECTION_OUTPUT); - items[0] = SPA_DICT_ITEM_INIT("my-tag-key", "my-special-tag-value"); - spa_tag_build_add_dict(&b, &SPA_DICT_INIT(items, 1)); + spa_tag_build_add_dict(&b, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM("my-tag-key", "my-special-tag-value"))); params[1] = spa_tag_build_end(&b, &f); } diff --git a/src/gst/gstpipewireclock.c b/src/gst/gstpipewireclock.c index 0502a0a6..701bb6ff 100644 --- a/src/gst/gstpipewireclock.c +++ b/src/gst/gstpipewireclock.c @@ -38,7 +38,7 @@ gst_pipewire_clock_get_internal_time (GstClock * clock) return pclock->last_time; now = pw_stream_get_nsec(s->pwstream); -#if 0 +#if 1 struct pw_time t; if (s->pwstream == NULL || pw_stream_get_time_n (s->pwstream, &t, sizeof(t)) < 0 || diff --git a/src/gst/gstpipewirecore.c b/src/gst/gstpipewirecore.c index 6c4fb4b8..dcb45ef0 100644 --- a/src/gst/gstpipewirecore.c +++ b/src/gst/gstpipewirecore.c @@ -3,6 +3,7 @@ /* SPDX-License-Identifier: MIT */ #include "config.h" +#include <errno.h> #include <unistd.h> #include <fcntl.h> @@ -105,7 +106,7 @@ mainloop_failed: } connection_failed: { - GST_ERROR ("error connect: %m"); + GST_ERROR ("error connect: %s", strerror (errno)); pw_thread_loop_unlock (core->loop); pw_context_destroy (core->context); pw_thread_loop_destroy (core->loop); diff --git a/src/gst/gstpipewiredeviceprovider.c b/src/gst/gstpipewiredeviceprovider.c index 363ca3e2..c9d0d7ad 100644 --- a/src/gst/gstpipewiredeviceprovider.c +++ b/src/gst/gstpipewiredeviceprovider.c @@ -6,6 +6,7 @@ #include <string.h> +#include <spa/utils/json.h> #include <spa/utils/result.h> #include <spa/utils/string.h> @@ -203,6 +204,28 @@ static struct node_data *find_node_data(struct spa_list *nodes, uint32_t id) return NULL; } +static GstPipeWireDevice * +gst_pipewire_device_new (int fd, uint32_t id, uint64_t serial, + GstPipeWireDeviceType type, const gchar * element, int priority, + const gchar * klass, const gchar * display_name, const GstCaps * caps, + const GstStructure * props) +{ + GstPipeWireDevice *gstdev; + + gstdev = + g_object_new (GST_TYPE_PIPEWIRE_DEVICE, "display-name", display_name, + "caps", caps, "device-class", klass, "id", id, "serial", serial, "fd", fd, + "properties", props, NULL); + + gstdev->id = id; + gstdev->serial = serial; + gstdev->type = type; + gstdev->element = element; + gstdev->priority = priority; + + return gstdev; +} + static GstDevice * new_node (GstPipeWireDeviceProvider *self, struct node_data *data) { @@ -224,16 +247,27 @@ new_node (GstPipeWireDeviceProvider *self, struct node_data *data) return NULL; } - props = gst_structure_new_empty ("pipewire-proplist"); + props = gst_structure_new ("pipewire-proplist", "is-default", G_TYPE_BOOLEAN, FALSE, NULL); if (info->props) { const struct spa_dict_item *item; const char *str; - spa_dict_for_each (item, info->props) - gst_structure_set (props, item->key, G_TYPE_STRING, item->value, NULL); + klass = spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS); + name = spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION); - klass = spa_dict_lookup (info->props, PW_KEY_MEDIA_CLASS); - name = spa_dict_lookup (info->props, PW_KEY_NODE_DESCRIPTION); + spa_dict_for_each (item, info->props) { + gst_structure_set (props, item->key, G_TYPE_STRING, item->value, NULL); + if (spa_streq(item->key, "node.name") && klass) { + if (spa_streq(klass, "Audio/Source") && spa_streq(item->value, self->default_audio_source_name)) + gst_structure_set(props, "is-default", G_TYPE_BOOLEAN, TRUE, NULL); + else if (spa_streq(klass, "Audio/Sink") && + spa_streq(item->value, self->default_audio_sink_name)) + gst_structure_set(props, "is-default", G_TYPE_BOOLEAN, TRUE, NULL); + else if (spa_streq(klass, "Video/Source") && + spa_streq(item->value, self->default_video_source_name)) + gst_structure_set(props, "is-default", G_TYPE_BOOLEAN, TRUE, NULL); + } + } if ((str = spa_dict_lookup(info->props, PW_KEY_PRIORITY_SESSION))) priority = atoi(str); @@ -243,16 +277,8 @@ new_node (GstPipeWireDeviceProvider *self, struct node_data *data) if (name == NULL) name = "unknown"; - gstdev = g_object_new (GST_TYPE_PIPEWIRE_DEVICE, - "display-name", name, "caps", data->caps, "device-class", klass, - "id", data->id, "serial", data->serial, "fd", self->fd, - "properties", props, NULL); - - gstdev->id = data->id; - gstdev->serial = data->serial; - gstdev->type = type; - gstdev->element = element; - gstdev->priority = priority; + gstdev = gst_pipewire_device_new (self->fd, data->id, data->serial, type, + element, priority, klass, name, data->caps, props); if (props) gst_structure_free (props); @@ -498,6 +524,133 @@ static const struct pw_proxy_events proxy_port_events = { .destroy = destroy_port, }; +static gboolean +is_default_device_name (GstPipeWireDeviceProvider * self, + const gchar * name, const gchar * klass, GstPipeWireDeviceType type) +{ + gboolean ret = FALSE; + + GST_OBJECT_LOCK (self); + switch (type) { + case GST_PIPEWIRE_DEVICE_TYPE_SINK: + if (g_str_has_prefix (klass, "Audio")) + ret = !g_strcmp0 (name, self->default_audio_sink_name); + break; + case GST_PIPEWIRE_DEVICE_TYPE_SOURCE: + if (g_str_has_prefix (klass, "Audio")) + ret = !g_strcmp0 (name, self->default_audio_source_name); + else if (g_str_has_prefix (klass, "Video")) + ret = !g_strcmp0 (name, self->default_video_source_name); + break; + default: + GST_ERROR_OBJECT (self, "Unknown pipewire device type!"); + break; + } + GST_OBJECT_UNLOCK (self); + + return ret; +} + +static void +sync_default_devices (GstPipeWireDeviceProvider * self) +{ + GList *tmp, *devices = NULL; + + for (tmp = GST_DEVICE_PROVIDER_CAST (self)->devices; tmp; tmp = tmp->next) + devices = g_list_prepend (devices, gst_object_ref (tmp->data)); + + for (tmp = devices; tmp; tmp = tmp->next) { + GstPipeWireDevice *dev = tmp->data; + GstStructure *props = gst_device_get_properties (GST_DEVICE_CAST (dev)); + gboolean was_default = FALSE, is_default = FALSE; + const gchar *name; + gchar *klass = gst_device_get_device_class (GST_DEVICE_CAST (dev)); + + g_assert (props); + gst_structure_get_boolean (props, "is-default", &was_default); + name = gst_structure_get_string (props, "node.name"); + + switch (dev->type) { + case GST_PIPEWIRE_DEVICE_TYPE_SINK: + is_default = + is_default_device_name (self, name, klass, dev->type); + break; + case GST_PIPEWIRE_DEVICE_TYPE_SOURCE: + is_default = + is_default_device_name (self, name, klass, dev->type); + break; + case GST_PIPEWIRE_DEVICE_TYPE_UNKNOWN: + break; + } + + if (was_default != is_default) { + GstPipeWireDevice *updated_device; + gchar *display_name = gst_device_get_display_name (GST_DEVICE_CAST (dev)); + GstCaps *caps = gst_device_get_caps (GST_DEVICE_CAST (dev)); + + gst_structure_set (props, "is-default", G_TYPE_BOOLEAN, is_default, NULL); + updated_device = + gst_pipewire_device_new (self->fd, dev->id, dev->serial, dev->type, + dev->element, dev->priority, klass, display_name, caps, props); + + gst_device_provider_device_changed (GST_DEVICE_PROVIDER_CAST (self), + GST_DEVICE_CAST (updated_device), GST_DEVICE_CAST (dev)); + + g_free (display_name); + gst_caps_unref (caps); + } + gst_structure_free (props); + g_free (klass); + } + g_list_free_full (devices, gst_object_unref); +} + +static int metadata_property(void *data, uint32_t id, const char *key, + const char *type, const char *value) { + GstPipeWireDeviceProvider *self = data; + char name[1024]; + + if (value == NULL) + return 0; + + if (spa_streq(key, "default.audio.source")) { + if (!spa_streq(type, "Spa:String:JSON")) + return 0; + + g_free(self->default_audio_source_name); + if (spa_json_str_object_find(value, strlen(value), "name", name, sizeof(name)) >= 0) + self->default_audio_source_name = g_strdup(name); + goto sync_devices; + } + if (spa_streq(key, "default.audio.sink")) { + if (!spa_streq(type, "Spa:String:JSON")) + return 0; + + g_free(self->default_audio_sink_name); + if (spa_json_str_object_find(value, strlen(value), "name", name, sizeof(name)) >= 0) + self->default_audio_sink_name = g_strdup(name); + goto sync_devices; + } + if (spa_streq(key, "default.video.source")) { + if (!spa_streq(type, "Spa:String:JSON")) + return 0; + + g_free(self->default_video_source_name); + if (spa_json_str_object_find(value, strlen(value), "name", name, sizeof(name)) >= 0) + self->default_video_source_name = g_strdup(name); + goto sync_devices; + } + + return 0; + + sync_devices: + sync_default_devices (self); + return 0; +} + +static const struct pw_metadata_events metadata_events = { + PW_VERSION_METADATA_EVENTS, .property = metadata_property}; + static void registry_event_global(void *data, uint32_t id, uint32_t permissions, const char *type, uint32_t version, const struct spa_dict *props) @@ -564,6 +717,20 @@ static void registry_event_global(void *data, uint32_t id, uint32_t permissions, pw_port_add_listener(port, &pd->port_listener, &port_events, pd); pw_proxy_add_listener((struct pw_proxy*)port, &pd->proxy_listener, &proxy_port_events, pd); resync(self); + } else if (spa_streq(type, PW_TYPE_INTERFACE_Metadata) && props) { + const char *name; + + name = spa_dict_lookup(props, PW_KEY_METADATA_NAME); + if (name == NULL) + return; + + if (!spa_streq(name, "default")) + return; + + self->metadata = + pw_registry_bind(self->registry, id, type, PW_VERSION_METADATA, 0); + pw_metadata_add_listener(self->metadata, &self->metadata_listener, + &metadata_events, self); } return; @@ -689,6 +856,12 @@ gst_pipewire_device_provider_stop (GstDeviceProvider * provider) } GST_DEBUG_OBJECT (self, "stopping provider"); + if (self->metadata) { + spa_hook_remove(&self->metadata_listener); + pw_proxy_destroy((struct pw_proxy *)self->metadata); + self->metadata = NULL; + } + g_clear_pointer ((struct pw_proxy**)&self->registry, pw_proxy_destroy); if (self->core != NULL) { pw_thread_loop_unlock (self->core->loop); @@ -751,6 +924,9 @@ gst_pipewire_device_provider_finalize (GObject * object) GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object); g_free (self->client_name); + g_free (self->default_audio_source_name); + g_free (self->default_audio_sink_name); + g_free (self->default_video_source_name); G_OBJECT_CLASS (gst_pipewire_device_provider_parent_class)->finalize (object); } diff --git a/src/gst/gstpipewiredeviceprovider.h b/src/gst/gstpipewiredeviceprovider.h index f909cc49..82b2a8a1 100644 --- a/src/gst/gstpipewiredeviceprovider.h +++ b/src/gst/gstpipewiredeviceprovider.h @@ -9,8 +9,9 @@ #include <gst/gst.h> -#include <pipewire/pipewire.h> #include <gst/gstpipewirecore.h> +#include <pipewire/extensions/metadata.h> +#include <pipewire/pipewire.h> G_BEGIN_DECLS @@ -49,6 +50,14 @@ struct _GstPipeWireDeviceProvider { struct spa_hook core_listener; struct pw_registry *registry; struct spa_hook registry_listener; + + struct pw_metadata *metadata; + struct spa_hook metadata_listener; + + gchar *default_audio_source_name; + gchar *default_audio_sink_name; + gchar *default_video_source_name; + struct spa_list nodes; int seq; diff --git a/src/gst/gstpipewireformat.c b/src/gst/gstpipewireformat.c index 1116e8c6..24115325 100644 --- a/src/gst/gstpipewireformat.c +++ b/src/gst/gstpipewireformat.c @@ -131,6 +131,13 @@ static const uint32_t video_format_map[] = { SPA_VIDEO_FORMAT_Y444_12LE, }; +static const uint32_t interlace_mode_map[] = { + SPA_VIDEO_INTERLACE_MODE_PROGRESSIVE, + SPA_VIDEO_INTERLACE_MODE_INTERLEAVED, + SPA_VIDEO_INTERLACE_MODE_MIXED, + SPA_VIDEO_INTERLACE_MODE_FIELDS, +}; + #if __BYTE_ORDER == __BIG_ENDIAN #define _FORMAT_LE(fmt) SPA_AUDIO_FORMAT_ ## fmt ## _OE #define _FORMAT_BE(fmt) SPA_AUDIO_FORMAT_ ## fmt @@ -825,6 +832,14 @@ static char *video_id_to_dma_drm_fourcc(uint32_t id, uint64_t mod) } #endif +static const char *interlace_mode_id_to_string(uint32_t id) +{ + int idx; + if ((idx = find_index(interlace_mode_map, SPA_N_ELEMENTS(interlace_mode_map), id)) == -1) + return NULL; + return gst_video_interlace_mode_to_string(idx); +} + static const char *audio_id_to_string(uint32_t id) { int idx; @@ -1149,6 +1164,11 @@ gst_caps_from_format (const struct spa_pod *format) handle_id_prop (prop, "format", video_id_to_string, res); } } + if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_VIDEO_interlaceMode))) { + handle_id_prop (prop, "interlace-mode", interlace_mode_id_to_string, res); + } else { + gst_caps_set_simple(res, "interlace-mode", G_TYPE_STRING, "progressive", NULL); + } } else if (media_subtype == SPA_MEDIA_SUBTYPE_mjpg) { res = gst_caps_new_empty_simple ("image/jpeg"); diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c index 64982306..78869e16 100644 --- a/src/gst/gstpipewirepool.c +++ b/src/gst/gstpipewirepool.c @@ -16,6 +16,8 @@ #include "gstpipewirepool.h" #include <spa/debug/types.h> +#include <spa/utils/result.h> + GST_DEBUG_CATEGORY_STATIC (gst_pipewire_pool_debug_category); #define GST_CAT_DEFAULT gst_pipewire_pool_debug_category @@ -161,21 +163,31 @@ acquire_buffer (GstBufferPool * pool, GstBuffer ** buffer, if (G_UNLIKELY (GST_BUFFER_POOL_IS_FLUSHING (pool))) goto flushing; - if ((b = pw_stream_dequeue_buffer(s->pwstream))) + if ((b = pw_stream_dequeue_buffer(s->pwstream))) { + GST_LOG_OBJECT (pool, "dequeued buffer %p", b); break; + } - if (params && (params->flags & GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT)) - goto no_more_buffers; + if (params) { + if (params->flags & GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT) + goto no_more_buffers; + + if ((params->flags & GST_BUFFER_POOL_ACQUIRE_FLAG_LAST) && + p->paused) + goto paused; + } - GST_WARNING ("queue empty"); + GST_WARNING_OBJECT (pool, "failed to dequeue buffer: %s", strerror(errno)); g_cond_wait (&p->cond, GST_OBJECT_GET_LOCK (pool)); } data = b->user_data; + data->queued = FALSE; + *buffer = data->buf; GST_OBJECT_UNLOCK (pool); - GST_LOG_OBJECT (pool, "acquire buffer %p", *buffer); + GST_LOG_OBJECT (pool, "acquired gstbuffer %p", *buffer); return GST_FLOW_OK; @@ -184,6 +196,11 @@ flushing: GST_OBJECT_UNLOCK (pool); return GST_FLOW_FLUSHING; } +paused: + { + GST_OBJECT_UNLOCK (pool); + return GST_FLOW_CUSTOM_ERROR_1; + } no_more_buffers: { GST_LOG_OBJECT (pool, "no more buffers"); @@ -238,12 +255,22 @@ set_config (GstBufferPool * pool, GstStructure * config) return GST_BUFFER_POOL_CLASS (gst_pipewire_pool_parent_class)->set_config (pool, config); } + +void gst_pipewire_pool_set_paused (GstPipeWirePool *pool, gboolean paused) +{ + GST_DEBUG_OBJECT (pool, "pause: %u", paused); + GST_OBJECT_LOCK (pool); + pool->paused = paused; + g_cond_signal (&pool->cond); + GST_OBJECT_UNLOCK (pool); +} + static void flush_start (GstBufferPool * pool) { GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool); - GST_DEBUG ("flush start"); + GST_DEBUG_OBJECT (pool, "flush start"); GST_OBJECT_LOCK (pool); g_cond_signal (&p->cond); GST_OBJECT_UNLOCK (pool); @@ -253,6 +280,29 @@ static void release_buffer (GstBufferPool * pool, GstBuffer *buffer) { GST_LOG_OBJECT (pool, "release buffer %p", buffer); + + GstPipeWirePoolData *data = gst_pipewire_pool_get_data(buffer); + + GST_OBJECT_LOCK (pool); + + if (!data->queued && data->b != NULL) + { + GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool); + g_autoptr (GstPipeWireStream) s = g_weak_ref_get (&p->stream); + int res; + + pw_thread_loop_lock (s->core->loop); + + if ((res = pw_stream_return_buffer (s->pwstream, data->b)) < 0) { + GST_ERROR_OBJECT (pool,"can't return buffer %p; gstbuffer : %p, %s",data->b, buffer, spa_strerror(res)); + } else { + data->queued = TRUE; + GST_DEBUG_OBJECT (pool, "returned buffer %p; gstbuffer:%p", data->b, buffer); + } + + pw_thread_loop_unlock (s->core->loop); + } + GST_OBJECT_UNLOCK (pool); } static gboolean diff --git a/src/gst/gstpipewirepool.h b/src/gst/gstpipewirepool.h index b629f8a8..fb00a100 100644 --- a/src/gst/gstpipewirepool.h +++ b/src/gst/gstpipewirepool.h @@ -44,6 +44,13 @@ struct _GstPipeWirePool { GstAllocator *dmabuf_allocator; GCond cond; + gboolean paused; +}; + +enum GstPipeWirePoolMode { + USE_BUFFERPOOL_NO = 0, + USE_BUFFERPOOL_AUTO, + USE_BUFFERPOOL_YES }; GstPipeWirePool * gst_pipewire_pool_new (GstPipeWireStream *stream); @@ -59,6 +66,8 @@ gst_pipewire_pool_has_buffers (GstPipeWirePool *pool) GstPipeWirePoolData *gst_pipewire_pool_get_data (GstBuffer *buffer); +void gst_pipewire_pool_set_paused (GstPipeWirePool *pool, gboolean paused); + G_END_DECLS #endif /* __GST_PIPEWIRE_POOL_H__ */ diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c index b39a335d..aa9d94b9 100644 --- a/src/gst/gstpipewiresink.c +++ b/src/gst/gstpipewiresink.c @@ -26,6 +26,7 @@ #include <spa/pod/builder.h> #include <spa/utils/result.h> +#include <spa/utils/dll.h> #include <gst/video/video.h> @@ -36,6 +37,8 @@ GST_DEBUG_CATEGORY_STATIC (pipewire_sink_debug); #define GST_CAT_DEFAULT pipewire_sink_debug #define DEFAULT_PROP_MODE GST_PIPEWIRE_SINK_MODE_DEFAULT +#define DEFAULT_PROP_SLAVE_METHOD GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE +#define DEFAULT_PROP_USE_BUFFERPOOL USE_BUFFERPOOL_AUTO #define MIN_BUFFERS 8u @@ -48,7 +51,9 @@ enum PROP_CLIENT_PROPERTIES, PROP_STREAM_PROPERTIES, PROP_MODE, - PROP_FD + PROP_FD, + PROP_SLAVE_METHOD, + PROP_USE_BUFFERPOOL, }; GType @@ -71,6 +76,26 @@ gst_pipewire_sink_mode_get_type (void) return (GType) mode_type; } +GType +gst_pipewire_sink_slave_method_get_type (void) +{ + static gsize method_type = 0; + static const GEnumValue method[] = { + {GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE, "GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE", "none"}, + {GST_PIPEWIRE_SINK_SLAVE_METHOD_RESAMPLE, "GST_PIPEWIRE_SINK_SLAVE_METHOD_RESAMPLE", "resample"}, + {0, NULL, NULL}, + }; + + if (g_once_init_enter (&method_type)) { + GType tmp = + g_enum_register_static ("GstPipeWireSinkSlaveMethod", method); + g_once_init_leave (&method_type, tmp); + } + + return (GType) method_type; +} + + static GstStaticPadTemplate gst_pipewire_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", @@ -97,6 +122,8 @@ static GstCaps *gst_pipewire_sink_sink_fixate (GstBaseSink * bsink, static GstFlowReturn gst_pipewire_sink_render (GstBaseSink * psink, GstBuffer * buffer); +static gboolean gst_pipewire_sink_event (GstBaseSink *sink, GstEvent *event); + static GstClock * gst_pipewire_sink_provide_clock (GstElement * elem) { @@ -139,7 +166,9 @@ gst_pipewire_sink_propose_allocation (GstBaseSink * bsink, GstQuery * query) { GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (bsink); - gst_query_add_allocation_pool (query, GST_BUFFER_POOL_CAST (pwsink->stream->pool), 0, 0, 0); + if (pwsink->use_bufferpool != USE_BUFFERPOOL_NO) + gst_query_add_allocation_pool (query, GST_BUFFER_POOL_CAST (pwsink->stream->pool), 0, 0, 0); + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL); return TRUE; } @@ -224,6 +253,25 @@ gst_pipewire_sink_class_init (GstPipeWireSinkClass * klass) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, + PROP_SLAVE_METHOD, + g_param_spec_enum ("slave-method", + "Slave Method", + "Algorithm used to match the rate of the masterclock", + GST_TYPE_PIPEWIRE_SINK_SLAVE_METHOD, + DEFAULT_PROP_SLAVE_METHOD, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, + PROP_USE_BUFFERPOOL, + g_param_spec_boolean ("use-bufferpool", + "Use bufferpool", + "Use bufferpool (default: true for video, false for audio)", + DEFAULT_PROP_USE_BUFFERPOOL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + gstelement_class->provide_clock = gst_pipewire_sink_provide_clock; gstelement_class->change_state = gst_pipewire_sink_change_state; @@ -238,6 +286,7 @@ gst_pipewire_sink_class_init (GstPipeWireSinkClass * klass) gstbasesink_class->fixate = gst_pipewire_sink_sink_fixate; gstbasesink_class->propose_allocation = gst_pipewire_sink_propose_allocation; gstbasesink_class->render = gst_pipewire_sink_render; + gstbasesink_class->event = gst_pipewire_sink_event; GST_DEBUG_CATEGORY_INIT (pipewire_sink_debug, "pipewiresink", 0, "PipeWire Sink"); @@ -306,6 +355,8 @@ gst_pipewire_sink_init (GstPipeWireSink * sink) sink->stream = gst_pipewire_stream_new (GST_ELEMENT (sink)); sink->mode = DEFAULT_PROP_MODE; + sink->use_bufferpool = DEFAULT_PROP_USE_BUFFERPOOL; + sink->is_video = false; GST_OBJECT_FLAG_SET (sink, GST_ELEMENT_FLAG_PROVIDE_CLOCK); @@ -316,12 +367,14 @@ static GstCaps * gst_pipewire_sink_sink_fixate (GstBaseSink * bsink, GstCaps * caps) { GstStructure *structure; + GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK(bsink); caps = gst_caps_make_writable (caps); structure = gst_caps_get_structure (caps, 0); if (gst_structure_has_name (structure, "video/x-raw")) { + pwsink->is_video = true; gst_structure_fixate_field_nearest_int (structure, "width", 320); gst_structure_fixate_field_nearest_int (structure, "height", 240); gst_structure_fixate_field_nearest_fraction (structure, "framerate", 30, 1); @@ -407,6 +460,17 @@ gst_pipewire_sink_set_property (GObject * object, guint prop_id, pwsink->stream->fd = g_value_get_int (value); break; + case PROP_SLAVE_METHOD: + pwsink->slave_method = g_value_get_enum (value); + break; + + case PROP_USE_BUFFERPOOL: + if(g_value_get_boolean (value)) + pwsink->use_bufferpool = USE_BUFFERPOOL_YES; + else + pwsink->use_bufferpool = USE_BUFFERPOOL_NO; + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -448,12 +512,70 @@ gst_pipewire_sink_get_property (GObject * object, guint prop_id, g_value_set_int (value, pwsink->stream->fd); break; + case PROP_SLAVE_METHOD: + g_value_set_enum (value, pwsink->slave_method); + break; + + case PROP_USE_BUFFERPOOL: + g_value_set_boolean (value, !!pwsink->use_bufferpool); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } +static void rate_match_resample(GstPipeWireSink *pwsink) +{ + GstPipeWireStream *stream = pwsink->stream; + double err, corr; + struct pw_time ts; + guint64 queued, now, elapsed, target; + + if (!pwsink->rate_match) + return; + + pw_stream_get_time_n(stream->pwstream, &ts, sizeof(ts)); + now = pw_stream_get_nsec(stream->pwstream); + if (ts.now != 0) + elapsed = gst_util_uint64_scale_int (now - ts.now, ts.rate.denom, GST_SECOND * ts.rate.num); + else + elapsed = 0; + + queued = ts.queued - ts.size; + target = elapsed; + err = ((gint64)queued - ((gint64)target)); + + corr = spa_dll_update(&stream->dll, SPA_CLAMPD(err, -128.0, 128.0)); + + stream->err_wdw = (double)ts.rate.denom/ts.size; + + double avg = (stream->err_avg * stream->err_wdw + (err - stream->err_avg)) / (stream->err_wdw + 1.0); + stream->err_var = (stream->err_var * stream->err_wdw + + (err - stream->err_avg) * (err - avg)) / (stream->err_wdw + 1.0); + stream->err_avg = avg; + + if (stream->last_ts == 0 || stream->last_ts + SPA_NSEC_PER_SEC < now) { + double bw; + + stream->last_ts = now; + + if (stream->err_var == 0.0) + bw = 0.0; + else + bw = fabs(stream->err_avg) / sqrt(fabs(stream->err_var)); + + spa_dll_set_bw(&stream->dll, SPA_CLAMPD(bw, 0.001, SPA_DLL_BW_MAX), ts.size, ts.rate.denom); + + GST_INFO_OBJECT (pwsink, "q:%"PRIi64"/%"PRIi64" e:%"PRIu64" err:%+03f corr:%f %f %f %f", + ts.queued, ts.size, elapsed, err, corr, + stream->err_avg, stream->err_var, stream->dll.bw); + } + + pw_stream_set_rate (stream->pwstream, corr); +} + static void on_add_buffer (void *_data, struct pw_buffer *b) { @@ -481,14 +603,13 @@ static void do_send_buffer (GstPipeWireSink *pwsink, GstBuffer *buffer) { GstPipeWirePoolData *data; + GstPipeWireStream *stream = pwsink->stream; gboolean res; guint i; struct spa_buffer *b; data = gst_pipewire_pool_get_data(buffer); - GST_LOG_OBJECT (pwsink, "queue buffer %p, pw_buffer %p", buffer, data->b); - b = data->b->buffer; if (data->header) { @@ -508,12 +629,15 @@ do_send_buffer (GstPipeWireSink *pwsink, GstBuffer *buffer) data->crop->region.size.height = meta->width; } } + data->b->size = 0; for (i = 0; i < b->n_datas; i++) { struct spa_data *d = &b->datas[i]; GstMemory *mem = gst_buffer_peek_memory (buffer, i); d->chunk->offset = mem->offset; d->chunk->size = mem->size; - d->chunk->stride = pwsink->stream->pool->video_info.stride[i]; + d->chunk->stride = stream->pool->video_info.stride[i]; + + data->b->size += mem->size / 4; } GstVideoMeta *meta = gst_buffer_get_video_meta (buffer); @@ -532,8 +656,19 @@ do_send_buffer (GstPipeWireSink *pwsink, GstBuffer *buffer) } } - if ((res = pw_stream_queue_buffer (pwsink->stream->pwstream, data->b)) < 0) { - g_warning ("can't send buffer %s", spa_strerror(res)); + if ((res = pw_stream_queue_buffer (stream->pwstream, data->b)) < 0) { + GST_WARNING_OBJECT (pwsink, "can't send buffer %s", spa_strerror(res)); + } else { + data->queued = TRUE; + GST_LOG_OBJECT(pwsink, "queued pwbuffer: %p; gstbuffer %p ",data->b, buffer); + } + + switch (pwsink->slave_method) { + case GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE: + break; + case GST_PIPEWIRE_SINK_SLAVE_METHOD_RESAMPLE: + rate_match_resample(pwsink); + break; } } @@ -602,7 +737,6 @@ gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps) g_autoptr(GPtrArray) possible = NULL; enum pw_stream_state state; const char *error = NULL; - gboolean res = FALSE; GstStructure *config, *s; guint size; guint min_buffers; @@ -613,9 +747,21 @@ gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps) pwsink = GST_PIPEWIRE_SINK (bsink); s = gst_caps_get_structure (caps, 0); - rate = 0; - if (gst_structure_has_name (s, "audio/x-raw")) + if (gst_structure_has_name (s, "audio/x-raw")) { gst_structure_get_int (s, "rate", &rate); + pwsink->rate = rate; + pwsink->rate_match = true; + + /* Don't provide bufferpool for audio if not requested by the application/user */ + if (pwsink->use_bufferpool != USE_BUFFERPOOL_YES) + pwsink->use_bufferpool = USE_BUFFERPOOL_NO; + } else { + pwsink->rate = rate = 0; + pwsink->rate_match = false; + pwsink->is_video = true; + } + + spa_dll_set_bw(&pwsink->stream->dll, SPA_DLL_BW_MIN, 4096, rate); possible = gst_caps_to_format_all (caps); @@ -633,6 +779,7 @@ gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps) char buf[64]; flags = PW_STREAM_FLAG_ASYNC; + flags |= PW_STREAM_FLAG_EARLY_PROCESS; if (pwsink->mode != GST_PIPEWIRE_SINK_MODE_PROVIDE) flags |= PW_STREAM_FLAG_AUTOCONNECT; else @@ -685,20 +832,21 @@ gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps) } } } - res = TRUE; gst_pipewire_clock_reset (GST_PIPEWIRE_CLOCK (pwsink->stream->clock), 0); config = gst_buffer_pool_get_config (GST_BUFFER_POOL_CAST (pwsink->stream->pool)); gst_buffer_pool_config_get_params (config, NULL, &size, &min_buffers, &max_buffers); gst_buffer_pool_config_set_params (config, caps, size, min_buffers, max_buffers); + if(pwsink->is_video) + gst_buffer_pool_config_add_option(config, GST_BUFFER_POOL_OPTION_VIDEO_META); gst_buffer_pool_set_config (GST_BUFFER_POOL_CAST (pwsink->stream->pool), config); pw_thread_loop_unlock (pwsink->stream->core->loop); - pwsink->negotiated = res; + pwsink->negotiated = TRUE; - return res; + return TRUE; start_error: { @@ -714,7 +862,6 @@ gst_pipewire_sink_render (GstBaseSink * bsink, GstBuffer * buffer) GstPipeWireSink *pwsink; GstFlowReturn res = GST_FLOW_OK; const char *error = NULL; - gboolean unref_buffer = FALSE; pwsink = GST_PIPEWIRE_SINK (bsink); @@ -747,34 +894,99 @@ gst_pipewire_sink_render (GstBaseSink * bsink, GstBuffer * buffer) goto done_unlock; if (buffer->pool != GST_BUFFER_POOL_CAST (pwsink->stream->pool)) { - GstBuffer *b = NULL; - GstMapInfo info = { 0, }; - GstBufferPoolAcquireParams params = { 0, }; - - pw_thread_loop_unlock (pwsink->stream->core->loop); + gsize offset = 0; + gsize buf_size = gst_buffer_get_size (buffer); + + GST_TRACE_OBJECT(pwsink, "Buffer is not from pipewirepool, copying into our pool"); + + /* For some streams, the buffer size is changed and may exceed the acquired + * buffer size which is acquired from the pool of pipewiresink. Need split + * the buffer and send them in turn for this case */ + while (buf_size) { + GstBuffer *b = NULL; + GstMapInfo info = { 0, }; + GstBufferPoolAcquireParams params = { 0, }; + + pw_thread_loop_unlock (pwsink->stream->core->loop); + + params.flags = GST_BUFFER_POOL_ACQUIRE_FLAG_LAST; + res = gst_buffer_pool_acquire_buffer (GST_BUFFER_POOL_CAST (pwsink->stream->pool), + &b, ¶ms); + if (res == GST_FLOW_CUSTOM_ERROR_1) { + res = gst_base_sink_wait_preroll (bsink); + if (res != GST_FLOW_OK) + goto done; + continue; + } + if (res != GST_FLOW_OK) + goto done; + + if (pwsink->is_video) { + GstVideoFrame src, dst; + gboolean copied = FALSE; + buf_size = 0; // to break from the loop + + /* + splitting of buffers in the case of video might break the frame layout + and that seems to be causing issues while retrieving the buffers on the receiver + side. Hence use the video_frame_map to copy the buffer of bigger size into the + pipewirepool's buffer + */ + + if (!gst_video_frame_map (&dst, &pwsink->stream->pool->video_info, b, + GST_MAP_WRITE)) { + GST_ERROR_OBJECT(pwsink, "Failed to map dest buffer"); + return GST_FLOW_ERROR; + } + + if (!gst_video_frame_map (&src, &pwsink->stream->pool->video_info, buffer, GST_MAP_READ)) { + gst_video_frame_unmap (&dst); + GST_ERROR_OBJECT(pwsink, "Failed to map src buffer"); + return GST_FLOW_ERROR; + } + + copied = gst_video_frame_copy (&dst, &src); + + gst_video_frame_unmap (&src); + gst_video_frame_unmap (&dst); + + if (!copied) { + GST_ERROR_OBJECT(pwsink, "Failed to copy the frame"); + return GST_FLOW_ERROR; + } + + gst_buffer_copy_into(b, buffer, GST_BUFFER_COPY_METADATA, 0, -1); + } else { + gst_buffer_map (b, &info, GST_MAP_WRITE); + gsize extract_size = (buf_size <= info.maxsize) ? buf_size: info.maxsize; + gst_buffer_extract (buffer, offset, info.data, info.maxsize); + gst_buffer_unmap (b, &info); + gst_buffer_resize (b, 0, extract_size); + gst_buffer_copy_into(b, buffer, GST_BUFFER_COPY_METADATA, 0, -1); + buf_size -= extract_size; + offset += extract_size; + } - if ((res = gst_buffer_pool_acquire_buffer (GST_BUFFER_POOL_CAST (pwsink->stream->pool), &b, ¶ms)) != GST_FLOW_OK) - goto done; + pw_thread_loop_lock (pwsink->stream->core->loop); + if (pw_stream_get_state (pwsink->stream->pwstream, &error) != PW_STREAM_STATE_STREAMING) { + gst_buffer_unref (b); + goto done_unlock; + } - gst_buffer_map (b, &info, GST_MAP_WRITE); - gst_buffer_extract (buffer, 0, info.data, info.maxsize); - gst_buffer_unmap (b, &info); - gst_buffer_resize (b, 0, gst_buffer_get_size (buffer)); - gst_buffer_copy_into(b, buffer, GST_BUFFER_COPY_METADATA, 0, -1); - buffer = b; - unref_buffer = TRUE; + do_send_buffer (pwsink, b); + gst_buffer_unref (b); - pw_thread_loop_lock (pwsink->stream->core->loop); - if (pw_stream_get_state (pwsink->stream->pwstream, &error) != PW_STREAM_STATE_STREAMING) - goto done_unlock; - } + if (pw_stream_is_driving (pwsink->stream->pwstream)) + pw_stream_trigger_process (pwsink->stream->pwstream); + } + } else { + GST_TRACE_OBJECT(pwsink, "Buffer is from pipewirepool"); - do_send_buffer (pwsink, buffer); - if (unref_buffer) - gst_buffer_unref (buffer); + do_send_buffer (pwsink, buffer); - if (pw_stream_is_driving (pwsink->stream->pwstream)) - pw_stream_trigger_process (pwsink->stream->pwstream); + if (pw_stream_is_driving (pwsink->stream->pwstream)) + pw_stream_trigger_process (pwsink->stream->pwstream); + } done_unlock: pw_thread_loop_unlock (pwsink->stream->core->loop); @@ -817,20 +1029,14 @@ gst_pipewire_sink_change_state (GstElement * element, GstStateChange transition) pw_thread_loop_lock (this->stream->core->loop); pw_stream_set_active(this->stream->pwstream, false); pw_thread_loop_unlock (this->stream->core->loop); - break; - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: - /* uncork and start play */ - pw_thread_loop_lock (this->stream->core->loop); - pw_stream_set_active(this->stream->pwstream, true); - pw_thread_loop_unlock (this->stream->core->loop); - gst_buffer_pool_set_flushing(GST_BUFFER_POOL_CAST(this->stream->pool), FALSE); + gst_pipewire_pool_set_paused(this->stream->pool, TRUE); break; case GST_STATE_CHANGE_PLAYING_TO_PAUSED: /* stop play ASAP by corking */ + gst_pipewire_pool_set_paused(this->stream->pool, TRUE); pw_thread_loop_lock (this->stream->core->loop); pw_stream_set_active(this->stream->pwstream, false); pw_thread_loop_unlock (this->stream->core->loop); - gst_buffer_pool_set_flushing(GST_BUFFER_POOL_CAST(this->stream->pool), TRUE); break; default: break; @@ -839,6 +1045,16 @@ gst_pipewire_sink_change_state (GstElement * element, GstStateChange transition) ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_PLAYING: + /* For some cases, the param_changed event is earlier than the state switch + * from paused state to playing state which will wait until buffer pool is ready. + * Guarantee to finish preoll if needed to active buffer pool before uncorking and + * starting play */ + gst_pipewire_pool_set_paused(this->stream->pool, FALSE); + pw_thread_loop_lock (this->stream->core->loop); + pw_stream_set_active(this->stream->pwstream, true); + pw_thread_loop_unlock (this->stream->core->loop); + break; case GST_STATE_CHANGE_PLAYING_TO_PAUSED: break; case GST_STATE_CHANGE_PAUSED_TO_READY: @@ -859,3 +1075,42 @@ open_failed: return GST_STATE_CHANGE_FAILURE; } } + +static gboolean gst_pipewire_sink_event (GstBaseSink *sink, GstEvent *event) { + GstPipeWireSink *pw_sink = GST_PIPEWIRE_SINK(sink); + GstState current_state = GST_ELEMENT(sink)->current_state; + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_FLUSH_START: + { + GST_DEBUG_OBJECT (pw_sink, "flush-start"); + pw_thread_loop_lock (pw_sink->stream->core->loop); + + /* The stream would be already inactive if the sink is not PLAYING */ + if (current_state == GST_STATE_PLAYING) + pw_stream_set_active(pw_sink->stream->pwstream, false); + + gst_buffer_pool_set_flushing(GST_BUFFER_POOL_CAST(pw_sink->stream->pool), TRUE); + pw_stream_flush(pw_sink->stream->pwstream, false); + pw_thread_loop_unlock (pw_sink->stream->core->loop); + break; + } + case GST_EVENT_FLUSH_STOP: + { + GST_DEBUG_OBJECT (pw_sink, "flush-stop"); + pw_thread_loop_lock (pw_sink->stream->core->loop); + + /* The stream needs to remain inactive if the sink is not PLAYING */ + if (current_state == GST_STATE_PLAYING) + pw_stream_set_active(pw_sink->stream->pwstream, true); + + gst_buffer_pool_set_flushing(GST_BUFFER_POOL_CAST(pw_sink->stream->pool), FALSE); + pw_thread_loop_unlock (pw_sink->stream->core->loop); + break; + } + default: + break; + } + + return GST_BASE_SINK_CLASS (parent_class)->event (sink, event); +} diff --git a/src/gst/gstpipewiresink.h b/src/gst/gstpipewiresink.h index 74e6667e..60eb3b79 100644 --- a/src/gst/gstpipewiresink.h +++ b/src/gst/gstpipewiresink.h @@ -37,6 +37,22 @@ typedef enum #define GST_TYPE_PIPEWIRE_SINK_MODE (gst_pipewire_sink_mode_get_type ()) + +/** + * GstPipeWireSinkSlaveMethod: + * @GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE: no clock and timestamp slaving + * @GST_PIPEWIRE_SINK_SLAVE_METHOD_RESAMPLE: resample audio + * + * Different clock slaving methods + */ +typedef enum +{ + GST_PIPEWIRE_SINK_SLAVE_METHOD_NONE, + GST_PIPEWIRE_SINK_SLAVE_METHOD_RESAMPLE, +} GstPipeWireSinkSlaveMethod; + +#define GST_TYPE_PIPEWIRE_SINK_SLAVE_METHOD (gst_pipewire_sink_slave_method_get_type ()) + /** * GstPipeWireSink: * @@ -47,11 +63,16 @@ struct _GstPipeWireSink { /*< private >*/ GstPipeWireStream *stream; + gboolean use_bufferpool; /* video state */ gboolean negotiated; + gboolean rate_match; + gint rate; + gboolean is_video; GstPipeWireSinkMode mode; + GstPipeWireSinkSlaveMethod slave_method; }; GType gst_pipewire_sink_mode_get_type (void); diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 1bb31143..1cd04941 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -46,6 +46,7 @@ GST_DEBUG_CATEGORY_STATIC (pipewire_src_debug); #define DEFAULT_RESEND_LAST false #define DEFAULT_KEEPALIVE_TIME 0 #define DEFAULT_AUTOCONNECT true +#define DEFAULT_USE_BUFFERPOOL USE_BUFFERPOOL_AUTO enum { @@ -62,6 +63,7 @@ enum PROP_RESEND_LAST, PROP_KEEPALIVE_TIME, PROP_AUTOCONNECT, + PROP_USE_BUFFERPOOL, }; @@ -130,7 +132,11 @@ gst_pipewire_src_set_property (GObject * object, guint prop_id, break; case PROP_ALWAYS_COPY: - pwsrc->always_copy = g_value_get_boolean (value); + /* don't provide buffer if always copy*/ + if (g_value_get_boolean (value)) + pwsrc->use_bufferpool = USE_BUFFERPOOL_NO; + else + pwsrc->use_bufferpool = USE_BUFFERPOOL_YES; break; case PROP_MIN_BUFFERS: @@ -157,6 +163,13 @@ gst_pipewire_src_set_property (GObject * object, guint prop_id, pwsrc->autoconnect = g_value_get_boolean (value); break; + case PROP_USE_BUFFERPOOL: + if(g_value_get_boolean (value)) + pwsrc->use_bufferpool = USE_BUFFERPOOL_YES; + else + pwsrc->use_bufferpool = USE_BUFFERPOOL_NO; + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -191,7 +204,7 @@ gst_pipewire_src_get_property (GObject * object, guint prop_id, break; case PROP_ALWAYS_COPY: - g_value_set_boolean (value, pwsrc->always_copy); + g_value_set_boolean (value, !pwsrc->use_bufferpool); break; case PROP_MIN_BUFFERS: @@ -218,6 +231,10 @@ gst_pipewire_src_get_property (GObject * object, guint prop_id, g_value_set_boolean (value, pwsrc->autoconnect); break; + case PROP_USE_BUFFERPOOL: + g_value_set_boolean (value, !!pwsrc->use_bufferpool); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -331,7 +348,8 @@ gst_pipewire_src_class_init (GstPipeWireSrcClass * klass) "Always copy the buffer and data", DEFAULT_ALWAYS_COPY, G_PARAM_READWRITE | - G_PARAM_STATIC_STRINGS)); + G_PARAM_STATIC_STRINGS | + G_PARAM_DEPRECATED)); g_object_class_install_property (gobject_class, PROP_MIN_BUFFERS, @@ -387,6 +405,15 @@ gst_pipewire_src_class_init (GstPipeWireSrcClass * klass) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, + PROP_USE_BUFFERPOOL, + g_param_spec_boolean ("use-bufferpool", + "Use bufferpool", + "Use bufferpool (default: true for video, false for audio)", + DEFAULT_USE_BUFFERPOOL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS)); + gstelement_class->provide_clock = gst_pipewire_src_provide_clock; gstelement_class->change_state = gst_pipewire_src_change_state; gstelement_class->send_event = gst_pipewire_src_send_event; @@ -427,7 +454,7 @@ gst_pipewire_src_init (GstPipeWireSrc * src) src->stream = gst_pipewire_stream_new (GST_ELEMENT (src)); - src->always_copy = DEFAULT_ALWAYS_COPY; + src->use_bufferpool = DEFAULT_USE_BUFFERPOOL; src->min_buffers = DEFAULT_MIN_BUFFERS; src->max_buffers = DEFAULT_MAX_BUFFERS; src->resend_last = DEFAULT_RESEND_LAST; @@ -655,21 +682,38 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) for (i = 0; i < b->buffer->n_datas; i++) { struct spa_data *d = &b->buffer->datas[i]; + + if (d->chunk->size == 0) { + // Skip the 0 sized chunk, not adding to the buffer + GST_DEBUG_OBJECT(pwsrc, "Chunk size is 0, skipping"); + continue; + } + GstMemory *pmem = gst_buffer_peek_memory (data->buf, i); if (pmem) { GstMemory *mem; - if (!pwsrc->always_copy) + if (pwsrc->use_bufferpool != USE_BUFFERPOOL_NO) 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); } - if (d->chunk->flags & SPA_CHUNK_FLAG_CORRUPTED) + if (d->chunk->flags & SPA_CHUNK_FLAG_CORRUPTED) { + GST_DEBUG_OBJECT(pwsrc, "Buffer corrupted"); GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_CORRUPTED); + } } - if (!pwsrc->always_copy) + if (pwsrc->use_bufferpool != USE_BUFFERPOOL_NO) gst_buffer_add_parent_buffer_meta (buf, data->buf); gst_buffer_unref (data->buf); + + if (gst_buffer_get_size(buf) == 0) + { + GST_ERROR_OBJECT(pwsrc, "Buffer is empty, dropping this"); + gst_buffer_unref(buf); + buf = NULL; + } + return buf; } @@ -938,6 +982,9 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc) GST_DEBUG_OBJECT (basesrc, "connect capture with path %s, target-object %s", pwsrc->stream->path, pwsrc->stream->target_object); + pwsrc->possible_caps = possible_caps; + pwsrc->negotiated = FALSE; + enum pw_stream_flags flags; flags = PW_STREAM_FLAG_DONT_RECONNECT | PW_STREAM_FLAG_ASYNC; @@ -953,9 +1000,6 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc) pw_thread_loop_get_time (pwsrc->stream->core->loop, &abstime, GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC); - pwsrc->possible_caps = possible_caps; - pwsrc->negotiated = FALSE; - while (TRUE) { enum pw_stream_state state = pw_stream_get_state (pwsrc->stream->pwstream, &error); @@ -1047,7 +1091,7 @@ handle_format_change (GstPipeWireSrc *pwsrc, } pw_peer_caps = gst_caps_from_format (param); - if (pw_peer_caps) { + if (pw_peer_caps && pwsrc->possible_caps) { pwsrc->caps = gst_caps_intersect_full (pw_peer_caps, pwsrc->possible_caps, GST_CAPS_INTERSECT_FIRST); @@ -1087,6 +1131,10 @@ handle_format_change (GstPipeWireSrc *pwsrc, } else { pwsrc->negotiated = FALSE; pwsrc->is_video = FALSE; + + /* Don't provide bufferpool for audio if not requested by the application/user */ + if (pwsrc->use_bufferpool != USE_BUFFERPOOL_YES) + pwsrc->use_bufferpool = USE_BUFFERPOOL_NO; } if (pwsrc->caps) { diff --git a/src/gst/gstpipewiresrc.h b/src/gst/gstpipewiresrc.h index d8533c78..d5728cdc 100644 --- a/src/gst/gstpipewiresrc.h +++ b/src/gst/gstpipewiresrc.h @@ -36,7 +36,7 @@ struct _GstPipeWireSrc { GstPipeWireStream *stream; /*< private >*/ - gboolean always_copy; + gint use_bufferpool; gint min_buffers; gint max_buffers; gboolean resend_last; diff --git a/src/gst/gstpipewirestream.c b/src/gst/gstpipewirestream.c index bf764154..68cb9be2 100644 --- a/src/gst/gstpipewirestream.c +++ b/src/gst/gstpipewirestream.c @@ -19,6 +19,7 @@ gst_pipewire_stream_init (GstPipeWireStream * self) self->fd = -1; self->client_name = g_strdup (pw_get_client_name()); self->pool = gst_pipewire_pool_new (self); + spa_dll_init(&self->dll); } static void diff --git a/src/gst/gstpipewirestream.h b/src/gst/gstpipewirestream.h index ff8c8e2e..a301375c 100644 --- a/src/gst/gstpipewirestream.h +++ b/src/gst/gstpipewirestream.h @@ -11,6 +11,7 @@ #include "gstpipewirecore.h" #include <gst/gst.h> +#include <spa/utils/dll.h> #include <pipewire/pipewire.h> G_BEGIN_DECLS @@ -29,6 +30,13 @@ struct _GstPipeWireStream { GstPipeWirePool *pool; GstClock *clock; + guint64 position; + struct spa_dll dll; + double err_avg, err_var, err_wdw; + guint64 last_ts; + guint64 base_buffer_ts; + guint64 base_ts; + /* the actual pw stream */ struct pw_stream *pwstream; struct spa_hook pwstream_listener; diff --git a/src/gst/meson.build b/src/gst/meson.build index ba1f6d55..1e39bcf8 100644 --- a/src/gst/meson.build +++ b/src/gst/meson.build @@ -27,7 +27,7 @@ pipewire_gst_headers = [ pipewire_gst = shared_library('gstpipewire', pipewire_gst_sources, include_directories : [ configinc ], - dependencies : [ spa_dep, gst_dep, pipewire_dep ], + dependencies : [ spa_dep, gst_dep, pipewire_dep, mathlib ], install : true, install_dir : '@0@/gstreamer-1.0'.format(get_option('libdir')), ) diff --git a/src/modules/meson.build b/src/modules/meson.build index 3f400f08..51db7733 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -1,5 +1,4 @@ subdir('module-rt') -subdir('spa') # The list of "main" source files for modules, the ones that have the # doxygen documentation @@ -39,6 +38,10 @@ module_sources = [ 'module-rtp-session.c', 'module-rtp-source.c', 'module-rtp-sink.c', + 'module-spa-node.c', + 'module-spa-node-factory.c', + 'module-spa-device.c', + 'module-spa-device-factory.c', 'module-snapcast-discover.c', 'module-vban-recv.c', 'module-vban-send.c', @@ -71,101 +74,14 @@ pipewire_module_loopback = shared_library('pipewire-module-loopback', dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) -plugin_dependencies = [] -if get_option('spa-plugins').allowed() - plugin_dependencies += audioconvert_dep -endif - -simd_cargs = [] -simd_dependencies = [] - -if have_sse - filter_chain_sse = static_library('filter_chain_sse', - ['module-filter-chain/pffft.c', - 'module-filter-chain/dsp-ops-sse.c' ], - c_args : [sse_args, '-O3', '-DHAVE_SSE'], - dependencies : [ spa_dep ], - install : false - ) - simd_cargs += ['-DHAVE_SSE'] - simd_dependencies += filter_chain_sse -endif -if have_avx - filter_chain_avx = static_library('filter_chain_avx', - ['module-filter-chain/dsp-ops-avx.c' ], - c_args : [avx_args, fma_args,'-O3', '-DHAVE_AVX'], - dependencies : [ spa_dep ], - install : false - ) - simd_cargs += ['-DHAVE_AVX'] - simd_dependencies += filter_chain_avx -endif -if have_neon - filter_chain_neon = static_library('filter_chain_neon', - ['module-filter-chain/pffft.c' ], - c_args : [neon_args, '-O3', '-DHAVE_NEON'], - dependencies : [ spa_dep ], - install : false - ) - simd_cargs += ['-DHAVE_NEON'] - simd_dependencies += filter_chain_neon -endif - -filter_chain_c = static_library('filter_chain_c', - ['module-filter-chain/pffft.c', - 'module-filter-chain/dsp-ops.c', - 'module-filter-chain/dsp-ops-c.c' ], - c_args : [simd_cargs, '-O3', '-DPFFFT_SIMD_DISABLE'], - dependencies : [ spa_dep ], - install : false -) -simd_dependencies += filter_chain_c - -filter_chain_sources = [ - 'module-filter-chain.c', - 'module-filter-chain/biquad.c', - 'module-filter-chain/ladspa_plugin.c', - 'module-filter-chain/builtin_plugin.c', - 'module-filter-chain/convolver.c' -] -filter_chain_dependencies = [ - mathlib, dl_lib, pipewire_dep, sndfile_dep, plugin_dependencies -] - pipewire_module_filter_chain = shared_library('pipewire-module-filter-chain', - filter_chain_sources, - include_directories : [configinc], - install : true, - install_dir : modules_install_dir, - install_rpath: modules_install_dir, - link_with : simd_dependencies, - dependencies : filter_chain_dependencies, -) - -if libmysofa_dep.found() -pipewire_module_filter_chain_sofa = shared_library('pipewire-module-filter-chain-sofa', - [ 'module-filter-chain/sofa_plugin.c', - 'module-filter-chain/convolver.c' ], - include_directories : [configinc], - install : true, - install_dir : modules_install_dir, - install_rpath: modules_install_dir, - link_with : simd_dependencies, - dependencies : [ filter_chain_dependencies, libmysofa_dep ] -) -endif - -if lilv_lib.found() -pipewire_module_filter_chain_lv2 = shared_library('pipewire-module-filter-chain-lv2', - [ 'module-filter-chain/lv2_plugin.c' ], + [ 'module-filter-chain.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, - dependencies : [ filter_chain_dependencies, lilv_lib ] + dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) -endif - pipewire_module_combine_stream = shared_library('pipewire-module-combine-stream', [ 'module-combine-stream.c' ], @@ -259,7 +175,7 @@ pipewire_module_parametric_equalizer = shared_library('pipewire-module-parametri install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, - dependencies : [filter_chain_dependencies], + dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], ) pipewire_module_profiler = shared_library('pipewire-module-profiler', @@ -292,6 +208,39 @@ if build_module_rtkit endif summary({'rt': '@0@ RTKit'.format(build_module_rtkit ? 'with' : 'without')}, section: 'Optional Modules') +pipewire_module_spa_node = shared_library('pipewire-module-spa-node', + [ 'module-spa-node.c', 'spa/spa-node.c' ], + include_directories : [configinc], + install : true, + install_dir : modules_install_dir, + install_rpath: modules_install_dir, + dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], +) +pipewire_module_spa_node_factory = shared_library('pipewire-module-spa-node-factory', + [ 'module-spa-node-factory.c', 'spa/spa-node.c' ], + include_directories : [configinc], + install : true, + install_dir : modules_install_dir, + install_rpath: modules_install_dir, + dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], +) +pipewire_module_spa_device = shared_library('pipewire-module-spa-device', + [ 'module-spa-device.c', 'spa/spa-device.c' ], + include_directories : [configinc], + install : true, + install_dir : modules_install_dir, + install_rpath: modules_install_dir, + dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], +) +pipewire_module_spa_device_factory = shared_library('pipewire-module-spa-device-factory', + [ 'module-spa-device-factory.c', 'spa/spa-device.c' ], + include_directories : [configinc], + install : true, + install_dir : modules_install_dir, + install_rpath: modules_install_dir, + dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], +) + build_module_portal = dbus_dep.found() if build_module_portal pipewire_module_portal = shared_library('pipewire-module-portal', [ 'module-portal.c' ], diff --git a/src/modules/module-access.c b/src/modules/module-access.c index 9e06e567..2e246ae9 100644 --- a/src/modules/module-access.c +++ b/src/modules/module-access.c @@ -95,6 +95,22 @@ * - \ref PW_KEY_ACCESS * - \ref PW_KEY_CLIENT_ACCESS * + * ## Config override + * + * A `module.access.args` config section can be added + * to override the module arguments. + * + *\code{.unparsed} + * # ~/.config/pipewire/pipewire.conf.d/my-access-args.conf + * + * module.access.args = { + * access.socket = { + * pipewire-0 = "default", + * pipewire-0-manager = "unrestricted", + * } + * } + *\endcode + * * ## Example configuration * *\code{.unparsed} @@ -274,21 +290,16 @@ get_server_name(const struct spa_dict *props) static int parse_socket_args(struct impl *impl, const char *str) { - struct spa_json it[2]; + struct spa_json it[1]; char socket[PATH_MAX]; + const char *val; + int len; - spa_json_init(&it[0], str, strlen(str)); - - if (spa_json_enter_object(&it[0], &it[1]) <= 0) + if (spa_json_begin_object(&it[0], str, strlen(str)) <= 0) return -EINVAL; - while (spa_json_get_string(&it[1], socket, sizeof(socket)) > 0) { + while ((len = spa_json_object_next(&it[0], socket, sizeof(socket), &val)) > 0) { char value[256]; - const char *val; - int len; - - if ((len = spa_json_next(&it[1], &val)) <= 0) - return -EINVAL; if (spa_json_parse_stringn(val, len, value, sizeof(value)) <= 0) return -EINVAL; @@ -299,17 +310,11 @@ static int parse_socket_args(struct impl *impl, const char *str) return 0; } -static int parse_args(struct impl *impl, const struct pw_properties *props, const char *args_str) +static int parse_args(struct impl *impl, const struct pw_properties *props, const struct pw_properties *args) { - spa_autoptr(pw_properties) args = NULL; const char *str; int res; - if (args_str) - args = pw_properties_new_string(args_str); - else - args = pw_properties_new(NULL, NULL); - if ((str = pw_properties_get(args, "access.legacy")) != NULL) { impl->legacy = spa_atob(str); } else if (pw_properties_get(args, "access.socket")) { @@ -352,10 +357,11 @@ static int parse_args(struct impl *impl, const struct pw_properties *props, cons } SPA_EXPORT -int pipewire__module_init(struct pw_impl_module *module, const char *args) +int pipewire__module_init(struct pw_impl_module *module, const char *args_str) { struct pw_context *context = pw_impl_module_get_context(module); const struct pw_properties *props = pw_context_get_properties(context); + spa_autoptr(pw_properties) args = NULL; struct impl *impl; int res; @@ -365,7 +371,19 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (impl == NULL) return -errno; - pw_log_debug("module %p: new %s", impl, args); + pw_log_debug("module %p: new %s", impl, args_str); + + if (args_str) + args = pw_properties_new_string(args_str); + else + args = pw_properties_new(NULL, NULL); + + if (!args) { + res = -errno; + goto error; + } + + pw_context_conf_update_props(context, "module."NAME".args", args); impl->socket_access = pw_properties_new(NULL, NULL); diff --git a/src/modules/module-adapter.c b/src/modules/module-adapter.c index d92bc965..ea913eba 100644 --- a/src/modules/module-adapter.c +++ b/src/modules/module-adapter.c @@ -147,6 +147,22 @@ static const struct pw_impl_node_events node_events = { .initialized = node_initialized, }; +struct match { + struct pw_properties *props; + int count; +}; +#define MATCH_INIT(p) ((struct match){ .props = (p) }) + +static int execute_match(void *data, const char *location, const char *action, + const char *val, size_t len) +{ + struct match *match = data; + if (spa_streq(action, "update-props")) { + match->count += pw_properties_update_string(match->props, val, len); + } + return 1; +} + static void *create_object(void *_data, struct pw_resource *resource, const char *type, @@ -197,14 +213,20 @@ static void *create_object(void *_data, if (sscanf(str, "pointer:%p", &spa_follower) != 1) goto error_properties; } + if (spa_follower == NULL) { void *iface; const char *factory_name; + struct match match; factory_name = pw_properties_get(properties, SPA_KEY_FACTORY_NAME); if (factory_name == NULL) goto error_properties; + match = MATCH_INIT(properties); + pw_context_conf_section_match_rules(d->context, "node.rules", + &properties->dict, execute_match, &match); + handle = pw_context_load_spa_handle(d->context, factory_name, &properties->dict); diff --git a/src/modules/module-avb/adp.c b/src/modules/module-avb/adp.c index 19216bc8..2275b7b0 100644 --- a/src/modules/module-avb/adp.c +++ b/src/modules/module-avb/adp.c @@ -283,22 +283,18 @@ static int do_help(struct adp *adp, const char *args, FILE *out) static int do_discover(struct adp *adp, const char *args, FILE *out) { - struct spa_json it[2]; + struct spa_json it[1]; char key[128]; uint64_t entity_id = 0ULL; + int len; + const char *value; - spa_json_init(&it[0], args, strlen(args)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) + if (spa_json_begin_object(&it[0], args, strlen(args)) <= 0) return -EINVAL; - while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { - int len; - const char *value; + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &value)) > 0) { uint64_t id_val; - if ((len = spa_json_next(&it[1], &value)) <= 0) - break; - if (spa_json_is_null(value, len)) continue; diff --git a/src/modules/module-avb/maap.c b/src/modules/module-avb/maap.c index 5dbe962e..2ba40cd3 100644 --- a/src/modules/module-avb/maap.c +++ b/src/modules/module-avb/maap.c @@ -253,9 +253,10 @@ static int load_state(struct maap *maap) { const char *str; char key[512]; - struct spa_json it[3]; + struct spa_json it[2]; bool have_offset = false; - int count = 0, offset = 0; + int count = 0, offset = 0, len; + const char *val; snprintf(key, sizeof(key), "maap.%s", maap->server->ifname); pw_conf_load_state("module-avb", key, maap->props); @@ -263,20 +264,13 @@ static int load_state(struct maap *maap) 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) + if (spa_json_begin_array(&it[0], str, strlen(str)) <= 0) return 0; - if (spa_json_enter_object(&it[1], &it[2]) <= 0) + if (spa_json_enter_object(&it[0], &it[1]) <= 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; - + while ((len = spa_json_object_next(&it[1], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "start")) { uint8_t addr[6]; if (avb_utils_parse_addr(val, len, addr) >= 0 && diff --git a/src/modules/module-client-device.c b/src/modules/module-client-device.c index e2450e81..84b8456b 100644 --- a/src/modules/module-client-device.c +++ b/src/modules/module-client-device.c @@ -16,10 +16,63 @@ #include "module-client-device/client-device.h" /** \page page_module_client_device Client Device + * + * Allow clients to export devices to the PipeWire daemon. + * + * This module creates an export type for the \ref SPA_TYPE_INTERFACE_Device + * interface. + * + * With \ref pw_core_export(), objects of this type can be exported to the + * PipeWire server. All actions performed on the device locally will be visible + * to connecteced clients. + * + * In some cases, it is possible to use this factory directly. + * With \ref pw_core_create_object() on the `client-device` + * factory will result in a \ref SPA_TYPE_INTERFACE_Device proxy that can be + * used to control the server side created \ref pw_impl_device. + * + * Schematically, the client side \ref spa_device is wrapped in the ClientDevice + * proxy and unwrapped by the server side resource so that all actions on the client + * side device are reflected on the server side device and server side actions are + * reflected in the client. + * + *\code{.unparsed} + * + * client side proxy server side resource + * .------------------------------. .----------------------------------. + * | SPA_TYPE_INTERFACE_Device | | PW_TYPE_INTERFACE_Device | + * | | IPC |.--------------------------------.| + * | | -----> || SPA_TYPE_INTERFACE_Device || + * | | |'--------------------------------'| + * '------------------------------' '----------------------------------' + *\endcode * * ## Module Name * * `libpipewire-module-client-device` + * + * ## Module Options + * + * This module has no options. + * + * ## Properties for the create_object call + * + * All properties are passed directly to the \ref pw_context_create_device() call. + * + * ## Example configuration + * + * The module is usually added to the config file of the main PipeWire daemon and the + * clients. + * + *\code{.unparsed} + * context.modules = [ + * { name = libpipewire-module-client-device } + * ] + *\endcode + * + * ## See also + * + * - `module-spa-device-factory`: make nodes from a factory */ #define NAME "client-device" diff --git a/src/modules/module-client-node.c b/src/modules/module-client-node.c index 9e8922db..dc978084 100644 --- a/src/modules/module-client-node.c +++ b/src/modules/module-client-node.c @@ -13,14 +13,82 @@ #include <pipewire/impl.h> +#define PW_API_CLIENT_NODE_IMPL SPA_EXPORT #include "module-client-node/v0/client-node.h" #include "module-client-node/client-node.h" /** \page page_module_client_node Client Node + * + * Allow clients to export processing nodes to the PipeWire daemon. + * + * This module creates 2 export types, one for the \ref PW_TYPE_INTERFACE_Node and + * another for the \ref SPA_TYPE_INTERFACE_Node interfaces. + * + * With \ref pw_core_export(), objects of these types can be exported to the + * PipeWire server. All actions performed on the node locally will be visible + * to connecteced clients and scheduling of the Node will be performed. + * + * Objects of the \ref PW_TYPE_INTERFACE_Node interface can be made with + * \ref pw_context_create_node(), for example. You would manually need to create + * and add an object of the \ref SPA_TYPE_INTERFACE_Node interface. Exporting a + * \ref SPA_TYPE_INTERFACE_Node directly will first wrap it in a + * \ref PW_TYPE_INTERFACE_Node interface. + * + * Usually this module is not used directly but through the \ref pw_stream and + * \ref pw_filter APIs, which provides API to implement the \ref SPA_TYPE_INTERFACE_Node + * interface. + * + * In some cases, it is possible to use this factory directly (the PipeWire JACK + * implementation does this). With \ref pw_core_create_object() on the `client-node` + * factory will result in a \ref PW_TYPE_INTERFACE_ClientNode proxy that can be + * used to control the server side created \ref pw_impl_node. + * + * Schematically, the client side \ref pw_impl_node is wrapped in the ClientNode + * proxy and unwrapped by the server side resource so that all actions on the client + * side node are reflected on the server side node and server side actions are + * reflected in the client. + * + *\code{.unparsed} + * + * client side proxy server side resource + * .------------------------------. .----------------------------------. + * | PW_TYPE_INTERFACE_ClientNode | | PW_TYPE_INTERFACE_Node | + * |.----------------------------.| IPC |.--------------------------------.| + * || PW_TYPE_INTERFACE_Node || -----> || SPA_TYPE_INTERFACE_Node || + * ||.--------------------------.|| ||.------------------------------.|| + * ||| SPA_TYPE_INTERFACE_Node ||| ||| PW_TYPE_INTERFACE_ClientNode ||| + * ||| ||| ||| ||| + * ||'--------------------------'|| ||'------------------------------'|| + * |'----------------------------'| |'--------------------------------'| + * '------------------------------' '----------------------------------' + *\endcode * * ## Module Name * * `libpipewire-module-client-node` + * + * ## Module Options + * + * This module has no options. + * + * ## Properties for the create_object call + * + * All properties are passed directly to the \ref pw_context_create_node() call. + * + * ## Example configuration + * + * The module is usually added to the config file of the main PipeWire daemon and the + * clients. + * + *\code{.unparsed} + * context.modules = [ + * { name = libpipewire-module-client-node } + * ] + *\endcode + * + * ## See also + * + * - `module-spa-node-factory`: make nodes from a factory */ #define NAME "client-node" diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c index 20a23290..4afcb8f0 100644 --- a/src/modules/module-combine-stream.c +++ b/src/modules/module-combine-stream.c @@ -25,6 +25,7 @@ #include <spa/pod/builder.h> #include <spa/param/audio/format-utils.h> #include <spa/param/audio/raw.h> +#include <spa/param/audio/raw-json.h> #include <spa/param/latency-utils.h> #include <spa/param/tag-utils.h> @@ -324,44 +325,15 @@ struct stream { unsigned int have_latency:1; }; -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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - const char *str; - - spa_zero(*info); - info->format = SPA_AUDIO_FORMAT_F32P; - 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)); - if (info->channels == 0) - parse_position(info, DEFAULT_POSITION, strlen(DEFAULT_POSITION)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } static void ringbuffer_init(struct ringbuffer *r, void *buf, uint32_t size) @@ -397,6 +369,40 @@ static void ringbuffer_memcpy(struct ringbuffer *r, void *dst, void *src, uint32 } } +static void mix_f32(float *dst, float *src, uint32_t size) +{ + uint32_t i, s = size / sizeof(float); + for (i = 0; i < s; i++) + dst[i] += src[i]; +} + +static void ringbuffer_mix(struct ringbuffer *r, void *dst, void *src, uint32_t size) +{ + uint32_t avail; + + avail = SPA_MIN(size, r->size); + + /* buf to dst */ + if (dst && avail > 0) { + uint32_t l0 = SPA_MIN(avail, r->size - r->idx), l1 = avail - l0; + mix_f32(dst, SPA_PTROFF(r->buf, r->idx, void), l0); + if (SPA_UNLIKELY(l1 > 0)) + mix_f32(SPA_PTROFF(dst, l0, void), r->buf, l1); + dst = SPA_PTROFF(dst, avail, void); + } + /* src to dst */ + if (size > avail) { + if (dst) + mix_f32(dst, src, size - avail); + src = SPA_PTROFF(src, size - avail, void); + } + /* src to buf */ + if (avail > 0) { + spa_ringbuffer_write_data(NULL, r->buf, r->size, r->idx, src, avail); + r->idx = (r->idx + avail) % r->size; + } +} + static void ringbuffer_copy(struct ringbuffer *dst, struct ringbuffer *src) { uint32_t l0, l1; @@ -775,7 +781,7 @@ static int create_stream(struct stream_info *info) int res; uint32_t n_params, i, j; const struct spa_pod *params[1]; - const char *str, *node_name; + const char *str, *node_name, *dir_name; uint8_t buffer[1024]; struct spa_pod_builder b; struct spa_audio_info_raw remap_info, tmp_info; @@ -802,16 +808,32 @@ static int create_stream(struct stream_info *info) s->id = info->id; s->impl = impl; + s->stream_events = stream_events; + + flags = PW_STREAM_FLAG_AUTOCONNECT | + PW_STREAM_FLAG_MAP_BUFFERS | + PW_STREAM_FLAG_RT_PROCESS | + PW_STREAM_FLAG_ASYNC; + + if (impl->mode == MODE_SINK || impl->mode == MODE_CAPTURE) { + direction = PW_DIRECTION_OUTPUT; + flags |= PW_STREAM_FLAG_TRIGGER; + dir_name = "output"; + } else { + direction = PW_DIRECTION_INPUT; + s->stream_events.process = stream_input_process; + dir_name = "input"; + } s->info = impl->info; if ((str = pw_properties_get(info->stream_props, SPA_KEY_AUDIO_POSITION)) != NULL) - parse_position(&s->info, str, strlen(str)); + spa_audio_parse_position(str, strlen(str), s->info.position, &s->info.channels); if (s->info.channels == 0) s->info = impl->info; spa_zero(remap_info); if ((str = pw_properties_get(info->stream_props, "combine.audio.position")) != NULL) - parse_position(&remap_info, str, strlen(str)); + spa_audio_parse_position(str, strlen(str), remap_info.position, &remap_info.channels); if (remap_info.channels == 0) remap_info = s->info; @@ -835,10 +857,10 @@ static int create_stream(struct stream_info *info) if (pw_properties_get(info->stream_props, PW_KEY_MEDIA_NAME) == NULL) pw_properties_setf(info->stream_props, PW_KEY_MEDIA_NAME, - "%s output", str); + "%s %s", str, dir_name); if (pw_properties_get(info->stream_props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_setf(info->stream_props, PW_KEY_NODE_DESCRIPTION, - "%s output", str); + "%s %s", str, dir_name); str = pw_properties_get(impl->props, PW_KEY_NODE_NAME); if (str == NULL) @@ -846,7 +868,7 @@ static int create_stream(struct stream_info *info) if (pw_properties_get(info->stream_props, PW_KEY_NODE_NAME) == NULL) pw_properties_setf(info->stream_props, PW_KEY_NODE_NAME, - "output.%s_%s", str, node_name); + "%s.%s_%s", dir_name, str, node_name); if (info->on_demand_id) { s->on_demand_id = strdup(info->on_demand_id); @@ -861,21 +883,6 @@ static int create_stream(struct stream_info *info) if (s->stream == NULL) goto error_errno; - s->stream_events = stream_events; - - flags = PW_STREAM_FLAG_AUTOCONNECT | - PW_STREAM_FLAG_MAP_BUFFERS | - PW_STREAM_FLAG_RT_PROCESS | - PW_STREAM_FLAG_ASYNC; - - if (impl->mode == MODE_SINK || impl->mode == MODE_CAPTURE) { - direction = PW_DIRECTION_OUTPUT; - flags |= PW_STREAM_FLAG_TRIGGER; - } else { - direction = PW_DIRECTION_INPUT; - s->stream_events.process = stream_input_process; - } - pw_stream_add_listener(s->stream, &s->stream_listener, &s->stream_events, s); @@ -1177,11 +1184,14 @@ static void combine_output_process(void *d) struct pw_buffer *in, *out; struct stream *s; bool delay_changed = false; + bool mix[SPA_AUDIO_MAX_CHANNELS]; if ((out = pw_stream_dequeue_buffer(impl->combine)) == NULL) { pw_log_debug("%p: out of output buffers: %m", impl); return; } + for (uint32_t i = 0; i < out->buffer->n_datas; i++) + mix[i] = false; spa_list_for_each(s, &impl->streams, link) { uint32_t j; @@ -1214,7 +1224,6 @@ static void combine_output_process(void *d) ds = &in->buffer->datas[j]; - /* FIXME, need to do mixing for overlapping streams */ remap = s->remap[j]; if (remap < out->buffer->n_datas) { uint32_t offs, size; @@ -1225,8 +1234,14 @@ static void combine_output_process(void *d) size = SPA_MIN(ds->chunk->size, ds->maxsize - offs); size = SPA_MIN(size, dd->maxsize); - ringbuffer_memcpy(&s->delay[j], - dd->data, SPA_PTROFF(ds->data, offs, void), size); + if (mix[remap]) { + ringbuffer_mix(&s->delay[j], + dd->data, SPA_PTROFF(ds->data, offs, void), size); + } else { + ringbuffer_memcpy(&s->delay[j], + dd->data, SPA_PTROFF(ds->data, offs, void), size); + mix[remap] = true; + } outsize = SPA_MAX(outsize, size); stride = SPA_MAX(stride, ds->chunk->stride); diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c index dd86d792..4f08927a 100644 --- a/src/modules/module-echo-cancel.c +++ b/src/modules/module-echo-cancel.c @@ -21,6 +21,7 @@ #include <spa/debug/types.h> #include <spa/param/audio/format-utils.h> #include <spa/param/audio/raw.h> +#include <spa/param/audio/raw-json.h> #include <spa/param/latency-utils.h> #include <spa/pod/builder.h> #include <spa/pod/dynamic.h> @@ -152,7 +153,6 @@ 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 @@ -1191,48 +1191,17 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { - const char *str; - - *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)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_RATE, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) @@ -1390,17 +1359,21 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) } if ((str = pw_properties_get(impl->capture_props, SPA_KEY_AUDIO_POSITION)) != NULL) { - parse_position(&impl->capture_info, str, strlen(str)); + spa_audio_parse_position(str, strlen(str), + impl->capture_info.position, &impl->capture_info.channels); } if ((str = pw_properties_get(impl->source_props, SPA_KEY_AUDIO_POSITION)) != NULL) { - parse_position(&impl->source_info, str, strlen(str)); + spa_audio_parse_position(str, strlen(str), + impl->source_info.position, &impl->source_info.channels); } if ((str = pw_properties_get(impl->sink_props, SPA_KEY_AUDIO_POSITION)) != NULL) { - parse_position(&impl->sink_info, str, strlen(str)); + spa_audio_parse_position(str, strlen(str), + impl->sink_info.position, &impl->sink_info.channels); impl->playback_info = impl->sink_info; } if ((str = pw_properties_get(impl->playback_props, SPA_KEY_AUDIO_POSITION)) != NULL) { - parse_position(&impl->playback_info, str, strlen(str)); + spa_audio_parse_position(str, strlen(str), + impl->playback_info.position, &impl->playback_info.channels); if (impl->playback_info.channels != impl->sink_info.channels) impl->playback_info = impl->sink_info; } diff --git a/src/modules/module-example-filter.c b/src/modules/module-example-filter.c index 6a834643..5f6268dd 100644 --- a/src/modules/module-example-filter.c +++ b/src/modules/module-example-filter.c @@ -17,6 +17,7 @@ #include <spa/utils/json.h> #include <spa/utils/ringbuffer.h> #include <spa/param/latency-utils.h> +#include <spa/param/audio/raw-json.h> #include <spa/debug/types.h> #include <pipewire/impl.h> @@ -96,6 +97,8 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic +#define DEFAULT_POSITION "[ FL FR ]" + static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Create example filter streams" }, @@ -158,7 +161,15 @@ static void capture_destroy(void *d) static void capture_process(void *d) { struct impl *impl = d; - pw_stream_trigger_process(impl->playback); + int res; + if ((res = pw_stream_trigger_process(impl->playback)) < 0) { + while (true) { + struct pw_buffer *t; + if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL) + break; + pw_stream_queue_buffer(impl->capture, t); + } + } } static void playback_process(void *d) @@ -447,43 +458,16 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { - const char *str; - - *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_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)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_RATE, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) diff --git a/src/modules/module-example-sink.c b/src/modules/module-example-sink.c index a8fcb13c..8014f61f 100644 --- a/src/modules/module-example-sink.c +++ b/src/modules/module-example-sink.c @@ -24,6 +24,7 @@ #include <spa/pod/builder.h> #include <spa/param/audio/format-utils.h> #include <spa/param/audio/raw.h> +#include <spa/param/audio/raw-json.h> #include <pipewire/impl.h> #include <pipewire/i18n.h> @@ -272,61 +273,18 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static inline uint32_t 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 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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *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)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_FORMAT, + SPA_KEY_AUDIO_RATE, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } static int calc_frame_size(const struct spa_audio_info_raw *info) diff --git a/src/modules/module-example-source.c b/src/modules/module-example-source.c index c8f739c2..47a06189 100644 --- a/src/modules/module-example-source.c +++ b/src/modules/module-example-source.c @@ -24,6 +24,7 @@ #include <spa/pod/builder.h> #include <spa/param/audio/format-utils.h> #include <spa/param/audio/raw.h> +#include <spa/param/audio/raw-json.h> #include <pipewire/impl.h> #include <pipewire/i18n.h> @@ -278,61 +279,18 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static inline uint32_t 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 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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *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)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_FORMAT, + SPA_KEY_AUDIO_RATE, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } static int calc_frame_size(const struct spa_audio_info_raw *info) diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c index 26e281e4..4345e5a1 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -24,6 +24,8 @@ #include <spa/param/audio/format-utils.h> #include <spa/param/latency-utils.h> #include <spa/param/audio/raw.h> +#include <spa/param/audio/raw-json.h> +#include <spa/control/ump-utils.h> #include <pipewire/impl.h> #include <pipewire/i18n.h> @@ -341,30 +343,30 @@ static void midi_to_ffado(struct port *p, float *src, uint32_t n_samples) p->event_pos = 0; SPA_POD_SEQUENCE_FOREACH(seq, c) { - switch(c->type) { - case SPA_CONTROL_Midi: - { - uint8_t *data = SPA_POD_BODY(&c->value); - size_t size = SPA_POD_BODY_SIZE(&c->value); - - if (index < c->offset) - index = SPA_ROUND_UP_N(c->offset, 8); - for (i = 0; i < size; i++) { - if (index >= n_samples) { - /* keep events that don't fit for the next cycle */ - if (p->event_pos < sizeof(p->event_buffer)) - p->event_buffer[p->event_pos++] = data[i]; - else - unhandled++; - } + uint8_t data[16]; + int j, size; + + if (c->type != SPA_CONTROL_UMP) + continue; + + size = spa_ump_to_midi(SPA_POD_BODY(&c->value), + SPA_POD_BODY_SIZE(&c->value), data, sizeof(data)); + if (size <= 0) + continue; + + if (index < c->offset) + index = SPA_ROUND_UP_N(c->offset, 8); + for (j = 0; j < size; j++) { + if (index >= n_samples) { + /* keep events that don't fit for the next cycle */ + if (p->event_pos < sizeof(p->event_buffer)) + p->event_buffer[p->event_pos++] = data[j]; else - dst[index] = 0x01000000 | (uint32_t) data[i]; - index += 8; + unhandled++; } - break; - } - default: - break; + else + dst[index] = 0x01000000 | (uint32_t) data[j]; + index += 8; } } if (unhandled > 0) @@ -489,8 +491,16 @@ static void ffado_to_midi(struct port *p, float *dst, uint32_t *src, uint32_t si continue; if (process_byte(p, i, data & 0xff, &frame, &bytes, &size)) { - spa_pod_builder_control(&b, frame, SPA_CONTROL_Midi); - spa_pod_builder_bytes(&b, bytes, size); + uint64_t state = 0; + while (size > 0) { + uint32_t ev[4]; + int ev_size = spa_ump_from_midi(&bytes, &size, ev, sizeof(ev), 0, &state); + if (ev_size <= 0) + break; + + spa_pod_builder_control(&b, frame, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ev, ev_size); + } } } spa_pod_builder_pop(&b, &f); @@ -762,7 +772,7 @@ static int make_stream_ports(struct stream *s) break; case ffado_stream_type_midi: props = pw_properties_new( - PW_KEY_FORMAT_DSP, "8 bit raw midi", + PW_KEY_FORMAT_DSP, "32 bit raw UMP", PW_KEY_PORT_NAME, port->name, PW_KEY_PORT_PHYSICAL, "true", PW_KEY_PORT_TERMINAL, "true", @@ -1397,61 +1407,30 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -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 void parse_devices(struct impl *impl, const char *val, size_t len) { - struct spa_json it[2]; + struct spa_json it[1]; char v[FFADO_MAX_SPECSTRING_LENGTH]; - spa_json_init(&it[0], val, len); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], val, len); + if (spa_json_begin_array_relax(&it[0], val, len) <= 0) + return; impl->n_devices = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && + while (spa_json_get_string(&it[0], v, sizeof(v)) > 0 && impl->n_devices < FFADO_MAX_SPECSTRINGS) { impl->devices[impl->n_devices++] = strdup(v); } } -static void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - const char *str; - - spa_zero(*info); - info->format = SPA_AUDIO_FORMAT_F32P; - info->rate = 0; - 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)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index f2f6ad93..1446cc43 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -13,27 +13,21 @@ #include "config.h" -#include "module-filter-chain/plugin.h" - -#include <spa/utils/result.h> -#include <spa/utils/string.h> -#include <spa/utils/json.h> -#include <spa/support/cpu.h> #include <spa/param/latency-utils.h> #include <spa/param/tag-utils.h> +#include <spa/param/audio/raw-json.h> #include <spa/pod/dynamic.h> -#include <spa/debug/types.h> -#include <spa/debug/log.h> +#include <spa/filter-graph/filter-graph.h> -#include <pipewire/utils.h> #include <pipewire/impl.h> -#include <pipewire/extensions/profiler.h> #define NAME "filter-chain" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic +extern struct spa_handle_factory spa_filter_graph_factory; + /** * \page page_module_filter_chain Filter-Chain * @@ -101,18 +95,18 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); * Nodes describe the processing filters in the graph. Use a tool like lv2ls * or listplugins to get a list of available plugins, labels and the port names. * - * - `type` is one of `ladspa`, `lv2`, `builtin` or `sofa`. + * - `type` is one of `ladspa`, `lv2`, `builtin`, `sofa` or `ebur128`. * - `name` is the name for this node, you might need this later to refer to this node * and its ports when setting controls or making links. * - `plugin` is the type specific plugin name. * - For LADSPA plugins it will append `.so` to find the shared object with that * name in the LADSPA plugin path. * - For LV2, this is the plugin URI obtained with lv2ls. - * - For builtin and sofa this is ignored + * - For builtin, sofa and ebur128 this is ignored * - `label` is the type specific filter inside the plugin. * - For LADSPA this is the label * - For LV2 this is unused - * - For builtin and sofa this is the name of the filter to use + * - For builtin, sofa and ebur128 this is the name of the filter to use * * - `config` contains a filter specific configuration section. Some plugins need * this. (convolver, sofa, delay, ...) @@ -232,6 +226,84 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); * } *\endcode * + * ### Parametric EQ + * + * The parametric EQ chains a number of biquads together. It is more efficient than + * specifying a number of chained biquads and it can also load configuration from a + * file. + * + * The parametric EQ supports multichannel processing and has 8 input and 8 output ports + * that don't all need to be connected. The ports are named `In 1` to `In 8` and + * `Out 1` to `Out 8`. + * + *\code{.unparsed} + * filter.graph = { + * nodes = [ + * { + * type = builtin + * name = ... + * label = param_eq + * config = { + * filename = "..." + * #filename1 = "...", filename2 = "...", ... + * filters = [ + * { type = ..., freq = ..., gain = ..., q = ... }, + * { type = ..., freq = ..., gain = ..., q = ... }, + * .... + * ] + * #filters1 = [ ... ], filters2 = [ ... ], ... + * } + * ... + * } + * } + * ... + * } + *\endcode + * + * Either a `filename` or a `filters` array can be specified. The configuration + * will be used for all channels. Alternatively `filenameX` or `filtersX` where + * X is the channel number (between 1 and 8) can be used to load a channel + * specific configuration. + * + * The `filename` must point to a parametric equalizer configuration + * generated from the AutoEQ project or Squiglink. Both the projects allow + * equalizing headphones or an in-ear monitor to a target curve. + * + * A popular example of the above being EQ'ing to the Harman target curve + * or EQ'ing one headphone/IEM to another. + * + * For AutoEQ, see https://github.com/jaakkopasanen/AutoEq. + * For SquigLink, see https://squig.link/. + * + * Parametric equalizer configuration generated from AutoEQ or Squiglink looks + * like below. + * + * \code{.unparsed} + * Preamp: -6.8 dB + * Filter 1: ON PK Fc 21 Hz Gain 6.7 dB Q 1.100 + * Filter 2: ON PK Fc 85 Hz Gain 6.9 dB Q 3.000 + * Filter 3: ON PK Fc 110 Hz Gain -2.6 dB Q 2.700 + * Filter 4: ON PK Fc 210 Hz Gain 5.9 dB Q 2.100 + * Filter 5: ON PK Fc 710 Hz Gain -1.0 dB Q 0.600 + * Filter 6: ON PK Fc 1600 Hz Gain 2.3 dB Q 2.700 + * \endcode + * + * Fc, Gain and Q specify the frequency, gain and Q factor respectively. + * The fourth column can be one of PK, LSC or HSC specifying peaking, low + * shelf and high shelf filter respectively. More often than not only peaking + * filters are involved. + * + * The `filters` (or channel specific `filtersX` where X is the channel between 1 and + * 8) can contain an array of filter specification object with the following keys: + * + * `type` specifies the filter type, choose one from the available biquad labels. + * `freq` is the frequency passed to the biquad. + * `gain` is the gain passed to the biquad. + * `q` is the Q passed to the biquad. + * + * This makes it possible to also use the param eq without a file and with all the + * available biquads. + * * ### Convolver * * The convolver can be used to apply an impulse response to a signal. It is usually used @@ -271,7 +343,9 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); * computed automatically from the number of samples in the file. * - `tailsize` specifies the size of the tail blocks to use in the FFT. * - `gain` the overall gain to apply to the IR file. - * - `delay` The extra delay (in samples) to add to the IR. + * - `delay` The extra delay to add to the IR. A float number will be interpreted as seconds, + * and integer as samples. Using the delay in seconds is independent of the graph + * and IR rate and is recommended. * - `filename` The IR to load or create. Possible values are: * - `/hilbert` creates a [hilbert function](https://en.wikipedia.org/wiki/Hilbert_transform) * that can be used to phase shift the signal by +/-90 degrees. The @@ -353,6 +427,18 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); * It has an input port "In" and an output port "Out". It also has a "Control" * and "Notify" port for the control values. * + * ### Abs + * + * The abs plugin can be used to calculate the absolute value of samples. + * + * It has an input port "In" and an output port "Out". + * + * ### Sqrt + * + * The sqrt plugin can be used to calculate the square root of samples. + * + * It has an input port "In" and an output port "Out". + * * ### Exp * * The exp plugin can be used to calculate the exponential (base^x) of samples @@ -392,6 +478,34 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); * "Freq", "Ampl", "Offset" and "Phase" can be used to control the sine wave * frequency, amplitude, offset and phase. * + * ### Max + * + * Use the `max` plugin if you need to select the max value of two channels. + * + * It has two input ports "In 1" and "In 2" and one output port "Out". + * + * ### dcblock + * + * Use the `dcblock` plugin implements a [DC blocker](https://www.dsprelated.com/freebooks/filters/DC_Blocker.html). + * + * It has 8 input ports "In 1" to "In 8" and corresponding output ports "Out 1" + * to "Out 8". Not all ports need to be connected. + * + * It also has 1 control input port "R" that controls the DC block R factor. + * + * ### Ramp + * + * Use the `ramp` plugin creates a linear ramp from `Start` to `Stop`. + * + * It has 3 input control ports "Start", "Stop" and "Duration (s)". It also has one + * output port "Out". A linear ramp will be created from "Start" to "Stop" for a duration + * given by the "Duration (s)" control in (fractional) seconds. The current value will + * be stored in the output notify port "Current". + * + * The ramp output can, for example, be used as input for the `mult` plugin to create + * a volume ramp up or down. For more a more coarse volume ramp, the "Current" value + * can be used in the `linear` plugin. + * * ## SOFA filter * * There is an optional builtin SOFA filter available. @@ -447,6 +561,84 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); * - `Radius` controls how far away the signal is as a value between 0 and 100. * default is 1.0. * + * ## EBUR128 filter + * + * There is an optional EBU R128 filter available. + * + * ### ebur128 + * + * The ebur128 plugin can be used to measure the loudness of a signal. + * + * It has 7 input ports "In FL", "In FR", "In FC", "In UNUSED", "In SL", "In SR" + * and "In DUAL MONO", corresponding to the different input channels for EBUR128. + * Not all ports need to be connected for this filter. + * + * The input signal is passed unmodified on the "Out FL", "Out FR", "Out FC", + * "Out UNUSED", "Out SL", "Out SR" and "Out DUAL MONO" output ports. + * + * There are 7 output control ports that contain the measured loudness information + * and that can be used to control the processing of the audio. Some of these ports + * contain values in LUFS, or "Loudness Units relative to Full Scale". These are + * negative values, closer to 0 is louder. You can use the lufs2gain plugin to + * convert this value to again to adjust a volume (See below). + * + * "Momentary LUFS" contains the momentary loudness measurement with a 400ms window + * and 75% overlap. It works mostly like an R.M.S. meter. + * + * "Shortterm LUFS" contains the shortterm loudness in LUFS over a 3 second window. + * + * "Global LUFS" contains the global integrated loudness in LUFS over the max-history + * window. + * "Window LUFS" contains the global integrated loudness in LUFS over the max-window + * window. + * + * "Range LU" contains the loudness range (LRA) in LU units. + * + * "Peak" contains the peak loudness. + * + * "True Peak" contains the true peak loudness oversampling the signal. This can more + * accurately reflect the peak compared to "Peak". + * + * The node also has an optional `config` section with extra configuration: + * + *\code{.unparsed} + * filter.graph = { + * nodes = [ + * { + * type = ebur128 + * name = ... + * label = ebur128 + * config = { + * max-history = ... + * max-window = ... + * use-histogram = ... + * } + * ... + * } + * } + * ... + * } + *\endcode + * + * - `max-history` the maximum history to keep in (float) seconds. Default to 10.0 + * + * - `max-window` the maximum window to keep in (float) seconds. Default to 0.0 + * You will need to set this to some value to get "Window LUFS" + * output control values. + * + * - `use-histogram` uses the histogram algorithm to calculate loudness. Defaults + * to false. + * + * ### lufs2gain + * + * The lufs2gain plugin can be used to convert LUFS control values to gain. It needs + * a target LUFS control input to drive the conversion. + * + * It has 2 input control ports "LUFS" and "Target LUFS" and will produce 1 output + * control value "Gain". This gain can be used as input for the builtin `linear` + * node, for example, to adust the gain. + * + * * ## General options * * Options with well-known behavior. Most options can be added to the global @@ -615,149 +807,8 @@ static const struct spa_dict_item module_props[] = { #include <pipewire/pipewire.h> -#define MAX_HNDL 64 - #define DEFAULT_RATE 48000 -struct fc_plugin *load_ladspa_plugin(const struct spa_support *support, uint32_t n_support, - struct dsp_ops *dsp, const char *path, const struct spa_dict *info); -struct fc_plugin *load_builtin_plugin(const struct spa_support *support, uint32_t n_support, - struct dsp_ops *dsp, const char *path, const struct spa_dict *info); - -struct plugin { - struct spa_list link; - int ref; - char type[256]; - char path[PATH_MAX]; - - struct fc_plugin *plugin; - struct spa_list descriptor_list; -}; - -struct plugin_func { - struct spa_list link; - char type[256]; - fc_plugin_load_func *func; - void *hndl; -}; - -struct descriptor { - struct spa_list link; - int ref; - struct plugin *plugin; - char label[256]; - - const struct fc_descriptor *desc; - - uint32_t n_input; - uint32_t n_output; - uint32_t n_control; - uint32_t n_notify; - unsigned long *input; - unsigned long *output; - unsigned long *control; - unsigned long *notify; - float *default_control; -}; - -struct port { - struct spa_list link; - struct node *node; - - uint32_t idx; - unsigned long p; - - struct spa_list link_list; - uint32_t n_links; - uint32_t external; - - float control_data[MAX_HNDL]; - float *audio_data[MAX_HNDL]; -}; - -struct node { - struct spa_list link; - struct graph *graph; - - struct descriptor *desc; - - char name[256]; - char *config; - - struct port *input_port; - struct port *output_port; - struct port *control_port; - struct port *notify_port; - - uint32_t n_hndl; - void *hndl[MAX_HNDL]; - - unsigned int n_deps; - unsigned int visited:1; - unsigned int disabled:1; - unsigned int control_changed:1; -}; - -struct link { - struct spa_list link; - - struct spa_list input_link; - struct spa_list output_link; - - struct port *output; - struct port *input; -}; - -struct graph_port { - const struct fc_descriptor *desc; - void **hndl; - uint32_t port; - unsigned next:1; -}; - -struct graph_hndl { - const struct fc_descriptor *desc; - void **hndl; -}; - -struct volume { - bool mute; - uint32_t n_volumes; - float volumes[SPA_AUDIO_MAX_CHANNELS]; - - uint32_t n_ports; - struct port *ports[SPA_AUDIO_MAX_CHANNELS]; - float min[SPA_AUDIO_MAX_CHANNELS]; - float max[SPA_AUDIO_MAX_CHANNELS]; -#define SCALE_LINEAR 0 -#define SCALE_CUBIC 1 - int scale[SPA_AUDIO_MAX_CHANNELS]; -}; - -struct graph { - struct impl *impl; - - struct spa_list node_list; - struct spa_list link_list; - - uint32_t n_input; - struct graph_port *input; - - uint32_t n_output; - struct graph_port *output; - - uint32_t n_hndl; - struct graph_hndl *hndl; - - uint32_t n_control; - struct port **control_port; - - struct volume capture_volume; - struct volume playback_volume; - - unsigned instantiated:1; -}; - struct impl { struct pw_context *context; @@ -770,12 +821,6 @@ struct impl { struct spa_hook core_proxy_listener; struct spa_hook core_listener; - uint32_t quantum_limit; - struct dsp_ops dsp; - - struct spa_list plugin_list; - struct spa_list plugin_func_list; - struct pw_properties *capture_props; struct pw_stream *capture; struct spa_hook capture_listener; @@ -794,16 +839,13 @@ struct impl { long unsigned rate; - struct graph graph; - - float *silence_data; - float *discard_data; + struct spa_handle *handle; + struct spa_filter_graph *graph; + struct spa_hook graph_listener; + uint32_t n_inputs; + uint32_t n_outputs; }; -static int graph_instantiate(struct graph *graph); -static void graph_cleanup(struct graph *graph); - - static void capture_destroy(void *d) { struct impl *impl = d; @@ -814,18 +856,27 @@ static void capture_destroy(void *d) static void capture_process(void *d) { struct impl *impl = d; - pw_stream_trigger_process(impl->playback); + int res; + if ((res = pw_stream_trigger_process(impl->playback)) < 0) { + pw_log_debug("playback trigger error: %s", spa_strerror(res)); + while (true) { + struct pw_buffer *t; + if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL) + break; + pw_stream_queue_buffer(impl->capture, t); + } + } } static void playback_process(void *d) { struct impl *impl = d; struct pw_buffer *in, *out; - struct graph *graph = &impl->graph; - uint32_t i, j, insize = 0, outsize = 0, n_hndl = graph->n_hndl; + uint32_t i, data_size = 0; int32_t stride = 0; - struct graph_port *port; struct spa_data *bd; + const void *cin[128]; + void *cout[128]; in = NULL; while (true) { @@ -845,7 +896,7 @@ static void playback_process(void *d) if (in == NULL || out == NULL) goto done; - for (i = 0, j = 0; i < in->buffer->n_datas; i++) { + for (i = 0; i < in->buffer->n_datas; i++) { uint32_t offs, size; bd = &in->buffer->datas[i]; @@ -853,44 +904,32 @@ static void playback_process(void *d) offs = SPA_MIN(bd->chunk->offset, bd->maxsize); size = SPA_MIN(bd->chunk->size, bd->maxsize - offs); - while (j < graph->n_input) { - port = &graph->input[j++]; - if (port->desc) - port->desc->connect_port(*port->hndl, port->port, - SPA_PTROFF(bd->data, offs, void)); - if (!port->next) - break; + cin[i] = SPA_PTROFF(bd->data, offs, void); - } - insize = i == 0 ? size : SPA_MIN(insize, size); + data_size = i == 0 ? size : SPA_MIN(data_size, size); stride = SPA_MAX(stride, bd->chunk->stride); } - outsize = insize; + for (; i < impl->n_inputs; i++) + cin[i] = NULL; for (i = 0; i < out->buffer->n_datas; i++) { bd = &out->buffer->datas[i]; - outsize = SPA_MIN(outsize, bd->maxsize); + data_size = SPA_MIN(data_size, 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(bd->data, 0, outsize); + cout[i] = bd->data; bd->chunk->offset = 0; - bd->chunk->size = outsize; + bd->chunk->size = data_size; bd->chunk->stride = stride; } + for (; i < impl->n_outputs; i++) + cout[i] = NULL; - pw_log_trace_fp("%p: stride:%d in:%d out:%d requested:%"PRIu64" (%"PRIu64")", impl, - stride, insize, outsize, out->requested, out->requested * stride); + pw_log_trace_fp("%p: stride:%d size:%d requested:%"PRIu64" (%"PRIu64")", impl, + stride, data_size, 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)); - } + spa_filter_graph_process(impl->graph, cin, cout, data_size / sizeof(float)); done: if (in != NULL) @@ -899,441 +938,6 @@ done: pw_stream_queue_buffer(impl->playback, out); } -static float get_default(struct impl *impl, struct descriptor *desc, uint32_t p) -{ - struct fc_port *port = &desc->desc->ports[p]; - return port->def; -} - -static struct node *find_node(struct graph *graph, const char *name) -{ - struct node *node; - spa_list_for_each(node, &graph->node_list, link) { - if (spa_streq(node->name, name)) - return node; - } - return NULL; -} - -/* find a port by name. Valid syntax is: - * "<node_name>:<port_name>" - * "<node_name>:<port_id>" - * "<port_name>" - * "<port_id>" - * When no node_name is given, the port is assumed in the current node. */ -static struct port *find_port(struct node *node, const char *name, int descriptor) -{ - char *col, *node_name, *port_name, *str; - struct port *ports; - const struct fc_descriptor *d; - uint32_t i, n_ports, port_id = SPA_ID_INVALID; - - str = strdupa(name); - col = strchr(str, ':'); - if (col != NULL) { - struct node *find; - node_name = str; - port_name = col + 1; - *col = '\0'; - 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; - } - if (node == NULL) - return NULL; - - if (!spa_atou32(port_name, &port_id, 0)) - port_id = SPA_ID_INVALID; - - if (FC_IS_PORT_INPUT(descriptor)) { - if (FC_IS_PORT_CONTROL(descriptor)) { - ports = node->control_port; - n_ports = node->desc->n_control; - } else { - ports = node->input_port; - n_ports = node->desc->n_input; - } - } else if (FC_IS_PORT_OUTPUT(descriptor)) { - if (FC_IS_PORT_CONTROL(descriptor)) { - ports = node->notify_port; - n_ports = node->desc->n_notify; - } else { - ports = node->output_port; - n_ports = node->desc->n_output; - } - } else - return NULL; - - d = node->desc->desc; - for (i = 0; i < n_ports; i++) { - struct port *port = &ports[i]; - if (i == port_id || - spa_streq(d->ports[port->p].name, port_name)) - return port; - } - return NULL; -} - -static struct spa_pod *get_prop_info(struct graph *graph, struct spa_pod_builder *b, uint32_t idx) -{ - struct impl *impl = graph->impl; - struct spa_pod_frame f[2]; - struct port *port = graph->control_port[idx]; - struct node *node = port->node; - struct descriptor *desc = node->desc; - const struct fc_descriptor *d = desc->desc; - struct fc_port *p = &d->ports[port->p]; - float def, min, max; - char name[512]; - uint32_t rate = impl->rate ? impl->rate : DEFAULT_RATE; - - if (p->hint & FC_HINT_SAMPLE_RATE) { - def = p->def * rate; - min = p->min * rate; - max = p->max * rate; - } else { - def = p->def; - min = p->min; - max = p->max; - } - - if (node->name[0] != '\0') - snprintf(name, sizeof(name), "%s:%s", node->name, p->name); - else - snprintf(name, sizeof(name), "%s", p->name); - - spa_pod_builder_push_object(b, &f[0], - SPA_TYPE_OBJECT_PropInfo, SPA_PARAM_PropInfo); - spa_pod_builder_add (b, - SPA_PROP_INFO_name, SPA_POD_String(name), - 0); - spa_pod_builder_prop(b, SPA_PROP_INFO_type, 0); - if (p->hint & FC_HINT_BOOLEAN) { - if (min == max) { - spa_pod_builder_bool(b, def <= 0.0f ? false : true); - } else { - spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0); - spa_pod_builder_bool(b, def <= 0.0f ? false : true); - spa_pod_builder_bool(b, false); - spa_pod_builder_bool(b, true); - spa_pod_builder_pop(b, &f[1]); - } - } else if (p->hint & FC_HINT_INTEGER) { - if (min == max) { - spa_pod_builder_int(b, (int32_t)def); - } else { - spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Range, 0); - spa_pod_builder_int(b, (int32_t)def); - spa_pod_builder_int(b, (int32_t)min); - spa_pod_builder_int(b, (int32_t)max); - spa_pod_builder_pop(b, &f[1]); - } - } else { - if (min == max) { - spa_pod_builder_float(b, def); - } else { - spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Range, 0); - spa_pod_builder_float(b, def); - spa_pod_builder_float(b, min); - spa_pod_builder_float(b, max); - spa_pod_builder_pop(b, &f[1]); - } - } - spa_pod_builder_prop(b, SPA_PROP_INFO_params, 0); - spa_pod_builder_bool(b, true); - return spa_pod_builder_pop(b, &f[0]); -} - -static struct spa_pod *get_props_param(struct graph *graph, struct spa_pod_builder *b) -{ - struct spa_pod_frame f[2]; - uint32_t i; - char name[512]; - - spa_pod_builder_push_object(b, &f[0], - SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); - spa_pod_builder_prop(b, SPA_PROP_params, 0); - spa_pod_builder_push_struct(b, &f[1]); - - for (i = 0; i < graph->n_control; i++) { - struct port *port = graph->control_port[i]; - struct node *node = port->node; - struct descriptor *desc = node->desc; - const struct fc_descriptor *d = desc->desc; - struct fc_port *p = &d->ports[port->p]; - - if (node->name[0] != '\0') - snprintf(name, sizeof(name), "%s:%s", node->name, p->name); - else - snprintf(name, sizeof(name), "%s", p->name); - - spa_pod_builder_string(b, name); - if (p->hint & FC_HINT_BOOLEAN) { - spa_pod_builder_bool(b, port->control_data[0] <= 0.0f ? false : true); - } else if (p->hint & FC_HINT_INTEGER) { - spa_pod_builder_int(b, (int32_t)port->control_data[0]); - } else { - spa_pod_builder_float(b, port->control_data[0]); - } - } - spa_pod_builder_pop(b, &f[1]); - return spa_pod_builder_pop(b, &f[0]); -} - -static int port_set_control_value(struct port *port, float *value, uint32_t id) -{ - struct node *node = port->node; - struct descriptor *desc = node->desc; - float old; - bool changed; - - old = port->control_data[id]; - port->control_data[id] = value ? *value : desc->default_control[port->idx]; - pw_log_info("control %d %d ('%s') from %f to %f", port->idx, id, - desc->desc->ports[port->p].name, old, port->control_data[id]); - changed = old != port->control_data[id]; - node->control_changed |= changed; - return changed ? 1 : 0; -} - -static int set_control_value(struct node *node, const char *name, float *value) -{ - struct port *port; - int count = 0; - uint32_t i, n_hndl; - - port = find_port(node, name, FC_PORT_INPUT | FC_PORT_CONTROL); - if (port == NULL) - return -ENOENT; - - /* if we don't have any instances yet, set the first control value, we will - * copy to other instances later */ - n_hndl = SPA_MAX(1u, port->node->n_hndl); - for (i = 0; i < n_hndl; i++) - count += port_set_control_value(port, value, i); - - return count; -} - -static int parse_params(struct graph *graph, const struct spa_pod *pod) -{ - struct spa_pod_parser prs; - struct spa_pod_frame f; - int res, changed = 0; - struct node *def_node; - - def_node = spa_list_first(&graph->node_list, struct node, link); - - spa_pod_parser_pod(&prs, pod); - if (spa_pod_parser_push_struct(&prs, &f) < 0) - return 0; - - while (true) { - const char *name; - float value, *val = NULL; - double dbl_val; - bool bool_val; - int32_t int_val; - - if (spa_pod_parser_get_string(&prs, &name) < 0) - break; - if (spa_pod_parser_get_float(&prs, &value) >= 0) { - val = &value; - } else if (spa_pod_parser_get_double(&prs, &dbl_val) >= 0) { - value = (float)dbl_val; - val = &value; - } else if (spa_pod_parser_get_int(&prs, &int_val) >= 0) { - value = int_val; - val = &value; - } else if (spa_pod_parser_get_bool(&prs, &bool_val) >= 0) { - value = bool_val ? 1.0f : 0.0f; - val = &value; - } else { - struct spa_pod *pod; - spa_pod_parser_get_pod(&prs, &pod); - } - if ((res = set_control_value(def_node, name, val)) > 0) - changed += res; - } - return changed; -} - -static void graph_reset(struct graph *graph) -{ - uint32_t i; - 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); - if (d->activate) - d->activate(*hndl->hndl); - } -} - -static void node_control_changed(struct node *node) -{ - const struct fc_descriptor *d = node->desc->desc; - uint32_t i; - - if (!node->control_changed) - return; - - for (i = 0; i < node->n_hndl; i++) { - if (node->hndl[i] == NULL) - continue; - if (d->control_changed) - d->control_changed(node->hndl[i]); - } - node->control_changed = false; -} - -static void update_props_param(struct impl *impl) -{ - struct graph *graph = &impl->graph; - uint8_t buffer[1024]; - struct spa_pod_dynamic_builder b; - const struct spa_pod *params[1]; - - spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); - params[0] = get_props_param(graph, &b.b); - - pw_stream_update_params(impl->capture, params, 1); - spa_pod_dynamic_builder_clean(&b); -} - -static int sync_volume(struct graph *graph, struct volume *vol) -{ - uint32_t i; - int res = 0; - - if (vol->n_ports == 0) - return 0; - for (i = 0; i < vol->n_volumes; i++) { - uint32_t n_port = i % vol->n_ports, n_hndl; - struct port *p = vol->ports[n_port]; - float v = vol->mute ? 0.0f : vol->volumes[i]; - switch (vol->scale[n_port]) { - case SCALE_CUBIC: - v = cbrtf(v); - break; - } - v = v * (vol->max[n_port] - vol->min[n_port]) + vol->min[n_port]; - - n_hndl = SPA_MAX(1u, p->node->n_hndl); - res += port_set_control_value(p, &v, i % n_hndl); - } - return res; -} - -static void param_props_changed(struct impl *impl, const struct spa_pod *param, - bool capture) -{ - struct spa_pod_object *obj = (struct spa_pod_object *) param; - struct spa_pod_frame f[1]; - const struct spa_pod_prop *prop; - struct graph *graph = &impl->graph; - int changed = 0; - char buf[1024]; - struct spa_pod_dynamic_builder b; - struct volume *vol = capture ? &graph->capture_volume : - &graph->playback_volume; - bool do_volume = false; - - spa_pod_dynamic_builder_init(&b, buf, sizeof(buf), 1024); - spa_pod_builder_push_object(&b.b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); - - SPA_POD_OBJECT_FOREACH(obj, prop) { - switch (prop->key) { - case SPA_PROP_params: - changed += parse_params(graph, &prop->value); - spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop)); - break; - case SPA_PROP_mute: - { - bool mute; - if (spa_pod_get_bool(&prop->value, &mute) == 0) { - if (vol->mute != mute) { - vol->mute = mute; - do_volume = true; - } - } - spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop)); - break; - } - case SPA_PROP_channelVolumes: - { - uint32_t i, n_vols; - float vols[SPA_AUDIO_MAX_CHANNELS]; - - if ((n_vols = spa_pod_copy_array(&prop->value, SPA_TYPE_Float, vols, - SPA_AUDIO_MAX_CHANNELS)) > 0) { - if (vol->n_volumes != n_vols) - do_volume = true; - vol->n_volumes = n_vols; - for (i = 0; i < n_vols; i++) { - float v = vols[i]; - if (v != vol->volumes[i]) { - vol->volumes[i] = v; - do_volume = true; - } - } - } - spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop)); - break; - } - case SPA_PROP_softVolumes: - case SPA_PROP_softMute: - break; - default: - spa_pod_builder_raw_padded(&b.b, prop, SPA_POD_PROP_SIZE(prop)); - break; - } - } - if (do_volume && vol->n_ports != 0) { - float soft_vols[SPA_AUDIO_MAX_CHANNELS]; - uint32_t i; - - for (i = 0; i < vol->n_volumes; i++) - soft_vols[i] = (vol->mute || vol->volumes[i] == 0.0f) ? 0.0f : 1.0f; - - spa_pod_builder_prop(&b.b, SPA_PROP_softMute, 0); - spa_pod_builder_bool(&b.b, vol->mute); - spa_pod_builder_prop(&b.b, SPA_PROP_softVolumes, 0); - spa_pod_builder_array(&b.b, sizeof(float), SPA_TYPE_Float, - vol->n_volumes, soft_vols); - param = spa_pod_builder_pop(&b.b, &f[0]); - - sync_volume(graph, vol); - pw_stream_set_param(capture ? impl->capture : - impl->playback, SPA_PARAM_Props, param); - } - - spa_pod_dynamic_builder_clean(&b); - - if (changed > 0) { - struct node *node; - - spa_list_for_each(node, &graph->node_list, link) - node_control_changed(node); - - update_props_param(impl); - } - -} - static void param_latency_changed(struct impl *impl, const struct spa_pod *param) { struct spa_latency_info latency; @@ -1372,14 +976,14 @@ static void state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) { struct impl *impl = data; - struct graph *graph = &impl->graph; + struct spa_filter_graph *graph = impl->graph; int res; switch (state) { case PW_STREAM_STATE_PAUSED: pw_stream_flush(impl->playback, false); pw_stream_flush(impl->capture, false); - graph_reset(graph); + spa_filter_graph_reset(graph); break; case PW_STREAM_STATE_UNCONNECTED: pw_log_info("module %p: unconnected", impl); @@ -1399,9 +1003,13 @@ static void state_changed(void *data, enum pw_stream_state old, goto error; } if (impl->rate != target) { + char rate[64]; impl->rate = target; - graph_cleanup(graph); - if ((res = graph_instantiate(graph)) < 0) + snprintf(rate, sizeof(rate), "%lu", impl->rate); + spa_filter_graph_deactivate(graph); + if ((res = spa_filter_graph_activate(graph, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, rate)))) < 0) goto error; } break; @@ -1431,7 +1039,7 @@ static void param_changed(void *data, uint32_t id, const struct spa_pod *param, bool capture) { struct impl *impl = data; - struct graph *graph = &impl->graph; + struct spa_filter_graph *graph = impl->graph; int res; switch (id) { @@ -1440,7 +1048,7 @@ static void param_changed(void *data, uint32_t id, const struct spa_pod *param, struct spa_audio_info_raw info; spa_zero(info); if (param == NULL) { - graph_cleanup(graph); + spa_filter_graph_deactivate(graph); impl->rate = 0; } else { if ((res = spa_format_audio_raw_parse(param, &info)) < 0) @@ -1451,7 +1059,9 @@ static void param_changed(void *data, uint32_t id, const struct spa_pod *param, } case SPA_PARAM_Props: if (param != NULL) - param_props_changed(impl, param, capture); + spa_filter_graph_set_props(impl->graph, + capture ? SPA_DIRECTION_INPUT : SPA_DIRECTION_OUTPUT, param); + break; case SPA_PARAM_Latency: param_latency_changed(impl, param); @@ -1509,7 +1119,7 @@ static int setup_streams(struct impl *impl) struct pw_array offsets; const struct spa_pod **params = NULL; struct spa_pod_dynamic_builder b; - struct graph *graph = &impl->graph; + struct spa_filter_graph *graph = impl->graph; impl->capture = pw_stream_new(impl->core, "filter capture", impl->capture_props); @@ -1542,15 +1152,16 @@ static int setup_streams(struct impl *impl) spa_format_audio_raw_build(&b.b, SPA_PARAM_EnumFormat, &impl->capture_info); - for (i = 0; i < graph->n_control; i++) { + for (i = 0;; i++) { if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL) *offs = b.b.state.offset; - get_prop_info(graph, &b.b, i); + if (spa_filter_graph_enum_prop_info(graph, i, &b.b, NULL) != 1) + break; } if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL) *offs = b.b.state.offset; - get_props_param(graph, &b.b); + spa_filter_graph_get_props(graph, &b.b, NULL); n_params = pw_array_get_len(&offsets, uint32_t); if (n_params == 0) { @@ -1601,1256 +1212,62 @@ done: return res < 0 ? res : 0; } -static uint32_t count_array(struct spa_json *json) +static void copy_position(struct spa_audio_info_raw *dst, const struct spa_audio_info_raw *src) { - struct spa_json it = *json; - char v[256]; - uint32_t count = 0; - while (spa_json_get_string(&it, v, sizeof(v)) > 0) - count++; - return count; -} - -static void plugin_unref(struct plugin *hndl) -{ - if (--hndl->ref > 0) - return; - - fc_plugin_free(hndl->plugin); - - spa_list_remove(&hndl->link); - free(hndl); -} - - -static struct plugin_func *add_plugin_func(struct impl *impl, const char *type, - fc_plugin_load_func *func, void *hndl) -{ - struct plugin_func *pl; - - pl = calloc(1, sizeof(*pl)); - if (pl == NULL) - return NULL; - - snprintf(pl->type, sizeof(pl->type), "%s", type); - pl->func = func; - pl->hndl = hndl; - spa_list_append(&impl->plugin_func_list, &pl->link); - return pl; -} - -static void free_plugin_func(struct plugin_func *pl) -{ - spa_list_remove(&pl->link); - if (pl->hndl) - dlclose(pl->hndl); - free(pl); -} - -static fc_plugin_load_func *find_plugin_func(struct impl *impl, const char *type) -{ - fc_plugin_load_func *func = NULL; - void *hndl = NULL; - int res; - struct plugin_func *pl; - char module[PATH_MAX]; - const char *module_dir; - const char *state = NULL, *p; - size_t len; - - spa_list_for_each(pl, &impl->plugin_func_list, link) { - if (spa_streq(pl->type, type)) - return pl->func; - } - module_dir = getenv("PIPEWIRE_MODULE_DIR"); - if (module_dir == NULL) - module_dir = MODULEDIR; - pw_log_debug("moduledir set to: %s", module_dir); - - while ((p = pw_split_walk(module_dir, ":", &len, &state))) { - if ((res = spa_scnprintf(module, sizeof(module), - "%.*s/libpipewire-module-filter-chain-%s.so", - (int)len, p, type)) <= 0) - continue; - - hndl = dlopen(module, RTLD_NOW | RTLD_LOCAL); - if (hndl != NULL) - break; - - pw_log_debug("open plugin module %s failed: %s", module, dlerror()); - } - if (hndl == NULL) { - errno = ENOENT; - return NULL; - } - func = dlsym(hndl, FC_PLUGIN_LOAD_FUNC); - if (func != NULL) { - pw_log_info("opened plugin module %s", module); - pl = add_plugin_func(impl, type, func, hndl); - if (pl == NULL) - goto error_close; - } else { - errno = ENOSYS; - pw_log_error("%s is not a filter chain plugin: %m", module); - goto error_close; - } - return func; - -error_close: - dlclose(hndl); - return NULL; -} - -static struct plugin *plugin_load(struct impl *impl, const char *type, const char *path) -{ - struct fc_plugin *pl = NULL; - struct plugin *hndl; - const struct spa_support *support; - uint32_t n_support; - fc_plugin_load_func *plugin_func; - - spa_list_for_each(hndl, &impl->plugin_list, link) { - if (spa_streq(hndl->type, type) && - spa_streq(hndl->path, path)) { - hndl->ref++; - return hndl; - } - } - support = pw_context_get_support(impl->context, &n_support); - - plugin_func = find_plugin_func(impl, type); - if (plugin_func == NULL) { - pw_log_error("can't load plugin type '%s': %m", type); - pl = NULL; - } else { - pl = plugin_func(support, n_support, &impl->dsp, path, &impl->props->dict); - } - if (pl == NULL) - goto exit; - - hndl = calloc(1, sizeof(*hndl)); - if (!hndl) - return NULL; - - hndl->ref = 1; - snprintf(hndl->type, sizeof(hndl->type), "%s", type); - snprintf(hndl->path, sizeof(hndl->path), "%s", path); - - pw_log_info("successfully opened '%s':'%s'", type, path); - - hndl->plugin = pl; - - spa_list_init(&hndl->descriptor_list); - spa_list_append(&impl->plugin_list, &hndl->link); - - return hndl; -exit: - return NULL; -} - -static void descriptor_unref(struct descriptor *desc) -{ - if (--desc->ref > 0) - return; - - spa_list_remove(&desc->link); - plugin_unref(desc->plugin); - if (desc->desc) - fc_descriptor_free(desc->desc); - free(desc->input); - free(desc->output); - free(desc->control); - free(desc->default_control); - free(desc->notify); - free(desc); -} - -static struct descriptor *descriptor_load(struct impl *impl, const char *type, - const char *plugin, const char *label) -{ - struct plugin *hndl; - struct descriptor *desc; - const struct fc_descriptor *d; - uint32_t i, n_input, n_output, n_control, n_notify; - unsigned long p; - int res; - - if ((hndl = plugin_load(impl, type, plugin)) == NULL) - return NULL; - - spa_list_for_each(desc, &hndl->descriptor_list, link) { - if (spa_streq(desc->label, label)) { - desc->ref++; - - /* - * since ladspa_handle_load() increments the reference count of the handle, - * if the descriptor is found, then the handle's reference count - * has already been incremented to account for the descriptor, - * so we need to unref handle here since we're merely reusing - * thedescriptor, not creating a new one - */ - plugin_unref(hndl); - return desc; - } + if (SPA_FLAG_IS_SET(dst->flags, SPA_AUDIO_FLAG_UNPOSITIONED) && + !SPA_FLAG_IS_SET(src->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) { + for (uint32_t i = 0; i < src->channels; i++) + dst->position[i] = src->position[i]; + SPA_FLAG_CLEAR(dst->flags, SPA_AUDIO_FLAG_UNPOSITIONED); } - - desc = calloc(1, sizeof(*desc)); - desc->ref = 1; - desc->plugin = hndl; - spa_list_init(&desc->link); - - if ((d = hndl->plugin->make_desc(hndl->plugin, label)) == NULL) { - pw_log_error("cannot find label %s", label); - res = -ENOENT; - goto exit; - } - desc->desc = d; - snprintf(desc->label, sizeof(desc->label), "%s", label); - - n_input = n_output = n_control = n_notify = 0; - for (p = 0; p < d->n_ports; p++) { - struct fc_port *fp = &d->ports[p]; - if (FC_IS_PORT_AUDIO(fp->flags)) { - if (FC_IS_PORT_INPUT(fp->flags)) - n_input++; - else if (FC_IS_PORT_OUTPUT(fp->flags)) - n_output++; - } else if (FC_IS_PORT_CONTROL(fp->flags)) { - if (FC_IS_PORT_INPUT(fp->flags)) - n_control++; - else if (FC_IS_PORT_OUTPUT(fp->flags)) - n_notify++; - } - } - desc->input = calloc(n_input, sizeof(unsigned long)); - desc->output = calloc(n_output, sizeof(unsigned long)); - desc->control = calloc(n_control, sizeof(unsigned long)); - desc->default_control = calloc(n_control, sizeof(float)); - desc->notify = calloc(n_notify, sizeof(unsigned long)); - - for (p = 0; p < d->n_ports; p++) { - struct fc_port *fp = &d->ports[p]; - - if (FC_IS_PORT_AUDIO(fp->flags)) { - if (FC_IS_PORT_INPUT(fp->flags)) { - pw_log_info("using port %lu ('%s') as input %d", p, - fp->name, desc->n_input); - desc->input[desc->n_input++] = p; - } - else if (FC_IS_PORT_OUTPUT(fp->flags)) { - pw_log_info("using port %lu ('%s') as output %d", p, - fp->name, desc->n_output); - desc->output[desc->n_output++] = p; - } - } else if (FC_IS_PORT_CONTROL(fp->flags)) { - if (FC_IS_PORT_INPUT(fp->flags)) { - pw_log_info("using port %lu ('%s') as control %d", p, - fp->name, desc->n_control); - desc->control[desc->n_control++] = p; - } - else if (FC_IS_PORT_OUTPUT(fp->flags)) { - pw_log_info("using port %lu ('%s') as notify %d", p, - fp->name, desc->n_notify); - desc->notify[desc->n_notify++] = p; - } - } - } - if (desc->n_input == 0 && desc->n_output == 0) { - pw_log_error("plugin has no input and no output ports"); - res = -ENOTSUP; - goto exit; - } - for (i = 0; i < desc->n_control; i++) { - p = desc->control[i]; - desc->default_control[i] = get_default(impl, desc, p); - pw_log_info("control %d ('%s') default to %f", i, - d->ports[p].name, desc->default_control[i]); - } - spa_list_append(&hndl->descriptor_list, &desc->link); - - return desc; - -exit: - descriptor_unref(desc); - errno = -res; - return NULL; } -/** - * { - * ... - * } - */ -static int parse_config(struct node *node, struct spa_json *config) +static void graph_info(void *object, const struct spa_filter_graph_info *info) { - const char *val, *s = config->cur; - int res = 0, len; - struct spa_error_location loc; - - if ((len = spa_json_next(config, &val)) <= 0) { - res = -EINVAL; - goto done; - } - if (spa_json_is_null(val, len)) - goto done; - - if (spa_json_is_container(val, len)) { - len = spa_json_container_len(config, val, len); - if (len == 0) { - res = -EINVAL; - goto done; - } - } - if ((node->config = malloc(len+1)) == NULL) { - res = -errno; - goto done; - } - - spa_json_parse_stringn(val, len, node->config, len+1); -done: - if (spa_json_get_error(config, s, &loc)) - spa_debug_log_error_location(pw_log_get(), SPA_LOG_LEVEL_WARN, - &loc, "error: %s", loc.reason); - return res; -} - -/** - * { - * "Reverb tail" = 2.0 - * ... - * } - */ -static int parse_control(struct node *node, struct spa_json *control) -{ - char key[256]; - - while (spa_json_get_string(control, key, sizeof(key)) > 0) { - float fl; - const char *val; - int res, len; - - if ((len = spa_json_next(control, &val)) < 0) - break; - - if (spa_json_parse_float(val, len, &fl) <= 0) { - pw_log_warn("control '%s' expects a number, ignoring", key); - } - 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; -} - -/** - * output = [name:][portname] - * input = [name:][portname] - * ... - */ -static int parse_link(struct graph *graph, struct spa_json *json) -{ - char key[256]; - char output[256] = ""; - char input[256] = ""; - const char *val; - struct node *def_in_node, *def_out_node; - struct port *in_port, *out_port; - struct link *link; - - if (spa_list_is_empty(&graph->node_list)) { - pw_log_error("can't make links in graph without nodes"); - return -EINVAL; - } - - while (spa_json_get_string(json, key, sizeof(key)) > 0) { - if (spa_streq(key, "output")) { - if (spa_json_get_string(json, output, sizeof(output)) <= 0) { - pw_log_error("output expects a string"); - return -EINVAL; - } - } - else if (spa_streq(key, "input")) { - if (spa_json_get_string(json, input, sizeof(input)) <= 0) { - pw_log_error("input expects a string"); - return -EINVAL; - } - } - else { - pw_log_error("unexpected link key '%s'", key); - if (spa_json_next(json, &val) < 0) - break; - } - } - def_out_node = spa_list_first(&graph->node_list, struct node, link); - def_in_node = spa_list_last(&graph->node_list, struct node, link); - - out_port = find_port(def_out_node, output, FC_PORT_OUTPUT); - in_port = find_port(def_in_node, input, FC_PORT_INPUT); - - if (out_port == NULL && out_port == NULL) { - /* try control ports */ - out_port = find_port(def_out_node, output, FC_PORT_OUTPUT | FC_PORT_CONTROL); - in_port = find_port(def_in_node, input, FC_PORT_INPUT | FC_PORT_CONTROL); - } - if (in_port == NULL || out_port == NULL) { - if (out_port == NULL) - pw_log_error("unknown output port %s", output); - if (in_port == NULL) - pw_log_error("unknown input port %s", input); - return -ENOENT; - } - - if (in_port->n_links > 0) { - pw_log_info("Can't have more than 1 link to %s, use a mixer", input); - return -ENOTSUP; - } - - if ((link = calloc(1, sizeof(*link))) == NULL) - return -errno; - - link->output = out_port; - link->input = in_port; - - pw_log_info("linking %s:%s -> %s:%s", - out_port->node->name, - out_port->node->desc->desc->ports[out_port->p].name, - in_port->node->name, - in_port->node->desc->desc->ports[in_port->p].name); - - spa_list_append(&out_port->link_list, &link->output_link); - out_port->n_links++; - spa_list_append(&in_port->link_list, &link->input_link); - in_port->n_links++; - - in_port->node->n_deps++; - - spa_list_append(&graph->link_list, &link->link); - - return 0; -} - -static void link_free(struct link *link) -{ - spa_list_remove(&link->input_link); - link->input->n_links--; - link->input->node->n_deps--; - spa_list_remove(&link->output_link); - link->output->n_links--; - spa_list_remove(&link->link); - free(link); -} - -/** - * { - * control = [name:][portname] - * min = <float, default 0.0> - * max = <float, default 1.0> - * scale = <string, default "linear", options "linear","cubic"> - * } - */ -static int parse_volume(struct graph *graph, struct spa_json *json, bool capture) -{ - char key[256]; - char control[256] = ""; - char scale[64] = "linear"; - float min = 0.0f, max = 1.0f; - const char *val; - struct node *def_control; - struct port *port; - struct volume *vol = capture ? &graph->capture_volume : - &graph->playback_volume; - - if (spa_list_is_empty(&graph->node_list)) { - pw_log_error("can't set volume in graph without nodes"); - return -EINVAL; - } - while (spa_json_get_string(json, key, sizeof(key)) > 0) { - if (spa_streq(key, "control")) { - if (spa_json_get_string(json, control, sizeof(control)) <= 0) { - pw_log_error("control expects a string"); - return -EINVAL; - } - } - else if (spa_streq(key, "min")) { - if (spa_json_get_float(json, &min) <= 0) { - pw_log_error("min expects a float"); - return -EINVAL; - } - } - else if (spa_streq(key, "max")) { - if (spa_json_get_float(json, &max) <= 0) { - pw_log_error("max expects a float"); - return -EINVAL; - } - } - else if (spa_streq(key, "scale")) { - if (spa_json_get_string(json, scale, sizeof(scale)) <= 0) { - pw_log_error("scale expects a string"); - return -EINVAL; - } - } - else { - pw_log_error("unexpected volume key '%s'", key); - if (spa_json_next(json, &val) < 0) - break; - } - } - if (capture) - def_control = spa_list_first(&graph->node_list, struct node, link); - else - def_control = spa_list_last(&graph->node_list, struct node, link); - - port = find_port(def_control, control, FC_PORT_INPUT | FC_PORT_CONTROL); - if (port == NULL) { - pw_log_error("unknown control port %s", control); - return -ENOENT; - } - if (vol->n_ports >= SPA_AUDIO_MAX_CHANNELS) { - pw_log_error("too many volume controls"); - return -ENOSPC; - } - if (spa_streq(scale, "linear")) { - vol->scale[vol->n_ports] = SCALE_LINEAR; - } else if (spa_streq(scale, "cubic")) { - vol->scale[vol->n_ports] = SCALE_CUBIC; - } else { - pw_log_error("Invalid scale value '%s', use one of linear or cubic", scale); - return -EINVAL; - } - pw_log_info("volume %d: \"%s:%s\" min:%f max:%f scale:%s", vol->n_ports, port->node->name, - port->node->desc->desc->ports[port->p].name, min, max, scale); - - vol->ports[vol->n_ports] = port; - vol->min[vol->n_ports] = min; - vol->max[vol->n_ports] = max; - vol->n_ports++; - - return 0; -} - -/** - * type = ladspa - * name = rev - * plugin = g2reverb - * label = G2reverb - * config = { - * ... - * } - * control = { - * ... - * } - */ -static int load_node(struct graph *graph, struct spa_json *json) -{ - struct spa_json control, config; - struct descriptor *desc; - struct node *node; - const char *val; - char key[256]; - char type[256] = ""; - char name[256] = ""; - char plugin[256] = ""; - char label[256] = ""; - bool have_control = false; - bool have_config = false; - uint32_t i; - int res; - - while (spa_json_get_string(json, key, sizeof(key)) > 0) { - if (spa_streq("type", key)) { - if (spa_json_get_string(json, type, sizeof(type)) <= 0) { - pw_log_error("type expects a string"); - return -EINVAL; - } - } else if (spa_streq("name", key)) { - if (spa_json_get_string(json, name, sizeof(name)) <= 0) { - pw_log_error("name expects a string"); - return -EINVAL; - } - } else if (spa_streq("plugin", key)) { - if (spa_json_get_string(json, plugin, sizeof(plugin)) <= 0) { - pw_log_error("plugin expects a string"); - return -EINVAL; - } - } else if (spa_streq("label", key)) { - if (spa_json_get_string(json, label, sizeof(label)) <= 0) { - pw_log_error("label expects a string"); - return -EINVAL; - } - } else if (spa_streq("control", key)) { - if (spa_json_enter_object(json, &control) <= 0) { - pw_log_error("control expects an object"); - return -EINVAL; - } - have_control = true; - } else if (spa_streq("config", key)) { - config = SPA_JSON_SAVE(json); - have_config = true; - if (spa_json_next(json, &val) < 0) - break; - } else { - pw_log_warn("unexpected node key '%s'", key); - if (spa_json_next(json, &val) < 0) - break; - } - } - if (spa_streq(type, "builtin")) - snprintf(plugin, sizeof(plugin), "%s", "builtin"); - else if (spa_streq(type, "")) { - pw_log_error("missing plugin type"); - return -EINVAL; - } - - pw_log_info("loading type:%s plugin:%s label:%s", type, plugin, label); - - if ((desc = descriptor_load(graph->impl, type, plugin, label)) == NULL) - return -errno; - - node = calloc(1, sizeof(*node)); - if (node == NULL) - return -errno; - - node->graph = graph; - node->desc = desc; - snprintf(node->name, sizeof(node->name), "%s", name); - - node->input_port = calloc(desc->n_input, sizeof(struct port)); - node->output_port = calloc(desc->n_output, sizeof(struct port)); - node->control_port = calloc(desc->n_control, sizeof(struct port)); - node->notify_port = calloc(desc->n_notify, sizeof(struct port)); - - pw_log_info("loaded n_input:%d n_output:%d n_control:%d n_notify:%d", - desc->n_input, desc->n_output, - desc->n_control, desc->n_notify); - - for (i = 0; i < desc->n_input; i++) { - struct port *port = &node->input_port[i]; - port->node = node; - port->idx = i; - port->external = SPA_ID_INVALID; - port->p = desc->input[i]; - spa_list_init(&port->link_list); - } - for (i = 0; i < desc->n_output; i++) { - struct port *port = &node->output_port[i]; - port->node = node; - port->idx = i; - port->external = SPA_ID_INVALID; - port->p = desc->output[i]; - spa_list_init(&port->link_list); - } - for (i = 0; i < desc->n_control; i++) { - struct port *port = &node->control_port[i]; - port->node = node; - port->idx = i; - port->external = SPA_ID_INVALID; - port->p = desc->control[i]; - spa_list_init(&port->link_list); - port->control_data[0] = desc->default_control[i]; - } - for (i = 0; i < desc->n_notify; i++) { - struct port *port = &node->notify_port[i]; - port->node = node; - port->idx = i; - port->external = SPA_ID_INVALID; - port->p = desc->notify[i]; - spa_list_init(&port->link_list); - } - if (have_config) - if ((res = parse_config(node, &config)) < 0) - pw_log_warn("error parsing config: %s", spa_strerror(res)); - if (have_control) - parse_control(node, &control); - - spa_list_append(&graph->node_list, &node->link); - - return 0; -} - -static void node_cleanup(struct node *node) -{ - const struct fc_descriptor *d = node->desc->desc; - uint32_t i; - - for (i = 0; i < node->n_hndl; i++) { - if (node->hndl[i] == NULL) - continue; - pw_log_info("cleanup %s %d", d->name, i); - if (d->deactivate) - d->deactivate(node->hndl[i]); - d->cleanup(node->hndl[i]); - node->hndl[i] = NULL; - } -} - -static int port_ensure_data(struct port *port, uint32_t i, uint32_t max_samples) -{ - float *data; - if ((data = port->audio_data[i]) == NULL) { - data = calloc(max_samples, sizeof(float)); - if (data == NULL) { - pw_log_error("cannot create port data: %m"); - return -errno; - } - } - port->audio_data[i] = data; - return 0; -} - -static void port_free_data(struct port *port, uint32_t i) -{ - free(port->audio_data[i]); - port->audio_data[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++) - port_free_data(&node->output_port[j], i); - } - node_cleanup(node); - descriptor_unref(node->desc); - free(node->input_port); - free(node->output_port); - free(node->control_port); - free(node->notify_port); - free(node->config); - free(node); -} - -static void graph_cleanup(struct graph *graph) -{ - struct node *node; - if (!graph->instantiated) - return; - graph->instantiated = false; - spa_list_for_each(node, &graph->node_list, link) - node_cleanup(node); -} - -static int graph_instantiate(struct graph *graph) -{ - struct impl *impl = graph->impl; - struct node *node; - struct port *port; - struct link *link; - struct descriptor *desc; - const struct fc_descriptor *d; - const struct fc_plugin *p; - uint32_t i, j, max_samples = impl->quantum_limit; - int res; - float *sd, *dd; - - if (graph->instantiated) - return 0; - - graph->instantiated = true; - - /* first make instances */ - spa_list_for_each(node, &graph->node_list, link) { - node_cleanup(node); - - desc = node->desc; - d = desc->desc; - p = desc->plugin->plugin; - - for (i = 0; i < node->n_hndl; i++) { - pw_log_info("instantiate %s %d rate:%lu", d->name, i, impl->rate); - errno = EINVAL; - if ((node->hndl[i] = d->instantiate(p, d, impl->rate, i, node->config)) == NULL) { - pw_log_error("cannot create plugin instance %d rate:%lu: %m", i, impl->rate); - res = -errno; - goto error; - } - } - } - - /* then link ports and activate */ - spa_list_for_each(node, &graph->node_list, link) { - desc = node->desc; - d = desc->desc; - if (d->flags & FC_DESCRIPTOR_SUPPORTS_NULL_DATA) { - sd = dd = NULL; - } - else { - sd = impl->silence_data; - dd = impl->discard_data; - } - for (i = 0; i < node->n_hndl; i++) { - 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; - if ((res = port_ensure_data(peer, i, max_samples)) < 0) - goto error; - 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]; - if ((res = port_ensure_data(port, i, max_samples)) < 0) - goto error; - 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[i]); - - spa_list_for_each(link, &port->link_list, input_link) { - struct port *peer = link->output; - pw_log_info("connect control port %s[%d]:%s %p", - node->name, i, d->ports[port->p].name, - &peer->control_data[i]); - d->connect_port(node->hndl[i], port->p, &peer->control_data[i]); - } - } - for (j = 0; j < desc->n_notify; j++) { - port = &node->notify_port[j]; - pw_log_info("connect notify port %s[%d]:%s %p", - node->name, i, d->ports[port->p].name, - &port->control_data[i]); - d->connect_port(node->hndl[i], port->p, &port->control_data[i]); - } - if (d->activate) - d->activate(node->hndl[i]); - if (node->control_changed && d->control_changed) - d->control_changed(node->hndl[i]); - } - } - update_props_param(impl); - return 0; -error: - graph_cleanup(graph); - return res; -} - -/* any default values for the controls are set in the first instance - * of the control data. Duplicate this to the other instances now. */ -static void setup_node_controls(struct node *node) -{ - uint32_t i, j; - uint32_t n_hndl = node->n_hndl; - uint32_t n_ports = node->desc->n_control; - struct port *ports = node->control_port; - - for (i = 0; i < n_ports; i++) { - struct port *port = &ports[i]; - for (j = 1; j < n_hndl; j++) - port->control_data[j] = port->control_data[0]; - } -} - -static struct node *find_next_node(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; -} - -static int setup_graph(struct graph *graph, struct spa_json *inputs, struct spa_json *outputs) -{ - 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; - struct descriptor *desc; - const struct fc_descriptor *d; - char v[256]; - - first = spa_list_first(&graph->node_list, struct node, link); - last = spa_list_last(&graph->node_list, struct node, link); - - /* calculate the number of inputs and outputs into the graph. - * If we have a list of inputs/outputs, just count them. Otherwise - * we count all input ports of the first node and all output - * ports of the last node */ - if (inputs != NULL) { - n_input = count_array(inputs); - } else { - n_input = first->desc->n_input; - } - if (outputs != NULL) { - n_output = count_array(outputs); - } else { - n_output = last->desc->n_output; - } - if (n_input == 0) { - pw_log_error("no inputs"); - res = -EINVAL; - goto error; - } - if (n_output == 0) { - pw_log_error("no outputs"); - res = -EINVAL; - goto error; - } - + struct impl *impl = object; if (impl->capture_info.channels == 0) - impl->capture_info.channels = n_input; + impl->capture_info.channels = info->n_inputs; if (impl->playback_info.channels == 0) - impl->playback_info.channels = n_output; - - /* compare to the requested number of channels and duplicate the - * 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. 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. %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) { - node->n_hndl = n_hndl; - desc = node->desc; - n_control += desc->n_control; - n_nodes++; - setup_node_controls(node); - } - graph->n_input = 0; - graph->input = calloc(n_input * 16 * n_hndl, sizeof(struct graph_port)); - graph->n_output = 0; - graph->output = calloc(n_output * n_hndl, sizeof(struct graph_port)); - - /* now collect all input and output ports for all the handles. */ - for (i = 0; i < n_hndl; i++) { - if (inputs == NULL) { - desc = first->desc; - d = desc->desc; - for (j = 0; j < desc->n_input; j++) { - gp = &graph->input[graph->n_input++]; - 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->port = desc->input[j]; - } - } else { - struct spa_json it = *inputs; - while (spa_json_get_string(&it, v, sizeof(v)) > 0) { - if (spa_streq(v, "null")) { - gp = &graph->input[graph->n_input++]; - gp->desc = NULL; - pw_log_info("ignore input port %d", graph->n_input); - } else if ((port = find_port(first, v, FC_PORT_INPUT)) == NULL) { - res = -ENOENT; - pw_log_error("input port %s not found", v); - goto error; - } else { - bool disabled = false; - - desc = port->node->desc; - d = desc->desc; - if (i == 0 && port->external != SPA_ID_INVALID) { - pw_log_error("input port %s[%d]:%s already used as input %d, use mixer", - port->node->name, i, d->ports[port->p].name, - port->external); - res = -EBUSY; - goto error; - } - if (port->n_links > 0) { - pw_log_error("input port %s[%d]:%s already used by link, use mixer", - port->node->name, i, d->ports[port->p].name); - res = -EBUSY; - goto error; - } - - if (d->flags & FC_DESCRIPTOR_COPY) { - for (j = 0; j < desc->n_output; j++) { - struct port *p = &port->node->output_port[j]; - struct link *link; - - gp = NULL; - spa_list_for_each(link, &p->link_list, output_link) { - struct port *peer = link->input; - - pw_log_info("copy input port %s[%d]:%s", - port->node->name, i, - d->ports[port->p].name); - peer->external = graph->n_input; - gp = &graph->input[graph->n_input++]; - gp->desc = peer->node->desc->desc; - gp->hndl = &peer->node->hndl[i]; - gp->port = peer->p; - gp->next = true; - disabled = true; - } - if (gp != NULL) - gp->next = false; - } - port->node->disabled = disabled; - } - if (!disabled) { - pw_log_info("input port %s[%d]:%s", - port->node->name, i, d->ports[port->p].name); - port->external = graph->n_input; - gp = &graph->input[graph->n_input++]; - gp->desc = d; - gp->hndl = &port->node->hndl[i]; - gp->port = port->p; - gp->next = false; - } - } - } - } - if (outputs == NULL) { - desc = last->desc; - d = desc->desc; - for (j = 0; j < desc->n_output; j++) { - gp = &graph->output[graph->n_output++]; - 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->port = desc->output[j]; - } - } else { - struct spa_json it = *outputs; - while (spa_json_get_string(&it, v, sizeof(v)) > 0) { - gp = &graph->output[graph->n_output]; - if (spa_streq(v, "null")) { - gp->desc = NULL; - pw_log_info("silence output port %d", graph->n_output); - } else if ((port = find_port(last, v, FC_PORT_OUTPUT)) == NULL) { - res = -ENOENT; - pw_log_error("output port %s not found", v); - goto error; - } else { - desc = port->node->desc; - d = desc->desc; - if (i == 0 && port->external != SPA_ID_INVALID) { - pw_log_error("output port %s[%d]:%s already used as output %d, use copy", - port->node->name, i, d->ports[port->p].name, - port->external); - res = -EBUSY; - goto error; - } - if (port->n_links > 0) { - pw_log_error("output port %s[%d]:%s already used by link, use copy", - port->node->name, i, d->ports[port->p].name); - res = -EBUSY; - goto error; - } - pw_log_info("output port %s[%d]:%s", - port->node->name, i, d->ports[port->p].name); - port->external = graph->n_output; - gp->desc = d; - gp->hndl = &port->node->hndl[i]; - gp->port = port->p; - } - graph->n_output++; - } - } - } - - /* order all nodes based on dependencies */ - graph->n_hndl = 0; - graph->hndl = calloc(n_nodes * n_hndl, sizeof(struct graph_hndl)); - graph->n_control = 0; - graph->control_port = calloc(n_control, sizeof(struct port *)); - while (true) { - if ((node = find_next_node(graph)) == NULL) - break; + impl->playback_info.channels = info->n_outputs; - desc = node->desc; - d = desc->desc; + impl->n_inputs = info->n_inputs; + impl->n_outputs = info->n_outputs; - if (!node->disabled) { - for (i = 0; i < n_hndl; i++) { - gh = &graph->hndl[graph->n_hndl++]; - gh->hndl = &node->hndl[i]; - gh->desc = d; - } - } - for (i = 0; i < desc->n_output; i++) { - spa_list_for_each(link, &node->output_port[i].link_list, output_link) - link->input->node->n_deps--; - } - for (i = 0; i < desc->n_notify; i++) { - spa_list_for_each(link, &node->notify_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++) { - graph->control_port[graph->n_control] = &node->control_port[i]; - graph->n_control++; - } + if (impl->capture_info.channels == impl->playback_info.channels) { + copy_position(&impl->capture_info, &impl->playback_info); + copy_position(&impl->playback_info, &impl->capture_info); } - res = 0; -error: - return res; } -/** - * filter.graph = { - * nodes = [ - * { ... } ... - * ] - * links = [ - * { ... } ... - * ] - * inputs = [ ] - * outputs = [ ] - * } - */ -static int load_graph(struct graph *graph, struct pw_properties *props) +static void graph_apply_props(void *object, enum spa_direction direction, const struct spa_pod *props) { - struct spa_json it[3]; - struct spa_json inputs, outputs, *pinputs = NULL, *poutputs = NULL; - struct spa_json cvolumes, pvolumes, *pcvolumes = NULL, *ppvolumes = NULL; - struct spa_json nodes, *pnodes = NULL, links, *plinks = NULL; - const char *json, *val; - char key[256]; - int res; - - spa_list_init(&graph->node_list); - spa_list_init(&graph->link_list); + struct impl *impl = object; + pw_stream_set_param(direction == SPA_DIRECTION_INPUT ? + impl->capture : impl->playback, + SPA_PARAM_Props, props); +} - if ((json = pw_properties_get(props, "filter.graph")) == NULL) { - pw_log_error("missing filter.graph property"); - return -EINVAL; - } +static void graph_props_changed(void *object, enum spa_direction direction) +{ + struct impl *impl = object; + struct spa_filter_graph *graph = impl->graph; + uint8_t buffer[1024]; + struct spa_pod_dynamic_builder b; + const struct spa_pod *params[1]; - spa_json_init(&it[0], json, strlen(json)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) { - pw_log_error("filter.graph must be an object"); - return -EINVAL; - } + spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); + spa_filter_graph_get_props(graph, &b.b, (struct spa_pod **)¶ms[0]); - while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { - if (spa_streq("nodes", key)) { - if (spa_json_enter_array(&it[1], &nodes) <= 0) { - pw_log_error("nodes expects an array"); - return -EINVAL; - } - pnodes = &nodes; - } - else if (spa_streq("links", key)) { - if (spa_json_enter_array(&it[1], &links) <= 0) { - pw_log_error("links expects an array"); - return -EINVAL; - } - plinks = &links; - } - else if (spa_streq("inputs", key)) { - if (spa_json_enter_array(&it[1], &inputs) <= 0) { - pw_log_error("inputs expects an array"); - return -EINVAL; - } - pinputs = &inputs; - } - else if (spa_streq("outputs", key)) { - if (spa_json_enter_array(&it[1], &outputs) <= 0) { - pw_log_error("outputs expects an array"); - return -EINVAL; - } - poutputs = &outputs; - } - else if (spa_streq("capture.volumes", key)) { - if (spa_json_enter_array(&it[1], &cvolumes) <= 0) { - pw_log_error("capture.volumes expects an array"); - return -EINVAL; - } - pcvolumes = &cvolumes; - } - else if (spa_streq("playback.volumes", key)) { - if (spa_json_enter_array(&it[1], &pvolumes) <= 0) { - pw_log_error("playback.volumes expects an array"); - return -EINVAL; - } - ppvolumes = &pvolumes; - } else { - pw_log_warn("unexpected graph key '%s'", key); - if (spa_json_next(&it[1], &val) < 0) - break; - } - } - if (pnodes == NULL) { - pw_log_error("filter.graph is missing a nodes array"); - return -EINVAL; - } - while (spa_json_enter_object(pnodes, &it[2]) > 0) { - if ((res = load_node(graph, &it[2])) < 0) - return res; - } - if (plinks != NULL) { - while (spa_json_enter_object(plinks, &it[2]) > 0) { - if ((res = parse_link(graph, &it[2])) < 0) - return res; - } - } - if (pcvolumes != NULL) { - while (spa_json_enter_object(pcvolumes, &it[2]) > 0) { - if ((res = parse_volume(graph, &it[2], true)) < 0) - return res; - } - } - if (ppvolumes != NULL) { - while (spa_json_enter_object(ppvolumes, &it[2]) > 0) { - if ((res = parse_volume(graph, &it[2], false)) < 0) - return res; - } - } - return setup_graph(graph, pinputs, poutputs); + pw_stream_update_params(impl->capture, params, 1); + spa_pod_dynamic_builder_clean(&b); } -static void graph_free(struct graph *graph) -{ - struct link *link; - struct node *node; - spa_list_consume(link, &graph->link_list, link) - link_free(link); - spa_list_consume(node, &graph->node_list, link) - node_free(node); - free(graph->input); - free(graph->output); - free(graph->hndl); - free(graph->control_port); -} +struct spa_filter_graph_events graph_events = { + SPA_VERSION_FILTER_GRAPH_EVENTS, + .info = graph_info, + .apply_props = graph_apply_props, + .props_changed = graph_props_changed, +}; static void core_error(void *data, uint32_t id, int seq, int res, const char *message) { @@ -2887,8 +1304,6 @@ static const struct pw_proxy_events core_proxy_events = { static void impl_destroy(struct impl *impl) { - struct plugin_func *pl; - /* disconnect both streams before destroying any of them */ if (impl->capture) pw_stream_disconnect(impl->capture); @@ -2903,15 +1318,13 @@ static void impl_destroy(struct impl *impl) if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); + if (impl->handle) + pw_unload_spa_handle(impl->handle); + pw_properties_free(impl->capture_props); pw_properties_free(impl->playback_props); - graph_free(&impl->graph); - spa_list_consume(pl, &impl->plugin_func_list, link) - free_plugin_func(pl); pw_properties_free(impl->props); - free(impl->silence_data); - free(impl->discard_data); free(impl); } @@ -2927,43 +1340,14 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { - const char *str; - - *info = SPA_AUDIO_INFO_RAW_INIT( - .format = SPA_AUDIO_FORMAT_F32P); - 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)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), + &props->dict, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) @@ -2981,15 +1365,14 @@ SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); + const struct pw_properties *p; struct pw_properties *props; struct impl *impl; uint32_t id = pw_global_get_id(pw_impl_module_get_global(module)); uint32_t pid = getpid(); const char *str; int res; - const struct spa_support *support; - uint32_t n_support; - struct spa_cpu *cpu_iface; + void *iface = NULL; PW_LOG_TOPIC_INIT(mod_topic); @@ -3021,35 +1404,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->module = module; impl->context = context; - impl->graph.impl = impl; - - spa_list_init(&impl->plugin_list); - spa_list_init(&impl->plugin_func_list); - - add_plugin_func(impl, "builtin", load_builtin_plugin, NULL); - add_plugin_func(impl, "ladspa", load_ladspa_plugin, NULL); - - support = pw_context_get_support(impl->context, &n_support); - impl->quantum_limit = pw_properties_get_uint32( - pw_context_get_properties(impl->context), - "default.clock.quantum-limit", 8192u); - - pw_properties_setf(props, "clock.quantum-limit", "%u", impl->quantum_limit); - - impl->silence_data = calloc(impl->quantum_limit, sizeof(float)); - if (impl->silence_data == NULL) { - res = -errno; - goto error; - } - - impl->discard_data = calloc(impl->quantum_limit, sizeof(float)); - if (impl->discard_data == NULL) { - res = -errno; - goto error; - } - - cpu_iface = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); - dsp_ops_init(&impl->dsp, cpu_iface ? spa_cpu_get_flags(cpu_iface) : 0); if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL) pw_properties_setf(props, PW_KEY_NODE_GROUP, "filter-chain-%u-%u", pid, id); @@ -3117,11 +1471,29 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_setf(impl->playback_props, PW_KEY_MEDIA_NAME, "%s output", pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION)); - if ((res = load_graph(&impl->graph, props)) < 0) { - pw_log_error("can't load graph: %s", spa_strerror(res)); + p = pw_context_get_properties(impl->context); + pw_properties_set(props, "clock.quantum-limit", + pw_properties_get(p, "default.clock.quantum-limit")); + + pw_properties_setf(props, "filter-graph.n_inputs", "%d", impl->capture_info.channels); + pw_properties_setf(props, "filter-graph.n_outputs", "%d", impl->playback_info.channels); + + pw_properties_set(props, SPA_KEY_LIBRARY_NAME, "filter-graph/libspa-filter-graph"); + impl->handle = pw_context_load_spa_handle(impl->context, "filter.graph", &props->dict); + if (impl->handle == NULL) { + res = -errno; goto error; } + res = spa_handle_get_interface(impl->handle, SPA_TYPE_INTERFACE_FilterGraph, &iface); + if (res < 0 || iface == NULL) + goto error; + + impl->graph = iface; + + spa_filter_graph_add_listener(impl->graph, &impl->graph_listener, + &graph_events, impl); + impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { str = pw_properties_get(props, PW_KEY_REMOTE_NAME); diff --git a/src/modules/module-filter-chain/biquad.c b/src/modules/module-filter-chain/biquad.c deleted file mode 100644 index c11f76e0..00000000 --- a/src/modules/module-filter-chain/biquad.c +++ /dev/null @@ -1,364 +0,0 @@ -/* Copyright (c) 2013 The Chromium OS Authors. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE file. - */ - -/* Copyright (C) 2010 Google Inc. All rights reserved. - * Use of this source code is governed by a BSD-style license that can be - * found in the LICENSE.WEBKIT file. - */ - -#include <math.h> -#include "biquad.h" - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -static void set_coefficient(struct biquad *bq, double b0, double b1, double b2, - double a0, double a1, double a2) -{ - double a0_inv = 1 / a0; - bq->b0 = (float)(b0 * a0_inv); - bq->b1 = (float)(b1 * a0_inv); - bq->b2 = (float)(b2 * a0_inv); - bq->a1 = (float)(a1 * a0_inv); - bq->a2 = (float)(a2 * a0_inv); -} - -static void biquad_lowpass(struct biquad *bq, double cutoff, double resonance) -{ - /* Limit cutoff to 0 to 1. */ - cutoff = fmax(0.0, fmin(cutoff, 1.0)); - - if (cutoff == 1 || cutoff == 0) { - /* When cutoff is 1, the z-transform is 1. - * When cutoff is zero, nothing gets through the filter, so set - * coefficients up correctly. - */ - set_coefficient(bq, cutoff, 0, 0, 1, 0, 0); - return; - } - - /* Compute biquad coefficients for lowpass filter */ - resonance = fmax(0.0, resonance); /* can't go negative */ - double g = pow(10.0, 0.05 * resonance); - double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2); - - double theta = M_PI * cutoff; - double sn = 0.5 * d * sin(theta); - double beta = 0.5 * (1 - sn) / (1 + sn); - double gamma = (0.5 + beta) * cos(theta); - double alpha = 0.25 * (0.5 + beta - gamma); - - double b0 = 2 * alpha; - double b1 = 2 * 2 * alpha; - double b2 = 2 * alpha; - double a1 = 2 * -gamma; - double a2 = 2 * beta; - - set_coefficient(bq, b0, b1, b2, 1, a1, a2); -} - -static void biquad_highpass(struct biquad *bq, double cutoff, double resonance) -{ - /* Limit cutoff to 0 to 1. */ - cutoff = fmax(0.0, fmin(cutoff, 1.0)); - - if (cutoff == 1 || cutoff == 0) { - /* When cutoff is one, the z-transform is 0. */ - /* When cutoff is zero, we need to be careful because the above - * gives a quadratic divided by the same quadratic, with poles - * and zeros on the unit circle in the same place. When cutoff - * is zero, the z-transform is 1. - */ - set_coefficient(bq, 1 - cutoff, 0, 0, 1, 0, 0); - return; - } - - /* Compute biquad coefficients for highpass filter */ - resonance = fmax(0.0, resonance); /* can't go negative */ - double g = pow(10.0, 0.05 * resonance); - double d = sqrt((4 - sqrt(16 - 16 / (g * g))) / 2); - - double theta = M_PI * cutoff; - double sn = 0.5 * d * sin(theta); - double beta = 0.5 * (1 - sn) / (1 + sn); - double gamma = (0.5 + beta) * cos(theta); - double alpha = 0.25 * (0.5 + beta + gamma); - - double b0 = 2 * alpha; - double b1 = 2 * -2 * alpha; - double b2 = 2 * alpha; - double a1 = 2 * -gamma; - double a2 = 2 * beta; - - set_coefficient(bq, b0, b1, b2, 1, a1, a2); -} - -static void biquad_bandpass(struct biquad *bq, double frequency, double Q) -{ - /* No negative frequencies allowed. */ - frequency = fmax(0.0, frequency); - - /* Don't let Q go negative, which causes an unstable filter. */ - Q = fmax(0.0, Q); - - if (frequency <= 0 || frequency >= 1) { - /* When the cutoff is zero, the z-transform approaches 0, if Q - * > 0. When both Q and cutoff are zero, the z-transform is - * pretty much undefined. What should we do in this case? - * For now, just make the filter 0. When the cutoff is 1, the - * z-transform also approaches 0. - */ - set_coefficient(bq, 0, 0, 0, 1, 0, 0); - return; - } - if (Q <= 0) { - /* When Q = 0, the above formulas have problems. If we - * look at the z-transform, we can see that the limit - * as Q->0 is 1, so set the filter that way. - */ - set_coefficient(bq, 1, 0, 0, 1, 0, 0); - return; - } - - double w0 = M_PI * frequency; - double alpha = sin(w0) / (2 * Q); - double k = cos(w0); - - double b0 = alpha; - double b1 = 0; - double b2 = -alpha; - double a0 = 1 + alpha; - double a1 = -2 * k; - double a2 = 1 - alpha; - - set_coefficient(bq, b0, b1, b2, a0, a1, a2); -} - -static void biquad_lowshelf(struct biquad *bq, double frequency, double db_gain) -{ - /* Clip frequencies to between 0 and 1, inclusive. */ - frequency = fmax(0.0, fmin(frequency, 1.0)); - - double A = pow(10.0, db_gain / 40); - - if (frequency == 1) { - /* The z-transform is a constant gain. */ - set_coefficient(bq, A * A, 0, 0, 1, 0, 0); - return; - } - if (frequency <= 0) { - /* When frequency is 0, the z-transform is 1. */ - set_coefficient(bq, 1, 0, 0, 1, 0, 0); - return; - } - - double w0 = M_PI * frequency; - double S = 1; /* filter slope (1 is max value) */ - double alpha = 0.5 * sin(w0) * sqrt((A + 1 / A) * (1 / S - 1) + 2); - double k = cos(w0); - double k2 = 2 * sqrt(A) * alpha; - double a_plus_one = A + 1; - double a_minus_one = A - 1; - - double b0 = A * (a_plus_one - a_minus_one * k + k2); - double b1 = 2 * A * (a_minus_one - a_plus_one * k); - double b2 = A * (a_plus_one - a_minus_one * k - k2); - double a0 = a_plus_one + a_minus_one * k + k2; - double a1 = -2 * (a_minus_one + a_plus_one * k); - double a2 = a_plus_one + a_minus_one * k - k2; - - set_coefficient(bq, b0, b1, b2, a0, a1, a2); -} - -static void biquad_highshelf(struct biquad *bq, double frequency, - double db_gain) -{ - /* Clip frequencies to between 0 and 1, inclusive. */ - frequency = fmax(0.0, fmin(frequency, 1.0)); - - double A = pow(10.0, db_gain / 40); - - if (frequency == 1) { - /* The z-transform is 1. */ - set_coefficient(bq, 1, 0, 0, 1, 0, 0); - return; - } - if (frequency <= 0) { - /* When frequency = 0, the filter is just a gain, A^2. */ - set_coefficient(bq, A * A, 0, 0, 1, 0, 0); - return; - } - - double w0 = M_PI * frequency; - double S = 1; /* filter slope (1 is max value) */ - double alpha = 0.5 * sin(w0) * sqrt((A + 1 / A) * (1 / S - 1) + 2); - double k = cos(w0); - double k2 = 2 * sqrt(A) * alpha; - double a_plus_one = A + 1; - double a_minus_one = A - 1; - - double b0 = A * (a_plus_one + a_minus_one * k + k2); - double b1 = -2 * A * (a_minus_one + a_plus_one * k); - double b2 = A * (a_plus_one + a_minus_one * k - k2); - double a0 = a_plus_one - a_minus_one * k + k2; - double a1 = 2 * (a_minus_one - a_plus_one * k); - double a2 = a_plus_one - a_minus_one * k - k2; - - set_coefficient(bq, b0, b1, b2, a0, a1, a2); -} - -static void biquad_peaking(struct biquad *bq, double frequency, double Q, - double db_gain) -{ - /* Clip frequencies to between 0 and 1, inclusive. */ - frequency = fmax(0.0, fmin(frequency, 1.0)); - - /* Don't let Q go negative, which causes an unstable filter. */ - Q = fmax(0.0, Q); - - double A = pow(10.0, db_gain / 40); - - if (frequency <= 0 || frequency >= 1) { - /* When frequency is 0 or 1, the z-transform is 1. */ - set_coefficient(bq, 1, 0, 0, 1, 0, 0); - return; - } - if (Q <= 0) { - /* When Q = 0, the above formulas have problems. If we - * look at the z-transform, we can see that the limit - * as Q->0 is A^2, so set the filter that way. - */ - set_coefficient(bq, A * A, 0, 0, 1, 0, 0); - return; - } - - double w0 = M_PI * frequency; - double alpha = sin(w0) / (2 * Q); - double k = cos(w0); - - double b0 = 1 + alpha * A; - double b1 = -2 * k; - double b2 = 1 - alpha * A; - double a0 = 1 + alpha / A; - double a1 = -2 * k; - double a2 = 1 - alpha / A; - - set_coefficient(bq, b0, b1, b2, a0, a1, a2); -} - -static void biquad_notch(struct biquad *bq, double frequency, double Q) -{ - /* Clip frequencies to between 0 and 1, inclusive. */ - frequency = fmax(0.0, fmin(frequency, 1.0)); - - /* Don't let Q go negative, which causes an unstable filter. */ - Q = fmax(0.0, Q); - - if (frequency <= 0 || frequency >= 1) { - /* When frequency is 0 or 1, the z-transform is 1. */ - set_coefficient(bq, 1, 0, 0, 1, 0, 0); - return; - } - if (Q <= 0) { - /* When Q = 0, the above formulas have problems. If we - * look at the z-transform, we can see that the limit - * as Q->0 is 0, so set the filter that way. - */ - set_coefficient(bq, 0, 0, 0, 1, 0, 0); - return; - } - - double w0 = M_PI * frequency; - double alpha = sin(w0) / (2 * Q); - double k = cos(w0); - - double b0 = 1; - double b1 = -2 * k; - double b2 = 1; - double a0 = 1 + alpha; - double a1 = -2 * k; - double a2 = 1 - alpha; - - set_coefficient(bq, b0, b1, b2, a0, a1, a2); -} - -static void biquad_allpass(struct biquad *bq, double frequency, double Q) -{ - /* Clip frequencies to between 0 and 1, inclusive. */ - frequency = fmax(0.0, fmin(frequency, 1.0)); - - /* Don't let Q go negative, which causes an unstable filter. */ - Q = fmax(0.0, Q); - - if (frequency <= 0 || frequency >= 1) { - /* When frequency is 0 or 1, the z-transform is 1. */ - set_coefficient(bq, 1, 0, 0, 1, 0, 0); - return; - } - - if (Q <= 0) { - /* When Q = 0, the above formulas have problems. If we - * look at the z-transform, we can see that the limit - * as Q->0 is -1, so set the filter that way. - */ - set_coefficient(bq, -1, 0, 0, 1, 0, 0); - return; - } - - double w0 = M_PI * frequency; - double alpha = sin(w0) / (2 * Q); - double k = cos(w0); - - double b0 = 1 - alpha; - double b1 = -2 * k; - double b2 = 1 + alpha; - double a0 = 1 + alpha; - double a1 = -2 * k; - double a2 = 1 - alpha; - - set_coefficient(bq, b0, b1, b2, a0, a1, a2); -} - -void biquad_set(struct biquad *bq, enum biquad_type type, double freq, double Q, - double gain) -{ - /* Clear history values. */ - bq->x1 = 0; - bq->x2 = 0; - bq->y1 = 0; - bq->y2 = 0; - - switch (type) { - case BQ_LOWPASS: - biquad_lowpass(bq, freq, Q); - break; - case BQ_HIGHPASS: - biquad_highpass(bq, freq, Q); - break; - case BQ_BANDPASS: - biquad_bandpass(bq, freq, Q); - break; - case BQ_LOWSHELF: - biquad_lowshelf(bq, freq, gain); - break; - case BQ_HIGHSHELF: - biquad_highshelf(bq, freq, gain); - break; - case BQ_PEAKING: - biquad_peaking(bq, freq, Q, gain); - break; - case BQ_NOTCH: - biquad_notch(bq, freq, Q); - break; - case BQ_ALLPASS: - biquad_allpass(bq, freq, Q); - break; - case BQ_NONE: - /* Default is an identity filter. */ - set_coefficient(bq, 1, 0, 0, 1, 0, 0); - break; - } -} diff --git a/src/modules/module-filter-chain/builtin_plugin.c b/src/modules/module-filter-chain/builtin_plugin.c deleted file mode 100644 index f7d75ae7..00000000 --- a/src/modules/module-filter-chain/builtin_plugin.c +++ /dev/null @@ -1,1771 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#include "config.h" - -#include <float.h> -#include <math.h> -#ifdef HAVE_SNDFILE -#include <sndfile.h> -#endif -#include <unistd.h> -#include <limits.h> - -#include <spa/utils/json.h> -#include <spa/utils/result.h> -#include <spa/support/cpu.h> -#include <spa/plugins/audioconvert/resample.h> - -#include <pipewire/log.h> - -#include "plugin.h" - -#include "biquad.h" -#include "pffft.h" -#include "convolver.h" -#include "dsp-ops.h" - -#define MAX_RATES 32u - -struct plugin { - struct fc_plugin plugin; - struct dsp_ops *dsp_ops; -}; - -struct builtin { - struct plugin *plugin; - unsigned long rate; - float *port[64]; - - int type; - struct biquad bq; - float freq; - float Q; - float gain; - float b0, b1, b2; - float a0, a1, a2; - float accum; -}; - -static void *builtin_instantiate(const struct fc_plugin *plugin, const struct fc_descriptor * Descriptor, - unsigned long SampleRate, int index, const char *config) -{ - struct builtin *impl; - - impl = calloc(1, sizeof(*impl)); - if (impl == NULL) - return NULL; - - impl->plugin = (struct plugin *) plugin; - impl->rate = SampleRate; - - return impl; -} - -static void builtin_connect_port(void *Instance, unsigned long Port, float * DataLocation) -{ - struct builtin *impl = Instance; - impl->port[Port] = DataLocation; -} - -static void builtin_cleanup(void * Instance) -{ - struct builtin *impl = Instance; - free(impl); -} - -/** copy */ -static void copy_run(void * Instance, unsigned long SampleCount) -{ - struct builtin *impl = Instance; - float *in = impl->port[1], *out = impl->port[0]; - dsp_ops_copy(impl->plugin->dsp_ops, out, in, SampleCount); -} - -static struct fc_port copy_ports[] = { - { .index = 0, - .name = "Out", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, - }, - { .index = 1, - .name = "In", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - } -}; - -static const struct fc_descriptor copy_desc = { - .name = "copy", - .flags = FC_DESCRIPTOR_COPY, - - .n_ports = 2, - .ports = copy_ports, - - .instantiate = builtin_instantiate, - .connect_port = builtin_connect_port, - .run = copy_run, - .cleanup = builtin_cleanup, -}; - -/** mixer */ -static void mixer_run(void * Instance, unsigned long SampleCount) -{ - struct builtin *impl = Instance; - int i, n_src = 0; - float *out = impl->port[0]; - const void *src[8]; - float gains[8]; - - if (out == NULL) - return; - - for (i = 0; i < 8; i++) { - float *in = impl->port[1+i]; - float gain = impl->port[9+i][0]; - - if (in == NULL || gain == 0.0f) - continue; - - src[n_src] = in; - gains[n_src++] = gain; - } - dsp_ops_mix_gain(impl->plugin->dsp_ops, out, src, gains, n_src, SampleCount); -} - -static struct fc_port mixer_ports[] = { - { .index = 0, - .name = "Out", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, - }, - - { .index = 1, - .name = "In 1", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 2, - .name = "In 2", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 3, - .name = "In 3", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 4, - .name = "In 4", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 5, - .name = "In 5", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 6, - .name = "In 6", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 7, - .name = "In 7", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 8, - .name = "In 8", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - - { .index = 9, - .name = "Gain 1", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0f, .min = 0.0f, .max = 10.0f - }, - { .index = 10, - .name = "Gain 2", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0f, .min = 0.0f, .max = 10.0f - }, - { .index = 11, - .name = "Gain 3", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0f, .min = 0.0f, .max = 10.0f - }, - { .index = 12, - .name = "Gain 4", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0f, .min = 0.0f, .max = 10.0f - }, - { .index = 13, - .name = "Gain 5", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0f, .min = 0.0f, .max = 10.0f - }, - { .index = 14, - .name = "Gain 6", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0f, .min = 0.0f, .max = 10.0f - }, - { .index = 15, - .name = "Gain 7", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0f, .min = 0.0f, .max = 10.0f - }, - { .index = 16, - .name = "Gain 8", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0f, .min = 0.0f, .max = 10.0f - }, -}; - -static const struct fc_descriptor mixer_desc = { - .name = "mixer", - .flags = FC_DESCRIPTOR_SUPPORTS_NULL_DATA, - - .n_ports = 17, - .ports = mixer_ports, - - .instantiate = builtin_instantiate, - .connect_port = builtin_connect_port, - .run = mixer_run, - .cleanup = builtin_cleanup, -}; - -/** biquads */ -static int bq_type_from_name(const char *name) -{ - if (spa_streq(name, "bq_lowpass")) - return BQ_LOWPASS; - if (spa_streq(name, "bq_highpass")) - return BQ_HIGHPASS; - if (spa_streq(name, "bq_bandpass")) - return BQ_BANDPASS; - if (spa_streq(name, "bq_lowshelf")) - return BQ_LOWSHELF; - if (spa_streq(name, "bq_highshelf")) - return BQ_HIGHSHELF; - if (spa_streq(name, "bq_peaking")) - return BQ_PEAKING; - if (spa_streq(name, "bq_notch")) - return BQ_NOTCH; - if (spa_streq(name, "bq_allpass")) - return BQ_ALLPASS; - if (spa_streq(name, "bq_raw")) - return BQ_NONE; - return BQ_NONE; -} - -static void bq_raw_update(struct builtin *impl, float b0, float b1, float b2, - float a0, float a1, float a2) -{ - struct biquad *bq = &impl->bq; - impl->b0 = b0; - impl->b1 = b1; - impl->b2 = b2; - impl->a0 = a0; - impl->a1 = a1; - impl->a2 = a2; - if (a0 != 0.0f) - a0 = 1.0f / a0; - bq->b0 = impl->b0 * a0; - bq->b1 = impl->b1 * a0; - bq->b2 = impl->b2 * a0; - bq->a1 = impl->a1 * a0; - bq->a2 = impl->a2 * a0; - bq->x1 = bq->x2 = bq->y1 = bq->y2 = 0.0; -} - -/* - * config = { - * coefficients = [ - * { rate = 44100, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. }, - * { rate = 48000, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. }, - * { rate = 192000, b0=.., b1=.., b2=.., a0=.., a1=.., a2=.. } - * ] - * } - */ -static void *bq_instantiate(const struct fc_plugin *plugin, const struct fc_descriptor * Descriptor, - unsigned long SampleRate, int index, const char *config) -{ - struct builtin *impl; - struct spa_json it[4]; - const char *val; - char key[256]; - uint32_t best_rate = 0; - - impl = calloc(1, sizeof(*impl)); - if (impl == NULL) - return NULL; - - impl->plugin = (struct plugin *) plugin; - impl->rate = SampleRate; - impl->b0 = impl->a0 = 1.0f; - impl->type = bq_type_from_name(Descriptor->name); - if (impl->type != BQ_NONE) - return impl; - - if (config == NULL) { - pw_log_error("biquads:bq_raw requires a config section"); - goto error; - } - - spa_json_init(&it[0], config, strlen(config)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) { - pw_log_error("biquads:config section must be an object"); - goto error; - } - - while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { - if (spa_streq(key, "coefficients")) { - if (spa_json_enter_array(&it[1], &it[2]) <= 0) { - pw_log_error("biquads:coefficients require an array"); - goto error; - } - while (spa_json_enter_object(&it[2], &it[3]) > 0) { - int32_t rate = 0; - float b0 = 1.0f, b1 = 0.0f, b2 = 0.0f; - float a0 = 1.0f, a1 = 0.0f, a2 = 0.0f; - - while (spa_json_get_string(&it[3], key, sizeof(key)) > 0) { - if (spa_streq(key, "rate")) { - if (spa_json_get_int(&it[3], &rate) <= 0) { - pw_log_error("biquads:rate requires a number"); - goto error; - } - } - else if (spa_streq(key, "b0")) { - if (spa_json_get_float(&it[3], &b0) <= 0) { - pw_log_error("biquads:b0 requires a float"); - goto error; - } - } - else if (spa_streq(key, "b1")) { - if (spa_json_get_float(&it[3], &b1) <= 0) { - pw_log_error("biquads:b1 requires a float"); - goto error; - } - } - else if (spa_streq(key, "b2")) { - if (spa_json_get_float(&it[3], &b2) <= 0) { - pw_log_error("biquads:b2 requires a float"); - goto error; - } - } - else if (spa_streq(key, "a0")) { - if (spa_json_get_float(&it[3], &a0) <= 0) { - pw_log_error("biquads:a0 requires a float"); - goto error; - } - } - else if (spa_streq(key, "a1")) { - if (spa_json_get_float(&it[3], &a1) <= 0) { - pw_log_error("biquads:a1 requires a float"); - goto error; - } - } - else if (spa_streq(key, "a2")) { - if (spa_json_get_float(&it[3], &a2) <= 0) { - pw_log_error("biquads:a0 requires a float"); - goto error; - } - } - else { - pw_log_warn("biquads: ignoring coefficients key: '%s'", key); - if (spa_json_next(&it[3], &val) < 0) - break; - } - } - if (labs((long)rate - (long)SampleRate) < - labs((long)best_rate - (long)SampleRate)) { - best_rate = rate; - bq_raw_update(impl, b0, b1, b2, a0, a1, a2); - } - } - } - else { - pw_log_warn("biquads: ignoring config key: '%s'", key); - if (spa_json_next(&it[1], &val) < 0) - break; - } - } - - return impl; -error: - free(impl); - errno = EINVAL; - return NULL; -} - -#define BQ_NUM_PORTS 11 -static struct fc_port bq_ports[] = { - { .index = 0, - .name = "Out", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, - }, - { .index = 1, - .name = "In", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 2, - .name = "Freq", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .hint = FC_HINT_SAMPLE_RATE, - .def = 0.0f, .min = 0.0f, .max = 1.0f, - }, - { .index = 3, - .name = "Q", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 0.0f, .min = 0.0f, .max = 10.0f, - }, - { .index = 4, - .name = "Gain", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 0.0f, .min = -120.0f, .max = 20.0f, - }, - { .index = 5, - .name = "b0", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0f, .min = -10.0f, .max = 10.0f, - }, - { .index = 6, - .name = "b1", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 0.0f, .min = -10.0f, .max = 10.0f, - }, - { .index = 7, - .name = "b2", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 0.0f, .min = -10.0f, .max = 10.0f, - }, - { .index = 8, - .name = "a0", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0f, .min = -10.0f, .max = 10.0f, - }, - { .index = 9, - .name = "a1", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 0.0f, .min = -10.0f, .max = 10.0f, - }, - { .index = 10, - .name = "a2", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 0.0f, .min = -10.0f, .max = 10.0f, - }, - -}; - -static void bq_freq_update(struct builtin *impl, int type, float freq, float Q, float gain) -{ - struct biquad *bq = &impl->bq; - impl->freq = freq; - impl->Q = Q; - impl->gain = gain; - biquad_set(bq, type, freq * 2 / impl->rate, Q, gain); - impl->port[5][0] = impl->b0 = bq->b0; - impl->port[6][0] = impl->b1 = bq->b1; - impl->port[7][0] = impl->b2 = bq->b2; - impl->port[8][0] = impl->a0 = 1.0f; - impl->port[9][0] = impl->a1 = bq->a1; - impl->port[10][0] = impl->a2 = bq->a2; -} - -static void bq_activate(void * Instance) -{ - struct builtin *impl = Instance; - if (impl->type == BQ_NONE) { - impl->port[5][0] = impl->b0; - impl->port[6][0] = impl->b1; - impl->port[7][0] = impl->b2; - impl->port[8][0] = impl->a0; - impl->port[9][0] = impl->a1; - impl->port[10][0] = impl->a2; - } else { - float freq = impl->port[2][0]; - float Q = impl->port[3][0]; - float gain = impl->port[4][0]; - bq_freq_update(impl, impl->type, freq, Q, gain); - } -} - -static void bq_run(void *Instance, unsigned long samples) -{ - struct builtin *impl = Instance; - struct biquad *bq = &impl->bq; - float *out = impl->port[0]; - float *in = impl->port[1]; - - if (impl->type == BQ_NONE) { - float b0, b1, b2, a0, a1, a2; - b0 = impl->port[5][0]; - b1 = impl->port[6][0]; - b2 = impl->port[7][0]; - a0 = impl->port[8][0]; - a1 = impl->port[9][0]; - a2 = impl->port[10][0]; - if (impl->b0 != b0 || impl->b1 != b1 || impl->b2 != b2 || - impl->a0 != a0 || impl->a1 != a1 || impl->a2 != a2) { - bq_raw_update(impl, b0, b1, b2, a0, a1, a2); - } - } else { - float freq = impl->port[2][0]; - float Q = impl->port[3][0]; - float gain = impl->port[4][0]; - if (impl->freq != freq || impl->Q != Q || impl->gain != gain) - bq_freq_update(impl, impl->type, freq, Q, gain); - } - dsp_ops_biquad_run(impl->plugin->dsp_ops, bq, out, in, samples); -} - -/** bq_lowpass */ -static const struct fc_descriptor bq_lowpass_desc = { - .name = "bq_lowpass", - - .n_ports = BQ_NUM_PORTS, - .ports = bq_ports, - - .instantiate = bq_instantiate, - .connect_port = builtin_connect_port, - .activate = bq_activate, - .run = bq_run, - .cleanup = builtin_cleanup, -}; - -/** bq_highpass */ -static const struct fc_descriptor bq_highpass_desc = { - .name = "bq_highpass", - - .n_ports = BQ_NUM_PORTS, - .ports = bq_ports, - - .instantiate = bq_instantiate, - .connect_port = builtin_connect_port, - .activate = bq_activate, - .run = bq_run, - .cleanup = builtin_cleanup, -}; - -/** bq_bandpass */ -static const struct fc_descriptor bq_bandpass_desc = { - .name = "bq_bandpass", - - .n_ports = BQ_NUM_PORTS, - .ports = bq_ports, - - .instantiate = bq_instantiate, - .connect_port = builtin_connect_port, - .activate = bq_activate, - .run = bq_run, - .cleanup = builtin_cleanup, -}; - -/** bq_lowshelf */ -static const struct fc_descriptor bq_lowshelf_desc = { - .name = "bq_lowshelf", - - .n_ports = BQ_NUM_PORTS, - .ports = bq_ports, - - .instantiate = bq_instantiate, - .connect_port = builtin_connect_port, - .activate = bq_activate, - .run = bq_run, - .cleanup = builtin_cleanup, -}; - -/** bq_highshelf */ -static const struct fc_descriptor bq_highshelf_desc = { - .name = "bq_highshelf", - - .n_ports = BQ_NUM_PORTS, - .ports = bq_ports, - - .instantiate = bq_instantiate, - .connect_port = builtin_connect_port, - .activate = bq_activate, - .run = bq_run, - .cleanup = builtin_cleanup, -}; - -/** bq_peaking */ -static const struct fc_descriptor bq_peaking_desc = { - .name = "bq_peaking", - - .n_ports = BQ_NUM_PORTS, - .ports = bq_ports, - - .instantiate = bq_instantiate, - .connect_port = builtin_connect_port, - .activate = bq_activate, - .run = bq_run, - .cleanup = builtin_cleanup, -}; - -/** bq_notch */ -static const struct fc_descriptor bq_notch_desc = { - .name = "bq_notch", - - .n_ports = BQ_NUM_PORTS, - .ports = bq_ports, - - .instantiate = bq_instantiate, - .connect_port = builtin_connect_port, - .activate = bq_activate, - .run = bq_run, - .cleanup = builtin_cleanup, -}; - - -/** bq_allpass */ -static const struct fc_descriptor bq_allpass_desc = { - .name = "bq_allpass", - - .n_ports = BQ_NUM_PORTS, - .ports = bq_ports, - - .instantiate = bq_instantiate, - .connect_port = builtin_connect_port, - .activate = bq_activate, - .run = bq_run, - .cleanup = builtin_cleanup, -}; - -/* bq_raw */ -static const struct fc_descriptor bq_raw_desc = { - .name = "bq_raw", - - .n_ports = BQ_NUM_PORTS, - .ports = bq_ports, - - .instantiate = bq_instantiate, - .connect_port = builtin_connect_port, - .activate = bq_activate, - .run = bq_run, - .cleanup = builtin_cleanup, -}; - -/** convolve */ -struct convolver_impl { - struct plugin *plugin; - unsigned long rate; - float *port[64]; - - struct convolver *conv; -}; - -#ifdef HAVE_SNDFILE -static float *read_samples_from_sf(SNDFILE *f, SF_INFO info, float gain, int delay, - int offset, int length, int channel, long unsigned *rate, int *n_samples) { - float *samples; - int i, n; - - if (length <= 0) - length = info.frames; - else - length = SPA_MIN(length, info.frames); - - length -= SPA_MIN(offset, length); - - n = delay + length; - if (n == 0) - return NULL; - - samples = calloc(n * info.channels, sizeof(float)); - if (samples == NULL) - return NULL; - - if (offset > 0) - sf_seek(f, offset, SEEK_SET); - sf_readf_float(f, samples + (delay * info.channels), length); - - channel = channel % info.channels; - - for (i = 0; i < n; i++) - samples[i] = samples[info.channels * i + channel] * gain; - - *n_samples = n; - *rate = info.samplerate; - return samples; -} -#endif - -static float *read_closest(char **filenames, float gain, int delay, int offset, - int length, int channel, long unsigned *rate, int *n_samples) -{ -#ifdef HAVE_SNDFILE - SF_INFO infos[MAX_RATES]; - SNDFILE *fs[MAX_RATES]; - - spa_zero(infos); - spa_zero(fs); - - int diff = INT_MAX; - uint32_t best = 0, i; - float *samples = NULL; - - for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) { - fs[i] = sf_open(filenames[i], SFM_READ, &infos[i]); - if (fs[i] == NULL) - continue; - - if (labs((long)infos[i].samplerate - (long)*rate) < diff) { - best = i; - diff = labs((long)infos[i].samplerate - (long)*rate); - pw_log_debug("new closest match: %d", infos[i].samplerate); - } - } - if (fs[best] != NULL) { - pw_log_info("loading best rate:%u %s", infos[best].samplerate, filenames[best]); - samples = read_samples_from_sf(fs[best], infos[best], gain, delay, - offset, length, channel, rate, n_samples); - } else { - char buf[PATH_MAX]; - pw_log_error("Can't open any sample file (CWD %s):", - getcwd(buf, sizeof(buf))); - for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) { - fs[i] = sf_open(filenames[i], SFM_READ, &infos[i]); - if (fs[i] == NULL) - pw_log_error(" failed file %s: %s", filenames[i], sf_strerror(fs[i])); - else - pw_log_warn(" unexpectedly opened file %s", filenames[i]); - } - } - for (i = 0; i < MAX_RATES; i++) - if (fs[i] != NULL) - sf_close(fs[i]); - - return samples; -#else - pw_log_error("compiled without sndfile support, can't load samples: " - "using dirac impulse"); - float *samples = calloc(1, sizeof(float)); - samples[0] = gain; - *n_samples = 1; - return samples; -#endif -} - -static float *create_hilbert(const char *filename, float gain, int delay, int offset, - int length, int *n_samples) -{ - float *samples, v; - int i, n, h; - - if (length <= 0) - length = 1024; - - length -= SPA_MIN(offset, length); - - n = delay + length; - if (n == 0) - return NULL; - - samples = calloc(n, sizeof(float)); - if (samples == NULL) - return NULL; - - gain *= 2 / (float)M_PI; - h = length / 2; - for (i = 1; i < h; i += 2) { - v = (gain / i) * (0.43f + 0.57f * cosf(i * (float)M_PI / h)); - samples[delay + h + i] = -v; - samples[delay + h - i] = v; - } - *n_samples = n; - return samples; -} - -static float *create_dirac(const char *filename, float gain, int delay, int offset, - int length, int *n_samples) -{ - float *samples; - int n; - - n = delay + 1; - - samples = calloc(n, sizeof(float)); - if (samples == NULL) - return NULL; - - samples[delay] = gain; - - *n_samples = n; - return samples; -} - -static float *resample_buffer(struct dsp_ops *dsp_ops, float *samples, int *n_samples, - unsigned long in_rate, unsigned long out_rate, uint32_t quality) -{ -#ifdef HAVE_SPA_PLUGINS - uint32_t in_len, out_len, total_out = 0; - int out_n_samples; - float *out_samples, *out_buf, *in_buf; - struct resample r; - int res; - - spa_zero(r); - r.channels = 1; - r.i_rate = in_rate; - r.o_rate = out_rate; - r.cpu_flags = dsp_ops->cpu_flags; - r.quality = quality; - if ((res = resample_native_init(&r)) < 0) { - pw_log_error("resampling failed: %s", spa_strerror(res)); - errno = -res; - return NULL; - } - - out_n_samples = SPA_ROUND_UP(*n_samples * out_rate, in_rate) / in_rate; - out_samples = calloc(out_n_samples, sizeof(float)); - if (out_samples == NULL) - goto error; - - in_len = *n_samples; - in_buf = samples; - out_len = out_n_samples; - out_buf = out_samples; - - pw_log_info("Resampling filter: rate: %lu => %lu, n_samples: %u => %u, q:%u", - in_rate, out_rate, in_len, out_len, quality); - - resample_process(&r, (void*)&in_buf, &in_len, (void*)&out_buf, &out_len); - pw_log_debug("resampled: %u -> %u samples", in_len, out_len); - total_out += out_len; - - in_len = resample_delay(&r); - in_buf = calloc(in_len, sizeof(float)); - if (in_buf == NULL) - goto error; - - out_buf = out_samples + total_out; - out_len = out_n_samples - total_out; - - pw_log_debug("flushing resampler: %u in %u out", in_len, out_len); - resample_process(&r, (void*)&in_buf, &in_len, (void*)&out_buf, &out_len); - pw_log_debug("flushed: %u -> %u samples", in_len, out_len); - total_out += out_len; - - free(in_buf); - free(samples); - resample_free(&r); - - *n_samples = total_out; - - float gain = (float)in_rate / (float)out_rate; - for (uint32_t i = 0; i < total_out; i++) - out_samples[i] = out_samples[i] * gain; - - return out_samples; - -error: - resample_free(&r); - free(samples); - free(out_samples); - return NULL; -#else - pw_log_error("compiled without spa-plugins support, can't resample"); - float *out_samples = calloc(*n_samples, sizeof(float)); - memcpy(out_samples, samples, *n_samples * sizeof(float)); - return out_samples; -#endif -} - -static void * convolver_instantiate(const struct fc_plugin *plugin, const struct fc_descriptor * Descriptor, - unsigned long SampleRate, int index, const char *config) -{ - struct convolver_impl *impl; - float *samples; - int offset = 0, length = 0, channel = index, n_samples = 0, len; - uint32_t i = 0; - struct spa_json it[3]; - const char *val; - char key[256], v[256]; - char *filenames[MAX_RATES] = { 0 }; - int blocksize = 0, tailsize = 0; - int delay = 0; - int resample_quality = RESAMPLE_DEFAULT_QUALITY; - float gain = 1.0f; - unsigned long rate; - - errno = EINVAL; - if (config == NULL) { - pw_log_error("convolver: requires a config section"); - return NULL; - } - - spa_json_init(&it[0], config, strlen(config)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) { - pw_log_error("convolver:config must be an object"); - return NULL; - } - - while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { - if (spa_streq(key, "blocksize")) { - if (spa_json_get_int(&it[1], &blocksize) <= 0) { - pw_log_error("convolver:blocksize requires a number"); - return NULL; - } - } - else if (spa_streq(key, "tailsize")) { - if (spa_json_get_int(&it[1], &tailsize) <= 0) { - pw_log_error("convolver:tailsize requires a number"); - return NULL; - } - } - else if (spa_streq(key, "gain")) { - if (spa_json_get_float(&it[1], &gain) <= 0) { - pw_log_error("convolver:gain requires a number"); - return NULL; - } - } - else if (spa_streq(key, "delay")) { - if (spa_json_get_int(&it[1], &delay) <= 0) { - pw_log_error("convolver:delay requires a number"); - return NULL; - } - } - else if (spa_streq(key, "filename")) { - if ((len = spa_json_next(&it[1], &val)) <= 0) { - pw_log_error("convolver:filename requires a string or an array"); - return NULL; - } - if (spa_json_is_array(val, len)) { - spa_json_enter(&it[1], &it[2]); - while (spa_json_get_string(&it[2], v, sizeof(v)) > 0 && - i < SPA_N_ELEMENTS(filenames)) { - filenames[i] = strdup(v); - i++; - } - } - else if (spa_json_parse_stringn(val, len, v, sizeof(v)) <= 0) { - pw_log_error("convolver:filename requires a string or an array"); - return NULL; - } else { - filenames[0] = strdup(v); - } - } - else if (spa_streq(key, "offset")) { - if (spa_json_get_int(&it[1], &offset) <= 0) { - pw_log_error("convolver:offset requires a number"); - return NULL; - } - } - else if (spa_streq(key, "length")) { - if (spa_json_get_int(&it[1], &length) <= 0) { - pw_log_error("convolver:length requires a number"); - return NULL; - } - } - else if (spa_streq(key, "channel")) { - if (spa_json_get_int(&it[1], &channel) <= 0) { - pw_log_error("convolver:channel requires a number"); - return NULL; - } - } - else if (spa_streq(key, "resample_quality")) { - if (spa_json_get_int(&it[1], &resample_quality) <= 0) { - pw_log_error("convolver:resample_quality requires a number"); - return NULL; - } - } - else { - pw_log_warn("convolver: ignoring config key: '%s'", key); - if (spa_json_next(&it[1], &val) < 0) - break; - } - } - if (filenames[0] == NULL) { - pw_log_error("convolver:filename was not given"); - return NULL; - } - - if (delay < 0) - delay = 0; - if (offset < 0) - offset = 0; - - if (spa_streq(filenames[0], "/hilbert")) { - samples = create_hilbert(filenames[0], gain, delay, offset, - length, &n_samples); - } else if (spa_streq(filenames[0], "/dirac")) { - samples = create_dirac(filenames[0], gain, delay, offset, - length, &n_samples); - } else { - rate = SampleRate; - samples = read_closest(filenames, gain, delay, offset, - length, channel, &rate, &n_samples); - if (samples != NULL && rate != SampleRate) { - struct plugin *p = (struct plugin *) plugin; - samples = resample_buffer(p->dsp_ops, samples, &n_samples, - rate, SampleRate, resample_quality); - } - } - - for (i = 0; i < MAX_RATES; i++) - if (filenames[i]) - free(filenames[i]); - - if (samples == NULL) { - errno = ENOENT; - return NULL; - } - - if (blocksize <= 0) - blocksize = SPA_CLAMP(n_samples, 64, 256); - if (tailsize <= 0) - tailsize = SPA_CLAMP(4096, blocksize, 32768); - - pw_log_info("using n_samples:%u %d:%d blocksize", n_samples, - blocksize, tailsize); - - impl = calloc(1, sizeof(*impl)); - if (impl == NULL) - goto error; - - impl->plugin = (struct plugin *) plugin; - impl->rate = SampleRate; - - impl->conv = convolver_new(impl->plugin->dsp_ops, blocksize, tailsize, samples, n_samples); - if (impl->conv == NULL) - goto error; - - free(samples); - - return impl; -error: - free(samples); - free(impl); - return NULL; -} - -static void convolver_connect_port(void * Instance, unsigned long Port, - float * DataLocation) -{ - struct convolver_impl *impl = Instance; - impl->port[Port] = DataLocation; -} - -static void convolver_cleanup(void * Instance) -{ - struct convolver_impl *impl = Instance; - if (impl->conv) - convolver_free(impl->conv); - free(impl); -} - -static struct fc_port convolve_ports[] = { - { .index = 0, - .name = "Out", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, - }, - { .index = 1, - .name = "In", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, -}; - -static void convolver_deactivate(void * Instance) -{ - struct convolver_impl *impl = Instance; - convolver_reset(impl->conv); -} - -static void convolve_run(void * Instance, unsigned long SampleCount) -{ - struct convolver_impl *impl = Instance; - convolver_run(impl->conv, impl->port[1], impl->port[0], SampleCount); -} - -static const struct fc_descriptor convolve_desc = { - .name = "convolver", - - .n_ports = 2, - .ports = convolve_ports, - - .instantiate = convolver_instantiate, - .connect_port = convolver_connect_port, - .deactivate = convolver_deactivate, - .run = convolve_run, - .cleanup = convolver_cleanup, -}; - -/** delay */ -struct delay_impl { - struct plugin *plugin; - unsigned long rate; - float *port[4]; - - float delay; - uint32_t delay_samples; - uint32_t buffer_samples; - float *buffer; - uint32_t ptr; -}; - -static void delay_cleanup(void * Instance) -{ - struct delay_impl *impl = Instance; - free(impl->buffer); - free(impl); -} - -static void *delay_instantiate(const struct fc_plugin *plugin, const struct fc_descriptor * Descriptor, - unsigned long SampleRate, int index, const char *config) -{ - struct delay_impl *impl; - struct spa_json it[2]; - const char *val; - char key[256]; - float max_delay = 1.0f; - - if (config == NULL) { - pw_log_error("delay: requires a config section"); - errno = EINVAL; - return NULL; - } - - spa_json_init(&it[0], config, strlen(config)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) { - pw_log_error("delay:config must be an object"); - return NULL; - } - - while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { - if (spa_streq(key, "max-delay")) { - if (spa_json_get_float(&it[1], &max_delay) <= 0) { - pw_log_error("delay:max-delay requires a number"); - return NULL; - } - } else { - pw_log_warn("delay: ignoring config key: '%s'", key); - if (spa_json_next(&it[1], &val) < 0) - break; - } - } - if (max_delay <= 0.0f) - max_delay = 1.0f; - - impl = calloc(1, sizeof(*impl)); - if (impl == NULL) - return NULL; - - impl->plugin = (struct plugin *) plugin; - impl->rate = SampleRate; - impl->buffer_samples = (uint32_t)(max_delay * impl->rate); - pw_log_info("max-delay:%f seconds rate:%lu samples:%d", max_delay, impl->rate, impl->buffer_samples); - - impl->buffer = calloc(impl->buffer_samples, sizeof(float)); - if (impl->buffer == NULL) { - delay_cleanup(impl); - return NULL; - } - return impl; -} - -static void delay_connect_port(void * Instance, unsigned long Port, - float * DataLocation) -{ - struct delay_impl *impl = Instance; - if (Port > 2) - return; - impl->port[Port] = DataLocation; -} - -static void delay_run(void * Instance, unsigned long SampleCount) -{ - struct delay_impl *impl = Instance; - float *in = impl->port[1], *out = impl->port[0]; - float delay = impl->port[2][0]; - unsigned long n; - uint32_t r, w; - - if (delay != impl->delay) { - impl->delay_samples = SPA_CLAMP((uint32_t)(delay * impl->rate), 0u, impl->buffer_samples-1); - impl->delay = delay; - } - r = impl->ptr; - w = impl->ptr + impl->delay_samples; - if (w >= impl->buffer_samples) - w -= impl->buffer_samples; - - for (n = 0; n < SampleCount; n++) { - impl->buffer[w] = in[n]; - out[n] = impl->buffer[r]; - if (++r >= impl->buffer_samples) - r = 0; - if (++w >= impl->buffer_samples) - w = 0; - } - impl->ptr = r; -} - -static struct fc_port delay_ports[] = { - { .index = 0, - .name = "Out", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, - }, - { .index = 1, - .name = "In", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 2, - .name = "Delay (s)", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 0.0f, .min = 0.0f, .max = 100.0f - }, -}; - -static const struct fc_descriptor delay_desc = { - .name = "delay", - - .n_ports = 3, - .ports = delay_ports, - - .instantiate = delay_instantiate, - .connect_port = delay_connect_port, - .run = delay_run, - .cleanup = delay_cleanup, -}; - -/* invert */ -static void invert_run(void * Instance, unsigned long SampleCount) -{ - struct builtin *impl = Instance; - float *in = impl->port[1], *out = impl->port[0]; - unsigned long n; - for (n = 0; n < SampleCount; n++) - out[n] = -in[n]; -} - -static struct fc_port invert_ports[] = { - { .index = 0, - .name = "Out", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, - }, - { .index = 1, - .name = "In", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, -}; - -static const struct fc_descriptor invert_desc = { - .name = "invert", - - .n_ports = 2, - .ports = invert_ports, - - .instantiate = builtin_instantiate, - .connect_port = builtin_connect_port, - .run = invert_run, - .cleanup = builtin_cleanup, -}; - -/* clamp */ -static void clamp_run(void * Instance, unsigned long SampleCount) -{ - struct builtin *impl = Instance; - float min = impl->port[4][0], max = impl->port[5][0]; - float *in = impl->port[1], *out = impl->port[0]; - float *ctrl = impl->port[3], *notify = impl->port[2]; - - if (in != NULL && out != NULL) { - unsigned long n; - for (n = 0; n < SampleCount; n++) - out[n] = SPA_CLAMPF(in[n], min, max); - } - if (ctrl != NULL && notify != NULL) - notify[0] = SPA_CLAMPF(ctrl[0], min, max); -} - -static struct fc_port clamp_ports[] = { - { .index = 0, - .name = "Out", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, - }, - { .index = 1, - .name = "In", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 2, - .name = "Notify", - .flags = FC_PORT_OUTPUT | FC_PORT_CONTROL, - }, - { .index = 3, - .name = "Control", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - }, - { .index = 4, - .name = "Min", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 0.0f, .min = -100.0f, .max = 100.0f - }, - { .index = 5, - .name = "Max", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0f, .min = -100.0f, .max = 100.0f - }, -}; - -static const struct fc_descriptor clamp_desc = { - .name = "clamp", - .flags = FC_DESCRIPTOR_SUPPORTS_NULL_DATA, - - .n_ports = SPA_N_ELEMENTS(clamp_ports), - .ports = clamp_ports, - - .instantiate = builtin_instantiate, - .connect_port = builtin_connect_port, - .run = clamp_run, - .cleanup = builtin_cleanup, -}; - -/* linear */ -static void linear_run(void * Instance, unsigned long SampleCount) -{ - struct builtin *impl = Instance; - float mult = impl->port[4][0], add = impl->port[5][0]; - float *in = impl->port[1], *out = impl->port[0]; - float *ctrl = impl->port[3], *notify = impl->port[2]; - - if (in != NULL && out != NULL) - dsp_ops_linear(impl->plugin->dsp_ops, out, in, mult, add, SampleCount); - - if (ctrl != NULL && notify != NULL) - notify[0] = ctrl[0] * mult + add; -} - -static struct fc_port linear_ports[] = { - { .index = 0, - .name = "Out", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, - }, - { .index = 1, - .name = "In", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 2, - .name = "Notify", - .flags = FC_PORT_OUTPUT | FC_PORT_CONTROL, - }, - { .index = 3, - .name = "Control", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - }, - { .index = 4, - .name = "Mult", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0f, .min = -10.0f, .max = 10.0f - }, - { .index = 5, - .name = "Add", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 0.0f, .min = -10.0f, .max = 10.0f - }, -}; - -static const struct fc_descriptor linear_desc = { - .name = "linear", - .flags = FC_DESCRIPTOR_SUPPORTS_NULL_DATA, - - .n_ports = SPA_N_ELEMENTS(linear_ports), - .ports = linear_ports, - - .instantiate = builtin_instantiate, - .connect_port = builtin_connect_port, - .run = linear_run, - .cleanup = builtin_cleanup, -}; - - -/* reciprocal */ -static void recip_run(void * Instance, unsigned long SampleCount) -{ - struct builtin *impl = Instance; - float *in = impl->port[1], *out = impl->port[0]; - float *ctrl = impl->port[3], *notify = impl->port[2]; - - if (in != NULL && out != NULL) { - unsigned long n; - for (n = 0; n < SampleCount; n++) { - if (in[0] == 0.0f) - out[n] = 0.0f; - else - out[n] = 1.0f / in[n]; - } - } - if (ctrl != NULL && notify != NULL) { - if (ctrl[0] == 0.0f) - notify[0] = 0.0f; - else - notify[0] = 1.0f / ctrl[0]; - } -} - -static struct fc_port recip_ports[] = { - { .index = 0, - .name = "Out", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, - }, - { .index = 1, - .name = "In", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 2, - .name = "Notify", - .flags = FC_PORT_OUTPUT | FC_PORT_CONTROL, - }, - { .index = 3, - .name = "Control", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - }, -}; - -static const struct fc_descriptor recip_desc = { - .name = "recip", - .flags = FC_DESCRIPTOR_SUPPORTS_NULL_DATA, - - .n_ports = SPA_N_ELEMENTS(recip_ports), - .ports = recip_ports, - - .instantiate = builtin_instantiate, - .connect_port = builtin_connect_port, - .run = recip_run, - .cleanup = builtin_cleanup, -}; - -/* exp */ -static void exp_run(void * Instance, unsigned long SampleCount) -{ - struct builtin *impl = Instance; - float base = impl->port[4][0]; - float *in = impl->port[1], *out = impl->port[0]; - float *ctrl = impl->port[3], *notify = impl->port[2]; - - if (in != NULL && out != NULL) { - unsigned long n; - for (n = 0; n < SampleCount; n++) - out[n] = powf(base, in[n]); - } - if (ctrl != NULL && notify != NULL) - notify[0] = powf(base, ctrl[0]); -} - -static struct fc_port exp_ports[] = { - { .index = 0, - .name = "Out", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, - }, - { .index = 1, - .name = "In", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 2, - .name = "Notify", - .flags = FC_PORT_OUTPUT | FC_PORT_CONTROL, - }, - { .index = 3, - .name = "Control", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - }, - { .index = 4, - .name = "Base", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = (float)M_E, .min = -10.0f, .max = 10.0f - }, -}; - -static const struct fc_descriptor exp_desc = { - .name = "exp", - .flags = FC_DESCRIPTOR_SUPPORTS_NULL_DATA, - - .n_ports = SPA_N_ELEMENTS(exp_ports), - .ports = exp_ports, - - .instantiate = builtin_instantiate, - .connect_port = builtin_connect_port, - .run = exp_run, - .cleanup = builtin_cleanup, -}; - -/* log */ -static void log_run(void * Instance, unsigned long SampleCount) -{ - struct builtin *impl = Instance; - float base = impl->port[4][0]; - float m1 = impl->port[5][0]; - float m2 = impl->port[6][0]; - float *in = impl->port[1], *out = impl->port[0]; - float *ctrl = impl->port[3], *notify = impl->port[2]; - float lb = log2f(base); - - if (in != NULL && out != NULL) { - unsigned long n; - for (n = 0; n < SampleCount; n++) - out[n] = m2 * log2f(fabsf(in[n] * m1)) / lb; - } - if (ctrl != NULL && notify != NULL) - notify[0] = m2 * log2f(fabsf(ctrl[0] * m1)) / lb; -} - -static struct fc_port log_ports[] = { - { .index = 0, - .name = "Out", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, - }, - { .index = 1, - .name = "In", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 2, - .name = "Notify", - .flags = FC_PORT_OUTPUT | FC_PORT_CONTROL, - }, - { .index = 3, - .name = "Control", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - }, - { .index = 4, - .name = "Base", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = (float)M_E, .min = 2.0f, .max = 100.0f - }, - { .index = 5, - .name = "M1", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0f, .min = -10.0f, .max = 10.0f - }, - { .index = 6, - .name = "M2", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0f, .min = -10.0f, .max = 10.0f - }, -}; - -static const struct fc_descriptor log_desc = { - .name = "log", - .flags = FC_DESCRIPTOR_SUPPORTS_NULL_DATA, - - .n_ports = SPA_N_ELEMENTS(log_ports), - .ports = log_ports, - - .instantiate = builtin_instantiate, - .connect_port = builtin_connect_port, - .run = log_run, - .cleanup = builtin_cleanup, -}; - -/* mult */ -static void mult_run(void * Instance, unsigned long SampleCount) -{ - struct builtin *impl = Instance; - int i, n_src = 0; - float *out = impl->port[0]; - const void *src[8]; - - if (out == NULL) - return; - - for (i = 0; i < 8; i++) { - float *in = impl->port[1+i]; - - if (in == NULL) - continue; - - src[n_src++] = in; - } - dsp_ops_mult(impl->plugin->dsp_ops, out, src, n_src, SampleCount); -} - -static struct fc_port mult_ports[] = { - { .index = 0, - .name = "Out", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, - }, - { .index = 1, - .name = "In 1", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 2, - .name = "In 2", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 3, - .name = "In 3", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 4, - .name = "In 4", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 5, - .name = "In 5", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 6, - .name = "In 6", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 7, - .name = "In 7", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, - { .index = 8, - .name = "In 8", - .flags = FC_PORT_INPUT | FC_PORT_AUDIO, - }, -}; - -static const struct fc_descriptor mult_desc = { - .name = "mult", - .flags = FC_DESCRIPTOR_SUPPORTS_NULL_DATA, - - .n_ports = SPA_N_ELEMENTS(mult_ports), - .ports = mult_ports, - - .instantiate = builtin_instantiate, - .connect_port = builtin_connect_port, - .run = mult_run, - .cleanup = builtin_cleanup, -}; - -#define M_PI_M2f (float)(M_PI+M_PI) - -/* sine */ -static void sine_run(void * Instance, unsigned long SampleCount) -{ - struct builtin *impl = Instance; - float *out = impl->port[0]; - float *notify = impl->port[1]; - float freq = impl->port[2][0]; - float ampl = impl->port[3][0]; - float offs = impl->port[5][0]; - unsigned long n; - - for (n = 0; n < SampleCount; n++) { - if (out != NULL) - out[n] = sinf(impl->accum) * ampl + offs; - if (notify != NULL && n == 0) - notify[0] = sinf(impl->accum) * ampl + offs; - - impl->accum += M_PI_M2f * freq / impl->rate; - if (impl->accum >= M_PI_M2f) - impl->accum -= M_PI_M2f; - } -} - -static struct fc_port sine_ports[] = { - { .index = 0, - .name = "Out", - .flags = FC_PORT_OUTPUT | FC_PORT_AUDIO, - }, - { .index = 1, - .name = "Notify", - .flags = FC_PORT_OUTPUT | FC_PORT_CONTROL, - }, - { .index = 2, - .name = "Freq", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 440.0f, .min = 0.0f, .max = 1000000.0f - }, - { .index = 3, - .name = "Ampl", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 1.0, .min = 0.0f, .max = 10.0f - }, - { .index = 4, - .name = "Phase", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 0.0f, .min = (float)-M_PI, .max = (float)M_PI - }, - { .index = 5, - .name = "Offset", - .flags = FC_PORT_INPUT | FC_PORT_CONTROL, - .def = 0.0f, .min = -10.0f, .max = 10.0f - }, -}; - -static const struct fc_descriptor sine_desc = { - .name = "sine", - .flags = FC_DESCRIPTOR_SUPPORTS_NULL_DATA, - - .n_ports = SPA_N_ELEMENTS(sine_ports), - .ports = sine_ports, - - .instantiate = builtin_instantiate, - .connect_port = builtin_connect_port, - .run = sine_run, - .cleanup = builtin_cleanup, -}; - -static const struct fc_descriptor * builtin_descriptor(unsigned long Index) -{ - switch(Index) { - case 0: - return &mixer_desc; - case 1: - return &bq_lowpass_desc; - case 2: - return &bq_highpass_desc; - case 3: - return &bq_bandpass_desc; - case 4: - return &bq_lowshelf_desc; - case 5: - return &bq_highshelf_desc; - case 6: - return &bq_peaking_desc; - case 7: - return &bq_notch_desc; - case 8: - return &bq_allpass_desc; - case 9: - return ©_desc; - case 10: - return &convolve_desc; - case 11: - return &delay_desc; - case 12: - return &invert_desc; - case 13: - return &bq_raw_desc; - case 14: - return &clamp_desc; - case 15: - return &linear_desc; - case 16: - return &recip_desc; - case 17: - return &exp_desc; - case 18: - return &log_desc; - case 19: - return &mult_desc; - case 20: - return &sine_desc; - } - return NULL; -} - -static const struct fc_descriptor *builtin_make_desc(struct fc_plugin *plugin, const char *name) -{ - unsigned long i; - for (i = 0; ;i++) { - const struct fc_descriptor *d = builtin_descriptor(i); - if (d == NULL) - break; - if (spa_streq(d->name, name)) - return d; - } - return NULL; -} - -static void builtin_plugin_unload(struct fc_plugin *p) -{ - free(p); -} - -struct fc_plugin *load_builtin_plugin(const struct spa_support *support, uint32_t n_support, - struct dsp_ops *dsp, const char *plugin, const struct spa_dict *info) -{ - struct plugin *impl = calloc (1, sizeof (struct plugin)); - impl->plugin.make_desc = builtin_make_desc; - impl->plugin.unload = builtin_plugin_unload; - impl->dsp_ops = dsp; - pffft_select_cpu(dsp->cpu_flags); - return (struct fc_plugin *) impl; -} diff --git a/src/modules/module-filter-chain/dsp-ops-avx.c b/src/modules/module-filter-chain/dsp-ops-avx.c deleted file mode 100644 index b2f7a683..00000000 --- a/src/modules/module-filter-chain/dsp-ops-avx.c +++ /dev/null @@ -1,65 +0,0 @@ -/* Spa */ -/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#include <string.h> -#include <stdio.h> -#include <math.h> - -#include <spa/utils/defs.h> - -#include "dsp-ops.h" - -#include <immintrin.h> - -void dsp_sum_avx(struct dsp_ops *ops, float *r, const float *a, const float *b, uint32_t n_samples) -{ - uint32_t n, unrolled; - __m256 in[4]; - - unrolled = n_samples & ~31; - - if (SPA_LIKELY(SPA_IS_ALIGNED(r, 32)) && - SPA_LIKELY(SPA_IS_ALIGNED(a, 32)) && - SPA_LIKELY(SPA_IS_ALIGNED(b, 32))) { - for (n = 0; n < unrolled; n += 32) { - in[0] = _mm256_load_ps(&a[n+ 0]); - in[1] = _mm256_load_ps(&a[n+ 8]); - in[2] = _mm256_load_ps(&a[n+16]); - in[3] = _mm256_load_ps(&a[n+24]); - - in[0] = _mm256_add_ps(in[0], _mm256_load_ps(&b[n+ 0])); - in[1] = _mm256_add_ps(in[1], _mm256_load_ps(&b[n+ 8])); - in[2] = _mm256_add_ps(in[2], _mm256_load_ps(&b[n+16])); - in[3] = _mm256_add_ps(in[3], _mm256_load_ps(&b[n+24])); - - _mm256_store_ps(&r[n+ 0], in[0]); - _mm256_store_ps(&r[n+ 8], in[1]); - _mm256_store_ps(&r[n+16], in[2]); - _mm256_store_ps(&r[n+24], in[3]); - } - } else { - for (n = 0; n < unrolled; n += 32) { - in[0] = _mm256_loadu_ps(&a[n+ 0]); - in[1] = _mm256_loadu_ps(&a[n+ 8]); - in[2] = _mm256_loadu_ps(&a[n+16]); - in[3] = _mm256_loadu_ps(&a[n+24]); - - in[0] = _mm256_add_ps(in[0], _mm256_loadu_ps(&b[n+ 0])); - in[1] = _mm256_add_ps(in[1], _mm256_loadu_ps(&b[n+ 8])); - in[2] = _mm256_add_ps(in[2], _mm256_loadu_ps(&b[n+16])); - in[3] = _mm256_add_ps(in[3], _mm256_loadu_ps(&b[n+24])); - - _mm256_storeu_ps(&r[n+ 0], in[0]); - _mm256_storeu_ps(&r[n+ 8], in[1]); - _mm256_storeu_ps(&r[n+16], in[2]); - _mm256_storeu_ps(&r[n+24], in[3]); - } - } - for (; n < n_samples; n++) { - __m128 in[1]; - in[0] = _mm_load_ss(&a[n]); - in[0] = _mm_add_ss(in[0], _mm_load_ss(&b[n])); - _mm_store_ss(&r[n], in[0]); - } -} diff --git a/src/modules/module-filter-chain/dsp-ops-c.c b/src/modules/module-filter-chain/dsp-ops-c.c deleted file mode 100644 index 82d20b50..00000000 --- a/src/modules/module-filter-chain/dsp-ops-c.c +++ /dev/null @@ -1,196 +0,0 @@ -/* Spa */ -/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#include <string.h> -#include <stdio.h> -#include <math.h> -#include <float.h> - -#include <spa/utils/defs.h> - -#include "pffft.h" -#include "dsp-ops.h" - -void dsp_clear_c(struct dsp_ops *ops, void * SPA_RESTRICT dst, uint32_t n_samples) -{ - memset(dst, 0, sizeof(float) * n_samples); -} - -static inline void dsp_add_c(struct dsp_ops *ops, void * SPA_RESTRICT dst, - const void * SPA_RESTRICT src, uint32_t n_samples) -{ - uint32_t i; - const float *s = src; - float *d = dst; - for (i = 0; i < n_samples; i++) - d[i] += s[i]; -} - -static inline void dsp_gain_c(struct dsp_ops *ops, void * SPA_RESTRICT dst, - const void * SPA_RESTRICT src, float gain, uint32_t n_samples) -{ - uint32_t i; - const float *s = src; - float *d = dst; - if (gain == 0.0f) - dsp_clear_c(ops, dst, n_samples); - else if (gain == 1.0f) - dsp_copy_c(ops, dst, src, n_samples); - else { - for (i = 0; i < n_samples; i++) - d[i] = s[i] * gain; - } -} - -static inline void dsp_gain_add_c(struct dsp_ops *ops, void * SPA_RESTRICT dst, - const void * SPA_RESTRICT src, float gain, uint32_t n_samples) -{ - uint32_t i; - const float *s = src; - float *d = dst; - - if (gain == 0.0f) - return; - else if (gain == 1.0f) - dsp_add_c(ops, dst, src, n_samples); - else { - for (i = 0; i < n_samples; i++) - d[i] += s[i] * gain; - } -} - - -void dsp_copy_c(struct dsp_ops *ops, void * SPA_RESTRICT dst, - const void * SPA_RESTRICT src, uint32_t n_samples) -{ - if (dst != src) - spa_memcpy(dst, src, sizeof(float) * n_samples); -} - -void dsp_mix_gain_c(struct dsp_ops *ops, - void * SPA_RESTRICT dst, - const void * SPA_RESTRICT src[], - float gain[], uint32_t n_src, uint32_t n_samples) -{ - uint32_t i; - if (n_src == 0) { - dsp_clear_c(ops, dst, n_samples); - } else { - dsp_gain_c(ops, dst, src[0], gain[0], n_samples); - for (i = 1; i < n_src; i++) - dsp_gain_add_c(ops, dst, src[i], gain[i], n_samples); - } -} - -static inline void dsp_mult1_c(struct dsp_ops *ops, void * SPA_RESTRICT dst, - const void * SPA_RESTRICT src, uint32_t n_samples) -{ - uint32_t i; - const float *s = src; - float *d = dst; - for (i = 0; i < n_samples; i++) - d[i] *= s[i]; -} - -void dsp_mult_c(struct dsp_ops *ops, - void * SPA_RESTRICT dst, - const void * SPA_RESTRICT src[], - uint32_t n_src, uint32_t n_samples) -{ - uint32_t i; - if (n_src == 0) { - dsp_clear_c(ops, dst, n_samples); - } else { - dsp_copy_c(ops, dst, src[0], n_samples); - for (i = 1; i < n_src; i++) - dsp_mult1_c(ops, dst, src[i], n_samples); - } -} - -void dsp_biquad_run_c(struct dsp_ops *ops, struct biquad *bq, - float *out, const float *in, uint32_t n_samples) -{ - float x, y, x1, x2; - float b0, b1, b2, a1, a2; - uint32_t i; - - x1 = bq->x1; - x2 = bq->x2; - b0 = bq->b0; - b1 = bq->b1; - b2 = bq->b2; - a1 = bq->a1; - a2 = bq->a2; - for (i = 0; i < n_samples; i++) { - x = in[i]; - y = b0 * x + x1; - x1 = b1 * x - a1 * y + x2; - x2 = b2 * x - a2 * y; - out[i] = y; - } -#define F(x) (-FLT_MIN < (x) && (x) < FLT_MIN ? 0.0f : (x)) - bq->x1 = F(x1); - bq->x2 = F(x2); -#undef F -} - -void dsp_sum_c(struct dsp_ops *ops, float * dst, - const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t n_samples) -{ - uint32_t i; - for (i = 0; i < n_samples; i++) - dst[i] = a[i] + b[i]; -} - -void dsp_linear_c(struct dsp_ops *ops, float * dst, - const float * SPA_RESTRICT src, const float mult, - const float add, uint32_t n_samples) -{ - uint32_t i; - if (add == 0.0f) { - dsp_gain_c(ops, dst, src, mult, n_samples); - } else { - if (mult == 0.0f) { - for (i = 0; i < n_samples; i++) - dst[i] = add; - } else if (mult == 1.0f) { - for (i = 0; i < n_samples; i++) - dst[i] = src[i] + add; - } else { - for (i = 0; i < n_samples; i++) - dst[i] = mult * src[i] + add; - } - } -} - -void *dsp_fft_new_c(struct dsp_ops *ops, int32_t size, bool real) -{ - return pffft_new_setup(size, real ? PFFFT_REAL : PFFFT_COMPLEX); -} - -void dsp_fft_free_c(struct dsp_ops *ops, void *fft) -{ - pffft_destroy_setup(fft); -} -void dsp_fft_run_c(struct dsp_ops *ops, void *fft, int direction, - const float * SPA_RESTRICT src, float * SPA_RESTRICT dst) -{ - pffft_transform(fft, src, dst, NULL, direction < 0 ? PFFFT_BACKWARD : PFFFT_FORWARD); -} - -void dsp_fft_cmul_c(struct dsp_ops *ops, void *fft, - float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, - const float * SPA_RESTRICT b, uint32_t len, const float scale) -{ - pffft_zconvolve(fft, a, b, dst, scale); -} - -void dsp_fft_cmuladd_c(struct dsp_ops *ops, void *fft, - float * SPA_RESTRICT dst, const float * SPA_RESTRICT src, - const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, - uint32_t len, const float scale) -{ - pffft_zconvolve_accumulate(fft, a, b, src, dst, scale); -} - diff --git a/src/modules/module-filter-chain/dsp-ops-sse.c b/src/modules/module-filter-chain/dsp-ops-sse.c deleted file mode 100644 index 9eb94f79..00000000 --- a/src/modules/module-filter-chain/dsp-ops-sse.c +++ /dev/null @@ -1,122 +0,0 @@ -/* Spa */ -/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#include <string.h> -#include <stdio.h> -#include <math.h> - -#include <spa/utils/defs.h> - -#include "dsp-ops.h" - -#include <xmmintrin.h> - -void dsp_mix_gain_sse(struct dsp_ops *ops, - void * SPA_RESTRICT dst, - const void * SPA_RESTRICT src[], - float gain[], uint32_t n_src, uint32_t n_samples) -{ - if (n_src == 0) { - memset(dst, 0, n_samples * sizeof(float)); - } else if (n_src == 1 && gain[0] == 1.0f) { - if (dst != src[0]) - spa_memcpy(dst, src[0], n_samples * sizeof(float)); - } else { - uint32_t n, i, unrolled; - __m128 in[4], g; - 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; - - for (n = 0; n < unrolled; n += 16) { - g = _mm_set1_ps(gain[0]); - in[0] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 0])); - in[1] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 4])); - in[2] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+ 8])); - in[3] = _mm_mul_ps(g, _mm_load_ps(&s[0][n+12])); - - for (i = 1; i < n_src; i++) { - g = _mm_set1_ps(gain[i]); - in[0] = _mm_add_ps(in[0], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 0]))); - in[1] = _mm_add_ps(in[1], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 4]))); - in[2] = _mm_add_ps(in[2], _mm_mul_ps(g, _mm_load_ps(&s[i][n+ 8]))); - in[3] = _mm_add_ps(in[3], _mm_mul_ps(g, _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++) { - g = _mm_set_ss(gain[0]); - in[0] = _mm_mul_ss(g, _mm_load_ss(&s[0][n])); - for (i = 1; i < n_src; i++) { - g = _mm_set_ss(gain[i]); - in[0] = _mm_add_ss(in[0], _mm_mul_ss(g, _mm_load_ss(&s[i][n]))); - } - _mm_store_ss(&d[n], in[0]); - } - } -} - -void dsp_sum_sse(struct dsp_ops *ops, float *r, const float *a, const float *b, uint32_t n_samples) -{ - uint32_t n, unrolled; - __m128 in[4]; - - unrolled = n_samples & ~15; - - if (SPA_LIKELY(SPA_IS_ALIGNED(r, 16)) && - SPA_LIKELY(SPA_IS_ALIGNED(a, 16)) && - SPA_LIKELY(SPA_IS_ALIGNED(b, 16))) { - for (n = 0; n < unrolled; n += 16) { - in[0] = _mm_load_ps(&a[n+ 0]); - in[1] = _mm_load_ps(&a[n+ 4]); - in[2] = _mm_load_ps(&a[n+ 8]); - in[3] = _mm_load_ps(&a[n+12]); - - in[0] = _mm_add_ps(in[0], _mm_load_ps(&b[n+ 0])); - in[1] = _mm_add_ps(in[1], _mm_load_ps(&b[n+ 4])); - in[2] = _mm_add_ps(in[2], _mm_load_ps(&b[n+ 8])); - in[3] = _mm_add_ps(in[3], _mm_load_ps(&b[n+12])); - - _mm_store_ps(&r[n+ 0], in[0]); - _mm_store_ps(&r[n+ 4], in[1]); - _mm_store_ps(&r[n+ 8], in[2]); - _mm_store_ps(&r[n+12], in[3]); - } - } else { - for (n = 0; n < unrolled; n += 16) { - in[0] = _mm_loadu_ps(&a[n+ 0]); - in[1] = _mm_loadu_ps(&a[n+ 4]); - in[2] = _mm_loadu_ps(&a[n+ 8]); - in[3] = _mm_loadu_ps(&a[n+12]); - - in[0] = _mm_add_ps(in[0], _mm_loadu_ps(&b[n+ 0])); - in[1] = _mm_add_ps(in[1], _mm_loadu_ps(&b[n+ 4])); - in[2] = _mm_add_ps(in[2], _mm_loadu_ps(&b[n+ 8])); - in[3] = _mm_add_ps(in[3], _mm_loadu_ps(&b[n+12])); - - _mm_storeu_ps(&r[n+ 0], in[0]); - _mm_storeu_ps(&r[n+ 4], in[1]); - _mm_storeu_ps(&r[n+ 8], in[2]); - _mm_storeu_ps(&r[n+12], in[3]); - } - } - for (; n < n_samples; n++) { - in[0] = _mm_load_ss(&a[n]); - in[0] = _mm_add_ss(in[0], _mm_load_ss(&b[n])); - _mm_store_ss(&r[n], in[0]); - } -} diff --git a/src/modules/module-filter-chain/dsp-ops.h b/src/modules/module-filter-chain/dsp-ops.h deleted file mode 100644 index a1a7ab90..00000000 --- a/src/modules/module-filter-chain/dsp-ops.h +++ /dev/null @@ -1,136 +0,0 @@ -/* Spa */ -/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#ifndef DSP_OPS_H -#define DSP_OPS_H - -#include <spa/utils/defs.h> - -#include "biquad.h" - -struct dsp_ops; - -struct dsp_ops_funcs { - void (*clear) (struct dsp_ops *ops, void * SPA_RESTRICT dst, uint32_t n_samples); - void (*copy) (struct dsp_ops *ops, - void * SPA_RESTRICT dst, - const void * SPA_RESTRICT src, uint32_t n_samples); - void (*mix_gain) (struct dsp_ops *ops, - void * SPA_RESTRICT dst, - const void * SPA_RESTRICT src[], - float gain[], uint32_t n_src, uint32_t n_samples); - void (*biquad_run) (struct dsp_ops *ops, struct biquad *bq, - float *out, const float *in, uint32_t n_samples); - void (*sum) (struct dsp_ops *ops, - float * dst, const float * SPA_RESTRICT a, - const float * SPA_RESTRICT b, uint32_t n_samples); - - void *(*fft_new) (struct dsp_ops *ops, int32_t size, bool real); - void (*fft_free) (struct dsp_ops *ops, void *fft); - void (*fft_run) (struct dsp_ops *ops, void *fft, int direction, - const float * SPA_RESTRICT src, float * SPA_RESTRICT dst); - void (*fft_cmul) (struct dsp_ops *ops, void *fft, - float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, - const float * SPA_RESTRICT b, uint32_t len, const float scale); - void (*fft_cmuladd) (struct dsp_ops *ops, void *fft, - float * dst, const float * src, - const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, - uint32_t len, const float scale); - void (*linear) (struct dsp_ops *ops, - float * dst, const float * SPA_RESTRICT src, - const float mult, const float add, uint32_t n_samples); - void (*mult) (struct dsp_ops *ops, - void * SPA_RESTRICT dst, - const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples); -}; - -struct dsp_ops { - uint32_t cpu_flags; - - void (*free) (struct dsp_ops *ops); - - struct dsp_ops_funcs funcs; - - const void *priv; -}; - -int dsp_ops_init(struct dsp_ops *ops, uint32_t cpu_flags); - -#define dsp_ops_free(ops) (ops)->free(ops) - -#define dsp_ops_clear(ops,...) (ops)->funcs.clear(ops, __VA_ARGS__) -#define dsp_ops_copy(ops,...) (ops)->funcs.copy(ops, __VA_ARGS__) -#define dsp_ops_mix_gain(ops,...) (ops)->funcs.mix_gain(ops, __VA_ARGS__) -#define dsp_ops_biquad_run(ops,...) (ops)->funcs.biquad_run(ops, __VA_ARGS__) -#define dsp_ops_sum(ops,...) (ops)->funcs.sum(ops, __VA_ARGS__) -#define dsp_ops_linear(ops,...) (ops)->funcs.linear(ops, __VA_ARGS__) -#define dsp_ops_mult(ops,...) (ops)->funcs.mult(ops, __VA_ARGS__) - -#define dsp_ops_fft_new(ops,...) (ops)->funcs.fft_new(ops, __VA_ARGS__) -#define dsp_ops_fft_free(ops,...) (ops)->funcs.fft_free(ops, __VA_ARGS__) -#define dsp_ops_fft_run(ops,...) (ops)->funcs.fft_run(ops, __VA_ARGS__) -#define dsp_ops_fft_cmul(ops,...) (ops)->funcs.fft_cmul(ops, __VA_ARGS__) -#define dsp_ops_fft_cmuladd(ops,...) (ops)->funcs.fft_cmuladd(ops, __VA_ARGS__) - -#define MAKE_CLEAR_FUNC(arch) \ -void dsp_clear_##arch(struct dsp_ops *ops, void * SPA_RESTRICT dst, uint32_t n_samples) -#define MAKE_COPY_FUNC(arch) \ -void dsp_copy_##arch(struct dsp_ops *ops, void * SPA_RESTRICT dst, \ - const void * SPA_RESTRICT src, uint32_t n_samples) -#define MAKE_MIX_GAIN_FUNC(arch) \ -void dsp_mix_gain_##arch(struct dsp_ops *ops, void * SPA_RESTRICT dst, \ - const void * SPA_RESTRICT src[], float gain[], uint32_t n_src, uint32_t n_samples) -#define MAKE_BIQUAD_RUN_FUNC(arch) \ -void dsp_biquad_run_##arch (struct dsp_ops *ops, struct biquad *bq, \ - float *out, const float *in, uint32_t n_samples) -#define MAKE_SUM_FUNC(arch) \ -void dsp_sum_##arch (struct dsp_ops *ops, float * SPA_RESTRICT dst, \ - const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, uint32_t n_samples) -#define MAKE_LINEAR_FUNC(arch) \ -void dsp_linear_##arch (struct dsp_ops *ops, float * SPA_RESTRICT dst, \ - const float * SPA_RESTRICT src, const float mult, const float add, uint32_t n_samples) -#define MAKE_MULT_FUNC(arch) \ -void dsp_mult_##arch(struct dsp_ops *ops, void * SPA_RESTRICT dst, \ - const void * SPA_RESTRICT src[], uint32_t n_src, uint32_t n_samples) - -#define MAKE_FFT_NEW_FUNC(arch) \ -void *dsp_fft_new_##arch(struct dsp_ops *ops, int32_t size, bool real) -#define MAKE_FFT_FREE_FUNC(arch) \ -void dsp_fft_free_##arch(struct dsp_ops *ops, void *fft) -#define MAKE_FFT_RUN_FUNC(arch) \ -void dsp_fft_run_##arch(struct dsp_ops *ops, void *fft, int direction, \ - const float * SPA_RESTRICT src, float * SPA_RESTRICT dst) -#define MAKE_FFT_CMUL_FUNC(arch) \ -void dsp_fft_cmul_##arch(struct dsp_ops *ops, void *fft, \ - float * SPA_RESTRICT dst, const float * SPA_RESTRICT a, \ - const float * SPA_RESTRICT b, uint32_t len, const float scale) -#define MAKE_FFT_CMULADD_FUNC(arch) \ -void dsp_fft_cmuladd_##arch(struct dsp_ops *ops, void *fft, \ - float * dst, const float * src, \ - const float * SPA_RESTRICT a, const float * SPA_RESTRICT b, \ - uint32_t len, const float scale) - -MAKE_CLEAR_FUNC(c); -MAKE_COPY_FUNC(c); -MAKE_MIX_GAIN_FUNC(c); -MAKE_BIQUAD_RUN_FUNC(c); -MAKE_SUM_FUNC(c); -MAKE_LINEAR_FUNC(c); -MAKE_MULT_FUNC(c); - -MAKE_FFT_NEW_FUNC(c); -MAKE_FFT_FREE_FUNC(c); -MAKE_FFT_RUN_FUNC(c); -MAKE_FFT_CMUL_FUNC(c); -MAKE_FFT_CMULADD_FUNC(c); - -#if defined (HAVE_SSE) -MAKE_MIX_GAIN_FUNC(sse); -MAKE_SUM_FUNC(sse); -#endif -#if defined (HAVE_AVX) -MAKE_SUM_FUNC(avx); -#endif - -#endif /* DSP_OPS_H */ diff --git a/src/modules/module-filter-chain/ladspa_plugin.c b/src/modules/module-filter-chain/ladspa_plugin.c deleted file mode 100644 index bfc21dc4..00000000 --- a/src/modules/module-filter-chain/ladspa_plugin.c +++ /dev/null @@ -1,256 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#include "config.h" - -#include <dlfcn.h> -#include <math.h> -#include <limits.h> - -#include <spa/utils/defs.h> -#include <spa/utils/list.h> -#include <spa/utils/string.h> - -#include <pipewire/log.h> -#include <pipewire/utils.h> - -#include "plugin.h" -#include "ladspa.h" - -struct plugin { - struct fc_plugin plugin; - void *handle; - LADSPA_Descriptor_Function desc_func; -}; - -struct descriptor { - struct fc_descriptor desc; - const LADSPA_Descriptor *d; -}; - -static void *ladspa_instantiate(const struct fc_plugin *plugin, const struct fc_descriptor *desc, - unsigned long SampleRate, int index, const char *config) -{ - struct descriptor *d = (struct descriptor *)desc; - return d->d->instantiate(d->d, SampleRate); -} - -static const LADSPA_Descriptor *find_desc(LADSPA_Descriptor_Function desc_func, const char *name) -{ - unsigned long i; - for (i = 0; ;i++) { - const LADSPA_Descriptor *d = desc_func(i); - if (d == NULL) - break; - if (spa_streq(d->Label, name)) - return d; - } - return NULL; -} - -static float get_default(struct fc_port *port, LADSPA_PortRangeHintDescriptor hint, - LADSPA_Data lower, LADSPA_Data upper) -{ - LADSPA_Data def; - - switch (hint & LADSPA_HINT_DEFAULT_MASK) { - case LADSPA_HINT_DEFAULT_MINIMUM: - def = lower; - break; - case LADSPA_HINT_DEFAULT_MAXIMUM: - def = upper; - break; - case LADSPA_HINT_DEFAULT_LOW: - if (LADSPA_IS_HINT_LOGARITHMIC(hint)) - def = (LADSPA_Data) expf(logf(lower) * 0.75f + logf(upper) * 0.25f); - else - def = (LADSPA_Data) (lower * 0.75f + upper * 0.25f); - break; - case LADSPA_HINT_DEFAULT_MIDDLE: - if (LADSPA_IS_HINT_LOGARITHMIC(hint)) - def = (LADSPA_Data) expf(logf(lower) * 0.5f + logf(upper) * 0.5f); - else - def = (LADSPA_Data) (lower * 0.5f + upper * 0.5f); - break; - case LADSPA_HINT_DEFAULT_HIGH: - if (LADSPA_IS_HINT_LOGARITHMIC(hint)) - def = (LADSPA_Data) expf(logf(lower) * 0.25f + logf(upper) * 0.75f); - else - def = (LADSPA_Data) (lower * 0.25f + upper * 0.75f); - break; - case LADSPA_HINT_DEFAULT_0: - def = 0.0f; - break; - case LADSPA_HINT_DEFAULT_1: - def = 1.0f; - break; - case LADSPA_HINT_DEFAULT_100: - def = 100.0f; - break; - case LADSPA_HINT_DEFAULT_440: - def = 440.0f; - break; - default: - if (upper == lower) - def = upper; - else - def = SPA_CLAMPF(0.5f * upper, lower, upper); - break; - } - if (LADSPA_IS_HINT_INTEGER(hint)) - def = roundf(def); - return def; -} - -static void ladspa_port_update_ranges(struct descriptor *dd, struct fc_port *port) -{ - const LADSPA_Descriptor *d = dd->d; - unsigned long p = port->index; - LADSPA_PortRangeHintDescriptor hint = d->PortRangeHints[p].HintDescriptor; - LADSPA_Data lower, upper; - - lower = d->PortRangeHints[p].LowerBound; - upper = d->PortRangeHints[p].UpperBound; - - port->hint = hint; - port->def = get_default(port, hint, lower, upper); - port->min = lower; - port->max = upper; -} - -static void ladspa_free(const struct fc_descriptor *desc) -{ - struct descriptor *d = (struct descriptor*)desc; - free(d->desc.ports); - free(d); -} - -static const struct fc_descriptor *ladspa_make_desc(struct fc_plugin *plugin, const char *name) -{ - struct plugin *p = (struct plugin *)plugin; - struct descriptor *desc; - const LADSPA_Descriptor *d; - uint32_t i; - - d = find_desc(p->desc_func, name); - if (d == NULL) - return NULL; - - desc = calloc(1, sizeof(*desc)); - desc->d = d; - - desc->desc.instantiate = ladspa_instantiate; - desc->desc.cleanup = d->cleanup; - desc->desc.connect_port = d->connect_port; - desc->desc.activate = d->activate; - desc->desc.deactivate = d->deactivate; - desc->desc.run = d->run; - - desc->desc.free = ladspa_free; - - desc->desc.name = d->Label; - desc->desc.flags = 0; - - desc->desc.n_ports = d->PortCount; - desc->desc.ports = calloc(desc->desc.n_ports, sizeof(struct fc_port)); - - for (i = 0; i < desc->desc.n_ports; i++) { - desc->desc.ports[i].index = i; - desc->desc.ports[i].name = d->PortNames[i]; - desc->desc.ports[i].flags = d->PortDescriptors[i]; - ladspa_port_update_ranges(desc, &desc->desc.ports[i]); - } - return &desc->desc; -} - -static void ladspa_unload(struct fc_plugin *plugin) -{ - struct plugin *p = (struct plugin *)plugin; - if (p->handle) - dlclose(p->handle); - free(p); -} - -static struct fc_plugin *ladspa_handle_load_by_path(const char *path) -{ - struct plugin *p; - int res; - - p = calloc(1, sizeof(*p)); - if (!p) - return NULL; - - p->handle = dlopen(path, RTLD_NOW); - if (!p->handle) { - pw_log_debug("failed to open '%s': %s", path, dlerror()); - res = -ENOENT; - goto exit; - } - - pw_log_info("successfully opened '%s'", path); - - p->desc_func = (LADSPA_Descriptor_Function) dlsym(p->handle, "ladspa_descriptor"); - if (!p->desc_func) { - pw_log_warn("cannot find descriptor function in '%s': %s", path, dlerror()); - res = -ENOSYS; - goto exit; - } - p->plugin.make_desc = ladspa_make_desc; - p->plugin.unload = ladspa_unload; - - return &p->plugin; - -exit: - if (p->handle) - dlclose(p->handle); - free(p); - errno = -res; - return NULL; -} - -struct fc_plugin *load_ladspa_plugin(const struct spa_support *support, uint32_t n_support, - struct dsp_ops *dsp, const char *plugin, const struct spa_dict *info) -{ - struct fc_plugin *pl = NULL; - - if (plugin[0] != '/') { - const char *search_dirs, *p, *state = NULL; - char path[PATH_MAX]; - size_t len; - - search_dirs = getenv("LADSPA_PATH"); - if (!search_dirs) - search_dirs = "/usr/lib64/ladspa:/usr/lib/ladspa:" LIBDIR; - - /* - * set the errno for the case when `ladspa_handle_load_by_path()` - * is never called, which can only happen if the supplied - * LADSPA_PATH contains too long paths - */ - errno = ENAMETOOLONG; - - while ((p = pw_split_walk(search_dirs, ":", &len, &state))) { - int pathlen; - - if (len >= sizeof(path)) - continue; - - pathlen = snprintf(path, sizeof(path), "%.*s/%s.so", (int) len, p, plugin); - if (pathlen < 0 || (size_t) pathlen >= sizeof(path)) - continue; - - pl = ladspa_handle_load_by_path(path); - if (pl != NULL) - break; - } - } - else { - pl = ladspa_handle_load_by_path(plugin); - } - - if (pl == NULL) - pw_log_error("failed to load plugin '%s': %s", plugin, strerror(errno)); - - return pl; -} diff --git a/src/modules/module-filter-chain/plugin.h b/src/modules/module-filter-chain/plugin.h deleted file mode 100644 index 08932615..00000000 --- a/src/modules/module-filter-chain/plugin.h +++ /dev/null @@ -1,86 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#ifndef PLUGIN_H -#define PLUGIN_H - -#include <stdint.h> -#include <stddef.h> - -#include <spa/support/plugin.h> - -#include "dsp-ops.h" - -struct fc_plugin { - const struct fc_descriptor *(*make_desc)(struct fc_plugin *plugin, const char *name); - void (*unload) (struct fc_plugin *plugin); -}; - -struct fc_port { - uint32_t index; - const char *name; -#define FC_PORT_INPUT (1ULL << 0) -#define FC_PORT_OUTPUT (1ULL << 1) -#define FC_PORT_CONTROL (1ULL << 2) -#define FC_PORT_AUDIO (1ULL << 3) - uint64_t flags; - -#define FC_HINT_BOOLEAN (1ULL << 2) -#define FC_HINT_SAMPLE_RATE (1ULL << 3) -#define FC_HINT_INTEGER (1ULL << 5) - uint64_t hint; - float def; - float min; - float max; -}; - -#define FC_IS_PORT_INPUT(x) ((x) & FC_PORT_INPUT) -#define FC_IS_PORT_OUTPUT(x) ((x) & FC_PORT_OUTPUT) -#define FC_IS_PORT_CONTROL(x) ((x) & FC_PORT_CONTROL) -#define FC_IS_PORT_AUDIO(x) ((x) & FC_PORT_AUDIO) - -struct fc_descriptor { - const char *name; -#define FC_DESCRIPTOR_SUPPORTS_NULL_DATA (1ULL << 0) -#define FC_DESCRIPTOR_COPY (1ULL << 1) - uint64_t flags; - - void (*free) (const struct fc_descriptor *desc); - - uint32_t n_ports; - struct fc_port *ports; - - void *(*instantiate) (const struct fc_plugin *plugin, const struct fc_descriptor *desc, - unsigned long SampleRate, int index, const char *config); - - void (*cleanup) (void *instance); - - void (*connect_port) (void *instance, unsigned long port, float *data); - void (*control_changed) (void *instance); - - void (*activate) (void *instance); - void (*deactivate) (void *instance); - - void (*run) (void *instance, unsigned long SampleCount); -}; - -static inline void fc_plugin_free(struct fc_plugin *plugin) -{ - if (plugin->unload) - plugin->unload(plugin); -} - -static inline void fc_descriptor_free(const struct fc_descriptor *desc) -{ - if (desc->free) - desc->free(desc); -} - -#define FC_PLUGIN_LOAD_FUNC "pipewire__filter_chain_plugin_load" - -typedef struct fc_plugin *(fc_plugin_load_func)(const struct spa_support *support, uint32_t n_support, - struct dsp_ops *dsp, const char *path, const struct spa_dict *info); - - -#endif /* PLUGIN_H */ diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 635b4470..48e8c6f8 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -25,6 +25,8 @@ #include <spa/param/audio/format-utils.h> #include <spa/param/latency-utils.h> #include <spa/param/audio/raw.h> +#include <spa/param/audio/raw-json.h> +#include <spa/control/ump-utils.h> #include <pipewire/impl.h> #include <pipewire/i18n.h> @@ -108,7 +110,6 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define MAX_PORTS 128 #define DEFAULT_CLIENT_NAME "PipeWire" -#define DEFAULT_CHANNELS 2 #define DEFAULT_POSITION "[ FL FR ]" #define DEFAULT_MIDI_PORTS 1 @@ -259,23 +260,23 @@ static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_s seq = (struct spa_pod_sequence*)pod; SPA_POD_SEQUENCE_FOREACH(seq, c) { - switch(c->type) { - case SPA_CONTROL_Midi: - { - uint8_t *data = SPA_POD_BODY(&c->value); - size_t size = SPA_POD_BODY_SIZE(&c->value); + uint8_t data[16]; + int size; - if (impl->fix_midi) - fix_midi_event(data, size); + if (c->type != SPA_CONTROL_UMP) + continue; - if ((res = jack.midi_event_write(dst, c->offset, data, size)) < 0) - pw_log_warn("midi %p: can't write event: %s", dst, - spa_strerror(res)); - break; - } - default: - break; - } + size = spa_ump_to_midi(SPA_POD_BODY(&c->value), + SPA_POD_BODY_SIZE(&c->value), data, sizeof(data)); + if (size <= 0) + continue; + + if (impl->fix_midi) + fix_midi_event(data, size); + + if ((res = jack.midi_event_write(dst, c->offset, data, size)) < 0) + pw_log_warn("midi %p: can't write event: %s", dst, + spa_strerror(res)); } } @@ -291,9 +292,19 @@ static void jack_to_midi(float *dst, float *src, uint32_t size) spa_pod_builder_push_sequence(&b, &f, 0); for (i = 0; i < count; i++) { jack_midi_event_t ev; + uint64_t state = 0; + jack.midi_event_get(&ev, src, i); - spa_pod_builder_control(&b, ev.time, SPA_CONTROL_Midi); - spa_pod_builder_bytes(&b, ev.buffer, ev.size); + + while (ev.size > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&ev.buffer, &ev.size, ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + break; + + spa_pod_builder_control(&b, ev.time, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ump, ump_size); + } } spa_pod_builder_pop(&b, &f); } @@ -492,7 +503,7 @@ static void make_stream_ports(struct stream *s) } else { snprintf(name, sizeof(name), "%s_%d", prefix, i - s->info.channels); props = pw_properties_new( - PW_KEY_FORMAT_DSP, "8 bit raw midi", + PW_KEY_FORMAT_DSP, "32 bit raw UMP", PW_KEY_PORT_NAME, name, PW_KEY_PORT_PHYSICAL, "true", NULL); @@ -988,45 +999,15 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - const char *str; - - spa_zero(*info); - info->format = SPA_AUDIO_FORMAT_F32P; - info->rate = 0; - 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)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) diff --git a/src/modules/module-jackdbus-detect.c b/src/modules/module-jackdbus-detect.c index 152ba6c9..23bce1b4 100644 --- a/src/modules/module-jackdbus-detect.c +++ b/src/modules/module-jackdbus-detect.c @@ -40,6 +40,19 @@ * There are no module-specific options, all arguments are passed to * \ref page_module_jack_tunnel. * + * ## Config override + * + * A `module.jackdbus-detect.args` config section can be added + * to override the module arguments. + * + *\code{.unparsed} + * # ~/.config/pipewire/pipewire.conf.d/my-jack-dbus-detect-args.conf + * + * module.jackdbus-detect.args = { + * #tunnel.mode = duplex + * } + *\endcode + * * ## Example configuration *\code{.unparsed} * # ~/.config/pipewire/pipewire.conf.d/my-jack-dbus-detect.conf @@ -359,6 +372,9 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->context = context; impl->properties = args ? pw_properties_new_string(args) : NULL; + if (impl->properties) + pw_context_conf_update_props(context, "module."NAME".args", impl->properties); + impl->conn = spa_dbus_get_connection(dbus, SPA_DBUS_TYPE_SESSION); if (impl->conn == NULL) { res = -errno; diff --git a/src/modules/module-link-factory.c b/src/modules/module-link-factory.c index 56a0b963..ca2b8a75 100644 --- a/src/modules/module-link-factory.c +++ b/src/modules/module-link-factory.c @@ -15,10 +15,109 @@ #include <pipewire/impl.h> /** \page page_module_link_factory Link Factory + * + * Allows clients to create links between ports. + * + * This module creates a new factory. Clients that can see the factory + * can use the factory name (`link-factory`) to create new link + * objects with \ref pw_core_create_object(). It is also possible to create + * objects in the config file. + * + * Object of the \ref PW_TYPE_INTERFACE_Link will be created and a proxy + * to it will be returned. + * + * As an argument to the create_object call, a set of properties will + * control what ports will be linked. * * ## Module Name * * `libpipewire-module-link-factory` + * + * ## Module Options + * + * - `allow.link.passive`: if the `link.passive` property is allowed. Default false. + * By default, the core will decide when a link is passive + * based on the properties of the node and ports. + * + * ## Properties for the create_object call + * + * - `link.output.node`: The output node to use. This can be the node object.id, node.name, + * node.nick, node.description or object.path of a node. When the + * property is not given or NULL, the output port should be + * specified. + * - `link.output.port`: The output port to link. This can be a port object.id, port.name, + * port.alias or object.path. If an output node is specified, the + * port must belong to the node. Finding a port in a node using the + * port.id is deprecated and may lead to unexpected results when the + * port.id also matches an object.id. If no output port is given, an + * output node must be specified and a random (unlinked) port will + * be used from the node. + * - `link.input.node`: The input node to use. This can be the node object.id, node.name, + * node.nick, node.description or object.path of a node. When the + * property is not given or NULL, the input port should be + * specified. + * - `link.input.port`: The input port to link. This can be a port object.id, port.name, + * port.alias or object.path. If an input node is specified, the + * port must belong to the node. Finding a port in a node using the + * port.id is deprecated and may lead to unexpected results when the + * port.id also matches an object.id. If no input port is given, an + * input node must be specified and a random (unlinked) port will + * be used from the node. + * - `object.linger`: Keep the link around even when the client that created it is gone. + * - `link.passive`: The link is passive, meaning that it will not keep nodes busy. + * By default this property is ignored and the node and port properties + * are used to determine the passive state of the link. + * + * ## Example configuration + * + * The module is usually added to the config file of the main pipewire daemon. + * + *\code{.unparsed} + * context.modules = [ + * { name = libpipewire-link-factory + * args = { + * #allow.link.passive = false + * } + * } + * ] + *\endcode + + * ## Config override + * + * A `module.link-factory.args` config section can be added + * to override the module arguments. + * + *\code{.unparsed} + * # ~/.config/pipewire/pipewire.conf.d/my-link-factory-args.conf + * + * module.link-factory.args = { + * #allow.link.passive = false + * } + *\endcode + * + * ## Config objects + * + * To create an object from the factory, one can use the \ref pw_core_create_object() + * method or make an object in the `context.objects` section like: + * + *\code{.unparsed} + * context.objects = [ + * { factory = link-factory + * args = { + * link.output.node = system + * link.output.port = capture_2 + * link.input.node = my-mic + * link.input.port = input_FR + * } + * } + *\endcode + * + * Note that this only works when the ports that need to be linked are available + * at the time the config file is parsed. + * + * ## See also + * + * - `pw-link`: a tool to manage port links */ #define NAME "link-factory" @@ -163,8 +262,7 @@ static void link_state_changed(void *data, enum pw_link_state old, switch (state) { case PW_LINK_STATE_ERROR: - if (ld->linger) - pw_work_queue_add(d->work, ld, 0, destroy_link, ld); + pw_work_queue_add(d->work, ld, 0, destroy_link, ld); break; default: break; @@ -274,11 +372,15 @@ static struct pw_impl_port *find_port(struct pw_context *context, if (find.id != SPA_ID_INVALID) { struct pw_global *global = pw_context_find_global(context, find.id); /* find port by global id */ - if (global != NULL && pw_global_is_type(global, PW_TYPE_INTERFACE_Port)) - return pw_global_get_object(global); + if (global != NULL && pw_global_is_type(global, PW_TYPE_INTERFACE_Port)) { + find.port = pw_global_get_object(global); + if (find.port != NULL && + (node == NULL || pw_impl_port_get_node(find.port) == node)) + return find.port; + } } if (node != NULL) { - /* find port by local id */ + /* find port by local id (deprecated) */ if (find.id != SPA_ID_INVALID) { find.port = pw_impl_node_find_port(node, find.direction, find.id); if (find.port != NULL) @@ -544,6 +646,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) data->props = args ? pw_properties_new_string(args) : NULL; if (data->props) { + pw_context_conf_update_props(context, "module."NAME".args", data->props); + data->allow_passive = pw_properties_get_bool(data->props, "allow.link.passive", false); } diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c index 32eb592f..f4b068be 100644 --- a/src/modules/module-loopback.c +++ b/src/modules/module-loopback.c @@ -17,6 +17,7 @@ #include <spa/utils/json.h> #include <spa/utils/ringbuffer.h> #include <spa/param/latency-utils.h> +#include <spa/param/audio/raw-json.h> #include <spa/debug/types.h> #include <pipewire/impl.h> @@ -77,6 +78,36 @@ * to and from this common channel layout. This can be used to implement up or * downmixing loopback sinks/sources. * + * ## Example configuration of source to sink link + * + * This loopback links a source node to a sink node. You can change the target.object + * properties to match your source/sink node.name. + * + *\code{.unparsed} + * # ~/.config/pipewire/pipewire.conf.d/my-loopback-0.conf + * + * context.modules = [ + * { name = libpipewire-module-loopback + * args = { + * capture.props = { + * # if you want to capture sink monitor ports, + * # uncomment the next line and set the target.object + * # to the sink name. + * #stream.capture.sink = true + * target.object = "alsa_input.usb-C-Media_Electronics_Inc._TONOR_TC-777_Audio_Device-00.mono-fallback" + * node.passive = true + * node.dont-reconnect = true + * } + * playback.props = { + * target.object = "alsa_output.usb-0d8c_USB_Sound_Device-00.analog-surround-71" + * node.dont-reconnect = true + * node.passive = true + * } + * } + * } + * ] + *\endcode + * * ## Example configuration of a virtual sink * * This Virtual sink routes stereo input to the rear channels of a 7.1 sink. @@ -168,6 +199,35 @@ * ] *\endcode * + * ## Example configuration of a downmix source + * + * This Virtual source has 2 input channels and a mono output channel. It will perform + * downmixing from the two first AUX channels of a pro-audio device. + * + *\code{.unparsed} + * # ~/.config/pipewire/pipewire.conf.d/my-loopback-4.conf + * + * context.modules = [ + * { name = libpipewire-module-loopback + * args = { + * node.description = "Downmix Source" + * audio.position = [ AUX0 AUX1 ] + * capture.props = { + * node.name = "effect_input.downmix" + * target.object = "alsa_input.usb-BEHRINGER_UMC404HD_192k-00.pro-input-0" + * node.passive = true + * stream.dont-remix = true + * } + * playback.props = { + * node.name = "effect_output.downmix" + * media.class = Audio/Source + * audio.position = [ MONO ] + * } + * } + * } + * ] + *\endcode + * * ## See also * * `pw-loopback` : a tool that loads the loopback module with given parameters. @@ -230,6 +290,9 @@ struct impl { struct spa_hook playback_listener; struct spa_audio_info_raw playback_info; + struct spa_process_latency_info process_latency[2]; + struct spa_latency_info latency[2]; + unsigned int do_disconnect:1; unsigned int recalc_delay:1; @@ -277,7 +340,15 @@ static void recalculate_delay(struct impl *impl) static void capture_process(void *d) { struct impl *impl = d; - pw_stream_trigger_process(impl->playback); + int res; + if ((res = pw_stream_trigger_process(impl->playback)) < 0) { + while (true) { + struct pw_buffer *t; + if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL) + break; + pw_stream_queue_buffer(impl->capture, t); + } + } } static void playback_process(void *d) @@ -370,24 +441,99 @@ static void playback_process(void *d) pw_stream_queue_buffer(impl->playback, out); } +static void build_latency_params(struct impl *impl, struct spa_pod_builder *b, + const struct spa_pod *params[], uint32_t max_params) +{ + struct spa_latency_info latency; + latency = impl->latency[0]; + spa_process_latency_info_add(&impl->process_latency[0], &latency); + params[0] = spa_latency_build(b, SPA_PARAM_Latency, &latency); + latency = impl->latency[1]; + spa_process_latency_info_add(&impl->process_latency[1], &latency); + params[1] = spa_latency_build(b, SPA_PARAM_Latency, &latency); +} + +static struct spa_pod *build_props(struct impl *impl, struct spa_pod_builder *b, + enum spa_direction direction) +{ + int64_t nsec = impl->process_latency[direction].ns; + + return spa_pod_builder_add_object(b, + SPA_TYPE_OBJECT_Props, SPA_PARAM_Props, + SPA_PROP_latencyOffsetNsec, SPA_POD_Long(nsec)); +} + static void param_latency_changed(struct impl *impl, const struct spa_pod *param, - struct pw_stream *other) + struct pw_stream *stream, struct pw_stream *other) { struct spa_latency_info latency; uint8_t buffer[1024]; struct spa_pod_builder b; - const struct spa_pod *params[1]; + const struct spa_pod *params[2]; if (param == NULL || spa_latency_parse(param, &latency) < 0) return; + impl->latency[latency.direction] = latency; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); - params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - pw_stream_update_params(other, params, 1); + build_latency_params(impl, &b, params, 2); + + pw_stream_update_params(stream, params, 2); + pw_stream_update_params(other, params, 2); impl->recalc_delay = true; } +static void emit_process_latency_changed(struct impl *impl, + enum spa_direction direction, struct pw_stream *stream) +{ + uint8_t buffer[4096]; + struct spa_pod_builder b; + const struct spa_pod *params[4]; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + params[0] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, + &impl->process_latency[direction]); + if (stream == impl->capture) + params[1] = build_props(impl, &b, SPA_DIRECTION_INPUT); + else + params[1] = build_props(impl, &b, SPA_DIRECTION_OUTPUT); + build_latency_params(impl, &b, ¶ms[2], 2); + pw_stream_update_params(stream, params, 4); +} + +static void param_process_latency_changed(struct impl *impl, const struct spa_pod *param, + enum spa_direction direction, struct pw_stream *stream) +{ + struct spa_process_latency_info info; + + if (param == NULL) + spa_zero(info); + else if (spa_process_latency_parse(param, &info) < 0) + return; + + impl->process_latency[direction] = info; + + emit_process_latency_changed(impl, direction, stream); +} + +static void param_props_changed(struct impl *impl, const struct spa_pod *param, + enum spa_direction direction, struct pw_stream *stream) +{ + int64_t nsec; + + if (!param) + nsec = 0; + else if (spa_pod_parse_object(param, + SPA_TYPE_OBJECT_Props, NULL, + SPA_PROP_latencyOffsetNsec, SPA_POD_Long(&nsec)) < 0) + return; + + impl->process_latency[direction].ns = nsec; + emit_process_latency_changed(impl, direction, stream); +} + static void param_tag_changed(struct impl *impl, const struct spa_pod *param, struct pw_stream *other) { @@ -506,7 +652,13 @@ static void capture_param_changed(void *data, uint32_t id, const struct spa_pod param_format_changed(impl, param, impl->capture, true); break; case SPA_PARAM_Latency: - param_latency_changed(impl, param, impl->playback); + param_latency_changed(impl, param, impl->capture, impl->playback); + break; + case SPA_PARAM_Props: + param_props_changed(impl, param, SPA_DIRECTION_INPUT, impl->capture); + break; + case SPA_PARAM_ProcessLatency: + param_process_latency_changed(impl, param, SPA_DIRECTION_INPUT, impl->capture); break; case SPA_PARAM_Tag: param_tag_changed(impl, param, impl->playback); @@ -551,7 +703,13 @@ static void playback_param_changed(void *data, uint32_t id, const struct spa_pod param_format_changed(impl, param, impl->playback, false); break; case SPA_PARAM_Latency: - param_latency_changed(impl, param, impl->capture); + param_latency_changed(impl, param, impl->playback, impl->capture); + break; + case SPA_PARAM_Props: + param_props_changed(impl, param, SPA_DIRECTION_OUTPUT, impl->playback); + break; + case SPA_PARAM_ProcessLatency: + param_process_latency_changed(impl, param, SPA_DIRECTION_OUTPUT, impl->playback); break; case SPA_PARAM_Tag: param_tag_changed(impl, param, impl->capture); @@ -694,43 +852,15 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *info) { - const char *str; - - *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_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)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P")), + &props->dict, + SPA_KEY_AUDIO_RATE, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) @@ -783,6 +913,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->module = module; impl->context = context; + impl->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT); + impl->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL) pw_properties_setf(props, PW_KEY_NODE_GROUP, "loopback-%u-%u", pid, id); diff --git a/src/modules/module-metadata.c b/src/modules/module-metadata.c index 2d05fb83..a1255da3 100644 --- a/src/modules/module-metadata.c +++ b/src/modules/module-metadata.c @@ -12,14 +12,95 @@ #include <spa/utils/result.h> #include <spa/utils/json.h> +#define PW_API_METADATA_IMPL SPA_EXPORT #include <pipewire/impl.h> #include <pipewire/extensions/metadata.h> /** \page page_module_metadata Metadata + * + * Allows clients to export a metadata store to the PipeWire server. + * + * Both the client and the server need to load this module for the metadata + * to be exported. + * + * This module creates a new factory and a new export type for the + * \ref PW_TYPE_INTERFACE_Metadata interface. + * + * A client will first create an implementation of the PW_TYPE_INTERFACE_Metadata + * interface with \ref pw_context_create_metadata(), for example. With the + * \ref pw_core_export(), this module will create a server side resource to expose + * the metadata implementation to other clients. Modifications done by the client + * on the local metadata interface will be visible to all PipeWire clients. + * + * It is also possible to use the factory to create metadata in the current + * processes using a config file fragment. + * + * As an argument to the create_object call, a set of properties will + * control the name of the metadata and some initial values. * * ## Module Name * * `libpipewire-module-metadata` + * + * ## Module Options + * + * This module has no options. + * + * ## Properties for the create_object call + * + * - `metadata.name`: The name of the new metadata object. If not given, the metadata + * object name will be `default`. + * - `metadata.values`: A JSON array of objects with initial values for the metadata object. + * + * the `metadata.values` key has the following layout: + * + * \code{.unparsed} + * metadata.values = [ + * { id = <int> key = <key> type = <type> value = <object> } + * .... + * ] + * \endcode + * + * - `id`: an optional object id for the metadata, default 0 + * - `key`: a string, the metadata key + * - `type`: an optional metadata value type + * - `value`: a JSON item, the metadata value. + * + * ## Example configuration + * + * The module is usually added to the config file of the main PipeWire daemon and the + * clients. + * + *\code{.unparsed} + * context.modules = [ + * { name = libpipewire-module-metadata } + * ] + *\endcode + + * ## Config objects + * + * To create an object from the factory, one can use the \ref pw_core_create_object() + * method or make an object in the `context.objects` section like in the main PipeWire + * daemon config file: + * + *\code{.unparsed} + * context.objects = [ + * { factory = metadata + * args = { + * metadata.name = default + * metadata.values = [ + * { key = default.audio.sink value = { name = somesink } } + * { key = default.audio.source value = { name = somesource } } + * ] + * } + * } + *\endcode + * + * This creates a new default metadata store with 2 key/values. + * + * ## See also + * + * - `pw-metadata`: a tool to manage metadata */ #define NAME "metadata" @@ -66,23 +147,17 @@ struct factory_data { */ static int fill_metadata(struct pw_metadata *metadata, const char *str) { - struct spa_json it[3]; + struct spa_json it[2]; - spa_json_init(&it[0], str, strlen(str)); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) + if (spa_json_begin_array(&it[0], str, strlen(str)) <= 0) return -EINVAL; - while (spa_json_enter_object(&it[1], &it[2]) > 0) { + while (spa_json_enter_object(&it[0], &it[1]) > 0) { char key[256], *k = NULL, *v = NULL, *t = NULL; - int id = 0; - - while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { - int len; - const char *val; - - if ((len = spa_json_next(&it[2], &val)) <= 0) - return -EINVAL; + int id = 0, len; + const char *val; + while ((len = spa_json_object_next(&it[1], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "id")) { if (spa_json_parse_int(val, len, &id) <= 0) return -EINVAL; @@ -94,7 +169,7 @@ static int fill_metadata(struct pw_metadata *metadata, const char *str) spa_json_parse_stringn(val, len, t, len+1); } else if (spa_streq(key, "value")) { if (spa_json_is_container(val, len)) - len = spa_json_container_len(&it[2], val, len); + len = spa_json_container_len(&it[1], val, len); if ((v = malloc(len+1)) != NULL) spa_json_parse_stringn(val, len, v, len+1); } diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index 15806c71..3675a33f 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -26,6 +26,7 @@ #include <spa/debug/types.h> #include <spa/pod/builder.h> #include <spa/param/audio/format-utils.h> +#include <spa/param/audio/raw-json.h> #include <spa/param/latency-utils.h> #include <spa/param/audio/raw.h> @@ -55,10 +56,12 @@ * - `driver.mode`: the driver mode, sink|source|duplex, default duplex * - `local.ifname = <str>`: interface name to use * - `net.ip =<str>`: multicast IP address, default "225.3.19.154" - * - `net.port =<int>`: control port, default "19000" + * - `net.port =<int>`: control port, default 19000 * - `net.mtu = <int>`: MTU to use, default 1500 * - `net.ttl = <int>`: TTL to use, default 1 * - `net.loop = <bool>`: loopback multicast, default false + * - `source.ip =<str>`: IP address to bind to, default "0.0.0.0" + * - `source.port =<int>`: port to bind to, default 0 (allocate) * - `netjack2.client-name`: the name of the NETJACK2 client. * - `netjack2.save`: if jack port connections should be save automatically. Can also be * placed per stream. @@ -123,6 +126,8 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define DEFAULT_NET_LOOP false #define DEFAULT_NET_DSCP 34 /* Default to AES-67 AF41 (34) */ #define MAX_MTU 9000 +#define DEFAULT_SOURCE_IP "0.0.0.0" +#define DEFAULT_SOURCE_PORT 0 #define DEFAULT_NETWORK_LATENCY 2 #define NETWORK_MAX_LATENCY 30 @@ -143,6 +148,8 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); "( net.mtu=<MTU to use, default 1500> ) " \ "( net.ttl=<TTL to use, default 1> ) " \ "( net.loop=<loopback, default false> ) " \ + "( source.ip=<ip address to bind, default 0.0.0.0> ) " \ + "( source.port=<port to bind, default 0> ) " \ "( netjack2.client-name=<name of the NETJACK2 client> ) " \ "( netjack2.save=<bool, save ports> ) " \ "( netjack2.latency=<latency in cycles, default 2> ) " \ @@ -395,7 +402,7 @@ static void make_stream_ports(struct stream *s) struct impl *impl = s->impl; uint32_t i; struct pw_properties *props; - const char *str, *prefix; + const char *str; char name[256]; bool is_midi; uint8_t buffer[512]; @@ -403,14 +410,6 @@ static void make_stream_ports(struct stream *s) struct spa_latency_info latency; const struct spa_pod *params[1]; - if (s->direction == PW_DIRECTION_INPUT) { - /* sink */ - prefix = "playback"; - } else { - /* source */ - prefix = "capture"; - } - for (i = 0; i < s->n_ports; i++) { struct port *port = s->ports[i]; @@ -422,24 +421,18 @@ static void make_stream_ports(struct stream *s) if (i < s->info.channels) { str = spa_debug_type_find_short_name(spa_type_audio_channel, s->info.position[i % SPA_AUDIO_MAX_CHANNELS]); - if (str) - snprintf(name, sizeof(name), "%s_%s", prefix, str); - else - snprintf(name, sizeof(name), "%s_%d", prefix, i); - props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_AUDIO_CHANNEL, str ? str : "UNK", PW_KEY_PORT_PHYSICAL, "true", - PW_KEY_PORT_NAME, name, NULL); is_midi = false; } else { - snprintf(name, sizeof(name), "%s_%d", prefix, i - s->info.channels); + snprintf(name, sizeof(name), "midi%d", i - s->info.channels); props = pw_properties_new( - PW_KEY_FORMAT_DSP, "8 bit raw midi", - PW_KEY_PORT_NAME, name, + PW_KEY_FORMAT_DSP, "32 bit raw UMP", + PW_KEY_AUDIO_CHANNEL, name, PW_KEY_PORT_PHYSICAL, "true", NULL); @@ -973,11 +966,15 @@ static int create_netjack2_socket(struct impl *impl) if ((str = pw_properties_get(impl->props, "net.ip")) == NULL) str = DEFAULT_NET_IP; if ((res = pw_net_parse_address(str, port, &impl->dst_addr, &impl->dst_len)) < 0) { - pw_log_error("invalid net.ip %s: %s", str, spa_strerror(res)); + pw_log_error("invalid net.ip:%s port:%d: %s", str, port, spa_strerror(res)); goto out; } - if ((res = pw_net_parse_address("0.0.0.0", 0, &impl->src_addr, &impl->src_len)) < 0) { - pw_log_error("invalid source.ip: %s", spa_strerror(res)); + + port = pw_properties_get_uint32(impl->props, "source.port", DEFAULT_SOURCE_PORT); + if ((str = pw_properties_get(impl->props, "source.ip")) == NULL) + str = DEFAULT_SOURCE_IP; + if ((res = pw_net_parse_address(str, port, &impl->src_addr, &impl->src_len)) < 0) { + pw_log_error("invalid source.ip:%s port:%d: %s", str, port, spa_strerror(res)); goto out; } @@ -1150,45 +1147,15 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - const char *str; - - spa_zero(*info); - info->format = SPA_AUDIO_FORMAT_F32P; - info->rate = 0; - 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)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index 4217fbcb..8229b5d5 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -29,6 +29,7 @@ #include <spa/param/audio/format-utils.h> #include <spa/param/latency-utils.h> #include <spa/param/audio/raw.h> +#include <spa/param/audio/raw-json.h> #include <pipewire/impl.h> #include <pipewire/i18n.h> @@ -61,7 +62,7 @@ * - `net.ttl = <int>`: TTL to use, default 1 * - `net.loop = <bool>`: loopback multicast, default false * - `netjack2.connect`: if jack ports should be connected automatically. Can also be - * placed per stream. + * placed per stream, default false. * - `netjack2.sample-rate`: the sample rate to use, default 48000 * - `netjack2.period-size`: the buffer size to use, default 1024 * - `netjack2.encoding`: the encoding, float|opus|int, default float @@ -131,6 +132,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define NETWORK_MAX_LATENCY 30 +#define DEFAULT_CONNECT false #define DEFAULT_SAMPLE_RATE 48000 #define DEFAULT_PERIOD_SIZE 1024 #define DEFAULT_ENCODING "float" @@ -146,7 +148,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); "( net.mtu=<MTU to use, default 1500> ) " \ "( net.ttl=<TTL to use, default 1> ) " \ "( net.loop=<loopback, default false> ) " \ - "( netjack2.connect=<bool, autoconnect ports> ) " \ + "( netjack2.connect=<autoconnect ports, default false> ) " \ "( netjack2.sample-rate=<sampl erate, default 48000> ) "\ "( netjack2.period-size=<period size, default 1024> ) " \ "( midi.ports=<number of midi ports> ) " \ @@ -390,11 +392,8 @@ static void follower_free(struct follower *follower) free(follower); } -static int -do_stop_follower(struct spa_loop *loop, - bool async, uint32_t seq, const void *data, size_t size, void *user_data) +static int stop_follower(struct follower *follower) { - struct follower *follower = user_data; follower->started = false; if (follower->source.filter) pw_filter_set_active(follower->source.filter, false); @@ -421,12 +420,10 @@ static void on_setup_io(void *data, int fd, uint32_t mask) { struct follower *follower = data; - struct impl *impl = follower->impl; if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { pw_log_warn("error:%08x", mask); - pw_loop_destroy_source(impl->main_loop, follower->setup_socket); - follower->setup_socket = NULL; + stop_follower(follower); return; } if (mask & SPA_IO_IN) { @@ -471,7 +468,6 @@ on_data_io(void *data, int fd, uint32_t mask) pw_log_warn("error:%08x", mask); pw_loop_destroy_source(impl->data_loop, follower->socket); follower->socket = NULL; - pw_loop_invoke(impl->main_loop, do_stop_follower, 1, NULL, 0, false, follower); return; } if (mask & SPA_IO_IN) { @@ -517,7 +513,7 @@ static void make_stream_ports(struct stream *s) struct follower *follower = s->follower; uint32_t i; struct pw_properties *props; - const char *str, *prefix; + const char *str; char name[256]; bool is_midi; uint8_t buffer[512]; @@ -525,14 +521,6 @@ static void make_stream_ports(struct stream *s) struct spa_latency_info latency; const struct spa_pod *params[1]; - if (s->direction == PW_DIRECTION_INPUT) { - /* sink */ - prefix = "playback"; - } else { - /* source */ - prefix = "capture"; - } - for (i = 0; i < s->n_ports; i++) { struct port *port = s->ports[i]; if (port != NULL) { @@ -543,25 +531,20 @@ static void make_stream_ports(struct stream *s) if (i < s->info.channels) { str = spa_debug_type_find_short_name(spa_type_audio_channel, s->info.position[i]); - if (str) - snprintf(name, sizeof(name), "%s_%s", prefix, str); - else - snprintf(name, sizeof(name), "%s_%d", prefix, i); props = pw_properties_new( PW_KEY_FORMAT_DSP, "32 bit float mono audio", PW_KEY_AUDIO_CHANNEL, str ? str : "UNK", PW_KEY_PORT_PHYSICAL, "true", - PW_KEY_PORT_NAME, name, NULL); is_midi = false; } else { - snprintf(name, sizeof(name), "%s_%d", prefix, i - s->info.channels); + snprintf(name, sizeof(name), "midi%d", i - s->info.channels); props = pw_properties_new( - PW_KEY_FORMAT_DSP, "8 bit raw midi", - PW_KEY_PORT_NAME, name, + PW_KEY_FORMAT_DSP, "32 bit raw UMP", PW_KEY_PORT_PHYSICAL, "true", + PW_KEY_AUDIO_CHANNEL, name, NULL); is_midi = true; @@ -753,7 +736,7 @@ static bool is_multicast(struct sockaddr *sa, socklen_t salen) } static int make_data_socket(struct sockaddr_storage *sa, socklen_t salen, - bool loop, int ttl, int dscp, char *ifname) + bool loop, int ttl, int dscp, const char *ifname) { int af, fd, val, res; struct timeval timeout; @@ -763,6 +746,13 @@ static int make_data_socket(struct sockaddr_storage *sa, socklen_t salen, pw_log_error("socket failed: %m"); return -errno; } +#ifdef SO_BINDTODEVICE + if (ifname && setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)) < 0) { + res = -errno; + pw_log_error("setsockopt(SO_BINDTODEVICE) failed: %m"); + goto error; + } +#endif if (connect(fd, (struct sockaddr*)sa, salen) < 0) { res = -errno; pw_log_error("connect() failed: %m"); @@ -795,7 +785,7 @@ error: } static int make_announce_socket(struct sockaddr_storage *sa, socklen_t salen, - char *ifname) + const char *ifname) { int af, fd, val, res; struct ifreq req; @@ -963,7 +953,8 @@ static int handle_follower_available(struct impl *impl, struct nj2_session_param goto create_failed; fd = make_data_socket(addr, addr_len, impl->loop, - impl->ttl, impl->dscp, NULL); + impl->ttl, impl->dscp, + pw_properties_get(impl->props, "local.ifname")); if (fd < 0) goto socket_failed; @@ -1089,8 +1080,10 @@ static int create_netjack2_socket(struct impl *impl) impl->ttl = pw_properties_get_uint32(impl->props, "net.ttl", DEFAULT_NET_TTL); impl->loop = pw_properties_get_bool(impl->props, "net.loop", DEFAULT_NET_LOOP); impl->dscp = pw_properties_get_uint32(impl->props, "net.dscp", DEFAULT_NET_DSCP); + str = pw_properties_get(impl->props, "local.ifname"); - fd = make_announce_socket(&impl->src_addr, impl->src_len, NULL); + fd = make_announce_socket(&impl->src_addr, impl->src_len, + pw_properties_get(impl->props, "local.ifname")); if (fd < 0) { res = fd; pw_log_error("can't create socket: %s", spa_strerror(res)); @@ -1176,45 +1169,15 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - const char *str; - - spa_zero(*info); - info->format = SPA_AUDIO_FORMAT_F32P; - info->rate = 0; - 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)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, "F32P"), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) @@ -1329,10 +1292,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (pw_properties_get(props, PW_KEY_NODE_LOCK_RATE) == NULL) pw_properties_set(props, PW_KEY_NODE_LOCK_RATE, "true"); - pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); pw_properties_set(impl->sink_props, PW_KEY_NODE_NAME, "netjack2_manager_send"); - - pw_properties_set(impl->source_props, PW_KEY_MEDIA_CLASS, "Audio/Source"); pw_properties_set(impl->source_props, PW_KEY_NODE_NAME, "netjack2_manager_recv"); if ((str = pw_properties_get(props, "sink.props")) != NULL) @@ -1349,6 +1309,26 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_NODE_LOCK_RATE); copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); + copy_props(impl, props, "netjack2.connect"); + + if (pw_properties_get_bool(impl->sink_props, "netjack2.connect", DEFAULT_CONNECT)) { + if (pw_properties_get(impl->sink_props, PW_KEY_NODE_AUTOCONNECT) == NULL) + pw_properties_set(impl->sink_props, PW_KEY_NODE_AUTOCONNECT, "true"); + if (pw_properties_get(impl->sink_props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS, "Stream/Input/Audio"); + } else { + if (pw_properties_get(impl->sink_props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); + } + if (pw_properties_get_bool(impl->source_props, "netjack2.connect", DEFAULT_CONNECT)) { + if (pw_properties_get(impl->source_props, PW_KEY_NODE_AUTOCONNECT) == NULL) + pw_properties_set(impl->source_props, PW_KEY_NODE_AUTOCONNECT, "true"); + if (pw_properties_get(impl->source_props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_set(impl->source_props, PW_KEY_MEDIA_CLASS, "Stream/Output/Audio"); + } else { + if (pw_properties_get(impl->source_props, PW_KEY_MEDIA_CLASS) == NULL) + pw_properties_set(impl->source_props, PW_KEY_MEDIA_CLASS, "Audio/Source"); + } impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index 9417323a..bac6f132 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -1,5 +1,6 @@ -#include <byteswap.h> +#include <spa/utils/endian.h> +#include <spa/control/ump-utils.h> #ifdef HAVE_OPUS_CUSTOM #include <opus/opus.h> @@ -248,7 +249,7 @@ static void midi_to_netjack2(struct netjack2_peer *peer, struct spa_pod_sequence *seq; struct spa_pod_control *c; struct nj2_midi_event *ev; - uint32_t free_size; + int free_size; buf->magic = MIDI_BUFFER_MAGIC; buf->buffer_size = peer->quantum_limit * sizeof(float); @@ -271,40 +272,41 @@ static void midi_to_netjack2(struct netjack2_peer *peer, free_size = buf->buffer_size - sizeof(*buf); SPA_POD_SEQUENCE_FOREACH(seq, c) { - switch(c->type) { - case SPA_CONTROL_Midi: - { - uint8_t *data = SPA_POD_BODY(&c->value); - size_t size = SPA_POD_BODY_SIZE(&c->value); - void *ptr; - - if (c->offset >= n_samples || - size >= free_size) { - buf->lost_events++; - continue; - } - if (peer->fix_midi) - fix_midi_event(data, size); - - ev = &buf->event[buf->event_count]; - ev->time = c->offset; - ev->size = size; - if (size <= MIDI_INLINE_MAX) { - ptr = ev->buffer; - } else { - buf->write_pos += size; - ev->offset = buf->buffer_size - 1 - buf->write_pos; - free_size -= size; - ptr = SPA_PTROFF(buf, ev->offset, void); - } - memcpy(ptr, data, size); - buf->event_count++; - free_size -= sizeof(*ev); - break; + int size; + uint8_t data[16]; + void *ptr; + + if (c->type != SPA_CONTROL_UMP) + continue; + + size = spa_ump_to_midi(SPA_POD_BODY(&c->value), + SPA_POD_BODY_SIZE(&c->value), data, sizeof(data)); + if (size <= 0) + continue; + + if (c->offset >= n_samples || + size >= free_size) { + buf->lost_events++; + continue; } - default: - break; + + if (peer->fix_midi) + fix_midi_event(data, size); + + ev = &buf->event[buf->event_count]; + ev->time = c->offset; + ev->size = size; + if (size <= MIDI_INLINE_MAX) { + ptr = ev->buffer; + } else { + buf->write_pos += size; + ev->offset = buf->buffer_size - 1 - buf->write_pos; + free_size -= size; + ptr = SPA_PTROFF(buf, ev->offset, void); } + memcpy(ptr, data, size); + buf->event_count++; + free_size -= sizeof(*ev); } if (buf->write_pos > 0) memmove(SPA_PTROFF(buf, sizeof(*buf) + buf->event_count * sizeof(struct nj2_midi_event), void), @@ -322,7 +324,9 @@ static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_b spa_pod_builder_push_sequence(&b, &f, 0); for (i = 0; buf != NULL && i < buf->event_count; i++) { struct nj2_midi_event *ev = &buf->event[i]; - void *data; + uint8_t *data; + size_t s; + uint64_t state = 0; if (ev->size <= MIDI_INLINE_MAX) data = ev->buffer; @@ -331,8 +335,16 @@ static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_b else continue; - spa_pod_builder_control(&b, ev->time, SPA_CONTROL_Midi); - spa_pod_builder_bytes(&b, data, ev->size); + s = ev->size; + while (s > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&data, &s, ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + break; + + spa_pod_builder_control(&b, ev->time, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ump, ump_size); + } } spa_pod_builder_pop(&b, &f); } diff --git a/src/modules/module-parametric-equalizer.c b/src/modules/module-parametric-equalizer.c index 6d29dcf0..315662f8 100644 --- a/src/modules/module-parametric-equalizer.c +++ b/src/modules/module-parametric-equalizer.c @@ -4,11 +4,12 @@ /* SPDX-License-Identifier: MIT */ #include <errno.h> +#include <limits.h> #include "config.h" #include <spa/utils/result.h> -#include <spa/utils/json.h> +#include <spa/utils/json-core.h> #include <spa/param/audio/raw.h> #include <pipewire/impl.h> @@ -30,6 +31,7 @@ * Parametric equalizer configuration generated from AutoEQ or Squiglink looks * like below. * + * \code{.unparsed} * Preamp: -6.8 dB * Filter 1: ON PK Fc 21 Hz Gain 6.7 dB Q 1.100 * Filter 2: ON PK Fc 85 Hz Gain 6.9 dB Q 3.000 @@ -37,6 +39,7 @@ * Filter 4: ON PK Fc 210 Hz Gain 5.9 dB Q 2.100 * Filter 5: ON PK Fc 710 Hz Gain -1.0 dB Q 0.600 * Filter 6: ON PK Fc 1600 Hz Gain 2.3 dB Q 2.700 + * \endcode * * Fc, Gain and Q specify the frequency, gain and Q factor respectively. * The fourth column can be one of PK, LSC or HSC specifying peaking, low @@ -58,7 +61,9 @@ * - `equalizer.description = <str>`: Name which will show up in * - `audio.channels = <int>`: Number of audio channels, default 2 * - `audio.position = <str>`: Channel map, default "[FL, FR]" - * - `remote.name =<str>`: environment with remote name, default "pipewire-0" + * - `remote.name = <str>`: environment with remote name, default "pipewire-0" + * - `capture.props = {}`: properties passed to the input stream, default `{ media.class = "Audio/Sink", node.name = "effect_input.eq<number of nodes>" }` + * - `playback.props = {}`: properties passed to the output stream, default `{ node.passive = true, node.name = "effect_output.eq<number of nodes>" }` * * ## General options * @@ -80,6 +85,12 @@ * #equalizer.description = "Parametric EQ Sink" * #audio.channels = 2 * #audio.position = [FL, FR] + * #capture.props = { + * # node.name = "Parametric EQ input" + * #} + * #playback.props = { + * # node.name = "Parametric EQ output" + * #} * } * } * ] @@ -100,8 +111,10 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define MODULE_USAGE "( remote.name=<remote> ) " \ "( equalizer.filepath=<filepath> )" \ "( equalizer.description=<description> )" \ - "( audio.channels=<number of channels>)" \ - "( audio.position=<channel map>)" + "( audio.channels=<number of channels> )" \ + "( audio.position=<channel map> )" \ + "( capture.props=<properties> )" \ + "( playback.props=<properties> )" static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>" }, @@ -123,8 +136,6 @@ struct impl { struct spa_hook module_listener; struct spa_hook eq_module_listener; - char position[64]; - uint32_t channels; unsigned int do_disconnect:1; }; @@ -148,154 +159,82 @@ static const struct pw_impl_module_events filter_chain_module_events = { .destroy = filter_chain_module_destroy, }; -void init_eq_node(FILE *f, const char *node_desc) +static int enhance_properties(struct pw_properties *props, const char *key, ...) { - fprintf(f, "{\n"); - fprintf(f, "node.description = \"%s\"\n", node_desc); - fprintf(f, "media.name = \"%s\"\n", node_desc); - fprintf(f, "filter.graph = {\n"); - fprintf(f, "nodes = [\n"); -} - -void add_eq_node(FILE *f, struct eq_node_param *param, uint32_t eq_band_idx) -{ - char str1[64], str2[64]; - - fprintf(f, "{\n"); - fprintf(f, "type = builtin\n"); - fprintf(f, "name = eq_band_%d\n", eq_band_idx); - - if (strcmp(param->filter_type, "PK") == 0) { - fprintf(f, "label = bq_peaking\n"); - } else if (strcmp(param->filter_type, "LSC") == 0) { - fprintf(f, "label = bq_lowshelf\n"); - } else if (strcmp(param->filter_type, "HSC") == 0) { - fprintf(f, "label = bq_highshelf\n"); - } else { - fprintf(f, "label = bq_peaking\n"); - } - - fprintf(f, "control = { \"Freq\" = %d \"Q\" = %s \"Gain\" = %s }\n", param->freq, - spa_json_format_float(str1, sizeof(str1), param->q_fact), - spa_json_format_float(str2, sizeof(str2), param->gain)); - - fprintf(f, "}\n"); -} + FILE *f; + spa_autoptr(pw_properties) p = NULL; + char *args = NULL; + const char *str; + size_t size; + va_list varargs; + int res; -void end_eq_node(struct impl *impl, FILE *f, uint32_t number_of_nodes) -{ - fprintf(f, "]\n"); + if ((str = pw_properties_get(props, key)) == NULL) + str = "{}"; + if ((p = pw_properties_new_string(str)) == NULL) + return -errno; - fprintf(f, "links = [\n"); - for (uint32_t i = 1; i < number_of_nodes; i++) { - fprintf(f, "{ output = \"eq_band_%d:Out\" input = \"eq_band_%d:In\" }\n", i, i + 1); + va_start(varargs, key); + while (true) { + char *k, *v; + k = va_arg(varargs, char *); + if (k == NULL) + break; + v = va_arg(varargs, char *); + if (v == NULL || pw_properties_get(p, k) == NULL) + pw_properties_set(p, k, v); + } + va_end(varargs); + + if ((f = open_memstream(&args, &size)) == NULL) { + res = -errno; + pw_log_error("Can't open memstream: %m"); + return res; } - fprintf(f, "]\n"); - - fprintf(f, "}\n"); - fprintf(f, "audio.channels = %d\n", impl->channels); - fprintf(f, "audio.position = %s\n", impl->position); - - fprintf(f, "capture.props = {\n"); - fprintf(f, "node.name = \"effect_input.eq%d\"\n", number_of_nodes); - fprintf(f, "media.class = Audio/Sink\n"); - fprintf(f, "}\n"); - - fprintf(f, "playback.props = {\n"); - fprintf(f, "node.name = \"effect_output.eq%d\"\n", number_of_nodes); - fprintf(f, "node.passive = true\n"); - fprintf(f, "}\n"); + pw_properties_serialize_dict(f, &p->dict, PW_PROPERTIES_FLAG_ENCLOSE); + fclose(f); - fprintf(f, "}\n"); + pw_properties_set(props, key, args); + free(args); + return 0; } -int32_t parse_eq_filter_file(struct impl *impl, FILE *f) +static int create_eq_filter(struct impl *impl, const char *filename) { - struct eq_node_param eq_param; - FILE *memstream = NULL; + FILE *f = NULL; const char* str; - char *args = NULL; - char *line = NULL; - ssize_t nread; - size_t len, size; - uint32_t eq_band_idx = 1; - uint32_t eq_bands = 0; + size_t size; int32_t res = 0; + char path[PATH_MAX]; - if ((memstream = open_memstream(&args, &size)) == NULL) { - res = -errno; - pw_log_error("Can't open memstream: %m"); - goto done; + if ((str = pw_properties_get(impl->props, "equalizer.description")) != NULL) { + if (pw_properties_get(impl->props, PW_KEY_NODE_DESCRIPTION) == NULL) + pw_properties_set(impl->props, PW_KEY_NODE_DESCRIPTION, str); + if (pw_properties_get(impl->props, PW_KEY_MEDIA_NAME) == NULL) + pw_properties_set(impl->props, PW_KEY_MEDIA_NAME, str); } - if ((str = pw_properties_get(impl->props, "equalizer.description")) == NULL) - str = DEFAULT_DESCRIPTION; - init_eq_node(memstream, str); - - /* - * Read the Preamp gain line. - * Example: Preamp: -6.8 dB - * - * When a pre-amp gain is required, which is usually the case when - * applying EQ, we need to modify the first EQ band to apply a - * bq_highshelf filter at frequency 0 Hz with the provided negative - * gain. - * - * Pre-amp gain is always negative to offset the effect of possible - * clipping introduced by the amplification resulting from EQ. - */ - spa_zero(eq_param); - nread = getline(&line, &len, f); - if (nread != -1 && sscanf(line, "%*s %6f %*s", &eq_param.gain) == 1) { - memcpy(eq_param.filter, "ON", 2); - memcpy(eq_param.filter_type, "HSC", 3); - eq_param.freq = 0; - eq_param.q_fact = 1.0; - - add_eq_node(memstream, &eq_param, eq_band_idx); - - eq_band_idx++; - eq_bands++; - } + spa_json_encode_string(path, sizeof(path), filename); + pw_properties_setf(impl->props, "filter.graph", + "{" + " nodes = [ " + " { type = builtin name = eq label = param_eq " + " config = { filename = %s } " + " } " + " ] " + "}", path); - /* Read the filter bands */ - while ((nread = getline(&line, &len, f)) != -1) { - spa_zero(eq_param); - - /* - * On field widths: - * - filter can be ON or OFF - * - filter type can be PK, LSC, HSC - * - freq can be at most 5 decimal digits - * - gain can be -xy.z - * - Q can be x.y00 - * - * Use a field width of 6 for gain and Q to account for any - * possible zeros. - */ - if (sscanf(line, "%*s %*d: %3s %3s %*s %5d %*s %*s %6f %*s %*c %6f", - eq_param.filter, eq_param.filter_type, &eq_param.freq, - &eq_param.gain, &eq_param.q_fact) == 5) { - if (strcmp(eq_param.filter, "ON") == 0) { - add_eq_node(memstream, &eq_param, eq_band_idx); - - eq_band_idx++; - eq_bands++; - } - } - } + enhance_properties(impl->props, "capture.props", PW_KEY_MEDIA_CLASS, "Audio/Sink", NULL); + enhance_properties(impl->props, "playback.props", PW_KEY_NODE_PASSIVE, "true", NULL); - if (eq_bands > 0) { - end_eq_node(impl, memstream, eq_bands); - } else { - pw_log_error("failed to parse equalizer configuration"); + if ((f = open_memstream(&args, &size)) == NULL) { res = -errno; + pw_log_error("Can't open memstream: %m"); goto done; } - - fclose(memstream); - memstream = NULL; + pw_properties_serialize_dict(f, &impl->props->dict, PW_PROPERTIES_FLAG_ENCLOSE); + fclose(f); pw_log_info("loading new module-filter-chain with args: %s", args); impl->eq_module = pw_context_load_module(impl->context, @@ -315,10 +254,7 @@ int32_t parse_eq_filter_file(struct impl *impl, FILE *f) res = 0; done: - if (memstream != NULL) - fclose(memstream); free(args); - return res; } @@ -377,7 +313,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) struct pw_properties *props = NULL; struct impl *impl; const char *str; - FILE *f = NULL; int res; PW_LOG_TOPIC_INIT(mod_topic); @@ -426,39 +361,17 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) &impl->core_listener, &core_events, impl); - impl->channels = pw_properties_get_uint32(impl->props, PW_KEY_AUDIO_CHANNELS, DEFAULT_CHANNELS); - if (impl->channels == 0) { - res = -EINVAL; - pw_log_error("invalid channels '%d'", impl->channels); - goto error; - } - - if ((str = pw_properties_get(impl->props, SPA_KEY_AUDIO_POSITION)) == NULL) - str = DEFAULT_POSITION; - strncpy(impl->position, str, strlen(str)); - if ((str = pw_properties_get(props, "equalizer.filepath")) == NULL) { - res = -errno; - pw_log_error( "missing property equalizer.filepath: %m"); + res = -ENOENT; + pw_log_error( "missing property equalizer.filepath: %s", spa_strerror(res)); goto error; } - pw_log_info("Loading equalizer file %s for parsing", str); - - if ((f = fopen(str, "r")) == NULL) { - res = -errno; - pw_log_error("failed to open equalizer file: %m"); + if ((res = create_eq_filter(impl, str)) < 0) { + pw_log_error("failed to parse equalizer file: %s", spa_strerror(res)); goto error; } - if (parse_eq_filter_file(impl, f) == -1) { - res = -EINVAL; - pw_log_error("failed to parse equalizer file: %m"); - goto error; - } - - fclose(f); - pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); @@ -466,10 +379,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) return 0; error: - if (f != NULL) - fclose(f); - impl_destroy(impl); - return res; } diff --git a/src/modules/module-pipe-tunnel.c b/src/modules/module-pipe-tunnel.c index 22c809f2..afc8e0f1 100644 --- a/src/modules/module-pipe-tunnel.c +++ b/src/modules/module-pipe-tunnel.c @@ -28,6 +28,7 @@ #include <spa/param/audio/format-utils.h> #include <spa/param/latency-utils.h> #include <spa/param/audio/raw.h> +#include <spa/param/audio/raw-json.h> #include <pipewire/impl.h> #include <pipewire/i18n.h> @@ -123,7 +124,6 @@ #define DEFAULT_FORMAT "S16" #define DEFAULT_RATE 48000 -#define DEFAULT_CHANNELS 2 #define DEFAULT_POSITION "[ FL FR ]" #define RINGBUFFER_SIZE (1u << 22) @@ -196,7 +196,6 @@ struct impl { void *buffer; uint32_t target_buffer; - struct spa_io_rate_match *rate_match; struct spa_io_position *position; struct spa_dll dll; @@ -364,22 +363,17 @@ static void playback_stream_process(void *data) static void update_rate(struct impl *impl, uint32_t filled) { - float error; + double error; - if (impl->rate_match == NULL) - return; - - error = (float)impl->target_buffer - (float)(filled); - error = SPA_CLAMP(error, -impl->max_error, impl->max_error); + error = (double)impl->target_buffer - (double)(filled); + error = SPA_CLAMPD(error, -impl->max_error, impl->max_error); impl->corr = spa_dll_update(&impl->dll, error); pw_log_debug("error:%f corr:%f current:%u target:%u", error, impl->corr, filled, impl->target_buffer); - if (!impl->driving) { - SPA_FLAG_SET(impl->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE); - impl->rate_match->rate = 1.0 / impl->corr; - } + if (!impl->driving) + pw_stream_set_rate(impl->stream, 1.0 / impl->corr); } static void capture_stream_process(void *data) @@ -452,9 +446,6 @@ static void stream_io_changed(void *data, uint32_t id, void *area, uint32_t size { struct impl *impl = data; switch (id) { - case SPA_IO_RateMatch: - impl->rate_match = area; - break; case SPA_IO_Position: impl->position = area; break; @@ -754,61 +745,18 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - -static inline uint32_t 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 void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *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)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_FORMAT, + SPA_KEY_AUDIO_RATE, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } static int calc_frame_size(const struct spa_audio_info_raw *info) diff --git a/src/modules/module-profiler.c b/src/modules/module-profiler.c index 0b764d57..ec8c5c7f 100644 --- a/src/modules/module-profiler.c +++ b/src/modules/module-profiler.c @@ -18,6 +18,7 @@ #include <spa/utils/ringbuffer.h> #include <spa/param/profiler.h> +#define PW_API_PROFILER SPA_EXPORT #include <pipewire/private.h> #include <pipewire/impl.h> #include <pipewire/extensions/profiler.h> @@ -34,14 +35,36 @@ * * `libpipewire-module-profiler` * + * ## Module Options + * + * - `profile.interval.ms`: Can be used to avoid gathering profiling information + * on every processing cycle. This allows trading off + * CPU usage for profiling accuracy. Default 0 + * + * ## Config override + * + * A `module.profiler.args` config section can be added + * to override the module arguments. + * + *\code{.unparsed} + * # ~/.config/pipewire/pipewire.conf.d/my-profiler-args.conf + * + * module.profiler.args = { + * #profile.interval.ms = 10 + * } + *\endcode + * * ## Example configuration * - * The module has no arguments and is usually added to the config file of - * the main pipewire daemon. + * The module is usually added to the config file of the main pipewire daemon. * *\code{.unparsed} * context.modules = [ - * { name = libpipewire-module-profiler } + * { name = libpipewire-module-profiler + * args = { + * #profile.interval.ms = 0 + * } + * } * ] *\endcode * @@ -68,9 +91,14 @@ int pw_protocol_native_ext_profiler_init(struct pw_context *context); #define pw_profiler_resource_profile(r,...) \ pw_profiler_resource(r,profile,0,__VA_ARGS__) +#define DEFAULT_INTERVAL 0 + +#define MODULE_USAGE "( profile.interval.ms=<minimum interval for sampling data (in ms) ) " + static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, { PW_KEY_MODULE_DESCRIPTION, "Generate Profiling data" }, + { PW_KEY_MODULE_USAGE, MODULE_USAGE }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; @@ -109,6 +137,9 @@ struct impl { uint8_t *flush; size_t flush_size; + + uint32_t interval; + uint64_t last_signal_time; }; struct resource_data { @@ -165,6 +196,13 @@ static void do_flush_event(void *data, uint64_t count) pw_profiler_resource_profile(resource, &p->pod); } +static void update_denom(struct spa_fraction *frac, uint32_t denom) +{ + if (frac->denom != 0) + frac->num = frac->num * denom / frac->denom; + frac->denom = denom; +} + static void context_do_profile(void *data) { struct node *n = data; @@ -182,6 +220,11 @@ static void context_do_profile(void *data) if (SPA_FLAG_IS_SET(pos->clock.flags, SPA_IO_CLOCK_FLAG_FREEWHEEL)) return; + if (a->signal_time - impl->last_signal_time < impl->interval) + goto done; + + impl->last_signal_time = a->signal_time; + spa_pod_builder_init(&b, n->tmp, sizeof(n->tmp)); spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Profiler, 0); @@ -206,7 +249,9 @@ static void context_do_profile(void *data) SPA_POD_Long(pos->clock.delay), SPA_POD_Double(pos->clock.rate_diff), SPA_POD_Long(pos->clock.next_nsec), - SPA_POD_Int(pos->state)); + SPA_POD_Int(pos->state), + SPA_POD_Int(pos->clock.cycle), + SPA_POD_Long(pos->clock.xrun)); spa_pod_builder_prop(&b, SPA_PROFILER_driverBlock, 0); spa_pod_builder_add_struct(&b, @@ -224,6 +269,8 @@ static void context_do_profile(void *data) struct pw_impl_node *n = t->node; struct pw_node_activation *na; struct spa_fraction latency; + struct pw_node_activation *a = n->rt.target.activation; + struct spa_io_position *pos = &a->position; if (t->id == id) continue; @@ -233,9 +280,9 @@ static void context_do_profile(void *data) if (n->force_quantum != 0) latency.num = n->force_quantum; if (n->force_rate != 0) - latency.denom = n->force_rate; + update_denom(&latency, n->force_rate); else if (n->rate.denom != 0) - latency.denom = n->rate.denom; + update_denom(&latency, n->rate.denom); } else { spa_zero(latency); } @@ -252,6 +299,21 @@ static void context_do_profile(void *data) SPA_POD_Int(na->status), SPA_POD_Fraction(&latency), SPA_POD_Int(na->xrun_count)); + + if (n->driver) { + spa_pod_builder_prop(&b, SPA_PROFILER_followerClock, 0); + spa_pod_builder_add_struct(&b, + SPA_POD_Int(pos->clock.id), + SPA_POD_String(pos->clock.name), + SPA_POD_Long(pos->clock.nsec), + SPA_POD_Fraction(&pos->clock.rate), + SPA_POD_Long(pos->clock.position), + SPA_POD_Long(pos->clock.duration), + SPA_POD_Long(pos->clock.delay), + SPA_POD_Double(pos->clock.rate_diff), + SPA_POD_Long(pos->clock.next_nsec), + SPA_POD_Long(pos->clock.xrun)); + } } spa_pod_builder_pop(&b, &f[0]); @@ -475,6 +537,12 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->properties = props; impl->main_loop = pw_context_get_main_loop(impl->context); + pw_context_conf_update_props(context, "module."NAME".args", props); + + impl->interval = SPA_NSEC_PER_MSEC * + pw_properties_get_uint32(props, "profile.interval.ms", DEFAULT_INTERVAL); + impl->last_signal_time = 0; + impl->global = pw_global_new(context, PW_TYPE_INTERFACE_Profiler, PW_VERSION_PROFILER, diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c index c6efc373..c7536bd0 100644 --- a/src/modules/module-protocol-native.c +++ b/src/modules/module-protocol-native.c @@ -70,7 +70,7 @@ PW_LOG_TOPIC(mod_topic_connection, "conn." NAME); * a client and a server using unix local sockets. * * Normally this module is loaded in both client and server config files - * so that they cam communicate. + * so that they can communicate. * * ## Module Name * @@ -130,6 +130,22 @@ PW_LOG_TOPIC(mod_topic_connection, "conn." NAME); * local context. This can be done even when the server is not a daemon. It can * be used to treat a local context as if it was a server. * + * ## Config override + * + * A `module.protocol-native.args` config section can be added + * to override the module arguments. + * + *\code{.unparsed} + * # ~/.config/pipewire/pipewire.conf.d/my-protocol-native-args.conf + * + * module.protocol-native.args = { + * sockets = [ + * { name = "pipewire-0" } + * { name = "pipewire-0-manager" } + * ] + * } + *\endcode + * * ## Example configuration * *\code{.unparsed} @@ -730,7 +746,7 @@ static int init_socket_name(struct server *s, const char *name) const char *runtime_dir; bool path_is_absolute; - path_is_absolute = name[0] == '/'; + path_is_absolute = name[0] == '/' || name[0] == '@'; runtime_dir = get_runtime_dir(); @@ -768,6 +784,9 @@ static int lock_socket(struct server *s) { int res; + if (s->addr.sun_path[0] == '\0') + return 0; + snprintf(s->lock_addr, sizeof(s->lock_addr), "%s%s", s->addr.sun_path, LOCK_SUFFIX); s->fd_lock = open(s->lock_addr, O_CREAT | O_CLOEXEC, @@ -923,18 +942,24 @@ static int add_socket(struct pw_protocol *protocol, struct server *s, struct soc res = -errno; goto error; } - if (stat(s->addr.sun_path, &socket_stat) < 0) { - if (errno != ENOENT) { - res = -errno; - pw_log_error("server %p: stat %s failed with error: %m", - s, s->addr.sun_path); - goto error_close; + if (s->addr.sun_path[0] == '@') { + s->addr.sun_path[0] = 0; + size = (socklen_t) (strlen(&s->addr.sun_path[1]) + 1); + } else { + if (stat(s->addr.sun_path, &socket_stat) < 0) { + if (errno != ENOENT) { + res = -errno; + pw_log_error("server %p: stat %s failed with error: %m", + s, s->addr.sun_path); + goto error_close; + } + } else if (socket_stat.st_mode & S_IWUSR || socket_stat.st_mode & S_IWGRP) { + unlink(s->addr.sun_path); } - } else if (socket_stat.st_mode & S_IWUSR || socket_stat.st_mode & S_IWGRP) { - unlink(s->addr.sun_path); + size = (socklen_t) (strlen(s->addr.sun_path) + 1); } - size = offsetof(struct sockaddr_un, sun_path) + strlen(s->addr.sun_path); + size += offsetof(struct sockaddr_un, sun_path); if (bind(fd, (struct sockaddr *) &s->addr, size) < 0) { res = -errno; pw_log_error("server %p: bind() failed with error: %m", s); @@ -959,12 +984,6 @@ static int add_socket(struct pw_protocol *protocol, struct server *s, struct soc s, info->name); } - 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) { @@ -976,6 +995,11 @@ static int add_socket(struct pw_protocol *protocol, struct server *s, struct soc res = -errno; goto error_close; } + res = write_socket_address(s); + if (res < 0) { + pw_log_warn("server %p: failed to write socket address: %s", s, + spa_strerror(res)); + } return 0; error_close: @@ -1311,9 +1335,8 @@ impl_new_client(struct pw_protocol *protocol, if (props) { str = spa_dict_lookup(props, PW_KEY_REMOTE_INTENTION); - if (str == NULL && - (str = spa_dict_lookup(props, PW_KEY_REMOTE_NAME)) != NULL && - spa_streq(str, "internal")) + if ((str == NULL || spa_streq(str, "generic")) && + spa_streq(spa_dict_lookup(props, PW_KEY_REMOTE_NAME), "internal")) str = "internal"; } if (str == NULL) @@ -1659,7 +1682,7 @@ static int create_servers(struct pw_protocol *this, struct pw_impl_core *core, const struct pw_properties *props, const struct pw_properties *args) { const char *sockets = args ? pw_properties_get(args, "sockets") : NULL; - struct spa_json it[3]; + struct spa_json it[2]; spa_autoptr(pw_properties) p = pw_properties_copy(props); if (sockets == NULL) { @@ -1681,16 +1704,16 @@ static int create_servers(struct pw_protocol *this, struct pw_impl_core *core, return 0; } - spa_json_init(&it[0], sockets, strlen(sockets)); - - if (spa_json_enter_array(&it[0], &it[1]) <= 0) + if (spa_json_begin_array(&it[0], sockets, strlen(sockets)) <= 0) goto error_invalid; - while (spa_json_enter_object(&it[1], &it[2]) > 0) { + while (spa_json_enter_object(&it[0], &it[1]) > 0) { struct socket_info info = {0}; char key[256]; char name[PATH_MAX]; char selinux_context[PATH_MAX]; + const char *value; + int len; info.uid = getuid(); info.gid = getgid(); @@ -1698,13 +1721,7 @@ static int create_servers(struct pw_protocol *this, struct pw_impl_core *core, pw_properties_clear(p); pw_properties_update(p, &props->dict); - while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { - const char *value; - int len; - - if ((len = spa_json_next(&it[2], &value)) <= 0) - goto error_invalid; - + while ((len = spa_json_object_next(&it[1], key, sizeof(key), &value)) > 0) { if (spa_streq(key, "name")) { if (spa_json_parse_stringn(value, len, name, sizeof(name)) < 0) goto error_invalid; @@ -1762,7 +1779,7 @@ static int create_servers(struct pw_protocol *this, struct pw_impl_core *core, info.has_mode = true; } else if (spa_streq(key, "props")) { if (spa_json_is_container(value, len)) - len = spa_json_container_len(&it[2], value, len); + len = spa_json_container_len(&it[1], value, len); pw_properties_update_string(p, value, len); } @@ -1805,7 +1822,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args_str) return -EEXIST; } - args = args_str ? pw_properties_new_string(args_str) : NULL; + args = args_str ? pw_properties_new_string(args_str) : pw_properties_new(NULL, NULL); + if (!args) + return -errno; + + pw_context_conf_update_props(context, "module."NAME".args", args); this = pw_protocol_new(context, PW_TYPE_INFO_PROTOCOL_Native, sizeof(struct protocol_data)); if (this == NULL) diff --git a/src/modules/module-protocol-native/local-socket.c b/src/modules/module-protocol-native/local-socket.c index f514881a..1e6addef 100644 --- a/src/modules/module-protocol-native/local-socket.c +++ b/src/modules/module-protocol-native/local-socket.c @@ -83,6 +83,11 @@ static int try_connect(struct pw_protocol_client *client, else name_size = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", runtime_dir, name) + 1; + if (addr.sun_path[0] == '@') { + addr.sun_path[0] = '\0'; + name_size--; + } + if (name_size > (int) sizeof addr.sun_path) { if (runtime_dir == NULL) pw_log_error("client %p: socket path \"%s\" plus null terminator exceeds %i bytes", @@ -122,14 +127,21 @@ error: } static int try_connect_name(struct pw_protocol_client *client, - const char *name, + const char *name, bool manager, void (*done_callback) (void *data, int res), void *data) { const char *runtime_dir; + char path[PATH_MAX]; int res; - if (name[0] == '/') { + if (manager && !spa_strendswith(name, "-manager")) { + snprintf(path, sizeof(path), "%s-manager", name); + res = try_connect_name(client, path, false, done_callback, data); + if (res >= 0) + return res; + } + if (name[0] == '/' || name[0] == '@') { return try_connect(client, NULL, name, done_callback, data); } else { runtime_dir = get_runtime_dir(); @@ -152,21 +164,22 @@ int pw_protocol_native_connect_local_socket(struct pw_protocol_client *client, void *data) { const char *name; - struct spa_json it[2]; + struct spa_json it[1]; char path[PATH_MAX]; int res = -EINVAL; + bool manager; + + manager = props && spa_streq(spa_dict_lookup(props, PW_KEY_REMOTE_INTENTION), "manager"); name = get_remote(props); if (name == NULL) return -EINVAL; - spa_json_init(&it[0], name, strlen(name)); - - if (spa_json_enter_array(&it[0], &it[1]) < 0) - return try_connect_name(client, name, done_callback, data); + if (spa_json_begin_array(&it[0], name, strlen(name)) <= 0) + return try_connect_name(client, name, manager, done_callback, data); - while (spa_json_get_string(&it[1], path, sizeof(path)) > 0) { - res = try_connect_name(client, path, done_callback, data); + while (spa_json_get_string(&it[0], path, sizeof(path)) > 0) { + res = try_connect_name(client, path, manager, done_callback, data); if (res < 0) continue; break; diff --git a/src/modules/module-protocol-native/security-context.c b/src/modules/module-protocol-native/security-context.c index 22da8b55..df0541a9 100644 --- a/src/modules/module-protocol-native/security-context.c +++ b/src/modules/module-protocol-native/security-context.c @@ -6,6 +6,8 @@ #include <pipewire/pipewire.h> #include <pipewire/impl.h> #include <pipewire/private.h> + +#define PW_API_SECURITY_CONTEXT SPA_EXPORT #include <pipewire/extensions/security-context.h> PW_LOG_TOPIC_EXTERN(mod_topic); diff --git a/src/modules/module-protocol-pulse.c b/src/modules/module-protocol-pulse.c index 556fe257..ea5eb6cb 100644 --- a/src/modules/module-protocol-pulse.c +++ b/src/modules/module-protocol-pulse.c @@ -256,10 +256,12 @@ * # Extra commands can be executed here. * # load-module : loads a module with args and flags * # args = "<module-name> <module-args>" - * # flags = [ "no-fail" ] + * # ( flags = [ "no-fail" ] ) + * # ( condition = [ { <key1> = <value1>, ... } ... ] ) + * # conditions will check the pulse.properties key/values. * pulse.cmd = [ * { cmd = "load-module" args = "module-always-sink" flags = [ ] } - * #{ cmd = "load-module" args = "module-switch-on-connect" } + * #{ cmd = "load-module" args = "module-switch-on-connect" condition = [ { pulse.cmd.switch-on-connect = true } ] * #{ cmd = "load-module" args = "module-gsettings" flags = [ "nofail" ] } * ] *\endcode @@ -296,7 +298,8 @@ * ## 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. + * section with a `quirks` and an `update-props` action. These rules operate on the client + * properties (not the stream properties, see above). * *\code{.unparsed} * # ~/.config/pipewire/pipewire-pulse.conf.d/custom.conf @@ -338,11 +341,20 @@ * * `block-source-volume` blocks the client from updating any source volumes. This can be used * to disable things like automatic gain control. * * `block-sink-volume` blocks the client from updating any sink volumes. + * * `block-record-stream` blocks the client from creating any record stream. + * * `block-playback-stream` blocks the client from creating any playback stream. * * ### update-props * * Takes an object with the properties to update on the client. Common actions are to - * tweak the quantum values. + * tweak the quantum values. You can use the stream specific keys in pulse.properties. + * + * ### startup notification + * + * A newline will be written into the notification file descriptor when the server has + * started if the following environment variable is set: + * + * - PIPEWIRE_PULSE_NOTIFICATION_FD * * ## Example configuration * diff --git a/src/modules/module-protocol-pulse/client.c b/src/modules/module-protocol-pulse/client.c index c6c037f1..454ffe7b 100644 --- a/src/modules/module-protocol-pulse/client.c +++ b/src/modules/module-protocol-pulse/client.c @@ -301,23 +301,23 @@ static bool drop_from_out_queue(struct client *client, struct message *m) } /* returns true if an event with the (mask, event, index) triplet should be dropped because it is redundant */ -static bool client_prune_subscribe_events(struct client *client, uint32_t event, uint32_t index) +static bool client_prune_subscribe_events(struct client *client, uint32_t facility, uint32_t type, uint32_t index) { struct message *m, *t; - if ((event & SUBSCRIPTION_EVENT_TYPE_MASK) == SUBSCRIPTION_EVENT_NEW) + if (type == SUBSCRIPTION_EVENT_NEW) return false; /* NOTE: reverse iteration */ spa_list_for_each_safe_reverse(m, t, &client->out_messages, link) { if (m->type != MESSAGE_TYPE_SUBSCRIPTION_EVENT) continue; - if ((m->u.subscription_event.event ^ event) & SUBSCRIPTION_EVENT_FACILITY_MASK) + if ((m->u.subscription_event.event & SUBSCRIPTION_EVENT_FACILITY_MASK) != facility) continue; if (m->u.subscription_event.index != index) continue; - if ((event & SUBSCRIPTION_EVENT_TYPE_MASK) == SUBSCRIPTION_EVENT_REMOVE) { + if (type == SUBSCRIPTION_EVENT_REMOVE) { /* This object is being removed, hence there is * point in keeping the old events regarding * entry in the queue. */ @@ -338,8 +338,7 @@ static bool client_prune_subscribe_events(struct client *client, uint32_t event, if (is_new) break; } - - if ((event & SUBSCRIPTION_EVENT_TYPE_MASK) == SUBSCRIPTION_EVENT_CHANGE) { + else if (type == SUBSCRIPTION_EVENT_CHANGE) { /* This object has changed. If a "new" or "change" event for * this object is still in the queue we can exit. */ goto drop; @@ -349,28 +348,46 @@ static bool client_prune_subscribe_events(struct client *client, uint32_t event, return false; drop: - pw_log_debug("client %p: dropped redundant event for object %u", client, index); + pw_log_debug("client %p: dropped redundant event '%s' on %s #%u", + client, + subscription_event_type_to_string(type), subscription_event_facility_to_string(facility), + index); return true; } -int client_queue_subscribe_event(struct client *client, uint32_t mask, uint32_t event, uint32_t index) +int client_queue_subscribe_event(struct client *client, uint32_t facility, uint32_t type, uint32_t index) { + spa_assert( + type == SUBSCRIPTION_EVENT_NEW || + type == SUBSCRIPTION_EVENT_CHANGE || + type == SUBSCRIPTION_EVENT_REMOVE + ); + + const uint32_t mask = 1u << facility; + spa_assert(SUBSCRIPTION_MASK_ALL & mask); + if (client->disconnect) return -ENOTCONN; if (!(client->subscribed & mask)) return 0; - pw_log_debug("client %p: SUBSCRIBE event:%08x index:%u", client, event, index); + pw_log_debug("client %p: SUBSCRIBE facility:%s (%u) type:%s (0x%02x) index:%u", + client, + subscription_event_facility_to_string(facility), facility, + subscription_event_type_to_string(type), type, + index); - if (client_prune_subscribe_events(client, event, index)) + if (client_prune_subscribe_events(client, facility, type, index)) return 0; struct message *reply = message_alloc(client->impl, -1, 0); if (!reply) return -errno; + const uint32_t event = facility | type; + reply->type = MESSAGE_TYPE_SUBSCRIPTION_EVENT; reply->u.subscription_event.event = event; reply->u.subscription_event.index = index; diff --git a/src/modules/module-protocol-pulse/client.h b/src/modules/module-protocol-pulse/client.h index e67c1afc..18c3efb7 100644 --- a/src/modules/module-protocol-pulse/client.h +++ b/src/modules/module-protocol-pulse/client.h @@ -99,7 +99,7 @@ void client_disconnect(struct client *client); void client_free(struct client *client); int client_queue_message(struct client *client, struct message *msg); int client_flush_messages(struct client *client); -int client_queue_subscribe_event(struct client *client, uint32_t mask, uint32_t event, uint32_t id); +int client_queue_subscribe_event(struct client *client, uint32_t facility, uint32_t type, uint32_t index); void client_update_routes(struct client *client, const char *key, const char *value); diff --git a/src/modules/module-protocol-pulse/cmd.c b/src/modules/module-protocol-pulse/cmd.c index 0fcd83a9..58f0ce4b 100644 --- a/src/modules/module-protocol-pulse/cmd.c +++ b/src/modules/module-protocol-pulse/cmd.c @@ -73,42 +73,50 @@ static int parse_cmd(void *user_data, const char *location, int res = 0; spa_autofree char *s = strndup(str, len); - spa_json_init(&it[0], s, len); - if (spa_json_enter_array(&it[0], &it[1]) < 0) { + if (spa_json_begin_array(&it[0], s, len) < 0) { pw_log_error("config file error: pulse.cmd is not an array"); return -EINVAL; } - while (spa_json_enter_object(&it[1], &it[2]) > 0) { + while (spa_json_enter_object(&it[0], &it[1]) > 0) { char *cmd = NULL, *args = NULL, *flags = NULL; + const char *val; + bool have_match = true; + int l; - 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; - + while ((l = spa_json_object_next(&it[1], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "cmd")) { cmd = (char*)val; - spa_json_parse_stringn(val, len, cmd, len+1); + spa_json_parse_stringn(val, l, cmd, l+1); } else if (spa_streq(key, "args")) { args = (char*)val; - spa_json_parse_stringn(val, len, args, len+1); + spa_json_parse_stringn(val, l, args, l+1); } else if (spa_streq(key, "flags")) { - if (spa_json_is_container(val, len)) - len = spa_json_container_len(&it[2], val, len); + if (spa_json_is_container(val, l)) + l = spa_json_container_len(&it[1], val, l); flags = (char*)val; - spa_json_parse_stringn(val, len, flags, len+1); + spa_json_parse_stringn(val, l, flags, l+1); + } else if (spa_streq(key, "condition")) { + if (!spa_json_is_array(val, l)) { + pw_log_warn("expected array for condition in '%.*s'", + (int)l, str); + break; + } + spa_json_enter(&it[1], &it[2]); + have_match = pw_conf_find_match(&it[2], &impl->props->dict, true); + } else { + pw_log_warn("unknown pulse.cmd key %s", key); } } + if (!have_match) + continue; + if (cmd != NULL) res = do_cmd(impl, cmd, args, flags); if (res < 0) break; } - - return res; + return res < 0 ? res : 0; } int cmd_run(struct impl *impl) diff --git a/src/modules/module-protocol-pulse/defs.h b/src/modules/module-protocol-pulse/defs.h index 8832f0aa..fa47c3d8 100644 --- a/src/modules/module-protocol-pulse/defs.h +++ b/src/modules/module-protocol-pulse/defs.h @@ -153,21 +153,6 @@ static inline int err_to_res(int err) return -EIO; } -enum { - SUBSCRIPTION_MASK_NULL = 0x0000U, - SUBSCRIPTION_MASK_SINK = 0x0001U, - SUBSCRIPTION_MASK_SOURCE = 0x0002U, - SUBSCRIPTION_MASK_SINK_INPUT = 0x0004U, - SUBSCRIPTION_MASK_SOURCE_OUTPUT = 0x0008U, - SUBSCRIPTION_MASK_MODULE = 0x0010U, - SUBSCRIPTION_MASK_CLIENT = 0x0020U, - SUBSCRIPTION_MASK_SAMPLE_CACHE = 0x0040U, - SUBSCRIPTION_MASK_SERVER = 0x0080U, - SUBSCRIPTION_MASK_AUTOLOAD = 0x0100U, - SUBSCRIPTION_MASK_CARD = 0x0200U, - SUBSCRIPTION_MASK_ALL = 0x02ffU -}; - enum { SUBSCRIPTION_EVENT_SINK = 0x0000U, SUBSCRIPTION_EVENT_SOURCE = 0x0001U, @@ -177,7 +162,7 @@ enum { SUBSCRIPTION_EVENT_CLIENT = 0x0005U, SUBSCRIPTION_EVENT_SAMPLE_CACHE = 0x0006U, SUBSCRIPTION_EVENT_SERVER = 0x0007U, - SUBSCRIPTION_EVENT_AUTOLOAD = 0x0008U, + // SUBSCRIPTION_EVENT_AUTOLOAD = 0x0008U, SUBSCRIPTION_EVENT_CARD = 0x0009U, SUBSCRIPTION_EVENT_FACILITY_MASK = 0x000FU, @@ -187,6 +172,31 @@ enum { SUBSCRIPTION_EVENT_TYPE_MASK = 0x0030U }; +enum { + SUBSCRIPTION_MASK_NULL = 0, + SUBSCRIPTION_MASK_SINK = 1u << SUBSCRIPTION_EVENT_SINK, + SUBSCRIPTION_MASK_SOURCE = 1u << SUBSCRIPTION_EVENT_SOURCE, + SUBSCRIPTION_MASK_SINK_INPUT = 1u << SUBSCRIPTION_EVENT_SINK_INPUT, + SUBSCRIPTION_MASK_SOURCE_OUTPUT = 1u << SUBSCRIPTION_EVENT_SOURCE_OUTPUT, + SUBSCRIPTION_MASK_MODULE = 1u << SUBSCRIPTION_EVENT_MODULE, + SUBSCRIPTION_MASK_CLIENT = 1u << SUBSCRIPTION_EVENT_CLIENT, + SUBSCRIPTION_MASK_SAMPLE_CACHE = 1u << SUBSCRIPTION_EVENT_SAMPLE_CACHE, + SUBSCRIPTION_MASK_SERVER = 1u << SUBSCRIPTION_EVENT_SERVER, + // SUBSCRIPTION_MASK_AUTOLOAD = 1u << SUBSCRIPTION_EVENT_AUTOLOAD, + SUBSCRIPTION_MASK_CARD = 1u << SUBSCRIPTION_EVENT_CARD, + SUBSCRIPTION_MASK_ALL = + SUBSCRIPTION_MASK_SINK + | SUBSCRIPTION_MASK_SOURCE + | SUBSCRIPTION_MASK_SINK_INPUT + | SUBSCRIPTION_MASK_SOURCE_OUTPUT + | SUBSCRIPTION_MASK_MODULE + | SUBSCRIPTION_MASK_CLIENT + | SUBSCRIPTION_MASK_SAMPLE_CACHE + | SUBSCRIPTION_MASK_SERVER + // | SUBSCRIPTION_MASK_AUTOLOAD + | SUBSCRIPTION_MASK_CARD +}; + enum { STATE_INVALID = -1, STATE_RUNNING = 0, @@ -236,6 +246,41 @@ enum { SOURCE_FLAT_VOLUME = 0x0080U, }; +static inline const char *subscription_event_type_to_string(uint32_t type) +{ + switch (type) { + case SUBSCRIPTION_EVENT_NEW: + return "new"; + case SUBSCRIPTION_EVENT_CHANGE: + return "change"; + case SUBSCRIPTION_EVENT_REMOVE: + return "remove"; + } + + return NULL; +} + +static inline const char *subscription_event_facility_to_string(uint32_t facility) +{ + static const char * const strings[] = { + [SUBSCRIPTION_EVENT_SINK] = "sink", + [SUBSCRIPTION_EVENT_SOURCE] = "source", + [SUBSCRIPTION_EVENT_SINK_INPUT] = "sink-input", + [SUBSCRIPTION_EVENT_SOURCE_OUTPUT] = "source-output", + [SUBSCRIPTION_EVENT_MODULE] = "module", + [SUBSCRIPTION_EVENT_CLIENT] = "client", + [SUBSCRIPTION_EVENT_SAMPLE_CACHE] = "sample-cache", + [SUBSCRIPTION_EVENT_SERVER] = "server", + // [SUBSCRIPTION_EVENT_AUTOLOAD] = "autoload", + [SUBSCRIPTION_EVENT_CARD] = "card", + }; + + if (facility >= SPA_N_ELEMENTS(strings)) + return NULL; + + return strings[facility]; +} + static const char * const port_types[] = { "unknown", "aux", diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c index 5626fe27..7ce9757c 100644 --- a/src/modules/module-protocol-pulse/format.c +++ b/src/modules/module-protocol-pulse/format.c @@ -7,6 +7,7 @@ #include <spa/param/audio/format.h> #include <spa/param/audio/format-utils.h> #include <spa/param/audio/raw.h> +#include <spa/param/audio/raw-json.h> #include <spa/utils/json.h> #include "format.h" @@ -130,22 +131,12 @@ uint32_t format_pa2id(enum sample_format format) const char *format_id2name(uint32_t format) { - int i; - for (i = 0; spa_type_audio_format[i].name; i++) { - if (spa_type_audio_format[i].type == format) - return spa_debug_type_short_name(spa_type_audio_format[i].name); - } - return "UNKNOWN"; + return spa_type_audio_format_to_short_name(format); } uint32_t format_name2id(const char *name) { - int i; - for (i = 0; spa_type_audio_format[i].name; i++) { - if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_format[i].name))) - return spa_type_audio_format[i].type; - } - return SPA_AUDIO_FORMAT_UNKNOWN; + return spa_type_audio_format_from_short_name(name); } uint32_t format_paname2id(const char *name, size_t size) @@ -230,6 +221,24 @@ uint32_t sample_spec_frame_size(const struct sample_spec *ss) } } +void sample_spec_silence(const struct sample_spec *ss, void *data, size_t size) +{ + switch (ss->format) { + case SPA_AUDIO_FORMAT_U8: + memset(data, 0x80, size); + break; + case SPA_AUDIO_FORMAT_ALAW: + memset(data, 0x80 ^ 0x55, size); + break; + case SPA_AUDIO_FORMAT_ULAW: + memset(data, 0x00 ^ 0xff, size); + break; + default: + memset(data, 0, size); + break; + } +} + bool sample_spec_valid(const struct sample_spec *ss) { return (sample_spec_frame_size(ss) > 0 && @@ -289,22 +298,12 @@ uint32_t channel_pa2id(enum channel_position channel) const char *channel_id2name(uint32_t channel) { - int i; - for (i = 0; spa_type_audio_channel[i].name; i++) { - if (spa_type_audio_channel[i].type == channel) - return spa_debug_type_short_name(spa_type_audio_channel[i].name); - } - return "UNK"; + return spa_type_audio_channel_to_short_name(channel); } uint32_t channel_name2id(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; + return spa_type_audio_channel_from_short_name(name); } enum channel_position channel_id2pa(uint32_t id, uint32_t *aux) @@ -354,6 +353,14 @@ void channel_map_to_positions(const struct channel_map *map, uint32_t *pos) pos[i] = map->map[i]; } +void positions_to_channel_map(const uint32_t *pos, uint32_t channels, struct channel_map *map) +{ + uint32_t i; + for (i = 0; i < channels; i++) + map->map[i] = pos[i]; + map->channels = channels; +} + void channel_map_parse(const char *str, struct channel_map *map) { const char *p = str; @@ -440,18 +447,9 @@ void channel_map_parse(const char *str, struct channel_map *map) void channel_map_parse_position(const char *str, struct channel_map *map) { - struct spa_json it[2]; - char v[256]; - - spa_json_init(&it[0], str, strlen(str)); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], str, strlen(str)); - - map->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - map->channels < SPA_AUDIO_MAX_CHANNELS) { - map->map[map->channels++] = channel_name2id(v); - } + uint32_t channels = 0, position[SPA_AUDIO_MAX_CHANNELS]; + spa_audio_parse_position(str, strlen(str), position, &channels); + positions_to_channel_map(position, channels, map); } bool channel_map_valid(const struct channel_map *map) @@ -806,8 +804,7 @@ static uint32_t format_info_get_format(const struct format_info *info) if ((str = pw_properties_get(info->props, "format.sample_format")) == NULL) return SPA_AUDIO_FORMAT_UNKNOWN; - spa_json_init(&it[0], str, strlen(str)); - if ((len = spa_json_next(&it[0], &val)) <= 0) + if ((len = spa_json_begin(&it[0], str, strlen(str), &val)) <= 0) return SPA_AUDIO_FORMAT_UNKNOWN; if (spa_json_is_string(val, len)) @@ -825,8 +822,7 @@ static int format_info_get_rate(const struct format_info *info) if ((str = pw_properties_get(info->props, "format.rate")) == NULL) return -ENOENT; - spa_json_init(&it[0], str, strlen(str)); - if ((len = spa_json_next(&it[0], &val)) <= 0) + if ((len = spa_json_begin(&it[0], str, strlen(str), &val)) <= 0) return -EINVAL; if (spa_json_is_int(val, len)) { if (spa_json_parse_int(val, len, &v) <= 0) @@ -862,8 +858,7 @@ int format_info_to_spec(const struct format_info *info, struct sample_spec *ss, if ((str = pw_properties_get(info->props, "format.channels")) == NULL) return -ENOENT; - spa_json_init(&it[0], str, strlen(str)); - if ((len = spa_json_next(&it[0], &val)) <= 0) + if ((len = spa_json_begin(&it[0], str, strlen(str), &val)) <= 0) return -EINVAL; if (spa_json_is_float(val, len)) { if (spa_json_parse_float(val, len, &f) <= 0) @@ -877,8 +872,7 @@ int format_info_to_spec(const struct format_info *info, struct sample_spec *ss, return -ENOTSUP; if ((str = pw_properties_get(info->props, "format.channel_map")) != NULL) { - spa_json_init(&it[0], str, strlen(str)); - if ((len = spa_json_next(&it[0], &val)) <= 0) + if ((len = spa_json_begin(&it[0], str, strlen(str), &val)) <= 0) return -EINVAL; if (!spa_json_is_string(val, len)) return -EINVAL; diff --git a/src/modules/module-protocol-pulse/format.h b/src/modules/module-protocol-pulse/format.h index 8f0133f4..d564b182 100644 --- a/src/modules/module-protocol-pulse/format.h +++ b/src/modules/module-protocol-pulse/format.h @@ -11,7 +11,7 @@ struct spa_pod; struct spa_pod_builder; -#define RATE_MAX (48000u*8u) +#define RATE_MAX (48000u*16u) #define CHANNELS_MAX (64u) enum sample_format { @@ -182,6 +182,7 @@ const char *format_encoding2name(enum encoding enc); uint32_t format_encoding2id(enum encoding enc); uint32_t sample_spec_frame_size(const struct sample_spec *ss); +void sample_spec_silence(const struct sample_spec *ss, void *data, size_t size); bool sample_spec_valid(const struct sample_spec *ss); void sample_spec_fix(struct sample_spec *ss, struct channel_map *map, diff --git a/src/modules/module-protocol-pulse/internal.h b/src/modules/module-protocol-pulse/internal.h index a24296ae..6e7eba7b 100644 --- a/src/modules/module-protocol-pulse/internal.h +++ b/src/modules/module-protocol-pulse/internal.h @@ -83,6 +83,6 @@ void impl_add_listener(struct impl *impl, struct spa_hook *listener, const struct impl_events *events, void *data); -void broadcast_subscribe_event(struct impl *impl, uint32_t mask, uint32_t event, uint32_t id); +void broadcast_subscribe_event(struct impl *impl, uint32_t facility, uint32_t type, uint32_t id); #endif diff --git a/src/modules/module-protocol-pulse/module.c b/src/modules/module-protocol-pulse/module.c index 4b9217a0..0d45399c 100644 --- a/src/modules/module-protocol-pulse/module.c +++ b/src/modules/module-protocol-pulse/module.c @@ -106,8 +106,8 @@ int module_unload(struct module *module) if (module->loaded) broadcast_subscribe_event(impl, - SUBSCRIPTION_MASK_MODULE, - SUBSCRIPTION_EVENT_REMOVE | SUBSCRIPTION_EVENT_MODULE, + SUBSCRIPTION_EVENT_MODULE, + SUBSCRIPTION_EVENT_REMOVE, module->index); module_free(module); diff --git a/src/modules/module-protocol-pulse/modules/module-stream-restore.c b/src/modules/module-protocol-pulse/modules/module-stream-restore.c index 79481390..bdb9cfd7 100644 --- a/src/modules/module-protocol-pulse/modules/module-stream-restore.c +++ b/src/modules/module-protocol-pulse/modules/module-stream-restore.c @@ -177,7 +177,7 @@ static int do_extension_stream_restore_read(struct module *module, struct client reply = reply_new(client, tag); spa_dict_for_each(item, &client->routes->dict) { - struct spa_json it[3]; + struct spa_json it[2]; const char *value; char name[1024], key[128]; char device_name[1024] = "\0"; @@ -185,52 +185,52 @@ static int do_extension_stream_restore_read(struct module *module, struct client struct volume vol = VOLUME_INIT; struct channel_map map = CHANNEL_MAP_INIT; float volume = 0.0f; + int len; if (key_to_name(item->key, name, sizeof(name)) < 0) continue; pw_log_debug("%s -> %s: %s", item->key, name, item->value); - spa_json_init(&it[0], item->value, strlen(item->value)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) + if (spa_json_begin_object(&it[0], item->value, strlen(item->value)) <= 0) continue; - while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &value)) > 0) { if (spa_streq(key, "volume")) { - if (spa_json_get_float(&it[1], &volume) <= 0) + if (spa_json_parse_float(value, len, &volume) <= 0) continue; } else if (spa_streq(key, "mute")) { - if (spa_json_get_bool(&it[1], &mute) <= 0) + if (spa_json_parse_bool(value, len, &mute) <= 0) continue; } else if (spa_streq(key, "volumes")) { vol = VOLUME_INIT; - if (spa_json_enter_array(&it[1], &it[2]) <= 0) + if (!spa_json_is_array(value, len)) continue; + spa_json_enter(&it[0], &it[1]); for (vol.channels = 0; vol.channels < CHANNELS_MAX; vol.channels++) { - if (spa_json_get_float(&it[2], &vol.values[vol.channels]) <= 0) + if (spa_json_get_float(&it[1], &vol.values[vol.channels]) <= 0) break; } } else if (spa_streq(key, "channels")) { - if (spa_json_enter_array(&it[1], &it[2]) <= 0) + if (!spa_json_is_array(value, len)) continue; + spa_json_enter(&it[0], &it[1]); for (map.channels = 0; map.channels < CHANNELS_MAX; map.channels++) { char chname[16]; - if (spa_json_get_string(&it[2], chname, sizeof(chname)) <= 0) + if (spa_json_get_string(&it[1], chname, sizeof(chname)) <= 0) break; map.map[map.channels] = channel_name2id(chname); } } else if (spa_streq(key, "target-node")) { - if (spa_json_get_string(&it[1], device_name, sizeof(device_name)) <= 0) + if (spa_json_parse_stringn(value, len, device_name, sizeof(device_name)) <= 0) continue; } - else if (spa_json_next(&it[1], &value) <= 0) - break; } message_put(reply, TAG_STRING, name, diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 313054b5..cb66f75e 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -100,13 +100,13 @@ static struct sample *find_sample(struct impl *impl, uint32_t index, const char return NULL; } -void broadcast_subscribe_event(struct impl *impl, uint32_t mask, uint32_t event, uint32_t index) +void broadcast_subscribe_event(struct impl *impl, uint32_t facility, uint32_t type, uint32_t index) { struct server *s; spa_list_for_each(s, &impl->servers, link) { struct client *c; spa_list_for_each(c, &s->clients, link) - client_queue_subscribe_event(c, mask, event, index); + client_queue_subscribe_event(c, facility, type, index); } } @@ -203,46 +203,36 @@ static struct stream *find_stream(struct client *client, uint32_t index) static int send_object_event(struct client *client, struct pw_manager_object *o, uint32_t type) { - uint32_t event = 0, mask = 0, res_index = o->index; + uint32_t event = 0, res_index = o->index; pw_log_debug("index:%d id:%d %08" PRIx64 " type:%u", o->index, o->id, o->change_mask, type); if (pw_manager_object_is_sink(o) && o->change_mask & PW_MANAGER_OBJECT_FLAG_SINK) { client_queue_subscribe_event(client, - SUBSCRIPTION_MASK_SINK, - SUBSCRIPTION_EVENT_SINK | type, + SUBSCRIPTION_EVENT_SINK, + type, res_index); } - if (pw_manager_object_is_source_or_monitor(o) && o->change_mask & PW_MANAGER_OBJECT_FLAG_SOURCE) { - mask = SUBSCRIPTION_MASK_SOURCE; + + if (pw_manager_object_is_source_or_monitor(o) && o->change_mask & PW_MANAGER_OBJECT_FLAG_SOURCE) event = SUBSCRIPTION_EVENT_SOURCE; - } - else if (pw_manager_object_is_sink_input(o)) { - mask = SUBSCRIPTION_MASK_SINK_INPUT; + else if (pw_manager_object_is_sink_input(o)) event = SUBSCRIPTION_EVENT_SINK_INPUT; - } - else if (pw_manager_object_is_source_output(o)) { - mask = SUBSCRIPTION_MASK_SOURCE_OUTPUT; + else if (pw_manager_object_is_source_output(o)) event = SUBSCRIPTION_EVENT_SOURCE_OUTPUT; - } - else if (pw_manager_object_is_module(o)) { - mask = SUBSCRIPTION_MASK_MODULE; + else if (pw_manager_object_is_module(o)) event = SUBSCRIPTION_EVENT_MODULE; - } - else if (pw_manager_object_is_client(o)) { - mask = SUBSCRIPTION_MASK_CLIENT; + else if (pw_manager_object_is_client(o)) event = SUBSCRIPTION_EVENT_CLIENT; - } - else if (pw_manager_object_is_card(o)) { - mask = SUBSCRIPTION_MASK_CARD; + else if (pw_manager_object_is_card(o)) event = SUBSCRIPTION_EVENT_CARD; - } else + else event = SPA_ID_INVALID; if (event != SPA_ID_INVALID) client_queue_subscribe_event(client, - mask, - event | type, + event, + type, res_index); return 0; } @@ -370,8 +360,8 @@ static void send_latency_offset_subscribe_event(struct client *client, struct pw if (changed) client_queue_subscribe_event(client, - SUBSCRIPTION_MASK_CARD, - SUBSCRIPTION_EVENT_CARD | SUBSCRIPTION_EVENT_CHANGE, + SUBSCRIPTION_EVENT_CARD, + SUBSCRIPTION_EVENT_CHANGE, id_to_index(manager, card_id)); } @@ -398,9 +388,8 @@ static void send_default_change_subscribe_event(struct client *client, bool sink if (changed) client_queue_subscribe_event(client, - SUBSCRIPTION_MASK_SERVER, - SUBSCRIPTION_EVENT_CHANGE | SUBSCRIPTION_EVENT_SERVER, + SUBSCRIPTION_EVENT_CHANGE, -1); } @@ -928,29 +917,6 @@ static void manager_object_data_timeout(void *data, struct pw_manager_object *o, temporary_move_target_timeout(client, o); } -static int json_object_find(const char *obj, const char *key, char *value, size_t len) -{ - struct spa_json it[2]; - const char *v; - char k[128]; - - spa_json_init(&it[0], obj, strlen(obj)); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) - return -EINVAL; - - while (spa_json_get_string(&it[1], k, sizeof(k)) > 0) { - if (spa_streq(k, key)) { - if (spa_json_get_string(&it[1], value, len) <= 0) - continue; - return 0; - } else { - if (spa_json_next(&it[1], &v) <= 0) - break; - } - } - return -ENOENT; -} - static void manager_metadata(void *data, struct pw_manager_object *o, uint32_t subject, const char *key, const char *type, const char *value) { @@ -965,7 +931,7 @@ static void manager_metadata(void *data, struct pw_manager_object *o, if (key == NULL || spa_streq(key, "default.audio.sink")) { if (value != NULL) { - if (json_object_find(value, + if (spa_json_str_object_find(value, strlen(value), "name", name, sizeof(name)) < 0) value = NULL; else @@ -980,7 +946,7 @@ static void manager_metadata(void *data, struct pw_manager_object *o, } if (key == NULL || spa_streq(key, "default.audio.source")) { if (value != NULL) { - if (json_object_find(value, + if (spa_json_str_object_find(value, strlen(value), "name", name, sizeof(name)) < 0) value = NULL; else @@ -1143,7 +1109,7 @@ static void stream_state_changed(void *data, enum pw_stream_state old, switch (state) { case PW_STREAM_STATE_ERROR: - reply_error(client, -1, stream->create_tag, -EIO); + reply_error(client, -1, stream->create_tag, -errno); destroy_stream = true; break; case PW_STREAM_STATE_UNCONNECTED: @@ -1424,20 +1390,7 @@ static void stream_process(void *data) if (avail < (int32_t)minreq || stream->corked) { /* underrun, produce a silence buffer */ size = SPA_MIN(d->maxsize, minreq); - switch (stream->ss.format) { - case SPA_AUDIO_FORMAT_U8: - memset(p, 0x80, size); - break; - case SPA_AUDIO_FORMAT_ALAW: - memset(p, 0x80 ^ 0x55, size); - break; - case SPA_AUDIO_FORMAT_ULAW: - memset(p, 0x00 ^ 0xff, size); - break; - default: - memset(p, 0, size); - break; - } + sample_spec_silence(&stream->ss, p, size); if (stream->draining && !stream->corked) { stream->draining = false; @@ -1760,6 +1713,9 @@ static int do_create_playback_stream(struct client *client, uint32_t command, ui if (n_valid_formats == 0) goto error_no_formats; + if (client->quirks & QUIRK_BLOCK_PLAYBACK_STREAM) + goto error_no_permission; + stream = stream_new(client, STREAM_TYPE_PLAYBACK, tag, &ss, &map, &attr); if (stream == NULL) goto error_errno; @@ -1774,6 +1730,8 @@ static int do_create_playback_stream(struct client *client, uint32_t command, ui stream->is_underrun = true; stream->underrun_for = -1; + pw_properties_set(props, "pulse.corked", corked ? "true" : "false"); + if (rate != 0) { struct spa_fraction lat; fix_playback_buffer_attr(stream, &attr, ss_rate, &lat); @@ -1832,6 +1790,9 @@ error_protocol: error_no_formats: res = -ENOTSUP; goto error; +error_no_permission: + res = -EPERM; + goto error; error_invalid: res = -EINVAL; goto error; @@ -2026,6 +1987,9 @@ static int do_create_record_stream(struct client *client, uint32_t command, uint if (n_valid_formats == 0) goto error_no_formats; + if (client->quirks & QUIRK_BLOCK_RECORD_STREAM) + goto error_no_permission; + stream = stream_new(client, STREAM_TYPE_RECORD, tag, &ss, &map, &attr); if (stream == NULL) goto error_errno; @@ -2041,6 +2005,8 @@ static int do_create_record_stream(struct client *client, uint32_t command, uint if (client->quirks & QUIRK_REMOVE_CAPTURE_DONT_MOVE) no_move = false; + pw_properties_set(props, "pulse.corked", corked ? "true" : "false"); + if (rate != 0) { struct spa_fraction lat; fix_record_buffer_attr(stream, &attr, ss_rate, &lat); @@ -2112,6 +2078,9 @@ error_protocol: error_no_formats: res = -ENOTSUP; goto error; +error_no_permission: + res = -EPERM; + goto error; error_invalid: res = -EINVAL; goto error; @@ -2411,8 +2380,8 @@ static int do_finish_upload_stream(struct client *client, uint32_t command, uint stream_free(stream); broadcast_subscribe_event(impl, - SUBSCRIPTION_MASK_SAMPLE_CACHE, - event | SUBSCRIPTION_EVENT_SAMPLE_CACHE, + SUBSCRIPTION_EVENT_SAMPLE_CACHE, + event, sample->index); return reply_simple_ack(client, tag); @@ -2621,9 +2590,8 @@ static int do_remove_sample(struct client *client, uint32_t command, uint32_t ta return -ENOENT; broadcast_subscribe_event(impl, - SUBSCRIPTION_MASK_SAMPLE_CACHE, - SUBSCRIPTION_EVENT_REMOVE | SUBSCRIPTION_EVENT_SAMPLE_CACHE, + SUBSCRIPTION_EVENT_REMOVE, sample->index); pw_map_remove(&impl->samples, sample->index); @@ -2655,8 +2623,7 @@ static int do_cork_stream(struct client *client, uint32_t command, uint32_t tag, if (stream == NULL || stream->type == STREAM_TYPE_UPLOAD) return -ENOENT; - stream->corked = cork; - stream_set_paused(stream, cork, "cork request"); + stream_set_corked(stream, cork); if (cork) { stream->is_underrun = true; } else { @@ -4042,6 +4009,7 @@ static int fill_sink_input_info(struct client *client, struct message *m, uint32_t module_id = SPA_ID_INVALID, client_id = SPA_ID_INVALID; uint32_t peer_index; struct device_info dev_info; + bool corked; if (!pw_manager_object_is_sink_input(o) || info == NULL || info->props == NULL) return -ENOENT; @@ -4069,6 +4037,10 @@ static int fill_sink_input_info(struct client *client, struct message *m, else peer_index = SPA_ID_INVALID; } + if ((str = spa_dict_lookup(info->props, "pulse.corked")) != NULL) + corked = spa_atob(str); + else + corked = dev_info.state != STATE_RUNNING; message_put(m, TAG_U32, o->index, /* sink_input index */ @@ -4094,7 +4066,7 @@ static int fill_sink_input_info(struct client *client, struct message *m, TAG_INVALID); if (client->version >= 19) message_put(m, - TAG_BOOLEAN, dev_info.state != STATE_RUNNING, /* corked */ + TAG_BOOLEAN, corked, /* corked */ TAG_INVALID); if (client->version >= 20) message_put(m, @@ -4121,6 +4093,7 @@ static int fill_source_output_info(struct client *client, struct message *m, uint32_t module_id = SPA_ID_INVALID, client_id = SPA_ID_INVALID; uint32_t peer_index; struct device_info dev_info; + bool corked; if (!pw_manager_object_is_source_output(o) || info == NULL || info->props == NULL) return -ENOENT; @@ -4148,6 +4121,10 @@ static int fill_source_output_info(struct client *client, struct message *m, else peer_index = SPA_ID_INVALID; } + if ((str = spa_dict_lookup(info->props, "pulse.corked")) != NULL) + corked = spa_atob(str); + else + corked = dev_info.state != STATE_RUNNING; message_put(m, TAG_U32, o->index, /* source_output index */ @@ -4168,7 +4145,7 @@ static int fill_source_output_info(struct client *client, struct message *m, TAG_INVALID); if (client->version >= 19) message_put(m, - TAG_BOOLEAN, dev_info.state != STATE_RUNNING, /* corked */ + TAG_BOOLEAN, corked, /* corked */ TAG_INVALID); if (client->version >= 22) { struct format_info fi; @@ -4878,8 +4855,8 @@ static void handle_module_loaded(struct module *module, struct client *client, u module->loaded = true; broadcast_subscribe_event(impl, - SUBSCRIPTION_MASK_MODULE, - SUBSCRIPTION_EVENT_NEW | SUBSCRIPTION_EVENT_MODULE, + SUBSCRIPTION_EVENT_MODULE, + SUBSCRIPTION_EVENT_NEW, module->index); if (client != NULL) { @@ -5576,6 +5553,8 @@ struct pw_protocol_pulse *pw_protocol_pulse_new(struct pw_context *context, cmd_run(impl); + notify_startup(); + return (struct pw_protocol_pulse *) impl; error_free: diff --git a/src/modules/module-protocol-pulse/quirks.c b/src/modules/module-protocol-pulse/quirks.c index e2e972e9..34cd4484 100644 --- a/src/modules/module-protocol-pulse/quirks.c +++ b/src/modules/module-protocol-pulse/quirks.c @@ -19,6 +19,8 @@ static uint64_t parse_quirks(const char *str) { "remove-capture-dont-move", QUIRK_REMOVE_CAPTURE_DONT_MOVE }, { "block-source-volume", QUIRK_BLOCK_SOURCE_VOLUME }, { "block-sink-volume", QUIRK_BLOCK_SINK_VOLUME }, + { "block-record-stream", QUIRK_BLOCK_RECORD_STREAM }, + { "block-playback-stream", QUIRK_BLOCK_PLAYBACK_STREAM }, }; SPA_FOR_EACH_ELEMENT_VAR(quirk_keys, i) { if (spa_streq(str, i->key)) diff --git a/src/modules/module-protocol-pulse/quirks.h b/src/modules/module-protocol-pulse/quirks.h index e817d0b8..56ca9fa0 100644 --- a/src/modules/module-protocol-pulse/quirks.h +++ b/src/modules/module-protocol-pulse/quirks.h @@ -12,6 +12,8 @@ #define QUIRK_REMOVE_CAPTURE_DONT_MOVE (1ull<<1) /** removes the capture stream DONT_MOVE flag */ #define QUIRK_BLOCK_SOURCE_VOLUME (1ull<<2) /** block volume changes to sources */ #define QUIRK_BLOCK_SINK_VOLUME (1ull<<3) /** block volume changes to sinks */ +#define QUIRK_BLOCK_RECORD_STREAM (1ull<<4) /** block creating a record stream */ +#define QUIRK_BLOCK_PLAYBACK_STREAM (1ull<<5) /** block creating a playback stream */ int client_update_quirks(struct client *client); diff --git a/src/modules/module-protocol-pulse/server.c b/src/modules/module-protocol-pulse/server.c index 3e25eaad..fe7d47ab 100644 --- a/src/modules/module-protocol-pulse/server.c +++ b/src/modules/module-protocol-pulse/server.c @@ -103,6 +103,15 @@ finish: return 0; } +static void stream_clear_data(struct stream *stream, + uint32_t offset, uint32_t len) +{ + uint32_t l0 = SPA_MIN(len, MAXLENGTH - offset), l1 = len - l0; + sample_spec_silence(&stream->ss, SPA_PTROFF(stream->buffer, offset, void), l0); + if (SPA_UNLIKELY(l1 > 0)) + sample_spec_silence(&stream->ss, stream->buffer, l1); +} + static int handle_memblock(struct client *client, struct message *msg) { struct stream *stream; @@ -149,6 +158,15 @@ static int handle_memblock(struct client *client, struct message *msg) goto finish; } + if (diff > 0) { + pw_log_debug("clear gap of %"PRIu64, diff); + /* if we jump forwards, clear the data we skipped because we might otherwise + * play back old data. FIXME, if the write pointer goes backwards and + * forwards, this might clear valid data. We should probably keep track of + * the highest write pointer and only clear when we go past that one. */ + stream_clear_data(stream, index % MAXLENGTH, SPA_MIN(diff, MAXLENGTH)); + } + index += diff; filled += diff; stream->write_index += diff; @@ -981,31 +999,27 @@ int servers_create_and_start(struct impl *impl, const char *addresses, struct pw { int len, res, count = 0, err = 0; /* store the first error to return when no servers could be created */ const char *v; - struct spa_json it[3]; + struct spa_json it[2]; /* update `err` if it hasn't been set to an errno */ #define UPDATE_ERR(e) do { if (err == 0) err = (e); } while (false) /* collect addresses into an array of `struct sockaddr_storage` */ - spa_json_init(&it[0], addresses, strlen(addresses)); /* [ <server-spec> ... ] */ - if (spa_json_enter_array(&it[0], &it[1]) < 0) + if (spa_json_begin_array(&it[0], addresses, strlen(addresses)) < 0) return -EINVAL; /* a server-spec is either an address or an object */ - while ((len = spa_json_next(&it[1], &v)) > 0) { + while ((len = spa_json_next(&it[0], &v)) > 0) { char addr_str[FORMATTED_SOCKET_ADDR_STRLEN] = { 0 }; char key[128], client_access[64] = { 0 }; struct sockaddr_storage addrs[2]; int i, max_clients = MAX_CLIENTS, listen_backlog = LISTEN_BACKLOG, n_addr; if (spa_json_is_object(v, len)) { - spa_json_enter(&it[1], &it[2]); - while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { - if ((len = spa_json_next(&it[2], &v)) <= 0) - break; - + spa_json_enter(&it[0], &it[1]); + while ((len = spa_json_object_next(&it[1], key, sizeof(key), &v)) > 0) { if (spa_streq(key, "address")) { spa_json_parse_stringn(v, len, addr_str, sizeof(addr_str)); } else if (spa_streq(key, "max-clients")) { diff --git a/src/modules/module-protocol-pulse/stream.c b/src/modules/module-protocol-pulse/stream.c index 94d1fdd9..e3b8b7b0 100644 --- a/src/modules/module-protocol-pulse/stream.c +++ b/src/modules/module-protocol-pulse/stream.c @@ -211,6 +211,16 @@ void stream_set_paused(struct stream *stream, bool paused, const char *reason) pw_stream_set_active(stream->stream, !paused); } +void stream_set_corked(struct stream *stream, bool cork) +{ + stream->corked = cork; + pw_log_info("cork %d", cork); + pw_stream_update_properties(stream->stream, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM("pulse.corked", cork ? "true" : "false"))); + stream_set_paused(stream, cork, "cork request"); +} + int stream_send_underflow(struct stream *stream, int64_t offset) { struct client *client = stream->client; diff --git a/src/modules/module-protocol-pulse/stream.h b/src/modules/module-protocol-pulse/stream.h index b0522ea4..64ecea68 100644 --- a/src/modules/module-protocol-pulse/stream.h +++ b/src/modules/module-protocol-pulse/stream.h @@ -107,6 +107,7 @@ void stream_free(struct stream *stream); void stream_flush(struct stream *stream); uint32_t stream_pop_missing(struct stream *stream); +void stream_set_corked(struct stream *stream, bool corked); void stream_set_paused(struct stream *stream, bool paused, const char *reason); int stream_send_underflow(struct stream *stream, int64_t offset); diff --git a/src/modules/module-protocol-pulse/utils.c b/src/modules/module-protocol-pulse/utils.c index 5ad8184a..e6b24e70 100644 --- a/src/modules/module-protocol-pulse/utils.c +++ b/src/modules/module-protocol-pulse/utils.c @@ -148,18 +148,21 @@ pid_t get_client_pid(struct client *client, int client_fd) const char *get_server_name(struct pw_context *context) { - const char *name = NULL; + const char *name = NULL, *sep; const struct pw_properties *props = pw_context_get_properties(context); name = getenv("PIPEWIRE_REMOTE"); if ((name == NULL || name[0] == '\0') && props != NULL) name = pw_properties_get(props, PW_KEY_REMOTE_NAME); + if (name != NULL && (sep = strrchr(name, '/')) != NULL) + name = sep+1; if (name == NULL || name[0] == '\0') name = PW_DEFAULT_REMOTE; return name; } -int create_pid_file(void) { +int create_pid_file(void) +{ char pid_file[PATH_MAX]; FILE *f; int res; @@ -185,3 +188,41 @@ int create_pid_file(void) { return 0; } + +int notify_startup(void) +{ + long v; + int fd, res = 0; + char *endptr; + const char *env = getenv("PIPEWIRE_PULSE_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("can't parse PIPEWIRE_PULSE_NOTIFICATION_FD env: %m"); + goto error; + } + fd = (int)v; + if (v != fd) { + res = -ERANGE; + pw_log_error("invalid PIPEWIRE_PULSE_NOTIFICATION_FD %ld: %s", v, spa_strerror(res)); + goto error; + } + if (dprintf(fd, "\n") < 0) { + res = -errno; + pw_log_error("can't signal PIPEWIRE_PULSE_NOTIFICATION_FD: %m"); + goto error; + } + close(fd); + unsetenv("PIPEWIRE_PULSE_NOTIFICATION_FD"); + return 0; + +error: + return res; +} diff --git a/src/modules/module-protocol-pulse/utils.h b/src/modules/module-protocol-pulse/utils.h index 4f867202..2b34bb83 100644 --- a/src/modules/module-protocol-pulse/utils.h +++ b/src/modules/module-protocol-pulse/utils.h @@ -16,5 +16,6 @@ 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); int create_pid_file(void); +int notify_startup(void); #endif /* PULSE_SERVER_UTILS_H */ diff --git a/src/modules/module-protocol-simple.c b/src/modules/module-protocol-simple.c index 5e327b68..bbed3b5f 100644 --- a/src/modules/module-protocol-simple.c +++ b/src/modules/module-protocol-simple.c @@ -27,6 +27,7 @@ #include <spa/debug/types.h> #include <spa/param/audio/type-info.h> #include <spa/param/audio/format-utils.h> +#include <spa/param/audio/raw-json.h> #include <pipewire/impl.h> @@ -785,42 +786,6 @@ static void impl_free(struct impl *impl) free(impl); } -static inline uint32_t 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 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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - static int calc_frame_size(struct spa_audio_info_raw *info) { int res = info->channels; @@ -857,23 +822,16 @@ static int calc_frame_size(struct spa_audio_info_raw *info) static int parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *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)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_FORMAT, + SPA_KEY_AUDIO_RATE, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); return calc_frame_size(info); } @@ -892,7 +850,7 @@ static void copy_props(struct impl *impl, const char *key) static int parse_params(struct impl *impl) { const char *str; - struct spa_json it[2]; + struct spa_json it[1]; char value[512]; pw_properties_fetch_bool(impl->props, "capture", &impl->capture); @@ -967,9 +925,8 @@ static int parse_params(struct impl *impl) if ((str = pw_properties_get(impl->props, "server.address")) == NULL) str = DEFAULT_SERVER; - spa_json_init(&it[0], str, strlen(str)); - if (spa_json_enter_array(&it[0], &it[1]) > 0) { - while (spa_json_get_string(&it[1], value, sizeof(value)) > 0) { + if (spa_json_begin_array_relax(&it[0], str, strlen(str)) > 0) { + while (spa_json_get_string(&it[0], value, sizeof(value)) > 0) { if (create_server(impl, value) == NULL) { pw_log_warn("%p: can't create server for %s: %m", impl, value); diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c index ccc24376..32d01122 100644 --- a/src/modules/module-pulse-tunnel.c +++ b/src/modules/module-pulse-tunnel.c @@ -27,6 +27,7 @@ #include <spa/param/audio/format-utils.h> #include <spa/param/latency-utils.h> #include <spa/param/audio/raw.h> +#include <spa/param/audio/raw-json.h> #include <pipewire/impl.h> #include <pipewire/i18n.h> @@ -186,9 +187,8 @@ struct impl { uint32_t target_latency; uint32_t current_latency; uint32_t target_buffer; - struct spa_io_rate_match *rate_match; struct spa_dll dll; - float max_error; + double max_error; unsigned resync:1; bool do_disconnect:1; @@ -331,23 +331,19 @@ static void stream_param_changed(void *d, uint32_t id, const struct spa_pod *par static void update_rate(struct impl *impl, uint32_t filled) { - float error, corr; + double error, corr; uint32_t current_latency; - if (impl->rate_match == NULL) - return; - current_latency = impl->current_latency + filled; - error = (float)impl->target_latency - (float)(current_latency); - error = SPA_CLAMP(error, -impl->max_error, impl->max_error); + error = (double)impl->target_latency - (double)(current_latency); + error = SPA_CLAMPD(error, -impl->max_error, impl->max_error); - corr = (float)spa_dll_update(&impl->dll, error); + corr = spa_dll_update(&impl->dll, error); pw_log_debug("error:%f corr:%f current:%u target:%u", error, corr, current_latency, impl->target_latency); - SPA_FLAG_SET(impl->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE); - impl->rate_match->rate = 1.0f / corr; + pw_stream_set_rate(impl->stream, 1.0 / corr); } static void playback_stream_process(void *d) @@ -440,21 +436,10 @@ static void capture_stream_process(void *d) pw_stream_queue_buffer(impl->stream, buf); } -static void stream_io_changed(void *data, uint32_t id, void *area, uint32_t size) -{ - struct impl *impl = data; - switch (id) { - case SPA_IO_RateMatch: - impl->rate_match = area; - break; - } -} - static const struct pw_stream_events playback_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = stream_destroy, .state_changed = stream_state_changed, - .io_changed = stream_io_changed, .param_changed = stream_param_changed, .process = playback_stream_process }; @@ -463,7 +448,6 @@ static const struct pw_stream_events capture_stream_events = { PW_VERSION_STREAM_EVENTS, .destroy = stream_destroy, .state_changed = stream_state_changed, - .io_changed = stream_io_changed, .param_changed = stream_param_changed, .process = capture_stream_process }; @@ -752,7 +736,7 @@ static void stream_latency_update_cb(pa_stream *s, void *userdata) pa_usec_t usec; int negative; pa_stream_get_latency(s, &usec, &negative); - pw_log_debug("latency %ld negative %d", usec, negative); + pw_log_debug("latency %" PRIu64 " negative %d", usec, negative); } static int create_pulse_stream(struct impl *impl) @@ -1064,61 +1048,18 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - -static inline uint32_t 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 void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *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)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_FORMAT, + SPA_KEY_AUDIO_RATE, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } static int calc_frame_size(struct spa_audio_info_raw *info) @@ -1267,7 +1208,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) goto error; } spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MIN, 128, impl->info.rate); - impl->max_error = 256.0f; + impl->max_error = 256.0; impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); if (impl->core == NULL) { diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index da6fc74b..9d940681 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -271,6 +271,9 @@ struct impl { bool mute; float volume; + struct spa_latency_info latency_info; + struct spa_process_latency_info process_latency; + struct spa_ringbuffer ring; uint8_t buffer[BUFFER_SIZE]; @@ -509,21 +512,11 @@ static void stream_send_packet(void *data, struct iovec *iov, size_t iovlen) out_vec[msg.msg_iovlen++] = (struct iovec) { header, 12 }; out_vec[msg.msg_iovlen++] = (struct iovec) { out, len }; - pw_log_debug("raop sending %ld", out_vec[0].iov_len + out_vec[1].iov_len + out_vec[2].iov_len); + pw_log_debug("raop sending %zu", out_vec[0].iov_len + out_vec[1].iov_len + out_vec[2].iov_len); send_packet(impl->server_fd, &msg); } -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 create_udp_socket(struct impl *impl, uint16_t *port) { int res, ip_version, fd, val, i, af; @@ -858,15 +851,29 @@ static uint32_t msec_to_samples(struct impl *impl, uint32_t msec) return (uint64_t) msec * impl->rate / 1000; } -static int rtsp_record_reply(void *data, int status, const struct spa_dict *headers, const struct pw_array *content) +static void update_latency(struct impl *impl) { - struct impl *impl = data; - const char *str; - uint32_t n_params; - const struct spa_pod *params[2]; + uint32_t n_params = 0; + const struct spa_pod *params[3]; uint8_t buffer[1024]; struct spa_pod_builder b; struct spa_latency_info latency; + + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + latency = SPA_LATENCY_INFO(PW_DIRECTION_INPUT); + + spa_process_latency_info_add(&impl->process_latency, &latency); + params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); + params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &impl->latency_info); + params[n_params++] = spa_process_latency_build(&b, SPA_PARAM_ProcessLatency, &impl->process_latency); + rtp_stream_update_params(impl->stream, params, n_params); +} + +static int rtsp_record_reply(void *data, int status, const struct spa_dict *headers, const struct pw_array *content) +{ + struct impl *impl = data; + const char *str; char progress[128]; struct timespec timeout, interval; @@ -884,25 +891,21 @@ static int rtsp_record_reply(void *data, int status, const struct spa_dict *head interval.tv_sec = 2; interval.tv_nsec = 0; - if (!impl->feedback_timer) + // feedback timer is only needed for auth_setup encryption + if (impl->encryption == CRYPTO_AUTH_SETUP && !impl->feedback_timer) { + impl->feedback_timer = pw_loop_add_timer(impl->loop, rtsp_do_post_feedback, impl); - pw_loop_update_timer(impl->loop, impl->feedback_timer, &timeout, &interval, false); + pw_loop_update_timer(impl->loop, impl->feedback_timer, &timeout, &interval, false); + } if ((str = spa_dict_lookup(headers, "Audio-Latency")) != NULL) { uint32_t l; if (spa_atou32(str, &l, 0)) impl->latency = SPA_MAX(l, impl->latency); } + impl->process_latency.rate = impl->latency + msec_to_samples(impl, RAOP_LATENCY_MS); - spa_zero(latency); - latency.direction = PW_DIRECTION_INPUT; - latency.min_rate = latency.max_rate = impl->latency + msec_to_samples(impl, RAOP_LATENCY_MS); - - n_params = 0; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency); - - rtp_stream_update_params(impl->stream, params, n_params); + update_latency(impl); rtp_stream_set_first(impl->stream); @@ -1664,6 +1667,32 @@ static void stream_props_changed(struct impl *impl, uint32_t id, const struct sp rtp_stream_set_param(impl->stream, id, param); } +static void param_latency_changed(struct impl *impl, const struct spa_pod *param) +{ + struct spa_latency_info latency; + + if (param == NULL || spa_latency_parse(param, &latency) < 0) + return; + if (latency.direction == SPA_DIRECTION_OUTPUT) + impl->latency_info = latency; + + update_latency(impl); +} + +static void param_process_latency_changed(struct impl *impl, const struct spa_pod *param) +{ + struct spa_process_latency_info info; + + if (param == NULL) + spa_zero(info); + else if (spa_process_latency_parse(param, &info) < 0) + return; + if (spa_process_latency_info_compare(&impl->process_latency, &info) == 0) + return; + impl->process_latency = info; + update_latency(impl); +} + static void stream_param_changed(void *data, uint32_t id, const struct spa_pod *param) { struct impl *impl = data; @@ -1679,6 +1708,12 @@ static void stream_param_changed(void *data, uint32_t id, const struct spa_pod * if (param != NULL) stream_props_changed(impl, id, param); break; + case SPA_PARAM_Latency: + param_latency_changed(impl, param); + break; + case SPA_PARAM_ProcessLatency: + param_process_latency_changed(impl, param); + break; default: break; } @@ -1808,6 +1843,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->context = context; impl->loop = pw_context_get_main_loop(context); + impl->latency_info = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT); + ip = pw_properties_get(props, "raop.ip"); port = pw_properties_get(props, "raop.port"); if (ip == NULL || port == NULL) { diff --git a/src/modules/module-raop/rtsp-client.c b/src/modules/module-raop/rtsp-client.c index 952891d9..fae71977 100644 --- a/src/modules/module-raop/rtsp-client.c +++ b/src/modules/module-raop/rtsp-client.c @@ -576,7 +576,7 @@ int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url, if ((f = open_memstream((char**)&msg, &len)) == NULL) return -errno; - fseek(f, sizeof(*msg), SEEK_SET); + fseek(f, offsetof(struct message, data), SEEK_SET); cseq = ++client->cseq; @@ -598,7 +598,7 @@ int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url, fclose(f); - msg->len = len - sizeof(*msg); + msg->len = len - offsetof(struct message, data); msg->offset = 0; msg->reply = reply; msg->user_data = user_data; diff --git a/src/modules/module-roc-source.c b/src/modules/module-roc-source.c index dc37756d..b5a5ea99 100644 --- a/src/modules/module-roc-source.c +++ b/src/modules/module-roc-source.c @@ -45,9 +45,16 @@ * - `local.repair.port = <str>`: local receiver TCP/UDP port for receiver packets * - `local.control.port = <str>`: local receiver TCP/UDP port for control packets * - `sess.latency.msec = <str>`: target network latency in milliseconds - * - `resampler.profile = <str>`: Possible values: `disable`, `high`, - * `medium`, `low`. - * - `fec.code = <str>`: Possible values: `disable`, `rs8m`, `ldpc` + * - `roc.resampler.backend = <str>`: Possible values: `default`, `builtin`, + * `speex`, `speexdec`. + * - `roc.resampler.profile = <str>`: Possible values: `default`, `high`, + * `medium`, `low`. + * - `roc.latency-tuner.backend = <str>`: Possible values: `default`, `niq` + * - `roc.latency-tuner.profile = <str>`: Possible values: `default`, `intact`, + * `responsive`, `gradual` + * - `fec.code = <str>`: Possible values: `default`, `disable`, `rs8m`, `ldpc` + * + * - `resampler.profile = <str>`: Deprecated, use roc.resampler.profile * * ## General options * @@ -65,7 +72,10 @@ * { name = libpipewire-module-roc-source * args = { * local.ip = 0.0.0.0 - * resampler.profile = medium + * #roc.resampler.backend = default + * roc.resampler.profile = medium + * #roc.latency-tuner.backend = default + * #roc.latency-tuner.profile = default * fec.code = disable * sess.latency.msec = 5000 * local.source.port = 10001 @@ -109,6 +119,9 @@ struct module_roc_source_data { roc_receiver *receiver; roc_resampler_profile resampler_profile; + roc_resampler_backend resampler_backend; + roc_latency_tuner_backend latency_tuner_backend; + roc_latency_tuner_profile latency_tuner_profile; roc_fec_encoding fec_code; uint32_t rate; char *local_ip; @@ -275,6 +288,9 @@ static int roc_source_setup(struct module_roc_source_data *data) receiver_config.frame_encoding.channels = ROC_CHANNEL_LAYOUT_STEREO; receiver_config.frame_encoding.format = ROC_FORMAT_PCM_FLOAT32; receiver_config.resampler_profile = data->resampler_profile; + receiver_config.resampler_backend = data->resampler_backend; + receiver_config.latency_tuner_backend = data->latency_tuner_backend; + receiver_config.latency_tuner_profile = data->latency_tuner_profile; info.rate = data->rate; @@ -377,7 +393,10 @@ static const struct spa_dict_item module_roc_source_info[] = { { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>" }, { PW_KEY_MODULE_DESCRIPTION, "roc source" }, { PW_KEY_MODULE_USAGE, "( source.name=<name for the source> ) " - "( resampler.profile=<empty>|disable|high|medium|low ) " + "( roc.resampler.backend=<empty>|default|builtin|speex|speexdec ) " + "( roc.resampler.profile=<empty>|default|high|medium|low ) " + "( roc.latency-tuner.backend=<empty>|default|niq ) " + "( roc.latency-tuner.profile=<empty>|default|intact|responsive|gradual ) " "( fec.code=<empty>|disable|rs8m|ldpc ) " "( sess.latency.msec=<target network latency in milliseconds> ) " "( local.ip=<local receiver ip> ) " @@ -473,8 +492,16 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) } else { data->sess_latency_msec = PW_ROC_DEFAULT_SESS_LATENCY; } - - if ((str = pw_properties_get(props, "resampler.profile")) != NULL) { + if ((str = pw_properties_get(props, "roc.resampler.backend")) != NULL) { + if (pw_roc_parse_resampler_backend(&data->resampler_backend, str)) { + pw_log_warn("Invalid resampler backend %s, using default", str); + data->resampler_backend = ROC_RESAMPLER_BACKEND_DEFAULT; + } + } else { + data->resampler_backend = ROC_RESAMPLER_BACKEND_DEFAULT; + } + if ((str = pw_properties_get(props, "roc.resampler.profile")) != NULL || + (str = pw_properties_get(props, "resampler.profile")) != NULL) { if (pw_roc_parse_resampler_profile(&data->resampler_profile, str)) { pw_log_warn("Invalid resampler profile %s, using default", str); data->resampler_profile = ROC_RESAMPLER_PROFILE_DEFAULT; @@ -482,6 +509,22 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) } else { data->resampler_profile = ROC_RESAMPLER_PROFILE_DEFAULT; } + if ((str = pw_properties_get(props, "roc.latency-tuner.backend")) != NULL) { + if (pw_roc_parse_latency_tuner_backend(&data->latency_tuner_backend, str)) { + pw_log_warn("Invalid latency-tuner backend %s, using default", str); + data->latency_tuner_backend = ROC_LATENCY_TUNER_BACKEND_DEFAULT; + } + } else { + data->latency_tuner_backend = ROC_LATENCY_TUNER_BACKEND_DEFAULT; + } + if ((str = pw_properties_get(props, "roc.latency-tuner.profile")) != NULL) { + if (pw_roc_parse_latency_tuner_profile(&data->latency_tuner_profile, str)) { + pw_log_warn("Invalid latency-tuner profile %s, using default", str); + data->latency_tuner_profile = ROC_LATENCY_TUNER_PROFILE_DEFAULT; + } + } else { + data->latency_tuner_profile = ROC_LATENCY_TUNER_PROFILE_DEFAULT; + } if ((str = pw_properties_get(props, "fec.code")) != NULL) { if (pw_roc_parse_fec_encoding(&data->fec_code, str)) { pw_log_error("Invalid fec code %s, using default", str); diff --git a/src/modules/module-roc/common.h b/src/modules/module-roc/common.h index 2164a342..4b30f41d 100644 --- a/src/modules/module-roc/common.h +++ b/src/modules/module-roc/common.h @@ -16,7 +16,7 @@ static inline int pw_roc_parse_fec_encoding(roc_fec_encoding *out, const char *str) { - if (!str || !*str) + if (!str || !*str || spa_streq(str, "default")) *out = ROC_FEC_ENCODING_DEFAULT; else if (spa_streq(str, "disable")) *out = ROC_FEC_ENCODING_DISABLE; @@ -31,7 +31,7 @@ static inline int pw_roc_parse_fec_encoding(roc_fec_encoding *out, const char *s static inline int pw_roc_parse_resampler_profile(roc_resampler_profile *out, const char *str) { - if (!str || !*str) + if (!str || !*str || spa_streq(str, "default")) *out = ROC_RESAMPLER_PROFILE_DEFAULT; else if (spa_streq(str, "high")) *out = ROC_RESAMPLER_PROFILE_HIGH; @@ -44,6 +44,47 @@ static inline int pw_roc_parse_resampler_profile(roc_resampler_profile *out, con return 0; } +static inline int pw_roc_parse_resampler_backend(roc_resampler_backend *out, const char *str) +{ + if (!str || !*str || spa_streq(str, "default")) + *out = ROC_RESAMPLER_BACKEND_DEFAULT; + else if (spa_streq(str, "builtin")) + *out = ROC_RESAMPLER_BACKEND_BUILTIN; + else if (spa_streq(str, "speex")) + *out = ROC_RESAMPLER_BACKEND_SPEEX; + else if (spa_streq(str, "speexdec")) + *out = ROC_RESAMPLER_BACKEND_SPEEXDEC; + else + return -EINVAL; + return 0; +} + +static inline int pw_roc_parse_latency_tuner_backend(roc_latency_tuner_backend *out, const char *str) +{ + if (!str || !*str || spa_streq(str, "default")) + *out = ROC_LATENCY_TUNER_BACKEND_DEFAULT; + else if (spa_streq(str, "niq")) + *out = ROC_LATENCY_TUNER_BACKEND_NIQ; + else + return -EINVAL; + return 0; +} + +static inline int pw_roc_parse_latency_tuner_profile(roc_latency_tuner_profile *out, const char *str) +{ + if (!str || !*str || spa_streq(str, "default")) + *out = ROC_LATENCY_TUNER_PROFILE_DEFAULT; + else if (spa_streq(str, "intact")) + *out = ROC_LATENCY_TUNER_PROFILE_INTACT; + else if (spa_streq(str, "responsive")) + *out = ROC_LATENCY_TUNER_PROFILE_RESPONSIVE; + else if (spa_streq(str, "gradual")) + *out = ROC_LATENCY_TUNER_PROFILE_GRADUAL; + else + return -EINVAL; + return 0; +} + static inline int pw_roc_create_endpoint(roc_endpoint **result, roc_protocol protocol, const char *ip, int port) { roc_endpoint *endpoint; diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c index af22e624..b2d0739c 100644 --- a/src/modules/module-rt.c +++ b/src/modules/module-rt.c @@ -95,6 +95,19 @@ * * The PipeWire server processes are explicitly configured with a valid nice level. * + * ## Config override + * + * A `module.rt.args` config section can be added + * to override the module arguments. + * + *\code{.unparsed} + * # ~/.config/pipewire/pipewire.conf.d/my-rt-args.conf + * + * module.rt.args = { + * #nice.level = 22 + * } + *\endcode + * * ## Example configuration * *\code{.unparsed} @@ -1076,6 +1089,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) res = -errno; goto error; } + pw_context_conf_update_props(context, "module."NAME".args", props); impl->context = context; impl->nice_level = pw_properties_get_int32(props, "nice.level", DEFAULT_NICE_LEVEL); @@ -1114,7 +1128,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if (!check_realtime_privileges(impl)) { if (!can_use_rtkit) { res = -ENOTSUP; - pw_log_warn("regular realtime scheduling not available" + pw_log_info("regular realtime scheduling not available" " (Portal/RTKit fallback disabled)"); goto error; } diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c index 2ed466b7..9eeb8502 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -60,6 +60,7 @@ * - `net.ttl = <int>`: TTL to use, default 1 * - `net.loop = <bool>`: loopback multicast, default false * - `stream.rules` = <rules>: match rules, use create-stream and announce-stream actions + * - `sap.max-sessions = <int>`: maximum number of concurrent send/receive sessions to track * - `sap.preamble-extra = [strings]`: extra attributes to add to the atomic SDP preamble * - `sap.end-extra = [strings]`: extra attributes to add to the end of the SDP message * @@ -135,7 +136,7 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic -#define MAX_SESSIONS 64 +#define DEFAULT_MAX_SESSIONS 64 #define DEFAULT_ANNOUNCE_RULES \ "[ { matches = [ { sess.sap.announce = true } ] actions = { announce-stream = { } } } ]" @@ -154,6 +155,8 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define DEFAULT_TTL 1 #define DEFAULT_LOOP false +#define MAX_SDP 2048 + #define USAGE "( local.ifname=<local interface name to use> ) " \ "( sap.ip=<SAP IP address to send announce, default:"DEFAULT_SAP_IP"> ) " \ "( sap.port=<SAP port to send on, default:"SPA_STRINGIFY(DEFAULT_SAP_PORT)"> ) " \ @@ -180,7 +183,8 @@ static const struct spa_dict_item module_info[] = { struct sdp_info { uint16_t hash; - uint32_t ntp; + uint32_t session_id; + uint32_t session_version; uint32_t t_ntp; char *origin; @@ -203,6 +207,7 @@ struct sdp_info { float ptime; uint32_t framecount; + uint32_t ssrc; uint32_t ts_offset; char *ts_refclk; }; @@ -220,6 +225,8 @@ struct session { struct sdp_info info; unsigned has_sent_sap:1; + unsigned has_sdp:1; + char sdp[MAX_SDP]; struct pw_properties *props; @@ -272,6 +279,7 @@ struct impl { struct spa_source *sap_source; uint32_t cleanup_interval; + uint32_t max_sessions; uint32_t n_sessions; struct spa_list sessions; @@ -376,7 +384,7 @@ static bool is_multicast(struct sockaddr *sa, socklen_t salen) static int make_unix_socket(const char *path) { struct sockaddr_un addr; - spa_autoclose int fd = socket(AF_UNIX, SOCK_DGRAM, 0); + spa_autoclose int fd = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); if (fd < 0) { pw_log_warn("Failed to create PTP management socket"); return -1; @@ -452,6 +460,8 @@ static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen, { int af, fd, val, res; struct ifreq req; + struct sockaddr_storage ba = *sa; + bool do_connect = false; char addr[128]; af = sa->ss_family; @@ -485,7 +495,11 @@ static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen, pw_log_info("join IPv4 group: %s iface:%d", addr, req.ifr_ifindex); res = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mr4, sizeof(mr4)); } else { - sa4->sin_addr.s_addr = INADDR_ANY; + struct sockaddr_in *ba4 = (struct sockaddr_in*)&ba; + if (ba4->sin_addr.s_addr != INADDR_ANY) { + ba4->sin_addr.s_addr = INADDR_ANY; + do_connect = true; + } } } else if (af == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)sa; @@ -498,7 +512,8 @@ static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen, pw_log_info("join IPv6 group: %s iface:%d", addr, req.ifr_ifindex); res = setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mr6, sizeof(mr6)); } else { - sa6->sin6_addr = in6addr_any; + struct sockaddr_in6 *ba6 = (struct sockaddr_in6*)&ba; + ba6->sin6_addr = in6addr_any; } } else { res = -EINVAL; @@ -511,11 +526,18 @@ static int make_recv_socket(struct sockaddr_storage *sa, socklen_t salen, goto error; } - if (bind(fd, (struct sockaddr*)sa, salen) < 0) { + if (bind(fd, (struct sockaddr*)&ba, salen) < 0) { res = -errno; pw_log_error("bind() failed: %m"); goto error; } + if (do_connect) { + if (connect(fd, (struct sockaddr*)sa, salen) < 0) { + res = -errno; + pw_log_error("connect() failed: %m"); + goto error; + } + } return fd; error: close(fd); @@ -529,9 +551,11 @@ static void update_ts_refclk(struct impl *impl) // Read if something is left in the socket int avail; - ioctl(impl->ptp_fd, FIONREAD, &avail); uint8_t tmp; - while (avail--) read(impl->ptp_fd, &tmp, 1); + + ioctl(impl->ptp_fd, FIONREAD, &avail); + pw_log_debug("Flushing stale data: %u bytes", avail); + while (avail-- && read(impl->ptp_fd, &tmp, 1)); struct ptp_management_msg req; spa_zero(req); @@ -634,68 +658,45 @@ static void update_ts_refclk(struct impl *impl) memcpy(impl->gm_id, gmid, 8); } -static int send_sap(struct impl *impl, struct session *sess, bool bye) +static int make_sdp(struct impl *impl, struct session *sess, char *buffer, size_t buffer_size) { - char buffer[2048], src_addr[64], dst_addr[64], dst_ttl[8]; - const char *user_name; - struct sockaddr *sa = (struct sockaddr*)&impl->src_addr; - struct sap_header header; - struct iovec iov[4]; - struct msghdr msg; - struct spa_strbuf buf; + char src_addr[64], dst_addr[64], dst_ttl[8]; struct sdp_info *sdp = &sess->info; bool src_ip4, dst_ip4; + bool multicast; + const char *user_name; + struct spa_strbuf buf; int res; - if (!sess->has_sent_sap && bye) - return 0; - - spa_zero(header); - header.v = 1; - header.t = bye; - header.msg_id_hash = sdp->hash; - - iov[0].iov_base = &header; - iov[0].iov_len = sizeof(header); - if ((res = pw_net_get_ip(&impl->src_addr, src_addr, sizeof(src_addr), &src_ip4, NULL)) < 0) return res; - if (src_ip4) { - iov[1].iov_base = &((struct sockaddr_in*) sa)->sin_addr; - iov[1].iov_len = 4U; - } else { - iov[1].iov_base = &((struct sockaddr_in6*) sa)->sin6_addr; - iov[1].iov_len = 16U; - header.a = 1; - } - iov[2].iov_base = SAP_MIME_TYPE; - iov[2].iov_len = sizeof(SAP_MIME_TYPE); - if ((res = pw_net_get_ip(&sdp->dst_addr, dst_addr, sizeof(dst_addr), &dst_ip4, NULL)) < 0) return res; if ((user_name = pw_get_user_name()) == NULL) user_name = "-"; + multicast = is_multicast((struct sockaddr*)&sdp->dst_addr, sdp->dst_len); + spa_zero(dst_ttl); - if (is_multicast((struct sockaddr*)&sdp->dst_addr, sdp->dst_len)) + if (multicast) snprintf(dst_ttl, sizeof(dst_ttl), "/%d", sdp->ttl); - spa_strbuf_init(&buf, buffer, sizeof(buffer)); + spa_strbuf_init(&buf, buffer, buffer_size); /* Don't add any sdp records in between this definition or change the order it will break compatibility with Dante/AES67 devices. Add new records to the end. */ spa_strbuf_append(&buf, "v=0\n" - "o=%s %u 0 IN %s %s\n" + "o=%s %u %u IN %s %s\n" "s=%s\n" "c=IN %s %s%s\n" "t=%u 0\n" "m=%s %u RTP/AVP %i\n", - user_name, sdp->ntp, src_ip4 ? "IP4" : "IP6", src_addr, + user_name, sdp->session_id, sdp->session_version, src_ip4 ? "IP4" : "IP6", src_addr, sdp->session_name, - dst_ip4 ? "IP4" : "IP6", dst_addr, dst_ttl, + (multicast ? dst_ip4 : src_ip4) ? "IP4" : "IP6", multicast ? dst_addr : src_addr, dst_ttl, sdp->t_ntp, sdp->media_type, sdp->dst_port, sdp->payload); @@ -732,6 +733,9 @@ static int send_sap(struct impl *impl, struct session *sess, bool bye) "a=source-filter: incl IN %s %s %s\n", dst_ip4 ? "IP4" : "IP6", dst_addr, src_addr); + if (sdp->ssrc > 0) + spa_strbuf_append(&buf, "a=ssrc:%u\n", sdp->ssrc); + if (sdp->ptime > 0) spa_strbuf_append(&buf, "a=ptime:%.6g\n", sdp->ptime); @@ -770,10 +774,95 @@ static int send_sap(struct impl *impl, struct session *sess, bool bye) if (impl->extra_attrs_end) spa_strbuf_append(&buf, "%s", impl->extra_attrs_end); - pw_log_debug("sending SAP for %u %s", sess->node->id, buffer); + return 0; +} + +static int send_sap(struct impl *impl, struct session *sess, bool bye) +{ + struct sap_header header; + struct iovec iov[4]; + struct msghdr msg; + struct sdp_info *sdp = &sess->info; + int res; + + if (!sess->has_sent_sap && bye) + return 0; + + if (impl->sap_fd == -1) { + int fd; + char addr[64]; + const char *str; + + if ((str = pw_properties_get(sess->props, "source.ip")) == NULL) { + if (impl->ifname) { + int fd = socket(impl->sap_addr.ss_family, SOCK_DGRAM, 0); + if (fd >= 0) { + struct ifreq req; + spa_zero(req); + req.ifr_addr.sa_family = impl->sap_addr.ss_family; + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", impl->ifname); + res = ioctl(fd, SIOCGIFADDR, &req); + if (res < 0) + pw_log_warn("SIOCGIFADDR %s failed: %m", impl->ifname); + str = inet_ntop(req.ifr_addr.sa_family, + &((struct sockaddr_in *)&req.ifr_addr)->sin_addr, + addr, sizeof(addr)); + if (str == NULL) { + pw_log_warn("can't parse interface ip: %m"); + } else { + pw_log_info("interface %s IP: %s", impl->ifname, str); + } + close(fd); + } + } + if (str == NULL) + str = impl->sap_addr.ss_family == AF_INET ? + DEFAULT_SOURCE_IP : DEFAULT_SOURCE_IP6; + } + if ((res = pw_net_parse_address(str, 0, &impl->src_addr, &impl->src_len)) < 0) { + pw_log_error("invalid source.ip %s: %s", str, spa_strerror(res)); + return res; + } + if ((fd = make_send_socket(&impl->src_addr, impl->src_len, + &impl->sap_addr, impl->sap_len, + impl->mcast_loop, impl->ttl)) < 0) + return fd; + + impl->sap_fd = fd; + } + + /* For the first session, we might not yet have an SDP because the + * socket needs to be open for us to get the interface address (which + * happens above. So let's create the SDP now, if needed. */ + if (!sess->has_sdp) { + res = make_sdp(impl, sess, sess->sdp, sizeof(sess->sdp)); + if (res != 0) { + pw_log_error("Failed to create SDP: %s", spa_strerror(res)); + return res; + } + sess->has_sdp = true; + } + + spa_zero(header); + header.v = 1; + header.t = bye; + header.msg_id_hash = sdp->hash; + + iov[0].iov_base = &header; + iov[0].iov_len = sizeof(header); - iov[3].iov_base = buffer; - iov[3].iov_len = strlen(buffer); + if (impl->src_addr.ss_family == AF_INET) { + iov[1].iov_base = &((struct sockaddr_in*) &impl->src_addr)->sin_addr; + iov[1].iov_len = 4U; + } else { + iov[1].iov_base = &((struct sockaddr_in6*) &impl->src_addr)->sin6_addr; + iov[1].iov_len = 16U; + header.a = 1; + } + iov[2].iov_base = SAP_MIME_TYPE; + iov[2].iov_len = sizeof(SAP_MIME_TYPE); + iov[3].iov_base = sess->sdp; + iov[3].iov_len = strlen(sess->sdp); msg.msg_name = NULL; msg.msg_namelen = 0; @@ -783,6 +872,8 @@ static int send_sap(struct impl *impl, struct session *sess, bool bye) msg.msg_controllen = 0; msg.msg_flags = 0; + pw_log_debug("sending SAP for %u %s", sess->node->id, sess->sdp); + res = sendmsg(impl->sap_fd, &msg, MSG_NOSIGNAL); if (res < 0) res = -errno; @@ -827,37 +918,50 @@ static struct session *session_find(struct impl *impl, const struct sdp_info *in return NULL; } +static inline void replace_str(char **dst, const char *val) +{ + free(*dst); + *dst = val ? strdup(val) : NULL; +} + static struct session *session_new_announce(struct impl *impl, struct node *node, struct pw_properties *props) { + char buffer[MAX_SDP]; struct session *sess = NULL; struct sdp_info *sdp; const char *str; uint32_t port; int res; - if (impl->n_sessions >= MAX_SESSIONS) { - pw_log_warn("too many sessions (%u >= %u)", impl->n_sessions, MAX_SESSIONS); - errno = EMFILE; - return NULL; + sess = node->session; + if (sess == NULL) { + if (impl->n_sessions >= impl->max_sessions) { + pw_log_warn("too many sessions (%u >= %u)", impl->n_sessions, impl->max_sessions); + errno = EMFILE; + return NULL; + } + sess = calloc(1, sizeof(struct session)); + if (sess == NULL) + return NULL; + + pw_log_info("created new session for node:%u", node->id); + node->session = sess; + sess->node = node; + sess->impl = impl; + sess->announce = true; + spa_list_append(&impl->sessions, &sess->link); + impl->n_sessions++; } - sess = calloc(1, sizeof(struct session)); - if (sess == NULL) - return NULL; - sdp = &sess->info; - sess->announce = true; - - sdp->hash = pw_rand32(); - sdp->ntp = (uint32_t) time(NULL) + 2208988800U + impl->n_sessions; - sdp->t_ntp = pw_properties_get_uint32(props, "rtp.ntp", sdp->ntp); + pw_properties_free(sess->props); sess->props = props; if ((str = pw_properties_get(props, "sess.name")) == NULL) str = pw_get_host_name(); - sdp->session_name = strdup(str); + replace_str(&sdp->session_name, str); if ((str = pw_properties_get(props, "rtp.destination.port")) == NULL) goto error_free; @@ -882,43 +986,78 @@ static struct session *session_new_announce(struct impl *impl, struct node *node if (!spa_atou32(str, &sdp->framecount, 0)) sdp->framecount = 0; - if ((str = pw_properties_get(props, "rtp.media")) != NULL) - sdp->media_type = strdup(str); - if ((str = pw_properties_get(props, "rtp.mime")) != NULL) - sdp->mime_type = strdup(str); + str = pw_properties_get(props, "rtp.media"); + replace_str(&sdp->media_type, str); + str = pw_properties_get(props, "rtp.mime"); + replace_str(&sdp->mime_type, str); + if ((str = pw_properties_get(props, "rtp.rate")) != NULL) sdp->rate = atoi(str); if ((str = pw_properties_get(props, "rtp.channels")) != NULL) sdp->channels = atoi(str); + if ((str = pw_properties_get(props, "rtp.ssrc")) != NULL) + sdp->ssrc = atoi(str); + else + sdp->ssrc = 0; if ((str = pw_properties_get(props, "rtp.ts-offset")) != NULL) sdp->ts_offset = atoi(str); - if ((str = pw_properties_get(props, "rtp.ts-refclk")) != NULL) - sdp->ts_refclk = strdup(str); + str = pw_properties_get(props, "rtp.ts-refclk"); + replace_str(&sdp->ts_refclk, str); + sess->ts_refclk_ptp = pw_properties_get_bool(props, "rtp.fetch-ts-refclk", false); if ((str = pw_properties_get(props, PW_KEY_NODE_CHANNELNAMES)) != NULL) { struct spa_strbuf buf; - spa_strbuf_init(&buf, sdp->channelmap, sizeof(sdp->channelmap)); - - struct spa_json it[2]; + struct spa_json it[1]; char v[256]; + int count = 0; - spa_json_init(&it[0], str, strlen(str)); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], str, strlen(str)); + spa_strbuf_init(&buf, sdp->channelmap, sizeof(sdp->channelmap)); - if (spa_json_get_string(&it[1], v, sizeof(v)) > 0) - spa_strbuf_append(&buf, "%s", v); - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0) - spa_strbuf_append(&buf, ", %s", v); + if (spa_json_begin_array_relax(&it[0], str, strlen(str)) > 0) { + while (spa_json_get_string(&it[0], v, sizeof(v)) > 0) + spa_strbuf_append(&buf, "%s%s", count++ > 0 ? ", " : "", v); + } } - pw_log_info("created new session for node:%u", node->id); - node->session = sess; - sess->node = node; + /* see if we can make an SDP, will fail for the first session because we + * haven't got the SAP socket open yet */ + res = make_sdp(impl, sess, buffer, sizeof(buffer)); + + /* we had no sdp or something changed */ + if (res == 0 && (!sess->has_sdp || strcmp(buffer, sess->sdp) != 0)) { + /* send bye on the old session */ + send_sap(impl, sess, 1); + + /* update the version and hash */ + sdp->hash = pw_rand32(); + if ((str = pw_properties_get(props, "sess.id")) != NULL) { + if (!spa_atou32(str, &sdp->session_id, 10)) { + pw_log_error("Invalid session id: %s (must be a uint32)", str); + goto error_free; + } + sdp->t_ntp = pw_properties_get_uint32(props, "rtp.ntp", + (uint32_t) time(NULL) + 2208988800U + impl->n_sessions); + } else { + sdp->session_id = (uint32_t) time(NULL) + 2208988800U + impl->n_sessions; + sdp->t_ntp = pw_properties_get_uint32(props, "rtp.ntp", sdp->session_id); + } + if ((str = pw_properties_get(props, "sess.version")) != NULL) { + if (!spa_atou32(str, &sdp->session_version, 10)) { + pw_log_error("Invalid session version: %s (must be a uint32)", str); + goto error_free; + } + } else { + sdp->session_version = sdp->t_ntp; + } - sess->impl = impl; - spa_list_append(&impl->sessions, &sess->link); - impl->n_sessions++; + /* make an updated SDP for sending, this should not actually fail */ + res = make_sdp(impl, sess, sess->sdp, sizeof(sess->sdp)); + + if (res == 0) + sess->has_sdp = true; + else + pw_log_error("Failed to create SDP: %s", spa_strerror(res)); + } send_sap(impl, sess, 0); @@ -1002,6 +1141,8 @@ static int session_load_source(struct session *session, struct pw_properties *pr if ((str = pw_properties_get(props, "rtp.channels")) != NULL) pw_properties_set(props, "audio.channels", str); } + if ((str = pw_properties_get(props, "rtp.ssrc")) != NULL) + fprintf(f, "\"rtp.receiver-ssrc\" = \"%s\", ", str); } else { pw_log_error("Unhandled media %s", media); res = -EINVAL; @@ -1080,8 +1221,8 @@ static struct session *session_new(struct impl *impl, struct sdp_info *info) const char *str; char dst_addr[64], tmp[64]; - if (impl->n_sessions >= MAX_SESSIONS) { - pw_log_warn("too many sessions (%u >= %u)", impl->n_sessions, MAX_SESSIONS); + if (impl->n_sessions >= impl->max_sessions) { + pw_log_warn("too many sessions (%u >= %u)", impl->n_sessions, impl->max_sessions); errno = EMFILE; return NULL; } @@ -1128,6 +1269,9 @@ static struct session *session_new(struct impl *impl, struct sdp_info *info) pw_properties_setf(props, "rtp.ts-offset", "%u", info->ts_offset); pw_properties_set(props, "rtp.ts-refclk", info->ts_refclk); + if (info->ssrc > 0) + pw_properties_setf(props, "rtp.ssrc", "%u", info->ssrc); + if (info->channelmap[0]) pw_properties_set(props, PW_KEY_NODE_CHANNELNAMES, info->channelmap); @@ -1277,13 +1421,25 @@ static int parse_sdp_a_rtpmap(struct impl *impl, char *c, struct sdp_info *info) return 0; } +static int parse_sdp_a_ssrc(struct impl *impl, char *c, struct sdp_info *info) +{ + if (!spa_strstartswith(c, "a=ssrc:")) + return 0; + + c += strlen("a=ssrc:"); + if (!spa_atou32(c, &info->ssrc, 10)) + return -EINVAL; + return 0; +} + static int parse_sdp_a_ptime(struct impl *impl, char *c, struct sdp_info *info) { if (!spa_strstartswith(c, "a=ptime:")) return 0; c += strlen("a=ptime:"); - spa_atof(c, &info->ptime); + if (!spa_atof(c, &info->ptime)) + return -EINVAL; return 0; } @@ -1342,6 +1498,8 @@ static int parse_sdp(struct impl *impl, char *sdp, struct sdp_info *info) res = parse_sdp_m(impl, s, info); else if (spa_strstartswith(s, "a=rtpmap:")) res = parse_sdp_a_rtpmap(impl, s, info); + else if (spa_strstartswith(s, "a=ssrc:")) + res = parse_sdp_a_ssrc(impl, s, info); else if (spa_strstartswith(s, "a=ptime:")) res = parse_sdp_a_ptime(impl, s, info); else if (spa_strstartswith(s, "a=mediaclk:")) @@ -1439,7 +1597,7 @@ on_sap_io(void *data, int fd, uint32_t mask) int res; if (mask & SPA_IO_IN) { - uint8_t buffer[2048]; + uint8_t buffer[MAX_SDP]; ssize_t len; if ((len = recv(fd, buffer, sizeof(buffer), 0)) < 0) { @@ -1457,17 +1615,10 @@ on_sap_io(void *data, int fd, uint32_t mask) static int start_sap(struct impl *impl) { - int fd, res; + int fd = -1, res; struct timespec value, interval; char addr[128] = "invalid"; - if ((fd = make_send_socket(&impl->src_addr, impl->src_len, - &impl->sap_addr, impl->sap_len, - impl->mcast_loop, impl->ttl)) < 0) - return fd; - - impl->sap_fd = fd; - pw_log_info("starting SAP timer"); impl->timer = pw_loop_add_timer(impl->loop, on_timer_event, impl); if (impl->timer == NULL) { @@ -1495,7 +1646,8 @@ static int start_sap(struct impl *impl) return 0; error: - close(fd); + if (fd > 0) + close(fd); return res; } @@ -1505,7 +1657,12 @@ static void node_event_info(void *data, const struct pw_node_info *info) struct impl *impl = n->impl; const char *str; - if (n->session != NULL || info == NULL) + if (info == NULL) + return; + + // We only really want to do anything here if properties are updated, + // or if we don't have a session for this node already + if (!(info->change_mask & PW_NODE_CHANGE_MASK_PROPS) && n->session != NULL) return; n->info = pw_node_info_merge(n->info, info, true); @@ -1678,7 +1835,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) uint32_t port; const char *str; int res = 0; - char addr[64]; PW_LOG_TOPIC_INIT(mod_topic); @@ -1723,70 +1879,34 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->cleanup_interval = pw_properties_get_uint32(impl->props, "sap.cleanup.sec", DEFAULT_CLEANUP_SEC); - if ((str = pw_properties_get(props, "source.ip")) == NULL) { - if (impl->ifname) { - int fd = socket(impl->sap_addr.ss_family, SOCK_DGRAM, 0); - if (fd >= 0) { - struct ifreq req; - spa_zero(req); - req.ifr_addr.sa_family = impl->sap_addr.ss_family; - snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", impl->ifname); - res = ioctl(fd, SIOCGIFADDR, &req); - if (res < 0) - pw_log_warn("SIOCGIFADDR %s failed: %m", impl->ifname); - str = inet_ntop(req.ifr_addr.sa_family, - &((struct sockaddr_in *)&req.ifr_addr)->sin_addr, - addr, sizeof(addr)); - if (str == NULL) { - pw_log_warn("can't parse interface ip: %m"); - } else { - pw_log_info("interface %s IP: %s", impl->ifname, str); - } - close(fd); - } - } - if (str == NULL) - str = impl->sap_addr.ss_family == AF_INET ? - DEFAULT_SOURCE_IP : DEFAULT_SOURCE_IP6; - } - if ((res = pw_net_parse_address(str, 0, &impl->src_addr, &impl->src_len)) < 0) { - pw_log_error("invalid source.ip %s: %s", str, spa_strerror(res)); - goto out; - } - impl->ttl = pw_properties_get_uint32(props, "net.ttl", DEFAULT_TTL); impl->mcast_loop = pw_properties_get_bool(props, "net.loop", DEFAULT_LOOP); + impl->max_sessions = pw_properties_get_uint32(props, "sap.max-sessions", DEFAULT_MAX_SESSIONS); impl->extra_attrs_preamble = NULL; impl->extra_attrs_end = NULL; - char buffer[2048]; + char buffer[MAX_SDP]; struct spa_strbuf buf; if ((str = pw_properties_get(props, "sap.preamble-extra")) != NULL) { spa_strbuf_init(&buf, buffer, sizeof(buffer)); - struct spa_json it[2]; + struct spa_json it[1]; char line[256]; - spa_json_init(&it[0], str, strlen(str)); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], str, strlen(str)); - - while (spa_json_get_string(&it[1], line, sizeof(line)) > 0) - spa_strbuf_append(&buf, "%s\n", line); - + if (spa_json_begin_array_relax(&it[0], str, strlen(str)) > 0) { + while (spa_json_get_string(&it[0], line, sizeof(line)) > 0) + spa_strbuf_append(&buf, "%s\n", line); + } impl->extra_attrs_preamble = strdup(buffer); } if ((str = pw_properties_get(props, "sap.end-extra")) != NULL) { spa_strbuf_init(&buf, buffer, sizeof(buffer)); - struct spa_json it[2]; + struct spa_json it[1]; char line[256]; - spa_json_init(&it[0], str, strlen(str)); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], str, strlen(str)); - - while (spa_json_get_string(&it[1], line, sizeof(line)) > 0) - spa_strbuf_append(&buf, "%s\n", line); - + if (spa_json_begin_array_relax(&it[0], str, strlen(str)) > 0) { + while (spa_json_get_string(&it[0], line, sizeof(line)) > 0) + spa_strbuf_append(&buf, "%s\n", line); + } impl->extra_attrs_end = strdup(buffer); } diff --git a/src/modules/module-rtp-sink.c b/src/modules/module-rtp-sink.c index 4d64b8b7..7abd4d53 100644 --- a/src/modules/module-rtp-sink.c +++ b/src/modules/module-rtp-sink.c @@ -65,6 +65,8 @@ * - `sess.ts-refclk = <string>`: the name of a reference clock * - `sess.media = <string>`: the media type audio|midi|opus, default audio * - `stream.props = {}`: properties to be passed to the stream + * - `aes67.driver-group = <string>`: for AES67 streams, can be specified in order to allow + * the sink to be driven by a different node than the PTP driver. * * ## General options * @@ -147,6 +149,7 @@ PW_LOG_TOPIC(mod_topic, "mod." NAME); "( 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"> ) " \ + "( aes67.driver-group=<driver driving the PTP send> ) " \ "( stream.props= { key=value ... } ) " static const struct spa_dict_item module_info[] = { @@ -334,7 +337,8 @@ static void stream_props_changed(struct impl *impl, uint32_t id, const struct sp struct spa_pod_frame f; const char *key; struct spa_pod *pod; - const char *value; + struct spa_dict_item items[4]; + unsigned int n_items = 0; if (spa_pod_parse_object(param, SPA_TYPE_OBJECT_Props, NULL, SPA_PROP_params, SPA_POD_OPT_Pod(¶ms)) < 0) @@ -343,27 +347,44 @@ static void stream_props_changed(struct impl *impl, uint32_t id, const struct sp if (spa_pod_parser_push_struct(&prs, &f) < 0) return; - while (true) { + while (n_items < SPA_N_ELEMENTS(items)) { + const char *value_str = NULL; + int value_int = -1; + if (spa_pod_parser_get_string(&prs, &key) < 0) break; if (spa_pod_parser_get_pod(&prs, &pod) < 0) break; - if (spa_pod_get_string(pod, &value) < 0) - continue; - pw_log_info("key '%s', value '%s'", key, value); - if (!spa_streq(key, "destination.ip")) + if (spa_pod_get_string(pod, &value_str) < 0 && + spa_pod_get_int(pod, &value_int) < 0) continue; - if (pw_net_parse_address(value, impl->dst_port, &impl->dst_addr, - &impl->dst_len) < 0) { - pw_log_error("invalid destination.ip: '%s'", value); - break; + pw_log_info("key '%s', value '%s'/%u", key, value_str, value_int); + if (spa_streq(key, "destination.ip")) { + if (!value_str || pw_net_parse_address(value_str, impl->dst_port, &impl->dst_addr, + &impl->dst_len) < 0) { + pw_log_error("invalid destination.ip: '%s'", value_str); + break; + } + pw_properties_set(impl->stream_props, "rtp.destination.ip", value_str); + items[n_items++] = SPA_DICT_ITEM_INIT("rtp.destination.ip", value_str); + } else if (spa_streq(key, "sess.name")) { + if (!value_str) { + pw_log_error("invalid sess.name"); + break; + } + pw_properties_set(impl->stream_props, "sess.name", value_str); + items[n_items++] = SPA_DICT_ITEM_INIT("sess.name", value_str); + } else if (spa_streq(key, "sess.id") || spa_streq(key, "sess.version")) { + if (value_int < 0 || (unsigned int)value_int > UINT32_MAX) { + pw_log_error("invalid %s: '%d'", key, value_int); + break; + } + pw_properties_setf(impl->stream_props, key, "%d", value_int); + items[n_items++] = SPA_DICT_ITEM_INIT(key, pw_properties_get(impl->stream_props, key)); } - pw_properties_set(impl->stream_props, "rtp.destination.ip", value); - struct spa_dict_item item[1]; - item[0] = SPA_DICT_ITEM_INIT("rtp.destination.ip", value); - rtp_stream_update_properties(impl->stream, &SPA_DICT_INIT(item, 1)); - break; } + + rtp_stream_update_properties(impl->stream, &SPA_DICT_INIT(items, n_items)); } } } @@ -466,6 +487,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) const char *str, *sess_name; int64_t ts_offset; int res = 0; + uint32_t header_size; PW_LOG_TOPIC_INIT(mod_topic); @@ -527,10 +549,13 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, "net.mtu"); copy_props(impl, props, "sess.media"); copy_props(impl, props, "sess.name"); + copy_props(impl, props, "sess.id"); + copy_props(impl, props, "sess.version"); copy_props(impl, props, "sess.min-ptime"); copy_props(impl, props, "sess.max-ptime"); copy_props(impl, props, "sess.latency.msec"); copy_props(impl, props, "sess.ts-refclk"); + copy_props(impl, props, "aes67.driver-group"); str = pw_properties_get(props, "local.ifname"); impl->ifname = str ? strdup(str) : NULL; @@ -560,6 +585,10 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) ts_offset = pw_rand32(); pw_properties_setf(stream_props, "rtp.sender-ts-offset", "%u", (uint32_t)ts_offset); + header_size = impl->dst_addr.ss_family == AF_INET ? + IP4_HEADER_SIZE : IP6_HEADER_SIZE; + header_size += UDP_HEADER_SIZE; + pw_properties_setf(stream_props, "net.header", "%u", header_size); pw_net_get_ip(&impl->src_addr, addr, sizeof(addr), NULL, NULL); pw_properties_set(stream_props, "rtp.source.ip", addr); pw_net_get_ip(&impl->dst_addr, addr, sizeof(addr), NULL, NULL); diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c index 79411f0d..98a26065 100644 --- a/src/modules/module-rtp-source.c +++ b/src/modules/module-rtp-source.c @@ -56,6 +56,7 @@ * - `sess.latency.msec = <float>`: target network latency in milliseconds, default 100 * - `sess.ignore-ssrc = <bool>`: ignore SSRC, default false * - `sess.media = <string>`: the media type audio|midi|opus, default audio + * - `stream.may-pause = <bool>`: pause the stream when no data is reveived, default false * - `stream.props = {}`: properties to be passed to the stream * * ## General options @@ -165,10 +166,33 @@ struct impl { uint8_t *buffer; size_t buffer_size; - unsigned receiving:1; - unsigned last_receiving:1; + bool receiving; + bool may_pause; + bool standby; + bool waiting; }; +static int do_start(struct spa_loop *loop, bool async, uint32_t seq, const void *data, + size_t size, void *user_data) +{ + struct impl *impl = user_data; + if (impl->waiting) { + struct spa_dict_item item[1]; + + impl->waiting = false; + impl->standby = false; + + pw_log_info("resume RTP source"); + + item[0] = SPA_DICT_ITEM_INIT("rtp.receiving", "true"); + rtp_stream_update_properties(impl->stream, &SPA_DICT_INIT(item, 1)); + + if (impl->may_pause) + rtp_stream_set_active(impl->stream, true); + } + return 0; +} + static void on_rtp_io(void *data, int fd, uint32_t mask) { @@ -187,7 +211,10 @@ on_rtp_io(void *data, int fd, uint32_t mask) goto receive_error; } - impl->receiving = true; + if (!impl->receiving) { + impl->receiving = true; + pw_loop_invoke(impl->loop, do_start, 1, NULL, 0, false, impl); + } } return; @@ -353,7 +380,7 @@ static void stream_state_changed(void *data, bool started, const char *error) rtp_stream_set_error(impl->stream, res, "Can't start RTP stream"); } } else { - if (!impl->always_process) + if (!impl->always_process && !impl->standby) stream_stop(impl); } } @@ -430,17 +457,22 @@ static void on_timer_event(void *data, uint64_t expirations) { struct impl *impl = data; - if (impl->receiving != impl->last_receiving) { - struct spa_dict_item item[1]; + pw_log_debug("timer %d", impl->receiving); - impl->last_receiving = impl->receiving; + if (!impl->receiving) { + if (!impl->standby) { + struct spa_dict_item item[1]; - item[0] = SPA_DICT_ITEM_INIT("rtp.receiving", impl->receiving ? "true" : "false"); - rtp_stream_update_properties(impl->stream, &SPA_DICT_INIT(item, 1)); - } + pw_log_info("timeout, standby RTP source"); + impl->standby = true; + impl->waiting = true; - if (!impl->receiving) { - pw_log_info("timeout, inactive RTP source"); + item[0] = SPA_DICT_ITEM_INIT("rtp.receiving", "false"); + rtp_stream_update_properties(impl->stream, &SPA_DICT_INIT(item, 1)); + + if (impl->may_pause) + rtp_stream_set_active(impl->stream, false); + } //pw_impl_module_schedule_destroy(impl->module); } else { pw_log_debug("timeout, keeping active RTP source"); @@ -532,6 +564,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) int64_t ts_offset; char addr[128]; int res = 0; + uint32_t header_size; PW_LOG_TOPIC_INIT(mod_topic); @@ -591,6 +624,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, "sess.latency.msec"); copy_props(impl, props, "sess.ts-direct"); copy_props(impl, props, "sess.ignore-ssrc"); + copy_props(impl, props, "stream.may-pause"); str = pw_properties_get(props, "local.ifname"); impl->ifname = str ? strdup(str) : NULL; @@ -611,6 +645,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_set(stream_props, "rtp.source.ip", addr); pw_properties_setf(stream_props, "rtp.source.port", "%u", impl->src_port); + header_size = impl->src_addr.ss_family == AF_INET ? + IP4_HEADER_SIZE : IP6_HEADER_SIZE; + header_size += UDP_HEADER_SIZE; + pw_properties_setf(stream_props, "net.header", "%u", header_size); + ts_offset = pw_properties_get_int64(props, "sess.ts-offset", DEFAULT_TS_OFFSET); if (ts_offset == -1) ts_offset = pw_rand32(); @@ -618,6 +657,13 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->always_process = pw_properties_get_bool(stream_props, PW_KEY_NODE_ALWAYS_PROCESS, true); + impl->may_pause = pw_properties_get_bool(stream_props, + "stream.may-pause", false); + impl->standby = false; + impl->waiting = true; + /* Because we don't know the stream receiving state at the start, we try to fake it + * till we make it (or get timed out) */ + pw_properties_set(stream_props, "rtp.receiving", "true"); impl->cleanup_interval = pw_properties_get_uint32(props, "cleanup.sec", DEFAULT_CLEANUP_SEC); diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c index 43b74e91..e89712a3 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -37,14 +37,14 @@ static void rtp_audio_process_playback(void *data) memset(d[0].data, 0, wanted * stride); if (impl->have_sync) { impl->have_sync = false; - level = SPA_LOG_LEVEL_WARN; + level = SPA_LOG_LEVEL_INFO; } else { level = SPA_LOG_LEVEL_DEBUG; } pw_log(level, "underrun %d/%u < %u", avail, target_buffer, wanted); } else { - float error, corr; + double error, corr; if (impl->first) { if ((uint32_t)avail > target_buffer) { uint32_t skip = avail - target_buffer; @@ -63,19 +63,28 @@ static void rtp_audio_process_playback(void *data) /* when not using direct timestamp and clocks are not * in sync, try to adjust our playback rate to keep the * requested target_buffer bytes in the ringbuffer */ - error = (float)target_buffer - (float)avail; - error = SPA_CLAMP(error, -impl->max_error, impl->max_error); + double in_flight = 0; + struct spa_io_position *pos = impl->io_position; + + if (SPA_LIKELY(pos && impl->last_recv_timestamp)) { + /* Account for samples that might be in flight but not yet received, and possibly + * samples that were received _after_ the process() tick and therefore should not + * yet be accounted for */ + int64_t in_flight_ns = pos->clock.nsec - impl->last_recv_timestamp; + /* Use the best relative rate we know */ + double relative_rate = impl->io_rate_match ? impl->io_rate_match->rate : pos->clock.rate_diff; + in_flight = (double)(in_flight_ns * impl->rate) * relative_rate / SPA_NSEC_PER_SEC; + } + + error = (double)target_buffer - (double)avail - in_flight; + error = SPA_CLAMPD(error, -impl->max_error, impl->max_error); - corr = (float)spa_dll_update(&impl->dll, error); + corr = spa_dll_update(&impl->dll, error); pw_log_trace("avail:%u target:%u error:%f corr:%f", avail, target_buffer, error, corr); - if (impl->io_rate_match) { - SPA_FLAG_SET(impl->io_rate_match->flags, - SPA_IO_RATE_MATCH_FLAG_ACTIVE); - impl->io_rate_match->rate = 1.0f / corr; - } + pw_stream_set_rate(impl->stream, 1.0 / corr); } spa_ringbuffer_read_data(&impl->ring, impl->buffer, @@ -131,6 +140,7 @@ static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len) timestamp = ntohl(hdr->timestamp) - impl->ts_offset; impl->receiving = true; + impl->last_recv_timestamp = pw_stream_get_nsec(impl->stream); plen = len - hlen; samples = plen / stride; @@ -187,8 +197,12 @@ invalid_len: pw_log_warn("invalid RTP length"); return -EINVAL; unexpected_ssrc: - pw_log_warn("unexpected SSRC (expected %u != %u)", - impl->ssrc, hdr->ssrc); + if (!impl->fixed_ssrc) { + /* We didn't have a configured SSRC, and there's more than one SSRC on + * this address/port pair */ + pw_log_warn("unexpected SSRC (expected %u != %u)", impl->ssrc, + hdr->ssrc); + } return -EINVAL; } @@ -214,7 +228,7 @@ set_iovec(struct spa_ringbuffer *rbuf, void *buffer, uint32_t size, iov[1].iov_base = buffer; } -static void rtp_audio_flush_packets(struct impl *impl, uint32_t num_packets) +static void rtp_audio_flush_packets(struct impl *impl, uint32_t num_packets, uint64_t set_timestamp) { int32_t avail, tosend; uint32_t stride, timestamp; @@ -250,7 +264,7 @@ static void rtp_audio_flush_packets(struct impl *impl, uint32_t num_packets) else header.m = 0; header.sequence_number = htons(impl->seq); - header.timestamp = htonl(impl->ts_offset + timestamp); + header.timestamp = htonl(impl->ts_offset + (set_timestamp ? set_timestamp : timestamp)); set_iovec(&impl->ring, impl->buffer, BUFFER_SIZE, @@ -289,7 +303,7 @@ static void rtp_audio_flush_timeout(struct impl *impl, uint64_t expirations) { if (expirations > 1) pw_log_warn("missing timeout %"PRIu64, expirations); - rtp_audio_flush_packets(impl, expirations); + rtp_audio_flush_packets(impl, expirations, 0); } static void rtp_audio_process_capture(void *data) @@ -303,6 +317,11 @@ static void rtp_audio_process_capture(void *data) struct spa_io_position *pos; uint64_t next_nsec, quantum; + if (impl->separate_sender) { + /* apply the DLL rate */ + pw_stream_set_rate(impl->stream, impl->ptp_corr); + } + if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) { pw_log_info("Out of stream buffers: %m"); return; @@ -322,6 +341,14 @@ static void rtp_audio_process_capture(void *data) timestamp = pos->clock.position * impl->rate / rate; next_nsec = pos->clock.next_nsec; quantum = (uint64_t)(pos->clock.duration * SPA_NSEC_PER_SEC / (rate * pos->clock.rate_diff)); + + if (impl->separate_sender) { + /* the sender process() function uses this for managing the DLL */ + impl->sink_nsec = pos->clock.nsec; + impl->sink_next_nsec = pos->clock.next_nsec; + impl->sink_resamp_delay = impl->io_rate_match->delay; + impl->sink_quantum = (uint64_t)(pos->clock.duration * SPA_NSEC_PER_SEC / rate); + } } else { timestamp = expected_timestamp; next_nsec = 0; @@ -336,17 +363,26 @@ static void rtp_audio_process_capture(void *data) impl->have_sync = true; expected_timestamp = timestamp; filled = 0; + + if (impl->separate_sender) { + /* the sender should know that the sync state has changed, and that it should + * refill the buffer */ + impl->refilling = true; + } } else { - if (SPA_ABS((int32_t)expected_timestamp - (int32_t)timestamp) > 32) { + if (SPA_ABS((int)expected_timestamp - (int)timestamp) > (int)quantum) { pw_log_warn("expected %u != timestamp %u", expected_timestamp, timestamp); impl->have_sync = false; - } else if (filled + wanted > (int32_t)(BUFFER_SIZE / stride)) { - pw_log_warn("overrun %u + %u > %u", filled, wanted, BUFFER_SIZE / stride); + } else if (filled + wanted > (int32_t)SPA_MIN(impl->target_buffer * 8, BUFFER_SIZE / stride)) { + pw_log_warn("overrun %u + %u > %u/%u", filled, wanted, + impl->target_buffer * 8, BUFFER_SIZE / stride); impl->have_sync = false; filled = 0; } } + pw_log_trace("writing %u samples at %u", wanted, expected_timestamp); + spa_ringbuffer_write_data(&impl->ring, impl->buffer, BUFFER_SIZE, @@ -357,12 +393,17 @@ static void rtp_audio_process_capture(void *data) pw_stream_queue_buffer(impl->stream, buf); + if (impl->separate_sender) { + /* sending will happen in a separate process() */ + return; + } + pending = filled / impl->psamples; num_queued = (filled + wanted) / impl->psamples; if (num_queued > 0) { /* flush all previous packets plus new one right away */ - rtp_audio_flush_packets(impl, pending + 1); + rtp_audio_flush_packets(impl, pending + 1, 0); num_queued -= SPA_MIN(num_queued, pending + 1); if (num_queued > 0) { @@ -375,13 +416,210 @@ static void rtp_audio_process_capture(void *data) } } -static int rtp_audio_init(struct impl *impl, enum spa_direction direction) +static void ptp_sender_destroy(void *d) +{ + struct impl *impl = d; + spa_hook_remove(&impl->ptp_sender_listener); + impl->ptp_sender = NULL; +} + +static void ptp_sender_process(void *d, struct spa_io_position *position) +{ + struct impl *impl = d; + uint64_t nsec, next_nsec, quantum, quantum_nsec; + uint32_t ptp_timestamp, rtp_timestamp, read_idx; + uint32_t rate; + uint32_t filled; + double error, in_flight, delay; + + nsec = position->clock.nsec; + next_nsec = position->clock.next_nsec; + + /* the ringbuffer indices are in sink timetamp domain */ + filled = spa_ringbuffer_get_read_index(&impl->ring, &read_idx); + + if (SPA_LIKELY(position)) { + rate = position->clock.rate.denom; + quantum = position->clock.duration; + quantum_nsec = (uint64_t)(quantum * SPA_NSEC_PER_SEC / rate); + /* PTP time tells us what time it is */ + ptp_timestamp = position->clock.position * impl->rate / rate; + /* RTP time is based on when we sent the first packet after the last sync */ + rtp_timestamp = impl->rtp_base_ts + read_idx; + } else { + pw_log_warn("No clock information, skipping"); + return; + } + + pw_log_trace("sink nsec:%"PRIu64", sink next_nsec:%"PRIu64", ptp nsec:%"PRIu64", ptp next_sec:%"PRIu64, + impl->sink_nsec, impl->sink_next_nsec, nsec, next_nsec); + + /* If send is lagging by more than 2 or more quanta, reset */ + if (!impl->refilling && impl->rtp_last_ts && + SPA_ABS((int32_t)ptp_timestamp - (int32_t)impl->rtp_last_ts) >= (int32_t)(2 * quantum)) { + pw_log_warn("expected %u - timestamp %u = %d >= 2 * %"PRIu64" quantum", rtp_timestamp, impl->rtp_last_ts, + (int)ptp_timestamp - (int)impl->rtp_last_ts, quantum); + goto resync; + } + + if (!impl->have_sync) { + pw_log_trace("Waiting for sync"); + return; + } + + in_flight = (double)impl->sink_quantum * impl->rate / SPA_NSEC_PER_SEC * + (double)(nsec - impl->sink_nsec) / (impl->sink_next_nsec - impl->sink_nsec); + delay = filled + in_flight + impl->sink_resamp_delay; + + /* Make sure the PTP node wake up times are within the bounds of sink + * node wake up times (with a little bit of tolerance). */ + if (SPA_LIKELY(nsec > impl->sink_nsec - quantum_nsec && + nsec < impl->sink_next_nsec + quantum_nsec)) { + /* Start adjusting if we're at/past the target delay. We requested ~1/2 the buffer + * size as the sink latency, so doing so ensures that we have two sink quanta of + * data, making the chance of and underrun low even for small buffer values */ + if (impl->refilling && (double)impl->target_buffer - delay <= 0) { + impl->refilling = false; + /* Store the offset for the PTP time at which we start sending */ + impl->rtp_base_ts = ptp_timestamp - read_idx; + rtp_timestamp = impl->rtp_base_ts + read_idx; /* = ptp_timestamp */ + pw_log_debug("start sending. sink quantum:%"PRIu64", ptp quantum:%"PRIu64"", impl->sink_quantum, quantum_nsec); + } + + if (!impl->refilling) { + /* + * As per Controlling Adaptive Resampling paper[1], maintain + * W(t) - R(t) - delta = 0. We keep delta as target_buffer. + * + * [1] http://kokkinizita.linuxaudio.org/papers/adapt-resamp.pdf + */ + error = delay - impl->target_buffer; + error = SPA_CLAMPD(error, -impl->max_error, impl->max_error); + impl->ptp_corr = spa_dll_update(&impl->ptp_dll, error); + + pw_log_debug("filled:%u in_flight:%g delay:%g target:%u error:%f corr:%f", + filled, in_flight, delay, impl->target_buffer, error, impl->ptp_corr); + + if (filled >= impl->psamples) { + rtp_audio_flush_packets(impl, 1, rtp_timestamp); + impl->rtp_last_ts = rtp_timestamp; + } + } + } else { + pw_log_warn("PTP node wake up time out of bounds !(%"PRIu64" < %"PRIu64" < %"PRIu64")", + impl->sink_nsec, nsec, impl->sink_next_nsec); + goto resync; + } + + return; + +resync: + impl->have_sync = false; + impl->rtp_last_ts = 0; + + return; +} + +static const struct pw_filter_events ptp_sender_events = { + PW_VERSION_FILTER_EVENTS, + .destroy = ptp_sender_destroy, + .process = ptp_sender_process +}; + +static int setup_ptp_sender(struct impl *impl, struct pw_core *core, enum pw_direction direction, const char *driver_grp) +{ + const struct spa_pod *params[4]; + struct pw_properties *filter_props = NULL; + struct spa_pod_builder b; + uint32_t n_params; + uint8_t buffer[1024]; + int ret; + + if (direction != PW_DIRECTION_INPUT) + return 0; + + if (driver_grp == NULL) { + pw_log_info("AES67 driver group not specified, no separate sender configured"); + return 0; + } + + pw_log_info("AES67 driver group: %s, setting up separate sender", driver_grp); + + spa_dll_init(&impl->ptp_dll); + /* BW selected empirically, as it converges most quickly and holds reasonably well in testing */ + spa_dll_set_bw(&impl->ptp_dll, SPA_DLL_BW_MAX, impl->psamples, impl->rate); + impl->ptp_corr = 1.0; + + n_params = 0; + spa_pod_builder_init(&b, buffer, sizeof(buffer)); + + filter_props = pw_properties_new(NULL, NULL); + if (filter_props == NULL) { + int res = -errno; + pw_log_error( "can't create properties: %m"); + return res; + } + + pw_properties_set(filter_props, PW_KEY_NODE_GROUP, driver_grp); + pw_properties_setf(filter_props, PW_KEY_NODE_NAME, "%s-ptp-sender", pw_stream_get_name(impl->stream)); + pw_properties_set(filter_props, PW_KEY_NODE_ALWAYS_PROCESS, "true"); + + /* + * sess.latency.msec defines how much data is buffered before it is + * sent out on the network. This is done by setting the node.latency + * to that value, and process function will get chunks of that size. + * It is then split up into psamples chunks and send every ptime. + * + * With this separate sender mechanism we have some latency in stream + * via node.latency, and some in ringbuffer between sink and sender. + * Ideally we want to have a total latency that still corresponds to + * sess.latency.msec. We do this by using the property setting and + * splitting some of it as stream latency and some as ringbuffer + * latency. The ringbuffer latency is actually determined by how + * long we wait before setting `refilling` to false and start the + * sending. Also, see `filter_process`. + */ + pw_properties_setf(filter_props, PW_KEY_NODE_FORCE_QUANTUM, "%u", impl->psamples); + pw_properties_setf(filter_props, PW_KEY_NODE_FORCE_RATE, "%u", impl->rate); + + impl->ptp_sender = pw_filter_new(core, NULL, filter_props); + if (impl->ptp_sender == NULL) + return -errno; + + pw_filter_add_listener(impl->ptp_sender, &impl->ptp_sender_listener, + &ptp_sender_events, impl); + + n_params = 0; + params[n_params++] = spa_format_audio_raw_build(&b, + SPA_PARAM_EnumFormat, &impl->info.info.raw); + params[n_params++] = spa_format_audio_raw_build(&b, + SPA_PARAM_Format, &impl->info.info.raw); + + ret = pw_filter_connect(impl->ptp_sender, + PW_FILTER_FLAG_RT_PROCESS, + params, n_params); + if (ret == 0) { + pw_log_info("created pw_filter for separate sender"); + impl->separate_sender = true; + } else { + pw_log_error("failed to create pw_filter for separate sender"); + impl->separate_sender = false; + } + + return ret; +} + +static int rtp_audio_init(struct impl *impl, struct pw_core *core, enum spa_direction direction, const char *ptp_driver) { if (direction == SPA_DIRECTION_INPUT) impl->stream_events.process = rtp_audio_process_capture; else impl->stream_events.process = rtp_audio_process_playback; + impl->receive_rtp = rtp_audio_receive; impl->flush_timeout = rtp_audio_flush_timeout; + + setup_ptp_sender(impl, core, direction, ptp_driver); + return 0; } diff --git a/src/modules/module-rtp/midi.c b/src/modules/module-rtp/midi.c index 663be0dc..1b7f9ad6 100644 --- a/src/modules/module-rtp/midi.c +++ b/src/modules/module-rtp/midi.c @@ -66,7 +66,7 @@ static void rtp_midi_process_playback(void *data) } else { timestamp = target; } - spa_pod_builder_control(&b, target - timestamp, SPA_CONTROL_Midi); + spa_pod_builder_control(&b, target - timestamp, c->type); spa_pod_builder_bytes(&b, SPA_POD_BODY(&c->value), SPA_POD_BODY_SIZE(&c->value)); @@ -242,25 +242,34 @@ static int rtp_midi_receive_midi(struct impl *impl, uint8_t *packet, uint32_t ti while (offs < end) { uint32_t delta; int size; + uint64_t state = 0; + uint8_t *d; + size_t s; if (first && !hdr->z) delta = 0; else offs += parse_varlen(&packet[offs], end - offs, &delta); - timestamp += (uint32_t)(delta * impl->corr); - spa_pod_builder_control(&b, timestamp, SPA_CONTROL_Midi); size = get_midi_size(&packet[offs], end - offs); - if (size <= 0 || offs + size > end) { pw_log_warn("invalid size (%08x) %d (%u %u)", packet[offs], size, offs, end); break; } - spa_pod_builder_bytes(&b, &packet[offs], size); + d = &packet[offs]; + s = size; + while (s > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&d, &s, ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + break; + spa_pod_builder_control(&b, timestamp, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ump, ump_size); + } offs += size; first = false; } @@ -321,8 +330,12 @@ invalid_len: pw_log_warn("invalid RTP length"); return -EINVAL; unexpected_ssrc: - pw_log_warn("unexpected SSRC (expected %u != %u)", - impl->ssrc, hdr->ssrc); + if (!impl->fixed_ssrc) { + /* We didn't have a configured SSRC, and there's more than one SSRC on + * this address/port pair */ + pw_log_warn("unexpected SSRC (expected %u != %u)", + impl->ssrc, hdr->ssrc); + } return -EINVAL; } @@ -374,14 +387,17 @@ static void rtp_midi_flush_packets(struct impl *impl, max_size = impl->payload_size - sizeof(midi_header); SPA_POD_SEQUENCE_FOREACH(sequence, c) { - void *ev; - uint32_t size, delta, offset; + uint32_t delta, offset; + uint8_t event[16]; + size_t size; - if (c->type != SPA_CONTROL_Midi) + if (c->type != SPA_CONTROL_UMP) continue; - ev = SPA_POD_BODY(&c->value), - size = SPA_POD_BODY_SIZE(&c->value); + size = spa_ump_to_midi(SPA_POD_BODY(&c->value), + SPA_POD_BODY_SIZE(&c->value), event, sizeof(event)); + if (size <= 0) + continue; offset = c->offset * impl->rate / rate; @@ -415,12 +431,12 @@ static void rtp_midi_flush_packets(struct impl *impl, header.sequence_number = htons(impl->seq); header.timestamp = htonl(impl->ts_offset + timestamp + base); - memcpy(&impl->buffer[len], ev, size); + memcpy(&impl->buffer[len], event, size); len += size; } else { delta = offset - prev_offset; prev_offset = offset; - len += write_event(&impl->buffer[len], delta, ev, size); + len += write_event(&impl->buffer[len], delta, event, size); } } if (len > 0) { diff --git a/src/modules/module-rtp/opus.c b/src/modules/module-rtp/opus.c index 2a5eeaf3..83857c80 100644 --- a/src/modules/module-rtp/opus.c +++ b/src/modules/module-rtp/opus.c @@ -49,7 +49,7 @@ static void rtp_opus_process_playback(void *data) pw_log(level, "underrun %d/%u < %u", avail, target_buffer, wanted); } else { - float error, corr; + double error, corr; if (impl->first) { if ((uint32_t)avail > target_buffer) { uint32_t skip = avail - target_buffer; @@ -68,19 +68,15 @@ static void rtp_opus_process_playback(void *data) /* when not using direct timestamp and clocks are not * in sync, try to adjust our playback rate to keep the * requested target_buffer bytes in the ringbuffer */ - error = (float)target_buffer - (float)avail; - error = SPA_CLAMP(error, -impl->max_error, impl->max_error); + error = (double)target_buffer - (double)avail; + error = SPA_CLAMPD(error, -impl->max_error, impl->max_error); - corr = (float)spa_dll_update(&impl->dll, error); + corr = spa_dll_update(&impl->dll, error); pw_log_trace("avail:%u target:%u error:%f corr:%f", avail, target_buffer, error, corr); - if (impl->io_rate_match) { - SPA_FLAG_SET(impl->io_rate_match->flags, - SPA_IO_RATE_MATCH_FLAG_ACTIVE); - impl->io_rate_match->rate = 1.0f / corr; - } + pw_stream_set_rate(impl->stream, 1.0 / corr); } spa_ringbuffer_read_data(&impl->ring, impl->buffer, @@ -202,8 +198,12 @@ invalid_len: pw_log_warn("invalid RTP length"); return -EINVAL; unexpected_ssrc: - pw_log_warn("unexpected SSRC (expected %u != %u)", - impl->ssrc, hdr->ssrc); + if (!impl->fixed_ssrc) { + /* We didn't have a configured SSRC, and there's more than one SSRC on + * this address/port pair */ + pw_log_warn("unexpected SSRC (expected %u != %u)", + impl->ssrc, hdr->ssrc); + } return -EINVAL; } diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c index 29e55ea9..f4d3fa3b 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -10,7 +10,9 @@ #include <spa/utils/ringbuffer.h> #include <spa/utils/dll.h> #include <spa/param/audio/format-utils.h> +#include <spa/param/audio/raw-json.h> #include <spa/control/control.h> +#include <spa/control/ump-utils.h> #include <spa/debug/types.h> #include <spa/debug/mem.h> #include <spa/debug/log.h> @@ -62,6 +64,7 @@ struct impl { uint8_t payload; uint32_t ssrc; uint16_t seq; + unsigned fixed_ssrc:1; unsigned have_ssrc:1; unsigned ignore_ssrc:1; unsigned have_seq:1; @@ -69,17 +72,19 @@ struct impl { uint32_t ts_offset; uint32_t psamples; uint32_t mtu; + uint32_t header_size; uint32_t payload_size; struct spa_ringbuffer ring; uint8_t buffer[BUFFER_SIZE]; + uint64_t last_recv_timestamp; struct spa_io_rate_match *io_rate_match; struct spa_io_position *io_position; struct spa_dll dll; double corr; uint32_t target_buffer; - float max_error; + double max_error; float last_timestamp; float last_time; @@ -98,6 +103,27 @@ struct impl { int (*receive_rtp)(struct impl *impl, uint8_t *buffer, ssize_t len); void (*flush_timeout)(struct impl *impl, uint64_t expirations); + + /* + * pw_filter where the filter would be driven at the PTP clock + * rate with RTP sink being driven at the sink driver clock rate + * or some ALSA clock rate. + */ + struct pw_filter *ptp_sender; + struct spa_hook ptp_sender_listener; + struct spa_dll ptp_dll; + double ptp_corr; + bool separate_sender; + bool refilling; + + /* Track some variables we need from the sink driver */ + uint64_t sink_next_nsec; + uint64_t sink_nsec; + uint64_t sink_resamp_delay; + uint64_t sink_quantum; + /* And some bookkeping for the sender processing */ + uint64_t rtp_base_ts; + uint32_t rtp_last_ts; }; static int do_emit_state_changed(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) @@ -161,7 +187,18 @@ static int stream_start(struct impl *impl) rtp_stream_emit_state_changed(impl, true, NULL); + if (impl->separate_sender) { + struct spa_dict_item items[1]; + items[0] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_ALWAYS_PROCESS, "true"); + + pw_filter_set_active(impl->ptp_sender, true); + pw_filter_update_properties(impl->ptp_sender, NULL, &SPA_DICT_INIT(items, 1)); + + pw_log_info("activated pw_filter for separate sender"); + } + impl->started = true; + return 0; } @@ -174,6 +211,16 @@ static int stream_stop(struct impl *impl) if (!impl->timer_running) rtp_stream_emit_state_changed(impl, false, NULL); + if (impl->separate_sender) { + struct spa_dict_item items[1]; + items[0] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_ALWAYS_PROCESS, "false"); + + pw_filter_update_properties(impl->ptp_sender, NULL, &SPA_DICT_INIT(items, 1)); + + pw_log_info("deactivating pw_filter for separate sender"); + pw_filter_set_active(impl->ptp_sender, false); + } + impl->started = false; return 0; } @@ -227,61 +274,18 @@ static const struct format_info *find_audio_format_info(const struct spa_audio_i return NULL; } -static inline uint32_t 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 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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *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)); + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_FORMAT, + SPA_KEY_AUDIO_RATE, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } static uint32_t msec_to_samples(struct impl *impl, float msec) @@ -304,7 +308,7 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, const struct rtp_stream_events *events, void *data) { struct impl *impl; - const char *str; + const char *str, *aes67_driver; char tmp[64]; uint8_t buffer[1024]; struct spa_pod_builder b; @@ -386,7 +390,7 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, res = -EINVAL; goto out; } - pw_properties_set(props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); + pw_properties_set(props, PW_KEY_FORMAT_DSP, "32 bit raw UMP"); impl->stride = impl->format_info->size; impl->rate = pw_properties_get_uint32(props, "midi.rate", 10000); if (impl->rate == 0) @@ -433,18 +437,21 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, impl->ssrc = pw_properties_get_uint32(props, "rtp.sender-ssrc", pw_rand32()); impl->ts_offset = pw_properties_get_uint32(props, "rtp.sender-ts-offset", pw_rand32()); } else { - impl->have_ssrc = pw_properties_fetch_uint32(props, "rtp.receiver-ssrc", &impl->ssrc); + impl->have_ssrc = impl->fixed_ssrc = pw_properties_fetch_uint32(props, "rtp.receiver-ssrc", &impl->ssrc); if (pw_properties_fetch_uint32(props, "rtp.receiver-ts-offset", &impl->ts_offset) < 0) impl->direct_timestamp = false; } impl->payload = pw_properties_get_uint32(props, "rtp.payload", impl->payload); impl->mtu = pw_properties_get_uint32(props, "net.mtu", DEFAULT_MTU); - if (impl->mtu <= PACKET_HEADER_SIZE) { + impl->header_size = pw_properties_get_uint32(props, "net.header", IP4_HEADER_SIZE + UDP_HEADER_SIZE); + impl->header_size += RTP_HEADER_SIZE; + + if (impl->mtu <= impl->header_size) { pw_log_error("invalid MTU %d, using %d", impl->mtu, DEFAULT_MTU); impl->mtu = DEFAULT_MTU; } - impl->payload_size = impl->mtu - PACKET_HEADER_SIZE; + impl->payload_size = impl->mtu - impl->header_size; impl->seq = pw_rand32(); @@ -518,14 +525,21 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, if (fmodf(impl->target_buffer, impl->psamples) != 0) { pw_log_warn("sess.latency.msec %f should be an integer multiple of rtp.ptime %f", latency_msec, ptime); - impl->target_buffer = (uint32_t)((impl->target_buffer / ptime) * impl->psamples); + impl->target_buffer = SPA_ROUND_DOWN(impl->target_buffer, impl->psamples); } + aes67_driver = pw_properties_get(props, "aes67.driver-group"); + pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", impl->rate); - if (direction == PW_DIRECTION_INPUT) { + if (direction == PW_DIRECTION_INPUT && !aes67_driver) { + /* While sending, we accept latency-sized buffers, and break it + * up and send in ptime intervals using a timer */ pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%d/%d", impl->target_buffer, impl->rate); } else { + /* For receive, and with split sending, we break up the latency + * as half being in stream latency, and the rest in our own + * ringbuffer latency */ pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%d/%d", impl->target_buffer / 2, impl->rate); } @@ -534,6 +548,7 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, pw_properties_setf(props, "rtp.media", "%s", impl->format_info->media_type); pw_properties_setf(props, "rtp.mime", "%s", impl->format_info->mime); pw_properties_setf(props, "rtp.payload", "%u", impl->payload); + pw_properties_setf(props, "rtp.ssrc", "%u", impl->ssrc); pw_properties_setf(props, "rtp.rate", "%u", impl->rate); if (impl->info.info.raw.channels > 0) pw_properties_setf(props, "rtp.channels", "%u", impl->info.info.raw.channels); @@ -564,7 +579,7 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core, params[n_params++] = spa_format_audio_build(&b, SPA_PARAM_EnumFormat, &impl->stream_info); flags |= PW_STREAM_FLAG_AUTOCONNECT; - rtp_audio_init(impl, direction); + rtp_audio_init(impl, core, direction, aes67_driver); break; case SPA_MEDIA_SUBTYPE_control: params[n_params++] = spa_pod_builder_add_object(&b, @@ -685,6 +700,12 @@ enum pw_stream_state rtp_stream_get_state(struct rtp_stream *s, const char **err return pw_stream_get_state(impl->stream, error); } +int rtp_stream_set_active(struct rtp_stream *s, bool active) +{ + struct impl *impl = (struct impl*)s; + + return pw_stream_set_active(impl->stream, active); +} int rtp_stream_set_param(struct rtp_stream *s, uint32_t id, const struct spa_pod *param) { diff --git a/src/modules/module-rtp/stream.h b/src/modules/module-rtp/stream.h index c14afcf5..83cf0da3 100644 --- a/src/modules/module-rtp/stream.h +++ b/src/modules/module-rtp/stream.h @@ -19,8 +19,11 @@ struct rtp_stream; #define ERROR_MSEC 2.0f #define DEFAULT_SESS_LATENCY 100.0f -/* 28 bytes IP/UDP, 12 bytes RTP header */ -#define PACKET_HEADER_SIZE (12+28) +#define IP4_HEADER_SIZE 20 +#define IP6_HEADER_SIZE 40 +#define UDP_HEADER_SIZE 8 +/* 12 bytes RTP header */ +#define RTP_HEADER_SIZE 12 #define DEFAULT_MTU 1280 #define DEFAULT_MIN_PTIME 2.0f @@ -59,6 +62,7 @@ size_t rtp_stream_get_mtu(struct rtp_stream *s); void rtp_stream_set_first(struct rtp_stream *s); +int rtp_stream_set_active(struct rtp_stream *s, bool active); void rtp_stream_set_error(struct rtp_stream *s, int res, const char *error); enum pw_stream_state rtp_stream_get_state(struct rtp_stream *s, const char **error); diff --git a/src/modules/module-snapcast-discover.c b/src/modules/module-snapcast-discover.c index 5bb2f863..a1140916 100644 --- a/src/modules/module-snapcast-discover.c +++ b/src/modules/module-snapcast-discover.c @@ -23,6 +23,7 @@ #include <spa/utils/string.h> #include <spa/utils/json.h> #include <spa/param/audio/format.h> +#include <spa/param/audio/raw-json.h> #include <spa/debug/types.h> #include <pipewire/impl.h> @@ -65,6 +66,26 @@ * - `stream.rules` = <rules>: match rules, use create-stream actions. See * \ref page_module_protocol_simple for module properties. * + * ### stream.rules matches + * + * - `snapcast.ip`: the IP address of the snapcast server + * - `snapcast.port`: the port of the snapcast server + * - `snapcast.ifindex`: the interface index where the snapcast announcement + * was received. + * - `snapcast.ifname`: the interface name where the snapcast announcement + * was received. + * - `snapcast.name`: the name of the snapcast server + * - `snapcast.hostname`: the hostname of the snapcast server + * - `snapcast.domain`: the domain of the snapcast server + * + * ### stream.rules create-stream + * + * In addition to all the properties that can be passed to + * \ref page_module_protocol_simple, you can also set: + * + * - `snapcast.stream-name`: The name of the stream on a snapcast server. + * - `node.name`: The name of the sink that is created on the sender. + * * ## Example configuration * *\code{.unparsed} @@ -76,9 +97,9 @@ * stream.rules = [ * { matches = [ * { snapcast.ip = "~.*" + * #snapcast.port = 1000 * #snapcast.ifindex = 1 * #snapcast.ifname = eth0 - * #snapcast.port = 1000 * #snapcast.name = "" * #snapcast.hostname = "" * #snapcast.domain = "" @@ -91,11 +112,18 @@ * #audio.channels = 2 * #audio.position = [ FL FR ] * # + * # The stream name as is appears on the snapcast + * # server: * #snapcast.stream-name = "PipeWire" * # + * # The name of the sink on the sender: + * #node.name = "Snapcast Sink" + * # * #capture = true + * #server.address = [ "tcp:4711" ] * #capture.props = { * #target.object = "" + * #node.latency = 2048/48000 * #media.class = "Audio/Sink" * #} * } @@ -461,14 +489,13 @@ static int snapcast_connect(struct tunnel *t) static int add_snapcast_stream(struct impl *impl, struct tunnel *t, struct pw_properties *props, const char *servers) { - struct spa_json it[2]; + struct spa_json it[1]; char v[256]; - spa_json_init(&it[0], servers, strlen(servers)); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], servers, strlen(servers)); + if (spa_json_begin_array_relax(&it[0], servers, strlen(servers)) <= 0) + return -EINVAL; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0) { + while (spa_json_get_string(&it[0], v, sizeof(v)) > 0) { t->server_address = strdup(v); snapcast_connect(t); return 0; @@ -476,67 +503,22 @@ static int add_snapcast_stream(struct impl *impl, struct tunnel *t, return -ENOENT; } -static inline uint32_t 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 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 void parse_position(struct spa_audio_info_raw *info, 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } -} - static void parse_audio_info(struct pw_properties *props, struct spa_audio_info_raw *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)); - if (info->format == 0) { - str = DEFAULT_FORMAT; - info->format = format_from_name(str, strlen(str)); - } - pw_properties_set(props, PW_KEY_AUDIO_FORMAT, str); - - info->rate = pw_properties_get_uint32(props, PW_KEY_AUDIO_RATE, info->rate); - if (info->rate == 0) - info->rate = DEFAULT_RATE; + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_FORMAT, + SPA_KEY_AUDIO_RATE, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); + + pw_properties_set(props, PW_KEY_AUDIO_FORMAT, + spa_type_audio_format_to_short_name(info->format)); pw_properties_setf(props, PW_KEY_AUDIO_RATE, "%d", info->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)); pw_properties_setf(props, PW_KEY_AUDIO_CHANNELS, "%d", info->channels); } diff --git a/src/modules/spa/module-device-factory.c b/src/modules/module-spa-device-factory.c similarity index 97% rename from src/modules/spa/module-device-factory.c rename to src/modules/module-spa-device-factory.c index 98acc4d6..eb8d2436 100644 --- a/src/modules/spa/module-device-factory.c +++ b/src/modules/module-spa-device-factory.c @@ -13,8 +13,12 @@ #include "pipewire/impl.h" -#include "spa-device.h" +#include "spa/spa-device.h" +/** \page page_module_spa_device_factory SPA Device factory + * + * Provide a factory to create SPA devices. + */ #define NAME "spa-device-factory" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); diff --git a/src/modules/spa/module-device.c b/src/modules/module-spa-device.c similarity index 95% rename from src/modules/spa/module-device.c rename to src/modules/module-spa-device.c index a5e09ecf..2d62ffb5 100644 --- a/src/modules/spa/module-device.c +++ b/src/modules/module-spa-device.c @@ -10,8 +10,12 @@ #include <pipewire/impl.h> -#include "spa-device.h" +#include "spa/spa-device.h" +/** \page page_module_spa_device SPA Device + * + * Load and manage an SPA device. + */ #define NAME "spa-device" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); diff --git a/src/modules/spa/module-node-factory.c b/src/modules/module-spa-node-factory.c similarity index 98% rename from src/modules/spa/module-node-factory.c rename to src/modules/module-spa-node-factory.c index dbbc6d0b..0f9a9702 100644 --- a/src/modules/spa/module-node-factory.c +++ b/src/modules/module-spa-node-factory.c @@ -13,8 +13,12 @@ #include "pipewire/impl.h" -#include "spa-node.h" +#include "spa/spa-node.h" +/** \page page_module_spa_node_factory SPA Node factory + * + * Provide a factory to create SPA nodes. + */ #define NAME "spa-node-factory" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); diff --git a/src/modules/spa/module-node.c b/src/modules/module-spa-node.c similarity index 95% rename from src/modules/spa/module-node.c rename to src/modules/module-spa-node.c index 9844607b..75009bc1 100644 --- a/src/modules/spa/module-node.c +++ b/src/modules/module-spa-node.c @@ -12,8 +12,12 @@ #include <pipewire/impl.h> -#include "spa-node.h" +#include "spa/spa-node.h" +/** \page page_module_spa_node SPA Node + * + * Load and manage an SPA node. + */ #define NAME "spa-node" PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); diff --git a/src/modules/module-vban-recv.c b/src/modules/module-vban-recv.c index 52cf363a..90205796 100644 --- a/src/modules/module-vban-recv.c +++ b/src/modules/module-vban-recv.c @@ -30,6 +30,7 @@ #include <pipewire/impl.h> #include <module-vban/stream.h> +#include <module-vban/vban.h> #include "network-utils.h" /** \page page_module_vban_recv VBAN receiver @@ -37,6 +38,9 @@ * The `vban-recv` module creates a PipeWire source that receives audio * and midi [VBAN](https://vb-audio.com) packets. * + * The receive will listen on a specific port (6980) and create a stream for each + * VBAN stream received on the port. + * * ## Module Name * * `libpipewire-module-vban-recv` @@ -46,22 +50,31 @@ * Options specific to the behavior of this module * * - `local.ifname = <str>`: interface name to use - * - `source.ip = <str>`: the source ip address, default 127.0.0.1 - * - `source.port = <int>`: the source port + * - `source.ip = <str>`: the source ip address to listen on, default 127.0.0.1 + * - `source.port = <int>`: the source port to listen on, default 6980 * - `node.always-process = <bool>`: true to receive even when not running * - `sess.latency.msec = <str>`: target network latency in milliseconds, default 100 - * - `sess.ignore-ssrc = <bool>`: ignore SSRC, default false - * - `sess.media = <string>`: the media type audio|midi|opus, default audio - * - `stream.props = {}`: properties to be passed to the stream + * - `stream.props = {}`: properties to be passed to all the stream + * - `stream.rules` = <rules>: match rules, use create-stream actions. + * + * ### stream.rules matches + * + * - `vban.ip`: the IP address of the VBAN sender + * - `vban.port`: the port of the VBAN sender + * - `sess.name`: the name of the VBAN stream + * + * ### stream.rules create-stream + * + * In addition to all the properties that can be passed to a stream, + * you can also set: + * + * - `sess.latency.msec = <str>`: target network latency in milliseconds, default 100 * * ## General options * * 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_MEDIA_NAME * - \ref PW_KEY_MEDIA_CLASS @@ -82,17 +95,36 @@ * #source.ip = 127.0.0.1 * #source.port = 6980 * sess.latency.msec = 100 - * #sess.ignore-ssrc = false * #node.always-process = false - * #sess.media = "audio" - * #audio.format = "S16LE" - * #audio.rate = 44100 - * #audio.channels = 2 * #audio.position = [ FL FR ] * stream.props = { * #media.class = "Audio/Source" - * node.name = "vban-receiver" + * #node.name = "vban-receiver" * } + * stream.rules = [ + * { matches = [ + * { sess.name = "~.*" + * #sess.media = "audio" | "midi" + * #vban.ip = "" + * #vban.port = 1000 + * #audio.channels = 2 + * #audio.format = "U8" | "S16LE" | "S24LE" | "S32LE" | "F32LE" | "F64LE" + * #audio.rate = 44100 + * } + * ] + * actions = { + * create-stream = { + * stream.props = { + * #sess.latency.msec = 100 + * #target.object = "" + * #audio.position = [ FL FR ] + * #media.class = "Audio/Source" + * #node.name = "vban-receiver" + * } + * } + * } + * } + * ] * } * } * ] @@ -110,16 +142,16 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define DEFAULT_SOURCE_IP "127.0.0.1" #define DEFAULT_SOURCE_PORT 6980 +#define DEFAULT_CREATE_RULES \ + "[ { matches = [ { sess.name = \"~.*\" } ] actions = { create-stream = { } } } ] " + #define USAGE "( local.ifname=<local interface name to use> ) " \ "( source.ip=<source IP address, default:"DEFAULT_SOURCE_IP"> ) " \ "( source.port=<int, source port, default:"SPA_STRINGIFY(DEFAULT_SOURCE_PORT)"> " \ "( sess.latency.msec=<target network latency, default "SPA_STRINGIFY(DEFAULT_SESS_LATENCY)"> ) "\ - "( sess.media=<string, the media type audio|midi, default audio> ) " \ - "( audio.format=<format, default:"DEFAULT_FORMAT"> ) " \ - "( 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= { key=value ... } ) " + "( stream.props= { key=value ... } ) " \ + "( stream.rules=<rules>, use create-stream actions )" static const struct spa_dict_item module_info[] = { { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" }, @@ -146,47 +178,31 @@ struct impl { bool always_process; uint32_t cleanup_interval; - struct spa_source *timer; - struct pw_properties *stream_props; - struct vban_stream *stream; + + struct spa_source *timer; uint16_t src_port; struct sockaddr_storage src_addr; socklen_t src_len; struct spa_source *source; - unsigned receiving:1; + struct spa_list streams; }; -static void -on_vban_io(void *data, int fd, uint32_t mask) -{ - struct impl *impl = data; - ssize_t len; - uint8_t buffer[2048]; - - if (mask & SPA_IO_IN) { - if ((len = recv(fd, buffer, sizeof(buffer), 0)) < 0) - goto receive_error; - - if (len < 12) - goto short_packet; +struct stream { + struct spa_list link; + struct impl *impl; - if (SPA_LIKELY(impl->stream)) - vban_stream_receive_packet(impl->stream, buffer, len); + struct vban_header header; + struct sockaddr_storage sa; + socklen_t salen; - impl->receiving = true; - } - return; + struct vban_stream *stream; -receive_error: - pw_log_warn("recv error: %m"); - return; -short_packet: - pw_log_warn("short packet received"); - return; -} + bool active; + bool receiving; +}; static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname) { @@ -266,7 +282,242 @@ error: return res; } -static int stream_start(struct impl *impl) +static void stream_destroy(void *d) +{ + struct stream *s = d; + s->stream = NULL; +} + +static void stream_state_changed(void *data, bool started, const char *error) +{ + struct stream *s = data; + struct impl *impl = s->impl; + + if (error) { + pw_log_error("stream error: %s", error); + pw_impl_module_schedule_destroy(impl->module); + } else if (started) { + s->active = true; + } else { + s->active = false; + } +} + +static const struct vban_stream_events stream_events = { + VBAN_VERSION_STREAM_EVENTS, + .destroy = stream_destroy, + .state_changed = stream_state_changed, +}; + +static int create_stream(struct stream *s, struct pw_properties *props) +{ + struct impl *impl = s->impl; + const char *sess_name, *ip, *port; + + ip = pw_properties_get(props, "vban.ip"); + port = pw_properties_get(props, "vban.port"); + sess_name = pw_properties_get(props, "sess.name"); + + if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) + pw_properties_setf(props, PW_KEY_NODE_NAME, "vban_session.%s.%s.%s", sess_name, ip, port); + if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) + pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "%s from %s", sess_name, ip); + if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) + pw_properties_setf(props, PW_KEY_MEDIA_NAME, "VBAN %s from %s", + sess_name, ip); + + s->stream = vban_stream_new(impl->core, + PW_DIRECTION_OUTPUT, spa_steal_ptr(props), + &stream_events, s); + if (s->stream == NULL) { + pw_log_error("can't create stream: %m"); + return -errno; + } + return 0; +} + +struct match_info { + struct stream *stream; + const struct pw_properties *props; + bool matched; +}; + +static int rule_matched(void *data, const char *location, const char *action, + const char *str, size_t len) +{ + struct match_info *i = data; + int res = 0; + + i->matched = true; + if (spa_streq(action, "create-stream")) { + struct pw_properties *p = pw_properties_copy(i->props); + pw_properties_update_string(p, str, len); + create_stream(i->stream, p); + } + return res; +} + + +static int +do_setup_stream(struct spa_loop *loop, + bool async, uint32_t seq, const void *data, size_t size, void *user_data) +{ + struct stream *s = user_data; + struct impl *impl = s->impl; + struct pw_properties *props; + int res; + const char *str; + char addr[128]; + uint16_t port = 0; + + props = pw_properties_copy(impl->stream_props); + + pw_net_get_ip(&s->sa, addr, sizeof(addr), NULL, &port); + + pw_properties_setf(props, "sess.name", "%s", s->header.stream_name); + pw_properties_setf(props, "vban.ip", "%s", addr); + pw_properties_setf(props, "vban.port", "%u", port); + + if ((s->header.format_SR & 0xE0) == VBAN_PROTOCOL_AUDIO && + (s->header.format_bit & 0xF0) == VBAN_CODEC_PCM) { + const char *fmt; + pw_properties_set(props, "sess.media", "audio"); + pw_properties_setf(props, PW_KEY_AUDIO_CHANNELS, "%u",s->header.format_nbc + 1); + pw_properties_setf(props, PW_KEY_AUDIO_RATE, "%u", vban_SR[s->header.format_SR & 0x1f]); + switch(s->header.format_bit & 0x07) { + case VBAN_DATATYPE_BYTE8: + fmt = "U8"; + break; + case VBAN_DATATYPE_INT16: + fmt = "S16LE"; + break; + case VBAN_DATATYPE_INT24: + fmt = "S24LE"; + break; + case VBAN_DATATYPE_INT32: + fmt = "S32LE"; + break; + case VBAN_DATATYPE_FLOAT32: + fmt = "F32LE"; + break; + case VBAN_DATATYPE_FLOAT64: + fmt = "F64LE"; + break; + break; + case VBAN_DATATYPE_12BITS: + case VBAN_DATATYPE_10BITS: + default: + pw_log_error("stream format %08x:%08x not supported", + s->header.format_SR, s->header.format_bit); + res = -ENOTSUP; + goto error; + } + pw_properties_set(props, PW_KEY_AUDIO_FORMAT, fmt); + } else if ((s->header.format_SR & 0xE0) == VBAN_PROTOCOL_SERIAL && + (s->header.format_bit & 0xF0) == VBAN_SERIAL_MIDI) { + pw_properties_set(props, "sess.media", "midi"); + } else { + pw_log_error("stream format %08x:%08x not supported", + s->header.format_SR, s->header.format_bit); + res = -ENOTSUP; + goto error; + } + + if ((str = pw_properties_get(impl->props, "stream.rules")) == NULL) + str = DEFAULT_CREATE_RULES; + if (str != NULL) { + struct match_info minfo = { + .stream = s, + .props = props, + }; + pw_conf_match_rules(str, strlen(str), NAME, &props->dict, + rule_matched, &minfo); + + if (!minfo.matched) + pw_log_info("unmatched stream found %s", str); + } + res = 0; +error: + pw_properties_free(props); + return res; +} + +static struct stream *make_stream(struct impl *impl, const struct vban_header *hdr, + struct sockaddr_storage *sa, socklen_t salen) +{ + struct stream *stream; + + stream = calloc(1, sizeof(*stream)); + if (stream == NULL) + return NULL; + + stream->impl = impl; + stream->header = *hdr; + stream->sa = *sa; + stream->salen = salen; + spa_list_append(&impl->streams, &stream->link); + + pw_loop_invoke(impl->loop, do_setup_stream, 1, NULL, 0, false, stream); + + return stream; +} + +static struct stream *find_stream(struct impl *impl, const char *name) +{ + struct stream *s; + spa_list_for_each(s, &impl->streams, link) { + if (strncmp(s->header.stream_name, name, VBAN_STREAM_NAME_SIZE) == 0) + return s; + } + return NULL; +} + +static void +on_vban_io(void *data, int fd, uint32_t mask) +{ + struct impl *impl = data; + ssize_t len; + uint8_t buffer[2048]; + + if (mask & SPA_IO_IN) { + struct vban_header *hdr; + struct stream *s; + struct sockaddr_storage sa; + socklen_t salen = sizeof(sa); + + if ((len = recvfrom(fd, buffer, sizeof(buffer), 0, + (struct sockaddr*)&sa, &salen)) < 0) + goto receive_error; + + if (len < VBAN_HEADER_SIZE) + goto short_packet; + + hdr = (struct vban_header *)buffer; + if (strncmp(hdr->vban, "VBAN", 4)) + goto invalid_version; + + s = find_stream(impl, hdr->stream_name); + if (SPA_UNLIKELY(s == NULL)) + s = make_stream(impl, hdr, &sa, salen); + if (SPA_LIKELY(s != NULL && s->active)) { + s->receiving = true; + vban_stream_receive_packet(s->stream, buffer, len); + } + } + return; + +receive_error: + pw_log_warn("recv error: %m"); + return; +short_packet: + pw_log_warn("short packet received"); + return; +invalid_version: + pw_log_warn("invalid VBAN version"); + return; +} + +static int listen_start(struct impl *impl) { int fd; @@ -291,7 +542,7 @@ static int stream_start(struct impl *impl) return 0; } -static void stream_stop(struct impl *impl) +static void listen_stop(struct impl *impl) { if (!impl->source) return; @@ -302,45 +553,29 @@ static void stream_stop(struct impl *impl) impl->source = NULL; } -static void stream_destroy(void *d) -{ - struct impl *impl = d; - impl->stream = NULL; -} -static void stream_state_changed(void *data, bool started, const char *error) +static void destroy_stream(struct stream *s) { - struct impl *impl = data; - - if (error) { - pw_log_error("stream error: %s", error); - pw_impl_module_schedule_destroy(impl->module); - } else if (started) { - if ((errno = -stream_start(impl)) < 0) - pw_log_error("failed to start VBAN stream: %m"); - } else { - if (!impl->always_process) - stream_stop(impl); - } + spa_list_remove(&s->link); + if (s->stream) + vban_stream_destroy(s->stream); + free(s); } -static const struct vban_stream_events stream_events = { - VBAN_VERSION_STREAM_EVENTS, - .destroy = stream_destroy, - .state_changed = stream_state_changed, -}; - static void on_timer_event(void *data, uint64_t expirations) { struct impl *impl = data; + struct stream *s; - if (!impl->receiving) { - pw_log_info("timeout, inactive VBAN source"); - //pw_impl_module_schedule_destroy(impl->module); - } else { - pw_log_debug("timeout, keeping active VBAN source"); + spa_list_for_each(s, &impl->streams, link) { + if (!s->receiving) { + pw_log_info("timeout, inactive VBAN source"); + //pw_impl_module_schedule_destroy(impl->module); + } else { + pw_log_debug("timeout, keeping active VBAN source"); + } + s->receiving = false; } - impl->receiving = false; } static void core_destroy(void *d) @@ -357,10 +592,12 @@ static const struct pw_proxy_events core_proxy_events = { static void impl_destroy(struct impl *impl) { - if (impl->stream) - vban_stream_destroy(impl->stream); - if (impl->source) - pw_loop_destroy_source(impl->data_loop, impl->source); + struct stream *s; + + listen_stop(impl); + + spa_list_consume(s, &impl->streams, link) + destroy_stream(s); if (impl->core && impl->do_disconnect) pw_core_disconnect(impl->core); @@ -420,7 +657,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); struct impl *impl; - const char *str, *sess_name; + const char *str; struct timespec value, interval; struct pw_properties *props, *stream_props; int res = 0; @@ -446,26 +683,14 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->context = context; impl->loop = pw_context_get_main_loop(context); impl->data_loop = pw_context_acquire_loop(context, &props->dict); - - if ((sess_name = pw_properties_get(props, "sess.name")) == NULL) - sess_name = pw_get_host_name(); + spa_list_init(&impl->streams); pw_properties_set(props, PW_KEY_NODE_LOOP_NAME, impl->data_loop->name); - if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) - pw_properties_setf(props, PW_KEY_NODE_NAME, "vban_session.%s", sess_name); - if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) - pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "%s", sess_name); - if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) - pw_properties_setf(props, PW_KEY_MEDIA_NAME, "VBAN Session with %s", - sess_name); if ((str = pw_properties_get(props, "stream.props")) != NULL) pw_properties_update_string(stream_props, str, strlen(str)); copy_props(impl, props, PW_KEY_NODE_LOOP_NAME); - copy_props(impl, props, PW_KEY_AUDIO_FORMAT); - copy_props(impl, props, PW_KEY_AUDIO_RATE); - copy_props(impl, props, PW_KEY_AUDIO_CHANNELS); copy_props(impl, props, SPA_KEY_AUDIO_POSITION); copy_props(impl, props, PW_KEY_NODE_NAME); copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); @@ -476,10 +701,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) copy_props(impl, props, PW_KEY_MEDIA_NAME); copy_props(impl, props, PW_KEY_MEDIA_CLASS); copy_props(impl, props, "net.mtu"); - copy_props(impl, props, "sess.media"); - copy_props(impl, props, "sess.name"); - copy_props(impl, props, "sess.min-ptime"); - copy_props(impl, props, "sess.max-ptime"); copy_props(impl, props, "sess.latency.msec"); str = pw_properties_get(props, "local.ifname"); @@ -538,12 +759,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) interval.tv_nsec = 0; pw_loop_update_timer(impl->loop, impl->timer, &value, &interval, false); - impl->stream = vban_stream_new(impl->core, - PW_DIRECTION_OUTPUT, pw_properties_copy(stream_props), - &stream_events, impl); - if (impl->stream == NULL) { - res = -errno; - pw_log_error("can't create stream: %m"); + if ((res = listen_start(impl)) < 0) { + pw_log_error("failed to start VBAN stream: %s", spa_strerror(res)); goto out; } diff --git a/src/modules/module-vban-send.c b/src/modules/module-vban-send.c index b0861e9c..5fc6793a 100644 --- a/src/modules/module-vban-send.c +++ b/src/modules/module-vban-send.c @@ -26,6 +26,7 @@ #include <pipewire/impl.h> #include <module-vban/stream.h> +#include <module-vban/vban.h> #include "network-utils.h" #ifndef IPTOS_DSCP @@ -354,6 +355,7 @@ SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) { struct pw_context *context = pw_impl_module_get_context(module); + uint32_t id = pw_global_get_id(pw_impl_module_get_global(module)); struct impl *impl; struct pw_properties *props = NULL, *stream_props = NULL; char addr[64]; @@ -382,7 +384,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) stream_props = pw_properties_new(NULL, NULL); if (stream_props == NULL) { res = -errno; - pw_log_error( "can't create properties: %m"); + pw_log_error("can't create properties: %m"); goto out; } impl->stream_props = stream_props; @@ -391,15 +393,22 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->context = context; impl->loop = pw_context_get_main_loop(context); + if ((sess_name = pw_properties_get(props, "sess.name")) == NULL) + pw_properties_setf(props, "sess.name", "%s-%d", + pw_get_host_name(), id); if ((sess_name = pw_properties_get(props, "sess.name")) == NULL) sess_name = pw_get_host_name(); + if (strlen(sess_name) > VBAN_STREAM_NAME_SIZE) + pw_log_warn("session name '%s' will be truncated to %d characters", + sess_name, VBAN_STREAM_NAME_SIZE); + if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) pw_properties_setf(props, PW_KEY_NODE_NAME, "vban_session.%s", sess_name); if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "%s", sess_name); if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) - pw_properties_setf(props, PW_KEY_MEDIA_NAME, "VBAN Session with %s", + pw_properties_setf(props, PW_KEY_MEDIA_NAME, "VBAN Session %s", sess_name); if ((str = pw_properties_get(props, "stream.props")) != NULL) diff --git a/src/modules/module-vban/audio.c b/src/modules/module-vban/audio.c index 4aa0fae9..09924689 100644 --- a/src/modules/module-vban/audio.c +++ b/src/modules/module-vban/audio.c @@ -30,14 +30,14 @@ static void vban_audio_process_playback(void *data) memset(d[0].data, 0, wanted * stride); if (impl->have_sync) { impl->have_sync = false; - level = SPA_LOG_LEVEL_WARN; + level = SPA_LOG_LEVEL_INFO; } else { level = SPA_LOG_LEVEL_DEBUG; } pw_log(level, "underrun %d/%u < %u", avail, target_buffer, wanted); } else { - float error, corr; + double error, corr; if (impl->first) { if ((uint32_t)avail > target_buffer) { uint32_t skip = avail - target_buffer; @@ -54,19 +54,16 @@ static void vban_audio_process_playback(void *data) } /* try to adjust our playback rate to keep the * requested target_buffer bytes in the ringbuffer */ - error = (float)target_buffer - (float)avail; - error = SPA_CLAMP(error, -impl->max_error, impl->max_error); + error = (double)target_buffer - (double)avail; + error = SPA_CLAMPD(error, -impl->max_error, impl->max_error); - corr = (float)spa_dll_update(&impl->dll, error); + corr = spa_dll_update(&impl->dll, error); pw_log_debug("avail:%u target:%u error:%f corr:%f", avail, target_buffer, error, corr); - if (impl->io_rate_match) { - SPA_FLAG_SET(impl->io_rate_match->flags, - SPA_IO_RATE_MATCH_FLAG_ACTIVE); - impl->io_rate_match->rate = 1.0f / corr; - } + pw_stream_set_rate(impl->stream, 1.0 / corr); + spa_ringbuffer_read_data(&impl->ring, impl->buffer, BUFFER_SIZE, @@ -92,12 +89,7 @@ static int vban_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len) uint32_t stride = impl->stride; int32_t filled; - if (len < VBAN_HEADER_SIZE) - goto short_packet; - hdr = (struct vban_header*)buffer; - if (strncmp(hdr->vban, "VBAN", 3)) - goto invalid_version; impl->receiving = true; @@ -155,14 +147,6 @@ static int vban_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len) spa_ringbuffer_write_update(&impl->ring, write); } return 0; - -short_packet: - pw_log_warn("short packet received"); - return -EINVAL; -invalid_version: - pw_log_warn("invalid VBAN version"); - spa_debug_log_mem(pw_log_get(), SPA_LOG_LEVEL_INFO, 0, buffer, len); - return -EPROTO; } static inline void diff --git a/src/modules/module-vban/midi.c b/src/modules/module-vban/midi.c index 7a688299..f820b683 100644 --- a/src/modules/module-vban/midi.c +++ b/src/modules/module-vban/midi.c @@ -67,7 +67,7 @@ static void vban_midi_process_playback(void *data) } else { timestamp = target; } - spa_pod_builder_control(&b, target - timestamp, SPA_CONTROL_Midi); + spa_pod_builder_control(&b, target - timestamp, c->type); spa_pod_builder_bytes(&b, SPA_POD_BODY(&c->value), SPA_POD_BODY_SIZE(&c->value)); @@ -162,19 +162,29 @@ static int vban_midi_receive_midi(struct impl *impl, uint8_t *packet, while (offs < plen) { int size; - - spa_pod_builder_control(&b, timestamp, SPA_CONTROL_Midi); + uint8_t *midi_data; + size_t midi_size; + uint64_t midi_state = 0; size = get_midi_size(&packet[offs], plen - offs); - if (size <= 0 || offs + size > plen) { pw_log_warn("invalid size (%08x) %d (%u %u)", packet[offs], size, offs, plen); break; } - spa_pod_builder_bytes(&b, &packet[offs], size); - + midi_data = &packet[offs]; + midi_size = size; + while (midi_size > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&midi_data, &midi_size, + ump, sizeof(ump), 0, &midi_state); + if (ump_size <= 0) + break; + + spa_pod_builder_control(&b, timestamp, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ump, ump_size); + } offs += size; } spa_pod_builder_pop(&b, &f[0]); @@ -191,13 +201,7 @@ static int vban_midi_receive(struct impl *impl, uint8_t *buffer, ssize_t len) ssize_t hlen; uint32_t n_frames; - if (len < VBAN_HEADER_SIZE) - goto short_packet; - hdr = (struct vban_header*)buffer; - if (strncmp(hdr->vban, "VBAN", 3)) - goto invalid_version; - hlen = VBAN_HEADER_SIZE; n_frames = hdr->n_frames; @@ -211,14 +215,6 @@ static int vban_midi_receive(struct impl *impl, uint8_t *buffer, ssize_t len) impl->receiving = true; return vban_midi_receive_midi(impl, buffer, hlen, len); - -short_packet: - pw_log_warn("short packet received"); - return -EINVAL; -invalid_version: - pw_log_warn("invalid RTP version"); - spa_debug_log_mem(pw_log_get(), SPA_LOG_LEVEL_INFO, 0, buffer, len); - return -EPROTO; } static void vban_midi_flush_packets(struct impl *impl, @@ -239,14 +235,17 @@ static void vban_midi_flush_packets(struct impl *impl, len = 0; SPA_POD_SEQUENCE_FOREACH(sequence, c) { - void *ev; - uint32_t size; + int size; + uint8_t event[16]; + + if (c->type != SPA_CONTROL_UMP) + continue; - if (c->type != SPA_CONTROL_Midi) + size = spa_ump_to_midi(SPA_POD_BODY(&c->value), + SPA_POD_BODY_SIZE(&c->value), event, sizeof(event)); + if (size <= 0) continue; - ev = SPA_POD_BODY(&c->value), - size = SPA_POD_BODY_SIZE(&c->value); if (len == 0) { /* start new packet */ header.n_frames++; @@ -258,7 +257,7 @@ static void vban_midi_flush_packets(struct impl *impl, vban_stream_emit_send_packet(impl, iov, 2); len = 0; } - memcpy(&impl->buffer[len], ev, size); + memcpy(&impl->buffer[len], event, size); len += size; } if (len > 0) { diff --git a/src/modules/module-vban/stream.c b/src/modules/module-vban/stream.c index ed32f3bd..efa5af37 100644 --- a/src/modules/module-vban/stream.c +++ b/src/modules/module-vban/stream.c @@ -10,7 +10,10 @@ #include <spa/utils/ringbuffer.h> #include <spa/utils/dll.h> #include <spa/param/audio/format-utils.h> +#include <spa/param/audio/layout.h> +#include <spa/param/audio/raw-json.h> #include <spa/control/control.h> +#include <spa/control/ump-utils.h> #include <spa/debug/types.h> #include <spa/debug/mem.h> #include <spa/debug/log.h> @@ -62,12 +65,11 @@ struct impl { struct spa_ringbuffer ring; uint8_t buffer[BUFFER_SIZE]; - struct spa_io_rate_match *io_rate_match; struct spa_io_position *io_position; struct spa_dll dll; double corr; uint32_t target_buffer; - float max_error; + double max_error; float last_timestamp; float last_time; @@ -92,22 +94,19 @@ struct format_info { }; static const struct format_info audio_format_info[] = { - { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_U8, 1, VBAN_DATATYPE_U8, }, + { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_U8, 1, VBAN_DATATYPE_BYTE8, }, { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_S16_LE, 2, VBAN_DATATYPE_INT16, }, { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_S24_LE, 3, VBAN_DATATYPE_INT24, }, { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_S32_LE, 4, VBAN_DATATYPE_INT32, }, { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_F32_LE, 4, VBAN_DATATYPE_FLOAT32, }, { SPA_MEDIA_SUBTYPE_raw, SPA_AUDIO_FORMAT_F64_LE, 8, VBAN_DATATYPE_FLOAT64, }, - { SPA_MEDIA_SUBTYPE_control, 0, 1, VBAN_SERIAL_MIDI | VBAN_DATATYPE_U8, }, + { SPA_MEDIA_SUBTYPE_control, 0, 1, VBAN_SERIAL_MIDI | VBAN_DATATYPE_BYTE8, }, }; static void stream_io_changed(void *data, uint32_t id, void *area, uint32_t size) { struct impl *impl = data; switch (id) { - case SPA_IO_RateMatch: - impl->io_rate_match = area; - break; case SPA_IO_Position: impl->io_position = area; break; @@ -186,66 +185,47 @@ static const struct format_info *find_audio_format_info(const struct spa_audio_i return NULL; } -static inline uint32_t format_from_name(const char *name, size_t len) +static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *info) { - 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; + spa_audio_info_raw_init_dict_keys(info, + &SPA_DICT_ITEMS( + SPA_DICT_ITEM(SPA_KEY_AUDIO_FORMAT, DEFAULT_FORMAT), + SPA_DICT_ITEM(SPA_KEY_AUDIO_RATE, SPA_STRINGIFY(DEFAULT_RATE)), + SPA_DICT_ITEM(SPA_KEY_AUDIO_POSITION, DEFAULT_POSITION)), + &props->dict, + SPA_KEY_AUDIO_FORMAT, + SPA_KEY_AUDIO_RATE, + SPA_KEY_AUDIO_CHANNELS, + SPA_KEY_AUDIO_POSITION, NULL); } -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 void parse_position(struct spa_audio_info_raw *info, const char *val, size_t len) +static uint32_t msec_to_samples(struct impl *impl, uint32_t msec) { - 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); - - info->channels = 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && - info->channels < SPA_AUDIO_MAX_CHANNELS) { - info->position[info->channels++] = channel_from_name(v); - } + return msec * impl->rate / 1000; } -static void parse_audio_info(const struct pw_properties *props, struct spa_audio_info_raw *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 const struct spa_audio_layout_info layouts[] = { + { SPA_AUDIO_LAYOUT_Mono }, + { SPA_AUDIO_LAYOUT_Stereo }, + { SPA_AUDIO_LAYOUT_2_1 }, + { SPA_AUDIO_LAYOUT_3_1 }, + { SPA_AUDIO_LAYOUT_5_0 }, + { SPA_AUDIO_LAYOUT_5_1 }, + { SPA_AUDIO_LAYOUT_7_0 }, + { SPA_AUDIO_LAYOUT_7_1 }, +}; -static uint32_t msec_to_samples(struct impl *impl, uint32_t msec) +static void default_layout(uint32_t channels, uint32_t *position) { - return msec * impl->rate / 1000; + SPA_FOR_EACH_ELEMENT_VAR(layouts, l) { + if (l->n_channels == channels) { + for (uint32_t i = 0; i < l->n_channels; i++) + position[i] = l->position[i]; + return; + } + } + for (uint32_t i = 0; i < channels; i++) + position[i] = SPA_AUDIO_CHANNEL_AUX0 + i; } struct vban_stream *vban_stream_new(struct pw_core *core, @@ -295,6 +275,8 @@ struct vban_stream *vban_stream_new(struct pw_core *core, switch (impl->info.media_subtype) { case SPA_MEDIA_SUBTYPE_raw: parse_audio_info(props, &impl->info.info.raw); + if (SPA_FLAG_IS_SET(impl->info.info.raw.flags, SPA_AUDIO_FLAG_UNPOSITIONED)) + default_layout(impl->info.info.raw.channels, impl->info.info.raw.position); impl->stream_info = impl->info; impl->format_info = find_audio_format_info(&impl->info); if (impl->format_info == NULL) { @@ -325,13 +307,13 @@ struct vban_stream *vban_stream_new(struct pw_core *core, res = -EINVAL; goto out; } - pw_properties_set(props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); + pw_properties_set(props, PW_KEY_FORMAT_DSP, "32 bit raw UMP"); impl->stride = impl->format_info->size; impl->rate = pw_properties_get_uint32(props, "midi.rate", 10000); if (impl->rate == 0) impl->rate = 10000; - impl->header.format_SR = (0x1 << 5) | 14; /* 115200 */ + impl->header.format_SR = VBAN_PROTOCOL_SERIAL | VBAN_BPS_115200; impl->header.format_nbs = 0; impl->header.format_nbc = 0; impl->header.format_bit = impl->format_info->format_bit; diff --git a/src/modules/module-vban/vban.h b/src/modules/module-vban/vban.h index bf129920..efcfaf40 100644 --- a/src/modules/module-vban/vban.h +++ b/src/modules/module-vban/vban.h @@ -5,6 +5,10 @@ #ifndef PIPEWIRE_VBAN_H #define PIPEWIRE_VBAN_H +#include <stdint.h> + +#include <spa/utils/defs.h> + #ifdef __cplusplus extern "C" { #endif @@ -17,21 +21,30 @@ extern "C" { #define VBAN_SAMPLES_MAX_NB 256 struct vban_header { - char vban[4]; /* contains 'V' 'B', 'A', 'N' */ - uint8_t format_SR; /* SR index */ - uint8_t format_nbs; /* nb sample per frame (1 to 256) */ - uint8_t format_nbc; /* nb channel (1 to 256) */ - uint8_t format_bit; /* bit format */ + char vban[4]; /* contains 'V' 'B', 'A', 'N' */ + uint8_t format_SR; /* SR index */ + uint8_t format_nbs; /* nb sample per frame (1 to 256) */ + uint8_t format_nbc; /* nb channel (1 to 256) */ + uint8_t format_bit; /* bit format */ char stream_name[VBAN_STREAM_NAME_SIZE]; /* stream name */ - uint32_t n_frames; /* growing frame number. */ + uint32_t n_frames; /* growing frame number. */ } __attribute__ ((packed)); +#define VBAN_PROTOCOL_AUDIO 0x00 +#define VBAN_PROTOCOL_SERIAL 0x20 +#define VBAN_PROTOCOL_TXT 0x40 +#define VBAN_PROTOCOL_SERVICE 0x60 +#define VBAN_PROTOCOL_UNDEFINED_1 0x80 +#define VBAN_PROTOCOL_UNDEFINED_2 0xA0 +#define VBAN_PROTOCOL_UNDEFINED_3 0xC0 +#define VBAN_PROTOCOL_USER 0xE0 + #define VBAN_SR_MAXNUMBER 21 -static uint32_t const vban_SR[VBAN_SR_MAXNUMBER] = { - 6000, 12000, 24000, 48000, 96000, 192000, 384000, - 8000, 16000, 32000, 64000, 128000, 256000, 512000, - 11025, 22050, 44100, 88200, 176400, 352800, 705600 +static uint32_t const vban_SR[32] = { + 6000, 12000, 24000, 48000, 96000, 192000, 384000, + 8000, 16000, 32000, 64000, 128000, 256000, 512000, + 11025, 22050, 44100, 88200, 176400, 352800, 705600 }; static inline uint8_t vban_sr_index(uint32_t rate) @@ -44,7 +57,84 @@ static inline uint8_t vban_sr_index(uint32_t rate) return VBAN_SR_MAXNUMBER; } -#define VBAN_DATATYPE_U8 0x00 +#define VBAN_CODEC_PCM 0x00 +#define VBAN_CODEC_VBCA 0x10 //VB-AUDIO AOIP CODEC +#define VBAN_CODEC_VBCV 0x20 //VB-AUDIO VOIP CODEC +#define VBAN_CODEC_UNDEFINED_1 0x30 +#define VBAN_CODEC_UNDEFINED_2 0x40 +#define VBAN_CODEC_UNDEFINED_3 0x50 +#define VBAN_CODEC_UNDEFINED_4 0x60 +#define VBAN_CODEC_UNDEFINED_5 0x70 +#define VBAN_CODEC_UNDEFINED_6 0x80 +#define VBAN_CODEC_UNDEFINED_7 0x90 +#define VBAN_CODEC_UNDEFINED_8 0xA0 +#define VBAN_CODEC_UNDEFINED_9 0xB0 +#define VBAN_CODEC_UNDEFINED_10 0xC0 +#define VBAN_CODEC_UNDEFINED_11 0xD0 +#define VBAN_CODEC_UNDEFINED_12 0xE0 +#define VBAN_CODEC_USER 0xF0 + +#define VBAN_BPS_0 0 +#define VBAN_BPS_110 1 +#define VBAN_BPS_150 2 +#define VBAN_BPS_300 3 +#define VBAN_BPS_600 4 +#define VBAN_BPS_1200 5 +#define VBAN_BPS_2400 6 +#define VBAN_BPS_4800 7 +#define VBAN_BPS_9600 8 +#define VBAN_BPS_14400 9 +#define VBAN_BPS_19200 10 +#define VBAN_BPS_31250 11 +#define VBAN_BPS_38400 12 +#define VBAN_BPS_57600 13 +#define VBAN_BPS_115200 14 +#define VBAN_BPS_128000 15 +#define VBAN_BPS_230400 16 +#define VBAN_BPS_250000 17 +#define VBAN_BPS_256000 18 +#define VBAN_BPS_460800 19 +#define VBAN_BPS_921600 20 +#define VBAN_BPS_1000000 21 +#define VBAN_BPS_1500000 22 +#define VBAN_BPS_2000000 23 +#define VBAN_BPS_3000000 24 +#define VBAN_BPS_MAXNUMBER 25 + +static const int vban_BPSList[] = { + [VBAN_BPS_0] = 0, + [VBAN_BPS_110] = 110, + [VBAN_BPS_150] = 150, + [VBAN_BPS_300] = 300, + [VBAN_BPS_600] = 600, + [VBAN_BPS_1200] = 1200, + [VBAN_BPS_2400] = 2400, + [VBAN_BPS_4800] = 4800, + [VBAN_BPS_9600] = 9600, + [VBAN_BPS_14400] = 14400, + [VBAN_BPS_19200] = 19200, + [VBAN_BPS_31250] = 31250, + [VBAN_BPS_38400] = 38400, + [VBAN_BPS_57600] = 57600, + [VBAN_BPS_115200] = 115200, + [VBAN_BPS_128000] = 128000, + [VBAN_BPS_230400] = 230400, + [VBAN_BPS_250000] = 250000, + [VBAN_BPS_256000] = 256000, + [VBAN_BPS_460800] = 460800, + [VBAN_BPS_921600] = 921600, + [VBAN_BPS_1000000] = 1000000, + [VBAN_BPS_1500000] = 1500000, + [VBAN_BPS_2000000] = 2000000, + [VBAN_BPS_3000000] = 3000000, +}; +SPA_STATIC_ASSERT(SPA_N_ELEMENTS(vban_BPSList) == VBAN_BPS_MAXNUMBER); + +#define VBAN_SERIAL_GENERIC 0x00 +#define VBAN_SERIAL_MIDI 0x10 +#define VBAN_SERIAL_USER 0xf0 + +#define VBAN_DATATYPE_BYTE8 0x00 #define VBAN_DATATYPE_INT16 0x01 #define VBAN_DATATYPE_INT24 0x02 #define VBAN_DATATYPE_INT32 0x03 @@ -53,10 +143,6 @@ static inline uint8_t vban_sr_index(uint32_t rate) #define VBAN_DATATYPE_12BITS 0x06 #define VBAN_DATATYPE_10BITS 0x07 -#define VBAN_SERIAL_GENERIC 0x00 -#define VBAN_SERIAL_MIDI 0x10 -#define VBAN_SERIAL_USER 0xf0 - #ifdef __cplusplus } #endif diff --git a/src/modules/network-utils.h b/src/modules/network-utils.h index 25354f4c..16f9d927 100644 --- a/src/modules/network-utils.h +++ b/src/modules/network-utils.h @@ -82,31 +82,33 @@ static inline int pw_net_parse_address_port(const char *address, static inline int pw_net_get_ip(const struct sockaddr_storage *sa, char *ip, size_t len, bool *ip4, uint16_t *port) { + if (ip4) + *ip4 = sa->ss_family == AF_INET; + if (sa->ss_family == AF_INET) { struct sockaddr_in *in = (struct sockaddr_in*)sa; - inet_ntop(sa->ss_family, &in->sin_addr, ip, len); + if (inet_ntop(sa->ss_family, &in->sin_addr, ip, len) == NULL) + return -errno; if (port) *port = ntohs(in->sin_port); } else if (sa->ss_family == AF_INET6) { struct sockaddr_in6 *in = (struct sockaddr_in6*)sa; - inet_ntop(sa->ss_family, &in->sin6_addr, ip, len); + if (inet_ntop(sa->ss_family, &in->sin6_addr, ip, len) == NULL) + return -errno; if (port) *port = ntohs(in->sin6_port); - if (in->sin6_scope_id == 0 || len <= 1) - goto finish; - - size_t curlen = strlen(ip); - if (len-(curlen+1) >= IFNAMSIZ) { - ip += curlen+1; - ip[-1] = '%'; - if (if_indextoname(in->sin6_scope_id, ip) == NULL) - ip[-1] = 0; + + if (in->sin6_scope_id != 0 && len > 1) { + size_t curlen = strlen(ip); + if (len-(curlen+1) >= IFNAMSIZ) { + ip += curlen+1; + ip[-1] = '%'; + if (if_indextoname(in->sin6_scope_id, ip) == NULL) + ip[-1] = 0; + } } } else return -EINVAL; -finish: - if (ip4) - *ip4 = sa->ss_family == AF_INET; return 0; } diff --git a/src/modules/spa/meson.build b/src/modules/spa/meson.build deleted file mode 100644 index 8332910b..00000000 --- a/src/modules/spa/meson.build +++ /dev/null @@ -1,31 +0,0 @@ -pipewire_module_spa_node = shared_library('pipewire-module-spa-node', - [ 'module-node.c', 'spa-node.c' ], - include_directories : [configinc], - install : true, - install_dir : modules_install_dir, - dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], -) - -pipewire_module_spa_device = shared_library('pipewire-module-spa-device', - [ 'module-device.c', 'spa-device.c' ], - include_directories : [configinc], - install : true, - install_dir : modules_install_dir, - dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], -) - -pipewire_module_spa_node_factory = shared_library('pipewire-module-spa-node-factory', - [ 'module-node-factory.c', 'spa-node.c' ], - include_directories : [configinc], - install : true, - install_dir : modules_install_dir, - dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], -) - -pipewire_module_spa_device_factory = shared_library('pipewire-module-spa-device-factory', - [ 'module-device-factory.c', 'spa-device.c' ], - include_directories : [configinc], - install : true, - install_dir : modules_install_dir, - dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep], -) diff --git a/src/modules/spa/spa-node.c b/src/modules/spa/spa-node.c index a7205895..c9681290 100644 --- a/src/modules/spa/spa-node.c +++ b/src/modules/spa/spa-node.c @@ -133,84 +133,6 @@ void *pw_spa_node_get_user_data(struct pw_impl_node *node) return impl->user_data; } -static int -setup_props(struct pw_context *context, struct spa_node *spa_node, struct pw_properties *pw_props) -{ - int res; - struct spa_pod *props; - void *state = NULL; - const char *key; - uint32_t index = 0; - uint8_t buf[4096]; - struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf)); - const struct spa_pod_prop *prop = NULL; - - res = spa_node_enum_params_sync(spa_node, - SPA_PARAM_Props, &index, NULL, &props, - &b); - if (res != 1) { - if (res < 0) - pw_log_debug("spa_node_get_props result: %s", spa_strerror(res)); - if (res == -ENOTSUP || res == -ENOENT) - res = 0; - return res; - } - - while ((key = pw_properties_iterate(pw_props, &state))) { - uint32_t type = 0; - - type = spa_debug_type_find_type(spa_type_props, key); - if (type == SPA_TYPE_None) - continue; - - if ((prop = spa_pod_find_prop(props, prop, type))) { - const char *value = pw_properties_get(pw_props, key); - - if (value == NULL) - continue; - - pw_log_debug("configure prop %s to %s", key, value); - - switch(prop->value.type) { - case SPA_TYPE_Bool: - SPA_POD_VALUE(struct spa_pod_bool, &prop->value) = - pw_properties_parse_bool(value); - break; - case SPA_TYPE_Id: - SPA_POD_VALUE(struct spa_pod_id, &prop->value) = - pw_properties_parse_int(value); - break; - case SPA_TYPE_Int: - SPA_POD_VALUE(struct spa_pod_int, &prop->value) = - pw_properties_parse_int(value); - break; - case SPA_TYPE_Long: - SPA_POD_VALUE(struct spa_pod_long, &prop->value) = - pw_properties_parse_int64(value); - break; - case SPA_TYPE_Float: - SPA_POD_VALUE(struct spa_pod_float, &prop->value) = - pw_properties_parse_float(value); - break; - case SPA_TYPE_Double: - SPA_POD_VALUE(struct spa_pod_double, &prop->value) = - pw_properties_parse_double(value); - break; - case SPA_TYPE_String: - break; - default: - break; - } - } - } - - if ((res = spa_node_set_param(spa_node, SPA_PARAM_Props, 0, props)) < 0) { - pw_log_debug("spa_node_set_props failed: %s", spa_strerror(res)); - return res; - } - return 0; -} - struct match { struct pw_properties *props; int count; @@ -280,9 +202,6 @@ struct pw_impl_node *pw_spa_node_load(struct pw_context *context, spa_node = iface; - if ((res = setup_props(context, spa_node, properties)) < 0) - pw_log_warn("can't setup properties: %s", spa_strerror(res)); - this = pw_spa_node_new(context, flags, spa_node, handle, spa_steal_ptr(properties), user_data_size); if (this == NULL) { diff --git a/src/pipewire/array.h b/src/pipewire/array.h index cbded821..ca00e7b9 100644 --- a/src/pipewire/array.h +++ b/src/pipewire/array.h @@ -13,6 +13,11 @@ extern "C" { #include <spa/utils/defs.h> +#ifndef PW_API_ARRAY +#define PW_API_ARRAY static inline +#endif + + /** \defgroup pw_array Array * * \brief An array object @@ -71,7 +76,7 @@ struct pw_array { /** Initialize the array with given extend. Extend needs to be > 0 or else * the array will not be able to expand. */ -static inline void pw_array_init(struct pw_array *arr, size_t extend) +PW_API_ARRAY void pw_array_init(struct pw_array *arr, size_t extend) { arr->data = NULL; arr->size = arr->alloc = 0; @@ -79,7 +84,7 @@ static inline void pw_array_init(struct pw_array *arr, size_t extend) } /** Clear the array. This should be called when pw_array_init() was called. */ -static inline void pw_array_clear(struct pw_array *arr) +PW_API_ARRAY void pw_array_clear(struct pw_array *arr) { if (arr->extend > 0) free(arr->data); @@ -87,7 +92,7 @@ static inline void pw_array_clear(struct pw_array *arr) } /** Initialize a static array. */ -static inline void pw_array_init_static(struct pw_array *arr, void *data, size_t size) +PW_API_ARRAY void pw_array_init_static(struct pw_array *arr, void *data, size_t size) { arr->data = data; arr->alloc = size; @@ -95,13 +100,13 @@ static inline void pw_array_init_static(struct pw_array *arr, void *data, size_t } /** Reset the array */ -static inline void pw_array_reset(struct pw_array *arr) +PW_API_ARRAY void pw_array_reset(struct pw_array *arr) { arr->size = 0; } /** Make sure \a size bytes can be added to the array */ -static inline int pw_array_ensure_size(struct pw_array *arr, size_t size) +PW_API_ARRAY int pw_array_ensure_size(struct pw_array *arr, size_t size) { size_t alloc, need; @@ -124,7 +129,7 @@ static inline int pw_array_ensure_size(struct pw_array *arr, size_t size) /** Add \a ref size bytes to \a arr. A pointer to memory that can * hold at least \a size bytes is returned or NULL when an error occurred * and errno will be set.*/ -static inline void *pw_array_add(struct pw_array *arr, size_t size) +PW_API_ARRAY void *pw_array_add(struct pw_array *arr, size_t size) { void *p; @@ -139,7 +144,7 @@ static inline void *pw_array_add(struct pw_array *arr, size_t size) /** Add a pointer to array. Returns 0 on success and a negative errno style * error on failure. */ -static inline int pw_array_add_ptr(struct pw_array *arr, void *ptr) +PW_API_ARRAY int pw_array_add_ptr(struct pw_array *arr, void *ptr) { void **p = (void **)pw_array_add(arr, sizeof(void*)); if (p == NULL) diff --git a/src/pipewire/client.h b/src/pipewire/client.h index cd119643..7798e212 100644 --- a/src/pipewire/client.h +++ b/src/pipewire/client.h @@ -12,6 +12,7 @@ extern "C" { #include <spa/utils/defs.h> #include <spa/param/param.h> +#include <pipewire/type.h> #include <pipewire/proxy.h> #include <pipewire/permission.h> @@ -30,6 +31,10 @@ extern "C" { #define PW_VERSION_CLIENT 3 struct pw_client; +#ifndef PW_API_CLIENT_IMPL +#define PW_API_CLIENT_IMPL static inline +#endif + /* default ID of the current client after connect */ #define PW_ID_CLIENT 1 @@ -150,20 +155,45 @@ struct pw_client_methods { const struct pw_permission *permissions); }; -#define pw_client_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - spa_interface_call_res((struct spa_interface*)o, \ - struct pw_client_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define pw_client_add_listener(c,...) pw_client_method(c,add_listener,0,__VA_ARGS__) -#define pw_client_error(c,...) pw_client_method(c,error,0,__VA_ARGS__) -#define pw_client_update_properties(c,...) pw_client_method(c,update_properties,0,__VA_ARGS__) -#define pw_client_get_permissions(c,...) pw_client_method(c,get_permissions,0,__VA_ARGS__) -#define pw_client_update_permissions(c,...) pw_client_method(c,update_permissions,0,__VA_ARGS__) +/** \copydoc pw_client_methods.add_listener + * \sa pw_client_methods.add_listener */ +PW_API_CLIENT_IMPL int pw_client_add_listener(struct pw_client *object, + struct spa_hook *listener, + const struct pw_client_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, pw_client, (struct spa_interface*)object, add_listener, 0, + listener, events, data); +} +/** \copydoc pw_client_methods.error + * \sa pw_client_methods.error */ +PW_API_CLIENT_IMPL int pw_client_error(struct pw_client *object, uint32_t id, int res, const char *message) +{ + return spa_api_method_r(int, -ENOTSUP, pw_client, (struct spa_interface*)object, error, 0, + id, res, message); +} +/** \copydoc pw_client_methods.update_properties + * \sa pw_client_methods.update_properties */ +PW_API_CLIENT_IMPL int pw_client_update_properties(struct pw_client *object, const struct spa_dict *props) +{ + return spa_api_method_r(int, -ENOTSUP, pw_client, (struct spa_interface*)object, update_properties, 0, + props); +} +/** \copydoc pw_client_methods.get_permissions + * \sa pw_client_methods.get_permissions */ +PW_API_CLIENT_IMPL int pw_client_get_permissions(struct pw_client *object, uint32_t index, uint32_t num) +{ + return spa_api_method_r(int, -ENOTSUP, pw_client, (struct spa_interface*)object, get_permissions, 0, + index, num); +} +/** \copydoc pw_client_methods.update_permissions + * \sa pw_client_methods.update_permissions */ +PW_API_CLIENT_IMPL int pw_client_update_permissions(struct pw_client *object, uint32_t n_permissions, + const struct pw_permission *permissions) +{ + return spa_api_method_r(int, -ENOTSUP, pw_client, (struct spa_interface*)object, update_permissions, 0, + n_permissions, permissions); +} /** * \} diff --git a/src/pipewire/conf.c b/src/pipewire/conf.c index ee8e24a1..387a3ab9 100644 --- a/src/pipewire/conf.c +++ b/src/pipewire/conf.c @@ -565,19 +565,18 @@ static int parse_spa_libs(void *user_data, const char *location, { struct data *d = user_data; struct pw_context *context = d->context; - struct spa_json it[2]; + struct spa_json it[1]; char key[512], value[512]; int res; - spa_json_init(&it[0], str, len); - if (spa_json_enter_object(&it[0], &it[1]) < 0) { + if (spa_json_begin_object(&it[0], str, len) < 0) { pw_log_error("config file error: context.spa-libs is not an " "object in '%.*s'", (int)len, str); return -EINVAL; } - while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { - if (spa_json_get_string(&it[1], value, sizeof(value)) > 0) { + while (spa_json_get_string(&it[0], key, sizeof(key)) > 0) { + if (spa_json_get_string(&it[0], value, sizeof(value)) > 0) { if ((res = pw_context_add_spa_lib(context, key, value)) < 0) { pw_log_error("error adding spa-libs for '%s' in '%.*s': %s", key, (int)len, str, spa_strerror(res)); @@ -629,7 +628,8 @@ static int load_module(struct pw_context *context, const char *key, const char * * "!null" -> same as !null * !"null" and "!\"null\"" matches anything that is not the string "null" */ -static bool find_match(struct spa_json *arr, const struct spa_dict *props, bool condition) +SPA_EXPORT +bool pw_conf_find_match(struct spa_json *arr, const struct spa_dict *props, bool condition) { struct spa_json it[1]; const char *as = arr->cur; @@ -641,16 +641,10 @@ static bool find_match(struct spa_json *arr, const struct spa_dict *props, bool int match = 0, fail = 0; int len; - while (spa_json_get_string(&it[0], key, sizeof(key)) > 0) { + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &value)) > 0) { bool success = false, is_null, reg = false, parse_string = true; int skip = 0; - if ((len = spa_json_next(&it[0], &value)) <= 0) { - pw_log_warn("malformed match rule: key '%s' has " - "no value in '%.*s'", key, az, as); - break; - } - /* first decode a string, when there was a string, we assume it * can not be null but the "null" string, unless there is a modifier, * see below. */ @@ -752,44 +746,36 @@ static int parse_modules(void *user_data, const char *location, { struct data *d = user_data; struct pw_context *context = d->context; - struct spa_json it[4]; + struct spa_json it[3]; char key[512]; int res = 0, r; spa_autofree char *s = strndup(str, len); - spa_json_init(&it[0], s, len); - if (spa_json_enter_array(&it[0], &it[1]) < 0) { + if (spa_json_begin_array(&it[0], s, len) < 0) { pw_log_error("context.modules is not an array in '%.*s'", (int)len, str); return -EINVAL; } - while ((r = spa_json_enter_object(&it[1], &it[2])) > 0) { + while ((r = spa_json_enter_object(&it[0], &it[1])) > 0) { char *name = NULL, *args = NULL, *flags = NULL; bool have_match = true; + const char *val; + int l; - while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { - const char *val; - int l; - - if ((l = spa_json_next(&it[2], &val)) <= 0) { - pw_log_warn("malformed module: key '%s' has no " - "value in '%.*s'", key, (int)len, str); - break; - } - + while ((l = spa_json_object_next(&it[1], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "name")) { name = (char*)val; spa_json_parse_stringn(val, l, name, l+1); } else if (spa_streq(key, "args")) { if (spa_json_is_container(val, l)) - l = spa_json_container_len(&it[2], val, l); + l = spa_json_container_len(&it[1], val, l); args = (char*)val; spa_json_parse_stringn(val, l, args, l+1); } else if (spa_streq(key, "flags")) { if (spa_json_is_container(val, l)) - l = spa_json_container_len(&it[2], val, l); + l = spa_json_container_len(&it[1], val, l); flags = (char*)val; spa_json_parse_stringn(val, l, flags, l+1); } else if (spa_streq(key, "condition")) { @@ -798,8 +784,8 @@ static int parse_modules(void *user_data, const char *location, (int)len, str); break; } - spa_json_enter(&it[2], &it[3]); - have_match = find_match(&it[3], &context->properties->dict, true); + spa_json_enter(&it[1], &it[2]); + have_match = pw_conf_find_match(&it[2], &context->properties->dict, true); } else { pw_log_warn("unknown module key '%s' in '%.*s'", key, (int)len, str); @@ -862,43 +848,35 @@ static int parse_objects(void *user_data, const char *location, { struct data *d = user_data; struct pw_context *context = d->context; - struct spa_json it[4]; + struct spa_json it[3]; char key[512]; int res = 0, r; spa_autofree char *s = strndup(str, len); - spa_json_init(&it[0], s, len); - if (spa_json_enter_array(&it[0], &it[1]) < 0) { + if (spa_json_begin_array(&it[0], s, len) < 0) { pw_log_error("config file error: context.objects is not an array"); return -EINVAL; } - while ((r = spa_json_enter_object(&it[1], &it[2])) > 0) { + while ((r = spa_json_enter_object(&it[0], &it[1])) > 0) { char *factory = NULL, *args = NULL, *flags = NULL; bool have_match = true; + const char *val; + int l; - while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { - const char *val; - int l; - - if ((l = spa_json_next(&it[2], &val)) <= 0) { - pw_log_warn("malformed object: key '%s' has no " - "value in '%.*s'", key, (int)len, str); - break; - } - + while ((l = spa_json_object_next(&it[1], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "factory")) { factory = (char*)val; spa_json_parse_stringn(val, l, factory, l+1); } else if (spa_streq(key, "args")) { if (spa_json_is_container(val, l)) - l = spa_json_container_len(&it[2], val, l); + l = spa_json_container_len(&it[1], val, l); args = (char*)val; spa_json_parse_stringn(val, l, args, l+1); } else if (spa_streq(key, "flags")) { if (spa_json_is_container(val, l)) - l = spa_json_container_len(&it[2], val, l); + l = spa_json_container_len(&it[1], val, l); flags = (char*)val; spa_json_parse_stringn(val, l, flags, l+1); @@ -908,8 +886,8 @@ static int parse_objects(void *user_data, const char *location, (int)len, str); break; } - spa_json_enter(&it[2], &it[3]); - have_match = find_match(&it[3], &context->properties->dict, true); + spa_json_enter(&it[1], &it[2]); + have_match = pw_conf_find_match(&it[2], &context->properties->dict, true); } else { pw_log_warn("unknown object key '%s' in '%.*s'", key, (int)len, str); @@ -1046,40 +1024,30 @@ static int parse_exec(void *user_data, const char *location, { struct data *d = user_data; struct pw_context *context = d->context; - struct spa_json it[4]; + struct spa_json it[3]; char key[512]; int r, res = 0; spa_autofree char *s = strndup(str, len); - spa_json_init(&it[0], s, len); - if (spa_json_enter_array(&it[0], &it[1]) < 0) { + if (spa_json_begin_array(&it[0], s, len) < 0) { pw_log_error("config file error: context.exec is not an array in '%.*s'", (int)len, str); return -EINVAL; } - while ((r = spa_json_enter_object(&it[1], &it[2])) > 0) { + while ((r = spa_json_enter_object(&it[0], &it[1])) > 0) { char *path = NULL; - const char *args_val = "[]"; - int args_len = 2; + const char *args_val = "[]", *val; + int args_len = 2, l; bool have_match = true; - while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { - const char *val; - int l; - - if ((l = spa_json_next(&it[2], &val)) <= 0) { - pw_log_warn("malformed exec: key '%s' has no " - "value in '%.*s'", key, (int)len, str); - break; - } - + while ((l = spa_json_object_next(&it[1], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "path")) { path = (char*)val; spa_json_parse_stringn(val, l, path, l+1); } else if (spa_streq(key, "args")) { if (spa_json_is_container(val, l)) - l = spa_json_container_len(&it[2], val, l); + l = spa_json_container_len(&it[1], val, l); args_val = val; args_len = l; } else if (spa_streq(key, "condition")) { @@ -1088,8 +1056,8 @@ static int parse_exec(void *user_data, const char *location, (int)len, str); goto next; } - spa_json_enter(&it[2], &it[3]); - have_match = find_match(&it[3], &context->properties->dict, true); + spa_json_enter(&it[1], &it[2]); + have_match = pw_conf_find_match(&it[2], &context->properties->dict, true); } else { pw_log_warn("unknown exec key '%s' in '%.*s'", key, (int)len, str); @@ -1232,6 +1200,10 @@ int pw_conf_load_conf_for_context(struct pw_properties *props, struct pw_propert 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 (spa_streq(conf_name, "client-rt.conf")) { + pw_log_warn("setting config.name to client-rt.conf is deprecated, using client.conf"); + conf_name = NULL; + } if (conf_name == NULL) conf_name = "client.conf"; else if (!valid_conf_name(conf_name)) { @@ -1307,43 +1279,41 @@ int pw_conf_match_rules(const char *str, size_t len, const char *location, void *data) { const char *val; - struct spa_json it[4], actions; + struct spa_json it[3], actions; int r; - spa_json_init(&it[0], str, len); - if (spa_json_enter_array(&it[0], &it[1]) < 0) { + if (spa_json_begin_array(&it[0], str, len) < 0) { pw_log_warn("expect array of match rules in: '%.*s'", (int)len, str); return 0; } - while ((r = spa_json_enter_object(&it[1], &it[2])) > 0) { + while ((r = spa_json_enter_object(&it[0], &it[1])) > 0) { char key[64]; bool have_match = false, have_actions = false; + int res, l; - while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { + while ((l = spa_json_object_next(&it[1], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "matches")) { - if (spa_json_enter_array(&it[2], &it[3]) < 0) { + if (!spa_json_is_array(val, l)) { pw_log_warn("expected array as matches in '%.*s'", (int)len, str); break; } - have_match = find_match(&it[3], props, false); + spa_json_enter(&it[1], &it[2]); + have_match = pw_conf_find_match(&it[2], props, false); } else if (spa_streq(key, "actions")) { - if (spa_json_enter_object(&it[2], &actions) > 0) - have_actions = true; - else + if (!spa_json_is_object(val, l)) { pw_log_warn("expected object as match actions in '%.*s'", (int)len, str); + } else { + have_actions = true; + spa_json_enter(&it[1], &actions); + } } else { pw_log_warn("unknown match key '%s'", key); - if (spa_json_next(&it[2], &val) <= 0) { - pw_log_warn("malformed match rule: key '%s' has " - "no value in '%.*s'", key, (int)len, str); - break; - } } } if (!have_match) @@ -1353,16 +1323,9 @@ int pw_conf_match_rules(const char *str, size_t len, const char *location, continue; } - while (spa_json_get_string(&actions, key, sizeof(key)) > 0) { - int res, len; + while ((len = spa_json_object_next(&actions, key, sizeof(key), &val)) > 0) { pw_log_debug("action %s", key); - if ((len = spa_json_next(&actions, &val)) <= 0) { - pw_log_warn("malformed action: key '%s' has no value in '%.*s'", - key, (int)len, str); - break; - } - if (spa_json_is_container(val, len)) len = spa_json_container_len(&actions, val, len); diff --git a/src/pipewire/conf.h b/src/pipewire/conf.h index 66898b1f..783c1356 100644 --- a/src/pipewire/conf.h +++ b/src/pipewire/conf.h @@ -5,6 +5,8 @@ #ifndef PIPEWIRE_CONF_H #define PIPEWIRE_CONF_H +#include <spa/utils/json-core.h> + #include <pipewire/context.h> /** \defgroup pw_conf Configuration @@ -21,6 +23,8 @@ int pw_conf_load_conf(const char *prefix, const char *name, struct pw_properties 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); +bool pw_conf_find_match(struct spa_json *arr, const struct spa_dict *props, bool condition); + int pw_conf_section_update_props(const struct spa_dict *conf, const char *section, struct pw_properties *props); diff --git a/src/pipewire/context.c b/src/pipewire/context.c index e30a2ce7..e81de912 100644 --- a/src/pipewire/context.c +++ b/src/pipewire/context.c @@ -49,7 +49,7 @@ struct data_loop { struct pw_data_loop *impl; bool autostart; bool started; - int ref; + uint64_t last_used; }; /** \cond */ @@ -204,20 +204,21 @@ static int setup_data_loops(struct impl *impl) lib_name = pw_properties_get(this->properties, "context.data-loop." PW_KEY_LIBRARY_NAME_SYSTEM); if ((str = pw_properties_get(this->properties, "context.data-loops")) != NULL) { - struct spa_json it[4]; + struct spa_json it[2]; char key[512]; int r, len = strlen(str); spa_autofree char *s = strndup(str, len); i = 0; - spa_json_init(&it[0], s, len); - if (spa_json_enter_array(&it[0], &it[1]) < 0) { + if (spa_json_begin_array(&it[0], s, len) < 0) { pw_log_error("context.data-loops is not an array in '%s'", str); res = -EINVAL; goto exit; } - while ((r = spa_json_enter_object(&it[1], &it[2])) > 0) { + while ((r = spa_json_enter_object(&it[0], &it[1])) > 0) { char *props = NULL; + const char *val; + int l; if (i >= MAX_LOOPS) { pw_log_warn("too many context.data-loops, using first %d", @@ -229,17 +230,9 @@ static int setup_data_loops(struct impl *impl) pw_properties_update(pr, &this->properties->dict); pw_properties_set(pr, PW_KEY_LIBRARY_NAME_SYSTEM, lib_name); - while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) { - const char *val; - int l; - - if ((l = spa_json_next(&it[2], &val)) <= 0) { - pw_log_warn("malformed data-loop: key '%s' has no " - "value in '%.*s'", key, (int)len, str); - break; - } + while ((l = spa_json_object_next(&it[1], key, sizeof(key), &val)) > 0) { if (spa_json_is_container(val, l)) - l = spa_json_container_len(&it[2], val, l); + l = spa_json_container_len(&it[1], val, l); props = (char*)val; spa_json_parse_stringn(val, l, props, l+1); @@ -679,12 +672,12 @@ static struct pw_data_loop *acquire_data_loop(struct impl *impl, const char *nam } } - pw_log_debug("%d: name:'%s' class:'%s' score:%d ref:%d", i, - ln, l->impl->class, score, l->ref); + pw_log_debug("%d: name:'%s' class:'%s' score:%d last_used:%"PRIu64, i, + ln, l->impl->class, score, l->last_used); if ((best_loop == NULL) || (score > best_score) || - (score == best_score && l->ref < best_loop->ref)) { + (score == best_score && l->last_used < best_loop->last_used)) { best_loop = l; best_score = score; } @@ -692,15 +685,15 @@ static struct pw_data_loop *acquire_data_loop(struct impl *impl, const char *nam if (best_loop == NULL) return NULL; - best_loop->ref++; + best_loop->last_used = get_time_ns(impl->this.main_loop->system); if ((res = data_loop_start(impl, best_loop)) < 0) { errno = -res; return NULL; } - pw_log_info("%p: using name:'%s' class:'%s' ref:%d", impl, + pw_log_info("%p: using name:'%s' class:'%s' last_used:%"PRIu64, impl, best_loop->impl->loop->name, - best_loop->impl->class, best_loop->ref); + best_loop->impl->class, best_loop->last_used); return best_loop->impl; } @@ -744,9 +737,8 @@ void pw_context_release_loop(struct pw_context *context, struct pw_loop *loop) for (i = 0; i < impl->n_data_loops; i++) { struct data_loop *l = &impl->data_loops[i]; if (l->impl->loop == loop) { - l->ref--; - pw_log_info("release name:'%s' class:'%s' ref:%d", l->impl->loop->name, - l->impl->class, l->ref); + pw_log_info("release name:'%s' class:'%s' last_used:%"PRIu64, + l->impl->loop->name, l->impl->class, l->last_used); return; } } @@ -982,7 +974,15 @@ int pw_context_find_format(struct pw_context *context, if (res == -ENOENT || res == 0) { pw_log_debug("%p: no input format filter, using output format: %s", context, spa_strerror(res)); - *format = filter; + + uint32_t offset = builder->state.offset; + res = spa_pod_builder_raw_padded(builder, filter, SPA_POD_SIZE(filter)); + if (res < 0) { + *error = spa_aprintf("failed to add pod"); + goto error; + } + + *format = spa_pod_builder_deref(builder, offset); } else { *error = spa_aprintf("error input enum formats: %s", spa_strerror(res)); goto error; @@ -1011,7 +1011,15 @@ int pw_context_find_format(struct pw_context *context, if (res == -ENOENT || res == 0) { pw_log_debug("%p: no output format filter, using input format: %s", context, spa_strerror(res)); - *format = filter; + + uint32_t offset = builder->state.offset; + res = spa_pod_builder_raw_padded(builder, filter, SPA_POD_SIZE(filter)); + if (res < 0) { + *error = spa_aprintf("failed to add pod"); + goto error; + } + + *format = spa_pod_builder_deref(builder, offset); } else { *error = spa_aprintf("error output enum formats: %s", spa_strerror(res)); goto error; @@ -1511,8 +1519,8 @@ int pw_context_recalc_graph(struct pw_context *context, const char *reason) struct pw_impl_node *n, *s, *target, *fallback; const uint32_t *rates; uint32_t max_quantum, min_quantum, def_quantum, rate_quantum, floor_quantum, ceil_quantum; - uint32_t n_rates, def_rate; - bool freewheel, global_force_rate, global_force_quantum, transport_start; + uint32_t n_rates, def_rate, transport; + bool freewheel, global_force_rate, global_force_quantum; struct spa_list collect; pw_log_info("%p: busy:%d reason:%s", context, impl->recalc, reason); @@ -1525,7 +1533,6 @@ int pw_context_recalc_graph(struct pw_context *context, const char *reason) again: impl->recalc = true; freewheel = false; - transport_start = false; /* clean up the flags first */ spa_list_for_each(n, &context->node_list, link) { @@ -1758,10 +1765,16 @@ again: /* Here we are allowed to change the rate of the driver. * Start with the default rate. If the desired rate is * allowed, switch to it */ - target_rate = node_def_rate; if (rate.denom != 0 && rate.num == 1) - target_rate = find_best_rate(node_rates, node_n_rates, - rate.denom, target_rate); + target_rate = rate.denom; + else + target_rate = node_def_rate; + + target_rate = find_best_rate(node_rates, node_n_rates, + target_rate, node_def_rate); + + pw_log_debug("%p: def_rate:%d target_rate:%d rate:%d/%d", context, + node_def_rate, target_rate, rate.num, rate.denom); } was_target_pending = n->target_pending; @@ -1791,15 +1804,15 @@ again: if (node_rate_quantum != 0 && current_rate != node_rate_quantum) { /* the quantum values are scaled with the current rate */ - node_def_quantum = node_def_quantum * current_rate / node_rate_quantum; - node_min_quantum = node_min_quantum * current_rate / node_rate_quantum; - node_max_quantum = node_max_quantum * current_rate / node_rate_quantum; + node_def_quantum = SPA_SCALE32(node_def_quantum, current_rate, node_rate_quantum); + node_min_quantum = SPA_SCALE32(node_min_quantum, current_rate, node_rate_quantum); + node_max_quantum = SPA_SCALE32(node_max_quantum, current_rate, node_rate_quantum); } /* calculate desired quantum. Don't limit to the max_latency when we are * going to force a quantum or rate and reconfigure the nodes. */ if (max_latency.denom != 0 && !force_quantum && !force_rate) { - uint32_t tmp = (max_latency.num * current_rate / max_latency.denom); + uint32_t tmp = SPA_SCALE32(max_latency.num, current_rate, max_latency.denom); if (tmp < node_max_quantum) node_max_quantum = tmp; } @@ -1816,7 +1829,7 @@ again: else { target_quantum = node_def_quantum; if (latency.denom != 0) - target_quantum = (latency.num * current_rate / latency.denom); + target_quantum = SPA_SCALE32(latency.num, current_rate, latency.denom); target_quantum = SPA_CLAMP(target_quantum, node_min_quantum, node_max_quantum); target_quantum = SPA_CLAMP(target_quantum, floor_quantum, ceil_quantum); @@ -1873,10 +1886,14 @@ again: n->rt.position->clock.target_duration, n->rt.position->clock.target_rate.denom, n->name); + transport = PW_NODE_ACTIVATION_COMMAND_NONE; + /* first change the node states of the followers to the new target */ spa_list_for_each(s, &n->follower_list, follower_link) { - if (s->transport) - transport_start = true; + if (s->transport != PW_NODE_ACTIVATION_COMMAND_NONE) { + transport = s->transport; + s->transport = PW_NODE_ACTIVATION_COMMAND_NONE; + } if (s == n) continue; pw_log_debug("%p: follower %p: active:%d '%s'", @@ -1884,10 +1901,10 @@ again: ensure_state(s, running); } - SPA_ATOMIC_STORE(n->rt.target.activation->command, - transport_start ? - PW_NODE_ACTIVATION_COMMAND_START : - PW_NODE_ACTIVATION_COMMAND_STOP); + if (transport != PW_NODE_ACTIVATION_COMMAND_NONE) { + pw_log_info("%s: transport %d", n->name, transport); + SPA_ATOMIC_STORE(n->rt.target.activation->command, transport); + } /* now that all the followers are ready, start the driver */ ensure_state(n, running); diff --git a/src/pipewire/core.c b/src/pipewire/core.c index a627452f..3eb6d7bb 100644 --- a/src/pipewire/core.c +++ b/src/pipewire/core.c @@ -12,6 +12,9 @@ #include <spa/pod/parser.h> #include <spa/debug/types.h> +#define PW_API_CORE_IMPL SPA_EXPORT +#define PW_API_REGISTRY_IMPL SPA_EXPORT + #include "pipewire/pipewire.h" #include "pipewire/private.h" @@ -20,6 +23,18 @@ PW_LOG_TOPIC_EXTERN(log_core); #define PW_LOG_TOPIC_DEFAULT log_core +static void core_event_info(void *data, const struct pw_core_info *info) +{ + struct pw_core *this = data; + if (info && info->props) { + static const char * const keys[] = { + "default.clock.quantum-limit", + NULL + }; + pw_properties_update_keys(this->context->properties, info->props, keys); + } +} + static void core_event_ping(void *data, uint32_t id, int seq) { struct pw_core *this = data; @@ -111,6 +126,7 @@ static void core_event_remove_mem(void *data, uint32_t id) static const struct pw_core_events core_events = { PW_VERSION_CORE_EVENTS, + .info = core_event_info, .error = core_event_error, .ping = core_event_ping, .done = core_event_done, @@ -476,6 +492,15 @@ struct pw_mempool * pw_core_get_mempool(struct pw_core *core) return core->pool; } +SPA_EXPORT +void pw_core_add_proxy_listener(struct pw_core *object, + struct spa_hook *listener, + const struct pw_proxy_events *events, + void *data) +{ + pw_proxy_add_listener((struct pw_proxy *)object, listener, events, data); +} + SPA_EXPORT int pw_core_disconnect(struct pw_core *core) { diff --git a/src/pipewire/core.h b/src/pipewire/core.h index 171d35d6..be7beb41 100644 --- a/src/pipewire/core.h +++ b/src/pipewire/core.h @@ -14,6 +14,8 @@ extern "C" { #include <spa/utils/hook.h> +#include <pipewire/type.h> + /** \defgroup pw_core Core * * \brief The core global object. @@ -41,6 +43,13 @@ struct pw_core; #define PW_VERSION_REGISTRY 3 struct pw_registry; +#ifndef PW_API_CORE_IMPL +#define PW_API_CORE_IMPL static inline +#endif +#ifndef PW_API_REGISTRY_IMPL +#define PW_API_REGISTRY_IMPL static inline +#endif + /** The default remote name to connect to */ #define PW_DEFAULT_REMOTE "pipewire-0" @@ -334,23 +343,51 @@ struct pw_core_methods { int (*destroy) (void *object, void *proxy); }; -#define pw_core_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - spa_interface_call_res((struct spa_interface*)o, \ - struct pw_core_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) -#define pw_core_add_listener(c,...) pw_core_method(c,add_listener,0,__VA_ARGS__) -#define pw_core_hello(c,...) pw_core_method(c,hello,0,__VA_ARGS__) -#define pw_core_sync(c,...) pw_core_method(c,sync,0,__VA_ARGS__) -#define pw_core_pong(c,...) pw_core_method(c,pong,0,__VA_ARGS__) -#define pw_core_error(c,...) pw_core_method(c,error,0,__VA_ARGS__) - - -static inline +/** \copydoc pw_core_methods.add_listener + * \sa pw_core_methods.add_listener */ +PW_API_CORE_IMPL int pw_core_add_listener(struct pw_core *object, + struct spa_hook *listener, + const struct pw_core_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_core, (struct spa_interface*)object, add_listener, 0, + listener, events, data); +} +/** \copydoc pw_core_methods.hello + * \sa pw_core_methods.hello */ +PW_API_CORE_IMPL int pw_core_hello(struct pw_core *object, uint32_t version) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_core, (struct spa_interface*)object, hello, 0, + version); +} +/** \copydoc pw_core_methods.sync + * \sa pw_core_methods.sync */ +PW_API_CORE_IMPL int pw_core_sync(struct pw_core *object, uint32_t id, int seq) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_core, (struct spa_interface*)object, sync, 0, + id, seq); +} +/** \copydoc pw_core_methods.pong + * \sa pw_core_methods.pong */ +PW_API_CORE_IMPL int pw_core_pong(struct pw_core *object, uint32_t id, int seq) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_core, (struct spa_interface*)object, pong, 0, + id, seq); +} +/** \copydoc pw_core_methods.error + * \sa pw_core_methods.error */ +PW_API_CORE_IMPL int pw_core_error(struct pw_core *object, uint32_t id, int seq, int res, const char *message) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_core, (struct spa_interface*)object, error, 0, + id, seq, res, message); +} +PW_API_CORE_IMPL SPA_PRINTF_FUNC(5, 0) int pw_core_errorv(struct pw_core *core, uint32_t id, int seq, int res, const char *message, va_list args) @@ -361,7 +398,7 @@ pw_core_errorv(struct pw_core *core, uint32_t id, int seq, return pw_core_error(core, id, seq, res, buffer); } -static inline +PW_API_CORE_IMPL SPA_PRINTF_FUNC(5, 6) int pw_core_errorf(struct pw_core *core, uint32_t id, int seq, int res, const char *message, ...) @@ -374,17 +411,18 @@ pw_core_errorf(struct pw_core *core, uint32_t id, int seq, return r; } -static inline struct pw_registry * +/** \copydoc pw_core_methods.get_registry + * \sa pw_core_methods.get_registry */ +PW_API_CORE_IMPL struct pw_registry * pw_core_get_registry(struct pw_core *core, uint32_t version, size_t user_data_size) { - struct pw_registry *res = NULL; - spa_interface_call_res((struct spa_interface*)core, - struct pw_core_methods, res, - get_registry, 0, version, user_data_size); - return res; + return spa_api_method_r(struct pw_registry*, NULL, + pw_core, (struct spa_interface*)core, get_registry, 0, + version, user_data_size); } - -static inline void * +/** \copydoc pw_core_methods.create_object + * \sa pw_core_methods.create_object */ +PW_API_CORE_IMPL void * pw_core_create_object(struct pw_core *core, const char *factory_name, const char *type, @@ -392,15 +430,18 @@ pw_core_create_object(struct pw_core *core, const struct spa_dict *props, size_t user_data_size) { - void *res = NULL; - spa_interface_call_res((struct spa_interface*)core, - struct pw_core_methods, res, - create_object, 0, factory_name, - type, version, props, user_data_size); - return res; + return spa_api_method_r(void*, NULL, + pw_core, (struct spa_interface*)core, create_object, 0, + factory_name, type, version, props, user_data_size); +} +/** \copydoc pw_core_methods.destroy + * \sa pw_core_methods.destroy */ +PW_API_CORE_IMPL void +pw_core_destroy(struct pw_core *core, void *proxy) +{ + spa_api_method_v(pw_core, (struct spa_interface*)core, destroy, 0, + proxy); } - -#define pw_core_destroy(c,...) pw_core_method(c,destroy,0,__VA_ARGS__) /** * \} @@ -516,31 +557,38 @@ struct pw_registry_methods { int (*destroy) (void *object, uint32_t id); }; -#define pw_registry_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - spa_interface_call_res((struct spa_interface*)o, \ - struct pw_registry_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) /** Registry */ -#define pw_registry_add_listener(p,...) pw_registry_method(p,add_listener,0,__VA_ARGS__) - -static inline void * +/** \copydoc pw_registry_methods.add_listener + * \sa pw_registry_methods.add_listener */ +PW_API_REGISTRY_IMPL int pw_registry_add_listener(struct pw_registry *registry, + struct spa_hook *listener, + const struct pw_registry_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_registry, (struct spa_interface*)registry, add_listener, 0, + listener, events, data); +} +/** \copydoc pw_registry_methods.bind + * \sa pw_registry_methods.bind */ +PW_API_REGISTRY_IMPL void * pw_registry_bind(struct pw_registry *registry, uint32_t id, const char *type, uint32_t version, size_t user_data_size) { - void *res = NULL; - spa_interface_call_res((struct spa_interface*)registry, - struct pw_registry_methods, res, - bind, 0, id, type, version, user_data_size); - return res; + return spa_api_method_r(void*, NULL, + pw_registry, (struct spa_interface*)registry, bind, 0, + id, type, version, user_data_size); +} +/** \copydoc pw_registry_methods.destroy + * \sa pw_registry_methods.destroy */ +PW_API_REGISTRY_IMPL int +pw_registry_destroy(struct pw_registry *registry, uint32_t id) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_registry, (struct spa_interface*)registry, destroy, 0, id); } - -#define pw_registry_destroy(p,...) pw_registry_method(p,destroy,0,__VA_ARGS__) /** * \} diff --git a/src/pipewire/device.h b/src/pipewire/device.h index 4b546b99..9154daee 100644 --- a/src/pipewire/device.h +++ b/src/pipewire/device.h @@ -30,6 +30,10 @@ extern "C" { #define PW_VERSION_DEVICE 3 struct pw_device; +#ifndef PW_API_DEVICE_IMPL +#define PW_API_DEVICE_IMPL static inline +#endif + /** The device information. Extra information can be added in later versions */ struct pw_device_info { uint32_t id; /**< id of the global */ @@ -141,19 +145,44 @@ struct pw_device_methods { const struct spa_pod *param); }; -#define pw_device_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - spa_interface_call_res((struct spa_interface*)o, \ - struct pw_device_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define pw_device_add_listener(c,...) pw_device_method(c,add_listener,0,__VA_ARGS__) -#define pw_device_subscribe_params(c,...) pw_device_method(c,subscribe_params,0,__VA_ARGS__) -#define pw_device_enum_params(c,...) pw_device_method(c,enum_params,0,__VA_ARGS__) -#define pw_device_set_param(c,...) pw_device_method(c,set_param,0,__VA_ARGS__) +/** \copydoc pw_device_methods.add_listener + * \sa pw_device_methods.add_listener */ +PW_API_DEVICE_IMPL int pw_device_add_listener(struct pw_device *object, + struct spa_hook *listener, + const struct pw_device_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_device, (struct spa_interface*)object, add_listener, 0, + listener, events, data); +} +/** \copydoc pw_device_methods.subscribe_params + * \sa pw_device_methods.subscribe_params */ +PW_API_DEVICE_IMPL int pw_device_subscribe_params(struct pw_device *object, uint32_t *ids, uint32_t n_ids) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_device, (struct spa_interface*)object, subscribe_params, 0, + ids, n_ids); +} +/** \copydoc pw_device_methods.enum_params + * \sa pw_device_methods.enum_params */ +PW_API_DEVICE_IMPL int pw_device_enum_params(struct pw_device *object, + int seq, uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_device, (struct spa_interface*)object, enum_params, 0, + seq, id, start, num, filter); +} +/** \copydoc pw_device_methods.set_param + * \sa pw_device_methods.set_param */ +PW_API_DEVICE_IMPL int pw_device_set_param(struct pw_device *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_device, (struct spa_interface*)object, set_param, 0, + id, flags, param); +} /** * \} diff --git a/src/pipewire/extensions/client-node.h b/src/pipewire/extensions/client-node.h index d1c6e733..69f278e0 100644 --- a/src/pipewire/extensions/client-node.h +++ b/src/pipewire/extensions/client-node.h @@ -30,6 +30,10 @@ extern "C" { #define PW_VERSION_CLIENT_NODE 6 struct pw_client_node; +#ifndef PW_API_CLIENT_NODE_IMPL +#define PW_API_CLIENT_NODE_IMPL static inline +#endif + #define PW_EXTENSION_MODULE_CLIENT_NODE PIPEWIRE_MODULE_PREFIX "module-client-node" /** information about a buffer */ @@ -303,33 +307,54 @@ struct pw_client_node_methods { struct spa_buffer **buffers); }; - -#define pw_client_node_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - spa_interface_call_res((struct spa_interface*)o, \ - struct pw_client_node_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define pw_client_node_add_listener(c,...) pw_client_node_method(c,add_listener,0,__VA_ARGS__) - -static inline struct pw_node * +PW_API_CLIENT_NODE_IMPL int pw_client_node_add_listener(struct pw_client_node *object, + struct spa_hook *listener, + const struct pw_client_node_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, pw_client_node, (struct spa_interface*)object, + add_listener, 0, listener, events, data); +} +PW_API_CLIENT_NODE_IMPL struct pw_node * pw_client_node_get_node(struct pw_client_node *p, uint32_t version, size_t user_data_size) { - struct pw_node *res = NULL; - spa_interface_call_res((struct spa_interface*)p, - struct pw_client_node_methods, res, + return spa_api_method_r(struct pw_node*, NULL, pw_client_node, (struct spa_interface*)p, get_node, 0, version, user_data_size); - return res; } - -#define pw_client_node_update(c,...) pw_client_node_method(c,update,0,__VA_ARGS__) -#define pw_client_node_port_update(c,...) pw_client_node_method(c,port_update,0,__VA_ARGS__) -#define pw_client_node_set_active(c,...) pw_client_node_method(c,set_active,0,__VA_ARGS__) -#define pw_client_node_event(c,...) pw_client_node_method(c,event,0,__VA_ARGS__) -#define pw_client_node_port_buffers(c,...) pw_client_node_method(c,port_buffers,0,__VA_ARGS__) +PW_API_CLIENT_NODE_IMPL int pw_client_node_update(struct pw_client_node *object, + uint32_t change_mask, + uint32_t n_params, const struct spa_pod **params, + const struct spa_node_info *info) +{ + return spa_api_method_r(int, -ENOTSUP, pw_client_node, (struct spa_interface*)object, + update, 0, change_mask, n_params, params, info); +} +PW_API_CLIENT_NODE_IMPL int pw_client_node_port_update(struct pw_client_node *object, + enum spa_direction direction, uint32_t port_id, + uint32_t change_mask, + uint32_t n_params, const struct spa_pod **params, + const struct spa_port_info *info) +{ + return spa_api_method_r(int, -ENOTSUP, pw_client_node, (struct spa_interface*)object, + port_update, 0, direction, port_id, change_mask, n_params, params, info); +} +PW_API_CLIENT_NODE_IMPL int pw_client_node_set_active(struct pw_client_node *object, bool active) +{ + return spa_api_method_r(int, -ENOTSUP, pw_client_node, (struct spa_interface*)object, + set_active, 0, active); +} +PW_API_CLIENT_NODE_IMPL int pw_client_node_event(struct pw_client_node *object, const struct spa_event *event) +{ + return spa_api_method_r(int, -ENOTSUP, pw_client_node, (struct spa_interface*)object, + event, 0, event); +} +PW_API_CLIENT_NODE_IMPL int pw_client_node_port_buffers(struct pw_client_node *object, + enum spa_direction direction, uint32_t port_id, + uint32_t mix_id, uint32_t n_buffers, struct spa_buffer **buffers) +{ + return spa_api_method_r(int, -ENOTSUP, pw_client_node, (struct spa_interface*)object, + port_buffers, 0, direction, port_id, mix_id, n_buffers, buffers); +} /** * \} diff --git a/src/pipewire/extensions/metadata.h b/src/pipewire/extensions/metadata.h index 8c0641fb..58057c60 100644 --- a/src/pipewire/extensions/metadata.h +++ b/src/pipewire/extensions/metadata.h @@ -10,6 +10,9 @@ extern "C" { #endif #include <spa/utils/defs.h> +#include <spa/utils/hook.h> + +#include <errno.h> /** \defgroup pw_metadata Metadata * Metadata interface @@ -26,6 +29,10 @@ extern "C" { #define PW_VERSION_METADATA 3 struct pw_metadata; +#ifndef PW_API_METADATA_IMPL +#define PW_API_METADATA_IMPL static inline +#endif + #define PW_EXTENSION_MODULE_METADATA PIPEWIRE_MODULE_PREFIX "module-metadata" #define PW_METADATA_EVENT_PROPERTY 0 @@ -89,19 +96,36 @@ struct pw_metadata_methods { int (*clear) (void *object); }; - -#define pw_metadata_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - spa_interface_call_res((struct spa_interface*)o, \ - struct pw_metadata_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define pw_metadata_add_listener(c,...) pw_metadata_method(c,add_listener,0,__VA_ARGS__) -#define pw_metadata_set_property(c,...) pw_metadata_method(c,set_property,0,__VA_ARGS__) -#define pw_metadata_clear(c) pw_metadata_method(c,clear,0) +/** \copydoc pw_metadata_methods.add_listener + * \sa pw_metadata_methods.add_listener */ +PW_API_METADATA_IMPL int pw_metadata_add_listener(struct pw_metadata *object, + struct spa_hook *listener, + const struct pw_metadata_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_metadata, (struct spa_interface*)object, add_listener, 0, + listener, events, data); +} +/** \copydoc pw_metadata_methods.set_property + * \sa pw_metadata_methods.set_property */ +PW_API_METADATA_IMPL int pw_metadata_set_property(struct pw_metadata *object, + uint32_t subject, + const char *key, + const char *type, + const char *value) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_metadata, (struct spa_interface*)object, set_property, 0, + subject, key, type, value); +} +/** \copydoc pw_metadata_methods.clear + * \sa pw_metadata_methods.clear */ +PW_API_METADATA_IMPL int pw_metadata_clear(struct pw_metadata *object) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_metadata, (struct spa_interface*)object, clear, 0); +} #define PW_KEY_METADATA_NAME "metadata.name" #define PW_KEY_METADATA_VALUES "metadata.values" diff --git a/src/pipewire/extensions/profiler.h b/src/pipewire/extensions/profiler.h index 81e997b4..d0e8908e 100644 --- a/src/pipewire/extensions/profiler.h +++ b/src/pipewire/extensions/profiler.h @@ -24,6 +24,10 @@ extern "C" { #define PW_VERSION_PROFILER 3 struct pw_profiler; +#ifndef PW_API_PROFILER +#define PW_API_PROFILER static inline +#endif + #define PW_EXTENSION_MODULE_PROFILER PIPEWIRE_MODULE_PREFIX "module-profiler" #define PW_PROFILER_PERM_MASK PW_PERM_R @@ -53,16 +57,17 @@ struct pw_profiler_methods { void *data); }; -#define pw_profiler_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - spa_interface_call_res((struct spa_interface*)o, \ - struct pw_profiler_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define pw_profiler_add_listener(c,...) pw_profiler_method(c,add_listener,0,__VA_ARGS__) +/** \copydoc pw_profiler_methods.add_listener + * \sa pw_profiler_methods.add_listener */ +PW_API_PROFILER int pw_profiler_add_listener(struct pw_profiler *object, + struct spa_hook *listener, + const struct pw_profiler_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_profiler, (struct spa_interface*)object, add_listener, 0, + listener, events, data); +} #define PW_KEY_PROFILER_NAME "profiler.name" diff --git a/src/pipewire/extensions/security-context.h b/src/pipewire/extensions/security-context.h index e21b5a3d..5a3cd2c1 100644 --- a/src/pipewire/extensions/security-context.h +++ b/src/pipewire/extensions/security-context.h @@ -26,6 +26,10 @@ extern "C" { #define PW_VERSION_SECURITY_CONTEXT 3 struct pw_security_context; +#ifndef PW_API_SECURITY_CONTEXT +#define PW_API_SECURITY_CONTEXT static inline +#endif + #define PW_EXTENSION_MODULE_SECURITY_CONTEXT PIPEWIRE_MODULE_PREFIX "module-security-context" #define PW_SECURITY_CONTEXT_EVENT_NUM 0 @@ -94,18 +98,27 @@ struct pw_security_context_methods { const struct spa_dict *props); }; - -#define pw_security_context_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - spa_interface_call_res((struct spa_interface*)o, \ - struct pw_security_context_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define pw_security_context_add_listener(c,...) pw_security_context_method(c,add_listener,0,__VA_ARGS__) -#define pw_security_context_create(c,...) pw_security_context_method(c,create,0,__VA_ARGS__) +/** \copydoc pw_security_context_methods.add_listener + * \sa pw_security_context_methods.add_listener */ +PW_API_SECURITY_CONTEXT int pw_security_context_add_listener(struct pw_security_context *object, + struct spa_hook *listener, + const struct pw_security_context_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_security_context, (struct spa_interface*)object, add_listener, 0, + listener, events, data); +} + +/** \copydoc pw_security_context_methods.create + * \sa pw_security_context_methods.create */ +PW_API_SECURITY_CONTEXT int pw_security_context_create(struct pw_security_context *object, + int listen_fd, int close_fd, const struct spa_dict *props) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_security_context, (struct spa_interface*)object, create, 0, + listen_fd, close_fd, props); +} /** * \} diff --git a/src/pipewire/factory.h b/src/pipewire/factory.h index 6eda0420..9c6ab74f 100644 --- a/src/pipewire/factory.h +++ b/src/pipewire/factory.h @@ -32,6 +32,10 @@ extern "C" { #define PW_VERSION_FACTORY 3 struct pw_factory; +#ifndef PW_API_FACTORY_IMPL +#define PW_API_FACTORY_IMPL static inline +#endif + /** The factory information. Extra information can be added in later versions */ struct pw_factory_info { uint32_t id; /**< id of the global */ @@ -83,16 +87,17 @@ struct pw_factory_methods { void *data); }; -#define pw_factory_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - spa_interface_call_res((struct spa_interface*)o, \ - struct pw_factory_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define pw_factory_add_listener(c,...) pw_factory_method(c,add_listener,0,__VA_ARGS__) +/** \copydoc pw_factory_methods.add_listener + * \sa pw_factory_methods.add_listener */ +PW_API_FACTORY_IMPL int pw_factory_add_listener(struct pw_factory *object, + struct spa_hook *listener, + const struct pw_factory_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_factory, (struct spa_interface*)object, add_listener, 0, + listener, events, data); +} /** * \} diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c index 4c774a9d..519c1e2f 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -146,6 +146,7 @@ struct filter { unsigned int warn_mlock:1; unsigned int trigger:1; int in_emit_param_changed; + int pending_drain; }; static int get_param_index(uint32_t id) @@ -406,8 +407,10 @@ static bool filter_set_state(struct pw_filter *filter, enum pw_filter_state stat pw_filter_state_as_string(old), pw_filter_state_as_string(state), res, error); - if (state == PW_FILTER_STATE_ERROR) + if (state == PW_FILTER_STATE_ERROR) { pw_log_error("%p: error (%d) %s", filter, res, error); + errno = -res; + } filter->state = state; pw_filter_emit_state_changed(filter, old, state, error); @@ -989,13 +992,19 @@ do_call_drained(struct spa_loop *loop, struct pw_filter *filter = &impl->this; pw_log_trace("%p: drained", filter); pw_filter_emit_drained(filter); + SPA_ATOMIC_DEC(impl->pending_drain); return 0; } static void call_drained(struct filter *impl) { - pw_loop_invoke(impl->main_loop, - do_call_drained, 1, NULL, 0, false, impl); + pw_log_info("%p: drained", impl); + if (SPA_ATOMIC_INC(impl->pending_drain) == 1) { + pw_loop_invoke(impl->main_loop, + do_call_drained, 1, NULL, 0, false, impl); + } else { + SPA_ATOMIC_DEC(impl->pending_drain); + } } static int impl_node_process(void *object) @@ -1119,11 +1128,14 @@ static void proxy_destroy(void *_data) static void proxy_error(void *_data, int seq, int res, const char *message) { struct pw_filter *filter = _data; + int old_errno = errno; /* we just emit the state change here to inform the application. * If this is supposed to be a permanent error, the app should * do a pw_filter_set_error() */ + errno = -res; pw_filter_emit_state_changed(filter, filter->state, PW_FILTER_STATE_ERROR, message); + errno = old_errno; } static void proxy_bound_props(void *_data, uint32_t global_id, const struct spa_dict *props) @@ -1467,6 +1479,8 @@ enum pw_filter_state pw_filter_get_state(struct pw_filter *filter, const char ** { if (error) *error = filter->error; + if (filter->state == PW_FILTER_STATE_ERROR) + errno = -filter->error_res; return filter->state; } @@ -1762,17 +1776,26 @@ static void add_video_dsp_port_params(struct filter *impl, struct port *port) SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_DSP_F32))); } -static void add_control_dsp_port_params(struct filter *impl, struct port *port) +static void add_control_dsp_port_params(struct filter *impl, struct port *port, uint32_t types) { uint8_t buffer[4096]; struct spa_pod_builder b; + struct spa_pod_frame f[1]; 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_application), + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), + 0); + if (types != 0) { + spa_pod_builder_add(&b, + SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int(types), + 0); + } add_param(impl, port, SPA_PARAM_EnumFormat, PARAM_FLAG_LOCKED, - 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))); + spa_pod_builder_pop(&b, &f[0])); } SPA_EXPORT @@ -1828,9 +1851,12 @@ void *pw_filter_add_port(struct pw_filter *filter, add_audio_dsp_port_params(impl, p); else if (spa_streq(str, "32 bit float RGBA video")) add_video_dsp_port_params(impl, p); - else if (spa_streq(str, "8 bit raw midi") || - spa_streq(str, "8 bit raw control")) - add_control_dsp_port_params(impl, p); + else if (spa_streq(str, "8 bit raw midi")) + add_control_dsp_port_params(impl, p, 1u << SPA_CONTROL_Midi); + else if (spa_streq(str, "8 bit raw control")) + add_control_dsp_port_params(impl, p, 0); + else if (spa_streq(str, "32 bit raw UMP")) + add_control_dsp_port_params(impl, p, 1u << SPA_CONTROL_UMP); } /* then override with user provided if any */ if (update_params(impl, p, SPA_ID_INVALID, params, n_params) < 0) @@ -2079,20 +2105,23 @@ do_trigger_process(struct spa_loop *loop, return spa_node_call_ready(&impl->callbacks, res); } -static int do_trigger_request_process(struct spa_loop *loop, +static int do_emit_event(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct filter *impl = user_data; - uint8_t buffer[1024]; - struct spa_pod_builder b = { 0 }; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - spa_node_emit_event(&impl->hooks, - spa_pod_builder_add_object(&b, - SPA_TYPE_EVENT_Node, SPA_NODE_EVENT_RequestProcess)); + const struct spa_event *event = data; + spa_node_emit_event(&impl->hooks, event); return 0; } +SPA_EXPORT +int pw_filter_emit_event(struct pw_filter *filter, const struct spa_event *event) +{ + struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this); + return pw_loop_invoke(impl->main_loop, + do_emit_event, 1, event, SPA_POD_SIZE(&event->pod), false, impl); +} + SPA_EXPORT int pw_filter_trigger_process(struct pw_filter *filter) { @@ -2102,13 +2131,13 @@ int pw_filter_trigger_process(struct pw_filter *filter) pw_log_trace_fp("%p: driving:%d", impl, filter->node->driving); if (impl->trigger) { - pw_impl_node_trigger(filter->node); + res = pw_impl_node_trigger(filter->node); } else if (filter->node->driving) { res = pw_loop_invoke(impl->data_loop, do_trigger_process, 1, NULL, 0, false, impl); } else { - res = pw_loop_invoke(impl->main_loop, - do_trigger_request_process, 1, NULL, 0, false, impl); + pw_filter_emit_event(filter, + &SPA_NODE_EVENT_INIT(SPA_NODE_EVENT_RequestProcess)); } return res; } diff --git a/src/pipewire/filter.h b/src/pipewire/filter.h index 701c4481..8298b654 100644 --- a/src/pipewire/filter.h +++ b/src/pipewire/filter.h @@ -29,6 +29,7 @@ struct pw_filter; #include <spa/node/io.h> #include <spa/param/param.h> #include <spa/pod/command.h> +#include <spa/pod/event.h> #include <pipewire/core.h> #include <pipewire/stream.h> @@ -61,7 +62,8 @@ struct pw_filter_events { uint32_t version; void (*destroy) (void *data); - /** when the filter state changes */ + /** when the filter state changes. Since 1.4 this also sets errno when the + * new state is PW_FILTER_STATE_ERROR */ void (*state_changed) (void *data, enum pw_filter_state old, enum pw_filter_state state, const char *error); @@ -79,7 +81,9 @@ struct pw_filter_events { /** do processing. This is normally called from the * mainloop but can also be called directly from the realtime data - * thread if the user is prepared to deal with this. */ + * thread if the user is prepared to deal with this with the + * PW_FILTER_FLAG_RT_PROCESS. Only call methods marked with RT safe + * from this event when called from the realtime thread. */ void (*process) (void *data, struct spa_io_position *position); /** The filter is drained */ @@ -100,7 +104,8 @@ enum pw_filter_flags { * called explicitly */ PW_FILTER_FLAG_DRIVER = (1 << 1), /**< be a driver */ PW_FILTER_FLAG_RT_PROCESS = (1 << 2), /**< call process from the realtime - * thread */ + * thread. Only call methods marked as + * RT safe. */ PW_FILTER_FLAG_CUSTOM_LATENCY = (1 << 3), /**< don't call the default latency algorithm * but emit the param_changed event for the * ports when Latency params are received. */ @@ -149,6 +154,8 @@ void pw_filter_add_listener(struct pw_filter *filter, const struct pw_filter_events *events, void *data); +/** Get the current filter state. Since 1.4 this also sets errno when the + * state is PW_FILTER_STATE_ERROR */ enum pw_filter_state pw_filter_get_state(struct pw_filter *filter, const char **error); const char *pw_filter_get_name(struct pw_filter *filter); @@ -211,26 +218,26 @@ pw_filter_update_params(struct pw_filter *filter, /**< a \ref pw_filter */ /** Query the time on the filter, deprecated, use the spa_io_position in the - * process() method for timing information. */ + * process() method for timing information. RT safe. */ SPA_DEPRECATED int pw_filter_get_time(struct pw_filter *filter, struct pw_time *time); /** Get the current time in nanoseconds. This value can be compared with - * the nsec value in the spa_io_position. Since 1.1.0 */ + * the nsec value in the spa_io_position. RT safe. Since 1.1.0 */ uint64_t pw_filter_get_nsec(struct pw_filter *filter); /** Get the data loop that is doing the processing of this filter. This loop - * is assigned after pw_filter_connect(). * Since 1.1.0 */ + * is assigned after pw_filter_connect(). Since 1.1.0 */ struct pw_loop *pw_filter_get_data_loop(struct pw_filter *filter); /** Get a buffer that can be filled for output ports or consumed - * for input ports. */ + * for input ports. RT safe. */ struct pw_buffer *pw_filter_dequeue_buffer(void *port_data); -/** Submit a buffer for playback or recycle a buffer for capture. */ +/** Submit a buffer for playback or recycle a buffer for capture. RT safe. */ int pw_filter_queue_buffer(void *port_data, struct pw_buffer *buffer); -/** Get a data pointer to the buffer data */ +/** Get a data pointer to the buffer data. RT safe. */ void *pw_filter_get_dsp_buffer(void *port_data, uint32_t n_samples); /** Activate or deactivate the filter */ @@ -240,7 +247,8 @@ int pw_filter_set_active(struct pw_filter *filter, bool active); * be called when all data is played or recorded. The filter can be resumed * after the drain by setting it active again with * \ref pw_filter_set_active(). A flush without a drain is mostly useful afer - * a state change to PAUSED, to flush any remaining data from the queues. */ + * a state change to PAUSED, to flush any remaining data from the queues. + * RT safe. */ int pw_filter_flush(struct pw_filter *filter, bool drain); /** Check if the filter is driving. The filter needs to have the @@ -250,13 +258,17 @@ int pw_filter_flush(struct pw_filter *filter, bool drain); bool pw_filter_is_driving(struct pw_filter *filter); /** Check if the graph is using lazy scheduling. - * Since 1.2.7 */ + * Since 1.4.0 */ bool pw_filter_is_lazy(struct pw_filter *filter); /** Trigger a push/pull on the filter. One iteration of the graph will - * be scheduled and process() will be called. Since 0.3.66 */ + * be scheduled and process() will be called. RT safe. Since 0.3.66 */ int pw_filter_trigger_process(struct pw_filter *filter); +/** Emit an event from this filter. RT safe. + * Since 1.2.6 */ +int pw_filter_emit_event(struct pw_filter *filter, const struct spa_event *event); + /** * \} */ diff --git a/src/pipewire/impl-client.c b/src/pipewire/impl-client.c index 06e3d53b..d46e0793 100644 --- a/src/pipewire/impl-client.c +++ b/src/pipewire/impl-client.c @@ -6,8 +6,12 @@ #include <string.h> #include <assert.h> +#include <spa/utils/defs.h> #include <spa/utils/string.h> +#define PW_API_CLIENT_IMPL SPA_EXPORT +#include "pipewire/client.h" + #include "pipewire/impl.h" #include "pipewire/private.h" diff --git a/src/pipewire/impl-device.c b/src/pipewire/impl-device.c index 0a96baaf..71e2e266 100644 --- a/src/pipewire/impl-device.c +++ b/src/pipewire/impl-device.c @@ -11,6 +11,7 @@ #include <spa/utils/string.h> #include <spa/utils/json-pod.h> +#define PW_API_DEVICE_IMPL SPA_EXPORT #include "pipewire/impl.h" #include "pipewire/private.h" diff --git a/src/pipewire/impl-factory.c b/src/pipewire/impl-factory.c index 8863ef2e..413fa339 100644 --- a/src/pipewire/impl-factory.c +++ b/src/pipewire/impl-factory.c @@ -7,6 +7,8 @@ #include <spa/debug/types.h> #include <spa/utils/string.h> +#define PW_API_FACTORY_IMPL SPA_EXPORT + #include "pipewire/impl.h" #include "pipewire/private.h" diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index 6824301e..3df092ad 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -13,6 +13,7 @@ #include <spa/param/param.h> #include <spa/debug/types.h> +#define PW_API_LINK_IMPL SPA_EXPORT #include "pipewire/impl-link.h" #include "pipewire/private.h" @@ -33,6 +34,10 @@ struct impl { uint32_t output_busy_id; uint32_t input_busy_id; + int output_pending_seq; + int input_pending_seq; + int output_result; + int input_result; struct spa_pod *format_filter; struct pw_properties *properties; @@ -68,28 +73,36 @@ static void info_changed(struct pw_impl_link *link) link->info.change_mask = 0; } -static inline void input_set_busy_id(struct pw_impl_link *link, uint32_t id) +static inline int input_set_busy_id(struct pw_impl_link *link, uint32_t id, int pending_seq) { struct impl *impl = SPA_CONTAINER_OF(link, struct impl, this); + int res = impl->input_result; if (impl->input_busy_id != SPA_ID_INVALID) link->input->busy_count--; if (id != SPA_ID_INVALID) link->input->busy_count++; impl->input_busy_id = id; + impl->input_pending_seq = SPA_RESULT_ASYNC_SEQ(pending_seq); + impl->input_result = 0; if (link->input->busy_count < 0) pw_log_error("%s: invalid busy count:%d", link->name, link->input->busy_count); + return res; } -static inline void output_set_busy_id(struct pw_impl_link *link, uint32_t id) +static inline int output_set_busy_id(struct pw_impl_link *link, uint32_t id, int pending_seq) { struct impl *impl = SPA_CONTAINER_OF(link, struct impl, this); + int res = impl->output_result; if (impl->output_busy_id != SPA_ID_INVALID) link->output->busy_count--; if (id != SPA_ID_INVALID) link->output->busy_count++; impl->output_busy_id = id; + impl->output_pending_seq = SPA_RESULT_ASYNC_SEQ(pending_seq); + impl->output_result = 0; if (link->output->busy_count < 0) pw_log_error("%s: invalid busy count:%d", link->name, link->output->busy_count); + return res; } static void link_update_state(struct pw_impl_link *link, enum pw_link_state state, int res, char *error) @@ -147,10 +160,10 @@ static void link_update_state(struct pw_impl_link *link, enum pw_link_state stat link->prepared = false; link->preparing = false; - output_set_busy_id(link, SPA_ID_INVALID); + output_set_busy_id(link, SPA_ID_INVALID, SPA_ID_INVALID); pw_work_queue_cancel(impl->work, &link->output_link, SPA_ID_INVALID); - input_set_busy_id(link, SPA_ID_INVALID); + input_set_busy_id(link, SPA_ID_INVALID, SPA_ID_INVALID); pw_work_queue_cancel(impl->work, &link->input_link, SPA_ID_INVALID); } } @@ -167,13 +180,12 @@ static void complete_ready(void *obj, void *data, int res, uint32_t id) port = this->output; if (id != SPA_ID_INVALID) { - if (id == impl->input_busy_id) { - input_set_busy_id(this, SPA_ID_INVALID); - } else if (id == impl->output_busy_id) { - output_set_busy_id(this, SPA_ID_INVALID); - } else { + if (id == impl->input_busy_id) + res = input_set_busy_id(this, SPA_ID_INVALID, SPA_ID_INVALID); + else if (id == impl->output_busy_id) + res = output_set_busy_id(this, SPA_ID_INVALID, SPA_ID_INVALID); + else return; - } } pw_log_debug("%p: obj:%p port %p complete state:%d: %s", this, obj, port, @@ -183,13 +195,12 @@ static void complete_ready(void *obj, void *data, int res, uint32_t id) if (port->state < PW_IMPL_PORT_STATE_READY) pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_READY, 0, NULL); + if (this->input->state >= PW_IMPL_PORT_STATE_READY && + this->output->state >= PW_IMPL_PORT_STATE_READY) + link_update_state(this, PW_LINK_STATE_ALLOCATING, 0, NULL); } else { - pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_ERROR, - res, spa_aprintf("port error going to READY: %s", spa_strerror(res))); + link_update_state(this, PW_LINK_STATE_ERROR, -EIO, strdup("Format negotiation failed")); } - if (this->input->state >= PW_IMPL_PORT_STATE_READY && - this->output->state >= PW_IMPL_PORT_STATE_READY) - link_update_state(this, PW_LINK_STATE_ALLOCATING, 0, NULL); } static void complete_paused(void *obj, void *data, int res, uint32_t id) @@ -208,13 +219,12 @@ static void complete_paused(void *obj, void *data, int res, uint32_t id) } if (id != SPA_ID_INVALID) { - if (id == impl->input_busy_id) { - input_set_busy_id(this, SPA_ID_INVALID); - } else if (id == impl->output_busy_id) { - output_set_busy_id(this, SPA_ID_INVALID); - } else { + if (id == impl->input_busy_id) + res = input_set_busy_id(this, SPA_ID_INVALID, SPA_ID_INVALID); + else if (id == impl->output_busy_id) + res = output_set_busy_id(this, SPA_ID_INVALID, SPA_ID_INVALID); + else return; - } } pw_log_debug("%p: obj:%p port %p complete state:%d: %s", this, obj, port, @@ -225,13 +235,13 @@ static void complete_paused(void *obj, void *data, int res, uint32_t id) pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_PAUSED, 0, NULL); mix->have_buffers = true; + + if (this->rt.in_mix.have_buffers && this->rt.out_mix.have_buffers) + link_update_state(this, PW_LINK_STATE_PAUSED, 0, NULL); } else { - pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_ERROR, - res, spa_aprintf("port error going to PAUSED: %s", spa_strerror(res))); mix->have_buffers = false; + link_update_state(this, PW_LINK_STATE_ERROR, -EIO, strdup("Buffer allocation failed")); } - if (this->rt.in_mix.have_buffers && this->rt.out_mix.have_buffers) - link_update_state(this, PW_LINK_STATE_PAUSED, 0, NULL); } static void complete_sync(void *obj, void *data, int res, uint32_t id) @@ -395,10 +405,11 @@ static int do_negotiate(struct pw_impl_link *this) goto error; } if (SPA_RESULT_IS_ASYNC(res)) { - res = spa_node_sync(output->node->node, res); - busy_id = pw_work_queue_add(impl->work, &this->output_link, res, + pw_log_info("output set format %d", res); + busy_id = pw_work_queue_add(impl->work, &this->output_link, + spa_node_sync(output->node->node, res), complete_ready, this); - output_set_busy_id(this, busy_id); + output_set_busy_id(this, busy_id, res); } else { complete_ready(&this->output_link, this, res, SPA_ID_INVALID); } @@ -414,10 +425,11 @@ static int do_negotiate(struct pw_impl_link *this) goto error; } if (SPA_RESULT_IS_ASYNC(res2)) { - res2 = spa_node_sync(input->node->node, res2); - busy_id = pw_work_queue_add(impl->work, &this->input_link, res2, + pw_log_info("input set format %d", res2); + busy_id = pw_work_queue_add(impl->work, &this->input_link, + spa_node_sync(input->node->node, res2), complete_ready, this); - input_set_busy_id(this, busy_id); + input_set_busy_id(this, busy_id, res2); if (res == 0) res = res2; } else { @@ -579,10 +591,10 @@ static int do_allocation(struct pw_impl_link *this) goto error_clear; } if (SPA_RESULT_IS_ASYNC(res)) { - res = spa_node_sync(output->node->node, res); - busy_id = pw_work_queue_add(impl->work, &this->output_link, res, + busy_id = pw_work_queue_add(impl->work, &this->output_link, + spa_node_sync(output->node->node, res), complete_paused, this); - output_set_busy_id(this, busy_id); + output_set_busy_id(this, busy_id, res); if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) return 0; } else { @@ -602,10 +614,10 @@ static int do_allocation(struct pw_impl_link *this) } if (SPA_RESULT_IS_ASYNC(res)) { - res = spa_node_sync(input->node->node, res); - busy_id = pw_work_queue_add(impl->work, &this->input_link, res, + busy_id = pw_work_queue_add(impl->work, &this->input_link, + spa_node_sync(input->node->node, res), complete_paused, this); - input_set_busy_id(this, busy_id); + input_set_busy_id(this, busy_id, res); } else { complete_paused(&this->input_link, this, res, SPA_ID_INVALID); } @@ -742,7 +754,7 @@ static void input_remove(struct pw_impl_link *this, struct pw_impl_port *port) pw_log_debug("%p: remove input port %p", this, port); - input_set_busy_id(this, SPA_ID_INVALID); + input_set_busy_id(this, SPA_ID_INVALID, SPA_ID_INVALID); spa_hook_remove(&impl->input_port_listener); spa_hook_remove(&impl->input_node_listener); @@ -772,7 +784,7 @@ static void output_remove(struct pw_impl_link *this, struct pw_impl_port *port) pw_log_debug("%p: remove output port %p", this, port); - output_set_busy_id(this, SPA_ID_INVALID); + output_set_busy_id(this, SPA_ID_INVALID, SPA_ID_INVALID); spa_hook_remove(&impl->output_port_listener); spa_hook_remove(&impl->output_node_listener); @@ -1007,8 +1019,12 @@ static void input_node_result(void *data, int seq, int res, uint32_t type, const { struct impl *impl = data; struct pw_impl_port *port = impl->this.input; - pw_log_trace("%p: input port %p result seq:%d res:%d type:%u", - impl, port, seq, res, type); + pw_log_trace("%p: input port %p result seq:%d %d res:%d type:%u", + impl, port, seq, SPA_RESULT_ASYNC_SEQ(seq), res, type); + + if (type == SPA_RESULT_TYPE_NODE_ERROR && impl->input_pending_seq == seq) + impl->input_result = res; + node_result(impl, &impl->this.input_link, seq, res, type, result); } @@ -1016,8 +1032,12 @@ static void output_node_result(void *data, int seq, int res, uint32_t type, cons { struct impl *impl = data; struct pw_impl_port *port = impl->this.output; - pw_log_trace("%p: output port %p result seq:%d res:%d type:%u", - impl, port, seq, res, type); + pw_log_trace("%p: output port %p result seq:%d %d res:%d type:%u", + impl, port, seq, SPA_RESULT_ASYNC_SEQ(seq), res, type); + + if (type == SPA_RESULT_TYPE_NODE_ERROR && impl->output_pending_seq == seq) + impl->output_result = res; + node_result(impl, &impl->this.output_link, seq, res, type, result); } @@ -1377,8 +1397,9 @@ struct pw_impl_link *pw_context_create_link(struct pw_context *context, this->name = spa_aprintf("%d.%d.%d -> %d.%d.%d", output_node->info.id, output->port_id, this->rt.out_mix.port.port_id, input_node->info.id, input->port_id, this->rt.in_mix.port.port_id); - pw_log_info("(%s) (%s) -> (%s) async:%d:%04x:%04x:%d", this->name, output_node->name, + pw_log_info("(%s) (%s) -> (%s) async:%d:%d:%d:%04x:%04x:%d", this->name, output_node->name, input_node->name, output_node->driving, + output_node->async, input_node->async, output->flags, input->flags, impl->async); pw_impl_port_emit_link_added(output, this); diff --git a/src/pipewire/impl-module.c b/src/pipewire/impl-module.c index 2388760d..10aa432f 100644 --- a/src/pipewire/impl-module.c +++ b/src/pipewire/impl-module.c @@ -16,6 +16,7 @@ #include <spa/utils/cleanup.h> #include <spa/utils/string.h> +#define PW_API_MODULE_IMPL SPA_EXPORT #include "pipewire/impl.h" #include "pipewire/private.h" diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index 7329884b..351f98b0 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -22,6 +22,7 @@ #include <spa/utils/string.h> #include <spa/utils/json-pod.h> +#define PW_API_NODE_IMPL SPA_EXPORT #include "pipewire/impl-node.h" #include "pipewire/private.h" @@ -47,6 +48,8 @@ struct impl { unsigned int cache_params:1; unsigned int pending_play:1; + struct spa_command *pending_request_process; + char *group; char *link_group; char *sync_group; @@ -334,7 +337,7 @@ static int start_node(struct pw_impl_node *this) } else { /* driver nodes will wait until all other nodes are started before * they are started */ - this->pending_request_process = 0; + spa_clear_ptr(impl->pending_request_process, free); res = EBUSY; } @@ -439,7 +442,7 @@ static void node_update_state(struct pw_impl_node *node, enum pw_node_state stat state = PW_NODE_STATE_ERROR; error = spa_aprintf("Start error: %s", spa_strerror(res)); remove_node_from_graph(node); - } else if (node->pending_request_process > 0) { + } else if (impl->pending_request_process != NULL) { emit_pending_request_process = true; } } @@ -448,7 +451,8 @@ static void node_update_state(struct pw_impl_node *node, enum pw_node_state stat case PW_NODE_STATE_SUSPENDED: case PW_NODE_STATE_ERROR: if (state != PW_NODE_STATE_IDLE || node->pause_on_idle) - remove_node_from_graph(node); + if (old != PW_NODE_STATE_CREATING) + remove_node_from_graph(node); break; default: break; @@ -475,10 +479,9 @@ static void node_update_state(struct pw_impl_node *node, enum pw_node_state stat pw_impl_node_emit_state_changed(node, old, state, error); if (emit_pending_request_process) { - pw_log_debug("%p: request process:%d", node, node->pending_request_process); - node->pending_request_process = 0; - spa_node_send_command(node->node, - &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_RequestProcess)); + pw_log_debug("%p: request process:%p", node, impl->pending_request_process); + spa_node_send_command(node->node, impl->pending_request_process); + spa_clear_ptr(impl->pending_request_process, free); } node->info.change_mask |= PW_NODE_CHANGE_MASK_STATE; @@ -834,7 +837,9 @@ int pw_impl_node_set_io(struct pw_impl_node *this, uint32_t id, void *data, size res = spa_node_set_io(this->node, id, data, size); - if (res >= 0 && !SPA_RESULT_IS_ASYNC(res) && this->rt.position) + if (this->rt.position && + ((res >= 0 && !SPA_RESULT_IS_ASYNC(res)) || + this->rt.target.activation->client_version < 1)) this->rt.target.activation->active_driver_id = this->rt.position->clock.id; pw_log_debug("%p: set io: %s", this, spa_strerror(res)); @@ -1114,23 +1119,21 @@ static void check_properties(struct pw_impl_node *node) const char *str, *recalc_reason = NULL; struct spa_fraction frac; uint32_t value; - bool driver, trigger, transport, sync, async; + bool driver, trigger, sync, async; struct match match; match = MATCH_INIT(node); pw_context_conf_section_match_rules(context, "node.rules", &node->properties->dict, execute_match, &match); - if ((str = pw_properties_get(node->properties, PW_KEY_PRIORITY_DRIVER))) { - value = pw_properties_parse_int(str); - if (value != node->priority_driver) { - pw_log_debug("%p: priority driver %d -> %d", node, node->priority_driver, value); - node->priority_driver = value; - if (node->registered && node->driver) { - remove_driver(context, node); - insert_driver(context, node); - recalc_reason = "driver priority changed"; - } + value = pw_properties_get_uint32(node->properties, PW_KEY_PRIORITY_DRIVER, 0); + if (value != node->priority_driver) { + pw_log_debug("%p: priority driver %d -> %d", node, node->priority_driver, value); + node->priority_driver = value; + if (node->registered && node->driver) { + remove_driver(context, node); + insert_driver(context, node); + recalc_reason = "driver priority changed"; } } node->supports_lazy = pw_properties_get_uint32(node->properties, PW_KEY_NODE_SUPPORTS_LAZY, 0); @@ -1219,10 +1222,13 @@ static void check_properties(struct pw_impl_node *node) recalc_reason = "sync changed"; } - transport = pw_properties_get_bool(node->properties, PW_KEY_NODE_TRANSPORT, false); - if (transport != node->transport) { - pw_log_info("%p: transport %d -> %d", node, node->transport, transport); - node->transport = transport; + str = pw_properties_get(node->properties, PW_KEY_NODE_TRANSPORT); + if (str != NULL) { + node->transport = spa_atob(str) ? + PW_NODE_ACTIVATION_COMMAND_START : + PW_NODE_ACTIVATION_COMMAND_STOP; + pw_log_info("%p: transport %d", node, node->transport); + pw_properties_set(node->properties, PW_KEY_NODE_TRANSPORT, NULL); recalc_reason = "transport changed"; } async = pw_properties_get_bool(node->properties, PW_KEY_NODE_ASYNC, false); @@ -1230,6 +1236,7 @@ static void check_properties(struct pw_impl_node *node) if (async != node->async) { pw_log_info("%p: async %d -> %d", node, node->async, async); node->async = async; + SPA_FLAG_UPDATE(node->rt.target.activation->flags, PW_NODE_ACTIVATION_FLAG_ASYNC, async); } if ((str = pw_properties_get(node->properties, PW_KEY_MEDIA_CLASS)) != NULL && @@ -1277,13 +1284,11 @@ static void check_properties(struct pw_impl_node *node) } node->lock_quantum = pw_properties_get_bool(node->properties, PW_KEY_NODE_LOCK_QUANTUM, false); - if ((str = pw_properties_get(node->properties, PW_KEY_NODE_FORCE_QUANTUM))) { - if (spa_atou32(str, &value, 0) && - node->force_quantum != value) { - node->force_quantum = value; - node->stamp = ++context->stamp; - recalc_reason = "force quantum changed"; - } + value = pw_properties_get_uint32(node->properties, PW_KEY_NODE_FORCE_QUANTUM, 0); + if (node->force_quantum != value) { + node->force_quantum = value; + node->stamp = ++context->stamp; + recalc_reason = "force quantum changed"; } if ((str = pw_properties_get(node->properties, PW_KEY_NODE_RATE))) { @@ -1299,18 +1304,17 @@ static void check_properties(struct pw_impl_node *node) } node->lock_rate = pw_properties_get_bool(node->properties, PW_KEY_NODE_LOCK_RATE, false); - if ((str = pw_properties_get(node->properties, PW_KEY_NODE_FORCE_RATE))) { - if (spa_atou32(str, &value, 0)) { - if (value == 0) - value = node->rate.denom; - if (node->force_rate != value) { - pw_log_info("(%s-%u) force-rate:%u -> %u", node->name, - node->info.id, node->force_rate, value); - node->force_rate = value; - node->stamp = ++context->stamp; - recalc_reason = "force rate changed"; - } - } + value = pw_properties_get_uint32(node->properties, PW_KEY_NODE_FORCE_RATE, SPA_ID_INVALID); + if (value == 0) + value = node->rate.denom; + if (value == SPA_ID_INVALID) + value = 0; + if (node->force_rate != value) { + pw_log_info("(%s-%u) force-rate:%u -> %u", node->name, + node->info.id, node->force_rate, value); + node->force_rate = value; + node->stamp = ++context->stamp; + recalc_reason = "force rate changed"; } pw_log_debug("%p: driver:%d recalc:%s active:%d", node, node->driver, @@ -1354,7 +1358,7 @@ static inline void debug_xrun_target(struct pw_impl_node *driver, enum spa_log_level level = SPA_LOG_LEVEL_DEBUG; if ((suppressed = spa_ratelimit_test(&driver->rt.rate_limit, nsec)) >= 0) - level = SPA_LOG_LEVEL_WARN; + level = SPA_LOG_LEVEL_INFO; pw_log(level, "(%s-%u) xrun state:%p pending:%d/%d s:%"PRIu64" a:%"PRIu64" f:%"PRIu64 " waiting:%"PRIu64" process:%"PRIu64" status:%s (%d suppressed)", @@ -1375,7 +1379,7 @@ static inline void debug_xrun_graph(struct pw_impl_node *driver, uint64_t nsec, struct pw_node_target *t; if ((suppressed = spa_ratelimit_test(&driver->rt.rate_limit, nsec)) >= 0) - level = SPA_LOG_LEVEL_WARN; + level = SPA_LOG_LEVEL_INFO; pw_log(level, "(%s-%u) graph xrun %s (%d suppressed)", driver->name, driver->info.id, str_status(old_status), suppressed); @@ -1409,7 +1413,7 @@ static void debug_sync_timeout(struct pw_impl_node *driver, uint64_t nsec) int suppressed; if ((suppressed = spa_ratelimit_test(&driver->rt.rate_limit, nsec)) >= 0) - level = SPA_LOG_LEVEL_WARN; + level = SPA_LOG_LEVEL_INFO; pw_log(level, "(%s-%u) sync timeout, going to RUNNING (%d suppressed)", driver->name, driver->info.id, suppressed); @@ -1537,8 +1541,7 @@ int pw_impl_node_trigger(struct pw_impl_node *node) { uint64_t nsec = get_time_ns(node->rt.target.system); struct pw_node_target *t = &node->rt.target; - t->trigger(t, nsec); - return 0; + return t->trigger(t, nsec); } static void node_on_fd_events(struct spa_source *source) @@ -1901,16 +1904,16 @@ static void node_result(void *data, int seq, int res, uint32_t type, const void pw_impl_node_emit_result(node, seq, res, type, result); } -static void handle_request_process(struct pw_impl_node *node) +static void handle_request_process_command(struct pw_impl_node *node, const struct spa_command *command) { struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); if (node->driving) { pw_log_debug("request process %d %d", node->info.state, impl->pending_state); if (node->info.state == PW_NODE_STATE_RUNNING) { - spa_node_send_command(node->driver_node->node, - &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_RequestProcess)); + spa_node_send_command(node->driver_node->node, command); } else if (impl->pending_state == PW_NODE_STATE_RUNNING) { - node->pending_request_process++; + spa_clear_ptr(impl->pending_request_process, free); + impl->pending_request_process = (struct spa_command*)spa_pod_copy(&command->pod); } } } @@ -1932,9 +1935,18 @@ static void node_event(void *data, const struct spa_event *event) break; case SPA_NODE_EVENT_RequestProcess: if (!node->driving && !node->exported) { + struct spa_command *command; + size_t size = SPA_POD_SIZE(&event->pod); + + /* turn the event and all the arguments into a command */ + command = alloca(size); + memcpy(command, event, size); + command->body.body.type = SPA_TYPE_COMMAND_Node; + command->body.body.id = SPA_NODE_COMMAND_RequestProcess; + /* send the request process to the driver but only on the * server size */ - handle_request_process(node->driver_node); + handle_request_process_command(node->driver_node, command); } break; default: @@ -2048,12 +2060,13 @@ static int node_ready(void *data, int status) struct pw_impl_node *node = data; struct pw_impl_node *driver = node->driver_node; struct pw_node_activation *a = node->rt.target.activation; + struct pw_node_activation_state *state = &a->state[0]; struct spa_system *data_system = node->rt.target.system; struct pw_node_target *t, *reposition_target = NULL;; struct pw_impl_port *p; struct spa_io_clock *cl = &node->rt.position->clock; int sync_type, all_ready, update_sync, target_sync, old_status; - uint32_t owner[2], reposition_owner; + uint32_t owner[2], reposition_owner, pending; uint64_t min_timeout = UINT64_MAX, nsec; pw_log_trace_fp("%p: ready driver:%d exported:%d %p status:%d prepared:%d", node, @@ -2096,20 +2109,6 @@ static int node_ready(void *data, int status) } } - /* This update is done too late, the driver should do this - * before calling the ready callback so that it can use the new target - * duration and rate to schedule the next update. We do this here to - * help drivers that don't support this yet */ - if (SPA_UNLIKELY(cl->duration != cl->target_duration || - cl->rate.denom != cl->target_rate.denom)) { - pw_log_warn("driver %s did not update duration/rate (%"PRIu64"/%"PRIu64" %u/%u)", - node->name, - cl->duration, cl->target_duration, - cl->rate.denom, cl->target_rate.denom); - cl->duration = cl->target_duration; - cl->rate = cl->target_rate; - } - sync_type = check_updates(node, &reposition_owner); owner[0] = SPA_ATOMIC_LOAD(a->segment_owner[0]); owner[1] = SPA_ATOMIC_LOAD(a->segment_owner[1]); @@ -2117,6 +2116,7 @@ again: all_ready = sync_type == SYNC_CHECK; update_sync = !all_ready; target_sync = sync_type == SYNC_START ? true : false; + pending = 0; spa_list_for_each(t, &driver->rt.target_list, link) { struct pw_node_activation *ta = t->activation; @@ -2125,6 +2125,14 @@ again: ta->driver_id = driver->info.id; retry_status: pw_node_activation_state_reset(&ta->state[0]); + + if (ta->active_driver_id != ta->driver_id) { + pw_log_trace_fp("%p: (%s-%u) %d waiting for driver %d<>%d", t->node, + t->name, t->id, ta->status, + ta->active_driver_id, ta->driver_id); + continue; + } + /* we don't change the state of inactive nodes and don't use them * for reposition. The pending will be at least 1 and they might * get decremented to 0 but since the status is inactive, we don't @@ -2138,6 +2146,9 @@ retry_status: if (SPA_UNLIKELY(!SPA_ATOMIC_CAS(ta->status, old_status, PW_NODE_ACTIVATION_NOT_TRIGGERED))) goto retry_status; + if (!SPA_FLAG_IS_SET(ta->flags, PW_NODE_ACTIVATION_FLAG_ASYNC)) + pending++; + if (old_status == PW_NODE_ACTIVATION_TRIGGERED || old_status == PW_NODE_ACTIVATION_AWAKE) { update_xrun_stats(ta, 1, nsec / 1000, 0); @@ -2178,6 +2189,7 @@ retry_status: reposition_target = NULL; goto again; } + state->pending = pending; update_position(node, all_ready, nsec); @@ -2450,6 +2462,7 @@ void pw_impl_node_destroy(struct pw_impl_node *node) pw_work_queue_cancel(impl->work, node, SPA_ID_INVALID); pw_properties_free(node->properties); + spa_clear_ptr(impl->pending_request_process, free); clear_info(node); @@ -2846,7 +2859,7 @@ int pw_impl_node_send_command(struct pw_impl_node *node, const struct spa_comman switch (id) { case SPA_NODE_COMMAND_RequestProcess: - handle_request_process(node); + handle_request_process_command(node, command); break; default: res = spa_node_send_command(node->node, command); diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index bbfa9ede..4356604a 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -19,6 +19,7 @@ #include <spa/pod/dynamic.h> #include <spa/debug/pod.h> +#define PW_API_PORT_IMPL SPA_EXPORT #include "pipewire/impl.h" #include "pipewire/private.h" @@ -1282,20 +1283,18 @@ int pw_impl_port_add(struct pw_impl_port *port, struct pw_impl_node *node) channel_names = pw_properties_get(nprops, PW_KEY_NODE_CHANNELNAMES); if (channel_names != NULL) { - struct spa_json it[2]; + struct spa_json it[1]; char v[256]; uint32_t i; - spa_json_init(&it[0], channel_names, strlen(channel_names)); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], channel_names, strlen(channel_names)); + if (spa_json_begin_array_relax(&it[0], channel_names, strlen(channel_names)) > 0) { + for (i = 0; i < port->port_id + 1; i++) + if (spa_json_get_string(&it[0], v, sizeof(v)) <= 0) + break; - for (i = 0; i < port->port_id + 1; i++) - if (spa_json_get_string(&it[1], v, sizeof(v)) <= 0) - break; - - if (i == port->port_id + 1 && strlen(v) > 0) - snprintf(position, sizeof(position), "%s", v); + if (i == port->port_id + 1 && strlen(v) > 0) + snprintf(position, sizeof(position), "%s", v); + } } if (pw_properties_get(port->properties, PW_KEY_PORT_NAME) == NULL) { diff --git a/src/pipewire/keys.h b/src/pipewire/keys.h index 600b2fa6..de4a9d40 100644 --- a/src/pipewire/keys.h +++ b/src/pipewire/keys.h @@ -104,9 +104,11 @@ extern "C" { * default pipewire-0, overwritten by * env(PIPEWIRE_REMOTE). May also be * a SPA-JSON array of sockets, to be tried - * in order. */ + * in order. The "internal" remote name and + * "generic" intention connects to the local + * PipeWire instance. */ #define PW_KEY_REMOTE_INTENTION "remote.intention" /**< The intention of the remote connection, - * "generic", "screencast" */ + * "generic", "screencast", "manager" */ /** application keys */ #define PW_KEY_APP_NAME "application.name" /**< application name. Ex: "Totem Music Player" */ diff --git a/src/pipewire/link.h b/src/pipewire/link.h index ef96dfe2..316fb044 100644 --- a/src/pipewire/link.h +++ b/src/pipewire/link.h @@ -36,6 +36,11 @@ extern "C" { #define PW_VERSION_LINK 3 struct pw_link; +#ifndef PW_API_LINK_IMPL +#define PW_API_LINK_IMPL static inline +#endif + + /** \enum pw_link_state The different link states */ enum pw_link_state { PW_LINK_STATE_ERROR = -2, /**< the link is in error */ @@ -108,16 +113,17 @@ struct pw_link_methods { void *data); }; -#define pw_link_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - spa_interface_call_res((struct spa_interface*)o, \ - struct pw_link_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define pw_link_add_listener(c,...) pw_link_method(c,add_listener,0,__VA_ARGS__) +/** \copydoc pw_link_methods.add_listener + * \sa pw_link_methods.add_listener */ +PW_API_LINK_IMPL int pw_link_add_listener(struct pw_link *object, + struct spa_hook *listener, + const struct pw_link_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_link, (struct spa_interface*)object, add_listener, 0, + listener, events, data); +} /** * \} diff --git a/src/pipewire/loop.c b/src/pipewire/loop.c index 64baaa7f..2e7fb47d 100644 --- a/src/pipewire/loop.c +++ b/src/pipewire/loop.c @@ -8,6 +8,7 @@ #include <spa/utils/names.h> #include <spa/utils/result.h> +#define PW_API_LOOP_IMPL SPA_EXPORT #include <pipewire/pipewire.h> #include <pipewire/private.h> #include <pipewire/loop.h> diff --git a/src/pipewire/loop.h b/src/pipewire/loop.h index 2ec26f5e..9de1cbf3 100644 --- a/src/pipewire/loop.h +++ b/src/pipewire/loop.h @@ -34,6 +34,10 @@ struct pw_loop { const char *name; }; +#ifndef PW_API_LOOP_IMPL +#define PW_API_LOOP_IMPL static inline +#endif + struct pw_loop * pw_loop_new(const struct spa_dict *props); @@ -42,27 +46,103 @@ pw_loop_destroy(struct pw_loop *loop); int pw_loop_set_name(struct pw_loop *loop, const char *name); -#define pw_loop_add_source(l,...) spa_loop_add_source((l)->loop,__VA_ARGS__) -#define pw_loop_update_source(l,...) spa_loop_update_source((l)->loop,__VA_ARGS__) -#define pw_loop_remove_source(l,...) spa_loop_remove_source((l)->loop,__VA_ARGS__) -#define pw_loop_invoke(l,...) spa_loop_invoke((l)->loop,__VA_ARGS__) - -#define pw_loop_get_fd(l) spa_loop_control_get_fd((l)->control) -#define pw_loop_add_hook(l,...) spa_loop_control_add_hook((l)->control,__VA_ARGS__) -#define pw_loop_enter(l) spa_loop_control_enter((l)->control) -#define pw_loop_leave(l) spa_loop_control_leave((l)->control) -#define pw_loop_iterate(l,...) spa_loop_control_iterate_fast((l)->control,__VA_ARGS__) - -#define pw_loop_add_io(l,...) spa_loop_utils_add_io((l)->utils,__VA_ARGS__) -#define pw_loop_update_io(l,...) spa_loop_utils_update_io((l)->utils,__VA_ARGS__) -#define pw_loop_add_idle(l,...) spa_loop_utils_add_idle((l)->utils,__VA_ARGS__) -#define pw_loop_enable_idle(l,...) spa_loop_utils_enable_idle((l)->utils,__VA_ARGS__) -#define pw_loop_add_event(l,...) spa_loop_utils_add_event((l)->utils,__VA_ARGS__) -#define pw_loop_signal_event(l,...) spa_loop_utils_signal_event((l)->utils,__VA_ARGS__) -#define pw_loop_add_timer(l,...) spa_loop_utils_add_timer((l)->utils,__VA_ARGS__) -#define pw_loop_update_timer(l,...) spa_loop_utils_update_timer((l)->utils,__VA_ARGS__) -#define pw_loop_add_signal(l,...) spa_loop_utils_add_signal((l)->utils,__VA_ARGS__) -#define pw_loop_destroy_source(l,...) spa_loop_utils_destroy_source((l)->utils,__VA_ARGS__) +PW_API_LOOP_IMPL int pw_loop_add_source(struct pw_loop *object, struct spa_source *source) +{ + return spa_loop_add_source(object->loop, source); +} +PW_API_LOOP_IMPL int pw_loop_update_source(struct pw_loop *object, struct spa_source *source) +{ + return spa_loop_update_source(object->loop, source); +} +PW_API_LOOP_IMPL int pw_loop_remove_source(struct pw_loop *object, struct spa_source *source) +{ + return spa_loop_remove_source(object->loop, source); +} +PW_API_LOOP_IMPL int pw_loop_invoke(struct pw_loop *object, + spa_invoke_func_t func, uint32_t seq, const void *data, + size_t size, bool block, void *user_data) +{ + return spa_loop_invoke(object->loop, func, seq, data, size, block, user_data); +} + +PW_API_LOOP_IMPL int pw_loop_get_fd(struct pw_loop *object) +{ + return spa_loop_control_get_fd(object->control); +} +PW_API_LOOP_IMPL void pw_loop_add_hook(struct pw_loop *object, + struct spa_hook *hook, const struct spa_loop_control_hooks *hooks, + void *data) +{ + spa_loop_control_add_hook(object->control, hook, hooks, data); +} +PW_API_LOOP_IMPL void pw_loop_enter(struct pw_loop *object) +{ + spa_loop_control_enter(object->control); +} +PW_API_LOOP_IMPL void pw_loop_leave(struct pw_loop *object) +{ + spa_loop_control_leave(object->control); +} +PW_API_LOOP_IMPL int pw_loop_iterate(struct pw_loop *object, + int timeout) +{ + return spa_loop_control_iterate_fast(object->control, timeout); +} + +PW_API_LOOP_IMPL struct spa_source * +pw_loop_add_io(struct pw_loop *object, int fd, uint32_t mask, + bool close, spa_source_io_func_t func, void *data) +{ + return spa_loop_utils_add_io(object->utils, fd, mask, close, func, data); +} +PW_API_LOOP_IMPL int pw_loop_update_io(struct pw_loop *object, + struct spa_source *source, uint32_t mask) +{ + return spa_loop_utils_update_io(object->utils, source, mask); +} +PW_API_LOOP_IMPL struct spa_source * +pw_loop_add_idle(struct pw_loop *object, bool enabled, + spa_source_idle_func_t func, void *data) +{ + return spa_loop_utils_add_idle(object->utils, enabled, func, data); +} +PW_API_LOOP_IMPL int pw_loop_enable_idle(struct pw_loop *object, + struct spa_source *source, bool enabled) +{ + return spa_loop_utils_enable_idle(object->utils, source, enabled); +} +PW_API_LOOP_IMPL struct spa_source * +pw_loop_add_event(struct pw_loop *object, spa_source_event_func_t func, void *data) +{ + return spa_loop_utils_add_event(object->utils, func, data); +} +PW_API_LOOP_IMPL int pw_loop_signal_event(struct pw_loop *object, + struct spa_source *source) +{ + return spa_loop_utils_signal_event(object->utils, source); +} +PW_API_LOOP_IMPL struct spa_source * +pw_loop_add_timer(struct pw_loop *object, spa_source_timer_func_t func, void *data) +{ + return spa_loop_utils_add_timer(object->utils, func, data); +} +PW_API_LOOP_IMPL int pw_loop_update_timer(struct pw_loop *object, + struct spa_source *source, struct timespec *value, + struct timespec *interval, bool absolute) +{ + return spa_loop_utils_update_timer(object->utils, source, value, interval, absolute); +} +PW_API_LOOP_IMPL struct spa_source * +pw_loop_add_signal(struct pw_loop *object, int signal_number, + spa_source_signal_func_t func, void *data) +{ + return spa_loop_utils_add_signal(object->utils, signal_number, func, data); +} +PW_API_LOOP_IMPL void pw_loop_destroy_source(struct pw_loop *object, + struct spa_source *source) +{ + return spa_loop_utils_destroy_source(object->utils, source); +} /** * \} diff --git a/src/pipewire/map.h b/src/pipewire/map.h index 39d6d084..fb00ddf6 100644 --- a/src/pipewire/map.h +++ b/src/pipewire/map.h @@ -15,6 +15,10 @@ extern "C" { #include <spa/utils/defs.h> #include <pipewire/array.h> +#ifndef PW_API_MAP +#define PW_API_MAP static inline +#endif + /** \defgroup pw_map Map * * \brief A map that holds pointers to objects indexed by id @@ -93,7 +97,7 @@ struct pw_map { * \param size the initial size of the map * \param extend the amount to bytes to grow the map with when needed */ -static inline void pw_map_init(struct pw_map *map, size_t size, size_t extend) +PW_API_MAP void pw_map_init(struct pw_map *map, size_t size, size_t extend) { pw_array_init(&map->items, extend * sizeof(union pw_map_item)); pw_array_ensure_size(&map->items, size * sizeof(union pw_map_item)); @@ -103,7 +107,7 @@ static inline void pw_map_init(struct pw_map *map, size_t size, size_t extend) /** Clear a map and free the data storage. All previously returned ids * must be treated as invalid. */ -static inline void pw_map_clear(struct pw_map *map) +PW_API_MAP void pw_map_clear(struct pw_map *map) { pw_array_clear(&map->items); } @@ -111,7 +115,7 @@ static inline void pw_map_clear(struct pw_map *map) /** Reset a map but keep previously allocated storage. All previously * returned ids must be treated as invalid. */ -static inline void pw_map_reset(struct pw_map *map) +PW_API_MAP void pw_map_reset(struct pw_map *map) { pw_array_reset(&map->items); map->free_list = SPA_ID_INVALID; @@ -123,7 +127,7 @@ static inline void pw_map_reset(struct pw_map *map) * \return the id where the item was inserted or SPA_ID_INVALID when the * item can not be inserted. */ -static inline uint32_t pw_map_insert_new(struct pw_map *map, void *data) +PW_API_MAP uint32_t pw_map_insert_new(struct pw_map *map, void *data) { union pw_map_item *start, *item; uint32_t id; @@ -150,7 +154,7 @@ static inline uint32_t pw_map_insert_new(struct pw_map *map, void *data) * \param data the data to insert * \return 0 on success, -ENOSPC value when the index is invalid or a negative errno */ -static inline int pw_map_insert_at(struct pw_map *map, uint32_t id, void *data) +PW_API_MAP int pw_map_insert_at(struct pw_map *map, uint32_t id, void *data) { size_t size = pw_map_get_size(map); union pw_map_item *item; @@ -175,7 +179,7 @@ static inline int pw_map_insert_at(struct pw_map *map, uint32_t id, void *data) * \param map the map to remove from * \param id the index to remove */ -static inline void pw_map_remove(struct pw_map *map, uint32_t id) +PW_API_MAP void pw_map_remove(struct pw_map *map, uint32_t id) { if (pw_map_id_is_free(map, id)) return; @@ -189,7 +193,7 @@ static inline void pw_map_remove(struct pw_map *map, uint32_t id) * \param id the index to look at * \return the item at \a id or NULL when no such item exists */ -static inline void *pw_map_lookup(const struct pw_map *map, uint32_t id) +PW_API_MAP void *pw_map_lookup(const struct pw_map *map, uint32_t id) { if (SPA_LIKELY(pw_map_check_id(map, id))) { union pw_map_item *item = pw_map_get_item(map, id); @@ -207,7 +211,7 @@ static inline void *pw_map_lookup(const struct pw_map *map, uint32_t id) * \param data data to pass to \a func * \return the result of the last call to \a func or 0 when all callbacks returned 0. */ -static inline int pw_map_for_each(const struct pw_map *map, +PW_API_MAP int pw_map_for_each(const struct pw_map *map, int (*func) (void *item_data, void *data), void *data) { union pw_map_item *item; diff --git a/src/pipewire/mem.c b/src/pipewire/mem.c index 5a2bcc8a..f5cc198f 100644 --- a/src/pipewire/mem.c +++ b/src/pipewire/mem.c @@ -18,6 +18,7 @@ #include <spa/utils/list.h> #include <spa/buffer/buffer.h> +#define PW_API_MEM SPA_EXPORT #include <pipewire/log.h> #include <pipewire/map.h> #include <pipewire/mem.h> diff --git a/src/pipewire/mem.h b/src/pipewire/mem.h index 8ce57bd9..525e5852 100644 --- a/src/pipewire/mem.h +++ b/src/pipewire/mem.h @@ -11,6 +11,10 @@ extern "C" { #endif +#ifndef PW_API_MEM +#define PW_API_MEM static inline +#endif + /** \defgroup pw_memblock Memory Blocks * Memory allocation and pools. */ @@ -123,7 +127,7 @@ struct pw_memblock * pw_mempool_import(struct pw_mempool *pool, void pw_memblock_free(struct pw_memblock *mem); /** Unref a memblock */ -static inline void pw_memblock_unref(struct pw_memblock *mem) +PW_API_MEM void pw_memblock_unref(struct pw_memblock *mem) { if (--mem->ref == 0) pw_memblock_free(mem); @@ -173,7 +177,7 @@ struct pw_map_range { /** Calculate parameters to mmap() memory into \a range so that * \a size bytes at \a offset can be mapped with mmap(). */ -static inline void pw_map_range_init(struct pw_map_range *range, +PW_API_MEM void pw_map_range_init(struct pw_map_range *range, uint32_t offset, uint32_t size, uint32_t page_size) { diff --git a/src/pipewire/module.h b/src/pipewire/module.h index bd4eca61..f5531af8 100644 --- a/src/pipewire/module.h +++ b/src/pipewire/module.h @@ -29,6 +29,10 @@ extern "C" { #define PW_VERSION_MODULE 3 struct pw_module; +#ifndef PW_API_MODULE_IMPL +#define PW_API_MODULE_IMPL static inline +#endif + /** The module information. Extra information can be added in later versions */ struct pw_module_info { uint32_t id; /**< id of the global */ @@ -81,16 +85,17 @@ struct pw_module_methods { void *data); }; -#define pw_module_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - spa_interface_call_res((struct spa_interface*)o, \ - struct pw_module_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define pw_module_add_listener(c,...) pw_module_method(c,add_listener,0,__VA_ARGS__) +/** \copydoc pw_module_methods.add_listener + * \sa pw_module_methods.add_listener */ +PW_API_MODULE_IMPL int pw_module_add_listener(struct pw_module *object, + struct spa_hook *listener, + const struct pw_module_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_module, (struct spa_interface*)object, add_listener, 0, + listener, events, data); +} /** * \} diff --git a/src/pipewire/node.h b/src/pipewire/node.h index 87ba1a06..1ee9d212 100644 --- a/src/pipewire/node.h +++ b/src/pipewire/node.h @@ -34,6 +34,10 @@ extern "C" { #define PW_VERSION_NODE 3 struct pw_node; +#ifndef PW_API_NODE_IMPL +#define PW_API_NODE_IMPL static inline +#endif + /** \enum pw_node_state The different node states */ enum pw_node_state { PW_NODE_STATE_ERROR = -1, /**< error state */ @@ -179,21 +183,52 @@ struct pw_node_methods { int (*send_command) (void *object, const struct spa_command *command); }; -#define pw_node_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - spa_interface_call_res((struct spa_interface*)o, \ - struct pw_node_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -/** Node */ -#define pw_node_add_listener(c,...) pw_node_method(c,add_listener,0,__VA_ARGS__) -#define pw_node_subscribe_params(c,...) pw_node_method(c,subscribe_params,0,__VA_ARGS__) -#define pw_node_enum_params(c,...) pw_node_method(c,enum_params,0,__VA_ARGS__) -#define pw_node_set_param(c,...) pw_node_method(c,set_param,0,__VA_ARGS__) -#define pw_node_send_command(c,...) pw_node_method(c,send_command,0,__VA_ARGS__) + +/** \copydoc pw_node_methods.add_listener + * \sa pw_node_methods.add_listener */ +PW_API_NODE_IMPL int pw_node_add_listener(struct pw_node *object, + struct spa_hook *listener, + const struct pw_node_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_node, (struct spa_interface*)object, add_listener, 0, + listener, events, data); +} +/** \copydoc pw_node_methods.subscribe_params + * \sa pw_node_methods.subscribe_params */ +PW_API_NODE_IMPL int pw_node_subscribe_params(struct pw_node *object, uint32_t *ids, uint32_t n_ids) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_node, (struct spa_interface*)object, subscribe_params, 0, + ids, n_ids); +} +/** \copydoc pw_node_methods.enum_params + * \sa pw_node_methods.enum_params */ +PW_API_NODE_IMPL int pw_node_enum_params(struct pw_node *object, + int seq, uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_node, (struct spa_interface*)object, enum_params, 0, + seq, id, start, num, filter); +} +/** \copydoc pw_node_methods.set_param + * \sa pw_node_methods.set_param */ +PW_API_NODE_IMPL int pw_node_set_param(struct pw_node *object, uint32_t id, uint32_t flags, + const struct spa_pod *param) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_node, (struct spa_interface*)object, set_param, 0, + id, flags, param); +} +/** \copydoc pw_node_methods.send_command + * \sa pw_node_methods.send_command */ +PW_API_NODE_IMPL int pw_node_send_command(struct pw_node *object, const struct spa_command *command) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_node, (struct spa_interface*)object, send_command, 0, command); +} /** * \} diff --git a/src/pipewire/pipewire.c b/src/pipewire/pipewire.c index 74545d61..4451fa52 100644 --- a/src/pipewire/pipewire.c +++ b/src/pipewire/pipewire.c @@ -533,7 +533,10 @@ void pw_init(int *argc, char **argv[]) str = "true"; items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_COLORS, str); } - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_TIMESTAMP, "true"); + if ((str = getenv("PIPEWIRE_LOG_TIMESTAMP")) != NULL) + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_TIMESTAMP, str); + else + items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_TIMESTAMP, "local"); if ((str = getenv("PIPEWIRE_LOG_LINE")) == NULL || spa_atob(str)) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_LINE, "true"); snprintf(level, sizeof(level), "%d", pw_log_level); diff --git a/src/pipewire/port.h b/src/pipewire/port.h index d0ada8b7..ea4cb33b 100644 --- a/src/pipewire/port.h +++ b/src/pipewire/port.h @@ -34,6 +34,10 @@ extern "C" { #define PW_VERSION_PORT 3 struct pw_port; +#ifndef PW_API_PORT_IMPL +#define PW_API_PORT_IMPL static inline +#endif + /** The direction of a port */ #define pw_direction spa_direction #define PW_DIRECTION_INPUT SPA_DIRECTION_INPUT @@ -141,18 +145,35 @@ struct pw_port_methods { const struct spa_pod *filter); }; -#define pw_port_method(o,method,version,...) \ -({ \ - int _res = -ENOTSUP; \ - spa_interface_call_res((struct spa_interface*)o, \ - struct pw_port_methods, _res, \ - method, version, ##__VA_ARGS__); \ - _res; \ -}) - -#define pw_port_add_listener(c,...) pw_port_method(c,add_listener,0,__VA_ARGS__) -#define pw_port_subscribe_params(c,...) pw_port_method(c,subscribe_params,0,__VA_ARGS__) -#define pw_port_enum_params(c,...) pw_port_method(c,enum_params,0,__VA_ARGS__) +/** \copydoc pw_port_methods.add_listener + * \sa pw_port_methods.add_listener */ +PW_API_PORT_IMPL int pw_port_add_listener(struct pw_port *object, + struct spa_hook *listener, + const struct pw_port_events *events, + void *data) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_port, (struct spa_interface*)object, add_listener, 0, + listener, events, data); +} +/** \copydoc pw_port_methods.subscribe_params + * \sa pw_port_methods.subscribe_params */ +PW_API_PORT_IMPL int pw_port_subscribe_params(struct pw_port *object, uint32_t *ids, uint32_t n_ids) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_port, (struct spa_interface*)object, subscribe_params, 0, + ids, n_ids); +} +/** \copydoc pw_port_methods.enum_params + * \sa pw_port_methods.enum_params */ +PW_API_PORT_IMPL int pw_port_enum_params(struct pw_port *object, + int seq, uint32_t id, uint32_t start, uint32_t num, + const struct spa_pod *filter) +{ + return spa_api_method_r(int, -ENOTSUP, + pw_port, (struct spa_interface*)object, enum_params, 0, + seq, id, start, num, filter); +} /** * \} diff --git a/src/pipewire/private.h b/src/pipewire/private.h index 5a7bb42b..cddd3b82 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -539,7 +539,7 @@ struct pw_node_target { struct pw_node_activation *activation; struct spa_system *system; int fd; - void (*trigger)(struct pw_node_target *t, uint64_t nsec); + int (*trigger)(struct pw_node_target *t, uint64_t nsec); unsigned int active:1; unsigned int added:1; }; @@ -593,10 +593,12 @@ struct pw_node_activation { struct pw_node_activation_state state[2]; /* one current state and one next state, * as version flag */ - uint64_t signal_time; - uint64_t awake_time; - uint64_t finish_time; - uint64_t prev_signal_time; + uint64_t signal_time; /* time at which the node was triggered (i.e. marked + * as ready to start processing in the current loop + * iteration) */ + uint64_t awake_time; /* time at which processing actually started */ + uint64_t finish_time; /* time at which processing was completed */ + uint64_t prev_signal_time; /* previous time at which the node was triggered */ /* updates */ struct spa_io_segment reposition; /* reposition info, used when driver reposition_owner @@ -619,6 +621,7 @@ struct pw_node_activation { uint32_t driver_id; /* the current node driver id */ #define PW_NODE_ACTIVATION_FLAG_NONE 0 #define PW_NODE_ACTIVATION_FLAG_PROFILER (1<<0) /* the profiler is running */ +#define PW_NODE_ACTIVATION_FLAG_ASYNC (1<<1) /* the node is async */ uint32_t flags; /* extra flags */ struct spa_io_position position; /* contains current position and segment info. * extra info is updated by nodes that have set @@ -653,44 +656,53 @@ static inline uint64_t get_time_ns(struct spa_system *system) /* called from data-loop decrement the dependency counter of the target and when * there are no more dependencies, trigger the node. */ -static inline void trigger_target_v1(struct pw_node_target *t, uint64_t nsec) +static inline int trigger_target_v1(struct pw_node_target *t, uint64_t nsec) { struct pw_node_activation *a = t->activation; struct pw_node_activation_state *state = &a->state[0]; int32_t pending = SPA_ATOMIC_DEC(state->pending); + int res = pending == 0, r; pw_log_trace_fp("%p: (%s-%u) state:%p pending:%d/%d", t->node, t->name, t->id, state, pending, state->required); - if (pending == 0) { - if (SPA_ATOMIC_CAS(a->status, + if (res) { + if (SPA_LIKELY(SPA_ATOMIC_CAS(a->status, PW_NODE_ACTIVATION_NOT_TRIGGERED, - PW_NODE_ACTIVATION_TRIGGERED)) { + PW_NODE_ACTIVATION_TRIGGERED))) { a->signal_time = nsec; - if (SPA_UNLIKELY(spa_system_eventfd_write(t->system, t->fd, 1) < 0)) - pw_log_warn("%p: write failed %m", t->node); + if (SPA_UNLIKELY((r = spa_system_eventfd_write(t->system, t->fd, 1)) < 0)) { + pw_log_warn("%p: write failed %s", t->node, spa_strerror(r)); + res = r; + } } else { pw_log_trace_fp("%p: (%s-%u) not ready %d", t->node, t->name, t->id, a->status); + res = -EIO; } } + return res; } -static inline void trigger_target_v0(struct pw_node_target *t, uint64_t nsec) +static inline int trigger_target_v0(struct pw_node_target *t, uint64_t nsec) { struct pw_node_activation *a = t->activation; struct pw_node_activation_state *state = &a->state[0]; int32_t pending = SPA_ATOMIC_DEC(state->pending); + int res = pending == 0, r; pw_log_trace_fp("%p: (%s-%u) state:%p pending:%d/%d", t->node, t->name, t->id, state, pending, state->required); - if (pending == 0) { + if (res) { SPA_ATOMIC_STORE(a->status, PW_NODE_ACTIVATION_TRIGGERED); a->signal_time = nsec; - if (SPA_UNLIKELY(spa_system_eventfd_write(t->system, t->fd, 1) < 0)) - pw_log_warn("%p: write failed %m", t->node); + if (SPA_UNLIKELY((r = spa_system_eventfd_write(t->system, t->fd, 1)) < 0)) { + res = r; + pw_log_warn("%p: write failed %s", t->node, spa_strerror(r)); + } } + return res; } struct pw_node_peer { @@ -783,10 +795,11 @@ struct pw_impl_node { unsigned int can_suspend:1; unsigned int checked; /**< for sorting */ unsigned int sync:1; /**< the sync-groups are active */ - unsigned int transport:1; /**< the transport is active */ unsigned int async:1; /**< async processing, one cycle latency */ unsigned int lazy:1; /**< the graph is lazy scheduling */ + uint32_t transport; /**< latest transport request */ + uint32_t port_user_data_size; /**< extra size for port user data */ struct spa_list driver_link; @@ -844,8 +857,6 @@ struct pw_impl_node { uint64_t driver_start; uint64_t elapsed; /* elapsed time in playing */ - uint32_t pending_request_process; - void *user_data; /**< extra user data */ }; diff --git a/src/pipewire/properties.c b/src/pipewire/properties.c index c7fa6b78..de81088b 100644 --- a/src/pipewire/properties.c +++ b/src/pipewire/properties.c @@ -12,6 +12,7 @@ #include <spa/utils/result.h> #include <spa/debug/log.h> +#define PW_API_PROPERTIES SPA_EXPORT #include "pipewire/array.h" #include "pipewire/log.h" #include "pipewire/utils.h" @@ -210,39 +211,34 @@ exit_noupdate: static int update_string(struct pw_properties *props, const char *str, size_t size, int *count, struct spa_error_location *loc) { - struct spa_json it[2]; + struct spa_json it[1]; char key[1024]; struct spa_error_location el; bool err; - int res, cnt = 0; + int len, res, cnt = 0; struct properties changes; + const char *value; + + if ((res = spa_json_begin_object_relax(&it[0], str, size)) <= 0) + return res; if (props) properties_init(&changes, 16); - spa_json_init(&it[0], str, size); - if (spa_json_enter_object(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], str, size); - - while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) { - int len; - const char *value; + while ((len = spa_json_object_next(&it[0], key, sizeof(key), &value)) > 0) { char *val = NULL; - if ((len = spa_json_next(&it[1], &value)) <= 0) - break; - if (spa_json_is_null(value, len)) val = NULL; else { if (spa_json_is_container(value, len)) - len = spa_json_container_len(&it[1], value, len); + len = spa_json_container_len(&it[0], value, len); if (len <= 0) break; if (props) { if ((val = malloc(len+1)) == NULL) { - it[1].state = SPA_JSON_ERROR_FLAG; + it[0].state = SPA_JSON_ERROR_FLAG; break; } spa_json_parse_stringn(value, len, val, len+1); @@ -257,12 +253,12 @@ static int update_string(struct pw_properties *props, const char *str, size_t si } /* item changed or added, apply changes later */ if ((errno = -add_item(&changes, key, false, val, true) < 0)) { - it[1].state = SPA_JSON_ERROR_FLAG; + it[0].state = SPA_JSON_ERROR_FLAG; break; } } } - if ((err = spa_json_get_error(&it[1], str, &el))) { + if ((err = spa_json_get_error(&it[0], str, &el))) { if (loc == NULL) spa_debug_log_error_location(pw_log_get(), SPA_LOG_LEVEL_WARN, &el, "error parsing more than %d properties: %s", @@ -888,14 +884,12 @@ static int dump(struct dump_config *c, int indent, struct spa_json *it, const ch fprintf(file, "{"); spa_json_enter(it, &sub); indent += c->indent; - while (spa_json_get_string(&sub, key, sizeof(key)) > 0) { + while ((len = spa_json_object_next(&sub, key, sizeof(key), &value)) > 0) { fprintf(file, "%s%s%*s", count++ > 0 ? "," : "", c->sep, indent, ""); encode_string(c, KEY(c), key, strlen(key), NORMAL(c)); fprintf(file, ": "); - if ((len = spa_json_next(&sub, &value)) <= 0) - break; dump(c, indent, &sub, value, len); } indent -= c->indent; diff --git a/src/pipewire/properties.h b/src/pipewire/properties.h index dec816a8..769198dc 100644 --- a/src/pipewire/properties.h +++ b/src/pipewire/properties.h @@ -15,6 +15,10 @@ extern "C" { #include <spa/utils/dict.h> #include <spa/utils/string.h> +#ifndef PW_API_PROPERTIES +#define PW_API_PROPERTIES static inline +#endif + /** \defgroup pw_properties Properties * * Properties are used to pass around arbitrary key/value pairs. @@ -101,7 +105,7 @@ pw_properties_fetch_int64(const struct pw_properties *properties, const char *ke int pw_properties_fetch_bool(const struct pw_properties *properties, const char *key, bool *value); -static inline uint32_t +PW_API_PROPERTIES uint32_t pw_properties_get_uint32(const struct pw_properties *properties, const char *key, uint32_t deflt) { uint32_t val = deflt; @@ -109,7 +113,7 @@ pw_properties_get_uint32(const struct pw_properties *properties, const char *key return val; } -static inline int32_t +PW_API_PROPERTIES int32_t pw_properties_get_int32(const struct pw_properties *properties, const char *key, int32_t deflt) { int32_t val = deflt; @@ -117,7 +121,7 @@ pw_properties_get_int32(const struct pw_properties *properties, const char *key, return val; } -static inline uint64_t +PW_API_PROPERTIES uint64_t pw_properties_get_uint64(const struct pw_properties *properties, const char *key, uint64_t deflt) { uint64_t val = deflt; @@ -125,7 +129,7 @@ pw_properties_get_uint64(const struct pw_properties *properties, const char *key return val; } -static inline int64_t +PW_API_PROPERTIES int64_t pw_properties_get_int64(const struct pw_properties *properties, const char *key, int64_t deflt) { int64_t val = deflt; @@ -134,7 +138,7 @@ pw_properties_get_int64(const struct pw_properties *properties, const char *key, } -static inline bool +PW_API_PROPERTIES bool pw_properties_get_bool(const struct pw_properties *properties, const char *key, bool deflt) { bool val = deflt; @@ -152,31 +156,31 @@ pw_properties_iterate(const struct pw_properties *properties, void **state); #define PW_PROPERTIES_FLAG_COLORS (1<<4) int pw_properties_serialize_dict(FILE *f, const struct spa_dict *dict, uint32_t flags); -static inline bool pw_properties_parse_bool(const char *value) { +PW_API_PROPERTIES bool pw_properties_parse_bool(const char *value) { return spa_atob(value); } -static inline int pw_properties_parse_int(const char *value) { +PW_API_PROPERTIES int pw_properties_parse_int(const char *value) { int v; return spa_atoi32(value, &v, 0) ? v: 0; } -static inline int64_t pw_properties_parse_int64(const char *value) { +PW_API_PROPERTIES int64_t pw_properties_parse_int64(const char *value) { int64_t v; return spa_atoi64(value, &v, 0) ? v : 0; } -static inline uint64_t pw_properties_parse_uint64(const char *value) { +PW_API_PROPERTIES uint64_t pw_properties_parse_uint64(const char *value) { uint64_t v; return spa_atou64(value, &v, 0) ? v : 0; } -static inline float pw_properties_parse_float(const char *value) { +PW_API_PROPERTIES float pw_properties_parse_float(const char *value) { float v; return spa_atof(value, &v) ? v : 0.0f; } -static inline double pw_properties_parse_double(const char *value) { +PW_API_PROPERTIES double pw_properties_parse_double(const char *value) { double v; return spa_atod(value, &v) ? v : 0.0; } diff --git a/src/pipewire/settings.c b/src/pipewire/settings.c index 350efc69..597e686a 100644 --- a/src/pipewire/settings.c +++ b/src/pipewire/settings.c @@ -90,14 +90,13 @@ static bool uint32_array_contains(uint32_t *vals, uint32_t n_vals, uint32_t val) static uint32_t parse_uint32_array(const char *str, uint32_t *vals, uint32_t max, uint32_t def) { uint32_t count = 0, r; - struct spa_json it[2]; + struct spa_json it[1]; char v[256]; - spa_json_init(&it[0], str, strlen(str)); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], str, strlen(str)); + if (spa_json_begin_array_relax(&it[0], str, strlen(str)) <= 0) + return 0; - while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 && + while (spa_json_get_string(&it[0], v, sizeof(v)) > 0 && count < max) { if (spa_atou32(v, &r, 0)) vals[count++] = r; diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index a4f33ebc..5e5aed3b 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -115,11 +115,12 @@ struct stream { uint64_t change_mask_all; struct spa_node_info info; -#define NODE_PropInfo 0 -#define NODE_Props 1 -#define NODE_EnumFormat 2 -#define NODE_Format 3 -#define N_NODE_PARAMS 4 +#define NODE_PropInfo 0 +#define NODE_Props 1 +#define NODE_EnumFormat 2 +#define NODE_Format 3 +#define NODE_ProcessLatency 4 +#define N_NODE_PARAMS 5 struct spa_param_info params[N_NODE_PARAMS]; uint32_t media_type; @@ -153,6 +154,7 @@ struct stream { unsigned int trigger_done_rt:1; int in_set_param; int in_emit_param_changed; + int pending_drain; }; static int get_param_index(uint32_t id) @@ -166,6 +168,8 @@ static int get_param_index(uint32_t id) return NODE_EnumFormat; case SPA_PARAM_Format: return NODE_Format; + case SPA_PARAM_ProcessLatency: + return NODE_ProcessLatency; default: return -1; } @@ -245,10 +249,13 @@ static int add_param(struct stream *impl, memcpy(p->param, param, SPA_POD_SIZE(param)); SPA_POD_OBJECT_ID(p->param) = id; - if (id == SPA_PARAM_Buffers && - SPA_FLAG_IS_SET(impl->flags, PW_STREAM_FLAG_MAP_BUFFERS) && - impl->direction == SPA_DIRECTION_INPUT) - fix_datatype(p->param); + switch (id) { + case SPA_PARAM_Buffers: + if (impl->direction == SPA_DIRECTION_INPUT && + SPA_FLAG_IS_SET(impl->flags, PW_STREAM_FLAG_MAP_BUFFERS)) + fix_datatype(p->param); + break; + } spa_list_append(&impl->param_list, &p->link); @@ -306,7 +313,7 @@ static void clear_params(struct stream *impl, uint32_t id) } } -static int update_params(struct stream *impl, uint32_t id, +static int update_params(struct stream *impl, uint32_t id, uint32_t flags, const struct spa_pod **params, uint32_t n_params) { uint32_t i; @@ -322,7 +329,7 @@ static int update_params(struct stream *impl, uint32_t id, } } for (i = 0; i < n_params; i++) { - if ((res = add_param(impl, id, 0, params[i])) < 0) + if ((res = add_param(impl, id, flags, params[i])) < 0) break; } return res; @@ -393,8 +400,10 @@ static bool stream_set_state(struct pw_stream *stream, enum pw_stream_state stat pw_stream_state_as_string(old), pw_stream_state_as_string(state), res, stream->error); - if (state == PW_STREAM_STATE_ERROR) + if (state == PW_STREAM_STATE_ERROR) { pw_log_error("%p: error (%d) %s", stream, res, error); + errno = -res; + } stream->state = state; pw_stream_emit_state_changed(stream, old, state, error); @@ -449,14 +458,19 @@ do_call_drained(struct spa_loop *loop, struct pw_stream *stream = &impl->this; pw_log_trace_fp("%p: drained", stream); pw_stream_emit_drained(stream); + SPA_ATOMIC_DEC(impl->pending_drain); return 0; } static void call_drained(struct stream *impl) { pw_log_info("%p: drained", impl); - pw_loop_invoke(impl->main_loop, - do_call_drained, 1, NULL, 0, false, impl); + if (SPA_ATOMIC_INC(impl->pending_drain) == 1) { + pw_loop_invoke(impl->main_loop, + do_call_drained, 1, NULL, 0, false, impl); + } else { + SPA_ATOMIC_DEC(impl->pending_drain); + } } static int @@ -601,8 +615,13 @@ static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struc struct stream *impl = object; struct pw_stream *stream = &impl->this; - if (id != SPA_PARAM_Props) + switch (id) { + case SPA_PARAM_Props: + case SPA_PARAM_ProcessLatency: + break; + default: return -ENOTSUP; + } if (impl->in_set_param == 0) emit_param_changed(impl, id, param); @@ -627,7 +646,19 @@ static inline void copy_position(struct stream *impl, int64_t queued) impl->base_pos = p->clock.position - impl->time.ticks; impl->clock_id = p->clock.id; } - impl->time.ticks = p->clock.position - impl->base_pos; + if (SPA_FLAG_IS_SET(p->clock.flags, SPA_IO_CLOCK_FLAG_NO_RATE)) { + if (p->clock.rate.num == 0 || p->clock.rate.denom == 0) { + impl->time.ticks = p->clock.nsec; + impl->time.rate.num = 1; + impl->time.rate.denom = SPA_NSEC_PER_SEC; + } else { + impl->time.ticks = (p->clock.nsec * p->clock.rate.denom) / + (SPA_NSEC_PER_SEC * p->clock.rate.num); + } + } else { + impl->time.ticks = p->clock.position - impl->base_pos; + } + impl->time.delay = 0; impl->time.queued = queued; impl->quantum = p->clock.duration; @@ -819,8 +850,14 @@ static void clear_buffers(struct pw_stream *stream) if (b->busy) SPA_ATOMIC_DEC(b->busy->count); } - } else + } else { clear_queue(impl, &impl->dequeued); + struct spa_io_buffers *io = impl->io; + if (io && io->status == SPA_STATUS_HAVE_DATA) { + io->buffer_id = SPA_ID_INVALID; + io->status = SPA_STATUS_OK; + } + } clear_queue(impl, &impl->queued); } @@ -870,7 +907,7 @@ static int impl_port_set_param(void *object, if (param) pw_log_pod(SPA_LOG_LEVEL_DEBUG, param); - if ((res = update_params(impl, id, ¶m, param ? 1 : 0)) < 0) + if ((res = update_params(impl, id, 0, ¶m, param ? 1 : 0)) < 0) return res; switch (id) { @@ -1134,11 +1171,14 @@ static void proxy_destroy(void *_data) static void proxy_error(void *_data, int seq, int res, const char *message) { struct pw_stream *stream = _data; + int old_errno = errno; /* we just emit the state change here to inform the application. * If this is supposed to be a permanent error, the app should * do a pw_stream_set_error() */ + errno = -res; pw_stream_emit_state_changed(stream, stream->state, PW_STREAM_STATE_ERROR, message); + errno = old_errno; } static void proxy_bound_props(void *data, uint32_t global_id, const struct spa_dict *props) @@ -1737,6 +1777,8 @@ enum pw_stream_state pw_stream_get_state(struct pw_stream *stream, const char ** { if (error) *error = stream->error; + if (stream->state == PW_STREAM_STATE_ERROR) + errno = -stream->error_res; return stream->state; } @@ -1919,6 +1961,7 @@ pw_stream_connect(struct pw_stream *stream, 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->params[NODE_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, SPA_PARAM_INFO_READWRITE); impl->info.params = impl->params; impl->info.n_params = N_NODE_PARAMS; impl->info.change_mask = impl->change_mask_all; @@ -2010,7 +2053,7 @@ pw_stream_connect(struct pw_stream *stream, pw_properties_set(impl->port_props, PW_KEY_FORMAT_DSP, str); else if (impl->media_type == SPA_MEDIA_TYPE_application && impl->media_subtype == SPA_MEDIA_SUBTYPE_control) - pw_properties_set(impl->port_props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); + pw_properties_set(impl->port_props, PW_KEY_FORMAT_DSP, "32 bit raw UMP"); if (pw_properties_get(impl->port_props, PW_KEY_PORT_GROUP) == NULL) pw_properties_set(impl->port_props, PW_KEY_PORT_GROUP, "stream.0"); @@ -2077,9 +2120,8 @@ pw_stream_connect(struct pw_stream *stream, if (pw_properties_get(props, PW_KEY_PORT_GROUP) == NULL) pw_properties_set(props, PW_KEY_PORT_GROUP, "stream.0"); - if (impl->media_type == SPA_MEDIA_TYPE_audio - || (impl->media_type == SPA_MEDIA_TYPE_video - && pw_properties_get(props, "video.adapt.converter"))) { + if (impl->media_type == SPA_MEDIA_TYPE_audio || + impl->media_type == SPA_MEDIA_TYPE_video) { factory = pw_context_find_factory(impl->context, "adapter"); if (factory == NULL) { pw_log_error("%p: no adapter factory found", stream); @@ -2196,7 +2238,7 @@ int pw_stream_update_params(struct pw_stream *stream, ensure_loop(impl->main_loop, return -EIO); pw_log_debug("%p: update params", stream); - if ((res = update_params(impl, SPA_ID_INVALID, params, n_params)) < 0) + if ((res = update_params(impl, SPA_ID_INVALID, 0, params, n_params)) < 0) return res; if (impl->in_emit_param_changed == 0) { @@ -2362,7 +2404,8 @@ int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t time->delay += (int64_t)(((impl->latency.min_quantum + impl->latency.max_quantum) / 2.0f) * quantum); time->delay += (impl->latency.min_rate + impl->latency.max_rate) / 2; - time->delay += ((impl->latency.min_ns + impl->latency.max_ns) / 2) * time->rate.denom / SPA_NSEC_PER_SEC; + time->delay += ((impl->latency.min_ns + impl->latency.max_ns) / 2) * + (int64_t)time->rate.denom / (int64_t)SPA_NSEC_PER_SEC; avail_buffers = spa_ringbuffer_get_read_index(&impl->dequeued.ring, &index); avail_buffers = SPA_CLAMP(avail_buffers, 0, (int32_t)impl->n_buffers); @@ -2462,6 +2505,41 @@ int pw_stream_queue_buffer(struct pw_stream *stream, struct pw_buffer *buffer) return res; } +static inline int queue_push_front(struct stream *stream, struct queue *queue, struct buffer *buffer) +{ + int ret = 0; + uint32_t index; + + if ((ret = spa_ringbuffer_get_read_index(&queue->ring, &index)) < 0) + return ret; + + /* undo the pop operation and place the buffer in front of the queue */ + index -= 1; + queue->ids[index & MASK_BUFFERS] = buffer->id; + queue->outcount -= buffer->this.size; + SPA_FLAG_SET(buffer->flags, BUFFER_FLAG_QUEUED); + spa_ringbuffer_read_update(&queue->ring, index); + + return ret; +} + +SPA_EXPORT +int pw_stream_return_buffer(struct pw_stream *stream, struct pw_buffer *buffer) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + struct buffer *b = SPA_CONTAINER_OF(buffer, struct buffer, this); + + pw_log_trace_fp("%p: %p id: %d", impl, buffer, b->id); + + /* dequeue increments the busy count, so undo that */ + if (b->busy) { + SPA_ATOMIC_DEC(b->busy->count); + pw_log_trace_fp("%p: %p: %p busy count %u", impl, b, b->busy, SPA_ATOMIC_LOAD(b->busy->count)); + } + + return queue_push_front(impl, &impl->dequeued, b); +} + static int do_flush(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) @@ -2546,20 +2624,23 @@ do_trigger_driver(struct spa_loop *loop, return spa_node_call_ready(&impl->callbacks, res); } -static int do_trigger_request_process(struct spa_loop *loop, +static int do_emit_event(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data) { struct stream *impl = user_data; - uint8_t buffer[1024]; - struct spa_pod_builder b = { 0 }; - - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - spa_node_emit_event(&impl->hooks, - spa_pod_builder_add_object(&b, - SPA_TYPE_EVENT_Node, SPA_NODE_EVENT_RequestProcess)); + const struct spa_event *event = data; + spa_node_emit_event(&impl->hooks, event); return 0; } +SPA_EXPORT +int pw_stream_emit_event(struct pw_stream *stream, const struct spa_event *event) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + return pw_loop_invoke(impl->main_loop, + do_emit_event, 1, event, SPA_POD_SIZE(&event->pod), false, impl); +} + SPA_EXPORT int pw_stream_trigger_process(struct pw_stream *stream) { @@ -2572,13 +2653,33 @@ int pw_stream_trigger_process(struct pw_stream *stream) impl->using_trigger = true; if (impl->trigger) { - pw_impl_node_trigger(stream->node); + res = pw_impl_node_trigger(stream->node); } else if (stream->node->driving) { res = pw_loop_invoke(impl->data_loop, do_trigger_driver, 1, NULL, 0, false, impl); } else { - res = pw_loop_invoke(impl->main_loop, - do_trigger_request_process, 1, NULL, 0, false, impl); + pw_stream_emit_event(stream, + &SPA_NODE_EVENT_INIT(SPA_NODE_EVENT_RequestProcess)); } return res; } + +SPA_EXPORT +int pw_stream_set_rate(struct pw_stream *stream, double rate) +{ + struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); + bool enable; + + if (impl->rate_match == NULL) + return -ENOTSUP; + + if (rate <= 0.0) { + rate = 1.0; + enable = false; + } else { + enable = true; + } + impl->rate_match->rate = rate; + SPA_FLAG_UPDATE(impl->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE, enable); + return 0; +} diff --git a/src/pipewire/stream.h b/src/pipewire/stream.h index b1c8c6cd..8e8ba095 100644 --- a/src/pipewire/stream.h +++ b/src/pipewire/stream.h @@ -133,8 +133,25 @@ extern "C" { * When the buffer has been processed, call \ref pw_stream_queue_buffer() * to let PipeWire reuse the buffer. * + * Although not strictly required, it is recommended to call \ref + * pw_stream_dequeue_buffer() and pw_stream_queue_buffer() from the + * process() callback to minimize the amount of buffering and + * maximize the amount of buffer reuse in the stream. + * + * It is also possible to dequeue the buffer from the process event, + * then process and queue the buffer from a helper thread. It is also + * possible to dequeue, process and queue a buffer from a helper thread + * after receiving the process event. + * * \subsection ssec_produce Produce data * + * The process event is emitted when a new buffer should be queued. + * + * When the PW_STREAM_FLAG_RT_PROCESS flag was given, this function will be + * called from a realtime thread and it is not safe to call non-reatime + * functions such as doing file operations, blocking operations or any of + * the PipeWire functions that are not explicitly marked as being RT safe. + * * \ref pw_stream_dequeue_buffer() gives an empty buffer that can be filled. * * The buffer is owned by the stream and stays alive until the @@ -142,8 +159,19 @@ extern "C" { * * Filled buffers should be queued with \ref pw_stream_queue_buffer(). * - * The process event is emitted when PipeWire has emptied a buffer that - * can now be refilled. + * Although not strictly required, it is recommended to call \ref + * pw_stream_dequeue_buffer() and pw_stream_queue_buffer() from the + * process() callback to minimize the amount of buffering and + * maximize the amount of buffer reuse in the stream. + * + * Buffers that are queued after the process event completes will be delayed + * to the next processing cycle. + * + * \section sec_stream_timing Obtaining timing information + * + * With \ref pw_stream_get_time_n() and pw_stream_get_nsec() on can get accurate + * timing information of the data and the graph in the \ref pw_time. See + * the documentation of these functions. * * \section sec_stream_driving Driving the graph * @@ -157,6 +185,26 @@ extern "C" { * true. It must then use pw_stream_trigger_process() to start the graph * cycle. * + * \ref pw_stream_trigger_process() will result in a process event, where a buffer + * should be dequeued, and queued again. This is the recommended behaviour that + * minimizes buffering and maximized buffer reuse. + * + * Producers of data that drive the graph can also dequeue a buffer in a helper + * thread, fill it with data and then call \ref pw_stream_trigger_process() to + * start the graph cycle. In the process event they will then queue the filled + * buffer and dequeue a new empty buffer to fill again in the helper thread, + * + * Consumers of data that drive the graph (pull based scheduling) will use + * \ref pw_stream_trigger_process() to start the graph and will dequeue, process + * and queue the buffers in the process event. + * + * \section sec_stream_process_requests Request processing + * + * A stream that is not driving the graph can request a new graph cycle by doing + * \ref pw_stream_trigger_process(). This will result in a RequestProcess command + * in the driver stream. If the driver supports this, it can then perform + * \ref pw_stream_trigger_process() to start the actual graph cycle. + * * \section sec_stream_disconnect Disconnect * * Use \ref pw_stream_disconnect() to disconnect a stream after use. @@ -192,6 +240,7 @@ struct pw_stream; #include <spa/buffer/buffer.h> #include <spa/param/param.h> #include <spa/pod/command.h> +#include <spa/pod/event.h> /** \enum pw_stream_state The state of a stream */ enum pw_stream_state { @@ -265,6 +314,13 @@ struct pw_stream_control { * caused by the hardware. The delay is usually quite stable and should only change when * the topology, quantum or samplerate of the graph changes. * + * The delay requires the application to send the stream early relative to other synchronized + * streams in order to arrive at the edge of the graph in time. This is usually done by + * delaying the other streams with the given delay. + * + * Note that the delay can be negative. A negative delay means that this stream should be + * delayed with the (positive) delay relative to other streams. + * * pw_time.queued and pw_time.buffered is expressed in the time domain of the stream, * or the format that is used for the buffers of this stream. * @@ -356,7 +412,8 @@ struct pw_stream_events { uint32_t version; void (*destroy) (void *data); - /** when the stream state changes */ + /** when the stream state changes. Since 1.4 this also sets errno when the + * new state is PW_STREAM_STATE_ERROR */ void (*state_changed) (void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error); @@ -461,6 +518,8 @@ void pw_stream_add_listener(struct pw_stream *stream, const struct pw_stream_events *events, void *data); +/** Get the current stream state. Since 1.4 this also sets errno when the + * state is PW_STREAM_STATE_ERROR */ enum pw_stream_state pw_stream_get_state(struct pw_stream *stream, const char **error); const char *pw_stream_get_name(struct pw_stream *stream); @@ -529,11 +588,11 @@ const struct pw_stream_control *pw_stream_get_control(struct pw_stream *stream, /** Set control values */ int pw_stream_set_control(struct pw_stream *stream, uint32_t id, uint32_t n_values, float *values, ...); -/** Query the time on the stream */ +/** Query the time on the stream, RT safe */ int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t size); /** Get the current time in nanoseconds. This value can be compared with - * the \ref pw_time.now value. Since 1.1.0 */ + * the \ref pw_time.now value. RT safe. Since 1.1.0 */ uint64_t pw_stream_get_nsec(struct pw_stream *stream); /** Get the data loop that is doing the processing of this stream. This loop @@ -541,17 +600,21 @@ uint64_t pw_stream_get_nsec(struct pw_stream *stream); struct pw_loop *pw_stream_get_data_loop(struct pw_stream *stream); /** Query the time on the stream, deprecated since 0.3.50, - * use pw_stream_get_time_n() to get the fields added since 0.3.50. */ + * use pw_stream_get_time_n() to get the fields added since 0.3.50. RT safe. */ SPA_DEPRECATED int pw_stream_get_time(struct pw_stream *stream, struct pw_time *time); /** Get a buffer that can be filled for playback streams or consumed - * for capture streams. */ + * for capture streams. RT safe. */ struct pw_buffer *pw_stream_dequeue_buffer(struct pw_stream *stream); -/** Submit a buffer for playback or recycle a buffer for capture. */ +/** Submit a buffer for playback or recycle a buffer for capture. RT safe. */ int pw_stream_queue_buffer(struct pw_stream *stream, struct pw_buffer *buffer); +/** Return a buffer to the queue without using it. This makes the buffer + * immediately available to dequeue again. RT safe. */ +int pw_stream_return_buffer(struct pw_stream *stream, struct pw_buffer *buffer); + /** Activate or deactivate the stream */ int pw_stream_set_active(struct pw_stream *stream, bool active); @@ -560,7 +623,7 @@ int pw_stream_set_active(struct pw_stream *stream, bool active); * after the drain by setting it active again with * \ref pw_stream_set_active(). A flush without a drain is mostly useful afer * a state change to PAUSED, to flush any remaining data from the queues and - * the converters. */ + * the converters. RT safe. */ int pw_stream_flush(struct pw_stream *stream, bool drain); /** Check if the stream is driving. The stream needs to have the @@ -581,7 +644,7 @@ bool pw_stream_is_driving(struct pw_stream *stream); * * It is not a requirement that all RequestProcess events/commands * need to start a graph cycle. - * Since 1.2.7 */ + * Since 1.4.0 */ bool pw_stream_is_lazy(struct pw_stream *stream); /** Trigger a push/pull on the stream. One iteration of the graph will @@ -610,9 +673,23 @@ bool pw_stream_is_lazy(struct pw_stream *stream); * driver. If the graph is not lazy scheduling and the stream is not a * driver, this method will have no effect. * + * RT safe. + * * Since 0.3.34 */ int pw_stream_trigger_process(struct pw_stream *stream); +/** Emit an event from this stream. RT safe. + * Since 1.2.6 */ +int pw_stream_emit_event(struct pw_stream *stream, const struct spa_event *event); + +/** Adjust the rate of the stream. + * When the stream is using an adaptive resampler, adjust the resampler rate. + * When there is no resampler, -ENOTSUP is returned. Activating the adaptive + * resampler will add a small amount of delay to the samples, you can deactivate + * it again by setting a value <= 0.0. RT safe. + * Since 1.4.0 */ +int pw_stream_set_rate(struct pw_stream *stream, double rate); + /** * \} */ diff --git a/src/pipewire/thread-loop.h b/src/pipewire/thread-loop.h index f1eb1910..2734799d 100644 --- a/src/pipewire/thread-loop.h +++ b/src/pipewire/thread-loop.h @@ -61,6 +61,11 @@ extern "C" { * * All events and callbacks are called with the thread lock held. * + * An exception to this is for the data processing callbacks, which are + * explcitly marked as being emitted from the data realtime threads. One + * such callback is the \ref pw_stream::process() callback when the + * \ref PW_STREAM_FLAG_RT_PROCESS is set. + * */ /** \defgroup pw_thread_loop Thread Loop * diff --git a/src/pipewire/thread.c b/src/pipewire/thread.c index 125a16a7..defa6a68 100644 --- a/src/pipewire/thread.c +++ b/src/pipewire/thread.c @@ -15,6 +15,7 @@ #include <spa/utils/list.h> #include <spa/utils/json.h> +#define PW_API_THREAD_IMPL SPA_EXPORT #include <pipewire/log.h> #include <pipewire/private.h> #include <pipewire/thread.h> @@ -30,15 +31,14 @@ do { \ static int parse_affinity(const char *affinity, cpu_set_t *set) { - struct spa_json it[2]; + struct spa_json it[1]; int v; CPU_ZERO(set); - spa_json_init(&it[0], affinity, strlen(affinity)); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], affinity, strlen(affinity)); + if (spa_json_begin_array_relax(&it[0], affinity, strlen(affinity)) <= 0) + return 0; - while (spa_json_get_int(&it[1], &v) > 0) { + while (spa_json_get_int(&it[0], &v) > 0) { if (v >= 0 && v < CPU_SETSIZE) CPU_SET(v, set); } @@ -117,7 +117,7 @@ static struct spa_thread *impl_create(void *object, pw_log_warn("pthread_setname error: %s", strerror(err)); if ((str = spa_dict_lookup(props, SPA_KEY_THREAD_AFFINITY)) != NULL && (err = thread_setaffinity(pt, str)) != 0) - pw_log_warn("pthread_setaffinity error: %s", strerror(err)); + pw_log_warn("pthread_setaffinity error: %s", strerror(-err)); } return (struct spa_thread*)pt; } diff --git a/src/pipewire/thread.h b/src/pipewire/thread.h index 5ce948dc..f53e6295 100644 --- a/src/pipewire/thread.h +++ b/src/pipewire/thread.h @@ -29,11 +29,31 @@ void pw_thread_utils_set(struct spa_thread_utils *impl); struct spa_thread_utils *pw_thread_utils_get(void); void *pw_thread_fill_attr(const struct spa_dict *props, void *attr); -#define pw_thread_utils_create(...) spa_thread_utils_create(pw_thread_utils_get(), ##__VA_ARGS__) -#define pw_thread_utils_join(...) spa_thread_utils_join(pw_thread_utils_get(), ##__VA_ARGS__) -#define pw_thread_utils_get_rt_range(...) spa_thread_utils_get_rt_range(pw_thread_utils_get(), ##__VA_ARGS__) -#define pw_thread_utils_acquire_rt(...) spa_thread_utils_acquire_rt(pw_thread_utils_get(), ##__VA_ARGS__) -#define pw_thread_utils_drop_rt(...) spa_thread_utils_drop_rt(pw_thread_utils_get(), ##__VA_ARGS__) +#ifndef PW_API_THREAD_IMPL +#define PW_API_THREAD_IMPL static inline +#endif + +PW_API_THREAD_IMPL struct spa_thread *pw_thread_utils_create( + const struct spa_dict *props, void *(*start_routine)(void*), void *arg) +{ + return spa_thread_utils_create(pw_thread_utils_get(), props, start_routine, arg); +} +PW_API_THREAD_IMPL int pw_thread_utils_join(struct spa_thread *thread, void **retval) +{ + return spa_thread_utils_join(pw_thread_utils_get(), thread, retval); +} +PW_API_THREAD_IMPL int pw_thread_utils_get_rt_range(const struct spa_dict *props, int *min, int *max) +{ + return spa_thread_utils_get_rt_range(pw_thread_utils_get(), props, min, max); +} +PW_API_THREAD_IMPL int pw_thread_utils_acquire_rt(struct spa_thread *thread, int priority) +{ + return spa_thread_utils_acquire_rt(pw_thread_utils_get(), thread, priority); +} +PW_API_THREAD_IMPL int pw_thread_utils_drop_rt(struct spa_thread *thread) +{ + return spa_thread_utils_drop_rt(pw_thread_utils_get(), thread); +} /** * \} diff --git a/src/pipewire/utils.c b/src/pipewire/utils.c index 4db73a04..103058a6 100644 --- a/src/pipewire/utils.c +++ b/src/pipewire/utils.c @@ -130,7 +130,7 @@ SPA_EXPORT char **pw_strv_parse(const char *val, size_t len, int max_tokens, int *n_tokens) { struct pw_array arr; - struct spa_json it[2]; + struct spa_json it[1]; int n = 0, l, res; const char *value; struct spa_error_location el; @@ -140,11 +140,10 @@ char **pw_strv_parse(const char *val, size_t len, int max_tokens, int *n_tokens) pw_array_init(&arr, sizeof(char*) * 16); - spa_json_init(&it[0], val, len); - if (spa_json_enter_array(&it[0], &it[1]) <= 0) - spa_json_init(&it[1], val, len); + if (spa_json_begin_array_relax(&it[0], val, len) <= 0) + return NULL; - while ((l = spa_json_next(&it[1], &value)) > 0 && n + 1 < max_tokens) { + while ((l = spa_json_next(&it[0], &value)) > 0 && n + 1 < max_tokens) { char *s; if ((s = malloc(l+1)) == NULL) @@ -161,7 +160,7 @@ char **pw_strv_parse(const char *val, size_t len, int max_tokens, int *n_tokens) if ((res = pw_array_add_ptr(&arr, NULL)) < 0) goto error; done: - if ((res = spa_json_get_error(&it[1], val, &el))) { + if ((res = spa_json_get_error(&it[0], val, &el))) { spa_debug_log_error_location(pw_log_get(), SPA_LOG_LEVEL_WARN, &el, "error parsing strv: %s", el.reason); @@ -179,7 +178,7 @@ done: error_errno: res = -errno; error: - it[1].state = SPA_JSON_ERROR_FLAG; + it[0].state = SPA_JSON_ERROR_FLAG; errno = -res; goto done; } diff --git a/src/tests/test-security-context.c b/src/tests/test-security-context.c index 2ee7a386..a81ed1d1 100644 --- a/src/tests/test-security-context.c +++ b/src/tests/test-security-context.c @@ -97,7 +97,7 @@ static void test_create(void) struct registry_info info; struct spa_hook listener; int res, listen_fd, close_fd[2]; - char temp[PATH_MAX] = "/tmp/pipewire-XXXXXX"; + char temp[] = "/tmp/pipewire-XXXXXX"; struct sockaddr_un sockaddr = {0}; loop = pw_main_loop_new(NULL); diff --git a/src/tools/dfffile.c b/src/tools/dfffile.c index dd40be4b..c2e4db22 100644 --- a/src/tools/dfffile.c +++ b/src/tools/dfffile.c @@ -14,22 +14,25 @@ #include "dfffile.h" +#define BLOCKSIZE 8192 + struct dff_file { - uint8_t *data; - size_t size; + uint8_t *buffer; + size_t blocksize; + size_t offset; int mode; - int fd; + bool close; + FILE *file; + size_t pos; struct dff_file_info info; - - uint8_t *p; - size_t offset; }; struct dff_chunk { uint32_t id; uint64_t size; + uint64_t pos; void *data; }; @@ -57,28 +60,44 @@ static inline uint64_t parse_be64(const uint8_t *in) return res; } -static inline int f_avail(struct dff_file *f) +static inline int f_read(struct dff_file *f, void *data, size_t size) { - if (f->p < f->data + f->size) - return f->size + f->data - f->p; + size_t s = fread(data, 1, size, f->file); + f->pos += s; + if (s < size) + return -1; return 0; } static int read_chunk(struct dff_file *f, struct dff_chunk *c) { - if (f_avail(f) < 12) + uint8_t data[12]; + + if (f_read(f, data, sizeof(data)) < 0) return -ENOSPC; - c->id = parse_be32(f->p); /* id of this chunk */ - c->size = parse_be64(f->p + 4); /* size of this chunk */ - f->p += 12; - c->data = f->p; + c->id = parse_be32(data); /* id of this chunk */ + c->size = parse_be64(data + 4); /* size of this chunk */ + c->pos = f->pos; return 0; } static int skip_chunk(struct dff_file *f, const struct dff_chunk *c) { - f->p = SPA_PTROFF(c->data, c->size, uint8_t); + size_t bytes; + uint8_t data[256]; + + if (c->pos + c->size <= f->pos) + return 0; + + bytes = c->size + c->pos - f->pos; + while (bytes > 0) { + size_t s = fread(data, 1, SPA_MIN(bytes, sizeof(data)), f->file); + if (s == 0) + break; + f->pos += s; + bytes -= s; + } return 0; } @@ -86,22 +105,26 @@ static int read_PROP(struct dff_file *f, struct dff_chunk *prop) { struct dff_chunk c[1]; int res; + uint8_t data[4]; - if (f_avail(f) < 4 || - memcmp(prop->data, "SND ", 4) != 0) + if (f_read(f, data, sizeof(data)) < 0 || + memcmp(data, "SND ", 4) != 0) return -EINVAL; - f->p += 4; - while (f->p < SPA_PTROFF(prop->data, prop->size, uint8_t)) { + while (f->pos < prop->pos + prop->size) { if ((res = read_chunk(f, &c[0])) < 0) return res; switch (c[0].id) { case FOURCC('F', 'S', ' ', ' '): - f->info.rate = parse_be32(f->p); + if (f_read(f, data, 4) < 0) + return -EINVAL; + f->info.rate = parse_be32(data); break; case FOURCC('C', 'H', 'N', 'L'): - f->info.channels = parse_be16(f->p); + if (f_read(f, data, 2) < 0) + return -EINVAL; + f->info.channels = parse_be16(data); switch (f->info.channels) { case 2: f->info.channel_type = 2; @@ -116,7 +139,9 @@ static int read_PROP(struct dff_file *f, struct dff_chunk *prop) break; case FOURCC('C', 'M', 'P', 'R'): { - uint32_t cmpr = parse_be32(f->p); + if (f_read(f, data, 4) < 0) + return -EINVAL; + uint32_t cmpr = parse_be32(data); if (cmpr != FOURCC('D', 'S', 'D', ' ')) return -ENOTSUP; break; @@ -138,15 +163,16 @@ static int read_FRM8(struct dff_file *f) struct dff_chunk c[2]; int res; bool found_dsd = false; + uint8_t data[4]; if ((res = read_chunk(f, &c[0])) < 0) return res; if (c[0].id != FOURCC('F','R','M','8')) return -EINVAL; - if (f_avail(f) < 4 || - memcmp(c[0].data, "DSD ", 4) != 0) + + if (f_read(f, data, sizeof(data)) < 0 || + memcmp(data, "DSD ", 4) != 0) return -EINVAL; - f->p += 4; while (true) { if ((res = read_chunk(f, &c[1])) < 0) @@ -181,37 +207,33 @@ static int read_FRM8(struct dff_file *f) static int open_read(struct dff_file *f, const char *filename, struct dff_file_info *info) { int res; - struct stat st; - if ((f->fd = open(filename, O_RDONLY)) < 0) { - res = -errno; - goto exit; + if (strcmp(filename, "-") != 0) { + if ((f->file = fopen(filename, "r")) == NULL) { + res = -errno; + goto exit; + } + f->close = true; + } else { + f->close = false; + f->file = stdin; } - if (fstat(f->fd, &st) < 0) { - res = -errno; + if ((res = read_FRM8(f)) < 0) goto exit_close; - } - f->size = st.st_size; - f->data = mmap(NULL, f->size, PROT_READ, MAP_SHARED, f->fd, 0); - if (f->data == MAP_FAILED) { + f->blocksize = BLOCKSIZE * f->info.channels; + f->buffer = calloc(1, f->blocksize); + if (f->buffer == NULL) { res = -errno; goto exit_close; } - - f->p = f->data; - - if ((res = read_FRM8(f)) < 0) - goto exit_unmap; - f->mode = 1; *info = f->info; return 0; -exit_unmap: - munmap(f->data, f->size); exit_close: - close(f->fd); + if (f->close) + fclose(f->file); exit: return res; } @@ -267,18 +289,26 @@ dff_file_read(struct dff_file *f, void *data, size_t samples, const struct dff_l int32_t step = SPA_ABS(layout->interleave); uint32_t channels = f->info.channels; bool rev = layout->lsb != f->info.lsb; - size_t total, offset, scale; + size_t total, offset, scale, pos; offset = f->offset; + pos = offset % f->blocksize; scale = SPA_CLAMP(f->info.rate / (44100u * 64u), 1u, 4u); samples *= step; samples *= scale; - for (total = 0; total < samples && offset < f->info.length; total++) { + for (total = 0; total < samples; total++) { uint32_t i; int32_t j; - const uint8_t *s = f->p + offset; + const uint8_t *s = f->buffer + pos; + + if (pos == 0) { + if (fread(f->buffer, 1, f->blocksize, f->file) != f->blocksize) + break; + } + if (f->info.length > 0 && offset >= f->info.length) + break; for (i = 0; i < layout->channels; i++) { if (layout->interleave > 0) { @@ -294,6 +324,9 @@ dff_file_read(struct dff_file *f, void *data, size_t samples, const struct dff_l } } offset += step * channels; + pos += step * channels; + if (pos == f->blocksize) + pos = 0; } f->offset = offset; @@ -302,12 +335,9 @@ dff_file_read(struct dff_file *f, void *data, size_t samples, const struct dff_l int dff_file_close(struct dff_file *f) { - if (f->mode == 1) { - munmap(f->data, f->size); - } else - return -EINVAL; - - close(f->fd); + if (f->close) + fclose(f->file); + free(f->buffer); free(f); return 0; } diff --git a/src/tools/dsffile.c b/src/tools/dsffile.c index 213eefe2..4122944a 100644 --- a/src/tools/dsffile.c +++ b/src/tools/dsffile.c @@ -14,16 +14,14 @@ #include "dsffile.h" struct dsf_file { - uint8_t *data; - size_t size; + uint8_t *buffer; + size_t offset; int mode; - int fd; + bool close; + FILE *file; struct dsf_file_info info; - - uint8_t *p; - size_t offset; }; static inline uint32_t parse_le32(const uint8_t *in) @@ -44,104 +42,110 @@ static inline uint64_t parse_le64(const uint8_t *in) return res; } -static inline int f_avail(struct dsf_file *f) +static inline int f_skip(struct dsf_file *f, size_t bytes) { - if (f->p < f->data + f->size) - return f->size + f->data - f->p; + uint8_t data[256]; + while (bytes > 0) { + size_t s = fread(data, 1, SPA_MIN(bytes, sizeof(data)), f->file); + bytes -= s; + } return 0; } static int read_DSD(struct dsf_file *f) { + size_t s; uint64_t size; + uint8_t data[28]; - if (f_avail(f) < 28 || - memcmp(f->p, "DSD ", 4) != 0) + s = fread(data, 1, 28, f->file); + if (s < 28 || memcmp(data, "DSD ", 4) != 0) return -EINVAL; - size = parse_le64(f->p + 4); /* size of this chunk */ - parse_le64(f->p + 12); /* total size */ - parse_le64(f->p + 20); /* metadata */ - f->p += size; + size = parse_le64(data + 4); /* size of this chunk */ + parse_le64(data + 12); /* total size */ + parse_le64(data + 20); /* metadata */ + if (size > s) + f_skip(f, size - s); return 0; } static int read_fmt(struct dsf_file *f) { + size_t s; uint64_t size; + uint8_t data[52]; - if (f_avail(f) < 52 || - memcmp(f->p, "fmt ", 4) != 0) + s = fread(data, 1, 52, f->file); + if (s < 52 || memcmp(data, "fmt ", 4) != 0) return -EINVAL; - size = parse_le64(f->p + 4); /* size of this chunk */ - if (parse_le32(f->p + 12) != 1) /* version */ + size = parse_le64(data + 4); /* size of this chunk */ + if (parse_le32(data + 12) != 1) /* version */ return -EINVAL; - if (parse_le32(f->p + 16) != 0) /* format id */ + if (parse_le32(data + 16) != 0) /* format id */ return -EINVAL; - f->info.channel_type = parse_le32(f->p + 20); - f->info.channels = parse_le32(f->p + 24); - f->info.rate = parse_le32(f->p + 28); - f->info.lsb = parse_le32(f->p + 32) == 1; - f->info.samples = parse_le64(f->p + 36); - f->info.blocksize = parse_le32(f->p + 44); - f->p += size; + f->info.channel_type = parse_le32(data + 20); + f->info.channels = parse_le32(data + 24); + f->info.rate = parse_le32(data + 28); + f->info.lsb = parse_le32(data + 32) == 1; + f->info.samples = parse_le64(data + 36); + f->info.blocksize = parse_le32(data + 44); + if (size > s) + f_skip(f, size - s); + + f->buffer = calloc(1, f->info.blocksize * f->info.channels); + if (f->buffer == NULL) + return -errno; + return 0; } static int read_data(struct dsf_file *f) { + size_t s; uint64_t size; + uint8_t data[12]; - if (f_avail(f) < 12 || - memcmp(f->p, "data", 4) != 0) + s = fread(data, 1, 12, f->file); + if (s < 12 || memcmp(data, "data", 4) != 0) return -EINVAL; - size = parse_le64(f->p + 4); /* size of this chunk */ + size = parse_le64(data + 4); /* size of this chunk */ f->info.length = size - 12; - f->p += 12; return 0; } static int open_read(struct dsf_file *f, const char *filename, struct dsf_file_info *info) { int res; - struct stat st; - - if ((f->fd = open(filename, O_RDONLY)) < 0) { - res = -errno; - goto exit; - } - if (fstat(f->fd, &st) < 0) { - res = -errno; - goto exit_close; - } - f->size = st.st_size; - f->data = mmap(NULL, f->size, PROT_READ, MAP_SHARED, f->fd, 0); - if (f->data == MAP_FAILED) { - res = -errno; - goto exit_close; + if (strcmp(filename, "-") != 0) { + if ((f->file = fopen(filename, "r")) == NULL) { + res = -errno; + goto exit; + } + f->close = true; + } else { + f->close = false; + f->file = stdin; } - f->p = f->data; - if ((res = read_DSD(f)) < 0) - goto exit_unmap; + goto exit_close; if ((res = read_fmt(f)) < 0) - goto exit_unmap; + goto exit_close; if ((res = read_data(f)) < 0) - goto exit_unmap; + goto exit_close; f->mode = 1; *info = f->info; return 0; -exit_unmap: - munmap(f->data, f->size); exit_close: - close(f->fd); + if (f->close) + fclose(f->file); exit: return res; } @@ -197,21 +201,28 @@ dsf_file_read(struct dsf_file *f, void *data, size_t samples, const struct dsf_l int step = SPA_ABS(layout->interleave); bool rev = layout->lsb != f->info.lsb; size_t total, block, offset, pos, scale; + size_t blocksize = f->info.blocksize * f->info.channels; block = f->offset / f->info.blocksize; - offset = block * f->info.blocksize * f->info.channels; + offset = block * blocksize; 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; + for (total = 0; total < samples; total++) { uint32_t i; + if (pos == 0) { + if (fread(f->buffer, 1, blocksize, f->file) != blocksize) + break; + } + if (f->info.length > 0 && offset + pos >= f->info.length) { + break; + } for (i = 0; i < layout->channels; i++) { - const uint8_t *c = &s[f->info.blocksize * i]; + const uint8_t *c = &f->buffer[f->info.blocksize * i + pos]; int j; if (layout->interleave > 0) { @@ -225,7 +236,7 @@ dsf_file_read(struct dsf_file *f, void *data, size_t samples, const struct dsf_l pos += step; if (pos == f->info.blocksize) { pos = 0; - offset += f->info.blocksize * f->info.channels; + offset += blocksize; } } f->offset += total * step; @@ -235,12 +246,9 @@ dsf_file_read(struct dsf_file *f, void *data, size_t samples, const struct dsf_l int dsf_file_close(struct dsf_file *f) { - if (f->mode == 1) { - munmap(f->data, f->size); - } else - return -EINVAL; - - close(f->fd); + if (f->close) + fclose(f->file); + free(f->buffer); free(f); return 0; } diff --git a/src/tools/midifile.c b/src/tools/midifile.c index 2ee2be62..80c5bbb1 100644 --- a/src/tools/midifile.c +++ b/src/tools/midifile.c @@ -10,6 +10,7 @@ #include <math.h> #include <spa/utils/string.h> +#include <spa/control/ump-utils.h> #include "midifile.h" @@ -18,27 +19,28 @@ struct midi_track { uint16_t id; - uint8_t *data; + long start; uint32_t size; + long pos; - uint8_t *p; int64_t tick; unsigned int eof:1; uint8_t event[4]; }; struct midi_file { - uint8_t *data; - size_t size; - int mode; - int fd; + FILE *file; + bool close; + long pos; + + uint8_t *buffer; + size_t buffer_size; struct midi_file_info info; uint32_t length; uint32_t tempo; - uint8_t *p; int64_t tick; double tick_sec; double tick_start; @@ -56,138 +58,157 @@ static inline uint32_t parse_be32(const uint8_t *in) return (in[0] << 24) | (in[1] << 16) | (in[2] << 8) | in[3]; } -static inline int mf_avail(struct midi_file *mf) +static inline int mf_read(struct midi_file *mf, void *data, size_t size) { - if (mf->p < mf->data + mf->size) - return mf->size + mf->data - mf->p; - return 0; + if (fread(data, size, 1, mf->file) != 1) + return 0; + mf->pos += size; + return 1; } static inline int tr_avail(struct midi_track *tr) { if (tr->eof) return 0; - if (tr->p < tr->data + tr->size) - return tr->size + tr->data - tr->p; + if (tr->size == 0) + return 1; + if (tr->pos < tr->start + tr->size) + return tr->size + tr->start - tr->pos; tr->eof = true; return 0; } static int read_mthd(struct midi_file *mf) { - if (mf_avail(mf) < 14 || - memcmp(mf->p, "MThd", 4) != 0) + uint8_t data[14]; + + if (mf_read(mf, data, sizeof(data)) != 1 || + memcmp(data, "MThd", 4) != 0) return -EINVAL; - mf->length = parse_be32(mf->p + 4); - mf->info.format = parse_be16(mf->p + 8); - mf->info.ntracks = parse_be16(mf->p + 10); - mf->info.division = parse_be16(mf->p + 12); + mf->length = parse_be32(data + 4); + mf->info.format = parse_be16(data + 8); + mf->info.ntracks = parse_be16(data + 10); + mf->info.division = parse_be16(data + 12); + return 0; +} + +static int parse_varlen(struct midi_file *mf, struct midi_track *tr, uint32_t *result) +{ + uint32_t value = 0; + uint8_t data[1]; - mf->p += 14; + while (mf_read(mf, data, 1) == 1) { + value = (value << 7) | (data[0] & 0x7f); + if ((data[0] & 0x80) == 0) + break; + } + *result = value; return 0; } -static int read_mtrk(struct midi_file *mf, struct midi_track *track) +static int read_delta_time(struct midi_file *mf, struct midi_track *tr) { - if (mf_avail(mf) < 8 || - memcmp(mf->p, "MTrk", 4) != 0) - return -EINVAL; + int res; + uint32_t delta_time; + + if ((res = parse_varlen(mf, tr, &delta_time)) < 0) + return res; + + tr->tick += delta_time; + tr->pos = mf->pos; + return 0; - track->data = track->p = mf->p + 8; - track->size = parse_be32(mf->p + 4); +} - mf->p = track->data + track->size; - if (mf->p > mf->data + mf->size) +static int read_mtrk(struct midi_file *mf, struct midi_track *track) +{ + uint8_t data[8]; + + if (mf_read(mf, data, sizeof(data)) != 1 || + memcmp(data, "MTrk", 4) != 0) return -EINVAL; - return 0; + track->start = track->pos = mf->pos; + track->size = parse_be32(data + 4); + + return read_delta_time(mf, track); } -static int parse_varlen(struct midi_file *mf, struct midi_track *tr, uint32_t *result) +static uint8_t *ensure_buffer(struct midi_file *mf, struct midi_track *tr, size_t size) { - uint32_t value = 0; + if (size <= 4) + return tr->event; - while (tr_avail(tr) > 0) { - uint8_t b = *tr->p++; - value = (value << 7) | (b & 0x7f); - if ((b & 0x80) == 0) - break; + if (size > mf->buffer_size) { + mf->buffer = realloc(mf->buffer, size); + mf->buffer_size = size; } - *result = value; - return 0; + return mf->buffer; } static int open_read(struct midi_file *mf, const char *filename, struct midi_file_info *info) { int res; uint16_t i; - struct stat st; - - if ((mf->fd = open(filename, O_RDONLY)) < 0) { - res = -errno; - goto exit; - } - if (fstat(mf->fd, &st) < 0) { - res = -errno; - goto exit_close; - } - mf->size = st.st_size; - mf->data = mmap(NULL, mf->size, PROT_READ, MAP_SHARED, mf->fd, 0); - if (mf->data == MAP_FAILED) { - res = -errno; - goto exit_close; + if (strcmp(filename, "-") != 0) { + if ((mf->file = fopen(filename, "r")) == NULL) { + res = -errno; + goto exit; + } + mf->close = true; + } else { + mf->file = stdin; + mf->close = false; } - mf->p = mf->data; - if ((res = read_mthd(mf)) < 0) - goto exit_unmap; + goto exit_close; mf->tempo = DEFAULT_TEMPO; mf->tick = 0; for (i = 0; i < mf->info.ntracks; i++) { struct midi_track *tr = &mf->tracks[i]; - uint32_t delta_time; if ((res = read_mtrk(mf, tr)) < 0) - goto exit_unmap; + goto exit_close; - if ((res = parse_varlen(mf, tr, &delta_time)) < 0) - goto exit_unmap; - - tr->tick = delta_time; tr->id = i; + + if (i + 1 < mf->info.ntracks && + fseek(mf->file, tr->start + tr->size, SEEK_SET) != 0) { + res = -errno; + goto exit_close; + } } mf->mode = 1; *info = mf->info; return 0; -exit_unmap: - munmap(mf->data, mf->size); exit_close: - close(mf->fd); + if (mf->close) + fclose(mf->file); exit: return res; } -static inline int write_n(int fd, const void *buf, int count) +static inline int write_n(FILE *file, const void *buf, int count) { - return write(fd, buf, count) == (ssize_t)count ? count : -errno; + return fwrite(buf, 1, count, file) == (size_t)count ? count : -errno; } -static inline int write_be16(int fd, uint16_t val) +static inline int write_be16(FILE *file, uint16_t val) { uint8_t buf[2] = { val >> 8, val }; - return write_n(fd, buf, 2); + return write_n(file, buf, 2); } -static inline int write_be32(int fd, uint32_t val) +static inline int write_be32(FILE *file, uint32_t val) { uint8_t buf[4] = { val >> 24, val >> 16, val >> 8, val }; - return write_n(fd, buf, 4); + return write_n(file, buf, 4); } #define CHECK_RES(expr) if ((res = (expr)) < 0) return res @@ -197,17 +218,17 @@ static int write_headers(struct midi_file *mf) struct midi_track *tr = &mf->tracks[0]; int res; - lseek(mf->fd, 0, SEEK_SET); + fseek(mf->file, 0, SEEK_SET); mf->length = 6; - CHECK_RES(write_n(mf->fd, "MThd", 4)); - CHECK_RES(write_be32(mf->fd, mf->length)); - CHECK_RES(write_be16(mf->fd, mf->info.format)); - CHECK_RES(write_be16(mf->fd, mf->info.ntracks)); - CHECK_RES(write_be16(mf->fd, mf->info.division)); + CHECK_RES(write_n(mf->file, "MThd", 4)); + CHECK_RES(write_be32(mf->file, mf->length)); + CHECK_RES(write_be16(mf->file, mf->info.format)); + CHECK_RES(write_be16(mf->file, mf->info.ntracks)); + CHECK_RES(write_be16(mf->file, mf->info.division)); - CHECK_RES(write_n(mf->fd, "MTrk", 4)); - CHECK_RES(write_be32(mf->fd, tr->size)); + CHECK_RES(write_n(mf->file, "MTrk", 4)); + CHECK_RES(write_be32(mf->file, tr->size)); return 0; } @@ -225,9 +246,15 @@ static int open_write(struct midi_file *mf, const char *filename, struct midi_fi if (info->division == 0) info->division = 96; - if ((mf->fd = open(filename, O_WRONLY | O_CREAT, 0660)) < 0) { - res = -errno; - goto exit; + if (strcmp(filename, "-") != 0) { + if ((mf->file = fopen(filename, "w")) == NULL) { + res = -errno; + goto exit; + } + mf->close = true; + } else { + mf->file = stdout; + mf->close = false; } mf->mode = 2; mf->tempo = DEFAULT_TEMPO; @@ -270,17 +297,17 @@ int midi_file_close(struct midi_file *mf) { int res; - if (mf->mode == 1) { - munmap(mf->data, mf->size); - } else if (mf->mode == 2) { + if (mf->mode == 2) { uint8_t buf[4] = { 0x00, 0xff, 0x2f, 0x00 }; - CHECK_RES(write_n(mf->fd, buf, 4)); + CHECK_RES(write_n(mf->file, buf, 4)); mf->tracks[0].size += 4; CHECK_RES(write_headers(mf)); } else return -EINVAL; - close(mf->fd); + if (mf->close) + fclose(mf->file); + free(mf->buffer); free(mf); return 0; } @@ -302,6 +329,7 @@ static int peek_next(struct midi_file *mf, struct midi_event *ev) ev->track = found->id; ev->sec = mf->tick_sec + ((found->tick - mf->tick_start) * (double)mf->tempo) / (1000000.0 * mf->info.division); + ev->type = MIDI_EVENT_TYPE_MIDI1; return 1; } @@ -320,22 +348,31 @@ int midi_file_next_time(struct midi_file *mf, double *sec) int midi_file_read_event(struct midi_file *mf, struct midi_event *event) { struct midi_track *tr; - uint32_t delta_time, size; + uint32_t size; uint8_t status, meta; int res, running; + long offs; + + event->data = NULL; if ((res = peek_next(mf, event)) <= 0) return res; tr = &mf->tracks[event->track]; - status = *tr->p; + + offs = tr->pos; + if (offs != mf->pos) { + if (fseek(mf->file, offs, SEEK_SET) != 0) + return -errno; + } + + mf_read(mf, &status, 1); running = (status & 0x80) == 0; if (running) { + tr->event[1] = status; status = tr->event[0]; - event->data = tr->event; } else { - event->data = tr->p++; tr->event[0] = status; } @@ -350,52 +387,82 @@ int midi_file_read_event(struct midi_file *mf, struct midi_event *event) break; case 0xff: - meta = *tr->p++; + if (running) + return -EINVAL; + + mf_read(mf, &meta, 1); if ((res = parse_varlen(mf, tr, &size)) < 0) return res; - event->meta.offset = tr->p - event->data; + event->meta.offset = 2; event->meta.size = size; + if ((event->data = ensure_buffer(mf, tr, size + event->meta.offset)) == NULL) + return -ENOMEM; + + event->data[0] = status; + event->data[1] = meta; + if (size > 0 && mf_read(mf, &event->data[2], size) != 1) + return -EINVAL; + switch (meta) { case 0x2f: tr->eof = true; break; case 0x51: + { if (size < 3) return -EINVAL; mf->tick_sec = event->sec; mf->tick_start = tr->tick; - event->meta.parsed.tempo.uspqn = mf->tempo = (tr->p[0]<<16) | (tr->p[1]<<8) | tr->p[2]; + event->meta.parsed.tempo.uspqn = mf->tempo = + (event->data[2]<<16) | (event->data[3]<<8) | event->data[4]; break; } - size += tr->p - event->data; + } + size += event->meta.offset; break; case 0xf0: case 0xf7: + if (running) + return -EINVAL; + if ((res = parse_varlen(mf, tr, &size)) < 0) return res; - size += tr->p - event->data; + + if ((event->data = ensure_buffer(mf, tr, size + 1)) == NULL) + return -ENOMEM; + + event->data[0] = status; + if (mf_read(mf, &event->data[1], size) != 1) + return -EINVAL; + + size += 1; break; default: return -EINVAL; } event->size = size; - - if (running) { - memcpy(&event->data[1], tr->p, size - 1); - tr->p += size - 1; - } else { - tr->p = event->data + event->size; + if (event->data == NULL) { + if ((event->data = ensure_buffer(mf, tr, size)) == NULL) + return -ENOMEM; + event->data[0] = tr->event[0]; + if (running) { + event->data[1] = tr->event[1]; + if (size > 2 && mf_read(mf, &event->data[2], size - 2) != 1) + return -EINVAL; + } else { + if (size > 1 && mf_read(mf, &event->data[1], size - 1) != 1) + return -EINVAL; + } } - if ((res = parse_varlen(mf, tr, &delta_time)) < 0) + if ((res = read_delta_time(mf, tr)) < 0) return res; - tr->tick += delta_time; return 1; } @@ -412,7 +479,7 @@ static int write_varlen(struct midi_file *mf, struct midi_track *tr, uint32_t va } do { b = buffer & 0xff; - CHECK_RES(write_n(mf->fd, &b, 1)); + CHECK_RES(write_n(mf->file, &b, 1)); tr->size++; buffer >>= 8; } while (b & 0x80); @@ -424,13 +491,31 @@ int midi_file_write_event(struct midi_file *mf, const struct midi_event *event) { struct midi_track *tr; uint32_t tick; + void *data; + size_t size; int res; + uint8_t ev[32]; spa_return_val_if_fail(event != NULL, -EINVAL); spa_return_val_if_fail(mf != NULL, -EINVAL); spa_return_val_if_fail(event->track == 0, -EINVAL); spa_return_val_if_fail(event->size > 1, -EINVAL); + switch (event->type) { + case MIDI_EVENT_TYPE_MIDI1: + data = event->data; + size = event->size; + break; + case MIDI_EVENT_TYPE_UMP: + data = ev; + size = spa_ump_to_midi((uint32_t*)event->data, event->size, ev, sizeof(ev)); + if (size == 0) + return 0; + break; + default: + return -EINVAL; + } + tr = &mf->tracks[event->track]; tick = (uint32_t)(event->sec * (1000000.0 * mf->info.division) / (double)mf->tempo); @@ -438,8 +523,8 @@ int midi_file_write_event(struct midi_file *mf, const struct midi_event *event) CHECK_RES(write_varlen(mf, tr, tick - tr->tick)); tr->tick = tick; - CHECK_RES(write_n(mf->fd, event->data, event->size)); - tr->size += event->size; + CHECK_RES(write_n(mf->file, data, size)); + tr->size += size; return 0; } @@ -587,7 +672,7 @@ static void dump_mem(FILE *out, const char *label, uint8_t *data, uint32_t size) fprintf(out, "%02x ", *data++); } -int midi_file_dump_event(FILE *out, const struct midi_event *ev) +static int dump_event_midi1(FILE *out, const struct midi_event *ev) { fprintf(out, "track:%2d sec:%f ", ev->track, ev->sec); @@ -662,19 +747,21 @@ int midi_file_dump_event(FILE *out, const struct midi_event *ev) fprintf(out, "Active Sensing"); break; case 0xff: + { + uint8_t *meta = &ev->data[ev->meta.offset]; fprintf(out, "Meta: "); switch (ev->data[1]) { case 0x00: - fprintf(out, "Sequence Number %3d %3d", ev->data[3], ev->data[4]); + fprintf(out, "Sequence Number %3d %3d", meta[0], meta[1]); break; case 0x01 ... 0x09: - fprintf(out, "%s: %s", event_names[ev->data[1] - 1], &ev->data[ev->meta.offset]); + fprintf(out, "%s: %s", event_names[ev->data[1] - 1], meta); break; case 0x20: - fprintf(out, "Channel Prefix: %03d", ev->data[3]); + fprintf(out, "Channel Prefix: %03d", meta[0]); break; case 0x21: - fprintf(out, "Midi Port: %03d", ev->data[3]); + fprintf(out, "Midi Port: %03d", meta[0]); break; case 0x2f: fprintf(out, "End Of Track"); @@ -686,20 +773,20 @@ int midi_file_dump_event(FILE *out, const struct midi_event *ev) break; case 0x54: fprintf(out, "SMPTE Offset: %s %02d:%02d:%02d:%02d.%03d", - smpte_rates[(ev->data[3] & 0x60) >> 5], - ev->data[3] & 0x1f, ev->data[4], ev->data[5], - ev->data[6], ev->data[7]); + smpte_rates[(meta[0] & 0x60) >> 5], + meta[0] & 0x1f, meta[1], meta[2], + meta[3], meta[4]); break; case 0x58: fprintf(out, "Time Signature: %d/%d, %d clocks per click, %d notated 32nd notes per quarter note", - ev->data[3], (int)pow(2, ev->data[4]), ev->data[5], ev->data[6]); + meta[0], (int)pow(2, meta[1]), meta[2], meta[3]); break; case 0x59: { - int sf = ev->data[3]; + int sf = meta[0]; fprintf(out, "Key Signature: %d %s: %s", abs(sf), sf > 0 ? "sharps" : "flats", - ev->data[4] == 0 ? + meta[1] == 0 ? major_keys[SPA_CLAMP(sf + 9, 0, 18)] : minor_keys[SPA_CLAMP(sf + 9, 0, 18)]); break; @@ -711,10 +798,217 @@ int midi_file_dump_event(FILE *out, const struct midi_event *ev) dump_mem(out, "Invalid", ev->data, ev->size); } break; + } default: dump_mem(out, "Unknown", ev->data, ev->size); break; } - fprintf(out, "\n"); return 0; } + +static int dump_event_midi2_channel(FILE *out, const struct midi_event *ev) +{ + uint32_t *d = (uint32_t*)ev->data; + uint8_t status = d[0] >> 16; + + fprintf(out, "track:%2d sec:%f ", ev->track, ev->sec); + + switch (status) { + case 0x00 ... 0x0f: + case 0x10 ... 0x1f: + { + uint8_t note = (d[0] >> 8) & 0x7f; + uint8_t index = d[0] & 0xff; + fprintf(out, "%s Per-Note controller (channel %2d): note %3s%d, index %u, value %u", + (status & 0xf0) == 0x00 ? "Registered" : "Assignable", + (status & 0x0f) + 1, + note_names[note % 12], note / 12 -1, index, d[1]); + break; + } + case 0x20 ... 0x2f: + case 0x30 ... 0x3f: + { + uint16_t index = (d[0] & 0x7f) | ((d[0] & 0x7f00) >> 1); + fprintf(out, "%s controller (channel %2d): index %u, value %u", + (status & 0xf0) == 0x20 ? "Registered" : "Assignable", + (status & 0x0f) + 1, index, d[1]); + break; + } + case 0x40 ... 0x4f: + case 0x50 ... 0x5f: + { + uint16_t index = (d[0] & 0x7f) | ((d[0] & 0x7f00) >> 1); + fprintf(out, "Relative %s controller (channel %2d): index %u, value %u", + (status & 0xf0) == 0x20 ? "Registered" : "Assignable", + (status & 0x0f) + 1, index, d[1]); + break; + } + case 0x60 ... 0x6f: + { + uint8_t note = (d[0] >> 8) & 0x7f; + fprintf(out, "Per-Note Pitch Bend (channel %2d): note %3s%d, pitch %u", + (status & 0x0f) + 1, + note_names[note % 12], note / 12 -1, d[1]); + break; + } + case 0x80 ... 0x8f: + { + uint8_t note = (d[0] >> 8) & 0x7f; + uint8_t attr_type = d[0] & 0xff; + uint16_t velocity = (d[1] >> 16) & 0xffff; + uint16_t attr_data = (d[1]) & 0xffff; + fprintf(out, "Note Off (channel %2d): note %3s%d, velocity %5d, attr (%u)%u", + (status & 0x0f) + 1, + note_names[note % 12], note / 12 -1, + velocity, attr_type, attr_data); + break; + } + case 0x90 ... 0x9f: + { + uint8_t note = (d[0] >> 8) & 0x7f; + uint8_t attr_type = d[0] & 0xff; + uint16_t velocity = (d[1] >> 16) & 0xffff; + uint16_t attr_data = (d[1]) & 0xffff; + fprintf(out, "Note On (channel %2d): note %3s%d, velocity %5d, attr (%u)%u", + (status & 0x0f) + 1, + note_names[note % 12], note / 12 -1, + velocity, attr_type, attr_data); + break; + } + case 0xa0 ... 0xaf: + { + uint8_t note = (d[0] >> 8) & 0x7f; + fprintf(out, "Aftertouch (channel %2d): note %3s%d, pressure %u", + (status & 0x0f) + 1, + note_names[note % 12], note / 12 -1, d[1]); + break; + } + case 0xb0 ... 0xbf: + { + uint8_t index = (d[0] >> 8) & 0x7f; + fprintf(out, "Controller (channel %2d): controller %3d (%s), value %u", + (status & 0x0f) + 1, index, + controller_name(index), d[1]); + break; + } + case 0xc0 ... 0xcf: + { + uint8_t flags = (d[0] & 0xff); + uint8_t program = (d[1] >> 24) & 0x7f; + uint16_t bank = (d[1] & 0x7f) | ((d[1] & 0x7f00) >> 1); + fprintf(out, "Program (channel %2d): flags %u program %3d (%s), bank %u", + (status & 0x0f) + 1, flags, program, + program_names[program], bank); + break; + } + case 0xd0 ... 0xdf: + fprintf(out, "Channel Pressure (channel %2d): pressure %u", + (status & 0x0f) + 1, d[1]); + break; + case 0xe0 ... 0xef: + fprintf(out, "Pitch Bend (channel %2d): value %u", + (status & 0x0f) + 1, d[1]); + break; + case 0xf0 ... 0xff: + { + uint8_t note = (d[0] >> 8) & 0x7f; + uint8_t flags = d[0] & 0xff; + fprintf(out, "Per-Note management (channel %2d): note %3s%d, flags %u", + (status & 0x0f) + 1, + note_names[note % 12], note / 12 -1, flags); + break; + } + default: + dump_mem(out, "Unknown", ev->data, ev->size); + break; + } + + return 0; +} + +static int dump_event_ump(FILE *out, const struct midi_event *ev) +{ + uint32_t *d = (uint32_t*)ev->data; + uint8_t group = (d[0] >> 24) & 0xf; + uint8_t mt = (d[0] >> 28) & 0xf; + int res = 0; + + fprintf(out, "group:%2d ", group); + + switch (mt) { + case 0x0: + dump_mem(out, "Utility", ev->data, ev->size); + break; + case 0x1: + dump_mem(out, "SysRT", ev->data, ev->size); + break; + case 0x2: + { + struct midi_event ev1; + uint8_t msg[4]; + + ev1 = *ev; + msg[0] = (d[0] >> 16); + msg[1] = (d[0] >> 8); + msg[2] = (d[0]); + if (msg[0] >= 0xc0 && msg[0] <= 0xdf) + ev1.size = 2; + else + ev1.size = 3; + ev1.data = msg; + dump_event_midi1(out, &ev1); + break; + } + case 0x3: + { + uint8_t status = (d[0] >> 20) & 0xf; + uint8_t bytes = SPA_CLAMP((d[0] >> 16) & 0xf, 0u, 6u); + uint8_t b[6] = { d[0] >> 8, d[0], d[1] >> 24, d[1] >> 16, d[1] >> 8, d[1] }; + switch (status) { + case 0x0: + dump_mem(out, "SysEx7 (Complete) ", b, bytes); + break; + case 0x1: + dump_mem(out, "SysEx7 (Start) ", b, bytes); + break; + case 0x2: + dump_mem(out, "SysEx7 (Continue) ", b, bytes); + break; + case 0x3: + dump_mem(out, "SysEx7 (End) ", b, bytes); + break; + default: + dump_mem(out, "SysEx7 (invalid)", ev->data, ev->size); + break; + } + break; + } + case 0x4: + res = dump_event_midi2_channel(out, ev); + break; + case 0x5: + dump_mem(out, "Data128", ev->data, ev->size); + break; + default: + dump_mem(out, "Reserved", ev->data, ev->size); + break; + } + return res; +} + +int midi_file_dump_event(FILE *out, const struct midi_event *ev) +{ + int res; + switch (ev->type) { + case MIDI_EVENT_TYPE_MIDI1: + res = dump_event_midi1(out, ev); + break; + case MIDI_EVENT_TYPE_UMP: + res = dump_event_ump(out, ev); + break; + default: + return -EINVAL; + } + fprintf(out, "\n"); + return res; +} diff --git a/src/tools/midifile.h b/src/tools/midifile.h index 767be7d7..6b3c23b2 100644 --- a/src/tools/midifile.h +++ b/src/tools/midifile.h @@ -9,6 +9,9 @@ struct midi_file; struct midi_event { +#define MIDI_EVENT_TYPE_MIDI1 0 +#define MIDI_EVENT_TYPE_UMP 1 + uint32_t type; uint32_t track; double sec; uint8_t *data; diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 377be270..f485a08f 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -19,7 +19,8 @@ #include <spa/param/audio/layout.h> #include <spa/param/audio/format-utils.h> -#include <spa/param/audio/type-info.h> +#include <spa/param/audio/raw-json.h> +#include <spa/utils/type-info.h> #include <spa/param/tag-utils.h> #include <spa/param/props.h> #include <spa/utils/result.h> @@ -27,6 +28,7 @@ #include <spa/utils/json.h> #include <spa/debug/types.h> #include <spa/debug/file.h> +#include <spa/control/ump-utils.h> #include <pipewire/pipewire.h> #include <pipewire/i18n.h> @@ -78,8 +80,8 @@ struct data; typedef int (*fill_fn)(struct data *d, void *dest, unsigned int n_frames, bool *null_frame); struct channelmap { - int n_channels; - int channels[SPA_AUDIO_MAX_CHANNELS]; + uint32_t n_channels; + uint32_t channels[SPA_AUDIO_MAX_CHANNELS]; }; struct data { @@ -102,6 +104,7 @@ struct data { #define TYPE_ENCODED 3 #endif int data_type; + bool raw; const char *remote_name; const char *media_type; const char *media_category; @@ -117,7 +120,7 @@ struct data { unsigned int bitrate; unsigned int rate; - int channels; + uint32_t channels; struct channelmap channelmap; unsigned int stride; enum unit latency_unit; @@ -564,10 +567,10 @@ static int channelmap_from_sf(struct channelmap *map) [SF_CHANNEL_MAP_TOP_REAR_RIGHT] = SPA_AUDIO_CHANNEL_TRR, [SF_CHANNEL_MAP_TOP_REAR_CENTER] = SPA_AUDIO_CHANNEL_TRC }; - int i; + uint32_t i; for (i = 0; i < map->n_channels; i++) { - if (map->channels[i] >= 0 && map->channels[i] < (int) SPA_N_ELEMENTS(table)) + if (map->channels[i] < SPA_N_ELEMENTS(table)) map->channels[i] = table[map->channels[i]]; else map->channels[i] = SPA_AUDIO_CHANNEL_UNKNOWN; @@ -576,8 +579,8 @@ static int channelmap_from_sf(struct channelmap *map) } struct mapping { const char *name; - unsigned int channels; - unsigned int values[32]; + uint32_t channels; + uint32_t values[32]; }; static const struct mapping maps[] = @@ -597,21 +600,8 @@ static const struct mapping maps[] = { "surround-71", SPA_AUDIO_LAYOUT_7_1 }, }; -static unsigned int find_channel(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 int parse_channelmap(const char *channel_map, struct channelmap *map) { - int i, nch; - SPA_FOR_EACH_ELEMENT_VAR(maps, m) { if (spa_streq(m->name, channel_map)) { map->n_channels = m->channels; @@ -621,16 +611,7 @@ static int parse_channelmap(const char *channel_map, struct channelmap *map) } } - spa_auto(pw_strv) ch = pw_split_strv(channel_map, ",", SPA_AUDIO_MAX_CHANNELS, &nch); - if (ch == NULL) - return -1; - - map->n_channels = nch; - for (i = 0; i < map->n_channels; i++) { - int c = find_channel(ch[i]); - map->channels[i] = c; - } - + spa_audio_parse_position(channel_map, strlen(channel_map), map->channels, &map->n_channels); return 0; } @@ -671,13 +652,11 @@ static int channelmap_default(struct channelmap *map, int n_channels) static void channelmap_print(struct channelmap *map) { - int i; + uint32_t i; for (i = 0; i < map->n_channels; i++) { - const char *name = spa_debug_type_find_name(spa_type_audio_channel, map->channels[i]); - if (name == NULL) - name = ":UNK"; - printf("%s%s", spa_debug_type_short_name(name), i + 1 < map->n_channels ? "," : ""); + const char *name = spa_type_audio_channel_to_short_name(map->channels[i]); + fprintf(stderr, "%s%s", name, i + 1 < map->n_channels ? "," : ""); } } @@ -686,7 +665,7 @@ static void on_core_info(void *userdata, const struct pw_core_info *info) struct data *data = userdata; if (data->verbose) - printf("remote %"PRIu32" is named \"%s\"\n", + fprintf(stderr, "remote %"PRIu32" is named \"%s\"\n", info->id, info->name); } @@ -715,7 +694,7 @@ on_state_changed(void *userdata, enum pw_stream_state old, int ret; if (data->verbose) - printf("stream state changed %s -> %s\n", + fprintf(stderr, "stream state changed %s -> %s\n", pw_stream_state_as_string(old), pw_stream_state_as_string(state)); @@ -726,7 +705,7 @@ on_state_changed(void *userdata, enum pw_stream_state old, SPA_PROP_volume, 1, &data->volume, 0); if (data->verbose) - printf("stream set volume to %.3f - %s\n", data->volume, + fprintf(stderr, "stream set volume to %.3f - %s\n", data->volume, ret == 0 ? "success" : "FAILED"); data->volume_is_set = true; @@ -735,7 +714,7 @@ on_state_changed(void *userdata, enum pw_stream_state old, struct timespec timeout = {0, 1}, interval = {1, 0}; struct pw_loop *l = pw_main_loop_get_loop(data->loop); pw_loop_update_timer(l, data->timer, &timeout, &interval, false); - printf("stream node %"PRIu32"\n", + fprintf(stderr, "stream node %"PRIu32"\n", pw_stream_get_node_id(data->stream)); } break; @@ -747,13 +726,13 @@ on_state_changed(void *userdata, enum pw_stream_state old, } break; case PW_STREAM_STATE_ERROR: - printf("stream node %"PRIu32" error: %s\n", + fprintf(stderr, "stream node %"PRIu32" error: %s\n", pw_stream_get_node_id(data->stream), error); pw_main_loop_quit(data->loop); break; case PW_STREAM_STATE_UNCONNECTED: - printf("stream node %"PRIu32" unconnected\n", + fprintf(stderr, "stream node %"PRIu32" unconnected\n", pw_stream_get_node_id(data->stream)); pw_main_loop_quit(data->loop); break; @@ -784,7 +763,7 @@ on_param_changed(void *userdata, uint32_t id, const struct spa_pod *param) int err; if (data->verbose) - printf("stream param change: %s\n", + fprintf(stderr, "stream param change: %s\n", spa_debug_type_find_name(spa_type_param, id)); if (id != SPA_PARAM_Format || param == NULL) @@ -811,7 +790,7 @@ on_param_changed(void *userdata, uint32_t id, const struct spa_pod *param) data->stride = data->dsf.layout.channels * SPA_ABS(data->dsf.layout.interleave); if (data->verbose) { - printf("DSD: channels:%d bitorder:%s interleave:%d stride:%d\n", + fprintf(stderr, "DSD: channels:%d bitorder:%s interleave:%d stride:%d\n", data->dsf.layout.channels, data->dsf.layout.lsb ? "lsb" : "msb", data->dsf.layout.interleave, @@ -873,7 +852,7 @@ static void on_process(void *userdata) fprintf(stderr, "fill error %d\n", n_fill_frames); } else { if (data->verbose) - printf("drain start\n"); + fprintf(stderr, "drain start\n"); } } else { bool null_frame = false; @@ -904,7 +883,7 @@ static void on_drained(void *userdata) struct data *data = userdata; if (data->verbose) - printf("stream drained\n"); + fprintf(stderr, "stream drained\n"); data->drained = true; pw_main_loop_quit(data->loop); @@ -930,7 +909,7 @@ static void do_print_delay(void *userdata, uint64_t expirations) struct data *data = userdata; struct pw_time time; pw_stream_get_time_n(data->stream, &time, sizeof(time)); - printf("stream time: now:%"PRIi64" rate:%u/%u ticks:%"PRIu64 + fprintf(stderr, "stream time: now:%"PRIi64" rate:%u/%u ticks:%"PRIu64 " delay:%"PRIi64" queued:%"PRIu64 " buffered:%"PRIi64" buffers:%u avail:%u size:%"PRIu64"\n", time.now, @@ -961,6 +940,8 @@ static const struct option long_options[] = { { "record", no_argument, NULL, 'r' }, { "playback", no_argument, NULL, 'p' }, { "midi", no_argument, NULL, 'm' }, + { "dsd", no_argument, NULL, 'd' }, + { "encoded", no_argument, NULL, 'o' }, { "remote", required_argument, NULL, 'R' }, @@ -977,6 +958,7 @@ static const struct option long_options[] = { { "format", required_argument, NULL, OPT_FORMAT }, { "volume", required_argument, NULL, OPT_VOLUME }, { "quality", required_argument, NULL, 'q' }, + { "raw", no_argument, NULL, 'a' }, { NULL, 0, NULL, 0 } }; @@ -1021,6 +1003,7 @@ static void show_usage(const char *name, bool is_error) " --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" + " -a, --raw RAW mode\n" "\n"), DEFAULT_RATE, DEFAULT_CHANNELS, @@ -1080,13 +1063,36 @@ static int midi_play(struct data *d, void *src, unsigned int n_frames, bool *nul midi_file_read_event(d->midi.file, &ev); if (d->verbose) - midi_file_dump_event(stdout, &ev); + midi_file_dump_event(stderr, &ev); + + if (ev.type == MIDI_EVENT_TYPE_MIDI1) { + size_t size; + uint8_t *data; + uint64_t state = 0; - if (ev.data[0] == 0xff) + if (ev.data[0] == 0xff) + continue; + + data = ev.data; + size = ev.size; + + while (size > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&data, &size, + ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + break; + + spa_pod_builder_control(&b, frame, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ump, ump_size); + } + } else if (ev.type == MIDI_EVENT_TYPE_UMP) { + spa_pod_builder_control(&b, frame, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ev.data, ev.size); + } + else continue; - spa_pod_builder_control(&b, frame, SPA_CONTROL_Midi); - spa_pod_builder_bytes(&b, ev.data, ev.size); have_data = true; } spa_pod_builder_pop(&b, &f); @@ -1111,16 +1117,17 @@ static int midi_record(struct data *d, void *src, unsigned int n_frames, bool *n SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c) { struct midi_event ev; - if (c->type != SPA_CONTROL_Midi) + if (c->type != SPA_CONTROL_UMP) continue; ev.track = 0; ev.sec = (frame + c->offset) / (float) d->position->clock.rate.denom; ev.data = SPA_POD_BODY(&c->value), ev.size = SPA_POD_BODY_SIZE(&c->value); + ev.type = MIDI_EVENT_TYPE_UMP; if (d->verbose) - midi_file_dump_event(stdout, &ev); + midi_file_dump_event(stderr, &ev); midi_file_write_event(d->midi.file, &ev); } @@ -1145,7 +1152,7 @@ static int setup_midifile(struct data *data) } if (data->verbose) - printf("midifile: opened file \"%s\" format %08x ntracks:%d div:%d\n", + fprintf(stderr, "midifile: opened file \"%s\" format %08x ntracks:%d div:%d\n", data->filename, data->midi.info.format, data->midi.info.ntracks, data->midi.info.division); @@ -1196,7 +1203,7 @@ static int setup_dsdfile(struct data *data) if (data->dsf.file != NULL) { if (data->verbose) - printf("dsffile: opened file \"%s\" channels:%d rate:%d " + fprintf(stderr, "dsffile: opened file \"%s\" channels:%d rate:%d " "samples:%"PRIu64" bitorder:%s\n", data->filename, data->dsf.info.channels, data->dsf.info.rate, @@ -1206,7 +1213,7 @@ static int setup_dsdfile(struct data *data) data->fill = dsf_play; } else { if (data->verbose) - printf("dfffile: opened file \"%s\" channels:%d rate:%d " + fprintf(stderr, "dfffile: opened file \"%s\" channels:%d rate:%d " "samples:%"PRIu64" bitorder:%s\n", data->filename, data->dff.info.channels, data->dff.info.rate, @@ -1250,7 +1257,7 @@ static int setup_pipe(struct data *data) 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", + fprintf(stderr, "PIPE: rate=%u channels=%u fmt=%s samplesize=%u stride=%u\n", data->rate, data->channels, info->name, info->width, data->stride); @@ -1420,7 +1427,7 @@ static int setup_encodedfile(struct data *data) data->fill = encoded_playback_fill; if (data->verbose) { - printf("Opened file \"%s\" with encoded audio; channels:%d rate:%d bitrate: %d time units %d/%d\n", + fprintf(stderr, "Opened file \"%s\" with encoded audio; channels:%d rate:%d bitrate: %d time units %d/%d\n", data->filename, data->channels, data->rate, data->bitrate, data->encoded.audio_stream->time_base.num, data->encoded.audio_stream->time_base.den); } @@ -1467,9 +1474,9 @@ static int setup_sndfile(struct data *data) } if (data->verbose) - printf("sndfile: opened file \"%s\" format %08x channels:%d rate:%d\n", + fprintf(stderr, "sndfile: opened file \"%s\" format %08x channels:%d rate:%d\n", data->filename, info.format, info.channels, info.samplerate); - if (data->channels > 0 && info.channels != data->channels) { + if (data->channels > 0 && info.channels != (int)data->channels) { fprintf(stderr, "sndfile: given channels (%u) don't match file channels (%d)\n", data->channels, info.channels); return -EINVAL; @@ -1494,9 +1501,9 @@ static int setup_sndfile(struct data *data) def = true; } if (data->verbose) { - printf("sndfile: using %s channel map: ", def ? "default" : "file"); + fprintf(stderr, "sndfile: using %s channel map: ", def ? "default" : "file"); channelmap_print(&data->channelmap); - printf("\n"); + fprintf(stderr, "\n"); } } fill_properties(data); @@ -1510,7 +1517,7 @@ static int setup_sndfile(struct data *data) return -EIO; if (data->verbose) - printf("PCM: fmt:%s rate:%u channels:%u width:%u\n", + fprintf(stderr, "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 */ @@ -1590,7 +1597,7 @@ static int setup_properties(struct data *data) } if (data->verbose) - printf("rate:%d latency:%u (%.3fs)\n", + fprintf(stderr, "rate:%d latency:%u (%.3fs)\n", data->rate, nom, data->rate ? (double)nom/data->rate : 0.0f); if (nom && pw_properties_get(data->props, PW_KEY_NODE_LATENCY) == NULL) pw_properties_setf(data->props, PW_KEY_NODE_LATENCY, "%u/%u", nom, data->rate); @@ -1664,9 +1671,9 @@ int main(int argc, char *argv[]) } #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION - while ((c = getopt_long(argc, argv, "hvprmdoR:q:P:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hvprmdoR:q:P:a", long_options, NULL)) != -1) { #else - while ((c = getopt_long(argc, argv, "hvprmdR:q:P:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hvprmdR:q:P:a", long_options, NULL)) != -1) { #endif switch (c) { @@ -1718,6 +1725,10 @@ int main(int argc, char *argv[]) data.quality = atoi(optarg); break; + case 'a': + data.raw = true; + break; + case OPT_MEDIA_TYPE: data.media_type = optarg; break; @@ -1846,11 +1857,7 @@ int main(int argc, char *argv[]) pw_loop_add_signal(l, SIGINT, do_quit, &data); pw_loop_add_signal(l, SIGTERM, do_quit, &data); - data.context = pw_context_new(l, - pw_properties_new( - PW_KEY_CONFIG_NAME, "client-rt.conf", - NULL), - 0); + data.context = pw_context_new(l, NULL, 0); if (!data.context) { fprintf(stderr, "error: pw_context_new() failed: %m\n"); goto error_no_context; @@ -1867,7 +1874,7 @@ int main(int argc, char *argv[]) } pw_core_add_listener(data.core, &data.core_listener, &core_events, &data); - if (spa_streq(data.filename, "-")) { + if (data.raw) { ret = setup_pipe(&data); } else { switch (data.data_type) { @@ -1952,7 +1959,7 @@ int main(int argc, char *argv[]) SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); - pw_properties_set(data.props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); + pw_properties_set(data.props, PW_KEY_FORMAT_DSP, "32 bit raw UMP"); break; case TYPE_DSD: { @@ -2008,7 +2015,7 @@ int main(int argc, char *argv[]) pw_stream_add_listener(data.stream, &data.stream_listener, &stream_events, &data); if (data.verbose) - printf("connecting %s stream; target=%s\n", + fprintf(stderr, "connecting %s stream; target=%s\n", data.mode == mode_playback ? "playback" : "record", data.target); @@ -2032,11 +2039,11 @@ int main(int argc, char *argv[]) const char *key, *val; if ((props = pw_stream_get_properties(data.stream)) != NULL) { - printf("stream properties:\n"); + fprintf(stderr, "stream properties:\n"); pstate = NULL; while ((key = pw_properties_iterate(props, &pstate)) != NULL && (val = pw_properties_get(props, key)) != NULL) { - printf("\t%s = \"%s\"\n", key, val); + fprintf(stderr, "\t%s = \"%s\"\n", key, val); } } } diff --git a/src/tools/pw-cli.c b/src/tools/pw-cli.c index 4b0e5a9d..467a4be6 100644 --- a/src/tools/pw-cli.c +++ b/src/tools/pw-cli.c @@ -10,9 +10,6 @@ #include <signal.h> #include <string.h> #include <ctype.h> -#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) -#include <alloca.h> -#endif #include <getopt.h> #include <fnmatch.h> #ifdef HAVE_READLINE @@ -30,6 +27,7 @@ #include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/debug/pod.h> +#include <spa/debug/file.h> #include <spa/utils/keys.h> #include <spa/utils/json-pod.h> #include <spa/pod/dynamic.h> @@ -163,14 +161,17 @@ static void print_params(struct spa_param_info *params, uint32_t n_params, char } } +#if 0 static bool do_not_implemented(struct data *data, const char *cmd, char *args, char **error) { *error = spa_aprintf("Command \"%s\" not yet implemented", cmd); return false; } +#endif static bool do_help(struct data *data, const char *cmd, char *args, char **error); static bool do_load_module(struct data *data, const char *cmd, char *args, char **error); +static bool do_unload_module(struct data *data, const char *cmd, char *args, char **error); static bool do_list_objects(struct data *data, const char *cmd, char *args, char **error); static bool do_connect(struct data *data, const char *cmd, char *args, char **error); static bool do_disconnect(struct data *data, const char *cmd, char *args, char **error); @@ -194,7 +195,7 @@ static bool do_quit(struct data *data, const char *cmd, char *args, char **error static const struct command command_list[] = { { "help", "h", "Show this help", do_help }, { "load-module", "lm", "Load a module. <module-name> [<module-arguments>]", do_load_module }, - { "unload-module", "um", "Unload a module. <module-var>", do_not_implemented }, + { "unload-module", "um", "Unload a module. <module-var>", do_unload_module }, { "connect", "con", "Connect to a remote. [<remote-name>]", do_connect }, { "disconnect", "dis", "Disconnect from a remote. [<remote-var>]", do_disconnect }, { "list-remotes", "lr", "List connected remotes.", do_list_remotes }, @@ -263,6 +264,29 @@ static bool do_load_module(struct data *data, const char *cmd, char *args, char return true; } +static bool do_unload_module(struct data *data, const char *cmd, char *args, char **error) +{ + char *a[1]; + int n; + struct pw_impl_module *module; + uint32_t idx; + + n = pw_split_ip(args, WHITESPACE, 1, a); + if (n < 1) { + *error = spa_aprintf("%s <module-var>", cmd); + return false; + } + idx = atoi(a[0]); + module = pw_map_lookup(&data->vars, idx); + if (module == NULL) { + *error = spa_aprintf("%s: unknown module '%s'", cmd, a[0]); + return false; + } + pw_map_remove(&data->vars, idx); + pw_impl_module_destroy(module); + return true; +} + static void on_core_info(void *_data, const struct pw_core_info *info) { struct remote_data *rd = _data; @@ -1780,6 +1804,7 @@ static bool do_set_param(struct data *data, const char *cmd, char *args, char ** spa_auto(spa_pod_dynamic_builder) b = { 0 }; const struct spa_type_info *ti; struct spa_pod *pod; + struct spa_error_location loc; spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); @@ -1804,8 +1829,13 @@ static bool do_set_param(struct data *data, const char *cmd, char *args, char ** *error = spa_aprintf("%s: unknown param type: %s", cmd, a[1]); return false; } - if ((res = spa_json_to_pod(&b.b, 0, ti, a[2], strlen(a[2]))) < 0) { - *error = spa_aprintf("%s: can't make pod: %s", cmd, spa_strerror(res)); + if ((res = spa_json_to_pod_checked(&b.b, 0, ti, a[2], strlen(a[2]), &loc)) < 0) { + if (loc.line != 0) { + spa_debug_file_error_location(stderr, &loc, + "syntax error in json '%s': %s", + a[2], loc.reason); + } + *error = spa_aprintf("%s: invalid pod: %s", cmd, loc.reason); return false; } if ((pod = spa_pod_builder_deref(&b.b, 0)) == NULL) { @@ -2129,15 +2159,6 @@ children_of(struct remote_data *rd, uint32_t parent_id, return count; } -#define INDENT(_level) \ - ({ \ - int __level = (_level); \ - char *_indent = alloca(__level + 1); \ - memset(_indent, '\t', __level); \ - _indent[__level] = '\0'; \ - (const char *)_indent; \ - }) - static bool parse(struct data *data, char *buf, char **error) { char *a[2]; diff --git a/src/tools/pw-config.c b/src/tools/pw-config.c index de38095f..bfc64e42 100644 --- a/src/tools/pw-config.c +++ b/src/tools/pw-config.c @@ -66,8 +66,7 @@ static int do_merge_section(void *data, const char *location, const char *sectio int l; const char *value; - spa_json_init(&it[0], str, len); - if ((l = spa_json_next(&it[0], &value)) <= 0) + if ((l = spa_json_begin(&it[0], str, len, &value)) <= 0) return 0; if (spa_json_is_array(value, l)) { diff --git a/src/tools/pw-container.c b/src/tools/pw-container.c index daa2c7b6..02b7c881 100644 --- a/src/tools/pw-container.c +++ b/src/tools/pw-container.c @@ -96,8 +96,8 @@ static void core_event_done(void *object, uint32_t id, int seq) static int roundtrip(struct data *data) { struct spa_hook core_listener; - const struct pw_core_events core_events = { - PW_VERSION_CORE_EVENTS, + static const struct pw_core_events core_events = { + PW_VERSION_CORE_EVENTS, .done = core_event_done, }; spa_zero(core_listener); @@ -150,7 +150,7 @@ int main(int argc, char *argv[]) }; struct spa_error_location loc; int c, res, listen_fd, close_fd[2]; - char temp[PATH_MAX] = "/tmp/pipewire-XXXXXX"; + char temp[] = "/tmp/pipewire-XXXXXX"; struct sockaddr_un sockaddr = {0}; data.props = pw_properties_new( @@ -209,8 +209,8 @@ int main(int argc, char *argv[]) data.core = pw_context_connect(data.context, pw_properties_new( - PW_KEY_REMOTE_NAME, opt_remote ? opt_remote : - ("[" PW_DEFAULT_REMOTE "-manager," PW_DEFAULT_REMOTE "]"), + PW_KEY_REMOTE_INTENTION, "manager", + PW_KEY_REMOTE_NAME, opt_remote, NULL), 0); if (data.core == NULL) { diff --git a/src/tools/pw-dot.c b/src/tools/pw-dot.c index 089f0565..8536e130 100644 --- a/src/tools/pw-dot.c +++ b/src/tools/pw-dot.c @@ -1276,7 +1276,7 @@ static int get_data_from_json(struct data *data, const char *json_path) int fd, len; void *json; struct stat sbuf; - struct spa_json it[2]; + struct spa_json it[1]; const char *value; struct spa_error_location loc; @@ -1296,18 +1296,16 @@ static int get_data_from_json(struct data *data, const char *json_path) } close(fd); - spa_json_init(&it[0], json, sbuf.st_size); - - if (spa_json_enter_array(&it[0], &it[1]) <= 0) { + if (spa_json_begin_array(&it[0], json, sbuf.st_size) <= 0) { fprintf(stderr, "expected top-level array in JSON file '%s'\n", json_path); munmap(json, sbuf.st_size); return -1; } - while ((len = spa_json_next(&it[1], &value)) > 0 && spa_json_is_object(value, len)) { + while ((len = spa_json_next(&it[0], &value)) > 0 && spa_json_is_object(value, len)) { struct pw_properties *obj; obj = pw_properties_new(NULL, NULL); - len = spa_json_container_len(&it[1], value, len); + len = spa_json_container_len(&it[0], value, len); pw_properties_update_string(obj, value, len); handle_json_obj(data, obj); pw_properties_free(obj); diff --git a/src/tools/pw-dump.c b/src/tools/pw-dump.c index e55e2e60..179e0c67 100644 --- a/src/tools/pw-dump.c +++ b/src/tools/pw-dump.c @@ -1126,11 +1126,8 @@ static void json_dump_val(struct data *d, const char *key, struct spa_json *it, char val[1024]; put_begin(d, key, "{", STATE_SIMPLE); spa_json_enter(it, &sub); - while (spa_json_get_string(&sub, val, sizeof(val)) > 0) { - if ((len = spa_json_next(&sub, &value)) <= 0) - break; + while ((len = spa_json_object_next(&sub, val, sizeof(val), &value)) > 0) json_dump_val(d, val, &sub, value, len); - } put_end(d, "}", STATE_SIMPLE); } else if (spa_json_is_string(value, len)) { put_encoded_string(d, key, strndupa(value, len)); @@ -1144,8 +1141,7 @@ static void json_dump(struct data *d, const char *key, const char *value) struct spa_json it[1]; int len; const char *val; - spa_json_init(&it[0], value, strlen(value)); - if ((len = spa_json_next(&it[0], &val)) >= 0) + if ((len = spa_json_begin(&it[0], value, strlen(value), &val)) >= 0) json_dump_val(d, key, &it[0], val, len); } @@ -1608,8 +1604,8 @@ int main(int argc, char *argv[]) data.core = pw_context_connect(data.context, pw_properties_new( - PW_KEY_REMOTE_NAME, opt_remote ? opt_remote : - ("[" PW_DEFAULT_REMOTE "-manager," PW_DEFAULT_REMOTE "]"), + PW_KEY_REMOTE_INTENTION, "manager", + PW_KEY_REMOTE_NAME, opt_remote, NULL), 0); if (data.core == NULL) { diff --git a/src/tools/pw-loopback.c b/src/tools/pw-loopback.c index 1f77f6ae..65a613c1 100644 --- a/src/tools/pw-loopback.c +++ b/src/tools/pw-loopback.c @@ -77,9 +77,9 @@ static void show_help(struct data *data, const char *name, bool error) " -l, --latency Desired latency in ms\n" " -d, --delay Desired delay in float s\n" " -C --capture Capture source to connect to (name or serial)\n" - " --capture-props Capture stream properties\n" + " -i --capture-props Capture stream properties\n" " -P --playback Playback sink to connect to (name or serial)\n" - " --playback-props Playback stream properties\n", + " -o --playback-props Playback stream properties\n", name, data->opt_node_name, data->opt_group_name, diff --git a/src/tools/pw-mididump.c b/src/tools/pw-mididump.c index 7fa672f9..a29bb725 100644 --- a/src/tools/pw-mididump.c +++ b/src/tools/pw-mididump.c @@ -13,6 +13,7 @@ #include <spa/control/control.h> #include <spa/param/audio/format-utils.h> #include <spa/param/props.h> +#include <spa/debug/mem.h> #include <pipewire/pipewire.h> #include <pipewire/filter.h> @@ -45,7 +46,7 @@ static int dump_file(const char *filename) return -1; } - printf("opened %s\n", filename); + printf("opened %s format:%u ntracks:%u division:%u\n", filename, info.format, info.ntracks, info.division); while (midi_file_read_event(file, &ev) == 1) { midi_file_dump_event(stdout, &ev); @@ -86,15 +87,16 @@ static void on_process(void *_data, struct spa_io_position *position) SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c) { struct midi_event ev; - if (c->type != SPA_CONTROL_Midi) + if (c->type != SPA_CONTROL_UMP) continue; ev.track = 0; ev.sec = (frame + c->offset) / (float) position->clock.rate.denom; ev.data = SPA_POD_BODY(&c->value), ev.size = SPA_POD_BODY_SIZE(&c->value); + ev.type = MIDI_EVENT_TYPE_UMP; - printf("%4d: ", c->offset); + fprintf(stdout, "%4d: ", c->offset); midi_file_dump_event(stdout, &ev); } @@ -139,7 +141,7 @@ static int dump_filter(struct data *data) PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( - PW_KEY_FORMAT_DSP, "8 bit raw midi", + PW_KEY_FORMAT_DSP, "32 bit raw UMP", PW_KEY_PORT_NAME, "input", NULL), NULL, 0); diff --git a/src/tools/pw-mon.c b/src/tools/pw-mon.c index 381778b3..e66de979 100644 --- a/src/tools/pw-mon.c +++ b/src/tools/pw-mon.c @@ -886,8 +886,8 @@ int main(int argc, char *argv[]) data.core = pw_context_connect(data.context, pw_properties_new( - PW_KEY_REMOTE_NAME, opt_remote ? opt_remote : - ("[" PW_DEFAULT_REMOTE "-manager," PW_DEFAULT_REMOTE "]"), + PW_KEY_REMOTE_INTENTION, "manager", + PW_KEY_REMOTE_NAME, opt_remote, NULL), 0); if (data.core == NULL) { diff --git a/src/tools/pw-profiler.c b/src/tools/pw-profiler.c index 277c9f9b..a73cc7ec 100644 --- a/src/tools/pw-profiler.c +++ b/src/tools/pw-profiler.c @@ -9,6 +9,7 @@ #include <spa/utils/result.h> #include <spa/utils/string.h> +#include <spa/utils/json.h> #include <spa/pod/parser.h> #include <spa/debug/types.h> @@ -36,6 +37,8 @@ struct data { const char *filename; FILE *output; + bool json_dump; + uint32_t iterations; int64_t count; int64_t start_status; @@ -58,28 +61,74 @@ struct measurement { int64_t awake; int64_t finish; int32_t status; + struct spa_fraction latency; + int32_t xrun_count; }; struct point { int64_t count; float cpu_load[3]; struct spa_io_clock clock; + int transport_state; struct measurement driver; struct measurement follower[MAX_FOLLOWERS]; }; +static const char *status_to_string(int status) +{ + switch (status) { + case 0: + return "not-triggered"; + case 1: + return "triggered"; + case 2: + return "awake"; + case 3: + return "finished"; + case 4: + return "inactive"; + } + return "unknown"; +} +static const char *transport_to_string(int state) +{ + switch(state) { + case SPA_IO_POSITION_STATE_STOPPED: + return "stopped"; + case SPA_IO_POSITION_STATE_STARTING: + return "starting"; + case SPA_IO_POSITION_STATE_RUNNING: + return "running"; + } + return "unknown"; +} + static int process_info(struct data *d, const struct spa_pod *pod, struct point *point) { - return spa_pod_parse_struct(pod, + int res; + char cpu_load0[128], cpu_load1[128], cpu_load2[128]; + + res = spa_pod_parse_struct(pod, SPA_POD_Long(&point->count), SPA_POD_Float(&point->cpu_load[0]), SPA_POD_Float(&point->cpu_load[1]), SPA_POD_Float(&point->cpu_load[2])); + if (d->json_dump) { + fprintf(stdout, "{ \"type\": \"info\", \"count\": %"PRIu64", " + "\"cpu_load0\": %s, \"cpu_load1\": %s, \"cpu_load2\": %s },\n", + point->count, + spa_json_format_float(cpu_load0, sizeof(cpu_load0), point->cpu_load[0]), + spa_json_format_float(cpu_load1, sizeof(cpu_load1), point->cpu_load[1]), + spa_json_format_float(cpu_load2, sizeof(cpu_load2), point->cpu_load[2])); + } + return res; } static int process_clock(struct data *d, const struct spa_pod *pod, struct point *point) { - return spa_pod_parse_struct(pod, + int res; + char val[128]; + res = spa_pod_parse_struct(pod, SPA_POD_Int(&point->clock.flags), SPA_POD_Int(&point->clock.id), SPA_POD_Stringn(point->clock.name, sizeof(point->clock.name)), @@ -89,7 +138,25 @@ static int process_clock(struct data *d, const struct spa_pod *pod, struct point SPA_POD_Long(&point->clock.duration), SPA_POD_Long(&point->clock.delay), SPA_POD_Double(&point->clock.rate_diff), - SPA_POD_Long(&point->clock.next_nsec)); + SPA_POD_Long(&point->clock.next_nsec), + SPA_POD_Int(&point->transport_state), + SPA_POD_OPT_Int(&point->clock.cycle), + SPA_POD_OPT_Long(&point->clock.xrun)); + if (d->json_dump) { + fprintf(stdout, "{ \"type\": \"clock\", \"flags\": %u, \"id\": %u, " + "\"name\": \"%s\", \"nsec\": %"PRIu64", \"rate\": \"%u/%u\", " + "\"position\": %"PRIu64", \"duration\": %"PRIu64", " + "\"delay\": %"PRIu64", \"diff\": %s, \"next_nsec\": %"PRIu64", " + "\"transport\": \"%s\", \"cycle\": %u, \"xrun\": %"PRIu64" },\n", + point->clock.flags, point->clock.id, point->clock.name, + point->clock.nsec, point->clock.rate.num, point->clock.rate.denom, + point->clock.position, point->clock.duration, + point->clock.delay, + spa_json_format_float(val, sizeof(val), (float)point->clock.rate_diff), + point->clock.next_nsec, transport_to_string(point->transport_state), + point->clock.cycle, point->clock.xrun); + } + return res; } static int process_driver_block(struct data *d, const struct spa_pod *pod, struct point *point) @@ -107,14 +174,27 @@ static int process_driver_block(struct data *d, const struct spa_pod *pod, struc SPA_POD_Long(&driver.signal), SPA_POD_Long(&driver.awake), SPA_POD_Long(&driver.finish), - SPA_POD_Int(&driver.status))) < 0) + SPA_POD_Int(&driver.status), + SPA_POD_Fraction(&driver.latency), + SPA_POD_Int(&driver.xrun_count))) < 0) return res; + if (d->json_dump) { + fprintf(stdout, "{ \"type\": \"driver\", \"id\": %u, \"name\": \"%s\", \"prev\": %"PRIu64", " + "\"signal\": %"PRIu64", \"awake\": %"PRIu64", " + "\"finish\": %"PRIu64", \"status\": \"%s\", \"latency\": \"%u/%u\", " + "\"xrun_count\": %u },\n", + driver_id, name, driver.prev_signal, driver.signal, + driver.awake, driver.finish, status_to_string(driver.status), + driver.latency.num, driver.latency.denom, + driver.xrun_count); + } + if (d->driver_id == 0) { d->driver_id = driver_id; - printf("logging driver %u\n", driver_id); + pw_log_info("logging driver %u", driver_id); } - else if (d->driver_id != driver_id) + else if (d->driver_id != driver_id && !d->json_dump) return -1; point->driver = driver; @@ -143,7 +223,8 @@ static int add_follower(struct data *d, uint32_t id, const char *name) strncpy(d->followers[idx].name, name, MAX_NAME); d->followers[idx].name[MAX_NAME-1] = '\0'; d->followers[idx].id = id; - printf("logging follower %u (\"%s\")\n", id, name); + + pw_log_info("logging follower %u (\"%s\")", id, name); return idx; } @@ -163,9 +244,23 @@ static int process_follower_block(struct data *d, const struct spa_pod *pod, str SPA_POD_Long(&m.signal), SPA_POD_Long(&m.awake), SPA_POD_Long(&m.finish), - SPA_POD_Int(&m.status))) < 0) + SPA_POD_Int(&m.status), + SPA_POD_Fraction(&m.latency), + SPA_POD_Int(&m.xrun_count))) < 0) return res; + if (d->json_dump) { + fprintf(stdout, "{ \"type\": \"follower\", \"id\": %u, \"name\": \"%s\", \"prev\": %"PRIu64", " + "\"signal\": %"PRIu64", \"awake\": %"PRIu64", " + "\"finish\": %"PRIu64", \"status\": \"%s\", \"latency\": \"%u/%u\", " + "\"xrun_count\": %u },\n", + id, name, m.prev_signal, m.signal, + m.awake, m.finish, status_to_string(m.status), + m.latency.num, m.latency.denom, + m.xrun_count); + } + + if ((idx = find_follower(d, id, name)) < 0) { if ((idx = add_follower(d, id, name)) < 0) { pw_log_warn("too many followers"); @@ -176,6 +271,39 @@ static int process_follower_block(struct data *d, const struct spa_pod *pod, str return 0; } +static int process_follower_clock(struct data *d, const struct spa_pod *pod, struct point *point) +{ + int res; + char val[128]; + struct spa_io_clock clock; + + res = spa_pod_parse_struct(pod, + SPA_POD_Int(&clock.id), + SPA_POD_Stringn(clock.name, sizeof(clock.name)), + SPA_POD_Long(&clock.nsec), + SPA_POD_Fraction(&clock.rate), + SPA_POD_Long(&clock.position), + SPA_POD_Long(&clock.duration), + SPA_POD_Long(&clock.delay), + SPA_POD_Double(&clock.rate_diff), + SPA_POD_Long(&clock.next_nsec), + SPA_POD_Long(&clock.xrun)); + if (d->json_dump) { + fprintf(stdout, "{ \"type\": \"followerClock\", \"id\": %u, " + "\"name\": \"%s\", \"nsec\": %"PRIu64", \"rate\": \"%u/%u\", " + "\"position\": %"PRIu64", \"duration\": %"PRIu64", " + "\"delay\": %"PRIu64", \"diff\": %s, \"next_nsec\": %"PRIu64", " + "\"xrun\": %"PRIu64" },\n", + clock.id, clock.name, + clock.nsec, clock.rate.num, clock.rate.denom, + clock.position, clock.duration, + clock.delay, + spa_json_format_float(val, sizeof(val), (float)clock.rate_diff), + clock.next_nsec, clock.xrun); + } + return res; +} + static void dump_point(struct data *d, struct point *point) { int i; @@ -224,7 +352,7 @@ static void dump_point(struct data *d, struct point *point) d->last_status = point->clock.nsec; } else if (point->clock.nsec - d->last_status > SPA_NSEC_PER_SEC) { - printf("logging %"PRIi64" samples %"PRIi64" seconds [CPU %f %f %f]\r", + fprintf(stderr, "logging %"PRIi64" samples %"PRIi64" seconds [CPU %f %f %f]\r", d->count, (int64_t) ((d->last_status - d->start_status) / SPA_NSEC_PER_SEC), point->cpu_load[0], point->cpu_load[1], point->cpu_load[2]); d->last_status = point->clock.nsec; @@ -240,7 +368,7 @@ static void dump_scripts(struct data *d) if (d->driver_id == 0) return; - printf("\ndumping scripts for %d followers\n", d->n_followers); + fprintf(stderr, "\ndumping scripts for %d followers\n", d->n_followers); out = fopen("Timing1.plot", "we"); if (out == NULL) { @@ -254,9 +382,9 @@ static void dump_scripts(struct data *d) "set title \"Audio driver timing\"\n" "set xlabel \"audio cycles\"\n" "set ylabel \"usec\"\n" - "plot \"%1$s\" using 3 title \"Audio driver delay\" with lines, " - "\"%1$s\" using 1 title \"Audio period\" with lines," - "\"%1$s\" using 4 title \"Audio estimated\" with lines\n" + "plot \"%1$s\" using 3 title \"Audio driver delay (h/w ptr - wakeup time)\" with lines, " + "\"%1$s\" using 1 title \"Audio period (current wakeup - prev wakeup)\" with lines," + "\"%1$s\" using 4 title \"Audio estimated (cycle period or quantum)\" with lines\n" "unset multiplot\n" "unset output\n", d->filename); fclose(out); @@ -270,7 +398,7 @@ static void dump_scripts(struct data *d) "set output 'Timing2.svg\n" "set terminal svg\n" "set grid\n" - "set title \"Driver end date\"\n" + "set title \"Driver end date (total cycle processing time)\"\n" "set xlabel \"audio cycles\"\n" "set ylabel \"usec\"\n" "plot \"%s\" using 2 title \"Driver end date\" with lines \n" @@ -287,7 +415,8 @@ static void dump_scripts(struct data *d) "set terminal svg\n" "set multiplot\n" "set grid\n" - "set title \"Clients end date\"\n" + "set key tmargin\n" + "set title \"Clients end date (scheduled -> finished)\"\n" "set xlabel \"audio cycles\"\n" "set ylabel \"usec\"\n" "plot " @@ -317,7 +446,8 @@ static void dump_scripts(struct data *d) "set terminal svg\n" "set multiplot\n" "set grid\n" - "set title \"Clients scheduling latency\"\n" + "set key tmargin\n" + "set title \"Clients scheduling latency (scheduled -> active)\"\n" "set xlabel \"audio cycles\"\n" "set ylabel \"usec\"\n" "plot "); @@ -344,7 +474,8 @@ static void dump_scripts(struct data *d) "set terminal svg\n" "set multiplot\n" "set grid\n" - "set title \"Clients duration\"\n" + "set key tmargin\n" + "set title \"Clients duration (active -> finished)\"\n" "set xlabel \"audio cycles\"\n" "set ylabel \"usec\"\n" "plot "); @@ -431,6 +562,9 @@ static void profiler_profile(void *data, const struct spa_pod *pod) case SPA_PROFILER_followerBlock: process_follower_block(d, &p->value, &point); break; + case SPA_PROFILER_followerClock: + process_follower_clock(d, &p->value, &point); + break; default: break; } @@ -440,7 +574,13 @@ static void profiler_profile(void *data, const struct spa_pod *pod) if (res < 0) continue; - dump_point(d, &point); + if (!d->json_dump) + dump_point(d, &point); + + if (d->iterations > 0 && --d->iterations == 0) { + pw_main_loop_quit(d->loop); + break; + } } } @@ -468,7 +608,7 @@ static void registry_event_global(void *data, uint32_t id, if (proxy == NULL) goto error_proxy; - printf("Attaching to Profiler id:%d\n", id); + pw_log_info("Attaching to Profiler id:%d", id); d->profiler = proxy; pw_proxy_add_object_listener(proxy, &d->profiler_listener, &profiler_events, d); @@ -526,7 +666,9 @@ static void show_help(const char *name, bool error) " -h, --help Show this help\n" " --version Show version\n" " -r, --remote Remote daemon name\n" - " -o, --output Profiler output name (default \"%s\")\n", + " -o, --output Profiler output name (default \"%s\")\n" + " -J, --json Dump raw data as JSON\n" + " -n, --iterations Collect this many samples\n", name, DEFAULT_FILENAME); } @@ -542,6 +684,8 @@ int main(int argc, char *argv[]) { "version", no_argument, NULL, 'V' }, { "remote", required_argument, NULL, 'r' }, { "output", required_argument, NULL, 'o' }, + { "json", no_argument, NULL, 'J' }, + { "iterations", required_argument, NULL, 'n' }, { NULL, 0, NULL, 0} }; int c; @@ -549,7 +693,7 @@ int main(int argc, char *argv[]) setlocale(LC_ALL, ""); pw_init(&argc, &argv); - while ((c = getopt_long(argc, argv, "hVr:o:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hVr:o:Jn:", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(argv[0], false); @@ -568,6 +712,12 @@ int main(int argc, char *argv[]) case 'r': opt_remote = optarg; break; + case 'J': + data.json_dump = true; + break; + case 'n': + spa_atou32(optarg, &data.iterations, 10); + break; default: show_help(argv[0], true); return -1; @@ -604,14 +754,17 @@ int main(int argc, char *argv[]) data.filename = opt_output; - data.output = fopen(data.filename, "we"); - if (data.output == NULL) { - fprintf(stderr, "Can't open file %s: %m\n", data.filename); - return -1; + if (!data.json_dump) { + data.output = fopen(data.filename, "we"); + if (data.output == NULL) { + fprintf(stderr, "Can't open file %s: %m\n", data.filename); + return -1; + } + fprintf(stderr, "Logging to %s\n", data.filename); + } else { + printf("["); } - printf("Logging to %s\n", data.filename); - pw_core_add_listener(data.core, &data.core_listener, &core_events, &data); @@ -635,9 +788,12 @@ int main(int argc, char *argv[]) pw_context_destroy(data.context); pw_main_loop_destroy(data.loop); - fclose(data.output); - - dump_scripts(&data); + if (!data.json_dump) { + fclose(data.output); + dump_scripts(&data); + } else { + printf("{ } ]\n"); + } pw_deinit(); diff --git a/src/tools/pw-top.c b/src/tools/pw-top.c index dc99ef0a..fc4da923 100644 --- a/src/tools/pw-top.c +++ b/src/tools/pw-top.c @@ -8,6 +8,8 @@ #include <locale.h> #include <ncurses.h> +#undef clear + #include <spa/utils/result.h> #include <spa/utils/string.h> #include <spa/pod/parser.h> @@ -233,8 +235,7 @@ static void node_param(void *data, int seq, struct spa_audio_info_raw info = { 0 }; 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), + spa_type_audio_format_to_short_name(info.format), info.channels, info.rate); } break; @@ -272,7 +273,7 @@ static void node_param(void *data, int seq, struct spa_video_info_raw info = { 0 }; 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), + spa_type_video_format_to_short_name(info.format), info.size.width, info.size.height); } break; @@ -869,8 +870,8 @@ int main(int argc, char *argv[]) data.core = pw_context_connect(data.context, pw_properties_new( - PW_KEY_REMOTE_NAME, opt_remote ? opt_remote : - ("[" PW_DEFAULT_REMOTE "-manager," PW_DEFAULT_REMOTE "]"), + PW_KEY_REMOTE_INTENTION, "manager", + PW_KEY_REMOTE_NAME, opt_remote, NULL), 0); if (data.core == NULL) { diff --git a/subprojects/webrtc-audio-processing.wrap b/subprojects/webrtc-audio-processing.wrap index 1382212d..593acd83 100644 --- a/subprojects/webrtc-audio-processing.wrap +++ b/subprojects/webrtc-audio-processing.wrap @@ -2,7 +2,7 @@ directory = webrtc-audio-processing url = https://gitlab.freedesktop.org/pulseaudio/webrtc-audio-processing.git push-url = git@gitlab.freedesktop.org:pulseaudio/webrtc-audio-processing.git -revision = v1.3 +revision = v2.1 [provide] -dependency_names = webrtc-audio-coding-1, webrtc-audio-processing-1 +dependency_names = webrtc-audio-processing-2 diff --git a/subprojects/wireplumber.wrap b/subprojects/wireplumber.wrap index 0153b2ac..6527259f 100644 --- a/subprojects/wireplumber.wrap +++ b/subprojects/wireplumber.wrap @@ -1,3 +1,3 @@ [wrap-git] url = https://gitlab.freedesktop.org/pipewire/wireplumber.git -revision = head +revision = master diff --git a/test/meson.build b/test/meson.build index 75ae9602..43a59a25 100644 --- a/test/meson.build +++ b/test/meson.build @@ -111,6 +111,7 @@ endif test('test-spa', executable('test-spa', 'test-spa-buffer.c', + 'test-spa-control.c', 'test-spa-json.c', 'test-spa-utils.c', 'test-spa-log.c', diff --git a/test/test-example.c b/test/test-example.c index 60d3392d..c080168f 100644 --- a/test/test-example.c +++ b/test/test-example.c @@ -182,7 +182,9 @@ PWTEST(daemon_test) core = pw_context_connect(ctx, NULL, 0); pwtest_ptr_notnull(core); + pw_loop_enter(loop); pw_loop_iterate(loop, -1); + pw_loop_leave(loop); pw_core_disconnect(core); pw_context_destroy(ctx); pw_loop_destroy(loop); @@ -205,7 +207,9 @@ PWTEST(daemon_test_without_daemon) pwtest_ptr_notnull(core); /* Expect this to fail because we don't have a daemon */ + pw_loop_enter(loop); pw_loop_iterate(loop, -1); + pw_loop_leave(loop); pw_core_disconnect(core); pw_context_destroy(ctx); pw_loop_destroy(loop); diff --git a/test/test-spa-control.c b/test/test-spa-control.c new file mode 100644 index 00000000..d493b293 --- /dev/null +++ b/test/test-spa-control.c @@ -0,0 +1,173 @@ +/* Simple Plugin API */ +/* SPDX-FileCopyrightText: Copyright © 2024 Wim Taymans. */ +/* SPDX-License-Identifier: MIT */ + +#include "pwtest.h" + +#include <spa/control/control.h> +#include <spa/control/ump-utils.h> + +PWTEST(control_abi_types) +{ + /* contol */ + pwtest_int_eq(SPA_CONTROL_Invalid, 0); + pwtest_int_eq(SPA_CONTROL_Properties, 1); + pwtest_int_eq(SPA_CONTROL_Midi, 2); + pwtest_int_eq(SPA_CONTROL_OSC, 3); + pwtest_int_eq(SPA_CONTROL_UMP, 4); + pwtest_int_eq(_SPA_CONTROL_LAST, 5); + + return PWTEST_PASS; +} + +static inline uint32_t tohex(char v) +{ + if (v >= '0' && v <= '9') + return v - '0'; + if (v >= 'a' && v <= 'f') + return v - 'a' + 10; + return 0; +} + +static size_t parse_midi(const char *midi, uint8_t *data, size_t max_size) +{ + size_t size = 0; + while (*midi) { + while (*midi == ' ') + midi++; + data[size++] = tohex(*(midi+0)) << 4 | + tohex(*(midi+1)); + midi+=2; + } + return size; +} + +static size_t parse_ump(const char *ump, uint32_t *data, size_t max_size) +{ + size_t size = 0; + while (*ump) { + while (*ump == ' ') + ump++; + data[size++] = tohex(*(ump+0)) << 28 | + tohex(*(ump+1)) << 24 | + tohex(*(ump+2)) << 20 | + tohex(*(ump+3)) << 16 | + tohex(*(ump+4)) << 12 | + tohex(*(ump+5)) << 8 | + tohex(*(ump+6)) << 4 | + tohex(*(ump+7)); + ump+=8; + } + return size * 4; +} + +static int do_midi_to_ump_test(char *midi, char *ump) +{ + int i; + size_t m_size, u_size, u_offs = 0; + uint8_t *m_data = alloca(strlen(midi) / 2); + uint32_t *u_data = alloca(strlen(ump) / 2); + uint64_t state = 0; + + m_size = parse_midi(midi, m_data, sizeof(m_data)); + u_size = parse_ump(ump, u_data, sizeof(u_data)); + + while (m_size > 0) { + uint32_t ump[4]; + fprintf(stdout, "%zd %08x\n", m_size, *m_data); + int ump_size = spa_ump_from_midi(&m_data, &m_size, + ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + return -1; + + if (u_size <= u_offs) + return -1; + + for (i = 0; i < ump_size / 4; i++) { + fprintf(stdout, "%08x %08x\n", u_data[u_offs], ump[i]); + spa_assert(u_data[u_offs++] == ump[i]); + } + } + return 0; +} + +PWTEST(control_midi_to_ump) +{ + /* sysex */ + do_midi_to_ump_test("f0 f7", + "30000000 00000000"); + + do_midi_to_ump_test("f0 01 02 03 04 05 f7", + "30050102 03040500"); + + do_midi_to_ump_test("f0 01 02 03 04 05 06 f7", + "30060102 03040506"); + do_midi_to_ump_test("f0 01 02 03 04 05 06 07 f7", + "30160102 03040506 30310700 00000000"); + do_midi_to_ump_test("f0 01 02 03 04 05 06 07 08 09 10 11 12 13 f7", + "30160102 03040506 30260708 09101112 30311300 00000000"); + + do_midi_to_ump_test("f0 01 02 03 04 05 06 f0", + "30160102 03040506"); + do_midi_to_ump_test("f7 01 02 03 04 05 06 07 08 f0", + "30260102 03040506 30220708 00000000"); + do_midi_to_ump_test("f7 01 02 03 04 05 06 07 08 09 f7", + "30260102 03040506 30330708 09000000"); + + return PWTEST_PASS; +} + +static int do_ump_to_midi_test(char *ump, char *midi) +{ + int i; + size_t m_size, u_size, m_offs = 0; + uint8_t *m_data = alloca(strlen(midi) / 2); + uint32_t *u_data = alloca(strlen(ump) / 2); + + u_size = parse_ump(ump, u_data, sizeof(u_data)); + m_size = parse_midi(midi, m_data, sizeof(m_data)); + + spa_assert(u_size > 0); + spa_assert(m_size > 0); + + while (u_size > 0) { + uint8_t midi[32]; + fprintf(stdout, "%zd %08x\n", u_size, *u_data); + int midi_size = spa_ump_to_midi(u_data, u_size, + midi, sizeof(midi)); + if (midi_size <= 0) + return midi_size; + + if (m_size <= m_offs) + return -1; + + for (i = 0; i < midi_size; i++) { + fprintf(stdout, "%08x %08x\n", m_data[m_offs], midi[i]); + spa_assert(m_data[m_offs++] == midi[i]); + } + u_size -= spa_ump_message_size(*u_data >> 28) * 4; + u_data += spa_ump_message_size(*u_data >> 28); + } + return 0; +} + +PWTEST(control_ump_to_midi) +{ + spa_assert(do_ump_to_midi_test("30000000 00000000", + "f0 f7") >= 0); + spa_assert(do_ump_to_midi_test("30050102 03040500", + "f0 01 02 03 04 05 f7") >= 0); + + spa_assert(do_ump_to_midi_test("30160102 03040506 30260708 09101112 30311300 00000000", + "f0 01 02 03 04 05 06 07 08 09 10 11 12 13 f7") >= 0); + return PWTEST_PASS; +} + +PWTEST_SUITE(spa_buffer) +{ + pwtest_add(control_abi_types, PWTEST_NOARG); + pwtest_add(control_midi_to_ump, PWTEST_NOARG); + pwtest_add(control_ump_to_midi, PWTEST_NOARG); + + return PWTEST_PASS; +} diff --git a/test/test-spa-json.c b/test/test-spa-json.c index 869a2040..b07acc00 100644 --- a/test/test-spa-json.c +++ b/test/test-spa-json.c @@ -1065,6 +1065,38 @@ PWTEST(json_data) return PWTEST_PASS; } +PWTEST(json_object_find) +{ + const char *json = " { " + "\"foo\": \"bar\"," + "\"int-key\": 42," + "\"list-key\": []," + "\"obj-key\": {}," + "\"bool-key\": true," + "\"float-key\": 66.6" + " } "; + char value[128]; + + pwtest_int_eq(spa_json_str_object_find(json, strlen(json), "unknown-key", value, 128), -2); + pwtest_int_eq(spa_json_str_object_find("{", 1, "key", value, 128), -2); + pwtest_int_eq(spa_json_str_object_find("this is no json", 15, "key", value, 128), -22); + pwtest_int_eq(spa_json_str_object_find(json, strlen(json), "foo", value, 128), 1); + pwtest_str_eq(value, "bar"); + pwtest_int_eq(spa_json_str_object_find(json, strlen(json), "int-key", value, 128), 1); + pwtest_str_eq(value, "42"); + pwtest_int_eq(spa_json_str_object_find(json, strlen(json), "list-key", value, 128), 1); + pwtest_str_eq(value, "["); + pwtest_int_eq(spa_json_str_object_find(json, strlen(json), "obj-key", value, 128), 1); + pwtest_str_eq(value, "{"); + pwtest_int_eq(spa_json_str_object_find(json, strlen(json), "bool-key", value, 128), 1); + pwtest_str_eq(value, "true"); + pwtest_int_eq(spa_json_str_object_find(json, strlen(json), "float-key", value, 128), 1); + pwtest_str_eq(value, "66.6"); + + return PWTEST_PASS; +} + + PWTEST_SUITE(spa_json) { pwtest_add(json_abi, PWTEST_NOARG); @@ -1077,6 +1109,7 @@ PWTEST_SUITE(spa_json) pwtest_add(json_float_check, PWTEST_NOARG); pwtest_add(json_int, PWTEST_NOARG); pwtest_add(json_data, PWTEST_NOARG); + pwtest_add(json_object_find, PWTEST_NOARG); return PWTEST_PASS; } -- GitLab From f2154c1e6bd6b596b14bd079d9b02e45cf32ddbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dylan=20A=C3=AFssi?= <dylan.aissi@collabora.com> Date: Wed, 12 Mar 2025 16:46:26 +0100 Subject: [PATCH 2/5] Inject -Wno-error=format-overflow -Wno-error=format-truncation in debian/rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In contrary to Debian, we use by default -Wformat-overflow=2 and -Wformat-truncation=2 in Apertis, but due to the use of -Werror this package FTBFS with: "cc1: all warnings being treated as errors". In order to avoid this error, we don't treat these warnings as errors. ../src/pipewire/conf.c:283:44: error: ‘/’ directive output may be truncated writing 1 byte into a region of size between 0 and 1 [-Werror=format-truncation=] Signed-off-by: Dylan Aïssi <dylan.aissi@collabora.com> --- debian/rules | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/debian/rules b/debian/rules index 776eefd0..b6f5f200 100755 --- a/debian/rules +++ b/debian/rules @@ -3,6 +3,13 @@ export DEB_BUILD_MAINT_OPTIONS = hardening=+all export DEB_LDFLAGS_MAINT_APPEND = -Wl,-z,defs +# In contrary to Debian, we use by default -Wformat-overflow=2 -Wformat-truncation=2 +# in Apertis, but due to the use of -Werror this package FTBFS with: +# "cc1: all warnings being treated as errors". In order to avoid this error, we +# don't treat these warnings as errors. +export DEB_CFLAGS_MAINT_APPEND = -Wno-error=format-overflow -Wno-error=format-truncation +export DEB_CXXFLAGS_MAINT_APPEND = -Wno-error=format-overflow -Wno-error=format-truncation + %: dh $@ -Nlibspa-0.2-jack -- GitLab From 8cc3595aa03487ff424835f88c92029addece993 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dylan=20A=C3=AFssi?= <dylan.aissi@collabora.com> Date: Wed, 12 Mar 2025 18:30:45 +0100 Subject: [PATCH 3/5] dpkg-shlibdeps: exclude ump-source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It depends on libjack.so.0 which is not in a standard folder. The same issue is probably hidden in Debian because in contrary to Apertis, the libjack-jackd2-dev is still in the Build-deps, thus libjack.so.0 is available from the package libjack-jackd2-0. Some work are required in Debian to make pipewire-jack the new jackd3... Signed-off-by: Dylan Aïssi <dylan.aissi@collabora.com> --- debian/rules | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/rules b/debian/rules index b6f5f200..1c4462ae 100755 --- a/debian/rules +++ b/debian/rules @@ -138,4 +138,5 @@ override_dh_shlibdeps-arch: dh_shlibdeps \ --remaining-packages \ -l/usr/lib/$(DEB_HOST_MULTIARCH)/pipewire-0.3 \ + -Xexamples/jack/ump-source \ $(NULL) -- GitLab From 5461006b0fca2aded58e8be5fe9e53a02fe3e8d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dylan=20A=C3=AFssi?= <dylan.aissi@collabora.com> Date: Mon, 10 Mar 2025 14:45:10 +0100 Subject: [PATCH 4/5] Release pipewire version 1.4.0-1+apertis1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Dylan Aïssi <dylan.aissi@collabora.com> --- debian/changelog | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/debian/changelog b/debian/changelog index 3fc5769d..c7618230 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,27 @@ +pipewire (1.4.0-1+apertis1) apertis; urgency=medium + + * Sync from debian/trixie. + * Remaining Apertis specific changes: + - Continue to disable build-depends: + - libffado-dev (not needed) + - libfreeaptx-dev (proprietary codec, unknown legal status) + - libjack-jackd2-dev (not needed) + - liblilv-dev (not needed) + - libmysofa-dev (not needed) + - libsdl2 (not needed) + - libsnapd-glib-dev (not needed) + - libxfixes-dev (not needed) + - Replace libreadline-dev by libeditreadline-dev in Build-Deps + - Install AppArmor rules + - Inject -Wno-error=format-overflow -Wno-error=format-truncation in + debian/rules. In contrary to Debian, we use by default -Wformat-overflow=2 + and -Wformat-truncation=2 in Apertis, but due to the use of -Werror this + package FTBFS with: "cc1: all warnings being treated as errors". + In order to avoid this error, we don't treat these warnings as errors. + - dpkg-shlibdeps: exclude the binary example ump-source + + -- Dylan Aïssi <dylan.aissi@collabora.com> Mon, 10 Mar 2025 14:44:42 +0100 + pipewire (1.4.0-1) unstable; urgency=medium * New upstream release -- GitLab From caa3aaa6dabfc91705706e224ef0f3ddad1800da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dylan=20A=C3=AFssi?= <dylan.aissi@collabora.com> Date: Mon, 10 Mar 2025 13:53:00 +0000 Subject: [PATCH 5/5] Refresh the automatically detected licensing information MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Dylan Aïssi <dylan.aissi@collabora.com> --- debian/apertis/copyright | 97 ++++++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 24 deletions(-) diff --git a/debian/apertis/copyright b/debian/apertis/copyright index ffd19efc..0119075f 100644 --- a/debian/apertis/copyright +++ b/debian/apertis/copyright @@ -26,6 +26,22 @@ Copyright: 2021, jothepro 2000-2002, Richard W.E. Furse, Paul Barton-Davis License: Expat +Files: spa/plugins/bluez5/g722/* +Copyright: 2004-2010 Marcel Holtmann + 2006-2010 Nokia Corporation + 2016-2017 Arkadiusz Bokowy + 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: debian/tests/gstreamer1.0-pipewire + debian/tests/libpipewire-0.3-dev +Copyright: 2018-2021, 2024, Collabora Ltd. +License: Expat + Files: doc/doxygen-awesome.css Copyright: 2021, jothepro License: Expat @@ -95,9 +111,18 @@ Files: pipewire-jack/src/control.c Copyright: 2021, Florian Hülsmann <fh@cbix.de> License: Expat -Files: spa/examples/adapter-control.c - spa/examples/local-libcamera.c -Copyright: 2018-2021, Collabora Ltd. +Files: pipewire-jack/src/pipewire-jack.c +Copyright: 2024, Nedko Arnaudov + 2018, Wim Taymans <wim.taymans@gmail.com> +License: Expat + +Files: spa/examples/* +Copyright: 2018-2021, 2024, Collabora Ltd. +License: Expat + +Files: spa/examples/example-control.c + spa/examples/local-v4l2.c +Copyright: 2015-2024, Wim Taymans <wim.taymans@gmail.com> License: Expat Files: spa/include-private/* @@ -105,7 +130,7 @@ Copyright: 2023, PipeWire authors License: Expat Files: spa/include/spa/monitor/type-info.h -Copyright: 2018-2021, Collabora Ltd. +Copyright: 2018-2021, 2024, Collabora Ltd. License: Expat Files: spa/include/spa/param/audio/compressed.h @@ -186,6 +211,14 @@ Files: spa/plugins/alsa/mixer/profile-sets/kinect-audio.conf Copyright: 2011, Antonio Ospite <ospite@studenti.unina.it> License: LGPL-2.1+ +Files: spa/plugins/audioconvert/fmt-ops-rvv.c +Copyright: 2023, Institue of Software Chinese Academy of Sciences (ISCAS). +License: Expat + +Files: spa/plugins/audioconvert/spa-resample-dump-coeffs.c +Copyright: 2024, Arun Raghavan <arun@asymptotic.io> +License: Expat + Files: spa/plugins/bluez5/* Copyright: 2021-2024, Pauli Virtanen <pav@iki.fi> License: Expat @@ -194,6 +227,7 @@ Files: spa/plugins/bluez5/a2dp-codec-aac.c spa/plugins/bluez5/a2dp-codec-aptx.c spa/plugins/bluez5/a2dp-codec-ldac.c spa/plugins/bluez5/a2dp-codec-sbc.c + spa/plugins/bluez5/asha-codec-g722.c spa/plugins/bluez5/bluez5-dbus.c spa/plugins/bluez5/bluez5-device.c spa/plugins/bluez5/codec-loader.c @@ -228,7 +262,9 @@ Files: spa/plugins/bluez5/backend-hsphfpd.c spa/plugins/bluez5/sco-io.c spa/plugins/bluez5/sco-sink.c spa/plugins/bluez5/sco-source.c -Copyright: 2018-2021, Collabora Ltd. + spa/plugins/bluez5/telephony.c + spa/plugins/bluez5/telephony.h +Copyright: 2018-2021, 2024, Collabora Ltd. License: Expat Files: spa/plugins/bluez5/backend-native.c @@ -267,6 +303,15 @@ Files: spa/plugins/bluez5/rtp.h Copyright: 2004-2010, Marcel Holtmann <marcel@holtmann.org> License: LGPL-2.1+ +Files: spa/plugins/filter-graph/convolver.c +Copyright: 2021, Wim Taymans <wim.taymans@gmail.com> + 2017, HiFi-LoFi +License: Expat + +Files: spa/plugins/filter-graph/ladspa.h +Copyright: 2000-2002, Richard W.E. Furse, Paul Barton-Davis +License: LGPL-2.1+ + Files: spa/plugins/libcamera/libcamera-device.cpp spa/plugins/libcamera/libcamera-utils.cpp Copyright: 2019, 2020, 2024, Collabora Ltd. @@ -274,7 +319,7 @@ Copyright: 2019, 2020, 2024, Collabora Ltd. License: Expat Files: spa/plugins/libcamera/libcamera-source.cpp -Copyright: 2018-2021, Collabora Ltd. +Copyright: 2018-2021, 2024, Collabora Ltd. License: Expat Files: spa/plugins/libcamera/libcamera.c @@ -282,10 +327,19 @@ Files: spa/plugins/libcamera/libcamera.c Copyright: 2020, collabora License: Expat +Files: spa/plugins/support/cpu-riscv.c +Copyright: 2023, Institue of Software Chinese Academy of Sciences (ISCAS). +License: Expat + Files: spa/plugins/support/journal.c Copyright: 2020, Sergey Bugaev License: Expat +Files: spa/plugins/videoconvert/videoconvert-dummy.c +Copyright: 2023, columbarius + 2019, Wim Taymans <wim.taymans@gmail.com> +License: Expat + Files: spa/plugins/vulkan/dmabuf.h spa/plugins/vulkan/dmabuf_fallback.c spa/plugins/vulkan/dmabuf_linux.c @@ -331,15 +385,6 @@ Copyright: 2021, Wim Taymans <wim.taymans@gmail.com> 2021, Arun Raghavan <arun@asymptotic.io> License: Expat -Files: src/modules/module-filter-chain/convolver.c -Copyright: 2021, Wim Taymans <wim.taymans@gmail.com> - 2017, HiFi-LoFi -License: Expat - -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-jackdbus-detect.c src/modules/module-portal.c Copyright: 2019, Red Hat Inc. @@ -422,20 +467,20 @@ Copyright: 2024, Dmitry Sharshakov <d3dx12.xx@gmail.com> License: Expat Files: src/modules/module-session-manager.c -Copyright: 2018-2021, Collabora Ltd. +Copyright: 2018-2021, 2024, Collabora Ltd. License: Expat Files: src/modules/module-session-manager/* -Copyright: 2018-2021, Collabora Ltd. +Copyright: 2018-2021, 2024, Collabora Ltd. License: Expat -Files: src/modules/spa/module-node.c +Files: src/modules/module-spa-node.c Copyright: 2018, Wim Taymans <wim.taymans@gmail.com> 2016, Axis Communications <dev-gstreamer@axis.com> License: Expat Files: src/pipewire/extensions/* -Copyright: 2018-2021, Collabora Ltd. +Copyright: 2018-2021, 2024, Collabora Ltd. License: Expat Files: src/pipewire/extensions/client-node.h @@ -453,7 +498,7 @@ Copyright: 2018, Wim Taymans <wim.taymans@gmail.com> License: Expat Files: src/tests/test-endpoint.c -Copyright: 2018-2021, Collabora Ltd. +Copyright: 2018-2021, 2024, Collabora Ltd. License: Expat Files: src/tools/pw-cat.c @@ -461,7 +506,7 @@ Copyright: 2020, Konsulko Group License: Expat Files: src/tools/pw-dot.c -Copyright: 2018-2021, Collabora Ltd. +Copyright: 2018-2021, 2024, Collabora Ltd. License: Expat Files: test/* @@ -491,10 +536,14 @@ License: Expat Files: test/test-spa-buffer.c test/test-spa-utils.c -Copyright: 2018-2021, Collabora Ltd. +Copyright: 2018-2021, 2024, Collabora Ltd. +License: Expat + +Files: test/test-spa-control.c +Copyright: 2024, Wim Taymans. License: Expat -Files: doc/dox/config/pipewire-props.7.md spa/plugins/audioconvert/biquad.c spa/plugins/audioconvert/biquad.h spa/plugins/audioconvert/crossover.c spa/plugins/audioconvert/crossover.h src/modules/module-filter-chain/biquad.c src/modules/module-filter-chain/biquad.h +Files: doc/dox/config/pipewire-props.7.md spa/plugins/audioconvert/biquad.c spa/plugins/audioconvert/biquad.h spa/plugins/audioconvert/crossover.c spa/plugins/audioconvert/crossover.h spa/plugins/filter-graph/biquad.h Copyright: 2009 Lennart Poettering 2010 David Henningsson 2013 Inigo Quilez @@ -516,7 +565,7 @@ Copyright: 2009 Lennart Poettering 2021 Florian Hülsmann License: Expat -Files: src/modules/module-filter-chain/pffft.c src/modules/module-filter-chain/pffft.h +Files: spa/plugins/filter-graph/pffft.c spa/plugins/filter-graph/pffft.h Copyright: 2013 Julien Pommier 2004 The University Corporation for Atmospheric Research License: FFTPACK -- GitLab