summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/daemon/client-rt.conf.in114
-rw-r--r--src/daemon/client.conf.in85
-rw-r--r--src/daemon/filter-chain.conf.in62
-rw-r--r--src/daemon/filter-chain/demonic.conf63
-rw-r--r--src/daemon/filter-chain/meson.build19
-rw-r--r--src/daemon/filter-chain/sink-dolby-surround.conf46
-rw-r--r--src/daemon/filter-chain/sink-eq6.conf70
-rw-r--r--src/daemon/filter-chain/sink-make-LFE.conf56
-rw-r--r--src/daemon/filter-chain/sink-matrix-spatialiser.conf41
-rw-r--r--src/daemon/filter-chain/sink-mix-FL-FR.conf40
-rw-r--r--src/daemon/filter-chain/sink-virtual-surround-5.1-kemar.conf177
-rw-r--r--src/daemon/filter-chain/sink-virtual-surround-7.1-hesuvi.conf101
-rw-r--r--src/daemon/filter-chain/source-duplicate-FL.conf52
-rw-r--r--src/daemon/filter-chain/source-rnnoise.conf35
-rw-r--r--src/daemon/jack.conf.in128
-rw-r--r--src/daemon/meson.build141
-rw-r--r--src/daemon/minimal.conf.in352
-rw-r--r--src/daemon/pipewire-avb.conf.in73
-rw-r--r--src/daemon/pipewire-pulse.conf.in160
-rw-r--r--src/daemon/pipewire.c143
-rw-r--r--src/daemon/pipewire.conf.in259
-rw-r--r--src/daemon/pipewire.desktop.in9
-rw-r--r--src/daemon/systemd/meson.build6
-rw-r--r--src/daemon/systemd/system/meson.build15
-rw-r--r--src/daemon/systemd/system/pipewire.service.in35
-rw-r--r--src/daemon/systemd/system/pipewire.socket12
-rw-r--r--src/daemon/systemd/user/filter-chain.service.in21
-rw-r--r--src/daemon/systemd/user/meson.build27
-rw-r--r--src/daemon/systemd/user/pipewire-pulse.service.in36
-rw-r--r--src/daemon/systemd/user/pipewire-pulse.socket11
-rw-r--r--src/daemon/systemd/user/pipewire.service.in32
-rw-r--r--src/daemon/systemd/user/pipewire.socket9
-rw-r--r--src/examples/audio-capture.c209
-rw-r--r--src/examples/audio-dsp-filter.c180
-rw-r--r--src/examples/audio-dsp-src.c165
-rw-r--r--src/examples/audio-src.c186
-rw-r--r--src/examples/bluez-session.c400
-rw-r--r--src/examples/export-sink.c584
-rw-r--r--src/examples/export-source.c566
-rw-r--r--src/examples/export-spa-device.c144
-rw-r--r--src/examples/export-spa.c183
-rw-r--r--src/examples/local-v4l2.c469
-rw-r--r--src/examples/meson.build51
-rw-r--r--src/examples/sdl.h198
-rw-r--r--src/examples/video-dsp-play.c315
-rw-r--r--src/examples/video-play-fixate.c516
-rw-r--r--src/examples/video-play-pull.c588
-rw-r--r--src/examples/video-play-reneg.c438
-rw-r--r--src/examples/video-play.c529
-rw-r--r--src/examples/video-src-alloc.c464
-rw-r--r--src/examples/video-src-fixate.c602
-rw-r--r--src/examples/video-src-reneg.c509
-rw-r--r--src/examples/video-src.c357
-rw-r--r--src/gst/.editorconfig7
-rw-r--r--src/gst/gstpipewire.c69
-rw-r--r--src/gst/gstpipewireclock.c127
-rw-r--r--src/gst/gstpipewireclock.h71
-rw-r--r--src/gst/gstpipewirecore.c202
-rw-r--r--src/gst/gstpipewirecore.h60
-rw-r--r--src/gst/gstpipewiredeviceprovider.c754
-rw-r--r--src/gst/gstpipewiredeviceprovider.h110
-rw-r--r--src/gst/gstpipewireformat.c981
-rw-r--r--src/gst/gstpipewireformat.h42
-rw-r--r--src/gst/gstpipewirepool.c276
-rw-r--r--src/gst/gstpipewirepool.h92
-rw-r--r--src/gst/gstpipewiresink.c924
-rw-r--r--src/gst/gstpipewiresink.h110
-rw-r--r--src/gst/gstpipewiresrc.c1439
-rw-r--r--src/gst/gstpipewiresrc.h110
-rw-r--r--src/gst/meson.build33
-rw-r--r--src/meson.build15
-rw-r--r--src/modules/flatpak-utils.h156
-rw-r--r--src/modules/meson.build609
-rw-r--r--src/modules/module-access.c364
-rw-r--r--src/modules/module-adapter.c394
-rw-r--r--src/modules/module-adapter/adapter.c548
-rw-r--r--src/modules/module-adapter/adapter.h48
-rw-r--r--src/modules/module-avb.c129
-rw-r--r--src/modules/module-avb/aaf.h102
-rw-r--r--src/modules/module-avb/acmp.c476
-rw-r--r--src/modules/module-avb/acmp.h99
-rw-r--r--src/modules/module-avb/adp.c381
-rw-r--r--src/modules/module-avb/adp.h105
-rw-r--r--src/modules/module-avb/aecp-aem-descriptors.h247
-rw-r--r--src/modules/module-avb/aecp-aem.c285
-rw-r--r--src/modules/module-avb/aecp-aem.h345
-rw-r--r--src/modules/module-avb/aecp.c168
-rw-r--r--src/modules/module-avb/aecp.h60
-rw-r--r--src/modules/module-avb/avb.c106
-rw-r--r--src/modules/module-avb/avb.h44
-rw-r--r--src/modules/module-avb/avdecc.c335
-rw-r--r--src/modules/module-avb/descriptors.h274
-rw-r--r--src/modules/module-avb/iec61883.h110
-rw-r--r--src/modules/module-avb/internal.h166
-rw-r--r--src/modules/module-avb/maap.c469
-rw-r--r--src/modules/module-avb/maap.h70
-rw-r--r--src/modules/module-avb/mmrp.c233
-rw-r--r--src/modules/module-avb/mmrp.h68
-rw-r--r--src/modules/module-avb/mrp.c612
-rw-r--r--src/modules/module-avb/mrp.h181
-rw-r--r--src/modules/module-avb/msrp.c459
-rw-r--r--src/modules/module-avb/msrp.h134
-rw-r--r--src/modules/module-avb/mvrp.c297
-rw-r--r--src/modules/module-avb/mvrp.h62
-rw-r--r--src/modules/module-avb/packets.h101
-rw-r--r--src/modules/module-avb/srp.c59
-rw-r--r--src/modules/module-avb/srp.h32
-rw-r--r--src/modules/module-avb/stream.c589
-rw-r--r--src/modules/module-avb/stream.h104
-rw-r--r--src/modules/module-avb/utils.h86
-rw-r--r--src/modules/module-client-device.c232
-rw-r--r--src/modules/module-client-device/client-device.h44
-rw-r--r--src/modules/module-client-device/protocol-native.c556
-rw-r--r--src/modules/module-client-device/proxy-device.c90
-rw-r--r--src/modules/module-client-device/resource-device.c155
-rw-r--r--src/modules/module-client-node.c229
-rw-r--r--src/modules/module-client-node/client-node.c1777
-rw-r--r--src/modules/module-client-node/client-node.h60
-rw-r--r--src/modules/module-client-node/protocol-native.c1259
-rw-r--r--src/modules/module-client-node/remote-node.c1339
-rw-r--r--src/modules/module-client-node/v0/client-node.c1447
-rw-r--r--src/modules/module-client-node/v0/client-node.h101
-rw-r--r--src/modules/module-client-node/v0/ext-client-node.h414
-rw-r--r--src/modules/module-client-node/v0/protocol-native.c534
-rw-r--r--src/modules/module-client-node/v0/transport.c262
-rw-r--r--src/modules/module-client-node/v0/transport.h59
-rw-r--r--src/modules/module-combine-stream.c1063
-rw-r--r--src/modules/module-echo-cancel.c1350
-rw-r--r--src/modules/module-example-sink.c498
-rw-r--r--src/modules/module-example-source.c504
-rw-r--r--src/modules/module-fallback-sink.c473
-rw-r--r--src/modules/module-filter-chain.c2411
-rw-r--r--src/modules/module-filter-chain/biquad.c364
-rw-r--r--src/modules/module-filter-chain/biquad.h57
-rw-r--r--src/modules/module-filter-chain/builtin_plugin.c1035
-rw-r--r--src/modules/module-filter-chain/convolver.c425
-rw-r--r--src/modules/module-filter-chain/convolver.h34
-rw-r--r--src/modules/module-filter-chain/dsp-ops-avx.c85
-rw-r--r--src/modules/module-filter-chain/dsp-ops-c.c174
-rw-r--r--src/modules/module-filter-chain/dsp-ops-sse.c142
-rw-r--r--src/modules/module-filter-chain/dsp-ops.c114
-rw-r--r--src/modules/module-filter-chain/dsp-ops.h140
-rw-r--r--src/modules/module-filter-chain/ladspa.h603
-rw-r--r--src/modules/module-filter-chain/ladspa_plugin.c275
-rw-r--r--src/modules/module-filter-chain/lv2_plugin.c522
-rw-r--r--src/modules/module-filter-chain/pffft.c2381
-rw-r--r--src/modules/module-filter-chain/pffft.h181
-rw-r--r--src/modules/module-filter-chain/plugin.h112
-rw-r--r--src/modules/module-link-factory.c565
-rw-r--r--src/modules/module-loopback.c737
-rw-r--r--src/modules/module-metadata.c241
-rw-r--r--src/modules/module-metadata/metadata.c316
-rw-r--r--src/modules/module-metadata/protocol-native.c354
-rw-r--r--src/modules/module-metadata/proxy-metadata.c92
-rw-r--r--src/modules/module-pipe-tunnel.c730
-rw-r--r--src/modules/module-portal.c348
-rw-r--r--src/modules/module-profiler.c463
-rw-r--r--src/modules/module-profiler/protocol-native.c128
-rw-r--r--src/modules/module-protocol-native.c1528
-rw-r--r--src/modules/module-protocol-native/connection.c866
-rw-r--r--src/modules/module-protocol-native/connection.h112
-rw-r--r--src/modules/module-protocol-native/defs.h49
-rw-r--r--src/modules/module-protocol-native/local-socket.c169
-rw-r--r--src/modules/module-protocol-native/portal-screencast.c41
-rw-r--r--src/modules/module-protocol-native/protocol-footer.c152
-rw-r--r--src/modules/module-protocol-native/protocol-footer.h59
-rw-r--r--src/modules/module-protocol-native/protocol-native.c2236
-rw-r--r--src/modules/module-protocol-native/test-connection.c225
-rw-r--r--src/modules/module-protocol-native/v0/interfaces.h534
-rw-r--r--src/modules/module-protocol-native/v0/protocol-native.c1371
-rw-r--r--src/modules/module-protocol-native/v0/typemap.h282
-rw-r--r--src/modules/module-protocol-pulse.c387
-rw-r--r--src/modules/module-protocol-pulse/client.c390
-rw-r--r--src/modules/module-protocol-pulse/client.h136
-rw-r--r--src/modules/module-protocol-pulse/cmd.c136
-rw-r--r--src/modules/module-protocol-pulse/cmd.h32
-rw-r--r--src/modules/module-protocol-pulse/collect.c549
-rw-r--r--src/modules/module-protocol-pulse/collect.h166
-rw-r--r--src/modules/module-protocol-pulse/commands.h208
-rw-r--r--src/modules/module-protocol-pulse/dbus-name.c87
-rw-r--r--src/modules/module-protocol-pulse/dbus-name.h33
-rw-r--r--src/modules/module-protocol-pulse/defs.h265
-rw-r--r--src/modules/module-protocol-pulse/extension.c45
-rw-r--r--src/modules/module-protocol-pulse/extension.h47
-rw-r--r--src/modules/module-protocol-pulse/extensions/ext-device-manager.c8
-rw-r--r--src/modules/module-protocol-pulse/extensions/ext-device-restore.c340
-rw-r--r--src/modules/module-protocol-pulse/extensions/ext-stream-restore.c330
-rw-r--r--src/modules/module-protocol-pulse/extensions/registry.h13
-rw-r--r--src/modules/module-protocol-pulse/format.c861
-rw-r--r--src/modules/module-protocol-pulse/format.h233
-rw-r--r--src/modules/module-protocol-pulse/internal.h108
-rw-r--r--src/modules/module-protocol-pulse/log.h34
-rw-r--r--src/modules/module-protocol-pulse/manager.c1028
-rw-r--r--src/modules/module-protocol-pulse/manager.h145
-rw-r--r--src/modules/module-protocol-pulse/message-handler.c143
-rw-r--r--src/modules/module-protocol-pulse/message-handler.h8
-rw-r--r--src/modules/module-protocol-pulse/message.c879
-rw-r--r--src/modules/module-protocol-pulse/message.h75
-rw-r--r--src/modules/module-protocol-pulse/module.c333
-rw-r--r--src/modules/module-protocol-pulse/module.h94
-rw-r--r--src/modules/module-protocol-pulse/modules/module-always-sink.c122
-rw-r--r--src/modules/module-protocol-pulse/modules/module-combine-sink.c341
-rw-r--r--src/modules/module-protocol-pulse/modules/module-echo-cancel.c276
-rw-r--r--src/modules/module-protocol-pulse/modules/module-gsettings.c298
-rw-r--r--src/modules/module-protocol-pulse/modules/module-ladspa-sink.c257
-rw-r--r--src/modules/module-protocol-pulse/modules/module-ladspa-source.c265
-rw-r--r--src/modules/module-protocol-pulse/modules/module-loopback.c245
-rw-r--r--src/modules/module-protocol-pulse/modules/module-native-protocol-tcp.c127
-rw-r--r--src/modules/module-protocol-pulse/modules/module-null-sink.c228
-rw-r--r--src/modules/module-protocol-pulse/modules/module-pipe-sink.c210
-rw-r--r--src/modules/module-protocol-pulse/modules/module-pipe-source.c210
-rw-r--r--src/modules/module-protocol-pulse/modules/module-raop-discover.c112
-rw-r--r--src/modules/module-protocol-pulse/modules/module-remap-sink.c253
-rw-r--r--src/modules/module-protocol-pulse/modules/module-remap-source.c260
-rw-r--r--src/modules/module-protocol-pulse/modules/module-roc-sink-input.c201
-rw-r--r--src/modules/module-protocol-pulse/modules/module-roc-sink.c197
-rw-r--r--src/modules/module-protocol-pulse/modules/module-roc-source.c206
-rw-r--r--src/modules/module-protocol-pulse/modules/module-rtp-recv.c164
-rw-r--r--src/modules/module-protocol-pulse/modules/module-rtp-send.c224
-rw-r--r--src/modules/module-protocol-pulse/modules/module-simple-protocol-tcp.c210
-rw-r--r--src/modules/module-protocol-pulse/modules/module-switch-on-connect.c291
-rw-r--r--src/modules/module-protocol-pulse/modules/module-tunnel-sink.c230
-rw-r--r--src/modules/module-protocol-pulse/modules/module-tunnel-source.c220
-rw-r--r--src/modules/module-protocol-pulse/modules/module-x11-bell.c130
-rw-r--r--src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c133
-rw-r--r--src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c746
-rw-r--r--src/modules/module-protocol-pulse/operation.c92
-rw-r--r--src/modules/module-protocol-pulse/operation.h50
-rw-r--r--src/modules/module-protocol-pulse/pending-sample.c50
-rw-r--r--src/modules/module-protocol-pulse/pending-sample.h48
-rw-r--r--src/modules/module-protocol-pulse/pulse-server.c5689
-rw-r--r--src/modules/module-protocol-pulse/pulse-server.h56
-rw-r--r--src/modules/module-protocol-pulse/quirks.c75
-rw-r--r--src/modules/module-protocol-pulse/quirks.h36
-rw-r--r--src/modules/module-protocol-pulse/remap.c58
-rw-r--r--src/modules/module-protocol-pulse/remap.h52
-rw-r--r--src/modules/module-protocol-pulse/reply.c84
-rw-r--r--src/modules/module-protocol-pulse/reply.h42
-rw-r--r--src/modules/module-protocol-pulse/sample-play.c211
-rw-r--r--src/modules/module-protocol-pulse/sample-play.h76
-rw-r--r--src/modules/module-protocol-pulse/sample.c50
-rw-r--r--src/modules/module-protocol-pulse/sample.h61
-rw-r--r--src/modules/module-protocol-pulse/server.c1087
-rw-r--r--src/modules/module-protocol-pulse/server.h60
-rw-r--r--src/modules/module-protocol-pulse/stream.c428
-rw-r--r--src/modules/module-protocol-pulse/stream.h141
-rw-r--r--src/modules/module-protocol-pulse/utils.c207
-rw-r--r--src/modules/module-protocol-pulse/utils.h40
-rw-r--r--src/modules/module-protocol-pulse/volume.c114
-rw-r--r--src/modules/module-protocol-pulse/volume.h85
-rw-r--r--src/modules/module-protocol-simple.c911
-rw-r--r--src/modules/module-pulse-tunnel.c1080
-rw-r--r--src/modules/module-raop-discover.c547
-rw-r--r--src/modules/module-raop-sink.c1848
-rw-r--r--src/modules/module-raop/rtsp-client.c631
-rw-r--r--src/modules/module-raop/rtsp-client.h92
-rw-r--r--src/modules/module-roc-sink.c514
-rw-r--r--src/modules/module-roc-source.c546
-rw-r--r--src/modules/module-roc/common.h71
-rw-r--r--src/modules/module-rt.c1095
-rw-r--r--src/modules/module-rtp-sink.c975
-rw-r--r--src/modules/module-rtp-source.c1200
-rw-r--r--src/modules/module-rtp/rtp.h78
-rw-r--r--src/modules/module-rtp/sap.h58
-rw-r--r--src/modules/module-session-manager.c74
-rw-r--r--src/modules/module-session-manager/client-endpoint/client-endpoint.c296
-rw-r--r--src/modules/module-session-manager/client-endpoint/client-endpoint.h62
-rw-r--r--src/modules/module-session-manager/client-endpoint/endpoint-stream.c352
-rw-r--r--src/modules/module-session-manager/client-endpoint/endpoint-stream.h64
-rw-r--r--src/modules/module-session-manager/client-endpoint/endpoint.c385
-rw-r--r--src/modules/module-session-manager/client-endpoint/endpoint.h61
-rw-r--r--src/modules/module-session-manager/client-session/client-session.c295
-rw-r--r--src/modules/module-session-manager/client-session/client-session.h62
-rw-r--r--src/modules/module-session-manager/client-session/endpoint-link.c369
-rw-r--r--src/modules/module-session-manager/client-session/endpoint-link.h65
-rw-r--r--src/modules/module-session-manager/client-session/session.c344
-rw-r--r--src/modules/module-session-manager/client-session/session.h62
-rw-r--r--src/modules/module-session-manager/endpoint-link.c590
-rw-r--r--src/modules/module-session-manager/endpoint-stream.c581
-rw-r--r--src/modules/module-session-manager/endpoint.c590
-rw-r--r--src/modules/module-session-manager/protocol-native.c3083
-rw-r--r--src/modules/module-session-manager/proxy-session-manager.c188
-rw-r--r--src/modules/module-session-manager/session.c578
-rw-r--r--src/modules/module-x11-bell.c364
-rw-r--r--src/modules/module-zeroconf-discover.c565
-rw-r--r--src/modules/module-zeroconf-discover/avahi-poll.c201
-rw-r--r--src/modules/module-zeroconf-discover/avahi-poll.h31
-rw-r--r--src/modules/spa/meson.build31
-rw-r--r--src/modules/spa/module-device-factory.c281
-rw-r--r--src/modules/spa/module-device.c127
-rw-r--r--src/modules/spa/module-node-factory.c280
-rw-r--r--src/modules/spa/module-node.c129
-rw-r--r--src/modules/spa/spa-device.c161
-rw-r--r--src/modules/spa/spa-device.h62
-rw-r--r--src/modules/spa/spa-node.c292
-rw-r--r--src/modules/spa/spa-node.h63
-rw-r--r--src/pipewire/array.h176
-rw-r--r--src/pipewire/buffers.c368
-rw-r--r--src/pipewire/buffers.h75
-rw-r--r--src/pipewire/client.h186
-rw-r--r--src/pipewire/conf.c1186
-rw-r--r--src/pipewire/conf.h49
-rw-r--r--src/pipewire/context.c1461
-rw-r--r--src/pipewire/context.h195
-rw-r--r--src/pipewire/control.c267
-rw-r--r--src/pipewire/control.h82
-rw-r--r--src/pipewire/core.c495
-rw-r--r--src/pipewire/core.h629
-rw-r--r--src/pipewire/data-loop.c292
-rw-r--r--src/pipewire/data-loop.h113
-rw-r--r--src/pipewire/device.h178
-rw-r--r--src/pipewire/extensions/client-node.h357
-rw-r--r--src/pipewire/extensions/meson.build21
-rw-r--r--src/pipewire/extensions/metadata.h112
-rw-r--r--src/pipewire/extensions/profiler.h95
-rw-r--r--src/pipewire/extensions/protocol-native.h105
-rw-r--r--src/pipewire/extensions/session-manager.h47
-rw-r--r--src/pipewire/extensions/session-manager/impl-interfaces.h298
-rw-r--r--src/pipewire/extensions/session-manager/interfaces.h479
-rw-r--r--src/pipewire/extensions/session-manager/introspect-funcs.h323
-rw-r--r--src/pipewire/extensions/session-manager/introspect.h130
-rw-r--r--src/pipewire/extensions/session-manager/keys.h67
-rw-r--r--src/pipewire/factory.h123
-rw-r--r--src/pipewire/filter.c1962
-rw-r--r--src/pipewire/filter.h250
-rw-r--r--src/pipewire/global.c430
-rw-r--r--src/pipewire/global.h165
-rw-r--r--src/pipewire/i18n.h56
-rw-r--r--src/pipewire/impl-client.c780
-rw-r--r--src/pipewire/impl-client.h185
-rw-r--r--src/pipewire/impl-core.c673
-rw-r--r--src/pipewire/impl-core.h104
-rw-r--r--src/pipewire/impl-device.c935
-rw-r--r--src/pipewire/impl-device.h116
-rw-r--r--src/pipewire/impl-factory.c301
-rw-r--r--src/pipewire/impl-factory.h131
-rw-r--r--src/pipewire/impl-link.c1508
-rw-r--r--src/pipewire/impl-link.h126
-rw-r--r--src/pipewire/impl-metadata.c627
-rw-r--r--src/pipewire/impl-metadata.h114
-rw-r--r--src/pipewire/impl-module.c427
-rw-r--r--src/pipewire/impl-module.h120
-rw-r--r--src/pipewire/impl-node.c2316
-rw-r--r--src/pipewire/impl-node.h190
-rw-r--r--src/pipewire/impl-port.c1614
-rw-r--r--src/pipewire/impl-port.h144
-rw-r--r--src/pipewire/impl.h62
-rw-r--r--src/pipewire/introspect.c590
-rw-r--r--src/pipewire/keys.h356
-rw-r--r--src/pipewire/link.h148
-rw-r--r--src/pipewire/log.c291
-rw-r--r--src/pipewire/log.h182
-rw-r--r--src/pipewire/loop.c162
-rw-r--r--src/pipewire/loop.h90
-rw-r--r--src/pipewire/main-loop.c157
-rw-r--r--src/pipewire/main-loop.h86
-rw-r--r--src/pipewire/map.h252
-rw-r--r--src/pipewire/mem.c808
-rw-r--r--src/pipewire/mem.h212
-rw-r--r--src/pipewire/meson.build138
-rw-r--r--src/pipewire/module.h121
-rw-r--r--src/pipewire/node.h216
-rw-r--r--src/pipewire/permission.h86
-rw-r--r--src/pipewire/pipewire.c871
-rw-r--r--src/pipewire/pipewire.h123
-rw-r--r--src/pipewire/port.h179
-rw-r--r--src/pipewire/private.h1310
-rw-r--r--src/pipewire/properties.c733
-rw-r--r--src/pipewire/properties.h199
-rw-r--r--src/pipewire/protocol.c188
-rw-r--r--src/pipewire/protocol.h162
-rw-r--r--src/pipewire/proxy.c374
-rw-r--r--src/pipewire/proxy.h219
-rw-r--r--src/pipewire/resource.c351
-rw-r--r--src/pipewire/resource.h174
-rw-r--r--src/pipewire/settings.c337
-rw-r--r--src/pipewire/stream.c2414
-rw-r--r--src/pipewire/stream.h529
-rw-r--r--src/pipewire/thread-loop.c469
-rw-r--r--src/pipewire/thread-loop.h175
-rw-r--r--src/pipewire/thread.c160
-rw-r--r--src/pipewire/thread.h65
-rw-r--r--src/pipewire/type.h65
-rw-r--r--src/pipewire/utils.c214
-rw-r--r--src/pipewire/utils.h111
-rw-r--r--src/pipewire/version.h.in68
-rw-r--r--src/pipewire/work-queue.c269
-rw-r--r--src/pipewire/work-queue.h71
-rw-r--r--src/tests/meson.build52
-rw-r--r--src/tests/test-cpp.cpp35
-rw-r--r--src/tests/test-endpoint.c469
-rw-r--r--src/tests/test-filter.c375
-rw-r--r--src/tests/test-interfaces.c382
-rw-r--r--src/tests/test-stream.c257
-rw-r--r--src/tools/dsffile.c266
-rw-r--r--src/tools/dsffile.h52
-rw-r--r--src/tools/meson.build88
-rw-r--r--src/tools/midifile.c740
-rw-r--r--src/tools/midifile.h64
-rw-r--r--src/tools/pw-cat.c1971
-rw-r--r--src/tools/pw-cli.c2375
-rw-r--r--src/tools/pw-dot.c1169
-rw-r--r--src/tools/pw-dump.c1664
-rw-r--r--src/tools/pw-link.c912
-rw-r--r--src/tools/pw-loopback.c284
-rw-r--r--src/tools/pw-metadata.c297
-rw-r--r--src/tools/pw-mididump.c233
-rw-r--r--src/tools/pw-mon.c875
-rw-r--r--src/tools/pw-profiler.c665
-rw-r--r--src/tools/pw-reserve.c253
-rw-r--r--src/tools/pw-top.c862
-rw-r--r--src/tools/reserve.c527
-rw-r--r--src/tools/reserve.h82
413 files changed, 150125 insertions, 0 deletions
diff --git a/src/daemon/client-rt.conf.in b/src/daemon/client-rt.conf.in
new file mode 100644
index 0000000..b73a904
--- /dev/null
+++ b/src/daemon/client-rt.conf.in
@@ -0,0 +1,114 @@
+# 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 ] ]
+ #}
+ #
+ # Loads a module with the given parameters.
+ # If ifexists is given, the module is ignored when it is not found.
+ # If nofail is given, module initialization failures are ignored.
+ #
+ # Uses realtime scheduling to boost the audio thread priorities
+ { name = libpipewire-module-rt
+ args = {
+ #rt.prio = 88
+ #rt.time.soft = -1
+ #rt.time.hard = -1
+ }
+ flags = [ ifexists nofail ]
+ }
+
+ # The native communication protocol.
+ { name = libpipewire-module-protocol-native }
+
+ # Allows creating nodes that run in the context of the
+ # client. Is used by all clients that want to provide
+ # data to PipeWire.
+ { name = libpipewire-module-client-node }
+
+ # 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
+}
+
+alsa.properties = {
+ #alsa.format = 0
+ #alsa.rate = 0
+ #alsa.channels = 0
+ #alsa.period-bytes = 0
+ #alsa.buffer-bytes = 0
+ #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
new file mode 100644
index 0000000..b465eb6
--- /dev/null
+++ b/src/daemon/client.conf.in
@@ -0,0 +1,85 @@
+# 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.conf.d/ for system-wide changes or in
+# ~/.config/pipewire/client.conf.d/ for local changes.
+#
+
+context.properties = {
+ ## Configure properties in the system.
+ #mem.warn-mlock = false
+ #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 ] ]
+ #}
+ #
+ # 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.
+ #
+
+ # 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
+}
diff --git a/src/daemon/filter-chain.conf.in b/src/daemon/filter-chain.conf.in
new file mode 100644
index 0000000..28d2a5a
--- /dev/null
+++ b/src/daemon/filter-chain.conf.in
@@ -0,0 +1,62 @@
+# Filter-chain config file for PipeWire version @VERSION@ #
+#
+# This is a base config file for running filters.
+#
+# Place filter fragments in @PIPEWIRE_CONFIG_DIR@/filter-chain.conf.d/
+# for system-wide changes or in ~/.config/pipewire/filter-chain.conf.d/
+# for local changes.
+#
+# Run the filters with pipewire -c filter-chain.conf
+#
+
+context.properties = {
+ ## Configure properties in the system.
+ #mem.warn-mlock = false
+ #mem.allow-mlock = true
+ #mem.mlock-all = false
+ log.level = 0
+}
+
+context.spa-libs = {
+ #<factory-name regex> = <library-name>
+ #
+ # Used to find spa factory names. It maps an spa factory name
+ # regular expression to a library name that should contain
+ # that factory.
+ #
+ audio.convert.* = audioconvert/libspa-audioconvert
+ support.* = support/libspa-support
+}
+
+context.modules = [
+ #{ name = <module-name>
+ # [ args = { <key> = <value> ... } ]
+ # [ flags = [ [ ifexists ] [ nofail ] ]
+ #}
+ #
+ # Loads a module with the given parameters.
+ # If ifexists is given, the module is ignored when it is not found.
+ # If nofail is given, module initialization failures are ignored.
+ #
+ # Uses realtime scheduling to boost the audio thread priorities
+ { name = libpipewire-module-rt
+ args = {
+ #rt.prio = 88
+ #rt.time.soft = -1
+ #rt.time.hard = -1
+ }
+ flags = [ ifexists nofail ]
+ }
+
+ # The native communication protocol.
+ { name = libpipewire-module-protocol-native }
+
+ # Allows creating nodes that run in the context of the
+ # client. Is used by all clients that want to provide
+ # data to PipeWire.
+ { name = libpipewire-module-client-node }
+
+ # Makes a factory for wrapping nodes in an adapter with a
+ # converter and resampler.
+ { name = libpipewire-module-adapter }
+]
diff --git a/src/daemon/filter-chain/demonic.conf b/src/daemon/filter-chain/demonic.conf
new file mode 100644
index 0000000..df52dba
--- /dev/null
+++ b/src/daemon/filter-chain/demonic.conf
@@ -0,0 +1,63 @@
+# filter-chain example config file for PipeWire version @VERSION@ #
+#
+# Copy this file into a conf.d/ directory such as
+# ~/.config/pipewire/filter-chain.conf.d/
+#
+context.modules = [
+ { name = libpipewire-module-filter-chain
+ args = {
+ #audio.format = F32
+ #audio.rate = 48000
+ audio.channels = 2
+ audio.position = [ FL FR ]
+ node.description = "Demonic example"
+ media.name = "Demonic example"
+ filter.graph = {
+ nodes = [
+ {
+ name = rev
+ type = ladspa
+ plugin = revdelay_1605
+ label = revdelay
+ control = {
+ "Delay Time (s)" = 2.0
+ }
+ }
+ {
+ name = pitch
+ type = ladspa
+ plugin = am_pitchshift_1433
+ label = amPitchshift
+ control = {
+ "Pitch shift" = 0.6
+ }
+ }
+ {
+ name = rev2
+ type = ladspa
+ plugin = g2reverb
+ label = G2reverb
+ control = {
+ "Reverb tail" = 0.5
+ "Damping" = 0.9
+ }
+ }
+ ]
+ links = [
+ { output = "rev:Output" input = "pitch:Input" }
+ { output = "pitch:Output" input = "rev2:In L" }
+ ]
+ inputs = [ "rev:Input" ]
+ outputs = [ "rev2:Out L" ]
+ }
+ capture.props = {
+ node.name = "effect_input.filter-chain-demonic"
+ #media.class = Audio/Sink
+ }
+ playback.props = {
+ node.name = "effect_output.filter-chain-demonic"
+ #media.class = Audio/Source
+ }
+ }
+ }
+]
diff --git a/src/daemon/filter-chain/meson.build b/src/daemon/filter-chain/meson.build
new file mode 100644
index 0000000..91d4458
--- /dev/null
+++ b/src/daemon/filter-chain/meson.build
@@ -0,0 +1,19 @@
+conf_files = [
+ [ 'demonic.conf', 'demonic.conf' ],
+ [ 'source-duplicate-FL.conf', 'source-duplicate-FL.conf' ],
+ [ 'sink-mix-FL-FR.conf', 'sink-mix-FL-FR.conf' ],
+ [ 'sink-make-LFE.conf', 'sink-make-LFE.conf' ],
+ [ 'sink-virtual-surround-5.1-kemar.conf', 'sink-virtual-surround-5.1-kemar.conf' ],
+ [ 'sink-virtual-surround-7.1-hesuvi.conf', 'sink-virtual-surround-7.1-hesuvi.conf' ],
+ [ 'sink-dolby-surround.conf', 'sink-dolby-surround.conf' ],
+ [ 'sink-eq6.conf', 'sink-eq6.conf' ],
+ [ 'sink-matrix-spatialiser.conf', 'sink-matrix-spatialiser.conf' ],
+ [ 'source-rnnoise.conf', 'source-rnnoise.conf' ],
+]
+
+foreach c : conf_files
+ configure_file(input : c.get(0),
+ output : c.get(1),
+ configuration : conf_config,
+ install_dir : pipewire_confdatadir / 'filter-chain')
+endforeach
diff --git a/src/daemon/filter-chain/sink-dolby-surround.conf b/src/daemon/filter-chain/sink-dolby-surround.conf
new file mode 100644
index 0000000..a53009f
--- /dev/null
+++ b/src/daemon/filter-chain/sink-dolby-surround.conf
@@ -0,0 +1,46 @@
+# Dolby Surround encoder 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 = "Dolby Surround Sink"
+ media.name = "Dolby Surround Sink"
+ filter.graph = {
+ nodes = [
+ {
+ type = builtin
+ name = mixer
+ label = mixer
+ control = { "Gain 1" = 0.5 "Gain 2" = 0.5 }
+ }
+ {
+ type = ladspa
+ name = enc
+ plugin = surround_encoder_1401
+ label = surroundEncoder
+ }
+ ]
+ links = [
+ { output = "mixer:Out" input = "enc:S" }
+ ]
+ inputs = [ "enc:L" "enc:R" "enc:C" null "mixer:In 1" "mixer:In 2" ]
+ outputs = [ "enc:Lt" "enc:Rt" ]
+ }
+ capture.props = {
+ node.name = "effect_input.dolby_surround"
+ media.class = Audio/Sink
+ audio.channels = 6
+ audio.position = [ FL FR FC LFE SL SR ]
+ }
+ playback.props = {
+ node.name = "effect_output.dolby_surround"
+ node.passive = true
+ audio.channels = 2
+ audio.position = [ FL FR ]
+ }
+ }
+ }
+]
diff --git a/src/daemon/filter-chain/sink-eq6.conf b/src/daemon/filter-chain/sink-eq6.conf
new file mode 100644
index 0000000..4cdf21b
--- /dev/null
+++ b/src/daemon/filter-chain/sink-eq6.conf
@@ -0,0 +1,70 @@
+# 6 band sink equalizer
+#
+# 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 = "Equalizer Sink"
+ media.name = "Equalizer Sink"
+ filter.graph = {
+ nodes = [
+ {
+ type = builtin
+ name = eq_band_1
+ label = bq_lowshelf
+ control = { "Freq" = 100.0 "Q" = 1.0 "Gain" = 0.0 }
+ }
+ {
+ type = builtin
+ name = eq_band_2
+ label = bq_peaking
+ control = { "Freq" = 100.0 "Q" = 1.0 "Gain" = 0.0 }
+ }
+ {
+ type = builtin
+ name = eq_band_3
+ label = bq_peaking
+ control = { "Freq" = 500.0 "Q" = 1.0 "Gain" = 0.0 }
+ }
+ {
+ type = builtin
+ name = eq_band_4
+ label = bq_peaking
+ control = { "Freq" = 2000.0 "Q" = 1.0 "Gain" = 0.0 }
+ }
+ {
+ type = builtin
+ name = eq_band_5
+ label = bq_peaking
+ control = { "Freq" = 5000.0 "Q" = 1.0 "Gain" = 0.0 }
+ }
+ {
+ type = builtin
+ name = eq_band_6
+ label = bq_highshelf
+ control = { "Freq" = 5000.0 "Q" = 1.0 "Gain" = 0.0 }
+ }
+ ]
+ links = [
+ { output = "eq_band_1:Out" input = "eq_band_2:In" }
+ { output = "eq_band_2:Out" input = "eq_band_3:In" }
+ { output = "eq_band_3:Out" input = "eq_band_4:In" }
+ { output = "eq_band_4:Out" input = "eq_band_5:In" }
+ { output = "eq_band_5:Out" input = "eq_band_6:In" }
+ ]
+ }
+ audio.channels = 2
+ audio.position = [ FL FR ]
+ capture.props = {
+ node.name = "effect_input.eq6"
+ media.class = Audio/Sink
+ }
+ playback.props = {
+ node.name = "effect_output.eq6"
+ node.passive = true
+ }
+ }
+ }
+]
diff --git a/src/daemon/filter-chain/sink-make-LFE.conf b/src/daemon/filter-chain/sink-make-LFE.conf
new file mode 100644
index 0000000..4ab770e
--- /dev/null
+++ b/src/daemon/filter-chain/sink-make-LFE.conf
@@ -0,0 +1,56 @@
+# An example filter chain that makes a stereo sink that mixes
+# the FL and FR channels to FL, FR, LFE
+#
+# Copy this file into a conf.d/ directory
+#
+context.modules = [
+ { name = libpipewire-module-filter-chain
+ args = {
+ node.description = "LFE example"
+ media.name = "LFE example"
+ filter.graph = {
+ nodes = [
+ { name = copyIL type = builtin label = copy }
+ { name = copyOL type = builtin label = copy }
+ { name = copyIR type = builtin label = copy }
+ { name = copyOR type = builtin label = copy }
+ {
+ name = mix
+ type = builtin
+ label = mixer
+ control = {
+ "Gain 1" = 0.5
+ "Gain 2" = 0.5
+ }
+ }
+ {
+ type = builtin
+ name = lpLFE
+ label = bq_lowpass
+ control = { "Freq" = 150.0 }
+ }
+ ]
+ links = [
+ { output = "copyIL:Out" input = "copyOL:In" }
+ { output = "copyIR:Out" input = "copyOR:In" }
+ { output = "copyIL:Out" input = "mix:In 1" }
+ { output = "copyIR:Out" input = "mix:In 2" }
+ { output = "mix:Out" input = "lpLFE:In" }
+ ]
+ inputs = [ "copyIL:In" "copyIR:In" ]
+ outputs = [ "copyOL:Out" "copyOR:Out" "lpLFE:Out"]
+ }
+ capture.props = {
+ node.name = "input_lfe"
+ audio.position = [ FL FR ]
+ media.class = "Audio/Sink"
+ }
+ playback.props = {
+ node.name = "output_lfe"
+ audio.position = [ FL FR LFE ]
+ stream.dont-remix = true
+ node.passive = true
+ }
+ }
+ }
+]
diff --git a/src/daemon/filter-chain/sink-matrix-spatialiser.conf b/src/daemon/filter-chain/sink-matrix-spatialiser.conf
new file mode 100644
index 0000000..b39890c
--- /dev/null
+++ b/src/daemon/filter-chain/sink-matrix-spatialiser.conf
@@ -0,0 +1,41 @@
+# Matrix Spatialiser sink
+#
+# Copy this file into a conf.d/ directory such as
+# ~/.config/pipewire/filter-chain.conf.d/
+#
+# ( Jean-Philippe Guillemin <hyp3ri0n@sfr.fr> )
+#
+
+context.modules = [
+ { name = libpipewire-module-filter-chain
+ args = {
+ node.description = "Matrix Spatialiser"
+ media.name = "Matrix Spatialiser"
+ filter.graph = {
+ nodes = [
+ {
+ type = ladspa
+ name = matrix
+ plugin = matrix_spatialiser_1422
+ label = matrixSpatialiser
+ control = {
+ "Width" = 80
+ }
+ }
+ ]
+ inputs = [ "matrix:Input L" "matrix:Input R" ]
+ outputs = [ "matrix:Output L" "matrix:Output R" ]
+ }
+ audio.channels = 2
+ audio.position = [ FL FR ]
+ capture.props = {
+ node.name = "effect_input.matrix_spatialiser"
+ media.class = Audio/Sink
+ }
+ playback.props = {
+ node.name = "effect_output.matrix_spatialiser"
+ node.passive = true
+ }
+ }
+ }
+]
diff --git a/src/daemon/filter-chain/sink-mix-FL-FR.conf b/src/daemon/filter-chain/sink-mix-FL-FR.conf
new file mode 100644
index 0000000..8288fad
--- /dev/null
+++ b/src/daemon/filter-chain/sink-mix-FL-FR.conf
@@ -0,0 +1,40 @@
+# An example filter chain that makes a stereo sink that mixes
+# the FL and FR channels to a single FL channel
+#
+# Copy this file into a conf.d/ directory such as
+# ~/.config/pipewire/filter-chain.conf.d/
+#
+context.modules = [
+ { name = libpipewire-module-filter-chain
+ args = {
+ node.description = "Mix example"
+ media.name = "Mix example"
+ filter.graph = {
+ nodes = [
+ {
+ name = mix
+ type = builtin
+ label = mixer
+ control = {
+ "Gain 1" = 0.5
+ "Gain 2" = 0.5
+ }
+ }
+ ]
+ inputs = [ "mix:In 1" "mix:In 2" ]
+ outputs = [ "mix:Out" ]
+ }
+ capture.props = {
+ node.name = "mix_input.mix-FL-FR-to-FL"
+ audio.position = [ FL FR ]
+ media.class = "Audio/Sink"
+ }
+ playback.props = {
+ node.name = "mix_output.mix-FL-FR-to-FL"
+ audio.position = [ FL ]
+ stream.dont-remix = true
+ node.passive = true
+ }
+ }
+ }
+]
diff --git a/src/daemon/filter-chain/sink-virtual-surround-5.1-kemar.conf b/src/daemon/filter-chain/sink-virtual-surround-5.1-kemar.conf
new file mode 100644
index 0000000..ee8929b
--- /dev/null
+++ b/src/daemon/filter-chain/sink-virtual-surround-5.1-kemar.conf
@@ -0,0 +1,177 @@
+# Convolver 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 = "Virtual Surround Sink"
+ media.name = "Virtual Surround Sink"
+ filter.graph = {
+ nodes = [
+ {
+ type = builtin
+ label = convolver
+ name = convFL_L
+ config = {
+ filename = "hrir_kemar/hrir-kemar.wav"
+ channel = 0
+ }
+ }
+ {
+ type = builtin
+ label = convolver
+ name = convFL_R
+ config = {
+ filename = "hrir_kemar/hrir-kemar.wav"
+ channel = 1
+ }
+ }
+ {
+ type = builtin
+ label = convolver
+ name = convFR_L
+ config = {
+ filename = "hrir_kemar/hrir-kemar.wav"
+ channel = 1
+ }
+ }
+ {
+ type = builtin
+ label = convolver
+ name = convFR_R
+ config = {
+ filename = "hrir_kemar/hrir-kemar.wav"
+ channel = 0
+ }
+ }
+ {
+ type = builtin
+ label = convolver
+ name = convFC
+ config = {
+ filename = "hrir_kemar/hrir-kemar.wav"
+ channel = 2
+ }
+ }
+ {
+ type = builtin
+ label = convolver
+ name = convLFE
+ config = {
+ filename = "hrir_kemar/hrir-kemar.wav"
+ channel = 3
+ }
+ }
+ {
+ type = builtin
+ label = convolver
+ name = convSL_L
+ config = {
+ filename = "hrir_kemar/hrir-kemar.wav"
+ channel = 4
+ }
+ }
+ {
+ type = builtin
+ label = convolver
+ name = convSL_R
+ config = {
+ filename = "hrir_kemar/hrir-kemar.wav"
+ channel = 5
+ }
+ }
+ {
+ type = builtin
+ label = convolver
+ name = convSR_L
+ config = {
+ filename = "hrir_kemar/hrir-kemar.wav"
+ channel = 5
+ }
+ }
+ {
+ type = builtin
+ label = convolver
+ name = convSR_R
+ config = {
+ filename = "hrir_kemar/hrir-kemar.wav"
+ channel = 4
+ }
+ }
+ {
+ type = builtin
+ label = mixer
+ name = mixL
+ }
+ {
+ type = builtin
+ label = mixer
+ name = mixR
+ }
+ {
+ type = builtin
+ label = copy
+ name = copyFL
+ }
+ {
+ type = builtin
+ label = copy
+ name = copyFR
+ }
+ {
+ type = builtin
+ label = copy
+ name = copySL
+ }
+ {
+ type = builtin
+ label = copy
+ name = copySR
+ }
+ ]
+ links = [
+ { output = "copyFL:Out" input = "convFL_L:In" }
+ { output = "copyFL:Out" input = "convFL_R:In" }
+ { output = "copyFR:Out" input = "convFR_R:In" }
+ { output = "copyFR:Out" input = "convFR_L:In" }
+
+ { output = "copySL:Out" input = "convSL_L:In" }
+ { output = "copySL:Out" input = "convSL_R:In" }
+ { output = "copySR:Out" input = "convSR_R:In" }
+ { output = "copySR:Out" input = "convSR_L:In" }
+
+ { output = "convFL_L:Out" input = "mixL:In 1" }
+ { output = "convFR_L:Out" input = "mixL:In 2" }
+ { output = "convFC:Out" input = "mixL:In 3" }
+ { output = "convLFE:Out" input = "mixL:In 4" }
+ { output = "convSL_L:Out" input = "mixL:In 5" }
+ { output = "convSR_L:Out" input = "mixL:In 6" }
+
+ { output = "convFL_R:Out" input = "mixR:In 1" }
+ { output = "convFR_R:Out" input = "mixR:In 2" }
+ { output = "convFC:Out" input = "mixR:In 3" }
+ { output = "convLFE:Out" input = "mixR:In 4" }
+ { output = "convSL_R:Out" input = "mixR:In 5" }
+ { output = "convSR_R:Out" input = "mixR:In 6" }
+ ]
+ inputs = [ "copyFL:In" "copyFR:In" "convFC:In" "convLFE:In" "copySL:In" "copySR:In" ]
+ outputs = [ "mixL:Out" "mixR:Out" ]
+
+ }
+ capture.props = {
+ node.name = "effect_input.virtual-surround-5.1-kemar"
+ media.class = Audio/Sink
+ audio.channels = 6
+ audio.position = [ FL FR FC LFE SL SR]
+ }
+ playback.props = {
+ node.name = "effect_output.virtual-surround-5.1-kemar"
+ node.passive = true
+ audio.channels = 2
+ audio.position = [ FL FR ]
+ }
+ }
+ }
+]
diff --git a/src/daemon/filter-chain/sink-virtual-surround-7.1-hesuvi.conf b/src/daemon/filter-chain/sink-virtual-surround-7.1-hesuvi.conf
new file mode 100644
index 0000000..4aad310
--- /dev/null
+++ b/src/daemon/filter-chain/sink-virtual-surround-7.1-hesuvi.conf
@@ -0,0 +1,101 @@
+# Convolver 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 = "Virtual Surround Sink"
+ media.name = "Virtual Surround Sink"
+ filter.graph = {
+ nodes = [
+ # duplicate inputs
+ { type = builtin label = copy name = copyFL }
+ { type = builtin label = copy name = copyFR }
+ { type = builtin label = copy name = copyFC }
+ { type = builtin label = copy name = copyRL }
+ { type = builtin label = copy name = copyRR }
+ { type = builtin label = copy name = copySL }
+ { type = builtin label = copy name = copySR }
+ { type = builtin label = copy name = copyLFE }
+
+ # apply hrir - HeSuVi 14-channel WAV (not the *-.wav variants) (note: */44/* in HeSuVi are the same, but resampled to 44100)
+ { type = builtin label = convolver name = convFL_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 0 } }
+ { type = builtin label = convolver name = convFL_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 1 } }
+ { type = builtin label = convolver name = convSL_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 2 } }
+ { type = builtin label = convolver name = convSL_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 3 } }
+ { type = builtin label = convolver name = convRL_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 4 } }
+ { type = builtin label = convolver name = convRL_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 5 } }
+ { type = builtin label = convolver name = convFC_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 6 } }
+ { type = builtin label = convolver name = convFR_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 7 } }
+ { type = builtin label = convolver name = convFR_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 8 } }
+ { type = builtin label = convolver name = convSR_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 9 } }
+ { type = builtin label = convolver name = convSR_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 10 } }
+ { type = builtin label = convolver name = convRR_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 11 } }
+ { type = builtin label = convolver name = convRR_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 12 } }
+ { type = builtin label = convolver name = convFC_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 13 } }
+
+ # treat LFE as FC
+ { type = builtin label = convolver name = convLFE_L config = { filename = "hrir_hesuvi/hrir.wav" channel = 6 } }
+ { type = builtin label = convolver name = convLFE_R config = { filename = "hrir_hesuvi/hrir.wav" channel = 13 } }
+
+ # stereo output
+ { type = builtin label = mixer name = mixL }
+ { type = builtin label = mixer name = mixR }
+ ]
+ links = [
+ # input
+ { output = "copyFL:Out" input="convFL_L:In" }
+ { output = "copyFL:Out" input="convFL_R:In" }
+ { output = "copySL:Out" input="convSL_L:In" }
+ { output = "copySL:Out" input="convSL_R:In" }
+ { output = "copyRL:Out" input="convRL_L:In" }
+ { output = "copyRL:Out" input="convRL_R:In" }
+ { output = "copyFC:Out" input="convFC_L:In" }
+ { output = "copyFR:Out" input="convFR_R:In" }
+ { output = "copyFR:Out" input="convFR_L:In" }
+ { output = "copySR:Out" input="convSR_R:In" }
+ { output = "copySR:Out" input="convSR_L:In" }
+ { output = "copyRR:Out" input="convRR_R:In" }
+ { output = "copyRR:Out" input="convRR_L:In" }
+ { output = "copyFC:Out" input="convFC_R:In" }
+ { output = "copyLFE:Out" input="convLFE_L:In" }
+ { output = "copyLFE:Out" input="convLFE_R:In" }
+
+ # output
+ { output = "convFL_L:Out" input="mixL:In 1" }
+ { output = "convFL_R:Out" input="mixR:In 1" }
+ { output = "convSL_L:Out" input="mixL:In 2" }
+ { output = "convSL_R:Out" input="mixR:In 2" }
+ { output = "convRL_L:Out" input="mixL:In 3" }
+ { output = "convRL_R:Out" input="mixR:In 3" }
+ { output = "convFC_L:Out" input="mixL:In 4" }
+ { output = "convFC_R:Out" input="mixR:In 4" }
+ { output = "convFR_R:Out" input="mixR:In 5" }
+ { output = "convFR_L:Out" input="mixL:In 5" }
+ { output = "convSR_R:Out" input="mixR:In 6" }
+ { output = "convSR_L:Out" input="mixL:In 6" }
+ { output = "convRR_R:Out" input="mixR:In 7" }
+ { output = "convRR_L:Out" input="mixL:In 7" }
+ { output = "convLFE_R:Out" input="mixR:In 8" }
+ { output = "convLFE_L:Out" input="mixL:In 8" }
+ ]
+ inputs = [ "copyFL:In" "copyFR:In" "copyFC:In" "copyLFE:In" "copyRL:In" "copyRR:In", "copySL:In", "copySR:In" ]
+ outputs = [ "mixL:Out" "mixR:Out" ]
+ }
+ capture.props = {
+ node.name = "effect_input.virtual-surround-7.1-hesuvi"
+ media.class = Audio/Sink
+ audio.channels = 8
+ audio.position = [ FL FR FC LFE RL RR SL SR ]
+ }
+ playback.props = {
+ node.name = "effect_output.virtual-surround-7.1-hesuvi"
+ node.passive = true
+ audio.channels = 2
+ audio.position = [ FL FR ]
+ }
+ }
+ }
+]
diff --git a/src/daemon/filter-chain/source-duplicate-FL.conf b/src/daemon/filter-chain/source-duplicate-FL.conf
new file mode 100644
index 0000000..7e0158f
--- /dev/null
+++ b/src/daemon/filter-chain/source-duplicate-FL.conf
@@ -0,0 +1,52 @@
+# An example filter chain that makes a source that duplicates the FL channel
+# to FL and FR.
+#
+# Copy this file into a conf.d/ directory such as
+# ~/.config/pipewire/filter-chain.conf.d/
+#
+context.modules = [
+ { name = libpipewire-module-filter-chain
+ args = {
+ node.description = "Remap example"
+ media.name = "Remap example"
+ filter.graph = {
+ nodes = [
+ {
+ name = copyIL
+ type = builtin
+ label = copy
+ }
+ {
+ name = copyOL
+ type = builtin
+ label = copy
+ }
+ {
+ name = copyOR
+ type = builtin
+ label = copy
+ }
+ ]
+ links = [
+ # we can only tee from nodes, not inputs so we need
+ # to copy the inputs and then tee.
+ { output = "copyIL:Out" input = "copyOL:In" }
+ { output = "copyIL:Out" input = "copyOR:In" }
+ ]
+ inputs = [ "copyIL:In" ]
+ outputs = [ "copyOL:Out" "copyOR:Out" ]
+ }
+ capture.props = {
+ node.name = "remap_input.remap-FL-to-FL-FR"
+ audio.position = [ FL ]
+ stream.dont-remix = true
+ node.passive = true
+ }
+ playback.props = {
+ node.name = "remap_output.remap-FL-to-FL-FR"
+ audio.position = [ FL FR ]
+ media.class = "Audio/Source"
+ }
+ }
+ }
+]
diff --git a/src/daemon/filter-chain/source-rnnoise.conf b/src/daemon/filter-chain/source-rnnoise.conf
new file mode 100644
index 0000000..5babd81
--- /dev/null
+++ b/src/daemon/filter-chain/source-rnnoise.conf
@@ -0,0 +1,35 @@
+# Noise canceling source
+#
+# 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 = "Noise Canceling source"
+ media.name = "Noise Canceling source"
+ filter.graph = {
+ nodes = [
+ {
+ type = ladspa
+ name = rnnoise
+ plugin = librnnoise_ladspa
+ label = noise_suppressor_stereo
+ control = {
+ "VAD Threshold (%)" 50.0
+ }
+ }
+ ]
+ }
+ audio.position = [ FL FR ]
+ capture.props = {
+ node.name = "effect_input.rnnoise"
+ node.passive = true
+ }
+ playback.props = {
+ node.name = "effect_output.rnnoise"
+ media.class = Audio/Source
+ }
+ }
+ }
+]
diff --git a/src/daemon/jack.conf.in b/src/daemon/jack.conf.in
new file mode 100644
index 0000000..95a86cb
--- /dev/null
+++ b/src/daemon/jack.conf.in
@@ -0,0 +1,128 @@
+# JACK 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@/jack.conf.d/ for system-wide changes or in
+# ~/.config/pipewire/jack.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.
+ #
+ support.* = support/libspa-support
+}
+
+context.modules = [
+ #{ name = <module-name>
+ # [ args = { <key> = <value> ... } ]
+ # [ flags = [ [ ifexists ] [ nofail ] ]
+ #}
+ #
+ # Loads a module with the given parameters.
+ # If ifexists is given, the module is ignored when it is not found.
+ # If nofail is given, module initialization failures are ignored.
+ #
+ #
+ # Boost the data thread priority.
+ { name = libpipewire-module-rt
+ args = {
+ #rt.prio = 88
+ #rt.time.soft = -1
+ #rt.time.hard = -1
+ }
+ flags = [ ifexists nofail ]
+ }
+
+ # The native communication protocol.
+ { name = libpipewire-module-protocol-native }
+
+ # Allows creating nodes that run in the context of the
+ # client. Is used by all clients that want to provide
+ # data to PipeWire.
+ { name = libpipewire-module-client-node }
+
+ # Allows applications to create metadata objects. It creates
+ # a factory for Metadata objects.
+ { name = libpipewire-module-metadata }
+]
+
+# global properties for all jack clients
+jack.properties = {
+ #node.latency = 1024/48000
+ #node.rate = 1/48000
+ #node.quantum = 1024/48000
+ #node.lock-quantum = true
+ #node.force-quantum = 0
+ #jack.show-monitor = true
+ #jack.merge-monitor = true
+ #jack.short-name = false
+ #jack.filter-name = false
+ #jack.filter-char = " "
+ #
+ # allow: Don't restrict self connect requests
+ # fail-external: Fail self connect requests to external ports only
+ # ignore-external: Ignore self connect requests to external ports only
+ # fail-all: Fail all self connect requests
+ # ignore-all: Ignore all self connect requests
+ #jack.self-connect-mode = allow
+ #jack.locked-process = true
+ #jack.default-as-system = false
+ #jack.fix-midi-events = true
+ #jack.global-buffer-size = false
+}
+
+# client specific properties
+jack.rules = [
+ { matches = [
+ {
+ # all keys must match the value. ~ starts regex.
+ #client.name = "Carla"
+ #application.process.binary = "jack_simple_client"
+ #application.name = "~jack_simple_client.*"
+ }
+ ]
+ actions = {
+ update-props = {
+ #node.latency = 512/48000
+ }
+ }
+ }
+ { matches = [ { application.process.binary = "jack_bufsize" } ]
+ actions = {
+ update-props = {
+ jack.global-buffer-size = true # quantum set globally using metadata
+ }
+ }
+ }
+ { matches = [ { application.process.binary = "qsynth" } ]
+ actions = {
+ update-props = {
+ node.pause-on-idle = false # makes audio fade out when idle
+ node.passive = true # makes the sink and qsynth suspend
+ }
+ }
+ }
+ { matches = [ { client.name = "Mixxx" } ]
+ actions = {
+ update-props = {
+ jack.merge-monitor = false
+ }
+ }
+ }
+]
diff --git a/src/daemon/meson.build b/src/daemon/meson.build
new file mode 100644
index 0000000..5d5914e
--- /dev/null
+++ b/src/daemon/meson.build
@@ -0,0 +1,141 @@
+pipewire_daemon_sources = [
+ 'pipewire.c',
+]
+
+pipewire_c_args = [
+ '-DG_LOG_DOMAIN=g_log_domain_pipewire',
+]
+
+conf_config = configuration_data()
+conf_config.set('VERSION', '"@0@"'.format(pipewire_version))
+conf_config.set('PIPEWIRE_CONFIG_DIR', pipewire_configdir)
+conf_config.set('session_manager_path', pipewire_bindir / 'pipewire-media-session')
+conf_config.set('session_manager_args', '')
+conf_config.set('pipewire_path', pipewire_bindir / 'pipewire')
+conf_config.set('pipewire_pulse_path', pipewire_bindir / 'pipewire-pulse')
+conf_config.set('sm_comment', '#')
+conf_config.set('pulse_comment', '#')
+
+conf_config_uninstalled = conf_config
+conf_config_uninstalled.set('pipewire_path',
+ meson.project_build_root() / 'src' / 'daemon' / 'pipewire')
+conf_config_uninstalled.set('pipewire_pulse_path',
+ meson.project_build_root() / 'src' / 'daemon' / 'pipewire-pulse')
+conf_config_uninstalled.set('pulse_comment', '')
+
+build_ms = 'media-session' in get_option('session-managers')
+build_wp = 'wireplumber' in get_option('session-managers')
+default_sm = get_option('session-managers').get(0, '')
+
+summary({'Build media-session': build_ms,
+ 'Build wireplumber': build_wp,
+ 'Default session-manager': default_sm},
+ section: 'Session managers',
+ bool_yn: true)
+
+if build_wp
+ wp_proj = subproject('wireplumber', required : true)
+endif
+if build_ms
+ ms_proj = subproject('media-session', required : true)
+endif
+
+if default_sm == ''
+ summary({'No session manager': 'pw-uninstalled.sh will not work out of the box!'})
+elif default_sm == 'media-session'
+ ms_bindir = ms_proj.get_variable('media_session_bin_dir', pipewire_bindir)
+ conf_config.set('session_manager_path', ms_bindir / 'pipewire-media-session')
+
+ ms_uninstalled = ms_proj.get_variable('media_session_uninstalled')
+ conf_config_uninstalled.set('session_manager_path', ms_uninstalled.full_path())
+ conf_config_uninstalled.set('session_manager_args', 'pipewire-media-session')
+ conf_config_uninstalled.set('sm_comment', '')
+elif default_sm == 'wireplumber'
+ wp_bindir = wp_proj.get_variable('wireplumber_bin_dir', pipewire_bindir)
+ conf_config.set('session_manager_path', wp_bindir / 'wireplumber')
+
+ wp_uninstalled = wp_proj.get_variable('wireplumber_uninstalled')
+ conf_config_uninstalled.set('session_manager_path', wp_uninstalled.full_path())
+ conf_config_uninstalled.set('session_manager_args', 'wireplumber')
+ conf_config_uninstalled.set('sm_comment', '')
+else
+ conf_config_uninstalled.set('session_manager_path', default_sm)
+ conf_config_uninstalled.set('sm_comment', '')
+endif
+
+conf_files = [
+ 'pipewire.conf',
+ 'client.conf',
+ 'client-rt.conf',
+ 'filter-chain.conf',
+ 'jack.conf',
+ 'minimal.conf',
+ 'pipewire-pulse.conf',
+ 'pipewire-avb.conf',
+]
+
+foreach c : conf_files
+ configure_file(input : '@0@.in'.format(c),
+ output : c,
+ configuration : conf_config,
+ install_dir : pipewire_confdatadir)
+endforeach
+
+configure_file(input : 'pipewire.conf.in',
+ output : 'pipewire-uninstalled.conf',
+ configuration : conf_config_uninstalled)
+
+pipewire_exec = executable('pipewire',
+ pipewire_daemon_sources,
+ install: true,
+ c_args : pipewire_c_args,
+ include_directories : [ configinc ],
+ dependencies : [ spa_dep, pipewire_dep, ],
+)
+
+executable('pipewire-pulse',
+ pipewire_daemon_sources,
+ install: true,
+ c_args : pipewire_c_args,
+ include_directories : [ configinc ],
+ dependencies : [ spa_dep, pipewire_dep, ],
+)
+
+executable('pipewire-avb',
+ pipewire_daemon_sources,
+ install: true,
+ c_args : pipewire_c_args,
+ include_directories : [ configinc ],
+ dependencies : [ spa_dep, pipewire_dep, ],
+)
+
+ln = find_program('ln')
+
+custom_target('pipewire-uninstalled',
+ build_by_default: true,
+ install: false,
+ input: pipewire_exec,
+ output: 'pipewire-uninstalled',
+ command: [ln, '-fs', meson.project_build_root() + '/@INPUT@', '@OUTPUT@'],
+)
+
+#desktop_file = i18n.merge_file(
+# input : 'pipewire.desktop.in',
+# output : 'pipewire.desktop',
+# po_dir : po_dir,
+# type : 'desktop',
+# install : true,
+# install_dir : pipewire_sysconfdir / 'xdg' / 'autostart'
+#)
+#
+#desktop_utils = find_program('desktop-file-validate', required: false)
+#if desktop_utils.found()
+# test('Validate desktop file', desktop_utils,
+# args: [ desktop_file ],
+# )
+#endif
+
+subdir('filter-chain')
+if systemd.found()
+ subdir('systemd')
+endif
diff --git a/src/daemon/minimal.conf.in b/src/daemon/minimal.conf.in
new file mode 100644
index 0000000..4a3a2cb
--- /dev/null
+++ b/src/daemon/minimal.conf.in
@@ -0,0 +1,352 @@
+# Simple daemon 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@/minimal.conf.d/ for system-wide changes or in
+# ~/.config/pipewire/minimal.conf.d/ for local changes.
+#
+
+context.properties = {
+ ## Configure properties in the system.
+ #library.name.system = support/libspa-support
+ #context.data-loop.library.name.system = support/libspa-support
+ #support.dbus = true
+ #link.max-buffers = 64
+ link.max-buffers = 16 # version < 3 clients can't handle more
+ #mem.warn-mlock = false
+ #mem.allow-mlock = true
+ #mem.mlock-all = false
+ #clock.power-of-two-quantum = true
+ #log.level = 2
+ #cpu.zero.denormals = false
+
+ core.daemon = true # listening for socket connections
+ core.name = pipewire-0 # core name and socket name
+
+ ## Properties for the DSP configuration.
+ #default.clock.rate = 48000
+ #default.clock.allowed-rates = [ 48000 ]
+ #default.clock.quantum = 1024
+ #default.clock.min-quantum = 32
+ #default.clock.max-quantum = 2048
+ #default.clock.quantum-limit = 8192
+ #default.video.width = 640
+ #default.video.height = 480
+ #default.video.rate.num = 25
+ #default.video.rate.denom = 1
+ #
+ settings.check-quantum = true
+ settings.check-rate = true
+ #
+ # These overrides are only applied when running in a vm.
+ vm.overrides = {
+ default.clock.min-quantum = 1024
+ }
+}
+
+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
+ api.alsa.* = alsa/libspa-alsa
+ support.* = support/libspa-support
+}
+
+context.modules = [
+ #{ name = <module-name>
+ # [ args = { <key> = <value> ... } ]
+ # [ flags = [ [ ifexists ] [ nofail ] ]
+ #}
+ #
+ # Loads a module with the given parameters.
+ # If ifexists is given, the module is ignored when it is not found.
+ # If nofail is given, module initialization failures are ignored.
+ #
+
+ # Uses realtime scheduling to boost the audio thread priorities. This uses
+ # RTKit if the user doesn't have permission to use regular realtime
+ # scheduling.
+ { name = libpipewire-module-rt
+ args = {
+ nice.level = -11
+ #rt.prio = 88
+ #rt.time.soft = -1
+ #rt.time.hard = -1
+ }
+ flags = [ ifexists nofail ]
+ }
+
+ # The native communication protocol.
+ { name = libpipewire-module-protocol-native }
+
+ # 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 }
+
+ # Allows applications to create metadata objects. It creates
+ # a factory for Metadata objects.
+ { name = libpipewire-module-metadata }
+
+ # Creates a factory for making nodes that run in the
+ # context of the PipeWire server.
+ { name = libpipewire-module-spa-node-factory }
+
+ # 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 }
+
+ # The access module can perform access checks and block
+ # new clients.
+ { name = libpipewire-module-access
+ args = {
+ # access.allowed to list an array of paths of allowed
+ # apps.
+ #access.allowed = [
+ # @session_manager_path@
+ #]
+
+ # An array of rejected paths.
+ #access.rejected = [ ]
+
+ # An array of paths with restricted access.
+ #access.restricted = [ ]
+
+ # Anything not in the above lists gets assigned the
+ # access.force permission.
+ #access.force = flatpak
+ }
+ }
+
+ # Makes a factory for wrapping nodes in an adapter with a
+ # converter and resampler.
+ { name = libpipewire-module-adapter }
+
+ # Makes a factory for creating links between ports.
+ { name = libpipewire-module-link-factory }
+]
+
+context.objects = [
+ #{ factory = <factory-name>
+ # [ args = { <key> = <value> ... } ]
+ # [ flags = [ [ nofail ] ]
+ #}
+ #
+ # 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 Spa:Pod:Object: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 } }
+ #{ factory = spa-node-factory args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } }
+
+ # Make a default metadata store
+ { factory = metadata args = { metadata.name = default } }
+
+ # A default dummy driver. This handles nodes marked with the "node.always-driver"
+ # property when no other driver is currently active. JACK clients need this.
+ { factory = spa-node-factory
+ args = {
+ factory.name = support.node.driver
+ node.name = Dummy-Driver
+ node.group = pipewire.dummy
+ priority.driver = 20000
+ }
+ }
+ { factory = spa-node-factory
+ args = {
+ factory.name = support.node.driver
+ node.name = Freewheel-Driver
+ priority.driver = 19000
+ node.group = pipewire.freewheel
+ node.freewheel = true
+ }
+ }
+
+ # This creates a single PCM source device for the given
+ # alsa device path hw:0. You can change source to sink
+ # to make a sink in the same way.
+ { factory = adapter
+ args = {
+ factory.name = api.alsa.pcm.source
+ node.name = "system"
+ node.description = "system"
+ media.class = "Audio/Source"
+ api.alsa.path = "hw:0"
+ #api.alsa.period-size = 0
+ #api.alsa.period-num = 0
+ #api.alsa.headroom = 0
+ #api.alsa.start-delay = 0
+ #api.alsa.disable-mmap = false
+ #api.alsa.disable-batch = false
+ #api.alsa.use-chmap = false
+ #api.alsa.multirate = true
+ #latency.internal.rate = 0
+ #latency.internal.ns = 0
+ #clock.name = api.alsa.0
+ node.suspend-on-idle = true
+ #audio.format = "S32"
+ #audio.rate = 48000
+ #audio.allowed-rates = [ ]
+ #audio.channels = 4
+ #audio.position = [ FL FR RL RR ]
+ #resample.quality = 4
+ resample.disable = true
+ #monitor.channel-volumes = false
+ #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
+ channelmix.disable = true
+ #dither.noise = 0
+ #node.param.Props = {
+ # params = [
+ # audio.channels 6
+ # ]
+ #}
+ adapter.auto-port-config = {
+ mode = dsp
+ monitor = false
+ control = false
+ position = unknown # aux, preserve
+ }
+ #node.param.PortConfig = {
+ # direction = Output
+ # mode = dsp
+ # format = {
+ # mediaType = audio
+ # mediaSubtype = raw
+ # format = F32
+ # rate = 48000
+ # channels = 4
+ # position = [ FL FR RL RR ]
+ # }
+ #}
+ }
+ }
+ { factory = adapter
+ args = {
+ factory.name = api.alsa.pcm.sink
+ node.name = "system"
+ node.description = "system"
+ media.class = "Audio/Sink"
+ api.alsa.path = "hw:0"
+ #api.alsa.period-size = 0
+ #api.alsa.period-num = 0
+ #api.alsa.headroom = 0
+ #api.alsa.start-delay = 0
+ #api.alsa.disable-mmap = false
+ #api.alsa.disable-batch = false
+ #api.alsa.use-chmap = false
+ #api.alsa.multirate = true
+ #latency.internal.rate = 0
+ #latency.internal.ns = 0
+ #clock.name = api.alsa.0
+ node.suspend-on-idle = true
+ #audio.format = "S32"
+ #audio.rate = 48000
+ #audio.allowed-rates = [ ]
+ #audio.channels = 2
+ #audio.position = "FL,FR"
+ #resample.quality = 4
+ resample.disable = true
+ #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
+ channelmix.disable = true
+ #dither.noise = 0
+ #node.param.Props = {
+ # params = [
+ # audio.format S16
+ # ]
+ #}
+ adapter.auto-port-config = {
+ mode = dsp
+ monitor = false
+ control = false
+ position = unknown # aux, preserve
+ }
+ #node.param.PortConfig = {
+ # direction = Input
+ # mode = dsp
+ # monitor = true
+ # format = {
+ # mediaType = audio
+ # mediaSubtype = raw
+ # format = F32
+ # rate = 48000
+ # channels = 4
+ # }
+ #}
+ }
+ }
+ # This creates a new Source node. It will have input ports
+ # that you can link, to provide audio for this source.
+ #{ factory = adapter
+ # args = {
+ # factory.name = support.null-audio-sink
+ # node.name = "my-mic"
+ # node.description = "Microphone"
+ # media.class = "Audio/Source/Virtual"
+ # audio.position = "FL,FR"
+ # adapter.auto-port-config = {
+ # mode = dsp
+ # monitor = true
+ # position = preserve # unknown, aux, preserve
+ # }
+ # }
+ #}
+ # This creates a new link between the source and the virtual
+ # source ports.
+ #{ factory = link-factory
+ # args = {
+ # link.output.node = system
+ # link.output.port = capture_1
+ # link.input.node = my-mic
+ # link.input.port = input_FL
+ # link.passive = true
+ # }
+ #}
+ #{ factory = link-factory
+ # args = {
+ # link.output.node = system
+ # link.output.port = capture_2
+ # link.input.node = my-mic
+ # link.input.port = input_FR
+ # link.passive = true
+ # }
+ #}
+]
+
+context.exec = [
+ #{ path = <program-name> [ args = "<arguments>" ] }
+ #
+ # Execute the given program with arguments.
+ #
+ # You can optionally start the pulseaudio-server here as well
+ # but it is better to start it as a systemd service.
+ # It can be interesting to start another daemon here that listens
+ # on another address with the -a option (eg. -a tcp:4713).
+ #
+ #@pulse_comment@{ path = "@pipewire_path@" args = "-c pipewire-pulse.conf" }
+]
diff --git a/src/daemon/pipewire-avb.conf.in b/src/daemon/pipewire-avb.conf.in
new file mode 100644
index 0000000..68f89ca
--- /dev/null
+++ b/src/daemon/pipewire-avb.conf.in
@@ -0,0 +1,73 @@
+# PulseAudio config file for PipeWire version @VERSION@ #
+#
+# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes
+# or in ~/.config/pipewire for local changes.
+#
+# It is also possible to place a file with an updated section in
+# @PIPEWIRE_CONFIG_DIR@/pipewire-pulse.conf.d/ for system-wide changes or in
+# ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes.
+#
+
+context.properties = {
+ ## Configure properties in the system.
+ #mem.warn-mlock = false
+ #mem.allow-mlock = true
+ #mem.mlock-all = false
+ #log.level = 2
+
+ #default.clock.quantum-limit = 8192
+}
+
+context.spa-libs = {
+ audio.convert.* = audioconvert/libspa-audioconvert
+ support.* = support/libspa-support
+}
+
+context.modules = [
+ { name = libpipewire-module-rt
+ args = {
+ nice.level = -11
+ #rt.prio = 88
+ #rt.time.soft = -1
+ #rt.time.hard = -1
+ }
+ flags = [ ifexists nofail ]
+ }
+ { name = libpipewire-module-protocol-native }
+ { name = libpipewire-module-client-node }
+ { name = libpipewire-module-adapter }
+ { name = libpipewire-module-avb
+ args = {
+ # contents of avb.properties can also be placed here
+ # to have config per server.
+ }
+ }
+]
+
+# Extra modules can be loaded here. Setup in default.pa can be moved here
+context.exec = [
+ #{ path = "pactl" args = "load-module module-always-sink" }
+]
+
+stream.properties = {
+ #node.latency = 1024/48000
+ #node.autoconnect = true
+ #resample.quality = 4
+ #channelmix.normalize = false
+ #channelmix.mix-lfe = true
+ #channelmix.upmix = true
+ #channelmix.lfe-cutoff = 120
+ #channelmix.fc-cutoff = 6000
+ #channelmix.rear-delay = 12.0
+ #channelmix.stereo-widen = 0.1
+ #channelmix.hilbert-taps = 0
+}
+
+avb.properties = {
+ # the addresses this server listens on
+ #ifname = "eth0.2"
+ ifname = "enp3s0"
+ # These overrides are only applied when running in a vm.
+ vm.overrides = {
+ }
+}
diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in
new file mode 100644
index 0000000..18bca3a
--- /dev/null
+++ b/src/daemon/pipewire-pulse.conf.in
@@ -0,0 +1,160 @@
+# PulseAudio config file for PipeWire version @VERSION@ #
+#
+# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes
+# or in ~/.config/pipewire for local changes.
+#
+# It is also possible to place a file with an updated section in
+# @PIPEWIRE_CONFIG_DIR@/pipewire-pulse.conf.d/ for system-wide changes or in
+# ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes.
+#
+
+context.properties = {
+ ## Configure properties in the system.
+ #mem.warn-mlock = false
+ #mem.allow-mlock = true
+ #mem.mlock-all = false
+ #log.level = 2
+
+ #default.clock.quantum-limit = 8192
+}
+
+context.spa-libs = {
+ audio.convert.* = audioconvert/libspa-audioconvert
+ support.* = support/libspa-support
+}
+
+context.modules = [
+ { name = libpipewire-module-rt
+ args = {
+ nice.level = -11
+ #rt.prio = 88
+ #rt.time.soft = -1
+ #rt.time.hard = -1
+ }
+ flags = [ ifexists nofail ]
+ }
+ { name = libpipewire-module-protocol-native }
+ { name = libpipewire-module-client-node }
+ { name = libpipewire-module-adapter }
+ { name = libpipewire-module-metadata }
+
+ { name = libpipewire-module-protocol-pulse
+ args = {
+ # contents of pulse.properties can also be placed here
+ # to have config per server.
+ }
+ }
+]
+
+# Extra scripts can be started here. Setup in default.pa can be moved in
+# a script or in pulse.cmd below
+context.exec = [
+ #{ path = "pactl" args = "load-module module-always-sink" }
+ #{ path = "pactl" args = "upload-sample my-sample.wav my-sample" }
+ #{ path = "/usr/bin/sh" args = "~/.config/pipewire/default.pw" }
+]
+
+# Extra commands can be executed here.
+# load-module : loads a module with args and flags
+# args = "<module-name> <module-args>"
+# flags = [ "no-fail" ]
+pulse.cmd = [
+ { cmd = "load-module" args = "module-always-sink" flags = [ ] }
+ #{ cmd = "load-module" args = "module-switch-on-connect" }
+ #{ cmd = "load-module" args = "module-gsettings" flags = [ "nofail" ] }
+]
+
+stream.properties = {
+ #node.latency = 1024/48000
+ #node.autoconnect = true
+ #resample.quality = 4
+ #channelmix.normalize = false
+ #channelmix.mix-lfe = 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
+}
+
+pulse.properties = {
+ # the addresses this server listens on
+ server.address = [
+ "unix:native"
+ #"unix:/tmp/something" # absolute paths may be used
+ #"tcp:4713" # IPv4 and IPv6 on all addresses
+ #"tcp:[::]:9999" # IPv6 on all addresses
+ #"tcp:127.0.0.1:8888" # IPv4 on a single address
+ #
+ #{ address = "tcp:4713" # address
+ # max-clients = 64 # maximum number of clients
+ # listen-backlog = 32 # backlog in the server listen queue
+ # client.access = "restricted" # permissions for clients
+ #}
+ ]
+ #pulse.min.req = 256/48000 # 5ms
+ #pulse.default.req = 960/48000 # 20 milliseconds
+ #pulse.min.frag = 256/48000 # 5ms
+ #pulse.default.frag = 96000/48000 # 2 seconds
+ #pulse.default.tlength = 96000/48000 # 2 seconds
+ #pulse.min.quantum = 256/48000 # 5ms
+ #pulse.idle.timeout = 0 # don't pause after underruns
+ #pulse.default.format = F32
+ #pulse.default.position = [ FL FR ]
+ # These overrides are only applied when running in a vm.
+ vm.overrides = {
+ pulse.min.quantum = 1024/48000 # 22ms
+ }
+}
+
+# client/stream specific properties
+pulse.rules = [
+ {
+ matches = [
+ {
+ # all keys must match the value. ~ starts regex.
+ #client.name = "Firefox"
+ #application.process.binary = "teams"
+ #application.name = "~speech-dispatcher.*"
+ }
+ ]
+ actions = {
+ update-props = {
+ #node.latency = 512/48000
+ }
+ # Possible quirks:"
+ # force-s16-info forces sink and source info as S16 format
+ # remove-capture-dont-move removes the capture DONT_MOVE flag
+ #quirks = [ ]
+ }
+ }
+ {
+ # skype does not want to use devices that don't have an S16 sample format.
+ matches = [
+ { application.process.binary = "teams" }
+ { application.process.binary = "teams-insiders" }
+ { application.process.binary = "skypeforlinux" }
+ ]
+ actions = { quirks = [ force-s16-info ] }
+ }
+ {
+ # firefox marks the capture streams as don't move and then they
+ # can't be moved with pavucontrol or other tools.
+ matches = [ { application.process.binary = "firefox" } ]
+ actions = { quirks = [ remove-capture-dont-move ] }
+ }
+ {
+ # speech dispatcher asks for too small latency and then underruns.
+ matches = [ { application.name = "~speech-dispatcher.*" } ]
+ actions = {
+ update-props = {
+ pulse.min.req = 512/48000 # 10.6ms
+ pulse.min.quantum = 512/48000 # 10.6ms
+ pulse.idle.timeout = 5 # pause after 5 seconds of underrun
+ }
+ }
+ }
+]
diff --git a/src/daemon/pipewire.c b/src/daemon/pipewire.c
new file mode 100644
index 0000000..97569c0
--- /dev/null
+++ b/src/daemon/pipewire.c
@@ -0,0 +1,143 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <limits.h>
+#include <signal.h>
+#include <getopt.h>
+#include <libgen.h>
+#include <locale.h>
+
+#include <spa/utils/result.h>
+#include <pipewire/pipewire.h>
+
+#include <pipewire/i18n.h>
+
+#include "config.h"
+
+static void do_quit(void *data, int signal_number)
+{
+ struct pw_main_loop *loop = data;
+ pw_main_loop_quit(loop);
+}
+
+static void show_help(const char *name, const char *config_name)
+{
+ fprintf(stdout, _("%s [options]\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " -c, --config Load config (Default %s)\n"),
+ name,
+ config_name);
+}
+
+int main(int argc, char *argv[])
+{
+ struct pw_context *context = NULL;
+ struct pw_main_loop *loop = NULL;
+ struct pw_properties *properties = NULL;
+ static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "config", required_argument, NULL, 'c' },
+ { "verbose", no_argument, NULL, 'v' },
+
+ { NULL, 0, NULL, 0}
+ };
+ int c, res = 0;
+ char path[PATH_MAX];
+ const char *config_name;
+ enum spa_log_level level = pw_log_level;
+
+ if (setenv("PIPEWIRE_INTERNAL", "1", 1) < 0)
+ fprintf(stderr, "can't set PIPEWIRE_INTERNAL env: %m");
+
+ snprintf(path, sizeof(path), "%s.conf", argv[0]);
+ config_name = basename(path);
+
+ setlocale(LC_ALL, "");
+ pw_init(&argc, &argv);
+
+ while ((c = getopt_long(argc, argv, "hVc:v", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'v':
+ if (level < SPA_LOG_LEVEL_TRACE)
+ pw_log_set_level(++level);
+ break;
+ case 'h':
+ show_help(argv[0], config_name);
+ return 0;
+ case 'V':
+ fprintf(stdout, "%s\n"
+ "Compiled with libpipewire %s\n"
+ "Linked with libpipewire %s\n",
+ argv[0],
+ pw_get_headers_version(),
+ pw_get_library_version());
+ return 0;
+ case 'c':
+ config_name = optarg;
+ break;
+ default:
+ res = -EINVAL;
+ goto done;
+ }
+ }
+
+ properties = pw_properties_new(
+ PW_KEY_CONFIG_NAME, config_name,
+ NULL);
+
+ loop = pw_main_loop_new(&properties->dict);
+ if (loop == NULL) {
+ pw_log_error("failed to create main-loop: %m");
+ res = -errno;
+ goto done;
+ }
+
+ pw_loop_add_signal(pw_main_loop_get_loop(loop), SIGINT, do_quit, loop);
+ pw_loop_add_signal(pw_main_loop_get_loop(loop), SIGTERM, do_quit, loop);
+
+ context = pw_context_new(pw_main_loop_get_loop(loop), properties, 0);
+ properties = NULL;
+
+ if (context == NULL) {
+ pw_log_error("failed to create context: %m");
+ res = -errno;
+ goto done;
+ }
+
+ pw_log_info("start main loop");
+ pw_main_loop_run(loop);
+ pw_log_info("leave main loop");
+
+done:
+ pw_properties_free(properties);
+ if (context)
+ pw_context_destroy(context);
+ if (loop)
+ pw_main_loop_destroy(loop);
+ pw_deinit();
+
+ return res;
+}
diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in
new file mode 100644
index 0000000..4aa30a4
--- /dev/null
+++ b/src/daemon/pipewire.conf.in
@@ -0,0 +1,259 @@
+# Daemon config file for PipeWire version @VERSION@ #
+#
+# Copy and edit this file in @PIPEWIRE_CONFIG_DIR@ for system-wide changes
+# or in ~/.config/pipewire for local changes.
+#
+# It is also possible to place a file with an updated section in
+# @PIPEWIRE_CONFIG_DIR@/pipewire.conf.d/ for system-wide changes or in
+# ~/.config/pipewire/pipewire.conf.d/ for local changes.
+#
+
+context.properties = {
+ ## Configure properties in the system.
+ #library.name.system = support/libspa-support
+ #context.data-loop.library.name.system = support/libspa-support
+ #support.dbus = true
+ #link.max-buffers = 64
+ link.max-buffers = 16 # version < 3 clients can't handle more
+ #mem.warn-mlock = false
+ #mem.allow-mlock = true
+ #mem.mlock-all = false
+ #clock.power-of-two-quantum = true
+ #log.level = 2
+ #cpu.zero.denormals = false
+
+ core.daemon = true # listening for socket connections
+ core.name = pipewire-0 # core name and socket name
+
+ ## Properties for the DSP configuration.
+ #default.clock.rate = 48000
+ #default.clock.allowed-rates = [ 48000 ]
+ #default.clock.quantum = 1024
+ default.clock.min-quantum = 16
+ #default.clock.max-quantum = 2048
+ #default.clock.quantum-limit = 8192
+ #default.video.width = 640
+ #default.video.height = 480
+ #default.video.rate.num = 25
+ #default.video.rate.denom = 1
+ #
+ #settings.check-quantum = false
+ #settings.check-rate = false
+ #
+ # These overrides are only applied when running in a vm.
+ vm.overrides = {
+ default.clock.min-quantum = 1024
+ }
+}
+
+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
+ avb.* = avb/libspa-avb
+ api.alsa.* = alsa/libspa-alsa
+ api.v4l2.* = v4l2/libspa-v4l2
+ api.libcamera.* = libcamera/libspa-libcamera
+ api.bluez5.* = bluez5/libspa-bluez5
+ api.vulkan.* = vulkan/libspa-vulkan
+ api.jack.* = jack/libspa-jack
+ support.* = support/libspa-support
+ #videotestsrc = videotestsrc/libspa-videotestsrc
+ #audiotestsrc = audiotestsrc/libspa-audiotestsrc
+}
+
+context.modules = [
+ #{ name = <module-name>
+ # [ args = { <key> = <value> ... } ]
+ # [ flags = [ [ ifexists ] [ nofail ] ]
+ #}
+ #
+ # Loads a module with the given parameters.
+ # If ifexists is given, the module is ignored when it is not found.
+ # If nofail is given, module initialization failures are ignored.
+ #
+
+ # Uses realtime scheduling to boost the audio thread priorities. This uses
+ # RTKit if the user doesn't have permission to use regular realtime
+ # scheduling.
+ { name = libpipewire-module-rt
+ args = {
+ nice.level = -11
+ #rt.prio = 88
+ #rt.time.soft = -1
+ #rt.time.hard = -1
+ }
+ flags = [ ifexists nofail ]
+ }
+
+ # The native communication protocol.
+ { name = libpipewire-module-protocol-native }
+
+ # 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 }
+
+ # Allows applications to create metadata objects. It creates
+ # a factory for Metadata objects.
+ { name = libpipewire-module-metadata }
+
+ # Creates a factory for making devices that run in the
+ # context of the PipeWire server.
+ { name = libpipewire-module-spa-device-factory }
+
+ # Creates a factory for making nodes that run in the
+ # context of the PipeWire server.
+ { name = libpipewire-module-spa-node-factory }
+
+ # 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 }
+
+ # 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 ]
+ }
+
+ # The access module can perform access checks and block
+ # new clients.
+ { name = libpipewire-module-access
+ args = {
+ # access.allowed to list an array of paths of allowed
+ # apps.
+ #access.allowed = [
+ # @session_manager_path@
+ #]
+
+ # An array of rejected paths.
+ #access.rejected = [ ]
+
+ # An array of paths with restricted access.
+ #access.restricted = [ ]
+
+ # Anything not in the above lists gets assigned the
+ # access.force permission.
+ #access.force = flatpak
+ }
+ }
+
+ # Makes a factory for wrapping nodes in an adapter with a
+ # converter and resampler.
+ { name = libpipewire-module-adapter }
+
+ # Makes a factory for creating links between ports.
+ { name = libpipewire-module-link-factory }
+
+ # Provides factories to make session manager objects.
+ { name = libpipewire-module-session-manager }
+
+ # Use libcanberra to play X11 Bell
+ { name = libpipewire-module-x11-bell
+ args = {
+ #sink.name = "@DEFAULT_SINK@"
+ #sample.name = "bell-window-system"
+ #x11.display = null
+ #x11.xauthority = null
+ }
+ flags = [ ifexists nofail ]
+ }
+]
+
+context.objects = [
+ #{ factory = <factory-name>
+ # [ args = { <key> = <value> ... } ]
+ # [ flags = [ [ nofail ] ]
+ #}
+ #
+ # 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 Spa:Pod:Object: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 } }
+ #{ 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-driver"
+ # property when no other driver is currently active. JACK clients need this.
+ { factory = spa-node-factory
+ args = {
+ factory.name = support.node.driver
+ node.name = Dummy-Driver
+ node.group = pipewire.dummy
+ priority.driver = 20000
+ }
+ }
+ { factory = spa-node-factory
+ args = {
+ factory.name = support.node.driver
+ node.name = Freewheel-Driver
+ priority.driver = 19000
+ node.group = pipewire.freewheel
+ node.freewheel = true
+ }
+ }
+ # This creates a new Source node. It will have input ports
+ # that you can link, to provide audio for this source.
+ #{ factory = adapter
+ # args = {
+ # factory.name = support.null-audio-sink
+ # node.name = "my-mic"
+ # node.description = "Microphone"
+ # media.class = "Audio/Source/Virtual"
+ # audio.position = "FL,FR"
+ # }
+ #}
+
+ # This creates a single PCM source device for the given
+ # alsa device path hw:0. You can change source to sink
+ # to make a sink in the same way.
+ #{ factory = adapter
+ # args = {
+ # factory.name = api.alsa.pcm.source
+ # node.name = "alsa-source"
+ # node.description = "PCM Source"
+ # media.class = "Audio/Source"
+ # api.alsa.path = "hw:0"
+ # api.alsa.period-size = 1024
+ # api.alsa.headroom = 0
+ # api.alsa.disable-mmap = false
+ # api.alsa.disable-batch = false
+ # audio.format = "S16LE"
+ # audio.rate = 48000
+ # audio.channels = 2
+ # audio.position = "FL,FR"
+ # }
+ #}
+]
+
+context.exec = [
+ #{ path = <program-name> [ args = "<arguments>" ] }
+ #
+ # Execute the given program with arguments.
+ #
+ # You can optionally start the session manager here,
+ # but it is better to start it as a systemd service.
+ # Run the session manager with -h for options.
+ #
+ @sm_comment@{ path = "@session_manager_path@" args = "@session_manager_args@" }
+ #
+ # You can optionally start the pulseaudio-server here as well
+ # but it is better to start it as a systemd service.
+ # It can be interesting to start another daemon here that listens
+ # on another address with the -a option (eg. -a tcp:4713).
+ #
+ @pulse_comment@{ path = "@pipewire_path@" args = "-c pipewire-pulse.conf" }
+]
diff --git a/src/daemon/pipewire.desktop.in b/src/daemon/pipewire.desktop.in
new file mode 100644
index 0000000..ebf0e30
--- /dev/null
+++ b/src/daemon/pipewire.desktop.in
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Version=1.0
+Name=PipeWire Media System
+Comment=Start the PipeWire Media System
+Exec=pipewire
+Terminal=false
+Type=Application
+X-GNOME-Autostart-Phase=Initialization
+X-KDE-autostart-phase=1
diff --git a/src/daemon/systemd/meson.build b/src/daemon/systemd/meson.build
new file mode 100644
index 0000000..482a44c
--- /dev/null
+++ b/src/daemon/systemd/meson.build
@@ -0,0 +1,6 @@
+if get_option('systemd-system-service').allowed()
+ subdir('system')
+endif
+if get_option('systemd-user-service').allowed()
+ subdir('user')
+endif
diff --git a/src/daemon/systemd/system/meson.build b/src/daemon/systemd/system/meson.build
new file mode 100644
index 0000000..84ca0b0
--- /dev/null
+++ b/src/daemon/systemd/system/meson.build
@@ -0,0 +1,15 @@
+systemd_system_services_dir = systemd.get_variable('systemdsystemunitdir', pkgconfig_define : [ 'rootprefix', prefix])
+if get_option('systemd-system-unit-dir') != ''
+ systemd_system_services_dir = get_option('systemd-system-unit-dir')
+endif
+
+install_data(sources : 'pipewire.socket',
+ install_dir : systemd_system_services_dir)
+
+systemd_config = configuration_data()
+systemd_config.set('PW_BINARY', pipewire_bindir / 'pipewire')
+
+configure_file(input : 'pipewire.service.in',
+ output : 'pipewire.service',
+ configuration : systemd_config,
+ install_dir : systemd_system_services_dir)
diff --git a/src/daemon/systemd/system/pipewire.service.in b/src/daemon/systemd/system/pipewire.service.in
new file mode 100644
index 0000000..8b75ba2
--- /dev/null
+++ b/src/daemon/systemd/system/pipewire.service.in
@@ -0,0 +1,35 @@
+[Unit]
+Description=PipeWire Multimedia Service
+
+# We require pipewire.socket to be active before starting the daemon, because
+# while it is possible to use the service without the socket, it is not clear
+# why it would be desirable.
+#
+# Installing pipewire and doing `systemctl start pipewire` will not get the
+# socket started, which might be confusing and problematic if the server is to
+# be restarted later on, as the client autospawn feature might kick in. Also, a
+# start of the socket unit will fail, adding to the confusion.
+#
+# After=pipewire.socket is not needed, as it is already implicit in the
+# socket-service relationship, see systemd.socket(5).
+Requires=pipewire.socket
+
+[Service]
+LockPersonality=yes
+MemoryDenyWriteExecute=yes
+NoNewPrivileges=yes
+RestrictNamespaces=yes
+SystemCallArchitectures=native
+SystemCallFilter=@system-service
+Type=simple
+AmbientCapabilities=CAP_SYS_NICE
+ExecStart=@PW_BINARY@
+Restart=on-failure
+RuntimeDirectory=pipewire
+RuntimeDirectoryPreserve=yes
+User=pipewire
+Environment=PIPEWIRE_RUNTIME_DIR=%t/pipewire
+
+[Install]
+Also=pipewire.socket
+WantedBy=default.target
diff --git a/src/daemon/systemd/system/pipewire.socket b/src/daemon/systemd/system/pipewire.socket
new file mode 100644
index 0000000..2e3cb71
--- /dev/null
+++ b/src/daemon/systemd/system/pipewire.socket
@@ -0,0 +1,12 @@
+[Unit]
+Description=PipeWire Multimedia System Socket
+
+[Socket]
+Priority=6
+ListenStream=%t/pipewire/pipewire-0
+SocketUser=pipewire
+SocketGroup=pipewire
+SocketMode=0660
+
+[Install]
+WantedBy=sockets.target
diff --git a/src/daemon/systemd/user/filter-chain.service.in b/src/daemon/systemd/user/filter-chain.service.in
new file mode 100644
index 0000000..542cbd7
--- /dev/null
+++ b/src/daemon/systemd/user/filter-chain.service.in
@@ -0,0 +1,21 @@
+[Unit]
+Description=PipeWire filter chain daemon
+
+After=pipewire.service pipewire-session-manager.service
+BindsTo=pipewire.service
+
+[Service]
+LockPersonality=yes
+MemoryDenyWriteExecute=yes
+NoNewPrivileges=yes
+RestrictNamespaces=yes
+SystemCallArchitectures=native
+SystemCallFilter=@system-service
+Type=simple
+ExecStart=@PW_BINARY@ -c filter-chain.conf
+Restart=on-failure
+Slice=session.slice
+
+[Install]
+Also=pipewire.socket
+WantedBy=default.target
diff --git a/src/daemon/systemd/user/meson.build b/src/daemon/systemd/user/meson.build
new file mode 100644
index 0000000..1022762
--- /dev/null
+++ b/src/daemon/systemd/user/meson.build
@@ -0,0 +1,27 @@
+systemd_user_services_dir = systemd.get_variable('systemduserunitdir', pkgconfig_define : [ 'prefix', prefix])
+if get_option('systemd-user-unit-dir') != ''
+ systemd_user_services_dir = get_option('systemd-user-unit-dir')
+endif
+
+install_data(
+ sources : ['pipewire.socket', 'pipewire-pulse.socket'],
+ install_dir : systemd_user_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_user_services_dir)
+
+configure_file(input : 'pipewire-pulse.service.in',
+ output : 'pipewire-pulse.service',
+ configuration : systemd_config,
+ install_dir : systemd_user_services_dir)
+
+configure_file(input : 'filter-chain.service.in',
+ output : 'filter-chain.service',
+ configuration : systemd_config,
+ install_dir : systemd_user_services_dir)
diff --git a/src/daemon/systemd/user/pipewire-pulse.service.in b/src/daemon/systemd/user/pipewire-pulse.service.in
new file mode 100644
index 0000000..73d22e5
--- /dev/null
+++ b/src/daemon/systemd/user/pipewire-pulse.service.in
@@ -0,0 +1,36 @@
+[Unit]
+Description=PipeWire PulseAudio
+
+# We require pipewire-pulse.socket to be active before starting the daemon, because
+# while it is possible to use the service without the socket, it is not clear
+# why it would be desirable.
+#
+# A user installing pipewire and doing `systemctl --user start pipewire-pulse`
+# will not get the socket started, which might be confusing and problematic if
+# the server is to be restarted later on, as the client autospawn feature
+# might kick in. Also, a start of the socket unit will fail, adding to the
+# confusion.
+#
+# After=pipewire-pulse.socket is not needed, as it is already implicit in the
+# socket-service relationship, see systemd.socket(5).
+Requires=pipewire-pulse.socket
+ConditionUser=!root
+Wants=pipewire.service pipewire-session-manager.service
+After=pipewire.service pipewire-session-manager.service
+Conflicts=pulseaudio.service
+
+[Service]
+LockPersonality=yes
+MemoryDenyWriteExecute=yes
+NoNewPrivileges=yes
+RestrictNamespaces=yes
+SystemCallArchitectures=native
+SystemCallFilter=@system-service
+Type=simple
+ExecStart=@PW_PULSE_BINARY@
+Restart=on-failure
+Slice=session.slice
+
+[Install]
+Also=pipewire-pulse.socket
+WantedBy=default.target
diff --git a/src/daemon/systemd/user/pipewire-pulse.socket b/src/daemon/systemd/user/pipewire-pulse.socket
new file mode 100644
index 0000000..1ae5eda
--- /dev/null
+++ b/src/daemon/systemd/user/pipewire-pulse.socket
@@ -0,0 +1,11 @@
+[Unit]
+Description=PipeWire PulseAudio
+ConditionUser=!root
+Conflicts=pulseaudio.socket
+
+[Socket]
+Priority=6
+ListenStream=%t/pulse/native
+
+[Install]
+WantedBy=sockets.target
diff --git a/src/daemon/systemd/user/pipewire.service.in b/src/daemon/systemd/user/pipewire.service.in
new file mode 100644
index 0000000..b9b1373
--- /dev/null
+++ b/src/daemon/systemd/user/pipewire.service.in
@@ -0,0 +1,32 @@
+[Unit]
+Description=PipeWire Multimedia Service
+
+# We require pipewire.socket to be active before starting the daemon, because
+# while it is possible to use the service without the socket, it is not clear
+# why it would be desirable.
+#
+# A user installing pipewire and doing `systemctl --user start pipewire`
+# will not get the socket started, which might be confusing and problematic if
+# the server is to be restarted later on, as the client autospawn feature
+# might kick in. Also, a start of the socket unit will fail, adding to the
+# confusion.
+#
+# After=pipewire.socket is not needed, as it is already implicit in the
+# socket-service relationship, see systemd.socket(5).
+Requires=pipewire.socket
+
+[Service]
+LockPersonality=yes
+MemoryDenyWriteExecute=yes
+NoNewPrivileges=yes
+RestrictNamespaces=yes
+SystemCallArchitectures=native
+SystemCallFilter=@system-service
+Type=simple
+ExecStart=@PW_BINARY@
+Restart=on-failure
+Slice=session.slice
+
+[Install]
+Also=pipewire.socket
+WantedBy=default.target
diff --git a/src/daemon/systemd/user/pipewire.socket b/src/daemon/systemd/user/pipewire.socket
new file mode 100644
index 0000000..232bbb8
--- /dev/null
+++ b/src/daemon/systemd/user/pipewire.socket
@@ -0,0 +1,9 @@
+[Unit]
+Description=PipeWire Multimedia System Socket
+
+[Socket]
+Priority=6
+ListenStream=%t/pipewire-0
+
+[Install]
+WantedBy=sockets.target
diff --git a/src/examples/audio-capture.c b/src/examples/audio-capture.c
new file mode 100644
index 0000000..4c1afbb
--- /dev/null
+++ b/src/examples/audio-capture.c
@@ -0,0 +1,209 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Audio capture using \ref pw_stream "pw_stream".
+ [title]
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <math.h>
+#include <signal.h>
+
+#include <spa/param/audio/format-utils.h>
+
+#include <pipewire/pipewire.h>
+
+struct data {
+ struct pw_main_loop *loop;
+ struct pw_stream *stream;
+
+ struct spa_audio_info format;
+ unsigned move:1;
+};
+
+/* our data processing function is in general:
+ *
+ * struct pw_buffer *b;
+ * b = pw_stream_dequeue_buffer(stream);
+ *
+ * .. consume stuff in the buffer ...
+ *
+ * pw_stream_queue_buffer(stream, b);
+ */
+static void on_process(void *userdata)
+{
+ struct data *data = userdata;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ float *samples, max;
+ uint32_t c, n, n_channels, n_samples, peak;
+
+ if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL) {
+ pw_log_warn("out of buffers: %m");
+ return;
+ }
+
+ buf = b->buffer;
+ if ((samples = buf->datas[0].data) == NULL)
+ return;
+
+ n_channels = data->format.info.raw.channels;
+ n_samples = buf->datas[0].chunk->size / sizeof(float);
+
+ /* move cursor up */
+ if (data->move)
+ fprintf(stdout, "%c[%dA", 0x1b, n_channels + 1);
+ fprintf(stdout, "captured %d samples\n", n_samples / n_channels);
+ for (c = 0; c < data->format.info.raw.channels; c++) {
+ max = 0.0f;
+ for (n = c; n < n_samples; n += n_channels)
+ max = fmaxf(max, fabsf(samples[n]));
+
+ peak = SPA_CLAMP(max * 30, 0, 39);
+
+ fprintf(stdout, "channel %d: |%*s%*s| peak:%f\n",
+ c, peak+1, "*", 40 - peak, "", max);
+ }
+ data->move = true;
+ fflush(stdout);
+
+ pw_stream_queue_buffer(data->stream, b);
+}
+
+/* Be notified when the stream param changes. We're only looking at the
+ * format changes.
+ */
+static void
+on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param)
+{
+ struct data *data = _data;
+
+ /* NULL means to clear the format */
+ if (param == NULL || id != SPA_PARAM_Format)
+ return;
+
+ if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0)
+ return;
+
+ /* only accept raw audio */
+ if (data->format.media_type != SPA_MEDIA_TYPE_audio ||
+ data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
+ return;
+
+ /* call a helper function to parse the format for us. */
+ spa_format_audio_raw_parse(param, &data->format.info.raw);
+
+ fprintf(stdout, "capturing rate:%d channels:%d\n",
+ data->format.info.raw.rate, data->format.info.raw.channels);
+
+}
+
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .param_changed = on_stream_param_changed,
+ .process = on_process,
+};
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_main_loop_quit(data->loop);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct pw_properties *props;
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+
+ pw_init(&argc, &argv);
+
+ /* make a main loop. If you already have another main loop, you can add
+ * the fd of this pipewire mainloop to it. */
+ data.loop = pw_main_loop_new(NULL);
+
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data);
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data);
+
+ /* Create a simple stream, the simple stream manages the core and remote
+ * objects for you if you don't need to deal with them.
+ *
+ * If you plan to autoconnect your stream, you need to provide at least
+ * media, category and role properties.
+ *
+ * Pass your events and a user_data pointer as the last arguments. This
+ * will inform you about the stream state. The most important event
+ * you need to listen to is the process event where you need to produce
+ * the data.
+ */
+ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio",
+ PW_KEY_MEDIA_CATEGORY, "Capture",
+ PW_KEY_MEDIA_ROLE, "Music",
+ NULL);
+ if (argc > 1)
+ /* Set stream target if given on command line */
+ pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]);
+
+ /* uncomment if you want to capture from the sink monitor ports */
+ /* pw_properties_set(props, PW_KEY_STREAM_CAPTURE_SINK, "true"); */
+
+ data.stream = pw_stream_new_simple(
+ pw_main_loop_get_loop(data.loop),
+ "audio-capture",
+ props,
+ &stream_events,
+ &data);
+
+ /* Make one parameter with the supported formats. The SPA_PARAM_EnumFormat
+ * id means that this is a format enumeration (of 1 value).
+ * We leave the channels and rate empty to accept the native graph
+ * rate and channels. */
+ params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
+ &SPA_AUDIO_INFO_RAW_INIT(
+ .format = SPA_AUDIO_FORMAT_F32));
+
+ /* Now connect this stream. We ask that our process function is
+ * called in a realtime thread. */
+ pw_stream_connect(data.stream,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, 1);
+
+ /* and wait while we let things run */
+ pw_main_loop_run(data.loop);
+
+ pw_stream_destroy(data.stream);
+ pw_main_loop_destroy(data.loop);
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/examples/audio-dsp-filter.c b/src/examples/audio-dsp-filter.c
new file mode 100644
index 0000000..fbcc226
--- /dev/null
+++ b/src/examples/audio-dsp-filter.c
@@ -0,0 +1,180 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Audio filter using \ref pw_filter "pw_filter".
+ [title]
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <math.h>
+#include <signal.h>
+
+#include <spa/pod/builder.h>
+#include <spa/param/latency-utils.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/filter.h>
+
+struct data;
+
+struct port {
+ struct data *data;
+};
+
+struct data {
+ struct pw_main_loop *loop;
+ struct pw_filter *filter;
+ struct port *in_port;
+ struct port *out_port;
+};
+
+/* our data processing function is in general:
+ *
+ * struct pw_buffer *b;
+ * in = pw_filter_dequeue_buffer(filter, in_port);
+ * out = pw_filter_dequeue_buffer(filter, out_port);
+ *
+ * .. do stuff with buffers ...
+ *
+ * pw_filter_queue_buffer(filter, in_port, in);
+ * pw_filter_queue_buffer(filter, out_port, out);
+ *
+ * For DSP ports, there is a shortcut to directly dequeue, get
+ * the data and requeue the buffer with pw_filter_get_dsp_buffer().
+ *
+ *
+ */
+static void on_process(void *userdata, struct spa_io_position *position)
+{
+ struct data *data = userdata;
+ float *in, *out;
+ uint32_t n_samples = position->clock.duration;
+
+ pw_log_trace("do process %d", n_samples);
+
+ in = pw_filter_get_dsp_buffer(data->in_port, n_samples);
+ out = pw_filter_get_dsp_buffer(data->out_port, n_samples);
+
+ if (in == NULL || out == NULL)
+ return;
+
+ memcpy(out, in, n_samples * sizeof(float));
+}
+
+static const struct pw_filter_events filter_events = {
+ PW_VERSION_FILTER_EVENTS,
+ .process = on_process,
+};
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_main_loop_quit(data->loop);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+
+ pw_init(&argc, &argv);
+
+ /* make a main loop. If you already have another main loop, you can add
+ * the fd of this pipewire mainloop to it. */
+ data.loop = pw_main_loop_new(NULL);
+
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data);
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data);
+
+ /* Create a simple filter, the simple filter manages the core and remote
+ * objects for you if you don't need to deal with them.
+ *
+ * Pass your events and a user_data pointer as the last arguments. This
+ * will inform you about the filter state. The most important event
+ * you need to listen to is the process event where you need to process
+ * the data.
+ */
+ data.filter = pw_filter_new_simple(
+ pw_main_loop_get_loop(data.loop),
+ "audio-filter",
+ pw_properties_new(
+ PW_KEY_MEDIA_TYPE, "Audio",
+ PW_KEY_MEDIA_CATEGORY, "Filter",
+ PW_KEY_MEDIA_ROLE, "DSP",
+ NULL),
+ &filter_events,
+ &data);
+
+ /* make an audio DSP input port */
+ data.in_port = pw_filter_add_port(data.filter,
+ PW_DIRECTION_INPUT,
+ PW_FILTER_PORT_FLAG_MAP_BUFFERS,
+ sizeof(struct port),
+ pw_properties_new(
+ PW_KEY_FORMAT_DSP, "32 bit float mono audio",
+ PW_KEY_PORT_NAME, "input",
+ NULL),
+ NULL, 0);
+
+ /* make an audio DSP output port */
+ data.out_port = pw_filter_add_port(data.filter,
+ PW_DIRECTION_OUTPUT,
+ PW_FILTER_PORT_FLAG_MAP_BUFFERS,
+ sizeof(struct port),
+ pw_properties_new(
+ PW_KEY_FORMAT_DSP, "32 bit float mono audio",
+ PW_KEY_PORT_NAME, "output",
+ NULL),
+ NULL, 0);
+
+ params[0] = spa_process_latency_build(&b,
+ SPA_PARAM_ProcessLatency,
+ &SPA_PROCESS_LATENCY_INFO_INIT(
+ .ns = 10 * SPA_NSEC_PER_MSEC
+ ));
+
+
+ /* Now connect this filter. We ask that our process function is
+ * called in a realtime thread. */
+ if (pw_filter_connect(data.filter,
+ PW_FILTER_FLAG_RT_PROCESS,
+ params, 1) < 0) {
+ fprintf(stderr, "can't connect\n");
+ return -1;
+ }
+
+ /* and wait while we let things run */
+ pw_main_loop_run(data.loop);
+
+ pw_filter_destroy(data.filter);
+ pw_main_loop_destroy(data.loop);
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/examples/audio-dsp-src.c b/src/examples/audio-dsp-src.c
new file mode 100644
index 0000000..d7bf523
--- /dev/null
+++ b/src/examples/audio-dsp-src.c
@@ -0,0 +1,165 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Audio source using \ref pw_filter "pw_filter"
+ [title]
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <math.h>
+#include <signal.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/filter.h>
+
+#define M_PI_M2 ( M_PI + M_PI )
+
+#define DEFAULT_RATE 44100
+#define DEFAULT_FREQ 440
+#define DEFAULT_VOLUME 0.7
+
+struct data;
+
+struct port {
+ struct data *data;
+ double accumulator;
+};
+
+struct data {
+ struct pw_main_loop *loop;
+ struct pw_filter *filter;
+ struct port *out_port;
+};
+
+/* our data processing function is in general:
+ *
+ * struct pw_buffer *b;
+ * out = pw_filter_dequeue_buffer(filter, out_port);
+ *
+ * .. generate data in the buffer ...
+ *
+ * pw_filter_queue_buffer(filter, out_port, out);
+ *
+ * For DSP ports, there is a shortcut to directly dequeue, get
+ * the data and requeue the buffer with pw_filter_get_dsp_buffer().
+ */
+static void on_process(void *userdata, struct spa_io_position *position)
+{
+ struct data *data = userdata;
+ float *out;
+ struct port *out_port = data->out_port;
+ uint32_t i, n_samples = position->clock.duration;
+
+ pw_log_trace("do process %d", n_samples);
+
+ out = pw_filter_get_dsp_buffer(out_port, n_samples);
+ if (out == NULL)
+ return;
+
+ for (i = 0; i < n_samples; i++) {
+ out_port->accumulator += M_PI_M2 * DEFAULT_FREQ / DEFAULT_RATE;
+ if (out_port->accumulator >= M_PI_M2)
+ out_port->accumulator -= M_PI_M2;
+
+ *out++ = sin(out_port->accumulator) * DEFAULT_VOLUME;
+ }
+}
+
+static const struct pw_filter_events filter_events = {
+ PW_VERSION_FILTER_EVENTS,
+ .process = on_process,
+};
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_main_loop_quit(data->loop);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+
+ pw_init(&argc, &argv);
+
+ /* make a main loop. If you already have another main loop, you can add
+ * the fd of this pipewire mainloop to it. */
+ data.loop = pw_main_loop_new(NULL);
+
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data);
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data);
+
+ /* Create a simple filter, the simple filter manages the core and remote
+ * objects for you if you don't need to deal with them.
+ *
+ * Pass your events and a user_data pointer as the last arguments. This
+ * will inform you about the filter state. The most important event
+ * you need to listen to is the process event where you need to process
+ * the data.
+ */
+ data.filter = pw_filter_new_simple(
+ pw_main_loop_get_loop(data.loop),
+ "audio-dsp-src",
+ pw_properties_new(
+ PW_KEY_MEDIA_TYPE, "Audio",
+ PW_KEY_MEDIA_CATEGORY, "Source",
+ PW_KEY_MEDIA_ROLE, "DSP",
+ PW_KEY_MEDIA_CLASS, "Stream/Output/Audio",
+ PW_KEY_NODE_AUTOCONNECT, "true",
+ NULL),
+ &filter_events,
+ &data);
+
+ /* make an audio DSP output port */
+ data.out_port = pw_filter_add_port(data.filter,
+ PW_DIRECTION_OUTPUT,
+ PW_FILTER_PORT_FLAG_MAP_BUFFERS,
+ sizeof(struct port),
+ pw_properties_new(
+ PW_KEY_FORMAT_DSP, "32 bit float mono audio",
+ PW_KEY_PORT_NAME, "output",
+ NULL),
+ NULL, 0);
+
+ /* Now connect this filter. We ask that our process function is
+ * called in a realtime thread. */
+ if (pw_filter_connect(data.filter,
+ PW_FILTER_FLAG_RT_PROCESS,
+ NULL, 0) < 0) {
+ fprintf(stderr, "can't connect\n");
+ return -1;
+ }
+
+ /* and wait while we let things run */
+ pw_main_loop_run(data.loop);
+
+ pw_filter_destroy(data.filter);
+ pw_main_loop_destroy(data.loop);
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/examples/audio-src.c b/src/examples/audio-src.c
new file mode 100644
index 0000000..1217732
--- /dev/null
+++ b/src/examples/audio-src.c
@@ -0,0 +1,186 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Audio source using \ref pw_stream "pw_stream".
+ [title]
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <math.h>
+#include <signal.h>
+
+#include <spa/param/audio/format-utils.h>
+
+#include <pipewire/pipewire.h>
+
+#define M_PI_M2 ( M_PI + M_PI )
+
+#define DEFAULT_RATE 44100
+#define DEFAULT_CHANNELS 2
+#define DEFAULT_VOLUME 0.7
+
+struct data {
+ struct pw_main_loop *loop;
+ struct pw_stream *stream;
+
+ double accumulator;
+};
+
+static void fill_f32(struct data *d, void *dest, int n_frames)
+{
+ float *dst = dest, val;
+ int i, c;
+
+ for (i = 0; i < n_frames; i++) {
+ d->accumulator += M_PI_M2 * 440 / DEFAULT_RATE;
+ if (d->accumulator >= M_PI_M2)
+ d->accumulator -= M_PI_M2;
+
+ val = sin(d->accumulator) * DEFAULT_VOLUME;
+ for (c = 0; c < DEFAULT_CHANNELS; c++)
+ *dst++ = val;
+ }
+}
+
+/* our data processing function is in general:
+ *
+ * struct pw_buffer *b;
+ * b = pw_stream_dequeue_buffer(stream);
+ *
+ * .. generate stuff in the buffer ...
+ *
+ * pw_stream_queue_buffer(stream, b);
+ */
+static void on_process(void *userdata)
+{
+ struct data *data = userdata;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ int n_frames, stride;
+ uint8_t *p;
+
+ 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;
+
+ stride = sizeof(float) * DEFAULT_CHANNELS;
+ n_frames = SPA_MIN(b->requested, buf->datas[0].maxsize / stride);
+
+ fill_f32(data, p, n_frames);
+
+ 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);
+}
+
+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->loop);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct pw_properties *props;
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+
+ pw_init(&argc, &argv);
+
+ /* make a main loop. If you already have another main loop, you can add
+ * the fd of this pipewire mainloop to it. */
+ data.loop = pw_main_loop_new(NULL);
+
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data);
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data);
+
+ /* Create a simple stream, the simple stream manages the core and remote
+ * objects for you if you don't need to deal with them.
+ *
+ * If you plan to autoconnect your stream, you need to provide at least
+ * media, category and role properties.
+ *
+ * Pass your events and a user_data pointer as the last arguments. This
+ * will inform you about the stream state. The most important event
+ * you need to listen to is the process event where you need to produce
+ * the data.
+ */
+ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio",
+ PW_KEY_MEDIA_CATEGORY, "Playback",
+ PW_KEY_MEDIA_ROLE, "Music",
+ NULL);
+ if (argc > 1)
+ /* Set stream target if given on command line */
+ pw_properties_set(props, PW_KEY_TARGET_OBJECT, argv[1]);
+ data.stream = pw_stream_new_simple(
+ pw_main_loop_get_loop(data.loop),
+ "audio-src",
+ 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.loop);
+
+ pw_stream_destroy(data.stream);
+ pw_main_loop_destroy(data.loop);
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/examples/bluez-session.c b/src/examples/bluez-session.c
new file mode 100644
index 0000000..7d7e94c
--- /dev/null
+++ b/src/examples/bluez-session.c
@@ -0,0 +1,400 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Using the \ref spa_device "SPA Device API", among other things.
+ [title]
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <math.h>
+#include <time.h>
+
+#include "config.h"
+
+#include <spa/monitor/device.h>
+#include <spa/node/node.h>
+#include <spa/utils/hook.h>
+#include <spa/utils/names.h>
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/props.h>
+#include <spa/debug/dict.h>
+
+#include "pipewire/pipewire.h"
+
+#define NAME "bluez-session"
+
+struct impl;
+struct object;
+
+struct node {
+ struct impl *impl;
+ struct object *object;
+ struct spa_list link;
+ uint32_t id;
+
+ struct spa_handle *handle;
+ struct pw_proxy *proxy;
+ struct spa_node *node;
+};
+
+struct object {
+ struct impl *impl;
+ struct spa_list link;
+ uint32_t id;
+
+ struct spa_handle *handle;
+ struct pw_proxy *proxy;
+ struct spa_device *device;
+ struct spa_hook listener;
+
+ struct spa_list node_list;
+};
+
+struct impl {
+ struct timespec now;
+
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ struct spa_handle *handle;
+ struct spa_device *device;
+ struct spa_hook listener;
+
+ struct spa_list device_list;
+};
+
+static struct node *find_node(struct object *obj, uint32_t id)
+{
+ struct node *node;
+
+ spa_list_for_each(node, &obj->node_list, link) {
+ if (node->id == id)
+ return node;
+ }
+ return NULL;
+}
+
+static void update_node(struct object *obj, struct node *node,
+ const struct spa_device_object_info *info)
+{
+ pw_log_debug("update node %u", node->id);
+ spa_debug_dict(0, info->props);
+}
+
+static struct node *create_node(struct object *obj, uint32_t id,
+ const struct spa_device_object_info *info)
+{
+ struct node *node;
+ struct impl *impl = obj->impl;
+ struct pw_context *context = impl->context;
+ struct spa_handle *handle;
+ int res;
+ void *iface;
+
+ pw_log_debug("new node %u", id);
+
+ if (!spa_streq(info->type, SPA_TYPE_INTERFACE_Node))
+ return NULL;
+
+ handle = pw_context_load_spa_handle(context,
+ info->factory_name,
+ info->props);
+ if (handle == NULL) {
+ pw_log_error("can't make factory instance: %m");
+ goto exit;
+ }
+
+ if ((res = spa_handle_get_interface(handle, info->type, &iface)) < 0) {
+ pw_log_error("can't get %s interface: %s", info->type, spa_strerror(res));
+ goto unload_handle;
+ }
+
+ node = calloc(1, sizeof(*node));
+ if (node == NULL)
+ goto unload_handle;
+
+ node->impl = impl;
+ node->object = obj;
+ node->id = id;
+ node->handle = handle;
+ node->node = iface;
+ node->proxy = pw_core_export(impl->core,
+ info->type, info->props, node->node, 0);
+ if (node->proxy == NULL)
+ goto clean_node;
+
+ spa_list_append(&obj->node_list, &node->link);
+
+ update_node(obj, node, info);
+
+ return node;
+
+clean_node:
+ free(node);
+unload_handle:
+ pw_unload_spa_handle(handle);
+exit:
+ return NULL;
+}
+
+static void remove_node(struct object *obj, struct node *node)
+{
+ pw_log_debug("remove node %u", node->id);
+ spa_list_remove(&node->link);
+ pw_proxy_destroy(node->proxy);
+ free(node->handle);
+ free(node);
+}
+
+static void device_object_info(void *data, uint32_t id,
+ const struct spa_device_object_info *info)
+{
+ struct object *obj = data;
+ struct node *node;
+
+ node = find_node(obj, id);
+
+ if (info == NULL) {
+ if (node == NULL) {
+ pw_log_warn("object %p: unknown node %u", obj, id);
+ return;
+ }
+ remove_node(obj, node);
+ } else if (node == NULL) {
+ create_node(obj, id, info);
+ } else {
+ update_node(obj, node, info);
+ }
+
+}
+
+static const struct spa_device_events device_events = {
+ SPA_VERSION_DEVICE_EVENTS,
+ .object_info = device_object_info
+};
+
+static struct object *find_object(struct impl *impl, uint32_t id)
+{
+ struct object *obj;
+
+ spa_list_for_each(obj, &impl->device_list, link) {
+ if (obj->id == id)
+ return obj;
+ }
+ return NULL;
+}
+
+static void update_object(struct impl *impl, struct object *obj,
+ const struct spa_device_object_info *info)
+{
+ pw_log_debug("update object %u", obj->id);
+ spa_debug_dict(0, info->props);
+}
+
+static struct object *create_object(struct impl *impl, uint32_t id,
+ const struct spa_device_object_info *info)
+{
+ struct pw_context *context = impl->context;
+ struct object *obj;
+ struct spa_handle *handle;
+ int res;
+ void *iface;
+
+ pw_log_debug("new object %u", id);
+
+ if (!spa_streq(info->type, SPA_TYPE_INTERFACE_Device))
+ return NULL;
+
+ handle = pw_context_load_spa_handle(context,
+ info->factory_name,
+ info->props);
+ if (handle == NULL) {
+ pw_log_error("can't make factory instance: %m");
+ goto exit;
+ }
+
+ if ((res = spa_handle_get_interface(handle, info->type, &iface)) < 0) {
+ pw_log_error("can't get %s interface: %s", info->type, spa_strerror(res));
+ goto unload_handle;
+ }
+
+ obj = calloc(1, sizeof(*obj));
+ if (obj == NULL)
+ goto unload_handle;
+
+ obj->impl = impl;
+ obj->id = id;
+ obj->handle = handle;
+ obj->device = iface;
+ obj->proxy = pw_core_export(impl->core,
+ info->type, info->props, obj->device, 0);
+ if (obj->proxy == NULL)
+ goto clean_object;
+
+ spa_list_init(&obj->node_list);
+
+ spa_device_add_listener(obj->device,
+ &obj->listener, &device_events, obj);
+
+ spa_list_append(&impl->device_list, &obj->link);
+
+ update_object(impl, obj, info);
+
+ return obj;
+
+clean_object:
+ free(obj);
+unload_handle:
+ pw_unload_spa_handle(handle);
+exit:
+ return NULL;
+}
+
+static void remove_object(struct impl *impl, struct object *obj)
+{
+ pw_log_debug("remove object %u", obj->id);
+ spa_list_remove(&obj->link);
+ spa_hook_remove(&obj->listener);
+ pw_proxy_destroy(obj->proxy);
+ free(obj->handle);
+ free(obj);
+}
+
+static void dbus_device_object_info(void *data, uint32_t id,
+ const struct spa_device_object_info *info)
+{
+ struct impl *impl = data;
+ struct object *obj;
+
+ obj = find_object(impl, id);
+
+ if (info == NULL) {
+ if (obj == NULL)
+ return;
+ remove_object(impl, obj);
+ } else if (obj == NULL) {
+ if (create_object(impl, id, info) == NULL)
+ return;
+ } else {
+ update_object(impl, obj, info);
+ }
+}
+
+static const struct spa_device_events dbus_device_events =
+{
+ SPA_VERSION_DEVICE_EVENTS,
+ .object_info = dbus_device_object_info,
+};
+
+static int start_monitor(struct impl *impl)
+{
+ struct spa_handle *handle;
+ int res;
+ void *iface;
+
+ handle = pw_context_load_spa_handle(impl->context, SPA_NAME_API_BLUEZ5_ENUM_DBUS, NULL);
+ if (handle == NULL) {
+ res = -errno;
+ goto out;
+ }
+
+ if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Device, &iface)) < 0) {
+ pw_log_error("can't get MONITOR interface: %d", res);
+ goto out_unload;
+ }
+
+ impl->handle = handle;
+ impl->device = iface;
+
+ spa_device_add_listener(impl->device, &impl->listener, &dbus_device_events, impl);
+
+ return 0;
+
+ out_unload:
+ pw_unload_spa_handle(handle);
+ out:
+ return res;
+}
+
+static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct impl *impl = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_main_loop_quit(impl->loop);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+};
+
+int main(int argc, char *argv[])
+{
+ struct impl impl = { 0, };
+ int res;
+
+ pw_init(&argc, &argv);
+
+ impl.loop = pw_main_loop_new(NULL);
+ impl.context = pw_context_new(pw_main_loop_get_loop(impl.loop), NULL, 0);
+
+ clock_gettime(CLOCK_MONOTONIC, &impl.now);
+
+ spa_list_init(&impl.device_list);
+
+ impl.core = pw_context_connect(impl.context, NULL, 0);
+ if (impl.core == NULL) {
+ pw_log_error(NAME" %p: can't connect %m", &impl);
+ return -1;
+ }
+
+ pw_core_add_listener(impl.core,
+ &impl.core_listener,
+ &core_events, &impl);
+
+ if ((res = start_monitor(&impl)) < 0) {
+ pw_log_error(NAME" %p: error starting monitor: %s", &impl, spa_strerror(res));
+ return -1;
+ }
+
+ pw_main_loop_run(impl.loop);
+
+ pw_context_destroy(impl.context);
+ pw_main_loop_destroy(impl.loop);
+
+ return 0;
+}
diff --git a/src/examples/export-sink.c b/src/examples/export-sink.c
new file mode 100644
index 0000000..ee8a57c
--- /dev/null
+++ b/src/examples/export-sink.c
@@ -0,0 +1,584 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Exporting and implementing a video sink SPA node, using \ref api_pw_core.
+ [title]
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <sys/mman.h>
+
+#include <spa/utils/result.h>
+#include <spa/param/video/format-utils.h>
+#include <spa/param/props.h>
+#include <spa/node/utils.h>
+#include <spa/node/io.h>
+#include <spa/pod/filter.h>
+#include <spa/debug/format.h>
+#include <spa/debug/pod.h>
+
+#include <pipewire/pipewire.h>
+
+#define WIDTH 640
+#define HEIGHT 480
+#define BPP 3
+
+#include "sdl.h"
+
+#define M_PI_M2 ( M_PI + M_PI )
+
+#define MAX_BUFFERS 64
+
+#define DEFAULT_PARAM 0.1
+
+struct props {
+ double param;
+};
+
+static void reset_props(struct props *props)
+{
+ props->param = DEFAULT_PARAM;
+}
+
+struct data {
+ struct props props;
+
+ const char *path;
+
+ SDL_Renderer *renderer;
+ SDL_Window *window;
+ SDL_Texture *texture;
+
+ struct pw_main_loop *loop;
+
+ struct pw_context *context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ struct spa_node impl_node;
+ struct spa_hook_list hooks;
+ struct spa_io_buffers *io;
+ struct spa_io_sequence *io_notify;
+ uint32_t io_notify_size;
+ double param_accum;
+
+ uint8_t buffer[1024];
+
+ struct spa_video_info_raw format;
+ int32_t stride;
+
+ struct spa_port_info info;
+ struct spa_param_info params[5];
+
+ struct spa_region region;
+
+ struct spa_buffer *buffers[MAX_BUFFERS];
+ uint32_t n_buffers;
+};
+
+static void handle_events(struct data *data)
+{
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_QUIT:
+ pw_main_loop_quit(data->loop);
+ break;
+ }
+ }
+}
+
+static void update_param(struct data *data)
+{
+ struct spa_pod_builder b = { 0, };
+ struct spa_pod_frame f[2];
+
+ if (data->io_notify == NULL)
+ return;
+
+ spa_pod_builder_init(&b, data->io_notify, data->io_notify_size);
+ spa_pod_builder_push_sequence(&b, &f[0], 0);
+ spa_pod_builder_control(&b, 0, SPA_CONTROL_Properties);
+ spa_pod_builder_push_object(&b, &f[1], SPA_TYPE_OBJECT_Props, 0);
+ spa_pod_builder_prop(&b, SPA_PROP_contrast, 0);
+ spa_pod_builder_float(&b, (sin(data->param_accum) * 127.0) + 127.0);
+ spa_pod_builder_pop(&b, &f[1]);
+ spa_pod_builder_pop(&b, &f[0]);
+
+ data->param_accum += M_PI_M2 / 30.0;
+ if (data->param_accum >= M_PI_M2)
+ data->param_accum -= M_PI_M2;
+}
+
+static int impl_send_command(void *object, const struct spa_command *command)
+{
+ return 0;
+}
+
+static int impl_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct spa_node_events *events,
+ void *data)
+{
+ struct data *d = object;
+ struct spa_hook_list save;
+ uint64_t old;
+
+ spa_hook_list_isolate(&d->hooks, &save, listener, events, data);
+
+ old = d->info.change_mask;
+ d->info.change_mask = SPA_PORT_CHANGE_MASK_FLAGS |
+ SPA_PORT_CHANGE_MASK_PARAMS;
+ spa_node_emit_port_info(&d->hooks, SPA_DIRECTION_INPUT, 0, &d->info);
+ d->info.change_mask = old;
+
+ spa_hook_list_join(&d->hooks, &save);
+
+ return 0;
+}
+
+static int impl_set_callbacks(void *object,
+ const struct spa_node_callbacks *callbacks, void *data)
+{
+ return 0;
+}
+
+static int impl_set_io(void *object,
+ uint32_t id, void *data, size_t size)
+{
+ return 0;
+}
+
+static int impl_port_set_io(void *object,
+ enum spa_direction direction, uint32_t port_id,
+ uint32_t id, void *data, size_t size)
+{
+ struct data *d = object;
+
+ switch (id) {
+ case SPA_IO_Buffers:
+ d->io = data;
+ break;
+ case SPA_IO_Notify:
+ d->io_notify = data;
+ d->io_notify_size = size;
+ break;
+ default:
+ return -ENOENT;
+ }
+ return 0;
+}
+
+static int impl_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 data *d = 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;
+
+ 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:
+ {
+ SDL_RendererInfo info;
+
+ if (result.index != 0)
+ return 0;
+
+ SDL_GetRendererInfo(d->renderer, &info);
+ param = sdl_build_formats(&info, &b);
+ break;
+ }
+ case SPA_PARAM_Format:
+ if (result.index != 0 || d->format.format == 0)
+ return 0;
+ param = spa_format_video_raw_build(&b, id, &d->format);
+ break;
+
+ case SPA_PARAM_Buffers:
+ if (result.index != 0)
+ return 0;
+
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamBuffers, id,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 2, MAX_BUFFERS),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(d->stride * d->format.size.height),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(d->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;
+ case 1:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, id,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region)));
+ break;
+ default:
+ return 0;
+ }
+ break;
+
+ case SPA_PARAM_IO:
+ switch (result.index) {
+ case 0:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamIO, id,
+ SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers),
+ SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers)));
+ break;
+ case 1:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamIO, id,
+ SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Notify),
+ SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_sequence) + 1024));
+ break;
+ default:
+ return 0;
+ }
+ break;
+ default:
+ return -ENOENT;
+ }
+
+ if (spa_pod_filter(&b, &result.param, param, filter) < 0)
+ goto next;
+
+ spa_node_emit_result(&d->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+
+ if (++count != num)
+ goto next;
+
+ 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 data *d = object;
+ Uint32 sdl_format;
+ void *dest;
+
+ if (format == NULL) {
+ spa_zero(d->format);
+ SDL_DestroyTexture(d->texture);
+ d->texture = NULL;
+ } else {
+ spa_debug_format(0, NULL, format);
+
+ spa_format_video_raw_parse(format, &d->format);
+
+ sdl_format = id_to_sdl_format(d->format.format);
+ if (sdl_format == SDL_PIXELFORMAT_UNKNOWN)
+ return -EINVAL;
+ if (d->format.size.width == 0 ||
+ d->format.size.height == 0)
+ return -EINVAL;
+
+ d->texture = SDL_CreateTexture(d->renderer,
+ sdl_format,
+ SDL_TEXTUREACCESS_STREAMING,
+ d->format.size.width,
+ d->format.size.height);
+ SDL_LockTexture(d->texture, NULL, &dest, &d->stride);
+ SDL_UnlockTexture(d->texture);
+
+ }
+ d->info.change_mask = SPA_PORT_CHANGE_MASK_PARAMS;
+ if (format) {
+ d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE);
+ d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ);
+ } else {
+ d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
+ d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
+ }
+
+ spa_node_emit_port_info(&d->hooks, direction, port_id, &d->info);
+ d->info.change_mask = 0;
+
+ return 0;
+}
+
+static int impl_port_set_param(void *object,
+ enum spa_direction direction, uint32_t port_id,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ if (id == SPA_PARAM_Format) {
+ return port_set_format(object, direction, port_id, flags, param);
+ }
+ else
+ return -ENOENT;
+}
+
+static int impl_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 data *d = object;
+ uint32_t i;
+
+ if (n_buffers > MAX_BUFFERS)
+ return -ENOSPC;
+
+ for (i = 0; i < n_buffers; i++)
+ d->buffers[i] = buffers[i];
+ d->n_buffers = n_buffers;
+ return 0;
+}
+
+static int do_render(struct spa_loop *loop, bool async, uint32_t seq,
+ const void *_data, size_t size, void *user_data)
+{
+ struct data *d = user_data;
+ const struct spa_buffer *buf = *(struct spa_buffer**)_data;
+ uint8_t *map;
+ void *sdata, *ddata;
+ int sstride, dstride, ostride;
+ uint32_t i;
+ uint8_t *src, *dst;
+ struct spa_meta *m;
+ struct spa_meta_region *r;
+
+ handle_events(d);
+
+ if (buf->datas[0].type == SPA_DATA_MemFd ||
+ buf->datas[0].type == SPA_DATA_DmaBuf) {
+ map = mmap(NULL, buf->datas[0].maxsize + buf->datas[0].mapoffset, PROT_READ,
+ MAP_PRIVATE, buf->datas[0].fd, 0);
+ sdata = SPA_PTROFF(map, buf->datas[0].mapoffset, uint8_t);
+ } else if (buf->datas[0].type == SPA_DATA_MemPtr) {
+ map = NULL;
+ sdata = buf->datas[0].data;
+ } else
+ return -EINVAL;
+
+ if (SDL_LockTexture(d->texture, NULL, &ddata, &dstride) < 0) {
+ fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError());
+ return -EIO;
+ }
+
+ if ((m = spa_buffer_find_meta(buf, SPA_META_VideoDamage))) {
+ spa_meta_for_each(r, m) {
+ if (!spa_meta_region_is_valid(r))
+ break;
+ if (memcmp(&r->region, &d->region, sizeof(struct spa_region)) == 0)
+ break;
+ d->region = r->region;
+ fprintf(stderr, "region %dx%d->%dx%d\n",
+ r->region.position.x, r->region.position.y,
+ r->region.size.width, r->region.size.height);
+ }
+ }
+
+ sstride = buf->datas[0].chunk->stride;
+ ostride = SPA_MIN(sstride, dstride);
+
+ src = sdata;
+ dst = ddata;
+ for (i = 0; i < d->format.size.height; i++) {
+ memcpy(dst, src, ostride);
+ src += sstride;
+ dst += dstride;
+ }
+ SDL_UnlockTexture(d->texture);
+
+ SDL_RenderClear(d->renderer);
+ SDL_RenderCopy(d->renderer, d->texture, NULL, NULL);
+ SDL_RenderPresent(d->renderer);
+
+ if (map)
+ munmap(map, buf->datas[0].maxsize + buf->datas[0].mapoffset);
+
+ return 0;
+}
+
+static int impl_node_process(void *object)
+{
+ struct data *d = object;
+ struct spa_buffer *buf;
+ int res;
+
+ if (d->io->status != SPA_STATUS_HAVE_DATA)
+ return SPA_STATUS_NEED_DATA;
+
+ if (d->io->buffer_id >= d->n_buffers)
+ return SPA_STATUS_NEED_DATA;
+
+ buf = d->buffers[d->io->buffer_id];
+
+ if ((res = pw_loop_invoke(pw_main_loop_get_loop(d->loop), do_render,
+ SPA_ID_INVALID, &buf, sizeof(struct spa_buffer *),
+ false, d)) < 0)
+ return res;
+
+ update_param(d);
+
+ return d->io->status = SPA_STATUS_NEED_DATA;
+}
+
+static const struct spa_node_methods impl_node = {
+ SPA_VERSION_NODE_METHODS,
+ .add_listener = impl_add_listener,
+ .set_callbacks = impl_set_callbacks,
+ .set_io = impl_set_io,
+ .send_command = impl_send_command,
+ .port_set_io = impl_port_set_io,
+ .port_enum_params = impl_port_enum_params,
+ .port_set_param = impl_port_set_param,
+ .port_use_buffers = impl_port_use_buffers,
+ .process = impl_node_process,
+};
+
+static void make_node(struct data *data)
+{
+ struct pw_properties *props;
+
+ props = pw_properties_new(PW_KEY_NODE_AUTOCONNECT, "true", NULL);
+ if (data->path)
+ pw_properties_set(props, PW_KEY_TARGET_OBJECT, data->path);
+ pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Stream/Input/Video");
+ pw_properties_set(props, PW_KEY_MEDIA_TYPE, "Video");
+ pw_properties_set(props, PW_KEY_MEDIA_CATEGORY, "Capture");
+ pw_properties_set(props, PW_KEY_MEDIA_ROLE, "Camera");
+
+ data->impl_node.iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_Node,
+ SPA_VERSION_NODE,
+ &impl_node, data);
+ pw_core_export(data->core, SPA_TYPE_INTERFACE_Node,
+ &props->dict, &data->impl_node, 0);
+ pw_properties_free(props);
+}
+
+static void set_permissions(struct data *data)
+{
+ struct pw_permission permissions[2];
+
+ /* an example, set specific permissions on one object, this is the
+ * core object. */
+ permissions[0] = PW_PERMISSION_INIT(PW_ID_CORE, PW_PERM_R | PW_PERM_X);
+ /* remove WX from all other objects */
+ permissions[1] = PW_PERMISSION_INIT(PW_ID_ANY, PW_PERM_R);
+
+ pw_client_update_permissions(
+ pw_core_get_client(data->core),
+ 2, permissions);
+}
+
+static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct data *d = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE)
+ pw_main_loop_quit(d->loop);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+};
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+
+ pw_init(&argc, &argv);
+
+ data.loop = pw_main_loop_new(NULL);
+ data.context = pw_context_new(pw_main_loop_get_loop(data.loop), NULL, 0);
+ data.path = argc > 1 ? argv[1] : NULL;
+
+ spa_hook_list_init(&data.hooks);
+
+ data.info = SPA_PORT_INFO_INIT();
+ data.info.change_mask = SPA_PORT_CHANGE_MASK_FLAGS;
+ data.info.flags = 0;
+ data.info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
+ data.params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ);
+ data.params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ);
+ data.params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ);
+ data.params[3] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ);
+ data.params[4] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
+ data.info.params = data.params;
+ data.info.n_params = 5;
+
+ reset_props(&data.props);
+
+ 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;
+ }
+
+ data.core = pw_context_connect(data.context, NULL, 0);
+ if (data.core == NULL) {
+ printf("can't connect: %m\n");
+ return -1;
+ }
+ pw_core_add_listener(data.core, &data.core_listener, &core_events, &data);
+
+ set_permissions(&data);
+
+ make_node(&data);
+
+ pw_main_loop_run(data.loop);
+
+ pw_context_destroy(data.context);
+ pw_main_loop_destroy(data.loop);
+
+ return 0;
+}
diff --git a/src/examples/export-source.c b/src/examples/export-source.c
new file mode 100644
index 0000000..eb8fdb5
--- /dev/null
+++ b/src/examples/export-source.c
@@ -0,0 +1,566 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Exporting and implementing a video source SPA node, using \ref api_pw_core.
+ [title]
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <math.h>
+#include <sys/mman.h>
+
+#include <spa/utils/result.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/props.h>
+#include <spa/node/io.h>
+#include <spa/node/utils.h>
+#include <spa/pod/filter.h>
+#include <spa/debug/format.h>
+
+#include <pipewire/pipewire.h>
+
+#define M_PI_M2 ( M_PI + M_PI )
+
+#define BUFFER_SAMPLES 128
+#define MAX_BUFFERS 32
+
+struct buffer {
+ uint32_t id;
+ struct spa_buffer *buffer;
+ struct spa_list link;
+ void *ptr;
+ bool mapped;
+};
+
+struct data {
+ const char *path;
+
+ struct pw_main_loop *loop;
+
+ struct pw_context *context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ uint64_t info_all;
+ struct spa_port_info info;
+ struct spa_dict_item items[1];
+ struct spa_dict dict;
+ struct spa_param_info params[5];
+
+ struct spa_node impl_node;
+ struct spa_hook_list hooks;
+ struct spa_io_buffers *io;
+ struct spa_io_control *io_notify;
+ uint32_t io_notify_size;
+
+ struct spa_audio_info_raw format;
+
+ struct buffer buffers[MAX_BUFFERS];
+ uint32_t n_buffers;
+ struct spa_list empty;
+
+ double accumulator;
+ double volume_accum;
+};
+
+static void update_volume(struct data *data)
+{
+ struct spa_pod_builder b = { 0, };
+ struct spa_pod_frame f[2];
+
+ if (data->io_notify == NULL)
+ return;
+
+ spa_pod_builder_init(&b, data->io_notify, data->io_notify_size);
+ spa_pod_builder_push_sequence(&b, &f[0], 0);
+ spa_pod_builder_control(&b, 0, SPA_CONTROL_Properties);
+ spa_pod_builder_push_object(&b, &f[1], SPA_TYPE_OBJECT_Props, 0);
+ spa_pod_builder_prop(&b, SPA_PROP_volume, 0);
+ spa_pod_builder_float(&b, (sin(data->volume_accum) / 2.0) + 0.5);
+ spa_pod_builder_pop(&b, &f[1]);
+ spa_pod_builder_pop(&b, &f[0]);
+
+ data->volume_accum += M_PI_M2 / 1000.0;
+ if (data->volume_accum >= M_PI_M2)
+ data->volume_accum -= M_PI_M2;
+}
+
+static int impl_send_command(void *object, const struct spa_command *command)
+{
+ return 0;
+}
+
+static int impl_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct spa_node_events *events,
+ void *data)
+{
+ struct data *d = object;
+ struct spa_hook_list save;
+ uint64_t old;
+
+ spa_hook_list_isolate(&d->hooks, &save, listener, events, data);
+
+ old = d->info.change_mask;
+ d->info.change_mask = d->info_all;
+ spa_node_emit_port_info(&d->hooks, SPA_DIRECTION_OUTPUT, 0, &d->info);
+ d->info.change_mask = old;
+
+ spa_hook_list_join(&d->hooks, &save);
+ return 0;
+}
+
+static int impl_set_callbacks(void *object,
+ const struct spa_node_callbacks *callbacks, void *data)
+{
+ return 0;
+}
+
+static int impl_set_io(void *object,
+ uint32_t id, void *data, size_t size)
+{
+ return 0;
+}
+
+static int impl_port_set_io(void *object, enum spa_direction direction, uint32_t port_id,
+ uint32_t id, void *data, size_t size)
+{
+ struct data *d = object;
+
+ switch (id) {
+ case SPA_IO_Buffers:
+ d->io = data;
+ break;
+ case SPA_IO_Notify:
+ d->io_notify = data;
+ d->io_notify_size = size;
+ break;
+ default:
+ return -ENOENT;
+ }
+ return 0;
+}
+
+static int impl_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 data *d = 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;
+
+ 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 (result.index != 0)
+ return 0;
+
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
+ SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
+ SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(5,
+ SPA_AUDIO_FORMAT_S16,
+ SPA_AUDIO_FORMAT_S16P,
+ SPA_AUDIO_FORMAT_S16,
+ SPA_AUDIO_FORMAT_F32P,
+ SPA_AUDIO_FORMAT_F32),
+ SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, INT32_MAX),
+ SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_RANGE_Int(44100, 1, INT32_MAX));
+ break;
+
+ case SPA_PARAM_Format:
+ if (result.index != 0)
+ return 0;
+ if (d->format.format == 0)
+ return 0;
+ param = spa_format_audio_raw_build(&b, id, &d->format);
+ break;
+
+ case SPA_PARAM_Buffers:
+ if (result.index > 0)
+ return 0;
+
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamBuffers, id,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(1, 1, 32),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
+ SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(
+ BUFFER_SAMPLES * sizeof(float), 32, INT32_MAX),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(sizeof(float)));
+ break;
+
+ case SPA_PARAM_Meta:
+ switch (result.index) {
+ case 0:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, id,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)));
+ break;
+ default:
+ return 0;
+ }
+ break;
+ case SPA_PARAM_IO:
+ switch (result.index) {
+ case 0:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamIO, id,
+ SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers),
+ SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers)));
+ break;
+ case 1:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamIO, id,
+ SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Notify),
+ SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_sequence) + 1024));
+ break;
+ default:
+ return 0;
+ }
+ break;
+ default:
+ return -ENOENT;
+ }
+
+ if (spa_pod_filter(&b, &result.param, param, filter) < 0)
+ goto next;
+
+ spa_node_emit_result(&d->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+
+ if (++count != num)
+ goto next;
+
+ 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 data *d = object;
+
+ if (format == NULL) {
+ spa_zero(d->format);
+ } else {
+ spa_debug_format(0, NULL, format);
+
+ if (spa_format_audio_raw_parse(format, &d->format) < 0)
+ return -EINVAL;
+
+ if (d->format.format != SPA_AUDIO_FORMAT_S16 &&
+ d->format.format != SPA_AUDIO_FORMAT_F32)
+ return -EINVAL;
+ if (d->format.rate == 0 ||
+ d->format.channels == 0 ||
+ d->format.channels > SPA_AUDIO_MAX_CHANNELS)
+ return -EINVAL;
+ }
+
+ d->info.change_mask = SPA_PORT_CHANGE_MASK_PARAMS;
+ if (format) {
+ d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE);
+ d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ);
+ } else {
+ d->params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
+ d->params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
+ }
+ spa_node_emit_port_info(&d->hooks, SPA_DIRECTION_OUTPUT, 0, &d->info);
+
+ return 0;
+}
+
+static int impl_port_set_param(void *object,
+ enum spa_direction direction, uint32_t port_id,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ if (id == SPA_PARAM_Format) {
+ return port_set_format(object, direction, port_id, flags, param);
+ }
+ else
+ return -ENOENT;
+}
+
+static int impl_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 data *d = object;
+ uint32_t i;
+
+ if (n_buffers > MAX_BUFFERS)
+ return -ENOSPC;
+
+ for (i = 0; i < n_buffers; i++) {
+ struct buffer *b = &d->buffers[i];
+ struct spa_data *datas = buffers[i]->datas;
+
+ if (datas[0].data != NULL) {
+ b->ptr = datas[0].data;
+ b->mapped = false;
+ }
+ else if (datas[0].type == SPA_DATA_MemFd ||
+ datas[0].type == SPA_DATA_DmaBuf) {
+ b->ptr = mmap(NULL, datas[0].maxsize + datas[0].mapoffset, PROT_WRITE,
+ MAP_SHARED, datas[0].fd, 0);
+ if (b->ptr == MAP_FAILED) {
+ pw_log_error("failed to buffer mem");
+ return -errno;
+
+ }
+ b->ptr = SPA_PTROFF(b->ptr, datas[0].mapoffset, void);
+ b->mapped = true;
+ }
+ else {
+ pw_log_error("invalid buffer mem");
+ return -EINVAL;
+ }
+ b->id = i;
+ b->buffer = buffers[i];
+ pw_log_debug("got buffer %d size %d", i, datas[0].maxsize);
+ spa_list_append(&d->empty, &b->link);
+ }
+ d->n_buffers = n_buffers;
+ return 0;
+}
+
+static inline void reuse_buffer(struct data *d, uint32_t id)
+{
+ pw_log_trace("export-source %p: recycle buffer %d", d, id);
+ spa_list_append(&d->empty, &d->buffers[id].link);
+}
+
+static int impl_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id)
+{
+ struct data *d = object;
+ reuse_buffer(d, buffer_id);
+ return 0;
+}
+
+static void fill_f32(struct data *d, void *dest, int avail)
+{
+ float *dst = dest;
+ int n_samples = avail / (sizeof(float) * d->format.channels);
+ int i;
+ uint32_t c;
+
+ for (i = 0; i < n_samples; i++) {
+ float val;
+
+ d->accumulator += M_PI_M2 * 440 / d->format.rate;
+ if (d->accumulator >= M_PI_M2)
+ d->accumulator -= M_PI_M2;
+
+ val = sin(d->accumulator);
+
+ for (c = 0; c < d->format.channels; c++)
+ *dst++ = val;
+ }
+}
+
+static void fill_s16(struct data *d, void *dest, int avail)
+{
+ int16_t *dst = dest;
+ int n_samples = avail / (sizeof(int16_t) * d->format.channels);
+ int i;
+ uint32_t c;
+
+ for (i = 0; i < n_samples; i++) {
+ int16_t val;
+
+ d->accumulator += M_PI_M2 * 440 / d->format.rate;
+ if (d->accumulator >= M_PI_M2)
+ d->accumulator -= M_PI_M2;
+
+ val = (int16_t) (sin(d->accumulator) * 32767.0);
+
+ for (c = 0; c < d->format.channels; c++)
+ *dst++ = val;
+ }
+}
+
+static int impl_node_process(void *object)
+{
+ struct data *d = object;
+ struct buffer *b;
+ int avail;
+ struct spa_io_buffers *io = d->io;
+ uint32_t maxsize, index = 0;
+ uint32_t filled, offset;
+ struct spa_data *od;
+
+ if (io->buffer_id < d->n_buffers) {
+ reuse_buffer(d, io->buffer_id);
+ io->buffer_id = SPA_ID_INVALID;
+ }
+ if (spa_list_is_empty(&d->empty)) {
+ pw_log_error("export-source %p: out of buffers", d);
+ return -EPIPE;
+ }
+ b = spa_list_first(&d->empty, struct buffer, link);
+ spa_list_remove(&b->link);
+
+ od = b->buffer->datas;
+
+ maxsize = od[0].maxsize;
+
+ filled = 0;
+ index = 0;
+ avail = maxsize - filled;
+ offset = index % maxsize;
+
+ if (offset + avail > maxsize)
+ avail = maxsize - offset;
+
+ if (d->format.format == SPA_AUDIO_FORMAT_S16)
+ fill_s16(d, SPA_PTROFF(b->ptr, offset, void), avail);
+ else if (d->format.format == SPA_AUDIO_FORMAT_F32)
+ fill_f32(d, SPA_PTROFF(b->ptr, offset, void), avail);
+
+ od[0].chunk->offset = 0;
+ od[0].chunk->size = avail;
+ od[0].chunk->stride = 0;
+
+ io->buffer_id = b->id;
+ io->status = SPA_STATUS_HAVE_DATA;
+
+ update_volume(d);
+
+ return SPA_STATUS_HAVE_DATA;
+}
+
+static const struct spa_node_methods impl_node = {
+ SPA_VERSION_NODE_METHODS,
+ .add_listener = impl_add_listener,
+ .set_callbacks = impl_set_callbacks,
+ .set_io = impl_set_io,
+ .send_command = impl_send_command,
+ .port_set_io = impl_port_set_io,
+ .port_enum_params = impl_port_enum_params,
+ .port_set_param = impl_port_set_param,
+ .port_use_buffers = impl_port_use_buffers,
+ .port_reuse_buffer = impl_port_reuse_buffer,
+ .process = impl_node_process,
+};
+
+static void make_node(struct data *data)
+{
+ struct pw_properties *props;
+
+ props = pw_properties_new(PW_KEY_NODE_AUTOCONNECT, "true",
+ PW_KEY_NODE_EXCLUSIVE, "true",
+ PW_KEY_MEDIA_TYPE, "Audio",
+ PW_KEY_MEDIA_CATEGORY, "Playback",
+ PW_KEY_MEDIA_ROLE, "Music",
+ NULL);
+ if (data->path)
+ pw_properties_set(props, PW_KEY_TARGET_OBJECT, data->path);
+
+ data->impl_node.iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_Node,
+ SPA_VERSION_NODE,
+ &impl_node, data);
+ pw_core_export(data->core, SPA_TYPE_INTERFACE_Node,
+ &props->dict, &data->impl_node, 0);
+ pw_properties_free(props);
+}
+
+static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct data *d = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE)
+ pw_main_loop_quit(d->loop);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+};
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+
+ pw_init(&argc, &argv);
+
+ data.loop = pw_main_loop_new(NULL);
+ data.context = pw_context_new(pw_main_loop_get_loop(data.loop), NULL, 0);
+ data.path = argc > 1 ? argv[1] : NULL;
+
+ data.info_all = SPA_PORT_CHANGE_MASK_FLAGS |
+ SPA_PORT_CHANGE_MASK_PROPS |
+ SPA_PORT_CHANGE_MASK_PARAMS;
+ data.info = SPA_PORT_INFO_INIT();
+ data.info.flags = 0;
+ data.items[0] = SPA_DICT_ITEM_INIT(PW_KEY_FORMAT_DSP, "32 bit float mono audio");
+ data.dict = SPA_DICT_INIT_ARRAY(data.items);
+ data.info.props = &data.dict;
+ data.params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ);
+ data.params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ);
+ data.params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ);
+ data.params[3] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
+ data.params[4] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
+ data.info.params = data.params;
+ data.info.n_params = 5;
+
+ spa_list_init(&data.empty);
+ spa_hook_list_init(&data.hooks);
+
+ if ((data.core = pw_context_connect(data.context, NULL, 0)) == NULL) {
+ printf("can't connect: %m\n");
+ return -1;
+ }
+
+ pw_core_add_listener(data.core, &data.core_listener, &core_events, &data);
+
+ make_node(&data);
+
+ pw_main_loop_run(data.loop);
+
+ pw_context_destroy(data.context);
+ pw_main_loop_destroy(data.loop);
+
+ return 0;
+}
diff --git a/src/examples/export-spa-device.c b/src/examples/export-spa-device.c
new file mode 100644
index 0000000..4629a02
--- /dev/null
+++ b/src/examples/export-spa-device.c
@@ -0,0 +1,144 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Exporting and loading a SPA device, using \ref api_pw_core.
+ [title]
+ */
+
+#include <stdio.h>
+#include <sys/mman.h>
+#include <signal.h>
+
+#include <spa/utils/result.h>
+#include <spa/param/video/format-utils.h>
+#include <spa/param/props.h>
+
+#include <pipewire/impl.h>
+
+struct data {
+ struct pw_main_loop *loop;
+
+ struct pw_context *context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ struct pw_impl_device *device;
+ const char *library;
+ const char *factory;
+ const char *path;
+};
+
+static int make_device(struct data *data)
+{
+ struct pw_impl_factory *factory;
+ struct pw_properties *props;
+
+ factory = pw_context_find_factory(data->context, "spa-device-factory");
+ if (factory == NULL)
+ return -1;
+
+ props = pw_properties_new(SPA_KEY_LIBRARY_NAME, data->library,
+ SPA_KEY_FACTORY_NAME, data->factory, NULL);
+
+ data->device = pw_impl_factory_create_object(factory,
+ NULL,
+ PW_TYPE_INTERFACE_Device,
+ PW_VERSION_DEVICE,
+ props, SPA_ID_INVALID);
+
+ pw_core_export(data->core, SPA_TYPE_INTERFACE_Device, NULL,
+ pw_impl_device_get_implementation(data->device), 0);
+
+ return 0;
+}
+
+static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct data *d = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE)
+ pw_main_loop_quit(d->loop);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+};
+
+static void do_quit(void *data, int signal_number)
+{
+ struct data *d = data;
+ pw_main_loop_quit(d->loop);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ struct pw_loop *l;
+
+ pw_init(&argc, &argv);
+
+ if (argc < 3) {
+ fprintf(stderr, "usage: %s <library> <factory>\n\n"
+ "\texample: %s v4l2/libspa-v4l2 api.v4l2.device\n\n",
+ argv[0], argv[0]);
+ return -1;
+ }
+
+ data.loop = pw_main_loop_new(NULL);
+ l = pw_main_loop_get_loop(data.loop);
+ pw_loop_add_signal(l, SIGINT, do_quit, &data);
+ pw_loop_add_signal(l, SIGTERM, do_quit, &data);
+ data.context = pw_context_new(l, NULL, 0);
+ data.library = argv[1];
+ data.factory = argv[2];
+
+ pw_context_load_module(data.context, "libpipewire-module-spa-device-factory", NULL, NULL);
+
+ data.core = pw_context_connect(data.context, NULL, 0);
+ if (data.core == NULL) {
+ pw_log_error("can't connect %m");
+ return -1;
+ }
+
+ pw_core_add_listener(data.core, &data.core_listener, &core_events, &data);
+
+ if (make_device(&data) < 0) {
+ pw_log_error("can't make device");
+ return -1;
+ }
+
+ pw_main_loop_run(data.loop);
+
+ pw_context_destroy(data.context);
+ pw_main_loop_destroy(data.loop);
+
+ return 0;
+}
diff --git a/src/examples/export-spa.c b/src/examples/export-spa.c
new file mode 100644
index 0000000..e2f27cf
--- /dev/null
+++ b/src/examples/export-spa.c
@@ -0,0 +1,183 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Exporting and loading a SPA node, using \ref api_pw_core.
+ [title]
+ */
+
+#include <stdio.h>
+#include <sys/mman.h>
+#include <signal.h>
+
+#include <spa/utils/result.h>
+#include <spa/param/video/format-utils.h>
+#include <spa/param/props.h>
+
+#include <pipewire/impl.h>
+
+struct data {
+ struct pw_main_loop *loop;
+
+ struct pw_context *context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ struct spa_node *node;
+ const char *library;
+ const char *factory;
+ const char *path;
+
+ struct pw_proxy *proxy;
+ struct spa_hook proxy_listener;
+ uint32_t id;
+};
+
+static void proxy_event_bound(void *_data, uint32_t global_id)
+{
+ struct data *data = _data;
+ if (data->id != global_id) {
+ printf("node id: %u\n", global_id);
+ data->id = global_id;
+ }
+}
+
+static const struct pw_proxy_events proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .bound = proxy_event_bound,
+};
+
+static int make_node(struct data *data)
+{
+ struct pw_properties *props;
+ struct spa_handle *hndl;
+ void *iface;
+ int res;
+
+ props = pw_properties_new(SPA_KEY_LIBRARY_NAME, data->library,
+ SPA_KEY_FACTORY_NAME, data->factory,
+ NULL);
+
+
+ hndl = pw_context_load_spa_handle(data->context, data->factory, &props->dict);
+ if (hndl == NULL)
+ return -errno;
+
+ if ((res = spa_handle_get_interface(hndl, SPA_TYPE_INTERFACE_Node, &iface)) < 0)
+ return res;
+
+ data->node = iface;
+
+ if (data->path) {
+ pw_properties_set(props, PW_KEY_NODE_AUTOCONNECT, "true");
+ pw_properties_set(props, PW_KEY_TARGET_OBJECT, data->path);
+ }
+
+ data->proxy = pw_core_export(data->core,
+ SPA_TYPE_INTERFACE_Node, &props->dict,
+ data->node, 0);
+ pw_properties_free(props);
+
+ if (data->proxy == NULL)
+ return -errno;
+
+ pw_proxy_add_listener(data->proxy,
+ &data->proxy_listener, &proxy_events, data);
+
+ return 0;
+}
+
+static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct data *d = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE)
+ pw_main_loop_quit(d->loop);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+};
+
+static void do_quit(void *data, int signal_number)
+{
+ struct data *d = data;
+ pw_main_loop_quit(d->loop);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ struct pw_loop *l;
+
+ pw_init(&argc, &argv);
+
+ if (argc < 3) {
+ fprintf(stderr, "usage: %s <library> <factory> [path]\n\n"
+ "\texample: %s v4l2/libspa-v4l2 api.v4l2.source\n\n",
+ argv[0], argv[0]);
+ return -1;
+ }
+
+ data.loop = pw_main_loop_new(NULL);
+ l = pw_main_loop_get_loop(data.loop);
+ pw_loop_add_signal(l, SIGINT, do_quit, &data);
+ pw_loop_add_signal(l, SIGTERM, do_quit, &data);
+ data.context = pw_context_new(l, NULL, 0);
+ data.library = argv[1];
+ data.factory = argv[2];
+ if (argc > 3)
+ data.path = argv[3];
+
+ pw_context_load_module(data.context, "libpipewire-module-spa-node-factory", NULL, NULL);
+
+ data.core = pw_context_connect(data.context, NULL, 0);
+ if (data.core == NULL) {
+ printf("can't connect: %m\n");
+ return -1;
+ }
+ pw_core_add_listener(data.core,
+ &data.core_listener,
+ &core_events, &data);
+
+ if (make_node(&data) < 0) {
+ pw_log_error("can't make node");
+ return -1;
+ }
+
+ pw_main_loop_run(data.loop);
+
+ pw_proxy_destroy(data.proxy);
+ pw_core_disconnect(data.core);
+ pw_context_destroy(data.context);
+ pw_main_loop_destroy(data.loop);
+
+ return 0;
+}
diff --git a/src/examples/local-v4l2.c b/src/examples/local-v4l2.c
new file mode 100644
index 0000000..1d70e9e
--- /dev/null
+++ b/src/examples/local-v4l2.c
@@ -0,0 +1,469 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Using libspa-v4l2
+ [title]
+ */
+
+#include <stdio.h>
+#include <sys/mman.h>
+
+#define WIDTH 640
+#define HEIGHT 480
+#define BPP 3
+#define MAX_BUFFERS 32
+
+#include "sdl.h"
+
+#include <spa/param/video/format-utils.h>
+#include <spa/param/props.h>
+#include <spa/pod/filter.h>
+#include <spa/node/io.h>
+#include <spa/node/utils.h>
+#include <spa/debug/format.h>
+#include <spa/utils/names.h>
+
+#include <pipewire/impl.h>
+
+struct data {
+ SDL_Renderer *renderer;
+ SDL_Window *window;
+ SDL_Texture *texture;
+
+ struct pw_main_loop *loop;
+
+ struct pw_context *context;
+ struct pw_core *core;
+
+ struct spa_port_info info;
+ struct spa_param_info params[4];
+
+ struct spa_node impl_node;
+ struct spa_io_buffers *io;
+
+ struct spa_hook_list hooks;
+
+ struct spa_video_info_raw format;
+ int32_t stride;
+
+ struct spa_buffer *buffers[MAX_BUFFERS];
+ int n_buffers;
+
+ struct pw_proxy *out, *in, *link;
+};
+
+static void handle_events(struct data *data)
+{
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_QUIT:
+ pw_main_loop_quit(data->loop);
+ break;
+ }
+ }
+}
+
+static int impl_set_io(void *object, uint32_t id, void *data, size_t size)
+{
+ return 0;
+}
+
+static int impl_send_command(void *object, const struct spa_command *command)
+{
+ return 0;
+}
+
+static int impl_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct spa_node_events *events,
+ void *data)
+{
+ struct data *d = object;
+ struct spa_hook_list save;
+
+ spa_hook_list_isolate(&d->hooks, &save, listener, events, data);
+
+ spa_node_emit_port_info(&d->hooks, SPA_DIRECTION_INPUT, 0, &d->info);
+
+ spa_hook_list_join(&d->hooks, &save);
+
+ return 0;
+}
+
+static int impl_set_callbacks(void *object,
+ const struct spa_node_callbacks *callbacks, void *data)
+{
+ return 0;
+}
+
+static int impl_port_set_io(void *object, enum spa_direction direction, uint32_t port_id,
+ uint32_t id, void *data, size_t size)
+{
+ struct data *d = object;
+
+ if (id == SPA_IO_Buffers)
+ d->io = data;
+ else
+ return -ENOENT;
+
+ return 0;
+}
+
+static int impl_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 data *d = 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;
+
+ 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:
+ {
+ SDL_RendererInfo info;
+
+ if (result.index > 0)
+ return 0;
+
+ SDL_GetRendererInfo(d->renderer, &info);
+ param = sdl_build_formats(&info, &b);
+ break;
+ }
+ case SPA_PARAM_Buffers:
+ if (result.index > 0)
+ return 0;
+
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamBuffers, id,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(2, 1, 32),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(d->stride * d->format.size.height),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(d->stride));
+ break;
+
+ case SPA_PARAM_Meta:
+ if (result.index > 0)
+ return 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 -ENOENT;
+ }
+
+ if (spa_pod_filter(&b, &result.param, param, filter) < 0)
+ goto next;
+
+ spa_node_emit_result(&d->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+
+ if (++count != num)
+ goto next;
+
+ 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 data *d = object;
+ Uint32 sdl_format;
+ void *dest;
+
+ if (format == NULL) {
+ spa_zero(d->format);
+ SDL_DestroyTexture(d->texture);
+ d->texture = NULL;
+ } else {
+ spa_debug_format(0, NULL, format);
+
+ spa_format_video_raw_parse(format, &d->format);
+
+ sdl_format = id_to_sdl_format(d->format.format);
+ if (sdl_format == SDL_PIXELFORMAT_UNKNOWN)
+ return -EINVAL;
+ if (d->format.size.width == 0 ||
+ d->format.size.height == 0)
+ return -EINVAL;
+
+ d->texture = SDL_CreateTexture(d->renderer,
+ sdl_format,
+ SDL_TEXTUREACCESS_STREAMING,
+ d->format.size.width,
+ d->format.size.height);
+ SDL_LockTexture(d->texture, NULL, &dest, &d->stride);
+ SDL_UnlockTexture(d->texture);
+ }
+
+ d->info.change_mask = SPA_PORT_CHANGE_MASK_PARAMS;
+ if (format) {
+ d->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_READWRITE);
+ d->params[2] = SPA_PARAM_INFO(SPA_PARAM_Buffers, SPA_PARAM_INFO_READ);
+ } else {
+ d->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
+ d->params[2] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
+ }
+ spa_node_emit_port_info(&d->hooks, SPA_DIRECTION_INPUT, 0, &d->info);
+
+ return 0;
+}
+
+static int impl_port_set_param(void *object,
+ enum spa_direction direction, uint32_t port_id,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ if (id == SPA_PARAM_Format) {
+ return port_set_format(object, direction, port_id, flags, param);
+ }
+ else
+ return -ENOENT;
+}
+
+static int impl_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 data *d = object;
+ uint32_t i;
+
+ if (n_buffers > MAX_BUFFERS)
+ return -ENOSPC;
+
+ for (i = 0; i < n_buffers; i++)
+ d->buffers[i] = buffers[i];
+ d->n_buffers = n_buffers;
+ return 0;
+}
+
+static int do_render(struct spa_loop *loop, bool async, uint32_t seq,
+ const void *_data, size_t size, void *user_data)
+{
+ struct data *d = user_data;
+ struct spa_buffer *buf;
+ uint8_t *map;
+ void *sdata, *ddata;
+ int sstride, dstride, ostride;
+ uint32_t i;
+ uint8_t *src, *dst;
+
+ buf = d->buffers[d->io->buffer_id];
+
+ if (buf->datas[0].type == SPA_DATA_MemFd ||
+ buf->datas[0].type == SPA_DATA_DmaBuf) {
+ map = mmap(NULL, buf->datas[0].maxsize + buf->datas[0].mapoffset, PROT_READ,
+ MAP_PRIVATE, buf->datas[0].fd, 0);
+ sdata = SPA_PTROFF(map, buf->datas[0].mapoffset, uint8_t);
+ } else if (buf->datas[0].type == SPA_DATA_MemPtr) {
+ map = NULL;
+ sdata = buf->datas[0].data;
+ } else
+ return -EINVAL;
+
+ if (SDL_LockTexture(d->texture, NULL, &ddata, &dstride) < 0) {
+ fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError());
+ return -EIO;
+ }
+ sstride = buf->datas[0].chunk->stride;
+ ostride = SPA_MIN(sstride, dstride);
+
+ src = sdata;
+ dst = ddata;
+ for (i = 0; i < d->format.size.height; i++) {
+ memcpy(dst, src, ostride);
+ src += sstride;
+ dst += dstride;
+ }
+ SDL_UnlockTexture(d->texture);
+
+ SDL_RenderClear(d->renderer);
+ SDL_RenderCopy(d->renderer, d->texture, NULL, NULL);
+ SDL_RenderPresent(d->renderer);
+
+ if (map)
+ munmap(map, buf->datas[0].maxsize + buf->datas[0].mapoffset);
+
+ return 0;
+}
+
+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,
+ SPA_ID_INVALID, NULL, 0, true, d)) < 0)
+ return res;
+
+ handle_events(d);
+
+ d->io->status = SPA_STATUS_NEED_DATA;
+
+ return SPA_STATUS_NEED_DATA;
+}
+
+static const struct spa_node_methods impl_node = {
+ SPA_VERSION_NODE_METHODS,
+ .add_listener = impl_add_listener,
+ .set_callbacks = impl_set_callbacks,
+ .set_io = impl_set_io,
+ .send_command = impl_send_command,
+ .port_set_io = impl_port_set_io,
+ .port_enum_params = impl_port_enum_params,
+ .port_set_param = impl_port_set_param,
+ .port_use_buffers = impl_port_use_buffers,
+ .process = impl_node_process,
+};
+
+static int make_nodes(struct data *data)
+{
+ struct pw_properties *props;
+
+ data->impl_node.iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_Node,
+ SPA_VERSION_NODE,
+ &impl_node, data);
+
+ data->info = SPA_PORT_INFO_INIT();
+ data->info.change_mask =
+ SPA_PORT_CHANGE_MASK_FLAGS |
+ SPA_PORT_CHANGE_MASK_PARAMS;
+ data->info.flags = 0;
+ data->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ);
+ data->params[1] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
+ data->params[2] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
+ data->params[3] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ);
+ data->info.params = data->params;
+ data->info.n_params = SPA_N_ELEMENTS(data->params);
+
+ data->in = pw_core_export(data->core,
+ SPA_TYPE_INTERFACE_Node,
+ NULL,
+ &data->impl_node,
+ 0);
+
+ props = pw_properties_new(
+ SPA_KEY_LIBRARY_NAME, "v4l2/libspa-v4l2",
+ SPA_KEY_FACTORY_NAME, SPA_NAME_API_V4L2_SOURCE,
+ NULL);
+
+ data->out = pw_core_create_object(data->core,
+ "spa-node-factory",
+ PW_TYPE_INTERFACE_Node,
+ PW_VERSION_NODE,
+ &props->dict, 0);
+
+
+ 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_properties_clear(props);
+
+ pw_properties_setf(props,
+ PW_KEY_LINK_OUTPUT_NODE, "%d", pw_proxy_get_bound_id(data->out));
+ pw_properties_setf(props,
+ PW_KEY_LINK_INPUT_NODE, "%d", pw_proxy_get_bound_id(data->in));
+
+ data->link = pw_core_create_object(data->core,
+ "link-factory",
+ PW_TYPE_INTERFACE_Link,
+ PW_VERSION_LINK,
+ &props->dict, 0);
+
+ pw_properties_free(props);
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+
+ pw_init(&argc, &argv);
+
+ data.loop = pw_main_loop_new(NULL);
+ data.context = pw_context_new(
+ pw_main_loop_get_loop(data.loop),
+ pw_properties_new(
+ PW_KEY_CORE_DAEMON, "false",
+ NULL), 0);
+
+ spa_hook_list_init(&data.hooks);
+
+ 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);
+
+ 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;
+ }
+
+ data.core = pw_context_connect_self(data.context, NULL, 0);
+ if (data.core == NULL) {
+ printf("can't connect to core: %m\n");
+ return -1;
+ }
+
+ make_nodes(&data);
+
+ pw_main_loop_run(data.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_deinit();
+
+ return 0;
+}
diff --git a/src/examples/meson.build b/src/examples/meson.build
new file mode 100644
index 0000000..e2f2600
--- /dev/null
+++ b/src/examples/meson.build
@@ -0,0 +1,51 @@
+# Examples, in order from simple to complicated
+examples = [
+ 'audio-src',
+ 'audio-dsp-src',
+ 'audio-dsp-filter',
+ 'audio-capture',
+ 'video-play',
+ 'video-src',
+ 'video-dsp-play',
+ 'video-play-pull',
+ 'video-play-reneg',
+ 'video-src-alloc',
+ 'video-src-reneg',
+ 'video-src-fixate',
+ 'video-play-fixate',
+ 'export-sink',
+ 'export-source',
+ 'export-spa',
+ 'export-spa-device',
+ 'bluez-session',
+ 'local-v4l2',
+]
+
+examples_extra_deps = {
+ 'video-src-fixate': [drm_dep],
+ 'video-play': [sdl_dep],
+ 'video-play-reneg': [sdl_dep],
+ 'video-play-fixate': [sdl_dep, drm_dep],
+ 'video-play-pull': [sdl_dep],
+ 'video-dsp-play': [sdl_dep],
+ 'local-v4l2': [sdl_dep],
+ 'export-sink': [sdl_dep],
+}
+
+foreach c : examples
+ deps = examples_extra_deps.get(c, [])
+
+ found = true
+ foreach dep : deps
+ found = found and dep.found()
+ endforeach
+
+ if found
+ executable(
+ c, c + '.c',
+ install : installed_tests_enabled,
+ install_dir : installed_tests_execdir / 'examples',
+ dependencies : [pipewire_dep, mathlib] + deps,
+ )
+ endif
+endforeach
diff --git a/src/examples/sdl.h b/src/examples/sdl.h
new file mode 100644
index 0000000..74ea74a
--- /dev/null
+++ b/src/examples/sdl.h
@@ -0,0 +1,198 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ SDL2 video format conversions
+ [title]
+ */
+
+#include <SDL2/SDL.h>
+
+#include <spa/utils/type.h>
+#include <spa/pod/builder.h>
+#include <spa/param/video/raw.h>
+#include <spa/param/video/format.h>
+
+static struct {
+ Uint32 format;
+ uint32_t id;
+} sdl_video_formats[] = {
+#if SDL_BYTEORDER == SDL_BIG_ENDIAN
+ { SDL_PIXELFORMAT_UNKNOWN, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_INDEX1LSB, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_UNKNOWN, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_INDEX1LSB, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_INDEX1MSB, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_INDEX4LSB, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_INDEX4MSB, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_INDEX8, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGB332, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGB444, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGB555, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_BGR555, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_ARGB4444, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGBA4444, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_ABGR4444, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_BGRA4444, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_ARGB1555, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGBA5551, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_ABGR1555, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_BGRA5551, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGB565, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_BGR565, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGB24, SPA_VIDEO_FORMAT_RGB,},
+ { SDL_PIXELFORMAT_RGB888, SPA_VIDEO_FORMAT_RGB,},
+ { SDL_PIXELFORMAT_RGBX8888, SPA_VIDEO_FORMAT_RGBx,},
+ { SDL_PIXELFORMAT_BGR24, SPA_VIDEO_FORMAT_BGR,},
+ { SDL_PIXELFORMAT_BGR888, SPA_VIDEO_FORMAT_BGR,},
+ { SDL_PIXELFORMAT_BGRX8888, SPA_VIDEO_FORMAT_BGRx,},
+ { SDL_PIXELFORMAT_ARGB2101010, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGBA8888, SPA_VIDEO_FORMAT_RGBA,},
+ { SDL_PIXELFORMAT_ARGB8888, SPA_VIDEO_FORMAT_ARGB,},
+ { SDL_PIXELFORMAT_BGRA8888, SPA_VIDEO_FORMAT_BGRA,},
+ { SDL_PIXELFORMAT_ABGR8888, SPA_VIDEO_FORMAT_ABGR,},
+ { SDL_PIXELFORMAT_YV12, SPA_VIDEO_FORMAT_YV12,},
+ { SDL_PIXELFORMAT_IYUV, SPA_VIDEO_FORMAT_I420,},
+ { SDL_PIXELFORMAT_YUY2, SPA_VIDEO_FORMAT_YUY2,},
+ { SDL_PIXELFORMAT_UYVY, SPA_VIDEO_FORMAT_UYVY,},
+ { SDL_PIXELFORMAT_YVYU, SPA_VIDEO_FORMAT_YVYU,},
+#if SDL_VERSION_ATLEAST(2,0,4)
+ { SDL_PIXELFORMAT_NV12, SPA_VIDEO_FORMAT_NV12,},
+ { SDL_PIXELFORMAT_NV21, SPA_VIDEO_FORMAT_NV21,},
+#endif
+#else
+ { SDL_PIXELFORMAT_UNKNOWN, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_INDEX1LSB, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_UNKNOWN, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_INDEX1LSB, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_INDEX1MSB, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_INDEX4LSB, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_INDEX4MSB, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_INDEX8, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGB332, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGB444, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGB555, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_BGR555, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_ARGB4444, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGBA4444, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_ABGR4444, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_BGRA4444, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_ARGB1555, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGBA5551, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_ABGR1555, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_BGRA5551, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGB565, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_BGR565, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGB24, SPA_VIDEO_FORMAT_BGR,},
+ { SDL_PIXELFORMAT_RGB888, SPA_VIDEO_FORMAT_BGR,},
+ { SDL_PIXELFORMAT_RGBX8888, SPA_VIDEO_FORMAT_xBGR,},
+ { SDL_PIXELFORMAT_BGR24, SPA_VIDEO_FORMAT_RGB,},
+ { SDL_PIXELFORMAT_BGR888, SPA_VIDEO_FORMAT_RGB,},
+ { SDL_PIXELFORMAT_BGRX8888, SPA_VIDEO_FORMAT_xRGB,},
+ { SDL_PIXELFORMAT_ARGB2101010, SPA_VIDEO_FORMAT_UNKNOWN,},
+ { SDL_PIXELFORMAT_RGBA8888, SPA_VIDEO_FORMAT_ABGR,},
+ { SDL_PIXELFORMAT_ARGB8888, SPA_VIDEO_FORMAT_BGRA,},
+ { SDL_PIXELFORMAT_BGRA8888, SPA_VIDEO_FORMAT_ARGB,},
+ { SDL_PIXELFORMAT_ABGR8888, SPA_VIDEO_FORMAT_RGBA,},
+ { SDL_PIXELFORMAT_YV12, SPA_VIDEO_FORMAT_YV12,},
+ { SDL_PIXELFORMAT_IYUV, SPA_VIDEO_FORMAT_I420,},
+ { SDL_PIXELFORMAT_YUY2, SPA_VIDEO_FORMAT_YUY2,},
+ { SDL_PIXELFORMAT_UYVY, SPA_VIDEO_FORMAT_UYVY,},
+ { SDL_PIXELFORMAT_YVYU, SPA_VIDEO_FORMAT_YVYU,},
+#if SDL_VERSION_ATLEAST(2,0,4)
+ { SDL_PIXELFORMAT_NV12, SPA_VIDEO_FORMAT_NV12,},
+ { SDL_PIXELFORMAT_NV21, SPA_VIDEO_FORMAT_NV21,},
+#endif
+#endif
+};
+
+static inline uint32_t sdl_format_to_id(Uint32 format)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(sdl_video_formats, f) {
+ if (f->format == format)
+ return f->id;
+ }
+ return SPA_VIDEO_FORMAT_UNKNOWN;
+}
+
+static inline Uint32 id_to_sdl_format(uint32_t id)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(sdl_video_formats, f) {
+ if (f->id == id)
+ return f->format;
+ }
+ return SDL_PIXELFORMAT_UNKNOWN;
+}
+
+static inline struct spa_pod *sdl_build_formats(SDL_RendererInfo *info, struct spa_pod_builder *b)
+{
+ uint32_t i, c;
+ struct spa_pod_frame f[2];
+
+ /* make an object of type SPA_TYPE_OBJECT_Format and id SPA_PARAM_EnumFormat.
+ * The object type is important because it defines the properties that are
+ * acceptable. The id gives more context about what the object is meant to
+ * contain. In this case we enumerate supported formats. */
+ spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
+ /* add media type and media subtype properties */
+ spa_pod_builder_prop(b, SPA_FORMAT_mediaType, 0);
+ spa_pod_builder_id(b, SPA_MEDIA_TYPE_video);
+ spa_pod_builder_prop(b, SPA_FORMAT_mediaSubtype, 0);
+ spa_pod_builder_id(b, SPA_MEDIA_SUBTYPE_raw);
+
+ /* build an enumeration of formats */
+ spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_format, 0);
+ spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0);
+ /* first the formats supported by the textures */
+ for (i = 0, c = 0; i < info->num_texture_formats; i++) {
+ uint32_t id = sdl_format_to_id(info->texture_formats[i]);
+ if (id == 0)
+ continue;
+ if (c++ == 0)
+ spa_pod_builder_id(b, id);
+ spa_pod_builder_id(b, id);
+ }
+ /* then all the other ones SDL can convert from/to */
+ SPA_FOR_EACH_ELEMENT_VAR(sdl_video_formats, f) {
+ uint32_t id = f->id;
+ if (id != SPA_VIDEO_FORMAT_UNKNOWN)
+ spa_pod_builder_id(b, id);
+ }
+ spa_pod_builder_id(b, SPA_VIDEO_FORMAT_RGBA_F32);
+ spa_pod_builder_pop(b, &f[1]);
+ /* add size and framerate ranges */
+ spa_pod_builder_add(b,
+ SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
+ &SPA_RECTANGLE(WIDTH, HEIGHT),
+ &SPA_RECTANGLE(1,1),
+ &SPA_RECTANGLE(info->max_texture_width,
+ info->max_texture_height)),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_CHOICE_RANGE_Fraction(
+ &SPA_FRACTION(25,1),
+ &SPA_FRACTION(0,1),
+ &SPA_FRACTION(30,1)),
+ 0);
+ return spa_pod_builder_pop(b, &f[0]);
+}
diff --git a/src/examples/video-dsp-play.c b/src/examples/video-dsp-play.c
new file mode 100644
index 0000000..6068e2a
--- /dev/null
+++ b/src/examples/video-dsp-play.c
@@ -0,0 +1,315 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Video input stream using \ref pw_filter "pw_filter".
+ [title]
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#include <spa/param/video/format-utils.h>
+#include <spa/param/props.h>
+#include <spa/debug/format.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/filter.h>
+
+#define WIDTH 640
+#define HEIGHT 480
+#define BPP 3
+
+#define MAX_BUFFERS 64
+
+#include "sdl.h"
+
+struct pixel {
+ float r, g, b, a;
+};
+
+struct data {
+ const char *target;
+
+ SDL_Renderer *renderer;
+ SDL_Window *window;
+ SDL_Texture *texture;
+ SDL_Texture *cursor;
+
+ struct pw_main_loop *loop;
+
+ struct pw_filter *filter;
+ struct spa_hook filter_listener;
+
+ void *in_port;
+
+ struct spa_io_position *position;
+ struct spa_video_info_dsp format;
+
+ int counter;
+ SDL_Rect rect;
+ SDL_Rect cursor_rect;
+};
+
+static void handle_events(struct data *data)
+{
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_QUIT:
+ pw_main_loop_quit(data->loop);
+ break;
+ }
+ }
+}
+
+/* our data processing function is in general:
+ *
+ * struct pw_buffer *b;
+ * b = pw_filter_dequeue_buffer(port);
+ *
+ * .. do stuff with buffer ...
+ *
+ * pw_filter_queue_buffer(port, b);
+ */
+static void
+on_process(void *_data, struct spa_io_position *position)
+{
+ struct data *data = _data;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ void *sdata, *ddata;
+ int sstride, dstride;
+ uint32_t i, j;
+ uint8_t *src, *dst;
+
+ b = NULL;
+ while (true) {
+ struct pw_buffer *t;
+ if ((t = pw_filter_dequeue_buffer(data->in_port)) == NULL)
+ break;
+ if (b)
+ pw_filter_queue_buffer(data->in_port, b);
+ b = t;
+ }
+ if (b == NULL) {
+ pw_log_warn("out of buffers: %m");
+ return;
+ }
+
+ buf = b->buffer;
+
+ pw_log_trace("new buffer %p %dx%d", buf,
+ data->position->video.size.width, data->position->video.size.height);
+
+ handle_events(data);
+
+ if ((sdata = buf->datas[0].data) == NULL) {
+ pw_log_error("no buffer data");
+ goto done;
+ }
+
+ if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) {
+ pw_log_error("Couldn't lock texture: %s", SDL_GetError());
+ goto done;
+ }
+
+ /* copy video image in texture */
+ sstride = buf->datas[0].chunk->stride;
+ if (sstride == 0)
+ sstride = buf->datas[0].chunk->size / data->position->video.size.height;
+
+ src = sdata;
+ dst = ddata;
+
+ for (i = 0; i < data->position->video.size.height; i++) {
+ struct pixel *p = (struct pixel *) src;
+ for (j = 0; j < data->position->video.size.width; j++) {
+ dst[j * 4 + 0] = SPA_CLAMP(p[j].r * 255.0f, 0, 255);
+ dst[j * 4 + 1] = SPA_CLAMP(p[j].g * 255.0f, 0, 255);
+ dst[j * 4 + 2] = SPA_CLAMP(p[j].b * 255.0f, 0, 255);
+ dst[j * 4 + 3] = SPA_CLAMP(p[j].a * 255.0f, 0, 255);
+ }
+ src += sstride;
+ dst += dstride;
+ }
+ SDL_UnlockTexture(data->texture);
+
+ SDL_RenderClear(data->renderer);
+ SDL_RenderCopy(data->renderer, data->texture, &data->rect, NULL);
+ SDL_RenderPresent(data->renderer);
+
+ done:
+ pw_filter_queue_buffer(data->in_port, b);
+}
+
+static void on_filter_state_changed(void *_data, enum pw_filter_state old,
+ enum pw_filter_state state, const char *error)
+{
+ struct data *data = _data;
+ fprintf(stderr, "filter state: \"%s\"\n", pw_filter_state_as_string(state));
+ switch (state) {
+ case PW_FILTER_STATE_UNCONNECTED:
+ pw_main_loop_quit(data->loop);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+on_filter_io_changed(void *_data, void *port_data, uint32_t id, void *area, uint32_t size)
+{
+ struct data *data = _data;
+
+ switch (id) {
+ case SPA_IO_Position:
+ data->position = area;
+ break;
+ }
+}
+
+static void
+on_filter_param_changed(void *_data, void *port_data, uint32_t id, const struct spa_pod *param)
+{
+ struct data *data = _data;
+ struct pw_filter *filter = data->filter;
+
+ /* NULL means to clear the format */
+ if (param == NULL || id != SPA_PARAM_Format)
+ return;
+
+ /* call a helper function to parse the format for us. */
+ spa_format_video_dsp_parse(param, &data->format);
+
+ if (data->format.format != SPA_VIDEO_FORMAT_RGBA_F32) {
+ pw_filter_set_error(filter, -EINVAL, "unknown format");
+ return;
+ }
+
+ data->texture = SDL_CreateTexture(data->renderer,
+ SDL_PIXELFORMAT_RGBA32,
+ SDL_TEXTUREACCESS_STREAMING,
+ data->position->video.size.width,
+ data->position->video.size.height);
+ if (data->texture == NULL) {
+ pw_filter_set_error(filter, -errno, "can't create texture");
+ return;
+ }
+
+ data->rect.x = 0;
+ data->rect.y = 0;
+ data->rect.w = data->position->video.size.width;
+ data->rect.h = data->position->video.size.height;
+}
+
+/* these are the filter events we listen for */
+static const struct pw_filter_events filter_events = {
+ PW_VERSION_FILTER_EVENTS,
+ .state_changed = on_filter_state_changed,
+ .io_changed = on_filter_io_changed,
+ .param_changed = on_filter_param_changed,
+ .process = on_process,
+};
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+
+ pw_init(&argc, &argv);
+
+ /* create a main loop */
+ data.loop = pw_main_loop_new(NULL);
+
+ data.target = argc > 1 ? argv[1] : NULL;
+
+ /* create a simple filter, the simple filter manages to core and remote
+ * objects for you if you don't need to deal with them
+ *
+ * If you plan to autoconnect your filter, you need to provide at least
+ * media, category and role properties
+ *
+ * Pass your events and a user_data pointer as the last arguments. This
+ * will inform you about the filter state. The most important event
+ * you need to listen to is the process event where you need to consume
+ * the data provided to you.
+ */
+ data.filter = pw_filter_new_simple(
+ pw_main_loop_get_loop(data.loop),
+ "video-dsp-play",
+ pw_properties_new(
+ PW_KEY_MEDIA_TYPE, "Video",
+ PW_KEY_MEDIA_CATEGORY, "Capture",
+ PW_KEY_MEDIA_ROLE, "DSP",
+ PW_KEY_NODE_AUTOCONNECT, data.target ? "true" : "false",
+ PW_KEY_TARGET_OBJECT, data.target,
+ PW_KEY_MEDIA_CLASS, "Stream/Input/Video",
+ NULL),
+ &filter_events,
+ &data);
+
+
+ if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ if (SDL_CreateWindowAndRenderer
+ (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) {
+ fprintf(stderr, "can't create window: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ /* Make a new DSP port. This will automatically set up the right
+ * parameters for the port */
+ data.in_port = pw_filter_add_port(data.filter,
+ PW_DIRECTION_INPUT,
+ PW_FILTER_PORT_FLAG_MAP_BUFFERS,
+ 0,
+ pw_properties_new(
+ PW_KEY_FORMAT_DSP, "32 bit float RGBA video",
+ PW_KEY_PORT_NAME, "input",
+ NULL),
+ NULL, 0);
+
+ pw_filter_connect(data.filter,
+ 0, /* no flags */
+ NULL, 0);
+
+ /* do things until we quit the mainloop */
+ pw_main_loop_run(data.loop);
+
+ pw_filter_destroy(data.filter);
+ pw_main_loop_destroy(data.loop);
+
+ SDL_DestroyTexture(data.texture);
+ if (data.cursor)
+ SDL_DestroyTexture(data.cursor);
+ SDL_DestroyRenderer(data.renderer);
+ SDL_DestroyWindow(data.window);
+
+ return 0;
+}
diff --git a/src/examples/video-play-fixate.c b/src/examples/video-play-fixate.c
new file mode 100644
index 0000000..7477c5a
--- /dev/null
+++ b/src/examples/video-play-fixate.c
@@ -0,0 +1,516 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Video input stream using \ref pw_stream "pw_stream", with format fixation.
+ [title]
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <signal.h>
+#include <libdrm/drm_fourcc.h>
+
+#include <spa/utils/result.h>
+#include <spa/param/video/format-utils.h>
+#include <spa/param/props.h>
+#include <spa/debug/format.h>
+
+#include <pipewire/pipewire.h>
+
+#define WIDTH 640
+#define HEIGHT 480
+
+#define MAX_BUFFERS 64
+#define MAX_MOD 8
+
+#include "sdl.h"
+
+struct pixel {
+ float r, g, b, a;
+};
+
+struct pw_version {
+ int major;
+ int minor;
+ int micro;
+};
+
+struct modifier_info {
+ uint32_t spa_format;
+ uint32_t n_modifiers;
+ uint64_t modifiers[MAX_MOD];
+};
+
+struct data {
+ const char *path;
+
+ SDL_Renderer *renderer;
+ SDL_Window *window;
+ SDL_Texture *texture;
+ SDL_Texture *cursor;
+
+ struct pw_main_loop *loop;
+ struct spa_source *reneg;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ struct spa_video_info format;
+ int32_t stride;
+ struct spa_rectangle size;
+
+ uint32_t n_mod_info;
+ struct modifier_info mod_info[2];
+
+ int counter;
+};
+
+static struct pw_version parse_pw_version(const char* version) {
+ struct pw_version pw_version;
+ sscanf(version, "%d.%d.%d", &pw_version.major, &pw_version.minor,
+ &pw_version.micro);
+ return pw_version;
+}
+
+static bool has_pw_version(int major, int minor, int micro) {
+ struct pw_version pw_version = parse_pw_version(pw_get_library_version());
+ printf("PW Version: %d.%d.%d\n", pw_version.major, pw_version.minor,
+ pw_version.micro);
+ return major <= pw_version.major && minor <= pw_version.minor && micro <= pw_version.micro;
+}
+
+static void init_modifiers(struct data *data)
+{
+ data->n_mod_info = 1;
+ data->mod_info[0].spa_format = SPA_VIDEO_FORMAT_RGB;
+ data->mod_info[0].n_modifiers = 2;
+ data->mod_info[0].modifiers[0] = DRM_FORMAT_MOD_LINEAR;
+ data->mod_info[0].modifiers[1] = DRM_FORMAT_MOD_INVALID;
+}
+
+static void destroy_modifiers(struct data *data)
+{
+ data->mod_info[0].n_modifiers = 0;
+}
+
+static void strip_modifier(struct data *data, uint32_t spa_format, uint64_t modifier)
+{
+ if (data->mod_info[0].spa_format != spa_format)
+ return;
+ struct modifier_info *mod_info = &data->mod_info[0];
+ uint32_t counter = 0;
+ // Dropping of single modifiers is only supported on PipeWire 0.3.40 and newer.
+ // On older PipeWire just dropping all modifiers might work on Versions newer then 0.3.33/35
+ if (has_pw_version(0,3,40)) {
+ printf("Dropping a single modifier\n");
+ for (uint32_t i = 0; i < mod_info->n_modifiers; i++) {
+ if (mod_info->modifiers[i] == modifier)
+ continue;
+ mod_info->modifiers[counter++] = mod_info->modifiers[i];
+ }
+ } else {
+ printf("Dropping all modifiers\n");
+ counter = 0;
+ }
+ mod_info->n_modifiers = counter;
+}
+
+static void handle_events(struct data *data)
+{
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_QUIT:
+ pw_main_loop_quit(data->loop);
+ break;
+ }
+ }
+}
+
+static struct spa_pod *build_format(struct spa_pod_builder *b, SDL_RendererInfo *info, enum spa_video_format format,
+ uint64_t *modifiers, int modifier_count)
+{
+ struct spa_pod_frame f[2];
+ int i, c;
+
+ 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_video), 0);
+ spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
+ /* format */
+ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);
+ /* modifiers */
+ if (modifier_count == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) {
+ // we only support implicit modifiers, use shortpath to skip fixation phase
+ spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY);
+ spa_pod_builder_long(b, modifiers[0]);
+ } else if (modifier_count > 0) {
+ // build an enumeration of modifiers
+ spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
+ spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0);
+ // modifiers from the array
+ for (i = 0, c = 0; i < modifier_count; i++) {
+ spa_pod_builder_long(b, modifiers[i]);
+ if (c++ == 0)
+ spa_pod_builder_long(b, modifiers[i]);
+ }
+ spa_pod_builder_pop(b, &f[1]);
+ }
+ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size,
+ SPA_POD_CHOICE_RANGE_Rectangle(
+ &SPA_RECTANGLE(WIDTH, HEIGHT),
+ &SPA_RECTANGLE(1,1),
+ &SPA_RECTANGLE(info->max_texture_width,
+ info->max_texture_height)),
+ 0);
+ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate,
+ SPA_POD_CHOICE_RANGE_Fraction(
+ &SPA_FRACTION(25,1),
+ &SPA_FRACTION(0,1),
+ &SPA_FRACTION(30,1)),
+ 0);
+ return spa_pod_builder_pop(b, &f[0]);
+}
+
+/* our data processing function is in general:
+ *
+ * struct pw_buffer *b;
+ * b = pw_stream_dequeue_buffer(stream);
+ *
+ * .. do stuff with buffer ...
+ *
+ * pw_stream_queue_buffer(stream, b);
+ */
+static void
+on_process(void *_data)
+{
+ struct data *data = _data;
+ struct pw_stream *stream = data->stream;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ void *sdata, *ddata;
+ int sstride, dstride, ostride;
+ uint32_t i;
+ uint8_t *src, *dst;
+
+ b = NULL;
+ /* dequeue and queue old buffers, use the last available
+ * buffer */
+ while (true) {
+ struct pw_buffer *t;
+ if ((t = pw_stream_dequeue_buffer(stream)) == NULL)
+ break;
+ if (b)
+ pw_stream_queue_buffer(stream, b);
+ b = t;
+ }
+ if (b == NULL) {
+ pw_log_warn("out of buffers: %m");
+ return;
+ }
+
+ buf = b->buffer;
+
+ pw_log_info("new buffer %p", buf);
+
+ handle_events(data);
+
+ if (buf->datas[0].type == SPA_DATA_DmaBuf) {
+ // Simulate a failed import of a DmaBuf
+ // We should try another modifier
+ printf("Failed to import dmabuf, stripping modifier %"PRIu64"\n", data->format.info.raw.modifier);
+ strip_modifier(data, data->format.info.raw.format, data->format.info.raw.modifier);
+ pw_loop_signal_event(pw_main_loop_get_loop(data->loop), data->reneg);
+ goto done;
+ }
+
+ if ((sdata = buf->datas[0].data) == NULL)
+ goto done;
+
+ if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) {
+ fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError());
+ goto done;
+ }
+
+ /* copy video image in texture */
+ sstride = buf->datas[0].chunk->stride;
+ if (sstride == 0)
+ sstride = buf->datas[0].chunk->size / data->size.height;
+ ostride = SPA_MIN(sstride, dstride);
+
+ src = sdata;
+ dst = ddata;
+
+ for (i = 0; i < data->size.height; i++) {
+ memcpy(dst, src, ostride);
+ src += sstride;
+ dst += dstride;
+ }
+ SDL_UnlockTexture(data->texture);
+
+ SDL_RenderClear(data->renderer);
+ /* now render the video */
+ SDL_RenderCopy(data->renderer, data->texture, NULL, NULL);
+ SDL_RenderPresent(data->renderer);
+
+ done:
+ pw_stream_queue_buffer(stream, b);
+}
+
+static void on_stream_state_changed(void *_data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct data *data = _data;
+ fprintf(stderr, "stream state: \"%s\"\n", pw_stream_state_as_string(state));
+ switch (state) {
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_main_loop_quit(data->loop);
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ default:
+ break;
+ }
+}
+
+/* Be notified when the stream param changes. We're only looking at the
+ * format changes.
+ *
+ * We are now supposed to call pw_stream_finish_format() with success or
+ * failure, depending on if we can support the format. Because we gave
+ * a list of supported formats, this should be ok.
+ *
+ * As part of pw_stream_finish_format() we can provide parameters that
+ * will control the buffer memory allocation. This includes the metadata
+ * that we would like on our buffer, the size, alignment, etc.
+ */
+static void
+on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param)
+{
+ struct data *data = _data;
+ struct pw_stream *stream = data->stream;
+ uint8_t params_buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
+ const struct spa_pod *params[1];
+ Uint32 sdl_format;
+ void *d;
+
+ /* NULL means to clear the format */
+ if (param == NULL || id != SPA_PARAM_Format)
+ return;
+
+ fprintf(stderr, "got format:\n");
+ spa_debug_format(2, NULL, param);
+
+ if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0)
+ return;
+
+ if (data->format.media_type != SPA_MEDIA_TYPE_video ||
+ data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
+ return;
+
+ /* call a helper function to parse the format for us. */
+ spa_format_video_raw_parse(param, &data->format.info.raw);
+ sdl_format = id_to_sdl_format(data->format.info.raw.format);
+ data->size = data->format.info.raw.size;
+
+ if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) {
+ pw_stream_set_error(stream, -EINVAL, "unknown pixel format");
+ return;
+ }
+ if (data->size.width == 0 || data->size.height == 0) {
+ pw_stream_set_error(stream, -EINVAL, "invalid size");
+ return;
+ }
+
+ data->texture = SDL_CreateTexture(data->renderer,
+ sdl_format,
+ SDL_TEXTUREACCESS_STREAMING,
+ data->size.width,
+ data->size.height);
+ SDL_LockTexture(data->texture, NULL, &d, &data->stride);
+ SDL_UnlockTexture(data->texture);
+
+ /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size,
+ * number, stride etc of the buffers */
+ params[0] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->size.height),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride),
+ SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<<SPA_DATA_MemPtr) | (1<<SPA_DATA_DmaBuf)));
+
+ /* we are done */
+ pw_stream_update_params(stream, params, 1);
+}
+
+/* these are the stream events we listen for */
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .state_changed = on_stream_state_changed,
+ .param_changed = on_stream_param_changed,
+ .process = on_process,
+};
+
+static int build_formats(struct data *data, struct spa_pod_builder *b, const struct spa_pod **params)
+{
+ SDL_RendererInfo info;
+ int n_params = 0;
+
+ SDL_GetRendererInfo(data->renderer, &info);
+
+ if (data->mod_info[0].n_modifiers > 0) {
+ params[n_params++] = build_format(b, &info, SPA_VIDEO_FORMAT_RGB, data->mod_info[0].modifiers, data->mod_info[0].n_modifiers);
+ }
+ params[n_params++] = build_format(b, &info, SPA_VIDEO_FORMAT_RGB, NULL, 0);
+
+ for (int i=0; i < n_params; i++) {
+ spa_debug_format(2, NULL, params[i]);
+ }
+
+ return n_params;
+}
+
+static void reneg_format(void *_data, uint64_t expiration)
+{
+ struct data *data = (struct data*) _data;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ const struct spa_pod *params[2];
+ uint32_t n_params;
+
+ if (data->format.info.raw.format == 0)
+ return;
+
+ fprintf(stderr, "renegotiate formats:\n");
+ n_params = build_formats(data, &b, params);
+
+ pw_stream_update_params(data->stream, params, n_params);
+}
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_main_loop_quit(data->loop);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ const struct spa_pod *params[2];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ struct pw_properties *props;
+ int res, n_params;
+
+ pw_init(&argc, &argv);
+
+ /* create a main loop */
+ data.loop = pw_main_loop_new(NULL);
+
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data);
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data);
+
+ /* create a simple stream, the simple stream manages to core and remote
+ * objects for you if you don't need to deal with them
+ *
+ * If you plan to autoconnect your stream, you need to provide at least
+ * media, category and role properties
+ *
+ * Pass your events and a user_data pointer as the last arguments. This
+ * will inform you about the stream state. The most important event
+ * you need to listen to is the process event where you need to consume
+ * the data provided to you.
+ */
+ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video",
+ PW_KEY_MEDIA_CATEGORY, "Capture",
+ PW_KEY_MEDIA_ROLE, "Camera",
+ NULL),
+ data.path = argc > 1 ? argv[1] : NULL;
+ if (data.path)
+ /* Set stream target if given on command line */
+ pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path);
+
+ data.stream = pw_stream_new_simple(
+ pw_main_loop_get_loop(data.loop),
+ "video-play-fixate",
+ props,
+ &stream_events,
+ &data);
+
+ if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ init_modifiers(&data);
+
+ if (SDL_CreateWindowAndRenderer
+ (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) {
+ fprintf(stderr, "can't create window: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ /* build the extra parameters to connect with. To connect, we can provide
+ * a list of supported formats. We use a builder that writes the param
+ * object to the stack. */
+ printf("supported formats:\n");
+ n_params = build_formats(&data, &b, params);
+
+ /* now connect the stream, we need a direction (input/output),
+ * an optional target node to connect to, some flags and parameters
+ */
+ if ((res = pw_stream_connect(data.stream,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */
+ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */
+ params, n_params)) /* extra parameters, see above */ < 0) {
+ fprintf(stderr, "can't connect: %s\n", spa_strerror(res));
+ return -1;
+ }
+
+ data.reneg = pw_loop_add_event(pw_main_loop_get_loop(data.loop), reneg_format, &data);
+
+ /* do things until we quit the mainloop */
+ pw_main_loop_run(data.loop);
+
+ pw_stream_destroy(data.stream);
+ pw_main_loop_destroy(data.loop);
+
+ destroy_modifiers(&data);
+
+ SDL_DestroyTexture(data.texture);
+ if (data.cursor)
+ SDL_DestroyTexture(data.cursor);
+ SDL_DestroyRenderer(data.renderer);
+ SDL_DestroyWindow(data.window);
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/examples/video-play-pull.c b/src/examples/video-play-pull.c
new file mode 100644
index 0000000..fd0e305
--- /dev/null
+++ b/src/examples/video-play-pull.c
@@ -0,0 +1,588 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Video input stream using \ref pw_stream_trigger_process, for pull mode.
+ [title]
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <spa/utils/result.h>
+#include <spa/param/video/format-utils.h>
+#include <spa/param/props.h>
+#include <spa/debug/format.h>
+
+#include <pipewire/pipewire.h>
+
+#define WIDTH 640
+#define HEIGHT 480
+
+#define MAX_BUFFERS 64
+
+#include "sdl.h"
+
+struct pixel {
+ float r, g, b, a;
+};
+
+struct data {
+ const char *path;
+
+ SDL_Renderer *renderer;
+ SDL_Window *window;
+ SDL_Texture *texture;
+ SDL_Texture *cursor;
+
+ struct pw_main_loop *loop;
+ struct spa_source *timer;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ struct spa_io_position *position;
+
+ struct spa_video_info format;
+ int32_t stride;
+ struct spa_rectangle size;
+
+ int counter;
+ SDL_Rect rect;
+ SDL_Rect cursor_rect;
+ bool is_yuv;
+ bool have_request_process;
+};
+
+static void handle_events(struct data *data)
+{
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_QUIT:
+ pw_main_loop_quit(data->loop);
+ break;
+ }
+ }
+}
+
+/* our data processing function is in general:
+ *
+ * struct pw_buffer *b;
+ * b = pw_stream_dequeue_buffer(stream);
+ *
+ * .. do stuff with buffer ...
+ *
+ * pw_stream_queue_buffer(stream, b);
+ */
+static void
+on_process(void *_data)
+{
+ struct data *data = _data;
+ struct pw_stream *stream = data->stream;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ void *sdata, *ddata;
+ int sstride, dstride, ostride;
+ struct spa_meta_region *mc;
+ struct spa_meta_cursor *mcs;
+ uint32_t i, j;
+ uint8_t *src, *dst;
+ bool render_cursor = false;
+
+ b = NULL;
+ while (true) {
+ struct pw_buffer *t;
+ if ((t = pw_stream_dequeue_buffer(stream)) == NULL)
+ break;
+ if (b)
+ pw_stream_queue_buffer(stream, b);
+ b = t;
+ }
+ if (b == NULL) {
+ pw_log_warn("out of buffers: %m");
+ return;
+ }
+
+ buf = b->buffer;
+
+ pw_log_trace("new buffer %p", buf);
+
+ handle_events(data);
+
+ if ((sdata = buf->datas[0].data) == NULL)
+ goto done;
+
+ /* get the videocrop metadata if any */
+ if ((mc = spa_buffer_find_meta_data(buf, SPA_META_VideoCrop, sizeof(*mc))) &&
+ spa_meta_region_is_valid(mc)) {
+ data->rect.x = mc->region.position.x;
+ data->rect.y = mc->region.position.y;
+ data->rect.w = mc->region.size.width;
+ data->rect.h = mc->region.size.height;
+ }
+ /* get cursor metadata */
+ if ((mcs = spa_buffer_find_meta_data(buf, SPA_META_Cursor, sizeof(*mcs))) &&
+ spa_meta_cursor_is_valid(mcs)) {
+ struct spa_meta_bitmap *mb;
+ void *cdata;
+ int cstride;
+
+ data->cursor_rect.x = mcs->position.x;
+ data->cursor_rect.y = mcs->position.y;
+
+ mb = SPA_PTROFF(mcs, mcs->bitmap_offset, struct spa_meta_bitmap);
+ data->cursor_rect.w = mb->size.width;
+ data->cursor_rect.h = mb->size.height;
+
+ if (data->cursor == NULL) {
+ data->cursor = SDL_CreateTexture(data->renderer,
+ id_to_sdl_format(mb->format),
+ SDL_TEXTUREACCESS_STREAMING,
+ mb->size.width, mb->size.height);
+ SDL_SetTextureBlendMode(data->cursor, SDL_BLENDMODE_BLEND);
+ }
+
+
+ if (SDL_LockTexture(data->cursor, NULL, &cdata, &cstride) < 0) {
+ fprintf(stderr, "Couldn't lock cursor texture: %s\n", SDL_GetError());
+ goto done;
+ }
+
+ /* copy the cursor bitmap into the texture */
+ src = SPA_PTROFF(mb, mb->offset, uint8_t);
+ dst = cdata;
+ ostride = SPA_MIN(cstride, mb->stride);
+
+ for (i = 0; i < mb->size.height; i++) {
+ memcpy(dst, src, ostride);
+ dst += cstride;
+ src += mb->stride;
+ }
+ SDL_UnlockTexture(data->cursor);
+
+ render_cursor = true;
+ }
+
+ /* copy video image in texture */
+ if (data->is_yuv) {
+ sstride = data->stride;
+ SDL_UpdateYUVTexture(data->texture,
+ NULL,
+ sdata,
+ sstride,
+ SPA_PTROFF(sdata, sstride * data->size.height, void),
+ sstride / 2,
+ SPA_PTROFF(sdata, 5 * (sstride * data->size.height) / 4, void),
+ sstride / 2);
+ }
+ else {
+ if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) {
+ fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError());
+ goto done;
+ }
+
+ sstride = buf->datas[0].chunk->stride;
+ if (sstride == 0)
+ sstride = buf->datas[0].chunk->size / data->size.height;
+ ostride = SPA_MIN(sstride, dstride);
+
+ src = sdata;
+ dst = ddata;
+
+ if (data->format.media_subtype == SPA_MEDIA_SUBTYPE_dsp) {
+ for (i = 0; i < data->size.height; i++) {
+ struct pixel *p = (struct pixel *) src;
+ for (j = 0; j < data->size.width; j++) {
+ dst[j * 4 + 0] = SPA_CLAMP(p[j].r * 255.0f, 0, 255);
+ dst[j * 4 + 1] = SPA_CLAMP(p[j].g * 255.0f, 0, 255);
+ dst[j * 4 + 2] = SPA_CLAMP(p[j].b * 255.0f, 0, 255);
+ dst[j * 4 + 3] = SPA_CLAMP(p[j].a * 255.0f, 0, 255);
+ }
+ src += sstride;
+ dst += dstride;
+ }
+ } else {
+ for (i = 0; i < data->size.height; i++) {
+ memcpy(dst, src, ostride);
+ src += sstride;
+ dst += dstride;
+ }
+ }
+ SDL_UnlockTexture(data->texture);
+ }
+
+ SDL_RenderClear(data->renderer);
+ /* now render the video and then the cursor if any */
+ SDL_RenderCopy(data->renderer, data->texture, &data->rect, NULL);
+ if (render_cursor) {
+ SDL_RenderCopy(data->renderer, data->cursor, NULL, &data->cursor_rect);
+ }
+ SDL_RenderPresent(data->renderer);
+
+ done:
+ pw_stream_queue_buffer(stream, b);
+}
+
+static void enable_timeouts(struct data *data, bool enabled)
+{
+ struct timespec timeout, interval, *to, *iv;
+
+ if (!enabled || data->have_request_process) {
+ to = iv = NULL;
+ } else {
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 1;
+ interval.tv_sec = 0;
+ interval.tv_nsec = 80 * SPA_NSEC_PER_MSEC;
+ to = &timeout;
+ iv = &interval;
+ }
+ pw_loop_update_timer(pw_main_loop_get_loop(data->loop),
+ data->timer, to, iv, false);
+}
+
+
+static void on_stream_state_changed(void *_data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct data *data = _data;
+ fprintf(stderr, "stream state: \"%s\"\n", pw_stream_state_as_string(state));
+ switch (state) {
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_main_loop_quit(data->loop);
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ enable_timeouts(data, false);
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ enable_timeouts(data, true);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+on_stream_io_changed(void *_data, uint32_t id, void *area, uint32_t size)
+{
+ struct data *data = _data;
+
+ switch (id) {
+ case SPA_IO_Position:
+ data->position = area;
+ break;
+ }
+}
+
+static void
+on_trigger_done(void *_data)
+{
+ struct data *data = _data;
+ pw_log_trace("%p trigger done", data);
+}
+
+static void on_timeout(void *userdata, uint64_t expirations)
+{
+ struct data *data = userdata;
+ if (!data->have_request_process)
+ pw_stream_trigger_process(data->stream);
+}
+
+static void
+on_command(void *_data, const struct spa_command *command)
+{
+ struct data *data = _data;
+ switch (SPA_NODE_COMMAND_ID(command)) {
+ case SPA_NODE_COMMAND_RequestProcess:
+ data->have_request_process = true;
+ enable_timeouts(data, false);
+ pw_stream_trigger_process(data->stream);
+ break;
+ default:
+ break;
+ }
+}
+
+/* Be notified when the stream param changes. We're only looking at the
+ * format changes.
+ *
+ * We are now supposed to call pw_stream_finish_format() with success or
+ * failure, depending on if we can support the format. Because we gave
+ * a list of supported formats, this should be ok.
+ *
+ * As part of pw_stream_finish_format() we can provide parameters that
+ * will control the buffer memory allocation. This includes the metadata
+ * that we would like on our buffer, the size, alignment, etc.
+ */
+static void
+on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param)
+{
+ struct data *data = _data;
+ struct pw_stream *stream = data->stream;
+ uint8_t params_buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
+ const struct spa_pod *params[5];
+ Uint32 sdl_format;
+ void *d;
+ int32_t mult, size;
+
+ /* NULL means to clear the format */
+ if (param == NULL || id != SPA_PARAM_Format)
+ return;
+
+ fprintf(stderr, "got format:\n");
+ spa_debug_format(2, NULL, param);
+
+ if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0)
+ return;
+
+ if (data->format.media_type != SPA_MEDIA_TYPE_video)
+ return;
+
+ switch (data->format.media_subtype) {
+ case SPA_MEDIA_SUBTYPE_raw:
+ /* call a helper function to parse the format for us. */
+ spa_format_video_raw_parse(param, &data->format.info.raw);
+ sdl_format = id_to_sdl_format(data->format.info.raw.format);
+ data->size = SPA_RECTANGLE(data->format.info.raw.size.width,
+ data->format.info.raw.size.height);
+ mult = 1;
+ break;
+ case SPA_MEDIA_SUBTYPE_dsp:
+ spa_format_video_dsp_parse(param, &data->format.info.dsp);
+ if (data->format.info.dsp.format != SPA_VIDEO_FORMAT_DSP_F32)
+ return;
+ sdl_format = SDL_PIXELFORMAT_RGBA32;
+ data->size = SPA_RECTANGLE(data->position->video.size.width,
+ data->position->video.size.height);
+ mult = 4;
+ break;
+ default:
+ sdl_format = SDL_PIXELFORMAT_UNKNOWN;
+ break;
+ }
+
+ if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) {
+ pw_stream_set_error(stream, -EINVAL, "unknown pixel format");
+ return;
+ }
+ if (data->size.width == 0 || data->size.height == 0) {
+ pw_stream_set_error(stream, -EINVAL, "invalid size");
+ return;
+ }
+
+ data->texture = SDL_CreateTexture(data->renderer,
+ sdl_format,
+ SDL_TEXTUREACCESS_STREAMING,
+ data->size.width,
+ data->size.height);
+ SDL_LockTexture(data->texture, NULL, &d, &data->stride);
+ SDL_UnlockTexture(data->texture);
+
+ switch(sdl_format) {
+ case SDL_PIXELFORMAT_YV12:
+ case SDL_PIXELFORMAT_IYUV:
+ size = (data->stride * data->size.height) * 3 / 2;
+ data->is_yuv = true;
+ break;
+ default:
+ size = data->stride * data->size.height;
+ break;
+ }
+
+ data->rect.x = 0;
+ data->rect.y = 0;
+ data->rect.w = data->size.width;
+ data->rect.h = data->size.height;
+
+ /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size,
+ * number, stride etc of the buffers */
+ params[0] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(size * mult),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride * mult),
+ SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<<SPA_DATA_MemPtr)));
+
+ /* a header metadata with timing information */
+ params[1] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)));
+ /* video cropping information */
+ params[2] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region)));
+#define CURSOR_META_SIZE(w,h) (sizeof(struct spa_meta_cursor) + \
+ sizeof(struct spa_meta_bitmap) + w * h * 4)
+ /* cursor information */
+ params[3] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor),
+ SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int(
+ CURSOR_META_SIZE(64,64),
+ CURSOR_META_SIZE(1,1),
+ CURSOR_META_SIZE(256,256)));
+
+ /* we are done */
+ pw_stream_update_params(stream, params, 4);
+}
+
+/* these are the stream events we listen for */
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .state_changed = on_stream_state_changed,
+ .io_changed = on_stream_io_changed,
+ .param_changed = on_stream_param_changed,
+ .process = on_process,
+ .trigger_done = on_trigger_done,
+ .command = on_command,
+};
+
+static int build_format(struct data *data, struct spa_pod_builder *b, const struct spa_pod **params)
+{
+ SDL_RendererInfo info;
+
+ SDL_GetRendererInfo(data->renderer, &info);
+ params[0] = sdl_build_formats(&info, b);
+
+ fprintf(stderr, "supported SDL formats:\n");
+ spa_debug_format(2, NULL, params[0]);
+
+ params[1] = spa_pod_builder_add_object(b,
+ SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
+ SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp),
+ SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_DSP_F32));
+
+ fprintf(stderr, "supported DSP formats:\n");
+ spa_debug_format(2, NULL, params[1]);
+
+ return 2;
+}
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_main_loop_quit(data->loop);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ const struct spa_pod *params[2];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ struct pw_properties *props;
+ int res, n_params;
+
+ pw_init(&argc, &argv);
+
+ /* create a main loop */
+ data.loop = pw_main_loop_new(NULL);
+
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data);
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data);
+
+ /* the timer to pull in data */
+ data.timer = pw_loop_add_timer(pw_main_loop_get_loop(data.loop), on_timeout, &data);
+
+ /* create a simple stream, the simple stream manages to core and remote
+ * objects for you if you don't need to deal with them
+ *
+ * If you plan to autoconnect your stream, you need to provide at least
+ * media, category and role properties
+ *
+ * Pass your events and a user_data pointer as the last arguments. This
+ * will inform you about the stream state. The most important event
+ * you need to listen to is the process event where you need to consume
+ * the data provided to you.
+ */
+ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video",
+ PW_KEY_MEDIA_CATEGORY, "Capture",
+ PW_KEY_MEDIA_ROLE, "Camera",
+ PW_KEY_PRIORITY_DRIVER, "10000",
+ NULL),
+ data.path = argc > 1 ? argv[1] : NULL;
+ if (data.path)
+ /* Set stream target if given on command line */
+ pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path);
+
+ data.stream = pw_stream_new_simple(
+ pw_main_loop_get_loop(data.loop),
+ "video-play",
+ props,
+ &stream_events,
+ &data);
+
+ if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ if (SDL_CreateWindowAndRenderer
+ (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) {
+ fprintf(stderr, "can't create window: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ /* build the extra parameters to connect with. To connect, we can provide
+ * a list of supported formats. We use a builder that writes the param
+ * object to the stack. */
+ n_params = build_format(&data, &b, params);
+
+ /* now connect the stream, we need a direction (input/output),
+ * an optional target node to connect to, some flags and parameters
+ */
+ if ((res = pw_stream_connect(data.stream,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_DRIVER | /* we're driver, we pull */
+ PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */
+ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */
+ params, n_params)) /* extra parameters, see above */ < 0) {
+ fprintf(stderr, "can't connect: %s\n", spa_strerror(res));
+ return -1;
+ }
+
+ /* do things until we quit the mainloop */
+ pw_main_loop_run(data.loop);
+
+ pw_stream_destroy(data.stream);
+ pw_main_loop_destroy(data.loop);
+
+ SDL_DestroyTexture(data.texture);
+ if (data.cursor)
+ SDL_DestroyTexture(data.cursor);
+ SDL_DestroyRenderer(data.renderer);
+ SDL_DestroyWindow(data.window);
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/examples/video-play-reneg.c b/src/examples/video-play-reneg.c
new file mode 100644
index 0000000..26b19cb
--- /dev/null
+++ b/src/examples/video-play-reneg.c
@@ -0,0 +1,438 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Video input stream using \ref pw_stream "pw_stream", with format renegotiation.
+ [title]
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <spa/utils/result.h>
+#include <spa/param/video/format-utils.h>
+#include <spa/param/props.h>
+#include <spa/debug/format.h>
+
+#include <pipewire/pipewire.h>
+
+#define WIDTH 640
+#define HEIGHT 480
+
+#define MAX_BUFFERS 64
+
+#include "sdl.h"
+
+struct pixel {
+ float r, g, b, a;
+};
+
+struct data {
+ const char *path;
+
+ SDL_Renderer *renderer;
+ SDL_Window *window;
+ SDL_Texture *texture;
+ SDL_Texture *cursor;
+
+ struct pw_main_loop *loop;
+ struct spa_source *timer;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ struct spa_video_info format;
+ int32_t stride;
+ struct spa_rectangle size;
+
+ int counter;
+};
+
+static void handle_events(struct data *data)
+{
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_QUIT:
+ pw_main_loop_quit(data->loop);
+ break;
+ }
+ }
+}
+
+/* our data processing function is in general:
+ *
+ * struct pw_buffer *b;
+ * b = pw_stream_dequeue_buffer(stream);
+ *
+ * .. do stuff with buffer ...
+ *
+ * pw_stream_queue_buffer(stream, b);
+ */
+static void
+on_process(void *_data)
+{
+ struct data *data = _data;
+ struct pw_stream *stream = data->stream;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ void *sdata, *ddata;
+ int sstride, dstride, ostride;
+ uint32_t i;
+ uint8_t *src, *dst;
+
+ b = NULL;
+ /* dequeue and queue old buffers, use the last available
+ * buffer */
+ while (true) {
+ struct pw_buffer *t;
+ if ((t = pw_stream_dequeue_buffer(stream)) == NULL)
+ break;
+ if (b)
+ pw_stream_queue_buffer(stream, b);
+ b = t;
+ }
+ if (b == NULL) {
+ pw_log_warn("out of buffers: %m");
+ return;
+ }
+
+ buf = b->buffer;
+
+ pw_log_info("new buffer %p", buf);
+
+ handle_events(data);
+
+ if ((sdata = buf->datas[0].data) == NULL)
+ goto done;
+
+ if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) {
+ fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError());
+ goto done;
+ }
+
+ /* copy video image in texture */
+ sstride = buf->datas[0].chunk->stride;
+ if (sstride == 0)
+ sstride = buf->datas[0].chunk->size / data->size.height;
+ ostride = SPA_MIN(sstride, dstride);
+
+ src = sdata;
+ dst = ddata;
+
+ for (i = 0; i < data->size.height; i++) {
+ memcpy(dst, src, ostride);
+ src += sstride;
+ dst += dstride;
+ }
+ SDL_UnlockTexture(data->texture);
+
+ SDL_RenderClear(data->renderer);
+ /* now render the video */
+ SDL_RenderCopy(data->renderer, data->texture, NULL, NULL);
+ SDL_RenderPresent(data->renderer);
+
+ done:
+ pw_stream_queue_buffer(stream, b);
+}
+
+static void on_stream_state_changed(void *_data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct data *data = _data;
+ fprintf(stderr, "stream state: \"%s\"\n", pw_stream_state_as_string(state));
+ switch (state) {
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_main_loop_quit(data->loop);
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ pw_loop_update_timer(pw_main_loop_get_loop(data->loop),
+ data->timer, NULL, NULL, false);
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ {
+ struct timespec timeout, interval;
+
+ timeout.tv_sec = 1;
+ timeout.tv_nsec = 0;
+ interval.tv_sec = 1;
+ interval.tv_nsec = 0;
+
+ pw_loop_update_timer(pw_main_loop_get_loop(data->loop),
+ data->timer, &timeout, &interval, false);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+/* Be notified when the stream param changes. We're only looking at the
+ * format changes.
+ *
+ * We are now supposed to call pw_stream_finish_format() with success or
+ * failure, depending on if we can support the format. Because we gave
+ * a list of supported formats, this should be ok.
+ *
+ * As part of pw_stream_finish_format() we can provide parameters that
+ * will control the buffer memory allocation. This includes the metadata
+ * that we would like on our buffer, the size, alignment, etc.
+ */
+static void
+on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param)
+{
+ struct data *data = _data;
+ struct pw_stream *stream = data->stream;
+ uint8_t params_buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
+ const struct spa_pod *params[1];
+ Uint32 sdl_format;
+ void *d;
+
+ /* NULL means to clear the format */
+ if (param == NULL || id != SPA_PARAM_Format)
+ return;
+
+ fprintf(stderr, "got format:\n");
+ spa_debug_format(2, NULL, param);
+
+ if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0)
+ return;
+
+ if (data->format.media_type != SPA_MEDIA_TYPE_video ||
+ data->format.media_subtype != SPA_MEDIA_SUBTYPE_raw)
+ return;
+
+ /* call a helper function to parse the format for us. */
+ spa_format_video_raw_parse(param, &data->format.info.raw);
+ sdl_format = id_to_sdl_format(data->format.info.raw.format);
+ data->size = data->format.info.raw.size;
+
+ if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) {
+ pw_stream_set_error(stream, -EINVAL, "unknown pixel format");
+ return;
+ }
+ if (data->size.width == 0 || data->size.height == 0) {
+ pw_stream_set_error(stream, -EINVAL, "invalid size");
+ return;
+ }
+
+ data->texture = SDL_CreateTexture(data->renderer,
+ sdl_format,
+ SDL_TEXTUREACCESS_STREAMING,
+ data->size.width,
+ data->size.height);
+ SDL_LockTexture(data->texture, NULL, &d, &data->stride);
+ SDL_UnlockTexture(data->texture);
+
+ /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size,
+ * number, stride etc of the buffers */
+ params[0] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->size.height),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride),
+ SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<<SPA_DATA_MemPtr)));
+
+ /* we are done */
+ pw_stream_update_params(stream, params, 1);
+}
+
+/* these are the stream events we listen for */
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .state_changed = on_stream_state_changed,
+ .param_changed = on_stream_param_changed,
+ .process = on_process,
+};
+
+static int build_format(struct data *data, struct spa_pod_builder *b, const struct spa_pod **params)
+{
+ SDL_RendererInfo info;
+
+ SDL_GetRendererInfo(data->renderer, &info);
+ params[0] = sdl_build_formats(&info, b);
+
+ fprintf(stderr, "supported SDL formats:\n");
+ spa_debug_format(2, NULL, params[0]);
+
+ return 1;
+}
+
+static int reneg_format(struct data *data)
+{
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ const struct spa_pod *params[2];
+ int32_t width, height;
+
+ if (data->format.info.raw.format == 0)
+ return -EBUSY;
+
+ width = data->counter & 1 ? 320 : 640;
+ height = data->counter & 1 ? 240 : 480;
+
+ fprintf(stderr, "renegotiate to %dx%d:\n", width, height);
+ params[0] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
+ 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_Id(data->format.info.raw.format),
+ SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&data->format.info.raw.framerate));
+
+ pw_stream_update_params(data->stream, params, 1);
+
+ data->counter++;
+ return 0;
+}
+
+static int reneg_buffers(struct data *data)
+{
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ const struct spa_pod *params[2];
+
+ fprintf(stderr, "renegotiate buffers\n");
+ params[0] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->size.height),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride));
+
+ pw_stream_update_params(data->stream, params, 1);
+
+ data->counter++;
+ return 0;
+}
+
+static void on_timeout(void *userdata, uint64_t expirations)
+{
+ struct data *data = userdata;
+ if (1)
+ reneg_format(data);
+ else
+ reneg_buffers(data);
+}
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_main_loop_quit(data->loop);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ const struct spa_pod *params[2];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ struct pw_properties *props;
+ int res, n_params;
+
+ pw_init(&argc, &argv);
+
+ /* create a main loop */
+ data.loop = pw_main_loop_new(NULL);
+
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data);
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data);
+
+ /* create a simple stream, the simple stream manages to core and remote
+ * objects for you if you don't need to deal with them
+ *
+ * If you plan to autoconnect your stream, you need to provide at least
+ * media, category and role properties
+ *
+ * Pass your events and a user_data pointer as the last arguments. This
+ * will inform you about the stream state. The most important event
+ * you need to listen to is the process event where you need to consume
+ * the data provided to you.
+ */
+ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video",
+ PW_KEY_MEDIA_CATEGORY, "Capture",
+ PW_KEY_MEDIA_ROLE, "Camera",
+ NULL),
+ data.path = argc > 1 ? argv[1] : NULL;
+ if (data.path)
+ /* Set stream target if given on command line */
+ pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path);
+
+ data.stream = pw_stream_new_simple(
+ pw_main_loop_get_loop(data.loop),
+ "video-play-reneg",
+ props,
+ &stream_events,
+ &data);
+
+ if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ if (SDL_CreateWindowAndRenderer
+ (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) {
+ fprintf(stderr, "can't create window: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ /* build the extra parameters to connect with. To connect, we can provide
+ * a list of supported formats. We use a builder that writes the param
+ * object to the stack. */
+ n_params = build_format(&data, &b, params);
+
+ /* now connect the stream, we need a direction (input/output),
+ * an optional target node to connect to, some flags and parameters
+ */
+ if ((res = pw_stream_connect(data.stream,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */
+ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */
+ params, n_params)) /* extra parameters, see above */ < 0) {
+ fprintf(stderr, "can't connect: %s\n", spa_strerror(res));
+ return -1;
+ }
+
+ data.timer = pw_loop_add_timer(pw_main_loop_get_loop(data.loop), on_timeout, &data);
+
+ /* do things until we quit the mainloop */
+ pw_main_loop_run(data.loop);
+
+ pw_stream_destroy(data.stream);
+ pw_main_loop_destroy(data.loop);
+
+ SDL_DestroyTexture(data.texture);
+ if (data.cursor)
+ SDL_DestroyTexture(data.cursor);
+ SDL_DestroyRenderer(data.renderer);
+ SDL_DestroyWindow(data.window);
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/examples/video-play.c b/src/examples/video-play.c
new file mode 100644
index 0000000..9cbbab6
--- /dev/null
+++ b/src/examples/video-play.c
@@ -0,0 +1,529 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Video input stream using \ref pw_stream "pw_stream".
+ [title]
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+
+#include <spa/utils/result.h>
+#include <spa/param/video/format-utils.h>
+#include <spa/param/props.h>
+#include <spa/debug/format.h>
+
+#include <pipewire/pipewire.h>
+
+#define WIDTH 640
+#define HEIGHT 480
+
+#define MAX_BUFFERS 64
+
+#include "sdl.h"
+
+struct pixel {
+ float r, g, b, a;
+};
+
+struct data {
+ const char *path;
+
+ SDL_Renderer *renderer;
+ SDL_Window *window;
+ SDL_Texture *texture;
+ SDL_Texture *cursor;
+
+ struct pw_main_loop *loop;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ struct spa_io_position *position;
+
+ struct spa_video_info format;
+ int32_t stride;
+ struct spa_rectangle size;
+
+ int counter;
+ SDL_Rect rect;
+ SDL_Rect cursor_rect;
+ bool is_yuv;
+};
+
+static void handle_events(struct data *data)
+{
+ SDL_Event event;
+ while (SDL_PollEvent(&event)) {
+ switch (event.type) {
+ case SDL_QUIT:
+ pw_main_loop_quit(data->loop);
+ break;
+ }
+ }
+}
+
+/* our data processing function is in general:
+ *
+ * struct pw_buffer *b;
+ * b = pw_stream_dequeue_buffer(stream);
+ *
+ * .. do stuff with buffer ...
+ *
+ * pw_stream_queue_buffer(stream, b);
+ */
+static void
+on_process(void *_data)
+{
+ struct data *data = _data;
+ struct pw_stream *stream = data->stream;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ void *sdata, *ddata;
+ int sstride, dstride, ostride;
+ struct spa_meta_region *mc;
+ struct spa_meta_cursor *mcs;
+ uint32_t i, j;
+ uint8_t *src, *dst;
+ bool render_cursor = false;
+
+ b = NULL;
+ while (true) {
+ struct pw_buffer *t;
+ if ((t = pw_stream_dequeue_buffer(stream)) == NULL)
+ break;
+ if (b)
+ pw_stream_queue_buffer(stream, b);
+ b = t;
+ }
+ if (b == NULL) {
+ pw_log_warn("out of buffers: %m");
+ return;
+ }
+
+ buf = b->buffer;
+
+ pw_log_trace("new buffer %p", buf);
+
+ handle_events(data);
+
+ if ((sdata = buf->datas[0].data) == NULL)
+ goto done;
+
+ /* get the videocrop metadata if any */
+ if ((mc = spa_buffer_find_meta_data(buf, SPA_META_VideoCrop, sizeof(*mc))) &&
+ spa_meta_region_is_valid(mc)) {
+ data->rect.x = mc->region.position.x;
+ data->rect.y = mc->region.position.y;
+ data->rect.w = mc->region.size.width;
+ data->rect.h = mc->region.size.height;
+ }
+ /* get cursor metadata */
+ if ((mcs = spa_buffer_find_meta_data(buf, SPA_META_Cursor, sizeof(*mcs))) &&
+ spa_meta_cursor_is_valid(mcs)) {
+ struct spa_meta_bitmap *mb;
+ void *cdata;
+ int cstride;
+
+ data->cursor_rect.x = mcs->position.x;
+ data->cursor_rect.y = mcs->position.y;
+
+ mb = SPA_PTROFF(mcs, mcs->bitmap_offset, struct spa_meta_bitmap);
+ data->cursor_rect.w = mb->size.width;
+ data->cursor_rect.h = mb->size.height;
+
+ if (data->cursor == NULL) {
+ data->cursor = SDL_CreateTexture(data->renderer,
+ id_to_sdl_format(mb->format),
+ SDL_TEXTUREACCESS_STREAMING,
+ mb->size.width, mb->size.height);
+ SDL_SetTextureBlendMode(data->cursor, SDL_BLENDMODE_BLEND);
+ }
+
+
+ if (SDL_LockTexture(data->cursor, NULL, &cdata, &cstride) < 0) {
+ fprintf(stderr, "Couldn't lock cursor texture: %s\n", SDL_GetError());
+ goto done;
+ }
+
+ /* copy the cursor bitmap into the texture */
+ src = SPA_PTROFF(mb, mb->offset, uint8_t);
+ dst = cdata;
+ ostride = SPA_MIN(cstride, mb->stride);
+
+ for (i = 0; i < mb->size.height; i++) {
+ memcpy(dst, src, ostride);
+ dst += cstride;
+ src += mb->stride;
+ }
+ SDL_UnlockTexture(data->cursor);
+
+ render_cursor = true;
+ }
+
+ /* copy video image in texture */
+ if (data->is_yuv) {
+ sstride = data->stride;
+ SDL_UpdateYUVTexture(data->texture,
+ NULL,
+ sdata,
+ sstride,
+ SPA_PTROFF(sdata, sstride * data->size.height, void),
+ sstride / 2,
+ SPA_PTROFF(sdata, 5 * (sstride * data->size.height) / 4, void),
+ sstride / 2);
+ }
+ else {
+ if (SDL_LockTexture(data->texture, NULL, &ddata, &dstride) < 0) {
+ fprintf(stderr, "Couldn't lock texture: %s\n", SDL_GetError());
+ goto done;
+ }
+
+ sstride = buf->datas[0].chunk->stride;
+ if (sstride == 0)
+ sstride = buf->datas[0].chunk->size / data->size.height;
+ ostride = SPA_MIN(sstride, dstride);
+
+ src = sdata;
+ dst = ddata;
+
+ if (data->format.media_subtype == SPA_MEDIA_SUBTYPE_dsp) {
+ for (i = 0; i < data->size.height; i++) {
+ struct pixel *p = (struct pixel *) src;
+ for (j = 0; j < data->size.width; j++) {
+ dst[j * 4 + 0] = SPA_CLAMP(p[j].r * 255.0f, 0, 255);
+ dst[j * 4 + 1] = SPA_CLAMP(p[j].g * 255.0f, 0, 255);
+ dst[j * 4 + 2] = SPA_CLAMP(p[j].b * 255.0f, 0, 255);
+ dst[j * 4 + 3] = SPA_CLAMP(p[j].a * 255.0f, 0, 255);
+ }
+ src += sstride;
+ dst += dstride;
+ }
+ } else {
+ for (i = 0; i < data->size.height; i++) {
+ memcpy(dst, src, ostride);
+ src += sstride;
+ dst += dstride;
+ }
+ }
+ SDL_UnlockTexture(data->texture);
+ }
+
+ SDL_RenderClear(data->renderer);
+ /* now render the video and then the cursor if any */
+ SDL_RenderCopy(data->renderer, data->texture, &data->rect, NULL);
+ if (render_cursor) {
+ SDL_RenderCopy(data->renderer, data->cursor, NULL, &data->cursor_rect);
+ }
+ SDL_RenderPresent(data->renderer);
+
+ done:
+ pw_stream_queue_buffer(stream, b);
+}
+
+static void on_stream_state_changed(void *_data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct data *data = _data;
+ fprintf(stderr, "stream state: \"%s\"\n", pw_stream_state_as_string(state));
+ switch (state) {
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_main_loop_quit(data->loop);
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ /* because we started inactive, activate ourselves now */
+ pw_stream_set_active(data->stream, true);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+on_stream_io_changed(void *_data, uint32_t id, void *area, uint32_t size)
+{
+ struct data *data = _data;
+
+ switch (id) {
+ case SPA_IO_Position:
+ data->position = area;
+ break;
+ }
+}
+
+/* Be notified when the stream param changes. We're only looking at the
+ * format changes.
+ *
+ * We are now supposed to call pw_stream_finish_format() with success or
+ * failure, depending on if we can support the format. Because we gave
+ * a list of supported formats, this should be ok.
+ *
+ * As part of pw_stream_finish_format() we can provide parameters that
+ * will control the buffer memory allocation. This includes the metadata
+ * that we would like on our buffer, the size, alignment, etc.
+ */
+static void
+on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param)
+{
+ struct data *data = _data;
+ struct pw_stream *stream = data->stream;
+ uint8_t params_buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
+ const struct spa_pod *params[5];
+ Uint32 sdl_format;
+ void *d;
+ int32_t mult, size;
+
+ /* NULL means to clear the format */
+ if (param == NULL || id != SPA_PARAM_Format)
+ return;
+
+ fprintf(stderr, "got format:\n");
+ spa_debug_format(2, NULL, param);
+
+ if (spa_format_parse(param, &data->format.media_type, &data->format.media_subtype) < 0)
+ return;
+
+ if (data->format.media_type != SPA_MEDIA_TYPE_video)
+ return;
+
+ switch (data->format.media_subtype) {
+ case SPA_MEDIA_SUBTYPE_raw:
+ /* call a helper function to parse the format for us. */
+ spa_format_video_raw_parse(param, &data->format.info.raw);
+ sdl_format = id_to_sdl_format(data->format.info.raw.format);
+ data->size = SPA_RECTANGLE(data->format.info.raw.size.width,
+ data->format.info.raw.size.height);
+ mult = 1;
+ break;
+ case SPA_MEDIA_SUBTYPE_dsp:
+ spa_format_video_dsp_parse(param, &data->format.info.dsp);
+ if (data->format.info.dsp.format != SPA_VIDEO_FORMAT_DSP_F32)
+ return;
+ sdl_format = SDL_PIXELFORMAT_RGBA32;
+ data->size = SPA_RECTANGLE(data->position->video.size.width,
+ data->position->video.size.height);
+ mult = 4;
+ break;
+ default:
+ sdl_format = SDL_PIXELFORMAT_UNKNOWN;
+ break;
+ }
+
+ if (sdl_format == SDL_PIXELFORMAT_UNKNOWN) {
+ pw_stream_set_error(stream, -EINVAL, "unknown pixel format");
+ return;
+ }
+ if (data->size.width == 0 || data->size.height == 0) {
+ pw_stream_set_error(stream, -EINVAL, "invalid size");
+ return;
+ }
+
+ data->texture = SDL_CreateTexture(data->renderer,
+ sdl_format,
+ SDL_TEXTUREACCESS_STREAMING,
+ data->size.width,
+ data->size.height);
+ SDL_LockTexture(data->texture, NULL, &d, &data->stride);
+ SDL_UnlockTexture(data->texture);
+
+ switch(sdl_format) {
+ case SDL_PIXELFORMAT_YV12:
+ case SDL_PIXELFORMAT_IYUV:
+ size = (data->stride * data->size.height) * 3 / 2;
+ data->is_yuv = true;
+ break;
+ default:
+ size = data->stride * data->size.height;
+ break;
+ }
+
+ data->rect.x = 0;
+ data->rect.y = 0;
+ data->rect.w = data->size.width;
+ data->rect.h = data->size.height;
+
+ /* a SPA_TYPE_OBJECT_ParamBuffers object defines the acceptable size,
+ * number, stride etc of the buffers */
+ params[0] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(size * mult),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride * mult),
+ SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int((1<<SPA_DATA_MemPtr)));
+
+ /* a header metadata with timing information */
+ params[1] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)));
+ /* video cropping information */
+ params[2] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region)));
+#define CURSOR_META_SIZE(w,h) (sizeof(struct spa_meta_cursor) + \
+ sizeof(struct spa_meta_bitmap) + w * h * 4)
+ /* cursor information */
+ params[3] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor),
+ SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int(
+ CURSOR_META_SIZE(64,64),
+ CURSOR_META_SIZE(1,1),
+ CURSOR_META_SIZE(256,256)));
+
+ /* we are done */
+ pw_stream_update_params(stream, params, 4);
+}
+
+/* these are the stream events we listen for */
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .state_changed = on_stream_state_changed,
+ .io_changed = on_stream_io_changed,
+ .param_changed = on_stream_param_changed,
+ .process = on_process,
+};
+
+static int build_format(struct data *data, struct spa_pod_builder *b, const struct spa_pod **params)
+{
+ SDL_RendererInfo info;
+
+ SDL_GetRendererInfo(data->renderer, &info);
+ params[0] = sdl_build_formats(&info, b);
+
+ fprintf(stderr, "supported SDL formats:\n");
+ spa_debug_format(2, NULL, params[0]);
+
+ params[1] = spa_pod_builder_add_object(b,
+ SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
+ SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp),
+ SPA_FORMAT_VIDEO_format, SPA_POD_Id(SPA_VIDEO_FORMAT_DSP_F32));
+
+ fprintf(stderr, "supported DSP formats:\n");
+ spa_debug_format(2, NULL, params[1]);
+
+ return 2;
+}
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_main_loop_quit(data->loop);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ const struct spa_pod *params[2];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ struct pw_properties *props;
+ int res, n_params;
+
+ pw_init(&argc, &argv);
+
+ /* create a main loop */
+ data.loop = pw_main_loop_new(NULL);
+
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data);
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data);
+
+ /* create a simple stream, the simple stream manages to core and remote
+ * objects for you if you don't need to deal with them
+ *
+ * If you plan to autoconnect your stream, you need to provide at least
+ * media, category and role properties
+ *
+ * Pass your events and a user_data pointer as the last arguments. This
+ * will inform you about the stream state. The most important event
+ * you need to listen to is the process event where you need to consume
+ * the data provided to you.
+ */
+ props = pw_properties_new(PW_KEY_MEDIA_TYPE, "Video",
+ PW_KEY_MEDIA_CATEGORY, "Capture",
+ PW_KEY_MEDIA_ROLE, "Camera",
+ NULL),
+ data.path = argc > 1 ? argv[1] : NULL;
+ if (data.path)
+ pw_properties_set(props, PW_KEY_TARGET_OBJECT, data.path);
+
+ data.stream = pw_stream_new_simple(
+ pw_main_loop_get_loop(data.loop),
+ "video-play",
+ props,
+ &stream_events,
+ &data);
+
+ if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ fprintf(stderr, "can't initialize SDL: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ if (SDL_CreateWindowAndRenderer
+ (WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &data.window, &data.renderer)) {
+ fprintf(stderr, "can't create window: %s\n", SDL_GetError());
+ return -1;
+ }
+
+ /* build the extra parameters to connect with. To connect, we can provide
+ * a list of supported formats. We use a builder that writes the param
+ * object to the stack. */
+ n_params = build_format(&data, &b, params);
+
+ /* now connect the stream, we need a direction (input/output),
+ * an optional target node to connect to, some flags and parameters
+ */
+ if ((res = pw_stream_connect(data.stream,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT | /* try to automatically connect this stream */
+ PW_STREAM_FLAG_INACTIVE | /* we will activate ourselves */
+ PW_STREAM_FLAG_MAP_BUFFERS, /* mmap the buffer data for us */
+ params, n_params)) /* extra parameters, see above */ < 0) {
+ fprintf(stderr, "can't connect: %s\n", spa_strerror(res));
+ return -1;
+ }
+
+ /* do things until we quit the mainloop */
+ pw_main_loop_run(data.loop);
+
+ pw_stream_destroy(data.stream);
+ pw_main_loop_destroy(data.loop);
+
+ SDL_DestroyTexture(data.texture);
+ if (data.cursor)
+ SDL_DestroyTexture(data.cursor);
+ SDL_DestroyRenderer(data.renderer);
+ SDL_DestroyWindow(data.window);
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/examples/video-src-alloc.c b/src/examples/video-src-alloc.c
new file mode 100644
index 0000000..ef364fd
--- /dev/null
+++ b/src/examples/video-src-alloc.c
@@ -0,0 +1,464 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Allocating buffer memory and sending fds to the server.
+ [title]
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <signal.h>
+#include <math.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include <spa/param/video/format-utils.h>
+
+#include <pipewire/pipewire.h>
+
+#define BPP 3
+#define CURSOR_WIDTH 64
+#define CURSOR_HEIGHT 64
+#define CURSOR_BPP 4
+
+#define MAX_BUFFERS 64
+
+#define M_PI_M2 ( M_PI + M_PI )
+
+struct data {
+ struct pw_thread_loop *loop;
+ struct spa_source *timer;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ struct spa_video_info_raw format;
+ int32_t stride;
+
+ int counter;
+ uint32_t seq;
+
+ double crop;
+ double accumulator;
+};
+
+static void draw_elipse(uint32_t *dst, int width, int height, uint32_t color)
+{
+ int i, j, r1, r2, r12, r22, r122;
+
+ r1 = width/2;
+ r12 = r1 * r1;
+ r2 = height/2;
+ r22 = r2 * r2;
+ r122 = r12 * r22;
+
+ for (i = -r2; i < r2; i++) {
+ for (j = -r1; j < r1; j++) {
+ dst[(i + r2)*width+(j+r1)] =
+ (i * i * r12 + j * j * r22 <= r122) ? color : 0x00000000;
+ }
+ }
+}
+
+/* called when we should push a new buffer in the queue */
+static void on_process(void *userdata)
+{
+ struct data *data = userdata;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ uint32_t i, j;
+ uint8_t *p;
+ struct spa_meta *m;
+ struct spa_meta_header *h;
+ struct spa_meta_region *mc;
+ struct spa_meta_cursor *mcs;
+
+ 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;
+
+ if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)))) {
+#if 0
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ h->pts = SPA_TIMESPEC_TO_NSEC(&now);
+#else
+ h->pts = -1;
+#endif
+ h->flags = 0;
+ h->seq = data->seq++;
+ h->dts_offset = 0;
+ }
+ if ((m = spa_buffer_find_meta(buf, SPA_META_VideoDamage))) {
+ struct spa_meta_region *r = spa_meta_first(m);
+
+ if (spa_meta_check(r, m)) {
+ r->region.position = SPA_POINT(0,0);
+ r->region.size = data->format.size;
+ r++;
+ }
+ if (spa_meta_check(r, m))
+ r->region = SPA_REGION(0,0,0,0);
+ }
+ if ((mc = spa_buffer_find_meta_data(buf, SPA_META_VideoCrop, sizeof(*mc)))) {
+ data->crop = (sin(data->accumulator) + 1.0) * 32.0;
+ mc->region.position.x = data->crop;
+ mc->region.position.y = data->crop;
+ mc->region.size.width = data->format.size.width - data->crop*2;
+ mc->region.size.height = data->format.size.height - data->crop*2;
+ }
+ if ((mcs = spa_buffer_find_meta_data(buf, SPA_META_Cursor, sizeof(*mcs)))) {
+ struct spa_meta_bitmap *mb;
+ uint32_t *bitmap, color;
+
+ mcs->id = 1;
+ mcs->position.x = (sin(data->accumulator) + 1.0) * 160.0 + 80;
+ mcs->position.y = (cos(data->accumulator) + 1.0) * 100.0 + 50;
+ mcs->hotspot.x = 0;
+ mcs->hotspot.y = 0;
+ mcs->bitmap_offset = sizeof(struct spa_meta_cursor);
+
+ mb = SPA_PTROFF(mcs, mcs->bitmap_offset, struct spa_meta_bitmap);
+ mb->format = SPA_VIDEO_FORMAT_ARGB;
+ mb->size.width = CURSOR_WIDTH;
+ mb->size.height = CURSOR_HEIGHT;
+ mb->stride = CURSOR_WIDTH * CURSOR_BPP;
+ mb->offset = sizeof(struct spa_meta_bitmap);
+
+ bitmap = SPA_PTROFF(mb, mb->offset, uint32_t);
+ color = (cos(data->accumulator) + 1.0) * (1 << 23);
+ color |= 0xff000000;
+
+ draw_elipse(bitmap, mb->size.width, mb->size.height, color);
+ }
+
+ for (i = 0; i < data->format.size.height; i++) {
+ for (j = 0; j < data->format.size.width * BPP; j++) {
+ p[j] = data->counter + j * i;
+ }
+ p += data->stride;
+ data->counter += 13;
+ }
+
+ data->accumulator += M_PI_M2 / 50.0;
+ if (data->accumulator >= M_PI_M2)
+ data->accumulator -= M_PI_M2;
+
+ buf->datas[0].chunk->offset = 0;
+ buf->datas[0].chunk->size = data->format.size.height * data->stride;
+ buf->datas[0].chunk->stride = data->stride;
+
+ pw_stream_queue_buffer(data->stream, b);
+}
+
+/* trigger the graph when we are a driver */
+static void on_timeout(void *userdata, uint64_t expirations)
+{
+ struct data *data = userdata;
+ pw_log_trace("timeout");
+ pw_stream_trigger_process(data->stream);
+}
+
+/* when the stream is STREAMING, start the timer at 40ms intervals
+ * to produce and push a frame. In other states we PAUSE the timer. */
+static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state,
+ const char *error)
+{
+ struct data *data = _data;
+
+ printf("stream state: \"%s\"\n", pw_stream_state_as_string(state));
+
+ switch (state) {
+ case PW_STREAM_STATE_PAUSED:
+ printf("node id: %d\n", pw_stream_get_node_id(data->stream));
+ pw_loop_update_timer(pw_thread_loop_get_loop(data->loop),
+ data->timer, NULL, NULL, false);
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ {
+ struct timespec timeout, interval;
+
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 1;
+ interval.tv_sec = 0;
+ interval.tv_nsec = 40 * SPA_NSEC_PER_MSEC;
+
+ if (pw_stream_is_driving(data->stream))
+ pw_loop_update_timer(pw_thread_loop_get_loop(data->loop),
+ data->timer, &timeout, &interval, false);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+/* we set the PW_STREAM_FLAG_ALLOC_BUFFERS flag when connecting so we need
+ * to provide buffer memory. */
+static void on_stream_add_buffer(void *_data, struct pw_buffer *buffer)
+{
+ struct data *data = _data;
+ struct spa_buffer *buf = buffer->buffer;
+ struct spa_data *d;
+#ifdef HAVE_MEMFD_CREATE
+ unsigned int seals;
+#endif
+
+ pw_log_info("add buffer %p", buffer);
+ d = buf->datas;
+
+ if ((d[0].type & (1<<SPA_DATA_MemFd)) == 0) {
+ pw_log_error("unsupported data type %08x", d[0].type);
+ return;
+ }
+
+ /* create the memfd on the buffer, set the type and flags */
+ d[0].type = SPA_DATA_MemFd;
+ d[0].flags = SPA_DATA_FLAG_READWRITE;
+#ifdef HAVE_MEMFD_CREATE
+ d[0].fd = memfd_create("video-src-memfd", MFD_CLOEXEC | MFD_ALLOW_SEALING);
+#else
+ d[0].fd = -1;
+#endif
+ if (d[0].fd == -1) {
+ pw_log_error("can't create memfd: %m");
+ return;
+ }
+ d[0].mapoffset = 0;
+ d[0].maxsize = data->stride * data->format.size.height;
+
+ /* truncate to the right size before we set seals */
+ if (ftruncate(d[0].fd, d[0].maxsize) < 0) {
+ pw_log_error("can't truncate to %d: %m", d[0].maxsize);
+ return;
+ }
+#ifdef HAVE_MEMFD_CREATE
+ /* not enforced yet but server might require SEAL_SHRINK later */
+ seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL;
+ if (fcntl(d[0].fd, F_ADD_SEALS, seals) == -1) {
+ pw_log_warn("Failed to add seals: %m");
+ }
+#endif
+
+ /* now mmap so we can write to it in the process function above */
+ d[0].data = mmap(NULL, d[0].maxsize, PROT_READ|PROT_WRITE,
+ MAP_SHARED, d[0].fd, d[0].mapoffset);
+ if (d[0].data == MAP_FAILED) {
+ pw_log_error("can't mmap memory: %m");
+ return;
+ }
+}
+
+/* close the memfd we set on the buffers here */
+static void on_stream_remove_buffer(void *_data, struct pw_buffer *buffer)
+{
+ struct spa_buffer *buf = buffer->buffer;
+ struct spa_data *d;
+
+ d = buf->datas;
+ pw_log_info("remove buffer %p", buffer);
+
+ munmap(d[0].data, d[0].maxsize);
+ close(d[0].fd);
+}
+
+/* Be notified when the stream param changes. We're only looking at the
+ * format param.
+ *
+ * We are now supposed to call pw_stream_update_params() with success or
+ * failure, depending on if we can support the format. Because we gave
+ * a list of supported formats, this should be ok.
+ *
+ * As part of pw_stream_update_params() we can provide parameters that
+ * will control the buffer memory allocation. This includes the metadata
+ * that we would like on our buffer, the size, alignment, etc.
+ */
+static void
+on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param)
+{
+ struct data *data = _data;
+ struct pw_stream *stream = data->stream;
+ uint8_t params_buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
+ const struct spa_pod *params[5];
+
+ if (param == NULL || id != SPA_PARAM_Format)
+ return;
+
+ spa_format_video_raw_parse(param, &data->format);
+
+ data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4);
+
+ params[0] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->format.size.height),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride),
+ SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1<<SPA_DATA_MemFd));
+
+ params[1] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)));
+
+ params[2] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage),
+ SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int(
+ sizeof(struct spa_meta_region) * 16,
+ sizeof(struct spa_meta_region) * 1,
+ sizeof(struct spa_meta_region) * 16));
+ params[3] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region)));
+#define CURSOR_META_SIZE(w,h) (sizeof(struct spa_meta_cursor) + \
+ sizeof(struct spa_meta_bitmap) + w * h * CURSOR_BPP)
+ params[4] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor),
+ SPA_PARAM_META_size, SPA_POD_Int(
+ CURSOR_META_SIZE(CURSOR_WIDTH,CURSOR_HEIGHT)));
+
+ pw_stream_update_params(stream, params, 5);
+}
+
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .process = on_process,
+ .state_changed = on_stream_state_changed,
+ .param_changed = on_stream_param_changed,
+ .add_buffer = on_stream_add_buffer,
+ .remove_buffer = on_stream_remove_buffer,
+};
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_thread_loop_signal(data->loop, false);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+
+ pw_init(&argc, &argv);
+
+ /* create a thread loop and start it */
+ data.loop = pw_thread_loop_new("video-src-alloc", NULL);
+
+ /* take the lock around all PipeWire functions. In callbacks, the lock
+ * is already taken for you but it's ok to lock again because the lock is
+ * recursive */
+ pw_thread_loop_lock(data.loop);
+
+ /* install some handlers to exit nicely */
+ pw_loop_add_signal(pw_thread_loop_get_loop(data.loop), SIGINT, do_quit, &data);
+ pw_loop_add_signal(pw_thread_loop_get_loop(data.loop), SIGTERM, do_quit, &data);
+
+ /* start after the signal handlers are set */
+ pw_thread_loop_start(data.loop);
+
+ /* create a simple stream, the simple stream manages the core
+ * object for you if you don't want to deal with them.
+ *
+ * We're making a new video provider. We need to set the media-class
+ * property.
+ *
+ * Pass your events and a user_data pointer as the last arguments. This
+ * will inform you about the stream state. The most important event
+ * you need to listen to is the process event where you need to provide
+ * the data.
+ */
+ data.stream = pw_stream_new_simple(
+ pw_thread_loop_get_loop(data.loop),
+ "video-src-alloc",
+ pw_properties_new(
+ PW_KEY_MEDIA_CLASS, "Video/Source",
+ NULL),
+ &stream_events,
+ &data);
+
+ /* make a timer to schedule our frames */
+ data.timer = pw_loop_add_timer(pw_thread_loop_get_loop(data.loop), on_timeout, &data);
+
+ /* build the extra parameter for the connection. Here we make an
+ * EnumFormat parameter which lists the possible formats we can provide.
+ * The server will select a format that matches and informs us about this
+ * in the stream param_changed event.
+ */
+ params[0] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
+ 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_Id(SPA_VIDEO_FORMAT_RGB),
+ SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
+ &SPA_RECTANGLE(320, 240),
+ &SPA_RECTANGLE(1, 1),
+ &SPA_RECTANGLE(4096, 4096)),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(25, 1)));
+
+ /* now connect the stream, we need a direction (input/output),
+ * an optional target node to connect to, some flags and parameters.
+ *
+ * Here we pass PW_STREAM_FLAG_ALLOC_BUFFERS. We should in the
+ * add_buffer callback configure the buffer memory. This should be
+ * fd backed memory (memfd, dma-buf, ...) that can be shared with
+ * the server. */
+ pw_stream_connect(data.stream,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_DRIVER |
+ PW_STREAM_FLAG_ALLOC_BUFFERS,
+ params, 1);
+
+ /* unlock, run the loop and wait, this will trigger the callbacks */
+ pw_thread_loop_wait(data.loop);
+
+ /* unlock before stop */
+ pw_thread_loop_unlock(data.loop);
+ pw_thread_loop_stop(data.loop);
+
+ pw_stream_destroy(data.stream);
+
+ /* destroy after dependent objects are destroyed */
+ pw_thread_loop_destroy(data.loop);
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/examples/video-src-fixate.c b/src/examples/video-src-fixate.c
new file mode 100644
index 0000000..fdcc666
--- /dev/null
+++ b/src/examples/video-src-fixate.c
@@ -0,0 +1,602 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Fixating negotiated modifiers.
+ [title]
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <signal.h>
+#include <math.h>
+#include <libdrm/drm_fourcc.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include <spa/param/video/format-utils.h>
+#include <spa/debug/format.h>
+
+#include <pipewire/pipewire.h>
+
+#define BPP 3
+#define CURSOR_WIDTH 64
+#define CURSOR_HEIGHT 64
+#define CURSOR_BPP 4
+
+#define MAX_BUFFERS 64
+
+#define M_PI_M2 ( M_PI + M_PI )
+
+uint64_t supported_modifiers[] = {DRM_FORMAT_MOD_INVALID, DRM_FORMAT_MOD_LINEAR};
+
+struct data {
+ struct pw_thread_loop *loop;
+ struct spa_source *timer;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ struct spa_video_info_raw format;
+ int32_t stride;
+
+ int counter;
+ uint32_t seq;
+
+ double crop;
+ double accumulator;
+};
+
+static void draw_elipse(uint32_t *dst, int width, int height, uint32_t color)
+{
+ int i, j, r1, r2, r12, r22, r122;
+
+ r1 = width/2;
+ r12 = r1 * r1;
+ r2 = height/2;
+ r22 = r2 * r2;
+ r122 = r12 * r22;
+
+ for (i = -r2; i < r2; i++) {
+ for (j = -r1; j < r1; j++) {
+ dst[(i + r2)*width+(j+r1)] =
+ (i * i * r12 + j * j * r22 <= r122) ? color : 0x00000000;
+ }
+ }
+}
+
+static struct spa_pod *fixate_format(struct spa_pod_builder *b, enum spa_video_format format,
+ uint64_t *modifier)
+{
+ struct spa_pod_frame f[1];
+
+ 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_video), 0);
+ spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
+ /* format */
+ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);
+ /* modifiers */
+ if (modifier) {
+ // we only support implicit modifiers, use shortpath to skip fixation phase
+ spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY);
+ spa_pod_builder_long(b, *modifier);
+ }
+ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size,
+ SPA_POD_CHOICE_RANGE_Rectangle(
+ &SPA_RECTANGLE(320, 240),
+ &SPA_RECTANGLE(1,1),
+ &SPA_RECTANGLE(4096,4096)),
+ 0);
+ // variable framerate
+ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate,
+ SPA_POD_Fraction(&SPA_FRACTION(25, 1)), 0);
+ return spa_pod_builder_pop(b, &f[0]);
+}
+
+static struct spa_pod *build_format(struct spa_pod_builder *b, enum spa_video_format format,
+ uint64_t *modifiers, int modifier_count)
+{
+ struct spa_pod_frame f[2];
+ int i, c;
+
+ 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_video), 0);
+ spa_pod_builder_add(b, SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw), 0);
+ /* format */
+ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_format, SPA_POD_Id(format), 0);
+ /* modifiers */
+ if (modifier_count == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) {
+ // we only support implicit modifiers, use shortpath to skip fixation phase
+ spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY);
+ spa_pod_builder_long(b, modifiers[0]);
+ } else if (modifier_count > 0) {
+ // build an enumeration of modifiers
+ spa_pod_builder_prop(b, SPA_FORMAT_VIDEO_modifier, SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE);
+ spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Enum, 0);
+ // modifiers from the array
+ for (i = 0, c = 0; i < modifier_count; i++) {
+ spa_pod_builder_long(b, modifiers[i]);
+ if (c++ == 0)
+ spa_pod_builder_long(b, modifiers[i]);
+ }
+ spa_pod_builder_pop(b, &f[1]);
+ }
+ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_size,
+ SPA_POD_CHOICE_RANGE_Rectangle(
+ &SPA_RECTANGLE(320, 240),
+ &SPA_RECTANGLE(1,1),
+ &SPA_RECTANGLE(4096,4096)),
+ 0);
+ // variable framerate
+ spa_pod_builder_add(b, SPA_FORMAT_VIDEO_framerate,
+ SPA_POD_Fraction(&SPA_FRACTION(25, 1)), 0);
+ return spa_pod_builder_pop(b, &f[0]);
+}
+
+/* called when we should push a new buffer in the queue */
+static void on_process(void *userdata)
+{
+ struct data *data = userdata;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ uint32_t i, j;
+ uint8_t *p;
+ struct spa_meta *m;
+ struct spa_meta_header *h;
+ struct spa_meta_region *mc;
+ struct spa_meta_cursor *mcs;
+
+ 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) {
+ printf("No data ptr\n");
+ goto done;
+ }
+
+ if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)))) {
+#if 0
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ h->pts = SPA_TIMESPEC_TO_NSEC(&now);
+#else
+ h->pts = -1;
+#endif
+ h->flags = 0;
+ h->seq = data->seq++;
+ h->dts_offset = 0;
+ }
+ if ((m = spa_buffer_find_meta(buf, SPA_META_VideoDamage))) {
+ struct spa_meta_region *r = spa_meta_first(m);
+
+ if (spa_meta_check(r, m)) {
+ r->region.position = SPA_POINT(0,0);
+ r->region.size = data->format.size;
+ r++;
+ }
+ if (spa_meta_check(r, m))
+ r->region = SPA_REGION(0,0,0,0);
+ }
+ if ((mc = spa_buffer_find_meta_data(buf, SPA_META_VideoCrop, sizeof(*mc)))) {
+ data->crop = (sin(data->accumulator) + 1.0) * 32.0;
+ mc->region.position.x = data->crop;
+ mc->region.position.y = data->crop;
+ mc->region.size.width = data->format.size.width - data->crop*2;
+ mc->region.size.height = data->format.size.height - data->crop*2;
+ }
+ if ((mcs = spa_buffer_find_meta_data(buf, SPA_META_Cursor, sizeof(*mcs)))) {
+ struct spa_meta_bitmap *mb;
+ uint32_t *bitmap, color;
+
+ mcs->id = 1;
+ mcs->position.x = (sin(data->accumulator) + 1.0) * 160.0 + 80;
+ mcs->position.y = (cos(data->accumulator) + 1.0) * 100.0 + 50;
+ mcs->hotspot.x = 0;
+ mcs->hotspot.y = 0;
+ mcs->bitmap_offset = sizeof(struct spa_meta_cursor);
+
+ mb = SPA_PTROFF(mcs, mcs->bitmap_offset, struct spa_meta_bitmap);
+ mb->format = SPA_VIDEO_FORMAT_ARGB;
+ mb->size.width = CURSOR_WIDTH;
+ mb->size.height = CURSOR_HEIGHT;
+ mb->stride = CURSOR_WIDTH * CURSOR_BPP;
+ mb->offset = sizeof(struct spa_meta_bitmap);
+
+ bitmap = SPA_PTROFF(mb, mb->offset, uint32_t);
+ color = (cos(data->accumulator) + 1.0) * (1 << 23);
+ color |= 0xff000000;
+
+ draw_elipse(bitmap, mb->size.width, mb->size.height, color);
+ }
+
+ for (i = 0; i < data->format.size.height; i++) {
+ for (j = 0; j < data->format.size.width * BPP; j++) {
+ p[j] = data->counter + j * i;
+ }
+ p += data->stride;
+ data->counter += 13;
+ }
+
+ data->accumulator += M_PI_M2 / 50.0;
+ if (data->accumulator >= M_PI_M2)
+ data->accumulator -= M_PI_M2;
+
+ buf->datas[0].chunk->offset = 0;
+ buf->datas[0].chunk->size = data->format.size.height * data->stride;
+ buf->datas[0].chunk->stride = data->stride;
+
+done:
+ pw_stream_queue_buffer(data->stream, b);
+}
+
+/* trigger the graph when we are a driver */
+static void on_timeout(void *userdata, uint64_t expirations)
+{
+ struct data *data = userdata;
+ pw_log_trace("timeout");
+ pw_stream_trigger_process(data->stream);
+}
+
+/* when the stream is STREAMING, start the timer at 40ms intervals
+ * to produce and push a frame. In other states we PAUSE the timer. */
+static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state,
+ const char *error)
+{
+ struct data *data = _data;
+
+ printf("stream state: \"%s\"\n", pw_stream_state_as_string(state));
+
+ switch (state) {
+ case PW_STREAM_STATE_PAUSED:
+ printf("node id: %d\n", pw_stream_get_node_id(data->stream));
+ pw_loop_update_timer(pw_thread_loop_get_loop(data->loop),
+ data->timer, NULL, NULL, false);
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ {
+ struct timespec timeout, interval;
+
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 1;
+ interval.tv_sec = 0;
+ interval.tv_nsec = 40 * SPA_NSEC_PER_MSEC;
+
+ if (pw_stream_is_driving(data->stream))
+ pw_loop_update_timer(pw_thread_loop_get_loop(data->loop),
+ data->timer, &timeout, &interval, false);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+/* we set the PW_STREAM_FLAG_ALLOC_BUFFERS flag when connecting so we need
+ * to provide buffer memory. */
+static void on_stream_add_buffer(void *_data, struct pw_buffer *buffer)
+{
+ printf("add_buffer\n");
+ struct data *data = _data;
+ struct spa_buffer *buf = buffer->buffer;
+ struct spa_data *d;
+#ifdef HAVE_MEMFD_CREATE
+ unsigned int seals;
+#endif
+
+ pw_log_info("add buffer %p", buffer);
+ d = buf->datas;
+
+ if ((d[0].type & (1<<SPA_DATA_DmaBuf)) > 0) {
+ printf("pretend to support dmabufs while setting the fd to -1\n");
+ d[0].type = SPA_DATA_DmaBuf;
+ d[0].fd = -1;
+ d[0].data = NULL;
+ return;
+ }
+
+ if ((d[0].type & (1<<SPA_DATA_MemFd)) == 0) {
+ pw_log_error("unsupported data type %08x", d[0].type);
+ return;
+ }
+
+ printf("use memfd\n");
+ /* create the memfd on the buffer, set the type and flags */
+ d[0].type = SPA_DATA_MemFd;
+ d[0].flags = SPA_DATA_FLAG_READWRITE;
+#ifdef HAVE_MEMFD_CREATE
+ d[0].fd = memfd_create("video-src-fixate-memfd", MFD_CLOEXEC | MFD_ALLOW_SEALING);
+#else
+ d[0].fd = -1;
+#endif
+ if (d[0].fd == -1) {
+ pw_log_error("can't create memfd: %m");
+ return;
+ }
+ d[0].mapoffset = 0;
+ d[0].maxsize = data->stride * data->format.size.height;
+
+ /* truncate to the right size before we set seals */
+ if (ftruncate(d[0].fd, d[0].maxsize) < 0) {
+ pw_log_error("can't truncate to %d: %m", d[0].maxsize);
+ return;
+ }
+#ifdef HAVE_MEMFD_CREATE
+ /* not enforced yet but server might require SEAL_SHRINK later */
+ seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL;
+ if (fcntl(d[0].fd, F_ADD_SEALS, seals) == -1) {
+ pw_log_warn("Failed to add seals: %m");
+ }
+#endif
+
+ /* now mmap so we can write to it in the process function above */
+ d[0].data = mmap(NULL, d[0].maxsize, PROT_READ|PROT_WRITE,
+ MAP_SHARED, d[0].fd, d[0].mapoffset);
+ if (d[0].data == MAP_FAILED) {
+ pw_log_error("can't mmap memory: %m");
+ return;
+ }
+}
+
+/* close the memfd we set on the buffers here */
+static void on_stream_remove_buffer(void *_data, struct pw_buffer *buffer)
+{
+ printf("remove_buffer\n");
+ struct spa_buffer *buf = buffer->buffer;
+ struct spa_data *d;
+
+ d = buf->datas;
+ pw_log_info("remove buffer %p", buffer);
+ if ((d[0].type & (1<<SPA_DATA_DmaBuf)) == 0)
+ return;
+
+ munmap(d[0].data, d[0].maxsize);
+ close(d[0].fd);
+}
+
+/* Be notified when the stream param changes. We're only looking at the
+ * format param.
+ *
+ * We are now supposed to call pw_stream_update_params() with success or
+ * failure, depending on if we can support the format. Because we gave
+ * a list of supported formats, this should be ok.
+ *
+ * As part of pw_stream_update_params() we can provide parameters that
+ * will control the buffer memory allocation. This includes the metadata
+ * that we would like on our buffer, the size, alignment, etc.
+ */
+static void
+on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param)
+{
+ struct data *data = _data;
+ struct pw_stream *stream = data->stream;
+ uint8_t params_buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
+ const struct spa_pod *params[5];
+ int blocks, size, stride, buffertypes;
+
+ if (param == NULL || id != SPA_PARAM_Format)
+ return;
+
+ printf("param changed: \n");
+ spa_debug_format(4, NULL, param);
+
+ spa_format_video_raw_parse(param, &data->format);
+
+ data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4);
+
+ const struct spa_pod_prop *prop_modifier;
+ // check if client supports modifier
+ if ((prop_modifier = spa_pod_find_prop(param, NULL, SPA_FORMAT_VIDEO_modifier)) == NULL) {
+ blocks = 1;
+ size = data->stride * data->format.size.height;
+ stride = data->stride;
+ buffertypes = (1<<SPA_DATA_MemFd);
+ } else {
+ // check if the modifier is fixated
+ if ((prop_modifier->flags & SPA_POD_PROP_FLAG_DONT_FIXATE) > 0) {
+ const struct spa_pod *pod_modifier = &prop_modifier->value;
+ printf("fixating format\n");
+
+ uint32_t n_modifiers = SPA_POD_CHOICE_N_VALUES(pod_modifier);
+ uint64_t *modifiers = SPA_POD_CHOICE_VALUES(pod_modifier);
+ uint64_t modifier;
+ // shortcut for the old gbm allocator path
+ if (n_modifiers == 1 && modifiers[0] == DRM_FORMAT_MOD_INVALID) {
+ modifier = modifiers[0];
+ } else {
+ // Use the allocator to find the best modifier from the list
+ modifier = modifiers[rand()%n_modifiers];
+ }
+
+ params[0] = fixate_format(&b, SPA_VIDEO_FORMAT_RGB, &modifier);
+
+ params[1] = build_format(&b, SPA_VIDEO_FORMAT_RGB,
+ supported_modifiers, sizeof(supported_modifiers)/sizeof(supported_modifiers[0]));
+ params[2] = build_format(&b, SPA_VIDEO_FORMAT_RGB,
+ NULL, 0);
+
+ printf("announcing fixated EnumFormats\n");
+ for (unsigned int i=0; i < 3; i++) {
+ spa_debug_format(4, NULL, params[i]);
+ }
+
+ pw_stream_update_params(stream, params, 3);
+ return;
+ }
+ printf("no fixation required\n");
+ blocks = 1;
+ size = data->stride * data->format.size.height;
+ stride = data->stride;
+ buffertypes = (1<<SPA_DATA_DmaBuf);
+ }
+
+ params[0] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks),
+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(size),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride),
+ SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(buffertypes));
+
+ params[1] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)));
+
+ params[2] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage),
+ SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int(
+ sizeof(struct spa_meta_region) * 16,
+ sizeof(struct spa_meta_region) * 1,
+ sizeof(struct spa_meta_region) * 16));
+ params[3] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region)));
+#define CURSOR_META_SIZE(w,h) (sizeof(struct spa_meta_cursor) + \
+ sizeof(struct spa_meta_bitmap) + w * h * CURSOR_BPP)
+ params[4] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor),
+ SPA_PARAM_META_size, SPA_POD_Int(
+ CURSOR_META_SIZE(CURSOR_WIDTH,CURSOR_HEIGHT)));
+
+ pw_stream_update_params(stream, params, 5);
+}
+
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .process = on_process,
+ .state_changed = on_stream_state_changed,
+ .param_changed = on_stream_param_changed,
+ .add_buffer = on_stream_add_buffer,
+ .remove_buffer = on_stream_remove_buffer,
+};
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_thread_loop_signal(data->loop, false);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ const struct spa_pod *params[2];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+
+ srand(32);
+
+ pw_init(&argc, &argv);
+
+ /* create a thread loop and start it */
+ data.loop = pw_thread_loop_new("video-src-fixate", NULL);
+
+ /* take the lock around all PipeWire functions. In callbacks, the lock
+ * is already taken for you but it's ok to lock again because the lock is
+ * recursive */
+ pw_thread_loop_lock(data.loop);
+
+ /* install some handlers to exit nicely */
+ pw_loop_add_signal(pw_thread_loop_get_loop(data.loop), SIGINT, do_quit, &data);
+ pw_loop_add_signal(pw_thread_loop_get_loop(data.loop), SIGTERM, do_quit, &data);
+
+ /* start after the signal handlers are set */
+ pw_thread_loop_start(data.loop);
+
+ /* create a simple stream, the simple stream manages the core
+ * object for you if you don't want to deal with them.
+ *
+ * We're making a new video provider. We need to set the media-class
+ * property.
+ *
+ * Pass your events and a user_data pointer as the last arguments. This
+ * will inform you about the stream state. The most important event
+ * you need to listen to is the process event where you need to provide
+ * the data.
+ */
+ data.stream = pw_stream_new_simple(
+ pw_thread_loop_get_loop(data.loop),
+ "video-src-fixate",
+ pw_properties_new(
+ PW_KEY_MEDIA_CLASS, "Video/Source",
+ NULL),
+ &stream_events,
+ &data);
+
+ /* make a timer to schedule our frames */
+ data.timer = pw_loop_add_timer(pw_thread_loop_get_loop(data.loop), on_timeout, &data);
+
+ /* build the extra parameter for the connection. Here we make an
+ * EnumFormat parameter which lists the possible formats we can provide.
+ * The server will select a format that matches and informs us about this
+ * in the stream param_changed event.
+ */
+ params[0] = build_format(&b, SPA_VIDEO_FORMAT_RGB,
+ supported_modifiers, sizeof(supported_modifiers)/sizeof(supported_modifiers[0]));
+ params[1] = build_format(&b, SPA_VIDEO_FORMAT_RGB, NULL, 0);
+
+ printf("announcing starting EnumFormats\n");
+ for (unsigned int i=0; i < 2; i++) {
+ spa_debug_format(4, NULL, params[i]);
+ }
+
+ /* now connect the stream, we need a direction (input/output),
+ * an optional target node to connect to, some flags and parameters.
+ *
+ * Here we pass PW_STREAM_FLAG_ALLOC_BUFFERS. We should in the
+ * add_buffer callback configure the buffer memory. This should be
+ * fd backed memory (memfd, dma-buf, ...) that can be shared with
+ * the server. */
+ pw_stream_connect(data.stream,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_DRIVER |
+ PW_STREAM_FLAG_ALLOC_BUFFERS,
+ params, 2);
+
+ /* unlock, run the loop and wait, this will trigger the callbacks */
+ pw_thread_loop_wait(data.loop);
+
+ /* unlock before stop */
+ pw_thread_loop_unlock(data.loop);
+ pw_thread_loop_stop(data.loop);
+
+ pw_stream_destroy(data.stream);
+
+ /* destroy after dependent objects are destroyed */
+ pw_thread_loop_destroy(data.loop);
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/examples/video-src-reneg.c b/src/examples/video-src-reneg.c
new file mode 100644
index 0000000..172e7dc
--- /dev/null
+++ b/src/examples/video-src-reneg.c
@@ -0,0 +1,509 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Renegotiating video producer and consumer formats with \ref pw_stream
+ [title]
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <signal.h>
+#include <math.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include <spa/param/video/format-utils.h>
+
+#include <pipewire/pipewire.h>
+
+#define BPP 3
+#define CURSOR_WIDTH 64
+#define CURSOR_HEIGHT 64
+#define CURSOR_BPP 4
+
+#define MAX_BUFFERS 64
+
+#define M_PI_M2 ( M_PI + M_PI )
+
+struct data {
+ struct pw_thread_loop *loop;
+ struct spa_source *timer;
+ struct spa_source *reneg_timer;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ struct spa_video_info_raw format;
+ int32_t stride;
+
+ int counter;
+ int cycle;
+ uint32_t seq;
+
+ double crop;
+ double accumulator;
+};
+
+static void draw_elipse(uint32_t *dst, int width, int height, uint32_t color)
+{
+ int i, j, r1, r2, r12, r22, r122;
+
+ r1 = width/2;
+ r12 = r1 * r1;
+ r2 = height/2;
+ r22 = r2 * r2;
+ r122 = r12 * r22;
+
+ for (i = -r2; i < r2; i++) {
+ for (j = -r1; j < r1; j++) {
+ dst[(i + r2)*width+(j+r1)] =
+ (i * i * r12 + j * j * r22 <= r122) ? color : 0x00000000;
+ }
+ }
+}
+
+/* called when we should push a new buffer in the queue */
+static void on_process(void *userdata)
+{
+ struct data *data = userdata;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ uint32_t i, j;
+ uint8_t *p;
+ struct spa_meta *m;
+ struct spa_meta_header *h;
+ struct spa_meta_region *mc;
+ struct spa_meta_cursor *mcs;
+
+ pw_log_trace("timeout");
+
+ 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;
+
+ if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)))) {
+#if 0
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ h->pts = SPA_TIMESPEC_TO_NSEC(&now);
+#else
+ h->pts = -1;
+#endif
+ h->flags = 0;
+ h->seq = data->seq++;
+ h->dts_offset = 0;
+ }
+ if ((m = spa_buffer_find_meta(buf, SPA_META_VideoDamage))) {
+ struct spa_meta_region *r = spa_meta_first(m);
+
+ if (spa_meta_check(r, m)) {
+ r->region.position = SPA_POINT(0,0);
+ r->region.size = data->format.size;
+ r++;
+ }
+ if (spa_meta_check(r, m))
+ r->region = SPA_REGION(0,0,0,0);
+ }
+ if ((mc = spa_buffer_find_meta_data(buf, SPA_META_VideoCrop, sizeof(*mc)))) {
+ data->crop = (sin(data->accumulator) + 1.0) * 32.0;
+ mc->region.position.x = data->crop;
+ mc->region.position.y = data->crop;
+ mc->region.size.width = data->format.size.width - data->crop*2;
+ mc->region.size.height = data->format.size.height - data->crop*2;
+ }
+ if ((mcs = spa_buffer_find_meta_data(buf, SPA_META_Cursor, sizeof(*mcs)))) {
+ struct spa_meta_bitmap *mb;
+ uint32_t *bitmap, color;
+
+ mcs->id = 1;
+ mcs->position.x = (sin(data->accumulator) + 1.0) * 160.0 + 80;
+ mcs->position.y = (cos(data->accumulator) + 1.0) * 100.0 + 50;
+ mcs->hotspot.x = 0;
+ mcs->hotspot.y = 0;
+ mcs->bitmap_offset = sizeof(struct spa_meta_cursor);
+
+ mb = SPA_PTROFF(mcs, mcs->bitmap_offset, struct spa_meta_bitmap);
+ mb->format = SPA_VIDEO_FORMAT_ARGB;
+ mb->size.width = CURSOR_WIDTH;
+ mb->size.height = CURSOR_HEIGHT;
+ mb->stride = CURSOR_WIDTH * CURSOR_BPP;
+ mb->offset = sizeof(struct spa_meta_bitmap);
+
+ bitmap = SPA_PTROFF(mb, mb->offset, uint32_t);
+ color = (cos(data->accumulator) + 1.0) * (1 << 23);
+ color |= 0xff000000;
+
+ draw_elipse(bitmap, mb->size.width, mb->size.height, color);
+ }
+
+ for (i = 0; i < data->format.size.height; i++) {
+ for (j = 0; j < data->format.size.width * BPP; j++) {
+ p[j] = data->counter + j * i;
+ }
+ p += data->stride;
+ data->counter += 13;
+ }
+
+ data->accumulator += M_PI_M2 / 50.0;
+ if (data->accumulator >= M_PI_M2)
+ data->accumulator -= M_PI_M2;
+
+ buf->datas[0].chunk->offset = 0;
+ buf->datas[0].chunk->size = data->format.size.height * data->stride;
+ buf->datas[0].chunk->stride = data->stride;
+
+ pw_stream_queue_buffer(data->stream, b);
+}
+
+/* called on timeout and we should start the graph */
+static void on_timeout(void *userdata, uint64_t expirations)
+{
+ struct data *data = userdata;
+ pw_log_trace("timeout");
+ pw_stream_trigger_process(data->stream);
+}
+
+/* when the stream is STREAMING, start the timer at 40ms intervals
+ * to produce and push a frame. In other states we PAUSE the timer. */
+static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state,
+ const char *error)
+{
+ struct data *data = _data;
+
+ printf("stream state: \"%s\"\n", pw_stream_state_as_string(state));
+
+ switch (state) {
+ case PW_STREAM_STATE_PAUSED:
+ printf("node id: %d\n", pw_stream_get_node_id(data->stream));
+ pw_loop_update_timer(pw_thread_loop_get_loop(data->loop),
+ data->timer, NULL, NULL, false);
+ pw_loop_update_timer(pw_thread_loop_get_loop(data->loop),
+ data->reneg_timer, NULL, NULL, false);
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ {
+ struct timespec timeout, interval;
+
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 1;
+ interval.tv_sec = 0;
+ interval.tv_nsec = 40 * SPA_NSEC_PER_MSEC;
+
+ if (pw_stream_is_driving(data->stream))
+ pw_loop_update_timer(pw_thread_loop_get_loop(data->loop),
+ data->timer, &timeout, &interval, false);
+
+ timeout.tv_sec = 1;
+ timeout.tv_nsec = 0;
+ interval.tv_sec = 1;
+ interval.tv_nsec = 0;
+
+ pw_loop_update_timer(pw_thread_loop_get_loop(data->loop),
+ data->reneg_timer, &timeout, &interval, false);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+/* we set the PW_STREAM_FLAG_ALLOC_BUFFERS flag when connecting so we need
+ * to provide buffer memory. */
+static void on_stream_add_buffer(void *_data, struct pw_buffer *buffer)
+{
+ struct data *data = _data;
+ struct spa_buffer *buf = buffer->buffer;
+ struct spa_data *d;
+#ifdef HAVE_MEMFD_CREATE
+ unsigned int seals;
+#endif
+
+ pw_log_info("add buffer %p", buffer);
+ d = buf->datas;
+
+ if ((d[0].type & (1<<SPA_DATA_MemFd)) == 0) {
+ pw_log_error("unsupported data type %08x", d[0].type);
+ return;
+ }
+
+ /* create the memfd on the buffer, set the type and flags */
+ d[0].type = SPA_DATA_MemFd;
+ d[0].flags = SPA_DATA_FLAG_READWRITE;
+#ifdef HAVE_MEMFD_CREATE
+ d[0].fd = memfd_create("video-src-memfd", MFD_CLOEXEC | MFD_ALLOW_SEALING);
+#else
+ d[0].fd = -1;
+#endif
+ if (d[0].fd == -1) {
+ pw_log_error("can't create memfd: %m");
+ return;
+ }
+ d[0].mapoffset = 0;
+ d[0].maxsize = data->stride * data->format.size.height;
+
+ /* truncate to the right size before we set seals */
+ if (ftruncate(d[0].fd, d[0].maxsize) < 0) {
+ pw_log_error("can't truncate to %d: %m", d[0].maxsize);
+ return;
+ }
+#ifdef HAVE_MEMFD_CREATE
+ /* not enforced yet but server might require SEAL_SHRINK later */
+ seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL;
+ if (fcntl(d[0].fd, F_ADD_SEALS, seals) == -1) {
+ pw_log_warn("Failed to add seals: %m");
+ }
+#endif
+
+ /* now mmap so we can write to it in the process function above */
+ d[0].data = mmap(NULL, d[0].maxsize, PROT_READ|PROT_WRITE,
+ MAP_SHARED, d[0].fd, d[0].mapoffset);
+ if (d[0].data == MAP_FAILED) {
+ pw_log_error("can't mmap memory: %m");
+ return;
+ }
+}
+
+/* close the memfd we set on the buffers here */
+static void on_stream_remove_buffer(void *_data, struct pw_buffer *buffer)
+{
+ struct spa_buffer *buf = buffer->buffer;
+ struct spa_data *d;
+
+ d = buf->datas;
+ pw_log_info("remove buffer %p", buffer);
+
+ munmap(d[0].data, d[0].maxsize);
+ close(d[0].fd);
+}
+
+/* Be notified when the stream param changes. We're only looking at the
+ * format param.
+ *
+ * We are now supposed to call pw_stream_update_params() with success or
+ * failure, depending on if we can support the format. Because we gave
+ * a list of supported formats, this should be ok.
+ *
+ * As part of pw_stream_update_params() we can provide parameters that
+ * will control the buffer memory allocation. This includes the metadata
+ * that we would like on our buffer, the size, alignment, etc.
+ */
+static void
+on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param)
+{
+ struct data *data = _data;
+ struct pw_stream *stream = data->stream;
+ uint8_t params_buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
+ const struct spa_pod *params[5];
+
+ if (param == NULL || id != SPA_PARAM_Format)
+ return;
+
+ pw_log_info("format changed");
+ spa_format_video_raw_parse(param, &data->format);
+
+ data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4);
+
+ params[0] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->format.size.height),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride),
+ SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(1<<SPA_DATA_MemFd));
+
+ params[1] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)));
+
+ params[2] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage),
+ SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int(
+ sizeof(struct spa_meta_region) * 16,
+ sizeof(struct spa_meta_region) * 1,
+ sizeof(struct spa_meta_region) * 16));
+ params[3] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region)));
+#define CURSOR_META_SIZE(w,h) (sizeof(struct spa_meta_cursor) + \
+ sizeof(struct spa_meta_bitmap) + w * h * CURSOR_BPP)
+ params[4] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor),
+ SPA_PARAM_META_size, SPA_POD_Int(
+ CURSOR_META_SIZE(CURSOR_WIDTH,CURSOR_HEIGHT)));
+
+ pw_stream_update_params(stream, params, 5);
+}
+
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .process = on_process,
+ .state_changed = on_stream_state_changed,
+ .param_changed = on_stream_param_changed,
+ .add_buffer = on_stream_add_buffer,
+ .remove_buffer = on_stream_remove_buffer,
+};
+
+static void on_reneg_timeout(void *userdata, uint64_t expirations)
+{
+ struct data *data = userdata;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ const struct spa_pod *params[2];
+ int32_t width, height;
+
+ width = data->cycle & 1 ? 320 : 640;
+ height = data->cycle & 1 ? 240 : 480;
+
+ fprintf(stderr, "renegotiate to %dx%d:\n", width, height);
+ params[0] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
+ 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_Id(SPA_VIDEO_FORMAT_RGB),
+ SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&SPA_RECTANGLE(width, height)),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(25, 1)));
+
+ pw_stream_update_params(data->stream, params, 1);
+
+ data->cycle++;
+}
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_thread_loop_signal(data->loop, false);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+
+ pw_init(&argc, &argv);
+
+ /* create a thread loop and start it */
+ data.loop = pw_thread_loop_new("video-src-alloc", NULL);
+
+ /* take the lock around all PipeWire functions. In callbacks, the lock
+ * is already taken for you but it's ok to lock again because the lock is
+ * recursive */
+ pw_thread_loop_lock(data.loop);
+
+ /* install some handlers to exit nicely */
+ pw_loop_add_signal(pw_thread_loop_get_loop(data.loop), SIGINT, do_quit, &data);
+ pw_loop_add_signal(pw_thread_loop_get_loop(data.loop), SIGTERM, do_quit, &data);
+
+ /* start after the signal handlers are set */
+ pw_thread_loop_start(data.loop);
+
+ /* create a simple stream, the simple stream manages the core
+ * object for you if you don't want to deal with them.
+ *
+ * We're making a new video provider. We need to set the media-class
+ * property.
+ *
+ * Pass your events and a user_data pointer as the last arguments. This
+ * will inform you about the stream state. The most important event
+ * you need to listen to is the process event where you need to provide
+ * the data.
+ */
+ data.stream = pw_stream_new_simple(
+ pw_thread_loop_get_loop(data.loop),
+ "video-src-alloc",
+ pw_properties_new(
+ PW_KEY_MEDIA_CLASS, "Video/Source",
+ NULL),
+ &stream_events,
+ &data);
+
+ /* make a timer to schedule our frames */
+ data.timer = pw_loop_add_timer(pw_thread_loop_get_loop(data.loop),
+ on_timeout, &data);
+
+ /* make a timer to schedule renegotiation */
+ data.reneg_timer = pw_loop_add_timer(pw_thread_loop_get_loop(data.loop),
+ on_reneg_timeout, &data);
+
+ /* build the extra parameter for the connection. Here we make an
+ * EnumFormat parameter which lists the possible formats we can provide.
+ * The server will select a format that matches and informs us about this
+ * in the stream param_changed event.
+ */
+ params[0] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
+ 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_Id(SPA_VIDEO_FORMAT_RGB),
+ SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
+ &SPA_RECTANGLE(320, 240),
+ &SPA_RECTANGLE(1, 1),
+ &SPA_RECTANGLE(4096, 4096)),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(25, 1)));
+
+ /* now connect the stream, we need a direction (input/output),
+ * an optional target node to connect to, some flags and parameters.
+ *
+ * Here we pass PW_STREAM_FLAG_ALLOC_BUFFERS. We should in the
+ * add_buffer callback configure the buffer memory. This should be
+ * fd backed memory (memfd, dma-buf, ...) that can be shared with
+ * the server. */
+ pw_stream_connect(data.stream,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_DRIVER |
+ PW_STREAM_FLAG_ALLOC_BUFFERS,
+ params, 1);
+
+ /* unlock, run the loop and wait, this will trigger the callbacks */
+ pw_thread_loop_wait(data.loop);
+
+ /* unlock before stop */
+ pw_thread_loop_unlock(data.loop);
+ pw_thread_loop_stop(data.loop);
+
+ pw_stream_destroy(data.stream);
+
+ /* destroy after dependent objects are destroyed */
+ pw_thread_loop_destroy(data.loop);
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/examples/video-src.c b/src/examples/video-src.c
new file mode 100644
index 0000000..d7e2051
--- /dev/null
+++ b/src/examples/video-src.c
@@ -0,0 +1,357 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ [title]
+ Video source using \ref pw_stream.
+ [title]
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <signal.h>
+#include <math.h>
+
+#include <spa/param/video/format-utils.h>
+
+#include <pipewire/pipewire.h>
+
+#define BPP 3
+#define CURSOR_WIDTH 64
+#define CURSOR_HEIGHT 64
+#define CURSOR_BPP 4
+
+#define MAX_BUFFERS 64
+
+#define M_PI_M2 ( M_PI + M_PI )
+
+struct data {
+ struct pw_main_loop *loop;
+ struct spa_source *timer;
+
+ struct pw_context *context;
+ struct pw_core *core;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ struct spa_video_info_raw format;
+ int32_t stride;
+
+ int counter;
+ uint32_t seq;
+
+ double crop;
+ double accumulator;
+ int res;
+};
+
+static void draw_elipse(uint32_t *dst, int width, int height, uint32_t color)
+{
+ int i, j, r1, r2, r12, r22, r122;
+
+ r1 = width/2;
+ r12 = r1 * r1;
+ r2 = height/2;
+ r22 = r2 * r2;
+ r122 = r12 * r22;
+
+ for (i = -r2; i < r2; i++) {
+ for (j = -r1; j < r1; j++) {
+ dst[(i + r2)*width+(j+r1)] =
+ (i * i * r12 + j * j * r22 <= r122) ? color : 0x00000000;
+ }
+ }
+}
+
+static void on_process(void *userdata)
+{
+ struct data *data = userdata;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ uint32_t i, j;
+ uint8_t *p;
+ struct spa_meta *m;
+ struct spa_meta_header *h;
+ struct spa_meta_region *mc;
+ struct spa_meta_cursor *mcs;
+
+ 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;
+
+ if ((h = spa_buffer_find_meta_data(buf, SPA_META_Header, sizeof(*h)))) {
+#if 0
+ struct timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ h->pts = SPA_TIMESPEC_TO_NSEC(&now);
+#else
+ h->pts = -1;
+#endif
+ h->flags = 0;
+ h->seq = data->seq++;
+ h->dts_offset = 0;
+ }
+ if ((m = spa_buffer_find_meta(buf, SPA_META_VideoDamage))) {
+ struct spa_meta_region *r = spa_meta_first(m);
+
+ if (spa_meta_check(r, m)) {
+ r->region.position = SPA_POINT(0,0);
+ r->region.size = data->format.size;
+ r++;
+ }
+ if (spa_meta_check(r, m))
+ r->region = SPA_REGION(0,0,0,0);
+ }
+ if ((mc = spa_buffer_find_meta_data(buf, SPA_META_VideoCrop, sizeof(*mc)))) {
+ data->crop = (sin(data->accumulator) + 1.0) * 32.0;
+ mc->region.position.x = data->crop;
+ mc->region.position.y = data->crop;
+ mc->region.size.width = data->format.size.width - data->crop*2;
+ mc->region.size.height = data->format.size.height - data->crop*2;
+ }
+ if ((mcs = spa_buffer_find_meta_data(buf, SPA_META_Cursor, sizeof(*mcs)))) {
+ struct spa_meta_bitmap *mb;
+ uint32_t *bitmap, color;
+
+ mcs->id = 1;
+ mcs->position.x = (sin(data->accumulator) + 1.0) * 160.0 + 80;
+ mcs->position.y = (cos(data->accumulator) + 1.0) * 100.0 + 50;
+ mcs->hotspot.x = 0;
+ mcs->hotspot.y = 0;
+ mcs->bitmap_offset = sizeof(struct spa_meta_cursor);
+
+ mb = SPA_PTROFF(mcs, mcs->bitmap_offset, struct spa_meta_bitmap);
+ mb->format = SPA_VIDEO_FORMAT_ARGB;
+ mb->size.width = CURSOR_WIDTH;
+ mb->size.height = CURSOR_HEIGHT;
+ mb->stride = CURSOR_WIDTH * CURSOR_BPP;
+ mb->offset = sizeof(struct spa_meta_bitmap);
+
+ bitmap = SPA_PTROFF(mb, mb->offset, uint32_t);
+ color = (cos(data->accumulator) + 1.0) * (1 << 23);
+ color |= 0xff000000;
+
+ draw_elipse(bitmap, mb->size.width, mb->size.height, color);
+ }
+
+ for (i = 0; i < data->format.size.height; i++) {
+ for (j = 0; j < data->format.size.width * BPP; j++) {
+ p[j] = data->counter + j * i;
+ }
+ p += data->stride;
+ data->counter += 13;
+ }
+
+ data->accumulator += M_PI_M2 / 50.0;
+ if (data->accumulator >= M_PI_M2)
+ data->accumulator -= M_PI_M2;
+
+ buf->datas[0].chunk->offset = 0;
+ buf->datas[0].chunk->size = data->format.size.height * data->stride;
+ buf->datas[0].chunk->stride = data->stride;
+
+ pw_stream_queue_buffer(data->stream, b);
+}
+
+static void on_timeout(void *userdata, uint64_t expirations)
+{
+ struct data *data = userdata;
+ pw_log_trace("timeout");
+ pw_stream_trigger_process(data->stream);
+}
+
+static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum pw_stream_state state,
+ const char *error)
+{
+ struct data *data = _data;
+
+ printf("stream state: \"%s\"\n", pw_stream_state_as_string(state));
+
+ switch (state) {
+ case PW_STREAM_STATE_ERROR:
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_main_loop_quit(data->loop);
+ break;
+
+ case PW_STREAM_STATE_PAUSED:
+ printf("node id: %d\n", pw_stream_get_node_id(data->stream));
+ pw_loop_update_timer(pw_main_loop_get_loop(data->loop),
+ data->timer, NULL, NULL, false);
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ {
+ struct timespec timeout, interval;
+
+ timeout.tv_sec = 0;
+ timeout.tv_nsec = 1;
+ interval.tv_sec = 0;
+ interval.tv_nsec = 40 * SPA_NSEC_PER_MSEC;
+
+ pw_loop_update_timer(pw_main_loop_get_loop(data->loop),
+ data->timer, &timeout, &interval, false);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static void
+on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param)
+{
+ struct data *data = _data;
+ struct pw_stream *stream = data->stream;
+ uint8_t params_buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(params_buffer, sizeof(params_buffer));
+ const struct spa_pod *params[5];
+
+ if (param == NULL || id != SPA_PARAM_Format)
+ return;
+
+ spa_format_video_raw_parse(param, &data->format);
+
+ data->stride = SPA_ROUND_UP_N(data->format.size.width * BPP, 4);
+
+ params[0] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(8, 2, MAX_BUFFERS),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
+ SPA_PARAM_BUFFERS_size, SPA_POD_Int(data->stride * data->format.size.height),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(data->stride));
+
+ params[1] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_header)));
+
+ params[2] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoDamage),
+ SPA_PARAM_META_size, SPA_POD_CHOICE_RANGE_Int(
+ sizeof(struct spa_meta_region) * 16,
+ sizeof(struct spa_meta_region) * 1,
+ sizeof(struct spa_meta_region) * 16));
+ params[3] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_region)));
+#define CURSOR_META_SIZE(w,h) (sizeof(struct spa_meta_cursor) + \
+ sizeof(struct spa_meta_bitmap) + w * h * CURSOR_BPP)
+ params[4] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Cursor),
+ SPA_PARAM_META_size, SPA_POD_Int(
+ CURSOR_META_SIZE(CURSOR_WIDTH,CURSOR_HEIGHT)));
+
+ pw_stream_update_params(stream, params, 5);
+}
+
+static void
+on_trigger_done(void *_data)
+{
+ pw_log_trace("trigger done");
+}
+
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .process = on_process,
+ .state_changed = on_stream_state_changed,
+ .param_changed = on_stream_param_changed,
+ .trigger_done = on_trigger_done,
+};
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_main_loop_quit(data->loop);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+
+ pw_init(&argc, &argv);
+
+ data.loop = pw_main_loop_new(NULL);
+
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGINT, do_quit, &data);
+ pw_loop_add_signal(pw_main_loop_get_loop(data.loop), SIGTERM, do_quit, &data);
+
+ data.context = pw_context_new(pw_main_loop_get_loop(data.loop), NULL, 0);
+
+ data.timer = pw_loop_add_timer(pw_main_loop_get_loop(data.loop), on_timeout, &data);
+
+ data.core = pw_context_connect(data.context, NULL, 0);
+ if (data.core == NULL) {
+ fprintf(stderr, "can't connect: %m\n");
+ data.res = -errno;
+ goto cleanup;
+ }
+
+ data.stream = pw_stream_new(data.core, "video-src",
+ pw_properties_new(
+ PW_KEY_MEDIA_CLASS, "Video/Source",
+ NULL));
+
+ params[0] = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
+ 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_Id(SPA_VIDEO_FORMAT_RGB),
+ SPA_FORMAT_VIDEO_size, SPA_POD_CHOICE_RANGE_Rectangle(
+ &SPA_RECTANGLE(320, 240),
+ &SPA_RECTANGLE(1, 1),
+ &SPA_RECTANGLE(4096, 4096)),
+ SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&SPA_FRACTION(25, 1)));
+
+ pw_stream_add_listener(data.stream,
+ &data.stream_listener,
+ &stream_events,
+ &data);
+
+ pw_stream_connect(data.stream,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_DRIVER |
+ PW_STREAM_FLAG_MAP_BUFFERS,
+ params, 1);
+
+ pw_main_loop_run(data.loop);
+
+cleanup:
+ pw_context_destroy(data.context);
+ pw_main_loop_destroy(data.loop);
+ pw_deinit();
+
+ return data.res;
+}
diff --git a/src/gst/.editorconfig b/src/gst/.editorconfig
new file mode 100644
index 0000000..b6330b2
--- /dev/null
+++ b/src/gst/.editorconfig
@@ -0,0 +1,7 @@
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
diff --git a/src/gst/gstpipewire.c b/src/gst/gstpipewire.c
new file mode 100644
index 0000000..b87f8a3
--- /dev/null
+++ b/src/gst/gstpipewire.c
@@ -0,0 +1,69 @@
+/* PipeWire GStreamer Elements
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * SECTION:element-pipewiresrc
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch -v pipewiresrc ! ximagesink
+ * ]| Shows PipeWire output in an X window.
+ * </refsect2>
+ */
+
+#include "config.h"
+
+#include "gstpipewiresrc.h"
+#include "gstpipewiresink.h"
+#include "gstpipewiredeviceprovider.h"
+
+GST_DEBUG_CATEGORY (pipewire_debug);
+
+static gboolean
+plugin_init (GstPlugin *plugin)
+{
+ pw_init (NULL, NULL);
+
+ gst_element_register (plugin, "pipewiresrc", GST_RANK_PRIMARY + 1,
+ GST_TYPE_PIPEWIRE_SRC);
+ gst_element_register (plugin, "pipewiresink", GST_RANK_NONE,
+ GST_TYPE_PIPEWIRE_SINK);
+
+#ifdef HAVE_GSTREAMER_DEVICE_PROVIDER
+ if (!gst_device_provider_register (plugin, "pipewiredeviceprovider",
+ GST_RANK_PRIMARY + 1, GST_TYPE_PIPEWIRE_DEVICE_PROVIDER))
+ return FALSE;
+#endif
+
+ GST_DEBUG_CATEGORY_INIT (pipewire_debug, "pipewire", 0, "PipeWire elements");
+
+ return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ pipewire,
+ "Uses PipeWire to handle media streams",
+ plugin_init, PACKAGE_VERSION, "MIT/X11", "pipewire", "pipewire.org")
diff --git a/src/gst/gstpipewireclock.c b/src/gst/gstpipewireclock.c
new file mode 100644
index 0000000..44b1158
--- /dev/null
+++ b/src/gst/gstpipewireclock.c
@@ -0,0 +1,127 @@
+/* GStreamer
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <gst/gst.h>
+
+#include "gstpipewireclock.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_pipewire_clock_debug_category);
+#define GST_CAT_DEFAULT gst_pipewire_clock_debug_category
+
+G_DEFINE_TYPE (GstPipeWireClock, gst_pipewire_clock, GST_TYPE_SYSTEM_CLOCK);
+
+GstClock *
+gst_pipewire_clock_new (struct pw_stream *stream, GstClockTime last_time)
+{
+ GstPipeWireClock *clock;
+
+ clock = g_object_new (GST_TYPE_PIPEWIRE_CLOCK, NULL);
+ clock->stream = stream;
+ clock->last_time = last_time;
+ clock->time_offset = last_time;
+
+ return GST_CLOCK_CAST (clock);
+}
+
+static GstClockTime
+gst_pipewire_clock_get_internal_time (GstClock * clock)
+{
+ GstPipeWireClock *pclock = (GstPipeWireClock *) clock;
+ GstClockTime result;
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+#if 0
+ struct pw_time t;
+ if (pclock->stream == NULL ||
+ pw_stream_get_time (pclock->stream, &t) < 0 ||
+ t.rate.denom == 0)
+ return pclock->last_time;
+
+ result = gst_util_uint64_scale_int (t.ticks, GST_SECOND * t.rate.num, t.rate.denom);
+ result += SPA_TIMESPEC_TO_NSEC(&ts) - t.now;
+
+ result += pclock->time_offset;
+ pclock->last_time = result;
+
+ GST_DEBUG ("%"PRId64", %d/%d %"PRId64" %"PRId64,
+ t.ticks, t.rate.num, t.rate.denom, t.now, result);
+#else
+ result = SPA_TIMESPEC_TO_NSEC(&ts);
+ result += pclock->time_offset;
+ pclock->last_time = result;
+#endif
+
+ return result;
+}
+
+static void
+gst_pipewire_clock_finalize (GObject * object)
+{
+ GstPipeWireClock *clock = GST_PIPEWIRE_CLOCK (object);
+
+ GST_DEBUG_OBJECT (clock, "finalize");
+
+ G_OBJECT_CLASS (gst_pipewire_clock_parent_class)->finalize (object);
+}
+
+static void
+gst_pipewire_clock_class_init (GstPipeWireClockClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstClockClass *gstclock_class = GST_CLOCK_CLASS (klass);
+
+ gobject_class->finalize = gst_pipewire_clock_finalize;
+
+ gstclock_class->get_internal_time = gst_pipewire_clock_get_internal_time;
+
+ GST_DEBUG_CATEGORY_INIT (gst_pipewire_clock_debug_category, "pipewireclock", 0,
+ "debug category for pipewireclock object");
+}
+
+static void
+gst_pipewire_clock_init (GstPipeWireClock * clock)
+{
+ GST_OBJECT_FLAG_SET (clock, GST_CLOCK_FLAG_CAN_SET_MASTER);
+}
+
+void
+gst_pipewire_clock_reset (GstPipeWireClock * clock, GstClockTime time)
+{
+ GstClockTimeDiff time_offset;
+
+ if (clock->last_time >= time)
+ time_offset = clock->last_time - time;
+ else
+ time_offset = -(time - clock->last_time);
+
+ clock->time_offset = time_offset;
+
+ GST_DEBUG_OBJECT (clock,
+ "reset clock to %" GST_TIME_FORMAT ", last %" GST_TIME_FORMAT
+ ", offset %" GST_STIME_FORMAT, GST_TIME_ARGS (time),
+ GST_TIME_ARGS (clock->last_time), GST_STIME_ARGS (time_offset));
+}
diff --git a/src/gst/gstpipewireclock.h b/src/gst/gstpipewireclock.h
new file mode 100644
index 0000000..0a3e447
--- /dev/null
+++ b/src/gst/gstpipewireclock.h
@@ -0,0 +1,71 @@
+/* GStreamer
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __GST_PIPEWIRE_CLOCK_H__
+#define __GST_PIPEWIRE_CLOCK_H__
+
+#include <gst/gst.h>
+
+#include <pipewire/pipewire.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_PIPEWIRE_CLOCK \
+ (gst_pipewire_clock_get_type())
+#define GST_PIPEWIRE_CLOCK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_CLOCK,GstPipeWireClock))
+#define GST_PIPEWIRE_CLOCK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_CLOCK,GstPipeWireClockClass))
+#define GST_IS_PIPEWIRE_CLOCK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_CLOCK))
+#define GST_IS_PIPEWIRE_CLOCK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_CLOCK))
+#define GST_PIPEWIRE_CLOCK_GET_CLASS(klass) \
+ (G_TYPE_INSTANCE_GET_CLASS ((klass), GST_TYPE_PIPEWIRE_CLOCK, GstPipeWireClockClass))
+
+typedef struct _GstPipeWireClock GstPipeWireClock;
+typedef struct _GstPipeWireClockClass GstPipeWireClockClass;
+
+struct _GstPipeWireClock {
+ GstSystemClock parent;
+
+ struct pw_stream *stream;
+ GstClockTime last_time;
+ GstClockTimeDiff time_offset;
+};
+
+struct _GstPipeWireClockClass {
+ GstSystemClockClass parent_class;
+};
+
+GType gst_pipewire_clock_get_type (void);
+
+GstClock * gst_pipewire_clock_new (struct pw_stream *stream,
+ GstClockTime last_time);
+void gst_pipewire_clock_reset (GstPipeWireClock *clock,
+ GstClockTime time);
+
+G_END_DECLS
+
+#endif /* __GST_PIPEWIRE_CLOCK_H__ */
diff --git a/src/gst/gstpipewirecore.c b/src/gst/gstpipewirecore.c
new file mode 100644
index 0000000..6910f4e
--- /dev/null
+++ b/src/gst/gstpipewirecore.c
@@ -0,0 +1,202 @@
+/* GStreamer
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <spa/utils/result.h>
+
+#include "gstpipewirecore.h"
+
+/* a list of global cores indexed by fd. */
+G_LOCK_DEFINE_STATIC (cores_lock);
+static GList *cores;
+
+static void
+on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ GstPipeWireCore *core = data;
+
+ pw_log_warn("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE) {
+ core->last_error = res;
+ }
+ pw_thread_loop_signal(core->loop, FALSE);
+}
+
+static void on_core_done (void *data, uint32_t id, int seq)
+{
+ GstPipeWireCore * core = data;
+ if (id == PW_ID_CORE) {
+ core->last_seq = seq;
+ pw_thread_loop_signal (core->loop, FALSE);
+ }
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .done = on_core_done,
+ .error = on_core_error,
+};
+
+static GstPipeWireCore *make_core (int fd)
+{
+ GstPipeWireCore *core;
+
+ core = g_new (GstPipeWireCore, 1);
+ core->refcount = 1;
+ core->fd = fd;
+ core->loop = pw_thread_loop_new ("pipewire-main-loop", NULL);
+ core->context = pw_context_new (pw_thread_loop_get_loop(core->loop), NULL, 0);
+ core->last_seq = -1;
+ core->last_error = 0;
+ GST_DEBUG ("loop %p context %p", core->loop, core->context);
+
+ if (pw_thread_loop_start (core->loop) < 0)
+ goto mainloop_failed;
+
+ pw_thread_loop_lock (core->loop);
+
+ if (fd == -1)
+ core->core = pw_context_connect (core->context, NULL, 0);
+ else
+ core->core = pw_context_connect_fd (core->context, fcntl(fd, F_DUPFD_CLOEXEC, 3), NULL, 0);
+
+ if (core->core == NULL)
+ goto connection_failed;
+
+ pw_core_add_listener(core->core,
+ &core->core_listener,
+ &core_events,
+ core);
+
+ pw_thread_loop_unlock (core->loop);
+
+ return core;
+
+mainloop_failed:
+ {
+ GST_ERROR ("error starting mainloop");
+ pw_context_destroy (core->context);
+ pw_thread_loop_destroy (core->loop);
+ g_free (core);
+ return NULL;
+ }
+connection_failed:
+ {
+ GST_ERROR ("error connect: %m");
+ pw_thread_loop_unlock (core->loop);
+ pw_context_destroy (core->context);
+ pw_thread_loop_destroy (core->loop);
+ g_free (core);
+ return NULL;
+ }
+}
+
+typedef struct {
+ int fd;
+} FindData;
+
+static gint
+core_find (GstPipeWireCore * core, FindData * data)
+{
+ /* fd's must match */
+ if (core->fd == data->fd)
+ return 0;
+ return 1;
+}
+
+GstPipeWireCore *gst_pipewire_core_get (int fd)
+{
+ GstPipeWireCore *core;
+ FindData data;
+ GList *found;
+
+ data.fd = fd;
+
+ G_LOCK (cores_lock);
+ found = g_list_find_custom (cores, &data, (GCompareFunc) core_find);
+ if (found != NULL) {
+ core = (GstPipeWireCore *) found->data;
+ core->refcount++;
+ GST_DEBUG ("found core %p", core);
+ } else {
+ core = make_core(fd);
+ if (core != NULL) {
+ GST_DEBUG ("created core %p", core);
+ /* add to list on success */
+ cores = g_list_prepend (cores, core);
+ } else {
+ GST_WARNING ("could not create core");
+ }
+ }
+ G_UNLOCK (cores_lock);
+
+ return core;
+}
+
+static void do_sync(GstPipeWireCore * core)
+{
+ struct timespec abstime;
+ core->pending_seq = pw_core_sync(core->core, 0, core->pending_seq);
+ pw_thread_loop_get_time (core->loop, &abstime,
+ GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC);
+ while (true) {
+ if (core->last_seq == core->pending_seq || core->last_error < 0)
+ break;
+ if (pw_thread_loop_timed_wait_full (core->loop, &abstime) < 0)
+ break;
+ }
+}
+
+void gst_pipewire_core_release (GstPipeWireCore *core)
+{
+ gboolean zero;
+
+ G_LOCK (cores_lock);
+ core->refcount--;
+ if ((zero = (core->refcount == 0))) {
+ GST_DEBUG ("closing core %p", core);
+ /* remove from list, we can release the mutex after removing the connection
+ * from the list because after that, nobody can access the connection anymore. */
+ cores = g_list_remove (cores, core);
+ }
+ G_UNLOCK (cores_lock);
+
+ if (zero) {
+ pw_thread_loop_lock (core->loop);
+ do_sync(core);
+
+ pw_core_disconnect (core->core);
+ pw_thread_loop_unlock (core->loop);
+ pw_thread_loop_stop (core->loop);
+ pw_context_destroy (core->context);
+ pw_thread_loop_destroy (core->loop);
+
+ free(core);
+ }
+}
diff --git a/src/gst/gstpipewirecore.h b/src/gst/gstpipewirecore.h
new file mode 100644
index 0000000..c64d466
--- /dev/null
+++ b/src/gst/gstpipewirecore.h
@@ -0,0 +1,60 @@
+/* GStreamer
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __GST_PIPEWIRE_CORE_H__
+#define __GST_PIPEWIRE_CORE_H__
+
+#include <gst/gst.h>
+
+#include <pipewire/pipewire.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GstPipeWireCore GstPipeWireCore;
+
+#define GST_PIPEWIRE_DEFAULT_TIMEOUT 30
+
+/**
+ * GstPipeWireCore:
+ *
+ * Opaque data structure.
+ */
+struct _GstPipeWireCore {
+ gint refcount;
+ int fd;
+ struct pw_thread_loop *loop;
+ struct pw_context *context;
+ struct pw_core *core;
+ struct spa_hook core_listener;
+ int last_error;
+ int last_seq;
+ int pending_seq;
+};
+
+GstPipeWireCore *gst_pipewire_core_get (int fd);
+void gst_pipewire_core_release (GstPipeWireCore *core);
+
+G_END_DECLS
+
+#endif /* __GST_PIPEWIRE_CORE_H__ */
diff --git a/src/gst/gstpipewiredeviceprovider.c b/src/gst/gstpipewiredeviceprovider.c
new file mode 100644
index 0000000..598a7c5
--- /dev/null
+++ b/src/gst/gstpipewiredeviceprovider.c
@@ -0,0 +1,754 @@
+/* GStreamer
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+
+#include <gst/gst.h>
+
+#include "gstpipewireformat.h"
+#include "gstpipewiredeviceprovider.h"
+#include "gstpipewiresrc.h"
+#include "gstpipewiresink.h"
+
+GST_DEBUG_CATEGORY_EXTERN (pipewire_debug);
+#define GST_CAT_DEFAULT pipewire_debug
+
+G_DEFINE_TYPE (GstPipeWireDevice, gst_pipewire_device, GST_TYPE_DEVICE);
+
+enum
+{
+ PROP_ID = 1,
+ PROP_SERIAL,
+ PROP_FD_DEVICE,
+};
+
+static GstElement *
+gst_pipewire_device_create_element (GstDevice * device, const gchar * name)
+{
+ GstPipeWireDevice *pipewire_dev = GST_PIPEWIRE_DEVICE (device);
+ GstElement *elem;
+ gchar *serial_str;
+
+ elem = gst_element_factory_make (pipewire_dev->element, name);
+
+ serial_str = g_strdup_printf ("%"PRIu64, pipewire_dev->serial);
+ g_object_set (elem, "target-object", serial_str,
+ "fd", pipewire_dev->fd, NULL);
+ g_free (serial_str);
+
+ return elem;
+}
+
+static gboolean
+gst_pipewire_device_reconfigure_element (GstDevice * device, GstElement * element)
+{
+ GstPipeWireDevice *pipewire_dev = GST_PIPEWIRE_DEVICE (device);
+ gchar *serial_str;
+
+ if (spa_streq(pipewire_dev->element, "pipewiresrc")) {
+ if (!GST_IS_PIPEWIRE_SRC (element))
+ return FALSE;
+ } else if (spa_streq(pipewire_dev->element, "pipewiresink")) {
+ if (!GST_IS_PIPEWIRE_SINK (element))
+ return FALSE;
+ } else {
+ g_assert_not_reached ();
+ }
+
+ serial_str = g_strdup_printf ("%"PRIu64, pipewire_dev->serial);
+ g_object_set (element, "target-object", serial_str,
+ "fd", pipewire_dev->fd, NULL);
+ g_free (serial_str);
+
+ return TRUE;
+}
+
+
+static void
+gst_pipewire_device_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireDevice *device;
+
+ device = GST_PIPEWIRE_DEVICE_CAST (object);
+
+ switch (prop_id) {
+ case PROP_ID:
+ g_value_set_uint (value, device->id);
+ break;
+ case PROP_SERIAL:
+ g_value_set_uint64 (value, device->serial);
+ break;
+ case PROP_FD_DEVICE:
+ g_value_set_int (value, device->fd);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_pipewire_device_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireDevice *device;
+
+ device = GST_PIPEWIRE_DEVICE_CAST (object);
+
+ switch (prop_id) {
+ case PROP_ID:
+ device->id = g_value_get_uint (value);
+ break;
+ case PROP_SERIAL:
+ device->serial = g_value_get_uint64 (value);
+ break;
+ case PROP_FD_DEVICE:
+ device->fd = g_value_get_int (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_pipewire_device_finalize (GObject * object)
+{
+ G_OBJECT_CLASS (gst_pipewire_device_parent_class)->finalize (object);
+}
+
+static void
+gst_pipewire_device_class_init (GstPipeWireDeviceClass * klass)
+{
+ GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ dev_class->create_element = gst_pipewire_device_create_element;
+ dev_class->reconfigure_element = gst_pipewire_device_reconfigure_element;
+
+ object_class->get_property = gst_pipewire_device_get_property;
+ object_class->set_property = gst_pipewire_device_set_property;
+ object_class->finalize = gst_pipewire_device_finalize;
+
+ g_object_class_install_property (object_class, PROP_ID,
+ g_param_spec_uint ("id", "Id",
+ "The internal id of the PipeWire device", 0, G_MAXUINT32, SPA_ID_INVALID,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_SERIAL,
+ g_param_spec_uint64 ("serial", "Serial",
+ "The internal serial of the PipeWire device", 0, G_MAXUINT64, SPA_ID_INVALID,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class,
+ PROP_FD_DEVICE,
+ g_param_spec_int ("fd", "Fd", "The fd to connect with", -1, G_MAXINT, -1,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gst_pipewire_device_init (GstPipeWireDevice * device)
+{
+}
+
+G_DEFINE_TYPE (GstPipeWireDeviceProvider, gst_pipewire_device_provider,
+ GST_TYPE_DEVICE_PROVIDER);
+
+enum
+{
+ PROP_0,
+ PROP_CLIENT_NAME,
+ PROP_FD,
+ PROP_LAST
+};
+
+struct node_data {
+ struct spa_list link;
+ GstPipeWireDeviceProvider *self;
+ struct pw_node *proxy;
+ struct spa_hook proxy_listener;
+ uint32_t id;
+ uint64_t serial;
+ struct spa_hook node_listener;
+ struct pw_node_info *info;
+ GstCaps *caps;
+ GstDevice *dev;
+};
+
+struct port_data {
+ struct node_data *node_data;
+ struct pw_port *proxy;
+ struct spa_hook proxy_listener;
+ uint32_t id;
+ uint64_t serial;
+ struct spa_hook port_listener;
+};
+
+static struct node_data *find_node_data(struct spa_list *nodes, uint32_t id)
+{
+ struct node_data *n;
+ spa_list_for_each(n, nodes, link) {
+ if (n->id == id)
+ return n;
+ }
+ return NULL;
+}
+
+static GstDevice *
+new_node (GstPipeWireDeviceProvider *self, struct node_data *data)
+{
+ GstStructure *props;
+ const gchar *klass = NULL, *name = NULL;
+ GstPipeWireDeviceType type;
+ const struct pw_node_info *info = data->info;
+ const gchar *element = NULL;
+ GstPipeWireDevice *gstdev;
+
+ if (info->max_input_ports > 0 && info->max_output_ports == 0) {
+ type = GST_PIPEWIRE_DEVICE_TYPE_SINK;
+ element = "pipewiresink";
+ } else if (info->max_output_ports > 0 && info->max_input_ports == 0) {
+ type = GST_PIPEWIRE_DEVICE_TYPE_SOURCE;
+ element = "pipewiresrc";
+ } else {
+ return NULL;
+ }
+
+ props = gst_structure_new_empty ("pipewire-proplist");
+ if (info->props) {
+ const struct spa_dict_item *item;
+ 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);
+ }
+ if (klass == NULL)
+ klass = "unknown/unknown";
+ 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;
+ if (props)
+ gst_structure_free (props);
+
+ return GST_DEVICE (gstdev);
+}
+
+static void do_add_nodes(GstPipeWireDeviceProvider *self)
+{
+ struct node_data *nd;
+
+ spa_list_for_each(nd, &self->nodes, link) {
+ if (nd->dev != NULL)
+ continue;
+ pw_log_info("add node %d", nd->id);
+ nd->dev = new_node (self, nd);
+ if (nd->dev) {
+ if(self->list_only)
+ self->devices = g_list_prepend (self->devices, gst_object_ref_sink (nd->dev));
+ else
+ gst_device_provider_device_add (GST_DEVICE_PROVIDER (self), nd->dev);
+ }
+ }
+}
+
+static void resync(GstPipeWireDeviceProvider *self)
+{
+ self->seq = pw_core_sync(self->core->core, PW_ID_CORE, self->seq);
+ pw_log_debug("resync %d", self->seq);
+}
+
+static void
+on_core_done (void *data, uint32_t id, int seq)
+{
+ GstPipeWireDeviceProvider *self = data;
+
+ pw_log_debug("check %d %d", seq, self->seq);
+ if (id == PW_ID_CORE && seq == self->seq) {
+ do_add_nodes(self);
+ self->end = true;
+ if (self->core)
+ pw_thread_loop_signal (self->core->loop, FALSE);
+ }
+}
+
+
+static void
+on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ GstPipeWireDeviceProvider *self = data;
+
+ pw_log_warn("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE) {
+ self->error = res;
+ }
+ pw_thread_loop_signal(self->core->loop, FALSE);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .done = on_core_done,
+ .error = on_core_error,
+};
+
+static void port_event_info(void *data, const struct pw_port_info *info)
+{
+ struct port_data *port_data = data;
+ struct node_data *node_data = port_data->node_data;
+ uint32_t i;
+
+ pw_log_debug("%p", port_data);
+
+ if (info->change_mask & PW_PORT_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ uint32_t id = info->params[i].id;
+
+ if (id == SPA_PARAM_EnumFormat &&
+ info->params[i].flags & SPA_PARAM_INFO_READ &&
+ node_data->caps == NULL) {
+ node_data->caps = gst_caps_new_empty ();
+ pw_port_enum_params(port_data->proxy, 0, id, 0, UINT32_MAX, NULL);
+ resync(node_data->self);
+ }
+ }
+ }
+}
+
+static void port_event_param(void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next, const struct spa_pod *param)
+{
+ struct port_data *port_data = data;
+ struct node_data *node_data = port_data->node_data;
+ GstCaps *c1;
+
+ c1 = gst_caps_from_format (param);
+ if (c1 && node_data->caps)
+ gst_caps_append (node_data->caps, c1);
+}
+
+static const struct pw_port_events port_events = {
+ PW_VERSION_PORT_EVENTS,
+ .info = port_event_info,
+ .param = port_event_param
+};
+
+static void node_event_info(void *data, const struct pw_node_info *info)
+{
+ struct node_data *node_data = data;
+ uint32_t i;
+
+ pw_log_debug("%p", node_data->proxy);
+
+ info = node_data->info = pw_node_info_update(node_data->info, info);
+
+ if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ uint32_t id = info->params[i].id;
+
+ if (id == SPA_PARAM_EnumFormat &&
+ info->params[i].flags & SPA_PARAM_INFO_READ &&
+ node_data->caps == NULL) {
+ node_data->caps = gst_caps_new_empty ();
+ pw_node_enum_params(node_data->proxy, 0, id, 0, UINT32_MAX, NULL);
+ resync(node_data->self);
+ }
+ }
+ }
+}
+
+static void node_event_param(void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next, const struct spa_pod *param)
+{
+ struct node_data *node_data = data;
+ GstCaps *c1;
+
+ c1 = gst_caps_from_format (param);
+ if (c1 && node_data->caps)
+ gst_caps_append (node_data->caps, c1);
+}
+
+static const struct pw_node_events node_events = {
+ PW_VERSION_NODE_EVENTS,
+ .info = node_event_info,
+ .param = node_event_param
+};
+
+static void
+removed_node (void *data)
+{
+ struct node_data *nd = data;
+ pw_proxy_destroy((struct pw_proxy*)nd->proxy);
+}
+
+static void
+destroy_node (void *data)
+{
+ struct node_data *nd = data;
+ GstPipeWireDeviceProvider *self = nd->self;
+ GstDeviceProvider *provider = GST_DEVICE_PROVIDER (self);
+
+ pw_log_debug("destroy %p", nd);
+
+ if (nd->dev != NULL) {
+ gst_device_provider_device_remove (provider, GST_DEVICE (nd->dev));
+ }
+ if (nd->caps)
+ gst_caps_unref(nd->caps);
+ if (nd->info)
+ pw_node_info_free(nd->info);
+
+ spa_list_remove(&nd->link);
+}
+
+static const struct pw_proxy_events proxy_node_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .removed = removed_node,
+ .destroy = destroy_node,
+};
+
+static void
+removed_port (void *data)
+{
+ struct port_data *pd = data;
+ pw_proxy_destroy((struct pw_proxy*)pd->proxy);
+}
+
+static void
+destroy_port (void *data)
+{
+ struct port_data *pd = data;
+ pw_log_debug("destroy %p", pd);
+}
+
+static const struct pw_proxy_events proxy_port_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .removed = removed_port,
+ .destroy = destroy_port,
+};
+
+static void registry_event_global(void *data, uint32_t id, uint32_t permissions,
+ const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ GstPipeWireDeviceProvider *self = data;
+ GstDeviceProvider *provider = (GstDeviceProvider*)self;
+ struct node_data *nd;
+ const char *str;
+
+ if (spa_streq(type, PW_TYPE_INTERFACE_Node)) {
+ struct pw_node *node;
+
+ node = pw_registry_bind(self->registry,
+ id, type, PW_VERSION_NODE, sizeof(*nd));
+ if (node == NULL)
+ goto no_mem;
+
+ if (props != NULL) {
+ str = spa_dict_lookup(props, PW_KEY_OBJECT_PATH);
+ if (str != NULL) {
+ if (g_str_has_prefix(str, "alsa:"))
+ gst_device_provider_hide_provider (provider, "pulsedeviceprovider");
+ else if (g_str_has_prefix(str, "v4l2:"))
+ gst_device_provider_hide_provider (provider, "v4l2deviceprovider");
+ else if (g_str_has_prefix(str, "libcamera:"))
+ gst_device_provider_hide_provider (provider, "libcameraprovider");
+ }
+ }
+
+ nd = pw_proxy_get_user_data((struct pw_proxy*)node);
+ nd->self = self;
+ nd->proxy = node;
+ nd->id = id;
+ if (!props || !spa_atou64(spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL), &nd->serial, 0))
+ nd->serial = SPA_ID_INVALID;
+ spa_list_append(&self->nodes, &nd->link);
+ pw_node_add_listener(node, &nd->node_listener, &node_events, nd);
+ pw_proxy_add_listener((struct pw_proxy*)node, &nd->proxy_listener, &proxy_node_events, nd);
+ resync(self);
+ }
+ else if (spa_streq(type, PW_TYPE_INTERFACE_Port)) {
+ struct pw_port *port;
+ struct port_data *pd;
+
+ if ((str = spa_dict_lookup(props, PW_KEY_NODE_ID)) == NULL)
+ return;
+
+ if ((nd = find_node_data(&self->nodes, atoi(str))) == NULL)
+ return;
+
+ port = pw_registry_bind(self->registry,
+ id, type, PW_VERSION_PORT, sizeof(*pd));
+ if (port == NULL)
+ goto no_mem;
+
+ pd = pw_proxy_get_user_data((struct pw_proxy*)port);
+ pd->node_data = nd;
+ pd->proxy = port;
+ pd->id = id;
+ if (!props || !spa_atou64(spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL), &pd->serial, 0))
+ pd->serial = SPA_ID_INVALID;
+ 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);
+ }
+
+ return;
+
+no_mem:
+ GST_ERROR_OBJECT(self, "failed to create proxy");
+ return;
+}
+
+static void registry_event_global_remove(void *data, uint32_t id)
+{
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+ .global_remove = registry_event_global_remove,
+};
+
+static GList *
+gst_pipewire_device_provider_probe (GstDeviceProvider * provider)
+{
+ GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider);
+
+ GST_DEBUG_OBJECT (self, "starting probe");
+
+ self->core = gst_pipewire_core_get(self->fd);
+ if (self->core == NULL) {
+ GST_ERROR_OBJECT (self, "Failed to connect");
+ goto failed;
+ }
+
+ GST_DEBUG_OBJECT (self, "connected");
+
+ pw_thread_loop_lock (self->core->loop);
+
+ spa_list_init(&self->nodes);
+ spa_list_init(&self->pending);
+ self->end = FALSE;
+ self->error = 0;
+ self->list_only = TRUE;
+ self->devices = NULL;
+ self->registry = pw_core_get_registry(self->core->core, PW_VERSION_REGISTRY, 0);
+
+ pw_core_add_listener(self->core->core, &self->core_listener, &core_events, self);
+ pw_registry_add_listener(self->registry, &self->registry_listener, &registry_events, self);
+
+ resync(self);
+
+ for (;;) {
+ if (self->error < 0)
+ break;
+ if (self->end)
+ break;
+ pw_thread_loop_wait (self->core->loop);
+ }
+
+ GST_DEBUG_OBJECT (self, "disconnect");
+
+ g_clear_pointer ((struct pw_proxy**)&self->registry, pw_proxy_destroy);
+ pw_thread_loop_unlock (self->core->loop);
+ g_clear_pointer (&self->core, gst_pipewire_core_release);
+
+ return self->devices;
+
+failed:
+ return NULL;
+}
+
+static gboolean
+gst_pipewire_device_provider_start (GstDeviceProvider * provider)
+{
+ GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider);
+
+ GST_DEBUG_OBJECT (self, "starting provider");
+
+ self->core = gst_pipewire_core_get(self->fd);
+ if (self->core == NULL) {
+ GST_ERROR_OBJECT (self, "Failed to connect");
+ goto failed;
+ }
+
+ GST_DEBUG_OBJECT (self, "connected");
+
+ pw_thread_loop_lock (self->core->loop);
+
+ spa_list_init(&self->nodes);
+ spa_list_init(&self->pending);
+ self->end = FALSE;
+ self->error = 0;
+ self->list_only = FALSE;
+ self->registry = pw_core_get_registry(self->core->core, PW_VERSION_REGISTRY, 0);
+
+ pw_core_add_listener(self->core->core, &self->core_listener, &core_events, self);
+ pw_registry_add_listener(self->registry, &self->registry_listener, &registry_events, self);
+
+ resync(self);
+
+ for (;;) {
+ if (self->error < 0)
+ break;
+ if (self->end)
+ break;
+ pw_thread_loop_wait (self->core->loop);
+ }
+
+ GST_DEBUG_OBJECT (self, "started");
+
+ pw_thread_loop_unlock (self->core->loop);
+
+ return TRUE;
+
+failed:
+ return TRUE;
+}
+
+static void
+gst_pipewire_device_provider_stop (GstDeviceProvider * provider)
+{
+ GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (provider);
+
+ GST_DEBUG_OBJECT (self, "stopping provider");
+
+ g_clear_pointer ((struct pw_proxy**)&self->registry, pw_proxy_destroy);
+ g_clear_pointer (&self->core, gst_pipewire_core_release);
+}
+
+static void
+gst_pipewire_device_provider_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object);
+
+ switch (prop_id) {
+ case PROP_CLIENT_NAME:
+ g_free (self->client_name);
+ if (!g_value_get_string (value)) {
+ GST_WARNING_OBJECT (self,
+ "Empty PipeWire client name not allowed. "
+ "Resetting to default value");
+ self->client_name = g_strdup(pw_get_client_name ());
+ } else
+ self->client_name = g_value_dup_string (value);
+ break;
+
+ case PROP_FD:
+ self->fd = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_pipewire_device_provider_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object);
+
+ switch (prop_id) {
+ case PROP_CLIENT_NAME:
+ g_value_set_string (value, self->client_name);
+ break;
+
+ case PROP_FD:
+ g_value_set_int (value, self->fd);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_pipewire_device_provider_finalize (GObject * object)
+{
+ GstPipeWireDeviceProvider *self = GST_PIPEWIRE_DEVICE_PROVIDER (object);
+
+ g_free (self->client_name);
+
+ G_OBJECT_CLASS (gst_pipewire_device_provider_parent_class)->finalize (object);
+}
+
+static void
+gst_pipewire_device_provider_class_init (GstPipeWireDeviceProviderClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
+
+ gobject_class->set_property = gst_pipewire_device_provider_set_property;
+ gobject_class->get_property = gst_pipewire_device_provider_get_property;
+ gobject_class->finalize = gst_pipewire_device_provider_finalize;
+
+ dm_class->probe = gst_pipewire_device_provider_probe;
+ dm_class->start = gst_pipewire_device_provider_start;
+ dm_class->stop = gst_pipewire_device_provider_stop;
+
+ g_object_class_install_property (gobject_class,
+ PROP_CLIENT_NAME,
+ g_param_spec_string ("client-name", "Client Name",
+ "The PipeWire client_name_to_use", pw_get_client_name (),
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ GST_PARAM_MUTABLE_READY));
+
+ g_object_class_install_property (gobject_class,
+ PROP_FD,
+ g_param_spec_int ("fd", "Fd", "The fd to connect with", -1, G_MAXINT, -1,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE));
+
+ gst_device_provider_class_set_static_metadata (dm_class,
+ "PipeWire Device Provider", "Sink/Source/Audio/Video",
+ "List and provide PipeWire source and sink devices",
+ "Wim Taymans <wim.taymans@gmail.com>");
+}
+
+static void
+gst_pipewire_device_provider_init (GstPipeWireDeviceProvider * self)
+{
+ self->client_name = g_strdup(pw_get_client_name ());
+ self->fd = -1;
+}
diff --git a/src/gst/gstpipewiredeviceprovider.h b/src/gst/gstpipewiredeviceprovider.h
new file mode 100644
index 0000000..c940ba4
--- /dev/null
+++ b/src/gst/gstpipewiredeviceprovider.h
@@ -0,0 +1,110 @@
+/* GStreamer
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#ifndef __GST_PIPEWIRE_DEVICE_PROVIDER_H__
+#define __GST_PIPEWIRE_DEVICE_PROVIDER_H__
+
+#include "config.h"
+
+#include <gst/gst.h>
+
+#include <pipewire/pipewire.h>
+#include <gst/gstpipewirecore.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GstPipeWireDevice GstPipeWireDevice;
+typedef struct _GstPipeWireDeviceClass GstPipeWireDeviceClass;
+
+#define GST_TYPE_PIPEWIRE_DEVICE (gst_pipewire_device_get_type())
+#define GST_IS_PIPEWIRE_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PIPEWIRE_DEVICE))
+#define GST_IS_PIPEWIRE_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PIPEWIRE_DEVICE))
+#define GST_PIPEWIRE_DEVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PIPEWIRE_DEVICE, GstPipeWireDeviceClass))
+#define GST_PIPEWIRE_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PIPEWIRE_DEVICE, GstPipeWireDevice))
+#define GST_PIPEWIRE_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE, GstPipeWireDeviceClass))
+#define GST_PIPEWIRE_DEVICE_CAST(obj) ((GstPipeWireDevice *)(obj))
+
+typedef enum {
+ GST_PIPEWIRE_DEVICE_TYPE_UNKNOWN,
+ GST_PIPEWIRE_DEVICE_TYPE_SOURCE,
+ GST_PIPEWIRE_DEVICE_TYPE_SINK,
+} GstPipeWireDeviceType;
+
+struct _GstPipeWireDevice {
+ GstDevice parent;
+
+ GstPipeWireDeviceType type;
+ uint32_t id;
+ uint64_t serial;
+ int fd;
+ const gchar *element;
+};
+
+struct _GstPipeWireDeviceClass {
+ GstDeviceClass parent_class;
+};
+
+GType gst_pipewire_device_get_type (void);
+
+typedef struct _GstPipeWireDeviceProvider GstPipeWireDeviceProvider;
+typedef struct _GstPipeWireDeviceProviderClass GstPipeWireDeviceProviderClass;
+
+#define GST_TYPE_PIPEWIRE_DEVICE_PROVIDER (gst_pipewire_device_provider_get_type())
+#define GST_IS_PIPEWIRE_DEVICE_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER))
+#define GST_IS_PIPEWIRE_DEVICE_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER))
+#define GST_PIPEWIRE_DEVICE_PROVIDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER, GstPipeWireDeviceProviderClass))
+#define GST_PIPEWIRE_DEVICE_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_PIPEWIRE_DEVICE_PROVIDER, GstPipeWireDeviceProvider))
+#define GST_PIPEWIRE_DEVICE_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE_PROVIDER, GstPipeWireDeviceProviderClass))
+#define GST_PIPEWIRE_DEVICE_PROVIDER_CAST(obj) ((GstPipeWireDeviceProvider *)(obj))
+
+struct _GstPipeWireDeviceProvider {
+ GstDeviceProvider parent;
+
+ gchar *client_name;
+ int fd;
+
+ GstPipeWireCore *core;
+ struct spa_hook core_listener;
+ struct pw_registry *registry;
+ struct spa_hook registry_listener;
+ struct spa_list nodes;
+ struct spa_list pending;
+ int seq;
+
+ int error;
+ gboolean end;
+ gboolean list_only;
+ GList *devices;
+};
+
+struct _GstPipeWireDeviceProviderClass {
+ GstDeviceProviderClass parent_class;
+};
+
+GType gst_pipewire_device_provider_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_PIPEWIRE_DEVICE_PROVIDER_H__ */
diff --git a/src/gst/gstpipewireformat.c b/src/gst/gstpipewireformat.c
new file mode 100644
index 0000000..006c499
--- /dev/null
+++ b/src/gst/gstpipewireformat.c
@@ -0,0 +1,981 @@
+/* GStreamer
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+
+#include <gst/gst.h>
+#include <gst/allocators/gstdmabuf.h>
+#include <gst/video/video.h>
+#include <gst/audio/audio.h>
+
+#include <spa/utils/string.h>
+#include <spa/utils/type.h>
+#include <spa/param/video/format-utils.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/pod/builder.h>
+
+#include "gstpipewireformat.h"
+
+#ifndef DRM_FORMAT_MOD_INVALID
+#define DRM_FORMAT_MOD_INVALID ((1ULL << 56) - 1)
+#endif
+
+#ifndef DRM_FORMAT_MOD_LINEAR
+#define DRM_FORMAT_MOD_LINEAR 0
+#endif
+
+struct media_type {
+ const char *name;
+ uint32_t media_type;
+ uint32_t media_subtype;
+};
+
+static const struct media_type media_type_map[] = {
+ { "video/x-raw", SPA_MEDIA_TYPE_video, SPA_MEDIA_SUBTYPE_raw },
+ { "audio/x-raw", SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_raw },
+ { "image/jpeg", SPA_MEDIA_TYPE_video, SPA_MEDIA_SUBTYPE_mjpg },
+ { "video/x-jpeg", SPA_MEDIA_TYPE_video, SPA_MEDIA_SUBTYPE_mjpg },
+ { "video/x-h264", SPA_MEDIA_TYPE_video, SPA_MEDIA_SUBTYPE_h264 },
+ { "audio/x-mulaw", SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_raw },
+ { "audio/x-alaw", SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_raw },
+ { "audio/mpeg", SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_mp3 },
+ { "audio/x-flac", SPA_MEDIA_TYPE_audio, SPA_MEDIA_SUBTYPE_flac },
+ { NULL, }
+};
+
+static const uint32_t video_format_map[] = {
+ SPA_VIDEO_FORMAT_UNKNOWN,
+ SPA_VIDEO_FORMAT_ENCODED,
+ SPA_VIDEO_FORMAT_I420,
+ SPA_VIDEO_FORMAT_YV12,
+ SPA_VIDEO_FORMAT_YUY2,
+ SPA_VIDEO_FORMAT_UYVY,
+ SPA_VIDEO_FORMAT_AYUV,
+ SPA_VIDEO_FORMAT_RGBx,
+ SPA_VIDEO_FORMAT_BGRx,
+ SPA_VIDEO_FORMAT_xRGB,
+ SPA_VIDEO_FORMAT_xBGR,
+ SPA_VIDEO_FORMAT_RGBA,
+ SPA_VIDEO_FORMAT_BGRA,
+ SPA_VIDEO_FORMAT_ARGB,
+ SPA_VIDEO_FORMAT_ABGR,
+ SPA_VIDEO_FORMAT_RGB,
+ SPA_VIDEO_FORMAT_BGR,
+ SPA_VIDEO_FORMAT_Y41B,
+ SPA_VIDEO_FORMAT_Y42B,
+ SPA_VIDEO_FORMAT_YVYU,
+ SPA_VIDEO_FORMAT_Y444,
+ SPA_VIDEO_FORMAT_v210,
+ SPA_VIDEO_FORMAT_v216,
+ SPA_VIDEO_FORMAT_NV12,
+ SPA_VIDEO_FORMAT_NV21,
+ SPA_VIDEO_FORMAT_GRAY8,
+ SPA_VIDEO_FORMAT_GRAY16_BE,
+ SPA_VIDEO_FORMAT_GRAY16_LE,
+ SPA_VIDEO_FORMAT_v308,
+ SPA_VIDEO_FORMAT_RGB16,
+ SPA_VIDEO_FORMAT_BGR16,
+ SPA_VIDEO_FORMAT_RGB15,
+ SPA_VIDEO_FORMAT_BGR15,
+ SPA_VIDEO_FORMAT_UYVP,
+ SPA_VIDEO_FORMAT_A420,
+ SPA_VIDEO_FORMAT_RGB8P,
+ SPA_VIDEO_FORMAT_YUV9,
+ SPA_VIDEO_FORMAT_YVU9,
+ SPA_VIDEO_FORMAT_IYU1,
+ SPA_VIDEO_FORMAT_ARGB64,
+ SPA_VIDEO_FORMAT_AYUV64,
+ SPA_VIDEO_FORMAT_r210,
+ SPA_VIDEO_FORMAT_I420_10BE,
+ SPA_VIDEO_FORMAT_I420_10LE,
+ SPA_VIDEO_FORMAT_I422_10BE,
+ SPA_VIDEO_FORMAT_I422_10LE,
+ SPA_VIDEO_FORMAT_Y444_10BE,
+ SPA_VIDEO_FORMAT_Y444_10LE,
+ SPA_VIDEO_FORMAT_GBR,
+ SPA_VIDEO_FORMAT_GBR_10BE,
+ SPA_VIDEO_FORMAT_GBR_10LE,
+ SPA_VIDEO_FORMAT_NV16,
+ SPA_VIDEO_FORMAT_NV24,
+ SPA_VIDEO_FORMAT_NV12_64Z32,
+ SPA_VIDEO_FORMAT_A420_10BE,
+ SPA_VIDEO_FORMAT_A420_10LE,
+ SPA_VIDEO_FORMAT_A422_10BE,
+ SPA_VIDEO_FORMAT_A422_10LE,
+ SPA_VIDEO_FORMAT_A444_10BE,
+ SPA_VIDEO_FORMAT_A444_10LE,
+ SPA_VIDEO_FORMAT_NV61,
+ SPA_VIDEO_FORMAT_P010_10BE,
+ SPA_VIDEO_FORMAT_P010_10LE,
+ SPA_VIDEO_FORMAT_IYU2,
+ SPA_VIDEO_FORMAT_VYUY,
+ SPA_VIDEO_FORMAT_GBRA,
+ SPA_VIDEO_FORMAT_GBRA_10BE,
+ SPA_VIDEO_FORMAT_GBRA_10LE,
+ SPA_VIDEO_FORMAT_GBR_12BE,
+ SPA_VIDEO_FORMAT_GBR_12LE,
+ SPA_VIDEO_FORMAT_GBRA_12BE,
+ SPA_VIDEO_FORMAT_GBRA_12LE,
+ SPA_VIDEO_FORMAT_I420_12BE,
+ SPA_VIDEO_FORMAT_I420_12LE,
+ SPA_VIDEO_FORMAT_I422_12BE,
+ SPA_VIDEO_FORMAT_I422_12LE,
+ SPA_VIDEO_FORMAT_Y444_12BE,
+ SPA_VIDEO_FORMAT_Y444_12LE,
+};
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define _FORMAT_LE(fmt) SPA_AUDIO_FORMAT_ ## fmt ## _OE
+#define _FORMAT_BE(fmt) SPA_AUDIO_FORMAT_ ## fmt
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+#define _FORMAT_LE(fmt) SPA_AUDIO_FORMAT_ ## fmt
+#define _FORMAT_BE(fmt) SPA_AUDIO_FORMAT_ ## fmt ## _OE
+#endif
+
+static const uint32_t audio_format_map[] = {
+ SPA_AUDIO_FORMAT_UNKNOWN,
+ SPA_AUDIO_FORMAT_ENCODED,
+ SPA_AUDIO_FORMAT_S8,
+ SPA_AUDIO_FORMAT_U8,
+ _FORMAT_LE (S16),
+ _FORMAT_BE (S16),
+ _FORMAT_LE (U16),
+ _FORMAT_BE (U16),
+ _FORMAT_LE (S24_32),
+ _FORMAT_BE (S24_32),
+ _FORMAT_LE (U24_32),
+ _FORMAT_BE (U24_32),
+ _FORMAT_LE (S32),
+ _FORMAT_BE (S32),
+ _FORMAT_LE (U32),
+ _FORMAT_BE (U32),
+ _FORMAT_LE (S24),
+ _FORMAT_BE (S24),
+ _FORMAT_LE (U24),
+ _FORMAT_BE (U24),
+ _FORMAT_LE (S20),
+ _FORMAT_BE (S20),
+ _FORMAT_LE (U20),
+ _FORMAT_BE (U20),
+ _FORMAT_LE (S18),
+ _FORMAT_BE (S18),
+ _FORMAT_LE (U18),
+ _FORMAT_BE (U18),
+ _FORMAT_LE (F32),
+ _FORMAT_BE (F32),
+ _FORMAT_LE (F64),
+ _FORMAT_BE (F64),
+};
+
+typedef struct {
+ struct spa_pod_builder b;
+ const struct media_type *type;
+ uint32_t id;
+ const GstCapsFeatures *cf;
+ const GstStructure *cs;
+ GPtrArray *array;
+} ConvertData;
+
+static const struct media_type *
+find_media_types (const char *name)
+{
+ int i;
+ for (i = 0; media_type_map[i].name; i++) {
+ if (spa_streq(media_type_map[i].name, name))
+ return &media_type_map[i];
+ }
+ return NULL;
+}
+
+static int find_index(const uint32_t *items, int n_items, uint32_t id)
+{
+ int i;
+ for (i = 0; i < n_items; i++)
+ if (items[i] == id)
+ return i;
+ return -1;
+}
+
+static const char *
+get_nth_string (const GValue *val, int idx)
+{
+ const GValue *v = NULL;
+ GType type = G_VALUE_TYPE (val);
+
+ if (type == G_TYPE_STRING && idx == 0)
+ v = val;
+ else if (type == GST_TYPE_LIST) {
+ GArray *array = g_value_peek_pointer (val);
+ if (idx < (int)(array->len + 1)) {
+ v = &g_array_index (array, GValue, SPA_MAX (idx - 1, 0));
+ }
+ }
+ if (v)
+ return g_value_get_string (v);
+
+ return NULL;
+}
+
+static bool
+get_nth_int (const GValue *val, int idx, int *res)
+{
+ const GValue *v = NULL;
+ GType type = G_VALUE_TYPE (val);
+
+ if (type == G_TYPE_INT && idx == 0) {
+ v = val;
+ } else if (type == GST_TYPE_INT_RANGE) {
+ if (idx == 0 || idx == 1) {
+ *res = gst_value_get_int_range_min (val);
+ return true;
+ } else if (idx == 2) {
+ *res = gst_value_get_int_range_max (val);
+ return true;
+ }
+ } else if (type == GST_TYPE_LIST) {
+ GArray *array = g_value_peek_pointer (val);
+ if (idx < (int)(array->len + 1)) {
+ v = &g_array_index (array, GValue, SPA_MAX (idx - 1, 0));
+ }
+ }
+ if (v) {
+ *res = g_value_get_int (v);
+ return true;
+ }
+ return false;
+}
+
+static gboolean
+get_nth_fraction (const GValue *val, int idx, struct spa_fraction *f)
+{
+ const GValue *v = NULL;
+ GType type = G_VALUE_TYPE (val);
+
+ if (type == GST_TYPE_FRACTION && idx == 0) {
+ v = val;
+ } else if (type == GST_TYPE_FRACTION_RANGE) {
+ if (idx == 0 || idx == 1) {
+ v = gst_value_get_fraction_range_min (val);
+ } else if (idx == 2) {
+ v = gst_value_get_fraction_range_max (val);
+ }
+ } else if (type == GST_TYPE_LIST) {
+ GArray *array = g_value_peek_pointer (val);
+ if (idx < (int)(array->len + 1)) {
+ v = &g_array_index (array, GValue, SPA_MAX (idx-1, 0));
+ }
+ }
+ if (v) {
+ f->num = gst_value_get_fraction_numerator (v);
+ f->denom = gst_value_get_fraction_denominator (v);
+ return true;
+ }
+ return false;
+}
+
+static gboolean
+get_nth_rectangle (const GValue *width, const GValue *height, int idx, struct spa_rectangle *r)
+{
+ const GValue *w = NULL, *h = NULL;
+ GType wt = G_VALUE_TYPE (width);
+ GType ht = G_VALUE_TYPE (height);
+
+ if (wt == G_TYPE_INT && ht == G_TYPE_INT && idx == 0) {
+ w = width;
+ h = height;
+ } else if (wt == GST_TYPE_INT_RANGE && ht == GST_TYPE_INT_RANGE) {
+ if (idx == 0 || idx == 1) {
+ r->width = gst_value_get_int_range_min (width);
+ r->height = gst_value_get_int_range_min (height);
+ return true;
+ } else if (idx == 2) {
+ r->width = gst_value_get_int_range_max (width);
+ r->height = gst_value_get_int_range_max (height);
+ return true;
+ } else if (idx == 3) {
+ r->width = gst_value_get_int_range_step (width);
+ r->height = gst_value_get_int_range_step (height);
+ if (r->width > 1 || r->height > 1)
+ return true;
+ else
+ return false;
+ }
+ } else if (wt == GST_TYPE_LIST && ht == GST_TYPE_LIST) {
+ GArray *wa = g_value_peek_pointer (width);
+ GArray *ha = g_value_peek_pointer (height);
+ if (idx < (int)(wa->len + 1))
+ w = &g_array_index (wa, GValue, SPA_MAX (idx-1, 0));
+ if (idx < (int)(ha->len + 1))
+ h = &g_array_index (ha, GValue, SPA_MAX (idx-1, 0));
+ }
+ if (w && h) {
+ r->width = g_value_get_int (w);
+ r->height = g_value_get_int (h);
+ return true;
+ }
+ return false;
+}
+
+static uint32_t
+get_range_type (const GValue *val)
+{
+ GType type = G_VALUE_TYPE (val);
+
+ if (type == GST_TYPE_LIST)
+ return SPA_CHOICE_Enum;
+ if (type == GST_TYPE_DOUBLE_RANGE || type == GST_TYPE_FRACTION_RANGE)
+ return SPA_CHOICE_Range;
+ if (type == GST_TYPE_INT_RANGE) {
+ if (gst_value_get_int_range_step (val) == 1)
+ return SPA_CHOICE_Range;
+ else
+ return SPA_CHOICE_Step;
+ }
+ if (type == GST_TYPE_INT64_RANGE) {
+ if (gst_value_get_int64_range_step (val) == 1)
+ return SPA_CHOICE_Range;
+ else
+ return SPA_CHOICE_Step;
+ }
+ return SPA_CHOICE_None;
+}
+
+static uint32_t
+get_range_type2 (const GValue *v1, const GValue *v2)
+{
+ uint32_t r1, r2;
+
+ r1 = get_range_type (v1);
+ r2 = get_range_type (v2);
+
+ if (r1 == r2)
+ return r1;
+ if (r1 == SPA_CHOICE_Step || r2 == SPA_CHOICE_Step)
+ return SPA_CHOICE_Step;
+ if (r1 == SPA_CHOICE_Range || r2 == SPA_CHOICE_Range)
+ return SPA_CHOICE_Range;
+ return SPA_CHOICE_Range;
+}
+
+static gboolean
+handle_video_fields (ConvertData *d)
+{
+ const GValue *value, *value2;
+ int i;
+ struct spa_pod_choice *choice;
+ struct spa_pod_frame f;
+
+ value = gst_structure_get_value (d->cs, "format");
+ if (value) {
+ const char *v;
+ int idx;
+ for (i = 0; (v = get_nth_string (value, i)); i++) {
+ if (i == 0) {
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_VIDEO_format, 0);
+ spa_pod_builder_push_choice(&d->b, &f, get_range_type (value), 0);
+ }
+
+ idx = gst_video_format_from_string (v);
+ if (idx != GST_VIDEO_FORMAT_UNKNOWN && idx < (int)SPA_N_ELEMENTS (video_format_map))
+ spa_pod_builder_id (&d->b, video_format_map[idx]);
+ }
+ if (i > 0) {
+ choice = spa_pod_builder_pop(&d->b, &f);
+ if (i == 1)
+ choice->body.type = SPA_CHOICE_None;
+ }
+ }
+ value = gst_structure_get_value (d->cs, "width");
+ value2 = gst_structure_get_value (d->cs, "height");
+ if (value && value2) {
+ struct spa_rectangle v;
+ for (i = 0; get_nth_rectangle (value, value2, i, &v); i++) {
+ if (i == 0) {
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_VIDEO_size, 0);
+ spa_pod_builder_push_choice(&d->b, &f, get_range_type2 (value, value2), 0);
+ }
+
+ spa_pod_builder_rectangle (&d->b, v.width, v.height);
+ }
+ if (i > 0) {
+ choice = spa_pod_builder_pop(&d->b, &f);
+ if (i == 1)
+ choice->body.type = SPA_CHOICE_None;
+ }
+ }
+
+ value = gst_structure_get_value (d->cs, "framerate");
+ if (value) {
+ struct spa_fraction v;
+ for (i = 0; get_nth_fraction (value, i, &v); i++) {
+ if (i == 0) {
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_VIDEO_framerate, 0);
+ spa_pod_builder_push_choice(&d->b, &f, get_range_type (value), 0);
+ }
+
+ spa_pod_builder_fraction (&d->b, v.num, v.denom);
+ }
+ if (i > 0) {
+ choice = spa_pod_builder_pop(&d->b, &f);
+ if (i == 1)
+ choice->body.type = SPA_CHOICE_None;
+ }
+ }
+
+ value = gst_structure_get_value (d->cs, "max-framerate");
+ if (value) {
+ struct spa_fraction v;
+ for (i = 0; get_nth_fraction (value, i, &v); i++) {
+ if (i == 0) {
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_VIDEO_maxFramerate, 0);
+ spa_pod_builder_push_choice(&d->b, &f, get_range_type (value), 0);
+ }
+
+ spa_pod_builder_fraction (&d->b, v.num, v.denom);
+ }
+ if (i > 0) {
+ choice = spa_pod_builder_pop(&d->b, &f);
+ if (i == 1)
+ choice->body.type = SPA_CHOICE_None;
+ }
+ }
+ return TRUE;
+}
+
+static void
+set_default_channels (struct spa_pod_builder *b, uint32_t channels)
+{
+ uint32_t position[SPA_AUDIO_MAX_CHANNELS] = {0};
+ gboolean ok = TRUE;
+
+ switch (channels) {
+ case 8:
+ position[6] = SPA_AUDIO_CHANNEL_SL;
+ position[7] = SPA_AUDIO_CHANNEL_SR;
+ SPA_FALLTHROUGH
+ case 6:
+ position[5] = SPA_AUDIO_CHANNEL_LFE;
+ SPA_FALLTHROUGH
+ case 5:
+ position[4] = SPA_AUDIO_CHANNEL_FC;
+ SPA_FALLTHROUGH
+ case 4:
+ position[2] = SPA_AUDIO_CHANNEL_RL;
+ position[3] = SPA_AUDIO_CHANNEL_RR;
+ SPA_FALLTHROUGH
+ case 2:
+ position[0] = SPA_AUDIO_CHANNEL_FL;
+ position[1] = SPA_AUDIO_CHANNEL_FR;
+ break;
+ case 1:
+ position[0] = SPA_AUDIO_CHANNEL_MONO;
+ break;
+ default:
+ ok = FALSE;
+ break;
+ }
+
+ if (ok)
+ spa_pod_builder_add (b, SPA_FORMAT_AUDIO_position,
+ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, channels, position), 0);
+}
+
+static gboolean
+handle_audio_fields (ConvertData *d)
+{
+ const GValue *value;
+ struct spa_pod_choice *choice;
+ struct spa_pod_frame f;
+ int i = 0;
+
+ value = gst_structure_get_value (d->cs, "format");
+ if (value) {
+ const char *v;
+ int idx;
+ for (i = 0; (v = get_nth_string (value, i)); i++) {
+ if (i == 0) {
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_AUDIO_format, 0);
+ spa_pod_builder_push_choice(&d->b, &f, get_range_type (value), 0);
+ }
+
+ idx = gst_audio_format_from_string (v);
+ if (idx < (int)SPA_N_ELEMENTS (audio_format_map))
+ spa_pod_builder_id (&d->b, audio_format_map[idx]);
+ }
+ if (i > 0) {
+ choice = spa_pod_builder_pop(&d->b, &f);
+ if (i == 1)
+ choice->body.type = SPA_CHOICE_None;
+ }
+ } else if (strcmp(d->type->name, "audio/x-mulaw") == 0) {
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_AUDIO_format, 0);
+ spa_pod_builder_id (&d->b, SPA_AUDIO_FORMAT_ULAW);
+ } else if (strcmp(d->type->name, "audio/x-alaw") == 0) {
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_AUDIO_format, 0);
+ spa_pod_builder_id (&d->b, SPA_AUDIO_FORMAT_ALAW);
+ } else if (strcmp(d->type->name, "audio/mpeg") == 0) {
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_AUDIO_format, 0);
+ spa_pod_builder_id (&d->b, SPA_AUDIO_FORMAT_ENCODED);
+ } else if (strcmp(d->type->name, "audio/x-flac") == 0) {
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_AUDIO_format, 0);
+ spa_pod_builder_id (&d->b, SPA_AUDIO_FORMAT_ENCODED);
+ }
+
+#if 0
+ value = gst_structure_get_value (d->cs, "layout");
+ if (value) {
+ const char *v;
+ for (i = 0; (v = get_nth_string (value, i)); i++) {
+ enum spa_audio_layout layout;
+
+ if (spa_streq(v, "interleaved"))
+ layout = SPA_AUDIO_LAYOUT_INTERLEAVED;
+ else if (spa_streq(v, "non-interleaved"))
+ layout = SPA_AUDIO_LAYOUT_NON_INTERLEAVED;
+ else
+ break;
+
+ if (i == 0) {
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_AUDIO_layout, 0);
+ spa_pod_builder_push_choice(&d->b, &f, get_range_type (value), 0);
+ }
+
+ spa_pod_builder_id (&d->b, layout);
+ }
+ if (i > 0) {
+ choice = spa_pod_builder_pop(&d->b, &f);
+ if (i == 1)
+ choice->body.type = SPA_CHOICE_None;
+ }
+ }
+#endif
+ value = gst_structure_get_value (d->cs, "rate");
+ if (value) {
+ int v;
+ for (i = 0; get_nth_int (value, i, &v); i++) {
+ if (i == 0) {
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_AUDIO_rate, 0);
+ spa_pod_builder_push_choice(&d->b, &f, get_range_type (value), 0);
+ }
+
+ spa_pod_builder_int (&d->b, v);
+ }
+ if (i > 0) {
+ choice = spa_pod_builder_pop(&d->b, &f);
+ if (i == 1)
+ choice->body.type = SPA_CHOICE_None;
+ }
+ }
+ value = gst_structure_get_value (d->cs, "channels");
+ if (value) {
+ int v;
+ for (i = 0; get_nth_int (value, i, &v); i++) {
+ if (i == 0) {
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_AUDIO_channels, 0);
+ spa_pod_builder_push_choice(&d->b, &f, get_range_type (value), 0);
+ }
+
+ spa_pod_builder_int (&d->b, v);
+ }
+ if (i > 0) {
+ choice = spa_pod_builder_pop(&d->b, &f);
+ if (i == 1) {
+ choice->body.type = SPA_CHOICE_None;
+ set_default_channels (&d->b, v);
+ }
+ }
+ }
+ return TRUE;
+}
+
+static int
+builder_overflow (void *event_data, uint32_t size)
+{
+ struct spa_pod_builder *b = event_data;
+ b->size = SPA_ROUND_UP_N (size, 512);
+ b->data = realloc (b->data, b->size);
+ if (b->data == NULL)
+ return -errno;
+ return 0;
+}
+
+static const struct spa_pod_builder_callbacks builder_callbacks = {
+ SPA_VERSION_POD_BUILDER_CALLBACKS,
+ .overflow = builder_overflow
+};
+
+static struct spa_pod *
+convert_1 (ConvertData *d)
+{
+ struct spa_pod_frame f;
+
+ if (!(d->type = find_media_types (gst_structure_get_name (d->cs))))
+ return NULL;
+
+ spa_pod_builder_set_callbacks(&d->b, &builder_callbacks, &d->b);
+
+ spa_pod_builder_push_object (&d->b, &f, SPA_TYPE_OBJECT_Format, d->id);
+
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_mediaType, 0);
+ spa_pod_builder_id(&d->b, d->type->media_type);
+
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_mediaSubtype, 0);
+ spa_pod_builder_id(&d->b, d->type->media_subtype);
+
+ if (d->cf && gst_caps_features_contains (d->cf, GST_CAPS_FEATURE_MEMORY_DMABUF)) {
+ struct spa_pod_frame f2;
+
+ spa_pod_builder_prop (&d->b, SPA_FORMAT_VIDEO_modifier,
+ (SPA_POD_PROP_FLAG_MANDATORY | SPA_POD_PROP_FLAG_DONT_FIXATE));
+ spa_pod_builder_push_choice (&d->b, &f2, SPA_CHOICE_Enum, 0);
+ spa_pod_builder_long (&d->b, DRM_FORMAT_MOD_INVALID);
+ spa_pod_builder_long (&d->b, DRM_FORMAT_MOD_INVALID);
+ spa_pod_builder_long (&d->b, DRM_FORMAT_MOD_LINEAR);
+ spa_pod_builder_pop (&d->b, &f2);
+ }
+
+ if (d->type->media_type == SPA_MEDIA_TYPE_video)
+ handle_video_fields (d);
+ else if (d->type->media_type == SPA_MEDIA_TYPE_audio)
+ handle_audio_fields (d);
+
+ spa_pod_builder_pop (&d->b, &f);
+
+ return SPA_PTROFF (d->b.data, 0, struct spa_pod);
+}
+
+struct spa_pod *
+gst_caps_to_format (GstCaps *caps, guint index, uint32_t id)
+{
+ ConvertData d;
+ struct spa_pod *res;
+
+ g_return_val_if_fail (GST_IS_CAPS (caps), NULL);
+ g_return_val_if_fail (gst_caps_is_fixed (caps), NULL);
+
+ spa_zero (d);
+ d.cf = gst_caps_get_features (caps, index);
+ d.cs = gst_caps_get_structure (caps, index);
+ d.id = id;
+
+ res = convert_1 (&d);
+
+ return res;
+}
+
+static gboolean
+foreach_func (GstCapsFeatures *features,
+ GstStructure *structure,
+ ConvertData *d)
+{
+ struct spa_pod *fmt;
+ int idx;
+
+ spa_zero(d->b);
+ d->cf = features;
+ d->cs = structure;
+
+ if (d->cf && gst_caps_features_contains (d->cf, GST_CAPS_FEATURE_MEMORY_DMABUF))
+ idx = 0;
+ else
+ idx = -1;
+
+ if ((fmt = convert_1 (d)))
+ g_ptr_array_insert (d->array, idx, fmt);
+
+ return TRUE;
+}
+
+
+GPtrArray *
+gst_caps_to_format_all (GstCaps *caps, uint32_t id)
+{
+ ConvertData d;
+
+ spa_zero (d);
+ d.id = id;
+ d.array = g_ptr_array_new_full (gst_caps_get_size (caps), (GDestroyNotify)g_free);
+
+ gst_caps_foreach (caps, (GstCapsForeachFunc) foreach_func, &d);
+
+ return d.array;
+}
+
+typedef const char *(*id_to_string_func)(uint32_t id);
+
+static const char *video_id_to_string(uint32_t id)
+{
+ int idx;
+ if ((idx = find_index(video_format_map, SPA_N_ELEMENTS(video_format_map), id)) == -1)
+ return NULL;
+ return gst_video_format_to_string(idx);
+}
+
+static const char *audio_id_to_string(uint32_t id)
+{
+ int idx;
+ if ((idx = find_index(audio_format_map, SPA_N_ELEMENTS(audio_format_map), id)) == -1)
+ return NULL;
+ return gst_audio_format_to_string(idx);
+}
+
+static void
+handle_id_prop (const struct spa_pod_prop *prop, const char *key, id_to_string_func func, GstCaps *res)
+{
+ const char * str;
+ struct spa_pod *val;
+ uint32_t *id;
+ uint32_t i, n_items, choice;
+
+ val = spa_pod_get_values(&prop->value, &n_items, &choice);
+ if (val->type != SPA_TYPE_Id)
+ return;
+
+ id = SPA_POD_BODY(val);
+
+ switch (choice) {
+ case SPA_CHOICE_None:
+ if (!(str = func(id[0])))
+ return;
+ gst_caps_set_simple (res, key, G_TYPE_STRING, str, NULL);
+ break;
+ case SPA_CHOICE_Enum:
+ {
+ GValue list = { 0 }, v = { 0 };
+
+ g_value_init (&list, GST_TYPE_LIST);
+ for (i = 1; i < n_items; i++) {
+ if (!(str = func(id[i])))
+ continue;
+
+ g_value_init (&v, G_TYPE_STRING);
+ g_value_set_string (&v, str);
+ gst_value_list_append_and_take_value (&list, &v);
+ }
+ gst_caps_set_value (res, key, &list);
+ g_value_unset (&list);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static void
+handle_int_prop (const struct spa_pod_prop *prop, const char *key, GstCaps *res)
+{
+ struct spa_pod *val;
+ uint32_t *ints;
+ uint32_t i, n_items, choice;
+
+ val = spa_pod_get_values(&prop->value, &n_items, &choice);
+ if (val->type != SPA_TYPE_Int)
+ return;
+
+ ints = SPA_POD_BODY(val);
+
+ switch (choice) {
+ case SPA_CHOICE_None:
+ gst_caps_set_simple (res, key, G_TYPE_INT, ints[0], NULL);
+ break;
+ case SPA_CHOICE_Range:
+ case SPA_CHOICE_Step:
+ {
+ if (n_items < 3)
+ return;
+ gst_caps_set_simple (res, key, GST_TYPE_INT_RANGE, ints[1], ints[2], NULL);
+ break;
+ }
+ case SPA_CHOICE_Enum:
+ {
+ GValue list = { 0 }, v = { 0 };
+
+ g_value_init (&list, GST_TYPE_LIST);
+ for (i = 1; i < n_items; i++) {
+ g_value_init (&v, G_TYPE_INT);
+ g_value_set_int (&v, ints[i]);
+ gst_value_list_append_and_take_value (&list, &v);
+ }
+ gst_caps_set_value (res, key, &list);
+ g_value_unset (&list);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static void
+handle_rect_prop (const struct spa_pod_prop *prop, const char *width, const char *height, GstCaps *res)
+{
+ struct spa_pod *val;
+ struct spa_rectangle *rect;
+ uint32_t i, n_items, choice;
+
+ val = spa_pod_get_values(&prop->value, &n_items, &choice);
+ if (val->type != SPA_TYPE_Rectangle)
+ return;
+
+ rect = SPA_POD_BODY(val);
+
+ switch (choice) {
+ case SPA_CHOICE_None:
+ gst_caps_set_simple (res, width, G_TYPE_INT, rect[0].width,
+ height, G_TYPE_INT, rect[0].height, NULL);
+ break;
+ case SPA_CHOICE_Range:
+ case SPA_CHOICE_Step:
+ {
+ if (n_items < 3)
+ return;
+ gst_caps_set_simple (res, width, GST_TYPE_INT_RANGE, rect[1].width, rect[2].width,
+ height, GST_TYPE_INT_RANGE, rect[1].height, rect[2].height, NULL);
+ break;
+ }
+ case SPA_CHOICE_Enum:
+ {
+ GValue l1 = { 0 }, l2 = { 0 }, v1 = { 0 }, v2 = { 0 };
+
+ g_value_init (&l1, GST_TYPE_LIST);
+ g_value_init (&l2, GST_TYPE_LIST);
+ for (i = 1; i < n_items; i++) {
+ g_value_init (&v1, G_TYPE_INT);
+ g_value_set_int (&v1, rect[i].width);
+ gst_value_list_append_and_take_value (&l1, &v1);
+
+ g_value_init (&v2, G_TYPE_INT);
+ g_value_set_int (&v2, rect[i].height);
+ gst_value_list_append_and_take_value (&l2, &v2);
+ }
+ gst_caps_set_value (res, width, &l1);
+ gst_caps_set_value (res, height, &l2);
+ g_value_unset (&l1);
+ g_value_unset (&l2);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+static void
+handle_fraction_prop (const struct spa_pod_prop *prop, const char *key, GstCaps *res)
+{
+ struct spa_pod *val;
+ struct spa_fraction *fract;
+ uint32_t i, n_items, choice;
+
+ val = spa_pod_get_values(&prop->value, &n_items, &choice);
+ if (val->type != SPA_TYPE_Fraction)
+ return;
+
+ fract = SPA_POD_BODY(val);
+
+ switch (choice) {
+ case SPA_CHOICE_None:
+ gst_caps_set_simple (res, key, GST_TYPE_FRACTION, fract[0].num, fract[0].denom, NULL);
+ break;
+ case SPA_CHOICE_Range:
+ case SPA_CHOICE_Step:
+ {
+ if (n_items < 3)
+ return;
+ gst_caps_set_simple (res, key, GST_TYPE_FRACTION_RANGE, fract[1].num, fract[1].denom,
+ fract[2].num, fract[2].denom, NULL);
+ break;
+ }
+ case SPA_CHOICE_Enum:
+ {
+ GValue l1 = { 0 }, v1 = { 0 };
+
+ g_value_init (&l1, GST_TYPE_LIST);
+ for (i = 1; i < n_items; i++) {
+ g_value_init (&v1, GST_TYPE_FRACTION);
+ gst_value_set_fraction (&v1, fract[i].num, fract[i].denom);
+ gst_value_list_append_and_take_value (&l1, &v1);
+ }
+ gst_caps_set_value (res, key, &l1);
+ g_value_unset (&l1);
+ break;
+ }
+ default:
+ break;
+ }
+}
+GstCaps *
+gst_caps_from_format (const struct spa_pod *format)
+{
+ GstCaps *res = NULL;
+ uint32_t media_type, media_subtype;
+ const struct spa_pod_prop *prop = NULL;
+ const struct spa_pod_object *obj = (const struct spa_pod_object *) format;
+
+ if (spa_format_parse(format, &media_type, &media_subtype) < 0)
+ return res;
+
+ if (media_type == SPA_MEDIA_TYPE_video) {
+ if (media_subtype == SPA_MEDIA_SUBTYPE_raw) {
+ res = gst_caps_new_empty_simple ("video/x-raw");
+ if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_VIDEO_format))) {
+ handle_id_prop (prop, "format", video_id_to_string, res);
+ }
+ }
+ else if (media_subtype == SPA_MEDIA_SUBTYPE_mjpg) {
+ res = gst_caps_new_empty_simple ("image/jpeg");
+ }
+ else if (media_subtype == SPA_MEDIA_SUBTYPE_h264) {
+ res = gst_caps_new_simple ("video/x-h264",
+ "stream-format", G_TYPE_STRING, "byte-stream",
+ "alignment", G_TYPE_STRING, "au",
+ NULL);
+ } else {
+ return NULL;
+ }
+ if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_VIDEO_size))) {
+ handle_rect_prop (prop, "width", "height", res);
+ }
+ if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_VIDEO_framerate))) {
+ handle_fraction_prop (prop, "framerate", res);
+ }
+ if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_VIDEO_maxFramerate))) {
+ handle_fraction_prop (prop, "max-framerate", res);
+ }
+ } else if (media_type == SPA_MEDIA_TYPE_audio) {
+ if (media_subtype == SPA_MEDIA_SUBTYPE_raw) {
+ res = gst_caps_new_simple ("audio/x-raw",
+ "layout", G_TYPE_STRING, "interleaved",
+ NULL);
+ if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_AUDIO_format))) {
+ handle_id_prop (prop, "format", audio_id_to_string, res);
+ }
+ if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_AUDIO_rate))) {
+ handle_int_prop (prop, "rate", res);
+ }
+ if ((prop = spa_pod_object_find_prop (obj, prop, SPA_FORMAT_AUDIO_channels))) {
+ handle_int_prop (prop, "channels", res);
+ }
+ }
+ }
+ return res;
+}
diff --git a/src/gst/gstpipewireformat.h b/src/gst/gstpipewireformat.h
new file mode 100644
index 0000000..e077986
--- /dev/null
+++ b/src/gst/gstpipewireformat.h
@@ -0,0 +1,42 @@
+/* GStreamer
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _GST_PIPEWIRE_FORMAT_H_
+#define _GST_PIPEWIRE_FORMAT_H_
+
+#include <gst/gst.h>
+
+#include <spa/pod/pod.h>
+
+G_BEGIN_DECLS
+
+struct spa_pod * gst_caps_to_format (GstCaps *caps,
+ guint index, uint32_t id);
+GPtrArray * gst_caps_to_format_all (GstCaps *caps, uint32_t id);
+
+GstCaps * gst_caps_from_format (const struct spa_pod *format);
+
+G_END_DECLS
+
+#endif
diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c
new file mode 100644
index 0000000..7ecb802
--- /dev/null
+++ b/src/gst/gstpipewirepool.c
@@ -0,0 +1,276 @@
+/* GStreamer
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+
+#include <gst/gst.h>
+
+#include <gst/allocators/gstfdmemory.h>
+#include <gst/allocators/gstdmabuf.h>
+
+#include <gst/video/gstvideometa.h>
+
+#include "gstpipewirepool.h"
+
+GST_DEBUG_CATEGORY_STATIC (gst_pipewire_pool_debug_category);
+#define GST_CAT_DEFAULT gst_pipewire_pool_debug_category
+
+G_DEFINE_TYPE (GstPipeWirePool, gst_pipewire_pool, GST_TYPE_BUFFER_POOL);
+
+enum
+{
+ ACTIVATED,
+ /* FILL ME */
+ LAST_SIGNAL
+};
+
+
+static guint pool_signals[LAST_SIGNAL] = { 0 };
+
+static GQuark pool_data_quark;
+
+GstPipeWirePool *
+gst_pipewire_pool_new (void)
+{
+ GstPipeWirePool *pool;
+
+ pool = g_object_new (GST_TYPE_PIPEWIRE_POOL, NULL);
+
+ return pool;
+}
+
+static void
+pool_data_destroy (gpointer user_data)
+{
+ GstPipeWirePoolData *data = user_data;
+
+ gst_object_unref (data->pool);
+ g_slice_free (GstPipeWirePoolData, data);
+}
+
+void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b)
+{
+ GstBuffer *buf;
+ uint32_t i;
+ GstPipeWirePoolData *data;
+
+ GST_LOG_OBJECT (pool, "wrap buffer");
+
+ data = g_slice_new (GstPipeWirePoolData);
+
+ buf = gst_buffer_new ();
+
+ for (i = 0; i < b->buffer->n_datas; i++) {
+ struct spa_data *d = &b->buffer->datas[i];
+ GstMemory *gmem = NULL;
+
+ GST_LOG_OBJECT (pool, "wrap buffer %d %d", d->mapoffset, d->maxsize);
+ if (d->type == SPA_DATA_MemFd) {
+ GST_LOG_OBJECT (pool, "memory type MemFd");
+ gmem = gst_fd_allocator_alloc (pool->fd_allocator, dup(d->fd),
+ d->mapoffset + d->maxsize, GST_FD_MEMORY_FLAG_NONE);
+ gst_memory_resize (gmem, d->mapoffset, d->maxsize);
+ }
+ else if(d->type == SPA_DATA_DmaBuf) {
+ GST_LOG_OBJECT (pool, "memory type DmaBuf");
+ gmem = gst_fd_allocator_alloc (pool->dmabuf_allocator, dup(d->fd),
+ d->mapoffset + d->maxsize, GST_FD_MEMORY_FLAG_NONE);
+ gst_memory_resize (gmem, d->mapoffset, d->maxsize);
+ }
+ else if (d->type == SPA_DATA_MemPtr) {
+ GST_LOG_OBJECT (pool, "memory type MemPtr");
+ gmem = gst_memory_new_wrapped (0, d->data, d->maxsize, 0,
+ d->maxsize, NULL, NULL);
+ }
+ if (gmem)
+ gst_buffer_insert_memory (buf, i, gmem);
+ }
+
+ data->pool = gst_object_ref (pool);
+ data->owner = NULL;
+ data->header = spa_buffer_find_meta_data (b->buffer, SPA_META_Header, sizeof(*data->header));
+ data->flags = GST_BUFFER_FLAGS (buf);
+ data->b = b;
+ data->buf = buf;
+ data->crop = spa_buffer_find_meta_data (b->buffer, SPA_META_VideoCrop, sizeof(*data->crop));
+ if (data->crop)
+ gst_buffer_add_video_crop_meta(buf);
+ data->videotransform =
+ spa_buffer_find_meta_data (b->buffer, SPA_META_VideoTransform, sizeof(*data->videotransform));
+
+ gst_mini_object_set_qdata (GST_MINI_OBJECT_CAST (buf),
+ pool_data_quark,
+ data,
+ pool_data_destroy);
+ b->user_data = data;
+}
+
+GstPipeWirePoolData *gst_pipewire_pool_get_data (GstBuffer *buffer)
+{
+ return gst_mini_object_get_qdata (GST_MINI_OBJECT_CAST (buffer), pool_data_quark);
+}
+
+#if 0
+gboolean
+gst_pipewire_pool_add_buffer (GstPipeWirePool *pool, GstBuffer *buffer)
+{
+ g_return_val_if_fail (GST_IS_PIPEWIRE_POOL (pool), FALSE);
+ g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE);
+
+ GST_OBJECT_LOCK (pool);
+ g_queue_push_tail (&pool->available, buffer);
+ g_cond_signal (&pool->cond);
+ GST_OBJECT_UNLOCK (pool);
+
+ return TRUE;
+}
+
+gboolean
+gst_pipewire_pool_remove_buffer (GstPipeWirePool *pool, GstBuffer *buffer)
+{
+ gboolean res;
+
+ g_return_val_if_fail (GST_IS_PIPEWIRE_POOL (pool), FALSE);
+ g_return_val_if_fail (GST_IS_BUFFER (buffer), FALSE);
+
+ GST_OBJECT_LOCK (pool);
+ res = g_queue_remove (&pool->available, buffer);
+ GST_OBJECT_UNLOCK (pool);
+
+ return res;
+}
+#endif
+
+static GstFlowReturn
+acquire_buffer (GstBufferPool * pool, GstBuffer ** buffer,
+ GstBufferPoolAcquireParams * params)
+{
+ GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool);
+ GstPipeWirePoolData *data;
+ struct pw_buffer *b;
+
+ GST_OBJECT_LOCK (pool);
+ while (TRUE) {
+ if (G_UNLIKELY (GST_BUFFER_POOL_IS_FLUSHING (pool)))
+ goto flushing;
+
+ if ((b = pw_stream_dequeue_buffer(p->stream)))
+ break;
+
+ if (params && (params->flags & GST_BUFFER_POOL_ACQUIRE_FLAG_DONTWAIT))
+ goto no_more_buffers;
+
+ GST_WARNING ("queue empty");
+ g_cond_wait (&p->cond, GST_OBJECT_GET_LOCK (pool));
+ }
+
+ data = b->user_data;
+ *buffer = data->buf;
+
+ GST_OBJECT_UNLOCK (pool);
+ GST_DEBUG ("acquire buffer %p", *buffer);
+
+ return GST_FLOW_OK;
+
+flushing:
+ {
+ GST_OBJECT_UNLOCK (pool);
+ return GST_FLOW_FLUSHING;
+ }
+no_more_buffers:
+ {
+ GST_LOG_OBJECT (pool, "no more buffers");
+ GST_OBJECT_UNLOCK (pool);
+ return GST_FLOW_EOS;
+ }
+}
+
+static void
+flush_start (GstBufferPool * pool)
+{
+ GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool);
+
+ GST_DEBUG ("flush start");
+ GST_OBJECT_LOCK (pool);
+ g_cond_signal (&p->cond);
+ GST_OBJECT_UNLOCK (pool);
+}
+
+static void
+release_buffer (GstBufferPool * pool, GstBuffer *buffer)
+{
+ GST_DEBUG ("release buffer %p", buffer);
+}
+
+static gboolean
+do_start (GstBufferPool * pool)
+{
+ g_signal_emit (pool, pool_signals[ACTIVATED], 0, NULL);
+ return TRUE;
+}
+
+static void
+gst_pipewire_pool_finalize (GObject * object)
+{
+ GstPipeWirePool *pool = GST_PIPEWIRE_POOL (object);
+
+ GST_DEBUG_OBJECT (pool, "finalize");
+ g_object_unref (pool->fd_allocator);
+ g_object_unref (pool->dmabuf_allocator);
+
+ G_OBJECT_CLASS (gst_pipewire_pool_parent_class)->finalize (object);
+}
+
+static void
+gst_pipewire_pool_class_init (GstPipeWirePoolClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstBufferPoolClass *bufferpool_class = GST_BUFFER_POOL_CLASS (klass);
+
+ gobject_class->finalize = gst_pipewire_pool_finalize;
+
+ bufferpool_class->start = do_start;
+ bufferpool_class->flush_start = flush_start;
+ bufferpool_class->acquire_buffer = acquire_buffer;
+ bufferpool_class->release_buffer = release_buffer;
+
+ pool_signals[ACTIVATED] =
+ g_signal_new ("activated", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 0, G_TYPE_NONE);
+
+ GST_DEBUG_CATEGORY_INIT (gst_pipewire_pool_debug_category, "pipewirepool", 0,
+ "debug category for pipewirepool object");
+
+ pool_data_quark = g_quark_from_static_string ("GstPipeWirePoolDataQuark");
+}
+
+static void
+gst_pipewire_pool_init (GstPipeWirePool * pool)
+{
+ pool->fd_allocator = gst_fd_allocator_new ();
+ pool->dmabuf_allocator = gst_dmabuf_allocator_new ();
+ g_cond_init (&pool->cond);
+}
diff --git a/src/gst/gstpipewirepool.h b/src/gst/gstpipewirepool.h
new file mode 100644
index 0000000..acf8106
--- /dev/null
+++ b/src/gst/gstpipewirepool.h
@@ -0,0 +1,92 @@
+/* GStreamer
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __GST_PIPEWIRE_POOL_H__
+#define __GST_PIPEWIRE_POOL_H__
+
+#include <gst/gst.h>
+
+#include <pipewire/pipewire.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_PIPEWIRE_POOL \
+ (gst_pipewire_pool_get_type())
+#define GST_PIPEWIRE_POOL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_POOL,GstPipeWirePool))
+#define GST_PIPEWIRE_POOL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_POOL,GstPipeWirePoolClass))
+#define GST_IS_PIPEWIRE_POOL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_POOL))
+#define GST_IS_PIPEWIRE_POOL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_POOL))
+#define GST_PIPEWIRE_POOL_GET_CLASS(klass) \
+ (G_TYPE_INSTANCE_GET_CLASS ((klass), GST_TYPE_PIPEWIRE_POOL, GstPipeWirePoolClass))
+
+typedef struct _GstPipeWirePoolData GstPipeWirePoolData;
+typedef struct _GstPipeWirePool GstPipeWirePool;
+typedef struct _GstPipeWirePoolClass GstPipeWirePoolClass;
+
+struct _GstPipeWirePoolData {
+ GstPipeWirePool *pool;
+ void *owner;
+ struct spa_meta_header *header;
+ guint flags;
+ struct pw_buffer *b;
+ GstBuffer *buf;
+ gboolean queued;
+ struct spa_meta_region *crop;
+ struct spa_meta_videotransform *videotransform;
+};
+
+struct _GstPipeWirePool {
+ GstBufferPool parent;
+
+ struct pw_stream *stream;
+ struct pw_type *t;
+
+ GstAllocator *fd_allocator;
+ GstAllocator *dmabuf_allocator;
+
+ GCond cond;
+};
+
+struct _GstPipeWirePoolClass {
+ GstBufferPoolClass parent_class;
+};
+
+GType gst_pipewire_pool_get_type (void);
+
+GstPipeWirePool * gst_pipewire_pool_new (void);
+
+void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *buffer);
+
+GstPipeWirePoolData *gst_pipewire_pool_get_data (GstBuffer *buffer);
+
+//gboolean gst_pipewire_pool_add_buffer (GstPipeWirePool *pool, GstBuffer *buffer);
+//gboolean gst_pipewire_pool_remove_buffer (GstPipeWirePool *pool, GstBuffer *buffer);
+
+G_END_DECLS
+
+#endif /* __GST_PIPEWIRE_POOL_H__ */
diff --git a/src/gst/gstpipewiresink.c b/src/gst/gstpipewiresink.c
new file mode 100644
index 0000000..91bdfcc
--- /dev/null
+++ b/src/gst/gstpipewiresink.c
@@ -0,0 +1,924 @@
+/* GStreamer
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * SECTION:element-pipewiresink
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch -v videotestsrc ! pipewiresink
+ * ]| Sends a test video source to PipeWire
+ * </refsect2>
+ */
+
+#define PW_ENABLE_DEPRECATED
+
+#include "config.h"
+#include "gstpipewiresink.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/socket.h>
+
+#include <spa/pod/builder.h>
+#include <spa/utils/result.h>
+
+#include <gst/video/video.h>
+
+#include "gstpipewireformat.h"
+
+GST_DEBUG_CATEGORY_STATIC (pipewire_sink_debug);
+#define GST_CAT_DEFAULT pipewire_sink_debug
+
+#define DEFAULT_PROP_MODE GST_PIPEWIRE_SINK_MODE_DEFAULT
+
+#define MIN_BUFFERS 8u
+
+enum
+{
+ PROP_0,
+ PROP_PATH,
+ PROP_TARGET_OBJECT,
+ PROP_CLIENT_NAME,
+ PROP_CLIENT_PROPERTIES,
+ PROP_STREAM_PROPERTIES,
+ PROP_MODE,
+ PROP_FD
+};
+
+GType
+gst_pipewire_sink_mode_get_type (void)
+{
+ static gsize mode_type = 0;
+ static const GEnumValue mode[] = {
+ {GST_PIPEWIRE_SINK_MODE_DEFAULT, "GST_PIPEWIRE_SINK_MODE_DEFAULT", "default"},
+ {GST_PIPEWIRE_SINK_MODE_RENDER, "GST_PIPEWIRE_SINK_MODE_RENDER", "render"},
+ {GST_PIPEWIRE_SINK_MODE_PROVIDE, "GST_PIPEWIRE_SINK_MODE_PROVIDE", "provide"},
+ {0, NULL, NULL},
+ };
+
+ if (g_once_init_enter (&mode_type)) {
+ GType tmp =
+ g_enum_register_static ("GstPipeWireSinkMode", mode);
+ g_once_init_leave (&mode_type, tmp);
+ }
+
+ return (GType) mode_type;
+}
+
+
+static GstStaticPadTemplate gst_pipewire_sink_template =
+GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY
+ );
+
+#define gst_pipewire_sink_parent_class parent_class
+G_DEFINE_TYPE (GstPipeWireSink, gst_pipewire_sink, GST_TYPE_BASE_SINK);
+
+static void gst_pipewire_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_pipewire_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static GstStateChangeReturn
+gst_pipewire_sink_change_state (GstElement * element, GstStateChange transition);
+
+static gboolean gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps);
+static GstCaps *gst_pipewire_sink_sink_fixate (GstBaseSink * bsink,
+ GstCaps * caps);
+
+static GstFlowReturn gst_pipewire_sink_render (GstBaseSink * psink,
+ GstBuffer * buffer);
+static gboolean gst_pipewire_sink_start (GstBaseSink * basesink);
+static gboolean gst_pipewire_sink_stop (GstBaseSink * basesink);
+
+static void
+gst_pipewire_sink_finalize (GObject * object)
+{
+ GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object);
+
+ g_object_unref (pwsink->pool);
+
+ if (pwsink->stream_properties)
+ gst_structure_free (pwsink->stream_properties);
+ if (pwsink->client_properties)
+ gst_structure_free (pwsink->client_properties);
+ g_free (pwsink->path);
+ g_free (pwsink->target_object);
+ g_free (pwsink->client_name);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+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->pool), 0, 0, 0);
+ return TRUE;
+}
+
+static void
+gst_pipewire_sink_class_init (GstPipeWireSinkClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBaseSinkClass *gstbasesink_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbasesink_class = (GstBaseSinkClass *) klass;
+
+ gobject_class->finalize = gst_pipewire_sink_finalize;
+ gobject_class->set_property = gst_pipewire_sink_set_property;
+ gobject_class->get_property = gst_pipewire_sink_get_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_PATH,
+ g_param_spec_string ("path",
+ "Path",
+ "The sink path to connect to (NULL = default)",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_DEPRECATED));
+
+ g_object_class_install_property (gobject_class,
+ PROP_TARGET_OBJECT,
+ g_param_spec_string ("target-object",
+ "Target object",
+ "The sink name/serial to connect to (NULL = default)",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_CLIENT_NAME,
+ g_param_spec_string ("client-name",
+ "Client Name",
+ "The client name to use (NULL = default)",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_CLIENT_PROPERTIES,
+ g_param_spec_boxed ("client-properties",
+ "Client properties",
+ "List of PipeWire client properties",
+ GST_TYPE_STRUCTURE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_STREAM_PROPERTIES,
+ g_param_spec_boxed ("stream-properties",
+ "Stream properties",
+ "List of PipeWire stream properties",
+ GST_TYPE_STRUCTURE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_MODE,
+ g_param_spec_enum ("mode",
+ "Mode",
+ "The mode to operate in",
+ GST_TYPE_PIPEWIRE_SINK_MODE,
+ DEFAULT_PROP_MODE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_FD,
+ g_param_spec_int ("fd",
+ "Fd",
+ "The fd to connect with",
+ -1, G_MAXINT, -1,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ gstelement_class->change_state = gst_pipewire_sink_change_state;
+
+ gst_element_class_set_static_metadata (gstelement_class,
+ "PipeWire sink", "Sink/Video",
+ "Send video to PipeWire", "Wim Taymans <wim.taymans@gmail.com>");
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&gst_pipewire_sink_template));
+
+ gstbasesink_class->set_caps = gst_pipewire_sink_setcaps;
+ gstbasesink_class->fixate = gst_pipewire_sink_sink_fixate;
+ gstbasesink_class->propose_allocation = gst_pipewire_sink_propose_allocation;
+ gstbasesink_class->start = gst_pipewire_sink_start;
+ gstbasesink_class->stop = gst_pipewire_sink_stop;
+ gstbasesink_class->render = gst_pipewire_sink_render;
+
+ GST_DEBUG_CATEGORY_INIT (pipewire_sink_debug, "pipewiresink", 0,
+ "PipeWire Sink");
+}
+
+static void
+pool_activated (GstPipeWirePool *pool, GstPipeWireSink *sink)
+{
+ GstStructure *config;
+ GstCaps *caps;
+ guint size;
+ guint min_buffers;
+ guint max_buffers;
+ const struct spa_pod *port_params[3];
+ struct spa_pod_builder b = { NULL };
+ uint8_t buffer[1024];
+ struct spa_pod_frame f;
+
+ config = gst_buffer_pool_get_config (GST_BUFFER_POOL (pool));
+ gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, &max_buffers);
+
+ spa_pod_builder_init (&b, buffer, sizeof (buffer));
+ spa_pod_builder_push_object (&b, &f, SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers);
+ if (size == 0)
+ spa_pod_builder_add (&b,
+ SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX),
+ 0);
+ else
+ spa_pod_builder_add (&b,
+ SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(size, size, INT32_MAX),
+ 0);
+
+ spa_pod_builder_add (&b,
+ SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX),
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(
+ SPA_MAX(MIN_BUFFERS, min_buffers),
+ SPA_MAX(MIN_BUFFERS, min_buffers),
+ max_buffers ? max_buffers : INT32_MAX),
+ SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(
+ (1<<SPA_DATA_MemFd) |
+ (1<<SPA_DATA_MemPtr)),
+ 0);
+ port_params[0] = spa_pod_builder_pop (&b, &f);
+
+ port_params[1] = spa_pod_builder_add_object (&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Int(SPA_META_Header),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_header)));
+
+ port_params[2] = spa_pod_builder_add_object (&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Int(SPA_META_VideoCrop),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_region)));
+
+ pw_thread_loop_lock (sink->core->loop);
+ pw_stream_update_params (sink->stream, port_params, 3);
+ pw_thread_loop_unlock (sink->core->loop);
+}
+
+static void
+gst_pipewire_sink_init (GstPipeWireSink * sink)
+{
+ sink->pool = gst_pipewire_pool_new ();
+ sink->client_name = g_strdup(pw_get_client_name());
+ sink->mode = DEFAULT_PROP_MODE;
+ sink->fd = -1;
+
+ g_signal_connect (sink->pool, "activated", G_CALLBACK (pool_activated), sink);
+}
+
+static GstCaps *
+gst_pipewire_sink_sink_fixate (GstBaseSink * bsink, GstCaps * caps)
+{
+ GstStructure *structure;
+
+ caps = gst_caps_make_writable (caps);
+
+ structure = gst_caps_get_structure (caps, 0);
+
+ if (gst_structure_has_name (structure, "video/x-raw")) {
+ 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);
+
+ if (gst_structure_has_field (structure, "pixel-aspect-ratio"))
+ gst_structure_fixate_field_nearest_fraction (structure,
+ "pixel-aspect-ratio", 1, 1);
+ else
+ gst_structure_set (structure, "pixel-aspect-ratio", GST_TYPE_FRACTION, 1, 1,
+ NULL);
+
+ if (gst_structure_has_field (structure, "colorimetry"))
+ gst_structure_fixate_field_string (structure, "colorimetry", "bt601");
+ if (gst_structure_has_field (structure, "chroma-site"))
+ gst_structure_fixate_field_string (structure, "chroma-site", "mpeg2");
+
+ if (gst_structure_has_field (structure, "interlace-mode"))
+ gst_structure_fixate_field_string (structure, "interlace-mode",
+ "progressive");
+ else
+ gst_structure_set (structure, "interlace-mode", G_TYPE_STRING,
+ "progressive", NULL);
+ } else if (gst_structure_has_name (structure, "audio/x-raw")) {
+ gst_structure_fixate_field_string (structure, "format", "S16LE");
+ gst_structure_fixate_field_nearest_int (structure, "channels", 2);
+ gst_structure_fixate_field_nearest_int (structure, "rate", 44100);
+ } else if (gst_structure_has_name (structure, "audio/mpeg")) {
+ gst_structure_fixate_field_string (structure, "format", "Encoded");
+ gst_structure_fixate_field_nearest_int (structure, "channels", 2);
+ gst_structure_fixate_field_nearest_int (structure, "rate", 44100);
+ } else if (gst_structure_has_name (structure, "audio/x-flac")) {
+ gst_structure_fixate_field_string (structure, "format", "Encoded");
+ gst_structure_fixate_field_nearest_int (structure, "channels", 2);
+ gst_structure_fixate_field_nearest_int (structure, "rate", 44100);
+ }
+
+ caps = GST_BASE_SINK_CLASS (parent_class)->fixate (bsink, caps);
+
+ return caps;
+}
+
+static void
+gst_pipewire_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object);
+
+ switch (prop_id) {
+ case PROP_PATH:
+ g_free (pwsink->path);
+ pwsink->path = g_value_dup_string (value);
+ break;
+
+ case PROP_TARGET_OBJECT:
+ g_free (pwsink->target_object);
+ pwsink->target_object = g_value_dup_string (value);
+ break;
+
+ case PROP_CLIENT_NAME:
+ g_free (pwsink->client_name);
+ pwsink->client_name = g_value_dup_string (value);
+ break;
+
+ case PROP_CLIENT_PROPERTIES:
+ if (pwsink->client_properties)
+ gst_structure_free (pwsink->client_properties);
+ pwsink->client_properties =
+ gst_structure_copy (gst_value_get_structure (value));
+ break;
+
+ case PROP_STREAM_PROPERTIES:
+ if (pwsink->stream_properties)
+ gst_structure_free (pwsink->stream_properties);
+ pwsink->stream_properties =
+ gst_structure_copy (gst_value_get_structure (value));
+ break;
+
+ case PROP_MODE:
+ pwsink->mode = g_value_get_enum (value);
+ break;
+
+ case PROP_FD:
+ pwsink->fd = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_pipewire_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (object);
+
+ switch (prop_id) {
+ case PROP_PATH:
+ g_value_set_string (value, pwsink->path);
+ break;
+
+ case PROP_TARGET_OBJECT:
+ g_value_set_string (value, pwsink->target_object);
+ break;
+
+ case PROP_CLIENT_NAME:
+ g_value_set_string (value, pwsink->client_name);
+ break;
+
+ case PROP_CLIENT_PROPERTIES:
+ gst_value_set_structure (value, pwsink->client_properties);
+ break;
+
+ case PROP_STREAM_PROPERTIES:
+ gst_value_set_structure (value, pwsink->stream_properties);
+ break;
+
+ case PROP_MODE:
+ g_value_set_enum (value, pwsink->mode);
+ break;
+
+ case PROP_FD:
+ g_value_set_int (value, pwsink->fd);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+on_add_buffer (void *_data, struct pw_buffer *b)
+{
+ GstPipeWireSink *pwsink = _data;
+ gst_pipewire_pool_wrap_buffer (pwsink->pool, b);
+}
+
+static void
+on_remove_buffer (void *_data, struct pw_buffer *b)
+{
+ GstPipeWireSink *pwsink = _data;
+ GstPipeWirePoolData *data = b->user_data;
+
+ GST_LOG_OBJECT (pwsink, "remove buffer");
+
+ gst_buffer_unref (data->buf);
+}
+
+static void
+do_send_buffer (GstPipeWireSink *pwsink, GstBuffer *buffer)
+{
+ GstPipeWirePoolData *data;
+ gboolean res;
+ guint i;
+ struct spa_buffer *b;
+
+ data = gst_pipewire_pool_get_data(buffer);
+
+ b = data->b->buffer;
+
+ if (data->header) {
+ data->header->seq = GST_BUFFER_OFFSET (buffer);
+ data->header->pts = GST_BUFFER_PTS (buffer);
+ data->header->dts_offset = GST_BUFFER_DTS (buffer);
+ }
+ if (data->crop) {
+ GstVideoCropMeta *meta = gst_buffer_get_video_crop_meta (buffer);
+ if (meta) {
+ data->crop->region.position.x = meta->x;
+ data->crop->region.position.y = meta->y;
+ data->crop->region.size.width = meta->width;
+ data->crop->region.size.height = meta->width;
+ }
+ }
+ 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 = 0;
+ }
+
+ if ((res = pw_stream_queue_buffer (pwsink->stream, data->b)) < 0) {
+ g_warning ("can't send buffer %s", spa_strerror(res));
+ }
+}
+
+
+static void
+on_process (void *data)
+{
+ GstPipeWireSink *pwsink = data;
+ GST_DEBUG ("signal");
+ g_cond_signal (&pwsink->pool->cond);
+}
+
+static void
+on_state_changed (void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error)
+{
+ GstPipeWireSink *pwsink = data;
+
+ GST_DEBUG ("got stream state %d", state);
+
+ switch (state) {
+ case PW_STREAM_STATE_UNCONNECTED:
+ case PW_STREAM_STATE_CONNECTING:
+ case PW_STREAM_STATE_PAUSED:
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ if (pw_stream_is_driving (pwsink->stream))
+ pw_stream_trigger_process (pwsink->stream);
+ break;
+ case PW_STREAM_STATE_ERROR:
+ GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED,
+ ("stream error: %s", error), (NULL));
+ break;
+ }
+ pw_thread_loop_signal (pwsink->core->loop, FALSE);
+}
+
+static void
+on_param_changed (void *data, uint32_t id, const struct spa_pod *param)
+{
+ GstPipeWireSink *pwsink = data;
+
+ if (param == NULL || id != SPA_PARAM_Format)
+ return;
+
+ if (gst_buffer_pool_is_active (GST_BUFFER_POOL_CAST (pwsink->pool)))
+ pool_activated (pwsink->pool, pwsink);
+}
+
+static gboolean
+gst_pipewire_sink_setcaps (GstBaseSink * bsink, GstCaps * caps)
+{
+ GstPipeWireSink *pwsink;
+ GPtrArray *possible;
+ enum pw_stream_state state;
+ const char *error = NULL;
+ gboolean res = FALSE;
+ GstStructure *config;
+ guint size;
+ guint min_buffers;
+ guint max_buffers;
+ struct timespec abstime;
+
+ pwsink = GST_PIPEWIRE_SINK (bsink);
+
+ possible = gst_caps_to_format_all (caps, SPA_PARAM_EnumFormat);
+
+ pw_thread_loop_lock (pwsink->core->loop);
+ state = pw_stream_get_state (pwsink->stream, &error);
+
+ if (state == PW_STREAM_STATE_ERROR)
+ goto start_error;
+
+ if (state == PW_STREAM_STATE_UNCONNECTED) {
+ enum pw_stream_flags flags = 0;
+ uint32_t target_id;
+
+ if (pwsink->mode != GST_PIPEWIRE_SINK_MODE_PROVIDE)
+ flags |= PW_STREAM_FLAG_AUTOCONNECT;
+ else
+ flags |= PW_STREAM_FLAG_DRIVER;
+
+ target_id = pwsink->path ? (uint32_t)atoi(pwsink->path) : PW_ID_ANY;
+
+ if (pwsink->target_object) {
+ struct spa_dict_item items[2] = {
+ SPA_DICT_ITEM_INIT(PW_KEY_TARGET_OBJECT, pwsink->target_object),
+ /* XXX deprecated but the portal and some example apps only
+ * provide the object id */
+ SPA_DICT_ITEM_INIT(PW_KEY_NODE_TARGET, NULL),
+ };
+ struct spa_dict dict = SPA_DICT_INIT_ARRAY(items);
+ uint64_t serial;
+
+ /* If target.object is a name, set it also to node.target */
+ if (spa_atou64(pwsink->target_object, &serial, 0)) {
+ dict.n_items = 1;
+ } else {
+ target_id = PW_ID_ANY;
+ items[1].value = pwsink->target_object;
+ }
+
+ pw_stream_update_properties (pwsink->stream, &dict);
+ }
+
+ pw_stream_connect (pwsink->stream,
+ PW_DIRECTION_OUTPUT,
+ target_id,
+ flags,
+ (const struct spa_pod **) possible->pdata,
+ possible->len);
+
+ pw_thread_loop_get_time (pwsink->core->loop, &abstime,
+ GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC);
+
+ while (TRUE) {
+ state = pw_stream_get_state (pwsink->stream, &error);
+
+ if (state >= PW_STREAM_STATE_PAUSED)
+ break;
+
+ if (state == PW_STREAM_STATE_ERROR)
+ goto start_error;
+
+ if (pw_thread_loop_timed_wait_full (pwsink->core->loop, &abstime) < 0) {
+ error = "timeout";
+ goto start_error;
+ }
+ }
+ }
+ res = TRUE;
+
+ config = gst_buffer_pool_get_config (GST_BUFFER_POOL_CAST (pwsink->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);
+ gst_buffer_pool_set_config (GST_BUFFER_POOL_CAST (pwsink->pool), config);
+
+ pw_thread_loop_unlock (pwsink->core->loop);
+
+ pwsink->negotiated = res;
+
+ return res;
+
+start_error:
+ {
+ GST_ERROR ("could not start stream: %s", error);
+ pw_thread_loop_unlock (pwsink->core->loop);
+ g_ptr_array_unref (possible);
+ return FALSE;
+ }
+}
+
+static GstFlowReturn
+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);
+
+ if (!pwsink->negotiated)
+ goto not_negotiated;
+
+ if (buffer->pool != GST_BUFFER_POOL_CAST (pwsink->pool) &&
+ !gst_buffer_pool_is_active (GST_BUFFER_POOL_CAST (pwsink->pool))) {
+ GstStructure *config;
+ GstCaps *caps;
+ guint size, min_buffers, max_buffers;
+
+ config = gst_buffer_pool_get_config (GST_BUFFER_POOL_CAST (pwsink->pool));
+ gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, &max_buffers);
+
+ size = (size == 0) ? gst_buffer_get_size (buffer) : size;
+
+ gst_buffer_pool_config_set_params (config, caps, size, min_buffers, max_buffers);
+ gst_buffer_pool_set_config (GST_BUFFER_POOL_CAST (pwsink->pool), config);
+
+ gst_buffer_pool_set_active (GST_BUFFER_POOL_CAST (pwsink->pool), TRUE);
+ }
+
+ pw_thread_loop_lock (pwsink->core->loop);
+ if (pw_stream_get_state (pwsink->stream, &error) != PW_STREAM_STATE_STREAMING)
+ goto done_unlock;
+
+ if (buffer->pool != GST_BUFFER_POOL_CAST (pwsink->pool)) {
+ GstBuffer *b = NULL;
+ GstMapInfo info = { 0, };
+ GstBufferPoolAcquireParams params = { 0, };
+
+ pw_thread_loop_unlock (pwsink->core->loop);
+
+ if ((res = gst_buffer_pool_acquire_buffer (GST_BUFFER_POOL_CAST (pwsink->pool), &b, &params)) != GST_FLOW_OK)
+ goto done;
+
+ 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));
+ buffer = b;
+ unref_buffer = TRUE;
+
+ pw_thread_loop_lock (pwsink->core->loop);
+ if (pw_stream_get_state (pwsink->stream, &error) != PW_STREAM_STATE_STREAMING)
+ goto done_unlock;
+ }
+
+ GST_DEBUG ("push buffer");
+ do_send_buffer (pwsink, buffer);
+ if (unref_buffer)
+ gst_buffer_unref (buffer);
+
+ if (pw_stream_is_driving (pwsink->stream))
+ pw_stream_trigger_process (pwsink->stream);
+
+done_unlock:
+ pw_thread_loop_unlock (pwsink->core->loop);
+done:
+ return res;
+
+not_negotiated:
+ {
+ return GST_FLOW_NOT_NEGOTIATED;
+ }
+}
+
+static gboolean
+copy_properties (GQuark field_id,
+ const GValue *value,
+ gpointer user_data)
+{
+ struct pw_properties *properties = user_data;
+ GValue dst = { 0 };
+
+ if (g_value_type_transformable (G_VALUE_TYPE(value), G_TYPE_STRING)) {
+ g_value_init(&dst, G_TYPE_STRING);
+ if (g_value_transform(value, &dst)) {
+ pw_properties_set (properties,
+ g_quark_to_string (field_id),
+ g_value_get_string (&dst));
+ }
+ g_value_unset(&dst);
+ }
+ return TRUE;
+}
+
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .state_changed = on_state_changed,
+ .param_changed = on_param_changed,
+ .add_buffer = on_add_buffer,
+ .remove_buffer = on_remove_buffer,
+ .process = on_process,
+};
+
+static gboolean
+gst_pipewire_sink_start (GstBaseSink * basesink)
+{
+ GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (basesink);
+ struct pw_properties *props;
+
+ pwsink->negotiated = FALSE;
+
+ pw_thread_loop_lock (pwsink->core->loop);
+
+ props = pw_properties_new (NULL, NULL);
+ if (pwsink->client_name) {
+ pw_properties_set (props, PW_KEY_NODE_NAME, pwsink->client_name);
+ pw_properties_set (props, PW_KEY_NODE_DESCRIPTION, pwsink->client_name);
+ }
+ if (pwsink->stream_properties) {
+ gst_structure_foreach (pwsink->stream_properties, copy_properties, props);
+ }
+
+ if ((pwsink->stream = pw_stream_new (pwsink->core->core, pwsink->client_name, props)) == NULL)
+ goto no_stream;
+
+ pwsink->pool->stream = pwsink->stream;
+
+ pw_stream_add_listener(pwsink->stream,
+ &pwsink->stream_listener,
+ &stream_events,
+ pwsink);
+
+ pw_thread_loop_unlock (pwsink->core->loop);
+
+ return TRUE;
+
+no_stream:
+ {
+ GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED, ("can't create stream"), (NULL));
+ pw_thread_loop_unlock (pwsink->core->loop);
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_pipewire_sink_stop (GstBaseSink * basesink)
+{
+ GstPipeWireSink *pwsink = GST_PIPEWIRE_SINK (basesink);
+
+ pw_thread_loop_lock (pwsink->core->loop);
+ if (pwsink->stream) {
+ pw_stream_destroy (pwsink->stream);
+ pwsink->stream = NULL;
+ pwsink->pool->stream = NULL;
+ }
+ pw_thread_loop_unlock (pwsink->core->loop);
+
+ pwsink->negotiated = FALSE;
+
+ return TRUE;
+}
+
+static gboolean
+gst_pipewire_sink_open (GstPipeWireSink * pwsink)
+{
+ struct pw_properties *props;
+
+ GST_DEBUG_OBJECT (pwsink, "open");
+
+ pwsink->core = gst_pipewire_core_get(pwsink->fd);
+ if (pwsink->core == NULL)
+ goto connect_error;
+
+ pw_thread_loop_lock (pwsink->core->loop);
+
+ props = pw_properties_new (NULL, NULL);
+ if (pwsink->client_properties) {
+ gst_structure_foreach (pwsink->client_properties, copy_properties, props);
+ pw_core_update_properties (pwsink->core->core, &props->dict);
+ }
+ pw_properties_free(props);
+ pw_thread_loop_unlock (pwsink->core->loop);
+
+ return TRUE;
+
+ /* ERRORS */
+connect_error:
+ {
+ GST_ELEMENT_ERROR (pwsink, RESOURCE, FAILED,
+ ("Failed to connect"), (NULL));
+ return FALSE;
+ }
+}
+
+static gboolean
+gst_pipewire_sink_close (GstPipeWireSink * pwsink)
+{
+ pw_thread_loop_lock (pwsink->core->loop);
+ if (pwsink->stream) {
+ pw_stream_destroy (pwsink->stream);
+ pwsink->stream = NULL;
+ }
+ pw_thread_loop_unlock (pwsink->core->loop);
+
+ if (pwsink->core) {
+ gst_pipewire_core_release (pwsink->core);
+ pwsink->core = NULL;
+ }
+ return TRUE;
+}
+
+static GstStateChangeReturn
+gst_pipewire_sink_change_state (GstElement * element, GstStateChange transition)
+{
+ GstStateChangeReturn ret;
+ GstPipeWireSink *this = GST_PIPEWIRE_SINK_CAST (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!gst_pipewire_sink_open (this))
+ goto open_failed;
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ /* uncork and start play */
+ pw_thread_loop_lock (this->core->loop);
+ pw_stream_set_active(this->stream, true);
+ pw_thread_loop_unlock (this->core->loop);
+ gst_buffer_pool_set_flushing(GST_BUFFER_POOL_CAST(this->pool), FALSE);
+ break;
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ /* stop play ASAP by corking */
+ pw_thread_loop_lock (this->core->loop);
+ pw_stream_set_active(this->stream, false);
+ pw_thread_loop_unlock (this->core->loop);
+ gst_buffer_pool_set_flushing(GST_BUFFER_POOL_CAST(this->pool), TRUE);
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ gst_buffer_pool_set_active(GST_BUFFER_POOL_CAST(this->pool), FALSE);
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ gst_pipewire_sink_close (this);
+ break;
+ default:
+ break;
+ }
+ return ret;
+
+ /* ERRORS */
+open_failed:
+ {
+ return GST_STATE_CHANGE_FAILURE;
+ }
+}
diff --git a/src/gst/gstpipewiresink.h b/src/gst/gstpipewiresink.h
new file mode 100644
index 0000000..703cd0f
--- /dev/null
+++ b/src/gst/gstpipewiresink.h
@@ -0,0 +1,110 @@
+/* GStreamer
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __GST_PIPEWIRE_SINK_H__
+#define __GST_PIPEWIRE_SINK_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstbasesink.h>
+
+#include <pipewire/pipewire.h>
+#include <gst/gstpipewirepool.h>
+#include <gst/gstpipewirecore.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_PIPEWIRE_SINK \
+ (gst_pipewire_sink_get_type())
+#define GST_PIPEWIRE_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_SINK,GstPipeWireSink))
+#define GST_PIPEWIRE_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_SINK,GstPipeWireSinkClass))
+#define GST_IS_PIPEWIRE_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_SINK))
+#define GST_IS_PIPEWIRE_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_SINK))
+#define GST_PIPEWIRE_SINK_CAST(obj) \
+ ((GstPipeWireSink *) (obj))
+
+typedef struct _GstPipeWireSink GstPipeWireSink;
+typedef struct _GstPipeWireSinkClass GstPipeWireSinkClass;
+
+
+/**
+ * GstPipeWireSinkMode:
+ * @GST_PIPEWIRE_SINK_MODE_DEFAULT: the default mode as configured in the server
+ * @GST_PIPEWIRE_SINK_MODE_RENDER: try to render the media
+ * @GST_PIPEWIRE_SINK_MODE_PROVIDE: provide the media
+ *
+ * Different modes of operation.
+ */
+typedef enum
+{
+ GST_PIPEWIRE_SINK_MODE_DEFAULT,
+ GST_PIPEWIRE_SINK_MODE_RENDER,
+ GST_PIPEWIRE_SINK_MODE_PROVIDE,
+} GstPipeWireSinkMode;
+
+#define GST_TYPE_PIPEWIRE_SINK_MODE (gst_pipewire_sink_mode_get_type ())
+
+/**
+ * GstPipeWireSink:
+ *
+ * Opaque data structure.
+ */
+struct _GstPipeWireSink {
+ GstBaseSink element;
+
+ /*< private >*/
+ gchar *path;
+ gchar *target_object;
+ gchar *client_name;
+ int fd;
+
+ /* video state */
+ gboolean negotiated;
+
+ GstPipeWireCore *core;
+ struct spa_hook core_listener;
+ GstStructure *client_properties;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ GstStructure *stream_properties;
+ GstPipeWireSinkMode mode;
+
+ GstPipeWirePool *pool;
+};
+
+struct _GstPipeWireSinkClass {
+ GstBaseSinkClass parent_class;
+};
+
+GType gst_pipewire_sink_get_type (void);
+GType gst_pipewire_sink_mode_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_PIPEWIRE_SINK_H__ */
diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c
new file mode 100644
index 0000000..7243cc1
--- /dev/null
+++ b/src/gst/gstpipewiresrc.c
@@ -0,0 +1,1439 @@
+/* GStreamer
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/**
+ * SECTION:element-pipewiresrc
+ *
+ * <refsect2>
+ * <title>Example launch line</title>
+ * |[
+ * gst-launch -v pipewiresrc ! videoconvert ! ximagesink
+ * ]| Shows pipewire output in an X window.
+ * </refsect2>
+ */
+
+#define PW_ENABLE_DEPRECATED
+
+#include "config.h"
+#include "gstpipewiresrc.h"
+#include "gstpipewireformat.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <spa/param/video/format.h>
+#include <spa/pod/builder.h>
+#include <spa/utils/result.h>
+
+#include <gst/net/gstnetclientclock.h>
+#include <gst/allocators/gstfdmemory.h>
+#include <gst/allocators/gstdmabuf.h>
+#include <gst/video/video.h>
+
+#include "gstpipewireclock.h"
+
+static GQuark process_mem_data_quark;
+
+GST_DEBUG_CATEGORY_STATIC (pipewire_src_debug);
+#define GST_CAT_DEFAULT pipewire_src_debug
+
+#define DEFAULT_ALWAYS_COPY false
+#define DEFAULT_MIN_BUFFERS 8
+#define DEFAULT_MAX_BUFFERS INT32_MAX
+#define DEFAULT_RESEND_LAST false
+#define DEFAULT_KEEPALIVE_TIME 0
+
+enum
+{
+ PROP_0,
+ PROP_PATH,
+ PROP_TARGET_OBJECT,
+ PROP_CLIENT_NAME,
+ PROP_CLIENT_PROPERTIES,
+ PROP_STREAM_PROPERTIES,
+ PROP_ALWAYS_COPY,
+ PROP_MIN_BUFFERS,
+ PROP_MAX_BUFFERS,
+ PROP_FD,
+ PROP_RESEND_LAST,
+ PROP_KEEPALIVE_TIME,
+};
+
+
+static GstStaticPadTemplate gst_pipewire_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS_ANY
+ );
+
+#define gst_pipewire_src_parent_class parent_class
+G_DEFINE_TYPE (GstPipeWireSrc, gst_pipewire_src, GST_TYPE_PUSH_SRC);
+
+static GstStateChangeReturn
+gst_pipewire_src_change_state (GstElement * element, GstStateChange transition);
+
+static gboolean gst_pipewire_src_send_event (GstElement * elem, GstEvent * event);
+
+static gboolean gst_pipewire_src_negotiate (GstBaseSrc * basesrc);
+
+static GstFlowReturn gst_pipewire_src_create (GstPushSrc * psrc,
+ GstBuffer ** buffer);
+static gboolean gst_pipewire_src_unlock (GstBaseSrc * basesrc);
+static gboolean gst_pipewire_src_unlock_stop (GstBaseSrc * basesrc);
+static gboolean gst_pipewire_src_start (GstBaseSrc * basesrc);
+static gboolean gst_pipewire_src_stop (GstBaseSrc * basesrc);
+static gboolean gst_pipewire_src_event (GstBaseSrc * src, GstEvent * event);
+static gboolean gst_pipewire_src_query (GstBaseSrc * src, GstQuery * query);
+
+static void
+gst_pipewire_src_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (object);
+
+ switch (prop_id) {
+ case PROP_PATH:
+ g_free (pwsrc->path);
+ pwsrc->path = g_value_dup_string (value);
+ break;
+
+ case PROP_TARGET_OBJECT:
+ g_free (pwsrc->target_object);
+ pwsrc->target_object = g_value_dup_string (value);
+ break;
+
+ case PROP_CLIENT_NAME:
+ g_free (pwsrc->client_name);
+ pwsrc->client_name = g_value_dup_string (value);
+ break;
+
+ case PROP_CLIENT_PROPERTIES:
+ if (pwsrc->client_properties)
+ gst_structure_free (pwsrc->client_properties);
+ pwsrc->client_properties =
+ gst_structure_copy (gst_value_get_structure (value));
+ break;
+
+ case PROP_STREAM_PROPERTIES:
+ if (pwsrc->stream_properties)
+ gst_structure_free (pwsrc->stream_properties);
+ pwsrc->stream_properties =
+ gst_structure_copy (gst_value_get_structure (value));
+ break;
+
+ case PROP_ALWAYS_COPY:
+ pwsrc->always_copy = g_value_get_boolean (value);
+ break;
+
+ case PROP_MIN_BUFFERS:
+ pwsrc->min_buffers = g_value_get_int (value);
+ break;
+
+ case PROP_MAX_BUFFERS:
+ pwsrc->max_buffers = g_value_get_int (value);
+ break;
+
+ case PROP_FD:
+ pwsrc->fd = g_value_get_int (value);
+ break;
+
+ case PROP_RESEND_LAST:
+ pwsrc->resend_last = g_value_get_boolean (value);
+ break;
+
+ case PROP_KEEPALIVE_TIME:
+ pwsrc->keepalive_time = g_value_get_int (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_pipewire_src_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (object);
+
+ switch (prop_id) {
+ case PROP_PATH:
+ g_value_set_string (value, pwsrc->path);
+ break;
+
+ case PROP_TARGET_OBJECT:
+ g_value_set_string (value, pwsrc->target_object);
+ break;
+
+ case PROP_CLIENT_NAME:
+ g_value_set_string (value, pwsrc->client_name);
+ break;
+
+ case PROP_CLIENT_PROPERTIES:
+ gst_value_set_structure (value, pwsrc->client_properties);
+ break;
+
+ case PROP_STREAM_PROPERTIES:
+ gst_value_set_structure (value, pwsrc->stream_properties);
+ break;
+
+ case PROP_ALWAYS_COPY:
+ g_value_set_boolean (value, pwsrc->always_copy);
+ break;
+
+ case PROP_MIN_BUFFERS:
+ g_value_set_int (value, pwsrc->min_buffers);
+ break;
+
+ case PROP_MAX_BUFFERS:
+ g_value_set_int (value, pwsrc->max_buffers);
+ break;
+
+ case PROP_FD:
+ g_value_set_int (value, pwsrc->fd);
+ break;
+
+ case PROP_RESEND_LAST:
+ g_value_set_boolean (value, pwsrc->resend_last);
+ break;
+
+ case PROP_KEEPALIVE_TIME:
+ g_value_set_int (value, pwsrc->keepalive_time);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static GstClock *
+gst_pipewire_src_provide_clock (GstElement * elem)
+{
+ GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (elem);
+ GstClock *clock;
+
+ GST_OBJECT_LOCK (pwsrc);
+ if (!GST_OBJECT_FLAG_IS_SET (pwsrc, GST_ELEMENT_FLAG_PROVIDE_CLOCK))
+ goto clock_disabled;
+
+ if (pwsrc->clock && pwsrc->is_live)
+ clock = GST_CLOCK_CAST (gst_object_ref (pwsrc->clock));
+ else
+ clock = NULL;
+ GST_OBJECT_UNLOCK (pwsrc);
+
+ return clock;
+
+ /* ERRORS */
+clock_disabled:
+ {
+ GST_DEBUG_OBJECT (pwsrc, "clock provide disabled");
+ GST_OBJECT_UNLOCK (pwsrc);
+ return NULL;
+ }
+}
+
+static void
+gst_pipewire_src_finalize (GObject * object)
+{
+ GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (object);
+
+ if (pwsrc->stream_properties)
+ gst_structure_free (pwsrc->stream_properties);
+ if (pwsrc->client_properties)
+ gst_structure_free (pwsrc->client_properties);
+ if (pwsrc->clock)
+ gst_object_unref (pwsrc->clock);
+ g_free (pwsrc->path);
+ g_free (pwsrc->target_object);
+ g_free (pwsrc->client_name);
+ g_object_unref(pwsrc->pool);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_pipewire_src_class_init (GstPipeWireSrcClass * klass)
+{
+ GObjectClass *gobject_class;
+ GstElementClass *gstelement_class;
+ GstBaseSrcClass *gstbasesrc_class;
+ GstPushSrcClass *gstpushsrc_class;
+
+ gobject_class = (GObjectClass *) klass;
+ gstelement_class = (GstElementClass *) klass;
+ gstbasesrc_class = (GstBaseSrcClass *) klass;
+ gstpushsrc_class = (GstPushSrcClass *) klass;
+
+ gobject_class->finalize = gst_pipewire_src_finalize;
+ gobject_class->set_property = gst_pipewire_src_set_property;
+ gobject_class->get_property = gst_pipewire_src_get_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_PATH,
+ g_param_spec_string ("path",
+ "Path",
+ "The source path to connect to (NULL = default)",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS |
+ G_PARAM_DEPRECATED));
+
+ g_object_class_install_property (gobject_class,
+ PROP_TARGET_OBJECT,
+ g_param_spec_string ("target-object",
+ "Target object",
+ "The source name/serial to connect to (NULL = default)",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_CLIENT_NAME,
+ g_param_spec_string ("client-name",
+ "Client Name",
+ "The client name to use (NULL = default)",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_CLIENT_PROPERTIES,
+ g_param_spec_boxed ("client-properties",
+ "client properties",
+ "list of PipeWire client properties",
+ GST_TYPE_STRUCTURE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_STREAM_PROPERTIES,
+ g_param_spec_boxed ("stream-properties",
+ "stream properties",
+ "list of PipeWire stream properties",
+ GST_TYPE_STRUCTURE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_ALWAYS_COPY,
+ g_param_spec_boolean ("always-copy",
+ "Always copy",
+ "Always copy the buffer and data",
+ DEFAULT_ALWAYS_COPY,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_MIN_BUFFERS,
+ g_param_spec_int ("min-buffers",
+ "Min Buffers",
+ "Minimum number of buffers to negotiate with PipeWire",
+ 1, G_MAXINT, DEFAULT_MIN_BUFFERS,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_MAX_BUFFERS,
+ g_param_spec_int ("max-buffers",
+ "Max Buffers",
+ "Maximum number of buffers to negotiate with PipeWire",
+ 1, G_MAXINT, DEFAULT_MAX_BUFFERS,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_FD,
+ g_param_spec_int ("fd",
+ "Fd",
+ "The fd to connect with",
+ -1, G_MAXINT, -1,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_RESEND_LAST,
+ g_param_spec_boolean ("resend-last",
+ "Resend last",
+ "Resend last buffer on EOS",
+ DEFAULT_RESEND_LAST,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_KEEPALIVE_TIME,
+ g_param_spec_int ("keepalive-time",
+ "Keepalive Time",
+ "Periodically send last buffer (in milliseconds, 0 = disabled)",
+ 0, G_MAXINT, DEFAULT_KEEPALIVE_TIME,
+ 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;
+
+ gst_element_class_set_static_metadata (gstelement_class,
+ "PipeWire source", "Source/Video",
+ "Uses PipeWire to create video", "Wim Taymans <wim.taymans@gmail.com>");
+
+ gst_element_class_add_pad_template (gstelement_class,
+ gst_static_pad_template_get (&gst_pipewire_src_template));
+
+ gstbasesrc_class->negotiate = gst_pipewire_src_negotiate;
+ gstbasesrc_class->unlock = gst_pipewire_src_unlock;
+ gstbasesrc_class->unlock_stop = gst_pipewire_src_unlock_stop;
+ gstbasesrc_class->start = gst_pipewire_src_start;
+ gstbasesrc_class->stop = gst_pipewire_src_stop;
+ gstbasesrc_class->event = gst_pipewire_src_event;
+ gstbasesrc_class->query = gst_pipewire_src_query;
+ gstpushsrc_class->create = gst_pipewire_src_create;
+
+ GST_DEBUG_CATEGORY_INIT (pipewire_src_debug, "pipewiresrc", 0,
+ "PipeWire Source");
+
+ process_mem_data_quark = g_quark_from_static_string ("GstPipeWireSrcProcessMemQuark");
+}
+
+static void
+gst_pipewire_src_init (GstPipeWireSrc * src)
+{
+ /* we operate in time */
+ gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_TIME);
+
+ /* we're a live source, unless explicitly requested not to be */
+ gst_base_src_set_live (GST_BASE_SRC (src), TRUE);
+
+ GST_OBJECT_FLAG_SET (src, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
+
+ src->always_copy = DEFAULT_ALWAYS_COPY;
+ src->min_buffers = DEFAULT_MIN_BUFFERS;
+ src->max_buffers = DEFAULT_MAX_BUFFERS;
+ src->fd = -1;
+ src->resend_last = DEFAULT_RESEND_LAST;
+ src->keepalive_time = DEFAULT_KEEPALIVE_TIME;
+
+ src->client_name = g_strdup(pw_get_client_name ());
+
+ src->pool = gst_pipewire_pool_new ();
+}
+
+static gboolean
+buffer_recycle (GstMiniObject *obj)
+{
+ GstPipeWireSrc *src;
+ GstPipeWirePoolData *data;
+ int res;
+
+ data = gst_pipewire_pool_get_data (GST_BUFFER_CAST(obj));
+
+ GST_OBJECT_LOCK (data->pool);
+ if (!obj->dispose) {
+ GST_OBJECT_UNLOCK (data->pool);
+ return TRUE;
+ }
+
+ GST_BUFFER_FLAGS (obj) = data->flags;
+ src = data->owner;
+
+ pw_thread_loop_lock (src->core->loop);
+ if (!obj->dispose) {
+ pw_thread_loop_unlock (src->core->loop);
+ GST_OBJECT_UNLOCK (data->pool);
+ return TRUE;
+ }
+
+ gst_mini_object_ref (obj);
+
+ data->queued = TRUE;
+
+ if ((res = pw_stream_queue_buffer (src->stream, data->b)) < 0)
+ GST_WARNING_OBJECT (src, "can't queue recycled buffer %p, %s", obj, spa_strerror(res));
+ else
+ GST_LOG_OBJECT (src, "recycle buffer %p", obj);
+
+ pw_thread_loop_unlock (src->core->loop);
+
+ GST_OBJECT_UNLOCK (data->pool);
+
+ return FALSE;
+}
+
+static void
+on_add_buffer (void *_data, struct pw_buffer *b)
+{
+ GstPipeWireSrc *pwsrc = _data;
+ GstPipeWirePoolData *data;
+
+ gst_pipewire_pool_wrap_buffer (pwsrc->pool, b);
+ data = b->user_data;
+ GST_DEBUG_OBJECT (pwsrc, "add buffer %p", data->buf);
+ data->owner = pwsrc;
+ data->queued = TRUE;
+ GST_MINI_OBJECT_CAST (data->buf)->dispose = buffer_recycle;
+}
+
+static void
+on_remove_buffer (void *_data, struct pw_buffer *b)
+{
+ GstPipeWireSrc *pwsrc = _data;
+ GstPipeWirePoolData *data = b->user_data;
+ GstBuffer *buf = data->buf;
+ int res;
+
+ GST_DEBUG_OBJECT (pwsrc, "remove buffer %p", buf);
+
+ GST_MINI_OBJECT_CAST (buf)->dispose = NULL;
+
+ if (data->queued) {
+ gst_buffer_unref (buf);
+ } else {
+ if ((res = pw_stream_queue_buffer (pwsrc->stream, b)) < 0)
+ GST_WARNING_OBJECT (pwsrc, "can't queue removed buffer %p, %s", buf, spa_strerror(res));
+ }
+}
+
+static const char * const transform_map[] = {
+ [SPA_META_TRANSFORMATION_None] = "rotate-0",
+ [SPA_META_TRANSFORMATION_90] = "rotate-90",
+ [SPA_META_TRANSFORMATION_180] = "rotate-180",
+ [SPA_META_TRANSFORMATION_270] = "rotate-270",
+ [SPA_META_TRANSFORMATION_Flipped] = "flip-rotate-0",
+ [SPA_META_TRANSFORMATION_Flipped90] = "flip-rotate-270",
+ [SPA_META_TRANSFORMATION_Flipped180] = "flip-rotate-180",
+ [SPA_META_TRANSFORMATION_Flipped270] = "flip-rotate-90",
+};
+
+static const char *spa_transform_value_to_gst_image_orientation(uint32_t transform_value)
+{
+ if (transform_value >= SPA_N_ELEMENTS(transform_map))
+ transform_value = SPA_META_TRANSFORMATION_None;
+
+ return transform_map[transform_value];
+}
+
+static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc)
+{
+ struct pw_buffer *b;
+ GstBuffer *buf;
+ GstPipeWirePoolData *data;
+ struct spa_meta_header *h;
+ struct spa_meta_region *crop;
+ struct spa_meta_videotransform *videotransform;
+ guint i;
+
+ b = pw_stream_dequeue_buffer (pwsrc->stream);
+ if (b == NULL)
+ return NULL;
+
+ data = b->user_data;
+
+ if (!GST_IS_BUFFER (data->buf)) {
+ GST_ERROR_OBJECT (pwsrc, "stream buffer %p is missing", data->buf);
+ return NULL;
+ }
+
+ if (!data->queued) {
+ GST_ERROR_OBJECT (pwsrc, "buffer %p was not recycled", data->buf);
+ return NULL;
+ }
+
+ GST_LOG_OBJECT (pwsrc, "got new buffer %p", data->buf);
+
+ buf = gst_buffer_new ();
+
+ data->queued = FALSE;
+ GST_BUFFER_PTS (buf) = GST_CLOCK_TIME_NONE;
+ GST_BUFFER_DTS (buf) = GST_CLOCK_TIME_NONE;
+
+ h = data->header;
+ if (h) {
+ GST_LOG_OBJECT (pwsrc, "pts %" G_GUINT64_FORMAT ", dts_offset %" G_GUINT64_FORMAT, h->pts, h->dts_offset);
+
+ if (GST_CLOCK_TIME_IS_VALID (h->pts)) {
+ GST_BUFFER_PTS (buf) = h->pts + GST_PIPEWIRE_CLOCK (pwsrc->clock)->time_offset;
+ if (GST_BUFFER_PTS (buf) + h->dts_offset > 0)
+ GST_BUFFER_DTS (buf) = GST_BUFFER_PTS (buf) + h->dts_offset;
+ }
+ GST_BUFFER_OFFSET (buf) = h->seq;
+ }
+ crop = data->crop;
+ if (crop) {
+ GstVideoCropMeta *meta = gst_buffer_get_video_crop_meta(buf);
+ if (meta) {
+ meta->x = crop->region.position.x;
+ meta->y = crop->region.position.y;
+ meta->width = crop->region.size.width;
+ meta->height = crop->region.size.height;
+ }
+ }
+
+ videotransform = data->videotransform;
+ if (videotransform) {
+ if (pwsrc->transform_value != videotransform->transform) {
+ GstEvent *tag_event;
+ const char* tag_string;
+
+ tag_string =
+ spa_transform_value_to_gst_image_orientation(videotransform->transform);
+
+ GST_LOG_OBJECT (pwsrc, "got new videotransform: %u / %s",
+ videotransform->transform, tag_string);
+
+ tag_event = gst_event_new_tag(gst_tag_list_new(GST_TAG_IMAGE_ORIENTATION,
+ tag_string, NULL));
+ gst_pad_push_event (GST_BASE_SRC_PAD (pwsrc), tag_event);
+
+ pwsrc->transform_value = videotransform->transform;
+ }
+ }
+
+ for (i = 0; i < b->buffer->n_datas; i++) {
+ struct spa_data *d = &b->buffer->datas[i];
+ GstMemory *pmem = gst_buffer_peek_memory (data->buf, i);
+ if (pmem) {
+ GstMemory *mem;
+ if (!pwsrc->always_copy)
+ mem = gst_memory_share (pmem, d->chunk->offset, d->chunk->size);
+ else
+ mem = gst_memory_copy (pmem, d->chunk->offset, d->chunk->size);
+ gst_buffer_insert_memory (buf, i, mem);
+ }
+ if (d->chunk->flags & SPA_CHUNK_FLAG_CORRUPTED)
+ GST_BUFFER_FLAG_SET (buf, GST_BUFFER_FLAG_CORRUPTED);
+ }
+ if (!pwsrc->always_copy)
+ gst_buffer_add_parent_buffer_meta (buf, data->buf);
+ gst_buffer_unref (data->buf);
+ return buf;
+}
+
+static void
+on_process (void *_data)
+{
+ GstPipeWireSrc *pwsrc = _data;
+ pw_thread_loop_signal (pwsrc->core->loop, FALSE);
+}
+
+static void
+on_state_changed (void *data,
+ enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ GstPipeWireSrc *pwsrc = data;
+
+ GST_DEBUG ("got stream state %s", pw_stream_state_as_string (state));
+
+ switch (state) {
+ case PW_STREAM_STATE_UNCONNECTED:
+ case PW_STREAM_STATE_CONNECTING:
+ case PW_STREAM_STATE_PAUSED:
+ case PW_STREAM_STATE_STREAMING:
+ break;
+ case PW_STREAM_STATE_ERROR:
+ GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED,
+ ("stream error: %s", error), (NULL));
+ break;
+ }
+ pw_thread_loop_signal (pwsrc->core->loop, FALSE);
+}
+
+static void
+parse_stream_properties (GstPipeWireSrc *pwsrc, const struct pw_properties *props)
+{
+ const gchar *var;
+ gboolean is_live;
+
+ GST_OBJECT_LOCK (pwsrc);
+ var = pw_properties_get (props, PW_KEY_STREAM_IS_LIVE);
+ is_live = pwsrc->is_live = var ? pw_properties_parse_bool(var) : TRUE;
+
+ var = pw_properties_get (props, PW_KEY_STREAM_LATENCY_MIN);
+ pwsrc->min_latency = var ? (GstClockTime) atoi (var) : 0;
+
+ var = pw_properties_get (props, PW_KEY_STREAM_LATENCY_MAX);
+ pwsrc->max_latency = var ? (GstClockTime) atoi (var) : GST_CLOCK_TIME_NONE;
+ GST_OBJECT_UNLOCK (pwsrc);
+
+ GST_DEBUG_OBJECT (pwsrc, "live %d", is_live);
+
+ gst_base_src_set_live (GST_BASE_SRC (pwsrc), is_live);
+}
+
+static gboolean
+gst_pipewire_src_stream_start (GstPipeWireSrc *pwsrc)
+{
+ const char *error = NULL;
+ struct timespec abstime;
+
+ pw_thread_loop_lock (pwsrc->core->loop);
+ GST_DEBUG_OBJECT (pwsrc, "doing stream start");
+
+ pw_thread_loop_get_time (pwsrc->core->loop, &abstime,
+ GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC);
+
+ while (TRUE) {
+ enum pw_stream_state state = pw_stream_get_state (pwsrc->stream, &error);
+
+ GST_DEBUG_OBJECT (pwsrc, "waiting for STREAMING, now %s", pw_stream_state_as_string (state));
+ if (state == PW_STREAM_STATE_STREAMING)
+ break;
+
+ if (state == PW_STREAM_STATE_ERROR)
+ goto start_error;
+
+ if (pwsrc->flushing) {
+ error = "flushing";
+ goto start_error;
+ }
+
+ if (pw_thread_loop_timed_wait_full (pwsrc->core->loop, &abstime) < 0) {
+ error = "timeout";
+ goto start_error;
+ }
+ }
+
+ parse_stream_properties (pwsrc, pw_stream_get_properties (pwsrc->stream));
+ GST_DEBUG_OBJECT (pwsrc, "signal started");
+ pwsrc->started = TRUE;
+ pw_thread_loop_signal (pwsrc->core->loop, FALSE);
+ pw_thread_loop_unlock (pwsrc->core->loop);
+
+ return TRUE;
+
+start_error:
+ {
+ GST_DEBUG_OBJECT (pwsrc, "error starting stream: %s", error);
+ pw_thread_loop_signal (pwsrc->core->loop, FALSE);
+ pw_thread_loop_unlock (pwsrc->core->loop);
+ return FALSE;
+ }
+}
+
+static enum pw_stream_state
+wait_started (GstPipeWireSrc *this)
+{
+ enum pw_stream_state state;
+ const char *error = NULL;
+ struct timespec abstime;
+
+ pw_thread_loop_lock (this->core->loop);
+
+ pw_thread_loop_get_time (this->core->loop, &abstime,
+ GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC);
+
+ while (TRUE) {
+ state = pw_stream_get_state (this->stream, &error);
+
+ GST_DEBUG_OBJECT (this, "waiting for started signal, state now %s",
+ pw_stream_state_as_string (state));
+
+ if (state == PW_STREAM_STATE_ERROR)
+ break;
+
+ if (this->flushing) {
+ state = PW_STREAM_STATE_ERROR;
+ break;
+ }
+
+ if (this->started)
+ break;
+
+ if (pw_thread_loop_timed_wait_full (this->core->loop, &abstime) < 0) {
+ state = PW_STREAM_STATE_ERROR;
+ break;
+ }
+ }
+ GST_DEBUG_OBJECT (this, "got started signal: %s",
+ pw_stream_state_as_string (state));
+ pw_thread_loop_unlock (this->core->loop);
+
+ return state;
+}
+
+static gboolean
+gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
+{
+ GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (basesrc);
+ GstCaps *thiscaps;
+ GstCaps *caps = NULL;
+ GstCaps *peercaps = NULL;
+ gboolean result = FALSE;
+ GPtrArray *possible;
+ const char *error = NULL;
+ struct timespec abstime;
+ uint32_t target_id;
+
+ /* first see what is possible on our source pad */
+ thiscaps = gst_pad_query_caps (GST_BASE_SRC_PAD (basesrc), NULL);
+ GST_DEBUG_OBJECT (basesrc, "caps of src: %" GST_PTR_FORMAT, thiscaps);
+ /* nothing or anything is allowed, we're done */
+ if (thiscaps == NULL)
+ goto no_nego_needed;
+
+ if (G_UNLIKELY (gst_caps_is_empty (thiscaps)))
+ goto no_caps;
+
+ /* get the peer caps */
+ peercaps = gst_pad_peer_query_caps (GST_BASE_SRC_PAD (basesrc), thiscaps);
+ GST_DEBUG_OBJECT (basesrc, "caps of peer: %" GST_PTR_FORMAT, peercaps);
+ if (peercaps) {
+ /* The result is already a subset of our caps */
+ caps = peercaps;
+ gst_caps_unref (thiscaps);
+ } else {
+ /* no peer, work with our own caps then */
+ caps = thiscaps;
+ }
+ if (caps == NULL || gst_caps_is_empty (caps))
+ goto no_common_caps;
+
+ GST_DEBUG_OBJECT (basesrc, "have common caps: %" GST_PTR_FORMAT, caps);
+
+ /* open a connection with these caps */
+ possible = gst_caps_to_format_all (caps, SPA_PARAM_EnumFormat);
+ gst_caps_unref (caps);
+
+ /* first disconnect */
+ pw_thread_loop_lock (pwsrc->core->loop);
+ if (pw_stream_get_state(pwsrc->stream, &error) != PW_STREAM_STATE_UNCONNECTED) {
+ GST_DEBUG_OBJECT (basesrc, "disconnect capture");
+ pw_stream_disconnect (pwsrc->stream);
+ while (TRUE) {
+ enum pw_stream_state state = pw_stream_get_state (pwsrc->stream, &error);
+
+ GST_DEBUG_OBJECT (basesrc, "waiting for UNCONNECTED, now %s", pw_stream_state_as_string (state));
+ if (state == PW_STREAM_STATE_UNCONNECTED)
+ break;
+
+ if (state == PW_STREAM_STATE_ERROR || pwsrc->flushing) {
+ g_ptr_array_unref (possible);
+ goto connect_error;
+ }
+
+ pw_thread_loop_wait (pwsrc->core->loop);
+ }
+ }
+
+ target_id = pwsrc->path ? (uint32_t)atoi(pwsrc->path) : PW_ID_ANY;
+
+ if (pwsrc->target_object) {
+ struct spa_dict_item items[2] = {
+ SPA_DICT_ITEM_INIT(PW_KEY_TARGET_OBJECT, pwsrc->target_object),
+ /* XXX deprecated but the portal and some example apps only
+ * provide the object id */
+ SPA_DICT_ITEM_INIT(PW_KEY_NODE_TARGET, NULL),
+ };
+ struct spa_dict dict = SPA_DICT_INIT_ARRAY(items);
+ uint64_t serial;
+
+ /* If target.object is a name, set it also to node.target */
+ if (spa_atou64(pwsrc->target_object, &serial, 0)) {
+ dict.n_items = 1;
+ } else {
+ target_id = PW_ID_ANY;
+ items[1].value = pwsrc->target_object;
+ }
+
+ pw_stream_update_properties (pwsrc->stream, &dict);
+ }
+
+ GST_DEBUG_OBJECT (basesrc, "connect capture with path %s, target-object %s",
+ pwsrc->path, pwsrc->target_object);
+ pwsrc->negotiated = FALSE;
+ pw_stream_connect (pwsrc->stream,
+ PW_DIRECTION_INPUT,
+ target_id,
+ PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_DONT_RECONNECT,
+ (const struct spa_pod **)possible->pdata,
+ possible->len);
+ g_ptr_array_free (possible, TRUE);
+
+ pw_thread_loop_get_time (pwsrc->core->loop, &abstime,
+ GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC);
+
+ while (TRUE) {
+ enum pw_stream_state state = pw_stream_get_state (pwsrc->stream, &error);
+
+ GST_DEBUG_OBJECT (basesrc, "waiting for NEGOTIATED, now %s", pw_stream_state_as_string (state));
+ if (state == PW_STREAM_STATE_ERROR || pwsrc->flushing)
+ goto connect_error;
+
+ if (pwsrc->negotiated)
+ break;
+
+ if (pw_thread_loop_timed_wait_full (pwsrc->core->loop, &abstime) < 0)
+ goto connect_error;
+ }
+ caps = pwsrc->caps;
+ pwsrc->caps = NULL;
+ pw_thread_loop_unlock (pwsrc->core->loop);
+
+ if (caps == NULL)
+ goto no_caps;
+
+ gst_pipewire_clock_reset (GST_PIPEWIRE_CLOCK (pwsrc->clock), 0);
+
+ GST_DEBUG_OBJECT (pwsrc, "set format %" GST_PTR_FORMAT, caps);
+ result = gst_base_src_set_caps (GST_BASE_SRC (pwsrc), caps);
+ gst_caps_unref (caps);
+
+ result = gst_pipewire_src_stream_start (pwsrc);
+
+ pwsrc->started = result;
+
+ return result;
+
+no_nego_needed:
+ {
+ GST_DEBUG_OBJECT (basesrc, "no negotiation needed");
+ if (thiscaps)
+ gst_caps_unref (thiscaps);
+ return TRUE;
+ }
+no_caps:
+ {
+ GST_ELEMENT_ERROR (basesrc, STREAM, FORMAT,
+ ("No supported formats found"),
+ ("This element did not produce valid caps"));
+ if (thiscaps)
+ gst_caps_unref (thiscaps);
+ return FALSE;
+ }
+no_common_caps:
+ {
+ GST_ELEMENT_ERROR (basesrc, STREAM, FORMAT,
+ ("No supported formats found"),
+ ("This element does not have formats in common with the peer"));
+ if (caps)
+ gst_caps_unref (caps);
+ return FALSE;
+ }
+connect_error:
+ {
+ GST_DEBUG_OBJECT (basesrc, "connect error");
+ pw_thread_loop_unlock (pwsrc->core->loop);
+ return FALSE;
+ }
+}
+
+static void
+on_param_changed (void *data, uint32_t id,
+ const struct spa_pod *param)
+{
+ GstPipeWireSrc *pwsrc = data;
+
+ if (param == NULL || id != SPA_PARAM_Format) {
+ GST_DEBUG_OBJECT (pwsrc, "clear format");
+ return;
+ }
+ if (pwsrc->caps)
+ gst_caps_unref(pwsrc->caps);
+ pwsrc->caps = gst_caps_from_format (param);
+
+ pwsrc->negotiated = pwsrc->caps != NULL;
+
+ if (pwsrc->negotiated) {
+ const struct spa_pod *params[4];
+ struct spa_pod_builder b = { NULL };
+ uint8_t buffer[512];
+ uint32_t buffers = CLAMP (16, pwsrc->min_buffers, pwsrc->max_buffers);
+ int buffertypes;
+
+ buffertypes = (1<<SPA_DATA_DmaBuf);
+ if (spa_pod_find_prop (param, NULL, SPA_FORMAT_VIDEO_modifier)) {
+ gst_caps_features_remove (gst_caps_get_features (pwsrc->caps, 0),
+ GST_CAPS_FEATURE_MEMORY_SYSTEM_MEMORY);
+ gst_caps_features_add (gst_caps_get_features (pwsrc->caps, 0),
+ GST_CAPS_FEATURE_MEMORY_DMABUF);
+ } else {
+ buffertypes |= ((1<<SPA_DATA_MemFd) | (1<<SPA_DATA_MemPtr));
+ }
+
+ GST_DEBUG_OBJECT (pwsrc, "we got format %" GST_PTR_FORMAT, pwsrc->caps);
+
+ spa_pod_builder_init (&b, buffer, sizeof (buffer));
+ params[0] = spa_pod_builder_add_object (&b,
+ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers,
+ pwsrc->min_buffers,
+ pwsrc->max_buffers),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_CHOICE_RANGE_Int(0, 1, INT32_MAX),
+ SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_CHOICE_RANGE_Int(0, 0, INT32_MAX),
+ SPA_PARAM_BUFFERS_dataType, SPA_POD_CHOICE_FLAGS_Int(buffertypes));
+
+ params[1] = spa_pod_builder_add_object (&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Header),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_header)));
+ params[2] = spa_pod_builder_add_object (&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoCrop),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_region)));
+ params[3] = spa_pod_builder_add_object (&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_VideoTransform),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof (struct spa_meta_videotransform)));
+
+ GST_DEBUG_OBJECT (pwsrc, "doing finish format");
+ pw_stream_update_params (pwsrc->stream, params, SPA_N_ELEMENTS(params));
+ } else {
+ GST_WARNING_OBJECT (pwsrc, "finish format with error");
+ pw_stream_set_error (pwsrc->stream, -EINVAL, "unhandled format");
+ }
+ pw_thread_loop_signal (pwsrc->core->loop, FALSE);
+}
+
+static gboolean
+gst_pipewire_src_unlock (GstBaseSrc * basesrc)
+{
+ GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (basesrc);
+
+ pw_thread_loop_lock (pwsrc->core->loop);
+ GST_DEBUG_OBJECT (pwsrc, "setting flushing");
+ pwsrc->flushing = TRUE;
+ pw_thread_loop_signal (pwsrc->core->loop, FALSE);
+ pw_thread_loop_unlock (pwsrc->core->loop);
+
+ return TRUE;
+}
+
+static gboolean
+gst_pipewire_src_unlock_stop (GstBaseSrc * basesrc)
+{
+ GstPipeWireSrc *pwsrc = GST_PIPEWIRE_SRC (basesrc);
+
+ pw_thread_loop_lock (pwsrc->core->loop);
+ GST_DEBUG_OBJECT (pwsrc, "unsetting flushing");
+ pwsrc->flushing = FALSE;
+ pw_thread_loop_unlock (pwsrc->core->loop);
+
+ return TRUE;
+}
+
+static gboolean
+gst_pipewire_src_event (GstBaseSrc * src, GstEvent * event)
+{
+ gboolean res = FALSE;
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_CUSTOM_UPSTREAM:
+ if (gst_video_event_is_force_key_unit (event)) {
+ GstClockTime running_time;
+ gboolean all_headers;
+ guint count;
+
+ gst_video_event_parse_upstream_force_key_unit (event,
+ &running_time, &all_headers, &count);
+
+ res = TRUE;
+ } else {
+ res = GST_BASE_SRC_CLASS (parent_class)->event (src, event);
+ }
+ break;
+ default:
+ res = GST_BASE_SRC_CLASS (parent_class)->event (src, event);
+ break;
+ }
+ return res;
+}
+
+static gboolean
+gst_pipewire_src_query (GstBaseSrc * src, GstQuery * query)
+{
+ gboolean res = FALSE;
+ GstPipeWireSrc *pwsrc;
+
+ pwsrc = GST_PIPEWIRE_SRC (src);
+
+ switch (GST_QUERY_TYPE (query)) {
+ case GST_QUERY_LATENCY:
+ GST_OBJECT_LOCK (pwsrc);
+ pwsrc->min_latency = 10000000;
+ pwsrc->max_latency = GST_CLOCK_TIME_NONE;
+ gst_query_set_latency (query, pwsrc->is_live, pwsrc->min_latency, pwsrc->max_latency);
+ GST_OBJECT_UNLOCK (pwsrc);
+ res = TRUE;
+ break;
+ default:
+ res = GST_BASE_SRC_CLASS (parent_class)->query (src, query);
+ break;
+ }
+ return res;
+}
+
+static GstFlowReturn
+gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer)
+{
+ GstPipeWireSrc *pwsrc;
+ GstClockTime pts, dts, base_time;
+ const char *error = NULL;
+ GstBuffer *buf;
+ gboolean update_time = FALSE, timeout = FALSE;
+
+ pwsrc = GST_PIPEWIRE_SRC (psrc);
+
+ pw_thread_loop_lock (pwsrc->core->loop);
+ if (!pwsrc->negotiated)
+ goto not_negotiated;
+
+ while (TRUE) {
+ enum pw_stream_state state;
+
+ if (pwsrc->flushing)
+ goto streaming_stopped;
+
+ if (pwsrc->stream == NULL)
+ goto streaming_error;
+
+ state = pw_stream_get_state (pwsrc->stream, &error);
+ if (state == PW_STREAM_STATE_ERROR)
+ goto streaming_error;
+
+ if (state != PW_STREAM_STATE_STREAMING)
+ goto streaming_stopped;
+
+ if (pwsrc->eos) {
+ if (pwsrc->last_buffer == NULL)
+ goto streaming_eos;
+ buf = pwsrc->last_buffer;
+ pwsrc->last_buffer = NULL;
+ update_time = TRUE;
+ GST_LOG_OBJECT (pwsrc, "EOS, send last buffer");
+ break;
+ } else if (timeout) {
+ if (pwsrc->last_buffer != NULL) {
+ update_time = TRUE;
+ buf = gst_buffer_ref(pwsrc->last_buffer);
+ GST_LOG_OBJECT (pwsrc, "timeout, send keepalive buffer");
+ break;
+ }
+ } else {
+ buf = dequeue_buffer (pwsrc);
+ GST_LOG_OBJECT (pwsrc, "popped buffer %p", buf);
+ if (buf != NULL) {
+ if (pwsrc->resend_last || pwsrc->keepalive_time > 0)
+ gst_buffer_replace (&pwsrc->last_buffer, buf);
+ break;
+ }
+ }
+ timeout = FALSE;
+ if (pwsrc->keepalive_time > 0) {
+ struct timespec abstime;
+ pw_thread_loop_get_time(pwsrc->core->loop, &abstime,
+ pwsrc->keepalive_time * SPA_NSEC_PER_MSEC);
+ if (pw_thread_loop_timed_wait_full (pwsrc->core->loop, &abstime) == -ETIMEDOUT)
+ timeout = TRUE;
+ } else {
+ pw_thread_loop_wait (pwsrc->core->loop);
+ }
+ }
+ pw_thread_loop_unlock (pwsrc->core->loop);
+
+ *buffer = buf;
+
+ if (pwsrc->is_live)
+ base_time = GST_ELEMENT_CAST (psrc)->base_time;
+ else
+ base_time = 0;
+
+ if (update_time) {
+ GstClock *clock = gst_element_get_clock (GST_ELEMENT_CAST (pwsrc));
+ if (clock != NULL) {
+ pts = dts = gst_clock_get_time (clock);
+ gst_object_unref (clock);
+ } else {
+ pts = dts = GST_CLOCK_TIME_NONE;
+ }
+ } else {
+ pts = GST_BUFFER_PTS (*buffer);
+ dts = GST_BUFFER_DTS (*buffer);
+ }
+
+ if (GST_CLOCK_TIME_IS_VALID (pts))
+ pts = (pts >= base_time ? pts - base_time : 0);
+ if (GST_CLOCK_TIME_IS_VALID (dts))
+ dts = (dts >= base_time ? dts - base_time : 0);
+
+ GST_LOG_OBJECT (pwsrc,
+ "pts %" G_GUINT64_FORMAT ", dts %" G_GUINT64_FORMAT
+ ", base-time %" GST_TIME_FORMAT " -> %" GST_TIME_FORMAT ", %" GST_TIME_FORMAT,
+ GST_BUFFER_PTS (*buffer), GST_BUFFER_DTS (*buffer), GST_TIME_ARGS (base_time),
+ GST_TIME_ARGS (pts), GST_TIME_ARGS (dts));
+
+ GST_BUFFER_PTS (*buffer) = pts;
+ GST_BUFFER_DTS (*buffer) = dts;
+
+ return GST_FLOW_OK;
+
+not_negotiated:
+ {
+ pw_thread_loop_unlock (pwsrc->core->loop);
+ return GST_FLOW_NOT_NEGOTIATED;
+ }
+streaming_eos:
+ {
+ pw_thread_loop_unlock (pwsrc->core->loop);
+ return GST_FLOW_EOS;
+ }
+streaming_error:
+ {
+ pw_thread_loop_unlock (pwsrc->core->loop);
+ return GST_FLOW_ERROR;
+ }
+streaming_stopped:
+ {
+ pw_thread_loop_unlock (pwsrc->core->loop);
+ return GST_FLOW_FLUSHING;
+ }
+}
+
+static gboolean
+gst_pipewire_src_start (GstBaseSrc * basesrc)
+{
+ return TRUE;
+}
+
+static gboolean
+gst_pipewire_src_stop (GstBaseSrc * basesrc)
+{
+ GstPipeWireSrc *pwsrc;
+
+ pwsrc = GST_PIPEWIRE_SRC (basesrc);
+
+ pw_thread_loop_lock (pwsrc->core->loop);
+ pwsrc->eos = false;
+ gst_buffer_replace (&pwsrc->last_buffer, NULL);
+ gst_caps_replace(&pwsrc->caps, NULL);
+ pw_thread_loop_unlock (pwsrc->core->loop);
+
+ return TRUE;
+}
+
+static gboolean
+copy_properties (GQuark field_id,
+ const GValue *value,
+ gpointer user_data)
+{
+ struct pw_properties *properties = user_data;
+ GValue dst = { 0 };
+
+ if (g_value_type_transformable (G_VALUE_TYPE(value), G_TYPE_STRING)) {
+ g_value_init(&dst, G_TYPE_STRING);
+ if (g_value_transform(value, &dst)) {
+ pw_properties_set (properties,
+ g_quark_to_string (field_id),
+ g_value_get_string (&dst));
+ }
+ g_value_unset(&dst);
+ }
+ return TRUE;
+}
+
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .state_changed = on_state_changed,
+ .param_changed = on_param_changed,
+ .add_buffer = on_add_buffer,
+ .remove_buffer = on_remove_buffer,
+ .process = on_process,
+};
+
+static gboolean
+gst_pipewire_src_open (GstPipeWireSrc * pwsrc)
+{
+ struct pw_properties *props;
+
+ GST_DEBUG_OBJECT (pwsrc, "open");
+
+ pwsrc->core = gst_pipewire_core_get(pwsrc->fd);
+ if (pwsrc->core == NULL)
+ goto connect_error;
+
+ pw_thread_loop_lock (pwsrc->core->loop);
+
+ props = pw_properties_new (NULL, NULL);
+ if (pwsrc->client_properties) {
+ gst_structure_foreach (pwsrc->client_properties, copy_properties, props);
+ pw_core_update_properties (pwsrc->core->core, &props->dict);
+ pw_properties_clear(props);
+ }
+ if (pwsrc->client_name) {
+ pw_properties_set (props, PW_KEY_NODE_NAME, pwsrc->client_name);
+ pw_properties_set (props, PW_KEY_NODE_DESCRIPTION, pwsrc->client_name);
+ }
+ if (pwsrc->stream_properties) {
+ gst_structure_foreach (pwsrc->stream_properties, copy_properties, props);
+ }
+
+ if ((pwsrc->stream = pw_stream_new (pwsrc->core->core,
+ pwsrc->client_name, props)) == NULL)
+ goto no_stream;
+
+
+ pw_stream_add_listener(pwsrc->stream,
+ &pwsrc->stream_listener,
+ &stream_events,
+ pwsrc);
+
+ pwsrc->clock = gst_pipewire_clock_new (pwsrc->stream, pwsrc->last_time);
+ pw_thread_loop_unlock (pwsrc->core->loop);
+
+ return TRUE;
+
+ /* ERRORS */
+connect_error:
+ {
+ GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED, ("can't connect"), (NULL));
+ return FALSE;
+ }
+no_stream:
+ {
+ GST_ELEMENT_ERROR (pwsrc, RESOURCE, FAILED, ("can't create stream"), (NULL));
+ pw_thread_loop_unlock (pwsrc->core->loop);
+ gst_pipewire_core_release (pwsrc->core);
+ pwsrc->core = NULL;
+ return FALSE;
+ }
+}
+
+static void
+gst_pipewire_src_close (GstPipeWireSrc * pwsrc)
+{
+ pwsrc->last_time = gst_clock_get_time (pwsrc->clock);
+
+ GST_DEBUG_OBJECT (pwsrc, "close");
+
+ gst_element_post_message (GST_ELEMENT (pwsrc),
+ gst_message_new_clock_lost (GST_OBJECT_CAST (pwsrc), pwsrc->clock));
+
+ GST_OBJECT_LOCK (pwsrc);
+ GST_PIPEWIRE_CLOCK (pwsrc->clock)->stream = NULL;
+ g_clear_object (&pwsrc->clock);
+ GST_OBJECT_UNLOCK (pwsrc);
+
+ GST_OBJECT_LOCK (pwsrc->pool);
+ pw_thread_loop_lock (pwsrc->core->loop);
+ if (pwsrc->stream) {
+ pw_stream_destroy (pwsrc->stream);
+ pwsrc->stream = NULL;
+ }
+ pw_thread_loop_unlock (pwsrc->core->loop);
+ GST_OBJECT_UNLOCK (pwsrc->pool);
+
+ if (pwsrc->core) {
+ gst_pipewire_core_release (pwsrc->core);
+ pwsrc->core = NULL;
+ }
+}
+
+static gboolean
+gst_pipewire_src_send_event (GstElement * elem, GstEvent * event)
+{
+ GstPipeWireSrc *this = GST_PIPEWIRE_SRC_CAST (elem);
+ gboolean ret;
+
+ switch (GST_EVENT_TYPE (event)) {
+ case GST_EVENT_EOS:
+ GST_DEBUG_OBJECT (this, "got EOS");
+ pw_thread_loop_lock (this->core->loop);
+ this->eos = true;
+ pw_thread_loop_signal (this->core->loop, FALSE);
+ pw_thread_loop_unlock (this->core->loop);
+ ret = TRUE;
+ break;
+ default:
+ ret = GST_ELEMENT_CLASS (parent_class)->send_event (elem, event);
+ break;
+ }
+ return ret;
+}
+
+static GstStateChangeReturn
+gst_pipewire_src_change_state (GstElement * element, GstStateChange transition)
+{
+ GstStateChangeReturn ret;
+ GstPipeWireSrc *this = GST_PIPEWIRE_SRC_CAST (element);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_NULL_TO_READY:
+ if (!gst_pipewire_src_open (this))
+ goto open_failed;
+ break;
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
+ /* uncork and start recording */
+ pw_thread_loop_lock (this->core->loop);
+ pw_stream_set_active(this->stream, true);
+ pw_thread_loop_unlock (this->core->loop);
+ break;
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ /* stop recording ASAP by corking */
+ pw_thread_loop_lock (this->core->loop);
+ pw_stream_set_active(this->stream, false);
+ pw_thread_loop_unlock (this->core->loop);
+ break;
+ default:
+ break;
+ }
+
+ ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
+
+ switch (transition) {
+ case GST_STATE_CHANGE_READY_TO_PAUSED:
+ if (wait_started (this) == PW_STREAM_STATE_ERROR)
+ goto open_failed;
+
+ if (gst_base_src_is_live (GST_BASE_SRC (element)))
+ ret = GST_STATE_CHANGE_NO_PREROLL;
+ break;
+ case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
+ break;
+ case GST_STATE_CHANGE_PAUSED_TO_READY:
+ pw_thread_loop_lock (this->core->loop);
+ this->negotiated = FALSE;
+ pw_thread_loop_unlock (this->core->loop);
+ break;
+ case GST_STATE_CHANGE_READY_TO_NULL:
+ gst_pipewire_src_close (this);
+ break;
+ default:
+ break;
+ }
+ return ret;
+
+ /* ERRORS */
+open_failed:
+ {
+ return GST_STATE_CHANGE_FAILURE;
+ }
+}
diff --git a/src/gst/gstpipewiresrc.h b/src/gst/gstpipewiresrc.h
new file mode 100644
index 0000000..e1def85
--- /dev/null
+++ b/src/gst/gstpipewiresrc.h
@@ -0,0 +1,110 @@
+/* GStreamer
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __GST_PIPEWIRE_SRC_H__
+#define __GST_PIPEWIRE_SRC_H__
+
+#include <gst/gst.h>
+#include <gst/base/gstpushsrc.h>
+
+#include <pipewire/pipewire.h>
+#include <gst/gstpipewirepool.h>
+#include <gst/gstpipewirecore.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_PIPEWIRE_SRC \
+ (gst_pipewire_src_get_type())
+#define GST_PIPEWIRE_SRC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_PIPEWIRE_SRC,GstPipeWireSrc))
+#define GST_PIPEWIRE_SRC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_PIPEWIRE_SRC,GstPipeWireSrcClass))
+#define GST_IS_PIPEWIRE_SRC(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_PIPEWIRE_SRC))
+#define GST_IS_PIPEWIRE_SRC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_PIPEWIRE_SRC))
+#define GST_PIPEWIRE_SRC_CAST(obj) \
+ ((GstPipeWireSrc *) (obj))
+
+typedef struct _GstPipeWireSrc GstPipeWireSrc;
+typedef struct _GstPipeWireSrcClass GstPipeWireSrcClass;
+
+/**
+ * GstPipeWireSrc:
+ *
+ * Opaque data structure.
+ */
+struct _GstPipeWireSrc {
+ GstPushSrc element;
+
+ /*< private >*/
+ gchar *path;
+ gchar *target_object;
+ gchar *client_name;
+ gboolean always_copy;
+ gint min_buffers;
+ gint max_buffers;
+ int fd;
+ gboolean resend_last;
+ gint keepalive_time;
+
+ GstCaps *caps;
+
+ gboolean negotiated;
+ gboolean flushing;
+ gboolean started;
+ gboolean eos;
+
+ gboolean is_live;
+ GstClockTime min_latency;
+ GstClockTime max_latency;
+
+ GstStructure *client_properties;
+ GstPipeWireCore *core;
+ struct spa_hook core_listener;
+ int last_seq;
+ int pending_seq;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ GstBuffer *last_buffer;
+ GstStructure *stream_properties;
+
+ GstPipeWirePool *pool;
+ GstClock *clock;
+ GstClockTime last_time;
+
+ enum spa_meta_videotransform_value transform_value;
+};
+
+struct _GstPipeWireSrcClass {
+ GstPushSrcClass parent_class;
+};
+
+GType gst_pipewire_src_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_PIPEWIRE_SRC_H__ */
diff --git a/src/gst/meson.build b/src/gst/meson.build
new file mode 100644
index 0000000..fd552f6
--- /dev/null
+++ b/src/gst/meson.build
@@ -0,0 +1,33 @@
+pipewire_gst_sources = [
+ 'gstpipewire.c',
+ 'gstpipewirecore.c',
+ 'gstpipewireclock.c',
+ 'gstpipewireformat.c',
+ 'gstpipewirepool.c',
+ 'gstpipewiresink.c',
+ 'gstpipewiresrc.c',
+]
+
+if get_option('gstreamer-device-provider').allowed()
+ pipewire_gst_sources += [ 'gstpipewiredeviceprovider.c' ]
+endif
+
+pipewire_gst_headers = [
+ 'gstpipewireclock.h',
+ 'gstpipewirecore.h',
+ 'gstpipewiredeviceprovider.h',
+ 'gstpipewireformat.h',
+ 'gstpipewirepool.h',
+ 'gstpipewiresink.h',
+ 'gstpipewiresrc.h',
+]
+
+pipewire_gst = shared_library('gstpipewire',
+ pipewire_gst_sources,
+ include_directories : [ configinc ],
+ dependencies : [ spa_dep, gst_dep, pipewire_dep ],
+ install : true,
+ install_dir : '@0@/gstreamer-1.0'.format(get_option('libdir')),
+)
+
+plugins = [pipewire_gst]
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..cefc329
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,15 @@
+
+subdir('pipewire')
+subdir('daemon')
+subdir('tools')
+subdir('modules')
+if get_option('examples').allowed()
+ subdir('examples')
+endif
+if get_option('tests').allowed()
+ subdir('tests')
+endif
+
+if gst_dep.length() != 0
+ subdir('gst')
+endif
diff --git a/src/modules/flatpak-utils.h b/src/modules/flatpak-utils.h
new file mode 100644
index 0000000..8952ac4
--- /dev/null
+++ b/src/modules/flatpak-utils.h
@@ -0,0 +1,156 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef FLATPAK_UTILS_H
+#define FLATPAK_UTILS_H
+
+#include <stdio.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_VFS_H
+#include <sys/vfs.h>
+#endif
+
+#include "config.h"
+
+#ifdef HAVE_GLIB2
+#include <glib.h>
+#endif
+
+#include <spa/utils/result.h>
+#include <pipewire/log.h>
+
+static int pw_check_flatpak_parse_metadata(const char *buf, size_t size, char **app_id, char **devices)
+{
+#ifdef HAVE_GLIB2
+ /*
+ * See flatpak-metadata(5)
+ *
+ * The .flatpak-info file is in GLib key_file .ini format.
+ */
+ g_autoptr(GKeyFile) metadata = NULL;
+ char *s;
+
+ metadata = g_key_file_new();
+ if (!g_key_file_load_from_data(metadata, buf, size, G_KEY_FILE_NONE, NULL))
+ return -EINVAL;
+
+ if (app_id) {
+ s = g_key_file_get_value(metadata, "Application", "name", NULL);
+ *app_id = s ? strdup(s) : NULL;
+ g_free(s);
+ }
+
+ if (devices) {
+ s = g_key_file_get_value(metadata, "Context", "devices", NULL);
+ *devices = s ? strdup(s) : NULL;
+ g_free(s);
+ }
+
+ return 0;
+#else
+ return -ENOTSUP;
+#endif
+}
+
+static int pw_check_flatpak(pid_t pid, char **app_id, char **devices)
+{
+#if defined(__linux__)
+ char root_path[2048];
+ int root_fd, info_fd, res;
+ struct stat stat_buf;
+
+ if (app_id)
+ *app_id = NULL;
+ if (devices)
+ *devices = NULL;
+
+ snprintf(root_path, sizeof(root_path), "/proc/%d/root", (int)pid);
+ root_fd = openat (AT_FDCWD, root_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY);
+ if (root_fd == -1) {
+ res = -errno;
+ if (res == -EACCES) {
+ struct statfs buf;
+ /* Access to the root dir isn't allowed. This can happen if the root is on a fuse
+ * filesystem, such as in a toolbox container. We will never have a fuse rootfs
+ * in the flatpak case, so in that case its safe to ignore this and
+ * continue to detect other types of apps. */
+ if (statfs(root_path, &buf) == 0 &&
+ buf.f_type == 0x65735546) /* FUSE_SUPER_MAGIC */
+ return 0;
+ }
+ /* Not able to open the root dir shouldn't happen. Probably the app died and
+ * we're failing due to /proc/$pid not existing. In that case fail instead
+ * of treating this as privileged. */
+ pw_log_info("failed to open \"%s\": %s", root_path, spa_strerror(res));
+ return res;
+ }
+ info_fd = openat (root_fd, ".flatpak-info", O_RDONLY | O_CLOEXEC | O_NOCTTY);
+ close (root_fd);
+ if (info_fd == -1) {
+ if (errno == ENOENT) {
+ pw_log_debug("no .flatpak-info, client on the host");
+ /* No file => on the host */
+ return 0;
+ }
+ res = -errno;
+ pw_log_error("error opening .flatpak-info: %m");
+ return res;
+ }
+ if (fstat (info_fd, &stat_buf) != 0 || !S_ISREG (stat_buf.st_mode)) {
+ /* Some weird fd => failure, assume sandboxed */
+ pw_log_error("error fstat .flatpak-info: %m");
+ } else if (app_id || devices) {
+ /* Parse the application ID if needed */
+ const size_t size = stat_buf.st_size;
+
+ if (size > 0) {
+ void *buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, info_fd, 0);
+ if (buf != MAP_FAILED) {
+ res = pw_check_flatpak_parse_metadata(buf, size, app_id, devices);
+ munmap(buf, size);
+ } else {
+ res = -errno;
+ }
+ } else {
+ res = -EINVAL;
+ }
+
+ if (res == -EINVAL)
+ pw_log_error("PID %d .flatpak-info file is malformed",
+ (int)pid);
+ else if (res < 0)
+ pw_log_error("PID %d .flatpak-info parsing failed: %s",
+ (int)pid, spa_strerror(res));
+ }
+ close(info_fd);
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+#endif /* FLATPAK_UTILS_H */
diff --git a/src/modules/meson.build b/src/modules/meson.build
new file mode 100644
index 0000000..89e4233
--- /dev/null
+++ b/src/modules/meson.build
@@ -0,0 +1,609 @@
+subdir('spa')
+
+# The list of "main" source files for modules, the ones that have the
+# doxygen documentation
+module_sources = [
+ 'module-access.c',
+ 'module-adapter.c',
+ 'module-avb.c',
+ 'module-client-device.c',
+ 'module-client-node.c',
+ 'module-combine-stream.c',
+ 'module-echo-cancel.c',
+ 'module-example-sink.c',
+ 'module-example-source.c',
+ 'module-fallback-sink.c',
+ 'module-filter-chain.c',
+ 'module-link-factory.c',
+ 'module-loopback.c',
+ 'module-metadata.c',
+ 'module-pipe-tunnel.c',
+ 'module-portal.c',
+ 'module-profiler.c',
+ 'module-protocol-native.c',
+ 'module-protocol-pulse.c',
+ 'module-protocol-simple.c',
+ 'module-pulse-tunnel.c',
+ 'module-rt.c',
+ 'module-raop-discover.c',
+ 'module-raop-sink.c',
+ 'module-rtp-source.c',
+ 'module-rtp-sink.c',
+ 'module-session-manager.c',
+ 'module-zeroconf-discover.c',
+ 'module-roc-source.c',
+ 'module-roc-sink.c',
+ 'module-x11-bell.c',
+]
+
+pipewire_module_access_deps = [spa_dep, mathlib, dl_lib, pipewire_dep]
+if flatpak_support
+ pipewire_module_access_deps += glib2_dep
+endif
+
+pipewire_module_access = shared_library('pipewire-module-access', [ 'module-access.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : pipewire_module_access_deps
+)
+
+pipewire_module_loopback = shared_library('pipewire-module-loopback',
+ [ 'module-loopback.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep],
+)
+
+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, audioconvert_dep
+]
+
+if lilv_lib.found()
+ filter_chain_sources += [
+ 'module-filter-chain/lv2_plugin.c'
+ ]
+ filter_chain_dependencies += [ lilv_lib ]
+endif
+
+
+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,
+)
+
+pipewire_module_echo_cancel_sources = [
+ 'module-echo-cancel.c',
+]
+
+pipewire_module_combine_stream = shared_library('pipewire-module-combine-stream',
+ [ 'module-combine-stream.c' ],
+ include_directories : [configinc],
+ install : false,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [spa_dep, dl_lib, pipewire_dep],
+)
+
+pipewire_module_echo_cancel = shared_library('pipewire-module-echo-cancel',
+ pipewire_module_echo_cancel_sources,
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [mathlib, dl_lib, pipewire_dep],
+)
+
+pipewire_module_profiler = shared_library('pipewire-module-profiler',
+ [ 'module-profiler.c',
+ 'module-profiler/protocol-native.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_rt = shared_library('pipewire-module-rt', [ 'module-rt.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [dbus_dep, mathlib, dl_lib, pipewire_dep],
+)
+
+build_module_rtkit = dbus_dep.found() and (get_option('legacy-rtkit') == true)
+if build_module_rtkit
+# TODO: This serves as a temporary alias to prevent breaking existing setups
+# while `module-rtkit` is being migrated to `module-rt`
+pipewire_module_rtkit = shared_library('pipewire-module-rtkit', [ 'module-rt.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [dbus_dep, mathlib, dl_lib, pipewire_dep],
+)
+endif
+summary({'rt': '@0@ RTKit'.format(build_module_rtkit ? 'with' : 'without')}, section: 'Optional Modules')
+
+build_module_portal = dbus_dep.found()
+if build_module_portal
+pipewire_module_portal = shared_library('pipewire-module-portal', [ 'module-portal.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [dbus_dep, mathlib, dl_lib, pipewire_dep],
+)
+endif
+summary({'portal': build_module_portal}, bool_yn: true, section: 'Optional Modules')
+
+pipewire_module_client_device = shared_library('pipewire-module-client-device',
+ [ 'module-client-device.c',
+ 'module-client-device/resource-device.c',
+ 'module-client-device/proxy-device.c',
+ 'module-client-device/protocol-native.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_link_factory = shared_library('pipewire-module-link-factory',
+ [ 'module-link-factory.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_protocol_deps = [mathlib, dl_lib, pipewire_dep]
+
+if systemd_dep.found()
+ pipewire_module_protocol_deps += systemd_dep
+endif
+
+pipewire_module_protocol_native = shared_library('pipewire-module-protocol-native',
+ [ 'module-protocol-native.c',
+ 'module-protocol-native/local-socket.c',
+ 'module-protocol-native/portal-screencast.c',
+ 'module-protocol-native/protocol-native.c',
+ 'module-protocol-native/v0/protocol-native.c',
+ 'module-protocol-native/protocol-footer.c',
+ 'module-protocol-native/connection.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : pipewire_module_protocol_deps,
+)
+
+pipewire_module_protocol_pulse_deps = pipewire_module_protocol_deps
+
+pipewire_module_protocol_pulse_sources = [
+ 'module-protocol-pulse.c',
+ 'module-protocol-pulse/client.c',
+ 'module-protocol-pulse/collect.c',
+ 'module-protocol-pulse/cmd.c',
+ 'module-protocol-pulse/extension.c',
+ 'module-protocol-pulse/extensions/ext-device-manager.c',
+ 'module-protocol-pulse/extensions/ext-device-restore.c',
+ 'module-protocol-pulse/extensions/ext-stream-restore.c',
+ 'module-protocol-pulse/format.c',
+ 'module-protocol-pulse/manager.c',
+ 'module-protocol-pulse/message.c',
+ 'module-protocol-pulse/message-handler.c',
+ 'module-protocol-pulse/module.c',
+ 'module-protocol-pulse/operation.c',
+ 'module-protocol-pulse/pending-sample.c',
+ 'module-protocol-pulse/pulse-server.c',
+ 'module-protocol-pulse/quirks.c',
+ 'module-protocol-pulse/remap.c',
+ 'module-protocol-pulse/reply.c',
+ 'module-protocol-pulse/sample.c',
+ 'module-protocol-pulse/sample-play.c',
+ 'module-protocol-pulse/server.c',
+ 'module-protocol-pulse/stream.c',
+ 'module-protocol-pulse/utils.c',
+ 'module-protocol-pulse/volume.c',
+ 'module-protocol-pulse/modules/module-always-sink.c',
+ 'module-protocol-pulse/modules/module-combine-sink.c',
+ 'module-protocol-pulse/modules/module-echo-cancel.c',
+ 'module-protocol-pulse/modules/module-ladspa-sink.c',
+ 'module-protocol-pulse/modules/module-ladspa-source.c',
+ 'module-protocol-pulse/modules/module-loopback.c',
+ 'module-protocol-pulse/modules/module-native-protocol-tcp.c',
+ 'module-protocol-pulse/modules/module-null-sink.c',
+ 'module-protocol-pulse/modules/module-pipe-source.c',
+ 'module-protocol-pulse/modules/module-pipe-sink.c',
+ 'module-protocol-pulse/modules/module-raop-discover.c',
+ 'module-protocol-pulse/modules/module-remap-sink.c',
+ 'module-protocol-pulse/modules/module-remap-source.c',
+ 'module-protocol-pulse/modules/module-roc-sink.c',
+ 'module-protocol-pulse/modules/module-roc-sink-input.c',
+ 'module-protocol-pulse/modules/module-roc-source.c',
+ 'module-protocol-pulse/modules/module-rtp-recv.c',
+ 'module-protocol-pulse/modules/module-rtp-send.c',
+ 'module-protocol-pulse/modules/module-simple-protocol-tcp.c',
+ 'module-protocol-pulse/modules/module-switch-on-connect.c',
+ 'module-protocol-pulse/modules/module-tunnel-sink.c',
+ 'module-protocol-pulse/modules/module-tunnel-source.c',
+ 'module-protocol-pulse/modules/module-x11-bell.c',
+ 'module-protocol-pulse/modules/module-zeroconf-discover.c',
+]
+
+if dbus_dep.found()
+ pipewire_module_protocol_pulse_sources += [
+ 'module-protocol-pulse/dbus-name.c',
+ ]
+ pipewire_module_protocol_pulse_deps += dbus_dep
+endif
+
+if avahi_dep.found()
+ pipewire_module_protocol_pulse_sources += [
+ 'module-protocol-pulse/modules/module-zeroconf-publish.c',
+ 'module-zeroconf-discover/avahi-poll.c',
+ ]
+ pipewire_module_protocol_pulse_deps += avahi_dep
+ cdata.set('HAVE_AVAHI', true)
+endif
+
+if gio_dep.found()
+ pipewire_module_protocol_pulse_sources += [
+ 'module-protocol-pulse/modules/module-gsettings.c',
+ ]
+ pipewire_module_protocol_pulse_deps += gio_dep
+ cdata.set('HAVE_GIO', true)
+endif
+
+if flatpak_support
+ pipewire_module_protocol_pulse_deps += glib2_dep
+endif
+
+pipewire_module_protocol_pulse = shared_library('pipewire-module-protocol-pulse',
+ pipewire_module_protocol_pulse_sources,
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : pipewire_module_protocol_pulse_deps,
+)
+
+build_module_pulse_tunnel = pulseaudio_dep.found()
+if build_module_pulse_tunnel
+ pipewire_module_pulse_tunnel = shared_library('pipewire-module-pulse-tunnel',
+ [ 'module-pulse-tunnel.c',
+ 'module-protocol-pulse/format.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [mathlib, dl_lib, pipewire_dep, pulseaudio_dep],
+)
+endif
+summary({'pulse-tunnel': build_module_pulse_tunnel}, bool_yn: true, section: 'Optional Modules')
+
+pipewire_module_pipe_tunnel = shared_library('pipewire-module-pipe-tunnel',
+ [ 'module-pipe-tunnel.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [mathlib, dl_lib, pipewire_dep],
+)
+
+pipewire_module_protocol_simple = shared_library('pipewire-module-protocol-simple',
+ [ 'module-protocol-simple.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : pipewire_module_protocol_deps,
+)
+
+pipewire_module_example_sink = shared_library('pipewire-module-example-sink',
+ [ 'module-example-sink.c' ],
+ include_directories : [configinc],
+ install : false,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep],
+)
+
+pipewire_module_example_sink = shared_library('pipewire-module-example-source',
+ [ 'module-example-source.c' ],
+ include_directories : [configinc],
+ install : false,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep],
+)
+
+pipewire_module_client_node = shared_library('pipewire-module-client-node',
+ [ 'module-client-node.c',
+ 'module-client-node/remote-node.c',
+ 'module-client-node/client-node.c',
+ 'module-client-node/protocol-native.c',
+ 'module-client-node/v0/client-node.c',
+ 'module-client-node/v0/transport.c',
+ 'module-client-node/v0/protocol-native.c',
+ 'spa/spa-node.c', ],
+ include_directories : [configinc],
+ link_with : pipewire_module_protocol_native,
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep],
+)
+
+pipewire_module_metadata = shared_library('pipewire-module-metadata',
+ [ 'module-metadata.c',
+ 'module-metadata/proxy-metadata.c',
+ 'module-metadata/metadata.c',
+ 'module-metadata/protocol-native.c'],
+ include_directories : [configinc],
+ link_with : pipewire_module_protocol_native,
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [spa_dep, mathlib, dl_lib, pipewire_dep],
+)
+
+test('pw-test-protocol-native',
+ executable('pw-test-protocol-native',
+ [ 'module-protocol-native/test-connection.c',
+ 'module-protocol-native/connection.c' ],
+ c_args : libpipewire_c_args,
+ include_directories : [configinc ],
+ dependencies : [spa_dep, pipewire_dep],
+ install : installed_tests_enabled,
+ install_dir : installed_tests_execdir,
+ ),
+ env : [
+ 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')),
+ 'PIPEWIRE_CONFIG_DIR=@0@'.format(pipewire_dep.get_variable('confdatadir')),
+ 'PIPEWIRE_MODULE_DIR=@0@'.format(pipewire_dep.get_variable('moduledir')),
+ ]
+)
+
+if installed_tests_enabled
+ test_conf = configuration_data()
+ test_conf.set('exec', installed_tests_execdir / 'pw-test-protocol-native')
+ configure_file(
+ input: installed_tests_template,
+ output: 'pw-test-protocol-native.test',
+ install_dir: installed_tests_metadir,
+ configuration: test_conf
+ )
+endif
+
+pipewire_module_adapter = shared_library('pipewire-module-adapter',
+ [ 'module-adapter.c',
+ 'module-adapter/adapter.c',
+ 'spa/spa-node.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep],
+)
+
+pipewire_module_session_manager = shared_library('pipewire-module-session-manager',
+ [ 'module-session-manager.c',
+ 'module-session-manager/client-endpoint/client-endpoint.c',
+ 'module-session-manager/client-endpoint/endpoint-stream.c',
+ 'module-session-manager/client-endpoint/endpoint.c',
+ 'module-session-manager/client-session/client-session.c',
+ 'module-session-manager/client-session/endpoint-link.c',
+ 'module-session-manager/client-session/session.c',
+ 'module-session-manager/endpoint-link.c',
+ 'module-session-manager/endpoint-stream.c',
+ 'module-session-manager/endpoint.c',
+ 'module-session-manager/protocol-native.c',
+ 'module-session-manager/proxy-session-manager.c',
+ 'module-session-manager/session.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_zeroconf_discover = avahi_dep.found()
+if build_module_zeroconf_discover
+pipewire_module_zeroconf_discover = shared_library('pipewire-module-zeroconf-discover',
+ [ 'module-zeroconf-discover.c',
+ 'module-protocol-pulse/format.c',
+ 'module-zeroconf-discover/avahi-poll.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, avahi_dep],
+)
+endif
+summary({'zeroconf-discover': build_module_zeroconf_discover}, bool_yn: true, section: 'Optional Modules')
+
+build_module_raop_discover = avahi_dep.found()
+if build_module_raop_discover
+pipewire_module_raop_discover = shared_library('pipewire-module-raop-discover',
+ [ 'module-raop-discover.c',
+ 'module-zeroconf-discover/avahi-poll.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, avahi_dep],
+)
+endif
+summary({'raop-discover (needs Avahi)': build_module_raop_discover}, bool_yn: true, section: 'Optional Modules')
+
+build_module_raop = openssl_lib.found()
+if build_module_raop
+pipewire_module_raop_sink = shared_library('pipewire-module-raop-sink',
+ [ 'module-raop-sink.c',
+ 'module-raop/rtsp-client.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, openssl_lib],
+)
+endif
+summary({'raop-sink (requires OpenSSL)': build_module_raop}, bool_yn: true, section: 'Optional Modules')
+
+roc_dep = dependency('roc', required: get_option('roc'))
+summary({'ROC': roc_dep.found()}, bool_yn: true, section: 'Streaming between daemons')
+
+pipewire_module_rtp_source = shared_library('pipewire-module-rtp-source',
+ [ 'module-rtp-source.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep],
+)
+
+pipewire_module_rtp_sink = shared_library('pipewire-module-rtp-sink',
+ [ 'module-rtp-sink.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep],
+)
+
+build_module_roc = roc_dep.found()
+if build_module_roc
+pipewire_module_roc_sink = shared_library('pipewire-module-roc-sink',
+ [ 'module-roc-sink.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, roc_dep],
+)
+
+pipewire_module_roc_source = shared_library('pipewire-module-roc-source',
+ [ 'module-roc-source.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, roc_dep],
+)
+endif
+summary({'roc-sink': build_module_roc}, bool_yn: true, section: 'Optional Modules')
+summary({'roc-source': build_module_roc}, bool_yn: true, section: 'Optional Modules')
+
+build_module_x11_bell = x11_dep.found() and canberra_dep.found()
+if build_module_x11_bell
+pipewire_module_x11_bell = shared_library('pipewire-module-x11-bell',
+ [ 'module-x11-bell.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, x11_dep, xfixes_dep, canberra_dep],
+)
+endif
+summary({'x11-bell': build_module_x11_bell}, bool_yn: true, section: 'Optional Modules')
+
+pipewire_module_fallback_sink = shared_library('pipewire-module-fallback-sink',
+ [ 'module-fallback-sink.c' ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep],
+)
+
+build_module_avb = get_option('avb').require(host_machine.system() == 'linux', error_message: 'AVB support is only available on Linux').allowed()
+if build_module_avb
+pipewire_module_avb = shared_library('pipewire-module-avb',
+ [ 'module-avb.c',
+ 'module-avb/avb.c',
+ 'module-avb/adp.c',
+ 'module-avb/acmp.c',
+ 'module-avb/aecp.c',
+ 'module-avb/aecp-aem.c',
+ 'module-avb/avdecc.c',
+ 'module-avb/maap.c',
+ 'module-avb/mmrp.c',
+ 'module-avb/mrp.c',
+ 'module-avb/msrp.c',
+ 'module-avb/mvrp.c',
+ 'module-avb/srp.c',
+ 'module-avb/stream.c'
+ ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep],
+)
+endif
+summary({'avb': build_module_avb}, bool_yn: true, section: 'Optional Modules')
diff --git a/src/modules/module-access.c b/src/modules/module-access.c
new file mode 100644
index 0000000..db12ad7
--- /dev/null
+++ b/src/modules/module-access.c
@@ -0,0 +1,364 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#ifdef HAVE_SYS_VFS_H
+#include <sys/vfs.h>
+#endif
+#ifdef HAVE_SYS_MOUNT_H
+#include <sys/mount.h>
+#endif
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/utils/json.h>
+
+#include <pipewire/impl.h>
+
+#include "flatpak-utils.h"
+
+/** \page page_module_access PipeWire Module: Access
+ *
+ *
+ * The `access` module performs access checks on clients. The access check
+ * is only performed once per client, subsequent checks return the same
+ * resolution.
+ *
+ * Permissions assigned to a client are configured as arguments to this
+ * module, see the example configuration below. A special use-case is Flatpak
+ * where the permission management is delegated.
+ *
+ * This module sets the \ref PW_KEY_ACCESS property to one of
+ * - `allowed`: the client is explicitly allowed to access all resources
+ * - `rejected`: the client does not have access to any resources and a
+ * resource error is generated
+ * - `restricted`: the client is restricted, see note below
+ * - `flatpak`: restricted, special case for clients running inside flatpak,
+ * see note below
+ * - `$access.force`: the value of the `access.force` argument given in the
+ * module configuration.
+ * - `unrestricted`: the client is allowed to access all resources. This is the
+ * default for clients not listed in any of the `access.*` options
+ * unless the client requested reduced permissions in \ref
+ * PW_KEY_CLIENT_ACCESS.
+ *
+ * \note Clients with a resolution other than `allowed` or `rejected` rely
+ * on an external actor to update that property once permission is
+ * granted or rejected.
+ *
+ * For connections from applications running inside Flatpak not mediated
+ * by a portal, the `access` module itself sets the `pipewire.access.portal.app_id`
+ * property to the Flatpak application ID.
+ *
+ * ## Module Options
+ *
+ * Options specific to the behavior of this module
+ *
+ * - ``access.allowed = []``: an array of paths of allowed applications
+ * - ``access.rejected = []``: an array of paths of rejected applications
+ * - ``access.restricted = []``: an array of paths of restricted applications
+ * - ``access.force = <str>``: forces an external permissions check (e.g. a flatpak
+ * portal)
+ *
+ * ## General options
+ *
+ * Options with well-known behavior:
+ *
+ * - \ref PW_KEY_ACCESS
+ * - \ref PW_KEY_CLIENT_ACCESS
+ *
+ * ## Example configuration
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-access
+ * args = {
+ * access.allowed = [
+ * /usr/bin/pipewire-media-session
+ * /usr/bin/important-thing
+ * ]
+ *
+ * access.rejected = [
+ * /usr/bin/microphone-snooper
+ * ]
+ *
+ * #access.restricted = [ ]
+ *
+ * # Anything not in the above lists gets assigned the
+ * # access.force permission.
+ * #access.force = flatpak
+ * }
+ * }
+ *]
+ *\endcode
+ *
+ * \see pw_resource_error
+ * \see pw_impl_client_update_permissions
+ */
+
+#define NAME "access"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define MODULE_USAGE "[ access.force=flatpak ] " \
+ "[ access.allowed=<cmd-line> ] " \
+ "[ access.rejected=<cmd-line> ] " \
+ "[ access.restricted=<cmd-line> ] " \
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Perform access check" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct impl {
+ struct pw_context *context;
+ struct pw_properties *properties;
+
+ struct spa_hook context_listener;
+ struct spa_hook module_listener;
+};
+
+static int check_cmdline(struct pw_impl_client *client, int pid, const char *str)
+{
+ char path[2048], key[1024];
+ ssize_t len;
+ int fd, res;
+ struct spa_json it[2];
+
+ sprintf(path, "/proc/%u/cmdline", pid);
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ res = -errno;
+ goto exit;
+ }
+ if ((len = read(fd, path, sizeof(path)-1)) < 0) {
+ res = -errno;
+ goto exit_close;
+ }
+ path[len] = '\0';
+
+ spa_json_init(&it[0], str, strlen(str));
+ if ((res = spa_json_enter_array(&it[0], &it[1])) <= 0)
+ goto exit_close;
+
+ while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) {
+ if (spa_streq(path, key)) {
+ res = 1;
+ goto exit_close;
+ }
+ }
+ res = 0;
+exit_close:
+ close(fd);
+exit:
+ return res;
+}
+
+static void
+context_check_access(void *data, struct pw_impl_client *client)
+{
+ struct impl *impl = data;
+ struct pw_permission permissions[1];
+ struct spa_dict_item items[2];
+ const struct pw_properties *props;
+ const char *str, *access;
+ char *flatpak_app_id = NULL;
+ int nitems = 0;
+ int pid, res;
+
+ pid = -EINVAL;
+ if ((props = pw_impl_client_get_properties(client)) != NULL) {
+ if ((str = pw_properties_get(props, PW_KEY_ACCESS)) != NULL) {
+ pw_log_info("client %p: has already access: '%s'", client, str);
+ return;
+ }
+ pw_properties_fetch_int32(props, PW_KEY_SEC_PID, &pid);
+ }
+
+ if (pid < 0) {
+ pw_log_info("client %p: no trusted pid found, assuming not sandboxed", client);
+ access = "no-pid";
+ goto granted;
+ } else {
+ pw_log_info("client %p has trusted pid %d", client, pid);
+ }
+
+ if (impl->properties && (str = pw_properties_get(impl->properties, "access.allowed")) != NULL) {
+ res = check_cmdline(client, pid, str);
+ if (res < 0) {
+ pw_log_warn("%p: client %p allowed check failed: %s",
+ impl, client, spa_strerror(res));
+ } else if (res > 0) {
+ access = "allowed";
+ goto granted;
+ }
+ }
+
+ if (impl->properties && (str = pw_properties_get(impl->properties, "access.rejected")) != NULL) {
+ res = check_cmdline(client, pid, str);
+ if (res < 0) {
+ pw_log_warn("%p: client %p rejected check failed: %s",
+ impl, client, spa_strerror(res));
+ } else if (res > 0) {
+ res = -EACCES;
+ access = "rejected";
+ goto rejected;
+ }
+ }
+
+ if (impl->properties && (str = pw_properties_get(impl->properties, "access.restricted")) != NULL) {
+ res = check_cmdline(client, pid, str);
+ if (res < 0) {
+ pw_log_warn("%p: client %p restricted check failed: %s",
+ impl, client, spa_strerror(res));
+ }
+ else if (res > 0) {
+ pw_log_debug(" %p: restricted client %p added", impl, client);
+ access = "restricted";
+ goto wait_permissions;
+ }
+ }
+ if (impl->properties &&
+ (access = pw_properties_get(impl->properties, "access.force")) != NULL)
+ goto wait_permissions;
+
+ res = pw_check_flatpak(pid, &flatpak_app_id, NULL);
+ if (res != 0) {
+ if (res < 0) {
+ if (res == -EACCES) {
+ access = "unrestricted";
+ goto granted;
+ }
+ pw_log_warn("%p: client %p sandbox check failed: %s",
+ impl, client, spa_strerror(res));
+ }
+ else if (res > 0) {
+ pw_log_debug(" %p: flatpak client %p added", impl, client);
+ }
+ access = "flatpak";
+ items[nitems++] = SPA_DICT_ITEM_INIT("pipewire.access.portal.app_id",
+ flatpak_app_id);
+ goto wait_permissions;
+ }
+
+ if ((access = pw_properties_get(props, PW_KEY_CLIENT_ACCESS)) == NULL)
+ access = "unrestricted";
+
+ if (spa_streq(access, "unrestricted") || spa_streq(access, "allowed"))
+ goto granted;
+ else
+ goto wait_permissions;
+
+granted:
+ pw_log_info("%p: client %p '%s' access granted", impl, client, access);
+ items[nitems++] = SPA_DICT_ITEM_INIT(PW_KEY_ACCESS, access);
+ pw_impl_client_update_properties(client, &SPA_DICT_INIT(items, nitems));
+
+ permissions[0] = PW_PERMISSION_INIT(PW_ID_ANY, PW_PERM_ALL);
+ pw_impl_client_update_permissions(client, 1, permissions);
+ goto done;
+
+wait_permissions:
+ pw_log_info("%p: client %p wait for '%s' permissions",
+ impl, client, access);
+ items[nitems++] = SPA_DICT_ITEM_INIT(PW_KEY_ACCESS, access);
+ pw_impl_client_update_properties(client, &SPA_DICT_INIT(items, nitems));
+ goto done;
+
+rejected:
+ pw_resource_error(pw_impl_client_get_core_resource(client), res, access);
+ items[nitems++] = SPA_DICT_ITEM_INIT(PW_KEY_ACCESS, access);
+ pw_impl_client_update_properties(client, &SPA_DICT_INIT(items, nitems));
+ goto done;
+
+done:
+ free(flatpak_app_id);
+ return;
+}
+
+static const struct pw_context_events context_events = {
+ PW_VERSION_CONTEXT_EVENTS,
+ .check_access = context_check_access,
+};
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+
+ spa_hook_remove(&impl->context_listener);
+ spa_hook_remove(&impl->module_listener);
+
+ pw_properties_free(impl->properties);
+
+ free(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props;
+ struct impl *impl;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args)
+ props = pw_properties_new_string(args);
+ else
+ props = NULL;
+
+ impl->context = context;
+ impl->properties = props;
+
+ pw_context_add_listener(context, &impl->context_listener, &context_events, impl);
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+}
diff --git a/src/modules/module-adapter.c b/src/modules/module-adapter.c
new file mode 100644
index 0000000..edc183b
--- /dev/null
+++ b/src/modules/module-adapter.c
@@ -0,0 +1,394 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "config.h"
+
+#include <spa/node/node.h>
+#include <spa/utils/hook.h>
+#include <spa/utils/result.h>
+
+#include <pipewire/impl.h>
+
+#include "modules/spa/spa-node.h"
+#include "module-adapter/adapter.h"
+
+/** \page page_module_adapter PipeWire Module: Adapter
+ */
+#define NAME "adapter"
+
+PW_LOG_TOPIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define FACTORY_USAGE SPA_KEY_FACTORY_NAME"=<factory-name> " \
+ "["SPA_KEY_LIBRARY_NAME"=<library-name>] " \
+ ADAPTER_USAGE
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Manage adapter nodes" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct factory_data {
+ struct pw_impl_factory *factory;
+ struct spa_hook factory_listener;
+
+ struct spa_list node_list;
+
+ struct pw_context *context;
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+};
+
+struct node_data {
+ struct factory_data *data;
+ struct spa_list link;
+ struct pw_impl_node *adapter;
+ struct pw_impl_node *follower;
+ struct spa_handle *handle;
+ struct spa_hook adapter_listener;
+ struct pw_resource *resource;
+ struct pw_resource *bound_resource;
+ struct spa_hook resource_listener;
+ uint32_t new_id;
+ unsigned int linger;
+};
+
+static void resource_destroy(void *data)
+{
+ struct node_data *nd = data;
+
+ pw_log_debug("%p: destroy %p", nd, nd->adapter);
+ spa_hook_remove(&nd->resource_listener);
+ nd->bound_resource = NULL;
+ if (nd->adapter && !nd->linger)
+ pw_impl_node_destroy(nd->adapter);
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = resource_destroy
+};
+
+static void node_destroy(void *data)
+{
+ struct node_data *nd = data;
+ pw_log_debug("%p: destroy %p", nd, nd->adapter);
+ spa_list_remove(&nd->link);
+ nd->adapter = NULL;
+}
+
+static void node_free(void *data)
+{
+ struct node_data *nd = data;
+
+ pw_log_debug("%p: free %p", nd, nd->follower);
+
+ if (nd->bound_resource != NULL)
+ spa_hook_remove(&nd->resource_listener);
+
+ spa_hook_remove(&nd->adapter_listener);
+
+ if (nd->follower)
+ pw_impl_node_destroy(nd->follower);
+ if (nd->handle)
+ pw_unload_spa_handle(nd->handle);
+}
+
+static void node_initialized(void *data)
+{
+ struct node_data *nd = data;
+ struct pw_impl_client *client;
+ struct pw_resource *bound_resource;
+ struct pw_global *global;
+ int res;
+
+ if (nd->resource == NULL)
+ return;
+
+ client = pw_resource_get_client(nd->resource);
+ global = pw_impl_node_get_global(nd->adapter);
+
+ res = pw_global_bind(global, client,
+ PW_PERM_ALL, PW_VERSION_NODE, nd->new_id);
+ if (res < 0)
+ goto error_bind;
+
+ if ((bound_resource = pw_impl_client_find_resource(client, nd->new_id)) == NULL) {
+ res = -EIO;
+ goto error_bind;
+ }
+
+ nd->bound_resource = bound_resource;
+ pw_resource_add_listener(bound_resource, &nd->resource_listener, &resource_events, nd);
+ return;
+
+error_bind:
+ pw_resource_errorf_id(nd->resource, nd->new_id, res,
+ "can't bind adapter node: %s", spa_strerror(res));
+ return;
+}
+
+
+static const struct pw_impl_node_events node_events = {
+ PW_VERSION_IMPL_NODE_EVENTS,
+ .destroy = node_destroy,
+ .free = node_free,
+ .initialized = node_initialized,
+};
+
+static void *create_object(void *_data,
+ struct pw_resource *resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id)
+{
+ struct factory_data *d = _data;
+ struct pw_impl_client *client;
+ struct pw_impl_node *adapter, *follower;
+ struct spa_node *spa_follower;
+ const char *str;
+ int res;
+ struct node_data *nd;
+ bool linger, do_register;
+ struct spa_handle *handle = NULL;
+ const struct pw_properties *p;
+
+ if (properties == NULL)
+ goto error_properties;
+
+ pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d",
+ pw_impl_factory_get_info(d->factory)->id);
+
+ linger = pw_properties_get_bool(properties, PW_KEY_OBJECT_LINGER, false);
+ do_register = pw_properties_get_bool(properties, PW_KEY_OBJECT_REGISTER, true);
+
+ p = pw_context_get_properties(d->context);
+ pw_properties_set(properties, "clock.quantum-limit",
+ pw_properties_get(p, "default.clock.quantum-limit"));
+
+ client = resource ? pw_resource_get_client(resource): NULL;
+ if (client && !linger) {
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d",
+ pw_impl_client_get_info(client)->id);
+ }
+
+ follower = NULL;
+ spa_follower = NULL;
+ str = pw_properties_get(properties, "adapt.follower.node");
+ if (str != NULL) {
+ if (sscanf(str, "pointer:%p", &follower) != 1)
+ goto error_properties;
+ spa_follower = pw_impl_node_get_implementation(follower);
+ }
+ str = pw_properties_get(properties, "adapt.follower.spa-node");
+ if (str != NULL) {
+ if (sscanf(str, "pointer:%p", &spa_follower) != 1)
+ goto error_properties;
+ }
+ if (spa_follower == NULL) {
+ void *iface;
+ const char *factory_name;
+
+ factory_name = pw_properties_get(properties, SPA_KEY_FACTORY_NAME);
+ if (factory_name == NULL)
+ goto error_properties;
+
+ handle = pw_context_load_spa_handle(d->context,
+ factory_name,
+ properties ? &properties->dict : NULL);
+ if (handle == NULL)
+ goto error_errno;
+
+ if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Node, &iface)) < 0)
+ goto error_res;
+
+ spa_follower = iface;
+ }
+ if (spa_follower == NULL) {
+ res = -EINVAL;
+ goto error_res;
+ }
+
+ adapter = pw_adapter_new(pw_impl_module_get_context(d->module),
+ spa_follower,
+ properties,
+ sizeof(struct node_data));
+ properties = NULL;
+
+ if (adapter == NULL) {
+ if (errno == ENOMEM || errno == EBUSY)
+ goto error_errno;
+ else
+ goto error_usage;
+ }
+
+ nd = pw_adapter_get_user_data(adapter);
+ nd->data = d;
+ nd->adapter = adapter;
+ nd->follower = follower;
+ nd->handle = handle;
+ nd->resource = resource;
+ nd->new_id = new_id;
+ nd->linger = linger;
+ spa_list_append(&d->node_list, &nd->link);
+
+ pw_impl_node_add_listener(adapter, &nd->adapter_listener, &node_events, nd);
+
+ if (do_register)
+ pw_impl_node_register(adapter, NULL);
+ else
+ pw_impl_node_initialized(adapter);
+
+ return adapter;
+
+error_properties:
+ res = -EINVAL;
+ pw_resource_errorf_id(resource, new_id, res, "usage: " FACTORY_USAGE);
+ goto error_cleanup;
+error_errno:
+ res = -errno;
+error_res:
+ pw_resource_errorf_id(resource, new_id, res, "can't create node: %s", spa_strerror(res));
+ goto error_cleanup;
+error_usage:
+ res = -EINVAL;
+ pw_log_error("usage: "ADAPTER_USAGE);
+ pw_resource_errorf_id(resource, new_id, res, "usage: "ADAPTER_USAGE);
+ goto error_cleanup;
+error_cleanup:
+ pw_properties_free(properties);
+ if (handle)
+ pw_unload_spa_handle(handle);
+ errno = -res;
+ return NULL;
+}
+
+static const struct pw_impl_factory_implementation impl_factory = {
+ PW_VERSION_IMPL_FACTORY_IMPLEMENTATION,
+ .create_object = create_object,
+};
+
+static void factory_destroy(void *data)
+{
+ struct factory_data *d = data;
+ struct node_data *nd;
+
+ spa_hook_remove(&d->factory_listener);
+
+ spa_list_consume(nd, &d->node_list, link)
+ pw_impl_node_destroy(nd->adapter);
+
+ d->factory = NULL;
+ if (d->module)
+ pw_impl_module_destroy(d->module);
+}
+
+static const struct pw_impl_factory_events factory_events = {
+ PW_VERSION_IMPL_FACTORY_EVENTS,
+ .destroy = factory_destroy,
+};
+
+static void module_destroy(void *data)
+{
+ struct factory_data *d = data;
+
+ pw_log_debug("%p: destroy", d);
+ spa_hook_remove(&d->module_listener);
+ d->module = NULL;
+
+ if (d->factory)
+ pw_impl_factory_destroy(d->factory);
+}
+
+static void module_registered(void *data)
+{
+ struct factory_data *d = data;
+ struct pw_impl_module *module = d->module;
+ struct pw_impl_factory *factory = d->factory;
+ struct spa_dict_item items[1];
+ char id[16];
+ int res;
+
+ snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id);
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
+ pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
+
+ if ((res = pw_impl_factory_register(factory, NULL)) < 0) {
+ pw_log_error("%p: can't register factory: %s", factory, spa_strerror(res));
+ }
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+ .registered = module_registered,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_factory *factory;
+ struct factory_data *data;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ factory = pw_context_create_factory(context,
+ "adapter",
+ PW_TYPE_INTERFACE_Node,
+ PW_VERSION_NODE,
+ pw_properties_new(
+ PW_KEY_FACTORY_USAGE, FACTORY_USAGE,
+ NULL),
+ sizeof(*data));
+ if (factory == NULL)
+ return -errno;
+
+ data = pw_impl_factory_get_user_data(factory);
+ data->factory = factory;
+ data->context = context;
+ data->module = module;
+ spa_list_init(&data->node_list);
+
+ pw_log_debug("module %p: new", module);
+
+ pw_impl_factory_add_listener(factory, &data->factory_listener,
+ &factory_events, data);
+ pw_impl_factory_set_implementation(factory,
+ &impl_factory,
+ data);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ return 0;
+}
diff --git a/src/modules/module-adapter/adapter.c b/src/modules/module-adapter/adapter.c
new file mode 100644
index 0000000..19c5713
--- /dev/null
+++ b/src/modules/module-adapter/adapter.c
@@ -0,0 +1,548 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <math.h>
+#include <time.h>
+
+#include "config.h"
+
+#include <spa/node/node.h>
+#include <spa/node/utils.h>
+#include <spa/utils/hook.h>
+#include <spa/utils/result.h>
+#include <spa/utils/names.h>
+#include <spa/utils/string.h>
+#include <spa/utils/type-info.h>
+#include <spa/param/format.h>
+#include <spa/param/audio/format.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/format-utils.h>
+#include <spa/debug/types.h>
+#include <spa/utils/json-pod.h>
+
+#include "pipewire/pipewire.h"
+
+#include "modules/spa/spa-node.h"
+
+#define NAME "adapter"
+
+PW_LOG_TOPIC_EXTERN(mod_topic);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct buffer {
+ struct spa_buffer buf;
+ struct spa_data datas[1];
+ struct spa_chunk chunk[1];
+};
+
+struct node {
+ struct pw_context *context;
+
+ struct pw_impl_node *node;
+ struct spa_hook node_listener;
+
+ struct spa_node *follower;
+
+ void *user_data;
+ enum pw_direction direction;
+ struct pw_properties *props;
+
+ uint32_t media_type;
+ uint32_t media_subtype;
+
+ struct spa_list ports;
+};
+
+/** \endcond */
+static void node_free(void *data)
+{
+ struct node *n = data;
+ spa_hook_remove(&n->node_listener);
+ pw_properties_free(n->props);
+}
+
+static void node_port_init(void *data, struct pw_impl_port *port)
+{
+ struct node *n = data;
+ const struct pw_properties *old;
+ enum pw_direction direction;
+ struct pw_properties *new;
+ const char *str, *path, *desc, *nick, *name, *node_name, *media_class, *prop_port_names, *override_device_prefix;
+ char position[8], *prefix;
+ bool is_monitor, is_device, is_duplex, is_virtual, is_control = false, no_device_port_prefix;
+
+ direction = pw_impl_port_get_direction(port);
+
+ old = pw_impl_port_get_properties(port);
+
+ is_monitor = pw_properties_get_bool(old, PW_KEY_PORT_MONITOR, false);
+ if (!is_monitor && direction != n->direction)
+ return;
+
+ if ((str = pw_properties_get(old, PW_KEY_FORMAT_DSP)) != NULL)
+ is_control = spa_streq(str, "8 bit raw midi");
+
+ path = pw_properties_get(n->props, PW_KEY_OBJECT_PATH);
+ media_class = pw_properties_get(n->props, PW_KEY_MEDIA_CLASS);
+
+ if (media_class != NULL &&
+ (strstr(media_class, "Sink") != NULL ||
+ strstr(media_class, "Source") != NULL))
+ is_device = true;
+ else
+ is_device = false;
+
+ is_duplex = media_class != NULL && strstr(media_class, "Duplex") != NULL;
+ is_virtual = media_class != NULL && strstr(media_class, "Virtual") != NULL;
+
+ new = pw_properties_new(NULL, NULL);
+
+ override_device_prefix = pw_properties_get(n->props, PW_KEY_NODE_DEVICE_PORT_NAME_PREFIX);
+
+ if (is_control)
+ prefix = direction == PW_DIRECTION_INPUT ?
+ "control" : "notify";
+ else if (is_duplex)
+ prefix = direction == PW_DIRECTION_INPUT ?
+ "playback" : "capture";
+ else if (is_virtual)
+ prefix = direction == PW_DIRECTION_INPUT ?
+ "input" : "capture";
+ else if (is_device)
+ prefix = direction == PW_DIRECTION_INPUT ?
+ override_device_prefix != NULL ?
+ strdup(override_device_prefix) : "playback"
+ : is_monitor ? "monitor" : override_device_prefix != NULL ?
+ strdup(override_device_prefix) : "capture";
+ else
+ prefix = direction == PW_DIRECTION_INPUT ?
+ "input" : is_monitor ? "monitor" : "output";
+
+ if ((str = pw_properties_get(old, PW_KEY_AUDIO_CHANNEL)) == NULL ||
+ spa_streq(str, "UNK")) {
+ snprintf(position, sizeof(position), "%d", pw_impl_port_get_id(port) + 1);
+ str = position;
+ }
+ if (direction == n->direction) {
+ if (is_device) {
+ pw_properties_set(new, PW_KEY_PORT_PHYSICAL, "true");
+ pw_properties_set(new, PW_KEY_PORT_TERMINAL, "true");
+ }
+ }
+
+ desc = pw_properties_get(n->props, PW_KEY_NODE_DESCRIPTION);
+ nick = pw_properties_get(n->props, PW_KEY_NODE_NICK);
+ name = pw_properties_get(n->props, PW_KEY_NODE_NAME);
+
+ if ((node_name = desc) == NULL && (node_name = nick) == NULL &&
+ (node_name = name) == NULL)
+ node_name = "node";
+
+ pw_properties_setf(new, PW_KEY_OBJECT_PATH, "%s:%s_%d",
+ path ? path : node_name, prefix, pw_impl_port_get_id(port));
+
+ no_device_port_prefix = is_device && !is_monitor
+ && override_device_prefix != NULL && strlen(override_device_prefix) == 0;
+
+ if (is_control)
+ pw_properties_setf(new, PW_KEY_PORT_NAME, "%s", prefix);
+ else if (no_device_port_prefix)
+ pw_properties_setf(new, PW_KEY_PORT_NAME, "%s", str);
+ else
+ pw_properties_setf(new, PW_KEY_PORT_NAME, "%s_%s", prefix, str);
+
+ if ((node_name = nick) == NULL && (node_name = desc) == NULL &&
+ (node_name = name) == NULL)
+ node_name = "node";
+
+ if (is_control)
+ pw_properties_setf(new, PW_KEY_PORT_ALIAS, "%s:%s",
+ node_name, prefix);
+ else
+ pw_properties_setf(new, PW_KEY_PORT_ALIAS, "%s:%s_%s",
+ node_name, prefix, str);
+
+ prop_port_names = pw_properties_get(n->props, PW_KEY_NODE_CHANNELNAMES);
+ if (prop_port_names) {
+ struct spa_json it[2];
+ char v[256];
+
+ spa_json_init(&it[0], prop_port_names, strlen(prop_port_names));
+ if (spa_json_enter_array(&it[0], &it[1]) <= 0)
+ spa_json_init(&it[1], prop_port_names, strlen(prop_port_names));
+
+ uint32_t i;
+ for (i = 0; i < pw_impl_port_get_id(port) + 1; i++)
+ if (spa_json_get_string(&it[1], v, sizeof(v)) <= 0)
+ break;
+
+ if (i == pw_impl_port_get_id(port) + 1 && strlen(v) > 0) {
+ if (no_device_port_prefix) {
+ pw_properties_setf(new, PW_KEY_PORT_NAME, "%s", v);
+ } else {
+ pw_properties_setf(new, PW_KEY_PORT_NAME, "%s_%s", prefix, v);
+ }
+ }
+ }
+
+ pw_impl_port_update_properties(port, &new->dict);
+ pw_properties_free(new);
+}
+
+static const struct pw_impl_node_events node_events = {
+ PW_VERSION_IMPL_NODE_EVENTS,
+ .free = node_free,
+ .port_init = node_port_init,
+};
+
+static int handle_node_param(struct pw_impl_node *node, const char *key, const char *value)
+{
+ const struct spa_type_info *ti;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ struct spa_pod *pod;
+ int res;
+
+ ti = spa_debug_type_find_short(spa_type_param, key);
+ if (ti == NULL)
+ return -ENOENT;
+
+ if ((res = spa_json_to_pod(&b, 0, ti, value, strlen(value))) < 0)
+ return res;
+
+ if ((pod = spa_pod_builder_deref(&b, 0)) == NULL)
+ return -ENOSPC;
+
+ if ((res = pw_impl_node_set_param(node, ti->type, 0, pod)) < 0)
+ return res;
+
+ return 0;
+}
+
+static int find_format(struct spa_node *node, enum pw_direction direction,
+ uint32_t *media_type, uint32_t *media_subtype)
+{
+ uint32_t state = 0;
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+ int res;
+ struct spa_pod *format;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ if ((res = spa_node_port_enum_params_sync(node,
+ direction == PW_DIRECTION_INPUT ?
+ SPA_DIRECTION_INPUT :
+ SPA_DIRECTION_OUTPUT, 0,
+ SPA_PARAM_EnumFormat, &state,
+ NULL, &format, &b)) != 1) {
+ res = res < 0 ? res : -ENOENT;
+ pw_log_warn("%p: can't get format: %s", node, spa_strerror(res));
+ return res;
+ }
+
+ if ((res = spa_format_parse(format, media_type, media_subtype)) < 0)
+ return res;
+
+ pw_log_debug("%p: %s/%s", node,
+ spa_debug_type_find_name(spa_type_media_type, *media_type),
+ spa_debug_type_find_name(spa_type_media_subtype, *media_subtype));
+ return 0;
+}
+
+static int do_auto_port_config(struct node *n, const char *str)
+{
+ uint32_t state = 0, i;
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+#define POSITION_PRESERVE 0
+#define POSITION_AUX 1
+#define POSITION_UNKNOWN 2
+ int 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];
+ char key[1024], val[256];
+
+ spa_json_init(&it[0], str, strlen(str));
+ if (spa_json_enter_object(&it[0], &it[1]) <= 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;
+
+ if (spa_streq(key, "mode")) {
+ mode = spa_debug_type_find_type_short(spa_type_param_port_config_mode, val);
+ if (mode == SPA_ID_INVALID)
+ mode = SPA_PARAM_PORT_CONFIG_MODE_none;
+ } else if (spa_streq(key, "monitor")) {
+ monitor = spa_atob(val);
+ } else if (spa_streq(key, "control")) {
+ control = spa_atob(val);
+ } else if (spa_streq(key, "position")) {
+ if (spa_streq(val, "unknown"))
+ position = POSITION_UNKNOWN;
+ else if (spa_streq(val, "aux"))
+ position = POSITION_AUX;
+ else
+ position = POSITION_PRESERVE;
+ }
+ }
+
+ 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(n->follower,
+ n->direction == PW_DIRECTION_INPUT ?
+ SPA_DIRECTION_INPUT :
+ SPA_DIRECTION_OUTPUT, 0,
+ SPA_PARAM_EnumFormat, &state,
+ NULL, &param, &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)
+ 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)
+ continue;
+
+ format = info;
+ have_format = true;
+ }
+ 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;
+ }
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ param = spa_format_audio_raw_build(&b, SPA_PARAM_Format, &format.info.raw);
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig,
+ SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(n->direction),
+ SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(mode),
+ SPA_PARAM_PORT_CONFIG_monitor, SPA_POD_Bool(monitor),
+ SPA_PARAM_PORT_CONFIG_control, SPA_POD_Bool(control),
+ SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param));
+ pw_impl_node_set_param(n->node, SPA_PARAM_PortConfig, 0, param);
+
+ return 0;
+}
+
+struct info_data {
+ struct spa_hook listener;
+ struct spa_node *node;
+ struct pw_properties *props;
+ uint32_t n_input_ports;
+ uint32_t max_input_ports;
+ uint32_t n_output_ports;
+ uint32_t max_output_ports;
+};
+
+static void info_event(void *data, const struct spa_node_info *info)
+{
+ struct info_data *d = data;
+
+ pw_properties_update(d->props, info->props);
+
+ d->max_input_ports = info->max_input_ports;
+ d->max_output_ports = info->max_output_ports;
+}
+
+static void port_info_event(void *data, enum spa_direction direction, uint32_t port,
+ const struct spa_port_info *info)
+{
+ struct info_data *d = data;
+
+ if (direction == SPA_DIRECTION_OUTPUT)
+ d->n_output_ports++;
+ else if (direction == SPA_DIRECTION_INPUT)
+ d->n_input_ports++;
+}
+
+static const struct spa_node_events node_info_events = {
+ .version = SPA_VERSION_NODE_EVENTS,
+ .info = info_event,
+ .port_info = port_info_event,
+};
+
+struct pw_impl_node *pw_adapter_new(struct pw_context *context,
+ struct spa_node *follower,
+ struct pw_properties *props,
+ size_t user_data_size)
+{
+ struct pw_impl_node *node;
+ struct node *n;
+ const char *str, *factory_name;
+ enum pw_direction direction;
+ int res;
+ uint32_t media_type, media_subtype;
+ const struct spa_dict_item *it;
+ struct pw_properties *copy;
+ struct info_data info;
+
+ spa_zero(info);
+ info.node = follower;
+ info.props = props;
+
+ res = spa_node_add_listener(info.node, &info.listener, &node_info_events, &info);
+ if (res < 0)
+ goto error;
+
+ spa_hook_remove(&info.listener);
+
+ pw_log_debug("%p: in %d/%d out %d/%d", info.node,
+ info.n_input_ports, info.max_input_ports,
+ info.n_output_ports, info.max_output_ports);
+
+ if (info.n_output_ports > 0) {
+ direction = PW_DIRECTION_OUTPUT;
+ } else if (info.n_input_ports > 0) {
+ direction = PW_DIRECTION_INPUT;
+ } else {
+ res = -EINVAL;
+ goto error;
+ }
+
+ if ((str = pw_properties_get(props, PW_KEY_NODE_ID)) != NULL)
+ pw_properties_set(props, PW_KEY_NODE_SESSION, str);
+
+ if (pw_properties_get(props, "factory.mode") == NULL) {
+ if (direction == PW_DIRECTION_INPUT)
+ str = "merge";
+ else
+ str = "split";
+ pw_properties_set(props, "factory.mode", str);
+ }
+
+ if ((res = find_format(follower, direction, &media_type, &media_subtype)) < 0)
+ goto error;
+
+ if (media_type == SPA_MEDIA_TYPE_audio) {
+ pw_properties_setf(props, "audio.adapt.follower", "pointer:%p", follower);
+ pw_properties_set(props, SPA_KEY_LIBRARY_NAME, "audioconvert/libspa-audioconvert");
+ if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL)
+ pw_properties_setf(props, PW_KEY_MEDIA_CLASS, "Audio/%s",
+ direction == PW_DIRECTION_INPUT ? "Sink" : "Source");
+ factory_name = SPA_NAME_AUDIO_ADAPT;
+ }
+ else if (media_type == SPA_MEDIA_TYPE_video) {
+ pw_properties_setf(props, "video.adapt.follower", "pointer:%p", follower);
+ pw_properties_set(props, SPA_KEY_LIBRARY_NAME, "videoconvert/libspa-videoconvert");
+ if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL)
+ pw_properties_setf(props, PW_KEY_MEDIA_CLASS, "Video/%s",
+ direction == PW_DIRECTION_INPUT ? "Sink" : "Source");
+ factory_name = SPA_NAME_VIDEO_ADAPT;
+ } else {
+ res = -ENOTSUP;
+ goto error;
+ }
+
+ copy = pw_properties_new(NULL, NULL);
+ spa_dict_for_each(it, &props->dict) {
+ if (!spa_strstartswith(it->key, "node.param.") &&
+ !spa_strstartswith(it->key, "port.param."))
+ pw_properties_set(copy, it->key, it->value);
+ }
+ node = pw_spa_node_load(context,
+ factory_name,
+ PW_SPA_NODE_FLAG_ACTIVATE | PW_SPA_NODE_FLAG_NO_REGISTER,
+ copy, sizeof(struct node) + user_data_size);
+ if (node == NULL) {
+ res = -errno;
+ pw_log_error("can't load spa node: %m");
+ goto error;
+ }
+
+ n = pw_spa_node_get_user_data(node);
+ n->context = context;
+ n->node = node;
+ n->follower = follower;
+ n->direction = direction;
+ n->props = props;
+ n->media_type = media_type;
+ n->media_subtype = media_subtype;
+ spa_list_init(&n->ports);
+
+ if (user_data_size > 0)
+ n->user_data = SPA_PTROFF(n, sizeof(struct node), void);
+
+ pw_impl_node_add_listener(node, &n->node_listener, &node_events, n);
+
+ if ((str = pw_properties_get(props, "adapter.auto-port-config")) != NULL)
+ do_auto_port_config(n, str);
+
+ spa_dict_for_each(it, &props->dict) {
+ if (spa_strstartswith(it->key, "node.param.")) {
+ if ((res = handle_node_param(node, &it->key[11], it->value)) < 0)
+ pw_log_warn("can't set node param: %s", spa_strerror(res));
+ }
+ }
+ return node;
+
+error:
+ pw_properties_free(props);
+ errno = -res;
+ return NULL;
+}
+
+void *pw_adapter_get_user_data(struct pw_impl_node *node)
+{
+ struct node *n = pw_spa_node_get_user_data(node);
+ return n->user_data;
+}
diff --git a/src/modules/module-adapter/adapter.h b/src/modules/module-adapter/adapter.h
new file mode 100644
index 0000000..80fa9c7
--- /dev/null
+++ b/src/modules/module-adapter/adapter.h
@@ -0,0 +1,48 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_ADAPTER_H
+#define PIPEWIRE_ADAPTER_H
+
+#include <pipewire/impl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ADAPTER_USAGE PW_KEY_NODE_NAME"=<string> "
+
+struct pw_impl_node *
+pw_adapter_new(struct pw_context *context,
+ struct spa_node *follower,
+ struct pw_properties *properties,
+ size_t user_data_size);
+
+void *pw_adapter_get_user_data(struct pw_impl_node *node);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_ADAPTER_H */
diff --git a/src/modules/module-avb.c b/src/modules/module-avb.c
new file mode 100644
index 0000000..2359c2d
--- /dev/null
+++ b/src/modules/module-avb.c
@@ -0,0 +1,129 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/utils/json.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/i18n.h>
+
+#include "module-avb/avb.h"
+
+/** \page page_module_avb PipeWire Module: AVB
+ */
+
+#define NAME "avb"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define MODULE_USAGE " "
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Manage an AVB network" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+
+struct impl {
+ struct pw_context *context;
+
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_avb *avb;
+};
+
+static void impl_free(struct impl *impl)
+{
+ free(impl);
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->module_listener);
+ impl_free(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props;
+ struct impl *impl;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ goto error_errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args == NULL)
+ args = "";
+
+ props = pw_properties_new_string(args);
+ if (props == NULL)
+ goto error_errno;
+
+ impl->module = module;
+ impl->context = context;
+
+ impl->avb = pw_avb_new(context, props, 0);
+ if (impl->avb == NULL)
+ goto error_errno;
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+
+error_errno:
+ res = -errno;
+ if (impl)
+ impl_free(impl);
+ return res;
+}
diff --git a/src/modules/module-avb/aaf.h b/src/modules/module-avb/aaf.h
new file mode 100644
index 0000000..b444ce2
--- /dev/null
+++ b/src/modules/module-avb/aaf.h
@@ -0,0 +1,102 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_AAF_H
+#define AVB_AAF_H
+
+struct avb_packet_aaf {
+ uint8_t subtype;
+#if __BYTE_ORDER == __BIG_ENDIAN
+ unsigned sv:1;
+ unsigned version:3;
+ unsigned mr:1;
+ unsigned _r1:1;
+ unsigned gv:1;
+ unsigned tv:1;
+
+ uint8_t seq_num;
+
+ unsigned _r2:7;
+ unsigned tu:1;
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+ unsigned tv:1;
+ unsigned gv:1;
+ unsigned _r1:1;
+ unsigned mr:1;
+ unsigned version:3;
+ unsigned sv:1;
+
+ uint8_t seq_num;
+
+ unsigned tu:1;
+ unsigned _r2:7;
+#endif
+ uint64_t stream_id;
+ uint32_t timestamp;
+#define AVB_AAF_FORMAT_USER 0x00
+#define AVB_AAF_FORMAT_FLOAT_32BIT 0x01
+#define AVB_AAF_FORMAT_INT_32BIT 0x02
+#define AVB_AAF_FORMAT_INT_24BIT 0x03
+#define AVB_AAF_FORMAT_INT_16BIT 0x04
+#define AVB_AAF_FORMAT_AES3_32BIT 0x05
+ uint8_t format;
+
+#define AVB_AAF_PCM_NSR_USER 0x00
+#define AVB_AAF_PCM_NSR_8KHZ 0x01
+#define AVB_AAF_PCM_NSR_16KHZ 0x02
+#define AVB_AAF_PCM_NSR_32KHZ 0x03
+#define AVB_AAF_PCM_NSR_44_1KHZ 0x04
+#define AVB_AAF_PCM_NSR_48KHZ 0x05
+#define AVB_AAF_PCM_NSR_88_2KHZ 0x06
+#define AVB_AAF_PCM_NSR_96KHZ 0x07
+#define AVB_AAF_PCM_NSR_176_4KHZ 0x08
+#define AVB_AAF_PCM_NSR_192KHZ 0x09
+#define AVB_AAF_PCM_NSR_24KHZ 0x0A
+#if __BYTE_ORDER == __BIG_ENDIAN
+ unsigned nsr:4;
+ unsigned _r3:4;
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+ unsigned _r3:4;
+ unsigned nsr:4;
+#endif
+ uint8_t chan_per_frame;
+ uint8_t bit_depth;
+ uint16_t data_len;
+
+#define AVB_AAF_PCM_SP_NORMAL 0x00
+#define AVB_AAF_PCM_SP_SPARSE 0x01
+#if __BYTE_ORDER == __BIG_ENDIAN
+ unsigned _r4:3;
+ unsigned sp:1;
+ unsigned event:4;
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+ unsigned event:4;
+ unsigned sp:1;
+ unsigned _r4:3;
+#endif
+ uint8_t _r5;
+ uint8_t payload[0];
+} __attribute__ ((__packed__));
+
+#endif /* AVB_AAF_H */
diff --git a/src/modules/module-avb/acmp.c b/src/modules/module-avb/acmp.c
new file mode 100644
index 0000000..a7a409a
--- /dev/null
+++ b/src/modules/module-avb/acmp.c
@@ -0,0 +1,476 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/json.h>
+#include <spa/debug/mem.h>
+
+#include <pipewire/pipewire.h>
+
+#include "acmp.h"
+#include "msrp.h"
+#include "internal.h"
+#include "stream.h"
+
+static const uint8_t mac[6] = AVB_BROADCAST_MAC;
+
+struct pending {
+ struct spa_list link;
+ uint64_t last_time;
+ uint64_t timeout;
+ uint16_t old_sequence_id;
+ uint16_t sequence_id;
+ uint16_t retry;
+ size_t size;
+ void *ptr;
+};
+
+struct acmp {
+ struct server *server;
+ struct spa_hook server_listener;
+
+#define PENDING_TALKER 0
+#define PENDING_LISTENER 1
+#define PENDING_CONTROLLER 2
+ struct spa_list pending[3];
+ uint16_t sequence_id[3];
+};
+
+static void *pending_new(struct acmp *acmp, uint32_t type, uint64_t now, uint32_t timeout_ms,
+ const void *m, size_t size)
+{
+ struct pending *p;
+ struct avb_ethernet_header *h;
+ struct avb_packet_acmp *pm;
+
+ p = calloc(1, sizeof(*p) + size);
+ if (p == NULL)
+ return NULL;
+ p->last_time = now;
+ p->timeout = timeout_ms * SPA_NSEC_PER_MSEC;
+ p->sequence_id = acmp->sequence_id[type]++;
+ p->size = size;
+ p->ptr = SPA_PTROFF(p, sizeof(*p), void);
+ memcpy(p->ptr, m, size);
+
+ h = p->ptr;
+ pm = SPA_PTROFF(h, sizeof(*h), void);
+ p->old_sequence_id = ntohs(pm->sequence_id);
+ pm->sequence_id = htons(p->sequence_id);
+ spa_list_append(&acmp->pending[type], &p->link);
+
+ return p->ptr;
+}
+
+static struct pending *pending_find(struct acmp *acmp, uint32_t type, uint16_t sequence_id)
+{
+ struct pending *p;
+ spa_list_for_each(p, &acmp->pending[type], link)
+ if (p->sequence_id == sequence_id)
+ return p;
+ return NULL;
+}
+
+static void pending_free(struct acmp *acmp, struct pending *p)
+{
+ spa_list_remove(&p->link);
+ free(p);
+}
+
+struct msg_info {
+ uint16_t type;
+ const char *name;
+ int (*handle) (struct acmp *acmp, uint64_t now, const void *m, int len);
+};
+
+static int reply_not_supported(struct acmp *acmp, uint8_t type, const void *m, int len)
+{
+ struct server *server = acmp->server;
+ uint8_t buf[len];
+ struct avb_ethernet_header *h = (void*)buf;
+ struct avb_packet_acmp *reply = SPA_PTROFF(h, sizeof(*h), void);
+
+ memcpy(h, m, len);
+ AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, type);
+ AVB_PACKET_ACMP_SET_STATUS(reply, AVB_ACMP_STATUS_NOT_SUPPORTED);
+
+ return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len);
+}
+
+static int retry_pending(struct acmp *acmp, uint64_t now, struct pending *p)
+{
+ struct server *server = acmp->server;
+ struct avb_ethernet_header *h = p->ptr;
+ p->retry++;
+ p->last_time = now;
+ return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, p->ptr, p->size);
+}
+
+static int handle_connect_tx_command(struct acmp *acmp, uint64_t now, const void *m, int len)
+{
+ struct server *server = acmp->server;
+ uint8_t buf[len];
+ struct avb_ethernet_header *h = (void*)buf;
+ struct avb_packet_acmp *reply = SPA_PTROFF(h, sizeof(*h), void);
+ const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void);
+ int status = AVB_ACMP_STATUS_SUCCESS;
+ struct stream *stream;
+
+ if (be64toh(p->talker_guid) != server->entity_id)
+ return 0;
+
+ memcpy(buf, m, len);
+ stream = server_find_stream(server, SPA_DIRECTION_OUTPUT,
+ reply->talker_unique_id);
+ if (stream == NULL) {
+ status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX;
+ goto done;
+ }
+
+ AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE);
+ reply->stream_id = htobe64(stream->id);
+
+ stream_activate(stream, now);
+
+ memcpy(reply->stream_dest_mac, stream->addr, 6);
+ reply->connection_count = htons(1);
+ reply->stream_vlan_id = htons(stream->vlan_id);
+
+done:
+ AVB_PACKET_ACMP_SET_STATUS(reply, status);
+ return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, buf, len);
+}
+
+static int handle_connect_tx_response(struct acmp *acmp, uint64_t now, const void *m, int len)
+{
+ struct server *server = acmp->server;
+ struct avb_ethernet_header *h;
+ const struct avb_packet_acmp *resp = SPA_PTROFF(m, sizeof(*h), void);
+ struct avb_packet_acmp *reply;
+ struct pending *pending;
+ uint16_t sequence_id;
+ struct stream *stream;
+ int res;
+
+ if (be64toh(resp->listener_guid) != server->entity_id)
+ return 0;
+
+ sequence_id = ntohs(resp->sequence_id);
+
+ pending = pending_find(acmp, PENDING_TALKER, sequence_id);
+ if (pending == NULL)
+ return 0;
+
+ h = pending->ptr;
+ pending->size = SPA_MIN((int)pending->size, len);
+ memcpy(h, m, pending->size);
+
+ reply = SPA_PTROFF(h, sizeof(*h), void);
+ reply->sequence_id = htons(pending->old_sequence_id);
+ AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE);
+
+ stream = server_find_stream(server, SPA_DIRECTION_INPUT,
+ ntohs(reply->listener_unique_id));
+ if (stream == NULL)
+ return 0;
+
+ stream->peer_id = be64toh(reply->stream_id);
+ memcpy(stream->addr, reply->stream_dest_mac, 6);
+ stream_activate(stream, now);
+
+ res = avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, pending->size);
+
+ pending_free(acmp, pending);
+
+ return res;
+}
+
+static int handle_disconnect_tx_command(struct acmp *acmp, uint64_t now, const void *m, int len)
+{
+ struct server *server = acmp->server;
+ uint8_t buf[len];
+ struct avb_ethernet_header *h = (void*)buf;
+ struct avb_packet_acmp *reply = SPA_PTROFF(h, sizeof(*h), void);
+ const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void);
+ int status = AVB_ACMP_STATUS_SUCCESS;
+ struct stream *stream;
+
+ if (be64toh(p->talker_guid) != server->entity_id)
+ return 0;
+
+ memcpy(buf, m, len);
+ stream = server_find_stream(server, SPA_DIRECTION_OUTPUT,
+ reply->talker_unique_id);
+ if (stream == NULL) {
+ status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX;
+ goto done;
+ }
+
+ AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_RESPONSE);
+
+ stream_deactivate(stream, now);
+
+done:
+ AVB_PACKET_ACMP_SET_STATUS(reply, status);
+ return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, buf, len);
+}
+
+static int handle_disconnect_tx_response(struct acmp *acmp, uint64_t now, const void *m, int len)
+{
+ struct server *server = acmp->server;
+ struct avb_ethernet_header *h;
+ struct avb_packet_acmp *reply;
+ const struct avb_packet_acmp *resp = SPA_PTROFF(m, sizeof(*h), void);
+ struct pending *pending;
+ uint16_t sequence_id;
+ struct stream *stream;
+ int res;
+
+ if (be64toh(resp->listener_guid) != server->entity_id)
+ return 0;
+
+ sequence_id = ntohs(resp->sequence_id);
+
+ pending = pending_find(acmp, PENDING_TALKER, sequence_id);
+ if (pending == NULL)
+ return 0;
+
+ h = pending->ptr;
+ pending->size = SPA_MIN((int)pending->size, len);
+ memcpy(h, m, pending->size);
+
+ reply = SPA_PTROFF(h, sizeof(*h), void);
+ reply->sequence_id = htons(pending->old_sequence_id);
+ AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE);
+
+ stream = server_find_stream(server, SPA_DIRECTION_INPUT,
+ reply->listener_unique_id);
+ if (stream == NULL)
+ return 0;
+
+ stream_deactivate(stream, now);
+
+ res = avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, pending->size);
+
+ pending_free(acmp, pending);
+
+ return res;
+}
+
+static int handle_connect_rx_command(struct acmp *acmp, uint64_t now, const void *m, int len)
+{
+ struct server *server = acmp->server;
+ struct avb_ethernet_header *h;
+ const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void);
+ struct avb_packet_acmp *cmd;
+
+ if (be64toh(p->listener_guid) != server->entity_id)
+ return 0;
+
+ h = pending_new(acmp, PENDING_TALKER, now,
+ AVB_ACMP_TIMEOUT_CONNECT_TX_COMMAND_MS, m, len);
+ if (h == NULL)
+ return -errno;
+
+ cmd = SPA_PTROFF(h, sizeof(*h), void);
+ AVB_PACKET_ACMP_SET_MESSAGE_TYPE(cmd, AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND);
+ AVB_PACKET_ACMP_SET_STATUS(cmd, AVB_ACMP_STATUS_SUCCESS);
+
+ return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, len);
+}
+
+static int handle_ignore(struct acmp *acmp, uint64_t now, const void *m, int len)
+{
+ return 0;
+}
+
+static int handle_disconnect_rx_command(struct acmp *acmp, uint64_t now, const void *m, int len)
+{
+ struct server *server = acmp->server;
+ struct avb_ethernet_header *h;
+ const struct avb_packet_acmp *p = SPA_PTROFF(m, sizeof(*h), void);
+ struct avb_packet_acmp *cmd;
+
+ if (be64toh(p->listener_guid) != server->entity_id)
+ return 0;
+
+ h = pending_new(acmp, PENDING_TALKER, now,
+ AVB_ACMP_TIMEOUT_DISCONNECT_TX_COMMAND_MS, m, len);
+ if (h == NULL)
+ return -errno;
+
+ cmd = SPA_PTROFF(h, sizeof(*h), void);
+ AVB_PACKET_ACMP_SET_MESSAGE_TYPE(cmd, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_COMMAND);
+ AVB_PACKET_ACMP_SET_STATUS(cmd, AVB_ACMP_STATUS_SUCCESS);
+
+ return avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, len);
+}
+
+static const struct msg_info msg_info[] = {
+ { AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND, "connect-tx-command", handle_connect_tx_command, },
+ { AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE, "connect-tx-response", handle_connect_tx_response, },
+ { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_COMMAND, "disconnect-tx-command", handle_disconnect_tx_command, },
+ { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_RESPONSE, "disconnect-tx-response", handle_disconnect_tx_response, },
+ { AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND, "get-tx-state-command", NULL, },
+ { AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_RESPONSE, "get-tx-state-response", handle_ignore, },
+ { AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_COMMAND, "connect-rx-command", handle_connect_rx_command, },
+ { AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE, "connect-rx-response", handle_ignore, },
+ { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_COMMAND, "disconnect-rx-command", handle_disconnect_rx_command, },
+ { AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE, "disconnect-rx-response", handle_ignore, },
+ { AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_COMMAND, "get-rx-state-command", NULL, },
+ { AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_RESPONSE, "get-rx-state-response", handle_ignore, },
+ { AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_COMMAND, "get-tx-connection-command", NULL, },
+ { AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_RESPONSE, "get-tx-connection-response", handle_ignore, },
+};
+
+static inline const struct msg_info *find_msg_info(uint16_t type, const char *name)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(msg_info, i) {
+ if ((name == NULL && type == i->type) ||
+ (name != NULL && spa_streq(name, i->name)))
+ return i;
+ }
+ return NULL;
+}
+
+static int acmp_message(void *data, uint64_t now, const void *message, int len)
+{
+ struct acmp *acmp = data;
+ struct server *server = acmp->server;
+ const struct avb_ethernet_header *h = message;
+ const struct avb_packet_acmp *p = SPA_PTROFF(h, sizeof(*h), void);
+ const struct msg_info *info;
+ int message_type;
+
+ if (ntohs(h->type) != AVB_TSN_ETH)
+ return 0;
+ if (memcmp(h->dest, mac, 6) != 0 &&
+ memcmp(h->dest, server->mac_addr, 6) != 0)
+ return 0;
+
+ if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_ACMP)
+ return 0;
+
+ message_type = AVB_PACKET_ACMP_GET_MESSAGE_TYPE(p);
+
+ info = find_msg_info(message_type, NULL);
+ if (info == NULL)
+ return 0;
+
+ pw_log_info("got ACMP message %s", info->name);
+
+ if (info->handle == NULL)
+ return reply_not_supported(acmp, message_type | 1, message, len);
+
+ return info->handle(acmp, now, message, len);
+}
+
+static void acmp_destroy(void *data)
+{
+ struct acmp *acmp = data;
+ spa_hook_remove(&acmp->server_listener);
+ free(acmp);
+}
+
+static void check_timeout(struct acmp *acmp, uint64_t now, uint16_t type)
+{
+ struct pending *p, *t;
+
+ spa_list_for_each_safe(p, t, &acmp->pending[type], link) {
+ if (p->last_time + p->timeout > now)
+ continue;
+
+ if (p->retry == 0) {
+ pw_log_info("%p: pending timeout, retry", p);
+ retry_pending(acmp, now, p);
+ } else {
+ pw_log_info("%p: pending timeout, fail", p);
+ pending_free(acmp, p);
+ }
+ }
+}
+static void acmp_periodic(void *data, uint64_t now)
+{
+ struct acmp *acmp = data;
+ check_timeout(acmp, now, PENDING_TALKER);
+ check_timeout(acmp, now, PENDING_LISTENER);
+ check_timeout(acmp, now, PENDING_CONTROLLER);
+}
+
+static int do_help(struct acmp *acmp, const char *args, FILE *out)
+{
+ fprintf(out, "{ \"type\": \"help\","
+ "\"text\": \""
+ "/adp/help: this help \\n"
+ "\" }");
+ return 0;
+}
+
+static int acmp_command(void *data, uint64_t now, const char *command, const char *args, FILE *out)
+{
+ struct acmp *acmp = data;
+ int res;
+
+ if (!spa_strstartswith(command, "/acmp/"))
+ return 0;
+
+ command += strlen("/acmp/");
+
+ if (spa_streq(command, "help"))
+ res = do_help(acmp, args, out);
+ else
+ res = -ENOTSUP;
+
+ return res;
+}
+
+static const struct server_events server_events = {
+ AVB_VERSION_SERVER_EVENTS,
+ .destroy = acmp_destroy,
+ .message = acmp_message,
+ .periodic = acmp_periodic,
+ .command = acmp_command
+};
+
+struct avb_acmp *avb_acmp_register(struct server *server)
+{
+ struct acmp *acmp;
+
+ acmp = calloc(1, sizeof(*acmp));
+ if (acmp == NULL)
+ return NULL;
+
+ acmp->server = server;
+ spa_list_init(&acmp->pending[PENDING_TALKER]);
+ spa_list_init(&acmp->pending[PENDING_LISTENER]);
+ spa_list_init(&acmp->pending[PENDING_CONTROLLER]);
+
+ avdecc_server_add_listener(server, &acmp->server_listener, &server_events, acmp);
+
+ return (struct avb_acmp*)acmp;
+}
+
+void avb_acmp_unregister(struct avb_acmp *acmp)
+{
+ acmp_destroy(acmp);
+}
diff --git a/src/modules/module-avb/acmp.h b/src/modules/module-avb/acmp.h
new file mode 100644
index 0000000..5a41c66
--- /dev/null
+++ b/src/modules/module-avb/acmp.h
@@ -0,0 +1,99 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_ACMP_H
+#define AVB_ACMP_H
+
+#include "packets.h"
+#include "internal.h"
+
+#define AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND 0
+#define AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE 1
+#define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_COMMAND 2
+#define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_RESPONSE 3
+#define AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND 4
+#define AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_RESPONSE 5
+#define AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_COMMAND 6
+#define AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE 7
+#define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_COMMAND 8
+#define AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE 9
+#define AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_COMMAND 10
+#define AVB_ACMP_MESSAGE_TYPE_GET_RX_STATE_RESPONSE 11
+#define AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_COMMAND 12
+#define AVB_ACMP_MESSAGE_TYPE_GET_TX_CONNECTION_RESPONSE 13
+
+#define AVB_ACMP_STATUS_SUCCESS 0
+#define AVB_ACMP_STATUS_LISTENER_UNKNOWN_ID 1
+#define AVB_ACMP_STATUS_TALKER_UNKNOWN_ID 2
+#define AVB_ACMP_STATUS_TALKER_DEST_MAC_FAIL 3
+#define AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX 4
+#define AVB_ACMP_STATUS_TALKER_NO_BANDWIDTH 5
+#define AVB_ACMP_STATUS_TALKER_EXCLUSIVE 6
+#define AVB_ACMP_STATUS_LISTENER_TALKER_TIMEOUT 7
+#define AVB_ACMP_STATUS_LISTENER_EXCLUSIVE 8
+#define AVB_ACMP_STATUS_STATE_UNAVAILABLE 9
+#define AVB_ACMP_STATUS_NOT_CONNECTED 10
+#define AVB_ACMP_STATUS_NO_SUCH_CONNECTION 11
+#define AVB_ACMP_STATUS_COULD_NOT_SEND_MESSAGE 12
+#define AVB_ACMP_STATUS_TALKER_MISBEHAVING 13
+#define AVB_ACMP_STATUS_LISTENER_MISBEHAVING 14
+#define AVB_ACMP_STATUS_RESERVED 15
+#define AVB_ACMP_STATUS_CONTROLLER_NOT_AUTHORIZED 16
+#define AVB_ACMP_STATUS_INCOMPATIBLE_REQUEST 17
+#define AVB_ACMP_STATUS_LISTENER_INVALID_CONNECTION 18
+#define AVB_ACMP_STATUS_NOT_SUPPORTED 31
+
+#define AVB_ACMP_TIMEOUT_CONNECT_TX_COMMAND_MS 2000
+#define AVB_ACMP_TIMEOUT_DISCONNECT_TX_COMMAND_MS 200
+#define AVB_ACMP_TIMEOUT_GET_TX_STATE_COMMAND 200
+#define AVB_ACMP_TIMEOUT_CONNECT_RX_COMMAND_MS 4500
+#define AVB_ACMP_TIMEOUT_DISCONNECT_RX_COMMAND_MS 500
+#define AVB_ACMP_TIMEOUT_GET_RX_STATE_COMMAND_MS 200
+#define AVB_ACMP_TIMEOUT_GET_TX_CONNECTION_COMMAND 200
+
+struct avb_packet_acmp {
+ struct avb_packet_header hdr;
+ uint64_t stream_id;
+ uint64_t controller_guid;
+ uint64_t talker_guid;
+ uint64_t listener_guid;
+ uint16_t talker_unique_id;
+ uint16_t listener_unique_id;
+ char stream_dest_mac[6];
+ uint16_t connection_count;
+ uint16_t sequence_id;
+ uint16_t flags;
+ uint16_t stream_vlan_id;
+ uint16_t reserved;
+} __attribute__ ((__packed__));
+
+#define AVB_PACKET_ACMP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v)
+#define AVB_PACKET_ACMP_SET_STATUS(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v)
+
+#define AVB_PACKET_ACMP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr)
+#define AVB_PACKET_ACMP_GET_STATUS(p) AVB_PACKET_GET_SUB2(&(p)->hdr)
+
+struct avb_acmp *avb_acmp_register(struct server *server);
+
+#endif /* AVB_ACMP_H */
diff --git a/src/modules/module-avb/adp.c b/src/modules/module-avb/adp.c
new file mode 100644
index 0000000..6b13c41
--- /dev/null
+++ b/src/modules/module-avb/adp.c
@@ -0,0 +1,381 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/json.h>
+
+#include <pipewire/pipewire.h>
+
+#include "adp.h"
+#include "aecp-aem-descriptors.h"
+#include "internal.h"
+#include "utils.h"
+
+static const uint8_t mac[6] = AVB_BROADCAST_MAC;
+
+struct entity {
+ struct spa_list link;
+ uint64_t entity_id;
+ uint64_t last_time;
+ int valid_time;
+ unsigned advertise:1;
+ size_t len;
+ uint8_t buf[128];
+};
+
+struct adp {
+ struct server *server;
+ struct spa_hook server_listener;
+
+ struct spa_list entities;
+ uint32_t available_index;
+};
+
+static struct entity *find_entity_by_id(struct adp *adp, uint64_t id)
+{
+ struct entity *e;
+ spa_list_for_each(e, &adp->entities, link)
+ if (e->entity_id == id)
+ return e;
+ return NULL;
+}
+static void entity_free(struct entity *e)
+{
+ spa_list_remove(&e->link);
+ free(e);
+}
+
+static int send_departing(struct adp *adp, uint64_t now, struct entity *e)
+{
+ struct avb_ethernet_header *h = (void*)e->buf;
+ struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void);
+
+ AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING);
+ p->available_index = htonl(adp->available_index++);
+ avb_server_send_packet(adp->server, mac, AVB_TSN_ETH, e->buf, e->len);
+ e->last_time = now;
+ return 0;
+}
+
+static int send_advertise(struct adp *adp, uint64_t now, struct entity *e)
+{
+ struct avb_ethernet_header *h = (void*)e->buf;
+ struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void);
+
+ AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE);
+ p->available_index = htonl(adp->available_index++);
+ avb_server_send_packet(adp->server, mac, AVB_TSN_ETH, e->buf, e->len);
+ e->last_time = now;
+ return 0;
+}
+
+static int send_discover(struct adp *adp, uint64_t entity_id)
+{
+ uint8_t buf[128];
+ struct avb_ethernet_header *h = (void*)buf;
+ struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void);
+ size_t len = sizeof(*h) + sizeof(*p);
+
+ spa_memzero(buf, sizeof(buf));
+ AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP);
+ AVB_PACKET_SET_LENGTH(&p->hdr, AVB_ADP_CONTROL_DATA_LENGTH);
+ AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_DISCOVER);
+ p->entity_id = htonl(entity_id);
+ avb_server_send_packet(adp->server, mac, AVB_TSN_ETH, buf, len);
+ return 0;
+}
+
+static int adp_message(void *data, uint64_t now, const void *message, int len)
+{
+ struct adp *adp = data;
+ struct server *server = adp->server;
+ const struct avb_ethernet_header *h = message;
+ const struct avb_packet_adp *p = SPA_PTROFF(h, sizeof(*h), void);
+ struct entity *e;
+ int message_type;
+ char buf[128];
+ uint64_t entity_id;
+
+ if (ntohs(h->type) != AVB_TSN_ETH)
+ return 0;
+ if (memcmp(h->dest, mac, 6) != 0 &&
+ memcmp(h->dest, server->mac_addr, 6) != 0)
+ return 0;
+
+ if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_ADP ||
+ AVB_PACKET_GET_LENGTH(&p->hdr) < AVB_ADP_CONTROL_DATA_LENGTH)
+ return 0;
+
+ message_type = AVB_PACKET_ADP_GET_MESSAGE_TYPE(p);
+ entity_id = be64toh(p->entity_id);
+
+ e = find_entity_by_id(adp, entity_id);
+
+ switch (message_type) {
+ case AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE:
+ if (e == NULL) {
+ e = calloc(1, sizeof(*e));
+ if (e == NULL)
+ return -errno;
+
+ memcpy(e->buf, message, len);
+ e->len = len;
+ e->valid_time = AVB_PACKET_ADP_GET_VALID_TIME(p);
+ e->entity_id = entity_id;
+ spa_list_append(&adp->entities, &e->link);
+ pw_log_info("entity %s available",
+ avb_utils_format_id(buf, sizeof(buf), entity_id));
+ }
+ e->last_time = now;
+ break;
+ case AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING:
+ if (e != NULL) {
+ pw_log_info("entity %s departing",
+ avb_utils_format_id(buf, sizeof(buf), entity_id));
+ entity_free(e);
+ }
+ break;
+ case AVB_ADP_MESSAGE_TYPE_ENTITY_DISCOVER:
+ pw_log_info("entity %s advertise",
+ avb_utils_format_id(buf, sizeof(buf), entity_id));
+ if (entity_id == 0UL) {
+ spa_list_for_each(e, &adp->entities, link)
+ if (e->advertise)
+ send_advertise(adp, now, e);
+ } else if (e != NULL &&
+ e->advertise && e->entity_id == entity_id) {
+ send_advertise(adp, now, e);
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void adp_destroy(void *data)
+{
+ struct adp *adp = data;
+ spa_hook_remove(&adp->server_listener);
+ free(adp);
+}
+
+static void check_timeout(struct adp *adp, uint64_t now)
+{
+ struct entity *e, *t;
+ char buf[128];
+
+ spa_list_for_each_safe(e, t, &adp->entities, link) {
+ if (e->last_time + (e->valid_time + 2) * SPA_NSEC_PER_SEC > now)
+ continue;
+
+ pw_log_info("entity %s timeout",
+ avb_utils_format_id(buf, sizeof(buf), e->entity_id));
+
+ if (e->advertise)
+ send_departing(adp, now, e);
+
+ entity_free(e);
+ }
+}
+static void check_readvertize(struct adp *adp, uint64_t now, struct entity *e)
+{
+ char buf[128];
+
+ if (!e->advertise)
+ return;
+
+ if (e->last_time + (e->valid_time / 2) * SPA_NSEC_PER_SEC > now)
+ return;
+
+ pw_log_debug("entity %s readvertise",
+ avb_utils_format_id(buf, sizeof(buf), e->entity_id));
+
+ send_advertise(adp, now, e);
+}
+
+static int check_advertise(struct adp *adp, uint64_t now)
+{
+ struct server *server = adp->server;
+ const struct descriptor *d;
+ struct avb_aem_desc_entity *entity;
+ struct avb_aem_desc_avb_interface *avb_interface;
+ struct entity *e;
+ uint64_t entity_id;
+ struct avb_ethernet_header *h;
+ struct avb_packet_adp *p;
+ char buf[128];
+
+ d = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0);
+ if (d == NULL)
+ return 0;
+
+ entity = d->ptr;
+ entity_id = be64toh(entity->entity_id);
+
+ if ((e = find_entity_by_id(adp, entity_id)) != NULL) {
+ if (e->advertise)
+ check_readvertize(adp, now, e);
+ return 0;
+ }
+
+ d = server_find_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0);
+ avb_interface = d ? d->ptr : NULL;
+
+ pw_log_info("entity %s advertise",
+ avb_utils_format_id(buf, sizeof(buf), entity_id));
+
+ e = calloc(1, sizeof(*e));
+ if (e == NULL)
+ return -errno;
+
+ e->advertise = true;
+ e->valid_time = 10;
+ e->last_time = now;
+ e->entity_id = entity_id;
+ e->len = sizeof(*h) + sizeof(*p);
+
+ h = (void*)e->buf;
+ p = SPA_PTROFF(h, sizeof(*h), void);
+ AVB_PACKET_SET_LENGTH(&p->hdr, AVB_ADP_CONTROL_DATA_LENGTH);
+ AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP);
+ AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE);
+ AVB_PACKET_ADP_SET_VALID_TIME(p, e->valid_time);
+
+ p->entity_id = entity->entity_id;
+ p->entity_model_id = entity->entity_model_id;
+ p->entity_capabilities = entity->entity_capabilities;
+ p->talker_stream_sources = entity->talker_stream_sources;
+ p->talker_capabilities = entity->talker_capabilities;
+ p->listener_stream_sinks = entity->listener_stream_sinks;
+ p->listener_capabilities = entity->listener_capabilities;
+ p->controller_capabilities = entity->controller_capabilities;
+ p->available_index = entity->available_index;
+ if (avb_interface) {
+ p->gptp_grandmaster_id = avb_interface->clock_identity;
+ p->gptp_domain_number = avb_interface->domain_number;
+ }
+ p->identify_control_index = 0;
+ p->interface_index = 0;
+ p->association_id = entity->association_id;
+
+ spa_list_append(&adp->entities, &e->link);
+
+ return 0;
+}
+
+static void adp_periodic(void *data, uint64_t now)
+{
+ struct adp *adp = data;
+ check_timeout(adp, now);
+ check_advertise(adp, now);
+}
+
+static int do_help(struct adp *adp, const char *args, FILE *out)
+{
+ fprintf(out, "{ \"type\": \"help\","
+ "\"text\": \""
+ "/adp/help: this help \\n"
+ "/adp/discover [{ \"entity-id\": <id> }] : trigger discover\\n"
+ "\" }");
+ return 0;
+}
+
+static int do_discover(struct adp *adp, const char *args, FILE *out)
+{
+ struct spa_json it[2];
+ char key[128];
+ uint64_t entity_id = 0ULL;
+
+ spa_json_init(&it[0], args, strlen(args));
+ if (spa_json_enter_object(&it[0], &it[1]) <= 0)
+ return -EINVAL;
+
+ while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) {
+ int len;
+ const char *value;
+ uint64_t id_val;
+
+ if ((len = spa_json_next(&it[1], &value)) <= 0)
+ break;
+
+ if (spa_json_is_null(value, len))
+ continue;
+
+ if (spa_streq(key, "entity-id")) {
+ if (avb_utils_parse_id(value, len, &id_val) >= 0)
+ entity_id = id_val;
+ }
+ }
+ send_discover(adp, entity_id);
+ return 0;
+}
+
+static int adp_command(void *data, uint64_t now, const char *command, const char *args, FILE *out)
+{
+ struct adp *adp = data;
+ int res;
+
+ if (!spa_strstartswith(command, "/adp/"))
+ return 0;
+
+ command += strlen("/adp/");
+
+ if (spa_streq(command, "help"))
+ res = do_help(adp, args, out);
+ else if (spa_streq(command, "discover"))
+ res = do_discover(adp, args, out);
+ else
+ res = -ENOTSUP;
+
+ return res;
+}
+
+static const struct server_events server_events = {
+ AVB_VERSION_SERVER_EVENTS,
+ .destroy = adp_destroy,
+ .message = adp_message,
+ .periodic = adp_periodic,
+ .command = adp_command
+};
+
+struct avb_adp *avb_adp_register(struct server *server)
+{
+ struct adp *adp;
+
+ adp = calloc(1, sizeof(*adp));
+ if (adp == NULL)
+ return NULL;
+
+ adp->server = server;
+ spa_list_init(&adp->entities);
+
+ avdecc_server_add_listener(server, &adp->server_listener, &server_events, adp);
+
+ return (struct avb_adp*)adp;
+}
+
+void avb_adp_unregister(struct avb_adp *adp)
+{
+ adp_destroy(adp);
+}
diff --git a/src/modules/module-avb/adp.h b/src/modules/module-avb/adp.h
new file mode 100644
index 0000000..c546088
--- /dev/null
+++ b/src/modules/module-avb/adp.h
@@ -0,0 +1,105 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_ADP_H
+#define AVB_ADP_H
+
+#include "packets.h"
+#include "internal.h"
+
+#define AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE 0
+#define AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING 1
+#define AVB_ADP_MESSAGE_TYPE_ENTITY_DISCOVER 2
+
+#define AVB_ADP_ENTITY_CAPABILITY_EFU_MODE (1u<<0)
+#define AVB_ADP_ENTITY_CAPABILITY_ADDRESS_ACCESS_SUPPORTED (1u<<1)
+#define AVB_ADP_ENTITY_CAPABILITY_GATEWAY_ENTITY (1u<<2)
+#define AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED (1u<<3)
+#define AVB_ADP_ENTITY_CAPABILITY_LEGACY_AVC (1u<<4)
+#define AVB_ADP_ENTITY_CAPABILITY_ASSOCIATION_ID_SUPPORTED (1u<<5)
+#define AVB_ADP_ENTITY_CAPABILITY_ASSOCIATION_ID_VALID (1u<<6)
+#define AVB_ADP_ENTITY_CAPABILITY_VENDOR_UNIQUE_SUPPORTED (1u<<7)
+#define AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED (1u<<8)
+#define AVB_ADP_ENTITY_CAPABILITY_CLASS_B_SUPPORTED (1u<<9)
+#define AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED (1u<<10)
+#define AVB_ADP_ENTITY_CAPABILITY_AEM_AUTHENTICATION_SUPPORTED (1u<<11)
+#define AVB_ADP_ENTITY_CAPABILITY_AEM_AUTHENTICATION_REQUIRED (1u<<12)
+#define AVB_ADP_ENTITY_CAPABILITY_AEM_PERSISTENT_ACQUIRE_SUPPORTED (1u<<13)
+#define AVB_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID (1u<<14)
+#define AVB_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID (1u<<15)
+#define AVB_ADP_ENTITY_CAPABILITY_GENERAL_CONTROLLER_IGNORE (1u<<16)
+#define AVB_ADP_ENTITY_CAPABILITY_ENTITY_NOT_READY (1u<<17)
+
+#define AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED (1u<<0)
+#define AVB_ADP_TALKER_CAPABILITY_OTHER_SOURCE (1u<<9)
+#define AVB_ADP_TALKER_CAPABILITY_CONTROL_SOURCE (1u<<10)
+#define AVB_ADP_TALKER_CAPABILITY_MEDIA_CLOCK_SOURCE (1u<<11)
+#define AVB_ADP_TALKER_CAPABILITY_SMPTE_SOURCE (1u<<12)
+#define AVB_ADP_TALKER_CAPABILITY_MIDI_SOURCE (1u<<13)
+#define AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE (1u<<14)
+#define AVB_ADP_TALKER_CAPABILITY_VIDEO_SOURCE (1u<<15)
+
+#define AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED (1u<<0)
+#define AVB_ADP_LISTENER_CAPABILITY_OTHER_SINK (1u<<9)
+#define AVB_ADP_LISTENER_CAPABILITY_CONTROL_SINK (1u<<10)
+#define AVB_ADP_LISTENER_CAPABILITY_MEDIA_CLOCK_SINK (1u<<11)
+#define AVB_ADP_LISTENER_CAPABILITY_SMPTE_SINK (1u<<12)
+#define AVB_ADP_LISTENER_CAPABILITY_MIDI_SINK (1u<<13)
+#define AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK (1u<<14)
+#define AVB_ADP_LISTENER_CAPABILITY_VIDEO_SINK (1u<<15)
+
+#define AVB_ADP_CONTROLLER_CAPABILITY_IMPLEMENTED (1u<<0)
+#define AVB_ADP_CONTROLLER_CAPABILITY_LAYER3_PROXY (1u<<1)
+
+#define AVB_ADP_CONTROL_DATA_LENGTH 56
+
+struct avb_packet_adp {
+ struct avb_packet_header hdr;
+ uint64_t entity_id;
+ uint64_t entity_model_id;
+ uint32_t entity_capabilities;
+ uint16_t talker_stream_sources;
+ uint16_t talker_capabilities;
+ uint16_t listener_stream_sinks;
+ uint16_t listener_capabilities;
+ uint32_t controller_capabilities;
+ uint32_t available_index;
+ uint64_t gptp_grandmaster_id;
+ uint8_t gptp_domain_number;
+ uint8_t reserved0[3];
+ uint16_t identify_control_index;
+ uint16_t interface_index;
+ uint64_t association_id;
+ uint32_t reserved1;
+} __attribute__ ((__packed__));
+
+#define AVB_PACKET_ADP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v)
+#define AVB_PACKET_ADP_SET_VALID_TIME(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v)
+
+#define AVB_PACKET_ADP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr)
+#define AVB_PACKET_ADP_GET_VALID_TIME(p) AVB_PACKET_GET_SUB2(&(p)->hdr)
+
+struct avb_adp *avb_adp_register(struct server *server);
+
+#endif /* AVB_ADP_H */
diff --git a/src/modules/module-avb/aecp-aem-descriptors.h b/src/modules/module-avb/aecp-aem-descriptors.h
new file mode 100644
index 0000000..101c331
--- /dev/null
+++ b/src/modules/module-avb/aecp-aem-descriptors.h
@@ -0,0 +1,247 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_AECP_AEM_DESCRIPTORS_H
+#define AVB_AECP_AEM_DESCRIPTORS_H
+
+#include "internal.h"
+
+#define AVB_AEM_DESC_ENTITY 0x0000
+#define AVB_AEM_DESC_CONFIGURATION 0x0001
+#define AVB_AEM_DESC_AUDIO_UNIT 0x0002
+#define AVB_AEM_DESC_VIDEO_UNIT 0x0003
+#define AVB_AEM_DESC_SENSOR_UNIT 0x0004
+#define AVB_AEM_DESC_STREAM_INPUT 0x0005
+#define AVB_AEM_DESC_STREAM_OUTPUT 0x0006
+#define AVB_AEM_DESC_JACK_INPUT 0x0007
+#define AVB_AEM_DESC_JACK_OUTPUT 0x0008
+#define AVB_AEM_DESC_AVB_INTERFACE 0x0009
+#define AVB_AEM_DESC_CLOCK_SOURCE 0x000a
+#define AVB_AEM_DESC_MEMORY_OBJECT 0x000b
+#define AVB_AEM_DESC_LOCALE 0x000c
+#define AVB_AEM_DESC_STRINGS 0x000d
+#define AVB_AEM_DESC_STREAM_PORT_INPUT 0x000e
+#define AVB_AEM_DESC_STREAM_PORT_OUTPUT 0x000f
+#define AVB_AEM_DESC_EXTERNAL_PORT_INPUT 0x0010
+#define AVB_AEM_DESC_EXTERNAL_PORT_OUTPUT 0x0011
+#define AVB_AEM_DESC_INTERNAL_PORT_INPUT 0x0012
+#define AVB_AEM_DESC_INTERNAL_PORT_OUTPUT 0x0013
+#define AVB_AEM_DESC_AUDIO_CLUSTER 0x0014
+#define AVB_AEM_DESC_VIDEO_CLUSTER 0x0015
+#define AVB_AEM_DESC_SENSOR_CLUSTER 0x0016
+#define AVB_AEM_DESC_AUDIO_MAP 0x0017
+#define AVB_AEM_DESC_VIDEO_MAP 0x0018
+#define AVB_AEM_DESC_SENSOR_MAP 0x0019
+#define AVB_AEM_DESC_CONTROL 0x001a
+#define AVB_AEM_DESC_SIGNAL_SELECTOR 0x001b
+#define AVB_AEM_DESC_MIXER 0x001c
+#define AVB_AEM_DESC_MATRIX 0x001d
+#define AVB_AEM_DESC_MATRIX_SIGNAL 0x001e
+#define AVB_AEM_DESC_SIGNAL_SPLITTER 0x001f
+#define AVB_AEM_DESC_SIGNAL_COMBINER 0x0020
+#define AVB_AEM_DESC_SIGNAL_DEMULTIPLEXER 0x0021
+#define AVB_AEM_DESC_SIGNAL_MULTIPLEXER 0x0022
+#define AVB_AEM_DESC_SIGNAL_TRANSCODER 0x0023
+#define AVB_AEM_DESC_CLOCK_DOMAIN 0x0024
+#define AVB_AEM_DESC_CONTROL_BLOCK 0x0025
+#define AVB_AEM_DESC_INVALID 0xffff
+
+struct avb_aem_desc_entity {
+ uint64_t entity_id;
+ uint64_t entity_model_id;
+ uint32_t entity_capabilities;
+ uint16_t talker_stream_sources;
+ uint16_t talker_capabilities;
+ uint16_t listener_stream_sinks;
+ uint16_t listener_capabilities;
+ uint32_t controller_capabilities;
+ uint32_t available_index;
+ uint64_t association_id;
+ char entity_name[64];
+ uint16_t vendor_name_string;
+ uint16_t model_name_string;
+ char firmware_version[64];
+ char group_name[64];
+ char serial_number[64];
+ uint16_t configurations_count;
+ uint16_t current_configuration;
+} __attribute__ ((__packed__));
+
+struct avb_aem_desc_descriptor_count {
+ uint16_t descriptor_type;
+ uint16_t descriptor_count;
+} __attribute__ ((__packed__));
+
+struct avb_aem_desc_configuration {
+ char object_name[64];
+ uint16_t localized_description;
+ uint16_t descriptor_counts_count;
+ uint16_t descriptor_counts_offset;
+ struct avb_aem_desc_descriptor_count descriptor_counts[0];
+} __attribute__ ((__packed__));
+
+struct avb_aem_desc_sampling_rate {
+ uint32_t pull_frequency;
+} __attribute__ ((__packed__));
+
+struct avb_aem_desc_audio_unit {
+ char object_name[64];
+ uint16_t localized_description;
+ uint16_t clock_domain_index;
+ uint16_t number_of_stream_input_ports;
+ uint16_t base_stream_input_port;
+ uint16_t number_of_stream_output_ports;
+ uint16_t base_stream_output_port;
+ uint16_t number_of_external_input_ports;
+ uint16_t base_external_input_port;
+ uint16_t number_of_external_output_ports;
+ uint16_t base_external_output_port;
+ uint16_t number_of_internal_input_ports;
+ uint16_t base_internal_input_port;
+ uint16_t number_of_internal_output_ports;
+ uint16_t base_internal_output_port;
+ uint16_t number_of_controls;
+ uint16_t base_control;
+ uint16_t number_of_signal_selectors;
+ uint16_t base_signal_selector;
+ uint16_t number_of_mixers;
+ uint16_t base_mixer;
+ uint16_t number_of_matrices;
+ uint16_t base_matrix;
+ uint16_t number_of_splitters;
+ uint16_t base_splitter;
+ uint16_t number_of_combiners;
+ uint16_t base_combiner;
+ uint16_t number_of_demultiplexers;
+ uint16_t base_demultiplexer;
+ uint16_t number_of_multiplexers;
+ uint16_t base_multiplexer;
+ uint16_t number_of_transcoders;
+ uint16_t base_transcoder;
+ uint16_t number_of_control_blocks;
+ uint16_t base_control_block;
+ uint32_t current_sampling_rate;
+ uint16_t sampling_rates_offset;
+ uint16_t sampling_rates_count;
+ struct avb_aem_desc_sampling_rate sampling_rates[0];
+} __attribute__ ((__packed__));
+
+#define AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE (1u<<0)
+#define AVB_AEM_DESC_STREAM_FLAG_CLASS_A (1u<<1)
+#define AVB_AEM_DESC_STREAM_FLAG_CLASS_B (1u<<2)
+#define AVB_AEM_DESC_STREAM_FLAG_SUPPORTS_ENCRYPTED (1u<<3)
+#define AVB_AEM_DESC_STREAM_FLAG_PRIMARY_BACKUP_SUPPORTED (1u<<4)
+#define AVB_AEM_DESC_STREAM_FLAG_PRIMARY_BACKUP_VALID (1u<<5)
+#define AVB_AEM_DESC_STREAM_FLAG_SECONDARY_BACKUP_SUPPORTED (1u<<6)
+#define AVB_AEM_DESC_STREAM_FLAG_SECONDARY_BACKUP_VALID (1u<<7)
+#define AVB_AEM_DESC_STREAM_FLAG_TERTIARY_BACKUP_SUPPORTED (1u<<8)
+#define AVB_AEM_DESC_STREAM_FLAG_TERTIARY_BACKUP_VALID (1u<<9)
+
+struct avb_aem_desc_stream {
+ char object_name[64];
+ uint16_t localized_description;
+ uint16_t clock_domain_index;
+ uint16_t stream_flags;
+ uint64_t current_format;
+ uint16_t formats_offset;
+ uint16_t number_of_formats;
+ uint64_t backup_talker_entity_id_0;
+ uint16_t backup_talker_unique_id_0;
+ uint64_t backup_talker_entity_id_1;
+ uint16_t backup_talker_unique_id_1;
+ uint64_t backup_talker_entity_id_2;
+ uint16_t backup_talker_unique_id_2;
+ uint64_t backedup_talker_entity_id;
+ uint16_t backedup_talker_unique;
+ uint16_t avb_interface_index;
+ uint32_t buffer_length;
+ uint64_t stream_formats[0];
+} __attribute__ ((__packed__));
+
+#define AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED (1<<0)
+#define AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_SUPPORTED (1<<1)
+#define AVB_AEM_DESC_AVB_INTERFACE_FLAG_SRP_SUPPORTED (1<<2)
+
+struct avb_aem_desc_avb_interface {
+ char object_name[64];
+ uint16_t localized_description;
+ uint8_t mac_address[6];
+ uint16_t interface_flags;
+ uint64_t clock_identity;
+ uint8_t priority1;
+ uint8_t clock_class;
+ uint16_t offset_scaled_log_variance;
+ uint8_t clock_accuracy;
+ uint8_t priority2;
+ uint8_t domain_number;
+ int8_t log_sync_interval;
+ int8_t log_announce_interval;
+ int8_t log_pdelay_interval;
+ uint16_t port_number;
+} __attribute__ ((__packed__));
+
+#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INTERNAL 0x0000
+#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_EXTERNAL 0x0001
+#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM 0x0002
+#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_MEDIA_CLOCK_STREAM 0x0003
+#define AVB_AEM_DESC_CLOCK_SOURCE_TYPE_EXPANSION 0xffff
+
+struct avb_aem_desc_clock_source {
+ char object_name[64];
+ uint16_t localized_description;
+ uint16_t clock_source_flags;
+ uint16_t clock_source_type;
+ uint64_t clock_source_identifier;
+ uint16_t clock_source_location_type;
+ uint16_t clock_source_location_index;
+} __attribute__ ((__packed__));
+
+struct avb_aem_desc_locale {
+ char locale_identifier[64];
+ uint16_t number_of_strings;
+ uint16_t base_strings;
+} __attribute__ ((__packed__));
+
+struct avb_aem_desc_strings {
+ char string_0[64];
+ char string_1[64];
+ char string_2[64];
+ char string_3[64];
+ char string_4[64];
+ char string_5[64];
+ char string_6[64];
+} __attribute__ ((__packed__));
+
+struct avb_aem_desc_stream_port {
+ uint16_t clock_domain_index;
+ uint16_t port_flags;
+ uint16_t number_of_controls;
+ uint16_t base_control;
+ uint16_t number_of_clusters;
+ uint16_t base_cluster;
+ uint16_t number_of_maps;
+ uint16_t base_map;
+} __attribute__ ((__packed__));
+
+#endif /* AVB_AECP_AEM_DESCRIPTORS_H */
diff --git a/src/modules/module-avb/aecp-aem.c b/src/modules/module-avb/aecp-aem.c
new file mode 100644
index 0000000..d191330
--- /dev/null
+++ b/src/modules/module-avb/aecp-aem.c
@@ -0,0 +1,285 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "aecp-aem.h"
+#include "aecp-aem-descriptors.h"
+
+static int reply_status(struct aecp *aecp, int status, const void *m, int len)
+{
+ struct server *server = aecp->server;
+ uint8_t buf[len];
+ struct avb_ethernet_header *h = (void*)buf;
+ struct avb_packet_aecp_header *reply = SPA_PTROFF(h, sizeof(*h), void);
+
+ memcpy(buf, m, len);
+ AVB_PACKET_AECP_SET_MESSAGE_TYPE(reply, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
+ AVB_PACKET_AECP_SET_STATUS(reply, status);
+
+ return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len);
+}
+
+static int reply_not_implemented(struct aecp *aecp, const void *m, int len)
+{
+ return reply_status(aecp, AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED, m, len);
+}
+
+static int reply_success(struct aecp *aecp, const void *m, int len)
+{
+ return reply_status(aecp, AVB_AECP_AEM_STATUS_SUCCESS, m, len);
+}
+
+/* ACQUIRE_ENTITY */
+static int handle_acquire_entity(struct aecp *aecp, const void *m, int len)
+{
+ struct server *server = aecp->server;
+ const struct avb_packet_aecp_aem *p = m;
+ const struct avb_packet_aecp_aem_acquire *ae;
+ const struct descriptor *desc;
+ uint16_t desc_type, desc_id;
+
+ ae = (const struct avb_packet_aecp_aem_acquire*)p->payload;
+
+ desc_type = ntohs(ae->descriptor_type);
+ desc_id = ntohs(ae->descriptor_id);
+
+ desc = server_find_descriptor(server, desc_type, desc_id);
+ if (desc == NULL)
+ return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
+
+ if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0)
+ return reply_not_implemented(aecp, m, len);
+
+ return reply_success(aecp, m, len);
+}
+
+/* LOCK_ENTITY */
+static int handle_lock_entity(struct aecp *aecp, const void *m, int len)
+{
+ struct server *server = aecp->server;
+ const struct avb_packet_aecp_aem *p = m;
+ const struct avb_packet_aecp_aem_acquire *ae;
+ const struct descriptor *desc;
+ uint16_t desc_type, desc_id;
+
+ ae = (const struct avb_packet_aecp_aem_acquire*)p->payload;
+
+ desc_type = ntohs(ae->descriptor_type);
+ desc_id = ntohs(ae->descriptor_id);
+
+ desc = server_find_descriptor(server, desc_type, desc_id);
+ if (desc == NULL)
+ return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
+
+ if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0)
+ return reply_not_implemented(aecp, m, len);
+
+ return reply_success(aecp, m, len);
+}
+
+/* READ_DESCRIPTOR */
+static int handle_read_descriptor(struct aecp *aecp, const void *m, int len)
+{
+ struct server *server = aecp->server;
+ const struct avb_ethernet_header *h = m;
+ const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
+ struct avb_packet_aecp_aem *reply;
+ const struct avb_packet_aecp_aem_read_descriptor *rd;
+ uint16_t desc_type, desc_id;
+ const struct descriptor *desc;
+ uint8_t buf[2048];
+ size_t size, psize;
+
+ rd = (struct avb_packet_aecp_aem_read_descriptor*)p->payload;
+
+ desc_type = ntohs(rd->descriptor_type);
+ desc_id = ntohs(rd->descriptor_id);
+
+ pw_log_info("descriptor type:%04x index:%d", desc_type, desc_id);
+
+ desc = server_find_descriptor(server, desc_type, desc_id);
+ if (desc == NULL)
+ return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
+
+ memcpy(buf, m, len);
+
+ psize = sizeof(*rd);
+ size = sizeof(*h) + sizeof(*reply) + psize;
+
+ memcpy(buf + size, desc->ptr, desc->size);
+ size += desc->size;
+ psize += desc->size;
+
+ h = (void*)buf;
+ reply = SPA_PTROFF(h, sizeof(*h), void);
+ AVB_PACKET_AECP_SET_MESSAGE_TYPE(&reply->aecp, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
+ AVB_PACKET_AECP_SET_STATUS(&reply->aecp, AVB_AECP_AEM_STATUS_SUCCESS);
+ AVB_PACKET_SET_LENGTH(&reply->aecp.hdr, psize + 12);
+
+ return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size);
+}
+
+/* GET_AVB_INFO */
+static int handle_get_avb_info(struct aecp *aecp, const void *m, int len)
+{
+ struct server *server = aecp->server;
+ const struct avb_ethernet_header *h = m;
+ const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
+ struct avb_packet_aecp_aem *reply;
+ struct avb_packet_aecp_aem_get_avb_info *i;
+ struct avb_aem_desc_avb_interface *avb_interface;
+ uint16_t desc_type, desc_id;
+ const struct descriptor *desc;
+ uint8_t buf[2048];
+ size_t size, psize;
+
+ i = (struct avb_packet_aecp_aem_get_avb_info*)p->payload;
+
+ desc_type = ntohs(i->descriptor_type);
+ desc_id = ntohs(i->descriptor_id);
+
+ desc = server_find_descriptor(server, desc_type, desc_id);
+ if (desc == NULL)
+ return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
+
+ if (desc_type != AVB_AEM_DESC_AVB_INTERFACE || desc_id != 0)
+ return reply_not_implemented(aecp, m, len);
+
+ avb_interface = desc->ptr;
+
+ memcpy(buf, m, len);
+
+ psize = sizeof(*i);
+ size = sizeof(*h) + sizeof(*reply) + psize;
+
+ h = (void*)buf;
+ reply = SPA_PTROFF(h, sizeof(*h), void);
+ AVB_PACKET_AECP_SET_MESSAGE_TYPE(&reply->aecp, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
+ AVB_PACKET_AECP_SET_STATUS(&reply->aecp, AVB_AECP_AEM_STATUS_SUCCESS);
+ AVB_PACKET_SET_LENGTH(&reply->aecp.hdr, psize + 12);
+
+ i = (struct avb_packet_aecp_aem_get_avb_info*)reply->payload;
+ i->gptp_grandmaster_id = avb_interface->clock_identity;
+ i->propagation_delay = htonl(0);
+ i->gptp_domain_number = avb_interface->domain_number;
+ i->flags = 0;
+ i->msrp_mappings_count = htons(0);
+
+ return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size);
+}
+
+/* AEM_COMMAND */
+struct cmd_info {
+ uint16_t type;
+ const char *name;
+ int (*handle) (struct aecp *aecp, const void *p, int len);
+};
+
+static const struct cmd_info cmd_info[] = {
+ { AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, "acquire-entity", handle_acquire_entity, },
+ { AVB_AECP_AEM_CMD_LOCK_ENTITY, "lock-entity", handle_lock_entity, },
+ { AVB_AECP_AEM_CMD_ENTITY_AVAILABLE, "entity-available", NULL, },
+ { AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE, "controller-available", NULL, },
+ { AVB_AECP_AEM_CMD_READ_DESCRIPTOR, "read-descriptor", handle_read_descriptor, },
+ { AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR, "write-descriptor", NULL, },
+ { AVB_AECP_AEM_CMD_SET_CONFIGURATION, "set-configuration", NULL, },
+ { AVB_AECP_AEM_CMD_GET_CONFIGURATION, "get-configuration", NULL, },
+ { AVB_AECP_AEM_CMD_SET_STREAM_FORMAT, "set-stream-format", NULL, },
+ { AVB_AECP_AEM_CMD_GET_STREAM_FORMAT, "get-stream-format", NULL, },
+ { AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT, "set-video-format", NULL, },
+ { AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT, "get-video-format", NULL, },
+ { AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT, "set-sensor-format", NULL, },
+ { AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT, "get-sensor-format", NULL, },
+ { AVB_AECP_AEM_CMD_SET_STREAM_INFO, "set-stream-info", NULL, },
+ { AVB_AECP_AEM_CMD_GET_STREAM_INFO, "get-stream-info", NULL, },
+ { AVB_AECP_AEM_CMD_SET_NAME, "set-name", NULL, },
+ { AVB_AECP_AEM_CMD_GET_NAME, "get-name", NULL, },
+ { AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID, "set-association-id", NULL, },
+ { AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID, "get-association-id", NULL, },
+ { AVB_AECP_AEM_CMD_SET_SAMPLING_RATE, "set-sampling-rate", NULL, },
+ { AVB_AECP_AEM_CMD_GET_SAMPLING_RATE, "get-sampling-rate", NULL, },
+ { AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE, "set-clock-source", NULL, },
+ { AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE, "get-clock-source", NULL, },
+ { AVB_AECP_AEM_CMD_SET_CONTROL, "set-control", NULL, },
+ { AVB_AECP_AEM_CMD_GET_CONTROL, "get-control", NULL, },
+ { AVB_AECP_AEM_CMD_INCREMENT_CONTROL, "increment-control", NULL, },
+ { AVB_AECP_AEM_CMD_DECREMENT_CONTROL, "decrement-control", NULL, },
+ { AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR, "set-signal-selector", NULL, },
+ { AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR, "get-signal-selector", NULL, },
+ { AVB_AECP_AEM_CMD_SET_MIXER, "set-mixer", NULL, },
+ { AVB_AECP_AEM_CMD_GET_MIXER, "get-mixer", NULL, },
+ { AVB_AECP_AEM_CMD_SET_MATRIX, "set-matrix", NULL, },
+ { AVB_AECP_AEM_CMD_GET_MATRIX, "get-matrix", NULL, },
+ { AVB_AECP_AEM_CMD_START_STREAMING, "start-streaming", NULL, },
+ { AVB_AECP_AEM_CMD_STOP_STREAMING, "stop-streaming", NULL, },
+ { AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION, "register-unsolicited-notification", NULL, },
+ { AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION, "deregister-unsolicited-notification", NULL, },
+ { AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION, "identify-notification", NULL, },
+ { AVB_AECP_AEM_CMD_GET_AVB_INFO, "get-avb-info", handle_get_avb_info, },
+ { AVB_AECP_AEM_CMD_GET_AS_PATH, "get-as-path", NULL, },
+ { AVB_AECP_AEM_CMD_GET_COUNTERS, "get-counters", NULL, },
+ { AVB_AECP_AEM_CMD_REBOOT, "reboot", NULL, },
+ { AVB_AECP_AEM_CMD_GET_AUDIO_MAP, "get-audio-map", NULL, },
+ { AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS, "add-audio-mappings", NULL, },
+ { AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS, "remove-audio-mappings", NULL, },
+ { AVB_AECP_AEM_CMD_GET_VIDEO_MAP, "get-video-map", NULL, },
+ { AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS, "add-video-mappings", NULL, },
+ { AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS, "remove-video-mappings", NULL, },
+ { AVB_AECP_AEM_CMD_GET_SENSOR_MAP, "get-sensor-map", NULL, }
+};
+
+static inline const struct cmd_info *find_cmd_info(uint16_t type, const char *name)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(cmd_info, i) {
+ if ((name == NULL && type == i->type) ||
+ (name != NULL && spa_streq(name, i->name)))
+ return i;
+ }
+ return NULL;
+}
+
+int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len)
+{
+ const struct avb_ethernet_header *h = m;
+ const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
+ uint16_t cmd_type;
+ const struct cmd_info *info;
+
+ cmd_type = AVB_PACKET_AEM_GET_COMMAND_TYPE(p);
+
+ info = find_cmd_info(cmd_type, NULL);
+ if (info == NULL)
+ return reply_not_implemented(aecp, m, len);
+
+ pw_log_info("aem command %s", info->name);
+
+ if (info->handle == NULL)
+ return reply_not_implemented(aecp, m, len);
+
+ return info->handle(aecp, m, len);
+}
+
+int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len)
+{
+ return 0;
+}
diff --git a/src/modules/module-avb/aecp-aem.h b/src/modules/module-avb/aecp-aem.h
new file mode 100644
index 0000000..dcf26b5
--- /dev/null
+++ b/src/modules/module-avb/aecp-aem.h
@@ -0,0 +1,345 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_AEM_H
+#define AVB_AEM_H
+
+#include "aecp.h"
+
+#define AVB_AECP_AEM_STATUS_SUCCESS 0
+#define AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED 1
+#define AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR 2
+#define AVB_AECP_AEM_STATUS_ENTITY_LOCKED 3
+#define AVB_AECP_AEM_STATUS_ENTITY_ACQUIRED 4
+#define AVB_AECP_AEM_STATUS_NOT_AUTHENTICATED 5
+#define AVB_AECP_AEM_STATUS_AUTHENTICATION_DISABLED 6
+#define AVB_AECP_AEM_STATUS_BAD_ARGUMENTS 7
+#define AVB_AECP_AEM_STATUS_NO_RESOURCES 8
+#define AVB_AECP_AEM_STATUS_IN_PROGRESS 9
+#define AVB_AECP_AEM_STATUS_ENTITY_MISBEHAVING 10
+#define AVB_AECP_AEM_STATUS_NOT_SUPPORTED 11
+#define AVB_AECP_AEM_STATUS_STREAM_IS_RUNNING 12
+
+#define AVB_AECP_AEM_CMD_ACQUIRE_ENTITY 0x0000
+#define AVB_AECP_AEM_CMD_LOCK_ENTITY 0x0001
+#define AVB_AECP_AEM_CMD_ENTITY_AVAILABLE 0x0002
+#define AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE 0x0003
+#define AVB_AECP_AEM_CMD_READ_DESCRIPTOR 0x0004
+#define AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR 0x0005
+#define AVB_AECP_AEM_CMD_SET_CONFIGURATION 0x0006
+#define AVB_AECP_AEM_CMD_GET_CONFIGURATION 0x0007
+#define AVB_AECP_AEM_CMD_SET_STREAM_FORMAT 0x0008
+#define AVB_AECP_AEM_CMD_GET_STREAM_FORMAT 0x0009
+#define AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT 0x000a
+#define AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT 0x000b
+#define AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT 0x000c
+#define AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT 0x000d
+#define AVB_AECP_AEM_CMD_SET_STREAM_INFO 0x000e
+#define AVB_AECP_AEM_CMD_GET_STREAM_INFO 0x000f
+#define AVB_AECP_AEM_CMD_SET_NAME 0x0010
+#define AVB_AECP_AEM_CMD_GET_NAME 0x0011
+#define AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID 0x0012
+#define AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID 0x0013
+#define AVB_AECP_AEM_CMD_SET_SAMPLING_RATE 0x0014
+#define AVB_AECP_AEM_CMD_GET_SAMPLING_RATE 0x0015
+#define AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE 0x0016
+#define AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE 0x0017
+#define AVB_AECP_AEM_CMD_SET_CONTROL 0x0018
+#define AVB_AECP_AEM_CMD_GET_CONTROL 0x0019
+#define AVB_AECP_AEM_CMD_INCREMENT_CONTROL 0x001a
+#define AVB_AECP_AEM_CMD_DECREMENT_CONTROL 0x001b
+#define AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR 0x001c
+#define AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR 0x001d
+#define AVB_AECP_AEM_CMD_SET_MIXER 0x001e
+#define AVB_AECP_AEM_CMD_GET_MIXER 0x001f
+#define AVB_AECP_AEM_CMD_SET_MATRIX 0x0020
+#define AVB_AECP_AEM_CMD_GET_MATRIX 0x0021
+#define AVB_AECP_AEM_CMD_START_STREAMING 0x0022
+#define AVB_AECP_AEM_CMD_STOP_STREAMING 0x0023
+#define AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION 0x0024
+#define AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION 0x0025
+#define AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION 0x0026
+#define AVB_AECP_AEM_CMD_GET_AVB_INFO 0x0027
+#define AVB_AECP_AEM_CMD_GET_AS_PATH 0x0028
+#define AVB_AECP_AEM_CMD_GET_COUNTERS 0x0029
+#define AVB_AECP_AEM_CMD_REBOOT 0x002a
+#define AVB_AECP_AEM_CMD_GET_AUDIO_MAP 0x002b
+#define AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS 0x002c
+#define AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS 0x002d
+#define AVB_AECP_AEM_CMD_GET_VIDEO_MAP 0x002e
+#define AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS 0x002f
+#define AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS 0x0030
+#define AVB_AECP_AEM_CMD_GET_SENSOR_MAP 0x0031
+#define AVB_AECP_AEM_CMD_ADD_SENSOR_MAPPINGS 0x0032
+#define AVB_AECP_AEM_CMD_REMOVE_SENSOR_MAPPINGS 0x0033
+#define AVB_AECP_AEM_CMD_START_OPERATION 0x0034
+#define AVB_AECP_AEM_CMD_ABORT_OPERATION 0x0035
+#define AVB_AECP_AEM_CMD_OPERATION_STATUS 0x0036
+#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY 0x0037
+#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY 0x0038
+#define AVB_AECP_AEM_CMD_AUTH_GET_KEY_LIST 0x0039
+#define AVB_AECP_AEM_CMD_AUTH_GET_KEY 0x003a
+#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY_TO_CHAIN 0x003b
+#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY_FROM_CHAIN 0x003c
+#define AVB_AECP_AEM_CMD_AUTH_GET_KEYCHAIN_LIST 0x003d
+#define AVB_AECP_AEM_CMD_AUTH_GET_IDENTITY 0x003e
+#define AVB_AECP_AEM_CMD_AUTH_ADD_TOKEN 0x003f
+#define AVB_AECP_AEM_CMD_AUTH_DELETE_TOKEN 0x0040
+#define AVB_AECP_AEM_CMD_AUTHENTICATE 0x0041
+#define AVB_AECP_AEM_CMD_DEAUTHENTICATE 0x0042
+#define AVB_AECP_AEM_CMD_ENABLE_TRANSPORT_SECURITY 0x0043
+#define AVB_AECP_AEM_CMD_DISABLE_TRANSPORT_SECURITY 0x0044
+#define AVB_AECP_AEM_CMD_ENABLE_STREAM_ENCRYPTION 0x0045
+#define AVB_AECP_AEM_CMD_DISABLE_STREAM_ENCRYPTION 0x0046
+#define AVB_AECP_AEM_CMD_SET_MEMORY_OBJECT_LENGTH 0x0047
+#define AVB_AECP_AEM_CMD_GET_MEMORY_OBJECT_LENGTH 0x0048
+#define AVB_AECP_AEM_CMD_SET_STREAM_BACKUP 0x0049
+#define AVB_AECP_AEM_CMD_GET_STREAM_BACKUP 0x004a
+#define AVB_AECP_AEM_CMD_EXPANSION 0x7fff
+
+#define AVB_AEM_ACQUIRE_ENTITY_PERSISTENT_FLAG (1<<0)
+
+struct avb_packet_aecp_aem_acquire {
+ uint32_t flags;
+ uint64_t owner_guid;
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_lock {
+ uint32_t flags;
+ uint64_t locked_guid;
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_read_descriptor {
+ uint16_t configuration;
+ uint8_t reserved[2];
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_setget_configuration {
+ uint16_t reserved;
+ uint16_t configuration_index;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_setget_stream_format {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+ uint64_t stream_format;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_setget_video_format {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+ uint32_t format_specific;
+ uint16_t aspect_ratio;
+ uint16_t color_space;
+ uint32_t frame_size;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_setget_sensor_format {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+ uint64_t sensor_format;
+} __attribute__ ((__packed__));
+
+
+#define AVB_AEM_STREAM_INFO_FLAG_CLASS_B (1u<<0)
+#define AVB_AEM_STREAM_INFO_FLAG_FAST_CONNECT (1u<<1)
+#define AVB_AEM_STREAM_INFO_FLAG_SAVED_STATE (1u<<2)
+#define AVB_AEM_STREAM_INFO_FLAG_STREAMING_WAIT (1u<<3)
+#define AVB_AEM_STREAM_INFO_FLAG_ENCRYPTED_PDU (1u<<4)
+#define AVB_AEM_STREAM_INFO_FLAG_STREAM_VLAN_ID_VALID (1u<<25)
+#define AVB_AEM_STREAM_INFO_FLAG_CONNECTED (1u<<26)
+#define AVB_AEM_STREAM_INFO_FLAG_MSRP_FAILURE_VALID (1u<<27)
+#define AVB_AEM_STREAM_INFO_FLAG_STREAM_DEST_MAC_VALID (1u<<28)
+#define AVB_AEM_STREAM_INFO_FLAG_MSRP_ACC_LAT_VALID (1u<<29)
+#define AVB_AEM_STREAM_INFO_FLAG_STREAM_ID_VALID (1u<<30)
+#define AVB_AEM_STREAM_INFO_FLAG_STREAM_FORMAT_VALID (1u<<31)
+
+struct avb_packet_aecp_aem_setget_stream_info {
+ uint16_t descriptor_type;
+ uint16_t descriptor_index;
+ uint32_t aem_stream_info_flags;
+ uint64_t stream_format;
+ uint64_t stream_id;
+ uint32_t msrp_accumulated_latency;
+ uint8_t stream_dest_mac[6];
+ uint8_t msrp_failure_code;
+ uint8_t reserved;
+ uint64_t msrp_failure_bridge_id;
+ uint16_t stream_vlan_id;
+ uint16_t reserved2;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_setget_name {
+ uint16_t descriptor_type;
+ uint16_t descriptor_index;
+ uint16_t name_index;
+ uint16_t configuration_index;
+ char name[64];
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_setget_association_id {
+ uint16_t descriptor_type;
+ uint16_t descriptor_index;
+ uint64_t association_id;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_setget_sampling_rate {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+ uint32_t sampling_rate;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_setget_clock_source {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+ uint16_t clock_source_index;
+ uint16_t reserved;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_setget_control {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_incdec_control {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+ uint16_t index_count;
+ uint16_t reserved;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_setget_signal_selector {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+ uint16_t signal_type;
+ uint16_t signal_index;
+ uint16_t signal_output;
+ uint16_t reserved;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_setget_mixer {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_setget_matrix {
+ uint16_t descriptor_type;
+ uint16_t descriptor_index;
+ uint16_t matrix_column;
+ uint16_t matrix_row;
+ uint16_t region_width;
+ uint16_t region_height;
+ uint16_t rep_direction_value_count;
+ uint16_t item_offset;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_startstop_streaming {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_identify_notification {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_msrp_mapping {
+ uint8_t traffic_class;
+ uint8_t priority;
+ uint16_t vlan_id;
+} __attribute__ ((__packed__));
+
+#define AVB_AEM_AVB_INFO_FLAG_GPTP_GRANDMASTER_SUPPORTED (1u<<0)
+#define AVB_AEM_AVB_INFO_FLAG_GPTP_ENABLED (1u<<1)
+#define AVB_AEM_AVB_INFO_FLAG_SRP_ENABLED (1u<<2)
+
+struct avb_packet_aecp_aem_get_avb_info {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+ uint64_t gptp_grandmaster_id;
+ uint32_t propagation_delay;
+ uint8_t gptp_domain_number;
+ uint8_t flags;
+ uint16_t msrp_mappings_count;
+ uint8_t msrp_mappings[0];
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_get_as_path {
+ uint16_t descriptor_index;
+ uint16_t reserved;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_get_counters {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+ uint32_t counters_valid;
+ uint8_t counters_block[0];
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_reboot {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_start_operation {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+ uint16_t operation_id;
+ uint16_t operation_type;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem_operation_status {
+ uint16_t descriptor_type;
+ uint16_t descriptor_id;
+ uint16_t operation_id;
+ uint16_t percent_complete;
+} __attribute__ ((__packed__));
+
+struct avb_packet_aecp_aem {
+ struct avb_packet_aecp_header aecp;
+#if __BYTE_ORDER == __BIG_ENDIAN
+ unsigned u:1;
+ unsigned cmd1:7;
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+ unsigned cmd1:7;
+ unsigned u:1;
+#endif
+ uint8_t cmd2;
+ uint8_t payload[0];
+} __attribute__ ((__packed__));
+
+#define AVB_PACKET_AEM_SET_COMMAND_TYPE(p,v) ((p)->cmd1 = ((v) >> 8),(p)->cmd2 = (v))
+
+#define AVB_PACKET_AEM_GET_COMMAND_TYPE(p) ((p)->cmd1 << 8 | (p)->cmd2)
+
+int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len);
+int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len);
+
+#endif /* AVB_AEM_H */
diff --git a/src/modules/module-avb/aecp.c b/src/modules/module-avb/aecp.c
new file mode 100644
index 0000000..d581f81
--- /dev/null
+++ b/src/modules/module-avb/aecp.c
@@ -0,0 +1,168 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/json.h>
+#include <spa/debug/mem.h>
+
+#include <pipewire/pipewire.h>
+
+#include "aecp.h"
+#include "aecp-aem.h"
+#include "internal.h"
+
+static const uint8_t mac[6] = AVB_BROADCAST_MAC;
+
+struct msg_info {
+ uint16_t type;
+ const char *name;
+ int (*handle) (struct aecp *aecp, const void *p, int len);
+};
+
+static int reply_not_implemented(struct aecp *aecp, const void *p, int len)
+{
+ struct server *server = aecp->server;
+ uint8_t buf[len];
+ struct avb_ethernet_header *h = (void*)buf;
+ struct avb_packet_aecp_header *reply = SPA_PTROFF(h, sizeof(*h), void);
+
+ memcpy(h, p, len);
+ AVB_PACKET_AECP_SET_STATUS(reply, AVB_AECP_STATUS_NOT_IMPLEMENTED);
+
+ return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len);
+}
+
+static const struct msg_info msg_info[] = {
+ { AVB_AECP_MESSAGE_TYPE_AEM_COMMAND, "aem-command", avb_aecp_aem_handle_command, },
+ { AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE, "aem-response", avb_aecp_aem_handle_response, },
+ { AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_COMMAND, "address-access-command", NULL, },
+ { AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_RESPONSE, "address-access-response", NULL, },
+ { AVB_AECP_MESSAGE_TYPE_AVC_COMMAND, "avc-command", NULL, },
+ { AVB_AECP_MESSAGE_TYPE_AVC_RESPONSE, "avc-response", NULL, },
+ { AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_COMMAND, "vendor-unique-command", NULL, },
+ { AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_RESPONSE, "vendor-unique-response", NULL, },
+ { AVB_AECP_MESSAGE_TYPE_EXTENDED_COMMAND, "extended-command", NULL, },
+ { AVB_AECP_MESSAGE_TYPE_EXTENDED_RESPONSE, "extended-response", NULL, },
+};
+
+static inline const struct msg_info *find_msg_info(uint16_t type, const char *name)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(msg_info, i) {
+ if ((name == NULL && type == i->type) ||
+ (name != NULL && spa_streq(name, i->name)))
+ return i;
+ }
+ return NULL;
+}
+
+static int aecp_message(void *data, uint64_t now, const void *message, int len)
+{
+ struct aecp *aecp = data;
+ struct server *server = aecp->server;
+ const struct avb_ethernet_header *h = message;
+ const struct avb_packet_aecp_header *p = SPA_PTROFF(h, sizeof(*h), void);
+ const struct msg_info *info;
+ int message_type;
+
+ if (ntohs(h->type) != AVB_TSN_ETH)
+ return 0;
+ if (memcmp(h->dest, mac, 6) != 0 &&
+ memcmp(h->dest, server->mac_addr, 6) != 0)
+ return 0;
+ if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_AECP)
+ return 0;
+
+ message_type = AVB_PACKET_AECP_GET_MESSAGE_TYPE(p);
+
+ info = find_msg_info(message_type, NULL);
+ if (info == NULL)
+ return reply_not_implemented(aecp, message, len);
+
+ pw_log_debug("got AECP message %s", info->name);
+
+ if (info->handle == NULL)
+ return reply_not_implemented(aecp, message, len);
+
+ return info->handle(aecp, message, len);
+}
+
+static void aecp_destroy(void *data)
+{
+ struct aecp *aecp = data;
+ spa_hook_remove(&aecp->server_listener);
+ free(aecp);
+}
+
+static int do_help(struct aecp *aecp, const char *args, FILE *out)
+{
+ fprintf(out, "{ \"type\": \"help\","
+ "\"text\": \""
+ "/adp/help: this help \\n"
+ "\" }");
+ return 0;
+}
+
+static int aecp_command(void *data, uint64_t now, const char *command, const char *args, FILE *out)
+{
+ struct aecp *aecp = data;
+ int res;
+
+ if (!spa_strstartswith(command, "/aecp/"))
+ return 0;
+
+ command += strlen("/aecp/");
+
+ if (spa_streq(command, "help"))
+ res = do_help(aecp, args, out);
+ else
+ res = -ENOTSUP;
+
+ return res;
+}
+
+static const struct server_events server_events = {
+ AVB_VERSION_SERVER_EVENTS,
+ .destroy = aecp_destroy,
+ .message = aecp_message,
+ .command = aecp_command
+};
+
+struct avb_aecp *avb_aecp_register(struct server *server)
+{
+ struct aecp *aecp;
+
+ aecp = calloc(1, sizeof(*aecp));
+ if (aecp == NULL)
+ return NULL;
+
+ aecp->server = server;
+
+ avdecc_server_add_listener(server, &aecp->server_listener, &server_events, aecp);
+
+ return (struct avb_aecp*)aecp;
+}
+
+void avb_aecp_unregister(struct avb_aecp *aecp)
+{
+ aecp_destroy(aecp);
+}
diff --git a/src/modules/module-avb/aecp.h b/src/modules/module-avb/aecp.h
new file mode 100644
index 0000000..a3515f0
--- /dev/null
+++ b/src/modules/module-avb/aecp.h
@@ -0,0 +1,60 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_AECP_H
+#define AVB_AECP_H
+
+#include "packets.h"
+#include "internal.h"
+
+#define AVB_AECP_MESSAGE_TYPE_AEM_COMMAND 0
+#define AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE 1
+#define AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_COMMAND 2
+#define AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_RESPONSE 3
+#define AVB_AECP_MESSAGE_TYPE_AVC_COMMAND 4
+#define AVB_AECP_MESSAGE_TYPE_AVC_RESPONSE 5
+#define AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_COMMAND 6
+#define AVB_AECP_MESSAGE_TYPE_VENDOR_UNIQUE_RESPONSE 7
+#define AVB_AECP_MESSAGE_TYPE_EXTENDED_COMMAND 14
+#define AVB_AECP_MESSAGE_TYPE_EXTENDED_RESPONSE 15
+
+#define AVB_AECP_STATUS_SUCCESS 0
+#define AVB_AECP_STATUS_NOT_IMPLEMENTED 1
+
+struct avb_packet_aecp_header {
+ struct avb_packet_header hdr;
+ uint64_t target_guid;
+ uint64_t controller_guid;
+ uint16_t sequence_id;
+} __attribute__ ((__packed__));
+
+#define AVB_PACKET_AECP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v)
+#define AVB_PACKET_AECP_SET_STATUS(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v)
+
+#define AVB_PACKET_AECP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr)
+#define AVB_PACKET_AECP_GET_STATUS(p) AVB_PACKET_GET_SUB2(&(p)->hdr)
+
+struct avb_aecp *avb_aecp_register(struct server *server);
+
+#endif /* AVB_AECP_H */
diff --git a/src/modules/module-avb/avb.c b/src/modules/module-avb/avb.c
new file mode 100644
index 0000000..2afdc21
--- /dev/null
+++ b/src/modules/module-avb/avb.c
@@ -0,0 +1,106 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "internal.h"
+
+#include <spa/support/cpu.h>
+
+struct pw_avb *pw_avb_new(struct pw_context *context,
+ struct pw_properties *props, size_t user_data_size)
+{
+ struct impl *impl;
+ const struct spa_support *support;
+ uint32_t n_support;
+ struct spa_cpu *cpu;
+ const char *str;
+ int res = 0;
+
+ impl = calloc(1, sizeof(*impl) + user_data_size);
+ if (impl == NULL)
+ goto error_exit;
+
+ if (props == NULL)
+ props = pw_properties_new(NULL, NULL);
+ if (props == NULL)
+ goto error_free;
+
+ support = pw_context_get_support(context, &n_support);
+ cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU);
+
+ pw_context_conf_update_props(context, "avb.properties", props);
+
+ if ((str = pw_properties_get(props, "vm.overrides")) != NULL) {
+ if (cpu != NULL && spa_cpu_get_vm_type(cpu) != SPA_CPU_VM_NONE)
+ pw_properties_update_string(props, str, strlen(str));
+ pw_properties_set(props, "vm.overrides", NULL);
+ }
+
+ impl->context = context;
+ impl->loop = pw_context_get_main_loop(context);
+ impl->props = props;
+ impl->core = pw_context_get_object(context, PW_TYPE_INTERFACE_Core);
+ if (impl->core == NULL) {
+ str = pw_properties_get(props, PW_KEY_REMOTE_NAME);
+ impl->core = pw_context_connect(context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ impl->do_disconnect = true;
+ }
+ if (impl->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto error_free;
+ }
+
+ spa_list_init(&impl->servers);
+
+ avdecc_server_new(impl, &props->dict);
+
+ return (struct pw_avb*)impl;
+
+error_free:
+ free(impl);
+error_exit:
+ pw_properties_free(props);
+ if (res < 0)
+ errno = -res;
+ return NULL;
+}
+
+static void impl_free(struct impl *impl)
+{
+ struct server *s;
+
+ spa_list_consume(s, &impl->servers, link)
+ avdecc_server_free(s);
+ free(impl);
+}
+
+void pw_avb_destroy(struct pw_avb *avb)
+{
+ struct impl *impl = (struct impl*)avb;
+ impl_free(impl);
+}
diff --git a/src/modules/module-avb/avb.h b/src/modules/module-avb/avb.h
new file mode 100644
index 0000000..cad7dd2
--- /dev/null
+++ b/src/modules/module-avb/avb.h
@@ -0,0 +1,44 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_AVB_H
+#define PIPEWIRE_AVB_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct pw_context;
+struct pw_properties;
+struct pw_avb;
+
+struct pw_avb *pw_avb_new(struct pw_context *context,
+ struct pw_properties *props, size_t user_data_size);
+void pw_avb_destroy(struct pw_avb *avb);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_AVB_H */
diff --git a/src/modules/module-avb/avdecc.c b/src/modules/module-avb/avdecc.c
new file mode 100644
index 0000000..308ba48
--- /dev/null
+++ b/src/modules/module-avb/avdecc.c
@@ -0,0 +1,335 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <linux/filter.h>
+#include <linux/net_tstamp.h>
+#include <limits.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <spa/support/cpu.h>
+#include <spa/debug/mem.h>
+
+#include <pipewire/pipewire.h>
+
+#include "avb.h"
+#include "packets.h"
+#include "internal.h"
+#include "stream.h"
+#include "acmp.h"
+#include "adp.h"
+#include "aecp.h"
+#include "maap.h"
+#include "mmrp.h"
+#include "msrp.h"
+#include "mvrp.h"
+#include "descriptors.h"
+#include "utils.h"
+
+#define DEFAULT_INTERVAL 1
+
+#define server_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct server_events, m, v, ##__VA_ARGS__)
+#define server_emit_destroy(s) server_emit(s, destroy, 0)
+#define server_emit_message(s,n,m,l) server_emit(s, message, 0, n, m, l)
+#define server_emit_periodic(s,n) server_emit(s, periodic, 0, n)
+#define server_emit_command(s,n,c,a,f) server_emit(s, command, 0, n, c, a, f)
+
+static void on_timer_event(void *data, uint64_t expirations)
+{
+ struct server *server = data;
+ struct timespec now;
+ clock_gettime(CLOCK_REALTIME, &now);
+ server_emit_periodic(server, SPA_TIMESPEC_TO_NSEC(&now));
+}
+
+static void on_socket_data(void *data, int fd, uint32_t mask)
+{
+ struct server *server = data;
+ struct timespec now;
+
+ if (mask & SPA_IO_IN) {
+ int len;
+ uint8_t buffer[2048];
+
+ len = recv(fd, buffer, sizeof(buffer), 0);
+
+ if (len < 0) {
+ pw_log_warn("got recv error: %m");
+ }
+ else if (len < (int)sizeof(struct avb_packet_header)) {
+ pw_log_warn("short packet received (%d < %d)", len,
+ (int)sizeof(struct avb_packet_header));
+ } else {
+ clock_gettime(CLOCK_REALTIME, &now);
+ server_emit_message(server, SPA_TIMESPEC_TO_NSEC(&now), buffer, len);
+ }
+ }
+}
+
+int avb_server_send_packet(struct server *server, const uint8_t dest[6],
+ uint16_t type, void *data, size_t size)
+{
+ struct avb_ethernet_header *hdr = (struct avb_ethernet_header*)data;
+ int res = 0;
+
+ memcpy(hdr->dest, dest, ETH_ALEN);
+ memcpy(hdr->src, server->mac_addr, ETH_ALEN);
+ hdr->type = htons(type);
+
+ if (send(server->source->fd, data, size, 0) < 0) {
+ res = -errno;
+ pw_log_warn("got send error: %m");
+ }
+ return res;
+}
+
+static int load_filter(int fd, uint16_t eth, const uint8_t dest[6], const uint8_t mac[6])
+{
+ struct sock_fprog filter;
+ struct sock_filter bpf_code[] = {
+ BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 12),
+ BPF_JUMP(BPF_JMP|BPF_JEQ, eth, 0, 8),
+ BPF_STMT(BPF_LD|BPF_W|BPF_ABS, 2),
+ BPF_JUMP(BPF_JMP|BPF_JEQ, (dest[2] << 24) |
+ (dest[3] << 16) |
+ (dest[4] << 8) |
+ (dest[5]), 0, 2),
+ BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 0),
+ BPF_JUMP(BPF_JMP|BPF_JEQ, (dest[0] << 8) |
+ (dest[1]), 3, 4),
+ BPF_JUMP(BPF_JMP|BPF_JEQ, (mac[2] << 24) |
+ (mac[3] << 16) |
+ (mac[4] << 8) |
+ (mac[5]), 0, 3),
+ BPF_STMT(BPF_LD|BPF_H|BPF_ABS, 0),
+ BPF_JUMP(BPF_JMP|BPF_JEQ, (mac[0] << 8) |
+ (mac[1]), 0, 1),
+ BPF_STMT(BPF_RET, 0x00040000),
+ BPF_STMT(BPF_RET, 0x00000000),
+ };
+ filter.len = sizeof(bpf_code) / 8;
+ filter.filter = bpf_code;
+
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER,
+ &filter, sizeof(filter)) < 0) {
+ pw_log_error("setsockopt(ATTACH_FILTER) failed: %m");
+ return -errno;
+ }
+ return 0;
+}
+
+int avb_server_make_socket(struct server *server, uint16_t type, const uint8_t mac[6])
+{
+ int fd, res;
+ struct ifreq req;
+ struct packet_mreq mreq;
+ struct sockaddr_ll sll;
+
+ fd = socket(AF_PACKET, SOCK_RAW|SOCK_NONBLOCK, htons(ETH_P_ALL));
+ if (fd < 0) {
+ pw_log_error("socket() failed: %m");
+ return -errno;
+ }
+
+ spa_zero(req);
+ snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname);
+ if (ioctl(fd, SIOCGIFINDEX, &req) < 0) {
+ res = -errno;
+ pw_log_error("SIOCGIFINDEX %s failed: %m", server->ifname);
+ goto error_close;
+ }
+ server->ifindex = req.ifr_ifindex;
+
+ spa_zero(req);
+ snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname);
+ if (ioctl(fd, SIOCGIFHWADDR, &req) < 0) {
+ res = -errno;
+ pw_log_error("SIOCGIFHWADDR %s failed: %m", server->ifname);
+ goto error_close;
+ }
+ memcpy(server->mac_addr, req.ifr_hwaddr.sa_data, sizeof(server->mac_addr));
+
+ server->entity_id = (uint64_t)server->mac_addr[0] << 56 |
+ (uint64_t)server->mac_addr[1] << 48 |
+ (uint64_t)server->mac_addr[2] << 40 |
+ (uint64_t)0xff << 32 |
+ (uint64_t)0xfe << 24 |
+ (uint64_t)server->mac_addr[3] << 16 |
+ (uint64_t)server->mac_addr[4] << 8 |
+ (uint64_t)server->mac_addr[5];
+
+ spa_zero(sll);
+ sll.sll_family = AF_PACKET;
+ sll.sll_protocol = htons(ETH_P_ALL);
+ sll.sll_ifindex = server->ifindex;
+ if (bind(fd, (struct sockaddr *) &sll, sizeof(sll)) < 0) {
+ res = -errno;
+ pw_log_error("bind() failed: %m");
+ goto error_close;
+ }
+
+ spa_zero(mreq);
+ mreq.mr_ifindex = server->ifindex;
+ mreq.mr_type = PACKET_MR_MULTICAST;
+ mreq.mr_alen = ETH_ALEN;
+ memcpy(mreq.mr_address, mac, ETH_ALEN);
+
+ if (setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP,
+ &mreq, sizeof(mreq)) < 0) {
+ res = -errno;
+ pw_log_error("setsockopt(ADD_MEMBERSHIP) failed: %m");
+ goto error_close;
+ }
+
+ if ((res = load_filter(fd, type, mac, server->mac_addr)) < 0)
+ goto error_close;
+
+ return fd;
+
+error_close:
+ close(fd);
+ return res;
+}
+
+static int setup_socket(struct server *server)
+{
+ struct impl *impl = server->impl;
+ int fd, res;
+ static const uint8_t bmac[6] = AVB_BROADCAST_MAC;
+ struct timespec value, interval;
+
+ fd = avb_server_make_socket(server, AVB_TSN_ETH, bmac);
+ if (fd < 0)
+ return fd;
+
+ pw_log_info("0x%"PRIx64" %d", server->entity_id, server->ifindex);
+
+ server->source = pw_loop_add_io(impl->loop, fd, SPA_IO_IN, true, on_socket_data, server);
+ if (server->source == NULL) {
+ res = -errno;
+ pw_log_error("server %p: can't create server source: %m", impl);
+ goto error_no_source;
+ }
+ server->timer = pw_loop_add_timer(impl->loop, on_timer_event, server);
+ if (server->timer == NULL) {
+ res = -errno;
+ pw_log_error("server %p: can't create timer source: %m", impl);
+ goto error_no_timer;
+ }
+ value.tv_sec = 0;
+ value.tv_nsec = 1;
+ interval.tv_sec = DEFAULT_INTERVAL;
+ interval.tv_nsec = 0;
+ pw_loop_update_timer(impl->loop, server->timer, &value, &interval, false);
+
+ return 0;
+
+error_no_timer:
+ pw_loop_destroy_source(impl->loop, server->source);
+ server->source = NULL;
+error_no_source:
+ close(fd);
+ return res;
+}
+
+struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props)
+{
+ struct server *server;
+ int res = 0;
+
+ server = calloc(1, sizeof(*server));
+ if (server == NULL)
+ return NULL;
+
+ server->impl = impl;
+ spa_list_append(&impl->servers, &server->link);
+ server->ifname = strdup(spa_dict_lookup(props, "ifname"));
+ spa_hook_list_init(&server->listener_list);
+ spa_list_init(&server->descriptors);
+ spa_list_init(&server->streams);
+
+ server->debug_messages = false;
+
+ if ((res = setup_socket(server)) < 0)
+ goto error_free;
+
+ init_descriptors(server);
+
+ server->mrp = avb_mrp_new(server);
+ if (server->mrp == NULL)
+ goto error_free;
+
+ avb_aecp_register(server);
+ server->maap = avb_maap_register(server);
+ server->mmrp = avb_mmrp_register(server);
+ server->msrp = avb_msrp_register(server);
+ server->mvrp = avb_mvrp_register(server);
+ avb_adp_register(server);
+ avb_acmp_register(server);
+
+ server->domain_attr = avb_msrp_attribute_new(server->msrp,
+ AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN);
+ server->domain_attr->attr.domain.sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT;
+ server->domain_attr->attr.domain.sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT;
+ server->domain_attr->attr.domain.sr_class_vid = htons(AVB_DEFAULT_VLAN);
+
+ avb_mrp_attribute_begin(server->domain_attr->mrp, 0);
+ avb_mrp_attribute_join(server->domain_attr->mrp, 0, true);
+
+ server_create_stream(server, SPA_DIRECTION_INPUT, 0);
+ server_create_stream(server, SPA_DIRECTION_OUTPUT, 0);
+
+ avb_maap_reserve(server->maap, 1);
+
+ return server;
+
+error_free:
+ free(server);
+ if (res < 0)
+ errno = -res;
+ return NULL;
+}
+
+void avdecc_server_add_listener(struct server *server, struct spa_hook *listener,
+ const struct server_events *events, void *data)
+{
+ spa_hook_list_append(&server->listener_list, listener, events, data);
+}
+
+void avdecc_server_free(struct server *server)
+{
+ struct impl *impl = server->impl;
+
+ spa_list_remove(&server->link);
+ if (server->source)
+ pw_loop_destroy_source(impl->loop, server->source);
+ if (server->timer)
+ pw_loop_destroy_source(impl->loop, server->source);
+ spa_hook_list_clean(&server->listener_list);
+ free(server);
+}
diff --git a/src/modules/module-avb/descriptors.h b/src/modules/module-avb/descriptors.h
new file mode 100644
index 0000000..56397e3
--- /dev/null
+++ b/src/modules/module-avb/descriptors.h
@@ -0,0 +1,274 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "aecp-aem.h"
+#include "aecp-aem-descriptors.h"
+#include "internal.h"
+
+void init_descriptors(struct server *server)
+{
+ server_add_descriptor(server, AVB_AEM_DESC_STRINGS, 0,
+ sizeof(struct avb_aem_desc_strings),
+ &(struct avb_aem_desc_strings)
+ {
+ .string_0 = "PipeWire",
+ .string_1 = "Configuration 1",
+ .string_2 = "Wim Taymans",
+ });
+ server_add_descriptor(server, AVB_AEM_DESC_LOCALE, 0,
+ sizeof(struct avb_aem_desc_locale),
+ &(struct avb_aem_desc_locale)
+ {
+ .locale_identifier = "en-EN",
+ .number_of_strings = htons(1),
+ .base_strings = htons(0)
+ });
+ server_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0,
+ sizeof(struct avb_aem_desc_entity),
+ &(struct avb_aem_desc_entity)
+ {
+ .entity_id = htobe64(server->entity_id),
+ .entity_model_id = htobe64(0),
+ .entity_capabilities = htonl(
+ AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED |
+ AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED |
+ AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED |
+ AVB_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID |
+ AVB_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID),
+
+ .talker_stream_sources = htons(8),
+ .talker_capabilities = htons(
+ AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED |
+ AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE),
+ .listener_stream_sinks = htons(8),
+ .listener_capabilities = htons(
+ AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED |
+ AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK),
+ .controller_capabilities = htons(0),
+ .available_index = htonl(0),
+ .association_id = htobe64(0),
+ .entity_name = "PipeWire",
+ .vendor_name_string = htons(2),
+ .model_name_string = htons(0),
+ .firmware_version = "0.3.48",
+ .group_name = "",
+ .serial_number = "",
+ .configurations_count = htons(1),
+ .current_configuration = htons(0)
+ });
+ struct {
+ struct avb_aem_desc_configuration desc;
+ struct avb_aem_desc_descriptor_count descriptor_counts[8];
+ } __attribute__ ((__packed__)) config =
+ {
+ {
+ .object_name = "Configuration 1",
+ .localized_description = htons(1),
+ .descriptor_counts_count = htons(8),
+ .descriptor_counts_offset = htons(
+ 4 + sizeof(struct avb_aem_desc_configuration)),
+ },
+ .descriptor_counts = {
+ { htons(AVB_AEM_DESC_AUDIO_UNIT), htons(1) },
+ { htons(AVB_AEM_DESC_STREAM_INPUT), htons(1) },
+ { htons(AVB_AEM_DESC_STREAM_OUTPUT), htons(1) },
+ { htons(AVB_AEM_DESC_AVB_INTERFACE), htons(1) },
+ { htons(AVB_AEM_DESC_CLOCK_SOURCE), htons(1) },
+ { htons(AVB_AEM_DESC_CONTROL), htons(2) },
+ { htons(AVB_AEM_DESC_LOCALE), htons(1) },
+ { htons(AVB_AEM_DESC_CLOCK_DOMAIN), htons(1) }
+ }
+ };
+ server_add_descriptor(server, AVB_AEM_DESC_CONFIGURATION, 0,
+ sizeof(config), &config);
+
+ struct {
+ struct avb_aem_desc_audio_unit desc;
+ struct avb_aem_desc_sampling_rate sampling_rates[6];
+ } __attribute__ ((__packed__)) audio_unit =
+ {
+ {
+ .object_name = "PipeWire",
+ .localized_description = htons(0),
+ .clock_domain_index = htons(0),
+ .number_of_stream_input_ports = htons(1),
+ .base_stream_input_port = htons(0),
+ .number_of_stream_output_ports = htons(1),
+ .base_stream_output_port = htons(0),
+ .number_of_external_input_ports = htons(8),
+ .base_external_input_port = htons(0),
+ .number_of_external_output_ports = htons(8),
+ .base_external_output_port = htons(0),
+ .number_of_internal_input_ports = htons(0),
+ .base_internal_input_port = htons(0),
+ .number_of_internal_output_ports = htons(0),
+ .base_internal_output_port = htons(0),
+ .number_of_controls = htons(0),
+ .base_control = htons(0),
+ .number_of_signal_selectors = htons(0),
+ .base_signal_selector = htons(0),
+ .number_of_mixers = htons(0),
+ .base_mixer = htons(0),
+ .number_of_matrices = htons(0),
+ .base_matrix = htons(0),
+ .number_of_splitters = htons(0),
+ .base_splitter = htons(0),
+ .number_of_combiners = htons(0),
+ .base_combiner = htons(0),
+ .number_of_demultiplexers = htons(0),
+ .base_demultiplexer = htons(0),
+ .number_of_multiplexers = htons(0),
+ .base_multiplexer = htons(0),
+ .number_of_transcoders = htons(0),
+ .base_transcoder = htons(0),
+ .number_of_control_blocks = htons(0),
+ .base_control_block = htons(0),
+ .current_sampling_rate = htonl(48000),
+ .sampling_rates_offset = htons(
+ 4 + sizeof(struct avb_aem_desc_audio_unit)),
+ .sampling_rates_count = htons(6),
+ },
+ .sampling_rates = {
+ { .pull_frequency = htonl(44100) },
+ { .pull_frequency = htonl(48000) },
+ { .pull_frequency = htonl(88200) },
+ { .pull_frequency = htonl(96000) },
+ { .pull_frequency = htonl(176400) },
+ { .pull_frequency = htonl(192000) },
+ }
+ };
+ server_add_descriptor(server, AVB_AEM_DESC_AUDIO_UNIT, 0,
+ sizeof(audio_unit), &audio_unit);
+
+ struct {
+ struct avb_aem_desc_stream desc;
+ uint64_t stream_formats[6];
+ } __attribute__ ((__packed__)) stream_input_0 =
+ {
+ {
+ .object_name = "Stream Input 1",
+ .localized_description = htons(0xffff),
+ .clock_domain_index = htons(0),
+ .stream_flags = htons(
+ AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE |
+ AVB_AEM_DESC_STREAM_FLAG_CLASS_A),
+ .current_format = htobe64(0x00a0020840000800ULL),
+ .formats_offset = htons(
+ 4 + sizeof(struct avb_aem_desc_stream)),
+ .number_of_formats = htons(6),
+ .backup_talker_entity_id_0 = htobe64(0),
+ .backup_talker_unique_id_0 = htons(0),
+ .backup_talker_entity_id_1 = htobe64(0),
+ .backup_talker_unique_id_1 = htons(0),
+ .backup_talker_entity_id_2 = htobe64(0),
+ .backup_talker_unique_id_2 = htons(0),
+ .backedup_talker_entity_id = htobe64(0),
+ .backedup_talker_unique = htons(0),
+ .avb_interface_index = htons(0),
+ .buffer_length = htons(8)
+ },
+ .stream_formats = {
+ htobe64(0x00a0010860000800ULL),
+ htobe64(0x00a0020860000800ULL),
+ htobe64(0x00a0030860000800ULL),
+ htobe64(0x00a0040860000800ULL),
+ htobe64(0x00a0050860000800ULL),
+ htobe64(0x00a0060860000800ULL),
+ },
+ };
+ server_add_descriptor(server, AVB_AEM_DESC_STREAM_INPUT, 0,
+ sizeof(stream_input_0), &stream_input_0);
+
+ struct {
+ struct avb_aem_desc_stream desc;
+ uint64_t stream_formats[6];
+ } __attribute__ ((__packed__)) stream_output_0 =
+ {
+ {
+ .object_name = "Stream Output 1",
+ .localized_description = htons(0xffff),
+ .clock_domain_index = htons(0),
+ .stream_flags = htons(
+ AVB_AEM_DESC_STREAM_FLAG_CLASS_A),
+ .current_format = htobe64(0x00a0020840000800ULL),
+ .formats_offset = htons(
+ 4 + sizeof(struct avb_aem_desc_stream)),
+ .number_of_formats = htons(6),
+ .backup_talker_entity_id_0 = htobe64(0),
+ .backup_talker_unique_id_0 = htons(0),
+ .backup_talker_entity_id_1 = htobe64(0),
+ .backup_talker_unique_id_1 = htons(0),
+ .backup_talker_entity_id_2 = htobe64(0),
+ .backup_talker_unique_id_2 = htons(0),
+ .backedup_talker_entity_id = htobe64(0),
+ .backedup_talker_unique = htons(0),
+ .avb_interface_index = htons(0),
+ .buffer_length = htons(8)
+ },
+ .stream_formats = {
+ htobe64(0x00a0010860000800ULL),
+ htobe64(0x00a0020860000800ULL),
+ htobe64(0x00a0030860000800ULL),
+ htobe64(0x00a0040860000800ULL),
+ htobe64(0x00a0050860000800ULL),
+ htobe64(0x00a0060860000800ULL),
+ },
+ };
+ server_add_descriptor(server, AVB_AEM_DESC_STREAM_OUTPUT, 0,
+ sizeof(stream_output_0), &stream_output_0);
+
+ struct avb_aem_desc_avb_interface avb_interface = {
+ .localized_description = htons(0xffff),
+ .interface_flags = htons(
+ AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED),
+ .clock_identity = htobe64(0),
+ .priority1 = 0,
+ .clock_class = 0,
+ .offset_scaled_log_variance = htons(0),
+ .clock_accuracy = 0,
+ .priority2 = 0,
+ .domain_number = 0,
+ .log_sync_interval = 0,
+ .log_announce_interval = 0,
+ .log_pdelay_interval = 0,
+ .port_number = 0,
+ };
+ strncpy(avb_interface.object_name, server->ifname, 63);
+ memcpy(avb_interface.mac_address, server->mac_addr, 6);
+ server_add_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0,
+ sizeof(avb_interface), &avb_interface);
+
+ struct avb_aem_desc_clock_source clock_source = {
+ .object_name = "Stream Clock",
+ .localized_description = htons(0xffff),
+ .clock_source_flags = htons(0),
+ .clock_source_type = htons(
+ AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM),
+ .clock_source_identifier = htobe64(0),
+ .clock_source_location_type = htons(AVB_AEM_DESC_STREAM_INPUT),
+ .clock_source_location_index = htons(0),
+ };
+ server_add_descriptor(server, AVB_AEM_DESC_CLOCK_SOURCE, 0,
+ sizeof(clock_source), &clock_source);
+}
diff --git a/src/modules/module-avb/iec61883.h b/src/modules/module-avb/iec61883.h
new file mode 100644
index 0000000..6ca8724
--- /dev/null
+++ b/src/modules/module-avb/iec61883.h
@@ -0,0 +1,110 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_IEC61883_H
+#define AVB_IEC61883_H
+
+#include "packets.h"
+
+struct avb_packet_iec61883 {
+ uint8_t subtype;
+#if __BYTE_ORDER == __BIG_ENDIAN
+ unsigned sv:1;
+ unsigned version:3;
+ unsigned mr:1;
+ unsigned _r1:1;
+ unsigned gv:1;
+ unsigned tv:1;
+
+ uint8_t seq_num;
+
+ unsigned _r2:7;
+ unsigned tu:1;
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+ unsigned tv:1;
+ unsigned gv:1;
+ unsigned _r1:1;
+ unsigned mr:1;
+ unsigned version:3;
+ unsigned sv:1;
+
+ uint8_t seq_num;
+
+ unsigned tu:1;
+ unsigned _r2:7;
+#endif
+ uint64_t stream_id;
+ uint32_t timestamp;
+ uint32_t gateway_info;
+ uint16_t data_len;
+#if __BYTE_ORDER == __BIG_ENDIAN
+ uint8_t tag:2;
+ uint8_t channel:6;
+
+ uint8_t tcode:4;
+ uint8_t app:4;
+
+ uint8_t qi1:2; /* CIP Quadlet Indicator 1 */
+ uint8_t sid:6; /* CIP Source ID */
+
+ uint8_t dbs; /* CIP Data Block Size */
+
+ uint8_t fn:2; /* CIP Fraction Number */
+ uint8_t qpc:3; /* CIP Quadlet Padding Count */
+ uint8_t sph:1; /* CIP Source Packet Header */
+ uint8_t _r3:2;
+
+ uint8_t dbc; /* CIP Data Block Continuity */
+
+ uint8_t qi2:2; /* CIP Quadlet Indicator 2 */
+ uint8_t format_id:6; /* CIP Format ID */
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+ uint8_t channel:6;
+ uint8_t tag:2;
+
+ uint8_t app:4;
+ uint8_t tcode:4;
+
+ uint8_t sid:6; /* CIP Source ID */
+ uint8_t qi1:2; /* CIP Quadlet Indicator 1 */
+
+ uint8_t dbs; /* CIP Data Block Size */
+
+ uint8_t _r3:2;
+ uint8_t sph:1; /* CIP Source Packet Header */
+ uint8_t qpc:3; /* CIP Quadlet Padding Count */
+ uint8_t fn:2; /* CIP Fraction Number */
+
+ uint8_t dbc; /* CIP Data Block Continuity */
+
+ uint8_t format_id:6; /* CIP Format ID */
+ uint8_t qi2:2; /* CIP Quadlet Indicator 2 */
+#endif
+ uint8_t fdf; /* CIP Format Dependent Field */
+ uint16_t syt;
+
+ uint8_t payload[0];
+} __attribute__ ((__packed__));
+
+#endif /* AVB_IEC61883_H */
diff --git a/src/modules/module-avb/internal.h b/src/modules/module-avb/internal.h
new file mode 100644
index 0000000..9d29d92
--- /dev/null
+++ b/src/modules/module-avb/internal.h
@@ -0,0 +1,166 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_INTERNAL_H
+#define AVB_INTERNAL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pipewire/pipewire.h>
+
+struct server;
+struct avb_mrp;
+
+#define AVB_TSN_ETH 0x22f0
+#define AVB_BROADCAST_MAC { 0x91, 0xe0, 0xf0, 0x01, 0x00, 0x00 };
+
+struct impl {
+ struct pw_loop *loop;
+ struct pw_context *context;
+ struct spa_hook context_listener;
+ struct pw_core *core;
+ unsigned do_disconnect:1;
+
+ struct pw_properties *props;
+
+ struct spa_list servers;
+};
+
+struct server_events {
+#define AVB_VERSION_SERVER_EVENTS 0
+ uint32_t version;
+
+ /** the server is destroyed */
+ void (*destroy) (void *data);
+
+ int (*message) (void *data, uint64_t now, const void *message, int len);
+
+ void (*periodic) (void *data, uint64_t now);
+
+ int (*command) (void *data, uint64_t now, const char *command, const char *args, FILE *out);
+};
+
+struct descriptor {
+ struct spa_list link;
+ uint16_t type;
+ uint16_t index;
+ uint32_t size;
+ void *ptr;
+};
+
+struct server {
+ struct spa_list link;
+ struct impl *impl;
+
+ char *ifname;
+ uint8_t mac_addr[6];
+ uint64_t entity_id;
+ int ifindex;
+
+ struct spa_source *source;
+ struct spa_source *timer;
+
+ struct spa_hook_list listener_list;
+
+ struct spa_list descriptors;
+ struct spa_list streams;
+
+ unsigned debug_messages:1;
+
+ struct avb_mrp *mrp;
+ struct avb_mmrp *mmrp;
+ struct avb_mvrp *mvrp;
+ struct avb_msrp *msrp;
+ struct avb_maap *maap;
+
+ struct avb_msrp_attribute *domain_attr;
+};
+
+#include "stream.h"
+
+static inline const struct descriptor *server_find_descriptor(struct server *server,
+ uint16_t type, uint16_t index)
+{
+ struct descriptor *d;
+ spa_list_for_each(d, &server->descriptors, link) {
+ if (d->type == type &&
+ d->index == index)
+ return d;
+ }
+ return NULL;
+}
+static inline void *server_add_descriptor(struct server *server,
+ uint16_t type, uint16_t index, size_t size, void *ptr)
+{
+ struct descriptor *d;
+
+ if ((d = calloc(1, sizeof(struct descriptor) + size)) == NULL)
+ return NULL;
+
+ d->type = type;
+ d->index = index;
+ d->size = size;
+ d->ptr = SPA_PTROFF(d, sizeof(struct descriptor), void);
+ if (ptr)
+ memcpy(d->ptr, ptr, size);
+ spa_list_append(&server->descriptors, &d->link);
+ return d->ptr;
+}
+
+static inline struct stream *server_find_stream(struct server *server,
+ enum spa_direction direction, uint16_t index)
+{
+ struct stream *s;
+ spa_list_for_each(s, &server->streams, link) {
+ if (s->direction == direction &&
+ s->index == index)
+ return s;
+ }
+ return NULL;
+}
+
+struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props);
+void avdecc_server_free(struct server *server);
+
+void avdecc_server_add_listener(struct server *server, struct spa_hook *listener,
+ const struct server_events *events, void *data);
+
+int avb_server_make_socket(struct server *server, uint16_t type, const uint8_t mac[6]);
+
+int avb_server_send_packet(struct server *server, const uint8_t dest[6],
+ uint16_t type, void *data, size_t size);
+
+struct aecp {
+ struct server *server;
+ struct spa_hook server_listener;
+};
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* AVB_INTERNAL_H */
diff --git a/src/modules/module-avb/maap.c b/src/modules/module-avb/maap.c
new file mode 100644
index 0000000..7d195be
--- /dev/null
+++ b/src/modules/module-avb/maap.c
@@ -0,0 +1,469 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <unistd.h>
+
+#include <spa/utils/json.h>
+
+#include <pipewire/pipewire.h>
+
+#include "utils.h"
+#include "maap.h"
+
+#define MAAP_ALLOCATION_POOL_SIZE 0xFE00
+#define MAAP_ALLOCATION_POOL_BASE { 0x91, 0xe0, 0xf0, 0x00, 0x00, 0x00 }
+static uint8_t maap_base[6] = MAAP_ALLOCATION_POOL_BASE;
+
+#define MAAP_PROBE_RETRANSMITS 3
+
+#define MAAP_PROBE_INTERVAL_MS 500
+#define MAAP_PROBE_INTERVAL_VAR_MS 100
+
+#define MAAP_ANNOUNCE_INTERVAL_MS 3000
+#define MAAP_ANNOUNCE_INTERVAL_VAR_MS 2000
+
+struct maap {
+ struct server *server;
+ struct spa_hook server_listener;
+
+ struct pw_properties *props;
+
+ struct spa_source *source;
+
+#define STATE_IDLE 0
+#define STATE_PROBE 1
+#define STATE_ANNOUNCE 2
+ uint32_t state;
+ uint64_t timeout;
+ uint32_t probe_count;
+
+ unsigned short xsubi[3];
+
+ uint16_t offset;
+ uint16_t count;
+};
+
+static const char *message_type_as_string(uint8_t message_type)
+{
+ switch (message_type) {
+ case AVB_MAAP_MESSAGE_TYPE_PROBE:
+ return "PROBE";
+ case AVB_MAAP_MESSAGE_TYPE_DEFEND:
+ return "DEFEND";
+ case AVB_MAAP_MESSAGE_TYPE_ANNOUNCE:
+ return "ANNOUNCE";
+ }
+ return "INVALID";
+}
+
+static void maap_message_debug(struct maap *maap, const struct avb_packet_maap *p)
+{
+ uint32_t v;
+ const uint8_t *addr;
+
+ v = AVB_PACKET_MAAP_GET_MESSAGE_TYPE(p);
+ pw_log_info("message-type: %d (%s)", v, message_type_as_string(v));
+ pw_log_info(" maap-version: %d", AVB_PACKET_MAAP_GET_MAAP_VERSION(p));
+ pw_log_info(" length: %d", AVB_PACKET_GET_LENGTH(&p->hdr));
+
+ pw_log_info(" stream-id: 0x%"PRIx64, AVB_PACKET_MAAP_GET_STREAM_ID(p));
+ addr = AVB_PACKET_MAAP_GET_REQUEST_START(p);
+ pw_log_info(" request-start: %02x:%02x:%02x:%02x:%02x:%02x",
+ addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+ pw_log_info(" request-count: %d", AVB_PACKET_MAAP_GET_REQUEST_COUNT(p));
+ addr = AVB_PACKET_MAAP_GET_CONFLICT_START(p);
+ pw_log_info(" conflict-start: %02x:%02x:%02x:%02x:%02x:%02x",
+ addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+ pw_log_info(" conflict-count: %d", AVB_PACKET_MAAP_GET_CONFLICT_COUNT(p));
+}
+
+#define PROBE_TIMEOUT(n) ((n) + (MAAP_PROBE_INTERVAL_MS + \
+ drand48() * MAAP_PROBE_INTERVAL_VAR_MS) * SPA_NSEC_PER_MSEC)
+#define ANNOUNCE_TIMEOUT(n) ((n) + (MAAP_ANNOUNCE_INTERVAL_MS + \
+ drand48() * MAAP_ANNOUNCE_INTERVAL_VAR_MS) * SPA_NSEC_PER_MSEC)
+
+static int make_new_address(struct maap *maap, uint64_t now, int range)
+{
+ maap->offset = nrand48(maap->xsubi) % (MAAP_ALLOCATION_POOL_SIZE - range);
+ maap->count = range;
+ maap->state = STATE_PROBE;
+ maap->probe_count = MAAP_PROBE_RETRANSMITS;
+ maap->timeout = PROBE_TIMEOUT(now);
+ return 0;
+}
+
+static uint16_t maap_check_conflict(struct maap *maap, const uint8_t request_start[6],
+ uint16_t request_count, uint8_t conflict_start[6])
+{
+ uint16_t our_start, our_end;
+ uint16_t req_start, req_end;
+ uint16_t conf_start, conf_count = 0;
+
+ if (memcmp(request_start, maap_base, 4) != 0)
+ return 0;
+
+ our_start = maap->offset;
+ our_end = our_start + maap->count;
+ req_start = request_start[4] << 8 | request_start[5];
+ req_end = req_start + request_count;
+
+ if (our_start >= req_start && our_start <= req_end) {
+ conf_start = our_start;
+ conf_count = SPA_MIN(our_end, req_end) - our_start;
+ }
+ else if (req_start >= our_start && req_start <= our_end) {
+ conf_start = req_start;
+ conf_count = SPA_MIN(req_end, our_end) - req_start;
+ }
+ if (conf_count == 0)
+ return 0;
+
+ conflict_start[4] = conf_start >> 8;
+ conflict_start[5] = conf_start;
+ return conf_count;
+}
+
+static int send_packet(struct maap *maap, uint64_t now,
+ uint8_t type, const uint8_t conflict_start[6], uint16_t conflict_count)
+{
+ struct avb_ethernet_header *h;
+ struct avb_packet_maap *p;
+ uint8_t buf[1024];
+ uint8_t bmac[6] = AVB_MAAP_MAC;
+ int res = 0;
+ uint8_t start[6];
+
+ spa_memzero(buf, sizeof(buf));
+ h = (void*)buf;
+ p = SPA_PTROFF(h, sizeof(*h), void);
+
+ memcpy(h->dest, bmac, 6);
+ memcpy(h->src, maap->server->mac_addr, 6);
+ h->type = htons(AVB_TSN_ETH);
+
+ p->hdr.subtype = AVB_SUBTYPE_MAAP;
+ AVB_PACKET_SET_LENGTH(&p->hdr, sizeof(*p));
+
+ AVB_PACKET_MAAP_SET_MAAP_VERSION(p, 1);
+ AVB_PACKET_MAAP_SET_MESSAGE_TYPE(p, type);
+
+ memcpy(start, maap_base, 4);
+ start[4] = maap->offset >> 8;
+ start[5] = maap->offset;
+ AVB_PACKET_MAAP_SET_REQUEST_START(p, start);
+ AVB_PACKET_MAAP_SET_REQUEST_COUNT(p, maap->count);
+ if (conflict_count) {
+ AVB_PACKET_MAAP_SET_CONFLICT_START(p, conflict_start);
+ AVB_PACKET_MAAP_SET_CONFLICT_COUNT(p, conflict_count);
+ }
+
+ if (maap->server->debug_messages) {
+ pw_log_info("send: %d (%s)", type, message_type_as_string(type));
+ maap_message_debug(maap, p);
+ }
+
+ if (send(maap->source->fd, p, sizeof(*h) + sizeof(*p), 0) < 0) {
+ res = -errno;
+ pw_log_warn("got send error: %m");
+ }
+ return res;
+}
+
+static int handle_probe(struct maap *maap, uint64_t now, const struct avb_packet_maap *p)
+{
+ uint8_t conflict_start[6];
+ uint16_t conflict_count;
+
+ conflict_count = maap_check_conflict(maap, p->request_start, ntohs(p->request_count),
+ conflict_start);
+ if (conflict_count == 0)
+ return 0;
+
+ switch (maap->state) {
+ case STATE_PROBE:
+ make_new_address(maap, now, 8);
+ break;
+ case STATE_ANNOUNCE:
+ send_packet(maap, now, AVB_MAAP_MESSAGE_TYPE_DEFEND, conflict_start, conflict_count);
+ break;
+ }
+ return 0;
+}
+
+static int handle_defend(struct maap *maap, uint64_t now, const struct avb_packet_maap *p)
+{
+ uint8_t conflict_start[6];
+ uint16_t conflict_count;
+
+ conflict_count = maap_check_conflict(maap, p->conflict_start, ntohs(p->conflict_count),
+ conflict_start);
+ if (conflict_count != 0)
+ make_new_address(maap, now, 8);
+ return 0;
+}
+
+static int maap_message(struct maap *maap, uint64_t now, const void *message, int len)
+{
+ const struct avb_packet_maap *p = message;
+
+ if (AVB_PACKET_GET_SUBTYPE(&p->hdr) != AVB_SUBTYPE_MAAP)
+ return 0;
+
+ if (maap->server->debug_messages)
+ maap_message_debug(maap, p);
+
+ switch (AVB_PACKET_MAAP_GET_MESSAGE_TYPE(p)) {
+ case AVB_MAAP_MESSAGE_TYPE_PROBE:
+ handle_probe(maap, now, p);
+ break;
+ case AVB_MAAP_MESSAGE_TYPE_DEFEND:
+ case AVB_MAAP_MESSAGE_TYPE_ANNOUNCE:
+ handle_defend(maap, now, p);
+ break;
+ }
+ return 0;
+}
+
+static void on_socket_data(void *data, int fd, uint32_t mask)
+{
+ struct maap *maap = data;
+ struct timespec now;
+
+ if (mask & SPA_IO_IN) {
+ int len;
+ uint8_t buffer[2048];
+
+ len = recv(fd, buffer, sizeof(buffer), 0);
+
+ if (len < 0) {
+ pw_log_warn("got recv error: %m");
+ }
+ else if (len < (int)sizeof(struct avb_packet_header)) {
+ pw_log_warn("short packet received (%d < %d)", len,
+ (int)sizeof(struct avb_packet_header));
+ } else {
+ clock_gettime(CLOCK_REALTIME, &now);
+ maap_message(maap, SPA_TIMESPEC_TO_NSEC(&now), buffer, len);
+ }
+ }
+}
+
+static int load_state(struct maap *maap)
+{
+ const char *str;
+ char key[512];
+ struct spa_json it[3];
+ bool have_offset = false;
+ int count = 0, offset = 0;
+
+ snprintf(key, sizeof(key), "maap.%s", maap->server->ifname);
+ pw_conf_load_state("module-avb", key, maap->props);
+
+ if ((str = pw_properties_get(maap->props, "maap.addresses")) == NULL)
+ return 0;
+
+ spa_json_init(&it[0], str, strlen(str));
+ if (spa_json_enter_array(&it[0], &it[1]) <= 0)
+ return 0;
+
+ if (spa_json_enter_object(&it[1], &it[2]) <= 0)
+ return 0;
+
+ while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) {
+ const char *val;
+ int len;
+
+ if ((len = spa_json_next(&it[2], &val)) <= 0)
+ break;
+
+ if (spa_streq(key, "start")) {
+ uint8_t addr[6];
+ if (avb_utils_parse_addr(val, len, addr) >= 0 &&
+ memcmp(addr, maap_base, 4) == 0) {
+ offset = addr[4] << 8 | addr[5];
+ have_offset = true;
+ }
+ }
+ else if (spa_streq(key, "count")) {
+ spa_json_parse_int(val, len, &count);
+ }
+ }
+ if (count > 0 && have_offset) {
+ maap->count = count;
+ maap->offset = offset;
+ maap->state = STATE_PROBE;
+ maap->probe_count = MAAP_PROBE_RETRANSMITS;
+ maap->timeout = PROBE_TIMEOUT(0);
+ }
+ return 0;
+}
+
+static int save_state(struct maap *maap)
+{
+ char *ptr;
+ size_t size;
+ FILE *f;
+ char key[512];
+ uint32_t count;
+
+ if ((f = open_memstream(&ptr, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "[ ");
+ fprintf(f, "{ \"start\": \"%02x:%02x:%02x:%02x:%02x:%02x\", ",
+ maap_base[0], maap_base[1], maap_base[2],
+ maap_base[3], (maap->offset >> 8) & 0xff,
+ maap->offset & 0xff);
+ fprintf(f, " \"count\": %u } ", maap->count);
+ fprintf(f, "]");
+ fclose(f);
+
+ count = pw_properties_set(maap->props, "maap.addresses", ptr);
+ free(ptr);
+
+ if (count > 0) {
+ snprintf(key, sizeof(key), "maap.%s", maap->server->ifname);
+ pw_conf_save_state("module-avb", key, maap->props);
+ }
+ return 0;
+}
+
+static void maap_periodic(void *data, uint64_t now)
+{
+ struct maap *maap = data;
+
+ if (now < maap->timeout)
+ return;
+
+ switch(maap->state) {
+ case STATE_IDLE:
+ break;
+ case STATE_PROBE:
+ send_packet(maap, now, AVB_MAAP_MESSAGE_TYPE_PROBE, NULL, 0);
+ if (--maap->probe_count == 0) {
+ maap->state = STATE_ANNOUNCE;
+ save_state(maap);
+ }
+ maap->timeout = PROBE_TIMEOUT(now);
+ break;
+ case STATE_ANNOUNCE:
+ send_packet(maap, now, AVB_MAAP_MESSAGE_TYPE_ANNOUNCE, NULL, 0);
+ maap->timeout = ANNOUNCE_TIMEOUT(now);
+ break;
+ }
+}
+
+static void maap_free(struct maap *maap)
+{
+ pw_loop_destroy_source(maap->server->impl->loop, maap->source);
+ spa_hook_remove(&maap->server_listener);
+ pw_properties_free(maap->props);
+ free(maap);
+}
+
+static void maap_destroy(void *data)
+{
+ struct maap *maap = data;
+ maap_free(maap);
+}
+
+static const struct server_events server_events = {
+ AVB_VERSION_SERVER_EVENTS,
+ .destroy = maap_destroy,
+ .periodic = maap_periodic,
+};
+
+struct avb_maap *avb_maap_register(struct server *server)
+{
+ struct maap *maap;
+ uint8_t bmac[6] = AVB_MAAP_MAC;
+ int fd, res;
+
+ fd = avb_server_make_socket(server, AVB_TSN_ETH, bmac);
+ if (fd < 0) {
+ res = fd;
+ goto error;
+ }
+
+ maap = calloc(1, sizeof(*maap));
+ if (maap == NULL) {
+ res = -errno;
+ goto error_close;
+ }
+ maap->props = pw_properties_new(NULL, NULL);
+ if (maap->props == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+
+ maap->server = server;
+ pw_log_info("0x%"PRIx64" %d", server->entity_id, server->ifindex);
+
+ if (pw_getrandom(maap->xsubi, sizeof(maap->xsubi), 0) != sizeof(maap->xsubi)) {
+ res = -errno;
+ goto error_free;
+ }
+ load_state(maap);
+
+ maap->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, maap);
+ if (maap->source == NULL) {
+ res = -errno;
+ pw_log_error("maap %p: can't create maap source: %m", maap);
+ goto error_free;
+ }
+ avdecc_server_add_listener(server, &maap->server_listener, &server_events, maap);
+
+ return (struct avb_maap *)maap;
+
+error_free:
+ free(maap);
+error_close:
+ close(fd);
+error:
+ errno = -res;
+ return NULL;
+}
+
+int avb_maap_reserve(struct avb_maap *m, uint32_t count)
+{
+ struct maap *maap = (struct maap*)m;
+ if (count > maap->count)
+ make_new_address(maap, 0, count);
+ return 0;
+}
+
+int avb_maap_get_address(struct avb_maap *m, uint8_t addr[6], uint32_t index)
+{
+ struct maap *maap = (struct maap*)m;
+ uint16_t offset;
+
+ if (maap->state != STATE_ANNOUNCE)
+ return -EAGAIN;
+
+ memcpy(addr, maap_base, 6);
+ offset = maap->offset + index;
+ addr[4] = offset >> 8;
+ addr[5] = offset;
+ return 0;
+}
diff --git a/src/modules/module-avb/maap.h b/src/modules/module-avb/maap.h
new file mode 100644
index 0000000..6e56f8e
--- /dev/null
+++ b/src/modules/module-avb/maap.h
@@ -0,0 +1,70 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_MAAP_H
+#define AVB_MAAP_H
+
+#include "packets.h"
+#include "internal.h"
+
+#define AVB_TSN_ETH 0x22f0
+#define AVB_MAAP_MAC { 0x91, 0xe0, 0xf0, 0x00, 0xff, 0x00 };
+
+#define AVB_MAAP_MESSAGE_TYPE_PROBE 1
+#define AVB_MAAP_MESSAGE_TYPE_DEFEND 2
+#define AVB_MAAP_MESSAGE_TYPE_ANNOUNCE 3
+
+struct avb_packet_maap {
+ struct avb_packet_header hdr;
+ uint64_t stream_id;
+ uint8_t request_start[6];
+ uint16_t request_count;
+ uint8_t conflict_start[6];
+ uint16_t conflict_count;
+} __attribute__ ((__packed__));
+
+#define AVB_PACKET_MAAP_SET_MESSAGE_TYPE(p,v) AVB_PACKET_SET_SUB1(&(p)->hdr, v)
+#define AVB_PACKET_MAAP_SET_MAAP_VERSION(p,v) AVB_PACKET_SET_SUB2(&(p)->hdr, v)
+#define AVB_PACKET_MAAP_SET_STREAM_ID(p,v) ((p)->stream_id = htobe64(v))
+#define AVB_PACKET_MAAP_SET_REQUEST_START(p,v) memcpy((p)->request_start, (v), 6)
+#define AVB_PACKET_MAAP_SET_REQUEST_COUNT(p,v) ((p)->request_count = htons(v))
+#define AVB_PACKET_MAAP_SET_CONFLICT_START(p,v) memcpy((p)->conflict_start, (v), 6)
+#define AVB_PACKET_MAAP_SET_CONFLICT_COUNT(p,v) ((p)->conflict_count = htons(v))
+
+#define AVB_PACKET_MAAP_GET_MESSAGE_TYPE(p) AVB_PACKET_GET_SUB1(&(p)->hdr)
+#define AVB_PACKET_MAAP_GET_MAAP_VERSION(p) AVB_PACKET_GET_SUB2(&(p)->hdr)
+#define AVB_PACKET_MAAP_GET_STREAM_ID(p) be64toh((p)->stream_id)
+#define AVB_PACKET_MAAP_GET_REQUEST_START(p) ((p)->request_start)
+#define AVB_PACKET_MAAP_GET_REQUEST_COUNT(p) ntohs((p)->request_count)
+#define AVB_PACKET_MAAP_GET_CONFLICT_START(p) ((p)->conflict_start)
+#define AVB_PACKET_MAAP_GET_CONFLICT_COUNT(p) ntohs((p)->conflict_count)
+
+struct avb_maap;
+
+struct avb_maap *avb_maap_register(struct server *server);
+
+int avb_maap_reserve(struct avb_maap *maap, uint32_t count);
+int avb_maap_get_address(struct avb_maap *maap, uint8_t addr[6], uint32_t index);
+
+#endif /* AVB_MAAP_H */
diff --git a/src/modules/module-avb/mmrp.c b/src/modules/module-avb/mmrp.c
new file mode 100644
index 0000000..022aea8
--- /dev/null
+++ b/src/modules/module-avb/mmrp.c
@@ -0,0 +1,233 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <unistd.h>
+
+#include <pipewire/pipewire.h>
+
+#include "utils.h"
+#include "mmrp.h"
+
+static const uint8_t mmrp_mac[6] = AVB_MMRP_MAC;
+
+struct attr {
+ struct avb_mmrp_attribute attr;
+ struct spa_list link;
+};
+
+struct mmrp {
+ struct server *server;
+ struct spa_hook server_listener;
+
+ struct spa_source *source;
+
+ struct spa_list attributes;
+};
+
+static bool mmrp_check_header(void *data, const void *hdr, size_t *hdr_size, bool *has_params)
+{
+ const struct avb_packet_mmrp_msg *msg = hdr;
+ uint8_t attr_type = msg->attribute_type;
+
+ if (!AVB_MMRP_ATTRIBUTE_TYPE_VALID(attr_type))
+ return false;
+
+ *hdr_size = sizeof(*msg);
+ *has_params = false;
+ return true;
+}
+
+static int mmrp_attr_event(void *data, uint64_t now, uint8_t attribute_type, uint8_t event)
+{
+ struct mmrp *mmrp = data;
+ struct attr *a;
+ spa_list_for_each(a, &mmrp->attributes, link)
+ if (a->attr.type == attribute_type)
+ avb_mrp_attribute_update_state(a->attr.mrp, now, event);
+ return 0;
+}
+
+static void debug_service_requirement(const struct avb_packet_mmrp_service_requirement *t)
+{
+ char buf[128];
+ pw_log_info("service requirement");
+ pw_log_info(" %s", avb_utils_format_addr(buf, sizeof(buf), t->addr));
+}
+
+static int process_service_requirement(struct mmrp *mmrp, uint64_t now, uint8_t attr_type,
+ const void *m, uint8_t event, uint8_t param, int num)
+{
+ const struct avb_packet_mmrp_service_requirement *t = m;
+ struct attr *a;
+
+ debug_service_requirement(t);
+
+ spa_list_for_each(a, &mmrp->attributes, link)
+ if (a->attr.type == attr_type &&
+ memcmp(a->attr.attr.service_requirement.addr, t->addr, 6) == 0)
+ avb_mrp_attribute_rx_event(a->attr.mrp, now, event);
+ return 0;
+}
+
+static void debug_process_mac(const struct avb_packet_mmrp_mac *t)
+{
+ char buf[128];
+ pw_log_info("mac");
+ pw_log_info(" %s", avb_utils_format_addr(buf, sizeof(buf), t->addr));
+}
+
+static int process_mac(struct mmrp *mmrp, uint64_t now, uint8_t attr_type,
+ const void *m, uint8_t event, uint8_t param, int num)
+{
+ const struct avb_packet_mmrp_mac *t = m;
+ struct attr *a;
+
+ debug_process_mac(t);
+
+ spa_list_for_each(a, &mmrp->attributes, link)
+ if (a->attr.type == attr_type &&
+ memcmp(a->attr.attr.mac.addr, t->addr, 6) == 0)
+ avb_mrp_attribute_rx_event(a->attr.mrp, now, event);
+ return 0;
+}
+
+static const struct {
+ int (*dispatch) (struct mmrp *mmrp, uint64_t now, uint8_t attr_type,
+ const void *m, uint8_t event, uint8_t param, int num);
+} dispatch[] = {
+ [AVB_MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT] = { process_service_requirement, },
+ [AVB_MMRP_ATTRIBUTE_TYPE_MAC] = { process_mac, },
+};
+
+static int mmrp_process(void *data, uint64_t now, uint8_t attribute_type, const void *value,
+ uint8_t event, uint8_t param, int index)
+{
+ struct mmrp *mmrp = data;
+ return dispatch[attribute_type].dispatch(mmrp, now,
+ attribute_type, value, event, param, index);
+}
+
+static const struct avb_mrp_parse_info info = {
+ AVB_VERSION_MRP_PARSE_INFO,
+ .check_header = mmrp_check_header,
+ .attr_event = mmrp_attr_event,
+ .process = mmrp_process,
+};
+
+static int mmrp_message(struct mmrp *mmrp, uint64_t now, const void *message, int len)
+{
+ pw_log_debug("MMRP");
+ return avb_mrp_parse_packet(mmrp->server->mrp,
+ now, message, len, &info, mmrp);
+}
+
+static void on_socket_data(void *data, int fd, uint32_t mask)
+{
+ struct mmrp *mmrp = data;
+ struct timespec now;
+
+ if (mask & SPA_IO_IN) {
+ int len;
+ uint8_t buffer[2048];
+
+ len = recv(fd, buffer, sizeof(buffer), 0);
+
+ if (len < 0) {
+ pw_log_warn("got recv error: %m");
+ }
+ else if (len < (int)sizeof(struct avb_packet_header)) {
+ pw_log_warn("short packet received (%d < %d)", len,
+ (int)sizeof(struct avb_packet_header));
+ } else {
+ clock_gettime(CLOCK_REALTIME, &now);
+ mmrp_message(mmrp, SPA_TIMESPEC_TO_NSEC(&now), buffer, len);
+ }
+ }
+}
+static void mmrp_destroy(void *data)
+{
+ struct mmrp *mmrp = data;
+ spa_hook_remove(&mmrp->server_listener);
+ pw_loop_destroy_source(mmrp->server->impl->loop, mmrp->source);
+ free(mmrp);
+}
+
+static const struct server_events server_events = {
+ AVB_VERSION_SERVER_EVENTS,
+ .destroy = mmrp_destroy,
+};
+
+struct avb_mmrp_attribute *avb_mmrp_attribute_new(struct avb_mmrp *m,
+ uint8_t type)
+{
+ struct mmrp *mmrp = (struct mmrp*)m;
+ struct avb_mrp_attribute *attr;
+ struct attr *a;
+
+ attr = avb_mrp_attribute_new(mmrp->server->mrp, sizeof(struct attr));
+
+ a = attr->user_data;
+ a->attr.mrp = attr;
+ a->attr.type = type;
+ spa_list_append(&mmrp->attributes, &a->link);
+
+ return &a->attr;
+}
+
+struct avb_mmrp *avb_mmrp_register(struct server *server)
+{
+ struct mmrp *mmrp;
+ int fd, res;
+
+ fd = avb_server_make_socket(server, AVB_MMRP_ETH, mmrp_mac);
+ if (fd < 0) {
+ errno = -fd;
+ return NULL;
+ }
+ mmrp = calloc(1, sizeof(*mmrp));
+ if (mmrp == NULL) {
+ res = -errno;
+ goto error_close;
+ }
+
+ mmrp->server = server;
+ spa_list_init(&mmrp->attributes);
+
+ mmrp->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, mmrp);
+ if (mmrp->source == NULL) {
+ res = -errno;
+ pw_log_error("mmrp %p: can't create mmrp source: %m", mmrp);
+ goto error_no_source;
+ }
+ avdecc_server_add_listener(server, &mmrp->server_listener, &server_events, mmrp);
+
+ return (struct avb_mmrp*)mmrp;
+
+error_no_source:
+ free(mmrp);
+error_close:
+ close(fd);
+ errno = -res;
+ return NULL;
+}
diff --git a/src/modules/module-avb/mmrp.h b/src/modules/module-avb/mmrp.h
new file mode 100644
index 0000000..b7bcf8c
--- /dev/null
+++ b/src/modules/module-avb/mmrp.h
@@ -0,0 +1,68 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_MMRP_H
+#define AVB_MMRP_H
+
+#include "mrp.h"
+#include "internal.h"
+
+#define AVB_MMRP_ETH 0x88f6
+#define AVB_MMRP_MAC { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x20 }
+
+#define AVB_MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT 1
+#define AVB_MMRP_ATTRIBUTE_TYPE_MAC 2
+#define AVB_MMRP_ATTRIBUTE_TYPE_VALID(t) ((t)>=1 && (t)<=2)
+
+struct avb_packet_mmrp_msg {
+ uint8_t attribute_type;
+ uint8_t attribute_length;
+ uint8_t attribute_list[0];
+} __attribute__ ((__packed__));
+
+struct avb_packet_mmrp_service_requirement {
+ unsigned char addr[6];
+} __attribute__ ((__packed__));
+
+struct avb_packet_mmrp_mac {
+ unsigned char addr[6];
+} __attribute__ ((__packed__));
+
+struct avb_mmrp;
+
+struct avb_mmrp_attribute {
+ struct avb_mrp_attribute *mrp;
+ uint8_t type;
+ union {
+ struct avb_packet_mmrp_service_requirement service_requirement;
+ struct avb_packet_mmrp_mac mac;
+ } attr;
+};
+
+struct avb_mmrp_attribute *avb_mmrp_attribute_new(struct avb_mmrp *mmrp,
+ uint8_t type);
+
+struct avb_mmrp *avb_mmrp_register(struct server *server);
+
+#endif /* AVB_MMRP_H */
diff --git a/src/modules/module-avb/mrp.c b/src/modules/module-avb/mrp.c
new file mode 100644
index 0000000..7b6bc46
--- /dev/null
+++ b/src/modules/module-avb/mrp.c
@@ -0,0 +1,612 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/pipewire.h>
+
+#include "mrp.h"
+
+#define MRP_JOINTIMER_MS 100
+#define MRP_LVTIMER_MS 1000
+#define MRP_LVATIMER_MS 10000
+#define MRP_PERIODTIMER_MS 1000
+
+#define mrp_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct avb_mrp_events, m, v, ##__VA_ARGS__)
+#define mrp_emit_event(s,n,e) mrp_emit(s,event,0,n,e)
+#define mrp_emit_notify(s,n,a,e) mrp_emit(s,notify,0,n,a,e)
+
+#define mrp_attribute_emit(a,m,v,...) spa_hook_list_call(&a->listener_list, struct avb_mrp_attribute_events, m, v, ##__VA_ARGS__)
+#define mrp_attribute_emit_notify(a,n,e) mrp_attribute_emit(a,notify,0,n,e)
+
+
+struct mrp;
+
+struct attribute {
+ struct avb_mrp_attribute attr;
+ struct mrp *mrp;
+ struct spa_list link;
+ uint8_t applicant_state;
+ uint8_t registrar_state;
+ uint64_t leave_timeout;
+ unsigned joined:1;
+ struct spa_hook_list listener_list;
+};
+
+struct mrp {
+ struct server *server;
+ struct spa_hook server_listener;
+
+ struct spa_hook_list listener_list;
+
+ struct spa_list attributes;
+
+ uint64_t periodic_timeout;
+ uint64_t leave_all_timeout;
+ uint64_t join_timeout;
+};
+
+static void mrp_destroy(void *data)
+{
+ struct mrp *mrp = data;
+ spa_hook_remove(&mrp->server_listener);
+ free(mrp);
+}
+
+static void global_event(struct mrp *mrp, uint64_t now, uint8_t event)
+{
+ struct attribute *a;
+ spa_list_for_each(a, &mrp->attributes, link)
+ avb_mrp_attribute_update_state(&a->attr, now, event);
+ mrp_emit_event(mrp, now, event);
+}
+
+static void mrp_periodic(void *data, uint64_t now)
+{
+ struct mrp *mrp = data;
+ bool leave_all = false;
+ struct attribute *a;
+
+ if (now > mrp->periodic_timeout) {
+ if (mrp->periodic_timeout > 0)
+ global_event(mrp, now, AVB_MRP_EVENT_PERIODIC);
+ mrp->periodic_timeout = now + MRP_PERIODTIMER_MS * SPA_NSEC_PER_MSEC;
+ }
+ if (now > mrp->leave_all_timeout) {
+ if (mrp->leave_all_timeout > 0) {
+ global_event(mrp, now, AVB_MRP_EVENT_RX_LVA);
+ leave_all = true;
+ }
+ mrp->leave_all_timeout = now + (MRP_LVATIMER_MS + (random() % (MRP_LVATIMER_MS / 2)))
+ * SPA_NSEC_PER_MSEC;
+ }
+
+ if (now > mrp->join_timeout) {
+ if (mrp->join_timeout > 0) {
+ uint8_t event = leave_all ? AVB_MRP_EVENT_TX_LVA : AVB_MRP_EVENT_TX;
+ global_event(mrp, now, event);
+ }
+ mrp->join_timeout = now + MRP_JOINTIMER_MS * SPA_NSEC_PER_MSEC;
+ }
+
+ spa_list_for_each(a, &mrp->attributes, link) {
+ if (a->leave_timeout > 0 && now > a->leave_timeout) {
+ a->leave_timeout = 0;
+ avb_mrp_attribute_update_state(&a->attr, now, AVB_MRP_EVENT_LV_TIMER);
+ }
+ }
+}
+
+static const struct server_events server_events = {
+ AVB_VERSION_SERVER_EVENTS,
+ .destroy = mrp_destroy,
+ .periodic = mrp_periodic,
+};
+
+int avb_mrp_parse_packet(struct avb_mrp *mrp, uint64_t now, const void *pkt, int len,
+ const struct avb_mrp_parse_info *info, void *data)
+{
+ uint8_t *e = SPA_PTROFF(pkt, len, uint8_t);
+ uint8_t *m = SPA_PTROFF(pkt, sizeof(struct avb_packet_mrp), uint8_t);
+
+ while (m < e && (m[0] != 0 || m[1] != 0)) {
+ const struct avb_packet_mrp_hdr *hdr = (const struct avb_packet_mrp_hdr*)m;
+ uint8_t attr_type = hdr->attribute_type;
+ uint8_t attr_len = hdr->attribute_length;
+ size_t hdr_size;
+ bool has_param;
+
+ if (!info->check_header(data, hdr, &hdr_size, &has_param))
+ return -EINVAL;
+
+ m += hdr_size;
+
+ while (m < e && (m[0] != 0 || m[1] != 0)) {
+ const struct avb_packet_mrp_vector *v =
+ (const struct avb_packet_mrp_vector*)m;
+ uint16_t i, num_values = AVB_MRP_VECTOR_GET_NUM_VALUES(v);
+ uint8_t event_len = (num_values+2)/3;
+ uint8_t param_len = has_param ? (num_values+3)/4 : 0;
+ int plen = sizeof(*v) + attr_len + event_len + param_len;
+ const uint8_t *first = v->first_value;
+ uint8_t event[3], param[4] = { 0, };
+
+ if (m + plen > e)
+ return -EPROTO;
+
+ if (v->lva)
+ info->attr_event(data, now, attr_type, AVB_MRP_EVENT_RX_LVA);
+
+ for (i = 0; i < num_values; i++) {
+ if (i % 3 == 0) {
+ uint8_t ep = first[attr_len + i/3];
+ event[2] = ep % 6; ep /= 6;
+ event[1] = ep % 6; ep /= 6;
+ event[0] = ep % 6;
+ }
+ if (has_param && (i % 4 == 0)) {
+ uint8_t ep = first[attr_len + event_len + i/4];
+ param[3] = ep % 4; ep /= 4;
+ param[2] = ep % 4; ep /= 4;
+ param[1] = ep % 4; ep /= 4;
+ param[0] = ep % 4;
+ }
+ info->process(data, now, attr_type, first,
+ event[i%3], param[i%4], i);
+ }
+ m += plen;
+ }
+ m += 2;
+ }
+ return 0;
+}
+
+const char *avb_mrp_notify_name(uint8_t notify)
+{
+ switch(notify) {
+ case AVB_MRP_NOTIFY_NEW:
+ return "new";
+ case AVB_MRP_NOTIFY_JOIN:
+ return "join";
+ case AVB_MRP_NOTIFY_LEAVE:
+ return "leave";
+ }
+ return "unknown";
+}
+
+const char *avb_mrp_send_name(uint8_t send)
+{
+ switch(send) {
+ case AVB_MRP_SEND_NEW:
+ return "new";
+ case AVB_MRP_SEND_JOININ:
+ return "joinin";
+ case AVB_MRP_SEND_IN:
+ return "in";
+ case AVB_MRP_SEND_JOINMT:
+ return "joinmt";
+ case AVB_MRP_SEND_MT:
+ return "mt";
+ case AVB_MRP_SEND_LV:
+ return "leave";
+ }
+ return "unknown";
+}
+
+struct avb_mrp_attribute *avb_mrp_attribute_new(struct avb_mrp *m,
+ size_t user_size)
+{
+ struct mrp *mrp = (struct mrp*)m;
+ struct attribute *a;
+
+ a = calloc(1, sizeof(*a) + user_size);
+ if (a == NULL)
+ return NULL;
+
+ a->mrp = mrp;
+ a->attr.user_data = SPA_PTROFF(a, sizeof(*a), void);
+ spa_hook_list_init(&a->listener_list);
+ spa_list_append(&mrp->attributes, &a->link);
+
+ return &a->attr;
+}
+
+void avb_mrp_attribute_destroy(struct avb_mrp_attribute *attr)
+{
+ struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr);
+ spa_list_remove(&a->link);
+ free(a);
+}
+
+void avb_mrp_attribute_add_listener(struct avb_mrp_attribute *attr, struct spa_hook *listener,
+ const struct avb_mrp_attribute_events *events, void *data)
+{
+ struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr);
+ spa_hook_list_append(&a->listener_list, listener, events, data);
+}
+
+void avb_mrp_attribute_update_state(struct avb_mrp_attribute *attr, uint64_t now,
+ int event)
+{
+ struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr);
+ struct mrp *mrp = a->mrp;
+ uint8_t notify = 0, state;
+ uint8_t send = 0;
+
+ state = a->registrar_state;
+
+ switch (event) {
+ case AVB_MRP_EVENT_BEGIN:
+ state = AVB_MRP_MT;
+ break;
+ case AVB_MRP_EVENT_RX_NEW:
+ notify = AVB_MRP_NOTIFY_NEW;
+ switch (state) {
+ case AVB_MRP_LV:
+ a->leave_timeout = 0;
+ break;
+ }
+ state = AVB_MRP_IN;
+ break;
+ case AVB_MRP_EVENT_RX_JOININ:
+ case AVB_MRP_EVENT_RX_JOINMT:
+ switch (state) {
+ case AVB_MRP_LV:
+ a->leave_timeout = 0;
+ break;
+ case AVB_MRP_MT:
+ notify = AVB_MRP_NOTIFY_JOIN;
+ break;
+ }
+ state = AVB_MRP_IN;
+ break;
+ case AVB_MRP_EVENT_RX_LV:
+ case AVB_MRP_EVENT_RX_LVA:
+ case AVB_MRP_EVENT_TX_LVA:
+ case AVB_MRP_EVENT_REDECLARE:
+ switch (state) {
+ case AVB_MRP_IN:
+ a->leave_timeout = now + MRP_LVTIMER_MS * SPA_NSEC_PER_MSEC;
+ //state = AVB_MRP_LV;
+ break;
+ }
+ break;
+ case AVB_MRP_EVENT_FLUSH:
+ switch (state) {
+ case AVB_MRP_LV:
+ notify = AVB_MRP_NOTIFY_LEAVE;
+ break;
+ }
+ state = AVB_MRP_MT;
+ break;
+ case AVB_MRP_EVENT_LV_TIMER:
+ switch (state) {
+ case AVB_MRP_LV:
+ notify = AVB_MRP_NOTIFY_LEAVE;
+ state = AVB_MRP_MT;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ if (notify) {
+ mrp_attribute_emit_notify(a, now, notify);
+ mrp_emit_notify(mrp, now, &a->attr, notify);
+ }
+
+ if (a->registrar_state != state || notify) {
+ pw_log_debug("attr %p: %d %d -> %d %d", a, event, a->registrar_state, state, notify);
+ a->registrar_state = state;
+ }
+
+ state = a->applicant_state;
+
+ switch (event) {
+ case AVB_MRP_EVENT_BEGIN:
+ state = AVB_MRP_VO;
+ break;
+ case AVB_MRP_EVENT_NEW:
+ switch (state) {
+ case AVB_MRP_VN:
+ case AVB_MRP_AN:
+ break;
+ default:
+ state = AVB_MRP_VN;
+ break;
+ }
+ break;
+ case AVB_MRP_EVENT_JOIN:
+ switch (state) {
+ case AVB_MRP_VO:
+ case AVB_MRP_LO:
+ state = AVB_MRP_VP;
+ break;
+ case AVB_MRP_LA:
+ state = AVB_MRP_AA;
+ break;
+ case AVB_MRP_AO:
+ state = AVB_MRP_AP;
+ break;
+ case AVB_MRP_QO:
+ state = AVB_MRP_QP;
+ break;
+ }
+ break;
+ case AVB_MRP_EVENT_LV:
+ switch (state) {
+ case AVB_MRP_VP:
+ state = AVB_MRP_VO;
+ break;
+ case AVB_MRP_VN:
+ case AVB_MRP_AN:
+ case AVB_MRP_AA:
+ case AVB_MRP_QA:
+ state = AVB_MRP_LA;
+ break;
+ case AVB_MRP_AP:
+ state = AVB_MRP_AO;
+ break;
+ case AVB_MRP_QP:
+ state = AVB_MRP_QO;
+ break;
+ }
+ break;
+ case AVB_MRP_EVENT_RX_JOININ:
+ switch (state) {
+ case AVB_MRP_VO:
+ state = AVB_MRP_AO;
+ break;
+ case AVB_MRP_VP:
+ state = AVB_MRP_AP;
+ break;
+ case AVB_MRP_AA:
+ state = AVB_MRP_QA;
+ break;
+ case AVB_MRP_AO:
+ state = AVB_MRP_QO;
+ break;
+ case AVB_MRP_AP:
+ state = AVB_MRP_QP;
+ break;
+ }
+ SPA_FALLTHROUGH;
+ case AVB_MRP_EVENT_RX_IN:
+ switch (state) {
+ case AVB_MRP_AA:
+ state = AVB_MRP_QA;
+ break;
+ }
+ break;
+ case AVB_MRP_EVENT_RX_JOINMT:
+ case AVB_MRP_EVENT_RX_MT:
+ switch (state) {
+ case AVB_MRP_QA:
+ state = AVB_MRP_AA;
+ break;
+ case AVB_MRP_QO:
+ state = AVB_MRP_AO;
+ break;
+ case AVB_MRP_QP:
+ state = AVB_MRP_AP;
+ break;
+ case AVB_MRP_LO:
+ state = AVB_MRP_VO;
+ break;
+ }
+ break;
+ case AVB_MRP_EVENT_RX_LV:
+ case AVB_MRP_EVENT_RX_LVA:
+ case AVB_MRP_EVENT_REDECLARE:
+ switch (state) {
+ case AVB_MRP_VO:
+ case AVB_MRP_AO:
+ case AVB_MRP_QO:
+ state = AVB_MRP_LO;
+ break;
+ case AVB_MRP_AN:
+ state = AVB_MRP_VN;
+ break;
+ case AVB_MRP_AA:
+ case AVB_MRP_QA:
+ case AVB_MRP_AP:
+ case AVB_MRP_QP:
+ state = AVB_MRP_VP;
+ break;
+ }
+ break;
+ case AVB_MRP_EVENT_PERIODIC:
+ switch (state) {
+ case AVB_MRP_QA:
+ state = AVB_MRP_AA;
+ break;
+ case AVB_MRP_QP:
+ state = AVB_MRP_AP;
+ break;
+ }
+ break;
+ case AVB_MRP_EVENT_TX:
+ switch (state) {
+ case AVB_MRP_VP:
+ case AVB_MRP_AA:
+ case AVB_MRP_AP:
+ if (a->registrar_state == AVB_MRP_IN)
+ send = AVB_MRP_SEND_JOININ;
+ else
+ send = AVB_MRP_SEND_JOINMT;
+ break;
+ case AVB_MRP_VN:
+ case AVB_MRP_AN:
+ send = AVB_MRP_SEND_NEW;
+ break;
+ case AVB_MRP_LA:
+ send = AVB_MRP_SEND_LV;
+ break;
+ case AVB_MRP_LO:
+ if (a->registrar_state == AVB_MRP_IN)
+ send = AVB_MRP_SEND_IN;
+ else
+ send = AVB_MRP_SEND_MT;
+ break;
+ }
+ switch (state) {
+ case AVB_MRP_VP:
+ state = AVB_MRP_AA;
+ break;
+ case AVB_MRP_VN:
+ state = AVB_MRP_AN;
+ break;
+ case AVB_MRP_AN:
+ if(a->registrar_state == AVB_MRP_IN)
+ state = AVB_MRP_QA;
+ else
+ state = AVB_MRP_AA;
+ break;
+ case AVB_MRP_AA:
+ case AVB_MRP_AP:
+ state = AVB_MRP_QA;
+ break;
+ case AVB_MRP_LA:
+ case AVB_MRP_LO:
+ state = AVB_MRP_VO;
+ break;
+ }
+ break;
+ case AVB_MRP_EVENT_TX_LVA:
+ {
+ switch (state) {
+ case AVB_MRP_VP:
+ if (a->registrar_state == AVB_MRP_IN)
+ send = AVB_MRP_SEND_IN;
+ else
+ send = AVB_MRP_SEND_MT;
+ break;
+ case AVB_MRP_VN:
+ case AVB_MRP_AN:
+ send = AVB_MRP_SEND_NEW;
+ break;
+ case AVB_MRP_AA:
+ case AVB_MRP_QA:
+ case AVB_MRP_AP:
+ case AVB_MRP_QP:
+ if (a->registrar_state == AVB_MRP_IN)
+ send = AVB_MRP_SEND_JOININ;
+ else
+ send = AVB_MRP_SEND_JOINMT;
+ break;
+ }
+ switch (state) {
+ case AVB_MRP_VO:
+ case AVB_MRP_LA:
+ case AVB_MRP_AO:
+ case AVB_MRP_QO:
+ state = AVB_MRP_LO;
+ break;
+ case AVB_MRP_VP:
+ state = AVB_MRP_AA;
+ break;
+ case AVB_MRP_VN:
+ state = AVB_MRP_AN;
+ break;
+ case AVB_MRP_AN:
+ case AVB_MRP_AA:
+ case AVB_MRP_AP:
+ case AVB_MRP_QP:
+ state = AVB_MRP_QA;
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ if (a->applicant_state != state || send) {
+ pw_log_debug("attr %p: %d %d -> %d %d", a, event, a->applicant_state, state, send);
+ a->applicant_state = state;
+ }
+ if (a->joined)
+ a->attr.pending_send = send;
+}
+
+void avb_mrp_attribute_rx_event(struct avb_mrp_attribute *attr, uint64_t now, uint8_t event)
+{
+ static const int map[] = {
+ [AVB_MRP_ATTRIBUTE_EVENT_NEW] = AVB_MRP_EVENT_RX_NEW,
+ [AVB_MRP_ATTRIBUTE_EVENT_JOININ] = AVB_MRP_EVENT_RX_JOININ,
+ [AVB_MRP_ATTRIBUTE_EVENT_IN] = AVB_MRP_EVENT_RX_IN,
+ [AVB_MRP_ATTRIBUTE_EVENT_JOINMT] = AVB_MRP_EVENT_RX_JOINMT,
+ [AVB_MRP_ATTRIBUTE_EVENT_MT] = AVB_MRP_EVENT_RX_MT,
+ [AVB_MRP_ATTRIBUTE_EVENT_LV] = AVB_MRP_EVENT_RX_LV,
+ };
+ avb_mrp_attribute_update_state(attr, now, map[event]);
+}
+
+void avb_mrp_attribute_begin(struct avb_mrp_attribute *attr, uint64_t now)
+{
+ struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr);
+ a->leave_timeout = 0;
+ avb_mrp_attribute_update_state(attr, now, AVB_MRP_EVENT_BEGIN);
+}
+
+void avb_mrp_attribute_join(struct avb_mrp_attribute *attr, uint64_t now, bool is_new)
+{
+ struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr);
+ a->joined = true;
+ int event = is_new ? AVB_MRP_EVENT_NEW : AVB_MRP_EVENT_JOIN;
+ avb_mrp_attribute_update_state(attr, now, event);
+}
+
+void avb_mrp_attribute_leave(struct avb_mrp_attribute *attr, uint64_t now)
+{
+ struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr);
+ avb_mrp_attribute_update_state(attr, now, AVB_MRP_EVENT_LV);
+ a->joined = false;
+}
+
+void avb_mrp_destroy(struct avb_mrp *mrp)
+{
+ mrp_destroy(mrp);
+}
+
+struct avb_mrp *avb_mrp_new(struct server *server)
+{
+ struct mrp *mrp;
+
+ mrp = calloc(1, sizeof(*mrp));
+ if (mrp == NULL)
+ return NULL;
+
+ mrp->server = server;
+ spa_list_init(&mrp->attributes);
+ spa_hook_list_init(&mrp->listener_list);
+
+ avdecc_server_add_listener(server, &mrp->server_listener, &server_events, mrp);
+
+ return (struct avb_mrp*)mrp;
+}
+
+void avb_mrp_add_listener(struct avb_mrp *m, struct spa_hook *listener,
+ const struct avb_mrp_events *events, void *data)
+{
+ struct mrp *mrp = (struct mrp*)m;
+ spa_hook_list_append(&mrp->listener_list, listener, events, data);
+}
diff --git a/src/modules/module-avb/mrp.h b/src/modules/module-avb/mrp.h
new file mode 100644
index 0000000..0a05d4b
--- /dev/null
+++ b/src/modules/module-avb/mrp.h
@@ -0,0 +1,181 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_MRP_H
+#define AVB_MRP_H
+
+#include "packets.h"
+#include "internal.h"
+
+#define AVB_MRP_PROTOCOL_VERSION 0
+
+struct avb_packet_mrp {
+ struct avb_ethernet_header eth;
+ uint8_t version;
+} __attribute__ ((__packed__));
+
+struct avb_packet_mrp_hdr {
+ uint8_t attribute_type;
+ uint8_t attribute_length;
+} __attribute__ ((__packed__));
+
+struct avb_packet_mrp_vector {
+#if __BYTE_ORDER == __BIG_ENDIAN
+ unsigned lva:3;
+ unsigned nv1:5;
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+ unsigned nv1:5;
+ unsigned lva:3;
+#endif
+ uint8_t nv2;
+ uint8_t first_value[0];
+} __attribute__ ((__packed__));
+
+#define AVB_MRP_VECTOR_SET_NUM_VALUES(a,v) ((a)->nv1 = ((v) >> 8),(a)->nv2 = (v))
+#define AVB_MRP_VECTOR_GET_NUM_VALUES(a) ((a)->nv1 << 8 | (a)->nv2)
+
+struct avb_packet_mrp_footer {
+ uint16_t end_mark;
+} __attribute__ ((__packed__));
+
+/* applicant states */
+#define AVB_MRP_VO 0 /* Very anxious Observer */
+#define AVB_MRP_VP 1 /* Very anxious Passive */
+#define AVB_MRP_VN 2 /* Very anxious New */
+#define AVB_MRP_AN 3 /* Anxious New */
+#define AVB_MRP_AA 4 /* Anxious Active */
+#define AVB_MRP_QA 5 /* Quiet Active */
+#define AVB_MRP_LA 6 /* Leaving Active */
+#define AVB_MRP_AO 7 /* Anxious Observer */
+#define AVB_MRP_QO 8 /* Quiet Observer */
+#define AVB_MRP_AP 9 /* Anxious Passive */
+#define AVB_MRP_QP 10 /* Quiet Passive */
+#define AVB_MRP_LO 11 /* Leaving Observer */
+
+/* registrar states */
+#define AVB_MRP_IN 16
+#define AVB_MRP_LV 17
+#define AVB_MRP_MT 18
+
+/* events */
+#define AVB_MRP_EVENT_BEGIN 0
+#define AVB_MRP_EVENT_NEW 1
+#define AVB_MRP_EVENT_JOIN 2
+#define AVB_MRP_EVENT_LV 3
+#define AVB_MRP_EVENT_TX 4
+#define AVB_MRP_EVENT_TX_LVA 5
+#define AVB_MRP_EVENT_TX_LVAF 6
+#define AVB_MRP_EVENT_RX_NEW 7
+#define AVB_MRP_EVENT_RX_JOININ 8
+#define AVB_MRP_EVENT_RX_IN 9
+#define AVB_MRP_EVENT_RX_JOINMT 10
+#define AVB_MRP_EVENT_RX_MT 11
+#define AVB_MRP_EVENT_RX_LV 12
+#define AVB_MRP_EVENT_RX_LVA 13
+#define AVB_MRP_EVENT_FLUSH 14
+#define AVB_MRP_EVENT_REDECLARE 15
+#define AVB_MRP_EVENT_PERIODIC 16
+#define AVB_MRP_EVENT_LV_TIMER 17
+#define AVB_MRP_EVENT_LVA_TIMER 18
+
+/* attribute events */
+#define AVB_MRP_ATTRIBUTE_EVENT_NEW 0
+#define AVB_MRP_ATTRIBUTE_EVENT_JOININ 1
+#define AVB_MRP_ATTRIBUTE_EVENT_IN 2
+#define AVB_MRP_ATTRIBUTE_EVENT_JOINMT 3
+#define AVB_MRP_ATTRIBUTE_EVENT_MT 4
+#define AVB_MRP_ATTRIBUTE_EVENT_LV 5
+
+#define AVB_MRP_SEND_NEW 1
+#define AVB_MRP_SEND_JOININ 2
+#define AVB_MRP_SEND_IN 3
+#define AVB_MRP_SEND_JOINMT 4
+#define AVB_MRP_SEND_MT 5
+#define AVB_MRP_SEND_LV 6
+
+#define AVB_MRP_NOTIFY_NEW 1
+#define AVB_MRP_NOTIFY_JOIN 2
+#define AVB_MRP_NOTIFY_LEAVE 3
+
+const char *avb_mrp_notify_name(uint8_t notify);
+const char *avb_mrp_send_name(uint8_t send);
+
+struct avb_mrp_attribute {
+ uint8_t pending_send;
+ void *user_data;
+};
+
+struct avb_mrp_attribute_events {
+#define AVB_VERSION_MRP_ATTRIBUTE_EVENTS 0
+ uint32_t version;
+
+ void (*notify) (void *data, uint64_t now, uint8_t notify);
+};
+
+struct avb_mrp_attribute *avb_mrp_attribute_new(struct avb_mrp *mrp,
+ size_t user_size);
+void avb_mrp_attribute_destroy(struct avb_mrp_attribute *attr);
+
+void avb_mrp_attribute_update_state(struct avb_mrp_attribute *attr, uint64_t now, int event);
+
+void avb_mrp_attribute_rx_event(struct avb_mrp_attribute *attr, uint64_t now, uint8_t event);
+
+void avb_mrp_attribute_begin(struct avb_mrp_attribute *attr, uint64_t now);
+void avb_mrp_attribute_join(struct avb_mrp_attribute *attr, uint64_t now, bool is_new);
+void avb_mrp_attribute_leave(struct avb_mrp_attribute *attr, uint64_t now);
+
+void avb_mrp_attribute_add_listener(struct avb_mrp_attribute *attr, struct spa_hook *listener,
+ const struct avb_mrp_attribute_events *events, void *data);
+
+struct avb_mrp_parse_info {
+#define AVB_VERSION_MRP_PARSE_INFO 0
+ uint32_t version;
+
+ bool (*check_header) (void *data, const void *hdr, size_t *hdr_size, bool *has_params);
+
+ int (*attr_event) (void *data, uint64_t now, uint8_t attribute_type, uint8_t event);
+
+ int (*process) (void *data, uint64_t now, uint8_t attribute_type, const void *value,
+ uint8_t event, uint8_t param, int index);
+};
+
+int avb_mrp_parse_packet(struct avb_mrp *mrp, uint64_t now, const void *pkt, int size,
+ const struct avb_mrp_parse_info *cb, void *data);
+
+struct avb_mrp_events {
+#define AVB_VERSION_MRP_EVENTS 0
+ uint32_t version;
+
+ void (*event) (void *data, uint64_t now, uint8_t event);
+
+ void (*notify) (void *data, uint64_t now, struct avb_mrp_attribute *attr, uint8_t notify);
+};
+
+struct avb_mrp *avb_mrp_new(struct server *server);
+void avb_mrp_destroy(struct avb_mrp *mrp);
+
+void avb_mrp_add_listener(struct avb_mrp *mrp, struct spa_hook *listener,
+ const struct avb_mrp_events *events, void *data);
+
+#endif /* AVB_MRP_H */
diff --git a/src/modules/module-avb/msrp.c b/src/modules/module-avb/msrp.c
new file mode 100644
index 0000000..85d3ff9
--- /dev/null
+++ b/src/modules/module-avb/msrp.c
@@ -0,0 +1,459 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <unistd.h>
+
+#include <spa/debug/mem.h>
+
+#include <pipewire/pipewire.h>
+
+#include "utils.h"
+#include "msrp.h"
+
+static const uint8_t msrp_mac[6] = AVB_MSRP_MAC;
+
+struct attr {
+ struct avb_msrp_attribute attr;
+ struct msrp *msrp;
+ struct spa_hook listener;
+ struct spa_list link;
+};
+
+struct msrp {
+ struct server *server;
+ struct spa_hook server_listener;
+ struct spa_hook mrp_listener;
+
+ struct spa_source *source;
+
+ struct spa_list attributes;
+};
+
+static void debug_msrp_talker_common(const struct avb_packet_msrp_talker *t)
+{
+ char buf[128];
+ pw_log_info(" stream-id: %s", avb_utils_format_id(buf, sizeof(buf), be64toh(t->stream_id)));
+ pw_log_info(" dest-addr: %s", avb_utils_format_addr(buf, sizeof(buf), t->dest_addr));
+ pw_log_info(" vlan-id: %d", ntohs(t->vlan_id));
+ pw_log_info(" tspec-max-frame-size: %d", ntohs(t->tspec_max_frame_size));
+ pw_log_info(" tspec-max-interval-frames: %d", ntohs(t->tspec_max_interval_frames));
+ pw_log_info(" priority: %d", t->priority);
+ pw_log_info(" rank: %d", t->rank);
+ pw_log_info(" accumulated-latency: %d", ntohl(t->accumulated_latency));
+}
+
+static void debug_msrp_talker(const struct avb_packet_msrp_talker *t)
+{
+ pw_log_info("talker");
+ debug_msrp_talker_common(t);
+}
+
+static void notify_talker(struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify)
+{
+ pw_log_info("> notify talker: %s", avb_mrp_notify_name(notify));
+ debug_msrp_talker(&attr->attr.attr.talker);
+}
+
+static int process_talker(struct msrp *msrp, uint64_t now, uint8_t attr_type,
+ const void *m, uint8_t event, uint8_t param, int num)
+{
+ const struct avb_packet_msrp_talker *t = m;
+ struct attr *a;
+ spa_list_for_each(a, &msrp->attributes, link)
+ if (a->attr.type == attr_type &&
+ a->attr.attr.talker.stream_id == t->stream_id) {
+ a->attr.attr.talker = *t;
+ avb_mrp_attribute_rx_event(a->attr.mrp, now, event);
+ }
+ return 0;
+}
+static int encode_talker(struct msrp *msrp, struct attr *a, void *m)
+{
+ struct avb_packet_msrp_msg *msg = m;
+ struct avb_packet_mrp_vector *v;
+ struct avb_packet_msrp_talker *t;
+ struct avb_packet_mrp_footer *f;
+ uint8_t *ev;
+ size_t attr_list_length = sizeof(*v) + sizeof(*t) + sizeof(*f) + 1;
+
+ msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE;
+ msg->attribute_length = sizeof(*t);
+ msg->attribute_list_length = htons(attr_list_length);
+
+ v = (struct avb_packet_mrp_vector *)msg->attribute_list;
+ v->lva = 0;
+ AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1);
+
+ t = (struct avb_packet_msrp_talker *)v->first_value;
+ *t = a->attr.attr.talker;
+
+ ev = SPA_PTROFF(t, sizeof(*t), uint8_t);
+ *ev = a->attr.mrp->pending_send * 6 * 6;
+
+ f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer);
+ f->end_mark = 0;
+
+ return attr_list_length + sizeof(*msg);
+}
+
+
+static void debug_msrp_talker_fail(const struct avb_packet_msrp_talker_fail *t)
+{
+ char buf[128];
+ pw_log_info("talker fail");
+ debug_msrp_talker_common(&t->talker);
+ pw_log_info(" bridge-id: %s", avb_utils_format_id(buf, sizeof(buf), be64toh(t->bridge_id)));
+ pw_log_info(" failure-code: %d", t->failure_code);
+}
+
+static int process_talker_fail(struct msrp *msrp, uint64_t now, uint8_t attr_type,
+ const void *m, uint8_t event, uint8_t param, int num)
+{
+ const struct avb_packet_msrp_talker_fail *t = m;
+ struct attr *a;
+
+ debug_msrp_talker_fail(t);
+
+ spa_list_for_each(a, &msrp->attributes, link)
+ if (a->attr.type == attr_type &&
+ a->attr.attr.talker_fail.talker.stream_id == t->talker.stream_id)
+ avb_mrp_attribute_rx_event(a->attr.mrp, now, event);
+ return 0;
+}
+
+static void debug_msrp_listener(const struct avb_packet_msrp_listener *l, uint8_t param)
+{
+ char buf[128];
+ pw_log_info("listener");
+ pw_log_info(" %s", avb_utils_format_id(buf, sizeof(buf), be64toh(l->stream_id)));
+ pw_log_info(" %d", param);
+}
+
+static void notify_listener(struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify)
+{
+ pw_log_info("> notify listener: %s", avb_mrp_notify_name(notify));
+ debug_msrp_listener(&attr->attr.attr.listener, attr->attr.param);
+}
+
+static int process_listener(struct msrp *msrp, uint64_t now, uint8_t attr_type,
+ const void *m, uint8_t event, uint8_t param, int num)
+{
+ const struct avb_packet_msrp_listener *l = m;
+ struct attr *a;
+ spa_list_for_each(a, &msrp->attributes, link)
+ if (a->attr.type == attr_type &&
+ a->attr.attr.listener.stream_id == l->stream_id)
+ avb_mrp_attribute_rx_event(a->attr.mrp, now, event);
+ return 0;
+}
+static int encode_listener(struct msrp *msrp, struct attr *a, void *m)
+{
+ struct avb_packet_msrp_msg *msg = m;
+ struct avb_packet_mrp_vector *v;
+ struct avb_packet_msrp_listener *l;
+ struct avb_packet_mrp_footer *f;
+ uint8_t *ev;
+ size_t attr_list_length = sizeof(*v) + sizeof(*l) + sizeof(*f) + 1 + 1;
+
+ msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_LISTENER;
+ msg->attribute_length = sizeof(*l);
+ msg->attribute_list_length = htons(attr_list_length);
+
+ v = (struct avb_packet_mrp_vector *)msg->attribute_list;
+ v->lva = 0;
+ AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1);
+
+ l = (struct avb_packet_msrp_listener *)v->first_value;
+ *l = a->attr.attr.listener;
+
+ ev = SPA_PTROFF(l, sizeof(*l), uint8_t);
+ *ev = a->attr.mrp->pending_send * 6 * 6;
+
+ ev = SPA_PTROFF(ev, sizeof(*ev), uint8_t);
+ *ev = a->attr.param * 4 * 4 * 4;
+
+ f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer);
+ f->end_mark = 0;
+
+ return attr_list_length + sizeof(*msg);
+}
+
+static void debug_msrp_domain(const struct avb_packet_msrp_domain *d)
+{
+ pw_log_info("domain");
+ pw_log_info(" id: %d", d->sr_class_id);
+ pw_log_info(" prio: %d", d->sr_class_priority);
+ pw_log_info(" vid: %d", ntohs(d->sr_class_vid));
+}
+
+static void notify_domain(struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify)
+{
+ pw_log_info("> notify domain: %s", avb_mrp_notify_name(notify));
+ debug_msrp_domain(&attr->attr.attr.domain);
+}
+
+static int process_domain(struct msrp *msrp, uint64_t now, uint8_t attr_type,
+ const void *m, uint8_t event, uint8_t param, int num)
+{
+ struct attr *a;
+ spa_list_for_each(a, &msrp->attributes, link)
+ if (a->attr.type == attr_type)
+ avb_mrp_attribute_rx_event(a->attr.mrp, now, event);
+ return 0;
+}
+
+static int encode_domain(struct msrp *msrp, struct attr *a, void *m)
+{
+ struct avb_packet_msrp_msg *msg = m;
+ struct avb_packet_mrp_vector *v;
+ struct avb_packet_msrp_domain *d;
+ struct avb_packet_mrp_footer *f;
+ uint8_t *ev;
+ size_t attr_list_length = sizeof(*v) + sizeof(*d) + sizeof(*f) + 1;
+
+ msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN;
+ msg->attribute_length = sizeof(*d);
+ msg->attribute_list_length = htons(attr_list_length);
+
+ v = (struct avb_packet_mrp_vector *)msg->attribute_list;
+ v->lva = 0;
+ AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1);
+
+ d = (struct avb_packet_msrp_domain *)v->first_value;
+ *d = a->attr.attr.domain;
+
+ ev = SPA_PTROFF(d, sizeof(*d), uint8_t);
+ *ev = a->attr.mrp->pending_send * 36;
+
+ f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer);
+ f->end_mark = 0;
+
+ return attr_list_length + sizeof(*msg);
+}
+
+static const struct {
+ const char *name;
+ int (*process) (struct msrp *msrp, uint64_t now, uint8_t attr_type,
+ const void *m, uint8_t event, uint8_t param, int num);
+ int (*encode) (struct msrp *msrp, struct attr *attr, void *m);
+ void (*notify) (struct msrp *msrp, uint64_t now, struct attr *attr, uint8_t notify);
+} dispatch[] = {
+ [AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE] = { "talker", process_talker, encode_talker, notify_talker, },
+ [AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED] = { "talker-fail", process_talker_fail, NULL, NULL },
+ [AVB_MSRP_ATTRIBUTE_TYPE_LISTENER] = { "listener", process_listener, encode_listener, notify_listener },
+ [AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN] = { "domain", process_domain, encode_domain, notify_domain, },
+};
+
+static bool msrp_check_header(void *data, const void *hdr, size_t *hdr_size, bool *has_params)
+{
+ const struct avb_packet_msrp_msg *msg = hdr;
+ uint8_t attr_type = msg->attribute_type;
+
+ if (!AVB_MSRP_ATTRIBUTE_TYPE_VALID(attr_type))
+ return false;
+
+ *hdr_size = sizeof(*msg);
+ *has_params = attr_type == AVB_MSRP_ATTRIBUTE_TYPE_LISTENER;
+ return true;
+}
+
+static int msrp_attr_event(void *data, uint64_t now, uint8_t attribute_type, uint8_t event)
+{
+ struct msrp *msrp = data;
+ struct attr *a;
+ spa_list_for_each(a, &msrp->attributes, link)
+ if (a->attr.type == attribute_type)
+ avb_mrp_attribute_update_state(a->attr.mrp, now, event);
+ return 0;
+}
+
+static int msrp_process(void *data, uint64_t now, uint8_t attribute_type, const void *value,
+ uint8_t event, uint8_t param, int index)
+{
+ struct msrp *msrp = data;
+ return dispatch[attribute_type].process(msrp, now,
+ attribute_type, value, event, param, index);
+}
+
+static const struct avb_mrp_parse_info info = {
+ AVB_VERSION_MRP_PARSE_INFO,
+ .check_header = msrp_check_header,
+ .attr_event = msrp_attr_event,
+ .process = msrp_process,
+};
+
+
+static int msrp_message(struct msrp *msrp, uint64_t now, const void *message, int len)
+{
+ return avb_mrp_parse_packet(msrp->server->mrp,
+ now, message, len, &info, msrp);
+}
+static void on_socket_data(void *data, int fd, uint32_t mask)
+{
+ struct msrp *msrp = data;
+ struct timespec now;
+
+ if (mask & SPA_IO_IN) {
+ int len;
+ uint8_t buffer[2048];
+
+ len = recv(fd, buffer, sizeof(buffer), 0);
+
+ if (len < 0) {
+ pw_log_warn("got recv error: %m");
+ }
+ else if (len < (int)sizeof(struct avb_packet_header)) {
+ pw_log_warn("short packet received (%d < %d)", len,
+ (int)sizeof(struct avb_packet_header));
+ } else {
+ clock_gettime(CLOCK_REALTIME, &now);
+ msrp_message(msrp, SPA_TIMESPEC_TO_NSEC(&now), buffer, len);
+ }
+ }
+}
+
+static void msrp_destroy(void *data)
+{
+ struct msrp *msrp = data;
+ spa_hook_remove(&msrp->server_listener);
+ pw_loop_destroy_source(msrp->server->impl->loop, msrp->source);
+ free(msrp);
+}
+
+static const struct server_events server_events = {
+ AVB_VERSION_SERVER_EVENTS,
+ .destroy = msrp_destroy,
+};
+
+static void msrp_notify(void *data, uint64_t now, uint8_t notify)
+{
+ struct attr *a = data;
+ struct msrp *msrp = a->msrp;
+ return dispatch[a->attr.type].notify(msrp, now, a, notify);
+}
+
+static const struct avb_mrp_attribute_events mrp_attr_events = {
+ AVB_VERSION_MRP_ATTRIBUTE_EVENTS,
+ .notify = msrp_notify,
+};
+
+struct avb_msrp_attribute *avb_msrp_attribute_new(struct avb_msrp *m,
+ uint8_t type)
+{
+ struct msrp *msrp = (struct msrp*)m;
+ struct avb_mrp_attribute *attr;
+ struct attr *a;
+
+ attr = avb_mrp_attribute_new(msrp->server->mrp, sizeof(struct attr));
+
+ a = attr->user_data;
+ a->msrp = msrp;
+ a->attr.mrp = attr;
+ a->attr.type = type;
+ spa_list_append(&msrp->attributes, &a->link);
+ avb_mrp_attribute_add_listener(attr, &a->listener, &mrp_attr_events, a);
+
+ return &a->attr;
+}
+
+static void msrp_event(void *data, uint64_t now, uint8_t event)
+{
+ struct msrp *msrp = data;
+ uint8_t buffer[2048];
+ struct avb_packet_mrp *p = (struct avb_packet_mrp*)buffer;
+ struct avb_packet_mrp_footer *f;
+ void *msg = SPA_PTROFF(buffer, sizeof(*p), void);
+ struct attr *a;
+ int len, count = 0;
+ size_t total = sizeof(*p) + 2;
+
+ p->version = AVB_MRP_PROTOCOL_VERSION;
+
+ spa_list_for_each(a, &msrp->attributes, link) {
+ if (!a->attr.mrp->pending_send)
+ continue;
+ if (dispatch[a->attr.type].encode == NULL)
+ continue;
+
+ pw_log_debug("send %s %s", dispatch[a->attr.type].name,
+ avb_mrp_send_name(a->attr.mrp->pending_send));
+
+ len = dispatch[a->attr.type].encode(msrp, a, msg);
+ if (len < 0)
+ break;
+
+ count++;
+ msg = SPA_PTROFF(msg, len, void);
+ total += len;
+ }
+ f = (struct avb_packet_mrp_footer *)msg;
+ f->end_mark = 0;
+
+ if (count > 0)
+ avb_server_send_packet(msrp->server, msrp_mac, AVB_MSRP_ETH,
+ buffer, total);
+}
+
+static const struct avb_mrp_events mrp_events = {
+ AVB_VERSION_MRP_EVENTS,
+ .event = msrp_event,
+};
+
+struct avb_msrp *avb_msrp_register(struct server *server)
+{
+ struct msrp *msrp;
+ int fd, res;
+
+ fd = avb_server_make_socket(server, AVB_MSRP_ETH, msrp_mac);
+ if (fd < 0) {
+ errno = -fd;
+ return NULL;
+ }
+ msrp = calloc(1, sizeof(*msrp));
+ if (msrp == NULL) {
+ res = -errno;
+ goto error_close;
+ }
+
+ msrp->server = server;
+ spa_list_init(&msrp->attributes);
+
+ msrp->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, msrp);
+ if (msrp->source == NULL) {
+ res = -errno;
+ pw_log_error("msrp %p: can't create msrp source: %m", msrp);
+ goto error_no_source;
+ }
+ avdecc_server_add_listener(server, &msrp->server_listener, &server_events, msrp);
+ avb_mrp_add_listener(server->mrp, &msrp->mrp_listener, &mrp_events, msrp);
+
+ return (struct avb_msrp*)msrp;
+
+error_no_source:
+ free(msrp);
+error_close:
+ close(fd);
+ errno = -res;
+ return NULL;
+}
diff --git a/src/modules/module-avb/msrp.h b/src/modules/module-avb/msrp.h
new file mode 100644
index 0000000..0922e6b
--- /dev/null
+++ b/src/modules/module-avb/msrp.h
@@ -0,0 +1,134 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_MSRP_H
+#define AVB_MSRP_H
+
+#include "internal.h"
+#include "mrp.h"
+
+#define AVB_MSRP_ETH 0x22ea
+#define AVB_MSRP_MAC { 0x01, 0x80, 0xc2, 0x00, 0x00, 0xe };
+
+#define AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE 1
+#define AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED 2
+#define AVB_MSRP_ATTRIBUTE_TYPE_LISTENER 3
+#define AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN 4
+#define AVB_MSRP_ATTRIBUTE_TYPE_VALID(t) ((t)>=1 && (t)<=4)
+
+struct avb_packet_msrp_msg {
+ uint8_t attribute_type;
+ uint8_t attribute_length;
+ uint16_t attribute_list_length;
+ uint8_t attribute_list[0];
+} __attribute__ ((__packed__));
+
+#define AVB_MSRP_TSPEC_MAX_INTERVAL_FRAMES_DEFAULT 1
+#define AVB_MSRP_RANK_DEFAULT 1
+#define AVB_MSRP_PRIORITY_DEFAULT 3
+
+struct avb_packet_msrp_talker {
+ uint64_t stream_id;
+ uint8_t dest_addr[6];
+ uint16_t vlan_id;
+ uint16_t tspec_max_frame_size;
+ uint16_t tspec_max_interval_frames;
+#if __BYTE_ORDER == __BIG_ENDIAN
+ unsigned priority:3;
+ unsigned rank:1;
+ unsigned reserved:4;
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+ unsigned reserved:4;
+ unsigned rank:1;
+ unsigned priority:3;
+#endif
+ uint32_t accumulated_latency;
+} __attribute__ ((__packed__));
+
+/* failure codes */
+#define AVB_MRP_FAIL_BANDWIDTH 1
+#define AVB_MRP_FAIL_BRIDGE 2
+#define AVB_MRP_FAIL_TC_BANDWIDTH 3
+#define AVB_MRP_FAIL_ID_BUSY 4
+#define AVB_MRP_FAIL_DSTADDR_BUSY 5
+#define AVB_MRP_FAIL_PREEMPTED 6
+#define AVB_MRP_FAIL_LATENCY_CHNG 7
+#define AVB_MRP_FAIL_PORT_NOT_AVB 8
+#define AVB_MRP_FAIL_DSTADDR_FULL 9
+#define AVB_MRP_FAIL_AVB_MRP_RESOURCE 10
+#define AVB_MRP_FAIL_MMRP_RESOURCE 11
+#define AVB_MRP_FAIL_DSTADDR_FAIL 12
+#define AVB_MRP_FAIL_PRIO_NOT_SR 13
+#define AVB_MRP_FAIL_FRAME_SIZE 14
+#define AVB_MRP_FAIL_FANIN_EXCEED 15
+#define AVB_MRP_FAIL_STREAM_CHANGE 16
+#define AVB_MRP_FAIL_VLAN_BLOCKED 17
+#define AVB_MRP_FAIL_VLAN_DISABLED 18
+#define AVB_MRP_FAIL_SR_PRIO_ERR 19
+
+struct avb_packet_msrp_talker_fail {
+ struct avb_packet_msrp_talker talker;
+ uint64_t bridge_id;
+ uint8_t failure_code;
+} __attribute__ ((__packed__));
+
+struct avb_packet_msrp_listener {
+ uint64_t stream_id;
+} __attribute__ ((__packed__));
+
+/* domain discovery */
+#define AVB_MSRP_CLASS_ID_DEFAULT 6
+#define AVB_DEFAULT_VLAN 2
+
+struct avb_packet_msrp_domain {
+ uint8_t sr_class_id;
+ uint8_t sr_class_priority;
+ uint16_t sr_class_vid;
+} __attribute__ ((__packed__));
+
+#define AVB_MSRP_LISTENER_PARAM_IGNORE 0
+#define AVB_MSRP_LISTENER_PARAM_ASKING_FAILED 1
+#define AVB_MSRP_LISTENER_PARAM_READY 2
+#define AVB_MSRP_LISTENER_PARAM_READY_FAILED 3
+
+struct avb_msrp_attribute {
+ struct avb_mrp_attribute *mrp;
+ uint8_t type;
+ uint8_t param;
+ union {
+ struct avb_packet_msrp_talker talker;
+ struct avb_packet_msrp_talker_fail talker_fail;
+ struct avb_packet_msrp_listener listener;
+ struct avb_packet_msrp_domain domain;
+ } attr;
+};
+
+struct avb_msrp;
+
+struct avb_msrp_attribute *avb_msrp_attribute_new(struct avb_msrp *msrp,
+ uint8_t type);
+
+struct avb_msrp *avb_msrp_register(struct server *server);
+
+#endif /* AVB_MSRP_H */
diff --git a/src/modules/module-avb/mvrp.c b/src/modules/module-avb/mvrp.c
new file mode 100644
index 0000000..2f5f6ea
--- /dev/null
+++ b/src/modules/module-avb/mvrp.c
@@ -0,0 +1,297 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <unistd.h>
+
+#include <pipewire/pipewire.h>
+
+#include "mvrp.h"
+
+static const uint8_t mvrp_mac[6] = AVB_MVRP_MAC;
+
+struct attr {
+ struct avb_mvrp_attribute attr;
+ struct spa_hook listener;
+ struct spa_list link;
+ struct mvrp *mvrp;
+};
+
+struct mvrp {
+ struct server *server;
+ struct spa_hook server_listener;
+ struct spa_hook mrp_listener;
+
+ struct spa_source *source;
+
+ struct spa_list attributes;
+};
+
+static bool mvrp_check_header(void *data, const void *hdr, size_t *hdr_size, bool *has_params)
+{
+ const struct avb_packet_mvrp_msg *msg = hdr;
+ uint8_t attr_type = msg->attribute_type;
+
+ if (!AVB_MVRP_ATTRIBUTE_TYPE_VALID(attr_type))
+ return false;
+
+ *hdr_size = sizeof(*msg);
+ *has_params = false;
+ return true;
+}
+
+static int mvrp_attr_event(void *data, uint64_t now, uint8_t attribute_type, uint8_t event)
+{
+ struct mvrp *mvrp = data;
+ struct attr *a;
+ spa_list_for_each(a, &mvrp->attributes, link)
+ if (a->attr.type == attribute_type)
+ avb_mrp_attribute_rx_event(a->attr.mrp, now, event);
+ return 0;
+}
+
+static void debug_vid(const struct avb_packet_mvrp_vid *t)
+{
+ pw_log_info("vid");
+ pw_log_info(" %d", ntohs(t->vlan));
+}
+
+static int process_vid(struct mvrp *mvrp, uint64_t now, uint8_t attr_type,
+ const void *m, uint8_t event, uint8_t param, int num)
+{
+ return mvrp_attr_event(mvrp, now, attr_type, event);
+}
+
+static int encode_vid(struct mvrp *mvrp, struct attr *a, void *m)
+{
+ struct avb_packet_mvrp_msg *msg = m;
+ struct avb_packet_mrp_vector *v;
+ struct avb_packet_mvrp_vid *d;
+ struct avb_packet_mrp_footer *f;
+ uint8_t *ev;
+ size_t attr_list_length = sizeof(*v) + sizeof(*d) + sizeof(*f) + 1;
+
+ msg->attribute_type = AVB_MVRP_ATTRIBUTE_TYPE_VID;
+ msg->attribute_length = sizeof(*d);
+
+ v = (struct avb_packet_mrp_vector *)msg->attribute_list;
+ v->lva = 0;
+ AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1);
+
+ d = (struct avb_packet_mvrp_vid *)v->first_value;
+ *d = a->attr.attr.vid;
+
+ ev = SPA_PTROFF(d, sizeof(*d), uint8_t);
+ *ev = a->attr.mrp->pending_send * 36;
+
+ f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer);
+ f->end_mark = 0;
+
+ return attr_list_length + sizeof(*msg);
+}
+
+static void notify_vid(struct mvrp *mvrp, uint64_t now, struct attr *attr, uint8_t notify)
+{
+ pw_log_info("> notify vid: %s", avb_mrp_notify_name(notify));
+ debug_vid(&attr->attr.attr.vid);
+}
+
+static const struct {
+ const char *name;
+ int (*process) (struct mvrp *mvrp, uint64_t now, uint8_t attr_type,
+ const void *m, uint8_t event, uint8_t param, int num);
+ int (*encode) (struct mvrp *mvrp, struct attr *attr, void *m);
+ void (*notify) (struct mvrp *mvrp, uint64_t now, struct attr *attr, uint8_t notify);
+} dispatch[] = {
+ [AVB_MVRP_ATTRIBUTE_TYPE_VID] = { "vid", process_vid, encode_vid, notify_vid },
+};
+
+static int mvrp_process(void *data, uint64_t now, uint8_t attribute_type, const void *value,
+ uint8_t event, uint8_t param, int index)
+{
+ struct mvrp *mvrp = data;
+ return dispatch[attribute_type].process(mvrp, now,
+ attribute_type, value, event, param, index);
+}
+
+static const struct avb_mrp_parse_info info = {
+ AVB_VERSION_MRP_PARSE_INFO,
+ .check_header = mvrp_check_header,
+ .attr_event = mvrp_attr_event,
+ .process = mvrp_process,
+};
+
+static int mvrp_message(struct mvrp *mvrp, uint64_t now, const void *message, int len)
+{
+ pw_log_debug("MVRP");
+ return avb_mrp_parse_packet(mvrp->server->mrp,
+ now, message, len, &info, mvrp);
+}
+
+static void on_socket_data(void *data, int fd, uint32_t mask)
+{
+ struct mvrp *mvrp = data;
+ struct timespec now;
+
+ if (mask & SPA_IO_IN) {
+ int len;
+ uint8_t buffer[2048];
+
+ len = recv(fd, buffer, sizeof(buffer), 0);
+
+ if (len < 0) {
+ pw_log_warn("got recv error: %m");
+ }
+ else if (len < (int)sizeof(struct avb_packet_header)) {
+ pw_log_warn("short packet received (%d < %d)", len,
+ (int)sizeof(struct avb_packet_header));
+ } else {
+ clock_gettime(CLOCK_REALTIME, &now);
+ mvrp_message(mvrp, SPA_TIMESPEC_TO_NSEC(&now), buffer, len);
+ }
+ }
+}
+
+static void mvrp_destroy(void *data)
+{
+ struct mvrp *mvrp = data;
+ spa_hook_remove(&mvrp->server_listener);
+ pw_loop_destroy_source(mvrp->server->impl->loop, mvrp->source);
+ free(mvrp);
+}
+
+static const struct server_events server_events = {
+ AVB_VERSION_SERVER_EVENTS,
+ .destroy = mvrp_destroy,
+};
+
+static void mvrp_notify(void *data, uint64_t now, uint8_t notify)
+{
+ struct attr *a = data;
+ struct mvrp *mvrp = a->mvrp;
+ return dispatch[a->attr.type].notify(mvrp, now, a, notify);
+}
+
+static const struct avb_mrp_attribute_events mrp_attr_events = {
+ AVB_VERSION_MRP_ATTRIBUTE_EVENTS,
+ .notify = mvrp_notify,
+};
+
+struct avb_mvrp_attribute *avb_mvrp_attribute_new(struct avb_mvrp *m,
+ uint8_t type)
+{
+ struct mvrp *mvrp = (struct mvrp*)m;
+ struct avb_mrp_attribute *attr;
+ struct attr *a;
+
+ attr = avb_mrp_attribute_new(mvrp->server->mrp, sizeof(struct attr));
+
+ a = attr->user_data;
+ a->attr.mrp = attr;
+ a->attr.type = type;
+ spa_list_append(&mvrp->attributes, &a->link);
+ avb_mrp_attribute_add_listener(attr, &a->listener, &mrp_attr_events, a);
+
+ return &a->attr;
+}
+
+static void mvrp_event(void *data, uint64_t now, uint8_t event)
+{
+ struct mvrp *mvrp = data;
+ uint8_t buffer[2048];
+ struct avb_packet_mrp *p = (struct avb_packet_mrp*)buffer;
+ struct avb_packet_mrp_footer *f;
+ void *msg = SPA_PTROFF(buffer, sizeof(*p), void);
+ struct attr *a;
+ int len, count = 0;
+ size_t total = sizeof(*p) + 2;
+
+ p->version = AVB_MRP_PROTOCOL_VERSION;
+
+ spa_list_for_each(a, &mvrp->attributes, link) {
+ if (!a->attr.mrp->pending_send)
+ continue;
+ if (dispatch[a->attr.type].encode == NULL)
+ continue;
+
+ pw_log_debug("send %s %s", dispatch[a->attr.type].name,
+ avb_mrp_send_name(a->attr.mrp->pending_send));
+
+ len = dispatch[a->attr.type].encode(mvrp, a, msg);
+ if (len < 0)
+ break;
+
+ count++;
+ msg = SPA_PTROFF(msg, len, void);
+ total += len;
+ }
+ f = (struct avb_packet_mrp_footer *)msg;
+ f->end_mark = 0;
+
+ if (count > 0)
+ avb_server_send_packet(mvrp->server, mvrp_mac, AVB_MVRP_ETH,
+ buffer, total);
+}
+
+static const struct avb_mrp_events mrp_events = {
+ AVB_VERSION_MRP_EVENTS,
+ .event = mvrp_event,
+};
+
+struct avb_mvrp *avb_mvrp_register(struct server *server)
+{
+ struct mvrp *mvrp;
+ int fd, res;
+
+ fd = avb_server_make_socket(server, AVB_MVRP_ETH, mvrp_mac);
+ if (fd < 0) {
+ errno = -fd;
+ return NULL;
+ }
+ mvrp = calloc(1, sizeof(*mvrp));
+ if (mvrp == NULL) {
+ res = -errno;
+ goto error_close;
+ }
+
+ mvrp->server = server;
+ spa_list_init(&mvrp->attributes);
+
+ mvrp->source = pw_loop_add_io(server->impl->loop, fd, SPA_IO_IN, true, on_socket_data, mvrp);
+ if (mvrp->source == NULL) {
+ res = -errno;
+ pw_log_error("mvrp %p: can't create mvrp source: %m", mvrp);
+ goto error_no_source;
+ }
+ avdecc_server_add_listener(server, &mvrp->server_listener, &server_events, mvrp);
+ avb_mrp_add_listener(server->mrp, &mvrp->mrp_listener, &mrp_events, mvrp);
+
+ return (struct avb_mvrp*)mvrp;
+
+error_no_source:
+ free(mvrp);
+error_close:
+ close(fd);
+ errno = -res;
+ return NULL;
+}
diff --git a/src/modules/module-avb/mvrp.h b/src/modules/module-avb/mvrp.h
new file mode 100644
index 0000000..da3d5dc
--- /dev/null
+++ b/src/modules/module-avb/mvrp.h
@@ -0,0 +1,62 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_MVRP_H
+#define AVB_MVRP_H
+
+#include "mrp.h"
+#include "internal.h"
+
+#define AVB_MVRP_ETH 0x88f5
+#define AVB_MVRP_MAC { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x21 };
+
+struct avb_packet_mvrp_msg {
+ uint8_t attribute_type;
+ uint8_t attribute_length;
+ uint8_t attribute_list[0];
+} __attribute__ ((__packed__));
+
+#define AVB_MVRP_ATTRIBUTE_TYPE_VID 1
+#define AVB_MVRP_ATTRIBUTE_TYPE_VALID(t) ((t)==1)
+
+struct avb_packet_mvrp_vid {
+ uint16_t vlan;
+} __attribute__ ((__packed__));
+
+struct avb_mvrp;
+
+struct avb_mvrp_attribute {
+ struct avb_mrp_attribute *mrp;
+ uint8_t type;
+ union {
+ struct avb_packet_mvrp_vid vid;
+ } attr;
+};
+
+struct avb_mvrp_attribute *avb_mvrp_attribute_new(struct avb_mvrp *mvrp,
+ uint8_t type);
+
+struct avb_mvrp *avb_mvrp_register(struct server *server);
+
+#endif /* AVB_MVRP_H */
diff --git a/src/modules/module-avb/packets.h b/src/modules/module-avb/packets.h
new file mode 100644
index 0000000..f35738a
--- /dev/null
+++ b/src/modules/module-avb/packets.h
@@ -0,0 +1,101 @@
+/* Spa AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_PACKETS_H
+#define AVB_PACKETS_H
+
+#include <arpa/inet.h>
+
+#define AVB_SUBTYPE_61883_IIDC 0x00
+#define AVB_SUBTYPE_MMA_STREAM 0x01
+#define AVB_SUBTYPE_AAF 0x02
+#define AVB_SUBTYPE_CVF 0x03
+#define AVB_SUBTYPE_CRF 0x04
+#define AVB_SUBTYPE_TSCF 0x05
+#define AVB_SUBTYPE_SVF 0x06
+#define AVB_SUBTYPE_RVF 0x07
+#define AVB_SUBTYPE_AEF_CONTINUOUS 0x6E
+#define AVB_SUBTYPE_VSF_STREAM 0x6F
+#define AVB_SUBTYPE_EF_STREAM 0x7F
+#define AVB_SUBTYPE_NTSCF 0x82
+#define AVB_SUBTYPE_ESCF 0xEC
+#define AVB_SUBTYPE_EECF 0xED
+#define AVB_SUBTYPE_AEF_DISCRETE 0xEE
+#define AVB_SUBTYPE_ADP 0xFA
+#define AVB_SUBTYPE_AECP 0xFB
+#define AVB_SUBTYPE_ACMP 0xFC
+#define AVB_SUBTYPE_MAAP 0xFE
+#define AVB_SUBTYPE_EF_CONTROL 0xFF
+
+struct avb_ethernet_header {
+ uint8_t dest[6];
+ uint8_t src[6];
+ uint16_t type;
+} __attribute__ ((__packed__));
+
+struct avb_frame_header {
+ uint8_t dest[6];
+ uint8_t src[6];
+ uint16_t type; /* 802.1Q Virtual Lan 0x8100 */
+ uint16_t prio_cfi_id;
+ uint16_t etype;
+} __attribute__ ((__packed__));
+
+struct avb_packet_header {
+ uint8_t subtype;
+#if __BYTE_ORDER == __BIG_ENDIAN
+ unsigned sv:1; /* stream_id valid */
+ unsigned version:3;
+ unsigned subtype_data1:4;
+
+ unsigned subtype_data2:5;
+ unsigned len1:3;
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+ unsigned subtype_data1:4;
+ unsigned version:3;
+ unsigned sv:1;
+
+ unsigned len1:3;
+ unsigned subtype_data2:5;
+#elif
+#error "Unknown byte order"
+#endif
+ uint8_t len2:8;
+} __attribute__ ((__packed__));
+
+#define AVB_PACKET_SET_SUBTYPE(p,v) ((p)->subtype = (v))
+#define AVB_PACKET_SET_SV(p,v) ((p)->sv = (v))
+#define AVB_PACKET_SET_VERSION(p,v) ((p)->version = (v))
+#define AVB_PACKET_SET_SUB1(p,v) ((p)->subtype_data1 = (v))
+#define AVB_PACKET_SET_SUB2(p,v) ((p)->subtype_data2 = (v))
+#define AVB_PACKET_SET_LENGTH(p,v) ((p)->len1 = ((v) >> 8),(p)->len2 = (v))
+
+#define AVB_PACKET_GET_SUBTYPE(p) ((p)->subtype)
+#define AVB_PACKET_GET_SV(p) ((p)->sv)
+#define AVB_PACKET_GET_VERSION(p) ((p)->version)
+#define AVB_PACKET_GET_SUB1(p) ((p)->subtype_data1)
+#define AVB_PACKET_GET_SUB2(p) ((p)->subtype_data2)
+#define AVB_PACKET_GET_LENGTH(p) ((p)->len1 << 8 | (p)->len2)
+
+#endif /* AVB_PACKETS_H */
diff --git a/src/modules/module-avb/srp.c b/src/modules/module-avb/srp.c
new file mode 100644
index 0000000..89d75f1
--- /dev/null
+++ b/src/modules/module-avb/srp.c
@@ -0,0 +1,59 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/pipewire.h>
+
+#include "srp.h"
+
+struct srp {
+ struct server *server;
+ struct spa_hook server_listener;
+};
+
+static void srp_destroy(void *data)
+{
+ struct srp *srp = data;
+ spa_hook_remove(&srp->server_listener);
+ free(srp);
+}
+
+static const struct server_events server_events = {
+ AVB_VERSION_SERVER_EVENTS,
+ .destroy = srp_destroy,
+};
+
+int avb_srp_register(struct server *server)
+{
+ struct srp *srp;
+
+ srp = calloc(1, sizeof(*srp));
+ if (srp == NULL)
+ return -errno;
+
+ srp->server = server;
+
+ avdecc_server_add_listener(server, &srp->server_listener, &server_events, srp);
+
+ return 0;
+}
diff --git a/src/modules/module-avb/srp.h b/src/modules/module-avb/srp.h
new file mode 100644
index 0000000..853321f
--- /dev/null
+++ b/src/modules/module-avb/srp.h
@@ -0,0 +1,32 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_SRP_H
+#define AVB_SRP_H
+
+#include "internal.h"
+
+int avb_srp_register(struct server *server);
+
+#endif /* AVB_SRP_H */
diff --git a/src/modules/module-avb/stream.c b/src/modules/module-avb/stream.c
new file mode 100644
index 0000000..0f5b148
--- /dev/null
+++ b/src/modules/module-avb/stream.c
@@ -0,0 +1,589 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <linux/if_ether.h>
+#include <linux/if_packet.h>
+#include <linux/net_tstamp.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+
+#include <spa/debug/mem.h>
+#include <spa/pod/builder.h>
+#include <spa/param/audio/format-utils.h>
+
+#include "iec61883.h"
+#include "stream.h"
+#include "utils.h"
+#include "aecp-aem-descriptors.h"
+
+static void on_stream_destroy(void *d)
+{
+ struct stream *stream = d;
+ spa_hook_remove(&stream->stream_listener);
+ stream->stream = NULL;
+}
+
+static void on_source_stream_process(void *data)
+{
+ struct stream *stream = data;
+ struct pw_buffer *buf;
+ struct spa_data *d;
+ uint32_t index, n_bytes;
+ int32_t avail, wanted;
+
+ if ((buf = pw_stream_dequeue_buffer(stream->stream)) == NULL) {
+ pw_log_debug("out of buffers: %m");
+ return;
+ }
+
+ d = buf->buffer->datas;
+
+ wanted = buf->requested ? buf->requested * stream->stride : d[0].maxsize;
+
+ n_bytes = SPA_MIN(d[0].maxsize, (uint32_t)wanted);
+
+ avail = spa_ringbuffer_get_read_index(&stream->ring, &index);
+
+ if (avail < wanted) {
+ pw_log_debug("capture underrun %d < %d", avail, wanted);
+ memset(d[0].data, 0, n_bytes);
+ } else {
+ spa_ringbuffer_read_data(&stream->ring,
+ stream->buffer_data,
+ stream->buffer_size,
+ index % stream->buffer_size,
+ d[0].data, n_bytes);
+ index += n_bytes;
+ spa_ringbuffer_read_update(&stream->ring, index);
+ }
+
+ d[0].chunk->size = n_bytes;
+ d[0].chunk->stride = stream->stride;
+ d[0].chunk->offset = 0;
+ buf->size = n_bytes / stream->stride;
+
+ pw_stream_queue_buffer(stream->stream, buf);
+}
+
+static const struct pw_stream_events source_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = on_stream_destroy,
+ .process = on_source_stream_process
+};
+
+static inline void
+set_iovec(struct spa_ringbuffer *rbuf, void *buffer, uint32_t size,
+ uint32_t offset, struct iovec *iov, uint32_t len)
+{
+ iov[0].iov_len = SPA_MIN(len, size - offset);
+ iov[0].iov_base = SPA_PTROFF(buffer, offset, void);
+ iov[1].iov_len = len - iov[0].iov_len;
+ iov[1].iov_base = buffer;
+}
+
+static int flush_write(struct stream *stream, uint64_t current_time)
+{
+ int32_t avail;
+ uint32_t index;
+ uint64_t ptime, txtime;
+ int pdu_count;
+ ssize_t n;
+ struct avb_frame_header *h = (void*)stream->pdu;
+ struct avb_packet_iec61883 *p = SPA_PTROFF(h, sizeof(*h), void);
+ uint8_t dbc;
+
+ avail = spa_ringbuffer_get_read_index(&stream->ring, &index);
+
+ pdu_count = (avail / stream->stride) / stream->frames_per_pdu;
+
+ txtime = current_time + stream->t_uncertainty;
+ ptime = txtime + stream->mtt;
+ dbc = stream->dbc;
+
+ while (pdu_count--) {
+ *(uint64_t*)CMSG_DATA(stream->cmsg) = txtime;
+
+ set_iovec(&stream->ring,
+ stream->buffer_data,
+ stream->buffer_size,
+ index % stream->buffer_size,
+ &stream->iov[1], stream->payload_size);
+
+ p->seq_num = stream->pdu_seq++;
+ p->tv = 1;
+ p->timestamp = ptime;
+ p->dbc = dbc;
+
+ n = sendmsg(stream->source->fd, &stream->msg, MSG_NOSIGNAL);
+ if (n < 0 || n != (ssize_t)stream->pdu_size) {
+ pw_log_error("sendmsg() failed %zd != %zd: %m",
+ n, stream->pdu_size);
+ }
+ txtime += stream->pdu_period;
+ ptime += stream->pdu_period;
+ index += stream->payload_size;
+ dbc += stream->frames_per_pdu;
+ }
+ stream->dbc = dbc;
+ spa_ringbuffer_read_update(&stream->ring, index);
+ return 0;
+}
+
+static void on_sink_stream_process(void *data)
+{
+ struct stream *stream = data;
+ struct pw_buffer *buf;
+ struct spa_data *d;
+ int32_t filled;
+ uint32_t index, offs, avail, size;
+ struct timespec now;
+
+ if ((buf = pw_stream_dequeue_buffer(stream->stream)) == NULL) {
+ pw_log_debug("out of buffers: %m");
+ return;
+ }
+
+ d = buf->buffer->datas;
+
+ offs = SPA_MIN(d[0].chunk->offset, d[0].maxsize);
+ size = SPA_MIN(d[0].chunk->size, d[0].maxsize - offs);
+ avail = size - offs;
+
+ filled = spa_ringbuffer_get_write_index(&stream->ring, &index);
+
+ if (filled >= (int32_t)stream->buffer_size) {
+ pw_log_warn("playback overrun %d >= %zd", filled, stream->buffer_size);
+ } else {
+ spa_ringbuffer_write_data(&stream->ring,
+ stream->buffer_data,
+ stream->buffer_size,
+ index % stream->buffer_size,
+ SPA_PTROFF(d[0].data, offs, void), avail);
+ index += avail;
+ spa_ringbuffer_write_update(&stream->ring, index);
+ }
+ pw_stream_queue_buffer(stream->stream, buf);
+
+ clock_gettime(CLOCK_TAI, &now);
+ flush_write(stream, SPA_TIMESPEC_TO_NSEC(&now));
+}
+
+static void setup_pdu(struct stream *stream)
+{
+ struct avb_frame_header *h;
+ struct avb_packet_iec61883 *p;
+ ssize_t payload_size, hdr_size, pdu_size;
+
+ spa_memzero(stream->pdu, sizeof(stream->pdu));
+ h = (struct avb_frame_header*)stream->pdu;
+ p = SPA_PTROFF(h, sizeof(*h), void);
+
+ hdr_size = sizeof(*h) + sizeof(*p);
+ payload_size = stream->stride * stream->frames_per_pdu;
+ pdu_size = hdr_size + payload_size;
+
+ h->type = htons(0x8100);
+ h->prio_cfi_id = htons((stream->prio << 13) | stream->vlan_id);
+ h->etype = htons(0x22f0);
+
+ if (stream->direction == SPA_DIRECTION_OUTPUT) {
+ p->subtype = AVB_SUBTYPE_61883_IIDC;
+ p->sv = 1;
+ p->stream_id = htobe64(stream->id);
+ p->data_len = htons(payload_size+8);
+ p->tag = 0x1;
+ p->channel = 0x1f;
+ p->tcode = 0xa;
+ p->sid = 0x3f;
+ p->dbs = stream->info.info.raw.channels;
+ p->qi2 = 0x2;
+ p->format_id = 0x10;
+ p->fdf = 0x2;
+ p->syt = htons(0x0008);
+ }
+ stream->hdr_size = hdr_size;
+ stream->payload_size = payload_size;
+ stream->pdu_size = pdu_size;
+}
+
+static int setup_msg(struct stream *stream)
+{
+ stream->iov[0].iov_base = stream->pdu;
+ stream->iov[0].iov_len = stream->hdr_size;
+ stream->iov[1].iov_base = SPA_PTROFF(stream->pdu, stream->hdr_size, void);
+ stream->iov[1].iov_len = stream->payload_size;
+ stream->iov[2].iov_base = SPA_PTROFF(stream->pdu, stream->hdr_size, void);
+ stream->iov[2].iov_len = 0;
+ stream->msg.msg_name = &stream->sock_addr;
+ stream->msg.msg_namelen = sizeof(stream->sock_addr);
+ stream->msg.msg_iov = stream->iov;
+ stream->msg.msg_iovlen = 3;
+ stream->msg.msg_control = stream->control;
+ stream->msg.msg_controllen = sizeof(stream->control);
+ stream->cmsg = CMSG_FIRSTHDR(&stream->msg);
+ stream->cmsg->cmsg_level = SOL_SOCKET;
+ stream->cmsg->cmsg_type = SCM_TXTIME;
+ stream->cmsg->cmsg_len = CMSG_LEN(sizeof(__u64));
+ return 0;
+}
+
+static const struct pw_stream_events sink_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = on_stream_destroy,
+ .process = on_sink_stream_process
+};
+
+struct stream *server_create_stream(struct server *server,
+ enum spa_direction direction, uint16_t index)
+{
+ struct stream *stream;
+ const struct descriptor *desc;
+ uint32_t n_params;
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b;
+ int res;
+
+ desc = server_find_descriptor(server,
+ direction == SPA_DIRECTION_INPUT ?
+ AVB_AEM_DESC_STREAM_INPUT :
+ AVB_AEM_DESC_STREAM_OUTPUT, index);
+ if (desc == NULL)
+ return NULL;
+
+ stream = calloc(1, sizeof(*stream));
+ if (stream == NULL)
+ return NULL;
+
+ stream->server = server;
+ stream->direction = direction;
+ stream->index = index;
+ stream->desc = desc;
+ spa_list_append(&server->streams, &stream->link);
+
+ stream->prio = AVB_MSRP_PRIORITY_DEFAULT;
+ stream->vlan_id = AVB_DEFAULT_VLAN;
+
+ stream->id = (uint64_t)server->mac_addr[0] << 56 |
+ (uint64_t)server->mac_addr[1] << 48 |
+ (uint64_t)server->mac_addr[2] << 40 |
+ (uint64_t)server->mac_addr[3] << 32 |
+ (uint64_t)server->mac_addr[4] << 24 |
+ (uint64_t)server->mac_addr[5] << 16 |
+ htons(index);
+
+ stream->vlan_attr = avb_mvrp_attribute_new(server->mvrp,
+ AVB_MVRP_ATTRIBUTE_TYPE_VID);
+ stream->vlan_attr->attr.vid.vlan = htons(stream->vlan_id);
+
+ stream->buffer_data = calloc(1, BUFFER_SIZE);
+ stream->buffer_size = BUFFER_SIZE;
+ spa_ringbuffer_init(&stream->ring);
+
+ if (direction == SPA_DIRECTION_INPUT) {
+ stream->stream = pw_stream_new(server->impl->core, "source",
+ pw_properties_new(
+ PW_KEY_MEDIA_CLASS, "Audio/Source",
+ PW_KEY_NODE_NAME, "avb.source",
+ PW_KEY_NODE_DESCRIPTION, "AVB Source",
+ PW_KEY_NODE_WANT_DRIVER, "true",
+ NULL));
+ } else {
+ stream->stream = pw_stream_new(server->impl->core, "sink",
+ pw_properties_new(
+ PW_KEY_MEDIA_CLASS, "Audio/Sink",
+ PW_KEY_NODE_NAME, "avb.sink",
+ PW_KEY_NODE_DESCRIPTION, "AVB Sink",
+ PW_KEY_NODE_WANT_DRIVER, "true",
+ NULL));
+ }
+ if (stream->stream == NULL)
+ goto error_free;
+
+ pw_stream_add_listener(stream->stream,
+ &stream->stream_listener,
+ direction == SPA_DIRECTION_INPUT ?
+ &source_stream_events :
+ &sink_stream_events,
+ stream);
+
+ stream->info.info.raw.format = SPA_AUDIO_FORMAT_S24_32_BE;
+ stream->info.info.raw.flags = SPA_AUDIO_FLAG_UNPOSITIONED;
+ stream->info.info.raw.rate = 48000;
+ stream->info.info.raw.channels = 8;
+ stream->stride = stream->info.info.raw.channels * 4;
+
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_format_audio_raw_build(&b,
+ SPA_PARAM_EnumFormat, &stream->info.info.raw);
+
+ if ((res = pw_stream_connect(stream->stream,
+ pw_direction_reverse(direction),
+ PW_ID_ANY,
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_INACTIVE |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0)
+ goto error_free_stream;
+
+ stream->frames_per_pdu = 6;
+ stream->pdu_period = SPA_NSEC_PER_SEC * stream->frames_per_pdu /
+ stream->info.info.raw.rate;
+
+ setup_pdu(stream);
+ setup_msg(stream);
+
+ stream->listener_attr = avb_msrp_attribute_new(server->msrp,
+ AVB_MSRP_ATTRIBUTE_TYPE_LISTENER);
+ stream->talker_attr = avb_msrp_attribute_new(server->msrp,
+ AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE);
+ stream->talker_attr->attr.talker.vlan_id = htons(stream->vlan_id);
+ stream->talker_attr->attr.talker.tspec_max_frame_size = htons(32 + stream->frames_per_pdu * stream->stride);
+ stream->talker_attr->attr.talker.tspec_max_interval_frames =
+ htons(AVB_MSRP_TSPEC_MAX_INTERVAL_FRAMES_DEFAULT);
+ stream->talker_attr->attr.talker.priority = stream->prio;
+ stream->talker_attr->attr.talker.rank = AVB_MSRP_RANK_DEFAULT;
+ stream->talker_attr->attr.talker.accumulated_latency = htonl(95);
+
+ return stream;
+
+error_free_stream:
+ pw_stream_destroy(stream->stream);
+ errno = -res;
+error_free:
+ free(stream);
+ return NULL;
+}
+
+void stream_destroy(struct stream *stream)
+{
+ avb_mrp_attribute_destroy(stream->listener_attr->mrp);
+ spa_list_remove(&stream->link);
+ free(stream);
+}
+
+static int setup_socket(struct stream *stream)
+{
+ struct server *server = stream->server;
+ int fd, res;
+ char buf[128];
+ struct ifreq req;
+
+ fd = socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, htons(ETH_P_ALL));
+ if (fd < 0) {
+ pw_log_error("socket() failed: %m");
+ return -errno;
+ }
+
+ spa_zero(req);
+ snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname);
+ res = ioctl(fd, SIOCGIFINDEX, &req);
+ if (res < 0) {
+ pw_log_error("SIOCGIFINDEX %s failed: %m", server->ifname);
+ res = -errno;
+ goto error_close;
+ }
+
+ spa_zero(stream->sock_addr);
+ stream->sock_addr.sll_family = AF_PACKET;
+ stream->sock_addr.sll_protocol = htons(ETH_P_TSN);
+ stream->sock_addr.sll_ifindex = req.ifr_ifindex;
+
+ if (stream->direction == SPA_DIRECTION_OUTPUT) {
+ struct sock_txtime txtime_cfg;
+
+ res = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &stream->prio,
+ sizeof(stream->prio));
+ if (res < 0) {
+ pw_log_error("setsockopt(SO_PRIORITY %d) failed: %m", stream->prio);
+ res = -errno;
+ goto error_close;
+ }
+
+ txtime_cfg.clockid = CLOCK_TAI;
+ txtime_cfg.flags = 0;
+ res = setsockopt(fd, SOL_SOCKET, SO_TXTIME, &txtime_cfg,
+ sizeof(txtime_cfg));
+ if (res < 0) {
+ pw_log_error("setsockopt(SO_TXTIME) failed: %m");
+ res = -errno;
+ goto error_close;
+ }
+ } else {
+ struct packet_mreq mreq;
+
+ res = bind(fd, (struct sockaddr *) &stream->sock_addr, sizeof(stream->sock_addr));
+ if (res < 0) {
+ pw_log_error("bind() failed: %m");
+ res = -errno;
+ goto error_close;
+ }
+
+ spa_zero(mreq);
+ mreq.mr_ifindex = req.ifr_ifindex;
+ mreq.mr_type = PACKET_MR_MULTICAST;
+ mreq.mr_alen = ETH_ALEN;
+ memcpy(&mreq.mr_address, stream->addr, ETH_ALEN);
+ res = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP,
+ &mreq, sizeof(struct packet_mreq));
+
+ pw_log_info("join %s", avb_utils_format_addr(buf, 128, stream->addr));
+
+ if (res < 0) {
+ pw_log_error("setsockopt(ADD_MEMBERSHIP) failed: %m");
+ res = -errno;
+ goto error_close;
+ }
+ }
+ return fd;
+
+error_close:
+ close(fd);
+ return res;
+}
+
+static void handle_iec61883_packet(struct stream *stream,
+ struct avb_packet_iec61883 *p, int len)
+{
+ uint32_t index, n_bytes;
+ int32_t filled;
+
+ filled = spa_ringbuffer_get_write_index(&stream->ring, &index);
+ n_bytes = ntohs(p->data_len) - 8;
+
+ if (filled + n_bytes > stream->buffer_size) {
+ pw_log_debug("capture overrun");
+ } else {
+ spa_ringbuffer_write_data(&stream->ring,
+ stream->buffer_data,
+ stream->buffer_size,
+ index % stream->buffer_size,
+ p->payload, n_bytes);
+ index += n_bytes;
+ spa_ringbuffer_write_update(&stream->ring, index);
+ }
+}
+
+static void on_socket_data(void *data, int fd, uint32_t mask)
+{
+ struct stream *stream = data;
+
+ if (mask & SPA_IO_IN) {
+ int len;
+ uint8_t buffer[2048];
+
+ len = recv(fd, buffer, sizeof(buffer), 0);
+
+ if (len < 0) {
+ pw_log_warn("got recv error: %m");
+ }
+ else if (len < (int)sizeof(struct avb_packet_header)) {
+ pw_log_warn("short packet received (%d < %d)", len,
+ (int)sizeof(struct avb_packet_header));
+ } else {
+ struct avb_frame_header *h = (void*)buffer;
+ struct avb_packet_iec61883 *p = SPA_PTROFF(h, sizeof(*h), void);
+
+ if (memcmp(h->dest, stream->addr, 6) != 0 ||
+ p->subtype != AVB_SUBTYPE_61883_IIDC)
+ return;
+
+ handle_iec61883_packet(stream, p, len - sizeof(*h));
+ }
+ }
+}
+
+int stream_activate(struct stream *stream, uint64_t now)
+{
+ struct server *server = stream->server;
+ struct avb_frame_header *h = (void*)stream->pdu;
+ int fd, res;
+
+ if (stream->source == NULL) {
+ if ((fd = setup_socket(stream)) < 0)
+ return fd;
+
+ stream->source = pw_loop_add_io(server->impl->loop, fd,
+ SPA_IO_IN, true, on_socket_data, stream);
+ if (stream->source == NULL) {
+ res = -errno;
+ pw_log_error("stream %p: can't create source: %m", stream);
+ close(fd);
+ return res;
+ }
+ }
+
+ avb_mrp_attribute_begin(stream->vlan_attr->mrp, now);
+ avb_mrp_attribute_join(stream->vlan_attr->mrp, now, true);
+
+ if (stream->direction == SPA_DIRECTION_INPUT) {
+ stream->listener_attr->attr.listener.stream_id = htobe64(stream->peer_id);
+ stream->listener_attr->param = AVB_MSRP_LISTENER_PARAM_READY;
+ avb_mrp_attribute_begin(stream->listener_attr->mrp, now);
+ avb_mrp_attribute_join(stream->listener_attr->mrp, now, true);
+
+ stream->talker_attr->attr.talker.stream_id = htobe64(stream->peer_id);
+ avb_mrp_attribute_begin(stream->talker_attr->mrp, now);
+ } else {
+ if ((res = avb_maap_get_address(server->maap, stream->addr, stream->index)) < 0)
+ return res;
+
+ stream->listener_attr->attr.listener.stream_id = htobe64(stream->id);
+ stream->listener_attr->param = AVB_MSRP_LISTENER_PARAM_IGNORE;
+ avb_mrp_attribute_begin(stream->listener_attr->mrp, now);
+
+ stream->talker_attr->attr.talker.stream_id = htobe64(stream->id);
+ memcpy(stream->talker_attr->attr.talker.dest_addr, stream->addr, 6);
+
+ stream->sock_addr.sll_halen = ETH_ALEN;
+ memcpy(&stream->sock_addr.sll_addr, stream->addr, ETH_ALEN);
+ memcpy(h->dest, stream->addr, 6);
+ memcpy(h->src, server->mac_addr, 6);
+ avb_mrp_attribute_begin(stream->talker_attr->mrp, now);
+ avb_mrp_attribute_join(stream->talker_attr->mrp, now, true);
+ }
+ pw_stream_set_active(stream->stream, true);
+ return 0;
+}
+
+int stream_deactivate(struct stream *stream, uint64_t now)
+{
+ pw_stream_set_active(stream->stream, false);
+
+ if (stream->source != NULL) {
+ pw_loop_destroy_source(stream->server->impl->loop, stream->source);
+ stream->source = NULL;
+ }
+
+ avb_mrp_attribute_leave(stream->vlan_attr->mrp, now);
+
+ if (stream->direction == SPA_DIRECTION_INPUT) {
+ avb_mrp_attribute_leave(stream->listener_attr->mrp, now);
+ } else {
+ avb_mrp_attribute_leave(stream->talker_attr->mrp, now);
+ }
+ return 0;
+}
diff --git a/src/modules/module-avb/stream.h b/src/modules/module-avb/stream.h
new file mode 100644
index 0000000..7062e25
--- /dev/null
+++ b/src/modules/module-avb/stream.h
@@ -0,0 +1,104 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_STREAM_H
+#define AVB_STREAM_H
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <linux/if_packet.h>
+#include <net/if.h>
+
+#include <spa/utils/ringbuffer.h>
+#include <spa/param/audio/format.h>
+
+#include <pipewire/pipewire.h>
+
+#define BUFFER_SIZE (1u<<16)
+#define BUFFER_MASK (BUFFER_SIZE-1)
+
+struct stream {
+ struct spa_list link;
+
+ struct server *server;
+
+ uint16_t direction;
+ uint16_t index;
+ const struct descriptor *desc;
+ uint64_t id;
+ uint64_t peer_id;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ uint8_t addr[6];
+ struct spa_source *source;
+ int prio;
+ int vlan_id;
+ int mtt;
+ int t_uncertainty;
+ uint32_t frames_per_pdu;
+ int ptime_tolerance;
+
+ uint8_t pdu[2048];
+ size_t hdr_size;
+ size_t payload_size;
+ size_t pdu_size;
+ int64_t pdu_period;
+ uint8_t pdu_seq;
+ uint8_t prev_seq;
+ uint8_t dbc;
+
+ struct iovec iov[3];
+ struct sockaddr_ll sock_addr;
+ struct msghdr msg;
+ char control[CMSG_SPACE(sizeof(uint64_t))];
+ struct cmsghdr *cmsg;
+
+ struct spa_ringbuffer ring;
+ void *buffer_data;
+ size_t buffer_size;
+
+ uint64_t format;
+ uint32_t stride;
+ struct spa_audio_info info;
+
+ struct avb_msrp_attribute *talker_attr;
+ struct avb_msrp_attribute *listener_attr;
+ struct avb_mvrp_attribute *vlan_attr;
+};
+
+#include "msrp.h"
+#include "mvrp.h"
+#include "maap.h"
+
+struct stream *server_create_stream(struct server *server,
+ enum spa_direction direction, uint16_t index);
+
+void stream_destroy(struct stream *stream);
+
+int stream_activate(struct stream *stream, uint64_t now);
+int stream_deactivate(struct stream *stream, uint64_t now);
+
+#endif /* AVB_STREAM_H */
diff --git a/src/modules/module-avb/utils.h b/src/modules/module-avb/utils.h
new file mode 100644
index 0000000..f626722
--- /dev/null
+++ b/src/modules/module-avb/utils.h
@@ -0,0 +1,86 @@
+/* AVB support
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef AVB_UTILS_H
+#define AVB_UTILS_H
+
+#include <spa/utils/json.h>
+
+#include "internal.h"
+
+static inline char *avb_utils_format_id(char *str, size_t size, const uint64_t id)
+{
+ snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x:%04x",
+ (uint8_t)(id >> 56),
+ (uint8_t)(id >> 48),
+ (uint8_t)(id >> 40),
+ (uint8_t)(id >> 32),
+ (uint8_t)(id >> 24),
+ (uint8_t)(id >> 16),
+ (uint16_t)(id));
+ return str;
+}
+
+static inline int avb_utils_parse_id(const char *str, int len, uint64_t *id)
+{
+ char s[64];
+ uint8_t v[6];
+ uint16_t unique_id;
+ if (spa_json_parse_stringn(str, len, s, sizeof(s)) <= 0)
+ return -EINVAL;
+ if (sscanf(s, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx:%hx",
+ &v[0], &v[1], &v[2], &v[3],
+ &v[4], &v[5], &unique_id) == 7) {
+ *id = (uint64_t) v[0] << 56 |
+ (uint64_t) v[1] << 48 |
+ (uint64_t) v[2] << 40 |
+ (uint64_t) v[3] << 32 |
+ (uint64_t) v[4] << 24 |
+ (uint64_t) v[5] << 16 |
+ unique_id;
+ } else if (!spa_atou64(str, id, 0))
+ return -EINVAL;
+ return 0;
+}
+
+static inline char *avb_utils_format_addr(char *str, size_t size, const uint8_t addr[6])
+{
+ snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x",
+ addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+ return str;
+}
+static inline int avb_utils_parse_addr(const char *str, int len, uint8_t addr[6])
+{
+ char s[64];
+ uint8_t v[6];
+ if (spa_json_parse_stringn(str, len, s, sizeof(s)) <= 0)
+ return -EINVAL;
+ if (sscanf(s, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
+ &v[0], &v[1], &v[2], &v[3], &v[4], &v[5]) != 6)
+ return -EINVAL;
+ memcpy(addr, v, 6);
+ return 0;
+}
+
+#endif /* AVB_UTILS_H */
diff --git a/src/modules/module-client-device.c b/src/modules/module-client-device.c
new file mode 100644
index 0000000..cf3bae3
--- /dev/null
+++ b/src/modules/module-client-device.c
@@ -0,0 +1,232 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+
+#include <pipewire/impl.h>
+
+#include "module-client-device/client-device.h"
+
+/** \page page_module_client_device PipeWire Module: Client Device
+ */
+
+#define NAME "client-device"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Allow clients to create and control remote devices" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct pw_proxy *pw_core_spa_device_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size);
+
+struct pw_protocol *pw_protocol_native_ext_client_device_init(struct pw_context *context);
+
+struct factory_data {
+ struct pw_impl_factory *factory;
+ struct spa_hook factory_listener;
+
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_export_type export_spadevice;
+};
+
+static void *create_object(void *_data,
+ struct pw_resource *resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id)
+{
+ struct factory_data *data = _data;
+ struct pw_impl_factory *factory = data->factory;
+ void *result;
+ struct pw_resource *device_resource;
+ struct pw_impl_client *client;
+ int res;
+
+ if (resource == NULL) {
+ res = -EINVAL;
+ goto error_exit;
+ }
+
+ client = pw_resource_get_client(resource);
+ device_resource = pw_resource_new(client, new_id, PW_PERM_ALL, type, version, 0);
+ if (device_resource == NULL) {
+ res = -errno;
+ goto error_resource;
+ }
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL) {
+ res = -errno;
+ goto error_properties;
+ }
+
+ pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d",
+ pw_global_get_id(pw_impl_factory_get_global(factory)));
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d",
+ pw_global_get_id(pw_impl_client_get_global(client)));
+
+ result = pw_client_device_new(device_resource, properties);
+ if (result == NULL) {
+ res = -errno;
+ goto error_device;
+ }
+ return result;
+
+error_resource:
+ pw_log_error("can't create resource: %s", spa_strerror(res));
+ pw_resource_errorf_id(resource, new_id, res, "can't create resource: %s", spa_strerror(res));
+ goto error_exit;
+error_properties:
+ pw_log_error("can't create properties: %s", spa_strerror(res));
+ pw_resource_errorf_id(resource, new_id, res, "can't create properties: %s", spa_strerror(res));
+ goto error_exit_free;
+error_device:
+ pw_log_error("can't create device: %s", spa_strerror(res));
+ pw_resource_errorf_id(resource, new_id, res, "can't create device: %s", spa_strerror(res));
+ goto error_exit_free;
+
+error_exit_free:
+ pw_resource_remove(device_resource);
+error_exit:
+ errno = -res;
+ return NULL;
+}
+
+static const struct pw_impl_factory_implementation impl_factory = {
+ PW_VERSION_IMPL_FACTORY_IMPLEMENTATION,
+ .create_object = create_object,
+};
+
+static void factory_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->factory_listener);
+ d->factory = NULL;
+ if (d->module)
+ pw_impl_module_destroy(d->module);
+}
+
+static const struct pw_impl_factory_events factory_events = {
+ PW_VERSION_IMPL_FACTORY_EVENTS,
+ .destroy = factory_destroy,
+};
+
+static void module_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->module_listener);
+ spa_list_remove(&d->export_spadevice.link);
+ d->module = NULL;
+ if (d->factory)
+ pw_impl_factory_destroy(d->factory);
+}
+
+static void module_registered(void *data)
+{
+ struct factory_data *d = data;
+ struct pw_impl_module *module = d->module;
+ struct pw_impl_factory *factory = d->factory;
+ struct spa_dict_item items[1];
+ char id[16];
+ int res;
+
+ snprintf(id, sizeof(id), "%d", pw_global_get_id(pw_impl_module_get_global(module)));
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
+ pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
+
+ if ((res = pw_impl_factory_register(factory, NULL)) < 0) {
+ pw_log_error("%p: can't register factory: %s", factory, spa_strerror(res));
+ }
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+ .registered = module_registered,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_factory *factory;
+ struct factory_data *data;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+ factory = pw_context_create_factory(context,
+ "client-device",
+ SPA_TYPE_INTERFACE_Device,
+ SPA_VERSION_DEVICE,
+ pw_properties_new(
+ PW_KEY_FACTORY_USAGE, CLIENT_DEVICE_USAGE,
+ NULL),
+ sizeof(*data));
+ if (factory == NULL)
+ return -errno;
+
+ data = pw_impl_factory_get_user_data(factory);
+ data->factory = factory;
+ data->module = module;
+
+ pw_log_debug("module %p: new", module);
+
+ pw_impl_factory_set_implementation(factory,
+ &impl_factory,
+ data);
+
+ data->export_spadevice.type = SPA_TYPE_INTERFACE_Device;
+ data->export_spadevice.func = pw_core_spa_device_export;
+ if ((res = pw_context_register_export_type(context, &data->export_spadevice)) < 0)
+ goto error;
+
+ pw_protocol_native_ext_client_device_init(context);
+
+ pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data);
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+error:
+ pw_impl_factory_destroy(data->factory);
+ return res;
+}
diff --git a/src/modules/module-client-device/client-device.h b/src/modules/module-client-device/client-device.h
new file mode 100644
index 0000000..ada2b7d
--- /dev/null
+++ b/src/modules/module-client-device/client-device.h
@@ -0,0 +1,44 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_CLIENT_DEVICE_H
+#define PIPEWIRE_CLIENT_DEVICE_H
+
+#include <pipewire/impl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define CLIENT_DEVICE_USAGE "["PW_KEY_DEVICE_NAME"=<string>]"
+
+struct pw_device *
+pw_client_device_new(struct pw_resource *resource,
+ struct pw_properties *properties);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_CLIENT_DEVICE_H */
diff --git a/src/modules/module-client-device/protocol-native.c b/src/modules/module-client-device/protocol-native.c
new file mode 100644
index 0000000..2734aac
--- /dev/null
+++ b/src/modules/module-client-device/protocol-native.c
@@ -0,0 +1,556 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+
+#include <spa/pod/builder.h>
+#include <spa/pod/parser.h>
+#include <spa/utils/result.h>
+
+#include <pipewire/impl.h>
+
+#include <pipewire/extensions/protocol-native.h>
+
+#define MAX_DICT 1024
+#define MAX_PARAM_INFO 128
+
+static inline void push_item(struct spa_pod_builder *b, const struct spa_dict_item *item)
+{
+ const char *str;
+ spa_pod_builder_string(b, item->key);
+ str = item->value;
+ if (spa_strstartswith(str, "pointer:"))
+ str = "";
+ spa_pod_builder_string(b, str);
+}
+
+static inline int parse_item(struct spa_pod_parser *prs, struct spa_dict_item *item)
+{
+ int res;
+ if ((res = spa_pod_parser_get(prs,
+ SPA_POD_String(&item->key),
+ SPA_POD_String(&item->value),
+ NULL)) < 0)
+ return res;
+ if (spa_strstartswith(item->value, "pointer:"))
+ item->value = "";
+ return 0;
+}
+
+#define parse_dict(prs,d) \
+do { \
+ uint32_t i; \
+ if (spa_pod_parser_get(prs, \
+ SPA_POD_Int(&(d)->n_items), NULL) < 0) \
+ return -EINVAL; \
+ if ((d)->n_items > 0) { \
+ if ((d)->n_items > MAX_DICT) \
+ return -ENOSPC; \
+ (d)->items = alloca((d)->n_items * sizeof(struct spa_dict_item)); \
+ for (i = 0; i < (d)->n_items; i++) { \
+ if (parse_item(prs, (struct spa_dict_item *) &(d)->items[i]) < 0) \
+ return -EINVAL; \
+ } \
+ } \
+} while(0)
+
+#define parse_param_info(prs,n_params,params) \
+do { \
+ uint32_t i; \
+ if (spa_pod_parser_get(prs, \
+ SPA_POD_Int(&(n_params)), NULL) < 0) \
+ return -EINVAL; \
+ if (n_params > 0) { \
+ if (n_params > MAX_PARAM_INFO) \
+ return -ENOSPC; \
+ params = alloca(n_params * sizeof(struct spa_param_info)); \
+ for (i = 0; i < n_params; i++) { \
+ if (spa_pod_parser_get(prs, \
+ SPA_POD_Id(&(params[i]).id), \
+ SPA_POD_Int(&(params[i]).flags), NULL) < 0) \
+ return -EINVAL; \
+ } \
+ } \
+} while(0)
+
+static int device_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct spa_device_events *events,
+ void *data)
+{
+ struct pw_resource *resource = object;
+ pw_resource_add_object_listener(resource, listener, events, data);
+ return 0;
+}
+
+static int device_demarshal_add_listener(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ return -ENOTSUP;
+}
+
+static int device_marshal_sync(void *object, int seq)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, SPA_DEVICE_METHOD_SYNC, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int device_demarshal_sync(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ int seq;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq)) < 0)
+ return -EINVAL;
+
+ pw_proxy_notify(proxy, struct spa_device_methods, sync, 0, seq);
+ return 0;
+}
+
+static int device_marshal_enum_params(void *object, int seq,
+ uint32_t id, uint32_t index, uint32_t max,
+ const struct spa_pod *filter)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, SPA_DEVICE_METHOD_ENUM_PARAMS, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(max),
+ SPA_POD_Pod(filter));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int device_demarshal_enum_params(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t id, index, max;
+ int seq;
+ struct spa_pod *filter;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&max),
+ SPA_POD_Pod(&filter)) < 0)
+ return -EINVAL;
+
+ pw_proxy_notify(proxy, struct spa_device_methods, enum_params, 0,
+ seq, id, index, max, filter);
+ return 0;
+}
+
+static int device_marshal_set_param(void *object,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, SPA_DEVICE_METHOD_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int device_demarshal_set_param(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t id, flags;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ pw_proxy_notify(proxy, struct spa_device_methods, set_param, 0,
+ id, flags, param);
+ return 0;
+}
+
+static void device_marshal_info(void *data,
+ const struct spa_device_info *info)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f[2];
+ uint32_t i, n_items;
+
+ b = pw_protocol_native_begin_proxy(proxy, SPA_DEVICE_EVENT_INFO, NULL);
+
+ spa_pod_builder_push_struct(b, &f[0]);
+ if (info) {
+ uint64_t change_mask = info->change_mask;
+
+ change_mask &= SPA_DEVICE_CHANGE_MASK_FLAGS |
+ SPA_DEVICE_CHANGE_MASK_PROPS |
+ SPA_DEVICE_CHANGE_MASK_PARAMS;
+
+ n_items = info->props ? info->props->n_items : 0;
+
+ spa_pod_builder_push_struct(b, &f[1]);
+ spa_pod_builder_add(b,
+ SPA_POD_Long(change_mask),
+ SPA_POD_Long(info->flags),
+ SPA_POD_Int(n_items), NULL);
+ for (i = 0; i < n_items; i++)
+ push_item(b, &info->props->items[i]);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->n_params), NULL);
+ for (i = 0; i < info->n_params; i++) {
+ spa_pod_builder_add(b,
+ SPA_POD_Id(info->params[i].id),
+ SPA_POD_Int(info->params[i].flags), NULL);
+ }
+ spa_pod_builder_pop(b, &f[1]);
+ } else {
+ spa_pod_builder_add(b,
+ SPA_POD_Pod(NULL), NULL);
+ }
+ spa_pod_builder_pop(b, &f[0]);
+
+ pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int device_demarshal_info(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_parser prs;
+ struct spa_pod *ipod;
+ struct spa_device_info info = SPA_DEVICE_INFO_INIT(), *infop;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_PodStruct(&ipod)) < 0)
+ return -EINVAL;
+
+ if (ipod) {
+ struct spa_pod_parser p2;
+ struct spa_pod_frame f2;
+ infop = &info;
+
+ spa_pod_parser_pod(&p2, ipod);
+ if (spa_pod_parser_push_struct(&p2, &f2) < 0 ||
+ spa_pod_parser_get(&p2,
+ SPA_POD_Long(&info.change_mask),
+ SPA_POD_Long(&info.flags), NULL) < 0)
+ return -EINVAL;
+
+ info.change_mask &= SPA_DEVICE_CHANGE_MASK_FLAGS |
+ SPA_DEVICE_CHANGE_MASK_PROPS |
+ SPA_DEVICE_CHANGE_MASK_PARAMS;
+
+ parse_dict(&p2, &props);
+ if (props.n_items > 0)
+ info.props = &props;
+
+ parse_param_info(&p2, info.n_params, info.params);
+ }
+ else {
+ infop = NULL;
+ }
+ pw_resource_notify(resource, struct spa_device_events, info, 0, infop);
+ return 0;
+}
+
+static void device_marshal_result(void *data,
+ int seq, int res, uint32_t type, const void *result)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f[2];
+
+ b = pw_protocol_native_begin_proxy(proxy, SPA_DEVICE_EVENT_RESULT, NULL);
+ spa_pod_builder_push_struct(b, &f[0]);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(seq),
+ SPA_POD_Int(res),
+ SPA_POD_Id(type),
+ NULL);
+
+ switch (type) {
+ case SPA_RESULT_TYPE_DEVICE_PARAMS:
+ {
+ const struct spa_result_device_params *r = result;
+ spa_pod_builder_add(b,
+ SPA_POD_Id(r->id),
+ SPA_POD_Int(r->index),
+ SPA_POD_Int(r->next),
+ SPA_POD_Pod(r->param),
+ NULL);
+ break;
+ }
+ default:
+ break;
+ }
+
+ spa_pod_builder_pop(b, &f[0]);
+
+ pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int device_demarshal_result(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[1];
+ int seq, res;
+ uint32_t type;
+ const void *result;
+ struct spa_result_device_params params;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Int(&res),
+ SPA_POD_Id(&type),
+ NULL) < 0)
+ return -EINVAL;
+
+ switch (type) {
+ case SPA_RESULT_TYPE_DEVICE_PARAMS:
+ if (spa_pod_parser_get(&prs,
+ SPA_POD_Id(&params.id),
+ SPA_POD_Int(&params.index),
+ SPA_POD_Int(&params.next),
+ SPA_POD_PodObject(&params.param),
+ NULL) < 0)
+ return -EINVAL;
+
+ result = &params;
+ break;
+
+ default:
+ result = NULL;
+ break;
+ }
+
+ pw_resource_notify(resource, struct spa_device_events, result, 0, seq, res, type, result);
+ return 0;
+}
+
+static void device_marshal_event(void *data, const struct spa_event *event)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, SPA_DEVICE_EVENT_EVENT, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Pod(event));
+
+ pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int device_demarshal_event(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_parser prs;
+ struct spa_event *event;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_PodObject(&event)) < 0)
+ return -EINVAL;
+
+ pw_resource_notify(resource, struct spa_device_events, event, 0, event);
+ return 0;
+}
+
+static void device_marshal_object_info(void *data, uint32_t id,
+ const struct spa_device_object_info *info)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f[2];
+ uint32_t i, n_items;
+
+ b = pw_protocol_native_begin_proxy(proxy, SPA_DEVICE_EVENT_OBJECT_INFO, NULL);
+ spa_pod_builder_push_struct(b, &f[0]);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(id),
+ NULL);
+ if (info) {
+ uint64_t change_mask = info->change_mask;
+
+ change_mask &= SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS |
+ SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS;
+
+ n_items = info->props ? info->props->n_items : 0;
+
+ spa_pod_builder_push_struct(b, &f[1]);
+ spa_pod_builder_add(b,
+ SPA_POD_String(info->type),
+ SPA_POD_Long(change_mask),
+ SPA_POD_Long(info->flags),
+ SPA_POD_Int(n_items), NULL);
+ for (i = 0; i < n_items; i++)
+ push_item(b, &info->props->items[i]);
+ spa_pod_builder_pop(b, &f[1]);
+ } else {
+ spa_pod_builder_add(b,
+ SPA_POD_Pod(NULL), NULL);
+ }
+ spa_pod_builder_pop(b, &f[0]);
+
+ pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int device_demarshal_object_info(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_parser prs;
+ struct spa_device_object_info info = SPA_DEVICE_OBJECT_INFO_INIT(), *infop;
+ struct spa_pod *ipod;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ uint32_t id;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id),
+ SPA_POD_PodStruct(&ipod)) < 0)
+ return -EINVAL;
+
+ if (ipod) {
+ struct spa_pod_parser p2;
+ struct spa_pod_frame f2;
+ infop = &info;
+
+ spa_pod_parser_pod(&p2, ipod);
+ if (spa_pod_parser_push_struct(&p2, &f2) < 0 ||
+ spa_pod_parser_get(&p2,
+ SPA_POD_String(&info.type),
+ SPA_POD_Long(&info.change_mask),
+ SPA_POD_Long(&info.flags), NULL) < 0)
+ return -EINVAL;
+
+ info.change_mask &= SPA_DEVICE_OBJECT_CHANGE_MASK_FLAGS |
+ SPA_DEVICE_CHANGE_MASK_PROPS;
+
+ parse_dict(&p2, &props);
+ if (props.n_items > 0)
+ info.props = &props;
+ } else {
+ infop = NULL;
+ }
+
+ pw_resource_notify(resource, struct spa_device_events, object_info, 0, id, infop);
+ return 0;
+}
+
+static const struct spa_device_methods pw_protocol_native_device_method_marshal = {
+ SPA_VERSION_DEVICE_METHODS,
+ .add_listener = &device_marshal_add_listener,
+ .sync = &device_marshal_sync,
+ .enum_params = &device_marshal_enum_params,
+ .set_param = &device_marshal_set_param
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_device_method_demarshal[SPA_DEVICE_METHOD_NUM] =
+{
+ [SPA_DEVICE_METHOD_ADD_LISTENER] = { &device_demarshal_add_listener, 0 },
+ [SPA_DEVICE_METHOD_SYNC] = { &device_demarshal_sync, 0 },
+ [SPA_DEVICE_METHOD_ENUM_PARAMS] = { &device_demarshal_enum_params, 0 },
+ [SPA_DEVICE_METHOD_SET_PARAM] = { &device_demarshal_set_param, 0 },
+};
+
+static const struct spa_device_events pw_protocol_native_device_event_marshal = {
+ SPA_VERSION_DEVICE_EVENTS,
+ .info = &device_marshal_info,
+ .result = &device_marshal_result,
+ .event = &device_marshal_event,
+ .object_info = &device_marshal_object_info,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_device_event_demarshal[SPA_DEVICE_EVENT_NUM] =
+{
+ [SPA_DEVICE_EVENT_INFO] = { &device_demarshal_info, 0 },
+ [SPA_DEVICE_EVENT_RESULT] = { &device_demarshal_result, 0 },
+ [SPA_DEVICE_EVENT_EVENT] = { &device_demarshal_event, 0 },
+ [SPA_DEVICE_EVENT_OBJECT_INFO] = { &device_demarshal_object_info, 0 },
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_client_device_marshal = {
+ SPA_TYPE_INTERFACE_Device,
+ SPA_VERSION_DEVICE,
+ PW_PROTOCOL_MARSHAL_FLAG_IMPL,
+ SPA_DEVICE_EVENT_NUM,
+ SPA_DEVICE_METHOD_NUM,
+ .client_marshal = &pw_protocol_native_device_event_marshal,
+ .server_demarshal = pw_protocol_native_device_event_demarshal,
+ .server_marshal = &pw_protocol_native_device_method_marshal,
+ .client_demarshal = pw_protocol_native_device_method_demarshal,
+};
+
+struct pw_protocol *pw_protocol_native_ext_client_device_init(struct pw_context *context)
+{
+ struct pw_protocol *protocol;
+
+ protocol = pw_context_find_protocol(context, PW_TYPE_INFO_PROTOCOL_Native);
+
+ if (protocol == NULL)
+ return NULL;
+
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_client_device_marshal);
+
+ return protocol;
+}
diff --git a/src/modules/module-client-device/proxy-device.c b/src/modules/module-client-device/proxy-device.c
new file mode 100644
index 0000000..43bdf8e
--- /dev/null
+++ b/src/modules/module-client-device/proxy-device.c
@@ -0,0 +1,90 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <spa/pod/parser.h>
+#include <spa/monitor/device.h>
+
+#include "pipewire/pipewire.h"
+
+struct device_data {
+ struct spa_device *device;
+ struct spa_hook device_listener;
+ struct spa_hook device_methods;
+
+ struct pw_proxy *proxy;
+ struct spa_hook proxy_listener;
+};
+
+static void proxy_device_destroy(void *_data)
+{
+ struct device_data *data = _data;
+ spa_hook_remove(&data->device_listener);
+ spa_hook_remove(&data->device_methods);
+ spa_hook_remove(&data->proxy_listener);
+}
+
+static const struct pw_proxy_events proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .destroy = proxy_device_destroy,
+};
+
+struct pw_proxy *pw_core_spa_device_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size)
+{
+ struct spa_device *device = object;
+ struct spa_interface *iface, *diface;
+ struct pw_proxy *proxy;
+ struct device_data *data;
+
+ proxy = pw_core_create_object(core,
+ "client-device",
+ SPA_TYPE_INTERFACE_Device,
+ SPA_VERSION_DEVICE,
+ props,
+ user_data_size + sizeof(struct device_data));
+ if (proxy == NULL)
+ return NULL;
+
+ data = pw_proxy_get_user_data(proxy);
+ data = SPA_PTROFF(data, user_data_size, struct device_data);
+ data->device = device;
+ data->proxy = proxy;
+
+ iface = (struct spa_interface*)proxy;
+ diface = (struct spa_interface*)device;
+
+ pw_proxy_add_listener(proxy, &data->proxy_listener, &proxy_events, data);
+
+ pw_proxy_add_object_listener(proxy, &data->device_methods,
+ diface->cb.funcs, diface->cb.data);
+ spa_device_add_listener(device, &data->device_listener,
+ iface->cb.funcs, iface->cb.data);
+
+ return proxy;
+}
diff --git a/src/modules/module-client-device/resource-device.c b/src/modules/module-client-device/resource-device.c
new file mode 100644
index 0000000..0f5a40b
--- /dev/null
+++ b/src/modules/module-client-device/resource-device.c
@@ -0,0 +1,155 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <spa/monitor/device.h>
+#include <spa/monitor/utils.h>
+#include <spa/pod/filter.h>
+#include <spa/pod/parser.h>
+#include <spa/debug/types.h>
+
+#include <pipewire/impl.h>
+
+struct impl {
+ struct pw_context *context;
+ struct pw_impl_device *device;
+ struct spa_hook device_listener;
+
+ struct pw_resource *resource;
+ struct spa_hook resource_listener;
+ struct spa_hook object_listener;
+
+ unsigned int registered:1;
+};
+
+static void device_info(void *data, const struct spa_device_info *info)
+{
+ struct impl *impl = data;
+ if (!impl->registered) {
+ pw_impl_device_set_implementation(impl->device,
+ (struct spa_device*)impl->resource);
+ pw_impl_device_register(impl->device, NULL);
+ impl->registered = true;
+ }
+}
+
+static const struct spa_device_events object_events = {
+ SPA_VERSION_DEVICE_EVENTS,
+ .info = device_info,
+};
+
+static void device_resource_destroy(void *data)
+{
+ struct impl *impl = data;
+
+ pw_log_debug("client-device %p: destroy", impl);
+
+ impl->resource = NULL;
+ spa_hook_remove(&impl->device_listener);
+ spa_hook_remove(&impl->resource_listener);
+ spa_hook_remove(&impl->object_listener);
+ pw_impl_device_destroy(impl->device);
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = device_resource_destroy,
+};
+
+static void device_destroy(void *data)
+{
+ struct impl *impl = data;
+
+ pw_log_debug("client-device %p: destroy", impl);
+
+ impl->device = NULL;
+ spa_hook_remove(&impl->device_listener);
+ spa_hook_remove(&impl->resource_listener);
+ spa_hook_remove(&impl->object_listener);
+ pw_resource_destroy(impl->resource);
+}
+
+static void device_initialized(void *data)
+{
+ struct impl *impl = data;
+ struct pw_impl_device *device = impl->device;
+ struct pw_global *global = pw_impl_device_get_global(device);
+ uint32_t id = pw_global_get_id(global);
+
+ pw_log_debug("client-device %p: initialized global:%d", impl, id);
+ pw_resource_set_bound_id(impl->resource, id);
+}
+
+static const struct pw_impl_device_events device_events = {
+ PW_VERSION_IMPL_DEVICE_EVENTS,
+ .destroy = device_destroy,
+ .initialized = device_initialized,
+};
+
+struct pw_impl_device *pw_client_device_new(struct pw_resource *resource,
+ struct pw_properties *properties)
+{
+ struct impl *impl;
+ struct pw_impl_device *device;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct pw_context *context = pw_impl_client_get_context(client);
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL)
+ return NULL;
+
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d",
+ pw_impl_client_get_info(client)->id);
+
+ device = pw_context_create_device(context, properties, sizeof(struct impl));
+ if (device == NULL)
+ return NULL;
+
+ impl = pw_impl_device_get_user_data(device);
+ impl->device = device;
+ impl->context = context;
+ impl->resource = resource;
+
+ pw_impl_device_add_listener(impl->device,
+ &impl->device_listener,
+ &device_events, impl);
+
+ pw_resource_add_listener(impl->resource,
+ &impl->resource_listener,
+ &resource_events,
+ impl);
+ pw_resource_add_object_listener(impl->resource,
+ &impl->object_listener,
+ &object_events,
+ impl);
+
+ return device;
+}
diff --git a/src/modules/module-client-node.c b/src/modules/module-client-node.c
new file mode 100644
index 0000000..4dc9bf8
--- /dev/null
+++ b/src/modules/module-client-node.c
@@ -0,0 +1,229 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+
+#include <pipewire/impl.h>
+
+#include "module-client-node/v0/client-node.h"
+#include "module-client-node/client-node.h"
+
+/** \page page_module_client_node PipeWire Module: Client Node
+ */
+
+#define NAME "client-node"
+
+PW_LOG_TOPIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Allow clients to create and control remote nodes" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct pw_proxy *pw_core_node_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object, size_t user_data_size);
+struct pw_proxy *pw_core_spa_node_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object, size_t user_data_size);
+
+struct pw_protocol *pw_protocol_native_ext_client_node_init(struct pw_context *context);
+struct pw_protocol *pw_protocol_native_ext_client_node0_init(struct pw_context *context);
+
+struct factory_data {
+ struct pw_impl_factory *factory;
+ struct spa_hook factory_listener;
+
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_export_type export_node;
+ struct pw_export_type export_spanode;
+};
+
+static void *create_object(void *_data,
+ struct pw_resource *resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id)
+{
+ void *result;
+ struct pw_resource *node_resource;
+ struct pw_impl_client *client;
+ int res;
+
+ if (resource == NULL) {
+ res = -EINVAL;
+ goto error_exit;
+ }
+
+ client = pw_resource_get_client(resource);
+ node_resource = pw_resource_new(client, new_id, PW_PERM_ALL, type, version, 0);
+ if (node_resource == NULL) {
+ res = -errno;
+ goto error_resource;
+ }
+
+ if (version == 0) {
+ result = pw_impl_client_node0_new(node_resource, properties);
+ } else {
+ result = pw_impl_client_node_new(node_resource, properties, true);
+ }
+ if (result == NULL) {
+ res = -errno;
+ goto error_node;
+ }
+ return result;
+
+error_resource:
+ pw_log_error("can't create resource: %s", spa_strerror(res));
+ pw_resource_errorf_id(resource, new_id, res, "can't create resource: %s", spa_strerror(res));
+ goto error_exit;
+error_node:
+ pw_log_error("can't create node: %s", spa_strerror(res));
+ pw_resource_errorf_id(resource, new_id, res, "can't create node: %s", spa_strerror(res));
+ goto error_exit;
+error_exit:
+ errno = -res;
+ return NULL;
+}
+
+static const struct pw_impl_factory_implementation impl_factory = {
+ PW_VERSION_IMPL_FACTORY_IMPLEMENTATION,
+ .create_object = create_object,
+};
+
+static void factory_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->factory_listener);
+ d->factory = NULL;
+ if (d->module)
+ pw_impl_module_destroy(d->module);
+}
+
+static const struct pw_impl_factory_events factory_events = {
+ PW_VERSION_IMPL_FACTORY_EVENTS,
+ .destroy = factory_destroy,
+};
+
+static void module_destroy(void *data)
+{
+ struct factory_data *d = data;
+
+ spa_hook_remove(&d->module_listener);
+ spa_list_remove(&d->export_node.link);
+ spa_list_remove(&d->export_spanode.link);
+
+ d->module = NULL;
+ if (d->factory)
+ pw_impl_factory_destroy(d->factory);
+}
+
+static void module_registered(void *data)
+{
+ struct factory_data *d = data;
+ struct pw_impl_module *module = d->module;
+ struct pw_impl_factory *factory = d->factory;
+ struct spa_dict_item items[1];
+ char id[16];
+ int res;
+
+ snprintf(id, sizeof(id), "%d", pw_global_get_id(pw_impl_module_get_global(module)));
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
+ pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
+
+ if ((res = pw_impl_factory_register(factory, NULL)) < 0) {
+ pw_log_error("%p: can't register factory: %s", factory, spa_strerror(res));
+ }
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+ .registered = module_registered,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_factory *factory;
+ struct factory_data *data;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ factory = pw_context_create_factory(context,
+ "client-node",
+ PW_TYPE_INTERFACE_ClientNode,
+ PW_VERSION_CLIENT_NODE,
+ NULL,
+ sizeof(*data));
+ if (factory == NULL)
+ return -errno;
+
+ data = pw_impl_factory_get_user_data(factory);
+ data->factory = factory;
+ data->module = module;
+
+ pw_log_debug("module %p: new", module);
+
+ pw_impl_factory_set_implementation(factory,
+ &impl_factory,
+ data);
+
+ data->export_node.type = PW_TYPE_INTERFACE_Node;
+ data->export_node.func = pw_core_node_export;
+ if ((res = pw_context_register_export_type(context, &data->export_node)) < 0)
+ goto error;
+
+ data->export_spanode.type = SPA_TYPE_INTERFACE_Node;
+ data->export_spanode.func = pw_core_spa_node_export;
+ if ((res = pw_context_register_export_type(context, &data->export_spanode)) < 0)
+ goto error_remove;
+
+ pw_protocol_native_ext_client_node_init(context);
+ pw_protocol_native_ext_client_node0_init(context);
+
+ pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data);
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+error_remove:
+ spa_list_remove(&data->export_node.link);
+error:
+ pw_impl_factory_destroy(data->factory);
+ return res;
+}
diff --git a/src/modules/module-client-node/client-node.c b/src/modules/module-client-node/client-node.c
new file mode 100644
index 0000000..d5c133a
--- /dev/null
+++ b/src/modules/module-client-node/client-node.c
@@ -0,0 +1,1777 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <spa/support/system.h>
+#include <spa/node/node.h>
+#include <spa/node/utils.h>
+#include <spa/pod/filter.h>
+#include <spa/pod/dynamic.h>
+#include <spa/pod/parser.h>
+#include <spa/debug/types.h>
+
+#include <pipewire/pipewire.h>
+#include "pipewire/private.h"
+
+#include "modules/spa/spa-node.h"
+#include "client-node.h"
+
+PW_LOG_TOPIC_EXTERN(mod_topic);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+/** \cond */
+
+#define MAX_BUFFERS 64
+#define MAX_METAS 16u
+#define MAX_DATAS 64u
+#define MAX_AREAS 2048
+
+#define CHECK_FREE_PORT(this,d,p) (p <= pw_map_get_size(&this->ports[d]) && !CHECK_PORT(this,d,p))
+#define CHECK_PORT(this,d,p) (pw_map_lookup(&this->ports[d], p) != NULL)
+#define GET_PORT(this,d,p) (pw_map_lookup(&this->ports[d], p))
+
+#define CHECK_PORT_BUFFER(this,b,p) (b < p->n_buffers)
+
+struct buffer {
+ struct spa_buffer *outbuf;
+ struct spa_buffer buffer;
+ struct spa_meta metas[MAX_METAS];
+ struct spa_data datas[MAX_DATAS];
+ struct pw_memblock *mem;
+};
+
+struct mix {
+ unsigned int valid:1;
+ uint32_t id;
+ struct port *port;
+ uint32_t peer_id;
+ uint32_t n_buffers;
+ struct buffer buffers[MAX_BUFFERS];
+};
+
+struct params {
+ uint32_t n_params;
+ struct spa_pod **params;
+};
+
+struct port {
+ struct pw_impl_port *port;
+ struct node *node;
+ struct impl *impl;
+
+ enum spa_direction direction;
+ uint32_t id;
+
+ struct spa_node mix_node;
+
+ struct spa_port_info info;
+ struct pw_properties *properties;
+
+ struct params params;
+
+ unsigned int removed:1;
+ unsigned int destroyed:1;
+
+ struct pw_array mix;
+};
+
+struct node {
+ struct spa_node node;
+
+ struct impl *impl;
+
+ struct spa_log *log;
+ struct spa_loop *data_loop;
+ struct spa_system *data_system;
+
+ struct spa_hook_list hooks;
+ struct spa_callbacks callbacks;
+
+ struct pw_resource *resource;
+ struct pw_impl_client *client;
+
+ struct spa_source data_source;
+ int writefd;
+
+ struct pw_map ports[2];
+
+ struct port dummy;
+
+ struct params params;
+};
+
+struct impl {
+ struct pw_impl_client_node this;
+
+ struct pw_context *context;
+
+ struct node node;
+
+ struct pw_map io_map;
+ struct pw_memblock *io_areas;
+
+ struct pw_memblock *activation;
+
+ struct spa_hook node_listener;
+ struct spa_hook resource_listener;
+ struct spa_hook object_listener;
+
+ uint32_t node_id;
+
+ uint32_t bind_node_version;
+ uint32_t bind_node_id;
+
+ int fds[2];
+ int other_fds[2];
+};
+
+#define pw_client_node_resource(r,m,v,...) \
+ pw_resource_call_res(r,struct pw_client_node_events,m,v,__VA_ARGS__)
+
+#define pw_client_node_resource_transport(r,...) \
+ pw_client_node_resource(r,transport,0,__VA_ARGS__)
+#define pw_client_node_resource_set_param(r,...) \
+ pw_client_node_resource(r,set_param,0,__VA_ARGS__)
+#define pw_client_node_resource_set_io(r,...) \
+ pw_client_node_resource(r,set_io,0,__VA_ARGS__)
+#define pw_client_node_resource_event(r,...) \
+ pw_client_node_resource(r,event,0,__VA_ARGS__)
+#define pw_client_node_resource_command(r,...) \
+ pw_client_node_resource(r,command,0,__VA_ARGS__)
+#define pw_client_node_resource_add_port(r,...) \
+ pw_client_node_resource(r,add_port,0,__VA_ARGS__)
+#define pw_client_node_resource_remove_port(r,...) \
+ pw_client_node_resource(r,remove_port,0,__VA_ARGS__)
+#define pw_client_node_resource_port_set_param(r,...) \
+ pw_client_node_resource(r,port_set_param,0,__VA_ARGS__)
+#define pw_client_node_resource_port_use_buffers(r,...) \
+ pw_client_node_resource(r,port_use_buffers,0,__VA_ARGS__)
+#define pw_client_node_resource_port_set_io(r,...) \
+ pw_client_node_resource(r,port_set_io,0,__VA_ARGS__)
+#define pw_client_node_resource_set_activation(r,...) \
+ pw_client_node_resource(r,set_activation,0,__VA_ARGS__)
+#define pw_client_node_resource_port_set_mix_info(r,...) \
+ pw_client_node_resource(r,port_set_mix_info,1,__VA_ARGS__)
+
+static int update_params(struct params *p, uint32_t n_params, const struct spa_pod **params)
+{
+ uint32_t i;
+ for (i = 0; i < p->n_params; i++)
+ free(p->params[i]);
+ p->n_params = n_params;
+ if (p->n_params == 0) {
+ free(p->params);
+ p->params = NULL;
+ } else {
+ struct spa_pod **np;
+ np = pw_reallocarray(p->params, p->n_params, sizeof(struct spa_pod *));
+ if (np == NULL) {
+ pw_log_error("%p: can't realloc: %m", p);
+ free(p->params);
+ p->params = NULL;
+ p->n_params = 0;
+ return -errno;
+ }
+ p->params = np;
+ }
+ for (i = 0; i < p->n_params; i++)
+ p->params[i] = params[i] ? spa_pod_copy(params[i]) : NULL;
+ return 0;
+}
+
+static int
+do_port_use_buffers(struct impl *impl,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t mix_id,
+ uint32_t flags,
+ struct spa_buffer **buffers,
+ uint32_t n_buffers);
+
+/** \endcond */
+
+static struct mix *find_mix(struct port *p, uint32_t mix_id)
+{
+ struct mix *mix;
+ size_t len;
+
+ if (mix_id == SPA_ID_INVALID)
+ mix_id = 0;
+ else
+ mix_id++;
+
+ len = pw_array_get_len(&p->mix, struct mix);
+ if (mix_id >= len) {
+ size_t need = sizeof(struct mix) * (mix_id + 1 - len);
+ void *ptr = pw_array_add(&p->mix, need);
+ if (ptr == NULL)
+ return NULL;
+ memset(ptr, 0, need);
+ }
+ mix = pw_array_get_unchecked(&p->mix, mix_id, struct mix);
+ return mix;
+}
+
+static void mix_init(struct mix *mix, struct port *p, uint32_t id)
+{
+ mix->valid = true;
+ mix->id = id;
+ mix->port = p;
+ mix->n_buffers = 0;
+}
+
+static struct mix *ensure_mix(struct impl *impl, struct port *p, uint32_t mix_id)
+{
+ struct mix *mix;
+
+ if ((mix = find_mix(p, mix_id)) == NULL)
+ return NULL;
+ if (mix->valid)
+ return mix;
+ mix_init(mix, p, mix_id);
+ return mix;
+}
+
+static void clear_data(struct node *this, struct spa_data *d)
+{
+ struct impl *impl = this->impl;
+
+ switch (d->type) {
+ case SPA_DATA_MemId:
+ {
+ uint32_t id;
+ struct pw_memblock *m;
+
+ id = SPA_PTR_TO_UINT32(d->data);
+ m = pw_mempool_find_id(this->client->pool, id);
+ if (m) {
+ pw_log_debug("%p: mem %d", impl, m->id);
+ pw_memblock_unref(m);
+ }
+ break;
+ }
+ case SPA_DATA_MemFd:
+ case SPA_DATA_DmaBuf:
+ pw_log_debug("%p: close fd:%d", impl, (int)d->fd);
+ close(d->fd);
+ break;
+ default:
+ break;
+ }
+}
+
+static int clear_buffers(struct node *this, struct mix *mix)
+{
+ uint32_t i, j;
+
+ for (i = 0; i < mix->n_buffers; i++) {
+ struct buffer *b = &mix->buffers[i];
+
+ spa_log_debug(this->log, "%p: clear buffer %d", this, i);
+
+ for (j = 0; j < b->buffer.n_datas; j++) {
+ struct spa_data *d = &b->datas[j];
+ clear_data(this, d);
+ }
+ pw_memblock_unref(b->mem);
+ }
+ mix->n_buffers = 0;
+ return 0;
+}
+
+static void mix_clear(struct node *this, struct mix *mix)
+{
+ struct port *port = mix->port;
+
+ if (!mix->valid)
+ return;
+ do_port_use_buffers(this->impl, port->direction, port->id,
+ mix->id, 0, NULL, 0);
+ mix->valid = false;
+}
+
+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 node *this = object;
+ uint8_t buffer[1024];
+ struct spa_pod_dynamic_builder b;
+ struct spa_result_node_params result;
+ uint32_t count = 0;
+ bool found = false;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(num != 0, -EINVAL);
+
+ result.id = id;
+ result.next = 0;
+
+ while (true) {
+ struct spa_pod *param;
+
+ result.index = result.next++;
+ if (result.index >= this->params.n_params)
+ break;
+
+ param = this->params.params[result.index];
+
+ if (param == NULL || !spa_pod_is_object_id(param, id))
+ continue;
+
+ found = true;
+
+ if (result.index < start)
+ continue;
+
+ spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
+ if (spa_pod_filter(&b.b, &result.param, param, filter) == 0) {
+ pw_log_debug("%p: %d param %u", this, seq, result.index);
+ spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+ count++;
+ }
+ spa_pod_dynamic_builder_clean(&b);
+
+ if (count == num)
+ break;
+ }
+ return found ? 0 : -ENOENT;
+}
+
+static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct node *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ if (this->resource == NULL)
+ return param == NULL ? 0 : -EIO;
+
+ return pw_client_node_resource_set_param(this->resource, id, flags, param);
+}
+
+static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
+{
+ struct node *this = object;
+ struct impl *impl = this->impl;
+ struct pw_memmap *mm, *old;
+ uint32_t memid, mem_offset, mem_size;
+ uint32_t tag[5] = { impl->node_id, id, };
+
+ if (impl->this.flags & 1)
+ return 0;
+
+ old = pw_mempool_find_tag(this->client->pool, tag, sizeof(tag));
+
+ if (data) {
+ mm = pw_mempool_import_map(this->client->pool,
+ impl->context->pool, data, size, tag);
+ if (mm == NULL)
+ return -errno;
+
+ mem_offset = mm->offset;
+ memid = mm->block->id;
+ mem_size = size;
+ }
+ else {
+ memid = SPA_ID_INVALID;
+ mem_offset = mem_size = 0;
+ }
+ pw_memmap_free(old);
+
+ if (this->resource == NULL)
+ return data == NULL ? 0 : -EIO;
+
+ return pw_client_node_resource_set_io(this->resource,
+ id,
+ memid,
+ mem_offset, mem_size);
+}
+
+static int impl_node_send_command(void *object, const struct spa_command *command)
+{
+ struct node *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(command != NULL, -EINVAL);
+
+ pw_log_debug("%p: send command %d", this, SPA_COMMAND_TYPE(command));
+
+ if (this->resource == NULL)
+ return -EIO;
+
+ return pw_client_node_resource_command(this->resource, command);
+}
+
+
+static void emit_port_info(struct node *this, struct port *port)
+{
+ spa_node_emit_port_info(&this->hooks,
+ port->direction, port->id, &port->info);
+}
+
+static int impl_node_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct spa_node_events *events,
+ void *data)
+{
+ struct node *this = object;
+ struct spa_hook_list save;
+ union pw_map_item *item;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ spa_hook_list_isolate(&this->hooks, &save, listener, events, data);
+
+ pw_array_for_each(item, &this->ports[SPA_DIRECTION_INPUT].items) {
+ if (item->data)
+ emit_port_info(this, item->data);
+ }
+ pw_array_for_each(item, &this->ports[SPA_DIRECTION_OUTPUT].items) {
+ if (item->data)
+ emit_port_info(this, item->data);
+ }
+ 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 node *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ this->callbacks = SPA_CALLBACKS_INIT(callbacks, data);
+
+ return 0;
+}
+
+static int
+impl_node_sync(void *object, int seq)
+{
+ struct node *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ pw_log_debug("%p: sync", this);
+
+ if (this->resource == NULL)
+ return -EIO;
+
+ return pw_resource_ping(this->resource, seq);
+}
+
+static void
+do_update_port(struct node *this,
+ struct port *port,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct spa_port_info *info)
+{
+ if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_PARAMS) {
+ spa_log_debug(this->log, "%p: port %u update %d params", this, port->id, n_params);
+ update_params(&port->params, n_params, params);
+ }
+
+ if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_INFO) {
+ pw_properties_free(port->properties);
+ port->properties = NULL;
+ port->info.props = NULL;
+ port->info.n_params = 0;
+ port->info.params = NULL;
+
+ if (info) {
+ port->info = *info;
+ if (info->props) {
+ port->properties = pw_properties_new_dict(info->props);
+ port->info.props = &port->properties->dict;
+ }
+ port->info.n_params = 0;
+ port->info.params = NULL;
+ spa_node_emit_port_info(&this->hooks, port->direction, port->id, info);
+ }
+ }
+}
+
+static void
+clear_port(struct node *this, struct port *port)
+{
+ struct mix *mix;
+
+ spa_log_debug(this->log, "%p: clear port %p", this, port);
+
+ do_update_port(this, port,
+ PW_CLIENT_NODE_PORT_UPDATE_PARAMS |
+ PW_CLIENT_NODE_PORT_UPDATE_INFO, 0, NULL, NULL);
+
+ pw_array_for_each(mix, &port->mix)
+ mix_clear(this, mix);
+ pw_array_clear(&port->mix);
+ pw_array_init(&port->mix, sizeof(struct mix) * 2);
+
+ pw_map_insert_at(&this->ports[port->direction], port->id, NULL);
+
+ if (!port->removed)
+ spa_node_emit_port_info(&this->hooks, port->direction, port->id, NULL);
+}
+
+static int
+impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id,
+ const struct spa_dict *props)
+{
+ struct node *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(CHECK_FREE_PORT(this, direction, port_id), -EINVAL);
+
+ if (this->resource == NULL)
+ return -EIO;
+
+ return pw_client_node_resource_add_port(this->resource, direction, port_id, props);
+}
+
+static int
+impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id)
+{
+ struct node *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
+
+ if (this->resource == NULL)
+ return -EIO;
+
+ return pw_client_node_resource_remove_port(this->resource, direction, port_id);
+}
+
+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 node *this = object;
+ struct port *port;
+ uint8_t buffer[1024];
+ struct spa_pod_dynamic_builder b;
+ struct spa_result_node_params result;
+ uint32_t count = 0;
+ bool found = false;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(num != 0, -EINVAL);
+
+ port = GET_PORT(this, direction, port_id);
+ spa_return_val_if_fail(port != NULL, -EINVAL);
+
+ pw_log_debug("%p: seq:%d port %d.%d id:%u start:%u num:%u n_params:%d",
+ this, seq, direction, port_id, id, start, num, port->params.n_params);
+
+ result.id = id;
+ result.next = 0;
+
+ while (true) {
+ struct spa_pod *param;
+
+ result.index = result.next++;
+ if (result.index >= port->params.n_params)
+ break;
+
+ param = port->params.params[result.index];
+
+ if (param == NULL || !spa_pod_is_object_id(param, id))
+ continue;
+
+ found = true;
+
+ if (result.index < start)
+ continue;
+
+ spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
+ if (spa_pod_filter(&b.b, &result.param, param, filter) == 0) {
+ pw_log_debug("%p: %d param %u", this, seq, result.index);
+ spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+ count++;
+ }
+ spa_pod_dynamic_builder_clean(&b);
+
+ if (count == num)
+ break;
+ }
+ return found ? 0 : -ENOENT;
+}
+
+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 node *this = object;
+ struct port *port;
+ struct mix *mix;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ port = GET_PORT(this, direction, port_id);
+ if(port == NULL)
+ return param == NULL ? 0 : -EINVAL;
+
+ pw_log_debug("%p: port %d.%d set param %s %d", this,
+ direction, port_id,
+ spa_debug_type_find_name(spa_type_param, id), id);
+
+ if (id == SPA_PARAM_Format) {
+ pw_array_for_each(mix, &port->mix)
+ clear_buffers(this, mix);
+ }
+ if (this->resource == NULL)
+ return param == NULL ? 0 : -EIO;
+
+ return pw_client_node_resource_port_set_param(this->resource,
+ direction, port_id,
+ id, flags,
+ param);
+}
+
+static int do_port_set_io(struct impl *impl,
+ enum spa_direction direction, uint32_t port_id,
+ uint32_t mix_id,
+ uint32_t id, void *data, size_t size)
+{
+ struct node *this = &impl->node;
+ uint32_t memid, mem_offset, mem_size;
+ struct port *port;
+ struct mix *mix;
+ uint32_t tag[5] = { impl->node_id, direction, port_id, mix_id, id };
+ struct pw_memmap *mm, *old;
+
+ pw_log_debug("%p: %s port %d.%d set io %p %zd", this,
+ direction == SPA_DIRECTION_INPUT ? "input" : "output",
+ port_id, mix_id, data, size);
+
+ port = GET_PORT(this, direction, port_id);
+ if (port == NULL)
+ return data == NULL ? 0 : -EINVAL;
+
+ if ((mix = find_mix(port, mix_id)) == NULL || !mix->valid)
+ return -EINVAL;
+
+ old = pw_mempool_find_tag(this->client->pool, tag, sizeof(tag));
+
+ if (data) {
+ mm = pw_mempool_import_map(this->client->pool,
+ impl->context->pool, data, size, tag);
+ if (mm == NULL)
+ return -errno;
+
+ mem_offset = mm->offset;
+ memid = mm->block->id;
+ mem_size = size;
+ }
+ else {
+ memid = SPA_ID_INVALID;
+ mem_offset = mem_size = 0;
+ }
+ pw_memmap_free(old);
+
+ if (this->resource == NULL)
+ return data == NULL ? 0 : -EIO;
+
+ return pw_client_node_resource_port_set_io(this->resource,
+ direction, port_id,
+ mix_id,
+ id,
+ memid,
+ mem_offset, mem_size);
+}
+
+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)
+{
+ /* ignore io on the node itself, we only care about the io on the
+ * port mixers, the io on the node ports itself is handled on the
+ * client side */
+ return -EINVAL;
+}
+
+static int
+do_port_use_buffers(struct impl *impl,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t mix_id,
+ uint32_t flags,
+ struct spa_buffer **buffers,
+ uint32_t n_buffers)
+{
+ struct node *this = &impl->node;
+ struct port *p;
+ struct mix *mix;
+ uint32_t i, j;
+ struct pw_client_node_buffer *mb;
+
+ p = GET_PORT(this, direction, port_id);
+ if (p == NULL)
+ return n_buffers == 0 ? 0 : -EINVAL;
+
+ if (n_buffers > MAX_BUFFERS)
+ return -ENOSPC;
+
+ spa_log_debug(this->log, "%p: %s port %d.%d use buffers %p %u flags:%08x", this,
+ direction == SPA_DIRECTION_INPUT ? "input" : "output",
+ port_id, mix_id, buffers, n_buffers, flags);
+
+ if ((mix = find_mix(p, mix_id)) == NULL || !mix->valid)
+ return -EINVAL;
+
+ if (direction == SPA_DIRECTION_OUTPUT) {
+ mix_id = SPA_ID_INVALID;
+ if ((mix = find_mix(p, mix_id)) == NULL || !mix->valid)
+ return -EINVAL;
+ }
+
+ clear_buffers(this, mix);
+
+ if (n_buffers > 0) {
+ mb = alloca(n_buffers * sizeof(struct pw_client_node_buffer));
+ } else {
+ mb = NULL;
+ }
+
+ if (this->resource == NULL)
+ return n_buffers == 0 ? 0 : -EIO;
+
+ if (p->destroyed)
+ return 0;
+
+ for (i = 0; i < n_buffers; i++) {
+ struct buffer *b = &mix->buffers[i];
+ struct pw_memblock *mem, *m;
+ void *baseptr, *endptr;
+
+ b->outbuf = buffers[i];
+ memcpy(&b->buffer, buffers[i], sizeof(struct spa_buffer));
+ b->buffer.datas = b->datas;
+ b->buffer.metas = b->metas;
+
+ if (buffers[i]->n_metas > 0)
+ baseptr = buffers[i]->metas[0].data;
+ else if (buffers[i]->n_datas > 0)
+ baseptr = buffers[i]->datas[0].chunk;
+ else
+ return -EINVAL;
+
+ if ((mem = pw_mempool_find_ptr(impl->context->pool, baseptr)) == NULL)
+ return -EINVAL;
+
+ endptr = SPA_PTROFF(baseptr, buffers[i]->n_datas * sizeof(struct spa_chunk), void);
+ for (j = 0; j < buffers[i]->n_metas; j++) {
+ endptr = SPA_PTROFF(endptr, SPA_ROUND_UP_N(buffers[i]->metas[j].size, 8), void);
+ }
+ for (j = 0; j < buffers[i]->n_datas; j++) {
+ struct spa_data *d = &buffers[i]->datas[j];
+ if (d->type == SPA_DATA_MemPtr) {
+ if ((m = pw_mempool_find_ptr(impl->context->pool, d->data)) == NULL ||
+ m != mem)
+ return -EINVAL;
+ endptr = SPA_MAX(endptr, SPA_PTROFF(d->data, d->maxsize, void));
+ }
+ }
+ if (endptr > SPA_PTROFF(baseptr, mem->size, void))
+ return -EINVAL;
+
+ m = pw_mempool_import_block(this->client->pool, mem);
+ if (m == NULL)
+ return -errno;
+
+ b->mem = m;
+
+ mb[i].buffer = &b->buffer;
+ mb[i].mem_id = m->id;
+ mb[i].offset = SPA_PTRDIFF(baseptr, mem->map->ptr);
+ mb[i].size = SPA_PTRDIFF(endptr, baseptr);
+ spa_log_debug(this->log, "%p: buffer %d %d %d %d", this, i, mb[i].mem_id,
+ mb[i].offset, mb[i].size);
+
+ b->buffer.n_metas = SPA_MIN(buffers[i]->n_metas, MAX_METAS);
+ for (j = 0; j < b->buffer.n_metas; j++)
+ memcpy(&b->buffer.metas[j], &buffers[i]->metas[j], sizeof(struct spa_meta));
+
+ b->buffer.n_datas = SPA_MIN(buffers[i]->n_datas, MAX_DATAS);
+ for (j = 0; j < b->buffer.n_datas; j++) {
+ struct spa_data *d = &buffers[i]->datas[j];
+
+ memcpy(&b->datas[j], d, sizeof(struct spa_data));
+
+ if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC)
+ continue;
+
+ switch (d->type) {
+ case SPA_DATA_DmaBuf:
+ case SPA_DATA_MemFd:
+ {
+ uint32_t flags = PW_MEMBLOCK_FLAG_DONT_CLOSE;
+
+ if (d->flags & SPA_DATA_FLAG_READABLE)
+ flags |= PW_MEMBLOCK_FLAG_READABLE;
+ if (d->flags & SPA_DATA_FLAG_WRITABLE)
+ flags |= PW_MEMBLOCK_FLAG_WRITABLE;
+
+ spa_log_debug(this->log, "mem %d type:%d fd:%d", j, d->type, (int)d->fd);
+ m = pw_mempool_import(this->client->pool,
+ flags, d->type, d->fd);
+ if (m == NULL)
+ return -errno;
+
+ b->datas[j].type = SPA_DATA_MemId;
+ b->datas[j].data = SPA_UINT32_TO_PTR(m->id);
+ break;
+ }
+ case SPA_DATA_MemPtr:
+ spa_log_debug(this->log, "mem %d %zd", j, SPA_PTRDIFF(d->data, baseptr));
+ b->datas[j].data = SPA_INT_TO_PTR(SPA_PTRDIFF(d->data, baseptr));
+ break;
+ default:
+ b->datas[j].type = SPA_ID_INVALID;
+ b->datas[j].data = NULL;
+ spa_log_error(this->log, "invalid memory type %d", d->type);
+ break;
+ }
+ }
+ }
+ mix->n_buffers = n_buffers;
+
+ return pw_client_node_resource_port_use_buffers(this->resource,
+ direction, port_id, mix_id, flags,
+ n_buffers, mb);
+}
+
+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 node *this = object;
+ struct impl *impl;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ impl = this->impl;
+
+ return do_port_use_buffers(impl, direction, port_id,
+ SPA_ID_INVALID, flags, buffers, n_buffers);
+}
+
+static int
+impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id)
+{
+ struct node *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL);
+
+ spa_log_trace_fp(this->log, "reuse buffer %d", buffer_id);
+
+ return -ENOTSUP;
+}
+
+static int impl_node_process(void *object)
+{
+ struct node *this = object;
+ struct impl *impl = this->impl;
+ struct pw_impl_node *n = impl->this.node;
+ struct timespec ts;
+
+ spa_log_trace_fp(this->log, "%p: send process driver:%p", this, impl->this.node->driver_node);
+
+ if (SPA_UNLIKELY(spa_system_clock_gettime(this->data_system, CLOCK_MONOTONIC, &ts) < 0))
+ spa_zero(ts);
+
+ n->rt.activation->status = PW_NODE_ACTIVATION_TRIGGERED;
+ n->rt.activation->signal_time = SPA_TIMESPEC_TO_NSEC(&ts);
+
+ if (SPA_UNLIKELY(spa_system_eventfd_write(this->data_system, this->writefd, 1) < 0))
+ spa_log_warn(this->log, "%p: error %m", this);
+
+ return SPA_STATUS_OK;
+}
+
+static struct pw_node *
+client_node_get_node(void *data,
+ uint32_t version,
+ size_t user_data_size)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+ uint32_t new_id = user_data_size;
+
+ pw_log_debug("%p: bind %u/%u", this, new_id, version);
+
+ impl->bind_node_version = version;
+ impl->bind_node_id = new_id;
+ pw_map_insert_at(&this->client->objects, new_id, NULL);
+
+ return NULL;
+}
+
+static int
+client_node_update(void *data,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct spa_node_info *info)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+
+ if (change_mask & PW_CLIENT_NODE_UPDATE_PARAMS) {
+ pw_log_debug("%p: update %d params", this, n_params);
+ update_params(&this->params, n_params, params);
+ }
+ if (change_mask & PW_CLIENT_NODE_UPDATE_INFO) {
+ spa_node_emit_info(&this->hooks, info);
+ }
+ pw_log_debug("%p: got node update", this);
+ return 0;
+}
+
+static int
+client_node_port_update(void *data,
+ 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)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+ struct port *port;
+ bool remove;
+
+ spa_log_debug(this->log, "%p: got port update change:%08x params:%d",
+ this, change_mask, n_params);
+
+ remove = (change_mask == 0);
+
+ port = GET_PORT(this, direction, port_id);
+
+ if (remove) {
+ if (port == NULL)
+ return 0;
+ port->destroyed = true;
+ clear_port(this, port);
+ } else {
+ struct port *target;
+
+ if (port == NULL) {
+ if (!CHECK_FREE_PORT(this, direction, port_id))
+ return -EINVAL;
+
+ target = &this->dummy;
+ spa_zero(this->dummy);
+ target->direction = direction;
+ target->id = port_id;
+ } else
+ target = port;
+
+ do_update_port(this,
+ target,
+ change_mask,
+ n_params, params,
+ info);
+ }
+ return 0;
+}
+
+static int client_node_set_active(void *data, bool active)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+ spa_log_debug(this->log, "%p: active:%d", this, active);
+ return pw_impl_node_set_active(impl->this.node, active);
+}
+
+static int client_node_event(void *data, const struct spa_event *event)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+ spa_node_emit_event(&this->hooks, event);
+ return 0;
+}
+
+static int client_node_port_buffers(void *data,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t mix_id,
+ uint32_t n_buffers,
+ struct spa_buffer **buffers)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+ struct port *p;
+ struct mix *mix;
+ uint32_t i, j;
+
+ spa_log_debug(this->log, "%p: %s port %d.%d buffers %p %u", this,
+ direction == SPA_DIRECTION_INPUT ? "input" : "output",
+ port_id, mix_id, buffers, n_buffers);
+
+ p = GET_PORT(this, direction, port_id);
+ spa_return_val_if_fail(p != NULL, -EINVAL);
+
+ if (direction == SPA_DIRECTION_OUTPUT)
+ mix_id = SPA_ID_INVALID;
+
+ if ((mix = find_mix(p, mix_id)) == NULL || !mix->valid)
+ return -EINVAL;
+
+ if (mix->n_buffers != n_buffers)
+ return -EINVAL;
+
+ for (i = 0; i < n_buffers; i++) {
+ struct spa_buffer *oldbuf, *newbuf;
+ struct buffer *b = &mix->buffers[i];
+
+ oldbuf = b->outbuf;
+ newbuf = buffers[i];
+
+ spa_log_debug(this->log, "buffer %d n_datas:%d", i, newbuf->n_datas);
+
+ if (oldbuf->n_datas != newbuf->n_datas)
+ return -EINVAL;
+
+ for (j = 0; j < b->buffer.n_datas; j++) {
+ struct spa_chunk *oldchunk = oldbuf->datas[j].chunk;
+ struct spa_data *d = &newbuf->datas[j];
+
+ /* overwrite everything except the chunk */
+ oldbuf->datas[j] = *d;
+ oldbuf->datas[j].chunk = oldchunk;
+
+ b->datas[j].type = d->type;
+ b->datas[j].fd = d->fd;
+
+ spa_log_debug(this->log, " data %d type:%d fl:%08x fd:%d, offs:%d max:%d",
+ j, d->type, d->flags, (int) d->fd, d->mapoffset,
+ d->maxsize);
+ }
+ }
+ mix->n_buffers = n_buffers;
+
+ return 0;
+}
+
+static const struct pw_client_node_methods client_node_methods = {
+ PW_VERSION_CLIENT_NODE_METHODS,
+ .get_node = client_node_get_node,
+ .update = client_node_update,
+ .port_update = client_node_port_update,
+ .set_active = client_node_set_active,
+ .event = client_node_event,
+ .port_buffers = client_node_port_buffers,
+};
+
+static void node_on_data_fd_events(struct spa_source *source)
+{
+ struct node *this = source->data;
+
+ if (source->rmask & (SPA_IO_ERR | SPA_IO_HUP)) {
+ spa_log_warn(this->log, "%p: got error", this);
+ return;
+ }
+
+ if (source->rmask & SPA_IO_IN) {
+ uint64_t cmd;
+ struct pw_impl_node *node = this->impl->this.node;
+
+ if (SPA_UNLIKELY(spa_system_eventfd_read(this->data_system,
+ this->data_source.fd, &cmd) < 0))
+ pw_log_warn("%p: read failed %m", this);
+ else if (SPA_UNLIKELY(cmd > 1))
+ pw_log_info("(%s-%u) client missed %"PRIu64" wakeups",
+ node->name, node->info.id, cmd - 1);
+
+ spa_log_trace_fp(this->log, "%p: got ready", this);
+ spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_DATA);
+ }
+}
+
+static const struct spa_node_methods impl_node = {
+ SPA_VERSION_NODE_METHODS,
+ .add_listener = impl_node_add_listener,
+ .set_callbacks = impl_node_set_callbacks,
+ .sync = impl_node_sync,
+ .enum_params = impl_node_enum_params,
+ .set_param = impl_node_set_param,
+ .set_io = impl_node_set_io,
+ .send_command = impl_node_send_command,
+ .add_port = impl_node_add_port,
+ .remove_port = impl_node_remove_port,
+ .port_enum_params = impl_node_port_enum_params,
+ .port_set_param = impl_node_port_set_param,
+ .port_use_buffers = impl_node_port_use_buffers,
+ .port_set_io = impl_node_port_set_io,
+ .port_reuse_buffer = impl_node_port_reuse_buffer,
+ .process = impl_node_process,
+};
+
+static int
+node_init(struct node *this,
+ struct spa_dict *info,
+ const struct spa_support *support,
+ uint32_t n_support)
+{
+ 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);
+
+ if (this->data_loop == NULL) {
+ spa_log_error(this->log, "a data-loop is needed");
+ return -EINVAL;
+ }
+ if (this->data_system == NULL) {
+ spa_log_error(this->log, "a data-system is needed");
+ return -EINVAL;
+ }
+
+ this->node.iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_Node,
+ SPA_VERSION_NODE,
+ &impl_node, this);
+ spa_hook_list_init(&this->hooks);
+
+ this->data_source.func = node_on_data_fd_events;
+ this->data_source.data = this;
+ this->data_source.fd = -1;
+ this->data_source.mask = SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP;
+ this->data_source.rmask = 0;
+
+ return 0;
+}
+
+static int node_clear(struct node *this)
+{
+ update_params(&this->params, 0, NULL);
+ return 0;
+}
+
+static int do_remove_source(struct spa_loop *loop,
+ bool async,
+ uint32_t seq,
+ const void *data,
+ size_t size,
+ void *user_data)
+{
+ struct spa_source *source = user_data;
+ spa_loop_remove_source(loop, source);
+ return 0;
+}
+
+static void client_node_resource_destroy(void *data)
+{
+ struct impl *impl = data;
+ struct pw_impl_client_node *this = &impl->this;
+ struct node *node = &impl->node;
+
+ pw_log_debug("%p: destroy", node);
+
+ impl->node.resource = this->resource = NULL;
+ spa_hook_remove(&impl->resource_listener);
+ spa_hook_remove(&impl->object_listener);
+
+ if (node->data_source.fd != -1) {
+ spa_loop_invoke(node->data_loop,
+ do_remove_source,
+ SPA_ID_INVALID,
+ NULL,
+ 0,
+ true,
+ &node->data_source);
+ }
+ if (this->node)
+ pw_impl_node_destroy(this->node);
+}
+
+static void client_node_resource_error(void *data, int seq, int res, const char *message)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+ struct spa_result_node_error result;
+
+ pw_log_error("%p: error seq:%d %d (%s)", this, seq, res, message);
+ result.message = message;
+ spa_node_emit_result(&this->hooks, seq, res, SPA_RESULT_TYPE_NODE_ERROR, &result);
+}
+
+static void client_node_resource_pong(void *data, int seq)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+
+ pw_log_debug("%p: got pong, emit result %d", this, seq);
+ spa_node_emit_result(&this->hooks, seq, 0, 0, NULL);
+}
+
+void pw_impl_client_node_registered(struct pw_impl_client_node *this, struct pw_global *global)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ struct pw_impl_node *node = this->node;
+ struct pw_impl_client *client = impl->node.client;
+ uint32_t node_id = global->id;
+
+ pw_log_debug("%p: %d", &impl->node, node_id);
+
+ impl->activation = pw_mempool_import_block(client->pool, node->activation);
+ if (impl->activation == NULL) {
+ pw_log_debug("%p: can't import block: %m", &impl->node);
+ return;
+ }
+ impl->node_id = node_id;
+
+ if (this->resource == NULL)
+ return;
+
+ pw_resource_set_bound_id(this->resource, node_id);
+
+ pw_client_node_resource_transport(this->resource,
+ impl->other_fds[0],
+ impl->other_fds[1],
+ impl->activation->id,
+ 0,
+ sizeof(struct pw_node_activation));
+
+ if (impl->bind_node_id) {
+ pw_global_bind(global, client, PW_PERM_ALL,
+ impl->bind_node_version, impl->bind_node_id);
+ }
+}
+
+static void node_initialized(void *data)
+{
+ struct impl *impl = data;
+ struct pw_impl_client_node *this = &impl->this;
+ struct node *node = &impl->node;
+ struct pw_global *global;
+ struct spa_system *data_system = impl->node.data_system;
+ size_t size;
+
+ impl->fds[0] = spa_system_eventfd_create(data_system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);
+ impl->fds[1] = spa_system_eventfd_create(data_system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);
+ impl->other_fds[0] = impl->fds[1];
+ impl->other_fds[1] = impl->fds[0];
+ node->data_source.fd = impl->fds[0];
+ node->writefd = impl->fds[1];
+
+ spa_loop_add_source(node->data_loop, &node->data_source);
+ pw_log_debug("%p: transport read-fd:%d write-fd:%d", node, impl->fds[0], impl->fds[1]);
+
+ size = sizeof(struct spa_io_buffers) * MAX_AREAS;
+
+ impl->io_areas = pw_mempool_alloc(impl->context->pool,
+ PW_MEMBLOCK_FLAG_READWRITE |
+ PW_MEMBLOCK_FLAG_MAP |
+ PW_MEMBLOCK_FLAG_SEAL,
+ SPA_DATA_MemFd, size);
+ if (impl->io_areas == NULL)
+ return;
+
+ pw_log_debug("%p: io areas %p", node, impl->io_areas->map->ptr);
+
+ if ((global = pw_impl_node_get_global(this->node)) != NULL)
+ pw_impl_client_node_registered(this, global);
+}
+
+static void node_free(void *data)
+{
+ struct impl *impl = data;
+ struct pw_impl_client_node *this = &impl->this;
+ struct node *node = &impl->node;
+ struct spa_system *data_system = node->data_system;
+ uint32_t tag[5] = { impl->node_id, };
+ struct pw_memmap *mm;
+
+ this->node = NULL;
+
+ pw_log_debug("%p: free", node);
+ node_clear(node);
+
+ spa_hook_remove(&impl->node_listener);
+
+ while ((mm = pw_mempool_find_tag(node->client->pool, tag, sizeof(uint32_t))) != NULL)
+ pw_memmap_free(mm);
+
+ if (this->resource)
+ pw_resource_destroy(this->resource);
+
+ if (impl->activation)
+ pw_memblock_unref(impl->activation);
+ if (impl->io_areas)
+ pw_memblock_unref(impl->io_areas);
+
+ pw_map_clear(&impl->node.ports[0]);
+ pw_map_clear(&impl->node.ports[1]);
+ pw_map_clear(&impl->io_map);
+
+ if (impl->fds[0] != -1)
+ spa_system_close(data_system, impl->fds[0]);
+ if (impl->fds[1] != -1)
+ spa_system_close(data_system, impl->fds[1]);
+ free(impl);
+}
+
+static int port_init_mix(void *data, struct pw_impl_port_mix *mix)
+{
+ struct port *port = data;
+ struct impl *impl = port->impl;
+ struct mix *m;
+
+ if ((m = ensure_mix(impl, port, mix->port.port_id)) == NULL)
+ return -ENOMEM;
+
+ mix->id = pw_map_insert_new(&impl->io_map, NULL);
+ if (mix->id == SPA_ID_INVALID) {
+ m->valid = false;
+ return -errno;
+ }
+ if (mix->id > MAX_AREAS) {
+ pw_map_remove(&impl->io_map, mix->id);
+ m->valid = false;
+ return -ENOMEM;
+ }
+
+ mix->io = SPA_PTROFF(impl->io_areas->map->ptr,
+ mix->id * sizeof(struct spa_io_buffers), void);
+ *mix->io = SPA_IO_BUFFERS_INIT;
+
+ m->peer_id = mix->peer_id;
+
+ pw_log_debug("%p: init mix id:%d io:%p base:%p", impl,
+ mix->id, mix->io, impl->io_areas->map->ptr);
+
+ return 0;
+}
+
+static int port_release_mix(void *data, struct pw_impl_port_mix *mix)
+{
+ struct port *port = data;
+ struct impl *impl = port->impl;
+ struct node *this = &impl->node;
+ struct mix *m;
+
+ pw_log_debug("%p: remove mix id:%d io:%p base:%p",
+ this, mix->id, mix->io, impl->io_areas->map->ptr);
+
+ if ((m = find_mix(port, mix->port.port_id)) == NULL || !m->valid)
+ return -EINVAL;
+
+ pw_map_remove(&impl->io_map, mix->id);
+ m->valid = false;
+
+ return 0;
+}
+
+static const struct pw_impl_port_implementation port_impl = {
+ PW_VERSION_PORT_IMPLEMENTATION,
+ .init_mix = port_init_mix,
+ .release_mix = port_release_mix,
+};
+
+static int
+impl_mix_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 port *port = object;
+
+ if (port->direction != direction)
+ return -ENOTSUP;
+
+ return impl_node_port_enum_params(&port->node->node, seq, direction, port->id,
+ id, start, num, filter);
+}
+
+static int
+impl_mix_port_set_param(void *object,
+ enum spa_direction direction, uint32_t port_id,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ return -ENOTSUP;
+}
+
+static int
+impl_mix_add_port(void *object, enum spa_direction direction, uint32_t mix_id,
+ const struct spa_dict *props)
+{
+ struct port *port = object;
+ pw_log_debug("%p: add port %d:%d.%d", object, direction, port->id, mix_id);
+ return 0;
+}
+
+static int
+impl_mix_remove_port(void *object, enum spa_direction direction, uint32_t mix_id)
+{
+ struct port *port = object;
+ pw_log_debug("%p: remove port %d:%d.%d", object, direction, port->id, mix_id);
+ return 0;
+}
+
+static int
+impl_mix_port_use_buffers(void *object,
+ enum spa_direction direction,
+ uint32_t mix_id,
+ uint32_t flags,
+ struct spa_buffer **buffers,
+ uint32_t n_buffers)
+{
+ struct port *port = object;
+ struct impl *impl = port->impl;
+
+ return do_port_use_buffers(impl, direction, port->id, mix_id, flags, buffers, n_buffers);
+}
+
+static int impl_mix_port_set_io(void *object,
+ enum spa_direction direction, uint32_t mix_id,
+ uint32_t id, void *data, size_t size)
+{
+ struct port *p = object;
+ struct pw_impl_port *port = p->port;
+ struct impl *impl = port->owner_data;
+ struct node *this = &impl->node;
+ struct pw_impl_port_mix *mix;
+
+ mix = pw_map_lookup(&port->mix_port_map, mix_id);
+ if (mix == NULL)
+ return -EINVAL;
+
+ if (id == SPA_IO_Buffers) {
+ if (data && size >= sizeof(struct spa_io_buffers))
+ mix->io = data;
+ else
+ mix->io = NULL;
+
+ if (mix->io != NULL && this->resource && this->resource->version >= 4)
+ pw_client_node_resource_port_set_mix_info(this->resource,
+ direction, port->port_id,
+ mix->port.port_id, mix->peer_id, NULL);
+ }
+
+ return do_port_set_io(impl,
+ direction, port->port_id, mix->port.port_id,
+ id, data, size);
+}
+
+static int
+impl_mix_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id)
+{
+ struct port *p = object;
+ return impl_node_port_reuse_buffer(&p->node->node, p->id, buffer_id);
+}
+
+static int impl_mix_process(void *object)
+{
+ return SPA_STATUS_HAVE_DATA;
+}
+
+static const struct spa_node_methods impl_port_mix = {
+ SPA_VERSION_NODE_METHODS,
+ .port_enum_params = impl_mix_port_enum_params,
+ .port_set_param = impl_mix_port_set_param,
+ .add_port = impl_mix_add_port,
+ .remove_port = impl_mix_remove_port,
+ .port_use_buffers = impl_mix_port_use_buffers,
+ .port_set_io = impl_mix_port_set_io,
+ .port_reuse_buffer = impl_mix_port_reuse_buffer,
+ .process = impl_mix_process,
+};
+
+static void node_port_init(void *data, struct pw_impl_port *port)
+{
+ struct impl *impl = data;
+ struct port *p = pw_impl_port_get_user_data(port);
+ struct node *this = &impl->node;
+
+ pw_log_debug("%p: port %p init", this, port);
+
+ *p = this->dummy;
+ p->port = port;
+ p->node = this;
+ p->direction = port->direction;
+ p->id = port->port_id;
+ p->impl = impl;
+ pw_array_init(&p->mix, sizeof(struct mix) * 2);
+ p->mix_node.iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_Node,
+ SPA_VERSION_NODE,
+ &impl_port_mix, p);
+ ensure_mix(impl, p, SPA_ID_INVALID);
+
+ pw_map_insert_at(&this->ports[p->direction], p->id, p);
+ return;
+}
+
+static void node_port_added(void *data, struct pw_impl_port *port)
+{
+ struct impl *impl = data;
+ struct port *p = pw_impl_port_get_user_data(port);
+
+ port->flags |= PW_IMPL_PORT_FLAG_NO_MIXER;
+
+ port->impl = SPA_CALLBACKS_INIT(&port_impl, p);
+ port->owner_data = impl;
+
+ pw_impl_port_set_mix(port, &p->mix_node,
+ PW_IMPL_PORT_MIX_FLAG_MULTI |
+ PW_IMPL_PORT_MIX_FLAG_MIX_ONLY);
+}
+
+static void node_port_removed(void *data, struct pw_impl_port *port)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+ struct port *p = pw_impl_port_get_user_data(port);
+
+ pw_log_debug("%p: port %p remove", this, port);
+
+ p->removed = true;
+ clear_port(this, p);
+}
+
+static void node_peer_added(void *data, struct pw_impl_node *peer)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+ struct pw_memblock *m;
+
+ if (peer == impl->this.node)
+ return;
+
+ m = pw_mempool_import_block(this->client->pool, peer->activation);
+ if (m == NULL) {
+ pw_log_debug("%p: can't ensure mem: %m", this);
+ return;
+ }
+ pw_log_debug("%p: peer %p id:%u added mem_id:%u", &impl->this, peer,
+ peer->info.id, m->id);
+
+ if (this->resource == NULL)
+ return;
+
+ pw_client_node_resource_set_activation(this->resource,
+ peer->info.id,
+ peer->source.fd,
+ m->id,
+ 0,
+ sizeof(struct pw_node_activation));
+}
+
+static void node_peer_removed(void *data, struct pw_impl_node *peer)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+ struct pw_memblock *m;
+
+ if (peer == impl->this.node)
+ return;
+
+ m = pw_mempool_find_fd(this->client->pool, peer->activation->fd);
+ if (m == NULL) {
+ pw_log_warn("%p: unknown peer %p fd:%d", this, peer,
+ peer->source.fd);
+ return;
+ }
+ pw_log_debug("%p: peer %p %u removed", this, peer,
+ peer->info.id);
+
+ if (this->resource != NULL) {
+ pw_client_node_resource_set_activation(this->resource,
+ peer->info.id,
+ -1,
+ SPA_ID_INVALID,
+ 0,
+ 0);
+ }
+
+ pw_memblock_unref(m);
+}
+
+static void node_driver_changed(void *data, struct pw_impl_node *old, struct pw_impl_node *driver)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+
+ pw_log_debug("%p: driver changed %p -> %p", this, old, driver);
+
+ node_peer_removed(data, old);
+ node_peer_added(data, driver);
+}
+
+static const struct pw_impl_node_events node_events = {
+ PW_VERSION_IMPL_NODE_EVENTS,
+ .free = node_free,
+ .initialized = node_initialized,
+ .port_init = node_port_init,
+ .port_added = node_port_added,
+ .port_removed = node_port_removed,
+ .peer_added = node_peer_added,
+ .peer_removed = node_peer_removed,
+ .driver_changed = node_driver_changed,
+};
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = client_node_resource_destroy,
+ .error = client_node_resource_error,
+ .pong = client_node_resource_pong,
+};
+
+/** Create a new client node
+ * \param client an owner \ref pw_client
+ * \param id an id
+ * \param name a name
+ * \param properties extra properties
+ * \return a newly allocated client node
+ *
+ * Create a new \ref pw_impl_node.
+ *
+ * \memberof pw_impl_client_node
+ */
+struct pw_impl_client_node *pw_impl_client_node_new(struct pw_resource *resource,
+ struct pw_properties *properties,
+ bool do_register)
+{
+ struct impl *impl;
+ struct pw_impl_client_node *this;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct pw_context *context = pw_impl_client_get_context(client);
+ const struct spa_support *support;
+ uint32_t n_support;
+ int res;
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL) {
+ res = -errno;
+ goto error_exit_cleanup;
+ }
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL) {
+ res = -errno;
+ goto error_exit_free;
+ }
+
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", client->global->id);
+
+ this = &impl->this;
+
+ impl->context = context;
+ impl->fds[0] = impl->fds[1] = -1;
+ pw_log_debug("%p: new", &impl->node);
+
+ support = pw_context_get_support(impl->context, &n_support);
+ node_init(&impl->node, NULL, support, n_support);
+ impl->node.impl = impl;
+ impl->node.resource = resource;
+ impl->node.client = client;
+ this->flags = do_register ? 0 : 1;
+
+ pw_map_init(&impl->node.ports[0], 64, 64);
+ pw_map_init(&impl->node.ports[1], 64, 64);
+ pw_map_init(&impl->io_map, 64, 64);
+
+ this->resource = resource;
+ this->node = pw_spa_node_new(context,
+ PW_SPA_NODE_FLAG_ASYNC |
+ (do_register ? 0 : PW_SPA_NODE_FLAG_NO_REGISTER),
+ (struct spa_node *)&impl->node.node,
+ NULL,
+ properties, 0);
+
+ if (this->node == NULL)
+ goto error_no_node;
+
+ this->node->remote = true;
+ this->flags = 0;
+
+ pw_resource_add_listener(this->resource,
+ &impl->resource_listener,
+ &resource_events,
+ impl);
+ pw_resource_add_object_listener(this->resource,
+ &impl->object_listener,
+ &client_node_methods,
+ impl);
+
+ this->node->port_user_data_size = sizeof(struct port);
+
+ pw_impl_node_add_listener(this->node, &impl->node_listener, &node_events, impl);
+
+ return this;
+
+error_no_node:
+ res = -errno;
+ node_clear(&impl->node);
+ properties = NULL;
+ goto error_exit_free;
+
+error_exit_free:
+ free(impl);
+error_exit_cleanup:
+ if (resource)
+ pw_resource_destroy(resource);
+ pw_properties_free(properties);
+ errno = -res;
+ return NULL;
+}
+
+/** Destroy a client node
+ * \param node the client node to destroy
+ * \memberof pw_impl_client_node
+ */
+void pw_impl_client_node_destroy(struct pw_impl_client_node *node)
+{
+ pw_resource_destroy(node->resource);
+}
diff --git a/src/modules/module-client-node/client-node.h b/src/modules/module-client-node/client-node.h
new file mode 100644
index 0000000..f7e060a
--- /dev/null
+++ b/src/modules/module-client-node/client-node.h
@@ -0,0 +1,60 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_CLIENT_NODE_H
+#define PIPEWIRE_CLIENT_NODE_H
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/client-node.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \class pw_impl_client_node
+ *
+ * PipeWire client node interface
+ */
+struct pw_impl_client_node {
+ struct pw_impl_node *node;
+
+ struct pw_resource *resource;
+ uint32_t flags;
+};
+
+struct pw_impl_client_node *
+pw_impl_client_node_new(struct pw_resource *resource,
+ struct pw_properties *properties,
+ bool do_register);
+
+void
+pw_impl_client_node_destroy(struct pw_impl_client_node *node);
+
+void pw_impl_client_node_registered(struct pw_impl_client_node *node, struct pw_global *global);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_CLIENT_NODE_H */
diff --git a/src/modules/module-client-node/protocol-native.c b/src/modules/module-client-node/protocol-native.c
new file mode 100644
index 0000000..dd51692
--- /dev/null
+++ b/src/modules/module-client-node/protocol-native.c
@@ -0,0 +1,1259 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+
+#include <spa/pod/builder.h>
+#include <spa/pod/parser.h>
+#include <spa/utils/result.h>
+
+#include <pipewire/impl.h>
+
+#include <pipewire/extensions/protocol-native.h>
+#include <pipewire/extensions/client-node.h>
+
+#define MAX_DICT 1024
+#define MAX_PARAMS 4096
+#define MAX_PARAM_INFO 128
+#define MAX_BUFFERS 64
+#define MAX_METAS 16u
+#define MAX_DATAS 64u
+
+PW_LOG_TOPIC_EXTERN(mod_topic);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+static inline void push_item(struct spa_pod_builder *b, const struct spa_dict_item *item)
+{
+ const char *str;
+ spa_pod_builder_string(b, item->key);
+ str = item->value;
+ if (spa_strstartswith(str, "pointer:"))
+ str = "";
+ spa_pod_builder_string(b, str);
+}
+
+static void push_dict(struct spa_pod_builder *b, const struct spa_dict *dict)
+{
+ uint32_t i, n_items;
+ struct spa_pod_frame f;
+
+ n_items = dict ? dict->n_items : 0;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_int(b, n_items);
+ for (i = 0; i < n_items; i++)
+ push_item(b, &dict->items[i]);
+ spa_pod_builder_pop(b, &f);
+}
+
+static inline int parse_item(struct spa_pod_parser *prs, struct spa_dict_item *item)
+{
+ int res;
+ if ((res = spa_pod_parser_get(prs,
+ SPA_POD_String(&item->key),
+ SPA_POD_String(&item->value),
+ NULL)) < 0)
+ return res;
+ if (spa_strstartswith(item->value, "pointer:"))
+ item->value = "";
+ return 0;
+}
+
+#define parse_dict(prs,d) \
+do { \
+ uint32_t i; \
+ if (spa_pod_parser_get(prs, \
+ SPA_POD_Int(&(d)->n_items), NULL) < 0) \
+ return -EINVAL; \
+ (d)->items = NULL; \
+ if ((d)->n_items > 0) { \
+ if ((d)->n_items > MAX_DICT) \
+ return -ENOSPC; \
+ (d)->items = alloca((d)->n_items * sizeof(struct spa_dict_item)); \
+ for (i = 0; i < (d)->n_items; i++) { \
+ if (parse_item(prs, (struct spa_dict_item *) &(d)->items[i]) < 0) \
+ return -EINVAL; \
+ } \
+ } \
+} while(0)
+
+#define parse_dict_struct(prs,f,dict) \
+do { \
+ if (spa_pod_parser_push_struct(prs, f) < 0) \
+ return -EINVAL; \
+ parse_dict(prs, dict); \
+ spa_pod_parser_pop(prs, f); \
+} while(0)
+
+#define parse_params(prs,n_params,params) \
+do { \
+ uint32_t i; \
+ if (spa_pod_parser_get(prs, \
+ SPA_POD_Int(&n_params), NULL) < 0) \
+ return -EINVAL; \
+ params = NULL; \
+ if (n_params > 0) { \
+ if (n_params > MAX_PARAMS) \
+ return -ENOSPC; \
+ params = alloca(n_params * sizeof(struct spa_pod *)); \
+ for (i = 0; i < n_params; i++) { \
+ if (spa_pod_parser_get(prs, \
+ SPA_POD_PodObject(&params[i]), NULL) < 0) \
+ return -EINVAL; \
+ } \
+ } \
+} while(0)
+
+#define parse_param_info(prs,n_params,params) \
+do { \
+ uint32_t i; \
+ if (spa_pod_parser_get(prs, \
+ SPA_POD_Int(&(n_params)), NULL) < 0) \
+ return -EINVAL; \
+ params = NULL; \
+ if (n_params > 0) { \
+ if (n_params > MAX_PARAM_INFO) \
+ return -ENOSPC; \
+ params = alloca(n_params * sizeof(struct spa_param_info)); \
+ for (i = 0; i < n_params; i++) { \
+ if (spa_pod_parser_get(prs, \
+ SPA_POD_Id(&(params[i]).id), \
+ SPA_POD_Int(&(params[i]).flags), NULL) < 0) \
+ return -EINVAL; \
+ } \
+ } \
+} while(0)
+
+static int client_node_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_client_node_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static struct pw_node *
+client_node_marshal_get_node(void *object, uint32_t version, size_t user_data_size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ struct pw_proxy *res;
+ uint32_t new_id;
+
+ res = pw_proxy_new(object, PW_TYPE_INTERFACE_Node, version, user_data_size);
+ if (res == NULL)
+ return NULL;
+
+ new_id = pw_proxy_get_id(res);
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_NODE_METHOD_GET_NODE, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(version),
+ SPA_POD_Int(new_id));
+
+ pw_protocol_native_end_proxy(proxy, b);
+
+ return (struct pw_node *) res;
+}
+
+static int
+client_node_marshal_update(void *object,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct spa_node_info *info)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f[2];
+ uint32_t i, n_items, n_info_params;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_NODE_METHOD_UPDATE, NULL);
+
+ spa_pod_builder_push_struct(b, &f[0]);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(change_mask),
+ SPA_POD_Int(n_params), NULL);
+
+ for (i = 0; i < n_params; i++)
+ spa_pod_builder_add(b, SPA_POD_Pod(params[i]), NULL);
+
+ if (info) {
+ uint64_t change_mask = info->change_mask;
+
+ change_mask &= SPA_NODE_CHANGE_MASK_FLAGS |
+ SPA_NODE_CHANGE_MASK_PROPS |
+ SPA_NODE_CHANGE_MASK_PARAMS;
+
+ n_items = info->props && (change_mask & SPA_NODE_CHANGE_MASK_PROPS) ?
+ info->props->n_items : 0;
+ n_info_params = (change_mask & SPA_NODE_CHANGE_MASK_PARAMS) ?
+ info->n_params : 0;
+
+ spa_pod_builder_push_struct(b, &f[1]);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->max_input_ports),
+ SPA_POD_Int(info->max_output_ports),
+ SPA_POD_Long(change_mask),
+ SPA_POD_Long(info->flags),
+ SPA_POD_Int(n_items), NULL);
+ for (i = 0; i < n_items; i++)
+ push_item(b, &info->props->items[i]);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(n_info_params), NULL);
+ for (i = 0; i < n_info_params; i++) {
+ spa_pod_builder_add(b,
+ SPA_POD_Id(info->params[i].id),
+ SPA_POD_Int(info->params[i].flags), NULL);
+ }
+ spa_pod_builder_pop(b, &f[1]);
+
+ } else {
+ spa_pod_builder_add(b,
+ SPA_POD_Pod(NULL), NULL);
+ }
+ spa_pod_builder_pop(b, &f[0]);
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int
+client_node_marshal_port_update(void *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)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f[2];
+ uint32_t i, n_items;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_NODE_METHOD_PORT_UPDATE, NULL);
+
+ spa_pod_builder_push_struct(b, &f[0]);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(direction),
+ SPA_POD_Int(port_id),
+ SPA_POD_Int(change_mask),
+ SPA_POD_Int(n_params), NULL);
+
+ for (i = 0; i < n_params; i++)
+ spa_pod_builder_add(b,
+ SPA_POD_Pod(params[i]), NULL);
+
+ if (info) {
+ uint64_t change_mask = info->change_mask;
+
+ n_items = info->props ? info->props->n_items : 0;
+
+ change_mask &= SPA_PORT_CHANGE_MASK_FLAGS |
+ SPA_PORT_CHANGE_MASK_RATE |
+ SPA_PORT_CHANGE_MASK_PROPS |
+ SPA_PORT_CHANGE_MASK_PARAMS;
+
+ spa_pod_builder_push_struct(b, &f[1]);
+ spa_pod_builder_add(b,
+ SPA_POD_Long(change_mask),
+ SPA_POD_Long(info->flags),
+ SPA_POD_Int(info->rate.num),
+ SPA_POD_Int(info->rate.denom),
+ SPA_POD_Int(n_items), NULL);
+ for (i = 0; i < n_items; i++)
+ push_item(b, &info->props->items[i]);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->n_params), NULL);
+ for (i = 0; i < info->n_params; i++) {
+ spa_pod_builder_add(b,
+ SPA_POD_Id(info->params[i].id),
+ SPA_POD_Int(info->params[i].flags), NULL);
+ }
+ spa_pod_builder_pop(b, &f[1]);
+
+ } else {
+ spa_pod_builder_add(b,
+ SPA_POD_Pod(NULL), NULL);
+ }
+ spa_pod_builder_pop(b, &f[0]);
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int client_node_marshal_set_active(void *object, bool active)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_NODE_METHOD_SET_ACTIVE, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Bool(active));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int client_node_marshal_event_method(void *object, const struct spa_event *event)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_NODE_METHOD_EVENT, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Pod(event));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int
+client_node_marshal_port_buffers(void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t mix_id,
+ uint32_t n_buffers,
+ struct spa_buffer **buffers)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f[2];
+ uint32_t i, j;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_NODE_METHOD_PORT_BUFFERS, NULL);
+
+ spa_pod_builder_push_struct(b, &f[0]);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(direction),
+ SPA_POD_Int(port_id),
+ SPA_POD_Int(mix_id),
+ SPA_POD_Int(n_buffers), NULL);
+
+ for (i = 0; i < n_buffers; i++) {
+ struct spa_buffer *buf = buffers[i];
+
+ spa_pod_builder_add(b,
+ SPA_POD_Int(buf->n_datas), NULL);
+
+ for (j = 0; j < buf->n_datas; j++) {
+ struct spa_data *d = &buf->datas[j];
+ spa_pod_builder_add(b,
+ SPA_POD_Id(d->type),
+ SPA_POD_Fd(pw_protocol_native_add_proxy_fd(proxy, d->fd)),
+ SPA_POD_Int(d->flags),
+ SPA_POD_Int(d->mapoffset),
+ SPA_POD_Int(d->maxsize), NULL);
+ }
+ }
+ spa_pod_builder_pop(b, &f[0]);
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int client_node_demarshal_transport(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t mem_id, offset, sz;
+ int64_t ridx, widx;
+ int readfd, writefd;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Fd(&ridx),
+ SPA_POD_Fd(&widx),
+ SPA_POD_Int(&mem_id),
+ SPA_POD_Int(&offset),
+ SPA_POD_Int(&sz)) < 0)
+ return -EINVAL;
+
+ readfd = pw_protocol_native_get_proxy_fd(proxy, ridx);
+ writefd = pw_protocol_native_get_proxy_fd(proxy, widx);
+
+ if (readfd < 0 || writefd < 0)
+ return -EINVAL;
+
+ pw_proxy_notify(proxy, struct pw_client_node_events, transport, 0,
+ readfd, writefd, mem_id,
+ offset, sz);
+ return 0;
+}
+
+static int client_node_demarshal_set_param(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, flags;
+ const struct spa_pod *param = NULL;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_PodObject(&param)) < 0)
+ return -EINVAL;
+
+ pw_proxy_notify(proxy, struct pw_client_node_events, set_param, 0, id, flags, param);
+ return 0;
+}
+
+static int client_node_demarshal_event_event(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ const struct spa_event *event;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_PodObject(&event)) < 0)
+ return -EINVAL;
+
+ pw_proxy_notify(proxy, struct pw_client_node_events, event, 0, event);
+ return 0;
+}
+
+static int client_node_demarshal_command(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ const struct spa_command *command;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_PodObject(&command)) < 0)
+ return -EINVAL;
+
+ pw_proxy_notify(proxy, struct pw_client_node_events, command, 0, command);
+ return 0;
+}
+
+static int client_node_demarshal_add_port(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[2];
+ int32_t direction, port_id;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0)
+ return -EINVAL;
+
+ if (spa_pod_parser_get(&prs,
+ SPA_POD_Int(&direction),
+ SPA_POD_Int(&port_id), NULL) < 0)
+ return -EINVAL;
+
+ parse_dict_struct(&prs, &f[1], &props);
+
+ pw_proxy_notify(proxy, struct pw_client_node_events, add_port, 0, direction, port_id,
+ props.n_items ? &props : NULL);
+ return 0;
+}
+
+static int client_node_demarshal_remove_port(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ int32_t direction, port_id;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&direction),
+ SPA_POD_Int(&port_id)) < 0)
+ return -EINVAL;
+
+ pw_proxy_notify(proxy, struct pw_client_node_events, remove_port, 0, direction, port_id);
+ return 0;
+}
+
+static int client_node_demarshal_port_set_param(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t direction, port_id, id, flags;
+ const struct spa_pod *param = NULL;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&direction),
+ SPA_POD_Int(&port_id),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_PodObject(&param)) < 0)
+ return -EINVAL;
+
+ pw_proxy_notify(proxy, struct pw_client_node_events, port_set_param, 0,
+ direction, port_id, id, flags, param);
+ return 0;
+}
+
+static int client_node_demarshal_port_use_buffers(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ uint32_t direction, port_id, mix_id, flags, n_buffers, data_id;
+ struct pw_client_node_buffer *buffers;
+ uint32_t i, j;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&direction),
+ SPA_POD_Int(&port_id),
+ SPA_POD_Int(&mix_id),
+ SPA_POD_Int(&flags),
+ SPA_POD_Int(&n_buffers), NULL) < 0)
+ return -EINVAL;
+
+ if (n_buffers > MAX_BUFFERS)
+ return -ENOSPC;
+
+ buffers = alloca(sizeof(struct pw_client_node_buffer) * n_buffers);
+ for (i = 0; i < n_buffers; i++) {
+ struct spa_buffer *buf = buffers[i].buffer = alloca(sizeof(struct spa_buffer));
+
+ if (spa_pod_parser_get(&prs,
+ SPA_POD_Int(&buffers[i].mem_id),
+ SPA_POD_Int(&buffers[i].offset),
+ SPA_POD_Int(&buffers[i].size),
+ SPA_POD_Int(&buf->n_metas), NULL) < 0)
+ return -EINVAL;
+
+ if (buf->n_metas > MAX_METAS)
+ return -ENOSPC;
+
+ buf->metas = alloca(sizeof(struct spa_meta) * buf->n_metas);
+ for (j = 0; j < buf->n_metas; j++) {
+ struct spa_meta *m = &buf->metas[j];
+
+ if (spa_pod_parser_get(&prs,
+ SPA_POD_Id(&m->type),
+ SPA_POD_Int(&m->size), NULL) < 0)
+ return -EINVAL;
+
+ m->data = NULL;
+ }
+ if (spa_pod_parser_get(&prs,
+ SPA_POD_Int(&buf->n_datas), NULL) < 0)
+ return -EINVAL;
+
+ if (buf->n_datas > MAX_DATAS)
+ return -ENOSPC;
+
+ buf->datas = alloca(sizeof(struct spa_data) * buf->n_datas);
+ for (j = 0; j < buf->n_datas; j++) {
+ struct spa_data *d = &buf->datas[j];
+
+ if (spa_pod_parser_get(&prs,
+ SPA_POD_Id(&d->type),
+ SPA_POD_Int(&data_id),
+ SPA_POD_Int(&d->flags),
+ SPA_POD_Int(&d->mapoffset),
+ SPA_POD_Int(&d->maxsize), NULL) < 0)
+ return -EINVAL;
+
+ d->fd = -1;
+ d->data = SPA_UINT32_TO_PTR(data_id);
+ d->chunk = NULL;
+ }
+ }
+ pw_proxy_notify(proxy, struct pw_client_node_events, port_use_buffers, 0,
+ direction,
+ port_id,
+ mix_id,
+ flags,
+ n_buffers, buffers);
+ return 0;
+}
+
+static int client_node_demarshal_port_set_io(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t direction, port_id, mix_id, id, memid, off, sz;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&direction),
+ SPA_POD_Int(&port_id),
+ SPA_POD_Int(&mix_id),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&memid),
+ SPA_POD_Int(&off),
+ SPA_POD_Int(&sz)) < 0)
+ return -EINVAL;
+
+ pw_proxy_notify(proxy, struct pw_client_node_events, port_set_io, 0,
+ direction, port_id, mix_id,
+ id, memid,
+ off, sz);
+ return 0;
+}
+
+static int client_node_demarshal_set_activation(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t node_id, memid, off, sz;
+ int64_t sigidx;
+ int signalfd;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&node_id),
+ SPA_POD_Fd(&sigidx),
+ SPA_POD_Int(&memid),
+ SPA_POD_Int(&off),
+ SPA_POD_Int(&sz)) < 0)
+ return -EINVAL;
+
+ signalfd = pw_protocol_native_get_proxy_fd(proxy, sigidx);
+
+ pw_proxy_notify(proxy, struct pw_client_node_events, set_activation, 0,
+ node_id,
+ signalfd,
+ memid,
+ off, sz);
+ return 0;
+}
+
+static int client_node_demarshal_port_set_mix_info(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t direction, port_id, mix_id, peer_id;
+ struct spa_pod_frame f[2];
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&direction),
+ SPA_POD_Int(&port_id),
+ SPA_POD_Int(&mix_id),
+ SPA_POD_Int(&peer_id), NULL) < 0)
+ return -EINVAL;
+
+ parse_dict_struct(&prs, &f[1], &props);
+
+ pw_proxy_notify(proxy, struct pw_client_node_events, port_set_mix_info, 1,
+ direction, port_id, mix_id,
+ peer_id, &props);
+ return 0;
+}
+
+static int client_node_demarshal_set_io(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, memid, off, sz;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&memid),
+ SPA_POD_Int(&off),
+ SPA_POD_Int(&sz)) < 0)
+ return -EINVAL;
+
+ pw_proxy_notify(proxy, struct pw_client_node_events, set_io, 0,
+ id, memid, off, sz);
+ return 0;
+}
+
+static int client_node_marshal_transport(void *data, int readfd, int writefd,
+ uint32_t mem_id, uint32_t offset, uint32_t size)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_TRANSPORT, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Fd(pw_protocol_native_add_resource_fd(resource, readfd)),
+ SPA_POD_Fd(pw_protocol_native_add_resource_fd(resource, writefd)),
+ SPA_POD_Int(mem_id),
+ SPA_POD_Int(offset),
+ SPA_POD_Int(size));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int
+client_node_marshal_set_param(void *data, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int client_node_marshal_event_event(void *data, const struct spa_event *event)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_EVENT, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Pod(event));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int
+client_node_marshal_command(void *data, const struct spa_command *command)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_COMMAND, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Pod(command));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int
+client_node_marshal_add_port(void *data,
+ enum spa_direction direction, uint32_t port_id,
+ const struct spa_dict *props)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_ADD_PORT, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(direction),
+ SPA_POD_Int(port_id), NULL);
+ push_dict(b, props);
+ spa_pod_builder_pop(b, &f);
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int
+client_node_marshal_remove_port(void *data,
+ enum spa_direction direction, uint32_t port_id)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_REMOVE_PORT, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(direction),
+ SPA_POD_Int(port_id));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int
+client_node_marshal_port_set_param(void *data,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t id,
+ uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_PORT_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(direction),
+ SPA_POD_Int(port_id),
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int
+client_node_marshal_port_use_buffers(void *data,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t mix_id,
+ uint32_t flags,
+ uint32_t n_buffers, struct pw_client_node_buffer *buffers)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, j;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_PORT_USE_BUFFERS, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(direction),
+ SPA_POD_Int(port_id),
+ SPA_POD_Int(mix_id),
+ SPA_POD_Int(flags),
+ SPA_POD_Int(n_buffers), NULL);
+
+ for (i = 0; i < n_buffers; i++) {
+ struct spa_buffer *buf = buffers[i].buffer;
+
+ spa_pod_builder_add(b,
+ SPA_POD_Int(buffers[i].mem_id),
+ SPA_POD_Int(buffers[i].offset),
+ SPA_POD_Int(buffers[i].size),
+ SPA_POD_Int(buf->n_metas), NULL);
+
+ for (j = 0; j < buf->n_metas; j++) {
+ struct spa_meta *m = &buf->metas[j];
+ spa_pod_builder_add(b,
+ SPA_POD_Id(m->type),
+ SPA_POD_Int(m->size), NULL);
+ }
+ spa_pod_builder_add(b,
+ SPA_POD_Int(buf->n_datas), NULL);
+ for (j = 0; j < buf->n_datas; j++) {
+ struct spa_data *d = &buf->datas[j];
+ spa_pod_builder_add(b,
+ SPA_POD_Id(d->type),
+ SPA_POD_Int(SPA_PTR_TO_UINT32(d->data)),
+ SPA_POD_Int(d->flags),
+ SPA_POD_Int(d->mapoffset),
+ SPA_POD_Int(d->maxsize), NULL);
+ }
+ }
+ spa_pod_builder_pop(b, &f);
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int
+client_node_marshal_port_set_io(void *data,
+ uint32_t direction,
+ uint32_t port_id,
+ uint32_t mix_id,
+ uint32_t id,
+ uint32_t memid,
+ uint32_t offset,
+ uint32_t size)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_PORT_SET_IO, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(direction),
+ SPA_POD_Int(port_id),
+ SPA_POD_Int(mix_id),
+ SPA_POD_Id(id),
+ SPA_POD_Int(memid),
+ SPA_POD_Int(offset),
+ SPA_POD_Int(size));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int
+client_node_marshal_set_activation(void *data,
+ uint32_t node_id,
+ int signalfd,
+ uint32_t memid,
+ uint32_t offset,
+ uint32_t size)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_SET_ACTIVATION, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(node_id),
+ SPA_POD_Fd(pw_protocol_native_add_resource_fd(resource, signalfd)),
+ SPA_POD_Int(memid),
+ SPA_POD_Int(offset),
+ SPA_POD_Int(size));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int
+client_node_marshal_set_io(void *data,
+ uint32_t id,
+ uint32_t memid,
+ uint32_t offset,
+ uint32_t size)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_SET_IO, NULL);
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(memid),
+ SPA_POD_Int(offset),
+ SPA_POD_Int(size));
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int
+client_node_marshal_port_set_mix_info(void *data,
+ uint32_t direction,
+ uint32_t port_id,
+ uint32_t mix_id,
+ uint32_t peer_id,
+ const struct spa_dict *props)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE_EVENT_PORT_SET_MIX_INFO, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(direction),
+ SPA_POD_Int(port_id),
+ SPA_POD_Int(mix_id),
+ SPA_POD_Int(peer_id), NULL);
+ push_dict(b, props);
+ spa_pod_builder_pop(b, &f);
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+
+static int client_node_demarshal_get_node(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ int32_t version, new_id;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&version),
+ SPA_POD_Int(&new_id)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_client_node_methods, get_node, 0,
+ version, new_id);
+}
+
+static int client_node_demarshal_update(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[2];
+ uint32_t change_mask, n_params;
+ const struct spa_pod **params = NULL;
+ struct spa_node_info info = SPA_NODE_INFO_INIT(), *infop = NULL;
+ struct spa_pod *ipod;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&change_mask), NULL) < 0)
+ return -EINVAL;
+
+ parse_params(&prs, n_params, params);
+
+ if (spa_pod_parser_get(&prs,
+ SPA_POD_PodStruct(&ipod), NULL) < 0)
+ return -EINVAL;
+
+ if (ipod) {
+ struct spa_pod_parser p2;
+ struct spa_pod_frame f2;
+ infop = &info;
+
+ spa_pod_parser_pod(&p2, ipod);
+ if (spa_pod_parser_push_struct(&p2, &f2) < 0 ||
+ spa_pod_parser_get(&p2,
+ SPA_POD_Int(&info.max_input_ports),
+ SPA_POD_Int(&info.max_output_ports),
+ SPA_POD_Long(&info.change_mask),
+ SPA_POD_Long(&info.flags), NULL) < 0)
+ return -EINVAL;
+
+ info.change_mask &= SPA_NODE_CHANGE_MASK_FLAGS |
+ SPA_NODE_CHANGE_MASK_PROPS |
+ SPA_NODE_CHANGE_MASK_PARAMS;
+
+ parse_dict(&p2, &props);
+ if (props.n_items > 0)
+ info.props = &props;
+
+ parse_param_info(&p2, info.n_params, info.params);
+ }
+
+ pw_resource_notify(resource, struct pw_client_node_methods, update, 0, change_mask,
+ n_params,
+ params, infop);
+ return 0;
+}
+
+static int client_node_demarshal_port_update(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ uint32_t direction, port_id, change_mask, n_params;
+ const struct spa_pod **params = NULL;
+ struct spa_port_info info = SPA_PORT_INFO_INIT(), *infop = NULL;
+ struct spa_pod *ipod;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&direction),
+ SPA_POD_Int(&port_id),
+ SPA_POD_Int(&change_mask), NULL) < 0)
+ return -EINVAL;
+
+ parse_params(&prs, n_params, params);
+
+ if (spa_pod_parser_get(&prs,
+ SPA_POD_PodStruct(&ipod), NULL) < 0)
+ return -EINVAL;
+
+ if (ipod) {
+ struct spa_pod_parser p2;
+ struct spa_pod_frame f2;
+ infop = &info;
+
+ spa_pod_parser_pod(&p2, ipod);
+ if (spa_pod_parser_push_struct(&p2, &f2) < 0 ||
+ spa_pod_parser_get(&p2,
+ SPA_POD_Long(&info.change_mask),
+ SPA_POD_Long(&info.flags),
+ SPA_POD_Int(&info.rate.num),
+ SPA_POD_Int(&info.rate.denom), NULL) < 0)
+ return -EINVAL;
+
+ info.change_mask &= SPA_PORT_CHANGE_MASK_FLAGS |
+ SPA_PORT_CHANGE_MASK_RATE |
+ SPA_PORT_CHANGE_MASK_PROPS |
+ SPA_PORT_CHANGE_MASK_PARAMS;
+
+ parse_dict(&p2, &props);
+ if (props.n_items > 0)
+ info.props = &props;
+
+ parse_param_info(&p2, info.n_params, info.params);
+ }
+
+ pw_resource_notify(resource, struct pw_client_node_methods, port_update, 0, direction,
+ port_id,
+ change_mask,
+ n_params,
+ params, infop);
+ return 0;
+}
+
+static int client_node_demarshal_set_active(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ bool active;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Bool(&active)) < 0)
+ return -EINVAL;
+
+ pw_resource_notify(resource, struct pw_client_node_methods, set_active, 0, active);
+ return 0;
+}
+
+static int client_node_demarshal_event_method(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ const struct spa_event *event;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_PodObject(&event)) < 0)
+ return -EINVAL;
+
+ pw_resource_notify(resource, struct pw_client_node_methods, event, 0, event);
+ return 0;
+}
+
+static int client_node_demarshal_port_buffers(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ uint32_t i, j, direction, port_id, mix_id, n_buffers;
+ int64_t data_fd;
+ struct spa_buffer **buffers = NULL;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&direction),
+ SPA_POD_Int(&port_id),
+ SPA_POD_Int(&mix_id),
+ SPA_POD_Int(&n_buffers), NULL) < 0)
+ return -EINVAL;
+
+ if (n_buffers > MAX_BUFFERS)
+ return -ENOSPC;
+
+ buffers = alloca(sizeof(struct spa_buffer*) * n_buffers);
+ for (i = 0; i < n_buffers; i++) {
+ struct spa_buffer *buf = buffers[i] = alloca(sizeof(struct spa_buffer));
+
+ spa_zero(*buf);
+ if (spa_pod_parser_get(&prs,
+ SPA_POD_Int(&buf->n_datas), NULL) < 0)
+ return -EINVAL;
+
+ if (buf->n_datas > MAX_DATAS)
+ return -ENOSPC;
+
+ buf->datas = alloca(sizeof(struct spa_data) * buf->n_datas);
+ for (j = 0; j < buf->n_datas; j++) {
+ struct spa_data *d = &buf->datas[j];
+
+ if (spa_pod_parser_get(&prs,
+ SPA_POD_Id(&d->type),
+ SPA_POD_Fd(&data_fd),
+ SPA_POD_Int(&d->flags),
+ SPA_POD_Int(&d->mapoffset),
+ SPA_POD_Int(&d->maxsize), NULL) < 0)
+ return -EINVAL;
+
+ d->fd = pw_protocol_native_get_resource_fd(resource, data_fd);
+ }
+ }
+
+ pw_resource_notify(resource, struct pw_client_node_methods, port_buffers, 0,
+ direction, port_id, mix_id, n_buffers, buffers);
+
+ return 0;
+}
+
+static const struct pw_client_node_methods pw_protocol_native_client_node_method_marshal = {
+ PW_VERSION_CLIENT_NODE_METHODS,
+ .add_listener = &client_node_marshal_add_listener,
+ .get_node = &client_node_marshal_get_node,
+ .update = &client_node_marshal_update,
+ .port_update = &client_node_marshal_port_update,
+ .set_active = &client_node_marshal_set_active,
+ .event = &client_node_marshal_event_method,
+ .port_buffers = &client_node_marshal_port_buffers
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_client_node_method_demarshal[PW_CLIENT_NODE_METHOD_NUM] =
+{
+ [PW_CLIENT_NODE_METHOD_ADD_LISTENER] = { NULL, 0 },
+ [PW_CLIENT_NODE_METHOD_GET_NODE] = { &client_node_demarshal_get_node, 0 },
+ [PW_CLIENT_NODE_METHOD_UPDATE] = { &client_node_demarshal_update, 0 },
+ [PW_CLIENT_NODE_METHOD_PORT_UPDATE] = { &client_node_demarshal_port_update, 0 },
+ [PW_CLIENT_NODE_METHOD_SET_ACTIVE] = { &client_node_demarshal_set_active, 0 },
+ [PW_CLIENT_NODE_METHOD_EVENT] = { &client_node_demarshal_event_method, 0 },
+ [PW_CLIENT_NODE_METHOD_PORT_BUFFERS] = { &client_node_demarshal_port_buffers, 0 }
+};
+
+static const struct pw_client_node_events pw_protocol_native_client_node_event_marshal = {
+ PW_VERSION_CLIENT_NODE_EVENTS,
+ .transport = &client_node_marshal_transport,
+ .set_param = &client_node_marshal_set_param,
+ .set_io = &client_node_marshal_set_io,
+ .event = &client_node_marshal_event_event,
+ .command = &client_node_marshal_command,
+ .add_port = &client_node_marshal_add_port,
+ .remove_port = &client_node_marshal_remove_port,
+ .port_set_param = &client_node_marshal_port_set_param,
+ .port_use_buffers = &client_node_marshal_port_use_buffers,
+ .port_set_io = &client_node_marshal_port_set_io,
+ .set_activation = &client_node_marshal_set_activation,
+ .port_set_mix_info = &client_node_marshal_port_set_mix_info,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_client_node_event_demarshal[PW_CLIENT_NODE_EVENT_NUM] =
+{
+ [PW_CLIENT_NODE_EVENT_TRANSPORT] = { &client_node_demarshal_transport, 0 },
+ [PW_CLIENT_NODE_EVENT_SET_PARAM] = { &client_node_demarshal_set_param, 0 },
+ [PW_CLIENT_NODE_EVENT_SET_IO] = { &client_node_demarshal_set_io, 0 },
+ [PW_CLIENT_NODE_EVENT_EVENT] = { &client_node_demarshal_event_event, 0 },
+ [PW_CLIENT_NODE_EVENT_COMMAND] = { &client_node_demarshal_command, 0 },
+ [PW_CLIENT_NODE_EVENT_ADD_PORT] = { &client_node_demarshal_add_port, 0 },
+ [PW_CLIENT_NODE_EVENT_REMOVE_PORT] = { &client_node_demarshal_remove_port, 0 },
+ [PW_CLIENT_NODE_EVENT_PORT_SET_PARAM] = { &client_node_demarshal_port_set_param, 0 },
+ [PW_CLIENT_NODE_EVENT_PORT_USE_BUFFERS] = { &client_node_demarshal_port_use_buffers, 0 },
+ [PW_CLIENT_NODE_EVENT_PORT_SET_IO] = { &client_node_demarshal_port_set_io, 0 },
+ [PW_CLIENT_NODE_EVENT_SET_ACTIVATION] = { &client_node_demarshal_set_activation, 0 },
+ [PW_CLIENT_NODE_EVENT_PORT_SET_MIX_INFO] = { &client_node_demarshal_port_set_mix_info, 0 }
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_client_node_marshal = {
+ PW_TYPE_INTERFACE_ClientNode,
+ PW_VERSION_CLIENT_NODE,
+ 0,
+ PW_CLIENT_NODE_METHOD_NUM,
+ PW_CLIENT_NODE_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_client_node_method_marshal,
+ .server_demarshal = &pw_protocol_native_client_node_method_demarshal,
+ .server_marshal = &pw_protocol_native_client_node_event_marshal,
+ .client_demarshal = pw_protocol_native_client_node_event_demarshal,
+};
+
+struct pw_protocol *pw_protocol_native_ext_client_node_init(struct pw_context *context)
+{
+ struct pw_protocol *protocol;
+
+ protocol = pw_context_find_protocol(context, PW_TYPE_INFO_PROTOCOL_Native);
+
+ if (protocol == NULL)
+ return NULL;
+
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_client_node_marshal);
+
+ return protocol;
+}
diff --git a/src/modules/module-client-node/remote-node.c b/src/modules/module-client-node/remote-node.c
new file mode 100644
index 0000000..051ab07
--- /dev/null
+++ b/src/modules/module-client-node/remote-node.c
@@ -0,0 +1,1339 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <time.h>
+#include <sys/mman.h>
+
+#include <spa/pod/parser.h>
+#include <spa/pod/dynamic.h>
+#include <spa/node/utils.h>
+#include <spa/utils/result.h>
+#include <spa/debug/types.h>
+
+#include "pipewire/pipewire.h"
+#include "pipewire/private.h"
+
+#include "pipewire/extensions/protocol-native.h"
+#include "pipewire/extensions/client-node.h"
+
+#define MAX_BUFFERS 64
+
+PW_LOG_TOPIC_EXTERN(mod_topic);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+/** \cond */
+static bool mlock_warned = false;
+
+struct buffer {
+ uint32_t id;
+ struct spa_buffer *buf;
+ struct pw_memmap *mem;
+};
+
+struct mix {
+ struct spa_list link;
+ struct pw_impl_port *port;
+ uint32_t mix_id;
+ struct pw_impl_port_mix mix;
+ struct pw_array buffers;
+ bool active;
+};
+
+struct node_data {
+ struct pw_context *context;
+
+ struct pw_mempool *pool;
+
+ uint32_t remote_id;
+ int rtwritefd;
+ struct pw_memmap *activation;
+
+ struct spa_list mix[2];
+ struct spa_list free_mix;
+
+ struct pw_impl_node *node;
+ struct spa_hook node_listener;
+ unsigned int do_free:1;
+ unsigned int have_transport:1;
+ unsigned int allow_mlock:1;
+ unsigned int warn_mlock:1;
+
+ struct pw_client_node *client_node;
+ struct spa_hook client_node_listener;
+ struct spa_hook proxy_client_node_listener;
+
+ struct spa_list links;
+};
+
+struct link {
+ struct spa_list link;
+ struct node_data *data;
+ struct pw_memmap *map;
+ struct pw_node_target target;
+ uint32_t node_id;
+ int signalfd;
+};
+
+/** \endcond */
+
+static struct link *find_activation(struct spa_list *links, uint32_t node_id)
+{
+ struct link *l;
+
+ spa_list_for_each(l, links, link) {
+ if (l->node_id == node_id)
+ return l;
+ }
+ return NULL;
+}
+
+static int
+do_deactivate_link(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct link *link = user_data;
+ pw_log_trace("link %p deactivate", link);
+ spa_list_remove(&link->target.link);
+ return 0;
+}
+
+static void clear_link(struct node_data *data, struct link *link)
+{
+ struct pw_context *context = data->context;
+ pw_log_debug("link %p", link);
+ pw_loop_invoke(context->data_loop,
+ do_deactivate_link, SPA_ID_INVALID, NULL, 0, true, link);
+ pw_memmap_free(link->map);
+ spa_system_close(context->data_system, link->signalfd);
+ spa_list_remove(&link->link);
+ free(link);
+}
+
+static void clean_transport(struct node_data *data)
+{
+ struct link *l;
+ uint32_t tag[5] = { data->remote_id, };
+ struct pw_memmap *mm;
+
+ if (!data->have_transport)
+ return;
+
+ spa_list_consume(l, &data->links, link)
+ clear_link(data, l);
+
+ while ((mm = pw_mempool_find_tag(data->pool, tag, sizeof(uint32_t))) != NULL) {
+ if (mm->tag[1] == SPA_ID_INVALID)
+ spa_node_set_io(data->node->node, mm->tag[2], NULL, 0);
+
+ pw_memmap_free(mm);
+ }
+
+ pw_memmap_free(data->activation);
+ data->node->rt.activation = data->node->activation->map->ptr;
+
+ spa_system_close(data->context->data_system, data->rtwritefd);
+ data->have_transport = false;
+}
+
+static void mix_init(struct mix *mix, struct pw_impl_port *port, uint32_t mix_id)
+{
+ pw_log_debug("port %p: mix init %d.%d", port, port->port_id, mix_id);
+ mix->port = port;
+ mix->mix_id = mix_id;
+ pw_impl_port_init_mix(port, &mix->mix);
+ mix->active = false;
+ pw_array_init(&mix->buffers, 32);
+ pw_array_ensure_size(&mix->buffers, sizeof(struct buffer) * 64);
+}
+
+static int
+do_deactivate_mix(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct mix *mix = user_data;
+ spa_list_remove(&mix->mix.rt_link);
+ return 0;
+}
+
+static int
+deactivate_mix(struct node_data *data, struct mix *mix)
+{
+ if (mix->active) {
+ pw_log_debug("node %p: mix %p deactivate", data, mix);
+ pw_loop_invoke(data->context->data_loop,
+ do_deactivate_mix, SPA_ID_INVALID, NULL, 0, true, mix);
+ mix->active = false;
+ }
+ return 0;
+}
+
+static int
+do_activate_mix(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct mix *mix = user_data;
+
+ spa_list_append(&mix->port->rt.mix_list, &mix->mix.rt_link);
+ return 0;
+}
+
+static int
+activate_mix(struct node_data *data, struct mix *mix)
+{
+ if (!mix->active) {
+ pw_log_debug("node %p: mix %p activate", data, mix);
+ pw_loop_invoke(data->context->data_loop,
+ do_activate_mix, SPA_ID_INVALID, NULL, 0, false, mix);
+ mix->active = true;
+ }
+ return 0;
+}
+
+static struct mix *find_mix(struct node_data *data,
+ enum spa_direction direction, uint32_t port_id, uint32_t mix_id)
+{
+ struct mix *mix;
+
+ spa_list_for_each(mix, &data->mix[direction], link) {
+ if (mix->port->port_id == port_id &&
+ mix->mix_id == mix_id) {
+ pw_log_debug("port %p: found mix %d:%d.%d", mix->port,
+ direction, port_id, mix_id);
+ return mix;
+ }
+ }
+ return NULL;
+}
+
+static struct mix *ensure_mix(struct node_data *data,
+ enum spa_direction direction, uint32_t port_id, uint32_t mix_id)
+{
+ struct mix *mix;
+ struct pw_impl_port *port;
+
+ if ((mix = find_mix(data, direction, port_id, mix_id)))
+ return mix;
+
+ port = pw_impl_node_find_port(data->node, direction, port_id);
+ if (port == NULL)
+ return NULL;
+
+ if (spa_list_is_empty(&data->free_mix)) {
+ if ((mix = calloc(1, sizeof(*mix))) == NULL)
+ return NULL;
+ } else {
+ mix = spa_list_first(&data->free_mix, struct mix, link);
+ spa_list_remove(&mix->link);
+ }
+
+ mix_init(mix, port, mix_id);
+ spa_list_append(&data->mix[direction], &mix->link);
+
+ return mix;
+}
+
+
+static int client_node_transport(void *_data,
+ int readfd, int writefd, uint32_t mem_id, uint32_t offset, uint32_t size)
+{
+ struct node_data *data = _data;
+ struct pw_proxy *proxy = (struct pw_proxy*)data->client_node;
+
+ clean_transport(data);
+
+ data->activation = pw_mempool_map_id(data->pool, mem_id,
+ PW_MEMMAP_FLAG_READWRITE, offset, size, NULL);
+ if (data->activation == NULL) {
+ pw_log_warn("remote-node %p: can't map activation: %m", proxy);
+ return -errno;
+ }
+
+ data->node->rt.activation = data->activation->ptr;
+
+ pw_log_debug("remote-node %p: fds:%d %d node:%u activation:%p",
+ proxy, readfd, writefd, data->remote_id, data->activation->ptr);
+
+ data->rtwritefd = writefd;
+ spa_system_close(data->context->data_system, data->node->source.fd);
+ data->node->source.fd = readfd;
+
+ data->have_transport = true;
+
+ if (data->node->active)
+ pw_client_node_set_active(data->client_node, true);
+
+ return 0;
+}
+
+static int add_node_update(struct node_data *data, uint32_t change_mask, uint32_t info_mask)
+{
+ struct pw_impl_node *node = data->node;
+ struct spa_node_info ni = SPA_NODE_INFO_INIT();
+ uint32_t n_params = 0;
+ struct spa_pod **params = NULL;
+ int res;
+
+ if (change_mask & PW_CLIENT_NODE_UPDATE_PARAMS) {
+ uint32_t i, idx, id;
+ uint8_t buf[4096];
+ struct spa_pod_dynamic_builder b;
+
+ for (i = 0; i < node->info.n_params; i++) {
+ struct spa_pod *param;
+
+ id = node->info.params[i].id;
+ if (id == SPA_PARAM_Invalid)
+ continue;
+
+ for (idx = 0;;) {
+ spa_pod_dynamic_builder_init(&b, buf, sizeof(buf), 4096);
+
+ res = spa_node_enum_params_sync(node->node,
+ id, &idx, NULL, &param, &b.b);
+ if (res == 1) {
+ void *p;
+ p = pw_reallocarray(params, n_params + 1, sizeof(struct spa_pod *));
+ if (p == NULL) {
+ res = -errno;
+ pw_log_error("realloc failed: %m");
+ } else {
+ params = p;
+ params[n_params++] = spa_pod_copy(param);
+ }
+ }
+ spa_pod_dynamic_builder_clean(&b);
+ if (res != 1)
+ break;
+ }
+ }
+ }
+ if (change_mask & PW_CLIENT_NODE_UPDATE_INFO) {
+ ni.max_input_ports = node->info.max_input_ports;
+ ni.max_output_ports = node->info.max_output_ports;
+ ni.change_mask = info_mask;
+ ni.flags = node->spa_flags;
+ ni.props = node->info.props;
+ ni.params = node->info.params;
+ ni.n_params = node->info.n_params;
+ }
+
+ res = pw_client_node_update(data->client_node,
+ change_mask,
+ n_params,
+ (const struct spa_pod **)params,
+ &ni);
+
+ if (params) {
+ while (n_params > 0)
+ free(params[--n_params]);
+ free(params);
+ }
+ return res;
+}
+
+static int add_port_update(struct node_data *data, struct pw_impl_port *port, uint32_t change_mask)
+{
+ struct spa_port_info pi = SPA_PORT_INFO_INIT();
+ uint32_t n_params = 0;
+ struct spa_pod **params = NULL;
+ int res;
+
+ if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_PARAMS) {
+ uint32_t i, idx, id;
+ uint8_t buf[4096];
+ struct spa_pod_dynamic_builder b;
+
+ for (i = 0; i < port->info.n_params; i++) {
+ struct spa_pod *param;
+
+ id = port->info.params[i].id;
+ if (id == SPA_PARAM_Invalid)
+ continue;
+
+ for (idx = 0;;) {
+ spa_pod_dynamic_builder_init(&b, buf, sizeof(buf), 4096);
+
+ res = spa_node_port_enum_params_sync(port->node->node,
+ port->direction, port->port_id,
+ id, &idx, NULL, &param, &b.b);
+ if (res == 1) {
+ void *p;
+ p = pw_reallocarray(params, n_params + 1, sizeof(struct spa_pod*));
+ if (p == NULL) {
+ res = -errno;
+ pw_log_error("realloc failed: %m");
+ } else {
+ params = p;
+ params[n_params++] = spa_pod_copy(param);
+ }
+ }
+ spa_pod_dynamic_builder_clean(&b);
+
+ if (res != 1)
+ break;
+
+ }
+ }
+ }
+ if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_INFO) {
+ pi.change_mask = SPA_PORT_CHANGE_MASK_FLAGS |
+ SPA_PORT_CHANGE_MASK_RATE |
+ SPA_PORT_CHANGE_MASK_PROPS |
+ SPA_PORT_CHANGE_MASK_PARAMS;
+ pi.flags = port->spa_flags;
+ pi.rate = SPA_FRACTION(0, 1);
+ pi.props = &port->properties->dict;
+ SPA_FLAG_CLEAR(pi.flags, SPA_PORT_FLAG_DYNAMIC_DATA);
+ pi.n_params = port->info.n_params;
+ pi.params = port->info.params;
+ }
+
+ res = pw_client_node_port_update(data->client_node,
+ port->direction,
+ port->port_id,
+ change_mask,
+ n_params,
+ (const struct spa_pod **)params,
+ &pi);
+ if (params) {
+ while (n_params > 0)
+ free(params[--n_params]);
+ free(params);
+ }
+ return res;
+}
+
+static int
+client_node_set_param(void *_data, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct node_data *data = _data;
+ struct pw_proxy *proxy = (struct pw_proxy*)data->client_node;
+ int res;
+
+ pw_log_debug("node %p: set_param %s:", proxy,
+ spa_debug_type_find_name(spa_type_param, id));
+
+ res = spa_node_set_param(data->node->node, id, flags, param);
+
+ if (res < 0) {
+ pw_log_error("node %p: set_param %s (%d) %p: %s", proxy,
+ spa_debug_type_find_name(spa_type_param, id),
+ id, param, spa_strerror(res));
+ pw_proxy_errorf(proxy, res, "node_set_param(%s) failed: %s",
+ spa_debug_type_find_name(spa_type_param, id),
+ spa_strerror(res));
+ }
+ return res;
+}
+
+static int
+client_node_set_io(void *_data,
+ uint32_t id,
+ uint32_t memid,
+ uint32_t offset,
+ uint32_t size)
+{
+ struct node_data *data = _data;
+ struct pw_proxy *proxy = (struct pw_proxy*)data->client_node;
+ struct pw_memmap *old, *mm;
+ void *ptr;
+ uint32_t tag[5] = { data->remote_id, SPA_ID_INVALID, id, };
+ int res;
+
+ old = pw_mempool_find_tag(data->pool, tag, sizeof(tag));
+
+ if (memid == SPA_ID_INVALID) {
+ mm = ptr = NULL;
+ size = 0;
+ } else {
+ mm = pw_mempool_map_id(data->pool, memid,
+ PW_MEMMAP_FLAG_READWRITE, offset, size, tag);
+ if (mm == NULL) {
+ pw_log_warn("can't map memory id %u: %m", memid);
+ res = -errno;
+ goto exit;
+ }
+ ptr = mm->ptr;
+ }
+
+ pw_log_debug("node %p: set io %s %p", proxy,
+ spa_debug_type_find_name(spa_type_io, id), ptr);
+
+ res = spa_node_set_io(data->node->node, id, ptr, size);
+
+ pw_memmap_free(old);
+exit:
+ if (res < 0) {
+ pw_log_error("node %p: set_io: %s", proxy, spa_strerror(res));
+ pw_proxy_errorf(proxy, res, "node_set_io failed: %s", spa_strerror(res));
+ }
+ return res;
+}
+
+static int client_node_event(void *data, const struct spa_event *event)
+{
+ pw_log_warn("unhandled node event %d", SPA_EVENT_TYPE(event));
+ return -ENOTSUP;
+}
+
+static int client_node_command(void *_data, const struct spa_command *command)
+{
+ struct node_data *data = _data;
+ struct pw_proxy *proxy = (struct pw_proxy*)data->client_node;
+ int res;
+
+ switch (SPA_NODE_COMMAND_ID(command)) {
+ case SPA_NODE_COMMAND_Pause:
+ pw_log_debug("node %p: pause", proxy);
+
+ if ((res = pw_impl_node_set_state(data->node, PW_NODE_STATE_IDLE)) < 0) {
+ pw_log_warn("node %p: pause failed", proxy);
+ pw_proxy_error(proxy, res, "pause failed");
+ }
+
+ break;
+ case SPA_NODE_COMMAND_Start:
+ pw_log_debug("node %p: start", proxy);
+
+ if ((res = pw_impl_node_set_state(data->node, PW_NODE_STATE_RUNNING)) < 0) {
+ pw_log_warn("node %p: start failed", proxy);
+ pw_proxy_error(proxy, res, "start failed");
+ }
+ break;
+
+ case SPA_NODE_COMMAND_Suspend:
+ pw_log_debug("node %p: suspend", proxy);
+ if ((res = pw_impl_node_set_state(data->node, PW_NODE_STATE_SUSPENDED)) < 0) {
+ pw_log_warn("node %p: suspend failed", proxy);
+ pw_proxy_error(proxy, res, "suspend failed");
+ }
+ break;
+ case SPA_NODE_COMMAND_RequestProcess:
+ res = pw_impl_node_send_command(data->node, command);
+ break;
+ default:
+ pw_log_warn("unhandled node command %d", SPA_NODE_COMMAND_ID(command));
+ res = -ENOTSUP;
+ pw_proxy_errorf(proxy, res, "command %d not supported", SPA_NODE_COMMAND_ID(command));
+ }
+ return res;
+}
+
+static int
+client_node_add_port(void *_data, enum spa_direction direction, uint32_t port_id,
+ const struct spa_dict *props)
+{
+ struct node_data *data = _data;
+ struct pw_proxy *proxy = (struct pw_proxy*)data->client_node;
+ pw_log_warn("add port not supported");
+ pw_proxy_error(proxy, -ENOTSUP, "add port not supported");
+ return -ENOTSUP;
+}
+
+static int
+client_node_remove_port(void *_data, enum spa_direction direction, uint32_t port_id)
+{
+ struct node_data *data = _data;
+ struct pw_proxy *proxy = (struct pw_proxy*)data->client_node;
+ pw_log_warn("remove port not supported");
+ pw_proxy_error(proxy, -ENOTSUP, "remove port not supported");
+ return -ENOTSUP;
+}
+
+static int clear_buffers(struct node_data *data, struct mix *mix)
+{
+ struct pw_impl_port *port = mix->port;
+ struct buffer *b;
+ int res;
+
+ pw_log_debug("port %p: clear %zd buffers mix:%d", port,
+ pw_array_get_len(&mix->buffers, struct buffer *),
+ mix->mix_id);
+
+ if ((res = pw_impl_port_use_buffers(port, &mix->mix, 0, NULL, 0)) < 0) {
+ pw_log_error("port %p: error clear buffers %s", port, spa_strerror(res));
+ return res;
+ }
+
+ pw_array_for_each(b, &mix->buffers) {
+ pw_log_debug("port %p: clear buffer %d map %p %p",
+ port, b->id, b->mem, b->buf);
+ pw_memmap_free(b->mem);
+ free(b->buf);
+ }
+ mix->buffers.size = 0;
+ return 0;
+}
+
+static int
+client_node_port_set_param(void *_data,
+ enum spa_direction direction, uint32_t port_id,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct node_data *data = _data;
+ struct pw_proxy *proxy = (struct pw_proxy*)data->client_node;
+ struct pw_impl_port *port;
+ int res;
+
+ port = pw_impl_node_find_port(data->node, direction, port_id);
+ if (port == NULL) {
+ res = -EINVAL;
+ goto error_exit;
+ }
+
+ pw_log_debug("port %p: set_param %s %p", port,
+ spa_debug_type_find_name(spa_type_param, id), param);
+
+ res = pw_impl_port_set_param(port, id, flags, param);
+ if (res < 0)
+ goto error_exit;
+
+ if (id == SPA_PARAM_Format) {
+ struct mix *mix;
+ spa_list_for_each(mix, &data->mix[direction], link) {
+ if (mix->port->port_id == port_id)
+ clear_buffers(data, mix);
+ }
+ }
+ return res;
+
+error_exit:
+ pw_log_error("port %p: set_param %d %p: %s", port, id, param, spa_strerror(res));
+ pw_proxy_errorf(proxy, res, "port_set_param(%s) failed: %s",
+ spa_debug_type_find_name(spa_type_param, id),
+ spa_strerror(res));
+ return res;
+}
+
+static int
+client_node_port_use_buffers(void *_data,
+ enum spa_direction direction, uint32_t port_id, uint32_t mix_id,
+ uint32_t flags,
+ uint32_t n_buffers, struct pw_client_node_buffer *buffers)
+{
+ struct node_data *data = _data;
+ struct pw_proxy *proxy = (struct pw_proxy*)data->client_node;
+ struct buffer *bid;
+ uint32_t i, j;
+ struct spa_buffer *b, **bufs;
+ struct mix *mix;
+ int res, prot;
+
+ mix = ensure_mix(data, direction, port_id, mix_id);
+ if (mix == NULL) {
+ res = -ENOENT;
+ goto error_exit;
+ }
+
+ if (n_buffers > MAX_BUFFERS)
+ return -ENOSPC;
+
+ prot = PW_MEMMAP_FLAG_READWRITE;
+
+ /* clear previous buffers */
+ clear_buffers(data, mix);
+
+ bufs = alloca(n_buffers * sizeof(struct spa_buffer *));
+
+ for (i = 0; i < n_buffers; i++) {
+ size_t size;
+ off_t offset;
+ struct pw_memmap *mm;
+
+ mm = pw_mempool_map_id(data->pool, buffers[i].mem_id,
+ prot, buffers[i].offset, buffers[i].size, NULL);
+ if (mm == NULL) {
+ res = -errno;
+ goto error_exit_cleanup;
+ }
+
+ bid = pw_array_add(&mix->buffers, sizeof(struct buffer));
+ if (bid == NULL) {
+ res = -errno;
+ goto error_exit_cleanup;
+ }
+ bid->id = i;
+ bid->mem = mm;
+
+ if (data->allow_mlock && mlock(mm->ptr, mm->size) < 0)
+ if (errno != ENOMEM || !mlock_warned) {
+ pw_log(data->warn_mlock ? SPA_LOG_LEVEL_WARN : SPA_LOG_LEVEL_DEBUG,
+ "Failed to mlock memory %p %u: %s",
+ mm->ptr, mm->size,
+ errno == ENOMEM ?
+ "This is not a problem but for best performance, "
+ "consider increasing RLIMIT_MEMLOCK" : strerror(errno));
+ mlock_warned |= errno == ENOMEM;
+ }
+
+ size = sizeof(struct spa_buffer);
+ for (j = 0; j < buffers[i].buffer->n_metas; j++)
+ size += sizeof(struct spa_meta);
+ for (j = 0; j < buffers[i].buffer->n_datas; j++)
+ size += sizeof(struct spa_data);
+
+ b = bid->buf = malloc(size);
+ if (b == NULL) {
+ res = -errno;
+ goto error_exit_cleanup;
+ }
+ memcpy(b, buffers[i].buffer, sizeof(struct spa_buffer));
+
+ b->metas = SPA_PTROFF(b, sizeof(struct spa_buffer), struct spa_meta);
+ b->datas = SPA_PTROFF(b->metas, sizeof(struct spa_meta) * b->n_metas,
+ struct spa_data);
+
+ pw_log_debug("add buffer mem:%d id:%d offset:%u size:%u %p", mm->block->id,
+ bid->id, buffers[i].offset, buffers[i].size, bid->buf);
+
+ offset = 0;
+ for (j = 0; j < b->n_metas; j++) {
+ struct spa_meta *m = &b->metas[j];
+ memcpy(m, &buffers[i].buffer->metas[j], sizeof(struct spa_meta));
+ m->data = SPA_PTROFF(mm->ptr, offset, void);
+ offset += SPA_ROUND_UP_N(m->size, 8);
+ }
+
+ for (j = 0; j < b->n_datas; j++) {
+ struct spa_data *d = &b->datas[j];
+
+ memcpy(d, &buffers[i].buffer->datas[j], sizeof(struct spa_data));
+ d->chunk =
+ SPA_PTROFF(mm->ptr, offset + sizeof(struct spa_chunk) * j,
+ struct spa_chunk);
+
+ if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC)
+ continue;
+
+ if (d->type == SPA_DATA_MemId) {
+ uint32_t mem_id = SPA_PTR_TO_UINT32(d->data);
+ struct pw_memblock *bm;
+
+ bm = pw_mempool_find_id(data->pool, mem_id);
+ if (bm == NULL) {
+ pw_log_error("unknown buffer mem %u", mem_id);
+ res = -ENODEV;
+ goto error_exit_cleanup;
+ }
+
+ d->fd = bm->fd;
+ d->type = bm->type;
+ d->data = NULL;
+
+ pw_log_debug(" data %d %u -> fd %d maxsize %d",
+ j, bm->id, bm->fd, d->maxsize);
+ } else if (d->type == SPA_DATA_MemPtr) {
+ int offs = SPA_PTR_TO_INT(d->data);
+ d->data = SPA_PTROFF(mm->ptr, offs, void);
+ d->fd = -1;
+ pw_log_debug(" data %d id:%u -> mem:%p offs:%d maxsize:%d",
+ j, bid->id, d->data, offs, d->maxsize);
+ } else {
+ pw_log_warn("unknown buffer data type %d", d->type);
+ }
+ }
+ bufs[i] = b;
+ }
+
+ if ((res = pw_impl_port_use_buffers(mix->port, &mix->mix, flags, bufs, n_buffers)) < 0)
+ goto error_exit_cleanup;
+
+ if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) {
+ pw_client_node_port_buffers(data->client_node,
+ direction, port_id, mix_id,
+ n_buffers,
+ bufs);
+ }
+ return res;
+
+error_exit_cleanup:
+ clear_buffers(data, mix);
+error_exit:
+ pw_log_error("port %p: use_buffers: %d %s", mix, res, spa_strerror(res));
+ pw_proxy_errorf(proxy, res, "port_use_buffers error: %s", spa_strerror(res));
+ return res;
+}
+
+static int
+client_node_port_set_io(void *_data,
+ uint32_t direction,
+ uint32_t port_id,
+ uint32_t mix_id,
+ uint32_t id,
+ uint32_t memid,
+ uint32_t offset,
+ uint32_t size)
+{
+ struct node_data *data = _data;
+ struct pw_proxy *proxy = (struct pw_proxy*)data->client_node;
+ struct mix *mix;
+ struct pw_memmap *mm, *old;
+ void *ptr;
+ int res = 0;
+ uint32_t tag[5] = { data->remote_id, direction, port_id, mix_id, id };
+
+ mix = ensure_mix(data, direction, port_id, mix_id);
+ if (mix == NULL) {
+ res = -ENOENT;
+ goto exit;
+ }
+
+ old = pw_mempool_find_tag(data->pool, tag, sizeof(tag));
+
+ if (memid == SPA_ID_INVALID) {
+ mm = ptr = NULL;
+ size = 0;
+ }
+ else {
+ mm = pw_mempool_map_id(data->pool, memid,
+ PW_MEMMAP_FLAG_READWRITE, offset, size, tag);
+ if (mm == NULL) {
+ pw_log_warn("can't map memory id %u: %m", memid);
+ res = -errno;
+ goto exit;
+ }
+ ptr = mm->ptr;
+ }
+
+ pw_log_debug("port %p: set io:%s new:%p old:%p", mix->port,
+ spa_debug_type_find_name(spa_type_io, id), ptr, mix->mix.io);
+
+ if (id == SPA_IO_Buffers) {
+ if (ptr == NULL && mix->mix.io)
+ deactivate_mix(data, mix);
+ }
+
+ if ((res = spa_node_port_set_io(mix->port->mix,
+ direction, mix->mix.port.port_id, id, ptr, size)) < 0) {
+ if (res == -ENOTSUP)
+ res = 0;
+ else
+ goto exit_free;
+ }
+ if (id == SPA_IO_Buffers) {
+ mix->mix.io = ptr;
+ if (ptr)
+ activate_mix(data, mix);
+ }
+exit_free:
+ pw_memmap_free(old);
+exit:
+ if (res < 0) {
+ pw_log_error("port %p: set_io: %s", mix, spa_strerror(res));
+ pw_proxy_errorf(proxy, res, "port_set_io failed: %s", spa_strerror(res));
+ }
+ return res;
+}
+
+static int link_signal_func(void *user_data)
+{
+ struct link *link = user_data;
+ struct spa_system *data_system = link->data->context->data_system;
+
+ pw_log_trace_fp("link %p: signal %p", link, link->target.activation);
+ if (SPA_UNLIKELY(spa_system_eventfd_write(data_system, link->signalfd, 1) < 0))
+ pw_log_warn("link %p: write failed %m", link);
+
+ return 0;
+}
+
+static int
+do_activate_link(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct link *link = user_data;
+ struct node_data *d = link->data;
+ pw_log_trace("link %p activate", link);
+ spa_list_append(&d->node->rt.target_list, &link->target.link);
+ return 0;
+}
+
+static int
+client_node_set_activation(void *_data,
+ uint32_t node_id,
+ int signalfd,
+ uint32_t memid,
+ uint32_t offset,
+ uint32_t size)
+{
+ struct node_data *data = _data;
+ struct pw_proxy *proxy = (struct pw_proxy*)data->client_node;
+ struct pw_impl_node *node = data->node;
+ struct pw_memmap *mm;
+ void *ptr;
+ struct link *link;
+ int res = 0;
+
+ if (data->remote_id == node_id) {
+ pw_log_debug("node %p: our activation %u: %u %u %u", node, node_id,
+ memid, offset, size);
+ spa_system_close(data->context->data_system, signalfd);
+ return 0;
+ }
+
+ if (memid == SPA_ID_INVALID) {
+ mm = ptr = NULL;
+ size = 0;
+ } else {
+ mm = pw_mempool_map_id(data->pool, memid,
+ PW_MEMMAP_FLAG_READWRITE, offset, size, NULL);
+ if (mm == NULL) {
+ res = -errno;
+ goto error_exit;
+ }
+ ptr = mm->ptr;
+ }
+ pw_log_debug("node %p: set activation %d %p %u %u", node, node_id, ptr, offset, size);
+
+ if (ptr) {
+ link = calloc(1, sizeof(struct link));
+ if (link == NULL) {
+ res = -errno;
+ goto error_exit;
+ }
+ link->data = data;
+ link->node_id = node_id;
+ link->map = mm;
+ link->target.activation = ptr;
+ link->signalfd = signalfd;
+ link->target.signal_func = link_signal_func;
+ link->target.data = link;
+ link->target.node = NULL;
+ spa_list_append(&data->links, &link->link);
+
+ pw_loop_invoke(data->context->data_loop,
+ do_activate_link, SPA_ID_INVALID, NULL, 0, false, link);
+
+ pw_log_debug("node %p: link %p: fd:%d id:%u state %p required %d, pending %d",
+ node, link, signalfd,
+ link->target.activation->position.clock.id,
+ &link->target.activation->state[0],
+ link->target.activation->state[0].required,
+ link->target.activation->state[0].pending);
+ } else {
+ link = find_activation(&data->links, node_id);
+ if (link == NULL) {
+ res = -ENOENT;
+ goto error_exit;
+ }
+ clear_link(data, link);
+ }
+ return res;
+
+error_exit:
+ pw_log_error("node %p: set activation %d: %s", node, node_id, spa_strerror(res));
+ pw_proxy_errorf(proxy, res, "set_activation: %s", spa_strerror(res));
+ return res;
+}
+
+static const struct pw_client_node_events client_node_events = {
+ PW_VERSION_CLIENT_NODE_EVENTS,
+ .transport = client_node_transport,
+ .set_param = client_node_set_param,
+ .set_io = client_node_set_io,
+ .event = client_node_event,
+ .command = client_node_command,
+ .add_port = client_node_add_port,
+ .remove_port = client_node_remove_port,
+ .port_set_param = client_node_port_set_param,
+ .port_use_buffers = client_node_port_use_buffers,
+ .port_set_io = client_node_port_set_io,
+ .set_activation = client_node_set_activation,
+};
+
+static void do_node_init(struct node_data *data)
+{
+ struct pw_impl_port *port;
+
+ pw_log_debug("%p: node %p init", data, data->node);
+ add_node_update(data, PW_CLIENT_NODE_UPDATE_PARAMS |
+ PW_CLIENT_NODE_UPDATE_INFO,
+ SPA_NODE_CHANGE_MASK_FLAGS |
+ SPA_NODE_CHANGE_MASK_PROPS |
+ SPA_NODE_CHANGE_MASK_PARAMS);
+
+ spa_list_for_each(port, &data->node->input_ports, link) {
+ add_port_update(data, port,
+ PW_CLIENT_NODE_PORT_UPDATE_PARAMS |
+ PW_CLIENT_NODE_PORT_UPDATE_INFO);
+ }
+ spa_list_for_each(port, &data->node->output_ports, link) {
+ add_port_update(data, port,
+ PW_CLIENT_NODE_PORT_UPDATE_PARAMS |
+ PW_CLIENT_NODE_PORT_UPDATE_INFO);
+ }
+}
+
+static void clear_mix(struct node_data *data, struct mix *mix)
+{
+ pw_log_debug("port %p: mix clear %d.%d", mix->port, mix->port->port_id, mix->mix_id);
+
+ deactivate_mix(data, mix);
+
+ spa_list_remove(&mix->link);
+
+ clear_buffers(data, mix);
+ pw_array_clear(&mix->buffers);
+
+ spa_list_append(&data->free_mix, &mix->link);
+ pw_impl_port_release_mix(mix->port, &mix->mix);
+}
+
+static void clean_node(struct node_data *d)
+{
+ struct mix *mix;
+
+ if (d->have_transport) {
+ spa_list_consume(mix, &d->mix[SPA_DIRECTION_INPUT], link)
+ clear_mix(d, mix);
+ spa_list_consume(mix, &d->mix[SPA_DIRECTION_OUTPUT], link)
+ clear_mix(d, mix);
+ }
+ spa_list_consume(mix, &d->free_mix, link) {
+ spa_list_remove(&mix->link);
+ free(mix);
+ }
+ clean_transport(d);
+}
+
+static void node_destroy(void *data)
+{
+ struct node_data *d = data;
+
+ pw_log_debug("%p: destroy", d);
+
+ clean_node(d);
+}
+
+static void node_free(void *data)
+{
+ struct node_data *d = data;
+ pw_log_debug("%p: free", d);
+ d->node = NULL;
+}
+
+static void node_info_changed(void *data, const struct pw_node_info *info)
+{
+ struct node_data *d = data;
+ uint32_t change_mask, info_mask;
+
+ pw_log_debug("info changed %p", d);
+
+ if (d->client_node == NULL)
+ return;
+
+ change_mask = PW_CLIENT_NODE_UPDATE_INFO;
+ info_mask = SPA_NODE_CHANGE_MASK_FLAGS;
+ if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS) {
+ info_mask |= SPA_NODE_CHANGE_MASK_PROPS;
+ }
+ if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) {
+ change_mask |= PW_CLIENT_NODE_UPDATE_PARAMS;
+ info_mask |= SPA_NODE_CHANGE_MASK_PARAMS;
+ }
+ add_node_update(d, change_mask, info_mask);
+}
+
+static void node_port_info_changed(void *data, struct pw_impl_port *port,
+ const struct pw_port_info *info)
+{
+ struct node_data *d = data;
+ uint32_t change_mask = 0;
+
+ pw_log_debug("info changed %p", d);
+
+ if (d->client_node == NULL)
+ return;
+
+ if (info->change_mask & PW_PORT_CHANGE_MASK_PROPS)
+ change_mask |= PW_CLIENT_NODE_PORT_UPDATE_INFO;
+ if (info->change_mask & PW_PORT_CHANGE_MASK_PARAMS) {
+ change_mask |= PW_CLIENT_NODE_PORT_UPDATE_PARAMS;
+ change_mask |= PW_CLIENT_NODE_PORT_UPDATE_INFO;
+ }
+ add_port_update(d, port, change_mask);
+}
+
+static void node_port_removed(void *data, struct pw_impl_port *port)
+{
+ struct node_data *d = data;
+ struct mix *mix, *tmp;
+
+ pw_log_debug("removed %p", d);
+
+ if (d->client_node == NULL)
+ return;
+
+ pw_client_node_port_update(d->client_node,
+ port->direction,
+ port->port_id,
+ 0, 0, NULL, NULL);
+
+ spa_list_for_each_safe(mix, tmp, &d->mix[port->direction], link) {
+ if (mix->port == port)
+ clear_mix(d, mix);
+ }
+}
+
+static void node_active_changed(void *data, bool active)
+{
+ struct node_data *d = data;
+ pw_log_debug("active %d", active);
+
+ if (d->client_node == NULL)
+ return;
+
+ pw_client_node_set_active(d->client_node, active);
+}
+
+static void node_event(void *data, const struct spa_event *event)
+{
+ struct node_data *d = data;
+ pw_log_debug("%p", d);
+
+ if (d->client_node == NULL)
+ return;
+ pw_client_node_event(d->client_node, event);
+}
+
+static const struct pw_impl_node_events node_events = {
+ PW_VERSION_IMPL_NODE_EVENTS,
+ .destroy = node_destroy,
+ .free = node_free,
+ .info_changed = node_info_changed,
+ .port_info_changed = node_port_info_changed,
+ .port_removed = node_port_removed,
+ .active_changed = node_active_changed,
+ .event = node_event,
+};
+
+static void client_node_removed(void *_data)
+{
+ struct node_data *data = _data;
+ pw_log_debug("%p: removed", data);
+
+ spa_hook_remove(&data->proxy_client_node_listener);
+ spa_hook_remove(&data->client_node_listener);
+
+ if (data->node) {
+ spa_hook_remove(&data->node_listener);
+ pw_impl_node_set_state(data->node, PW_NODE_STATE_SUSPENDED);
+
+ clean_node(data);
+
+ if (data->do_free)
+ pw_impl_node_destroy(data->node);
+ }
+ data->client_node = NULL;
+}
+
+static void client_node_destroy(void *_data)
+{
+ struct node_data *data = _data;
+
+ pw_log_debug("%p: destroy", data);
+ client_node_removed(_data);
+}
+
+static void client_node_bound(void *_data, uint32_t global_id)
+{
+ struct node_data *data = _data;
+ pw_log_debug("%p: bound %u", data, global_id);
+ data->remote_id = global_id;
+}
+
+static const struct pw_proxy_events proxy_client_node_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .removed = client_node_removed,
+ .destroy = client_node_destroy,
+ .bound = client_node_bound,
+};
+
+static int node_ready(void *d, int status)
+{
+ struct node_data *data = d;
+ struct pw_impl_node *node = data->node;
+ struct pw_node_activation *a = node->rt.activation;
+ struct spa_system *data_system = data->context->data_system;
+ struct timespec ts;
+ struct pw_impl_port *p;
+
+ pw_log_trace_fp("node %p: ready driver:%d exported:%d status:%d", node,
+ node->driver, node->exported, status);
+
+ if (status & SPA_STATUS_HAVE_DATA) {
+ spa_list_for_each(p, &node->rt.output_mix, rt.node_link)
+ spa_node_process(p->mix);
+ }
+
+ spa_system_clock_gettime(data_system, CLOCK_MONOTONIC, &ts);
+ a->status = PW_NODE_ACTIVATION_TRIGGERED;
+ a->signal_time = SPA_TIMESPEC_TO_NSEC(&ts);
+
+ if (SPA_UNLIKELY(spa_system_eventfd_write(data_system, data->rtwritefd, 1) < 0))
+ pw_log_warn("node %p: write failed %m", node);
+
+ return 0;
+}
+
+static int node_reuse_buffer(void *data, uint32_t port_id, uint32_t buffer_id)
+{
+ return 0;
+}
+
+static int node_xrun(void *d, uint64_t trigger, uint64_t delay, struct spa_pod *info)
+{
+ struct node_data *data = d;
+ struct pw_impl_node *node = data->node;
+ struct pw_node_activation *a = node->rt.activation;
+
+ a->xrun_count++;
+ a->xrun_time = trigger;
+ a->xrun_delay = delay;
+ a->max_delay = SPA_MAX(a->max_delay, delay);
+
+ pw_log_debug("node %p: XRun! count:%u time:%"PRIu64" delay:%"PRIu64" max:%"PRIu64,
+ node, a->xrun_count, trigger, delay, a->max_delay);
+
+ pw_context_driver_emit_xrun(data->context, node);
+
+ return 0;
+}
+
+static const struct spa_node_callbacks node_callbacks = {
+ SPA_VERSION_NODE_CALLBACKS,
+ .ready = node_ready,
+ .reuse_buffer = node_reuse_buffer,
+ .xrun = node_xrun
+};
+
+static struct pw_proxy *node_export(struct pw_core *core, void *object, bool do_free,
+ size_t user_data_size)
+{
+ struct pw_impl_node *node = object;
+ struct pw_proxy *client_node;
+ struct node_data *data;
+
+ user_data_size = SPA_ROUND_UP_N(user_data_size, __alignof__(struct node_data));
+
+ client_node = pw_core_create_object(core,
+ "client-node",
+ PW_TYPE_INTERFACE_ClientNode,
+ PW_VERSION_CLIENT_NODE,
+ &node->properties->dict,
+ user_data_size + sizeof(struct node_data));
+ if (client_node == NULL)
+ goto error;
+
+ data = pw_proxy_get_user_data(client_node);
+ data = SPA_PTROFF(data, user_data_size, struct node_data);
+ data->pool = pw_core_get_mempool(core);
+ data->node = node;
+ data->do_free = do_free;
+ data->context = pw_impl_node_get_context(node);
+ data->client_node = (struct pw_client_node *)client_node;
+ data->remote_id = SPA_ID_INVALID;
+
+
+ data->allow_mlock = pw_properties_get_bool(node->properties, "mem.allow-mlock",
+ data->context->settings.mem_allow_mlock);
+
+ data->warn_mlock = pw_properties_get_bool(node->properties, "mem.warn-mlock",
+ data->context->settings.mem_warn_mlock);
+
+ node->exported = true;
+
+ spa_list_init(&data->free_mix);
+ spa_list_init(&data->mix[0]);
+ spa_list_init(&data->mix[1]);
+
+ spa_list_init(&data->links);
+
+ pw_proxy_add_listener(client_node,
+ &data->proxy_client_node_listener,
+ &proxy_client_node_events, data);
+
+ spa_node_set_callbacks(node->node, &node_callbacks, data);
+ pw_impl_node_add_listener(node, &data->node_listener, &node_events, data);
+
+ pw_client_node_add_listener(data->client_node,
+ &data->client_node_listener,
+ &client_node_events,
+ data);
+ do_node_init(data);
+
+ return client_node;
+error:
+ if (do_free)
+ pw_impl_node_destroy(node);
+ return NULL;
+
+}
+
+struct pw_proxy *pw_core_node_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size)
+{
+ struct pw_impl_node *node = object;
+
+ if (props)
+ pw_impl_node_update_properties(node, props);
+ return node_export(core, object, false, user_data_size);
+}
+
+struct pw_proxy *pw_core_spa_node_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size)
+{
+ struct pw_impl_node *node;
+ struct pw_proxy *proxy;
+ const char *str;
+ bool do_register;
+
+ str = props ? spa_dict_lookup(props, PW_KEY_OBJECT_REGISTER) : NULL;
+ do_register = str ? pw_properties_parse_bool(str) : true;
+
+ node = pw_context_create_node(pw_core_get_context(core),
+ props ? pw_properties_new_dict(props) : NULL, 0);
+ if (node == NULL)
+ return NULL;
+
+ pw_impl_node_set_implementation(node, (struct spa_node*)object);
+
+ if (do_register)
+ pw_impl_node_register(node, NULL);
+
+ proxy = node_export(core, node, true, user_data_size);
+ if (proxy)
+ pw_impl_node_set_active(node, true);
+
+ return proxy;
+}
diff --git a/src/modules/module-client-node/v0/client-node.c b/src/modules/module-client-node/v0/client-node.c
new file mode 100644
index 0000000..e70a7c2
--- /dev/null
+++ b/src/modules/module-client-node/v0/client-node.c
@@ -0,0 +1,1447 @@
+/* PipeWire
+ *
+ * Copyright © 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dlfcn.h>
+#include <sys/socket.h>
+#include <sys/mman.h>
+
+#include <spa/node/node.h>
+#include <spa/node/utils.h>
+#include <spa/node/io.h>
+#include <spa/pod/filter.h>
+#include <spa/utils/keys.h>
+
+#define PW_ENABLE_DEPRECATED
+
+#include "pipewire/pipewire.h"
+#include "pipewire/private.h"
+
+#include "pipewire/context.h"
+#include "modules/spa/spa-node.h"
+#include "client-node.h"
+#include "transport.h"
+
+PW_LOG_TOPIC_EXTERN(mod_topic);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+/** \cond */
+
+#define MAX_INPUTS 64
+#define MAX_OUTPUTS 64
+
+#define MAX_BUFFERS 64
+
+#define CHECK_IN_PORT_ID(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_INPUTS)
+#define CHECK_OUT_PORT_ID(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) < MAX_OUTPUTS)
+#define CHECK_PORT_ID(this,d,p) (CHECK_IN_PORT_ID(this,d,p) || CHECK_OUT_PORT_ID(this,d,p))
+#define CHECK_FREE_IN_PORT(this,d,p) (CHECK_IN_PORT_ID(this,d,p) && !(this)->in_ports[p].valid)
+#define CHECK_FREE_OUT_PORT(this,d,p) (CHECK_OUT_PORT_ID(this,d,p) && !(this)->out_ports[p].valid)
+#define CHECK_FREE_PORT(this,d,p) (CHECK_FREE_IN_PORT (this,d,p) || CHECK_FREE_OUT_PORT (this,d,p))
+#define CHECK_IN_PORT(this,d,p) (CHECK_IN_PORT_ID(this,d,p) && (this)->in_ports[p].valid)
+#define CHECK_OUT_PORT(this,d,p) (CHECK_OUT_PORT_ID(this,d,p) && (this)->out_ports[p].valid)
+#define CHECK_PORT(this,d,p) (CHECK_IN_PORT (this,d,p) || CHECK_OUT_PORT (this,d,p))
+
+#define GET_IN_PORT(this,p) (&this->in_ports[p])
+#define GET_OUT_PORT(this,p) (&this->out_ports[p])
+#define GET_PORT(this,d,p) (d == SPA_DIRECTION_INPUT ? GET_IN_PORT(this,p) : GET_OUT_PORT(this,p))
+
+#define CHECK_PORT_BUFFER(this,b,p) (b < p->n_buffers)
+
+extern uint32_t pw_protocol_native0_type_from_v2(struct pw_impl_client *client, uint32_t type);
+extern uint32_t pw_protocol_native0_name_to_v2(struct pw_impl_client *client, const char *name);
+
+struct mem {
+ uint32_t id;
+ int ref;
+ int fd;
+ uint32_t type;
+ uint32_t flags;
+};
+
+struct buffer {
+ struct spa_buffer *outbuf;
+ struct spa_buffer buffer;
+ struct spa_meta metas[4];
+ struct spa_data datas[4];
+ bool outstanding;
+ uint32_t memid;
+};
+
+struct port {
+ uint32_t id;
+ enum spa_direction direction;
+
+ bool valid;
+ struct spa_port_info info;
+ struct pw_properties *properties;
+
+ bool have_format;
+ uint32_t n_params;
+ struct spa_pod **params;
+ struct spa_io_buffers *io;
+
+ uint32_t n_buffers;
+ struct buffer buffers[MAX_BUFFERS];
+};
+
+struct node {
+ struct spa_node node;
+
+ struct impl *impl;
+
+ struct spa_log *log;
+ struct spa_loop *data_loop;
+ struct spa_system *data_system;
+
+ struct spa_hook_list hooks;
+ struct spa_callbacks callbacks;
+
+ struct spa_io_position *position;
+
+ struct pw_resource *resource;
+
+ struct spa_source data_source;
+ int writefd;
+
+ struct spa_node_info info;
+
+ uint32_t n_inputs;
+ uint32_t n_outputs;
+ struct port in_ports[MAX_INPUTS];
+ struct port out_ports[MAX_OUTPUTS];
+
+ uint32_t n_params;
+ struct spa_pod **params;
+
+ uint32_t seq;
+ uint32_t init_pending;
+};
+
+struct impl {
+ struct pw_impl_client_node0 this;
+
+ bool client_reuse;
+
+ struct pw_context *context;
+
+ struct node node;
+
+ struct pw_client_node0_transport *transport;
+
+ struct spa_hook node_listener;
+ struct spa_hook resource_listener;
+ struct spa_hook object_listener;
+
+ struct pw_array mems;
+
+ int fds[2];
+ int other_fds[2];
+
+ uint32_t input_ready;
+ bool out_pending;
+};
+
+/** \endcond */
+
+static struct mem *ensure_mem(struct impl *impl, int fd, uint32_t type, uint32_t flags)
+{
+ struct mem *m, *f = NULL;
+
+ pw_array_for_each(m, &impl->mems) {
+ if (m->ref <= 0)
+ f = m;
+ else if (m->fd == fd)
+ goto found;
+ }
+
+ if (f == NULL) {
+ m = pw_array_add(&impl->mems, sizeof(struct mem));
+ m->id = pw_array_get_len(&impl->mems, struct mem) - 1;
+ m->ref = 0;
+ }
+ else {
+ m = f;
+ }
+ m->fd = fd;
+ m->type = type;
+ m->flags = flags;
+
+ pw_client_node0_resource_add_mem(impl->node.resource,
+ m->id,
+ type,
+ m->fd,
+ m->flags);
+ found:
+ m->ref++;
+ return m;
+}
+
+
+static int clear_buffers(struct node *this, struct port *port)
+{
+ uint32_t i, j;
+ struct impl *impl = this->impl;
+
+ for (i = 0; i < port->n_buffers; i++) {
+ struct buffer *b = &port->buffers[i];
+ struct mem *m;
+
+ spa_log_debug(this->log, "node %p: clear buffer %d", this, i);
+
+ for (j = 0; j < b->buffer.n_datas; j++) {
+ struct spa_data *d = &b->datas[j];
+
+ if (d->type == SPA_DATA_DmaBuf ||
+ d->type == SPA_DATA_MemFd) {
+ uint32_t id;
+
+ id = SPA_PTR_TO_UINT32(b->buffer.datas[j].data);
+ m = pw_array_get_unchecked(&impl->mems, id, struct mem);
+ m->ref--;
+ }
+ }
+ m = pw_array_get_unchecked(&impl->mems, b->memid, struct mem);
+ m->ref--;
+ }
+ port->n_buffers = 0;
+ return 0;
+}
+
+static void emit_port_info(struct node *this, struct port *port)
+{
+ spa_node_emit_port_info(&this->hooks,
+ port->direction, port->id, &port->info);
+}
+
+static int impl_node_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct spa_node_events *events,
+ void *data)
+{
+ struct node *this = object;
+ struct spa_hook_list save;
+ uint32_t i;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ spa_hook_list_isolate(&this->hooks, &save, listener, events, data);
+
+ for (i = 0; i < MAX_INPUTS; i++) {
+ if (this->in_ports[i].valid)
+ emit_port_info(this, &this->in_ports[i]);
+ }
+ for (i = 0; i < MAX_OUTPUTS; i++) {
+ if (this->out_ports[i].valid)
+ emit_port_info(this, &this->out_ports[i]);
+ }
+ spa_hook_list_join(&this->hooks, &save);
+
+ 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 node *this = object;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = { 0 };
+ struct spa_result_node_params result;
+ uint32_t count = 0;
+ bool found = false;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(num != 0, -EINVAL);
+
+ result.id = id;
+ result.next = 0;
+
+ while (true) {
+ struct spa_pod *param;
+
+ result.index = result.next++;
+ if (result.index >= this->n_params)
+ break;
+
+ param = this->params[result.index];
+
+ if (param == NULL || !spa_pod_is_object_id(param, id))
+ continue;
+
+ found = true;
+
+ if (result.index < start)
+ continue;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ if (spa_pod_filter(&b, &result.param, param, filter) != 0)
+ continue;
+
+ pw_log_debug("%p: %d param %u", this, seq, result.index);
+ spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+
+ if (++count == num)
+ break;
+ }
+ return found ? 0 : -ENOENT;
+}
+
+static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct node *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ if (this->resource == NULL)
+ return -EIO;
+
+ pw_client_node0_resource_set_param(this->resource, this->seq, id, flags, param);
+
+ return SPA_RESULT_RETURN_ASYNC(this->seq++);
+}
+
+static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
+{
+ struct node *this = object;
+ int res = 0;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ switch(id) {
+ case SPA_IO_Position:
+ this->position = data;
+ break;
+ default:
+ res = -ENOTSUP;
+ break;
+ }
+ return res;
+}
+
+static inline void do_flush(struct node *this)
+{
+ if (spa_system_eventfd_write(this->data_system, this->writefd, 1) < 0)
+ spa_log_warn(this->log, "node %p: error flushing : %s", this, strerror(errno));
+
+}
+
+static int send_clock_update(struct node *this)
+{
+ struct pw_impl_client *client = this->resource->client;
+ uint32_t type = pw_protocol_native0_name_to_v2(client, SPA_TYPE_INFO_NODE_COMMAND_BASE "ClockUpdate");
+ struct timespec ts;
+ int64_t now;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ now = SPA_TIMESPEC_TO_NSEC(&ts);
+ pw_log_trace("%p: now %"PRIi64, this, now);
+
+ struct spa_command_node0_clock_update cu =
+ SPA_COMMAND_NODE0_CLOCK_UPDATE_INIT(type,
+ SPA_COMMAND_NODE0_CLOCK_UPDATE_TIME |
+ SPA_COMMAND_NODE0_CLOCK_UPDATE_SCALE |
+ SPA_COMMAND_NODE0_CLOCK_UPDATE_STATE |
+ SPA_COMMAND_NODE0_CLOCK_UPDATE_LATENCY, /* change_mask */
+ SPA_USEC_PER_SEC, /* rate */
+ now / SPA_NSEC_PER_USEC, /* ticks */
+ now, /* monotonic_time */
+ 0, /* offset */
+ (1 << 16) | 1, /* scale */
+ SPA_CLOCK0_STATE_RUNNING, /* state */
+ SPA_COMMAND_NODE0_CLOCK_UPDATE_FLAG_LIVE, /* flags */
+ 0); /* latency */
+
+ pw_client_node0_resource_command(this->resource, this->seq, (const struct spa_command*)&cu);
+ return SPA_RESULT_RETURN_ASYNC(this->seq++);
+}
+
+static int impl_node_send_command(void *object, const struct spa_command *command)
+{
+ struct node *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(command != NULL, -EINVAL);
+
+ if (this->resource == NULL)
+ return -EIO;
+
+ if (SPA_NODE_COMMAND_ID(command) == SPA_NODE_COMMAND_Start) {
+ send_clock_update(this);
+ }
+
+ pw_client_node0_resource_command(this->resource, this->seq, command);
+ return SPA_RESULT_RETURN_ASYNC(this->seq++);
+}
+
+static int
+impl_node_set_callbacks(void *object,
+ const struct spa_node_callbacks *callbacks,
+ void *data)
+{
+ struct node *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ this->callbacks = SPA_CALLBACKS_INIT(callbacks, data);
+
+ return 0;
+}
+
+static int
+impl_node_sync(void *object, int seq)
+{
+ struct node *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+
+ pw_log_debug("%p: sync %p", this, this->resource);
+
+ if (this->resource == NULL)
+ return -EIO;
+
+ this->init_pending = SPA_RESULT_RETURN_ASYNC(this->seq++);
+
+ return this->init_pending;
+}
+
+
+extern struct spa_pod *pw_protocol_native0_pod_from_v2(struct pw_impl_client *client, const struct spa_pod *pod);
+extern int pw_protocol_native0_pod_to_v2(struct pw_impl_client *client, const struct spa_pod *pod,
+ struct spa_pod_builder *b);
+
+static void
+do_update_port(struct node *this,
+ 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)
+{
+ struct port *port;
+
+ port = GET_PORT(this, direction, port_id);
+
+ if (!port->valid) {
+ spa_log_debug(this->log, "node %p: adding port %d, direction %d",
+ this, port_id, direction);
+ port->id = port_id;
+ port->direction = direction;
+ port->have_format = false;
+ port->valid = true;
+
+ if (direction == SPA_DIRECTION_INPUT)
+ this->n_inputs++;
+ else
+ this->n_outputs++;
+ }
+
+ if (change_mask & PW_CLIENT_NODE0_PORT_UPDATE_PARAMS) {
+ uint32_t i;
+
+ port->have_format = false;
+
+ spa_log_debug(this->log, "node %p: port %u update %d params", this, port_id, n_params);
+ for (i = 0; i < port->n_params; i++)
+ free(port->params[i]);
+ port->n_params = n_params;
+ if (port->n_params == 0) {
+ free(port->params);
+ port->params = NULL;
+ } else {
+ void *p;
+ p = pw_reallocarray(port->params, port->n_params, sizeof(struct spa_pod *));
+ if (p == NULL) {
+ pw_log_error("%p: port %u can't realloc: %m", this, port_id);
+ free(port->params);
+ port->n_params = 0;
+ }
+ port->params = p;
+ }
+ for (i = 0; i < port->n_params; i++) {
+ port->params[i] = params[i] ?
+ pw_protocol_native0_pod_from_v2(this->resource->client, params[i]) : NULL;
+
+ if (port->params[i] && spa_pod_is_object_id(port->params[i], SPA_PARAM_Format))
+ port->have_format = true;
+ }
+ }
+
+ if (change_mask & PW_CLIENT_NODE0_PORT_UPDATE_INFO) {
+ pw_properties_free(port->properties);
+ port->properties = NULL;
+ port->info.props = NULL;
+ port->info.n_params = 0;
+ port->info.params = NULL;
+
+ if (info) {
+ port->info = *info;
+ if (info->props) {
+ port->properties = pw_properties_new_dict(info->props);
+ port->info.props = &port->properties->dict;
+ }
+ }
+ spa_node_emit_port_info(&this->hooks, direction, port_id, info);
+ }
+}
+
+static void
+clear_port(struct node *this,
+ struct port *port, enum spa_direction direction, uint32_t port_id)
+{
+ do_update_port(this,
+ direction,
+ port_id,
+ PW_CLIENT_NODE0_PORT_UPDATE_PARAMS |
+ PW_CLIENT_NODE0_PORT_UPDATE_INFO, 0, NULL, NULL);
+ clear_buffers(this, port);
+}
+
+static void do_uninit_port(struct node *this, enum spa_direction direction, uint32_t port_id)
+{
+ struct port *port;
+
+ spa_log_debug(this->log, "node %p: removing port %d", this, port_id);
+
+ if (direction == SPA_DIRECTION_INPUT) {
+ port = GET_IN_PORT(this, port_id);
+ this->n_inputs--;
+ } else {
+ port = GET_OUT_PORT(this, port_id);
+ this->n_outputs--;
+ }
+ clear_port(this, port, direction, port_id);
+ port->valid = false;
+ spa_node_emit_port_info(&this->hooks, direction, port_id, NULL);
+}
+
+static int
+impl_node_add_port(void *object, enum spa_direction direction, uint32_t port_id,
+ const struct spa_dict *props)
+{
+ struct node *this = object;
+ struct port *port;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(CHECK_FREE_PORT(this, direction, port_id), -EINVAL);
+
+ port = GET_PORT(this, direction, port_id);
+ clear_port(this, port, direction, port_id);
+
+ return 0;
+}
+
+static int
+impl_node_remove_port(void *object, enum spa_direction direction, uint32_t port_id)
+{
+ struct node *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
+
+ do_uninit_port(this, direction, port_id);
+
+ return 0;
+}
+
+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 node *this = object;
+ struct port *port;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = { 0 };
+ struct spa_result_node_params result;
+ uint32_t count = 0;
+ bool found = false;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(num != 0, -EINVAL);
+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
+
+ port = GET_PORT(this, direction, port_id);
+
+ pw_log_debug("%p: %d port %d.%d %u %u %u", this, seq,
+ direction, port_id, id, start, num);
+
+ result.id = id;
+ result.next = 0;
+
+ while (true) {
+ struct spa_pod *param;
+
+ result.index = result.next++;
+ if (result.index >= port->n_params)
+ break;
+
+ param = port->params[result.index];
+
+ if (param == NULL || !spa_pod_is_object_id(param, id))
+ continue;
+
+ found = true;
+
+ if (result.index < start)
+ continue;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ if (spa_pod_filter(&b, &result.param, param, filter) < 0)
+ continue;
+
+ pw_log_debug("%p: %d param %u", this, seq, result.index);
+ spa_node_emit_result(&this->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+
+ if (++count == num)
+ break;
+ }
+ return found ? 0 : -ENOENT;
+}
+
+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 node *this = object;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
+
+ if (this->resource == NULL)
+ return -EIO;
+
+ pw_client_node0_resource_port_set_param(this->resource,
+ this->seq,
+ direction, port_id,
+ id, flags,
+ param);
+ return SPA_RESULT_RETURN_ASYNC(this->seq++);
+}
+
+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 node *this = object;
+ struct impl *impl;
+ struct pw_memblock *mem;
+ struct mem *m;
+ uint32_t memid, mem_offset, mem_size;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
+
+ impl = this->impl;
+
+ spa_log_debug(this->log, "node %p: port %d.%d set io %d %p", this,
+ direction, port_id, id, data);
+
+ if (id == SPA_IO_Buffers) {
+ struct port *port = GET_PORT(this, direction, port_id);
+ port->io = data;
+ }
+
+ if (this->resource == NULL)
+ return -EIO;
+
+
+ if (data) {
+ if ((mem = pw_mempool_find_ptr(impl->context->pool, data)) == NULL)
+ return -EINVAL;
+
+ mem_offset = SPA_PTRDIFF(data, mem->map->ptr);
+ mem_size = mem->size;
+ if (mem_size - mem_offset < size)
+ return -EINVAL;
+
+ mem_offset += mem->map->offset;
+ m = ensure_mem(impl, mem->fd, SPA_DATA_MemFd, mem->flags);
+ memid = m->id;
+ }
+ else {
+ memid = SPA_ID_INVALID;
+ mem_offset = mem_size = 0;
+ }
+
+ pw_client_node0_resource_port_set_io(this->resource,
+ this->seq,
+ direction, port_id,
+ id,
+ memid,
+ mem_offset, mem_size);
+ return SPA_RESULT_RETURN_ASYNC(this->seq++);
+}
+
+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 node *this = object;
+ struct impl *impl;
+ struct port *port;
+ uint32_t i, j;
+ struct pw_client_node0_buffer *mb;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(CHECK_PORT(this, direction, port_id), -EINVAL);
+
+ impl = this->impl;
+ spa_log_debug(this->log, "node %p: use buffers %p %u", this, buffers, n_buffers);
+
+ port = GET_PORT(this, direction, port_id);
+
+ if (!port->have_format)
+ return -EIO;
+
+ clear_buffers(this, port);
+
+ if (n_buffers > 0) {
+ mb = alloca(n_buffers * sizeof(struct pw_client_node0_buffer));
+ } else {
+ mb = NULL;
+ }
+
+ port->n_buffers = n_buffers;
+
+ if (this->resource == NULL)
+ return -EIO;
+
+ for (i = 0; i < n_buffers; i++) {
+ struct buffer *b = &port->buffers[i];
+ struct pw_memblock *mem;
+ struct mem *m;
+ size_t data_size;
+ void *baseptr;
+
+ b->outbuf = buffers[i];
+ memcpy(&b->buffer, buffers[i], sizeof(struct spa_buffer));
+ b->buffer.datas = b->datas;
+ b->buffer.metas = b->metas;
+
+ if (buffers[i]->n_metas > 0)
+ baseptr = buffers[i]->metas[0].data;
+ else if (buffers[i]->n_datas > 0)
+ baseptr = buffers[i]->datas[0].chunk;
+ else
+ return -EINVAL;
+
+ if ((mem = pw_mempool_find_ptr(impl->context->pool, baseptr)) == NULL)
+ return -EINVAL;
+
+ data_size = 0;
+ for (j = 0; j < buffers[i]->n_metas; j++) {
+ data_size += buffers[i]->metas[j].size;
+ }
+ for (j = 0; j < buffers[i]->n_datas; j++) {
+ struct spa_data *d = buffers[i]->datas;
+ data_size += sizeof(struct spa_chunk);
+ if (d->type == SPA_DATA_MemPtr)
+ data_size += d->maxsize;
+ }
+
+ m = ensure_mem(impl, mem->fd, SPA_DATA_MemFd, mem->flags);
+ b->memid = m->id;
+
+ mb[i].buffer = &b->buffer;
+ mb[i].mem_id = b->memid;
+ mb[i].offset = SPA_PTRDIFF(baseptr, SPA_PTROFF(mem->map->ptr, mem->map->offset, void));
+ mb[i].size = data_size;
+
+ for (j = 0; j < buffers[i]->n_metas; j++)
+ memcpy(&b->buffer.metas[j], &buffers[i]->metas[j], sizeof(struct spa_meta));
+ b->buffer.n_metas = j;
+
+ for (j = 0; j < buffers[i]->n_datas; j++) {
+ struct spa_data *d = &buffers[i]->datas[j];
+
+ memcpy(&b->buffer.datas[j], d, sizeof(struct spa_data));
+
+ if (d->type == SPA_DATA_DmaBuf ||
+ d->type == SPA_DATA_MemFd) {
+ m = ensure_mem(impl, d->fd, d->type, d->flags);
+ b->buffer.datas[j].data = SPA_UINT32_TO_PTR(m->id);
+ } else if (d->type == SPA_DATA_MemPtr) {
+ b->buffer.datas[j].data = SPA_INT_TO_PTR(SPA_PTRDIFF(d->data, baseptr));
+ } else {
+ b->buffer.datas[j].type = SPA_ID_INVALID;
+ b->buffer.datas[j].data = 0;
+ spa_log_error(this->log, "invalid memory type %d", d->type);
+ }
+ }
+ }
+
+ pw_client_node0_resource_port_use_buffers(this->resource,
+ this->seq,
+ direction, port_id,
+ n_buffers, mb);
+
+ return SPA_RESULT_RETURN_ASYNC(this->seq++);
+}
+
+static int
+impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id)
+{
+ struct node *this = object;
+ struct impl *impl;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(CHECK_OUT_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL);
+
+ impl = this->impl;
+
+ spa_log_trace(this->log, "reuse buffer %d", buffer_id);
+
+ pw_client_node0_transport_add_message(impl->transport, (struct pw_client_node0_message *)
+ &PW_CLIENT_NODE0_MESSAGE_PORT_REUSE_BUFFER_INIT(port_id, buffer_id));
+ do_flush(this);
+
+ return 0;
+}
+
+static int impl_node_process_input(struct spa_node *node)
+{
+ struct node *this = SPA_CONTAINER_OF(node, struct node, node);
+ struct impl *impl = this->impl;
+// bool client_reuse = impl->client_reuse;
+ uint32_t i;
+ int res;
+
+ if (impl->input_ready == 0) {
+ /* the client is not ready to receive our buffers, recycle them */
+ pw_log_trace("node not ready, recycle buffers");
+ for (i = 0; i < MAX_INPUTS; i++) {
+ struct port *p = &this->in_ports[i];
+ struct spa_io_buffers *io = p->io;
+
+ if (!p->valid || io == NULL)
+ continue;
+
+ io->status = SPA_STATUS_NEED_DATA;
+ }
+ res = SPA_STATUS_NEED_DATA;
+ }
+ else {
+ for (i = 0; i < MAX_INPUTS; i++) {
+ struct port *p = &this->in_ports[i];
+ struct spa_io_buffers *io = p->io;
+
+ if (!p->valid || io == NULL)
+ continue;
+
+ pw_log_trace("set io status to %d %d", io->status, io->buffer_id);
+ impl->transport->inputs[p->id] = *io;
+
+ /* explicitly recycle buffers when the client is not going to do it */
+// if (!client_reuse && (pp = p->peer))
+// spa_node_port_reuse_buffer(pp->node->implementation,
+// pp->port_id, io->buffer_id);
+ }
+ pw_client_node0_transport_add_message(impl->transport,
+ &PW_CLIENT_NODE0_MESSAGE_INIT(PW_CLIENT_NODE0_MESSAGE_PROCESS_INPUT));
+ do_flush(this);
+
+ impl->input_ready--;
+ res = SPA_STATUS_OK;
+ }
+ return res;
+}
+
+#if 0
+/** this is used for clients providing data to pipewire and currently
+ * not supported in the compat layer */
+static int impl_node_process_output(struct spa_node *node)
+{
+ struct node *this;
+ struct impl *impl;
+ uint32_t i;
+
+ this = SPA_CONTAINER_OF(node, struct node, node);
+ impl = this->impl;
+
+ if (impl->out_pending)
+ goto done;
+
+ impl->out_pending = true;
+
+ for (i = 0; i < MAX_OUTPUTS; i++) {
+ struct port *p = &this->out_ports[i];
+ struct spa_io_buffers *io = p->io;
+
+ if (!p->valid || io == NULL)
+ continue;
+
+ impl->transport->outputs[p->id] = *io;
+
+ pw_log_trace("%d %d -> %d %d", io->status, io->buffer_id,
+ impl->transport->outputs[p->id].status,
+ impl->transport->outputs[p->id].buffer_id);
+ }
+
+ done:
+ pw_client_node0_transport_add_message(impl->transport,
+ &PW_CLIENT_NODE0_MESSAGE_INIT(PW_CLIENT_NODE0_MESSAGE_PROCESS_OUTPUT));
+ do_flush(this);
+
+ return SPA_STATUS_OK;
+}
+#endif
+
+static int impl_node_process(void *object)
+{
+ struct node *this = object;
+ struct impl *impl = this->impl;
+ struct pw_impl_node *n = impl->this.node;
+
+ return impl_node_process_input(n->node);
+}
+
+static int handle_node_message(struct node *this, struct pw_client_node0_message *message)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, node);
+ uint32_t i;
+
+ switch (PW_CLIENT_NODE0_MESSAGE_TYPE(message)) {
+ case PW_CLIENT_NODE0_MESSAGE_HAVE_OUTPUT:
+ for (i = 0; i < MAX_OUTPUTS; i++) {
+ struct port *p = &this->out_ports[i];
+ struct spa_io_buffers *io = p->io;
+ if (!p->valid || io == NULL)
+ continue;
+ *io = impl->transport->outputs[p->id];
+ pw_log_trace("have output %d %d", io->status, io->buffer_id);
+ }
+ impl->out_pending = false;
+ spa_node_call_ready(&this->callbacks, SPA_STATUS_HAVE_DATA);
+ break;
+
+ case PW_CLIENT_NODE0_MESSAGE_NEED_INPUT:
+ for (i = 0; i < MAX_INPUTS; i++) {
+ struct port *p = &this->in_ports[i];
+ struct spa_io_buffers *io = p->io;
+ if (!p->valid || io == NULL)
+ continue;
+ pw_log_trace("need input %d %d", i, p->id);
+ *io = impl->transport->inputs[p->id];
+ pw_log_trace("need input %d %d", io->status, io->buffer_id);
+ }
+ impl->input_ready++;
+ spa_node_call_ready(&this->callbacks, SPA_STATUS_NEED_DATA);
+ break;
+
+ case PW_CLIENT_NODE0_MESSAGE_PORT_REUSE_BUFFER:
+ if (impl->client_reuse) {
+ struct pw_client_node0_message_port_reuse_buffer *p =
+ (struct pw_client_node0_message_port_reuse_buffer *) message;
+ spa_node_call_reuse_buffer(&this->callbacks, p->body.port_id.value,
+ p->body.buffer_id.value);
+ }
+ break;
+
+ default:
+ pw_log_warn("unhandled message %d", PW_CLIENT_NODE0_MESSAGE_TYPE(message));
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+static void setup_transport(struct impl *impl)
+{
+ struct node *this = &impl->node;
+ uint32_t max_inputs = 0, max_outputs = 0, n_inputs = 0, n_outputs = 0;
+ struct spa_dict_item items[1];
+
+ n_inputs = this->n_inputs;
+ max_inputs = this->info.max_input_ports == 0 ? this->n_inputs : this->info.max_input_ports;
+ n_outputs = this->n_outputs;
+ max_outputs = this->info.max_output_ports == 0 ? this->n_outputs : this->info.max_output_ports;
+
+ impl->transport = pw_client_node0_transport_new(impl->context, max_inputs, max_outputs);
+ impl->transport->area->n_input_ports = n_inputs;
+ impl->transport->area->n_output_ports = n_outputs;
+
+ if (n_inputs > 0) {
+ items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Stream/Input/Video");
+ } else {
+ items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_MEDIA_CLASS, "Stream/Output/Video");
+ }
+ pw_impl_node_update_properties(impl->this.node, &SPA_DICT_INIT(items, 1));
+}
+
+static void
+client_node0_done(void *data, int seq, int res)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+
+ if (seq == 0 && res == 0 && impl->transport == NULL)
+ setup_transport(impl);
+
+ pw_log_debug("seq:%d res:%d pending:%d", seq, res, this->init_pending);
+ spa_node_emit_result(&this->hooks, seq, res, 0, NULL);
+
+ if (this->init_pending != SPA_ID_INVALID) {
+ spa_node_emit_result(&this->hooks, this->init_pending, res, 0, NULL);
+ this->init_pending = SPA_ID_INVALID;
+ }
+}
+
+static void
+client_node0_update(void *data,
+ uint32_t change_mask,
+ uint32_t max_input_ports,
+ uint32_t max_output_ports,
+ uint32_t n_params,
+ const struct spa_pod **params)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+
+ if (change_mask & PW_CLIENT_NODE0_UPDATE_MAX_INPUTS)
+ this->info.max_input_ports = max_input_ports;
+ if (change_mask & PW_CLIENT_NODE0_UPDATE_MAX_OUTPUTS)
+ this->info.max_output_ports = max_output_ports;
+ if (change_mask & PW_CLIENT_NODE0_UPDATE_PARAMS) {
+ uint32_t i;
+ spa_log_debug(this->log, "node %p: update %d params", this, n_params);
+
+ for (i = 0; i < this->n_params; i++)
+ free(this->params[i]);
+ this->n_params = n_params;
+ if (this->n_params == 0) {
+ free(this->params);
+ this->params = NULL;
+ } else {
+ void *p;
+ p = pw_reallocarray(this->params, this->n_params, sizeof(struct spa_pod *));
+ if (p == NULL) {
+ pw_log_error("%p: can't realloc: %m", this);
+ free(this->params);
+ this->n_params = 0;
+ }
+ this->params = p;
+ }
+ for (i = 0; i < this->n_params; i++)
+ this->params[i] = params[i] ? spa_pod_copy(params[i]) : NULL;
+ }
+ if (change_mask & (PW_CLIENT_NODE0_UPDATE_MAX_INPUTS | PW_CLIENT_NODE0_UPDATE_MAX_OUTPUTS)) {
+ spa_node_emit_info(&this->hooks, &this->info);
+ }
+
+ spa_log_debug(this->log, "node %p: got node update max_in %u, max_out %u", this,
+ this->info.max_input_ports, this->info.max_output_ports);
+}
+
+static void
+client_node0_port_update(void *data,
+ 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)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+ bool remove;
+
+ spa_log_debug(this->log, "node %p: got port update", this);
+ if (!CHECK_PORT_ID(this, direction, port_id))
+ return;
+
+ remove = (change_mask == 0);
+
+ if (remove) {
+ do_uninit_port(this, direction, port_id);
+ } else {
+ do_update_port(this,
+ direction,
+ port_id,
+ change_mask,
+ n_params, params, info);
+ }
+}
+
+static void client_node0_set_active(void *data, bool active)
+{
+ struct impl *impl = data;
+ pw_impl_node_set_active(impl->this.node, active);
+}
+
+static void client_node0_event(void *data, struct spa_event *event)
+{
+ struct impl *impl = data;
+ struct node *this = &impl->node;
+
+ switch (SPA_EVENT_TYPE(event)) {
+ case SPA_NODE0_EVENT_RequestClockUpdate:
+ send_clock_update(this);
+ break;
+ default:
+ spa_node_emit_event(&this->hooks, event);
+ }
+}
+
+static const struct pw_client_node0_methods client_node0_methods = {
+ PW_VERSION_CLIENT_NODE0_METHODS,
+ .done = client_node0_done,
+ .update = client_node0_update,
+ .port_update = client_node0_port_update,
+ .set_active = client_node0_set_active,
+ .event = client_node0_event,
+};
+
+static void node_on_data_fd_events(struct spa_source *source)
+{
+ struct node *this = source->data;
+ struct impl *impl = this->impl;
+
+ if (source->rmask & (SPA_IO_ERR | SPA_IO_HUP)) {
+ spa_log_warn(this->log, "node %p: got error", this);
+ return;
+ }
+
+ if (source->rmask & SPA_IO_IN) {
+ struct pw_client_node0_message message;
+ uint64_t cmd;
+
+ if (spa_system_eventfd_read(this->data_system, this->data_source.fd, &cmd) < 0)
+ spa_log_warn(this->log, "node %p: error reading message: %s",
+ this, strerror(errno));
+
+ while (pw_client_node0_transport_next_message(impl->transport, &message) == 1) {
+ struct pw_client_node0_message *msg = alloca(SPA_POD_SIZE(&message));
+ pw_client_node0_transport_parse_message(impl->transport, msg);
+ handle_node_message(this, msg);
+ }
+ }
+}
+
+static const struct spa_node_methods impl_node = {
+ SPA_VERSION_NODE_METHODS,
+ .add_listener = impl_node_add_listener,
+ .set_callbacks = impl_node_set_callbacks,
+ .sync = impl_node_sync,
+ .enum_params = impl_node_enum_params,
+ .set_param = impl_node_set_param,
+ .set_io = impl_node_set_io,
+ .send_command = impl_node_send_command,
+ .add_port = impl_node_add_port,
+ .remove_port = impl_node_remove_port,
+ .port_enum_params = impl_node_port_enum_params,
+ .port_set_param = impl_node_port_set_param,
+ .port_use_buffers = impl_node_port_use_buffers,
+ .port_set_io = impl_node_port_set_io,
+ .port_reuse_buffer = impl_node_port_reuse_buffer,
+ .process = impl_node_process,
+};
+
+static int
+node_init(struct node *this,
+ struct spa_dict *info,
+ const struct spa_support *support,
+ uint32_t n_support)
+{
+ 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);
+
+ if (this->data_loop == NULL) {
+ spa_log_error(this->log, "a data-loop is needed");
+ return -EINVAL;
+ }
+
+ this->node.iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_Node,
+ SPA_VERSION_NODE,
+ &impl_node, this);
+ spa_hook_list_init(&this->hooks);
+
+ this->data_source.func = node_on_data_fd_events;
+ this->data_source.data = this;
+ this->data_source.fd = -1;
+ this->data_source.mask = SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP;
+ this->data_source.rmask = 0;
+
+ this->seq = 1;
+ this->init_pending = SPA_ID_INVALID;
+
+ return SPA_RESULT_RETURN_ASYNC(this->seq++);
+}
+
+static int node_clear(struct node *this)
+{
+ uint32_t i;
+
+ for (i = 0; i < MAX_INPUTS; i++) {
+ if (this->in_ports[i].valid)
+ clear_port(this, &this->in_ports[i], SPA_DIRECTION_INPUT, i);
+ }
+ for (i = 0; i < MAX_OUTPUTS; i++) {
+ if (this->out_ports[i].valid)
+ clear_port(this, &this->out_ports[i], SPA_DIRECTION_OUTPUT, i);
+ }
+
+ return 0;
+}
+
+static int do_remove_source(struct spa_loop *loop,
+ bool async,
+ uint32_t seq,
+ const void *data,
+ size_t size,
+ void *user_data)
+{
+ struct spa_source *source = user_data;
+ spa_loop_remove_source(loop, source);
+ return 0;
+}
+
+static void client_node0_resource_destroy(void *data)
+{
+ struct impl *impl = data;
+ struct pw_impl_client_node0 *this = &impl->this;
+ struct node *node = &impl->node;
+
+ pw_log_debug("client-node %p: destroy", impl);
+
+ impl->node.resource = this->resource = NULL;
+ spa_hook_remove(&impl->resource_listener);
+ spa_hook_remove(&impl->object_listener);
+
+ if (node->data_source.fd != -1) {
+ spa_loop_invoke(node->data_loop,
+ do_remove_source,
+ SPA_ID_INVALID,
+ NULL,
+ 0,
+ true,
+ &node->data_source);
+ }
+ if (this->node)
+ pw_impl_node_destroy(this->node);
+}
+
+static void node_initialized(void *data)
+{
+ struct impl *impl = data;
+ struct pw_impl_client_node0 *this = &impl->this;
+ struct pw_impl_node *node = this->node;
+ struct spa_system *data_system = impl->node.data_system;
+
+ if (this->resource == NULL)
+ return;
+
+ impl->fds[0] = spa_system_eventfd_create(data_system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);
+ impl->fds[1] = spa_system_eventfd_create(data_system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);
+ impl->node.data_source.fd = impl->fds[0];
+ impl->node.writefd = impl->fds[1];
+ impl->other_fds[0] = impl->fds[1];
+ impl->other_fds[1] = impl->fds[0];
+
+ spa_loop_add_source(impl->node.data_loop, &impl->node.data_source);
+ pw_log_debug("client-node %p: transport fd %d %d", node, impl->fds[0], impl->fds[1]);
+
+ pw_client_node0_resource_transport(this->resource,
+ pw_global_get_id(pw_impl_node_get_global(node)),
+ impl->other_fds[0],
+ impl->other_fds[1],
+ impl->transport);
+}
+
+static void node_free(void *data)
+{
+ struct impl *impl = data;
+ struct pw_impl_client_node0 *this = &impl->this;
+ struct spa_system *data_system = impl->node.data_system;
+
+ this->node = NULL;
+
+ pw_log_debug("client-node %p: free", &impl->this);
+ node_clear(&impl->node);
+
+ if (impl->transport)
+ pw_client_node0_transport_destroy(impl->transport);
+
+ spa_hook_remove(&impl->node_listener);
+
+ if (this->resource)
+ pw_resource_destroy(this->resource);
+
+ pw_array_clear(&impl->mems);
+
+ if (impl->fds[0] != -1)
+ spa_system_close(data_system, impl->fds[0]);
+ if (impl->fds[1] != -1)
+ spa_system_close(data_system, impl->fds[1]);
+ free(impl);
+}
+
+static const struct pw_impl_node_events node_events = {
+ PW_VERSION_IMPL_NODE_EVENTS,
+ .free = node_free,
+ .initialized = node_initialized,
+};
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = client_node0_resource_destroy,
+};
+
+static void convert_properties(struct pw_properties *properties)
+{
+ static const struct {
+ const char *from, *to;
+ } props[] = {
+ { "pipewire.autoconnect", PW_KEY_NODE_AUTOCONNECT, },
+ /* XXX deprecated */
+ { "pipewire.target.node", PW_KEY_NODE_TARGET, }
+ };
+
+ const char *str;
+
+ SPA_FOR_EACH_ELEMENT_VAR(props, p) {
+ if ((str = pw_properties_get(properties, p->from)) != NULL) {
+ pw_properties_set(properties, p->to, str);
+ pw_properties_set(properties, p->from, NULL);
+ }
+ }
+}
+
+/** Create a new client node
+ * \param client an owner \ref pw_client
+ * \param id an id
+ * \param name a name
+ * \param properties extra properties
+ * \return a newly allocated client node
+ *
+ * Create a new \ref pw_impl_node.
+ *
+ * \memberof pw_impl_client_node
+ */
+struct pw_impl_client_node0 *pw_impl_client_node0_new(struct pw_resource *resource,
+ struct pw_properties *properties)
+{
+ struct impl *impl;
+ struct pw_impl_client_node0 *this;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct pw_context *context = pw_impl_client_get_context(client);
+ const struct spa_support *support;
+ uint32_t n_support;
+ const char *name;
+ int res;
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL) {
+ res = -errno;
+ goto error_exit_free;
+ }
+ convert_properties(properties);
+
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", client->global->id);
+
+ impl->context = context;
+ impl->fds[0] = impl->fds[1] = -1;
+ pw_log_debug("client-node %p: new", impl);
+
+ support = pw_context_get_support(impl->context, &n_support);
+
+ node_init(&impl->node, NULL, support, n_support);
+ impl->node.impl = impl;
+
+ pw_array_init(&impl->mems, 64);
+
+ if ((name = pw_properties_get(properties, "node.name")) == NULL)
+ name = "client-node";
+ pw_properties_set(properties, PW_KEY_MEDIA_TYPE, "Video");
+
+ impl->node.resource = resource;
+ this->resource = resource;
+ this->node = pw_spa_node_new(context,
+ PW_SPA_NODE_FLAG_ASYNC,
+ &impl->node.node,
+ NULL,
+ properties, 0);
+ if (this->node == NULL) {
+ res = -errno;
+ goto error_no_node;
+ }
+
+ impl->client_reuse = pw_properties_get_bool(properties, "pipewire.client.reuse", false);
+
+ pw_resource_add_listener(this->resource,
+ &impl->resource_listener,
+ &resource_events,
+ impl);
+ pw_resource_add_object_listener(this->resource,
+ &impl->object_listener,
+ &client_node0_methods,
+ impl);
+
+
+ pw_impl_node_add_listener(this->node, &impl->node_listener, &node_events, impl);
+
+ return this;
+
+error_no_node:
+ pw_resource_destroy(this->resource);
+ node_clear(&impl->node);
+error_exit_free:
+ free(impl);
+ errno = -res;
+ return NULL;
+}
+
+/** Destroy a client node
+ * \param node the client node to destroy
+ * \memberof pw_impl_client_node
+ */
+void pw_impl_client_node0_destroy(struct pw_impl_client_node0 *node)
+{
+ pw_resource_destroy(node->resource);
+}
diff --git a/src/modules/module-client-node/v0/client-node.h b/src/modules/module-client-node/v0/client-node.h
new file mode 100644
index 0000000..04b77d4
--- /dev/null
+++ b/src/modules/module-client-node/v0/client-node.h
@@ -0,0 +1,101 @@
+/* PipeWire
+ *
+ * Copyright © 2015 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_CLIENT_NODE0_H
+#define PIPEWIRE_CLIENT_NODE0_H
+
+#include <pipewire/impl.h>
+
+#include "ext-client-node.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** The state of the clock */
+enum spa_clock0_state {
+ SPA_CLOCK0_STATE_STOPPED, /*< the clock is stopped */
+ SPA_CLOCK0_STATE_PAUSED, /*< the clock is paused */
+ SPA_CLOCK0_STATE_RUNNING, /*< the clock is running */
+};
+
+struct spa_command_node0_clock_update_body {
+ struct spa_pod_object_body body;
+#define SPA_COMMAND_NODE0_CLOCK_UPDATE_TIME (1 << 0)
+#define SPA_COMMAND_NODE0_CLOCK_UPDATE_SCALE (1 << 1)
+#define SPA_COMMAND_NODE0_CLOCK_UPDATE_STATE (1 << 2)
+#define SPA_COMMAND_NODE0_CLOCK_UPDATE_LATENCY (1 << 3)
+ struct spa_pod_int change_mask SPA_ALIGNED(8);
+ struct spa_pod_int rate SPA_ALIGNED(8);
+ struct spa_pod_long ticks SPA_ALIGNED(8);
+ struct spa_pod_long monotonic_time SPA_ALIGNED(8);
+ struct spa_pod_long offset SPA_ALIGNED(8);
+ struct spa_pod_int scale SPA_ALIGNED(8);
+ struct spa_pod_int state SPA_ALIGNED(8);
+#define SPA_COMMAND_NODE0_CLOCK_UPDATE_FLAG_LIVE (1 << 0)
+ struct spa_pod_int flags SPA_ALIGNED(8);
+ struct spa_pod_long latency SPA_ALIGNED(8);
+};
+
+struct spa_command_node0_clock_update {
+ struct spa_pod pod;
+ struct spa_command_node0_clock_update_body body;
+};
+
+#define SPA_COMMAND_NODE0_CLOCK_UPDATE_INIT(type,change_mask,rate,ticks,monotonic_time,offset,scale,state,flags,latency) \
+ SPA_COMMAND_INIT_FULL(struct spa_command_node0_clock_update, \
+ sizeof(struct spa_command_node0_clock_update_body), 0, type, \
+ SPA_POD_INIT_Int(change_mask), \
+ SPA_POD_INIT_Int(rate), \
+ SPA_POD_INIT_Long(ticks), \
+ SPA_POD_INIT_Long(monotonic_time), \
+ SPA_POD_INIT_Long(offset), \
+ SPA_POD_INIT_Int(scale), \
+ SPA_POD_INIT_Int(state), \
+ SPA_POD_INIT_Int(flags), \
+ SPA_POD_INIT_Long(latency))
+
+
+/** \class pw_impl_client_node0
+ *
+ * PipeWire client node interface
+ */
+struct pw_impl_client_node0 {
+ struct pw_impl_node *node;
+
+ struct pw_resource *resource;
+};
+
+struct pw_impl_client_node0 *
+pw_impl_client_node0_new(struct pw_resource *resource,
+ struct pw_properties *properties);
+
+void
+pw_impl_client_node0_destroy(struct pw_impl_client_node0 *node);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_CLIENT_NODE0_H */
diff --git a/src/modules/module-client-node/v0/ext-client-node.h b/src/modules/module-client-node/v0/ext-client-node.h
new file mode 100644
index 0000000..21ba597
--- /dev/null
+++ b/src/modules/module-client-node/v0/ext-client-node.h
@@ -0,0 +1,414 @@
+/* PipeWire
+ *
+ * Copyright © 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __PIPEWIRE_EXT_CLIENT_NODE0_H__
+#define __PIPEWIRE_EXT_CLIENT_NODE0_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/param/param.h>
+#include <spa/node/node.h>
+
+#include <pipewire/proxy.h>
+
+#define PW_TYPE_INTERFACE_ClientNode PW_TYPE_INFO_INTERFACE_BASE "ClientNode"
+
+#define PW_VERSION_CLIENT_NODE0 0
+
+struct pw_client_node0_message;
+
+/** Shared structure between client and server \memberof pw_client_node */
+struct pw_client_node0_area {
+ uint32_t max_input_ports; /**< max input ports of the node */
+ uint32_t n_input_ports; /**< number of input ports of the node */
+ uint32_t max_output_ports; /**< max output ports of the node */
+ uint32_t n_output_ports; /**< number of output ports of the node */
+};
+
+/** \class pw_client_node0_transport
+ *
+ * \brief Transport object
+ *
+ * The transport object contains shared data and ringbuffers to exchange
+ * events and data between the server and the client in a low-latency and
+ * lockfree way.
+ */
+struct pw_client_node0_transport {
+ struct pw_client_node0_area *area; /**< the transport area */
+ struct spa_io_buffers *inputs; /**< array of buffer input io */
+ struct spa_io_buffers *outputs; /**< array of buffer output io */
+ void *input_data; /**< input memory for ringbuffer */
+ struct spa_ringbuffer *input_buffer; /**< ringbuffer for input memory */
+ void *output_data; /**< output memory for ringbuffer */
+ struct spa_ringbuffer *output_buffer; /**< ringbuffer for output memory */
+
+ /** Destroy a transport
+ * \param trans a transport to destroy
+ * \memberof pw_client_node0_transport
+ */
+ void (*destroy) (struct pw_client_node0_transport *trans);
+
+ /** Add a message to the transport
+ * \param trans the transport to send the message on
+ * \param message the message to add
+ * \return 0 on success, < 0 on error
+ *
+ * Write \a message to the shared ringbuffer.
+ */
+ int (*add_message) (struct pw_client_node0_transport *trans, struct pw_client_node0_message *message);
+
+ /** Get next message from a transport
+ * \param trans the transport to get the message of
+ * \param[out] message the message to read
+ * \return < 0 on error, 1 when a message is available,
+ * 0 when no more messages are available.
+ *
+ * Get the skeleton next message from \a trans into \a message. This function will
+ * only read the head and object body of the message.
+ *
+ * After the complete size of the message has been calculated, you should call
+ * \ref parse_message() to read the complete message contents.
+ */
+ int (*next_message) (struct pw_client_node0_transport *trans, struct pw_client_node0_message *message);
+
+ /** Parse the complete message on transport
+ * \param trans the transport to read from
+ * \param[out] message memory that can hold the complete message
+ * \return 0 on success, < 0 on error
+ *
+ * Use this function after \ref next_message().
+ */
+ int (*parse_message) (struct pw_client_node0_transport *trans, void *message);
+};
+
+#define pw_client_node0_transport_destroy(t) ((t)->destroy((t)))
+#define pw_client_node0_transport_add_message(t,m) ((t)->add_message((t), (m)))
+#define pw_client_node0_transport_next_message(t,m) ((t)->next_message((t), (m)))
+#define pw_client_node0_transport_parse_message(t,m) ((t)->parse_message((t), (m)))
+
+enum pw_client_node0_message_type {
+ PW_CLIENT_NODE0_MESSAGE_HAVE_OUTPUT, /*< signal that the node has output */
+ PW_CLIENT_NODE0_MESSAGE_NEED_INPUT, /*< signal that the node needs input */
+ PW_CLIENT_NODE0_MESSAGE_PROCESS_INPUT, /*< instruct the node to process input */
+ PW_CLIENT_NODE0_MESSAGE_PROCESS_OUTPUT, /*< instruct the node output is processed */
+ PW_CLIENT_NODE0_MESSAGE_PORT_REUSE_BUFFER, /*< reuse a buffer */
+};
+
+struct pw_client_node0_message_body {
+ struct spa_pod_int type SPA_ALIGNED(8); /*< one of enum pw_client_node0_message_type */
+};
+
+struct pw_client_node0_message {
+ struct spa_pod_struct pod;
+ struct pw_client_node0_message_body body;
+};
+
+struct pw_client_node0_message_port_reuse_buffer_body {
+ struct spa_pod_int type SPA_ALIGNED(8); /*< PW_CLIENT_NODE0_MESSAGE_PORT_REUSE_BUFFER */
+ struct spa_pod_int port_id SPA_ALIGNED(8); /*< port id */
+ struct spa_pod_int buffer_id SPA_ALIGNED(8); /*< buffer id to reuse */
+};
+
+struct pw_client_node0_message_port_reuse_buffer {
+ struct spa_pod_struct pod;
+ struct pw_client_node0_message_port_reuse_buffer_body body;
+};
+
+#define PW_CLIENT_NODE0_MESSAGE_TYPE(message) (((struct pw_client_node0_message*)(message))->body.type.value)
+
+#define PW_CLIENT_NODE0_MESSAGE_INIT(message) ((struct pw_client_node0_message) \
+ { { { sizeof(struct pw_client_node0_message_body), SPA_TYPE_Struct } }, \
+ { SPA_POD_INIT_Int(message) } })
+
+#define PW_CLIENT_NODE0_MESSAGE_INIT_FULL(type,size,message,...) (type) \
+ { { { size, SPA_TYPE_Struct } }, \
+ { SPA_POD_INIT_Int(message), ##__VA_ARGS__ } } \
+
+#define PW_CLIENT_NODE0_MESSAGE_PORT_REUSE_BUFFER_INIT(port_id,buffer_id) \
+ PW_CLIENT_NODE0_MESSAGE_INIT_FULL(struct pw_client_node0_message_port_reuse_buffer, \
+ sizeof(struct pw_client_node0_message_port_reuse_buffer_body), \
+ PW_CLIENT_NODE0_MESSAGE_PORT_REUSE_BUFFER, \
+ SPA_POD_INIT_Int(port_id), \
+ SPA_POD_INIT_Int(buffer_id))
+
+/** information about a buffer */
+struct pw_client_node0_buffer {
+ uint32_t mem_id; /**< the memory id for the metadata */
+ uint32_t offset; /**< offset in memory */
+ uint32_t size; /**< size in memory */
+ struct spa_buffer *buffer; /**< buffer describing metadata and buffer memory */
+};
+
+#define PW_CLIENT_NODE0_METHOD_DONE 0
+#define PW_CLIENT_NODE0_METHOD_UPDATE 1
+#define PW_CLIENT_NODE0_METHOD_PORT_UPDATE 2
+#define PW_CLIENT_NODE0_METHOD_SET_ACTIVE 3
+#define PW_CLIENT_NODE0_METHOD_EVENT 4
+#define PW_CLIENT_NODE0_METHOD_DESTROY 5
+#define PW_CLIENT_NODE0_METHOD_NUM 6
+
+/** \ref pw_client_node methods */
+struct pw_client_node0_methods {
+#define PW_VERSION_CLIENT_NODE0_METHODS 0
+ uint32_t version;
+
+ /** Complete an async operation */
+ void (*done) (void *object, int seq, int res);
+
+ /**
+ * Update the node ports and properties
+ *
+ * Update the maximum number of ports and the params of the
+ * client node.
+ * \param change_mask bitfield with changed parameters
+ * \param max_input_ports new max input ports
+ * \param max_output_ports new max output ports
+ * \param params new params
+ */
+ void (*update) (void *object,
+#define PW_CLIENT_NODE0_UPDATE_MAX_INPUTS (1 << 0)
+#define PW_CLIENT_NODE0_UPDATE_MAX_OUTPUTS (1 << 1)
+#define PW_CLIENT_NODE0_UPDATE_PARAMS (1 << 2)
+ uint32_t change_mask,
+ uint32_t max_input_ports,
+ uint32_t max_output_ports,
+ uint32_t n_params,
+ const struct spa_pod **params);
+
+ /**
+ * Update a node port
+ *
+ * Update the information of one port of a node.
+ * \param direction the direction of the port
+ * \param port_id the port id to update
+ * \param change_mask a bitfield of changed items
+ * \param n_params number of port parameters
+ * \param params array of port parameters
+ * \param info port information
+ */
+ void (*port_update) (void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+#define PW_CLIENT_NODE0_PORT_UPDATE_PARAMS (1 << 0)
+#define PW_CLIENT_NODE0_PORT_UPDATE_INFO (1 << 1)
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct spa_port_info *info);
+ /**
+ * Activate or deactivate the node
+ */
+ void (*set_active) (void *object, bool active);
+ /**
+ * Send an event to the node
+ * \param event the event to send
+ */
+ void (*event) (void *object, struct spa_event *event);
+ /**
+ * Destroy the client_node
+ */
+ void (*destroy) (void *object);
+};
+
+#define PW_CLIENT_NODE0_EVENT_ADD_MEM 0
+#define PW_CLIENT_NODE0_EVENT_TRANSPORT 1
+#define PW_CLIENT_NODE0_EVENT_SET_PARAM 2
+#define PW_CLIENT_NODE0_EVENT_EVENT 3
+#define PW_CLIENT_NODE0_EVENT_COMMAND 4
+#define PW_CLIENT_NODE0_EVENT_ADD_PORT 5
+#define PW_CLIENT_NODE0_EVENT_REMOVE_PORT 6
+#define PW_CLIENT_NODE0_EVENT_PORT_SET_PARAM 7
+#define PW_CLIENT_NODE0_EVENT_PORT_USE_BUFFERS 8
+#define PW_CLIENT_NODE0_EVENT_PORT_COMMAND 9
+#define PW_CLIENT_NODE0_EVENT_PORT_SET_IO 10
+#define PW_CLIENT_NODE0_EVENT_NUM 11
+
+/** \ref pw_client_node events */
+struct pw_client_node0_events {
+#define PW_VERSION_CLIENT_NODE0_EVENTS 0
+ uint32_t version;
+ /**
+ * Memory was added to a node
+ *
+ * \param mem_id the id of the memory
+ * \param type the memory type
+ * \param memfd the fd of the memory
+ * \param flags flags for the \a memfd
+ */
+ void (*add_mem) (void *data,
+ uint32_t mem_id,
+ uint32_t type,
+ int memfd,
+ uint32_t flags);
+ /**
+ * Notify of a new transport area
+ *
+ * The transport area is used to exchange real-time commands between
+ * the client and the server.
+ *
+ * \param node_id the node id created for this client node
+ * \param readfd fd for signal data can be read
+ * \param writefd fd for signal data can be written
+ * \param transport the shared transport area
+ */
+ void (*transport) (void *data,
+ uint32_t node_id,
+ int readfd,
+ int writefd,
+ struct pw_client_node0_transport *transport);
+ /**
+ * Notify of a property change
+ *
+ * When the server configures the properties on the node
+ * this event is sent
+ *
+ * \param seq a sequence number
+ * \param id the id of the parameter
+ * \param flags parameter flags
+ * \param param the param to set
+ */
+ void (*set_param) (void *data, uint32_t seq,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+ /**
+ * Receive an event from the client node
+ * \param event the received event */
+ void (*event) (void *data, const struct spa_event *event);
+ /**
+ * Notify of a new node command
+ *
+ * \param seq a sequence number
+ * \param command the command
+ */
+ void (*command) (void *data, uint32_t seq, const struct spa_command *command);
+ /**
+ * A new port was added to the node
+ *
+ * The server can at any time add a port to the node when there
+ * are free ports available.
+ *
+ * \param seq a sequence number
+ * \param direction the direction of the port
+ * \param port_id the new port id
+ */
+ void (*add_port) (void *data,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id);
+ /**
+ * A port was removed from the node
+ *
+ * \param seq a sequence number
+ * \param direction a port direction
+ * \param port_id the remove port id
+ */
+ void (*remove_port) (void *data,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id);
+ /**
+ * A parameter was configured on the port
+ *
+ * \param seq a sequence number
+ * \param direction a port direction
+ * \param port_id the port id
+ * \param id the id of the parameter
+ * \param flags flags used when setting the param
+ * \param param the new param
+ */
+ void (*port_set_param) (void *data,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+ /**
+ * Notify the port of buffers
+ *
+ * \param seq a sequence number
+ * \param direction a port direction
+ * \param port_id the port id
+ * \param n_buffer the number of buffers
+ * \param buffers and array of buffer descriptions
+ */
+ void (*port_use_buffers) (void *data,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t n_buffers,
+ struct pw_client_node0_buffer *buffers);
+ /**
+ * Notify of a new port command
+ *
+ * \param direction a port direction
+ * \param port_id the port id
+ * \param command the command
+ */
+ void (*port_command) (void *data,
+ enum spa_direction direction,
+ uint32_t port_id,
+ const struct spa_command *command);
+
+ /**
+ * Configure the io area with \a id of \a port_id.
+ *
+ * \param seq a sequence number
+ * \param direction the direction of the port
+ * \param port_id the port id
+ * \param id the id of the io area to set
+ * \param mem_id the id of the memory to use
+ * \param offset offset of io area in memory
+ * \param size size of the io area
+ */
+ void (*port_set_io) (void *data,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t id,
+ uint32_t mem_id,
+ uint32_t offset,
+ uint32_t size);
+};
+#define pw_client_node0_resource(r,m,v,...) pw_resource_call(r, struct pw_client_node0_events, m, v, ##__VA_ARGS__)
+
+#define pw_client_node0_resource_add_mem(r,...) pw_client_node0_resource(r,add_mem,0,__VA_ARGS__)
+#define pw_client_node0_resource_transport(r,...) pw_client_node0_resource(r,transport,0,__VA_ARGS__)
+#define pw_client_node0_resource_set_param(r,...) pw_client_node0_resource(r,set_param,0,__VA_ARGS__)
+#define pw_client_node0_resource_event(r,...) pw_client_node0_resource(r,event,0,__VA_ARGS__)
+#define pw_client_node0_resource_command(r,...) pw_client_node0_resource(r,command,0,__VA_ARGS__)
+#define pw_client_node0_resource_add_port(r,...) pw_client_node0_resource(r,add_port,0,__VA_ARGS__)
+#define pw_client_node0_resource_remove_port(r,...) pw_client_node0_resource(r,remove_port,0,__VA_ARGS__)
+#define pw_client_node0_resource_port_set_param(r,...) pw_client_node0_resource(r,port_set_param,0,__VA_ARGS__)
+#define pw_client_node0_resource_port_use_buffers(r,...) pw_client_node0_resource(r,port_use_buffers,0,__VA_ARGS__)
+#define pw_client_node0_resource_port_command(r,...) pw_client_node0_resource(r,port_command,0,__VA_ARGS__)
+#define pw_client_node0_resource_port_set_io(r,...) pw_client_node0_resource(r,port_set_io,0,__VA_ARGS__)
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* __PIPEWIRE_EXT_CLIENT_NODE0_H__ */
diff --git a/src/modules/module-client-node/v0/protocol-native.c b/src/modules/module-client-node/v0/protocol-native.c
new file mode 100644
index 0000000..86d8fe9
--- /dev/null
+++ b/src/modules/module-client-node/v0/protocol-native.c
@@ -0,0 +1,534 @@
+/* PipeWire
+ *
+ * Copyright © 2017 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+
+#include <spa/pod/parser.h>
+#include <spa/pod/builder.h>
+#include <spa/utils/type-info.h>
+
+#include "pipewire/impl.h"
+
+#include "pipewire/extensions/protocol-native.h"
+
+#include "ext-client-node.h"
+
+#include "transport.h"
+
+#define PW_PROTOCOL_NATIVE_FLAG_REMAP (1<<0)
+
+extern uint32_t pw_protocol_native0_find_type(struct pw_impl_client *client, const char *type);
+extern int pw_protocol_native0_pod_to_v2(struct pw_impl_client *client, const struct spa_pod *pod,
+ struct spa_pod_builder *b);
+extern struct spa_pod * pw_protocol_native0_pod_from_v2(struct pw_impl_client *client,
+ const struct spa_pod *pod);
+extern uint32_t pw_protocol_native0_type_to_v2(struct pw_impl_client *client,
+ const struct spa_type_info *info, uint32_t type);
+
+static void
+client_node_marshal_add_mem(void *data,
+ uint32_t mem_id,
+ uint32_t type,
+ int memfd, uint32_t flags)
+{
+ struct pw_resource *resource = data;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_builder *b;
+ const char *typename;
+
+ switch (type) {
+ case SPA_DATA_MemFd:
+ typename = "Spa:Enum:DataType:Fd:MemFd";
+ break;
+ case SPA_DATA_DmaBuf:
+ typename = "Spa:Enum:DataType:Fd:DmaBuf";
+ break;
+ default:
+ case SPA_DATA_MemPtr:
+ return;
+
+ }
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_ADD_MEM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ "i", mem_id,
+ "I", pw_protocol_native0_find_type(client, typename),
+ "i", pw_protocol_native_add_resource_fd(resource, memfd),
+ "i", flags);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void client_node_marshal_transport(void *data, uint32_t node_id, int readfd, int writefd,
+ struct pw_client_node0_transport *transport)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct pw_client_node0_transport_info info;
+
+ pw_client_node0_transport_get_info(transport, &info);
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_TRANSPORT, NULL);
+
+ spa_pod_builder_add_struct(b,
+ "i", node_id,
+ "i", pw_protocol_native_add_resource_fd(resource, readfd),
+ "i", pw_protocol_native_add_resource_fd(resource, writefd),
+ "i", pw_protocol_native_add_resource_fd(resource, info.memfd),
+ "i", info.offset,
+ "i", info.size);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void
+client_node_marshal_set_param(void *data, uint32_t seq, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ "i", seq,
+ "I", id,
+ "i", flags,
+ "P", param);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void client_node_marshal_event_event(void *data, const struct spa_event *event)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_EVENT, NULL);
+
+ spa_pod_builder_add_struct(b, "P", event);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void
+client_node_marshal_command(void *data, uint32_t seq, const struct spa_command *command)
+{
+ struct pw_resource *resource = data;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_COMMAND, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b, "i", seq, NULL);
+ if (SPA_COMMAND_TYPE(command) == 0)
+ spa_pod_builder_add(b, "P", command, NULL);
+ else
+ pw_protocol_native0_pod_to_v2(client, (struct spa_pod *)command, b);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void
+client_node_marshal_add_port(void *data,
+ uint32_t seq, enum spa_direction direction, uint32_t port_id)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_ADD_PORT, NULL);
+
+ spa_pod_builder_add_struct(b,
+ "i", seq,
+ "i", direction,
+ "i", port_id);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void
+client_node_marshal_remove_port(void *data,
+ uint32_t seq, enum spa_direction direction, uint32_t port_id)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_REMOVE_PORT, NULL);
+
+ spa_pod_builder_add_struct(b,
+ "i", seq,
+ "i", direction,
+ "i", port_id);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void
+client_node_marshal_port_set_param(void *data,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t id,
+ uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ const char *typename;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_PORT_SET_PARAM, NULL);
+
+ switch (id) {
+ case SPA_PARAM_Props:
+ typename = "Spa:Enum:ParamId:Props";
+ break;
+ case SPA_PARAM_Format:
+ typename = "Spa:Enum:ParamId:Format";
+ break;
+ default:
+ return;
+ }
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ "i", seq,
+ "i", direction,
+ "i", port_id,
+ "I", pw_protocol_native0_find_type(client, typename),
+ "i", flags, NULL);
+ pw_protocol_native0_pod_to_v2(client, param, b);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void
+client_node_marshal_port_use_buffers(void *data,
+ uint32_t seq,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t n_buffers, struct pw_client_node0_buffer *buffers)
+{
+ struct pw_resource *resource = data;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, j;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_PORT_USE_BUFFERS, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ "i", seq,
+ "i", direction,
+ "i", port_id,
+ "i", n_buffers, NULL);
+
+ for (i = 0; i < n_buffers; i++) {
+ struct spa_buffer *buf = buffers[i].buffer;
+
+ spa_pod_builder_add(b,
+ "i", buffers[i].mem_id,
+ "i", buffers[i].offset,
+ "i", buffers[i].size,
+ "i", i,
+ "i", buf->n_metas, NULL);
+
+ for (j = 0; j < buf->n_metas; j++) {
+ struct spa_meta *m = &buf->metas[j];
+ spa_pod_builder_add(b,
+ "I", pw_protocol_native0_type_to_v2(client, spa_type_meta_type, m->type),
+ "i", m->size, NULL);
+ }
+ spa_pod_builder_add(b, "i", buf->n_datas, NULL);
+ for (j = 0; j < buf->n_datas; j++) {
+ struct spa_data *d = &buf->datas[j];
+ spa_pod_builder_add(b,
+ "I", pw_protocol_native0_type_to_v2(client, spa_type_data_type, d->type),
+ "i", SPA_PTR_TO_UINT32(d->data),
+ "i", d->flags,
+ "i", d->mapoffset,
+ "i", d->maxsize, NULL);
+ }
+ }
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void
+client_node_marshal_port_command(void *data,
+ uint32_t direction,
+ uint32_t port_id,
+ const struct spa_command *command)
+{
+ struct pw_resource *resource = data;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_PORT_COMMAND, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ "i", direction,
+ "i", port_id, NULL);
+ pw_protocol_native0_pod_to_v2(client, (struct spa_pod *)command, b);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void
+client_node_marshal_port_set_io(void *data,
+ uint32_t seq,
+ uint32_t direction,
+ uint32_t port_id,
+ uint32_t id,
+ uint32_t memid,
+ uint32_t offset,
+ uint32_t size)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_NODE0_EVENT_PORT_SET_IO, NULL);
+
+ spa_pod_builder_add_struct(b,
+ "i", seq,
+ "i", direction,
+ "i", port_id,
+ "I", id,
+ "i", memid,
+ "i", offset,
+ "i", size);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+
+static int client_node_demarshal_done(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t seq, res;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ "i", &seq,
+ "i", &res) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_client_node0_methods, done, 0, seq, res);
+}
+
+static int client_node_demarshal_update(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ uint32_t change_mask, max_input_ports, max_output_ports, n_params;
+ const struct spa_pod **params;
+ uint32_t i;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f) < 0 ||
+ spa_pod_parser_get(&prs,
+ "i", &change_mask,
+ "i", &max_input_ports,
+ "i", &max_output_ports,
+ "i", &n_params, NULL) < 0)
+ return -EINVAL;
+
+ params = alloca(n_params * sizeof(struct spa_pod *));
+ for (i = 0; i < n_params; i++)
+ if (spa_pod_parser_get(&prs, "O", &params[i], NULL) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_client_node0_methods, update, 0, change_mask,
+ max_input_ports,
+ max_output_ports,
+ n_params,
+ params);
+}
+
+static int client_node_demarshal_port_update(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[2];
+ uint32_t i, direction, port_id, change_mask, n_params;
+ const struct spa_pod **params = NULL;
+ struct spa_port_info info = { 0 }, *infop = NULL;
+ struct spa_dict props;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get(&prs,
+ "i", &direction,
+ "i", &port_id,
+ "i", &change_mask,
+ "i", &n_params, NULL) < 0)
+ return -EINVAL;
+
+ params = alloca(n_params * sizeof(struct spa_pod *));
+ for (i = 0; i < n_params; i++)
+ if (spa_pod_parser_get(&prs, "O", &params[i], NULL) < 0)
+ return -EINVAL;
+
+
+ if (spa_pod_parser_push_struct(&prs, &f[1]) >= 0) {
+ infop = &info;
+
+ if (spa_pod_parser_get(&prs,
+ "i", &info.flags,
+ "i", &info.rate,
+ "i", &props.n_items, NULL) < 0)
+ return -EINVAL;
+
+ if (props.n_items > 0) {
+ info.props = &props;
+
+ props.items = alloca(props.n_items * sizeof(struct spa_dict_item));
+ for (i = 0; i < props.n_items; i++) {
+ if (spa_pod_parser_get(&prs,
+ "s", &props.items[i].key,
+ "s", &props.items[i].value,
+ NULL) < 0)
+ return -EINVAL;
+ }
+ }
+ }
+
+ return pw_resource_notify(resource, struct pw_client_node0_methods, port_update, 0, direction,
+ port_id,
+ change_mask,
+ n_params,
+ params, infop);
+}
+
+static int client_node_demarshal_set_active(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ int active;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ "b", &active) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_client_node0_methods, set_active, 0, active);
+}
+
+static int client_node_demarshal_event_method(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_parser prs;
+ struct spa_event *event;
+ int res;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ "O", &event) < 0)
+ return -EINVAL;
+
+ event = (struct spa_event*)pw_protocol_native0_pod_from_v2(client, (struct spa_pod *)event);
+
+ res = pw_resource_notify(resource, struct pw_client_node0_methods, event, 0, event);
+ free(event);
+
+ return res;
+}
+
+static int client_node_demarshal_destroy(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ int res;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs, NULL) < 0)
+ return -EINVAL;
+
+ res = pw_resource_notify(resource, struct pw_client_node0_methods, destroy, 0);
+ pw_resource_destroy(resource);
+ return res;
+}
+
+static const struct pw_protocol_native_demarshal pw_protocol_native_client_node_method_demarshal[] = {
+ { &client_node_demarshal_done, 0, 0 },
+ { &client_node_demarshal_update, 0, PW_PROTOCOL_NATIVE_FLAG_REMAP },
+ { &client_node_demarshal_port_update, 0, PW_PROTOCOL_NATIVE_FLAG_REMAP },
+ { &client_node_demarshal_set_active, 0, 0 },
+ { &client_node_demarshal_event_method, 0, PW_PROTOCOL_NATIVE_FLAG_REMAP },
+ { &client_node_demarshal_destroy, 0, 0 },
+};
+
+static const struct pw_client_node0_events pw_protocol_native_client_node_event_marshal = {
+ PW_VERSION_CLIENT_NODE0_EVENTS,
+ &client_node_marshal_add_mem,
+ &client_node_marshal_transport,
+ &client_node_marshal_set_param,
+ &client_node_marshal_event_event,
+ &client_node_marshal_command,
+ &client_node_marshal_add_port,
+ &client_node_marshal_remove_port,
+ &client_node_marshal_port_set_param,
+ &client_node_marshal_port_use_buffers,
+ &client_node_marshal_port_command,
+ &client_node_marshal_port_set_io,
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_client_node_marshal = {
+ PW_TYPE_INTERFACE_ClientNode,
+ PW_VERSION_CLIENT_NODE0,
+ PW_CLIENT_NODE0_METHOD_NUM,
+ PW_CLIENT_NODE0_EVENT_NUM,
+ 0,
+ NULL,
+ .server_demarshal = &pw_protocol_native_client_node_method_demarshal,
+ .server_marshal = &pw_protocol_native_client_node_event_marshal,
+ NULL,
+};
+
+struct pw_protocol *pw_protocol_native_ext_client_node0_init(struct pw_context *context)
+{
+ struct pw_protocol *protocol;
+
+ protocol = pw_context_find_protocol(context, PW_TYPE_INFO_PROTOCOL_Native);
+
+ if (protocol == NULL)
+ return NULL;
+
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_client_node_marshal);
+
+ return protocol;
+}
diff --git a/src/modules/module-client-node/v0/transport.c b/src/modules/module-client-node/v0/transport.c
new file mode 100644
index 0000000..fa84795
--- /dev/null
+++ b/src/modules/module-client-node/v0/transport.c
@@ -0,0 +1,262 @@
+/* PipeWire
+ *
+ * Copyright © 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <errno.h>
+#include <sys/mman.h>
+
+#include <spa/utils/ringbuffer.h>
+#include <spa/node/io.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/private.h>
+
+#include "ext-client-node.h"
+
+#include "transport.h"
+
+/** \cond */
+
+#define INPUT_BUFFER_SIZE (1<<12)
+#define OUTPUT_BUFFER_SIZE (1<<12)
+
+struct transport {
+ struct pw_client_node0_transport trans;
+
+ struct pw_memblock *mem;
+ size_t offset;
+
+ struct pw_client_node0_message current;
+ uint32_t current_index;
+};
+/** \endcond */
+
+static size_t area_get_size(struct pw_client_node0_area *area)
+{
+ size_t size;
+ size = sizeof(struct pw_client_node0_area);
+ size += area->max_input_ports * sizeof(struct spa_io_buffers);
+ size += area->max_output_ports * sizeof(struct spa_io_buffers);
+ size += sizeof(struct spa_ringbuffer);
+ size += INPUT_BUFFER_SIZE;
+ size += sizeof(struct spa_ringbuffer);
+ size += OUTPUT_BUFFER_SIZE;
+ return size;
+}
+
+static void transport_setup_area(void *p, struct pw_client_node0_transport *trans)
+{
+ struct pw_client_node0_area *a;
+
+ trans->area = a = p;
+ p = SPA_PTROFF(p, sizeof(struct pw_client_node0_area), struct spa_io_buffers);
+
+ trans->inputs = p;
+ p = SPA_PTROFF(p, a->max_input_ports * sizeof(struct spa_io_buffers), void);
+
+ trans->outputs = p;
+ p = SPA_PTROFF(p, a->max_output_ports * sizeof(struct spa_io_buffers), void);
+
+ trans->input_buffer = p;
+ p = SPA_PTROFF(p, sizeof(struct spa_ringbuffer), void);
+
+ trans->input_data = p;
+ p = SPA_PTROFF(p, INPUT_BUFFER_SIZE, void);
+
+ trans->output_buffer = p;
+ p = SPA_PTROFF(p, sizeof(struct spa_ringbuffer), void);
+
+ trans->output_data = p;
+ p = SPA_PTROFF(p, OUTPUT_BUFFER_SIZE, void);
+}
+
+static void transport_reset_area(struct pw_client_node0_transport *trans)
+{
+ uint32_t i;
+ struct pw_client_node0_area *a = trans->area;
+
+ for (i = 0; i < a->max_input_ports; i++) {
+ trans->inputs[i] = SPA_IO_BUFFERS_INIT;
+ }
+ for (i = 0; i < a->max_output_ports; i++) {
+ trans->outputs[i] = SPA_IO_BUFFERS_INIT;
+ }
+ spa_ringbuffer_init(trans->input_buffer);
+ spa_ringbuffer_init(trans->output_buffer);
+}
+
+static void destroy(struct pw_client_node0_transport *trans)
+{
+ struct transport *impl = (struct transport *) trans;
+
+ pw_log_debug("transport %p: destroy", trans);
+
+ pw_memblock_free(impl->mem);
+ free(impl);
+}
+
+static int add_message(struct pw_client_node0_transport *trans, struct pw_client_node0_message *message)
+{
+ struct transport *impl = (struct transport *) trans;
+ int32_t filled, avail;
+ uint32_t size, index;
+
+ if (impl == NULL || message == NULL)
+ return -EINVAL;
+
+ filled = spa_ringbuffer_get_write_index(trans->output_buffer, &index);
+ avail = OUTPUT_BUFFER_SIZE - filled;
+ size = SPA_POD_SIZE(message);
+ if (avail < (int)size)
+ return -ENOSPC;
+
+ spa_ringbuffer_write_data(trans->output_buffer,
+ trans->output_data, OUTPUT_BUFFER_SIZE,
+ index & (OUTPUT_BUFFER_SIZE - 1), message, size);
+ spa_ringbuffer_write_update(trans->output_buffer, index + size);
+
+ return 0;
+}
+
+static int next_message(struct pw_client_node0_transport *trans, struct pw_client_node0_message *message)
+{
+ struct transport *impl = (struct transport *) trans;
+ int32_t avail;
+
+ if (impl == NULL || message == NULL)
+ return -EINVAL;
+
+ avail = spa_ringbuffer_get_read_index(trans->input_buffer, &impl->current_index);
+ if (avail < (int) sizeof(struct pw_client_node0_message))
+ return 0;
+
+ spa_ringbuffer_read_data(trans->input_buffer,
+ trans->input_data, INPUT_BUFFER_SIZE,
+ impl->current_index & (INPUT_BUFFER_SIZE - 1),
+ &impl->current, sizeof(struct pw_client_node0_message));
+
+ if (avail < (int) SPA_POD_SIZE(&impl->current))
+ return 0;
+
+ *message = impl->current;
+
+ return 1;
+}
+
+static int parse_message(struct pw_client_node0_transport *trans, void *message)
+{
+ struct transport *impl = (struct transport *) trans;
+ uint32_t size;
+
+ if (impl == NULL || message == NULL)
+ return -EINVAL;
+
+ size = SPA_POD_SIZE(&impl->current);
+
+ spa_ringbuffer_read_data(trans->input_buffer,
+ trans->input_data, INPUT_BUFFER_SIZE,
+ impl->current_index & (INPUT_BUFFER_SIZE - 1), message, size);
+ spa_ringbuffer_read_update(trans->input_buffer, impl->current_index + size);
+
+ return 0;
+}
+
+/** Create a new transport
+ * \param max_input_ports maximum number of input_ports
+ * \param max_output_ports maximum number of output_ports
+ * \return a newly allocated \ref pw_client_node0_transport
+ * \memberof pw_client_node0_transport
+ */
+struct pw_client_node0_transport *
+pw_client_node0_transport_new(struct pw_context *context,
+ uint32_t max_input_ports, uint32_t max_output_ports)
+{
+ struct transport *impl;
+ struct pw_client_node0_transport *trans;
+ struct pw_client_node0_area area = { 0 };
+
+ area.max_input_ports = max_input_ports;
+ area.n_input_ports = 0;
+ area.max_output_ports = max_output_ports;
+ area.n_output_ports = 0;
+
+ impl = calloc(1, sizeof(struct transport));
+ if (impl == NULL)
+ return NULL;
+
+ pw_log_debug("transport %p: new %d %d", impl, max_input_ports, max_output_ports);
+
+ trans = &impl->trans;
+ impl->offset = 0;
+
+ impl->mem = pw_mempool_alloc(context->pool,
+ PW_MEMBLOCK_FLAG_READWRITE |
+ PW_MEMBLOCK_FLAG_MAP |
+ PW_MEMBLOCK_FLAG_SEAL,
+ SPA_DATA_MemFd, area_get_size(&area));
+ if (impl->mem == NULL) {
+ free(impl);
+ return NULL;
+ }
+
+ memcpy(impl->mem->map->ptr, &area, sizeof(struct pw_client_node0_area));
+ transport_setup_area(impl->mem->map->ptr, trans);
+ transport_reset_area(trans);
+
+ trans->destroy = destroy;
+ trans->add_message = add_message;
+ trans->next_message = next_message;
+ trans->parse_message = parse_message;
+
+ return trans;
+}
+
+struct pw_client_node0_transport *
+pw_client_node0_transport_new_from_info(struct pw_client_node0_transport_info *info)
+{
+ errno = ENOTSUP;
+ return NULL;
+}
+
+/** Get transport info
+ * \param trans the transport to get info of
+ * \param[out] info transport info
+ * \return 0 on success
+ *
+ * Fill \a info with the transport info of \a trans. This information can be
+ * passed to the client to set up the shared transport.
+ *
+ * \memberof pw_client_node0_transport
+ */
+int pw_client_node0_transport_get_info(struct pw_client_node0_transport *trans,
+ struct pw_client_node0_transport_info *info)
+{
+ struct transport *impl = (struct transport *) trans;
+
+ info->memfd = impl->mem->fd;
+ info->offset = impl->offset;
+ info->size = impl->mem->size;
+
+ return 0;
+}
diff --git a/src/modules/module-client-node/v0/transport.h b/src/modules/module-client-node/v0/transport.h
new file mode 100644
index 0000000..3abcd6d
--- /dev/null
+++ b/src/modules/module-client-node/v0/transport.h
@@ -0,0 +1,59 @@
+/* PipeWire
+ *
+ * Copyright © 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef __PIPEWIRE_CLIENT_NODE0_TRANSPORT_H__
+#define __PIPEWIRE_CLIENT_NODE0_TRANSPORT_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <string.h>
+
+#include <spa/utils/defs.h>
+
+#include <pipewire/mem.h>
+
+/** information about the transport region \memberof pw_client_node */
+struct pw_client_node0_transport_info {
+ int memfd; /**< the memfd of the transport area */
+ uint32_t offset; /**< offset to map \a memfd at */
+ uint32_t size; /**< size of memfd mapping */
+};
+
+struct pw_client_node0_transport *
+pw_client_node0_transport_new(struct pw_context *context, uint32_t max_input_ports, uint32_t max_output_ports);
+
+struct pw_client_node0_transport *
+pw_client_node0_transport_new_from_info(struct pw_client_node0_transport_info *info);
+
+int
+pw_client_node0_transport_get_info(struct pw_client_node0_transport *trans,
+ struct pw_client_node0_transport_info *info);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* __PIPEWIRE_CLIENT_NODE0_TRANSPORT_H__ */
diff --git a/src/modules/module-combine-stream.c b/src/modules/module-combine-stream.c
new file mode 100644
index 0000000..88b5644
--- /dev/null
+++ b/src/modules/module-combine-stream.c
@@ -0,0 +1,1063 @@
+/* PipeWire
+ *
+ * Copyright © 2023 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.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/utils/ringbuffer.h>
+#include <spa/debug/types.h>
+#include <spa/pod/builder.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/audio/raw.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/i18n.h>
+
+/** \page page_module_combine_stream PipeWire Module: Combine Stream
+ *
+ * The combine stream can make:
+ *
+ * - a new virtual sink that forwards audio to other sinks
+ * - a new virtual source that combines audio from other sources
+ *
+ * ## Module Options
+ *
+ * - `node.name`: a unique name for the stream
+ * - `node.description`: a human readable name for the stream
+ * - `combine.mode` = capture | playback | sink | source, default sink
+ * - `combine.props = {}`: properties to be passed to the sink/source
+ * - `stream.props = {}`: properties to be passed to the streams
+ *
+ * ## General options
+ *
+ * Options with well-known behavior.
+ *
+ * - \ref PW_KEY_REMOTE_NAME
+ * - \ref PW_KEY_AUDIO_CHANNELS
+ * - \ref SPA_KEY_AUDIO_POSITION
+ * - \ref PW_KEY_MEDIA_NAME
+ * - \ref PW_KEY_NODE_LATENCY
+ * - \ref PW_KEY_NODE_NAME
+ * - \ref PW_KEY_NODE_DESCRIPTION
+ * - \ref PW_KEY_NODE_GROUP
+ * - \ref PW_KEY_NODE_VIRTUAL
+ * - \ref PW_KEY_MEDIA_CLASS
+ *
+ * ## Stream options
+ *
+ * - `audio.position`: Set the stream channel map. By default this is the same channel
+ * map as the combine stream.
+ * - `combine.audio.position`: map the combine audio positions to the stream positions.
+ * combine input channels are mapped one-by-one to stream output channels.
+ *
+ * ## Example configuration
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-combine-stream
+ * args = {
+ * combine.mode = sink
+ * node.name = "combine_sink"
+ * node.description = "My Combine Sink"
+ * combine.props = {
+ * audio.position = [ FL FR ]
+ * }
+ * stream.props = {
+ * }
+ * stream.rules = [
+ * {
+ * matches = [
+ * # any of the items in matches needs to match, if one does,
+ * # actions are emited.
+ * {
+ * # all keys must match the value. ~ in value starts regex.
+ * #node.name = "~alsa_input.*"
+ * media.class = "Audio/Sink"
+ * }
+ * ]
+ * actions = {
+ * create-stream = {
+ * #combine.audio.position = [ FL FR ]
+ * #audio.position = [ FL FR ]
+ * }
+ * }
+ * }
+ * ]
+ * }
+ * }
+ * ]
+ *\endcode
+ *
+ * Below is an example configuration that makes a 5.1 virtual audio sink
+ * from 3 separate stereo sinks.
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-combine-stream
+ * args = {
+ * combine.mode = sink
+ * node.name = "combine_sink_5_1"
+ * node.description = "My 5.1 Combine Sink"
+ * combine.props = {
+ * audio.position = [ FL FR FC LFE SL SR ]
+ * }
+ * stream.props = {
+ * stream.dont-remix = true # link matching channels without remixing
+ * }
+ * stream.rules = [
+ * { matches = [
+ * { media.class = "Audio/Sink"
+ * node.name = "alsa_output.usb-Topping_E30-00.analog-stereo"
+ * } ]
+ * actions = { create-stream = {
+ * combine.audio.position = [ FL FR ]
+ * audio.position = [ FL FR ]
+ * } } }
+ * { matches = [
+ * { media.class = "Audio/Sink"
+ * node.name = "alsa_output.usb-BEHRINGER_UMC404HD_192k-00.pro-output-0"
+ * } ]
+ * actions = { create-stream = {
+ * combine.audio.position = [ FC LFE ]
+ * audio.position = [ AUX0 AUX1 ]
+ * } } }
+ * { matches = [
+ * { media.class = "Audio/Sink"
+ * node.name = "alsa_output.pci-0000_00_1b.0.analog-stereo"
+ * } ]
+ * actions = { create-stream = {
+ * combine.audio.position = [ SL SR ]
+ * audio.position = [ FL FR ]
+ * } } }
+ * ]
+ * }
+ * }
+ * ]
+ *\endcode
+ *
+ * Below is an example configuration that makes a 4.0 virtual audio source
+ * from 2 separate stereo sources.
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-combine-stream
+ * args = {
+ * combine.mode = source
+ * node.name = "combine_source_4_0"
+ * node.description = "My 4.0 Combine Source"
+ * combine.props = {
+ * audio.position = [ FL FR SL SR ]
+ * }
+ * stream.props = {
+ * stream.dont-remix = true
+ * }
+ * stream.rules = [
+ * { matches = [
+ * { media.class = "Audio/Source"
+ * node.name = "alsa_input.usb-046d_HD_Pro_Webcam_C920_09D53E1F-02.analog-stereo"
+ * } ]
+ * actions = { create-stream = {
+ * audio.position = [ FL FR ]
+ * combine.audio.position = [ FL FR ]
+ * } } }
+ * { matches = [
+ * { media.class = "Audio/Source"
+ * node.name = "alsa_input.usb-046d_0821_9534DE90-00.analog-stereo"
+ * } ]
+ * actions = { create-stream = {
+ * audio.position = [ FL FR ]
+ * combine.audio.position = [ SL SR ]
+ * } } }
+ * ]
+ * }
+ * }
+ * ]
+ *\endcode
+ */
+
+#define NAME "combine-stream"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define DEFAULT_CHANNELS 2
+#define DEFAULT_POSITION "[ FL FR ]"
+
+#define MODULE_USAGE "[ node.latency=<latency as fraction> ] " \
+ "[ combine.mode=<mode of stream, playback|capture|sink|source>, default:sink ] " \
+ "[ node.name=<name of the stream> ] " \
+ "[ node.description=<description of the stream> ] " \
+ "[ audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS) "> ] " \
+ "[ audio.position=<channel map, default:"DEFAULT_POSITION"> ] " \
+ "[ combine.props=<properties> ] " \
+ "[ stream.props=<properties> ] " \
+ "[ stream.rules=<properties> ] "
+
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Combine multiple streams into a single stream" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct impl {
+ struct pw_context *context;
+ struct pw_data_loop *data_loop;
+
+ struct pw_properties *props;
+
+#define MODE_SINK 0
+#define MODE_SOURCE 1
+#define MODE_CAPTURE 2
+#define MODE_PLAYBACK 3
+ uint32_t mode;
+ struct pw_impl_module *module;
+
+ struct spa_hook module_listener;
+
+ struct pw_core *core;
+ struct spa_hook core_proxy_listener;
+ struct spa_hook core_listener;
+
+ struct pw_registry *registry;
+ struct spa_hook registry_listener;
+
+ struct pw_properties *combine_props;
+ struct pw_stream *combine;
+ struct spa_hook combine_listener;
+ struct pw_stream_events combine_events;
+ uint32_t combine_id;
+
+ struct pw_properties *stream_props;
+
+ struct spa_audio_info_raw info;
+
+ unsigned int do_disconnect:1;
+
+ struct spa_list streams;
+ uint32_t n_streams;
+};
+
+struct stream {
+ uint32_t id;
+
+ struct impl *impl;
+
+ struct spa_list link;
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+ struct pw_stream_events stream_events;
+
+ struct spa_audio_info_raw info;
+ uint32_t remap[SPA_AUDIO_MAX_CHANNELS];
+
+ unsigned int ready:1;
+ unsigned int added: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));
+}
+
+static struct stream *find_stream(struct impl *impl, uint32_t id)
+{
+ struct stream *s;
+ spa_list_for_each(s, &impl->streams, link)
+ if (s->id == id)
+ return s;
+ return NULL;
+}
+
+static int do_add_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;
+ if (!s->added) {
+ spa_list_append(&impl->streams, &s->link);
+ impl->n_streams++;
+ s->added = true;
+ }
+ return 0;
+}
+
+static int do_remove_stream(struct spa_loop *loop, bool async, uint32_t seq,
+ const void *data, size_t size, void *user_data)
+{
+ struct stream *s = user_data;
+ if (s->added) {
+ spa_list_remove(&s->link);
+ s->impl->n_streams--;
+ s->added = false;
+ }
+ return 0;
+}
+
+static void destroy_stream(struct stream *s)
+{
+ pw_log_debug("destroy stream %d", s->id);
+
+ pw_data_loop_invoke(s->impl->data_loop, do_remove_stream, 0, NULL, 0, true, s);
+
+ if (s->stream) {
+ spa_hook_remove(&s->stream_listener);
+ pw_stream_destroy(s->stream);
+ }
+ free(s);
+}
+
+static void stream_destroy(void *d)
+{
+ struct stream *s = d;
+ spa_hook_remove(&s->stream_listener);
+ s->stream = NULL;
+ destroy_stream(s);
+}
+
+static void stream_input_process(void *d)
+{
+ struct stream *s = d, *t;
+ struct impl *impl = s->impl;
+ bool ready = true;
+
+ s->ready = true;
+ pw_log_debug("stream ready %p", s);
+ spa_list_for_each(t, &impl->streams, link) {
+ if (!t->ready) {
+ ready = false;
+ break;
+ }
+ }
+ if (ready) {
+ pw_log_debug("do trigger");
+ pw_stream_trigger_process(impl->combine);
+ }
+}
+
+static void stream_state_changed(void *d, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct stream *s = d;
+ switch (state) {
+ case PW_STREAM_STATE_ERROR:
+ case PW_STREAM_STATE_UNCONNECTED:
+ stream_destroy(s);
+ break;
+ default:
+ break;
+ }
+}
+
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = stream_destroy,
+ .state_changed = stream_state_changed,
+};
+
+struct stream_info {
+ struct impl *impl;
+ uint32_t id;
+ const struct spa_dict *props;
+ struct pw_properties *stream_props;
+};
+
+static int create_stream(struct stream_info *info)
+{
+ struct impl *impl = info->impl;
+ int res;
+ uint32_t n_params, i, j;
+ const struct spa_pod *params[1];
+ const char *str, *node_name;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b;
+ struct spa_audio_info_raw remap_info, tmp_info;
+ struct stream *s;
+ enum pw_stream_flags flags;
+ enum pw_direction direction;
+
+ node_name = spa_dict_lookup(info->props, "node.name");
+ if (node_name == NULL)
+ node_name = spa_dict_lookup(info->props, "object.serial");
+ if (node_name == NULL)
+ return -EIO;
+
+ pw_log_info("create stream for %d %s", info->id, node_name);
+
+ s = calloc(1, sizeof(*s));
+ if (s == NULL)
+ goto error_errno;
+
+ s->id = info->id;
+ s->impl = impl;
+
+ s->info = impl->info;
+ if ((str = pw_properties_get(info->stream_props, SPA_KEY_AUDIO_POSITION)) != NULL)
+ parse_position(&s->info, str, strlen(str));
+ 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));
+ if (remap_info.channels == 0)
+ remap_info = s->info;
+
+ tmp_info = impl->info;
+ for (i = 0; i < remap_info.channels; i++) {
+ s->remap[i] = i;
+ for (j = 0; j < tmp_info.channels; j++) {
+ if (tmp_info.position[j] == remap_info.position[i]) {
+ s->remap[i] = j;
+ break;
+ }
+ }
+ pw_log_info("remap %d -> %d", i, s->remap[i]);
+ }
+
+ str = pw_properties_get(impl->props, PW_KEY_NODE_DESCRIPTION);
+ if (str == NULL)
+ str = pw_properties_get(impl->props, PW_KEY_NODE_NAME);
+ if (str == NULL)
+ str = node_name;
+
+ 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);
+ 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);
+
+ str = pw_properties_get(impl->props, PW_KEY_NODE_NAME);
+ if (str == NULL)
+ str = "combine_stream";
+
+ 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);
+ if (pw_properties_get(info->stream_props, PW_KEY_TARGET_OBJECT) == NULL)
+ pw_properties_set(info->stream_props, PW_KEY_TARGET_OBJECT, node_name);
+
+ s->stream = pw_stream_new(impl->core, "Combine stream", info->stream_props);
+ info->stream_props = NULL;
+ 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;
+
+ 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);
+
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_format_audio_raw_build(&b,
+ SPA_PARAM_EnumFormat, &s->info);
+
+ if ((res = pw_stream_connect(s->stream,
+ direction, PW_ID_ANY, flags, params, n_params)) < 0)
+ goto error;
+
+ pw_data_loop_invoke(impl->data_loop, do_add_stream, 0, NULL, 0, true, s);
+ return 0;
+
+error_errno:
+ res = -errno;
+error:
+ if (s)
+ destroy_stream(s);
+ return res;
+}
+
+static int rule_matched(void *data, const char *location, const char *action,
+ const char *str, size_t len)
+{
+ struct stream_info *i = data;
+ struct impl *impl = i->impl;
+ int res = 0;
+
+ if (spa_streq(action, "create-stream")) {
+ i->stream_props = pw_properties_copy(impl->stream_props);
+
+ pw_properties_update_string(i->stream_props, str, len);
+
+ res = create_stream(i);
+
+ pw_properties_free(i->stream_props);
+ }
+
+ return res;
+}
+
+static void registry_event_global(void *data, uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ struct impl *impl = data;
+ const char *str;
+ struct stream_info info;
+
+ if (!spa_streq(type, PW_TYPE_INTERFACE_Node) || props == NULL)
+ return;
+
+ if (id == impl->combine_id)
+ return;
+
+ spa_zero(info);
+ info.impl = impl;
+ info.id = id;
+ info.props = props;
+
+ str = pw_properties_get(impl->props, "stream.rules");
+ if (str == NULL) {
+ if (impl->mode == MODE_CAPTURE || impl->mode == MODE_SINK)
+ str = "[ { matches = [ { media.class = \"Audio/Sink\" } ] "
+ " actions = { create-stream = {} } } ]";
+ else if (impl->mode == MODE_PLAYBACK || impl->mode == MODE_SOURCE)
+ str = "[ { matches = [ { media.class = \"Audio/Source\" } ] "
+ " actions = { create-stream = {} } } ]";
+ }
+ pw_conf_match_rules(str, strlen(str), NAME, props, rule_matched, &info);
+}
+
+static void registry_event_global_remove(void *data, uint32_t id)
+{
+ struct impl *impl = data;
+ struct stream *s;
+
+ s = find_stream(impl, id);
+ if (s == NULL)
+ return;
+
+ destroy_stream(s);
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+ .global_remove = registry_event_global_remove,
+};
+
+static void combine_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->combine_listener);
+ impl->combine = NULL;
+}
+
+static void combine_state_changed(void *d, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct impl *impl = d;
+ switch (state) {
+ case PW_STREAM_STATE_ERROR:
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_impl_module_schedule_destroy(impl->module);
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ impl->combine_id = pw_stream_get_node_id(impl->combine);
+ pw_log_info("got combine id %d", impl->combine_id);
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ break;
+ default:
+ break;
+ }
+}
+
+static void combine_input_process(void *d)
+{
+ struct impl *impl = d;
+ struct pw_buffer *in, *out;
+ struct stream *s;
+
+ if ((in = pw_stream_dequeue_buffer(impl->combine)) == NULL) {
+ pw_log_debug("out of buffers: %m");
+ return;
+ }
+
+ spa_list_for_each(s, &impl->streams, link) {
+ uint32_t j;
+
+ if (s->stream == NULL)
+ continue;
+
+ if ((out = pw_stream_dequeue_buffer(s->stream)) == NULL) {
+ pw_log_warn("out of playback buffers: %m");
+ goto do_trigger;
+ }
+
+ for (j = 0; j < out->buffer->n_datas; j++) {
+ struct spa_data *ds, *dd;
+ uint32_t outsize = 0, remap;
+ int32_t stride = 0;
+
+ dd = &out->buffer->datas[j];
+
+ remap = s->remap[j];
+ if (remap < in->buffer->n_datas) {
+ uint32_t offs, size;
+
+ ds = &in->buffer->datas[remap];
+
+ offs = SPA_MIN(ds->chunk->offset, ds->maxsize);
+ size = SPA_MIN(ds->chunk->size, ds->maxsize - offs);
+
+ memcpy(dd->data,
+ SPA_PTROFF(ds->data, offs, void), size);
+
+ outsize = SPA_MAX(outsize, size);
+ stride = SPA_MAX(stride, ds->chunk->stride);
+ } else {
+ memset(dd->data, 0, outsize);
+ }
+ dd->chunk->offset = 0;
+ dd->chunk->size = outsize;
+ dd->chunk->stride = stride;
+ }
+ pw_stream_queue_buffer(s->stream, out);
+do_trigger:
+ pw_stream_trigger_process(s->stream);
+ }
+ pw_stream_queue_buffer(impl->combine, in);
+}
+
+static void combine_output_process(void *d)
+{
+ struct impl *impl = d;
+ struct pw_buffer *in, *out;
+ struct stream *s;
+
+ if ((out = pw_stream_dequeue_buffer(impl->combine)) == NULL) {
+ pw_log_debug("out of buffers: %m");
+ return;
+ }
+
+ spa_list_for_each(s, &impl->streams, link) {
+ uint32_t j;
+
+ if (s->stream == NULL)
+ continue;
+
+ if ((in = pw_stream_dequeue_buffer(s->stream)) == NULL) {
+ pw_log_warn("%p: out of capture buffers: %m", s);
+ continue;
+ }
+ s->ready = false;
+
+ for (j = 0; j < in->buffer->n_datas; j++) {
+ struct spa_data *ds, *dd;
+ uint32_t outsize = 0, remap;
+ int32_t stride = 0;
+
+ 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;
+
+ dd = &out->buffer->datas[remap];
+
+ offs = SPA_MIN(ds->chunk->offset, ds->maxsize);
+ size = SPA_MIN(ds->chunk->size, ds->maxsize - offs);
+ size = SPA_MIN(size, dd->maxsize);
+
+ memcpy(dd->data,
+ SPA_PTROFF(ds->data, offs, void), size);
+
+ outsize = SPA_MAX(outsize, size);
+ stride = SPA_MAX(stride, ds->chunk->stride);
+
+ dd->chunk->offset = 0;
+ dd->chunk->size = outsize;
+ dd->chunk->stride = stride;
+ }
+ }
+ pw_stream_queue_buffer(s->stream, in);
+ }
+ pw_stream_queue_buffer(impl->combine, out);
+}
+
+static const struct pw_stream_events combine_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = combine_destroy,
+ .state_changed = combine_state_changed,
+};
+
+static int create_combine(struct impl *impl)
+{
+ int res;
+ uint32_t n_params;
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b;
+ enum pw_direction direction;
+ enum pw_stream_flags flags;
+
+ impl->combine = pw_stream_new(impl->core, "Combine stream", impl->combine_props);
+ impl->combine_props = NULL;
+
+ if (impl->combine == NULL)
+ return -errno;
+
+ flags = PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS;
+
+ impl->combine_events = combine_events;
+
+ if (impl->mode == MODE_SINK || impl->mode == MODE_CAPTURE) {
+ direction = PW_DIRECTION_INPUT;
+ impl->combine_events.process = combine_input_process;
+ } else {
+ direction = PW_DIRECTION_OUTPUT;
+ impl->combine_events.process = combine_output_process;
+ flags |= PW_STREAM_FLAG_TRIGGER;
+ }
+
+ pw_stream_add_listener(impl->combine,
+ &impl->combine_listener,
+ &impl->combine_events, impl);
+
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_format_audio_raw_build(&b,
+ SPA_PARAM_EnumFormat, &impl->info);
+
+ if ((res = pw_stream_connect(impl->combine,
+ direction, PW_ID_ANY, flags, params, n_params)) < 0)
+ return res;
+
+ return 0;
+}
+
+static void core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct impl *impl = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = core_error,
+};
+
+static void core_removed(void *d)
+{
+ struct impl *impl = d;
+ if (impl->core) {
+ spa_hook_remove(&impl->core_listener);
+ impl->core = NULL;
+ }
+ if (impl->registry) {
+ spa_hook_remove(&impl->registry_listener);
+ pw_proxy_destroy((struct pw_proxy*)impl->registry);
+ impl->registry = NULL;
+ }
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ .removed = core_removed,
+};
+
+static void impl_destroy(struct impl *impl)
+{
+ struct stream *s;
+
+ spa_list_consume(s, &impl->streams, link)
+ destroy_stream(s);
+
+ if (impl->combine)
+ pw_stream_destroy(impl->combine);
+
+ if (impl->registry) {
+ spa_hook_remove(&impl->registry_listener);
+ pw_proxy_destroy((struct pw_proxy*)impl->registry);
+ impl->registry = NULL;
+ }
+ if (impl->core) {
+ spa_hook_remove(&impl->core_listener);
+ if (impl->do_disconnect)
+ pw_core_disconnect(impl->core);
+ impl->core = NULL;
+ }
+
+ pw_properties_free(impl->stream_props);
+ pw_properties_free(impl->combine_props);
+ pw_properties_free(impl->props);
+
+ free(impl);
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->module_listener);
+ impl_destroy(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+static void copy_props(const struct pw_properties *props, struct pw_properties *target,
+ const char *key)
+{
+ const char *str;
+ if ((str = pw_properties_get(props, key)) != NULL) {
+ if (pw_properties_get(target, key) == NULL)
+ pw_properties_set(target, key, str);
+ }
+}
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props = NULL;
+ uint32_t id = pw_global_get_id(pw_impl_module_get_global(module));
+ uint32_t pid = getpid();
+ struct impl *impl;
+ const char *str, *prefix;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+ impl->data_loop = pw_context_get_data_loop(context);
+
+ spa_list_init(&impl->streams);
+
+ if (args == NULL)
+ args = "";
+
+ props = pw_properties_new_string(args);
+ if (props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+ impl->props = props;
+
+ if ((str = pw_properties_get(props, "combine.mode")) == NULL)
+ str = "sink";
+
+ if (spa_streq(str, "sink")) {
+ impl->mode = MODE_SINK;
+ prefix = "sink";
+ } else if (spa_streq(str, "capture")) {
+ impl->mode = MODE_CAPTURE;
+ prefix = "capture";
+ } else if (spa_streq(str, "source")) {
+ impl->mode = MODE_SOURCE;
+ prefix = "source";
+ } else if (spa_streq(str, "playback")) {
+ impl->mode = MODE_PLAYBACK;
+ prefix = "playback";
+ } else {
+ pw_log_warn("unknown combine.mode '%s', using 'sink'", str);
+ impl->mode = MODE_SINK;
+ prefix = "sink";
+ }
+
+ impl->combine_props = pw_properties_new(NULL, NULL);
+ impl->stream_props = pw_properties_new(NULL, NULL);
+ if (impl->combine_props == NULL || impl->stream_props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+
+ impl->module = module;
+ impl->context = context;
+
+ if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_GROUP, "combine-%s-%u-%u",
+ prefix, pid, id);
+ if (pw_properties_get(props, PW_KEY_NODE_LINK_GROUP) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_LINK_GROUP, "combine-%s-%u-%u",
+ prefix, pid, id);
+ if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
+ if (pw_properties_get(props, "resample.prefill") == NULL)
+ pw_properties_set(props, "resample.prefill", "true");
+
+ if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL) {
+ if (impl->mode == MODE_SINK)
+ pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
+ else if (impl->mode == MODE_SOURCE)
+ pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Source");
+ }
+
+ if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_NAME, "combine-%s-%u-%u",
+ prefix, pid, id);
+ if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION,
+ "Combine %s", prefix);
+
+ if ((str = pw_properties_get(props, "combine.props")) != NULL)
+ pw_properties_update_string(impl->combine_props, str, strlen(str));
+ if ((str = pw_properties_get(props, "stream.props")) != NULL)
+ pw_properties_update_string(impl->stream_props, str, strlen(str));
+
+ copy_props(props, impl->combine_props, PW_KEY_AUDIO_CHANNELS);
+ copy_props(props, impl->combine_props, SPA_KEY_AUDIO_POSITION);
+ copy_props(props, impl->combine_props, PW_KEY_NODE_NAME);
+ copy_props(props, impl->combine_props, PW_KEY_NODE_DESCRIPTION);
+ copy_props(props, impl->combine_props, PW_KEY_NODE_GROUP);
+ copy_props(props, impl->combine_props, PW_KEY_NODE_LINK_GROUP);
+ copy_props(props, impl->combine_props, PW_KEY_NODE_LATENCY);
+ copy_props(props, impl->combine_props, PW_KEY_NODE_VIRTUAL);
+ copy_props(props, impl->combine_props, PW_KEY_MEDIA_CLASS);
+ copy_props(props, impl->combine_props, "resample.prefill");
+
+ parse_audio_info(impl->combine_props, &impl->info);
+
+ copy_props(props, impl->stream_props, PW_KEY_NODE_GROUP);
+ copy_props(props, impl->stream_props, PW_KEY_NODE_VIRTUAL);
+ copy_props(props, impl->stream_props, PW_KEY_NODE_LINK_GROUP);
+ copy_props(props, impl->stream_props, "resample.prefill");
+
+ if (pw_properties_get(impl->stream_props, PW_KEY_MEDIA_ROLE) == NULL)
+ pw_properties_set(props, PW_KEY_MEDIA_ROLE, "filter");
+ if (pw_properties_get(impl->stream_props, PW_KEY_NODE_PASSIVE) == NULL)
+ pw_properties_set(impl->stream_props, PW_KEY_NODE_PASSIVE, "true");
+ if (pw_properties_get(impl->stream_props, PW_KEY_NODE_DONT_RECONNECT) == NULL)
+ pw_properties_set(impl->stream_props, PW_KEY_NODE_DONT_RECONNECT, "true");
+
+ 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);
+ impl->core = pw_context_connect(impl->context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ impl->do_disconnect = true;
+ }
+ if (impl->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto error;
+ }
+
+ pw_proxy_add_listener((struct pw_proxy*)impl->core,
+ &impl->core_proxy_listener,
+ &core_proxy_events, impl);
+ pw_core_add_listener(impl->core,
+ &impl->core_listener,
+ &core_events, impl);
+
+ if ((res = create_combine(impl)) < 0)
+ goto error;
+
+ impl->registry = pw_core_get_registry(impl->core, PW_VERSION_REGISTRY, 0);
+ pw_registry_add_listener(impl->registry, &impl->registry_listener,
+ &registry_events, impl);
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+
+error:
+ impl_destroy(impl);
+ return res;
+}
diff --git a/src/modules/module-echo-cancel.c b/src/modules/module-echo-cancel.c
new file mode 100644
index 0000000..34c16c7
--- /dev/null
+++ b/src/modules/module-echo-cancel.c
@@ -0,0 +1,1350 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ * © 2021 Arun Raghavan <arun@asymptotic.io>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <math.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <spa/debug/types.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/audio/raw.h>
+#include <spa/param/latency-utils.h>
+#include <spa/pod/builder.h>
+#include <spa/pod/dynamic.h>
+#include <spa/support/plugin.h>
+#include <spa/utils/json.h>
+#include <spa/utils/names.h>
+#include <spa/utils/result.h>
+#include <spa/utils/ringbuffer.h>
+#include <spa/utils/string.h>
+#include <spa/support/plugin-loader.h>
+#include <spa/interfaces/audio/aec.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/pipewire.h>
+
+#include <pipewire/extensions/profiler.h>
+
+/** \page page_module_echo_cancel PipeWire Module: Echo Cancel
+ *
+ * The `echo-cancel` module performs echo cancellation. The module creates
+ * virtual `echo-cancel-capture` source and `echo-cancel-playback` sink
+ * nodes and the associated streams.
+ *
+ * ## Module Options
+ *
+ * Options specific to the behavior of this module
+ *
+ * - `capture.props = {}`: properties to be passed to the capture stream
+ * - `source.props = {}`: properties to be passed to the source stream
+ * - `sink.props = {}`: properties to be passed to the sink stream
+ * - `playback.props = {}`: properties to be passed to the playback stream
+ * - `library.name = <str>`: the echo cancellation library Currently supported:
+ * `aec/libspa-aec-webrtc`. Leave unset to use the default method (`aec/libspa-aec-webrtc`).
+ * - `aec.args = <str>`: arguments to pass to the echo cancellation method
+ * - `monitor.mode`: Instead of making a sink, make a stream that captures from
+ * the monitor ports of the default sink.
+ *
+ * ## General options
+ *
+ * Options with well-known behavior:
+ *
+ * - \ref PW_KEY_AUDIO_RATE
+ * - \ref PW_KEY_AUDIO_CHANNELS
+ * - \ref SPA_KEY_AUDIO_POSITION
+ * - \ref PW_KEY_MEDIA_CLASS
+ * - \ref PW_KEY_NODE_LATENCY
+ * - \ref PW_KEY_NODE_NAME
+ * - \ref PW_KEY_NODE_DESCRIPTION
+ * - \ref PW_KEY_NODE_GROUP
+ * - \ref PW_KEY_NODE_LINK_GROUP
+ * - \ref PW_KEY_NODE_VIRTUAL
+ * - \ref PW_KEY_NODE_LATENCY
+ * - \ref PW_KEY_REMOTE_NAME
+ *
+ * ## Example configuration
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-echo-cancel
+ * args = {
+ * # library.name = aec/libspa-aec-webrtc
+ * # node.latency = 1024/48000
+ * # monitor.mode = false
+ * capture.props = {
+ * node.name = "Echo Cancellation Capture"
+ * }
+ * source.props = {
+ * node.name = "Echo Cancellation Source"
+ * }
+ * sink.props = {
+ * node.name = "Echo Cancellation Sink"
+ * }
+ * playback.props = {
+ * node.name = "Echo Cancellation Playback"
+ * }
+ * }
+ * }
+ *]
+ *\endcode
+ *
+ */
+
+/**
+ * .--------. .---------. .--------. .----------. .-------.
+ * | source | --> | capture | --> | | --> | source | --> | app |
+ * '--------' '---------' | echo | '----------' '-------'
+ * | cancel |
+ * .--------. .---------. | | .----------. .--------.
+ * | app | --> | sink | --> | | --> | playback | --> | sink |
+ * '--------' '---------' '--------' '----------' '--------'
+ */
+#define NAME "echo-cancel"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define DEFAULT_RATE 48000
+#define DEFAULT_CHANNELS 2
+#define DEFAULT_POSITION "[ FL FR ]"
+
+/* Hopefully this is enough for any combination of AEC engine and resampler
+ * input requirement for rate matching */
+#define MAX_BUFSIZE_MS 100
+#define DELAY_MS 0
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Echo Cancellation" },
+ { PW_KEY_MODULE_USAGE, " [ remote.name=<remote> ] "
+ "[ node.latency=<latency as fraction> ] "
+ "[ audio.rate=<sample rate> ] "
+ "[ audio.channels=<number of channels> ] "
+ "[ audio.position=<channel map> ] "
+ "[ buffer.max_size=<max buffer size in ms> ] "
+ "[ buffer.play_delay=<delay as fraction> ] "
+ "[ library.name =<library name> ] "
+ "[ aec.args=<aec arguments> ] "
+ "[ capture.props=<properties> ] "
+ "[ source.props=<properties> ] "
+ "[ sink.props=<properties> ] "
+ "[ playback.props=<properties> ] " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct impl {
+ struct pw_context *context;
+
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_core *core;
+ struct spa_hook core_proxy_listener;
+ struct spa_hook core_listener;
+
+ struct spa_audio_info_raw info;
+
+ struct pw_properties *capture_props;
+ struct pw_stream *capture;
+ struct spa_hook capture_listener;
+ struct spa_audio_info_raw capture_info;
+
+ struct pw_properties *source_props;
+ struct pw_stream *source;
+ struct spa_hook source_listener;
+ struct spa_audio_info_raw source_info;
+
+ void *rec_buffer[SPA_AUDIO_MAX_CHANNELS];
+ uint32_t rec_ringsize;
+ struct spa_ringbuffer rec_ring;
+
+ struct pw_properties *playback_props;
+ struct pw_stream *playback;
+ struct spa_hook playback_listener;
+ struct spa_audio_info_raw playback_info;
+
+ struct pw_properties *sink_props;
+ struct pw_stream *sink;
+ struct spa_hook sink_listener;
+ void *play_buffer[SPA_AUDIO_MAX_CHANNELS];
+ uint32_t play_ringsize;
+ struct spa_ringbuffer play_ring;
+ struct spa_ringbuffer play_delayed_ring;
+ struct spa_audio_info_raw sink_info;
+
+ void *out_buffer[SPA_AUDIO_MAX_CHANNELS];
+ uint32_t out_ringsize;
+ struct spa_ringbuffer out_ring;
+
+ struct spa_audio_aec *aec;
+ uint32_t aec_blocksize;
+
+ unsigned int capture_ready:1;
+ unsigned int sink_ready:1;
+
+ unsigned int do_disconnect:1;
+
+ uint32_t max_buffer_size;
+ uint32_t buffer_delay;
+ uint32_t current_delay;
+
+ struct spa_handle *spa_handle;
+ struct spa_plugin_loader *loader;
+
+ bool monitor_mode;
+};
+
+static void process(struct impl *impl)
+{
+ struct pw_buffer *cout;
+ struct pw_buffer *pout = NULL;
+ float rec_buf[impl->info.channels][impl->aec_blocksize / sizeof(float)];
+ float play_buf[impl->info.channels][impl->aec_blocksize / sizeof(float)];
+ float play_delayed_buf[impl->info.channels][impl->aec_blocksize / sizeof(float)];
+ float out_buf[impl->info.channels][impl->aec_blocksize / sizeof(float)];
+ const float *rec[impl->info.channels];
+ const float *play[impl->info.channels];
+ const float *play_delayed[impl->info.channels];
+ float *out[impl->info.channels];
+ struct spa_data *dd;
+ uint32_t i, size;
+ uint32_t rindex, pindex, oindex, pdindex, avail;
+ int32_t stride = 0;
+
+ if (impl->playback != NULL && (pout = pw_stream_dequeue_buffer(impl->playback)) == NULL) {
+ pw_log_debug("out of playback buffers: %m");
+ goto done;
+ }
+
+ size = impl->aec_blocksize;
+
+ /* First read a block from the playback and capture ring buffers */
+
+ spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex);
+ spa_ringbuffer_get_read_index(&impl->play_ring, &pindex);
+ spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &pdindex);
+
+ for (i = 0; i < impl->info.channels; i++) {
+ /* captured samples, with echo from sink */
+ rec[i] = &rec_buf[i][0];
+ /* echo from sink */
+ play[i] = &play_buf[i][0];
+ /* echo from sink delayed */
+ play_delayed[i] = &play_delayed_buf[i][0];
+ /* filtered samples, without echo from sink */
+ out[i] = &out_buf[i][0];
+
+ stride = 0;
+ spa_ringbuffer_read_data(&impl->rec_ring, impl->rec_buffer[i],
+ impl->rec_ringsize, rindex % impl->rec_ringsize,
+ (void*)rec[i], size);
+
+ stride = 0;
+ spa_ringbuffer_read_data(&impl->play_ring, impl->play_buffer[i],
+ impl->play_ringsize, pindex % impl->play_ringsize,
+ (void *)play[i], size);
+
+ stride = 0;
+ spa_ringbuffer_read_data(&impl->play_delayed_ring, impl->play_buffer[i],
+ impl->play_ringsize, pdindex % impl->play_ringsize,
+ (void *)play_delayed[i], size);
+
+ if (pout != NULL) {
+ /* output to sink, just copy */
+ dd = &pout->buffer->datas[i];
+ memcpy(dd->data, play[i], size);
+
+ dd->chunk->offset = 0;
+ dd->chunk->size = size;
+ dd->chunk->stride = stride;
+ }
+ }
+
+ spa_ringbuffer_read_update(&impl->rec_ring, rindex + size);
+ spa_ringbuffer_read_update(&impl->play_ring, pindex + size);
+ spa_ringbuffer_read_update(&impl->play_delayed_ring, pdindex + size);
+
+ if (impl->playback != NULL)
+ pw_stream_queue_buffer(impl->playback, pout);
+
+ if (SPA_UNLIKELY (impl->current_delay < impl->buffer_delay)) {
+ uint32_t delay_left = impl->buffer_delay - impl->current_delay;
+ uint32_t silence_size;
+
+ /* don't run the canceller until play_buffer has been filled,
+ * copy silence to output in the meantime */
+ silence_size = SPA_MIN(size, delay_left * sizeof(float));
+ for (i = 0; i < impl->info.channels; i++)
+ memset(out[i], 0, silence_size);
+ impl->current_delay += silence_size / sizeof(float);
+ pw_log_debug("current_delay %d", impl->current_delay);
+
+ if (silence_size != size) {
+ const float *pd[impl->info.channels];
+ float *o[impl->info.channels];
+
+ for (i = 0; i < impl->info.channels; i++) {
+ pd[i] = play_delayed[i] + delay_left;
+ o[i] = out[i] + delay_left;
+ }
+ spa_audio_aec_run(impl->aec, rec, pd, o, size / sizeof(float) - delay_left);
+ }
+ } else {
+ /* run the canceller */
+ spa_audio_aec_run(impl->aec, rec, play_delayed, out, size / sizeof(float));
+ }
+
+ /* Next, copy over the output to the output ringbuffer */
+ avail = spa_ringbuffer_get_write_index(&impl->out_ring, &oindex);
+ if (avail + size > impl->out_ringsize) {
+ uint32_t rindex, drop;
+
+ /* Drop enough so we have size bytes left */
+ drop = avail + size - impl->out_ringsize;
+ pw_log_debug("output ringbuffer xrun %d + %u > %u, dropping %u",
+ avail, size, impl->out_ringsize, drop);
+
+ spa_ringbuffer_get_read_index(&impl->out_ring, &rindex);
+ spa_ringbuffer_read_update(&impl->out_ring, rindex + drop);
+
+ avail += drop;
+ }
+
+ for (i = 0; i < impl->info.channels; i++) {
+ /* captured samples, with echo from sink */
+ spa_ringbuffer_write_data(&impl->out_ring, impl->out_buffer[i],
+ impl->out_ringsize, oindex % impl->out_ringsize,
+ (void *)out[i], size);
+ }
+
+ spa_ringbuffer_write_update(&impl->out_ring, oindex + size);
+
+ /* And finally take data from the output ringbuffer and make it
+ * available on the source */
+
+ avail = spa_ringbuffer_get_read_index(&impl->out_ring, &oindex);
+ while (avail >= size) {
+ if ((cout = pw_stream_dequeue_buffer(impl->source)) == NULL) {
+ pw_log_debug("out of source buffers: %m");
+ break;
+ }
+
+ for (i = 0; i < impl->info.channels; i++) {
+ dd = &cout->buffer->datas[i];
+ spa_ringbuffer_read_data(&impl->out_ring, impl->out_buffer[i],
+ impl->out_ringsize, oindex % impl->out_ringsize,
+ (void *)dd->data, size);
+ dd->chunk->offset = 0;
+ dd->chunk->size = size;
+ dd->chunk->stride = 0;
+ }
+
+ pw_stream_queue_buffer(impl->source, cout);
+
+ oindex += size;
+ spa_ringbuffer_read_update(&impl->out_ring, oindex);
+ avail -= size;
+ }
+
+done:
+ impl->sink_ready = false;
+ impl->capture_ready = false;
+}
+
+static void capture_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->capture_listener);
+ impl->capture = NULL;
+}
+
+static void capture_process(void *data)
+{
+ struct impl *impl = data;
+ struct pw_buffer *buf;
+ struct spa_data *d;
+ uint32_t i, index, offs, size;
+ int32_t avail;
+
+ if ((buf = pw_stream_dequeue_buffer(impl->capture)) == NULL) {
+ pw_log_debug("out of capture buffers: %m");
+ return;
+ }
+ d = &buf->buffer->datas[0];
+
+ offs = SPA_MIN(d->chunk->offset, d->maxsize);
+ size = SPA_MIN(d->chunk->size, d->maxsize - offs);
+
+ avail = spa_ringbuffer_get_write_index(&impl->rec_ring, &index);
+
+ if (avail + size > impl->rec_ringsize) {
+ uint32_t rindex, drop;
+
+ /* Drop enough so we have size bytes left */
+ drop = avail + size - impl->rec_ringsize;
+ pw_log_debug("capture ringbuffer xrun %d + %u > %u, dropping %u",
+ avail, size, impl->rec_ringsize, drop);
+
+ spa_ringbuffer_get_read_index(&impl->rec_ring, &rindex);
+ spa_ringbuffer_read_update(&impl->rec_ring, rindex + drop);
+
+ avail += drop;
+ }
+
+ /* If we don't know what size to push yet, use the canceller blocksize
+ * if it has a specific requirement, else keep the block size the same
+ * on input and output or what the resampler needs */
+ if (impl->aec_blocksize == 0) {
+ impl->aec_blocksize = size;
+ pw_log_debug("Setting AEC block size to %u", impl->aec_blocksize);
+ }
+
+ for (i = 0; i < impl->info.channels; i++) {
+ /* captured samples, with echo from sink */
+ d = &buf->buffer->datas[i];
+
+ offs = SPA_MIN(d->chunk->offset, d->maxsize);
+ size = SPA_MIN(d->chunk->size, d->maxsize - offs);
+
+ spa_ringbuffer_write_data(&impl->rec_ring, impl->rec_buffer[i],
+ impl->rec_ringsize, index % impl->rec_ringsize,
+ SPA_PTROFF(d->data, offs, void), size);
+ }
+
+ spa_ringbuffer_write_update(&impl->rec_ring, index + size);
+
+ if (avail + size >= impl->aec_blocksize) {
+ impl->capture_ready = true;
+ if (impl->sink_ready)
+ process(impl);
+ }
+
+ pw_stream_queue_buffer(impl->capture, buf);
+}
+
+static void capture_state_changed(void *data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct impl *impl = data;
+ switch (state) {
+ case PW_STREAM_STATE_PAUSED:
+ pw_stream_flush(impl->source, false);
+ pw_stream_flush(impl->capture, false);
+ break;
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_log_info("%p: input unconnected", impl);
+ pw_impl_module_schedule_destroy(impl->module);
+ break;
+ case PW_STREAM_STATE_ERROR:
+ pw_log_info("%p: input error: %s", impl, error);
+ break;
+ default:
+ break;
+ }
+}
+
+static void source_state_changed(void *data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct impl *impl = data;
+ int res;
+
+ switch (state) {
+ case PW_STREAM_STATE_PAUSED:
+ pw_stream_flush(impl->source, false);
+ pw_stream_flush(impl->capture, false);
+
+ if (old == PW_STREAM_STATE_STREAMING) {
+ pw_log_debug("%p: deactivate %s", impl, impl->aec->name);
+ res = spa_audio_aec_deactivate(impl->aec);
+ if (res < 0 && res != -EOPNOTSUPP) {
+ pw_log_error("aec plugin %s deactivate failed: %s", impl->aec->name, spa_strerror(res));
+ }
+ }
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ pw_log_debug("%p: activate %s", impl, impl->aec->name);
+ res = spa_audio_aec_activate(impl->aec);
+ if (res < 0 && res != -EOPNOTSUPP) {
+ pw_log_error("aec plugin %s activate failed: %s", impl->aec->name, spa_strerror(res));
+ }
+ break;
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_log_info("%p: input unconnected", impl);
+ pw_impl_module_schedule_destroy(impl->module);
+ break;
+ case PW_STREAM_STATE_ERROR:
+ pw_log_info("%p: input error: %s", impl, error);
+ break;
+ default:
+ break;
+ }
+}
+
+static void input_param_latency_changed(struct impl *impl, const struct spa_pod *param)
+{
+ struct spa_latency_info latency;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b;
+ const struct spa_pod *params[1];
+
+ if (spa_latency_parse(param, &latency) < 0)
+ return;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency);
+
+ if (latency.direction == SPA_DIRECTION_INPUT)
+ pw_stream_update_params(impl->source, params, 1);
+ else
+ pw_stream_update_params(impl->capture, params, 1);
+}
+static struct spa_pod* get_props_param(struct impl* impl, struct spa_pod_builder* b)
+{
+ if (spa_audio_aec_get_params(impl->aec, NULL) > 0) {
+ struct spa_pod_frame f[2];
+ 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]);
+
+ spa_audio_aec_get_params(impl->aec, b);
+
+ spa_pod_builder_pop(b, &f[1]);
+ return spa_pod_builder_pop(b, &f[0]);
+ }
+ return NULL;
+}
+
+static void input_param_changed(void *data, uint32_t id, const struct spa_pod* param)
+{
+ struct spa_pod_object* obj = (struct spa_pod_object*)param;
+ const struct spa_pod_prop* prop;
+ struct impl* impl = data;
+ switch (id) {
+ case SPA_PARAM_Latency:
+ input_param_latency_changed(impl, param);
+ break;
+ case SPA_PARAM_Props:
+ if (param != NULL) {
+ uint8_t buffer[1024];
+ struct spa_pod_dynamic_builder b;
+ const struct spa_pod* params[1];
+ SPA_POD_OBJECT_FOREACH(obj, prop)
+ {
+ if (prop->key == SPA_PROP_params) {
+ spa_audio_aec_set_params(impl->aec, &prop->value);
+ }
+ }
+
+ spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
+ params[0] = get_props_param(impl, &b.b);
+ if (params[0]) {
+ pw_stream_update_params(impl->capture, params, 1);
+ pw_stream_update_params(impl->playback, params, 1);
+ }
+ spa_pod_dynamic_builder_clean(&b);
+ } else {
+ pw_log_warn("param is null");
+ }
+ break;
+ }
+}
+
+static const struct pw_stream_events capture_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = capture_destroy,
+ .state_changed = capture_state_changed,
+ .process = capture_process,
+ .param_changed = input_param_changed
+};
+
+static void source_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->source_listener);
+ impl->source = NULL;
+}
+
+static const struct pw_stream_events source_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = source_destroy,
+ .state_changed = source_state_changed,
+ .param_changed = input_param_changed
+};
+
+static void output_state_changed(void *data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct impl *impl = data;
+ switch (state) {
+ case PW_STREAM_STATE_PAUSED:
+ pw_stream_flush(impl->sink, false);
+ if (impl->playback != NULL)
+ pw_stream_flush(impl->playback, false);
+ if (old == PW_STREAM_STATE_STREAMING) {
+ impl->current_delay = 0;
+ }
+ break;
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_log_info("%p: output unconnected", impl);
+ pw_impl_module_schedule_destroy(impl->module);
+ break;
+ case PW_STREAM_STATE_ERROR:
+ pw_log_info("%p: output error: %s", impl, error);
+ break;
+ default:
+ break;
+ }
+}
+
+static void output_param_latency_changed(struct impl *impl, const struct spa_pod *param)
+{
+ struct spa_latency_info latency;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b;
+ const struct spa_pod *params[1];
+
+ if (spa_latency_parse(param, &latency) < 0)
+ return;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency);
+
+ if (latency.direction == SPA_DIRECTION_INPUT)
+ pw_stream_update_params(impl->sink, params, 1);
+ else if (impl->playback != NULL)
+ pw_stream_update_params(impl->playback, params, 1);
+}
+
+static void output_param_changed(void *data, uint32_t id, const struct spa_pod *param)
+{
+ struct spa_pod_object *obj = (struct spa_pod_object *) param;
+ const struct spa_pod_prop *prop;
+ struct impl *impl = data;
+ switch (id) {
+ case SPA_PARAM_Latency:
+ output_param_latency_changed(impl, param);
+ break;
+ case SPA_PARAM_Props:
+ if (param != NULL) {
+ uint8_t buffer[1024];
+ struct spa_pod_dynamic_builder b;
+ const struct spa_pod* params[1];
+
+ SPA_POD_OBJECT_FOREACH(obj, prop)
+ {
+ if (prop->key == SPA_PROP_params) {
+ spa_audio_aec_set_params(impl->aec, &prop->value);
+ }
+ }
+ spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
+ params[0] = get_props_param(impl, &b.b);
+ if (params[0] != NULL) {
+ pw_stream_update_params(impl->capture, params, 1);
+ pw_stream_update_params(impl->playback, params, 1);
+ }
+ spa_pod_dynamic_builder_clean(&b);
+ }
+ break;
+ }
+}
+
+static void sink_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->sink_listener);
+ impl->sink = NULL;
+}
+
+static void sink_process(void *data)
+{
+ struct impl *impl = data;
+ struct pw_buffer *buf;
+ struct spa_data *d;
+ uint32_t i, index, offs, size;
+ int32_t avail;
+
+ if ((buf = pw_stream_dequeue_buffer(impl->sink)) == NULL) {
+ pw_log_debug("out of sink buffers: %m");
+ return;
+ }
+ d = &buf->buffer->datas[0];
+
+ offs = SPA_MIN(d->chunk->offset, d->maxsize);
+ size = SPA_MIN(d->chunk->size, d->maxsize - offs);
+
+ avail = spa_ringbuffer_get_write_index(&impl->play_ring, &index);
+
+ if (avail + size > impl->play_ringsize) {
+ uint32_t rindex, drop;
+
+ /* Drop enough so we have size bytes left */
+ drop = avail + size - impl->play_ringsize;
+ pw_log_debug("sink ringbuffer xrun %d + %u > %u, dropping %u",
+ avail, size, impl->play_ringsize, drop);
+
+ spa_ringbuffer_get_read_index(&impl->play_ring, &rindex);
+ spa_ringbuffer_read_update(&impl->play_ring, rindex + drop);
+
+ spa_ringbuffer_get_read_index(&impl->play_delayed_ring, &rindex);
+ spa_ringbuffer_read_update(&impl->play_delayed_ring, rindex + drop);
+
+ avail += drop;
+ }
+
+ if (impl->aec_blocksize == 0) {
+ impl->aec_blocksize = size;
+ pw_log_debug("Setting AEC block size to %u", impl->aec_blocksize);
+ }
+
+ for (i = 0; i < impl->info.channels; i++) {
+ /* echo from sink */
+ d = &buf->buffer->datas[i];
+
+ offs = SPA_MIN(d->chunk->offset, d->maxsize);
+ size = SPA_MIN(d->chunk->size, d->maxsize - offs);
+
+ spa_ringbuffer_write_data(&impl->play_ring, impl->play_buffer[i],
+ impl->play_ringsize, index % impl->play_ringsize,
+ SPA_PTROFF(d->data, offs, void), size);
+ }
+ spa_ringbuffer_write_update(&impl->play_ring, index + size);
+
+ if (avail + size >= impl->aec_blocksize) {
+ impl->sink_ready = true;
+ if (impl->capture_ready)
+ process(impl);
+ }
+
+ pw_stream_queue_buffer(impl->sink, buf);
+}
+
+static void playback_destroy(void *d)
+{
+ struct impl *impl = d;
+ if (impl->playback != NULL) {
+ spa_hook_remove(&impl->playback_listener);
+ impl->playback = NULL;
+ }
+}
+
+static const struct pw_stream_events playback_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = playback_destroy,
+ .state_changed = output_state_changed,
+ .param_changed = output_param_changed
+};
+static const struct pw_stream_events sink_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = sink_destroy,
+ .process = sink_process,
+ .state_changed = output_state_changed,
+ .param_changed = output_param_changed
+};
+
+static int setup_streams(struct impl *impl)
+{
+ int res;
+ uint32_t n_params, i;
+ uint32_t offsets[512];
+ const struct spa_pod *params[512];
+ struct spa_pod_dynamic_builder b;
+ uint32_t index;
+
+ impl->capture = pw_stream_new(impl->core,
+ "Echo-Cancel Capture", impl->capture_props);
+ impl->capture_props = NULL;
+ if (impl->capture == NULL)
+ return -errno;
+
+ pw_stream_add_listener(impl->capture,
+ &impl->capture_listener,
+ &capture_events, impl);
+
+ impl->source = pw_stream_new(impl->core,
+ "Echo-Cancel Source", impl->source_props);
+ impl->source_props = NULL;
+ if (impl->source == NULL)
+ return -errno;
+
+ pw_stream_add_listener(impl->source,
+ &impl->source_listener,
+ &source_events, impl);
+
+ if (impl->monitor_mode) {
+ impl->playback = NULL;
+ } else {
+ impl->playback = pw_stream_new(impl->core,
+ "Echo-Cancel Playback", impl->playback_props);
+ impl->playback_props = NULL;
+ if (impl->playback == NULL)
+ return -errno;
+
+ pw_stream_add_listener(impl->playback,
+ &impl->playback_listener,
+ &playback_events, impl);
+ }
+
+ impl->sink = pw_stream_new(impl->core,
+ "Echo-Cancel Sink", impl->sink_props);
+ impl->sink_props = NULL;
+ if (impl->sink == NULL)
+ return -errno;
+
+ pw_stream_add_listener(impl->sink,
+ &impl->sink_listener,
+ &sink_events, impl);
+
+ n_params = 0;
+ spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
+
+ offsets[n_params++] = b.b.state.offset;
+ spa_format_audio_raw_build(&b.b, SPA_PARAM_EnumFormat, &impl->capture_info);
+
+ int nbr_of_external_props = spa_audio_aec_enum_props(impl->aec, 0, NULL);
+ if (nbr_of_external_props > 0) {
+ for (int i = 0; i < nbr_of_external_props; i++) {
+ offsets[n_params++] = b.b.state.offset;
+ spa_audio_aec_enum_props(impl->aec, i, &b.b);
+ }
+ get_props_param(impl, &b.b);
+ }
+
+ for (i = 0; i < n_params; i++)
+ params[i] = spa_pod_builder_deref(&b.b, offsets[i]);
+
+ if ((res = pw_stream_connect(impl->capture,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0) {
+ spa_pod_dynamic_builder_clean(&b);
+ return res;
+ }
+
+ offsets[0] = b.b.state.offset;
+ spa_format_audio_raw_build(&b.b, SPA_PARAM_EnumFormat, &impl->source_info);
+
+ for (i = 0; i < n_params; i++)
+ params[i] = spa_pod_builder_deref(&b.b, offsets[i]);
+
+ if ((res = pw_stream_connect(impl->source,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0) {
+ spa_pod_dynamic_builder_clean(&b);
+ return res;
+ }
+
+ offsets[0] = b.b.state.offset;
+ spa_format_audio_raw_build(&b.b, SPA_PARAM_EnumFormat, &impl->sink_info);
+
+ for (i = 0; i < n_params; i++)
+ params[i] = spa_pod_builder_deref(&b.b, offsets[i]);
+
+ if ((res = pw_stream_connect(impl->sink,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ impl->playback != NULL ?
+ PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS :
+ PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0) {
+ spa_pod_dynamic_builder_clean(&b);
+ return res;
+ }
+
+ offsets[0] = b.b.state.offset;
+ spa_format_audio_raw_build(&b.b, SPA_PARAM_EnumFormat, &impl->playback_info);
+
+ for (i = 0; i < n_params; i++)
+ params[i] = spa_pod_builder_deref(&b.b, offsets[i]);
+
+ if (impl->playback != NULL && (res = pw_stream_connect(impl->playback,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0) {
+ spa_pod_dynamic_builder_clean(&b);
+ return res;
+ }
+
+ spa_pod_dynamic_builder_clean(&b);
+
+ impl->rec_ringsize = sizeof(float) * impl->max_buffer_size * impl->info.rate / 1000;
+ impl->play_ringsize = sizeof(float) * ((impl->max_buffer_size * impl->info.rate / 1000) + impl->buffer_delay);
+ impl->out_ringsize = sizeof(float) * impl->max_buffer_size * impl->info.rate / 1000;
+ for (i = 0; i < impl->info.channels; i++) {
+ impl->rec_buffer[i] = malloc(impl->rec_ringsize);
+ impl->play_buffer[i] = malloc(impl->play_ringsize);
+ impl->out_buffer[i] = malloc(impl->out_ringsize);
+ }
+ spa_ringbuffer_init(&impl->rec_ring);
+ spa_ringbuffer_init(&impl->play_ring);
+ spa_ringbuffer_init(&impl->play_delayed_ring);
+ spa_ringbuffer_init(&impl->out_ring);
+
+ spa_ringbuffer_get_write_index(&impl->play_ring, &index);
+ spa_ringbuffer_write_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay)));
+ spa_ringbuffer_get_read_index(&impl->play_ring, &index);
+ spa_ringbuffer_read_update(&impl->play_ring, index + (sizeof(float) * (impl->buffer_delay)));
+
+ return 0;
+}
+
+static void core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct impl *impl = data;
+
+ if (res == -ENOENT) {
+ pw_log_info("id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+ } else {
+ pw_log_warn("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+ }
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = core_error,
+};
+
+static void core_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->core_listener);
+ impl->core = NULL;
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ .destroy = core_destroy,
+};
+
+static void impl_destroy(struct impl *impl)
+{
+ uint32_t i;
+ if (impl->capture)
+ pw_stream_destroy(impl->capture);
+ if (impl->source)
+ pw_stream_destroy(impl->source);
+ if (impl->playback)
+ pw_stream_destroy(impl->playback);
+ if (impl->sink)
+ pw_stream_destroy(impl->sink);
+ if (impl->core && impl->do_disconnect)
+ pw_core_disconnect(impl->core);
+ if (impl->spa_handle)
+ spa_plugin_loader_unload(impl->loader, impl->spa_handle);
+ pw_properties_free(impl->capture_props);
+ pw_properties_free(impl->source_props);
+ pw_properties_free(impl->playback_props);
+ pw_properties_free(impl->sink_props);
+
+ for (i = 0; i < impl->info.channels; i++) {
+ if (impl->rec_buffer[i])
+ free(impl->rec_buffer[i]);
+ if (impl->play_buffer[i])
+ free(impl->play_buffer[i]);
+ if (impl->out_buffer[i])
+ free(impl->out_buffer[i]);
+ }
+
+ free(impl);
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->module_listener);
+ impl_destroy(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_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));
+}
+
+static void copy_props(struct impl *impl, struct pw_properties *props, const char *key)
+{
+ const char *str;
+ if ((str = pw_properties_get(props, key)) != NULL) {
+ if (pw_properties_get(impl->capture_props, key) == NULL)
+ pw_properties_set(impl->capture_props, key, str);
+ if (pw_properties_get(impl->source_props, key) == NULL)
+ pw_properties_set(impl->source_props, key, str);
+ if (pw_properties_get(impl->playback_props, key) == NULL)
+ pw_properties_set(impl->playback_props, key, str);
+ if (pw_properties_get(impl->sink_props, key) == NULL)
+ pw_properties_set(impl->sink_props, key, str);
+ }
+}
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props, *aec_props;
+ struct impl *impl;
+ uint32_t id = pw_global_get_id(pw_impl_module_get_global(module));
+ uint32_t pid = getpid();
+ const char *str;
+ const char *path;
+ int res = 0;
+ struct spa_handle *handle = NULL;
+ void *iface;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args)
+ props = pw_properties_new_string(args);
+ else
+ props = pw_properties_new(NULL, NULL);
+ if (props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+
+ impl->capture_props = pw_properties_new(NULL, NULL);
+ impl->source_props = pw_properties_new(NULL, NULL);
+ impl->playback_props = pw_properties_new(NULL, NULL);
+ impl->sink_props = pw_properties_new(NULL, NULL);
+ if (impl->source_props == NULL || impl->sink_props == NULL ||
+ impl->capture_props == NULL || impl->playback_props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+
+ impl->monitor_mode = false;
+ if ((str = pw_properties_get(props, "monitor.mode")) != NULL)
+ impl->monitor_mode = pw_properties_parse_bool(str);
+
+ impl->module = module;
+ impl->context = context;
+
+ if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_GROUP, "echo-cancel-%u-%u", pid, id);
+ if (pw_properties_get(props, PW_KEY_NODE_LINK_GROUP) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_LINK_GROUP, "echo-cancel-%u-%u", pid, id);
+ if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
+ if (pw_properties_get(props, "resample.prefill") == NULL)
+ pw_properties_set(props, "resample.prefill", "true");
+
+ parse_audio_info(props, &impl->info);
+
+ impl->capture_info = impl->info;
+ impl->source_info = impl->info;
+ impl->sink_info = impl->info;
+ impl->playback_info = impl->info;
+
+ if ((str = pw_properties_get(props, "capture.props")) != NULL)
+ pw_properties_update_string(impl->capture_props, str, strlen(str));
+ if ((str = pw_properties_get(props, "source.props")) != NULL)
+ pw_properties_update_string(impl->source_props, str, strlen(str));
+ if ((str = pw_properties_get(props, "sink.props")) != NULL)
+ pw_properties_update_string(impl->sink_props, str, strlen(str));
+ if ((str = pw_properties_get(props, "playback.props")) != NULL)
+ pw_properties_update_string(impl->playback_props, str, strlen(str));
+
+ if (pw_properties_get(impl->capture_props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_set(impl->capture_props, PW_KEY_NODE_NAME, "echo-cancel-capture");
+ if (pw_properties_get(impl->capture_props, PW_KEY_NODE_DESCRIPTION) == NULL)
+ pw_properties_set(impl->capture_props, PW_KEY_NODE_DESCRIPTION, "Echo-Cancel Capture");
+ if (pw_properties_get(impl->capture_props, PW_KEY_NODE_PASSIVE) == NULL)
+ pw_properties_set(impl->capture_props, PW_KEY_NODE_PASSIVE, "true");
+
+ if (pw_properties_get(impl->source_props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_set(impl->source_props, PW_KEY_NODE_NAME, "echo-cancel-source");
+ if (pw_properties_get(impl->source_props, PW_KEY_NODE_DESCRIPTION) == NULL)
+ pw_properties_set(impl->source_props, PW_KEY_NODE_DESCRIPTION, "Echo-Cancel Source");
+ if (pw_properties_get(impl->source_props, PW_KEY_MEDIA_CLASS) == NULL)
+ pw_properties_set(impl->source_props, PW_KEY_MEDIA_CLASS, "Audio/Source");
+
+ if (pw_properties_get(impl->playback_props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_set(impl->playback_props, PW_KEY_NODE_NAME, "echo-cancel-playback");
+ if (pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION) == NULL)
+ pw_properties_set(impl->playback_props, PW_KEY_NODE_DESCRIPTION, "Echo-Cancel Playback");
+ if (pw_properties_get(impl->playback_props, PW_KEY_NODE_PASSIVE) == NULL)
+ pw_properties_set(impl->playback_props, PW_KEY_NODE_PASSIVE, "true");
+
+ if (pw_properties_get(impl->sink_props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_set(impl->sink_props, PW_KEY_NODE_NAME, "echo-cancel-sink");
+ if (pw_properties_get(impl->sink_props, PW_KEY_NODE_DESCRIPTION) == NULL)
+ pw_properties_set(impl->sink_props, PW_KEY_NODE_DESCRIPTION, "Echo-Cancel Sink");
+ if (pw_properties_get(impl->sink_props, PW_KEY_MEDIA_CLASS) == NULL)
+ pw_properties_set(impl->sink_props, PW_KEY_MEDIA_CLASS,
+ impl->monitor_mode ? "Stream/Input/Audio" : "Audio/Sink");
+ if (impl->monitor_mode) {
+ if (pw_properties_get(impl->sink_props, PW_KEY_NODE_PASSIVE) == NULL)
+ pw_properties_set(impl->sink_props, PW_KEY_NODE_PASSIVE, "true");
+ if (pw_properties_get(impl->sink_props, PW_KEY_STREAM_MONITOR) == NULL)
+ pw_properties_set(impl->sink_props, PW_KEY_STREAM_MONITOR, "true");
+ if (pw_properties_get(impl->sink_props, PW_KEY_STREAM_CAPTURE_SINK) == NULL)
+ pw_properties_set(impl->sink_props, PW_KEY_STREAM_CAPTURE_SINK, "true");
+ }
+
+ if ((str = pw_properties_get(props, "aec.method")) != NULL)
+ pw_log_warn("aec.method is not supported anymore use library.name");
+
+ /* Use webrtc as default */
+ if ((path = pw_properties_get(props, "library.name")) == NULL)
+ path = "aec/libspa-aec-webrtc";
+
+ const struct spa_support *support;
+ uint32_t n_support;
+
+ support = pw_context_get_support(context, &n_support);
+ impl->loader = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_PluginLoader);
+ if (impl->loader == NULL) {
+ pw_log_error("a plugin loader is needed");
+ return -EINVAL;
+ }
+
+ struct spa_dict_item info_items[] = {
+ { SPA_KEY_LIBRARY_NAME, path },
+ };
+ struct spa_dict info = SPA_DICT_INIT_ARRAY(info_items);
+
+ handle = spa_plugin_loader_load(impl->loader, SPA_NAME_AEC, &info);
+ if (handle == NULL) {
+ pw_log_error("aec plugin %s not available library.name %s", SPA_NAME_AEC, path);
+ return -ENOENT;
+ }
+
+ if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_AUDIO_AEC, &iface)) < 0) {
+ pw_log_error("can't get %s interface %d", SPA_TYPE_INTERFACE_AUDIO_AEC, res);
+ return res;
+ }
+ impl->aec = iface;
+ impl->spa_handle = handle;
+
+ if (impl->aec->iface.version > SPA_VERSION_AUDIO_AEC) {
+ pw_log_error("codec plugin %s has incompatible ABI version (%d > %d)",
+ SPA_NAME_AEC, impl->aec->iface.version, SPA_VERSION_AUDIO_AEC);
+ res = -ENOENT;
+ goto error;
+ }
+
+ pw_log_info("Using plugin AEC %s with version %d", impl->aec->name,
+ impl->aec->iface.version);
+
+ if ((str = pw_properties_get(props, "aec.args")) != NULL)
+ aec_props = pw_properties_new_string(str);
+ else
+ aec_props = pw_properties_new(NULL, NULL);
+
+ res = spa_audio_aec_init(impl->aec, &aec_props->dict, &impl->info);
+
+ pw_properties_free(aec_props);
+
+ if (res < 0) {
+ pw_log_error("aec plugin %s create failed: %s", impl->aec->name,
+ spa_strerror(res));
+ goto error;
+ }
+
+ if (impl->aec->latency) {
+ unsigned int num, denom, req_num, req_denom;
+ unsigned int factor = 0;
+ unsigned int new_num = 0;
+
+ spa_assert_se(sscanf(impl->aec->latency, "%u/%u", &num, &denom) == 2);
+
+ if ((str = pw_properties_get(props, PW_KEY_NODE_LATENCY)) != NULL) {
+ sscanf(str, "%u/%u", &req_num, &req_denom);
+ factor = (req_num * denom) / (req_denom * num);
+ new_num = req_num / factor * factor;
+ }
+
+ if (factor == 0 || new_num == 0) {
+ pw_log_info("Setting node latency to %s", impl->aec->latency);
+ pw_properties_set(props, PW_KEY_NODE_LATENCY, impl->aec->latency);
+ impl->aec_blocksize = sizeof(float) * impl->info.rate * num / denom;
+ } else {
+ pw_log_info("Setting node latency to %u/%u", new_num, req_denom);
+ pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u", new_num, req_denom);
+ impl->aec_blocksize = sizeof(float) * impl->info.rate * num / denom * factor;
+ }
+ } else {
+ /* Implementation doesn't care about the block size */
+ impl->aec_blocksize = 0;
+ }
+
+ 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);
+ impl->core = pw_context_connect(impl->context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ impl->do_disconnect = true;
+ }
+ if (impl->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto error;
+ }
+
+ copy_props(impl, props, PW_KEY_NODE_GROUP);
+ copy_props(impl, props, PW_KEY_NODE_LINK_GROUP);
+ copy_props(impl, props, PW_KEY_NODE_VIRTUAL);
+ copy_props(impl, props, PW_KEY_NODE_LATENCY);
+ copy_props(impl, props, SPA_KEY_AUDIO_CHANNELS);
+ copy_props(impl, props, SPA_KEY_AUDIO_POSITION);
+ copy_props(impl, props, "resample.prefill");
+
+ if ((str = pw_properties_get(impl->capture_props, SPA_KEY_AUDIO_POSITION)) != NULL)
+ parse_position(&impl->capture_info, str, strlen(str));
+ if ((str = pw_properties_get(impl->source_props, SPA_KEY_AUDIO_POSITION)) != NULL)
+ parse_position(&impl->source_info, str, strlen(str));
+ if ((str = pw_properties_get(impl->sink_props, SPA_KEY_AUDIO_POSITION)) != NULL)
+ parse_position(&impl->sink_info, str, strlen(str));
+ if ((str = pw_properties_get(impl->playback_props, SPA_KEY_AUDIO_POSITION)) != NULL)
+ parse_position(&impl->playback_info, str, strlen(str));
+
+ if (impl->capture_info.channels != impl->info.channels)
+ impl->capture_info = impl->info;
+ if (impl->source_info.channels != impl->info.channels)
+ impl->source_info = impl->info;
+ if (impl->sink_info.channels != impl->info.channels)
+ impl->sink_info = impl->info;
+ if (impl->playback_info.channels != impl->info.channels)
+ impl->playback_info = impl->info;
+
+ impl->max_buffer_size = pw_properties_get_uint32(props,"buffer.max_size", MAX_BUFSIZE_MS);
+
+ if ((str = pw_properties_get(props, "buffer.play_delay")) != NULL) {
+ int req_num, req_denom;
+ if (sscanf(str, "%u/%u", &req_num, &req_denom) == 2) {
+ if (req_denom != 0) {
+ impl->buffer_delay = (impl->info.rate*req_num)/req_denom;
+ } else {
+ impl->buffer_delay = DELAY_MS * impl->info.rate / 1000;
+ pw_log_warn("Sample rate for buffer.play_delay is 0 using default");
+ }
+ } else {
+ impl->buffer_delay = DELAY_MS * impl->info.rate / 1000;
+ pw_log_warn("Wrong value/format for buffer.play_delay using default");
+ }
+ } else {
+ impl->buffer_delay = DELAY_MS * impl->info.rate / 1000;
+ }
+
+ pw_properties_free(props);
+
+ pw_proxy_add_listener((struct pw_proxy*)impl->core,
+ &impl->core_proxy_listener,
+ &core_proxy_events, impl);
+ pw_core_add_listener(impl->core,
+ &impl->core_listener,
+ &core_events, impl);
+
+ setup_streams(impl);
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+
+error:
+ pw_properties_free(props);
+ impl_destroy(impl);
+ return res;
+}
diff --git a/src/modules/module-example-sink.c b/src/modules/module-example-sink.c
new file mode 100644
index 0000000..d999922
--- /dev/null
+++ b/src/modules/module-example-sink.c
@@ -0,0 +1,498 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.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/utils/ringbuffer.h>
+#include <spa/debug/types.h>
+#include <spa/pod/builder.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/audio/raw.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/i18n.h>
+
+/** \page page_module_example_sink PipeWire Module: Example Sink
+ *
+ * The example sink is a good starting point for writing a custom
+ * sink. We refer to the source code for more information.
+ *
+ * ## Module Options
+ *
+ * - `node.name`: a unique name for the stream
+ * - `node.description`: a human readable name for the stream
+ * - `stream.props = {}`: properties to be passed to the stream
+ *
+ * ## 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_NODE_LATENCY
+ * - \ref PW_KEY_NODE_NAME
+ * - \ref PW_KEY_NODE_DESCRIPTION
+ * - \ref PW_KEY_NODE_GROUP
+ * - \ref PW_KEY_NODE_VIRTUAL
+ * - \ref PW_KEY_MEDIA_CLASS
+ *
+ * ## Example configuration
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-example-sink
+ * args = {
+ * node.name = "example_sink"
+ * node.description = "My Example Sink"
+ * stream.props = {
+ * audio.position = [ FL FR ]
+ * }
+ * }
+ * }
+ * ]
+ *\endcode
+ */
+
+#define NAME "example-sink"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define DEFAULT_FORMAT "S16"
+#define DEFAULT_RATE 48000
+#define DEFAULT_CHANNELS 2
+#define DEFAULT_POSITION "[ FL FR ]"
+
+#define MODULE_USAGE "[ node.latency=<latency as fraction> ] " \
+ "[ node.name=<name of the nodes> ] " \
+ "[ node.description=<description of the nodes> ] " \
+ "[ audio.format=<format, default:"DEFAULT_FORMAT"> ] " \
+ "[ audio.rate=<sample rate, default: "SPA_STRINGIFY(DEFAULT_RATE)"> ] " \
+ "[ audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS) "> ] " \
+ "[ audio.position=<channel map, default:"DEFAULT_POSITION"> ] " \
+ "[ stream.props=<properties> ] "
+
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "An example audio sink" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct impl {
+ struct pw_context *context;
+
+ struct pw_properties *props;
+
+ struct pw_impl_module *module;
+
+ struct spa_hook module_listener;
+
+ struct pw_core *core;
+ struct spa_hook core_proxy_listener;
+ struct spa_hook core_listener;
+
+ struct pw_properties *stream_props;
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+ struct spa_audio_info_raw info;
+ uint32_t frame_size;
+
+ unsigned int do_disconnect:1;
+};
+
+static void stream_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->stream_listener);
+ impl->stream = NULL;
+}
+
+static void stream_state_changed(void *d, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct impl *impl = d;
+ switch (state) {
+ case PW_STREAM_STATE_ERROR:
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_impl_module_schedule_destroy(impl->module);
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ case PW_STREAM_STATE_STREAMING:
+ break;
+ default:
+ break;
+ }
+}
+
+static void playback_stream_process(void *d)
+{
+ struct impl *impl = d;
+ struct pw_buffer *buf;
+ struct spa_data *bd;
+ void *data;
+ uint32_t offs, size;
+
+ if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) {
+ pw_log_debug("out of buffers: %m");
+ return;
+ }
+
+ bd = &buf->buffer->datas[0];
+
+ offs = SPA_MIN(bd->chunk->offset, bd->maxsize);
+ size = SPA_MIN(bd->chunk->size, bd->maxsize - offs);
+ data = SPA_PTROFF(bd->data, offs, void);
+
+ /* write buffer contents here */
+ pw_log_info("got buffer of size %d and data %p", size, data);
+
+ pw_stream_queue_buffer(impl->stream, buf);
+}
+
+static const struct pw_stream_events playback_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = stream_destroy,
+ .state_changed = stream_state_changed,
+ .process = playback_stream_process
+};
+
+static int create_stream(struct impl *impl)
+{
+ int res;
+ uint32_t n_params;
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b;
+
+ impl->stream = pw_stream_new(impl->core, "example sink", impl->stream_props);
+ impl->stream_props = NULL;
+
+ if (impl->stream == NULL)
+ return -errno;
+
+ pw_stream_add_listener(impl->stream,
+ &impl->stream_listener,
+ &playback_stream_events, impl);
+
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_format_audio_raw_build(&b,
+ SPA_PARAM_EnumFormat, &impl->info);
+
+ if ((res = pw_stream_connect(impl->stream,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0)
+ return res;
+
+ return 0;
+}
+
+static void core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct impl *impl = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = core_error,
+};
+
+static void core_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->core_listener);
+ impl->core = NULL;
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ .destroy = core_destroy,
+};
+
+static void impl_destroy(struct impl *impl)
+{
+ if (impl->stream)
+ pw_stream_destroy(impl->stream);
+ if (impl->core && impl->do_disconnect)
+ pw_core_disconnect(impl->core);
+
+ pw_properties_free(impl->stream_props);
+ pw_properties_free(impl->props);
+
+ free(impl);
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->module_listener);
+ impl_destroy(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_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));
+}
+
+static int calc_frame_size(const struct spa_audio_info_raw *info)
+{
+ int res = info->channels;
+ switch (info->format) {
+ case SPA_AUDIO_FORMAT_U8:
+ case SPA_AUDIO_FORMAT_S8:
+ case SPA_AUDIO_FORMAT_ALAW:
+ case SPA_AUDIO_FORMAT_ULAW:
+ return res;
+ case SPA_AUDIO_FORMAT_S16:
+ case SPA_AUDIO_FORMAT_S16_OE:
+ case SPA_AUDIO_FORMAT_U16:
+ return res * 2;
+ case SPA_AUDIO_FORMAT_S24:
+ case SPA_AUDIO_FORMAT_S24_OE:
+ case SPA_AUDIO_FORMAT_U24:
+ return res * 3;
+ case SPA_AUDIO_FORMAT_S24_32:
+ case SPA_AUDIO_FORMAT_S24_32_OE:
+ case SPA_AUDIO_FORMAT_S32:
+ case SPA_AUDIO_FORMAT_S32_OE:
+ case SPA_AUDIO_FORMAT_U32:
+ case SPA_AUDIO_FORMAT_U32_OE:
+ case SPA_AUDIO_FORMAT_F32:
+ case SPA_AUDIO_FORMAT_F32_OE:
+ return res * 4;
+ case SPA_AUDIO_FORMAT_F64:
+ case SPA_AUDIO_FORMAT_F64_OE:
+ return res * 8;
+ default:
+ return 0;
+ }
+}
+
+static void copy_props(struct impl *impl, struct pw_properties *props, const char *key)
+{
+ const char *str;
+ if ((str = pw_properties_get(props, key)) != NULL) {
+ if (pw_properties_get(impl->stream_props, key) == NULL)
+ pw_properties_set(impl->stream_props, key, str);
+ }
+}
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props = NULL;
+ uint32_t id = pw_global_get_id(pw_impl_module_get_global(module));
+ uint32_t pid = getpid();
+ struct impl *impl;
+ const char *str;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args == NULL)
+ args = "";
+
+ props = pw_properties_new_string(args);
+ if (props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+ impl->props = props;
+
+ impl->stream_props = pw_properties_new(NULL, NULL);
+ if (impl->stream_props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+
+ impl->module = module;
+ impl->context = context;
+
+ if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
+
+ if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL)
+ pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
+
+ if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_NAME, "example-sink-%u-%u", pid, id);
+ if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_DESCRIPTION,
+ pw_properties_get(props, PW_KEY_NODE_NAME));
+
+ if ((str = pw_properties_get(props, "stream.props")) != NULL)
+ pw_properties_update_string(impl->stream_props, str, strlen(str));
+
+ 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);
+ copy_props(impl, props, PW_KEY_NODE_GROUP);
+ copy_props(impl, props, PW_KEY_NODE_LATENCY);
+ copy_props(impl, props, PW_KEY_NODE_VIRTUAL);
+ copy_props(impl, props, PW_KEY_MEDIA_CLASS);
+
+ parse_audio_info(impl->stream_props, &impl->info);
+
+ impl->frame_size = calc_frame_size(&impl->info);
+ if (impl->frame_size == 0) {
+ res = -EINVAL;
+ pw_log_error( "can't parse audio format");
+ goto error;
+ }
+
+ 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);
+ impl->core = pw_context_connect(impl->context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ impl->do_disconnect = true;
+ }
+ if (impl->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto error;
+ }
+
+ pw_proxy_add_listener((struct pw_proxy*)impl->core,
+ &impl->core_proxy_listener,
+ &core_proxy_events, impl);
+ pw_core_add_listener(impl->core,
+ &impl->core_listener,
+ &core_events, impl);
+
+ if ((res = create_stream(impl)) < 0)
+ goto error;
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+
+error:
+ impl_destroy(impl);
+ return res;
+}
diff --git a/src/modules/module-example-source.c b/src/modules/module-example-source.c
new file mode 100644
index 0000000..1db62df
--- /dev/null
+++ b/src/modules/module-example-source.c
@@ -0,0 +1,504 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.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/utils/ringbuffer.h>
+#include <spa/debug/types.h>
+#include <spa/pod/builder.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/audio/raw.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/i18n.h>
+
+/** \page page_module_example_source PipeWire Module: Example Source
+ *
+ * The example source is a good starting point for writing a custom
+ * source. We refer to the source code for more information.
+ *
+ * ## Module Options
+ *
+ * - `node.name`: a unique name for the stream
+ * - `node.description`: a human readable name for the stream
+ * - `stream.props = {}`: properties to be passed to the stream
+ *
+ * ## 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_NODE_LATENCY
+ * - \ref PW_KEY_NODE_NAME
+ * - \ref PW_KEY_NODE_DESCRIPTION
+ * - \ref PW_KEY_NODE_GROUP
+ * - \ref PW_KEY_NODE_VIRTUAL
+ * - \ref PW_KEY_MEDIA_CLASS
+ *
+ * ## Example configuration
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-example-source
+ * args = {
+ * node.name = "example_source"
+ * node.description = "My Example Source"
+ * stream.props = {
+ * audio.position = [ FL FR ]
+ * }
+ * }
+ * }
+ * ]
+ *\endcode
+ */
+
+#define NAME "example-source"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define DEFAULT_FORMAT "S16"
+#define DEFAULT_RATE 48000
+#define DEFAULT_CHANNELS 2
+#define DEFAULT_POSITION "[ FL FR ]"
+
+#define MODULE_USAGE "[ node.latency=<latency as fraction> ] " \
+ "[ node.name=<name of the nodes> ] " \
+ "[ node.description=<description of the nodes> ] " \
+ "[ audio.format=<format, default:"DEFAULT_FORMAT"> ] " \
+ "[ audio.rate=<sample rate, default: "SPA_STRINGIFY(DEFAULT_RATE)"> ] " \
+ "[ audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS) "> ] " \
+ "[ audio.position=<channel map, default:"DEFAULT_POSITION"> ] " \
+ "[ stream.props=<properties> ] "
+
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "An example audio source" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct impl {
+ struct pw_context *context;
+
+ struct pw_properties *props;
+
+ struct pw_impl_module *module;
+
+ struct spa_hook module_listener;
+
+ struct pw_core *core;
+ struct spa_hook core_proxy_listener;
+ struct spa_hook core_listener;
+
+ struct pw_properties *stream_props;
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+ struct spa_audio_info_raw info;
+ uint32_t frame_size;
+
+ unsigned int do_disconnect:1;
+ unsigned int unloading:1;
+};
+
+static void stream_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->stream_listener);
+ impl->stream = NULL;
+}
+
+static void stream_state_changed(void *d, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct impl *impl = d;
+ switch (state) {
+ case PW_STREAM_STATE_ERROR:
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_impl_module_schedule_destroy(impl->module);
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ case PW_STREAM_STATE_STREAMING:
+ break;
+ default:
+ break;
+ }
+}
+
+static void capture_stream_process(void *d)
+{
+ struct impl *impl = d;
+ struct pw_buffer *buf;
+ struct spa_data *bd;
+ void *data;
+ uint32_t size;
+
+ if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) {
+ pw_log_debug("out of buffers: %m");
+ return;
+ }
+
+ bd = &buf->buffer->datas[0];
+
+ data = bd->data;
+ size = buf->requested ? buf->requested * impl->frame_size : bd->maxsize;
+
+ /* fill buffer contents here */
+ pw_log_info("fill buffer data %p with up to %u bytes", data, size);
+
+ bd->chunk->size = size;
+ bd->chunk->stride = impl->frame_size;
+ bd->chunk->offset = 0;
+ buf->size = size / impl->frame_size;
+
+ pw_stream_queue_buffer(impl->stream, buf);
+}
+
+static const struct pw_stream_events capture_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = stream_destroy,
+ .state_changed = stream_state_changed,
+ .process = capture_stream_process
+};
+
+static int create_stream(struct impl *impl)
+{
+ int res;
+ uint32_t n_params;
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b;
+
+ impl->stream = pw_stream_new(impl->core, "example source", impl->stream_props);
+ impl->stream_props = NULL;
+
+ if (impl->stream == NULL)
+ return -errno;
+
+ pw_stream_add_listener(impl->stream,
+ &impl->stream_listener,
+ &capture_stream_events, impl);
+
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_format_audio_raw_build(&b,
+ SPA_PARAM_EnumFormat, &impl->info);
+
+ if ((res = pw_stream_connect(impl->stream,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0)
+ return res;
+
+ return 0;
+}
+
+static void core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct impl *impl = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = core_error,
+};
+
+static void core_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->core_listener);
+ impl->core = NULL;
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ .destroy = core_destroy,
+};
+
+static void impl_destroy(struct impl *impl)
+{
+ if (impl->stream)
+ pw_stream_destroy(impl->stream);
+ if (impl->core && impl->do_disconnect)
+ pw_core_disconnect(impl->core);
+
+ pw_properties_free(impl->stream_props);
+ pw_properties_free(impl->props);
+
+ free(impl);
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ impl->unloading = true;
+ spa_hook_remove(&impl->module_listener);
+ impl_destroy(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_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));
+}
+
+static int calc_frame_size(const struct spa_audio_info_raw *info)
+{
+ int res = info->channels;
+ switch (info->format) {
+ case SPA_AUDIO_FORMAT_U8:
+ case SPA_AUDIO_FORMAT_S8:
+ case SPA_AUDIO_FORMAT_ALAW:
+ case SPA_AUDIO_FORMAT_ULAW:
+ return res;
+ case SPA_AUDIO_FORMAT_S16:
+ case SPA_AUDIO_FORMAT_S16_OE:
+ case SPA_AUDIO_FORMAT_U16:
+ return res * 2;
+ case SPA_AUDIO_FORMAT_S24:
+ case SPA_AUDIO_FORMAT_S24_OE:
+ case SPA_AUDIO_FORMAT_U24:
+ return res * 3;
+ case SPA_AUDIO_FORMAT_S24_32:
+ case SPA_AUDIO_FORMAT_S24_32_OE:
+ case SPA_AUDIO_FORMAT_S32:
+ case SPA_AUDIO_FORMAT_S32_OE:
+ case SPA_AUDIO_FORMAT_U32:
+ case SPA_AUDIO_FORMAT_U32_OE:
+ case SPA_AUDIO_FORMAT_F32:
+ case SPA_AUDIO_FORMAT_F32_OE:
+ return res * 4;
+ case SPA_AUDIO_FORMAT_F64:
+ case SPA_AUDIO_FORMAT_F64_OE:
+ return res * 8;
+ default:
+ return 0;
+ }
+}
+
+static void copy_props(struct impl *impl, struct pw_properties *props, const char *key)
+{
+ const char *str;
+ if ((str = pw_properties_get(props, key)) != NULL) {
+ if (pw_properties_get(impl->stream_props, key) == NULL)
+ pw_properties_set(impl->stream_props, key, str);
+ }
+}
+
+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));
+ uint32_t pid = getpid();
+ struct pw_properties *props = NULL;
+ struct impl *impl;
+ const char *str;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args == NULL)
+ args = "";
+
+ props = pw_properties_new_string(args);
+ if (props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+ impl->props = props;
+
+ impl->stream_props = pw_properties_new(NULL, NULL);
+ if (impl->stream_props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+
+ impl->module = module;
+ impl->context = context;
+
+ if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
+
+ if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL)
+ pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Source");
+
+ if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_NAME, "example-source-%u-%u", pid, id);
+ if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_DESCRIPTION,
+ pw_properties_get(props, PW_KEY_NODE_NAME));
+
+ if ((str = pw_properties_get(props, "stream.props")) != NULL)
+ pw_properties_update_string(impl->stream_props, str, strlen(str));
+
+ 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);
+ copy_props(impl, props, PW_KEY_NODE_GROUP);
+ copy_props(impl, props, PW_KEY_NODE_LATENCY);
+ copy_props(impl, props, PW_KEY_NODE_VIRTUAL);
+ copy_props(impl, props, PW_KEY_MEDIA_CLASS);
+
+ parse_audio_info(impl->stream_props, &impl->info);
+
+ impl->frame_size = calc_frame_size(&impl->info);
+ if (impl->frame_size == 0) {
+ res = -EINVAL;
+ pw_log_error( "can't parse audio format");
+ goto error;
+ }
+
+ 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);
+ impl->core = pw_context_connect(impl->context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ impl->do_disconnect = true;
+ }
+ if (impl->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto error;
+ }
+
+ pw_proxy_add_listener((struct pw_proxy*)impl->core,
+ &impl->core_proxy_listener,
+ &core_proxy_events, impl);
+ pw_core_add_listener(impl->core,
+ &impl->core_listener,
+ &core_events, impl);
+
+ if ((res = create_stream(impl)) < 0)
+ goto error;
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+
+error:
+ impl_destroy(impl);
+ return res;
+}
diff --git a/src/modules/module-fallback-sink.c b/src/modules/module-fallback-sink.c
new file mode 100644
index 0000000..9a277d1
--- /dev/null
+++ b/src/modules/module-fallback-sink.c
@@ -0,0 +1,473 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/param/audio/raw.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/i18n.h>
+
+/** \page page_module_fallback_sink PipeWire Module: Fallback Sink
+ *
+ * Fallback sink, which appear dynamically when no other sinks are
+ * present. This is only useful for Pulseaudio compatibility.
+ */
+
+#define NAME "fallback-sink"
+
+#define DEFAULT_SINK_NAME "auto_null"
+#define DEFAULT_SINK_DESCRIPTION _("Dummy Output")
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define MODULE_USAGE ("[ sink.name=<str> ] " \
+ "[ sink.description=<str> ] ")
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Pauli Virtanen <pav@iki.fi>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Dynamically appearing fallback sink" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct bitmap {
+ uint8_t *data;
+ size_t size;
+ size_t items;
+};
+
+struct impl {
+ struct pw_context *context;
+
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_core *core;
+ struct pw_registry *registry;
+ struct pw_proxy *sink;
+
+ struct spa_hook core_listener;
+ struct spa_hook core_proxy_listener;
+ struct spa_hook registry_listener;
+ struct spa_hook sink_listener;
+
+ struct pw_properties *properties;
+
+ struct bitmap sink_ids;
+ struct bitmap fallback_sink_ids;
+
+ int check_seq;
+
+ unsigned int do_disconnect:1;
+ unsigned int scheduled:1;
+};
+
+static int bitmap_add(struct bitmap *map, uint32_t i)
+{
+ const uint32_t pos = (i >> 3);
+ const uint8_t mask = 1 << (i & 0x7);
+
+ if (pos >= map->size) {
+ size_t new_size = map->size + pos + 16;
+ void *p;
+
+ p = realloc(map->data, new_size);
+ if (!p)
+ return -errno;
+
+ memset((uint8_t*)p + map->size, 0, new_size - map->size);
+ map->data = p;
+ map->size = new_size;
+ }
+
+ if (map->data[pos] & mask)
+ return 1;
+
+ map->data[pos] |= mask;
+ ++map->items;
+
+ return 0;
+}
+
+static bool bitmap_remove(struct bitmap *map, uint32_t i)
+{
+ const uint32_t pos = (i >> 3);
+ const uint8_t mask = 1 << (i & 0x7);
+
+ if (pos >= map->size)
+ return false;
+
+ if (!(map->data[pos] & mask))
+ return false;
+
+ map->data[pos] &= ~mask;
+ --map->items;
+
+ return true;
+}
+
+static void bitmap_free(struct bitmap *map)
+{
+ free(map->data);
+ spa_zero(*map);
+}
+
+static int add_id(struct bitmap *map, uint32_t id)
+{
+ int res;
+
+ if (id == SPA_ID_INVALID)
+ return -EINVAL;
+
+ if ((res = bitmap_add(map, id)) < 0)
+ pw_log_error("%s", spa_strerror(res));
+
+ return res;
+}
+
+static void reschedule_check(struct impl *impl)
+{
+ if (!impl->scheduled)
+ return;
+
+ impl->check_seq = pw_core_sync(impl->core, 0, impl->check_seq);
+}
+
+static void schedule_check(struct impl *impl)
+{
+ if (impl->scheduled)
+ return;
+
+ impl->scheduled = true;
+ impl->check_seq = pw_core_sync(impl->core, 0, impl->check_seq);
+}
+
+static void sink_proxy_removed(void *data)
+{
+ struct impl *impl = data;
+
+ pw_proxy_destroy(impl->sink);
+}
+
+static void sink_proxy_bound(void *data, uint32_t id)
+{
+ struct impl *impl = data;
+
+ add_id(&impl->sink_ids, id);
+ add_id(&impl->fallback_sink_ids, id);
+
+ reschedule_check(impl);
+ schedule_check(impl);
+}
+
+static void sink_proxy_destroy(void *data)
+{
+ struct impl *impl = data;
+
+ pw_log_debug("fallback dummy sink destroyed");
+
+ spa_hook_remove(&impl->sink_listener);
+ impl->sink = NULL;
+}
+
+static const struct pw_proxy_events sink_proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .removed = sink_proxy_removed,
+ .bound = sink_proxy_bound,
+ .destroy = sink_proxy_destroy,
+};
+
+static int sink_create(struct impl *impl)
+{
+ if (impl->sink)
+ return 0;
+
+ pw_log_info("creating fallback dummy sink");
+
+ impl->sink = pw_core_create_object(impl->core,
+ "adapter", PW_TYPE_INTERFACE_Node, PW_VERSION_NODE,
+ impl->properties ? &impl->properties->dict : NULL, 0);
+ if (impl->sink == NULL)
+ return -errno;
+
+ pw_proxy_add_listener(impl->sink, &impl->sink_listener, &sink_proxy_events, impl);
+
+ return 0;
+}
+
+static void sink_destroy(struct impl *impl)
+{
+ if (!impl->sink)
+ return;
+
+ pw_log_info("removing fallback dummy sink");
+ pw_proxy_destroy(impl->sink);
+}
+
+static void check_sinks(struct impl *impl)
+{
+ int res;
+
+ pw_log_debug("seeing %zu sink(s), %zu fallback sink(s)",
+ impl->sink_ids.items, impl->fallback_sink_ids.items);
+
+ if (impl->sink_ids.items > impl->fallback_sink_ids.items) {
+ sink_destroy(impl);
+ } else {
+ if ((res = sink_create(impl)) < 0)
+ pw_log_error("error creating sink: %s", spa_strerror(res));
+ }
+}
+
+static void registry_event_global(void *data, uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ struct impl *impl = data;
+ const char *str;
+
+ reschedule_check(impl);
+
+ if (!props)
+ return;
+
+ if (!spa_streq(type, PW_TYPE_INTERFACE_Node))
+ return;
+
+ str = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
+ if (!(spa_streq(str, "Audio/Sink") || spa_streq(str, "Audio/Sink/Virtual")))
+ return;
+
+ add_id(&impl->sink_ids, id);
+ schedule_check(impl);
+}
+
+static void registry_event_global_remove(void *data, uint32_t id)
+{
+ struct impl *impl = data;
+
+ reschedule_check(impl);
+
+ bitmap_remove(&impl->fallback_sink_ids, id);
+ if (bitmap_remove(&impl->sink_ids, id))
+ schedule_check(impl);
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+ .global_remove = registry_event_global_remove,
+};
+
+static void core_done(void *data, uint32_t id, int seq)
+{
+ struct impl *impl = data;
+
+ if (seq == impl->check_seq) {
+ impl->scheduled = false;
+ check_sinks(impl);
+ }
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .done = core_done,
+};
+
+static void core_proxy_removed(void *data)
+{
+ struct impl *impl = data;
+
+ if (impl->registry) {
+ spa_hook_remove(&impl->registry_listener);
+ pw_proxy_destroy((struct pw_proxy*)impl->registry);
+ impl->registry = NULL;
+ }
+}
+
+static void core_proxy_destroy(void *data)
+{
+ struct impl *impl = data;
+
+ spa_hook_remove(&impl->core_listener);
+ spa_hook_remove(&impl->core_proxy_listener);
+ impl->core = NULL;
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .destroy = core_proxy_destroy,
+ .removed = core_proxy_removed,
+};
+
+static void impl_destroy(struct impl *impl)
+{
+ sink_destroy(impl);
+
+ if (impl->registry) {
+ spa_hook_remove(&impl->registry_listener);
+ pw_proxy_destroy((struct pw_proxy*)impl->registry);
+ impl->registry = NULL;
+ }
+
+ if (impl->core) {
+ spa_hook_remove(&impl->core_listener);
+ spa_hook_remove(&impl->core_proxy_listener);
+ if (impl->do_disconnect)
+ pw_core_disconnect(impl->core);
+ impl->core = NULL;
+ }
+
+ if (impl->properties) {
+ pw_properties_free(impl->properties);
+ impl->properties = NULL;
+ }
+
+ bitmap_free(&impl->sink_ids);
+ bitmap_free(&impl->fallback_sink_ids);
+
+ free(impl);
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->module_listener);
+ impl_destroy(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props = NULL;
+ struct impl *impl = NULL;
+ const char *str;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ goto error_errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args == NULL)
+ args = "";
+
+ impl->module = module;
+ impl->context = context;
+
+ props = pw_properties_new_string(args);
+ if (props == NULL)
+ goto error_errno;
+
+ impl->properties = pw_properties_new(NULL, NULL);
+ if (impl->properties == NULL)
+ goto error_errno;
+
+ if ((str = pw_properties_get(props, "sink.name")) == NULL)
+ str = DEFAULT_SINK_NAME;
+ pw_properties_set(impl->properties, PW_KEY_NODE_NAME, str);
+
+ if ((str = pw_properties_get(props, "sink.description")) == NULL)
+ str = DEFAULT_SINK_DESCRIPTION;
+ pw_properties_set(impl->properties, PW_KEY_NODE_DESCRIPTION, str);
+
+ pw_properties_setf(impl->properties, SPA_KEY_AUDIO_RATE, "%u", 48000);
+ pw_properties_setf(impl->properties, SPA_KEY_AUDIO_CHANNELS, "%u", 2);
+ pw_properties_set(impl->properties, SPA_KEY_AUDIO_POSITION, "FL,FR");
+
+ pw_properties_set(impl->properties, PW_KEY_MEDIA_CLASS, "Audio/Sink");
+ pw_properties_set(impl->properties, PW_KEY_FACTORY_NAME, "support.null-audio-sink");
+ pw_properties_set(impl->properties, PW_KEY_NODE_VIRTUAL, "true");
+ pw_properties_set(impl->properties, "monitor.channel-volumes", "true");
+
+ 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);
+ impl->core = pw_context_connect(impl->context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ impl->do_disconnect = true;
+ }
+ if (impl->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto error;
+ }
+
+ pw_proxy_add_listener((struct pw_proxy*)impl->core,
+ &impl->core_proxy_listener, &core_proxy_events,
+ impl);
+
+ pw_core_add_listener(impl->core, &impl->core_listener, &core_events, impl);
+
+ impl->registry = pw_core_get_registry(impl->core,
+ PW_VERSION_REGISTRY, 0);
+ if (impl->registry == NULL)
+ goto error_errno;
+
+ pw_registry_add_listener(impl->registry,
+ &impl->registry_listener,
+ &registry_events, impl);
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ schedule_check(impl);
+
+ pw_properties_free(props);
+ return 0;
+
+error_errno:
+ res = -errno;
+error:
+ if (props)
+ pw_properties_free(props);
+ if (impl)
+ impl_destroy(impl);
+ return res;
+}
diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c
new file mode 100644
index 0000000..fd41d20
--- /dev/null
+++ b/src/modules/module-filter-chain.c
@@ -0,0 +1,2411 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dlfcn.h>
+#include <unistd.h>
+
+#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/pod/dynamic.h>
+#include <spa/debug/types.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
+
+/**
+ * \page page_module_filter_chain PipeWire Module: Filter-Chain
+ *
+ * The filter-chain allows you to create an arbitrary processing graph
+ * from LADSPA, LV2 and builtin filters. This filter can be made into a
+ * virtual sink/source or between any 2 nodes in the graph.
+ *
+ * The filter chain is built with 2 streams, a capture stream providing
+ * the input to the filter chain and a playback stream sending out the
+ * filtered stream to the next nodes in the graph.
+ *
+ * Because both ends of the filter-chain are built with streams, the session
+ * manager can manage the configuration and connection with the sinks and
+ * sources automatically.
+ *
+ * ## Module Options
+ *
+ * - `node.description`: a human readable name for the filter chain
+ * - `filter.graph = []`: a description of the filter graph to run, see below
+ * - `capture.props = {}`: properties to be passed to the input stream
+ * - `playback.props = {}`: properties to be passed to the output stream
+ *
+ * ## Filter graph description
+ *
+ * The general structure of the graph description is as follows:
+ *
+ *\code{.unparsed}
+ * filter.graph = {
+ * nodes = [
+ * {
+ * type = <ladspa | lv2 | builtin>
+ * name = <name>
+ * plugin = <plugin>
+ * label = <label>
+ * config = {
+ * <configkey> = <value> ...
+ * }
+ * control = {
+ * <controlname|controlindex> = <value> ...
+ * }
+ * }
+ * ...
+ * ]
+ * links = [
+ * { output = <portname> input = <portname> }
+ * ...
+ * ]
+ * inputs = [ <portname> ... ]
+ * outputs = [ <portname> ... ]
+ * }
+ *\endcode
+ *
+ * ### Nodes
+ *
+ * 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` or `builtin`
+ * - `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 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 this is the name of the filter to use
+ *
+ * - `config` contains a filter specific configuration section. The convolver
+ * plugin needs this.
+ * - `control` contains the initial values for the control ports of the filter.
+ *
+ * ### Links
+ *
+ * Links can be made between ports of nodes. The `portname` is given as
+ * `<node_name>:<port_name>`.
+ *
+ * You can tee the output of filters to multiple other filters. You need to
+ * use a mixer if you want the output of multiple filters to go into one
+ * filter input port.
+ *
+ * links can be omited when the graph has just 1 filter.
+ *
+ * ### Inputs and Outputs
+ *
+ * These are the entry and exit ports into the graph definition. Their number
+ * defines the number of channels used by the filter-chain.
+ *
+ * The `<portname>` can be `null` when a channel is to be ignored.
+ *
+ * Each input/output in the graph can only be linked to one filter input/output.
+ * You need to use the copy builtin filter if the stream signal needs to be routed
+ * to multiple filters. You need to use the mixer builtin plugin if multiple graph
+ * outputs need to go to one output stream.
+ *
+ * inputs and outputs can be omitted, in which case the filter-chain will use all
+ * inputs from the first filter and all outputs from the last filter node. The
+ * graph will then be duplicated as many times to match the number of input/output
+ * channels of the streams.
+ *
+ * ## Builtin filters
+ *
+ * There are some useful builtin filters available. You select them with the label
+ * of the filter node.
+ *
+ * ### Mixer
+ *
+ * Use the `mixer` plugin if you have multiple input signals that need to be mixed together.
+ *
+ * The mixer plugin has up to 8 input ports labeled "In 1" to "In 8" and each with
+ * a gain control labeled "Gain 1" to "Gain 8". There is an output port labeled
+ * "Out". Unused input ports will be ignoded and not cause overhead.
+ *
+ * ### Copy
+ *
+ * Use the `copy` plugin if you need to copy a stream input signal to multiple filters.
+ *
+ * It has one input port "In" and one output port "Out".
+ *
+ * ### Biquads
+ *
+ * Biquads can be used to do all kinds of filtering. They are also used when creating
+ * equalizers.
+ *
+ * All biquad filters have an input port "In" and an output port "Out". They have
+ * a "Freq", "Q" and "Gain" control. Their meaning depends on the particular biquad that
+ * is used. The following labels can be used:
+ *
+ * - `bq_lowpass` a lowpass filter.
+ * - `bq_highpass` a highpass filter.
+ * - `bq_bandpass` a bandpass filter.
+ * - `bq_lowshelf` a low shelf filter.
+ * - `bq_highshelf` a high shelf filter.
+ * - `bq_peaking` a peaking filter.
+ * - `bq_notch` a notch filter.
+ * - `bq_allpass` an allpass filter.
+ *
+ * ### Convolver
+ *
+ * The convolver can be used to apply an impulse response to a signal. It is usually used
+ * for reverbs or virtual surround. The convolver is implemented with a fast FFT
+ * implementation.
+ *
+ * The convolver has an input port "In" and an output port "Out". It requires a config
+ * section in the node declaration in this format:
+ *
+ *\code{.unparsed}
+ * filter.graph = {
+ * nodes = [
+ * {
+ * type = builtin
+ * name = ...
+ * label = convolver
+ * config = {
+ * blocksize = ...
+ * tailsize = ...
+ * gain = ...
+ * delay = ...
+ * filename = ...
+ * offset = ...
+ * length = ...
+ * channel = ...
+ * }
+ * ...
+ * }
+ * }
+ * ...
+ * }
+ *\endcode
+ *
+ * - `blocksize` specifies the size of the blocks to use in the FFT. It is a value
+ * between 64 and 256. When not specified, this value is
+ * 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.
+ * - `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
+ * `length` will be used as the number of coefficients.
+ * - `/dirac` creates a [Dirac function](https://en.wikipedia.org/wiki/Dirac_delta_function) that
+ * can be used as gain.
+ * - A filename to load as the IR. This needs to be a file format supported
+ * by sndfile.
+ * - `offset` The sample offset in the file as the start of the IR.
+ * - `length` The number of samples to use as the IR.
+ * - `channel` The channel to use from the file as the IR.
+ *
+ * ### Delay
+ *
+ * The delay can be used to delay a signal in time.
+ *
+ * The delay has an input port "In" and an output port "Out". It also has
+ * a "Delay (s)" control port. It requires a config section in the node declaration
+ * in this format:
+ *
+ *\code{.unparsed}
+ * filter.graph = {
+ * nodes = [
+ * {
+ * type = builtin
+ * name = ...
+ * label = delay
+ * config = {
+ * "max-delay" = ...
+ * }
+ * control = {
+ * "Delay (s)" = ...
+ * }
+ * ...
+ * }
+ * }
+ * ...
+ * }
+ *\endcode
+ *
+ * - `max-delay` the maximum delay in seconds. The "Delay (s)" parameter will
+ * be clamped to this value.
+ *
+ * ## General options
+ *
+ * Options with well-known behavior. Most options can be added to the global
+ * configuration or the individual streams:
+ *
+ * - \ref PW_KEY_REMOTE_NAME
+ * - \ref PW_KEY_AUDIO_RATE
+ * - \ref PW_KEY_AUDIO_CHANNELS
+ * - \ref SPA_KEY_AUDIO_POSITION
+ * - \ref PW_KEY_MEDIA_NAME
+ * - \ref PW_KEY_NODE_LATENCY
+ * - \ref PW_KEY_NODE_DESCRIPTION
+ * - \ref PW_KEY_NODE_GROUP
+ * - \ref PW_KEY_NODE_LINK_GROUP
+ * - \ref PW_KEY_NODE_VIRTUAL
+ * - \ref PW_KEY_NODE_NAME: See notes below. If not specified, defaults to
+ * 'filter-chain-<pid>-<module-id>'.
+ *
+ * Stream only properties:
+ *
+ * - \ref PW_KEY_MEDIA_CLASS
+ * - \ref PW_KEY_NODE_NAME: if not given per stream, the global node.name will be
+ * prefixed with 'input.' and 'output.' to generate a capture and playback
+ * stream node.name respectively.
+ *
+ * ## Example configuration of a virtual source
+ *
+ * This example uses the rnnoise LADSPA plugin to create a new
+ * virtual source.
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-filter-chain
+ * args = {
+ * node.description = "Noise Canceling source"
+ * media.name = "Noise Canceling source"
+ * filter.graph = {
+ * nodes = [
+ * {
+ * type = ladspa
+ * name = rnnoise
+ * plugin = ladspa/librnnoise_ladspa
+ * label = noise_suppressor_stereo
+ * control = {
+ * "VAD Threshold (%)" 50.0
+ * }
+ * }
+ * ]
+ * }
+ * capture.props = {
+ * node.name = "capture.rnnoise_source"
+ * node.passive = true
+ * }
+ * playback.props = {
+ * node.name = "rnnoise_source"
+ * media.class = Audio/Source
+ * }
+ * }
+ * }
+ * ]
+ *\endcode
+ *
+ * ## Example configuration of a Dolby Surround encoder virtual Sink
+ *
+ * This example uses the ladpsa surround encoder to encode a 5.1 signal
+ * to a stereo Dolby Surround signal.
+ *
+ *\code{.unparsed}
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-filter-chain
+ * args = {
+ * node.description = "Dolby Surround Sink"
+ * media.name = "Dolby Surround Sink"
+ * filter.graph = {
+ * nodes = [
+ * {
+ * type = builtin
+ * name = mixer
+ * label = mixer
+ * control = { "Gain 1" = 0.5 "Gain 2" = 0.5 }
+ * }
+ * {
+ * type = ladspa
+ * name = enc
+ * plugin = surround_encoder_1401
+ * label = surroundEncoder
+ * }
+ * ]
+ * links = [
+ * { output = "mixer:Out" input = "enc:S" }
+ * ]
+ * inputs = [ "enc:L" "enc:R" "enc:C" null "mixer:In 1" "mixer:In 2" ]
+ * outputs = [ "enc:Lt" "enc:Rt" ]
+ * }
+ * capture.props = {
+ * node.name = "effect_input.dolby_surround"
+ * media.class = Audio/Sink
+ * audio.channels = 6
+ * audio.position = [ FL FR FC LFE SL SR ]
+ * }
+ * playback.props = {
+ * node.name = "effect_output.dolby_surround"
+ * node.passive = true
+ * audio.channels = 2
+ * audio.position = [ FL FR ]
+ * }
+ * }
+ * }
+ * ]
+ *\endcode
+ */
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Create filter chain streams" },
+ { PW_KEY_MODULE_USAGE, " [ remote.name=<remote> ] "
+ "[ node.latency=<latency as fraction> ] "
+ "[ node.description=<description of the nodes> ] "
+ "[ audio.rate=<sample rate> ] "
+ "[ audio.channels=<number of channels> ] "
+ "[ audio.position=<channel map> ] "
+ "filter.graph = [ "
+ " nodes = [ "
+ " { "
+ " type = <ladspa | lv2 | builtin> "
+ " name = <name> "
+ " plugin = <plugin> "
+ " label = <label> "
+ " config = { "
+ " <configkey> = <value> ... "
+ " } "
+ " control = { "
+ " <controlname|controlindex> = <value> ... "
+ " } "
+ " } "
+ " ] "
+ " links = [ "
+ " { output = <portname> input = <portname> } ... "
+ " ] "
+ " inputs = [ <portname> ... ] "
+ " outputs = [ <portname> ... ] "
+ "] "
+ "[ capture.props=<properties> ] "
+ "[ playback.props=<properties> ] " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <getopt.h>
+#include <limits.h>
+#include <math.h>
+
+#include <spa/utils/result.h>
+#include <spa/pod/builder.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/audio/raw.h>
+
+#include <pipewire/pipewire.h>
+
+#define MAX_HNDL 64
+#define MAX_SAMPLES 8192
+
+static float silence_data[MAX_SAMPLES];
+static float discard_data[MAX_SAMPLES];
+
+struct plugin {
+ struct spa_list link;
+ int ref;
+ char type[64];
+ char path[PATH_MAX];
+
+ struct fc_plugin *plugin;
+ struct spa_list descriptor_list;
+};
+
+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;
+ 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;
+};
+
+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 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 impl {
+ struct pw_context *context;
+
+ struct pw_impl_module *module;
+
+ struct spa_hook module_listener;
+
+ struct pw_core *core;
+ struct spa_hook core_proxy_listener;
+ struct spa_hook core_listener;
+
+ struct dsp_ops dsp;
+
+ struct spa_list plugin_list;
+
+ struct pw_properties *capture_props;
+ struct pw_stream *capture;
+ struct spa_hook capture_listener;
+ struct spa_audio_info_raw capture_info;
+
+ struct pw_properties *playback_props;
+ struct pw_stream *playback;
+ struct spa_hook playback_listener;
+ struct spa_audio_info_raw playback_info;
+
+ unsigned int do_disconnect:1;
+
+ long unsigned rate;
+
+ struct graph graph;
+};
+
+static int graph_instantiate(struct graph *graph);
+static void graph_cleanup(struct graph *graph);
+
+
+static void capture_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->capture_listener);
+ impl->capture = NULL;
+}
+
+static void capture_process(void *d)
+{
+ struct impl *impl = d;
+ pw_stream_trigger_process(impl->playback);
+}
+
+static void playback_process(void *d)
+{
+ struct impl *impl = d;
+ struct pw_buffer *in, *out;
+ struct graph *graph = &impl->graph;
+ uint32_t i, j, insize = 0, outsize = 0, n_hndl = graph->n_hndl;
+ int32_t stride = 0;
+ struct graph_port *port;
+ struct spa_data *bd;
+
+ if ((in = pw_stream_dequeue_buffer(impl->capture)) == NULL)
+ pw_log_debug("%p: out of capture buffers: %m", impl);
+
+ if ((out = pw_stream_dequeue_buffer(impl->playback)) == NULL)
+ pw_log_debug("%p: out of playback buffers: %m", impl);
+
+ if (in == NULL || out == NULL)
+ goto done;
+
+ for (i = 0, j = 0; i < in->buffer->n_datas; i++) {
+ uint32_t offs, size;
+
+ bd = &in->buffer->datas[i];
+
+ 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;
+
+ }
+ insize = i == 0 ? size : SPA_MIN(insize, size);
+ stride = SPA_MAX(stride, bd->chunk->stride);
+ }
+ outsize = insize;
+
+ for (i = 0; i < out->buffer->n_datas; i++) {
+ bd = &out->buffer->datas[i];
+
+ outsize = SPA_MIN(outsize, bd->maxsize);
+
+ port = i < graph->n_output ? &graph->output[i] : NULL;
+
+ if (port && port->desc)
+ port->desc->connect_port(*port->hndl, port->port, bd->data);
+ else
+ memset(bd->data, 0, outsize);
+
+ bd->chunk->offset = 0;
+ bd->chunk->size = outsize;
+ bd->chunk->stride = stride;
+ }
+
+ pw_log_trace_fp("%p: stride:%d in:%d out:%d requested:%"PRIu64" (%"PRIu64")", impl,
+ stride, insize, outsize, out->requested, out->requested * stride);
+
+ for (i = 0; i < n_hndl; i++) {
+ struct graph_hndl *hndl = &graph->hndl[i];
+ hndl->desc->run(*hndl->hndl, outsize / sizeof(float));
+ }
+
+done:
+ if (in != NULL)
+ pw_stream_queue_buffer(impl->capture, in);
+ if (out != NULL)
+ 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 : 48000;
+
+ 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, def);
+ } else {
+ spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_Range, 0);
+ spa_pod_builder_int(b, def);
+ spa_pod_builder_int(b, min);
+ spa_pod_builder_int(b, 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.0f ? false : true);
+ } else if (p->hint & FC_HINT_INTEGER) {
+ spa_pod_builder_int(b, port->control_data);
+ } else {
+ spa_pod_builder_float(b, port->control_data);
+ }
+ }
+ spa_pod_builder_pop(b, &f[1]);
+ return spa_pod_builder_pop(b, &f[0]);
+}
+
+static int set_control_value(struct node *node, const char *name, float *value)
+{
+ struct descriptor *desc;
+ struct port *port;
+ float old;
+
+ port = find_port(node, name, FC_PORT_INPUT | FC_PORT_CONTROL);
+ if (port == NULL)
+ return -ENOENT;
+
+ node = port->node;
+ desc = node->desc;
+
+ old = port->control_data;
+ port->control_data = value ? *value : desc->default_control[port->idx];
+ pw_log_info("control %d ('%s') from %f to %f", port->idx, name, old, port->control_data);
+ return old == port->control_data ? 0 : 1;
+}
+
+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 = 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 param_props_changed(struct impl *impl, const struct spa_pod *param)
+{
+ struct spa_pod_object *obj = (struct spa_pod_object *) param;
+ const struct spa_pod_prop *prop;
+ struct graph *graph = &impl->graph;
+ int changed = 0;
+
+ SPA_POD_OBJECT_FOREACH(obj, prop) {
+ if (prop->key == SPA_PROP_params)
+ changed += parse_params(graph, &prop->value);
+ }
+ if (changed > 0) {
+ 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 void param_latency_changed(struct impl *impl, const struct spa_pod *param)
+{
+ struct spa_latency_info latency;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b;
+ const struct spa_pod *params[1];
+
+ if (spa_latency_parse(param, &latency) < 0)
+ return;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &latency);
+
+ if (latency.direction == SPA_DIRECTION_INPUT)
+ pw_stream_update_params(impl->capture, params, 1);
+ else
+ pw_stream_update_params(impl->playback, params, 1);
+}
+
+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;
+
+ switch (state) {
+ case PW_STREAM_STATE_PAUSED:
+ pw_stream_flush(impl->playback, false);
+ pw_stream_flush(impl->capture, false);
+ graph_reset(graph);
+ break;
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_log_info("module %p: unconnected", impl);
+ pw_impl_module_schedule_destroy(impl->module);
+ break;
+ case PW_STREAM_STATE_ERROR:
+ pw_log_info("module %p: error: %s", impl, error);
+ break;
+ default:
+ break;
+ }
+}
+
+static void param_changed(void *data, uint32_t id, const struct spa_pod *param)
+{
+ struct impl *impl = data;
+ struct graph *graph = &impl->graph;
+ int res;
+
+ switch (id) {
+ case SPA_PARAM_Format:
+ if (param == NULL) {
+ graph_cleanup(graph);
+ } else {
+ struct spa_audio_info_raw info;
+ spa_zero(info);
+ if ((res = spa_format_audio_raw_parse(param, &info)) < 0)
+ goto error;
+ if (info.rate == 0) {
+ res = -EINVAL;
+ goto error;
+ }
+ impl->rate = info.rate;
+ if ((res = graph_instantiate(graph)) < 0)
+ goto error;
+ }
+ break;
+ case SPA_PARAM_Props:
+ if (param != NULL)
+ param_props_changed(impl, param);
+ break;
+ case SPA_PARAM_Latency:
+ param_latency_changed(impl, param);
+ break;
+ }
+ return;
+
+error:
+ pw_stream_set_error(impl->capture, res, "can't start graph: %s",
+ spa_strerror(res));
+}
+
+static const struct pw_stream_events in_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = capture_destroy,
+ .process = capture_process,
+ .state_changed = state_changed,
+ .param_changed = param_changed
+};
+
+static void playback_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->playback_listener);
+ impl->playback = NULL;
+}
+
+static const struct pw_stream_events out_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = playback_destroy,
+ .process = playback_process,
+ .state_changed = state_changed,
+ .param_changed = param_changed
+};
+
+static int setup_streams(struct impl *impl)
+{
+ int res;
+ uint32_t i, n_params, *offs;
+ struct pw_array offsets;
+ const struct spa_pod **params = NULL;
+ struct spa_pod_dynamic_builder b;
+ struct graph *graph = &impl->graph;
+
+ impl->capture = pw_stream_new(impl->core,
+ "filter capture", impl->capture_props);
+ impl->capture_props = NULL;
+ if (impl->capture == NULL)
+ return -errno;
+
+ pw_stream_add_listener(impl->capture,
+ &impl->capture_listener,
+ &in_stream_events, impl);
+
+ impl->playback = pw_stream_new(impl->core,
+ "filter playback", impl->playback_props);
+ impl->playback_props = NULL;
+ if (impl->playback == NULL)
+ return -errno;
+
+ pw_stream_add_listener(impl->playback,
+ &impl->playback_listener,
+ &out_stream_events, impl);
+
+ spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
+ pw_array_init(&offsets, 512);
+
+ if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) == NULL) {
+ res = -errno;
+ goto done;
+ }
+ *offs = b.b.state.offset;
+ spa_format_audio_raw_build(&b.b,
+ SPA_PARAM_EnumFormat, &impl->capture_info);
+
+ for (i = 0; i < graph->n_control; i++) {
+ if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL)
+ *offs = b.b.state.offset;
+ get_prop_info(graph, &b.b, i);
+ }
+
+ if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL)
+ *offs = b.b.state.offset;
+ get_props_param(graph, &b.b);
+
+ n_params = pw_array_get_len(&offsets, uint32_t);
+ if (n_params == 0) {
+ res = -ENOMEM;
+ goto done;
+ }
+ if ((params = calloc(n_params, sizeof(struct spa_pod*))) == NULL) {
+ res = -errno;
+ goto done;
+ }
+
+ offs = offsets.data;
+ for (i = 0; i < n_params; i++)
+ params[i] = spa_pod_builder_deref(&b.b, offs[i]);
+
+ res = pw_stream_connect(impl->capture,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params);
+
+ spa_pod_dynamic_builder_clean(&b);
+ if (res < 0)
+ goto done;
+
+ n_params = 0;
+ spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
+ params[n_params++] = spa_format_audio_raw_build(&b.b,
+ SPA_PARAM_EnumFormat, &impl->playback_info);
+
+ res = pw_stream_connect(impl->playback,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS |
+ PW_STREAM_FLAG_TRIGGER,
+ params, n_params);
+ spa_pod_dynamic_builder_clean(&b);
+
+done:
+ free(params);
+ pw_array_clear(&offsets);
+
+ return res < 0 ? res : 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)
+{
+ if (--hndl->ref > 0)
+ return;
+
+ fc_plugin_free(hndl->plugin);
+
+ spa_list_remove(&hndl->link);
+ free(hndl);
+}
+
+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;
+
+ 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);
+
+ if (spa_streq(type, "builtin")) {
+ pl = load_builtin_plugin(support, n_support, &impl->dsp, path, NULL);
+ }
+ else if (spa_streq(type, "ladspa")) {
+ pl = load_ladspa_plugin(support, n_support, &impl->dsp, path, NULL);
+ }
+ else if (spa_streq(type, "lv2")) {
+#ifdef HAVE_LILV
+ pl = load_lv2_plugin(support, n_support, &impl->dsp, path, NULL);
+#else
+ pw_log_error("filter-chain is compiled without lv2 support");
+ pl = NULL;
+ errno = ENOTSUP;
+#endif
+ } else {
+ pw_log_error("invalid plugin type '%s'", type);
+ pl = NULL;
+ errno = EINVAL;
+ }
+ 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'", 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;
+ }
+ }
+
+ 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)
+{
+ const char *val;
+ int len;
+
+ if ((len = spa_json_next(config, &val)) <= 0)
+ return len;
+
+ if (spa_json_is_null(val, len))
+ return 0;
+
+ if (spa_json_is_container(val, len))
+ len = spa_json_container_len(config, val, len);
+
+ if ((node->config = malloc(len+1)) == NULL)
+ return -errno;
+
+ spa_json_parse_stringn(val, len, node->config, len+1);
+
+ return 0;
+}
+
+/**
+ * {
+ * "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_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 if (spa_json_next(json, &val) < 0)
+ break;
+ }
+ def_node = spa_list_first(&graph->node_list, struct node, link);
+ if ((out_port = find_port(def_node, output, FC_PORT_OUTPUT)) == NULL) {
+ pw_log_error("unknown output port %s", output);
+ return -ENOENT;
+ }
+ def_node = spa_list_last(&graph->node_list, struct node, link);
+ if ((in_port = find_port(def_node, input, FC_PORT_INPUT)) == 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);
+}
+
+/**
+ * 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 if (spa_json_next(json, &val) < 0)
+ break;
+ }
+
+ if (spa_streq(type, "builtin"))
+ snprintf(plugin, sizeof(plugin), "%s", "builtin");
+
+ 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 = 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;
+ 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)
+{
+ float *data;
+ if ((data = port->audio_data[i]) == NULL) {
+ data = calloc(1, MAX_SAMPLES * sizeof(float));
+ if (data == NULL) {
+ pw_log_error("cannot create port data: %m");
+ return -errno;
+ }
+ }
+ port->audio_data[i] = data;
+ 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);
+}
+
+static void graph_cleanup(struct graph *graph)
+{
+ struct node *node;
+ 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;
+ uint32_t i, j;
+ int res;
+
+ spa_list_for_each(node, &graph->node_list, link) {
+ float *sd = silence_data, *dd = discard_data;
+
+ node_cleanup(node);
+
+ desc = node->desc;
+ d = desc->desc;
+ if (d->flags & FC_DESCRIPTOR_SUPPORTS_NULL_DATA)
+ sd = dd = NULL;
+
+ for (i = 0; i < node->n_hndl; i++) {
+ pw_log_info("instantiate %s %d rate:%lu", d->name, i, impl->rate);
+ if ((node->hndl[i] = d->instantiate(d, impl->rate, i, node->config)) == NULL) {
+ pw_log_error("cannot create plugin instance: %m");
+ res = -errno;
+ goto error;
+ }
+ for (j = 0; j < desc->n_input; j++) {
+ port = &node->input_port[j];
+ d->connect_port(node->hndl[i], port->p, sd);
+
+ spa_list_for_each(link, &port->link_list, input_link) {
+ struct port *peer = link->output;
+ if ((res = port_ensure_data(peer, i)) < 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)) < 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);
+ }
+ for (j = 0; j < desc->n_notify; j++) {
+ port = &node->notify_port[j];
+ d->connect_port(node->hndl[i], port->p, &port->control_data);
+ }
+ if (d->activate)
+ d->activate(node->hndl[i]);
+ }
+ }
+ return 0;
+error:
+ graph_cleanup(graph);
+ return res;
+}
+
+static 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;
+ }
+
+ if (impl->capture_info.channels == 0)
+ impl->capture_info.channels = n_input;
+ 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++;
+ }
+ 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 {
+ 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;
+ }
+ if (gp != NULL)
+ gp->next = false;
+ }
+ port->node->disabled = true;
+ } else {
+ pw_log_info("input port %s[%d]:%s",
+ port->node->name, i, d->ports[port->p].name);
+ port->external = graph->n_input;
+ gp = &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;
+
+ 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--;
+ }
+
+ /* 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 = [ ]
+ * }
+ */
+static int load_graph(struct graph *graph, struct pw_properties *props)
+{
+ struct spa_json it[3];
+ struct spa_json inputs, outputs, *pinputs = NULL, *poutputs = 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);
+
+ if ((json = pw_properties_get(props, "filter.graph")) == NULL) {
+ pw_log_error("missing filter.graph property");
+ return -EINVAL;
+ }
+
+ 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;
+ }
+
+ 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_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;
+ }
+ }
+ 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 void core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct impl *impl = data;
+
+ if (res == -ENOENT) {
+ pw_log_info("message id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+ } else {
+ pw_log_warn("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+ }
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = core_error,
+};
+
+static void core_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->core_listener);
+ impl->core = NULL;
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ .destroy = core_destroy,
+};
+
+static void impl_destroy(struct impl *impl)
+{
+ /* disconnect both streams before destroying any of them */
+ if (impl->capture)
+ pw_stream_disconnect(impl->capture);
+ if (impl->playback)
+ pw_stream_disconnect(impl->playback);
+
+ if (impl->capture)
+ pw_stream_destroy(impl->capture);
+ if (impl->playback)
+ pw_stream_destroy(impl->playback);
+
+ if (impl->core && impl->do_disconnect)
+ pw_core_disconnect(impl->core);
+
+ pw_properties_free(impl->capture_props);
+ pw_properties_free(impl->playback_props);
+ graph_free(&impl->graph);
+ free(impl);
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->module_listener);
+ impl_destroy(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_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));
+}
+
+static void copy_props(struct impl *impl, struct pw_properties *props, const char *key)
+{
+ const char *str;
+ if ((str = pw_properties_get(props, key)) != NULL) {
+ if (pw_properties_get(impl->capture_props, key) == NULL)
+ pw_properties_set(impl->capture_props, key, str);
+ if (pw_properties_get(impl->playback_props, key) == NULL)
+ pw_properties_set(impl->playback_props, key, str);
+ }
+}
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props;
+ struct impl *impl;
+ 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;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args)
+ props = pw_properties_new_string(args);
+ else
+ props = pw_properties_new(NULL, NULL);
+
+ if (props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+
+ impl->capture_props = pw_properties_new(NULL, NULL);
+ impl->playback_props = pw_properties_new(NULL, NULL);
+ if (impl->capture_props == NULL || impl->playback_props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+
+ impl->module = module;
+ impl->context = context;
+ impl->graph.impl = impl;
+
+ spa_list_init(&impl->plugin_list);
+
+ support = pw_context_get_support(impl->context, &n_support);
+
+ cpu_iface = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU);
+ impl->dsp.cpu_flags = cpu_iface ? spa_cpu_get_flags(cpu_iface) : 0;
+ dsp_ops_init(&impl->dsp);
+
+ if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_GROUP, "filter-chain-%u-%u", pid, id);
+ if (pw_properties_get(props, PW_KEY_NODE_LINK_GROUP) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_LINK_GROUP, "filter-chain-%u-%u", pid, id);
+ if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
+ if (pw_properties_get(props, "resample.prefill") == NULL)
+ pw_properties_set(props, "resample.prefill", "true");
+ if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "filter-chain-%u-%u", pid, id);
+
+ if ((str = pw_properties_get(props, "capture.props")) != NULL)
+ pw_properties_update_string(impl->capture_props, str, strlen(str));
+ if ((str = pw_properties_get(props, "playback.props")) != NULL)
+ pw_properties_update_string(impl->playback_props, str, strlen(str));
+
+ 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_DESCRIPTION);
+ copy_props(impl, props, PW_KEY_NODE_GROUP);
+ copy_props(impl, props, PW_KEY_NODE_LINK_GROUP);
+ copy_props(impl, props, PW_KEY_NODE_LATENCY);
+ copy_props(impl, props, PW_KEY_NODE_VIRTUAL);
+ copy_props(impl, props, PW_KEY_MEDIA_NAME);
+ copy_props(impl, props, "resample.prefill");
+
+ parse_audio_info(impl->capture_props, &impl->capture_info);
+ parse_audio_info(impl->playback_props, &impl->playback_info);
+
+ if (impl->capture_info.rate && !impl->playback_info.rate)
+ impl->playback_info.rate = impl->capture_info.rate;
+ else if (impl->playback_info.rate && !impl->capture_info.rate)
+ impl->capture_info.rate = !impl->playback_info.rate;
+ else if (impl->capture_info.rate != impl->playback_info.rate) {
+ pw_log_warn("Both capture and playback rate are set, but"
+ " they are different. Using the highest of two. This behaviour"
+ " is deprecated, please use equal rates in the module config");
+ impl->playback_info.rate = impl->capture_info.rate =
+ SPA_MAX(impl->playback_info.rate, impl->capture_info.rate);
+ }
+
+ if ((str = pw_properties_get(props, PW_KEY_NODE_NAME)) == NULL) {
+ pw_properties_setf(props, PW_KEY_NODE_NAME,
+ "filter-chain-%u-%u", pid, id);
+ str = pw_properties_get(props, PW_KEY_NODE_NAME);
+ }
+ if (pw_properties_get(impl->capture_props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_setf(impl->capture_props, PW_KEY_NODE_NAME,
+ "input.%s", str);
+ if (pw_properties_get(impl->playback_props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_setf(impl->playback_props, PW_KEY_NODE_NAME,
+ "output.%s", str);
+
+ if (pw_properties_get(impl->capture_props, PW_KEY_MEDIA_NAME) == NULL)
+ pw_properties_setf(impl->capture_props, PW_KEY_MEDIA_NAME, "%s input",
+ pw_properties_get(impl->capture_props, PW_KEY_NODE_DESCRIPTION));
+ if (pw_properties_get(impl->playback_props, PW_KEY_MEDIA_NAME) == NULL)
+ 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));
+ goto error;
+ }
+
+ 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);
+ impl->core = pw_context_connect(impl->context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ impl->do_disconnect = true;
+ }
+ if (impl->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto error;
+ }
+ pw_properties_free(props);
+
+ pw_proxy_add_listener((struct pw_proxy*)impl->core,
+ &impl->core_proxy_listener,
+ &core_proxy_events, impl);
+ pw_core_add_listener(impl->core,
+ &impl->core_listener,
+ &core_events, impl);
+
+ setup_streams(impl);
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+
+error:
+ pw_properties_free(props);
+ impl_destroy(impl);
+ return res;
+}
diff --git a/src/modules/module-filter-chain/biquad.c b/src/modules/module-filter-chain/biquad.c
new file mode 100644
index 0000000..58c8dd0
--- /dev/null
+++ b/src/modules/module-filter-chain/biquad.c
@@ -0,0 +1,364 @@
+/* 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 = b0 * a0_inv;
+ bq->b1 = b1 * a0_inv;
+ bq->b2 = b2 * a0_inv;
+ bq->a1 = a1 * a0_inv;
+ bq->a2 = 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/biquad.h b/src/modules/module-filter-chain/biquad.h
new file mode 100644
index 0000000..650b263
--- /dev/null
+++ b/src/modules/module-filter-chain/biquad.h
@@ -0,0 +1,57 @@
+/* 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.
+ */
+
+#ifndef BIQUAD_H_
+#define BIQUAD_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* 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
+ * y2.
+ *
+ * We use double during the coefficients calculation for better accuracy, but
+ * float is used during the actual filtering for faster computation.
+ */
+struct biquad {
+ 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.
+ * Args:
+ * bq - The biquad filter we want to set.
+ * 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, double Q,
+ double gain);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* BIQUAD_H_ */
diff --git a/src/modules/module-filter-chain/builtin_plugin.c b/src/modules/module-filter-chain/builtin_plugin.c
new file mode 100644
index 0000000..068df99
--- /dev/null
+++ b/src/modules/module-filter-chain/builtin_plugin.c
@@ -0,0 +1,1035 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <float.h>
+#include <math.h>
+#ifdef HAVE_SNDFILE
+#include <sndfile.h>
+#endif
+
+#include <spa/utils/json.h>
+#include <spa/utils/result.h>
+#include <spa/support/cpu.h>
+#include <spa/plugins/audioconvert/resample.h>
+
+#include <pipewire/log.h>
+
+#include "plugin.h"
+
+#include "biquad.h"
+#include "pffft.h"
+#include "convolver.h"
+#include "dsp-ops.h"
+
+#define MAX_RATES 32u
+
+static struct dsp_ops *dsp_ops;
+
+struct builtin {
+ unsigned long rate;
+ float *port[64];
+
+ struct biquad bq;
+ float freq;
+ float Q;
+ float gain;
+};
+
+static void *builtin_instantiate(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->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(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(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,
+};
+
+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,
+ },
+};
+
+static void bq_run(struct builtin *impl, unsigned long samples, int type)
+{
+ struct biquad *bq = &impl->bq;
+ float *out = impl->port[0];
+ float *in = impl->port[1];
+ float freq = impl->port[2][0];
+ float Q = impl->port[3][0];
+ float gain = impl->port[4][0];
+
+ if (impl->freq != freq || impl->Q != Q || impl->gain != gain) {
+ impl->freq = freq;
+ impl->Q = Q;
+ impl->gain = gain;
+ biquad_set(bq, type, freq * 2 / impl->rate, Q, gain);
+ }
+ dsp_ops_biquad_run(dsp_ops, bq, out, in, samples);
+}
+
+/** bq_lowpass */
+static void bq_lowpass_run(void * Instance, unsigned long SampleCount)
+{
+ struct builtin *impl = Instance;
+ bq_run(impl, SampleCount, BQ_LOWPASS);
+}
+
+static const struct fc_descriptor bq_lowpass_desc = {
+ .name = "bq_lowpass",
+
+ .n_ports = 5,
+ .ports = bq_ports,
+
+ .instantiate = builtin_instantiate,
+ .connect_port = builtin_connect_port,
+ .run = bq_lowpass_run,
+ .cleanup = builtin_cleanup,
+};
+
+/** bq_highpass */
+static void bq_highpass_run(void * Instance, unsigned long SampleCount)
+{
+ struct builtin *impl = Instance;
+ bq_run(impl, SampleCount, BQ_HIGHPASS);
+}
+
+static const struct fc_descriptor bq_highpass_desc = {
+ .name = "bq_highpass",
+
+ .n_ports = 5,
+ .ports = bq_ports,
+
+ .instantiate = builtin_instantiate,
+ .connect_port = builtin_connect_port,
+ .run = bq_highpass_run,
+ .cleanup = builtin_cleanup,
+};
+
+/** bq_bandpass */
+static void bq_bandpass_run(void * Instance, unsigned long SampleCount)
+{
+ struct builtin *impl = Instance;
+ bq_run(impl, SampleCount, BQ_BANDPASS);
+}
+
+static const struct fc_descriptor bq_bandpass_desc = {
+ .name = "bq_bandpass",
+
+ .n_ports = 5,
+ .ports = bq_ports,
+
+ .instantiate = builtin_instantiate,
+ .connect_port = builtin_connect_port,
+ .run = bq_bandpass_run,
+ .cleanup = builtin_cleanup,
+};
+
+/** bq_lowshelf */
+static void bq_lowshelf_run(void * Instance, unsigned long SampleCount)
+{
+ struct builtin *impl = Instance;
+ bq_run(impl, SampleCount, BQ_LOWSHELF);
+}
+
+static const struct fc_descriptor bq_lowshelf_desc = {
+ .name = "bq_lowshelf",
+
+ .n_ports = 5,
+ .ports = bq_ports,
+
+ .instantiate = builtin_instantiate,
+ .connect_port = builtin_connect_port,
+ .run = bq_lowshelf_run,
+ .cleanup = builtin_cleanup,
+};
+
+/** bq_highshelf */
+static void bq_highshelf_run(void * Instance, unsigned long SampleCount)
+{
+ struct builtin *impl = Instance;
+ bq_run(impl, SampleCount, BQ_HIGHSHELF);
+}
+
+static const struct fc_descriptor bq_highshelf_desc = {
+ .name = "bq_highshelf",
+
+ .n_ports = 5,
+ .ports = bq_ports,
+
+ .instantiate = builtin_instantiate,
+ .connect_port = builtin_connect_port,
+ .run = bq_highshelf_run,
+ .cleanup = builtin_cleanup,
+};
+
+/** bq_peaking */
+static void bq_peaking_run(void * Instance, unsigned long SampleCount)
+{
+ struct builtin *impl = Instance;
+ bq_run(impl, SampleCount, BQ_PEAKING);
+}
+
+static const struct fc_descriptor bq_peaking_desc = {
+ .name = "bq_peaking",
+
+ .n_ports = 5,
+ .ports = bq_ports,
+
+ .instantiate = builtin_instantiate,
+ .connect_port = builtin_connect_port,
+ .run = bq_peaking_run,
+ .cleanup = builtin_cleanup,
+};
+
+/** bq_notch */
+static void bq_notch_run(void * Instance, unsigned long SampleCount)
+{
+ struct builtin *impl = Instance;
+ bq_run(impl, SampleCount, BQ_NOTCH);
+}
+
+static const struct fc_descriptor bq_notch_desc = {
+ .name = "bq_notch",
+
+ .n_ports = 5,
+ .ports = bq_ports,
+
+ .instantiate = builtin_instantiate,
+ .connect_port = builtin_connect_port,
+ .run = bq_notch_run,
+ .cleanup = builtin_cleanup,
+};
+
+
+/** bq_allpass */
+static void bq_allpass_run(void * Instance, unsigned long SampleCount)
+{
+ struct builtin *impl = Instance;
+ bq_run(impl, SampleCount, BQ_ALLPASS);
+}
+
+static const struct fc_descriptor bq_allpass_desc = {
+ .name = "bq_allpass",
+
+ .n_ports = 5,
+ .ports = bq_ports,
+
+ .instantiate = builtin_instantiate,
+ .connect_port = builtin_connect_port,
+ .run = bq_allpass_run,
+ .cleanup = builtin_cleanup,
+};
+
+/** convolve */
+struct convolver_impl {
+ 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;
+
+ 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])
+ 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);
+ }
+ }
+
+ pw_log_debug("loading %s", filenames[best]);
+ float *samples = read_samples_from_sf(fs[best], infos[best], gain, delay,
+ offset, length, channel, rate, n_samples);
+
+ for (i = 0; i < MAX_RATES; i++)
+ if (fs[i])
+ 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 / M_PI;
+ h = length / 2;
+ for (i = 1; i < h; i += 2) {
+ v = (gain / i) * (0.43f + 0.57f * cosf(i * 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(float *samples, int *n_samples,
+ unsigned long in_rate, unsigned long out_rate, uint32_t quality)
+{
+ uint32_t in_len, out_len, total_out = 0;
+ int out_n_samples;
+ float *out_samples, *out_buf, *in_buf;
+ struct resample r;
+ int res;
+
+ spa_zero(r);
+ r.channels = 1;
+ r.i_rate = in_rate;
+ r.o_rate = out_rate;
+ r.cpu_flags = dsp_ops->cpu_flags;
+ r.quality = quality;
+ if ((res = resample_native_init(&r)) < 0) {
+ pw_log_error("resampling failed: %s", spa_strerror(res));
+ errno = -res;
+ return NULL;
+ }
+
+ out_n_samples = SPA_ROUND_UP(*n_samples * out_rate, in_rate) / in_rate;
+ out_samples = calloc(out_n_samples, sizeof(float));
+ if (out_samples == NULL)
+ goto error;
+
+ in_len = *n_samples;
+ in_buf = samples;
+ out_len = out_n_samples;
+ out_buf = out_samples;
+
+ pw_log_info("Resampling filter: rate: %lu => %lu, n_samples: %u => %u, q:%u",
+ in_rate, out_rate, in_len, out_len, quality);
+
+ resample_process(&r, (void*)&in_buf, &in_len, (void*)&out_buf, &out_len);
+ pw_log_debug("resampled: %u -> %u samples", in_len, out_len);
+ total_out += out_len;
+
+ in_len = resample_delay(&r);
+ in_buf = calloc(in_len, sizeof(float));
+ if (in_buf == NULL)
+ goto error;
+
+ out_buf = out_samples + total_out;
+ out_len = out_n_samples - total_out;
+
+ pw_log_debug("flushing resampler: %u in %u out", in_len, out_len);
+ resample_process(&r, (void*)&in_buf, &in_len, (void*)&out_buf, &out_len);
+ pw_log_debug("flushed: %u -> %u samples", in_len, out_len);
+ total_out += out_len;
+
+ free(in_buf);
+ free(samples);
+ resample_free(&r);
+
+ *n_samples = total_out;
+
+ 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;
+}
+
+static void * convolver_instantiate(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, 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)
+ return NULL;
+
+ spa_json_init(&it[0], config, strlen(config));
+ if (spa_json_enter_object(&it[0], &it[1]) <= 0)
+ 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[i] = 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 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)
+ samples = resample_buffer(samples, &n_samples,
+ rate, SampleRate, resample_quality);
+ }
+ if (samples == NULL) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ for (i = 0; i < MAX_RATES; i++)
+ if (filenames[i])
+ free(filenames[i]);
+
+ 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->rate = SampleRate;
+
+ impl->conv = convolver_new(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 {
+ 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_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) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ spa_json_init(&it[0], config, strlen(config));
+ if (spa_json_enter_object(&it[0], &it[1]) <= 0)
+ 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 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->rate = SampleRate;
+ impl->buffer_samples = 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(delay * impl->rate, 0, 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,
+};
+
+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 &copy_desc;
+ case 10:
+ return &convolve_desc;
+ case 11:
+ return &delay_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 struct fc_plugin builtin_plugin = {
+ .make_desc = builtin_make_desc
+};
+
+struct fc_plugin *load_builtin_plugin(const struct spa_support *support, uint32_t n_support,
+ struct dsp_ops *dsp, const char *plugin, const char *config)
+{
+ dsp_ops = dsp;
+ pffft_select_cpu(dsp->cpu_flags);
+ return &builtin_plugin;
+}
diff --git a/src/modules/module-filter-chain/convolver.c b/src/modules/module-filter-chain/convolver.c
new file mode 100644
index 0000000..6bed5b1
--- /dev/null
+++ b/src/modules/module-filter-chain/convolver.c
@@ -0,0 +1,425 @@
+/* PipeWire
+ *
+ * Copyright (c) 2017 HiFi-LoFi
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Adapted from https://github.com/HiFi-LoFi/FFTConvolver
+ */
+#include "convolver.h"
+
+#include <spa/utils/defs.h>
+
+#include <math.h>
+
+static struct dsp_ops *dsp;
+
+struct convolver1 {
+ int blockSize;
+ int segSize;
+ int segCount;
+ int fftComplexSize;
+
+ float **segments;
+ float **segmentsIr;
+
+ float *fft_buffer;
+
+ void *fft;
+ void *ifft;
+
+ float *pre_mult;
+ float *conv;
+ float *overlap;
+
+ float *inputBuffer;
+ int inputBufferFill;
+
+ int current;
+ 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(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;
+ while (r < val)
+ r *= 2;
+ return r;
+}
+
+static void convolver1_reset(struct convolver1 *conv)
+{
+ int i;
+ for (i = 0; i < conv->segCount; i++)
+ fft_cpx_clear(conv->segments[i], conv->fftComplexSize);
+ dsp_ops_clear(dsp, conv->overlap, conv->blockSize);
+ dsp_ops_clear(dsp, conv->inputBuffer, conv->segSize);
+ fft_cpx_clear(conv->pre_mult, conv->fftComplexSize);
+ fft_cpx_clear(conv->conv, conv->fftComplexSize);
+ conv->inputBufferFill = 0;
+ conv->current = 0;
+}
+
+static struct convolver1 *convolver1_new(int block, const float *ir, int irlen)
+{
+ struct convolver1 *conv;
+ int i;
+
+ if (block == 0)
+ return NULL;
+
+ while (irlen > 0 && fabs(ir[irlen-1]) < 0.000001f)
+ irlen--;
+
+ conv = calloc(1, sizeof(*conv));
+ if (conv == NULL)
+ return NULL;
+
+ if (irlen == 0)
+ return conv;
+
+ 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(dsp, conv->segSize, true);
+ if (conv->fft == NULL)
+ goto error;
+ conv->ifft = dsp_ops_fft_new(dsp, conv->segSize, true);
+ if (conv->ifft == NULL)
+ goto error;
+
+ conv->fft_buffer = fft_alloc(conv->segSize);
+ if (conv->fft_buffer == NULL)
+ goto error;
+
+ conv->segments = calloc(sizeof(float*), conv->segCount);
+ conv->segmentsIr = calloc(sizeof(float*), conv->segCount);
+
+ 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);
+
+ dsp_ops_copy(dsp, conv->fft_buffer, &ir[i * conv->blockSize], copy);
+ if (copy < conv->segSize)
+ dsp_ops_clear(dsp, conv->fft_buffer + copy, conv->segSize - copy);
+
+ dsp_ops_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->scale = 1.0f / conv->segSize;
+ convolver1_reset(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);
+ 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(dsp, conv->fft);
+ if (conv->ifft)
+ dsp_ops_fft_free(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)
+{
+ int i, processed = 0;
+
+ if (conv == NULL || conv->segCount == 0) {
+ dsp_ops_clear(dsp, output, len);
+ return len;
+ }
+
+ while (processed < len) {
+ const int processing = SPA_MIN(len - processed, conv->blockSize - conv->inputBufferFill);
+ const int inputBufferPos = conv->inputBufferFill;
+
+ dsp_ops_copy(dsp, conv->inputBuffer + inputBufferPos, input + processed, processing);
+ if (inputBufferPos == 0 && processing < conv->blockSize)
+ dsp_ops_clear(dsp, conv->inputBuffer + processing, conv->blockSize - processing);
+
+ dsp_ops_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(dsp, conv->fft, conv->pre_mult,
+ conv->segmentsIr[1],
+ conv->segments[indexAudio],
+ conv->fftComplexSize, conv->scale);
+
+ for (i = 2; i < conv->segCount; i++) {
+ indexAudio = (conv->current + i) % conv->segCount;
+
+ dsp_ops_fft_cmuladd(dsp, conv->fft,
+ conv->pre_mult,
+ conv->pre_mult,
+ conv->segmentsIr[i],
+ conv->segments[indexAudio],
+ conv->fftComplexSize, conv->scale);
+ }
+ }
+ dsp_ops_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(dsp, conv->fft,
+ conv->conv,
+ conv->segments[conv->current],
+ conv->segmentsIr[0],
+ conv->fftComplexSize, conv->scale);
+ }
+
+ dsp_ops_fft_run(dsp, conv->ifft, -1, conv->conv, conv->fft_buffer);
+
+ dsp_ops_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(dsp, conv->overlap, conv->fft_buffer + conv->blockSize, conv->blockSize);
+
+ conv->current = (conv->current > 0) ? (conv->current - 1) : (conv->segCount - 1);
+ }
+
+ processed += processing;
+ }
+ return len;
+}
+
+struct convolver
+{
+ int headBlockSize;
+ int tailBlockSize;
+ struct convolver1 *headConvolver;
+ struct convolver1 *tailConvolver0;
+ float *tailOutput0;
+ float *tailPrecalculated0;
+ struct convolver1 *tailConvolver;
+ float *tailOutput;
+ float *tailPrecalculated;
+ float *tailInput;
+ int tailInputFill;
+ int precalculatedPos;
+};
+
+void convolver_reset(struct convolver *conv)
+{
+ if (conv->headConvolver)
+ convolver1_reset(conv->headConvolver);
+ if (conv->tailConvolver0) {
+ convolver1_reset(conv->tailConvolver0);
+ dsp_ops_clear(dsp, conv->tailOutput0, conv->tailBlockSize);
+ dsp_ops_clear(dsp, conv->tailPrecalculated0, conv->tailBlockSize);
+ }
+ if (conv->tailConvolver) {
+ convolver1_reset(conv->tailConvolver);
+ dsp_ops_clear(dsp, conv->tailOutput, conv->tailBlockSize);
+ dsp_ops_clear(dsp, conv->tailPrecalculated, conv->tailBlockSize);
+ }
+ 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 *conv;
+ int head_ir_len;
+
+ dsp = dsp_ops;
+
+ if (head_block == 0 || tail_block == 0)
+ return NULL;
+
+ head_block = SPA_MAX(1, head_block);
+ if (head_block > tail_block)
+ SPA_SWAP(head_block, tail_block);
+
+ while (irlen > 0 && fabs(ir[irlen-1]) < 0.000001f)
+ irlen--;
+
+ conv = calloc(1, sizeof(*conv));
+ if (conv == NULL)
+ return NULL;
+
+ if (irlen == 0)
+ return conv;
+
+ 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(conv->headBlockSize, ir, head_ir_len);
+
+ if (irlen > conv->tailBlockSize) {
+ int conv1IrLen = SPA_MIN(irlen - conv->tailBlockSize, conv->tailBlockSize);
+ conv->tailConvolver0 = convolver1_new(conv->headBlockSize, ir + conv->tailBlockSize, conv1IrLen);
+ conv->tailOutput0 = fft_alloc(conv->tailBlockSize);
+ conv->tailPrecalculated0 = fft_alloc(conv->tailBlockSize);
+ }
+
+ if (irlen > 2 * conv->tailBlockSize) {
+ int tailIrLen = irlen - (2 * conv->tailBlockSize);
+ conv->tailConvolver = convolver1_new(conv->tailBlockSize, ir + (2 * conv->tailBlockSize), tailIrLen);
+ conv->tailOutput = fft_alloc(conv->tailBlockSize);
+ conv->tailPrecalculated = fft_alloc(conv->tailBlockSize);
+ }
+
+ if (conv->tailConvolver0 || conv->tailConvolver)
+ conv->tailInput = fft_alloc(conv->tailBlockSize);
+
+ convolver_reset(conv);
+
+ return conv;
+}
+
+void convolver_free(struct convolver *conv)
+{
+ if (conv->headConvolver)
+ convolver1_free(conv->headConvolver);
+ if (conv->tailConvolver0)
+ convolver1_free(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);
+ free(conv);
+}
+
+int convolver_run(struct convolver *conv, const float *input, float *output, int length)
+{
+ convolver1_run(conv->headConvolver, input, output, length);
+
+ if (conv->tailInput) {
+ int processed = 0;
+
+ while (processed < length) {
+ int remaining = length - processed;
+ int processing = SPA_MIN(remaining, conv->headBlockSize - (conv->tailInputFill % conv->headBlockSize));
+
+ if (conv->tailPrecalculated0)
+ dsp_ops_sum(dsp, &output[processed], &output[processed],
+ &conv->tailPrecalculated0[conv->precalculatedPos],
+ processing);
+ if (conv->tailPrecalculated)
+ dsp_ops_sum(dsp, &output[processed], &output[processed],
+ &conv->tailPrecalculated[conv->precalculatedPos],
+ processing);
+ conv->precalculatedPos += processing;
+
+ dsp_ops_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,
+ conv->tailInput + blockOffset,
+ conv->tailOutput0 + blockOffset,
+ conv->headBlockSize);
+ if (conv->tailInputFill == conv->tailBlockSize)
+ SPA_SWAP(conv->tailPrecalculated0, conv->tailOutput0);
+ }
+
+ if (conv->tailPrecalculated &&
+ conv->tailInputFill == conv->tailBlockSize) {
+ SPA_SWAP(conv->tailPrecalculated, conv->tailOutput);
+ convolver1_run(conv->tailConvolver, conv->tailInput,
+ conv->tailOutput, conv->tailBlockSize);
+ }
+ if (conv->tailInputFill == conv->tailBlockSize) {
+ conv->tailInputFill = 0;
+ conv->precalculatedPos = 0;
+ }
+ processed += processing;
+ }
+ }
+ return 0;
+}
diff --git a/src/modules/module-filter-chain/convolver.h b/src/modules/module-filter-chain/convolver.h
new file mode 100644
index 0000000..1988114
--- /dev/null
+++ b/src/modules/module-filter-chain/convolver.h
@@ -0,0 +1,34 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+
+#include "dsp-ops.h"
+
+struct convolver *convolver_new(struct dsp_ops *dsp, int block, int tail, const float *ir, int irlen);
+void convolver_free(struct convolver *conv);
+
+void convolver_reset(struct convolver *conv);
+int convolver_run(struct convolver *conv, const float *input, float *output, int length);
diff --git a/src/modules/module-filter-chain/dsp-ops-avx.c b/src/modules/module-filter-chain/dsp-ops-avx.c
new file mode 100644
index 0000000..7ea5456
--- /dev/null
+++ b/src/modules/module-filter-chain/dsp-ops-avx.c
@@ -0,0 +1,85 @@
+/* Spa
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+
+#include <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
new file mode 100644
index 0000000..a913f5a
--- /dev/null
+++ b/src/modules/module-filter-chain/dsp-ops-c.c
@@ -0,0 +1,174 @@
+/* Spa
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+#include <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;
+ 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;
+ 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 if (n_src == 1) {
+ if (dst != src[0])
+ dsp_copy_c(ops, dst, src[0], n_samples);
+ } else {
+ if (gain[0] == 1.0f)
+ dsp_copy_c(ops, dst, src[0], n_samples);
+ else
+ dsp_gain_c(ops, dst, src[0], gain[0], n_samples);
+
+ for (i = 1; i < n_src; i++) {
+ if (gain[i] == 1.0f)
+ dsp_add_c(ops, dst, src[i], n_samples);
+ else
+ dsp_gain_add_c(ops, dst, src[i], gain[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 x1, x2, y1, y2;
+ float b0, b1, b2, a1, a2;
+ uint32_t i;
+
+ x1 = bq->x1;
+ x2 = bq->x2;
+ y1 = bq->y1;
+ y2 = bq->y2;
+ b0 = bq->b0;
+ b1 = bq->b1;
+ b2 = bq->b2;
+ a1 = bq->a1;
+ a2 = bq->a2;
+ for (i = 0; i < n_samples; i++) {
+ float x = in[i];
+ float y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
+ out[i] = y;
+ x2 = x1;
+ x1 = x;
+ y2 = y1;
+ y1 = y;
+ }
+#define F(x) (-FLT_MIN < (x) && (x) < FLT_MIN ? 0.0f : (x))
+ bq->x1 = F(x1);
+ bq->x2 = F(x2);
+ bq->y1 = F(y1);
+ bq->y2 = F(y2);
+#undef F
+}
+
+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_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
new file mode 100644
index 0000000..bcc3499
--- /dev/null
+++ b/src/modules/module-filter-chain/dsp-ops-sse.c
@@ -0,0 +1,142 @@
+/* Spa
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+
+#include <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) {
+ 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.c b/src/modules/module-filter-chain/dsp-ops.c
new file mode 100644
index 0000000..d35d5c9
--- /dev/null
+++ b/src/modules/module-filter-chain/dsp-ops.c
@@ -0,0 +1,114 @@
+/* Spa
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+
+#include <spa/support/cpu.h>
+#include <spa/utils/defs.h>
+#include <spa/param/audio/format-utils.h>
+
+#include "dsp-ops.h"
+
+struct dsp_info {
+ uint32_t cpu_flags;
+
+ struct dsp_ops_funcs funcs;
+};
+
+static 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.sum = dsp_sum_avx,
+ .funcs.fft_new = dsp_fft_new_c,
+ .funcs.fft_free = dsp_fft_free_c,
+ .funcs.fft_run = dsp_fft_run_c,
+ .funcs.fft_cmul = dsp_fft_cmul_c,
+ .funcs.fft_cmuladd = dsp_fft_cmuladd_c,
+ },
+#endif
+#if defined (HAVE_SSE)
+ { SPA_CPU_FLAG_SSE,
+ .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.sum = dsp_sum_sse,
+ .funcs.fft_new = dsp_fft_new_c,
+ .funcs.fft_free = dsp_fft_free_c,
+ .funcs.fft_run = dsp_fft_run_c,
+ .funcs.fft_cmul = dsp_fft_cmul_c,
+ .funcs.fft_cmuladd = dsp_fft_cmuladd_c,
+ },
+#endif
+ { 0,
+ .funcs.clear = dsp_clear_c,
+ .funcs.copy = dsp_copy_c,
+ .funcs.mix_gain = dsp_mix_gain_c,
+ .funcs.biquad_run = dsp_biquad_run_c,
+ .funcs.sum = dsp_sum_c,
+ .funcs.fft_new = dsp_fft_new_c,
+ .funcs.fft_free = dsp_fft_free_c,
+ .funcs.fft_run = dsp_fft_run_c,
+ .funcs.fft_cmul = dsp_fft_cmul_c,
+ .funcs.fft_cmuladd = dsp_fft_cmuladd_c,
+ },
+};
+
+#define MATCH_CPU_FLAGS(a,b) ((a) == 0 || ((a) & (b)) == a)
+
+static const struct dsp_info *find_dsp_info(uint32_t cpu_flags)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(dsp_table, t) {
+ if (MATCH_CPU_FLAGS(t->cpu_flags, cpu_flags))
+ return t;
+ }
+ return NULL;
+}
+
+static void impl_dsp_ops_free(struct dsp_ops *ops)
+{
+ spa_zero(*ops);
+}
+
+int dsp_ops_init(struct dsp_ops *ops)
+{
+ const struct dsp_info *info;
+
+ info = find_dsp_info(ops->cpu_flags);
+ if (info == NULL)
+ return -ENOTSUP;
+
+ ops->priv = info;
+ ops->free = impl_dsp_ops_free;
+ ops->funcs = info->funcs;
+
+ return 0;
+}
diff --git a/src/modules/module-filter-chain/dsp-ops.h b/src/modules/module-filter-chain/dsp-ops.h
new file mode 100644
index 0000000..bd77062
--- /dev/null
+++ b/src/modules/module-filter-chain/dsp-ops.h
@@ -0,0 +1,140 @@
+/* Spa
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#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);
+};
+
+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);
+
+#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_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_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_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.h b/src/modules/module-filter-chain/ladspa.h
new file mode 100644
index 0000000..b1a9c4e
--- /dev/null
+++ b/src/modules/module-filter-chain/ladspa.h
@@ -0,0 +1,603 @@
+/* ladspa.h
+
+ Linux Audio Developer's Simple Plugin API Version 1.1[LGPL].
+ Copyright (C) 2000-2002 Richard W.E. Furse, Paul Barton-Davis,
+ Stefan Westerfeld.
+
+ This library 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.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ USA. */
+
+#ifndef LADSPA_INCLUDED
+#define LADSPA_INCLUDED
+
+#define LADSPA_VERSION "1.1"
+#define LADSPA_VERSION_MAJOR 1
+#define LADSPA_VERSION_MINOR 1
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*****************************************************************************/
+
+/* Overview:
+
+ There is a large number of synthesis packages in use or development
+ on the Linux platform at this time. This API (`The Linux Audio
+ Developer's Simple Plugin API') attempts to give programmers the
+ ability to write simple `plugin' audio processors in C/C++ and link
+ them dynamically (`plug') into a range of these packages (`hosts').
+ It should be possible for any host and any plugin to communicate
+ completely through this interface.
+
+ This API is deliberately short and simple. To achieve compatibility
+ with a range of promising Linux sound synthesis packages it
+ attempts to find the `greatest common divisor' in their logical
+ behaviour. Having said this, certain limiting decisions are
+ implicit, notably the use of a fixed type (LADSPA_Data) for all
+ data transfer and absence of a parameterised `initialisation'
+ phase. See below for the LADSPA_Data typedef.
+
+ Plugins are expected to distinguish between control and audio
+ data. Plugins have `ports' that are inputs or outputs for audio or
+ control data and each plugin is `run' for a `block' corresponding
+ to a short time interval measured in samples. Audio data is
+ communicated using arrays of LADSPA_Data, allowing a block of audio
+ to be processed by the plugin in a single pass. Control data is
+ communicated using single LADSPA_Data values. Control data has a
+ single value at the start of a call to the `run()' or `run_adding()'
+ function, and may be considered to remain this value for its
+ duration. The plugin may assume that all its input and output ports
+ have been connected to the relevant data location (see the
+ `connect_port()' function below) before it is asked to run.
+
+ Plugins will reside in shared object files suitable for dynamic
+ linking by dlopen() and family. The file will provide a number of
+ `plugin types' that can be used to instantiate actual plugins
+ (sometimes known as `plugin instances') that can be connected
+ together to perform tasks.
+
+ This API contains very limited error-handling. */
+
+/*****************************************************************************/
+
+/* Fundamental data type passed in and out of plugin. This data type
+ is used to communicate audio samples and control values. It is
+ assumed that the plugin will work sensibly given any numeric input
+ value although it may have a preferred range (see hints below).
+
+ For audio it is generally assumed that 1.0f is the `0dB' reference
+ amplitude and is a `normal' signal level. */
+
+typedef float LADSPA_Data;
+
+/*****************************************************************************/
+
+/* Special Plugin Properties:
+
+ Optional features of the plugin type are encapsulated in the
+ LADSPA_Properties type. This is assembled by ORing individual
+ properties together. */
+
+typedef int LADSPA_Properties;
+
+/* Property LADSPA_PROPERTY_REALTIME indicates that the plugin has a
+ real-time dependency (e.g. listens to a MIDI device) and so its
+ output must not be cached or subject to significant latency. */
+#define LADSPA_PROPERTY_REALTIME 0x1
+
+/* Property LADSPA_PROPERTY_INPLACE_BROKEN indicates that the plugin
+ may cease to work correctly if the host elects to use the same data
+ location for both input and output (see connect_port()). This
+ should be avoided as enabling this flag makes it impossible for
+ hosts to use the plugin to process audio `in-place.' */
+#define LADSPA_PROPERTY_INPLACE_BROKEN 0x2
+
+/* Property LADSPA_PROPERTY_HARD_RT_CAPABLE indicates that the plugin
+ is capable of running not only in a conventional host but also in a
+ `hard real-time' environment. To qualify for this the plugin must
+ satisfy all of the following:
+
+ (1) The plugin must not use malloc(), free() or other heap memory
+ management within its run() or run_adding() functions. All new
+ memory used in run() must be managed via the stack. These
+ restrictions only apply to the run() function.
+
+ (2) The plugin will not attempt to make use of any library
+ functions with the exceptions of functions in the ANSI standard C
+ and C maths libraries, which the host is expected to provide.
+
+ (3) The plugin will not access files, devices, pipes, sockets, IPC
+ or any other mechanism that might result in process or thread
+ blocking.
+
+ (4) The plugin will take an amount of time to execute a run() or
+ run_adding() call approximately of form (A+B*SampleCount) where A
+ and B depend on the machine and host in use. This amount of time
+ may not depend on input signals or plugin state. The host is left
+ the responsibility to perform timings to estimate upper bounds for
+ A and B. */
+#define LADSPA_PROPERTY_HARD_RT_CAPABLE 0x4
+
+#define LADSPA_IS_REALTIME(x) ((x) & LADSPA_PROPERTY_REALTIME)
+#define LADSPA_IS_INPLACE_BROKEN(x) ((x) & LADSPA_PROPERTY_INPLACE_BROKEN)
+#define LADSPA_IS_HARD_RT_CAPABLE(x) ((x) & LADSPA_PROPERTY_HARD_RT_CAPABLE)
+
+/*****************************************************************************/
+
+/* Plugin Ports:
+
+ Plugins have `ports' that are inputs or outputs for audio or
+ data. Ports can communicate arrays of LADSPA_Data (for audio
+ inputs/outputs) or single LADSPA_Data values (for control
+ input/outputs). This information is encapsulated in the
+ LADSPA_PortDescriptor type which is assembled by ORing individual
+ properties together.
+
+ Note that a port must be an input or an output port but not both
+ and that a port must be a control or audio port but not both. */
+
+typedef int LADSPA_PortDescriptor;
+
+/* Property LADSPA_PORT_INPUT indicates that the port is an input. */
+#define LADSPA_PORT_INPUT 0x1
+
+/* Property LADSPA_PORT_OUTPUT indicates that the port is an output. */
+#define LADSPA_PORT_OUTPUT 0x2
+
+/* Property LADSPA_PORT_CONTROL indicates that the port is a control
+ port. */
+#define LADSPA_PORT_CONTROL 0x4
+
+/* Property LADSPA_PORT_AUDIO indicates that the port is a audio
+ port. */
+#define LADSPA_PORT_AUDIO 0x8
+
+#define LADSPA_IS_PORT_INPUT(x) ((x) & LADSPA_PORT_INPUT)
+#define LADSPA_IS_PORT_OUTPUT(x) ((x) & LADSPA_PORT_OUTPUT)
+#define LADSPA_IS_PORT_CONTROL(x) ((x) & LADSPA_PORT_CONTROL)
+#define LADSPA_IS_PORT_AUDIO(x) ((x) & LADSPA_PORT_AUDIO)
+
+/*****************************************************************************/
+
+/* Plugin Port Range Hints:
+
+ The host may wish to provide a representation of data entering or
+ leaving a plugin (e.g. to generate a GUI automatically). To make
+ this more meaningful, the plugin should provide `hints' to the host
+ describing the usual values taken by the data.
+
+ Note that these are only hints. The host may ignore them and the
+ plugin must not assume that data supplied to it is meaningful. If
+ the plugin receives invalid input data it is expected to continue
+ to run without failure and, where possible, produce a sensible
+ output (e.g. a high-pass filter given a negative cutoff frequency
+ might switch to an all-pass mode).
+
+ Hints are meaningful for all input and output ports but hints for
+ input control ports are expected to be particularly useful.
+
+ More hint information is encapsulated in the
+ LADSPA_PortRangeHintDescriptor type which is assembled by ORing
+ individual hint types together. Hints may require further
+ LowerBound and UpperBound information.
+
+ All the hint information for a particular port is aggregated in the
+ LADSPA_PortRangeHint structure. */
+
+typedef int LADSPA_PortRangeHintDescriptor;
+
+/* Hint LADSPA_HINT_BOUNDED_BELOW indicates that the LowerBound field
+ of the LADSPA_PortRangeHint should be considered meaningful. The
+ value in this field should be considered the (inclusive) lower
+ bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also
+ specified then the value of LowerBound should be multiplied by the
+ sample rate. */
+#define LADSPA_HINT_BOUNDED_BELOW 0x1
+
+/* Hint LADSPA_HINT_BOUNDED_ABOVE indicates that the UpperBound field
+ of the LADSPA_PortRangeHint should be considered meaningful. The
+ value in this field should be considered the (inclusive) upper
+ bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also
+ specified then the value of UpperBound should be multiplied by the
+ sample rate. */
+#define LADSPA_HINT_BOUNDED_ABOVE 0x2
+
+/* Hint LADSPA_HINT_TOGGLED indicates that the data item should be
+ considered a Boolean toggle. Data less than or equal to zero should
+ be considered `off' or `false,' and data above zero should be
+ considered `on' or `true.' LADSPA_HINT_TOGGLED may not be used in
+ conjunction with any other hint except LADSPA_HINT_DEFAULT_0 or
+ LADSPA_HINT_DEFAULT_1. */
+#define LADSPA_HINT_TOGGLED 0x4
+
+/* Hint LADSPA_HINT_SAMPLE_RATE indicates that any bounds specified
+ should be interpreted as multiples of the sample rate. For
+ instance, a frequency range from 0Hz to the Nyquist frequency (half
+ the sample rate) could be requested by this hint in conjunction
+ with LowerBound = 0 and UpperBound = 0.5. Hosts that support bounds
+ at all must support this hint to retain meaning. */
+#define LADSPA_HINT_SAMPLE_RATE 0x8
+
+/* Hint LADSPA_HINT_LOGARITHMIC indicates that it is likely that the
+ user will find it more intuitive to view values using a logarithmic
+ scale. This is particularly useful for frequencies and gains. */
+#define LADSPA_HINT_LOGARITHMIC 0x10
+
+/* Hint LADSPA_HINT_INTEGER indicates that a user interface would
+ probably wish to provide a stepped control taking only integer
+ values. Any bounds set should be slightly wider than the actual
+ integer range required to avoid floating point rounding errors. For
+ instance, the integer set {0,1,2,3} might be described as [-0.1,
+ 3.1]. */
+#define LADSPA_HINT_INTEGER 0x20
+
+/* The various LADSPA_HINT_HAS_DEFAULT_* hints indicate a `normal'
+ value for the port that is sensible as a default. For instance,
+ this value is suitable for use as an initial value in a user
+ interface or as a value the host might assign to a control port
+ when the user has not provided one. Defaults are encoded using a
+ mask so only one default may be specified for a port. Some of the
+ hints make use of lower and upper bounds, in which case the
+ relevant bound or bounds must be available and
+ LADSPA_HINT_SAMPLE_RATE must be applied as usual. The resulting
+ default must be rounded if LADSPA_HINT_INTEGER is present. Default
+ values were introduced in LADSPA v1.1. */
+#define LADSPA_HINT_DEFAULT_MASK 0x3C0
+
+/* This default values indicates that no default is provided. */
+#define LADSPA_HINT_DEFAULT_NONE 0x0
+
+/* This default hint indicates that the suggested lower bound for the
+ port should be used. */
+#define LADSPA_HINT_DEFAULT_MINIMUM 0x40
+
+/* This default hint indicates that a low value between the suggested
+ lower and upper bounds should be chosen. For ports with
+ LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.75 +
+ log(upper) * 0.25). Otherwise, this should be (lower * 0.75 + upper
+ * 0.25). */
+#define LADSPA_HINT_DEFAULT_LOW 0x80
+
+/* This default hint indicates that a middle value between the
+ suggested lower and upper bounds should be chosen. For ports with
+ LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.5 +
+ log(upper) * 0.5). Otherwise, this should be (lower * 0.5 + upper *
+ 0.5). */
+#define LADSPA_HINT_DEFAULT_MIDDLE 0xC0
+
+/* This default hint indicates that a high value between the suggested
+ lower and upper bounds should be chosen. For ports with
+ LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.25 +
+ log(upper) * 0.75). Otherwise, this should be (lower * 0.25 + upper
+ * 0.75). */
+#define LADSPA_HINT_DEFAULT_HIGH 0x100
+
+/* This default hint indicates that the suggested upper bound for the
+ port should be used. */
+#define LADSPA_HINT_DEFAULT_MAXIMUM 0x140
+
+/* This default hint indicates that the number 0 should be used. Note
+ that this default may be used in conjunction with
+ LADSPA_HINT_TOGGLED. */
+#define LADSPA_HINT_DEFAULT_0 0x200
+
+/* This default hint indicates that the number 1 should be used. Note
+ that this default may be used in conjunction with
+ LADSPA_HINT_TOGGLED. */
+#define LADSPA_HINT_DEFAULT_1 0x240
+
+/* This default hint indicates that the number 100 should be used. */
+#define LADSPA_HINT_DEFAULT_100 0x280
+
+/* This default hint indicates that the Hz frequency of `concert A'
+ should be used. This will be 440 unless the host uses an unusual
+ tuning convention, in which case it may be within a few Hz. */
+#define LADSPA_HINT_DEFAULT_440 0x2C0
+
+#define LADSPA_IS_HINT_BOUNDED_BELOW(x) ((x) & LADSPA_HINT_BOUNDED_BELOW)
+#define LADSPA_IS_HINT_BOUNDED_ABOVE(x) ((x) & LADSPA_HINT_BOUNDED_ABOVE)
+#define LADSPA_IS_HINT_TOGGLED(x) ((x) & LADSPA_HINT_TOGGLED)
+#define LADSPA_IS_HINT_SAMPLE_RATE(x) ((x) & LADSPA_HINT_SAMPLE_RATE)
+#define LADSPA_IS_HINT_LOGARITHMIC(x) ((x) & LADSPA_HINT_LOGARITHMIC)
+#define LADSPA_IS_HINT_INTEGER(x) ((x) & LADSPA_HINT_INTEGER)
+
+#define LADSPA_IS_HINT_HAS_DEFAULT(x) ((x) & LADSPA_HINT_DEFAULT_MASK)
+#define LADSPA_IS_HINT_DEFAULT_MINIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_MINIMUM)
+#define LADSPA_IS_HINT_DEFAULT_LOW(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_LOW)
+#define LADSPA_IS_HINT_DEFAULT_MIDDLE(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_MIDDLE)
+#define LADSPA_IS_HINT_DEFAULT_HIGH(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_HIGH)
+#define LADSPA_IS_HINT_DEFAULT_MAXIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_MAXIMUM)
+#define LADSPA_IS_HINT_DEFAULT_0(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_0)
+#define LADSPA_IS_HINT_DEFAULT_1(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_1)
+#define LADSPA_IS_HINT_DEFAULT_100(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_100)
+#define LADSPA_IS_HINT_DEFAULT_440(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \
+ == LADSPA_HINT_DEFAULT_440)
+
+typedef struct _LADSPA_PortRangeHint {
+
+ /* Hints about the port. */
+ LADSPA_PortRangeHintDescriptor HintDescriptor;
+
+ /* Meaningful when hint LADSPA_HINT_BOUNDED_BELOW is active. When
+ LADSPA_HINT_SAMPLE_RATE is also active then this value should be
+ multiplied by the relevant sample rate. */
+ LADSPA_Data LowerBound;
+
+ /* Meaningful when hint LADSPA_HINT_BOUNDED_ABOVE is active. When
+ LADSPA_HINT_SAMPLE_RATE is also active then this value should be
+ multiplied by the relevant sample rate. */
+ LADSPA_Data UpperBound;
+
+} LADSPA_PortRangeHint;
+
+/*****************************************************************************/
+
+/* Plugin Handles:
+
+ This plugin handle indicates a particular instance of the plugin
+ concerned. It is valid to compare this to NULL (0 for C++) but
+ otherwise the host should not attempt to interpret it. The plugin
+ may use it to reference internal instance data. */
+
+typedef void * LADSPA_Handle;
+
+/*****************************************************************************/
+
+/* Descriptor for a Type of Plugin:
+
+ This structure is used to describe a plugin type. It provides a
+ number of functions to examine the type, instantiate it, link it to
+ buffers and workspaces and to run it. */
+
+typedef struct _LADSPA_Descriptor {
+
+ /* This numeric identifier indicates the plugin type
+ uniquely. Plugin programmers may reserve ranges of IDs from a
+ central body to avoid clashes. Hosts may assume that IDs are
+ below 0x1000000. */
+ unsigned long UniqueID;
+
+ /* This identifier can be used as a unique, case-sensitive
+ identifier for the plugin type within the plugin file. Plugin
+ types should be identified by file and label rather than by index
+ or plugin name, which may be changed in new plugin
+ versions. Labels must not contain white-space characters. */
+ const char * Label;
+
+ /* This indicates a number of properties of the plugin. */
+ LADSPA_Properties Properties;
+
+ /* This member points to the null-terminated name of the plugin
+ (e.g. "Sine Oscillator"). */
+ const char * Name;
+
+ /* This member points to the null-terminated string indicating the
+ maker of the plugin. This can be an empty string but not NULL. */
+ const char * Maker;
+
+ /* This member points to the null-terminated string indicating any
+ copyright applying to the plugin. If no Copyright applies the
+ string "None" should be used. */
+ const char * Copyright;
+
+ /* This indicates the number of ports (input AND output) present on
+ the plugin. */
+ unsigned long PortCount;
+
+ /* This member indicates an array of port descriptors. Valid indices
+ vary from 0 to PortCount-1. */
+ const LADSPA_PortDescriptor * PortDescriptors;
+
+ /* This member indicates an array of null-terminated strings
+ describing ports (e.g. "Frequency (Hz)"). Valid indices vary from
+ 0 to PortCount-1. */
+ const char * const * PortNames;
+
+ /* This member indicates an array of range hints for each port (see
+ above). Valid indices vary from 0 to PortCount-1. */
+ const LADSPA_PortRangeHint * PortRangeHints;
+
+ /* This may be used by the plugin developer to pass any custom
+ implementation data into an instantiate call. It must not be used
+ or interpreted by the host. It is expected that most plugin
+ writers will not use this facility as LADSPA_Handle should be
+ used to hold instance data. */
+ void * ImplementationData;
+
+ /* This member is a function pointer that instantiates a plugin. A
+ handle is returned indicating the new plugin instance. The
+ instantiation function accepts a sample rate as a parameter. The
+ plugin descriptor from which this instantiate function was found
+ must also be passed. This function must return NULL if
+ instantiation fails.
+
+ Note that instance initialisation should generally occur in
+ activate() rather than here. */
+ LADSPA_Handle (*instantiate)(const struct _LADSPA_Descriptor * Descriptor,
+ unsigned long SampleRate);
+
+ /* This member is a function pointer that connects a port on an
+ instantiated plugin to a memory location at which a block of data
+ for the port will be read/written. The data location is expected
+ to be an array of LADSPA_Data for audio ports or a single
+ LADSPA_Data value for control ports. Memory issues will be
+ managed by the host. The plugin must read/write the data at these
+ locations every time run() or run_adding() is called and the data
+ present at the time of this connection call should not be
+ considered meaningful.
+
+ connect_port() may be called more than once for a plugin instance
+ to allow the host to change the buffers that the plugin is
+ reading or writing. These calls may be made before or after
+ activate() or deactivate() calls.
+
+ connect_port() must be called at least once for each port before
+ run() or run_adding() is called. When working with blocks of
+ LADSPA_Data the plugin should pay careful attention to the block
+ size passed to the run function as the block allocated may only
+ just be large enough to contain the block of samples.
+
+ Plugin writers should be aware that the host may elect to use the
+ same buffer for more than one port and even use the same buffer
+ for both input and output (see LADSPA_PROPERTY_INPLACE_BROKEN).
+ However, overlapped buffers or use of a single buffer for both
+ audio and control data may result in unexpected behaviour. */
+ void (*connect_port)(LADSPA_Handle Instance,
+ unsigned long Port,
+ LADSPA_Data * DataLocation);
+
+ /* This member is a function pointer that initialises a plugin
+ instance and activates it for use. This is separated from
+ instantiate() to aid real-time support and so that hosts can
+ reinitialise a plugin instance by calling deactivate() and then
+ activate(). In this case the plugin instance must reset all state
+ information dependent on the history of the plugin instance
+ except for any data locations provided by connect_port() and any
+ gain set by set_run_adding_gain(). If there is nothing for
+ activate() to do then the plugin writer may provide a NULL rather
+ than an empty function.
+
+ When present, hosts must call this function once before run() (or
+ run_adding()) is called for the first time. This call should be
+ made as close to the run() call as possible and indicates to
+ real-time plugins that they are now live. Plugins should not rely
+ on a prompt call to run() after activate(). activate() may not be
+ called again unless deactivate() is called first. Note that
+ connect_port() may be called before or after a call to
+ activate(). */
+ void (*activate)(LADSPA_Handle Instance);
+
+ /* This method is a function pointer that runs an instance of a
+ plugin for a block. Two parameters are required: the first is a
+ handle to the particular instance to be run and the second
+ indicates the block size (in samples) for which the plugin
+ instance may run.
+
+ Note that if an activate() function exists then it must be called
+ before run() or run_adding(). If deactivate() is called for a
+ plugin instance then the plugin instance may not be reused until
+ activate() has been called again.
+
+ If the plugin has the property LADSPA_PROPERTY_HARD_RT_CAPABLE
+ then there are various things that the plugin should not do
+ within the run() or run_adding() functions (see above). */
+ void (*run)(LADSPA_Handle Instance,
+ unsigned long SampleCount);
+
+ /* This method is a function pointer that runs an instance of a
+ plugin for a block. This has identical behaviour to run() except
+ in the way data is output from the plugin. When run() is used,
+ values are written directly to the memory areas associated with
+ the output ports. However when run_adding() is called, values
+ must be added to the values already present in the memory
+ areas. Furthermore, output values written must be scaled by the
+ current gain set by set_run_adding_gain() (see below) before
+ addition.
+
+ run_adding() is optional. When it is not provided by a plugin,
+ this function pointer must be set to NULL. When it is provided,
+ the function set_run_adding_gain() must be provided also. */
+ void (*run_adding)(LADSPA_Handle Instance,
+ unsigned long SampleCount);
+
+ /* This method is a function pointer that sets the output gain for
+ use when run_adding() is called (see above). If this function is
+ never called the gain is assumed to default to 1. Gain
+ information should be retained when activate() or deactivate()
+ are called.
+
+ This function should be provided by the plugin if and only if the
+ run_adding() function is provided. When it is absent this
+ function pointer must be set to NULL. */
+ void (*set_run_adding_gain)(LADSPA_Handle Instance,
+ LADSPA_Data Gain);
+
+ /* This is the counterpart to activate() (see above). If there is
+ nothing for deactivate() to do then the plugin writer may provide
+ a NULL rather than an empty function.
+
+ Hosts must deactivate all activated units after they have been
+ run() (or run_adding()) for the last time. This call should be
+ made as close to the last run() call as possible and indicates to
+ real-time plugins that they are no longer live. Plugins should
+ not rely on prompt deactivation. Note that connect_port() may be
+ called before or after a call to deactivate().
+
+ Deactivation is not similar to pausing as the plugin instance
+ will be reinitialised when activate() is called to reuse it. */
+ void (*deactivate)(LADSPA_Handle Instance);
+
+ /* Once an instance of a plugin has been finished with it can be
+ deleted using the following function. The instance handle passed
+ ceases to be valid after this call.
+
+ If activate() was called for a plugin instance then a
+ corresponding call to deactivate() must be made before cleanup()
+ is called. */
+ void (*cleanup)(LADSPA_Handle Instance);
+
+} LADSPA_Descriptor;
+
+/**********************************************************************/
+
+/* Accessing a Plugin: */
+
+/* The exact mechanism by which plugins are loaded is host-dependent,
+ however all most hosts will need to know is the name of shared
+ object file containing the plugin types. To allow multiple hosts to
+ share plugin types, hosts may wish to check for environment
+ variable LADSPA_PATH. If present, this should contain a
+ colon-separated path indicating directories that should be searched
+ (in order) when loading plugin types.
+
+ A plugin programmer must include a function called
+ "ladspa_descriptor" with the following function prototype within
+ the shared object file. This function will have C-style linkage (if
+ you are using C++ this is taken care of by the `extern "C"' clause
+ at the top of the file).
+
+ A host will find the plugin shared object file by one means or
+ another, find the ladspa_descriptor() function, call it, and
+ proceed from there.
+
+ Plugin types are accessed by index (not ID) using values from 0
+ upwards. Out of range indexes must result in this function
+ returning NULL, so the plugin count can be determined by checking
+ for the least index that results in NULL being returned. */
+
+const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index);
+
+/* Datatype corresponding to the ladspa_descriptor() function. */
+typedef const LADSPA_Descriptor *
+(*LADSPA_Descriptor_Function)(unsigned long Index);
+
+/**********************************************************************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LADSPA_INCLUDED */
+
+/* EOF */
diff --git a/src/modules/module-filter-chain/ladspa_plugin.c b/src/modules/module-filter-chain/ladspa_plugin.c
new file mode 100644
index 0000000..195d1ab
--- /dev/null
+++ b/src/modules/module-filter-chain/ladspa_plugin.c
@@ -0,0 +1,275 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <dlfcn.h>
+#include <math.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_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 char *config)
+{
+ struct fc_plugin *pl = NULL;
+
+ if (plugin[0] != '/') {
+ const char *search_dirs, *p;
+ 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(NULL, ":", &len, &search_dirs))) {
+ 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/lv2_plugin.c b/src/modules/module-filter-chain/lv2_plugin.c
new file mode 100644
index 0000000..f0bd836
--- /dev/null
+++ b/src/modules/module-filter-chain/lv2_plugin.c
@@ -0,0 +1,522 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <dlfcn.h>
+#include <math.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>
+
+#if defined __has_include
+# if __has_include (<lv2/atom/atom.h>)
+
+ #include <lv2/atom/atom.h>
+ #include <lv2/buf-size/buf-size.h>
+ #include <lv2/worker/worker.h>
+ #include <lv2/options/options.h>
+ #include <lv2/parameters/parameters.h>
+
+# else
+
+ #include <lv2/lv2plug.in/ns/ext/atom/atom.h>
+ #include <lv2/lv2plug.in/ns/ext/buf-size/buf-size.h>
+ #include <lv2/lv2plug.in/ns/ext/worker/worker.h>
+ #include <lv2/lv2plug.in/ns/ext/options/options.h>
+ #include <lv2/lv2plug.in/ns/ext/parameters/parameters.h>
+
+# endif
+
+#endif
+
+#include "plugin.h"
+
+static struct context *_context;
+
+typedef struct URITable {
+ struct pw_array array;
+} URITable;
+
+static void uri_table_init(URITable *table)
+{
+ pw_array_init(&table->array, 1024);
+}
+
+static void uri_table_destroy(URITable *table)
+{
+ char **p;
+ pw_array_for_each(p, &table->array)
+ free(*p);
+ pw_array_clear(&table->array);
+}
+
+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;
+}
+
+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, char*);
+ return NULL;
+}
+
+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;
+ LilvNode *lv2_ControlPort;
+ LilvNode *lv2_Optional;
+ LilvNode *atom_AtomPort;
+ LilvNode *atom_Sequence;
+ LilvNode *urid_map;
+ LilvNode *powerOf2BlockLength;
+ LilvNode *fixedBlockLength;
+ LilvNode *boundedBlockLength;
+ LilvNode* worker_schedule;
+ LilvNode* worker_iface;
+
+ URITable uri_table;
+ LV2_URID_Map map;
+ LV2_Feature map_feature;
+ LV2_URID_Unmap unmap;
+ LV2_Feature unmap_feature;
+
+ LV2_URID atom_Int;
+ LV2_URID atom_Float;
+};
+
+#define context_map(c,uri) ((c)->map.map((c)->map.handle,(uri)))
+
+static void context_free(struct context *c)
+{
+ if (c->world) {
+ lilv_node_free(c->worker_schedule);
+ lilv_node_free(c->powerOf2BlockLength);
+ lilv_node_free(c->fixedBlockLength);
+ lilv_node_free(c->boundedBlockLength);
+ lilv_node_free(c->urid_map);
+ lilv_node_free(c->atom_Sequence);
+ lilv_node_free(c->atom_AtomPort);
+ lilv_node_free(c->lv2_Optional);
+ lilv_node_free(c->lv2_ControlPort);
+ lilv_node_free(c->lv2_AudioPort);
+ lilv_node_free(c->lv2_OutputPort);
+ lilv_node_free(c->lv2_InputPort);
+ lilv_world_free(c->world);
+ }
+ uri_table_destroy(&c->uri_table);
+ free(c);
+}
+
+static const LV2_Feature buf_size_features[3] = {
+ { LV2_BUF_SIZE__powerOf2BlockLength, NULL },
+ { LV2_BUF_SIZE__fixedBlockLength, NULL },
+ { LV2_BUF_SIZE__boundedBlockLength, NULL },
+};
+
+static struct context *context_new(const struct spa_support *support, uint32_t n_support)
+{
+ struct context *c;
+
+ c = calloc(1, sizeof(*c));
+ if (c == NULL)
+ return NULL;
+
+ uri_table_init(&c->uri_table);
+ c->world = lilv_world_new();
+ if (c->world == NULL)
+ goto error;
+
+ lilv_world_load_all(c->world);
+
+ c->lv2_InputPort = lilv_new_uri(c->world, LV2_CORE__InputPort);
+ c->lv2_OutputPort = lilv_new_uri(c->world, LV2_CORE__OutputPort);
+ c->lv2_AudioPort = lilv_new_uri(c->world, LV2_CORE__AudioPort);
+ c->lv2_ControlPort = lilv_new_uri(c->world, LV2_CORE__ControlPort);
+ c->lv2_Optional = lilv_new_uri(c->world, LV2_CORE__connectionOptional);
+ c->atom_AtomPort = lilv_new_uri(c->world, LV2_ATOM__AtomPort);
+ c->atom_Sequence = lilv_new_uri(c->world, LV2_ATOM__Sequence);
+ c->urid_map = lilv_new_uri(c->world, LV2_URID__map);
+ c->powerOf2BlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__powerOf2BlockLength);
+ c->fixedBlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__fixedBlockLength);
+ c->boundedBlockLength = lilv_new_uri(c->world, LV2_BUF_SIZE__boundedBlockLength);
+ c->worker_schedule = lilv_new_uri(c->world, LV2_WORKER__schedule);
+ c->worker_iface = lilv_new_uri(c->world, LV2_WORKER__interface);
+
+ c->map.handle = &c->uri_table;
+ c->map.map = uri_table_map;
+ c->map_feature.URI = LV2_URID_MAP_URI;
+ c->map_feature.data = &c->map;
+ c->unmap.handle = &c->uri_table;
+ c->unmap.unmap = uri_table_unmap;
+ c->unmap_feature.URI = LV2_URID_UNMAP_URI;
+ c->unmap_feature.data = &c->unmap;
+
+ 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)
+{
+ if (_context == NULL) {
+ _context = context_new(support, n_support);
+ if (_context == NULL)
+ return NULL;
+ }
+ _context->ref++;
+ return _context;
+}
+
+static void context_unref(struct context *context)
+{
+ if (--_context->ref == 0) {
+ context_free(_context);
+ _context = NULL;
+ }
+}
+
+struct plugin {
+ struct fc_plugin plugin;
+ struct context *c;
+ const LilvPlugin *p;
+};
+
+struct descriptor {
+ struct fc_descriptor desc;
+ struct plugin *p;
+};
+
+struct instance {
+ struct descriptor *desc;
+ LilvInstance *instance;
+ LV2_Worker_Schedule work_schedule;
+ LV2_Feature work_schedule_feature;
+ LV2_Options_Option options[6];
+ LV2_Feature options_feature;
+
+ const LV2_Feature *features[7];
+
+ const LV2_Worker_Interface *work_iface;
+
+ int32_t block_length;
+};
+
+static int
+do_respond(struct spa_loop *loop, bool async, uint32_t seq, const void *data,
+ size_t size, void *user_data)
+{
+ struct instance *i = (struct instance*)user_data;
+ i->work_iface->work_response(i->instance->lv2_handle, size, data);
+ return 0;
+}
+
+/** Called by the plugin to respond to non-RT work. */
+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);
+ return LV2_WORKER_SUCCESS;
+}
+
+static int
+do_schedule(struct spa_loop *loop, bool async, uint32_t seq, const void *data,
+ size_t size, void *user_data)
+{
+ struct instance *i = (struct instance*)user_data;
+ i->work_iface->work(i->instance->lv2_handle, work_respond, i, size, data);
+ return 0;
+}
+
+/** Called by the plugin to schedule non-RT work. */
+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);
+ return LV2_WORKER_SUCCESS;
+}
+
+static void *lv2_instantiate(const struct fc_descriptor *desc,
+ unsigned long SampleRate, int index, const char *config)
+{
+ struct descriptor *d = (struct descriptor*)desc;
+ struct plugin *p = d->p;
+ struct context *c = p->c;
+ struct instance *i;
+ uint32_t n_features = 0;
+ static const int32_t min_block_length = 1;
+ static const int32_t max_block_length = 8192;
+ static const int32_t seq_size = 32768;
+ float fsample_rate = SampleRate;
+
+ i = calloc(1, sizeof(*i));
+ if (i == NULL)
+ return NULL;
+
+ i->block_length = 1024;
+ i->desc = d;
+ i->features[n_features++] = &c->map_feature;
+ i->features[n_features++] = &c->unmap_feature;
+ i->features[n_features++] = &buf_size_features[0];
+ i->features[n_features++] = &buf_size_features[1];
+ i->features[n_features++] = &buf_size_features[2];
+ if (lilv_plugin_has_feature(p->p, c->worker_schedule)) {
+ i->work_schedule.handle = i;
+ i->work_schedule.schedule_work = work_schedule;
+ i->work_schedule_feature.URI = LV2_WORKER__schedule;
+ i->work_schedule_feature.data = &i->work_schedule;
+ i->features[n_features++] = &i->work_schedule_feature;
+ }
+
+ i->options[0] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
+ context_map(c, LV2_BUF_SIZE__minBlockLength), sizeof(int32_t),
+ c->atom_Int, &min_block_length };
+ i->options[1] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
+ context_map(c, LV2_BUF_SIZE__maxBlockLength), sizeof(int32_t),
+ c->atom_Int, &max_block_length };
+ i->options[2] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
+ context_map(c, LV2_BUF_SIZE__sequenceSize), sizeof(int32_t),
+ c->atom_Int, &seq_size };
+ i->options[3] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
+ context_map(c, "http://lv2plug.in/ns/ext/buf-size#nominalBlockLength"), sizeof(int32_t),
+ c->atom_Int, &i->block_length },
+ i->options[4] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0,
+ context_map(c, LV2_PARAMETERS__sampleRate), sizeof(float),
+ c->atom_Float, &fsample_rate };
+ i->options[5] = (LV2_Options_Option) { LV2_OPTIONS_INSTANCE, 0, 0, 0, 0, NULL };
+
+ i->options_feature.URI = LV2_OPTIONS__options;
+ i->options_feature.data = i->options;
+ i->features[n_features++] = &i->options_feature;
+
+ i->instance = lilv_plugin_instantiate(p->p, SampleRate, i->features);
+ if (i->instance == NULL) {
+ free(i);
+ return NULL;
+ }
+ if (lilv_plugin_has_extension_data(p->p, c->worker_iface)) {
+ i->work_iface = (const LV2_Worker_Interface*)
+ lilv_instance_get_extension_data(i->instance, LV2_WORKER__interface);
+ }
+
+ return i;
+}
+
+static void lv2_cleanup(void *instance)
+{
+ struct instance *i = instance;
+ lilv_instance_free(i->instance);
+ free(i);
+}
+
+static void lv2_connect_port(void *instance, unsigned long port, float *data)
+{
+ struct instance *i = instance;
+ lilv_instance_connect_port(i->instance, port, data);
+}
+
+static void lv2_activate(void *instance)
+{
+ struct instance *i = instance;
+ lilv_instance_activate(i->instance);
+}
+
+static void lv2_deactivate(void *instance)
+{
+ struct instance *i = instance;
+ lilv_instance_deactivate(i->instance);
+}
+
+static void lv2_run(void *instance, unsigned long SampleCount)
+{
+ struct instance *i = instance;
+ lilv_instance_run(i->instance, SampleCount);
+ if (i->work_iface != NULL && i->work_iface->end_run != NULL)
+ i->work_iface->end_run(i->instance);
+}
+
+static void lv2_free(const struct fc_descriptor *desc)
+{
+ struct descriptor *d = (struct descriptor*)desc;
+ free((char*)d->desc.name);
+ free(d->desc.ports);
+ free(d);
+}
+
+static const struct fc_descriptor *lv2_make_desc(struct fc_plugin *plugin, const char *name)
+{
+ struct plugin *p = (struct plugin *)plugin;
+ struct context *c = p->c;
+ struct descriptor *desc;
+ uint32_t i;
+ float *mins, *maxes, *controls;
+
+ desc = calloc(1, sizeof(*desc));
+ if (desc == NULL)
+ return NULL;
+
+ desc->p = p;
+ desc->desc.instantiate = lv2_instantiate;
+ desc->desc.cleanup = lv2_cleanup;
+ desc->desc.connect_port = lv2_connect_port;
+ desc->desc.activate = lv2_activate;
+ desc->desc.deactivate = lv2_deactivate;
+ desc->desc.run = lv2_run;
+
+ desc->desc.free = lv2_free;
+
+ desc->desc.name = strdup(name);
+ 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));
+
+ mins = alloca(desc->desc.n_ports * sizeof(float));
+ maxes = alloca(desc->desc.n_ports * sizeof(float));
+ controls = alloca(desc->desc.n_ports * sizeof(float));
+
+ lilv_plugin_get_port_ranges_float(p->p, mins, maxes, controls);
+
+ 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];
+
+ 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;
+ if (lilv_port_is_a(p->p, port, c->lv2_OutputPort))
+ fp->flags |= FC_PORT_OUTPUT;
+ if (lilv_port_is_a(p->p, port, c->lv2_ControlPort))
+ fp->flags |= FC_PORT_CONTROL;
+ if (lilv_port_is_a(p->p, port, c->lv2_AudioPort))
+ fp->flags |= FC_PORT_AUDIO;
+
+ fp->hint = 0;
+ fp->min = mins[i];
+ fp->max = maxes[i];
+ fp->def = controls[i];
+ }
+ return &desc->desc;
+}
+
+static void lv2_unload(struct fc_plugin *plugin)
+{
+ struct plugin *p = (struct plugin *)plugin;
+ context_unref(p->c);
+ free(p);
+}
+
+struct fc_plugin *load_lv2_plugin(const struct spa_support *support, uint32_t n_support,
+ struct dsp_ops *ops, const char *plugin_uri, const char *config)
+{
+ struct context *c;
+ const LilvPlugins *plugins;
+ const LilvPlugin *plugin;
+ LilvNode *uri;
+ int res;
+ struct plugin *p;
+
+ c = context_ref(support, n_support);
+ if (c == NULL)
+ return NULL;
+
+ uri = lilv_new_uri(c->world, plugin_uri);
+ if (uri == NULL) {
+ pw_log_warn("invalid URI %s", plugin_uri);
+ res = -EINVAL;
+ goto error_unref;
+ }
+
+ plugins = lilv_world_get_all_plugins(c->world);
+ plugin = lilv_plugins_get_by_uri(plugins, uri);
+ lilv_node_free(uri);
+
+ if (plugin == NULL) {
+ pw_log_warn("can't load plugin %s", plugin_uri);
+ res = -EINVAL;
+ goto error_unref;
+ }
+
+ p = calloc(1, sizeof(*p));
+ if (!p) {
+ res = -errno;
+ goto error_unref;
+ }
+ p->p = plugin;
+ p->c = c;
+
+ p->plugin.make_desc = lv2_make_desc;
+ p->plugin.unload = lv2_unload;
+
+ return &p->plugin;
+
+error_unref:
+ context_unref(c);
+ errno = -res;
+ return NULL;
+}
diff --git a/src/modules/module-filter-chain/pffft.c b/src/modules/module-filter-chain/pffft.c
new file mode 100644
index 0000000..0370191
--- /dev/null
+++ b/src/modules/module-filter-chain/pffft.c
@@ -0,0 +1,2381 @@
+/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com )
+
+ Based on original fortran 77 code from FFTPACKv4 from NETLIB
+ (http://www.netlib.org/fftpack), authored by Dr Paul Swarztrauber
+ of NCAR, in 1985.
+
+ As confirmed by the NCAR fftpack software curators, the following
+ FFTPACKv5 license applies to FFTPACKv4 sources. My changes are
+ released under the same terms.
+
+ FFTPACK license:
+
+ http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html
+
+ Copyright (c) 2004 the University Corporation for Atmospheric
+ Research ("UCAR"). All rights reserved. Developed by NCAR's
+ Computational and Information Systems Laboratory, UCAR,
+ www.cisl.ucar.edu.
+
+ Redistribution and use of the Software in source and binary forms,
+ with or without modification, is permitted provided that the
+ following conditions are met:
+
+ - Neither the names of NCAR's Computational and Information Systems
+ Laboratory, the University Corporation for Atmospheric Research,
+ nor the names of its sponsors or contributors may be used to
+ endorse or promote products derived from this Software without
+ specific prior written permission.
+
+ - Redistributions of source code must retain the above copyright
+ notices, this list of conditions, and the disclaimer below.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions, and the disclaimer below in the
+ documentation and/or other materials provided with the
+ distribution.
+
+ THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
+ SOFTWARE.
+
+ PFFFT : a Pretty Fast FFT.
+
+ This file is largerly based on the original FFTPACK implementation, modified in
+ order to take advantage of SIMD instructions of modern CPUs.
+*/
+
+/*
+ ChangeLog:
+ - 2011/10/02, version 1: This is the very first release of this file.
+*/
+
+#include "pffft.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <stdint.h>
+#include <assert.h>
+
+#include <spa/support/cpu.h>
+
+/* detect compiler flavour */
+#if defined(_MSC_VER)
+#define COMPILER_MSVC
+#elif defined(__GNUC__)
+#define COMPILER_GCC
+#endif
+
+#if defined(COMPILER_GCC)
+#define ALWAYS_INLINE(return_type) inline return_type __attribute__ ((always_inline))
+#define NEVER_INLINE(return_type) return_type __attribute__ ((noinline))
+#define RESTRICT __restrict
+#define VLA_ARRAY_ON_STACK(type__, varname__, size__) type__ varname__[size__];
+#elif defined(COMPILER_MSVC)
+#define ALWAYS_INLINE(return_type) __forceinline return_type
+#define NEVER_INLINE(return_type) __declspec(noinline) return_type
+#define RESTRICT __restrict
+#define VLA_ARRAY_ON_STACK(type__, varname__, size__) type__ *varname__ = (type__*)_alloca(size__ * sizeof(type__))
+#endif
+
+/*
+ vector support macros: the rest of the code is independent of
+ SSE/Altivec/NEON -- adding support for other platforms with 4-element
+ vectors should be limited to these macros
+*/
+
+// define PFFFT_SIMD_DISABLE if you want to use scalar code instead of simd code
+//#define PFFFT_SIMD_DISABLE
+
+/*
+ Altivec support macros
+*/
+#if !defined(PFFFT_SIMD_DISABLE) && (defined(HAVE_ALTIVEC))
+typedef vector float v4sf;
+#define SIMD_SZ 4
+#define VZERO() ((vector float) vec_splat_u8(0))
+#define VMUL(a,b) vec_madd(a,b, VZERO())
+#define VADD(a,b) vec_add(a,b)
+#define VMADD(a,b,c) vec_madd(a,b,c)
+#define VSUB(a,b) vec_sub(a,b)
+inline v4sf ld_ps1(const float *p)
+{
+ v4sf v = vec_lde(0, p);
+ return vec_splat(vec_perm(v, v, vec_lvsl(0, p)), 0);
+}
+
+#define LD_PS1(p) ld_ps1(&p)
+#define INTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = vec_mergeh(in1, in2); out2 = vec_mergel(in1, in2); out1 = tmp__; }
+#define UNINTERLEAVE2(in1, in2, out1, out2) { \
+ vector unsigned char vperm1 = (vector unsigned char)(0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27); \
+ vector unsigned char vperm2 = (vector unsigned char)(4,5,6,7,12,13,14,15,20,21,22,23,28,29,30,31); \
+ v4sf tmp__ = vec_perm(in1, in2, vperm1); out2 = vec_perm(in1, in2, vperm2); out1 = tmp__; \
+ }
+#define VTRANSPOSE4(x0,x1,x2,x3) { \
+ v4sf y0 = vec_mergeh(x0, x2); \
+ v4sf y1 = vec_mergel(x0, x2); \
+ v4sf y2 = vec_mergeh(x1, x3); \
+ v4sf y3 = vec_mergel(x1, x3); \
+ x0 = vec_mergeh(y0, y2); \
+ x1 = vec_mergel(y0, y2); \
+ x2 = vec_mergeh(y1, y3); \
+ x3 = vec_mergel(y1, y3); \
+ }
+#define VSWAPHL(a,b) vec_perm(a,b, (vector unsigned char)(16,17,18,19,20,21,22,23,8,9,10,11,12,13,14,15))
+#define VALIGNED(ptr) ((((uintptr_t)(ptr)) & 0xF) == 0)
+#define pffft_funcs pffft_funcs_altivec
+#define new_setup_simd new_setup_altivec
+#define zreorder_simd zreorder_altivec
+#define zconvolve_accumulate_simd zconvolve_accumulate_altivec
+#define zconvolve_simd zconvolve_altivec
+#define transform_simd transform_altivec
+
+/*
+ SSE1 support macros
+*/
+#elif !defined(PFFFT_SIMD_DISABLE) && (defined(HAVE_SSE))
+
+#include <xmmintrin.h>
+typedef __m128 v4sf;
+#define SIMD_SZ 4 // 4 floats by simd vector -- this is pretty much hardcoded in the preprocess/finalize functions anyway so you will have to work if you want to enable AVX with its 256-bit vectors.
+#define VZERO() _mm_setzero_ps()
+#define VMUL(a,b) _mm_mul_ps(a,b)
+#define VADD(a,b) _mm_add_ps(a,b)
+#define VMADD(a,b,c) _mm_add_ps(_mm_mul_ps(a,b), c)
+#define VSUB(a,b) _mm_sub_ps(a,b)
+#define LD_PS1(p) _mm_set1_ps(p)
+#define INTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = _mm_unpacklo_ps(in1, in2); out2 = _mm_unpackhi_ps(in1, in2); out1 = tmp__; }
+#define UNINTERLEAVE2(in1, in2, out1, out2) { v4sf tmp__ = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(2,0,2,0)); out2 = _mm_shuffle_ps(in1, in2, _MM_SHUFFLE(3,1,3,1)); out1 = tmp__; }
+#define VTRANSPOSE4(x0,x1,x2,x3) _MM_TRANSPOSE4_PS(x0,x1,x2,x3)
+#define VSWAPHL(a,b) _mm_shuffle_ps(b, a, _MM_SHUFFLE(3,2,1,0))
+#define VALIGNED(ptr) ((((uintptr_t)(ptr)) & 0xF) == 0)
+#define pffft_funcs pffft_funcs_sse
+#define new_setup_simd new_setup_sse
+#define zreorder_simd zreorder_sse
+#define zconvolve_accumulate_simd zconvolve_accumulate_sse
+#define zconvolve_simd zconvolve_sse
+#define transform_simd transform_sse
+
+/*
+ ARM NEON support macros
+*/
+#elif !defined(PFFFT_SIMD_DISABLE) && (defined(HAVE_NEON))
+#include <arm_neon.h>
+typedef float32x4_t v4sf;
+#define SIMD_SZ 4
+#define VZERO() vdupq_n_f32(0)
+#define VMUL(a,b) vmulq_f32(a,b)
+#define VADD(a,b) vaddq_f32(a,b)
+#define VMADD(a,b,c) vmlaq_f32(c,a,b)
+#define VSUB(a,b) vsubq_f32(a,b)
+#define LD_PS1(p) vld1q_dup_f32(&(p))
+#define INTERLEAVE2(in1, in2, out1, out2) { float32x4x2_t tmp__ = vzipq_f32(in1,in2); out1=tmp__.val[0]; out2=tmp__.val[1]; }
+#define UNINTERLEAVE2(in1, in2, out1, out2) { float32x4x2_t tmp__ = vuzpq_f32(in1,in2); out1=tmp__.val[0]; out2=tmp__.val[1]; }
+#define VTRANSPOSE4(x0,x1,x2,x3) { \
+ float32x4x2_t t0_ = vzipq_f32(x0, x2); \
+ float32x4x2_t t1_ = vzipq_f32(x1, x3); \
+ float32x4x2_t u0_ = vzipq_f32(t0_.val[0], t1_.val[0]); \
+ float32x4x2_t u1_ = vzipq_f32(t0_.val[1], t1_.val[1]); \
+ x0 = u0_.val[0]; x1 = u0_.val[1]; x2 = u1_.val[0]; x3 = u1_.val[1]; \
+ }
+// marginally faster version
+//# define VTRANSPOSE4(x0,x1,x2,x3) { asm("vtrn.32 %q0, %q1;\n vtrn.32 %q2,%q3\n vswp %f0,%e2\n vswp %f1,%e3" : "+w"(x0), "+w"(x1), "+w"(x2), "+w"(x3)::); }
+#define VSWAPHL(a,b) vcombine_f32(vget_low_f32(b), vget_high_f32(a))
+#define VALIGNED(ptr) ((((uintptr_t)(ptr)) & 0x3) == 0)
+#define pffft_funcs pffft_funcs_neon
+#define new_setup_simd new_setup_neon
+#define zreorder_simd zreorder_neon
+#define zconvolve_accumulate_simd zconvolve_accumulate_neon
+#define zconvolve_simd zconvolve_neon
+#define transform_simd transform_neon
+#else
+#if !defined(PFFFT_SIMD_DISABLE)
+#warning "building with simd disabled !\n";
+#define PFFFT_SIMD_DISABLE // fallback to scalar code
+#endif
+#endif
+
+// fallback mode for situations where SSE/Altivec are not available, use scalar mode instead
+#ifdef PFFFT_SIMD_DISABLE
+typedef float v4sf;
+#define SIMD_SZ 1
+#define VZERO() 0.f
+#define VMUL(a,b) ((a)*(b))
+#define VADD(a,b) ((a)+(b))
+#define VMADD(a,b,c) ((a)*(b)+(c))
+#define VSUB(a,b) ((a)-(b))
+#define LD_PS1(p) (p)
+#define VALIGNED(ptr) ((((uintptr_t)(ptr)) & 0x3) == 0)
+#define pffft_funcs pffft_funcs_c
+#define new_setup_simd new_setup_c
+#define zreorder_simd zreorder_c
+#define zconvolve_accumulate_simd zconvolve_accumulate_c
+#define zconvolve_simd zconvolve_c
+#define transform_simd transform_c
+#endif
+
+// shortcuts for complex multiplcations
+#define VCPLXMUL(ar,ai,br,bi) { v4sf tmp; tmp=VMUL(ar,bi); ar=VMUL(ar,br); ar=VSUB(ar,VMUL(ai,bi)); ai=VMUL(ai,br); ai=VADD(ai,tmp); }
+#define VCPLXMULCONJ(ar,ai,br,bi) { v4sf tmp; tmp=VMUL(ar,bi); ar=VMUL(ar,br); ar=VADD(ar,VMUL(ai,bi)); ai=VMUL(ai,br); ai=VSUB(ai,tmp); }
+#ifndef SVMUL
+// multiply a scalar with a vector
+#define SVMUL(f,v) VMUL(LD_PS1(f),v)
+#endif
+
+#if !defined(PFFFT_SIMD_DISABLE)
+typedef union v4sf_union {
+ v4sf v;
+ float f[4];
+} v4sf_union;
+
+#include <string.h>
+
+#define assertv4(v,f0,f1,f2,f3) assert(v.f[0] == (f0) && v.f[1] == (f1) && v.f[2] == (f2) && v.f[3] == (f3))
+
+/* detect bugs with the vector support macros */
+static void validate_pffft_simd(void)
+{
+ float f[16] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
+ v4sf_union a0, a1, a2, a3, t, u;
+ memcpy(a0.f, f, 4 * sizeof(float));
+ memcpy(a1.f, f + 4, 4 * sizeof(float));
+ memcpy(a2.f, f + 8, 4 * sizeof(float));
+ memcpy(a3.f, f + 12, 4 * sizeof(float));
+
+ t = a0;
+ u = a1;
+ t.v = VZERO();
+ printf("VZERO=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2], t.f[3]);
+ assertv4(t, 0, 0, 0, 0);
+ t.v = VADD(a1.v, a2.v);
+ printf("VADD(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2],
+ t.f[3]);
+ assertv4(t, 12, 14, 16, 18);
+ t.v = VMUL(a1.v, a2.v);
+ printf("VMUL(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2],
+ t.f[3]);
+ assertv4(t, 32, 45, 60, 77);
+ t.v = VMADD(a1.v, a2.v, a0.v);
+ printf("VMADD(4:7,8:11,0:3)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1],
+ t.f[2], t.f[3]);
+ assertv4(t, 32, 46, 62, 80);
+
+ INTERLEAVE2(a1.v, a2.v, t.v, u.v);
+ printf("INTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n",
+ t.f[0], t.f[1], t.f[2], t.f[3], u.f[0], u.f[1], u.f[2], u.f[3]);
+ assertv4(t, 4, 8, 5, 9);
+ assertv4(u, 6, 10, 7, 11);
+ UNINTERLEAVE2(a1.v, a2.v, t.v, u.v);
+ printf("UNINTERLEAVE2(4:7,8:11)=[%2g %2g %2g %2g] [%2g %2g %2g %2g]\n",
+ t.f[0], t.f[1], t.f[2], t.f[3], u.f[0], u.f[1], u.f[2], u.f[3]);
+ assertv4(t, 4, 6, 8, 10);
+ assertv4(u, 5, 7, 9, 11);
+
+ t.v = LD_PS1(f[15]);
+ printf("LD_PS1(15)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2],
+ t.f[3]);
+ assertv4(t, 15, 15, 15, 15);
+ t.v = VSWAPHL(a1.v, a2.v);
+ printf("VSWAPHL(4:7,8:11)=[%2g %2g %2g %2g]\n", t.f[0], t.f[1], t.f[2],
+ t.f[3]);
+ assertv4(t, 8, 9, 6, 7);
+ VTRANSPOSE4(a0.v, a1.v, a2.v, a3.v);
+ printf
+ ("VTRANSPOSE4(0:3,4:7,8:11,12:15)=[%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g] [%2g %2g %2g %2g]\n",
+ a0.f[0], a0.f[1], a0.f[2], a0.f[3], a1.f[0], a1.f[1], a1.f[2],
+ a1.f[3], a2.f[0], a2.f[1], a2.f[2], a2.f[3], a3.f[0], a3.f[1],
+ a3.f[2], a3.f[3]);
+ assertv4(a0, 0, 4, 8, 12);
+ assertv4(a1, 1, 5, 9, 13);
+ assertv4(a2, 2, 6, 10, 14);
+ assertv4(a3, 3, 7, 11, 15);
+}
+#else
+static void validate_pffft_simd(void)
+{
+} // allow test_pffft.c to call this function even when simd is not available..
+#endif //!PFFFT_SIMD_DISABLE
+
+/*
+ passf2 and passb2 has been merged here, fsign = -1 for passf2, +1 for passb2
+*/
+static NEVER_INLINE(void) passf2_ps(int ido, int l1, const v4sf * cc, v4sf * ch,
+ const float *wa1, float fsign)
+{
+ int k, i;
+ int l1ido = l1 * ido;
+ if (ido <= 2) {
+ for (k = 0; k < l1ido; k += ido, ch += ido, cc += 2 * ido) {
+ ch[0] = VADD(cc[0], cc[ido + 0]);
+ ch[l1ido] = VSUB(cc[0], cc[ido + 0]);
+ ch[1] = VADD(cc[1], cc[ido + 1]);
+ ch[l1ido + 1] = VSUB(cc[1], cc[ido + 1]);
+ }
+ } else {
+ for (k = 0; k < l1ido; k += ido, ch += ido, cc += 2 * ido) {
+ for (i = 0; i < ido - 1; i += 2) {
+ v4sf tr2 = VSUB(cc[i + 0], cc[i + ido + 0]);
+ v4sf ti2 = VSUB(cc[i + 1], cc[i + ido + 1]);
+ v4sf wr = LD_PS1(wa1[i]), wi =
+ VMUL(LD_PS1(fsign), LD_PS1(wa1[i + 1]));
+ ch[i] = VADD(cc[i + 0], cc[i + ido + 0]);
+ ch[i + 1] = VADD(cc[i + 1], cc[i + ido + 1]);
+ VCPLXMUL(tr2, ti2, wr, wi);
+ ch[i + l1ido] = tr2;
+ ch[i + l1ido + 1] = ti2;
+ }
+ }
+ }
+}
+
+/*
+ passf3 and passb3 has been merged here, fsign = -1 for passf3, +1 for passb3
+*/
+static NEVER_INLINE(void) passf3_ps(int ido, int l1, const v4sf * cc, v4sf * ch,
+ const float *wa1, const float *wa2,
+ float fsign)
+{
+ static const float taur = -0.5f;
+ float taui = 0.866025403784439f * fsign;
+ int i, k;
+ v4sf tr2, ti2, cr2, ci2, cr3, ci3, dr2, di2, dr3, di3;
+ int l1ido = l1 * ido;
+ float wr1, wi1, wr2, wi2;
+ assert(ido > 2);
+ for (k = 0; k < l1ido; k += ido, cc += 3 * ido, ch += ido) {
+ for (i = 0; i < ido - 1; i += 2) {
+ tr2 = VADD(cc[i + ido], cc[i + 2 * ido]);
+ cr2 = VADD(cc[i], SVMUL(taur, tr2));
+ ch[i] = VADD(cc[i], tr2);
+ ti2 = VADD(cc[i + ido + 1], cc[i + 2 * ido + 1]);
+ ci2 = VADD(cc[i + 1], SVMUL(taur, ti2));
+ ch[i + 1] = VADD(cc[i + 1], ti2);
+ cr3 = SVMUL(taui, VSUB(cc[i + ido], cc[i + 2 * ido]));
+ ci3 =
+ SVMUL(taui,
+ VSUB(cc[i + ido + 1], cc[i + 2 * ido + 1]));
+ dr2 = VSUB(cr2, ci3);
+ dr3 = VADD(cr2, ci3);
+ di2 = VADD(ci2, cr3);
+ di3 = VSUB(ci2, cr3);
+ wr1 = wa1[i], wi1 = fsign * wa1[i + 1], wr2 =
+ wa2[i], wi2 = fsign * wa2[i + 1];
+ VCPLXMUL(dr2, di2, LD_PS1(wr1), LD_PS1(wi1));
+ ch[i + l1ido] = dr2;
+ ch[i + l1ido + 1] = di2;
+ VCPLXMUL(dr3, di3, LD_PS1(wr2), LD_PS1(wi2));
+ ch[i + 2 * l1ido] = dr3;
+ ch[i + 2 * l1ido + 1] = di3;
+ }
+ }
+} /* passf3 */
+
+static NEVER_INLINE(void) passf4_ps(int ido, int l1, const v4sf * cc, v4sf * ch,
+ const float *wa1, const float *wa2,
+ const float *wa3, float fsign)
+{
+ /* isign == -1 for forward transform and +1 for backward transform */
+
+ int i, k;
+ v4sf ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3,
+ tr4;
+ int l1ido = l1 * ido;
+ if (ido == 2) {
+ for (k = 0; k < l1ido; k += ido, ch += ido, cc += 4 * ido) {
+ tr1 = VSUB(cc[0], cc[2 * ido + 0]);
+ tr2 = VADD(cc[0], cc[2 * ido + 0]);
+ ti1 = VSUB(cc[1], cc[2 * ido + 1]);
+ ti2 = VADD(cc[1], cc[2 * ido + 1]);
+ ti4 =
+ VMUL(VSUB(cc[1 * ido + 0], cc[3 * ido + 0]),
+ LD_PS1(fsign));
+ tr4 =
+ VMUL(VSUB(cc[3 * ido + 1], cc[1 * ido + 1]),
+ LD_PS1(fsign));
+ tr3 = VADD(cc[ido + 0], cc[3 * ido + 0]);
+ ti3 = VADD(cc[ido + 1], cc[3 * ido + 1]);
+
+ ch[0 * l1ido + 0] = VADD(tr2, tr3);
+ ch[0 * l1ido + 1] = VADD(ti2, ti3);
+ ch[1 * l1ido + 0] = VADD(tr1, tr4);
+ ch[1 * l1ido + 1] = VADD(ti1, ti4);
+ ch[2 * l1ido + 0] = VSUB(tr2, tr3);
+ ch[2 * l1ido + 1] = VSUB(ti2, ti3);
+ ch[3 * l1ido + 0] = VSUB(tr1, tr4);
+ ch[3 * l1ido + 1] = VSUB(ti1, ti4);
+ }
+ } else {
+ for (k = 0; k < l1ido; k += ido, ch += ido, cc += 4 * ido) {
+ for (i = 0; i < ido - 1; i += 2) {
+ float wr1, wi1, wr2, wi2, wr3, wi3;
+ tr1 = VSUB(cc[i + 0], cc[i + 2 * ido + 0]);
+ tr2 = VADD(cc[i + 0], cc[i + 2 * ido + 0]);
+ ti1 = VSUB(cc[i + 1], cc[i + 2 * ido + 1]);
+ ti2 = VADD(cc[i + 1], cc[i + 2 * ido + 1]);
+ tr4 =
+ VMUL(VSUB
+ (cc[i + 3 * ido + 1],
+ cc[i + 1 * ido + 1]), LD_PS1(fsign));
+ ti4 =
+ VMUL(VSUB
+ (cc[i + 1 * ido + 0],
+ cc[i + 3 * ido + 0]), LD_PS1(fsign));
+ tr3 =
+ VADD(cc[i + ido + 0], cc[i + 3 * ido + 0]);
+ ti3 =
+ VADD(cc[i + ido + 1], cc[i + 3 * ido + 1]);
+
+ ch[i] = VADD(tr2, tr3);
+ cr3 = VSUB(tr2, tr3);
+ ch[i + 1] = VADD(ti2, ti3);
+ ci3 = VSUB(ti2, ti3);
+
+ cr2 = VADD(tr1, tr4);
+ cr4 = VSUB(tr1, tr4);
+ ci2 = VADD(ti1, ti4);
+ ci4 = VSUB(ti1, ti4);
+ wr1 = wa1[i], wi1 = fsign * wa1[i + 1];
+ VCPLXMUL(cr2, ci2, LD_PS1(wr1), LD_PS1(wi1));
+ wr2 = wa2[i], wi2 = fsign * wa2[i + 1];
+ ch[i + l1ido] = cr2;
+ ch[i + l1ido + 1] = ci2;
+
+ VCPLXMUL(cr3, ci3, LD_PS1(wr2), LD_PS1(wi2));
+ wr3 = wa3[i], wi3 = fsign * wa3[i + 1];
+ ch[i + 2 * l1ido] = cr3;
+ ch[i + 2 * l1ido + 1] = ci3;
+
+ VCPLXMUL(cr4, ci4, LD_PS1(wr3), LD_PS1(wi3));
+ ch[i + 3 * l1ido] = cr4;
+ ch[i + 3 * l1ido + 1] = ci4;
+ }
+ }
+ }
+} /* passf4 */
+
+/*
+ passf5 and passb5 has been merged here, fsign = -1 for passf5, +1 for passb5
+*/
+static NEVER_INLINE(void) passf5_ps(int ido, int l1, const v4sf * cc, v4sf * ch,
+ const float *wa1, const float *wa2,
+ const float *wa3, const float *wa4,
+ float fsign)
+{
+ static const float tr11 = .309016994374947f;
+ const float ti11 = .951056516295154f * fsign;
+ static const float tr12 = -.809016994374947f;
+ const float ti12 = .587785252292473f * fsign;
+
+ /* Local variables */
+ int i, k;
+ v4sf ci2, ci3, ci4, ci5, di3, di4, di5, di2, cr2, cr3, cr5, cr4, ti2,
+ ti3, ti4, ti5, dr3, dr4, dr5, dr2, tr2, tr3, tr4, tr5;
+
+ float wr1, wi1, wr2, wi2, wr3, wi3, wr4, wi4;
+
+#define cc_ref(a_1,a_2) cc[(a_2-1)*ido + a_1 + 1]
+#define ch_ref(a_1,a_3) ch[(a_3-1)*l1*ido + a_1 + 1]
+
+ assert(ido > 2);
+ for (k = 0; k < l1; ++k, cc += 5 * ido, ch += ido) {
+ for (i = 0; i < ido - 1; i += 2) {
+ ti5 = VSUB(cc_ref(i, 2), cc_ref(i, 5));
+ ti2 = VADD(cc_ref(i, 2), cc_ref(i, 5));
+ ti4 = VSUB(cc_ref(i, 3), cc_ref(i, 4));
+ ti3 = VADD(cc_ref(i, 3), cc_ref(i, 4));
+ tr5 = VSUB(cc_ref(i - 1, 2), cc_ref(i - 1, 5));
+ tr2 = VADD(cc_ref(i - 1, 2), cc_ref(i - 1, 5));
+ tr4 = VSUB(cc_ref(i - 1, 3), cc_ref(i - 1, 4));
+ tr3 = VADD(cc_ref(i - 1, 3), cc_ref(i - 1, 4));
+ ch_ref(i - 1, 1) =
+ VADD(cc_ref(i - 1, 1), VADD(tr2, tr3));
+ ch_ref(i, 1) = VADD(cc_ref(i, 1), VADD(ti2, ti3));
+ cr2 =
+ VADD(cc_ref(i - 1, 1),
+ VADD(SVMUL(tr11, tr2), SVMUL(tr12, tr3)));
+ ci2 =
+ VADD(cc_ref(i, 1),
+ VADD(SVMUL(tr11, ti2), SVMUL(tr12, ti3)));
+ cr3 =
+ VADD(cc_ref(i - 1, 1),
+ VADD(SVMUL(tr12, tr2), SVMUL(tr11, tr3)));
+ ci3 =
+ VADD(cc_ref(i, 1),
+ VADD(SVMUL(tr12, ti2), SVMUL(tr11, ti3)));
+ cr5 = VADD(SVMUL(ti11, tr5), SVMUL(ti12, tr4));
+ ci5 = VADD(SVMUL(ti11, ti5), SVMUL(ti12, ti4));
+ cr4 = VSUB(SVMUL(ti12, tr5), SVMUL(ti11, tr4));
+ ci4 = VSUB(SVMUL(ti12, ti5), SVMUL(ti11, ti4));
+ dr3 = VSUB(cr3, ci4);
+ dr4 = VADD(cr3, ci4);
+ di3 = VADD(ci3, cr4);
+ di4 = VSUB(ci3, cr4);
+ dr5 = VADD(cr2, ci5);
+ dr2 = VSUB(cr2, ci5);
+ di5 = VSUB(ci2, cr5);
+ di2 = VADD(ci2, cr5);
+ wr1 = wa1[i], wi1 = fsign * wa1[i + 1], wr2 =
+ wa2[i], wi2 = fsign * wa2[i + 1];
+ wr3 = wa3[i], wi3 = fsign * wa3[i + 1], wr4 =
+ wa4[i], wi4 = fsign * wa4[i + 1];
+ VCPLXMUL(dr2, di2, LD_PS1(wr1), LD_PS1(wi1));
+ ch_ref(i - 1, 2) = dr2;
+ ch_ref(i, 2) = di2;
+ VCPLXMUL(dr3, di3, LD_PS1(wr2), LD_PS1(wi2));
+ ch_ref(i - 1, 3) = dr3;
+ ch_ref(i, 3) = di3;
+ VCPLXMUL(dr4, di4, LD_PS1(wr3), LD_PS1(wi3));
+ ch_ref(i - 1, 4) = dr4;
+ ch_ref(i, 4) = di4;
+ VCPLXMUL(dr5, di5, LD_PS1(wr4), LD_PS1(wi4));
+ ch_ref(i - 1, 5) = dr5;
+ ch_ref(i, 5) = di5;
+ }
+ }
+#undef ch_ref
+#undef cc_ref
+}
+
+static NEVER_INLINE(void) radf2_ps(int ido, int l1, const v4sf * RESTRICT cc,
+ v4sf * RESTRICT ch, const float *wa1)
+{
+ static const float minus_one = -1.f;
+ int i, k, l1ido = l1 * ido;
+ for (k = 0; k < l1ido; k += ido) {
+ v4sf a = cc[k], b = cc[k + l1ido];
+ ch[2 * k] = VADD(a, b);
+ ch[2 * (k + ido) - 1] = VSUB(a, b);
+ }
+ if (ido < 2)
+ return;
+ if (ido != 2) {
+ for (k = 0; k < l1ido; k += ido) {
+ for (i = 2; i < ido; i += 2) {
+ v4sf tr2 = cc[i - 1 + k + l1ido], ti2 =
+ cc[i + k + l1ido];
+ v4sf br = cc[i - 1 + k], bi = cc[i + k];
+ VCPLXMULCONJ(tr2, ti2, LD_PS1(wa1[i - 2]),
+ LD_PS1(wa1[i - 1]));
+ ch[i + 2 * k] = VADD(bi, ti2);
+ ch[2 * (k + ido) - i] = VSUB(ti2, bi);
+ ch[i - 1 + 2 * k] = VADD(br, tr2);
+ ch[2 * (k + ido) - i - 1] = VSUB(br, tr2);
+ }
+ }
+ if (ido % 2 == 1)
+ return;
+ }
+ for (k = 0; k < l1ido; k += ido) {
+ ch[2 * k + ido] = SVMUL(minus_one, cc[ido - 1 + k + l1ido]);
+ ch[2 * k + ido - 1] = cc[k + ido - 1];
+ }
+} /* radf2 */
+
+static NEVER_INLINE(void) radb2_ps(int ido, int l1, const v4sf * cc, v4sf * ch,
+ const float *wa1)
+{
+ static const float minus_two = -2;
+ int i, k, l1ido = l1 * ido;
+ v4sf a, b, c, d, tr2, ti2;
+ for (k = 0; k < l1ido; k += ido) {
+ a = cc[2 * k];
+ b = cc[2 * (k + ido) - 1];
+ ch[k] = VADD(a, b);
+ ch[k + l1ido] = VSUB(a, b);
+ }
+ if (ido < 2)
+ return;
+ if (ido != 2) {
+ for (k = 0; k < l1ido; k += ido) {
+ for (i = 2; i < ido; i += 2) {
+ a = cc[i - 1 + 2 * k];
+ b = cc[2 * (k + ido) - i - 1];
+ c = cc[i + 0 + 2 * k];
+ d = cc[2 * (k + ido) - i + 0];
+ ch[i - 1 + k] = VADD(a, b);
+ tr2 = VSUB(a, b);
+ ch[i + 0 + k] = VSUB(c, d);
+ ti2 = VADD(c, d);
+ VCPLXMUL(tr2, ti2, LD_PS1(wa1[i - 2]),
+ LD_PS1(wa1[i - 1]));
+ ch[i - 1 + k + l1ido] = tr2;
+ ch[i + 0 + k + l1ido] = ti2;
+ }
+ }
+ if (ido % 2 == 1)
+ return;
+ }
+ for (k = 0; k < l1ido; k += ido) {
+ a = cc[2 * k + ido - 1];
+ b = cc[2 * k + ido];
+ ch[k + ido - 1] = VADD(a, a);
+ ch[k + ido - 1 + l1ido] = SVMUL(minus_two, b);
+ }
+} /* radb2 */
+
+static void radf3_ps(int ido, int l1, const v4sf * RESTRICT cc,
+ v4sf * RESTRICT ch, const float *wa1, const float *wa2)
+{
+ static const float taur = -0.5f;
+ static const float taui = 0.866025403784439f;
+ int i, k, ic;
+ v4sf ci2, di2, di3, cr2, dr2, dr3, ti2, ti3, tr2, tr3, wr1, wi1, wr2,
+ wi2;
+ for (k = 0; k < l1; k++) {
+ cr2 = VADD(cc[(k + l1) * ido], cc[(k + 2 * l1) * ido]);
+ ch[3 * k * ido] = VADD(cc[k * ido], cr2);
+ ch[(3 * k + 2) * ido] =
+ SVMUL(taui,
+ VSUB(cc[(k + l1 * 2) * ido], cc[(k + l1) * ido]));
+ ch[ido - 1 + (3 * k + 1) * ido] =
+ VADD(cc[k * ido], SVMUL(taur, cr2));
+ }
+ if (ido == 1)
+ return;
+ for (k = 0; k < l1; k++) {
+ for (i = 2; i < ido; i += 2) {
+ ic = ido - i;
+ wr1 = LD_PS1(wa1[i - 2]);
+ wi1 = LD_PS1(wa1[i - 1]);
+ dr2 = cc[i - 1 + (k + l1) * ido];
+ di2 = cc[i + (k + l1) * ido];
+ VCPLXMULCONJ(dr2, di2, wr1, wi1);
+
+ wr2 = LD_PS1(wa2[i - 2]);
+ wi2 = LD_PS1(wa2[i - 1]);
+ dr3 = cc[i - 1 + (k + l1 * 2) * ido];
+ di3 = cc[i + (k + l1 * 2) * ido];
+ VCPLXMULCONJ(dr3, di3, wr2, wi2);
+
+ cr2 = VADD(dr2, dr3);
+ ci2 = VADD(di2, di3);
+ ch[i - 1 + 3 * k * ido] =
+ VADD(cc[i - 1 + k * ido], cr2);
+ ch[i + 3 * k * ido] = VADD(cc[i + k * ido], ci2);
+ tr2 = VADD(cc[i - 1 + k * ido], SVMUL(taur, cr2));
+ ti2 = VADD(cc[i + k * ido], SVMUL(taur, ci2));
+ tr3 = SVMUL(taui, VSUB(di2, di3));
+ ti3 = SVMUL(taui, VSUB(dr3, dr2));
+ ch[i - 1 + (3 * k + 2) * ido] = VADD(tr2, tr3);
+ ch[ic - 1 + (3 * k + 1) * ido] = VSUB(tr2, tr3);
+ ch[i + (3 * k + 2) * ido] = VADD(ti2, ti3);
+ ch[ic + (3 * k + 1) * ido] = VSUB(ti3, ti2);
+ }
+ }
+} /* radf3 */
+
+static void radb3_ps(int ido, int l1, const v4sf * RESTRICT cc,
+ v4sf * RESTRICT ch, const float *wa1, const float *wa2)
+{
+ static const float taur = -0.5f;
+ static const float taui = 0.866025403784439f;
+ static const float taui_2 = 0.866025403784439f * 2;
+ int i, k, ic;
+ v4sf ci2, ci3, di2, di3, cr2, cr3, dr2, dr3, ti2, tr2;
+ for (k = 0; k < l1; k++) {
+ tr2 = cc[ido - 1 + (3 * k + 1) * ido];
+ tr2 = VADD(tr2, tr2);
+ cr2 = VMADD(LD_PS1(taur), tr2, cc[3 * k * ido]);
+ ch[k * ido] = VADD(cc[3 * k * ido], tr2);
+ ci3 = SVMUL(taui_2, cc[(3 * k + 2) * ido]);
+ ch[(k + l1) * ido] = VSUB(cr2, ci3);
+ ch[(k + 2 * l1) * ido] = VADD(cr2, ci3);
+ }
+ if (ido == 1)
+ return;
+ for (k = 0; k < l1; k++) {
+ for (i = 2; i < ido; i += 2) {
+ ic = ido - i;
+ tr2 =
+ VADD(cc[i - 1 + (3 * k + 2) * ido],
+ cc[ic - 1 + (3 * k + 1) * ido]);
+ cr2 = VMADD(LD_PS1(taur), tr2, cc[i - 1 + 3 * k * ido]);
+ ch[i - 1 + k * ido] =
+ VADD(cc[i - 1 + 3 * k * ido], tr2);
+ ti2 =
+ VSUB(cc[i + (3 * k + 2) * ido],
+ cc[ic + (3 * k + 1) * ido]);
+ ci2 = VMADD(LD_PS1(taur), ti2, cc[i + 3 * k * ido]);
+ ch[i + k * ido] = VADD(cc[i + 3 * k * ido], ti2);
+ cr3 =
+ SVMUL(taui,
+ VSUB(cc[i - 1 + (3 * k + 2) * ido],
+ cc[ic - 1 + (3 * k + 1) * ido]));
+ ci3 =
+ SVMUL(taui,
+ VADD(cc[i + (3 * k + 2) * ido],
+ cc[ic + (3 * k + 1) * ido]));
+ dr2 = VSUB(cr2, ci3);
+ dr3 = VADD(cr2, ci3);
+ di2 = VADD(ci2, cr3);
+ di3 = VSUB(ci2, cr3);
+ VCPLXMUL(dr2, di2, LD_PS1(wa1[i - 2]),
+ LD_PS1(wa1[i - 1]));
+ ch[i - 1 + (k + l1) * ido] = dr2;
+ ch[i + (k + l1) * ido] = di2;
+ VCPLXMUL(dr3, di3, LD_PS1(wa2[i - 2]),
+ LD_PS1(wa2[i - 1]));
+ ch[i - 1 + (k + 2 * l1) * ido] = dr3;
+ ch[i + (k + 2 * l1) * ido] = di3;
+ }
+ }
+} /* radb3 */
+
+static NEVER_INLINE(void) radf4_ps(int ido, int l1, const v4sf * RESTRICT cc,
+ v4sf * RESTRICT ch,
+ const float *RESTRICT wa1,
+ const float *RESTRICT wa2,
+ const float *RESTRICT wa3)
+{
+ static const float minus_hsqt2 = (float)-0.7071067811865475;
+ int i, k, l1ido = l1 * ido;
+ {
+ const v4sf *RESTRICT cc_ = cc, *RESTRICT cc_end = cc + l1ido;
+ v4sf *RESTRICT ch_ = ch;
+ while (cc < cc_end) {
+ // this loop represents between 25% and 40% of total radf4_ps cost !
+ v4sf a0 = cc[0], a1 = cc[l1ido];
+ v4sf a2 = cc[2 * l1ido], a3 = cc[3 * l1ido];
+ v4sf tr1 = VADD(a1, a3);
+ v4sf tr2 = VADD(a0, a2);
+ ch[2 * ido - 1] = VSUB(a0, a2);
+ ch[2 * ido] = VSUB(a3, a1);
+ ch[0] = VADD(tr1, tr2);
+ ch[4 * ido - 1] = VSUB(tr2, tr1);
+ cc += ido;
+ ch += 4 * ido;
+ }
+ cc = cc_;
+ ch = ch_;
+ }
+ if (ido < 2)
+ return;
+ if (ido != 2) {
+ for (k = 0; k < l1ido; k += ido) {
+ const v4sf *RESTRICT pc = (v4sf *) (cc + 1 + k);
+ for (i = 2; i < ido; i += 2, pc += 2) {
+ int ic = ido - i;
+ v4sf wr, wi, cr2, ci2, cr3, ci3, cr4, ci4;
+ v4sf tr1, ti1, tr2, ti2, tr3, ti3, tr4, ti4;
+
+ cr2 = pc[1 * l1ido + 0];
+ ci2 = pc[1 * l1ido + 1];
+ wr = LD_PS1(wa1[i - 2]);
+ wi = LD_PS1(wa1[i - 1]);
+ VCPLXMULCONJ(cr2, ci2, wr, wi);
+
+ cr3 = pc[2 * l1ido + 0];
+ ci3 = pc[2 * l1ido + 1];
+ wr = LD_PS1(wa2[i - 2]);
+ wi = LD_PS1(wa2[i - 1]);
+ VCPLXMULCONJ(cr3, ci3, wr, wi);
+
+ cr4 = pc[3 * l1ido];
+ ci4 = pc[3 * l1ido + 1];
+ wr = LD_PS1(wa3[i - 2]);
+ wi = LD_PS1(wa3[i - 1]);
+ VCPLXMULCONJ(cr4, ci4, wr, wi);
+
+ /* at this point, on SSE, five of "cr2 cr3 cr4 ci2 ci3 ci4" should be loaded in registers */
+
+ tr1 = VADD(cr2, cr4);
+ tr4 = VSUB(cr4, cr2);
+ tr2 = VADD(pc[0], cr3);
+ tr3 = VSUB(pc[0], cr3);
+ ch[i - 1 + 4 * k] = VADD(tr1, tr2);
+ ch[ic - 1 + 4 * k + 3 * ido] = VSUB(tr2, tr1); // at this point tr1 and tr2 can be disposed
+ ti1 = VADD(ci2, ci4);
+ ti4 = VSUB(ci2, ci4);
+ ch[i - 1 + 4 * k + 2 * ido] = VADD(ti4, tr3);
+ ch[ic - 1 + 4 * k + 1 * ido] = VSUB(tr3, ti4); // dispose tr3, ti4
+ ti2 = VADD(pc[1], ci3);
+ ti3 = VSUB(pc[1], ci3);
+ ch[i + 4 * k] = VADD(ti1, ti2);
+ ch[ic + 4 * k + 3 * ido] = VSUB(ti1, ti2);
+ ch[i + 4 * k + 2 * ido] = VADD(tr4, ti3);
+ ch[ic + 4 * k + 1 * ido] = VSUB(tr4, ti3);
+ }
+ }
+ if (ido % 2 == 1)
+ return;
+ }
+ for (k = 0; k < l1ido; k += ido) {
+ v4sf a = cc[ido - 1 + k + l1ido], b =
+ cc[ido - 1 + k + 3 * l1ido];
+ v4sf c = cc[ido - 1 + k], d = cc[ido - 1 + k + 2 * l1ido];
+ v4sf ti1 = SVMUL(minus_hsqt2, VADD(a, b));
+ v4sf tr1 = SVMUL(minus_hsqt2, VSUB(b, a));
+ ch[ido - 1 + 4 * k] = VADD(tr1, c);
+ ch[ido - 1 + 4 * k + 2 * ido] = VSUB(c, tr1);
+ ch[4 * k + 1 * ido] = VSUB(ti1, d);
+ ch[4 * k + 3 * ido] = VADD(ti1, d);
+ }
+} /* radf4 */
+
+static NEVER_INLINE(void) radb4_ps(int ido, int l1, const v4sf * RESTRICT cc,
+ v4sf * RESTRICT ch,
+ const float *RESTRICT wa1,
+ const float *RESTRICT wa2,
+ const float *RESTRICT wa3)
+{
+ static const float minus_sqrt2 = (float)-1.414213562373095;
+ static const float two = 2.f;
+ int i, k, l1ido = l1 * ido;
+ v4sf ci2, ci3, ci4, cr2, cr3, cr4, ti1, ti2, ti3, ti4, tr1, tr2, tr3,
+ tr4;
+ {
+ const v4sf *RESTRICT cc_ = cc, *RESTRICT ch_end = ch + l1ido;
+ v4sf *ch_ = ch;
+ while (ch < ch_end) {
+ v4sf a = cc[0], b = cc[4 * ido - 1];
+ v4sf c = cc[2 * ido], d = cc[2 * ido - 1];
+ tr3 = SVMUL(two, d);
+ tr2 = VADD(a, b);
+ tr1 = VSUB(a, b);
+ tr4 = SVMUL(two, c);
+ ch[0 * l1ido] = VADD(tr2, tr3);
+ ch[2 * l1ido] = VSUB(tr2, tr3);
+ ch[1 * l1ido] = VSUB(tr1, tr4);
+ ch[3 * l1ido] = VADD(tr1, tr4);
+
+ cc += 4 * ido;
+ ch += ido;
+ }
+ cc = cc_;
+ ch = ch_;
+ }
+ if (ido < 2)
+ return;
+ if (ido != 2) {
+ for (k = 0; k < l1ido; k += ido) {
+ const v4sf *RESTRICT pc = (v4sf *) (cc - 1 + 4 * k);
+ v4sf *RESTRICT ph = (v4sf *) (ch + k + 1);
+ for (i = 2; i < ido; i += 2) {
+
+ tr1 = VSUB(pc[i], pc[4 * ido - i]);
+ tr2 = VADD(pc[i], pc[4 * ido - i]);
+ ti4 = VSUB(pc[2 * ido + i], pc[2 * ido - i]);
+ tr3 = VADD(pc[2 * ido + i], pc[2 * ido - i]);
+ ph[0] = VADD(tr2, tr3);
+ cr3 = VSUB(tr2, tr3);
+
+ ti3 =
+ VSUB(pc[2 * ido + i + 1],
+ pc[2 * ido - i + 1]);
+ tr4 =
+ VADD(pc[2 * ido + i + 1],
+ pc[2 * ido - i + 1]);
+ cr2 = VSUB(tr1, tr4);
+ cr4 = VADD(tr1, tr4);
+
+ ti1 = VADD(pc[i + 1], pc[4 * ido - i + 1]);
+ ti2 = VSUB(pc[i + 1], pc[4 * ido - i + 1]);
+
+ ph[1] = VADD(ti2, ti3);
+ ph += l1ido;
+ ci3 = VSUB(ti2, ti3);
+ ci2 = VADD(ti1, ti4);
+ ci4 = VSUB(ti1, ti4);
+ VCPLXMUL(cr2, ci2, LD_PS1(wa1[i - 2]),
+ LD_PS1(wa1[i - 1]));
+ ph[0] = cr2;
+ ph[1] = ci2;
+ ph += l1ido;
+ VCPLXMUL(cr3, ci3, LD_PS1(wa2[i - 2]),
+ LD_PS1(wa2[i - 1]));
+ ph[0] = cr3;
+ ph[1] = ci3;
+ ph += l1ido;
+ VCPLXMUL(cr4, ci4, LD_PS1(wa3[i - 2]),
+ LD_PS1(wa3[i - 1]));
+ ph[0] = cr4;
+ ph[1] = ci4;
+ ph = ph - 3 * l1ido + 2;
+ }
+ }
+ if (ido % 2 == 1)
+ return;
+ }
+ for (k = 0; k < l1ido; k += ido) {
+ int i0 = 4 * k + ido;
+ v4sf c = cc[i0 - 1], d = cc[i0 + 2 * ido - 1];
+ v4sf a = cc[i0 + 0], b = cc[i0 + 2 * ido + 0];
+ tr1 = VSUB(c, d);
+ tr2 = VADD(c, d);
+ ti1 = VADD(b, a);
+ ti2 = VSUB(b, a);
+ ch[ido - 1 + k + 0 * l1ido] = VADD(tr2, tr2);
+ ch[ido - 1 + k + 1 * l1ido] =
+ SVMUL(minus_sqrt2, VSUB(ti1, tr1));
+ ch[ido - 1 + k + 2 * l1ido] = VADD(ti2, ti2);
+ ch[ido - 1 + k + 3 * l1ido] =
+ SVMUL(minus_sqrt2, VADD(ti1, tr1));
+ }
+} /* radb4 */
+
+static void radf5_ps(int ido, int l1, const v4sf * RESTRICT cc,
+ v4sf * RESTRICT ch, const float *wa1, const float *wa2,
+ const float *wa3, const float *wa4)
+{
+ static const float tr11 = .309016994374947f;
+ static const float ti11 = .951056516295154f;
+ static const float tr12 = -.809016994374947f;
+ static const float ti12 = .587785252292473f;
+
+ /* System generated locals */
+ int cc_offset, ch_offset;
+
+ /* Local variables */
+ int i, k, ic;
+ v4sf ci2, di2, ci4, ci5, di3, di4, di5, ci3, cr2, cr3, dr2, dr3, dr4,
+ dr5, cr5, cr4, ti2, ti3, ti5, ti4, tr2, tr3, tr4, tr5;
+ int idp2;
+
+#define cc_ref(a_1,a_2,a_3) cc[((a_3)*l1 + (a_2))*ido + a_1]
+#define ch_ref(a_1,a_2,a_3) ch[((a_3)*5 + (a_2))*ido + a_1]
+
+ /* Parameter adjustments */
+ ch_offset = 1 + ido * 6;
+ ch -= ch_offset;
+ cc_offset = 1 + ido * (1 + l1);
+ cc -= cc_offset;
+
+ /* Function Body */
+ for (k = 1; k <= l1; ++k) {
+ cr2 = VADD(cc_ref(1, k, 5), cc_ref(1, k, 2));
+ ci5 = VSUB(cc_ref(1, k, 5), cc_ref(1, k, 2));
+ cr3 = VADD(cc_ref(1, k, 4), cc_ref(1, k, 3));
+ ci4 = VSUB(cc_ref(1, k, 4), cc_ref(1, k, 3));
+ ch_ref(1, 1, k) = VADD(cc_ref(1, k, 1), VADD(cr2, cr3));
+ ch_ref(ido, 2, k) =
+ VADD(cc_ref(1, k, 1),
+ VADD(SVMUL(tr11, cr2), SVMUL(tr12, cr3)));
+ ch_ref(1, 3, k) = VADD(SVMUL(ti11, ci5), SVMUL(ti12, ci4));
+ ch_ref(ido, 4, k) =
+ VADD(cc_ref(1, k, 1),
+ VADD(SVMUL(tr12, cr2), SVMUL(tr11, cr3)));
+ ch_ref(1, 5, k) = VSUB(SVMUL(ti12, ci5), SVMUL(ti11, ci4));
+ //printf("pffft: radf5, k=%d ch_ref=%f, ci4=%f\n", k, ch_ref(1, 5, k), ci4);
+ }
+ if (ido == 1) {
+ return;
+ }
+ idp2 = ido + 2;
+ for (k = 1; k <= l1; ++k) {
+ for (i = 3; i <= ido; i += 2) {
+ ic = idp2 - i;
+ dr2 = LD_PS1(wa1[i - 3]);
+ di2 = LD_PS1(wa1[i - 2]);
+ dr3 = LD_PS1(wa2[i - 3]);
+ di3 = LD_PS1(wa2[i - 2]);
+ dr4 = LD_PS1(wa3[i - 3]);
+ di4 = LD_PS1(wa3[i - 2]);
+ dr5 = LD_PS1(wa4[i - 3]);
+ di5 = LD_PS1(wa4[i - 2]);
+ VCPLXMULCONJ(dr2, di2, cc_ref(i - 1, k, 2),
+ cc_ref(i, k, 2));
+ VCPLXMULCONJ(dr3, di3, cc_ref(i - 1, k, 3),
+ cc_ref(i, k, 3));
+ VCPLXMULCONJ(dr4, di4, cc_ref(i - 1, k, 4),
+ cc_ref(i, k, 4));
+ VCPLXMULCONJ(dr5, di5, cc_ref(i - 1, k, 5),
+ cc_ref(i, k, 5));
+ cr2 = VADD(dr2, dr5);
+ ci5 = VSUB(dr5, dr2);
+ cr5 = VSUB(di2, di5);
+ ci2 = VADD(di2, di5);
+ cr3 = VADD(dr3, dr4);
+ ci4 = VSUB(dr4, dr3);
+ cr4 = VSUB(di3, di4);
+ ci3 = VADD(di3, di4);
+ ch_ref(i - 1, 1, k) =
+ VADD(cc_ref(i - 1, k, 1), VADD(cr2, cr3));
+ ch_ref(i, 1, k) = VSUB(cc_ref(i, k, 1), VADD(ci2, ci3)); //
+ tr2 =
+ VADD(cc_ref(i - 1, k, 1),
+ VADD(SVMUL(tr11, cr2), SVMUL(tr12, cr3)));
+ ti2 = VSUB(cc_ref(i, k, 1), VADD(SVMUL(tr11, ci2), SVMUL(tr12, ci3))); //
+ tr3 =
+ VADD(cc_ref(i - 1, k, 1),
+ VADD(SVMUL(tr12, cr2), SVMUL(tr11, cr3)));
+ ti3 = VSUB(cc_ref(i, k, 1), VADD(SVMUL(tr12, ci2), SVMUL(tr11, ci3))); //
+ tr5 = VADD(SVMUL(ti11, cr5), SVMUL(ti12, cr4));
+ ti5 = VADD(SVMUL(ti11, ci5), SVMUL(ti12, ci4));
+ tr4 = VSUB(SVMUL(ti12, cr5), SVMUL(ti11, cr4));
+ ti4 = VSUB(SVMUL(ti12, ci5), SVMUL(ti11, ci4));
+ ch_ref(i - 1, 3, k) = VSUB(tr2, tr5);
+ ch_ref(ic - 1, 2, k) = VADD(tr2, tr5);
+ ch_ref(i, 3, k) = VADD(ti2, ti5);
+ ch_ref(ic, 2, k) = VSUB(ti5, ti2);
+ ch_ref(i - 1, 5, k) = VSUB(tr3, tr4);
+ ch_ref(ic - 1, 4, k) = VADD(tr3, tr4);
+ ch_ref(i, 5, k) = VADD(ti3, ti4);
+ ch_ref(ic, 4, k) = VSUB(ti4, ti3);
+ }
+ }
+#undef cc_ref
+#undef ch_ref
+} /* radf5 */
+
+static void radb5_ps(int ido, int l1, const v4sf * RESTRICT cc,
+ v4sf * RESTRICT ch, const float *wa1, const float *wa2,
+ const float *wa3, const float *wa4)
+{
+ static const float tr11 = .309016994374947f;
+ static const float ti11 = .951056516295154f;
+ static const float tr12 = -.809016994374947f;
+ static const float ti12 = .587785252292473f;
+
+ int cc_offset, ch_offset;
+
+ /* Local variables */
+ int i, k, ic;
+ v4sf ci2, ci3, ci4, ci5, di3, di4, di5, di2, cr2, cr3, cr5, cr4, ti2,
+ ti3, ti4, ti5, dr3, dr4, dr5, dr2, tr2, tr3, tr4, tr5;
+ int idp2;
+
+#define cc_ref(a_1,a_2,a_3) cc[((a_3)*5 + (a_2))*ido + a_1]
+#define ch_ref(a_1,a_2,a_3) ch[((a_3)*l1 + (a_2))*ido + a_1]
+
+ /* Parameter adjustments */
+ ch_offset = 1 + ido * (1 + l1);
+ ch -= ch_offset;
+ cc_offset = 1 + ido * 6;
+ cc -= cc_offset;
+
+ /* Function Body */
+ for (k = 1; k <= l1; ++k) {
+ ti5 = VADD(cc_ref(1, 3, k), cc_ref(1, 3, k));
+ ti4 = VADD(cc_ref(1, 5, k), cc_ref(1, 5, k));
+ tr2 = VADD(cc_ref(ido, 2, k), cc_ref(ido, 2, k));
+ tr3 = VADD(cc_ref(ido, 4, k), cc_ref(ido, 4, k));
+ ch_ref(1, k, 1) = VADD(cc_ref(1, 1, k), VADD(tr2, tr3));
+ cr2 =
+ VADD(cc_ref(1, 1, k),
+ VADD(SVMUL(tr11, tr2), SVMUL(tr12, tr3)));
+ cr3 =
+ VADD(cc_ref(1, 1, k),
+ VADD(SVMUL(tr12, tr2), SVMUL(tr11, tr3)));
+ ci5 = VADD(SVMUL(ti11, ti5), SVMUL(ti12, ti4));
+ ci4 = VSUB(SVMUL(ti12, ti5), SVMUL(ti11, ti4));
+ ch_ref(1, k, 2) = VSUB(cr2, ci5);
+ ch_ref(1, k, 3) = VSUB(cr3, ci4);
+ ch_ref(1, k, 4) = VADD(cr3, ci4);
+ ch_ref(1, k, 5) = VADD(cr2, ci5);
+ }
+ if (ido == 1) {
+ return;
+ }
+ idp2 = ido + 2;
+ for (k = 1; k <= l1; ++k) {
+ for (i = 3; i <= ido; i += 2) {
+ ic = idp2 - i;
+ ti5 = VADD(cc_ref(i, 3, k), cc_ref(ic, 2, k));
+ ti2 = VSUB(cc_ref(i, 3, k), cc_ref(ic, 2, k));
+ ti4 = VADD(cc_ref(i, 5, k), cc_ref(ic, 4, k));
+ ti3 = VSUB(cc_ref(i, 5, k), cc_ref(ic, 4, k));
+ tr5 = VSUB(cc_ref(i - 1, 3, k), cc_ref(ic - 1, 2, k));
+ tr2 = VADD(cc_ref(i - 1, 3, k), cc_ref(ic - 1, 2, k));
+ tr4 = VSUB(cc_ref(i - 1, 5, k), cc_ref(ic - 1, 4, k));
+ tr3 = VADD(cc_ref(i - 1, 5, k), cc_ref(ic - 1, 4, k));
+ ch_ref(i - 1, k, 1) =
+ VADD(cc_ref(i - 1, 1, k), VADD(tr2, tr3));
+ ch_ref(i, k, 1) = VADD(cc_ref(i, 1, k), VADD(ti2, ti3));
+ cr2 =
+ VADD(cc_ref(i - 1, 1, k),
+ VADD(SVMUL(tr11, tr2), SVMUL(tr12, tr3)));
+ ci2 =
+ VADD(cc_ref(i, 1, k),
+ VADD(SVMUL(tr11, ti2), SVMUL(tr12, ti3)));
+ cr3 =
+ VADD(cc_ref(i - 1, 1, k),
+ VADD(SVMUL(tr12, tr2), SVMUL(tr11, tr3)));
+ ci3 =
+ VADD(cc_ref(i, 1, k),
+ VADD(SVMUL(tr12, ti2), SVMUL(tr11, ti3)));
+ cr5 = VADD(SVMUL(ti11, tr5), SVMUL(ti12, tr4));
+ ci5 = VADD(SVMUL(ti11, ti5), SVMUL(ti12, ti4));
+ cr4 = VSUB(SVMUL(ti12, tr5), SVMUL(ti11, tr4));
+ ci4 = VSUB(SVMUL(ti12, ti5), SVMUL(ti11, ti4));
+ dr3 = VSUB(cr3, ci4);
+ dr4 = VADD(cr3, ci4);
+ di3 = VADD(ci3, cr4);
+ di4 = VSUB(ci3, cr4);
+ dr5 = VADD(cr2, ci5);
+ dr2 = VSUB(cr2, ci5);
+ di5 = VSUB(ci2, cr5);
+ di2 = VADD(ci2, cr5);
+ VCPLXMUL(dr2, di2, LD_PS1(wa1[i - 3]),
+ LD_PS1(wa1[i - 2]));
+ VCPLXMUL(dr3, di3, LD_PS1(wa2[i - 3]),
+ LD_PS1(wa2[i - 2]));
+ VCPLXMUL(dr4, di4, LD_PS1(wa3[i - 3]),
+ LD_PS1(wa3[i - 2]));
+ VCPLXMUL(dr5, di5, LD_PS1(wa4[i - 3]),
+ LD_PS1(wa4[i - 2]));
+
+ ch_ref(i - 1, k, 2) = dr2;
+ ch_ref(i, k, 2) = di2;
+ ch_ref(i - 1, k, 3) = dr3;
+ ch_ref(i, k, 3) = di3;
+ ch_ref(i - 1, k, 4) = dr4;
+ ch_ref(i, k, 4) = di4;
+ ch_ref(i - 1, k, 5) = dr5;
+ ch_ref(i, k, 5) = di5;
+ }
+ }
+#undef cc_ref
+#undef ch_ref
+} /* radb5 */
+
+static NEVER_INLINE(v4sf *) rfftf1_ps(int n, const v4sf * input_readonly,
+ v4sf * work1, v4sf * work2,
+ const float *wa, const int *ifac)
+{
+ v4sf *in = (v4sf *) input_readonly;
+ v4sf *out = (in == work2 ? work1 : work2);
+ int nf = ifac[1], k1;
+ int l2 = n;
+ int iw = n - 1;
+ assert(in != out && work1 != work2);
+ for (k1 = 1; k1 <= nf; ++k1) {
+ int kh = nf - k1;
+ int ip = ifac[kh + 2];
+ int l1 = l2 / ip;
+ int ido = n / l2;
+ iw -= (ip - 1) * ido;
+ switch (ip) {
+ case 5:{
+ int ix2 = iw + ido;
+ int ix3 = ix2 + ido;
+ int ix4 = ix3 + ido;
+ radf5_ps(ido, l1, in, out, &wa[iw], &wa[ix2],
+ &wa[ix3], &wa[ix4]);
+ } break;
+ case 4:{
+ int ix2 = iw + ido;
+ int ix3 = ix2 + ido;
+ radf4_ps(ido, l1, in, out, &wa[iw], &wa[ix2],
+ &wa[ix3]);
+ } break;
+ case 3:{
+ int ix2 = iw + ido;
+ radf3_ps(ido, l1, in, out, &wa[iw], &wa[ix2]);
+ } break;
+ case 2:
+ radf2_ps(ido, l1, in, out, &wa[iw]);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ l2 = l1;
+ if (out == work2) {
+ out = work1;
+ in = work2;
+ } else {
+ out = work2;
+ in = work1;
+ }
+ }
+ return in; /* this is in fact the output .. */
+} /* rfftf1 */
+
+static NEVER_INLINE(v4sf *) rfftb1_ps(int n, const v4sf * input_readonly,
+ v4sf * work1, v4sf * work2,
+ const float *wa, const int *ifac)
+{
+ v4sf *in = (v4sf *) input_readonly;
+ v4sf *out = (in == work2 ? work1 : work2);
+ int nf = ifac[1], k1;
+ int l1 = 1;
+ int iw = 0;
+ assert(in != out);
+ for (k1 = 1; k1 <= nf; k1++) {
+ int ip = ifac[k1 + 1];
+ int l2 = ip * l1;
+ int ido = n / l2;
+ switch (ip) {
+ case 5:{
+ int ix2 = iw + ido;
+ int ix3 = ix2 + ido;
+ int ix4 = ix3 + ido;
+ radb5_ps(ido, l1, in, out, &wa[iw], &wa[ix2],
+ &wa[ix3], &wa[ix4]);
+ } break;
+ case 4:{
+ int ix2 = iw + ido;
+ int ix3 = ix2 + ido;
+ radb4_ps(ido, l1, in, out, &wa[iw], &wa[ix2],
+ &wa[ix3]);
+ } break;
+ case 3:{
+ int ix2 = iw + ido;
+ radb3_ps(ido, l1, in, out, &wa[iw], &wa[ix2]);
+ } break;
+ case 2:
+ radb2_ps(ido, l1, in, out, &wa[iw]);
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ l1 = l2;
+ iw += (ip - 1) * ido;
+
+ if (out == work2) {
+ out = work1;
+ in = work2;
+ } else {
+ out = work2;
+ in = work1;
+ }
+ }
+ return in; /* this is in fact the output .. */
+}
+
+static int decompose(int n, int *ifac, const int *ntryh)
+{
+ int nl = n, nf = 0, i, j = 0;
+ for (j = 0; ntryh[j]; ++j) {
+ int ntry = ntryh[j];
+ while (nl != 1) {
+ int nq = nl / ntry;
+ int nr = nl - ntry * nq;
+ if (nr == 0) {
+ ifac[2 + nf++] = ntry;
+ nl = nq;
+ if (ntry == 2 && nf != 1) {
+ for (i = 2; i <= nf; ++i) {
+ int ib = nf - i + 2;
+ ifac[ib + 1] = ifac[ib];
+ }
+ ifac[2] = 2;
+ }
+ } else
+ break;
+ }
+ }
+ ifac[0] = n;
+ ifac[1] = nf;
+ return nf;
+}
+
+static void rffti1_ps(int n, float *wa, int *ifac)
+{
+ static const int ntryh[] = { 4, 2, 3, 5, 0 };
+ int k1, j, ii;
+
+ int nf = decompose(n, ifac, ntryh);
+ float argh = (2 * M_PI) / n;
+ int is = 0;
+ int nfm1 = nf - 1;
+ int l1 = 1;
+ for (k1 = 1; k1 <= nfm1; k1++) {
+ int ip = ifac[k1 + 1];
+ int ld = 0;
+ int l2 = l1 * ip;
+ int ido = n / l2;
+ int ipm = ip - 1;
+ for (j = 1; j <= ipm; ++j) {
+ float argld;
+ int i = is, fi = 0;
+ ld += l1;
+ argld = ld * argh;
+ for (ii = 3; ii <= ido; ii += 2) {
+ i += 2;
+ fi += 1;
+ wa[i - 2] = cos(fi * argld);
+ wa[i - 1] = sin(fi * argld);
+ }
+ is += ido;
+ }
+ l1 = l2;
+ }
+} /* rffti1 */
+
+static void cffti1_ps(int n, float *wa, int *ifac)
+{
+ static const int ntryh[] = { 5, 3, 4, 2, 0 };
+ int k1, j, ii;
+
+ int nf = decompose(n, ifac, ntryh);
+ float argh = (2 * M_PI) / (float)n;
+ int i = 1;
+ int l1 = 1;
+ for (k1 = 1; k1 <= nf; k1++) {
+ int ip = ifac[k1 + 1];
+ int ld = 0;
+ int l2 = l1 * ip;
+ int ido = n / l2;
+ int idot = ido + ido + 2;
+ int ipm = ip - 1;
+ for (j = 1; j <= ipm; j++) {
+ float argld;
+ int i1 = i, fi = 0;
+ wa[i - 1] = 1;
+ wa[i] = 0;
+ ld += l1;
+ argld = ld * argh;
+ for (ii = 4; ii <= idot; ii += 2) {
+ i += 2;
+ fi += 1;
+ wa[i - 1] = cos(fi * argld);
+ wa[i] = sin(fi * argld);
+ }
+ if (ip > 5) {
+ wa[i1 - 1] = wa[i - 1];
+ wa[i1] = wa[i];
+ }
+ }
+ l1 = l2;
+ }
+} /* cffti1 */
+
+static v4sf *cfftf1_ps(int n, const v4sf * input_readonly, v4sf * work1, v4sf * work2,
+ const float *wa, const int *ifac, int isign)
+{
+ v4sf *in = (v4sf *) input_readonly;
+ v4sf *out = (in == work2 ? work1 : work2);
+ int nf = ifac[1], k1;
+ int l1 = 1;
+ int iw = 0;
+ assert(in != out && work1 != work2);
+ for (k1 = 2; k1 <= nf + 1; k1++) {
+ int ip = ifac[k1];
+ int l2 = ip * l1;
+ int ido = n / l2;
+ int idot = ido + ido;
+ switch (ip) {
+ case 5:{
+ int ix2 = iw + idot;
+ int ix3 = ix2 + idot;
+ int ix4 = ix3 + idot;
+ passf5_ps(idot, l1, in, out, &wa[iw], &wa[ix2],
+ &wa[ix3], &wa[ix4], isign);
+ } break;
+ case 4:{
+ int ix2 = iw + idot;
+ int ix3 = ix2 + idot;
+ passf4_ps(idot, l1, in, out, &wa[iw], &wa[ix2],
+ &wa[ix3], isign);
+ } break;
+ case 2:{
+ passf2_ps(idot, l1, in, out, &wa[iw], isign);
+ }
+ break;
+ case 3:{
+ int ix2 = iw + idot;
+ passf3_ps(idot, l1, in, out, &wa[iw], &wa[ix2],
+ isign);
+ } break;
+ default:
+ assert(0);
+ }
+ l1 = l2;
+ iw += (ip - 1) * idot;
+ if (out == work2) {
+ out = work1;
+ in = work2;
+ } else {
+ out = work2;
+ in = work1;
+ }
+ }
+
+ return in; /* this is in fact the output .. */
+}
+
+struct PFFFT_Setup {
+ int N;
+ int Ncvec; // nb of complex simd vectors (N/4 if PFFFT_COMPLEX, N/8 if PFFFT_REAL)
+ int ifac[15];
+ pffft_transform_t transform;
+ v4sf *data; // allocated room for twiddle coefs
+ float *e; // points into 'data' , N/4*3 elements
+ float *twiddle; // points into 'data', N/4 elements
+};
+
+struct funcs {
+ PFFFT_Setup * (*new_setup) (int N, pffft_transform_t transform);
+ void (*transform) (PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction, int ordered);
+ void (*zreorder)(PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction);
+ void (*zconvolve_accumulate)(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, const float *dft_c, float *dft_ab, float scaling);
+ void (*zconvolve)(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling);
+ int (*simd_size)(void);
+ void (*validate)(void);
+};
+
+static PFFFT_Setup *new_setup_simd(int N, pffft_transform_t transform)
+{
+ PFFFT_Setup *s = (PFFFT_Setup *) malloc(sizeof(PFFFT_Setup));
+ int k, m;
+ /* unfortunately, the fft size must be a multiple of 16 for complex FFTs
+ and 32 for real FFTs -- a lot of stuff would need to be rewritten to
+ handle other cases (or maybe just switch to a scalar fft, I don't know..) */
+ if (transform == PFFFT_REAL) {
+ assert((N % (2 * SIMD_SZ * SIMD_SZ)) == 0 && N > 0);
+ }
+ if (transform == PFFFT_COMPLEX) {
+ assert((N % (SIMD_SZ * SIMD_SZ)) == 0 && N > 0);
+ }
+ //assert((N % 32) == 0);
+ s->N = N;
+ s->transform = transform;
+ /* nb of complex simd vectors */
+ s->Ncvec = (transform == PFFFT_REAL ? N / 2 : N) / SIMD_SZ;
+ s->data = (v4sf *) pffft_aligned_malloc(2 * s->Ncvec * sizeof(v4sf));
+ s->e = (float *)s->data;
+ s->twiddle =
+ (float *)(s->data + (2 * s->Ncvec * (SIMD_SZ - 1)) / SIMD_SZ);
+
+ if (transform == PFFFT_REAL) {
+ for (k = 0; k < s->Ncvec; ++k) {
+ int i = k / SIMD_SZ;
+ int j = k % SIMD_SZ;
+ for (m = 0; m < SIMD_SZ - 1; ++m) {
+ float A = -2 * M_PI * (m + 1) * k / N;
+ s->e[(2 * (i * 3 + m) + 0) * SIMD_SZ + j] =
+ cos(A);
+ s->e[(2 * (i * 3 + m) + 1) * SIMD_SZ + j] =
+ sin(A);
+ }
+ }
+ rffti1_ps(N / SIMD_SZ, s->twiddle, s->ifac);
+ } else {
+ for (k = 0; k < s->Ncvec; ++k) {
+ int i = k / SIMD_SZ;
+ int j = k % SIMD_SZ;
+ for (m = 0; m < SIMD_SZ - 1; ++m) {
+ float A = -2 * M_PI * (m + 1) * k / N;
+ s->e[(2 * (i * 3 + m) + 0) * SIMD_SZ + j] =
+ cos(A);
+ s->e[(2 * (i * 3 + m) + 1) * SIMD_SZ + j] =
+ sin(A);
+ }
+ }
+ cffti1_ps(N / SIMD_SZ, s->twiddle, s->ifac);
+ }
+
+ /* check that N is decomposable with allowed prime factors */
+ for (k = 0, m = 1; k < s->ifac[1]; ++k) {
+ m *= s->ifac[2 + k];
+ }
+ if (m != N / SIMD_SZ) {
+ pffft_destroy_setup(s);
+ s = 0;
+ }
+
+ return s;
+}
+
+#if !defined(PFFFT_SIMD_DISABLE)
+
+/* [0 0 1 2 3 4 5 6 7 8] -> [0 8 7 6 5 4 3 2 1] */
+static void reversed_copy(int N, const v4sf * in, int in_stride, v4sf * out)
+{
+ v4sf g0, g1;
+ int k;
+ INTERLEAVE2(in[0], in[1], g0, g1);
+ in += in_stride;
+
+ *--out = VSWAPHL(g0, g1); // [g0l, g0h], [g1l g1h] -> [g1l, g0h]
+ for (k = 1; k < N; ++k) {
+ v4sf h0, h1;
+ INTERLEAVE2(in[0], in[1], h0, h1);
+ in += in_stride;
+ *--out = VSWAPHL(g1, h0);
+ *--out = VSWAPHL(h0, h1);
+ g1 = h1;
+ }
+ *--out = VSWAPHL(g1, g0);
+}
+
+static void unreversed_copy(int N, const v4sf * in, v4sf * out, int out_stride)
+{
+ v4sf g0, g1, h0, h1;
+ int k;
+ g0 = g1 = in[0];
+ ++in;
+ for (k = 1; k < N; ++k) {
+ h0 = *in++;
+ h1 = *in++;
+ g1 = VSWAPHL(g1, h0);
+ h0 = VSWAPHL(h0, h1);
+ UNINTERLEAVE2(h0, g1, out[0], out[1]);
+ out += out_stride;
+ g1 = h1;
+ }
+ h0 = *in++;
+ h1 = g0;
+ g1 = VSWAPHL(g1, h0);
+ h0 = VSWAPHL(h0, h1);
+ UNINTERLEAVE2(h0, g1, out[0], out[1]);
+}
+
+static void zreorder_simd(PFFFT_Setup * setup, const float *in, float *out,
+ pffft_direction_t direction)
+{
+ int k, N = setup->N, Ncvec = setup->Ncvec;
+ const v4sf *vin = (const v4sf *)in;
+ v4sf *vout = (v4sf *) out;
+ assert(in != out);
+ if (setup->transform == PFFFT_REAL) {
+ int k, dk = N / 32;
+ if (direction == PFFFT_FORWARD) {
+ for (k = 0; k < dk; ++k) {
+ INTERLEAVE2(vin[k * 8 + 0], vin[k * 8 + 1],
+ vout[2 * (0 * dk + k) + 0],
+ vout[2 * (0 * dk + k) + 1]);
+ INTERLEAVE2(vin[k * 8 + 4], vin[k * 8 + 5],
+ vout[2 * (2 * dk + k) + 0],
+ vout[2 * (2 * dk + k) + 1]);
+ }
+ reversed_copy(dk, vin + 2, 8, (v4sf *) (out + N / 2));
+ reversed_copy(dk, vin + 6, 8, (v4sf *) (out + N));
+ } else {
+ for (k = 0; k < dk; ++k) {
+ UNINTERLEAVE2(vin[2 * (0 * dk + k) + 0],
+ vin[2 * (0 * dk + k) + 1],
+ vout[k * 8 + 0], vout[k * 8 + 1]);
+ UNINTERLEAVE2(vin[2 * (2 * dk + k) + 0],
+ vin[2 * (2 * dk + k) + 1],
+ vout[k * 8 + 4], vout[k * 8 + 5]);
+ }
+ unreversed_copy(dk, (v4sf *) (in + N / 4),
+ (v4sf *) (out + N - 6 * SIMD_SZ), -8);
+ unreversed_copy(dk, (v4sf *) (in + 3 * N / 4),
+ (v4sf *) (out + N - 2 * SIMD_SZ), -8);
+ }
+ } else {
+ if (direction == PFFFT_FORWARD) {
+ for (k = 0; k < Ncvec; ++k) {
+ int kk = (k / 4) + (k % 4) * (Ncvec / 4);
+ INTERLEAVE2(vin[k * 2], vin[k * 2 + 1],
+ vout[kk * 2], vout[kk * 2 + 1]);
+ }
+ } else {
+ for (k = 0; k < Ncvec; ++k) {
+ int kk = (k / 4) + (k % 4) * (Ncvec / 4);
+ UNINTERLEAVE2(vin[kk * 2], vin[kk * 2 + 1],
+ vout[k * 2], vout[k * 2 + 1]);
+ }
+ }
+ }
+}
+
+static void pffft_cplx_finalize(int Ncvec, const v4sf * in, v4sf * out, const v4sf * e)
+{
+ int k, dk = Ncvec / SIMD_SZ; // number of 4x4 matrix blocks
+ v4sf r0, i0, r1, i1, r2, i2, r3, i3;
+ v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1;
+ assert(in != out);
+ for (k = 0; k < dk; ++k) {
+ r0 = in[8 * k + 0];
+ i0 = in[8 * k + 1];
+ r1 = in[8 * k + 2];
+ i1 = in[8 * k + 3];
+ r2 = in[8 * k + 4];
+ i2 = in[8 * k + 5];
+ r3 = in[8 * k + 6];
+ i3 = in[8 * k + 7];
+ VTRANSPOSE4(r0, r1, r2, r3);
+ VTRANSPOSE4(i0, i1, i2, i3);
+ VCPLXMUL(r1, i1, e[k * 6 + 0], e[k * 6 + 1]);
+ VCPLXMUL(r2, i2, e[k * 6 + 2], e[k * 6 + 3]);
+ VCPLXMUL(r3, i3, e[k * 6 + 4], e[k * 6 + 5]);
+
+ sr0 = VADD(r0, r2);
+ dr0 = VSUB(r0, r2);
+ sr1 = VADD(r1, r3);
+ dr1 = VSUB(r1, r3);
+ si0 = VADD(i0, i2);
+ di0 = VSUB(i0, i2);
+ si1 = VADD(i1, i3);
+ di1 = VSUB(i1, i3);
+
+ /*
+ transformation for each column is:
+
+ [1 1 1 1 0 0 0 0] [r0]
+ [1 0 -1 0 0 -1 0 1] [r1]
+ [1 -1 1 -1 0 0 0 0] [r2]
+ [1 0 -1 0 0 1 0 -1] [r3]
+ [0 0 0 0 1 1 1 1] * [i0]
+ [0 1 0 -1 1 0 -1 0] [i1]
+ [0 0 0 0 1 -1 1 -1] [i2]
+ [0 -1 0 1 1 0 -1 0] [i3]
+ */
+
+ r0 = VADD(sr0, sr1);
+ i0 = VADD(si0, si1);
+ r1 = VADD(dr0, di1);
+ i1 = VSUB(di0, dr1);
+ r2 = VSUB(sr0, sr1);
+ i2 = VSUB(si0, si1);
+ r3 = VSUB(dr0, di1);
+ i3 = VADD(di0, dr1);
+
+ *out++ = r0;
+ *out++ = i0;
+ *out++ = r1;
+ *out++ = i1;
+ *out++ = r2;
+ *out++ = i2;
+ *out++ = r3;
+ *out++ = i3;
+ }
+}
+
+static void pffft_cplx_preprocess(int Ncvec, const v4sf * in, v4sf * out,
+ const v4sf * e)
+{
+ int k, dk = Ncvec / SIMD_SZ; // number of 4x4 matrix blocks
+ v4sf r0, i0, r1, i1, r2, i2, r3, i3;
+ v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1;
+ assert(in != out);
+ for (k = 0; k < dk; ++k) {
+ r0 = in[8 * k + 0];
+ i0 = in[8 * k + 1];
+ r1 = in[8 * k + 2];
+ i1 = in[8 * k + 3];
+ r2 = in[8 * k + 4];
+ i2 = in[8 * k + 5];
+ r3 = in[8 * k + 6];
+ i3 = in[8 * k + 7];
+
+ sr0 = VADD(r0, r2);
+ dr0 = VSUB(r0, r2);
+ sr1 = VADD(r1, r3);
+ dr1 = VSUB(r1, r3);
+ si0 = VADD(i0, i2);
+ di0 = VSUB(i0, i2);
+ si1 = VADD(i1, i3);
+ di1 = VSUB(i1, i3);
+
+ r0 = VADD(sr0, sr1);
+ i0 = VADD(si0, si1);
+ r1 = VSUB(dr0, di1);
+ i1 = VADD(di0, dr1);
+ r2 = VSUB(sr0, sr1);
+ i2 = VSUB(si0, si1);
+ r3 = VADD(dr0, di1);
+ i3 = VSUB(di0, dr1);
+
+ VCPLXMULCONJ(r1, i1, e[k * 6 + 0], e[k * 6 + 1]);
+ VCPLXMULCONJ(r2, i2, e[k * 6 + 2], e[k * 6 + 3]);
+ VCPLXMULCONJ(r3, i3, e[k * 6 + 4], e[k * 6 + 5]);
+
+ VTRANSPOSE4(r0, r1, r2, r3);
+ VTRANSPOSE4(i0, i1, i2, i3);
+
+ *out++ = r0;
+ *out++ = i0;
+ *out++ = r1;
+ *out++ = i1;
+ *out++ = r2;
+ *out++ = i2;
+ *out++ = r3;
+ *out++ = i3;
+ }
+}
+
+static ALWAYS_INLINE(void) pffft_real_finalize_4x4(const v4sf * in0,
+ const v4sf * in1,
+ const v4sf * in,
+ const v4sf * e, v4sf * out)
+{
+ v4sf r0, i0, r1, i1, r2, i2, r3, i3;
+ v4sf sr0, dr0, sr1, dr1, si0, di0, si1, di1;
+ r0 = *in0;
+ i0 = *in1;
+ r1 = *in++;
+ i1 = *in++;
+ r2 = *in++;
+ i2 = *in++;
+ r3 = *in++;
+ i3 = *in++;
+ VTRANSPOSE4(r0, r1, r2, r3);
+ VTRANSPOSE4(i0, i1, i2, i3);
+
+ /*
+ transformation for each column is:
+
+ [1 1 1 1 0 0 0 0] [r0]
+ [1 0 -1 0 0 -1 0 1] [r1]
+ [1 0 -1 0 0 1 0 -1] [r2]
+ [1 -1 1 -1 0 0 0 0] [r3]
+ [0 0 0 0 1 1 1 1] * [i0]
+ [0 -1 0 1 -1 0 1 0] [i1]
+ [0 -1 0 1 1 0 -1 0] [i2]
+ [0 0 0 0 -1 1 -1 1] [i3]
+ */
+
+ //cerr << "matrix initial, before e , REAL:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n";
+ //cerr << "matrix initial, before e, IMAG :\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n";
+
+ VCPLXMUL(r1, i1, e[0], e[1]);
+ VCPLXMUL(r2, i2, e[2], e[3]);
+ VCPLXMUL(r3, i3, e[4], e[5]);
+
+ //cerr << "matrix initial, real part:\n 1: " << r0 << "\n 1: " << r1 << "\n 1: " << r2 << "\n 1: " << r3 << "\n";
+ //cerr << "matrix initial, imag part:\n 1: " << i0 << "\n 1: " << i1 << "\n 1: " << i2 << "\n 1: " << i3 << "\n";
+
+ sr0 = VADD(r0, r2);
+ dr0 = VSUB(r0, r2);
+ sr1 = VADD(r1, r3);
+ dr1 = VSUB(r3, r1);
+ si0 = VADD(i0, i2);
+ di0 = VSUB(i0, i2);
+ si1 = VADD(i1, i3);
+ di1 = VSUB(i3, i1);
+
+ r0 = VADD(sr0, sr1);
+ r3 = VSUB(sr0, sr1);
+ i0 = VADD(si0, si1);
+ i3 = VSUB(si1, si0);
+ r1 = VADD(dr0, di1);
+ r2 = VSUB(dr0, di1);
+ i1 = VSUB(dr1, di0);
+ i2 = VADD(dr1, di0);
+
+ *out++ = r0;
+ *out++ = i0;
+ *out++ = r1;
+ *out++ = i1;
+ *out++ = r2;
+ *out++ = i2;
+ *out++ = r3;
+ *out++ = i3;
+
+}
+
+static NEVER_INLINE(void) pffft_real_finalize(int Ncvec, const v4sf * in,
+ v4sf * out, const v4sf * e)
+{
+ int k, dk = Ncvec / SIMD_SZ; // number of 4x4 matrix blocks
+ /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */
+
+ v4sf_union cr, ci, *uout = (v4sf_union *) out;
+ v4sf save = in[7], zero = VZERO();
+ float xr0, xi0, xr1, xi1, xr2, xi2, xr3, xi3;
+ static const float s = M_SQRT2 / 2;
+
+ cr.v = in[0];
+ ci.v = in[Ncvec * 2 - 1];
+ assert(in != out);
+ pffft_real_finalize_4x4(&zero, &zero, in + 1, e, out);
+
+ /*
+ [cr0 cr1 cr2 cr3 ci0 ci1 ci2 ci3]
+
+ [Xr(1)] ] [1 1 1 1 0 0 0 0]
+ [Xr(N/4) ] [0 0 0 0 1 s 0 -s]
+ [Xr(N/2) ] [1 0 -1 0 0 0 0 0]
+ [Xr(3N/4)] [0 0 0 0 1 -s 0 s]
+ [Xi(1) ] [1 -1 1 -1 0 0 0 0]
+ [Xi(N/4) ] [0 0 0 0 0 -s -1 -s]
+ [Xi(N/2) ] [0 -1 0 1 0 0 0 0]
+ [Xi(3N/4)] [0 0 0 0 0 -s 1 -s]
+ */
+
+ xr0 = (cr.f[0] + cr.f[2]) + (cr.f[1] + cr.f[3]);
+ uout[0].f[0] = xr0;
+ xi0 = (cr.f[0] + cr.f[2]) - (cr.f[1] + cr.f[3]);
+ uout[1].f[0] = xi0;
+ xr2 = (cr.f[0] - cr.f[2]);
+ uout[4].f[0] = xr2;
+ xi2 = (cr.f[3] - cr.f[1]);
+ uout[5].f[0] = xi2;
+ xr1 = ci.f[0] + s * (ci.f[1] - ci.f[3]);
+ uout[2].f[0] = xr1;
+ xi1 = -ci.f[2] - s * (ci.f[1] + ci.f[3]);
+ uout[3].f[0] = xi1;
+ xr3 = ci.f[0] - s * (ci.f[1] - ci.f[3]);
+ uout[6].f[0] = xr3;
+ xi3 = ci.f[2] - s * (ci.f[1] + ci.f[3]);
+ uout[7].f[0] = xi3;
+
+ for (k = 1; k < dk; ++k) {
+ v4sf save_next = in[8 * k + 7];
+ pffft_real_finalize_4x4(&save, &in[8 * k + 0], in + 8 * k + 1,
+ e + k * 6, out + k * 8);
+ save = save_next;
+ }
+
+}
+
+static ALWAYS_INLINE(void) pffft_real_preprocess_4x4(const v4sf * in,
+ const v4sf * e, v4sf * out,
+ int first)
+{
+ v4sf r0 = in[0], i0 = in[1], r1 = in[2], i1 = in[3], r2 = in[4], i2 =
+ in[5], r3 = in[6], i3 = in[7];
+ /*
+ transformation for each column is:
+
+ [1 1 1 1 0 0 0 0] [r0]
+ [1 0 0 -1 0 -1 -1 0] [r1]
+ [1 -1 -1 1 0 0 0 0] [r2]
+ [1 0 0 -1 0 1 1 0] [r3]
+ [0 0 0 0 1 -1 1 -1] * [i0]
+ [0 -1 1 0 1 0 0 1] [i1]
+ [0 0 0 0 1 1 -1 -1] [i2]
+ [0 1 -1 0 1 0 0 1] [i3]
+ */
+
+ v4sf sr0 = VADD(r0, r3), dr0 = VSUB(r0, r3);
+ v4sf sr1 = VADD(r1, r2), dr1 = VSUB(r1, r2);
+ v4sf si0 = VADD(i0, i3), di0 = VSUB(i0, i3);
+ v4sf si1 = VADD(i1, i2), di1 = VSUB(i1, i2);
+
+ r0 = VADD(sr0, sr1);
+ r2 = VSUB(sr0, sr1);
+ r1 = VSUB(dr0, si1);
+ r3 = VADD(dr0, si1);
+ i0 = VSUB(di0, di1);
+ i2 = VADD(di0, di1);
+ i1 = VSUB(si0, dr1);
+ i3 = VADD(si0, dr1);
+
+ VCPLXMULCONJ(r1, i1, e[0], e[1]);
+ VCPLXMULCONJ(r2, i2, e[2], e[3]);
+ VCPLXMULCONJ(r3, i3, e[4], e[5]);
+
+ VTRANSPOSE4(r0, r1, r2, r3);
+ VTRANSPOSE4(i0, i1, i2, i3);
+
+ if (!first) {
+ *out++ = r0;
+ *out++ = i0;
+ }
+ *out++ = r1;
+ *out++ = i1;
+ *out++ = r2;
+ *out++ = i2;
+ *out++ = r3;
+ *out++ = i3;
+}
+
+static NEVER_INLINE(void) pffft_real_preprocess(int Ncvec, const v4sf * in,
+ v4sf * out, const v4sf * e)
+{
+ int k, dk = Ncvec / SIMD_SZ; // number of 4x4 matrix blocks
+ /* fftpack order is f0r f1r f1i f2r f2i ... f(n-1)r f(n-1)i f(n)r */
+
+ v4sf_union Xr, Xi, *uout = (v4sf_union *) out;
+ float cr0, ci0, cr1, ci1, cr2, ci2, cr3, ci3;
+ static const float s = M_SQRT2;
+ assert(in != out);
+ for (k = 0; k < 4; ++k) {
+ Xr.f[k] = ((float *)in)[8 * k];
+ Xi.f[k] = ((float *)in)[8 * k + 4];
+ }
+
+ pffft_real_preprocess_4x4(in, e, out + 1, 1); // will write only 6 values
+
+ /*
+ [Xr0 Xr1 Xr2 Xr3 Xi0 Xi1 Xi2 Xi3]
+
+ [cr0] [1 0 2 0 1 0 0 0]
+ [cr1] [1 0 0 0 -1 0 -2 0]
+ [cr2] [1 0 -2 0 1 0 0 0]
+ [cr3] [1 0 0 0 -1 0 2 0]
+ [ci0] [0 2 0 2 0 0 0 0]
+ [ci1] [0 s 0 -s 0 -s 0 -s]
+ [ci2] [0 0 0 0 0 -2 0 2]
+ [ci3] [0 -s 0 s 0 -s 0 -s]
+ */
+ for (k = 1; k < dk; ++k) {
+ pffft_real_preprocess_4x4(in + 8 * k, e + k * 6,
+ out - 1 + k * 8, 0);
+ }
+
+ cr0 = (Xr.f[0] + Xi.f[0]) + 2 * Xr.f[2];
+ uout[0].f[0] = cr0;
+ cr1 = (Xr.f[0] - Xi.f[0]) - 2 * Xi.f[2];
+ uout[0].f[1] = cr1;
+ cr2 = (Xr.f[0] + Xi.f[0]) - 2 * Xr.f[2];
+ uout[0].f[2] = cr2;
+ cr3 = (Xr.f[0] - Xi.f[0]) + 2 * Xi.f[2];
+ uout[0].f[3] = cr3;
+ ci0 = 2 * (Xr.f[1] + Xr.f[3]);
+ uout[2 * Ncvec - 1].f[0] = ci0;
+ ci1 = s * (Xr.f[1] - Xr.f[3]) - s * (Xi.f[1] + Xi.f[3]);
+ uout[2 * Ncvec - 1].f[1] = ci1;
+ ci2 = 2 * (Xi.f[3] - Xi.f[1]);
+ uout[2 * Ncvec - 1].f[2] = ci2;
+ ci3 = -s * (Xr.f[1] - Xr.f[3]) - s * (Xi.f[1] + Xi.f[3]);
+ uout[2 * Ncvec - 1].f[3] = ci3;
+}
+
+static void transform_simd(PFFFT_Setup * setup, const float *finput,
+ float *foutput, float * scratch,
+ pffft_direction_t direction, int ordered)
+{
+ int k, Ncvec = setup->Ncvec;
+ int nf_odd = (setup->ifac[1] & 1);
+
+ // temporary buffer is allocated on the stack if the scratch pointer is NULL
+ int stack_allocate = (scratch == 0 ? Ncvec * 2 : 1);
+ VLA_ARRAY_ON_STACK(v4sf, scratch_on_stack, stack_allocate);
+
+ const v4sf *vinput = (const v4sf *)finput;
+ v4sf *voutput = (v4sf *) foutput;
+ v4sf *buff[2] = { voutput, scratch ? (v4sf*)scratch : scratch_on_stack };
+ int ib = (nf_odd ^ ordered ? 1 : 0);
+
+ assert(VALIGNED(finput) && VALIGNED(foutput));
+
+ //assert(finput != foutput);
+ if (direction == PFFFT_FORWARD) {
+ ib = !ib;
+ if (setup->transform == PFFFT_REAL) {
+ ib = (rfftf1_ps(Ncvec * 2, vinput, buff[ib], buff[!ib],
+ setup->twiddle,
+ &setup->ifac[0]) == buff[0] ? 0 : 1);
+ pffft_real_finalize(Ncvec, buff[ib], buff[!ib],
+ (v4sf *) setup->e);
+ } else {
+ v4sf *tmp = buff[ib];
+ for (k = 0; k < Ncvec; ++k) {
+ UNINTERLEAVE2(vinput[k * 2], vinput[k * 2 + 1],
+ tmp[k * 2], tmp[k * 2 + 1]);
+ }
+ ib = (cfftf1_ps(Ncvec, buff[ib], buff[!ib], buff[ib],
+ setup->twiddle, &setup->ifac[0],
+ -1) == buff[0] ? 0 : 1);
+ pffft_cplx_finalize(Ncvec, buff[ib], buff[!ib],
+ (v4sf *) setup->e);
+ }
+ if (ordered) {
+ pffft_zreorder(setup, (float *)buff[!ib],
+ (float *)buff[ib], PFFFT_FORWARD);
+ } else
+ ib = !ib;
+ } else {
+ if (vinput == buff[ib]) {
+ ib = !ib; // may happen when finput == foutput
+ }
+ if (ordered) {
+ pffft_zreorder(setup, (float *)vinput,
+ (float *)buff[ib], PFFFT_BACKWARD);
+ vinput = buff[ib];
+ ib = !ib;
+ }
+ if (setup->transform == PFFFT_REAL) {
+ pffft_real_preprocess(Ncvec, vinput, buff[ib],
+ (v4sf *) setup->e);
+ ib = (rfftb1_ps
+ (Ncvec * 2, buff[ib], buff[0], buff[1],
+ setup->twiddle,
+ &setup->ifac[0]) == buff[0] ? 0 : 1);
+ } else {
+ pffft_cplx_preprocess(Ncvec, vinput, buff[ib],
+ (v4sf *) setup->e);
+ ib = (cfftf1_ps
+ (Ncvec, buff[ib], buff[0], buff[1],
+ setup->twiddle, &setup->ifac[0],
+ +1) == buff[0] ? 0 : 1);
+ for (k = 0; k < Ncvec; ++k) {
+ INTERLEAVE2(buff[ib][k * 2],
+ buff[ib][k * 2 + 1],
+ buff[ib][k * 2],
+ buff[ib][k * 2 + 1]);
+ }
+ }
+ }
+
+ if (buff[ib] != voutput) {
+ /* extra copy required -- this situation should only happen when finput == foutput */
+ assert(finput == foutput);
+ for (k = 0; k < Ncvec; ++k) {
+ v4sf a = buff[ib][2 * k], b = buff[ib][2 * k + 1];
+ voutput[2 * k] = a;
+ voutput[2 * k + 1] = b;
+ }
+ ib = !ib;
+ }
+ assert(buff[ib] == voutput);
+}
+
+static void zconvolve_accumulate_simd(PFFFT_Setup * s, const float *a, const float *b,
+ const float *c, float *ab, float scaling)
+{
+ const int Ncvec2 = s->Ncvec * 2;
+ const v4sf *RESTRICT va = (const v4sf *)a;
+ const v4sf *RESTRICT vb = (const v4sf *)b;
+ v4sf *RESTRICT vab = (v4sf *) ab;
+ v4sf *RESTRICT vc = (v4sf *) c;
+ v4sf vscal = LD_PS1(scaling);
+ float ar, ai, br, bi, cr, ci;
+ int i;
+
+#ifdef __arm__
+ __builtin_prefetch(va);
+ __builtin_prefetch(vb);
+ __builtin_prefetch(c);
+ __builtin_prefetch(va + 2);
+ __builtin_prefetch(vb + 2);
+ __builtin_prefetch(c + 2);
+ __builtin_prefetch(va + 4);
+ __builtin_prefetch(vb + 4);
+ __builtin_prefetch(c + 4);
+ __builtin_prefetch(va + 6);
+ __builtin_prefetch(vb + 6);
+ __builtin_prefetch(c + 6);
+#endif
+
+ assert(VALIGNED(a) && VALIGNED(b) && VALIGNED(ab));
+ ar = ((v4sf_union *) va)[0].f[0];
+ ai = ((v4sf_union *) va)[1].f[0];
+ br = ((v4sf_union *) vb)[0].f[0];
+ bi = ((v4sf_union *) vb)[1].f[0];
+ cr = ((v4sf_union *) vc)[0].f[0];
+ ci = ((v4sf_union *) vc)[1].f[0];
+
+ for (i = 0; i < Ncvec2; i += 4) {
+ v4sf ar, ai, br, bi;
+ ar = va[i + 0];
+ ai = va[i + 1];
+ br = vb[i + 0];
+ bi = vb[i + 1];
+ VCPLXMUL(ar, ai, br, bi);
+ vab[i + 0] = VMADD(ar, vscal, vc[i + 0]);
+ vab[i + 1] = VMADD(ai, vscal, vc[i + 1]);
+ ar = va[i + 2];
+ ai = va[i + 3];
+ br = vb[i + 2];
+ bi = vb[i + 3];
+ VCPLXMUL(ar, ai, br, bi);
+ vab[i + 2] = VMADD(ar, vscal, vc[i + 2]);
+ vab[i + 3] = VMADD(ai, vscal, vc[i + 3]);
+ }
+ if (s->transform == PFFFT_REAL) {
+ ((v4sf_union *) vab)[0].f[0] = cr + ar * br * scaling;
+ ((v4sf_union *) vab)[1].f[0] = ci + ai * bi * scaling;
+ }
+}
+
+static void zconvolve_simd(PFFFT_Setup * s, const float *a, const float *b,
+ float *ab, float scaling)
+{
+ v4sf vscal = LD_PS1(scaling);
+ const v4sf * RESTRICT va = (const v4sf*)a;
+ const v4sf * RESTRICT vb = (const v4sf*)b;
+ v4sf * RESTRICT vab = (v4sf*)ab;
+ float sar, sai, sbr, sbi;
+ const int Ncvec2 = s->Ncvec * 2;
+ int i;
+
+#ifdef __arm__
+ __builtin_prefetch(va);
+ __builtin_prefetch(vb);
+ __builtin_prefetch(vab);
+ __builtin_prefetch(va+2);
+ __builtin_prefetch(vb+2);
+ __builtin_prefetch(vab+2);
+ __builtin_prefetch(va+4);
+ __builtin_prefetch(vb+4);
+ __builtin_prefetch(vab+4);
+ __builtin_prefetch(va+6);
+ __builtin_prefetch(vb+6);
+ __builtin_prefetch(vab+6);
+#endif
+
+ assert(VALIGNED(a) && VALIGNED(b) && VALIGNED(ab));
+ sar = ((v4sf_union*)va)[0].f[0];
+ sai = ((v4sf_union*)va)[1].f[0];
+ sbr = ((v4sf_union*)vb)[0].f[0];
+ sbi = ((v4sf_union*)vb)[1].f[0];
+
+ /* default routine, works fine for non-arm cpus with current compilers */
+ for (i = 0; i < Ncvec2; i += 4) {
+ v4sf var, vai, vbr, vbi;
+ var = va[i + 0];
+ vai = va[i + 1];
+ vbr = vb[i + 0];
+ vbi = vb[i + 1];
+ VCPLXMUL(var, vai, vbr, vbi);
+ vab[i + 0] = VMUL(var, vscal);
+ vab[i + 1] = VMUL(vai, vscal);
+ var = va[i + 2];
+ vai = va[i + 3];
+ vbr = vb[i + 2];
+ vbi = vb[i + 3];
+ VCPLXMUL(var, vai, vbr, vbi);
+ vab[i + 2] = VMUL(var, vscal);
+ vab[i + 3] = VMUL(vai, vscal);
+ }
+
+ if (s->transform == PFFFT_REAL) {
+ ((v4sf_union*)vab)[0].f[0] = sar * sbr * scaling;
+ ((v4sf_union*)vab)[1].f[0] = sai * sbi * scaling;
+ }
+}
+
+#else // defined(PFFFT_SIMD_DISABLE)
+
+// standard routine using scalar floats, without SIMD stuff.
+
+static void zreorder_simd(PFFFT_Setup * setup, const float *in, float *out,
+ pffft_direction_t direction)
+{
+ int k, N = setup->N;
+ if (setup->transform == PFFFT_COMPLEX) {
+ for (k = 0; k < 2 * N; ++k)
+ out[k] = in[k];
+ return;
+ } else if (direction == PFFFT_FORWARD) {
+ float x_N = in[N - 1];
+ for (k = N - 1; k > 1; --k)
+ out[k] = in[k - 1];
+ out[0] = in[0];
+ out[1] = x_N;
+ } else {
+ float x_N = in[1];
+ for (k = 1; k < N - 1; ++k)
+ out[k] = in[k + 1];
+ out[0] = in[0];
+ out[N - 1] = x_N;
+ }
+}
+
+static void transform_simd(PFFFT_Setup * setup, const float *input,
+ float *output, float *scratch,
+ pffft_direction_t direction, int ordered)
+{
+ int Ncvec = setup->Ncvec;
+ int nf_odd = (setup->ifac[1] & 1);
+
+ // temporary buffer is allocated on the stack if the scratch pointer is NULL
+ int stack_allocate = (scratch == 0 ? Ncvec * 2 : 1);
+ VLA_ARRAY_ON_STACK(v4sf, scratch_on_stack, stack_allocate);
+ float *buff[2];
+ int ib;
+ if (scratch == 0)
+ scratch = scratch_on_stack;
+ buff[0] = output;
+ buff[1] = scratch;
+
+ if (setup->transform == PFFFT_COMPLEX)
+ ordered = 0; // it is always ordered.
+ ib = (nf_odd ^ ordered ? 1 : 0);
+
+ if (direction == PFFFT_FORWARD) {
+ if (setup->transform == PFFFT_REAL) {
+ ib = (rfftf1_ps(Ncvec * 2, input, buff[ib], buff[!ib],
+ setup->twiddle,
+ &setup->ifac[0]) == buff[0] ? 0 : 1);
+ } else {
+ ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib],
+ setup->twiddle, &setup->ifac[0],
+ -1) == buff[0] ? 0 : 1);
+ }
+ if (ordered) {
+ pffft_zreorder(setup, buff[ib], buff[!ib],
+ PFFFT_FORWARD);
+ ib = !ib;
+ }
+ } else {
+ if (input == buff[ib]) {
+ ib = !ib; // may happen when finput == foutput
+ }
+ if (ordered) {
+ pffft_zreorder(setup, input, buff[!ib], PFFFT_BACKWARD);
+ input = buff[!ib];
+ }
+ if (setup->transform == PFFFT_REAL) {
+ ib = (rfftb1_ps(Ncvec * 2, input, buff[ib], buff[!ib],
+ setup->twiddle,
+ &setup->ifac[0]) == buff[0] ? 0 : 1);
+ } else {
+ ib = (cfftf1_ps(Ncvec, input, buff[ib], buff[!ib],
+ setup->twiddle, &setup->ifac[0],
+ +1) == buff[0] ? 0 : 1);
+ }
+ }
+ if (buff[ib] != output) {
+ int k;
+ // extra copy required -- this situation should happens only when finput == foutput
+ assert(input == output);
+ for (k = 0; k < Ncvec; ++k) {
+ float a = buff[ib][2 * k], b = buff[ib][2 * k + 1];
+ output[2 * k] = a;
+ output[2 * k + 1] = b;
+ }
+ ib = !ib;
+ }
+ assert(buff[ib] == output);
+}
+
+
+static void zconvolve_accumulate_simd(PFFFT_Setup * s, const float *a,
+ const float *b, const float *c, float *ab, float scaling)
+{
+ int i, Ncvec2 = s->Ncvec * 2;
+
+ if (s->transform == PFFFT_REAL) {
+ // take care of the fftpack ordering
+ ab[0] = c[0] + a[0] * b[0] * scaling;
+ ab[Ncvec2 - 1] = c[Ncvec2 - 1] + a[Ncvec2 - 1] * b[Ncvec2 - 1] * scaling;
+ ++ab;
+ ++c;
+ ++a;
+ ++b;
+ Ncvec2 -= 2;
+ }
+ for (i = 0; i < Ncvec2; i += 2) {
+ float ar, ai, br, bi;
+ ar = a[i + 0];
+ ai = a[i + 1];
+ br = b[i + 0];
+ bi = b[i + 1];
+ VCPLXMUL(ar, ai, br, bi);
+ ab[i + 0] = c[i + 0] + ar * scaling;
+ ab[i + 1] = c[i + 1] + ai * scaling;
+ }
+}
+
+static void zconvolve_simd(PFFFT_Setup * s, const float *a,
+ const float *b, float *ab, float scaling)
+{
+ int i, Ncvec2 = s->Ncvec * 2;
+
+ if (s->transform == PFFFT_REAL) {
+ // take care of the fftpack ordering
+ ab[0] = a[0] * b[0] * scaling;
+ ab[Ncvec2 - 1] =
+ a[Ncvec2 - 1] * b[Ncvec2 - 1] * scaling;
+ ++ab;
+ ++a;
+ ++b;
+ Ncvec2 -= 2;
+ }
+ for (i = 0; i < Ncvec2; i += 2) {
+ float ar, ai, br, bi;
+ ar = a[i + 0];
+ ai = a[i + 1];
+ br = b[i + 0];
+ bi = b[i + 1];
+ VCPLXMUL(ar, ai, br, bi);
+ ab[i + 0] = ar * scaling;
+ ab[i + 1] = ai * scaling;
+ }
+}
+#endif // defined(PFFFT_SIMD_DISABLE)
+
+static int simd_size_simd(void)
+{
+ return SIMD_SZ;
+}
+
+struct funcs pffft_funcs = {
+ .new_setup = new_setup_simd,
+ .transform = transform_simd,
+ .zreorder = zreorder_simd,
+ .zconvolve_accumulate = zconvolve_accumulate_simd,
+ .zconvolve = zconvolve_simd,
+ .simd_size = simd_size_simd,
+ .validate = validate_pffft_simd,
+};
+
+#if defined(PFFFT_SIMD_DISABLE)
+
+extern struct funcs pffft_funcs_c;
+#if (defined(HAVE_SSE))
+extern struct funcs pffft_funcs_sse;
+#endif
+#if (defined(HAVE_ALTIVEC))
+extern struct funcs pffft_funcs_altivec;
+#endif
+#if (defined(HAVE_NEON))
+extern struct funcs pffft_funcs_neon;
+#endif
+
+static struct funcs *funcs = &pffft_funcs_c;
+
+/* SSE and co like 16-bytes aligned pointers */
+#define MALLOC_V4SF_ALIGNMENT 64 // with a 64-byte alignment, we are even aligned on L2 cache lines...
+void *pffft_aligned_malloc(size_t nb_bytes)
+{
+ void *p, *p0 = malloc(nb_bytes + MALLOC_V4SF_ALIGNMENT);
+ if (!p0)
+ return (void *)0;
+ p = (void *)(((size_t)p0 + MALLOC_V4SF_ALIGNMENT) &
+ (~((size_t)(MALLOC_V4SF_ALIGNMENT - 1))));
+ *((void **)p - 1) = p0;
+ return p;
+}
+
+void pffft_aligned_free(void *p)
+{
+ if (p)
+ free(*((void **)p - 1));
+}
+
+int pffft_simd_size(void)
+{
+ return funcs->simd_size();
+}
+
+PFFFT_Setup *pffft_new_setup(int N, pffft_transform_t transform)
+{
+ return funcs->new_setup(N, transform);
+}
+
+void pffft_destroy_setup(PFFFT_Setup * s)
+{
+ pffft_aligned_free(s->data);
+ free(s);
+}
+
+void pffft_transform(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction)
+{
+ return funcs->transform(setup, input, output, work, direction, 0);
+}
+
+void pffft_transform_ordered(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction)
+{
+ return funcs->transform(setup, input, output, work, direction, 1);
+}
+
+void pffft_zreorder(PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction)
+{
+ return funcs->zreorder(setup, input, output, direction);
+}
+
+void pffft_zconvolve_accumulate(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, const float *c, float *dft_ab, float scaling)
+{
+ return funcs->zconvolve_accumulate(setup, dft_a, dft_b, c, dft_ab, scaling);
+}
+
+void pffft_zconvolve(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling)
+{
+ return funcs->zconvolve(setup, dft_a, dft_b, dft_ab, scaling);
+}
+
+void pffft_select_cpu(int flags)
+{
+ funcs = &pffft_funcs_c;
+#if defined(HAVE_SSE)
+ if (flags & SPA_CPU_FLAG_SSE)
+ funcs = &pffft_funcs_sse;
+#endif
+#if defined(HAVE_NEON)
+ if (flags & SPA_CPU_FLAG_NEON)
+ funcs = &pffft_funcs_neon;
+#endif
+#if defined(HAVE_ALTIVEC)
+ if (flags & SPA_CPU_FLAG_ALTIVEC)
+ funcs = &pffft_funcs_altivec;
+#endif
+}
+
+#endif
diff --git a/src/modules/module-filter-chain/pffft.h b/src/modules/module-filter-chain/pffft.h
new file mode 100644
index 0000000..ac8e271
--- /dev/null
+++ b/src/modules/module-filter-chain/pffft.h
@@ -0,0 +1,181 @@
+/* Copyright (c) 2013 Julien Pommier ( pommier@modartt.com )
+
+ Based on original fortran 77 code from FFTPACKv4 from NETLIB,
+ authored by Dr Paul Swarztrauber of NCAR, in 1985.
+
+ As confirmed by the NCAR fftpack software curators, the following
+ FFTPACKv5 license applies to FFTPACKv4 sources. My changes are
+ released under the same terms.
+
+ FFTPACK license:
+
+ http://www.cisl.ucar.edu/css/software/fftpack5/ftpk.html
+
+ Copyright (c) 2004 the University Corporation for Atmospheric
+ Research ("UCAR"). All rights reserved. Developed by NCAR's
+ Computational and Information Systems Laboratory, UCAR,
+ www.cisl.ucar.edu.
+
+ Redistribution and use of the Software in source and binary forms,
+ with or without modification, is permitted provided that the
+ following conditions are met:
+
+ - Neither the names of NCAR's Computational and Information Systems
+ Laboratory, the University Corporation for Atmospheric Research,
+ nor the names of its sponsors or contributors may be used to
+ endorse or promote products derived from this Software without
+ specific prior written permission.
+
+ - Redistributions of source code must retain the above copyright
+ notices, this list of conditions, and the disclaimer below.
+
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions, and the disclaimer below in the
+ documentation and/or other materials provided with the
+ distribution.
+
+ THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT
+ HOLDERS BE LIABLE FOR ANY CLAIM, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
+ SOFTWARE.
+*/
+
+/*
+ PFFFT : a Pretty Fast FFT.
+
+ This is basically an adaptation of the single precision fftpack
+ (v4) as found on netlib taking advantage of SIMD instruction found
+ on cpus such as intel x86 (SSE1), powerpc (Altivec), and arm (NEON).
+
+ For architectures where no SIMD instruction is available, the code
+ falls back to a scalar version.
+
+ Restrictions:
+
+ - 1D transforms only, with 32-bit single precision.
+
+ - supports only transforms for inputs of length N of the form
+ N=(2^a)*(3^b)*(5^c), a >= 5, b >=0, c >= 0 (32, 48, 64, 96, 128,
+ 144, 160, etc are all acceptable lengths). Performance is best for
+ 128<=N<=8192.
+
+ - all (float*) pointers in the functions below are expected to
+ have an "simd-compatible" alignment, that is 16 bytes on x86 and
+ powerpc CPUs.
+
+ You can allocate such buffers with the functions
+ pffft_aligned_malloc / pffft_aligned_free (or with stuff like
+ posix_memalign..)
+
+*/
+
+#ifndef PFFFT_H
+#define PFFFT_H
+
+#include <stddef.h> // for size_t
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ /* opaque struct holding internal stuff (precomputed twiddle factors)
+ this struct can be shared by many threads as it contains only
+ read-only data.
+ */
+ typedef struct PFFFT_Setup PFFFT_Setup;
+
+ /* direction of the transform */
+ typedef enum { PFFFT_FORWARD, PFFFT_BACKWARD } pffft_direction_t;
+
+ /* type of transform */
+ typedef enum { PFFFT_REAL, PFFFT_COMPLEX } pffft_transform_t;
+
+ /*
+ prepare for performing transforms of size N -- the returned
+ PFFFT_Setup structure is read-only so it can safely be shared by
+ multiple concurrent threads.
+ */
+ PFFFT_Setup *pffft_new_setup(int N, pffft_transform_t transform);
+ void pffft_destroy_setup(PFFFT_Setup *);
+ /*
+ Perform a Fourier transform , The z-domain data is stored in the
+ most efficient order for transforming it back, or using it for
+ convolution. If you need to have its content sorted in the
+ "usual" way, that is as an array of interleaved complex numbers,
+ either use pffft_transform_ordered , or call pffft_zreorder after
+ the forward fft, and before the backward fft.
+
+ Transforms are not scaled: PFFFT_BACKWARD(PFFFT_FORWARD(x)) = N*x.
+ Typically you will want to scale the backward transform by 1/N.
+
+ The 'work' pointer should point to an area of N (2*N for complex
+ fft) floats, properly aligned. If 'work' is NULL, then stack will
+ be used instead (this is probably the best strategy for small
+ FFTs, say for N < 16384).
+
+ input and output may alias.
+ */
+ void pffft_transform(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction);
+
+ /*
+ Similar to pffft_transform, but makes sure that the output is
+ ordered as expected (interleaved complex numbers). This is
+ similar to calling pffft_transform and then pffft_zreorder.
+
+ input and output may alias.
+ */
+ void pffft_transform_ordered(PFFFT_Setup *setup, const float *input, float *output, float *work, pffft_direction_t direction);
+
+ /*
+ call pffft_zreorder(.., PFFFT_FORWARD) after pffft_transform(...,
+ PFFFT_FORWARD) if you want to have the frequency components in
+ the correct "canonical" order, as interleaved complex numbers.
+
+ (for real transforms, both 0-frequency and half frequency
+ components, which are real, are assembled in the first entry as
+ F(0)+i*F(n/2+1). Note that the original fftpack did place
+ F(n/2+1) at the end of the arrays).
+
+ input and output should not alias.
+ */
+ void pffft_zreorder(PFFFT_Setup *setup, const float *input, float *output, pffft_direction_t direction);
+
+ /*
+ Perform a multiplication of the frequency components of dft_a and
+ dft_b and accumulate them into dft_ab. The arrays should have
+ been obtained with pffft_transform(.., PFFFT_FORWARD) and should
+ *not* have been reordered with pffft_zreorder (otherwise just
+ perform the operation yourself as the dft coefs are stored as
+ interleaved complex numbers).
+
+ the operation performed is: dft_ab = dft_c + (dft_a * fdt_b)*scaling
+
+ The dft_a, dft_b and dft_ab pointers may alias.
+ */
+ void pffft_zconvolve_accumulate(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, const float *dft_c, float *dft_ab, float scaling);
+
+ void pffft_zconvolve(PFFFT_Setup *setup, const float *dft_a, const float *dft_b, float *dft_ab, float scaling);
+
+ /*
+ the float buffers must have the correct alignment (16-byte boundary
+ on intel and powerpc). This function may be used to obtain such
+ correctly aligned buffers.
+ */
+ void *pffft_aligned_malloc(size_t nb_bytes);
+ void pffft_aligned_free(void *);
+
+ /* return 4 or 1 depending on whether support for SSE/Altivec instructions was enabled when building pffft.c */
+ int pffft_simd_size(void);
+
+ void pffft_select_cpu(int flags);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // PFFFT_H
diff --git a/src/modules/module-filter-chain/plugin.h b/src/modules/module-filter-chain/plugin.h
new file mode 100644
index 0000000..72946e4
--- /dev/null
+++ b/src/modules/module-filter-chain/plugin.h
@@ -0,0 +1,112 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PLUGIN_H
+#define PLUGIN_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <errno.h>
+#include <stdio.h>
+#include <limits.h>
+
+#include <spa/support/plugin.h>
+#include <spa/utils/defs.h>
+#include <spa/utils/list.h>
+#include <spa/utils/string.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_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 (*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);
+}
+
+struct fc_plugin *load_ladspa_plugin(const struct spa_support *support, uint32_t n_support,
+ struct dsp_ops *dsp, const char *path, const char *config);
+struct fc_plugin *load_lv2_plugin(const struct spa_support *support, uint32_t n_support,
+ struct dsp_ops *dsp, const char *path, const char *config);
+struct fc_plugin *load_builtin_plugin(const struct spa_support *support, uint32_t n_support,
+ struct dsp_ops *dsp, const char *path, const char *config);
+
+#endif /* PLUGIN_H */
diff --git a/src/modules/module-link-factory.c b/src/modules/module-link-factory.c
new file mode 100644
index 0000000..509b211
--- /dev/null
+++ b/src/modules/module-link-factory.c
@@ -0,0 +1,565 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+
+#include <pipewire/impl.h>
+
+/** \page page_module_link_factory PipeWire Module: Link Factory
+ */
+
+#define NAME "link-factory"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define FACTORY_USAGE PW_KEY_LINK_OUTPUT_NODE"=<output-node> " \
+ "["PW_KEY_LINK_OUTPUT_PORT"=<output-port>] " \
+ PW_KEY_LINK_INPUT_NODE"=<input-node> " \
+ "["PW_KEY_LINK_INPUT_PORT"=<input-port>] " \
+ "["PW_KEY_OBJECT_LINGER"=<bool>] " \
+ "["PW_KEY_LINK_PASSIVE"=<bool>]"
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Allow clients to create links" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct factory_data {
+ struct pw_context *context;
+
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_impl_factory *factory;
+ struct spa_hook factory_listener;
+
+ struct spa_list link_list;
+
+ struct pw_work_queue *work;
+};
+
+struct link_data {
+ struct factory_data *data;
+ struct spa_list l;
+ struct pw_impl_link *link;
+ struct spa_hook link_listener;
+
+ struct pw_resource *resource;
+ struct spa_hook resource_listener;
+
+ struct pw_global *global;
+ struct spa_hook global_listener;
+
+ struct pw_resource *factory_resource;
+ uint32_t new_id;
+ bool linger;
+};
+
+static void resource_destroy(void *data)
+{
+ struct link_data *ld = data;
+ spa_hook_remove(&ld->resource_listener);
+ ld->resource = NULL;
+ if (ld->global)
+ pw_global_destroy(ld->global);
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = resource_destroy
+};
+
+static void global_destroy(void *data)
+{
+ struct link_data *ld = data;
+ struct factory_data *d = ld->data;
+ pw_work_queue_cancel(d->work, ld, SPA_ID_INVALID);
+ spa_hook_remove(&ld->global_listener);
+ ld->global = NULL;
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy
+};
+
+static void link_destroy(void *data)
+{
+ struct link_data *ld = data;
+ spa_list_remove(&ld->l);
+ spa_hook_remove(&ld->link_listener);
+ if (ld->global)
+ spa_hook_remove(&ld->global_listener);
+ if (ld->resource)
+ spa_hook_remove(&ld->resource_listener);
+}
+
+static void link_initialized(void *data)
+{
+ struct link_data *ld = data;
+ struct pw_impl_client *client;
+ int res;
+
+ if (ld->factory_resource == NULL)
+ return;
+
+ client = pw_resource_get_client(ld->factory_resource);
+ ld->global = pw_impl_link_get_global(ld->link);
+ pw_global_add_listener(ld->global, &ld->global_listener, &global_events, ld);
+
+ res = pw_global_bind(ld->global, client, PW_PERM_ALL, PW_VERSION_LINK, ld->new_id);
+ if (res < 0)
+ goto error_bind;
+
+ if (!ld->linger) {
+ ld->resource = pw_impl_client_find_resource(client, ld->new_id);
+ if (ld->resource == NULL) {
+ res = -ENOENT;
+ goto error_bind;
+ }
+ pw_resource_add_listener(ld->resource, &ld->resource_listener, &resource_events, ld);
+ }
+ return;
+
+error_bind:
+ pw_resource_errorf_id(ld->factory_resource, ld->new_id, res,
+ "can't bind link: %s", spa_strerror(res));
+}
+
+static void destroy_link(void *obj, void *data, int res, uint32_t id)
+{
+ struct link_data *ld = data;
+ if (ld->global)
+ pw_global_destroy(ld->global);
+}
+
+static void link_state_changed(void *data, enum pw_link_state old,
+ enum pw_link_state state, const char *error)
+{
+ struct link_data *ld = data;
+ struct factory_data *d = ld->data;
+
+ switch (state) {
+ case PW_LINK_STATE_ERROR:
+ if (ld->linger)
+ pw_work_queue_add(d->work, ld, 0, destroy_link, ld);
+ break;
+ default:
+ break;
+ }
+}
+
+static const struct pw_impl_link_events link_events = {
+ PW_VERSION_IMPL_LINK_EVENTS,
+ .destroy = link_destroy,
+ .initialized = link_initialized,
+ .state_changed = link_state_changed
+};
+
+static struct pw_impl_port *get_port(struct pw_impl_node *node, enum spa_direction direction)
+{
+ struct pw_impl_port *p;
+ struct pw_context *context = pw_impl_node_get_context(node);
+ int res;
+
+ p = pw_impl_node_find_port(node, direction, PW_ID_ANY);
+
+ if (p == NULL || pw_impl_port_is_linked(p)) {
+ uint32_t port_id;
+
+ port_id = pw_impl_node_get_free_port_id(node, direction);
+ if (port_id == SPA_ID_INVALID)
+ return NULL;
+
+ p = pw_context_create_port(context, direction, port_id, NULL, 0);
+ if (p == NULL)
+ return NULL;
+
+ if ((res = pw_impl_port_add(p, node)) < 0) {
+ pw_log_warn("can't add port: %s", spa_strerror(res));
+ errno = -res;
+ return NULL;
+ }
+ }
+ return p;
+}
+
+struct find_port {
+ uint32_t id;
+ const char *name;
+ enum spa_direction direction;
+ struct pw_impl_node *node;
+ struct pw_impl_port *port;
+};
+
+static int find_port_func(void *data, struct pw_global *global)
+{
+ struct find_port *find = data;
+ const char *str;
+ const struct pw_properties *props;
+
+ if (!pw_global_is_type(global, PW_TYPE_INTERFACE_Port))
+ return 0;
+ if (pw_global_get_id(global) == find->id)
+ goto found;
+
+ props = pw_global_get_properties(global);
+ if ((str = pw_properties_get(props, PW_KEY_OBJECT_PATH)) != NULL &&
+ spa_streq(str, find->name))
+ goto found;
+ return 0;
+found:
+ find->port = pw_global_get_object(global);
+ return 1;
+}
+
+static int find_node_port_func(void *data, struct pw_impl_port *port)
+{
+ struct find_port *find = data;
+ const char *str;
+ const struct pw_properties *props;
+
+ if (pw_impl_port_get_id(port) == find->id)
+ goto found;
+
+ props = pw_impl_port_get_properties(port);
+ if ((str = pw_properties_get(props, PW_KEY_PORT_NAME)) != NULL &&
+ spa_streq(str, find->name))
+ goto found;
+ if ((str = pw_properties_get(props, PW_KEY_PORT_ALIAS)) != NULL &&
+ spa_streq(str, find->name))
+ goto found;
+ if ((str = pw_properties_get(props, PW_KEY_OBJECT_PATH)) != NULL &&
+ spa_streq(str, find->name))
+ goto found;
+ return 0;
+found:
+ find->port = port;
+ return 1;
+}
+
+static struct pw_impl_port *find_port(struct pw_context *context,
+ struct pw_impl_node *node, enum spa_direction direction, const char *name)
+{
+ struct find_port find = {
+ .id = SPA_ID_INVALID,
+ .name = name,
+ .direction = direction,
+ .node = node
+ };
+ spa_atou32(name, &find.id, 0);
+
+ 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 (node != NULL) {
+ /* find port by local id */
+ if (find.id != SPA_ID_INVALID) {
+ find.port = pw_impl_node_find_port(node, find.direction, find.id);
+ if (find.port != NULL)
+ return find.port;
+ }
+ /* find port by local name */
+ if (pw_impl_node_for_each_port(find.node, find.direction,
+ find_node_port_func, &find) == 1)
+ return find.port;
+
+ } else {
+ /* find port by name */
+ if (pw_context_for_each_global(context, find_port_func, &find) == 1)
+ return find.port;
+ }
+ return NULL;
+}
+
+struct find_node {
+ uint32_t id;
+ const char *name;
+ struct pw_impl_node *node;
+};
+
+static int find_node_func(void *data, struct pw_global *global)
+{
+ struct find_node *find = data;
+ const char *str;
+ const struct pw_properties *props;
+
+ if (!pw_global_is_type(global, PW_TYPE_INTERFACE_Node))
+ return 0;
+ if (pw_global_get_id(global) == find->id)
+ goto found;
+
+ props = pw_global_get_properties(global);
+ if ((str = pw_properties_get(props, PW_KEY_NODE_NAME)) != NULL &&
+ spa_streq(str, find->name))
+ goto found;
+ if ((str = pw_properties_get(props, PW_KEY_NODE_NICK)) != NULL &&
+ spa_streq(str, find->name))
+ goto found;
+ if ((str = pw_properties_get(props, PW_KEY_NODE_DESCRIPTION)) != NULL &&
+ spa_streq(str, find->name))
+ goto found;
+ if ((str = pw_properties_get(props, PW_KEY_OBJECT_PATH)) != NULL &&
+ spa_streq(str, find->name))
+ goto found;
+ return 0;
+found:
+ find->node = pw_global_get_object(global);
+ return 1;
+}
+
+static struct pw_impl_node *find_node(struct pw_context *context, const char *name)
+{
+ struct find_node find = {
+ .id = SPA_ID_INVALID,
+ .name = name,
+ };
+ spa_atou32(name, &find.id, 0);
+
+ if (find.id != SPA_ID_INVALID) {
+ struct pw_global *global = pw_context_find_global(context, find.id);
+ if (global != NULL && pw_global_is_type(global, PW_TYPE_INTERFACE_Node))
+ return pw_global_get_object(global);
+ }
+ if (pw_context_for_each_global(context, find_node_func, &find) == 1)
+ return find.node;
+ return NULL;
+}
+
+static void *create_object(void *_data,
+ struct pw_resource *resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id)
+{
+ struct factory_data *d = _data;
+ struct pw_impl_client *client = NULL;
+ struct pw_impl_node *output_node, *input_node;
+ struct pw_impl_port *outport = NULL, *inport = NULL;
+ struct pw_context *context = d->context;
+ struct pw_impl_link *link;
+ const char *output_node_str, *input_node_str;
+ const char *output_port_str, *input_port_str;
+ struct link_data *ld;
+ int res;
+ bool linger;
+
+ if (properties == NULL)
+ goto error_properties;
+
+ if ((output_node_str = pw_properties_get(properties, PW_KEY_LINK_OUTPUT_NODE)) != NULL)
+ output_node = find_node(context, output_node_str);
+ else
+ output_node = NULL;
+
+ if ((output_port_str = pw_properties_get(properties, PW_KEY_LINK_OUTPUT_PORT)) != NULL)
+ outport = find_port(context, output_node, SPA_DIRECTION_OUTPUT, output_port_str);
+ else if (output_node != NULL)
+ outport = get_port(output_node, SPA_DIRECTION_OUTPUT);
+ if (outport == NULL)
+ goto error_output_port;
+
+ if ((input_node_str = pw_properties_get(properties, PW_KEY_LINK_INPUT_NODE)) != NULL)
+ input_node = find_node(context, input_node_str);
+ else
+ input_node = NULL;
+
+ if ((input_port_str = pw_properties_get(properties, PW_KEY_LINK_INPUT_PORT)) != NULL)
+ inport = find_port(context, input_node, SPA_DIRECTION_INPUT, input_port_str);
+ else if (input_node != NULL)
+ inport = get_port(input_node, SPA_DIRECTION_INPUT);
+ if (inport == NULL)
+ goto error_input_port;
+
+ linger = pw_properties_get_bool(properties, PW_KEY_OBJECT_LINGER, false);
+
+ pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d",
+ pw_impl_factory_get_info(d->factory)->id);
+
+ client = resource ? pw_resource_get_client(resource) : NULL;
+ if (client && !linger)
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d",
+ pw_impl_client_get_info(client)->id);
+
+
+ link = pw_context_create_link(context, outport, inport, NULL, properties, sizeof(struct link_data));
+ properties = NULL;
+ if (link == NULL) {
+ res = -errno;
+ goto error_create_link;
+ }
+
+ ld = pw_impl_link_get_user_data(link);
+ ld->data = d;
+ ld->factory_resource = resource;
+ ld->link = link;
+ ld->new_id = new_id;
+ ld->linger = linger;
+ spa_list_append(&d->link_list, &ld->l);
+
+ pw_impl_link_add_listener(link, &ld->link_listener, &link_events, ld);
+ if ((res = pw_impl_link_register(link, NULL)) < 0)
+ goto error_link_register;
+
+ return link;
+
+error_properties:
+ res = -EINVAL;
+ pw_resource_errorf_id(resource, new_id, res, NAME": no properties. usage:"FACTORY_USAGE);
+ goto error_exit;
+error_output_port:
+ res = -EINVAL;
+ pw_resource_errorf_id(resource, new_id, res, NAME": unknown output port %s", output_port_str);
+ goto error_exit;
+error_input_port:
+ res = -EINVAL;
+ pw_resource_errorf_id(resource, new_id, res, NAME": unknown input port %s", input_port_str);
+ goto error_exit;
+error_create_link:
+ pw_resource_errorf_id(resource, new_id, res, NAME": can't link ports %d and %d: %s",
+ pw_impl_port_get_info(outport)->id, pw_impl_port_get_info(inport)->id,
+ spa_strerror(res));
+ goto error_exit;
+error_link_register:
+ pw_resource_errorf_id(resource, new_id, res, NAME": can't register link: %s", spa_strerror(res));
+ goto error_exit;
+error_exit:
+ pw_properties_free(properties);
+ errno = -res;
+ return NULL;
+}
+
+static const struct pw_impl_factory_implementation impl_factory = {
+ PW_VERSION_IMPL_FACTORY_IMPLEMENTATION,
+ .create_object = create_object,
+};
+
+static void factory_destroy(void *data)
+{
+ struct factory_data *d = data;
+ struct link_data *ld, *t;
+
+ spa_hook_remove(&d->factory_listener);
+
+ spa_list_for_each_safe(ld, t, &d->link_list, l)
+ pw_impl_link_destroy(ld->link);
+
+ d->factory = NULL;
+ if (d->module)
+ pw_impl_module_destroy(d->module);
+}
+
+static const struct pw_impl_factory_events factory_events = {
+ PW_VERSION_IMPL_FACTORY_EVENTS,
+ .destroy = factory_destroy,
+};
+
+static void module_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->module_listener);
+ d->module = NULL;
+ if (d->factory)
+ pw_impl_factory_destroy(d->factory);
+}
+
+static void module_registered(void *data)
+{
+ struct factory_data *d = data;
+ struct pw_impl_module *module = d->module;
+ struct pw_impl_factory *factory = d->factory;
+ struct spa_dict_item items[1];
+ char id[16];
+ int res;
+
+ snprintf(id, sizeof(id), "%d", pw_global_get_id(pw_impl_module_get_global(module)));
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
+ pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
+
+ if ((res = pw_impl_factory_register(factory, NULL)) < 0) {
+ pw_log_error("%p: can't register factory: %s", factory, spa_strerror(res));
+ }
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+ .registered = module_registered,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_factory *factory;
+ struct factory_data *data;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ factory = pw_context_create_factory(context,
+ "link-factory",
+ PW_TYPE_INTERFACE_Link,
+ PW_VERSION_LINK,
+ pw_properties_new(
+ PW_KEY_FACTORY_USAGE, FACTORY_USAGE,
+ NULL),
+ sizeof(*data));
+ if (factory == NULL)
+ return -errno;
+
+ data = pw_impl_factory_get_user_data(factory);
+ data->factory = factory;
+ data->module = module;
+ data->context = context;
+ data->work = pw_context_get_work_queue(context);
+
+ spa_list_init(&data->link_list);
+
+ pw_log_debug("module %p: new", module);
+
+ pw_impl_factory_set_implementation(factory,
+ &impl_factory,
+ data);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data);
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ return 0;
+}
diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
new file mode 100644
index 0000000..7d66539
--- /dev/null
+++ b/src/modules/module-loopback.c
@@ -0,0 +1,737 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/utils/json.h>
+#include <spa/utils/ringbuffer.h>
+#include <spa/param/latency-utils.h>
+#include <spa/debug/types.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/profiler.h>
+
+/** \page page_module_loopback PipeWire Module: Loopback
+ *
+ * The loopback module passes the output of a capture stream unmodified to a playback stream.
+ * It can be used to construct a link between a source and sink but also to
+ * create new virtual sinks or sources or to remap channel between streams.
+ *
+ * Because both ends of the loopback are built with streams, the session manager can
+ * manage the configuration and connection with the sinks and sources.
+ *
+ * ## Module Options
+ *
+ * - `node.description`: a human readable name for the loopback streams
+ * - `target.delay.sec`: delay in seconds as float (Since 0.3.60)
+ * - `capture.props = {}`: properties to be passed to the input stream
+ * - `playback.props = {}`: properties to be passed to the output stream
+ *
+ * ## General options
+ *
+ * Options with well-known behavior. Most options can be added to the global
+ * configuration or the individual streams:
+ *
+ * - \ref PW_KEY_REMOTE_NAME
+ * - \ref PW_KEY_AUDIO_RATE
+ * - \ref PW_KEY_AUDIO_CHANNELS
+ * - \ref SPA_KEY_AUDIO_POSITION
+ * - \ref PW_KEY_MEDIA_NAME
+ * - \ref PW_KEY_NODE_LATENCY
+ * - \ref PW_KEY_NODE_DESCRIPTION
+ * - \ref PW_KEY_NODE_GROUP
+ * - \ref PW_KEY_NODE_LINK_GROUP
+ * - \ref PW_KEY_NODE_VIRTUAL
+ * - \ref PW_KEY_NODE_NAME: See notes below. If not specified, defaults to
+ * 'loopback-<pid>-<module-id>'.
+ *
+ * Stream only properties:
+ *
+ * - \ref PW_KEY_MEDIA_CLASS
+ * - \ref PW_KEY_NODE_NAME: if not given per stream, the global node.name will be
+ * prefixed with 'input.' and 'output.' to generate a capture and playback
+ * stream node.name respectively.
+ *
+ * ## Example configuration of a virtual sink
+ *
+ * This Virtual sink routes stereo input to the rear channels of a 7.1 sink.
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-loopback
+ * args = {
+ * node.description = "CM106 Stereo Pair 2"
+ * #target.delay.sec = 1.5
+ * capture.props = {
+ * node.name = "CM106_stereo_pair_2"
+ * media.class = "Audio/Sink"
+ * audio.position = [ FL FR ]
+ * }
+ * playback.props = {
+ * node.name = "playback.CM106_stereo_pair_2"
+ * audio.position = [ RL RR ]
+ * target.object = "alsa_output.usb-0d8c_USB_Sound_Device-00.analog-surround-71"
+ * node.dont-reconnect = true
+ * stream.dont-remix = true
+ * node.passive = true
+ * }
+ * }
+ * }
+ * ]
+ *\endcode
+ *
+ * ## See also
+ *
+ * `pw-loopback` : a tool that loads the loopback module with given parameters.
+ */
+
+#define NAME "loopback"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Create loopback streams" },
+ { PW_KEY_MODULE_USAGE, " [ remote.name=<remote> ] "
+ "[ node.latency=<latency as fraction> ] "
+ "[ node.description=<description of the nodes> ] "
+ "[ audio.rate=<sample rate> ] "
+ "[ audio.channels=<number of channels> ] "
+ "[ audio.position=<channel map> ] "
+ "[ target.delay.sec=<delay as seconds in float> ] "
+ "[ capture.props=<properties> ] "
+ "[ playback.props=<properties> ] " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+#include <stdlib.h>
+#include <signal.h>
+#include <getopt.h>
+#include <limits.h>
+#include <math.h>
+
+#include <spa/pod/builder.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/audio/raw.h>
+
+#include <pipewire/pipewire.h>
+
+struct impl {
+ struct pw_context *context;
+
+ struct pw_impl_module *module;
+
+ struct spa_hook module_listener;
+
+ struct pw_core *core;
+ struct spa_hook core_proxy_listener;
+ struct spa_hook core_listener;
+
+ struct pw_properties *capture_props;
+ struct pw_stream *capture;
+ struct spa_hook capture_listener;
+ struct spa_audio_info_raw capture_info;
+ struct spa_latency_info capture_latency;
+
+ struct pw_properties *playback_props;
+ struct pw_stream *playback;
+ struct spa_hook playback_listener;
+ struct spa_audio_info_raw playback_info;
+ struct spa_latency_info playback_latency;
+
+ unsigned int do_disconnect:1;
+ unsigned int recalc_delay:1;
+
+ float target_delay;
+ struct spa_ringbuffer buffer;
+ uint8_t *buffer_data;
+ uint32_t buffer_size;
+};
+
+static void capture_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->capture_listener);
+ impl->capture = NULL;
+}
+
+static void recalculate_delay(struct impl *impl)
+{
+ uint32_t target = impl->capture_info.rate * impl->target_delay, cdelay, pdelay;
+ uint32_t delay, w;
+ struct pw_time pwt;
+
+ pw_stream_get_time_n(impl->playback, &pwt, sizeof(pwt));
+ pdelay = pwt.delay;
+ pw_stream_get_time_n(impl->capture, &pwt, sizeof(pwt));
+ cdelay = pwt.delay;
+
+ delay = target - SPA_MIN(target, pdelay + cdelay);
+ delay = SPA_MIN(delay, impl->buffer_size / 4);
+
+ spa_ringbuffer_get_write_index(&impl->buffer, &w);
+ spa_ringbuffer_read_update(&impl->buffer, w - (delay * 4));
+
+ pw_log_info("target:%d c:%d + p:%d + delay:%d = (%d)",
+ target, cdelay, pdelay, delay,
+ cdelay + pdelay + delay);
+}
+
+static void capture_process(void *d)
+{
+ struct impl *impl = d;
+ pw_stream_trigger_process(impl->playback);
+}
+
+static void playback_process(void *d)
+{
+ struct impl *impl = d;
+ struct pw_buffer *in, *out;
+ uint32_t i;
+
+ if (impl->recalc_delay) {
+ recalculate_delay(impl);
+ impl->recalc_delay = false;
+ }
+
+ if ((in = pw_stream_dequeue_buffer(impl->capture)) == NULL)
+ pw_log_debug("out of capture buffers: %m");
+
+ if ((out = pw_stream_dequeue_buffer(impl->playback)) == NULL)
+ pw_log_debug("out of playback buffers: %m");
+
+ if (in != NULL && out != NULL) {
+ uint32_t outsize = UINT32_MAX;
+ int32_t stride = 0;
+ struct spa_data *d;
+ const void *src[in->buffer->n_datas];
+ uint32_t r, w, buffer_size;
+
+ for (i = 0; i < in->buffer->n_datas; i++) {
+ uint32_t offs, size;
+
+ d = &in->buffer->datas[i];
+ offs = SPA_MIN(d->chunk->offset, d->maxsize);
+ size = SPA_MIN(d->chunk->size, d->maxsize - offs);
+
+ src[i] = SPA_PTROFF(d->data, offs, void);
+ outsize = SPA_MIN(outsize, size);
+ stride = SPA_MAX(stride, d->chunk->stride);
+ }
+ if (impl->buffer_size > 0) {
+ buffer_size = impl->buffer_size;
+ spa_ringbuffer_get_write_index(&impl->buffer, &w);
+ for (i = 0; i < in->buffer->n_datas; i++) {
+ void *buffer_data = &impl->buffer_data[i * buffer_size];
+ spa_ringbuffer_write_data(&impl->buffer,
+ buffer_data, buffer_size,
+ w % buffer_size, src[i], outsize);
+ src[i] = buffer_data;
+ }
+ w += outsize;
+ spa_ringbuffer_write_update(&impl->buffer, w);
+ spa_ringbuffer_get_read_index(&impl->buffer, &r);
+ } else {
+ r = 0;
+ buffer_size = outsize;
+ }
+ for (i = 0; i < out->buffer->n_datas; i++) {
+ d = &out->buffer->datas[i];
+
+ outsize = SPA_MIN(outsize, d->maxsize);
+
+ if (i < in->buffer->n_datas)
+ spa_ringbuffer_read_data(&impl->buffer,
+ src[i], buffer_size,
+ r % buffer_size,
+ d->data, outsize);
+ else
+ memset(d->data, 0, outsize);
+
+ d->chunk->offset = 0;
+ d->chunk->size = outsize;
+ d->chunk->stride = stride;
+ }
+ if (impl->buffer_size > 0) {
+ r += outsize;
+ spa_ringbuffer_read_update(&impl->buffer, r);
+ }
+ }
+
+ if (in != NULL)
+ pw_stream_queue_buffer(impl->capture, in);
+ if (out != NULL)
+ pw_stream_queue_buffer(impl->playback, out);
+}
+
+static void param_latency_changed(struct impl *impl, const struct spa_pod *param,
+ struct spa_latency_info *info, struct pw_stream *other)
+{
+ struct spa_latency_info latency;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b;
+ const struct spa_pod *params[1];
+
+ if (spa_latency_parse(param, &latency) < 0)
+ return;
+
+ *info = 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);
+
+ impl->recalc_delay = true;
+}
+
+static void stream_state_changed(void *data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct impl *impl = data;
+ switch (state) {
+ case PW_STREAM_STATE_PAUSED:
+ pw_stream_flush(impl->playback, false);
+ pw_stream_flush(impl->capture, false);
+ impl->recalc_delay = true;
+ break;
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_log_info("module %p: unconnected", impl);
+ pw_impl_module_schedule_destroy(impl->module);
+ break;
+ case PW_STREAM_STATE_ERROR:
+ pw_log_info("module %p: error: %s", impl, error);
+ break;
+ default:
+ break;
+ }
+}
+
+static void recalculate_buffer(struct impl *impl)
+{
+ if (impl->target_delay > 0.0f) {
+ uint32_t delay = impl->capture_info.rate * impl->target_delay;
+ void *data;
+
+ impl->buffer_size = (delay + (1u<<15)) * 4;
+ data = realloc(impl->buffer_data, impl->buffer_size * impl->capture_info.channels);
+ if (data == NULL) {
+ pw_log_warn("can't allocate delay buffer, delay disabled: %m");
+ impl->buffer_size = 0;
+ free(impl->buffer_data);
+ }
+ impl->buffer_data = data;
+ spa_ringbuffer_init(&impl->buffer);
+ } else {
+ impl->buffer_size = 0;
+ free(impl->buffer_data);
+ impl->buffer_data = NULL;
+ }
+ pw_log_info("configured delay:%f buffer:%d", impl->target_delay, impl->buffer_size);
+ impl->recalc_delay = true;
+}
+
+static void capture_param_changed(void *data, uint32_t id, const struct spa_pod *param)
+{
+ struct impl *impl = data;
+
+ switch (id) {
+ case SPA_PARAM_Format:
+ {
+ struct spa_audio_info_raw info;
+ if (param == NULL)
+ return;
+ if (spa_format_audio_raw_parse(param, &info) < 0)
+ return;
+ if (info.rate == 0 ||
+ info.channels == 0 ||
+ info.channels > SPA_AUDIO_MAX_CHANNELS)
+ return;
+
+ impl->capture_info = info;
+ recalculate_buffer(impl);
+ break;
+ }
+ case SPA_PARAM_Latency:
+ param_latency_changed(impl, param, &impl->capture_latency, impl->playback);
+ break;
+ }
+}
+
+static const struct pw_stream_events in_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = capture_destroy,
+ .process = capture_process,
+ .state_changed = stream_state_changed,
+ .param_changed = capture_param_changed,
+};
+
+static void playback_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->playback_listener);
+ impl->playback = NULL;
+}
+
+static void playback_param_changed(void *data, uint32_t id, const struct spa_pod *param)
+{
+ struct impl *impl = data;
+
+ switch (id) {
+ case SPA_PARAM_Latency:
+ param_latency_changed(impl, param, &impl->playback_latency, impl->capture);
+ break;
+ }
+}
+static const struct pw_stream_events out_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = playback_destroy,
+ .process = playback_process,
+ .state_changed = stream_state_changed,
+ .param_changed = playback_param_changed,
+};
+
+static int setup_streams(struct impl *impl)
+{
+ int res;
+ uint32_t n_params;
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b;
+
+ impl->capture = pw_stream_new(impl->core,
+ "loopback capture", impl->capture_props);
+ impl->capture_props = NULL;
+ if (impl->capture == NULL)
+ return -errno;
+
+ pw_stream_add_listener(impl->capture,
+ &impl->capture_listener,
+ &in_stream_events, impl);
+
+ impl->playback = pw_stream_new(impl->core,
+ "loopback playback", impl->playback_props);
+ impl->playback_props = NULL;
+ if (impl->playback == NULL)
+ return -errno;
+
+ pw_stream_add_listener(impl->playback,
+ &impl->playback_listener,
+ &out_stream_events, impl);
+
+ /* connect playback first to activate it before capture triggers it */
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
+ &impl->playback_info);
+ if ((res = pw_stream_connect(impl->playback,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS |
+ PW_STREAM_FLAG_TRIGGER,
+ params, n_params)) < 0)
+ return res;
+
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
+ &impl->capture_info);
+ if ((res = pw_stream_connect(impl->capture,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0)
+ return res;
+
+ return 0;
+}
+
+static void core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct impl *impl = data;
+
+ if (res == -ENOENT) {
+ pw_log_info("message id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+ } else {
+ pw_log_warn("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+ }
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = core_error,
+};
+
+static void core_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->core_listener);
+ impl->core = NULL;
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ .destroy = core_destroy,
+};
+
+static void impl_destroy(struct impl *impl)
+{
+ /* deactivate both streams before destroying any of them */
+ if (impl->capture)
+ pw_stream_set_active(impl->capture, false);
+ if (impl->playback)
+ pw_stream_set_active(impl->playback, false);
+
+ if (impl->capture)
+ pw_stream_destroy(impl->capture);
+ if (impl->playback)
+ pw_stream_destroy(impl->playback);
+
+ if (impl->core && impl->do_disconnect)
+ pw_core_disconnect(impl->core);
+
+ pw_properties_free(impl->capture_props);
+ pw_properties_free(impl->playback_props);
+ free(impl);
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->module_listener);
+ impl_destroy(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_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));
+}
+
+static void copy_props(struct impl *impl, struct pw_properties *props, const char *key)
+{
+ const char *str;
+ if ((str = pw_properties_get(props, key)) != NULL) {
+ if (pw_properties_get(impl->capture_props, key) == NULL)
+ pw_properties_set(impl->capture_props, key, str);
+ if (pw_properties_get(impl->playback_props, key) == NULL)
+ pw_properties_set(impl->playback_props, key, str);
+ }
+}
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props;
+ struct impl *impl;
+ uint32_t id = pw_global_get_id(pw_impl_module_get_global(module));
+ uint32_t pid = getpid();
+ const char *str;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args)
+ props = pw_properties_new_string(args);
+ else
+ props = pw_properties_new(NULL, NULL);
+ if (props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+
+ impl->capture_props = pw_properties_new(NULL, NULL);
+ impl->playback_props = pw_properties_new(NULL, NULL);
+ if (impl->capture_props == NULL || impl->playback_props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+
+ impl->module = module;
+ impl->context = context;
+
+ if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_GROUP, "loopback-%u-%u", pid, id);
+ if (pw_properties_get(props, PW_KEY_NODE_LINK_GROUP) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_LINK_GROUP, "loopback-%u-%u", pid, id);
+ if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
+ if (pw_properties_get(props, "resample.prefill") == NULL)
+ pw_properties_set(props, "resample.prefill", "true");
+
+ if ((str = pw_properties_get(props, "capture.props")) != NULL)
+ pw_properties_update_string(impl->capture_props, str, strlen(str));
+ if ((str = pw_properties_get(props, "playback.props")) != NULL)
+ pw_properties_update_string(impl->playback_props, str, strlen(str));
+
+ if ((str = pw_properties_get(props, "target.delay.sec")) != NULL)
+ spa_atof(str, &impl->target_delay);
+ if (impl->target_delay > 0.0f &&
+ pw_properties_get(props, PW_KEY_NODE_LATENCY) == NULL)
+ /* a source and sink (USB) usually have a 1.5 quantum delay, so we use
+ * a 2 times smaller quantum to compensate */
+ pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u",
+ (unsigned)(impl->target_delay * 48000 / 3), 48000);
+
+ 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_DESCRIPTION);
+ copy_props(impl, props, PW_KEY_NODE_GROUP);
+ copy_props(impl, props, PW_KEY_NODE_LINK_GROUP);
+ copy_props(impl, props, PW_KEY_NODE_LATENCY);
+ copy_props(impl, props, PW_KEY_NODE_VIRTUAL);
+ copy_props(impl, props, PW_KEY_MEDIA_NAME);
+ copy_props(impl, props, "resample.prefill");
+
+ if ((str = pw_properties_get(props, PW_KEY_NODE_NAME)) == NULL) {
+ pw_properties_setf(props, PW_KEY_NODE_NAME,
+ "loopback-%u-%u", pid, id);
+ str = pw_properties_get(props, PW_KEY_NODE_NAME);
+ }
+ if (pw_properties_get(impl->capture_props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_setf(impl->capture_props, PW_KEY_NODE_NAME,
+ "input.%s", str);
+ if (pw_properties_get(impl->playback_props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_setf(impl->playback_props, PW_KEY_NODE_NAME,
+ "output.%s", str);
+ if (pw_properties_get(impl->capture_props, PW_KEY_NODE_DESCRIPTION) == NULL)
+ pw_properties_set(impl->capture_props, PW_KEY_NODE_DESCRIPTION, str);
+ if (pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION) == NULL)
+ pw_properties_set(impl->playback_props, PW_KEY_NODE_DESCRIPTION, str);
+
+ parse_audio_info(impl->capture_props, &impl->capture_info);
+ parse_audio_info(impl->playback_props, &impl->playback_info);
+
+ if (pw_properties_get(impl->capture_props, PW_KEY_MEDIA_NAME) == NULL)
+ pw_properties_setf(impl->capture_props, PW_KEY_MEDIA_NAME, "%s input",
+ pw_properties_get(impl->capture_props, PW_KEY_NODE_DESCRIPTION));
+ if (pw_properties_get(impl->playback_props, PW_KEY_MEDIA_NAME) == NULL)
+ pw_properties_setf(impl->playback_props, PW_KEY_MEDIA_NAME, "%s output",
+ pw_properties_get(impl->playback_props, PW_KEY_NODE_DESCRIPTION));
+
+ 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);
+ impl->core = pw_context_connect(impl->context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ impl->do_disconnect = true;
+ }
+ if (impl->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto error;
+ }
+
+ pw_properties_free(props);
+
+ pw_proxy_add_listener((struct pw_proxy*)impl->core,
+ &impl->core_proxy_listener,
+ &core_proxy_events, impl);
+ pw_core_add_listener(impl->core,
+ &impl->core_listener,
+ &core_events, impl);
+
+ setup_streams(impl);
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+
+error:
+ pw_properties_free(props);
+ impl_destroy(impl);
+ return res;
+}
diff --git a/src/modules/module-metadata.c b/src/modules/module-metadata.c
new file mode 100644
index 0000000..b8619a4
--- /dev/null
+++ b/src/modules/module-metadata.c
@@ -0,0 +1,241 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/metadata.h>
+
+/** \page page_module_metadata PipeWire Module: Metadata
+ */
+
+#define NAME "metadata"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Allow clients to create metadata store" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+
+void * pw_metadata_new(struct pw_context *context, struct pw_resource *resource,
+ struct pw_properties *properties);
+
+struct pw_proxy *pw_core_metadata_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object, size_t user_data_size);
+
+int pw_protocol_native_ext_metadata_init(struct pw_context *context);
+
+struct factory_data {
+ struct pw_impl_factory *factory;
+ struct spa_hook factory_listener;
+
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_export_type export_metadata;
+};
+
+static void *create_object(void *_data,
+ struct pw_resource *resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id)
+{
+ struct factory_data *data = _data;
+ struct pw_context *context = pw_impl_module_get_context(data->module);
+ void *result;
+ struct pw_resource *metadata_resource = NULL;
+ struct pw_impl_client *client = resource ? pw_resource_get_client(resource) : NULL;
+ int res;
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL)
+ return NULL;
+
+ pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d",
+ pw_impl_factory_get_info(data->factory)->id);
+ pw_properties_setf(properties, PW_KEY_MODULE_ID, "%d",
+ pw_impl_module_get_info(data->module)->id);
+
+ if (pw_properties_get(properties, PW_KEY_METADATA_NAME) == NULL)
+ pw_properties_set(properties, PW_KEY_METADATA_NAME, "default");
+
+ if (client) {
+ metadata_resource = pw_resource_new(client, new_id, PW_PERM_ALL, type, version, 0);
+ if (metadata_resource == NULL) {
+ res = -errno;
+ goto error_resource;
+ }
+
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d",
+ pw_impl_client_get_info(client)->id);
+
+ result = pw_metadata_new(context, metadata_resource, properties);
+ if (result == NULL) {
+ properties = NULL;
+ res = -errno;
+ goto error_node;
+ }
+ } else {
+ result = pw_context_create_metadata(context, NULL, properties, 0);
+ if (result == NULL) {
+ properties = NULL;
+ res = -errno;
+ goto error_node;
+ }
+ pw_impl_metadata_register(result, NULL);
+ }
+ return result;
+
+error_resource:
+ pw_resource_errorf_id(resource, new_id, res,
+ "can't create resource: %s", spa_strerror(res));
+ goto error_exit;
+error_node:
+ pw_resource_errorf_id(resource, new_id, res,
+ "can't create metadata: %s", spa_strerror(res));
+ goto error_exit_free;
+
+error_exit_free:
+ if (metadata_resource)
+ pw_resource_remove(metadata_resource);
+error_exit:
+ pw_properties_free(properties);
+ errno = -res;
+ return NULL;
+}
+
+static const struct pw_impl_factory_implementation impl_factory = {
+ PW_VERSION_IMPL_FACTORY_IMPLEMENTATION,
+ .create_object = create_object,
+};
+
+static void factory_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->factory_listener);
+ d->factory = NULL;
+ if (d->module)
+ pw_impl_module_destroy(d->module);
+}
+
+static const struct pw_impl_factory_events factory_events = {
+ PW_VERSION_IMPL_FACTORY_EVENTS,
+ .destroy = factory_destroy,
+};
+
+static void module_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->module_listener);
+ spa_list_remove(&d->export_metadata.link);
+ d->module = NULL;
+ if (d->factory)
+ pw_impl_factory_destroy(d->factory);
+}
+
+static void module_registered(void *data)
+{
+ struct factory_data *d = data;
+ struct pw_impl_module *module = d->module;
+ struct pw_impl_factory *factory = d->factory;
+ struct spa_dict_item items[1];
+ char id[16];
+ int res;
+
+ snprintf(id, sizeof(id), "%d", pw_global_get_id(pw_impl_module_get_global(module)));
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
+ pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
+
+ if ((res = pw_impl_factory_register(factory, NULL)) < 0) {
+ pw_log_error("%p: can't register factory: %s", factory, spa_strerror(res));
+ }
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+ .registered = module_registered,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_factory *factory;
+ struct factory_data *data;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ if ((res = pw_protocol_native_ext_metadata_init(context)) < 0)
+ return res;
+
+ factory = pw_context_create_factory(context,
+ "metadata",
+ PW_TYPE_INTERFACE_Metadata,
+ PW_VERSION_METADATA,
+ NULL,
+ sizeof(*data));
+ if (factory == NULL)
+ return -errno;
+
+ data = pw_impl_factory_get_user_data(factory);
+ data->factory = factory;
+ data->module = module;
+
+ pw_log_debug("module %p: new", module);
+
+ pw_impl_factory_set_implementation(factory,
+ &impl_factory,
+ data);
+
+ data->export_metadata.type = PW_TYPE_INTERFACE_Metadata;
+ data->export_metadata.func = pw_core_metadata_export;
+ if ((res = pw_context_register_export_type(context, &data->export_metadata)) < 0)
+ goto error;
+
+ pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data);
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+error:
+ pw_impl_factory_destroy(data->factory);
+ return res;
+}
diff --git a/src/modules/module-metadata/metadata.c b/src/modules/module-metadata/metadata.c
new file mode 100644
index 0000000..d863bea
--- /dev/null
+++ b/src/modules/module-metadata/metadata.c
@@ -0,0 +1,316 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/result.h>
+
+#include <pipewire/impl.h>
+
+#include <pipewire/extensions/metadata.h>
+
+#define NAME "metadata"
+
+struct impl {
+ struct spa_hook context_listener;
+
+ struct pw_global *global;
+ struct spa_hook global_listener;
+
+ struct pw_metadata *metadata;
+ struct pw_resource *resource;
+ struct spa_hook resource_listener;
+ int pending;
+};
+
+struct resource_data {
+ struct impl *impl;
+
+ struct pw_resource *resource;
+ struct spa_hook resource_listener;
+ struct spa_hook object_listener;
+ struct spa_hook metadata_listener;
+ struct spa_hook impl_resource_listener;
+ int pong_seq;
+};
+
+#define pw_metadata_resource(r,m,v,...) \
+ pw_resource_call_res(r,struct pw_metadata_events,m,v,__VA_ARGS__)
+
+#define pw_metadata_resource_property(r,...) \
+ pw_metadata_resource(r,property,0,__VA_ARGS__)
+
+static int metadata_property(void *data,
+ uint32_t subject,
+ const char *key,
+ const char *type,
+ const char *value)
+{
+ struct resource_data *d = data;
+ struct pw_resource *resource = d->resource;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct impl *impl = d->impl;
+
+ if (impl->pending == 0 || d->pong_seq != 0) {
+ if (pw_impl_client_check_permissions(client, subject, PW_PERM_R) >= 0)
+ pw_metadata_resource_property(d->resource, subject, key, type, value);
+ }
+ return 0;
+}
+
+static const struct pw_metadata_events metadata_events = {
+ PW_VERSION_METADATA_EVENTS,
+ .property = metadata_property,
+};
+
+static int metadata_set_property(void *object,
+ uint32_t subject,
+ const char *key,
+ const char *type,
+ const char *value)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ struct pw_resource *resource = d->resource;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ int res;
+
+ if ((res = pw_impl_client_check_permissions(client, subject, PW_PERM_R | PW_PERM_M)) < 0)
+ goto error;
+
+ pw_metadata_set_property(impl->metadata, subject, key, type, value);
+ return 0;
+
+error:
+ pw_resource_errorf(resource, res, "set property error for id %d: %s",
+ subject, spa_strerror(res));
+ return res;
+}
+
+static int metadata_clear(void *object)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ pw_metadata_clear(impl->metadata);
+ return 0;
+}
+
+static const struct pw_metadata_methods metadata_methods = {
+ PW_VERSION_METADATA_METHODS,
+ .set_property = metadata_set_property,
+ .clear = metadata_clear,
+};
+
+
+static void global_unbind(void *data)
+{
+ struct resource_data *d = data;
+ if (d->resource) {
+ spa_hook_remove(&d->resource_listener);
+ spa_hook_remove(&d->object_listener);
+ spa_hook_remove(&d->metadata_listener);
+ spa_hook_remove(&d->impl_resource_listener);
+ }
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = global_unbind,
+};
+
+static void remove_pending(struct resource_data *d)
+{
+ if (d->pong_seq != 0) {
+ pw_impl_client_set_busy(pw_resource_get_client(d->resource), false);
+ d->pong_seq = 0;
+ d->impl->pending--;
+ }
+}
+
+static void impl_resource_destroy(void *data)
+{
+ struct resource_data *d = data;
+ remove_pending(d);
+}
+
+static void impl_resource_pong(void *data, int seq)
+{
+ struct resource_data *d = data;
+ if (d->pong_seq == seq)
+ remove_pending(d);
+}
+
+static const struct pw_resource_events impl_resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = impl_resource_destroy,
+ .pong = impl_resource_pong,
+};
+
+static int
+global_bind(void *object, struct pw_impl_client *client, uint32_t permissions,
+ uint32_t version, uint32_t id)
+{
+ struct impl *impl = object;
+ struct pw_resource *resource;
+ struct resource_data *data;
+
+ resource = pw_resource_new(client, id, permissions, PW_TYPE_INTERFACE_Metadata, version, sizeof(*data));
+ if (resource == NULL)
+ return -errno;
+
+ data = pw_resource_get_user_data(resource);
+ data->impl = impl;
+ data->resource = resource;
+
+ pw_global_add_resource(impl->global, resource);
+
+ /* listen for when the resource goes away */
+ pw_resource_add_listener(resource,
+ &data->resource_listener,
+ &resource_events, data);
+
+ /* resource methods -> implementation */
+ pw_resource_add_object_listener(resource,
+ &data->object_listener,
+ &metadata_methods, data);
+
+ pw_impl_client_set_busy(client, true);
+
+ /* implementation events -> resource */
+ pw_metadata_add_listener(impl->metadata,
+ &data->metadata_listener,
+ &metadata_events, data);
+
+ pw_resource_add_listener(impl->resource,
+ &data->impl_resource_listener,
+ &impl_resource_events, data);
+
+ data->pong_seq = pw_resource_ping(impl->resource, data->pong_seq);
+ impl->pending++;
+
+ return 0;
+}
+
+static void global_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->global_listener);
+ impl->global = NULL;
+ if (impl->resource)
+ pw_resource_destroy(impl->resource);
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+
+static void context_global_removed(void *data, struct pw_global *global)
+{
+ struct impl *impl = data;
+ pw_metadata_set_property(impl->metadata,
+ pw_global_get_id(global), NULL, NULL, NULL);
+}
+
+static const struct pw_context_events context_events = {
+ PW_VERSION_CONTEXT_EVENTS,
+ .global_removed = context_global_removed,
+};
+
+static void global_resource_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->context_listener);
+ spa_hook_remove(&impl->resource_listener);
+ impl->resource = NULL;
+ impl->metadata = NULL;
+ if (impl->global)
+ pw_global_destroy(impl->global);
+ free(impl);
+}
+
+static const struct pw_resource_events global_resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = global_resource_destroy,
+};
+
+void *
+pw_metadata_new(struct pw_context *context, struct pw_resource *resource,
+ struct pw_properties *properties)
+{
+ struct impl *impl;
+ char serial_str[32];
+ struct spa_dict_item items[1] = {
+ SPA_DICT_ITEM_INIT(PW_KEY_OBJECT_SERIAL, serial_str),
+ };
+ struct spa_dict extra_props = SPA_DICT_INIT_ARRAY(items);
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ NULL
+ };
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL)
+ return NULL;
+
+ impl = calloc(1, sizeof(*impl));
+ if (impl == NULL) {
+ pw_properties_free(properties);
+ return NULL;
+ }
+
+ pw_resource_install_marshal(resource, true);
+
+ impl->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_Metadata,
+ PW_VERSION_METADATA,
+ properties,
+ global_bind, impl);
+ if (impl->global == NULL) {
+ free(impl);
+ return NULL;
+ }
+ impl->resource = resource;
+ impl->metadata = (struct pw_metadata*)resource;
+
+ spa_scnprintf(serial_str, sizeof(serial_str), "%"PRIu64,
+ pw_global_get_serial(impl->global));
+ pw_global_update_keys(impl->global, &extra_props, keys);
+
+ pw_context_add_listener(context, &impl->context_listener,
+ &context_events, impl);
+
+ pw_global_add_listener(impl->global,
+ &impl->global_listener,
+ &global_events, impl);
+
+ pw_resource_set_bound_id(resource, pw_global_get_id(impl->global));
+ pw_global_register(impl->global);
+
+ pw_resource_add_listener(resource,
+ &impl->resource_listener,
+ &global_resource_events, impl);
+
+ return impl;
+}
diff --git a/src/modules/module-metadata/protocol-native.c b/src/modules/module-metadata/protocol-native.c
new file mode 100644
index 0000000..54958db
--- /dev/null
+++ b/src/modules/module-metadata/protocol-native.c
@@ -0,0 +1,354 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+
+#include <spa/pod/builder.h>
+#include <spa/pod/parser.h>
+#include <spa/utils/result.h>
+
+#include <pipewire/pipewire.h>
+
+#include <pipewire/extensions/protocol-native.h>
+#include <pipewire/extensions/metadata.h>
+
+static int metadata_resource_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_metadata_events *events,
+ void *data)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ pw_resource_add_object_listener(resource, listener, events, data);
+
+ b = pw_protocol_native_begin_resource(resource, PW_METADATA_METHOD_ADD_LISTENER, NULL);
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int metadata_proxy_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_metadata_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static int metadata_resource_demarshal_add_listener(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ return -ENOTSUP;
+}
+
+static const struct pw_metadata_events pw_protocol_native_metadata_client_event_marshal;
+
+static int metadata_proxy_demarshal_add_listener(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_hook listener;
+ int res;
+
+ spa_zero(listener);
+ res = pw_proxy_notify(proxy, struct pw_metadata_methods, add_listener, 0,
+ &listener, &pw_protocol_native_metadata_client_event_marshal, object);
+ spa_hook_remove(&listener);
+
+ return res;
+}
+
+static void metadata_marshal_set_property(struct spa_pod_builder *b, uint32_t subject,
+ const char *key, const char *type, const char *value)
+{
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(subject),
+ SPA_POD_String(key),
+ SPA_POD_String(type),
+ SPA_POD_String(value));
+}
+
+static int metadata_proxy_marshal_set_property(void *object, uint32_t subject,
+ const char *key, const char *type, const char *value)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ b = pw_protocol_native_begin_proxy(proxy, PW_METADATA_METHOD_SET_PROPERTY, NULL);
+ metadata_marshal_set_property(b, subject, key, type, value);
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+static int metadata_resource_marshal_set_property(void *object, uint32_t subject,
+ const char *key, const char *type, const char *value)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+ b = pw_protocol_native_begin_resource(resource, PW_METADATA_METHOD_SET_PROPERTY, NULL);
+ metadata_marshal_set_property(b, subject, key, type, value);
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int metadata_demarshal_set_property(struct spa_pod_parser *prs, uint32_t *subject,
+ char **key, char **type, char **value)
+{
+ return spa_pod_parser_get_struct(prs,
+ SPA_POD_Int(subject),
+ SPA_POD_String(key),
+ SPA_POD_String(type),
+ SPA_POD_String(value));
+}
+
+static int metadata_proxy_demarshal_set_property(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t subject;
+ char *key, *type, *value;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (metadata_demarshal_set_property(&prs, &subject, &key, &type, &value) < 0)
+ return -EINVAL;
+ return pw_proxy_notify(proxy, struct pw_metadata_methods, set_property, 0, subject, key, type, value);
+}
+
+static int metadata_resource_demarshal_set_property(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t subject;
+ char *key, *type, *value;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (metadata_demarshal_set_property(&prs, &subject, &key, &type, &value) < 0)
+ return -EINVAL;
+ return pw_resource_notify(resource, struct pw_metadata_methods, set_property, 0, subject, key, type, value);
+}
+
+static void metadata_marshal_clear(struct spa_pod_builder *b)
+{
+ spa_pod_builder_add_struct(b, SPA_POD_None());
+}
+
+static int metadata_proxy_marshal_clear(void *object)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ b = pw_protocol_native_begin_proxy(proxy, PW_METADATA_METHOD_CLEAR, NULL);
+ metadata_marshal_clear(b);
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+static int metadata_resource_marshal_clear(void *object)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+ b = pw_protocol_native_begin_resource(resource, PW_METADATA_METHOD_CLEAR, NULL);
+ metadata_marshal_clear(b);
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int metadata_proxy_demarshal_clear(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs, SPA_POD_None()) < 0)
+ return -EINVAL;
+ pw_proxy_notify(proxy, struct pw_metadata_methods, clear, 0);
+ return 0;
+}
+
+static int metadata_resource_demarshal_clear(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs, SPA_POD_None()) < 0)
+ return -EINVAL;
+ pw_resource_notify(resource, struct pw_metadata_methods, clear, 0);
+ return 0;
+}
+
+static void metadata_marshal_property(struct spa_pod_builder *b, uint32_t subject,
+ const char *key, const char *type, const char *value)
+{
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(subject),
+ SPA_POD_String(key),
+ SPA_POD_String(type),
+ SPA_POD_String(value));
+}
+
+static int metadata_proxy_marshal_property(void *object, uint32_t subject,
+ const char *key, const char *type, const char *value)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ b = pw_protocol_native_begin_proxy(proxy, PW_METADATA_EVENT_PROPERTY, NULL);
+ metadata_marshal_property(b, subject, key, type, value);
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int metadata_resource_marshal_property(void *object, uint32_t subject,
+ const char *key, const char *type, const char *value)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+ b = pw_protocol_native_begin_resource(resource, PW_METADATA_EVENT_PROPERTY, NULL);
+ metadata_marshal_property(b, subject, key, type, value);
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int metadata_demarshal_property(struct spa_pod_parser *prs,
+ uint32_t *subject, char **key, char **type, char **value)
+{
+ return spa_pod_parser_get_struct(prs,
+ SPA_POD_Int(subject),
+ SPA_POD_String(key),
+ SPA_POD_String(type),
+ SPA_POD_String(value));
+}
+
+static int metadata_proxy_demarshal_property(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t subject;
+ char *key, *type, *value;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (metadata_demarshal_property(&prs,
+ &subject, &key, &type, &value) < 0)
+ return -EINVAL;
+ pw_proxy_notify(proxy, struct pw_metadata_events, property, 0, subject, key, type, value);
+ return 0;
+}
+
+static int metadata_resource_demarshal_property(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t subject;
+ char *key, *type, *value;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (metadata_demarshal_property(&prs,
+ &subject, &key, &type, &value) < 0)
+ return -EINVAL;
+ pw_resource_notify(resource, struct pw_metadata_events, property, 0, subject, key, type, value);
+ return 0;
+}
+
+static const struct pw_metadata_methods pw_protocol_native_metadata_client_method_marshal = {
+ PW_VERSION_METADATA_METHODS,
+ .add_listener = &metadata_proxy_marshal_add_listener,
+ .set_property = &metadata_proxy_marshal_set_property,
+ .clear = &metadata_proxy_marshal_clear,
+};
+static const struct pw_metadata_methods pw_protocol_native_metadata_server_method_marshal = {
+ PW_VERSION_METADATA_METHODS,
+ .add_listener = &metadata_resource_marshal_add_listener,
+ .set_property = &metadata_resource_marshal_set_property,
+ .clear = &metadata_resource_marshal_clear,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_metadata_client_method_demarshal[PW_METADATA_METHOD_NUM] =
+{
+ [PW_METADATA_METHOD_ADD_LISTENER] = { &metadata_proxy_demarshal_add_listener, 0 },
+ [PW_METADATA_METHOD_SET_PROPERTY] = { &metadata_proxy_demarshal_set_property, PW_PERM_W },
+ [PW_METADATA_METHOD_CLEAR] = { &metadata_proxy_demarshal_clear, PW_PERM_W },
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_metadata_server_method_demarshal[PW_METADATA_METHOD_NUM] =
+{
+ [PW_METADATA_METHOD_ADD_LISTENER] = { &metadata_resource_demarshal_add_listener, 0 },
+ [PW_METADATA_METHOD_SET_PROPERTY] = { &metadata_resource_demarshal_set_property, PW_PERM_W },
+ [PW_METADATA_METHOD_CLEAR] = { &metadata_resource_demarshal_clear, PW_PERM_W },
+};
+
+static const struct pw_metadata_events pw_protocol_native_metadata_client_event_marshal = {
+ PW_VERSION_METADATA_EVENTS,
+ .property = &metadata_proxy_marshal_property,
+};
+
+static const struct pw_metadata_events pw_protocol_native_metadata_server_event_marshal = {
+ PW_VERSION_METADATA_EVENTS,
+ .property = &metadata_resource_marshal_property,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_metadata_client_event_demarshal[PW_METADATA_EVENT_NUM] =
+{
+ [PW_METADATA_EVENT_PROPERTY] = { &metadata_proxy_demarshal_property, 0 },
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_metadata_server_event_demarshal[PW_METADATA_EVENT_NUM] =
+{
+ [PW_METADATA_EVENT_PROPERTY] = { &metadata_resource_demarshal_property, 0 },
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_metadata_marshal = {
+ PW_TYPE_INTERFACE_Metadata,
+ PW_VERSION_METADATA,
+ 0,
+ PW_METADATA_METHOD_NUM,
+ PW_METADATA_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_metadata_client_method_marshal,
+ .server_demarshal = pw_protocol_native_metadata_server_method_demarshal,
+ .server_marshal = &pw_protocol_native_metadata_server_event_marshal,
+ .client_demarshal = pw_protocol_native_metadata_client_event_demarshal,
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_metadata_impl_marshal = {
+ PW_TYPE_INTERFACE_Metadata,
+ PW_VERSION_METADATA,
+ PW_PROTOCOL_MARSHAL_FLAG_IMPL,
+ PW_METADATA_EVENT_NUM,
+ PW_METADATA_METHOD_NUM,
+ .client_marshal = &pw_protocol_native_metadata_client_event_marshal,
+ .server_demarshal = pw_protocol_native_metadata_server_event_demarshal,
+ .server_marshal = &pw_protocol_native_metadata_server_method_marshal,
+ .client_demarshal = pw_protocol_native_metadata_client_method_demarshal,
+};
+
+int pw_protocol_native_ext_metadata_init(struct pw_context *context)
+{
+ struct pw_protocol *protocol;
+
+ protocol = pw_context_find_protocol(context, PW_TYPE_INFO_PROTOCOL_Native);
+ if (protocol == NULL)
+ return -EPROTO;
+
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_metadata_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_metadata_impl_marshal);
+ return 0;
+}
diff --git a/src/modules/module-metadata/proxy-metadata.c b/src/modules/module-metadata/proxy-metadata.c
new file mode 100644
index 0000000..e8b747f
--- /dev/null
+++ b/src/modules/module-metadata/proxy-metadata.c
@@ -0,0 +1,92 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <spa/pod/parser.h>
+
+#include "pipewire/pipewire.h"
+#include "pipewire/extensions/metadata.h"
+
+struct object_data {
+ struct pw_metadata *object;
+ struct spa_hook object_listener;
+ struct spa_hook object_methods;
+
+ struct pw_proxy *proxy;
+ struct spa_hook proxy_listener;
+};
+
+static void proxy_object_destroy(void *_data)
+{
+ struct object_data *data = _data;
+ spa_hook_remove(&data->proxy_listener);
+ spa_hook_remove(&data->object_listener);
+ spa_hook_remove(&data->object_methods);
+}
+
+static const struct pw_proxy_events proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .destroy = proxy_object_destroy,
+};
+
+struct pw_proxy *pw_core_metadata_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size)
+{
+ struct pw_metadata *meta = object;
+ struct spa_interface *iface, *miface;
+ struct pw_proxy *proxy;
+ struct object_data *data;
+
+ proxy = pw_core_create_object(core,
+ "metadata",
+ PW_TYPE_INTERFACE_Metadata,
+ PW_VERSION_METADATA,
+ props,
+ user_data_size + sizeof(struct object_data));
+ if (proxy == NULL)
+ return NULL;
+
+ data = pw_proxy_get_user_data(proxy);
+ data = SPA_PTROFF(data, user_data_size, struct object_data);
+ data->object = object;
+ data->proxy = proxy;
+
+ iface = (struct spa_interface*)proxy;
+ miface = (struct spa_interface*)meta;
+
+ pw_proxy_install_marshal(proxy, true);
+
+ pw_proxy_add_listener(proxy, &data->proxy_listener, &proxy_events, data);
+
+ pw_proxy_add_object_listener(proxy, &data->object_methods,
+ miface->cb.funcs, miface->cb.data);
+ pw_metadata_add_listener(meta, &data->object_listener,
+ iface->cb.funcs, iface->cb.data);
+
+ return proxy;
+}
diff --git a/src/modules/module-pipe-tunnel.c b/src/modules/module-pipe-tunnel.c
new file mode 100644
index 0000000..a593305
--- /dev/null
+++ b/src/modules/module-pipe-tunnel.c
@@ -0,0 +1,730 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Sanchayan Maity <sanchayan@asymptotic.io>
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.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/utils/ringbuffer.h>
+#include <spa/utils/dll.h>
+#include <spa/debug/types.h>
+#include <spa/pod/builder.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/latency-utils.h>
+#include <spa/param/audio/raw.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/i18n.h>
+
+/** \page page_module_pipe_tunnel PipeWire Module: Unix Pipe Tunnel
+ *
+ * The pipe-tunnel module provides a source or sink that tunnels all audio to
+ * or from a unix pipe respectively.
+ *
+ * ## Module Options
+ *
+ * - `tunnel.mode`: the desired tunnel to create. (Default `playback`)
+ * - `pipe.filename`: the filename of the pipe.
+ * - `stream.props`: Extra properties for the local stream.
+ *
+ * When `tunnel.mode` is `capture`, a capture stream on the default source is
+ * created. Samples read from the pipe will be the contents of the captured source.
+ *
+ * When `tunnel.mode` is `sink`, a sink node is created. Samples read from the
+ * pipe will be the samples played on the sink.
+ *
+ * When `tunnel.mode` is `playback`, a playback stream on the default sink is
+ * created. Samples written to the pipe will be played on the sink.
+ *
+ * When `tunnel.mode` is `source`, a source node is created. Samples written to
+ * the pipe will be made available to streams connected to the source.
+ *
+ * When `pipe.filename` is not given, a default fifo in `/tmp/fifo_input` or
+ * `/tmp/fifo_output` will be created that can be written and read respectively,
+ * depending on the selected `tunnel.mode`.
+ *
+ * ## 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_NODE_LATENCY
+ * - \ref PW_KEY_NODE_NAME
+ * - \ref PW_KEY_NODE_DESCRIPTION
+ * - \ref PW_KEY_NODE_GROUP
+ * - \ref PW_KEY_NODE_VIRTUAL
+ * - \ref PW_KEY_MEDIA_CLASS
+ * - \ref PW_KEY_TARGET_OBJECT to specify the remote name or serial id to link to
+ *
+ * When not otherwise specified, the pipe will accept or produce a
+ * 16 bits, stereo, 48KHz sample stream.
+ *
+ * ## Example configuration of a pipe playback stream
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-pipe-tunnel
+ * args = {
+ * tunnel.mode = playback
+ * # Set the pipe name to tunnel to
+ * pipe.filename = "/tmp/fifo_output"
+ * #audio.format=<sample format>
+ * #audio.rate=<sample rate>
+ * #audio.channels=<number of channels>
+ * #audio.position=<channel map>
+ * #target.object=<remote target node>
+ * stream.props = {
+ * # extra sink properties
+ * }
+ * }
+ * }
+ * ]
+ *\endcode
+ */
+
+#define NAME "pipe-tunnel"
+
+#define DEFAULT_CAPTURE_FILENAME "/tmp/fifo_input"
+#define DEFAULT_PLAYBACK_FILENAME "/tmp/fifo_output"
+
+#define DEFAULT_FORMAT "S16"
+#define DEFAULT_RATE 48000
+#define DEFAULT_CHANNELS 2
+#define DEFAULT_POSITION "[ FL FR ]"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define MODULE_USAGE "[ remote.name=<remote> ] " \
+ "[ node.latency=<latency as fraction> ] " \
+ "[ node.name=<name of the nodes> ] " \
+ "[ node.description=<description of the nodes> ] " \
+ "[ target.object=<remote node target name or serial> ] "\
+ "[ audio.format=<sample format> ] " \
+ "[ audio.rate=<sample rate> ] " \
+ "[ audio.channels=<number of channels> ] " \
+ "[ audio.position=<channel map> ] " \
+ "[ tunnel.mode=capture|playback|sink|source " \
+ "[ pipe.filename=<filename> ]" \
+ "[ stream.props=<properties> ] "
+
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Create a UNIX pipe tunnel" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct impl {
+ struct pw_context *context;
+
+#define MODE_PLAYBACK 0
+#define MODE_CAPTURE 1
+#define MODE_SINK 2
+#define MODE_SOURCE 3
+ uint32_t mode;
+ struct pw_properties *props;
+
+ struct pw_impl_module *module;
+
+ struct spa_hook module_listener;
+
+ struct pw_core *core;
+ struct spa_hook core_proxy_listener;
+ struct spa_hook core_listener;
+
+ char *filename;
+ unsigned int unlink_fifo;
+ int fd;
+
+ struct pw_properties *stream_props;
+ enum pw_direction direction;
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+ struct spa_audio_info_raw info;
+ uint32_t frame_size;
+
+ unsigned int do_disconnect:1;
+ uint32_t leftover_count;
+ uint8_t *leftover;
+};
+
+static void stream_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->stream_listener);
+ impl->stream = NULL;
+}
+
+static void stream_state_changed(void *d, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct impl *impl = d;
+ switch (state) {
+ case PW_STREAM_STATE_ERROR:
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_impl_module_schedule_destroy(impl->module);
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ break;
+ default:
+ break;
+ }
+}
+
+static void playback_stream_process(void *data)
+{
+ struct impl *impl = data;
+ struct pw_buffer *buf;
+ uint32_t i, size, offs;
+ ssize_t written;
+
+ if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) {
+ pw_log_debug("out of buffers: %m");
+ return;
+ }
+
+ for (i = 0; i < buf->buffer->n_datas; i++) {
+ struct spa_data *d;
+ d = &buf->buffer->datas[i];
+
+ offs = SPA_MIN(d->chunk->offset, d->maxsize);
+ size = SPA_MIN(d->chunk->size, d->maxsize - offs);
+
+ while (size > 0) {
+ written = write(impl->fd, SPA_MEMBER(d->data, offs, void), size);
+ if (written < 0) {
+ if (errno == EINTR) {
+ /* retry if interrupted */
+ continue;
+ } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ /* Don't continue writing */
+ break;
+ } else {
+ pw_log_warn("Failed to write to pipe sink");
+ }
+ }
+ offs += written;
+ size -= written;
+ }
+ }
+ pw_stream_queue_buffer(impl->stream, buf);
+}
+
+static void capture_stream_process(void *data)
+{
+ struct impl *impl = data;
+ struct pw_buffer *buf;
+ struct spa_data *d;
+ uint32_t req;
+ ssize_t nread;
+
+ if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) {
+ pw_log_debug("out of buffers: %m");
+ return;
+ }
+
+ d = &buf->buffer->datas[0];
+
+ if ((req = buf->requested * impl->frame_size) == 0)
+ req = 4096 * impl->frame_size;
+
+ req = SPA_MIN(req, d->maxsize);
+
+ d->chunk->offset = 0;
+ d->chunk->stride = impl->frame_size;
+ d->chunk->size = SPA_MIN(req, impl->leftover_count);
+ memcpy(d->data, impl->leftover, d->chunk->size);
+ req -= d->chunk->size;
+
+ nread = read(impl->fd, SPA_PTROFF(d->data, d->chunk->size, void), req);
+ if (nread < 0) {
+ const bool important = !(errno == EINTR
+ || errno == EAGAIN
+ || errno == EWOULDBLOCK);
+
+ if (important)
+ pw_log_warn("failed to read from pipe (%s): %s",
+ impl->filename, strerror(errno));
+ }
+ else {
+ d->chunk->size += nread;
+ }
+
+ impl->leftover_count = d->chunk->size % impl->frame_size;
+ d->chunk->size -= impl->leftover_count;
+ memcpy(impl->leftover, SPA_PTROFF(d->data, d->chunk->size, void), impl->leftover_count);
+
+ pw_stream_queue_buffer(impl->stream, buf);
+}
+
+static const struct pw_stream_events playback_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = stream_destroy,
+ .state_changed = stream_state_changed,
+ .process = playback_stream_process
+};
+
+static const struct pw_stream_events capture_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = stream_destroy,
+ .state_changed = stream_state_changed,
+ .process = capture_stream_process
+};
+
+static int create_stream(struct impl *impl)
+{
+ int res;
+ uint32_t n_params;
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b;
+
+ impl->stream = pw_stream_new(impl->core, "pipe", impl->stream_props);
+ impl->stream_props = NULL;
+
+ if (impl->stream == NULL)
+ return -errno;
+
+ if (impl->direction == PW_DIRECTION_OUTPUT) {
+ pw_stream_add_listener(impl->stream,
+ &impl->stream_listener,
+ &capture_stream_events, impl);
+ } else {
+ pw_stream_add_listener(impl->stream,
+ &impl->stream_listener,
+ &playback_stream_events, impl);
+ }
+
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_format_audio_raw_build(&b,
+ SPA_PARAM_EnumFormat, &impl->info);
+
+ if ((res = pw_stream_connect(impl->stream,
+ impl->direction,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0)
+ return res;
+
+ return 0;
+}
+
+static int create_fifo(struct impl *impl)
+{
+ struct stat st;
+ const char *filename;
+ bool do_unlink_fifo = false;
+ int fd = -1, res;
+
+ if ((filename = pw_properties_get(impl->props, "pipe.filename")) == NULL)
+ filename = impl->direction == PW_DIRECTION_INPUT ?
+ DEFAULT_CAPTURE_FILENAME :
+ DEFAULT_PLAYBACK_FILENAME;
+
+ if (mkfifo(filename, 0666) < 0) {
+ if (errno != EEXIST) {
+ res = -errno;
+ pw_log_error("mkfifo('%s'): %s", filename, spa_strerror(res));
+ goto error;
+ }
+ } else {
+ /*
+ * Our umask is 077, so the pipe won't be created with the
+ * requested permissions. Let's fix the permissions with chmod().
+ */
+ if (chmod(filename, 0666) < 0)
+ pw_log_warn("chmod('%s'): %s", filename, spa_strerror(-errno));
+
+ do_unlink_fifo = true;
+ }
+
+ if ((fd = open(filename, O_RDWR | O_CLOEXEC | O_NONBLOCK, 0)) < 0) {
+ res = -errno;
+ pw_log_error("open('%s'): %s", filename, spa_strerror(res));
+ goto error;
+ }
+
+ if (fstat(fd, &st) < 0) {
+ res = -errno;
+ pw_log_error("fstat('%s'): %s", filename, spa_strerror(res));
+ goto error;
+ }
+
+ if (!S_ISFIFO(st.st_mode)) {
+ res = -EINVAL;
+ pw_log_error("'%s' is not a FIFO.", filename);
+ goto error;
+ }
+ pw_log_info("%s fifo '%s' with format:%s channels:%d rate:%d",
+ impl->direction == PW_DIRECTION_OUTPUT ? "reading from" : "writing to",
+ filename,
+ spa_debug_type_find_name(spa_type_audio_format, impl->info.format),
+ impl->info.channels, impl->info.rate);
+
+ impl->filename = strdup(filename);
+ impl->unlink_fifo = do_unlink_fifo;
+ impl->fd = fd;
+ return 0;
+
+error:
+ if (do_unlink_fifo)
+ unlink(filename);
+ if (fd >= 0)
+ close(fd);
+ return res;
+}
+
+static void core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct impl *impl = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = core_error,
+};
+
+static void core_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->core_listener);
+ impl->core = NULL;
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ .destroy = core_destroy,
+};
+
+static void impl_destroy(struct impl *impl)
+{
+ if (impl->stream)
+ pw_stream_destroy(impl->stream);
+ if (impl->core && impl->do_disconnect)
+ pw_core_disconnect(impl->core);
+
+ if (impl->filename) {
+ if (impl->unlink_fifo)
+ unlink(impl->filename);
+ free(impl->filename);
+ }
+ if (impl->fd >= 0)
+ close(impl->fd);
+
+ pw_properties_free(impl->stream_props);
+ pw_properties_free(impl->props);
+
+ free(impl->leftover);
+ free(impl);
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->module_listener);
+ impl_destroy(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_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));
+}
+
+static int calc_frame_size(const struct spa_audio_info_raw *info)
+{
+ int res = info->channels;
+ switch (info->format) {
+ case SPA_AUDIO_FORMAT_U8:
+ case SPA_AUDIO_FORMAT_S8:
+ case SPA_AUDIO_FORMAT_ALAW:
+ case SPA_AUDIO_FORMAT_ULAW:
+ return res;
+ case SPA_AUDIO_FORMAT_S16:
+ case SPA_AUDIO_FORMAT_S16_OE:
+ case SPA_AUDIO_FORMAT_U16:
+ return res * 2;
+ case SPA_AUDIO_FORMAT_S24:
+ case SPA_AUDIO_FORMAT_S24_OE:
+ case SPA_AUDIO_FORMAT_U24:
+ return res * 3;
+ case SPA_AUDIO_FORMAT_S24_32:
+ case SPA_AUDIO_FORMAT_S24_32_OE:
+ case SPA_AUDIO_FORMAT_S32:
+ case SPA_AUDIO_FORMAT_S32_OE:
+ case SPA_AUDIO_FORMAT_U32:
+ case SPA_AUDIO_FORMAT_U32_OE:
+ case SPA_AUDIO_FORMAT_F32:
+ case SPA_AUDIO_FORMAT_F32_OE:
+ return res * 4;
+ case SPA_AUDIO_FORMAT_F64:
+ case SPA_AUDIO_FORMAT_F64_OE:
+ return res * 8;
+ default:
+ return 0;
+ }
+}
+
+static void copy_props(struct impl *impl, struct pw_properties *props, const char *key)
+{
+ const char *str;
+ if ((str = pw_properties_get(props, key)) != NULL) {
+ if (pw_properties_get(impl->stream_props, key) == NULL)
+ pw_properties_set(impl->stream_props, key, str);
+ }
+}
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props = NULL;
+ struct impl *impl;
+ const char *str, *media_class = NULL;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ impl->fd = -1;
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args == NULL)
+ args = "";
+
+ props = pw_properties_new_string(args);
+ if (props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+ impl->props = props;
+
+ impl->stream_props = pw_properties_new(NULL, NULL);
+ if (impl->stream_props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+
+ impl->module = module;
+ impl->context = context;
+
+ if ((str = pw_properties_get(props, "tunnel.mode")) == NULL)
+ str = "playback";
+
+ if (spa_streq(str, "capture")) {
+ impl->mode = MODE_CAPTURE;
+ impl->direction = PW_DIRECTION_INPUT;
+ } else if (spa_streq(str, "playback")) {
+ impl->mode = MODE_PLAYBACK;
+ impl->direction = PW_DIRECTION_OUTPUT;
+ }else if (spa_streq(str, "sink")) {
+ impl->mode = MODE_SINK;
+ impl->direction = PW_DIRECTION_INPUT;
+ media_class = "Audio/Sink";
+ } else if (spa_streq(str, "source")) {
+ impl->mode = MODE_SOURCE;
+ impl->direction = PW_DIRECTION_OUTPUT;
+ media_class = "Audio/Source";
+ } else {
+ pw_log_error("invalid tunnel.mode '%s'", str);
+ res = -EINVAL;
+ goto error;
+ }
+
+ if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
+ if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL)
+ pw_properties_set(props, PW_KEY_MEDIA_CLASS, media_class);
+
+ if ((str = pw_properties_get(props, "stream.props")) != NULL)
+ pw_properties_update_string(impl->stream_props, str, strlen(str));
+
+ 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);
+ copy_props(impl, props, PW_KEY_NODE_GROUP);
+ copy_props(impl, props, PW_KEY_NODE_LATENCY);
+ copy_props(impl, props, PW_KEY_NODE_VIRTUAL);
+ copy_props(impl, props, PW_KEY_MEDIA_CLASS);
+ copy_props(impl, props, PW_KEY_TARGET_OBJECT);
+ copy_props(impl, props, "pipe.filename");
+
+ parse_audio_info(impl->stream_props, &impl->info);
+
+ impl->frame_size = calc_frame_size(&impl->info);
+ if (impl->frame_size == 0) {
+ pw_log_error("unsupported audio format:%d channels:%d",
+ impl->info.format, impl->info.channels);
+ res = -EINVAL;
+ goto error;
+ }
+ if (impl->info.rate != 0 &&
+ pw_properties_get(props, PW_KEY_NODE_RATE) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_RATE,
+ "1/%u", impl->info.rate),
+
+ copy_props(impl, props, PW_KEY_NODE_RATE);
+
+ impl->leftover = calloc(1, impl->frame_size);
+ if (impl->leftover == NULL) {
+ res = -errno;
+ pw_log_error("can't alloc leftover buffer: %m");
+ goto error;
+ }
+
+ 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);
+ impl->core = pw_context_connect(impl->context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ impl->do_disconnect = true;
+ }
+ if (impl->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto error;
+ }
+
+ pw_proxy_add_listener((struct pw_proxy*)impl->core,
+ &impl->core_proxy_listener,
+ &core_proxy_events, impl);
+ pw_core_add_listener(impl->core,
+ &impl->core_listener,
+ &core_events, impl);
+
+ if ((res = create_fifo(impl)) < 0)
+ goto error;
+
+ if ((res = create_stream(impl)) < 0)
+ goto error;
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+
+error:
+ impl_destroy(impl);
+ return res;
+}
diff --git a/src/modules/module-portal.c b/src/modules/module-portal.c
new file mode 100644
index 0000000..ee5aa73
--- /dev/null
+++ b/src/modules/module-portal.c
@@ -0,0 +1,348 @@
+/* PipeWire
+ *
+ * Copyright © 2016 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright © 2019 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#include <dbus/dbus.h>
+
+#include <spa/utils/string.h>
+#include <spa/utils/result.h>
+#include <spa/support/dbus.h>
+
+#include "pipewire/context.h"
+#include "pipewire/impl-client.h"
+#include "pipewire/log.h"
+#include "pipewire/module.h"
+#include "pipewire/utils.h"
+
+/** \page page_module_portal PipeWire Module: Portal
+ *
+ * The `portal` module performs access control management for clients started
+ * inside an XDG portal.
+ *
+ * The module connects to the session DBus and subscribes to
+ * `NameOwnerChanged` signals for the `org.freedesktop.portal.Desktop` name.
+ * The PID of the DBus name owner is the portal.
+ *
+ * A client connection from the portal PID to PipeWire gets assigned a \ref
+ * PW_KEY_ACCESS of `"portal"` and set to permissions ALL - it is the
+ * responsibility of the portal to limit the permissions before passing the
+ * connection on to the client. See \ref page_access for details on
+ * permissions.
+ *
+ * Clients connecting from other PIDs are ignored by this module.
+ *
+ * ## Module Options
+ *
+ * There are no module-specific options.
+ *
+ * ## General options
+ *
+ * There are no general options for this module.
+ *
+ * ## Example configuration
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-portal }
+ * ]
+ *\endcode
+ *
+ */
+
+#define NAME "portal"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct impl {
+ struct pw_context *context;
+ struct pw_properties *properties;
+
+ struct spa_dbus_connection *conn;
+ DBusConnection *bus;
+
+ struct spa_hook context_listener;
+ struct spa_hook module_listener;
+
+ DBusPendingCall *portal_pid_pending;
+ pid_t portal_pid;
+};
+
+static void
+context_check_access(void *data, struct pw_impl_client *client)
+{
+ struct impl *impl = data;
+ const struct pw_properties *props;
+ struct pw_permission permissions[1];
+ struct spa_dict_item items[1];
+ pid_t pid;
+
+ if (impl->portal_pid == 0)
+ return;
+
+ if ((props = pw_impl_client_get_properties(client)) == NULL)
+ return;
+
+ if (pw_properties_fetch_int32(props, PW_KEY_SEC_PID, &pid) < 0)
+ return;
+
+ if (pid != impl->portal_pid)
+ return;
+
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_ACCESS, "portal");
+ pw_impl_client_update_properties(client, &SPA_DICT_INIT(items, 1));
+
+ pw_log_info("%p: portal managed client %p added", impl, client);
+
+ /* portal makes this connection and will change the permissions before
+ * handing this connection to the client */
+ permissions[0] = PW_PERMISSION_INIT(PW_ID_ANY, PW_PERM_ALL);
+ pw_impl_client_update_permissions(client, 1, permissions);
+ return;
+}
+
+static const struct pw_context_events context_events = {
+ PW_VERSION_CONTEXT_EVENTS,
+ .check_access = context_check_access,
+};
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+
+ spa_hook_remove(&impl->context_listener);
+ spa_hook_remove(&impl->module_listener);
+
+ if (impl->bus)
+ dbus_connection_unref(impl->bus);
+ spa_dbus_connection_destroy(impl->conn);
+
+ pw_properties_free(impl->properties);
+
+ free(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+static void on_portal_pid_received(DBusPendingCall *pending,
+ void *user_data)
+{
+ struct impl *impl = user_data;
+ DBusMessage *m;
+ DBusError error;
+ uint32_t portal_pid = 0;
+
+ m = dbus_pending_call_steal_reply(pending);
+ dbus_pending_call_unref(pending);
+ impl->portal_pid_pending = NULL;
+
+ if (!m) {
+ pw_log_error("Failed to receive portal pid");
+ return;
+ }
+ if (dbus_message_is_error(m, DBUS_ERROR_NAME_HAS_NO_OWNER)) {
+ pw_log_info("Portal is not running");
+ return;
+ }
+ if (dbus_message_get_type(m) == DBUS_MESSAGE_TYPE_ERROR) {
+ const char *message = "unknown";
+ dbus_message_get_args(m, NULL, DBUS_TYPE_STRING, &message, DBUS_TYPE_INVALID);
+ pw_log_warn("Failed to receive portal pid: %s: %s",
+ dbus_message_get_error_name(m), message);
+ return;
+ }
+
+ dbus_error_init(&error);
+ dbus_message_get_args(m, &error, DBUS_TYPE_UINT32, &portal_pid,
+ DBUS_TYPE_INVALID);
+ dbus_message_unref(m);
+
+ if (dbus_error_is_set(&error)) {
+ impl->portal_pid = 0;
+ pw_log_warn("Could not get portal pid: %s", error.message);
+ dbus_error_free(&error);
+ } else {
+ pw_log_info("Got portal pid %d", portal_pid);
+ impl->portal_pid = portal_pid;
+ }
+}
+
+static void update_portal_pid(struct impl *impl)
+{
+ DBusMessage *m;
+ const char *name;
+ DBusPendingCall *pending;
+
+ impl->portal_pid = 0;
+
+ m = dbus_message_new_method_call("org.freedesktop.DBus",
+ "/org/freedesktop/DBus",
+ "org.freedesktop.DBus",
+ "GetConnectionUnixProcessID");
+
+ name = "org.freedesktop.portal.Desktop";
+ dbus_message_append_args(m,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send_with_reply(impl->bus, m, &pending, -1);
+ dbus_pending_call_set_notify(pending, on_portal_pid_received, impl, NULL);
+ if (impl->portal_pid_pending != NULL) {
+ dbus_pending_call_cancel(impl->portal_pid_pending);
+ dbus_pending_call_unref(impl->portal_pid_pending);
+ }
+ impl->portal_pid_pending = pending;
+}
+
+static DBusHandlerResult name_owner_changed_handler(DBusConnection *connection,
+ DBusMessage *message,
+ void *user_data)
+{
+ struct impl *impl = user_data;
+ const char *name;
+ const char *old_owner;
+ const char *new_owner;
+
+ if (!dbus_message_is_signal(message, "org.freedesktop.DBus",
+ "NameOwnerChanged"))
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ if (!dbus_message_get_args(message, NULL,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &old_owner,
+ DBUS_TYPE_STRING, &new_owner,
+ DBUS_TYPE_INVALID)) {
+ pw_log_error("Failed to get OwnerChanged args");
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+ }
+
+ if (!spa_streq(name, "org.freedesktop.portal.Desktop"))
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ if (spa_streq(new_owner, "")) {
+ impl->portal_pid = 0;
+ if (impl->portal_pid_pending != NULL) {
+ dbus_pending_call_cancel(impl->portal_pid_pending);
+ dbus_pending_call_unref(impl->portal_pid_pending);
+ }
+ }
+ else {
+ update_portal_pid(impl);
+ }
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+}
+
+static int init_dbus_connection(struct impl *impl)
+{
+ DBusError error;
+
+ impl->bus = spa_dbus_connection_get(impl->conn);
+ if (impl->bus == NULL)
+ return -EIO;
+
+ /* XXX: we don't handle dbus reconnection yet, so ref the handle instead */
+ dbus_connection_ref(impl->bus);
+
+ dbus_error_init(&error);
+
+ dbus_bus_add_match(impl->bus,
+ "type='signal',\
+ sender='org.freedesktop.DBus',\
+ interface='org.freedesktop.DBus',\
+ member='NameOwnerChanged'",
+ &error);
+ if (dbus_error_is_set(&error)) {
+ pw_log_error("Failed to add name owner changed listener: %s",
+ error.message);
+ dbus_error_free(&error);
+ return -EIO;
+ }
+
+ dbus_connection_add_filter(impl->bus, name_owner_changed_handler,
+ impl, NULL);
+
+ update_portal_pid(impl);
+
+ return 0;
+}
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct impl *impl;
+ struct spa_dbus *dbus;
+ const struct spa_support *support;
+ uint32_t n_support;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ support = pw_context_get_support(context, &n_support);
+
+ dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus);
+ if (dbus == NULL)
+ return -ENOTSUP;
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -ENOMEM;
+
+ pw_log_debug("module %p: new", impl);
+
+ impl->context = context;
+ impl->properties = args ? pw_properties_new_string(args) : NULL;
+
+ impl->conn = spa_dbus_get_connection(dbus, SPA_DBUS_TYPE_SESSION);
+ if (impl->conn == NULL) {
+ res = -errno;
+ goto error;
+ }
+
+ if ((res = init_dbus_connection(impl)) < 0)
+ goto error;
+
+ pw_context_add_listener(context, &impl->context_listener, &context_events, impl);
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ return 0;
+
+ error:
+ free(impl);
+ pw_log_error("Failed to connect to session bus: %s", spa_strerror(res));
+ return res;
+}
diff --git a/src/modules/module-profiler.c b/src/modules/module-profiler.c
new file mode 100644
index 0000000..e519e08
--- /dev/null
+++ b/src/modules/module-profiler.c
@@ -0,0 +1,463 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+#include <spa/utils/ringbuffer.h>
+#include <spa/param/profiler.h>
+
+#include <pipewire/private.h>
+#include <pipewire/impl.h>
+#include <pipewire/extensions/profiler.h>
+
+/** \page page_module_profiler PipeWire Module: Profiler
+ *
+ * The profiler module provides a Profiler interface for applications that
+ * can be used to receive profiling information.
+ *
+ * Use tools like pw-top and pw-profiler to collect profiling information
+ * about the pipewire graph.
+ *
+ * ## Example configuration
+ *
+ * The module has no arguments and is usually added to the config file of
+ * the main pipewire daemon.
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-profiler }
+ * ]
+ *\endcode
+ *
+ * ## See also
+ *
+ * - `pw-top`: a tool to display realtime profiler data
+ * - `pw-profiler`: a tool to collect and render profiler data
+ */
+
+#define NAME "profiler"
+
+PW_LOG_TOPIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define TMP_BUFFER (16 * 1024)
+#define MAX_BUFFER (8 * 1024 * 1024)
+#define MIN_FLUSH (16 * 1024)
+#define DEFAULT_IDLE 5
+#define DEFAULT_INTERVAL 1
+
+int pw_protocol_native_ext_profiler_init(struct pw_context *context);
+
+#define pw_profiler_resource(r,m,v,...) \
+ pw_resource_call(r,struct pw_profiler_events,m,v,__VA_ARGS__)
+
+#define pw_profiler_resource_profile(r,...) \
+ pw_profiler_resource(r,profile,0,__VA_ARGS__)
+
+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_VERSION, PACKAGE_VERSION },
+};
+
+struct impl {
+ struct pw_context *context;
+ struct pw_properties *properties;
+
+ struct spa_hook context_listener;
+ struct spa_hook module_listener;
+
+ struct pw_global *global;
+ struct spa_hook global_listener;
+
+ int64_t count;
+ uint32_t busy;
+ uint32_t empty;
+ struct spa_source *flush_timeout;
+ unsigned int flushing:1;
+ unsigned int listening:1;
+
+ struct spa_ringbuffer buffer;
+ uint8_t tmp[TMP_BUFFER];
+ uint8_t data[MAX_BUFFER];
+
+ uint8_t flush[MAX_BUFFER + sizeof(struct spa_pod_struct)];
+};
+
+struct resource_data {
+ struct impl *impl;
+
+ struct pw_resource *resource;
+ struct spa_hook resource_listener;
+};
+
+static void start_flush(struct impl *impl)
+{
+ struct timespec value, interval;
+
+ value.tv_sec = 0;
+ value.tv_nsec = 1;
+ interval.tv_sec = DEFAULT_INTERVAL;
+ interval.tv_nsec = 0;
+ pw_loop_update_timer(impl->context->main_loop,
+ impl->flush_timeout, &value, &interval, false);
+ impl->flushing = true;
+}
+
+static void stop_flush(struct impl *impl)
+{
+ struct timespec value, interval;
+
+ if (!impl->flushing)
+ return;
+
+ value.tv_sec = 0;
+ value.tv_nsec = 0;
+ interval.tv_sec = 0;
+ interval.tv_nsec = 0;
+ pw_loop_update_timer(impl->context->main_loop,
+ impl->flush_timeout, &value, &interval, false);
+ impl->flushing = false;
+}
+
+static void flush_timeout(void *data, uint64_t expirations)
+{
+ struct impl *impl = data;
+ int32_t avail;
+ uint32_t idx;
+ struct spa_pod_struct *p;
+ struct pw_resource *resource;
+
+ avail = spa_ringbuffer_get_read_index(&impl->buffer, &idx);
+
+ pw_log_trace("%p avail %d", impl, avail);
+
+ if (avail <= 0) {
+ if (++impl->empty == DEFAULT_IDLE)
+ stop_flush(impl);
+ return;
+ }
+ impl->empty = 0;
+
+ p = (struct spa_pod_struct *)impl->flush;
+ *p = SPA_POD_INIT_Struct(avail);
+
+ spa_ringbuffer_read_data(&impl->buffer, impl->data, MAX_BUFFER,
+ idx % MAX_BUFFER,
+ SPA_PTROFF(p, sizeof(struct spa_pod_struct), void), avail);
+ spa_ringbuffer_read_update(&impl->buffer, idx + avail);
+
+ spa_list_for_each(resource, &impl->global->resource_list, link)
+ pw_profiler_resource_profile(resource, &p->pod);
+}
+
+static void context_do_profile(void *data, struct pw_impl_node *node)
+{
+ struct impl *impl = data;
+ struct spa_pod_builder b;
+ struct spa_pod_frame f[2];
+ struct pw_node_activation *a = node->rt.activation;
+ struct spa_io_position *pos = &a->position;
+ struct pw_node_target *t;
+ int32_t filled;
+ uint32_t idx, avail;
+
+ if (SPA_FLAG_IS_SET(pos->clock.flags, SPA_IO_CLOCK_FLAG_FREEWHEEL))
+ return;
+
+ spa_pod_builder_init(&b, impl->tmp, sizeof(impl->tmp));
+ spa_pod_builder_push_object(&b, &f[0],
+ SPA_TYPE_OBJECT_Profiler, 0);
+
+ spa_pod_builder_prop(&b, SPA_PROFILER_info, 0);
+ spa_pod_builder_add_struct(&b,
+ SPA_POD_Long(impl->count),
+ SPA_POD_Float(a->cpu_load[0]),
+ SPA_POD_Float(a->cpu_load[1]),
+ SPA_POD_Float(a->cpu_load[2]),
+ SPA_POD_Int(a->xrun_count));
+
+ spa_pod_builder_prop(&b, SPA_PROFILER_clock, 0);
+ spa_pod_builder_add_struct(&b,
+ SPA_POD_Int(pos->clock.flags),
+ 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_builder_prop(&b, SPA_PROFILER_driverBlock, 0);
+ spa_pod_builder_add_struct(&b,
+ SPA_POD_Int(node->info.id),
+ SPA_POD_String(node->name),
+ SPA_POD_Long(a->prev_signal_time),
+ SPA_POD_Long(a->signal_time),
+ SPA_POD_Long(a->awake_time),
+ SPA_POD_Long(a->finish_time),
+ SPA_POD_Int(a->status),
+ SPA_POD_Fraction(&node->latency));
+
+ spa_list_for_each(t, &node->rt.target_list, link) {
+ struct pw_impl_node *n = t->node;
+ struct pw_node_activation *na;
+ struct spa_fraction latency;
+
+ if (n == NULL || n == node)
+ continue;
+
+ latency = n->latency;
+ if (n->force_quantum != 0)
+ latency.num = n->force_quantum;
+ if (n->force_rate != 0)
+ latency.denom = n->force_rate;
+ else if (n->rate.denom != 0)
+ latency.denom = n->rate.denom;
+
+ na = n->rt.activation;
+ spa_pod_builder_prop(&b, SPA_PROFILER_followerBlock, 0);
+ spa_pod_builder_add_struct(&b,
+ SPA_POD_Int(n->info.id),
+ SPA_POD_String(n->name),
+ SPA_POD_Long(a->signal_time),
+ SPA_POD_Long(na->signal_time),
+ SPA_POD_Long(na->awake_time),
+ SPA_POD_Long(na->finish_time),
+ SPA_POD_Int(na->status),
+ SPA_POD_Fraction(&latency));
+ }
+ spa_pod_builder_pop(&b, &f[0]);
+
+ if (b.state.offset > sizeof(impl->tmp))
+ goto done;
+
+ filled = spa_ringbuffer_get_write_index(&impl->buffer, &idx);
+ if (filled < 0 || filled > MAX_BUFFER) {
+ pw_log_warn("%p: queue xrun %d", impl, filled);
+ goto done;
+ }
+ avail = MAX_BUFFER - filled;
+ if (avail < b.state.offset) {
+ pw_log_warn("%p: queue full %d < %d", impl, avail, b.state.offset);
+ goto done;
+ }
+ spa_ringbuffer_write_data(&impl->buffer,
+ impl->data, MAX_BUFFER,
+ idx % MAX_BUFFER,
+ b.data, b.state.offset);
+ spa_ringbuffer_write_update(&impl->buffer, idx + b.state.offset);
+
+ if (!impl->flushing || filled + b.state.offset > MIN_FLUSH)
+ start_flush(impl);
+done:
+ impl->count++;
+}
+
+static const struct pw_context_driver_events context_events = {
+ PW_VERSION_CONTEXT_DRIVER_EVENTS,
+ .incomplete = context_do_profile,
+ .complete = context_do_profile,
+};
+
+static int do_stop(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct impl *impl = user_data;
+ spa_hook_remove(&impl->context_listener);
+ return 0;
+}
+
+static void stop_listener(struct impl *impl)
+{
+ if (impl->listening) {
+ pw_loop_invoke(impl->context->data_loop,
+ do_stop, SPA_ID_INVALID, NULL, 0, true, impl);
+ impl->listening = false;
+ }
+}
+
+static void resource_destroy(void *data)
+{
+ struct impl *impl = data;
+ if (--impl->busy == 0) {
+ pw_log_info("%p: stopping profiler", impl);
+ stop_listener(impl);
+ }
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = resource_destroy,
+};
+
+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;
+ spa_hook_list_append(&impl->context->driver_listener_list,
+ &impl->context_listener,
+ &context_events, impl);
+ return 0;
+}
+static int
+global_bind(void *object, struct pw_impl_client *client, uint32_t permissions,
+ uint32_t version, uint32_t id)
+{
+ struct impl *impl = object;
+ struct pw_global *global = impl->global;
+ struct pw_resource *resource;
+ struct resource_data *data;
+
+ resource = pw_resource_new(client, id, permissions,
+ PW_TYPE_INTERFACE_Profiler, version, sizeof(*data));
+ if (resource == NULL)
+ return -errno;
+
+ data = pw_resource_get_user_data(resource);
+ data->impl = impl;
+ data->resource = resource;
+ pw_global_add_resource(global, resource);
+
+ pw_resource_add_listener(resource, &data->resource_listener,
+ &resource_events, impl);
+
+ if (++impl->busy == 1) {
+ pw_log_info("%p: starting profiler", impl);
+ pw_loop_invoke(impl->context->data_loop,
+ do_start, SPA_ID_INVALID, NULL, 0, false, impl);
+ impl->listening = true;
+ }
+ return 0;
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+
+ if (impl->global != NULL)
+ pw_global_destroy(impl->global);
+
+ spa_hook_remove(&impl->module_listener);
+
+ pw_properties_free(impl->properties);
+
+ pw_loop_destroy_source(pw_context_get_main_loop(impl->context), impl->flush_timeout);
+
+ free(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+static void global_destroy(void *data)
+{
+ struct impl *impl = data;
+
+ stop_listener(impl);
+ stop_flush(impl);
+
+ spa_hook_remove(&impl->global_listener);
+ impl->global = NULL;
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props;
+ struct impl *impl;
+ struct pw_loop *main_loop = pw_context_get_main_loop(context);
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ NULL
+ };
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ pw_protocol_native_ext_profiler_init(context);
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args)
+ props = pw_properties_new_string(args);
+ else
+ props = pw_properties_new(NULL, NULL);
+
+ impl->context = context;
+ impl->properties = props;
+
+ spa_ringbuffer_init(&impl->buffer);
+
+ impl->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_Profiler,
+ PW_VERSION_PROFILER,
+ pw_properties_copy(props),
+ global_bind, impl);
+ if (impl->global == NULL) {
+ free(impl);
+ return -errno;
+ }
+ pw_properties_setf(impl->properties, PW_KEY_OBJECT_ID, "%d", impl->global->id);
+ pw_properties_setf(impl->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
+ pw_global_get_serial(impl->global));
+
+ impl->flush_timeout = pw_loop_add_timer(main_loop, flush_timeout, impl);
+
+ pw_global_update_keys(impl->global, &impl->properties->dict, keys);
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ pw_global_register(impl->global);
+
+ pw_global_add_listener(impl->global, &impl->global_listener, &global_events, impl);
+
+ return 0;
+}
diff --git a/src/modules/module-profiler/protocol-native.c b/src/modules/module-profiler/protocol-native.c
new file mode 100644
index 0000000..ac0644c
--- /dev/null
+++ b/src/modules/module-profiler/protocol-native.c
@@ -0,0 +1,128 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+
+#include <spa/pod/builder.h>
+#include <spa/pod/parser.h>
+#include <spa/utils/result.h>
+
+#include <pipewire/pipewire.h>
+
+#include <pipewire/extensions/protocol-native.h>
+#include <pipewire/extensions/profiler.h>
+
+PW_LOG_TOPIC_EXTERN(mod_topic);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+static int profiler_proxy_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_profiler_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static int profiler_demarshal_add_listener(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ return -ENOTSUP;
+}
+
+static void profiler_resource_marshal_profile(void *object, const struct spa_pod *pod)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_PROFILER_EVENT_PROFILE, NULL);
+
+ spa_pod_builder_add_struct(b, SPA_POD_Pod(pod));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int profiler_proxy_demarshal_profile(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ struct spa_pod *pod;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+
+ if (spa_pod_parser_get_struct(&prs, SPA_POD_Pod(&pod)) < 0)
+ return -EINVAL;
+
+ pw_proxy_notify(proxy, struct pw_profiler_events, profile, 0, pod);
+ return 0;
+}
+
+
+static const struct pw_profiler_methods pw_protocol_native_profiler_client_method_marshal = {
+ PW_VERSION_PROFILER_METHODS,
+ .add_listener = &profiler_proxy_marshal_add_listener,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_profiler_server_method_demarshal[PW_PROFILER_METHOD_NUM] =
+{
+ [PW_PROFILER_METHOD_ADD_LISTENER] = { &profiler_demarshal_add_listener, 0 },
+};
+
+static const struct pw_profiler_events pw_protocol_native_profiler_server_event_marshal = {
+ PW_VERSION_PROFILER_EVENTS,
+ .profile = &profiler_resource_marshal_profile,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_profiler_client_event_demarshal[PW_PROFILER_EVENT_NUM] =
+{
+ [PW_PROFILER_EVENT_PROFILE] = { &profiler_proxy_demarshal_profile, 0 },
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_profiler_marshal = {
+ PW_TYPE_INTERFACE_Profiler,
+ PW_VERSION_PROFILER,
+ 0,
+ PW_PROFILER_METHOD_NUM,
+ PW_PROFILER_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_profiler_client_method_marshal,
+ .server_demarshal = pw_protocol_native_profiler_server_method_demarshal,
+ .server_marshal = &pw_protocol_native_profiler_server_event_marshal,
+ .client_demarshal = pw_protocol_native_profiler_client_event_demarshal,
+};
+
+int pw_protocol_native_ext_profiler_init(struct pw_context *context)
+{
+ struct pw_protocol *protocol;
+
+ protocol = pw_context_find_protocol(context, PW_TYPE_INFO_PROTOCOL_Native);
+ if (protocol == NULL)
+ return -EPROTO;
+
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_profiler_marshal);
+ return 0;
+}
diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c
new file mode 100644
index 0000000..6fdc79b
--- /dev/null
+++ b/src/modules/module-protocol-native.c
@@ -0,0 +1,1528 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#include <ctype.h>
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+#if defined(__FreeBSD__) || defined(__MidnightBSD__)
+#include <sys/ucred.h>
+#endif
+
+#include <spa/pod/iter.h>
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/protocol-native.h>
+
+#include "pipewire/private.h"
+
+#include "modules/module-protocol-native/connection.h"
+#include "modules/module-protocol-native/defs.h"
+#include "modules/module-protocol-native/protocol-footer.h"
+
+
+#define NAME "protocol-native"
+PW_LOG_TOPIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+PW_LOG_TOPIC(mod_topic_connection, "conn." NAME);
+
+#undef spa_debug
+#define spa_debug(...) pw_logt_debug(mod_topic_connection, __VA_ARGS__)
+
+#include <spa/debug/pod.h>
+#include <spa/debug/types.h>
+
+/** \page page_module_protocol_native PipeWire Module: Protocol Native
+ *
+ * The native protocol module implements the PipeWire communication between
+ * 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.
+ *
+ * ## Module Options
+ *
+ * The module has no options.
+ *
+ * ## General Options
+ *
+ * The name of the core is obtained as:
+ *
+ * - PIPEWIRE_CORE : the environment variable with the name of the core
+ * - \ref PW_KEY_CORE_NAME : in the context properties
+ * - a name based on the process id
+ *
+ * The context will also become a server if:
+ *
+ * - PIPEWIRE_DAEMON : the environment is true
+ * - \ref PW_KEY_CORE_DAEMON : in the context properties is true
+ *
+ * The socket will be located in the directory obtained by looking at the
+ * following environment variables:
+ *
+ * - PIPEWIRE_RUNTIME_DIR
+ * - XDG_RUNTIME_DIR
+ * - USERPROFILE
+ *
+ * The socket address will be written into the notification file descriptor
+ * if the following environment variable is set:
+ *
+ * - PIPEWIRE_NOTIFICATION_FD
+ *
+ * When a client connect, the connection will be made to:
+ *
+ * - PIPEWIRE_REMOTE : the environment with the remote name
+ * - \ref PW_KEY_REMOTE_NAME : the property in the context.
+ * - The default remote named "pipewire-0"
+ *
+ * A Special remote named "internal" can be used to make a connection to the
+ * 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.
+ *
+ * ## Example configuration
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ { name = libpipewire-module-protocol-native }
+ * ]
+ *\endcode
+ */
+
+#ifndef UNIX_PATH_MAX
+#define UNIX_PATH_MAX 108
+#endif
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Native protocol using unix sockets" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+/* Required for s390x */
+#ifndef SO_PEERSEC
+#define SO_PEERSEC 31
+#endif
+
+static bool debug_messages = 0;
+
+#define LOCK_SUFFIX ".lock"
+#define LOCK_SUFFIXLEN 5
+
+void pw_protocol_native_init(struct pw_protocol *protocol);
+void pw_protocol_native0_init(struct pw_protocol *protocol);
+
+struct protocol_data {
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+ struct pw_protocol *protocol;
+
+ struct server *local;
+};
+
+struct client {
+ struct pw_protocol_client this;
+ struct pw_context *context;
+
+ struct spa_source *source;
+
+ struct pw_protocol_native_connection *connection;
+ struct spa_hook conn_listener;
+
+ int ref;
+
+ struct footer_core_global_state footer_state;
+
+ unsigned int connected:1;
+ unsigned int disconnecting:1;
+ unsigned int need_flush:1;
+ unsigned int paused:1;
+};
+
+static void client_unref(struct client *impl)
+{
+ if (--impl->ref == 0)
+ free(impl);
+}
+
+struct server {
+ struct pw_protocol_server this;
+
+ int fd_lock;
+ struct sockaddr_un addr;
+ char lock_addr[UNIX_PATH_MAX + LOCK_SUFFIXLEN];
+
+ struct pw_loop *loop;
+ struct spa_source *source;
+ struct spa_source *resume;
+ unsigned int activated:1;
+};
+
+struct client_data {
+ struct pw_impl_client *client;
+ struct spa_hook client_listener;
+
+ struct spa_list protocol_link;
+ struct server *server;
+
+ struct spa_source *source;
+ struct pw_protocol_native_connection *connection;
+ struct spa_hook conn_listener;
+
+ struct footer_client_global_state footer_state;
+
+ unsigned int busy:1;
+ unsigned int need_flush:1;
+
+ struct protocol_compat_v2 compat_v2;
+};
+
+static void debug_msg(const char *prefix, const struct pw_protocol_native_message *msg, bool hex)
+{
+ struct spa_pod *pod;
+ pw_logt_debug(mod_topic_connection,
+ "%s: id:%d op:%d size:%d seq:%d", prefix,
+ msg->id, msg->opcode, msg->size, msg->seq);
+
+ if ((pod = get_first_pod_from_data(msg->data, msg->size, 0)) != NULL)
+ spa_debug_pod(0, NULL, pod);
+ else
+ hex = true;
+ if (hex)
+ spa_debug_mem(0, msg->data, msg->size);
+
+ pw_logt_debug(mod_topic_connection, "%s ****", prefix);
+
+}
+
+static void pre_demarshal(struct pw_protocol_native_connection *conn,
+ const struct pw_protocol_native_message *msg,
+ void *object, const struct footer_demarshal *opcodes, size_t n_opcodes)
+{
+ struct spa_pod *footer = NULL;
+ struct spa_pod_parser parser;
+ struct spa_pod_frame f[2];
+ uint32_t opcode;
+ int ret;
+
+ footer = pw_protocol_native_connection_get_footer(conn, msg);
+ if (footer == NULL)
+ return; /* No valid footer. Ignore silently. */
+
+ /*
+ * Version 3 footer
+ *
+ * spa_pod Struct { [Id opcode, Struct { ... }]* }
+ */
+
+ spa_pod_parser_pod(&parser, footer);
+ if (spa_pod_parser_push_struct(&parser, &f[0]) < 0) {
+ pw_log_error("malformed message footer");
+ return;
+ }
+
+ while (1) {
+ if (spa_pod_parser_get_id(&parser, &opcode) < 0)
+ break;
+ if (spa_pod_parser_push_struct(&parser, &f[1]) < 0)
+ break;
+ if (opcode < n_opcodes) {
+ if ((ret = opcodes[opcode].demarshal(object, &parser)) < 0)
+ pw_log_error("failed processing message footer (opcode %u): %d (%s)",
+ opcode, ret, spa_strerror(ret));
+ } else {
+ /* Ignore (don't log errors), in case we need to extend this later. */
+ pw_log_debug("unknown message footer opcode %u", opcode);
+ }
+ spa_pod_parser_pop(&parser, &f[1]);
+ }
+}
+
+static int
+process_messages(struct client_data *data)
+{
+ struct pw_protocol_native_connection *conn = data->connection;
+ struct pw_impl_client *client = data->client;
+ struct pw_context *context = client->context;
+ const struct pw_protocol_native_message *msg;
+ struct pw_resource *resource;
+ int res;
+
+ context->current_client = client;
+
+ /* when the client is busy processing an async action, stop processing messages
+ * for the client until it finishes the action */
+ while (!data->busy) {
+ const struct pw_protocol_native_demarshal *demarshal;
+ const struct pw_protocol_marshal *marshal;
+ uint32_t permissions, required;
+
+ res = pw_protocol_native_connection_get_next(conn, &msg);
+ if (res < 0) {
+ if (res == -EAGAIN)
+ break;
+ goto error;
+ }
+ if (res == 0)
+ break;
+
+ if (client->core_resource == NULL) {
+ res = -EPROTO;
+ goto error;
+ }
+
+ client->recv_seq = msg->seq;
+
+ pw_log_trace("%p: got message %d from %u", client->protocol,
+ msg->opcode, msg->id);
+
+ if (debug_messages)
+ debug_msg("<<<<<< in", msg, false);
+
+ pre_demarshal(conn, msg, client, footer_client_demarshal,
+ SPA_N_ELEMENTS(footer_client_demarshal));
+
+ resource = pw_impl_client_find_resource(client, msg->id);
+ if (resource == NULL) {
+ pw_resource_errorf(client->core_resource,
+ -ENOENT, "unknown resource %u op:%u", msg->id, msg->opcode);
+ continue;
+ }
+
+ marshal = pw_resource_get_marshal(resource);
+ if (marshal == NULL || msg->opcode >= marshal->n_client_methods) {
+ pw_resource_errorf_id(resource, msg->id,
+ -ENOSYS, "invalid method id:%u op:%u",
+ msg->id, msg->opcode);
+ continue;
+ }
+
+ demarshal = marshal->server_demarshal;
+ if (!demarshal[msg->opcode].func) {
+ pw_resource_errorf_id(resource, msg->id,
+ -ENOTSUP, "function not supported id:%u op:%u",
+ msg->id, msg->opcode);
+ continue;
+ }
+
+ permissions = pw_resource_get_permissions(resource);
+ required = demarshal[msg->opcode].permissions | PW_PERM_X;
+
+ if ((required & permissions) != required) {
+ pw_resource_errorf_id(resource, msg->id,
+ -EACCES, "no permission to call method %u on %u "
+ "(requires "PW_PERMISSION_FORMAT", have "PW_PERMISSION_FORMAT")",
+ msg->opcode, msg->id,
+ PW_PERMISSION_ARGS(required), PW_PERMISSION_ARGS(permissions));
+ continue;
+ }
+
+ resource->refcount++;
+ pw_protocol_native_connection_enter(conn);
+ res = demarshal[msg->opcode].func(resource, msg);
+ pw_protocol_native_connection_leave(conn);
+ pw_resource_unref(resource);
+
+ if (res < 0) {
+ pw_resource_errorf_id(resource, msg->id,
+ res, "invalid message id:%u op:%u (%s)",
+ msg->id, msg->opcode, spa_strerror(res));
+ debug_msg("*invalid message*", msg, true);
+ }
+ }
+ res = 0;
+done:
+ context->current_client = NULL;
+
+ return res;
+
+error:
+ pw_resource_errorf(client->core_resource, res, "client error %d (%s)",
+ res, spa_strerror(res));
+ goto done;
+}
+
+static void
+client_busy_changed(void *data, bool busy)
+{
+ struct client_data *c = data;
+ struct server *s = c->server;
+ struct pw_impl_client *client = c->client;
+ uint32_t mask = c->source->mask;
+
+ c->busy = busy;
+
+ SPA_FLAG_UPDATE(mask, SPA_IO_IN, !busy);
+
+ pw_log_debug("%p: busy changed %d", client->protocol, busy);
+ pw_loop_update_io(client->context->main_loop, c->source, mask);
+
+ if (!busy)
+ pw_loop_signal_event(s->loop, s->resume);
+}
+
+static void handle_client_error(struct pw_impl_client *client, int res, const char *msg)
+{
+ if (res == -EPIPE || res == -ECONNRESET)
+ pw_log_info("%p: %s: client %p disconnected", client->protocol, msg, client);
+ else
+ pw_log_error("%p: %s: client %p error %d (%s)", client->protocol, msg,
+ client, res, spa_strerror(res));
+ if (!client->destroyed)
+ pw_impl_client_destroy(client);
+}
+
+static void
+connection_data(void *data, int fd, uint32_t mask)
+{
+ struct client_data *this = data;
+ struct pw_impl_client *client = this->client;
+ int res;
+
+ client->refcount++;
+
+ if (mask & SPA_IO_HUP) {
+ res = -EPIPE;
+ goto error;
+ }
+ if (mask & SPA_IO_ERR) {
+ res = -EIO;
+ goto error;
+ }
+ if (mask & SPA_IO_IN) {
+ if ((res = process_messages(this)) < 0)
+ goto error;
+ }
+ if (mask & SPA_IO_OUT || this->need_flush) {
+ this->need_flush = false;
+ res = pw_protocol_native_connection_flush(this->connection);
+ if (res >= 0) {
+ pw_loop_update_io(client->context->main_loop,
+ this->source, this->source->mask & ~SPA_IO_OUT);
+ } else if (res != -EAGAIN)
+ goto error;
+ }
+done:
+ pw_impl_client_unref(client);
+ return;
+error:
+ handle_client_error(client, res, "connection_data");
+ goto done;
+}
+
+static void client_destroy(void *data)
+{
+ struct client_data *this = data;
+ pw_log_debug("%p: destroy", this);
+ spa_list_remove(&this->protocol_link);
+}
+
+static void client_free(void *data)
+{
+ struct client_data *this = data;
+ struct pw_impl_client *client = this->client;
+
+ pw_log_debug("%p: free", this);
+ spa_hook_remove(&this->client_listener);
+
+ if (this->source)
+ pw_loop_destroy_source(client->context->main_loop, this->source);
+ if (this->connection)
+ pw_protocol_native_connection_destroy(this->connection);
+
+ pw_map_clear(&this->compat_v2.types);
+}
+
+static const struct pw_impl_client_events client_events = {
+ PW_VERSION_IMPL_CLIENT_EVENTS,
+ .destroy = client_destroy,
+ .free = client_free,
+ .busy_changed = client_busy_changed,
+};
+
+static void on_server_connection_destroy(void *data)
+{
+ struct client_data *this = data;
+ spa_hook_remove(&this->conn_listener);
+}
+
+static void on_start(void *data, uint32_t version)
+{
+ struct client_data *this = data;
+ struct pw_impl_client *client = this->client;
+
+ pw_log_debug("version %d", version);
+
+ if (client->core_resource != NULL)
+ pw_resource_remove(client->core_resource);
+
+ if (pw_global_bind(pw_impl_core_get_global(client->core), client,
+ PW_PERM_ALL, version, 0) < 0)
+ return;
+
+ if (version == 0)
+ client->compat_v2 = &this->compat_v2;
+
+ return;
+}
+
+static void on_server_need_flush(void *data)
+{
+ struct client_data *this = data;
+ struct pw_impl_client *client = this->client;
+
+ pw_log_trace("need flush");
+ this->need_flush = true;
+
+ if (this->source && !(this->source->mask & SPA_IO_OUT)) {
+ pw_loop_update_io(client->context->main_loop,
+ this->source, this->source->mask | SPA_IO_OUT);
+ }
+}
+
+static const struct pw_protocol_native_connection_events server_conn_events = {
+ PW_VERSION_PROTOCOL_NATIVE_CONNECTION_EVENTS,
+ .destroy = on_server_connection_destroy,
+ .start = on_start,
+ .need_flush = on_server_need_flush,
+};
+
+static bool check_print(const uint8_t *buffer, int len)
+{
+ int i;
+ while (len > 1 && buffer[len-1] == 0)
+ len--;
+ for (i = 0; i < len; i++)
+ if (!isprint(buffer[i]))
+ return false;
+ return true;
+}
+
+static struct client_data *client_new(struct server *s, int fd)
+{
+ struct client_data *this;
+ struct pw_impl_client *client;
+ struct pw_protocol *protocol = s->this.protocol;
+ socklen_t len;
+#if defined(__FreeBSD__) || defined(__MidnightBSD__)
+ struct xucred xucred;
+#else
+ struct ucred ucred;
+#endif
+ struct pw_context *context = protocol->context;
+ struct pw_properties *props;
+ uint8_t buffer[1024];
+ struct protocol_data *d = pw_protocol_get_user_data(protocol);
+ int i, res;
+
+ props = pw_properties_new(PW_KEY_PROTOCOL, "protocol-native", NULL);
+ if (props == NULL)
+ goto exit;
+
+#if defined(__linux__)
+ len = sizeof(ucred);
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) < 0) {
+ pw_log_warn("server %p: no peercred: %m", s);
+ } else {
+ pw_properties_setf(props, PW_KEY_SEC_PID, "%d", ucred.pid);
+ pw_properties_setf(props, PW_KEY_SEC_UID, "%d", ucred.uid);
+ pw_properties_setf(props, PW_KEY_SEC_GID, "%d", ucred.gid);
+ }
+
+ len = sizeof(buffer);
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buffer, &len) < 0) {
+ if (errno == ENOPROTOOPT)
+ pw_log_info("server %p: security label not available", s);
+ else
+ pw_log_warn("server %p: security label error: %m", s);
+ } else {
+ if (!check_print(buffer, len)) {
+ char *hex, *p;
+ static const char *ch = "0123456789abcdef";
+
+ p = hex = alloca(len * 2 + 10);
+ p += snprintf(p, 5, "hex:");
+ for(i = 0; i < (int)len; i++)
+ p += snprintf(p, 3, "%c%c",
+ ch[buffer[i] >> 4], ch[buffer[i] & 0xf]);
+ pw_properties_set(props, PW_KEY_SEC_LABEL, hex);
+
+ } else {
+ /* buffer is not null terminated, must use length explicitly */
+ pw_properties_setf(props, PW_KEY_SEC_LABEL, "%.*s",
+ (int)len, buffer);
+ }
+ }
+#elif defined(__FreeBSD__) || defined(__MidnightBSD__)
+ len = sizeof(xucred);
+ if (getsockopt(fd, 0, LOCAL_PEERCRED, &xucred, &len) < 0) {
+ pw_log_warn("server %p: no peercred: %m", s);
+ } else {
+#if __FreeBSD__ >= 13
+ pw_properties_setf(props, PW_KEY_SEC_PID, "%d", xucred.cr_pid);
+#endif
+ pw_properties_setf(props, PW_KEY_SEC_UID, "%d", xucred.cr_uid);
+ pw_properties_setf(props, PW_KEY_SEC_GID, "%d", xucred.cr_gid);
+ // this is what Linuxulator does at the moment, see sys/compat/linux/linux_socket.c
+ pw_properties_set(props, PW_KEY_SEC_LABEL, "unconfined");
+ }
+#endif
+
+ pw_properties_setf(props, PW_KEY_MODULE_ID, "%d", d->module->global->id);
+
+ client = pw_context_create_client(s->this.core,
+ protocol, props, sizeof(struct client_data));
+ if (client == NULL)
+ goto exit;
+
+ this = pw_impl_client_get_user_data(client);
+ spa_list_append(&s->this.client_list, &this->protocol_link);
+
+ this->server = s;
+ this->client = client;
+ pw_map_init(&this->compat_v2.types, 0, 32);
+
+ pw_impl_client_add_listener(client, &this->client_listener, &client_events, this);
+
+ this->source = pw_loop_add_io(pw_context_get_main_loop(context),
+ fd, SPA_IO_ERR | SPA_IO_HUP, true,
+ connection_data, this);
+ if (this->source == NULL) {
+ res = -errno;
+ goto cleanup_client;
+ }
+
+ this->connection = pw_protocol_native_connection_new(protocol->context, fd);
+ if (this->connection == NULL) {
+ res = -errno;
+ goto cleanup_client;
+ }
+
+ pw_protocol_native_connection_add_listener(this->connection,
+ &this->conn_listener,
+ &server_conn_events,
+ this);
+
+ if ((res = pw_impl_client_register(client, NULL)) < 0)
+ goto cleanup_client;
+
+ if (!client->busy)
+ pw_loop_update_io(pw_context_get_main_loop(context),
+ this->source, this->source->mask | SPA_IO_IN);
+
+ return this;
+
+cleanup_client:
+ pw_impl_client_destroy(client);
+ errno = -res;
+exit:
+ return NULL;
+}
+
+static const char *
+get_runtime_dir(void)
+{
+ const char *runtime_dir;
+
+ runtime_dir = getenv("PIPEWIRE_RUNTIME_DIR");
+ if (runtime_dir == NULL)
+ runtime_dir = getenv("XDG_RUNTIME_DIR");
+ if (runtime_dir == NULL)
+ runtime_dir = getenv("USERPROFILE");
+ return runtime_dir;
+}
+
+
+static int init_socket_name(struct server *s, const char *name)
+{
+ int name_size;
+ const char *runtime_dir;
+ bool path_is_absolute;
+
+ path_is_absolute = name[0] == '/';
+
+ runtime_dir = get_runtime_dir();
+
+ pw_log_debug("name:%s runtime_dir:%s", name, runtime_dir);
+
+ if (runtime_dir == NULL && !path_is_absolute) {
+ pw_log_error("server %p: name %s is not an absolute path and no runtime dir found. "
+ "Set one of PIPEWIRE_RUNTIME_DIR, XDG_RUNTIME_DIR or "
+ "USERPROFILE in the environment", s, name);
+ return -ENOENT;
+ }
+
+ s->addr.sun_family = AF_LOCAL;
+ if (path_is_absolute)
+ name_size = snprintf(s->addr.sun_path, sizeof(s->addr.sun_path),
+ "%s", name) + 1;
+ else
+ name_size = snprintf(s->addr.sun_path, sizeof(s->addr.sun_path),
+ "%s/%s", runtime_dir, name) + 1;
+
+ if (name_size > (int) sizeof(s->addr.sun_path)) {
+ if (path_is_absolute)
+ pw_log_error("server %p: socket path \"%s\" plus null terminator exceeds %i bytes",
+ s, name, (int) sizeof(s->addr.sun_path));
+ else
+ pw_log_error("server %p: socket path \"%s/%s\" plus null terminator exceeds %i bytes",
+ s, runtime_dir, name, (int) sizeof(s->addr.sun_path));
+ *s->addr.sun_path = 0;
+ return -ENAMETOOLONG;
+ }
+ return 0;
+}
+
+static int lock_socket(struct server *s)
+{
+ int res;
+
+ 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,
+ (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP));
+
+ if (s->fd_lock < 0) {
+ res = -errno;
+ pw_log_error("server %p: unable to open lockfile '%s': %m", s, s->lock_addr);
+ goto err;
+ }
+
+ if (flock(s->fd_lock, LOCK_EX | LOCK_NB) < 0) {
+ res = -errno;
+ pw_log_error("server %p: unable to lock lockfile '%s': %m"
+ " (maybe another daemon is running)",
+ s, s->lock_addr);
+ goto err_fd;
+ }
+ return 0;
+
+err_fd:
+ close(s->fd_lock);
+ s->fd_lock = -1;
+err:
+ *s->lock_addr = 0;
+ *s->addr.sun_path = 0;
+ return res;
+}
+
+static void
+socket_data(void *data, int fd, uint32_t mask)
+{
+ struct server *s = data;
+ struct client_data *client;
+ struct sockaddr_un name;
+ socklen_t length;
+ int client_fd;
+
+ length = sizeof(name);
+ client_fd = accept4(fd, (struct sockaddr *) &name, &length, SOCK_CLOEXEC);
+ if (client_fd < 0) {
+ pw_log_error("server %p: failed to accept: %m", s);
+ return;
+ }
+
+ client = client_new(s, client_fd);
+ if (client == NULL) {
+ pw_log_error("server %p: failed to create client", s);
+ close(client_fd);
+ return;
+ }
+}
+
+static int write_socket_address(struct server *s)
+{
+ long v;
+ int fd, res = 0;
+ char *endptr;
+ const char *env = getenv("PIPEWIRE_NOTIFICATION_FD");
+
+ if (env == NULL || env[0] == '\0')
+ return 0;
+
+ errno = 0;
+ v = strtol(env, &endptr, 10);
+ if (endptr[0] != '\0')
+ errno = EINVAL;
+ if (errno != 0) {
+ res = -errno;
+ pw_log_error("server %p: strtol() failed with error: %m", s);
+ goto error;
+ }
+ fd = (int)v;
+ if (v != fd) {
+ res = -ERANGE;
+ pw_log_error("server %p: invalid fd %ld: %s", s, v, spa_strerror(res));
+ goto error;
+ }
+ if (dprintf(fd, "%s\n", s->addr.sun_path) < 0) {
+ res = -errno;
+ pw_log_error("server %p: dprintf() failed with error: %m", s);
+ goto error;
+ }
+ close(fd);
+ unsetenv("PIPEWIRE_NOTIFICATION_FD");
+ return 0;
+
+error:
+ return res;
+}
+
+static int add_socket(struct pw_protocol *protocol, struct server *s)
+{
+ socklen_t size;
+ int fd = -1, res;
+ bool activated = false;
+
+#ifdef HAVE_SYSTEMD
+ {
+ int i, n = sd_listen_fds(0);
+ for (i = 0; i < n; ++i) {
+ if (sd_is_socket_unix(SD_LISTEN_FDS_START + i, SOCK_STREAM,
+ 1, s->addr.sun_path, 0) > 0) {
+ fd = SD_LISTEN_FDS_START + i;
+ activated = true;
+ pw_log_info("server %p: Found socket activation socket for '%s'",
+ s, s->addr.sun_path);
+ break;
+ }
+ }
+ }
+#endif
+
+ if (fd < 0) {
+ struct stat socket_stat;
+
+ if ((fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
+ 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;
+ }
+ } else if (socket_stat.st_mode & S_IWUSR || socket_stat.st_mode & S_IWGRP) {
+ unlink(s->addr.sun_path);
+ }
+
+ size = offsetof(struct sockaddr_un, sun_path) + strlen(s->addr.sun_path);
+ if (bind(fd, (struct sockaddr *) &s->addr, size) < 0) {
+ res = -errno;
+ pw_log_error("server %p: bind() failed with error: %m", s);
+ goto error_close;
+ }
+
+ if (listen(fd, 128) < 0) {
+ res = -errno;
+ pw_log_error("server %p: listen() failed with error: %m", s);
+ goto error_close;
+ }
+ }
+
+ 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) {
+ res = -errno;
+ goto error_close;
+ }
+ s->source = pw_loop_add_io(s->loop, fd, SPA_IO_IN, true, socket_data, s);
+ if (s->source == NULL) {
+ res = -errno;
+ goto error_close;
+ }
+ return 0;
+
+error_close:
+ close(fd);
+error:
+ return res;
+
+}
+
+static int impl_steal_fd(struct pw_protocol_client *client)
+{
+ struct client *impl = SPA_CONTAINER_OF(client, struct client, this);
+ int fd;
+
+ if (impl->source == NULL)
+ return -EIO;
+
+ fd = fcntl(impl->source->fd, F_DUPFD_CLOEXEC, 3);
+ if (fd < 0)
+ return -errno;
+
+ pw_protocol_client_disconnect(client);
+ return fd;
+}
+
+static int
+process_remote(struct client *impl)
+{
+ const struct pw_protocol_native_message *msg;
+ struct pw_protocol_native_connection *conn = impl->connection;
+ struct pw_core *this = impl->this.core;
+ int res = 0;
+
+ impl->ref++;
+ while (!impl->disconnecting && !impl->paused) {
+ struct pw_proxy *proxy;
+ const struct pw_protocol_native_demarshal *demarshal;
+ const struct pw_protocol_marshal *marshal;
+
+ res = pw_protocol_native_connection_get_next(conn, &msg);
+ if (res < 0) {
+ if (res == -EAGAIN)
+ res = 0;
+ break;
+ }
+ if (res == 0)
+ break;
+
+ pw_log_trace("%p: got message %d from %u seq:%d",
+ this, msg->opcode, msg->id, msg->seq);
+
+ this->recv_seq = msg->seq;
+
+ if (debug_messages)
+ debug_msg("<<<<<< in", msg, false);
+
+ pre_demarshal(conn, msg, this, footer_core_demarshal,
+ SPA_N_ELEMENTS(footer_core_demarshal));
+
+ proxy = pw_core_find_proxy(this, msg->id);
+ if (proxy == NULL || proxy->zombie) {
+ if (proxy == NULL)
+ pw_log_error("%p: could not find proxy %u", this, msg->id);
+ else
+ pw_log_debug("%p: zombie proxy %u", this, msg->id);
+
+ /* FIXME close fds */
+ continue;
+ }
+
+ marshal = pw_proxy_get_marshal(proxy);
+ if (marshal == NULL || msg->opcode >= marshal->n_server_methods) {
+ pw_log_error("%p: invalid method %u for %u (%d)",
+ this, msg->opcode, msg->id,
+ marshal ? marshal->n_server_methods : (uint32_t)-1);
+ continue;
+ }
+
+ demarshal = marshal->client_demarshal;
+ if (!demarshal[msg->opcode].func) {
+ pw_log_error("%p: function %d not implemented on %u",
+ this, msg->opcode, msg->id);
+ continue;
+ }
+ proxy->refcount++;
+ pw_protocol_native_connection_enter(conn);
+ res = demarshal[msg->opcode].func(proxy, msg);
+ pw_protocol_native_connection_leave(conn);
+ pw_proxy_unref(proxy);
+
+ if (res < 0) {
+ pw_log_error("%p: invalid message received %u for %u: %s",
+ this, msg->opcode, msg->id, spa_strerror(res));
+ debug_msg("*invalid*", msg, true);
+ }
+ res = 0;
+ }
+ client_unref(impl);
+ return res;
+}
+
+static void
+on_remote_data(void *data, int fd, uint32_t mask)
+{
+ struct client *impl = data;
+ struct pw_core *this = impl->this.core;
+ struct pw_proxy *core_proxy = (struct pw_proxy*)this;
+ struct pw_protocol_native_connection *conn = impl->connection;
+ struct pw_context *context = pw_core_get_context(this);
+ struct pw_loop *loop = pw_context_get_main_loop(context);
+ int res;
+
+ core_proxy->refcount++;
+ impl->ref++;
+
+ if (mask & (SPA_IO_ERR | SPA_IO_HUP)) {
+ res = -EPIPE;
+ goto error;
+ }
+ if (mask & SPA_IO_IN) {
+ if ((res = process_remote(impl)) < 0)
+ goto error;
+ }
+ if (mask & SPA_IO_OUT || impl->need_flush) {
+ if (!impl->connected) {
+ socklen_t len = sizeof res;
+
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &res, &len) < 0) {
+ res = -errno;
+ pw_log_error("getsockopt: %m");
+ goto error;
+ }
+ if (res != 0) {
+ res = -res;
+ goto error;
+ }
+ impl->connected = true;
+ pw_log_debug("%p: connected, fd %d", impl, fd);
+ }
+ impl->need_flush = false;
+ res = pw_protocol_native_connection_flush(conn);
+ if (res >= 0) {
+ pw_loop_update_io(loop, impl->source,
+ impl->source->mask & ~SPA_IO_OUT);
+ } else if (res != -EAGAIN)
+ goto error;
+ }
+
+done:
+ client_unref(impl);
+ pw_proxy_unref(core_proxy);
+ return;
+error:
+ pw_log_debug("%p: got connection error %d (%s)", impl, res, spa_strerror(res));
+ if (impl->source) {
+ pw_loop_destroy_source(loop, impl->source);
+ impl->source = NULL;
+ }
+ pw_proxy_notify(core_proxy,
+ struct pw_core_events, error, 0, 0,
+ this->recv_seq, res, "connection error");
+ goto done;
+}
+
+static int impl_connect_fd(struct pw_protocol_client *client, int fd, bool do_close)
+{
+ struct client *impl = SPA_CONTAINER_OF(client, struct client, this);
+
+ impl->connected = false;
+ impl->disconnecting = false;
+
+ pw_protocol_native_connection_set_fd(impl->connection, fd);
+ impl->source = pw_loop_add_io(impl->context->main_loop,
+ fd,
+ SPA_IO_IN | SPA_IO_OUT | SPA_IO_HUP | SPA_IO_ERR,
+ do_close, on_remote_data, impl);
+ if (impl->source == NULL)
+ return -errno;
+
+ return 0;
+}
+
+static void impl_disconnect(struct pw_protocol_client *client)
+{
+ struct client *impl = SPA_CONTAINER_OF(client, struct client, this);
+
+ impl->disconnecting = true;
+
+ if (impl->source)
+ pw_loop_destroy_source(impl->context->main_loop, impl->source);
+ impl->source = NULL;
+
+ pw_protocol_native_connection_set_fd(impl->connection, -1);
+}
+
+static void impl_destroy(struct pw_protocol_client *client)
+{
+ struct client *impl = SPA_CONTAINER_OF(client, struct client, this);
+
+ impl_disconnect(client);
+
+ if (impl->connection)
+ pw_protocol_native_connection_destroy(impl->connection);
+ impl->connection = NULL;
+
+ spa_list_remove(&client->link);
+ client_unref(impl);
+}
+
+static int impl_set_paused(struct pw_protocol_client *client, bool paused)
+{
+ struct client *impl = SPA_CONTAINER_OF(client, struct client, this);
+ uint32_t mask;
+
+ if (impl->source == NULL)
+ return -EIO;
+
+ mask = impl->source->mask;
+
+ impl->paused = paused;
+
+ SPA_FLAG_UPDATE(mask, SPA_IO_IN, !paused);
+
+ pw_log_debug("%p: paused %d", client->protocol, paused);
+ pw_loop_update_io(impl->context->main_loop, impl->source, mask);
+
+ return paused ? 0 : process_remote(impl);
+}
+
+static int pw_protocol_native_connect_internal(struct pw_protocol_client *client,
+ const struct spa_dict *props,
+ void (*done_callback) (void *data, int res),
+ void *data)
+{
+ int res, sv[2];
+ struct pw_protocol *protocol = client->protocol;
+ struct protocol_data *d = pw_protocol_get_user_data(protocol);
+ struct server *s = d->local;
+ struct pw_permission permissions[1];
+ struct client_data *c;
+
+ pw_log_debug("server %p: internal connect", s);
+
+ if (socketpair(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0, sv) < 0) {
+ res = -errno;
+ pw_log_error("server %p: socketpair() failed with error: %m", s);
+ goto error;
+ }
+
+ c = client_new(s, sv[0]);
+ if (c == NULL) {
+ res = -errno;
+ pw_log_error("server %p: failed to create client: %m", s);
+ goto error_close;
+ }
+ permissions[0] = PW_PERMISSION_INIT(PW_ID_ANY, PW_PERM_ALL);
+ pw_impl_client_update_permissions(c->client, 1, permissions);
+
+ res = pw_protocol_client_connect_fd(client, sv[1], true);
+done:
+ if (done_callback)
+ done_callback(data, res);
+ return res;
+
+error_close:
+ close(sv[0]);
+ close(sv[1]);
+error:
+ goto done;
+}
+
+static void on_client_connection_destroy(void *data)
+{
+ struct client *impl = data;
+ spa_hook_remove(&impl->conn_listener);
+}
+
+static void on_client_need_flush(void *data)
+{
+ struct client *impl = data;
+
+ pw_log_trace("need flush");
+ impl->need_flush = true;
+
+ if (impl->source && !(impl->source->mask & SPA_IO_OUT)) {
+ pw_loop_update_io(impl->context->main_loop,
+ impl->source, impl->source->mask | SPA_IO_OUT);
+ }
+}
+
+static const struct pw_protocol_native_connection_events client_conn_events = {
+ PW_VERSION_PROTOCOL_NATIVE_CONNECTION_EVENTS,
+ .destroy = on_client_connection_destroy,
+ .need_flush = on_client_need_flush,
+};
+
+static struct pw_protocol_client *
+impl_new_client(struct pw_protocol *protocol,
+ struct pw_core *core,
+ const struct spa_dict *props)
+{
+ struct client *impl;
+ struct pw_protocol_client *this;
+ const char *str = NULL;
+ int res;
+
+ if ((impl = calloc(1, sizeof(struct client))) == NULL)
+ return NULL;
+
+ pw_log_debug("%p: new client %p", protocol, impl);
+
+ this = &impl->this;
+ this->protocol = protocol;
+ this->core = core;
+
+ impl->ref = 1;
+ impl->context = protocol->context;
+ impl->connection = pw_protocol_native_connection_new(protocol->context, -1);
+ if (impl->connection == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+ pw_protocol_native_connection_add_listener(impl->connection,
+ &impl->conn_listener,
+ &client_conn_events,
+ impl);
+
+ if (props) {
+ str = spa_dict_lookup(props, PW_KEY_REMOTE_INTENTION);
+ if (str == NULL &&
+ (str = spa_dict_lookup(props, PW_KEY_REMOTE_NAME)) != NULL &&
+ spa_streq(str, "internal"))
+ str = "internal";
+ }
+ if (str == NULL)
+ str = "generic";
+
+ pw_log_debug("%p: connect %s", protocol, str);
+
+ if (spa_streq(str, "screencast"))
+ this->connect = pw_protocol_native_connect_portal_screencast;
+ else if (spa_streq(str, "internal"))
+ this->connect = pw_protocol_native_connect_internal;
+ else
+ this->connect = pw_protocol_native_connect_local_socket;
+
+ this->steal_fd = impl_steal_fd;
+ this->connect_fd = impl_connect_fd;
+ this->disconnect = impl_disconnect;
+ this->destroy = impl_destroy;
+ this->set_paused = impl_set_paused;
+
+ spa_list_append(&protocol->client_list, &this->link);
+
+ return this;
+
+error_free:
+ free(impl);
+ errno = -res;
+ return NULL;
+}
+
+static void destroy_server(struct pw_protocol_server *server)
+{
+ struct server *s = SPA_CONTAINER_OF(server, struct server, this);
+ struct client_data *data, *tmp;
+
+ pw_log_debug("%p: server %p", s->this.protocol, s);
+
+ spa_list_remove(&server->link);
+
+ spa_list_for_each_safe(data, tmp, &server->client_list, protocol_link)
+ pw_impl_client_destroy(data->client);
+
+ if (s->source)
+ pw_loop_destroy_source(s->loop, s->source);
+ if (s->resume)
+ pw_loop_destroy_source(s->loop, s->resume);
+ if (s->addr.sun_path[0] && !s->activated)
+ unlink(s->addr.sun_path);
+ if (s->lock_addr[0])
+ unlink(s->lock_addr);
+ if (s->fd_lock != -1)
+ close(s->fd_lock);
+ free(s);
+}
+
+static void do_resume(void *_data, uint64_t count)
+{
+ struct server *server = _data;
+ struct pw_protocol_server *this = &server->this;
+ struct client_data *data, *tmp;
+ int res;
+
+ pw_log_debug("flush");
+
+ spa_list_for_each_safe(data, tmp, &this->client_list, protocol_link) {
+ data->client->refcount++;
+ if ((res = process_messages(data)) < 0)
+ handle_client_error(data->client, res, "do_resume");
+ pw_impl_client_unref(data->client);
+ }
+ return;
+}
+
+static const char *
+get_server_name(const struct spa_dict *props)
+{
+ const char *name = NULL;
+
+ name = getenv("PIPEWIRE_CORE");
+ if (name == NULL && props != NULL)
+ name = spa_dict_lookup(props, PW_KEY_CORE_NAME);
+ if (name == NULL)
+ name = PW_DEFAULT_REMOTE;
+ return name;
+}
+
+static struct server *
+create_server(struct pw_protocol *protocol,
+ struct pw_impl_core *core,
+ const struct spa_dict *props)
+{
+ struct pw_protocol_server *this;
+ struct server *s;
+
+ if ((s = calloc(1, sizeof(struct server))) == NULL)
+ return NULL;
+
+ s->fd_lock = -1;
+
+ this = &s->this;
+ this->protocol = protocol;
+ this->core = core;
+ spa_list_init(&this->client_list);
+ this->destroy = destroy_server;
+
+ spa_list_append(&protocol->server_list, &this->link);
+
+ pw_log_debug("%p: created server %p", protocol, this);
+
+ return s;
+}
+
+static struct pw_protocol_server *
+impl_add_server(struct pw_protocol *protocol,
+ struct pw_impl_core *core,
+ const struct spa_dict *props)
+{
+ struct pw_protocol_server *this;
+ struct server *s;
+ const char *name;
+ int res;
+
+ if ((s = create_server(protocol, core, props)) == NULL)
+ return NULL;
+
+ this = &s->this;
+
+ name = get_server_name(props);
+
+ if ((res = init_socket_name(s, name)) < 0)
+ goto error;
+
+ if ((res = lock_socket(s)) < 0)
+ goto error;
+
+ if ((res = add_socket(protocol, s)) < 0)
+ goto error;
+
+ if ((s->resume = pw_loop_add_event(s->loop, do_resume, s)) == NULL)
+ goto error;
+
+ pw_log_info("%p: Listening on '%s'", protocol, name);
+
+ return this;
+
+error:
+ destroy_server(this);
+ errno = -res;
+ return NULL;
+}
+
+static const struct pw_protocol_implementation protocol_impl = {
+ PW_VERSION_PROTOCOL_IMPLEMENTATION,
+ .new_client = impl_new_client,
+ .add_server = impl_add_server,
+};
+
+static struct spa_pod_builder *
+impl_ext_begin_proxy(struct pw_proxy *proxy, uint8_t opcode, struct pw_protocol_native_message **msg)
+{
+ struct client *impl = SPA_CONTAINER_OF(proxy->core->conn, struct client, this);
+ return pw_protocol_native_connection_begin(impl->connection, proxy->id, opcode, msg);
+}
+
+static uint32_t impl_ext_add_proxy_fd(struct pw_proxy *proxy, int fd)
+{
+ struct client *impl = SPA_CONTAINER_OF(proxy->core->conn, struct client, this);
+ return pw_protocol_native_connection_add_fd(impl->connection, fd);
+}
+
+static int impl_ext_get_proxy_fd(struct pw_proxy *proxy, uint32_t index)
+{
+ struct client *impl = SPA_CONTAINER_OF(proxy->core->conn, struct client, this);
+ return pw_protocol_native_connection_get_fd(impl->connection, index);
+}
+
+static void assert_single_pod(struct spa_pod_builder *builder)
+{
+ /*
+ * Check the invariant that the message we just marshaled
+ * consists of at most one POD.
+ */
+ struct spa_pod *pod = builder->data;
+ spa_assert(builder->data == NULL ||
+ builder->state.offset < sizeof(struct spa_pod) ||
+ builder->state.offset == SPA_POD_SIZE(pod));
+}
+
+static int impl_ext_end_proxy(struct pw_proxy *proxy,
+ struct spa_pod_builder *builder)
+{
+ struct pw_core *core = proxy->core;
+ struct client *impl = SPA_CONTAINER_OF(core->conn, struct client, this);
+ assert_single_pod(builder);
+ marshal_core_footers(&impl->footer_state, core, builder);
+ return core->send_seq = pw_protocol_native_connection_end(impl->connection, builder);
+}
+
+static struct spa_pod_builder *
+impl_ext_begin_resource(struct pw_resource *resource,
+ uint8_t opcode, struct pw_protocol_native_message **msg)
+{
+ struct client_data *data = resource->client->user_data;
+ return pw_protocol_native_connection_begin(data->connection, resource->id, opcode, msg);
+}
+
+static uint32_t impl_ext_add_resource_fd(struct pw_resource *resource, int fd)
+{
+ struct client_data *data = resource->client->user_data;
+ return pw_protocol_native_connection_add_fd(data->connection, fd);
+}
+static int impl_ext_get_resource_fd(struct pw_resource *resource, uint32_t index)
+{
+ struct client_data *data = resource->client->user_data;
+ return pw_protocol_native_connection_get_fd(data->connection, index);
+}
+
+static int impl_ext_end_resource(struct pw_resource *resource,
+ struct spa_pod_builder *builder)
+{
+ struct client_data *data = resource->client->user_data;
+ struct pw_impl_client *client = resource->client;
+ assert_single_pod(builder);
+ marshal_client_footers(&data->footer_state, client, builder);
+ return client->send_seq = pw_protocol_native_connection_end(data->connection, builder);
+}
+static const struct pw_protocol_native_ext protocol_ext_impl = {
+ PW_VERSION_PROTOCOL_NATIVE_EXT,
+ .begin_proxy = impl_ext_begin_proxy,
+ .add_proxy_fd = impl_ext_add_proxy_fd,
+ .get_proxy_fd = impl_ext_get_proxy_fd,
+ .end_proxy = impl_ext_end_proxy,
+ .begin_resource = impl_ext_begin_resource,
+ .add_resource_fd = impl_ext_add_resource_fd,
+ .get_resource_fd = impl_ext_get_resource_fd,
+ .end_resource = impl_ext_end_resource,
+};
+
+static void module_destroy(void *data)
+{
+ struct protocol_data *d = data;
+
+ spa_hook_remove(&d->module_listener);
+
+ pw_protocol_destroy(d->protocol);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+static int need_server(struct pw_context *context, const struct spa_dict *props)
+{
+ const char *val = NULL;
+
+ val = getenv("PIPEWIRE_DAEMON");
+ if (val == NULL && props != NULL)
+ val = spa_dict_lookup(props, PW_KEY_CORE_DAEMON);
+ if (val && pw_properties_parse_bool(val))
+ return 1;
+ return 0;
+}
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_protocol *this;
+ struct protocol_data *d;
+ const struct pw_properties *props;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+ PW_LOG_TOPIC_INIT(mod_topic_connection);
+
+ if (pw_context_find_protocol(context, PW_TYPE_INFO_PROTOCOL_Native) != NULL)
+ return 0;
+
+ this = pw_protocol_new(context, PW_TYPE_INFO_PROTOCOL_Native, sizeof(struct protocol_data));
+ if (this == NULL)
+ return -errno;
+
+ debug_messages = mod_topic_connection->level >= SPA_LOG_LEVEL_DEBUG;
+
+ this->implementation = &protocol_impl;
+ this->extension = &protocol_ext_impl;
+
+ pw_protocol_native_init(this);
+ pw_protocol_native0_init(this);
+
+ pw_log_debug("%p: new debug:%d", this, debug_messages);
+
+ d = pw_protocol_get_user_data(this);
+ d->protocol = this;
+ d->module = module;
+
+ props = pw_context_get_properties(context);
+ d->local = create_server(this, context->core, &props->dict);
+
+ if (need_server(context, &props->dict)) {
+ if (impl_add_server(this, context->core, &props->dict) == NULL) {
+ res = -errno;
+ goto error_cleanup;
+ }
+ }
+
+ pw_impl_module_add_listener(module, &d->module_listener, &module_events, d);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+
+error_cleanup:
+ pw_protocol_destroy(this);
+ return res;
+}
diff --git a/src/modules/module-protocol-native/connection.c b/src/modules/module-protocol-native/connection.c
new file mode 100644
index 0000000..1ba256c
--- /dev/null
+++ b/src/modules/module-protocol-native/connection.c
@@ -0,0 +1,866 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+
+#include <spa/utils/result.h>
+#include <spa/pod/builder.h>
+
+#include <pipewire/pipewire.h>
+
+PW_LOG_TOPIC_EXTERN(mod_topic);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+PW_LOG_TOPIC_EXTERN(mod_topic_connection);
+
+#undef spa_debug
+#define spa_debug(...) pw_logt_debug(mod_topic_connection, __VA_ARGS__)
+#include <spa/debug/pod.h>
+
+#include "connection.h"
+#include "defs.h"
+
+#define MAX_BUFFER_SIZE (1024 * 32)
+#define MAX_FDS 1024u
+#define MAX_FDS_MSG 28
+
+#define HDR_SIZE_V0 8
+#define HDR_SIZE 16
+
+struct buffer {
+ uint8_t *buffer_data;
+ size_t buffer_size;
+ size_t buffer_maxsize;
+ int fds[MAX_FDS];
+ uint32_t n_fds;
+
+ uint32_t seq;
+ size_t offset;
+ size_t fds_offset;
+ struct pw_protocol_native_message msg;
+};
+
+struct reenter_item {
+ void *old_buffer_data;
+ struct pw_protocol_native_message return_msg;
+ struct spa_list link;
+};
+
+struct impl {
+ struct pw_protocol_native_connection this;
+ struct pw_context *context;
+
+ struct buffer in, out;
+ struct spa_pod_builder builder;
+
+ struct spa_list reenter_stack;
+ uint32_t pending_reentering;
+
+ uint32_t version;
+ size_t hdr_size;
+};
+
+/** \endcond */
+
+/** Get an fd from a connection
+ *
+ * \param conn the connection
+ * \param index the index of the fd to get
+ * \return the fd at \a index or -ENOENT when no such fd exists
+ *
+ * \memberof pw_protocol_native_connection
+ */
+int pw_protocol_native_connection_get_fd(struct pw_protocol_native_connection *conn, uint32_t index)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ struct buffer *buf = &impl->in;
+
+ if (index == SPA_ID_INVALID)
+ return -1;
+
+ if (index >= buf->msg.n_fds)
+ return -ENOENT;
+
+ return buf->msg.fds[index];
+}
+
+/** Add an fd to a connection
+ *
+ * \param conn the connection
+ * \param fd the fd to add
+ * \return the index of the fd or SPA_IDX_INVALID when an error occurred
+ *
+ * \memberof pw_protocol_native_connection
+ */
+uint32_t pw_protocol_native_connection_add_fd(struct pw_protocol_native_connection *conn, int fd)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ struct buffer *buf = &impl->out;
+ uint32_t index, i;
+
+ if (fd < 0)
+ return SPA_IDX_INVALID;
+
+ for (i = 0; i < buf->msg.n_fds; i++) {
+ if (buf->msg.fds[i] == fd)
+ return i;
+ }
+
+ index = buf->msg.n_fds;
+ if (index + buf->n_fds >= MAX_FDS) {
+ pw_log_error("connection %p: too many fds (%d)", conn, MAX_FDS);
+ return SPA_IDX_INVALID;
+ }
+
+ buf->msg.fds[index] = fcntl(fd, F_DUPFD_CLOEXEC, 0);
+ if (buf->msg.fds[index] == -1) {
+ pw_log_error("connection %p: can't DUP fd:%d %m", conn, fd);
+ return SPA_IDX_INVALID;
+ }
+ buf->msg.n_fds++;
+ pw_log_debug("connection %p: add fd %d (new fd:%d) at index %d",
+ conn, fd, buf->msg.fds[index], index);
+
+ return index;
+}
+
+static void *connection_ensure_size(struct pw_protocol_native_connection *conn, struct buffer *buf, size_t size)
+{
+ int res;
+
+ if (buf->buffer_size + size > buf->buffer_maxsize) {
+ void *np;
+ size_t ns;
+
+ ns = SPA_ROUND_UP_N(buf->buffer_size + size, MAX_BUFFER_SIZE);
+ np = realloc(buf->buffer_data, ns);
+ if (np == NULL) {
+ res = -errno;
+ free(buf->buffer_data);
+ buf->buffer_maxsize = 0;
+ spa_hook_list_call(&conn->listener_list,
+ struct pw_protocol_native_connection_events,
+ error, 0, res);
+ errno = -res;
+ return NULL;
+ }
+ buf->buffer_maxsize = ns;
+ buf->buffer_data = np;
+ pw_log_debug("connection %p: resize buffer to %zd %zd %zd",
+ conn, buf->buffer_size, size, buf->buffer_maxsize);
+ }
+ return (uint8_t *) buf->buffer_data + buf->buffer_size;
+}
+
+static void handle_connection_error(struct pw_protocol_native_connection *conn, int res)
+{
+ if (res == EPIPE || res == ECONNRESET)
+ pw_log_info("connection %p: could not recvmsg on fd:%d: %s", conn, conn->fd, strerror(res));
+ else
+ pw_log_error("connection %p: could not recvmsg on fd:%d: %s", conn, conn->fd, strerror(res));
+}
+
+static size_t cmsg_data_length(const struct cmsghdr *cmsg)
+{
+ const void *begin = CMSG_DATA(cmsg);
+ const void *end = SPA_PTROFF(cmsg, cmsg->cmsg_len, void);
+
+ spa_assert(begin <= end);
+
+ return SPA_PTRDIFF(end, begin);
+}
+
+static void close_all_fds(struct msghdr *msg, struct cmsghdr *from)
+{
+ for (; from != NULL; from = CMSG_NXTHDR(msg, from)) {
+ if (from->cmsg_level != SOL_SOCKET || from->cmsg_type != SCM_RIGHTS)
+ continue;
+
+ size_t n_fds = cmsg_data_length(from) / sizeof(int);
+ for (size_t i = 0; i < n_fds; i++) {
+ const void *p = SPA_PTROFF(CMSG_DATA(from), sizeof(int) * i, void);
+ int fd;
+
+ memcpy(&fd, p, sizeof(fd));
+ pw_log_debug("%p: close fd:%d", msg, fd);
+ close(fd);
+ }
+ }
+}
+
+static int refill_buffer(struct pw_protocol_native_connection *conn, struct buffer *buf)
+{
+ ssize_t len;
+ struct cmsghdr *cmsg = NULL;
+ struct msghdr msg = { 0 };
+ struct iovec iov[1];
+ union {
+ char cmsgbuf[CMSG_SPACE(MAX_FDS_MSG * sizeof(int))];
+ struct cmsghdr align;
+ } cmsgbuf;
+ int n_fds = 0;
+ size_t avail;
+
+ avail = buf->buffer_maxsize - buf->buffer_size;
+
+ iov[0].iov_base = buf->buffer_data + buf->buffer_size;
+ iov[0].iov_len = avail;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = &cmsgbuf;
+ msg.msg_controllen = sizeof(cmsgbuf);
+ msg.msg_flags = MSG_CMSG_CLOEXEC | MSG_DONTWAIT;
+
+ while (true) {
+ len = recvmsg(conn->fd, &msg, msg.msg_flags);
+ if (msg.msg_flags & MSG_CTRUNC)
+ goto cmsgs_truncated;
+ if (len == 0 && avail != 0)
+ return -EPIPE;
+ else if (len < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN && errno != EWOULDBLOCK)
+ goto recv_error;
+ return -EAGAIN;
+ }
+ break;
+ }
+
+ buf->buffer_size += len;
+
+ /* handle control messages */
+ for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+ if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS)
+ continue;
+
+ n_fds = cmsg_data_length(cmsg) / sizeof(int);
+ if (n_fds + buf->n_fds > MAX_FDS)
+ goto too_many_fds;
+ memcpy(&buf->fds[buf->n_fds], CMSG_DATA(cmsg), n_fds * sizeof(int));
+ buf->n_fds += n_fds;
+ }
+ pw_log_trace("connection %p: %d read %zd bytes and %d fds", conn, conn->fd, len,
+ n_fds);
+
+ return 0;
+
+ /* ERRORS */
+recv_error:
+ handle_connection_error(conn, errno);
+ return -errno;
+
+cmsgs_truncated:
+ close_all_fds(&msg, CMSG_FIRSTHDR(&msg));
+ return -EPROTO;
+
+too_many_fds:
+ close_all_fds(&msg, cmsg);
+ return -EPROTO;
+}
+
+static void clear_buffer(struct buffer *buf, bool fds)
+{
+ uint32_t i;
+ if (fds) {
+ for (i = 0; i < buf->n_fds; i++) {
+ pw_log_debug("%p: close fd:%d", buf, buf->fds[i]);
+ close(buf->fds[i]);
+ }
+ }
+ buf->n_fds = 0;
+ buf->buffer_size = 0;
+ buf->offset = 0;
+ buf->fds_offset = 0;
+}
+
+/** Prepare connection for calling from reentered context.
+ *
+ * This ensures that message buffers returned by get_next are not invalidated by additional
+ * calls made after enter. Leave invalidates the buffers at the higher stack level.
+ *
+ * \memberof pw_protocol_native_connection
+ */
+void pw_protocol_native_connection_enter(struct pw_protocol_native_connection *conn)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+
+ /* Postpone processing until get_next is actually called */
+ ++impl->pending_reentering;
+}
+
+static void pop_reenter_stack(struct impl *impl, uint32_t count)
+{
+ while (count > 0) {
+ struct reenter_item *item;
+
+ item = spa_list_last(&impl->reenter_stack, struct reenter_item, link);
+ spa_list_remove(&item->link);
+
+ free(item->return_msg.fds);
+ free(item->old_buffer_data);
+ free(item);
+
+ --count;
+ }
+}
+
+void pw_protocol_native_connection_leave(struct pw_protocol_native_connection *conn)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+
+ if (impl->pending_reentering > 0) {
+ --impl->pending_reentering;
+ } else {
+ pw_log_trace("connection %p: reenter: pop", impl);
+ pop_reenter_stack(impl, 1);
+ }
+}
+
+static int ensure_stack_level(struct impl *impl, struct pw_protocol_native_message **msg)
+{
+ void *data;
+ struct buffer *buf = &impl->in;
+ struct reenter_item *item, *new_item = NULL;
+
+ item = spa_list_last(&impl->reenter_stack, struct reenter_item, link);
+
+ if (SPA_LIKELY(impl->pending_reentering == 0)) {
+ new_item = item;
+ } else {
+ uint32_t new_count;
+
+ pw_log_trace("connection %p: reenter: push %d levels",
+ impl, impl->pending_reentering);
+
+ /* Append empty item(s) to the reenter stack */
+ for (new_count = 0; new_count < impl->pending_reentering; ++new_count) {
+ new_item = calloc(1, sizeof(struct reenter_item));
+ if (new_item == NULL) {
+ pop_reenter_stack(impl, new_count);
+ return -ENOMEM;
+ }
+ spa_list_append(&impl->reenter_stack, &new_item->link);
+ }
+
+ /*
+ * Stack level increased: we have to switch to a new message data buffer, because
+ * data of returned messages is contained in the buffer and might still be in
+ * use on the lower stack levels.
+ *
+ * We stash the buffer for the previous stack level, and allocate a new one for
+ * the new stack level. If there was a previous buffer for the previous level, we
+ * know its contents are no longer in use (the only active buffer at that stack
+ * level is buf->buffer_data), and we can recycle it as the new buffer (realloc
+ * instead of calloc).
+ *
+ * The current data contained in the buffer needs to be copied to the new buffer.
+ */
+
+ data = realloc(item->old_buffer_data, buf->buffer_maxsize);
+ if (data == NULL) {
+ pop_reenter_stack(impl, new_count);
+ return -ENOMEM;
+ }
+
+ item->old_buffer_data = buf->buffer_data;
+
+ memcpy(data, buf->buffer_data, buf->buffer_size);
+ buf->buffer_data = data;
+
+ impl->pending_reentering = 0;
+ }
+ if (new_item == NULL)
+ return -EIO;
+
+ /* Ensure fds buffer is allocated */
+ if (SPA_UNLIKELY(new_item->return_msg.fds == NULL)) {
+ data = calloc(MAX_FDS, sizeof(int));
+ if (data == NULL)
+ return -ENOMEM;
+ new_item->return_msg.fds = data;
+ }
+
+ *msg = &new_item->return_msg;
+
+ return 0;
+}
+
+/** Make a new connection object for the given socket
+ *
+ * \param fd the socket
+ * \returns a newly allocated connection object
+ *
+ * \memberof pw_protocol_native_connection
+ */
+struct pw_protocol_native_connection *pw_protocol_native_connection_new(struct pw_context *context, int fd)
+{
+ struct impl *impl;
+ struct pw_protocol_native_connection *this;
+ struct reenter_item *reenter_item;
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return NULL;
+
+ impl->context = context;
+
+ this = &impl->this;
+
+ pw_log_debug("connection %p: new fd:%d", this, fd);
+
+ this->fd = fd;
+ spa_hook_list_init(&this->listener_list);
+
+ impl->hdr_size = HDR_SIZE;
+ impl->version = 3;
+
+ impl->out.buffer_data = calloc(1, MAX_BUFFER_SIZE);
+ impl->out.buffer_maxsize = MAX_BUFFER_SIZE;
+ impl->in.buffer_data = calloc(1, MAX_BUFFER_SIZE);
+ impl->in.buffer_maxsize = MAX_BUFFER_SIZE;
+
+ reenter_item = calloc(1, sizeof(struct reenter_item));
+
+ if (impl->out.buffer_data == NULL || impl->in.buffer_data == NULL || reenter_item == NULL)
+ goto no_mem;
+
+ spa_list_init(&impl->reenter_stack);
+ spa_list_append(&impl->reenter_stack, &reenter_item->link);
+
+ return this;
+
+no_mem:
+ free(impl->out.buffer_data);
+ free(impl->in.buffer_data);
+ free(reenter_item);
+ free(impl);
+ return NULL;
+}
+
+int pw_protocol_native_connection_set_fd(struct pw_protocol_native_connection *conn, int fd)
+{
+ pw_log_debug("connection %p: fd:%d", conn, fd);
+ conn->fd = fd;
+ return 0;
+}
+
+/** Destroy a connection
+ *
+ * \param conn the connection to destroy
+ *
+ * \memberof pw_protocol_native_connection
+ */
+void pw_protocol_native_connection_destroy(struct pw_protocol_native_connection *conn)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+
+ pw_log_debug("connection %p: destroy", conn);
+
+ spa_hook_list_call(&conn->listener_list, struct pw_protocol_native_connection_events, destroy, 0);
+
+ spa_hook_list_clean(&conn->listener_list);
+
+ clear_buffer(&impl->out, true);
+ clear_buffer(&impl->in, true);
+ free(impl->out.buffer_data);
+ free(impl->in.buffer_data);
+
+ while (!spa_list_is_empty(&impl->reenter_stack))
+ pop_reenter_stack(impl, 1);
+
+ free(impl);
+}
+
+static int prepare_packet(struct pw_protocol_native_connection *conn, struct buffer *buf)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ uint8_t *data;
+ size_t size, len;
+ uint32_t *p;
+
+ data = buf->buffer_data + buf->offset;
+ size = buf->buffer_size - buf->offset;
+
+ if (size < impl->hdr_size)
+ return impl->hdr_size;
+
+ p = (uint32_t *) data;
+
+ buf->msg.id = p[0];
+ buf->msg.opcode = p[1] >> 24;
+ len = p[1] & 0xffffff;
+
+ if (buf->msg.id == 0 && buf->msg.opcode == 1) {
+ if (p[3] >= 4) {
+ pw_log_warn("old version detected");
+ impl->version = 0;
+ impl->hdr_size = HDR_SIZE_V0;
+ } else {
+ impl->version = 3;
+ impl->hdr_size = HDR_SIZE;
+ }
+ spa_hook_list_call(&conn->listener_list,
+ struct pw_protocol_native_connection_events,
+ start, 0, impl->version);
+ }
+ if (impl->version >= 3) {
+ buf->msg.seq = p[2];
+ buf->msg.n_fds = p[3];
+ } else {
+ buf->msg.seq = 0;
+ buf->msg.n_fds = 0;
+ }
+
+ data += impl->hdr_size;
+ size -= impl->hdr_size;
+ buf->msg.fds = &buf->fds[buf->fds_offset];
+
+ if (buf->msg.n_fds + buf->fds_offset > MAX_FDS)
+ return -EPROTO;
+
+ if (size < len)
+ return len;
+
+ buf->msg.size = len;
+ buf->msg.data = data;
+
+ buf->offset += impl->hdr_size + len;
+ buf->fds_offset += buf->msg.n_fds;
+
+ if (buf->offset >= buf->buffer_size)
+ clear_buffer(buf, false);
+
+ return 0;
+}
+
+/** Move to the next packet in the connection
+ *
+ * \param conn the connection
+ * \param opcode address of result opcode
+ * \param dest_id address of result destination id
+ * \param dt pointer to packet data
+ * \param sz size of packet data
+ * \return true on success
+ *
+ * Get the next packet in \a conn and store the opcode and destination
+ * id as well as the packet data and size.
+ *
+ * \memberof pw_protocol_native_connection
+ */
+int
+pw_protocol_native_connection_get_next(struct pw_protocol_native_connection *conn,
+ const struct pw_protocol_native_message **msg)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ int len, res;
+ struct buffer *buf;
+ struct pw_protocol_native_message *return_msg;
+ int *fds;
+
+ if ((res = ensure_stack_level(impl, &return_msg)) < 0)
+ return res;
+
+ buf = &impl->in;
+
+ while (1) {
+ len = prepare_packet(conn, buf);
+ if (len < 0)
+ return len;
+ if (len == 0)
+ break;
+
+ if (connection_ensure_size(conn, buf, len) == NULL)
+ return -errno;
+ if ((res = refill_buffer(conn, buf)) < 0)
+ return res;
+ }
+
+ /* Returned msg struct should be safe vs. reentering */
+ fds = return_msg->fds;
+ *return_msg = buf->msg;
+ if (buf->msg.n_fds > 0) {
+ memcpy(fds, buf->msg.fds, buf->msg.n_fds * sizeof(int));
+ }
+ return_msg->fds = fds;
+
+ *msg = return_msg;
+
+ return 1;
+}
+
+/** Get footer data from the tail of the current packet.
+ *
+ * \param conn the connection
+ * \param msg current message
+ * \return footer POD, or NULL if no valid footer present
+ *
+ * \memberof pw_protocol_native_connection
+ */
+struct spa_pod *pw_protocol_native_connection_get_footer(struct pw_protocol_native_connection *conn,
+ const struct pw_protocol_native_message *msg)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ struct spa_pod *pod;
+
+ if (impl->version != 3)
+ return NULL;
+
+ /*
+ * Protocol version 3 footer: a single SPA POD
+ */
+
+ /* Footer immediately follows the message POD, if it is present */
+ if ((pod = get_first_pod_from_data(msg->data, msg->size, 0)) == NULL)
+ return NULL;
+ pod = get_first_pod_from_data(msg->data, msg->size, SPA_POD_SIZE(pod));
+ if (pod == NULL)
+ return NULL;
+ pw_log_trace("connection %p: recv message footer, size:%zu",
+ conn, (size_t)SPA_POD_SIZE(pod));
+ return pod;
+}
+
+static inline void *begin_write(struct pw_protocol_native_connection *conn, uint32_t size)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ uint32_t *p;
+ struct buffer *buf = &impl->out;
+ /* header and size for payload */
+ if ((p = connection_ensure_size(conn, buf, impl->hdr_size + size)) == NULL)
+ return NULL;
+
+ return SPA_PTROFF(p, impl->hdr_size, void);
+}
+
+static int builder_overflow(void *data, uint32_t size)
+{
+ struct impl *impl = data;
+ struct spa_pod_builder *b = &impl->builder;
+
+ b->size = SPA_ROUND_UP_N(size, 4096);
+ if ((b->data = begin_write(&impl->this, b->size)) == NULL)
+ return -errno;
+ return 0;
+}
+
+static const struct spa_pod_builder_callbacks builder_callbacks = {
+ SPA_VERSION_POD_BUILDER_CALLBACKS,
+ .overflow = builder_overflow
+};
+
+struct spa_pod_builder *
+pw_protocol_native_connection_begin(struct pw_protocol_native_connection *conn,
+ uint32_t id, uint8_t opcode,
+ struct pw_protocol_native_message **msg)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ struct buffer *buf = &impl->out;
+
+ buf->msg.id = id;
+ buf->msg.opcode = opcode;
+ impl->builder = SPA_POD_BUILDER_INIT(NULL, 0);
+ spa_pod_builder_set_callbacks(&impl->builder, &builder_callbacks, impl);
+ if (impl->version >= 3) {
+ buf->msg.n_fds = 0;
+ buf->msg.fds = &buf->fds[buf->n_fds];
+ } else {
+ buf->msg.n_fds = buf->n_fds;
+ buf->msg.fds = &buf->fds[0];
+ }
+
+ buf->msg.seq = buf->seq;
+ if (msg)
+ *msg = &buf->msg;
+ return &impl->builder;
+}
+
+int
+pw_protocol_native_connection_end(struct pw_protocol_native_connection *conn,
+ struct spa_pod_builder *builder)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ uint32_t *p, size = builder->state.offset;
+ struct buffer *buf = &impl->out;
+ int res;
+
+ if ((p = connection_ensure_size(conn, buf, impl->hdr_size + size)) == NULL)
+ return -errno;
+
+ p[0] = buf->msg.id;
+ p[1] = (buf->msg.opcode << 24) | (size & 0xffffff);
+ if (impl->version >= 3) {
+ p[2] = buf->msg.seq;
+ p[3] = buf->msg.n_fds;
+ }
+
+ buf->buffer_size += impl->hdr_size + size;
+ if (impl->version >= 3)
+ buf->n_fds += buf->msg.n_fds;
+ else
+ buf->n_fds = buf->msg.n_fds;
+
+ if (mod_topic_connection->level >= SPA_LOG_LEVEL_DEBUG) {
+ pw_logt_debug(mod_topic_connection,
+ ">>>>>>>>> out: id:%d op:%d size:%d seq:%d",
+ buf->msg.id, buf->msg.opcode, size, buf->msg.seq);
+ spa_debug_pod(0, NULL, SPA_PTROFF(p, impl->hdr_size, struct spa_pod));
+ pw_logt_debug(mod_topic_connection,
+ ">>>>>>>>> out: done");
+ }
+
+ buf->seq = (buf->seq + 1) & SPA_ASYNC_SEQ_MASK;
+ res = SPA_RESULT_RETURN_ASYNC(buf->msg.seq);
+
+ spa_hook_list_call(&conn->listener_list,
+ struct pw_protocol_native_connection_events, need_flush, 0);
+
+ return res;
+}
+
+/** Flush the connection object
+ *
+ * \param conn the connection object
+ * \return 0 on success < 0 error code on error
+ *
+ * Write the queued messages on the connection to the socket
+ *
+ * \memberof pw_protocol_native_connection
+ */
+int pw_protocol_native_connection_flush(struct pw_protocol_native_connection *conn)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+ ssize_t sent, outsize;
+ struct msghdr msg = { 0 };
+ struct iovec iov[1];
+ struct cmsghdr *cmsg;
+ union {
+ char cmsgbuf[CMSG_SPACE(MAX_FDS_MSG * sizeof(int))];
+ struct cmsghdr align;
+ } cmsgbuf;
+ int res = 0, *fds;
+ uint32_t fds_len, to_close, n_fds, outfds, i;
+ struct buffer *buf;
+ void *data;
+ size_t size;
+
+ buf = &impl->out;
+ data = buf->buffer_data;
+ size = buf->buffer_size;
+ fds = buf->fds;
+ n_fds = buf->n_fds;
+ to_close = 0;
+
+ while (size > 0) {
+ if (n_fds > MAX_FDS_MSG) {
+ outfds = MAX_FDS_MSG;
+ outsize = SPA_MIN(sizeof(uint32_t), size);
+ } else {
+ outfds = n_fds;
+ outsize = size;
+ }
+
+ fds_len = outfds * sizeof(int);
+
+ iov[0].iov_base = data;
+ iov[0].iov_len = outsize;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ if (outfds > 0) {
+ msg.msg_control = &cmsgbuf;
+ msg.msg_controllen = CMSG_SPACE(fds_len);
+ cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(fds_len);
+ memcpy(CMSG_DATA(cmsg), fds, fds_len);
+ msg.msg_controllen = cmsg->cmsg_len;
+ } else {
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ }
+
+ while (true) {
+ sent = sendmsg(conn->fd, &msg, MSG_NOSIGNAL | MSG_DONTWAIT);
+ if (sent < 0) {
+ if (errno == EINTR)
+ continue;
+ else {
+ res = -errno;
+ goto exit;
+ }
+ }
+ break;
+ }
+ pw_log_trace("connection %p: %d written %zd bytes and %u fds", conn, conn->fd, sent,
+ outfds);
+
+ size -= sent;
+ data = SPA_PTROFF(data, sent, void);
+ n_fds -= outfds;
+ fds += outfds;
+ to_close += outfds;
+ }
+
+ res = 0;
+
+exit:
+ if (size > 0)
+ memmove(buf->buffer_data, data, size);
+ buf->buffer_size = size;
+ for (i = 0; i < to_close; i++) {
+ pw_log_debug("%p: close fd:%d", conn, buf->fds[i]);
+ close(buf->fds[i]);
+ }
+ if (n_fds > 0)
+ memmove(buf->fds, fds, n_fds * sizeof(int));
+ buf->n_fds = n_fds;
+ return res;
+}
+
+/** Clear the connection object
+ *
+ * \param conn the connection object
+ * \return 0 on success
+ *
+ * Remove all queued messages from \a conn
+ *
+ * \memberof pw_protocol_native_connection
+ */
+int pw_protocol_native_connection_clear(struct pw_protocol_native_connection *conn)
+{
+ struct impl *impl = SPA_CONTAINER_OF(conn, struct impl, this);
+
+ clear_buffer(&impl->out, true);
+ clear_buffer(&impl->in, true);
+
+ return 0;
+}
diff --git a/src/modules/module-protocol-native/connection.h b/src/modules/module-protocol-native/connection.h
new file mode 100644
index 0000000..d93829c
--- /dev/null
+++ b/src/modules/module-protocol-native/connection.h
@@ -0,0 +1,112 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_PROTOCOL_NATIVE_CONNECTION_H
+#define PIPEWIRE_PROTOCOL_NATIVE_CONNECTION_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+#include <pipewire/extensions/protocol-native.h>
+
+struct pw_protocol_native_connection_events {
+#define PW_VERSION_PROTOCOL_NATIVE_CONNECTION_EVENTS 0
+ uint32_t version;
+
+ void (*destroy) (void *data);
+
+ void (*error) (void *data, int error);
+
+ void (*need_flush) (void *data);
+
+ void (*start) (void *data, uint32_t version);
+};
+
+/** \class pw_protocol_native_connection
+ *
+ * \brief Manages the connection between client and server
+ *
+ * The \ref pw_protocol_native_connection handles the connection between client
+ * and server on a given socket.
+ */
+struct pw_protocol_native_connection {
+ int fd; /**< the socket */
+
+ struct spa_hook_list listener_list;
+};
+
+static inline void
+pw_protocol_native_connection_add_listener(struct pw_protocol_native_connection *conn,
+ struct spa_hook *listener,
+ const struct pw_protocol_native_connection_events *events,
+ void *data)
+{
+ spa_hook_list_append(&conn->listener_list, listener, events, data);
+}
+
+struct pw_protocol_native_connection *
+pw_protocol_native_connection_new(struct pw_context *context, int fd);
+
+int pw_protocol_native_connection_set_fd(struct pw_protocol_native_connection *conn, int fd);
+
+void
+pw_protocol_native_connection_destroy(struct pw_protocol_native_connection *conn);
+
+int
+pw_protocol_native_connection_get_next(struct pw_protocol_native_connection *conn,
+ const struct pw_protocol_native_message **msg);
+
+uint32_t pw_protocol_native_connection_add_fd(struct pw_protocol_native_connection *conn, int fd);
+int pw_protocol_native_connection_get_fd(struct pw_protocol_native_connection *conn, uint32_t index);
+
+struct spa_pod_builder *
+pw_protocol_native_connection_begin(struct pw_protocol_native_connection *conn,
+ uint32_t id, uint8_t opcode,
+ struct pw_protocol_native_message **msg);
+
+int
+pw_protocol_native_connection_end(struct pw_protocol_native_connection *conn,
+ struct spa_pod_builder *builder);
+
+int
+pw_protocol_native_connection_flush(struct pw_protocol_native_connection *conn);
+
+int
+pw_protocol_native_connection_clear(struct pw_protocol_native_connection *conn);
+
+void pw_protocol_native_connection_enter(struct pw_protocol_native_connection *conn);
+void pw_protocol_native_connection_leave(struct pw_protocol_native_connection *conn);
+
+struct spa_pod *pw_protocol_native_connection_get_footer(struct pw_protocol_native_connection *conn,
+ const struct pw_protocol_native_message *msg);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_PROTOCOL_NATIVE_CONNECTION_H */
diff --git a/src/modules/module-protocol-native/defs.h b/src/modules/module-protocol-native/defs.h
new file mode 100644
index 0000000..dc3c625
--- /dev/null
+++ b/src/modules/module-protocol-native/defs.h
@@ -0,0 +1,49 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+int pw_protocol_native_connect_local_socket(struct pw_protocol_client *client,
+ const struct spa_dict *props,
+ void (*done_callback) (void *data, int res),
+ void *data);
+int pw_protocol_native_connect_portal_screencast(struct pw_protocol_client *client,
+ const struct spa_dict *props,
+ void (*done_callback) (void *data, int res),
+ void *data);
+
+static inline void *get_first_pod_from_data(void *data, uint32_t maxsize, uint64_t offset)
+{
+ void *pod;
+ if (maxsize <= offset)
+ return NULL;
+
+ /* spa_pod_parser_advance() rounds up, so round down here to compensate */
+ maxsize = SPA_ROUND_DOWN_N(maxsize - offset, 8);
+ if (maxsize < sizeof(struct spa_pod))
+ return NULL;
+
+ pod = SPA_PTROFF(data, offset, void);
+ if (SPA_POD_BODY_SIZE(pod) > maxsize - sizeof(struct spa_pod))
+ return NULL;
+ return pod;
+}
diff --git a/src/modules/module-protocol-native/local-socket.c b/src/modules/module-protocol-native/local-socket.c
new file mode 100644
index 0000000..cbae203
--- /dev/null
+++ b/src/modules/module-protocol-native/local-socket.c
@@ -0,0 +1,169 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/file.h>
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#include <pipewire/pipewire.h>
+
+#define DEFAULT_SYSTEM_RUNTIME_DIR "/run/pipewire"
+
+PW_LOG_TOPIC_EXTERN(mod_topic);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+static const char *
+get_remote(const struct spa_dict *props)
+{
+ const char *name;
+
+ name = getenv("PIPEWIRE_REMOTE");
+ if ((name == NULL || name[0] == '\0') && props)
+ name = spa_dict_lookup(props, PW_KEY_REMOTE_NAME);
+ if (name == NULL || name[0] == '\0')
+ name = PW_DEFAULT_REMOTE;
+ return name;
+}
+
+static const char *
+get_runtime_dir(void)
+{
+ const char *runtime_dir;
+
+ runtime_dir = getenv("PIPEWIRE_RUNTIME_DIR");
+ if (runtime_dir == NULL)
+ runtime_dir = getenv("XDG_RUNTIME_DIR");
+ if (runtime_dir == NULL)
+ runtime_dir = getenv("USERPROFILE");
+ return runtime_dir;
+}
+
+static const char *
+get_system_dir(void)
+{
+ return DEFAULT_SYSTEM_RUNTIME_DIR;
+}
+
+static int try_connect(struct pw_protocol_client *client,
+ const char *runtime_dir, const char *name,
+ void (*done_callback) (void *data, int res),
+ void *data)
+{
+ struct sockaddr_un addr;
+ socklen_t size;
+ int res, name_size, fd;
+
+ pw_log_info("connecting to '%s' runtime_dir:%s", name, runtime_dir);
+
+ if ((fd = socket(PF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
+ res = -errno;
+ goto error;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_LOCAL;
+ if (runtime_dir == NULL)
+ name_size = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", name) + 1;
+ else
+ name_size = snprintf(addr.sun_path, sizeof(addr.sun_path), "%s/%s", runtime_dir, name) + 1;
+
+ 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",
+ client, name, (int) sizeof(addr.sun_path));
+ else
+ pw_log_error("client %p: socket path \"%s/%s\" plus null terminator exceeds %i bytes",
+ client, runtime_dir, name, (int) sizeof(addr.sun_path));
+ res = -ENAMETOOLONG;
+ goto error_close;
+ };
+
+ size = offsetof(struct sockaddr_un, sun_path) + name_size;
+
+ if (connect(fd, (struct sockaddr *) &addr, size) < 0) {
+ pw_log_debug("connect to '%s' failed: %m", name);
+ if (errno == ENOENT)
+ errno = EHOSTDOWN;
+ if (errno == EAGAIN) {
+ pw_log_info("client %p: connect pending, fd %d", client, fd);
+ } else {
+ res = -errno;
+ goto error_close;
+ }
+ }
+
+ res = pw_protocol_client_connect_fd(client, fd, true);
+
+ if (done_callback)
+ done_callback(data, res);
+
+ return res;
+
+error_close:
+ close(fd);
+error:
+ return res;
+}
+
+int pw_protocol_native_connect_local_socket(struct pw_protocol_client *client,
+ const struct spa_dict *props,
+ void (*done_callback) (void *data, int res),
+ void *data)
+{
+ const char *runtime_dir, *name;
+ int res;
+
+ name = get_remote(props);
+ if (name == NULL)
+ return -EINVAL;
+
+ if (name[0] == '/') {
+ res = try_connect(client, NULL, name, done_callback, data);
+ } else {
+ runtime_dir = get_runtime_dir();
+ if (runtime_dir != NULL) {
+ res = try_connect(client, runtime_dir, name, done_callback, data);
+ if (res >= 0)
+ goto exit;
+ }
+ runtime_dir = get_system_dir();
+ if (runtime_dir != NULL)
+ res = try_connect(client, runtime_dir, name, done_callback, data);
+ }
+exit:
+ return res;
+}
diff --git a/src/modules/module-protocol-native/portal-screencast.c b/src/modules/module-protocol-native/portal-screencast.c
new file mode 100644
index 0000000..7cb6614
--- /dev/null
+++ b/src/modules/module-protocol-native/portal-screencast.c
@@ -0,0 +1,41 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/socket.h>
+
+#include <pipewire/pipewire.h>
+
+int pw_protocol_native_connect_portal_screencast(struct pw_protocol_client *client,
+ const struct spa_dict *props,
+ void (*done_callback) (void *data, int res),
+ void *data)
+{
+ return -ENOTSUP;
+}
diff --git a/src/modules/module-protocol-native/protocol-footer.c b/src/modules/module-protocol-native/protocol-footer.c
new file mode 100644
index 0000000..9b6fe62
--- /dev/null
+++ b/src/modules/module-protocol-native/protocol-footer.c
@@ -0,0 +1,152 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+
+#include <spa/pod/builder.h>
+#include <spa/pod/parser.h>
+#include <spa/utils/result.h>
+
+#include <pipewire/private.h>
+
+#include "connection.h"
+#include "protocol-footer.h"
+#include "defs.h"
+
+PW_LOG_TOPIC_EXTERN(mod_topic);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct footer_builder {
+ struct spa_pod_builder *builder;
+ struct spa_pod_frame outer;
+ struct spa_pod_frame inner;
+ unsigned int started:1;
+};
+
+#define FOOTER_BUILDER_INIT(builder) ((struct footer_builder) { (builder) })
+
+static void start_footer_entry(struct footer_builder *fb, uint32_t opcode)
+{
+ if (!fb->started) {
+ spa_pod_builder_push_struct(fb->builder, &fb->outer);
+ fb->started = true;
+ }
+
+ spa_pod_builder_id(fb->builder, opcode);
+ spa_pod_builder_push_struct(fb->builder, &fb->inner);
+}
+
+static void end_footer_entry(struct footer_builder *fb)
+{
+ spa_pod_builder_pop(fb->builder, &fb->inner);
+}
+
+static void end_footer(struct footer_builder *fb)
+{
+ if (!fb->started)
+ return;
+
+ spa_pod_builder_pop(fb->builder, &fb->outer);
+}
+
+void marshal_core_footers(struct footer_core_global_state *state, struct pw_core *core,
+ struct spa_pod_builder *builder)
+{
+ struct footer_builder fb = FOOTER_BUILDER_INIT(builder);
+
+ if (core->recv_generation != state->last_recv_generation) {
+ state->last_recv_generation = core->recv_generation;
+
+ pw_log_trace("core %p: send client registry generation:%"PRIu64,
+ core, core->recv_generation);
+
+ start_footer_entry(&fb, FOOTER_CLIENT_OPCODE_GENERATION);
+ spa_pod_builder_long(fb.builder, core->recv_generation);
+ end_footer_entry(&fb);
+ }
+
+ end_footer(&fb);
+}
+
+void marshal_client_footers(struct footer_client_global_state *state, struct pw_impl_client *client,
+ struct spa_pod_builder *builder)
+{
+ struct footer_builder fb = FOOTER_BUILDER_INIT(builder);
+
+ if (client->context->generation != client->sent_generation) {
+ client->sent_generation = client->context->generation;
+
+ pw_log_trace("impl-client %p: send server registry generation:%"PRIu64,
+ client, client->context->generation);
+
+ start_footer_entry(&fb, FOOTER_CORE_OPCODE_GENERATION);
+ spa_pod_builder_long(fb.builder, client->context->generation);
+ end_footer_entry(&fb);
+ }
+
+ end_footer(&fb);
+}
+
+int demarshal_core_generation(void *object, struct spa_pod_parser *parser)
+{
+ struct pw_core *core = object;
+ int64_t generation;
+
+ if (spa_pod_parser_get_long(parser, &generation) < 0)
+ return -EINVAL;
+
+ core->recv_generation = SPA_MAX(core->recv_generation,
+ (uint64_t)generation);
+
+ pw_log_trace("core %p: recv server registry generation:%"PRIu64,
+ core, generation);
+
+ return 0;
+}
+
+int demarshal_client_generation(void *object, struct spa_pod_parser *parser)
+{
+ struct pw_impl_client *client = object;
+ int64_t generation;
+
+ if (spa_pod_parser_get_long(parser, &generation) < 0)
+ return -EINVAL;
+
+ client->recv_generation = SPA_MAX(client->recv_generation,
+ (uint64_t)generation);
+
+ pw_log_trace("impl-client %p: recv client registry generation:%"PRIu64,
+ client, generation);
+
+ return 0;
+}
+
+const struct footer_demarshal footer_core_demarshal[FOOTER_CORE_OPCODE_LAST] = {
+ [FOOTER_CORE_OPCODE_GENERATION] = (struct footer_demarshal){ .demarshal = demarshal_core_generation },
+};
+
+const struct footer_demarshal footer_client_demarshal[FOOTER_CLIENT_OPCODE_LAST] = {
+ [FOOTER_CLIENT_OPCODE_GENERATION] = (struct footer_demarshal){ .demarshal = demarshal_client_generation },
+};
diff --git a/src/modules/module-protocol-native/protocol-footer.h b/src/modules/module-protocol-native/protocol-footer.h
new file mode 100644
index 0000000..bdbec04
--- /dev/null
+++ b/src/modules/module-protocol-native/protocol-footer.h
@@ -0,0 +1,59 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/*
+ * Protocol footer.
+ *
+ * For passing around general state data that is not associated with
+ * messages sent to objects.
+ */
+
+enum {
+ FOOTER_CORE_OPCODE_GENERATION = 0,
+ FOOTER_CORE_OPCODE_LAST
+};
+
+enum {
+ FOOTER_CLIENT_OPCODE_GENERATION = 0,
+ FOOTER_CLIENT_OPCODE_LAST
+};
+
+struct footer_core_global_state {
+ uint64_t last_recv_generation;
+};
+
+struct footer_client_global_state {
+};
+
+struct footer_demarshal {
+ int (*demarshal)(void *object, struct spa_pod_parser *parser);
+};
+
+extern const struct footer_demarshal footer_core_demarshal[FOOTER_CORE_OPCODE_LAST];
+extern const struct footer_demarshal footer_client_demarshal[FOOTER_CLIENT_OPCODE_LAST];
+
+void marshal_core_footers(struct footer_core_global_state *state, struct pw_core *core,
+ struct spa_pod_builder *builder);
+void marshal_client_footers(struct footer_client_global_state *state, struct pw_impl_client *client,
+ struct spa_pod_builder *builder);
diff --git a/src/modules/module-protocol-native/protocol-native.c b/src/modules/module-protocol-native/protocol-native.c
new file mode 100644
index 0000000..1c2d83e
--- /dev/null
+++ b/src/modules/module-protocol-native/protocol-native.c
@@ -0,0 +1,2236 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+
+#include <spa/pod/builder.h>
+#include <spa/pod/parser.h>
+#include <spa/utils/result.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/protocol-native.h>
+
+#include "connection.h"
+
+#define MAX_DICT 1024
+#define MAX_PARAM_INFO 128
+#define MAX_PERMISSIONS 4096
+
+PW_LOG_TOPIC_EXTERN(mod_topic);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+static int core_method_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_core_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static int core_method_marshal_hello(void *object, uint32_t version)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CORE_METHOD_HELLO, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(version));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int core_method_marshal_sync(void *object, uint32_t id, int seq)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CORE_METHOD_SYNC, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id),
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int core_method_marshal_pong(void *object, uint32_t id, int seq)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CORE_METHOD_PONG, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id),
+ SPA_POD_Int(seq));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int core_method_marshal_error(void *object, uint32_t id, int seq, int res, const char *error)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CORE_METHOD_ERROR, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id),
+ SPA_POD_Int(seq),
+ SPA_POD_Int(res),
+ SPA_POD_String(error));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static struct pw_registry * core_method_marshal_get_registry(void *object,
+ uint32_t version, size_t user_data_size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ struct pw_proxy *res;
+ uint32_t new_id;
+
+ res = pw_proxy_new(object, PW_TYPE_INTERFACE_Registry, version, user_data_size);
+ if (res == NULL)
+ return NULL;
+
+ new_id = pw_proxy_get_id(res);
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CORE_METHOD_GET_REGISTRY, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(version),
+ SPA_POD_Int(new_id));
+
+ pw_protocol_native_end_proxy(proxy, b);
+
+ return (struct pw_registry *) res;
+}
+
+static inline void push_item(struct spa_pod_builder *b, const struct spa_dict_item *item)
+{
+ const char *str;
+ spa_pod_builder_string(b, item->key);
+ str = item->value;
+ if (spa_strstartswith(str, "pointer:"))
+ str = "";
+ spa_pod_builder_string(b, str);
+}
+
+static void push_dict(struct spa_pod_builder *b, const struct spa_dict *dict)
+{
+ uint32_t i, n_items;
+ struct spa_pod_frame f;
+
+ n_items = dict ? dict->n_items : 0;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_int(b, n_items);
+ for (i = 0; i < n_items; i++)
+ push_item(b, &dict->items[i]);
+ spa_pod_builder_pop(b, &f);
+}
+
+static inline int parse_item(struct spa_pod_parser *prs, struct spa_dict_item *item)
+{
+ int res;
+ if ((res = spa_pod_parser_get(prs,
+ SPA_POD_String(&item->key),
+ SPA_POD_String(&item->value),
+ NULL)) < 0)
+ return res;
+ if (spa_strstartswith(item->value, "pointer:"))
+ item->value = "";
+ return 0;
+}
+
+#define parse_dict(prs,d) \
+do { \
+ if (spa_pod_parser_get(prs, \
+ SPA_POD_Int(&(d)->n_items), NULL) < 0) \
+ return -EINVAL; \
+ (d)->items = NULL; \
+ if ((d)->n_items > 0) { \
+ uint32_t i; \
+ if ((d)->n_items > MAX_DICT) \
+ return -ENOSPC; \
+ (d)->items = alloca((d)->n_items * sizeof(struct spa_dict_item)); \
+ for (i = 0; i < (d)->n_items; i++) { \
+ if (parse_item(prs, (struct spa_dict_item *) &(d)->items[i]) < 0) \
+ return -EINVAL; \
+ } \
+ } \
+} while(0)
+
+#define parse_dict_struct(prs,f,dict) \
+do { \
+ if (spa_pod_parser_push_struct(prs, f) < 0) \
+ return -EINVAL; \
+ parse_dict(prs, dict); \
+ spa_pod_parser_pop(prs, f); \
+} while(0)
+
+static void push_params(struct spa_pod_builder *b, uint32_t n_params,
+ const struct spa_param_info *params)
+{
+ uint32_t i;
+ struct spa_pod_frame f;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_int(b, n_params);
+ for (i = 0; i < n_params; i++) {
+ spa_pod_builder_id(b, params[i].id);
+ spa_pod_builder_int(b, params[i].flags);
+ }
+ spa_pod_builder_pop(b, &f);
+}
+
+
+#define parse_params_struct(prs,f,params,n_params) \
+do { \
+ if (spa_pod_parser_push_struct(prs, f) < 0 || \
+ spa_pod_parser_get(prs, \
+ SPA_POD_Int(&(n_params)), NULL) < 0) \
+ return -EINVAL; \
+ (params) = NULL; \
+ if ((n_params) > 0) { \
+ uint32_t i; \
+ if ((n_params) > MAX_PARAM_INFO) \
+ return -ENOSPC; \
+ (params) = alloca((n_params) * sizeof(struct spa_param_info)); \
+ for (i = 0; i < (n_params); i++) { \
+ if (spa_pod_parser_get(prs, \
+ SPA_POD_Id(&(params)[i].id), \
+ SPA_POD_Int(&(params)[i].flags), NULL) < 0) \
+ return -EINVAL; \
+ } \
+ } \
+ spa_pod_parser_pop(prs, f); \
+} while(0)
+
+
+#define parse_permissions_struct(prs,f,n_permissions,permissions) \
+do { \
+ if (spa_pod_parser_push_struct(prs, f) < 0 || \
+ spa_pod_parser_get(prs, \
+ SPA_POD_Int(&(n_permissions)), NULL) < 0) \
+ return -EINVAL; \
+ (permissions) = NULL; \
+ if ((n_permissions) > 0) { \
+ uint32_t i; \
+ if ((n_permissions) > MAX_PERMISSIONS) \
+ return -ENOSPC; \
+ (permissions) = alloca((n_permissions) * sizeof(struct pw_permission)); \
+ for (i = 0; i < (n_permissions); i++) { \
+ if (spa_pod_parser_get(prs, \
+ SPA_POD_Int(&(permissions)[i].id), \
+ SPA_POD_Int(&(permissions)[i].permissions), NULL) < 0) \
+ return -EINVAL; \
+ } \
+ } \
+ spa_pod_parser_pop(prs, f); \
+} while(0)
+
+static void *
+core_method_marshal_create_object(void *object,
+ const char *factory_name,
+ const char *type, uint32_t version,
+ const struct spa_dict *props, size_t user_data_size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ struct pw_proxy *res;
+ uint32_t new_id;
+
+ res = pw_proxy_new(object, type, version, user_data_size);
+ if (res == NULL)
+ return NULL;
+
+ new_id = pw_proxy_get_id(res);
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CORE_METHOD_CREATE_OBJECT, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_String(factory_name),
+ SPA_POD_String(type),
+ SPA_POD_Int(version),
+ NULL);
+ push_dict(b, props);
+ spa_pod_builder_int(b, new_id);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_proxy(proxy, b);
+
+ return (void *)res;
+}
+
+static int
+core_method_marshal_destroy(void *object, void *p)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ uint32_t id = pw_proxy_get_id(p);
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CORE_METHOD_DESTROY, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int core_event_demarshal_info(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_core_info info = { .props = &props };
+ struct spa_pod_frame f[2];
+ struct spa_pod_parser prs;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0)
+ return -EINVAL;
+ if (spa_pod_parser_get(&prs,
+ SPA_POD_Int(&info.id),
+ SPA_POD_Int(&info.cookie),
+ SPA_POD_String(&info.user_name),
+ SPA_POD_String(&info.host_name),
+ SPA_POD_String(&info.version),
+ SPA_POD_String(&info.name),
+ SPA_POD_Long(&info.change_mask), NULL) < 0)
+ return -EINVAL;
+
+
+ parse_dict_struct(&prs, &f[1], &props);
+
+ return pw_proxy_notify(proxy, struct pw_core_events, info, 0, &info);
+}
+
+static int core_event_demarshal_done(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, seq;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id),
+ SPA_POD_Int(&seq)) < 0)
+ return -EINVAL;
+
+ if (id == SPA_ID_INVALID)
+ return 0;
+
+ return pw_proxy_notify(proxy, struct pw_core_events, done, 0, id, seq);
+}
+
+static int core_event_demarshal_ping(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, seq;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id),
+ SPA_POD_Int(&seq)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_core_events, ping, 0, id, seq);
+}
+
+static int core_event_demarshal_error(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, res;
+ int seq;
+ const char *error;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id),
+ SPA_POD_Int(&seq),
+ SPA_POD_Int(&res),
+ SPA_POD_String(&error)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_core_events, error, 0, id, seq, res, error);
+}
+
+static int core_event_demarshal_remove_id(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs, SPA_POD_Int(&id)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_core_events, remove_id, 0, id);
+}
+
+static int core_event_demarshal_bound_id(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, global_id;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id),
+ SPA_POD_Int(&global_id)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_core_events, bound_id, 0, id, global_id);
+}
+
+static int core_event_demarshal_add_mem(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, type, flags;
+ int64_t idx;
+ int fd;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id),
+ SPA_POD_Id(&type),
+ SPA_POD_Fd(&idx),
+ SPA_POD_Int(&flags)) < 0)
+ return -EINVAL;
+
+ fd = pw_protocol_native_get_proxy_fd(proxy, idx);
+
+ return pw_proxy_notify(proxy, struct pw_core_events, add_mem, 0, id, type, fd, flags);
+}
+
+static int core_event_demarshal_remove_mem(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_core_events, remove_mem, 0, id);
+}
+
+static void core_event_marshal_info(void *data, const struct pw_core_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CORE_EVENT_INFO, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->id),
+ SPA_POD_Int(info->cookie),
+ SPA_POD_String(info->user_name),
+ SPA_POD_String(info->host_name),
+ SPA_POD_String(info->version),
+ SPA_POD_String(info->name),
+ SPA_POD_Long(info->change_mask),
+ NULL);
+ push_dict(b, info->change_mask & PW_CORE_CHANGE_MASK_PROPS ? info->props : NULL);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void core_event_marshal_done(void *data, uint32_t id, int seq)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CORE_EVENT_DONE, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id),
+ SPA_POD_Int(seq));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void core_event_marshal_ping(void *data, uint32_t id, int seq)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct pw_protocol_native_message *msg;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CORE_EVENT_PING, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id),
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void core_event_marshal_error(void *data, uint32_t id, int seq, int res, const char *error)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CORE_EVENT_ERROR, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id),
+ SPA_POD_Int(seq),
+ SPA_POD_Int(res),
+ SPA_POD_String(error));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void core_event_marshal_remove_id(void *data, uint32_t id)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CORE_EVENT_REMOVE_ID, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void core_event_marshal_bound_id(void *data, uint32_t id, uint32_t global_id)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CORE_EVENT_BOUND_ID, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id),
+ SPA_POD_Int(global_id));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void core_event_marshal_add_mem(void *data, uint32_t id, uint32_t type, int fd, uint32_t flags)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CORE_EVENT_ADD_MEM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id),
+ SPA_POD_Id(type),
+ SPA_POD_Fd(pw_protocol_native_add_resource_fd(resource, fd)),
+ SPA_POD_Int(flags));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void core_event_marshal_remove_mem(void *data, uint32_t id)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CORE_EVENT_REMOVE_MEM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int core_method_demarshal_hello(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t version;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&version)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_core_methods, hello, 0, version);
+}
+
+static int core_method_demarshal_sync(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, seq;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id),
+ SPA_POD_Int(&seq)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_core_methods, sync, 0, id, seq);
+}
+
+static int core_method_demarshal_pong(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, seq;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id),
+ SPA_POD_Int(&seq)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_core_methods, pong, 0, id, seq);
+}
+
+static int core_method_demarshal_error(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, res;
+ int seq;
+ const char *error;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id),
+ SPA_POD_Int(&seq),
+ SPA_POD_Int(&res),
+ SPA_POD_String(&error)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_core_methods, error, 0, id, seq, res, error);
+}
+
+static int core_method_demarshal_get_registry(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ int32_t version, new_id;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&version),
+ SPA_POD_Int(&new_id)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_core_methods, get_registry, 0, version, new_id);
+}
+
+static int core_method_demarshal_create_object(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[2];
+ uint32_t version, new_id;
+ const char *factory_name, *type;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_String(&factory_name),
+ SPA_POD_String(&type),
+ SPA_POD_Int(&version),
+ NULL) < 0)
+ return -EINVAL;
+
+ parse_dict_struct(&prs, &f[1], &props);
+
+ if (spa_pod_parser_get(&prs,
+ SPA_POD_Int(&new_id), NULL) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_core_methods, create_object, 0, factory_name,
+ type, version,
+ &props, new_id);
+}
+
+static int core_method_demarshal_destroy(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct pw_resource *r;
+ struct spa_pod_parser prs;
+ uint32_t id;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id)) < 0)
+ return -EINVAL;
+
+ pw_log_debug("client %p: destroy resource %u", client, id);
+
+ if ((r = pw_impl_client_find_resource(client, id)) == NULL)
+ goto no_resource;
+
+ return pw_resource_notify(resource, struct pw_core_methods, destroy, 0, r);
+
+ no_resource:
+ pw_log_debug("client %p: unknown resource %u op:%u", client, id, msg->opcode);
+ pw_resource_errorf(resource, -ENOENT, "unknown resource %d op:%u", id, msg->opcode);
+ return 0;
+}
+
+static int registry_method_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_registry_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static void registry_marshal_global(void *data, uint32_t id, uint32_t permissions,
+ const char *type, uint32_t version, const struct spa_dict *props)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_REGISTRY_EVENT_GLOBAL, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(id),
+ SPA_POD_Int(permissions),
+ SPA_POD_String(type),
+ SPA_POD_Int(version),
+ NULL);
+ push_dict(b, props);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void registry_marshal_global_remove(void *data, uint32_t id)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_REGISTRY_EVENT_GLOBAL_REMOVE, NULL);
+
+ spa_pod_builder_add_struct(b, SPA_POD_Int(id));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int registry_demarshal_bind(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, version, new_id;
+ char *type;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id),
+ SPA_POD_String(&type),
+ SPA_POD_Int(&version),
+ SPA_POD_Int(&new_id)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_registry_methods, bind, 0, id, type, version, new_id);
+}
+
+static int registry_demarshal_destroy(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_registry_methods, destroy, 0, id);
+}
+
+static int module_method_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_module_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static void module_marshal_info(void *data, const struct pw_module_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_MODULE_EVENT_INFO, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->id),
+ SPA_POD_String(info->name),
+ SPA_POD_String(info->filename),
+ SPA_POD_String(info->args),
+ SPA_POD_Long(info->change_mask),
+ NULL);
+ push_dict(b, info->change_mask & PW_MODULE_CHANGE_MASK_PROPS ? info->props : NULL);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int module_demarshal_info(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[2];
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_module_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&info.id),
+ SPA_POD_String(&info.name),
+ SPA_POD_String(&info.filename),
+ SPA_POD_String(&info.args),
+ SPA_POD_Long(&info.change_mask), NULL) < 0)
+ return -EINVAL;
+
+ parse_dict_struct(&prs, &f[1], &props);
+
+ return pw_proxy_notify(proxy, struct pw_module_events, info, 0, &info);
+}
+
+static int device_method_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_device_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static void device_marshal_info(void *data, const struct pw_device_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_DEVICE_EVENT_INFO, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->id),
+ SPA_POD_Long(info->change_mask),
+ NULL);
+ push_dict(b, info->change_mask & PW_DEVICE_CHANGE_MASK_PROPS ? info->props : NULL);
+ push_params(b, info->n_params, info->params);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int device_demarshal_info(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[2];
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_device_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&info.id),
+ SPA_POD_Long(&info.change_mask), NULL) < 0)
+ return -EINVAL;
+
+ parse_dict_struct(&prs, &f[1], &props);
+ parse_params_struct(&prs, &f[1], info.params, info.n_params);
+
+ return pw_proxy_notify(proxy, struct pw_device_events, info, 0, &info);
+}
+
+static void device_marshal_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_DEVICE_EVENT_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(seq),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(next),
+ SPA_POD_Pod(param));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int device_demarshal_param(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, index, next;
+ int seq;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&next),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_device_events, param, 0,
+ seq, id, index, next, param);
+}
+
+static int device_marshal_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_DEVICE_METHOD_SUBSCRIBE_PARAMS, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int device_demarshal_subscribe_params(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t csize, ctype, n_ids;
+ uint32_t *ids;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
+ return -EINVAL;
+
+ if (ctype != SPA_TYPE_Id)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_device_methods, subscribe_params, 0,
+ ids, n_ids);
+}
+
+static int device_marshal_enum_params(void *object, int seq,
+ uint32_t id, uint32_t index, uint32_t num, const struct spa_pod *filter)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_DEVICE_METHOD_ENUM_PARAMS, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(num),
+ SPA_POD_Pod(filter));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int device_demarshal_enum_params(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, index, num;
+ int seq;
+ struct spa_pod *filter;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&num),
+ SPA_POD_Pod(&filter)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_device_methods, enum_params, 0,
+ seq, id, index, num, filter);
+}
+
+static int device_marshal_set_param(void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_DEVICE_METHOD_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int device_demarshal_set_param(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, flags;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_device_methods, set_param, 0, id, flags, param);
+}
+
+static int factory_method_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_factory_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static void factory_marshal_info(void *data, const struct pw_factory_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_FACTORY_EVENT_INFO, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->id),
+ SPA_POD_String(info->name),
+ SPA_POD_String(info->type),
+ SPA_POD_Int(info->version),
+ SPA_POD_Long(info->change_mask),
+ NULL);
+ push_dict(b, info->change_mask & PW_FACTORY_CHANGE_MASK_PROPS ? info->props : NULL);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int factory_demarshal_info(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[2];
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_factory_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&info.id),
+ SPA_POD_String(&info.name),
+ SPA_POD_String(&info.type),
+ SPA_POD_Int(&info.version),
+ SPA_POD_Long(&info.change_mask), NULL) < 0)
+ return -EINVAL;
+
+ parse_dict_struct(&prs, &f[1], &props);
+
+ return pw_proxy_notify(proxy, struct pw_factory_events, info, 0, &info);
+}
+
+static int node_method_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_node_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static void node_marshal_info(void *data, const struct pw_node_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_NODE_EVENT_INFO, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->id),
+ SPA_POD_Int(info->max_input_ports),
+ SPA_POD_Int(info->max_output_ports),
+ SPA_POD_Long(info->change_mask),
+ SPA_POD_Int(info->n_input_ports),
+ SPA_POD_Int(info->n_output_ports),
+ SPA_POD_Id(info->state),
+ SPA_POD_String(info->error),
+ NULL);
+ push_dict(b, info->change_mask & PW_NODE_CHANGE_MASK_PROPS ? info->props : NULL);
+ push_params(b, info->n_params, info->params);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int node_demarshal_info(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[2];
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_node_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&info.id),
+ SPA_POD_Int(&info.max_input_ports),
+ SPA_POD_Int(&info.max_output_ports),
+ SPA_POD_Long(&info.change_mask),
+ SPA_POD_Int(&info.n_input_ports),
+ SPA_POD_Int(&info.n_output_ports),
+ SPA_POD_Id(&info.state),
+ SPA_POD_String(&info.error), NULL) < 0)
+ return -EINVAL;
+
+ parse_dict_struct(&prs, &f[1], &props);
+ parse_params_struct(&prs, &f[1], info.params, info.n_params);
+
+ return pw_proxy_notify(proxy, struct pw_node_events, info, 0, &info);
+}
+
+static void node_marshal_param(void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next, const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_NODE_EVENT_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(seq),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(next),
+ SPA_POD_Pod(param));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int node_demarshal_param(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, index, next;
+ int seq;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&next),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_node_events, param, 0,
+ seq, id, index, next, param);
+}
+
+static int node_marshal_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_NODE_METHOD_SUBSCRIBE_PARAMS, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int node_demarshal_subscribe_params(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t csize, ctype, n_ids;
+ uint32_t *ids;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
+ return -EINVAL;
+
+ if (ctype != SPA_TYPE_Id)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_node_methods, subscribe_params, 0,
+ ids, n_ids);
+}
+
+static int node_marshal_enum_params(void *object, int seq, uint32_t id,
+ uint32_t index, uint32_t num, const struct spa_pod *filter)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_NODE_METHOD_ENUM_PARAMS, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(num),
+ SPA_POD_Pod(filter));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int node_demarshal_enum_params(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, index, num;
+ int seq;
+ struct spa_pod *filter;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&num),
+ SPA_POD_Pod(&filter)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_node_methods, enum_params, 0,
+ seq, id, index, num, filter);
+}
+
+static int node_marshal_set_param(void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_NODE_METHOD_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int node_demarshal_set_param(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, flags;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_node_methods, set_param, 0, id, flags, param);
+}
+
+static int node_marshal_send_command(void *object, const struct spa_command *command)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_NODE_METHOD_SEND_COMMAND, NULL);
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Pod(command));
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int node_demarshal_send_command(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ const struct spa_command *command;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Pod(&command)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_node_methods, send_command, 0, command);
+}
+
+static int port_method_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_port_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static void port_marshal_info(void *data, const struct pw_port_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_PORT_EVENT_INFO, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->id),
+ SPA_POD_Int(info->direction),
+ SPA_POD_Long(info->change_mask),
+ NULL);
+ push_dict(b, info->change_mask & PW_PORT_CHANGE_MASK_PROPS ? info->props : NULL);
+ push_params(b, info->n_params, info->params);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int port_demarshal_info(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[2];
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_port_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&info.id),
+ SPA_POD_Int(&info.direction),
+ SPA_POD_Long(&info.change_mask), NULL) < 0)
+ return -EINVAL;
+
+ parse_dict_struct(&prs, &f[1], &props);
+ parse_params_struct(&prs, &f[1], info.params, info.n_params);
+
+ return pw_proxy_notify(proxy, struct pw_port_events, info, 0, &info);
+}
+
+static void port_marshal_param(void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next, const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_PORT_EVENT_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(seq),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(next),
+ SPA_POD_Pod(param));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int port_demarshal_param(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, index, next;
+ int seq;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&next),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_port_events, param, 0,
+ seq, id, index, next, param);
+}
+
+static int port_marshal_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_PORT_METHOD_SUBSCRIBE_PARAMS, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int port_demarshal_subscribe_params(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t csize, ctype, n_ids;
+ uint32_t *ids;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
+ return -EINVAL;
+
+ if (ctype != SPA_TYPE_Id)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_port_methods, subscribe_params, 0,
+ ids, n_ids);
+}
+
+static int port_marshal_enum_params(void *object, int seq, uint32_t id,
+ uint32_t index, uint32_t num, const struct spa_pod *filter)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_PORT_METHOD_ENUM_PARAMS, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(num),
+ SPA_POD_Pod(filter));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int port_demarshal_enum_params(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, index, num;
+ int seq;
+ struct spa_pod *filter;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&num),
+ SPA_POD_Pod(&filter)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_port_methods, enum_params, 0,
+ seq, id, index, num, filter);
+}
+
+static int client_method_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_client_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static void client_marshal_info(void *data, const struct pw_client_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_EVENT_INFO, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->id),
+ SPA_POD_Long(info->change_mask),
+ NULL);
+ push_dict(b, info->change_mask & PW_CLIENT_CHANGE_MASK_PROPS ? info->props : NULL);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int client_demarshal_info(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[2];
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_client_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&info.id),
+ SPA_POD_Long(&info.change_mask), NULL) < 0)
+ return -EINVAL;
+
+ parse_dict_struct(&prs, &f[1], &props);
+
+ return pw_proxy_notify(proxy, struct pw_client_events, info, 0, &info);
+}
+
+static void client_marshal_permissions(void *data, uint32_t index, uint32_t n_permissions,
+ const struct pw_permission *permissions)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f[2];
+ uint32_t i, n = 0;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_EVENT_PERMISSIONS, NULL);
+
+ for (i = 0; i < n_permissions; i++) {
+ if (permissions[i].permissions != PW_PERM_INVALID)
+ n++;
+ }
+
+ spa_pod_builder_push_struct(b, &f[0]);
+ spa_pod_builder_int(b, index);
+ spa_pod_builder_push_struct(b, &f[1]);
+ spa_pod_builder_int(b, n);
+
+ for (i = 0; i < n_permissions; i++) {
+ if (permissions[i].permissions == PW_PERM_INVALID)
+ continue;
+ spa_pod_builder_int(b, permissions[i].id);
+ spa_pod_builder_int(b, permissions[i].permissions);
+ }
+ spa_pod_builder_pop(b, &f[1]);
+ spa_pod_builder_pop(b, &f[0]);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int client_demarshal_permissions(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct pw_permission *permissions;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[2];
+ uint32_t index, n_permissions;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&index), NULL) < 0)
+ return -EINVAL;
+
+ parse_permissions_struct(&prs, &f[1], n_permissions, permissions);
+
+ return pw_proxy_notify(proxy, struct pw_client_events, permissions, 0, index, n_permissions, permissions);
+}
+
+static int client_marshal_error(void *object, uint32_t id, int res, const char *error)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_METHOD_ERROR, NULL);
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id),
+ SPA_POD_Int(res),
+ SPA_POD_String(error));
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int client_demarshal_error(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, res;
+ const char *error;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id),
+ SPA_POD_Int(&res),
+ SPA_POD_String(&error)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_client_methods, error, 0, id, res, error);
+}
+
+static int client_marshal_get_permissions(void *object, uint32_t index, uint32_t num)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_METHOD_GET_PERMISSIONS, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(index),
+ SPA_POD_Int(num));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int client_marshal_update_properties(void *object, const struct spa_dict *props)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_METHOD_UPDATE_PROPERTIES, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ push_dict(b, props);
+ spa_pod_builder_pop(b, &f);
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int client_demarshal_update_properties(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[2];
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0)
+ return -EINVAL;
+
+ parse_dict_struct(&prs, &f[1], &props);
+
+ return pw_resource_notify(resource, struct pw_client_methods, update_properties, 0,
+ &props);
+}
+
+static int client_demarshal_get_permissions(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t index, num;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&num)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_client_methods, get_permissions, 0, index, num);
+}
+
+static int client_marshal_update_permissions(void *object, uint32_t n_permissions,
+ const struct pw_permission *permissions)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_CLIENT_METHOD_UPDATE_PERMISSIONS, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_int(b, n_permissions);
+ for (i = 0; i < n_permissions; i++) {
+ spa_pod_builder_int(b, permissions[i].id);
+ spa_pod_builder_int(b, permissions[i].permissions);
+ }
+ spa_pod_builder_pop(b, &f);
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int client_demarshal_update_permissions(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct pw_permission *permissions;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[1];
+ uint32_t n_permissions;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+
+ parse_permissions_struct(&prs, &f[0], n_permissions, permissions);
+
+ return pw_resource_notify(resource, struct pw_client_methods, update_permissions, 0,
+ n_permissions, permissions);
+}
+
+static int link_method_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_link_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static void link_marshal_info(void *data, const struct pw_link_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_LINK_EVENT_INFO, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->id),
+ SPA_POD_Int(info->output_node_id),
+ SPA_POD_Int(info->output_port_id),
+ SPA_POD_Int(info->input_node_id),
+ SPA_POD_Int(info->input_port_id),
+ SPA_POD_Long(info->change_mask),
+ SPA_POD_Int(info->state),
+ SPA_POD_String(info->error),
+ SPA_POD_Pod(info->format),
+ NULL);
+ push_dict(b, info->change_mask & PW_LINK_CHANGE_MASK_PROPS ? info->props : NULL);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int link_demarshal_info(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[2];
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_link_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&info.id),
+ SPA_POD_Int(&info.output_node_id),
+ SPA_POD_Int(&info.output_port_id),
+ SPA_POD_Int(&info.input_node_id),
+ SPA_POD_Int(&info.input_port_id),
+ SPA_POD_Long(&info.change_mask),
+ SPA_POD_Int(&info.state),
+ SPA_POD_String(&info.error),
+ SPA_POD_Pod(&info.format), NULL) < 0)
+ return -EINVAL;
+
+ parse_dict_struct(&prs, &f[1], &props);
+
+ return pw_proxy_notify(proxy, struct pw_link_events, info, 0, &info);
+}
+
+static int registry_demarshal_global(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[2];
+ uint32_t id, permissions, version;
+ char *type;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get(&prs,
+ SPA_POD_Int(&id),
+ SPA_POD_Int(&permissions),
+ SPA_POD_String(&type),
+ SPA_POD_Int(&version), NULL) < 0)
+ return -EINVAL;
+
+ parse_dict_struct(&prs, &f[1], &props);
+
+ return pw_proxy_notify(proxy, struct pw_registry_events,
+ global, 0, id, permissions, type, version, &props);
+}
+
+static int registry_demarshal_global_remove(void *data, const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_registry_events, global_remove, 0, id);
+}
+
+static void * registry_marshal_bind(void *object, uint32_t id,
+ const char *type, uint32_t version, size_t user_data_size)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ struct pw_proxy *res;
+ uint32_t new_id;
+
+ res = pw_proxy_new(object, type, version, user_data_size);
+ if (res == NULL)
+ return NULL;
+
+ new_id = pw_proxy_get_id(res);
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_REGISTRY_METHOD_BIND, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id),
+ SPA_POD_String(type),
+ SPA_POD_Int(version),
+ SPA_POD_Int(new_id));
+
+ pw_protocol_native_end_proxy(proxy, b);
+
+ return (void *) res;
+}
+
+static int registry_marshal_destroy(void *object, uint32_t id)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy, PW_REGISTRY_METHOD_DESTROY, NULL);
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id));
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static const struct pw_core_methods pw_protocol_native_core_method_marshal = {
+ PW_VERSION_CORE_METHODS,
+ .add_listener = &core_method_marshal_add_listener,
+ .hello = &core_method_marshal_hello,
+ .sync = &core_method_marshal_sync,
+ .pong = &core_method_marshal_pong,
+ .error = &core_method_marshal_error,
+ .get_registry = &core_method_marshal_get_registry,
+ .create_object = &core_method_marshal_create_object,
+ .destroy = &core_method_marshal_destroy,
+};
+
+static const struct pw_protocol_native_demarshal pw_protocol_native_core_method_demarshal[PW_CORE_METHOD_NUM] = {
+ [PW_CORE_METHOD_ADD_LISTENER] = { NULL, 0, },
+ [PW_CORE_METHOD_HELLO] = { &core_method_demarshal_hello, 0, },
+ [PW_CORE_METHOD_SYNC] = { &core_method_demarshal_sync, 0, },
+ [PW_CORE_METHOD_PONG] = { &core_method_demarshal_pong, 0, },
+ [PW_CORE_METHOD_ERROR] = { &core_method_demarshal_error, 0, },
+ [PW_CORE_METHOD_GET_REGISTRY] = { &core_method_demarshal_get_registry, 0, },
+ [PW_CORE_METHOD_CREATE_OBJECT] = { &core_method_demarshal_create_object, 0, },
+ [PW_CORE_METHOD_DESTROY] = { &core_method_demarshal_destroy, 0, }
+};
+
+static const struct pw_core_events pw_protocol_native_core_event_marshal = {
+ PW_VERSION_CORE_EVENTS,
+ .info = &core_event_marshal_info,
+ .done = &core_event_marshal_done,
+ .ping = &core_event_marshal_ping,
+ .error = &core_event_marshal_error,
+ .remove_id = &core_event_marshal_remove_id,
+ .bound_id = &core_event_marshal_bound_id,
+ .add_mem = &core_event_marshal_add_mem,
+ .remove_mem = &core_event_marshal_remove_mem,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_core_event_demarshal[PW_CORE_EVENT_NUM] =
+{
+ [PW_CORE_EVENT_INFO] = { &core_event_demarshal_info, 0, },
+ [PW_CORE_EVENT_DONE] = { &core_event_demarshal_done, 0, },
+ [PW_CORE_EVENT_PING] = { &core_event_demarshal_ping, 0, },
+ [PW_CORE_EVENT_ERROR] = { &core_event_demarshal_error, 0, },
+ [PW_CORE_EVENT_REMOVE_ID] = { &core_event_demarshal_remove_id, 0, },
+ [PW_CORE_EVENT_BOUND_ID] = { &core_event_demarshal_bound_id, 0, },
+ [PW_CORE_EVENT_ADD_MEM] = { &core_event_demarshal_add_mem, 0, },
+ [PW_CORE_EVENT_REMOVE_MEM] = { &core_event_demarshal_remove_mem, 0, },
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_core_marshal = {
+ PW_TYPE_INTERFACE_Core,
+ PW_VERSION_CORE,
+ 0,
+ PW_CORE_METHOD_NUM,
+ PW_CORE_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_core_method_marshal,
+ .server_demarshal = pw_protocol_native_core_method_demarshal,
+ .server_marshal = &pw_protocol_native_core_event_marshal,
+ .client_demarshal = pw_protocol_native_core_event_demarshal,
+};
+
+static const struct pw_registry_methods pw_protocol_native_registry_method_marshal = {
+ PW_VERSION_REGISTRY_METHODS,
+ .add_listener = &registry_method_marshal_add_listener,
+ .bind = &registry_marshal_bind,
+ .destroy = &registry_marshal_destroy,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_registry_method_demarshal[PW_REGISTRY_METHOD_NUM] =
+{
+ [PW_REGISTRY_METHOD_ADD_LISTENER] = { NULL, 0, },
+ [PW_REGISTRY_METHOD_BIND] = { &registry_demarshal_bind, 0, },
+ [PW_REGISTRY_METHOD_DESTROY] = { &registry_demarshal_destroy, 0, },
+};
+
+static const struct pw_registry_events pw_protocol_native_registry_event_marshal = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = &registry_marshal_global,
+ .global_remove = &registry_marshal_global_remove,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_registry_event_demarshal[PW_REGISTRY_EVENT_NUM] =
+{
+ [PW_REGISTRY_EVENT_GLOBAL] = { &registry_demarshal_global, 0, },
+ [PW_REGISTRY_EVENT_GLOBAL_REMOVE] = { &registry_demarshal_global_remove, 0, }
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_registry_marshal = {
+ PW_TYPE_INTERFACE_Registry,
+ PW_VERSION_REGISTRY,
+ 0,
+ PW_REGISTRY_METHOD_NUM,
+ PW_REGISTRY_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_registry_method_marshal,
+ .server_demarshal = pw_protocol_native_registry_method_demarshal,
+ .server_marshal = &pw_protocol_native_registry_event_marshal,
+ .client_demarshal = pw_protocol_native_registry_event_demarshal,
+};
+
+static const struct pw_module_events pw_protocol_native_module_event_marshal = {
+ PW_VERSION_MODULE_EVENTS,
+ .info = &module_marshal_info,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_module_event_demarshal[PW_MODULE_EVENT_NUM] =
+{
+ [PW_MODULE_EVENT_INFO] = { &module_demarshal_info, 0, },
+};
+
+
+static const struct pw_module_methods pw_protocol_native_module_method_marshal = {
+ PW_VERSION_MODULE_METHODS,
+ .add_listener = &module_method_marshal_add_listener,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_module_method_demarshal[PW_MODULE_METHOD_NUM] =
+{
+ [PW_MODULE_METHOD_ADD_LISTENER] = { NULL, 0, },
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_module_marshal = {
+ PW_TYPE_INTERFACE_Module,
+ PW_VERSION_MODULE,
+ 0,
+ PW_MODULE_METHOD_NUM,
+ PW_MODULE_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_module_method_marshal,
+ .server_demarshal = pw_protocol_native_module_method_demarshal,
+ .server_marshal = &pw_protocol_native_module_event_marshal,
+ .client_demarshal = pw_protocol_native_module_event_demarshal,
+};
+
+static const struct pw_factory_events pw_protocol_native_factory_event_marshal = {
+ PW_VERSION_FACTORY_EVENTS,
+ .info = &factory_marshal_info,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_factory_event_demarshal[PW_FACTORY_EVENT_NUM] =
+{
+ [PW_FACTORY_EVENT_INFO] = { &factory_demarshal_info, 0, },
+};
+
+static const struct pw_factory_methods pw_protocol_native_factory_method_marshal = {
+ PW_VERSION_FACTORY_METHODS,
+ .add_listener = &factory_method_marshal_add_listener,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_factory_method_demarshal[PW_FACTORY_METHOD_NUM] =
+{
+ [PW_FACTORY_METHOD_ADD_LISTENER] = { NULL, 0, },
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_factory_marshal = {
+ PW_TYPE_INTERFACE_Factory,
+ PW_VERSION_FACTORY,
+ 0,
+ PW_FACTORY_METHOD_NUM,
+ PW_FACTORY_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_factory_method_marshal,
+ .server_demarshal = pw_protocol_native_factory_method_demarshal,
+ .server_marshal = &pw_protocol_native_factory_event_marshal,
+ .client_demarshal = pw_protocol_native_factory_event_demarshal,
+};
+
+static const struct pw_device_methods pw_protocol_native_device_method_marshal = {
+ PW_VERSION_DEVICE_METHODS,
+ .add_listener = &device_method_marshal_add_listener,
+ .subscribe_params = &device_marshal_subscribe_params,
+ .enum_params = &device_marshal_enum_params,
+ .set_param = &device_marshal_set_param,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_device_method_demarshal[PW_DEVICE_METHOD_NUM] = {
+ [PW_DEVICE_METHOD_ADD_LISTENER] = { NULL, 0, },
+ [PW_DEVICE_METHOD_SUBSCRIBE_PARAMS] = { &device_demarshal_subscribe_params, 0, },
+ [PW_DEVICE_METHOD_ENUM_PARAMS] = { &device_demarshal_enum_params, 0, },
+ [PW_DEVICE_METHOD_SET_PARAM] = { &device_demarshal_set_param, PW_PERM_W, },
+};
+
+static const struct pw_device_events pw_protocol_native_device_event_marshal = {
+ PW_VERSION_DEVICE_EVENTS,
+ .info = &device_marshal_info,
+ .param = &device_marshal_param,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_device_event_demarshal[PW_DEVICE_EVENT_NUM] = {
+ [PW_DEVICE_EVENT_INFO] = { &device_demarshal_info, 0, },
+ [PW_DEVICE_EVENT_PARAM] = { &device_demarshal_param, 0, }
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_device_marshal = {
+ PW_TYPE_INTERFACE_Device,
+ PW_VERSION_DEVICE,
+ 0,
+ PW_DEVICE_METHOD_NUM,
+ PW_DEVICE_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_device_method_marshal,
+ .server_demarshal = pw_protocol_native_device_method_demarshal,
+ .server_marshal = &pw_protocol_native_device_event_marshal,
+ .client_demarshal = pw_protocol_native_device_event_demarshal,
+};
+
+static const struct pw_node_methods pw_protocol_native_node_method_marshal = {
+ PW_VERSION_NODE_METHODS,
+ .add_listener = &node_method_marshal_add_listener,
+ .subscribe_params = &node_marshal_subscribe_params,
+ .enum_params = &node_marshal_enum_params,
+ .set_param = &node_marshal_set_param,
+ .send_command = &node_marshal_send_command,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_node_method_demarshal[PW_NODE_METHOD_NUM] =
+{
+ [PW_NODE_METHOD_ADD_LISTENER] = { NULL, 0, },
+ [PW_NODE_METHOD_SUBSCRIBE_PARAMS] = { &node_demarshal_subscribe_params, 0, },
+ [PW_NODE_METHOD_ENUM_PARAMS] = { &node_demarshal_enum_params, 0, },
+ [PW_NODE_METHOD_SET_PARAM] = { &node_demarshal_set_param, PW_PERM_W, },
+ [PW_NODE_METHOD_SEND_COMMAND] = { &node_demarshal_send_command, PW_PERM_W, },
+};
+
+static const struct pw_node_events pw_protocol_native_node_event_marshal = {
+ PW_VERSION_NODE_EVENTS,
+ .info = &node_marshal_info,
+ .param = &node_marshal_param,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_node_event_demarshal[PW_NODE_EVENT_NUM] = {
+ [PW_NODE_EVENT_INFO] = { &node_demarshal_info, 0, },
+ [PW_NODE_EVENT_PARAM] = { &node_demarshal_param, 0, }
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_node_marshal = {
+ PW_TYPE_INTERFACE_Node,
+ PW_VERSION_NODE,
+ 0,
+ PW_NODE_METHOD_NUM,
+ PW_NODE_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_node_method_marshal,
+ .server_demarshal = pw_protocol_native_node_method_demarshal,
+ .server_marshal = &pw_protocol_native_node_event_marshal,
+ .client_demarshal = pw_protocol_native_node_event_demarshal,
+};
+
+
+static const struct pw_port_methods pw_protocol_native_port_method_marshal = {
+ PW_VERSION_PORT_METHODS,
+ .add_listener = &port_method_marshal_add_listener,
+ .subscribe_params = &port_marshal_subscribe_params,
+ .enum_params = &port_marshal_enum_params,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_port_method_demarshal[PW_PORT_METHOD_NUM] =
+{
+ [PW_PORT_METHOD_ADD_LISTENER] = { NULL, 0, },
+ [PW_PORT_METHOD_SUBSCRIBE_PARAMS] = { &port_demarshal_subscribe_params, 0, },
+ [PW_PORT_METHOD_ENUM_PARAMS] = { &port_demarshal_enum_params, 0, },
+};
+
+static const struct pw_port_events pw_protocol_native_port_event_marshal = {
+ PW_VERSION_PORT_EVENTS,
+ .info = &port_marshal_info,
+ .param = &port_marshal_param,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_port_event_demarshal[PW_PORT_EVENT_NUM] =
+{
+ [PW_PORT_EVENT_INFO] = { &port_demarshal_info, 0, },
+ [PW_PORT_EVENT_PARAM] = { &port_demarshal_param, 0, }
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_port_marshal = {
+ PW_TYPE_INTERFACE_Port,
+ PW_VERSION_PORT,
+ 0,
+ PW_PORT_METHOD_NUM,
+ PW_PORT_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_port_method_marshal,
+ .server_demarshal = pw_protocol_native_port_method_demarshal,
+ .server_marshal = &pw_protocol_native_port_event_marshal,
+ .client_demarshal = pw_protocol_native_port_event_demarshal,
+};
+
+static const struct pw_client_methods pw_protocol_native_client_method_marshal = {
+ PW_VERSION_CLIENT_METHODS,
+ .add_listener = &client_method_marshal_add_listener,
+ .error = &client_marshal_error,
+ .update_properties = &client_marshal_update_properties,
+ .get_permissions = &client_marshal_get_permissions,
+ .update_permissions = &client_marshal_update_permissions,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_client_method_demarshal[PW_CLIENT_METHOD_NUM] =
+{
+ [PW_CLIENT_METHOD_ADD_LISTENER] = { NULL, 0, },
+ [PW_CLIENT_METHOD_ERROR] = { &client_demarshal_error, PW_PERM_W, },
+ [PW_CLIENT_METHOD_UPDATE_PROPERTIES] = { &client_demarshal_update_properties, PW_PERM_W, },
+ [PW_CLIENT_METHOD_GET_PERMISSIONS] = { &client_demarshal_get_permissions, 0, },
+ [PW_CLIENT_METHOD_UPDATE_PERMISSIONS] = { &client_demarshal_update_permissions, PW_PERM_W, },
+};
+
+static const struct pw_client_events pw_protocol_native_client_event_marshal = {
+ PW_VERSION_CLIENT_EVENTS,
+ .info = &client_marshal_info,
+ .permissions = &client_marshal_permissions,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_client_event_demarshal[PW_CLIENT_EVENT_NUM] =
+{
+ [PW_CLIENT_EVENT_INFO] = { &client_demarshal_info, 0, },
+ [PW_CLIENT_EVENT_PERMISSIONS] = { &client_demarshal_permissions, 0, }
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_client_marshal = {
+ PW_TYPE_INTERFACE_Client,
+ PW_VERSION_CLIENT,
+ 0,
+ PW_CLIENT_METHOD_NUM,
+ PW_CLIENT_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_client_method_marshal,
+ .server_demarshal = pw_protocol_native_client_method_demarshal,
+ .server_marshal = &pw_protocol_native_client_event_marshal,
+ .client_demarshal = pw_protocol_native_client_event_demarshal,
+};
+
+
+static const struct pw_link_methods pw_protocol_native_link_method_marshal = {
+ PW_VERSION_LINK_METHODS,
+ .add_listener = &link_method_marshal_add_listener,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_link_method_demarshal[PW_LINK_METHOD_NUM] =
+{
+ [PW_LINK_METHOD_ADD_LISTENER] = { NULL, 0, },
+};
+
+static const struct pw_link_events pw_protocol_native_link_event_marshal = {
+ PW_VERSION_LINK_EVENTS,
+ .info = &link_marshal_info,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_link_event_demarshal[PW_LINK_EVENT_NUM] =
+{
+ [PW_LINK_EVENT_INFO] = { &link_demarshal_info, 0, }
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_link_marshal = {
+ PW_TYPE_INTERFACE_Link,
+ PW_VERSION_LINK,
+ 0,
+ PW_LINK_METHOD_NUM,
+ PW_LINK_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_link_method_marshal,
+ .server_demarshal = pw_protocol_native_link_method_demarshal,
+ .server_marshal = &pw_protocol_native_link_event_marshal,
+ .client_demarshal = pw_protocol_native_link_event_demarshal,
+};
+
+void pw_protocol_native_init(struct pw_protocol *protocol)
+{
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_core_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_registry_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_module_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_device_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_node_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_port_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_factory_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_client_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_link_marshal);
+}
diff --git a/src/modules/module-protocol-native/test-connection.c b/src/modules/module-protocol-native/test-connection.c
new file mode 100644
index 0000000..c7d2f69
--- /dev/null
+++ b/src/modules/module-protocol-native/test-connection.c
@@ -0,0 +1,225 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <sys/socket.h>
+
+#include <spa/pod/builder.h>
+#include <spa/pod/parser.h>
+#include <spa/utils/result.h>
+
+#include <pipewire/pipewire.h>
+
+#include "connection.h"
+
+#define NAME "protocol-native"
+PW_LOG_TOPIC(mod_topic, "mod." NAME);
+PW_LOG_TOPIC(mod_topic_connection, "conn." NAME);
+
+static void test_create(struct pw_protocol_native_connection *conn)
+{
+ const struct pw_protocol_native_message *msg;
+ int res;
+
+ res = pw_protocol_native_connection_get_next(conn, &msg);
+ spa_assert_se(res != 1);
+
+ res = pw_protocol_native_connection_get_fd(conn, 0);
+ spa_assert_se(res == -ENOENT);
+
+ res = pw_protocol_native_connection_flush(conn);
+ spa_assert_se(res == 0);
+
+ res = pw_protocol_native_connection_clear(conn);
+ spa_assert_se(res == 0);
+}
+
+static void write_message(struct pw_protocol_native_connection *conn, int fd)
+{
+ struct pw_protocol_native_message *msg;
+ struct spa_pod_builder *b;
+ int seq = -1, res;
+
+ b = pw_protocol_native_connection_begin(conn, 1, 5, &msg);
+ spa_assert_se(b != NULL);
+ spa_assert_se(msg->seq != -1);
+
+ seq = SPA_RESULT_RETURN_ASYNC(msg->seq);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(42),
+ SPA_POD_Id(SPA_TYPE_Object),
+ SPA_POD_Int(pw_protocol_native_connection_add_fd(conn, fd)));
+
+ res = pw_protocol_native_connection_end(conn, b);
+ spa_assert_se(seq == res);
+}
+
+static int read_message(struct pw_protocol_native_connection *conn,
+ const struct pw_protocol_native_message **pmsg)
+{
+ struct spa_pod_parser prs;
+ const struct pw_protocol_native_message *msg;
+ int res, fd;
+ uint32_t v_int, v_id, fdidx;
+
+ res = pw_protocol_native_connection_get_next(conn, &msg);
+ if (res != 1) {
+ pw_log_error("got %d", res);
+ return -1;
+ }
+
+ if (pmsg)
+ *pmsg = msg;
+
+ spa_assert_se(msg->opcode == 5);
+ spa_assert_se(msg->id == 1);
+ spa_assert_se(msg->data != NULL);
+ spa_assert_se(msg->size > 0);
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&v_int),
+ SPA_POD_Id(&v_id),
+ SPA_POD_Int(&fdidx)) < 0)
+ spa_assert_not_reached();
+
+ fd = pw_protocol_native_connection_get_fd(conn, fdidx);
+ spa_assert_se(fd != -ENOENT);
+ pw_log_debug("got fd %d %d", fdidx, fd);
+ return 0;
+}
+
+static void test_read_write(struct pw_protocol_native_connection *in,
+ struct pw_protocol_native_connection *out)
+{
+ write_message(out, 1);
+ pw_protocol_native_connection_flush(out);
+ write_message(out, 2);
+ pw_protocol_native_connection_flush(out);
+ spa_assert_se(read_message(in, NULL) == 0);
+ spa_assert_se(read_message(in, NULL) == 0);
+ spa_assert_se(read_message(in, NULL) == -1);
+
+ write_message(out, 1);
+ write_message(out, 2);
+ pw_protocol_native_connection_flush(out);
+ spa_assert_se(read_message(in, NULL) == 0);
+ spa_assert_se(read_message(in, NULL) == 0);
+ spa_assert_se(read_message(in, NULL) == -1);
+}
+
+static void test_reentering(struct pw_protocol_native_connection *in,
+ struct pw_protocol_native_connection *out)
+{
+ const struct pw_protocol_native_message *msg1, *msg2;
+ int i;
+
+#define READ_MSG(idx) \
+ spa_assert_se(read_message(in, &msg ## idx) == 0); \
+ spa_assert_se((msg ## idx)->n_fds == 1); \
+ spa_assert_se((msg ## idx)->size < sizeof(buf ## idx)); \
+ fd ## idx = (msg ## idx)->fds[0]; \
+ memcpy(buf ## idx, (msg ## idx)->data, (msg ## idx)->size); \
+ size ## idx = (msg ## idx)->size
+
+#define CHECK_MSG(idx) \
+ spa_assert_se((msg ## idx)->fds[0] == fd ## idx); \
+ spa_assert_se(memcmp((msg ## idx)->data, buf ## idx, size ## idx) == 0)
+
+ for (i = 0; i < 50; ++i) {
+ int fd1, fd2;
+ char buf1[1024], buf2[1024];
+ int size1, size2;
+
+ write_message(out, 1);
+ write_message(out, 2);
+ write_message(out, 1);
+ write_message(out, 2);
+ write_message(out, 1);
+ pw_protocol_native_connection_flush(out);
+
+ READ_MSG(1);
+ pw_protocol_native_connection_enter(in); /* 1 */
+ READ_MSG(2);
+ CHECK_MSG(1);
+ pw_protocol_native_connection_enter(in); /* 2 */
+ pw_protocol_native_connection_leave(in); /* 2 */
+ CHECK_MSG(1);
+ CHECK_MSG(2);
+ pw_protocol_native_connection_enter(in); /* 2 */
+ pw_protocol_native_connection_enter(in); /* 3 */
+ spa_assert_se(read_message(in, NULL) == 0);
+ CHECK_MSG(1);
+ CHECK_MSG(2);
+ pw_protocol_native_connection_leave(in); /* 3 */
+ spa_assert_se(read_message(in, NULL) == 0);
+ CHECK_MSG(1);
+ CHECK_MSG(2);
+ pw_protocol_native_connection_leave(in); /* 2 */
+ CHECK_MSG(2);
+ spa_assert_se(read_message(in, NULL) == 0);
+ CHECK_MSG(1);
+ pw_protocol_native_connection_leave(in); /* 1 */
+ CHECK_MSG(1);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+ struct pw_protocol_native_connection *in, *out;
+ int fds[2];
+
+ pw_init(&argc, &argv);
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+ PW_LOG_TOPIC_INIT(mod_topic_connection);
+
+ loop = pw_main_loop_new(NULL);
+ spa_assert_se(loop != NULL);
+ context = pw_context_new(pw_main_loop_get_loop(loop), NULL, 0);
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) {
+ spa_assert_not_reached();
+ return -1;
+ }
+
+ in = pw_protocol_native_connection_new(context, fds[0]);
+ spa_assert_se(in != NULL);
+ out = pw_protocol_native_connection_new(context, fds[1]);
+ spa_assert_se(out != NULL);
+
+ test_create(in);
+ test_create(out);
+ test_read_write(in, out);
+ test_reentering(in, out);
+
+ pw_protocol_native_connection_destroy(in);
+ pw_protocol_native_connection_destroy(out);
+ pw_context_destroy(context);
+ pw_main_loop_destroy(loop);
+
+ return 0;
+}
diff --git a/src/modules/module-protocol-native/v0/interfaces.h b/src/modules/module-protocol-native/v0/interfaces.h
new file mode 100644
index 0000000..dca9672
--- /dev/null
+++ b/src/modules/module-protocol-native/v0/interfaces.h
@@ -0,0 +1,534 @@
+/* PipeWire
+ *
+ * Copyright © 2016 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_INTERFACES_V0_H
+#define PIPEWIRE_INTERFACES_V0_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/param/param.h>
+#include <spa/node/node.h>
+
+#include <pipewire/pipewire.h>
+
+/** Core */
+
+#define PW_VERSION_CORE_V0 0
+
+#define PW_CORE_V0_METHOD_HELLO 0
+#define PW_CORE_V0_METHOD_UPDATE_TYPES 1
+#define PW_CORE_V0_METHOD_SYNC 2
+#define PW_CORE_V0_METHOD_GET_REGISTRY 3
+#define PW_CORE_V0_METHOD_CLIENT_UPDATE 4
+#define PW_CORE_V0_METHOD_PERMISSIONS 5
+#define PW_CORE_V0_METHOD_CREATE_OBJECT 6
+#define PW_CORE_V0_METHOD_DESTROY 7
+#define PW_CORE_V0_METHOD_NUM 8
+
+/**
+ * Key to update default permissions of globals without specific
+ * permissions. value is "[r][w][x]" */
+#define PW_CORE_PERMISSIONS_DEFAULT "permissions.default"
+
+/**
+ * Key to update specific permissions of a global. If the global
+ * did not have specific permissions, it will first be assigned
+ * the default permissions before it is updated.
+ * Value is "<global-id>:[r][w][x]"*/
+#define PW_CORE_PERMISSIONS_GLOBAL "permissions.global"
+
+/**
+ * Key to update specific permissions of all existing globals.
+ * This is equivalent to using \ref PW_CORE_PERMISSIONS_GLOBAL
+ * on each global id individually that did not have specific
+ * permissions.
+ * Value is "[r][w][x]" */
+#define PW_CORE_PERMISSIONS_EXISTING "permissions.existing"
+
+#define PW_LINK_OUTPUT_NODE_ID "link.output_node.id"
+#define PW_LINK_OUTPUT_PORT_ID "link.output_port.id"
+#define PW_LINK_INPUT_NODE_ID "link.input_node.id"
+#define PW_LINK_INPUT_PORT_ID "link.input_port.id"
+
+/**
+ * \struct pw_core_v0_methods
+ * \brief Core methods
+ *
+ * The core global object. This is a singleton object used for
+ * creating new objects in the remote PipeWire instance. It is
+ * also used for internal features.
+ */
+struct pw_core_v0_methods {
+#define PW_VERSION_CORE_V0_METHODS 0
+ uint32_t version;
+ /**
+ * Start a conversation with the server. This will send
+ * the core info and server types.
+ *
+ * All the existing resources for the client (except the core
+ * resource) will be destroyed.
+ */
+ void (*hello) (void *object);
+ /**
+ * Update the type map
+ *
+ * Send a type map update to the PipeWire server. The server uses this
+ * information to keep a mapping between client types and the server types.
+ * \param first_id the id of the first type
+ * \param types the types as a string
+ * \param n_types the number of types
+ */
+ void (*update_types) (void *object,
+ uint32_t first_id,
+ const char **types,
+ uint32_t n_types);
+ /**
+ * Do server roundtrip
+ *
+ * Ask the server to emit the 'done' event with \a id.
+ * Since methods are handled in-order and events are delivered
+ * in-order, this can be used as a barrier to ensure all previous
+ * methods and the resulting events have been handled.
+ * \param seq the sequence number passed to the done event
+ */
+ void (*sync) (void *object, uint32_t seq);
+ /**
+ * Get the registry object
+ *
+ * Create a registry object that allows the client to list and bind
+ * the global objects available from the PipeWire server
+ * \param version the client proxy id
+ * \param id the client proxy id
+ */
+ void (*get_registry) (void *object, uint32_t version, uint32_t new_id);
+ /**
+ * Update the client properties
+ * \param props the new client properties
+ */
+ void (*client_update) (void *object, const struct spa_dict *props);
+ /**
+ * Manage the permissions of the global objects
+ *
+ * Update the permissions of the global objects using the
+ * dictionary with properties.
+ *
+ * Globals can use the default permissions or can have specific
+ * permissions assigned to them.
+ *
+ * \param id the global id to change
+ * \param props dictionary with permission properties
+ */
+ void (*permissions) (void *object, const struct spa_dict *props);
+ /**
+ * Create a new object on the PipeWire server from a factory.
+ * Use a \a factory_name of "client-node" to create a
+ * \ref pw_client_node.
+ *
+ * \param factory_name the factory name to use
+ * \param type the interface to bind to
+ * \param version the version of the interface
+ * \param props extra properties
+ * \param new_id the client proxy id
+ */
+ void (*create_object) (void *object,
+ const char *factory_name,
+ uint32_t type,
+ uint32_t version,
+ const struct spa_dict *props,
+ uint32_t new_id);
+
+ /**
+ * Destroy an object id
+ *
+ * \param id the object id to destroy
+ */
+ void (*destroy) (void *object, uint32_t id);
+};
+
+#define PW_CORE_V0_EVENT_UPDATE_TYPES 0
+#define PW_CORE_V0_EVENT_DONE 1
+#define PW_CORE_V0_EVENT_ERROR 2
+#define PW_CORE_V0_EVENT_REMOVE_ID 3
+#define PW_CORE_V0_EVENT_INFO 4
+#define PW_CORE_V0_EVENT_NUM 5
+
+/** \struct pw_core_v0_events
+ * \brief Core events
+ * \ingroup pw_core_interface The pw_core interface
+ */
+struct pw_core_v0_events {
+#define PW_VERSION_CORE_V0_EVENTS 0
+ uint32_t version;
+ /**
+ * Update the type map
+ *
+ * Send a type map update to the client. The client uses this
+ * information to keep a mapping between server types and the client types.
+ * \param first_id the id of the first type
+ * \param types the types as a string
+ * \param n_types the number of \a types
+ */
+ void (*update_types) (void *data,
+ uint32_t first_id,
+ const char **types,
+ uint32_t n_types);
+ /**
+ * Emit a done event
+ *
+ * The done event is emitted as a result of a sync method with the
+ * same sequence number.
+ * \param seq the sequence number passed to the sync method call
+ */
+ void (*done) (void *data, uint32_t seq);
+ /**
+ * Fatal error event
+ *
+ * The error event is sent out when a fatal (non-recoverable)
+ * error has occurred. The id argument is the object where
+ * the error occurred, most often in response to a request to that
+ * object. The message is a brief description of the error,
+ * for (debugging) convenience.
+ * \param id object where the error occurred
+ * \param res error code
+ * \param error error description
+ */
+ void (*error) (void *data, uint32_t id, int res, const char *error, ...);
+ /**
+ * Remove an object ID
+ *
+ * This event is used internally by the object ID management
+ * logic. When a client deletes an object, the server will send
+ * this event to acknowledge that it has seen the delete request.
+ * When the client receives this event, it will know that it can
+ * safely reuse the object ID.
+ * \param id deleted object ID
+ */
+ void (*remove_id) (void *data, uint32_t id);
+ /**
+ * Notify new core info
+ *
+ * \param info new core info
+ */
+ void (*info) (void *data, struct pw_core_info *info);
+};
+
+#define pw_core_resource_v0_update_types(r,...) pw_resource_notify(r,struct pw_core_v0_events,update_types,__VA_ARGS__)
+#define pw_core_resource_v0_done(r,...) pw_resource_notify(r,struct pw_core_v0_events,done,__VA_ARGS__)
+#define pw_core_resource_v0_error(r,...) pw_resource_notify(r,struct pw_core_v0_events,error,__VA_ARGS__)
+#define pw_core_resource_v0_remove_id(r,...) pw_resource_notify(r,struct pw_core_v0_events,remove_id,__VA_ARGS__)
+#define pw_core_resource_v0_info(r,...) pw_resource_notify(r,struct pw_core_v0_events,info,__VA_ARGS__)
+
+
+#define PW_VERSION_REGISTRY_V0 0
+
+/** \page page_registry Registry
+ *
+ * \section page_registry_overview Overview
+ *
+ * The registry object is a singleton object that keeps track of
+ * global objects on the PipeWire instance. See also \ref page_global.
+ *
+ * Global objects typically represent an actual object in PipeWire
+ * (for example, a module or node) or they are singleton
+ * objects such as the core.
+ *
+ * When a client creates a registry object, the registry object
+ * will emit a global event for each global currently in the
+ * registry. Globals come and go as a result of device hotplugs or
+ * reconfiguration or other events, and the registry will send out
+ * global and global_remove events to keep the client up to date
+ * with the changes. To mark the end of the initial burst of
+ * events, the client can use the pw_core.sync methosd immediately
+ * after calling pw_core.get_registry.
+ *
+ * A client can bind to a global object by using the bind
+ * request. This creates a client-side proxy that lets the object
+ * emit events to the client and lets the client invoke methods on
+ * the object. See \ref page_proxy
+ *
+ * Clients can also change the permissions of the global objects that
+ * it can see. This is interesting when you want to configure a
+ * pipewire session before handing it to another application. You
+ * can, for example, hide certain existing or new objects or limit
+ * the access permissions on an object.
+ */
+#define PW_REGISTRY_V0_METHOD_BIND 0
+#define PW_REGISTRY_V0_METHOD_NUM 1
+
+/** Registry methods */
+struct pw_registry_v0_methods {
+#define PW_VERSION_REGISTRY_V0_METHODS 0
+ uint32_t version;
+ /**
+ * Bind to a global object
+ *
+ * Bind to the global object with \a id and use the client proxy
+ * with new_id as the proxy. After this call, methods can be
+ * send to the remote global object and events can be received
+ *
+ * \param id the global id to bind to
+ * \param type the interface type to bind to
+ * \param version the interface version to use
+ * \param new_id the client proxy to use
+ */
+ void (*bind) (void *object, uint32_t id, uint32_t type, uint32_t version, uint32_t new_id);
+};
+
+#define PW_REGISTRY_V0_EVENT_GLOBAL 0
+#define PW_REGISTRY_V0_EVENT_GLOBAL_REMOVE 1
+#define PW_REGISTRY_V0_EVENT_NUM 2
+
+/** Registry events */
+struct pw_registry_v0_events {
+#define PW_VERSION_REGISTRY_V0_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify of a new global object
+ *
+ * The registry emits this event when a new global object is
+ * available.
+ *
+ * \param id the global object id
+ * \param parent_id the parent global id
+ * \param permissions the permissions of the object
+ * \param type the type of the interface
+ * \param version the version of the interface
+ * \param props extra properties of the global
+ */
+ void (*global) (void *data, uint32_t id, uint32_t parent_id,
+ uint32_t permissions, uint32_t type, uint32_t version,
+ const struct spa_dict *props);
+ /**
+ * Notify of a global object removal
+ *
+ * Emitted when a global object was removed from the registry.
+ * If the client has any bindings to the global, it should destroy
+ * those.
+ *
+ * \param id the id of the global that was removed
+ */
+ void (*global_remove) (void *data, uint32_t id);
+};
+
+#define pw_registry_resource_v0_global(r,...) pw_resource_notify(r,struct pw_registry_v0_events,global,__VA_ARGS__)
+#define pw_registry_resource_v0_global_remove(r,...) pw_resource_notify(r,struct pw_registry_v0_events,global_remove,__VA_ARGS__)
+
+
+#define PW_VERSION_MODULE_V0 0
+
+#define PW_MODULE_V0_EVENT_INFO 0
+#define PW_MODULE_V0_EVENT_NUM 1
+
+/** Module events */
+struct pw_module_v0_events {
+#define PW_VERSION_MODULE_V0_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify module info
+ *
+ * \param info info about the module
+ */
+ void (*info) (void *data, struct pw_module_info *info);
+};
+
+#define pw_module_resource_v0_info(r,...) pw_resource_notify(r,struct pw_module_v0_events,info,__VA_ARGS__)
+
+#define PW_VERSION_NODE_V0 0
+
+#define PW_NODE_V0_EVENT_INFO 0
+#define PW_NODE_V0_EVENT_PARAM 1
+#define PW_NODE_V0_EVENT_NUM 2
+
+/** Node events */
+struct pw_node_v0_events {
+#define PW_VERSION_NODE_V0_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify node info
+ *
+ * \param info info about the node
+ */
+ void (*info) (void *data, struct pw_node_info *info);
+ /**
+ * Notify a node param
+ *
+ * Event emitted as a result of the enum_params method.
+ *
+ * \param id the param id
+ * \param index the param index
+ * \param next the param index of the next param
+ * \param param the parameter
+ */
+ void (*param) (void *data,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param);
+};
+
+#define pw_node_resource_v0_info(r,...) pw_resource_notify(r,struct pw_node_v0_events,info,__VA_ARGS__)
+#define pw_node_resource_v0_param(r,...) pw_resource_notify(r,struct pw_node_v0_events,param,__VA_ARGS__)
+
+#define PW_NODE_V0_METHOD_ENUM_PARAMS 0
+#define PW_NODE_V0_METHOD_NUM 1
+
+/** Node methods */
+struct pw_node_v0_methods {
+#define PW_VERSION_NODE_V0_METHODS 0
+ uint32_t version;
+ /**
+ * Enumerate node parameters
+ *
+ * Start enumeration of node parameters. For each param, a
+ * param event will be emitted.
+ *
+ * \param id the parameter id to enum or PW_ID_ANY for all
+ * \param start the start index or 0 for the first param
+ * \param num the maximum number of params to retrieve
+ * \param filter a param filter or NULL
+ */
+ void (*enum_params) (void *object, uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter);
+};
+
+#define PW_VERSION_PORT_V0 0
+
+#define PW_PORT_V0_EVENT_INFO 0
+#define PW_PORT_V0_EVENT_PARAM 1
+#define PW_PORT_V0_EVENT_NUM 2
+
+/** Port events */
+struct pw_port_v0_events {
+#define PW_VERSION_PORT_V0_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify port info
+ *
+ * \param info info about the port
+ */
+ void (*info) (void *data, struct pw_port_info *info);
+ /**
+ * Notify a port param
+ *
+ * Event emitted as a result of the enum_params method.
+ *
+ * \param id the param id
+ * \param index the param index
+ * \param next the param index of the next param
+ * \param param the parameter
+ */
+ void (*param) (void *data,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param);
+};
+
+#define pw_port_resource_v0_info(r,...) pw_resource_notify(r,struct pw_port_v0_events,info,__VA_ARGS__)
+#define pw_port_resource_v0_param(r,...) pw_resource_notify(r,struct pw_port_v0_events,param,__VA_ARGS__)
+
+#define PW_PORT_V0_METHOD_ENUM_PARAMS 0
+#define PW_PORT_V0_METHOD_NUM 1
+
+/** Port methods */
+struct pw_port_v0_methods {
+#define PW_VERSION_PORT_V0_METHODS 0
+ uint32_t version;
+ /**
+ * Enumerate port parameters
+ *
+ * Start enumeration of port parameters. For each param, a
+ * param event will be emitted.
+ *
+ * \param id the parameter id to enumerate
+ * \param start the start index or 0 for the first param
+ * \param num the maximum number of params to retrieve
+ * \param filter a param filter or NULL
+ */
+ void (*enum_params) (void *object, uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter);
+};
+
+#define PW_VERSION_FACTORY_V0 0
+
+#define PW_FACTORY_V0_EVENT_INFO 0
+#define PW_FACTORY_V0_EVENT_NUM 1
+
+/** Factory events */
+struct pw_factory_v0_events {
+#define PW_VERSION_FACTORY_V0_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify factory info
+ *
+ * \param info info about the factory
+ */
+ void (*info) (void *data, struct pw_factory_info *info);
+};
+
+#define pw_factory_resource_v0_info(r,...) pw_resource_notify(r,struct pw_factory_v0_events,info,__VA_ARGS__)
+
+#define PW_VERSION_CLIENT_V0 0
+
+#define PW_CLIENT_V0_EVENT_INFO 0
+#define PW_CLIENT_V0_EVENT_NUM 1
+
+/** Client events */
+struct pw_client_v0_events {
+#define PW_VERSION_CLIENT_V0_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify client info
+ *
+ * \param info info about the client
+ */
+ void (*info) (void *data, struct pw_client_info *info);
+};
+
+#define pw_client_resource_v0_info(r,...) pw_resource_notify(r,struct pw_client_v0_events,info,__VA_ARGS__)
+
+
+#define PW_VERSION_LINK_V0 0
+
+#define PW_LINK_V0_EVENT_INFO 0
+#define PW_LINK_V0_EVENT_NUM 1
+
+/** Link events */
+struct pw_link_v0_events {
+#define PW_VERSION_LINK_V0_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify link info
+ *
+ * \param info info about the link
+ */
+ void (*info) (void *data, struct pw_link_info *info);
+};
+
+#define pw_link_resource_v0_info(r,...) pw_resource_notify(r,struct pw_link_v0_events,info,__VA_ARGS__)
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_INTERFACES_V0_H */
diff --git a/src/modules/module-protocol-native/v0/protocol-native.c b/src/modules/module-protocol-native/v0/protocol-native.c
new file mode 100644
index 0000000..d6173ac
--- /dev/null
+++ b/src/modules/module-protocol-native/v0/protocol-native.c
@@ -0,0 +1,1371 @@
+/* PipeWire
+ *
+ * Copyright © 2017 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+
+#include "spa/pod/parser.h"
+#include "spa/pod/builder.h"
+#include "spa/debug/types.h"
+#include "spa/utils/string.h"
+
+#include "pipewire/pipewire.h"
+#include "pipewire/private.h"
+#include "pipewire/protocol.h"
+#include "pipewire/resource.h"
+#include "pipewire/extensions/protocol-native.h"
+#include "pipewire/extensions/metadata.h"
+#include "pipewire/extensions/session-manager.h"
+#include "pipewire/extensions/client-node.h"
+
+#include "interfaces.h"
+#include "typemap.h"
+
+#include "../connection.h"
+
+PW_LOG_TOPIC_EXTERN(mod_topic);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define PW_PROTOCOL_NATIVE_FLAG_REMAP (1<<0)
+
+SPA_EXPORT
+uint32_t pw_protocol_native0_find_type(struct pw_impl_client *client, const char *type)
+{
+ uint32_t i;
+ for (i = 0; i < SPA_N_ELEMENTS(type_map); i++) {
+ if (spa_streq(type_map[i].type, type))
+ return i;
+ }
+ return SPA_ID_INVALID;
+}
+
+static void
+update_types_server(struct pw_resource *resource)
+{
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CORE_V0_EVENT_UPDATE_TYPES, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ "i", 0,
+ "i", SPA_N_ELEMENTS(type_map), NULL);
+
+ for (i = 0; i < SPA_N_ELEMENTS(type_map); i++) {
+ spa_pod_builder_add(b, "s", type_map[i].type, NULL);
+ }
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+
+static void core_marshal_info(void *data, const struct pw_core_info *info)
+{
+ struct pw_resource *resource = data;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct protocol_compat_v2 *compat_v2 = client->compat_v2;
+ struct spa_pod_builder *b;
+ uint32_t i, n_items;
+ uint64_t change_mask = 0;
+ struct spa_pod_frame f;
+ struct pw_protocol_native_message *msg;
+
+#define PW_CORE_V0_CHANGE_MASK_USER_NAME (1 << 0)
+#define PW_CORE_V0_CHANGE_MASK_HOST_NAME (1 << 1)
+#define PW_CORE_V0_CHANGE_MASK_VERSION (1 << 2)
+#define PW_CORE_V0_CHANGE_MASK_NAME (1 << 3)
+#define PW_CORE_V0_CHANGE_MASK_COOKIE (1 << 4)
+#define PW_CORE_V0_CHANGE_MASK_PROPS (1 << 5)
+
+ if (compat_v2->send_types) {
+ update_types_server(resource);
+ change_mask |= PW_CORE_V0_CHANGE_MASK_USER_NAME |
+ PW_CORE_V0_CHANGE_MASK_HOST_NAME |
+ PW_CORE_V0_CHANGE_MASK_VERSION |
+ PW_CORE_V0_CHANGE_MASK_NAME |
+ PW_CORE_V0_CHANGE_MASK_COOKIE;
+ compat_v2->send_types = false;
+ }
+ b = pw_protocol_native_begin_resource(resource, PW_CORE_V0_EVENT_INFO, &msg);
+
+ n_items = info->props ? info->props->n_items : 0;
+
+ if (info->change_mask & PW_CORE_CHANGE_MASK_PROPS)
+ change_mask |= PW_CORE_V0_CHANGE_MASK_PROPS;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ "i", info->id,
+ "l", change_mask,
+ "s", info->user_name,
+ "s", info->host_name,
+ "s", info->version,
+ "s", info->name,
+ "i", info->cookie,
+ "i", n_items, NULL);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ "s", info->props->items[i].key,
+ "s", info->props->items[i].value, NULL);
+ }
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void core_marshal_done(void *data, uint32_t id, int seq)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CORE_V0_EVENT_DONE, NULL);
+
+ spa_pod_builder_add_struct(b, "i", seq);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void core_marshal_error(void *data, uint32_t id, int seq, int res, const char *error)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CORE_V0_EVENT_ERROR, NULL);
+
+ spa_pod_builder_add_struct(b,
+ "i", id,
+ "i", res,
+ "s", error);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void core_marshal_remove_id(void *data, uint32_t id)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CORE_V0_EVENT_REMOVE_ID, NULL);
+
+ spa_pod_builder_add_struct(b, "i", id);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int core_demarshal_client_update(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_dict props;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ uint32_t i;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f) < 0 ||
+ spa_pod_parser_get(&prs,
+ "i", &props.n_items, NULL) < 0)
+ return -EINVAL;
+
+ props.items = alloca(props.n_items * sizeof(struct spa_dict_item));
+ for (i = 0; i < props.n_items; i++) {
+ if (spa_pod_parser_get(&prs,
+ "s", &props.items[i].key,
+ "s", &props.items[i].value,
+ NULL) < 0)
+ return -EINVAL;
+ }
+ pw_impl_client_update_properties(client, &props);
+ return 0;
+}
+
+static uint32_t parse_perms(const char *str)
+{
+ uint32_t perms = 0;
+
+ while (*str != '\0') {
+ switch (*str++) {
+ case 'r':
+ perms |= PW_PERM_R;
+ break;
+ case 'w':
+ perms |= PW_PERM_W;
+ break;
+ case 'x':
+ perms |= PW_PERM_X;
+ break;
+ }
+ }
+ return perms;
+}
+
+static int core_demarshal_permissions(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_dict props;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ uint32_t i, n_permissions;
+ struct pw_permission *permissions, defperm = { 0, };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f) < 0 ||
+ spa_pod_parser_get(&prs, "i", &props.n_items, NULL) < 0)
+ return -EINVAL;
+
+ props.items = alloca(props.n_items * sizeof(struct spa_dict_item));
+
+ n_permissions = 0;
+ permissions = alloca(props.n_items * sizeof(struct pw_permission));
+
+ for (i = 0; i < props.n_items; i++) {
+ uint32_t id, perms;
+ const char *str;
+
+ if (spa_pod_parser_get(&prs,
+ "s", &props.items[i].key,
+ "s", &props.items[i].value,
+ NULL) < 0)
+ return -EINVAL;
+
+ str = props.items[i].value;
+ /* first set global permissions */
+ if (spa_streq(props.items[i].key, PW_CORE_PERMISSIONS_GLOBAL)) {
+ size_t len;
+
+ /* <global-id>:[r][w][x] */
+ len = strcspn(str, ":");
+ if (len == 0)
+ continue;
+ id = atoi(str);
+ perms = parse_perms(str + len);
+ permissions[n_permissions++] = PW_PERMISSION_INIT(id, perms);
+ } else if (spa_streq(props.items[i].key, PW_CORE_PERMISSIONS_DEFAULT)) {
+ perms = parse_perms(str);
+ defperm = PW_PERMISSION_INIT(PW_ID_ANY, perms);
+ }
+ }
+ /* add default permission if set */
+ if (defperm.id == PW_ID_ANY)
+ permissions[n_permissions++] = defperm;
+
+ for (i = 0; i < n_permissions; i++) {
+ pw_log_debug("%d: %d: %08x", i, permissions[i].id, permissions[i].permissions);
+ }
+
+ return pw_impl_client_update_permissions(resource->client,
+ n_permissions, permissions);
+}
+
+static int core_demarshal_hello(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ void *ptr;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ "P", &ptr) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_core_methods, hello, 0, 2);
+}
+
+static int core_demarshal_sync(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t seq;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs, "i", &seq) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_core_methods, sync, 0, 0, seq);
+}
+
+static int core_demarshal_get_registry(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ int32_t version, new_id;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ "i", &version,
+ "i", &new_id) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_core_methods, get_registry, 0, version, new_id);
+}
+
+SPA_EXPORT
+uint32_t pw_protocol_native0_type_from_v2(struct pw_impl_client *client, uint32_t type)
+{
+ void *t;
+ uint32_t index;
+ struct protocol_compat_v2 *compat_v2 = client->compat_v2;
+
+ if ((t = pw_map_lookup(&compat_v2->types, type)) == NULL)
+ return SPA_ID_INVALID;
+
+ index = PW_MAP_PTR_TO_ID(t);
+ if (index >= SPA_N_ELEMENTS(type_map))
+ return SPA_ID_INVALID;
+
+ return type_map[index].id;
+}
+
+SPA_EXPORT
+const char * pw_protocol_native0_name_from_v2(struct pw_impl_client *client, uint32_t type)
+{
+ void *t;
+ uint32_t index;
+ struct protocol_compat_v2 *compat_v2 = client->compat_v2;
+
+ if ((t = pw_map_lookup(&compat_v2->types, type)) == NULL)
+ return NULL;
+
+ index = PW_MAP_PTR_TO_ID(t);
+ if (index >= SPA_N_ELEMENTS(type_map))
+ return NULL;
+
+ return type_map[index].name;
+}
+
+SPA_EXPORT
+uint32_t pw_protocol_native0_name_to_v2(struct pw_impl_client *client, const char *name)
+{
+ uint32_t i;
+ /* match name to type table and return index */
+ for (i = 0; i < SPA_N_ELEMENTS(type_map); i++) {
+ if (type_map[i].name != NULL && spa_streq(type_map[i].name, name))
+ return i;
+ }
+ return SPA_ID_INVALID;
+}
+
+SPA_EXPORT
+uint32_t pw_protocol_native0_type_to_v2(struct pw_impl_client *client,
+ const struct spa_type_info *info, uint32_t type)
+{
+ const char *name;
+
+ /** find full name of type in type_info */
+ if ((name = spa_debug_type_find_name(info, type)) == NULL)
+ return SPA_ID_INVALID;
+
+ return pw_protocol_native0_name_to_v2(client, name);
+}
+
+struct spa_pod_prop_body0 {
+ uint32_t key;
+#define SPA_POD_PROP0_RANGE_NONE 0 /**< no range */
+#define SPA_POD_PROP0_RANGE_MIN_MAX 1 /**< property has range */
+#define SPA_POD_PROP0_RANGE_STEP 2 /**< property has range with step */
+#define SPA_POD_PROP0_RANGE_ENUM 3 /**< property has enumeration */
+#define SPA_POD_PROP0_RANGE_FLAGS 4 /**< property has flags */
+#define SPA_POD_PROP0_RANGE_MASK 0xf /**< mask to select range type */
+#define SPA_POD_PROP0_FLAG_UNSET (1 << 4) /**< property value is unset */
+#define SPA_POD_PROP0_FLAG_OPTIONAL (1 << 5) /**< property value is optional */
+#define SPA_POD_PROP0_FLAG_READONLY (1 << 6) /**< property is readonly */
+#define SPA_POD_PROP0_FLAG_DEPRECATED (1 << 7) /**< property is deprecated */
+#define SPA_POD_PROP0_FLAG_INFO (1 << 8) /**< property is informational and is not
+ * used when filtering */
+ uint32_t flags;
+ struct spa_pod value;
+ /* array with elements of value.size follows,
+ * first element is value/default, rest are alternatives */
+};
+
+/* v2 iterates object as containing spa_pod */
+#define SPA_POD_OBJECT_BODY_FOREACH0(body, size, iter) \
+ for ((iter) = SPA_PTROFF((body), sizeof(struct spa_pod_object_body), struct spa_pod); \
+ spa_pod_is_inside(body, size, iter); \
+ (iter) = spa_pod_next(iter))
+
+#define SPA_POD_PROP_ALTERNATIVE_FOREACH0(body, _size, iter) \
+ for ((iter) = SPA_PTROFF((body), (body)->value.size + \
+ sizeof(struct spa_pod_prop_body0), __typeof__(*iter)); \
+ (iter) <= SPA_PTROFF((body), (_size)-(body)->value.size, __typeof__(*iter)); \
+ (iter) = SPA_PTROFF((iter), (body)->value.size, __typeof__(*iter)))
+
+#define SPA0_POD_PROP_N_VALUES(b,size) (((size) - sizeof(struct spa_pod_prop_body0)) / (b)->value.size)
+
+static int remap_from_v2(uint32_t type, void *body, uint32_t size, struct pw_impl_client *client,
+ struct spa_pod_builder *builder)
+{
+ int res;
+
+ switch (type) {
+ case SPA_TYPE_Id:
+ spa_pod_builder_id(builder, pw_protocol_native0_type_from_v2(client, *(int32_t*) body));
+ break;
+
+ /** choice was props in v2 */
+ case SPA_TYPE_Choice:
+ {
+ struct spa_pod_prop_body0 *b = body;
+ struct spa_pod_frame f;
+ void *alt;
+ uint32_t key = pw_protocol_native0_type_from_v2(client, b->key);
+ enum spa_choice_type type;
+
+ spa_pod_builder_prop(builder, key, 0);
+
+ switch (b->flags & SPA_POD_PROP0_RANGE_MASK) {
+ default:
+ case SPA_POD_PROP0_RANGE_NONE:
+ type = SPA_CHOICE_None;
+ break;
+ case SPA_POD_PROP0_RANGE_MIN_MAX:
+ type = SPA_CHOICE_Range;
+ break;
+ case SPA_POD_PROP0_RANGE_STEP:
+ type = SPA_CHOICE_Step;
+ break;
+ case SPA_POD_PROP0_RANGE_ENUM:
+ type = SPA_CHOICE_Enum;
+ break;
+ case SPA_POD_PROP0_RANGE_FLAGS:
+ type = SPA_CHOICE_Flags;
+ break;
+ }
+ if (!SPA_FLAG_IS_SET(b->flags, SPA_POD_PROP0_FLAG_UNSET) &&
+ SPA0_POD_PROP_N_VALUES(b, size) == 1)
+ type = SPA_CHOICE_None;
+
+ spa_pod_builder_push_choice(builder, &f, type, 0);
+
+ if (b->value.type == SPA_TYPE_Id) {
+ uint32_t id;
+ if ((res = spa_pod_get_id(&b->value, &id)) < 0)
+ return res;
+ spa_pod_builder_id(builder, pw_protocol_native0_type_from_v2(client, id));
+ SPA_POD_PROP_ALTERNATIVE_FOREACH0(b, size, alt)
+ if ((res = remap_from_v2(b->value.type, alt, b->value.size, client, builder)) < 0)
+ return res;
+ } else {
+ spa_pod_builder_raw(builder, &b->value, size - sizeof(struct spa_pod));
+ }
+
+ spa_pod_builder_pop(builder, &f);
+
+ break;
+ }
+ case SPA_TYPE_Object:
+ {
+ struct spa_pod_object_body *b = body;
+ struct spa_pod *p;
+ struct spa_pod_frame f;
+ uint32_t type, count = 0;
+
+ /* type and id are switched */
+ type = pw_protocol_native0_type_from_v2(client, b->id),
+ spa_pod_builder_push_object(builder, &f, type,
+ pw_protocol_native0_type_from_v2(client, b->type));
+
+ /* object contained pods in v2 */
+ SPA_POD_OBJECT_BODY_FOREACH0(b, size, p) {
+ if (type == SPA_TYPE_OBJECT_Format && count < 2) {
+ uint32_t id;
+ if (spa_pod_get_id(p, &id) < 0)
+ continue;
+ id = pw_protocol_native0_type_from_v2(client, id);
+
+ if (count == 0) {
+ spa_pod_builder_prop(builder, SPA_FORMAT_mediaType, 0);
+ spa_pod_builder_id(builder, id);
+ }
+ if (count == 1) {
+ spa_pod_builder_prop(builder, SPA_FORMAT_mediaSubtype, 0);
+ spa_pod_builder_id(builder, id);
+ }
+ count++;
+ continue;
+ }
+ if ((res = remap_from_v2(p->type,
+ SPA_POD_BODY(p),
+ p->size,
+ client, builder)) < 0)
+ return res;
+ }
+ spa_pod_builder_pop(builder, &f);
+ break;
+ }
+ case SPA_TYPE_Struct:
+ {
+ struct spa_pod *b = body, *p;
+ struct spa_pod_frame f;
+
+ spa_pod_builder_push_struct(builder, &f);
+ SPA_POD_FOREACH(b, size, p)
+ if ((res = remap_from_v2(p->type, SPA_POD_BODY(p), p->size, client, builder)) < 0)
+ return res;
+ spa_pod_builder_pop(builder, &f);
+ break;
+ }
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int remap_to_v2(struct pw_impl_client *client, const struct spa_type_info *info,
+ uint32_t type, void *body, uint32_t size,
+ struct spa_pod_builder *builder)
+{
+ int res;
+
+ switch (type) {
+ case SPA_TYPE_Id:
+ spa_pod_builder_id(builder, pw_protocol_native0_type_to_v2(client, info, *(int32_t*) body));
+ break;
+
+ case SPA_TYPE_Object:
+ {
+ struct spa_pod_object_body *b = body;
+ struct spa_pod_prop *p;
+ struct spa_pod_frame f[2];
+ uint32_t type;
+ const struct spa_type_info *ti, *ii;
+
+ ti = spa_debug_type_find(info, b->type);
+ ii = ti ? spa_debug_type_find(ti->values, 0) : NULL;
+
+ if (b->type == SPA_TYPE_COMMAND_Node ||
+ b->type == SPA_TYPE_EVENT_Node) {
+ spa_pod_builder_push_object(builder, &f[0], 0,
+ pw_protocol_native0_type_to_v2(client, ii ? ii->values : NULL, b->id));
+ } else {
+ ii = ii ? spa_debug_type_find(ii->values, b->id) : NULL;
+ /* type and id are switched */
+ type = pw_protocol_native0_type_to_v2(client, info, b->type),
+ spa_pod_builder_push_object(builder, &f[0],
+ pw_protocol_native0_type_to_v2(client, ii ? ii->values : NULL, b->id), type);
+ }
+
+
+ info = ti ? ti->values : info;
+
+ SPA_POD_OBJECT_BODY_FOREACH(b, size, p) {
+ uint32_t key, flags;
+ uint32_t n_vals, choice;
+ struct spa_pod *values;
+
+ ii = spa_debug_type_find(info, p->key);
+
+ values = spa_pod_get_values(&p->value, &n_vals, &choice);
+
+ if (b->type == SPA_TYPE_OBJECT_Format &&
+ (p->key == SPA_FORMAT_mediaType ||
+ p->key == SPA_FORMAT_mediaSubtype)) {
+ uint32_t val;
+
+ if (spa_pod_get_id(values, &val) < 0)
+ continue;
+ spa_pod_builder_id(builder,
+ pw_protocol_native0_type_to_v2(client, ii ? ii->values : NULL, val));
+ continue;
+ }
+
+ flags = 0;
+ switch(choice) {
+ case SPA_CHOICE_None:
+ flags |= SPA_POD_PROP0_RANGE_NONE;
+ break;
+ case SPA_CHOICE_Range:
+ flags |= SPA_POD_PROP0_RANGE_MIN_MAX | SPA_POD_PROP0_FLAG_UNSET;
+ break;
+ case SPA_CHOICE_Step:
+ flags |= SPA_POD_PROP0_RANGE_STEP | SPA_POD_PROP0_FLAG_UNSET;
+ break;
+ case SPA_CHOICE_Enum:
+ flags |= SPA_POD_PROP0_RANGE_ENUM | SPA_POD_PROP0_FLAG_UNSET;
+ break;
+ case SPA_CHOICE_Flags:
+ flags |= SPA_POD_PROP0_RANGE_FLAGS | SPA_POD_PROP0_FLAG_UNSET;
+ break;
+ }
+
+ key = pw_protocol_native0_type_to_v2(client, info, p->key);
+
+ spa_pod_builder_push_choice(builder, &f[1], key, flags);
+
+ if (values->type == SPA_TYPE_Id) {
+ uint32_t i, *id = SPA_POD_BODY(values);
+
+ for (i = 0; i < n_vals; i++) {
+ spa_pod_builder_id(builder,
+ pw_protocol_native0_type_to_v2(client, ii ? ii->values : NULL, id[i]));
+ }
+
+ } else {
+ spa_pod_builder_raw(builder, values, sizeof(struct spa_pod) + n_vals * values->size);
+ }
+ spa_pod_builder_pop(builder, &f[1]);
+ }
+ spa_pod_builder_pop(builder, &f[0]);
+ break;
+ }
+ case SPA_TYPE_Struct:
+ {
+ struct spa_pod *b = body, *p;
+ struct spa_pod_frame f;
+
+ spa_pod_builder_push_struct(builder, &f);
+ SPA_POD_FOREACH(b, size, p)
+ if ((res = remap_to_v2(client, info, p->type, SPA_POD_BODY(p), p->size, builder)) < 0)
+ return res;
+ spa_pod_builder_pop(builder, &f);
+ break;
+ }
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+
+
+SPA_EXPORT
+struct spa_pod * pw_protocol_native0_pod_from_v2(struct pw_impl_client *client, const struct spa_pod *pod)
+{
+ uint8_t buffer[4096];
+ struct spa_pod *copy;
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, 4096);
+ int res;
+
+ if (pod == NULL)
+ return NULL;
+
+ if ((res = remap_from_v2(SPA_POD_TYPE(pod),
+ SPA_POD_BODY(pod),
+ SPA_POD_BODY_SIZE(pod),
+ client, &b)) < 0) {
+ errno = -res;
+ return NULL;
+ }
+ copy = spa_pod_copy(b.data);
+ return copy;
+}
+
+SPA_EXPORT
+int pw_protocol_native0_pod_to_v2(struct pw_impl_client *client, const struct spa_pod *pod,
+ struct spa_pod_builder *b)
+{
+ int res;
+
+ if (pod == NULL) {
+ spa_pod_builder_none(b);
+ return 0;
+ }
+
+ if ((res = remap_to_v2(client, pw_type_info(),
+ SPA_POD_TYPE(pod),
+ SPA_POD_BODY(pod),
+ SPA_POD_BODY_SIZE(pod),
+ b)) < 0) {
+ return -res;
+ }
+ return 0;
+}
+
+static int core_demarshal_create_object(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ uint32_t version, type, new_id, i;
+ const char *factory_name, *type_name;
+ struct spa_dict props;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f) < 0 ||
+ spa_pod_parser_get(&prs,
+ "s", &factory_name,
+ "I", &type,
+ "i", &version,
+ "i", &props.n_items, NULL) < 0)
+ return -EINVAL;
+
+ props.items = alloca(props.n_items * sizeof(struct spa_dict_item));
+ for (i = 0; i < props.n_items; i++) {
+ if (spa_pod_parser_get(&prs,
+ "s", &props.items[i].key,
+ "s", &props.items[i].value, NULL) < 0)
+ return -EINVAL;
+ }
+ if (spa_pod_parser_get(&prs, "i", &new_id, NULL) < 0)
+ return -EINVAL;
+
+ type_name = pw_protocol_native0_name_from_v2(client, type);
+ if (type_name == NULL)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_core_methods, create_object, 0, factory_name,
+ type_name, version,
+ &props, new_id);
+}
+
+static int core_demarshal_destroy(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object, *r;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_parser prs;
+ uint32_t id;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ "i", &id, NULL) < 0)
+ return -EINVAL;
+
+ pw_log_debug("client %p: destroy resource %u", client, id);
+
+ if ((r = pw_impl_client_find_resource(client, id)) == NULL)
+ goto no_resource;
+
+ return pw_resource_notify(resource, struct pw_core_methods, destroy, 0, r);
+
+no_resource:
+ pw_log_error("client %p: unknown resource %u op:%u", client, id, msg->opcode);
+ pw_resource_errorf(resource, -ENOENT, "unknown resource %d op:%u", id, msg->opcode);
+ return 0;
+}
+
+static int core_demarshal_update_types_server(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct protocol_compat_v2 *compat_v2 = client->compat_v2;
+ struct spa_pod_parser prs;
+ uint32_t first_id, n_types;
+ struct spa_pod_frame f;
+ const char **types;
+ uint32_t i;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs, &f) < 0 ||
+ spa_pod_parser_get(&prs,
+ "i", &first_id,
+ "i", &n_types,
+ NULL) < 0)
+ return -EINVAL;
+
+ if (first_id == 0)
+ compat_v2->send_types = true;
+
+ types = alloca(n_types * sizeof(char *));
+ for (i = 0; i < n_types; i++) {
+ if (spa_pod_parser_get(&prs, "s", &types[i], NULL) < 0)
+ return -EINVAL;
+ }
+
+ for (i = 0; i < n_types; i++, first_id++) {
+ uint32_t type_id = pw_protocol_native0_find_type(client, types[i]);
+ if (type_id == SPA_ID_INVALID)
+ continue;
+ if (pw_map_insert_at(&compat_v2->types, first_id, PW_MAP_ID_TO_PTR(type_id)) < 0)
+ pw_log_error("can't add type %d->%d for client", first_id, type_id);
+ }
+ return 0;
+}
+
+static void registry_marshal_global(void *data, uint32_t id, uint32_t permissions,
+ const char *type, uint32_t version, const struct spa_dict *props)
+{
+ struct pw_resource *resource = data;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, n_items, parent_id;
+ uint32_t type_id;
+ const char *str;
+
+ type_id = pw_protocol_native0_find_type(client, type);
+ if (type_id == SPA_ID_INVALID)
+ return;
+
+ b = pw_protocol_native_begin_resource(resource, PW_REGISTRY_V0_EVENT_GLOBAL, NULL);
+
+ n_items = props ? props->n_items : 0;
+
+ parent_id = 0;
+ if (props) {
+ if (spa_streq(type, PW_TYPE_INTERFACE_Port)) {
+ if ((str = spa_dict_lookup(props, "node.id")) != NULL)
+ parent_id = atoi(str);
+ } else if (spa_streq(type, PW_TYPE_INTERFACE_Node)) {
+ if ((str = spa_dict_lookup(props, "device.id")) != NULL)
+ parent_id = atoi(str);
+ } else if (spa_streq(type, PW_TYPE_INTERFACE_Client) ||
+ spa_streq(type, PW_TYPE_INTERFACE_Device) ||
+ spa_streq(type, PW_TYPE_INTERFACE_Factory)) {
+ if ((str = spa_dict_lookup(props, "module.id")) != NULL)
+ parent_id = atoi(str);
+ }
+ }
+
+ version = 0;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ "i", id,
+ "i", parent_id,
+ "i", permissions,
+ "I", type_id,
+ "i", version,
+ "i", n_items, NULL);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ "s", props->items[i].key,
+ "s", props->items[i].value, NULL);
+ }
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void registry_marshal_global_remove(void *data, uint32_t id)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource, PW_REGISTRY_V0_EVENT_GLOBAL_REMOVE, NULL);
+
+ spa_pod_builder_add_struct(b, "i", id);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int registry_demarshal_bind(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_parser prs;
+ uint32_t id, version, type, new_id;
+ const char *type_name;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ "i", &id,
+ "I", &type,
+ "i", &version,
+ "i", &new_id) < 0)
+ return -EINVAL;
+
+ type_name = pw_protocol_native0_name_from_v2(client, type);
+ if (type_name == NULL)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_registry_methods, bind, 0, id, type_name, version, new_id);
+}
+
+static void module_marshal_info(void *data, const struct pw_module_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, n_items;
+
+ b = pw_protocol_native_begin_resource(resource, PW_MODULE_V0_EVENT_INFO, NULL);
+
+ n_items = info->props ? info->props->n_items : 0;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ "i", info->id,
+ "l", info->change_mask,
+ "s", info->name,
+ "s", info->filename,
+ "s", info->args,
+ "i", n_items, NULL);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ "s", info->props->items[i].key,
+ "s", info->props->items[i].value, NULL);
+ }
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void factory_marshal_info(void *data, const struct pw_factory_info *info)
+{
+ struct pw_resource *resource = data;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, n_items, type, version;
+
+ type = pw_protocol_native0_find_type(client, info->type);
+ if (type == SPA_ID_INVALID)
+ return;
+
+ b = pw_protocol_native_begin_resource(resource, PW_FACTORY_V0_EVENT_INFO, NULL);
+
+ n_items = info->props ? info->props->n_items : 0;
+
+ version = 0;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ "i", info->id,
+ "l", info->change_mask,
+ "s", info->name,
+ "I", type,
+ "i", version,
+ "i", n_items, NULL);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ "s", info->props->items[i].key,
+ "s", info->props->items[i].value, NULL);
+ }
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void node_marshal_info(void *data, const struct pw_node_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, n_items;
+
+ b = pw_protocol_native_begin_resource(resource, PW_NODE_V0_EVENT_INFO, NULL);
+
+ n_items = info->props ? info->props->n_items : 0;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ "i", info->id,
+ "l", info->change_mask,
+ "s", "node.name",
+ "i", info->max_input_ports,
+ "i", info->n_input_ports,
+ "i", info->max_output_ports,
+ "i", info->n_output_ports,
+ "i", info->state,
+ "s", info->error,
+ "i", n_items, NULL);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ "s", info->props->items[i].key,
+ "s", info->props->items[i].value, NULL);
+ }
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void node_marshal_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_NODE_V0_EVENT_PARAM, NULL);
+
+ id = pw_protocol_native0_type_to_v2(client, spa_type_param, id),
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ "I", id,
+ "i", index,
+ "i", next,
+ NULL);
+ pw_protocol_native0_pod_to_v2(client, (struct spa_pod *)param, b);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int node_demarshal_enum_params(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_parser prs;
+ uint32_t id, index, num;
+ struct spa_pod *filter;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ "I", &id,
+ "i", &index,
+ "i", &num,
+ "P", &filter) < 0)
+ return -EINVAL;
+
+ id = pw_protocol_native0_type_from_v2(client, id);
+ filter = NULL;
+
+ return pw_resource_notify(resource, struct pw_node_methods, enum_params, 0,
+ 0, id, index, num, filter);
+}
+
+static void port_marshal_info(void *data, const struct pw_port_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, n_items;
+ uint64_t change_mask = 0;
+ const char *port_name;
+
+ b = pw_protocol_native_begin_resource(resource, PW_PORT_V0_EVENT_INFO, NULL);
+
+ n_items = info->props ? info->props->n_items : 0;
+
+#define PW_PORT_V0_CHANGE_MASK_NAME (1 << 0)
+#define PW_PORT_V0_CHANGE_MASK_PROPS (1 << 1)
+#define PW_PORT_V0_CHANGE_MASK_ENUM_PARAMS (1 << 2)
+
+ change_mask |= PW_PORT_V0_CHANGE_MASK_NAME;
+ if (info->change_mask & PW_PORT_CHANGE_MASK_PROPS)
+ change_mask |= PW_PORT_V0_CHANGE_MASK_PROPS;
+ if (info->change_mask & PW_PORT_CHANGE_MASK_PARAMS)
+ change_mask |= PW_PORT_V0_CHANGE_MASK_ENUM_PARAMS;
+
+ port_name = NULL;
+ if (info->props != NULL)
+ port_name = spa_dict_lookup(info->props, "port.name");
+ if (port_name == NULL)
+ port_name = "port.name";
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ "i", info->id,
+ "l", change_mask,
+ "s", port_name,
+ "i", n_items, NULL);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ "s", info->props->items[i].key,
+ "s", info->props->items[i].value, NULL);
+ }
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void port_marshal_param(void *data, int seq, uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+
+ b = pw_protocol_native_begin_resource(resource, PW_PORT_V0_EVENT_PARAM, NULL);
+
+ id = pw_protocol_native0_type_to_v2(client, spa_type_param, id),
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ "I", id,
+ "i", index,
+ "i", next,
+ NULL);
+ pw_protocol_native0_pod_to_v2(client, (struct spa_pod *)param, b);
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int port_demarshal_enum_params(void *object, const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ struct spa_pod_parser prs;
+ uint32_t id, index, num;
+ struct spa_pod *filter;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ "I", &id,
+ "i", &index,
+ "i", &num,
+ "P", &filter) < 0)
+ return -EINVAL;
+
+ id = pw_protocol_native0_type_from_v2(client, id);
+ filter = NULL;
+
+ return pw_resource_notify(resource, struct pw_port_methods, enum_params, 0,
+ 0, id, index, num, filter);
+}
+
+static void client_marshal_info(void *data, const struct pw_client_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, n_items;
+
+ b = pw_protocol_native_begin_resource(resource, PW_CLIENT_V0_EVENT_INFO, NULL);
+
+ n_items = info->props ? info->props->n_items : 0;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ "i", info->id,
+ "l", info->change_mask,
+ "i", n_items, NULL);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ "s", info->props->items[i].key,
+ "s", info->props->items[i].value, NULL);
+ }
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void client_marshal_permissions(void *data, uint32_t index, uint32_t n_permissions,
+ const struct pw_permission *permissions)
+{
+}
+
+
+static void link_marshal_info(void *data, const struct pw_link_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i, n_items;
+
+ b = pw_protocol_native_begin_resource(resource, PW_LINK_V0_EVENT_INFO, NULL);
+
+ n_items = info->props ? info->props->n_items : 0;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ "i", info->id,
+ "l", info->change_mask,
+ "i", info->output_node_id,
+ "i", info->output_port_id,
+ "i", info->input_node_id,
+ "i", info->input_port_id,
+ "P", info->format,
+ "i", n_items, NULL);
+
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ "s", info->props->items[i].key,
+ "s", info->props->items[i].value, NULL);
+ }
+ spa_pod_builder_pop(b, &f);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static const struct pw_protocol_native_demarshal pw_protocol_native_core_method_demarshal[PW_CORE_V0_METHOD_NUM] = {
+ [PW_CORE_V0_METHOD_HELLO] = { &core_demarshal_hello, 0, },
+ [PW_CORE_V0_METHOD_UPDATE_TYPES] = { &core_demarshal_update_types_server, 0, },
+ [PW_CORE_V0_METHOD_SYNC] = { &core_demarshal_sync, 0, },
+ [PW_CORE_V0_METHOD_GET_REGISTRY] = { &core_demarshal_get_registry, 0, },
+ [PW_CORE_V0_METHOD_CLIENT_UPDATE] = { &core_demarshal_client_update, 0, },
+ [PW_CORE_V0_METHOD_PERMISSIONS] = { &core_demarshal_permissions, 0, },
+ [PW_CORE_V0_METHOD_CREATE_OBJECT] = { &core_demarshal_create_object, 0, PW_PROTOCOL_NATIVE_FLAG_REMAP, },
+ [PW_CORE_V0_METHOD_DESTROY] = { &core_demarshal_destroy, 0, }
+};
+
+static const struct pw_core_events pw_protocol_native_core_event_marshal = {
+ PW_VERSION_CORE_EVENTS,
+ .info = &core_marshal_info,
+ .done = &core_marshal_done,
+ .error = &core_marshal_error,
+ .remove_id = &core_marshal_remove_id,
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_core_marshal = {
+ PW_TYPE_INTERFACE_Core,
+ PW_VERSION_CORE_V0,
+ PW_CORE_V0_METHOD_NUM,
+ PW_CORE_EVENT_NUM,
+ 0,
+ NULL,
+ pw_protocol_native_core_method_demarshal,
+ &pw_protocol_native_core_event_marshal,
+ NULL
+};
+
+static const struct pw_protocol_native_demarshal pw_protocol_native_registry_method_demarshal[] = {
+ [PW_REGISTRY_V0_METHOD_BIND] = { &registry_demarshal_bind, 0, PW_PROTOCOL_NATIVE_FLAG_REMAP, },
+};
+
+static const struct pw_registry_events pw_protocol_native_registry_event_marshal = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = &registry_marshal_global,
+ .global_remove = &registry_marshal_global_remove,
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_registry_marshal = {
+ PW_TYPE_INTERFACE_Registry,
+ PW_VERSION_REGISTRY_V0,
+ PW_REGISTRY_V0_METHOD_NUM,
+ PW_REGISTRY_EVENT_NUM,
+ 0,
+ NULL,
+ pw_protocol_native_registry_method_demarshal,
+ &pw_protocol_native_registry_event_marshal,
+ NULL
+};
+
+static const struct pw_module_events pw_protocol_native_module_event_marshal = {
+ PW_VERSION_MODULE_EVENTS,
+ .info = &module_marshal_info,
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_module_marshal = {
+ PW_TYPE_INTERFACE_Module,
+ PW_VERSION_MODULE_V0,
+ 0,
+ PW_MODULE_EVENT_NUM,
+ 0,
+ NULL, NULL,
+ &pw_protocol_native_module_event_marshal,
+ NULL
+};
+
+static const struct pw_factory_events pw_protocol_native_factory_event_marshal = {
+ PW_VERSION_FACTORY_EVENTS,
+ .info = &factory_marshal_info,
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_factory_marshal = {
+ PW_TYPE_INTERFACE_Factory,
+ PW_VERSION_FACTORY_V0,
+ 0,
+ PW_FACTORY_EVENT_NUM,
+ 0,
+ NULL, NULL,
+ &pw_protocol_native_factory_event_marshal,
+ NULL,
+};
+
+static const struct pw_protocol_native_demarshal pw_protocol_native_node_method_demarshal[] = {
+ [PW_NODE_V0_METHOD_ENUM_PARAMS] = { &node_demarshal_enum_params, 0, PW_PROTOCOL_NATIVE_FLAG_REMAP, },
+};
+
+static const struct pw_node_events pw_protocol_native_node_event_marshal = {
+ PW_VERSION_NODE_EVENTS,
+ .info = &node_marshal_info,
+ .param = &node_marshal_param,
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_node_marshal = {
+ PW_TYPE_INTERFACE_Node,
+ PW_VERSION_NODE_V0,
+ PW_NODE_V0_METHOD_NUM,
+ PW_NODE_EVENT_NUM,
+ 0,
+ NULL,
+ pw_protocol_native_node_method_demarshal,
+ &pw_protocol_native_node_event_marshal,
+ NULL
+};
+
+
+static const struct pw_protocol_native_demarshal pw_protocol_native_port_method_demarshal[] = {
+ [PW_PORT_V0_METHOD_ENUM_PARAMS] = { &port_demarshal_enum_params, 0, PW_PROTOCOL_NATIVE_FLAG_REMAP, },
+};
+
+static const struct pw_port_events pw_protocol_native_port_event_marshal = {
+ PW_VERSION_PORT_EVENTS,
+ .info = &port_marshal_info,
+ .param = &port_marshal_param,
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_port_marshal = {
+ PW_TYPE_INTERFACE_Port,
+ PW_VERSION_PORT_V0,
+ PW_PORT_V0_METHOD_NUM,
+ PW_PORT_EVENT_NUM,
+ 0,
+ NULL,
+ pw_protocol_native_port_method_demarshal,
+ &pw_protocol_native_port_event_marshal,
+ NULL
+};
+
+static const struct pw_client_events pw_protocol_native_client_event_marshal = {
+ PW_VERSION_CLIENT_EVENTS,
+ .info = &client_marshal_info,
+ .permissions = &client_marshal_permissions,
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_client_marshal = {
+ PW_TYPE_INTERFACE_Client,
+ PW_VERSION_CLIENT_V0,
+ 0,
+ PW_CLIENT_EVENT_NUM,
+ 0,
+ NULL, NULL,
+ &pw_protocol_native_client_event_marshal,
+ NULL,
+};
+
+static const struct pw_link_events pw_protocol_native_link_event_marshal = {
+ PW_VERSION_LINK_EVENTS,
+ .info = &link_marshal_info,
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_link_marshal = {
+ PW_TYPE_INTERFACE_Link,
+ PW_VERSION_LINK_V0,
+ 0,
+ PW_LINK_EVENT_NUM,
+ 0,
+ NULL, NULL,
+ &pw_protocol_native_link_event_marshal,
+ NULL
+};
+
+void pw_protocol_native0_init(struct pw_protocol *protocol)
+{
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_core_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_registry_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_module_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_node_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_port_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_factory_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_client_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_link_marshal);
+}
diff --git a/src/modules/module-protocol-native/v0/typemap.h b/src/modules/module-protocol-native/v0/typemap.h
new file mode 100644
index 0000000..1d80ea2
--- /dev/null
+++ b/src/modules/module-protocol-native/v0/typemap.h
@@ -0,0 +1,282 @@
+static const struct type_info {
+ const char *type;
+ const char *name;
+ uint32_t id;
+} type_map[] = {
+ { "Spa:Interface:TypeMap", SPA_TYPE_INFO_INTERFACE_BASE, 0, },
+ { "Spa:Interface:Log", SPA_TYPE_INTERFACE_Log, 0, },
+ { "Spa:Interface:Loop", SPA_TYPE_INTERFACE_Loop, 0, },
+ { "Spa:Interface:LoopControl", SPA_TYPE_INTERFACE_LoopControl, 0, },
+ { "Spa:Interface:LoopUtils", SPA_TYPE_INTERFACE_LoopUtils, 0, },
+ { "PipeWire:Interface:Core", PW_TYPE_INTERFACE_Core, 0, },
+ { "PipeWire:Interface:Registry", PW_TYPE_INTERFACE_Registry, 0, },
+ { "PipeWire:Interface:Node", PW_TYPE_INTERFACE_Node, 0, },
+ { "PipeWire:Interface:Port", PW_TYPE_INTERFACE_Port,0, },
+ { "PipeWire:Interface:Factory", PW_TYPE_INTERFACE_Factory, 0, },
+ { "PipeWire:Interface:Link", PW_TYPE_INTERFACE_Link, 0, },
+ { "PipeWire:Interface:Client", PW_TYPE_INTERFACE_Client, 0, },
+ { "PipeWire:Interface:Module", PW_TYPE_INTERFACE_Module, 0, },
+ { "PipeWire:Interface:Device", PW_TYPE_INTERFACE_Device, 0, },
+
+ { "PipeWire:Interface:Metadata", PW_TYPE_INTERFACE_Metadata, 0, },
+ { "PipeWire:Interface:Session", PW_TYPE_INTERFACE_Session, 0, },
+ { "PipeWire:Interface:Endpoint", PW_TYPE_INTERFACE_Endpoint, 0, },
+ { "PipeWire:Interface:EndpointStream", PW_TYPE_INTERFACE_EndpointStream, 0, },
+ { "PipeWire:Interface:EndpointLink", PW_TYPE_INTERFACE_EndpointLink, 0, },
+
+ { "PipeWire:Interface:ClientNode", PW_TYPE_INTERFACE_ClientNode, 0, },
+ { "PipeWire:Interface:ClientSession", PW_TYPE_INTERFACE_ClientSession, 0, },
+ { "PipeWire:Interface:ClientEndpoint", PW_TYPE_INTERFACE_ClientEndpoint, 0, },
+
+ { "Spa:Interface:Node", SPA_TYPE_INTERFACE_Node, 0, },
+ { "Spa:Interface:Clock", },
+ { "Spa:Interface:Monitor", },
+ { "Spa:Interface:Device", SPA_TYPE_INTERFACE_Device, 0, },
+ { "Spa:POD:Object:Param:Format", SPA_TYPE_INFO_Format, SPA_TYPE_OBJECT_Format, },
+ { "Spa:POD:Object:Param:Props", SPA_TYPE_INFO_Props, SPA_TYPE_OBJECT_Props, },
+ { "Spa:Pointer:IO:Buffers", },
+ { "Spa:Pointer:IO:Control:Range", },
+ { "Spa:Pointer:IO:Prop", },
+ { "Spa:Enum:ParamId:List", },
+ { "Spa:POD:Object:Param:List", },
+ { "Spa:POD:Object:Param:List:id", },
+ { "Spa:Enum:ParamId:PropInfo", SPA_TYPE_INFO_PARAM_ID_BASE "PropInfo", SPA_PARAM_PropInfo, },
+ { "Spa:POD:Object:Param:PropInfo", SPA_TYPE_INFO_PROP_INFO_BASE, SPA_PROP_INFO_START, },
+ { "Spa:POD:Object:Param:PropInfo:id", SPA_TYPE_INFO_PROP_INFO_BASE "id", SPA_PROP_INFO_id, },
+ { "Spa:POD:Object:Param:PropInfo:name", SPA_TYPE_INFO_PROP_INFO_BASE "name", SPA_PROP_INFO_name, },
+ { "Spa:POD:Object:Param:PropInfo:type", SPA_TYPE_INFO_PROP_INFO_BASE "typ", SPA_PROP_INFO_type, },
+ { "Spa:POD:Object:Param:PropInfo:labels", SPA_TYPE_INFO_PROP_INFO_BASE "labels", SPA_PROP_INFO_labels, },
+ { "Spa:Enum:ParamId:Props", SPA_TYPE_INFO_PARAM_ID_BASE "Props", SPA_PARAM_Props, },
+ { "Spa:Enum:ParamId:EnumFormat", SPA_TYPE_INFO_PARAM_ID_BASE "EnumFormat", SPA_PARAM_EnumFormat,},
+ { "Spa:Enum:ParamId:Format", SPA_TYPE_INFO_PARAM_ID_BASE "Format", SPA_PARAM_Format, },
+ { "Spa:Enum:ParamId:Buffers", SPA_TYPE_INFO_PARAM_ID_BASE "Buffers", SPA_PARAM_Buffers },
+ { "Spa:Enum:ParamId:Meta", SPA_TYPE_INFO_PARAM_ID_BASE "Meta", SPA_PARAM_Meta, },
+ { "Spa:Pointer:Meta:Header", SPA_TYPE_INFO_META_BASE "Header", SPA_META_Header, },
+ { "Spa:Pointer:Meta:VideoCrop", SPA_TYPE_INFO_META_REGION_BASE "VideoCrop", SPA_META_VideoCrop, },
+ { "Spa:Pointer:Meta:VideoDamage", SPA_TYPE_INFO_META_ARRAY_REGION_BASE "VideoDamage", SPA_META_VideoDamage, },
+ { "Spa:Pointer:Meta:Bitmap", SPA_TYPE_INFO_META_BASE "Bitmap", SPA_META_Bitmap, },
+ { "Spa:Pointer:Meta:Cursor", SPA_TYPE_INFO_META_BASE "Cursor", SPA_META_Cursor, },
+ { "Spa:Enum:DataType:MemPtr", SPA_TYPE_INFO_DATA_BASE "MemPtr", SPA_DATA_MemPtr, },
+ { "Spa:Enum:DataType:Fd:MemFd", SPA_TYPE_INFO_DATA_FD_BASE "MemFd", SPA_DATA_MemFd, },
+ { "Spa:Enum:DataType:Fd:DmaBuf", SPA_TYPE_INFO_DATA_FD_BASE "DmaBuf", SPA_DATA_DmaBuf, },
+ { "Spa:POD:Object:Event:Node:Error", SPA_TYPE_INFO_NODE_EVENT_BASE "Error", SPA_NODE_EVENT_Error, },
+ { "Spa:POD:Object:Event:Node:Buffering", SPA_TYPE_INFO_NODE_EVENT_BASE "Buffering", SPA_NODE_EVENT_Buffering, },
+ { "Spa:POD:Object:Event:Node:RequestRefresh", SPA_TYPE_INFO_NODE_EVENT_BASE "RequestRefresh", SPA_NODE_EVENT_RequestRefresh, },
+ { "Spa:POD:Object:Event:Node:RequestClockUpdate", SPA_TYPE_INFO_NODE_EVENT_BASE "RequestClockUpdate", SPA_NODE0_EVENT_RequestClockUpdate, },
+ { "Spa:POD:Object:Command:Node", SPA_TYPE_INFO_COMMAND_BASE "Node", SPA_TYPE_COMMAND_Node,},
+ { "Spa:POD:Object:Command:Node:Suspend", SPA_TYPE_INFO_NODE_COMMAND_BASE "Suspend", SPA_NODE_COMMAND_Suspend,},
+ { "Spa:POD:Object:Command:Node:Pause", SPA_TYPE_INFO_NODE_COMMAND_BASE "Pause", SPA_NODE_COMMAND_Pause, },
+ { "Spa:POD:Object:Command:Node:Start", SPA_TYPE_INFO_NODE_COMMAND_BASE "Start", SPA_NODE_COMMAND_Start, },
+ { "Spa:POD:Object:Command:Node:Enable", SPA_TYPE_INFO_NODE_COMMAND_BASE "Enable", SPA_NODE_COMMAND_Enable, },
+ { "Spa:POD:Object:Command:Node:Disable", SPA_TYPE_INFO_NODE_COMMAND_BASE "Disable", SPA_NODE_COMMAND_Disable, },
+ { "Spa:POD:Object:Command:Node:Flush", SPA_TYPE_INFO_NODE_COMMAND_BASE "Flush", SPA_NODE_COMMAND_Flush, },
+ { "Spa:POD:Object:Command:Node:Drain", SPA_TYPE_INFO_NODE_COMMAND_BASE "Drain", SPA_NODE_COMMAND_Drain, },
+ { "Spa:POD:Object:Command:Node:Marker", SPA_TYPE_INFO_NODE_COMMAND_BASE "Marker", SPA_NODE_COMMAND_Marker, },
+ { "Spa:POD:Object:Command:Node:ClockUpdate", SPA_TYPE_INFO_NODE_COMMAND_BASE "ClockUpdate", SPA_NODE0_COMMAND_ClockUpdate, },
+ { "Spa:POD:Object:Event:Monitor:Added", },
+ { "Spa:POD:Object:Event:Monitor:Removed", },
+ { "Spa:POD:Object:Event:Monitor:Changed", },
+ { "Spa:POD:Object:MonitorItem", },
+ { "Spa:POD:Object:MonitorItem:id", },
+ { "Spa:POD:Object:MonitorItem:flags", },
+ { "Spa:POD:Object:MonitorItem:state", },
+ { "Spa:POD:Object:MonitorItem:name", },
+ { "Spa:POD:Object:MonitorItem:class", },
+ { "Spa:POD:Object:MonitorItem:info", },
+ { "Spa:POD:Object:MonitorItem:factory", },
+ { "Spa:POD:Object:Param:Buffers", SPA_TYPE_INFO_PARAM_Buffers, SPA_TYPE_OBJECT_ParamBuffers, },
+ { "Spa:POD:Object:Param:Buffers:size", SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "size", SPA_PARAM_BUFFERS_size, },
+ { "Spa:POD:Object:Param:Buffers:stride", SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "stride", SPA_PARAM_BUFFERS_stride, },
+ { "Spa:POD:Object:Param:Buffers:buffers", SPA_TYPE_INFO_PARAM_BUFFERS_BASE "buffers", SPA_PARAM_BUFFERS_buffers, },
+ { "Spa:POD:Object:Param:Buffers:align",SPA_TYPE_INFO_PARAM_BLOCK_INFO_BASE "align", SPA_PARAM_BUFFERS_align, },
+ { "Spa:POD:Object:Param:Meta", SPA_TYPE_INFO_PARAM_Meta, SPA_TYPE_OBJECT_ParamMeta, },
+ { "Spa:POD:Object:Param:Meta:type", SPA_TYPE_INFO_PARAM_META_BASE "type", SPA_PARAM_META_type, },
+ { "Spa:POD:Object:Param:Meta:size", SPA_TYPE_INFO_PARAM_META_BASE "size", SPA_PARAM_META_size, },
+ { "Spa:POD:Object:Param:IO:id", },
+ { "Spa:POD:Object:Param:IO:size", },
+ { "Spa:Enum:ParamId:IO:Buffers", },
+ { "Spa:POD:Object:Param:IO:Buffers", },
+ { "Spa:Enum:ParamId:IO:Control", },
+ { "Spa:POD:Object:Param:IO:Control", },
+ { "Spa:Enum:ParamId:IO:Props:In", },
+ { "Spa:Enum:ParamId:IO:Props:Out", },
+ { "Spa:POD:Object:Param:IO:Prop", },
+ { "Spa:Interface:DBus", SPA_TYPE_INFO_INTERFACE_BASE "DBus", 0, },
+ { "Spa:Enum:MediaType:audio", SPA_TYPE_INFO_MEDIA_TYPE_BASE "audio", SPA_MEDIA_TYPE_audio, },
+ { "Spa:Enum:MediaType:video", SPA_TYPE_INFO_MEDIA_TYPE_BASE "video", SPA_MEDIA_TYPE_video, },
+ { "Spa:Enum:MediaType:image", SPA_TYPE_INFO_MEDIA_TYPE_BASE "image", SPA_MEDIA_TYPE_image, },
+ { "Spa:Enum:MediaType:binary", SPA_TYPE_INFO_MEDIA_TYPE_BASE "binary", SPA_MEDIA_TYPE_binary, },
+ { "Spa:Enum:MediaType:stream", SPA_TYPE_INFO_MEDIA_TYPE_BASE "stream", SPA_MEDIA_TYPE_stream, },
+ { "Spa:Enum:MediaType:application", SPA_TYPE_INFO_MEDIA_TYPE_BASE "application", SPA_MEDIA_TYPE_application, },
+ { "Spa:Enum:MediaSubtype:raw", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "raw", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:dsp", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "dsp", SPA_MEDIA_SUBTYPE_dsp, },
+ { "Spa:Enum:MediaSubtype:control", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "control", SPA_MEDIA_SUBTYPE_control, },
+ { "Spa:Enum:MediaSubtype:h264", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "h264", SPA_MEDIA_SUBTYPE_h264, },
+ { "Spa:Enum:MediaSubtype:mjpg", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mjpg", SPA_MEDIA_SUBTYPE_mjpg, },
+ { "Spa:Enum:MediaSubtype:dv", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "dv", SPA_MEDIA_SUBTYPE_dv, },
+ { "Spa:Enum:MediaSubtype:mpegts", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpegts", SPA_MEDIA_SUBTYPE_mpegts, },
+ { "Spa:Enum:MediaSubtype:h263", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "h263", SPA_MEDIA_SUBTYPE_h263, },
+ { "Spa:Enum:MediaSubtype:mpeg1", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpeg1", SPA_MEDIA_SUBTYPE_mpeg1, },
+ { "Spa:Enum:MediaSubtype:mpeg2", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpeg2", SPA_MEDIA_SUBTYPE_mpeg2, },
+ { "Spa:Enum:MediaSubtype:mpeg4", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mpeg4", SPA_MEDIA_SUBTYPE_mpeg4, },
+ { "Spa:Enum:MediaSubtype:xvid", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "xvid", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:vc1", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vc1", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:vp8", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vp8", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:vp9", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vp9", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:jpeg", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "jpeg", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:bayer", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "bayer", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:mp3", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "mp3", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:aac", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "aac", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:vorbis", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "vorbis", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:wma", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "wma", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:ra", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "ra", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:sbc", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "sbc", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:adpcm", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "adpcm", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:g723", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "g723", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:g726", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "g726", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:g729", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "g729", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:amr", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "amr", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:gsm", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "gsm", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:Enum:MediaSubtype:midi", SPA_TYPE_INFO_MEDIA_SUBTYPE_BASE "midi", SPA_MEDIA_SUBTYPE_raw, },
+ { "Spa:POD:Object:Param:Format:Video:format", SPA_TYPE_INFO_FORMAT_VIDEO_BASE "format", SPA_FORMAT_VIDEO_format,},
+ { "Spa:POD:Object:Param:Format:Video:size", SPA_TYPE_INFO_FORMAT_VIDEO_BASE "size", SPA_FORMAT_VIDEO_size,},
+ { "Spa:POD:Object:Param:Format:Video:framerate", SPA_TYPE_INFO_FORMAT_VIDEO_BASE "framerate", SPA_FORMAT_VIDEO_framerate},
+ { "Spa:POD:Object:Param:Format:Video:max-framerate", SPA_TYPE_INFO_FORMAT_VIDEO_BASE "maxFramerate", SPA_FORMAT_VIDEO_maxFramerate},
+ { "Spa:POD:Object:Param:Format:Video:views", SPA_TYPE_INFO_FORMAT_VIDEO_BASE "views", SPA_FORMAT_VIDEO_views},
+ { "Spa:POD:Object:Param:Format:Video:interlace-mode", },
+ { "Spa:POD:Object:Param:Format:Video:pixel-aspect-ratio", },
+ { "Spa:POD:Object:Param:Format:Video:multiview-mode", },
+ { "Spa:POD:Object:Param:Format:Video:multiview-flags", },
+ { "Spa:POD:Object:Param:Format:Video:chroma-site", },
+ { "Spa:POD:Object:Param:Format:Video:color-range", },
+ { "Spa:POD:Object:Param:Format:Video:color-matrix", },
+ { "Spa:POD:Object:Param:Format:Video:transfer-function", },
+ { "Spa:POD:Object:Param:Format:Video:color-primaries", },
+ { "Spa:POD:Object:Param:Format:Video:profile", },
+ { "Spa:POD:Object:Param:Format:Video:level", },
+ { "Spa:POD:Object:Param:Format:Video:stream-format", },
+ { "Spa:POD:Object:Param:Format:Video:alignment", },
+ { "Spa:POD:Object:Param:Format:Audio:format", SPA_TYPE_INFO_FORMAT_AUDIO_BASE "format", SPA_FORMAT_AUDIO_format,},
+ { "Spa:POD:Object:Param:Format:Audio:flags", },
+ { "Spa:POD:Object:Param:Format:Audio:layout", },
+ { "Spa:POD:Object:Param:Format:Audio:rate", },
+ { "Spa:POD:Object:Param:Format:Audio:channels", },
+ { "Spa:POD:Object:Param:Format:Audio:channel-mask", },
+ { "Spa:Enum:VideoFormat:encoded", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "encoded", SPA_VIDEO_FORMAT_ENCODED,},
+ { "Spa:Enum:VideoFormat:I420", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420", SPA_VIDEO_FORMAT_I420,},
+ { "Spa:Enum:VideoFormat:YV12", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YV12", SPA_VIDEO_FORMAT_YV12,},
+ { "Spa:Enum:VideoFormat:YUY2", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YUY2", SPA_VIDEO_FORMAT_YUY2,},
+ { "Spa:Enum:VideoFormat:UYVY", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "UYVY", SPA_VIDEO_FORMAT_UYVY,},
+ { "Spa:Enum:VideoFormat:AYUV", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "AYUV", SPA_VIDEO_FORMAT_AYUV,},
+ { "Spa:Enum:VideoFormat:RGBx", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBx", SPA_VIDEO_FORMAT_RGBx,},
+ { "Spa:Enum:VideoFormat:BGRx", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGRx", SPA_VIDEO_FORMAT_BGRx,},
+ { "Spa:Enum:VideoFormat:xRGB", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "xRGB", SPA_VIDEO_FORMAT_xRGB,},
+ { "Spa:Enum:VideoFormat:xBGR", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "xBGR", SPA_VIDEO_FORMAT_xBGR,},
+ { "Spa:Enum:VideoFormat:RGBA", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBA", SPA_VIDEO_FORMAT_RGBA,},
+ { "Spa:Enum:VideoFormat:BGRA", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGRA", SPA_VIDEO_FORMAT_BGRA,},
+ { "Spa:Enum:VideoFormat:ARGB", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ARGB", SPA_VIDEO_FORMAT_ARGB,},
+ { "Spa:Enum:VideoFormat:ABGR", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ABGR", SPA_VIDEO_FORMAT_ABGR,},
+ { "Spa:Enum:VideoFormat:RGB", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB", SPA_VIDEO_FORMAT_RGB,},
+ { "Spa:Enum:VideoFormat:BGR", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGR", SPA_VIDEO_FORMAT_BGR,},
+ { "Spa:Enum:VideoFormat:Y41B", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y41B", SPA_VIDEO_FORMAT_Y41B,},
+ { "Spa:Enum:VideoFormat:Y42B", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y42B", SPA_VIDEO_FORMAT_Y42B,},
+ { "Spa:Enum:VideoFormat:YVYU", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YVYU", SPA_VIDEO_FORMAT_YVYU,},
+ { "Spa:Enum:VideoFormat:Y444", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444", SPA_VIDEO_FORMAT_Y444,},
+ { "Spa:Enum:VideoFormat:v210", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "v210", SPA_VIDEO_FORMAT_v210,},
+ { "Spa:Enum:VideoFormat:v216", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "v216", SPA_VIDEO_FORMAT_v216,},
+ { "Spa:Enum:VideoFormat:NV12", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV12", SPA_VIDEO_FORMAT_NV12,},
+ { "Spa:Enum:VideoFormat:NV21", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV21", SPA_VIDEO_FORMAT_NV21,},
+ { "Spa:Enum:VideoFormat:GRAY8", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GRAY8", SPA_VIDEO_FORMAT_GRAY8,},
+ { "Spa:Enum:VideoFormat:GRAY16_BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GRAY16_BE", SPA_VIDEO_FORMAT_GRAY16_BE,},
+ { "Spa:Enum:VideoFormat:GRAY16_LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GRAY16_LE", SPA_VIDEO_FORMAT_GRAY16_LE,},
+ { "Spa:Enum:VideoFormat:v308", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "v308", SPA_VIDEO_FORMAT_v308,},
+ { "Spa:Enum:VideoFormat:RGB16", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB16", SPA_VIDEO_FORMAT_RGB16,},
+ { "Spa:Enum:VideoFormat:BGR16", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGR16", SPA_VIDEO_FORMAT_BGR16,},
+ { "Spa:Enum:VideoFormat:RGB15", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB15", SPA_VIDEO_FORMAT_RGB15,},
+ { "Spa:Enum:VideoFormat:BGR15", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGR15", SPA_VIDEO_FORMAT_BGR15,},
+ { "Spa:Enum:VideoFormat:UYVP", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "UYVP", SPA_VIDEO_FORMAT_UYVP,},
+ { "Spa:Enum:VideoFormat:A420", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A420", SPA_VIDEO_FORMAT_A420,},
+ { "Spa:Enum:VideoFormat:RGB8P", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGB8P", SPA_VIDEO_FORMAT_RGB8P,},
+ { "Spa:Enum:VideoFormat:YUV9", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YUV9", SPA_VIDEO_FORMAT_YUV9,},
+ { "Spa:Enum:VideoFormat:YVU9", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "YVU9", SPA_VIDEO_FORMAT_YVU9,},
+ { "Spa:Enum:VideoFormat:IYU1", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "IYU1", SPA_VIDEO_FORMAT_IYU1,},
+ { "Spa:Enum:VideoFormat:ARGB64", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ARGB64", SPA_VIDEO_FORMAT_ARGB64,},
+ { "Spa:Enum:VideoFormat:AYUV64", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "AYUV64", SPA_VIDEO_FORMAT_AYUV64,},
+ { "Spa:Enum:VideoFormat:r210", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "r210", SPA_VIDEO_FORMAT_r210,},
+ { "Spa:Enum:VideoFormat:I420_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_10BE", SPA_VIDEO_FORMAT_I420_10BE,},
+ { "Spa:Enum:VideoFormat:I420_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_10LE", SPA_VIDEO_FORMAT_I420_10LE,},
+ { "Spa:Enum:VideoFormat:I422_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_10BE", SPA_VIDEO_FORMAT_I422_10BE,},
+ { "Spa:Enum:VideoFormat:I422_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_10LE", SPA_VIDEO_FORMAT_I422_10LE,},
+ { "Spa:Enum:VideoFormat:Y444_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_10BE", SPA_VIDEO_FORMAT_Y444_10BE,},
+ { "Spa:Enum:VideoFormat:Y444_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_10LE", SPA_VIDEO_FORMAT_Y444_10LE,},
+ { "Spa:Enum:VideoFormat:GBR", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR", SPA_VIDEO_FORMAT_GBR,},
+ { "Spa:Enum:VideoFormat:GBR_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_10BE", SPA_VIDEO_FORMAT_GBR_10BE,},
+ { "Spa:Enum:VideoFormat:GBR_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_10LE", SPA_VIDEO_FORMAT_GBR_10LE,},
+ { "Spa:Enum:VideoFormat:NV16", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV16", SPA_VIDEO_FORMAT_NV16,},
+ { "Spa:Enum:VideoFormat:NV24", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV24", SPA_VIDEO_FORMAT_NV24,},
+ { "Spa:Enum:VideoFormat:NV12_64Z32", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV12_64Z32", SPA_VIDEO_FORMAT_NV12_64Z32,},
+ { "Spa:Enum:VideoFormat:A420_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A420_10BE", SPA_VIDEO_FORMAT_A420_10BE,},
+ { "Spa:Enum:VideoFormat:A420_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A420_10LE", SPA_VIDEO_FORMAT_A420_10LE,},
+ { "Spa:Enum:VideoFormat:A422_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A422_10BE", SPA_VIDEO_FORMAT_A422_10BE,},
+ { "Spa:Enum:VideoFormat:A422_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A422_10LE", SPA_VIDEO_FORMAT_A422_10LE,},
+ { "Spa:Enum:VideoFormat:A444_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A444_10BE", SPA_VIDEO_FORMAT_A444_10BE,},
+ { "Spa:Enum:VideoFormat:A444_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "A444_10LE", SPA_VIDEO_FORMAT_A444_10LE,},
+ { "Spa:Enum:VideoFormat:NV61", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "NV61", SPA_VIDEO_FORMAT_NV61,},
+ { "Spa:Enum:VideoFormat:P010_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "P010_10BE", SPA_VIDEO_FORMAT_P010_10BE,},
+ { "Spa:Enum:VideoFormat:P010_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "P010_10LE", SPA_VIDEO_FORMAT_P010_10LE,},
+ { "Spa:Enum:VideoFormat:IYU2", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "IYU2", SPA_VIDEO_FORMAT_IYU2,},
+ { "Spa:Enum:VideoFormat:VYUY", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "VYUY", SPA_VIDEO_FORMAT_VYUY,},
+ { "Spa:Enum:VideoFormat:GBRA", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA", SPA_VIDEO_FORMAT_GBRA,},
+ { "Spa:Enum:VideoFormat:GBRA_10BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_10BE", SPA_VIDEO_FORMAT_GBRA_10BE,},
+ { "Spa:Enum:VideoFormat:GBRA_10LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_10LE", SPA_VIDEO_FORMAT_GBRA_10LE,},
+ { "Spa:Enum:VideoFormat:GBR_12BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_12BE", SPA_VIDEO_FORMAT_GBR_12BE,},
+ { "Spa:Enum:VideoFormat:GBR_12LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBR_12LE", SPA_VIDEO_FORMAT_GBR_12LE,},
+ { "Spa:Enum:VideoFormat:GBRA_12BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_12BE", SPA_VIDEO_FORMAT_GBRA_12BE,},
+ { "Spa:Enum:VideoFormat:GBRA_12LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "GBRA_12LE", SPA_VIDEO_FORMAT_GBRA_12LE,},
+ { "Spa:Enum:VideoFormat:I420_12BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_12BE", SPA_VIDEO_FORMAT_I420_12BE,},
+ { "Spa:Enum:VideoFormat:I420_12LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I420_12LE", SPA_VIDEO_FORMAT_I420_12LE,},
+ { "Spa:Enum:VideoFormat:I422_12BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_12BE", SPA_VIDEO_FORMAT_I422_12BE,},
+ { "Spa:Enum:VideoFormat:I422_12LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "I422_12LE", SPA_VIDEO_FORMAT_I422_12LE,},
+ { "Spa:Enum:VideoFormat:Y444_12BE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_12BE", SPA_VIDEO_FORMAT_Y444_12BE,},
+ { "Spa:Enum:VideoFormat:Y444_12LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "Y444_12LE", SPA_VIDEO_FORMAT_Y444_12LE,},
+ { "Spa:Enum:VideoFormat:xRGB_210LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "xRGB_210LE", SPA_VIDEO_FORMAT_xRGB_210LE,},
+ { "Spa:Enum:VideoFormat:xBGR_210LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "xBGR_210LE", SPA_VIDEO_FORMAT_xBGR_210LE,},
+ { "Spa:Enum:VideoFormat:RGBx_102LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBx_102LE", SPA_VIDEO_FORMAT_RGBx_102LE,},
+ { "Spa:Enum:VideoFormat:BGRx_102LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGRx_102LE", SPA_VIDEO_FORMAT_BGRx_102LE,},
+ { "Spa:Enum:VideoFormat:ARGB_210LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ARGB_210LE", SPA_VIDEO_FORMAT_ARGB_210LE,},
+ { "Spa:Enum:VideoFormat:ABGR_210LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "ABGR_210LE", SPA_VIDEO_FORMAT_ABGR_210LE,},
+ { "Spa:Enum:VideoFormat:RGBA_102LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "RGBA_102LE", SPA_VIDEO_FORMAT_RGBA_102LE,},
+ { "Spa:Enum:VideoFormat:BGRA_102LE", SPA_TYPE_INFO_VIDEO_FORMAT_BASE "BGRA_102LE", SPA_VIDEO_FORMAT_BGRA_102LE,},
+ { "Spa:Enum:AudioFormat:ENCODED", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "ENCODED", SPA_AUDIO_FORMAT_ENCODED,},
+ { "Spa:Enum:AudioFormat:S8", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S8", SPA_AUDIO_FORMAT_S8, },
+ { "Spa:Enum:AudioFormat:U8", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U8", SPA_AUDIO_FORMAT_U8, },
+ { "Spa:Enum:AudioFormat:S16LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S16_LE", SPA_AUDIO_FORMAT_S16_LE, },
+ { "Spa:Enum:AudioFormat:U16LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U16_LE", SPA_AUDIO_FORMAT_U16_LE, },
+ { "Spa:Enum:AudioFormat:S24_32LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_32_LE", SPA_AUDIO_FORMAT_S24_32_LE, },
+ { "Spa:Enum:AudioFormat:U24_32LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_32_LE", SPA_AUDIO_FORMAT_U24_32_LE, },
+ { "Spa:Enum:AudioFormat:S32LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S32_LE", SPA_AUDIO_FORMAT_S32_LE, },
+ { "Spa:Enum:AudioFormat:U32LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U32_LE", SPA_AUDIO_FORMAT_U32_LE, },
+ { "Spa:Enum:AudioFormat:S24LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_LE", SPA_AUDIO_FORMAT_S24_LE, },
+ { "Spa:Enum:AudioFormat:U24LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_LE", SPA_AUDIO_FORMAT_U24_LE, },
+ { "Spa:Enum:AudioFormat:S20LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S20_LE", SPA_AUDIO_FORMAT_S20_LE, },
+ { "Spa:Enum:AudioFormat:U20LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U20_LE", SPA_AUDIO_FORMAT_U20_LE, },
+ { "Spa:Enum:AudioFormat:S18LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S18_LE", SPA_AUDIO_FORMAT_S18_LE, },
+ { "Spa:Enum:AudioFormat:U18LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U18_LE", SPA_AUDIO_FORMAT_U18_LE, },
+ { "Spa:Enum:AudioFormat:F32LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32_LE", SPA_AUDIO_FORMAT_F32_LE, },
+ { "Spa:Enum:AudioFormat:F64LE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F64_LE", SPA_AUDIO_FORMAT_F64_LE, },
+ { "Spa:Enum:AudioFormat:S16BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S16_BE", SPA_AUDIO_FORMAT_S16_BE, },
+ { "Spa:Enum:AudioFormat:U16BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U16_BE", SPA_AUDIO_FORMAT_U16_BE, },
+ { "Spa:Enum:AudioFormat:S24_32BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_32_BE", SPA_AUDIO_FORMAT_S24_32_BE, },
+ { "Spa:Enum:AudioFormat:U24_32BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_32_BE", SPA_AUDIO_FORMAT_U24_32_BE, },
+ { "Spa:Enum:AudioFormat:S32BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S32_BE", SPA_AUDIO_FORMAT_S32_BE, },
+ { "Spa:Enum:AudioFormat:U32BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U32_BE", SPA_AUDIO_FORMAT_U32_BE, },
+ { "Spa:Enum:AudioFormat:S24BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S24_BE", SPA_AUDIO_FORMAT_S24_BE, },
+ { "Spa:Enum:AudioFormat:U24BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U24_BE", SPA_AUDIO_FORMAT_U24_BE, },
+ { "Spa:Enum:AudioFormat:S20BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S20_BE", SPA_AUDIO_FORMAT_S20_BE, },
+ { "Spa:Enum:AudioFormat:U20BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U20_BE", SPA_AUDIO_FORMAT_U20_BE, },
+ { "Spa:Enum:AudioFormat:S18BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "S18_BE", SPA_AUDIO_FORMAT_S18_BE, },
+ { "Spa:Enum:AudioFormat:U18BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "U18_BE", SPA_AUDIO_FORMAT_U18_BE, },
+ { "Spa:Enum:AudioFormat:F32BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32_BE", SPA_AUDIO_FORMAT_F32_BE, },
+ { "Spa:Enum:AudioFormat:F64BE", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F64_BE", SPA_AUDIO_FORMAT_F64_BE, },
+ { "Spa:Enum:AudioFormat:F32P", SPA_TYPE_INFO_AUDIO_FORMAT_BASE "F32P", SPA_AUDIO_FORMAT_F32P, },
+};
diff --git a/src/modules/module-protocol-pulse.c b/src/modules/module-protocol-pulse.c
new file mode 100644
index 0000000..eaf4c2f
--- /dev/null
+++ b/src/modules/module-protocol-pulse.c
@@ -0,0 +1,387 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+
+#include <pipewire/impl.h>
+
+#include "module-protocol-pulse/pulse-server.h"
+
+/** \page page_module_protocol_pulse PipeWire Module: Protocol Pulse
+ *
+ * This module implements a complete PulseAudio server on top of
+ * PipeWire. This is only the server implementation, client are expected
+ * to use the original PulseAudio client library. This provides a
+ * high level of compatibility with existing applications; in fact,
+ * all usual PulseAudio tools such as pavucontrol, pactl, pamon, paplay
+ * should continue to work as they did before.
+ *
+ * This module is usually loaded as part of a standalone pipewire process,
+ * called pipewire-pulse, with the pipewire-pulse.conf config file.
+ *
+ * The pulse server implements a sample cache that is otherwise not
+ * available in PipeWire.
+ *
+ * ## Module Options
+ *
+ * The module arguments can be the contents of the pulse.properties but
+ * it is recommended to make a separate pulse.properties section in the
+ * config file so that overrides can be done.
+ *
+ * ## pulse.properties
+ *
+ * A config section with server properties can be given.
+ *
+ *\code{.unparsed}
+ * pulse.properties = {
+ * # the addresses this server listens on
+ * server.address = [
+ * "unix:native"
+ * #"unix:/tmp/something" # absolute paths may be used
+ * #"tcp:4713" # IPv4 and IPv6 on all addresses
+ * #"tcp:[::]:9999" # IPv6 on all addresses
+ * #"tcp:127.0.0.1:8888" # IPv4 on a single address
+ * #
+ * #{ address = "tcp:4713" # address
+ * # max-clients = 64 # maximum number of clients
+ * # listen-backlog = 32 # backlog in the server listen queue
+ * # client.access = "restricted" # permissions for clients
+ * #}
+ * ]
+ * #pulse.min.req = 256/48000 # 5ms
+ * #pulse.default.req = 960/48000 # 20 milliseconds
+ * #pulse.min.frag = 256/48000 # 5ms
+ * #pulse.default.frag = 96000/48000 # 2 seconds
+ * #pulse.default.tlength = 96000/48000 # 2 seconds
+ * #pulse.min.quantum = 256/48000 # 5ms
+ * #pulse.default.format = F32
+ * #pulse.default.position = [ FL FR ]
+ * # These overrides are only applied when running in a vm.
+ * vm.overrides = {
+ * pulse.min.quantum = 1024/48000 # 22ms
+ * }
+ * }
+ *\endcode
+ *
+ * ### Connection options
+ *
+ *\code{.unparsed}
+ * ...
+ * server.address = [
+ * "unix:native"
+ * # "tcp:4713"
+ * ]
+ * ...
+ *\endcode
+ *
+ * The addresses the server listens on when starting. Uncomment the `tcp:4713` entry to also
+ * make the server listen on a tcp socket. This is equivalent to loading `module-native-protocol-tcp`.
+ *
+ * There is also a slightly more verbose syntax with more options:
+ *
+ *\code{.unparsed}
+ * ....
+ * server.address = [
+ * { address = "tcp:4713" # address
+ * max-clients = 64 # maximum number of clients
+ * listen-backlog = 32 # backlog in the server listen queue
+ * client.access = "restricted" # permissions for clients
+ * }
+ * ....
+ *\endcode
+ *
+ * Use `client.access` to use one of the access methods to restrict the permissions given to
+ * clients connected via this address.
+ *
+ * By default network access is given the "restricted" permissions. The session manager is responsible
+ * for assigning permission to clients with restricted permissions (usually read-only permissions).
+ *
+ * ### Playback buffering options
+ *
+ *\code{.unparsed}
+ * pulse.min.req = 256/48000 # 5ms
+ *\endcode
+ *
+ * The minimum amount of data to request for clients. The client requested
+ * values will be clamped to this value. Lowering this value together with
+ * tlength can decrease latency if the client wants this, but increase CPU overhead.
+ *
+ *\code{.unparsed}
+ * pulse.default.req = 960/48000 # 20 milliseconds
+ *\endcode
+ *
+ * The default amount of data to request for clients. If the client does not
+ * specify any particular value, this default will be used. Lowering this value
+ * together with tlength can decrease latency but increase CPU overhead.
+ *
+ *\code{.unparsed}
+ * pulse.default.tlength = 96000/48000 # 2 seconds
+ *\endcode
+ *
+ * The target amount of data to buffer on the server side. If the client did not
+ * specify a value, this default will be used. Lower values can decrease the
+ * latency.
+ *
+ * ### Record buffering options
+ *
+ *\code{.unparsed}
+ * pulse.min.frag = 256/48000 # 5ms
+ *\endcode
+ *
+ * The minimum allowed size of the capture buffer before it is sent to a client.
+ * The requested value of the client will be clamped to this. Lowering this value
+ * can reduce latency at the expense of more CPU usage.
+ *
+ *\code{.unparsed}
+ * pulse.default.frag = 96000/48000 # 2 seconds
+ *\endcode
+ *
+ * The default size of the capture buffer before it is sent to a client. If the client
+ * did not specify any value, this default will be used. Lowering this value can
+ * reduce latency at the expense of more CPU usage.
+ *
+ * ### Scheduling options
+ *
+ *\code{.unparsed}
+ * pulse.min.quantum = 256/48000 # 5ms
+ *\endcode
+ *
+ * The minimum quantum (buffer size in samples) to use for pulseaudio clients.
+ * This value is calculated based on the frag and req/tlength for record and
+ * playback streams respectively and then clamped to this value to ensure no
+ * pulseaudio client asks for too small quantums. Lowering this value might
+ * decrease latency at the expense of more CPU usage.
+ *
+ * ### Format options
+ *
+ *\code{.unparsed}
+ * pulse.default.format = F32
+ *\endcode
+ *
+ * Some modules will default to this format when no other format was given. This
+ * is equivalent to the PulseAudio `default-sample-format` option in
+ * `/etc/pulse/daemon.conf`.
+ *
+ *\code{.unparsed}
+ * pulse.default.position = [ FL FR ]
+ *\endcode
+ *
+ * Some modules will default to this channelmap (with its number of channels).
+ * This is equivalent to the PulseAudio `default-sample-channels` and
+ * `default-channel-map` options in `/etc/pulse/daemon.conf`.
+ *
+ * ### VM options
+ *
+ *\code{.unparsed}
+ * vm.overrides = {
+ * pulse.min.quantum = 1024/48000 # 22ms
+ * }
+ *\endcode
+ *
+ * When running in a VM, the `vm.override` section will override the properties
+ * in pulse.properties with the given values. This might be interesting because
+ * VMs usually can't support the low latency settings that are possible on real
+ * hardware.
+ *
+ * ## Stream settings and rules
+ *
+ * Streams created by module-protocol-pulse will use the stream.properties
+ * section and stream.rules sections as usual.
+ *
+ * ## Application settings (Rules)
+ *
+ * The pulse protocol module supports generic config rules. It supports a pulse.rules
+ * section with a `quirks` and an `update-props` action.
+ *
+ *\code{.unparsed}
+ * pulse.rules = [
+ * {
+ * # skype does not want to use devices that don't have an S16 sample format.
+ * matches = [
+ * { application.process.binary = "teams" }
+ * { application.process.binary = "teams-insiders" }
+ * { application.process.binary = "skypeforlinux" }
+ * ]
+ * actions = { quirks = [ force-s16-info ] }
+ * }
+ * {
+ * # speech dispatcher asks for too small latency and then underruns.
+ * matches = [ { application.name = "~speech-dispatcher*" } ]
+ * actions = {
+ * update-props = {
+ * pulse.min.req = 1024/48000 # 21ms
+ * pulse.min.quantum = 1024/48000 # 21ms
+ * }
+ * }
+ * }
+ * ]
+ *\endcode
+ *
+ * ### Quirks
+ *
+ * The quirks action takes an array of quirks to apply for the client.
+ *
+ * * `force-s16-info` makes the sink and source introspect code pretend that the sample format
+ * is S16 (16 bits) samples. Some application refuse the sink/source if this
+ * is not the case.
+ * * `remove-capture-dont-move` Removes the DONT_MOVE flag on capture streams. Some applications
+ * set this flag so that the stream can't be moved anymore with tools such as
+ * pavucontrol.
+ *
+ * ### update-props
+ *
+ * Takes an object with the properties to update on the client. Common actions are to
+ * tweak the quantum values.
+ *
+ * ## Example configuration
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-protocol-pulse
+ * args = { }
+ * }
+ * ]
+ *
+ * pulse.properties = {
+ * server.address = [ "unix:native" ]
+ * }
+ *
+ * pulse.rules = [
+ * {
+ * # skype does not want to use devices that don't have an S16 sample format.
+ * matches = [
+ * { application.process.binary = "teams" }
+ * { application.process.binary = "teams-insiders" }
+ * { application.process.binary = "skypeforlinux" }
+ * ]
+ * actions = { quirks = [ force-s16-info ] }
+ * }
+ * {
+ * # speech dispatcher asks for too small latency and then underruns.
+ * matches = [ { application.name = "~speech-dispatcher*" } ]
+ * actions = {
+ * update-props = {
+ * pulse.min.req = 1024/48000 # 21ms
+ * pulse.min.quantum = 1024/48000 # 21ms
+ * }
+ * }
+ * }
+ * ]
+ *\endcode
+ */
+
+#define NAME "protocol-pulse"
+
+PW_LOG_TOPIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+PW_LOG_TOPIC(pulse_conn, "conn." NAME);
+PW_LOG_TOPIC(pulse_ext_dev_restore, "mod." NAME ".device-restore");
+PW_LOG_TOPIC(pulse_ext_stream_restore, "mod." NAME ".stream-restore");
+
+#define MODULE_USAGE PW_PROTOCOL_PULSE_USAGE
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Implement a PulseAudio server" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct impl {
+ struct pw_context *context;
+
+ struct spa_hook module_listener;
+
+ struct pw_protocol_pulse *pulse;
+};
+
+static void impl_free(struct impl *impl)
+{
+ spa_hook_remove(&impl->module_listener);
+ if (impl->pulse)
+ pw_protocol_pulse_destroy(impl->pulse);
+ free(impl);
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ pw_log_debug("module %p: destroy", impl);
+ impl_free(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props;
+ struct impl *impl;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+ PW_LOG_TOPIC_INIT(pulse_conn);
+ /* it's easier to init these here than adding an init() call to the
+ * extensions */
+ PW_LOG_TOPIC_INIT(pulse_ext_dev_restore);
+ PW_LOG_TOPIC_INIT(pulse_ext_stream_restore);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args)
+ props = pw_properties_new_string(args);
+ else
+ props = NULL;
+
+ impl->pulse = pw_protocol_pulse_new(context, props, 0);
+ if (impl->pulse == NULL) {
+ res = -errno;
+ free(impl);
+ return res;
+ }
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+}
diff --git a/src/modules/module-protocol-pulse/client.c b/src/modules/module-protocol-pulse/client.c
new file mode 100644
index 0000000..1e6d202
--- /dev/null
+++ b/src/modules/module-protocol-pulse/client.c
@@ -0,0 +1,390 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+#include <spa/utils/list.h>
+#include <pipewire/core.h>
+#include <pipewire/log.h>
+#include <pipewire/loop.h>
+#include <pipewire/map.h>
+#include <pipewire/properties.h>
+
+#include "client.h"
+#include "commands.h"
+#include "defs.h"
+#include "internal.h"
+#include "log.h"
+#include "manager.h"
+#include "message.h"
+#include "operation.h"
+#include "pending-sample.h"
+#include "server.h"
+#include "stream.h"
+
+#define client_emit_disconnect(c) spa_hook_list_call(&(c)->listener_list, struct client_events, disconnect, 0)
+
+struct client *client_new(struct server *server)
+{
+ struct client *client = calloc(1, sizeof(*client));
+ if (client == NULL)
+ return NULL;
+
+ client->ref = 1;
+ client->server = server;
+ client->impl = server->impl;
+ client->connect_tag = SPA_ID_INVALID;
+
+ pw_map_init(&client->streams, 16, 16);
+ spa_list_init(&client->out_messages);
+ spa_list_init(&client->operations);
+ spa_list_init(&client->pending_samples);
+ spa_list_init(&client->pending_streams);
+ spa_hook_list_init(&client->listener_list);
+
+ spa_list_append(&server->clients, &client->link);
+ server->n_clients++;
+
+ return client;
+}
+
+static int client_free_stream(void *item, void *data)
+{
+ struct stream *s = item;
+
+ stream_free(s);
+ return 0;
+}
+
+/*
+ * tries to detach the client from the server,
+ * but it does not drop the server's reference
+ */
+bool client_detach(struct client *client)
+{
+ struct impl *impl = client->impl;
+ struct server *server = client->server;
+
+ if (server == NULL)
+ return false;
+
+ pw_log_debug("client %p: detaching from server %p", client, server);
+
+ /* remove from the `server->clients` list */
+ spa_list_remove(&client->link);
+ spa_list_append(&impl->cleanup_clients, &client->link);
+
+ server->n_clients--;
+ if (server->wait_clients > 0 && --server->wait_clients == 0) {
+ int mask = server->source->mask;
+ SPA_FLAG_SET(mask, SPA_IO_IN);
+ pw_loop_update_io(impl->loop, server->source, mask);
+ }
+
+ client->server = NULL;
+
+ return true;
+}
+
+void client_disconnect(struct client *client)
+{
+ struct impl *impl = client->impl;
+
+ if (client->disconnect)
+ return;
+
+ client_emit_disconnect(client);
+
+ /* the client must be detached from the server to disconnect */
+ spa_assert(client->server == NULL);
+
+ client->disconnect = true;
+
+ pw_map_for_each(&client->streams, client_free_stream, client);
+
+ if (client->source) {
+ pw_loop_destroy_source(impl->loop, client->source);
+ client->source = NULL;
+ }
+
+ if (client->manager) {
+ pw_manager_destroy(client->manager);
+ client->manager = NULL;
+ }
+}
+
+void client_free(struct client *client)
+{
+ struct impl *impl = client->impl;
+ struct pending_sample *p;
+ struct message *msg;
+ struct operation *o;
+
+ pw_log_debug("client %p: free", client);
+
+ client_detach(client);
+ client_disconnect(client);
+
+ /* remove from the `impl->cleanup_clients` list */
+ spa_list_remove(&client->link);
+
+ spa_list_consume(p, &client->pending_samples, link)
+ pending_sample_free(p);
+
+ if (client->message)
+ message_free(client->message, false, false);
+
+ spa_list_consume(msg, &client->out_messages, link)
+ message_free(msg, true, false);
+
+ spa_list_consume(o, &client->operations, link)
+ operation_free(o);
+
+ if (client->core)
+ pw_core_disconnect(client->core);
+
+ pw_map_clear(&client->streams);
+
+ pw_work_queue_cancel(impl->work_queue, client, SPA_ID_INVALID);
+
+ free(client->default_sink);
+ free(client->default_source);
+
+ free(client->temporary_default_sink);
+ free(client->temporary_default_source);
+
+ pw_properties_free(client->props);
+ pw_properties_free(client->routes);
+
+ spa_hook_list_clean(&client->listener_list);
+
+ free(client);
+}
+
+int client_queue_message(struct client *client, struct message *msg)
+{
+ struct impl *impl = client->impl;
+ int res;
+
+ if (msg == NULL)
+ return -EINVAL;
+
+ if (client->disconnect) {
+ res = -ENOTCONN;
+ goto error;
+ }
+
+ if (msg->length == 0) {
+ res = 0;
+ goto error;
+ } else if (msg->length > msg->allocated) {
+ res = -ENOMEM;
+ goto error;
+ }
+
+ msg->offset = 0;
+ spa_list_append(&client->out_messages, &msg->link);
+
+ uint32_t mask = client->source->mask;
+ if (!SPA_FLAG_IS_SET(mask, SPA_IO_OUT)) {
+ SPA_FLAG_SET(mask, SPA_IO_OUT);
+ pw_loop_update_io(impl->loop, client->source, mask);
+ }
+
+ client->new_msg_since_last_flush = true;
+
+ return 0;
+
+error:
+ message_free(msg, false, false);
+ return res;
+}
+
+static int client_try_flush_messages(struct client *client)
+{
+ pw_log_trace("client %p: flushing", client);
+
+ spa_assert(!client->disconnect);
+
+ while (!spa_list_is_empty(&client->out_messages)) {
+ struct message *m = spa_list_first(&client->out_messages, struct message, link);
+ struct descriptor desc;
+ const void *data;
+ size_t size;
+
+ if (client->out_index < sizeof(desc)) {
+ desc.length = htonl(m->length);
+ desc.channel = htonl(m->channel);
+ desc.offset_hi = 0;
+ desc.offset_lo = 0;
+ desc.flags = 0;
+
+ data = SPA_PTROFF(&desc, client->out_index, void);
+ size = sizeof(desc) - client->out_index;
+ } else if (client->out_index < m->length + sizeof(desc)) {
+ uint32_t idx = client->out_index - sizeof(desc);
+ data = m->data + idx;
+ size = m->length - idx;
+ } else {
+ if (debug_messages && m->channel == SPA_ID_INVALID)
+ message_dump(SPA_LOG_LEVEL_INFO, m);
+ message_free(m, true, false);
+ client->out_index = 0;
+ continue;
+ }
+
+ while (true) {
+ ssize_t sent = send(client->source->fd, data, size, MSG_NOSIGNAL | MSG_DONTWAIT);
+ if (sent < 0) {
+ int res = -errno;
+ if (res == -EINTR)
+ continue;
+ return res;
+ }
+ client->out_index += sent;
+ break;
+ }
+ }
+ return 0;
+}
+
+int client_flush_messages(struct client *client)
+{
+ client->new_msg_since_last_flush = false;
+
+ int res = client_try_flush_messages(client);
+ if (res >= 0) {
+ uint32_t mask = client->source->mask;
+
+ if (SPA_FLAG_IS_SET(mask, SPA_IO_OUT)) {
+ SPA_FLAG_CLEAR(mask, SPA_IO_OUT);
+ pw_loop_update_io(client->impl->loop, client->source, mask);
+ }
+ } else {
+ if (res != -EAGAIN && res != -EWOULDBLOCK)
+ return res;
+ }
+ return 0;
+}
+
+static bool drop_from_out_queue(struct client *client, struct message *m)
+{
+ spa_assert(!spa_list_is_empty(&client->out_messages));
+
+ struct message *first = spa_list_first(&client->out_messages, struct message, link);
+ if (m == first && client->out_index > 0)
+ return false;
+
+ message_free(m, true, false);
+
+ return true;
+}
+
+/* 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)
+{
+ struct message *m, *t;
+
+ if ((event & SUBSCRIPTION_EVENT_TYPE_MASK) == SUBSCRIPTION_EVENT_NEW)
+ return false;
+
+ /* NOTE: reverse iteration */
+ spa_list_for_each_safe_reverse(m, t, &client->out_messages, link) {
+ if (m->extra[0] != COMMAND_SUBSCRIBE_EVENT)
+ continue;
+ if ((m->extra[1] ^ event) & SUBSCRIPTION_EVENT_FACILITY_MASK)
+ continue;
+ if (m->extra[2] != index)
+ continue;
+
+ if ((event & SUBSCRIPTION_EVENT_TYPE_MASK) == SUBSCRIPTION_EVENT_REMOVE) {
+ /* This object is being removed, hence there is
+ * point in keeping the old events regarding
+ * entry in the queue. */
+
+ bool is_new = (m->extra[1] & SUBSCRIPTION_EVENT_TYPE_MASK) == SUBSCRIPTION_EVENT_NEW;
+
+ if (drop_from_out_queue(client, m)) {
+ pw_log_debug("client %p: dropped redundant event due to remove event for object %u",
+ client, index);
+
+ /* if the NEW event for the current object could successfully be dropped,
+ there is no need to deliver the REMOVE event */
+ if (is_new)
+ goto drop;
+ }
+
+ /* stop if the NEW event for the current object is reached */
+ if (is_new)
+ break;
+ }
+
+ if ((event & SUBSCRIPTION_EVENT_TYPE_MASK) == 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;
+ }
+ }
+
+ return false;
+
+drop:
+ pw_log_debug("client %p: dropped redundant event for object %u", client, index);
+
+ return true;
+}
+
+int client_queue_subscribe_event(struct client *client, uint32_t mask, uint32_t event, uint32_t index)
+{
+ if (client->disconnect)
+ return -ENOTCONN;
+
+ if (!(client->subscribed & mask))
+ return 0;
+
+ pw_log_debug("client %p: SUBSCRIBE event:%08x index:%u", client, event, index);
+
+ if (client_prune_subscribe_events(client, event, index))
+ return 0;
+
+ struct message *reply = message_alloc(client->impl, -1, 0);
+ reply->extra[0] = COMMAND_SUBSCRIBE_EVENT;
+ reply->extra[1] = event;
+ reply->extra[2] = index;
+
+ message_put(reply,
+ TAG_U32, COMMAND_SUBSCRIBE_EVENT,
+ TAG_U32, -1,
+ TAG_U32, event,
+ TAG_U32, index,
+ TAG_INVALID);
+
+ return client_queue_message(client, reply);
+}
diff --git a/src/modules/module-protocol-pulse/client.h b/src/modules/module-protocol-pulse/client.h
new file mode 100644
index 0000000..ed0ee81
--- /dev/null
+++ b/src/modules/module-protocol-pulse/client.h
@@ -0,0 +1,136 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSER_SERVER_CLIENT_H
+#define PULSER_SERVER_CLIENT_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <spa/utils/list.h>
+#include <spa/utils/hook.h>
+#include <pipewire/map.h>
+
+struct impl;
+struct server;
+struct message;
+struct spa_source;
+struct pw_properties;
+struct pw_core;
+struct pw_manager;
+struct pw_manager_object;
+struct pw_properties;
+
+struct descriptor {
+ uint32_t length;
+ uint32_t channel;
+ uint32_t offset_hi;
+ uint32_t offset_lo;
+ uint32_t flags;
+};
+
+struct client {
+ struct spa_list link;
+ struct impl *impl;
+ struct server *server;
+
+ int ref;
+ const char *name; /* owned by `client::props` */
+
+ struct spa_source *source;
+
+ uint32_t version;
+
+ struct pw_properties *props;
+
+ uint64_t quirks;
+
+ struct pw_core *core;
+ struct pw_manager *manager;
+ struct spa_hook manager_listener;
+
+ uint32_t subscribed;
+
+ struct pw_manager_object *metadata_default;
+ char *default_sink;
+ char *default_source;
+ char *temporary_default_sink; /**< pending value, for MOVE_* commands */
+ char *temporary_default_source; /**< pending value, for MOVE_* commands */
+ struct pw_manager_object *metadata_routes;
+ struct pw_properties *routes;
+
+ uint32_t connect_tag;
+
+ uint32_t in_index;
+ uint32_t out_index;
+ struct descriptor desc;
+ struct message *message;
+
+ struct pw_map streams;
+ struct spa_list out_messages;
+
+ struct spa_list operations;
+
+ struct spa_list pending_samples;
+
+ struct spa_list pending_streams;
+
+ unsigned int disconnect:1;
+ unsigned int new_msg_since_last_flush:1;
+ unsigned int authenticated:1;
+
+ struct pw_manager_object *prev_default_sink;
+ struct pw_manager_object *prev_default_source;
+
+ struct spa_hook_list listener_list;
+};
+
+struct client_events {
+#define VERSION_CLIENT_EVENTS 0
+ uint32_t version;
+
+ void (*disconnect) (void *data);
+};
+
+struct client *client_new(struct server *server);
+bool client_detach(struct client *client);
+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);
+
+static inline void client_unref(struct client *client)
+{
+ if (--client->ref == 0)
+ client_free(client);
+}
+
+static inline void client_add_listener(struct client *client, struct spa_hook *listener,
+ const struct client_events *events, void *data)
+{
+ spa_hook_list_append(&client->listener_list, listener, events, data);
+}
+
+#endif /* PULSER_SERVER_CLIENT_H */
diff --git a/src/modules/module-protocol-pulse/cmd.c b/src/modules/module-protocol-pulse/cmd.c
new file mode 100644
index 0000000..e8e6406
--- /dev/null
+++ b/src/modules/module-protocol-pulse/cmd.c
@@ -0,0 +1,136 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/json.h>
+
+#include <pipewire/utils.h>
+
+#include "module.h"
+#include "cmd.h"
+
+static const char WHITESPACE[] = " \t\n\r";
+
+static int do_load_module(struct impl *impl, char *args, const char *flags)
+{
+ int res, n;
+ struct module *module;
+ char *a[2] = { NULL };
+
+ n = pw_split_ip(args, WHITESPACE, 2, a);
+ if (n < 1) {
+ pw_log_info("load-module expects module name");
+ return -EINVAL;
+ }
+
+ module = module_create(impl, a[0], a[1]);
+ if (module == NULL)
+ return -errno;
+ if ((res = module_load(module)) < 0)
+ return res;
+
+ return res;
+}
+
+static int do_cmd(struct impl *impl, const char *cmd, char *args, const char *flags)
+{
+ int res = 0;
+ if (spa_streq(cmd, "load-module")) {
+ res = do_load_module(impl, args, flags);
+ } else {
+ pw_log_warn("ignoring unknown command `%s` with args `%s`",
+ cmd, args);
+ }
+ if (res < 0) {
+ if (flags && strstr(flags, "nofail")) {
+ pw_log_info("nofail command %s %s: %s",
+ cmd, args, spa_strerror(res));
+ res = 0;
+ } else {
+ pw_log_error("can't run command %s %s: %s",
+ cmd, args, spa_strerror(res));
+ }
+ }
+ return res;
+}
+
+/*
+ * pulse.cmd = [
+ * { cmd = <command> [ args = "<arguments>" ] }
+ * ...
+ * ]
+ */
+static int parse_cmd(void *user_data, const char *location,
+ const char *section, const char *str, size_t len)
+{
+ struct impl *impl = user_data;
+ struct spa_json it[3];
+ char key[512], *s;
+ int res = 0;
+
+ s = strndup(str, len);
+ spa_json_init(&it[0], s, len);
+ if (spa_json_enter_array(&it[0], &it[1]) < 0) {
+ pw_log_error("config file error: pulse.cmd is not an array");
+ res = -EINVAL;
+ goto exit;
+ }
+
+ while (spa_json_enter_object(&it[1], &it[2]) > 0) {
+ char *cmd = NULL, *args = NULL, *flags = NULL;
+
+ while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) {
+ const char *val;
+ int len;
+
+ if ((len = spa_json_next(&it[2], &val)) <= 0)
+ break;
+
+ if (spa_streq(key, "cmd")) {
+ cmd = (char*)val;
+ spa_json_parse_stringn(val, len, cmd, len+1);
+ } else if (spa_streq(key, "args")) {
+ args = (char*)val;
+ spa_json_parse_stringn(val, len, args, len+1);
+ } else if (spa_streq(key, "flags")) {
+ if (spa_json_is_container(val, len))
+ len = spa_json_container_len(&it[2], val, len);
+ flags = (char*)val;
+ spa_json_parse_stringn(val, len, flags, len+1);
+ }
+ }
+ if (cmd != NULL)
+ res = do_cmd(impl, cmd, args, flags);
+ if (res < 0)
+ break;
+ }
+exit:
+ free(s);
+ return res;
+}
+
+int cmd_run(struct impl *impl)
+{
+ return pw_context_conf_section_for_each(impl->context, "pulse.cmd",
+ parse_cmd, impl);
+}
diff --git a/src/modules/module-protocol-pulse/cmd.h b/src/modules/module-protocol-pulse/cmd.h
new file mode 100644
index 0000000..81f528c
--- /dev/null
+++ b/src/modules/module-protocol-pulse/cmd.h
@@ -0,0 +1,32 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSER_SERVER_CMD_H
+#define PULSER_SERVER_CMD_H
+
+#include "internal.h"
+
+int cmd_run(struct impl *impl);
+
+#endif /* PULSER_SERVER_CMD_H */
diff --git a/src/modules/module-protocol-pulse/collect.c b/src/modules/module-protocol-pulse/collect.c
new file mode 100644
index 0000000..ac6fd6e
--- /dev/null
+++ b/src/modules/module-protocol-pulse/collect.c
@@ -0,0 +1,549 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/param/props.h>
+#include <spa/pod/builder.h>
+#include <spa/pod/parser.h>
+#include <spa/utils/string.h>
+#include <pipewire/pipewire.h>
+
+#include "collect.h"
+#include "defs.h"
+#include "log.h"
+#include "manager.h"
+
+void select_best(struct selector *s, struct pw_manager_object *o)
+{
+ int32_t prio = 0;
+
+ if (o->props &&
+ pw_properties_fetch_int32(o->props, PW_KEY_PRIORITY_SESSION, &prio) == 0) {
+ if (s->best == NULL || prio > s->score) {
+ s->best = o;
+ s->score = prio;
+ }
+ }
+}
+
+struct pw_manager_object *select_object(struct pw_manager *m, struct selector *s)
+{
+ struct pw_manager_object *o;
+ const char *str;
+
+ spa_list_for_each(o, &m->object_list, link) {
+ if (o->creating || o->removing)
+ continue;
+ if (s->type != NULL && !s->type(o))
+ continue;
+ if (o->id == s->id)
+ return o;
+ if (o->index == s->index)
+ return o;
+ if (s->accumulate)
+ s->accumulate(s, o);
+ if (o->props && s->key != NULL && s->value != NULL &&
+ (str = pw_properties_get(o->props, s->key)) != NULL &&
+ spa_streq(str, s->value))
+ return o;
+ if (s->value != NULL && (uint32_t)atoi(s->value) == o->index)
+ return o;
+ }
+ return s->best;
+}
+
+uint32_t id_to_index(struct pw_manager *m, uint32_t id)
+{
+ struct pw_manager_object *o;
+ spa_list_for_each(o, &m->object_list, link) {
+ if (o->id == id)
+ return o->index;
+ }
+ return SPA_ID_INVALID;
+}
+
+bool collect_is_linked(struct pw_manager *m, uint32_t id, enum pw_direction direction)
+{
+ struct pw_manager_object *o;
+ uint32_t in_node, out_node;
+
+ spa_list_for_each(o, &m->object_list, link) {
+ if (o->props == NULL || !pw_manager_object_is_link(o))
+ continue;
+
+ if (pw_properties_fetch_uint32(o->props, PW_KEY_LINK_OUTPUT_NODE, &out_node) != 0 ||
+ pw_properties_fetch_uint32(o->props, PW_KEY_LINK_INPUT_NODE, &in_node) != 0)
+ continue;
+
+ if ((direction == PW_DIRECTION_OUTPUT && id == out_node) ||
+ (direction == PW_DIRECTION_INPUT && id == in_node))
+ return true;
+ }
+ return false;
+}
+
+struct pw_manager_object *find_peer_for_link(struct pw_manager *m,
+ struct pw_manager_object *o, uint32_t id, enum pw_direction direction)
+{
+ struct pw_manager_object *p;
+ uint32_t in_node, out_node;
+
+ if (o->props == NULL)
+ return NULL;
+
+ if (pw_properties_fetch_uint32(o->props, PW_KEY_LINK_OUTPUT_NODE, &out_node) != 0 ||
+ pw_properties_fetch_uint32(o->props, PW_KEY_LINK_INPUT_NODE, &in_node) != 0)
+ return NULL;
+
+ if (direction == PW_DIRECTION_OUTPUT && id == out_node) {
+ struct selector sel = { .id = in_node, .type = pw_manager_object_is_sink, };
+ if ((p = select_object(m, &sel)) != NULL)
+ return p;
+ }
+ if (direction == PW_DIRECTION_INPUT && id == in_node) {
+ struct selector sel = { .id = out_node, .type = pw_manager_object_is_recordable, };
+ if ((p = select_object(m, &sel)) != NULL)
+ return p;
+ }
+ return NULL;
+}
+
+struct pw_manager_object *find_linked(struct pw_manager *m, uint32_t id, enum pw_direction direction)
+{
+ struct pw_manager_object *o, *p;
+
+ spa_list_for_each(o, &m->object_list, link) {
+ if (!pw_manager_object_is_link(o))
+ continue;
+ if ((p = find_peer_for_link(m, o, id, direction)) != NULL)
+ return p;
+ }
+ return NULL;
+}
+
+void collect_card_info(struct pw_manager_object *card, struct card_info *info)
+{
+ struct pw_manager_param *p;
+
+ spa_list_for_each(p, &card->param_list, link) {
+ switch (p->id) {
+ case SPA_PARAM_EnumProfile:
+ info->n_profiles++;
+ break;
+ case SPA_PARAM_Profile:
+ spa_pod_parse_object(p->param,
+ SPA_TYPE_OBJECT_ParamProfile, NULL,
+ SPA_PARAM_PROFILE_index, SPA_POD_Int(&info->active_profile));
+ break;
+ case SPA_PARAM_EnumRoute:
+ info->n_ports++;
+ break;
+ }
+ }
+}
+
+uint32_t collect_profile_info(struct pw_manager_object *card, struct card_info *card_info,
+ struct profile_info *profile_info)
+{
+ struct pw_manager_param *p;
+ struct profile_info *pi;
+ uint32_t n;
+
+ n = 0;
+ spa_list_for_each(p, &card->param_list, link) {
+ struct spa_pod *classes = NULL;
+
+ if (p->id != SPA_PARAM_EnumProfile)
+ continue;
+
+ pi = &profile_info[n];
+ spa_zero(*pi);
+
+ if (spa_pod_parse_object(p->param,
+ SPA_TYPE_OBJECT_ParamProfile, NULL,
+ SPA_PARAM_PROFILE_index, SPA_POD_Int(&pi->index),
+ SPA_PARAM_PROFILE_name, SPA_POD_String(&pi->name),
+ SPA_PARAM_PROFILE_description, SPA_POD_OPT_String(&pi->description),
+ SPA_PARAM_PROFILE_priority, SPA_POD_OPT_Int(&pi->priority),
+ SPA_PARAM_PROFILE_available, SPA_POD_OPT_Id(&pi->available),
+ SPA_PARAM_PROFILE_classes, SPA_POD_OPT_Pod(&classes)) < 0) {
+ continue;
+ }
+ if (pi->description == NULL)
+ pi->description = pi->name;
+ if (pi->index == card_info->active_profile)
+ card_info->active_profile_name = pi->name;
+
+ if (classes != NULL) {
+ struct spa_pod *iter;
+
+ SPA_POD_STRUCT_FOREACH(classes, iter) {
+ struct spa_pod_parser prs;
+ char *class;
+ uint32_t count;
+
+ spa_pod_parser_pod(&prs, iter);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_String(&class),
+ SPA_POD_Int(&count)) < 0)
+ continue;
+
+ if (spa_streq(class, "Audio/Sink"))
+ pi->n_sinks += count;
+ else if (spa_streq(class, "Audio/Source"))
+ pi->n_sources += count;
+ }
+ }
+ n++;
+ }
+ if (card_info->active_profile_name == NULL && n > 0)
+ card_info->active_profile_name = profile_info[0].name;
+
+ return n;
+}
+
+uint32_t find_profile_index(struct pw_manager_object *card, const char *name)
+{
+ struct pw_manager_param *p;
+
+ spa_list_for_each(p, &card->param_list, link) {
+ uint32_t index;
+ const char *test_name;
+
+ if (p->id != SPA_PARAM_EnumProfile)
+ continue;
+
+ if (spa_pod_parse_object(p->param,
+ SPA_TYPE_OBJECT_ParamProfile, NULL,
+ SPA_PARAM_PROFILE_index, SPA_POD_Int(&index),
+ SPA_PARAM_PROFILE_name, SPA_POD_String(&test_name)) < 0)
+ continue;
+
+ if (spa_streq(test_name, name))
+ return index;
+
+ }
+ return SPA_ID_INVALID;
+}
+
+void collect_device_info(struct pw_manager_object *device, struct pw_manager_object *card,
+ struct device_info *dev_info, bool monitor, struct defs *defs)
+{
+ struct pw_manager_param *p;
+
+ if (card && !monitor) {
+ spa_list_for_each(p, &card->param_list, link) {
+ uint32_t index, dev;
+ struct spa_pod *props;
+
+ if (p->id != SPA_PARAM_Route)
+ continue;
+
+ if (spa_pod_parse_object(p->param,
+ SPA_TYPE_OBJECT_ParamRoute, NULL,
+ SPA_PARAM_ROUTE_index, SPA_POD_Int(&index),
+ SPA_PARAM_ROUTE_device, SPA_POD_Int(&dev),
+ SPA_PARAM_ROUTE_props, SPA_POD_OPT_Pod(&props)) < 0)
+ continue;
+ if (dev != dev_info->device)
+ continue;
+ dev_info->active_port = index;
+ if (props) {
+ volume_parse_param(props, &dev_info->volume_info, monitor);
+ dev_info->have_volume = true;
+ }
+ }
+ }
+
+ spa_list_for_each(p, &device->param_list, link) {
+ switch (p->id) {
+ case SPA_PARAM_EnumFormat:
+ {
+ struct spa_pod *copy = spa_pod_copy(p->param);
+ spa_pod_fixate(copy);
+ format_parse_param(copy, true, &dev_info->ss, &dev_info->map,
+ &defs->sample_spec, &defs->channel_map);
+ free(copy);
+ break;
+ }
+ case SPA_PARAM_Format:
+ format_parse_param(p->param, true, &dev_info->ss, &dev_info->map,
+ NULL, NULL);
+ break;
+
+ case SPA_PARAM_Props:
+ if (!dev_info->have_volume) {
+ volume_parse_param(p->param, &dev_info->volume_info, monitor);
+ dev_info->have_volume = true;
+ }
+ dev_info->have_iec958codecs = spa_pod_find_prop(p->param,
+ NULL, SPA_PROP_iec958Codecs) != NULL;
+ break;
+ }
+ }
+ if (dev_info->ss.channels != dev_info->map.channels)
+ dev_info->ss.channels = dev_info->map.channels;
+ if (dev_info->volume_info.volume.channels != dev_info->map.channels)
+ dev_info->volume_info.volume.channels = dev_info->map.channels;
+}
+
+static bool array_contains(uint32_t *vals, uint32_t n_vals, uint32_t val)
+{
+ uint32_t n;
+ if (vals == NULL || n_vals == 0)
+ return false;
+ for (n = 0; n < n_vals; n++)
+ if (vals[n] == val)
+ return true;
+ return false;
+}
+
+uint32_t collect_port_info(struct pw_manager_object *card, struct card_info *card_info,
+ struct device_info *dev_info, struct port_info *port_info)
+{
+ struct pw_manager_param *p;
+ uint32_t n;
+
+ if (card == NULL)
+ return 0;
+
+ n = 0;
+ spa_list_for_each(p, &card->param_list, link) {
+ struct spa_pod *devices = NULL, *profiles = NULL;
+ struct port_info *pi;
+
+ if (p->id != SPA_PARAM_EnumRoute)
+ continue;
+
+ pi = &port_info[n];
+ spa_zero(*pi);
+
+ if (spa_pod_parse_object(p->param,
+ SPA_TYPE_OBJECT_ParamRoute, NULL,
+ SPA_PARAM_ROUTE_index, SPA_POD_Int(&pi->index),
+ SPA_PARAM_ROUTE_direction, SPA_POD_Id(&pi->direction),
+ SPA_PARAM_ROUTE_name, SPA_POD_String(&pi->name),
+ SPA_PARAM_ROUTE_description, SPA_POD_OPT_String(&pi->description),
+ SPA_PARAM_ROUTE_priority, SPA_POD_OPT_Int(&pi->priority),
+ SPA_PARAM_ROUTE_available, SPA_POD_OPT_Id(&pi->available),
+ SPA_PARAM_ROUTE_info, SPA_POD_OPT_Pod(&pi->info),
+ SPA_PARAM_ROUTE_devices, SPA_POD_OPT_Pod(&devices),
+ SPA_PARAM_ROUTE_profiles, SPA_POD_OPT_Pod(&profiles)) < 0)
+ continue;
+
+ if (pi->description == NULL)
+ pi->description = pi->name;
+ if (devices)
+ pi->devices = spa_pod_get_array(devices, &pi->n_devices);
+ if (profiles)
+ pi->profiles = spa_pod_get_array(profiles, &pi->n_profiles);
+
+ if (dev_info != NULL) {
+ if (pi->direction != dev_info->direction)
+ continue;
+ if (!array_contains(pi->profiles, pi->n_profiles, card_info->active_profile))
+ continue;
+ if (!array_contains(pi->devices, pi->n_devices, dev_info->device))
+ continue;
+ if (pi->index == dev_info->active_port)
+ dev_info->active_port_name = pi->name;
+ }
+
+ while (pi->info != NULL) {
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[1];
+ uint32_t n;
+ const char *key, *value;
+
+ spa_pod_parser_pod(&prs, pi->info);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get_int(&prs, (int32_t*)&pi->n_props) < 0)
+ break;
+
+ for (n = 0; n < pi->n_props; n++) {
+ if (spa_pod_parser_get(&prs,
+ SPA_POD_String(&key),
+ SPA_POD_String(&value),
+ NULL) < 0)
+ break;
+ if (spa_streq(key, "port.availability-group"))
+ pi->availability_group = value;
+ else if (spa_streq(key, "port.type"))
+ pi->type = port_type_value(value);
+ }
+ spa_pod_parser_pop(&prs, &f[0]);
+ break;
+ }
+ n++;
+ }
+ if (dev_info != NULL && dev_info->active_port_name == NULL && n > 0)
+ dev_info->active_port_name = port_info[0].name;
+ return n;
+}
+
+uint32_t find_port_index(struct pw_manager_object *card, uint32_t direction, const char *port_name)
+{
+ struct pw_manager_param *p;
+
+ spa_list_for_each(p, &card->param_list, link) {
+ uint32_t index, dir;
+ const char *name;
+
+ if (p->id != SPA_PARAM_EnumRoute)
+ continue;
+
+ if (spa_pod_parse_object(p->param,
+ SPA_TYPE_OBJECT_ParamRoute, NULL,
+ SPA_PARAM_ROUTE_index, SPA_POD_Int(&index),
+ SPA_PARAM_ROUTE_direction, SPA_POD_Id(&dir),
+ SPA_PARAM_ROUTE_name, SPA_POD_String(&name)) < 0)
+ continue;
+ if (dir != direction)
+ continue;
+ if (spa_streq(name, port_name))
+ return index;
+
+ }
+ return SPA_ID_INVALID;
+}
+
+struct spa_dict *collect_props(struct spa_pod *info, struct spa_dict *dict)
+{
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f[1];
+ int32_t n, n_items;
+
+ spa_pod_parser_pod(&prs, info);
+ if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
+ spa_pod_parser_get_int(&prs, &n_items) < 0)
+ return NULL;
+
+ for (n = 0; n < n_items; n++) {
+ if (spa_pod_parser_get(&prs,
+ SPA_POD_String(&dict->items[n].key),
+ SPA_POD_String(&dict->items[n].value),
+ NULL) < 0)
+ break;
+ }
+ spa_pod_parser_pop(&prs, &f[0]);
+ dict->n_items = n;
+ return dict;
+}
+
+uint32_t collect_transport_codec_info(struct pw_manager_object *card,
+ struct transport_codec_info *codecs, uint32_t max_codecs,
+ uint32_t *active)
+{
+ struct pw_manager_param *p;
+ uint32_t n_codecs = 0;
+
+ *active = SPA_ID_INVALID;
+
+ if (card == NULL)
+ return 0;
+
+ spa_list_for_each(p, &card->param_list, link) {
+ uint32_t iid;
+ const struct spa_pod_choice *type;
+ const struct spa_pod_struct *labels;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ int32_t *id;
+ bool first;
+
+ if (p->id != SPA_PARAM_PropInfo)
+ continue;
+
+ if (spa_pod_parse_object(p->param,
+ SPA_TYPE_OBJECT_PropInfo, NULL,
+ SPA_PROP_INFO_id, SPA_POD_Id(&iid),
+ SPA_PROP_INFO_type, SPA_POD_PodChoice(&type),
+ SPA_PROP_INFO_labels, SPA_POD_PodStruct(&labels)) < 0)
+ continue;
+
+ if (iid != SPA_PROP_bluetoothAudioCodec)
+ continue;
+
+ if (SPA_POD_CHOICE_TYPE(type) != SPA_CHOICE_Enum ||
+ SPA_POD_TYPE(SPA_POD_CHOICE_CHILD(type)) != SPA_TYPE_Int)
+ continue;
+
+ /*
+ * XXX: PropInfo currently uses Int, not Id, in type and labels.
+ */
+
+ /* Codec name list */
+ first = true;
+ SPA_POD_CHOICE_FOREACH(type, id) {
+ if (first) {
+ /* Skip default */
+ first = false;
+ continue;
+ }
+ if (n_codecs >= max_codecs)
+ break;
+ codecs[n_codecs++].id = *id;
+ }
+
+ /* Codec description list */
+ spa_pod_parser_pod(&prs, (struct spa_pod *)labels);
+ if (spa_pod_parser_push_struct(&prs, &f) < 0)
+ continue;
+
+ while (1) {
+ int32_t id;
+ const char *desc;
+ uint32_t j;
+
+ if (spa_pod_parser_get_int(&prs, &id) < 0 ||
+ spa_pod_parser_get_string(&prs, &desc) < 0)
+ break;
+
+ for (j = 0; j < n_codecs; ++j) {
+ if (codecs[j].id == (uint32_t)id)
+ codecs[j].description = desc;
+ }
+ }
+ }
+
+ /* Active codec */
+ spa_list_for_each(p, &card->param_list, link) {
+ uint32_t j;
+ uint32_t id;
+
+ if (p->id != SPA_PARAM_Props)
+ continue;
+
+ if (spa_pod_parse_object(p->param,
+ SPA_TYPE_OBJECT_Props, NULL,
+ SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(&id)) < 0)
+ continue;
+
+ for (j = 0; j < n_codecs; ++j) {
+ if (codecs[j].id == id)
+ *active = j;
+ }
+ }
+
+ return n_codecs;
+}
diff --git a/src/modules/module-protocol-pulse/collect.h b/src/modules/module-protocol-pulse/collect.h
new file mode 100644
index 0000000..d4b85ab
--- /dev/null
+++ b/src/modules/module-protocol-pulse/collect.h
@@ -0,0 +1,166 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ * Copyright © 2021 Sanchayan Maity <sanchayan@asymptotic.io>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSE_SERVER_COLLECT_H
+#define PULSE_SERVER_COLLECT_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <spa/param/bluetooth/audio.h>
+#include <pipewire/pipewire.h>
+
+#include "internal.h"
+#include "format.h"
+#include "volume.h"
+
+struct pw_manager;
+struct pw_manager_object;
+
+/* ========================================================================== */
+
+struct selector {
+ bool (*type) (struct pw_manager_object *o);
+ uint32_t id;
+ uint32_t index;
+ const char *key;
+ const char *value;
+ void (*accumulate) (struct selector *sel, struct pw_manager_object *o);
+ int32_t score;
+ struct pw_manager_object *best;
+};
+
+struct pw_manager_object *select_object(struct pw_manager *m, struct selector *s);
+uint32_t id_to_index(struct pw_manager *m, uint32_t id);
+void select_best(struct selector *s, struct pw_manager_object *o);
+
+/* ========================================================================== */
+
+struct device_info {
+ uint32_t direction;
+
+ struct sample_spec ss;
+ struct channel_map map;
+ struct volume_info volume_info;
+ unsigned int have_volume:1;
+ unsigned int have_iec958codecs:1;
+
+ uint32_t device;
+ uint32_t active_port;
+ const char *active_port_name;
+};
+
+#define DEVICE_INFO_INIT(_dir) \
+ (struct device_info) { \
+ .direction = _dir, \
+ .ss = SAMPLE_SPEC_INIT, \
+ .map = CHANNEL_MAP_INIT, \
+ .volume_info = VOLUME_INFO_INIT, \
+ .device = SPA_ID_INVALID, \
+ .active_port = SPA_ID_INVALID, \
+ }
+
+void collect_device_info(struct pw_manager_object *device, struct pw_manager_object *card,
+ struct device_info *dev_info, bool monitor, struct defs *defs);
+
+/* ========================================================================== */
+
+struct card_info {
+ uint32_t n_profiles;
+ uint32_t active_profile;
+ const char *active_profile_name;
+
+ uint32_t n_ports;
+};
+
+#define CARD_INFO_INIT \
+ (struct card_info) { \
+ .active_profile = SPA_ID_INVALID, \
+ }
+
+void collect_card_info(struct pw_manager_object *card, struct card_info *info);
+
+/* ========================================================================== */
+
+struct profile_info {
+ uint32_t index;
+ const char *name;
+ const char *description;
+ uint32_t priority;
+ uint32_t available;
+ uint32_t n_sources;
+ uint32_t n_sinks;
+};
+
+uint32_t collect_profile_info(struct pw_manager_object *card, struct card_info *card_info,
+ struct profile_info *profile_info);
+
+/* ========================================================================== */
+
+struct port_info {
+ uint32_t index;
+ uint32_t direction;
+ const char *name;
+ const char *description;
+ uint32_t priority;
+ uint32_t available;
+
+ const char *availability_group;
+ uint32_t type;
+
+ uint32_t n_devices;
+ uint32_t *devices;
+ uint32_t n_profiles;
+ uint32_t *profiles;
+
+ uint32_t n_props;
+ struct spa_pod *info;
+};
+
+uint32_t collect_port_info(struct pw_manager_object *card, struct card_info *card_info,
+ struct device_info *dev_info, struct port_info *port_info);
+
+/* ========================================================================== */
+
+struct transport_codec_info {
+ enum spa_bluetooth_audio_codec id;
+ const char *description;
+};
+
+uint32_t collect_transport_codec_info(struct pw_manager_object *card,
+ struct transport_codec_info *codecs, uint32_t max_codecs,
+ uint32_t *active);
+
+/* ========================================================================== */
+
+struct spa_dict *collect_props(struct spa_pod *info, struct spa_dict *dict);
+uint32_t find_profile_index(struct pw_manager_object *card, const char *name);
+uint32_t find_port_index(struct pw_manager_object *card, uint32_t direction, const char *port_name);
+struct pw_manager_object *find_peer_for_link(struct pw_manager *m,
+ struct pw_manager_object *o, uint32_t id, enum pw_direction direction);
+struct pw_manager_object *find_linked(struct pw_manager *m, uint32_t id, enum pw_direction direction);
+bool collect_is_linked(struct pw_manager *m, uint32_t id, enum pw_direction direction);
+
+#endif
diff --git a/src/modules/module-protocol-pulse/commands.h b/src/modules/module-protocol-pulse/commands.h
new file mode 100644
index 0000000..9dd1d74
--- /dev/null
+++ b/src/modules/module-protocol-pulse/commands.h
@@ -0,0 +1,208 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSE_SERVER_COMMANDS_H
+#define PULSE_SERVER_COMMANDS_H
+
+#include <stdint.h>
+
+struct client;
+struct message;
+
+enum pulseaudio_command {
+ /* Generic commands */
+ COMMAND_ERROR,
+ COMMAND_TIMEOUT, /* pseudo command */
+ COMMAND_REPLY,
+
+ /* CLIENT->SERVER */
+ COMMAND_CREATE_PLAYBACK_STREAM, /* Payload changed in v9, v12 (0.9.0, 0.9.8) */
+ COMMAND_DELETE_PLAYBACK_STREAM,
+ COMMAND_CREATE_RECORD_STREAM, /* Payload changed in v9, v12 (0.9.0, 0.9.8) */
+ COMMAND_DELETE_RECORD_STREAM,
+ COMMAND_EXIT,
+ COMMAND_AUTH,
+ COMMAND_SET_CLIENT_NAME,
+ COMMAND_LOOKUP_SINK,
+ COMMAND_LOOKUP_SOURCE,
+ COMMAND_DRAIN_PLAYBACK_STREAM,
+ COMMAND_STAT,
+ COMMAND_GET_PLAYBACK_LATENCY,
+ COMMAND_CREATE_UPLOAD_STREAM,
+ COMMAND_DELETE_UPLOAD_STREAM,
+ COMMAND_FINISH_UPLOAD_STREAM,
+ COMMAND_PLAY_SAMPLE,
+ COMMAND_REMOVE_SAMPLE,
+
+ COMMAND_GET_SERVER_INFO,
+ COMMAND_GET_SINK_INFO,
+ COMMAND_GET_SINK_INFO_LIST,
+ COMMAND_GET_SOURCE_INFO,
+ COMMAND_GET_SOURCE_INFO_LIST,
+ COMMAND_GET_MODULE_INFO,
+ COMMAND_GET_MODULE_INFO_LIST,
+ COMMAND_GET_CLIENT_INFO,
+ COMMAND_GET_CLIENT_INFO_LIST,
+ COMMAND_GET_SINK_INPUT_INFO, /* Payload changed in v11 (0.9.7) */
+ COMMAND_GET_SINK_INPUT_INFO_LIST, /* Payload changed in v11 (0.9.7) */
+ COMMAND_GET_SOURCE_OUTPUT_INFO,
+ COMMAND_GET_SOURCE_OUTPUT_INFO_LIST,
+ COMMAND_GET_SAMPLE_INFO,
+ COMMAND_GET_SAMPLE_INFO_LIST,
+ COMMAND_SUBSCRIBE,
+
+ COMMAND_SET_SINK_VOLUME,
+ COMMAND_SET_SINK_INPUT_VOLUME,
+ COMMAND_SET_SOURCE_VOLUME,
+
+ COMMAND_SET_SINK_MUTE,
+ COMMAND_SET_SOURCE_MUTE,
+
+ COMMAND_CORK_PLAYBACK_STREAM,
+ COMMAND_FLUSH_PLAYBACK_STREAM,
+ COMMAND_TRIGGER_PLAYBACK_STREAM,
+
+ COMMAND_SET_DEFAULT_SINK,
+ COMMAND_SET_DEFAULT_SOURCE,
+
+ COMMAND_SET_PLAYBACK_STREAM_NAME,
+ COMMAND_SET_RECORD_STREAM_NAME,
+
+ COMMAND_KILL_CLIENT,
+ COMMAND_KILL_SINK_INPUT,
+ COMMAND_KILL_SOURCE_OUTPUT,
+
+ COMMAND_LOAD_MODULE,
+ COMMAND_UNLOAD_MODULE,
+
+ /* Obsolete */
+ COMMAND_ADD_AUTOLOAD___OBSOLETE,
+ COMMAND_REMOVE_AUTOLOAD___OBSOLETE,
+ COMMAND_GET_AUTOLOAD_INFO___OBSOLETE,
+ COMMAND_GET_AUTOLOAD_INFO_LIST___OBSOLETE,
+
+ COMMAND_GET_RECORD_LATENCY,
+ COMMAND_CORK_RECORD_STREAM,
+ COMMAND_FLUSH_RECORD_STREAM,
+ COMMAND_PREBUF_PLAYBACK_STREAM,
+
+ /* SERVER->CLIENT */
+ COMMAND_REQUEST,
+ COMMAND_OVERFLOW,
+ COMMAND_UNDERFLOW,
+ COMMAND_PLAYBACK_STREAM_KILLED,
+ COMMAND_RECORD_STREAM_KILLED,
+ COMMAND_SUBSCRIBE_EVENT,
+
+ /* A few more client->server commands */
+
+ /* Supported since protocol v10 (0.9.5) */
+ COMMAND_MOVE_SINK_INPUT,
+ COMMAND_MOVE_SOURCE_OUTPUT,
+
+ /* Supported since protocol v11 (0.9.7) */
+ COMMAND_SET_SINK_INPUT_MUTE,
+
+ COMMAND_SUSPEND_SINK,
+ COMMAND_SUSPEND_SOURCE,
+
+ /* Supported since protocol v12 (0.9.8) */
+ COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR,
+ COMMAND_SET_RECORD_STREAM_BUFFER_ATTR,
+
+ COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE,
+ COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE,
+
+ /* SERVER->CLIENT */
+ COMMAND_PLAYBACK_STREAM_SUSPENDED,
+ COMMAND_RECORD_STREAM_SUSPENDED,
+ COMMAND_PLAYBACK_STREAM_MOVED,
+ COMMAND_RECORD_STREAM_MOVED,
+
+ /* Supported since protocol v13 (0.9.11) */
+ COMMAND_UPDATE_RECORD_STREAM_PROPLIST,
+ COMMAND_UPDATE_PLAYBACK_STREAM_PROPLIST,
+ COMMAND_UPDATE_CLIENT_PROPLIST,
+ COMMAND_REMOVE_RECORD_STREAM_PROPLIST,
+ COMMAND_REMOVE_PLAYBACK_STREAM_PROPLIST,
+ COMMAND_REMOVE_CLIENT_PROPLIST,
+
+ /* SERVER->CLIENT */
+ COMMAND_STARTED,
+
+ /* Supported since protocol v14 (0.9.12) */
+ COMMAND_EXTENSION,
+ /* Supported since protocol v15 (0.9.15) */
+ COMMAND_GET_CARD_INFO,
+ COMMAND_GET_CARD_INFO_LIST,
+ COMMAND_SET_CARD_PROFILE,
+
+ COMMAND_CLIENT_EVENT,
+ COMMAND_PLAYBACK_STREAM_EVENT,
+ COMMAND_RECORD_STREAM_EVENT,
+
+ /* SERVER->CLIENT */
+ COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED,
+ COMMAND_RECORD_BUFFER_ATTR_CHANGED,
+
+ /* Supported since protocol v16 (0.9.16) */
+ COMMAND_SET_SINK_PORT,
+ COMMAND_SET_SOURCE_PORT,
+
+ /* Supported since protocol v22 (1.0) */
+ COMMAND_SET_SOURCE_OUTPUT_VOLUME,
+ COMMAND_SET_SOURCE_OUTPUT_MUTE,
+
+ /* Supported since protocol v27 (3.0) */
+ COMMAND_SET_PORT_LATENCY_OFFSET,
+
+ /* Supported since protocol v30 (6.0) */
+ /* BOTH DIRECTIONS */
+ COMMAND_ENABLE_SRBCHANNEL,
+ COMMAND_DISABLE_SRBCHANNEL,
+
+ /* Supported since protocol v31 (9.0)
+ * BOTH DIRECTIONS */
+ COMMAND_REGISTER_MEMFD_SHMID,
+
+ /* Supported since protocol v35 (15.0) */
+ COMMAND_SEND_OBJECT_MESSAGE,
+
+ COMMAND_MAX
+};
+
+enum command_access_flag {
+ COMMAND_ACCESS_WITHOUT_AUTH = (1 << 0),
+ COMMAND_ACCESS_WITHOUT_MANAGER = (1 << 1),
+};
+
+struct command {
+ const char *name;
+ int (*run) (struct client *client, uint32_t command, uint32_t tag, struct message *msg);
+ uint32_t access;
+};
+
+extern const struct command commands[COMMAND_MAX];
+
+#endif /* PULSE_SERVER_COMMANDS_H */
diff --git a/src/modules/module-protocol-pulse/dbus-name.c b/src/modules/module-protocol-pulse/dbus-name.c
new file mode 100644
index 0000000..b82bc45
--- /dev/null
+++ b/src/modules/module-protocol-pulse/dbus-name.c
@@ -0,0 +1,87 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+
+#include <dbus/dbus.h>
+
+#include <spa/support/dbus.h>
+#include <spa/support/plugin.h>
+#include <pipewire/context.h>
+
+#include "log.h"
+#include "dbus-name.h"
+
+void *dbus_request_name(struct pw_context *context, const char *name)
+{
+ struct spa_dbus *dbus;
+ struct spa_dbus_connection *conn;
+ const struct spa_support *support;
+ uint32_t n_support;
+ DBusConnection *bus;
+ DBusError error;
+
+ support = pw_context_get_support(context, &n_support);
+
+ dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus);
+ if (dbus == NULL) {
+ errno = ENOTSUP;
+ return NULL;
+ }
+
+ conn = spa_dbus_get_connection(dbus, SPA_DBUS_TYPE_SESSION);
+ if (conn == NULL)
+ return NULL;
+
+ bus = spa_dbus_connection_get(conn);
+ if (bus == NULL) {
+ spa_dbus_connection_destroy(conn);
+ return NULL;
+ }
+
+ dbus_error_init(&error);
+
+ if (dbus_bus_request_name(bus, name,
+ DBUS_NAME_FLAG_DO_NOT_QUEUE,
+ &error) == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER)
+ return conn;
+
+ if (dbus_error_is_set(&error))
+ pw_log_error("Failed to acquire %s: %s: %s", name, error.name, error.message);
+ else
+ pw_log_error("D-Bus name %s already taken.", name);
+
+ dbus_error_free(&error);
+
+ spa_dbus_connection_destroy(conn);
+
+ errno = EEXIST;
+ return NULL;
+}
+
+void dbus_release_name(void *data)
+{
+ struct spa_dbus_connection *conn = data;
+ spa_dbus_connection_destroy(conn);
+}
diff --git a/src/modules/module-protocol-pulse/dbus-name.h b/src/modules/module-protocol-pulse/dbus-name.h
new file mode 100644
index 0000000..a15fb80
--- /dev/null
+++ b/src/modules/module-protocol-pulse/dbus-name.h
@@ -0,0 +1,33 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSE_SERVER_DBUS_NAME_H
+#define PULSE_SERVER_DBUS_NAME_H
+
+struct pw_context;
+
+void *dbus_request_name(struct pw_context *context, const char *name);
+void dbus_release_name(void *data);
+
+#endif /* PULSE_SERVER_DBUS_NAME_H */
diff --git a/src/modules/module-protocol-pulse/defs.h b/src/modules/module-protocol-pulse/defs.h
new file mode 100644
index 0000000..2e9a43e
--- /dev/null
+++ b/src/modules/module-protocol-pulse/defs.h
@@ -0,0 +1,265 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSE_SERVER_DEFS_H
+#define PULSE_SERVER_DEFS_H
+
+#include <pipewire/node.h>
+
+#define FLAG_SHMDATA 0x80000000LU
+#define FLAG_SHMDATA_MEMFD_BLOCK 0x20000000LU
+#define FLAG_SHMRELEASE 0x40000000LU
+#define FLAG_SHMREVOKE 0xC0000000LU
+#define FLAG_SHMMASK 0xFF000000LU
+#define FLAG_SEEKMASK 0x000000FFLU
+#define FLAG_SHMWRITABLE 0x00800000LU
+
+#define SEEK_RELATIVE 0
+#define SEEK_ABSOLUTE 1
+#define SEEK_RELATIVE_ON_READ 2
+#define SEEK_RELATIVE_END 3
+
+#define FRAME_SIZE_MAX_ALLOW (1024*1024*16)
+
+#define PROTOCOL_FLAG_MASK 0xffff0000u
+#define PROTOCOL_VERSION_MASK 0x0000ffffu
+#define PROTOCOL_VERSION 35
+
+#define NATIVE_COOKIE_LENGTH 256
+#define MAX_TAG_SIZE (64*1024)
+
+#define MIN_BUFFERS 1u
+#define MAX_BUFFERS 4u
+
+#define MAXLENGTH (4u*1024*1024) /* 4MB */
+
+#define SCACHE_ENTRY_SIZE_MAX (1024*1024*16)
+
+#define MODULE_INDEX_MASK 0xfffffffu
+#define MODULE_EXTENSION_FLAG (1u << 28)
+#define MODULE_FLAG (1u << 29)
+
+#define DEFAULT_SINK "@DEFAULT_SINK@"
+#define DEFAULT_SOURCE "@DEFAULT_SOURCE@"
+#define DEFAULT_MONITOR "@DEFAULT_MONITOR@"
+
+enum error_code {
+ ERR_OK = 0, /**< No error */
+ ERR_ACCESS, /**< Access failure */
+ ERR_COMMAND, /**< Unknown command */
+ ERR_INVALID, /**< Invalid argument */
+ ERR_EXIST, /**< Entity exists */
+ ERR_NOENTITY, /**< No such entity */
+ ERR_CONNECTIONREFUSED, /**< Connection refused */
+ ERR_PROTOCOL, /**< Protocol error */
+ ERR_TIMEOUT, /**< Timeout */
+ ERR_AUTHKEY, /**< No authentication key */
+ ERR_INTERNAL, /**< Internal error */
+ ERR_CONNECTIONTERMINATED, /**< Connection terminated */
+ ERR_KILLED, /**< Entity killed */
+ ERR_INVALIDSERVER, /**< Invalid server */
+ ERR_MODINITFAILED, /**< Module initialization failed */
+ ERR_BADSTATE, /**< Bad state */
+ ERR_NODATA, /**< No data */
+ ERR_VERSION, /**< Incompatible protocol version */
+ ERR_TOOLARGE, /**< Data too large */
+ ERR_NOTSUPPORTED, /**< Operation not supported \since 0.9.5 */
+ ERR_UNKNOWN, /**< The error code was unknown to the client */
+ ERR_NOEXTENSION, /**< Extension does not exist. \since 0.9.12 */
+ ERR_OBSOLETE, /**< Obsolete functionality. \since 0.9.15 */
+ ERR_NOTIMPLEMENTED, /**< Missing implementation. \since 0.9.15 */
+ ERR_FORKED, /**< The caller forked without calling execve() and tried to reuse the context. \since 0.9.15 */
+ ERR_IO, /**< An IO error happened. \since 0.9.16 */
+ ERR_BUSY, /**< Device or resource busy. \since 0.9.17 */
+ ERR_MAX /**< Not really an error but the first invalid error code */
+};
+
+static inline int res_to_err(int res)
+{
+ switch (res) {
+ case 0: return ERR_OK;
+ case -EACCES: case -EPERM: return ERR_ACCESS;
+ case -ENOTTY: return ERR_COMMAND;
+ case -EINVAL: return ERR_INVALID;
+ case -EEXIST: return ERR_EXIST;
+ case -ENOENT: case -ESRCH: case -ENXIO: case -ENODEV: return ERR_NOENTITY;
+ case -ECONNREFUSED:
+#ifdef ENONET
+ case -ENONET:
+#endif
+ case -EHOSTDOWN: case -ENETDOWN: return ERR_CONNECTIONREFUSED;
+ case -EPROTO: case -EBADMSG: return ERR_PROTOCOL;
+ case -ETIMEDOUT:
+#ifdef ETIME
+ case -ETIME:
+#endif
+ return ERR_TIMEOUT;
+#ifdef ENOKEY
+ case -ENOKEY: return ERR_AUTHKEY;
+#endif
+ case -ECONNRESET: case -EPIPE: return ERR_CONNECTIONTERMINATED;
+#ifdef EBADFD
+ case -EBADFD: return ERR_BADSTATE;
+#endif
+#ifdef ENODATA
+ case -ENODATA: return ERR_NODATA;
+#endif
+ case -EOVERFLOW: case -E2BIG: case -EFBIG:
+ case -ERANGE: case -ENAMETOOLONG: return ERR_TOOLARGE;
+ case -ENOTSUP: case -EPROTONOSUPPORT: case -ESOCKTNOSUPPORT: return ERR_NOTSUPPORTED;
+ case -ENOSYS: return ERR_NOTIMPLEMENTED;
+ case -EIO: return ERR_IO;
+ case -EBUSY: return ERR_BUSY;
+ case -ENFILE: case -EMFILE: return ERR_INTERNAL;
+ }
+ return ERR_UNKNOWN;
+}
+
+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,
+ SUBSCRIPTION_EVENT_SINK_INPUT = 0x0002U,
+ SUBSCRIPTION_EVENT_SOURCE_OUTPUT = 0x0003U,
+ SUBSCRIPTION_EVENT_MODULE = 0x0004U,
+ SUBSCRIPTION_EVENT_CLIENT = 0x0005U,
+ SUBSCRIPTION_EVENT_SAMPLE_CACHE = 0x0006U,
+ SUBSCRIPTION_EVENT_SERVER = 0x0007U,
+ SUBSCRIPTION_EVENT_AUTOLOAD = 0x0008U,
+ SUBSCRIPTION_EVENT_CARD = 0x0009U,
+ SUBSCRIPTION_EVENT_FACILITY_MASK = 0x000FU,
+
+ SUBSCRIPTION_EVENT_NEW = 0x0000U,
+ SUBSCRIPTION_EVENT_CHANGE = 0x0010U,
+ SUBSCRIPTION_EVENT_REMOVE = 0x0020U,
+ SUBSCRIPTION_EVENT_TYPE_MASK = 0x0030U
+};
+
+enum {
+ STATE_INVALID = -1,
+ STATE_RUNNING = 0,
+ STATE_IDLE = 1,
+ STATE_SUSPENDED = 2,
+ STATE_INIT = -2,
+ STATE_UNLINKED = -3
+};
+
+static inline int node_state(enum pw_node_state state)
+{
+ switch (state) {
+ case PW_NODE_STATE_ERROR:
+ return STATE_UNLINKED;
+ case PW_NODE_STATE_CREATING:
+ return STATE_INIT;
+ case PW_NODE_STATE_SUSPENDED:
+ return STATE_SUSPENDED;
+ case PW_NODE_STATE_IDLE:
+ return STATE_IDLE;
+ case PW_NODE_STATE_RUNNING:
+ return STATE_RUNNING;
+ }
+ return STATE_INVALID;
+}
+
+enum {
+ SINK_HW_VOLUME_CTRL = 0x0001U,
+ SINK_LATENCY = 0x0002U,
+ SINK_HARDWARE = 0x0004U,
+ SINK_NETWORK = 0x0008U,
+ SINK_HW_MUTE_CTRL = 0x0010U,
+ SINK_DECIBEL_VOLUME = 0x0020U,
+ SINK_FLAT_VOLUME = 0x0040U,
+ SINK_DYNAMIC_LATENCY = 0x0080U,
+ SINK_SET_FORMATS = 0x0100U,
+};
+
+enum {
+ SOURCE_HW_VOLUME_CTRL = 0x0001U,
+ SOURCE_LATENCY = 0x0002U,
+ SOURCE_HARDWARE = 0x0004U,
+ SOURCE_NETWORK = 0x0008U,
+ SOURCE_HW_MUTE_CTRL = 0x0010U,
+ SOURCE_DECIBEL_VOLUME = 0x0020U,
+ SOURCE_DYNAMIC_LATENCY = 0x0040U,
+ SOURCE_FLAT_VOLUME = 0x0080U,
+};
+
+static const char * const port_types[] = {
+ "unknown",
+ "aux",
+ "speaker",
+ "headphones",
+ "line",
+ "mic",
+ "headset",
+ "handset",
+ "earpiece",
+ "spdif",
+ "hdmi",
+ "tv",
+ "radio",
+ "video",
+ "usb",
+ "bluetooth",
+ "portable",
+ "handsfree",
+ "car",
+ "hifi",
+ "phone",
+ "network",
+ "analog",
+};
+
+static inline uint32_t port_type_value(const char *port_type)
+{
+ uint32_t i;
+ for (i = 0; i < SPA_N_ELEMENTS(port_types); i++) {
+ if (strcmp(port_types[i], port_type) == 0)
+ return i;
+ }
+ return 0;
+}
+
+#define METADATA_DEFAULT_SINK "default.audio.sink"
+#define METADATA_DEFAULT_SOURCE "default.audio.source"
+#define METADATA_CONFIG_DEFAULT_SINK "default.configured.audio.sink"
+#define METADATA_CONFIG_DEFAULT_SOURCE "default.configured.audio.source"
+#define METADATA_TARGET_NODE "target.node"
+#define METADATA_TARGET_OBJECT "target.object"
+
+#endif /* PULSE_SERVER_DEFS_H */
diff --git a/src/modules/module-protocol-pulse/extension.c b/src/modules/module-protocol-pulse/extension.c
new file mode 100644
index 0000000..0ffa4c5
--- /dev/null
+++ b/src/modules/module-protocol-pulse/extension.c
@@ -0,0 +1,45 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/defs.h>
+#include <spa/utils/string.h>
+
+#include "defs.h"
+#include "extension.h"
+#include "extensions/registry.h"
+
+static const struct extension extensions[] = {
+ { "module-stream-restore", 0 | MODULE_EXTENSION_FLAG, do_extension_stream_restore, },
+ { "module-device-restore", 1 | MODULE_EXTENSION_FLAG, do_extension_device_restore, },
+ { "module-device-manager", 2 | MODULE_EXTENSION_FLAG, do_extension_device_manager, },
+};
+
+const struct extension *extension_find(uint32_t index, const char *name)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(extensions, ext) {
+ if (index == ext->index || spa_streq(name, ext->name))
+ return ext;
+ }
+ return NULL;
+}
diff --git a/src/modules/module-protocol-pulse/extension.h b/src/modules/module-protocol-pulse/extension.h
new file mode 100644
index 0000000..3c1b059
--- /dev/null
+++ b/src/modules/module-protocol-pulse/extension.h
@@ -0,0 +1,47 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSE_SERVER_EXTENSION_H
+#define PULSE_SERVER_EXTENSION_H
+
+#include <stdint.h>
+
+struct client;
+struct message;
+
+struct extension_sub {
+ const char *name;
+ uint32_t command;
+ int (*process)(struct client *client, uint32_t command, uint32_t tag, struct message *m);
+};
+
+struct extension {
+ const char *name;
+ uint32_t index;
+ int (*process)(struct client *client, uint32_t tag, struct message *m);
+};
+
+const struct extension *extension_find(uint32_t index, const char *name);
+
+#endif /* PULSE_SERVER_EXTENSION_H */
diff --git a/src/modules/module-protocol-pulse/extensions/ext-device-manager.c b/src/modules/module-protocol-pulse/extensions/ext-device-manager.c
new file mode 100644
index 0000000..2ba080e
--- /dev/null
+++ b/src/modules/module-protocol-pulse/extensions/ext-device-manager.c
@@ -0,0 +1,8 @@
+#include <errno.h>
+
+#include "registry.h"
+
+int do_extension_device_manager(struct client *client, uint32_t tag, struct message *m)
+{
+ return -ENOTSUP;
+}
diff --git a/src/modules/module-protocol-pulse/extensions/ext-device-restore.c b/src/modules/module-protocol-pulse/extensions/ext-device-restore.c
new file mode 100644
index 0000000..4fab654
--- /dev/null
+++ b/src/modules/module-protocol-pulse/extensions/ext-device-restore.c
@@ -0,0 +1,340 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#define EXT_DEVICE_RESTORE_VERSION 1
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/dict.h>
+#include <spa/utils/string.h>
+#include <spa/utils/json.h>
+#include <pipewire/log.h>
+#include <pipewire/properties.h>
+
+#include "../client.h"
+#include "../collect.h"
+#include "../defs.h"
+#include "../extension.h"
+#include "../format.h"
+#include "../manager.h"
+#include "../message.h"
+#include "../reply.h"
+#include "../volume.h"
+#include "registry.h"
+
+PW_LOG_TOPIC_EXTERN(pulse_ext_dev_restore);
+#undef PW_LOG_TOPIC_DEFAULT
+#define PW_LOG_TOPIC_DEFAULT pulse_ext_dev_restore
+
+#define DEVICE_TYPE_SINK 0
+#define DEVICE_TYPE_SOURCE 1
+
+static int do_extension_device_restore_test(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct message *reply;
+
+ reply = reply_new(client, tag);
+ message_put(reply,
+ TAG_U32, EXT_DEVICE_RESTORE_VERSION,
+ TAG_INVALID);
+
+ return client_queue_message(client, reply);
+}
+
+static int do_extension_device_restore_subscribe(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ return reply_simple_ack(client, tag);
+}
+
+
+struct format_data {
+ struct client *client;
+ struct message *reply;
+};
+
+static int do_sink_read_format(void *data, struct pw_manager_object *o)
+{
+ struct format_data *d = data;
+ struct pw_manager_param *p;
+ struct format_info info[32];
+ uint32_t i, n_info = 0;
+
+ if (!pw_manager_object_is_sink(o))
+ return 0;
+
+ spa_list_for_each(p, &o->param_list, link) {
+ uint32_t index = 0;
+
+ if (p->id != SPA_PARAM_EnumFormat)
+ continue;
+
+ while (n_info < SPA_N_ELEMENTS(info)) {
+ spa_zero(info[n_info]);
+ if (format_info_from_param(&info[n_info], p->param, index++) < 0)
+ break;
+ if (info[n_info].encoding == ENCODING_ANY) {
+ format_info_clear(&info[n_info]);
+ continue;
+ }
+ n_info++;
+ }
+ }
+ message_put(d->reply,
+ TAG_U32, DEVICE_TYPE_SINK,
+ TAG_U32, o->index, /* sink index */
+ TAG_U8, n_info, /* n_formats */
+ TAG_INVALID);
+ for (i = 0; i < n_info; i++) {
+ message_put(d->reply,
+ TAG_FORMAT_INFO, &info[i],
+ TAG_INVALID);
+ format_info_clear(&info[i]);
+ }
+ return 0;
+}
+
+static int do_extension_device_restore_read_formats_all(struct client *client,
+ uint32_t command, uint32_t tag, struct message *m)
+{
+ struct pw_manager *manager = client->manager;
+ struct format_data data;
+
+ spa_zero(data);
+ data.client = client;
+ data.reply = reply_new(client, tag);
+
+ pw_manager_for_each_object(manager, do_sink_read_format, &data);
+
+ return client_queue_message(client, data.reply);
+}
+
+static int do_extension_device_restore_read_formats(struct client *client,
+ uint32_t command, uint32_t tag, struct message *m)
+{
+ struct pw_manager *manager = client->manager;
+ struct format_data data;
+ uint32_t type, sink_index;
+ struct selector sel;
+ struct pw_manager_object *o;
+ int res;
+
+ if ((res = message_get(m,
+ TAG_U32, &type,
+ TAG_U32, &sink_index,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ if (type != DEVICE_TYPE_SINK) {
+ pw_log_info("Device format reading is only supported on sinks");
+ return -ENOTSUP;
+ }
+
+ spa_zero(sel);
+ sel.index = sink_index;
+ sel.type = pw_manager_object_is_sink;
+
+ o = select_object(manager, &sel);
+ if (o == NULL)
+ return -ENOENT;
+
+ spa_zero(data);
+ data.client = client;
+ data.reply = reply_new(client, tag);
+
+ do_sink_read_format(&data, o);
+
+ return client_queue_message(client, data.reply);
+}
+
+static int set_card_codecs(struct pw_manager_object *o, uint32_t port_index,
+ uint32_t device_id, uint32_t n_codecs, uint32_t *codecs)
+{
+ char buf[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
+ struct spa_pod_frame f[2];
+ struct spa_pod *param;
+
+ if (!SPA_FLAG_IS_SET(o->permissions, PW_PERM_W | PW_PERM_X))
+ return -EACCES;
+
+ if (o->proxy == NULL)
+ return -ENOENT;
+
+ spa_pod_builder_push_object(&b, &f[0],
+ SPA_TYPE_OBJECT_ParamRoute, SPA_PARAM_Route);
+ spa_pod_builder_add(&b,
+ SPA_PARAM_ROUTE_index, SPA_POD_Int(port_index),
+ SPA_PARAM_ROUTE_device, SPA_POD_Int(device_id),
+ 0);
+ spa_pod_builder_prop(&b, SPA_PARAM_ROUTE_props, 0);
+ spa_pod_builder_push_object(&b, &f[1],
+ SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
+ spa_pod_builder_add(&b,
+ SPA_PROP_iec958Codecs, SPA_POD_Array(sizeof(uint32_t),
+ SPA_TYPE_Id, n_codecs, codecs), 0);
+ spa_pod_builder_pop(&b, &f[1]);
+ spa_pod_builder_prop(&b, SPA_PARAM_ROUTE_save, 0);
+ spa_pod_builder_bool(&b, true);
+ param = spa_pod_builder_pop(&b, &f[0]);
+
+ pw_device_set_param((struct pw_device*)o->proxy,
+ SPA_PARAM_Route, 0, param);
+ return 0;
+}
+
+static int set_node_codecs(struct pw_manager_object *o, uint32_t n_codecs, uint32_t *codecs)
+{
+ char buf[1024];
+ struct spa_pod_builder b;
+ struct spa_pod *param;
+
+ if (!SPA_FLAG_IS_SET(o->permissions, PW_PERM_W | PW_PERM_X))
+ return -EACCES;
+
+ if (o->proxy == NULL)
+ return -ENOENT;
+
+ spa_pod_builder_init(&b, buf, sizeof(buf));
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_Props, SPA_PARAM_Props,
+ SPA_PROP_iec958Codecs, SPA_POD_Array(sizeof(uint32_t),
+ SPA_TYPE_Id, n_codecs, codecs));
+
+ pw_node_set_param((struct pw_node*)o->proxy,
+ SPA_PARAM_Props, 0, param);
+
+ return 0;
+}
+
+
+static int do_extension_device_restore_save_formats(struct client *client,
+ uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ struct pw_manager *manager = client->manager;
+ struct selector sel;
+ struct pw_manager_object *o, *card = NULL;
+ struct pw_node_info *info;
+ int res;
+ uint32_t type, sink_index, card_id = SPA_ID_INVALID;
+ uint8_t i, n_formats;
+ uint32_t n_codecs = 0, codec, iec958codecs[32];
+ struct device_info dev_info;
+ const char *str;
+
+ if ((res = message_get(m,
+ TAG_U32, &type,
+ TAG_U32, &sink_index,
+ TAG_U8, &n_formats,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+ if (n_formats < 1)
+ return -EPROTO;
+
+ if (type != DEVICE_TYPE_SINK)
+ return -ENOTSUP;
+
+ for (i = 0; i < n_formats; ++i) {
+ struct format_info format;
+ spa_zero(format);
+ if (message_get(m,
+ TAG_FORMAT_INFO, &format,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ codec = format_encoding2id(format.encoding);
+ if (codec != SPA_ID_INVALID && n_codecs < SPA_N_ELEMENTS(iec958codecs))
+ iec958codecs[n_codecs++] = codec;
+
+ format_info_clear(&format);
+ }
+ if (n_codecs == 0)
+ return -ENOTSUP;
+
+ spa_zero(sel);
+ sel.index = sink_index;
+ sel.type = pw_manager_object_is_sink;
+
+ o = select_object(manager, &sel);
+ if (o == NULL || (info = o->info) == NULL || info->props == NULL)
+ return -ENOENT;
+
+ dev_info = DEVICE_INFO_INIT(SPA_DIRECTION_INPUT);
+
+ if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)) != NULL)
+ card_id = (uint32_t)atoi(str);
+ if ((str = spa_dict_lookup(info->props, "card.profile.device")) != NULL)
+ dev_info.device = (uint32_t)atoi(str);
+ if (card_id != SPA_ID_INVALID) {
+ struct selector sel = { .id = card_id, .type = pw_manager_object_is_card, };
+ card = select_object(manager, &sel);
+ }
+ collect_device_info(o, card, &dev_info, false, &impl->defs);
+
+ if (card != NULL && dev_info.active_port != SPA_ID_INVALID) {
+ res = set_card_codecs(card, dev_info.active_port,
+ dev_info.device, n_codecs, iec958codecs);
+ } else {
+ res = set_node_codecs(o, n_codecs, iec958codecs);
+ }
+ if (res < 0)
+ return res;
+
+ return reply_simple_ack(client, tag);
+}
+
+static const struct extension_sub ext_device_restore[] = {
+ { "TEST", 0, do_extension_device_restore_test, },
+ { "SUBSCRIBE", 1, do_extension_device_restore_subscribe, },
+ { "EVENT", 2, },
+ { "READ_FORMATS_ALL", 3, do_extension_device_restore_read_formats_all, },
+ { "READ_FORMATS", 4, do_extension_device_restore_read_formats, },
+ { "SAVE_FORMATS", 5, do_extension_device_restore_save_formats, },
+};
+
+int do_extension_device_restore(struct client *client, uint32_t tag, struct message *m)
+{
+ uint32_t command;
+ int res;
+
+ if ((res = message_get(m,
+ TAG_U32, &command,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ if (command >= SPA_N_ELEMENTS(ext_device_restore))
+ return -ENOTSUP;
+ if (ext_device_restore[command].process == NULL)
+ return -EPROTO;
+
+ pw_log_info("client %p [%s]: EXT_DEVICE_RESTORE_%s tag:%u",
+ client, client->name, ext_device_restore[command].name, tag);
+
+ return ext_device_restore[command].process(client, command, tag, m);
+}
diff --git a/src/modules/module-protocol-pulse/extensions/ext-stream-restore.c b/src/modules/module-protocol-pulse/extensions/ext-stream-restore.c
new file mode 100644
index 0000000..76c7332
--- /dev/null
+++ b/src/modules/module-protocol-pulse/extensions/ext-stream-restore.c
@@ -0,0 +1,330 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#define EXT_STREAM_RESTORE_VERSION 1
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/dict.h>
+#include <spa/utils/string.h>
+#include <spa/utils/json.h>
+#include <pipewire/log.h>
+#include <pipewire/properties.h>
+
+#include "../client.h"
+#include "../defs.h"
+#include "../extension.h"
+#include "../format.h"
+#include "../manager.h"
+#include "../message.h"
+#include "../remap.h"
+#include "../reply.h"
+#include "../volume.h"
+#include "registry.h"
+
+PW_LOG_TOPIC_EXTERN(pulse_ext_stream_restore);
+#undef PW_LOG_TOPIC_DEFAULT
+#define PW_LOG_TOPIC_DEFAULT pulse_ext_stream_restore
+
+static int do_extension_stream_restore_test(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct message *reply;
+
+ reply = reply_new(client, tag);
+ message_put(reply,
+ TAG_U32, EXT_STREAM_RESTORE_VERSION,
+ TAG_INVALID);
+
+ return client_queue_message(client, reply);
+}
+
+static int key_from_name(const char *name, char *key, size_t maxlen)
+{
+ const char *media_class, *select, *str;
+
+ if (spa_strstartswith(name, "sink-input-"))
+ media_class = "Output/Audio";
+ else if (spa_strstartswith(name, "source-output-"))
+ media_class = "Input/Audio";
+ else
+ return -1;
+
+ if ((str = strstr(name, "-by-media-role:")) != NULL) {
+ const struct str_map *map;
+ str += strlen("-by-media-role:");
+ map = str_map_find(media_role_map, NULL, str);
+ str = map ? map->pw_str : str;
+ select = "media.role";
+ }
+ else if ((str = strstr(name, "-by-application-id:")) != NULL) {
+ str += strlen("-by-application-id:");
+ select = "application.id";
+ }
+ else if ((str = strstr(name, "-by-application-name:")) != NULL) {
+ str += strlen("-by-application-name:");
+ select = "application.name";
+ }
+ else if ((str = strstr(name, "-by-media-name:")) != NULL) {
+ str += strlen("-by-media-name:");
+ select = "media.name";
+ } else
+ return -1;
+
+ snprintf(key, maxlen, "restore.stream.%s.%s:%s",
+ media_class, select, str);
+ return 0;
+}
+
+static int key_to_name(const char *key, char *name, size_t maxlen)
+{
+ const char *type, *select, *str;
+
+ if (spa_strstartswith(key, "restore.stream.Output/Audio."))
+ type = "sink-input";
+ else if (spa_strstartswith(key, "restore.stream.Input/Audio."))
+ type = "source-output";
+ else
+ type = "stream";
+
+ if ((str = strstr(key, ".media.role:")) != NULL) {
+ const struct str_map *map;
+ str += strlen(".media.role:");
+ map = str_map_find(media_role_map, str, NULL);
+ select = "media-role";
+ str = map ? map->pa_str : str;
+ }
+ else if ((str = strstr(key, ".application.id:")) != NULL) {
+ str += strlen(".application.id:");
+ select = "application-id";
+ }
+ else if ((str = strstr(key, ".application.name:")) != NULL) {
+ str += strlen(".application.name:");
+ select = "application-name";
+ }
+ else if ((str = strstr(key, ".media.name:")) != NULL) {
+ str += strlen(".media.name:");
+ select = "media-name";
+ }
+ else
+ return -1;
+
+ snprintf(name, maxlen, "%s-by-%s:%s", type, select, str);
+ return 0;
+
+}
+
+static int do_extension_stream_restore_read(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct message *reply;
+ const struct spa_dict_item *item;
+
+ reply = reply_new(client, tag);
+
+ spa_dict_for_each(item, &client->routes->dict) {
+ struct spa_json it[3];
+ const char *value;
+ char name[1024], key[128];
+ char device_name[1024] = "\0";
+ bool mute = false;
+ struct volume vol = VOLUME_INIT;
+ struct channel_map map = CHANNEL_MAP_INIT;
+ float volume = 0.0f;
+
+ 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)
+ continue;
+
+ while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) {
+ if (spa_streq(key, "volume")) {
+ if (spa_json_get_float(&it[1], &volume) <= 0)
+ continue;
+ }
+ else if (spa_streq(key, "mute")) {
+ if (spa_json_get_bool(&it[1], &mute) <= 0)
+ continue;
+ }
+ else if (spa_streq(key, "volumes")) {
+ vol = VOLUME_INIT;
+ if (spa_json_enter_array(&it[1], &it[2]) <= 0)
+ continue;
+
+ for (vol.channels = 0; vol.channels < CHANNELS_MAX; vol.channels++) {
+ if (spa_json_get_float(&it[2], &vol.values[vol.channels]) <= 0)
+ break;
+ }
+ }
+ else if (spa_streq(key, "channels")) {
+ if (spa_json_enter_array(&it[1], &it[2]) <= 0)
+ continue;
+
+ for (map.channels = 0; map.channels < CHANNELS_MAX; map.channels++) {
+ char chname[16];
+ if (spa_json_get_string(&it[2], 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)
+ continue;
+ }
+ else if (spa_json_next(&it[1], &value) <= 0)
+ break;
+ }
+ message_put(reply,
+ TAG_STRING, name,
+ TAG_CHANNEL_MAP, &map,
+ TAG_CVOLUME, &vol,
+ TAG_STRING, device_name[0] ? device_name : NULL,
+ TAG_BOOLEAN, mute,
+ TAG_INVALID);
+ }
+
+ return client_queue_message(client, reply);
+}
+
+static int do_extension_stream_restore_write(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ int res;
+ uint32_t mode;
+ bool apply;
+
+ if ((res = message_get(m,
+ TAG_U32, &mode,
+ TAG_BOOLEAN, &apply,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ while (m->offset < m->length) {
+ const char *name, *device_name = NULL;
+ struct channel_map map;
+ struct volume vol;
+ bool mute = false;
+ uint32_t i;
+ FILE *f;
+ char *ptr;
+ size_t size;
+ char key[1024], buf[128];
+
+ spa_zero(map);
+ spa_zero(vol);
+
+ if (message_get(m,
+ TAG_STRING, &name,
+ TAG_CHANNEL_MAP, &map,
+ TAG_CVOLUME, &vol,
+ TAG_STRING, &device_name,
+ TAG_BOOLEAN, &mute,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ if (name == NULL || name[0] == '\0')
+ return -EPROTO;
+
+ if ((f = open_memstream(&ptr, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ fprintf(f, " \"mute\": %s", mute ? "true" : "false");
+ if (vol.channels > 0) {
+ fprintf(f, ", \"volumes\": [");
+ for (i = 0; i < vol.channels; i++)
+ fprintf(f, "%s%s", (i == 0 ? " ":", "),
+ spa_json_format_float(buf, sizeof(buf), vol.values[i]));
+ fprintf(f, " ]");
+ }
+ if (map.channels > 0) {
+ fprintf(f, ", \"channels\": [");
+ for (i = 0; i < map.channels; i++)
+ fprintf(f, "%s\"%s\"", (i == 0 ? " ":", "), channel_id2name(map.map[i]));
+ fprintf(f, " ]");
+ }
+ if (device_name != NULL && device_name[0] &&
+ (client->default_source == NULL || !spa_streq(device_name, client->default_source)) &&
+ (client->default_sink == NULL || !spa_streq(device_name, client->default_sink)))
+ fprintf(f, ", \"target-node\": \"%s\"", device_name);
+ fprintf(f, " }");
+ fclose(f);
+ if (key_from_name(name, key, sizeof(key)) >= 0) {
+ pw_log_debug("%s -> %s: %s", name, key, ptr);
+ if ((res = pw_manager_set_metadata(client->manager,
+ client->metadata_routes,
+ PW_ID_CORE, key, "Spa:String:JSON", "%s", ptr)) < 0)
+ pw_log_warn("failed to set metadata %s = %s, %s", key, ptr, strerror(-res));
+ }
+ free(ptr);
+ }
+
+ return reply_simple_ack(client, tag);
+}
+
+static int do_extension_stream_restore_delete(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ return reply_simple_ack(client, tag);
+}
+
+static int do_extension_stream_restore_subscribe(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ return reply_simple_ack(client, tag);
+}
+
+static const struct extension_sub ext_stream_restore[] = {
+ { "TEST", 0, do_extension_stream_restore_test, },
+ { "READ", 1, do_extension_stream_restore_read, },
+ { "WRITE", 2, do_extension_stream_restore_write, },
+ { "DELETE", 3, do_extension_stream_restore_delete, },
+ { "SUBSCRIBE", 4, do_extension_stream_restore_subscribe, },
+ { "EVENT", 5, },
+};
+
+int do_extension_stream_restore(struct client *client, uint32_t tag, struct message *m)
+{
+ uint32_t command;
+ int res;
+
+ if ((res = message_get(m,
+ TAG_U32, &command,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ if (command >= SPA_N_ELEMENTS(ext_stream_restore))
+ return -ENOTSUP;
+ if (ext_stream_restore[command].process == NULL)
+ return -EPROTO;
+
+ pw_log_info("client %p [%s]: EXT_STREAM_RESTORE_%s tag:%u",
+ client, client->name, ext_stream_restore[command].name, tag);
+
+ return ext_stream_restore[command].process(client, command, tag, m);
+}
diff --git a/src/modules/module-protocol-pulse/extensions/registry.h b/src/modules/module-protocol-pulse/extensions/registry.h
new file mode 100644
index 0000000..ab9faf7
--- /dev/null
+++ b/src/modules/module-protocol-pulse/extensions/registry.h
@@ -0,0 +1,13 @@
+#ifndef PIPEWIRE_PULSE_EXTENSION_REGISTRY_H
+#define PIPEWIRE_PULSE_EXTENSION_REGISTRY_H
+
+#include <stdint.h>
+
+struct client;
+struct message;
+
+int do_extension_stream_restore(struct client *client, uint32_t tag, struct message *m);
+int do_extension_device_restore(struct client *client, uint32_t tag, struct message *m);
+int do_extension_device_manager(struct client *client, uint32_t tag, struct message *m);
+
+#endif /* PIPEWIRE_PULSE_EXTENSION_REGISTRY_H */
diff --git a/src/modules/module-protocol-pulse/format.c b/src/modules/module-protocol-pulse/format.c
new file mode 100644
index 0000000..ced75cf
--- /dev/null
+++ b/src/modules/module-protocol-pulse/format.c
@@ -0,0 +1,861 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/string.h>
+#include <spa/debug/types.h>
+#include <spa/param/audio/format.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/audio/raw.h>
+#include <spa/utils/json.h>
+
+#include "format.h"
+
+static const struct format audio_formats[] = {
+ [SAMPLE_U8] = { SAMPLE_U8, SPA_AUDIO_FORMAT_U8, "u8", 1 },
+ [SAMPLE_ALAW] = { SAMPLE_ALAW, SPA_AUDIO_FORMAT_ALAW, "aLaw", 1 },
+ [SAMPLE_ULAW] = { SAMPLE_ULAW, SPA_AUDIO_FORMAT_ULAW, "uLaw", 1 },
+ [SAMPLE_S16LE] = { SAMPLE_S16LE, SPA_AUDIO_FORMAT_S16_LE, "s16le", 2 },
+ [SAMPLE_S16BE] = { SAMPLE_S16BE, SPA_AUDIO_FORMAT_S16_BE, "s16be", 2 },
+ [SAMPLE_FLOAT32LE] = { SAMPLE_FLOAT32LE, SPA_AUDIO_FORMAT_F32_LE, "float32le", 4 },
+ [SAMPLE_FLOAT32BE] = { SAMPLE_FLOAT32BE, SPA_AUDIO_FORMAT_F32_BE, "float32be", 4 },
+ [SAMPLE_S32LE] = { SAMPLE_S32LE, SPA_AUDIO_FORMAT_S32_LE, "s32le", 4 },
+ [SAMPLE_S32BE] = { SAMPLE_S32BE, SPA_AUDIO_FORMAT_S32_BE, "s32be", 4 },
+ [SAMPLE_S24LE] = { SAMPLE_S24LE, SPA_AUDIO_FORMAT_S24_LE, "s24le", 3 },
+ [SAMPLE_S24BE] = { SAMPLE_S24BE, SPA_AUDIO_FORMAT_S24_BE, "s24be", 3 },
+ [SAMPLE_S24_32LE] = { SAMPLE_S24_32LE, SPA_AUDIO_FORMAT_S24_32_LE, "s24-32le", 4 },
+ [SAMPLE_S24_32BE] = { SAMPLE_S24_32BE, SPA_AUDIO_FORMAT_S24_32_BE, "s24-32be", 4 },
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+ { SAMPLE_S16BE, SPA_AUDIO_FORMAT_S16_BE, "s16ne", 2 },
+ { SAMPLE_FLOAT32BE, SPA_AUDIO_FORMAT_F32_BE, "float32ne", 4 },
+ { SAMPLE_S32BE, SPA_AUDIO_FORMAT_S32_BE, "s32ne", 4 },
+ { SAMPLE_S24BE, SPA_AUDIO_FORMAT_S24_BE, "s24ne", 3 },
+ { SAMPLE_S24_32BE, SPA_AUDIO_FORMAT_S24_32_BE, "s24-32ne", 4 },
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+ { SAMPLE_S16LE, SPA_AUDIO_FORMAT_S16_LE, "s16ne", 2 },
+ { SAMPLE_FLOAT32LE, SPA_AUDIO_FORMAT_F32_LE, "float32ne", 4 },
+ { SAMPLE_S32LE, SPA_AUDIO_FORMAT_S32_LE, "s32ne", 4 },
+ { SAMPLE_S24LE, SPA_AUDIO_FORMAT_S24_LE, "s24ne", 3 },
+ { SAMPLE_S24_32LE, SPA_AUDIO_FORMAT_S24_32_LE, "s24-32ne", 4 },
+#endif
+ /* planar formats, we just report them as interleaved */
+ { SAMPLE_U8, SPA_AUDIO_FORMAT_U8P, "u8ne", 1 },
+ { SAMPLE_S16NE, SPA_AUDIO_FORMAT_S16P, "s16ne", 2 },
+ { SAMPLE_S24_32NE, SPA_AUDIO_FORMAT_S24_32P, "s24-32ne", 4 },
+ { SAMPLE_S32NE, SPA_AUDIO_FORMAT_S32P, "s32ne", 4 },
+ { SAMPLE_S24NE, SPA_AUDIO_FORMAT_S24P, "s24ne", 3 },
+ { SAMPLE_FLOAT32NE, SPA_AUDIO_FORMAT_F32P, "float32ne", 4 },
+};
+
+static const struct channel audio_channels[] = {
+ [CHANNEL_POSITION_MONO] = { SPA_AUDIO_CHANNEL_MONO, "mono", },
+
+ [CHANNEL_POSITION_FRONT_LEFT] = { SPA_AUDIO_CHANNEL_FL, "front-left", },
+ [CHANNEL_POSITION_FRONT_RIGHT] = { SPA_AUDIO_CHANNEL_FR, "front-right", },
+ [CHANNEL_POSITION_FRONT_CENTER] = { SPA_AUDIO_CHANNEL_FC, "front-center", },
+
+ [CHANNEL_POSITION_REAR_CENTER] = { SPA_AUDIO_CHANNEL_RC, "rear-center", },
+ [CHANNEL_POSITION_REAR_LEFT] = { SPA_AUDIO_CHANNEL_RL, "rear-left", },
+ [CHANNEL_POSITION_REAR_RIGHT] = { SPA_AUDIO_CHANNEL_RR, "rear-right", },
+
+ [CHANNEL_POSITION_LFE] = { SPA_AUDIO_CHANNEL_LFE, "lfe", },
+ [CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = { SPA_AUDIO_CHANNEL_FLC, "front-left-of-center", },
+ [CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = { SPA_AUDIO_CHANNEL_FRC, "front-right-of-center", },
+
+ [CHANNEL_POSITION_SIDE_LEFT] = { SPA_AUDIO_CHANNEL_SL, "side-left", },
+ [CHANNEL_POSITION_SIDE_RIGHT] = { SPA_AUDIO_CHANNEL_SR, "side-right", },
+
+ [CHANNEL_POSITION_AUX0] = { SPA_AUDIO_CHANNEL_AUX0, "aux0", },
+ [CHANNEL_POSITION_AUX1] = { SPA_AUDIO_CHANNEL_AUX1, "aux1", },
+ [CHANNEL_POSITION_AUX2] = { SPA_AUDIO_CHANNEL_AUX2, "aux2", },
+ [CHANNEL_POSITION_AUX3] = { SPA_AUDIO_CHANNEL_AUX3, "aux3", },
+ [CHANNEL_POSITION_AUX4] = { SPA_AUDIO_CHANNEL_AUX4, "aux4", },
+ [CHANNEL_POSITION_AUX5] = { SPA_AUDIO_CHANNEL_AUX5, "aux5", },
+ [CHANNEL_POSITION_AUX6] = { SPA_AUDIO_CHANNEL_AUX6, "aux6", },
+ [CHANNEL_POSITION_AUX7] = { SPA_AUDIO_CHANNEL_AUX7, "aux7", },
+ [CHANNEL_POSITION_AUX8] = { SPA_AUDIO_CHANNEL_AUX8, "aux8", },
+ [CHANNEL_POSITION_AUX9] = { SPA_AUDIO_CHANNEL_AUX9, "aux9", },
+ [CHANNEL_POSITION_AUX10] = { SPA_AUDIO_CHANNEL_AUX10, "aux10", },
+ [CHANNEL_POSITION_AUX11] = { SPA_AUDIO_CHANNEL_AUX11, "aux11", },
+ [CHANNEL_POSITION_AUX12] = { SPA_AUDIO_CHANNEL_AUX12, "aux12", },
+ [CHANNEL_POSITION_AUX13] = { SPA_AUDIO_CHANNEL_AUX13, "aux13", },
+ [CHANNEL_POSITION_AUX14] = { SPA_AUDIO_CHANNEL_AUX14, "aux14", },
+ [CHANNEL_POSITION_AUX15] = { SPA_AUDIO_CHANNEL_AUX15, "aux15", },
+ [CHANNEL_POSITION_AUX16] = { SPA_AUDIO_CHANNEL_AUX16, "aux16", },
+ [CHANNEL_POSITION_AUX17] = { SPA_AUDIO_CHANNEL_AUX17, "aux17", },
+ [CHANNEL_POSITION_AUX18] = { SPA_AUDIO_CHANNEL_AUX18, "aux18", },
+ [CHANNEL_POSITION_AUX19] = { SPA_AUDIO_CHANNEL_AUX19, "aux19", },
+ [CHANNEL_POSITION_AUX20] = { SPA_AUDIO_CHANNEL_AUX20, "aux20", },
+ [CHANNEL_POSITION_AUX21] = { SPA_AUDIO_CHANNEL_AUX21, "aux21", },
+ [CHANNEL_POSITION_AUX22] = { SPA_AUDIO_CHANNEL_AUX22, "aux22", },
+ [CHANNEL_POSITION_AUX23] = { SPA_AUDIO_CHANNEL_AUX23, "aux23", },
+ [CHANNEL_POSITION_AUX24] = { SPA_AUDIO_CHANNEL_AUX24, "aux24", },
+ [CHANNEL_POSITION_AUX25] = { SPA_AUDIO_CHANNEL_AUX25, "aux25", },
+ [CHANNEL_POSITION_AUX26] = { SPA_AUDIO_CHANNEL_AUX26, "aux26", },
+ [CHANNEL_POSITION_AUX27] = { SPA_AUDIO_CHANNEL_AUX27, "aux27", },
+ [CHANNEL_POSITION_AUX28] = { SPA_AUDIO_CHANNEL_AUX28, "aux28", },
+ [CHANNEL_POSITION_AUX29] = { SPA_AUDIO_CHANNEL_AUX29, "aux29", },
+ [CHANNEL_POSITION_AUX30] = { SPA_AUDIO_CHANNEL_AUX30, "aux30", },
+ [CHANNEL_POSITION_AUX31] = { SPA_AUDIO_CHANNEL_AUX31, "aux31", },
+
+ [CHANNEL_POSITION_TOP_CENTER] = { SPA_AUDIO_CHANNEL_TC, "top-center", },
+
+ [CHANNEL_POSITION_TOP_FRONT_LEFT] = { SPA_AUDIO_CHANNEL_TFL, "top-front-left", },
+ [CHANNEL_POSITION_TOP_FRONT_RIGHT] = { SPA_AUDIO_CHANNEL_TFR, "top-front-right", },
+ [CHANNEL_POSITION_TOP_FRONT_CENTER] = { SPA_AUDIO_CHANNEL_TFC, "top-front-center", },
+
+ [CHANNEL_POSITION_TOP_REAR_LEFT] = { SPA_AUDIO_CHANNEL_TRL, "top-rear-left", },
+ [CHANNEL_POSITION_TOP_REAR_RIGHT] = { SPA_AUDIO_CHANNEL_TRR, "top-rear-right", },
+ [CHANNEL_POSITION_TOP_REAR_CENTER] = { SPA_AUDIO_CHANNEL_TRC, "top-rear-center", },
+};
+
+uint32_t format_pa2id(enum sample_format format)
+{
+ if (format < 0 || format >= SAMPLE_MAX)
+ return SPA_AUDIO_FORMAT_UNKNOWN;
+ return audio_formats[format].id;
+}
+
+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";
+}
+
+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_CHANNEL_UNKNOWN;
+}
+
+uint32_t format_paname2id(const char *name, size_t size)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(audio_formats, f) {
+ if (f->name != NULL &&
+ strncmp(name, f->name, size) == 0)
+ return f->id;
+ }
+ return SPA_AUDIO_FORMAT_UNKNOWN;
+}
+
+enum sample_format format_id2pa(uint32_t id)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(audio_formats, f) {
+ if (id == f->id)
+ return f->pa;
+ }
+ return SAMPLE_INVALID;
+}
+
+const char *format_id2paname(uint32_t id)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(audio_formats, f) {
+ if (id == f->id && f->name != NULL)
+ return f->name;
+ }
+ return "invalid";
+}
+
+uint32_t sample_spec_frame_size(const struct sample_spec *ss)
+{
+ switch (ss->format) {
+ case SPA_AUDIO_FORMAT_U8:
+ case SPA_AUDIO_FORMAT_U8P:
+ case SPA_AUDIO_FORMAT_S8:
+ case SPA_AUDIO_FORMAT_S8P:
+ case SPA_AUDIO_FORMAT_ULAW:
+ case SPA_AUDIO_FORMAT_ALAW:
+ return ss->channels;
+ case SPA_AUDIO_FORMAT_S16_LE:
+ case SPA_AUDIO_FORMAT_S16_BE:
+ case SPA_AUDIO_FORMAT_S16P:
+ case SPA_AUDIO_FORMAT_U16_LE:
+ case SPA_AUDIO_FORMAT_U16_BE:
+ return 2 * ss->channels;
+ case SPA_AUDIO_FORMAT_S24_LE:
+ case SPA_AUDIO_FORMAT_S24_BE:
+ case SPA_AUDIO_FORMAT_S24P:
+ case SPA_AUDIO_FORMAT_U24_LE:
+ case SPA_AUDIO_FORMAT_U24_BE:
+ case SPA_AUDIO_FORMAT_S20_LE:
+ case SPA_AUDIO_FORMAT_S20_BE:
+ case SPA_AUDIO_FORMAT_U20_LE:
+ case SPA_AUDIO_FORMAT_U20_BE:
+ case SPA_AUDIO_FORMAT_S18_LE:
+ case SPA_AUDIO_FORMAT_S18_BE:
+ case SPA_AUDIO_FORMAT_U18_LE:
+ case SPA_AUDIO_FORMAT_U18_BE:
+ return 3 * ss->channels;
+ case SPA_AUDIO_FORMAT_F32_LE:
+ case SPA_AUDIO_FORMAT_F32_BE:
+ case SPA_AUDIO_FORMAT_F32P:
+ case SPA_AUDIO_FORMAT_S32_LE:
+ case SPA_AUDIO_FORMAT_S32_BE:
+ case SPA_AUDIO_FORMAT_S32P:
+ case SPA_AUDIO_FORMAT_U32_LE:
+ case SPA_AUDIO_FORMAT_U32_BE:
+ case SPA_AUDIO_FORMAT_S24_32_LE:
+ case SPA_AUDIO_FORMAT_S24_32_BE:
+ case SPA_AUDIO_FORMAT_S24_32P:
+ case SPA_AUDIO_FORMAT_U24_32_LE:
+ case SPA_AUDIO_FORMAT_U24_32_BE:
+ return 4 * ss->channels;
+ case SPA_AUDIO_FORMAT_F64_LE:
+ case SPA_AUDIO_FORMAT_F64_BE:
+ case SPA_AUDIO_FORMAT_F64P:
+ return 8 * ss->channels;
+ default:
+ return 0;
+ }
+}
+
+bool sample_spec_valid(const struct sample_spec *ss)
+{
+ return (sample_spec_frame_size(ss) > 0 &&
+ ss->rate > 0 && ss->rate <= RATE_MAX &&
+ ss->channels > 0 && ss->channels <= CHANNELS_MAX);
+}
+
+uint32_t channel_pa2id(enum channel_position channel)
+{
+ if (channel < 0 || (size_t)channel >= SPA_N_ELEMENTS(audio_channels))
+ return SPA_AUDIO_CHANNEL_UNKNOWN;
+
+ return audio_channels[channel].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";
+}
+
+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;
+}
+
+enum channel_position channel_id2pa(uint32_t id, uint32_t *aux)
+{
+ size_t i;
+ for (i = 0; i < SPA_N_ELEMENTS(audio_channels); i++) {
+ if (id == audio_channels[i].channel)
+ return i;
+ }
+ return CHANNEL_POSITION_AUX0 + ((*aux)++ & 31);
+}
+
+const char *channel_id2paname(uint32_t id, uint32_t *aux)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(audio_channels, i) {
+ if (id == i->channel && i->name != NULL)
+ return i->name;
+ }
+ return audio_channels[CHANNEL_POSITION_AUX0 + ((*aux)++ & 31)].name;
+}
+
+uint32_t channel_paname2id(const char *name, size_t size)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(audio_channels, i) {
+ if (strncmp(name, i->name, size) == 0)
+ return i->channel;
+ }
+ return SPA_AUDIO_CHANNEL_UNKNOWN;
+}
+
+
+void channel_map_to_positions(const struct channel_map *map, uint32_t *pos)
+{
+ int i;
+ for (i = 0; i < map->channels; i++)
+ pos[i] = map->map[i];
+}
+
+void channel_map_parse(const char *str, struct channel_map *map)
+{
+ const char *p = str;
+ size_t len;
+
+ if (spa_streq(p, "stereo")) {
+ *map = (struct channel_map) {
+ .channels = 2,
+ .map[0] = SPA_AUDIO_CHANNEL_FL,
+ .map[1] = SPA_AUDIO_CHANNEL_FR,
+ };
+ } else if (spa_streq(p, "surround-21")) {
+ *map = (struct channel_map) {
+ .channels = 3,
+ .map[0] = SPA_AUDIO_CHANNEL_FL,
+ .map[1] = SPA_AUDIO_CHANNEL_FR,
+ .map[2] = SPA_AUDIO_CHANNEL_LFE,
+ };
+ } else if (spa_streq(p, "surround-40")) {
+ *map = (struct channel_map) {
+ .channels = 4,
+ .map[0] = SPA_AUDIO_CHANNEL_FL,
+ .map[1] = SPA_AUDIO_CHANNEL_FR,
+ .map[2] = SPA_AUDIO_CHANNEL_RL,
+ .map[3] = SPA_AUDIO_CHANNEL_RR,
+ };
+ } else if (spa_streq(p, "surround-41")) {
+ *map = (struct channel_map) {
+ .channels = 5,
+ .map[0] = SPA_AUDIO_CHANNEL_FL,
+ .map[1] = SPA_AUDIO_CHANNEL_FR,
+ .map[2] = SPA_AUDIO_CHANNEL_RL,
+ .map[3] = SPA_AUDIO_CHANNEL_RR,
+ .map[4] = SPA_AUDIO_CHANNEL_LFE,
+ };
+ } else if (spa_streq(p, "surround-50")) {
+ *map = (struct channel_map) {
+ .channels = 5,
+ .map[0] = SPA_AUDIO_CHANNEL_FL,
+ .map[1] = SPA_AUDIO_CHANNEL_FR,
+ .map[2] = SPA_AUDIO_CHANNEL_RL,
+ .map[3] = SPA_AUDIO_CHANNEL_RR,
+ .map[4] = SPA_AUDIO_CHANNEL_FC,
+ };
+ } else if (spa_streq(p, "surround-51")) {
+ *map = (struct channel_map) {
+ .channels = 6,
+ .map[0] = SPA_AUDIO_CHANNEL_FL,
+ .map[1] = SPA_AUDIO_CHANNEL_FR,
+ .map[2] = SPA_AUDIO_CHANNEL_RL,
+ .map[3] = SPA_AUDIO_CHANNEL_RR,
+ .map[4] = SPA_AUDIO_CHANNEL_FC,
+ .map[5] = SPA_AUDIO_CHANNEL_LFE,
+ };
+ } else if (spa_streq(p, "surround-71")) {
+ *map = (struct channel_map) {
+ .channels = 8,
+ .map[0] = SPA_AUDIO_CHANNEL_FL,
+ .map[1] = SPA_AUDIO_CHANNEL_FR,
+ .map[2] = SPA_AUDIO_CHANNEL_RL,
+ .map[3] = SPA_AUDIO_CHANNEL_RR,
+ .map[4] = SPA_AUDIO_CHANNEL_FC,
+ .map[5] = SPA_AUDIO_CHANNEL_LFE,
+ .map[6] = SPA_AUDIO_CHANNEL_SL,
+ .map[7] = SPA_AUDIO_CHANNEL_SR,
+ };
+ } else {
+ map->channels = 0;
+ while (*p && map->channels < SPA_AUDIO_MAX_CHANNELS) {
+ if ((len = strcspn(p, ",")) == 0)
+ break;
+ map->map[map->channels++] = channel_paname2id(p, len);
+ p += len + strspn(p+len, ",");
+ }
+ }
+}
+
+bool channel_map_valid(const struct channel_map *map)
+{
+ uint8_t i;
+ uint32_t aux = 0;
+ if (map->channels == 0 || map->channels > CHANNELS_MAX)
+ return false;
+ for (i = 0; i < map->channels; i++)
+ if (channel_id2pa(map->map[i], &aux) >= CHANNEL_POSITION_MAX)
+ return false;
+ return true;
+}
+
+struct encoding_info {
+ const char *name;
+ uint32_t id;
+};
+
+static const struct encoding_info encoding_names[] = {
+ [ENCODING_ANY] = { "ANY", 0 },
+ [ENCODING_PCM] = { "PCM", SPA_AUDIO_IEC958_CODEC_PCM },
+ [ENCODING_AC3_IEC61937] = { "AC3-IEC61937", SPA_AUDIO_IEC958_CODEC_AC3 },
+ [ENCODING_EAC3_IEC61937] = { "EAC3-IEC61937", SPA_AUDIO_IEC958_CODEC_EAC3 },
+ [ENCODING_MPEG_IEC61937] = { "MPEG-IEC61937", SPA_AUDIO_IEC958_CODEC_MPEG },
+ [ENCODING_DTS_IEC61937] = { "DTS-IEC61937", SPA_AUDIO_IEC958_CODEC_DTS },
+ [ENCODING_MPEG2_AAC_IEC61937] = { "MPEG2-AAC-IEC61937", SPA_AUDIO_IEC958_CODEC_MPEG2_AAC },
+ [ENCODING_TRUEHD_IEC61937] = { "TRUEHD-IEC61937", SPA_AUDIO_IEC958_CODEC_TRUEHD },
+ [ENCODING_DTSHD_IEC61937] = { "DTSHD-IEC61937", SPA_AUDIO_IEC958_CODEC_DTSHD },
+};
+
+const char *format_encoding2name(enum encoding enc)
+{
+ if (enc >= 0 && enc < (int)SPA_N_ELEMENTS(encoding_names) &&
+ encoding_names[enc].name != NULL)
+ return encoding_names[enc].name;
+ return "INVALID";
+}
+uint32_t format_encoding2id(enum encoding enc)
+{
+ if (enc >= 0 && enc < (int)SPA_N_ELEMENTS(encoding_names) &&
+ encoding_names[enc].name != NULL)
+ return encoding_names[enc].id;
+ return SPA_ID_INVALID;
+}
+
+static enum encoding format_encoding_from_id(uint32_t id)
+{
+ int i;
+ for (i = 0; i < (int)SPA_N_ELEMENTS(encoding_names); i++) {
+ if (encoding_names[i].id == id)
+ return i;
+ }
+ return ENCODING_ANY;
+}
+
+int format_parse_param(const struct spa_pod *param, bool collect,
+ struct sample_spec *ss, struct channel_map *map,
+ const struct sample_spec *def_ss, const struct channel_map *def_map)
+{
+ struct spa_audio_info info = { 0 };
+ uint32_t i;
+
+ if (spa_format_parse(param, &info.media_type, &info.media_subtype) < 0)
+ return -ENOTSUP;
+
+ if (info.media_type != SPA_MEDIA_TYPE_audio)
+ return -ENOTSUP;
+
+ switch (info.media_subtype) {
+ case SPA_MEDIA_SUBTYPE_raw:
+ if (spa_format_audio_raw_parse(param, &info.info.raw) < 0)
+ return -ENOTSUP;
+ if (def_ss != NULL) {
+ if (ss != NULL)
+ *ss = *def_ss;
+ } else {
+ if (info.info.raw.format == 0 ||
+ info.info.raw.rate == 0 ||
+ info.info.raw.channels == 0 ||
+ info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS)
+ return -ENOTSUP;
+ }
+ break;
+ case SPA_MEDIA_SUBTYPE_iec958:
+ {
+ struct spa_audio_info_iec958 iec;
+
+ if (collect)
+ break;
+
+ if (spa_format_audio_iec958_parse(param, &iec) < 0)
+ return -ENOTSUP;
+
+ info.info.raw.format = SPA_AUDIO_FORMAT_S16;
+ info.info.raw.rate = iec.rate;
+ info.info.raw.position[0] = SPA_AUDIO_CHANNEL_FL;
+ info.info.raw.position[1] = SPA_AUDIO_CHANNEL_FR;
+ switch (iec.codec) {
+ case SPA_AUDIO_IEC958_CODEC_TRUEHD:
+ case SPA_AUDIO_IEC958_CODEC_DTSHD:
+ info.info.raw.channels = 8;
+ info.info.raw.position[2] = SPA_AUDIO_CHANNEL_FC;
+ info.info.raw.position[3] = SPA_AUDIO_CHANNEL_LFE;
+ info.info.raw.position[4] = SPA_AUDIO_CHANNEL_SL;
+ info.info.raw.position[5] = SPA_AUDIO_CHANNEL_SR;
+ info.info.raw.position[6] = SPA_AUDIO_CHANNEL_RL;
+ info.info.raw.position[7] = SPA_AUDIO_CHANNEL_RR;
+ break;
+ default:
+ info.info.raw.channels = 2;
+ break;
+ }
+ break;
+ }
+ default:
+ return -ENOTSUP;
+ }
+ if (ss) {
+ if (info.info.raw.format)
+ ss->format = info.info.raw.format;
+ if (info.info.raw.rate)
+ ss->rate = info.info.raw.rate;
+ if (info.info.raw.channels)
+ ss->channels = info.info.raw.channels;
+ }
+ if (map) {
+ if (info.info.raw.channels) {
+ map->channels = info.info.raw.channels;
+ for (i = 0; i < map->channels; i++)
+ map->map[i] = info.info.raw.position[i];
+ }
+ }
+ return 0;
+}
+
+const struct spa_pod *format_build_param(struct spa_pod_builder *b, uint32_t id,
+ const struct sample_spec *spec, const struct channel_map *map)
+{
+ struct spa_pod_frame f;
+
+ spa_pod_builder_push_object(b, &f, 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),
+ 0);
+ if (spec->format != SPA_AUDIO_FORMAT_UNKNOWN)
+ spa_pod_builder_add(b,
+ SPA_FORMAT_AUDIO_format, SPA_POD_Id(spec->format), 0);
+ else {
+ spa_pod_builder_add(b,
+ SPA_FORMAT_AUDIO_format, SPA_POD_CHOICE_ENUM_Id(14,
+ SPA_AUDIO_FORMAT_F32,
+ SPA_AUDIO_FORMAT_F32,
+ SPA_AUDIO_FORMAT_F32_OE,
+ SPA_AUDIO_FORMAT_S32,
+ SPA_AUDIO_FORMAT_S32_OE,
+ SPA_AUDIO_FORMAT_S24_32,
+ SPA_AUDIO_FORMAT_S24_32_OE,
+ SPA_AUDIO_FORMAT_S24,
+ SPA_AUDIO_FORMAT_S24_OE,
+ SPA_AUDIO_FORMAT_S16,
+ SPA_AUDIO_FORMAT_S16_OE,
+ SPA_AUDIO_FORMAT_ULAW,
+ SPA_AUDIO_FORMAT_ALAW,
+ SPA_AUDIO_FORMAT_U8),
+ 0);
+
+ }
+
+ if (spec->rate != 0)
+ spa_pod_builder_add(b,
+ SPA_FORMAT_AUDIO_rate, SPA_POD_Int(spec->rate), 0);
+ if (spec->channels != 0) {
+ spa_pod_builder_add(b,
+ SPA_FORMAT_AUDIO_channels, SPA_POD_Int(spec->channels), 0);
+
+ if (map && map->channels == spec->channels) {
+ uint32_t positions[SPA_AUDIO_MAX_CHANNELS];
+ channel_map_to_positions(map, positions);
+ spa_pod_builder_add(b, SPA_FORMAT_AUDIO_position,
+ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id,
+ spec->channels, positions), 0);
+ }
+ }
+ return spa_pod_builder_pop(b, &f);
+}
+
+int format_info_from_spec(struct format_info *info, const struct sample_spec *ss,
+ const struct channel_map *map)
+{
+ spa_zero(*info);
+ info->encoding = ENCODING_PCM;
+ if ((info->props = pw_properties_new(NULL, NULL)) == NULL)
+ return -errno;
+
+ pw_properties_setf(info->props, "format.sample_format", "\"%s\"",
+ format_id2paname(ss->format));
+ pw_properties_setf(info->props, "format.rate", "%d", ss->rate);
+ pw_properties_setf(info->props, "format.channels", "%d", ss->channels);
+ if (map && map->channels == ss->channels) {
+ char chmap[1024] = "";
+ int i, o, r;
+ uint32_t aux = 0;
+
+ for (i = 0, o = 0; i < map->channels; i++) {
+ r = snprintf(chmap+o, sizeof(chmap)-o, "%s%s", i == 0 ? "" : ",",
+ channel_id2paname(map->map[i], &aux));
+ if (r < 0 || o + r >= (int)sizeof(chmap))
+ return -ENOSPC;
+ o += r;
+ }
+ pw_properties_setf(info->props, "format.channel_map", "\"%s\"", chmap);
+ }
+ return 0;
+}
+
+static int add_int(struct format_info *info, const char *k, struct spa_pod *param,
+ uint32_t key)
+{
+ const struct spa_pod_prop *prop;
+ struct spa_pod *val;
+ uint32_t i, n_values, choice;
+ int32_t *values;
+
+ prop = spa_pod_find_prop(param, NULL, key);
+ if (prop == NULL)
+ return -ENOENT;
+
+ val = spa_pod_get_values(&prop->value, &n_values, &choice);
+ if (val->type != SPA_TYPE_Int)
+ return -ENOTSUP;
+
+ values = SPA_POD_BODY(val);
+
+ switch (choice) {
+ case SPA_CHOICE_None:
+ pw_properties_setf(info->props, k, "%d", values[0]);
+ break;
+ case SPA_CHOICE_Range:
+ pw_properties_setf(info->props, k, "{ \"min\": %d, \"max\": %d }",
+ values[1], values[2]);
+ break;
+ case SPA_CHOICE_Enum:
+ {
+ char *ptr;
+ size_t size;
+ FILE *f;
+
+ if ((f = open_memstream(&ptr, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "[");
+ for (i = 1; i < n_values; i++)
+ fprintf(f, "%s %d", i == 1 ? "" : ",", values[i]);
+ fprintf(f, " ]");
+ fclose(f);
+
+ pw_properties_set(info->props, k, ptr);
+ free(ptr);
+ break;
+ }
+ default:
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+static int format_info_pcm_from_param(struct format_info *info, struct spa_pod *param, uint32_t index)
+{
+ if (index > 0)
+ return -ENOENT;
+
+ info->encoding = ENCODING_PCM;
+ /* don't add params here yet, pulseaudio doesn't do that either */
+ return 0;
+}
+
+static int format_info_iec958_from_param(struct format_info *info, struct spa_pod *param, uint32_t index)
+{
+ const struct spa_pod_prop *prop;
+ struct spa_pod *val;
+ uint32_t n_values, *values, choice;
+
+ prop = spa_pod_find_prop(param, NULL, SPA_FORMAT_AUDIO_iec958Codec);
+ if (prop == NULL)
+ return -ENOENT;
+
+ val = spa_pod_get_values(&prop->value, &n_values, &choice);
+ if (val->type != SPA_TYPE_Id)
+ return -ENOTSUP;
+
+ if (index >= n_values)
+ return -ENOENT;
+
+ values = SPA_POD_BODY(val);
+
+ switch (choice) {
+ case SPA_CHOICE_None:
+ info->encoding = format_encoding_from_id(values[index]);
+ break;
+ case SPA_CHOICE_Enum:
+ info->encoding = format_encoding_from_id(values[index+1]);
+ break;
+ default:
+ return -ENOTSUP;
+ }
+
+ if ((info->props = pw_properties_new(NULL, NULL)) == NULL)
+ return -errno;
+
+ add_int(info, "format.rate", param, SPA_FORMAT_AUDIO_rate);
+
+ return 0;
+}
+
+int format_info_from_param(struct format_info *info, struct spa_pod *param, uint32_t index)
+{
+ uint32_t media_type, media_subtype;
+ int res;
+
+ if (spa_format_parse(param, &media_type, &media_subtype) < 0)
+ return -ENOTSUP;
+
+ if (media_type != SPA_MEDIA_TYPE_audio)
+ return -ENOTSUP;
+
+ switch(media_subtype) {
+ case SPA_MEDIA_SUBTYPE_raw:
+ res = format_info_pcm_from_param(info, param, index);
+ break;
+ case SPA_MEDIA_SUBTYPE_iec958:
+ res = format_info_iec958_from_param(info, param, index);
+ break;
+ default:
+ return -ENOTSUP;
+ }
+ return res;
+}
+
+static uint32_t format_info_get_format(const struct format_info *info)
+{
+ const char *str, *val;
+ struct spa_json it[2];
+ int len;
+
+ 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)
+ return SPA_AUDIO_FORMAT_UNKNOWN;
+
+ if (spa_json_is_string(val, len))
+ return format_paname2id(val+1, len-2);
+
+ return SPA_AUDIO_FORMAT_UNKNOWN;
+}
+
+static int format_info_get_rate(const struct format_info *info)
+{
+ const char *str, *val;
+ struct spa_json it[2];
+ int len, v;
+
+ 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)
+ return -EINVAL;
+ if (spa_json_is_int(val, len)) {
+ if (spa_json_parse_int(val, len, &v) <= 0)
+ return -EINVAL;
+ return v;
+ }
+ return -ENOTSUP;
+}
+
+int format_info_to_spec(const struct format_info *info, struct sample_spec *ss,
+ struct channel_map *map)
+{
+ const char *str, *val;
+ struct spa_json it[2];
+ float f;
+ int res, len;
+
+ spa_zero(*ss);
+ spa_zero(*map);
+
+ if (info->encoding != ENCODING_PCM)
+ return -ENOTSUP;
+ if (info->props == NULL)
+ return -ENOENT;
+
+ if ((ss->format = format_info_get_format(info)) == SPA_AUDIO_FORMAT_UNKNOWN)
+ return -ENOTSUP;
+
+ if ((res = format_info_get_rate(info)) < 0)
+ return res;
+ ss->rate = res;
+
+ 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)
+ return -EINVAL;
+ if (spa_json_is_float(val, len)) {
+ if (spa_json_parse_float(val, len, &f) <= 0)
+ return -EINVAL;
+ ss->channels = f;
+ } else if (spa_json_is_array(val, len)) {
+ return -ENOTSUP;
+ } else if (spa_json_is_object(val, len)) {
+ return -ENOTSUP;
+ } else
+ 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)
+ return -EINVAL;
+ if (!spa_json_is_string(val, len))
+ return -EINVAL;
+ while ((*str == '\"' || *str == ',') &&
+ (len = strcspn(++str, "\",")) > 0) {
+ map->map[map->channels++] = channel_paname2id(str, len);
+ str += len;
+ }
+ }
+ return 0;
+}
+
+const struct spa_pod *format_info_build_param(struct spa_pod_builder *b, uint32_t id,
+ const struct format_info *info, uint32_t *rate)
+{
+ struct sample_spec ss;
+ struct channel_map map;
+ const struct spa_pod *param = NULL;
+ int res;
+
+ switch (info->encoding) {
+ case ENCODING_PCM:
+ if ((res = format_info_to_spec(info, &ss, &map)) < 0) {
+ errno = -res;
+ return NULL;
+ }
+ *rate = ss.rate;
+ param = format_build_param(b, id, &ss, &map);
+ break;
+ case ENCODING_AC3_IEC61937:
+ case ENCODING_EAC3_IEC61937:
+ case ENCODING_MPEG_IEC61937:
+ case ENCODING_DTS_IEC61937:
+ case ENCODING_MPEG2_AAC_IEC61937:
+ case ENCODING_TRUEHD_IEC61937:
+ case ENCODING_DTSHD_IEC61937:
+ {
+ struct spa_audio_info_iec958 i = { 0 };
+ i.codec = format_encoding2id(info->encoding);
+ if ((res = format_info_get_rate(info)) <= 0) {
+ errno = -res;
+ return NULL;
+ }
+ i.rate = res;
+ param = spa_format_audio_iec958_build(b, id, &i);
+ break;
+ }
+ default:
+ errno = ENOTSUP;
+ break;
+ }
+ return param;
+}
diff --git a/src/modules/module-protocol-pulse/format.h b/src/modules/module-protocol-pulse/format.h
new file mode 100644
index 0000000..4300fc0
--- /dev/null
+++ b/src/modules/module-protocol-pulse/format.h
@@ -0,0 +1,233 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSE_SERVER_FORMAT_H
+#define PULSE_SERVER_FORMAT_H
+
+#include <spa/utils/defs.h>
+#include <pipewire/properties.h>
+
+struct spa_pod;
+struct spa_pod_builder;
+
+#define RATE_MAX (48000u*8u)
+#define CHANNELS_MAX (64u)
+
+enum sample_format {
+ SAMPLE_U8,
+ SAMPLE_ALAW,
+ SAMPLE_ULAW,
+ SAMPLE_S16LE,
+ SAMPLE_S16BE,
+ SAMPLE_FLOAT32LE,
+ SAMPLE_FLOAT32BE,
+ SAMPLE_S32LE,
+ SAMPLE_S32BE,
+ SAMPLE_S24LE,
+ SAMPLE_S24BE,
+ SAMPLE_S24_32LE,
+ SAMPLE_S24_32BE,
+ SAMPLE_MAX,
+ SAMPLE_INVALID = -1
+};
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define SAMPLE_S16NE SAMPLE_S16BE
+#define SAMPLE_FLOAT32NE SAMPLE_FLOAT32BE
+#define SAMPLE_S32NE SAMPLE_S32BE
+#define SAMPLE_S24NE SAMPLE_S24BE
+#define SAMPLE_S24_32NE SAMPLE_S24_32BE
+#elif __BYTE_ORDER == __LITTLE_ENDIAN
+#define SAMPLE_S16NE SAMPLE_S16LE
+#define SAMPLE_FLOAT32NE SAMPLE_FLOAT32LE
+#define SAMPLE_S32NE SAMPLE_S32LE
+#define SAMPLE_S24NE SAMPLE_S24LE
+#define SAMPLE_S24_32NE SAMPLE_S24_32LE
+#endif
+
+struct format {
+ uint32_t pa;
+ uint32_t id;
+ const char *name;
+ uint32_t size;
+};
+
+struct sample_spec {
+ uint32_t format;
+ uint32_t rate;
+ uint8_t channels;
+};
+#define SAMPLE_SPEC_INIT \
+ (struct sample_spec) { \
+ .format = SPA_AUDIO_FORMAT_UNKNOWN, \
+ .rate = 0, \
+ .channels = 0, \
+ }
+
+enum channel_position {
+ CHANNEL_POSITION_INVALID = -1,
+ CHANNEL_POSITION_MONO = 0,
+ CHANNEL_POSITION_FRONT_LEFT,
+ CHANNEL_POSITION_FRONT_RIGHT,
+ CHANNEL_POSITION_FRONT_CENTER,
+
+ CHANNEL_POSITION_REAR_CENTER,
+ CHANNEL_POSITION_REAR_LEFT,
+ CHANNEL_POSITION_REAR_RIGHT,
+
+ CHANNEL_POSITION_LFE,
+ CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+ CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
+
+ CHANNEL_POSITION_SIDE_LEFT,
+ CHANNEL_POSITION_SIDE_RIGHT,
+ CHANNEL_POSITION_AUX0,
+ CHANNEL_POSITION_AUX1,
+ CHANNEL_POSITION_AUX2,
+ CHANNEL_POSITION_AUX3,
+ CHANNEL_POSITION_AUX4,
+ CHANNEL_POSITION_AUX5,
+ CHANNEL_POSITION_AUX6,
+ CHANNEL_POSITION_AUX7,
+ CHANNEL_POSITION_AUX8,
+ CHANNEL_POSITION_AUX9,
+ CHANNEL_POSITION_AUX10,
+ CHANNEL_POSITION_AUX11,
+ CHANNEL_POSITION_AUX12,
+ CHANNEL_POSITION_AUX13,
+ CHANNEL_POSITION_AUX14,
+ CHANNEL_POSITION_AUX15,
+ CHANNEL_POSITION_AUX16,
+ CHANNEL_POSITION_AUX17,
+ CHANNEL_POSITION_AUX18,
+ CHANNEL_POSITION_AUX19,
+ CHANNEL_POSITION_AUX20,
+ CHANNEL_POSITION_AUX21,
+ CHANNEL_POSITION_AUX22,
+ CHANNEL_POSITION_AUX23,
+ CHANNEL_POSITION_AUX24,
+ CHANNEL_POSITION_AUX25,
+ CHANNEL_POSITION_AUX26,
+ CHANNEL_POSITION_AUX27,
+ CHANNEL_POSITION_AUX28,
+ CHANNEL_POSITION_AUX29,
+ CHANNEL_POSITION_AUX30,
+ CHANNEL_POSITION_AUX31,
+
+ CHANNEL_POSITION_TOP_CENTER,
+
+ CHANNEL_POSITION_TOP_FRONT_LEFT,
+ CHANNEL_POSITION_TOP_FRONT_RIGHT,
+ CHANNEL_POSITION_TOP_FRONT_CENTER,
+
+ CHANNEL_POSITION_TOP_REAR_LEFT,
+ CHANNEL_POSITION_TOP_REAR_RIGHT,
+ CHANNEL_POSITION_TOP_REAR_CENTER,
+
+ CHANNEL_POSITION_MAX
+};
+
+struct channel {
+ uint32_t channel;
+ const char *name;
+};
+
+struct channel_map {
+ uint8_t channels;
+ uint32_t map[CHANNELS_MAX];
+};
+
+#define CHANNEL_MAP_INIT \
+ (struct channel_map) { \
+ .channels = 0, \
+ }
+
+enum encoding {
+ ENCODING_ANY,
+ ENCODING_PCM,
+ ENCODING_AC3_IEC61937,
+ ENCODING_EAC3_IEC61937,
+ ENCODING_MPEG_IEC61937,
+ ENCODING_DTS_IEC61937,
+ ENCODING_MPEG2_AAC_IEC61937,
+ ENCODING_TRUEHD_IEC61937,
+ ENCODING_DTSHD_IEC61937,
+ ENCODING_MAX,
+ ENCODING_INVALID = -1,
+};
+
+struct format_info {
+ enum encoding encoding;
+ struct pw_properties *props;
+};
+
+uint32_t format_pa2id(enum sample_format format);
+const char *format_id2name(uint32_t format);
+uint32_t format_name2id(const char *name);
+uint32_t format_paname2id(const char *name, size_t size);
+enum sample_format format_id2pa(uint32_t id);
+const char *format_id2paname(uint32_t id);
+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);
+bool sample_spec_valid(const struct sample_spec *ss);
+
+uint32_t channel_pa2id(enum channel_position channel);
+const char *channel_id2name(uint32_t channel);
+uint32_t channel_name2id(const char *name);
+enum channel_position channel_id2pa(uint32_t id, uint32_t *aux);
+const char *channel_id2paname(uint32_t id, uint32_t *aux);
+uint32_t channel_paname2id(const char *name, size_t size);
+
+void channel_map_to_positions(const struct channel_map *map, uint32_t *pos);
+void channel_map_parse(const char *str, struct channel_map *map);
+bool channel_map_valid(const struct channel_map *map);
+
+int format_parse_param(const struct spa_pod *param, bool collect, struct sample_spec *ss,
+ struct channel_map *map, const struct sample_spec *def_ss,
+ const struct channel_map *def_map);
+
+const struct spa_pod *format_build_param(struct spa_pod_builder *b, uint32_t id,
+ const struct sample_spec *spec, const struct channel_map *map);
+
+int format_info_from_spec(struct format_info *info, const struct sample_spec *ss,
+ const struct channel_map *map);
+int format_info_from_param(struct format_info *info, struct spa_pod *param, uint32_t index);
+
+const struct spa_pod *format_info_build_param(struct spa_pod_builder *b, uint32_t id,
+ const struct format_info *info, uint32_t *rate);
+
+int format_info_from_spec(struct format_info *info, const struct sample_spec *ss,
+ const struct channel_map *map);
+int format_info_to_spec(const struct format_info *info, struct sample_spec *ss,
+ struct channel_map *map);
+
+static inline void format_info_clear(struct format_info *info)
+{
+ pw_properties_free(info->props);
+ spa_zero(*info);
+}
+
+#endif
diff --git a/src/modules/module-protocol-pulse/internal.h b/src/modules/module-protocol-pulse/internal.h
new file mode 100644
index 0000000..1d5731c
--- /dev/null
+++ b/src/modules/module-protocol-pulse/internal.h
@@ -0,0 +1,108 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSE_SERVER_INTERNAL_H
+#define PULSE_SERVER_INTERNAL_H
+
+#include "config.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/ringbuffer.h>
+#include <pipewire/map.h>
+#include <pipewire/private.h>
+
+#include "format.h"
+#include "server.h"
+
+struct pw_loop;
+struct pw_context;
+struct pw_work_queue;
+struct pw_properties;
+
+struct defs {
+ struct spa_fraction min_req;
+ struct spa_fraction default_req;
+ struct spa_fraction min_frag;
+ struct spa_fraction default_frag;
+ struct spa_fraction default_tlength;
+ struct spa_fraction min_quantum;
+ struct sample_spec sample_spec;
+ struct channel_map channel_map;
+ uint32_t quantum_limit;
+ uint32_t idle_timeout;
+};
+
+struct stats {
+ uint32_t n_allocated;
+ uint32_t allocated;
+ uint32_t n_accumulated;
+ uint32_t accumulated;
+ uint32_t sample_cache;
+};
+
+struct impl {
+ struct pw_loop *loop;
+ struct pw_context *context;
+ struct spa_hook context_listener;
+
+ struct pw_properties *props;
+ void *dbus_name;
+
+ struct ratelimit rate_limit;
+
+ struct spa_hook_list hooks;
+ struct spa_list servers;
+
+ struct pw_work_queue *work_queue;
+ struct spa_list cleanup_clients;
+
+ struct pw_map samples;
+ struct pw_map modules;
+
+ struct spa_list free_messages;
+ struct defs defs;
+ struct stats stat;
+};
+
+struct impl_events {
+#define VERSION_IMPL_EVENTS 0
+ uint32_t version;
+
+ void (*server_started) (void *data, struct server *server);
+
+ void (*server_stopped) (void *data, struct server *server);
+};
+
+void impl_add_listener(struct impl *impl,
+ struct spa_hook *listener,
+ const struct impl_events *events, void *data);
+
+extern bool debug_messages;
+
+void broadcast_subscribe_event(struct impl *impl, uint32_t mask, uint32_t event, uint32_t id);
+
+#endif
diff --git a/src/modules/module-protocol-pulse/log.h b/src/modules/module-protocol-pulse/log.h
new file mode 100644
index 0000000..e1f5ca7
--- /dev/null
+++ b/src/modules/module-protocol-pulse/log.h
@@ -0,0 +1,34 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Red Hat, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+
+#ifndef PULSE_LOG_H
+#define PULSE_LOG_H
+
+#include <pipewire/log.h>
+
+PW_LOG_TOPIC_EXTERN(mod_topic);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#endif /* PULSE_LOG_H */
diff --git a/src/modules/module-protocol-pulse/manager.c b/src/modules/module-protocol-pulse/manager.c
new file mode 100644
index 0000000..7aa8d70
--- /dev/null
+++ b/src/modules/module-protocol-pulse/manager.c
@@ -0,0 +1,1028 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "manager.h"
+
+#include <spa/pod/iter.h>
+#include <spa/pod/parser.h>
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <pipewire/extensions/metadata.h>
+
+#include "log.h"
+#include "module-protocol-pulse/server.h"
+
+#define manager_emit_sync(m) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, sync, 0)
+#define manager_emit_added(m,o) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, added, 0, o)
+#define manager_emit_updated(m,o) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, updated, 0, o)
+#define manager_emit_removed(m,o) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, removed, 0, o)
+#define manager_emit_metadata(m,o,s,k,t,v) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, metadata,0,o,s,k,t,v)
+#define manager_emit_disconnect(m) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, disconnect, 0)
+#define manager_emit_object_data_timeout(m,o,k) spa_hook_list_call(&(m)->hooks, struct pw_manager_events, object_data_timeout,0,o,k)
+
+struct object;
+
+struct manager {
+ struct pw_manager this;
+
+ struct pw_loop *loop;
+
+ struct spa_hook core_listener;
+ struct spa_hook registry_listener;
+ int sync_seq;
+
+ struct spa_hook_list hooks;
+};
+
+struct object_info {
+ const char *type;
+ uint32_t version;
+ const void *events;
+ void (*init) (struct object *object);
+ void (*destroy) (struct object *object);
+};
+
+struct object_data {
+ struct spa_list link;
+ struct object *object;
+ const char *key;
+ size_t size;
+ struct spa_source *timer;
+};
+
+struct object {
+ struct pw_manager_object this;
+
+ struct manager *manager;
+
+ const struct object_info *info;
+
+ struct spa_list pending_list;
+
+ struct spa_hook proxy_listener;
+ struct spa_hook object_listener;
+
+ struct spa_list data_list;
+};
+
+static int core_sync(struct manager *m)
+{
+ m->sync_seq = pw_core_sync(m->this.core, PW_ID_CORE, m->sync_seq);
+ pw_log_debug("sync start %u", m->sync_seq);
+ return m->sync_seq;
+}
+
+static uint32_t clear_params(struct spa_list *param_list, uint32_t id)
+{
+ struct pw_manager_param *p, *t;
+ uint32_t count = 0;
+
+ spa_list_for_each_safe(p, t, param_list, link) {
+ if (id == SPA_ID_INVALID || p->id == id) {
+ spa_list_remove(&p->link);
+ free(p);
+ count++;
+ }
+ }
+ return count;
+}
+
+static struct pw_manager_param *add_param(struct spa_list *params,
+ int seq, uint32_t id, const struct spa_pod *param)
+{
+ struct pw_manager_param *p;
+
+ if (id == SPA_ID_INVALID) {
+ if (param == NULL || !spa_pod_is_object(param)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ id = SPA_POD_OBJECT_ID(param);
+ }
+
+ p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0));
+ if (p == NULL)
+ return NULL;
+
+ p->id = id;
+ p->seq = seq;
+ if (param != NULL) {
+ p->param = SPA_PTROFF(p, sizeof(*p), struct spa_pod);
+ memcpy(p->param, param, SPA_POD_SIZE(param));
+ } else {
+ clear_params(params, id);
+ p->param = NULL;
+ }
+ spa_list_append(params, &p->link);
+
+ return p;
+}
+
+static bool has_param(struct spa_list *param_list, struct pw_manager_param *p)
+{
+ struct pw_manager_param *t;
+ spa_list_for_each(t, param_list, link) {
+ if (p->id == t->id &&
+ SPA_POD_SIZE(p->param) == SPA_POD_SIZE(t->param) &&
+ memcmp(p->param, t->param, SPA_POD_SIZE(p->param)) == 0)
+ return true;
+ }
+ return false;
+}
+
+static struct object *find_object_by_id(struct manager *m, uint32_t id)
+{
+ struct object *o;
+ spa_list_for_each(o, &m->this.object_list, this.link) {
+ if (o->this.id == id)
+ return o;
+ }
+ return NULL;
+}
+
+static void object_update_params(struct object *o)
+{
+ struct pw_manager_param *p, *t;
+ uint32_t i;
+
+ for (i = 0; i < o->this.n_params; i++) {
+ spa_list_for_each_safe(p, t, &o->pending_list, link) {
+ if (p->id == o->this.params[i].id &&
+ p->seq != o->this.params[i].seq &&
+ p->param != NULL) {
+ spa_list_remove(&p->link);
+ free(p);
+ }
+ }
+ }
+
+ spa_list_consume(p, &o->pending_list, link) {
+ spa_list_remove(&p->link);
+ if (p->param == NULL) {
+ clear_params(&o->this.param_list, p->id);
+ free(p);
+ } else {
+ spa_list_append(&o->this.param_list, &p->link);
+ }
+ }
+}
+
+static void object_data_free(struct object_data *d)
+{
+ spa_list_remove(&d->link);
+ if (d->timer) {
+ pw_loop_destroy_source(d->object->manager->loop, d->timer);
+ d->timer = NULL;
+ }
+ free(d);
+}
+
+static void object_destroy(struct object *o)
+{
+ struct manager *m = o->manager;
+ struct object_data *d;
+ spa_list_remove(&o->this.link);
+ m->this.n_objects--;
+ if (o->this.proxy)
+ pw_proxy_destroy(o->this.proxy);
+ pw_properties_free(o->this.props);
+ if (o->this.message_object_path)
+ free(o->this.message_object_path);
+ clear_params(&o->this.param_list, SPA_ID_INVALID);
+ clear_params(&o->pending_list, SPA_ID_INVALID);
+ spa_list_consume(d, &o->data_list, link)
+ object_data_free(d);
+ free(o);
+}
+
+/* core */
+static const struct object_info core_info = {
+ .type = PW_TYPE_INTERFACE_Core,
+ .version = PW_VERSION_CORE,
+};
+
+/* client */
+static void client_event_info(void *data, const struct pw_client_info *info)
+{
+ struct object *o = data;
+ int changed = 0;
+
+ pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->this.id, info->change_mask);
+
+ info = o->this.info = pw_client_info_merge(o->this.info, info, o->this.changed == 0);
+ if (info == NULL)
+ return;
+
+ if (info->change_mask & PW_CLIENT_CHANGE_MASK_PROPS)
+ changed++;
+
+ if (changed) {
+ o->this.changed += changed;
+ core_sync(o->manager);
+ }
+}
+
+static const struct pw_client_events client_events = {
+ PW_VERSION_CLIENT_EVENTS,
+ .info = client_event_info,
+};
+
+static void client_destroy(struct object *o)
+{
+ if (o->this.info) {
+ pw_client_info_free(o->this.info);
+ o->this.info = NULL;
+ }
+}
+
+static const struct object_info client_info = {
+ .type = PW_TYPE_INTERFACE_Client,
+ .version = PW_VERSION_CLIENT,
+ .events = &client_events,
+ .destroy = client_destroy,
+};
+
+/* module */
+static void module_event_info(void *data, const struct pw_module_info *info)
+{
+ struct object *o = data;
+ int changed = 0;
+
+ pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->this.id, info->change_mask);
+
+ info = o->this.info = pw_module_info_merge(o->this.info, info, o->this.changed == 0);
+ if (info == NULL)
+ return;
+
+ if (info->change_mask & PW_MODULE_CHANGE_MASK_PROPS)
+ changed++;
+
+ if (changed) {
+ o->this.changed += changed;
+ core_sync(o->manager);
+ }
+}
+
+static const struct pw_module_events module_events = {
+ PW_VERSION_MODULE_EVENTS,
+ .info = module_event_info,
+};
+
+static void module_destroy(struct object *o)
+{
+ if (o->this.info) {
+ pw_module_info_free(o->this.info);
+ o->this.info = NULL;
+ }
+}
+
+static const struct object_info module_info = {
+ .type = PW_TYPE_INTERFACE_Module,
+ .version = PW_VERSION_MODULE,
+ .events = &module_events,
+ .destroy = module_destroy,
+};
+
+/* device */
+static void device_event_info(void *data, const struct pw_device_info *info)
+{
+ struct object *o = data;
+ uint32_t i, changed = 0;
+
+ pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->this.id, info->change_mask);
+
+ info = o->this.info = pw_device_info_merge(o->this.info, info, o->this.changed == 0);
+ if (info == NULL)
+ return;
+
+ o->this.n_params = info->n_params;
+ o->this.params = info->params;
+
+ if (info->change_mask & PW_DEVICE_CHANGE_MASK_PROPS)
+ changed++;
+
+ if (info->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ uint32_t id = info->params[i].id;
+ int res;
+
+ if (info->params[i].user == 0)
+ continue;
+ info->params[i].user = 0;
+
+ switch (id) {
+ case SPA_PARAM_EnumProfile:
+ case SPA_PARAM_Profile:
+ case SPA_PARAM_EnumRoute:
+ changed++;
+ break;
+ case SPA_PARAM_Route:
+ break;
+ }
+ add_param(&o->pending_list, info->params[i].seq, id, NULL);
+ if (!(info->params[i].flags & SPA_PARAM_INFO_READ))
+ continue;
+
+ res = pw_device_enum_params((struct pw_device*)o->this.proxy,
+ ++info->params[i].seq, id, 0, -1, NULL);
+ if (SPA_RESULT_IS_ASYNC(res))
+ info->params[i].seq = res;
+ }
+ }
+ if (changed) {
+ o->this.changed += changed;
+ core_sync(o->manager);
+ }
+}
+static struct object *find_device(struct manager *m, uint32_t card_id, uint32_t device)
+{
+ struct object *o;
+
+ spa_list_for_each(o, &m->this.object_list, this.link) {
+ struct pw_node_info *info;
+ const char *str;
+
+ if (!spa_streq(o->this.type, PW_TYPE_INTERFACE_Node))
+ continue;
+
+ if ((info = o->this.info) != NULL &&
+ (str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)) != NULL &&
+ (uint32_t)atoi(str) == card_id &&
+ (str = spa_dict_lookup(info->props, "card.profile.device")) != NULL &&
+ (uint32_t)atoi(str) == device)
+ return o;
+ }
+ return NULL;
+}
+
+static void device_event_param(void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct object *o = data, *dev;
+ struct manager *m = o->manager;
+ struct pw_manager_param *p;
+
+ p = add_param(&o->pending_list, seq, id, param);
+ if (p == NULL)
+ return;
+
+ if (id == SPA_PARAM_Route && !has_param(&o->this.param_list, p)) {
+ uint32_t idx, device;
+ if (spa_pod_parse_object(param,
+ SPA_TYPE_OBJECT_ParamRoute, NULL,
+ SPA_PARAM_ROUTE_index, SPA_POD_Int(&idx),
+ SPA_PARAM_ROUTE_device, SPA_POD_Int(&device)) < 0)
+ return;
+
+ if ((dev = find_device(m, o->this.id, device)) != NULL) {
+ dev->this.changed++;
+ core_sync(o->manager);
+ }
+ }
+}
+
+static const struct pw_device_events device_events = {
+ PW_VERSION_DEVICE_EVENTS,
+ .info = device_event_info,
+ .param = device_event_param,
+};
+
+static void device_destroy(struct object *o)
+{
+ if (o->this.info) {
+ pw_device_info_free(o->this.info);
+ o->this.info = NULL;
+ }
+}
+
+static const struct object_info device_info = {
+ .type = PW_TYPE_INTERFACE_Device,
+ .version = PW_VERSION_DEVICE,
+ .events = &device_events,
+ .destroy = device_destroy,
+};
+
+/* node */
+static void node_event_info(void *data, const struct pw_node_info *info)
+{
+ struct object *o = data;
+ uint32_t i, changed = 0;
+
+ pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->this.id, info->change_mask);
+
+ info = o->this.info = pw_node_info_merge(o->this.info, info, o->this.changed == 0);
+ if (info == NULL)
+ return;
+
+ o->this.n_params = info->n_params;
+ o->this.params = info->params;
+
+ if (info->change_mask & PW_NODE_CHANGE_MASK_STATE)
+ changed++;
+
+ if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS)
+ changed++;
+
+ if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ uint32_t id = info->params[i].id;
+ int res;
+
+ if (info->params[i].user == 0)
+ continue;
+ info->params[i].user = 0;
+
+ changed++;
+ add_param(&o->pending_list, info->params[i].seq, id, NULL);
+ if (!(info->params[i].flags & SPA_PARAM_INFO_READ))
+ continue;
+
+ res = pw_node_enum_params((struct pw_node*)o->this.proxy,
+ ++info->params[i].seq, id, 0, -1, NULL);
+ if (SPA_RESULT_IS_ASYNC(res))
+ info->params[i].seq = res;
+ }
+ }
+ if (changed) {
+ o->this.changed += changed;
+ core_sync(o->manager);
+ }
+}
+
+static void node_event_param(void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct object *o = data;
+ add_param(&o->pending_list, seq, id, param);
+}
+
+static const struct pw_node_events node_events = {
+ PW_VERSION_NODE_EVENTS,
+ .info = node_event_info,
+ .param = node_event_param,
+};
+
+static void node_destroy(struct object *o)
+{
+ if (o->this.info) {
+ pw_node_info_free(o->this.info);
+ o->this.info = NULL;
+ }
+}
+
+static const struct object_info node_info = {
+ .type = PW_TYPE_INTERFACE_Node,
+ .version = PW_VERSION_NODE,
+ .events = &node_events,
+ .destroy = node_destroy,
+};
+
+/* link */
+static const struct object_info link_info = {
+ .type = PW_TYPE_INTERFACE_Link,
+ .version = PW_VERSION_LINK,
+};
+
+/* metadata */
+static int metadata_property(void *data,
+ uint32_t subject,
+ const char *key,
+ const char *type,
+ const char *value)
+{
+ struct object *o = data;
+ struct manager *m = o->manager;
+ manager_emit_metadata(m, &o->this, subject, key, type, value);
+ return 0;
+}
+
+static const struct pw_metadata_events metadata_events = {
+ PW_VERSION_METADATA_EVENTS,
+ .property = metadata_property,
+};
+
+static void metadata_init(struct object *object)
+{
+ struct object *o = object;
+ struct manager *m = o->manager;
+ o->this.creating = false;
+ manager_emit_added(m, &o->this);
+}
+
+static const struct object_info metadata_info = {
+ .type = PW_TYPE_INTERFACE_Metadata,
+ .version = PW_VERSION_METADATA,
+ .events = &metadata_events,
+ .init = metadata_init,
+};
+
+static const struct object_info *objects[] =
+{
+ &core_info,
+ &module_info,
+ &client_info,
+ &device_info,
+ &node_info,
+ &link_info,
+ &metadata_info,
+};
+
+static const struct object_info *find_info(const char *type, uint32_t version)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(objects, i) {
+ if (spa_streq((*i)->type, type) &&
+ (*i)->version <= version)
+ return *i;
+ }
+ return NULL;
+}
+
+static void
+destroy_removed(void *data)
+{
+ struct object *o = data;
+ pw_proxy_destroy(o->this.proxy);
+}
+
+static void
+destroy_proxy(void *data)
+{
+ struct object *o = data;
+
+ spa_assert(o->info);
+
+ if (o->info->events)
+ spa_hook_remove(&o->object_listener);
+ spa_hook_remove(&o->proxy_listener);
+
+ if (o->info->destroy)
+ o->info->destroy(o);
+
+ o->this.proxy = NULL;
+}
+
+static const struct pw_proxy_events proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .removed = destroy_removed,
+ .destroy = destroy_proxy,
+};
+
+static void registry_event_global(void *data, uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ struct manager *m = data;
+ struct object *o;
+ const struct object_info *info;
+ const char *str;
+ struct pw_proxy *proxy;
+
+ info = find_info(type, version);
+ if (info == NULL)
+ return;
+
+ proxy = pw_registry_bind(m->this.registry,
+ id, type, info->version, 0);
+ if (proxy == NULL)
+ return;
+
+ o = calloc(1, sizeof(*o));
+ if (o == NULL) {
+ pw_log_error("can't alloc object for %u %s/%d: %m", id, type, version);
+ pw_proxy_destroy(proxy);
+ return;
+ }
+ str = props ? spa_dict_lookup(props, PW_KEY_OBJECT_SERIAL) : NULL;
+ if (!spa_atou64(str, &o->this.serial, 0))
+ o->this.serial = SPA_ID_INVALID;
+
+ o->this.id = id;
+ o->this.permissions = permissions;
+ o->this.type = info->type;
+ o->this.version = version;
+ o->this.index = o->this.serial < (1ULL<<32) ? o->this.serial : SPA_ID_INVALID;
+ o->this.props = props ? pw_properties_new_dict(props) : NULL;
+ o->this.proxy = proxy;
+ o->this.creating = true;
+ spa_list_init(&o->this.param_list);
+ spa_list_init(&o->pending_list);
+ spa_list_init(&o->data_list);
+
+ o->manager = m;
+ o->info = info;
+ spa_list_append(&m->this.object_list, &o->this.link);
+ m->this.n_objects++;
+
+ if (info->events)
+ pw_proxy_add_object_listener(proxy,
+ &o->object_listener,
+ o->info->events, o);
+ pw_proxy_add_listener(proxy,
+ &o->proxy_listener,
+ &proxy_events, o);
+
+ if (info->init)
+ info->init(o);
+
+ core_sync(m);
+}
+
+static void registry_event_global_remove(void *data, uint32_t id)
+{
+ struct manager *m = data;
+ struct object *o;
+
+ if ((o = find_object_by_id(m, id)) == NULL)
+ return;
+
+ o->this.removing = true;
+
+ if (!o->this.creating)
+ manager_emit_removed(m, &o->this);
+
+ object_destroy(o);
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+ .global_remove = registry_event_global_remove,
+};
+
+static void on_core_info(void *data, const struct pw_core_info *info)
+{
+ struct manager *m = data;
+ m->this.info = pw_core_info_merge(m->this.info, info, true);
+}
+
+static void on_core_done(void *data, uint32_t id, int seq)
+{
+ struct manager *m = data;
+ struct object *o;
+
+ if (id == PW_ID_CORE) {
+ if (m->sync_seq != seq)
+ return;
+
+ pw_log_debug("sync end %u/%u", m->sync_seq, seq);
+
+ manager_emit_sync(m);
+
+ spa_list_for_each(o, &m->this.object_list, this.link)
+ object_update_params(o);
+
+ spa_list_for_each(o, &m->this.object_list, this.link) {
+ if (o->this.creating) {
+ o->this.creating = false;
+ manager_emit_added(m, &o->this);
+ o->this.changed = 0;
+ } else if (o->this.changed > 0) {
+ manager_emit_updated(m, &o->this);
+ o->this.changed = 0;
+ }
+ }
+ }
+}
+
+static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct manager *m = data;
+
+ if (id == PW_ID_CORE && res == -EPIPE) {
+ pw_log_debug("connection error: %d, %s", res, message);
+ manager_emit_disconnect(m);
+ }
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .done = on_core_done,
+ .info = on_core_info,
+ .error = on_core_error
+};
+
+struct pw_manager *pw_manager_new(struct pw_core *core)
+{
+ struct manager *m;
+ struct pw_context *context;
+
+ m = calloc(1, sizeof(*m));
+ if (m == NULL)
+ return NULL;
+
+ m->this.core = core;
+ m->this.registry = pw_core_get_registry(m->this.core,
+ PW_VERSION_REGISTRY, 0);
+ if (m->this.registry == NULL) {
+ free(m);
+ return NULL;
+ }
+
+ context = pw_core_get_context(core);
+ m->loop = pw_context_get_main_loop(context);
+
+ spa_hook_list_init(&m->hooks);
+
+ spa_list_init(&m->this.object_list);
+
+ pw_core_add_listener(m->this.core,
+ &m->core_listener,
+ &core_events, m);
+ pw_registry_add_listener(m->this.registry,
+ &m->registry_listener,
+ &registry_events, m);
+
+ return &m->this;
+}
+
+void pw_manager_add_listener(struct pw_manager *manager,
+ struct spa_hook *listener,
+ const struct pw_manager_events *events, void *data)
+{
+ struct manager *m = SPA_CONTAINER_OF(manager, struct manager, this);
+ spa_hook_list_append(&m->hooks, listener, events, data);
+ core_sync(m);
+}
+
+int pw_manager_set_metadata(struct pw_manager *manager,
+ struct pw_manager_object *metadata,
+ uint32_t subject, const char *key, const char *type,
+ const char *format, ...)
+{
+ struct manager *m = SPA_CONTAINER_OF(manager, struct manager, this);
+ struct object *s;
+ va_list args;
+ char buf[1024];
+ char *value;
+
+ if ((s = find_object_by_id(m, subject)) == NULL)
+ return -ENOENT;
+ if (!SPA_FLAG_IS_SET(s->this.permissions, PW_PERM_M))
+ return -EACCES;
+
+ if (metadata == NULL)
+ return -ENOTSUP;
+ if (!SPA_FLAG_IS_SET(metadata->permissions, PW_PERM_W|PW_PERM_X))
+ return -EACCES;
+ if (metadata->proxy == NULL)
+ return -ENOENT;
+
+ if (type != NULL) {
+ va_start(args, format);
+ vsnprintf(buf, sizeof(buf), format, args);
+ va_end(args);
+ value = buf;
+ } else {
+ spa_assert(format == NULL);
+ value = NULL;
+ }
+
+ pw_metadata_set_property(metadata->proxy,
+ subject, key, type, value);
+ return 0;
+}
+
+int pw_manager_for_each_object(struct pw_manager *manager,
+ int (*callback) (void *data, struct pw_manager_object *object),
+ void *data)
+{
+ struct manager *m = SPA_CONTAINER_OF(manager, struct manager, this);
+ struct object *o;
+ int res;
+
+ spa_list_for_each(o, &m->this.object_list, this.link) {
+ if (o->this.creating || o->this.removing)
+ continue;
+ if ((res = callback(data, &o->this)) != 0)
+ return res;
+ }
+ return 0;
+}
+
+void pw_manager_destroy(struct pw_manager *manager)
+{
+ struct manager *m = SPA_CONTAINER_OF(manager, struct manager, this);
+ struct object *o;
+
+ spa_hook_list_clean(&m->hooks);
+
+ spa_hook_remove(&m->core_listener);
+
+ spa_list_consume(o, &m->this.object_list, this.link)
+ object_destroy(o);
+
+ spa_hook_remove(&m->registry_listener);
+ pw_proxy_destroy((struct pw_proxy*)m->this.registry);
+
+ if (m->this.info)
+ pw_core_info_free(m->this.info);
+
+ free(m);
+}
+
+static struct object_data *object_find_data(struct object *o, const char *key)
+{
+ struct object_data *d;
+ spa_list_for_each(d, &o->data_list, link) {
+ if (spa_streq(d->key, key))
+ return d;
+ }
+ return NULL;
+}
+
+void *pw_manager_object_add_data(struct pw_manager_object *obj, const char *key, size_t size)
+{
+ struct object *o = SPA_CONTAINER_OF(obj, struct object, this);
+ struct object_data *d;
+
+ d = object_find_data(o, key);
+ if (d != NULL) {
+ if (d->size == size)
+ goto done;
+ object_data_free(d);
+ }
+
+ d = calloc(1, sizeof(struct object_data) + size);
+ if (d == NULL)
+ return NULL;
+
+ d->object = o;
+ d->key = key;
+ d->size = size;
+
+ spa_list_append(&o->data_list, &d->link);
+
+done:
+ return SPA_PTROFF(d, sizeof(struct object_data), void);
+}
+
+static void object_data_timeout(void *data, uint64_t count)
+{
+ struct object_data *d = data;
+ struct object *o = d->object;
+ struct manager *m = o->manager;
+
+ pw_log_debug("manager:%p object id:%d data '%s' lifetime ends",
+ m, o->this.id, d->key);
+
+ if (d->timer) {
+ pw_loop_destroy_source(m->loop, d->timer);
+ d->timer = NULL;
+ }
+
+ manager_emit_object_data_timeout(m, &o->this, d->key);
+}
+
+void *pw_manager_object_add_temporary_data(struct pw_manager_object *obj, const char *key,
+ size_t size, uint64_t lifetime_nsec)
+{
+ struct object *o = SPA_CONTAINER_OF(obj, struct object, this);
+ struct object_data *d;
+ void *data;
+ struct timespec timeout = {0}, interval = {0};
+
+ data = pw_manager_object_add_data(obj, key, size);
+ if (data == NULL)
+ return NULL;
+
+ d = SPA_PTROFF(data, -sizeof(struct object_data), void);
+
+ if (d->timer == NULL)
+ d->timer = pw_loop_add_timer(o->manager->loop, object_data_timeout, d);
+ if (d->timer == NULL)
+ return NULL;
+
+ timeout.tv_sec = lifetime_nsec / SPA_NSEC_PER_SEC;
+ timeout.tv_nsec = lifetime_nsec % SPA_NSEC_PER_SEC;
+ pw_loop_update_timer(o->manager->loop, d->timer, &timeout, &interval, false);
+
+ return data;
+}
+
+void *pw_manager_object_get_data(struct pw_manager_object *obj, const char *id)
+{
+ struct object *o = SPA_CONTAINER_OF(obj, struct object, this);
+ struct object_data *d = object_find_data(o, id);
+
+ return d ? SPA_PTROFF(d, sizeof(*d), void) : NULL;
+}
+
+int pw_manager_sync(struct pw_manager *manager)
+{
+ struct manager *m = SPA_CONTAINER_OF(manager, struct manager, this);
+ return core_sync(m);
+}
+
+bool pw_manager_object_is_client(struct pw_manager_object *o)
+{
+ return spa_streq(o->type, PW_TYPE_INTERFACE_Client);
+}
+
+bool pw_manager_object_is_module(struct pw_manager_object *o)
+{
+ return spa_streq(o->type, PW_TYPE_INTERFACE_Module);
+}
+
+bool pw_manager_object_is_card(struct pw_manager_object *o)
+{
+ const char *str;
+ return spa_streq(o->type, PW_TYPE_INTERFACE_Device) &&
+ o->props != NULL &&
+ (str = pw_properties_get(o->props, PW_KEY_MEDIA_CLASS)) != NULL &&
+ spa_streq(str, "Audio/Device");
+}
+
+bool pw_manager_object_is_sink(struct pw_manager_object *o)
+{
+ const char *str;
+ return spa_streq(o->type, PW_TYPE_INTERFACE_Node) &&
+ o->props != NULL &&
+ (str = pw_properties_get(o->props, PW_KEY_MEDIA_CLASS)) != NULL &&
+ (spa_streq(str, "Audio/Sink") || spa_streq(str, "Audio/Duplex"));
+}
+
+bool pw_manager_object_is_source(struct pw_manager_object *o)
+{
+ const char *str;
+ return spa_streq(o->type, PW_TYPE_INTERFACE_Node) &&
+ o->props != NULL &&
+ (str = pw_properties_get(o->props, PW_KEY_MEDIA_CLASS)) != NULL &&
+ (spa_streq(str, "Audio/Source") ||
+ spa_streq(str, "Audio/Duplex") ||
+ spa_streq(str, "Audio/Source/Virtual"));
+}
+
+bool pw_manager_object_is_monitor(struct pw_manager_object *o)
+{
+ const char *str;
+ return spa_streq(o->type, PW_TYPE_INTERFACE_Node) &&
+ o->props != NULL &&
+ (str = pw_properties_get(o->props, PW_KEY_MEDIA_CLASS)) != NULL &&
+ (spa_streq(str, "Audio/Sink"));
+}
+
+bool pw_manager_object_is_virtual(struct pw_manager_object *o)
+{
+ const char *str;
+ struct pw_node_info *info;
+ return spa_streq(o->type, PW_TYPE_INTERFACE_Node) &&
+ (info = o->info) != NULL && info->props != NULL &&
+ (str = spa_dict_lookup(info->props, PW_KEY_NODE_VIRTUAL)) != NULL &&
+ pw_properties_parse_bool(str);
+}
+
+bool pw_manager_object_is_source_or_monitor(struct pw_manager_object *o)
+{
+ return pw_manager_object_is_source(o) || pw_manager_object_is_monitor(o);
+}
+
+bool pw_manager_object_is_sink_input(struct pw_manager_object *o)
+{
+ const char *str;
+ return spa_streq(o->type, PW_TYPE_INTERFACE_Node) &&
+ o->props != NULL &&
+ (str = pw_properties_get(o->props, PW_KEY_MEDIA_CLASS)) != NULL &&
+ spa_streq(str, "Stream/Output/Audio");
+}
+
+bool pw_manager_object_is_source_output(struct pw_manager_object *o)
+{
+ const char *str;
+ return spa_streq(o->type, PW_TYPE_INTERFACE_Node) &&
+ o->props != NULL &&
+ (str = pw_properties_get(o->props, PW_KEY_MEDIA_CLASS)) != NULL &&
+ spa_streq(str, "Stream/Input/Audio");
+}
+
+bool pw_manager_object_is_recordable(struct pw_manager_object *o)
+{
+ return pw_manager_object_is_source(o) || pw_manager_object_is_sink(o) || pw_manager_object_is_sink_input(o);
+}
+
+bool pw_manager_object_is_link(struct pw_manager_object *o)
+{
+ return spa_streq(o->type, PW_TYPE_INTERFACE_Link);
+}
diff --git a/src/modules/module-protocol-pulse/manager.h b/src/modules/module-protocol-pulse/manager.h
new file mode 100644
index 0000000..56aea25
--- /dev/null
+++ b/src/modules/module-protocol-pulse/manager.h
@@ -0,0 +1,145 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_MANAGER_H
+#define PIPEWIRE_MANAGER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/pod/pod.h>
+
+#include <pipewire/pipewire.h>
+
+struct pw_manager_object;
+
+struct pw_manager_events {
+#define PW_VERSION_MANAGER_EVENTS 0
+ uint32_t version;
+
+ void (*destroy) (void *data);
+
+ void (*sync) (void *data);
+
+ void (*added) (void *data, struct pw_manager_object *object);
+
+ void (*updated) (void *data, struct pw_manager_object *object);
+
+ void (*removed) (void *data, struct pw_manager_object *object);
+
+ void (*metadata) (void *data, struct pw_manager_object *object,
+ uint32_t subject, const char *key,
+ const char *type, const char *value);
+
+ void (*disconnect) (void *data);
+
+ void (*object_data_timeout) (void *data, struct pw_manager_object *object,
+ const char *key);
+};
+
+struct pw_manager {
+ struct pw_core *core;
+ struct pw_registry *registry;
+
+ struct pw_core_info *info;
+
+ uint32_t n_objects;
+ struct spa_list object_list;
+};
+
+struct pw_manager_param {
+ uint32_t id;
+ int32_t seq;
+ struct spa_list link; /**< link in manager_object param_list */
+ struct spa_pod *param;
+};
+
+struct pw_manager_object {
+ struct spa_list link; /**< link in manager object_list */
+ uint64_t serial;
+ uint32_t id;
+ uint32_t permissions;
+ const char *type;
+ uint32_t version;
+ uint32_t index;
+ struct pw_properties *props;
+ struct pw_proxy *proxy;
+ char *message_object_path;
+ int (*message_handler)(struct pw_manager *m, struct pw_manager_object *o,
+ const char *message, const char *params, char **response);
+
+ int changed;
+ void *info;
+ struct spa_param_info *params;
+ uint32_t n_params;
+
+ struct spa_list param_list;
+ unsigned int creating:1;
+ unsigned int removing:1;
+};
+
+struct pw_manager *pw_manager_new(struct pw_core *core);
+
+void pw_manager_add_listener(struct pw_manager *manager,
+ struct spa_hook *listener,
+ const struct pw_manager_events *events, void *data);
+
+int pw_manager_sync(struct pw_manager *manager);
+
+void pw_manager_destroy(struct pw_manager *manager);
+
+int pw_manager_set_metadata(struct pw_manager *manager,
+ struct pw_manager_object *metadata,
+ uint32_t subject, const char *key, const char *type,
+ const char *format, ...) SPA_PRINTF_FUNC(6,7);
+
+int pw_manager_for_each_object(struct pw_manager *manager,
+ int (*callback) (void *data, struct pw_manager_object *object),
+ void *data);
+
+void *pw_manager_object_add_data(struct pw_manager_object *o, const char *key, size_t size);
+void *pw_manager_object_get_data(struct pw_manager_object *obj, const char *key);
+void *pw_manager_object_add_temporary_data(struct pw_manager_object *o, const char *key,
+ size_t size, uint64_t lifetime_nsec);
+
+bool pw_manager_object_is_client(struct pw_manager_object *o);
+bool pw_manager_object_is_module(struct pw_manager_object *o);
+bool pw_manager_object_is_card(struct pw_manager_object *o);
+bool pw_manager_object_is_sink(struct pw_manager_object *o);
+bool pw_manager_object_is_source(struct pw_manager_object *o);
+bool pw_manager_object_is_monitor(struct pw_manager_object *o);
+bool pw_manager_object_is_virtual(struct pw_manager_object *o);
+bool pw_manager_object_is_source_or_monitor(struct pw_manager_object *o);
+bool pw_manager_object_is_sink_input(struct pw_manager_object *o);
+bool pw_manager_object_is_source_output(struct pw_manager_object *o);
+bool pw_manager_object_is_recordable(struct pw_manager_object *o);
+bool pw_manager_object_is_link(struct pw_manager_object *o);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_MANAGER_H */
diff --git a/src/modules/module-protocol-pulse/message-handler.c b/src/modules/module-protocol-pulse/message-handler.c
new file mode 100644
index 0000000..8763ea7
--- /dev/null
+++ b/src/modules/module-protocol-pulse/message-handler.c
@@ -0,0 +1,143 @@
+#include <stdint.h>
+
+#include <regex.h>
+
+#include <spa/param/props.h>
+#include <spa/pod/builder.h>
+#include <spa/pod/pod.h>
+#include <spa/utils/defs.h>
+#include <spa/utils/json.h>
+#include <spa/utils/string.h>
+
+#include <pipewire/pipewire.h>
+
+#include "collect.h"
+#include "log.h"
+#include "manager.h"
+#include "message-handler.h"
+
+static int bluez_card_object_message_handler(struct pw_manager *m, struct pw_manager_object *o, const char *message, const char *params, char **response)
+{
+ struct transport_codec_info codecs[64];
+ uint32_t n_codecs, active;
+
+ pw_log_debug(": bluez-card %p object message:'%s' params:'%s'", o, message, params);
+
+ n_codecs = collect_transport_codec_info(o, codecs, SPA_N_ELEMENTS(codecs), &active);
+
+ if (n_codecs == 0)
+ return -EINVAL;
+
+ if (spa_streq(message, "switch-codec")) {
+ char codec[256];
+ struct spa_json it;
+ char buf[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
+ struct spa_pod_frame f[1];
+ struct spa_pod *param;
+ uint32_t codec_id = SPA_ID_INVALID;
+
+ /* Parse args */
+ if (params == NULL)
+ return -EINVAL;
+
+ spa_json_init(&it, params, strlen(params));
+ if (spa_json_get_string(&it, codec, sizeof(codec)) <= 0)
+ return -EINVAL;
+
+ codec_id = atoi(codec);
+
+ /* Switch codec */
+ spa_pod_builder_push_object(&b, &f[0],
+ SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
+ spa_pod_builder_add(&b,
+ SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(codec_id), 0);
+ param = spa_pod_builder_pop(&b, &f[0]);
+
+ pw_device_set_param((struct pw_device *)o->proxy,
+ SPA_PARAM_Props, 0, param);
+ return 0;
+ } else if (spa_streq(message, "list-codecs")) {
+ uint32_t i;
+ FILE *r;
+ size_t size;
+ bool first = true;
+
+ r = open_memstream(response, &size);
+ if (r == NULL)
+ return -errno;
+
+ fputc('[', r);
+ for (i = 0; i < n_codecs; ++i) {
+ const char *desc = codecs[i].description;
+ fprintf(r, "%s{\"name\":\"%d\",\"description\":\"%s\"}",
+ first ? "" : ",",
+ (int)codecs[i].id, desc ? desc : "Unknown");
+ first = false;
+ }
+ fputc(']', r);
+
+ return fclose(r) ? -errno : 0;
+ } else if (spa_streq(message, "get-codec")) {
+ if (active == SPA_ID_INVALID)
+ *response = strdup("null");
+ else
+ *response = spa_aprintf("\"%d\"", (int)codecs[active].id);
+ return *response ? 0 : -ENOMEM;
+ }
+
+ return -ENOSYS;
+}
+
+static int core_object_message_handler(struct pw_manager *m, struct pw_manager_object *o, const char *message, const char *params, char **response)
+{
+ pw_log_debug(": core %p object message:'%s' params:'%s'", o, message, params);
+
+ if (spa_streq(message, "list-handlers")) {
+ FILE *r;
+ size_t size;
+ bool first = true;
+
+ r = open_memstream(response, &size);
+ if (r == NULL)
+ return -errno;
+
+ fputc('[', r);
+ spa_list_for_each(o, &m->object_list, link) {
+ if (o->message_object_path) {
+ fprintf(r, "%s{\"name\":\"%s\",\"description\":\"%s\"}",
+ first ? "" : ",",
+ o->message_object_path, o->type);
+ first = false;
+ }
+ }
+ fputc(']', r);
+ return fclose(r) ? -errno : 0;
+ }
+
+ return -ENOSYS;
+}
+
+void register_object_message_handlers(struct pw_manager_object *o)
+{
+ const char *str;
+
+ if (o->id == PW_ID_CORE) {
+ free(o->message_object_path);
+ o->message_object_path = strdup("/core");
+ o->message_handler = core_object_message_handler;
+ return;
+ }
+
+ if (pw_manager_object_is_card(o) && o->props != NULL &&
+ (str = pw_properties_get(o->props, PW_KEY_DEVICE_API)) != NULL &&
+ spa_streq(str, "bluez5")) {
+ str = pw_properties_get(o->props, PW_KEY_DEVICE_NAME);
+ if (str) {
+ free(o->message_object_path);
+ o->message_object_path = spa_aprintf("/card/%s/bluez", str);
+ o->message_handler = bluez_card_object_message_handler;
+ }
+ return;
+ }
+}
diff --git a/src/modules/module-protocol-pulse/message-handler.h b/src/modules/module-protocol-pulse/message-handler.h
new file mode 100644
index 0000000..da2a7b0
--- /dev/null
+++ b/src/modules/module-protocol-pulse/message-handler.h
@@ -0,0 +1,8 @@
+#ifndef PULSE_SERVER_MESSAGE_HANDLER_H
+#define PULSE_SERVER_MESSAGE_HANDLER_H
+
+struct pw_manager_object;
+
+void register_object_message_handlers(struct pw_manager_object *o);
+
+#endif /* PULSE_SERVER_MESSAGE_HANDLER_H */
diff --git a/src/modules/module-protocol-pulse/message.c b/src/modules/module-protocol-pulse/message.c
new file mode 100644
index 0000000..daf4d2e
--- /dev/null
+++ b/src/modules/module-protocol-pulse/message.c
@@ -0,0 +1,879 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <arpa/inet.h>
+#include <math.h>
+
+#include <spa/debug/buffer.h>
+#include <spa/utils/defs.h>
+#include <spa/utils/string.h>
+#include <pipewire/log.h>
+
+#include "defs.h"
+#include "format.h"
+#include "internal.h"
+#include "message.h"
+#include "remap.h"
+#include "volume.h"
+
+#define MAX_SIZE (256*1024)
+#define MAX_ALLOCATED (16*1024 *1024)
+
+#define VOLUME_MUTED ((uint32_t) 0U)
+#define VOLUME_NORM ((uint32_t) 0x10000U)
+#define VOLUME_MAX ((uint32_t) UINT32_MAX/2)
+
+#define PA_CHANNELS_MAX (32u)
+
+PW_LOG_TOPIC_EXTERN(pulse_conn);
+#define PW_LOG_TOPIC_DEFAULT pulse_conn
+
+static inline uint32_t volume_from_linear(float vol)
+{
+ uint32_t v;
+ if (vol <= 0.0f)
+ v = VOLUME_MUTED;
+ else
+ v = SPA_CLAMP((uint64_t) lround(cbrt(vol) * VOLUME_NORM),
+ VOLUME_MUTED, VOLUME_MAX);
+ return v;
+}
+
+static inline float volume_to_linear(uint32_t vol)
+{
+ float v = ((float)vol) / VOLUME_NORM;
+ return v * v * v;
+}
+
+static int read_u8(struct message *m, uint8_t *val)
+{
+ if (m->offset + 1 > m->length)
+ return -ENOSPC;
+ *val = m->data[m->offset];
+ m->offset++;
+ return 0;
+}
+
+static int read_u32(struct message *m, uint32_t *val)
+{
+ if (m->offset + 4 > m->length)
+ return -ENOSPC;
+ memcpy(val, &m->data[m->offset], 4);
+ *val = ntohl(*val);
+ m->offset += 4;
+ return 0;
+}
+static int read_u64(struct message *m, uint64_t *val)
+{
+ uint32_t tmp;
+ int res;
+ if ((res = read_u32(m, &tmp)) < 0)
+ return res;
+ *val = ((uint64_t)tmp) << 32;
+ if ((res = read_u32(m, &tmp)) < 0)
+ return res;
+ *val |= tmp;
+ return 0;
+}
+
+static int read_sample_spec(struct message *m, struct sample_spec *ss)
+{
+ int res;
+ uint8_t tmp;
+ if ((res = read_u8(m, &tmp)) < 0)
+ return res;
+ ss->format = format_pa2id(tmp);
+ if ((res = read_u8(m, &ss->channels)) < 0)
+ return res;
+ return read_u32(m, &ss->rate);
+}
+
+static int read_props(struct message *m, struct pw_properties *props, bool remap)
+{
+ int res;
+
+ while (true) {
+ const char *key;
+ const void *data;
+ uint32_t length;
+ size_t size;
+ const struct str_map *map;
+
+ if ((res = message_get(m,
+ TAG_STRING, &key,
+ TAG_INVALID)) < 0)
+ return res;
+
+ if (key == NULL)
+ break;
+
+ if ((res = message_get(m,
+ TAG_U32, &length,
+ TAG_INVALID)) < 0)
+ return res;
+ if (length > MAX_TAG_SIZE)
+ return -EINVAL;
+
+ if ((res = message_get(m,
+ TAG_ARBITRARY, &data, &size,
+ TAG_INVALID)) < 0)
+ return res;
+
+ if (remap && (map = str_map_find(props_key_map, NULL, key)) != NULL) {
+ key = map->pw_str;
+ if (map->child != NULL &&
+ (map = str_map_find(map->child, NULL, data)) != NULL)
+ data = map->pw_str;
+ }
+ pw_properties_set(props, key, data);
+ }
+ return 0;
+}
+
+static int read_arbitrary(struct message *m, const void **val, size_t *length)
+{
+ uint32_t len;
+ int res;
+ if ((res = read_u32(m, &len)) < 0)
+ return res;
+ if (m->offset + len > m->length)
+ return -ENOSPC;
+ *val = m->data + m->offset;
+ m->offset += len;
+ if (length)
+ *length = len;
+ return 0;
+}
+
+static int read_string(struct message *m, char **str)
+{
+ uint32_t n, maxlen;
+ if (m->offset + 1 > m->length)
+ return -ENOSPC;
+ maxlen = m->length - m->offset;
+ n = strnlen(SPA_PTROFF(m->data, m->offset, char), maxlen);
+ if (n == maxlen)
+ return -EINVAL;
+ *str = SPA_PTROFF(m->data, m->offset, char);
+ m->offset += n + 1;
+ return 0;
+}
+
+static int read_timeval(struct message *m, struct timeval *tv)
+{
+ int res;
+ uint32_t tmp;
+
+ if ((res = read_u32(m, &tmp)) < 0)
+ return res;
+ tv->tv_sec = tmp;
+ if ((res = read_u32(m, &tmp)) < 0)
+ return res;
+ tv->tv_usec = tmp;
+ return 0;
+}
+
+static int read_channel_map(struct message *m, struct channel_map *map)
+{
+ int res;
+ uint8_t i, tmp;
+
+ if ((res = read_u8(m, &map->channels)) < 0)
+ return res;
+ if (map->channels > CHANNELS_MAX)
+ return -EINVAL;
+ for (i = 0; i < map->channels; i ++) {
+ if ((res = read_u8(m, &tmp)) < 0)
+ return res;
+ map->map[i] = channel_pa2id(tmp);
+ }
+ return 0;
+}
+static int read_volume(struct message *m, float *vol)
+{
+ int res;
+ uint32_t v;
+ if ((res = read_u32(m, &v)) < 0)
+ return res;
+ *vol = volume_to_linear(v);
+ return 0;
+}
+
+static int read_cvolume(struct message *m, struct volume *vol)
+{
+ int res;
+ uint8_t i;
+
+ if ((res = read_u8(m, &vol->channels)) < 0)
+ return res;
+ if (vol->channels > CHANNELS_MAX)
+ return -EINVAL;
+ for (i = 0; i < vol->channels; i ++) {
+ if ((res = read_volume(m, &vol->values[i])) < 0)
+ return res;
+ }
+ return 0;
+}
+
+static int read_format_info(struct message *m, struct format_info *info)
+{
+ int res;
+ uint8_t tag, encoding;
+
+ spa_zero(*info);
+ if ((res = read_u8(m, &tag)) < 0)
+ return res;
+ if (tag != TAG_U8)
+ return -EPROTO;
+ if ((res = read_u8(m, &encoding)) < 0)
+ return res;
+ info->encoding = encoding;
+
+ if ((res = read_u8(m, &tag)) < 0)
+ return res;
+ if (tag != TAG_PROPLIST)
+ return -EPROTO;
+
+ info->props = pw_properties_new(NULL, NULL);
+ if (info->props == NULL)
+ return -errno;
+ if ((res = read_props(m, info->props, false)) < 0)
+ format_info_clear(info);
+ return res;
+}
+
+int message_get(struct message *m, ...)
+{
+ va_list va;
+ int res = 0;
+
+ va_start(va, m);
+
+ while (true) {
+ int tag = va_arg(va, int);
+ uint8_t dtag;
+ if (tag == TAG_INVALID)
+ break;
+
+ if ((res = read_u8(m, &dtag)) < 0)
+ goto done;
+
+ switch (dtag) {
+ case TAG_STRING:
+ if (tag != TAG_STRING)
+ goto invalid;
+ if ((res = read_string(m, va_arg(va, char**))) < 0)
+ goto done;
+ break;
+ case TAG_STRING_NULL:
+ if (tag != TAG_STRING)
+ goto invalid;
+ *va_arg(va, char**) = NULL;
+ break;
+ case TAG_U8:
+ if (dtag != tag)
+ goto invalid;
+ if ((res = read_u8(m, va_arg(va, uint8_t*))) < 0)
+ goto done;
+ break;
+ case TAG_U32:
+ if (dtag != tag)
+ goto invalid;
+ if ((res = read_u32(m, va_arg(va, uint32_t*))) < 0)
+ goto done;
+ break;
+ case TAG_S64:
+ case TAG_U64:
+ case TAG_USEC:
+ if (dtag != tag)
+ goto invalid;
+ if ((res = read_u64(m, va_arg(va, uint64_t*))) < 0)
+ goto done;
+ break;
+ case TAG_SAMPLE_SPEC:
+ if (dtag != tag)
+ goto invalid;
+ if ((res = read_sample_spec(m, va_arg(va, struct sample_spec*))) < 0)
+ goto done;
+ break;
+ case TAG_ARBITRARY:
+ {
+ const void **val = va_arg(va, const void**);
+ size_t *len = va_arg(va, size_t*);
+ if (dtag != tag)
+ goto invalid;
+ if ((res = read_arbitrary(m, val, len)) < 0)
+ goto done;
+ break;
+ }
+ case TAG_BOOLEAN_TRUE:
+ if (tag != TAG_BOOLEAN)
+ goto invalid;
+ *va_arg(va, bool*) = true;
+ break;
+ case TAG_BOOLEAN_FALSE:
+ if (tag != TAG_BOOLEAN)
+ goto invalid;
+ *va_arg(va, bool*) = false;
+ break;
+ case TAG_TIMEVAL:
+ if (dtag != tag)
+ goto invalid;
+ if ((res = read_timeval(m, va_arg(va, struct timeval*))) < 0)
+ goto done;
+ break;
+ case TAG_CHANNEL_MAP:
+ if (dtag != tag)
+ goto invalid;
+ if ((res = read_channel_map(m, va_arg(va, struct channel_map*))) < 0)
+ goto done;
+ break;
+ case TAG_CVOLUME:
+ if (dtag != tag)
+ goto invalid;
+ if ((res = read_cvolume(m, va_arg(va, struct volume*))) < 0)
+ goto done;
+ break;
+ case TAG_PROPLIST:
+ if (dtag != tag)
+ goto invalid;
+ if ((res = read_props(m, va_arg(va, struct pw_properties*), true)) < 0)
+ goto done;
+ break;
+ case TAG_VOLUME:
+ if (dtag != tag)
+ goto invalid;
+ if ((res = read_volume(m, va_arg(va, float*))) < 0)
+ goto done;
+ break;
+ case TAG_FORMAT_INFO:
+ if (dtag != tag)
+ goto invalid;
+ if ((res = read_format_info(m, va_arg(va, struct format_info*))) < 0)
+ goto done;
+ break;
+ }
+ }
+ res = 0;
+ goto done;
+
+invalid:
+ res = -EINVAL;
+
+done:
+ va_end(va);
+
+ return res;
+}
+
+static int ensure_size(struct message *m, uint32_t size)
+{
+ uint32_t alloc, diff;
+ void *data;
+
+ if (m->length > m->allocated)
+ return -ENOMEM;
+
+ if (m->length + size <= m->allocated)
+ return size;
+
+ alloc = SPA_ROUND_UP_N(SPA_MAX(m->allocated + size, 4096u), 4096u);
+ diff = alloc - m->allocated;
+ if ((data = realloc(m->data, alloc)) == NULL) {
+ free(m->data);
+ m->data = NULL;
+ m->impl->stat.allocated -= m->allocated;
+ m->allocated = 0;
+ return -errno;
+ }
+ m->impl->stat.allocated += diff;
+ m->impl->stat.accumulated += diff;
+ m->data = data;
+ m->allocated = alloc;
+ return size;
+}
+
+static void write_8(struct message *m, uint8_t val)
+{
+ if (ensure_size(m, 1) > 0)
+ m->data[m->length] = val;
+ m->length++;
+}
+
+static void write_32(struct message *m, uint32_t val)
+{
+ val = htonl(val);
+ if (ensure_size(m, 4) > 0)
+ memcpy(m->data + m->length, &val, 4);
+ m->length += 4;
+}
+
+static void write_string(struct message *m, const char *s)
+{
+ write_8(m, s ? TAG_STRING : TAG_STRING_NULL);
+ if (s != NULL) {
+ int len = strlen(s) + 1;
+ if (ensure_size(m, len) > 0)
+ strcpy(SPA_PTROFF(m->data, m->length, char), s);
+ m->length += len;
+ }
+}
+static void write_u8(struct message *m, uint8_t val)
+{
+ write_8(m, TAG_U8);
+ write_8(m, val);
+}
+
+static void write_u32(struct message *m, uint32_t val)
+{
+ write_8(m, TAG_U32);
+ write_32(m, val);
+}
+
+static void write_64(struct message *m, uint8_t tag, uint64_t val)
+{
+ write_8(m, tag);
+ write_32(m, val >> 32);
+ write_32(m, val);
+}
+
+static void write_sample_spec(struct message *m, struct sample_spec *ss)
+{
+ uint32_t channels = SPA_MIN(ss->channels, PA_CHANNELS_MAX);
+ write_8(m, TAG_SAMPLE_SPEC);
+ write_8(m, format_id2pa(ss->format));
+ write_8(m, channels);
+ write_32(m, ss->rate);
+}
+
+static void write_arbitrary(struct message *m, const void *p, size_t length)
+{
+ write_8(m, TAG_ARBITRARY);
+ write_32(m, length);
+ if (ensure_size(m, length) > 0)
+ memcpy(m->data + m->length, p, length);
+ m->length += length;
+}
+
+static void write_boolean(struct message *m, bool val)
+{
+ write_8(m, val ? TAG_BOOLEAN_TRUE : TAG_BOOLEAN_FALSE);
+}
+
+static void write_timeval(struct message *m, struct timeval *tv)
+{
+ write_8(m, TAG_TIMEVAL);
+ write_32(m, tv->tv_sec);
+ write_32(m, tv->tv_usec);
+}
+
+static void write_channel_map(struct message *m, struct channel_map *map)
+{
+ uint8_t i;
+ uint32_t aux = 0, channels = SPA_MIN(map->channels, PA_CHANNELS_MAX);
+ write_8(m, TAG_CHANNEL_MAP);
+ write_8(m, channels);
+ for (i = 0; i < channels; i ++)
+ write_8(m, channel_id2pa(map->map[i], &aux));
+}
+
+static void write_volume(struct message *m, float vol)
+{
+ write_8(m, TAG_VOLUME);
+ write_32(m, volume_from_linear(vol));
+}
+
+static void write_cvolume(struct message *m, struct volume *vol)
+{
+ uint8_t i;
+ uint32_t channels = SPA_MIN(vol->channels, PA_CHANNELS_MAX);
+ write_8(m, TAG_CVOLUME);
+ write_8(m, channels);
+ for (i = 0; i < channels; i ++)
+ write_32(m, volume_from_linear(vol->values[i]));
+}
+
+static void add_stream_group(struct message *m, struct spa_dict *dict, const char *key,
+ const char *media_class, const char *media_role)
+{
+ const char *str, *id, *prefix;
+ char *b;
+ int l;
+
+ if (media_class == NULL)
+ return;
+ if (spa_streq(media_class, "Stream/Output/Audio"))
+ prefix = "sink-input";
+ else if (spa_streq(media_class, "Stream/Input/Audio"))
+ prefix = "source-output";
+ else
+ return;
+
+ if ((str = media_role) != NULL)
+ id = "media-role";
+ else if ((str = spa_dict_lookup(dict, PW_KEY_APP_ID)) != NULL)
+ id = "application-id";
+ else if ((str = spa_dict_lookup(dict, PW_KEY_APP_NAME)) != NULL)
+ id = "application-name";
+ else if ((str = spa_dict_lookup(dict, PW_KEY_MEDIA_NAME)) != NULL)
+ id = "media-name";
+ else
+ return;
+
+ write_string(m, key);
+ l = strlen(prefix) + strlen(id) + strlen(str) + 6; /* "-by-" , ":" and \0 */
+ b = alloca(l);
+ snprintf(b, l, "%s-by-%s:%s", prefix, id, str);
+ write_u32(m, l);
+ write_arbitrary(m, b, l);
+}
+
+static void write_dict(struct message *m, struct spa_dict *dict, bool remap)
+{
+ const struct spa_dict_item *it;
+
+ write_8(m, TAG_PROPLIST);
+ if (dict != NULL) {
+ const char *media_class = NULL, *media_role = NULL;
+ spa_dict_for_each(it, dict) {
+ const char *key = it->key;
+ const char *val = it->value;
+ int l;
+ const struct str_map *map;
+
+ if (remap && (map = str_map_find(props_key_map, key, NULL)) != NULL) {
+ key = map->pa_str;
+ if (map->child != NULL &&
+ (map = str_map_find(map->child, val, NULL)) != NULL)
+ val = map->pa_str;
+ }
+ if (spa_streq(key, "media.class"))
+ media_class = val;
+ if (spa_streq(key, "media.role"))
+ media_role = val;
+
+ write_string(m, key);
+ l = strlen(val) + 1;
+ write_u32(m, l);
+ write_arbitrary(m, val, l);
+
+ }
+ if (remap)
+ add_stream_group(m, dict, "module-stream-restore.id",
+ media_class, media_role);
+ }
+ write_string(m, NULL);
+}
+
+static void write_format_info(struct message *m, struct format_info *info)
+{
+ write_8(m, TAG_FORMAT_INFO);
+ write_u8(m, (uint8_t) info->encoding);
+ write_dict(m, info->props ? &info->props->dict : NULL, false);
+}
+
+int message_put(struct message *m, ...)
+{
+ va_list va;
+
+ if (m == NULL)
+ return -EINVAL;
+
+ va_start(va, m);
+
+ while (true) {
+ int tag = va_arg(va, int);
+ if (tag == TAG_INVALID)
+ break;
+
+ switch (tag) {
+ case TAG_STRING:
+ write_string(m, va_arg(va, const char *));
+ break;
+ case TAG_U8:
+ write_u8(m, (uint8_t)va_arg(va, int));
+ break;
+ case TAG_U32:
+ write_u32(m, (uint32_t)va_arg(va, uint32_t));
+ break;
+ case TAG_S64:
+ case TAG_U64:
+ case TAG_USEC:
+ write_64(m, tag, va_arg(va, uint64_t));
+ break;
+ case TAG_SAMPLE_SPEC:
+ write_sample_spec(m, va_arg(va, struct sample_spec*));
+ break;
+ case TAG_ARBITRARY:
+ {
+ const void *p = va_arg(va, const void*);
+ size_t length = va_arg(va, size_t);
+ write_arbitrary(m, p, length);
+ break;
+ }
+ case TAG_BOOLEAN:
+ write_boolean(m, va_arg(va, int));
+ break;
+ case TAG_TIMEVAL:
+ write_timeval(m, va_arg(va, struct timeval*));
+ break;
+ case TAG_CHANNEL_MAP:
+ write_channel_map(m, va_arg(va, struct channel_map*));
+ break;
+ case TAG_CVOLUME:
+ write_cvolume(m, va_arg(va, struct volume*));
+ break;
+ case TAG_PROPLIST:
+ write_dict(m, va_arg(va, struct spa_dict*), true);
+ break;
+ case TAG_VOLUME:
+ write_volume(m, va_arg(va, double));
+ break;
+ case TAG_FORMAT_INFO:
+ write_format_info(m, va_arg(va, struct format_info*));
+ break;
+ }
+ }
+ va_end(va);
+
+ if (m->length > m->allocated)
+ return -ENOMEM;
+
+ return 0;
+}
+
+int message_dump(enum spa_log_level level, struct message *m)
+{
+ int res;
+ uint32_t i, offset = m->offset, o;
+
+ pw_log(level, "message: len:%d alloc:%u", m->length, m->allocated);
+ while (true) {
+ uint8_t tag;
+
+ o = m->offset;
+ if (read_u8(m, &tag) < 0)
+ break;
+
+ switch (tag) {
+ case TAG_STRING:
+ {
+ char *val;
+ if ((res = read_string(m, &val)) < 0)
+ return res;
+ pw_log(level, "%u: string: '%s'", o, val);
+ break;
+ }
+ case TAG_STRING_NULL:
+ pw_log(level, "%u: string: NULL", o);
+ break;
+ case TAG_U8:
+ {
+ uint8_t val;
+ if ((res = read_u8(m, &val)) < 0)
+ return res;
+ pw_log(level, "%u: u8: %u", o, val);
+ break;
+ }
+ case TAG_U32:
+ {
+ uint32_t val;
+ if ((res = read_u32(m, &val)) < 0)
+ return res;
+ pw_log(level, "%u: u32: %u", o, val);
+ break;
+ }
+ case TAG_S64:
+ {
+ uint64_t val;
+ if ((res = read_u64(m, &val)) < 0)
+ return res;
+ pw_log(level, "%u: s64: %"PRIi64"", o, (int64_t)val);
+ break;
+ }
+ case TAG_U64:
+ {
+ uint64_t val;
+ if ((res = read_u64(m, &val)) < 0)
+ return res;
+ pw_log(level, "%u: u64: %"PRIu64"", o, val);
+ break;
+ }
+ case TAG_USEC:
+ {
+ uint64_t val;
+ if ((res = read_u64(m, &val)) < 0)
+ return res;
+ pw_log(level, "%u: u64: %"PRIu64"", o, val);
+ break;
+ }
+ case TAG_SAMPLE_SPEC:
+ {
+ struct sample_spec ss;
+ if ((res = read_sample_spec(m, &ss)) < 0)
+ return res;
+ pw_log(level, "%u: ss: format:%s rate:%d channels:%u", o,
+ format_id2name(ss.format), ss.rate,
+ ss.channels);
+ break;
+ }
+ case TAG_ARBITRARY:
+ {
+ const void *mem;
+ size_t len;
+ if ((res = read_arbitrary(m, &mem, &len)) < 0)
+ return res;
+ spa_debug_mem(0, mem, len);
+ break;
+ }
+ case TAG_BOOLEAN_TRUE:
+ pw_log(level, "%u: bool: true", o);
+ break;
+ case TAG_BOOLEAN_FALSE:
+ pw_log(level, "%u: bool: false", o);
+ break;
+ case TAG_TIMEVAL:
+ {
+ struct timeval tv;
+ if ((res = read_timeval(m, &tv)) < 0)
+ return res;
+ pw_log(level, "%u: timeval: %lu:%lu", o, tv.tv_sec, tv.tv_usec);
+ break;
+ }
+ case TAG_CHANNEL_MAP:
+ {
+ struct channel_map map;
+ if ((res = read_channel_map(m, &map)) < 0)
+ return res;
+ pw_log(level, "%u: channelmap: channels:%u", o, map.channels);
+ for (i = 0; i < map.channels; i++)
+ pw_log(level, " %d: %s", i, channel_id2name(map.map[i]));
+ break;
+ }
+ case TAG_CVOLUME:
+ {
+ struct volume vol;
+ if ((res = read_cvolume(m, &vol)) < 0)
+ return res;
+ pw_log(level, "%u: cvolume: channels:%u", o, vol.channels);
+ for (i = 0; i < vol.channels; i++)
+ pw_log(level, " %d: %f", i, vol.values[i]);
+ break;
+ }
+ case TAG_PROPLIST:
+ {
+ struct pw_properties *props = pw_properties_new(NULL, NULL);
+ const struct spa_dict_item *it;
+ res = read_props(m, props, false);
+ if (res >= 0) {
+ pw_log(level, "%u: props: n_items:%u", o, props->dict.n_items);
+ spa_dict_for_each(it, &props->dict)
+ pw_log(level, " '%s': '%s'", it->key, it->value);
+ }
+ pw_properties_free(props);
+ if (res < 0)
+ return res;
+ break;
+ }
+ case TAG_VOLUME:
+ {
+ float vol;
+ if ((res = read_volume(m, &vol)) < 0)
+ return res;
+ pw_log(level, "%u: volume: %f", o, vol);
+ break;
+ }
+ case TAG_FORMAT_INFO:
+ {
+ struct format_info info;
+ const struct spa_dict_item *it;
+ if ((res = read_format_info(m, &info)) < 0)
+ return res;
+ pw_log(level, "%u: format-info: enc:%s n_items:%u",
+ o, format_encoding2name(info.encoding),
+ info.props->dict.n_items);
+ spa_dict_for_each(it, &info.props->dict)
+ pw_log(level, " '%s': '%s'", it->key, it->value);
+ break;
+ }
+ }
+ }
+ m->offset = offset;
+
+ return 0;
+}
+
+struct message *message_alloc(struct impl *impl, uint32_t channel, uint32_t size)
+{
+ struct message *msg;
+
+ if (!spa_list_is_empty(&impl->free_messages)) {
+ msg = spa_list_first(&impl->free_messages, struct message, link);
+ spa_list_remove(&msg->link);
+ pw_log_trace("using recycled message %p size:%d", msg, size);
+
+ spa_assert(msg->impl == impl);
+ } else {
+ if ((msg = calloc(1, sizeof(*msg))) == NULL)
+ return NULL;
+
+ pw_log_trace("new message %p size:%d", msg, size);
+ msg->impl = impl;
+ msg->impl->stat.n_allocated++;
+ msg->impl->stat.n_accumulated++;
+ }
+
+ if (ensure_size(msg, size) < 0) {
+ message_free(msg, false, true);
+ return NULL;
+ }
+
+ spa_zero(msg->extra);
+ msg->channel = channel;
+ msg->offset = 0;
+ msg->length = size;
+
+ return msg;
+}
+
+void message_free(struct message *msg, bool dequeue, bool destroy)
+{
+ if (dequeue)
+ spa_list_remove(&msg->link);
+
+ if (msg->impl->stat.allocated > MAX_ALLOCATED || msg->allocated > MAX_SIZE)
+ destroy = true;
+
+ if (destroy) {
+ pw_log_trace("destroy message %p size:%d", msg, msg->allocated);
+ msg->impl->stat.n_allocated--;
+ msg->impl->stat.allocated -= msg->allocated;
+ free(msg->data);
+ free(msg);
+ } else {
+ pw_log_trace("recycle message %p size:%d/%d", msg, msg->length, msg->allocated);
+ spa_list_append(&msg->impl->free_messages, &msg->link);
+ msg->length = 0;
+ }
+}
diff --git a/src/modules/module-protocol-pulse/message.h b/src/modules/module-protocol-pulse/message.h
new file mode 100644
index 0000000..ad95292
--- /dev/null
+++ b/src/modules/module-protocol-pulse/message.h
@@ -0,0 +1,75 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSE_SERVER_MESSAGE_H
+#define PULSE_SERVER_MESSAGE_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <spa/utils/list.h>
+#include <spa/support/log.h>
+
+struct impl;
+
+struct message {
+ struct spa_list link;
+ struct impl *impl;
+ uint32_t extra[4];
+ uint32_t channel;
+ uint32_t allocated;
+ uint32_t length;
+ uint32_t offset;
+ uint8_t *data;
+};
+
+enum {
+ TAG_INVALID = 0,
+ TAG_STRING = 't',
+ TAG_STRING_NULL = 'N',
+ TAG_U32 = 'L',
+ TAG_U8 = 'B',
+ TAG_U64 = 'R',
+ TAG_S64 = 'r',
+ TAG_SAMPLE_SPEC = 'a',
+ TAG_ARBITRARY = 'x',
+ TAG_BOOLEAN_TRUE = '1',
+ TAG_BOOLEAN_FALSE = '0',
+ TAG_BOOLEAN = TAG_BOOLEAN_TRUE,
+ TAG_TIMEVAL = 'T',
+ TAG_USEC = 'U' /* 64bit unsigned */,
+ TAG_CHANNEL_MAP = 'm',
+ TAG_CVOLUME = 'v',
+ TAG_PROPLIST = 'P',
+ TAG_VOLUME = 'V',
+ TAG_FORMAT_INFO = 'f',
+};
+
+struct message *message_alloc(struct impl *impl, uint32_t channel, uint32_t size);
+void message_free(struct message *msg, bool dequeue, bool destroy);
+int message_get(struct message *m, ...);
+int message_put(struct message *m, ...);
+int message_dump(enum spa_log_level level, struct message *m);
+
+#endif /* PULSE_SERVER_MESSAGE_H */
diff --git a/src/modules/module-protocol-pulse/module.c b/src/modules/module-protocol-pulse/module.c
new file mode 100644
index 0000000..ec10404
--- /dev/null
+++ b/src/modules/module-protocol-pulse/module.c
@@ -0,0 +1,333 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Georges Basile Stavracas Neto
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/list.h>
+#include <spa/utils/hook.h>
+#include <spa/utils/string.h>
+#include <pipewire/log.h>
+#include <pipewire/map.h>
+#include <pipewire/properties.h>
+#include <pipewire/work-queue.h>
+
+#include "defs.h"
+#include "format.h"
+#include "internal.h"
+#include "log.h"
+#include "module.h"
+#include "remap.h"
+
+static void on_module_unload(void *obj, void *data, int res, uint32_t index)
+{
+ struct module *module = obj;
+ module_unload(module);
+}
+
+void module_schedule_unload(struct module *module)
+{
+ if (module->unloading)
+ return;
+
+ pw_work_queue_add(module->impl->work_queue, module, 0, on_module_unload, NULL);
+ module->unloading = true;
+}
+
+static struct module *module_new(struct impl *impl, const struct module_info *info)
+{
+ struct module *module;
+
+ module = calloc(1, sizeof(*module) + info->data_size);
+ if (module == NULL)
+ return NULL;
+
+ module->index = SPA_ID_INVALID;
+ module->impl = impl;
+ module->info = info;
+ spa_hook_list_init(&module->listener_list);
+ module->user_data = SPA_PTROFF(module, sizeof(*module), void);
+ module->loaded = false;
+
+ return module;
+}
+
+void module_add_listener(struct module *module,
+ struct spa_hook *listener,
+ const struct module_events *events, void *data)
+{
+ spa_hook_list_append(&module->listener_list, listener, events, data);
+}
+
+int module_load(struct module *module)
+{
+ pw_log_info("load module index:%u name:%s", module->index, module->info->name);
+ if (module->info->load == NULL)
+ return -ENOTSUP;
+ /* subscription event is sent when the module does a
+ * module_emit_loaded() */
+ return module->info->load(module);
+}
+
+void module_free(struct module *module)
+{
+ struct impl *impl = module->impl;
+
+ module_emit_destroy(module);
+
+ if (module->index != SPA_ID_INVALID)
+ pw_map_remove(&impl->modules, module->index & MODULE_INDEX_MASK);
+
+ if (module->unloading)
+ pw_work_queue_cancel(impl->work_queue, module, SPA_ID_INVALID);
+
+ spa_hook_list_clean(&module->listener_list);
+ pw_properties_free(module->props);
+
+ free((char*)module->args);
+
+ free(module);
+}
+
+int module_unload(struct module *module)
+{
+ struct impl *impl = module->impl;
+ int res = 0;
+
+ pw_log_info("unload module index:%u name:%s", module->index, module->info->name);
+
+ if (module->info->unload)
+ res = module->info->unload(module);
+
+ if (module->loaded)
+ broadcast_subscribe_event(impl,
+ SUBSCRIPTION_MASK_MODULE,
+ SUBSCRIPTION_EVENT_REMOVE | SUBSCRIPTION_EVENT_MODULE,
+ module->index);
+
+ module_free(module);
+
+ return res;
+}
+
+/** utils */
+void module_args_add_props(struct pw_properties *props, const char *str)
+{
+ char *s = strdup(str), *p = s, *e, f;
+ const char *k, *v;
+ const struct str_map *map;
+
+ while (*p) {
+ while (*p && isspace(*p))
+ p++;
+ e = strchr(p, '=');
+ if (e == NULL)
+ break;
+ *e = '\0';
+ k = p;
+ p = e+1;
+
+ if (*p == '\"') {
+ p++;
+ f = '\"';
+ } else if (*p == '\'') {
+ p++;
+ f = '\'';
+ } else {
+ f = ' ';
+ }
+ v = p;
+ for (e = p; *e ; e++) {
+ if (*e == f)
+ break;
+ if (*e == '\\')
+ e++;
+ }
+ p = e;
+ if (*e != '\0')
+ p++;
+ *e = '\0';
+
+ if ((map = str_map_find(props_key_map, NULL, k)) != NULL) {
+ k = map->pw_str;
+ if (map->child != NULL &&
+ (map = str_map_find(map->child, NULL, v)) != NULL)
+ v = map->pw_str;
+ }
+ pw_properties_set(props, k, v);
+ }
+ free(s);
+}
+
+int module_args_to_audioinfo(struct impl *impl, struct pw_properties *props, struct spa_audio_info_raw *info)
+{
+ const char *str;
+ uint32_t i;
+
+ /* We don't use any incoming format setting and use our native format */
+ spa_zero(*info);
+ info->flags = SPA_AUDIO_FLAG_UNPOSITIONED;
+ info->format = SPA_AUDIO_FORMAT_F32P;
+
+ if ((str = pw_properties_get(props, "channels")) != NULL) {
+ info->channels = pw_properties_parse_int(str);
+ if (info->channels == 0 || info->channels > SPA_AUDIO_MAX_CHANNELS) {
+ pw_log_error("invalid channels '%s'", str);
+ return -EINVAL;
+ }
+ pw_properties_set(props, "channels", NULL);
+ }
+ if ((str = pw_properties_get(props, "channel_map")) != NULL) {
+ struct channel_map map;
+
+ channel_map_parse(str, &map);
+ if (map.channels == 0 || map.channels > SPA_AUDIO_MAX_CHANNELS) {
+ pw_log_error("invalid channel_map '%s'", str);
+ return -EINVAL;
+ }
+ if (info->channels == 0)
+ info->channels = map.channels;
+ if (info->channels != map.channels) {
+ pw_log_error("Mismatched channel map");
+ return -EINVAL;
+ }
+ channel_map_to_positions(&map, info->position);
+ info->flags &= ~SPA_AUDIO_FLAG_UNPOSITIONED;
+ pw_properties_set(props, "channel_map", NULL);
+ } else {
+ if (info->channels == 0)
+ info->channels = impl->defs.sample_spec.channels;
+
+ if (info->channels == impl->defs.channel_map.channels) {
+ channel_map_to_positions(&impl->defs.channel_map, info->position);
+ } else if (info->channels == 1) {
+ info->position[0] = SPA_AUDIO_CHANNEL_MONO;
+ } else if (info->channels == 2) {
+ info->position[0] = SPA_AUDIO_CHANNEL_FL;
+ info->position[1] = SPA_AUDIO_CHANNEL_FR;
+ } else {
+ /* FIXME add more mappings */
+ for (i = 0; i < info->channels; i++)
+ info->position[i] = SPA_AUDIO_CHANNEL_UNKNOWN;
+ }
+ if (info->position[0] != SPA_AUDIO_CHANNEL_UNKNOWN)
+ info->flags &= ~SPA_AUDIO_FLAG_UNPOSITIONED;
+ }
+
+ if ((str = pw_properties_get(props, "rate")) != NULL) {
+ info->rate = pw_properties_parse_int(str);
+ pw_properties_set(props, "rate", NULL);
+ } else {
+ info->rate = 0;
+ }
+ return 0;
+}
+
+bool module_args_parse_bool(const char *v)
+{
+ if (spa_streq(v, "1") || !strcasecmp(v, "y") || !strcasecmp(v, "t") ||
+ !strcasecmp(v, "yes") || !strcasecmp(v, "true") || !strcasecmp(v, "on"))
+ return true;
+ return false;
+}
+
+static const struct module_info *find_module_info(const char *name)
+{
+ extern const struct module_info __start_pw_mod_pulse_modules[];
+ extern const struct module_info __stop_pw_mod_pulse_modules[];
+
+ const struct module_info *info = __start_pw_mod_pulse_modules;
+
+ for (; info < __stop_pw_mod_pulse_modules; info++) {
+ if (spa_streq(info->name, name))
+ return info;
+ }
+
+ spa_assert(info == __stop_pw_mod_pulse_modules);
+
+ return NULL;
+}
+
+static int find_module_by_name(void *item_data, void *data)
+{
+ const char *name = data;
+ const struct module *module = item_data;
+ return spa_streq(module->info->name, name) ? 1 : 0;
+}
+
+struct module *module_create(struct impl *impl, const char *name, const char *args)
+{
+ const struct module_info *info;
+ struct module *module;
+
+ info = find_module_info(name);
+ if (info == NULL) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ if (info->load_once) {
+ int exists;
+ exists = pw_map_for_each(&impl->modules, find_module_by_name,
+ (void *)name);
+ if (exists) {
+ errno = EEXIST;
+ return NULL;
+ }
+ }
+
+ module = module_new(impl, info);
+ if (module == NULL)
+ return NULL;
+
+ module->props = pw_properties_new(NULL, NULL);
+ if (module->props == NULL) {
+ module_free(module);
+ return NULL;
+ }
+
+ if (args)
+ module_args_add_props(module->props, args);
+
+ int res = module->info->prepare(module);
+ if (res < 0) {
+ module_free(module);
+ errno = -res;
+ return NULL;
+ }
+
+ module->index = pw_map_insert_new(&impl->modules, module);
+ if (module->index == SPA_ID_INVALID) {
+ module_unload(module);
+ return NULL;
+ }
+
+ module->args = args ? strdup(args) : NULL;
+ module->index |= MODULE_FLAG;
+
+ return module;
+}
diff --git a/src/modules/module-protocol-pulse/module.h b/src/modules/module-protocol-pulse/module.h
new file mode 100644
index 0000000..1a6ffb0
--- /dev/null
+++ b/src/modules/module-protocol-pulse/module.h
@@ -0,0 +1,94 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Georges Basile Stavracas Neto
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_PULSE_MODULE_H
+#define PIPEWIRE_PULSE_MODULE_H
+
+#include <spa/param/audio/raw.h>
+#include <spa/utils/hook.h>
+
+#include "internal.h"
+
+struct module;
+struct pw_properties;
+
+struct module_info {
+ const char *name;
+
+ unsigned int load_once:1;
+
+ int (*prepare) (struct module *module);
+ int (*load) (struct module *module);
+ int (*unload) (struct module *module);
+
+ const struct spa_dict *properties;
+ size_t data_size;
+};
+
+#define DEFINE_MODULE_INFO(name) \
+ __attribute__((used)) \
+ __attribute__((retain)) \
+ __attribute__((section("pw_mod_pulse_modules"))) \
+ __attribute__((aligned(__alignof__(struct module_info)))) \
+ const struct module_info name
+
+struct module_events {
+#define VERSION_MODULE_EVENTS 0
+ uint32_t version;
+
+ void (*loaded) (void *data, int result);
+ void (*destroy) (void *data);
+};
+
+struct module {
+ uint32_t index;
+ const char *args;
+ struct pw_properties *props;
+ struct impl *impl;
+ const struct module_info *info;
+ struct spa_hook_list listener_list;
+ void *user_data;
+ unsigned int loaded:1;
+ unsigned int unloading:1;
+};
+
+#define module_emit_loaded(m,r) spa_hook_list_call(&m->listener_list, struct module_events, loaded, 0, r)
+#define module_emit_destroy(m) spa_hook_list_call(&(m)->listener_list, struct module_events, destroy, 0)
+
+struct module *module_create(struct impl *impl, const char *name, const char *args);
+void module_free(struct module *module);
+int module_load(struct module *module);
+int module_unload(struct module *module);
+void module_schedule_unload(struct module *module);
+
+void module_add_listener(struct module *module,
+ struct spa_hook *listener,
+ const struct module_events *events, void *data);
+
+void module_args_add_props(struct pw_properties *props, const char *str);
+int module_args_to_audioinfo(struct impl *impl, struct pw_properties *props, struct spa_audio_info_raw *info);
+bool module_args_parse_bool(const char *str);
+
+#endif
diff --git a/src/modules/module-protocol-pulse/modules/module-always-sink.c b/src/modules/module-protocol-pulse/modules/module-always-sink.c
new file mode 100644
index 0000000..7549c09
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-always-sink.c
@@ -0,0 +1,122 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/pipewire.h>
+
+#include "../module.h"
+
+#define NAME "always-sink"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_always_sink_data {
+ struct module *module;
+
+ struct pw_impl_module *mod;
+ struct spa_hook mod_listener;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_always_sink_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_always_sink_load(struct module *module)
+{
+ struct module_always_sink_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ const char *str;
+ size_t size;
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ if ((str = pw_properties_get(module->props, "sink_name")) != NULL)
+ fprintf(f, " sink.name = \"%s\"", str);
+ fprintf(f, " }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-fallback-sink",
+ args, NULL);
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+ return 0;
+}
+
+static int module_always_sink_unload(struct module *module)
+{
+ struct module_always_sink_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+ return 0;
+}
+
+static const struct spa_dict_item module_always_sink_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Pauli Virtanen <pav@iki.fi>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Always keeps at least one sink loaded even if it's a null one" },
+ { PW_KEY_MODULE_USAGE, "sink_name=<name of sink>" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_always_sink_prepare(struct module * const module)
+{
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ struct module_always_sink_data * const data = module->user_data;
+ data->module = module;
+
+ return 0;
+}
+
+DEFINE_MODULE_INFO(module_always_sink) = {
+ .name = "module-always-sink",
+ .load_once = true,
+ .prepare = module_always_sink_prepare,
+ .load = module_always_sink_load,
+ .unload = module_always_sink_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_always_sink_info),
+ .data_size = sizeof(struct module_always_sink_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-combine-sink.c b/src/modules/module-protocol-pulse/modules/module-combine-sink.c
new file mode 100644
index 0000000..86536f7
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-combine-sink.c
@@ -0,0 +1,341 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright © 2021 Arun Raghavan <arun@asymptotic.io>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/param/audio/format-utils.h>
+#include <spa/utils/json.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/utils.h>
+
+#include "../manager.h"
+#include "../module.h"
+
+#define NAME "combine-sink"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define MAX_SINKS 64 /* ... good enough for anyone */
+
+#define TIMEOUT_SINKS_MSEC 2000
+
+static const struct spa_dict_item module_combine_sink_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Arun Raghavan <arun@asymptotic.io>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Combine multiple sinks into a single sink" },
+ { PW_KEY_MODULE_USAGE, "sink_name=<name of the sink> "
+ "sink_properties=<properties for the sink> "
+ /* not a great name, but for backwards compatibility... */
+ "slaves=<sinks to combine> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<channel map> "
+ "remix=<remix channels> " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct module_combine_sink_data;
+
+struct module_combine_sink_data {
+ struct module *module;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+ struct pw_manager *manager;
+ struct spa_hook manager_listener;
+
+ struct pw_impl_module *mod;
+ struct spa_hook mod_listener;
+
+ char *sink_name;
+ char **sink_names;
+ struct pw_properties *combine_props;
+
+ struct spa_source *sinks_timeout;
+
+ struct spa_audio_info_raw info;
+
+ unsigned int sinks_pending;
+ unsigned int remix:1;
+ unsigned int load_emitted:1;
+ unsigned int start_error:1;
+};
+
+static void check_initialized(struct module_combine_sink_data *data)
+{
+ struct module *module = data->module;
+
+ if (data->load_emitted)
+ return;
+
+ if (data->start_error) {
+ pw_log_debug("module load error");
+ data->load_emitted = true;
+ module_emit_loaded(module, -EIO);
+ } else if (data->sinks_pending == 0) {
+ pw_log_debug("module loaded");
+ data->load_emitted = true;
+ module_emit_loaded(module, 0);
+ }
+}
+
+static void manager_added(void *d, struct pw_manager_object *o)
+{
+ struct module_combine_sink_data *data = d;
+ const char *str;
+ uint32_t val = 0;
+ struct pw_node_info *info;
+
+ if (!spa_streq(o->type, PW_TYPE_INTERFACE_Node) ||
+ (info = o->info) == NULL || info->props == NULL)
+ return;
+
+ str = spa_dict_lookup(info->props, "pulse.module.id");
+ if (str == NULL || !spa_atou32(str, &val, 0) || val != data->module->index)
+ return;
+
+ pw_log_info("found our %s, pending:%d",
+ pw_properties_get(o->props, PW_KEY_NODE_NAME),
+ data->sinks_pending);
+
+ if (!pw_manager_object_is_sink(o)) {
+ if (data->sinks_pending > 0)
+ data->sinks_pending--;
+ }
+ check_initialized(data);
+ return;
+}
+
+static const struct pw_manager_events manager_events = {
+ PW_VERSION_MANAGER_EVENTS,
+ .added = manager_added,
+};
+
+static void on_sinks_timeout(void *d, uint64_t count)
+{
+ struct module_combine_sink_data *data = d;
+
+ if (data->load_emitted)
+ return;
+
+ data->start_error = true;
+ check_initialized(data);
+}
+
+static void module_destroy(void *data)
+{
+ struct module_combine_sink_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_combine_sink_load(struct module *module)
+{
+ struct module_combine_sink_data *data = module->user_data;
+ uint32_t i;
+ FILE *f;
+ char *args;
+ size_t size;
+
+ data->core = pw_context_connect(module->impl->context, NULL, 0);
+ if (data->core == NULL)
+ return -errno;
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ fprintf(f, " node.name = %s", data->sink_name);
+ fprintf(f, " node.description = %s", data->sink_name);
+ if (data->info.rate != 0)
+ fprintf(f, " audio.rate = %u", data->info.rate);
+ if (data->info.channels != 0) {
+ fprintf(f, " audio.channels = %u", data->info.channels);
+ if (!(data->info.flags & SPA_AUDIO_FLAG_UNPOSITIONED)) {
+ fprintf(f, " audio.position = [ ");
+ for (i = 0; i < data->info.channels; i++)
+ fprintf(f, "%s%s", i == 0 ? "" : ",",
+ channel_id2name(data->info.position[i]));
+ fprintf(f, " ]");
+ }
+ }
+ fprintf(f, " combine.props = {");
+ fprintf(f, " pulse.module.id = %u", module->index);
+ pw_properties_serialize_dict(f, &data->combine_props->dict, 0);
+ fprintf(f, " } stream.props = {");
+ if (!data->remix)
+ fprintf(f, " "PW_KEY_STREAM_DONT_REMIX" = true");
+ fprintf(f, " pulse.module.id = %u", module->index);
+ fprintf(f, " } stream.rules = [");
+ if (data->sink_names == NULL) {
+ fprintf(f, " { matches = [ { media.class = \"Audio/Sink\" } ]");
+ fprintf(f, " actions = { create-stream = { } } }");
+ } else {
+ for (i = 0; data->sink_names[i] != NULL; i++) {
+ char name[1024];
+ spa_json_encode_string(name, sizeof(name)-1, data->sink_names[i]);
+ fprintf(f, " { matches = [ { media.class = \"Audio/Sink\" ");
+ fprintf(f, " node.name = %s } ]", name);
+ fprintf(f, " actions = { create-stream = { } } }");
+ }
+ }
+ fprintf(f, " ]");
+ fprintf(f, "}");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-combine-stream",
+ args, NULL);
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ data->manager = pw_manager_new(data->core);
+ if (data->manager == NULL)
+ return -errno;
+
+ pw_manager_add_listener(data->manager, &data->manager_listener,
+ &manager_events, data);
+
+ data->sinks_timeout = pw_loop_add_timer(module->impl->loop, on_sinks_timeout, data);
+ if (data->sinks_timeout) {
+ struct timespec timeout = {0};
+ timeout.tv_sec = TIMEOUT_SINKS_MSEC / 1000;
+ timeout.tv_nsec = (TIMEOUT_SINKS_MSEC % 1000) * SPA_NSEC_PER_MSEC;
+ pw_loop_update_timer(module->impl->loop, data->sinks_timeout, &timeout, NULL, false);
+ }
+ return data->load_emitted ? 0 : SPA_RESULT_RETURN_ASYNC(0);
+}
+
+static int module_combine_sink_unload(struct module *module)
+{
+ struct module_combine_sink_data *d = module->user_data;
+
+ if (d->sinks_timeout != NULL)
+ pw_loop_destroy_source(module->impl->loop, d->sinks_timeout);
+
+ if (d->mod != NULL) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+ if (d->manager != NULL) {
+ spa_hook_remove(&d->manager_listener);
+ pw_manager_destroy(d->manager);
+ }
+ if (d->core != NULL) {
+ spa_hook_remove(&d->core_listener);
+ pw_core_disconnect(d->core);
+ }
+ pw_free_strv(d->sink_names);
+ free(d->sink_name);
+ pw_properties_free(d->combine_props);
+ return 0;
+}
+
+static int module_combine_sink_prepare(struct module * const module)
+{
+ struct module_combine_sink_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *combine_props = NULL;
+ const char *str;
+ char *sink_name = NULL, **sink_names = NULL;
+ struct spa_audio_info_raw info = { 0 };
+ int res;
+ int num_sinks = 0;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ combine_props = pw_properties_new(NULL, NULL);
+
+ if ((str = pw_properties_get(props, "sink_name")) != NULL) {
+ sink_name = strdup(str);
+ pw_properties_set(props, "sink_name", NULL);
+ } else {
+ sink_name = strdup("combined");
+ }
+
+ if ((str = pw_properties_get(module->props, "sink_properties")) != NULL)
+ module_args_add_props(combine_props, str);
+
+ if ((str = pw_properties_get(props, "slaves")) != NULL) {
+ sink_names = pw_split_strv(str, ",", MAX_SINKS, &num_sinks);
+ pw_properties_set(props, "slaves", NULL);
+ }
+ d->remix = true;
+ if ((str = pw_properties_get(props, "remix")) != NULL) {
+ d->remix = pw_properties_parse_bool(str);
+ pw_properties_set(props, "remix", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "adjust_time")) != NULL) {
+ pw_log_info("The `adjust_time` modarg is ignored");
+ pw_properties_set(props, "adjust_time", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "resample_method")) != NULL) {
+ pw_log_info("The `resample_method` modarg is ignored");
+ pw_properties_set(props, "resample_method", NULL);
+ }
+
+ if (module_args_to_audioinfo(module->impl, props, &info) < 0) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ d->module = module;
+ d->info = info;
+ d->sink_name = sink_name;
+ d->sink_names = sink_names;
+ d->sinks_pending = (sink_names == NULL) ? 0 : num_sinks;
+ d->combine_props = combine_props;
+
+ return 0;
+out:
+ free(sink_name);
+ pw_free_strv(sink_names);
+ pw_properties_free(combine_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_combine_sink) = {
+ .name = "module-combine-sink",
+ .prepare = module_combine_sink_prepare,
+ .load = module_combine_sink_load,
+ .unload = module_combine_sink_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_combine_sink_info),
+ .data_size = sizeof(struct module_combine_sink_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-echo-cancel.c b/src/modules/module-protocol-pulse/modules/module-echo-cancel.c
new file mode 100644
index 0000000..c2ab8ad
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-echo-cancel.c
@@ -0,0 +1,276 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright © 2021 Arun Raghavan <arun@asymptotic.io>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/param/audio/format-utils.h>
+#include <spa/utils/hook.h>
+#include <spa/utils/json.h>
+#include <pipewire/pipewire.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "echo-cancel"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_echo_cancel_data {
+ struct module *module;
+
+ struct pw_impl_module *mod;
+ struct spa_hook mod_listener;
+
+ struct pw_properties *props;
+ struct pw_properties *capture_props;
+ struct pw_properties *source_props;
+ struct pw_properties *sink_props;
+ struct pw_properties *playback_props;
+
+ struct spa_audio_info_raw info;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_echo_cancel_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_echo_cancel_load(struct module *module)
+{
+ struct module_echo_cancel_data *data = module->user_data;
+ FILE *f;
+ const char *str;
+ char *args;
+ size_t size;
+ uint32_t i;
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ /* Can't just serialise this dict because the "null" method gets
+ * interpreted as a JSON null */
+ if ((str = pw_properties_get(data->props, "aec.method")))
+ fprintf(f, " aec.method = \"%s\"", str);
+ if ((str = pw_properties_get(data->props, "aec.args")))
+ fprintf(f, " aec.args = \"%s\"", str);
+ if (data->info.rate != 0)
+ fprintf(f, " audio.rate = %u", data->info.rate);
+ if (data->info.channels != 0) {
+ fprintf(f, " audio.channels = %u", data->info.channels);
+ if (!(data->info.flags & SPA_AUDIO_FLAG_UNPOSITIONED)) {
+ fprintf(f, " audio.position = [ ");
+ for (i = 0; i < data->info.channels; i++)
+ fprintf(f, "%s%s", i == 0 ? "" : ",",
+ channel_id2name(data->info.position[i]));
+ fprintf(f, " ]");
+ }
+ }
+ fprintf(f, " capture.props = {");
+ pw_properties_serialize_dict(f, &data->capture_props->dict, 0);
+ fprintf(f, " } source.props = {");
+ pw_properties_serialize_dict(f, &data->source_props->dict, 0);
+ fprintf(f, " } sink.props = {");
+ pw_properties_serialize_dict(f, &data->sink_props->dict, 0);
+ fprintf(f, " } playback.props = {");
+ pw_properties_serialize_dict(f, &data->playback_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-echo-cancel",
+ args, NULL);
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_echo_cancel_unload(struct module *module)
+{
+ struct module_echo_cancel_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ pw_properties_free(d->props);
+ pw_properties_free(d->capture_props);
+ pw_properties_free(d->source_props);
+ pw_properties_free(d->sink_props);
+ pw_properties_free(d->playback_props);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_echo_cancel_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Arun Raghavan <arun@asymptotic.io>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Acoustic echo canceller" },
+ { PW_KEY_MODULE_USAGE, "source_name=<name for the source> "
+ "source_properties=<properties for the source> "
+ "source_master=<name of source to filter> "
+ "sink_name=<name for the sink> "
+ "sink_properties=<properties for the sink> "
+ "sink_master=<name of sink to filter> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<channel map> "
+ "aec_method=<implementation to use> "
+ "aec_args=<parameters for the AEC engine> "
+#if 0
+ /* These are not implemented because they don't
+ * really make sense in the PipeWire context */
+ "format=<sample format> "
+ "adjust_time=<how often to readjust rates in s> "
+ "adjust_threshold=<how much drift to readjust after in ms> "
+ "autoloaded=<set if this module is being loaded automatically> "
+ "save_aec=<save AEC data in /tmp> "
+ "use_volume_sharing=<yes or no> "
+ "use_master_format=<yes or no> "
+#endif
+ },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_echo_cancel_prepare(struct module * const module)
+{
+ struct module_echo_cancel_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *aec_props = NULL, *sink_props = NULL, *source_props = NULL;
+ struct pw_properties *playback_props = NULL, *capture_props = NULL;
+ const char *str;
+ struct spa_audio_info_raw info = { 0 };
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ aec_props = pw_properties_new(NULL, NULL);
+ capture_props = pw_properties_new(NULL, NULL);
+ source_props = pw_properties_new(NULL, NULL);
+ sink_props = pw_properties_new(NULL, NULL);
+ playback_props = pw_properties_new(NULL, NULL);
+ if (!aec_props || !source_props || !sink_props || !capture_props || !playback_props) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ if ((str = pw_properties_get(props, "source_name")) != NULL) {
+ pw_properties_set(source_props, PW_KEY_NODE_NAME, str);
+ pw_properties_set(props, "source_name", NULL);
+ } else {
+ pw_properties_set(source_props, PW_KEY_NODE_NAME, "echo-cancel-source");
+ }
+
+ if ((str = pw_properties_get(props, "sink_name")) != NULL) {
+ pw_properties_set(sink_props, PW_KEY_NODE_NAME, str);
+ pw_properties_set(props, "sink_name", NULL);
+ } else {
+ pw_properties_set(sink_props, PW_KEY_NODE_NAME, "echo-cancel-sink");
+ }
+
+ if ((str = pw_properties_get(props, "source_master")) != NULL) {
+ if (spa_strendswith(str, ".monitor")) {
+ pw_properties_setf(capture_props, PW_KEY_TARGET_OBJECT,
+ "%.*s", (int)strlen(str)-8, str);
+ pw_properties_set(capture_props, PW_KEY_STREAM_CAPTURE_SINK,
+ "true");
+ } else {
+ pw_properties_set(capture_props, PW_KEY_TARGET_OBJECT, str);
+ }
+ pw_properties_set(props, "source_master", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "sink_master")) != NULL) {
+ pw_properties_set(playback_props, PW_KEY_TARGET_OBJECT, str);
+ pw_properties_set(props, "sink_master", NULL);
+ }
+
+ if (module_args_to_audioinfo(module->impl, props, &info) < 0) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ if ((str = pw_properties_get(props, "source_properties")) != NULL) {
+ module_args_add_props(source_props, str);
+ pw_properties_set(props, "source_properties", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "sink_properties")) != NULL) {
+ module_args_add_props(sink_props, str);
+ pw_properties_set(props, "sink_properties", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "aec_method")) != NULL) {
+ pw_properties_set(aec_props, "aec.method", str);
+ pw_properties_set(props, "aec_method", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "aec_args")) != NULL) {
+ pw_properties_set(aec_props, "aec.args", str);
+ pw_properties_set(props, "aec_args", NULL);
+ }
+
+ d->module = module;
+ d->props = aec_props;
+ d->capture_props = capture_props;
+ d->source_props = source_props;
+ d->sink_props = sink_props;
+ d->playback_props = playback_props;
+ d->info = info;
+
+ return 0;
+out:
+ pw_properties_free(aec_props);
+ pw_properties_free(playback_props);
+ pw_properties_free(sink_props);
+ pw_properties_free(source_props);
+ pw_properties_free(capture_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_echo_cancel) = {
+ .name = "module-echo-cancel",
+ .prepare = module_echo_cancel_prepare,
+ .load = module_echo_cancel_load,
+ .unload = module_echo_cancel_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_echo_cancel_info),
+ .data_size = sizeof(struct module_echo_cancel_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-gsettings.c b/src/modules/module-protocol-pulse/modules/module-gsettings.c
new file mode 100644
index 0000000..36e216b
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-gsettings.c
@@ -0,0 +1,298 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <gio/gio.h>
+#include <glib.h>
+
+#include <spa/debug/mem.h>
+#include <pipewire/pipewire.h>
+#include <pipewire/thread.h>
+
+#include "../module.h"
+
+#define NAME "gsettings"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define PA_GSETTINGS_MODULE_GROUP_SCHEMA "org.freedesktop.pulseaudio.module-group"
+#define PA_GSETTINGS_MODULE_GROUPS_SCHEMA "org.freedesktop.pulseaudio.module-groups"
+#define PA_GSETTINGS_MODULE_GROUPS_PATH "/org/freedesktop/pulseaudio/module-groups/"
+
+#define MAX_MODULES 10
+
+struct module_gsettings_data {
+ struct module *module;
+
+ GMainContext *context;
+ GMainLoop *loop;
+ struct spa_thread *thr;
+
+ GSettings *settings;
+ gchar **group_names;
+
+ struct spa_list groups;
+};
+
+struct group {
+ struct spa_list link;
+ char *name;
+ struct module *module;
+ struct spa_hook module_listener;
+};
+
+struct info {
+ bool enabled;
+ char *name;
+ char *module[MAX_MODULES];
+ char *args[MAX_MODULES];
+};
+
+static void clean_info(const struct info *info)
+{
+ int i;
+ for (i = 0; i < MAX_MODULES; i++) {
+ g_free(info->module[i]);
+ g_free(info->args[i]);
+ }
+ g_free(info->name);
+}
+
+static void unload_module(struct module_gsettings_data *d, struct group *g)
+{
+ spa_list_remove(&g->link);
+ g_free(g->name);
+ if (g->module)
+ module_unload(g->module);
+ free(g);
+}
+
+static void unload_group(struct module_gsettings_data *d, const char *name)
+{
+ struct group *g, *t;
+ spa_list_for_each_safe(g, t, &d->groups, link) {
+ if (spa_streq(g->name, name))
+ unload_module(d, g);
+ }
+}
+static void module_destroy(void *data)
+{
+ struct group *g = data;
+ if (g->module) {
+ spa_hook_remove(&g->module_listener);
+ g->module = NULL;
+ }
+}
+
+static const struct module_events module_gsettings_events = {
+ VERSION_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int load_group(struct module_gsettings_data *d, const struct info *info)
+{
+ struct group *g;
+ int i, res;
+
+ for (i = 0; i < MAX_MODULES; i++) {
+ if (info->module[i] == NULL || strlen(info->module[i]) <= 0)
+ break;
+
+ g = calloc(1, sizeof(struct group));
+ if (g == NULL)
+ return -errno;
+
+ g->name = strdup(info->name);
+ g->module = module_create(d->module->impl, info->module[i], info->args[i]);
+ if (g->module == NULL) {
+ pw_log_info("can't create module:%s args:%s: %m",
+ info->module[i], info->args[i]);
+ } else {
+ module_add_listener(g->module, &g->module_listener,
+ &module_gsettings_events, g);
+ if ((res = module_load(g->module)) < 0) {
+ pw_log_warn("can't load module:%s args:%s: %s",
+ info->module[i], info->args[i],
+ spa_strerror(res));
+ }
+ }
+ spa_list_append(&d->groups, &g->link);
+ }
+ return 0;
+}
+
+static int
+do_handle_info(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct module_gsettings_data *d = user_data;
+ const struct info *info = data;
+
+ unload_group(d, info->name);
+ if (info->enabled)
+ load_group(d, info);
+
+ clean_info(info);
+ return 0;
+}
+
+static void handle_module_group(struct module_gsettings_data *d, gchar *name)
+{
+ struct impl *impl = d->module->impl;
+ GSettings *settings;
+ gchar p[1024];
+ struct info info;
+ int i;
+
+ snprintf(p, sizeof(p), PA_GSETTINGS_MODULE_GROUPS_PATH"%s/", name);
+
+ settings = g_settings_new_with_path(PA_GSETTINGS_MODULE_GROUP_SCHEMA, p);
+ if (settings == NULL)
+ return;
+
+ spa_zero(info);
+ info.name = strdup(p);
+ info.enabled = g_settings_get_boolean(settings, "enabled");
+
+ for (i = 0; i < MAX_MODULES; i++) {
+ snprintf(p, sizeof(p), "name%d", i);
+ info.module[i] = g_settings_get_string(settings, p);
+
+ snprintf(p, sizeof(p), "args%i", i);
+ info.args[i] = g_settings_get_string(settings, p);
+ }
+ pw_loop_invoke(impl->loop, do_handle_info, 0,
+ &info, sizeof(info), false, d);
+
+ g_object_unref(G_OBJECT(settings));
+}
+
+static void module_group_callback(GSettings *settings, gchar *key, gpointer user_data)
+{
+ struct module_gsettings_data *d = g_object_get_data(G_OBJECT(settings), "module-data");
+ handle_module_group(d, user_data);
+}
+
+static void *do_loop(void *user_data)
+{
+ struct module_gsettings_data *d = user_data;
+
+ pw_log_info("enter");
+ g_main_context_push_thread_default(d->context);
+
+ d->loop = g_main_loop_new(d->context, FALSE);
+
+ g_main_loop_run(d->loop);
+
+ g_main_context_pop_thread_default(d->context);
+ g_main_loop_unref (d->loop);
+ d->loop = NULL;
+ pw_log_info("leave");
+
+ return NULL;
+}
+
+static int module_gsettings_load(struct module *module)
+{
+ struct module_gsettings_data *data = module->user_data;
+ gchar **name;
+
+ data->context = g_main_context_new();
+ g_main_context_push_thread_default(data->context);
+
+ data->settings = g_settings_new(PA_GSETTINGS_MODULE_GROUPS_SCHEMA);
+ if (data->settings == NULL)
+ return -EIO;
+
+ data->group_names = g_settings_list_children(data->settings);
+
+ for (name = data->group_names; *name; name++) {
+ GSettings *child = g_settings_get_child(data->settings, *name);
+ /* The child may have been removed between the
+ * g_settings_list_children() and g_settings_get_child() calls. */
+ if (child == NULL)
+ continue;
+
+ g_object_set_data(G_OBJECT(child), "module-data", data);
+ g_signal_connect(child, "changed", (GCallback) module_group_callback, *name);
+ handle_module_group(data, *name);
+ }
+ g_main_context_pop_thread_default(data->context);
+
+ data->thr = pw_thread_utils_create(NULL, do_loop, data);
+ return 0;
+}
+
+static gboolean
+do_stop(gpointer data)
+{
+ struct module_gsettings_data *d = data;
+ if (d->loop)
+ g_main_loop_quit(d->loop);
+ return FALSE;
+}
+
+static int module_gsettings_unload(struct module *module)
+{
+ struct module_gsettings_data *d = module->user_data;
+ struct group *g;
+
+ g_main_context_invoke(d->context, do_stop, d);
+ pw_thread_utils_join(d->thr, NULL);
+ g_main_context_unref(d->context);
+
+ spa_list_consume(g, &d->groups, link)
+ unload_module(d, g);
+
+ g_strfreev(d->group_names);
+ g_object_unref(G_OBJECT(d->settings));
+ return 0;
+}
+
+static int module_gsettings_prepare(struct module * const module)
+{
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ struct module_gsettings_data * const data = module->user_data;
+ spa_list_init(&data->groups);
+ data->module = module;
+
+ return 0;
+}
+
+static const struct spa_dict_item module_gsettings_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "GSettings Adapter" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+DEFINE_MODULE_INFO(module_gsettings) = {
+ .name = "module-gsettings",
+ .load_once = true,
+ .prepare = module_gsettings_prepare,
+ .load = module_gsettings_load,
+ .unload = module_gsettings_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_gsettings_info),
+ .data_size = sizeof(struct module_gsettings_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-ladspa-sink.c b/src/modules/module-protocol-pulse/modules/module-ladspa-sink.c
new file mode 100644
index 0000000..3b3b700
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-ladspa-sink.c
@@ -0,0 +1,257 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/hook.h>
+#include <spa/utils/json.h>
+#include <spa/param/audio/format-utils.h>
+
+#include <pipewire/pipewire.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "ladspa-sink"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_ladspa_sink_data {
+ struct module *module;
+
+ struct pw_impl_module *mod;
+ struct spa_hook mod_listener;
+
+ struct pw_properties *capture_props;
+ struct pw_properties *playback_props;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_ladspa_sink_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_ladspa_sink_load(struct module *module)
+{
+ struct module_ladspa_sink_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ const char *str, *plugin, *label;
+ size_t size;
+
+ if ((plugin = pw_properties_get(module->props, "plugin")) == NULL)
+ return -EINVAL;
+ if ((label = pw_properties_get(module->props, "label")) == NULL)
+ return -EINVAL;
+
+ pw_properties_setf(data->capture_props, PW_KEY_NODE_GROUP, "ladspa-sink-%u", module->index);
+ pw_properties_setf(data->playback_props, PW_KEY_NODE_GROUP, "ladspa-sink-%u", module->index);
+ pw_properties_setf(data->capture_props, "pulse.module.id", "%u", module->index);
+ pw_properties_setf(data->playback_props, "pulse.module.id", "%u", module->index);
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ pw_properties_serialize_dict(f, &module->props->dict, 0);
+ fprintf(f, " filter.graph = {");
+ fprintf(f, " nodes = [ { ");
+ fprintf(f, " type = ladspa ");
+ fprintf(f, " plugin = \"%s\" ", plugin);
+ fprintf(f, " label = \"%s\" ", label);
+ if ((str = pw_properties_get(module->props, "control")) != NULL) {
+ size_t len;
+ const char *s, *state = NULL;
+ int count = 0;
+
+ fprintf(f, " control = {");
+ while ((s = pw_split_walk(str, ", ", &len, &state))) {
+ fprintf(f, " \"%d\" = %.*s", count, (int)len, s);
+ count++;
+ }
+ fprintf(f, " }");
+ }
+ fprintf(f, " } ]");
+ if ((str = pw_properties_get(module->props, "inputs")) != NULL)
+ fprintf(f, " inputs = [ %s ] ", str);
+ if ((str = pw_properties_get(module->props, "outputs")) != NULL)
+ fprintf(f, " outputs = [ %s ] ", str);
+ fprintf(f, " }");
+ fprintf(f, " capture.props = {");
+ pw_properties_serialize_dict(f, &data->capture_props->dict, 0);
+ fprintf(f, " } playback.props = {");
+ pw_properties_serialize_dict(f, &data->playback_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-filter-chain",
+ args, NULL);
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_ladspa_sink_unload(struct module *module)
+{
+ struct module_ladspa_sink_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ pw_properties_free(d->capture_props);
+ pw_properties_free(d->playback_props);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_ladspa_sink_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Virtual LADSPA sink" },
+ { PW_KEY_MODULE_USAGE,
+ "sink_name=<name for the sink> "
+ "sink_properties=<properties for the sink> "
+ "sink_input_properties=<properties for the sink input> "
+ "master=<name of sink to filter> "
+ "sink_master=<name of sink to filter> "
+ "format=<sample format> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<input channel map> "
+ "plugin=<ladspa plugin name> "
+ "label=<ladspa plugin label> "
+ "control=<comma separated list of input control values> "
+ "input_ladspaport_map=<comma separated list of input LADSPA port names> "
+ "output_ladspaport_map=<comma separated list of output LADSPA port names> "},
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static void position_to_props(struct spa_audio_info_raw *info, struct pw_properties *props)
+{
+ char *s, *p;
+ uint32_t i;
+
+ pw_properties_setf(props, SPA_KEY_AUDIO_CHANNELS, "%u", info->channels);
+ p = s = alloca(info->channels * 8);
+ for (i = 0; i < info->channels; i++)
+ p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ",",
+ channel_id2name(info->position[i]));
+ pw_properties_set(props, SPA_KEY_AUDIO_POSITION, s);
+}
+
+static int module_ladspa_sink_prepare(struct module * const module)
+{
+ struct module_ladspa_sink_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *playback_props = NULL, *capture_props = NULL;
+ const char *str;
+ struct spa_audio_info_raw capture_info = { 0 };
+ struct spa_audio_info_raw playback_info = { 0 };
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ capture_props = pw_properties_new(NULL, NULL);
+ playback_props = pw_properties_new(NULL, NULL);
+ if (!capture_props || !playback_props) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ if ((str = pw_properties_get(props, "sink_name")) != NULL) {
+ pw_properties_set(capture_props, PW_KEY_NODE_NAME, str);
+ pw_properties_set(props, "sink_name", NULL);
+ }
+ if ((str = pw_properties_get(props, "sink_properties")) != NULL) {
+ module_args_add_props(capture_props, str);
+ pw_properties_set(props, "sink_properties", NULL);
+ }
+ if (pw_properties_get(capture_props, PW_KEY_MEDIA_CLASS) == NULL)
+ pw_properties_set(capture_props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
+ if (pw_properties_get(capture_props, PW_KEY_DEVICE_CLASS) == NULL)
+ pw_properties_set(capture_props, PW_KEY_DEVICE_CLASS, "filter");
+
+ if ((str = pw_properties_get(capture_props, PW_KEY_NODE_DESCRIPTION)) == NULL) {
+ str = pw_properties_get(capture_props, PW_KEY_NODE_NAME);
+ pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION,
+ "%s Sink", str);
+ } else {
+ pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, str);
+ }
+
+ if ((str = pw_properties_get(props, "master")) != NULL ||
+ (str = pw_properties_get(props, "sink_master")) != NULL) {
+ pw_properties_set(playback_props, PW_KEY_TARGET_OBJECT, str);
+ pw_properties_set(props, "master", NULL);
+ }
+
+ if (module_args_to_audioinfo(module->impl, props, &capture_info) < 0) {
+ res = -EINVAL;
+ goto out;
+ }
+ playback_info = capture_info;
+
+ position_to_props(&capture_info, capture_props);
+ position_to_props(&playback_info, playback_props);
+
+ if (pw_properties_get(playback_props, PW_KEY_NODE_PASSIVE) == NULL)
+ pw_properties_set(playback_props, PW_KEY_NODE_PASSIVE, "true");
+
+ d->module = module;
+ d->capture_props = capture_props;
+ d->playback_props = playback_props;
+
+ return 0;
+out:
+ pw_properties_free(playback_props);
+ pw_properties_free(capture_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_ladspa_sink) = {
+ .name = "module-ladspa-sink",
+ .prepare = module_ladspa_sink_prepare,
+ .load = module_ladspa_sink_load,
+ .unload = module_ladspa_sink_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_ladspa_sink_info),
+ .data_size = sizeof(struct module_ladspa_sink_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-ladspa-source.c b/src/modules/module-protocol-pulse/modules/module-ladspa-source.c
new file mode 100644
index 0000000..9533350
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-ladspa-source.c
@@ -0,0 +1,265 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/hook.h>
+#include <spa/utils/json.h>
+#include <spa/param/audio/format-utils.h>
+
+#include <pipewire/pipewire.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "ladspa-source"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_ladspa_source_data {
+ struct module *module;
+
+ struct pw_impl_module *mod;
+ struct spa_hook mod_listener;
+
+ struct pw_properties *capture_props;
+ struct pw_properties *playback_props;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_ladspa_source_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_ladspa_source_load(struct module *module)
+{
+ struct module_ladspa_source_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ const char *str, *plugin, *label;
+ size_t size;
+
+ if ((plugin = pw_properties_get(module->props, "plugin")) == NULL)
+ return -EINVAL;
+ if ((label = pw_properties_get(module->props, "label")) == NULL)
+ return -EINVAL;
+
+ pw_properties_setf(data->capture_props, PW_KEY_NODE_GROUP, "ladspa-source-%u", module->index);
+ pw_properties_setf(data->playback_props, PW_KEY_NODE_GROUP, "ladspa-source-%u", module->index);
+ pw_properties_setf(data->capture_props, "pulse.module.id", "%u", module->index);
+ pw_properties_setf(data->playback_props, "pulse.module.id", "%u", module->index);
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ pw_properties_serialize_dict(f, &module->props->dict, 0);
+ fprintf(f, " filter.graph = {");
+ fprintf(f, " nodes = [ { ");
+ fprintf(f, " type = ladspa ");
+ fprintf(f, " plugin = \"%s\" ", plugin);
+ fprintf(f, " label = \"%s\" ", label);
+ if ((str = pw_properties_get(module->props, "control")) != NULL) {
+ size_t len;
+ const char *s, *state = NULL;
+ int count = 0;
+
+ fprintf(f, " control = {");
+ while ((s = pw_split_walk(str, ", ", &len, &state))) {
+ fprintf(f, " \"%d\" = %.*s", count, (int)len, s);
+ count++;
+ }
+ fprintf(f, " }");
+ }
+ fprintf(f, " } ]");
+ if ((str = pw_properties_get(module->props, "inputs")) != NULL)
+ fprintf(f, " inputs = [ %s ] ", str);
+ if ((str = pw_properties_get(module->props, "outputs")) != NULL)
+ fprintf(f, " outputs = [ %s ] ", str);
+ fprintf(f, " }");
+ fprintf(f, " capture.props = {");
+ pw_properties_serialize_dict(f, &data->capture_props->dict, 0);
+ fprintf(f, " } playback.props = {");
+ pw_properties_serialize_dict(f, &data->playback_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-filter-chain",
+ args, NULL);
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_ladspa_source_unload(struct module *module)
+{
+ struct module_ladspa_source_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ pw_properties_free(d->capture_props);
+ pw_properties_free(d->playback_props);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_ladspa_source_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Virtual LADSPA source" },
+ { PW_KEY_MODULE_USAGE,
+ "source_name=<name for the source> "
+ "source_properties=<properties for the source> "
+ "source_output_properties=<properties for the source output> "
+ "master=<name of source to filter> "
+ "source_master=<name of source to filter> "
+ "format=<sample format> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<input channel map> "
+ "plugin=<ladspa plugin name> "
+ "label=<ladspa plugin label> "
+ "control=<comma separated list of input control values> "
+ "input_ladspaport_map=<comma separated list of input LADSPA port names> "
+ "output_ladspaport_map=<comma separated list of output LADSPA port names> "},
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static void position_to_props(struct spa_audio_info_raw *info, struct pw_properties *props)
+{
+ char *s, *p;
+ uint32_t i;
+
+ pw_properties_setf(props, SPA_KEY_AUDIO_CHANNELS, "%u", info->channels);
+ p = s = alloca(info->channels * 8);
+ for (i = 0; i < info->channels; i++)
+ p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ",",
+ channel_id2name(info->position[i]));
+ pw_properties_set(props, SPA_KEY_AUDIO_POSITION, s);
+}
+
+static int module_ladspa_source_prepare(struct module * const module)
+{
+ struct module_ladspa_source_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *playback_props = NULL, *capture_props = NULL;
+ const char *str;
+ struct spa_audio_info_raw capture_info = { 0 };
+ struct spa_audio_info_raw playback_info = { 0 };
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ capture_props = pw_properties_new(NULL, NULL);
+ playback_props = pw_properties_new(NULL, NULL);
+ if (!capture_props || !playback_props) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ if ((str = pw_properties_get(props, "source_name")) != NULL) {
+ pw_properties_set(playback_props, PW_KEY_NODE_NAME, str);
+ pw_properties_set(props, "source_name", NULL);
+ }
+ if ((str = pw_properties_get(props, "source_properties")) != NULL) {
+ module_args_add_props(playback_props, str);
+ pw_properties_set(props, "source_properties", NULL);
+ }
+ if (pw_properties_get(playback_props, PW_KEY_MEDIA_CLASS) == NULL)
+ pw_properties_set(playback_props, PW_KEY_MEDIA_CLASS, "Audio/Source");
+ if (pw_properties_get(playback_props, PW_KEY_DEVICE_CLASS) == NULL)
+ pw_properties_set(playback_props, PW_KEY_DEVICE_CLASS, "filter");
+
+ if ((str = pw_properties_get(playback_props, PW_KEY_NODE_DESCRIPTION)) == NULL) {
+ str = pw_properties_get(playback_props, PW_KEY_NODE_NAME);
+ pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION,
+ "%s Source", str);
+ } else {
+ pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, str);
+ }
+
+ if ((str = pw_properties_get(props, "master")) != NULL ||
+ (str = pw_properties_get(props, "source_master")) != NULL) {
+ if (spa_strendswith(str, ".monitor")) {
+ pw_properties_setf(capture_props, PW_KEY_TARGET_OBJECT,
+ "%.*s", (int)strlen(str)-8, str);
+ pw_properties_set(capture_props, PW_KEY_STREAM_CAPTURE_SINK,
+ "true");
+ } else {
+ pw_properties_set(capture_props, PW_KEY_TARGET_OBJECT, str);
+ }
+ pw_properties_set(props, "source_master", NULL);
+ pw_properties_set(props, "master", NULL);
+ }
+
+ if (module_args_to_audioinfo(module->impl, props, &playback_info) < 0) {
+ res = -EINVAL;
+ goto out;
+ }
+ capture_info = playback_info;
+
+ position_to_props(&capture_info, capture_props);
+ position_to_props(&playback_info, playback_props);
+
+ if (pw_properties_get(capture_props, PW_KEY_NODE_PASSIVE) == NULL)
+ pw_properties_set(capture_props, PW_KEY_NODE_PASSIVE, "true");
+
+ d->module = module;
+ d->capture_props = capture_props;
+ d->playback_props = playback_props;
+
+ return 0;
+out:
+ pw_properties_free(playback_props);
+ pw_properties_free(capture_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_ladspa_source) = {
+ .name = "module-ladspa-source",
+ .prepare = module_ladspa_source_prepare,
+ .load = module_ladspa_source_load,
+ .unload = module_ladspa_source_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_ladspa_source_info),
+ .data_size = sizeof(struct module_ladspa_source_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-loopback.c b/src/modules/module-protocol-pulse/modules/module-loopback.c
new file mode 100644
index 0000000..614ee50
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-loopback.c
@@ -0,0 +1,245 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright © 2021 Arun Raghavan <arun@asymptotic.io>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/param/audio/format-utils.h>
+#include <spa/utils/hook.h>
+#include <spa/utils/json.h>
+#include <pipewire/pipewire.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "loopback"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_loopback_data {
+ struct module *module;
+
+ struct pw_impl_module *mod;
+ struct spa_hook mod_listener;
+
+ struct pw_properties *capture_props;
+ struct pw_properties *playback_props;
+
+ struct spa_audio_info_raw info;
+ uint32_t latency_msec;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_loopback_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_loopback_load(struct module *module)
+{
+ struct module_loopback_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ size_t size, i;
+ char val[256];
+
+ pw_properties_setf(data->capture_props, PW_KEY_NODE_GROUP, "loopback-%u", module->index);
+ pw_properties_setf(data->playback_props, PW_KEY_NODE_GROUP, "loopback-%u", module->index);
+ pw_properties_setf(data->capture_props, "pulse.module.id", "%u", module->index);
+ pw_properties_setf(data->playback_props, "pulse.module.id", "%u", module->index);
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ if (data->info.channels != 0) {
+ fprintf(f, " audio.channels = %u", data->info.channels);
+ if (!(data->info.flags & SPA_AUDIO_FLAG_UNPOSITIONED)) {
+ fprintf(f, " audio.position = [ ");
+ for (i = 0; i < data->info.channels; i++)
+ fprintf(f, "%s%s", i == 0 ? "" : ",",
+ channel_id2name(data->info.position[i]));
+ fprintf(f, " ]");
+ }
+ }
+ if (data->latency_msec != 0)
+ fprintf(f, " target.delay.sec = %s",
+ spa_json_format_float(val, sizeof(val),
+ data->latency_msec / 1000.0f));
+ fprintf(f, " capture.props = {");
+ pw_properties_serialize_dict(f, &data->capture_props->dict, 0);
+ fprintf(f, " } playback.props = {");
+ pw_properties_serialize_dict(f, &data->playback_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-loopback",
+ args, NULL);
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_loopback_unload(struct module *module)
+{
+ struct module_loopback_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ pw_properties_free(d->capture_props);
+ pw_properties_free(d->playback_props);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_loopback_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Arun Raghavan <arun@asymptotic.io>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Loopback from source to sink" },
+ { PW_KEY_MODULE_USAGE, "source=<source to connect to> "
+ "sink=<sink to connect to> "
+ "latency_msec=<latency in ms> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<channel map> "
+ "sink_input_properties=<proplist> "
+ "source_output_properties=<proplist> "
+ "source_dont_move=<boolean> "
+ "sink_dont_move=<boolean> "
+ "remix=<remix channels?> " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_loopback_prepare(struct module * const module)
+{
+ struct module_loopback_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *playback_props = NULL, *capture_props = NULL;
+ const char *str;
+ struct spa_audio_info_raw info = { 0 };
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ capture_props = pw_properties_new(NULL, NULL);
+ playback_props = pw_properties_new(NULL, NULL);
+ if (!capture_props || !playback_props) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ /* The following modargs are not implemented:
+ * adjust_time, max_latency_msec, fast_adjust_threshold_msec: these are just not relevant
+ */
+
+ if ((str = pw_properties_get(props, "source")) != NULL) {
+ if (spa_strendswith(str, ".monitor")) {
+ pw_properties_setf(capture_props, PW_KEY_TARGET_OBJECT,
+ "%.*s", (int)strlen(str)-8, str);
+ pw_properties_set(capture_props, PW_KEY_STREAM_CAPTURE_SINK,
+ "true");
+ } else {
+ pw_properties_set(capture_props, PW_KEY_TARGET_OBJECT, str);
+ }
+ pw_properties_set(props, "source", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "sink")) != NULL) {
+ pw_properties_set(playback_props, PW_KEY_TARGET_OBJECT, str);
+ pw_properties_set(props, "sink", NULL);
+ }
+
+ if (module_args_to_audioinfo(module->impl, props, &info) < 0) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ if ((str = pw_properties_get(props, "source_dont_move")) != NULL) {
+ pw_properties_set(capture_props, PW_KEY_NODE_DONT_RECONNECT, str);
+ pw_properties_set(props, "source_dont_move", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "sink_dont_move")) != NULL) {
+ pw_properties_set(playback_props, PW_KEY_NODE_DONT_RECONNECT, str);
+ pw_properties_set(props, "sink_dont_move", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "remix")) != NULL) {
+ /* Note that the boolean is inverted */
+ pw_properties_set(playback_props, PW_KEY_STREAM_DONT_REMIX,
+ module_args_parse_bool(str) ? "false" : "true");
+ pw_properties_set(props, "remix", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "latency_msec")) != NULL)
+ d->latency_msec = atoi(str);
+
+ if ((str = pw_properties_get(props, "sink_input_properties")) != NULL) {
+ module_args_add_props(playback_props, str);
+ pw_properties_set(props, "sink_input_properties", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "source_output_properties")) != NULL) {
+ module_args_add_props(capture_props, str);
+ pw_properties_set(props, "source_output_properties", NULL);
+ }
+
+ d->module = module;
+ d->capture_props = capture_props;
+ d->playback_props = playback_props;
+ d->info = info;
+
+ return 0;
+out:
+ pw_properties_free(playback_props);
+ pw_properties_free(capture_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_loopback) = {
+ .name = "module-loopback",
+ .prepare = module_loopback_prepare,
+ .load = module_loopback_load,
+ .unload = module_loopback_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_loopback_info),
+ .data_size = sizeof(struct module_loopback_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-native-protocol-tcp.c b/src/modules/module-protocol-pulse/modules/module-native-protocol-tcp.c
new file mode 100644
index 0000000..e8f4c14
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-native-protocol-tcp.c
@@ -0,0 +1,127 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/pipewire.h>
+
+#include "../module.h"
+#include "../pulse-server.h"
+#include "../server.h"
+
+#define NAME "protocol-tcp"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_native_protocol_tcp_data {
+ struct module *module;
+ struct pw_array servers;
+};
+
+static int module_native_protocol_tcp_load(struct module *module)
+{
+ struct module_native_protocol_tcp_data *data = module->user_data;
+ struct impl *impl = module->impl;
+ const char *address;
+ int res;
+
+ if ((address = pw_properties_get(module->props, "pulse.tcp")) == NULL)
+ return -EIO;
+
+ pw_array_init(&data->servers, sizeof(struct server *));
+
+ res = servers_create_and_start(impl, address, &data->servers);
+ if (res < 0)
+ return res;
+
+ return 0;
+}
+
+static int module_native_protocol_tcp_unload(struct module *module)
+{
+ struct module_native_protocol_tcp_data *d = module->user_data;
+ struct server **s;
+
+ pw_array_for_each (s, &d->servers)
+ server_free(*s);
+
+ pw_array_clear(&d->servers);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_native_protocol_tcp_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Native protocol (TCP sockets)" },
+ { PW_KEY_MODULE_USAGE, "port=<TCP port number> "
+ "listen=<address to listen on> "
+ "auth-anonymous=<don't check for cookies?>"},
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_native_protocol_tcp_prepare(struct module * const module)
+{
+ struct module_native_protocol_tcp_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ const char *port, *listen, *auth;
+ FILE *f;
+ char *args;
+ size_t size;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ if ((port = pw_properties_get(props, "port")) == NULL)
+ port = SPA_STRINGIFY(PW_PROTOCOL_PULSE_DEFAULT_PORT);
+
+ listen = pw_properties_get(props, "listen");
+
+ auth = pw_properties_get(props, "auth-anonymous");
+
+ f = open_memstream(&args, &size);
+ if (f == NULL)
+ return -errno;
+
+ fprintf(f, "[ { ");
+ fprintf(f, " \"address\": \"tcp:%s%s%s\" ",
+ listen ? listen : "", listen ? ":" : "", port);
+ if (auth && module_args_parse_bool(auth))
+ fprintf(f, " \"client.access\": \"unrestricted\" ");
+ fprintf(f, "} ]");
+ fclose(f);
+
+ pw_properties_set(props, "pulse.tcp", args);
+ free(args);
+
+ d->module = module;
+
+ return 0;
+}
+
+DEFINE_MODULE_INFO(module_native_protocol_tcp) = {
+ .name = "module-native-protocol-tcp",
+ .prepare = module_native_protocol_tcp_prepare,
+ .load = module_native_protocol_tcp_load,
+ .unload = module_native_protocol_tcp_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_native_protocol_tcp_info),
+ .data_size = sizeof(struct module_native_protocol_tcp_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-null-sink.c b/src/modules/module-protocol-pulse/modules/module-null-sink.c
new file mode 100644
index 0000000..caae598
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-null-sink.c
@@ -0,0 +1,228 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Georges Basile Stavracas Neto
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/pipewire.h>
+
+#include "../manager.h"
+#include "../module.h"
+
+#define NAME "null-sink"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_null_sink_data {
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ struct pw_proxy *proxy;
+ struct spa_hook proxy_listener;
+};
+
+static void module_null_sink_proxy_removed(void *data)
+{
+ struct module *module = data;
+ struct module_null_sink_data *d = module->user_data;
+ pw_proxy_destroy(d->proxy);
+}
+
+static void module_null_sink_proxy_destroy(void *data)
+{
+ struct module *module = data;
+ struct module_null_sink_data *d = module->user_data;
+
+ pw_log_info("proxy %p destroy", d->proxy);
+
+ spa_hook_remove(&d->proxy_listener);
+ d->proxy = NULL;
+
+ module_schedule_unload(module);
+}
+
+static void module_null_sink_proxy_bound(void *data, uint32_t global_id)
+{
+ struct module *module = data;
+ struct module_null_sink_data *d = module->user_data;
+
+ pw_log_info("proxy %p bound", d->proxy);
+
+ module_emit_loaded(module, 0);
+}
+
+static void module_null_sink_proxy_error(void *data, int seq, int res, const char *message)
+{
+ struct module *module = data;
+ struct module_null_sink_data *d = module->user_data;
+
+ pw_log_info("proxy %p error %d", d->proxy, res);
+
+ pw_proxy_destroy(d->proxy);
+}
+
+static const struct pw_proxy_events proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .removed = module_null_sink_proxy_removed,
+ .bound = module_null_sink_proxy_bound,
+ .error = module_null_sink_proxy_error,
+ .destroy = module_null_sink_proxy_destroy,
+};
+
+static void module_null_sink_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct module *module = data;
+
+ pw_log_warn("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ module_schedule_unload(module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = module_null_sink_core_error,
+};
+
+static int module_null_sink_load(struct module *module)
+{
+ struct module_null_sink_data *d = module->user_data;
+
+ d->core = pw_context_connect(module->impl->context, NULL, 0);
+ if (d->core == NULL)
+ return -errno;
+
+ pw_core_add_listener(d->core, &d->core_listener, &core_events, module);
+
+ pw_properties_setf(module->props, "pulse.module.id", "%u", module->index);
+
+ d->proxy = pw_core_create_object(d->core,
+ "adapter", PW_TYPE_INTERFACE_Node, PW_VERSION_NODE,
+ module->props ? &module->props->dict : NULL, 0);
+ if (d->proxy == NULL)
+ return -errno;
+
+ pw_proxy_add_listener(d->proxy, &d->proxy_listener, &proxy_events, module);
+
+ return SPA_RESULT_RETURN_ASYNC(0);
+}
+
+static int module_null_sink_unload(struct module *module)
+{
+ struct module_null_sink_data *d = module->user_data;
+
+ if (d->proxy != NULL) {
+ spa_hook_remove(&d->proxy_listener);
+ pw_proxy_destroy(d->proxy);
+ d->proxy = NULL;
+ }
+
+ if (d->core != NULL) {
+ spa_hook_remove(&d->core_listener);
+ pw_core_disconnect(d->core);
+ d->core = NULL;
+ }
+
+ return 0;
+}
+
+static const struct spa_dict_item module_null_sink_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "A NULL sink" },
+ { PW_KEY_MODULE_USAGE, "sink_name=<name of sink> "
+ "sink_properties=<properties for the sink> "
+ "format=<sample format> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<channel map>" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_null_sink_prepare(struct module * const module)
+{
+ struct pw_properties * const props = module->props;
+ const char *str;
+ struct spa_audio_info_raw info = { 0 };
+ uint32_t i;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ if ((str = pw_properties_get(props, "sink_name")) != NULL) {
+ pw_properties_set(props, PW_KEY_NODE_NAME, str);
+ pw_properties_set(props, "sink_name", NULL);
+ }
+ else {
+ pw_properties_set(props, PW_KEY_NODE_NAME, "null-sink");
+ }
+
+ if ((str = pw_properties_get(props, "sink_properties")) != NULL) {
+ module_args_add_props(props, str);
+ pw_properties_set(props, "sink_properties", NULL);
+ }
+
+ if (module_args_to_audioinfo(module->impl, props, &info) < 0)
+ return -EINVAL;
+
+ if (info.rate)
+ pw_properties_setf(props, SPA_KEY_AUDIO_RATE, "%u", info.rate);
+ if (info.channels) {
+ char *s, *p;
+
+ pw_properties_setf(props, SPA_KEY_AUDIO_CHANNELS, "%u", info.channels);
+
+ p = s = alloca(info.channels * 8);
+ for (i = 0; i < info.channels; i++)
+ p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ",",
+ channel_id2name(info.position[i]));
+ pw_properties_set(props, SPA_KEY_AUDIO_POSITION, s);
+ }
+
+ if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL)
+ pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
+
+ if ((str = pw_properties_get(props, PW_KEY_NODE_DESCRIPTION)) == NULL) {
+ const char *name, *class;
+
+ name = pw_properties_get(props, PW_KEY_NODE_NAME);
+ class = pw_properties_get(props, PW_KEY_MEDIA_CLASS);
+ pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION,
+ "%s%s%s%ssink",
+ name, (name[0] == '\0') ? "" : " ",
+ class ? class : "", (class && class[0] != '\0') ? " " : "");
+ }
+ pw_properties_set(props, PW_KEY_FACTORY_NAME, "support.null-audio-sink");
+
+ if (pw_properties_get(props, "monitor.channel-volumes") == NULL)
+ pw_properties_set(props, "monitor.channel-volumes", "true");
+
+ return 0;
+}
+
+DEFINE_MODULE_INFO(module_null_sink) = {
+ .name = "module-null-sink",
+ .prepare = module_null_sink_prepare,
+ .load = module_null_sink_load,
+ .unload = module_null_sink_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_null_sink_info),
+ .data_size = sizeof(struct module_null_sink_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-pipe-sink.c b/src/modules/module-protocol-pulse/modules/module-pipe-sink.c
new file mode 100644
index 0000000..2cc36db
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-pipe-sink.c
@@ -0,0 +1,210 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright © 2021 Sanchayan Maity <sanchayan@asymptotic.io>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <pipewire/pipewire.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/utils/hook.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "pipe-sink"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_pipesink_data {
+ struct module *module;
+
+ struct spa_hook mod_listener;
+ struct pw_impl_module *mod;
+
+ struct pw_properties *capture_props;
+ struct spa_audio_info_raw info;
+ char *filename;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_pipesink_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_pipe_sink_load(struct module *module)
+{
+ struct module_pipesink_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ size_t size;
+ uint32_t i;
+
+ pw_properties_setf(data->capture_props, "pulse.module.id",
+ "%u", module->index);
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ fprintf(f, " \"tunnel.mode\" = \"sink\" ");
+ if (data->filename != NULL)
+ fprintf(f, " \"pipe.filename\": \"%s\"", data->filename);
+ if (data->info.format != 0)
+ fprintf(f, " \"audio.format\": \"%s\"", format_id2name(data->info.format));
+ if (data->info.rate != 0)
+ fprintf(f, " \"audio.rate\": %u,", data->info.rate);
+ if (data->info.channels != 0) {
+ fprintf(f, " \"audio.channels\": %u,", data->info.channels);
+ if (!(data->info.flags & SPA_AUDIO_FLAG_UNPOSITIONED)) {
+ fprintf(f, " \"audio.position\": [ ");
+ for (i = 0; i < data->info.channels; i++)
+ fprintf(f, "%s\"%s\"", i == 0 ? "" : ",",
+ channel_id2name(data->info.position[i]));
+ fprintf(f, " ],");
+ }
+ }
+ fprintf(f, " \"stream.props\": {");
+ pw_properties_serialize_dict(f, &data->capture_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-pipe-tunnel",
+ args, NULL);
+
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+ return 0;
+}
+
+static int module_pipe_sink_unload(struct module *module)
+{
+ struct module_pipesink_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+ pw_properties_free(d->capture_props);
+ free(d->filename);
+ return 0;
+}
+
+static const struct spa_dict_item module_pipe_sink_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Pipe sink" },
+ { PW_KEY_MODULE_USAGE, "file=<name of the FIFO special file to use> "
+ "sink_name=<name for the sink> "
+ "sink_properties=<sink properties> "
+ "format=<sample format> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<channel map> " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_pipe_sink_prepare(struct module * const module)
+{
+ struct module_pipesink_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *capture_props = NULL;
+ struct spa_audio_info_raw info = { 0 };
+ const char *str;
+ char *filename = NULL;
+ int res = 0;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ capture_props = pw_properties_new(NULL, NULL);
+ if (!capture_props) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ if (module_args_to_audioinfo(module->impl, props, &info) < 0) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ info.format = SPA_AUDIO_FORMAT_S16;
+ if ((str = pw_properties_get(props, "format")) != NULL) {
+ info.format = format_paname2id(str, strlen(str));
+ pw_properties_set(props, "format", NULL);
+ }
+ if ((str = pw_properties_get(props, "sink_name")) != NULL) {
+ pw_properties_set(capture_props, PW_KEY_NODE_NAME, str);
+ pw_properties_set(props, "sink_name", NULL);
+ }
+ if ((str = pw_properties_get(props, "sink_properties")) != NULL)
+ module_args_add_props(capture_props, str);
+
+ if ((str = pw_properties_get(props, "file")) != NULL) {
+ filename = strdup(str);
+ pw_properties_set(props, "file", NULL);
+ }
+ if ((str = pw_properties_get(capture_props, PW_KEY_DEVICE_ICON_NAME)) == NULL)
+ pw_properties_set(capture_props, PW_KEY_DEVICE_ICON_NAME,
+ "audio-card");
+ if ((str = pw_properties_get(capture_props, PW_KEY_NODE_NAME)) == NULL)
+ pw_properties_set(capture_props, PW_KEY_NODE_NAME,
+ "fifo_output");
+
+ d->module = module;
+ d->capture_props = capture_props;
+ d->info = info;
+ d->filename = filename;
+
+ return 0;
+out:
+ pw_properties_free(capture_props);
+ free(filename);
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_pipe_sink) = {
+ .name = "module-pipe-sink",
+ .prepare = module_pipe_sink_prepare,
+ .load = module_pipe_sink_load,
+ .unload = module_pipe_sink_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_pipe_sink_info),
+ .data_size = sizeof(struct module_pipesink_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-pipe-source.c b/src/modules/module-protocol-pulse/modules/module-pipe-source.c
new file mode 100644
index 0000000..6153635
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-pipe-source.c
@@ -0,0 +1,210 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright © 2021 Sanchayan Maity <sanchayan@asymptotic.io>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <pipewire/pipewire.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/utils/hook.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "pipe-source"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_pipesrc_data {
+ struct module *module;
+
+ struct spa_hook mod_listener;
+ struct pw_impl_module *mod;
+
+ struct pw_properties *playback_props;
+ struct spa_audio_info_raw info;
+ char *filename;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_pipesrc_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_pipe_source_load(struct module *module)
+{
+ struct module_pipesrc_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ size_t size;
+ uint32_t i;
+
+ pw_properties_setf(data->playback_props, "pulse.module.id",
+ "%u", module->index);
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ fprintf(f, " \"tunnel.mode\" = \"source\" ");
+ if (data->filename != NULL)
+ fprintf(f, " \"pipe.filename\": \"%s\"", data->filename);
+ if (data->info.format != 0)
+ fprintf(f, " \"audio.format\": \"%s\"", format_id2name(data->info.format));
+ if (data->info.rate != 0)
+ fprintf(f, " \"audio.rate\": %u,", data->info.rate);
+ if (data->info.channels != 0) {
+ fprintf(f, " \"audio.channels\": %u,", data->info.channels);
+ if (!(data->info.flags & SPA_AUDIO_FLAG_UNPOSITIONED)) {
+ fprintf(f, " \"audio.position\": [ ");
+ for (i = 0; i < data->info.channels; i++)
+ fprintf(f, "%s\"%s\"", i == 0 ? "" : ",",
+ channel_id2name(data->info.position[i]));
+ fprintf(f, " ],");
+ }
+ }
+ fprintf(f, " \"stream.props\": {");
+ pw_properties_serialize_dict(f, &data->playback_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-pipe-tunnel",
+ args, NULL);
+
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+ return 0;
+}
+
+static int module_pipe_source_unload(struct module *module)
+{
+ struct module_pipesrc_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+ pw_properties_free(d->playback_props);
+ free(d->filename);
+ return 0;
+}
+
+static const struct spa_dict_item module_pipe_source_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Pipe source" },
+ { PW_KEY_MODULE_USAGE, "file=<name of the FIFO special file to use> "
+ "source_name=<name for the source> "
+ "source_properties=<source properties> "
+ "format=<sample format> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<channel map> " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_pipe_source_prepare(struct module * const module)
+{
+ struct module_pipesrc_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *playback_props = NULL;
+ struct spa_audio_info_raw info = { 0 };
+ const char *str;
+ char *filename = NULL;
+ int res = 0;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ playback_props = pw_properties_new(NULL, NULL);
+ if (!playback_props) {
+ res = -errno;
+ goto out;
+ }
+
+ if (module_args_to_audioinfo(module->impl, props, &info) < 0) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ info.format = SPA_AUDIO_FORMAT_S16;
+ if ((str = pw_properties_get(props, "format")) != NULL) {
+ info.format = format_paname2id(str, strlen(str));
+ pw_properties_set(props, "format", NULL);
+ }
+ if ((str = pw_properties_get(props, "source_name")) != NULL) {
+ pw_properties_set(playback_props, PW_KEY_NODE_NAME, str);
+ pw_properties_set(props, "source_name", NULL);
+ }
+ if ((str = pw_properties_get(props, "source_properties")) != NULL)
+ module_args_add_props(playback_props, str);
+
+ if ((str = pw_properties_get(props, "file")) != NULL) {
+ filename = strdup(str);
+ pw_properties_set(props, "file", NULL);
+ }
+ if ((str = pw_properties_get(playback_props, PW_KEY_DEVICE_ICON_NAME)) == NULL)
+ pw_properties_set(playback_props, PW_KEY_DEVICE_ICON_NAME,
+ "audio-input-microphone");
+ if ((str = pw_properties_get(playback_props, PW_KEY_NODE_NAME)) == NULL)
+ pw_properties_set(playback_props, PW_KEY_NODE_NAME,
+ "fifo_input");
+
+ d->module = module;
+ d->playback_props = playback_props;
+ d->info = info;
+ d->filename = filename;
+
+ return 0;
+out:
+ pw_properties_free(playback_props);
+ free(filename);
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_pipe_source) = {
+ .name = "module-pipe-source",
+ .prepare = module_pipe_source_prepare,
+ .load = module_pipe_source_load,
+ .unload = module_pipe_source_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_pipe_source_info),
+ .data_size = sizeof(struct module_pipesrc_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-raop-discover.c b/src/modules/module-protocol-pulse/modules/module-raop-discover.c
new file mode 100644
index 0000000..e406189
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-raop-discover.c
@@ -0,0 +1,112 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/hook.h>
+#include <pipewire/pipewire.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "raop-discover"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+
+struct module_raop_discover_data {
+ struct module *module;
+
+ struct spa_hook mod_listener;
+ struct pw_impl_module *mod;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_raop_discover_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_raop_discover_load(struct module *module)
+{
+ struct module_raop_discover_data *data = module->user_data;
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-raop-discover",
+ NULL, NULL);
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_raop_discover_unload(struct module *module)
+{
+ struct module_raop_discover_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ return 0;
+}
+
+static const struct spa_dict_item module_raop_discover_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.con>" },
+ { PW_KEY_MODULE_DESCRIPTION, "mDNS/DNS-SD Service Discovery of RAOP devices" },
+ { PW_KEY_MODULE_USAGE, "" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_raop_discover_prepare(struct module * const module)
+{
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ struct module_raop_discover_data * const data = module->user_data;
+ data->module = module;
+
+ return 0;
+}
+
+DEFINE_MODULE_INFO(module_raop_discover) = {
+ .name = "module-raop-discover",
+ .load_once = true,
+ .prepare = module_raop_discover_prepare,
+ .load = module_raop_discover_load,
+ .unload = module_raop_discover_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_raop_discover_info),
+ .data_size = sizeof(struct module_raop_discover_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-remap-sink.c b/src/modules/module-protocol-pulse/modules/module-remap-sink.c
new file mode 100644
index 0000000..f6b57f0
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-remap-sink.c
@@ -0,0 +1,253 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/param/audio/format-utils.h>
+#include <spa/utils/hook.h>
+#include <spa/utils/json.h>
+#include <pipewire/pipewire.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "remap-sink"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_remap_sink_data {
+ struct module *module;
+
+ struct pw_impl_module *mod;
+ struct spa_hook mod_listener;
+
+ struct pw_properties *capture_props;
+ struct pw_properties *playback_props;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_remap_sink_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_remap_sink_load(struct module *module)
+{
+ struct module_remap_sink_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ size_t size;
+
+ pw_properties_setf(data->capture_props, PW_KEY_NODE_GROUP, "remap-sink-%u", module->index);
+ pw_properties_setf(data->playback_props, PW_KEY_NODE_GROUP, "remap-sink-%u", module->index);
+ pw_properties_setf(data->capture_props, "pulse.module.id", "%u", module->index);
+ pw_properties_setf(data->playback_props, "pulse.module.id", "%u", module->index);
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ pw_properties_serialize_dict(f, &module->props->dict, 0);
+ fprintf(f, " capture.props = {");
+ pw_properties_serialize_dict(f, &data->capture_props->dict, 0);
+ fprintf(f, " } playback.props = {");
+ pw_properties_serialize_dict(f, &data->playback_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-loopback",
+ args, NULL);
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_remap_sink_unload(struct module *module)
+{
+ struct module_remap_sink_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ pw_properties_free(d->capture_props);
+ pw_properties_free(d->playback_props);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_remap_sink_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Remap sink channels" },
+ { PW_KEY_MODULE_USAGE, "sink_name=<name for the sink> "
+ "sink_properties=<properties for the sink> "
+ "master=<name of sink to remap> "
+ "master_channel_map=<channel map> "
+ "format=<sample format> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<channel map> "
+ "resample_method=<resampler> "
+ "remix=<remix channels?>" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static void position_to_props(struct spa_audio_info_raw *info, struct pw_properties *props)
+{
+ char *s, *p;
+ uint32_t i;
+
+ pw_properties_setf(props, SPA_KEY_AUDIO_CHANNELS, "%u", info->channels);
+ p = s = alloca(info->channels * 8);
+ for (i = 0; i < info->channels; i++)
+ p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ",",
+ channel_id2name(info->position[i]));
+ pw_properties_set(props, SPA_KEY_AUDIO_POSITION, s);
+}
+
+static int module_remap_sink_prepare(struct module * const module)
+{
+ struct module_remap_sink_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *playback_props = NULL, *capture_props = NULL;
+ const char *str, *master;
+ struct spa_audio_info_raw capture_info = { 0 };
+ struct spa_audio_info_raw playback_info = { 0 };
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ capture_props = pw_properties_new(NULL, NULL);
+ playback_props = pw_properties_new(NULL, NULL);
+ if (!capture_props || !playback_props) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ master = pw_properties_get(props, "master");
+ if (pw_properties_get(props, "sink_name") == NULL) {
+ pw_properties_setf(props, "sink_name", "%s.remapped",
+ master ? master : "default");
+ }
+ if ((str = pw_properties_get(props, "sink_name")) != NULL) {
+ pw_properties_set(capture_props, PW_KEY_NODE_NAME, str);
+ pw_properties_setf(playback_props, PW_KEY_NODE_NAME, "output.%s", str);
+ pw_properties_set(props, "sink_name", NULL);
+ }
+ if ((str = pw_properties_get(props, "sink_properties")) != NULL) {
+ module_args_add_props(capture_props, str);
+ pw_properties_set(props, "sink_properties", NULL);
+ }
+ if (pw_properties_get(capture_props, PW_KEY_MEDIA_CLASS) == NULL)
+ pw_properties_set(capture_props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
+ if (pw_properties_get(capture_props, PW_KEY_DEVICE_CLASS) == NULL)
+ pw_properties_set(capture_props, PW_KEY_DEVICE_CLASS, "filter");
+
+ if ((str = pw_properties_get(capture_props, PW_KEY_MEDIA_NAME)) != NULL)
+ pw_properties_set(props, PW_KEY_MEDIA_NAME, str);
+ if ((str = pw_properties_get(capture_props, PW_KEY_NODE_DESCRIPTION)) != NULL) {
+ pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, str);
+ } else {
+ str = pw_properties_get(capture_props, PW_KEY_NODE_NAME);
+ if (master != NULL || str == NULL) {
+ pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION,
+ "Remapped %s sink",
+ master ? master : "default");
+ } else {
+ pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION,
+ "%s sink", str);
+ }
+ }
+ if ((str = pw_properties_get(props, "master")) != NULL) {
+ pw_properties_set(playback_props, PW_KEY_TARGET_OBJECT, str);
+ pw_properties_set(props, "master", NULL);
+ }
+
+ if (module_args_to_audioinfo(module->impl, props, &capture_info) < 0) {
+ res = -EINVAL;
+ goto out;
+ }
+ playback_info = capture_info;
+
+ if ((str = pw_properties_get(props, "master_channel_map")) != NULL) {
+ struct channel_map map;
+
+ channel_map_parse(str, &map);
+ if (map.channels == 0 || map.channels > SPA_AUDIO_MAX_CHANNELS) {
+ pw_log_error("invalid channel_map '%s'", str);
+ res = -EINVAL;
+ goto out;
+ }
+ channel_map_to_positions(&map, playback_info.position);
+ pw_properties_set(props, "master_channel_map", NULL);
+ }
+ position_to_props(&capture_info, capture_props);
+ position_to_props(&playback_info, playback_props);
+
+ if ((str = pw_properties_get(props, "remix")) != NULL) {
+ /* Note that the boolean is inverted */
+ pw_properties_set(playback_props, PW_KEY_STREAM_DONT_REMIX,
+ module_args_parse_bool(str) ? "false" : "true");
+ pw_properties_set(props, "remix", NULL);
+ }
+
+ if (pw_properties_get(playback_props, PW_KEY_NODE_PASSIVE) == NULL)
+ pw_properties_set(playback_props, PW_KEY_NODE_PASSIVE, "true");
+
+ d->module = module;
+ d->capture_props = capture_props;
+ d->playback_props = playback_props;
+
+ return 0;
+out:
+ pw_properties_free(playback_props);
+ pw_properties_free(capture_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_remap_sink) = {
+ .name = "module-remap-sink",
+ .prepare = module_remap_sink_prepare,
+ .load = module_remap_sink_load,
+ .unload = module_remap_sink_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_remap_sink_info),
+ .data_size = sizeof(struct module_remap_sink_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-remap-source.c b/src/modules/module-protocol-pulse/modules/module-remap-source.c
new file mode 100644
index 0000000..5ee6092
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-remap-source.c
@@ -0,0 +1,260 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/param/audio/format-utils.h>
+#include <spa/utils/hook.h>
+#include <spa/utils/json.h>
+#include <pipewire/pipewire.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "remap-sink"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_remap_source_data {
+ struct module *module;
+
+ struct pw_impl_module *mod;
+ struct spa_hook mod_listener;
+
+ struct pw_properties *capture_props;
+ struct pw_properties *playback_props;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_remap_source_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_remap_source_load(struct module *module)
+{
+ struct module_remap_source_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ size_t size;
+
+ pw_properties_setf(data->capture_props, PW_KEY_NODE_GROUP, "remap-source-%u", module->index);
+ pw_properties_setf(data->playback_props, PW_KEY_NODE_GROUP, "remap-source-%u", module->index);
+ pw_properties_setf(data->capture_props, "pulse.module.id", "%u", module->index);
+ pw_properties_setf(data->playback_props, "pulse.module.id", "%u", module->index);
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ pw_properties_serialize_dict(f, &module->props->dict, 0);
+ fprintf(f, " capture.props = { ");
+ pw_properties_serialize_dict(f, &data->capture_props->dict, 0);
+ fprintf(f, " } playback.props = { ");
+ pw_properties_serialize_dict(f, &data->playback_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-loopback",
+ args, NULL);
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_remap_source_unload(struct module *module)
+{
+ struct module_remap_source_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ pw_properties_free(d->capture_props);
+ pw_properties_free(d->playback_props);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_remap_source_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Remap source channels" },
+ { PW_KEY_MODULE_USAGE, "source_name=<name for the source> "
+ "source_properties=<properties for the source> "
+ "master=<name of source to filter> "
+ "master_channel_map=<channel map> "
+ "format=<sample format> "
+ "rate=<sample rate> "
+ "channels=<number of channels> "
+ "channel_map=<channel map> "
+ "resample_method=<resampler> "
+ "remix=<remix channels?>" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static void position_to_props(struct spa_audio_info_raw *info, struct pw_properties *props)
+{
+ char *s, *p;
+ uint32_t i;
+
+ pw_properties_setf(props, SPA_KEY_AUDIO_CHANNELS, "%u", info->channels);
+ p = s = alloca(info->channels * 8);
+ for (i = 0; i < info->channels; i++)
+ p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ",",
+ channel_id2name(info->position[i]));
+ pw_properties_set(props, SPA_KEY_AUDIO_POSITION, s);
+}
+
+static int module_remap_source_prepare(struct module * const module)
+{
+ struct module_remap_source_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *playback_props = NULL, *capture_props = NULL;
+ const char *str, *master;
+ struct spa_audio_info_raw capture_info = { 0 };
+ struct spa_audio_info_raw playback_info = { 0 };
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ capture_props = pw_properties_new(NULL, NULL);
+ playback_props = pw_properties_new(NULL, NULL);
+ if (!capture_props || !playback_props) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ master = pw_properties_get(props, "master");
+ if (pw_properties_get(props, "source_name") == NULL) {
+ pw_properties_setf(props, "source_name", "%s.remapped",
+ master ? master : "default");
+ }
+ if ((str = pw_properties_get(props, "source_name")) != NULL) {
+ pw_properties_set(playback_props, PW_KEY_NODE_NAME, str);
+ pw_properties_setf(capture_props, PW_KEY_NODE_NAME, "input.%s", str);
+ pw_properties_set(props, "source_name", NULL);
+ }
+ if ((str = pw_properties_get(props, "source_properties")) != NULL) {
+ module_args_add_props(playback_props, str);
+ pw_properties_set(props, "source_properties", NULL);
+ }
+ if (pw_properties_get(playback_props, PW_KEY_MEDIA_CLASS) == NULL)
+ pw_properties_set(playback_props, PW_KEY_MEDIA_CLASS, "Audio/Source");
+ if (pw_properties_get(playback_props, PW_KEY_DEVICE_CLASS) == NULL)
+ pw_properties_set(playback_props, PW_KEY_DEVICE_CLASS, "filter");
+
+ if ((str = pw_properties_get(playback_props, PW_KEY_MEDIA_NAME)) != NULL)
+ pw_properties_set(props, PW_KEY_MEDIA_NAME, str);
+ if ((str = pw_properties_get(playback_props, PW_KEY_NODE_DESCRIPTION)) != NULL) {
+ pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, str);
+ } else {
+ str = pw_properties_get(playback_props, PW_KEY_NODE_NAME);
+ if (master != NULL || str == NULL) {
+ pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION,
+ "Remapped %s source",
+ master ? master : "default");
+ } else {
+ pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION,
+ "%s source", str);
+ }
+ }
+ if ((str = pw_properties_get(props, "master")) != NULL) {
+ if (spa_strendswith(str, ".monitor")) {
+ pw_properties_setf(capture_props, PW_KEY_TARGET_OBJECT,
+ "%.*s", (int)strlen(str)-8, str);
+ pw_properties_set(capture_props, PW_KEY_STREAM_CAPTURE_SINK,
+ "true");
+ } else {
+ pw_properties_set(capture_props, PW_KEY_TARGET_OBJECT, str);
+ }
+ pw_properties_set(props, "master", NULL);
+ }
+
+ if (module_args_to_audioinfo(module->impl, props, &playback_info) < 0) {
+ res = -EINVAL;
+ goto out;
+ }
+ capture_info = playback_info;
+
+ if ((str = pw_properties_get(props, "master_channel_map")) != NULL) {
+ struct channel_map map;
+
+ channel_map_parse(str, &map);
+ if (map.channels == 0 || map.channels > SPA_AUDIO_MAX_CHANNELS) {
+ pw_log_error("invalid channel_map '%s'", str);
+ res = -EINVAL;
+ goto out;
+ }
+ channel_map_to_positions(&map, capture_info.position);
+ pw_properties_set(props, "master_channel_map", NULL);
+ }
+ position_to_props(&playback_info, playback_props);
+ position_to_props(&capture_info, capture_props);
+
+ if ((str = pw_properties_get(props, "remix")) != NULL) {
+ /* Note that the boolean is inverted */
+ pw_properties_set(capture_props, PW_KEY_STREAM_DONT_REMIX,
+ module_args_parse_bool(str) ? "false" : "true");
+ pw_properties_set(props, "remix", NULL);
+ }
+
+ if (pw_properties_get(capture_props, PW_KEY_NODE_PASSIVE) == NULL)
+ pw_properties_set(capture_props, PW_KEY_NODE_PASSIVE, "true");
+
+ d->module = module;
+ d->capture_props = capture_props;
+ d->playback_props = playback_props;
+
+ return 0;
+out:
+ pw_properties_free(playback_props);
+ pw_properties_free(capture_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_remap_source) = {
+ .name = "module-remap-source",
+ .prepare = module_remap_source_prepare,
+ .load = module_remap_source_load,
+ .unload = module_remap_source_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_remap_source_info),
+ .data_size = sizeof(struct module_remap_source_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-roc-sink-input.c b/src/modules/module-protocol-pulse/modules/module-roc-sink-input.c
new file mode 100644
index 0000000..3a672ac
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-roc-sink-input.c
@@ -0,0 +1,201 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright © 2021 Sanchayan Maity <sanchayan@asymptotic.io>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/hook.h>
+#include <pipewire/pipewire.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "roc-source"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_roc_sink_input_data {
+ struct module *module;
+
+ struct spa_hook mod_listener;
+ struct pw_impl_module *mod;
+
+ struct pw_properties *source_props;
+ struct pw_properties *roc_props;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_roc_sink_input_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_roc_sink_input_load(struct module *module)
+{
+ struct module_roc_sink_input_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ size_t size;
+
+ pw_properties_setf(data->source_props, "pulse.module.id",
+ "%u", module->index);
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ pw_properties_serialize_dict(f, &data->roc_props->dict, 0);
+ fprintf(f, " source.props = {");
+ pw_properties_serialize_dict(f, &data->source_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-roc-source",
+ args, NULL);
+
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_roc_sink_input_unload(struct module *module)
+{
+ struct module_roc_sink_input_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ pw_properties_free(d->roc_props);
+ pw_properties_free(d->source_props);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_roc_sink_input_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>" },
+ { PW_KEY_MODULE_DESCRIPTION, "roc sink-input" },
+ { PW_KEY_MODULE_USAGE, "sink=<name for the sink> "
+ "sink_input_properties=<properties for the sink_input> "
+ "resampler_profile=<empty>|disable|high|medium|low "
+ "fec_code=<empty>|disable|rs8m|ldpc "
+ "sess_latency_msec=<target network latency in milliseconds> "
+ "local_ip=<local receiver ip> "
+ "local_source_port=<local receiver port for source packets> "
+ "local_repair_port=<local receiver port for repair packets> " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_roc_sink_input_prepare(struct module * const module)
+{
+ struct module_roc_sink_input_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *source_props = NULL, *roc_props = NULL;
+ const char *str;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ source_props = pw_properties_new(NULL, NULL);
+ roc_props = pw_properties_new(NULL, NULL);
+ if (!source_props || !roc_props) {
+ res = -errno;
+ goto out;
+ }
+
+ if ((str = pw_properties_get(props, "sink")) != NULL) {
+ pw_properties_set(source_props, PW_KEY_TARGET_OBJECT, str);
+ pw_properties_set(props, "sink", NULL);
+ }
+ if ((str = pw_properties_get(props, "sink_input_properties")) != NULL) {
+ module_args_add_props(source_props, str);
+ pw_properties_set(props, "sink_input_properties", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "local_ip")) != NULL) {
+ pw_properties_set(roc_props, "local.ip", str);
+ pw_properties_set(props, "local_ip", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "local_source_port")) != NULL) {
+ pw_properties_set(roc_props, "local.source.port", str);
+ pw_properties_set(props, "local_source_port", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "local_repair_port")) != NULL) {
+ pw_properties_set(roc_props, "local.repair.port", str);
+ pw_properties_set(props, "local_repair_port", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "sess_latency_msec")) != NULL) {
+ pw_properties_set(roc_props, "sess.latency.msec", str);
+ pw_properties_set(props, "sess_latency_msec", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "resampler_profile")) != NULL) {
+ pw_properties_set(roc_props, "resampler.profile", str);
+ pw_properties_set(props, "resampler_profile", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "fec_code")) != NULL) {
+ pw_properties_set(roc_props, "fec.code", str);
+ pw_properties_set(props, "fec_code", NULL);
+ }
+
+ d->module = module;
+ d->source_props = source_props;
+ d->roc_props = roc_props;
+
+ return 0;
+out:
+ pw_properties_free(source_props);
+ pw_properties_free(roc_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_roc_sink_input) = {
+ .name = "module-roc-sink-input",
+ .prepare = module_roc_sink_input_prepare,
+ .load = module_roc_sink_input_load,
+ .unload = module_roc_sink_input_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_roc_sink_input_info),
+ .data_size = sizeof(struct module_roc_sink_input_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-roc-sink.c b/src/modules/module-protocol-pulse/modules/module-roc-sink.c
new file mode 100644
index 0000000..2dd5bb8
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-roc-sink.c
@@ -0,0 +1,197 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright © 2021 Sanchayan Maity <sanchayan@asymptotic.io>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/hook.h>
+#include <pipewire/pipewire.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "roc-sink"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_roc_sink_data {
+ struct module *module;
+
+ struct spa_hook mod_listener;
+ struct pw_impl_module *mod;
+
+ struct pw_properties *sink_props;
+ struct pw_properties *roc_props;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_roc_sink_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_roc_sink_load(struct module *module)
+{
+ struct module_roc_sink_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ size_t size;
+
+ pw_properties_setf(data->sink_props, "pulse.module.id",
+ "%u", module->index);
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ pw_properties_serialize_dict(f, &data->roc_props->dict, 0);
+ fprintf(f, " sink.props = {");
+ pw_properties_serialize_dict(f, &data->sink_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-roc-sink",
+ args, NULL);
+
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_roc_sink_unload(struct module *module)
+{
+ struct module_roc_sink_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ pw_properties_free(d->roc_props);
+ pw_properties_free(d->sink_props);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_roc_sink_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>" },
+ { PW_KEY_MODULE_DESCRIPTION, "roc sink" },
+ { PW_KEY_MODULE_USAGE, "sink_name=<name for the sink> "
+ "sink_properties=<properties for the sink> "
+ "fec_code=<empty>|disable|rs8m|ldpc "
+ "remote_ip=<remote receiver ip> "
+ "remote_source_port=<remote receiver port for source packets> "
+ "remote_repair_port=<remote receiver port for repair packets> " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_roc_sink_prepare(struct module * const module)
+{
+ struct module_roc_sink_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *sink_props = NULL, *roc_props = NULL;
+ const char *str;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ sink_props = pw_properties_new(NULL, NULL);
+ roc_props = pw_properties_new(NULL, NULL);
+ if (!sink_props || !roc_props) {
+ res = -errno;
+ goto out;
+ }
+
+ if ((str = pw_properties_get(props, "sink_name")) != NULL) {
+ pw_properties_set(sink_props, PW_KEY_NODE_NAME, str);
+ pw_properties_set(props, "sink_name", NULL);
+ }
+ if ((str = pw_properties_get(props, "sink_properties")) != NULL) {
+ module_args_add_props(sink_props, str);
+ pw_properties_set(props, "sink_properties", NULL);
+ }
+
+ if ((str = pw_properties_get(props, PW_KEY_MEDIA_CLASS)) == NULL) {
+ pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
+ pw_properties_set(sink_props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
+ }
+
+ if ((str = pw_properties_get(props, "remote_ip")) != NULL) {
+ pw_properties_set(roc_props, "remote.ip", str);
+ pw_properties_set(props, "remote_ip", NULL);
+ } else {
+ pw_log_error("Remote IP not specified");
+ res = -EINVAL;
+ goto out;
+ }
+
+ if ((str = pw_properties_get(props, "remote_source_port")) != NULL) {
+ pw_properties_set(roc_props, "remote.source.port", str);
+ pw_properties_set(props, "remote_source_port", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "remote_repair_port")) != NULL) {
+ pw_properties_set(roc_props, "remote.repair.port", str);
+ pw_properties_set(props, "remote_repair_port", NULL);
+ }
+ if ((str = pw_properties_get(props, "fec_code")) != NULL) {
+ pw_properties_set(roc_props, "fec.code", str);
+ pw_properties_set(props, "fec_code", NULL);
+ }
+
+ d->module = module;
+ d->sink_props = sink_props;
+ d->roc_props = roc_props;
+
+ return 0;
+out:
+ pw_properties_free(sink_props);
+ pw_properties_free(roc_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_roc_sink) = {
+ .name = "module-roc-sink",
+ .prepare = module_roc_sink_prepare,
+ .load = module_roc_sink_load,
+ .unload = module_roc_sink_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_roc_sink_info),
+ .data_size = sizeof(struct module_roc_sink_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-roc-source.c b/src/modules/module-protocol-pulse/modules/module-roc-source.c
new file mode 100644
index 0000000..681f27a
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-roc-source.c
@@ -0,0 +1,206 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright © 2021 Sanchayan Maity <sanchayan@asymptotic.io>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/hook.h>
+#include <pipewire/pipewire.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "roc-source"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_roc_source_data {
+ struct module *module;
+
+ struct spa_hook mod_listener;
+ struct pw_impl_module *mod;
+
+ struct pw_properties *source_props;
+ struct pw_properties *roc_props;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_roc_source_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_roc_source_load(struct module *module)
+{
+ struct module_roc_source_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ size_t size;
+
+ pw_properties_setf(data->source_props, "pulse.module.id",
+ "%u", module->index);
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ pw_properties_serialize_dict(f, &data->roc_props->dict, 0);
+ fprintf(f, " source.props = {");
+ pw_properties_serialize_dict(f, &data->source_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-roc-source",
+ args, NULL);
+
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_roc_source_unload(struct module *module)
+{
+ struct module_roc_source_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ pw_properties_free(d->roc_props);
+ pw_properties_free(d->source_props);
+
+ return 0;
+}
+
+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> "
+ "source_properties=<properties for the source> "
+ "resampler_profile=<empty>|disable|high|medium|low "
+ "fec_code=<empty>|disable|rs8m|ldpc "
+ "sess_latency_msec=<target network latency in milliseconds> "
+ "local_ip=<local receiver ip> "
+ "local_source_port=<local receiver port for source packets> "
+ "local_repair_port=<local receiver port for repair packets> " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_roc_source_prepare(struct module * const module)
+{
+ struct module_roc_source_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *source_props = NULL, *roc_props = NULL;
+ const char *str;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ source_props = pw_properties_new(NULL, NULL);
+ roc_props = pw_properties_new(NULL, NULL);
+ if (!source_props || !roc_props) {
+ res = -errno;
+ goto out;
+ }
+
+ if ((str = pw_properties_get(props, "source_name")) != NULL) {
+ pw_properties_set(source_props, PW_KEY_NODE_NAME, str);
+ pw_properties_set(props, "source_name", NULL);
+ }
+ if ((str = pw_properties_get(props, "source_properties")) != NULL) {
+ module_args_add_props(source_props, str);
+ pw_properties_set(props, "source_properties", NULL);
+ }
+
+ if ((str = pw_properties_get(props, PW_KEY_MEDIA_CLASS)) == NULL) {
+ pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Source");
+ pw_properties_set(source_props, PW_KEY_MEDIA_CLASS, "Audio/Source");
+ }
+
+ if ((str = pw_properties_get(props, "local_ip")) != NULL) {
+ pw_properties_set(roc_props, "local.ip", str);
+ pw_properties_set(props, "local_ip", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "local_source_port")) != NULL) {
+ pw_properties_set(roc_props, "local.source.port", str);
+ pw_properties_set(props, "local_source_port", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "local_repair_port")) != NULL) {
+ pw_properties_set(roc_props, "local.repair.port", str);
+ pw_properties_set(props, "local_repair_port", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "sess_latency_msec")) != NULL) {
+ pw_properties_set(roc_props, "sess.latency.msec", str);
+ pw_properties_set(props, "sess_latency_msec", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "resampler_profile")) != NULL) {
+ pw_properties_set(roc_props, "resampler.profile", str);
+ pw_properties_set(props, "resampler_profile", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "fec_code")) != NULL) {
+ pw_properties_set(roc_props, "fec.code", str);
+ pw_properties_set(props, "fec_code", NULL);
+ }
+
+ d->module = module;
+ d->source_props = source_props;
+ d->roc_props = roc_props;
+
+ return 0;
+out:
+ pw_properties_free(source_props);
+ pw_properties_free(roc_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_roc_source) = {
+ .name = "module-roc-source",
+ .prepare = module_roc_source_prepare,
+ .load = module_roc_source_load,
+ .unload = module_roc_source_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_roc_source_info),
+ .data_size = sizeof(struct module_roc_source_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-rtp-recv.c b/src/modules/module-protocol-pulse/modules/module-rtp-recv.c
new file mode 100644
index 0000000..fdaecd7
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-rtp-recv.c
@@ -0,0 +1,164 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/hook.h>
+#include <pipewire/pipewire.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "rtp-recv"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_rtp_recv_data {
+ struct module *module;
+
+ struct spa_hook mod_listener;
+ struct pw_impl_module *mod;
+
+ struct pw_properties *stream_props;
+ struct pw_properties *global_props;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_rtp_recv_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_rtp_recv_load(struct module *module)
+{
+ struct module_rtp_recv_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ size_t size;
+
+ pw_properties_setf(data->stream_props, "pulse.module.id",
+ "%u", module->index);
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ pw_properties_serialize_dict(f, &data->global_props->dict, 0);
+ fprintf(f, " stream.props = {");
+ pw_properties_serialize_dict(f, &data->stream_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-rtp-source",
+ args, NULL);
+
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_rtp_recv_unload(struct module *module)
+{
+ struct module_rtp_recv_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ pw_properties_free(d->global_props);
+ pw_properties_free(d->stream_props);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_rtp_recv_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Receive data from a network via RTP/SAP/SDP" },
+ { PW_KEY_MODULE_USAGE, "sink=<name of the sink> "
+ "sap_address=<multicast address to listen on> "
+ "latency_msec=<latency in ms> " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_rtp_recv_prepare(struct module * const module)
+{
+ struct module_rtp_recv_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *stream_props = NULL, *global_props = NULL;
+ const char *str;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ stream_props = pw_properties_new(NULL, NULL);
+ global_props = pw_properties_new(NULL, NULL);
+ if (!stream_props || !global_props) {
+ res = -errno;
+ goto out;
+ }
+ if ((str = pw_properties_get(props, "sink")) != NULL)
+ pw_properties_set(stream_props, PW_KEY_TARGET_OBJECT, str);
+
+ if ((str = pw_properties_get(props, "sap_address")) != NULL)
+ pw_properties_set(global_props, "sap.ip", str);
+
+ if ((str = pw_properties_get(props, "latency_msec")) != NULL)
+ pw_properties_set(global_props, "sess.latency.msec", str);
+
+ d->module = module;
+ d->stream_props = stream_props;
+ d->global_props = global_props;
+
+ return 0;
+out:
+ pw_properties_free(stream_props);
+ pw_properties_free(global_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_rtp_recv) = {
+ .name = "module-rtp-recv",
+ .prepare = module_rtp_recv_prepare,
+ .load = module_rtp_recv_load,
+ .unload = module_rtp_recv_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_rtp_recv_info),
+ .data_size = sizeof(struct module_rtp_recv_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-rtp-send.c b/src/modules/module-protocol-pulse/modules/module-rtp-send.c
new file mode 100644
index 0000000..b9aad05
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-rtp-send.c
@@ -0,0 +1,224 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/hook.h>
+#include <pipewire/pipewire.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "rtp-send"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_rtp_send_data {
+ struct module *module;
+
+ struct spa_hook mod_listener;
+ struct pw_impl_module *mod;
+
+ struct pw_properties *stream_props;
+ struct pw_properties *global_props;
+ struct spa_audio_info_raw info;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_rtp_send_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_rtp_send_load(struct module *module)
+{
+ struct module_rtp_send_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ size_t size;
+ uint32_t i;
+
+ pw_properties_setf(data->stream_props, "pulse.module.id",
+ "%u", module->index);
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ pw_properties_serialize_dict(f, &data->global_props->dict, 0);
+ if (data->info.format != 0)
+ fprintf(f, " \"audio.format\": \"%s\"", format_id2name(data->info.format));
+ if (data->info.rate != 0)
+ fprintf(f, " \"audio.rate\": %u,", data->info.rate);
+ if (data->info.channels != 0) {
+ fprintf(f, " \"audio.channels\": %u,", data->info.channels);
+ if (!(data->info.flags & SPA_AUDIO_FLAG_UNPOSITIONED)) {
+ fprintf(f, " \"audio.position\": [ ");
+ for (i = 0; i < data->info.channels; i++)
+ fprintf(f, "%s\"%s\"", i == 0 ? "" : ",",
+ channel_id2name(data->info.position[i]));
+ fprintf(f, " ],");
+ }
+ }
+ fprintf(f, " stream.props = {");
+ pw_properties_serialize_dict(f, &data->stream_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-rtp-sink",
+ args, NULL);
+
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_rtp_send_unload(struct module *module)
+{
+ struct module_rtp_send_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ pw_properties_free(d->global_props);
+ pw_properties_free(d->stream_props);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_rtp_send_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Read data from source and send it to the network via RTP/SAP/SDP" },
+ { PW_KEY_MODULE_USAGE, "source=<name of the source> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "destination_ip=<destination IP address> "
+ "source_ip=<source IP address> "
+ "port=<port number> "
+ "mtu=<maximum transfer unit> "
+ "loop=<loopback to local host?> "
+ "ttl=<ttl value> "
+ "inhibit_auto_suspend=<always|never|only_with_non_monitor_sources> "
+ "stream_name=<name of the stream> "
+ "enable_opus=<enable OPUS codec>" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_rtp_send_prepare(struct module * const module)
+{
+ struct module_rtp_send_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *stream_props = NULL, *global_props = NULL;
+ struct spa_audio_info_raw info = { 0 };
+ const char *str;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ stream_props = pw_properties_new(NULL, NULL);
+ global_props = pw_properties_new(NULL, NULL);
+ if (!stream_props || !global_props) {
+ res = -errno;
+ goto out;
+ }
+
+ if ((str = pw_properties_get(props, "source")) != NULL) {
+ if (spa_strendswith(str, ".monitor")) {
+ pw_properties_setf(stream_props, PW_KEY_TARGET_OBJECT,
+ "%.*s", (int)strlen(str)-8, str);
+ pw_properties_set(stream_props, PW_KEY_STREAM_CAPTURE_SINK,
+ "true");
+ } else {
+ pw_properties_set(stream_props, PW_KEY_TARGET_OBJECT, str);
+ }
+ }
+ if (module_args_to_audioinfo(module->impl, props, &info) < 0) {
+ res = -EINVAL;
+ goto out;
+ }
+ info.format = 0;
+ if ((str = pw_properties_get(props, "format")) != NULL) {
+ if ((info.format = format_paname2id(str, strlen(str))) ==
+ SPA_AUDIO_FORMAT_UNKNOWN) {
+ pw_log_error("unknown format %s", str);
+ res = -EINVAL;
+ goto out;
+ }
+ }
+
+ if ((str = pw_properties_get(props, "destination_ip")) != NULL)
+ pw_properties_set(global_props, "destination.ip", str);
+ if ((str = pw_properties_get(props, "source_ip")) != NULL)
+ pw_properties_set(global_props, "source.ip", str);
+ if ((str = pw_properties_get(props, "port")) != NULL)
+ pw_properties_set(global_props, "destination.port", str);
+ if ((str = pw_properties_get(props, "mtu")) != NULL)
+ pw_properties_set(global_props, "net.mtu", str);
+ if ((str = pw_properties_get(props, "loop")) != NULL)
+ pw_properties_set(global_props, "net.loop",
+ module_args_parse_bool(str) ? "true" : "false");
+ if ((str = pw_properties_get(props, "ttl")) != NULL)
+ pw_properties_set(global_props, "net.ttl", str);
+ if ((str = pw_properties_get(props, "stream_name")) != NULL)
+ pw_properties_set(global_props, "sess.name", str);
+
+ d->module = module;
+ d->stream_props = stream_props;
+ d->global_props = global_props;
+ d->info = info;
+
+ return 0;
+out:
+ pw_properties_free(stream_props);
+ pw_properties_free(global_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_rtp_send) = {
+ .name = "module-rtp-send",
+ .prepare = module_rtp_send_prepare,
+ .load = module_rtp_send_load,
+ .unload = module_rtp_send_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_rtp_send_info),
+ .data_size = sizeof(struct module_rtp_send_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-simple-protocol-tcp.c b/src/modules/module-protocol-pulse/modules/module-simple-protocol-tcp.c
new file mode 100644
index 0000000..3d0f7d7
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-simple-protocol-tcp.c
@@ -0,0 +1,210 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/impl.h>
+#include <pipewire/pipewire.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "simple-protocol-tcp"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_simple_protocol_tcp_data {
+ struct module *module;
+ struct pw_impl_module *mod;
+ struct spa_hook mod_listener;
+
+ struct pw_properties *module_props;
+
+ struct spa_audio_info_raw info;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_simple_protocol_tcp_data *d = data;
+
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_simple_protocol_tcp_load(struct module *module)
+{
+ struct module_simple_protocol_tcp_data *data = module->user_data;
+ struct impl *impl = module->impl;
+ char *args;
+ size_t size;
+ uint32_t i;
+ FILE *f;
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ if (data->info.rate != 0)
+ fprintf(f, " \"audio.rate\": %u,", data->info.rate);
+ if (data->info.channels != 0) {
+ fprintf(f, " \"audio.channels\": %u,", data->info.channels);
+ if (!(data->info.flags & SPA_AUDIO_FLAG_UNPOSITIONED)) {
+ fprintf(f, " \"audio.position\": [ ");
+ for (i = 0; i < data->info.channels; i++)
+ fprintf(f, "%s\"%s\"", i == 0 ? "" : ",",
+ channel_id2name(data->info.position[i]));
+ fprintf(f, " ],");
+ }
+ }
+ pw_properties_serialize_dict(f, &data->module_props->dict, 0);
+ fprintf(f, "}");
+ fclose(f);
+
+ data->mod = pw_context_load_module(impl->context,
+ "libpipewire-module-protocol-simple",
+ args, NULL);
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod, &data->mod_listener, &module_events, data);
+
+ return 0;
+}
+
+static int module_simple_protocol_tcp_unload(struct module *module)
+{
+ struct module_simple_protocol_tcp_data *d = module->user_data;
+
+ if (d->mod != NULL) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ }
+
+ pw_properties_free(d->module_props);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_simple_protocol_tcp_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Simple protocol (TCP sockets)" },
+ { PW_KEY_MODULE_USAGE, "rate=<sample rate> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "channel_map=<number of channels> "
+ "sink=<sink to connect to> "
+ "source=<source to connect to> "
+ "playback=<enable playback?> "
+ "record=<enable record?> "
+ "port=<TCP port number> "
+ "listen=<address to listen on>" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_simple_protocol_tcp_prepare(struct module * const module)
+{
+ struct module_simple_protocol_tcp_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *module_props = NULL;
+ const char *str, *port, *listen;
+ struct spa_audio_info_raw info = { 0 };
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ module_props = pw_properties_new(NULL, NULL);
+ if (module_props == NULL) {
+ res = -errno;
+ goto out;
+ }
+
+ if ((str = pw_properties_get(props, "format")) != NULL) {
+ pw_properties_set(module_props, "audio.format",
+ format_id2name(format_paname2id(str, strlen(str))));
+ pw_properties_set(props, "format", NULL);
+ }
+ if (module_args_to_audioinfo(module->impl, props, &info) < 0) {
+ res = -EINVAL;
+ goto out;
+ }
+ if ((str = pw_properties_get(props, "playback")) != NULL) {
+ pw_properties_set(module_props, "playback", str);
+ pw_properties_set(props, "playback", NULL);
+ }
+ if ((str = pw_properties_get(props, "record")) != NULL) {
+ pw_properties_set(module_props, "capture", str);
+ pw_properties_set(props, "record", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "source")) != NULL) {
+ if (spa_strendswith(str, ".monitor")) {
+ pw_properties_setf(module_props, "capture.node",
+ "%.*s", (int)strlen(str)-8, str);
+ pw_properties_set(module_props, PW_KEY_STREAM_CAPTURE_SINK,
+ "true");
+ } else {
+ pw_properties_set(module_props, "capture.node", str);
+ }
+ pw_properties_set(props, "source", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "sink")) != NULL) {
+ pw_properties_set(module_props, "playback.node", str);
+ pw_properties_set(props, "sink", NULL);
+ }
+
+ if ((port = pw_properties_get(props, "port")) == NULL)
+ port = "4711";
+ listen = pw_properties_get(props, "listen");
+
+ pw_properties_setf(module_props, "server.address", "[ \"tcp:%s%s%s\" ]",
+ listen ? listen : "", listen ? ":" : "", port);
+
+ d->module = module;
+ d->module_props = module_props;
+ d->info = info;
+
+ return 0;
+out:
+ pw_properties_free(module_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_simple_protocol_tcp) = {
+ .name = "module-simple-protocol-tcp",
+ .prepare = module_simple_protocol_tcp_prepare,
+ .load = module_simple_protocol_tcp_load,
+ .unload = module_simple_protocol_tcp_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_simple_protocol_tcp_info),
+ .data_size = sizeof(struct module_simple_protocol_tcp_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-switch-on-connect.c b/src/modules/module-protocol-pulse/modules/module-switch-on-connect.c
new file mode 100644
index 0000000..6354193
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-switch-on-connect.c
@@ -0,0 +1,291 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright © 2021 Pauli Virtanen <pav@iki.fi>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/hook.h>
+#include <spa/utils/json.h>
+#include <spa/utils/string.h>
+
+#include <regex.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#include "../manager.h"
+#include "../collect.h"
+
+#define NAME "switch-on-connect"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+/* Ignore HDMI by default */
+#define DEFAULT_BLOCKLIST "hdmi"
+
+struct module_switch_on_connect_data {
+ struct module *module;
+
+ struct pw_core *core;
+ struct pw_manager *manager;
+ struct spa_hook core_listener;
+ struct spa_hook manager_listener;
+ struct pw_manager_object *metadata_default;
+
+ regex_t blocklist;
+
+ int sync_seq;
+
+ unsigned int only_from_unavailable:1;
+ unsigned int ignore_virtual:1;
+ unsigned int started:1;
+};
+
+static void handle_metadata(struct module_switch_on_connect_data *d, struct pw_manager_object *old,
+ struct pw_manager_object *new, const char *name)
+{
+ if (spa_streq(name, "default")) {
+ if (d->metadata_default == old)
+ d->metadata_default = new;
+ }
+}
+
+static void manager_added(void *data, struct pw_manager_object *o)
+{
+ struct module_switch_on_connect_data *d = data;
+ struct pw_node_info *info = o->info;
+ struct pw_device_info *card_info = NULL;
+ uint32_t card_id = SPA_ID_INVALID;
+ struct pw_manager_object *card = NULL;
+ const char *str, *bus, *name;
+
+ if (spa_streq(o->type, PW_TYPE_INTERFACE_Metadata)) {
+ if (o->props != NULL &&
+ (str = pw_properties_get(o->props, PW_KEY_METADATA_NAME)) != NULL)
+ handle_metadata(d, NULL, o, str);
+ }
+
+ if (!d->metadata_default || !d->started)
+ return;
+
+ if (!(pw_manager_object_is_sink(o) || pw_manager_object_is_source_or_monitor(o)))
+ return;
+
+ if (!info || !info->props)
+ return;
+
+ name = spa_dict_lookup(info->props, PW_KEY_NODE_NAME);
+ if (!name)
+ return;
+
+ /* Find card */
+ if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)) != NULL)
+ card_id = (uint32_t)atoi(str);
+ if (card_id != SPA_ID_INVALID) {
+ struct selector sel = { .id = card_id, .type = pw_manager_object_is_card, };
+ card = select_object(d->manager, &sel);
+ }
+ if (!card)
+ return;
+ card_info = card->info;
+ if (!card_info || !card_info->props)
+ return;
+
+ pw_log_debug("considering switching to %s", name);
+
+ /* If internal device, only consider hdmi sinks */
+ str = spa_dict_lookup(info->props, "api.alsa.path");
+ bus = spa_dict_lookup(card_info->props, PW_KEY_DEVICE_BUS);
+ if ((spa_streq(bus, "pci") || spa_streq(bus, "isa")) &&
+ !(pw_manager_object_is_sink(o) && spa_strstartswith(str, "hdmi"))) {
+ pw_log_debug("not switching to internal device");
+ return;
+ }
+
+ if (regexec(&d->blocklist, name, 0, NULL, 0) == 0) {
+ pw_log_debug("not switching to blocklisted device");
+ return;
+ }
+
+ if (d->ignore_virtual && spa_dict_lookup(info->props, PW_KEY_DEVICE_API) == NULL) {
+ pw_log_debug("not switching to virtual device");
+ return;
+ }
+
+ if (d->only_from_unavailable) {
+ /* XXX: not implemented */
+ }
+
+ /* Switch default */
+ pw_log_debug("switching to %s", name);
+
+ pw_manager_set_metadata(d->manager, d->metadata_default,
+ PW_ID_CORE,
+ pw_manager_object_is_sink(o) ? METADATA_CONFIG_DEFAULT_SINK
+ : METADATA_CONFIG_DEFAULT_SOURCE,
+ "Spa:String:JSON", "{ \"name\"\"%s\" }", name);
+}
+
+static void manager_sync(void *data)
+{
+ struct module_switch_on_connect_data *d = data;
+
+ /* Manager emits devices/etc next --- enable started flag after that */
+ if (!d->started)
+ d->sync_seq = pw_core_sync(d->core, PW_ID_CORE, d->sync_seq);
+}
+
+static const struct pw_manager_events manager_events = {
+ PW_VERSION_MANAGER_EVENTS,
+ .added = manager_added,
+ .sync = manager_sync,
+};
+
+static void on_core_done(void *data, uint32_t id, int seq)
+{
+ struct module_switch_on_connect_data *d = data;
+ if (seq == d->sync_seq) {
+ pw_log_debug("%p: started", d);
+ d->started = true;
+ }
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .done = on_core_done,
+};
+
+static int module_switch_on_connect_load(struct module *module)
+{
+ struct impl *impl = module->impl;
+ struct module_switch_on_connect_data *d = module->user_data;
+ int res;
+
+ d->core = pw_context_connect(impl->context, NULL, 0);
+ if (d->core == NULL) {
+ res = -errno;
+ goto error;
+ }
+
+ d->manager = pw_manager_new(d->core);
+ if (d->manager == NULL) {
+ res = -errno;
+ pw_core_disconnect(d->core);
+ d->core = NULL;
+ goto error;
+ }
+
+ pw_manager_add_listener(d->manager, &d->manager_listener, &manager_events, d);
+ pw_core_add_listener(d->core, &d->core_listener, &core_events, d);
+
+ /* Postpone setting started flag after initial nodes emitted */
+ pw_manager_sync(d->manager);
+
+ return 0;
+
+error:
+ pw_log_error("%p: failed to connect: %s", impl, spa_strerror(res));
+ return res;
+}
+
+static int module_switch_on_connect_unload(struct module *module)
+{
+ struct module_switch_on_connect_data *d = module->user_data;
+
+ if (d->manager) {
+ spa_hook_remove(&d->manager_listener);
+ pw_manager_destroy(d->manager);
+ d->manager = NULL;
+ }
+
+ if (d->core) {
+ spa_hook_remove(&d->core_listener);
+ pw_core_disconnect(d->core);
+ d->core = NULL;
+ }
+
+ regfree(&d->blocklist);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_switch_on_connect_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Pauli Virtanen <pav@iki.fi>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Switch to new devices on connect. "
+ "This module exists for Pulseaudio compatibility, and is useful only when some applications "
+ "try to manage the default sinks/sources themselves and interfere with PipeWire's builtin "
+ "default device switching." },
+ { PW_KEY_MODULE_USAGE, "only_from_unavailable=<boolean, only switch from unavailable ports (not implemented yet)> "
+ "ignore_virtual=<boolean, ignore new virtual sinks and sources, defaults to true> "
+ "blocklist=<regex, ignore matching devices, default=hdmi> " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_switch_on_connect_prepare(struct module * const module)
+{
+ struct module_switch_on_connect_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ bool only_from_unavailable = false, ignore_virtual = true;
+ const char *str;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ if ((str = pw_properties_get(props, "only_from_unavailable")) != NULL) {
+ only_from_unavailable = module_args_parse_bool(str);
+ pw_properties_set(props, "only_from_unavailable", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "ignore_virtual")) != NULL) {
+ ignore_virtual = module_args_parse_bool(str);
+ pw_properties_set(props, "ignore_virtual", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "blocklist")) == NULL)
+ str = DEFAULT_BLOCKLIST;
+
+ if (regcomp(&d->blocklist, str, REG_NOSUB | REG_EXTENDED) != 0)
+ return -EINVAL;
+
+ pw_properties_set(props, "blocklist", NULL);
+
+ d->module = module;
+ d->ignore_virtual = ignore_virtual;
+ d->only_from_unavailable = only_from_unavailable;
+
+ if (d->only_from_unavailable) {
+ /* XXX: not implemented */
+ pw_log_warn("only_from_unavailable is not implemented");
+ }
+
+ return 0;
+}
+
+DEFINE_MODULE_INFO(module_switch_on_connect) = {
+ .name = "module-switch-on-connect",
+ .load_once = true,
+ .prepare = module_switch_on_connect_prepare,
+ .load = module_switch_on_connect_load,
+ .unload = module_switch_on_connect_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_switch_on_connect_info),
+ .data_size = sizeof(struct module_switch_on_connect_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-tunnel-sink.c b/src/modules/module-protocol-pulse/modules/module-tunnel-sink.c
new file mode 100644
index 0000000..55f9535
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-tunnel-sink.c
@@ -0,0 +1,230 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/param/audio/format-utils.h>
+#include <spa/utils/hook.h>
+#include <spa/utils/json.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/i18n.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "tunnel-sink"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_tunnel_sink_data {
+ struct module *module;
+
+ struct pw_impl_module *mod;
+ struct spa_hook mod_listener;
+
+ uint32_t latency_msec;
+
+ struct pw_properties *stream_props;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_tunnel_sink_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_tunnel_sink_load(struct module *module)
+{
+ struct module_tunnel_sink_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ size_t size;
+ const char *server;
+
+ server = pw_properties_get(module->props, "server");
+
+ pw_properties_setf(data->stream_props, "pulse.module.id",
+ "%u", module->index);
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ pw_properties_serialize_dict(f, &module->props->dict, 0);
+ fprintf(f, " pulse.server.address = \"%s\" ", server);
+ fprintf(f, " tunnel.mode = sink ");
+ if (data->latency_msec > 0)
+ fprintf(f, " pulse.latency = %u ", data->latency_msec);
+ fprintf(f, " stream.props = {");
+ pw_properties_serialize_dict(f, &data->stream_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-pulse-tunnel",
+ args, NULL);
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_tunnel_sink_unload(struct module *module)
+{
+ struct module_tunnel_sink_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ pw_properties_free(d->stream_props);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_tunnel_sink_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Create a network sink which connects to a remote PulseAudio server" },
+ { PW_KEY_MODULE_USAGE,
+ "server=<address> "
+ "sink=<name of the remote sink> "
+ "sink_name=<name for the local sink> "
+ "sink_properties=<properties for the local sink> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "channel_map=<channel map> "
+ "latency_msec=<fixed latency in ms> "
+ "cookie=<cookie file path>" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static void audio_info_to_props(struct spa_audio_info_raw *info, struct pw_properties *props)
+{
+ char *s, *p;
+ uint32_t i;
+
+ pw_properties_setf(props, SPA_KEY_AUDIO_CHANNELS, "%u", info->channels);
+ p = s = alloca(info->channels * 8);
+ for (i = 0; i < info->channels; i++)
+ p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ",",
+ channel_id2name(info->position[i]));
+ pw_properties_set(props, SPA_KEY_AUDIO_POSITION, s);
+}
+
+static int module_tunnel_sink_prepare(struct module * const module)
+{
+ struct module_tunnel_sink_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *stream_props = NULL;
+ const char *str, *server, *remote_sink_name;
+ struct spa_audio_info_raw info = { 0 };
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ stream_props = pw_properties_new(NULL, NULL);
+ if (stream_props == NULL) {
+ res = -ENOMEM;
+ goto out;
+ }
+
+ remote_sink_name = pw_properties_get(props, "sink");
+ if (remote_sink_name)
+ pw_properties_set(props, PW_KEY_TARGET_OBJECT, remote_sink_name);
+
+ if ((server = pw_properties_get(props, "server")) == NULL) {
+ pw_log_error("no server given");
+ res = -EINVAL;
+ goto out;
+ }
+
+ pw_properties_setf(stream_props, PW_KEY_NODE_DESCRIPTION,
+ _("Tunnel to %s/%s"), server,
+ remote_sink_name ? remote_sink_name : "");
+ pw_properties_set(stream_props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
+
+ if ((str = pw_properties_get(props, "sink_name")) != NULL) {
+ pw_properties_set(stream_props, PW_KEY_NODE_NAME, str);
+ pw_properties_set(props, "sink_name", NULL);
+ } else {
+ pw_properties_setf(stream_props, PW_KEY_NODE_NAME,
+ "tunnel-sink.%s", server);
+ }
+
+ if ((str = pw_properties_get(props, "sink_properties")) != NULL) {
+ module_args_add_props(stream_props, str);
+ pw_properties_set(props, "sink_properties", NULL);
+ }
+ if (module_args_to_audioinfo(module->impl, props, &info) < 0) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ audio_info_to_props(&info, stream_props);
+ if ((str = pw_properties_get(props, "format")) != NULL) {
+ uint32_t id = format_paname2id(str, strlen(str));
+ if (id == SPA_AUDIO_FORMAT_UNKNOWN) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ pw_properties_set(stream_props, PW_KEY_AUDIO_FORMAT, format_id2name(id));
+ }
+
+ d->module = module;
+ d->stream_props = stream_props;
+
+ pw_properties_fetch_uint32(props, "latency_msec", &d->latency_msec);
+
+ return 0;
+out:
+ pw_properties_free(stream_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_tunnel_sink) = {
+ .name = "module-tunnel-sink",
+ .prepare = module_tunnel_sink_prepare,
+ .load = module_tunnel_sink_load,
+ .unload = module_tunnel_sink_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_tunnel_sink_info),
+ .data_size = sizeof(struct module_tunnel_sink_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-tunnel-source.c b/src/modules/module-protocol-pulse/modules/module-tunnel-source.c
new file mode 100644
index 0000000..fa17c16
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-tunnel-source.c
@@ -0,0 +1,220 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/param/audio/format-utils.h>
+#include <spa/utils/hook.h>
+#include <spa/utils/json.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/i18n.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "tunnel-source"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_tunnel_source_data {
+ struct module *module;
+
+ struct pw_impl_module *mod;
+ struct spa_hook mod_listener;
+
+ uint32_t latency_msec;
+
+ struct pw_properties *stream_props;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_tunnel_source_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_tunnel_source_load(struct module *module)
+{
+ struct module_tunnel_source_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ size_t size;
+ const char *server;
+
+ pw_properties_setf(data->stream_props, "pulse.module.id",
+ "%u", module->index);
+
+ server = pw_properties_get(module->props, "server");
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ pw_properties_serialize_dict(f, &module->props->dict, 0);
+ fprintf(f, " pulse.server.address = \"%s\" ", server);
+ fprintf(f, " tunnel.mode = source ");
+ if (data->latency_msec > 0)
+ fprintf(f, " pulse.latency = %u ", data->latency_msec);
+ fprintf(f, " stream.props = {");
+ pw_properties_serialize_dict(f, &data->stream_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-pulse-tunnel",
+ args, NULL);
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_tunnel_source_unload(struct module *module)
+{
+ struct module_tunnel_source_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ pw_properties_free(d->stream_props);
+
+ return 0;
+}
+
+static const struct spa_dict_item module_tunnel_source_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Create a network source which connects to a remote PulseAudio server" },
+ { PW_KEY_MODULE_USAGE,
+ "server=<address> "
+ "source=<name of the remote source> "
+ "source_name=<name for the local source> "
+ "source_properties=<properties for the local source> "
+ "format=<sample format> "
+ "channels=<number of channels> "
+ "rate=<sample rate> "
+ "channel_map=<channel map> "
+ "latency_msec=<fixed latency in ms> "
+ "cookie=<cookie file path>" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static void audio_info_to_props(struct spa_audio_info_raw *info, struct pw_properties *props)
+{
+ char *s, *p;
+ uint32_t i;
+
+ pw_properties_setf(props, SPA_KEY_AUDIO_CHANNELS, "%u", info->channels);
+ p = s = alloca(info->channels * 8);
+ for (i = 0; i < info->channels; i++)
+ p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ",",
+ channel_id2name(info->position[i]));
+ pw_properties_set(props, SPA_KEY_AUDIO_POSITION, s);
+}
+
+static int module_tunnel_source_prepare(struct module * const module)
+{
+ struct module_tunnel_source_data * const d = module->user_data;
+ struct pw_properties * const props = module->props;
+ struct pw_properties *stream_props = NULL;
+ const char *str, *server, *remote_source_name;
+ struct spa_audio_info_raw info = { 0 };
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ stream_props = pw_properties_new(NULL, NULL);
+ if (stream_props == NULL) {
+ res = -ENOMEM;
+ goto out;
+ }
+
+ remote_source_name = pw_properties_get(props, "source");
+ if (remote_source_name)
+ pw_properties_set(props, PW_KEY_TARGET_OBJECT, remote_source_name);
+
+ if ((server = pw_properties_get(props, "server")) == NULL) {
+ pw_log_error("no server given");
+ res = -EINVAL;
+ goto out;
+ }
+
+ pw_properties_setf(stream_props, PW_KEY_NODE_DESCRIPTION,
+ _("Tunnel to %s/%s"), server,
+ remote_source_name ? remote_source_name : "");
+ pw_properties_set(stream_props, PW_KEY_MEDIA_CLASS, "Audio/Source");
+
+ if ((str = pw_properties_get(props, "source_name")) != NULL) {
+ pw_properties_set(stream_props, PW_KEY_NODE_NAME, str);
+ pw_properties_set(props, "source_name", NULL);
+ } else {
+ pw_properties_setf(stream_props, PW_KEY_NODE_NAME,
+ "tunnel-source.%s", server);
+ }
+ if ((str = pw_properties_get(props, "source_properties")) != NULL) {
+ module_args_add_props(stream_props, str);
+ pw_properties_set(props, "source_properties", NULL);
+ }
+ if (module_args_to_audioinfo(module->impl, props, &info) < 0) {
+ res = -EINVAL;
+ goto out;
+ }
+
+ audio_info_to_props(&info, stream_props);
+
+ d->module = module;
+ d->stream_props = stream_props;
+
+ pw_properties_fetch_uint32(props, "latency_msec", &d->latency_msec);
+
+ return 0;
+out:
+ pw_properties_free(stream_props);
+
+ return res;
+}
+
+DEFINE_MODULE_INFO(module_tunnel_source) = {
+ .name = "module-tunnel-source",
+ .prepare = module_tunnel_source_prepare,
+ .load = module_tunnel_source_load,
+ .unload = module_tunnel_source_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_tunnel_source_info),
+ .data_size = sizeof(struct module_tunnel_source_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-x11-bell.c b/src/modules/module-protocol-pulse/modules/module-x11-bell.c
new file mode 100644
index 0000000..9d7e217
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-x11-bell.c
@@ -0,0 +1,130 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/pipewire.h>
+
+#include "../module.h"
+
+#define NAME "x11-bell"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_x11_bell_data {
+ struct module *module;
+
+ struct pw_impl_module *mod;
+ struct spa_hook mod_listener;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_x11_bell_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_x11_bell_load(struct module *module)
+{
+ struct module_x11_bell_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ const char *str;
+ size_t size;
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ if ((str = pw_properties_get(module->props, "sink")) != NULL)
+ fprintf(f, " sink.name = \"%s\"", str);
+ if ((str = pw_properties_get(module->props, "sample")) != NULL)
+ fprintf(f, " sample.name = \"%s\"", str);
+ if ((str = pw_properties_get(module->props, "display")) != NULL)
+ fprintf(f, " x11.display = \"%s\"", str);
+ if ((str = pw_properties_get(module->props, "xauthority")) != NULL)
+ fprintf(f, " x11.xauthority = \"%s\"", str);
+ fprintf(f, " }");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-x11-bell",
+ args, NULL);
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+ return 0;
+}
+
+static int module_x11_bell_unload(struct module *module)
+{
+ struct module_x11_bell_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+ return 0;
+}
+
+static int module_x11_bell_prepare(struct module * const module)
+{
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ struct module_x11_bell_data * const data = module->user_data;
+ data->module = module;
+
+ return 0;
+}
+
+static const struct spa_dict_item module_x11_bell_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "X11 bell interceptor" },
+ { PW_KEY_MODULE_USAGE, "sink=<sink to connect to> "
+ "sample=<the sample to play> "
+ "display=<X11 display> "
+ "xauthority=<X11 Authority>" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+DEFINE_MODULE_INFO(module_x11_bell) = {
+ .name = "module-x11-bell",
+ .prepare = module_x11_bell_prepare,
+ .load = module_x11_bell_load,
+ .unload = module_x11_bell_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_x11_bell_info),
+ .data_size = sizeof(struct module_x11_bell_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c b/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c
new file mode 100644
index 0000000..ccdaf27
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-zeroconf-discover.c
@@ -0,0 +1,133 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/hook.h>
+#include <pipewire/pipewire.h>
+
+#include "../defs.h"
+#include "../module.h"
+
+#define NAME "zeroconf-discover"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+
+struct module_zeroconf_discover_data {
+ struct module *module;
+
+ struct spa_hook mod_listener;
+ struct pw_impl_module *mod;
+
+ uint32_t latency_msec;
+};
+
+static void module_destroy(void *data)
+{
+ struct module_zeroconf_discover_data *d = data;
+ spa_hook_remove(&d->mod_listener);
+ d->mod = NULL;
+ module_schedule_unload(d->module);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+static int module_zeroconf_discover_load(struct module *module)
+{
+ struct module_zeroconf_discover_data *data = module->user_data;
+ FILE *f;
+ char *args;
+ size_t size;
+
+ if ((f = open_memstream(&args, &size)) == NULL)
+ return -errno;
+
+ fprintf(f, "{");
+ if (data->latency_msec > 0)
+ fprintf(f, " pulse.latency = %u ", data->latency_msec);
+ fprintf(f, "}");
+ fclose(f);
+
+ data->mod = pw_context_load_module(module->impl->context,
+ "libpipewire-module-zeroconf-discover",
+ args, NULL);
+
+ free(args);
+
+ if (data->mod == NULL)
+ return -errno;
+
+ pw_impl_module_add_listener(data->mod,
+ &data->mod_listener,
+ &module_events, data);
+
+ return 0;
+}
+
+static int module_zeroconf_discover_unload(struct module *module)
+{
+ struct module_zeroconf_discover_data *d = module->user_data;
+
+ if (d->mod) {
+ spa_hook_remove(&d->mod_listener);
+ pw_impl_module_destroy(d->mod);
+ d->mod = NULL;
+ }
+
+ return 0;
+}
+
+static const struct spa_dict_item module_zeroconf_discover_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.con>" },
+ { PW_KEY_MODULE_DESCRIPTION, "mDNS/DNS-SD Service Discovery" },
+ { PW_KEY_MODULE_USAGE,
+ "latency_msec=<fixed latency in ms> " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_zeroconf_discover_prepare(struct module * const module)
+{
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ struct pw_properties * const props = module->props;
+ struct module_zeroconf_discover_data * const data = module->user_data;
+ data->module = module;
+
+ pw_properties_fetch_uint32(props, "latency_msec", &data->latency_msec);
+
+ return 0;
+}
+
+DEFINE_MODULE_INFO(module_zeroconf_discover) = {
+ .name = "module-zeroconf-discover",
+ .load_once = true,
+ .prepare = module_zeroconf_discover_prepare,
+ .load = module_zeroconf_discover_load,
+ .unload = module_zeroconf_discover_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_zeroconf_discover_info),
+ .data_size = sizeof(struct module_zeroconf_discover_data),
+};
diff --git a/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c
new file mode 100644
index 0000000..2f04867
--- /dev/null
+++ b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c
@@ -0,0 +1,746 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright © 2021 Sanchayan Maity <sanchayan@asymptotic.io>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <sys/utsname.h>
+#include <arpa/inet.h>
+
+#include <pipewire/pipewire.h>
+
+#include "../collect.h"
+#include "../defs.h"
+#include "../manager.h"
+#include "../module.h"
+#include "../pulse-server.h"
+#include "../server.h"
+#include "../../module-zeroconf-discover/avahi-poll.h"
+
+#include <avahi-client/client.h>
+#include <avahi-client/publish.h>
+#include <avahi-common/alternative.h>
+#include <avahi-common/error.h>
+#include <avahi-common/domain.h>
+#include <avahi-common/malloc.h>
+
+#define NAME "zeroconf-publish"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define SERVICE_TYPE_SINK "_pulse-sink._tcp"
+#define SERVICE_TYPE_SOURCE "_pulse-source._tcp"
+#define SERVICE_TYPE_SERVER "_pulse-server._tcp"
+#define SERVICE_SUBTYPE_SINK_HARDWARE "_hardware._sub."SERVICE_TYPE_SINK
+#define SERVICE_SUBTYPE_SINK_VIRTUAL "_virtual._sub."SERVICE_TYPE_SINK
+#define SERVICE_SUBTYPE_SOURCE_HARDWARE "_hardware._sub."SERVICE_TYPE_SOURCE
+#define SERVICE_SUBTYPE_SOURCE_VIRTUAL "_virtual._sub."SERVICE_TYPE_SOURCE
+#define SERVICE_SUBTYPE_SOURCE_MONITOR "_monitor._sub."SERVICE_TYPE_SOURCE
+#define SERVICE_SUBTYPE_SOURCE_NON_MONITOR "_non-monitor._sub."SERVICE_TYPE_SOURCE
+
+#define SERVICE_DATA_ID "module-zeroconf-publish.service"
+
+enum service_subtype {
+ SUBTYPE_HARDWARE,
+ SUBTYPE_VIRTUAL,
+ SUBTYPE_MONITOR
+};
+
+struct service {
+ struct spa_list link;
+
+ struct module_zeroconf_publish_data *userdata;
+
+ AvahiEntryGroup *entry_group;
+ AvahiStringList *txt;
+ struct server *server;
+
+ const char *service_type;
+ enum service_subtype subtype;
+
+ char *name;
+ bool is_sink;
+
+ struct sample_spec ss;
+ struct channel_map cm;
+ struct pw_properties *props;
+
+ char service_name[AVAHI_LABEL_MAX];
+ unsigned published:1;
+};
+
+struct module_zeroconf_publish_data {
+ struct module *module;
+
+ struct pw_core *core;
+ struct pw_manager *manager;
+
+ struct spa_hook core_listener;
+ struct spa_hook manager_listener;
+ struct spa_hook impl_listener;
+
+ AvahiPoll *avahi_poll;
+ AvahiClient *client;
+
+ /* lists of services */
+ struct spa_list pending;
+ struct spa_list published;
+};
+
+static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct module_zeroconf_publish_data *d = data;
+ struct module *module = d->module;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ module_schedule_unload(module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+};
+
+static void get_service_name(struct pw_manager_object *o, char *buf, size_t length)
+{
+ const char *hn, *un, *n;
+
+ hn = pw_get_host_name();
+ un = pw_get_user_name();
+ n = pw_properties_get(o->props, PW_KEY_NODE_DESCRIPTION);
+
+ snprintf(buf, length, "%s@%s: %s", un, hn, n);
+}
+
+static void service_free(struct service *s)
+{
+ pw_log_debug("service %p: free", s);
+
+ if (s->entry_group)
+ avahi_entry_group_free(s->entry_group);
+
+ if (s->name)
+ free(s->name);
+
+ pw_properties_free(s->props);
+ avahi_string_list_free(s->txt);
+ spa_list_remove(&s->link);
+}
+
+static void unpublish_service(struct service *s)
+{
+ spa_list_remove(&s->link);
+ spa_list_append(&s->userdata->pending, &s->link);
+ s->published = false;
+ s->server = NULL;
+}
+
+static void unpublish_all_services(struct module_zeroconf_publish_data *d)
+{
+ struct service *s;
+
+ spa_list_consume(s, &d->published, link)
+ unpublish_service(s);
+}
+
+static char* channel_map_snprint(char *s, size_t l, const struct channel_map *map)
+{
+ unsigned channel;
+ bool first = true;
+ char *e;
+ uint32_t aux = 0;
+
+ spa_assert(s);
+ spa_assert(l > 0);
+ spa_assert(map);
+
+ if (!channel_map_valid(map)) {
+ snprintf(s, l, "(invalid)");
+ return s;
+ }
+
+ *(e = s) = 0;
+
+ for (channel = 0; channel < map->channels && l > 1; channel++) {
+ l -= spa_scnprintf(e, l, "%s%s",
+ first ? "" : ",",
+ channel_id2paname(map->map[channel], &aux));
+
+ e = strchr(e, 0);
+ first = false;
+ }
+
+ return s;
+}
+
+static void fill_service_data(struct module_zeroconf_publish_data *d, struct service *s,
+ struct pw_manager_object *o)
+{
+ struct impl *impl = d->module->impl;
+ bool is_sink = pw_manager_object_is_sink(o);
+ bool is_source = pw_manager_object_is_source(o);
+ struct pw_node_info *info = o->info;
+ const char *name, *desc, *str;
+ uint32_t card_id = SPA_ID_INVALID;
+ struct pw_manager *manager = d->manager;
+ struct pw_manager_object *card = NULL;
+ struct card_info card_info = CARD_INFO_INIT;
+ struct device_info dev_info = is_sink ?
+ DEVICE_INFO_INIT(PW_DIRECTION_OUTPUT) : DEVICE_INFO_INIT(PW_DIRECTION_INPUT);
+ uint32_t flags = 0;
+
+ if (info == NULL || info->props == NULL)
+ return;
+
+ name = spa_dict_lookup(info->props, PW_KEY_NODE_NAME);
+ if ((desc = spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION)) == NULL)
+ desc = name ? name : "Unknown";
+ if (name == NULL)
+ name = "unknown";
+
+ if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)) != NULL)
+ card_id = (uint32_t)atoi(str);
+ if ((str = spa_dict_lookup(info->props, "card.profile.device")) != NULL)
+ dev_info.device = (uint32_t)atoi(str);
+ if (card_id != SPA_ID_INVALID) {
+ struct selector sel = { .id = card_id, .type = pw_manager_object_is_card, };
+ card = select_object(manager, &sel);
+ }
+ if (card)
+ collect_card_info(card, &card_info);
+
+ collect_device_info(o, card, &dev_info, false, &impl->defs);
+
+ if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_API)) != NULL) {
+ if (is_sink)
+ flags |= SINK_HARDWARE;
+ else if (is_source)
+ flags |= SOURCE_HARDWARE;
+ }
+
+ s->ss = dev_info.ss;
+ s->cm = dev_info.map;
+ s->name = strdup(name);
+ s->props = pw_properties_copy(o->props);
+
+ if (is_sink) {
+ s->is_sink = true;
+ s->service_type = SERVICE_TYPE_SINK;
+ s->subtype = flags & SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;
+ } else if (is_source) {
+ s->is_sink = false;
+ s->service_type = SERVICE_TYPE_SOURCE;
+ s->subtype = flags & SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;
+ } else
+ spa_assert_not_reached();
+}
+
+static struct service *create_service(struct module_zeroconf_publish_data *d, struct pw_manager_object *o)
+{
+ struct service *s;
+
+ s = pw_manager_object_add_data(o, SERVICE_DATA_ID, sizeof(*s));
+ if (s == NULL)
+ return NULL;
+
+ s->userdata = d;
+ s->entry_group = NULL;
+ get_service_name(o, s->service_name, sizeof(s->service_name));
+ spa_list_append(&d->pending, &s->link);
+
+ fill_service_data(d, s, o);
+
+ pw_log_debug("service %p: created for object %p", s, o);
+
+ return s;
+}
+
+static AvahiStringList* txt_record_server_data(struct pw_core_info *info, AvahiStringList *l)
+{
+ const char *t;
+ struct utsname u;
+
+ spa_assert(info);
+
+ l = avahi_string_list_add_pair(l, "server-version", PACKAGE_NAME" "PACKAGE_VERSION);
+
+ t = pw_get_user_name();
+ l = avahi_string_list_add_pair(l, "user-name", t);
+
+ if (uname(&u) >= 0) {
+ char sysname[sizeof(u.sysname) + sizeof(u.machine) + sizeof(u.release)];
+
+ snprintf(sysname, sizeof(sysname), "%s %s %s", u.sysname, u.machine, u.release);
+ l = avahi_string_list_add_pair(l, "uname", sysname);
+ }
+
+ t = pw_get_host_name();
+ l = avahi_string_list_add_pair(l, "fqdn", t);
+ l = avahi_string_list_add_printf(l, "cookie=0x%08x", info->cookie);
+
+ return l;
+}
+
+static void clear_entry_group(struct service *s)
+{
+ if (s->entry_group == NULL)
+ return;
+
+ avahi_entry_group_free(s->entry_group);
+ s->entry_group = NULL;
+}
+
+static void publish_service(struct service *s);
+
+static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata)
+{
+ struct service *s = userdata;
+
+ spa_assert(s);
+ if (!s->published) {
+ pw_log_info("cancel unpublished service: %s", s->service_name);
+ clear_entry_group(s);
+ return;
+ }
+
+ switch (state) {
+ case AVAHI_ENTRY_GROUP_ESTABLISHED:
+ pw_log_info("established service: %s", s->service_name);
+ break;
+ case AVAHI_ENTRY_GROUP_COLLISION:
+ {
+ char *t;
+
+ t = avahi_alternative_service_name(s->service_name);
+ pw_log_info("service name collision: renaming '%s' to '%s'", s->service_name, t);
+ snprintf(s->service_name, sizeof(s->service_name), "%s", t);
+ avahi_free(t);
+
+ unpublish_service(s);
+ publish_service(s);
+ break;
+ }
+ case AVAHI_ENTRY_GROUP_FAILURE:
+ pw_log_error("failed to establish service '%s': %s",
+ s->service_name,
+ avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
+ unpublish_service(s);
+ clear_entry_group(s);
+ break;
+
+ case AVAHI_ENTRY_GROUP_UNCOMMITED:
+ case AVAHI_ENTRY_GROUP_REGISTERING:
+ break;
+ }
+}
+
+#define PA_CHANNEL_MAP_SNPRINT_MAX (CHANNELS_MAX * 32)
+
+static AvahiStringList *get_service_txt(const struct service *s)
+{
+ static const char * const subtype_text[] = {
+ [SUBTYPE_HARDWARE] = "hardware",
+ [SUBTYPE_VIRTUAL] = "virtual",
+ [SUBTYPE_MONITOR] = "monitor"
+ };
+
+ static const struct mapping {
+ const char *pw_key, *txt_key;
+ } mappings[] = {
+ { PW_KEY_NODE_DESCRIPTION, "description" },
+ { PW_KEY_DEVICE_VENDOR_NAME, "vendor-name" },
+ { PW_KEY_DEVICE_PRODUCT_NAME, "product-name" },
+ { PW_KEY_DEVICE_CLASS, "class" },
+ { PW_KEY_DEVICE_FORM_FACTOR, "form-factor" },
+ { PW_KEY_DEVICE_ICON_NAME, "icon-name" },
+ };
+
+ char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+ AvahiStringList *txt = NULL;
+
+ txt = txt_record_server_data(s->userdata->manager->info, txt);
+
+ txt = avahi_string_list_add_pair(txt, "device", s->name);
+ txt = avahi_string_list_add_printf(txt, "rate=%u", s->ss.rate);
+ txt = avahi_string_list_add_printf(txt, "channels=%u", s->ss.channels);
+ txt = avahi_string_list_add_pair(txt, "format", format_id2paname(s->ss.format));
+ txt = avahi_string_list_add_pair(txt, "channel_map", channel_map_snprint(cm, sizeof(cm), &s->cm));
+ txt = avahi_string_list_add_pair(txt, "subtype", subtype_text[s->subtype]);
+
+ SPA_FOR_EACH_ELEMENT_VAR(mappings, m) {
+ const char *value = pw_properties_get(s->props, m->pw_key);
+ if (value != NULL)
+ txt = avahi_string_list_add_pair(txt, m->txt_key, value);
+ }
+
+ return txt;
+}
+
+static struct server *find_server(struct service *s, int *proto, uint16_t *port)
+{
+ struct module_zeroconf_publish_data *d = s->userdata;
+ struct impl *impl = d->module->impl;
+ struct server *server;
+
+ spa_list_for_each(server, &impl->servers, link) {
+ if (server->addr.ss_family == AF_INET) {
+ *proto = AVAHI_PROTO_INET;
+ *port = ntohs(((struct sockaddr_in*) &server->addr)->sin_port);
+ return server;
+ } else if (server->addr.ss_family == AF_INET6) {
+ *proto = AVAHI_PROTO_INET6;
+ *port = ntohs(((struct sockaddr_in6*) &server->addr)->sin6_port);
+ return server;
+ }
+ }
+
+ return NULL;
+}
+
+static void publish_service(struct service *s)
+{
+ struct module_zeroconf_publish_data *d = s->userdata;
+ int proto;
+ uint16_t port;
+
+ struct server *server = find_server(s, &proto, &port);
+ if (!server)
+ return;
+
+ pw_log_debug("found server:%p proto:%d port:%d", server, proto, port);
+
+ if (!d->client || avahi_client_get_state(d->client) != AVAHI_CLIENT_S_RUNNING)
+ return;
+
+ s->published = true;
+ if (!s->entry_group) {
+ s->entry_group = avahi_entry_group_new(d->client, service_entry_group_callback, s);
+ if (s->entry_group == NULL) {
+ pw_log_error("avahi_entry_group_new(): %s",
+ avahi_strerror(avahi_client_errno(d->client)));
+ goto error;
+ }
+ } else {
+ avahi_entry_group_reset(s->entry_group);
+ }
+
+ if (s->txt == NULL)
+ s->txt = get_service_txt(s);
+
+ if (avahi_entry_group_add_service_strlst(
+ s->entry_group,
+ AVAHI_IF_UNSPEC, proto,
+ 0,
+ s->service_name,
+ s->service_type,
+ NULL,
+ NULL,
+ port,
+ s->txt) < 0) {
+ pw_log_error("avahi_entry_group_add_service_strlst(): %s",
+ avahi_strerror(avahi_client_errno(d->client)));
+ goto error;
+ }
+
+ if (avahi_entry_group_add_service_subtype(
+ s->entry_group,
+ AVAHI_IF_UNSPEC, proto,
+ 0,
+ s->service_name,
+ s->service_type,
+ NULL,
+ s->is_sink ? (s->subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SINK_HARDWARE : SERVICE_SUBTYPE_SINK_VIRTUAL) :
+ (s->subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SOURCE_HARDWARE : (s->subtype == SUBTYPE_VIRTUAL ? SERVICE_SUBTYPE_SOURCE_VIRTUAL : SERVICE_SUBTYPE_SOURCE_MONITOR))) < 0) {
+
+ pw_log_error("avahi_entry_group_add_service_subtype(): %s",
+ avahi_strerror(avahi_client_errno(d->client)));
+ goto error;
+ }
+
+ if (!s->is_sink && s->subtype != SUBTYPE_MONITOR) {
+ if (avahi_entry_group_add_service_subtype(
+ s->entry_group,
+ AVAHI_IF_UNSPEC, proto,
+ 0,
+ s->service_name,
+ SERVICE_TYPE_SOURCE,
+ NULL,
+ SERVICE_SUBTYPE_SOURCE_NON_MONITOR) < 0) {
+ pw_log_error("avahi_entry_group_add_service_subtype(): %s",
+ avahi_strerror(avahi_client_errno(d->client)));
+ goto error;
+ }
+ }
+
+ if (avahi_entry_group_commit(s->entry_group) < 0) {
+ pw_log_error("avahi_entry_group_commit(): %s",
+ avahi_strerror(avahi_client_errno(d->client)));
+ goto error;
+ }
+
+ spa_list_remove(&s->link);
+ spa_list_append(&d->published, &s->link);
+ s->server = server;
+
+ pw_log_info("created service: %s", s->service_name);
+ return;
+
+error:
+ s->published = false;
+ return;
+}
+
+static void publish_pending(struct module_zeroconf_publish_data *data)
+{
+ struct service *s, *next;
+
+ spa_list_for_each_safe(s, next, &data->pending, link)
+ publish_service(s);
+}
+
+static void clear_pending_entry_groups(struct module_zeroconf_publish_data *data)
+{
+ struct service *s;
+
+ spa_list_for_each(s, &data->pending, link)
+ clear_entry_group(s);
+}
+
+static void client_callback(AvahiClient *c, AvahiClientState state, void *d)
+{
+ struct module_zeroconf_publish_data *data = d;
+
+ spa_assert(c);
+ spa_assert(data);
+
+ data->client = c;
+
+ switch (state) {
+ case AVAHI_CLIENT_S_RUNNING:
+ pw_log_info("the avahi daemon is up and running");
+ publish_pending(data);
+ break;
+ case AVAHI_CLIENT_S_COLLISION:
+ pw_log_error("host name collision");
+ unpublish_all_services(d);
+ break;
+ case AVAHI_CLIENT_FAILURE:
+ {
+ int err = avahi_client_errno(data->client);
+
+ pw_log_error("avahi client failure: %s", avahi_strerror(err));
+
+ unpublish_all_services(data);
+ clear_pending_entry_groups(data);
+ avahi_client_free(data->client);
+ data->client = NULL;
+
+ if (err == AVAHI_ERR_DISCONNECTED) {
+ data->client = avahi_client_new(data->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, data, &err);
+ if (data->client == NULL)
+ pw_log_error("failed to create avahi client: %s", avahi_strerror(err));
+ }
+
+ if (data->client == NULL)
+ module_schedule_unload(data->module);
+
+ break;
+ }
+ case AVAHI_CLIENT_CONNECTING:
+ pw_log_info("connecting to the avahi daemon...");
+ break;
+ default:
+ break;
+ }
+}
+
+static void manager_removed(void *d, struct pw_manager_object *o)
+{
+ if (!pw_manager_object_is_sink(o) && !pw_manager_object_is_source(o))
+ return;
+
+ struct service *s = pw_manager_object_get_data(o, SERVICE_DATA_ID);
+ if (s == NULL)
+ return;
+
+ service_free(s);
+}
+
+static void manager_added(void *d, struct pw_manager_object *o)
+{
+ struct service *s;
+ struct pw_node_info *info;
+ const char *str;
+
+ if (!pw_manager_object_is_sink(o) && !pw_manager_object_is_source(o))
+ return;
+
+ info = o->info;
+ if (info == NULL || info->props == NULL)
+ return;
+
+ if ((str = spa_dict_lookup(info->props, PW_KEY_NODE_NETWORK)) != NULL &&
+ spa_atob(str))
+ return;
+
+ s = create_service(d, o);
+ if (s == NULL)
+ return;
+
+ publish_service(s);
+}
+
+static const struct pw_manager_events manager_events = {
+ PW_VERSION_MANAGER_EVENTS,
+ .added = manager_added,
+ .removed = manager_removed,
+};
+
+
+static void impl_server_started(void *data, struct server *server)
+{
+ struct module_zeroconf_publish_data *d = data;
+ pw_log_info("a new server is started, try publish");
+ publish_pending(d);
+}
+
+static void impl_server_stopped(void *data, struct server *server)
+{
+ struct module_zeroconf_publish_data *d = data;
+ pw_log_info("a server stopped, try republish");
+
+ struct service *s, *tmp;
+ spa_list_for_each_safe(s, tmp, &d->published, link) {
+ if (s->server == server)
+ unpublish_service(s);
+ }
+
+ publish_pending(d);
+}
+
+static const struct impl_events impl_events = {
+ VERSION_IMPL_EVENTS,
+ .server_started = impl_server_started,
+ .server_stopped = impl_server_stopped,
+};
+
+static int module_zeroconf_publish_load(struct module *module)
+{
+ struct module_zeroconf_publish_data *data = module->user_data;
+ struct pw_loop *loop;
+ int error;
+
+ data->core = pw_context_connect(module->impl->context, NULL, 0);
+ if (data->core == NULL) {
+ pw_log_error("failed to connect to pipewire: %m");
+ return -errno;
+ }
+
+ pw_core_add_listener(data->core,
+ &data->core_listener,
+ &core_events, data);
+
+ loop = pw_context_get_main_loop(module->impl->context);
+ data->avahi_poll = pw_avahi_poll_new(loop);
+
+ data->client = avahi_client_new(data->avahi_poll, AVAHI_CLIENT_NO_FAIL,
+ client_callback, data, &error);
+ if (!data->client) {
+ pw_log_error("failed to create avahi client: %s", avahi_strerror(error));
+ return -errno;
+ }
+
+ data->manager = pw_manager_new(data->core);
+ if (data->manager == NULL) {
+ pw_log_error("failed to create pipewire manager: %m");
+ return -errno;
+ }
+
+ pw_manager_add_listener(data->manager, &data->manager_listener,
+ &manager_events, data);
+
+ impl_add_listener(module->impl, &data->impl_listener, &impl_events, data);
+
+ return 0;
+}
+
+static int module_zeroconf_publish_unload(struct module *module)
+{
+ struct module_zeroconf_publish_data *d = module->user_data;
+ struct service *s;
+
+ spa_hook_remove(&d->impl_listener);
+
+ unpublish_all_services(d);
+
+ spa_list_consume(s, &d->pending, link)
+ service_free(s);
+
+ if (d->client)
+ avahi_client_free(d->client);
+
+ if (d->avahi_poll)
+ pw_avahi_poll_free(d->avahi_poll);
+
+ if (d->manager != NULL) {
+ spa_hook_remove(&d->manager_listener);
+ pw_manager_destroy(d->manager);
+ }
+
+ if (d->core != NULL) {
+ spa_hook_remove(&d->core_listener);
+ pw_core_disconnect(d->core);
+ }
+
+ return 0;
+}
+
+static const struct spa_dict_item module_zeroconf_publish_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io" },
+ { PW_KEY_MODULE_DESCRIPTION, "mDNS/DNS-SD Service Publish" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static int module_zeroconf_publish_prepare(struct module * const module)
+{
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ struct module_zeroconf_publish_data * const data = module->user_data;
+ data->module = module;
+ spa_list_init(&data->pending);
+ spa_list_init(&data->published);
+
+ return 0;
+}
+
+DEFINE_MODULE_INFO(module_zeroconf_publish) = {
+ .name = "module-zeroconf-publish",
+ .prepare = module_zeroconf_publish_prepare,
+ .load = module_zeroconf_publish_load,
+ .unload = module_zeroconf_publish_unload,
+ .properties = &SPA_DICT_INIT_ARRAY(module_zeroconf_publish_info),
+ .data_size = sizeof(struct module_zeroconf_publish_data),
+};
diff --git a/src/modules/module-protocol-pulse/operation.c b/src/modules/module-protocol-pulse/operation.c
new file mode 100644
index 0000000..1c9833d
--- /dev/null
+++ b/src/modules/module-protocol-pulse/operation.c
@@ -0,0 +1,92 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+
+#include <spa/utils/list.h>
+#include <pipewire/log.h>
+
+#include "client.h"
+#include "log.h"
+#include "manager.h"
+#include "operation.h"
+#include "reply.h"
+
+int operation_new_cb(struct client *client, uint32_t tag,
+ void (*callback)(void *data, struct client *client, uint32_t tag),
+ void *data)
+{
+ struct operation *o;
+
+ if ((o = calloc(1, sizeof(*o))) == NULL)
+ return -errno;
+
+ o->client = client;
+ o->tag = tag;
+ o->callback = callback;
+ o->data = data;
+
+ spa_list_append(&client->operations, &o->link);
+ pw_manager_sync(client->manager);
+
+ pw_log_debug("client %p [%s]: new operation tag:%u", client, client->name, tag);
+
+ return 0;
+}
+
+int operation_new(struct client *client, uint32_t tag)
+{
+ return operation_new_cb(client, tag, NULL, NULL);
+}
+
+void operation_free(struct operation *o)
+{
+ spa_list_remove(&o->link);
+ free(o);
+}
+
+struct operation *operation_find(struct client *client, uint32_t tag)
+{
+ struct operation *o;
+ spa_list_for_each(o, &client->operations, link) {
+ if (o->tag == tag)
+ return o;
+ }
+ return NULL;
+}
+
+void operation_complete(struct operation *o)
+{
+ struct client *client = o->client;
+
+ pw_log_info("[%s]: tag:%u complete", client->name, o->tag);
+
+ spa_list_remove(&o->link);
+
+ if (o->callback)
+ o->callback(o->data, client, o->tag);
+ else
+ reply_simple_ack(client, o->tag);
+ free(o);
+}
diff --git a/src/modules/module-protocol-pulse/operation.h b/src/modules/module-protocol-pulse/operation.h
new file mode 100644
index 0000000..1fa07cc
--- /dev/null
+++ b/src/modules/module-protocol-pulse/operation.h
@@ -0,0 +1,50 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSER_SERVER_OPERATION_H
+#define PULSER_SERVER_OPERATION_H
+
+#include <stdint.h>
+
+#include <spa/utils/list.h>
+
+struct client;
+
+struct operation {
+ struct spa_list link;
+ struct client *client;
+ uint32_t tag;
+ void (*callback) (void *data, struct client *client, uint32_t tag);
+ void *data;
+};
+
+int operation_new(struct client *client, uint32_t tag);
+int operation_new_cb(struct client *client, uint32_t tag,
+ void (*callback) (void *data, struct client *client, uint32_t tag),
+ void *data);
+struct operation *operation_find(struct client *client, uint32_t tag);
+void operation_free(struct operation *o);
+void operation_complete(struct operation *o);
+
+#endif /* PULSER_SERVER_OPERATION_H */
diff --git a/src/modules/module-protocol-pulse/pending-sample.c b/src/modules/module-protocol-pulse/pending-sample.c
new file mode 100644
index 0000000..399fc3b
--- /dev/null
+++ b/src/modules/module-protocol-pulse/pending-sample.c
@@ -0,0 +1,50 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/utils/list.h>
+#include <spa/utils/hook.h>
+#include <pipewire/work-queue.h>
+
+#include "client.h"
+#include "internal.h"
+#include "log.h"
+#include "operation.h"
+#include "pending-sample.h"
+#include "sample-play.h"
+
+void pending_sample_free(struct pending_sample *ps)
+{
+ struct client * const client = ps->client;
+ struct impl * const impl = client->impl;
+ struct operation *o;
+
+ spa_list_remove(&ps->link);
+ spa_hook_remove(&ps->listener);
+ pw_work_queue_cancel(impl->work_queue, ps, SPA_ID_INVALID);
+
+ if ((o = operation_find(client, ps->tag)) != NULL)
+ operation_free(o);
+
+ sample_play_destroy(ps->play);
+}
diff --git a/src/modules/module-protocol-pulse/pending-sample.h b/src/modules/module-protocol-pulse/pending-sample.h
new file mode 100644
index 0000000..f467ca7
--- /dev/null
+++ b/src/modules/module-protocol-pulse/pending-sample.h
@@ -0,0 +1,48 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSE_SERVER_PENDING_SAMPLE_H
+#define PULSE_SERVER_PENDING_SAMPLE_H
+
+#include <stdint.h>
+
+#include <spa/utils/list.h>
+#include <spa/utils/hook.h>
+
+struct client;
+struct sample_play;
+
+struct pending_sample {
+ struct spa_list link;
+ struct client *client;
+ struct sample_play *play;
+ struct spa_hook listener;
+ uint32_t tag;
+ unsigned ready:1;
+ unsigned done:1;
+};
+
+void pending_sample_free(struct pending_sample *ps);
+
+#endif /* PULSE_SERVER_PENDING_SAMPLE_H */
diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c
new file mode 100644
index 0000000..41a814a
--- /dev/null
+++ b/src/modules/module-protocol-pulse/pulse-server.c
@@ -0,0 +1,5689 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include <pipewire/log.h>
+
+#include "log.h"
+
+#include <spa/support/cpu.h>
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/debug/dict.h>
+#include <spa/debug/mem.h>
+#include <spa/debug/types.h>
+#include <spa/param/audio/raw.h>
+#include <spa/pod/pod.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/props.h>
+#include <spa/utils/ringbuffer.h>
+#include <spa/utils/json.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/extensions/metadata.h>
+
+#include "pulse-server.h"
+#include "client.h"
+#include "collect.h"
+#include "commands.h"
+#include "cmd.h"
+#include "dbus-name.h"
+#include "defs.h"
+#include "extension.h"
+#include "format.h"
+#include "internal.h"
+#include "manager.h"
+#include "message.h"
+#include "message-handler.h"
+#include "module.h"
+#include "operation.h"
+#include "pending-sample.h"
+#include "quirks.h"
+#include "reply.h"
+#include "sample.h"
+#include "sample-play.h"
+#include "server.h"
+#include "stream.h"
+#include "utils.h"
+#include "volume.h"
+
+#define DEFAULT_MIN_REQ "256/48000"
+#define DEFAULT_DEFAULT_REQ "960/48000"
+#define DEFAULT_MIN_FRAG "256/48000"
+#define DEFAULT_DEFAULT_FRAG "96000/48000"
+#define DEFAULT_DEFAULT_TLENGTH "96000/48000"
+#define DEFAULT_MIN_QUANTUM "256/48000"
+#define DEFAULT_FORMAT "F32"
+#define DEFAULT_POSITION "[ FL FR ]"
+#define DEFAULT_IDLE_TIMEOUT "0"
+
+#define MAX_FORMATS 32
+/* The max amount of data we send in one block when capturing. In PulseAudio this
+ * size is derived from the mempool PA_MEMPOOL_SLOT_SIZE */
+#define MAX_BLOCK (64*1024)
+
+#define TEMPORARY_MOVE_TIMEOUT (SPA_NSEC_PER_SEC)
+
+PW_LOG_TOPIC_EXTERN(pulse_conn);
+
+bool debug_messages = false;
+
+struct latency_offset_data {
+ int64_t prev_latency_offset;
+ uint8_t initialized:1;
+};
+
+struct temporary_move_data {
+ uint32_t peer_index;
+ uint8_t used:1;
+};
+
+static struct sample *find_sample(struct impl *impl, uint32_t index, const char *name)
+{
+ union pw_map_item *item;
+
+ if (index != SPA_ID_INVALID)
+ return pw_map_lookup(&impl->samples, index);
+
+ pw_array_for_each(item, &impl->samples.items) {
+ struct sample *s = item->data;
+ if (!pw_map_item_is_free(item) &&
+ spa_streq(s->name, name))
+ return s;
+ }
+ return NULL;
+}
+
+void broadcast_subscribe_event(struct impl *impl, uint32_t mask, uint32_t event, 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);
+ }
+}
+
+static int do_command_auth(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct message *reply;
+ uint32_t version;
+ const void *cookie;
+ size_t len;
+
+ if (message_get(m,
+ TAG_U32, &version,
+ TAG_ARBITRARY, &cookie, &len,
+ TAG_INVALID) < 0) {
+ return -EPROTO;
+ }
+ if (version < 8)
+ return -EPROTO;
+ if (len != NATIVE_COOKIE_LENGTH)
+ return -EINVAL;
+
+ if ((version & PROTOCOL_VERSION_MASK) >= 13)
+ version &= PROTOCOL_VERSION_MASK;
+
+ client->version = version;
+ client->authenticated = true;
+
+ pw_log_info("client:%p AUTH tag:%u version:%d", client, tag, version);
+
+ reply = reply_new(client, tag);
+ message_put(reply,
+ TAG_U32, PROTOCOL_VERSION,
+ TAG_INVALID);
+
+ return client_queue_message(client, reply);
+}
+
+static int reply_set_client_name(struct client *client, uint32_t tag)
+{
+ struct pw_manager *manager = client->manager;
+ struct message *reply;
+ struct pw_client *c;
+ uint32_t id, index;
+
+ c = pw_core_get_client(client->core);
+ if (c == NULL)
+ return -ENOENT;
+
+ id = pw_proxy_get_bound_id((struct pw_proxy*)c);
+ index = id_to_index(manager, id);
+
+ pw_log_info("[%s] reply tag:%u id:%u index:%u", client->name, tag, id, index);
+
+ reply = reply_new(client, tag);
+
+ if (client->version >= 13) {
+ message_put(reply,
+ TAG_U32, index, /* client index */
+ TAG_INVALID);
+ }
+ return client_queue_message(client, reply);
+}
+
+static void manager_sync(void *data)
+{
+ struct client *client = data;
+ struct operation *o;
+
+ pw_log_debug("%p: manager sync", client);
+
+ if (client->connect_tag != SPA_ID_INVALID) {
+ reply_set_client_name(client, client->connect_tag);
+ client->connect_tag = SPA_ID_INVALID;
+ }
+
+ client->ref++;
+ spa_list_consume(o, &client->operations, link)
+ operation_complete(o);
+ client_unref(client);
+}
+
+static struct stream *find_stream(struct client *client, uint32_t index)
+{
+ union pw_map_item *item;
+ pw_array_for_each(item, &client->streams.items) {
+ struct stream *s = item->data;
+ if (!pw_map_item_is_free(item) &&
+ s->index == index)
+ return s;
+ }
+ return NULL;
+}
+
+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;
+
+ if (pw_manager_object_is_sink(o)) {
+ client_queue_subscribe_event(client,
+ SUBSCRIPTION_MASK_SINK,
+ SUBSCRIPTION_EVENT_SINK | type,
+ res_index);
+ }
+ if (pw_manager_object_is_source_or_monitor(o)) {
+ mask = SUBSCRIPTION_MASK_SOURCE;
+ event = SUBSCRIPTION_EVENT_SOURCE;
+ }
+ else if (pw_manager_object_is_sink_input(o)) {
+ mask = SUBSCRIPTION_MASK_SINK_INPUT;
+ event = SUBSCRIPTION_EVENT_SINK_INPUT;
+ }
+ else if (pw_manager_object_is_source_output(o)) {
+ mask = SUBSCRIPTION_MASK_SOURCE_OUTPUT;
+ event = SUBSCRIPTION_EVENT_SOURCE_OUTPUT;
+ }
+ else if (pw_manager_object_is_module(o)) {
+ mask = SUBSCRIPTION_MASK_MODULE;
+ event = SUBSCRIPTION_EVENT_MODULE;
+ }
+ else if (pw_manager_object_is_client(o)) {
+ mask = SUBSCRIPTION_MASK_CLIENT;
+ event = SUBSCRIPTION_EVENT_CLIENT;
+ }
+ else if (pw_manager_object_is_card(o)) {
+ mask = SUBSCRIPTION_MASK_CARD;
+ event = SUBSCRIPTION_EVENT_CARD;
+ } else
+ event = SPA_ID_INVALID;
+
+ if (event != SPA_ID_INVALID)
+ client_queue_subscribe_event(client,
+ mask,
+ event | type,
+ res_index);
+ return 0;
+}
+
+static uint32_t get_temporary_move_target(struct client *client, struct pw_manager_object *o)
+{
+ struct temporary_move_data *d;
+
+ d = pw_manager_object_get_data(o, "temporary_move_data");
+ if (d == NULL || d->peer_index == SPA_ID_INVALID)
+ return SPA_ID_INVALID;
+
+ pw_log_debug("[%s] using temporary move target for index:%d -> index:%d",
+ client->name, o->index, d->peer_index);
+ d->used = true;
+ return d->peer_index;
+}
+
+static void set_temporary_move_target(struct client *client, struct pw_manager_object *o, uint32_t index)
+{
+ struct temporary_move_data *d;
+
+ if (!pw_manager_object_is_sink_input(o) && !pw_manager_object_is_source_output(o))
+ return;
+
+ if (index == SPA_ID_INVALID) {
+ d = pw_manager_object_get_data(o, "temporary_move_data");
+ if (d == NULL)
+ return;
+ if (d->peer_index != SPA_ID_INVALID)
+ pw_log_debug("cleared temporary move target for index:%d", o->index);
+ d->peer_index = SPA_ID_INVALID;
+ d->used = false;
+ return;
+ }
+
+ d = pw_manager_object_add_temporary_data(o, "temporary_move_data",
+ sizeof(struct temporary_move_data),
+ TEMPORARY_MOVE_TIMEOUT);
+ if (d == NULL)
+ return;
+
+ pw_log_debug("[%s] set temporary move target for index:%d to index:%d",
+ client->name, o->index, index);
+ d->peer_index = index;
+ d->used = false;
+}
+
+static void temporary_move_target_timeout(struct client *client, struct pw_manager_object *o)
+{
+ struct temporary_move_data *d = pw_manager_object_get_data(o, "temporary_move_data");
+ struct pw_manager_object *peer;
+
+ /*
+ * Send change event if the temporary data was used, and the peer
+ * is not what we claimed.
+ */
+
+ if (d == NULL || d->peer_index == SPA_ID_INVALID || !d->used)
+ goto done;
+
+ peer = find_linked(client->manager, o->id, pw_manager_object_is_sink_input(o) ?
+ PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT);
+ if (peer == NULL || peer->index != d->peer_index) {
+ pw_log_debug("[%s] temporary move timeout for index:%d, send change event",
+ client->name, o->index);
+ send_object_event(client, o, SUBSCRIPTION_EVENT_CHANGE);
+ }
+
+done:
+ set_temporary_move_target(client, o, SPA_ID_INVALID);
+}
+
+static struct pw_manager_object *find_device(struct client *client,
+ uint32_t index, const char *name, bool sink, bool *is_monitor);
+
+static int64_t get_node_latency_offset(struct pw_manager_object *o)
+{
+ int64_t latency_offset = 0LL;
+ struct pw_manager_param *p;
+
+ spa_list_for_each(p, &o->param_list, link) {
+ if (p->id != SPA_PARAM_Props)
+ continue;
+ if (spa_pod_parse_object(p->param,
+ SPA_TYPE_OBJECT_Props, NULL,
+ SPA_PROP_latencyOffsetNsec, SPA_POD_Long(&latency_offset)) == 1)
+ break;
+ }
+ return latency_offset;
+}
+
+static void send_latency_offset_subscribe_event(struct client *client, struct pw_manager_object *o)
+{
+ struct pw_manager *manager = client->manager;
+ struct latency_offset_data *d;
+ struct pw_node_info *info;
+ const char *str;
+ uint32_t card_id = SPA_ID_INVALID;
+ int64_t latency_offset = 0LL;
+ bool changed = false;
+
+ if (!pw_manager_object_is_sink(o) && !pw_manager_object_is_source_or_monitor(o))
+ return;
+
+ /*
+ * Pulseaudio sends card change events on latency offset change.
+ */
+ if ((info = o->info) == NULL || info->props == NULL)
+ return;
+ if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)) != NULL)
+ card_id = (uint32_t)atoi(str);
+ if (card_id == SPA_ID_INVALID)
+ return;
+
+ d = pw_manager_object_add_data(o, "latency_offset_data", sizeof(struct latency_offset_data));
+ if (d == NULL)
+ return;
+
+ latency_offset = get_node_latency_offset(o);
+ changed = (!d->initialized || latency_offset != d->prev_latency_offset);
+
+ d->prev_latency_offset = latency_offset;
+ d->initialized = true;
+
+ if (changed)
+ client_queue_subscribe_event(client,
+ SUBSCRIPTION_MASK_CARD,
+ SUBSCRIPTION_EVENT_CARD | SUBSCRIPTION_EVENT_CHANGE,
+ id_to_index(manager, card_id));
+}
+
+static void send_default_change_subscribe_event(struct client *client, bool sink, bool source)
+{
+ struct pw_manager_object *def;
+ bool changed = false;
+
+ if (sink) {
+ def = find_device(client, SPA_ID_INVALID, NULL, true, NULL);
+ if (client->prev_default_sink != def) {
+ client->prev_default_sink = def;
+ changed = true;
+ }
+ }
+
+ if (source) {
+ def = find_device(client, SPA_ID_INVALID, NULL, false, NULL);
+ if (client->prev_default_source != def) {
+ client->prev_default_source = def;
+ changed = true;
+ }
+ }
+
+ if (changed)
+ client_queue_subscribe_event(client,
+ SUBSCRIPTION_MASK_SERVER,
+ SUBSCRIPTION_EVENT_CHANGE |
+ SUBSCRIPTION_EVENT_SERVER,
+ -1);
+}
+
+static void handle_metadata(struct client *client, struct pw_manager_object *old,
+ struct pw_manager_object *new, const char *name)
+{
+ if (spa_streq(name, "default")) {
+ if (client->metadata_default == old)
+ client->metadata_default = new;
+ }
+ else if (spa_streq(name, "route-settings")) {
+ if (client->metadata_routes == old)
+ client->metadata_routes = new;
+ }
+}
+
+static uint32_t frac_to_bytes_round_up(struct spa_fraction val, const struct sample_spec *ss)
+{
+ uint64_t u;
+ u = (uint64_t) (val.num * 1000000UL * (uint64_t) ss->rate) / val.denom;
+ u = (u + 1000000UL - 1) / 1000000UL;
+ u *= sample_spec_frame_size(ss);
+ return (uint32_t) u;
+}
+
+static void clamp_latency(struct stream *s, struct spa_fraction *lat)
+{
+ if (lat->num * s->min_quantum.denom / lat->denom < s->min_quantum.num)
+ lat->num = (s->min_quantum.num * lat->denom +
+ (s->min_quantum.denom -1)) / s->min_quantum.denom;
+}
+
+static uint64_t fix_playback_buffer_attr(struct stream *s, struct buffer_attr *attr,
+ uint32_t rate, struct spa_fraction *lat)
+{
+ uint32_t frame_size, max_prebuf, minreq, latency, max_latency, maxlength;
+ struct defs *defs = &s->impl->defs;
+
+ if ((frame_size = s->frame_size) == 0)
+ frame_size = sample_spec_frame_size(&s->ss);
+ if (frame_size == 0)
+ frame_size = 4;
+
+ maxlength = SPA_ROUND_DOWN(MAXLENGTH, frame_size);
+
+ pw_log_info("[%s] maxlength:%u tlength:%u minreq:%u prebuf:%u max:%u",
+ s->client->name, attr->maxlength, attr->tlength,
+ attr->minreq, attr->prebuf, maxlength);
+
+ minreq = frac_to_bytes_round_up(s->min_req, &s->ss);
+ max_latency = defs->quantum_limit * frame_size;
+
+ if (attr->maxlength == (uint32_t) -1 || attr->maxlength > maxlength)
+ attr->maxlength = maxlength;
+ else
+ attr->maxlength = SPA_ROUND_DOWN(attr->maxlength, frame_size);
+
+ minreq = SPA_MIN(minreq, attr->maxlength);
+
+ if (attr->tlength == (uint32_t) -1)
+ attr->tlength = frac_to_bytes_round_up(s->default_tlength, &s->ss);
+ attr->tlength = SPA_CLAMP(attr->tlength, minreq, attr->maxlength);
+ attr->tlength = SPA_ROUND_UP(attr->tlength, frame_size);
+
+ if (attr->minreq == (uint32_t) -1) {
+ uint32_t process = frac_to_bytes_round_up(s->default_req, &s->ss);
+ /* With low-latency, tlength/4 gives a decent default in all of traditional,
+ * adjust latency and early request modes. */
+ uint32_t m = attr->tlength / 4;
+ m = SPA_ROUND_DOWN(m, frame_size);
+ attr->minreq = SPA_MIN(process, m);
+ }
+ attr->minreq = SPA_MAX(attr->minreq, minreq);
+
+ if (attr->tlength < attr->minreq+frame_size)
+ attr->tlength = SPA_MIN(attr->minreq + frame_size, attr->maxlength);
+
+ if (s->early_requests) {
+ latency = attr->minreq;
+ } else if (s->adjust_latency) {
+ if (attr->tlength > attr->minreq * 2)
+ latency = SPA_MIN(max_latency, (attr->tlength - attr->minreq * 2) / 2);
+ else
+ latency = attr->minreq;
+
+ latency = SPA_ROUND_DOWN(latency, frame_size);
+
+ if (attr->tlength >= latency)
+ attr->tlength -= latency;
+ } else {
+ if (attr->tlength > attr->minreq * 2)
+ latency = SPA_MIN(max_latency, attr->tlength - attr->minreq * 2);
+ else
+ latency = attr->minreq;
+ }
+
+ if (attr->tlength < latency + 2 * attr->minreq)
+ attr->tlength = SPA_MIN(latency + 2 * attr->minreq, attr->maxlength);
+
+ attr->minreq = SPA_ROUND_DOWN(attr->minreq, frame_size);
+ if (attr->minreq <= 0) {
+ attr->minreq = frame_size;
+ attr->tlength += frame_size*2;
+ }
+ if (attr->tlength <= attr->minreq)
+ attr->tlength = SPA_MIN(attr->minreq*2 + frame_size, attr->maxlength);
+
+ max_prebuf = attr->tlength + frame_size - attr->minreq;
+ if (attr->prebuf == (uint32_t) -1 || attr->prebuf > max_prebuf)
+ attr->prebuf = max_prebuf;
+ attr->prebuf = SPA_ROUND_DOWN(attr->prebuf, frame_size);
+
+ attr->fragsize = 0;
+
+ lat->num = latency / frame_size;
+ lat->denom = rate;
+ clamp_latency(s, lat);
+
+ pw_log_info("[%s] maxlength:%u tlength:%u minreq:%u/%u prebuf:%u latency:%u/%u %u",
+ s->client->name, attr->maxlength, attr->tlength,
+ attr->minreq, minreq, attr->prebuf, lat->num, lat->denom, frame_size);
+
+ return lat->num * SPA_USEC_PER_SEC / lat->denom;
+}
+
+static uint64_t set_playback_buffer_attr(struct stream *s, struct buffer_attr *attr)
+{
+ struct spa_fraction lat;
+ uint64_t lat_usec;
+ struct spa_dict_item items[6];
+ char latency[32], rate[32];
+ char attr_maxlength[32];
+ char attr_tlength[32];
+ char attr_prebuf[32];
+ char attr_minreq[32];
+
+ lat_usec = fix_playback_buffer_attr(s, attr, s->ss.rate, &lat);
+
+ s->attr = *attr;
+
+ snprintf(latency, sizeof(latency), "%u/%u", lat.num, lat.denom);
+ snprintf(rate, sizeof(rate), "1/%u", lat.denom);
+ snprintf(attr_maxlength, sizeof(attr_maxlength), "%u", s->attr.maxlength);
+ snprintf(attr_tlength, sizeof(attr_tlength), "%u", s->attr.tlength);
+ snprintf(attr_prebuf, sizeof(attr_prebuf), "%u", s->attr.prebuf);
+ snprintf(attr_minreq, sizeof(attr_minreq), "%u", s->attr.minreq);
+
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_LATENCY, latency);
+ items[1] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_RATE, rate);
+ items[2] = SPA_DICT_ITEM_INIT("pulse.attr.maxlength", attr_maxlength);
+ items[3] = SPA_DICT_ITEM_INIT("pulse.attr.tlength", attr_tlength);
+ items[4] = SPA_DICT_ITEM_INIT("pulse.attr.prebuf", attr_prebuf);
+ items[5] = SPA_DICT_ITEM_INIT("pulse.attr.minreq", attr_minreq);
+ pw_stream_update_properties(s->stream, &SPA_DICT_INIT(items, 6));
+
+ if (s->attr.prebuf > 0)
+ s->in_prebuf = true;
+
+ return lat_usec;
+}
+
+static int reply_create_playback_stream(struct stream *stream, struct pw_manager_object *peer)
+{
+ struct client *client = stream->client;
+ struct pw_manager *manager = client->manager;
+ struct message *reply;
+ uint32_t missing, peer_index;
+ const char *peer_name;
+ uint64_t lat_usec;
+
+ stream->buffer = calloc(1, MAXLENGTH);
+ if (stream->buffer == NULL)
+ return -errno;
+
+ lat_usec = set_playback_buffer_attr(stream, &stream->attr);
+
+ missing = stream_pop_missing(stream);
+ stream->index = id_to_index(manager, stream->id);
+ stream->lat_usec = lat_usec;
+
+ pw_log_info("[%s] reply CREATE_PLAYBACK_STREAM tag:%u index:%u missing:%u lat:%"PRIu64,
+ client->name, stream->create_tag, stream->index, missing, lat_usec);
+
+ reply = reply_new(client, stream->create_tag);
+ message_put(reply,
+ TAG_U32, stream->channel, /* stream index/channel */
+ TAG_U32, stream->index, /* sink_input/stream index */
+ TAG_U32, missing, /* missing/requested bytes */
+ TAG_INVALID);
+
+ if (peer && pw_manager_object_is_sink(peer)) {
+ peer_index = peer->index;
+ peer_name = pw_properties_get(peer->props, PW_KEY_NODE_NAME);
+ } else {
+ peer_index = SPA_ID_INVALID;
+ peer_name = NULL;
+ }
+
+ if (client->version >= 9) {
+ message_put(reply,
+ TAG_U32, stream->attr.maxlength,
+ TAG_U32, stream->attr.tlength,
+ TAG_U32, stream->attr.prebuf,
+ TAG_U32, stream->attr.minreq,
+ TAG_INVALID);
+ }
+ if (client->version >= 12) {
+ message_put(reply,
+ TAG_SAMPLE_SPEC, &stream->ss,
+ TAG_CHANNEL_MAP, &stream->map,
+ TAG_U32, peer_index, /* sink index */
+ TAG_STRING, peer_name, /* sink name */
+ TAG_BOOLEAN, false, /* sink suspended state */
+ TAG_INVALID);
+ }
+ if (client->version >= 13) {
+ message_put(reply,
+ TAG_USEC, lat_usec, /* sink configured latency */
+ TAG_INVALID);
+ }
+ if (client->version >= 21) {
+ struct format_info info;
+ spa_zero(info);
+ info.encoding = ENCODING_PCM;
+ message_put(reply,
+ TAG_FORMAT_INFO, &info, /* sink_input format */
+ TAG_INVALID);
+ }
+
+ stream->create_tag = SPA_ID_INVALID;
+
+ return client_queue_message(client, reply);
+}
+
+static uint64_t fix_record_buffer_attr(struct stream *s, struct buffer_attr *attr,
+ uint32_t rate, struct spa_fraction *lat)
+{
+ uint32_t frame_size, minfrag, latency, maxlength;
+
+ if ((frame_size = s->frame_size) == 0)
+ frame_size = sample_spec_frame_size(&s->ss);
+ if (frame_size == 0)
+ frame_size = 4;
+
+ maxlength = SPA_ROUND_DOWN(MAXLENGTH, frame_size);
+
+ pw_log_info("[%s] maxlength:%u fragsize:%u framesize:%u",
+ s->client->name, attr->maxlength, attr->fragsize,
+ frame_size);
+
+ if (attr->maxlength == (uint32_t) -1 || attr->maxlength > maxlength)
+ attr->maxlength = maxlength;
+ else
+ attr->maxlength = SPA_ROUND_DOWN(attr->maxlength, frame_size);
+ attr->maxlength = SPA_MAX(attr->maxlength, frame_size);
+
+ minfrag = frac_to_bytes_round_up(s->min_frag, &s->ss);
+
+ if (attr->fragsize == (uint32_t) -1 || attr->fragsize == 0)
+ attr->fragsize = frac_to_bytes_round_up(s->default_frag, &s->ss);
+ attr->fragsize = SPA_CLAMP(attr->fragsize, minfrag, attr->maxlength);
+ attr->fragsize = SPA_ROUND_UP(attr->fragsize, frame_size);
+
+ attr->tlength = attr->minreq = attr->prebuf = 0;
+
+ /* make sure we can queue at least to fragsize without overruns */
+ if (attr->maxlength < attr->fragsize * 4) {
+ attr->maxlength = attr->fragsize * 4;
+ if (attr->maxlength > maxlength) {
+ attr->maxlength = maxlength;
+ attr->fragsize = SPA_ROUND_DOWN(maxlength / 4, frame_size);
+ }
+ }
+
+ latency = attr->fragsize;
+
+ lat->num = latency / frame_size;
+ lat->denom = rate;
+ clamp_latency(s, lat);
+
+ pw_log_info("[%s] maxlength:%u fragsize:%u minfrag:%u latency:%u/%u",
+ s->client->name, attr->maxlength, attr->fragsize, minfrag,
+ lat->num, lat->denom);
+
+ return lat->num * SPA_USEC_PER_SEC / lat->denom;
+}
+
+static uint64_t set_record_buffer_attr(struct stream *s, struct buffer_attr *attr)
+{
+ struct spa_dict_item items[4];
+ char latency[32], rate[32];
+ char attr_maxlength[32];
+ char attr_fragsize[32];
+ struct spa_fraction lat;
+ uint64_t lat_usec;
+
+ lat_usec = fix_record_buffer_attr(s, attr, s->ss.rate, &lat);
+
+ s->attr = *attr;
+
+ snprintf(latency, sizeof(latency), "%u/%u", lat.num, lat.denom);
+ snprintf(rate, sizeof(rate), "1/%u", lat.denom);
+
+ snprintf(attr_maxlength, sizeof(attr_maxlength), "%u", s->attr.maxlength);
+ snprintf(attr_fragsize, sizeof(attr_fragsize), "%u", s->attr.fragsize);
+
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_LATENCY, latency);
+ items[1] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_RATE, rate);
+ items[2] = SPA_DICT_ITEM_INIT("pulse.attr.maxlength", attr_maxlength);
+ items[3] = SPA_DICT_ITEM_INIT("pulse.attr.fragsize", attr_fragsize);
+ pw_stream_update_properties(s->stream, &SPA_DICT_INIT(items, 4));
+
+ return lat_usec;
+}
+
+static int reply_create_record_stream(struct stream *stream, struct pw_manager_object *peer)
+{
+ struct client *client = stream->client;
+ struct pw_manager *manager = client->manager;
+ char *tmp;
+ struct message *reply;
+ const char *peer_name, *name;
+ uint32_t peer_index;
+ uint64_t lat_usec;
+
+ stream->buffer = calloc(1, MAXLENGTH);
+ if (stream->buffer == NULL)
+ return -errno;
+
+ lat_usec = set_record_buffer_attr(stream, &stream->attr);
+
+ stream->index = id_to_index(manager, stream->id);
+ stream->lat_usec = lat_usec;
+
+ pw_log_info("[%s] reply CREATE_RECORD_STREAM tag:%u index:%u latency:%"PRIu64,
+ client->name, stream->create_tag, stream->index, lat_usec);
+
+ reply = reply_new(client, stream->create_tag);
+ message_put(reply,
+ TAG_U32, stream->channel, /* stream index/channel */
+ TAG_U32, stream->index, /* source_output/stream index */
+ TAG_INVALID);
+
+ if (peer && pw_manager_object_is_sink_input(peer))
+ peer = find_linked(manager, peer->id, PW_DIRECTION_OUTPUT);
+ if (peer && pw_manager_object_is_source_or_monitor(peer)) {
+ name = pw_properties_get(peer->props, PW_KEY_NODE_NAME);
+ peer_index = peer->index;
+ if (!pw_manager_object_is_source(peer)) {
+ size_t len = (name ? strlen(name) : 5) + 10;
+ peer_name = tmp = alloca(len);
+ snprintf(tmp, len, "%s.monitor", name ? name : "sink");
+ } else {
+ peer_name = name;
+ }
+ } else {
+ peer_index = SPA_ID_INVALID;
+ peer_name = NULL;
+ }
+
+ if (client->version >= 9) {
+ message_put(reply,
+ TAG_U32, stream->attr.maxlength,
+ TAG_U32, stream->attr.fragsize,
+ TAG_INVALID);
+ }
+ if (client->version >= 12) {
+ message_put(reply,
+ TAG_SAMPLE_SPEC, &stream->ss,
+ TAG_CHANNEL_MAP, &stream->map,
+ TAG_U32, peer_index, /* source index */
+ TAG_STRING, peer_name, /* source name */
+ TAG_BOOLEAN, false, /* source suspended state */
+ TAG_INVALID);
+ }
+ if (client->version >= 13) {
+ message_put(reply,
+ TAG_USEC, lat_usec, /* source configured latency */
+ TAG_INVALID);
+ }
+ if (client->version >= 22) {
+ struct format_info info;
+ spa_zero(info);
+ info.encoding = ENCODING_PCM;
+ message_put(reply,
+ TAG_FORMAT_INFO, &info, /* source_output format */
+ TAG_INVALID);
+ }
+
+ stream->create_tag = SPA_ID_INVALID;
+
+ return client_queue_message(client, reply);
+}
+
+static int reply_create_stream(struct stream *stream, struct pw_manager_object *peer)
+{
+ stream->peer_index = peer->index;
+ return stream->direction == PW_DIRECTION_OUTPUT ?
+ reply_create_playback_stream(stream, peer) :
+ reply_create_record_stream(stream, peer);
+}
+
+static void manager_added(void *data, struct pw_manager_object *o)
+{
+ struct client *client = data;
+ struct pw_manager *manager = client->manager;
+ const char *str;
+
+ register_object_message_handlers(o);
+
+ if (strcmp(o->type, PW_TYPE_INTERFACE_Core) == 0 && manager->info != NULL) {
+ struct pw_core_info *info = manager->info;
+ if (info->props) {
+ if ((str = spa_dict_lookup(info->props, "default.clock.rate")) != NULL)
+ client->impl->defs.sample_spec.rate = atoi(str);
+ if ((str = spa_dict_lookup(info->props, "default.clock.quantum-limit")) != NULL)
+ client->impl->defs.quantum_limit = atoi(str);
+ }
+ }
+
+ if (spa_streq(o->type, PW_TYPE_INTERFACE_Metadata)) {
+ if (o->props != NULL &&
+ (str = pw_properties_get(o->props, PW_KEY_METADATA_NAME)) != NULL)
+ handle_metadata(client, NULL, o, str);
+ }
+
+ if (spa_streq(o->type, PW_TYPE_INTERFACE_Link)) {
+ struct stream *s, *t;
+ struct pw_manager_object *peer = NULL;
+ union pw_map_item *item;
+ pw_array_for_each(item, &client->streams.items) {
+ struct stream *s = item->data;
+ const char *peer_name;
+
+ if (pw_map_item_is_free(item) || s->pending)
+ continue;
+ if (s->peer_index == SPA_ID_INVALID)
+ continue;
+
+ peer = find_peer_for_link(manager, o, s->id, s->direction);
+ if (peer == NULL || peer->props == NULL ||
+ peer->index == s->peer_index)
+ continue;
+
+ s->peer_index = peer->index;
+
+ peer_name = pw_properties_get(peer->props, PW_KEY_NODE_NAME);
+ if (peer_name && s->direction == PW_DIRECTION_INPUT &&
+ pw_manager_object_is_monitor(peer)) {
+ int len = strlen(peer_name) + 10;
+ char *tmp = alloca(len);
+ snprintf(tmp, len, "%s.monitor", peer_name);
+ peer_name = tmp;
+ }
+ if (peer_name != NULL)
+ stream_send_moved(s, peer->index, peer_name);
+ }
+ spa_list_for_each_safe(s, t, &client->pending_streams, link) {
+ peer = find_peer_for_link(manager, o, s->id, s->direction);
+ if (peer) {
+ reply_create_stream(s, peer);
+ spa_list_remove(&s->link);
+ s->pending = false;
+ }
+ }
+ }
+
+ send_object_event(client, o, SUBSCRIPTION_EVENT_NEW);
+
+ /* Adding sinks etc. may also change defaults */
+ send_default_change_subscribe_event(client, pw_manager_object_is_sink(o), pw_manager_object_is_source_or_monitor(o));
+}
+
+static void manager_updated(void *data, struct pw_manager_object *o)
+{
+ struct client *client = data;
+
+ send_object_event(client, o, SUBSCRIPTION_EVENT_CHANGE);
+
+ set_temporary_move_target(client, o, SPA_ID_INVALID);
+
+ send_latency_offset_subscribe_event(client, o);
+ send_default_change_subscribe_event(client, pw_manager_object_is_sink(o), pw_manager_object_is_source_or_monitor(o));
+}
+
+static void manager_removed(void *data, struct pw_manager_object *o)
+{
+ struct client *client = data;
+ const char *str;
+
+ send_object_event(client, o, SUBSCRIPTION_EVENT_REMOVE);
+
+ send_default_change_subscribe_event(client, pw_manager_object_is_sink(o), pw_manager_object_is_source_or_monitor(o));
+
+ if (spa_streq(o->type, PW_TYPE_INTERFACE_Metadata)) {
+ if (o->props != NULL &&
+ (str = pw_properties_get(o->props, PW_KEY_METADATA_NAME)) != NULL)
+ handle_metadata(client, o, NULL, str);
+ }
+}
+
+static void manager_object_data_timeout(void *data, struct pw_manager_object *o, const char *key)
+{
+ struct client *client = data;
+
+ if (spa_streq(key, "temporary_move_data"))
+ 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)
+{
+ struct client *client = data;
+ bool changed = false;
+
+ pw_log_debug("meta id:%d subject:%d key:%s type:%s value:%s",
+ o->id, subject, key, type, value);
+
+ if (subject == PW_ID_CORE && o == client->metadata_default) {
+ char name[1024];
+
+ if (key == NULL || spa_streq(key, "default.audio.sink")) {
+ if (value != NULL) {
+ if (json_object_find(value,
+ "name", name, sizeof(name)) < 0)
+ value = NULL;
+ else
+ value = name;
+ }
+ if ((changed = !spa_streq(client->default_sink, value))) {
+ free(client->default_sink);
+ client->default_sink = value ? strdup(value) : NULL;
+ }
+ free(client->temporary_default_sink);
+ client->temporary_default_sink = NULL;
+ }
+ if (key == NULL || spa_streq(key, "default.audio.source")) {
+ if (value != NULL) {
+ if (json_object_find(value,
+ "name", name, sizeof(name)) < 0)
+ value = NULL;
+ else
+ value = name;
+ }
+ if ((changed = !spa_streq(client->default_source, value))) {
+ free(client->default_source);
+ client->default_source = value ? strdup(value) : NULL;
+ }
+ free(client->temporary_default_source);
+ client->temporary_default_source = NULL;
+ }
+ if (changed)
+ send_default_change_subscribe_event(client, true, true);
+ }
+ if (subject == PW_ID_CORE && o == client->metadata_routes) {
+ if (key == NULL)
+ pw_properties_clear(client->routes);
+ else
+ pw_properties_set(client->routes, key, value);
+ }
+}
+
+
+static void do_free_client(void *obj, void *data, int res, uint32_t id)
+{
+ struct client *client = obj;
+ client_free(client);
+}
+
+static void manager_disconnect(void *data)
+{
+ struct client *client = data;
+ pw_log_debug("manager_disconnect()");
+ pw_work_queue_add(client->impl->work_queue, client, 0,
+ do_free_client, NULL);
+}
+
+static const struct pw_manager_events manager_events = {
+ PW_VERSION_MANAGER_EVENTS,
+ .sync = manager_sync,
+ .added = manager_added,
+ .updated = manager_updated,
+ .removed = manager_removed,
+ .metadata = manager_metadata,
+ .disconnect = manager_disconnect,
+ .object_data_timeout = manager_object_data_timeout,
+};
+
+static int do_set_client_name(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ const char *name = NULL;
+ int res = 0, changed = 0;
+
+ if (client->version < 13) {
+ if (message_get(m,
+ TAG_STRING, &name,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+ if (name)
+ changed += pw_properties_set(client->props,
+ PW_KEY_APP_NAME, name);
+ } else {
+ if (message_get(m,
+ TAG_PROPLIST, client->props,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+ changed++;
+ }
+
+ client_update_quirks(client);
+
+ client->name = pw_properties_get(client->props, PW_KEY_APP_NAME);
+ pw_log_info("[%s] %s tag:%d", client->name,
+ commands[command].name, tag);
+
+ if (client->core == NULL) {
+ client->core = pw_context_connect(impl->context,
+ pw_properties_copy(client->props), 0);
+ if (client->core == NULL) {
+ res = -errno;
+ goto error;
+ }
+ client->manager = pw_manager_new(client->core);
+ if (client->manager == NULL) {
+ res = -errno;
+ goto error;
+ }
+ client->connect_tag = tag;
+ pw_manager_add_listener(client->manager, &client->manager_listener,
+ &manager_events, client);
+ } else {
+ if (changed)
+ pw_core_update_properties(client->core, &client->props->dict);
+
+ if (client->connect_tag == SPA_ID_INVALID)
+ res = reply_set_client_name(client, tag);
+ }
+
+ return res;
+error:
+ pw_log_error("%p: failed to connect client: %s", impl, spa_strerror(res));
+ return res;
+
+}
+
+static int do_subscribe(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ uint32_t mask;
+
+ if (message_get(m,
+ TAG_U32, &mask,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] SUBSCRIBE tag:%u mask:%08x",
+ client->name, tag, mask);
+
+ if (mask & ~SUBSCRIPTION_MASK_ALL)
+ return -EINVAL;
+
+ client->subscribed = mask;
+
+ return reply_simple_ack(client, tag);
+}
+
+static void stream_control_info(void *data, uint32_t id,
+ const struct pw_stream_control *control)
+{
+ struct stream *stream = data;
+
+ switch (id) {
+ case SPA_PROP_channelVolumes:
+ stream->volume.channels = control->n_values;
+ memcpy(stream->volume.values, control->values, control->n_values * sizeof(float));
+ pw_log_info("stream %p: volume changed %f", stream, stream->volume.values[0]);
+ break;
+ case SPA_PROP_mute:
+ stream->muted = control->values[0] >= 0.5;
+ pw_log_info("stream %p: mute changed %d", stream, stream->muted);
+ break;
+ }
+}
+
+static void do_destroy_stream(void *obj, void *data, int res, uint32_t id)
+{
+ struct stream *stream = obj;
+
+ stream_free(stream);
+}
+
+static void stream_state_changed(void *data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct stream *stream = data;
+ struct client *client = stream->client;
+ struct impl *impl = client->impl;
+ bool destroy_stream = false;
+
+ switch (state) {
+ case PW_STREAM_STATE_ERROR:
+ reply_error(client, -1, stream->create_tag, -EIO);
+ destroy_stream = true;
+ break;
+ case PW_STREAM_STATE_UNCONNECTED:
+ if (stream->create_tag != SPA_ID_INVALID)
+ reply_error(client, -1, stream->create_tag, -ENOENT);
+ else
+ stream->killed = true;
+ destroy_stream = true;
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ stream->id = pw_stream_get_node_id(stream->stream);
+ break;
+ case PW_STREAM_STATE_CONNECTING:
+ case PW_STREAM_STATE_STREAMING:
+ break;
+ }
+
+ if (destroy_stream) {
+ pw_work_queue_add(impl->work_queue, stream, 0,
+ do_destroy_stream, NULL);
+ }
+}
+
+static const struct spa_pod *get_buffers_param(struct stream *s,
+ struct buffer_attr *attr, struct spa_pod_builder *b)
+{
+ const struct spa_pod *param;
+ uint32_t blocks, buffers, size, maxsize, stride;
+ struct defs *defs = &s->impl->defs;
+
+ blocks = 1;
+ stride = s->frame_size;
+
+ maxsize = defs->quantum_limit * 32 * s->frame_size;
+ if (s->direction == PW_DIRECTION_OUTPUT) {
+ size = attr->minreq;
+ } else {
+ size = attr->fragsize;
+ }
+ buffers = SPA_CLAMP(maxsize / size, MIN_BUFFERS, MAX_BUFFERS);
+
+ pw_log_info("[%s] stride %d maxsize %d size %u buffers %d", s->client->name,
+ stride, maxsize, size, buffers);
+
+ param = spa_pod_builder_add_object(b,
+ SPA_TYPE_OBJECT_ParamBuffers, SPA_PARAM_Buffers,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_CHOICE_RANGE_Int(buffers,
+ MIN_BUFFERS, MAX_BUFFERS),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(blocks),
+ SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_RANGE_Int(
+ size, size, maxsize),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(stride));
+ return param;
+}
+
+static void stream_param_changed(void *data, uint32_t id, const struct spa_pod *param)
+{
+ struct stream *stream = data;
+ const struct spa_pod *params[4];
+ uint32_t n_params = 0;
+ uint8_t buffer[4096];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ int res;
+
+ if (id != SPA_PARAM_Format || param == NULL)
+ return;
+
+ if ((res = format_parse_param(param, false, &stream->ss, &stream->map, NULL, NULL)) < 0) {
+ pw_stream_set_error(stream->stream, res, "format not supported");
+ return;
+ }
+
+ pw_log_info("[%s] got format:%s rate:%u channels:%u", stream->client->name,
+ format_id2name(stream->ss.format),
+ stream->ss.rate, stream->ss.channels);
+
+ stream->frame_size = sample_spec_frame_size(&stream->ss);
+ if (stream->frame_size == 0) {
+ pw_stream_set_error(stream->stream, res, "format not supported");
+ return;
+ }
+ stream->rate = stream->ss.rate;
+
+ if (stream->create_tag != SPA_ID_INVALID) {
+ struct pw_manager_object *peer;
+
+ if (stream->volume_set) {
+ pw_stream_set_control(stream->stream,
+ SPA_PROP_channelVolumes, stream->volume.channels, stream->volume.values, 0);
+ }
+ if (stream->muted_set) {
+ float val = stream->muted ? 1.0f : 0.0f;
+ pw_stream_set_control(stream->stream,
+ SPA_PROP_mute, 1, &val, 0);
+ }
+ if (stream->corked)
+ stream_set_paused(stream, true, "cork after create");
+
+ /* if peer exists, reply immediately, otherwise reply when the link is created */
+ peer = find_linked(stream->client->manager, stream->id, stream->direction);
+ if (peer) {
+ reply_create_stream(stream, peer);
+ } else {
+ spa_list_append(&stream->client->pending_streams, &stream->link);
+ stream->pending = true;
+ }
+ }
+
+ params[n_params++] = get_buffers_param(stream, &stream->attr, &b);
+ pw_stream_update_params(stream->stream, params, n_params);
+}
+
+static void stream_io_changed(void *data, uint32_t id, void *area, uint32_t size)
+{
+ struct stream *stream = data;
+ switch (id) {
+ case SPA_IO_Position:
+ stream->position = area;
+ break;
+ }
+}
+
+struct process_data {
+ struct pw_time pwt;
+ uint32_t read_inc;
+ uint32_t write_inc;
+ uint32_t underrun_for;
+ uint32_t playing_for;
+ uint32_t minreq;
+ uint32_t quantum;
+ unsigned int underrun:1;
+ unsigned int idle:1;
+};
+
+static int
+do_process_done(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct stream *stream = user_data;
+ struct client *client = stream->client;
+ struct impl *impl = client->impl;
+ const struct process_data *pd = data;
+ uint32_t index, towrite;
+ int32_t avail;
+
+ stream->timestamp = pd->pwt.now;
+ stream->delay = pd->pwt.buffered * SPA_USEC_PER_SEC / stream->ss.rate;
+ if (pd->pwt.rate.denom > 0)
+ stream->delay += pd->pwt.delay * SPA_USEC_PER_SEC * pd->pwt.rate.num / pd->pwt.rate.denom;
+
+ if (stream->direction == PW_DIRECTION_OUTPUT) {
+ if (pd->quantum != stream->last_quantum)
+ stream_update_minreq(stream, pd->minreq);
+ stream->last_quantum = pd->quantum;
+
+ stream->read_index += pd->read_inc;
+ if (stream->corked) {
+ if (stream->underrun_for != (uint64_t)-1)
+ stream->underrun_for += pd->underrun_for;
+ stream->playing_for = 0;
+ return 0;
+ }
+ if (pd->underrun != stream->is_underrun) {
+ stream->is_underrun = pd->underrun;
+ stream->underrun_for = 0;
+ stream->playing_for = 0;
+ if (pd->underrun)
+ stream_send_underflow(stream, stream->read_index);
+ else
+ stream_send_started(stream);
+ }
+ if (pd->idle) {
+ if (!stream->is_idle) {
+ stream->idle_time = stream->timestamp;
+ } else if (!stream->is_paused &&
+ stream->idle_timeout_sec > 0 &&
+ stream->timestamp - stream->idle_time >
+ (stream->idle_timeout_sec * SPA_NSEC_PER_SEC)) {
+ stream_set_paused(stream, true, "long underrun");
+ }
+ }
+ stream->is_idle = pd->idle;
+ stream->playing_for += pd->playing_for;
+ if (stream->underrun_for != (uint64_t)-1)
+ stream->underrun_for += pd->underrun_for;
+
+ stream_send_request(stream);
+ } else {
+ struct message *msg;
+ stream->write_index += pd->write_inc;
+
+ avail = spa_ringbuffer_get_read_index(&stream->ring, &index);
+
+ if (!spa_list_is_empty(&client->out_messages)) {
+ pw_log_debug("%p: [%s] pending read:%u avail:%d",
+ stream, client->name, index, avail);
+ return 0;
+ }
+
+ if (avail <= 0) {
+ /* underrun, can't really happen but if it does we
+ * do nothing and wait for more data */
+ pw_log_warn("%p: [%s] underrun read:%u avail:%d",
+ stream, client->name, index, avail);
+ } else {
+ if ((uint32_t)avail > stream->attr.maxlength) {
+ uint32_t skip = avail - stream->attr.fragsize;
+ /* overrun, catch up to latest fragment and send it */
+ pw_log_warn("%p: [%s] overrun recover read:%u avail:%d max:%u skip:%u",
+ stream, client->name, index, avail, stream->attr.maxlength, skip);
+ index += skip;
+ stream->read_index += skip;
+ avail = stream->attr.fragsize;
+ }
+ pw_log_trace("avail:%d index:%u", avail, index);
+
+ while ((uint32_t)avail >= stream->attr.fragsize) {
+ towrite = SPA_MIN(avail, MAX_BLOCK);
+ towrite = SPA_MIN(towrite, stream->attr.fragsize);
+ towrite = SPA_ROUND_DOWN(towrite, stream->frame_size);
+
+ msg = message_alloc(impl, stream->channel, towrite);
+ if (msg == NULL)
+ return -errno;
+
+ spa_ringbuffer_read_data(&stream->ring,
+ stream->buffer, MAXLENGTH,
+ index % MAXLENGTH,
+ msg->data, towrite);
+
+ client_queue_message(client, msg);
+
+ index += towrite;
+ avail -= towrite;
+ stream->read_index += towrite;
+ }
+ spa_ringbuffer_read_update(&stream->ring, index);
+ }
+ }
+ return 0;
+}
+
+
+static void stream_process(void *data)
+{
+ struct stream *stream = data;
+ struct client *client = stream->client;
+ struct impl *impl = stream->impl;
+ void *p;
+ struct pw_buffer *buffer;
+ struct spa_buffer *buf;
+ struct spa_data *d;
+ uint32_t offs, size, minreq = 0, index;
+ struct process_data pd;
+ bool do_flush = false;
+
+ if (stream->create_tag != SPA_ID_INVALID)
+ return;
+
+ pw_log_trace_fp("%p: process", stream);
+ buffer = pw_stream_dequeue_buffer(stream->stream);
+ if (buffer == NULL)
+ return;
+
+ buf = buffer->buffer;
+ d = &buf->datas[0];
+ if ((p = d->data) == NULL)
+ return;
+
+ spa_zero(pd);
+
+ if (stream->direction == PW_DIRECTION_OUTPUT) {
+ int32_t avail = spa_ringbuffer_get_read_index(&stream->ring, &index);
+
+ minreq = buffer->requested * stream->frame_size;
+ if (minreq == 0)
+ minreq = stream->attr.minreq;
+
+ pd.minreq = minreq;
+ pd.quantum = stream->position ? stream->position->clock.duration : minreq;
+
+ if (avail < (int32_t)minreq || stream->corked) {
+ /* underrun, produce a silence buffer */
+ size = SPA_MIN(d->maxsize, minreq);
+ memset(p, 0, size);
+
+ if (stream->draining && !stream->corked) {
+ stream->draining = false;
+ do_flush = true;
+ } else {
+ pd.underrun_for = size;
+ pd.underrun = true;
+ }
+ if ((stream->attr.prebuf == 0 || do_flush) && !stream->corked) {
+ if (avail > 0) {
+ avail = SPA_MIN((uint32_t)avail, size);
+ spa_ringbuffer_read_data(&stream->ring,
+ stream->buffer, MAXLENGTH,
+ index % MAXLENGTH,
+ p, avail);
+ }
+ index += size;
+ pd.read_inc = size;
+ spa_ringbuffer_read_update(&stream->ring, index);
+
+ pd.playing_for = size;
+ }
+ pd.idle = true;
+ pw_log_debug("%p: [%s] underrun read:%u avail:%d max:%u",
+ stream, client->name, index, avail, minreq);
+ } else {
+ if (avail > (int32_t)stream->attr.maxlength) {
+ uint32_t skip = avail - stream->attr.maxlength;
+ /* overrun, reported by other side, here we skip
+ * ahead to the oldest data. */
+ pw_log_debug("%p: [%s] overrun read:%u avail:%d max:%u skip:%u",
+ stream, client->name, index, avail,
+ stream->attr.maxlength, skip);
+ index += skip;
+ pd.read_inc = skip;
+ avail = stream->attr.maxlength;
+ }
+ size = SPA_MIN(d->maxsize, (uint32_t)avail);
+ size = SPA_MIN(size, minreq);
+
+ spa_ringbuffer_read_data(&stream->ring,
+ stream->buffer, MAXLENGTH,
+ index % MAXLENGTH,
+ p, size);
+
+ index += size;
+ pd.read_inc += size;
+ spa_ringbuffer_read_update(&stream->ring, index);
+
+ pd.playing_for = size;
+ pd.underrun = false;
+ }
+ d->chunk->offset = 0;
+ d->chunk->stride = stream->frame_size;
+ d->chunk->size = size;
+ buffer->size = size / stream->frame_size;
+ } else {
+ int32_t filled = spa_ringbuffer_get_write_index(&stream->ring, &index);
+
+ offs = SPA_MIN(d->chunk->offset, d->maxsize);
+ size = SPA_MIN(d->chunk->size, d->maxsize - offs);
+
+ if (filled < 0) {
+ /* underrun, can't really happen because we never read more
+ * than what's available on the other side */
+ pw_log_warn("%p: [%s] underrun write:%u filled:%d",
+ stream, client->name, index, filled);
+ } else if ((uint32_t)filled + size > stream->attr.maxlength) {
+ /* overrun, can happen when the other side is not
+ * reading fast enough. We still write our data into the
+ * ringbuffer and expect the other side to warn and catch up. */
+ pw_log_debug("%p: [%s] overrun write:%u filled:%d size:%u max:%u",
+ stream, client->name, index, filled,
+ size, stream->attr.maxlength);
+ }
+
+ spa_ringbuffer_write_data(&stream->ring,
+ stream->buffer, MAXLENGTH,
+ index % MAXLENGTH,
+ SPA_PTROFF(p, offs, void),
+ SPA_MIN(size, MAXLENGTH));
+
+ index += size;
+ pd.write_inc = size;
+ spa_ringbuffer_write_update(&stream->ring, index);
+ }
+ pw_stream_queue_buffer(stream->stream, buffer);
+
+ if (do_flush)
+ pw_stream_flush(stream->stream, true);
+
+ pw_stream_get_time_n(stream->stream, &pd.pwt, sizeof(pd.pwt));
+
+ pw_loop_invoke(impl->loop,
+ do_process_done, 1, &pd, sizeof(pd), false, stream);
+}
+
+static void stream_drained(void *data)
+{
+ struct stream *stream = data;
+ if (stream->drain_tag != 0) {
+ pw_log_info("[%s] drained channel:%u tag:%d",
+ stream->client->name, stream->channel,
+ stream->drain_tag);
+ reply_simple_ack(stream->client, stream->drain_tag);
+ stream->drain_tag = 0;
+
+ pw_stream_set_active(stream->stream, !stream->is_paused);
+ }
+}
+
+static const struct pw_stream_events stream_events =
+{
+ PW_VERSION_STREAM_EVENTS,
+ .control_info = stream_control_info,
+ .state_changed = stream_state_changed,
+ .param_changed = stream_param_changed,
+ .io_changed = stream_io_changed,
+ .process = stream_process,
+ .drained = stream_drained,
+};
+
+static void log_format_info(struct impl *impl, enum spa_log_level level, struct format_info *format)
+{
+ const struct spa_dict_item *it;
+ pw_logt(level, mod_topic, "%p: format %s",
+ impl, format_encoding2name(format->encoding));
+ spa_dict_for_each(it, &format->props->dict)
+ pw_logt(level, mod_topic, "%p: '%s': '%s'",
+ impl, it->key, it->value);
+}
+
+static int do_create_playback_stream(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ const char *name = NULL;
+ int res;
+ struct sample_spec ss;
+ struct channel_map map;
+ uint32_t sink_index, syncid, rate = 0;
+ const char *sink_name;
+ struct buffer_attr attr = { 0 };
+ bool corked = false,
+ no_remap = false,
+ no_remix = false,
+ fix_format = false,
+ fix_rate = false,
+ fix_channels = false,
+ no_move = false,
+ variable_rate = false,
+ muted = false,
+ adjust_latency = false,
+ early_requests = false,
+ dont_inhibit_auto_suspend = false,
+ volume_set = true,
+ muted_set = false,
+ fail_on_suspend = false,
+ relative_volume = false,
+ passthrough = false;
+ struct volume volume;
+ struct pw_properties *props = NULL;
+ uint8_t n_formats = 0;
+ struct stream *stream = NULL;
+ uint32_t n_params = 0, n_valid_formats = 0, flags;
+ const struct spa_pod *params[MAX_FORMATS];
+ uint8_t buffer[4096];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+
+ props = pw_properties_copy(client->props);
+ if (props == NULL)
+ goto error_errno;
+
+ if (client->version < 13) {
+ if ((res = message_get(m,
+ TAG_STRING, &name,
+ TAG_INVALID)) < 0)
+ goto error_protocol;
+ if (name == NULL)
+ goto error_protocol;
+ }
+ if (message_get(m,
+ TAG_SAMPLE_SPEC, &ss,
+ TAG_CHANNEL_MAP, &map,
+ TAG_U32, &sink_index,
+ TAG_STRING, &sink_name,
+ TAG_U32, &attr.maxlength,
+ TAG_BOOLEAN, &corked,
+ TAG_U32, &attr.tlength,
+ TAG_U32, &attr.prebuf,
+ TAG_U32, &attr.minreq,
+ TAG_U32, &syncid,
+ TAG_CVOLUME, &volume,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+
+ pw_log_info("[%s] CREATE_PLAYBACK_STREAM tag:%u corked:%u sink-name:%s sink-index:%u",
+ client->name, tag, corked, sink_name, sink_index);
+
+ if (sink_index != SPA_ID_INVALID && sink_name != NULL)
+ goto error_invalid;
+
+ if (client->version >= 12) {
+ if (message_get(m,
+ TAG_BOOLEAN, &no_remap,
+ TAG_BOOLEAN, &no_remix,
+ TAG_BOOLEAN, &fix_format,
+ TAG_BOOLEAN, &fix_rate,
+ TAG_BOOLEAN, &fix_channels,
+ TAG_BOOLEAN, &no_move,
+ TAG_BOOLEAN, &variable_rate,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ }
+ if (client->version >= 13) {
+ if (message_get(m,
+ TAG_BOOLEAN, &muted,
+ TAG_BOOLEAN, &adjust_latency,
+ TAG_PROPLIST, props,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ }
+ if (client->version >= 14) {
+ if (message_get(m,
+ TAG_BOOLEAN, &volume_set,
+ TAG_BOOLEAN, &early_requests,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ }
+ if (client->version >= 15) {
+ if (message_get(m,
+ TAG_BOOLEAN, &muted_set,
+ TAG_BOOLEAN, &dont_inhibit_auto_suspend,
+ TAG_BOOLEAN, &fail_on_suspend,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ }
+ if (client->version >= 17) {
+ if (message_get(m,
+ TAG_BOOLEAN, &relative_volume,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ }
+ if (client->version >= 18) {
+ if (message_get(m,
+ TAG_BOOLEAN, &passthrough,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ }
+
+ if (client->version >= 21) {
+ if (message_get(m,
+ TAG_U8, &n_formats,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+
+ if (n_formats) {
+ uint8_t i;
+ for (i = 0; i < n_formats; i++) {
+ struct format_info format;
+ uint32_t r;
+
+ if (message_get(m,
+ TAG_FORMAT_INFO, &format,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+
+ if (n_params < MAX_FORMATS &&
+ (params[n_params] = format_info_build_param(&b,
+ SPA_PARAM_EnumFormat, &format, &r)) != NULL) {
+ n_params++;
+ n_valid_formats++;
+ if (r > rate)
+ rate = r;
+ } else {
+ log_format_info(impl, SPA_LOG_LEVEL_WARN, &format);
+ }
+ format_info_clear(&format);
+ }
+ }
+ }
+ if (sample_spec_valid(&ss)) {
+ if (fix_format || fix_rate || fix_channels) {
+ struct sample_spec sfix = ss;
+ if (fix_format)
+ sfix.format = SPA_AUDIO_FORMAT_UNKNOWN;
+ if (fix_rate)
+ sfix.rate = 0;
+ if (fix_channels)
+ sfix.channels = 0;
+ if (n_params < MAX_FORMATS &&
+ (params[n_params] = format_build_param(&b,
+ SPA_PARAM_EnumFormat, &sfix,
+ sfix.channels > 0 ? &map : NULL)) != NULL) {
+ n_params++;
+ n_valid_formats++;
+ }
+ }
+ else if (n_params < MAX_FORMATS &&
+ (params[n_params] = format_build_param(&b,
+ SPA_PARAM_EnumFormat, &ss,
+ ss.channels > 0 ? &map : NULL)) != NULL) {
+ n_params++;
+ n_valid_formats++;
+ } else {
+ pw_log_warn("%p: unsupported format:%s rate:%d channels:%u",
+ impl, format_id2name(ss.format), ss.rate,
+ ss.channels);
+ }
+ rate = ss.rate;
+ }
+
+ if (m->offset != m->length)
+ goto error_protocol;
+
+ if (n_valid_formats == 0)
+ goto error_no_formats;
+
+ stream = stream_new(client, STREAM_TYPE_PLAYBACK, tag, &ss, &map, &attr);
+ if (stream == NULL)
+ goto error_errno;
+
+ stream->corked = corked;
+ stream->adjust_latency = adjust_latency;
+ stream->early_requests = early_requests;
+ stream->volume = volume;
+ stream->volume_set = volume_set;
+ stream->muted = muted;
+ stream->muted_set = muted_set;
+ stream->is_underrun = true;
+ stream->underrun_for = -1;
+
+ if (rate != 0) {
+ struct spa_fraction lat;
+ fix_playback_buffer_attr(stream, &attr, rate, &lat);
+ pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", rate);
+ pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u",
+ lat.num, lat.denom);
+ }
+ if (no_remix)
+ pw_properties_set(props, PW_KEY_STREAM_DONT_REMIX, "true");
+ flags = 0;
+ if (no_move)
+ flags |= PW_STREAM_FLAG_DONT_RECONNECT;
+
+ if (sink_name != NULL) {
+ pw_properties_set(props,
+ PW_KEY_TARGET_OBJECT, sink_name);
+ } else if (sink_index != SPA_ID_INVALID && sink_index != 0) {
+ pw_properties_setf(props,
+ PW_KEY_TARGET_OBJECT, "%u", sink_index);
+ }
+
+ stream->stream = pw_stream_new(client->core, name, props);
+ props = NULL;
+ if (stream->stream == NULL)
+ goto error_errno;
+
+ pw_log_debug("%p: new stream %p channel:%d passthrough:%d",
+ impl, stream, stream->channel, passthrough);
+
+ pw_stream_add_listener(stream->stream,
+ &stream->stream_listener,
+ &stream_events, stream);
+
+ pw_stream_connect(stream->stream,
+ PW_DIRECTION_OUTPUT,
+ SPA_ID_INVALID,
+ flags |
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_RT_PROCESS |
+ PW_STREAM_FLAG_MAP_BUFFERS,
+ params, n_params);
+
+ return 0;
+
+error_errno:
+ res = -errno;
+ goto error;
+error_protocol:
+ res = -EPROTO;
+ goto error;
+error_no_formats:
+ res = -ENOTSUP;
+ goto error;
+error_invalid:
+ res = -EINVAL;
+ goto error;
+error:
+ pw_properties_free(props);
+ if (stream)
+ stream_free(stream);
+ return res;
+}
+
+static int do_create_record_stream(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ const char *name = NULL;
+ int res;
+ struct sample_spec ss;
+ struct channel_map map;
+ uint32_t source_index;
+ const char *source_name;
+ struct buffer_attr attr = { 0 };
+ bool corked = false,
+ no_remap = false,
+ no_remix = false,
+ fix_format = false,
+ fix_rate = false,
+ fix_channels = false,
+ no_move = false,
+ variable_rate = false,
+ peak_detect = false,
+ adjust_latency = false,
+ early_requests = false,
+ dont_inhibit_auto_suspend = false,
+ volume_set = true,
+ muted = false,
+ muted_set = false,
+ fail_on_suspend = false,
+ relative_volume = false,
+ passthrough = false;
+ uint32_t direct_on_input_idx = SPA_ID_INVALID;
+ struct volume volume = VOLUME_INIT;
+ struct pw_properties *props = NULL;
+ uint8_t n_formats = 0;
+ struct stream *stream = NULL;
+ uint32_t n_params = 0, n_valid_formats = 0, flags, id, rate = 0;
+ const struct spa_pod *params[MAX_FORMATS];
+ uint8_t buffer[4096];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+
+ props = pw_properties_copy(client->props);
+ if (props == NULL)
+ goto error_errno;
+
+ if (client->version < 13) {
+ if (message_get(m,
+ TAG_STRING, &name,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ if (name == NULL)
+ goto error_protocol;
+ }
+ if (message_get(m,
+ TAG_SAMPLE_SPEC, &ss,
+ TAG_CHANNEL_MAP, &map,
+ TAG_U32, &source_index,
+ TAG_STRING, &source_name,
+ TAG_U32, &attr.maxlength,
+ TAG_BOOLEAN, &corked,
+ TAG_U32, &attr.fragsize,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+
+ pw_log_info("[%s] CREATE_RECORD_STREAM tag:%u corked:%u source-name:%s source-index:%u",
+ client->name, tag, corked, source_name, source_index);
+
+ if (source_index != SPA_ID_INVALID && source_name != NULL)
+ goto error_invalid;
+
+ if (client->version >= 12) {
+ if (message_get(m,
+ TAG_BOOLEAN, &no_remap,
+ TAG_BOOLEAN, &no_remix,
+ TAG_BOOLEAN, &fix_format,
+ TAG_BOOLEAN, &fix_rate,
+ TAG_BOOLEAN, &fix_channels,
+ TAG_BOOLEAN, &no_move,
+ TAG_BOOLEAN, &variable_rate,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ }
+ if (client->version >= 13) {
+ if (message_get(m,
+ TAG_BOOLEAN, &peak_detect,
+ TAG_BOOLEAN, &adjust_latency,
+ TAG_PROPLIST, props,
+ TAG_U32, &direct_on_input_idx,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ }
+ if (client->version >= 14) {
+ if (message_get(m,
+ TAG_BOOLEAN, &early_requests,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ }
+ if (client->version >= 15) {
+ if (message_get(m,
+ TAG_BOOLEAN, &dont_inhibit_auto_suspend,
+ TAG_BOOLEAN, &fail_on_suspend,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ }
+ if (client->version >= 22) {
+ if (message_get(m,
+ TAG_U8, &n_formats,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+
+ if (n_formats) {
+ uint8_t i;
+ for (i = 0; i < n_formats; i++) {
+ struct format_info format;
+ uint32_t r;
+
+ if (message_get(m,
+ TAG_FORMAT_INFO, &format,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+
+ if (n_params < MAX_FORMATS &&
+ (params[n_params] = format_info_build_param(&b,
+ SPA_PARAM_EnumFormat, &format, &r)) != NULL) {
+ n_params++;
+ n_valid_formats++;
+ if (r > rate)
+ rate = r;
+ } else {
+ log_format_info(impl, SPA_LOG_LEVEL_WARN, &format);
+ }
+ format_info_clear(&format);
+ }
+ }
+ if (message_get(m,
+ TAG_CVOLUME, &volume,
+ TAG_BOOLEAN, &muted,
+ TAG_BOOLEAN, &volume_set,
+ TAG_BOOLEAN, &muted_set,
+ TAG_BOOLEAN, &relative_volume,
+ TAG_BOOLEAN, &passthrough,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ } else {
+ volume_set = false;
+ }
+ if (sample_spec_valid(&ss)) {
+ if (fix_format || fix_rate || fix_channels) {
+ struct sample_spec sfix = ss;
+ if (fix_format)
+ sfix.format = SPA_AUDIO_FORMAT_UNKNOWN;
+ if (fix_rate)
+ sfix.rate = 0;
+ if (fix_channels)
+ sfix.channels = 0;
+ if (n_params < MAX_FORMATS &&
+ (params[n_params] = format_build_param(&b,
+ SPA_PARAM_EnumFormat, &sfix,
+ sfix.channels > 0 ? &map : NULL)) != NULL) {
+ n_params++;
+ n_valid_formats++;
+ }
+ }
+ else if (n_params < MAX_FORMATS &&
+ (params[n_params] = format_build_param(&b,
+ SPA_PARAM_EnumFormat, &ss,
+ ss.channels > 0 ? &map : NULL)) != NULL) {
+ n_params++;
+ n_valid_formats++;
+ } else {
+ pw_log_warn("%p: unsupported format:%s rate:%d channels:%u",
+ impl, format_id2name(ss.format), ss.rate,
+ ss.channels);
+ }
+ rate = ss.rate;
+ }
+ if (m->offset != m->length)
+ goto error_protocol;
+
+ if (n_valid_formats == 0)
+ goto error_no_formats;
+
+ stream = stream_new(client, STREAM_TYPE_RECORD, tag, &ss, &map, &attr);
+ if (stream == NULL)
+ goto error_errno;
+
+ stream->corked = corked;
+ stream->adjust_latency = adjust_latency;
+ stream->early_requests = early_requests;
+ stream->volume = volume;
+ stream->volume_set = volume_set;
+ stream->muted = muted;
+ stream->muted_set = muted_set;
+
+ if (client->quirks & QUIRK_REMOVE_CAPTURE_DONT_MOVE)
+ no_move = false;
+
+ if (rate != 0) {
+ struct spa_fraction lat;
+ fix_record_buffer_attr(stream, &attr, rate, &lat);
+ pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%u", rate);
+ pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%u/%u",
+ lat.num, lat.denom);
+ }
+ if (peak_detect)
+ pw_properties_set(props, PW_KEY_STREAM_MONITOR, "true");
+ if (no_remix)
+ pw_properties_set(props, PW_KEY_STREAM_DONT_REMIX, "true");
+ flags = 0;
+ if (no_move)
+ flags |= PW_STREAM_FLAG_DONT_RECONNECT;
+
+ if (direct_on_input_idx != SPA_ID_INVALID) {
+ source_index = direct_on_input_idx;
+ } else if (source_name != NULL) {
+ if ((id = atoi(source_name)) != 0)
+ source_index = id;
+ }
+ if (source_index != SPA_ID_INVALID && source_index != 0) {
+ pw_properties_setf(props,
+ PW_KEY_TARGET_OBJECT, "%u", source_index);
+ } else if (source_name != NULL) {
+ if (spa_strendswith(source_name, ".monitor")) {
+ pw_properties_setf(props,
+ PW_KEY_TARGET_OBJECT,
+ "%.*s", (int)strlen(source_name)-8, source_name);
+ pw_properties_set(props,
+ PW_KEY_STREAM_CAPTURE_SINK, "true");
+ } else {
+ pw_properties_set(props,
+ PW_KEY_TARGET_OBJECT, source_name);
+ }
+ }
+
+ stream->stream = pw_stream_new(client->core, name, props);
+ props = NULL;
+ if (stream->stream == NULL)
+ goto error_errno;
+
+ pw_stream_add_listener(stream->stream,
+ &stream->stream_listener,
+ &stream_events, stream);
+
+ pw_stream_connect(stream->stream,
+ PW_DIRECTION_INPUT,
+ SPA_ID_INVALID,
+ flags |
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_RT_PROCESS |
+ PW_STREAM_FLAG_MAP_BUFFERS,
+ params, n_params);
+
+ return 0;
+
+error_errno:
+ res = -errno;
+ goto error;
+error_protocol:
+ res = -EPROTO;
+ goto error;
+error_no_formats:
+ res = -ENOTSUP;
+ goto error;
+error_invalid:
+ res = -EINVAL;
+ goto error;
+error:
+ pw_properties_free(props);
+ if (stream)
+ stream_free(stream);
+ return res;
+}
+
+static int do_delete_stream(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ uint32_t channel;
+ struct stream *stream;
+ int res;
+
+ if ((res = message_get(m,
+ TAG_U32, &channel,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] DELETE_STREAM tag:%u channel:%u",
+ client->name, tag, channel);
+
+ stream = pw_map_lookup(&client->streams, channel);
+ if (stream == NULL)
+ return -ENOENT;
+ if (command == COMMAND_DELETE_PLAYBACK_STREAM &&
+ stream->type != STREAM_TYPE_PLAYBACK)
+ return -ENOENT;
+ if (command == COMMAND_DELETE_RECORD_STREAM &&
+ stream->type != STREAM_TYPE_RECORD)
+ return -ENOENT;
+ if (command == COMMAND_DELETE_UPLOAD_STREAM &&
+ stream->type != STREAM_TYPE_UPLOAD)
+ return -ENOENT;
+
+ stream_free(stream);
+
+ return reply_simple_ack(client, tag);
+}
+
+static int do_get_playback_latency(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ struct message *reply;
+ uint32_t channel;
+ struct timeval tv, now;
+ struct stream *stream;
+ int res;
+
+ if ((res = message_get(m,
+ TAG_U32, &channel,
+ TAG_TIMEVAL, &tv,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ pw_log_debug("%p: %s tag:%u channel:%u", impl, commands[command].name, tag, channel);
+ stream = pw_map_lookup(&client->streams, channel);
+ if (stream == NULL || stream->type != STREAM_TYPE_PLAYBACK)
+ return -ENOENT;
+
+ pw_log_debug("read:0x%"PRIx64" write:0x%"PRIx64" queued:%"PRIi64" delay:%"PRIi64
+ " playing:%"PRIu64,
+ stream->read_index, stream->write_index,
+ stream->write_index - stream->read_index, stream->delay,
+ stream->playing_for);
+
+ gettimeofday(&now, NULL);
+
+ reply = reply_new(client, tag);
+ message_put(reply,
+ TAG_USEC, stream->delay, /* sink latency + queued samples */
+ TAG_USEC, 0LL, /* always 0 */
+ TAG_BOOLEAN, stream->playing_for > 0 &&
+ !stream->corked, /* playing state */
+ TAG_TIMEVAL, &tv,
+ TAG_TIMEVAL, &now,
+ TAG_S64, stream->write_index,
+ TAG_S64, stream->read_index,
+ TAG_INVALID);
+
+ if (client->version >= 13) {
+ message_put(reply,
+ TAG_U64, stream->underrun_for,
+ TAG_U64, stream->playing_for,
+ TAG_INVALID);
+ }
+ return client_queue_message(client, reply);
+}
+
+static int do_get_record_latency(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ struct message *reply;
+ uint32_t channel;
+ struct timeval tv, now;
+ struct stream *stream;
+ int res;
+
+ if ((res = message_get(m,
+ TAG_U32, &channel,
+ TAG_TIMEVAL, &tv,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ pw_log_debug("%p: %s channel:%u", impl, commands[command].name, channel);
+ stream = pw_map_lookup(&client->streams, channel);
+ if (stream == NULL || stream->type != STREAM_TYPE_RECORD)
+ return -ENOENT;
+
+ pw_log_debug("read:0x%"PRIx64" write:0x%"PRIx64" queued:%"PRIi64" delay:%"PRIi64,
+ stream->read_index, stream->write_index,
+ stream->write_index - stream->read_index, stream->delay);
+
+
+ gettimeofday(&now, NULL);
+ reply = reply_new(client, tag);
+ message_put(reply,
+ TAG_USEC, 0LL, /* monitor latency */
+ TAG_USEC, stream->delay, /* source latency + queued */
+ TAG_BOOLEAN, !stream->corked, /* playing state */
+ TAG_TIMEVAL, &tv,
+ TAG_TIMEVAL, &now,
+ TAG_S64, stream->write_index,
+ TAG_S64, stream->read_index,
+ TAG_INVALID);
+
+ return client_queue_message(client, reply);
+}
+
+static int do_create_upload_stream(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ const char *name;
+ struct sample_spec ss;
+ struct channel_map map;
+ struct pw_properties *props = NULL;
+ uint32_t length;
+ struct stream *stream = NULL;
+ struct message *reply;
+ int res;
+
+ if ((props = pw_properties_copy(client->props)) == NULL)
+ goto error_errno;
+
+ if ((res = message_get(m,
+ TAG_STRING, &name,
+ TAG_SAMPLE_SPEC, &ss,
+ TAG_CHANNEL_MAP, &map,
+ TAG_U32, &length,
+ TAG_INVALID)) < 0)
+ goto error_proto;
+
+ if (client->version >= 13) {
+ if ((res = message_get(m,
+ TAG_PROPLIST, props,
+ TAG_INVALID)) < 0)
+ goto error_proto;
+
+ } else {
+ pw_properties_set(props, PW_KEY_MEDIA_NAME, name);
+ }
+ if (name == NULL)
+ name = pw_properties_get(props, "event.id");
+ if (name == NULL)
+ name = pw_properties_get(props, PW_KEY_MEDIA_NAME);
+
+ if (name == NULL ||
+ !sample_spec_valid(&ss) ||
+ !channel_map_valid(&map) ||
+ ss.channels != map.channels ||
+ length == 0 || (length % sample_spec_frame_size(&ss) != 0))
+ goto error_invalid;
+ if (length >= SCACHE_ENTRY_SIZE_MAX)
+ goto error_toolarge;
+
+ pw_log_info("[%s] %s tag:%u name:%s length:%d",
+ client->name, commands[command].name, tag,
+ name, length);
+
+ stream = stream_new(client, STREAM_TYPE_UPLOAD, tag, &ss, &map, &(struct buffer_attr) {
+ .maxlength = length,
+ });
+ if (stream == NULL)
+ goto error_errno;
+
+ stream->props = props;
+
+ stream->buffer = calloc(1, MAXLENGTH);
+ if (stream->buffer == NULL)
+ goto error_errno;
+
+ reply = reply_new(client, tag);
+ message_put(reply,
+ TAG_U32, stream->channel,
+ TAG_U32, length,
+ TAG_INVALID);
+ return client_queue_message(client, reply);
+
+error_errno:
+ res = -errno;
+ goto error;
+error_proto:
+ res = -EPROTO;
+ goto error;
+error_invalid:
+ res = -EINVAL;
+ goto error;
+error_toolarge:
+ res = -EOVERFLOW;
+ goto error;
+error:
+ pw_properties_free(props);
+ if (stream)
+ stream_free(stream);
+ return res;
+}
+
+static int do_finish_upload_stream(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ uint32_t channel, event;
+ struct stream *stream;
+ struct sample *sample;
+ const char *name;
+ int res;
+
+ if (message_get(m,
+ TAG_U32, &channel,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ stream = pw_map_lookup(&client->streams, channel);
+ if (stream == NULL || stream->type != STREAM_TYPE_UPLOAD)
+ return -ENOENT;
+
+ name = pw_properties_get(stream->props, "event.id");
+ if (name == NULL)
+ name = pw_properties_get(stream->props, PW_KEY_MEDIA_NAME);
+ if (name == NULL)
+ goto error_invalid;
+
+ pw_log_info("[%s] %s tag:%u channel:%u name:%s",
+ client->name, commands[command].name, tag,
+ channel, name);
+
+ struct sample *old = find_sample(impl, SPA_ID_INVALID, name);
+ if (old == NULL || old->ref > 1) {
+ sample = calloc(1, sizeof(*sample));
+ if (sample == NULL)
+ goto error_errno;
+
+ if (old != NULL) {
+ sample->index = old->index;
+ spa_assert_se(pw_map_insert_at(&impl->samples, sample->index, sample) == 0);
+
+ old->index = SPA_ID_INVALID;
+ sample_unref(old);
+ } else {
+ sample->index = pw_map_insert_new(&impl->samples, sample);
+ if (sample->index == SPA_ID_INVALID)
+ goto error_errno;
+ }
+ } else {
+ pw_properties_free(old->props);
+ free(old->buffer);
+ impl->stat.sample_cache -= old->length;
+
+ sample = old;
+ }
+
+ if (old != NULL)
+ event = SUBSCRIPTION_EVENT_CHANGE;
+ else
+ event = SUBSCRIPTION_EVENT_NEW;
+
+ sample->ref = 1;
+ sample->impl = impl;
+ sample->name = name;
+ sample->props = stream->props;
+ sample->ss = stream->ss;
+ sample->map = stream->map;
+ sample->buffer = stream->buffer;
+ sample->length = stream->attr.maxlength;
+
+ impl->stat.sample_cache += sample->length;
+
+ stream->props = NULL;
+ stream->buffer = NULL;
+ stream_free(stream);
+
+ broadcast_subscribe_event(impl,
+ SUBSCRIPTION_MASK_SAMPLE_CACHE,
+ event | SUBSCRIPTION_EVENT_SAMPLE_CACHE,
+ sample->index);
+
+ return reply_simple_ack(client, tag);
+
+error_errno:
+ res = -errno;
+ free(sample);
+ goto error;
+error_invalid:
+ res = -EINVAL;
+ goto error;
+error:
+ stream_free(stream);
+ return res;
+}
+
+static const char *get_default(struct client *client, bool sink)
+{
+ struct selector sel;
+ struct pw_manager *manager = client->manager;
+ struct pw_manager_object *o;
+ const char *def, *str, *mon;
+
+ spa_zero(sel);
+ if (sink) {
+ sel.type = pw_manager_object_is_sink;
+ sel.key = PW_KEY_NODE_NAME;
+ sel.value = client->default_sink;
+ def = DEFAULT_SINK;
+ } else {
+ sel.type = pw_manager_object_is_source_or_monitor;
+ sel.key = PW_KEY_NODE_NAME;
+ sel.value = client->default_source;
+ def = DEFAULT_SOURCE;
+ }
+ sel.accumulate = select_best;
+
+ o = select_object(manager, &sel);
+ if (o == NULL || o->props == NULL)
+ return def;
+ str = pw_properties_get(o->props, PW_KEY_NODE_NAME);
+
+ if (!sink && pw_manager_object_is_monitor(o)) {
+ def = DEFAULT_MONITOR;
+ if (str != NULL &&
+ (mon = pw_properties_get(o->props, PW_KEY_NODE_NAME".monitor")) == NULL) {
+ pw_properties_setf(o->props,
+ PW_KEY_NODE_NAME".monitor",
+ "%s.monitor", str);
+ }
+ str = pw_properties_get(o->props, PW_KEY_NODE_NAME".monitor");
+ }
+ if (str == NULL)
+ str = def;
+ return str;
+}
+
+static struct pw_manager_object *find_device(struct client *client,
+ uint32_t index, const char *name, bool sink, bool *is_monitor)
+{
+ struct selector sel;
+ bool monitor = false, find_default = false;
+ struct pw_manager_object *o;
+
+ if (name != NULL) {
+ if (spa_streq(name, DEFAULT_MONITOR)) {
+ if (sink)
+ return NULL;
+ sink = true;
+ find_default = true;
+ } else if (spa_streq(name, DEFAULT_SOURCE)) {
+ if (sink)
+ return NULL;
+ find_default = true;
+ } else if (spa_streq(name, DEFAULT_SINK)) {
+ if (!sink)
+ return NULL;
+ find_default = true;
+ } else if (spa_atou32(name, &index, 0)) {
+ name = NULL;
+ }
+ }
+ if (name == NULL && (index == SPA_ID_INVALID || index == 0))
+ find_default = true;
+
+ if (find_default) {
+ name = get_default(client, sink);
+ index = SPA_ID_INVALID;
+ }
+
+ if (name != NULL) {
+ if (spa_strendswith(name, ".monitor")) {
+ name = strndupa(name, strlen(name)-8);
+ monitor = true;
+ }
+ } else if (index == SPA_ID_INVALID)
+ return NULL;
+
+
+ spa_zero(sel);
+ sel.type = sink ?
+ pw_manager_object_is_sink :
+ pw_manager_object_is_source_or_monitor;
+ sel.index = index;
+ sel.key = PW_KEY_NODE_NAME;
+ sel.value = name;
+
+ o = select_object(client->manager, &sel);
+ if (o != NULL) {
+ if (!sink && pw_manager_object_is_monitor(o))
+ monitor = true;
+ }
+ if (is_monitor)
+ *is_monitor = monitor;
+
+ return o;
+}
+
+static void sample_play_finish(struct pending_sample *ps)
+{
+ struct client *client = ps->client;
+ pending_sample_free(ps);
+ client_unref(client);
+}
+
+static void sample_play_ready_reply(void *data, struct client *client, uint32_t tag)
+{
+ struct pending_sample *ps = data;
+ struct message *reply;
+ uint32_t index = id_to_index(client->manager, ps->play->id);
+
+ pw_log_info("[%s] PLAY_SAMPLE tag:%u index:%u",
+ client->name, ps->tag, index);
+
+ ps->ready = true;
+
+ reply = reply_new(client, ps->tag);
+ if (client->version >= 13)
+ message_put(reply,
+ TAG_U32, index,
+ TAG_INVALID);
+
+ client_queue_message(client, reply);
+
+ if (ps->done)
+ sample_play_finish(ps);
+}
+
+static void sample_play_ready(void *data, uint32_t id)
+{
+ struct pending_sample *ps = data;
+ struct client *client = ps->client;
+ operation_new_cb(client, ps->tag, sample_play_ready_reply, ps);
+}
+
+static void on_sample_done(void *obj, void *data, int res, uint32_t id)
+{
+ struct pending_sample *ps = obj;
+ ps->done = true;
+ if (ps->ready)
+ sample_play_finish(ps);
+}
+
+static void sample_play_done(void *data, int res)
+{
+ struct pending_sample *ps = data;
+ struct client *client = ps->client;
+ struct impl *impl = client->impl;
+
+ if (res < 0)
+ reply_error(client, COMMAND_PLAY_SAMPLE, ps->tag, res);
+ else
+ pw_log_info("[%s] PLAY_SAMPLE done tag:%u", client->name, ps->tag);
+
+ pw_work_queue_add(impl->work_queue, ps, 0,
+ on_sample_done, client);
+}
+
+static const struct sample_play_events sample_play_events = {
+ VERSION_SAMPLE_PLAY_EVENTS,
+ .ready = sample_play_ready,
+ .done = sample_play_done,
+};
+
+static int do_play_sample(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ uint32_t sink_index, volume;
+ struct sample *sample;
+ struct sample_play *play;
+ const char *sink_name, *name;
+ struct pw_properties *props = NULL;
+ struct pending_sample *ps;
+ struct pw_manager_object *o;
+ int res;
+
+ if ((props = pw_properties_new(NULL, NULL)) == NULL)
+ goto error_errno;
+
+ if ((res = message_get(m,
+ TAG_U32, &sink_index,
+ TAG_STRING, &sink_name,
+ TAG_U32, &volume,
+ TAG_STRING, &name,
+ TAG_INVALID)) < 0)
+ goto error_proto;
+
+ if (client->version >= 13) {
+ if ((res = message_get(m,
+ TAG_PROPLIST, props,
+ TAG_INVALID)) < 0)
+ goto error_proto;
+
+ }
+ pw_log_info("[%s] %s tag:%u sink_index:%u sink_name:%s name:%s",
+ client->name, commands[command].name, tag,
+ sink_index, sink_name, name);
+
+ pw_properties_update(props, &client->props->dict);
+
+ if (sink_index != SPA_ID_INVALID && sink_name != NULL)
+ goto error_inval;
+
+ o = find_device(client, sink_index, sink_name, PW_DIRECTION_OUTPUT, NULL);
+ if (o == NULL)
+ goto error_noent;
+
+ sample = find_sample(impl, SPA_ID_INVALID, name);
+ if (sample == NULL)
+ goto error_noent;
+
+ pw_properties_setf(props, PW_KEY_TARGET_OBJECT, "%"PRIu64, o->serial);
+
+ play = sample_play_new(client->core, sample, props, sizeof(struct pending_sample));
+ props = NULL;
+ if (play == NULL)
+ goto error_errno;
+
+ ps = play->user_data;
+ ps->client = client;
+ ps->play = play;
+ ps->tag = tag;
+ sample_play_add_listener(play, &ps->listener, &sample_play_events, ps);
+ spa_list_append(&client->pending_samples, &ps->link);
+ client->ref++;
+
+ return 0;
+
+error_errno:
+ res = -errno;
+ goto error;
+error_proto:
+ res = -EPROTO;
+ goto error;
+error_inval:
+ res = -EINVAL;
+ goto error;
+error_noent:
+ res = -ENOENT;
+ goto error;
+error:
+ pw_properties_free(props);
+ return res;
+}
+
+static int do_remove_sample(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ const char *name;
+ struct sample *sample;
+ int res;
+
+ if ((res = message_get(m,
+ TAG_STRING, &name,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u name:%s",
+ client->name, commands[command].name, tag,
+ name);
+ if (name == NULL)
+ return -EINVAL;
+ if ((sample = find_sample(impl, SPA_ID_INVALID, name)) == NULL)
+ return -ENOENT;
+
+ broadcast_subscribe_event(impl,
+ SUBSCRIPTION_MASK_SAMPLE_CACHE,
+ SUBSCRIPTION_EVENT_REMOVE |
+ SUBSCRIPTION_EVENT_SAMPLE_CACHE,
+ sample->index);
+
+ pw_map_remove(&impl->samples, sample->index);
+ sample->index = SPA_ID_INVALID;
+
+ sample_unref(sample);
+
+ return reply_simple_ack(client, tag);
+}
+
+static int do_cork_stream(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ uint32_t channel;
+ bool cork;
+ struct stream *stream;
+ int res;
+
+ if ((res = message_get(m,
+ TAG_U32, &channel,
+ TAG_BOOLEAN, &cork,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u channel:%u cork:%s",
+ client->name, commands[command].name, tag,
+ channel, cork ? "yes" : "no");
+
+ stream = pw_map_lookup(&client->streams, channel);
+ if (stream == NULL || stream->type == STREAM_TYPE_UPLOAD)
+ return -ENOENT;
+
+ stream->corked = cork;
+ stream_set_paused(stream, cork, "cork request");
+ if (cork) {
+ stream->is_underrun = true;
+ } else {
+ stream->playing_for = 0;
+ stream->underrun_for = -1;
+ stream_send_request(stream);
+ }
+
+ return reply_simple_ack(client, tag);
+}
+
+static int do_flush_trigger_prebuf_stream(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ uint32_t channel;
+ struct stream *stream;
+ int res;
+
+ if ((res = message_get(m,
+ TAG_U32, &channel,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u channel:%u",
+ client->name, commands[command].name, tag, channel);
+
+ stream = pw_map_lookup(&client->streams, channel);
+ if (stream == NULL || stream->type == STREAM_TYPE_UPLOAD)
+ return -ENOENT;
+
+ switch (command) {
+ case COMMAND_FLUSH_PLAYBACK_STREAM:
+ case COMMAND_FLUSH_RECORD_STREAM:
+ stream_flush(stream);
+ break;
+ case COMMAND_TRIGGER_PLAYBACK_STREAM:
+ case COMMAND_PREBUF_PLAYBACK_STREAM:
+ if (stream->type != STREAM_TYPE_PLAYBACK)
+ return -ENOENT;
+ if (command == COMMAND_TRIGGER_PLAYBACK_STREAM)
+ stream->in_prebuf = false;
+ else if (stream->attr.prebuf > 0)
+ stream->in_prebuf = true;
+ stream_send_request(stream);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return reply_simple_ack(client, tag);
+}
+
+static int set_node_volume_mute(struct pw_manager_object *o,
+ struct volume *vol, bool *mute, bool is_monitor)
+{
+ char buf[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
+ struct spa_pod_frame f[1];
+ struct spa_pod *param;
+ uint32_t volprop, muteprop;
+
+ if (!SPA_FLAG_IS_SET(o->permissions, PW_PERM_W | PW_PERM_X))
+ return -EACCES;
+ if (o->proxy == NULL)
+ return -ENOENT;
+
+ if (is_monitor) {
+ volprop = SPA_PROP_monitorVolumes;
+ muteprop = SPA_PROP_monitorMute;
+ } else {
+ volprop = SPA_PROP_channelVolumes;
+ muteprop = SPA_PROP_mute;
+ }
+
+ spa_pod_builder_push_object(&b, &f[0],
+ SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
+ if (vol)
+ spa_pod_builder_add(&b,
+ volprop, SPA_POD_Array(sizeof(float),
+ SPA_TYPE_Float,
+ vol->channels,
+ vol->values), 0);
+ if (mute)
+ spa_pod_builder_add(&b,
+ muteprop, SPA_POD_Bool(*mute), 0);
+ param = spa_pod_builder_pop(&b, &f[0]);
+
+ pw_node_set_param((struct pw_node*)o->proxy,
+ SPA_PARAM_Props, 0, param);
+ return 0;
+}
+
+static int set_card_volume_mute_delay(struct pw_manager_object *o, uint32_t port_index,
+ uint32_t device_id, struct volume *vol, bool *mute, int64_t *latency_offset)
+{
+ char buf[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
+ struct spa_pod_frame f[2];
+ struct spa_pod *param;
+
+ if (!SPA_FLAG_IS_SET(o->permissions, PW_PERM_W | PW_PERM_X))
+ return -EACCES;
+
+ if (o->proxy == NULL)
+ return -ENOENT;
+
+ spa_pod_builder_push_object(&b, &f[0],
+ SPA_TYPE_OBJECT_ParamRoute, SPA_PARAM_Route);
+ spa_pod_builder_add(&b,
+ SPA_PARAM_ROUTE_index, SPA_POD_Int(port_index),
+ SPA_PARAM_ROUTE_device, SPA_POD_Int(device_id),
+ 0);
+ spa_pod_builder_prop(&b, SPA_PARAM_ROUTE_props, 0);
+ spa_pod_builder_push_object(&b, &f[1],
+ SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
+ if (vol)
+ spa_pod_builder_add(&b,
+ SPA_PROP_channelVolumes, SPA_POD_Array(sizeof(float),
+ SPA_TYPE_Float,
+ vol->channels,
+ vol->values), 0);
+ if (mute)
+ spa_pod_builder_add(&b,
+ SPA_PROP_mute, SPA_POD_Bool(*mute), 0);
+ if (latency_offset)
+ spa_pod_builder_add(&b,
+ SPA_PROP_latencyOffsetNsec, SPA_POD_Long(*latency_offset), 0);
+ spa_pod_builder_pop(&b, &f[1]);
+ spa_pod_builder_prop(&b, SPA_PARAM_ROUTE_save, 0);
+ spa_pod_builder_bool(&b, true);
+ param = spa_pod_builder_pop(&b, &f[0]);
+
+ pw_device_set_param((struct pw_device*)o->proxy,
+ SPA_PARAM_Route, 0, param);
+ return 0;
+}
+
+static int set_card_port(struct pw_manager_object *o, uint32_t device_id,
+ uint32_t port_index)
+{
+ char buf[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
+
+ if (!SPA_FLAG_IS_SET(o->permissions, PW_PERM_W | PW_PERM_X))
+ return -EACCES;
+
+ if (o->proxy == NULL)
+ return -ENOENT;
+
+ pw_device_set_param((struct pw_device*)o->proxy,
+ SPA_PARAM_Route, 0,
+ spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamRoute, SPA_PARAM_Route,
+ SPA_PARAM_ROUTE_index, SPA_POD_Int(port_index),
+ SPA_PARAM_ROUTE_device, SPA_POD_Int(device_id),
+ SPA_PARAM_ROUTE_save, SPA_POD_Bool(true)));
+
+ return 0;
+}
+
+static int do_set_stream_volume(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct pw_manager *manager = client->manager;
+ uint32_t index;
+ struct stream *stream;
+ struct volume volume;
+ int res;
+
+ if ((res = message_get(m,
+ TAG_U32, &index,
+ TAG_CVOLUME, &volume,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u index:%u",
+ client->name, commands[command].name, tag, index);
+
+ stream = find_stream(client, index);
+ if (stream != NULL) {
+
+ if (volume_compare(&stream->volume, &volume) == 0)
+ goto done;
+
+ pw_stream_set_control(stream->stream,
+ SPA_PROP_channelVolumes, volume.channels, volume.values,
+ 0);
+ } else {
+ struct selector sel;
+ struct pw_manager_object *o;
+
+ spa_zero(sel);
+ sel.index = index;
+ if (command == COMMAND_SET_SINK_INPUT_VOLUME)
+ sel.type = pw_manager_object_is_sink_input;
+ else
+ sel.type = pw_manager_object_is_source_output;
+
+ o = select_object(manager, &sel);
+ if (o == NULL)
+ return -ENOENT;
+
+ if ((res = set_node_volume_mute(o, &volume, NULL, false)) < 0)
+ return res;
+ }
+done:
+ return operation_new(client, tag);
+}
+
+static int do_set_stream_mute(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct pw_manager *manager = client->manager;
+ uint32_t index;
+ struct stream *stream;
+ int res;
+ bool mute;
+
+ if ((res = message_get(m,
+ TAG_U32, &index,
+ TAG_BOOLEAN, &mute,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] DO_SET_STREAM_MUTE tag:%u index:%u mute:%u",
+ client->name, tag, index, mute);
+
+ stream = find_stream(client, index);
+ if (stream != NULL) {
+ float val;
+
+ if (stream->muted == mute)
+ goto done;
+
+ val = mute ? 1.0f : 0.0f;
+ pw_stream_set_control(stream->stream,
+ SPA_PROP_mute, 1, &val,
+ 0);
+ } else {
+ struct selector sel;
+ struct pw_manager_object *o;
+
+ spa_zero(sel);
+ sel.index = index;
+ if (command == COMMAND_SET_SINK_INPUT_MUTE)
+ sel.type = pw_manager_object_is_sink_input;
+ else
+ sel.type = pw_manager_object_is_source_output;
+
+ o = select_object(manager, &sel);
+ if (o == NULL)
+ return -ENOENT;
+
+ if ((res = set_node_volume_mute(o, NULL, &mute, false)) < 0)
+ return res;
+ }
+done:
+ return operation_new(client, tag);
+}
+
+static int do_set_volume(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ struct pw_manager *manager = client->manager;
+ struct pw_node_info *info;
+ uint32_t index, card_id = SPA_ID_INVALID;
+ const char *name, *str;
+ struct volume volume;
+ struct pw_manager_object *o, *card = NULL;
+ int res;
+ struct device_info dev_info;
+ enum pw_direction direction;
+ bool is_monitor;
+
+ if ((res = message_get(m,
+ TAG_U32, &index,
+ TAG_STRING, &name,
+ TAG_CVOLUME, &volume,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u index:%u name:%s",
+ client->name, commands[command].name, tag, index, name);
+
+ if ((index == SPA_ID_INVALID && name == NULL) ||
+ (index != SPA_ID_INVALID && name != NULL))
+ return -EINVAL;
+
+ if (command == COMMAND_SET_SINK_VOLUME)
+ direction = PW_DIRECTION_OUTPUT;
+ else
+ direction = PW_DIRECTION_INPUT;
+
+ o = find_device(client, index, name, direction == PW_DIRECTION_OUTPUT, &is_monitor);
+ if (o == NULL || (info = o->info) == NULL || info->props == NULL)
+ return -ENOENT;
+
+ dev_info = DEVICE_INFO_INIT(direction);
+
+ if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)) != NULL)
+ card_id = (uint32_t)atoi(str);
+ if ((str = spa_dict_lookup(info->props, "card.profile.device")) != NULL)
+ dev_info.device = (uint32_t)atoi(str);
+ if (card_id != SPA_ID_INVALID) {
+ struct selector sel = { .id = card_id, .type = pw_manager_object_is_card, };
+ card = select_object(manager, &sel);
+ }
+ collect_device_info(o, card, &dev_info, is_monitor, &impl->defs);
+
+ if (dev_info.have_volume &&
+ volume_compare(&dev_info.volume_info.volume, &volume) == 0)
+ goto done;
+
+ if (card != NULL && !is_monitor && dev_info.active_port != SPA_ID_INVALID)
+ res = set_card_volume_mute_delay(card, dev_info.active_port,
+ dev_info.device, &volume, NULL, NULL);
+ else
+ res = set_node_volume_mute(o, &volume, NULL, is_monitor);
+
+ if (res < 0)
+ return res;
+
+done:
+ return operation_new(client, tag);
+}
+
+static int do_set_mute(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ struct pw_manager *manager = client->manager;
+ struct pw_node_info *info;
+ uint32_t index, card_id = SPA_ID_INVALID;
+ const char *name, *str;
+ bool mute;
+ struct pw_manager_object *o, *card = NULL;
+ int res;
+ struct device_info dev_info;
+ enum pw_direction direction;
+ bool is_monitor;
+
+ if ((res = message_get(m,
+ TAG_U32, &index,
+ TAG_STRING, &name,
+ TAG_BOOLEAN, &mute,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u index:%u name:%s mute:%d",
+ client->name, commands[command].name, tag, index, name, mute);
+
+ if ((index == SPA_ID_INVALID && name == NULL) ||
+ (index != SPA_ID_INVALID && name != NULL))
+ return -EINVAL;
+
+ if (command == COMMAND_SET_SINK_MUTE)
+ direction = PW_DIRECTION_OUTPUT;
+ else
+ direction = PW_DIRECTION_INPUT;
+
+ o = find_device(client, index, name, direction == PW_DIRECTION_OUTPUT, &is_monitor);
+ if (o == NULL || (info = o->info) == NULL || info->props == NULL)
+ return -ENOENT;
+
+ dev_info = DEVICE_INFO_INIT(direction);
+
+ if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)) != NULL)
+ card_id = (uint32_t)atoi(str);
+ if ((str = spa_dict_lookup(info->props, "card.profile.device")) != NULL)
+ dev_info.device = (uint32_t)atoi(str);
+ if (card_id != SPA_ID_INVALID) {
+ struct selector sel = { .id = card_id, .type = pw_manager_object_is_card, };
+ card = select_object(manager, &sel);
+ }
+ collect_device_info(o, card, &dev_info, is_monitor, &impl->defs);
+
+ if (dev_info.have_volume &&
+ dev_info.volume_info.mute == mute)
+ goto done;
+
+ if (card != NULL && !is_monitor && dev_info.active_port != SPA_ID_INVALID)
+ res = set_card_volume_mute_delay(card, dev_info.active_port,
+ dev_info.device, NULL, &mute, NULL);
+ else
+ res = set_node_volume_mute(o, NULL, &mute, is_monitor);
+
+ if (res < 0)
+ return res;
+done:
+ return operation_new(client, tag);
+}
+
+static int do_set_port(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct pw_manager *manager = client->manager;
+ struct pw_node_info *info;
+ uint32_t index, card_id = SPA_ID_INVALID, device_id = SPA_ID_INVALID;
+ uint32_t port_index = SPA_ID_INVALID;
+ const char *name, *str, *port_name;
+ struct pw_manager_object *o, *card = NULL;
+ int res;
+ enum pw_direction direction;
+
+ if ((res = message_get(m,
+ TAG_U32, &index,
+ TAG_STRING, &name,
+ TAG_STRING, &port_name,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u index:%u name:%s port:%s",
+ client->name, commands[command].name, tag, index, name, port_name);
+
+ if ((index == SPA_ID_INVALID && name == NULL) ||
+ (index != SPA_ID_INVALID && name != NULL))
+ return -EINVAL;
+
+ if (command == COMMAND_SET_SINK_PORT)
+ direction = PW_DIRECTION_OUTPUT;
+ else
+ direction = PW_DIRECTION_INPUT;
+
+ o = find_device(client, index, name, direction == PW_DIRECTION_OUTPUT, NULL);
+ if (o == NULL || (info = o->info) == NULL || info->props == NULL)
+ return -ENOENT;
+
+ if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)) != NULL)
+ card_id = (uint32_t)atoi(str);
+ if ((str = spa_dict_lookup(info->props, "card.profile.device")) != NULL)
+ device_id = (uint32_t)atoi(str);
+ if (card_id != SPA_ID_INVALID) {
+ struct selector sel = { .id = card_id, .type = pw_manager_object_is_card, };
+ card = select_object(manager, &sel);
+ }
+ if (card == NULL || device_id == SPA_ID_INVALID)
+ return -ENOENT;
+
+ port_index = find_port_index(card, direction, port_name);
+ if (port_index == SPA_ID_INVALID)
+ return -ENOENT;
+
+ if ((res = set_card_port(card, device_id, port_index)) < 0)
+ return res;
+
+ return operation_new(client, tag);
+}
+
+static int do_set_port_latency_offset(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct pw_manager *manager = client->manager;
+ const char *port_name = NULL;
+ struct pw_manager_object *card;
+ struct selector sel;
+ struct card_info card_info = CARD_INFO_INIT;
+ struct port_info *port_info;
+ int64_t offset;
+ int64_t value;
+ int res;
+ uint32_t n_ports;
+ size_t i;
+
+ spa_zero(sel);
+ sel.key = PW_KEY_DEVICE_NAME;
+ sel.type = pw_manager_object_is_card;
+
+ if ((res = message_get(m,
+ TAG_U32, &sel.index,
+ TAG_STRING, &sel.value,
+ TAG_STRING, &port_name,
+ TAG_S64, &offset,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u index:%u card_name:%s port_name:%s offset:%"PRIi64,
+ client->name, commands[command].name, tag, sel.index, sel.value, port_name, offset);
+
+ if ((sel.index == SPA_ID_INVALID && sel.value == NULL) ||
+ (sel.index != SPA_ID_INVALID && sel.value != NULL))
+ return -EINVAL;
+ if (port_name == NULL)
+ return -EINVAL;
+
+ value = offset * 1000; /* to nsec */
+
+ if ((card = select_object(manager, &sel)) == NULL)
+ return -ENOENT;
+
+ collect_card_info(card, &card_info);
+ port_info = alloca(card_info.n_ports * sizeof(*port_info));
+ card_info.active_profile = SPA_ID_INVALID;
+ n_ports = collect_port_info(card, &card_info, NULL, port_info);
+
+ /* Set offset on all devices of the port */
+ res = -ENOENT;
+ for (i = 0; i < n_ports; i++) {
+ struct port_info *pi = &port_info[i];
+ size_t j;
+
+ if (!spa_streq(pi->name, port_name))
+ continue;
+
+ res = 0;
+ for (j = 0; j < pi->n_devices; ++j) {
+ res = set_card_volume_mute_delay(card, pi->index, pi->devices[j], NULL, NULL, &value);
+ if (res < 0)
+ break;
+ }
+
+ if (res < 0)
+ break;
+
+ return operation_new(client, tag);
+ }
+
+ return res;
+}
+
+static int do_set_stream_name(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ uint32_t channel;
+ struct stream *stream;
+ const char *name = NULL;
+ struct spa_dict_item items[1];
+ int res;
+
+ if ((res = message_get(m,
+ TAG_U32, &channel,
+ TAG_STRING, &name,
+ TAG_INVALID)) < 0)
+ return -EPROTO;
+
+ if (name == NULL)
+ return -EINVAL;
+
+ pw_log_info("[%s] SET_STREAM_NAME tag:%u channel:%d name:%s",
+ client->name, tag, channel, name);
+
+ stream = pw_map_lookup(&client->streams, channel);
+ if (stream == NULL || stream->type == STREAM_TYPE_UPLOAD)
+ return -ENOENT;
+
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MEDIA_NAME, name);
+ pw_stream_update_properties(stream->stream,
+ &SPA_DICT_INIT(items, 1));
+
+ return reply_simple_ack(client, tag);
+}
+
+static int do_update_proplist(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ uint32_t channel, mode;
+ struct stream *stream;
+ struct pw_properties *props;
+ int res;
+
+ props = pw_properties_new(NULL, NULL);
+ if (props == NULL)
+ return -errno;
+
+ if (command != COMMAND_UPDATE_CLIENT_PROPLIST) {
+ if (message_get(m,
+ TAG_U32, &channel,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ } else {
+ channel = SPA_ID_INVALID;
+ }
+
+ pw_log_info("[%s] %s tag:%u channel:%d",
+ client->name, commands[command].name, tag, channel);
+
+ if (message_get(m,
+ TAG_U32, &mode,
+ TAG_PROPLIST, props,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+
+ if (command != COMMAND_UPDATE_CLIENT_PROPLIST) {
+ stream = pw_map_lookup(&client->streams, channel);
+ if (stream == NULL || stream->type == STREAM_TYPE_UPLOAD)
+ goto error_noentity;
+
+ pw_stream_update_properties(stream->stream, &props->dict);
+ } else {
+ if (pw_properties_update(client->props, &props->dict) > 0) {
+ client_update_quirks(client);
+ client->name = pw_properties_get(client->props, PW_KEY_APP_NAME);
+ pw_core_update_properties(client->core, &client->props->dict);
+ }
+ }
+ res = reply_simple_ack(client, tag);
+exit:
+ pw_properties_free(props);
+ return res;
+
+error_protocol:
+ res = -EPROTO;
+ goto exit;
+error_noentity:
+ res = -ENOENT;
+ goto exit;
+}
+
+static int do_remove_proplist(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ uint32_t i, channel;
+ struct stream *stream;
+ struct pw_properties *props;
+ struct spa_dict dict;
+ struct spa_dict_item *items;
+ int res;
+
+ props = pw_properties_new(NULL, NULL);
+ if (props == NULL)
+ return -errno;
+
+ if (command != COMMAND_REMOVE_CLIENT_PROPLIST) {
+ if (message_get(m,
+ TAG_U32, &channel,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ } else {
+ channel = SPA_ID_INVALID;
+ }
+
+ pw_log_info("[%s] %s tag:%u channel:%d",
+ client->name, commands[command].name, tag, channel);
+
+ while (true) {
+ const char *key;
+
+ if (message_get(m,
+ TAG_STRING, &key,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ if (key == NULL)
+ break;
+ pw_properties_set(props, key, key);
+ }
+
+ dict.n_items = props->dict.n_items;
+ dict.items = items = alloca(sizeof(struct spa_dict_item) * dict.n_items);
+ for (i = 0; i < dict.n_items; i++) {
+ items[i].key = props->dict.items[i].key;
+ items[i].value = NULL;
+ }
+
+ if (command != COMMAND_UPDATE_CLIENT_PROPLIST) {
+ stream = pw_map_lookup(&client->streams, channel);
+ if (stream == NULL || stream->type == STREAM_TYPE_UPLOAD)
+ goto error_noentity;
+
+ pw_stream_update_properties(stream->stream, &dict);
+ } else {
+ pw_core_update_properties(client->core, &dict);
+ }
+ res = reply_simple_ack(client, tag);
+exit:
+ pw_properties_free(props);
+ return res;
+
+error_protocol:
+ res = -EPROTO;
+ goto exit;
+error_noentity:
+ res = -ENOENT;
+ goto exit;
+}
+
+
+static int do_get_server_info(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ struct pw_manager *manager = client->manager;
+ struct pw_core_info *info = manager ? manager->info : NULL;
+ char name[256];
+ struct message *reply;
+
+ pw_log_info("[%s] GET_SERVER_INFO tag:%u", client->name, tag);
+
+ snprintf(name, sizeof(name), "PulseAudio (on PipeWire %s)", pw_get_library_version());
+
+ reply = reply_new(client, tag);
+ message_put(reply,
+ TAG_STRING, name,
+ TAG_STRING, "15.0.0",
+ TAG_STRING, pw_get_user_name(),
+ TAG_STRING, pw_get_host_name(),
+ TAG_SAMPLE_SPEC, &impl->defs.sample_spec,
+ TAG_STRING, manager ? get_default(client, true) : "", /* default sink name */
+ TAG_STRING, manager ? get_default(client, false) : "", /* default source name */
+ TAG_U32, info ? info->cookie : 0, /* cookie */
+ TAG_INVALID);
+
+ if (client->version >= 15) {
+ message_put(reply,
+ TAG_CHANNEL_MAP, &impl->defs.channel_map,
+ TAG_INVALID);
+ }
+ return client_queue_message(client, reply);
+}
+
+static int do_stat(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ struct message *reply;
+
+ pw_log_info("[%s] STAT tag:%u", client->name, tag);
+
+ reply = reply_new(client, tag);
+ message_put(reply,
+ TAG_U32, impl->stat.n_allocated, /* n_allocated */
+ TAG_U32, impl->stat.allocated, /* allocated size */
+ TAG_U32, impl->stat.n_accumulated, /* n_accumulated */
+ TAG_U32, impl->stat.accumulated, /* accumulated_size */
+ TAG_U32, impl->stat.sample_cache, /* sample cache size */
+ TAG_INVALID);
+
+ return client_queue_message(client, reply);
+}
+
+static int do_lookup(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct message *reply;
+ struct pw_manager_object *o;
+ const char *name;
+ bool is_sink = command == COMMAND_LOOKUP_SINK;
+ bool is_monitor;
+
+ if (message_get(m,
+ TAG_STRING, &name,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] LOOKUP tag:%u name:'%s'", client->name, tag, name);
+
+ if ((o = find_device(client, SPA_ID_INVALID, name, is_sink, &is_monitor)) == NULL)
+ return -ENOENT;
+
+ reply = reply_new(client, tag);
+ message_put(reply,
+ TAG_U32, o->index,
+ TAG_INVALID);
+
+ return client_queue_message(client, reply);
+}
+
+static int do_drain_stream(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ uint32_t channel;
+ struct stream *stream;
+
+ if (message_get(m,
+ TAG_U32, &channel,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] DRAIN tag:%u channel:%d", client->name, tag, channel);
+ stream = pw_map_lookup(&client->streams, channel);
+ if (stream == NULL || stream->type != STREAM_TYPE_PLAYBACK)
+ return -ENOENT;
+
+ stream->drain_tag = tag;
+ stream->draining = true;
+ stream_set_paused(stream, false, "drain start");
+
+ return 0;
+}
+
+static int fill_client_info(struct client *client, struct message *m,
+ struct pw_manager_object *o)
+{
+ struct pw_client_info *info = o->info;
+ struct pw_manager *manager = client->manager;
+ const char *str;
+ uint32_t module_id = SPA_ID_INVALID;
+
+ if (!pw_manager_object_is_client(o) || info == NULL || info->props == NULL)
+ return -ENOENT;
+
+ if ((str = spa_dict_lookup(info->props, PW_KEY_MODULE_ID)) != NULL)
+ module_id = (uint32_t)atoi(str);
+
+ message_put(m,
+ TAG_U32, o->index, /* client index */
+ TAG_STRING, pw_properties_get(o->props, PW_KEY_APP_NAME),
+ TAG_U32, id_to_index(manager, module_id), /* module index */
+ TAG_STRING, "PipeWire", /* driver */
+ TAG_INVALID);
+ if (client->version >= 13) {
+ message_put(m,
+ TAG_PROPLIST, info->props,
+ TAG_INVALID);
+ }
+ return 0;
+}
+
+static int fill_module_info(struct client *client, struct message *m,
+ struct pw_manager_object *o)
+{
+ struct pw_module_info *info = o->info;
+
+ if (!pw_manager_object_is_module(o) || info == NULL || info->props == NULL)
+ return -ENOENT;
+
+ message_put(m,
+ TAG_U32, o->index, /* module index */
+ TAG_STRING, info->name,
+ TAG_STRING, info->args,
+ TAG_U32, -1, /* n_used */
+ TAG_INVALID);
+
+ if (client->version < 15) {
+ message_put(m,
+ TAG_BOOLEAN, false, /* auto unload deprecated */
+ TAG_INVALID);
+ }
+ if (client->version >= 15) {
+ message_put(m,
+ TAG_PROPLIST, info->props,
+ TAG_INVALID);
+ }
+ return 0;
+}
+
+static int fill_ext_module_info(struct client *client, struct message *m,
+ struct module *module)
+{
+ message_put(m,
+ TAG_U32, module->index, /* module index */
+ TAG_STRING, module->info->name,
+ TAG_STRING, module->args,
+ TAG_U32, -1, /* n_used */
+ TAG_INVALID);
+
+ if (client->version < 15) {
+ message_put(m,
+ TAG_BOOLEAN, false, /* auto unload deprecated */
+ TAG_INVALID);
+ }
+ if (client->version >= 15) {
+ message_put(m,
+ TAG_PROPLIST, module->info->properties,
+ TAG_INVALID);
+ }
+ return 0;
+}
+
+static int64_t get_port_latency_offset(struct client *client, struct pw_manager_object *card, struct port_info *pi)
+{
+ struct pw_manager *m = client->manager;
+ struct pw_manager_object *o;
+ size_t j;
+
+ /*
+ * The latency offset is a property of nodes in PipeWire, so we look it up on the
+ * nodes. We'll return the latency offset of the first node in the port.
+ *
+ * This is also because we need to be consistent with
+ * send_latency_offset_subscribe_event, which sends events on node changes. The
+ * route data might not be updated yet when these events arrive.
+ */
+ for (j = 0; j < pi->n_devices; ++j) {
+ spa_list_for_each(o, &m->object_list, link) {
+ const char *str;
+ uint32_t card_id = SPA_ID_INVALID;
+ uint32_t device_id = SPA_ID_INVALID;
+ struct pw_node_info *info;
+
+ if (o->creating || o->removing)
+ continue;
+ if (!pw_manager_object_is_sink(o) && !pw_manager_object_is_source_or_monitor(o))
+ continue;
+ if ((info = o->info) == NULL || info->props == NULL)
+ continue;
+ if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)) != NULL)
+ card_id = (uint32_t)atoi(str);
+ if (card_id != card->id)
+ continue;
+
+ if ((str = spa_dict_lookup(info->props, "card.profile.device")) != NULL)
+ device_id = (uint32_t)atoi(str);
+
+ if (device_id == pi->devices[j])
+ return get_node_latency_offset(o);
+ }
+ }
+
+ return 0LL;
+}
+
+static int fill_card_info(struct client *client, struct message *m,
+ struct pw_manager_object *o)
+{
+ struct pw_manager *manager = client->manager;
+ struct pw_device_info *info = o->info;
+ const char *str, *drv_name;
+ uint32_t module_id = SPA_ID_INVALID, n_profiles, n;
+ struct card_info card_info = CARD_INFO_INIT;
+ struct profile_info *profile_info;
+
+ if (!pw_manager_object_is_card(o) || info == NULL || info->props == NULL)
+ return -ENOENT;
+
+ if ((str = spa_dict_lookup(info->props, PW_KEY_MODULE_ID)) != NULL)
+ module_id = (uint32_t)atoi(str);
+
+ drv_name = spa_dict_lookup(info->props, PW_KEY_DEVICE_API);
+ if (drv_name && spa_streq("bluez5", drv_name))
+ drv_name = "module-bluez5-device.c"; /* blueman needs this */
+
+ message_put(m,
+ TAG_U32, o->index, /* card index */
+ TAG_STRING, spa_dict_lookup(info->props, PW_KEY_DEVICE_NAME),
+ TAG_U32, id_to_index(manager, module_id),
+ TAG_STRING, drv_name,
+ TAG_INVALID);
+
+ collect_card_info(o, &card_info);
+
+ message_put(m,
+ TAG_U32, card_info.n_profiles, /* n_profiles */
+ TAG_INVALID);
+
+ profile_info = alloca(card_info.n_profiles * sizeof(*profile_info));
+ n_profiles = collect_profile_info(o, &card_info, profile_info);
+
+ for (n = 0; n < n_profiles; n++) {
+ struct profile_info *pi = &profile_info[n];
+
+ message_put(m,
+ TAG_STRING, pi->name, /* profile name */
+ TAG_STRING, pi->description, /* profile description */
+ TAG_U32, pi->n_sinks, /* n_sinks */
+ TAG_U32, pi->n_sources, /* n_sources */
+ TAG_U32, pi->priority, /* priority */
+ TAG_INVALID);
+
+ if (client->version >= 29) {
+ message_put(m,
+ TAG_U32, pi->available != SPA_PARAM_AVAILABILITY_no, /* available */
+ TAG_INVALID);
+ }
+ }
+ message_put(m,
+ TAG_STRING, card_info.active_profile_name, /* active profile name */
+ TAG_PROPLIST, info->props,
+ TAG_INVALID);
+
+ if (client->version >= 26) {
+ uint32_t n_ports;
+ struct port_info *port_info, *pi;
+
+ port_info = alloca(card_info.n_ports * sizeof(*port_info));
+ card_info.active_profile = SPA_ID_INVALID;
+ n_ports = collect_port_info(o, &card_info, NULL, port_info);
+
+ message_put(m,
+ TAG_U32, n_ports, /* n_ports */
+ TAG_INVALID);
+
+ for (n = 0; n < n_ports; n++) {
+ struct spa_dict_item *items;
+ struct spa_dict *pdict = NULL, dict;
+ uint32_t i, pi_n_profiles;
+
+ pi = &port_info[n];
+
+ if (pi->info && pi->n_props > 0) {
+ items = alloca(pi->n_props * sizeof(*items));
+ dict.items = items;
+ pdict = collect_props(pi->info, &dict);
+ }
+
+ message_put(m,
+ TAG_STRING, pi->name, /* port name */
+ TAG_STRING, pi->description, /* port description */
+ TAG_U32, pi->priority, /* port priority */
+ TAG_U32, pi->available, /* port available */
+ TAG_U8, pi->direction == SPA_DIRECTION_INPUT ? 2 : 1, /* port direction */
+ TAG_PROPLIST, pdict, /* port proplist */
+ TAG_INVALID);
+
+ pi_n_profiles = SPA_MIN(pi->n_profiles, n_profiles);
+ if (pi->n_profiles != pi_n_profiles) {
+ /* libpulse assumes port profile array size <= n_profiles */
+ pw_log_error("%p: card %d port %d profiles inconsistent (%d < %d)",
+ client->impl, o->id, n, n_profiles, pi->n_profiles);
+ }
+
+ message_put(m,
+ TAG_U32, pi_n_profiles, /* n_profiles */
+ TAG_INVALID);
+
+ for (i = 0; i < pi_n_profiles; i++) {
+ uint32_t j;
+ const char *name = "off";
+
+ for (j = 0; j < n_profiles; ++j) {
+ if (profile_info[j].index == pi->profiles[i]) {
+ name = profile_info[j].name;
+ break;
+ }
+ }
+
+ message_put(m,
+ TAG_STRING, name, /* profile name */
+ TAG_INVALID);
+ }
+ if (client->version >= 27) {
+ int64_t latency_offset = get_port_latency_offset(client, o, pi);
+ message_put(m,
+ TAG_S64, latency_offset / 1000, /* port latency offset */
+ TAG_INVALID);
+ }
+ if (client->version >= 34) {
+ message_put(m,
+ TAG_STRING, pi->availability_group, /* available group */
+ TAG_U32, pi->type, /* port type */
+ TAG_INVALID);
+ }
+ }
+ }
+ return 0;
+}
+
+static int fill_sink_info_proplist(struct message *m, const struct spa_dict *sink_props,
+ const struct pw_manager_object *card)
+{
+ struct pw_device_info *card_info = card ? card->info : NULL;
+ struct pw_properties *props = NULL;
+
+ if (card_info && card_info->props) {
+ props = pw_properties_new_dict(sink_props);
+ if (props == NULL)
+ return -ENOMEM;
+
+ pw_properties_add(props, card_info->props);
+ sink_props = &props->dict;
+ }
+ message_put(m, TAG_PROPLIST, sink_props, TAG_INVALID);
+
+ pw_properties_free(props);
+
+ return 0;
+}
+
+static bool validate_device_info(struct device_info *dev_info)
+{
+ return sample_spec_valid(&dev_info->ss) &&
+ channel_map_valid(&dev_info->map) &&
+ volume_valid(&dev_info->volume_info.volume);
+}
+
+static int fill_sink_info(struct client *client, struct message *m,
+ struct pw_manager_object *o)
+{
+ struct impl *impl = client->impl;
+ struct pw_node_info *info = o->info;
+ struct pw_manager *manager = client->manager;
+ const char *name, *desc, *str;
+ char *monitor_name = NULL;
+ uint32_t module_id = SPA_ID_INVALID;
+ uint32_t card_id = SPA_ID_INVALID;
+ struct pw_manager_object *card = NULL;
+ uint32_t flags;
+ struct card_info card_info = CARD_INFO_INIT;
+ struct device_info dev_info = DEVICE_INFO_INIT(PW_DIRECTION_OUTPUT);
+ size_t size;
+
+ if (!pw_manager_object_is_sink(o) || info == NULL || info->props == NULL)
+ return -ENOENT;
+
+ name = spa_dict_lookup(info->props, PW_KEY_NODE_NAME);
+ if ((desc = spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION)) == NULL)
+ desc = name ? name : "Unknown";
+ if (name == NULL)
+ name = "unknown";
+
+ size = strlen(name) + 10;
+ monitor_name = alloca(size);
+ if (pw_manager_object_is_source(o))
+ snprintf(monitor_name, size, "%s", name);
+ else
+ snprintf(monitor_name, size, "%s.monitor", name);
+
+ if ((str = spa_dict_lookup(info->props, PW_KEY_MODULE_ID)) != NULL)
+ module_id = id_to_index(manager, (uint32_t)atoi(str));
+ if (module_id == SPA_ID_INVALID &&
+ (str = spa_dict_lookup(info->props, "pulse.module.id")) != NULL)
+ module_id = (uint32_t)atoi(str);
+ if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)) != NULL)
+ card_id = (uint32_t)atoi(str);
+ if ((str = spa_dict_lookup(info->props, "card.profile.device")) != NULL)
+ dev_info.device = (uint32_t)atoi(str);
+ if (card_id != SPA_ID_INVALID) {
+ struct selector sel = { .id = card_id, .type = pw_manager_object_is_card, };
+ card = select_object(manager, &sel);
+ }
+ if (card)
+ collect_card_info(card, &card_info);
+
+ collect_device_info(o, card, &dev_info, false, &impl->defs);
+
+ if (!validate_device_info(&dev_info)) {
+ pw_log_warn("%d: sink not ready: sample:%d map:%d volume:%d",
+ o->id, sample_spec_valid(&dev_info.ss),
+ channel_map_valid(&dev_info.map),
+ volume_valid(&dev_info.volume_info.volume));
+ return -ENOENT;
+ }
+
+ flags = SINK_LATENCY | SINK_DYNAMIC_LATENCY | SINK_DECIBEL_VOLUME;
+ if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_API)) != NULL)
+ flags |= SINK_HARDWARE;
+ if ((str = spa_dict_lookup(info->props, PW_KEY_NODE_NETWORK)) != NULL)
+ flags |= SINK_NETWORK;
+ if (SPA_FLAG_IS_SET(dev_info.volume_info.flags, VOLUME_HW_VOLUME))
+ flags |= SINK_HW_VOLUME_CTRL;
+ if (SPA_FLAG_IS_SET(dev_info.volume_info.flags, VOLUME_HW_MUTE))
+ flags |= SINK_HW_MUTE_CTRL;
+ if (dev_info.have_iec958codecs)
+ flags |= SINK_SET_FORMATS;
+
+ if (client->quirks & QUIRK_FORCE_S16_FORMAT)
+ dev_info.ss.format = SPA_AUDIO_FORMAT_S16;
+
+ message_put(m,
+ TAG_U32, o->index, /* sink index */
+ TAG_STRING, name,
+ TAG_STRING, desc,
+ TAG_SAMPLE_SPEC, &dev_info.ss,
+ TAG_CHANNEL_MAP, &dev_info.map,
+ TAG_U32, module_id, /* module index */
+ TAG_CVOLUME, &dev_info.volume_info.volume,
+ TAG_BOOLEAN, dev_info.volume_info.mute,
+ TAG_U32, o->index, /* monitor source index */
+ TAG_STRING, monitor_name, /* monitor source name */
+ TAG_USEC, 0LL, /* latency */
+ TAG_STRING, "PipeWire", /* driver */
+ TAG_U32, flags, /* flags */
+ TAG_INVALID);
+
+ if (client->version >= 13) {
+ int res;
+ if ((res = fill_sink_info_proplist(m, info->props, card)) < 0)
+ return res;
+ message_put(m,
+ TAG_USEC, 0LL, /* requested latency */
+ TAG_INVALID);
+ }
+ if (client->version >= 15) {
+ bool is_linked = collect_is_linked(manager, o->id, SPA_DIRECTION_INPUT);
+ int state = node_state(info->state);
+
+ /* running with nothing linked is probably the monitor that is
+ * keeping this sink busy */
+ if (state == STATE_RUNNING && !is_linked)
+ state = STATE_IDLE;
+
+ message_put(m,
+ TAG_VOLUME, dev_info.volume_info.base, /* base volume */
+ TAG_U32, state, /* state */
+ TAG_U32, dev_info.volume_info.steps, /* n_volume_steps */
+ TAG_U32, card ? card->index : SPA_ID_INVALID, /* card index */
+ TAG_INVALID);
+ }
+ if (client->version >= 16) {
+ uint32_t n_ports, n;
+ struct port_info *port_info, *pi;
+
+ port_info = alloca(card_info.n_ports * sizeof(*port_info));
+ n_ports = collect_port_info(card, &card_info, &dev_info, port_info);
+
+ message_put(m,
+ TAG_U32, n_ports, /* n_ports */
+ TAG_INVALID);
+ for (n = 0; n < n_ports; n++) {
+ pi = &port_info[n];
+ message_put(m,
+ TAG_STRING, pi->name, /* name */
+ TAG_STRING, pi->description, /* description */
+ TAG_U32, pi->priority, /* priority */
+ TAG_INVALID);
+ if (client->version >= 24) {
+ message_put(m,
+ TAG_U32, pi->available, /* available */
+ TAG_INVALID);
+ }
+ if (client->version >= 34) {
+ message_put(m,
+ TAG_STRING, pi->availability_group, /* availability_group */
+ TAG_U32, pi->type, /* type */
+ TAG_INVALID);
+ }
+ }
+ message_put(m,
+ TAG_STRING, dev_info.active_port_name, /* active port name */
+ TAG_INVALID);
+ }
+ if (client->version >= 21) {
+ struct pw_manager_param *p;
+ struct format_info info[32];
+ uint32_t i, n_info = 0;
+
+ spa_list_for_each(p, &o->param_list, link) {
+ uint32_t index = 0;
+
+ if (p->id != SPA_PARAM_EnumFormat)
+ continue;
+
+ while (n_info < SPA_N_ELEMENTS(info)) {
+ spa_zero(info[n_info]);
+ if (format_info_from_param(&info[n_info], p->param, index++) < 0)
+ break;
+ if (info[n_info].encoding == ENCODING_ANY ||
+ (info[n_info].encoding == ENCODING_PCM && info[n_info].props != NULL)) {
+ format_info_clear(&info[n_info]);
+ continue;
+ }
+ n_info++;
+ }
+ }
+ message_put(m,
+ TAG_U8, n_info, /* n_formats */
+ TAG_INVALID);
+ for (i = 0; i < n_info; i++) {
+ message_put(m,
+ TAG_FORMAT_INFO, &info[i],
+ TAG_INVALID);
+ format_info_clear(&info[i]);
+ }
+ }
+ return 0;
+}
+
+static int fill_source_info_proplist(struct message *m, const struct spa_dict *source_props,
+ const struct pw_manager_object *card, const bool is_monitor)
+{
+ struct pw_device_info *card_info = card ? card->info : NULL;
+ struct pw_properties *props = NULL;
+
+ if ((card_info && card_info->props) || is_monitor) {
+ props = pw_properties_new_dict(source_props);
+ if (props == NULL)
+ return -ENOMEM;
+
+ if (card_info && card_info->props)
+ pw_properties_add(props, card_info->props);
+
+ if (is_monitor)
+ pw_properties_set(props, PW_KEY_DEVICE_CLASS, "monitor");
+
+ source_props = &props->dict;
+ }
+ message_put(m, TAG_PROPLIST, source_props, TAG_INVALID);
+
+ pw_properties_free(props);
+
+ return 0;
+}
+
+static int fill_source_info(struct client *client, struct message *m,
+ struct pw_manager_object *o)
+{
+ struct impl *impl = client->impl;
+ struct pw_node_info *info = o->info;
+ struct pw_manager *manager = client->manager;
+ bool is_monitor;
+ const char *name, *desc, *str;
+ char *monitor_name = NULL;
+ char *monitor_desc = NULL;
+ uint32_t module_id = SPA_ID_INVALID;
+ uint32_t card_id = SPA_ID_INVALID;
+ struct pw_manager_object *card = NULL;
+ uint32_t flags;
+ struct card_info card_info = CARD_INFO_INIT;
+ struct device_info dev_info = DEVICE_INFO_INIT(PW_DIRECTION_INPUT);
+ size_t size;
+
+ is_monitor = pw_manager_object_is_monitor(o);
+ if ((!pw_manager_object_is_source(o) && !is_monitor) || info == NULL || info->props == NULL)
+ return -ENOENT;
+
+ name = spa_dict_lookup(info->props, PW_KEY_NODE_NAME);
+ if ((desc = spa_dict_lookup(info->props, PW_KEY_NODE_DESCRIPTION)) == NULL)
+ desc = name ? name : "Unknown";
+ if (name == NULL)
+ name = "unknown";
+
+ size = strlen(name) + 10;
+ monitor_name = alloca(size);
+ snprintf(monitor_name, size, "%s.monitor", name);
+
+ size = strlen(desc) + 20;
+ monitor_desc = alloca(size);
+ snprintf(monitor_desc, size, "Monitor of %s", desc);
+
+ if ((str = spa_dict_lookup(info->props, PW_KEY_MODULE_ID)) != NULL)
+ module_id = id_to_index(manager, (uint32_t)atoi(str));
+ if (module_id == SPA_ID_INVALID &&
+ (str = spa_dict_lookup(info->props, "pulse.module.id")) != NULL)
+ module_id = (uint32_t)atoi(str);
+ if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_ID)) != NULL)
+ card_id = (uint32_t)atoi(str);
+ if ((str = spa_dict_lookup(info->props, "card.profile.device")) != NULL)
+ dev_info.device = (uint32_t)atoi(str);
+
+ if (card_id != SPA_ID_INVALID) {
+ struct selector sel = { .id = card_id, .type = pw_manager_object_is_card, };
+ card = select_object(manager, &sel);
+ }
+ if (card)
+ collect_card_info(card, &card_info);
+
+ collect_device_info(o, card, &dev_info, is_monitor, &impl->defs);
+
+ if (!validate_device_info(&dev_info)) {
+ pw_log_warn("%d: source not ready: sample:%d map:%d volume:%d",
+ o->id, sample_spec_valid(&dev_info.ss),
+ channel_map_valid(&dev_info.map),
+ volume_valid(&dev_info.volume_info.volume));
+ return -ENOENT;
+ }
+
+ flags = SOURCE_LATENCY | SOURCE_DYNAMIC_LATENCY | SOURCE_DECIBEL_VOLUME;
+ if ((str = spa_dict_lookup(info->props, PW_KEY_DEVICE_API)) != NULL)
+ flags |= SOURCE_HARDWARE;
+ if ((str = spa_dict_lookup(info->props, PW_KEY_NODE_NETWORK)) != NULL)
+ flags |= SOURCE_NETWORK;
+ if (SPA_FLAG_IS_SET(dev_info.volume_info.flags, VOLUME_HW_VOLUME))
+ flags |= SOURCE_HW_VOLUME_CTRL;
+ if (SPA_FLAG_IS_SET(dev_info.volume_info.flags, VOLUME_HW_MUTE))
+ flags |= SOURCE_HW_MUTE_CTRL;
+
+ if (client->quirks & QUIRK_FORCE_S16_FORMAT)
+ dev_info.ss.format = SPA_AUDIO_FORMAT_S16;
+
+ message_put(m,
+ TAG_U32, o->index, /* source index */
+ TAG_STRING, is_monitor ? monitor_name : name,
+ TAG_STRING, is_monitor ? monitor_desc : desc,
+ TAG_SAMPLE_SPEC, &dev_info.ss,
+ TAG_CHANNEL_MAP, &dev_info.map,
+ TAG_U32, module_id, /* module index */
+ TAG_CVOLUME, &dev_info.volume_info.volume,
+ TAG_BOOLEAN, dev_info.volume_info.mute,
+ TAG_U32, is_monitor ? o->index : SPA_ID_INVALID,/* monitor of sink */
+ TAG_STRING, is_monitor ? name : NULL, /* monitor of sink name */
+ TAG_USEC, 0LL, /* latency */
+ TAG_STRING, "PipeWire", /* driver */
+ TAG_U32, flags, /* flags */
+ TAG_INVALID);
+
+ if (client->version >= 13) {
+ int res;
+ if ((res = fill_source_info_proplist(m, info->props, card, is_monitor)) < 0)
+ return res;
+ message_put(m,
+ TAG_USEC, 0LL, /* requested latency */
+ TAG_INVALID);
+ }
+ if (client->version >= 15) {
+ bool is_linked = collect_is_linked(manager, o->id, SPA_DIRECTION_OUTPUT);
+ int state = node_state(info->state);
+
+ /* running with nothing linked is probably the sink that is
+ * keeping this source busy */
+ if (state == STATE_RUNNING && !is_linked)
+ state = STATE_IDLE;
+
+ message_put(m,
+ TAG_VOLUME, dev_info.volume_info.base, /* base volume */
+ TAG_U32, state, /* state */
+ TAG_U32, dev_info.volume_info.steps, /* n_volume_steps */
+ TAG_U32, card ? card->index : SPA_ID_INVALID, /* card index */
+ TAG_INVALID);
+ }
+ if (client->version >= 16) {
+ uint32_t n_ports, n;
+ struct port_info *port_info, *pi;
+
+ port_info = alloca(card_info.n_ports * sizeof(*port_info));
+ n_ports = collect_port_info(card, &card_info, &dev_info, port_info);
+
+ message_put(m,
+ TAG_U32, n_ports, /* n_ports */
+ TAG_INVALID);
+ for (n = 0; n < n_ports; n++) {
+ pi = &port_info[n];
+ message_put(m,
+ TAG_STRING, pi->name, /* name */
+ TAG_STRING, pi->description, /* description */
+ TAG_U32, pi->priority, /* priority */
+ TAG_INVALID);
+ if (client->version >= 24) {
+ message_put(m,
+ TAG_U32, pi->available, /* available */
+ TAG_INVALID);
+ }
+ if (client->version >= 34) {
+ message_put(m,
+ TAG_STRING, pi->availability_group, /* availability_group */
+ TAG_U32, pi->type, /* type */
+ TAG_INVALID);
+ }
+ }
+ message_put(m,
+ TAG_STRING, dev_info.active_port_name, /* active port name */
+ TAG_INVALID);
+ }
+ if (client->version >= 21) {
+ struct format_info info;
+ spa_zero(info);
+ info.encoding = ENCODING_PCM;
+ message_put(m,
+ TAG_U8, 1, /* n_formats */
+ TAG_FORMAT_INFO, &info,
+ TAG_INVALID);
+ }
+ return 0;
+}
+
+static const char *get_media_name(struct pw_node_info *info)
+{
+ const char *media_name;
+ media_name = spa_dict_lookup(info->props, PW_KEY_MEDIA_NAME);
+ if (media_name == NULL)
+ media_name = "";
+ return media_name;
+}
+
+static int fill_sink_input_info(struct client *client, struct message *m,
+ struct pw_manager_object *o)
+{
+ struct impl *impl = client->impl;
+ struct pw_node_info *info = o->info;
+ struct pw_manager *manager = client->manager;
+ const char *str;
+ uint32_t module_id = SPA_ID_INVALID, client_id = SPA_ID_INVALID;
+ uint32_t peer_index;
+ struct device_info dev_info = DEVICE_INFO_INIT(PW_DIRECTION_OUTPUT);
+
+ if (!pw_manager_object_is_sink_input(o) || info == NULL || info->props == NULL)
+ return -ENOENT;
+
+ if ((str = spa_dict_lookup(info->props, PW_KEY_MODULE_ID)) != NULL)
+ module_id = id_to_index(manager, (uint32_t)atoi(str));
+ if (module_id == SPA_ID_INVALID &&
+ (str = spa_dict_lookup(info->props, "pulse.module.id")) != NULL)
+ module_id = (uint32_t)atoi(str);
+
+ if (!pw_manager_object_is_virtual(o) &&
+ (str = spa_dict_lookup(info->props, PW_KEY_CLIENT_ID)) != NULL)
+ client_id = (uint32_t)atoi(str);
+
+ collect_device_info(o, NULL, &dev_info, false, &impl->defs);
+
+ if (!validate_device_info(&dev_info))
+ return -ENOENT;
+
+ peer_index = get_temporary_move_target(client, o);
+ if (peer_index == SPA_ID_INVALID) {
+ struct pw_manager_object *peer;
+ peer = find_linked(manager, o->id, PW_DIRECTION_OUTPUT);
+ if (peer && pw_manager_object_is_sink(peer))
+ peer_index = peer->index;
+ else
+ peer_index = SPA_ID_INVALID;
+ }
+
+ message_put(m,
+ TAG_U32, o->index, /* sink_input index */
+ TAG_STRING, get_media_name(info),
+ TAG_U32, module_id, /* module index */
+ TAG_U32, id_to_index(manager, client_id), /* client index */
+ TAG_U32, peer_index, /* sink index */
+ TAG_SAMPLE_SPEC, &dev_info.ss,
+ TAG_CHANNEL_MAP, &dev_info.map,
+ TAG_CVOLUME, &dev_info.volume_info.volume,
+ TAG_USEC, 0LL, /* latency */
+ TAG_USEC, 0LL, /* sink latency */
+ TAG_STRING, "PipeWire", /* resample method */
+ TAG_STRING, "PipeWire", /* driver */
+ TAG_INVALID);
+ if (client->version >= 11)
+ message_put(m,
+ TAG_BOOLEAN, dev_info.volume_info.mute, /* muted */
+ TAG_INVALID);
+ if (client->version >= 13)
+ message_put(m,
+ TAG_PROPLIST, info->props,
+ TAG_INVALID);
+ if (client->version >= 19)
+ message_put(m,
+ TAG_BOOLEAN, info->state != PW_NODE_STATE_RUNNING, /* corked */
+ TAG_INVALID);
+ if (client->version >= 20)
+ message_put(m,
+ TAG_BOOLEAN, true, /* has_volume */
+ TAG_BOOLEAN, true, /* volume writable */
+ TAG_INVALID);
+ if (client->version >= 21) {
+ struct format_info fi;
+ format_info_from_spec(&fi, &dev_info.ss, &dev_info.map);
+ message_put(m,
+ TAG_FORMAT_INFO, &fi,
+ TAG_INVALID);
+ format_info_clear(&fi);
+ }
+ return 0;
+}
+
+static int fill_source_output_info(struct client *client, struct message *m,
+ struct pw_manager_object *o)
+{
+ struct impl *impl = client->impl;
+ struct pw_node_info *info = o->info;
+ struct pw_manager *manager = client->manager;
+ const char *str;
+ uint32_t module_id = SPA_ID_INVALID, client_id = SPA_ID_INVALID;
+ uint32_t peer_index;
+ struct device_info dev_info = DEVICE_INFO_INIT(PW_DIRECTION_INPUT);
+
+ if (!pw_manager_object_is_source_output(o) || info == NULL || info->props == NULL)
+ return -ENOENT;
+
+ if ((str = spa_dict_lookup(info->props, PW_KEY_MODULE_ID)) != NULL)
+ module_id = id_to_index(manager, (uint32_t)atoi(str));
+ if (module_id == SPA_ID_INVALID &&
+ (str = spa_dict_lookup(info->props, "pulse.module.id")) != NULL)
+ module_id = (uint32_t)atoi(str);
+
+ if (!pw_manager_object_is_virtual(o) &&
+ (str = spa_dict_lookup(info->props, PW_KEY_CLIENT_ID)) != NULL)
+ client_id = (uint32_t)atoi(str);
+
+ collect_device_info(o, NULL, &dev_info, false, &impl->defs);
+
+ if (!validate_device_info(&dev_info))
+ return -ENOENT;
+
+ peer_index = get_temporary_move_target(client, o);
+ if (peer_index == SPA_ID_INVALID) {
+ struct pw_manager_object *peer;
+ peer = find_linked(manager, o->id, PW_DIRECTION_INPUT);
+ if (peer && pw_manager_object_is_source_or_monitor(peer))
+ peer_index = peer->index;
+ else
+ peer_index = SPA_ID_INVALID;
+ }
+
+ message_put(m,
+ TAG_U32, o->index, /* source_output index */
+ TAG_STRING, get_media_name(info),
+ TAG_U32, module_id, /* module index */
+ TAG_U32, id_to_index(manager, client_id), /* client index */
+ TAG_U32, peer_index, /* source index */
+ TAG_SAMPLE_SPEC, &dev_info.ss,
+ TAG_CHANNEL_MAP, &dev_info.map,
+ TAG_USEC, 0LL, /* latency */
+ TAG_USEC, 0LL, /* source latency */
+ TAG_STRING, "PipeWire", /* resample method */
+ TAG_STRING, "PipeWire", /* driver */
+ TAG_INVALID);
+ if (client->version >= 13)
+ message_put(m,
+ TAG_PROPLIST, info->props,
+ TAG_INVALID);
+ if (client->version >= 19)
+ message_put(m,
+ TAG_BOOLEAN, info->state != PW_NODE_STATE_RUNNING, /* corked */
+ TAG_INVALID);
+ if (client->version >= 22) {
+ struct format_info fi;
+ format_info_from_spec(&fi, &dev_info.ss, &dev_info.map);
+ message_put(m,
+ TAG_CVOLUME, &dev_info.volume_info.volume,
+ TAG_BOOLEAN, dev_info.volume_info.mute, /* muted */
+ TAG_BOOLEAN, true, /* has_volume */
+ TAG_BOOLEAN, true, /* volume writable */
+ TAG_FORMAT_INFO, &fi,
+ TAG_INVALID);
+ format_info_clear(&fi);
+ }
+ return 0;
+}
+
+static int do_get_info(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ struct pw_manager *manager = client->manager;
+ struct message *reply = NULL;
+ int res;
+ struct pw_manager_object *o;
+ struct selector sel;
+ int (*fill_func) (struct client *client, struct message *m, struct pw_manager_object *o) = NULL;
+
+ spa_zero(sel);
+
+ if (message_get(m,
+ TAG_U32, &sel.index,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+
+ reply = reply_new(client, tag);
+
+ if (command == COMMAND_GET_MODULE_INFO && (sel.index & MODULE_FLAG) != 0) {
+ struct module *module;
+ module = pw_map_lookup(&impl->modules, sel.index & MODULE_INDEX_MASK);
+ if (module == NULL)
+ goto error_noentity;
+ fill_ext_module_info(client, reply, module);
+ return client_queue_message(client, reply);
+ }
+
+ switch (command) {
+ case COMMAND_GET_CLIENT_INFO:
+ sel.type = pw_manager_object_is_client;
+ fill_func = fill_client_info;
+ break;
+ case COMMAND_GET_MODULE_INFO:
+ sel.type = pw_manager_object_is_module;
+ fill_func = fill_module_info;
+ break;
+ case COMMAND_GET_CARD_INFO:
+ sel.type = pw_manager_object_is_card;
+ sel.key = PW_KEY_DEVICE_NAME;
+ fill_func = fill_card_info;
+ break;
+ case COMMAND_GET_SINK_INFO:
+ sel.type = pw_manager_object_is_sink;
+ sel.key = PW_KEY_NODE_NAME;
+ fill_func = fill_sink_info;
+ break;
+ case COMMAND_GET_SOURCE_INFO:
+ sel.type = pw_manager_object_is_source_or_monitor;
+ sel.key = PW_KEY_NODE_NAME;
+ fill_func = fill_source_info;
+ break;
+ case COMMAND_GET_SINK_INPUT_INFO:
+ sel.type = pw_manager_object_is_sink_input;
+ fill_func = fill_sink_input_info;
+ break;
+ case COMMAND_GET_SOURCE_OUTPUT_INFO:
+ sel.type = pw_manager_object_is_source_output;
+ fill_func = fill_source_output_info;
+ break;
+ }
+ if (sel.key) {
+ if (message_get(m,
+ TAG_STRING, &sel.value,
+ TAG_INVALID) < 0)
+ goto error_protocol;
+ }
+ if (fill_func == NULL)
+ goto error_invalid;
+
+ if (sel.index != SPA_ID_INVALID && sel.value != NULL)
+ goto error_invalid;
+
+ pw_log_info("[%s] %s tag:%u index:%u name:%s", client->name,
+ commands[command].name, tag, sel.index, sel.value);
+
+ if (command == COMMAND_GET_SINK_INFO || command == COMMAND_GET_SOURCE_INFO) {
+ o = find_device(client, sel.index, sel.value,
+ command == COMMAND_GET_SINK_INFO, NULL);
+ } else {
+ if (sel.value == NULL && sel.index == SPA_ID_INVALID)
+ goto error_invalid;
+ o = select_object(manager, &sel);
+ }
+ if (o == NULL)
+ goto error_noentity;
+
+ if ((res = fill_func(client, reply, o)) < 0)
+ goto error;
+
+ return client_queue_message(client, reply);
+
+error_protocol:
+ res = -EPROTO;
+ goto error;
+error_noentity:
+ res = -ENOENT;
+ goto error;
+error_invalid:
+ res = -EINVAL;
+ goto error;
+error:
+ if (reply)
+ message_free(reply, false, false);
+ return res;
+}
+
+static uint64_t bytes_to_usec(uint64_t length, const struct sample_spec *ss)
+{
+ uint64_t u;
+ uint64_t frame_size = sample_spec_frame_size(ss);
+ if (frame_size == 0)
+ return 0;
+ u = length / frame_size;
+ u *= SPA_USEC_PER_SEC;
+ u /= ss->rate;
+ return u;
+}
+
+static int fill_sample_info(struct client *client, struct message *m,
+ struct sample *sample)
+{
+ struct volume vol;
+
+ volume_make(&vol, sample->ss.channels);
+
+ message_put(m,
+ TAG_U32, sample->index,
+ TAG_STRING, sample->name,
+ TAG_CVOLUME, &vol,
+ TAG_USEC, bytes_to_usec(sample->length, &sample->ss),
+ TAG_SAMPLE_SPEC, &sample->ss,
+ TAG_CHANNEL_MAP, &sample->map,
+ TAG_U32, sample->length,
+ TAG_BOOLEAN, false, /* lazy */
+ TAG_STRING, NULL, /* filename */
+ TAG_INVALID);
+
+ if (client->version >= 13) {
+ message_put(m,
+ TAG_PROPLIST, &sample->props->dict,
+ TAG_INVALID);
+ }
+ return 0;
+}
+
+static int do_get_sample_info(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ struct message *reply = NULL;
+ uint32_t index;
+ const char *name;
+ struct sample *sample;
+ int res;
+
+ if (message_get(m,
+ TAG_U32, &index,
+ TAG_STRING, &name,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ if ((index == SPA_ID_INVALID && name == NULL) ||
+ (index != SPA_ID_INVALID && name != NULL))
+ return -EINVAL;
+
+ pw_log_info("[%s] %s tag:%u index:%u name:%s", client->name,
+ commands[command].name, tag, index, name);
+
+ if ((sample = find_sample(impl, index, name)) == NULL)
+ return -ENOENT;
+
+ reply = reply_new(client, tag);
+ if ((res = fill_sample_info(client, reply, sample)) < 0)
+ goto error;
+
+ return client_queue_message(client, reply);
+
+error:
+ if (reply)
+ message_free(reply, false, false);
+ return res;
+}
+
+static int do_get_sample_info_list(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ struct message *reply;
+ union pw_map_item *item;
+
+ pw_log_info("[%s] %s tag:%u", client->name,
+ commands[command].name, tag);
+
+ reply = reply_new(client, tag);
+ pw_array_for_each(item, &impl->samples.items) {
+ struct sample *s = item->data;
+ if (pw_map_item_is_free(item))
+ continue;
+ fill_sample_info(client, reply, s);
+ }
+ return client_queue_message(client, reply);
+}
+
+struct info_list_data {
+ struct client *client;
+ struct message *reply;
+ int (*fill_func) (struct client *client, struct message *m, struct pw_manager_object *o);
+};
+
+static int do_list_info(void *data, struct pw_manager_object *object)
+{
+ struct info_list_data *info = data;
+ info->fill_func(info->client, info->reply, object);
+ return 0;
+}
+
+static int do_info_list_module(void *item, void *data)
+{
+ struct module *m = item;
+ struct info_list_data *info = data;
+ fill_ext_module_info(info->client, info->reply, m);
+ return 0;
+}
+
+static int do_get_info_list(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ struct pw_manager *manager = client->manager;
+ struct info_list_data info;
+
+ pw_log_info("[%s] %s tag:%u", client->name,
+ commands[command].name, tag);
+
+ spa_zero(info);
+ info.client = client;
+
+ switch (command) {
+ case COMMAND_GET_CLIENT_INFO_LIST:
+ info.fill_func = fill_client_info;
+ break;
+ case COMMAND_GET_MODULE_INFO_LIST:
+ info.fill_func = fill_module_info;
+ break;
+ case COMMAND_GET_CARD_INFO_LIST:
+ info.fill_func = fill_card_info;
+ break;
+ case COMMAND_GET_SINK_INFO_LIST:
+ info.fill_func = fill_sink_info;
+ break;
+ case COMMAND_GET_SOURCE_INFO_LIST:
+ info.fill_func = fill_source_info;
+ break;
+ case COMMAND_GET_SINK_INPUT_INFO_LIST:
+ info.fill_func = fill_sink_input_info;
+ break;
+ case COMMAND_GET_SOURCE_OUTPUT_INFO_LIST:
+ info.fill_func = fill_source_output_info;
+ break;
+ default:
+ return -ENOTSUP;
+ }
+
+ info.reply = reply_new(client, tag);
+ if (info.fill_func)
+ pw_manager_for_each_object(manager, do_list_info, &info);
+
+ if (command == COMMAND_GET_MODULE_INFO_LIST)
+ pw_map_for_each(&impl->modules, do_info_list_module, &info);
+
+ return client_queue_message(client, info.reply);
+}
+
+static int do_set_stream_buffer_attr(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ uint32_t channel;
+ struct stream *stream;
+ struct message *reply;
+ struct buffer_attr attr;
+ bool adjust_latency = false, early_requests = false;
+
+ if (message_get(m,
+ TAG_U32, &channel,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u channel:%u", client->name,
+ commands[command].name, tag, channel);
+
+ stream = pw_map_lookup(&client->streams, channel);
+ if (stream == NULL)
+ return -ENOENT;
+
+ if (command == COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR) {
+ if (stream->type != STREAM_TYPE_PLAYBACK)
+ return -ENOENT;
+
+ if (message_get(m,
+ TAG_U32, &attr.maxlength,
+ TAG_U32, &attr.tlength,
+ TAG_U32, &attr.prebuf,
+ TAG_U32, &attr.minreq,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+ } else {
+ if (stream->type != STREAM_TYPE_RECORD)
+ return -ENOENT;
+
+ if (message_get(m,
+ TAG_U32, &attr.maxlength,
+ TAG_U32, &attr.fragsize,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+ }
+ if (client->version >= 13) {
+ if (message_get(m,
+ TAG_BOOLEAN, &adjust_latency,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+ }
+ if (client->version >= 14) {
+ if (message_get(m,
+ TAG_BOOLEAN, &early_requests,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+ }
+
+ reply = reply_new(client, tag);
+
+ stream->adjust_latency = adjust_latency;
+ stream->early_requests = early_requests;
+
+ if (command == COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR) {
+ stream->lat_usec = set_playback_buffer_attr(stream, &attr);
+
+ message_put(reply,
+ TAG_U32, stream->attr.maxlength,
+ TAG_U32, stream->attr.tlength,
+ TAG_U32, stream->attr.prebuf,
+ TAG_U32, stream->attr.minreq,
+ TAG_INVALID);
+ if (client->version >= 13) {
+ message_put(reply,
+ TAG_USEC, stream->lat_usec, /* configured_sink_latency */
+ TAG_INVALID);
+ }
+ } else {
+ stream->lat_usec = set_record_buffer_attr(stream, &attr);
+
+ message_put(reply,
+ TAG_U32, stream->attr.maxlength,
+ TAG_U32, stream->attr.fragsize,
+ TAG_INVALID);
+ if (client->version >= 13) {
+ message_put(reply,
+ TAG_USEC, stream->lat_usec, /* configured_source_latency */
+ TAG_INVALID);
+ }
+ }
+ return client_queue_message(client, reply);
+}
+
+static int do_update_stream_sample_rate(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ uint32_t channel, rate;
+ struct stream *stream;
+ float corr;
+
+ if (message_get(m,
+ TAG_U32, &channel,
+ TAG_U32, &rate,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u channel:%u rate:%u", client->name,
+ commands[command].name, tag, channel, rate);
+
+ stream = pw_map_lookup(&client->streams, channel);
+ if (stream == NULL || stream->type == STREAM_TYPE_UPLOAD)
+ return -ENOENT;
+
+ stream->rate = rate;
+
+ corr = (double)rate/(double)stream->ss.rate;
+ pw_stream_set_control(stream->stream, SPA_PROP_rate, 1, &corr, NULL);
+
+ return reply_simple_ack(client, tag);
+}
+
+static int do_extension(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ uint32_t index;
+ const char *name;
+ const struct extension *ext;
+
+ if (message_get(m,
+ TAG_U32, &index,
+ TAG_STRING, &name,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u index:%u name:%s", client->name,
+ commands[command].name, tag, index, name);
+
+ if ((index == SPA_ID_INVALID && name == NULL) ||
+ (index != SPA_ID_INVALID && name != NULL))
+ return -EINVAL;
+
+ ext = extension_find(index, name);
+ if (ext == NULL)
+ return -ENOENT;
+
+ return ext->process(client, tag, m);
+}
+
+static int do_set_profile(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct pw_manager *manager = client->manager;
+ struct pw_manager_object *o;
+ const char *profile_name;
+ uint32_t profile_index = SPA_ID_INVALID;
+ struct selector sel;
+ char buf[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
+
+ spa_zero(sel);
+ sel.key = PW_KEY_DEVICE_NAME;
+ sel.type = pw_manager_object_is_card;
+
+ if (message_get(m,
+ TAG_U32, &sel.index,
+ TAG_STRING, &sel.value,
+ TAG_STRING, &profile_name,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u index:%u name:%s profile:%s", client->name,
+ commands[command].name, tag, sel.index, sel.value, profile_name);
+
+ if ((sel.index == SPA_ID_INVALID && sel.value == NULL) ||
+ (sel.index != SPA_ID_INVALID && sel.value != NULL))
+ return -EINVAL;
+ if (profile_name == NULL)
+ return -EINVAL;
+
+ if ((o = select_object(manager, &sel)) == NULL)
+ return -ENOENT;
+
+ if ((profile_index = find_profile_index(o, profile_name)) == SPA_ID_INVALID)
+ return -ENOENT;
+
+ if (!SPA_FLAG_IS_SET(o->permissions, PW_PERM_W | PW_PERM_X))
+ return -EACCES;
+
+ if (o->proxy == NULL)
+ return -ENOENT;
+
+ pw_device_set_param((struct pw_device*)o->proxy,
+ SPA_PARAM_Profile, 0,
+ spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamProfile, SPA_PARAM_Profile,
+ SPA_PARAM_PROFILE_index, SPA_POD_Int(profile_index),
+ SPA_PARAM_PROFILE_save, SPA_POD_Bool(true)));
+
+ return operation_new(client, tag);
+}
+
+static int do_set_default(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct pw_manager *manager = client->manager;
+ struct pw_manager_object *o;
+ const char *name, *str;
+ int res;
+ bool sink = command == COMMAND_SET_DEFAULT_SINK;
+
+ if (message_get(m,
+ TAG_STRING, &name,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u name:%s", client->name,
+ commands[command].name, tag, name);
+
+ if (name != NULL && (o = find_device(client, SPA_ID_INVALID, name, sink, NULL)) == NULL)
+ return -ENOENT;
+
+ if (name != NULL) {
+ if (o->props && (str = pw_properties_get(o->props, PW_KEY_NODE_NAME)) != NULL)
+ name = str;
+ else if (spa_strendswith(name, ".monitor"))
+ name = strndupa(name, strlen(name)-8);
+
+ res = pw_manager_set_metadata(manager, client->metadata_default,
+ PW_ID_CORE,
+ sink ? METADATA_CONFIG_DEFAULT_SINK : METADATA_CONFIG_DEFAULT_SOURCE,
+ "Spa:String:JSON", "{ \"name\": \"%s\" }", name);
+ } else {
+ res = pw_manager_set_metadata(manager, client->metadata_default,
+ PW_ID_CORE,
+ sink ? METADATA_CONFIG_DEFAULT_SINK : METADATA_CONFIG_DEFAULT_SOURCE,
+ NULL, NULL);
+ }
+ if (res < 0)
+ return res;
+
+ /*
+ * The metadata is not necessarily updated within one server sync.
+ * Correct functioning of MOVE_* commands requires knowing the current
+ * default target, so we need to stash temporary values here in case
+ * the client emits them before metadata gets updated.
+ */
+ if (sink) {
+ free(client->temporary_default_sink);
+ client->temporary_default_sink = name ? strdup(name) : NULL;
+ } else {
+ free(client->temporary_default_source);
+ client->temporary_default_source = name ? strdup(name) : NULL;
+ }
+
+ return operation_new(client, tag);
+}
+
+static int do_suspend(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct pw_manager_object *o;
+ const char *name;
+ uint32_t index, cmd;
+ bool sink = command == COMMAND_SUSPEND_SINK, suspend;
+
+ if (message_get(m,
+ TAG_U32, &index,
+ TAG_STRING, &name,
+ TAG_BOOLEAN, &suspend,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u index:%u name:%s", client->name,
+ commands[command].name, tag, index, name);
+
+ if ((o = find_device(client, index, name, sink, NULL)) == NULL)
+ return -ENOENT;
+
+ if (o->proxy == NULL)
+ return -ENOENT;
+
+ if (suspend) {
+ cmd = SPA_NODE_COMMAND_Suspend;
+ pw_node_send_command((struct pw_node*)o->proxy, &SPA_NODE_COMMAND_INIT(cmd));
+ }
+ return operation_new(client, tag);
+}
+
+static int do_move_stream(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct pw_manager *manager = client->manager;
+ struct pw_manager_object *o, *dev, *dev_default;
+ uint32_t index, index_device;
+ int target_id;
+ int64_t target_serial;
+ const char *name_device;
+ const char *name;
+ struct pw_node_info *info;
+ struct selector sel;
+ int res;
+ bool sink = command == COMMAND_MOVE_SINK_INPUT;
+
+ if (message_get(m,
+ TAG_U32, &index,
+ TAG_U32, &index_device,
+ TAG_STRING, &name_device,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ if ((index_device == SPA_ID_INVALID && name_device == NULL) ||
+ (index_device != SPA_ID_INVALID && name_device != NULL))
+ return -EINVAL;
+
+ pw_log_info("[%s] %s tag:%u index:%u device:%d name:%s", client->name,
+ commands[command].name, tag, index, index_device, name_device);
+
+ spa_zero(sel);
+ sel.index = index;
+ sel.type = sink ? pw_manager_object_is_sink_input: pw_manager_object_is_source_output;
+
+ o = select_object(manager, &sel);
+ if (o == NULL)
+ return -ENOENT;
+
+ info = o->info;
+ if (info == NULL || info->props == NULL)
+ return -EINVAL;
+ if (spa_atob(spa_dict_lookup(info->props, PW_KEY_NODE_DONT_RECONNECT)))
+ return -EINVAL;
+
+ if ((dev = find_device(client, index_device, name_device, sink, NULL)) == NULL)
+ return -ENOENT;
+
+ /*
+ * The client metadata is not necessarily yet updated after SET_DEFAULT command,
+ * so use the temporary values if they are still set.
+ */
+ name = sink ? client->temporary_default_sink : client->temporary_default_source;
+ dev_default = find_device(client, SPA_ID_INVALID, name, sink, NULL);
+
+ if (dev == dev_default) {
+ /*
+ * When moving streams to a node that is equal to the default,
+ * Pulseaudio understands this to mean '... and unset preferred sink/source',
+ * forgetting target.node. Follow that behavior here.
+ */
+ target_id = -1;
+ target_serial = -1;
+ } else {
+ target_id = dev->id;
+ target_serial = dev->serial;
+ }
+
+ if ((res = pw_manager_set_metadata(manager, client->metadata_default,
+ o->id,
+ METADATA_TARGET_NODE,
+ SPA_TYPE_INFO_BASE"Id", "%d", target_id)) < 0)
+ return res;
+
+ if ((res = pw_manager_set_metadata(manager, client->metadata_default,
+ o->id,
+ METADATA_TARGET_OBJECT,
+ SPA_TYPE_INFO_BASE"Id", "%"PRIi64, target_serial)) < 0)
+ return res;
+
+ name = spa_dict_lookup(info->props, PW_KEY_NODE_NAME);
+ pw_log_debug("[%s] %s done tag:%u index:%u name:%s target:%d target-serial:%"PRIi64, client->name,
+ commands[command].name, tag, index, name ? name : "<null>",
+ target_id, target_serial);
+
+ /* We will temporarily claim the stream was already moved */
+ set_temporary_move_target(client, o, dev->index);
+ send_object_event(client, o, SUBSCRIPTION_EVENT_CHANGE);
+
+ return reply_simple_ack(client, tag);
+}
+
+static int do_kill(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct pw_manager *manager = client->manager;
+ struct pw_manager_object *o;
+ uint32_t index;
+ struct selector sel;
+
+ if (message_get(m,
+ TAG_U32, &index,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u index:%u", client->name,
+ commands[command].name, tag, index);
+
+ spa_zero(sel);
+ sel.index = index;
+ switch (command) {
+ case COMMAND_KILL_CLIENT:
+ sel.type = pw_manager_object_is_client;
+ break;
+ case COMMAND_KILL_SINK_INPUT:
+ sel.type = pw_manager_object_is_sink_input;
+ break;
+ case COMMAND_KILL_SOURCE_OUTPUT:
+ sel.type = pw_manager_object_is_source_output;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if ((o = select_object(manager, &sel)) == NULL)
+ return -ENOENT;
+
+ pw_registry_destroy(manager->registry, o->id);
+
+ return reply_simple_ack(client, tag);
+}
+
+static void handle_module_loaded(struct module *module, struct client *client, uint32_t tag, int result)
+{
+ const char *client_name = client != NULL ? client->name : "?";
+ struct impl *impl = module->impl;
+
+ spa_assert(!SPA_RESULT_IS_ASYNC(result));
+
+ if (SPA_RESULT_IS_OK(result)) {
+ pw_log_info("[%s] loaded module index:%u name:%s tag:%d",
+ client_name, module->index, module->info->name, tag);
+
+ module->loaded = true;
+
+ broadcast_subscribe_event(impl,
+ SUBSCRIPTION_MASK_MODULE,
+ SUBSCRIPTION_EVENT_NEW | SUBSCRIPTION_EVENT_MODULE,
+ module->index);
+
+ if (client != NULL) {
+ struct message *reply = reply_new(client, tag);
+
+ message_put(reply,
+ TAG_U32, module->index,
+ TAG_INVALID);
+ client_queue_message(client, reply);
+ }
+ }
+ else {
+ pw_log_warn("%p: [%s] failed to load module index:%u name:%s tag:%d result:%d (%s)",
+ impl, client_name,
+ module->index, module->info->name, tag,
+ result, spa_strerror(result));
+
+ module_schedule_unload(module);
+
+ if (client != NULL)
+ reply_error(client, COMMAND_LOAD_MODULE, tag, result);
+ }
+}
+
+struct pending_module {
+ struct client *client;
+ struct spa_hook client_listener;
+
+ struct module *module;
+ struct spa_hook module_listener;
+
+ struct spa_hook manager_listener;
+
+ uint32_t tag;
+
+ int result;
+ bool wait_sync;
+};
+
+static void finish_pending_module(struct pending_module *pm)
+{
+ spa_hook_remove(&pm->module_listener);
+
+ if (pm->client != NULL) {
+ spa_hook_remove(&pm->client_listener);
+ spa_hook_remove(&pm->manager_listener);
+ }
+
+ handle_module_loaded(pm->module, pm->client, pm->tag, pm->result);
+ free(pm);
+}
+
+static void on_load_module_manager_sync(void *data)
+{
+ struct pending_module *pm = data;
+
+ pw_log_debug("pending module %p: manager sync wait_sync:%d tag:%d",
+ pm, pm->wait_sync, pm->tag);
+
+ if (!pm->wait_sync)
+ return;
+
+ finish_pending_module(pm);
+}
+
+static void on_module_loaded(void *data, int result)
+{
+ struct pending_module *pm = data;
+
+ pw_log_debug("pending module %p: loaded, result:%d tag:%d",
+ pm, result, pm->tag);
+
+ pm->result = result;
+
+ /*
+ * Do manager sync first: the module may have its own core, so
+ * although things are completed on the server, our client
+ * might not yet see them.
+ */
+
+ if (pm->client == NULL) {
+ finish_pending_module(pm);
+ } else {
+ pw_log_debug("pending module %p: wait manager sync tag:%d", pm, pm->tag);
+ pm->wait_sync = true;
+ pw_manager_sync(pm->client->manager);
+ }
+}
+
+static void on_module_destroy(void *data)
+{
+ struct pending_module *pm = data;
+
+ pw_log_debug("pending module %p: destroyed, tag:%d",
+ pm, pm->tag);
+
+ pm->result = -ECANCELED;
+ finish_pending_module(pm);
+}
+
+static void on_client_disconnect(void *data)
+{
+ struct pending_module *pm = data;
+
+ pw_log_debug("pending module %p: client disconnect tag:%d", pm, pm->tag);
+
+ spa_hook_remove(&pm->client_listener);
+ spa_hook_remove(&pm->manager_listener);
+ pm->client = NULL;
+
+ if (pm->wait_sync)
+ finish_pending_module(pm);
+}
+
+static void on_load_module_manager_disconnect(void *data)
+{
+ on_client_disconnect(data);
+}
+
+static int do_load_module(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ static const struct module_events module_events = {
+ VERSION_MODULE_EVENTS,
+ .loaded = on_module_loaded,
+ .destroy = on_module_destroy,
+ };
+ static const struct client_events client_events = {
+ VERSION_CLIENT_EVENTS,
+ .disconnect = on_client_disconnect,
+ };
+ static const struct pw_manager_events manager_events = {
+ PW_VERSION_MANAGER_EVENTS,
+ .disconnect = on_load_module_manager_disconnect,
+ .sync = on_load_module_manager_sync,
+ };
+
+ struct impl *impl = client->impl;
+ const char *name, *argument;
+ struct module *module;
+ struct pending_module *pm;
+ int r;
+
+ if (message_get(m,
+ TAG_STRING, &name,
+ TAG_STRING, &argument,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s name:%s argument:%s",
+ client->name, commands[command].name, name, argument);
+
+ module = module_create(impl, name, argument);
+ if (module == NULL)
+ return -errno;
+
+ pm = calloc(1, sizeof(*pm));
+ if (pm == NULL)
+ return -errno;
+
+ pm->tag = tag;
+ pm->client = client;
+ pm->module = module;
+
+ pw_log_debug("pending module %p: start tag:%d", pm, tag);
+
+ r = module_load(module);
+
+ module_add_listener(module, &pm->module_listener, &module_events, pm);
+ client_add_listener(client, &pm->client_listener, &client_events, pm);
+ pw_manager_add_listener(client->manager, &pm->manager_listener, &manager_events, pm);
+
+ if (!SPA_RESULT_IS_ASYNC(r))
+ on_module_loaded(pm, r);
+
+ /*
+ * return 0 to prevent `handle_packet()` from sending a reply
+ * because we want `handle_module_loaded()` to send the reply
+ */
+ return 0;
+}
+
+static int do_unload_module(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ struct module *module;
+ uint32_t module_index;
+
+ if (message_get(m,
+ TAG_U32, &module_index,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u index:%u", client->name,
+ commands[command].name, tag, module_index);
+
+ if (module_index == SPA_ID_INVALID)
+ return -EINVAL;
+ if ((module_index & MODULE_FLAG) == 0)
+ return -EPERM;
+
+ module = pw_map_lookup(&impl->modules, module_index & MODULE_INDEX_MASK);
+ if (module == NULL)
+ return -ENOENT;
+
+ module_unload(module);
+
+ return operation_new(client, tag);
+}
+
+static int do_send_object_message(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ struct impl *impl = client->impl;
+ struct pw_manager *manager = client->manager;
+ const char *object_path = NULL;
+ const char *message = NULL;
+ const char *params = NULL;
+ char *response = NULL;
+ char *path = NULL;
+ struct message *reply;
+ struct pw_manager_object *o;
+ int len = 0;
+ int res;
+
+ if (message_get(m,
+ TAG_STRING, &object_path,
+ TAG_STRING, &message,
+ TAG_STRING, &params,
+ TAG_INVALID) < 0)
+ return -EPROTO;
+
+ pw_log_info("[%s] %s tag:%u object_path:'%s' message:'%s' params:'%s'",
+ client->name, commands[command].name, tag, object_path,
+ message, params ? params : "<null>");
+
+ if (object_path == NULL || message == NULL)
+ return -EINVAL;
+
+ len = strlen(object_path);
+ if (len > 0 && object_path[len - 1] == '/')
+ --len;
+ path = strndup(object_path, len);
+ if (path == NULL)
+ return -ENOMEM;
+
+ res = -ENOENT;
+
+ spa_list_for_each(o, &manager->object_list, link) {
+ if (o->message_object_path && spa_streq(o->message_object_path, path)) {
+ if (o->message_handler)
+ res = o->message_handler(manager, o, message, params, &response);
+ else
+ res = -ENOSYS;
+ break;
+ }
+ }
+
+ free(path);
+ if (res < 0)
+ return res;
+
+ pw_log_debug("%p: object message response:'%s'", impl, response ? response : "<null>");
+
+ reply = reply_new(client, tag);
+ message_put(reply, TAG_STRING, response, TAG_INVALID);
+ free(response);
+ return client_queue_message(client, reply);
+}
+
+static int do_error_access(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ return -EACCES;
+}
+
+static SPA_UNUSED int do_error_not_implemented(struct client *client, uint32_t command, uint32_t tag, struct message *m)
+{
+ return -ENOSYS;
+}
+
+#define COMMAND(name, ...) [COMMAND_ ## name] = { #name, __VA_ARGS__ }
+const struct command commands[COMMAND_MAX] =
+{
+ COMMAND(ERROR),
+ COMMAND(TIMEOUT), /* pseudo command */
+ COMMAND(REPLY),
+
+ /* CLIENT->SERVER */
+ COMMAND(CREATE_PLAYBACK_STREAM, do_create_playback_stream),
+ COMMAND(DELETE_PLAYBACK_STREAM, do_delete_stream),
+ COMMAND(CREATE_RECORD_STREAM, do_create_record_stream),
+ COMMAND(DELETE_RECORD_STREAM, do_delete_stream),
+ COMMAND(EXIT, do_error_access),
+ COMMAND(AUTH, do_command_auth, COMMAND_ACCESS_WITHOUT_AUTH | COMMAND_ACCESS_WITHOUT_MANAGER),
+ COMMAND(SET_CLIENT_NAME, do_set_client_name, COMMAND_ACCESS_WITHOUT_MANAGER),
+ COMMAND(LOOKUP_SINK, do_lookup),
+ COMMAND(LOOKUP_SOURCE, do_lookup),
+ COMMAND(DRAIN_PLAYBACK_STREAM, do_drain_stream),
+ COMMAND(STAT, do_stat, COMMAND_ACCESS_WITHOUT_MANAGER),
+ COMMAND(GET_PLAYBACK_LATENCY, do_get_playback_latency),
+ COMMAND(CREATE_UPLOAD_STREAM, do_create_upload_stream),
+ COMMAND(DELETE_UPLOAD_STREAM, do_delete_stream),
+ COMMAND(FINISH_UPLOAD_STREAM, do_finish_upload_stream),
+ COMMAND(PLAY_SAMPLE, do_play_sample),
+ COMMAND(REMOVE_SAMPLE, do_remove_sample),
+
+ COMMAND(GET_SERVER_INFO, do_get_server_info, COMMAND_ACCESS_WITHOUT_MANAGER),
+ COMMAND(GET_SINK_INFO, do_get_info),
+ COMMAND(GET_SOURCE_INFO, do_get_info),
+ COMMAND(GET_MODULE_INFO, do_get_info),
+ COMMAND(GET_CLIENT_INFO, do_get_info),
+ COMMAND(GET_SINK_INPUT_INFO, do_get_info),
+ COMMAND(GET_SOURCE_OUTPUT_INFO, do_get_info),
+ COMMAND(GET_SAMPLE_INFO, do_get_sample_info),
+ COMMAND(GET_CARD_INFO, do_get_info),
+ COMMAND(SUBSCRIBE, do_subscribe),
+
+ COMMAND(GET_SINK_INFO_LIST, do_get_info_list),
+ COMMAND(GET_SOURCE_INFO_LIST, do_get_info_list),
+ COMMAND(GET_MODULE_INFO_LIST, do_get_info_list),
+ COMMAND(GET_CLIENT_INFO_LIST, do_get_info_list),
+ COMMAND(GET_SINK_INPUT_INFO_LIST, do_get_info_list),
+ COMMAND(GET_SOURCE_OUTPUT_INFO_LIST, do_get_info_list),
+ COMMAND(GET_SAMPLE_INFO_LIST, do_get_sample_info_list),
+ COMMAND(GET_CARD_INFO_LIST, do_get_info_list),
+
+ COMMAND(SET_SINK_VOLUME, do_set_volume),
+ COMMAND(SET_SINK_INPUT_VOLUME, do_set_stream_volume),
+ COMMAND(SET_SOURCE_VOLUME, do_set_volume),
+
+ COMMAND(SET_SINK_MUTE, do_set_mute),
+ COMMAND(SET_SOURCE_MUTE, do_set_mute),
+
+ COMMAND(CORK_PLAYBACK_STREAM, do_cork_stream),
+ COMMAND(FLUSH_PLAYBACK_STREAM, do_flush_trigger_prebuf_stream),
+ COMMAND(TRIGGER_PLAYBACK_STREAM, do_flush_trigger_prebuf_stream),
+ COMMAND(PREBUF_PLAYBACK_STREAM, do_flush_trigger_prebuf_stream),
+
+ COMMAND(SET_DEFAULT_SINK, do_set_default),
+ COMMAND(SET_DEFAULT_SOURCE, do_set_default),
+
+ COMMAND(SET_PLAYBACK_STREAM_NAME, do_set_stream_name),
+ COMMAND(SET_RECORD_STREAM_NAME, do_set_stream_name),
+
+ COMMAND(KILL_CLIENT, do_kill),
+ COMMAND(KILL_SINK_INPUT, do_kill),
+ COMMAND(KILL_SOURCE_OUTPUT, do_kill),
+
+ COMMAND(LOAD_MODULE, do_load_module),
+ COMMAND(UNLOAD_MODULE, do_unload_module),
+
+ /* Obsolete */
+ COMMAND(ADD_AUTOLOAD___OBSOLETE, do_error_access),
+ COMMAND(REMOVE_AUTOLOAD___OBSOLETE, do_error_access),
+ COMMAND(GET_AUTOLOAD_INFO___OBSOLETE, do_error_access),
+ COMMAND(GET_AUTOLOAD_INFO_LIST___OBSOLETE, do_error_access),
+
+ COMMAND(GET_RECORD_LATENCY, do_get_record_latency),
+ COMMAND(CORK_RECORD_STREAM, do_cork_stream),
+ COMMAND(FLUSH_RECORD_STREAM, do_flush_trigger_prebuf_stream),
+
+ /* SERVER->CLIENT */
+ COMMAND(REQUEST),
+ COMMAND(OVERFLOW),
+ COMMAND(UNDERFLOW),
+ COMMAND(PLAYBACK_STREAM_KILLED),
+ COMMAND(RECORD_STREAM_KILLED),
+ COMMAND(SUBSCRIBE_EVENT),
+
+ /* A few more client->server commands */
+
+ /* Supported since protocol v10 (0.9.5) */
+ COMMAND(MOVE_SINK_INPUT, do_move_stream),
+ COMMAND(MOVE_SOURCE_OUTPUT, do_move_stream),
+
+ /* Supported since protocol v11 (0.9.7) */
+ COMMAND(SET_SINK_INPUT_MUTE, do_set_stream_mute),
+
+ COMMAND(SUSPEND_SINK, do_suspend),
+ COMMAND(SUSPEND_SOURCE, do_suspend),
+
+ /* Supported since protocol v12 (0.9.8) */
+ COMMAND(SET_PLAYBACK_STREAM_BUFFER_ATTR, do_set_stream_buffer_attr),
+ COMMAND(SET_RECORD_STREAM_BUFFER_ATTR, do_set_stream_buffer_attr),
+
+ COMMAND(UPDATE_PLAYBACK_STREAM_SAMPLE_RATE, do_update_stream_sample_rate),
+ COMMAND(UPDATE_RECORD_STREAM_SAMPLE_RATE, do_update_stream_sample_rate),
+
+ /* SERVER->CLIENT */
+ COMMAND(PLAYBACK_STREAM_SUSPENDED),
+ COMMAND(RECORD_STREAM_SUSPENDED),
+ COMMAND(PLAYBACK_STREAM_MOVED),
+ COMMAND(RECORD_STREAM_MOVED),
+
+ /* Supported since protocol v13 (0.9.11) */
+ COMMAND(UPDATE_RECORD_STREAM_PROPLIST, do_update_proplist),
+ COMMAND(UPDATE_PLAYBACK_STREAM_PROPLIST, do_update_proplist),
+ COMMAND(UPDATE_CLIENT_PROPLIST, do_update_proplist),
+
+ COMMAND(REMOVE_RECORD_STREAM_PROPLIST, do_remove_proplist),
+ COMMAND(REMOVE_PLAYBACK_STREAM_PROPLIST, do_remove_proplist),
+ COMMAND(REMOVE_CLIENT_PROPLIST, do_remove_proplist),
+
+ /* SERVER->CLIENT */
+ COMMAND(STARTED),
+
+ /* Supported since protocol v14 (0.9.12) */
+ COMMAND(EXTENSION, do_extension),
+ /* Supported since protocol v15 (0.9.15) */
+ COMMAND(SET_CARD_PROFILE, do_set_profile),
+
+ /* SERVER->CLIENT */
+ COMMAND(CLIENT_EVENT),
+ COMMAND(PLAYBACK_STREAM_EVENT),
+ COMMAND(RECORD_STREAM_EVENT),
+
+ /* SERVER->CLIENT */
+ COMMAND(PLAYBACK_BUFFER_ATTR_CHANGED),
+ COMMAND(RECORD_BUFFER_ATTR_CHANGED),
+
+ /* Supported since protocol v16 (0.9.16) */
+ COMMAND(SET_SINK_PORT, do_set_port),
+ COMMAND(SET_SOURCE_PORT, do_set_port),
+
+ /* Supported since protocol v22 (1.0) */
+ COMMAND(SET_SOURCE_OUTPUT_VOLUME, do_set_stream_volume),
+ COMMAND(SET_SOURCE_OUTPUT_MUTE, do_set_stream_mute),
+
+ /* Supported since protocol v27 (3.0) */
+ COMMAND(SET_PORT_LATENCY_OFFSET, do_set_port_latency_offset),
+
+ /* Supported since protocol v30 (6.0) */
+ /* BOTH DIRECTIONS */
+ COMMAND(ENABLE_SRBCHANNEL, do_error_access),
+ COMMAND(DISABLE_SRBCHANNEL, do_error_access),
+
+ /* Supported since protocol v31 (9.0)
+ * BOTH DIRECTIONS */
+ COMMAND(REGISTER_MEMFD_SHMID, do_error_access),
+
+ /* Supported since protocol v35 (15.0) */
+ COMMAND(SEND_OBJECT_MESSAGE, do_send_object_message),
+};
+#undef COMMAND
+
+static int impl_free_sample(void *item, void *data)
+{
+ struct sample *s = item;
+
+ spa_assert(s->ref == 1);
+ sample_unref(s);
+
+ return 0;
+}
+
+static int impl_unload_module(void *item, void *data)
+{
+ struct module *m = item;
+ module_unload(m);
+ return 0;
+}
+
+static void impl_clear(struct impl *impl)
+{
+ struct message *msg;
+ struct server *s;
+ struct client *c;
+
+ pw_map_for_each(&impl->modules, impl_unload_module, impl);
+ pw_map_clear(&impl->modules);
+
+ spa_list_consume(s, &impl->servers, link)
+ server_free(s);
+
+ spa_list_consume(c, &impl->cleanup_clients, link)
+ client_free(c);
+
+ spa_list_consume(msg, &impl->free_messages, link)
+ message_free(msg, true, true);
+
+ pw_map_for_each(&impl->samples, impl_free_sample, impl);
+ pw_map_clear(&impl->samples);
+
+ spa_hook_list_clean(&impl->hooks);
+
+#ifdef HAVE_DBUS
+ if (impl->dbus_name) {
+ dbus_release_name(impl->dbus_name);
+ impl->dbus_name = NULL;
+ }
+#endif
+
+ if (impl->context) {
+ spa_hook_remove(&impl->context_listener);
+ impl->context = NULL;
+ }
+
+ pw_properties_free(impl->props);
+ impl->props = NULL;
+}
+
+static void impl_free(struct impl *impl)
+{
+ impl_clear(impl);
+ free(impl);
+}
+
+static void context_destroy(void *data)
+{
+ impl_clear(data);
+}
+
+static const struct pw_context_events context_events = {
+ PW_VERSION_CONTEXT_EVENTS,
+ .destroy = context_destroy,
+};
+
+static int parse_frac(struct pw_properties *props, const char *key, const char *def,
+ struct spa_fraction *res)
+{
+ const char *str;
+ if (props == NULL ||
+ (str = pw_properties_get(props, key)) == NULL)
+ str = def;
+ if (sscanf(str, "%u/%u", &res->num, &res->denom) != 2 || res->denom == 0) {
+ pw_log_warn(": invalid fraction %s, default to %s", str, def);
+ sscanf(def, "%u/%u", &res->num, &res->denom);
+ }
+ pw_log_info(": defaults: %s = %u/%u", key, res->num, res->denom);
+ return 0;
+}
+
+static int parse_position(struct pw_properties *props, const char *key, const char *def,
+ struct channel_map *res)
+{
+ const char *str;
+ struct spa_json it[2];
+ char v[256];
+
+ if (props == NULL ||
+ (str = pw_properties_get(props, key)) == NULL)
+ str = def;
+
+ 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));
+
+ res->channels = 0;
+ while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 &&
+ res->channels < SPA_AUDIO_MAX_CHANNELS) {
+ res->map[res->channels++] = channel_name2id(v);
+ }
+ pw_log_info(": defaults: %s = %s", key, str);
+ return 0;
+}
+static int parse_format(struct pw_properties *props, const char *key, const char *def,
+ struct sample_spec *res)
+{
+ const char *str;
+ if (props == NULL ||
+ (str = pw_properties_get(props, key)) == NULL)
+ str = def;
+ res->format = format_name2id(str);
+ if (res->format == SPA_AUDIO_FORMAT_UNKNOWN) {
+ pw_log_warn(": unknown format %s, default to %s", str, def);
+ res->format = format_name2id(def);
+ }
+ pw_log_info(": defaults: %s = %s", key, format_id2name(res->format));
+ return 0;
+}
+static int parse_uint32(struct pw_properties *props, const char *key, const char *def,
+ uint32_t *res)
+{
+ const char *str;
+ if (props == NULL ||
+ (str = pw_properties_get(props, key)) == NULL)
+ str = def;
+ if (!spa_atou32(str, res, 0)) {
+ pw_log_warn(": invalid uint32_t %s, default to %s", str, def);
+ spa_atou32(def, res, 0);
+ }
+ pw_log_info(": defaults: %s = %u", key, *res);
+ return 0;
+}
+
+static void load_defaults(struct defs *def, struct pw_properties *props)
+{
+ parse_frac(props, "pulse.min.req", DEFAULT_MIN_REQ, &def->min_req);
+ parse_frac(props, "pulse.default.req", DEFAULT_DEFAULT_REQ, &def->default_req);
+ parse_frac(props, "pulse.min.frag", DEFAULT_MIN_FRAG, &def->min_frag);
+ parse_frac(props, "pulse.default.frag", DEFAULT_DEFAULT_FRAG, &def->default_frag);
+ parse_frac(props, "pulse.default.tlength", DEFAULT_DEFAULT_TLENGTH, &def->default_tlength);
+ parse_frac(props, "pulse.min.quantum", DEFAULT_MIN_QUANTUM, &def->min_quantum);
+ parse_format(props, "pulse.default.format", DEFAULT_FORMAT, &def->sample_spec);
+ parse_position(props, "pulse.default.position", DEFAULT_POSITION, &def->channel_map);
+ parse_uint32(props, "pulse.idle.timeout", DEFAULT_IDLE_TIMEOUT, &def->idle_timeout);
+ def->sample_spec.channels = def->channel_map.channels;
+ def->quantum_limit = 8192;
+}
+
+struct pw_protocol_pulse *pw_protocol_pulse_new(struct pw_context *context,
+ struct pw_properties *props, size_t user_data_size)
+{
+ const struct spa_support *support;
+ struct spa_cpu *cpu;
+ uint32_t n_support;
+ struct impl *impl;
+ const char *str;
+ int res = 0;
+
+ impl = calloc(1, sizeof(*impl) + user_data_size);
+ if (impl == NULL)
+ goto error_exit;
+
+ if (props == NULL)
+ props = pw_properties_new(NULL, NULL);
+ if (props == NULL)
+ goto error_free;
+
+ support = pw_context_get_support(context, &n_support);
+ cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU);
+
+ pw_context_conf_update_props(context, "pulse.properties", props);
+
+ if ((str = pw_properties_get(props, "vm.overrides")) != NULL) {
+ if (cpu != NULL && spa_cpu_get_vm_type(cpu) != SPA_CPU_VM_NONE)
+ pw_properties_update_string(props, str, strlen(str));
+ pw_properties_set(props, "vm.overrides", NULL);
+ }
+
+ load_defaults(&impl->defs, props);
+
+ debug_messages = pw_log_topic_enabled(SPA_LOG_LEVEL_INFO, pulse_conn);
+
+ impl->context = context;
+ impl->loop = pw_context_get_main_loop(context);
+ impl->props = props;
+
+ impl->work_queue = pw_context_get_work_queue(context);
+
+ spa_hook_list_init(&impl->hooks);
+ spa_list_init(&impl->servers);
+ impl->rate_limit.interval = 2 * SPA_NSEC_PER_SEC;
+ impl->rate_limit.burst = 1;
+ pw_map_init(&impl->samples, 16, 16);
+ pw_map_init(&impl->modules, 16, 16);
+ spa_list_init(&impl->cleanup_clients);
+ spa_list_init(&impl->free_messages);
+
+ str = pw_properties_get(props, "server.address");
+ if (str == NULL) {
+ pw_properties_setf(props, "server.address",
+ "[ \"%s-%s\" ]",
+ PW_PROTOCOL_PULSE_DEFAULT_SERVER,
+ get_server_name(context));
+ str = pw_properties_get(props, "server.address");
+ }
+
+ if (str == NULL)
+ goto error_free;
+
+ if ((res = servers_create_and_start(impl, str, NULL)) < 0) {
+ pw_log_error("%p: no servers could be started: %s",
+ impl, spa_strerror(res));
+ goto error_free;
+ }
+
+ if ((res = create_pid_file()) < 0) {
+ pw_log_warn("%p: can't create pid file: %s",
+ impl, spa_strerror(res));
+ }
+ pw_context_add_listener(context, &impl->context_listener,
+ &context_events, impl);
+
+#ifdef HAVE_DBUS
+ impl->dbus_name = dbus_request_name(context, "org.pulseaudio.Server");
+#endif
+ cmd_run(impl);
+
+ return (struct pw_protocol_pulse *) impl;
+
+error_free:
+ free(impl);
+
+error_exit:
+ pw_properties_free(props);
+
+ if (res < 0)
+ errno = -res;
+
+ return NULL;
+}
+
+void impl_add_listener(struct impl *impl,
+ struct spa_hook *listener,
+ const struct impl_events *events, void *data)
+{
+ spa_hook_list_append(&impl->hooks, listener, events, data);
+}
+
+void *pw_protocol_pulse_get_user_data(struct pw_protocol_pulse *pulse)
+{
+ return SPA_PTROFF(pulse, sizeof(struct impl), void);
+}
+
+void pw_protocol_pulse_destroy(struct pw_protocol_pulse *pulse)
+{
+ struct impl *impl = (struct impl*)pulse;
+ impl_free(impl);
+}
diff --git a/src/modules/module-protocol-pulse/pulse-server.h b/src/modules/module-protocol-pulse/pulse-server.h
new file mode 100644
index 0000000..3e8e6ee
--- /dev/null
+++ b/src/modules/module-protocol-pulse/pulse-server.h
@@ -0,0 +1,56 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_PROTOCOL_PULSE_H
+#define PIPEWIRE_PROTOCOL_PULSE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+#define PW_PROTOCOL_PULSE_DEFAULT_PORT 4713
+#define PW_PROTOCOL_PULSE_DEFAULT_SOCKET "native"
+
+#define PW_PROTOCOL_PULSE_DEFAULT_SERVER "unix:"PW_PROTOCOL_PULSE_DEFAULT_SOCKET
+
+#define PW_PROTOCOL_PULSE_USAGE "[ server.address=(tcp:[<ip>:]<port>|unix:<path>)[,...] ] " \
+
+struct pw_context;
+struct pw_properties;
+struct pw_protocol_pulse;
+struct pw_protocol_pulse_server;
+
+struct pw_protocol_pulse *pw_protocol_pulse_new(struct pw_context *context,
+ struct pw_properties *props, size_t user_data_size);
+void *pw_protocol_pulse_get_user_data(struct pw_protocol_pulse *pulse);
+void pw_protocol_pulse_destroy(struct pw_protocol_pulse *pulse);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_PROTOCOL_PULSE_H */
diff --git a/src/modules/module-protocol-pulse/quirks.c b/src/modules/module-protocol-pulse/quirks.c
new file mode 100644
index 0000000..eb22438
--- /dev/null
+++ b/src/modules/module-protocol-pulse/quirks.c
@@ -0,0 +1,75 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <regex.h>
+
+#include <spa/utils/json.h>
+
+#include <pipewire/properties.h>
+
+#include "log.h"
+#include "quirks.h"
+#include "internal.h"
+
+static uint64_t parse_quirks(const char *str)
+{
+ static const struct { const char *key; uint64_t value; } quirk_keys[] = {
+ { "force-s16-info", QUIRK_FORCE_S16_FORMAT },
+ { "remove-capture-dont-move", QUIRK_REMOVE_CAPTURE_DONT_MOVE },
+ };
+ SPA_FOR_EACH_ELEMENT_VAR(quirk_keys, i) {
+ if (spa_streq(str, i->key))
+ return i->value;
+ }
+ return 0;
+}
+
+static int apply_match(void *data, const char *location, const char *action,
+ const char *val, size_t len)
+{
+ struct client *client = data;
+
+ if (spa_streq(action, "update-props")) {
+ pw_properties_update_string(client->props, val, len);
+ } else if (spa_streq(action, "quirks")) {
+ struct spa_json quirks = SPA_JSON_INIT(val, len), it[1];
+ uint64_t quirks_cur = 0;
+ char v[128];
+
+ if (spa_json_enter_array(&quirks, &it[0]) > 0) {
+ while (spa_json_get_string(&it[0], v, sizeof(v)) > 0)
+ quirks_cur |= parse_quirks(v);
+ }
+ client->quirks = quirks_cur;
+ }
+ return 0;
+}
+
+int client_update_quirks(struct client *client)
+{
+ struct impl *impl = client->impl;
+ struct pw_context *context = impl->context;
+ return pw_context_conf_section_match_rules(context, "pulse.rules",
+ &client->props->dict, apply_match, client);
+}
diff --git a/src/modules/module-protocol-pulse/quirks.h b/src/modules/module-protocol-pulse/quirks.h
new file mode 100644
index 0000000..0229089
--- /dev/null
+++ b/src/modules/module-protocol-pulse/quirks.h
@@ -0,0 +1,36 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSER_SERVER_QUIRKS_H
+#define PULSER_SERVER_QUIRKS_H
+
+#include "client.h"
+
+#define QUIRK_FORCE_S16_FORMAT (1ull<<0) /** forces S16 sample format in sink and source
+ * info */
+#define QUIRK_REMOVE_CAPTURE_DONT_MOVE (1ull<<1) /** removes the capture stream DONT_MOVE flag */
+
+int client_update_quirks(struct client *client);
+
+#endif /* PULSER_SERVER_QUIRKS_H */
diff --git a/src/modules/module-protocol-pulse/remap.c b/src/modules/module-protocol-pulse/remap.c
new file mode 100644
index 0000000..1442dee
--- /dev/null
+++ b/src/modules/module-protocol-pulse/remap.c
@@ -0,0 +1,58 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stddef.h>
+
+#include <pipewire/keys.h>
+
+#include "remap.h"
+
+const struct str_map media_role_map[] = {
+ { "Movie", "video", },
+ { "Music", "music", },
+ { "Game", "game", },
+ { "Notification", "event", },
+ { "Communication", "phone", },
+ { "Movie", "animation", },
+ { "Production", "production", },
+ { "Accessibility", "a11y", },
+ { "Test", "test", },
+ { NULL, NULL },
+};
+
+const struct str_map props_key_map[] = {
+ { PW_KEY_DEVICE_BUS_PATH, "device.bus_path" },
+ { PW_KEY_DEVICE_SYSFS_PATH, "sysfs.path" },
+ { PW_KEY_DEVICE_FORM_FACTOR, "device.form_factor" },
+ { PW_KEY_DEVICE_ICON_NAME, "device.icon_name" },
+ { PW_KEY_DEVICE_INTENDED_ROLES, "device.intended_roles" },
+ { PW_KEY_NODE_DESCRIPTION, "device.description" },
+ { PW_KEY_MEDIA_ICON_NAME, "media.icon_name" },
+ { PW_KEY_APP_ICON_NAME, "application.icon_name" },
+ { PW_KEY_APP_PROCESS_MACHINE_ID, "application.process.machine_id" },
+ { PW_KEY_APP_PROCESS_SESSION_ID, "application.process.session_id" },
+ { PW_KEY_MEDIA_ROLE, "media.role", media_role_map },
+ { "pipe.filename", "device.string" },
+ { NULL, NULL },
+};
diff --git a/src/modules/module-protocol-pulse/remap.h b/src/modules/module-protocol-pulse/remap.h
new file mode 100644
index 0000000..49cbdf2
--- /dev/null
+++ b/src/modules/module-protocol-pulse/remap.h
@@ -0,0 +1,52 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSE_SERVER_REMAP_H
+#define PULSE_SERVER_REMAP_H
+
+#include <stddef.h>
+
+#include <spa/utils/string.h>
+
+struct str_map {
+ const char *pw_str;
+ const char *pa_str;
+ const struct str_map *child;
+};
+
+extern const struct str_map media_role_map[];
+
+extern const struct str_map props_key_map[];
+
+static inline const struct str_map *str_map_find(const struct str_map *map, const char *pw, const char *pa)
+{
+ size_t i;
+ for (i = 0; map[i].pw_str; i++)
+ if ((pw && spa_streq(map[i].pw_str, pw)) ||
+ (pa && spa_streq(map[i].pa_str, pa)))
+ return &map[i];
+ return NULL;
+}
+
+#endif /* PULSE_SERVER_REMAP_H */
diff --git a/src/modules/module-protocol-pulse/reply.c b/src/modules/module-protocol-pulse/reply.c
new file mode 100644
index 0000000..9444e5b
--- /dev/null
+++ b/src/modules/module-protocol-pulse/reply.c
@@ -0,0 +1,84 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdint.h>
+
+#include <spa/utils/result.h>
+#include <pipewire/log.h>
+
+#include "defs.h"
+#include "client.h"
+#include "commands.h"
+#include "message.h"
+#include "log.h"
+
+struct message *reply_new(const struct client *client, uint32_t tag)
+{
+ struct message *reply = message_alloc(client->impl, -1, 0);
+
+ pw_log_debug("client %p: new reply tag:%u", client, tag);
+
+ message_put(reply,
+ TAG_U32, COMMAND_REPLY,
+ TAG_U32, tag,
+ TAG_INVALID);
+
+ return reply;
+}
+
+int reply_error(struct client *client, uint32_t command, uint32_t tag, int res)
+{
+ struct impl *impl = client->impl;
+ struct message *reply;
+ uint32_t error = res_to_err(res);
+ const char *name;
+ enum spa_log_level level;
+
+ if (command < COMMAND_MAX)
+ name = commands[command].name;
+ else
+ name = "invalid";
+
+ switch (res) {
+ case -ENOENT:
+ case -ENOTSUP:
+ level = SPA_LOG_LEVEL_INFO;
+ break;
+ default:
+ level = SPA_LOG_LEVEL_WARN;
+ break;
+ }
+
+ pw_log(level, "client %p [%s]: ERROR command:%d (%s) tag:%u error:%u (%s)",
+ client, client->name, command, name, tag, error, spa_strerror(res));
+
+ reply = message_alloc(impl, -1, 0);
+ message_put(reply,
+ TAG_U32, COMMAND_ERROR,
+ TAG_U32, tag,
+ TAG_U32, error,
+ TAG_INVALID);
+
+ return client_queue_message(client, reply);
+}
diff --git a/src/modules/module-protocol-pulse/reply.h b/src/modules/module-protocol-pulse/reply.h
new file mode 100644
index 0000000..1ca9ad1
--- /dev/null
+++ b/src/modules/module-protocol-pulse/reply.h
@@ -0,0 +1,42 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSE_SERVER_REPLY_H
+#define PULSE_SERVER_REPLY_H
+
+#include <stdint.h>
+
+#include "client.h"
+
+struct message;
+
+struct message *reply_new(const struct client *client, uint32_t tag);
+int reply_error(struct client *client, uint32_t command, uint32_t tag, int res);
+
+static inline int reply_simple_ack(struct client *client, uint32_t tag)
+{
+ return client_queue_message(client, reply_new(client, tag));
+}
+
+#endif /* PULSE_SERVER_REPLY_H */
diff --git a/src/modules/module-protocol-pulse/sample-play.c b/src/modules/module-protocol-pulse/sample-play.c
new file mode 100644
index 0000000..37c68a3
--- /dev/null
+++ b/src/modules/module-protocol-pulse/sample-play.c
@@ -0,0 +1,211 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdint.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <spa/node/io.h>
+#include <spa/param/audio/raw.h>
+#include <spa/pod/builder.h>
+#include <spa/utils/hook.h>
+#include <pipewire/context.h>
+#include <pipewire/core.h>
+#include <pipewire/log.h>
+#include <pipewire/properties.h>
+#include <pipewire/stream.h>
+
+#include "format.h"
+#include "log.h"
+#include "sample.h"
+#include "sample-play.h"
+
+static void sample_play_stream_state_changed(void *data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct sample_play *p = data;
+
+ switch (state) {
+ case PW_STREAM_STATE_UNCONNECTED:
+ case PW_STREAM_STATE_ERROR:
+ sample_play_emit_done(p, -EIO);
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ p->id = pw_stream_get_node_id(p->stream);
+ sample_play_emit_ready(p, p->id);
+ break;
+ default:
+ break;
+ }
+}
+
+static void sample_play_stream_destroy(void *data)
+{
+ struct sample_play *p = data;
+
+ pw_log_info("destroy %s", p->sample->name);
+
+ spa_hook_remove(&p->listener);
+ p->stream = NULL;
+
+ sample_unref(p->sample);
+ p->sample = NULL;
+}
+
+static void sample_play_stream_process(void *data)
+{
+ struct sample_play *p = data;
+ struct sample *s = p->sample;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ uint32_t size;
+ uint8_t *d;
+
+ if (p->offset >= s->length) {
+ pw_stream_flush(p->stream, true);
+ return;
+ }
+
+ size = s->length - p->offset;
+
+ if ((b = pw_stream_dequeue_buffer(p->stream)) == NULL) {
+ pw_log_warn("out of buffers: %m");
+ return;
+ }
+
+ buf = b->buffer;
+ if ((d = buf->datas[0].data) == NULL)
+ return;
+
+ size = SPA_MIN(size, buf->datas[0].maxsize);
+ if (b->requested)
+ size = SPA_MIN(size, b->requested * p->stride);
+
+ memcpy(d, s->buffer + p->offset, size);
+
+ p->offset += size;
+
+ buf->datas[0].chunk->offset = 0;
+ buf->datas[0].chunk->stride = p->stride;
+ buf->datas[0].chunk->size = size;
+
+ pw_stream_queue_buffer(p->stream, b);
+}
+
+static void sample_play_stream_drained(void *data)
+{
+ struct sample_play *p = data;
+
+ sample_play_emit_done(p, 0);
+}
+
+static const struct pw_stream_events sample_play_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .state_changed = sample_play_stream_state_changed,
+ .destroy = sample_play_stream_destroy,
+ .process = sample_play_stream_process,
+ .drained = sample_play_stream_drained,
+};
+
+struct sample_play *sample_play_new(struct pw_core *core,
+ struct sample *sample, struct pw_properties *props,
+ size_t user_data_size)
+{
+ struct sample_play *p;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ const struct spa_pod *params[1];
+ uint32_t n_params = 0;
+ int res;
+
+ p = calloc(1, sizeof(*p) + user_data_size);
+ if (p == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+
+ p->context = pw_core_get_context(core);
+ p->main_loop = pw_context_get_main_loop(p->context);
+ spa_hook_list_init(&p->hooks);
+ p->user_data = SPA_PTROFF(p, sizeof(struct sample_play), void);
+
+ pw_properties_update(props, &sample->props->dict);
+
+ p->stream = pw_stream_new(core, sample->name, props);
+ props = NULL;
+ if (p->stream == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+
+ /* safe to increment the reference count here because it will be decreased
+ by the stream's 'destroy' event handler, which will be called
+ (even if `pw_stream_connect()` fails) */
+ p->sample = sample_ref(sample);
+ p->stride = sample_spec_frame_size(&sample->ss);
+
+ pw_stream_add_listener(p->stream,
+ &p->listener,
+ &sample_play_stream_events, p);
+
+ params[n_params++] = format_build_param(&b, SPA_PARAM_EnumFormat,
+ &sample->ss, &sample->map);
+
+ res = pw_stream_connect(p->stream,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params);
+ if (res < 0)
+ goto error_cleanup;
+
+ return p;
+
+error_cleanup:
+ pw_stream_destroy(p->stream);
+error_free:
+ pw_properties_free(props);
+ free(p);
+ errno = -res;
+ return NULL;
+}
+
+void sample_play_destroy(struct sample_play *p)
+{
+ if (p->stream)
+ pw_stream_destroy(p->stream);
+
+ spa_hook_list_clean(&p->hooks);
+
+ free(p);
+}
+
+void sample_play_add_listener(struct sample_play *p, struct spa_hook *listener,
+ const struct sample_play_events *events, void *data)
+{
+ spa_hook_list_append(&p->hooks, listener, events, data);
+}
diff --git a/src/modules/module-protocol-pulse/sample-play.h b/src/modules/module-protocol-pulse/sample-play.h
new file mode 100644
index 0000000..5738935
--- /dev/null
+++ b/src/modules/module-protocol-pulse/sample-play.h
@@ -0,0 +1,76 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSER_SERVER_SAMPLE_PLAY_H
+#define PULSER_SERVER_SAMPLE_PLAY_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <spa/utils/list.h>
+#include <spa/utils/hook.h>
+
+struct sample;
+struct pw_core;
+struct pw_loop;
+struct pw_stream;
+struct pw_context;
+struct pw_properties;
+
+struct sample_play_events {
+#define VERSION_SAMPLE_PLAY_EVENTS 0
+ uint32_t version;
+
+ void (*ready) (void *data, uint32_t id);
+
+ void (*done) (void *data, int err);
+};
+
+#define sample_play_emit_ready(p,i) spa_hook_list_call(&p->hooks, struct sample_play_events, ready, 0, i)
+#define sample_play_emit_done(p,r) spa_hook_list_call(&p->hooks, struct sample_play_events, done, 0, r)
+
+struct sample_play {
+ struct spa_list link;
+ struct sample *sample;
+ struct pw_stream *stream;
+ uint32_t id;
+ struct spa_hook listener;
+ struct pw_context *context;
+ struct pw_loop *main_loop;
+ uint32_t offset;
+ uint32_t stride;
+ struct spa_hook_list hooks;
+ void *user_data;
+};
+
+struct sample_play *sample_play_new(struct pw_core *core,
+ struct sample *sample, struct pw_properties *props,
+ size_t user_data_size);
+
+void sample_play_destroy(struct sample_play *p);
+
+void sample_play_add_listener(struct sample_play *p, struct spa_hook *listener,
+ const struct sample_play_events *events, void *data);
+
+#endif /* PULSER_SERVER_SAMPLE_PLAY_H */
diff --git a/src/modules/module-protocol-pulse/sample.c b/src/modules/module-protocol-pulse/sample.c
new file mode 100644
index 0000000..a2d8de9
--- /dev/null
+++ b/src/modules/module-protocol-pulse/sample.c
@@ -0,0 +1,50 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+
+#include <pipewire/log.h>
+#include <pipewire/map.h>
+#include <pipewire/properties.h>
+
+#include "internal.h"
+#include "log.h"
+#include "sample.h"
+
+void sample_free(struct sample *sample)
+{
+ struct impl * const impl = sample->impl;
+
+ pw_log_info("free sample id:%u name:%s", sample->index, sample->name);
+
+ impl->stat.sample_cache -= sample->length;
+
+ if (sample->index != SPA_ID_INVALID)
+ pw_map_remove(&impl->samples, sample->index);
+
+ pw_properties_free(sample->props);
+
+ free(sample->buffer);
+ free(sample);
+}
diff --git a/src/modules/module-protocol-pulse/sample.h b/src/modules/module-protocol-pulse/sample.h
new file mode 100644
index 0000000..db347eb
--- /dev/null
+++ b/src/modules/module-protocol-pulse/sample.h
@@ -0,0 +1,61 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSE_SERVER_SAMPLE_H
+#define PULSE_SERVER_SAMPLE_H
+
+#include <stdint.h>
+
+#include "format.h"
+
+struct impl;
+struct pw_properties;
+
+struct sample {
+ int ref;
+ uint32_t index;
+ struct impl *impl;
+ const char *name;
+ struct sample_spec ss;
+ struct channel_map map;
+ struct pw_properties *props;
+ uint32_t length;
+ uint8_t *buffer;
+};
+
+void sample_free(struct sample *sample);
+
+static inline struct sample *sample_ref(struct sample *sample)
+{
+ sample->ref++;
+ return sample;
+}
+
+static inline void sample_unref(struct sample *sample)
+{
+ if (--sample->ref == 0)
+ sample_free(sample);
+}
+
+#endif /* PULSE_SERVER_SAMPLE_H */
diff --git a/src/modules/module-protocol-pulse/server.c b/src/modules/module-protocol-pulse/server.c
new file mode 100644
index 0000000..927e253
--- /dev/null
+++ b/src/modules/module-protocol-pulse/server.c
@@ -0,0 +1,1087 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netinet/ip.h>
+#include <unistd.h>
+
+#ifdef HAVE_SYSTEMD
+#include <systemd/sd-daemon.h>
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/json.h>
+#include <spa/utils/result.h>
+#include <pipewire/pipewire.h>
+
+#include "client.h"
+#include "commands.h"
+#include "defs.h"
+#include "internal.h"
+#include "log.h"
+#include "message.h"
+#include "reply.h"
+#include "server.h"
+#include "stream.h"
+#include "utils.h"
+#include "flatpak-utils.h"
+
+#define LISTEN_BACKLOG 32
+#define MAX_CLIENTS 64
+
+static int handle_packet(struct client *client, struct message *msg)
+{
+ uint32_t command, tag;
+ int res = 0;
+
+ if (message_get(msg,
+ TAG_U32, &command,
+ TAG_U32, &tag,
+ TAG_INVALID) < 0) {
+ res = -EPROTO;
+ goto finish;
+ }
+
+ pw_log_debug("client %p: received packet command:%u tag:%u",
+ client, command, tag);
+
+ if (command >= COMMAND_MAX) {
+ res = -EINVAL;
+ goto finish;
+ }
+
+ if (debug_messages) {
+ pw_log_debug("client %p: command:%s", client, commands[command].name);
+ message_dump(SPA_LOG_LEVEL_INFO, msg);
+ }
+
+ const struct command *cmd = &commands[command];
+ if (cmd->run == NULL) {
+ res = -ENOTSUP;
+ goto finish;
+ }
+
+ if (!client->authenticated && !SPA_FLAG_IS_SET(cmd->access, COMMAND_ACCESS_WITHOUT_AUTH)) {
+ res = -EACCES;
+ goto finish;
+ }
+
+ if (client->manager == NULL && !SPA_FLAG_IS_SET(cmd->access, COMMAND_ACCESS_WITHOUT_MANAGER)) {
+ res = -EACCES;
+ goto finish;
+ }
+
+ res = cmd->run(client, command, tag, msg);
+
+finish:
+ message_free(msg, false, false);
+ if (res < 0)
+ reply_error(client, command, tag, res);
+
+ return 0;
+}
+
+static int handle_memblock(struct client *client, struct message *msg)
+{
+ struct stream *stream;
+ uint32_t channel, flags, index;
+ int64_t offset, diff;
+ int32_t filled;
+ int res = 0;
+
+ channel = ntohl(client->desc.channel);
+ offset = (int64_t) (
+ (((uint64_t) ntohl(client->desc.offset_hi)) << 32) |
+ (((uint64_t) ntohl(client->desc.offset_lo))));
+ flags = ntohl(client->desc.flags);
+
+ pw_log_debug("client %p: received memblock channel:%d offset:%" PRIi64 " flags:%08x size:%u",
+ client, channel, offset, flags, msg->length);
+
+ stream = pw_map_lookup(&client->streams, channel);
+ if (stream == NULL || stream->type == STREAM_TYPE_RECORD) {
+ pw_log_info("client %p [%s]: received memblock for unknown channel %d",
+ client, client->name, channel);
+ goto finish;
+ }
+
+ filled = spa_ringbuffer_get_write_index(&stream->ring, &index);
+ pw_log_debug("new block %p %p/%u filled:%d index:%d flags:%02x offset:%" PRIu64,
+ msg, msg->data, msg->length, filled, index, flags, offset);
+
+ switch (flags & FLAG_SEEKMASK) {
+ case SEEK_RELATIVE:
+ diff = offset;
+ break;
+ case SEEK_ABSOLUTE:
+ diff = offset - (int64_t)stream->write_index;
+ break;
+ case SEEK_RELATIVE_ON_READ:
+ case SEEK_RELATIVE_END:
+ diff = offset - (int64_t)filled;
+ break;
+ default:
+ pw_log_warn("client %p [%s]: received memblock frame with invalid seek mode: %" PRIu32,
+ client, client->name, (uint32_t)(flags & FLAG_SEEKMASK));
+ res = -EPROTO;
+ goto finish;
+ }
+
+ index += diff;
+ filled += diff;
+ stream->write_index += diff;
+ if ((flags & FLAG_SEEKMASK) == SEEK_RELATIVE)
+ stream->requested -= diff;
+
+ if (filled < 0) {
+ /* underrun, reported on reader side */
+ } else if (filled + msg->length > stream->attr.maxlength) {
+ /* overrun */
+ stream_send_overflow(stream);
+ }
+
+ /* always write data to ringbuffer, we expect the other side
+ * to recover */
+ spa_ringbuffer_write_data(&stream->ring,
+ stream->buffer, MAXLENGTH,
+ index % MAXLENGTH,
+ msg->data,
+ SPA_MIN(msg->length, MAXLENGTH));
+ index += msg->length;
+ spa_ringbuffer_write_update(&stream->ring, index);
+
+ stream->write_index += msg->length;
+ stream->requested -= msg->length;
+
+ stream_send_request(stream);
+
+ if (stream->is_paused && !stream->corked)
+ stream_set_paused(stream, false, "new data");
+
+finish:
+ message_free(msg, false, false);
+ return res;
+}
+
+static int do_read(struct client *client)
+{
+ struct impl * const impl = client->impl;
+ size_t size;
+ int res = 0;
+ void *data;
+
+ if (client->in_index < sizeof(client->desc)) {
+ data = SPA_PTROFF(&client->desc, client->in_index, void);
+ size = sizeof(client->desc) - client->in_index;
+ } else {
+ uint32_t idx = client->in_index - sizeof(client->desc);
+
+ if (client->message == NULL || client->message->length < idx) {
+ res = -EPROTO;
+ goto exit;
+ }
+
+ data = SPA_PTROFF(client->message->data, idx, void);
+ size = client->message->length - idx;
+ }
+
+ while (true) {
+ ssize_t r = recv(client->source->fd, data, size, MSG_DONTWAIT);
+
+ if (r == 0 && size != 0) {
+ res = -EPIPE;
+ goto exit;
+ } else if (r < 0) {
+ if (errno == EINTR)
+ continue;
+ res = -errno;
+ if (res != -EAGAIN && res != -EWOULDBLOCK &&
+ res != -EPIPE && res != -ECONNRESET)
+ pw_log_warn("recv client:%p res %zd: %m", client, r);
+ goto exit;
+ }
+
+ client->in_index += r;
+ break;
+ }
+
+ if (client->in_index == sizeof(client->desc)) {
+ uint32_t flags, length, channel;
+
+ flags = ntohl(client->desc.flags);
+ if ((flags & FLAG_SHMMASK) != 0) {
+ res = -EPROTO;
+ goto exit;
+ }
+
+ length = ntohl(client->desc.length);
+ if (length > FRAME_SIZE_MAX_ALLOW || length <= 0) {
+ pw_log_warn("client %p: received invalid frame size: %u",
+ client, length);
+ res = -EPROTO;
+ goto exit;
+ }
+
+ channel = ntohl(client->desc.channel);
+ if (channel == (uint32_t) -1) {
+ if (flags != 0) {
+ pw_log_warn("client %p: received packet frame with invalid flags",
+ client);
+ res = -EPROTO;
+ goto exit;
+ }
+ }
+
+ if (client->message)
+ message_free(client->message, false, false);
+
+ client->message = message_alloc(impl, channel, length);
+ } else if (client->message &&
+ client->in_index >= client->message->length + sizeof(client->desc)) {
+ struct message * const msg = client->message;
+
+ client->message = NULL;
+ client->in_index = 0;
+
+ if (msg->channel == (uint32_t)-1)
+ res = handle_packet(client, msg);
+ else
+ res = handle_memblock(client, msg);
+ }
+
+exit:
+ return res;
+}
+
+static void
+on_client_data(void *data, int fd, uint32_t mask)
+{
+ struct client * const client = data;
+ int res;
+
+ client->ref++;
+
+ if (mask & SPA_IO_HUP) {
+ res = -EPIPE;
+ goto error;
+ }
+
+ if (mask & SPA_IO_ERR) {
+ res = -EIO;
+ goto error;
+ }
+
+ if (mask & SPA_IO_IN) {
+ pw_log_trace("client %p: can read", client);
+ while (true) {
+ res = do_read(client);
+ if (res < 0) {
+ if (res != -EAGAIN && res != -EWOULDBLOCK)
+ goto error;
+ break;
+ }
+ }
+ }
+
+ if (mask & SPA_IO_OUT || client->new_msg_since_last_flush) {
+ res = client_flush_messages(client);
+ if (res < 0)
+ goto error;
+ }
+
+done:
+ /* drop the reference that was acquired at the beginning of the function */
+ client_unref(client);
+ return;
+
+error:
+ switch (res) {
+ case -EPIPE:
+ case -ECONNRESET:
+ pw_log_info("server %p: client %p [%s] disconnected",
+ client->server, client, client->name);
+ SPA_FALLTHROUGH;
+ case -EPROTO:
+ /*
+ * drop the server's reference to the client
+ * (if it hasn't been dropped already),
+ * it is guaranteed that this will not call `client_free()`
+ * since at the beginning of this function an extra reference
+ * has been acquired which will keep the client alive
+ */
+ if (client_detach(client))
+ client_unref(client);
+
+ /* then disconnect the client */
+ client_disconnect(client);
+ break;
+ default:
+ pw_log_error("server %p: client %p [%s] error %d (%s)",
+ client->server, client, client->name, res, spa_strerror(res));
+ break;
+ }
+
+ goto done;
+}
+
+static void
+on_connect(void *data, int fd, uint32_t mask)
+{
+ struct server * const server = data;
+ struct impl * const impl = server->impl;
+ struct sockaddr_storage name;
+ socklen_t length;
+ int client_fd, val;
+ struct client *client = NULL;
+ const char *client_access = NULL;
+ pid_t pid;
+
+ length = sizeof(name);
+ client_fd = accept4(fd, (struct sockaddr *) &name, &length, SOCK_CLOEXEC);
+ if (client_fd < 0) {
+ if (errno == EMFILE || errno == ENFILE) {
+ if (server->n_clients > 0) {
+ int m = server->source->mask;
+ SPA_FLAG_CLEAR(m, SPA_IO_IN);
+ pw_loop_update_io(impl->loop, server->source, m);
+ server->wait_clients++;
+ }
+ }
+ goto error;
+ }
+
+ if (server->n_clients >= server->max_clients) {
+ close(client_fd);
+ errno = ECONNREFUSED;
+ goto error;
+ }
+
+ client = client_new(server);
+ if (client == NULL)
+ goto error;
+
+ pw_log_debug("server %p: new client %p fd:%d", server, client, client_fd);
+
+ client->source = pw_loop_add_io(impl->loop,
+ client_fd,
+ SPA_IO_ERR | SPA_IO_HUP | SPA_IO_IN,
+ true, on_client_data, client);
+ if (client->source == NULL)
+ goto error;
+
+ client->props = pw_properties_new(
+ PW_KEY_CLIENT_API, "pipewire-pulse",
+ "config.ext", pw_properties_get(impl->props, "config.ext"),
+ NULL);
+ if (client->props == NULL)
+ goto error;
+
+ pw_properties_setf(client->props,
+ "pulse.server.type", "%s",
+ server->addr.ss_family == AF_UNIX ? "unix" : "tcp");
+
+ client->routes = pw_properties_new(NULL, NULL);
+ if (client->routes == NULL)
+ goto error;
+
+ if (server->client_access[0] != '\0')
+ client_access = server->client_access;
+
+ if (server->addr.ss_family == AF_UNIX) {
+ char *app_id = NULL, *devices = NULL;
+
+#ifdef SO_PRIORITY
+ val = 6;
+ if (setsockopt(client_fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0)
+ pw_log_warn("setsockopt(SO_PRIORITY) failed: %m");
+#endif
+ pid = get_client_pid(client, client_fd);
+ if (pid != 0 && pw_check_flatpak(pid, &app_id, &devices) == 1) {
+ /*
+ * XXX: we should really use Portal client access here
+ *
+ * However, session managers currently support only camera
+ * permissions, and the XDG Portal doesn't have a "Sound Manager"
+ * permission defined. So for now, use access=flatpak, and determine
+ * extra permissions here.
+ *
+ * The application has access to the Pulseaudio socket,
+ * and with real PA it would always then have full sound access.
+ * We'll restrict the full access here behind devices=all;
+ * if the application can access all devices it can then
+ * also sound and camera devices directly, so granting also the
+ * Manager permissions here is reasonable.
+ *
+ * The "Manager" permission in any case is also currently not safe
+ * as the session manager does not check any permission store
+ * for it.
+ */
+ client_access = "flatpak";
+ pw_properties_set(client->props, "pipewire.access.portal.app_id",
+ app_id);
+
+ if (devices && (spa_streq(devices, "all") ||
+ spa_strstartswith(devices, "all;") ||
+ strstr(devices, ";all;")))
+ pw_properties_set(client->props, PW_KEY_MEDIA_CATEGORY, "Manager");
+ else
+ pw_properties_set(client->props, PW_KEY_MEDIA_CATEGORY, NULL);
+ }
+ free(devices);
+ free(app_id);
+ }
+ else if (server->addr.ss_family == AF_INET || server->addr.ss_family == AF_INET6) {
+
+ val = 1;
+ if (setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)) < 0)
+ pw_log_warn("setsockopt(TCP_NODELAY) failed: %m");
+
+ if (server->addr.ss_family == AF_INET) {
+ val = IPTOS_LOWDELAY;
+ if (setsockopt(client_fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) < 0)
+ pw_log_warn("setsockopt(IP_TOS) failed: %m");
+ }
+ if (client_access == NULL)
+ client_access = "restricted";
+ }
+ pw_properties_set(client->props, PW_KEY_CLIENT_ACCESS, client_access);
+
+ return;
+
+error:
+ pw_log_error("server %p: failed to create client: %m", server);
+ if (client)
+ client_free(client);
+}
+
+static int parse_unix_address(const char *address, struct sockaddr_storage *addrs, int len)
+{
+ struct sockaddr_un addr = {0};
+ int res;
+
+ if (address[0] != '/') {
+ char runtime_dir[PATH_MAX];
+
+ if ((res = get_runtime_dir(runtime_dir, sizeof(runtime_dir))) < 0)
+ return res;
+
+ res = snprintf(addr.sun_path, sizeof(addr.sun_path),
+ "%s/%s", runtime_dir, address);
+ }
+ else {
+ res = snprintf(addr.sun_path, sizeof(addr.sun_path),
+ "%s", address);
+ }
+
+ if (res < 0)
+ return -EINVAL;
+
+ if ((size_t) res >= sizeof(addr.sun_path)) {
+ pw_log_warn("'%s...' too long", addr.sun_path);
+ return -ENAMETOOLONG;
+ }
+
+ if (len < 1)
+ return -ENOSPC;
+
+ addr.sun_family = AF_UNIX;
+
+ memcpy(&addrs[0], &addr, sizeof(addr));
+ return 1;
+}
+
+#ifndef SUN_LEN
+#define SUN_LEN(addr_un) \
+ (offsetof(struct sockaddr_un, sun_path) + strlen((addr_un)->sun_path))
+#endif
+
+static bool is_stale_socket(int fd, const struct sockaddr_un *addr_un)
+{
+ if (connect(fd, (const struct sockaddr *) addr_un, SUN_LEN(addr_un)) < 0) {
+ if (errno == ECONNREFUSED)
+ return true;
+ }
+
+ return false;
+}
+
+#ifdef HAVE_SYSTEMD
+static int check_systemd_activation(const char *path)
+{
+ const int n = sd_listen_fds(0);
+
+ for (int i = 0; i < n; i++) {
+ const int fd = SD_LISTEN_FDS_START + i;
+
+ if (sd_is_socket_unix(fd, SOCK_STREAM, 1, path, 0) > 0)
+ return fd;
+ }
+
+ return -1;
+}
+#else
+static inline int check_systemd_activation(SPA_UNUSED const char *path)
+{
+ return -1;
+}
+#endif
+
+static int start_unix_server(struct server *server, const struct sockaddr_storage *addr)
+{
+ const struct sockaddr_un * const addr_un = (const struct sockaddr_un *) addr;
+ struct stat socket_stat;
+ int fd, res;
+
+ spa_assert(addr_un->sun_family == AF_UNIX);
+
+ fd = check_systemd_activation(addr_un->sun_path);
+ if (fd >= 0) {
+ server->activated = true;
+ pw_log_info("server %p: found systemd socket activation socket for '%s'",
+ server, addr_un->sun_path);
+ goto done;
+ }
+ else {
+ server->activated = false;
+ }
+
+ fd = socket(addr_un->sun_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (fd < 0) {
+ res = -errno;
+ pw_log_info("server %p: socket() failed: %m", server);
+ goto error;
+ }
+
+ if (stat(addr_un->sun_path, &socket_stat) < 0) {
+ if (errno != ENOENT) {
+ res = -errno;
+ pw_log_warn("server %p: stat('%s') failed: %m",
+ server, addr_un->sun_path);
+ goto error_close;
+ }
+ }
+ else if (socket_stat.st_mode & S_IWUSR || socket_stat.st_mode & S_IWGRP) {
+ if (!S_ISSOCK(socket_stat.st_mode)) {
+ res = -EEXIST;
+ pw_log_warn("server %p: '%s' exists and is not a socket",
+ server, addr_un->sun_path);
+ goto error_close;
+ }
+
+ /* socket is there, check if it's stale */
+ if (!is_stale_socket(fd, addr_un)) {
+ res = -EADDRINUSE;
+ pw_log_warn("server %p: socket '%s' is in use",
+ server, addr_un->sun_path);
+ goto error_close;
+ }
+
+ pw_log_warn("server %p: unlinking stale socket '%s'",
+ server, addr_un->sun_path);
+
+ if (unlink(addr_un->sun_path) < 0)
+ pw_log_warn("server %p: unlink('%s') failed: %m",
+ server, addr_un->sun_path);
+ }
+ if (bind(fd, (const struct sockaddr *) addr_un, SUN_LEN(addr_un)) < 0) {
+ res = -errno;
+ pw_log_warn("server %p: bind() to '%s' failed: %m",
+ server, addr_un->sun_path);
+ goto error_close;
+ }
+
+ if (chmod(addr_un->sun_path, 0777) < 0)
+ pw_log_warn("server %p: chmod('%s') failed: %m",
+ server, addr_un->sun_path);
+
+ if (listen(fd, server->listen_backlog) < 0) {
+ res = -errno;
+ pw_log_warn("server %p: listen() on '%s' failed: %m",
+ server, addr_un->sun_path);
+ goto error_close;
+ }
+
+ pw_log_info("server %p: listening on unix:%s", server, addr_un->sun_path);
+
+done:
+ server->addr = *addr;
+
+ return fd;
+
+error_close:
+ close(fd);
+error:
+ return res;
+}
+
+static int parse_port(const char *port)
+{
+ const char *end;
+ long p;
+
+ if (port[0] == ':')
+ port += 1;
+
+ errno = 0;
+ p = strtol(port, (char **) &end, 0);
+
+ if (errno != 0)
+ return -errno;
+
+ if (end == port || *end != '\0')
+ return -EINVAL;
+
+ if (!(1 <= p && p <= 65535))
+ return -EINVAL;
+
+ return p;
+}
+
+static int parse_ipv6_address(const char *address, struct sockaddr_in6 *out)
+{
+ char addr_str[INET6_ADDRSTRLEN];
+ struct sockaddr_in6 addr = {0};
+ const char *end;
+ size_t len;
+ int res;
+
+ if (address[0] != '[')
+ return -EINVAL;
+
+ address += 1;
+
+ end = strchr(address, ']');
+ if (end == NULL)
+ return -EINVAL;
+
+ len = end - address;
+ if (len >= sizeof(addr_str))
+ return -ENAMETOOLONG;
+
+ memcpy(addr_str, address, len);
+ addr_str[len] = '\0';
+
+ res = inet_pton(AF_INET6, addr_str, &addr.sin6_addr.s6_addr);
+ if (res < 0)
+ return -errno;
+ if (res == 0)
+ return -EINVAL;
+
+ res = parse_port(end + 1);
+ if (res < 0)
+ return res;
+
+ addr.sin6_port = htons(res);
+ addr.sin6_family = AF_INET6;
+
+ *out = addr;
+
+ return 0;
+}
+
+static int parse_ipv4_address(const char *address, struct sockaddr_in *out)
+{
+ char addr_str[INET_ADDRSTRLEN];
+ struct sockaddr_in addr = {0};
+ size_t len;
+ int res;
+
+ len = strspn(address, "0123456789.");
+ if (len == 0)
+ return -EINVAL;
+ if (len >= sizeof(addr_str))
+ return -ENAMETOOLONG;
+
+ memcpy(addr_str, address, len);
+ addr_str[len] = '\0';
+
+ res = inet_pton(AF_INET, addr_str, &addr.sin_addr.s_addr);
+ if (res < 0)
+ return -errno;
+ if (res == 0)
+ return -EINVAL;
+
+ res = parse_port(address + len);
+ if (res < 0)
+ return res;
+
+ addr.sin_port = htons(res);
+ addr.sin_family = AF_INET;
+
+ *out = addr;
+
+ return 0;
+}
+
+#define FORMATTED_IP_ADDR_STRLEN (INET6_ADDRSTRLEN + 2 + 1 + 5)
+
+static int format_ip_address(const struct sockaddr_storage *addr, char *buffer, size_t buflen)
+{
+ char ip[INET6_ADDRSTRLEN];
+ const void *src;
+ bool is_ipv6 = false;
+ int port;
+
+ switch (addr->ss_family) {
+ case AF_INET:
+ src = &((struct sockaddr_in *) addr)->sin_addr.s_addr;
+ port = ntohs(((struct sockaddr_in *) addr)->sin_port);
+ break;
+ case AF_INET6:
+ is_ipv6 = true;
+ src = &((struct sockaddr_in6 *) addr)->sin6_addr.s6_addr;
+ port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port);
+ break;
+ default:
+ return -EAFNOSUPPORT;
+ }
+
+ if (inet_ntop(addr->ss_family, src, ip, sizeof(ip)) == NULL)
+ return -errno;
+
+ return snprintf(buffer, buflen, "%s%s%s:%d",
+ is_ipv6 ? "[" : "",
+ ip,
+ is_ipv6 ? "]" : "",
+ port);
+}
+
+static int get_ip_address_length(const struct sockaddr_storage *addr)
+{
+ switch (addr->ss_family) {
+ case AF_INET:
+ return sizeof(struct sockaddr_in);
+ case AF_INET6:
+ return sizeof(struct sockaddr_in6);
+ default:
+ return -EAFNOSUPPORT;
+ }
+}
+
+static int parse_ip_address(const char *address, struct sockaddr_storage *addrs, int len)
+{
+ char ip[FORMATTED_IP_ADDR_STRLEN];
+ struct sockaddr_storage addr;
+ int res;
+
+ res = parse_ipv6_address(address, (struct sockaddr_in6 *) &addr);
+ if (res == 0) {
+ if (len < 1)
+ return -ENOSPC;
+ addrs[0] = addr;
+ return 1;
+ }
+
+ res = parse_ipv4_address(address, (struct sockaddr_in *) &addr);
+ if (res == 0) {
+ if (len < 1)
+ return -ENOSPC;
+ addrs[0] = addr;
+ return 1;
+ }
+
+ res = parse_port(address);
+ if (res < 0)
+ return res;
+
+ if (len < 2)
+ return -ENOSPC;
+
+ snprintf(ip, sizeof(ip), "0.0.0.0:%d", res);
+ spa_assert_se(parse_ipv4_address(ip, (struct sockaddr_in *) &addr) == 0);
+ addrs[0] = addr;
+
+ snprintf(ip, sizeof(ip), "[::]:%d", res);
+ spa_assert_se(parse_ipv6_address(ip, (struct sockaddr_in6 *) &addr) == 0);
+ addrs[1] = addr;
+
+ return 2;
+}
+
+static int start_ip_server(struct server *server, const struct sockaddr_storage *addr)
+{
+ char ip[FORMATTED_IP_ADDR_STRLEN];
+ int fd, res;
+
+ spa_assert(addr->ss_family == AF_INET || addr->ss_family == AF_INET6);
+
+ fd = socket(addr->ss_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_TCP);
+ if (fd < 0) {
+ res = -errno;
+ pw_log_warn("server %p: socket() failed: %m", server);
+ goto error;
+ }
+
+ {
+ int on = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
+ pw_log_warn("server %p: setsockopt(SO_REUSEADDR) failed: %m", server);
+ }
+
+ if (addr->ss_family == AF_INET6) {
+ int on = 1;
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0)
+ pw_log_warn("server %p: setsockopt(IPV6_V6ONLY) failed: %m", server);
+ }
+
+ if (bind(fd, (const struct sockaddr *) addr, get_ip_address_length(addr)) < 0) {
+ res = -errno;
+ pw_log_warn("server %p: bind() failed: %m", server);
+ goto error_close;
+ }
+
+ if (listen(fd, server->listen_backlog) < 0) {
+ res = -errno;
+ pw_log_warn("server %p: listen() failed: %m", server);
+ goto error_close;
+ }
+
+ spa_assert_se(format_ip_address(addr, ip, sizeof(ip)) >= 0);
+ pw_log_info("server %p: listening on tcp:%s", server, ip);
+
+ server->addr = *addr;
+
+ return fd;
+
+error_close:
+ close(fd);
+error:
+ return res;
+}
+
+static struct server *server_new(struct impl *impl)
+{
+ struct server * const server = calloc(1, sizeof(*server));
+ if (server == NULL)
+ return NULL;
+
+ server->impl = impl;
+ server->addr.ss_family = AF_UNSPEC;
+ spa_list_init(&server->clients);
+ spa_list_append(&impl->servers, &server->link);
+
+ pw_log_debug("server %p: new", server);
+
+ return server;
+}
+
+static int server_start(struct server *server, const struct sockaddr_storage *addr)
+{
+ struct impl * const impl = server->impl;
+ int res = 0, fd;
+
+ switch (addr->ss_family) {
+ case AF_INET:
+ case AF_INET6:
+ fd = start_ip_server(server, addr);
+ break;
+ case AF_UNIX:
+ fd = start_unix_server(server, addr);
+ break;
+ default:
+ /* shouldn't happen */
+ fd = -EAFNOSUPPORT;
+ break;
+ }
+
+ if (fd < 0)
+ return fd;
+
+ server->source = pw_loop_add_io(impl->loop, fd, SPA_IO_IN, true, on_connect, server);
+ if (server->source == NULL) {
+ res = -errno;
+ pw_log_error("server %p: can't create server source: %m", impl);
+ }
+ if (res >= 0)
+ spa_hook_list_call(&impl->hooks, struct impl_events, server_started, 0, server);
+
+ return res;
+}
+
+static int parse_address(const char *address, struct sockaddr_storage *addrs, int len)
+{
+ if (spa_strstartswith(address, "tcp:"))
+ return parse_ip_address(address + strlen("tcp:"), addrs, len);
+
+ if (spa_strstartswith(address, "unix:"))
+ return parse_unix_address(address + strlen("unix:"), addrs, len);
+
+ return -EAFNOSUPPORT;
+}
+
+#define SUN_PATH_SIZE (sizeof(((struct sockaddr_un *) NULL)->sun_path))
+#define FORMATTED_UNIX_ADDR_STRLEN (SUN_PATH_SIZE + 5)
+#define FORMATTED_TCP_ADDR_STRLEN (FORMATTED_IP_ADDR_STRLEN + 4)
+#define FORMATTED_SOCKET_ADDR_STRLEN \
+ (FORMATTED_UNIX_ADDR_STRLEN > FORMATTED_TCP_ADDR_STRLEN ? \
+ FORMATTED_UNIX_ADDR_STRLEN : \
+ FORMATTED_TCP_ADDR_STRLEN)
+
+static int format_socket_address(const struct sockaddr_storage *addr, char *buffer, size_t buflen)
+{
+ if (addr->ss_family == AF_INET || addr->ss_family == AF_INET6) {
+ char ip[FORMATTED_IP_ADDR_STRLEN];
+
+ spa_assert_se(format_ip_address(addr, ip, sizeof(ip)) >= 0);
+
+ return snprintf(buffer, buflen, "tcp:%s", ip);
+ }
+ else if (addr->ss_family == AF_UNIX) {
+ const struct sockaddr_un *addr_un = (const struct sockaddr_un *) addr;
+
+ return snprintf(buffer, buflen, "unix:%s", addr_un->sun_path);
+ }
+
+ return -EAFNOSUPPORT;
+}
+
+int servers_create_and_start(struct impl *impl, const char *addresses, struct pw_array *servers)
+{
+ 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];
+
+ /* 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)
+ return -EINVAL;
+
+ /* a server-spec is either an address or an object */
+ while ((len = spa_json_next(&it[1], &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;
+
+ if (spa_streq(key, "address")) {
+ spa_json_parse_stringn(v, len, addr_str, sizeof(addr_str));
+ } else if (spa_streq(key, "max-clients")) {
+ spa_json_parse_int(v, len, &max_clients);
+ } else if (spa_streq(key, "listen-backlog")) {
+ spa_json_parse_int(v, len, &listen_backlog);
+ } else if (spa_streq(key, "client.access")) {
+ spa_json_parse_stringn(v, len, client_access, sizeof(client_access));
+ }
+ }
+ } else {
+ spa_json_parse_stringn(v, len, addr_str, sizeof(addr_str));
+ }
+
+ n_addr = parse_address(addr_str, addrs, SPA_N_ELEMENTS(addrs));
+ if (n_addr < 0) {
+ pw_log_warn("pulse-server %p: failed to parse address '%s': %s",
+ impl, addr_str, spa_strerror(n_addr));
+ UPDATE_ERR(n_addr);
+ continue;
+ }
+
+ /* try to create sockets for each address in the list */
+ for (i = 0; i < n_addr; i++) {
+ const struct sockaddr_storage *addr = &addrs[i];
+ struct server * const server = server_new(impl);
+
+ if (server == NULL) {
+ UPDATE_ERR(-errno);
+ continue;
+ }
+
+ server->max_clients = max_clients;
+ server->listen_backlog = listen_backlog;
+ memcpy(server->client_access, client_access, sizeof(client_access));
+
+ res = server_start(server, addr);
+ if (res < 0) {
+ spa_assert_se(format_socket_address(addr, addr_str, sizeof(addr_str)) >= 0);
+ pw_log_warn("pulse-server %p: failed to start server on '%s': %s",
+ impl, addr_str, spa_strerror(res));
+ UPDATE_ERR(res);
+ server_free(server);
+ continue;
+ }
+
+ if (servers != NULL)
+ pw_array_add_ptr(servers, server);
+
+ count += 1;
+ }
+ }
+ if (count == 0) {
+ UPDATE_ERR(-EINVAL);
+ return err;
+ }
+ return count;
+
+#undef UPDATE_ERR
+}
+
+void server_free(struct server *server)
+{
+ struct impl * const impl = server->impl;
+ struct client *c, *t;
+
+ pw_log_debug("server %p: free", server);
+
+ spa_list_remove(&server->link);
+
+ spa_list_for_each_safe(c, t, &server->clients, link) {
+ spa_assert_se(client_detach(c));
+ client_unref(c);
+ }
+
+ spa_hook_list_call(&impl->hooks, struct impl_events, server_stopped, 0, server);
+
+ if (server->source)
+ pw_loop_destroy_source(impl->loop, server->source);
+
+ if (server->addr.ss_family == AF_UNIX && !server->activated)
+ unlink(((const struct sockaddr_un *) &server->addr)->sun_path);
+
+ free(server);
+}
diff --git a/src/modules/module-protocol-pulse/server.h b/src/modules/module-protocol-pulse/server.h
new file mode 100644
index 0000000..9474715
--- /dev/null
+++ b/src/modules/module-protocol-pulse/server.h
@@ -0,0 +1,60 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSER_SERVER_SERVER_H
+#define PULSER_SERVER_SERVER_H
+
+#include <stdint.h>
+
+#include <sys/socket.h>
+
+#include <spa/utils/list.h>
+#include <spa/utils/hook.h>
+
+struct impl;
+struct pw_array;
+struct spa_source;
+
+struct server {
+ struct spa_list link;
+ struct impl *impl;
+
+ struct sockaddr_storage addr;
+
+ struct spa_source *source;
+ struct spa_list clients;
+
+ uint32_t max_clients;
+ uint32_t listen_backlog;
+ char client_access[64];
+
+ uint32_t n_clients;
+ uint32_t wait_clients;
+ unsigned int activated:1;
+};
+
+int servers_create_and_start(struct impl *impl, const char *addresses, struct pw_array *servers);
+void server_free(struct server *server);
+
+#endif /* PULSER_SERVER_SERVER_H */
diff --git a/src/modules/module-protocol-pulse/stream.c b/src/modules/module-protocol-pulse/stream.c
new file mode 100644
index 0000000..59fb8a3
--- /dev/null
+++ b/src/modules/module-protocol-pulse/stream.c
@@ -0,0 +1,428 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <spa/utils/hook.h>
+#include <spa/utils/ringbuffer.h>
+#include <pipewire/log.h>
+#include <pipewire/loop.h>
+#include <pipewire/map.h>
+#include <pipewire/properties.h>
+#include <pipewire/stream.h>
+#include <pipewire/work-queue.h>
+
+#include "client.h"
+#include "commands.h"
+#include "defs.h"
+#include "internal.h"
+#include "log.h"
+#include "message.h"
+#include "reply.h"
+#include "stream.h"
+
+static int parse_frac(struct pw_properties *props, const char *key,
+ const struct spa_fraction *def, struct spa_fraction *res)
+{
+ const char *str;
+ if (props == NULL ||
+ (str = pw_properties_get(props, key)) == NULL ||
+ sscanf(str, "%u/%u", &res->num, &res->denom) != 2 ||
+ res->denom == 0) {
+ *res = *def;
+ }
+ return 0;
+}
+
+struct stream *stream_new(struct client *client, enum stream_type type, uint32_t create_tag,
+ const struct sample_spec *ss, const struct channel_map *map,
+ const struct buffer_attr *attr)
+{
+ int res;
+ struct defs *defs = &client->impl->defs;
+ const char *str;
+
+ struct stream *stream = calloc(1, sizeof(*stream));
+ if (stream == NULL)
+ return NULL;
+
+ stream->channel = pw_map_insert_new(&client->streams, stream);
+ if (stream->channel == SPA_ID_INVALID)
+ goto error_errno;
+
+ stream->impl = client->impl;
+ stream->client = client;
+ stream->type = type;
+ stream->create_tag = create_tag;
+ stream->ss = *ss;
+ stream->map = *map;
+ stream->attr = *attr;
+ spa_ringbuffer_init(&stream->ring);
+
+ stream->peer_index = SPA_ID_INVALID;
+
+ parse_frac(client->props, "pulse.min.req", &defs->min_req, &stream->min_req);
+ parse_frac(client->props, "pulse.min.frag", &defs->min_frag, &stream->min_frag);
+ parse_frac(client->props, "pulse.min.quantum", &defs->min_quantum, &stream->min_quantum);
+ parse_frac(client->props, "pulse.default.req", &defs->default_req, &stream->default_req);
+ parse_frac(client->props, "pulse.default.frag", &defs->default_frag, &stream->default_frag);
+ parse_frac(client->props, "pulse.default.tlength", &defs->default_tlength, &stream->default_tlength);
+ stream->idle_timeout_sec = defs->idle_timeout;
+ if ((str = pw_properties_get(client->props, "pulse.idle.timeout")) != NULL)
+ spa_atou32(str, &stream->idle_timeout_sec, 0);
+
+ switch (type) {
+ case STREAM_TYPE_RECORD:
+ stream->direction = PW_DIRECTION_INPUT;
+ break;
+ case STREAM_TYPE_PLAYBACK:
+ case STREAM_TYPE_UPLOAD:
+ stream->direction = PW_DIRECTION_OUTPUT;
+ break;
+ default:
+ spa_assert_not_reached();
+ }
+
+ return stream;
+
+error_errno:
+ res = errno;
+ free(stream);
+ errno = res;
+
+ return NULL;
+}
+
+void stream_free(struct stream *stream)
+{
+ struct client *client = stream->client;
+ struct impl *impl = client->impl;
+
+ pw_log_debug("client %p: stream %p channel:%d", client, stream, stream->channel);
+
+ if (stream->pending)
+ spa_list_remove(&stream->link);
+
+ if (stream->drain_tag)
+ reply_error(client, -1, stream->drain_tag, -ENOENT);
+
+ if (stream->killed)
+ stream_send_killed(stream);
+
+ if (stream->stream) {
+ spa_hook_remove(&stream->stream_listener);
+ pw_stream_disconnect(stream->stream);
+
+ /* force processing of all pending messages before we destroy
+ * the stream */
+ pw_loop_invoke(impl->loop, NULL, 0, NULL, 0, false, client);
+
+ pw_stream_destroy(stream->stream);
+ }
+ if (stream->channel != SPA_ID_INVALID)
+ pw_map_remove(&client->streams, stream->channel);
+
+ pw_work_queue_cancel(impl->work_queue, stream, SPA_ID_INVALID);
+
+ if (stream->buffer)
+ free(stream->buffer);
+
+ pw_properties_free(stream->props);
+
+ free(stream);
+}
+
+void stream_flush(struct stream *stream)
+{
+ pw_stream_flush(stream->stream, false);
+
+ if (stream->type == STREAM_TYPE_PLAYBACK) {
+ stream->ring.writeindex = stream->ring.readindex;
+ stream->write_index = stream->read_index;
+
+ if (stream->attr.prebuf > 0)
+ stream->in_prebuf = true;
+
+ stream->playing_for = 0;
+ stream->underrun_for = -1;
+ stream->is_underrun = true;
+
+ stream_send_request(stream);
+ } else {
+ stream->ring.readindex = stream->ring.writeindex;
+ stream->read_index = stream->write_index;
+ }
+}
+
+static bool stream_prebuf_active(struct stream *stream, int32_t avail)
+{
+ if (stream->in_prebuf) {
+ if (avail >= (int32_t) stream->attr.prebuf)
+ stream->in_prebuf = false;
+ } else {
+ if (stream->attr.prebuf > 0 && avail <= 0)
+ stream->in_prebuf = true;
+ }
+ return stream->in_prebuf;
+}
+
+uint32_t stream_pop_missing(struct stream *stream)
+{
+ int64_t missing, avail;
+
+ avail = stream->write_index - stream->read_index;
+
+ missing = stream->attr.tlength;
+ missing -= stream->requested;
+ missing -= avail;
+
+ if (missing <= 0)
+ return 0;
+
+ if (missing < stream->attr.minreq && !stream_prebuf_active(stream, avail))
+ return 0;
+
+ stream->requested += missing;
+
+ return missing;
+}
+
+void stream_set_paused(struct stream *stream, bool paused, const char *reason)
+{
+ if (stream->is_paused == paused)
+ return;
+
+ if (reason && stream->client)
+ pw_log_info("%p: [%s] %s because of %s",
+ stream, stream->client->name,
+ paused ? "paused" : "resumed", reason);
+
+ stream->is_paused = paused;
+ pw_stream_set_active(stream->stream, !paused);
+}
+
+int stream_send_underflow(struct stream *stream, int64_t offset)
+{
+ struct client *client = stream->client;
+ struct impl *impl = client->impl;
+ struct message *reply;
+
+ if (ratelimit_test(&impl->rate_limit, stream->timestamp, SPA_LOG_LEVEL_INFO)) {
+ pw_log_info("[%s]: UNDERFLOW channel:%u offset:%" PRIi64,
+ client->name, stream->channel, offset);
+ }
+
+ reply = message_alloc(impl, -1, 0);
+ message_put(reply,
+ TAG_U32, COMMAND_UNDERFLOW,
+ TAG_U32, -1,
+ TAG_U32, stream->channel,
+ TAG_INVALID);
+
+ if (client->version >= 23) {
+ message_put(reply,
+ TAG_S64, offset,
+ TAG_INVALID);
+ }
+
+ return client_queue_message(client, reply);
+}
+
+int stream_send_overflow(struct stream *stream)
+{
+ struct client *client = stream->client;
+ struct impl *impl = client->impl;
+ struct message *reply;
+
+ pw_log_warn("client %p [%s]: stream %p OVERFLOW channel:%u",
+ client, client->name, stream, stream->channel);
+
+ reply = message_alloc(impl, -1, 0);
+ message_put(reply,
+ TAG_U32, COMMAND_OVERFLOW,
+ TAG_U32, -1,
+ TAG_U32, stream->channel,
+ TAG_INVALID);
+
+ return client_queue_message(client, reply);
+}
+
+int stream_send_killed(struct stream *stream)
+{
+ struct client *client = stream->client;
+ struct impl *impl = client->impl;
+ struct message *reply;
+ uint32_t command;
+
+ command = stream->direction == PW_DIRECTION_OUTPUT ?
+ COMMAND_PLAYBACK_STREAM_KILLED :
+ COMMAND_RECORD_STREAM_KILLED;
+
+ pw_log_info("[%s]: %s channel:%u",
+ client->name, commands[command].name, stream->channel);
+
+ if (client->version < 23)
+ return 0;
+
+ reply = message_alloc(impl, -1, 0);
+ message_put(reply,
+ TAG_U32, command,
+ TAG_U32, -1,
+ TAG_U32, stream->channel,
+ TAG_INVALID);
+
+ return client_queue_message(client, reply);
+}
+
+int stream_send_started(struct stream *stream)
+{
+ struct client *client = stream->client;
+ struct impl *impl = client->impl;
+ struct message *reply;
+
+ pw_log_debug("client %p [%s]: stream %p STARTED channel:%u",
+ client, client->name, stream, stream->channel);
+
+ reply = message_alloc(impl, -1, 0);
+ message_put(reply,
+ TAG_U32, COMMAND_STARTED,
+ TAG_U32, -1,
+ TAG_U32, stream->channel,
+ TAG_INVALID);
+
+ return client_queue_message(client, reply);
+}
+
+int stream_send_request(struct stream *stream)
+{
+ struct client *client = stream->client;
+ struct impl *impl = client->impl;
+ struct message *msg;
+ uint32_t size;
+
+ size = stream_pop_missing(stream);
+ pw_log_debug("stream %p: REQUEST channel:%d %u", stream, stream->channel, size);
+
+ if (size == 0)
+ return 0;
+
+ msg = message_alloc(impl, -1, 0);
+ message_put(msg,
+ TAG_U32, COMMAND_REQUEST,
+ TAG_U32, -1,
+ TAG_U32, stream->channel,
+ TAG_U32, size,
+ TAG_INVALID);
+
+ return client_queue_message(client, msg);
+}
+
+int stream_update_minreq(struct stream *stream, uint32_t minreq)
+{
+ struct client *client = stream->client;
+ struct impl *impl = client->impl;
+ uint32_t old_tlength = stream->attr.tlength;
+ uint32_t new_tlength = minreq + 2 * stream->attr.minreq;
+ uint64_t lat_usec;
+
+ if (new_tlength <= old_tlength)
+ return 0;
+
+ if (new_tlength > MAXLENGTH)
+ new_tlength = MAXLENGTH;
+
+ stream->attr.tlength = new_tlength;
+ if (stream->attr.tlength > stream->attr.maxlength)
+ stream->attr.maxlength = stream->attr.tlength;
+
+ if (client->version >= 15) {
+ struct message *msg;
+
+ lat_usec = minreq * SPA_USEC_PER_SEC / stream->ss.rate;
+
+ msg = message_alloc(impl, -1, 0);
+ message_put(msg,
+ TAG_U32, COMMAND_PLAYBACK_BUFFER_ATTR_CHANGED,
+ TAG_U32, -1,
+ TAG_U32, stream->channel,
+ TAG_U32, stream->attr.maxlength,
+ TAG_U32, stream->attr.tlength,
+ TAG_U32, stream->attr.prebuf,
+ TAG_U32, stream->attr.minreq,
+ TAG_USEC, lat_usec,
+ TAG_INVALID);
+ return client_queue_message(client, msg);
+ }
+ return 0;
+}
+
+int stream_send_moved(struct stream *stream, uint32_t peer_index, const char *peer_name)
+{
+ struct client *client = stream->client;
+ struct impl *impl = client->impl;
+ struct message *reply;
+ uint32_t command;
+
+ command = stream->direction == PW_DIRECTION_OUTPUT ?
+ COMMAND_PLAYBACK_STREAM_MOVED :
+ COMMAND_RECORD_STREAM_MOVED;
+
+ pw_log_info("client %p [%s]: stream %p %s channel:%u",
+ client, client->name, stream, commands[command].name,
+ stream->channel);
+
+ if (client->version < 12)
+ return 0;
+
+ reply = message_alloc(impl, -1, 0);
+ message_put(reply,
+ TAG_U32, command,
+ TAG_U32, -1,
+ TAG_U32, stream->channel,
+ TAG_U32, peer_index,
+ TAG_STRING, peer_name,
+ TAG_BOOLEAN, false, /* suspended */
+ TAG_INVALID);
+
+ if (client->version >= 13) {
+ if (command == COMMAND_PLAYBACK_STREAM_MOVED) {
+ message_put(reply,
+ TAG_U32, stream->attr.maxlength,
+ TAG_U32, stream->attr.tlength,
+ TAG_U32, stream->attr.prebuf,
+ TAG_U32, stream->attr.minreq,
+ TAG_USEC, stream->lat_usec,
+ TAG_INVALID);
+ } else {
+ message_put(reply,
+ TAG_U32, stream->attr.maxlength,
+ TAG_U32, stream->attr.fragsize,
+ TAG_USEC, stream->lat_usec,
+ TAG_INVALID);
+ }
+ }
+ return client_queue_message(client, reply);
+}
diff --git a/src/modules/module-protocol-pulse/stream.h b/src/modules/module-protocol-pulse/stream.h
new file mode 100644
index 0000000..424b2a1
--- /dev/null
+++ b/src/modules/module-protocol-pulse/stream.h
@@ -0,0 +1,141 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSER_SERVER_STREAM_H
+#define PULSER_SERVER_STREAM_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <spa/utils/hook.h>
+#include <spa/utils/ringbuffer.h>
+#include <pipewire/pipewire.h>
+
+#include "format.h"
+#include "volume.h"
+
+struct impl;
+struct client;
+struct spa_io_rate_match;
+
+struct buffer_attr {
+ uint32_t maxlength;
+ uint32_t tlength;
+ uint32_t prebuf;
+ uint32_t minreq;
+ uint32_t fragsize;
+};
+
+enum stream_type {
+ STREAM_TYPE_RECORD,
+ STREAM_TYPE_PLAYBACK,
+ STREAM_TYPE_UPLOAD,
+};
+
+struct stream {
+ struct spa_list link;
+ uint32_t create_tag;
+ uint32_t channel; /* index in map */
+ uint32_t id; /* id of global */
+ uint32_t index; /* index */
+
+ uint32_t peer_index;
+
+ struct impl *impl;
+ struct client *client;
+ enum stream_type type;
+ enum pw_direction direction;
+
+ struct pw_properties *props;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ struct spa_io_position *position;
+ struct spa_ringbuffer ring;
+ void *buffer;
+
+ int64_t read_index;
+ int64_t write_index;
+ uint64_t underrun_for;
+ uint64_t playing_for;
+ uint64_t ticks_base;
+ uint64_t timestamp;
+ uint64_t idle_time;
+ int64_t delay;
+
+ uint32_t last_quantum;
+ int64_t requested;
+
+ struct spa_fraction min_req;
+ struct spa_fraction default_req;
+ struct spa_fraction min_frag;
+ struct spa_fraction default_frag;
+ struct spa_fraction default_tlength;
+ struct spa_fraction min_quantum;
+ uint32_t idle_timeout_sec;
+
+ struct sample_spec ss;
+ struct channel_map map;
+ struct buffer_attr attr;
+ uint32_t frame_size;
+ uint32_t rate;
+ uint64_t lat_usec;
+
+ struct volume volume;
+ bool muted;
+
+ uint32_t drain_tag;
+ unsigned int corked:1;
+ unsigned int draining:1;
+ unsigned int volume_set:1;
+ unsigned int muted_set:1;
+ unsigned int early_requests:1;
+ unsigned int adjust_latency:1;
+ unsigned int is_underrun:1;
+ unsigned int in_prebuf:1;
+ unsigned int killed:1;
+ unsigned int pending:1;
+ unsigned int is_idle:1;
+ unsigned int is_paused:1;
+};
+
+struct stream *stream_new(struct client *client, enum stream_type type, uint32_t create_tag,
+ const struct sample_spec *ss, const struct channel_map *map,
+ const struct buffer_attr *attr);
+void stream_free(struct stream *stream);
+void stream_flush(struct stream *stream);
+uint32_t stream_pop_missing(struct stream *stream);
+
+void stream_set_paused(struct stream *stream, bool paused, const char *reason);
+
+int stream_send_underflow(struct stream *stream, int64_t offset);
+int stream_send_overflow(struct stream *stream);
+int stream_send_killed(struct stream *stream);
+int stream_send_started(struct stream *stream);
+int stream_send_request(struct stream *stream);
+int stream_update_minreq(struct stream *stream, uint32_t minreq);
+int stream_send_moved(struct stream *stream, uint32_t peer_index, const char *peer_name);
+
+#endif /* PULSER_SERVER_STREAM_H */
diff --git a/src/modules/module-protocol-pulse/utils.c b/src/modules/module-protocol-pulse/utils.c
new file mode 100644
index 0000000..7626449
--- /dev/null
+++ b/src/modules/module-protocol-pulse/utils.c
@@ -0,0 +1,207 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_VFS_H
+#include <sys/vfs.h>
+#endif
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#include <spa/utils/result.h>
+#include <pipewire/context.h>
+#include <pipewire/log.h>
+#include <pipewire/keys.h>
+
+#include "log.h"
+#include "utils.h"
+
+int get_runtime_dir(char *buf, size_t buflen)
+{
+ const char *runtime_dir, *dir = NULL;
+ struct stat stat_buf;
+ int res, size;
+
+ runtime_dir = getenv("PULSE_RUNTIME_PATH");
+ if (runtime_dir == NULL) {
+ runtime_dir = getenv("XDG_RUNTIME_DIR");
+ dir = "pulse";
+ }
+ if (runtime_dir == NULL) {
+ pw_log_error("could not find a suitable runtime directory in"
+ "$PULSE_RUNTIME_PATH and $XDG_RUNTIME_DIR");
+ return -ENOENT;
+ }
+
+ size = snprintf(buf, buflen, "%s%s%s", runtime_dir,
+ dir ? "/" : "", dir ? dir : "");
+ if (size < 0)
+ return -errno;
+ if ((size_t) size >= buflen) {
+ pw_log_error("path %s%s%s too long", runtime_dir,
+ dir ? "/" : "", dir ? dir : "");
+ return -ENAMETOOLONG;
+ }
+
+ if (stat(buf, &stat_buf) < 0) {
+ res = -errno;
+ if (res != -ENOENT) {
+ pw_log_error("stat() %s failed: %m", buf);
+ return res;
+ }
+ if (mkdir(buf, 0700) < 0) {
+ res = -errno;
+ pw_log_error("mkdir() %s failed: %m", buf);
+ return res;
+ }
+ pw_log_info("created %s", buf);
+ } else if (!S_ISDIR(stat_buf.st_mode)) {
+ pw_log_error("%s is not a directory", buf);
+ return -ENOTDIR;
+ }
+ return 0;
+}
+
+int check_flatpak(struct client *client, pid_t pid)
+{
+ char root_path[2048];
+ int root_fd, info_fd, res;
+ struct stat stat_buf;
+
+ sprintf(root_path, "/proc/%ld/root", (long) pid);
+ root_fd = openat(AT_FDCWD, root_path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY);
+ if (root_fd == -1) {
+ res = -errno;
+ if (res == -EACCES) {
+ struct statfs buf;
+ /* Access to the root dir isn't allowed. This can happen if the root is on a fuse
+ * filesystem, such as in a toolbox container. We will never have a fuse rootfs
+ * in the flatpak case, so in that case its safe to ignore this and
+ * continue to detect other types of apps. */
+ if (statfs(root_path, &buf) == 0 &&
+ buf.f_type == 0x65735546) /* FUSE_SUPER_MAGIC */
+ return 0;
+ }
+ /* Not able to open the root dir shouldn't happen. Probably the app died and
+ * we're failing due to /proc/$pid not existing. In that case fail instead
+ * of treating this as privileged. */
+ pw_log_info("failed to open \"%s\"%s", root_path, spa_strerror(res));
+ return res;
+ }
+ info_fd = openat(root_fd, ".flatpak-info", O_RDONLY | O_CLOEXEC | O_NOCTTY);
+ close(root_fd);
+ if (info_fd == -1) {
+ if (errno == ENOENT) {
+ pw_log_debug("no .flatpak-info, client on the host");
+ /* No file => on the host */
+ return 0;
+ }
+ res = -errno;
+ pw_log_error("error opening .flatpak-info: %m");
+ return res;
+ }
+ if (fstat(info_fd, &stat_buf) != 0 || !S_ISREG(stat_buf.st_mode)) {
+ /* Some weird fd => failure, assume sandboxed */
+ pw_log_error("error fstat .flatpak-info: %m");
+ }
+ close(info_fd);
+ return 1;
+}
+
+pid_t get_client_pid(struct client *client, int client_fd)
+{
+ socklen_t len;
+#if defined(__linux__)
+ struct ucred ucred;
+ len = sizeof(ucred);
+ if (getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) < 0) {
+ pw_log_warn("client %p: no peercred: %m", client);
+ } else
+ return ucred.pid;
+#elif defined(__FreeBSD__) || defined(__MidnightBSD__)
+ struct xucred xucred;
+ len = sizeof(xucred);
+ if (getsockopt(client_fd, 0, LOCAL_PEERCRED, &xucred, &len) < 0) {
+ pw_log_warn("client %p: no peercred: %m", client);
+ } else {
+#if __FreeBSD__ >= 13
+ return xucred.cr_pid;
+#endif
+ }
+#endif
+ return 0;
+}
+
+const char *get_server_name(struct pw_context *context)
+{
+ const char *name = NULL;
+ 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 || name[0] == '\0')
+ name = PW_DEFAULT_REMOTE;
+ return name;
+}
+
+int create_pid_file(void) {
+ char pid_file[PATH_MAX];
+ FILE *f;
+ int res;
+
+ if ((res = get_runtime_dir(pid_file, sizeof(pid_file))) < 0)
+ return res;
+
+ if (strlen(pid_file) > PATH_MAX - sizeof("/pid")) {
+ pw_log_error("path too long: %s/pid", pid_file);
+ return -ENAMETOOLONG;
+ }
+
+ strcat(pid_file, "/pid");
+
+ if ((f = fopen(pid_file, "we")) == NULL) {
+ res = -errno;
+ pw_log_error("failed to open pid file: %m");
+ return res;
+ }
+
+ fprintf(f, "%lu\n", (unsigned long) getpid());
+ fclose(f);
+
+ return 0;
+}
diff --git a/src/modules/module-protocol-pulse/utils.h b/src/modules/module-protocol-pulse/utils.h
new file mode 100644
index 0000000..fafccf3
--- /dev/null
+++ b/src/modules/module-protocol-pulse/utils.h
@@ -0,0 +1,40 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSE_SERVER_UTILS_H
+#define PULSE_SERVER_UTILS_H
+
+#include <stddef.h>
+#include <sys/types.h>
+
+struct client;
+struct pw_context;
+
+int get_runtime_dir(char *buf, size_t buflen);
+int check_flatpak(struct client *client, pid_t pid);
+pid_t get_client_pid(struct client *client, int client_fd);
+const char *get_server_name(struct pw_context *context);
+int create_pid_file(void);
+
+#endif /* PULSE_SERVER_UTILS_H */
diff --git a/src/modules/module-protocol-pulse/volume.c b/src/modules/module-protocol-pulse/volume.c
new file mode 100644
index 0000000..31f45a5
--- /dev/null
+++ b/src/modules/module-protocol-pulse/volume.c
@@ -0,0 +1,114 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/param/props.h>
+#include <spa/param/audio/raw.h>
+#include <spa/pod/iter.h>
+#include <spa/utils/defs.h>
+#include <pipewire/log.h>
+
+#include "log.h"
+#include "volume.h"
+
+int volume_compare(struct volume *vol, struct volume *other)
+{
+ uint8_t i;
+ if (vol->channels != other->channels) {
+ pw_log_info("channels %d<>%d", vol->channels, other->channels);
+ return -1;
+ }
+ for (i = 0; i < vol->channels; i++) {
+ if (vol->values[i] != other->values[i]) {
+ pw_log_info("%d: val %f<>%f", i, vol->values[i], other->values[i]);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+int volume_parse_param(const struct spa_pod *param, struct volume_info *info, bool monitor)
+{
+ struct spa_pod_object *obj = (struct spa_pod_object *) param;
+ struct spa_pod_prop *prop;
+
+ SPA_POD_OBJECT_FOREACH(obj, prop) {
+ switch (prop->key) {
+ case SPA_PROP_volume:
+ if (spa_pod_get_float(&prop->value, &info->level) < 0)
+ continue;
+ SPA_FLAG_UPDATE(info->flags, VOLUME_HW_VOLUME,
+ prop->flags & SPA_POD_PROP_FLAG_HARDWARE);
+
+ break;
+ case SPA_PROP_mute:
+ if (monitor)
+ continue;
+ if (spa_pod_get_bool(&prop->value, &info->mute) < 0)
+ continue;
+ SPA_FLAG_UPDATE(info->flags, VOLUME_HW_MUTE,
+ prop->flags & SPA_POD_PROP_FLAG_HARDWARE);
+ break;
+ case SPA_PROP_channelVolumes:
+ if (monitor)
+ continue;
+ info->volume.channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Float,
+ info->volume.values, SPA_AUDIO_MAX_CHANNELS);
+ SPA_FLAG_UPDATE(info->flags, VOLUME_HW_VOLUME,
+ prop->flags & SPA_POD_PROP_FLAG_HARDWARE);
+ break;
+ case SPA_PROP_monitorMute:
+ if (!monitor)
+ continue;
+ if (spa_pod_get_bool(&prop->value, &info->mute) < 0)
+ continue;
+ SPA_FLAG_CLEAR(info->flags, VOLUME_HW_MUTE);
+ break;
+ case SPA_PROP_monitorVolumes:
+ if (!monitor)
+ continue;
+ info->volume.channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Float,
+ info->volume.values, SPA_AUDIO_MAX_CHANNELS);
+ SPA_FLAG_CLEAR(info->flags, VOLUME_HW_VOLUME);
+ break;
+ case SPA_PROP_volumeBase:
+ if (spa_pod_get_float(&prop->value, &info->base) < 0)
+ continue;
+ break;
+ case SPA_PROP_volumeStep:
+ {
+ float step;
+ if (spa_pod_get_float(&prop->value, &step) >= 0)
+ info->steps = 0x10000u * step;
+ break;
+ }
+ case SPA_PROP_channelMap:
+ info->map.channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Id,
+ info->map.map, SPA_AUDIO_MAX_CHANNELS);
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
diff --git a/src/modules/module-protocol-pulse/volume.h b/src/modules/module-protocol-pulse/volume.h
new file mode 100644
index 0000000..11ec51a
--- /dev/null
+++ b/src/modules/module-protocol-pulse/volume.h
@@ -0,0 +1,85 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ * Copyright © 2021 Sanchayan Maity <sanchayan@asymptotic.io>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PULSE_SERVER_VOLUME_H
+#define PULSE_SERVER_VOLUME_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "format.h"
+
+struct spa_pod;
+
+struct volume {
+ uint8_t channels;
+ float values[CHANNELS_MAX];
+};
+
+#define VOLUME_INIT \
+ (struct volume) { \
+ .channels = 0, \
+ }
+
+struct volume_info {
+ struct volume volume;
+ struct channel_map map;
+ bool mute;
+ float level;
+ float base;
+ uint32_t steps;
+#define VOLUME_HW_VOLUME (1<<0)
+#define VOLUME_HW_MUTE (1<<1)
+ uint32_t flags;
+};
+
+#define VOLUME_INFO_INIT \
+ (struct volume_info) { \
+ .volume = VOLUME_INIT, \
+ .mute = false, \
+ .level = 1.0, \
+ .base = 1.0, \
+ .steps = 256, \
+ }
+
+static inline bool volume_valid(const struct volume *vol)
+{
+ if (vol->channels == 0 || vol->channels > CHANNELS_MAX)
+ return false;
+ return true;
+}
+
+static inline void volume_make(struct volume *vol, uint8_t channels)
+{
+ uint8_t i;
+ for (i = 0; i < channels; i++)
+ vol->values[i] = 1.0f;
+ vol->channels = channels;
+}
+
+int volume_compare(struct volume *vol, struct volume *other);
+int volume_parse_param(const struct spa_pod *param, struct volume_info *info, bool monitor);
+
+#endif
diff --git a/src/modules/module-protocol-simple.c b/src/modules/module-protocol-simple.c
new file mode 100644
index 0000000..1e17836
--- /dev/null
+++ b/src/modules/module-protocol-simple.c
@@ -0,0 +1,911 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netinet/ip.h>
+#include <sys/un.h>
+#include <arpa/inet.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/utils/json.h>
+#include <spa/pod/pod.h>
+#include <spa/pod/builder.h>
+#include <spa/debug/types.h>
+#include <spa/param/audio/type-info.h>
+#include <spa/param/audio/format-utils.h>
+
+#include <pipewire/impl.h>
+
+/** \page page_module_protocol_simple PipeWire Module: Protocol Simple
+ *
+ * The simple protocol provides a bidirectional audio stream on a network
+ * socket.
+ *
+ * It is meant to be used with the `simple protocol player` app, available on
+ * Android to play and record a stream.
+ *
+ * Each client that connects will create a capture and/or playback stream,
+ * depending on the configuration options.
+ *
+ * ## Module Options
+ *
+ * - `capture`: boolean if capture is enabled. This will create a capture stream
+ * for each connected client.
+ * - `playback`: boolean if playback is enabled. This will create a playback
+ * stream for each connected client.
+ * - `capture.node`: an optional node serial or name to use for capture.
+ * - `playback.node`: an optional node serial or name to use for playback.
+ * - `server.address = []`: an array of server addresses to listen on as
+ * tcp:<ip>:<port>.
+ *
+ * ## General options
+ *
+ * Options with well-known behavior.
+ *
+ * - \ref PW_KEY_REMOTE_NAME
+ * - \ref PW_KEY_AUDIO_RATE
+ * - \ref PW_KEY_AUDIO_FORMAT
+ * - \ref PW_KEY_AUDIO_CHANNELS
+ * - \ref SPA_KEY_AUDIO_POSITION
+ * - \ref PW_KEY_NODE_LATENCY
+ * - \ref PW_KEY_NODE_RATE
+ * - \ref PW_KEY_STREAM_CAPTURE_SINK
+ *
+ * By default the server will work with stereo 16 bits samples at 44.1KHz.
+ *
+ * ## Example configuration
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-protocol-simple
+ * args = {
+ * # Provide capture stream, clients can capture data from PipeWire
+ * capture = true
+ * #
+ * # Provide playback stream, client can send data to PipeWire for playback
+ * playback = true
+ * #
+ * # The node name or id to use for capture.
+ * #capture.node = null
+ * #
+ * # To make the capture stream capture the monitor ports
+ * #stream.capture.sink = false
+ * #
+ * # The node name or id to use for playback.
+ * #playback.node = null
+ * #
+ * #audio.rate = 44100
+ * #audio.format = S16
+ * #audio.channels = 2
+ * #audio.position = [ FL FR ]
+ * #
+ * # The addresses this server listens on for new
+ * # client connections
+ * server.address = [
+ * "tcp:4711"
+ * ]
+ * }
+ * }
+ * ]
+ *\endcode
+ */
+
+#define NAME "protocol-simple"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define DEFAULT_PORT 4711
+#define DEFAULT_SERVER "[ \"tcp:"SPA_STRINGIFY(DEFAULT_PORT)"\" ]"
+
+#define DEFAULT_FORMAT "S16"
+#define DEFAULT_RATE 44100
+#define DEFAULT_CHANNELS 2
+#define DEFAULT_POSITION "[ FL FR ]"
+#define DEFAULT_LATENCY "1024/44100"
+
+#define MAX_CLIENTS 10
+
+#define MODULE_USAGE "[ capture=<bool> ] " \
+ "[ playback=<bool> ] " \
+ "[ remote.name=<remote> ] " \
+ "[ node.latency=<num/denom, default:"DEFAULT_LATENCY"> ] " \
+ "[ node.rate=<1/rate, default:1/"SPA_STRINGIFY(DEFAULT_RATE)"> ] " \
+ "[ capture.node=<source-target> [ stream.capture.sink=true ]] " \
+ "[ playback.node=<sink-target> ] " \
+ "[ audio.rate=<sample-rate, default:"SPA_STRINGIFY(DEFAULT_RATE)"> ] " \
+ "[ audio.format=<format, default:"DEFAULT_FORMAT"> ] " \
+ "[ audio.channels=<channels, default: "SPA_STRINGIFY(DEFAULT_CHANNELS)"> ] " \
+ "[ audio.position=<position, default:"DEFAULT_POSITION"> ] " \
+ "[ server.address=<[ tcp:[<ip>:]<port>[,...] ], default:"DEFAULT_SERVER">" \
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Implements a simple protocol" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct impl {
+ struct pw_loop *loop;
+ struct pw_context *context;
+
+ struct pw_properties *props;
+ struct spa_hook module_listener;
+ struct spa_list server_list;
+
+ struct pw_work_queue *work_queue;
+
+ bool capture;
+ bool playback;
+
+ struct spa_audio_info_raw info;
+ uint32_t frame_size;
+};
+
+struct client {
+ struct spa_list link;
+ struct impl *impl;
+ struct server *server;
+
+ struct pw_core *core;
+ struct spa_hook core_proxy_listener;
+
+ struct spa_source *source;
+ char name[128];
+
+ struct pw_stream *capture;
+ struct spa_hook capture_listener;
+
+ struct pw_stream *playback;
+ struct spa_hook playback_listener;
+
+ unsigned int disconnect:1;
+ unsigned int disconnecting:1;
+ unsigned int cleanup:1;
+};
+
+struct server {
+ struct spa_list link;
+ struct impl *impl;
+
+#define SERVER_TYPE_INVALID 0
+#define SERVER_TYPE_UNIX 1
+#define SERVER_TYPE_INET 2
+ uint32_t type;
+ struct sockaddr_un addr;
+ struct spa_source *source;
+
+ struct spa_list client_list;
+ uint32_t n_clients;
+};
+
+static void client_disconnect(struct client *client)
+{
+ struct impl *impl = client->impl;
+
+ if (client->disconnect)
+ return;
+
+ client->disconnect = true;
+
+ if (client->source)
+ pw_loop_destroy_source(impl->loop, client->source);
+}
+
+static void client_free(struct client *client)
+{
+ struct impl *impl = client->impl;
+
+ pw_log_info("%p: client:%p [%s] free", impl, client, client->name);
+
+ client_disconnect(client);
+
+ pw_work_queue_cancel(impl->work_queue, client, SPA_ID_INVALID);
+
+ spa_list_remove(&client->link);
+ client->server->n_clients--;
+
+ if (client->capture)
+ pw_stream_destroy(client->capture);
+ if (client->playback)
+ pw_stream_destroy(client->playback);
+ if (client->core) {
+ client->disconnecting = true;
+ spa_hook_remove(&client->core_proxy_listener);
+ pw_core_disconnect(client->core);
+ }
+ free(client);
+}
+
+
+static void on_client_cleanup(void *obj, void *data, int res, uint32_t id)
+{
+ struct client *c = obj;
+ client_free(c);
+}
+
+static void client_cleanup(struct client *client)
+{
+ struct impl *impl = client->impl;
+ if (!client->cleanup) {
+ client->cleanup = true;
+ pw_work_queue_add(impl->work_queue, client, 0, on_client_cleanup, impl);
+ }
+}
+
+static void
+on_client_data(void *data, int fd, uint32_t mask)
+{
+ struct client *client = data;
+ struct impl *impl = client->impl;
+ int res;
+
+ if (mask & SPA_IO_HUP) {
+ res = -EPIPE;
+ goto error;
+ }
+ if (mask & SPA_IO_ERR) {
+ res = -EIO;
+ goto error;
+ }
+ return;
+
+error:
+ if (res == -EPIPE)
+ pw_log_info("%p: client:%p [%s] disconnected", impl, client, client->name);
+ else {
+ pw_log_error("%p: client:%p [%s] error %d (%s)", impl,
+ client, client->name, res, spa_strerror(res));
+ }
+ client_cleanup(client);
+}
+
+static void capture_process(void *data)
+{
+ struct client *client = data;
+ struct impl *impl = client->impl;
+ struct pw_buffer *buf;
+ struct spa_data *d;
+ uint32_t size, offset;
+ int res;
+
+ if ((buf = pw_stream_dequeue_buffer(client->capture)) == NULL) {
+ pw_log_debug("%p: client:%p [%s] out of capture buffers: %m", impl,
+ client, client->name);
+ return;
+ }
+ d = &buf->buffer->datas[0];
+
+ offset = SPA_MIN(d->chunk->offset, d->maxsize);
+ size = SPA_MIN(d->chunk->size, d->maxsize - offset);
+
+ while (size > 0) {
+ res = send(client->source->fd,
+ SPA_PTROFF(d->data, offset, void),
+ size,
+ MSG_NOSIGNAL | MSG_DONTWAIT);
+ if (res < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN && errno != EWOULDBLOCK)
+ pw_log_warn("%p: client:%p [%s] send error %d: %m", impl,
+ client, client->name, res);
+ client_cleanup(client);
+ break;
+ }
+ offset += res;
+ size -= res;
+ }
+ pw_stream_queue_buffer(client->capture, buf);
+}
+
+static void playback_process(void *data)
+{
+ struct client *client = data;
+ struct impl *impl = client->impl;
+ struct pw_buffer *buf;
+ uint32_t size, offset;
+ struct spa_data *d;
+ int res;
+
+ if ((buf = pw_stream_dequeue_buffer(client->playback)) == NULL) {
+ pw_log_debug("%p: client:%p [%s] out of playback buffers: %m", impl,
+ client, client->name);
+ return;
+ }
+ d = &buf->buffer->datas[0];
+
+ size = d->maxsize;
+ if (buf->requested)
+ size = SPA_MIN(size, buf->requested * impl->frame_size);
+
+ offset = 0;
+ while (size > 0) {
+ res = recv(client->source->fd,
+ SPA_PTROFF(d->data, offset, void),
+ size,
+ MSG_DONTWAIT);
+ if (res == 0) {
+ pw_log_info("%p: client:%p [%s] disconnect", impl,
+ client, client->name);
+ client_cleanup(client);
+ break;
+ }
+ if (res < 0) {
+ if (errno == EINTR)
+ continue;
+ if (errno != EAGAIN && errno != EWOULDBLOCK)
+ pw_log_warn("%p: client:%p [%s] recv error %d: %m",
+ impl, client, client->name, res);
+ break;
+ }
+ offset += res;
+ size -= res;
+ }
+ d->chunk->offset = 0;
+ d->chunk->size = offset;
+ d->chunk->stride = impl->frame_size;
+
+ pw_stream_queue_buffer(client->playback, buf);
+}
+
+static void capture_destroy(void *data)
+{
+ struct client *client = data;
+ spa_hook_remove(&client->capture_listener);
+ client->capture = NULL;
+}
+
+static void on_stream_state_changed(void *data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct client *client = data;
+ struct impl *impl = client->impl;
+
+ switch (state) {
+ case PW_STREAM_STATE_ERROR:
+ case PW_STREAM_STATE_UNCONNECTED:
+ if (!client->disconnect) {
+ pw_log_info("%p: client:%p [%s] stream error %s",
+ impl, client, client->name,
+ pw_stream_state_as_string(state));
+ client_cleanup(client);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void playback_destroy(void *data)
+{
+ struct client *client = data;
+ spa_hook_remove(&client->playback_listener);
+ client->playback = NULL;
+}
+
+static const struct pw_stream_events capture_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = capture_destroy,
+ .state_changed = on_stream_state_changed,
+ .process = capture_process
+};
+
+static const struct pw_stream_events playback_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = playback_destroy,
+ .state_changed = on_stream_state_changed,
+ .process = playback_process
+};
+
+static int create_streams(struct impl *impl, struct client *client)
+{
+ uint32_t n_params;
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ struct pw_properties *props;
+ const char *latency;
+ int res;
+
+ if ((latency = pw_properties_get(impl->props, PW_KEY_NODE_LATENCY)) == NULL)
+ latency = DEFAULT_LATENCY;
+
+ if (impl->capture) {
+ props = pw_properties_new(
+ PW_KEY_NODE_LATENCY, latency,
+ PW_KEY_NODE_RATE, pw_properties_get(impl->props, PW_KEY_NODE_RATE),
+ PW_KEY_TARGET_OBJECT, pw_properties_get(impl->props, "capture.node"),
+ PW_KEY_STREAM_CAPTURE_SINK, pw_properties_get(impl->props,
+ PW_KEY_STREAM_CAPTURE_SINK),
+ PW_KEY_NODE_NETWORK, "true",
+ NULL);
+ if (props == NULL)
+ return -errno;
+
+ pw_properties_setf(props,
+ PW_KEY_MEDIA_NAME, "%s capture", client->name);
+ client->capture = pw_stream_new(client->core,
+ pw_properties_get(props, PW_KEY_MEDIA_NAME),
+ props);
+ if (client->capture == NULL)
+ return -errno;
+
+ pw_stream_add_listener(client->capture, &client->capture_listener,
+ &capture_stream_events, client);
+ }
+ if (impl->playback) {
+ props = pw_properties_new(
+ PW_KEY_NODE_LATENCY, latency,
+ PW_KEY_NODE_RATE, pw_properties_get(impl->props, PW_KEY_NODE_RATE),
+ PW_KEY_TARGET_OBJECT, pw_properties_get(impl->props, "playback.node"),
+ PW_KEY_NODE_NETWORK, "true",
+ NULL);
+ if (props == NULL)
+ return -errno;
+
+ pw_properties_setf(props,
+ PW_KEY_MEDIA_NAME, "%s playback", client->name);
+
+ client->playback = pw_stream_new(client->core,
+ pw_properties_get(props, PW_KEY_MEDIA_NAME),
+ props);
+ if (client->playback == NULL)
+ return -errno;
+
+ pw_stream_add_listener(client->playback, &client->playback_listener,
+ &playback_stream_events, client);
+ }
+
+ n_params = 0;
+ params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
+ &impl->info);
+
+ if (impl->capture) {
+ if ((res = pw_stream_connect(client->capture,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0)
+ return res;
+ }
+ if (impl->playback) {
+ if ((res = pw_stream_connect(client->playback,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0)
+ return res;
+ }
+ return 0;
+}
+
+static void on_core_proxy_destroy(void *data)
+{
+ struct client *client = data;
+ spa_hook_remove(&client->core_proxy_listener);
+ client->core = NULL;
+ client_cleanup(client);
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ PW_VERSION_CORE_EVENTS,
+ .destroy = on_core_proxy_destroy,
+};
+
+static void
+on_connect(void *data, int fd, uint32_t mask)
+{
+ struct server *server = data;
+ struct impl *impl = server->impl;
+ struct sockaddr_in addr;
+ socklen_t addrlen;
+ int client_fd, val;
+ struct client *client = NULL;
+ struct pw_properties *props = NULL;
+
+ addrlen = sizeof(addr);
+ client_fd = accept4(fd, &addr, &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
+ if (client_fd < 0)
+ goto error;
+
+ if (server->n_clients >= MAX_CLIENTS) {
+ close(client_fd);
+ errno = ECONNREFUSED;
+ goto error;
+ }
+
+ client = calloc(1, sizeof(struct client));
+ if (client == NULL)
+ goto error;
+
+ client->impl = impl;
+ client->server = server;
+ spa_list_append(&server->client_list, &client->link);
+ server->n_clients++;
+
+ if (inet_ntop(addr.sin_family, &addr.sin_addr.s_addr, client->name, sizeof(client->name)) == NULL)
+ snprintf(client->name, sizeof(client->name), "client %d", client_fd);
+
+ client->source = pw_loop_add_io(impl->loop,
+ client_fd,
+ SPA_IO_ERR | SPA_IO_HUP,
+ true, on_client_data, client);
+ if (client->source == NULL)
+ goto error;
+
+ pw_log_info("%p: client:%p [%s] connected", impl, client, client->name);
+
+ props = pw_properties_new(
+ PW_KEY_CLIENT_API, "protocol-simple",
+ PW_KEY_REMOTE_NAME,
+ pw_properties_get(impl->props, PW_KEY_REMOTE_NAME),
+ NULL);
+ if (props == NULL)
+ goto error;
+
+ pw_properties_setf(props,
+ "protocol.server.type", "%s",
+ server->type == SERVER_TYPE_INET ? "tcp" : "unix");
+
+ if (server->type == SERVER_TYPE_UNIX) {
+ goto error;
+ } else if (server->type == SERVER_TYPE_INET) {
+ val = 1;
+ if (setsockopt(client_fd, IPPROTO_TCP, TCP_NODELAY,
+ (const void *) &val, sizeof(val)) < 0)
+ pw_log_warn("TCP_NODELAY failed: %m");
+
+ val = IPTOS_LOWDELAY;
+ if (setsockopt(client_fd, IPPROTO_IP, IP_TOS,
+ (const void *) &val, sizeof(val)) < 0)
+ pw_log_warn("IP_TOS failed: %m");
+
+ pw_properties_set(props, PW_KEY_CLIENT_ACCESS, "restricted");
+ }
+
+ client->core = pw_context_connect(impl->context, props, 0);
+ props = NULL;
+ if (client->core == NULL)
+ goto error;
+
+ pw_proxy_add_listener((struct pw_proxy*)client->core,
+ &client->core_proxy_listener, &core_proxy_events,
+ client);
+
+ create_streams(impl, client);
+
+ return;
+error:
+ pw_log_error("%p: failed to create client: %m", impl);
+ pw_properties_free(props);
+ if (client != NULL)
+ client_free(client);
+ return;
+}
+
+static int make_inet_socket(struct server *server, const char *name)
+{
+ struct sockaddr_in addr;
+ int res, fd, on;
+ uint32_t address = INADDR_ANY;
+ uint16_t port;
+ char *col;
+
+ col = strchr(name, ':');
+ if (col) {
+ struct in_addr ipv4;
+ char *n;
+ port = atoi(col+1);
+ n = strndupa(name, col - name);
+ if (inet_pton(AF_INET, n, &ipv4) > 0)
+ address = ntohl(ipv4.s_addr);
+ else
+ address = INADDR_ANY;
+ } else {
+ address = INADDR_ANY;
+ port = atoi(name);
+ }
+ if (port == 0)
+ port = DEFAULT_PORT;
+
+ if ((fd = socket(PF_INET, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
+ res = -errno;
+ pw_log_error("%p: socket() failed: %m", server);
+ goto error;
+ }
+
+ on = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *) &on, sizeof(on)) < 0)
+ pw_log_warn("%p: setsockopt(): %m", server);
+
+ spa_zero(addr);
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ addr.sin_addr.s_addr = htonl(address);
+
+ if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
+ res = -errno;
+ pw_log_error("%p: bind() failed: %m", server);
+ goto error_close;
+ }
+ if (listen(fd, 5) < 0) {
+ res = -errno;
+ pw_log_error("%p: listen() failed: %m", server);
+ goto error_close;
+ }
+ server->type = SERVER_TYPE_INET;
+ pw_log_info("listening on tcp:%08x:%u", address, port);
+
+ return fd;
+
+error_close:
+ close(fd);
+error:
+ return res;
+}
+
+static void server_free(struct server *server)
+{
+ struct impl *impl = server->impl;
+ struct client *c;
+
+ pw_log_debug("%p: free server %p", impl, server);
+
+ spa_list_remove(&server->link);
+ spa_list_consume(c, &server->client_list, link)
+ client_free(c);
+ if (server->source)
+ pw_loop_destroy_source(impl->loop, server->source);
+ free(server);
+}
+
+static struct server *create_server(struct impl *impl, const char *address)
+{
+ int fd, res;
+ struct server *server;
+
+ server = calloc(1, sizeof(struct server));
+ if (server == NULL)
+ return NULL;
+
+ server->impl = impl;
+ spa_list_init(&server->client_list);
+ spa_list_append(&impl->server_list, &server->link);
+
+ if (spa_strstartswith(address, "tcp:")) {
+ fd = make_inet_socket(server, address+4);
+ } else {
+ fd = -EINVAL;
+ }
+ if (fd < 0) {
+ res = fd;
+ goto error;
+ }
+ server->source = pw_loop_add_io(impl->loop, fd, SPA_IO_IN, true, on_connect, server);
+ if (server->source == NULL) {
+ res = -errno;
+ pw_log_error("%p: can't create server source: %m", impl);
+ goto error_close;
+ }
+ return server;
+
+error_close:
+ close(fd);
+error:
+ server_free(server);
+ errno = -res;
+ return NULL;
+}
+
+static void impl_free(struct impl *impl)
+{
+ struct server *s;
+
+ spa_hook_remove(&impl->module_listener);
+ spa_list_consume(s, &impl->server_list, link)
+ server_free(s);
+ pw_properties_free(impl->props);
+ 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 inline uint32_t parse_position(uint32_t *pos, const char *val, size_t len)
+{
+ uint32_t channels = 0;
+ 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);
+
+ while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 &&
+ channels < SPA_AUDIO_MAX_CHANNELS) {
+ pos[channels++] = channel_from_name(v);
+ }
+ return channels;
+}
+
+static int parse_params(struct impl *impl)
+{
+ const char *str;
+ struct spa_json it[2];
+ char value[512];
+
+ pw_properties_fetch_bool(impl->props, "capture", &impl->capture);
+ pw_properties_fetch_bool(impl->props, "playback", &impl->playback);
+ if (!impl->playback && !impl->capture) {
+ pw_log_error("missing capture or playback param");
+ return -EINVAL;
+ }
+
+ if ((str = pw_properties_get(impl->props, "audio.format")) == NULL)
+ str = DEFAULT_FORMAT;
+ impl->info.format = format_from_name(str, strlen(str));
+ if (impl->info.format == SPA_AUDIO_FORMAT_UNKNOWN) {
+ pw_log_error("invalid format '%s'", str);
+ return -EINVAL;
+ }
+ impl->info.rate = pw_properties_get_uint32(impl->props, "audio.rate", DEFAULT_RATE);
+ if (impl->info.rate == 0) {
+ pw_log_error("invalid rate '%s'", str);
+ return -EINVAL;
+ }
+ impl->info.channels = pw_properties_get_uint32(impl->props, "audio.channels", DEFAULT_CHANNELS);
+ if (impl->info.channels == 0) {
+ pw_log_error("invalid channels '%s'", str);
+ return -EINVAL;
+ }
+ if ((str = pw_properties_get(impl->props, "audio.position")) == NULL)
+ str = DEFAULT_POSITION;
+ if (parse_position(impl->info.position, str, strlen(str)) != impl->info.channels) {
+ pw_log_error("invalid position '%s'", str);
+ return -EINVAL;
+ }
+
+ switch (impl->info.format) {
+ case SPA_AUDIO_FORMAT_U8:
+ impl->frame_size = 1;
+ break;
+ case SPA_AUDIO_FORMAT_S16_LE:
+ case SPA_AUDIO_FORMAT_S16_BE:
+ case SPA_AUDIO_FORMAT_S16P:
+ impl->frame_size = 2;
+ break;
+ case SPA_AUDIO_FORMAT_S24_LE:
+ case SPA_AUDIO_FORMAT_S24_BE:
+ case SPA_AUDIO_FORMAT_S24P:
+ impl->frame_size = 3;
+ break;
+ default:
+ impl->frame_size = 4;
+ break;
+ }
+ impl->frame_size *= impl->info.channels;
+
+ 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 (create_server(impl, value) == NULL) {
+ pw_log_warn("%p: can't create server for %s: %m",
+ impl, value);
+ }
+ }
+ }
+ return 0;
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ pw_log_debug("module %p: destroy", impl);
+ impl_free(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props;
+ struct impl *impl;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args)
+ props = pw_properties_new_string(args);
+ else
+ props = pw_properties_new(NULL, NULL);
+
+ impl->context = context;
+ impl->loop = pw_context_get_main_loop(context);
+ impl->props = props;
+ spa_list_init(&impl->server_list);
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ impl->work_queue = pw_context_get_work_queue(context);
+
+ if ((res = parse_params(impl)) < 0)
+ goto error_free;
+
+ return 0;
+
+error_free:
+ impl_free(impl);
+ return res;
+}
diff --git a/src/modules/module-pulse-tunnel.c b/src/modules/module-pulse-tunnel.c
new file mode 100644
index 0000000..e2fe8b6
--- /dev/null
+++ b/src/modules/module-pulse-tunnel.c
@@ -0,0 +1,1080 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.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/utils/ringbuffer.h>
+#include <spa/utils/dll.h>
+#include <spa/debug/types.h>
+#include <spa/pod/builder.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/latency-utils.h>
+#include <spa/param/audio/raw.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/i18n.h>
+#include <pipewire/private.h>
+
+#include <pulse/pulseaudio.h>
+#include "module-protocol-pulse/format.h"
+
+/** \page page_module_pulse_tunnel PipeWire Module: Pulse Tunnel
+ *
+ * The pulse-tunnel module provides a source or sink that tunnels all audio to
+ * a remote PulseAudio connection.
+ *
+ * It is usually used with the PulseAudio or module-protocol-pulse on the remote
+ * end to accept the connection.
+ *
+ * This module is usually used together with module-zeroconf-discover that will
+ * automatically load the tunnel with the right parameters based on zeroconf
+ * information.
+ *
+ * ## Module Options
+ *
+ * - `tunnel.mode`: the desired tunnel to create, must be `source` or `sink`.
+ * (Default `sink`)
+ * - `pulse.server.address`: the address of the PulseAudio server to tunnel to.
+ * - `pulse.latency`: the latency to end-to-end latency in milliseconds to
+ * maintain (Default 200).
+ * - `stream.props`: Extra properties for the local stream.
+ *
+ * ## 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_NODE_LATENCY
+ * - \ref PW_KEY_NODE_NAME
+ * - \ref PW_KEY_NODE_DESCRIPTION
+ * - \ref PW_KEY_NODE_GROUP
+ * - \ref PW_KEY_NODE_VIRTUAL
+ * - \ref PW_KEY_MEDIA_CLASS
+ * - \ref PW_KEY_TARGET_OBJECT to specify the remote node.name or serial.id to link to
+ *
+ * ## Example configuration of a virtual sink
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-pulse-tunnel
+ * args = {
+ * tunnel.mode = sink
+ * # Set the remote address to tunnel to
+ * pulse.server.address = "tcp:192.168.1.126"
+ * #pulse.latency = 200
+ * #audio.rate=<sample rate>
+ * #audio.channels=<number of channels>
+ * #audio.position=<channel map>
+ * #target.object=<remote target name>
+ * stream.props = {
+ * # extra sink properties
+ * }
+ * }
+ * }
+ * ]
+ *\endcode
+ */
+
+#define NAME "pulse-tunnel"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define DEFAULT_FORMAT "S16"
+#define DEFAULT_RATE 48000
+#define DEFAULT_CHANNELS 2
+#define DEFAULT_POSITION "[ FL FR ]"
+
+#define MODULE_USAGE "[ remote.name=<remote> ] " \
+ "[ node.latency=<latency as fraction> ] " \
+ "[ node.name=<name of the nodes> ] " \
+ "[ node.description=<description of the nodes> ] " \
+ "[ node.target=<remote node target name or serial> ] " \
+ "[ audio.format=<sample format> ] " \
+ "[ audio.rate=<sample rate> ] " \
+ "[ audio.channels=<number of channels> ] " \
+ "[ audio.position=<channel map> ] " \
+ "pulse.server.address=<address> " \
+ "pulse.latency=<latency in msec> " \
+ "[ tunnel.mode=source|sink " \
+ "[ stream.props=<properties> ] "
+
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Create a PulseAudio tunnel" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+#define RINGBUFFER_SIZE (1u << 22)
+#define RINGBUFFER_MASK (RINGBUFFER_SIZE-1)
+
+#define DEFAULT_LATENCY_MSEC (200)
+
+struct impl {
+ struct pw_context *context;
+
+#define MODE_SINK 0
+#define MODE_SOURCE 1
+ uint32_t mode;
+ struct pw_properties *props;
+
+ struct pw_impl_module *module;
+
+ struct spa_hook module_listener;
+
+ struct pw_core *core;
+ struct spa_hook core_proxy_listener;
+ struct spa_hook core_listener;
+
+ uint32_t latency_msec;
+
+ struct pw_properties *stream_props;
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+ struct spa_audio_info_raw info;
+ uint32_t frame_size;
+
+ struct spa_ringbuffer ring;
+ void *buffer;
+ uint8_t empty[8192];
+
+ pa_threaded_mainloop *pa_mainloop;
+ pa_context *pa_context;
+ pa_stream *pa_stream;
+
+ struct ratelimit rate_limit;
+
+ uint32_t target_latency;
+ uint32_t current_latency;
+ uint32_t target_buffer;
+ struct spa_io_rate_match *rate_match;
+ struct spa_dll dll;
+ float max_error;
+ unsigned resync:1;
+
+ unsigned int do_disconnect:1;
+};
+
+static void cork_stream(struct impl *impl, bool cork)
+{
+ pa_operation *operation;
+
+ pa_threaded_mainloop_lock(impl->pa_mainloop);
+
+ pw_log_debug("corking: %d", cork);
+ if (cork && impl->mode == MODE_SINK) {
+ /* When the sink becomes suspended (which is the only case where we
+ * cork the stream), we don't want to keep any old data around, because
+ * the old data is most likely unrelated to the audio that will be
+ * played at the time when the sink starts running again. */
+ if ((operation = pa_stream_flush(impl->pa_stream, NULL, NULL)))
+ pa_operation_unref(operation);
+
+ spa_ringbuffer_init(&impl->ring);
+ memset(impl->buffer, 0, RINGBUFFER_SIZE);
+ }
+ if (!cork)
+ impl->resync = true;
+
+ if ((operation = pa_stream_cork(impl->pa_stream, cork, NULL, NULL)))
+ pa_operation_unref(operation);
+
+ pa_threaded_mainloop_unlock(impl->pa_mainloop);
+}
+
+static void stream_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->stream_listener);
+ impl->stream = NULL;
+}
+
+static void stream_state_changed(void *d, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct impl *impl = d;
+ switch (state) {
+ case PW_STREAM_STATE_ERROR:
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_impl_module_schedule_destroy(impl->module);
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ cork_stream(impl, true);
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ cork_stream(impl, false);
+ break;
+ default:
+ break;
+ }
+}
+
+static void update_rate(struct impl *impl, bool playback)
+{
+ float error, corr;
+
+ if (impl->rate_match == NULL)
+ return;
+
+ if (playback)
+ error = (float)impl->target_latency - (float)impl->current_latency;
+ else
+ error = (float)impl->current_latency - (float)impl->target_latency;
+ error = SPA_CLAMP(error, -impl->max_error, impl->max_error);
+
+ corr = spa_dll_update(&impl->dll, error);
+ pw_log_debug("error:%f corr:%f current:%u target:%u",
+ error, corr,
+ impl->current_latency, impl->target_latency);
+
+ SPA_FLAG_SET(impl->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE);
+ impl->rate_match->rate = 1.0f / corr;
+}
+
+static void playback_stream_process(void *d)
+{
+ struct impl *impl = d;
+ struct pw_buffer *buf;
+ struct spa_data *bd;
+ int32_t filled;
+ uint32_t write_index, offs, size;
+
+ if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) {
+ pw_log_debug("out of buffers: %m");
+ return;
+ }
+
+ bd = &buf->buffer->datas[0];
+ offs = SPA_MIN(bd->chunk->offset, bd->maxsize);
+ size = SPA_MIN(bd->chunk->size, bd->maxsize - offs);
+ size = SPA_MIN(size, RINGBUFFER_SIZE);
+
+ filled = spa_ringbuffer_get_write_index(&impl->ring, &write_index);
+
+ if (filled < 0) {
+ pw_log_warn("%p: underrun write:%u filled:%d",
+ impl, write_index, filled);
+ } else if ((uint32_t)filled + size > RINGBUFFER_SIZE) {
+ pw_log_warn("%p: overrun write:%u filled:%d + size:%u > max:%u",
+ impl, write_index, filled,
+ size, RINGBUFFER_SIZE);
+ impl->resync = true;
+ } else {
+ update_rate(impl, true);
+ }
+ spa_ringbuffer_write_data(&impl->ring,
+ impl->buffer, RINGBUFFER_SIZE,
+ write_index & RINGBUFFER_MASK,
+ SPA_PTROFF(bd->data, offs, void),
+ size);
+ write_index += size;
+ spa_ringbuffer_write_update(&impl->ring, write_index);
+
+ pw_stream_queue_buffer(impl->stream, buf);
+}
+
+static void capture_stream_process(void *d)
+{
+ struct impl *impl = d;
+ struct pw_buffer *buf;
+ struct spa_data *bd;
+ int32_t avail;
+ uint32_t size, req, index;
+
+ if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) {
+ pw_log_debug("out of buffers: %m");
+ return;
+ }
+
+ bd = &buf->buffer->datas[0];
+
+ if ((req = buf->requested * impl->frame_size) == 0)
+ req = 4096 * impl->frame_size;
+
+ size = SPA_MIN(bd->maxsize, req);
+
+ avail = spa_ringbuffer_get_read_index(&impl->ring, &index);
+ if (avail < (int32_t)size) {
+ memset(bd->data, 0, size);
+ } else {
+ if (avail > (int32_t)RINGBUFFER_SIZE) {
+ avail = impl->target_buffer;
+ index += avail - impl->target_buffer;
+ } else {
+ update_rate(impl, false);
+ }
+ spa_ringbuffer_read_data(&impl->ring,
+ impl->buffer, RINGBUFFER_SIZE,
+ index & RINGBUFFER_MASK,
+ bd->data, size);
+
+ index += size;
+ spa_ringbuffer_read_update(&impl->ring, index);
+ }
+ bd->chunk->offset = 0;
+ bd->chunk->size = size;
+ bd->chunk->stride = impl->frame_size;
+
+ pw_stream_queue_buffer(impl->stream, buf);
+}
+
+static void stream_io_changed(void *data, uint32_t id, void *area, uint32_t size)
+{
+ struct impl *impl = data;
+ switch (id) {
+ case SPA_IO_RateMatch:
+ impl->rate_match = area;
+ break;
+ }
+}
+
+static const struct pw_stream_events playback_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = stream_destroy,
+ .state_changed = stream_state_changed,
+ .io_changed = stream_io_changed,
+ .process = playback_stream_process
+};
+
+static const struct pw_stream_events capture_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = stream_destroy,
+ .state_changed = stream_state_changed,
+ .io_changed = stream_io_changed,
+ .process = capture_stream_process
+};
+
+static int create_stream(struct impl *impl)
+{
+ int res;
+ uint32_t n_params;
+ const struct spa_pod *params[2];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b;
+ struct spa_latency_info latency;
+
+ impl->stream = pw_stream_new(impl->core, "pulse", impl->stream_props);
+ impl->stream_props = NULL;
+
+ if (impl->stream == NULL)
+ return -errno;
+
+ if (impl->mode == MODE_SOURCE) {
+ pw_stream_add_listener(impl->stream,
+ &impl->stream_listener,
+ &capture_stream_events, impl);
+ } else {
+ pw_stream_add_listener(impl->stream,
+ &impl->stream_listener,
+ &playback_stream_events, impl);
+ }
+
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_format_audio_raw_build(&b,
+ SPA_PARAM_EnumFormat, &impl->info);
+
+ spa_zero(latency);
+ latency.direction = impl->mode == MODE_SOURCE ? PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT;
+ latency.min_ns = latency.max_ns = impl->latency_msec * SPA_NSEC_PER_MSEC;
+
+ params[n_params++] = spa_latency_build(&b,
+ SPA_PARAM_Latency, &latency);
+
+ if ((res = pw_stream_connect(impl->stream,
+ impl->mode == MODE_SOURCE ? PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0)
+ return res;
+
+ return 0;
+}
+
+static void context_state_cb(pa_context *c, void *userdata)
+{
+ struct impl *impl = userdata;
+ bool do_destroy = false;
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ do_destroy = true;
+ SPA_FALLTHROUGH;
+ case PA_CONTEXT_READY:
+ pa_threaded_mainloop_signal(impl->pa_mainloop, 0);
+ break;
+ case PA_CONTEXT_UNCONNECTED:
+ do_destroy = true;
+ break;
+ case PA_CONTEXT_CONNECTING:
+ case PA_CONTEXT_AUTHORIZING:
+ case PA_CONTEXT_SETTING_NAME:
+ break;
+ }
+ if (do_destroy)
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static void stream_state_cb(pa_stream *s, void * userdata)
+{
+ struct impl *impl = userdata;
+ bool do_destroy = false;
+ switch (pa_stream_get_state(s)) {
+ case PA_STREAM_FAILED:
+ case PA_STREAM_TERMINATED:
+ do_destroy = true;
+ SPA_FALLTHROUGH;
+ case PA_STREAM_READY:
+ pa_threaded_mainloop_signal(impl->pa_mainloop, 0);
+ break;
+ case PA_STREAM_UNCONNECTED:
+ do_destroy = true;
+ break;
+ case PA_STREAM_CREATING:
+ break;
+ }
+ if (do_destroy)
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static void stream_read_request_cb(pa_stream *s, size_t length, void *userdata)
+{
+ struct impl *impl = userdata;
+ int32_t filled;
+ uint32_t index;
+ pa_usec_t latency;
+ int negative;
+
+ filled = spa_ringbuffer_get_write_index(&impl->ring, &index);
+
+ if (filled < 0) {
+ pw_log_warn("%p: underrun write:%u filled:%d",
+ impl, index, filled);
+ } else if (filled + length > RINGBUFFER_SIZE) {
+ pw_log_warn("%p: overrun write:%u filled:%d",
+ impl, index, filled);
+ }
+ while (length > 0) {
+ const void *p;
+ size_t nbytes = 0;
+
+ if (SPA_UNLIKELY(pa_stream_peek(impl->pa_stream, &p, &nbytes) != 0)) {
+ pw_log_error("pa_stream_peek() failed: %s",
+ pa_strerror(pa_context_errno(impl->pa_context)));
+ return;
+ }
+ pw_log_debug("read %zd nbytes:%zd", length, nbytes);
+
+ if (length < nbytes)
+ break;
+
+ while (nbytes > 0) {
+ uint32_t to_write = SPA_MIN(nbytes, sizeof(impl->empty));
+
+ spa_ringbuffer_write_data(&impl->ring,
+ impl->buffer, RINGBUFFER_SIZE,
+ index & RINGBUFFER_MASK,
+ p ? p : impl->empty, to_write);
+
+ index += to_write;
+ p = p ? SPA_PTROFF(p, to_write, void) : NULL;
+ nbytes -= to_write;
+ length -= to_write;
+ filled += to_write;
+ }
+ pa_stream_drop(impl->pa_stream);
+ }
+
+ pa_stream_get_latency(impl->pa_stream, &latency, &negative);
+ impl->current_latency = latency * impl->info.rate / SPA_USEC_PER_SEC;
+ impl->current_latency += filled / impl->frame_size;
+
+ spa_ringbuffer_write_update(&impl->ring, index);
+}
+
+static void stream_write_request_cb(pa_stream *s, size_t length, void *userdata)
+{
+ struct impl *impl = userdata;
+ int32_t avail;
+ uint32_t index;
+ size_t size;
+ pa_usec_t latency;
+ int negative, res;
+
+ if (impl->resync) {
+ impl->resync = false;
+ avail = length + impl->target_buffer;
+ spa_ringbuffer_get_write_index(&impl->ring, &index);
+ index -= avail;
+ } else {
+ avail = spa_ringbuffer_get_read_index(&impl->ring, &index);
+ }
+
+ pa_stream_get_latency(impl->pa_stream, &latency, &negative);
+ impl->current_latency = latency * impl->info.rate / SPA_USEC_PER_SEC;
+ impl->current_latency += avail / impl->frame_size;
+
+ while (avail < (int32_t)length) {
+ uint32_t maxsize = SPA_ROUND_DOWN(sizeof(impl->empty), impl->frame_size);
+ /* send silence for the data we don't have */
+ size = SPA_MIN(length - avail, maxsize);
+ if ((res = pa_stream_write(impl->pa_stream,
+ impl->empty, size,
+ NULL, 0, PA_SEEK_RELATIVE)) != 0)
+ pw_log_warn("error writing stream: %s", pa_strerror(res));
+ length -= size;
+ }
+ while (length > 0 && avail >= (int32_t)length) {
+ void *data;
+
+ size = length;
+ pa_stream_begin_write(impl->pa_stream, &data, &size);
+
+ spa_ringbuffer_read_data(&impl->ring,
+ impl->buffer, RINGBUFFER_SIZE,
+ index & RINGBUFFER_MASK,
+ data, size);
+
+ if ((res = pa_stream_write(impl->pa_stream,
+ data, size, NULL, 0, PA_SEEK_RELATIVE)) != 0)
+ pw_log_warn("error writing stream: %zd %s", size,
+ pa_strerror(res));
+
+ index += size;
+ length -= size;
+ avail -= size;
+ spa_ringbuffer_read_update(&impl->ring, index);
+ }
+}
+static void stream_underflow_cb(pa_stream *s, void *userdata)
+{
+ struct impl *impl = userdata;
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ if (ratelimit_test(&impl->rate_limit, SPA_TIMESPEC_TO_NSEC(&ts), SPA_LOG_LEVEL_WARN))
+ pw_log_warn("underflow");
+ impl->resync = true;
+}
+static void stream_overflow_cb(pa_stream *s, void *userdata)
+{
+ struct impl *impl = userdata;
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ if (ratelimit_test(&impl->rate_limit, SPA_TIMESPEC_TO_NSEC(&ts), SPA_LOG_LEVEL_WARN))
+ pw_log_warn("overflow");
+ impl->resync = true;
+}
+
+static void stream_latency_update_cb(pa_stream *s, void *userdata)
+{
+ struct impl *impl = userdata;
+ pa_usec_t usec;
+ int negative;
+
+ pa_stream_get_latency(s, &usec, &negative);
+
+ pw_log_debug("latency %ld negative %d", usec, negative);
+ pa_threaded_mainloop_signal(impl->pa_mainloop, 0);
+}
+
+static pa_proplist* tunnel_new_proplist(struct impl *impl)
+{
+ pa_proplist *proplist = pa_proplist_new();
+ pa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "PipeWire");
+ pa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "org.pipewire.PipeWire");
+ pa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, PACKAGE_VERSION);
+ return proplist;
+}
+
+static int create_pulse_stream(struct impl *impl)
+{
+ pa_sample_spec ss;
+ pa_channel_map map;
+ const char *server_address, *remote_node_target;
+ pa_proplist *props = NULL;
+ pa_mainloop_api *api;
+ char stream_name[1024];
+ pa_buffer_attr bufferattr;
+ int res = -EIO;
+ uint32_t latency_bytes, i, aux = 0;
+
+ if ((impl->pa_mainloop = pa_threaded_mainloop_new()) == NULL)
+ goto error;
+
+ api = pa_threaded_mainloop_get_api(impl->pa_mainloop);
+
+ props = tunnel_new_proplist(impl);
+ impl->pa_context = pa_context_new_with_proplist(api, "PipeWire", props);
+ pa_proplist_free(props);
+
+ if (impl->pa_context == NULL)
+ goto error;
+
+ pa_context_set_state_callback(impl->pa_context, context_state_cb, impl);
+
+ server_address = pw_properties_get(impl->props, "pulse.server.address");
+
+ if (pa_context_connect(impl->pa_context, server_address, 0, NULL) < 0) {
+ res = pa_context_errno(impl->pa_context);
+ goto error;
+ }
+
+ pa_threaded_mainloop_lock(impl->pa_mainloop);
+
+ if (pa_threaded_mainloop_start(impl->pa_mainloop) < 0)
+ goto error_unlock;
+
+ for (;;) {
+ pa_context_state_t state;
+
+ state = pa_context_get_state(impl->pa_context);
+ if (state == PA_CONTEXT_READY)
+ break;
+
+ if (!PA_CONTEXT_IS_GOOD(state)) {
+ res = pa_context_errno(impl->pa_context);
+ goto error_unlock;
+ }
+ /* Wait until the context is ready */
+ pa_threaded_mainloop_wait(impl->pa_mainloop);
+ }
+
+ ss.format = (pa_sample_format_t) format_id2pa(impl->info.format);
+ ss.channels = impl->info.channels;
+ ss.rate = impl->info.rate;
+
+ map.channels = impl->info.channels;
+ for (i = 0; i < map.channels; i++)
+ map.map[i] = (pa_channel_position_t)channel_id2pa(impl->info.position[i], &aux);
+
+ snprintf(stream_name, sizeof(stream_name), _("Tunnel for %s@%s"),
+ pw_get_user_name(), pw_get_host_name());
+
+ if (!(impl->pa_stream = pa_stream_new(impl->pa_context, stream_name, &ss, &map))) {
+ res = pa_context_errno(impl->pa_context);
+ goto error_unlock;
+ }
+
+ pa_stream_set_state_callback(impl->pa_stream, stream_state_cb, impl);
+ pa_stream_set_read_callback(impl->pa_stream, stream_read_request_cb, impl);
+ pa_stream_set_write_callback(impl->pa_stream, stream_write_request_cb, impl);
+ pa_stream_set_underflow_callback(impl->pa_stream, stream_underflow_cb, impl);
+ pa_stream_set_overflow_callback(impl->pa_stream, stream_overflow_cb, impl);
+ pa_stream_set_latency_update_callback(impl->pa_stream, stream_latency_update_cb, impl);
+
+ remote_node_target = pw_properties_get(impl->props, PW_KEY_TARGET_OBJECT);
+
+ bufferattr.fragsize = (uint32_t) -1;
+ bufferattr.minreq = (uint32_t) -1;
+ bufferattr.maxlength = (uint32_t) -1;
+ bufferattr.prebuf = (uint32_t) -1;
+
+ latency_bytes = pa_usec_to_bytes(impl->latency_msec * SPA_USEC_PER_MSEC, &ss);
+
+ impl->target_latency = latency_bytes / impl->frame_size;
+
+ /* half in our buffer, half in the network + remote */
+ impl->target_buffer = latency_bytes / 2;
+
+ if (impl->mode == MODE_SOURCE) {
+ bufferattr.fragsize = latency_bytes / 2;
+
+ res = pa_stream_connect_record(impl->pa_stream,
+ remote_node_target, &bufferattr,
+ PA_STREAM_DONT_MOVE |
+ PA_STREAM_INTERPOLATE_TIMING |
+ PA_STREAM_ADJUST_LATENCY |
+ PA_STREAM_AUTO_TIMING_UPDATE);
+ } else {
+ bufferattr.tlength = latency_bytes / 2;
+ bufferattr.minreq = bufferattr.tlength / 4;
+ bufferattr.prebuf = bufferattr.tlength;
+
+ res = pa_stream_connect_playback(impl->pa_stream,
+ remote_node_target, &bufferattr,
+ PA_STREAM_DONT_MOVE |
+ PA_STREAM_INTERPOLATE_TIMING |
+ PA_STREAM_ADJUST_LATENCY |
+ PA_STREAM_AUTO_TIMING_UPDATE,
+ NULL, NULL);
+ }
+
+ if (res < 0) {
+ res = pa_context_errno(impl->pa_context);
+ goto error_unlock;
+ }
+
+ for (;;) {
+ pa_stream_state_t state;
+
+ state = pa_stream_get_state(impl->pa_stream);
+ if (state == PA_STREAM_READY)
+ break;
+
+ if (!PA_STREAM_IS_GOOD(state)) {
+ res = pa_context_errno(impl->pa_context);
+ goto error_unlock;
+ }
+
+ /* Wait until the stream is ready */
+ pa_threaded_mainloop_wait(impl->pa_mainloop);
+ }
+
+ pa_threaded_mainloop_unlock(impl->pa_mainloop);
+
+ return 0;
+
+error_unlock:
+ pa_threaded_mainloop_unlock(impl->pa_mainloop);
+error:
+ pw_log_error("failed to connect: %s", pa_strerror(res));
+ return -res;
+}
+
+
+static void core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct impl *impl = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = core_error,
+};
+
+static void core_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->core_listener);
+ impl->core = NULL;
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ .destroy = core_destroy,
+};
+
+static void impl_destroy(struct impl *impl)
+{
+
+ if (impl->pa_mainloop)
+ pa_threaded_mainloop_stop(impl->pa_mainloop);
+ if (impl->pa_stream)
+ pa_stream_unref(impl->pa_stream);
+ if (impl->pa_context) {
+ pa_context_disconnect(impl->pa_context);
+ pa_context_unref(impl->pa_context);
+ }
+ if (impl->pa_mainloop)
+ pa_threaded_mainloop_free(impl->pa_mainloop);
+
+ if (impl->stream)
+ pw_stream_destroy(impl->stream);
+ if (impl->core && impl->do_disconnect)
+ pw_core_disconnect(impl->core);
+
+ pw_properties_free(impl->stream_props);
+ pw_properties_free(impl->props);
+
+ free(impl->buffer);
+ free(impl);
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->module_listener);
+ impl_destroy(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_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));
+}
+
+static int calc_frame_size(struct spa_audio_info_raw *info)
+{
+ int res = info->channels;
+ switch (info->format) {
+ case SPA_AUDIO_FORMAT_U8:
+ case SPA_AUDIO_FORMAT_S8:
+ case SPA_AUDIO_FORMAT_ALAW:
+ case SPA_AUDIO_FORMAT_ULAW:
+ return res;
+ case SPA_AUDIO_FORMAT_S16:
+ case SPA_AUDIO_FORMAT_S16_OE:
+ case SPA_AUDIO_FORMAT_U16:
+ return res * 2;
+ case SPA_AUDIO_FORMAT_S24:
+ case SPA_AUDIO_FORMAT_S24_OE:
+ case SPA_AUDIO_FORMAT_U24:
+ return res * 3;
+ case SPA_AUDIO_FORMAT_S24_32:
+ case SPA_AUDIO_FORMAT_S24_32_OE:
+ case SPA_AUDIO_FORMAT_S32:
+ case SPA_AUDIO_FORMAT_S32_OE:
+ case SPA_AUDIO_FORMAT_U32:
+ case SPA_AUDIO_FORMAT_U32_OE:
+ case SPA_AUDIO_FORMAT_F32:
+ case SPA_AUDIO_FORMAT_F32_OE:
+ return res * 4;
+ case SPA_AUDIO_FORMAT_F64:
+ case SPA_AUDIO_FORMAT_F64_OE:
+ return res * 8;
+ default:
+ return 0;
+ }
+}
+
+static void copy_props(struct impl *impl, struct pw_properties *props, const char *key)
+{
+ const char *str;
+ if ((str = pw_properties_get(props, key)) != NULL) {
+ if (pw_properties_get(impl->stream_props, key) == NULL)
+ pw_properties_set(impl->stream_props, key, str);
+ }
+}
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props = NULL;
+ struct impl *impl;
+ const char *str;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args == NULL)
+ args = "";
+
+ props = pw_properties_new_string(args);
+ if (props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+ impl->props = props;
+
+ impl->stream_props = pw_properties_new(NULL, NULL);
+ if (impl->stream_props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+
+ impl->module = module;
+ impl->context = context;
+
+ spa_ringbuffer_init(&impl->ring);
+ impl->buffer = calloc(1, RINGBUFFER_SIZE);
+ spa_dll_init(&impl->dll);
+ impl->rate_limit.interval = 2 * SPA_NSEC_PER_SEC;
+ impl->rate_limit.burst = 1;
+
+ if ((str = pw_properties_get(props, "tunnel.mode")) != NULL) {
+ if (spa_streq(str, "source")) {
+ impl->mode = MODE_SOURCE;
+ } else if (spa_streq(str, "sink")) {
+ impl->mode = MODE_SINK;
+ } else {
+ pw_log_error("invalid tunnel.mode '%s'", str);
+ res = -EINVAL;
+ goto error;
+ }
+ }
+
+ impl->latency_msec = pw_properties_get_uint32(props, "pulse.latency", DEFAULT_LATENCY_MSEC);
+
+
+ if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
+ if (pw_properties_get(props, PW_KEY_NODE_NETWORK) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_NETWORK, "true");
+
+ if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL)
+ pw_properties_set(props, PW_KEY_MEDIA_CLASS,
+ impl->mode == MODE_SINK ?
+ "Audio/Sink" : "Audio/Source");
+
+ if ((str = pw_properties_get(props, "stream.props")) != NULL)
+ pw_properties_update_string(impl->stream_props, str, strlen(str));
+
+ 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);
+ copy_props(impl, props, PW_KEY_NODE_GROUP);
+ copy_props(impl, props, PW_KEY_NODE_LATENCY);
+ copy_props(impl, props, PW_KEY_NODE_VIRTUAL);
+ copy_props(impl, props, PW_KEY_NODE_NETWORK);
+ copy_props(impl, props, PW_KEY_MEDIA_CLASS);
+
+ parse_audio_info(impl->stream_props, &impl->info);
+
+ impl->frame_size = calc_frame_size(&impl->info);
+ if (impl->frame_size == 0) {
+ pw_log_error("unsupported audio format:%d channels:%d",
+ impl->info.format, impl->info.channels);
+ res = -EINVAL;
+ goto error;
+ }
+ spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MIN, 128, impl->info.rate);
+ impl->max_error = 256.0f;
+
+ 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);
+ impl->core = pw_context_connect(impl->context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ impl->do_disconnect = true;
+ }
+ if (impl->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto error;
+ }
+
+ pw_proxy_add_listener((struct pw_proxy*)impl->core,
+ &impl->core_proxy_listener,
+ &core_proxy_events, impl);
+ pw_core_add_listener(impl->core,
+ &impl->core_listener,
+ &core_events, impl);
+
+ if ((res = create_pulse_stream(impl)) < 0)
+ goto error;
+
+ if ((res = create_stream(impl)) < 0)
+ goto error;
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+
+error:
+ impl_destroy(impl);
+ return res;
+}
diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c
new file mode 100644
index 0000000..0c62011
--- /dev/null
+++ b/src/modules/module-raop-discover.c
@@ -0,0 +1,547 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/utils/json.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/i18n.h>
+
+#include <avahi-client/lookup.h>
+#include <avahi-common/error.h>
+#include <avahi-common/malloc.h>
+
+#include "module-protocol-pulse/format.h"
+#include "module-zeroconf-discover/avahi-poll.h"
+
+/** \page page_module_raop_discover PipeWire Module: RAOP Discover
+ *
+ * Automatically creates RAOP (Airplay) sink devices based on zeroconf
+ * information.
+ *
+ * This module will load module-raop-sink for each discovered sink
+ * with the right parameters.
+ *
+ * ## Module Options
+ *
+ * This module has no options.
+ *
+ * ## Example configuration
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-raop-discover
+ * args = { }
+ * }
+ * ]
+ *\endcode
+ *
+ * ## See also
+ *
+ * \ref page_module_raop_sink
+ */
+
+#define NAME "raop-discover"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define MODULE_USAGE " "
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Discover remote streams" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+#define SERVICE_TYPE_SINK "_raop._tcp"
+
+struct impl {
+ struct pw_context *context;
+
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_properties *properties;
+
+ AvahiPoll *avahi_poll;
+ AvahiClient *client;
+ AvahiServiceBrowser *sink_browser;
+
+ struct spa_list tunnel_list;
+};
+
+struct tunnel_info {
+ AvahiIfIndex interface;
+ AvahiProtocol protocol;
+ const char *name;
+ const char *type;
+ const char *domain;
+};
+
+#define TUNNEL_INFO(...) ((struct tunnel_info){ __VA_ARGS__ })
+
+struct tunnel {
+ struct spa_list link;
+ struct tunnel_info info;
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+};
+
+static int start_client(struct impl *impl);
+
+static struct tunnel *make_tunnel(struct impl *impl, const struct tunnel_info *info)
+{
+ struct tunnel *t;
+
+ t = calloc(1, sizeof(*t));
+ if (t == NULL)
+ return NULL;
+
+ t->info.interface = info->interface;
+ t->info.protocol = info->protocol;
+ t->info.name = strdup(info->name);
+ t->info.type = strdup(info->type);
+ t->info.domain = strdup(info->domain);
+ spa_list_append(&impl->tunnel_list, &t->link);
+
+ return t;
+}
+
+static struct tunnel *find_tunnel(struct impl *impl, const struct tunnel_info *info)
+{
+ struct tunnel *t;
+ spa_list_for_each(t, &impl->tunnel_list, link) {
+ if (t->info.interface == info->interface &&
+ t->info.protocol == info->protocol &&
+ spa_streq(t->info.name, info->name) &&
+ spa_streq(t->info.type, info->type) &&
+ spa_streq(t->info.domain, info->domain))
+ return t;
+ }
+ return NULL;
+}
+
+static void free_tunnel(struct tunnel *t)
+{
+ pw_impl_module_destroy(t->module);
+}
+
+static void impl_free(struct impl *impl)
+{
+ struct tunnel *t;
+
+ spa_list_consume(t, &impl->tunnel_list, link)
+ free_tunnel(t);
+
+ if (impl->sink_browser)
+ avahi_service_browser_free(impl->sink_browser);
+ if (impl->client)
+ avahi_client_free(impl->client);
+ if (impl->avahi_poll)
+ pw_avahi_poll_free(impl->avahi_poll);
+ pw_properties_free(impl->properties);
+ free(impl);
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->module_listener);
+ impl_free(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+static bool str_in_list(const char *haystack, const char *delimiters, const char *needle)
+{
+ const char *s, *state = NULL;
+ size_t len;
+ while ((s = pw_split_walk(haystack, delimiters, &len, &state))) {
+ if (spa_strneq(needle, s, len))
+ return true;
+ }
+ return false;
+}
+
+static void pw_properties_from_avahi_string(const char *key, const char *value,
+ struct pw_properties *props)
+{
+ if (spa_streq(key, "device")) {
+ pw_properties_set(props, "raop.device", value);
+ }
+ else if (spa_streq(key, "tp")) {
+ /* transport protocol, "UDP", "TCP", "UDP,TCP" */
+ if (str_in_list(value, ",", "UDP"))
+ value = "udp";
+ else if (str_in_list(value, ",", "TCP"))
+ value = "tcp";
+ pw_properties_set(props, "raop.transport", value);
+ } else if (spa_streq(key, "et")) {
+ /* Supported encryption types:
+ * 0 = none,
+ * 1 = RSA,
+ * 2 = FairPlay,
+ * 3 = MFiSAP,
+ * 4 = FairPlay SAPv2.5. */
+ if (str_in_list(value, ",", "1"))
+ value = "RSA";
+ else if (str_in_list(value, ",", "4"))
+ value = "auth_setup";
+ else
+ value = "none";
+ pw_properties_set(props, "raop.encryption.type", value);
+ } else if (spa_streq(key, "cn")) {
+ /* Suported audio codecs:
+ * 0 = PCM,
+ * 1 = ALAC,
+ * 2 = AAC,
+ * 3 = AAC ELD. */
+ if (str_in_list(value, ",", "0"))
+ value = "PCM";
+ else if (str_in_list(value, ",", "1"))
+ value = "ALAC";
+ else if (str_in_list(value, ",", "2"))
+ value = "AAC";
+ else if (str_in_list(value, ",", "2"))
+ value = "AAC-ELD";
+ else
+ value = "unknown";
+ pw_properties_set(props, "raop.audio.codec", value);
+ } else if (spa_streq(key, "ch")) {
+ /* Number of channels */
+ pw_properties_set(props, PW_KEY_AUDIO_CHANNELS, value);
+ } else if (spa_streq(key, "ss")) {
+ /* Sample size */
+ if (spa_streq(value, "16"))
+ value = "S16";
+ else if (spa_streq(value, "24"))
+ value = "S24";
+ else if (spa_streq(value, "32"))
+ value = "S32";
+ else
+ value = "UNKNOWN";
+ pw_properties_set(props, PW_KEY_AUDIO_FORMAT, value);
+ } else if (spa_streq(key, "sr")) {
+ /* Sample rate */
+ pw_properties_set(props, PW_KEY_AUDIO_RATE, value);
+ } else if (spa_streq(key, "am")) {
+ /* Device model */
+ pw_properties_set(props, "device.model", value);
+ }
+}
+
+static void submodule_destroy(void *data)
+{
+ struct tunnel *t = data;
+
+ spa_list_remove(&t->link);
+ spa_hook_remove(&t->module_listener);
+
+ free((char *) t->info.name);
+ free((char *) t->info.type);
+ free((char *) t->info.domain);
+
+ free(t);
+}
+
+static const struct pw_impl_module_events submodule_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = submodule_destroy,
+};
+
+static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol,
+ AvahiResolverEvent event, const char *name, const char *type, const char *domain,
+ const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt,
+ AvahiLookupResultFlags flags, void *userdata)
+{
+ struct impl *impl = userdata;
+ struct tunnel *t;
+ struct tunnel_info tinfo;
+ const char *str;
+ AvahiStringList *l;
+ FILE *f;
+ char *args;
+ size_t size;
+ struct pw_impl_module *mod;
+ struct pw_properties *props = NULL;
+ char at[AVAHI_ADDRESS_STR_MAX];
+
+ if (event != AVAHI_RESOLVER_FOUND) {
+ pw_log_error("Resolving of '%s' failed: %s", name,
+ avahi_strerror(avahi_client_errno(impl->client)));
+ goto done;
+ }
+ tinfo = TUNNEL_INFO(.interface = interface,
+ .protocol = protocol,
+ .name = name,
+ .type = type,
+ .domain = domain);
+
+ props = pw_properties_new(NULL, NULL);
+ if (props == NULL) {
+ pw_log_error("Can't allocate properties: %m");
+ goto done;
+ }
+
+ avahi_address_snprint(at, sizeof(at), a);
+
+ pw_properties_setf(props, "raop.hostname", "%s", at);
+ pw_properties_setf(props, "raop.port", "%u", port);
+
+ if ((str = strstr(name, "@"))) {
+ str++;
+ if (strlen(str) > 0)
+ pw_properties_set(props, PW_KEY_NODE_DESCRIPTION, str);
+ else
+ pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION,
+ "RAOP on %s", host_name);
+ }
+
+ for (l = txt; l; l = l->next) {
+ char *key, *value;
+
+ if (avahi_string_list_get_pair(l, &key, &value, NULL) != 0)
+ break;
+
+ pw_properties_from_avahi_string(key, value, props);
+ avahi_free(key);
+ avahi_free(value);
+ }
+
+
+ if ((f = open_memstream(&args, &size)) == NULL) {
+ pw_log_error("Can't open memstream: %m");
+ goto done;
+ }
+
+ fprintf(f, "{");
+ pw_properties_serialize_dict(f, &props->dict, 0);
+ fprintf(f, " stream.props = {");
+ fprintf(f, " }");
+ fprintf(f, "}");
+ fclose(f);
+
+ pw_properties_free(props);
+
+ pw_log_info("loading module args:'%s'", args);
+ mod = pw_context_load_module(impl->context,
+ "libpipewire-module-raop-sink",
+ args, NULL);
+ free(args);
+
+ if (mod == NULL) {
+ pw_log_error("Can't load module: %m");
+ goto done;
+ }
+
+ t = make_tunnel(impl, &tinfo);
+ if (t == NULL) {
+ pw_log_error("Can't make tunnel: %m");
+ pw_impl_module_destroy(mod);
+ goto done;
+ }
+
+ pw_impl_module_add_listener(mod, &t->module_listener, &submodule_events, t);
+
+ t->module = mod;
+
+done:
+ avahi_service_resolver_free(r);
+}
+
+
+static void browser_cb(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol,
+ AvahiBrowserEvent event, const char *name, const char *type, const char *domain,
+ AvahiLookupResultFlags flags, void *userdata)
+{
+ struct impl *impl = userdata;
+ struct tunnel_info info;
+ struct tunnel *t;
+
+ if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
+ return;
+
+ info = TUNNEL_INFO(.interface = interface,
+ .protocol = protocol,
+ .name = name,
+ .type = type,
+ .domain = domain);
+
+ t = find_tunnel(impl, &info);
+
+ switch (event) {
+ case AVAHI_BROWSER_NEW:
+ if (t != NULL)
+ return;
+ if (!(avahi_service_resolver_new(impl->client,
+ interface, protocol,
+ name, type, domain,
+ AVAHI_PROTO_UNSPEC, 0,
+ resolver_cb, impl)))
+ pw_log_error("can't make service resolver: %s",
+ avahi_strerror(avahi_client_errno(impl->client)));
+ break;
+ case AVAHI_BROWSER_REMOVE:
+ if (t == NULL)
+ return;
+ free_tunnel(t);
+ break;
+ default:
+ break;
+ }
+}
+
+
+static struct AvahiServiceBrowser *make_browser(struct impl *impl, const char *service_type)
+{
+ struct AvahiServiceBrowser *s;
+
+ s = avahi_service_browser_new(impl->client,
+ AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+ service_type, NULL, 0,
+ browser_cb, impl);
+ if (s == NULL) {
+ pw_log_error("can't make browser for %s: %s", service_type,
+ avahi_strerror(avahi_client_errno(impl->client)));
+ }
+ return s;
+}
+
+static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata)
+{
+ struct impl *impl = userdata;
+
+ impl->client = c;
+
+ switch (state) {
+ case AVAHI_CLIENT_S_REGISTERING:
+ case AVAHI_CLIENT_S_RUNNING:
+ case AVAHI_CLIENT_S_COLLISION:
+ if (impl->sink_browser == NULL)
+ impl->sink_browser = make_browser(impl, SERVICE_TYPE_SINK);
+ if (impl->sink_browser == NULL)
+ goto error;
+ break;
+ case AVAHI_CLIENT_FAILURE:
+ if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED)
+ start_client(impl);
+
+ SPA_FALLTHROUGH;
+ case AVAHI_CLIENT_CONNECTING:
+ if (impl->sink_browser) {
+ avahi_service_browser_free(impl->sink_browser);
+ impl->sink_browser = NULL;
+ }
+ break;
+ default:
+ break;
+ }
+ return;
+error:
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static int start_client(struct impl *impl)
+{
+ int res;
+ if ((impl->client = avahi_client_new(impl->avahi_poll,
+ AVAHI_CLIENT_NO_FAIL,
+ client_callback, impl,
+ &res)) == NULL) {
+ pw_log_error("can't create client: %s", avahi_strerror(res));
+ pw_impl_module_schedule_destroy(impl->module);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int start_avahi(struct impl *impl)
+{
+ struct pw_loop *loop;
+
+ loop = pw_context_get_main_loop(impl->context);
+ impl->avahi_poll = pw_avahi_poll_new(loop);
+
+ return start_client(impl);
+}
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props;
+ struct impl *impl;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ goto error_errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args == NULL)
+ args = "";
+
+ props = pw_properties_new_string(args);
+ if (props == NULL)
+ goto error_errno;
+
+ spa_list_init(&impl->tunnel_list);
+
+ impl->module = module;
+ impl->context = context;
+ impl->properties = props;
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ start_avahi(impl);
+
+ return 0;
+
+error_errno:
+ res = -errno;
+ if (impl)
+ impl_free(impl);
+ return res;
+}
diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c
new file mode 100644
index 0000000..303cb42
--- /dev/null
+++ b/src/modules/module-raop-sink.c
@@ -0,0 +1,1848 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <limits.h>
+#include <math.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <openssl/rsa.h>
+#include <openssl/engine.h>
+#include <openssl/aes.h>
+#include <openssl/md5.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/utils/json.h>
+#include <spa/utils/ringbuffer.h>
+#include <spa/debug/types.h>
+#include <spa/pod/builder.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/audio/raw.h>
+#include <spa/param/latency-utils.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/i18n.h>
+
+#include "module-raop/rtsp-client.h"
+
+/** \page page_module_raop_sink PipeWire Module: AirPlay Sink
+ *
+ * Creates a new Sink to stream to an Airplay device.
+ *
+ * Normally this sink is automatically created with \ref page_module_raop_discover
+ * with the right parameters but it is possible to manually create a RAOP sink
+ * as well.
+ *
+ * ## Module Options
+ *
+ * Options specific to the behavior of this module
+ *
+ * - `raop.hostname`: The hostname of the remote end.
+ * - `raop.port`: The port of the remote end.
+ * - `raop.transport`: The data transport to use, one of "udp" or "tcp". Defaults
+ * to "udp".
+ * - `raop.encryption.type`: The encryption type to use. One of "none", "RSA" or
+ * "auth_setup". Default is "none".
+ * - `raop.audio.codec`: The audio codec to use. Needs to be "PCM". Defaults to "PCM".
+ * - `raop.password`: The password to use.
+ * - `stream.props = {}`: properties to be passed to the sink stream
+ *
+ * Options with well-known behavior.
+ *
+ * - \ref PW_KEY_REMOTE_NAME
+ * - \ref PW_KEY_AUDIO_FORMAT
+ * - \ref PW_KEY_AUDIO_RATE
+ * - \ref PW_KEY_AUDIO_CHANNELS
+ * - \ref SPA_KEY_AUDIO_POSITION
+ * - \ref PW_KEY_NODE_NAME
+ * - \ref PW_KEY_NODE_DESCRIPTION
+ * - \ref PW_KEY_NODE_GROUP
+ * - \ref PW_KEY_NODE_LATENCY
+ * - \ref PW_KEY_NODE_VIRTUAL
+ * - \ref PW_KEY_MEDIA_CLASS
+ *
+ * ## Example configuration
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-raop-sink
+ * args = {
+ * # Set the remote address to tunnel to
+ * raop.hostname = "my-raop-device"
+ * raop.port = 8190
+ * #raop.transport = "udp"
+ * raop.encryption.type = "RSA"
+ * #raop.audio.codec = "PCM"
+ * #raop.password = "****"
+ * #audio.format = "S16"
+ * #audio.rate = 44100
+ * #audio.channels = 2
+ * #audio.position = [ FL FR ]
+ * stream.props = {
+ * # extra sink properties
+ * }
+ * }
+ * }
+ * ]
+ *\endcode
+ *
+ * ## See also
+ *
+ * \ref page_module_raop_discover
+ */
+
+#define NAME "raop-sink"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define FRAMES_PER_TCP_PACKET 4096
+#define FRAMES_PER_UDP_PACKET 352
+
+#define DEFAULT_TCP_AUDIO_PORT 6000
+#define DEFAULT_UDP_AUDIO_PORT 6000
+#define DEFAULT_UDP_CONTROL_PORT 6001
+#define DEFAULT_UDP_TIMING_PORT 6002
+
+#define AES_CHUNK_SIZE 16
+#ifndef MD5_DIGEST_LENGTH
+#define MD5_DIGEST_LENGTH 16
+#endif
+#define MD5_HASH_LENGTH (2*MD5_DIGEST_LENGTH)
+
+#define DEFAULT_USER_AGENT "iTunes/11.0.4 (Windows; N)"
+#define DEFAULT_USER_NAME "iTunes"
+
+#define MAX_PORT_RETRY 128
+
+#define DEFAULT_FORMAT "S16"
+#define DEFAULT_RATE 44100
+#define DEFAULT_CHANNELS 2
+#define DEFAULT_POSITION "[ FL FR ]"
+
+#define DEFAULT_LATENCY 22050
+
+#define MODULE_USAGE "[ raop.hostname=<name of host> ] " \
+ "[ raop.port=<remote port> ] " \
+ "[ raop.transport=<transport, default:udp> ] " \
+ "[ raop.encryption.type=<encryption, default:none> ] " \
+ "[ raop.audio.codec=PCM ] " \
+ "[ raop.password=<password for auth> ] " \
+ "[ node.latency=<latency as fraction> ] " \
+ "[ node.name=<name of the nodes> ] " \
+ "[ node.description=<description of the nodes> ] " \
+ "[ audio.format=<format, default:"DEFAULT_FORMAT"> ] " \
+ "[ audio.rate=<sample rate, default: "SPA_STRINGIFY(DEFAULT_RATE)"> ] " \
+ "[ audio.channels=<number of channels, default:"SPA_STRINGIFY(DEFAULT_CHANNELS)"> ] " \
+ "[ audio.position=<channel map, default:"DEFAULT_POSITION"> ] " \
+ "[ stream.props=<properties> ] "
+
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "An RAOP audio sink" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+enum {
+ PROTO_TCP,
+ PROTO_UDP,
+};
+enum {
+ CRYPTO_NONE,
+ CRYPTO_RSA,
+ CRYPTO_AUTH_SETUP,
+};
+enum {
+ CODEC_PCM,
+ CODEC_ALAC,
+ CODEC_AAC,
+ CODEC_AAC_ELD,
+};
+
+struct impl {
+ struct pw_context *context;
+
+ struct pw_properties *props;
+
+ struct pw_impl_module *module;
+ struct pw_loop *loop;
+
+ struct spa_hook module_listener;
+
+ int protocol;
+ int encryption;
+ int codec;
+
+ struct pw_core *core;
+ struct spa_hook core_proxy_listener;
+ struct spa_hook core_listener;
+
+ struct pw_properties *stream_props;
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+ struct spa_audio_info_raw info;
+ uint32_t frame_size;
+
+ struct pw_rtsp_client *rtsp;
+ struct spa_hook rtsp_listener;
+ struct pw_properties *headers;
+
+ char session_id[32];
+ char *password;
+
+ unsigned int do_disconnect:1;
+
+ uint8_t key[AES_CHUNK_SIZE]; /* Key for aes-cbc */
+ uint8_t iv[AES_CHUNK_SIZE]; /* Initialization vector for cbc */
+ AES_KEY aes; /* AES encryption */
+
+ uint16_t control_port;
+ int control_fd;
+ struct spa_source *control_source;
+
+ uint16_t timing_port;
+ int timing_fd;
+ struct spa_source *timing_source;
+
+ uint16_t server_port;
+ int server_fd;
+ struct spa_source *server_source;
+
+ uint32_t block_size;
+ uint32_t delay;
+ uint32_t latency;
+
+ uint16_t seq;
+ uint32_t rtptime;
+ uint32_t ssrc;
+ uint32_t sync;
+ uint32_t sync_period;
+ unsigned int first:1;
+ unsigned int connected:1;
+ unsigned int ready:1;
+ unsigned int recording:1;
+
+ uint8_t buffer[FRAMES_PER_TCP_PACKET * 4];
+ uint32_t filled;
+};
+
+static void stream_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->stream_listener);
+ impl->stream = NULL;
+}
+
+static inline void bit_writer(uint8_t **p, int *pos, uint8_t data, int len)
+{
+ int rb = 8 - *pos - len;
+ if (rb >= 0) {
+ **p = (*pos ? **p : 0) | (data << rb);
+ *pos += len;
+ } else {
+ *(*p)++ |= (data >> -rb);
+ **p = data << (8+rb);
+ *pos = -rb;
+ }
+}
+
+static int aes_encrypt(struct impl *impl, uint8_t *data, int len)
+{
+ uint8_t nv[AES_CHUNK_SIZE];
+ uint8_t *buffer;
+ int i, j;
+
+ memcpy(nv, impl->iv, AES_CHUNK_SIZE);
+ for (i = 0; i + AES_CHUNK_SIZE <= len; i += AES_CHUNK_SIZE) {
+ buffer = data + i;
+ for (j = 0; j < AES_CHUNK_SIZE; j++)
+ buffer[j] ^= nv[j];
+
+ AES_encrypt(buffer, buffer, &impl->aes);
+
+ memcpy(nv, buffer, AES_CHUNK_SIZE);
+ }
+ return i;
+}
+
+static inline uint64_t timespec_to_ntp(struct timespec *ts)
+{
+ uint64_t ntp = (uint64_t) ts->tv_nsec * UINT32_MAX / SPA_NSEC_PER_SEC;
+ return ntp | (uint64_t) (ts->tv_sec + 0x83aa7e80) << 32;
+}
+
+static inline uint64_t ntp_now(int clockid)
+{
+ struct timespec now;
+ clock_gettime(clockid, &now);
+ return timespec_to_ntp(&now);
+}
+
+static int send_udp_sync_packet(struct impl *impl,
+ struct sockaddr *dest_addr, socklen_t addrlen)
+{
+ uint32_t pkt[5];
+ uint32_t rtptime = impl->rtptime;
+ uint32_t delay = impl->delay;
+ uint64_t transmitted;
+
+ pkt[0] = htonl(0x80d40007);
+ if (impl->first)
+ pkt[0] |= htonl(0x10000000);
+ rtptime -= delay;
+ pkt[1] = htonl(rtptime);
+ transmitted = ntp_now(CLOCK_MONOTONIC);
+ pkt[2] = htonl(transmitted >> 32);
+ pkt[3] = htonl(transmitted & 0xffffffff);
+ rtptime += delay;
+ pkt[4] = htonl(rtptime);
+
+ pw_log_debug("sync: delayed:%u now:%"PRIu64" rtptime:%u",
+ rtptime - delay, transmitted, rtptime);
+
+ return sendto(impl->control_fd, pkt, sizeof(pkt), 0, dest_addr, addrlen);
+}
+
+static int send_udp_timing_packet(struct impl *impl, uint64_t remote, uint64_t received,
+ struct sockaddr *dest_addr, socklen_t addrlen)
+{
+ uint32_t pkt[8];
+ uint64_t transmitted;
+
+ pkt[0] = htonl(0x80d30007);
+ pkt[1] = 0x00000000;
+ pkt[2] = htonl(remote >> 32);
+ pkt[3] = htonl(remote & 0xffffffff);
+ pkt[4] = htonl(received >> 32);
+ pkt[5] = htonl(received & 0xffffffff);
+ transmitted = ntp_now(CLOCK_MONOTONIC);
+ pkt[6] = htonl(transmitted >> 32);
+ pkt[7] = htonl(transmitted & 0xffffffff);
+
+ pw_log_debug("sync: remote:%"PRIu64" received:%"PRIu64" transmitted:%"PRIu64,
+ remote, received, transmitted);
+
+ return sendto(impl->timing_fd, pkt, sizeof(pkt), 0, dest_addr, addrlen);
+}
+
+static int write_codec_pcm(void *dst, void *frames, uint32_t n_frames)
+{
+ uint8_t *bp, *b, *d = frames;
+ int bpos = 0;
+ uint32_t i;
+
+ b = bp = dst;
+
+ bit_writer(&bp, &bpos, 1, 3); /* channel=1, stereo */
+ bit_writer(&bp, &bpos, 0, 4); /* Unknown */
+ bit_writer(&bp, &bpos, 0, 8); /* Unknown */
+ bit_writer(&bp, &bpos, 0, 4); /* Unknown */
+ bit_writer(&bp, &bpos, 1, 1); /* Hassize */
+ bit_writer(&bp, &bpos, 0, 2); /* Unused */
+ bit_writer(&bp, &bpos, 1, 1); /* Is-not-compressed */
+ bit_writer(&bp, &bpos, (n_frames >> 24) & 0xff, 8);
+ bit_writer(&bp, &bpos, (n_frames >> 16) & 0xff, 8);
+ bit_writer(&bp, &bpos, (n_frames >> 8) & 0xff, 8);
+ bit_writer(&bp, &bpos, (n_frames) & 0xff, 8);
+
+ for (i = 0; i < n_frames; i++) {
+ bit_writer(&bp, &bpos, *(d + 1), 8);
+ bit_writer(&bp, &bpos, *(d + 0), 8);
+ bit_writer(&bp, &bpos, *(d + 3), 8);
+ bit_writer(&bp, &bpos, *(d + 2), 8);
+ d += 4;
+ }
+ return bp - b + 1;
+}
+
+static int flush_to_udp_packet(struct impl *impl)
+{
+ const size_t max = 12 + 8 + impl->block_size;
+ uint32_t pkt[max], len, n_frames;
+ uint8_t *dst;
+ int res;
+
+ if (!impl->recording)
+ return 0;
+
+ impl->sync++;
+ if (impl->first || impl->sync == impl->sync_period) {
+ impl->sync = 0;
+ send_udp_sync_packet(impl, NULL, 0);
+ }
+ pkt[0] = htonl(0x80600000);
+ if (impl->first)
+ pkt[0] |= htonl((uint32_t)0x80 << 16);
+ pkt[0] |= htonl((uint32_t)impl->seq);
+ pkt[1] = htonl(impl->rtptime);
+ pkt[2] = htonl(impl->ssrc);
+
+ n_frames = impl->filled / impl->frame_size;
+ dst = (uint8_t*)&pkt[3];
+
+ switch (impl->codec) {
+ case CODEC_PCM:
+ case CODEC_ALAC:
+ len = write_codec_pcm(dst, impl->buffer, n_frames);
+ break;
+ default:
+ len = 8 + impl->block_size;
+ memset(dst, 0, len);
+ break;
+ }
+ if (impl->encryption == CRYPTO_RSA)
+ aes_encrypt(impl, dst, len);
+
+ impl->rtptime += n_frames;
+ impl->seq = (impl->seq + 1) & 0xffff;
+
+ pw_log_debug("send %u", len + 12);
+ res = send(impl->server_fd, pkt, len + 12, 0);
+
+ impl->first = false;
+
+ return res;
+}
+
+static int flush_to_tcp_packet(struct impl *impl)
+{
+ const size_t max = 16 + 8 + impl->block_size;
+ uint32_t pkt[max], len, n_frames;
+ uint8_t *dst;
+ int res;
+
+ if (!impl->recording)
+ return 0;
+
+ pkt[0] = htonl(0x24000000);
+ pkt[1] = htonl(0x80e00000);
+ pkt[1] |= htonl((uint32_t)impl->seq);
+ pkt[2] = htonl(impl->rtptime);
+ pkt[3] = htonl(impl->ssrc);
+
+ n_frames = impl->filled / impl->frame_size;
+ dst = (uint8_t*)&pkt[4];
+
+ switch (impl->codec) {
+ case CODEC_PCM:
+ case CODEC_ALAC:
+ len = write_codec_pcm(dst, impl->buffer, n_frames);
+ break;
+ default:
+ len = 8 + impl->block_size;
+ memset(dst, 0, len);
+ break;
+ }
+ if (impl->encryption == CRYPTO_RSA)
+ aes_encrypt(impl, dst, len);
+
+ pkt[0] |= htonl((uint32_t) len + 12);
+
+ impl->rtptime += n_frames;
+ impl->seq = (impl->seq + 1) & 0xffff;
+
+ pw_log_debug("send %u", len + 16);
+ res = send(impl->server_fd, pkt, len + 16, 0);
+
+ impl->first = false;
+
+ return res;
+}
+
+static void playback_stream_process(void *d)
+{
+ struct impl *impl = d;
+ struct pw_buffer *buf;
+ struct spa_data *bd;
+ uint8_t *data;
+ uint32_t offs, size;
+
+ if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) {
+ pw_log_debug("out of buffers: %m");
+ return;
+ }
+
+ bd = &buf->buffer->datas[0];
+
+ offs = SPA_MIN(bd->chunk->offset, bd->maxsize);
+ size = SPA_MIN(bd->chunk->size, bd->maxsize - offs);
+ data = SPA_PTROFF(bd->data, offs, uint8_t);
+
+ while (size > 0 && impl->block_size > 0) {
+ uint32_t avail, to_fill;
+
+ avail = impl->block_size - impl->filled;
+ to_fill = SPA_MIN(avail, size);
+
+ memcpy(&impl->buffer[impl->filled], data, to_fill);
+
+ impl->filled += to_fill;
+ avail -= to_fill;
+ size -= to_fill;
+ data += to_fill;
+
+ if (avail == 0) {
+ switch (impl->protocol) {
+ case PROTO_UDP:
+ flush_to_udp_packet(impl);
+ break;
+ case PROTO_TCP:
+ flush_to_tcp_packet(impl);
+ break;
+ }
+ impl->filled = 0;
+ }
+ }
+
+ pw_stream_queue_buffer(impl->stream, buf);
+}
+
+static int create_udp_socket(struct impl *impl, uint16_t *port)
+{
+ int res, ip_version, fd, val, i, af;
+ struct sockaddr_in sa4;
+ struct sockaddr_in6 sa6;
+
+ if ((res = pw_rtsp_client_get_local_ip(impl->rtsp,
+ &ip_version, NULL, 0)) < 0)
+ return res;
+
+ if (ip_version == 4) {
+ sa4.sin_family = af = AF_INET;
+ sa4.sin_addr.s_addr = INADDR_ANY;
+ } else {
+ sa6.sin6_family = af = AF_INET6;
+ sa6.sin6_addr = in6addr_any;
+ }
+
+ if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
+ pw_log_error("socket failed: %m");
+ return -errno;
+ }
+
+#ifdef SO_TIMESTAMP
+ val = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &val, sizeof(val)) < 0) {
+ res = -errno;
+ pw_log_error("setsockopt failed: %m");
+ goto error;
+ }
+#endif
+ val = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
+ res = -errno;
+ pw_log_error("setsockopt failed: %m");
+ goto error;
+ }
+
+ for (i = 0; i < MAX_PORT_RETRY; i++) {
+ int ret;
+
+ if (ip_version == 4) {
+ sa4.sin_port = htons(*port);
+ ret = bind(fd, (struct sockaddr*)&sa4, sizeof(sa4));
+ } else {
+ sa6.sin6_port = htons(*port);
+ ret = bind(fd, (struct sockaddr*)&sa6, sizeof(sa6));
+ }
+ if (ret == 0)
+ break;
+ if (ret < 0 && errno != EADDRINUSE) {
+ res = -errno;
+ pw_log_error("bind failed: %m");
+ goto error;
+ }
+ (*port)++;
+ }
+ return fd;
+error:
+ close(fd);
+ return res;
+}
+
+static int connect_socket(struct impl *impl, int type, int fd, uint16_t port)
+{
+ const char *host;
+ struct sockaddr_in sa4;
+ struct sockaddr_in6 sa6;
+ struct sockaddr *sa;
+ size_t salen;
+ int res, af;
+
+ host = pw_properties_get(impl->props, "raop.hostname");
+ if (host == NULL)
+ return -EINVAL;
+
+ if (inet_pton(AF_INET, host, &sa4.sin_addr) > 0) {
+ sa4.sin_family = af = AF_INET;
+ sa4.sin_port = htons(port);
+ sa = (struct sockaddr *) &sa4;
+ salen = sizeof(sa4);
+ } else if (inet_pton(AF_INET6, host, &sa6.sin6_addr) > 0) {
+ sa6.sin6_family = af = AF_INET6;
+ sa6.sin6_port = htons(port);
+ sa = (struct sockaddr *) &sa6;
+ salen = sizeof(sa6);
+ } else {
+ pw_log_error("Invalid host '%s'", host);
+ return -EINVAL;
+ }
+
+ if (fd < 0 &&
+ (fd = socket(af, type | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
+ pw_log_error("socket failed: %m");
+ return -errno;
+ }
+
+ res = connect(fd, sa, salen);
+ if (res < 0 && errno != EINPROGRESS) {
+ res = -errno;
+ pw_log_error("connect failed: %m");
+ goto error;
+ }
+ pw_log_info("Connected to host:%s port:%d", host, port);
+ return fd;
+
+error:
+ if (fd >= 0)
+ close(fd);
+ return res;
+}
+
+static void
+on_timing_source_io(void *data, int fd, uint32_t mask)
+{
+ struct impl *impl = data;
+ uint32_t packet[8];
+ ssize_t bytes;
+
+ if (mask & SPA_IO_IN) {
+ uint64_t remote, received;
+ struct sockaddr_storage sender;
+ socklen_t sender_size = sizeof(sender);
+
+ received = ntp_now(CLOCK_MONOTONIC);
+ bytes = recvfrom(impl->timing_fd, packet, sizeof(packet), 0,
+ (struct sockaddr*)&sender, &sender_size);
+ if (bytes < 0) {
+ pw_log_debug("error reading timing packet: %m");
+ return;
+ }
+ if (bytes != sizeof(packet)) {
+ pw_log_warn("discarding short (%zd < %zd) timing packet",
+ bytes, sizeof(bytes));
+ return;
+ }
+ if (packet[0] != ntohl(0x80d20007))
+ return;
+
+ remote = ((uint64_t)ntohl(packet[6])) << 32 | ntohl(packet[7]);
+ if (send_udp_timing_packet(impl, remote, received,
+ (struct sockaddr *)&sender, sender_size) < 0) {
+ pw_log_warn("error sending timing packet");
+ return;
+ }
+ }
+}
+
+
+static void
+on_control_source_io(void *data, int fd, uint32_t mask)
+{
+ struct impl *impl = data;
+ uint32_t packet[2];
+ ssize_t bytes;
+
+ if (mask & SPA_IO_IN) {
+ uint32_t hdr;
+ uint16_t seq, num;
+
+ bytes = read(impl->control_fd, packet, sizeof(packet));
+ if (bytes < 0) {
+ pw_log_debug("error reading control packet: %m");
+ return;
+ }
+ if (bytes != sizeof(packet)) {
+ pw_log_warn("discarding short (%zd < %zd) control packet",
+ bytes, sizeof(bytes));
+ return;
+ }
+ hdr = ntohl(packet[0]);
+ if ((hdr & 0xff000000) != 0x80000000)
+ return;
+
+ seq = ntohl(packet[1]) >> 16;
+ num = ntohl(packet[1]) & 0xffff;
+ if (num == 0)
+ return;
+
+ switch (hdr >> 16 & 0xff) {
+ case 0xd5:
+ pw_log_debug("retransmit request seq:%u num:%u", seq, num);
+ /* retransmit request */
+ break;
+ }
+ }
+}
+
+static int rtsp_flush_reply(void *data, int status, const struct spa_dict *headers)
+{
+ pw_log_info("reply %d", status);
+ return 0;
+}
+
+static int rtsp_do_flush(struct impl *impl)
+{
+ int res;
+
+ if (!impl->recording)
+ return 0;
+
+ pw_properties_set(impl->headers, "Range", "npt=0-");
+ pw_properties_setf(impl->headers, "RTP-Info",
+ "seq=%u;rtptime=%u", impl->seq, impl->rtptime);
+
+ impl->recording = false;
+
+ res = pw_rtsp_client_send(impl->rtsp, "FLUSH", &impl->headers->dict,
+ NULL, NULL, rtsp_flush_reply, impl);
+
+ pw_properties_set(impl->headers, "Range", NULL);
+ pw_properties_set(impl->headers, "RTP-Info", NULL);
+
+ return res;
+}
+
+static int rtsp_record_reply(void *data, int status, const struct spa_dict *headers)
+{
+ struct impl *impl = data;
+ const char *str;
+ uint32_t n_params;
+ const struct spa_pod *params[2];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b;
+ struct spa_latency_info latency;
+ char progress[128];
+
+ pw_log_info("reply %d", status);
+
+ if ((str = spa_dict_lookup(headers, "Audio-Latency")) != NULL) {
+ if (!spa_atou32(str, &impl->latency, 0))
+ impl->latency = DEFAULT_LATENCY;
+ } else {
+ impl->latency = DEFAULT_LATENCY;
+ }
+
+ spa_zero(latency);
+ latency.direction = PW_DIRECTION_INPUT;
+ latency.min_rate = latency.max_rate = impl->latency;
+
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_latency_build(&b, SPA_PARAM_Latency, &latency);
+
+ pw_stream_update_params(impl->stream, params, n_params);
+
+ impl->first = true;
+ impl->sync = 0;
+ impl->sync_period = impl->info.rate / (impl->block_size / impl->frame_size);
+ impl->recording = true;
+
+ snprintf(progress, sizeof(progress), "progress: %s/%s/%s\r\n", "0", "0", "0");
+ return pw_rtsp_client_send(impl->rtsp, "SET_PARAMETER", NULL,
+ "text/parameters", progress, NULL, NULL);
+}
+
+static int rtsp_do_record(struct impl *impl)
+{
+ int res;
+
+ if (!impl->ready || impl->recording)
+ return 0;
+
+ pw_properties_set(impl->headers, "Range", "npt=0-");
+ pw_properties_setf(impl->headers, "RTP-Info",
+ "seq=%u;rtptime=%u", impl->seq, impl->rtptime);
+
+ res = pw_rtsp_client_send(impl->rtsp, "RECORD", &impl->headers->dict,
+ NULL, NULL, rtsp_record_reply, impl);
+
+ pw_properties_set(impl->headers, "Range", NULL);
+ pw_properties_set(impl->headers, "RTP-Info", NULL);
+
+ return res;
+}
+
+static void
+on_server_source_io(void *data, int fd, uint32_t mask)
+{
+ struct impl *impl = data;
+
+ if (mask & (SPA_IO_ERR | SPA_IO_HUP))
+ goto error;
+ if (mask & SPA_IO_OUT) {
+ int res;
+ socklen_t len;
+
+ pw_loop_update_io(impl->loop, impl->server_source,
+ impl->server_source->mask & ~SPA_IO_OUT);
+
+ len = sizeof(res);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &res, &len) < 0) {
+ pw_log_error("getsockopt: %m");
+ goto error;
+ }
+ if (res != 0)
+ goto error;
+
+ impl->ready = true;
+ if (pw_stream_get_state(impl->stream, NULL) == PW_STREAM_STATE_STREAMING)
+ rtsp_do_record(impl);
+ }
+ return;
+error:
+ pw_loop_update_io(impl->loop, impl->server_source, 0);
+}
+
+static int rtsp_setup_reply(void *data, int status, const struct spa_dict *headers)
+{
+ struct impl *impl = data;
+ const char *str, *state = NULL, *s;
+ size_t len;
+ uint64_t ntp;
+ uint16_t control_port, timing_port;
+
+ pw_log_info("reply %d", status);
+
+ if ((str = spa_dict_lookup(headers, "Session")) == NULL) {
+ pw_log_error("missing Session header");
+ return 0;
+ }
+ pw_properties_set(impl->headers, "Session", str);
+
+ if ((str = spa_dict_lookup(headers, "Transport")) == NULL) {
+ pw_log_error("missing Transport header");
+ return 0;
+ }
+
+ impl->server_port = control_port = timing_port = 0;
+ while ((s = pw_split_walk(str, ";", &len, &state)) != NULL) {
+ if (spa_strstartswith(s, "server_port=")) {
+ impl->server_port = atoi(s + 12);
+ }
+ else if (spa_strstartswith(s, "control_port=")) {
+ control_port = atoi(s + 13);
+ }
+ else if (spa_strstartswith(s, "timing_port=")) {
+ timing_port = atoi(s + 12);
+ }
+ }
+ if (impl->server_port == 0) {
+ pw_log_error("missing server port in Transport");
+ return 0;
+ }
+
+ if (pw_getrandom(&impl->seq, sizeof(impl->seq), 0) < 0 ||
+ pw_getrandom(&impl->rtptime, sizeof(impl->rtptime), 0) < 0) {
+ pw_log_error("error generating random seq and rtptime: %m");
+ return 0;
+ }
+
+ pw_log_info("server port:%u", impl->server_port);
+
+ switch (impl->protocol) {
+ case PROTO_TCP:
+ if ((impl->server_fd = connect_socket(impl, SOCK_STREAM, -1, impl->server_port)) < 0)
+ return impl->server_fd;
+
+ impl->server_source = pw_loop_add_io(impl->loop, impl->server_fd,
+ SPA_IO_OUT, false, on_server_source_io, impl);
+ break;
+
+ case PROTO_UDP:
+ if (control_port == 0 || timing_port == 0) {
+ pw_log_error("missing UDP ports in Transport");
+ return 0;
+ }
+ pw_log_info("control:%u timing:%u", control_port, timing_port);
+
+ if ((impl->server_fd = connect_socket(impl, SOCK_DGRAM, -1, impl->server_port)) < 0)
+ return impl->server_fd;
+ if ((impl->control_fd = connect_socket(impl, SOCK_DGRAM, impl->control_fd, control_port)) < 0)
+ return impl->control_fd;
+ if ((impl->timing_fd = connect_socket(impl, SOCK_DGRAM, impl->timing_fd, timing_port)) < 0)
+ return impl->timing_fd;
+
+ ntp = ntp_now(CLOCK_MONOTONIC);
+ send_udp_timing_packet(impl, ntp, ntp, NULL, 0);
+
+ impl->control_source = pw_loop_add_io(impl->loop, impl->control_fd,
+ SPA_IO_IN, false, on_control_source_io, impl);
+
+ impl->ready = true;
+ if (pw_stream_get_state(impl->stream, NULL) == PW_STREAM_STATE_STREAMING)
+ rtsp_do_record(impl);
+ break;
+ default:
+ return 0;
+ }
+ return 0;
+}
+
+static int rtsp_do_setup(struct impl *impl)
+{
+ int res;
+
+ switch (impl->protocol) {
+ case PROTO_TCP:
+ pw_properties_set(impl->headers, "Transport",
+ "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record");
+ break;
+
+ case PROTO_UDP:
+ impl->control_port = DEFAULT_UDP_CONTROL_PORT;
+ impl->timing_port = DEFAULT_UDP_TIMING_PORT;
+
+ impl->control_fd = create_udp_socket(impl, &impl->control_port);
+ impl->timing_fd = create_udp_socket(impl, &impl->timing_port);
+ if (impl->control_fd < 0 || impl->timing_fd < 0)
+ goto error;
+
+ impl->timing_source = pw_loop_add_io(impl->loop, impl->timing_fd,
+ SPA_IO_IN, false, on_timing_source_io, impl);
+
+ pw_properties_setf(impl->headers, "Transport",
+ "RTP/AVP/UDP;unicast;interleaved=0-1;mode=record;"
+ "control_port=%u;timing_port=%u",
+ impl->control_port, impl->timing_port);
+ break;
+
+ default:
+ return -ENOTSUP;
+ }
+
+ res = pw_rtsp_client_send(impl->rtsp, "SETUP", &impl->headers->dict,
+ NULL, NULL, rtsp_setup_reply, impl);
+
+ pw_properties_set(impl->headers, "Transport", NULL);
+
+ return res;
+error:
+ if (impl->control_fd > 0)
+ close(impl->control_fd);
+ impl->control_fd = -1;
+ if (impl->timing_fd > 0)
+ close(impl->timing_fd);
+ impl->timing_fd = -1;
+ return -EIO;
+}
+
+static int rtsp_announce_reply(void *data, int status, const struct spa_dict *headers)
+{
+ struct impl *impl = data;
+
+ pw_log_info("reply %d", status);
+
+ pw_properties_set(impl->headers, "Apple-Challenge", NULL);
+
+ return rtsp_do_setup(impl);
+}
+
+static void base64_encode(const uint8_t *data, size_t len, char *enc, char pad)
+{
+ static const char tab[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+ size_t i;
+ for (i = 0; i < len; i += 3) {
+ uint32_t v;
+ v = data[i+0] << 16;
+ v |= (i+1 < len ? data[i+1] : 0) << 8;
+ v |= (i+2 < len ? data[i+2] : 0);
+ *enc++ = tab[(v >> (3*6)) & 0x3f];
+ *enc++ = tab[(v >> (2*6)) & 0x3f];
+ *enc++ = i+1 < len ? tab[(v >> (1*6)) & 0x3f] : pad;
+ *enc++ = i+2 < len ? tab[(v >> (0*6)) & 0x3f] : pad;
+ }
+ *enc = '\0';
+}
+
+static size_t base64_decode(const char *data, size_t len, uint8_t *dec)
+{
+ uint8_t tab[] = {
+ 62, -1, -1, -1, 63, 52, 53, 54, 55, 56,
+ 57, 58, 59, 60, 61, -1, -1, -1, -1, -1,
+ -1, -1, 0, 1, 2, 3, 4, 5, 6, 7,
+ 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+ 18, 19, 20, 21, 22, 23, 24, 25, -1, -1,
+ -1, -1, -1, -1, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
+ 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 };
+ size_t i, j;
+ for (i = 0, j = 0; i < len; i += 4) {
+ uint32_t v;
+ v = tab[data[i+0]-43] << (3*6);
+ v |= tab[data[i+1]-43] << (2*6);
+ v |= (data[i+2] == '=' ? 0 : tab[data[i+2]-43]) << (1*6);
+ v |= (data[i+3] == '=' ? 0 : tab[data[i+3]-43]);
+ dec[j++] = (v >> 16) & 0xff;
+ if (data[i+2] != '=') dec[j++] = (v >> 8) & 0xff;
+ if (data[i+3] != '=') dec[j++] = v & 0xff;
+ }
+ return j;
+}
+
+static int rsa_encrypt(uint8_t *data, int len, uint8_t *res)
+{
+ RSA *rsa;
+ uint8_t modulus[256];
+ uint8_t exponent[8];
+ size_t size;
+ BIGNUM *n_bn = NULL;
+ BIGNUM *e_bn = NULL;
+ char n[] =
+ "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC"
+ "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR"
+ "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB"
+ "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ"
+ "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh"
+ "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew==";
+ char e[] = "AQAB";
+
+ rsa = RSA_new();
+
+ size = base64_decode(n, strlen(n), modulus);
+ n_bn = BN_bin2bn(modulus, size, NULL);
+
+ size = base64_decode(e, strlen(e), exponent);
+ e_bn = BN_bin2bn(exponent, size, NULL);
+
+ RSA_set0_key(rsa, n_bn, e_bn, NULL);
+
+ size = RSA_public_encrypt(len, data, res, rsa, RSA_PKCS1_OAEP_PADDING);
+ RSA_free(rsa);
+ return size;
+}
+
+static int rtsp_do_announce(struct impl *impl)
+{
+ const char *host;
+ uint8_t rsakey[512];
+ char key[512*2];
+ char iv[16*2];
+ int res, frames, i, ip_version;
+ char *sdp;
+ char local_ip[256];
+ int min_latency;
+ min_latency = DEFAULT_LATENCY;
+ host = pw_properties_get(impl->props, "raop.hostname");
+
+ if (impl->protocol == PROTO_TCP)
+ frames = FRAMES_PER_TCP_PACKET;
+ else
+ frames = FRAMES_PER_UDP_PACKET;
+
+ impl->block_size = frames * impl->frame_size;
+
+ pw_rtsp_client_get_local_ip(impl->rtsp, &ip_version,
+ local_ip, sizeof(local_ip));
+
+ switch (impl->encryption) {
+ case CRYPTO_NONE:
+ asprintf(&sdp, "v=0\r\n"
+ "o=iTunes %s 0 IN IP%d %s\r\n"
+ "s=iTunes\r\n"
+ "c=IN IP%d %s\r\n"
+ "t=0 0\r\n"
+ "m=audio 0 RTP/AVP 96\r\n"
+ "a=rtpmap:96 AppleLossless\r\n"
+ "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n",
+ impl->session_id, ip_version, local_ip,
+ ip_version, host, frames);
+ break;
+
+ case CRYPTO_AUTH_SETUP:
+ asprintf(&sdp, "v=0\r\n"
+ "o=iTunes %s 0 IN IP%d %s\r\n"
+ "s=iTunes\r\n"
+ "c=IN IP%d %s\r\n"
+ "t=0 0\r\n"
+ "m=audio 0 RTP/AVP 96\r\n"
+ "a=rtpmap:96 AppleLossless\r\n"
+ "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n"
+ "a=min-latency:%d",
+ impl->session_id, ip_version, local_ip,
+ ip_version, host, frames, min_latency);
+ break;
+
+ case CRYPTO_RSA:
+ if (pw_getrandom(impl->key, sizeof(impl->key), 0) < 0 ||
+ pw_getrandom(impl->iv, sizeof(impl->iv), 0) < 0)
+ return -errno;
+
+ AES_set_encrypt_key(impl->key, 128, &impl->aes);
+
+ i = rsa_encrypt(impl->key, 16, rsakey);
+ base64_encode(rsakey, i, key, '=');
+ base64_encode(impl->iv, 16, iv, '=');
+
+ asprintf(&sdp, "v=0\r\n"
+ "o=iTunes %s 0 IN IP%d %s\r\n"
+ "s=iTunes\r\n"
+ "c=IN IP%d %s\r\n"
+ "t=0 0\r\n"
+ "m=audio 0 RTP/AVP 96\r\n"
+ "a=rtpmap:96 AppleLossless\r\n"
+ "a=fmtp:96 %d 0 16 40 10 14 2 255 0 0 44100\r\n"
+ "a=rsaaeskey:%s\r\n"
+ "a=aesiv:%s\r\n",
+ impl->session_id, ip_version, local_ip,
+ ip_version, host, frames, key, iv);
+ break;
+ default:
+ return -ENOTSUP;
+ }
+ res = pw_rtsp_client_send(impl->rtsp, "ANNOUNCE", &impl->headers->dict,
+ "application/sdp", sdp, rtsp_announce_reply, impl);
+ free(sdp);
+
+ return res;
+}
+
+static int rtsp_auth_setup_reply(void *data, int status, const struct spa_dict *headers)
+{
+ struct impl *impl = data;
+
+ pw_log_info("reply %d", status);
+
+ return rtsp_do_announce(impl);
+}
+
+static int rtsp_do_auth_setup(struct impl *impl)
+{
+ static const unsigned char content[33] =
+ "\x01"
+ "\x59\x02\xed\xe9\x0d\x4e\xf2\xbd\x4c\xb6\x8a\x63\x30\x03\x82\x07"
+ "\xa9\x4d\xbd\x50\xd8\xaa\x46\x5b\x5d\x8c\x01\x2a\x0c\x7e\x1d\x4e";
+
+ return pw_rtsp_client_url_send(impl->rtsp, "/auth-setup", "POST", &impl->headers->dict,
+ "application/octet-stream", content, sizeof(content),
+ rtsp_auth_setup_reply, impl);
+}
+
+static const char *find_attr(char **tokens, const char *key)
+{
+ int i;
+ char *p, *s;
+ for (i = 0; tokens[i]; i++) {
+ if (!spa_strstartswith(tokens[i], key))
+ continue;
+ p = tokens[i] + strlen(key);
+ if ((s = rindex(p, '"')) == NULL)
+ continue;
+ *s = '\0';
+ if ((s = index(p, '"')) == NULL)
+ continue;
+ return s+1;
+ }
+ return NULL;
+}
+
+static int rtsp_auth_reply(void *data, int status, const struct spa_dict *headers)
+{
+ struct impl *impl = data;
+ int res = 0;
+
+ pw_log_info("auth %d", status);
+
+ switch (status) {
+ case 200:
+ if (impl->encryption == CRYPTO_AUTH_SETUP)
+ res = rtsp_do_auth_setup(impl);
+ else
+ res = rtsp_do_announce(impl);
+ break;
+ }
+ return res;
+}
+
+SPA_PRINTF_FUNC(2,3)
+static int MD5_hash(char hash[MD5_HASH_LENGTH+1], const char *fmt, ...)
+{
+ unsigned char d[MD5_DIGEST_LENGTH];
+ int i;
+ va_list args;
+ char buffer[1024];
+
+ va_start(args, fmt);
+ vsnprintf(buffer, sizeof(buffer), fmt, args);
+ va_end(args);
+
+ MD5((unsigned char*) buffer, strlen(buffer), d);
+ for (i = 0; i < MD5_DIGEST_LENGTH; i++)
+ sprintf(&hash[2*i], "%02x", (uint8_t) d[i]);
+ hash[MD5_HASH_LENGTH] = '\0';
+ return 0;
+}
+
+static int rtsp_do_auth(struct impl *impl, const struct spa_dict *headers)
+{
+ const char *str;
+ char **tokens;
+ int n_tokens;
+ char auth[1024];
+
+ if (impl->password == NULL)
+ return -ENOTSUP;
+
+ if ((str = spa_dict_lookup(headers, "WWW-Authenticate")) == NULL)
+ return -ENOENT;
+
+ pw_log_info("Auth: %s", str);
+
+ tokens = pw_split_strv(str, " ", INT_MAX, &n_tokens);
+ if (tokens == NULL || tokens[0] == NULL)
+ goto error;
+
+ if (spa_streq(tokens[0], "Basic")) {
+ char buf[256];
+ char enc[512];
+ spa_scnprintf(buf, sizeof(buf), "%s:%s", DEFAULT_USER_NAME, impl->password);
+ base64_encode((uint8_t*)buf, strlen(buf), enc, '=');
+ spa_scnprintf(auth, sizeof(auth), "Basic %s", enc);
+ }
+ else if (spa_streq(tokens[0], "Digest")) {
+ const char *realm, *nonce, *url;
+ char h1[MD5_HASH_LENGTH+1];
+ char h2[MD5_HASH_LENGTH+1];
+ char resp[MD5_HASH_LENGTH+1];
+
+ realm = find_attr(tokens, "realm");
+ nonce = find_attr(tokens, "nonce");
+ if (realm == NULL || nonce == NULL)
+ goto error;
+
+ url = pw_rtsp_client_get_url(impl->rtsp);
+
+ MD5_hash(h1, "%s:%s:%s", DEFAULT_USER_NAME, realm, impl->password);
+ MD5_hash(h2, "OPTIONS:%s", url);
+ MD5_hash(resp, "%s:%s:%s", h1, nonce, h2);
+
+ spa_scnprintf(auth, sizeof(auth),
+ "username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", response=\"%s\"",
+ DEFAULT_USER_NAME, realm, nonce, url, resp);
+ }
+ else
+ goto error;
+
+ pw_properties_setf(impl->headers, "Authorization", "%s %s",
+ tokens[0], auth);
+ pw_free_strv(tokens);
+
+ pw_rtsp_client_send(impl->rtsp, "OPTIONS", &impl->headers->dict,
+ NULL, NULL, rtsp_auth_reply, impl);
+
+ return 0;
+error:
+ pw_free_strv(tokens);
+ return -EINVAL;
+}
+
+static int rtsp_options_reply(void *data, int status, const struct spa_dict *headers)
+{
+ struct impl *impl = data;
+ int res = 0;
+
+ pw_log_info("options %d", status);
+
+ switch (status) {
+ case 401:
+ res = rtsp_do_auth(impl, headers);
+ break;
+ case 200:
+ if (impl->encryption == CRYPTO_AUTH_SETUP)
+ res = rtsp_do_auth_setup(impl);
+ else
+ res = rtsp_do_announce(impl);
+ break;
+ }
+ return res;
+}
+
+static void rtsp_connected(void *data)
+{
+ struct impl *impl = data;
+ uint32_t sci[2];
+ uint8_t rac[16];
+ char sac[16*4];
+
+ pw_log_info("connected");
+
+ impl->connected = true;
+
+ if (pw_getrandom(sci, sizeof(sci), 0) < 0 ||
+ pw_getrandom(rac, sizeof(rac), 0) < 0) {
+ pw_log_error("error generating random data: %m");
+ return;
+ }
+
+ pw_properties_setf(impl->headers, "Client-Instance",
+ "%08x%08x", sci[0], sci[1]);
+
+ base64_encode(rac, sizeof(rac), sac, '\0');
+ pw_properties_set(impl->headers, "Apple-Challenge", sac);
+
+ pw_properties_set(impl->headers, "User-Agent", DEFAULT_USER_AGENT);
+
+ pw_rtsp_client_send(impl->rtsp, "OPTIONS", &impl->headers->dict,
+ NULL, NULL, rtsp_options_reply, impl);
+}
+
+static void connection_cleanup(struct impl *impl)
+{
+ impl->ready = false;
+ if (impl->server_source != NULL) {
+ pw_loop_destroy_source(impl->loop, impl->server_source);
+ impl->server_source = NULL;
+ }
+ if (impl->server_fd >= 0) {
+ close(impl->server_fd);
+ impl->server_fd = -1;
+ }
+ if (impl->control_source != NULL) {
+ pw_loop_destroy_source(impl->loop, impl->control_source);
+ impl->control_source = NULL;
+ }
+ if (impl->control_fd >= 0) {
+ close(impl->control_fd);
+ impl->control_fd = -1;
+ }
+ if (impl->timing_source != NULL) {
+ pw_loop_destroy_source(impl->loop, impl->timing_source);
+ impl->timing_source = NULL;
+ }
+ if (impl->timing_fd >= 0) {
+ close(impl->timing_fd);
+ impl->timing_fd = -1;
+ }
+}
+
+static void rtsp_disconnected(void *data)
+{
+ struct impl *impl = data;
+ pw_log_info("disconnected");
+ impl->connected = false;
+ connection_cleanup(impl);
+}
+
+static void rtsp_error(void *data, int res)
+{
+ pw_log_error("error %d", res);
+}
+
+static void rtsp_message(void *data, int status,
+ const struct spa_dict *headers)
+{
+ const struct spa_dict_item *it;
+ pw_log_info("message %d", status);
+ spa_dict_for_each(it, headers)
+ pw_log_info(" %s: %s", it->key, it->value);
+
+}
+
+static const struct pw_rtsp_client_events rtsp_events = {
+ PW_VERSION_RTSP_CLIENT_EVENTS,
+ .connected = rtsp_connected,
+ .error = rtsp_error,
+ .disconnected = rtsp_disconnected,
+ .message = rtsp_message,
+};
+
+static void stream_state_changed(void *d, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct impl *impl = d;
+ switch (state) {
+ case PW_STREAM_STATE_ERROR:
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_impl_module_schedule_destroy(impl->module);
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ rtsp_do_flush(impl);
+ break;
+ case PW_STREAM_STATE_STREAMING:
+ rtsp_do_record(impl);
+ break;
+ default:
+ break;
+ }
+}
+
+static int rtsp_do_connect(struct impl *impl)
+{
+ const char *hostname, *port;
+ uint32_t session_id;
+
+ if (impl->connected) {
+ if (!impl->ready)
+ return rtsp_do_announce(impl);
+ return 0;
+ }
+
+ hostname = pw_properties_get(impl->props, "raop.hostname");
+ port = pw_properties_get(impl->props, "raop.port");
+ if (hostname == NULL || port == NULL)
+ return -EINVAL;
+
+ if (pw_getrandom(&session_id, sizeof(session_id), 0) < 0)
+ return -errno;
+
+ spa_scnprintf(impl->session_id, sizeof(impl->session_id), "%u", session_id);
+
+ return pw_rtsp_client_connect(impl->rtsp, hostname, atoi(port), impl->session_id);
+}
+
+static int rtsp_teardown_reply(void *data, int status, const struct spa_dict *headers)
+{
+ struct impl *impl = data;
+ const char *str;
+
+ pw_log_info("reply");
+
+ connection_cleanup(impl);
+
+ if ((str = spa_dict_lookup(headers, "Connection")) != NULL) {
+ if (spa_streq(str, "close"))
+ pw_rtsp_client_disconnect(impl->rtsp);
+ }
+ return 0;
+}
+
+static int rtsp_do_teardown(struct impl *impl)
+{
+ if (!impl->ready)
+ return 0;
+
+ return pw_rtsp_client_send(impl->rtsp, "TEARDOWN", NULL,
+ NULL, NULL, rtsp_teardown_reply, impl);
+}
+
+static void stream_param_changed(void *data, uint32_t id, const struct spa_pod *param)
+{
+ struct impl *impl = data;
+
+ switch (id) {
+ case SPA_PARAM_Format:
+ if (param == NULL)
+ rtsp_do_teardown(impl);
+ else
+ rtsp_do_connect(impl);
+ break;
+ default:
+ break;
+ }
+}
+
+static const struct pw_stream_events playback_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = stream_destroy,
+ .state_changed = stream_state_changed,
+ .param_changed = stream_param_changed,
+ .process = playback_stream_process
+};
+
+static int create_stream(struct impl *impl)
+{
+ int res;
+ uint32_t n_params;
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b;
+
+ impl->stream = pw_stream_new(impl->core, "RAOP sink", impl->stream_props);
+ impl->stream_props = NULL;
+
+ if (impl->stream == NULL)
+ return -errno;
+
+ pw_stream_add_listener(impl->stream,
+ &impl->stream_listener,
+ &playback_stream_events, impl);
+
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_format_audio_raw_build(&b,
+ SPA_PARAM_EnumFormat, &impl->info);
+
+ if ((res = pw_stream_connect(impl->stream,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0)
+ return res;
+
+ impl->headers = pw_properties_new(NULL, NULL);
+
+ impl->rtsp = pw_rtsp_client_new(impl->loop, NULL, 0);
+ if (impl->rtsp == NULL)
+ return -errno;
+
+ pw_rtsp_client_add_listener(impl->rtsp, &impl->rtsp_listener,
+ &rtsp_events, impl);
+
+ return 0;
+}
+
+static void core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct impl *impl = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = core_error,
+};
+
+static void core_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->core_listener);
+ impl->core = NULL;
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ .destroy = core_destroy,
+};
+
+static void impl_destroy(struct impl *impl)
+{
+ if (impl->stream)
+ pw_stream_destroy(impl->stream);
+ if (impl->core && impl->do_disconnect)
+ pw_core_disconnect(impl->core);
+
+ if (impl->rtsp)
+ pw_rtsp_client_destroy(impl->rtsp);
+
+ pw_properties_free(impl->headers);
+ pw_properties_free(impl->stream_props);
+ pw_properties_free(impl->props);
+ free(impl->password);
+ free(impl);
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->module_listener);
+ impl_destroy(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_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));
+}
+
+static int calc_frame_size(struct spa_audio_info_raw *info)
+{
+ int res = info->channels;
+ switch (info->format) {
+ case SPA_AUDIO_FORMAT_U8:
+ case SPA_AUDIO_FORMAT_S8:
+ case SPA_AUDIO_FORMAT_ALAW:
+ case SPA_AUDIO_FORMAT_ULAW:
+ return res;
+ case SPA_AUDIO_FORMAT_S16:
+ case SPA_AUDIO_FORMAT_S16_OE:
+ case SPA_AUDIO_FORMAT_U16:
+ return res * 2;
+ case SPA_AUDIO_FORMAT_S24:
+ case SPA_AUDIO_FORMAT_S24_OE:
+ case SPA_AUDIO_FORMAT_U24:
+ return res * 3;
+ case SPA_AUDIO_FORMAT_S24_32:
+ case SPA_AUDIO_FORMAT_S24_32_OE:
+ case SPA_AUDIO_FORMAT_S32:
+ case SPA_AUDIO_FORMAT_S32_OE:
+ case SPA_AUDIO_FORMAT_U32:
+ case SPA_AUDIO_FORMAT_U32_OE:
+ case SPA_AUDIO_FORMAT_F32:
+ case SPA_AUDIO_FORMAT_F32_OE:
+ return res * 4;
+ case SPA_AUDIO_FORMAT_F64:
+ case SPA_AUDIO_FORMAT_F64_OE:
+ return res * 8;
+ default:
+ return 0;
+ }
+}
+
+static void copy_props(struct impl *impl, struct pw_properties *props, const char *key)
+{
+ const char *str;
+ if ((str = pw_properties_get(props, key)) != NULL) {
+ if (pw_properties_get(impl->stream_props, key) == NULL)
+ pw_properties_set(impl->stream_props, key, str);
+ }
+}
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props = NULL;
+ uint32_t id = pw_global_get_id(pw_impl_module_get_global(module));
+ uint32_t pid = getpid();
+ struct impl *impl;
+ const char *str;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+ impl->server_fd = -1;
+ impl->control_fd = -1;
+ impl->timing_fd = -1;
+
+ if (args == NULL)
+ args = "";
+
+ props = pw_properties_new_string(args);
+ if (props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+ impl->props = props;
+
+ impl->stream_props = pw_properties_new(NULL, NULL);
+ if (impl->stream_props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto error;
+ }
+
+ impl->module = module;
+ impl->context = context;
+ impl->loop = pw_context_get_main_loop(context);
+
+ if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
+
+ if (pw_properties_get(props, PW_KEY_MEDIA_CLASS) == NULL)
+ pw_properties_set(props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
+
+ if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_NAME, "raop-sink-%u-%u", pid, id);
+ if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_DESCRIPTION,
+ pw_properties_get(props, PW_KEY_NODE_NAME));
+ if (pw_properties_get(props, PW_KEY_NODE_LATENCY) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_LATENCY, "352/44100");
+
+ if ((str = pw_properties_get(props, "stream.props")) != NULL)
+ pw_properties_update_string(impl->stream_props, str, strlen(str));
+
+ 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);
+ copy_props(impl, props, PW_KEY_NODE_GROUP);
+ copy_props(impl, props, PW_KEY_NODE_LATENCY);
+ copy_props(impl, props, PW_KEY_NODE_VIRTUAL);
+ copy_props(impl, props, PW_KEY_MEDIA_CLASS);
+
+ parse_audio_info(impl->stream_props, &impl->info);
+
+ impl->frame_size = calc_frame_size(&impl->info);
+ if (impl->frame_size == 0) {
+ pw_log_error("unsupported audio format:%d channels:%d",
+ impl->info.format, impl->info.channels);
+ res = -EINVAL;
+ goto error;
+ }
+
+ if ((str = pw_properties_get(props, "raop.transport")) == NULL)
+ str = "udp";
+ if (spa_streq(str, "udp"))
+ impl->protocol = PROTO_UDP;
+ else if (spa_streq(str, "tcp"))
+ impl->protocol = PROTO_TCP;
+ else {
+ pw_log_error( "can't handle transport %s", str);
+ res = -EINVAL;
+ goto error;
+ }
+
+ if ((str = pw_properties_get(props, "raop.encryption.type")) == NULL)
+ str = "none";
+ if (spa_streq(str, "none"))
+ impl->encryption = CRYPTO_NONE;
+ else if (spa_streq(str, "RSA"))
+ impl->encryption = CRYPTO_RSA;
+ else if (spa_streq(str, "auth_setup"))
+ impl->encryption = CRYPTO_AUTH_SETUP;
+ else {
+ pw_log_error( "can't handle encryption type %s", str);
+ res = -EINVAL;
+ goto error;
+ }
+
+ if ((str = pw_properties_get(props, "raop.audio.codec")) == NULL)
+ str = "PCM";
+ if (spa_streq(str, "PCM"))
+ impl->codec = CODEC_PCM;
+ else if (spa_streq(str, "ALAC"))
+ impl->codec = CODEC_ALAC;
+ else {
+ pw_log_error( "can't handle codec type %s", str);
+ res = -EINVAL;
+ goto error;
+ }
+ str = pw_properties_get(props, "raop.password");
+ impl->password = str ? strdup(str) : NULL;
+
+ 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);
+ impl->core = pw_context_connect(impl->context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ impl->do_disconnect = true;
+ }
+ if (impl->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto error;
+ }
+
+ pw_proxy_add_listener((struct pw_proxy*)impl->core,
+ &impl->core_proxy_listener,
+ &core_proxy_events, impl);
+ pw_core_add_listener(impl->core,
+ &impl->core_listener,
+ &core_events, impl);
+
+ if ((res = create_stream(impl)) < 0)
+ goto error;
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+
+error:
+ impl_destroy(impl);
+ return res;
+}
diff --git a/src/modules/module-raop/rtsp-client.c b/src/modules/module-raop/rtsp-client.c
new file mode 100644
index 0000000..4ac9a31
--- /dev/null
+++ b/src/modules/module-raop/rtsp-client.c
@@ -0,0 +1,631 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <spa/utils/result.h>
+
+#include "rtsp-client.h"
+
+#define pw_rtsp_client_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_rtsp_client_events, m, v, ##__VA_ARGS__)
+#define pw_rtsp_client_emit_destroy(c) pw_rtsp_client_emit(c, destroy, 0)
+#define pw_rtsp_client_emit_connected(c) pw_rtsp_client_emit(c, connected, 0)
+#define pw_rtsp_client_emit_disconnected(c) pw_rtsp_client_emit(c, disconnected, 0)
+#define pw_rtsp_client_emit_error(c,r) pw_rtsp_client_emit(c, error, 0, r)
+#define pw_rtsp_client_emit_message(c,...) pw_rtsp_client_emit(c, message, 0, __VA_ARGS__)
+
+struct message {
+ struct spa_list link;
+ void *data;
+ size_t len;
+ size_t offset;
+ uint32_t cseq;
+ int (*reply) (void *user_data, int status, const struct spa_dict *headers);
+ void *user_data;
+};
+
+enum client_recv_state {
+ CLIENT_RECV_NONE,
+ CLIENT_RECV_STATUS,
+ CLIENT_RECV_HEADERS,
+ CLIENT_RECV_CONTENT,
+};
+
+struct pw_rtsp_client {
+ struct pw_loop *loop;
+ struct pw_properties *props;
+
+ struct spa_hook_list listener_list;
+
+ char *session_id;
+ char *url;
+
+ union {
+ struct sockaddr sa;
+ struct sockaddr_in in;
+ struct sockaddr_in6 in6;
+ } local_addr;
+
+ struct spa_source *source;
+ unsigned int connecting:1;
+ unsigned int need_flush:1;
+
+ enum client_recv_state recv_state;
+ int status;
+ char line_buf[1024];
+ size_t line_pos;
+ struct pw_properties *headers;
+ size_t content_length;
+
+ uint32_t cseq;
+
+ struct spa_list messages;
+ struct spa_list pending;
+
+ void *user_data;
+};
+
+struct pw_rtsp_client *pw_rtsp_client_new(struct pw_loop *main_loop,
+ struct pw_properties *props,
+ size_t user_data_size)
+{
+ struct pw_rtsp_client *client;
+
+ client = calloc(1, sizeof(*client) + user_data_size);
+ if (client == NULL)
+ return NULL;
+
+ client->loop = main_loop;
+ client->props = props;
+ if (user_data_size > 0)
+ client->user_data = SPA_PTROFF(client, sizeof(*client), void);
+
+ spa_list_init(&client->messages);
+ spa_list_init(&client->pending);
+ spa_hook_list_init(&client->listener_list);
+ client->headers = pw_properties_new(NULL, NULL);
+ client->recv_state = CLIENT_RECV_NONE;
+
+ pw_log_info("new client %p", client);
+
+ return client;
+}
+
+void pw_rtsp_client_destroy(struct pw_rtsp_client *client)
+{
+ pw_log_info("destroy client %p", client);
+ pw_rtsp_client_emit_destroy(client);
+
+ pw_rtsp_client_disconnect(client);
+ pw_properties_free(client->headers);
+ pw_properties_free(client->props);
+ spa_hook_list_clean(&client->listener_list);
+ free(client);
+}
+
+void *pw_rtsp_client_get_user_data(struct pw_rtsp_client *client)
+{
+ return client->user_data;
+}
+
+const char *pw_rtsp_client_get_url(struct pw_rtsp_client *client)
+{
+ return client->url;
+}
+
+void pw_rtsp_client_add_listener(struct pw_rtsp_client *client,
+ struct spa_hook *listener,
+ const struct pw_rtsp_client_events *events, void *data)
+{
+ spa_hook_list_append(&client->listener_list, listener, events, data);
+}
+
+const struct pw_properties *pw_rtsp_client_get_properties(struct pw_rtsp_client *client)
+{
+ return client->props;
+}
+
+int pw_rtsp_client_get_local_ip(struct pw_rtsp_client *client,
+ int *version, char *ip, size_t len)
+{
+ if (client->local_addr.sa.sa_family == AF_INET) {
+ *version = 4;
+ if (ip)
+ inet_ntop(client->local_addr.sa.sa_family,
+ &client->local_addr.in.sin_addr, ip, len);
+ } else if (client->local_addr.sa.sa_family == AF_INET6) {
+ *version = 6;
+ if (ip)
+ inet_ntop(client->local_addr.sa.sa_family,
+ &client->local_addr.in6.sin6_addr,
+ ip, len);
+ } else
+ return -EIO;
+ return 0;
+}
+
+static int handle_connect(struct pw_rtsp_client *client, int fd)
+{
+ int res, ip_version;
+ socklen_t len;
+ char local_ip[INET6_ADDRSTRLEN];
+
+ len = sizeof(res);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &res, &len) < 0) {
+ pw_log_error("getsockopt: %m");
+ return -errno;
+ }
+ if (res != 0)
+ return -res;
+
+ len = sizeof(client->local_addr.sa);
+ if (getsockname(fd, &client->local_addr.sa, &len) < 0)
+ return -errno;
+
+ if ((res = pw_rtsp_client_get_local_ip(client, &ip_version,
+ local_ip, sizeof(local_ip))) < 0)
+ return res;
+
+ if (ip_version == 4)
+ asprintf(&client->url, "rtsp://%s/%s", local_ip, client->session_id);
+ else
+ asprintf(&client->url, "rtsp://[%s]/%s", local_ip, client->session_id);
+
+ pw_log_info("connected local ip %s", local_ip);
+
+ client->connecting = false;
+
+ client->recv_state = CLIENT_RECV_STATUS;
+ pw_properties_clear(client->headers);
+ client->status = 0;
+ client->line_pos = 0;
+ client->content_length = 0;
+
+ pw_rtsp_client_emit_connected(client);
+
+ return 0;
+}
+
+static int read_line(struct pw_rtsp_client *client, char **buf)
+{
+ int res;
+
+ while (true) {
+ uint8_t c;
+
+ res = read(client->source->fd, &c, 1);
+ if (res == 0)
+ return -EPIPE;
+ if (res < 0) {
+ res = -errno;
+ if (res == -EINTR)
+ continue;
+ if (res != -EAGAIN && res != -EWOULDBLOCK)
+ return res;
+ return 0;
+ }
+ if (c == '\n') {
+ client->line_buf[client->line_pos] = '\0';
+ client->line_pos = 0;
+ if (buf)
+ *buf = client->line_buf;
+ return 1;
+ }
+ if (c == '\r')
+ continue;
+ if (client->line_pos < sizeof(client->line_buf) - 1)
+ client->line_buf[client->line_pos++] = c;
+ client->line_buf[client->line_pos] = '\0';
+ }
+ return 0;
+}
+
+static struct message *find_pending(struct pw_rtsp_client *client, uint32_t cseq)
+{
+ struct message *msg;
+ spa_list_for_each(msg, &client->pending, link) {
+ if (msg->cseq == cseq)
+ return msg;
+ }
+ return NULL;
+}
+
+static int process_status(struct pw_rtsp_client *client, char *buf)
+{
+ const char *state = NULL, *s;
+ size_t len;
+
+ pw_log_info("status: %s", buf);
+
+ s = pw_split_walk(buf, " ", &len, &state);
+ if (!spa_strstartswith(s, "RTSP/"))
+ return -EPROTO;
+
+ s = pw_split_walk(buf, " ", &len, &state);
+ if (s == NULL)
+ return -EPROTO;
+
+ client->status = atoi(s);
+ if (client->status == 0)
+ return -EPROTO;
+
+ s = pw_split_walk(buf, " ", &len, &state);
+ if (s == NULL)
+ return -EPROTO;
+
+ pw_properties_clear(client->headers);
+ client->recv_state = CLIENT_RECV_HEADERS;
+
+ return 0;
+}
+
+static void dispatch_handler(struct pw_rtsp_client *client)
+{
+ uint32_t cseq;
+ int res;
+ struct message *msg;
+
+ if (pw_properties_fetch_uint32(client->headers, "CSeq", &cseq) < 0)
+ return;
+
+ pw_log_info("received reply to request with cseq:%" PRIu32, cseq);
+
+ msg = find_pending(client, cseq);
+ if (msg) {
+ res = msg->reply(msg->user_data, client->status, &client->headers->dict);
+ spa_list_remove(&msg->link);
+ free(msg);
+
+ if (res < 0)
+ pw_log_warn("client %p: handle reply cseq:%u error: %s",
+ client, cseq, spa_strerror(res));
+ }
+ else {
+ pw_rtsp_client_emit_message(client, client->status, &client->headers->dict);
+ }
+}
+
+static void process_received_message(struct pw_rtsp_client *client)
+{
+ client->recv_state = CLIENT_RECV_STATUS;
+ dispatch_handler(client);
+}
+
+static int process_header(struct pw_rtsp_client *client, char *buf)
+{
+ if (strlen(buf) > 0) {
+ char *key = buf, *value;
+
+ value = strstr(buf, ":");
+ if (value == NULL)
+ return -EPROTO;
+
+ *value++ = '\0';
+
+ value = pw_strip(value, " ");
+
+ pw_properties_set(client->headers, key, value);
+ }
+ else {
+ const struct spa_dict_item *it;
+ spa_dict_for_each(it, &client->headers->dict)
+ pw_log_info(" %s: %s", it->key, it->value);
+
+ client->content_length = pw_properties_get_uint32(client->headers, "Content-Length", 0);
+ if (client->content_length > 0)
+ client->recv_state = CLIENT_RECV_CONTENT;
+ else
+ process_received_message(client);
+ }
+
+ return 0;
+}
+
+static int process_content(struct pw_rtsp_client *client)
+{
+ char buf[1024];
+
+ while (client->content_length > 0) {
+ const size_t max_recv = SPA_MIN(sizeof(buf), client->content_length);
+
+ ssize_t res = read(client->source->fd, buf, max_recv);
+ if (res == 0)
+ return -EPIPE;
+
+ if (res < 0) {
+ res = -errno;
+ if (res == -EAGAIN || res == -EWOULDBLOCK)
+ return 0;
+
+ return res;
+ }
+
+ spa_assert((size_t) res <= client->content_length);
+ client->content_length -= res;
+ }
+
+ if (client->content_length == 0)
+ process_received_message(client);
+
+ return 0;
+}
+
+static int process_input(struct pw_rtsp_client *client)
+{
+ if (client->recv_state == CLIENT_RECV_STATUS || client->recv_state == CLIENT_RECV_HEADERS) {
+ char *buf = NULL;
+ int res;
+
+ if ((res = read_line(client, &buf)) <= 0)
+ return res;
+
+ pw_log_debug("received line: %s", buf);
+
+ switch (client->recv_state) {
+ case CLIENT_RECV_STATUS:
+ return process_status(client, buf);
+ case CLIENT_RECV_HEADERS:
+ return process_header(client, buf);
+ default:
+ spa_assert_not_reached();
+ }
+ }
+ else if (client->recv_state == CLIENT_RECV_CONTENT) {
+ return process_content(client);
+ }
+ else {
+ spa_assert_not_reached();
+ }
+}
+
+static int flush_output(struct pw_rtsp_client *client)
+{
+ int res;
+
+ client->need_flush = false;
+
+ while (true) {
+ struct message *msg;
+ void *data;
+ size_t size;
+
+ if (spa_list_is_empty(&client->messages))
+ break;
+
+ msg = spa_list_first(&client->messages, struct message, link);
+
+ if (msg->offset < msg->len) {
+ data = SPA_PTROFF(msg->data, msg->offset, void);
+ size = msg->len - msg->offset;
+ } else {
+ pw_log_info("sent: %s", (char *)msg->data);
+ spa_list_remove(&msg->link);
+ if (msg->reply != NULL)
+ spa_list_append(&client->pending, &msg->link);
+ else
+ free(msg);
+ continue;
+ }
+
+ while (true) {
+ res = send(client->source->fd, data, size, MSG_NOSIGNAL | MSG_DONTWAIT);
+ if (res < 0) {
+ res = -errno;
+ if (res == -EINTR)
+ continue;
+ if (res != -EAGAIN && res != -EWOULDBLOCK)
+ pw_log_warn("client %p: send %zu, error %d: %m",
+ client, size, res);
+ return res;
+ }
+ msg->offset += res;
+ break;
+ }
+ }
+ return 0;
+}
+
+static void
+on_source_io(void *data, int fd, uint32_t mask)
+{
+ struct pw_rtsp_client *client = data;
+ int res;
+
+ if (mask & (SPA_IO_ERR | SPA_IO_HUP)) {
+ res = -EPIPE;
+ goto error;
+ }
+ if (mask & SPA_IO_IN) {
+ if ((res = process_input(client)) < 0)
+ goto error;
+ }
+ if (mask & SPA_IO_OUT || client->need_flush) {
+ if (client->connecting) {
+ if ((res = handle_connect(client, fd)) < 0)
+ goto error;
+ }
+ res = flush_output(client);
+ if (res >= 0) {
+ pw_loop_update_io(client->loop, client->source,
+ client->source->mask & ~SPA_IO_OUT);
+ } else if (res != -EAGAIN)
+ goto error;
+ }
+done:
+ return;
+error:
+ pw_log_error("%p: got connection error %d (%s)", client, res, spa_strerror(res));
+ pw_rtsp_client_emit_error(client, res);
+ pw_rtsp_client_disconnect(client);
+ goto done;
+}
+
+int pw_rtsp_client_connect(struct pw_rtsp_client *client,
+ const char *hostname, uint16_t port, const char *session_id)
+{
+ struct addrinfo hints;
+ struct addrinfo *result, *rp;
+ int res, fd;
+ char port_str[12];
+
+ if (client->source != NULL)
+ pw_rtsp_client_disconnect(client);
+
+ pw_log_info("%p: connect %s:%u", client, hostname, port);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = 0;
+ hints.ai_protocol = 0;
+
+ spa_scnprintf(port_str, sizeof(port_str), "%u", port);
+
+ if ((res = getaddrinfo(hostname, port_str, &hints, &result)) != 0) {
+ pw_log_error("getaddrinfo: %s", gai_strerror(res));
+ return -EINVAL;
+ }
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ fd = socket(rp->ai_family,
+ rp->ai_socktype | SOCK_CLOEXEC | SOCK_NONBLOCK,
+ rp->ai_protocol);
+ if (fd == -1)
+ continue;
+
+ res = connect(fd, rp->ai_addr, rp->ai_addrlen);
+ if (res == 0 || (res < 0 && errno == EINPROGRESS))
+ break;
+
+ close(fd);
+ }
+ freeaddrinfo(result);
+
+ if (rp == NULL) {
+ pw_log_error("Could not connect to %s:%u", hostname, port);
+ return -EINVAL;
+ }
+
+ client->source = pw_loop_add_io(client->loop, fd,
+ SPA_IO_IN | SPA_IO_OUT | SPA_IO_HUP | SPA_IO_ERR,
+ true, on_source_io, client);
+
+ if (client->source == NULL) {
+ res = -errno;
+ pw_log_error("%p: source create failed: %m", client);
+ close(fd);
+ return res;
+ }
+ client->connecting = true;
+ free(client->session_id);
+ client->session_id = strdup(session_id);
+ pw_log_info("%p: connecting", client);
+
+ return 0;
+}
+
+int pw_rtsp_client_disconnect(struct pw_rtsp_client *client)
+{
+ if (client->source == NULL)
+ return 0;
+
+ pw_loop_destroy_source(client->loop, client->source);
+ client->source = NULL;
+ free(client->url);
+ client->url = NULL;
+ free(client->session_id);
+ client->session_id = NULL;
+ pw_rtsp_client_emit_disconnected(client);
+ return 0;
+}
+
+int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url,
+ const char *cmd, const struct spa_dict *headers,
+ const char *content_type, const void *content, size_t content_length,
+ int (*reply) (void *user_data, int status, const struct spa_dict *headers),
+ void *user_data)
+{
+ FILE *f;
+ size_t len;
+ const struct spa_dict_item *it;
+ struct message *msg;
+ uint32_t cseq;
+
+ if ((f = open_memstream((char**)&msg, &len)) == NULL)
+ return -errno;
+
+ fseek(f, sizeof(*msg), SEEK_SET);
+
+ cseq = ++client->cseq;
+
+ fprintf(f, "%s %s RTSP/1.0\r\n", cmd, url);
+ fprintf(f, "CSeq: %" PRIu32 "\r\n", cseq);
+
+ if (headers != NULL) {
+ spa_dict_for_each(it, headers)
+ fprintf(f, "%s: %s\r\n", it->key, it->value);
+ }
+ if (content_type != NULL && content != NULL) {
+ fprintf(f, "Content-Type: %s\r\nContent-Length: %zu\r\n",
+ content_type, content_length);
+ }
+ fprintf(f, "\r\n");
+
+ if (content_type && content)
+ fwrite(content, 1, content_length, f);
+
+ fclose(f);
+
+ msg->data = SPA_PTROFF(msg, sizeof(*msg), void);
+ msg->len = len - sizeof(*msg);
+ msg->offset = 0;
+ msg->reply = reply;
+ msg->user_data = user_data;
+ msg->cseq = cseq;
+
+ spa_list_append(&client->messages, &msg->link);
+
+ client->need_flush = true;
+ if (client->source && !(client->source->mask & SPA_IO_OUT)) {
+ pw_loop_update_io(client->loop, client->source,
+ client->source->mask | SPA_IO_OUT);
+ }
+ return 0;
+}
+
+int pw_rtsp_client_send(struct pw_rtsp_client *client,
+ const char *cmd, const struct spa_dict *headers,
+ const char *content_type, const char *content,
+ int (*reply) (void *user_data, int status, const struct spa_dict *headers),
+ void *user_data)
+{
+ const size_t content_length = content ? strlen(content) : 0;
+
+ return pw_rtsp_client_url_send(client, client->url, cmd, headers,
+ content_type, content, content_length,
+ reply, user_data);
+}
diff --git a/src/modules/module-raop/rtsp-client.h b/src/modules/module-raop/rtsp-client.h
new file mode 100644
index 0000000..014468b
--- /dev/null
+++ b/src/modules/module-raop/rtsp-client.h
@@ -0,0 +1,92 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_RTSP_CLIENT_H
+#define PIPEWIRE_RTSP_CLIENT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+
+#include <pipewire/pipewire.h>
+
+struct pw_rtsp_client;
+
+struct pw_rtsp_client_events {
+#define PW_VERSION_RTSP_CLIENT_EVENTS 0
+ uint32_t version;
+
+ void (*destroy) (void *data);
+
+ void (*connected) (void *data);
+ void (*error) (void *data, int res);
+ void (*disconnected) (void *data);
+
+ void (*message) (void *data, int status,
+ const struct spa_dict *headers);
+
+};
+
+struct pw_rtsp_client * pw_rtsp_client_new(struct pw_loop *main_loop,
+ struct pw_properties *props,
+ size_t user_data_size);
+
+void pw_rtsp_client_destroy(struct pw_rtsp_client *client);
+
+void *pw_rtsp_client_get_user_data(struct pw_rtsp_client *client);
+const char *pw_rtsp_client_get_url(struct pw_rtsp_client *client);
+
+void pw_rtsp_client_add_listener(struct pw_rtsp_client *client,
+ struct spa_hook *listener,
+ const struct pw_rtsp_client_events *events, void *data);
+
+const struct pw_properties *pw_rtsp_client_get_properties(struct pw_rtsp_client *client);
+
+int pw_rtsp_client_connect(struct pw_rtsp_client *client,
+ const char *hostname, uint16_t port, const char *session_id);
+int pw_rtsp_client_disconnect(struct pw_rtsp_client *client);
+
+int pw_rtsp_client_get_local_ip(struct pw_rtsp_client *client,
+ int *version, char *ip, size_t len);
+
+int pw_rtsp_client_url_send(struct pw_rtsp_client *client, const char *url,
+ const char *cmd, const struct spa_dict *headers,
+ const char *content_type, const void *content, size_t content_length,
+ int (*reply) (void *user_data, int status, const struct spa_dict *headers),
+ void *user_data);
+
+int pw_rtsp_client_send(struct pw_rtsp_client *client,
+ const char *cmd, const struct spa_dict *headers,
+ const char *content_type, const char *content,
+ int (*reply) (void *user_data, int status, const struct spa_dict *headers),
+ void *user_data);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_RTSP_CLIENT_H */
diff --git a/src/modules/module-roc-sink.c b/src/modules/module-roc-sink.c
new file mode 100644
index 0000000..86bd983
--- /dev/null
+++ b/src/modules/module-roc-sink.c
@@ -0,0 +1,514 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright © 2021 Sanchayan Maity <sanchayan@asymptotic.io>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <limits.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#include <spa/utils/hook.h>
+#include <spa/utils/result.h>
+#include <spa/param/audio/format-utils.h>
+
+#include <roc/config.h>
+#include <roc/log.h>
+#include <roc/context.h>
+#include <roc/log.h>
+#include <roc/sender.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/impl.h>
+
+#include "module-roc/common.h"
+
+/** \page page_module_roc_sink PipeWire Module: ROC sink
+ *
+ * The `roc-sink` module creates a PipeWire sink that sends samples to
+ * a preconfigured receiver address. One can then connect an audio stream
+ * of any running application to that sink or make it the default sink.
+ *
+ * ## Module Options
+ *
+ * Options specific to the behavior of this module
+ *
+ * - `sink.props = {}`: properties to be passed to the sink stream
+ * - `sink.name = <str>`: node.name of the sink
+ * - `remote.ip = <str>`: remote receiver ip
+ * - `remote.source.port = <str>`: remote receiver TCP/UDP port for source packets
+ * - `remote.repair.port = <str>`: remote receiver TCP/UDP port for receiver packets
+ * - `fec.code = <str>`: Possible values: `disable`, `rs8m`, `ldpc`
+ *
+ * ## General options
+ *
+ * Options with well-known behavior:
+ *
+ * - \ref PW_KEY_NODE_NAME
+ * - \ref PW_KEY_NODE_DESCRIPTION
+ * - \ref PW_KEY_MEDIA_NAME
+ *
+ * ## Example configuration
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-roc-sink
+ * args = {
+ * local.ip = 0.0.0.0
+ * fec.code = disable
+ * remote.ip = 192.168.0.244
+ * remote.source.port = 10001
+ * remote.repair.port = 10002
+ * sink.name = "ROC Sink"
+ * sink.props = {
+ * node.name = "roc-sink"
+ * }
+ * }
+ * }
+ *]
+ *\endcode
+ *
+ */
+
+#define NAME "roc-sink"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_roc_sink_data {
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+ struct pw_properties *props;
+ struct pw_context *module_context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+ struct spa_hook core_proxy_listener;
+
+ struct pw_stream *capture;
+ struct spa_hook capture_listener;
+ struct pw_properties *capture_props;
+
+ unsigned int do_disconnect:1;
+
+ roc_endpoint *remote_source_addr;
+ roc_endpoint *remote_repair_addr;
+ roc_context *context;
+ roc_sender *sender;
+
+ roc_fec_encoding fec_code;
+ uint32_t rate;
+ char *remote_ip;
+ int remote_source_port;
+ int remote_repair_port;
+};
+
+static void stream_destroy(void *d)
+{
+ struct module_roc_sink_data *data = d;
+ spa_hook_remove(&data->capture_listener);
+ data->capture = NULL;
+}
+
+static void capture_process(void *data)
+{
+ struct module_roc_sink_data *impl = data;
+ struct pw_buffer *in;
+ struct spa_data *d;
+ roc_frame frame;
+ uint32_t i, size, offset;
+
+ if ((in = pw_stream_dequeue_buffer(impl->capture)) == NULL) {
+ pw_log_debug("Out of capture buffers: %m");
+ return;
+ }
+
+ for (i = 0; i < in->buffer->n_datas; i++) {
+ d = &in->buffer->datas[i];
+
+ offset = SPA_MIN(d->chunk->offset, d->maxsize);
+ size = SPA_MIN(d->maxsize - offset, d->chunk->size);
+
+ while (size > 0) {
+ spa_zero(frame);
+
+ frame.samples = SPA_MEMBER(d->data, offset, void);
+ frame.samples_size = size;
+
+ if (roc_sender_write(impl->sender, &frame) != 0) {
+ pw_log_warn("Failed to write to roc sink");
+ break;
+ }
+
+ offset += frame.samples_size;
+ size -= frame.samples_size;
+ }
+ }
+ pw_stream_queue_buffer(impl->capture, in);
+}
+
+static void on_core_error(void *d, uint32_t id, int seq, int res, const char *message)
+{
+ struct module_roc_sink_data *data = d;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_impl_module_schedule_destroy(data->module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+};
+
+static void on_stream_state_changed(void *d, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct module_roc_sink_data *data = d;
+
+ switch (state) {
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_log_info("stream disconnected, unloading");
+ pw_impl_module_schedule_destroy(data->module);
+ break;
+ case PW_STREAM_STATE_ERROR:
+ pw_log_error("stream error: %s", error);
+ break;
+ default:
+ break;
+ }
+}
+
+static const struct pw_stream_events in_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = stream_destroy,
+ .state_changed = on_stream_state_changed,
+ .process = capture_process
+};
+
+static void core_destroy(void *d)
+{
+ struct module_roc_sink_data *data = d;
+ spa_hook_remove(&data->core_listener);
+ data->core = NULL;
+ pw_impl_module_schedule_destroy(data->module);
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ .destroy = core_destroy,
+};
+
+static void impl_destroy(struct module_roc_sink_data *data)
+{
+ if (data->capture)
+ pw_stream_destroy(data->capture);
+ if (data->core && data->do_disconnect)
+ pw_core_disconnect(data->core);
+
+ pw_properties_free(data->capture_props);
+ pw_properties_free(data->props);
+
+ if (data->sender)
+ roc_sender_close(data->sender);
+ if (data->context)
+ roc_context_close(data->context);
+
+ if (data->remote_source_addr)
+ (void) roc_endpoint_deallocate(data->remote_source_addr);
+ if (data->remote_repair_addr)
+ (void) roc_endpoint_deallocate(data->remote_repair_addr);
+
+ free(data->remote_ip);
+ free(data);
+}
+
+static void module_destroy(void *d)
+{
+ struct module_roc_sink_data *data = d;
+ spa_hook_remove(&data->module_listener);
+ impl_destroy(data);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+static int roc_sink_setup(struct module_roc_sink_data *data)
+{
+ roc_context_config context_config;
+ roc_sender_config sender_config;
+ struct spa_audio_info_raw info = { 0 };
+ const struct spa_pod *params[1];
+ struct spa_pod_builder b;
+ uint32_t n_params;
+ uint8_t buffer[1024];
+ int res;
+ roc_protocol audio_proto, repair_proto;
+
+ memset(&context_config, 0, sizeof(context_config));
+
+ res = roc_context_open(&context_config, &data->context);
+ if (res) {
+ pw_log_error("failed to create roc context: %d", res);
+ return -EINVAL;
+ }
+
+ memset(&sender_config, 0, sizeof(sender_config));
+
+ sender_config.frame_sample_rate = data->rate;
+ sender_config.frame_channels = ROC_CHANNEL_SET_STEREO;
+ sender_config.frame_encoding = ROC_FRAME_ENCODING_PCM_FLOAT;
+ sender_config.fec_encoding = data->fec_code;
+
+ info.rate = data->rate;
+
+ /* Fixed to be the same as ROC sender config above */
+ info.channels = 2;
+ info.format = SPA_AUDIO_FORMAT_F32;
+ info.position[0] = SPA_AUDIO_CHANNEL_FL;
+ info.position[1] = SPA_AUDIO_CHANNEL_FR;
+
+ pw_properties_setf(data->capture_props, PW_KEY_NODE_RATE, "1/%d", info.rate);
+
+ res = roc_sender_open(data->context, &sender_config, &data->sender);
+ if (res) {
+ pw_log_error("failed to create roc sender: %d", res);
+ return -EINVAL;
+ }
+
+ switch (data->fec_code) {
+ case ROC_FEC_ENCODING_DEFAULT:
+ case ROC_FEC_ENCODING_RS8M:
+ audio_proto = ROC_PROTO_RTP_RS8M_SOURCE;
+ repair_proto = ROC_PROTO_RS8M_REPAIR;
+ break;
+ case ROC_FEC_ENCODING_LDPC_STAIRCASE:
+ audio_proto = ROC_PROTO_RTP_LDPC_SOURCE;
+ repair_proto = ROC_PROTO_LDPC_REPAIR;
+ break;
+ default:
+ audio_proto = ROC_PROTO_RTP;
+ repair_proto = 0;
+ break;
+ }
+
+ res = pw_roc_create_endpoint(&data->remote_source_addr, audio_proto, data->remote_ip, data->remote_source_port);
+ if (res < 0) {
+ pw_log_warn("failed to create source endpoint: %s", spa_strerror(res));
+ return res;
+ }
+
+ if (roc_sender_connect(data->sender, ROC_SLOT_DEFAULT, ROC_INTERFACE_AUDIO_SOURCE,
+ data->remote_source_addr) != 0) {
+ pw_log_error("can't connect roc sender to remote source address");
+ return -EINVAL;
+ }
+
+ if (repair_proto != 0) {
+ res = pw_roc_create_endpoint(&data->remote_repair_addr, repair_proto, data->remote_ip, data->remote_repair_port);
+ if (res < 0) {
+ pw_log_error("failed to create repair endpoint: %s", spa_strerror(res));
+ return res;
+ }
+
+ if (roc_sender_connect(data->sender, ROC_SLOT_DEFAULT, ROC_INTERFACE_AUDIO_REPAIR,
+ data->remote_repair_addr) != 0) {
+ pw_log_error("can't connect roc sender to remote repair address");
+ return -EINVAL;
+ }
+ }
+
+ data->capture = pw_stream_new(data->core,
+ "roc-sink capture", data->capture_props);
+ data->capture_props = NULL;
+ if (data->capture == NULL)
+ return -errno;
+
+ pw_stream_add_listener(data->capture,
+ &data->capture_listener,
+ &in_stream_events, data);
+
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
+ &info);
+
+ if ((res = pw_stream_connect(data->capture,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0)
+ return res;
+
+ return 0;
+}
+
+static const struct spa_dict_item module_roc_sink_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Sanchayan Maity <sanchayan@asymptotic.io>" },
+ { PW_KEY_MODULE_DESCRIPTION, "roc sink" },
+ { PW_KEY_MODULE_USAGE, "sink.name=<name for the sink> "
+ "local.ip=<local sender ip> "
+ "fec.code=<empty>|disable|rs8m|ldpc "
+ "remote.ip=<remote receiver ip> "
+ "remote.source.port=<remote receiver port for source packets> "
+ "remote.repair.port=<remote receiver port for repair packets> "
+ "sink.props= { key=val ... } " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct module_roc_sink_data *data;
+ struct pw_properties *props = NULL, *capture_props = NULL;
+ const char *str;
+ int res = 0;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ data = calloc(1, sizeof(struct module_roc_sink_data));
+ if (data == NULL)
+ return -errno;
+
+ if (args == NULL)
+ args = "";
+
+ props = pw_properties_new_string(args);
+ if (props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto out;
+ }
+ data->props = props;
+
+ capture_props = pw_properties_new(NULL, NULL);
+ if (capture_props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto out;
+ }
+ data->capture_props = capture_props;
+
+ data->module = module;
+ data->module_context = context;
+
+ if ((str = pw_properties_get(props, "sink.name")) != NULL) {
+ pw_properties_set(capture_props, PW_KEY_NODE_NAME, str);
+ pw_properties_set(props, "sink.name", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "sink.props")) != NULL)
+ pw_properties_update_string(capture_props, str, strlen(str));
+
+ if (pw_properties_get(capture_props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_set(capture_props, PW_KEY_NODE_NAME, "roc-sink");
+ if (pw_properties_get(capture_props, PW_KEY_NODE_DESCRIPTION) == NULL)
+ pw_properties_set(capture_props, PW_KEY_NODE_DESCRIPTION, "ROC Sink");
+ if (pw_properties_get(capture_props, PW_KEY_NODE_VIRTUAL) == NULL)
+ pw_properties_set(capture_props, PW_KEY_NODE_VIRTUAL, "true");
+ if (pw_properties_get(capture_props, PW_KEY_NODE_NETWORK) == NULL)
+ pw_properties_set(capture_props, PW_KEY_NODE_NETWORK, "true");
+ if ((str = pw_properties_get(capture_props, PW_KEY_MEDIA_CLASS)) == NULL)
+ pw_properties_set(capture_props, PW_KEY_MEDIA_CLASS, "Audio/Sink");
+
+ data->rate = pw_properties_get_uint32(capture_props, PW_KEY_AUDIO_RATE, data->rate);
+ if (data->rate == 0)
+ data->rate = PW_ROC_DEFAULT_RATE;
+
+ if ((str = pw_properties_get(props, "remote.ip")) != NULL) {
+ data->remote_ip = strdup(str);
+ pw_properties_set(props, "remote.ip", NULL);
+ } else {
+ pw_log_error("Remote IP not specified");
+ res = -EINVAL;
+ goto out;
+ }
+
+ if ((str = pw_properties_get(props, "remote.source.port")) != NULL) {
+ data->remote_source_port = pw_properties_parse_int(str);
+ pw_properties_set(props, "remote.source.port", NULL);
+ } else {
+ data->remote_source_port = PW_ROC_DEFAULT_SOURCE_PORT;
+ }
+
+ if ((str = pw_properties_get(props, "remote.repair.port")) != NULL) {
+ data->remote_repair_port = pw_properties_parse_int(str);
+ pw_properties_set(props, "remote.repair.port", NULL);
+ } else {
+ data->remote_repair_port = PW_ROC_DEFAULT_REPAIR_PORT;
+ }
+ 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);
+ data->fec_code = ROC_FEC_ENCODING_DEFAULT;
+ }
+ pw_log_info("using fec.code %s %d", str, data->fec_code);
+ pw_properties_set(props, "fec.code", NULL);
+ } else {
+ data->fec_code = ROC_FEC_ENCODING_DEFAULT;
+ }
+
+
+ data->core = pw_context_get_object(data->module_context, PW_TYPE_INTERFACE_Core);
+ if (data->core == NULL) {
+ str = pw_properties_get(props, PW_KEY_REMOTE_NAME);
+ data->core = pw_context_connect(data->module_context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ data->do_disconnect = true;
+ }
+ if (data->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto out;
+ }
+
+ pw_proxy_add_listener((struct pw_proxy*)data->core,
+ &data->core_proxy_listener,
+ &core_proxy_events, data);
+ pw_core_add_listener(data->core,
+ &data->core_listener,
+ &core_events, data);
+
+ if ((res = roc_sink_setup(data)) < 0)
+ goto out;
+
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_roc_sink_info));
+
+ pw_log_info("Successfully loaded module-roc-sink");
+
+ return 0;
+
+out:
+ impl_destroy(data);
+ return res;
+}
diff --git a/src/modules/module-roc-source.c b/src/modules/module-roc-source.c
new file mode 100644
index 0000000..91fe60b
--- /dev/null
+++ b/src/modules/module-roc-source.c
@@ -0,0 +1,546 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ * Copyright © 2021 Sanchayan Maity <sanchayan@asymptotic.io>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <limits.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#include <spa/utils/hook.h>
+#include <spa/utils/result.h>
+#include <spa/param/audio/format-utils.h>
+
+#include <roc/config.h>
+#include <roc/log.h>
+#include <roc/context.h>
+#include <roc/log.h>
+#include <roc/receiver.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/impl.h>
+
+#include "module-roc/common.h"
+
+/** \page page_module_roc_source PipeWire Module: ROC source
+ *
+ * The `roc-source` module creates a PipeWire source that receives samples
+ * from ROC sender and passes them to the sink it is connected to. One can
+ * then connect it to any audio device.
+ *
+ * ## Module Options
+ *
+ * Options specific to the behavior of this module
+ *
+ * - `source.props = {}`: properties to be passed to the source stream
+ * - `source.name = <str>`: node.name of the source
+ * - `local.ip = <str>`: local sender ip
+ * - `local.source.port = <str>`: local receiver TCP/UDP port for source packets
+ * - `local.repair.port = <str>`: local receiver TCP/UDP port for receiver 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`
+ *
+ * ## General options
+ *
+ * Options with well-known behavior:
+ *
+ * - \ref PW_KEY_NODE_NAME
+ * - \ref PW_KEY_NODE_DESCRIPTION
+ * - \ref PW_KEY_MEDIA_NAME
+ *
+ * ## Example configuration
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-roc-source
+ * args = {
+ * local.ip = 0.0.0.0
+ * resampler.profile = medium
+ * fec.code = disable
+ * sess.latency.msec = 5000
+ * local.source.port = 10001
+ * local.repair.port = 10002
+ * source.name = "ROC Source"
+ * source.props = {
+ * node.name = "roc-source"
+ * }
+ * }
+ * }
+ *]
+ *\endcode
+ *
+ */
+
+#define NAME "roc-source"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct module_roc_source_data {
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+ struct pw_properties *props;
+ struct pw_context *module_context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+ struct spa_hook core_proxy_listener;
+
+ struct pw_stream *playback;
+ struct spa_hook playback_listener;
+ struct pw_properties *playback_props;
+
+ unsigned int do_disconnect:1;
+ uint32_t stride;
+
+ roc_endpoint *local_source_addr;
+ roc_endpoint *local_repair_addr;
+ roc_context *context;
+ roc_receiver *receiver;
+
+ roc_resampler_profile resampler_profile;
+ roc_fec_encoding fec_code;
+ uint32_t rate;
+ char *local_ip;
+ int local_source_port;
+ int local_repair_port;
+ int sess_latency_msec;
+};
+
+static void stream_destroy(void *d)
+{
+ struct module_roc_source_data *data = d;
+ spa_hook_remove(&data->playback_listener);
+ data->playback = NULL;
+}
+
+static void playback_process(void *data)
+{
+ struct module_roc_source_data *impl = data;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ roc_frame frame;
+ uint8_t *dst;
+
+ if ((b = pw_stream_dequeue_buffer(impl->playback)) == NULL) {
+ pw_log_debug("Out of playback buffers: %m");
+ return;
+ }
+
+ buf = b->buffer;
+ if ((dst = buf->datas[0].data) == NULL)
+ return;
+
+ buf->datas[0].chunk->offset = 0;
+ buf->datas[0].chunk->stride = impl->stride;
+ buf->datas[0].chunk->size = 0;
+
+ spa_zero(frame);
+ frame.samples = dst;
+ frame.samples_size = SPA_MIN(b->requested * impl->stride, buf->datas[0].maxsize);
+
+ if (roc_receiver_read(impl->receiver, &frame) != 0) {
+ /* Handle EOF and error */
+ pw_log_error("Failed to read from roc source");
+ pw_impl_module_schedule_destroy(impl->module);
+ frame.samples_size = 0;
+ }
+
+ buf->datas[0].chunk->size = frame.samples_size;
+ b->size = frame.samples_size / impl->stride;
+
+ pw_stream_queue_buffer(impl->playback, b);
+}
+
+static void on_core_error(void *d, uint32_t id, int seq, int res, const char *message)
+{
+ struct module_roc_source_data *data = d;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_impl_module_schedule_destroy(data->module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+};
+
+static void on_stream_state_changed(void *d, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct module_roc_source_data *data = d;
+
+ switch (state) {
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_log_info("stream disconnected, unloading");
+ pw_impl_module_schedule_destroy(data->module);
+ break;
+ case PW_STREAM_STATE_ERROR:
+ pw_log_error("stream error: %s", error);
+ break;
+ default:
+ break;
+ }
+}
+
+static const struct pw_stream_events out_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = stream_destroy,
+ .state_changed = on_stream_state_changed,
+ .process = playback_process
+};
+
+static void core_destroy(void *d)
+{
+ struct module_roc_source_data *data = d;
+ spa_hook_remove(&data->core_listener);
+ data->core = NULL;
+ pw_impl_module_schedule_destroy(data->module);
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ .destroy = core_destroy,
+};
+
+static void impl_destroy(struct module_roc_source_data *data)
+{
+ if (data->playback)
+ pw_stream_destroy(data->playback);
+ if (data->core && data->do_disconnect)
+ pw_core_disconnect(data->core);
+
+ pw_properties_free(data->playback_props);
+ pw_properties_free(data->props);
+
+ if (data->receiver)
+ roc_receiver_close(data->receiver);
+ if (data->context)
+ roc_context_close(data->context);
+
+ if (data->local_source_addr)
+ (void) roc_endpoint_deallocate(data->local_source_addr);
+ if (data->local_repair_addr)
+ (void) roc_endpoint_deallocate(data->local_repair_addr);
+
+ free(data->local_ip);
+ free(data);
+}
+
+static void module_destroy(void *d)
+{
+ struct module_roc_source_data *data = d;
+ spa_hook_remove(&data->module_listener);
+ impl_destroy(data);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+static int roc_source_setup(struct module_roc_source_data *data)
+{
+ roc_context_config context_config;
+ roc_receiver_config receiver_config;
+ struct spa_audio_info_raw info = { 0 };
+ const struct spa_pod *params[1];
+ struct spa_pod_builder b;
+ uint32_t n_params;
+ uint8_t buffer[1024];
+ int res;
+ roc_protocol audio_proto, repair_proto;
+
+ spa_zero(context_config);
+ res = roc_context_open(&context_config, &data->context);
+ if (res) {
+ pw_log_error("failed to create roc context: %d", res);
+ return -EINVAL;
+ }
+
+ spa_zero(receiver_config);
+ receiver_config.frame_sample_rate = data->rate;
+ receiver_config.frame_channels = ROC_CHANNEL_SET_STEREO;
+ receiver_config.frame_encoding = ROC_FRAME_ENCODING_PCM_FLOAT;
+ receiver_config.resampler_profile = data->resampler_profile;
+
+ info.rate = data->rate;
+
+ /* Fixed to be the same as ROC receiver config above */
+ info.channels = 2;
+ info.format = SPA_AUDIO_FORMAT_F32;
+ info.position[0] = SPA_AUDIO_CHANNEL_FL;
+ info.position[1] = SPA_AUDIO_CHANNEL_FR;
+ data->stride = info.channels * sizeof(float);
+
+ pw_properties_setf(data->playback_props, PW_KEY_NODE_RATE, "1/%d", info.rate);
+
+ /*
+ * Note that target latency is in nano seconds.
+ *
+ * The session will not start playing until it accumulates the
+ * requested latency. Then if resampler is enabled, the session will
+ * adjust it's clock to keep actual latency as close as possible to
+ * the target latency. If zero, default value will be used.
+ *
+ * See API reference:
+ * https://roc-streaming.org/toolkit/docs/api/reference.html
+ */
+ receiver_config.target_latency = data->sess_latency_msec * 1000000;
+
+ res = roc_receiver_open(data->context, &receiver_config, &data->receiver);
+ if (res) {
+ pw_log_error("failed to create roc receiver: %d", res);
+ return -EINVAL;
+ }
+
+ switch (data->fec_code) {
+ case ROC_FEC_ENCODING_DEFAULT:
+ case ROC_FEC_ENCODING_RS8M:
+ audio_proto = ROC_PROTO_RTP_RS8M_SOURCE;
+ repair_proto = ROC_PROTO_RS8M_REPAIR;
+ break;
+ case ROC_FEC_ENCODING_LDPC_STAIRCASE:
+ audio_proto = ROC_PROTO_RTP_LDPC_SOURCE;
+ repair_proto = ROC_PROTO_LDPC_REPAIR;
+ break;
+ default:
+ audio_proto = ROC_PROTO_RTP;
+ repair_proto = 0;
+ break;
+ }
+
+ res = pw_roc_create_endpoint(&data->local_source_addr, audio_proto, data->local_ip, data->local_source_port);
+ if (res < 0) {
+ pw_log_error("failed to create source endpoint: %s", spa_strerror(res));
+ return res;
+ }
+
+ if (roc_receiver_bind(data->receiver, ROC_SLOT_DEFAULT, ROC_INTERFACE_AUDIO_SOURCE,
+ data->local_source_addr) != 0) {
+ pw_log_error("can't connect roc receiver to local source address");
+ return -EINVAL;
+ }
+
+ if (repair_proto != 0) {
+ res = pw_roc_create_endpoint(&data->local_repair_addr, repair_proto, data->local_ip, data->local_repair_port);
+ if (res < 0) {
+ pw_log_error("failed to create repair endpoint: %s", spa_strerror(res));
+ return res;
+ }
+
+ if (roc_receiver_bind(data->receiver, ROC_SLOT_DEFAULT, ROC_INTERFACE_AUDIO_REPAIR,
+ data->local_repair_addr) != 0) {
+ pw_log_error("can't connect roc receiver to local repair address");
+ return -EINVAL;
+ }
+ }
+
+ data->playback = pw_stream_new(data->core,
+ "roc-source playback", data->playback_props);
+ data->playback_props = NULL;
+ if (data->playback == NULL)
+ return -errno;
+
+ pw_stream_add_listener(data->playback,
+ &data->playback_listener,
+ &out_stream_events, data);
+
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
+ &info);
+
+ if ((res = pw_stream_connect(data->playback,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0)
+ return res;
+
+ return 0;
+}
+
+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 "
+ "fec.code=<empty>|disable|rs8m|ldpc "
+ "sess.latency.msec=<target network latency in milliseconds> "
+ "local.ip=<local receiver ip> "
+ "local.source.port=<local receiver port for source packets> "
+ "local.repair.port=<local receiver port for repair packets> "
+ "source.props= { key=value ... }" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct module_roc_source_data *data;
+ struct pw_properties *props = NULL, *playback_props = NULL;
+ const char *str;
+ int res = 0;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ data = calloc(1, sizeof(struct module_roc_source_data));
+ if (data == NULL)
+ return -errno;
+
+ if (args == NULL)
+ args = "";
+
+ props = pw_properties_new_string(args);
+ if (props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto out;
+ }
+ data->props = props;
+
+ playback_props = pw_properties_new(NULL, NULL);
+ if (playback_props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto out;
+ }
+ data->playback_props = playback_props;
+
+ data->module = module;
+ data->module_context = context;
+
+ if ((str = pw_properties_get(props, "source.name")) != NULL) {
+ pw_properties_set(playback_props, PW_KEY_NODE_NAME, str);
+ pw_properties_set(props, "source.name", NULL);
+ }
+
+ if ((str = pw_properties_get(props, "source.props")) != NULL)
+ pw_properties_update_string(playback_props, str, strlen(str));
+
+ if (pw_properties_get(playback_props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_set(playback_props, PW_KEY_NODE_NAME, "roc-source");
+ if (pw_properties_get(playback_props, PW_KEY_NODE_DESCRIPTION) == NULL)
+ pw_properties_set(playback_props, PW_KEY_NODE_DESCRIPTION, "ROC Source");
+ if (pw_properties_get(playback_props, PW_KEY_NODE_VIRTUAL) == NULL)
+ pw_properties_set(playback_props, PW_KEY_NODE_VIRTUAL, "true");
+ if (pw_properties_get(playback_props, PW_KEY_NODE_NETWORK) == NULL)
+ pw_properties_set(playback_props, PW_KEY_NODE_NETWORK, "true");
+
+ data->rate = pw_properties_get_uint32(playback_props, PW_KEY_AUDIO_RATE, data->rate);
+ if (data->rate == 0)
+ data->rate = PW_ROC_DEFAULT_RATE;
+
+ if ((str = pw_properties_get(props, "local.ip")) != NULL) {
+ data->local_ip = strdup(str);
+ pw_properties_set(props, "local.ip", NULL);
+ } else {
+ data->local_ip = strdup(PW_ROC_DEFAULT_IP);
+ }
+
+ if ((str = pw_properties_get(props, "local.source.port")) != NULL) {
+ data->local_source_port = pw_properties_parse_int(str);
+ pw_properties_set(props, "local.source.port", NULL);
+ } else {
+ data->local_source_port = PW_ROC_DEFAULT_SOURCE_PORT;
+ }
+
+ if ((str = pw_properties_get(props, "local.repair.port")) != NULL) {
+ data->local_repair_port = pw_properties_parse_int(str);
+ pw_properties_set(props, "local.repair.port", NULL);
+ } else {
+ data->local_repair_port = PW_ROC_DEFAULT_REPAIR_PORT;
+ }
+
+ if ((str = pw_properties_get(props, "sess.latency.msec")) != NULL) {
+ data->sess_latency_msec = pw_properties_parse_int(str);
+ pw_properties_set(props, "sess.latency.msec", NULL);
+ } else {
+ data->sess_latency_msec = PW_ROC_DEFAULT_SESS_LATENCY;
+ }
+
+ if ((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;
+ }
+ pw_properties_set(props, "resampler.profile", NULL);
+ } else {
+ data->resampler_profile = ROC_RESAMPLER_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);
+ data->fec_code = ROC_FEC_ENCODING_DEFAULT;
+ }
+ pw_properties_set(props, "fec.code", NULL);
+ } else {
+ data->fec_code = ROC_FEC_ENCODING_DEFAULT;
+ }
+
+ data->core = pw_context_get_object(data->module_context, PW_TYPE_INTERFACE_Core);
+ if (data->core == NULL) {
+ str = pw_properties_get(props, PW_KEY_REMOTE_NAME);
+ data->core = pw_context_connect(data->module_context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ data->do_disconnect = true;
+ }
+ if (data->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto out;
+ }
+
+ pw_proxy_add_listener((struct pw_proxy*)data->core,
+ &data->core_proxy_listener,
+ &core_proxy_events, data);
+ pw_core_add_listener(data->core,
+ &data->core_listener,
+ &core_events, data);
+
+ if ((res = roc_source_setup(data)) < 0)
+ goto out;
+
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_roc_source_info));
+
+ pw_log_info("Successfully loaded module-roc-source");
+
+ return 0;
+out:
+ impl_destroy(data);
+ return res;
+}
diff --git a/src/modules/module-roc/common.h b/src/modules/module-roc/common.h
new file mode 100644
index 0000000..248c66e
--- /dev/null
+++ b/src/modules/module-roc/common.h
@@ -0,0 +1,71 @@
+#ifndef MODULE_ROC_COMMON_H
+#define MODULE_ROC_COMMON_H
+
+#include <roc/config.h>
+#include <roc/endpoint.h>
+
+#include <spa/utils/string.h>
+
+#define PW_ROC_DEFAULT_IP "0.0.0.0"
+#define PW_ROC_DEFAULT_SOURCE_PORT 10001
+#define PW_ROC_DEFAULT_REPAIR_PORT 10002
+#define PW_ROC_DEFAULT_SESS_LATENCY 200
+#define PW_ROC_DEFAULT_RATE 44100
+
+static inline int pw_roc_parse_fec_encoding(roc_fec_encoding *out, const char *str)
+{
+ if (!str || !*str)
+ *out = ROC_FEC_ENCODING_DEFAULT;
+ else if (spa_streq(str, "disable"))
+ *out = ROC_FEC_ENCODING_DISABLE;
+ else if (spa_streq(str, "rs8m"))
+ *out = ROC_FEC_ENCODING_RS8M;
+ else if (spa_streq(str, "ldpc"))
+ *out = ROC_FEC_ENCODING_LDPC_STAIRCASE;
+ else
+ return -EINVAL;
+ return 0;
+}
+
+static inline int pw_roc_parse_resampler_profile(roc_resampler_profile *out, const char *str)
+{
+ if (!str || !*str)
+ *out = ROC_RESAMPLER_PROFILE_DEFAULT;
+ else if (spa_streq(str, "disable"))
+ *out = ROC_RESAMPLER_PROFILE_DISABLE;
+ else if (spa_streq(str, "high"))
+ *out = ROC_RESAMPLER_PROFILE_HIGH;
+ else if (spa_streq(str, "medium"))
+ *out = ROC_RESAMPLER_PROFILE_MEDIUM;
+ else if (spa_streq(str, "low"))
+ *out = ROC_RESAMPLER_PROFILE_LOW;
+ 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;
+
+ if (roc_endpoint_allocate(&endpoint))
+ return -ENOMEM;
+
+ if (roc_endpoint_set_protocol(endpoint, protocol))
+ goto out_error_free_ep;
+
+ if (roc_endpoint_set_host(endpoint, ip))
+ goto out_error_free_ep;
+
+ if (roc_endpoint_set_port(endpoint, port))
+ goto out_error_free_ep;
+
+ *result = endpoint;
+ return 0;
+
+out_error_free_ep:
+ (void) roc_endpoint_deallocate(endpoint);
+ return -EINVAL;
+}
+
+#endif /* MODULE_ROC_COMMON_H */
diff --git a/src/modules/module-rt.c b/src/modules/module-rt.c
new file mode 100644
index 0000000..f6b4ade
--- /dev/null
+++ b/src/modules/module-rt.c
@@ -0,0 +1,1095 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+/***
+ Copyright 2009 Lennart Poettering
+ Copyright 2010 David Henningsson <diwic@ubuntu.com>
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+***/
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/stat.h>
+#if defined(__FreeBSD__) || defined(__MidnightBSD__)
+#include <sys/thr.h>
+#endif
+#include <fcntl.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/resource.h>
+#include <sys/syscall.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/thread.h>
+
+#ifdef HAVE_DBUS
+#include <spa/support/dbus.h>
+#include <dbus/dbus.h>
+#endif
+
+/** \page page_module_rt PipeWire Module: RT
+ *
+ * The `rt` modules can give real-time priorities to processing threads.
+ *
+ * It uses the operating system's scheduler to enable realtime scheduling
+ * for certain threads to assist with low latency audio processing.
+ * This requires `RLIMIT_RTPRIO` to be set to a value that's equal to this
+ * module's `rt.prio` parameter or higher. Most distros will come with some
+ * package that configures this for certain groups or users. If this is not set
+ * up and DBus is available, then this module will fall back to using RTKit.
+ *
+ * ## Module Options
+ *
+ * - `nice.level`: The nice value set for the application thread. It improves
+ * performance of the communication with the pipewire daemon.
+ * - `rt.prio`: The realtime priority of the data thread. Higher values are
+ * higher priority.
+ * - `rt.time.soft`, `rt.time.hard`: The amount of CPU time an RT thread can
+ * consume without doing any blocking calls before the kernel kills
+ * the thread. This is a safety measure to avoid lockups of the complete
+ * system when some thread consumes 100%.
+
+ * The nice level is by default set to an invalid value so that clients don't
+ * automatically have the nice level raised.
+ *
+ * The PipeWire server processes are explicitly configured with a valid nice level.
+ *
+ * ## Example configuration
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-rt
+ * args = {
+ * #nice.level = 20
+ * #rt.prio = 88
+ * #rt.time.soft = -1
+ * #rt.time.hard = -1
+ * }
+ * flags = [ ifexists nofail ]
+ * }
+ * ]
+ *\endcode
+ */
+
+#define NAME "rt"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define REALTIME_POLICY SCHED_FIFO
+#ifdef SCHED_RESET_ON_FORK
+#define PW_SCHED_RESET_ON_FORK SCHED_RESET_ON_FORK
+#else
+/* FreeBSD compat */
+#define PW_SCHED_RESET_ON_FORK 0
+#endif
+
+#define IS_VALID_NICE_LEVEL(l) ((l)>=-20 && (l)<=19)
+
+#define DEFAULT_NICE_LEVEL 20
+#define DEFAULT_RT_PRIO_MIN 11
+#define DEFAULT_RT_PRIO 88
+#define DEFAULT_RT_TIME_SOFT -1
+#define DEFAULT_RT_TIME_HARD -1
+
+#define MODULE_USAGE "[nice.level=<priority: default "SPA_STRINGIFY(DEFAULT_NICE_LEVEL)"(don't change)>] " \
+ "[rt.prio=<priority: default "SPA_STRINGIFY(DEFAULT_RT_PRIO)">] " \
+ "[rt.time.soft=<in usec: default "SPA_STRINGIFY(DEFAULT_RT_TIME_SOFT)"] " \
+ "[rt.time.hard=<in usec: default "SPA_STRINGIFY(DEFAULT_RT_TIME_HARD)"] "
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Use realtime thread scheduling, falling back to RTKit" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+#ifdef HAVE_DBUS
+#define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1"
+#define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1"
+#define RTKIT_INTERFACE "org.freedesktop.RealtimeKit1"
+
+#define XDG_PORTAL_SERVICE_NAME "org.freedesktop.portal.Desktop"
+#define XDG_PORTAL_OBJECT_PATH "/org/freedesktop/portal/desktop"
+#define XDG_PORTAL_INTERFACE "org.freedesktop.portal.Realtime"
+
+/** \cond */
+struct pw_rtkit_bus {
+ DBusConnection *bus;
+};
+/** \endcond */
+
+struct thread {
+ struct impl *impl;
+ struct spa_list link;
+ pthread_t thread;
+ pid_t pid;
+ void *(*start)(void*);
+ void *arg;
+};
+#endif /* HAVE_DBUS */
+
+struct impl {
+ struct pw_context *context;
+
+ struct spa_thread_utils thread_utils;
+
+ int nice_level;
+ int rt_prio;
+ rlim_t rt_time_soft;
+ rlim_t rt_time_hard;
+
+ struct spa_hook module_listener;
+
+#ifdef HAVE_DBUS
+ bool use_rtkit;
+ /* For D-Bus. These are const static. */
+ const char* service_name;
+ const char* object_path;
+ const char* interface;
+ struct pw_rtkit_bus *rtkit_bus;
+
+ /* These are only for the RTKit implementation to fill in the `thread`
+ * struct. Since there's barely any overhead here we'll do this
+ * regardless of which backend is used. */
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+ struct spa_list threads_list;
+#endif
+};
+
+#ifndef RLIMIT_RTTIME
+#define RLIMIT_RTTIME 15
+#endif
+
+static pid_t _gettid(void)
+{
+#if defined(HAVE_GETTID)
+ return (pid_t) gettid();
+#elif defined(__linux__)
+ return syscall(SYS_gettid);
+#elif defined(__FreeBSD__) || defined(__MidnightBSD__)
+ long pid;
+ thr_self(&pid);
+ return (pid_t)pid;
+#else
+#error "No gettid impl"
+#endif
+}
+
+#ifdef HAVE_DBUS
+struct pw_rtkit_bus *pw_rtkit_bus_get(DBusBusType bus_type)
+{
+ struct pw_rtkit_bus *bus;
+ DBusError error;
+
+ if (getenv("DISABLE_RTKIT")) {
+ errno = ENOTSUP;
+ return NULL;
+ }
+
+ dbus_error_init(&error);
+
+ bus = calloc(1, sizeof(struct pw_rtkit_bus));
+ if (bus == NULL)
+ return NULL;
+
+ bus->bus = dbus_bus_get_private(bus_type, &error);
+ if (bus->bus == NULL)
+ goto error;
+
+ dbus_connection_set_exit_on_disconnect(bus->bus, false);
+
+ return bus;
+
+error:
+ free(bus);
+ pw_log_error("Failed to connect to %s bus: %s",
+ bus_type == DBUS_BUS_SYSTEM ? "system" : "session", error.message);
+ dbus_error_free(&error);
+ errno = ECONNREFUSED;
+ return NULL;
+}
+
+struct pw_rtkit_bus *pw_rtkit_bus_get_system(void)
+{
+ return pw_rtkit_bus_get(DBUS_BUS_SYSTEM);
+}
+
+struct pw_rtkit_bus *pw_rtkit_bus_get_session(void)
+{
+ return pw_rtkit_bus_get(DBUS_BUS_SESSION);
+}
+
+bool pw_rtkit_check_xdg_portal(struct pw_rtkit_bus *system_bus)
+{
+ if (!dbus_bus_name_has_owner(system_bus->bus, XDG_PORTAL_SERVICE_NAME, NULL)) {
+ pw_log_warn("Can't find %s. Is xdg-desktop-portal running?", XDG_PORTAL_SERVICE_NAME);
+ return false;
+ }
+
+ return true;
+}
+
+void pw_rtkit_bus_free(struct pw_rtkit_bus *system_bus)
+{
+ dbus_connection_close(system_bus->bus);
+ dbus_connection_unref(system_bus->bus);
+ free(system_bus);
+}
+
+static int translate_error(const char *name)
+{
+ pw_log_warn("RTKit error: %s", name);
+
+ if (spa_streq(name, DBUS_ERROR_NO_MEMORY))
+ return -ENOMEM;
+ if (spa_streq(name, DBUS_ERROR_SERVICE_UNKNOWN) ||
+ spa_streq(name, DBUS_ERROR_NAME_HAS_NO_OWNER))
+ return -ENOENT;
+ if (spa_streq(name, DBUS_ERROR_ACCESS_DENIED) ||
+ spa_streq(name, DBUS_ERROR_AUTH_FAILED))
+ return -EACCES;
+
+ return -EIO;
+}
+
+static long long rtkit_get_int_property(struct impl *impl, const char *propname,
+ long long *propval)
+{
+ DBusMessage *m = NULL, *r = NULL;
+ DBusMessageIter iter, subiter;
+ dbus_int64_t i64;
+ dbus_int32_t i32;
+ DBusError error;
+ int current_type;
+ long long ret;
+ struct pw_rtkit_bus *connection = impl->rtkit_bus;
+
+ dbus_error_init(&error);
+
+ if (!(m = dbus_message_new_method_call(impl->service_name,
+ impl->object_path,
+ "org.freedesktop.DBus.Properties", "Get"))) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_STRING, &impl->interface,
+ DBUS_TYPE_STRING, &propname, DBUS_TYPE_INVALID)) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(r = dbus_connection_send_with_reply_and_block(connection->bus, m, -1, &error))) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+ if (dbus_set_error_from_message(&error, r)) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+ ret = -EBADMSG;
+ dbus_message_iter_init(r, &iter);
+ while ((current_type = dbus_message_iter_get_arg_type(&iter)) != DBUS_TYPE_INVALID) {
+
+ if (current_type == DBUS_TYPE_VARIANT) {
+ dbus_message_iter_recurse(&iter, &subiter);
+
+ while ((current_type =
+ dbus_message_iter_get_arg_type(&subiter)) != DBUS_TYPE_INVALID) {
+
+ if (current_type == DBUS_TYPE_INT32) {
+ dbus_message_iter_get_basic(&subiter, &i32);
+ *propval = i32;
+ ret = 0;
+ }
+
+ if (current_type == DBUS_TYPE_INT64) {
+ dbus_message_iter_get_basic(&subiter, &i64);
+ *propval = i64;
+ ret = 0;
+ }
+
+ dbus_message_iter_next(&subiter);
+ }
+ }
+ dbus_message_iter_next(&iter);
+ }
+
+finish:
+
+ if (m)
+ dbus_message_unref(m);
+
+ if (r)
+ dbus_message_unref(r);
+
+ dbus_error_free(&error);
+
+ return ret;
+}
+
+int pw_rtkit_get_max_realtime_priority(struct impl *impl)
+{
+ long long retval;
+ int err;
+
+ err = rtkit_get_int_property(impl, "MaxRealtimePriority", &retval);
+ return err < 0 ? err : retval;
+}
+
+int pw_rtkit_get_min_nice_level(struct impl *impl, int *min_nice_level)
+{
+ long long retval;
+ int err;
+
+ err = rtkit_get_int_property(impl, "MinNiceLevel", &retval);
+ if (err >= 0)
+ *min_nice_level = retval;
+ return err;
+}
+
+long long pw_rtkit_get_rttime_usec_max(struct impl *impl)
+{
+ long long retval;
+ int err;
+
+ err = rtkit_get_int_property(impl, "RTTimeUSecMax", &retval);
+ return err < 0 ? err : retval;
+}
+
+int pw_rtkit_make_realtime(struct impl *impl, pid_t thread, int priority)
+{
+ DBusMessage *m = NULL, *r = NULL;
+ dbus_uint64_t pid;
+ dbus_uint64_t u64;
+ dbus_uint32_t u32;
+ DBusError error;
+ int ret;
+ struct pw_rtkit_bus *connection = impl->rtkit_bus;
+
+ dbus_error_init(&error);
+
+ if (thread == 0)
+ thread = _gettid();
+
+ if (!(m = dbus_message_new_method_call(impl->service_name,
+ impl->object_path, impl->interface,
+ "MakeThreadRealtimeWithPID"))) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ pid = (dbus_uint64_t) getpid();
+ u64 = (dbus_uint64_t) thread;
+ u32 = (dbus_uint32_t) priority;
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_UINT64, &pid,
+ DBUS_TYPE_UINT64, &u64,
+ DBUS_TYPE_UINT32, &u32, DBUS_TYPE_INVALID)) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(r = dbus_connection_send_with_reply_and_block(connection->bus, m, -1, &error))) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+
+ if (dbus_set_error_from_message(&error, r)) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+ ret = 0;
+
+finish:
+
+ if (m)
+ dbus_message_unref(m);
+
+ if (r)
+ dbus_message_unref(r);
+
+ dbus_error_free(&error);
+
+ return ret;
+}
+
+int pw_rtkit_make_high_priority(struct impl *impl, pid_t thread, int nice_level)
+{
+ DBusMessage *m = NULL, *r = NULL;
+ dbus_uint64_t pid;
+ dbus_uint64_t u64;
+ dbus_int32_t s32;
+ DBusError error;
+ int ret;
+ struct pw_rtkit_bus *connection = impl->rtkit_bus;
+
+ dbus_error_init(&error);
+
+ if (thread == 0)
+ thread = _gettid();
+
+ if (!(m = dbus_message_new_method_call(impl->service_name,
+ impl->object_path, impl->interface,
+ "MakeThreadHighPriorityWithPID"))) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+ pid = (dbus_uint64_t) getpid();
+ u64 = (dbus_uint64_t) thread;
+ s32 = (dbus_int32_t) nice_level;
+
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_UINT64, &pid,
+ DBUS_TYPE_UINT64, &u64,
+ DBUS_TYPE_INT32, &s32, DBUS_TYPE_INVALID)) {
+ ret = -ENOMEM;
+ goto finish;
+ }
+
+
+
+ if (!(r = dbus_connection_send_with_reply_and_block(connection->bus, m, -1, &error))) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+
+ if (dbus_set_error_from_message(&error, r)) {
+ ret = translate_error(error.name);
+ goto finish;
+ }
+
+ ret = 0;
+
+finish:
+
+ if (m)
+ dbus_message_unref(m);
+
+ if (r)
+ dbus_message_unref(r);
+
+ dbus_error_free(&error);
+
+ return ret;
+}
+#endif /* HAVE_DBUS */
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ pw_context_set_object(impl->context, SPA_TYPE_INTERFACE_ThreadUtils, NULL);
+ spa_hook_remove(&impl->module_listener);
+
+#ifdef HAVE_DBUS
+ if (impl->rtkit_bus)
+ pw_rtkit_bus_free(impl->rtkit_bus);
+#endif
+
+ free(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+static int get_rt_priority_range(int *out_min, int *out_max)
+{
+ int min, max;
+
+ if ((min = sched_get_priority_min(REALTIME_POLICY)) < 0)
+ return -errno;
+ if ((max = sched_get_priority_max(REALTIME_POLICY)) < 0)
+ return -errno;
+
+ if (out_min)
+ *out_min = min;
+ if (out_max)
+ *out_max = max;
+
+ return 0;
+}
+/**
+ * Check if the current user has permissions to use realtime scheduling at the
+ * specified priority.
+ */
+static bool check_realtime_privileges(struct impl *impl)
+{
+ rlim_t priority = impl->rt_prio;
+ int err, old_policy, new_policy, min, max;
+ struct sched_param old_sched_params;
+ struct sched_param new_sched_params;
+ int try = 0;
+
+ while (try++ < 2) {
+ /* We could check `RLIMIT_RTPRIO`, but the BSDs generally don't have
+ * that available, and there are also other ways to use realtime
+ * scheduling without that rlimit being set such as `CAP_SYS_NICE` or
+ * running as root. Instead of checking a bunch of preconditions, we
+ * just try if setting realtime scheduling works or not. */
+ if ((err = pthread_getschedparam(pthread_self(), &old_policy, &old_sched_params)) != 0) {
+ pw_log_warn("Failed to check RLIMIT_RTPRIO: %s", strerror(err));
+ return false;
+ }
+ if ((err = get_rt_priority_range(&min, &max)) < 0) {
+ pw_log_warn("Failed to get priority range: %s", strerror(err));
+ return false;
+ }
+ if (try == 2) {
+ struct rlimit rlim;
+ /* second try, try to clamp to RLIMIT_RTPRIO */
+ if (getrlimit(RLIMIT_RTPRIO, &rlim) == 0 && max > (int)rlim.rlim_max) {
+ pw_log_info("Clamp rtprio %d to %d", (int)priority, (int)rlim.rlim_max);
+ max = (int)rlim.rlim_max;
+ }
+ else
+ break;
+ }
+ if (max < DEFAULT_RT_PRIO_MIN) {
+ pw_log_info("Priority max (%d) must be at least %d", max, DEFAULT_RT_PRIO_MIN);
+ return false;
+ }
+
+ /* If the current scheduling policy has `SCHED_RESET_ON_FORK` set, then
+ * this also needs to be set here or `pthread_setschedparam()` will return
+ * an error code. Similarly, if it is not set, then we don't want to set
+ * it here as it would irreversible change the current thread's
+ * scheduling policy. */
+ spa_zero(new_sched_params);
+ new_sched_params.sched_priority = SPA_CLAMP((int)priority, min, max);
+ new_policy = REALTIME_POLICY;
+ if ((old_policy & PW_SCHED_RESET_ON_FORK) != 0)
+ new_policy |= PW_SCHED_RESET_ON_FORK;
+
+ if (pthread_setschedparam(pthread_self(), new_policy, &new_sched_params) == 0) {
+ impl->rt_prio = new_sched_params.sched_priority;
+ pthread_setschedparam(pthread_self(), old_policy, &old_sched_params);
+ return true;
+ }
+ }
+ pw_log_info("Can't set rt prio to %d: %m (try increasing rlimits)", (int)priority);
+ return false;
+}
+
+static int sched_set_nice(int nice_level)
+{
+ if (setpriority(PRIO_PROCESS, _gettid(), nice_level) == 0)
+ return 0;
+ else
+ return -errno;
+}
+
+static int set_nice(struct impl *impl, int nice_level, bool warn)
+{
+ int res = 0;
+
+#ifdef HAVE_DBUS
+ if (impl->use_rtkit)
+ res = pw_rtkit_make_high_priority(impl, 0, nice_level);
+ else
+ res = sched_set_nice(nice_level);
+#else
+ res = sched_set_nice(nice_level);
+#endif
+
+ if (res < 0) {
+ if (warn)
+ pw_log_warn("could not set nice-level to %d: %s",
+ nice_level, spa_strerror(res));
+ } else {
+ pw_log_info("main thread nice level set to %d",
+ nice_level);
+ }
+ return res;
+}
+
+static int set_rlimit(struct impl *impl)
+{
+ struct rlimit rl;
+ int res = 0;
+
+ spa_zero(rl);
+ rl.rlim_cur = impl->rt_time_soft;
+ rl.rlim_max = impl->rt_time_hard;
+
+#ifdef HAVE_DBUS
+ if (impl->use_rtkit) {
+ long long rttime;
+ rttime = pw_rtkit_get_rttime_usec_max(impl);
+ if (rttime >= 0) {
+ if ((rlim_t)rttime < rl.rlim_cur) {
+ pw_log_debug("clamping rt.time.soft from %llu to %lld because of RTKit",
+ (long long)rl.rlim_cur, rttime);
+ }
+
+ rl.rlim_cur = SPA_MIN(rl.rlim_cur, (rlim_t)rttime);
+ rl.rlim_max = SPA_MIN(rl.rlim_max, (rlim_t)rttime);
+ }
+ }
+#endif
+
+ if (setrlimit(RLIMIT_RTTIME, &rl) < 0)
+ res = -errno;
+
+ if (res < 0)
+ pw_log_debug("setrlimit() failed: %s", spa_strerror(res));
+ else
+ pw_log_debug("rt.time.soft:%"PRIi64" rt.time.hard:%"PRIi64,
+ (int64_t)rl.rlim_cur, (int64_t)rl.rlim_max);
+
+ return res;
+}
+
+static int acquire_rt_sched(struct spa_thread *thread, int priority)
+{
+ int err, min, max;
+ struct sched_param sp;
+ pthread_t pt = (pthread_t)thread;
+
+ if ((err = get_rt_priority_range(&min, &max)) < 0)
+ return err;
+
+ if (priority < min || priority > max) {
+ pw_log_info("clamping priority %d to range %d - %d for policy %d",
+ priority, min, max, REALTIME_POLICY);
+ priority = SPA_CLAMP(priority, min, max);
+ }
+
+ spa_zero(sp);
+ sp.sched_priority = priority;
+ if ((err = pthread_setschedparam(pt, REALTIME_POLICY | PW_SCHED_RESET_ON_FORK, &sp)) != 0) {
+ pw_log_warn("could not make thread %p realtime: %s", thread, strerror(err));
+ return -err;
+ }
+
+ pw_log_info("acquired realtime priority %d for thread %p", priority, thread);
+ return 0;
+}
+
+static int impl_drop_rt_generic(void *object, struct spa_thread *thread)
+{
+ struct sched_param sp;
+ pthread_t pt = (pthread_t)thread;
+ int err;
+
+ spa_zero(sp);
+ if ((err = pthread_setschedparam(pt, SCHED_OTHER | PW_SCHED_RESET_ON_FORK, &sp)) != 0) {
+ pw_log_debug("thread %p: SCHED_OTHER|SCHED_RESET_ON_FORK failed: %s",
+ thread, strerror(err));
+ return -err;
+ }
+ pw_log_info("thread %p dropped realtime priority", thread);
+ return 0;
+}
+
+#ifdef HAVE_DBUS
+static struct thread *find_thread_by_pt(struct impl *impl, pthread_t pt)
+{
+ struct thread *t;
+
+ spa_list_for_each(t, &impl->threads_list, link) {
+ if (pthread_equal(t->thread, pt))
+ return t;
+ }
+ return NULL;
+}
+
+static void *custom_start(void *data)
+{
+ struct thread *this = data;
+ struct impl *impl = this->impl;
+
+ pthread_mutex_lock(&impl->lock);
+ this->pid = _gettid();
+ pthread_cond_broadcast(&impl->cond);
+ pthread_mutex_unlock(&impl->lock);
+
+ return this->start(this->arg);
+}
+
+static struct spa_thread *impl_create(void *object, const struct spa_dict *props,
+ void *(*start_routine)(void*), void *arg)
+{
+ struct impl *impl = object;
+ struct thread *this;
+ struct spa_thread *thread;
+
+ this = calloc(1, sizeof(*this));
+ this->impl = impl;
+ this->start = start_routine;
+ this->arg = arg;
+
+ /* This thread list is only used for the RTKit implementation */
+ pthread_mutex_lock(&impl->lock);
+ thread = pw_thread_utils_create(props, custom_start, this);
+ if (thread == NULL)
+ goto exit;
+
+ this->thread = (pthread_t)thread;
+ pthread_cond_wait(&impl->cond, &impl->lock);
+
+ spa_list_append(&impl->threads_list, &this->link);
+exit:
+ pthread_mutex_unlock(&impl->lock);
+
+ if (thread == NULL) {
+ free(this);
+ return NULL;
+ }
+ return thread;
+}
+
+static int impl_join(void *object, struct spa_thread *thread, void **retval)
+{
+ struct impl *impl = object;
+ pthread_t pt = (pthread_t)thread;
+ struct thread *thr;
+
+ pthread_mutex_lock(&impl->lock);
+ if ((thr = find_thread_by_pt(impl, pt)) != NULL) {
+ spa_list_remove(&thr->link);
+ free(thr);
+ }
+ pthread_mutex_unlock(&impl->lock);
+
+ return pthread_join(pt, retval);
+}
+
+
+static int get_rtkit_priority_range(struct impl *impl, int *min, int *max)
+{
+ if (min)
+ *min = 1;
+ if (max) {
+ if ((*max = pw_rtkit_get_max_realtime_priority(impl)) < 0)
+ return *max;
+ if (*max < 1)
+ *max = 1;
+ }
+ return 0;
+}
+
+static int impl_get_rt_range(void *object, const struct spa_dict *props,
+ int *min, int *max)
+{
+ struct impl *impl = object;
+ int res;
+ if (impl->use_rtkit)
+ res = get_rtkit_priority_range(impl, min, max);
+ else
+ res = get_rt_priority_range(min, max);
+ return res;
+}
+
+static pid_t impl_gettid(struct impl *impl, pthread_t pt)
+{
+ struct thread *thr;
+ pid_t pid;
+
+ pthread_mutex_lock(&impl->lock);
+ if ((thr = find_thread_by_pt(impl, pt)) != NULL)
+ pid = thr->pid;
+ else
+ pid = _gettid();
+ pthread_mutex_unlock(&impl->lock);
+
+ return pid;
+}
+
+static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority)
+{
+ struct impl *impl = object;
+ struct sched_param sp;
+ int err;
+ pthread_t pt = (pthread_t)thread;
+ pid_t pid;
+
+ /* See the docstring on `spa_thread_utils_methods::acquire_rt` */
+ if (priority == -1) {
+ priority = impl->rt_prio;
+ }
+
+ if (impl->use_rtkit) {
+ int min, max;
+
+ if ((err = get_rtkit_priority_range(impl, &min, &max)) < 0)
+ return err;
+
+ pid = impl_gettid(impl, pt);
+
+ if (priority < min || priority > max) {
+ pw_log_info("clamping requested priority %d for thread %d "
+ "between %d and %d", priority, pid, min, max);
+ priority = SPA_CLAMP(priority, min, max);
+ }
+
+ spa_zero(sp);
+ sp.sched_priority = priority;
+
+ if (pthread_setschedparam(pt, SCHED_OTHER | PW_SCHED_RESET_ON_FORK, &sp) == 0) {
+ pw_log_debug("SCHED_OTHER|SCHED_RESET_ON_FORK worked.");
+ }
+
+ if ((err = pw_rtkit_make_realtime(impl, pid, priority)) < 0) {
+ pw_log_warn("could not make thread %d realtime using RTKit: %s", pid, spa_strerror(err));
+ return err;
+ }
+
+ pw_log_info("acquired realtime priority %d for thread %d using RTKit", priority, pid);
+ return 0;
+ } else {
+ return acquire_rt_sched(thread, priority);
+ }
+}
+
+static const struct spa_thread_utils_methods impl_thread_utils = {
+ SPA_VERSION_THREAD_UTILS_METHODS,
+ .create = impl_create,
+ .join = impl_join,
+ .get_rt_range = impl_get_rt_range,
+ .acquire_rt = impl_acquire_rt,
+ .drop_rt = impl_drop_rt_generic,
+};
+
+#else /* HAVE_DBUS */
+
+static struct spa_thread *impl_create(void *object, const struct spa_dict *props,
+ void *(*start_routine)(void*), void *arg)
+{
+ return pw_thread_utils_create(props, start_routine, arg);
+}
+
+static int impl_join(void *object, struct spa_thread *thread, void **retval)
+{
+ return pw_thread_utils_join(thread, retval);
+}
+
+static int impl_get_rt_range(void *object, const struct spa_dict *props,
+ int *min, int *max)
+{
+ return get_rt_priority_range(min, max);
+}
+
+static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority)
+{
+ struct impl *impl = object;
+
+ /* See the docstring on `spa_thread_utils_methods::acquire_rt` */
+ if (priority == -1)
+ priority = impl->rt_prio;
+
+ return acquire_rt_sched(thread, priority);
+}
+
+static const struct spa_thread_utils_methods impl_thread_utils = {
+ SPA_VERSION_THREAD_UTILS_METHODS,
+ .create = impl_create,
+ .join = impl_join,
+ .get_rt_range = impl_get_rt_range,
+ .acquire_rt = impl_acquire_rt,
+ .drop_rt = impl_drop_rt_generic,
+};
+#endif /* HAVE_DBUS */
+
+
+#ifdef HAVE_DBUS
+static int check_rtkit(struct impl *impl, struct pw_context *context, bool *can_use_rtkit)
+{
+ const struct pw_properties *context_props;
+ const char *str;
+
+ *can_use_rtkit = true;
+
+ if ((context_props = pw_context_get_properties(context)) != NULL &&
+ (str = pw_properties_get(context_props, "support.dbus")) != NULL &&
+ !pw_properties_parse_bool(str))
+ *can_use_rtkit = false;
+
+ return 0;
+}
+#endif /* HAVE_DBUS */
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct impl *impl;
+ struct pw_properties *props;
+ int res = 0;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -ENOMEM;
+
+ pw_log_debug("module %p: new", impl);
+
+ props = args ? pw_properties_new_string(args) : pw_properties_new(NULL, NULL);
+ if (!props) {
+ res = -errno;
+ goto error;
+ }
+
+ impl->context = context;
+ impl->nice_level = pw_properties_get_int32(props, "nice.level", DEFAULT_NICE_LEVEL);
+ impl->rt_prio = pw_properties_get_int32(props, "rt.prio", DEFAULT_RT_PRIO);
+ impl->rt_time_soft = pw_properties_get_int32(props, "rt.time.soft", DEFAULT_RT_TIME_SOFT);
+ impl->rt_time_hard = pw_properties_get_int32(props, "rt.time.hard", DEFAULT_RT_TIME_HARD);
+
+ bool can_use_rtkit = false, use_rtkit = false;
+
+#ifdef HAVE_DBUS
+ spa_list_init(&impl->threads_list);
+ pthread_mutex_init(&impl->lock, NULL);
+ pthread_cond_init(&impl->cond, NULL);
+
+ if ((res = check_rtkit(impl, context, &can_use_rtkit)) < 0)
+ goto error;
+#endif
+ /* If the user has permissions to use regular realtime scheduling, as well as
+ * the nice level we want, then we'll use that instead of RTKit */
+ if (!check_realtime_privileges(impl)) {
+ if (!can_use_rtkit) {
+ res = -ENOTSUP;
+ pw_log_warn("regular realtime scheduling not available (RTKit fallback disabled)");
+ goto error;
+ }
+ use_rtkit = true;
+ }
+
+ if (IS_VALID_NICE_LEVEL(impl->nice_level)) {
+ if (set_nice(impl, impl->nice_level, !can_use_rtkit) < 0)
+ use_rtkit = can_use_rtkit;
+ }
+
+#ifdef HAVE_DBUS
+ impl->use_rtkit = use_rtkit;
+ if (impl->use_rtkit) {
+ /* Checking xdg-desktop-portal. It works fine in all situations. */
+ impl->rtkit_bus = pw_rtkit_bus_get_session();
+ if (impl->rtkit_bus != NULL) {
+ if (pw_rtkit_check_xdg_portal(impl->rtkit_bus)) {
+ impl->service_name = XDG_PORTAL_SERVICE_NAME;
+ impl->object_path = XDG_PORTAL_OBJECT_PATH;
+ impl->interface = XDG_PORTAL_INTERFACE;
+ } else {
+ pw_log_warn("found session bus but no portal");
+ pw_rtkit_bus_free(impl->rtkit_bus);
+ impl->rtkit_bus = NULL;
+ }
+ }
+ /* Failed to get xdg-desktop-portal, try to use rtkit. */
+ if (impl->rtkit_bus == NULL) {
+ impl->rtkit_bus = pw_rtkit_bus_get_system();
+ if (impl->rtkit_bus != NULL) {
+ impl->service_name = RTKIT_SERVICE_NAME;
+ impl->object_path = RTKIT_OBJECT_PATH;
+ impl->interface = RTKIT_INTERFACE;
+ } else {
+ res = -errno;
+ pw_log_warn("could not get system bus: %m");
+ goto error;
+ }
+ }
+ /* Retry set_nice with rtkit */
+ if (IS_VALID_NICE_LEVEL(impl->nice_level))
+ set_nice(impl, impl->nice_level, true);
+ }
+#endif
+ set_rlimit(impl);
+
+ impl->thread_utils.iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_ThreadUtils,
+ SPA_VERSION_THREAD_UTILS,
+ &impl_thread_utils, impl);
+
+ pw_context_set_object(context, SPA_TYPE_INTERFACE_ThreadUtils,
+ &impl->thread_utils);
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+ pw_impl_module_update_properties(module, &props->dict);
+
+#ifdef HAVE_DBUS
+ if (impl->use_rtkit) {
+ pw_log_debug("initialized using RTKit");
+ } else {
+ pw_log_debug("initialized using regular realtime scheduling");
+ }
+#else
+ pw_log_debug("initialized using regular realtime scheduling");
+#endif
+
+ goto done;
+
+error:
+#ifdef HAVE_DBUS
+ if (impl->rtkit_bus)
+ pw_rtkit_bus_free(impl->rtkit_bus);
+#endif
+ free(impl);
+done:
+ pw_properties_free(props);
+
+ return res;
+}
diff --git a/src/modules/module-rtp-sink.c b/src/modules/module-rtp-sink.c
new file mode 100644
index 0000000..b0c622d
--- /dev/null
+++ b/src/modules/module-rtp-sink.c
@@ -0,0 +1,975 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <limits.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <netinet/ip.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <ctype.h>
+
+#include <spa/utils/hook.h>
+#include <spa/utils/result.h>
+#include <spa/utils/ringbuffer.h>
+#include <spa/utils/json.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/debug/types.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/impl.h>
+
+#include <module-rtp/sap.h>
+#include <module-rtp/rtp.h>
+
+
+/** \page page_module_rtp_sink PipeWire Module: RTP sink
+ *
+ * The `rtp-sink` module creates a PipeWire sink that sends audio
+ * RTP packets.
+ *
+ * ## Module Options
+ *
+ * Options specific to the behavior of this module
+ *
+ * - `sap.ip = <str>`: IP address of the SAP messages, default "224.0.0.56"
+ * - `sap.port = <int>`: port of the SAP messages, default 9875
+ * - `source.ip =<str>`: source IP address, default "0.0.0.0"
+ * - `destination.ip =<str>`: destination IP address, default "224.0.0.56"
+ * - `destination.port =<int>`: destination port, default random beteen 46000 and 47024
+ * - `local.ifname = <str>`: interface name to use
+ * - `net.mtu = <int>`: MTU to use, default 1280
+ * - `net.ttl = <int>`: TTL to use, default 1
+ * - `net.loop = <bool>`: loopback multicast, default false
+ * - `sess.min-ptime = <int>`: minimum packet time in milliseconds, default 2
+ * - `sess.max-ptime = <int>`: maximum packet time in milliseconds, default 20
+ * - `sess.name = <str>`: a session name
+ * - `stream.props = {}`: properties to be passed to the stream
+ *
+ * ## 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_NODE_NAME
+ * - \ref PW_KEY_NODE_DESCRIPTION
+ * - \ref PW_KEY_MEDIA_NAME
+ * - \ref PW_KEY_NODE_GROUP
+ * - \ref PW_KEY_NODE_LATENCY
+ * - \ref PW_KEY_NODE_VIRTUAL
+ * - \ref PW_KEY_MEDIA_CLASS
+ *
+ * ## Example configuration
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-rtp-sink
+ * args = {
+ * #sap.ip = "224.0.0.56"
+ * #sap.port = 9875
+ * #source.ip = "0.0.0.0"
+ * #destination.ip = "224.0.0.56"
+ * #destination.port = 46000
+ * #local.ifname = "eth0"
+ * #net.mtu = 1280
+ * #net.ttl = 1
+ * #net.loop = false
+ * #sess.min-ptime = 2
+ * #sess.max-ptime = 20
+ * #sess.name = "PipeWire RTP stream"
+ * #audio.format = "S16BE"
+ * #audio.rate = 48000
+ * #audio.channels = 2
+ * #audio.position = [ FL FR ]
+ * stream.props = {
+ * node.name = "rtp-sink"
+ * }
+ * }
+ *}
+ *]
+ *\endcode
+ *
+ * \since 0.3.60
+ */
+
+#define NAME "rtp-sink"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define SAP_INTERVAL_SEC 5
+#define SAP_MIME_TYPE "application/sdp"
+
+#define BUFFER_SIZE (1u<<20)
+#define BUFFER_MASK (BUFFER_SIZE-1)
+
+#define DEFAULT_SAP_IP "224.0.0.56"
+#define DEFAULT_SAP_PORT 9875
+
+#define DEFAULT_FORMAT "S16BE"
+#define DEFAULT_RATE 48000
+#define DEFAULT_CHANNELS 2
+#define DEFAULT_POSITION "[ FL FR ]"
+
+#define DEFAULT_PORT 46000
+#define DEFAULT_SOURCE_IP "0.0.0.0"
+#define DEFAULT_DESTINATION_IP "224.0.0.56"
+#define DEFAULT_TTL 1
+#define DEFAULT_MTU 1280
+#define DEFAULT_LOOP false
+
+#define DEFAULT_MIN_PTIME 2
+#define DEFAULT_MAX_PTIME 20
+
+#define USAGE "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)"> " \
+ "source.ip=<source IP address, default:"DEFAULT_SOURCE_IP"> " \
+ "destination.ip=<destination IP address, default:"DEFAULT_DESTINATION_IP"> " \
+ "local.ifname=<local interface name to use> " \
+ "net.mtu=<desired MTU, default:"SPA_STRINGIFY(DEFAULT_MTU)"> " \
+ "net.ttl=<desired TTL, default:"SPA_STRINGIFY(DEFAULT_TTL)"> " \
+ "net.loop=<desired loopback, default:"SPA_STRINGIFY(DEFAULT_LOOP)"> " \
+ "sess.name=<a name for the session> " \
+ "sess.min-ptime=<minimum packet time in milliseconds, default:2> " \
+ "sess.max-ptime=<maximum packet time in milliseconds, default:20> " \
+ "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 ... }"
+
+static const struct spa_dict_item module_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "RTP Sink" },
+ { PW_KEY_MODULE_USAGE, USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+static const struct format_info {
+ uint32_t format;
+ uint32_t size;
+ const char *mime;
+} format_info[] = {
+ { SPA_AUDIO_FORMAT_U8, 1, "L8" },
+ { SPA_AUDIO_FORMAT_ALAW, 1, "PCMA" },
+ { SPA_AUDIO_FORMAT_ULAW, 1, "PCMU" },
+ { SPA_AUDIO_FORMAT_S16_BE, 2, "L16" },
+ { SPA_AUDIO_FORMAT_S24_BE, 3, "L24" },
+};
+
+static const struct format_info *find_format_info(uint32_t format)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(format_info, f)
+ if (f->format == format)
+ return f;
+ return NULL;
+}
+
+struct impl {
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+ struct pw_properties *props;
+ struct pw_context *module_context;
+
+ struct pw_loop *loop;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+ struct spa_hook core_proxy_listener;
+
+ struct spa_source *timer;
+
+ struct pw_properties *stream_props;
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ unsigned int do_disconnect:1;
+
+ char *ifname;
+ char *session_name;
+ int sess_latency_msec;
+ int mtu;
+ bool ttl;
+ bool mcast_loop;
+ uint32_t min_ptime;
+ uint32_t max_ptime;
+ uint32_t pbytes;
+
+ struct sockaddr_storage src_addr;
+ socklen_t src_len;
+
+ uint16_t port;
+ struct sockaddr_storage dst_addr;
+ socklen_t dst_len;
+
+ uint16_t sap_port;
+ struct sockaddr_storage sap_addr;
+ socklen_t sap_len;
+
+ uint16_t msg_id_hash;
+ uint32_t ntp;
+
+ struct spa_audio_info_raw info;
+ const struct format_info *format_info;
+ uint32_t frame_size;
+ int payload;
+ uint16_t seq;
+ uint32_t timestamp;
+ uint32_t ssrc;
+
+ struct spa_ringbuffer ring;
+ uint8_t buffer[BUFFER_SIZE];
+
+ int rtp_fd;
+ int sap_fd;
+};
+
+
+static void stream_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->stream_listener);
+ impl->stream = NULL;
+}
+
+static inline void
+set_iovec(struct spa_ringbuffer *rbuf, void *buffer, uint32_t size,
+ uint32_t offset, struct iovec *iov, uint32_t len)
+{
+ iov[0].iov_len = SPA_MIN(len, size - offset);
+ iov[0].iov_base = SPA_PTROFF(buffer, offset, void);
+ iov[1].iov_len = len - iov[0].iov_len;
+ iov[1].iov_base = buffer;
+}
+
+static void flush_packets(struct impl *impl)
+{
+ int32_t avail;
+ uint32_t index;
+ struct iovec iov[3];
+ struct msghdr msg;
+ ssize_t n;
+ struct rtp_header header;
+ int32_t tosend;
+
+ avail = spa_ringbuffer_get_read_index(&impl->ring, &index);
+
+ tosend = impl->pbytes;
+
+ if (avail < tosend)
+ return;
+
+ spa_zero(header);
+ header.v = 2;
+ header.pt = impl->payload;
+ header.ssrc = htonl(impl->ssrc);
+
+ iov[0].iov_base = &header;
+ iov[0].iov_len = sizeof(header);
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 3;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+
+ while (avail >= tosend) {
+ header.sequence_number = htons(impl->seq);
+ header.timestamp = htonl(impl->timestamp);
+
+ set_iovec(&impl->ring,
+ impl->buffer, BUFFER_SIZE,
+ index & BUFFER_MASK,
+ &iov[1], tosend);
+
+ n = sendmsg(impl->rtp_fd, &msg, MSG_NOSIGNAL);
+ if (n < 0) {
+ switch (errno) {
+ case ECONNREFUSED:
+ case ECONNRESET:
+ pw_log_debug("remote end not listening");
+ break;
+ default:
+ pw_log_warn("sendmsg() failed: %m");
+ break;
+ }
+ }
+
+ impl->seq++;
+ impl->timestamp += tosend / impl->frame_size;
+
+ index += tosend;
+ avail -= tosend;
+ }
+ spa_ringbuffer_read_update(&impl->ring, index);
+}
+
+static void stream_process(void *data)
+{
+ struct impl *impl = data;
+ struct pw_buffer *buf;
+ struct spa_data *d;
+ uint32_t index;
+ int32_t filled, wanted;
+
+ if ((buf = pw_stream_dequeue_buffer(impl->stream)) == NULL) {
+ pw_log_debug("Out of stream buffers: %m");
+ return;
+ }
+ d = buf->buffer->datas;
+
+ wanted = d[0].chunk->size;
+
+ filled = spa_ringbuffer_get_write_index(&impl->ring, &index);
+
+ if (filled + wanted > (int32_t)BUFFER_SIZE) {
+ pw_log_warn("overrun %u + %u > %u", filled, wanted, BUFFER_SIZE);
+ } else {
+ spa_ringbuffer_write_data(&impl->ring,
+ impl->buffer,
+ BUFFER_SIZE,
+ index & BUFFER_MASK,
+ d[0].data, wanted);
+
+ index += wanted;
+ spa_ringbuffer_write_update(&impl->ring, index);
+ }
+ pw_stream_queue_buffer(impl->stream, buf);
+
+ flush_packets(impl);
+}
+
+static void on_stream_state_changed(void *d, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct impl *impl = d;
+
+ switch (state) {
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_log_info("stream disconnected, unloading");
+ pw_impl_module_schedule_destroy(impl->module);
+ break;
+ case PW_STREAM_STATE_ERROR:
+ pw_log_error("stream error: %s", error);
+ break;
+ default:
+ break;
+ }
+}
+
+static const struct pw_stream_events in_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = stream_destroy,
+ .state_changed = on_stream_state_changed,
+ .process = stream_process
+};
+
+static int parse_address(const char *address, uint16_t port,
+ struct sockaddr_storage *addr, socklen_t *len)
+{
+ struct sockaddr_in *sa4 = (struct sockaddr_in*)addr;
+ struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)addr;
+
+ if (inet_pton(AF_INET, address, &sa4->sin_addr) > 0) {
+ sa4->sin_family = AF_INET;
+ sa4->sin_port = htons(port);
+ *len = sizeof(*sa4);
+ } else if (inet_pton(AF_INET6, address, &sa6->sin6_addr) > 0) {
+ sa6->sin6_family = AF_INET6;
+ sa6->sin6_port = htons(port);
+ *len = sizeof(*sa6);
+ } else
+ return -EINVAL;
+
+ return 0;
+}
+
+static bool is_multicast(struct sockaddr *sa, socklen_t salen)
+{
+ if (sa->sa_family == AF_INET) {
+ static const uint32_t ipv4_mcast_mask = 0xe0000000;
+ struct sockaddr_in *sa4 = (struct sockaddr_in*)sa;
+ return (ntohl(sa4->sin_addr.s_addr) & ipv4_mcast_mask) == ipv4_mcast_mask;
+ } else if (sa->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)sa;
+ return sa6->sin6_addr.s6_addr[0] == 0xff;
+ }
+ return false;
+}
+
+static int make_socket(struct sockaddr_storage *src, socklen_t src_len,
+ struct sockaddr_storage *dst, socklen_t dst_len,
+ bool loop, int ttl)
+{
+ int af, fd, val, res;
+
+ af = src->ss_family;
+ if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
+ pw_log_error("socket failed: %m");
+ return -errno;
+ }
+ if (bind(fd, (struct sockaddr*)src, src_len) < 0) {
+ res = -errno;
+ pw_log_error("bind() failed: %m");
+ goto error;
+ }
+ if (connect(fd, (struct sockaddr*)dst, dst_len) < 0) {
+ res = -errno;
+ pw_log_error("connect() failed: %m");
+ goto error;
+ }
+ if (is_multicast((struct sockaddr*)dst, dst_len)) {
+ val = loop;
+ if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &val, sizeof(val)) < 0)
+ pw_log_warn("setsockopt(IP_MULTICAST_LOOP) failed: %m");
+
+ val = ttl;
+ if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &val, sizeof(val)) < 0)
+ pw_log_warn("setsockopt(IP_MULTICAST_TTL) failed: %m");
+ }
+#ifdef SO_PRIORITY
+ val = 6;
+ if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &val, sizeof(val)) < 0)
+ pw_log_warn("setsockopt(SO_PRIORITY) failed: %m");
+#endif
+ val = IPTOS_LOWDELAY;
+ if (setsockopt(fd, IPPROTO_IP, IP_TOS, &val, sizeof(val)) < 0)
+ pw_log_warn("setsockopt(IP_TOS) failed: %m");
+
+
+ return fd;
+error:
+ close(fd);
+ return res;
+}
+
+static int setup_stream(struct impl *impl)
+{
+ const struct spa_pod *params[1];
+ struct spa_pod_builder b;
+ uint32_t n_params;
+ uint8_t buffer[1024];
+ struct pw_properties *props;
+ int res, fd;
+
+ props = pw_properties_copy(impl->stream_props);
+ if (props == NULL)
+ return -errno;
+
+ if (pw_properties_get(props, PW_KEY_NODE_LATENCY) == NULL) {
+ pw_properties_setf(props, PW_KEY_NODE_LATENCY,
+ "%d/%d", impl->pbytes / impl->frame_size,
+ impl->info.rate);
+ }
+ pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", impl->info.rate);
+
+ impl->stream = pw_stream_new(impl->core,
+ "rtp-sink capture", props);
+ if (impl->stream == NULL)
+ return -errno;
+
+ pw_stream_add_listener(impl->stream,
+ &impl->stream_listener,
+ &in_stream_events, impl);
+
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
+ &impl->info);
+
+ if ((res = pw_stream_connect(impl->stream,
+ PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0)
+ return res;
+
+
+ if ((fd = make_socket(&impl->src_addr, impl->src_len,
+ &impl->dst_addr, impl->dst_len,
+ impl->mcast_loop, impl->ttl)) < 0)
+ return fd;
+
+ impl->rtp_fd = fd;
+
+ return 0;
+}
+
+static int get_ip(const struct sockaddr_storage *sa, char *ip, size_t len)
+{
+ if (sa->ss_family == AF_INET) {
+ struct sockaddr_in *in = (struct sockaddr_in*)sa;
+ inet_ntop(sa->ss_family, &in->sin_addr, ip, len);
+ } 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);
+ } else
+ return -EIO;
+ return 0;
+}
+static void send_sap(struct impl *impl, bool bye)
+{
+ char buffer[2048], src_addr[64], dst_addr[64], dst_ttl[8];
+ const char *user_name, *af;
+ struct sockaddr *sa = (struct sockaddr*)&impl->src_addr;
+ struct sap_header header;
+ struct iovec iov[4];
+ struct msghdr msg;
+
+ spa_zero(header);
+ header.v = 1;
+ header.t = bye;
+ header.msg_id_hash = impl->msg_id_hash;
+
+ iov[0].iov_base = &header;
+ iov[0].iov_len = sizeof(header);
+
+ if (sa->sa_family == AF_INET) {
+ iov[1].iov_base = &((struct sockaddr_in*) sa)->sin_addr;
+ iov[1].iov_len = 4U;
+ af = "IP4";
+ } else {
+ iov[1].iov_base = &((struct sockaddr_in6*) sa)->sin6_addr;
+ iov[1].iov_len = 16U;
+ header.a = 1;
+ af = "IP6";
+ }
+ iov[2].iov_base = SAP_MIME_TYPE;
+ iov[2].iov_len = sizeof(SAP_MIME_TYPE);
+
+ get_ip(&impl->src_addr, src_addr, sizeof(src_addr));
+ get_ip(&impl->dst_addr, dst_addr, sizeof(dst_addr));
+
+ if ((user_name = pw_get_user_name()) == NULL)
+ user_name = "-";
+
+ spa_zero(dst_ttl);
+ if (is_multicast((struct sockaddr*)&impl->dst_addr, impl->dst_len))
+ snprintf(dst_ttl, sizeof(dst_ttl), "/%d", impl->ttl);
+
+ snprintf(buffer, sizeof(buffer),
+ "v=0\n"
+ "o=%s %u 0 IN %s %s\n"
+ "s=%s\n"
+ "c=IN %s %s%s\n"
+ "t=%u 0\n"
+ "a=recvonly\n"
+ "a=tool:PipeWire %s\n"
+ "m=audio %u RTP/AVP %i\n"
+ "a=rtpmap:%i %s/%u/%u\n"
+ "a=type:broadcast\n"
+ "a=ptime:%d\n",
+ user_name, impl->ntp, af, src_addr,
+ impl->session_name,
+ af, dst_addr, dst_ttl,
+ impl->ntp,
+ pw_get_library_version(),
+ impl->port, impl->payload,
+ impl->payload, impl->format_info->mime,
+ impl->info.rate, impl->info.channels,
+ (impl->pbytes / impl->frame_size) * 1000 / impl->info.rate);
+
+ iov[3].iov_base = buffer;
+ iov[3].iov_len = strlen(buffer);
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 4;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = 0;
+
+ sendmsg(impl->sap_fd, &msg, MSG_NOSIGNAL);
+}
+
+static void on_timer_event(void *data, uint64_t expirations)
+{
+ struct impl *impl = data;
+ send_sap(impl, 0);
+}
+
+static int start_sap_announce(struct impl *impl)
+{
+ int fd, res;
+ struct timespec value, interval;
+
+ if ((fd = make_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) {
+ res = -errno;
+ pw_log_error("can't create timer source: %m");
+ goto error;
+ }
+ value.tv_sec = 0;
+ value.tv_nsec = 1;
+ interval.tv_sec = SAP_INTERVAL_SEC;
+ interval.tv_nsec = 0;
+ pw_loop_update_timer(impl->loop, impl->timer, &value, &interval, false);
+
+ return 0;
+error:
+ close(fd);
+ return res;
+
+}
+
+static void core_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->core_listener);
+ impl->core = NULL;
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ .destroy = core_destroy,
+};
+
+static void impl_destroy(struct impl *impl)
+{
+ send_sap(impl, 1);
+
+ if (impl->stream)
+ pw_stream_destroy(impl->stream);
+
+ if (impl->core && impl->do_disconnect)
+ pw_core_disconnect(impl->core);
+
+ if (impl->timer)
+ pw_loop_destroy_source(impl->loop, impl->timer);
+
+ if (impl->rtp_fd != -1)
+ close(impl->rtp_fd);
+ if (impl->sap_fd != -1)
+ close(impl->sap_fd);
+
+ pw_properties_free(impl->stream_props);
+ pw_properties_free(impl->props);
+
+ free(impl->ifname);
+ free(impl->session_name);
+ free(impl);
+}
+
+static void module_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->module_listener);
+ impl_destroy(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+static void on_core_error(void *d, uint32_t id, int seq, int res, const char *message)
+{
+ struct impl *impl = d;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+};
+
+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));
+}
+
+static void copy_props(struct impl *impl, struct pw_properties *props, const char *key)
+{
+ const char *str;
+ if ((str = pw_properties_get(props, key)) != NULL) {
+ if (pw_properties_get(impl->stream_props, key) == NULL)
+ pw_properties_set(impl->stream_props, key, str);
+ }
+}
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct impl *impl;
+ struct pw_properties *props = NULL, *stream_props = NULL;
+ uint32_t id = pw_global_get_id(pw_impl_module_get_global(module));
+ uint32_t pid = getpid(), port, min_bytes, max_bytes;
+ char addr[64];
+ const char *str;
+ int res = 0;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ impl->rtp_fd = -1;
+ impl->sap_fd = -1;
+
+ if (args == NULL)
+ args = "";
+
+ props = pw_properties_new_string(args);
+ if (props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto out;
+ }
+ impl->props = props;
+
+ stream_props = pw_properties_new(NULL, NULL);
+ if (stream_props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto out;
+ }
+ impl->stream_props = stream_props;
+
+ impl->module = module;
+ impl->module_context = context;
+ impl->loop = pw_context_get_main_loop(context);
+
+ if (pw_properties_get(props, PW_KEY_NODE_VIRTUAL) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
+ if (pw_properties_get(stream_props, PW_KEY_NODE_NETWORK) == NULL)
+ pw_properties_set(stream_props, PW_KEY_NODE_NETWORK, "true");
+
+ if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL)
+ pw_properties_setf(props, PW_KEY_NODE_NAME, "rtp-sink-%u-%u", pid, id);
+ if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL)
+ pw_properties_set(props, PW_KEY_NODE_DESCRIPTION,
+ pw_properties_get(props, PW_KEY_NODE_NAME));
+ if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL)
+ pw_properties_set(props, PW_KEY_MEDIA_NAME, "RTP Sender Stream");
+
+ if ((str = pw_properties_get(props, "stream.props")) != NULL)
+ pw_properties_update_string(stream_props, str, strlen(str));
+
+ 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);
+ copy_props(impl, props, PW_KEY_NODE_GROUP);
+ copy_props(impl, props, PW_KEY_NODE_LATENCY);
+ copy_props(impl, props, PW_KEY_NODE_VIRTUAL);
+ copy_props(impl, props, PW_KEY_MEDIA_NAME);
+ copy_props(impl, props, PW_KEY_MEDIA_CLASS);
+
+ parse_audio_info(impl->stream_props, &impl->info);
+
+ impl->format_info = find_format_info(impl->info.format);
+ if (impl->format_info == NULL) {
+ pw_log_error("unsupported audio format:%d channels:%d",
+ impl->info.format, impl->info.channels);
+ res = -EINVAL;
+ goto out;
+ }
+ impl->frame_size = impl->format_info->size * impl->info.channels;
+ impl->msg_id_hash = rand();
+ impl->ntp = (uint32_t) time(NULL) + 2208988800U;
+
+ impl->payload = 127;
+ impl->seq = rand();
+ impl->timestamp = rand();
+ impl->ssrc = rand();
+
+ str = pw_properties_get(props, "local.ifname");
+ impl->ifname = str ? strdup(str) : NULL;
+
+ if ((str = pw_properties_get(props, "sap.ip")) == NULL)
+ str = DEFAULT_SAP_IP;
+ port = pw_properties_get_uint32(props, "sap.port", DEFAULT_SAP_PORT);
+ if ((res = parse_address(str, port, &impl->sap_addr, &impl->sap_len)) < 0) {
+ pw_log_error("invalid sap.ip %s: %s", str, spa_strerror(res));
+ goto out;
+ }
+
+ if ((str = pw_properties_get(props, "source.ip")) == NULL)
+ str = DEFAULT_SOURCE_IP;
+ if ((res = 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->port = DEFAULT_PORT + ((uint32_t) (rand() % 512) << 1);
+ impl->port = pw_properties_get_uint32(props, "destination.port", impl->port);
+ if ((str = pw_properties_get(props, "destination.ip")) == NULL)
+ str = DEFAULT_DESTINATION_IP;
+ if ((res = parse_address(str, impl->port, &impl->dst_addr, &impl->dst_len)) < 0) {
+ pw_log_error("invalid destination.ip %s: %s", str, spa_strerror(res));
+ goto out;
+ }
+
+ impl->mtu = pw_properties_get_uint32(props, "net.mtu", DEFAULT_MTU);
+ impl->ttl = pw_properties_get_uint32(props, "net.ttl", DEFAULT_TTL);
+ impl->mcast_loop = pw_properties_get_bool(props, "net.loop", DEFAULT_LOOP);
+
+ impl->min_ptime = pw_properties_get_uint32(props, "sess.min-ptime", DEFAULT_MIN_PTIME);
+ impl->max_ptime = pw_properties_get_uint32(props, "sess.max-ptime", DEFAULT_MAX_PTIME);
+
+ min_bytes = (impl->min_ptime * impl->info.rate / 1000) * impl->frame_size;
+ max_bytes = (impl->max_ptime * impl->info.rate / 1000) * impl->frame_size;
+
+ impl->pbytes = SPA_ROUND_DOWN(impl->mtu, impl->frame_size);
+ impl->pbytes = SPA_CLAMP(impl->pbytes, min_bytes, max_bytes);
+
+ if ((str = pw_properties_get(props, "sess.name")) == NULL)
+ pw_properties_setf(props, "sess.name", "PipeWire RTP Stream on %s",
+ pw_get_host_name());
+ str = pw_properties_get(props, "sess.name");
+ impl->session_name = str ? strdup(str) : NULL;
+
+ pw_properties_set(stream_props, "rtp.session", impl->session_name);
+ get_ip(&impl->src_addr, addr, sizeof(addr));
+ pw_properties_set(stream_props, "rtp.source.ip", addr);
+ get_ip(&impl->dst_addr, addr, sizeof(addr));
+ pw_properties_set(stream_props, "rtp.destination.ip", addr);
+ pw_properties_setf(stream_props, "rtp.destination.port", "%u", impl->port);
+ pw_properties_setf(stream_props, "rtp.mtu", "%u", impl->mtu);
+ pw_properties_setf(stream_props, "rtp.ttl", "%u", impl->ttl);
+ pw_properties_setf(stream_props, "rtp.ptime", "%u",
+ (impl->pbytes / impl->frame_size) * 1000 / impl->info.rate);
+
+ impl->core = pw_context_get_object(impl->module_context, PW_TYPE_INTERFACE_Core);
+ if (impl->core == NULL) {
+ str = pw_properties_get(props, PW_KEY_REMOTE_NAME);
+ impl->core = pw_context_connect(impl->module_context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ impl->do_disconnect = true;
+ }
+ if (impl->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto out;
+ }
+
+ pw_proxy_add_listener((struct pw_proxy*)impl->core,
+ &impl->core_proxy_listener,
+ &core_proxy_events, impl);
+ pw_core_add_listener(impl->core,
+ &impl->core_listener,
+ &core_events, impl);
+
+ if ((res = setup_stream(impl)) < 0)
+ goto out;
+
+ if ((res = start_sap_announce(impl)) < 0)
+ goto out;
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_info));
+
+ pw_log_info("Successfully loaded module-rtp-sink");
+
+ return 0;
+out:
+ impl_destroy(impl);
+ return res;
+}
diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c
new file mode 100644
index 0000000..5353532
--- /dev/null
+++ b/src/modules/module-rtp-source.c
@@ -0,0 +1,1200 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <limits.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <ctype.h>
+
+#include <spa/utils/hook.h>
+#include <spa/utils/result.h>
+#include <spa/utils/ringbuffer.h>
+#include <spa/utils/dll.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/debug/mem.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/impl.h>
+
+#include <module-rtp/sap.h>
+#include <module-rtp/rtp.h>
+
+#ifdef __FreeBSD__
+#define ifr_ifindex ifr_index
+#endif
+
+/** \page page_module_rtp_source PipeWire Module: RTP source
+ *
+ * The `rtp-source` module creates a PipeWire source that receives audio
+ * RTP packets.
+ *
+ * ## Module Options
+ *
+ * Options specific to the behavior of this module
+ *
+ * - `sap.ip = <str>`: IP address of the SAP messages, default "224.0.0.56"
+ * - `sap.port = <str>`: port of the SAP messages, default 9875
+ * - `local.ifname = <str>`: interface name to use
+ * - `sess.latency.msec = <str>`: target network latency in milliseconds, default 100
+ * - `stream.props = {}`: properties to be passed to the stream
+ *
+ * ## General options
+ *
+ * Options with well-known behavior:
+ *
+ * - \ref PW_KEY_NODE_NAME
+ * - \ref PW_KEY_NODE_DESCRIPTION
+ * - \ref PW_KEY_MEDIA_NAME
+ * - \ref PW_KEY_MEDIA_CLASS
+ *
+ * ## Example configuration
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-rtp-source
+ * args = {
+ * #sap.ip = 224.0.0.56
+ * #sap.port = 9875
+ * #local.ifname = eth0
+ * sess.latency.msec = 100
+ * stream.props = {
+ * #media.class = "Audio/Source"
+ * #node.name = "rtp-source"
+ * }
+ * stream.rules = [
+ * { matches = [
+ * # any of the items in matches needs to match, if one does,
+ * # actions are emited.
+ * { # all keys must match the value. ~ in value starts regex.
+ * #rtp.origin = "wim 3883629975 0 IN IP4 0.0.0.0"
+ * #rtp.payload = "127"
+ * #rtp.fmt = "L16/48000/2"
+ * #rtp.session = "PipeWire RTP Stream on fedora"
+ * }
+ * ]
+ * actions = {
+ * create-stream = {
+ * #sess.latency.msec = 100
+ * #target.object = ""
+ * }
+ * }
+ * }
+ * ]
+ * }
+ * }
+ * ]
+ *\endcode
+ *
+ * \since 0.3.60
+ */
+
+#define NAME "rtp-source"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define SAP_MIME_TYPE "application/sdp"
+
+#define ERROR_MSEC 2
+#define MAX_SESSIONS 16
+
+#define DEFAULT_CLEANUP_INTERVAL_SEC 90
+#define DEFAULT_SAP_IP "224.0.0.56"
+#define DEFAULT_SAP_PORT 9875
+#define DEFAULT_SESS_LATENCY 100
+
+#define BUFFER_SIZE (1u<<22)
+#define BUFFER_MASK (BUFFER_SIZE-1)
+
+#define USAGE "sap.ip=<SAP IP address to listen on, default "DEFAULT_SAP_IP"> " \
+ "sap.port=<SAP port to listen on, default "SPA_STRINGIFY(DEFAULT_SAP_PORT)"> " \
+ "local.ifname=<local interface name to use> " \
+ "sess.latency.msec=<target network latency, default "SPA_STRINGIFY(DEFAULT_SESS_LATENCY)"> " \
+ "stream.props= { key=value ... } " \
+ "stream.rules=<rules> "
+
+static const struct spa_dict_item module_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "RTP Source" },
+ { PW_KEY_MODULE_USAGE, USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct impl {
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+ struct pw_properties *props;
+ struct pw_context *module_context;
+
+ struct pw_loop *loop;
+ struct pw_loop *data_loop;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+ struct spa_hook core_proxy_listener;
+
+ struct spa_source *timer;
+ struct spa_source *sap_source;
+
+ struct pw_properties *stream_props;
+
+ unsigned int do_disconnect:1;
+
+ char *ifname;
+ char *sap_ip;
+ int sap_port;
+ int sess_latency_msec;
+ uint32_t cleanup_interval;
+
+ struct spa_list sessions;
+ uint32_t n_sessions;
+};
+
+static const struct format_info {
+ uint32_t format;
+ uint32_t size;
+ const char *mime;
+} format_info[] = {
+ { SPA_AUDIO_FORMAT_U8, 1, "L8" },
+ { SPA_AUDIO_FORMAT_ALAW, 1, "PCMA" },
+ { SPA_AUDIO_FORMAT_ULAW, 1, "PCMU" },
+ { SPA_AUDIO_FORMAT_S16_BE, 2, "L16" },
+ { SPA_AUDIO_FORMAT_S24_BE, 3, "L24" },
+};
+
+static const struct format_info *find_format_info(const char *mime)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(format_info, f)
+ if (spa_streq(f->mime, mime))
+ return f;
+ return NULL;
+}
+
+struct sdp_info {
+ uint16_t hash;
+
+ char origin[128];
+ char session[256];
+
+ struct sockaddr_storage sa;
+ socklen_t salen;
+
+ uint16_t port;
+ uint8_t payload;
+
+ const struct format_info *format_info;
+ struct spa_audio_info_raw info;
+ uint32_t stride;
+};
+
+struct session {
+ struct impl *impl;
+ struct spa_list link;
+
+ uint64_t timestamp;
+
+ struct sdp_info info;
+
+ struct spa_source *source;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ uint32_t expected_ssrc;
+ uint16_t expected_seq;
+ unsigned have_ssrc:1;
+ unsigned have_seq:1;
+ unsigned have_sync:1;
+
+ struct spa_ringbuffer ring;
+ uint8_t buffer[BUFFER_SIZE];
+
+ struct spa_io_rate_match *rate_match;
+ struct spa_dll dll;
+ uint32_t target_buffer;
+ uint32_t last_packet_size;
+ float max_error;
+ unsigned buffering:1;
+ unsigned first:1;
+};
+
+static void stream_destroy(void *d)
+{
+ struct session *sess = d;
+ spa_hook_remove(&sess->stream_listener);
+ sess->stream = NULL;
+}
+
+static void stream_process(void *data)
+{
+ struct session *sess = data;
+ struct pw_buffer *buf;
+ struct spa_data *d;
+ uint32_t index, target_buffer;
+ int32_t avail, wanted;
+
+ if ((buf = pw_stream_dequeue_buffer(sess->stream)) == NULL) {
+ pw_log_debug("Out of stream buffers: %m");
+ return;
+ }
+ d = buf->buffer->datas;
+
+ wanted = buf->requested ?
+ SPA_MIN(buf->requested * sess->info.stride, d[0].maxsize)
+ : d[0].maxsize;
+
+ avail = spa_ringbuffer_get_read_index(&sess->ring, &index);
+
+ target_buffer = sess->target_buffer + sess->last_packet_size / 2;
+
+ if (avail < wanted || sess->buffering) {
+ memset(d[0].data, 0, wanted);
+ if (!sess->buffering && sess->have_sync) {
+ pw_log_debug("underrun %u/%u < %u, buffering...",
+ avail, target_buffer, wanted);
+ sess->buffering = true;
+ }
+ } else {
+ float error, corr;
+ if (avail > (int32_t)SPA_MIN(target_buffer * 8, BUFFER_SIZE)) {
+ pw_log_warn("overrun %u > %u", avail, target_buffer * 8);
+ index += avail - target_buffer;
+ avail = target_buffer;
+ } else {
+ if (sess->first) {
+ if ((uint32_t)avail > target_buffer) {
+ uint32_t skip = avail - target_buffer;
+ pw_log_debug("first: avail:%d skip:%u target:%u",
+ avail, skip, target_buffer);
+ index += skip;
+ avail = target_buffer;
+ }
+ sess->first = false;
+ }
+ error = (float)target_buffer - (float)avail;
+ error = SPA_CLAMP(error, -sess->max_error, sess->max_error);
+
+ corr = spa_dll_update(&sess->dll, error);
+
+ pw_log_debug("avail:%u target:%u error:%f corr:%f", avail,
+ target_buffer, error, corr);
+
+ if (sess->rate_match) {
+ SPA_FLAG_SET(sess->rate_match->flags, SPA_IO_RATE_MATCH_FLAG_ACTIVE);
+ sess->rate_match->rate = 1.0f / corr;
+ }
+ }
+ spa_ringbuffer_read_data(&sess->ring,
+ sess->buffer,
+ BUFFER_SIZE,
+ index & BUFFER_MASK,
+ d[0].data, wanted);
+
+ index += wanted;
+ spa_ringbuffer_read_update(&sess->ring, index);
+ }
+ d[0].chunk->size = wanted;
+ d[0].chunk->stride = sess->info.stride;
+ d[0].chunk->offset = 0;
+ buf->size = wanted / sess->info.stride;
+
+ pw_stream_queue_buffer(sess->stream, buf);
+}
+
+static void on_stream_state_changed(void *d, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct session *sess = d;
+ struct impl *impl = sess->impl;
+
+ switch (state) {
+ case PW_STREAM_STATE_UNCONNECTED:
+ pw_log_info("stream disconnected, unloading");
+ pw_impl_module_schedule_destroy(impl->module);
+ break;
+ case PW_STREAM_STATE_ERROR:
+ pw_log_error("stream error: %s", error);
+ break;
+ default:
+ break;
+ }
+}
+
+static void stream_io_changed(void *data, uint32_t id, void *area, uint32_t size)
+{
+ struct session *sess = data;
+ switch (id) {
+ case SPA_IO_RateMatch:
+ sess->rate_match = area;
+ break;
+ }
+}
+
+static const struct pw_stream_events out_stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = stream_destroy,
+ .state_changed = on_stream_state_changed,
+ .io_changed = stream_io_changed,
+ .process = stream_process
+};
+
+static void
+on_rtp_io(void *data, int fd, uint32_t mask)
+{
+ struct session *sess = data;
+ struct rtp_header *hdr;
+ ssize_t len, hlen;
+ uint8_t buffer[2048], *payload;
+
+ if (mask & SPA_IO_IN) {
+ uint32_t index, expected_index, timestamp;
+ uint16_t seq;
+ int32_t filled;
+
+ if ((len = recv(fd, buffer, sizeof(buffer), 0)) < 0)
+ goto receive_error;
+
+ if (len < 12)
+ goto short_packet;
+
+ hdr = (struct rtp_header*)buffer;
+ if (hdr->v != 2)
+ goto invalid_version;
+
+ hlen = 12 + hdr->cc * 4;
+ if (hlen > len)
+ goto invalid_len;
+
+ if (sess->have_ssrc && sess->expected_ssrc != hdr->ssrc)
+ goto unexpected_ssrc;
+ sess->expected_ssrc = hdr->ssrc;
+ sess->have_ssrc = true;
+
+ seq = ntohs(hdr->sequence_number);
+ if (sess->have_seq && sess->expected_seq != seq) {
+ pw_log_warn("unexpected seq (%d != %d)", seq, sess->expected_seq);
+ }
+ sess->expected_seq = seq + 1;
+ sess->have_seq = true;
+
+ len = SPA_ROUND_DOWN(len - hlen, sess->info.stride);
+ payload = &buffer[hlen];
+
+ filled = spa_ringbuffer_get_write_index(&sess->ring, &index);
+
+ timestamp = ntohl(hdr->timestamp);
+ expected_index = timestamp * sess->info.stride;
+
+ if (!sess->have_sync) {
+ pw_log_trace("got rtp, no sync");
+ sess->ring.readindex = sess->ring.writeindex =
+ index = expected_index;
+ filled = 0;
+ sess->have_sync = true;
+ sess->buffering = true;
+ pw_log_debug("sync to timestamp %u", index);
+
+ spa_dll_init(&sess->dll);
+ spa_dll_set_bw(&sess->dll, SPA_DLL_BW_MIN, 128, sess->info.info.rate);
+
+ } else if (expected_index != index) {
+ pw_log_trace("got rtp, wrong timestamp");
+ pw_log_debug("unexpected timestamp (%u != %u)",
+ index / sess->info.stride,
+ expected_index / sess->info.stride);
+ index = expected_index;
+ filled = 0;
+ }
+
+ if (filled + len > BUFFER_SIZE) {
+ pw_log_debug("got rtp, capture overrun %u %zd", filled, len);
+ sess->have_sync = false;
+ } else {
+ uint32_t target_buffer;
+
+ pw_log_trace("got rtp packet len:%zd", len);
+ spa_ringbuffer_write_data(&sess->ring,
+ sess->buffer,
+ BUFFER_SIZE,
+ index & BUFFER_MASK,
+ payload, len);
+ index += len;
+ filled += len;
+ spa_ringbuffer_write_update(&sess->ring, index);
+
+ sess->last_packet_size = len;
+ target_buffer = sess->target_buffer + len/2;
+
+ if (sess->buffering && (uint32_t)filled > target_buffer) {
+ sess->buffering = false;
+ pw_log_debug("buffering done %u > %u",
+ filled, target_buffer);
+ }
+ }
+ }
+ 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 RTP version");
+ return;
+invalid_len:
+ pw_log_warn("invalid RTP length");
+ return;
+unexpected_ssrc:
+ pw_log_warn("unexpected SSRC (expected %u != %u)",
+ sess->expected_ssrc, hdr->ssrc);
+ return;
+}
+
+static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname)
+{
+ int af, fd, val, res;
+ struct ifreq req;
+
+ af = sa->sa_family;
+ if ((fd = socket(af, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) {
+ pw_log_error("socket failed: %m");
+ return -errno;
+ }
+#ifdef SO_TIMESTAMP
+ val = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &val, sizeof(val)) < 0) {
+ res = -errno;
+ pw_log_error("setsockopt failed: %m");
+ goto error;
+ }
+#endif
+ val = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
+ res = -errno;
+ pw_log_error("setsockopt failed: %m");
+ goto error;
+ }
+
+ spa_zero(req);
+ if (ifname) {
+ snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", ifname);
+ res = ioctl(fd, SIOCGIFINDEX, &req);
+ if (res < 0)
+ pw_log_warn("SIOCGIFINDEX %s failed: %m", ifname);
+ }
+ res = 0;
+ if (af == AF_INET) {
+ static const uint32_t ipv4_mcast_mask = 0xe0000000;
+ struct sockaddr_in *sa4 = (struct sockaddr_in*)sa;
+ if ((ntohl(sa4->sin_addr.s_addr) & ipv4_mcast_mask) == ipv4_mcast_mask) {
+ struct ip_mreqn mr4;
+ memset(&mr4, 0, sizeof(mr4));
+ mr4.imr_multiaddr = sa4->sin_addr;
+ mr4.imr_ifindex = req.ifr_ifindex;
+ res = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mr4, sizeof(mr4));
+ } else {
+ sa4->sin_addr.s_addr = INADDR_ANY;
+ }
+ } else if (af == AF_INET6) {
+ struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)sa;
+ if (sa6->sin6_addr.s6_addr[0] == 0xff) {
+ struct ipv6_mreq mr6;
+ memset(&mr6, 0, sizeof(mr6));
+ mr6.ipv6mr_multiaddr = sa6->sin6_addr;
+ mr6.ipv6mr_interface = req.ifr_ifindex;
+ res = setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mr6, sizeof(mr6));
+ } else {
+ sa6->sin6_addr = in6addr_any;
+ }
+ } else {
+ res = -EINVAL;
+ goto error;
+ }
+
+ if (res < 0) {
+ res = -errno;
+ pw_log_error("join mcast failed: %m");
+ goto error;
+ }
+
+ if (bind(fd, sa, salen) < 0) {
+ res = -errno;
+ pw_log_error("bind() failed: %m");
+ goto error;
+ }
+ return fd;
+error:
+ return res;
+}
+
+static uint32_t msec_to_bytes(struct sdp_info *info, uint32_t msec)
+{
+ return msec * info->stride * info->info.rate / 1000;
+}
+
+static void session_touch(struct session *sess)
+{
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ sess->timestamp = SPA_TIMESPEC_TO_NSEC(&ts);
+}
+
+static void session_free(struct session *sess)
+{
+ if (sess->impl) {
+ pw_log_info("free session %s %s", sess->info.origin, sess->info.session);
+ sess->impl->n_sessions--;
+ spa_list_remove(&sess->link);
+ }
+ if (sess->stream)
+ pw_stream_destroy(sess->stream);
+ if (sess->source)
+ pw_loop_destroy_source(sess->impl->data_loop, sess->source);
+ free(sess);
+}
+
+struct session_info {
+ struct session *session;
+ 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 session_info *i = data;
+ int res = 0;
+
+ i->matched = true;
+ if (spa_streq(action, "create-stream")) {
+ pw_properties_update_string(i->props, str, len);
+ }
+ return res;
+}
+
+static int session_new(struct impl *impl, struct sdp_info *info)
+{
+ struct session *session;
+ const struct spa_pod *params[1];
+ struct spa_pod_builder b;
+ uint32_t n_params;
+ uint8_t buffer[1024];
+ struct pw_properties *props;
+ int res, fd, sess_latency_msec;
+ const char *str;
+
+ if (impl->n_sessions >= MAX_SESSIONS) {
+ pw_log_warn("too many sessions (%u >= %u)", impl->n_sessions, MAX_SESSIONS);
+ return -EMFILE;
+ }
+
+ session = calloc(1, sizeof(struct session));
+ if (session == NULL)
+ return -errno;
+
+ session->info = *info;
+ session->first = true;
+
+ props = pw_properties_copy(impl->stream_props);
+ if (props == NULL) {
+ res = -errno;
+ goto error;
+ }
+
+ pw_properties_set(props, "rtp.origin", info->origin);
+ pw_properties_setf(props, "rtp.payload", "%u", info->payload);
+ pw_properties_setf(props, "rtp.fmt", "%s/%u/%u", info->format_info->mime,
+ info->info.rate, info->info.channels);
+ if (info->session[0]) {
+ pw_properties_set(props, "rtp.session", info->session);
+ pw_properties_setf(props, PW_KEY_MEDIA_NAME, "RTP Stream (%s)",
+ info->session);
+ pw_properties_setf(props, PW_KEY_NODE_NAME, "%s",
+ info->session);
+ } else {
+ pw_properties_set(props, PW_KEY_MEDIA_NAME, "RTP Stream");
+ }
+
+ if ((str = pw_properties_get(impl->props, "stream.rules")) != NULL) {
+ struct session_info sinfo = {
+ .session = session,
+ .props = props,
+ };
+ pw_conf_match_rules(str, strlen(str), NAME, &props->dict,
+ rule_matched, &sinfo);
+
+ if (!sinfo.matched) {
+ res = 0;
+ pw_log_info("session '%s' was not matched", info->session);
+ goto error;
+ }
+ }
+
+ pw_log_info("new session %s %s", info->origin, info->session);
+
+ sess_latency_msec = pw_properties_get_uint32(props,
+ "sess.latency.msec", impl->sess_latency_msec);
+
+ session->target_buffer = msec_to_bytes(info, sess_latency_msec);
+ session->max_error = msec_to_bytes(info, ERROR_MSEC);
+
+ pw_properties_setf(props, PW_KEY_NODE_RATE, "1/%d", info->info.rate);
+ pw_properties_setf(props, PW_KEY_NODE_LATENCY, "%d/%d",
+ session->target_buffer / (2 * info->stride), info->info.rate);
+
+ spa_dll_init(&session->dll);
+ spa_dll_set_bw(&session->dll, SPA_DLL_BW_MIN, 128, session->info.info.rate);
+
+ session->stream = pw_stream_new(impl->core,
+ "rtp-source playback", props);
+ if (session->stream == NULL) {
+ res = -errno;
+ pw_log_error("can't create stream: %m");
+ goto error;
+ }
+
+ pw_stream_add_listener(session->stream,
+ &session->stream_listener,
+ &out_stream_events, session);
+
+ n_params = 0;
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[n_params++] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat,
+ &info->info);
+
+ if ((res = pw_stream_connect(session->stream,
+ PW_DIRECTION_OUTPUT,
+ PW_ID_ANY,
+ PW_STREAM_FLAG_MAP_BUFFERS |
+ PW_STREAM_FLAG_AUTOCONNECT |
+ PW_STREAM_FLAG_RT_PROCESS,
+ params, n_params)) < 0) {
+ pw_log_error("can't connect stream: %s", spa_strerror(res));
+ goto error;
+ }
+
+ if ((fd = make_socket((const struct sockaddr *)&info->sa,
+ info->salen, impl->ifname)) < 0) {
+ res = fd;
+ goto error;
+ }
+
+ session->source = pw_loop_add_io(impl->data_loop, fd,
+ SPA_IO_IN, true, on_rtp_io, session);
+ if (session->source == NULL) {
+ res = -errno;
+ pw_log_error("can't create io source: %m");
+ goto error;
+ }
+
+ pw_log_info("starting RTP listener");
+ session_touch(session);
+
+ session->impl = impl;
+ spa_list_append(&impl->sessions, &session->link);
+ impl->n_sessions++;
+
+ return 0;
+error:
+ session_free(session);
+ return res;
+}
+
+static struct session *session_find(struct impl *impl, struct sdp_info *info)
+{
+ struct session *sess;
+ spa_list_for_each(sess, &impl->sessions, link) {
+ if (info->hash == sess->info.hash &&
+ spa_streq(info->origin, sess->info.origin))
+ return sess;
+ }
+ return NULL;
+}
+
+static int parse_sdp_c(struct impl *impl, char *c, struct sdp_info *info)
+{
+ int res;
+
+ c[strcspn(c, "/")] = 0;
+ if (spa_strstartswith(c, "c=IN IP4 ")) {
+ struct sockaddr_in *sa = (struct sockaddr_in*) &info->sa;
+
+ c += strlen("c=IN IP4 ");
+ if (inet_pton(AF_INET, c, &sa->sin_addr) <= 0) {
+ res = -errno;
+ pw_log_warn("inet_pton(%s) failed: %m", c);
+ goto error;
+ }
+ sa->sin_family = AF_INET;
+ info->salen = sizeof(struct sockaddr_in);
+ }
+ else if (spa_strstartswith(c, "c=IN IP6 ")) {
+ struct sockaddr_in6 *sa = (struct sockaddr_in6*) &info->sa;
+
+ c += strlen("c=IN IP6 ");
+ if (inet_pton(AF_INET6, c, &sa->sin6_addr) <= 0) {
+ res = -errno;
+ pw_log_warn("inet_pton(%s) failed: %m", c);
+ goto error;
+ }
+
+ sa->sin6_family = AF_INET6;
+ info->salen = sizeof(struct sockaddr_in6);
+ } else
+ return -EINVAL;
+
+
+ res= 0;
+error:
+ return res;
+}
+
+static int parse_sdp_m(struct impl *impl, char *c, struct sdp_info *info)
+{
+ int port, payload;
+
+ if (!spa_strstartswith(c, "m=audio "))
+ return -EINVAL;
+
+ c += strlen("m=audio ");
+ if (sscanf(c, "%i RTP/AVP %i", &port, &payload) != 2)
+ return -EINVAL;
+
+ if (port <= 0 || port > 0xFFFF)
+ return -EINVAL;
+
+ if (payload < 0 || payload > 127)
+ return -EINVAL;
+
+ info->port = (uint16_t) port;
+ info->payload = (uint8_t) payload;
+
+ return 0;
+}
+
+static int parse_sdp_a(struct impl *impl, char *c, struct sdp_info *info)
+{
+ int payload, len, rate, channels;
+
+ if (!spa_strstartswith(c, "a=rtpmap:"))
+ return 0;
+
+ c += strlen("a=rtpmap:");
+
+ if (sscanf(c, "%i %n", &payload, &len) != 1)
+ return -EINVAL;
+
+ if (payload < 0 || payload > 127)
+ return -EINVAL;
+
+ if (payload != info->payload)
+ return 0;
+
+ c += len;
+ c[strcspn(c, "/")] = 0;
+
+ info->format_info = find_format_info(c);
+ if (info->format_info == NULL)
+ return -EINVAL;
+
+ info->info.format = info->format_info->format;
+ info->stride = info->format_info->size;
+
+ c += strlen(c) + 1;
+ if (sscanf(c, "%u/%u", &rate, &channels) == 2) {
+ info->info.rate = rate;
+ info->info.channels = channels;
+ if (channels == 2) {
+ info->info.position[0] = SPA_AUDIO_CHANNEL_FL;
+ info->info.position[1] = SPA_AUDIO_CHANNEL_FR;
+ }
+ } else if (sscanf(c, "%u", &rate) == 1) {
+ info->info.rate = rate;
+ info->info.channels = 1;
+ } else
+ return -EINVAL;
+
+ info->stride *= info->info.channels;
+
+ return 0;
+}
+
+static int parse_sdp(struct impl *impl, char *sdp, struct sdp_info *info)
+{
+ char *s = sdp;
+ int count = 0, res = 0;
+ size_t l;
+
+ while (*s) {
+ if ((l = strcspn(s, "\r\n")) < 2)
+ goto too_short;
+
+ s[l] = 0;
+ pw_log_debug("%d: %s", count, s);
+
+ if (count++ == 0 && strcmp(s, "v=0") != 0)
+ goto invalid_version;
+
+ if (spa_strstartswith(s, "o="))
+ snprintf(info->origin, sizeof(info->origin), "%s", &s[2]);
+ else if (spa_strstartswith(s, "s="))
+ snprintf(info->session, sizeof(info->session), "%s", &s[2]);
+ else if (spa_strstartswith(s, "c="))
+ res = parse_sdp_c(impl, s, info);
+ else if (spa_strstartswith(s, "m="))
+ res = parse_sdp_m(impl, s, info);
+ else if (spa_strstartswith(s, "a="))
+ res = parse_sdp_a(impl, s, info);
+
+ if (res < 0)
+ goto error;
+ s += l + 1;
+ while (isspace(*s))
+ s++;
+ }
+ if (((struct sockaddr*) &info->sa)->sa_family == AF_INET)
+ ((struct sockaddr_in*) &info->sa)->sin_port = htons(info->port);
+ else
+ ((struct sockaddr_in6*) &info->sa)->sin6_port = htons(info->port);
+
+ return 0;
+too_short:
+ pw_log_warn("SDP: line starting with `%.6s...' too short", s);
+ return -EINVAL;
+invalid_version:
+ pw_log_warn("SDP: invalid first version line `%*s'", (int)l, s);
+ return -EINVAL;
+error:
+ pw_log_warn("SDP: error: %s", spa_strerror(res));
+ return res;
+}
+
+static int parse_sap(struct impl *impl, void *data, size_t len)
+{
+ struct sap_header *header;
+ char *mime, *sdp;
+ struct sdp_info info;
+ struct session *sess;
+ int res;
+ size_t offs;
+ bool bye;
+
+ if (len < 8)
+ return -EINVAL;
+
+ header = (struct sap_header*) data;
+ if (header->v != 1)
+ return -EINVAL;
+
+ if (header->e)
+ return -ENOTSUP;
+ if (header->c)
+ return -ENOTSUP;
+
+ offs = header->a ? 12 : 8;
+ offs += header->auth_len * 4;
+ if (len <= offs)
+ return -EINVAL;
+
+ mime = SPA_PTROFF(data, offs, char);
+ if (spa_strstartswith(mime, "v=0")) {
+ sdp = mime;
+ mime = SAP_MIME_TYPE;
+ } else if (spa_streq(mime, SAP_MIME_TYPE))
+ sdp = SPA_PTROFF(mime, strlen(mime)+1, char);
+ else
+ return -EINVAL;
+
+ pw_log_debug("got sap: %s %s", mime, sdp);
+
+ spa_zero(info);
+ if ((res = parse_sdp(impl, sdp, &info)) < 0)
+ return res;
+
+ bye = header->t;
+
+ sess = session_find(impl, &info);
+ if (sess == NULL) {
+ if (!bye)
+ session_new(impl, &info);
+ } else {
+ if (bye)
+ session_free(sess);
+ else
+ session_touch(sess);
+ }
+ return res;
+}
+
+static void
+on_sap_io(void *data, int fd, uint32_t mask)
+{
+ struct impl *impl = data;
+
+ if (mask & SPA_IO_IN) {
+ uint8_t buffer[2048];
+ ssize_t len;
+
+ if ((len = recv(fd, buffer, sizeof(buffer), 0)) < 0) {
+ pw_log_warn("recv error: %m");
+ return;
+ }
+ if ((size_t)len >= sizeof(buffer))
+ return;
+
+ buffer[len] = 0;
+ parse_sap(impl, buffer, len);
+ }
+}
+
+static int start_sap_listener(struct impl *impl)
+{
+ struct sockaddr_in sa4;
+ struct sockaddr_in6 sa6;
+ struct sockaddr *sa;
+ socklen_t salen;
+ int fd, res;
+
+ if (inet_pton(AF_INET, impl->sap_ip, &sa4.sin_addr) > 0) {
+ sa4.sin_family = AF_INET;
+ sa4.sin_port = htons(impl->sap_port);
+ sa = (struct sockaddr*) &sa4;
+ salen = sizeof(sa4);
+ } else if (inet_pton(AF_INET6, impl->sap_ip, &sa6.sin6_addr) > 0) {
+ sa6.sin6_family = AF_INET6;
+ sa6.sin6_port = htons(impl->sap_port);
+ sa = (struct sockaddr*) &sa6;
+ salen = sizeof(sa6);
+ } else
+ return -EINVAL;
+
+ if ((fd = make_socket(sa, salen, impl->ifname)) < 0)
+ return fd;
+
+ pw_log_info("starting SAP listener");
+ impl->sap_source = pw_loop_add_io(impl->loop, fd,
+ SPA_IO_IN, true, on_sap_io, impl);
+ if (impl->sap_source == NULL) {
+ res = -errno;
+ goto error;
+ }
+ return 0;
+error:
+ close(fd);
+ return res;
+
+}
+
+static void on_timer_event(void *data, uint64_t expirations)
+{
+ struct impl *impl = data;
+ struct timespec now;
+ struct session *sess, *tmp;
+ uint64_t timestamp, interval;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ timestamp = SPA_TIMESPEC_TO_NSEC(&now);
+ interval = impl->cleanup_interval * SPA_NSEC_PER_SEC;
+
+ spa_list_for_each_safe(sess, tmp, &impl->sessions, link) {
+ if (sess->timestamp + interval < timestamp) {
+ pw_log_debug("More than %lu elapsed from last advertisement at %lu", interval, sess->timestamp);
+ pw_log_info("No advertisement packets found for timeout, closing RTP source");
+ session_free(sess);
+ }
+ }
+}
+
+static void core_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->core_listener);
+ impl->core = NULL;
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_proxy_events core_proxy_events = {
+ .destroy = core_destroy,
+};
+
+static void impl_destroy(struct impl *impl)
+{
+ struct session *sess;
+ spa_list_consume(sess, &impl->sessions, link)
+ session_free(sess);
+
+ if (impl->core && impl->do_disconnect)
+ pw_core_disconnect(impl->core);
+
+ if (impl->sap_source)
+ pw_loop_destroy_source(impl->loop, impl->sap_source);
+ if (impl->timer)
+ pw_loop_destroy_source(impl->loop, impl->timer);
+
+ pw_properties_free(impl->stream_props);
+ pw_properties_free(impl->props);
+
+ free(impl->ifname);
+ free(impl->sap_ip);
+ free(impl);
+}
+
+static void module_destroy(void *d)
+{
+ struct impl *impl = d;
+ spa_hook_remove(&impl->module_listener);
+ impl_destroy(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+static void on_core_error(void *d, uint32_t id, int seq, int res, const char *message)
+{
+ struct impl *impl = d;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct impl *impl;
+ const char *str;
+ struct timespec value, interval;
+ int res = 0;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -errno;
+
+ spa_list_init(&impl->sessions);
+
+ if (args == NULL)
+ args = "";
+
+ impl->props = pw_properties_new_string(args);
+ impl->stream_props = pw_properties_new(NULL, NULL);
+ if (impl->props == NULL || impl->stream_props == NULL) {
+ res = -errno;
+ pw_log_error( "can't create properties: %m");
+ goto out;
+ }
+
+ impl->module = module;
+ impl->module_context = context;
+ impl->loop = pw_context_get_main_loop(context);
+ impl->data_loop = pw_data_loop_get_loop(pw_context_get_data_loop(context));
+
+ if (pw_properties_get(impl->stream_props, PW_KEY_NODE_VIRTUAL) == NULL)
+ pw_properties_set(impl->stream_props, PW_KEY_NODE_VIRTUAL, "true");
+ if (pw_properties_get(impl->stream_props, PW_KEY_NODE_NETWORK) == NULL)
+ pw_properties_set(impl->stream_props, PW_KEY_NODE_NETWORK, "true");
+
+ if ((str = pw_properties_get(impl->props, "stream.props")) != NULL)
+ pw_properties_update_string(impl->stream_props, str, strlen(str));
+
+ str = pw_properties_get(impl->props, "local.ifname");
+ impl->ifname = str ? strdup(str) : NULL;
+
+ str = pw_properties_get(impl->props, "sap.ip");
+ impl->sap_ip = strdup(str ? str : DEFAULT_SAP_IP);
+ impl->sap_port = pw_properties_get_uint32(impl->props,
+ "sap.port", DEFAULT_SAP_PORT);
+ impl->sess_latency_msec = pw_properties_get_uint32(impl->props,
+ "sess.latency.msec", DEFAULT_SESS_LATENCY);
+ impl->cleanup_interval = pw_properties_get_uint32(impl->props,
+ "sap.interval.sec", DEFAULT_CLEANUP_INTERVAL_SEC);
+
+ impl->core = pw_context_get_object(impl->module_context, PW_TYPE_INTERFACE_Core);
+ if (impl->core == NULL) {
+ str = pw_properties_get(impl->props, PW_KEY_REMOTE_NAME);
+ impl->core = pw_context_connect(impl->module_context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, str,
+ NULL),
+ 0);
+ impl->do_disconnect = true;
+ }
+ if (impl->core == NULL) {
+ res = -errno;
+ pw_log_error("can't connect: %m");
+ goto out;
+ }
+
+ pw_proxy_add_listener((struct pw_proxy*)impl->core,
+ &impl->core_proxy_listener,
+ &core_proxy_events, impl);
+ pw_core_add_listener(impl->core,
+ &impl->core_listener,
+ &core_events, impl);
+
+ impl->timer = pw_loop_add_timer(impl->loop, on_timer_event, impl);
+ if (impl->timer == NULL) {
+ res = -errno;
+ pw_log_error("can't create timer source: %m");
+ goto out;
+ }
+ value.tv_sec = 0;
+ value.tv_nsec = 1;
+ interval.tv_sec = impl->cleanup_interval;
+ interval.tv_nsec = 0;
+ pw_loop_update_timer(impl->loop, impl->timer, &value, &interval, false);
+
+ if ((res = start_sap_listener(impl)) < 0)
+ goto out;
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_info));
+
+ pw_log_info("Successfully loaded module-rtp-source");
+
+ return 0;
+out:
+ impl_destroy(impl);
+ return res;
+}
diff --git a/src/modules/module-rtp/rtp.h b/src/modules/module-rtp/rtp.h
new file mode 100644
index 0000000..92ff364
--- /dev/null
+++ b/src/modules/module-rtp/rtp.h
@@ -0,0 +1,78 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_RTP_H
+#define PIPEWIRE_RTP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct rtp_header {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ unsigned cc:4;
+ unsigned x:1;
+ unsigned p:1;
+ unsigned v:2;
+
+ unsigned pt:7;
+ unsigned m:1;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+ unsigned v:2;
+ unsigned p:1;
+ unsigned x:1;
+ unsigned cc:4;
+
+ unsigned m:1;
+ unsigned pt:7;
+#else
+#error "Unknown byte order"
+#endif
+ uint16_t sequence_number;
+ uint32_t timestamp;
+ uint32_t ssrc;
+ uint32_t csrc[0];
+} __attribute__ ((packed));
+
+struct rtp_payload {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ unsigned frame_count:4;
+ unsigned rfa0:1;
+ unsigned is_last_fragment:1;
+ unsigned is_first_fragment:1;
+ unsigned is_fragmented:1;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+ unsigned is_fragmented:1;
+ unsigned is_first_fragment:1;
+ unsigned is_last_fragment:1;
+ unsigned rfa0:1;
+ unsigned frame_count:4;
+#endif
+} __attribute__ ((packed));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_RTP_H */
diff --git a/src/modules/module-rtp/sap.h b/src/modules/module-rtp/sap.h
new file mode 100644
index 0000000..b9a0a7a
--- /dev/null
+++ b/src/modules/module-rtp/sap.h
@@ -0,0 +1,58 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_SAP_H
+#define PIPEWIRE_SAP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct sap_header {
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+ unsigned c:1;
+ unsigned e:1;
+ unsigned t:1;
+ unsigned r:1;
+ unsigned a:1;
+ unsigned v:3;
+#elif __BYTE_ORDER == __BIG_ENDIAN
+ unsigned v:3;
+ unsigned a:1;
+ unsigned r:1;
+ unsigned t:1;
+ unsigned e:1;
+ unsigned c:1;
+#else
+#error "Unknown byte order"
+#endif
+ uint8_t auth_len;
+ uint16_t msg_id_hash;
+} __attribute__ ((packed));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_SAP_H */
diff --git a/src/modules/module-session-manager.c b/src/modules/module-session-manager.c
new file mode 100644
index 0000000..85879f7
--- /dev/null
+++ b/src/modules/module-session-manager.c
@@ -0,0 +1,74 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <pipewire/impl.h>
+
+/** \page page_module_session_manager PipeWire Module: Session Manager
+ *
+ * This module implements some usefull objects for implementing a session
+ * manager. It is not yet actively used.
+ */
+
+/* client-endpoint.c */
+int client_endpoint_factory_init(struct pw_impl_module *module);
+/* client-session.c */
+int client_session_factory_init(struct pw_impl_module *module);
+
+int session_factory_init(struct pw_impl_module *module);
+int endpoint_factory_init(struct pw_impl_module *module);
+int endpoint_stream_factory_init(struct pw_impl_module *module);
+int endpoint_link_factory_init(struct pw_impl_module *module);
+
+/* protocol-native.c */
+int pw_protocol_native_ext_session_manager_init(struct pw_context *context);
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "George Kiagiadakis <george.kiagiadakis@collabora.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Implements objects for session management" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ int res;
+
+ if ((res = pw_protocol_native_ext_session_manager_init(context)) < 0)
+ return res;
+
+ client_endpoint_factory_init(module);
+ client_session_factory_init(module);
+ session_factory_init(module);
+ endpoint_factory_init(module);
+ endpoint_stream_factory_init(module);
+ endpoint_link_factory_init(module);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+}
diff --git a/src/modules/module-session-manager/client-endpoint/client-endpoint.c b/src/modules/module-session-manager/client-endpoint/client-endpoint.c
new file mode 100644
index 0000000..b2f2d98
--- /dev/null
+++ b/src/modules/module-session-manager/client-endpoint/client-endpoint.c
@@ -0,0 +1,296 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <spa/utils/result.h>
+#include <pipewire/impl.h>
+
+#include <pipewire/extensions/session-manager.h>
+
+#include "client-endpoint.h"
+#include "endpoint.h"
+#include "endpoint-stream.h"
+
+#define NAME "client-endpoint"
+
+struct factory_data {
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_impl_factory *factory;
+ struct spa_hook factory_listener;
+};
+
+static struct endpoint_stream *find_stream(struct client_endpoint *this, uint32_t id)
+{
+ struct endpoint_stream *s;
+ spa_list_for_each(s, &this->streams, link) {
+ if (s->id == id)
+ return s;
+ }
+ return NULL;
+}
+
+static int client_endpoint_update(void *object,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_info *info)
+{
+ struct client_endpoint *this = object;
+ struct endpoint *endpoint = &this->endpoint;
+
+ return endpoint_update(endpoint, change_mask, n_params, params, info);
+}
+
+static int client_endpoint_stream_update(void *object,
+ uint32_t stream_id,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_stream_info *info)
+{
+ struct client_endpoint *this = object;
+ struct endpoint *endpoint = &this->endpoint;
+ struct endpoint_stream *stream = find_stream(this, stream_id);
+ struct pw_properties *props = NULL;
+
+ if (!stream) {
+ static const char * const keys[] = {
+ PW_KEY_FACTORY_ID,
+ PW_KEY_CLIENT_ID,
+ PW_KEY_ENDPOINT_ID,
+ PW_KEY_PRIORITY_SESSION,
+ PW_KEY_ENDPOINT_MONITOR,
+ PW_KEY_ENDPOINT_STREAM_NAME,
+ PW_KEY_ENDPOINT_STREAM_DESCRIPTION,
+ NULL
+ };
+
+ struct pw_context *context = pw_global_get_context(endpoint->global);
+
+ stream = calloc(1, sizeof(struct endpoint_stream));
+ if (!stream)
+ goto no_mem;
+
+ props = pw_properties_new(NULL, NULL);
+ if (!props)
+ goto no_mem;
+
+ pw_properties_update_keys(props, &endpoint->props->dict, keys);
+ if (info && info->props)
+ pw_properties_update_keys(props, info->props, keys);
+
+ if (endpoint_stream_init(stream, stream_id, endpoint->info.id,
+ this, context, props) < 0)
+ goto no_mem;
+
+ spa_list_append(&this->streams, &stream->link);
+ }
+ else if (change_mask & PW_CLIENT_ENDPOINT_STREAM_UPDATE_DESTROYED) {
+ endpoint_stream_clear(stream);
+ spa_list_remove(&stream->link);
+ free(stream);
+ stream = NULL;
+ }
+
+ return stream ?
+ endpoint_stream_update(stream, change_mask, n_params, params, info)
+ : 0;
+
+ no_mem:
+ pw_properties_free(props);
+ free(stream);
+ pw_log_error(NAME" %p: cannot update stream: no memory", this);
+ pw_resource_errorf(this->resource, -ENOMEM,
+ NAME" %p: cannot update stream: no memory", this);
+ return -ENOMEM;
+}
+
+static const struct pw_client_endpoint_methods methods = {
+ PW_VERSION_CLIENT_ENDPOINT_METHODS,
+ .update = client_endpoint_update,
+ .stream_update = client_endpoint_stream_update,
+};
+
+static void client_endpoint_destroy(void *data)
+{
+ struct client_endpoint *this = data;
+ struct endpoint_stream *s;
+
+ pw_log_debug(NAME" %p: destroy", this);
+
+ spa_list_consume(s, &this->streams, link) {
+ endpoint_stream_clear(s);
+ spa_list_remove(&s->link);
+ free(s);
+ }
+ endpoint_clear(&this->endpoint);
+ spa_hook_remove(&this->resource_listener);
+
+ free(this);
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = client_endpoint_destroy,
+};
+
+static void *create_object(void *data,
+ struct pw_resource *owner_resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id)
+{
+ struct factory_data *d = data;
+ struct pw_impl_factory *factory = d->factory;
+ struct client_endpoint *this;
+ struct pw_impl_client *owner = pw_resource_get_client(owner_resource);
+ struct pw_context *context = pw_impl_client_get_context(owner);
+
+ this = calloc(1, sizeof(struct client_endpoint));
+ if (this == NULL)
+ goto no_mem;
+
+ spa_list_init(&this->streams);
+
+ pw_log_debug(NAME" %p: new", this);
+
+ if (!properties)
+ properties = pw_properties_new(NULL, NULL);
+ if (!properties)
+ goto no_mem;
+
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d",
+ pw_impl_client_get_info(owner)->id);
+ pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d",
+ pw_impl_factory_get_info(factory)->id);
+
+ this->resource = pw_resource_new(owner, new_id, PW_PERM_ALL, type, version, 0);
+ if (this->resource == NULL)
+ goto no_mem;
+
+ if (endpoint_init(&this->endpoint, this, context, properties) < 0)
+ goto no_mem;
+
+ pw_resource_add_listener(this->resource, &this->resource_listener,
+ &resource_events, this);
+ pw_resource_add_object_listener(this->resource, &this->object_listener,
+ &methods, this);
+
+ return this;
+
+ no_mem:
+ pw_properties_free(properties);
+ if (this && this->resource)
+ pw_resource_destroy(this->resource);
+ free(this);
+ pw_log_error("can't create client endpoint: no memory");
+ pw_resource_error(owner_resource, -ENOMEM,
+ "can't create client endpoint: no memory");
+ return NULL;
+}
+
+static const struct pw_impl_factory_implementation impl_factory = {
+ PW_VERSION_IMPL_FACTORY_IMPLEMENTATION,
+ .create_object = create_object,
+};
+
+static void factory_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->factory_listener);
+ d->factory = NULL;
+ if (d->module)
+ pw_impl_module_destroy(d->module);
+}
+
+static const struct pw_impl_factory_events factory_events = {
+ PW_VERSION_IMPL_FACTORY_EVENTS,
+ .destroy = factory_destroy,
+};
+
+static void module_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->module_listener);
+ d->module = NULL;
+ if (d->factory)
+ pw_impl_factory_destroy(d->factory);
+}
+
+static void module_registered(void *data)
+{
+ struct factory_data *d = data;
+ struct pw_impl_module *module = d->module;
+ struct pw_impl_factory *factory = d->factory;
+ struct spa_dict_item items[1];
+ char id[16];
+ int res;
+
+ snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id);
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
+ pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
+
+ if ((res = pw_impl_factory_register(factory, NULL)) < 0) {
+ pw_log_error(NAME" %p: can't register factory: %s", factory, spa_strerror(res));
+ }
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+ .registered = module_registered,
+};
+
+int client_endpoint_factory_init(struct pw_impl_module *module)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_factory *factory;
+ struct factory_data *data;
+
+ factory = pw_context_create_factory(context,
+ "client-endpoint",
+ PW_TYPE_INTERFACE_ClientEndpoint,
+ PW_VERSION_CLIENT_ENDPOINT,
+ NULL,
+ sizeof(*data));
+ if (factory == NULL)
+ return -ENOMEM;
+
+ data = pw_impl_factory_get_user_data(factory);
+ data->factory = factory;
+ data->module = module;
+
+ pw_impl_factory_set_implementation(factory, &impl_factory, data);
+ pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data);
+
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ return 0;
+}
diff --git a/src/modules/module-session-manager/client-endpoint/client-endpoint.h b/src/modules/module-session-manager/client-endpoint/client-endpoint.h
new file mode 100644
index 0000000..bc5630d
--- /dev/null
+++ b/src/modules/module-session-manager/client-endpoint/client-endpoint.h
@@ -0,0 +1,62 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef MODULE_SESSION_MANAGER_CLIENT_ENDPOINT_H
+#define MODULE_SESSION_MANAGER_CLIENT_ENDPOINT_H
+
+#include "endpoint.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct client_endpoint {
+ struct pw_resource *resource;
+ struct spa_hook resource_listener;
+ struct spa_hook object_listener;
+ struct endpoint endpoint;
+ struct spa_list streams;
+};
+
+#define pw_client_endpoint_resource(r,m,v,...) \
+ pw_resource_call_res(r,struct pw_client_endpoint_events,m,v,__VA_ARGS__)
+#define pw_client_endpoint_resource_set_id(r,...) \
+ pw_client_endpoint_resource(r,set_id,0,__VA_ARGS__)
+#define pw_client_endpoint_resource_set_session_id(r,...) \
+ pw_client_endpoint_resource(r,set_session_id,0,__VA_ARGS__)
+#define pw_client_endpoint_resource_set_param(r,...) \
+ pw_client_endpoint_resource(r,set_param,0,__VA_ARGS__)
+#define pw_client_endpoint_resource_stream_set_param(r,...) \
+ pw_client_endpoint_resource(r,stream_set_param,0,__VA_ARGS__)
+#define pw_client_endpoint_resource_create_link(r,...) \
+ pw_client_endpoint_resource(r,create_link,0,__VA_ARGS__)
+
+int client_endpoint_factory_init(struct pw_impl_module *module);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* MODULE_SESSION_MANAGER_CLIENT_ENDPOINT_H */
diff --git a/src/modules/module-session-manager/client-endpoint/endpoint-stream.c b/src/modules/module-session-manager/client-endpoint/endpoint-stream.c
new file mode 100644
index 0000000..8dde6f7
--- /dev/null
+++ b/src/modules/module-session-manager/client-endpoint/endpoint-stream.c
@@ -0,0 +1,352 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/session-manager.h>
+
+#include <spa/pod/filter.h>
+
+#include "endpoint-stream.h"
+#include "client-endpoint.h"
+
+#define NAME "endpoint-stream"
+
+struct resource_data {
+ struct endpoint_stream *stream;
+ struct spa_hook object_listener;
+ uint32_t n_subscribe_ids;
+ uint32_t subscribe_ids[32];
+};
+
+#define pw_endpoint_stream_resource(r,m,v,...) \
+ pw_resource_call(r,struct pw_endpoint_stream_events,m,v,__VA_ARGS__)
+#define pw_endpoint_stream_resource_info(r,...) \
+ pw_endpoint_stream_resource(r,info,0,__VA_ARGS__)
+#define pw_endpoint_stream_resource_param(r,...) \
+ pw_endpoint_stream_resource(r,param,0,__VA_ARGS__)
+
+static int endpoint_stream_enum_params (void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct pw_resource *resource = object;
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ struct endpoint_stream *this = data->stream;
+ struct spa_pod *result;
+ struct spa_pod *param;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = { 0 };
+ uint32_t index;
+ uint32_t next = start;
+ uint32_t count = 0;
+
+ while (true) {
+ index = next++;
+ if (index >= this->n_params)
+ break;
+
+ param = this->params[index];
+
+ if (param == NULL || !spa_pod_is_object_id(param, id))
+ continue;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ if (spa_pod_filter(&b, &result, param, filter) != 0)
+ continue;
+
+ pw_log_debug(NAME" %p: %d param %u", this, seq, index);
+
+ pw_endpoint_stream_resource_param(resource, seq, id, index, next, result);
+
+ if (++count == num)
+ break;
+ }
+ return 0;
+}
+
+static int endpoint_stream_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_resource *resource = object;
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ uint32_t i;
+
+ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids));
+ data->n_subscribe_ids = n_ids;
+
+ for (i = 0; i < n_ids; i++) {
+ data->subscribe_ids[i] = ids[i];
+ pw_log_debug(NAME" %p: resource %d subscribe param %u",
+ data->stream, pw_resource_get_id(resource), ids[i]);
+ endpoint_stream_enum_params(resource, 1, ids[i], 0, UINT32_MAX, NULL);
+ }
+ return 0;
+}
+
+static int endpoint_stream_set_param (void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = object;
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ struct endpoint_stream *this = data->stream;
+
+ pw_client_endpoint_resource_set_param(this->client_ep->resource,
+ id, flags, param);
+
+ return 0;
+}
+
+static const struct pw_endpoint_stream_methods methods = {
+ PW_VERSION_ENDPOINT_STREAM_METHODS,
+ .subscribe_params = endpoint_stream_subscribe_params,
+ .enum_params = endpoint_stream_enum_params,
+ .set_param = endpoint_stream_set_param,
+};
+
+struct emit_param_data {
+ struct endpoint_stream *this;
+ struct spa_pod *param;
+ uint32_t id;
+ uint32_t index;
+ uint32_t next;
+};
+
+static int emit_param(void *_data, struct pw_resource *resource)
+{
+ struct emit_param_data *d = _data;
+ struct resource_data *data;
+ uint32_t i;
+
+ data = pw_resource_get_user_data(resource);
+ for (i = 0; i < data->n_subscribe_ids; i++) {
+ if (data->subscribe_ids[i] == d->id) {
+ pw_endpoint_stream_resource_param(resource, 1,
+ d->id, d->index, d->next, d->param);
+ }
+ }
+ return 0;
+}
+
+static void endpoint_stream_notify_subscribed(struct endpoint_stream *this,
+ uint32_t index, uint32_t next)
+{
+ struct pw_global *global = this->global;
+ struct emit_param_data data;
+ struct spa_pod *param = this->params[index];
+
+ if (!param || !spa_pod_is_object (param))
+ return;
+
+ data.this = this;
+ data.param = param;
+ data.id = SPA_POD_OBJECT_ID (param);
+ data.index = index;
+ data.next = next;
+
+ pw_global_for_each_resource(global, emit_param, &data);
+}
+
+static int emit_info(void *data, struct pw_resource *resource)
+{
+ struct endpoint_stream *this = data;
+ pw_endpoint_stream_resource_info(resource, &this->info);
+ return 0;
+}
+
+int endpoint_stream_update(struct endpoint_stream *this,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_stream_info *info)
+{
+ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_PARAMS) {
+ uint32_t i;
+
+ pw_log_debug(NAME" %p: update %d params", this, n_params);
+
+ for (i = 0; i < this->n_params; i++)
+ free(this->params[i]);
+ this->n_params = n_params;
+ if (this->n_params == 0) {
+ free(this->params);
+ this->params = NULL;
+ } else {
+ void *p;
+ p = pw_reallocarray(this->params, n_params, sizeof(struct spa_pod*));
+ if (p == NULL) {
+ free(this->params);
+ this->params = NULL;
+ this->n_params = 0;
+ goto no_mem;
+ }
+ this->params = p;
+ }
+ for (i = 0; i < this->n_params; i++) {
+ this->params[i] = params[i] ? spa_pod_copy(params[i]) : NULL;
+ endpoint_stream_notify_subscribed(this, i, i+1);
+ }
+ }
+
+ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_INFO) {
+ if (info->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_LINK_PARAMS) {
+ free(this->info.link_params);
+ this->info.link_params = spa_pod_copy(info->link_params);
+ }
+
+ if (info->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS)
+ pw_properties_update(this->props, info->props);
+
+ if (info->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PARAMS) {
+ this->info.n_params = info->n_params;
+ if (info->n_params == 0) {
+ free(this->info.params);
+ this->info.params = NULL;
+ } else {
+ void *p;
+ p = pw_reallocarray(this->info.params, info->n_params, sizeof(struct spa_param_info));
+ if (p == NULL) {
+ free(this->info.params);
+ this->info.params = NULL;
+ this->info.n_params = 0;
+ goto no_mem;
+ }
+ this->info.params = p;
+ memcpy(this->info.params, info->params, info->n_params * sizeof(struct spa_param_info));
+ }
+ }
+
+ if (!this->info.name)
+ this->info.name = info->name ? strdup(info->name) : NULL;
+
+ this->info.change_mask = info->change_mask;
+ pw_global_for_each_resource(this->global, emit_info, this);
+ this->info.change_mask = 0;
+ }
+
+ return 0;
+
+ no_mem:
+ pw_log_error(NAME" can't update: no memory");
+ pw_resource_error(this->client_ep->resource, -ENOMEM,
+ NAME" can't update: no memory");
+ return -ENOMEM;
+}
+
+static int endpoint_stream_bind(void *_data, struct pw_impl_client *client,
+ uint32_t permissions, uint32_t version, uint32_t id)
+{
+ struct endpoint_stream *this = _data;
+ struct pw_global *global = this->global;
+ struct pw_resource *resource;
+ struct resource_data *data;
+
+ resource = pw_resource_new(client, id, permissions,
+ pw_global_get_type(global), version, sizeof(*data));
+ if (resource == NULL)
+ goto no_mem;
+
+ data = pw_resource_get_user_data(resource);
+ data->stream = this;
+ pw_resource_add_object_listener(resource, &data->object_listener,
+ &methods, resource);
+
+ pw_log_debug(NAME" %p: bound to %d", this, pw_resource_get_id(resource));
+ pw_global_add_resource(global, resource);
+
+ this->info.change_mask = PW_ENDPOINT_STREAM_CHANGE_MASK_ALL;
+ pw_endpoint_stream_resource_info(resource, &this->info);
+ this->info.change_mask = 0;
+
+ return 0;
+
+ no_mem:
+ pw_log_error(NAME" can't create resource: no memory");
+ pw_resource_error(this->client_ep->resource, -ENOMEM,
+ NAME" can't create resource: no memory");
+ return -ENOMEM;
+}
+
+int endpoint_stream_init(struct endpoint_stream *this,
+ uint32_t id, uint32_t endpoint_id,
+ struct client_endpoint *client_ep,
+ struct pw_context *context,
+ struct pw_properties *properties)
+{
+ pw_log_debug(NAME" %p: new", this);
+
+ this->client_ep = client_ep;
+ this->id = id;
+ this->props = properties;
+
+ pw_properties_setf(properties, PW_KEY_ENDPOINT_ID, "%u", endpoint_id);
+
+ properties = pw_properties_copy(properties);
+ if (!properties)
+ goto no_mem;
+
+ this->global = pw_global_new (context,
+ PW_TYPE_INTERFACE_EndpointStream,
+ PW_VERSION_ENDPOINT_STREAM,
+ properties, endpoint_stream_bind, this);
+ if (!this->global)
+ goto no_mem;
+
+ pw_properties_setf(this->props, PW_KEY_OBJECT_ID, "%u",
+ pw_global_get_id(this->global));
+ pw_properties_setf(this->props, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
+ pw_global_get_serial(this->global));
+
+ this->info.version = PW_VERSION_ENDPOINT_STREAM_INFO;
+ this->info.id = pw_global_get_id(this->global);
+ this->info.endpoint_id = endpoint_id;
+ this->info.props = &this->props->dict;
+
+ return pw_global_register(this->global);
+
+ no_mem:
+ pw_log_error(NAME" - can't create - out of memory");
+ return -ENOMEM;
+}
+
+void endpoint_stream_clear(struct endpoint_stream *this)
+{
+ uint32_t i;
+
+ pw_log_debug(NAME" %p: destroy", this);
+
+ pw_global_destroy(this->global);
+
+ for (i = 0; i < this->n_params; i++)
+ free(this->params[i]);
+ free(this->params);
+
+ free(this->info.name);
+ free(this->info.link_params);
+ free(this->info.params);
+
+ pw_properties_free(this->props);
+}
diff --git a/src/modules/module-session-manager/client-endpoint/endpoint-stream.h b/src/modules/module-session-manager/client-endpoint/endpoint-stream.h
new file mode 100644
index 0000000..7172287
--- /dev/null
+++ b/src/modules/module-session-manager/client-endpoint/endpoint-stream.h
@@ -0,0 +1,64 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef MODULE_SESSION_MANAGER_ENDPOINT_STREAM_H
+#define MODULE_SESSION_MANAGER_ENDPOINT_STREAM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct client_endpoint;
+
+struct endpoint_stream {
+ struct spa_list link;
+ struct client_endpoint *client_ep;
+ struct pw_global *global;
+ uint32_t id; /* endpoint-local stream id */
+ uint32_t n_params;
+ struct spa_pod **params;
+ struct pw_endpoint_stream_info info;
+ struct pw_properties *props; /* wrapper of info.props */
+};
+
+int endpoint_stream_init(struct endpoint_stream *this,
+ uint32_t id, uint32_t endpoint_id,
+ struct client_endpoint *client_ep,
+ struct pw_context *context,
+ struct pw_properties *properties);
+
+void endpoint_stream_clear(struct endpoint_stream *this);
+
+int endpoint_stream_update(struct endpoint_stream *this,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_stream_info *info);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* MODULE_SESSION_MANAGER_ENDPOINT_STREAM_H */
diff --git a/src/modules/module-session-manager/client-endpoint/endpoint.c b/src/modules/module-session-manager/client-endpoint/endpoint.c
new file mode 100644
index 0000000..aa13989
--- /dev/null
+++ b/src/modules/module-session-manager/client-endpoint/endpoint.c
@@ -0,0 +1,385 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/session-manager.h>
+
+#include <spa/pod/filter.h>
+
+#include "endpoint.h"
+#include "client-endpoint.h"
+
+#define NAME "endpoint"
+
+struct resource_data {
+ struct endpoint *endpoint;
+ struct spa_hook object_listener;
+ uint32_t n_subscribe_ids;
+ uint32_t subscribe_ids[32];
+};
+
+#define pw_endpoint_resource(r,m,v,...) \
+ pw_resource_call(r,struct pw_endpoint_events,m,v,__VA_ARGS__)
+#define pw_endpoint_resource_info(r,...) \
+ pw_endpoint_resource(r,info,0,__VA_ARGS__)
+#define pw_endpoint_resource_param(r,...) \
+ pw_endpoint_resource(r,param,0,__VA_ARGS__)
+
+static int endpoint_enum_params (void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct pw_resource *resource = object;
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ struct endpoint *this = data->endpoint;
+ struct spa_pod *result;
+ struct spa_pod *param;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = { 0 };
+ uint32_t index;
+ uint32_t next = start;
+ uint32_t count = 0;
+
+ pw_log_debug(NAME" %p: param %u %d/%d", this, id, start, num);
+
+ while (true) {
+ index = next++;
+ if (index >= this->n_params)
+ break;
+
+ param = this->params[index];
+
+ if (param == NULL || !spa_pod_is_object_id(param, id))
+ continue;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ if (spa_pod_filter(&b, &result, param, filter) != 0)
+ continue;
+
+ pw_log_debug(NAME" %p: %d param %u", this, seq, index);
+
+ pw_endpoint_resource_param(resource, seq, id, index, next, result);
+
+ if (++count == num)
+ break;
+ }
+ return 0;
+}
+
+static int endpoint_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_resource *resource = object;
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ uint32_t i;
+
+ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids));
+ data->n_subscribe_ids = n_ids;
+
+ for (i = 0; i < n_ids; i++) {
+ data->subscribe_ids[i] = ids[i];
+ pw_log_debug(NAME" %p: resource %d subscribe param %u",
+ data->endpoint, pw_resource_get_id(resource), ids[i]);
+ endpoint_enum_params(resource, 1, ids[i], 0, UINT32_MAX, NULL);
+ }
+ return 0;
+}
+
+static int endpoint_set_param (void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = object;
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ struct endpoint *this = data->endpoint;
+
+ pw_log_debug("%p", this);
+ pw_client_endpoint_resource_set_param(this->client_ep->resource,
+ id, flags, param);
+
+ return 0;
+}
+
+static int endpoint_create_link(void *object, const struct spa_dict *props)
+{
+ struct pw_resource *resource = object;
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ struct endpoint *this = data->endpoint;
+
+ pw_log_debug("%p", this);
+ pw_client_endpoint_resource_create_link(this->client_ep->resource,
+ props);
+
+ return 0;
+}
+
+static const struct pw_endpoint_methods methods = {
+ PW_VERSION_ENDPOINT_METHODS,
+ .subscribe_params = endpoint_subscribe_params,
+ .enum_params = endpoint_enum_params,
+ .set_param = endpoint_set_param,
+ .create_link = endpoint_create_link,
+};
+
+struct emit_param_data {
+ struct endpoint *this;
+ struct spa_pod *param;
+ uint32_t id;
+ uint32_t index;
+ uint32_t next;
+};
+
+static int emit_param(void *_data, struct pw_resource *resource)
+{
+ struct emit_param_data *d = _data;
+ struct resource_data *data;
+ uint32_t i;
+
+ data = pw_resource_get_user_data(resource);
+ for (i = 0; i < data->n_subscribe_ids; i++) {
+ if (data->subscribe_ids[i] == d->id) {
+ pw_endpoint_resource_param(resource, 1,
+ d->id, d->index, d->next, d->param);
+ }
+ }
+ return 0;
+}
+
+static void endpoint_notify_subscribed(struct endpoint *this,
+ uint32_t index, uint32_t next)
+{
+ struct pw_global *global = this->global;
+ struct emit_param_data data;
+ struct spa_pod *param = this->params[index];
+
+ if (!param || !spa_pod_is_object (param))
+ return;
+
+ data.this = this;
+ data.param = param;
+ data.id = SPA_POD_OBJECT_ID (param);
+ data.index = index;
+ data.next = next;
+
+ pw_global_for_each_resource(global, emit_param, &data);
+}
+
+static int emit_info(void *data, struct pw_resource *resource)
+{
+ struct endpoint *this = data;
+ pw_endpoint_resource_info(resource, &this->info);
+ return 0;
+}
+
+int endpoint_update(struct endpoint *this,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_info *info)
+{
+ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_PARAMS) {
+ uint32_t i;
+
+ pw_log_debug(NAME" %p: update %d params", this, n_params);
+
+ for (i = 0; i < this->n_params; i++)
+ free(this->params[i]);
+ this->n_params = n_params;
+ if (this->n_params == 0) {
+ free(this->params);
+ this->params = NULL;
+ } else {
+ void *p;
+ p = pw_reallocarray(this->params, n_params, sizeof(struct spa_pod*));
+ if (p == NULL) {
+ free(this->params);
+ this->params = NULL;
+ this->n_params = 0;
+ goto no_mem;
+ }
+ this->params = p;
+ }
+ for (i = 0; i < this->n_params; i++) {
+ this->params[i] = params[i] ? spa_pod_copy(params[i]) : NULL;
+ endpoint_notify_subscribed(this, i, i+1);
+ }
+ }
+
+ if (change_mask & PW_CLIENT_ENDPOINT_UPDATE_INFO) {
+ if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_STREAMS)
+ this->info.n_streams = info->n_streams;
+
+ if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_SESSION)
+ this->info.session_id = info->session_id;
+
+ if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS)
+ pw_properties_update(this->props, info->props);
+
+ if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) {
+ this->info.n_params = info->n_params;
+ if (info->n_params == 0) {
+ free(this->info.params);
+ this->info.params = NULL;
+ } else {
+ void *p;
+ p = pw_reallocarray(this->info.params, info->n_params, sizeof(struct spa_param_info));
+ if (p == NULL) {
+ free(this->info.params);
+ this->info.params = NULL;
+ this->info.n_params = 0;
+ goto no_mem;
+ }
+ this->info.params = p;
+ memcpy(this->info.params, info->params, info->n_params * sizeof(struct spa_param_info));
+ }
+ }
+
+ if (!this->info.name) {
+ this->info.name = info->name ? strdup(info->name) : NULL;
+ this->info.media_class = info->media_class ? strdup(info->media_class) : NULL;
+ this->info.direction = info->direction;
+ this->info.flags = info->flags;
+ }
+
+ this->info.change_mask = info->change_mask;
+ pw_global_for_each_resource(this->global, emit_info, this);
+ this->info.change_mask = 0;
+ }
+
+ return 0;
+
+ no_mem:
+ pw_log_error(NAME" can't update: no memory");
+ pw_resource_error(this->client_ep->resource, -ENOMEM,
+ NAME" can't update: no memory");
+ return -ENOMEM;
+}
+
+static int endpoint_bind(void *_data, struct pw_impl_client *client,
+ uint32_t permissions, uint32_t version, uint32_t id)
+{
+ struct endpoint *this = _data;
+ struct pw_global *global = this->global;
+ struct pw_resource *resource;
+ struct resource_data *data;
+
+ resource = pw_resource_new(client, id, permissions,
+ pw_global_get_type(global), version, sizeof(*data));
+ if (resource == NULL)
+ goto no_mem;
+
+ data = pw_resource_get_user_data(resource);
+ data->endpoint = this;
+ pw_resource_add_object_listener(resource, &data->object_listener,
+ &methods, resource);
+
+ pw_log_debug(NAME" %p: bound to %d", this, pw_resource_get_id(resource));
+ pw_global_add_resource(global, resource);
+
+ this->info.change_mask = PW_ENDPOINT_CHANGE_MASK_ALL;
+ pw_endpoint_resource_info(resource, &this->info);
+ this->info.change_mask = 0;
+
+ return 0;
+
+ no_mem:
+ pw_log_error(NAME" can't create resource: no memory");
+ pw_resource_error(this->client_ep->resource, -ENOMEM,
+ NAME" can't create resource: no memory");
+ return -ENOMEM;
+}
+
+int endpoint_init(struct endpoint *this,
+ struct client_endpoint *client_ep,
+ struct pw_context *context,
+ struct pw_properties *properties)
+{
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ PW_KEY_FACTORY_ID,
+ PW_KEY_CLIENT_ID,
+ PW_KEY_DEVICE_ID,
+ PW_KEY_NODE_ID,
+ PW_KEY_MEDIA_CLASS,
+ PW_KEY_SESSION_ID,
+ PW_KEY_PRIORITY_SESSION,
+ PW_KEY_ENDPOINT_NAME,
+ PW_KEY_ENDPOINT_CLIENT_ID,
+ PW_KEY_ENDPOINT_ICON_NAME,
+ PW_KEY_ENDPOINT_MONITOR,
+ NULL
+ };
+
+ pw_log_debug(NAME" %p: new", this);
+
+ this->client_ep = client_ep;
+ this->props = properties;
+
+ this->global = pw_global_new (context,
+ PW_TYPE_INTERFACE_Endpoint,
+ PW_VERSION_ENDPOINT,
+ NULL, endpoint_bind, this);
+ if (!this->global)
+ goto no_mem;
+
+ pw_properties_setf(this->props, PW_KEY_OBJECT_ID, "%u",
+ pw_global_get_id(this->global));
+ pw_properties_setf(this->props, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
+ pw_global_get_serial(this->global));
+
+ this->info.version = PW_VERSION_ENDPOINT_INFO;
+ this->info.id = pw_global_get_id(this->global);
+ this->info.props = &this->props->dict;
+
+ pw_global_update_keys(this->global, &this->props->dict, keys);
+
+ pw_resource_set_bound_id(client_ep->resource, this->info.id);
+
+ return pw_global_register(this->global);
+
+ no_mem:
+ pw_log_error(NAME" - can't create - out of memory");
+ return -ENOMEM;
+}
+
+void endpoint_clear(struct endpoint *this)
+{
+ uint32_t i;
+
+ pw_log_debug(NAME" %p: destroy", this);
+
+ pw_global_destroy(this->global);
+
+ for (i = 0; i < this->n_params; i++)
+ free(this->params[i]);
+ free(this->params);
+
+ free(this->info.name);
+ free(this->info.media_class);
+ free(this->info.params);
+
+ pw_properties_free(this->props);
+}
diff --git a/src/modules/module-session-manager/client-endpoint/endpoint.h b/src/modules/module-session-manager/client-endpoint/endpoint.h
new file mode 100644
index 0000000..5ceff39
--- /dev/null
+++ b/src/modules/module-session-manager/client-endpoint/endpoint.h
@@ -0,0 +1,61 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef MODULE_SESSION_MANAGER_ENDPOINT_H
+#define MODULE_SESSION_MANAGER_ENDPOINT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct client_endpoint;
+
+struct endpoint {
+ struct client_endpoint *client_ep;
+ struct pw_global *global;
+ uint32_t n_params;
+ struct spa_pod **params;
+ struct pw_endpoint_info info;
+ struct pw_properties *props; /* wrapper of info.props */
+};
+
+int endpoint_init(struct endpoint *this,
+ struct client_endpoint *client_ep,
+ struct pw_context *context,
+ struct pw_properties *properties);
+
+void endpoint_clear(struct endpoint *this);
+
+int endpoint_update(struct endpoint *this,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_info *info);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* MODULE_SESSION_MANAGER_ENDPOINT_H */
diff --git a/src/modules/module-session-manager/client-session/client-session.c b/src/modules/module-session-manager/client-session/client-session.c
new file mode 100644
index 0000000..89997c9
--- /dev/null
+++ b/src/modules/module-session-manager/client-session/client-session.c
@@ -0,0 +1,295 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <spa/utils/result.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/session-manager.h>
+
+#include "client-session.h"
+#include "session.h"
+#include "endpoint-link.h"
+
+#define NAME "client-session"
+
+struct factory_data {
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_impl_factory *factory;
+ struct spa_hook factory_listener;
+};
+
+static struct endpoint_link *find_link(struct client_session *this, uint32_t id)
+{
+ struct endpoint_link *l;
+ spa_list_for_each(l, &this->links, link) {
+ if (l->id == id)
+ return l;
+ }
+ return NULL;
+}
+
+static int client_session_update(void *object,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_session_info *info)
+{
+ struct client_session *this = object;
+ struct session *session = &this->session;
+
+ return session_update(session, change_mask, n_params, params, info);
+}
+
+static int client_session_link_update(void *object,
+ uint32_t link_id,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_link_info *info)
+{
+ struct client_session *this = object;
+ struct session *session = &this->session;
+ struct endpoint_link *link = find_link(this, link_id);
+ struct pw_properties *props = NULL;
+
+ if (!link) {
+ static const char * const keys[] = {
+ PW_KEY_FACTORY_ID,
+ PW_KEY_CLIENT_ID,
+ PW_KEY_SESSION_ID,
+ PW_KEY_ENDPOINT_LINK_OUTPUT_ENDPOINT,
+ PW_KEY_ENDPOINT_LINK_OUTPUT_STREAM,
+ PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT,
+ PW_KEY_ENDPOINT_LINK_INPUT_STREAM,
+ NULL
+ };
+
+ struct pw_context *context = pw_global_get_context(session->global);
+
+ link = calloc(1, sizeof(struct endpoint_link));
+ if (!link)
+ goto no_mem;
+
+ props = pw_properties_new(NULL, NULL);
+ if (!props)
+ goto no_mem;
+ pw_properties_update_keys(props, &session->props->dict, keys);
+ if (info && info->props)
+ pw_properties_update_keys(props, info->props, keys);
+
+ if (endpoint_link_init(link, link_id, session->info.id,
+ this, context, props) < 0)
+ goto no_mem;
+
+ spa_list_append(&this->links, &link->link);
+ }
+ else if (change_mask & PW_CLIENT_SESSION_LINK_UPDATE_DESTROYED) {
+ endpoint_link_clear(link);
+ spa_list_remove(&link->link);
+ free(link);
+ link = NULL;
+ }
+
+ return link ?
+ endpoint_link_update(link, change_mask, n_params, params, info)
+ : 0;
+
+ no_mem:
+ pw_properties_free(props);
+ free(link);
+ pw_log_error(NAME" %p: cannot update link: no memory", this);
+ pw_resource_error(this->resource, -ENOMEM,
+ "cannot update link: no memory");
+ return -ENOMEM;
+}
+
+static const struct pw_client_session_methods methods = {
+ PW_VERSION_CLIENT_SESSION_METHODS,
+ .update = client_session_update,
+ .link_update = client_session_link_update,
+};
+
+static void client_session_destroy(void *data)
+{
+ struct client_session *this = data;
+ struct endpoint_link *l;
+
+ pw_log_debug(NAME" %p: destroy", this);
+
+ spa_list_consume(l, &this->links, link) {
+ endpoint_link_clear(l);
+ spa_list_remove(&l->link);
+ free(l);
+ }
+ session_clear(&this->session);
+ spa_hook_remove(&this->resource_listener);
+
+ free(this);
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = client_session_destroy,
+};
+
+static void *create_object(void *data,
+ struct pw_resource *owner_resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id)
+{
+ struct factory_data *d = data;
+ struct pw_impl_factory *factory = d->factory;
+ struct client_session *this;
+ struct pw_impl_client *owner = pw_resource_get_client(owner_resource);
+ struct pw_context *context = pw_impl_client_get_context(owner);
+
+ this = calloc(1, sizeof(struct client_session));
+ if (this == NULL)
+ goto no_mem;
+
+ spa_list_init(&this->links);
+
+ pw_log_debug(NAME" %p: new", this);
+
+ if (!properties)
+ properties = pw_properties_new(NULL, NULL);
+ if (!properties)
+ goto no_mem;
+
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d",
+ pw_impl_client_get_info(owner)->id);
+ pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d",
+ pw_impl_factory_get_info(factory)->id);
+
+ this->resource = pw_resource_new(owner, new_id, PW_PERM_ALL, type, version, 0);
+ if (this->resource == NULL)
+ goto no_mem;
+
+ if (session_init(&this->session, this, context, properties) < 0)
+ goto no_mem;
+
+ pw_resource_add_listener(this->resource, &this->resource_listener,
+ &resource_events, this);
+ pw_resource_add_object_listener(this->resource, &this->object_listener,
+ &methods, this);
+
+ return this;
+
+ no_mem:
+ pw_properties_free(properties);
+ if (this && this->resource)
+ pw_resource_destroy(this->resource);
+ free(this);
+ pw_log_error("can't create client session: no memory");
+ pw_resource_error(owner_resource, -ENOMEM,
+ "can't create client session: no memory");
+ return NULL;
+}
+
+static const struct pw_impl_factory_implementation impl_factory = {
+ PW_VERSION_IMPL_FACTORY_IMPLEMENTATION,
+ .create_object = create_object,
+};
+
+static void factory_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->factory_listener);
+ d->factory = NULL;
+ if (d->module)
+ pw_impl_module_destroy(d->module);
+}
+
+static const struct pw_impl_factory_events factory_events = {
+ PW_VERSION_IMPL_FACTORY_EVENTS,
+ .destroy = factory_destroy,
+};
+
+static void module_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->module_listener);
+ d->module = NULL;
+ if (d->factory)
+ pw_impl_factory_destroy(d->factory);
+}
+
+static void module_registered(void *data)
+{
+ struct factory_data *d = data;
+ struct pw_impl_module *module = d->module;
+ struct pw_impl_factory *factory = d->factory;
+ struct spa_dict_item items[1];
+ char id[16];
+ int res;
+
+ snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id);
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
+ pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
+
+ if ((res = pw_impl_factory_register(factory, NULL)) < 0) {
+ pw_log_error(NAME" %p: can't register factory: %s", factory, spa_strerror(res));
+ }
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+ .registered = module_registered,
+};
+
+int client_session_factory_init(struct pw_impl_module *module)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_factory *factory;
+ struct factory_data *data;
+
+ factory = pw_context_create_factory(context,
+ "client-session",
+ PW_TYPE_INTERFACE_ClientSession,
+ PW_VERSION_CLIENT_SESSION,
+ NULL,
+ sizeof(*data));
+ if (factory == NULL)
+ return -ENOMEM;
+
+ data = pw_impl_factory_get_user_data(factory);
+ data->factory = factory;
+ data->module = module;
+
+ pw_impl_factory_set_implementation(factory, &impl_factory, data);
+ pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data);
+
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ return 0;
+}
diff --git a/src/modules/module-session-manager/client-session/client-session.h b/src/modules/module-session-manager/client-session/client-session.h
new file mode 100644
index 0000000..fc6124c
--- /dev/null
+++ b/src/modules/module-session-manager/client-session/client-session.h
@@ -0,0 +1,62 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef MODULE_SESSION_MANAGER_CLIENT_SESSION_H
+#define MODULE_SESSION_MANAGER_CLIENT_SESSION_H
+
+#include "session.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct client_session {
+ struct pw_resource *resource;
+ struct spa_hook resource_listener;
+ struct spa_hook object_listener;
+ struct session session;
+ struct spa_list links;
+};
+
+#define pw_client_session_resource(r,m,v,...) \
+ pw_resource_call_res(r,struct pw_client_session_events,m,v,__VA_ARGS__)
+#define pw_client_session_resource_set_id(r,...) \
+ pw_client_session_resource(r,set_id,0,__VA_ARGS__)
+#define pw_client_session_resource_set_param(r,...) \
+ pw_client_session_resource(r,set_param,0,__VA_ARGS__)
+#define pw_client_session_resource_link_set_param(r,...) \
+ pw_client_session_resource(r,link_set_param,0,__VA_ARGS__)
+#define pw_client_session_resource_create_link(r,...) \
+ pw_client_session_resource(r,create_link,0,__VA_ARGS__)
+#define pw_client_session_resource_destroy_link(r,...) \
+ pw_client_session_resource(r,destroy_link,0,__VA_ARGS__)
+#define pw_client_session_resource_link_request_state(r,...) \
+ pw_client_session_resource(r,link_request_state,0,__VA_ARGS__)
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* MODULE_SESSION_MANAGER_CLIENT_SESSION_H */
diff --git a/src/modules/module-session-manager/client-session/endpoint-link.c b/src/modules/module-session-manager/client-session/endpoint-link.c
new file mode 100644
index 0000000..0bdbfc9
--- /dev/null
+++ b/src/modules/module-session-manager/client-session/endpoint-link.c
@@ -0,0 +1,369 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/session-manager.h>
+
+#include <spa/pod/filter.h>
+
+#include "endpoint-link.h"
+#include "client-session.h"
+
+#define NAME "endpoint-link"
+
+struct resource_data {
+ struct endpoint_link *link;
+ struct spa_hook object_listener;
+ uint32_t n_subscribe_ids;
+ uint32_t subscribe_ids[32];
+};
+
+#define pw_endpoint_link_resource(r,m,v,...) \
+ pw_resource_call(r,struct pw_endpoint_link_events,m,v,__VA_ARGS__)
+#define pw_endpoint_link_resource_info(r,...) \
+ pw_endpoint_link_resource(r,info,0,__VA_ARGS__)
+#define pw_endpoint_link_resource_param(r,...) \
+ pw_endpoint_link_resource(r,param,0,__VA_ARGS__)
+
+static int endpoint_link_enum_params (void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct pw_resource *resource = object;
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ struct endpoint_link *this = data->link;
+ struct spa_pod *result;
+ struct spa_pod *param;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = { 0 };
+ uint32_t index;
+ uint32_t next = start;
+ uint32_t count = 0;
+
+ while (true) {
+ index = next++;
+ if (index >= this->n_params)
+ break;
+
+ param = this->params[index];
+
+ if (param == NULL || !spa_pod_is_object_id(param, id))
+ continue;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ if (spa_pod_filter(&b, &result, param, filter) != 0)
+ continue;
+
+ pw_log_debug(NAME" %p: %d param %u", this, seq, index);
+
+ pw_endpoint_link_resource_param(resource, seq, id, index, next, result);
+
+ if (++count == num)
+ break;
+ }
+ return 0;
+}
+
+static int endpoint_link_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_resource *resource = object;
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ uint32_t i;
+
+ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids));
+ data->n_subscribe_ids = n_ids;
+
+ for (i = 0; i < n_ids; i++) {
+ data->subscribe_ids[i] = ids[i];
+ pw_log_debug(NAME" %p: resource %d subscribe param %u",
+ data->link, pw_resource_get_id(resource), ids[i]);
+ endpoint_link_enum_params(resource, 1, ids[i], 0, UINT32_MAX, NULL);
+ }
+ return 0;
+}
+
+static int endpoint_link_set_param (void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = object;
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ struct endpoint_link *this = data->link;
+
+ pw_client_session_resource_set_param(this->client_sess->resource,
+ id, flags, param);
+
+ return 0;
+}
+
+static int endpoint_link_request_state(void *object, enum pw_endpoint_link_state state)
+{
+ struct pw_resource *resource = object;
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ struct endpoint_link *this = data->link;
+
+ pw_client_session_resource_link_request_state(this->client_sess->resource,
+ this->id, state);
+
+ return 0;
+}
+
+static const struct pw_endpoint_link_methods methods = {
+ PW_VERSION_ENDPOINT_LINK_METHODS,
+ .subscribe_params = endpoint_link_subscribe_params,
+ .enum_params = endpoint_link_enum_params,
+ .set_param = endpoint_link_set_param,
+ .request_state = endpoint_link_request_state,
+};
+
+struct emit_param_data {
+ struct endpoint_link *this;
+ struct spa_pod *param;
+ uint32_t id;
+ uint32_t index;
+ uint32_t next;
+};
+
+static int emit_param(void *_data, struct pw_resource *resource)
+{
+ struct emit_param_data *d = _data;
+ struct resource_data *data;
+ uint32_t i;
+
+ data = pw_resource_get_user_data(resource);
+ for (i = 0; i < data->n_subscribe_ids; i++) {
+ if (data->subscribe_ids[i] == d->id) {
+ pw_endpoint_link_resource_param(resource, 1,
+ d->id, d->index, d->next, d->param);
+ }
+ }
+ return 0;
+}
+
+static void endpoint_link_notify_subscribed(struct endpoint_link *this,
+ uint32_t index, uint32_t next)
+{
+ struct pw_global *global = this->global;
+ struct emit_param_data data;
+ struct spa_pod *param = this->params[index];
+
+ if (!param || !spa_pod_is_object (param))
+ return;
+
+ data.this = this;
+ data.param = param;
+ data.id = SPA_POD_OBJECT_ID (param);
+ data.index = index;
+ data.next = next;
+
+ pw_global_for_each_resource(global, emit_param, &data);
+}
+
+static int emit_info(void *data, struct pw_resource *resource)
+{
+ struct endpoint_link *this = data;
+ pw_endpoint_link_resource_info(resource, &this->info);
+ return 0;
+}
+
+int endpoint_link_update(struct endpoint_link *this,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_link_info *info)
+{
+ if (change_mask & PW_CLIENT_SESSION_UPDATE_PARAMS) {
+ uint32_t i;
+
+ pw_log_debug(NAME" %p: update %d params", this, n_params);
+
+ for (i = 0; i < this->n_params; i++)
+ free(this->params[i]);
+ this->n_params = n_params;
+ if (this->n_params == 0) {
+ free(this->params);
+ this->params = NULL;
+ } else {
+ void *p;
+ p = pw_reallocarray(this->params, n_params, sizeof(struct spa_pod*));
+ if (p == NULL) {
+ free(this->params);
+ this->params = NULL;
+ this->n_params = 0;
+ goto no_mem;
+ }
+ this->params = p;
+ }
+ for (i = 0; i < this->n_params; i++) {
+ this->params[i] = params[i] ? spa_pod_copy(params[i]) : NULL;
+ endpoint_link_notify_subscribed(this, i, i+1);
+ }
+ }
+
+ if (change_mask & PW_CLIENT_SESSION_UPDATE_INFO) {
+ if (info->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_STATE) {
+ this->info.state = info->state;
+ free(this->info.error);
+ this->info.error = info->error ? strdup(info->error) : NULL;
+ }
+
+ if (info->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_PROPS)
+ pw_properties_update(this->props, info->props);
+
+ if (info->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_PARAMS) {
+ this->info.n_params = info->n_params;
+ if (info->n_params == 0) {
+ free(this->info.params);
+ this->info.params = NULL;
+ } else {
+ void *p;
+ p = pw_reallocarray(this->info.params, info->n_params, sizeof(struct spa_param_info));
+ if (p == NULL) {
+ free(this->info.params);
+ this->info.params = NULL;
+ this->info.n_params = 0;
+ goto no_mem;
+ }
+ this->info.params = p;
+ memcpy(this->info.params, info->params, info->n_params * sizeof(struct spa_param_info));
+ }
+ }
+
+ if (!this->info.output_endpoint_id) {
+ this->info.output_endpoint_id = info->output_endpoint_id;
+ this->info.output_stream_id = info->output_stream_id;
+ this->info.input_endpoint_id = info->input_endpoint_id;
+ this->info.input_stream_id = info->input_stream_id;
+ }
+
+ this->info.change_mask = info->change_mask;
+ pw_global_for_each_resource(this->global, emit_info, this);
+ this->info.change_mask = 0;
+ }
+
+ return 0;
+
+ no_mem:
+ pw_log_error(NAME" %p: can't update: no memory", this);
+ pw_resource_error(this->client_sess->resource, -ENOMEM,
+ "can't update: no memory");
+ return -ENOMEM;
+}
+
+static int endpoint_link_bind(void *_data, struct pw_impl_client *client,
+ uint32_t permissions, uint32_t version, uint32_t id)
+{
+ struct endpoint_link *this = _data;
+ struct pw_global *global = this->global;
+ struct pw_resource *resource;
+ struct resource_data *data;
+
+ resource = pw_resource_new(client, id, permissions,
+ pw_global_get_type(global), version, sizeof(*data));
+ if (resource == NULL)
+ goto no_mem;
+
+ data = pw_resource_get_user_data(resource);
+ data->link = this;
+ pw_resource_add_object_listener(resource, &data->object_listener,
+ &methods, resource);
+
+ pw_log_debug(NAME" %p: bound to %d", this, pw_resource_get_id(resource));
+ pw_global_add_resource(global, resource);
+
+ this->info.change_mask = PW_ENDPOINT_LINK_CHANGE_MASK_ALL;
+ pw_endpoint_link_resource_info(resource, &this->info);
+ this->info.change_mask = 0;
+
+ return 0;
+
+ no_mem:
+ pw_log_error(NAME" %p: can't create resource: no memory", this);
+ pw_resource_error(this->client_sess->resource, -ENOMEM,
+ "can't create resource: no memory");
+ return -ENOMEM;
+}
+
+int endpoint_link_init(struct endpoint_link *this,
+ uint32_t id, uint32_t session_id,
+ struct client_session *client_sess,
+ struct pw_context *context,
+ struct pw_properties *properties)
+{
+ pw_log_debug(NAME" %p: new", this);
+
+ this->client_sess = client_sess;
+ this->id = id;
+ this->props = properties;
+
+ pw_properties_setf(properties, PW_KEY_SESSION_ID, "%u", session_id);
+
+ properties = pw_properties_copy(properties);
+ if (!properties)
+ goto no_mem;
+
+ this->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_EndpointLink,
+ PW_VERSION_ENDPOINT_LINK,
+ properties, endpoint_link_bind, this);
+ if (!this->global)
+ goto no_mem;
+
+ pw_properties_setf(this->props, PW_KEY_OBJECT_ID, "%u",
+ pw_global_get_id(this->global));
+ pw_properties_setf(this->props, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
+ pw_global_get_serial(this->global));
+
+ this->info.version = PW_VERSION_ENDPOINT_LINK_INFO;
+ this->info.id = pw_global_get_id(this->global);
+ this->info.session_id = session_id;
+ this->info.props = &this->props->dict;
+
+ return pw_global_register(this->global);
+
+ no_mem:
+ pw_log_error(NAME" - can't create - out of memory");
+ return -ENOMEM;
+}
+
+void endpoint_link_clear(struct endpoint_link *this)
+{
+ uint32_t i;
+
+ pw_log_debug(NAME" %p: destroy", this);
+
+ pw_global_destroy(this->global);
+
+ for (i = 0; i < this->n_params; i++)
+ free(this->params[i]);
+ free(this->params);
+
+ free(this->info.error);
+ free(this->info.params);
+
+ pw_properties_free(this->props);
+}
diff --git a/src/modules/module-session-manager/client-session/endpoint-link.h b/src/modules/module-session-manager/client-session/endpoint-link.h
new file mode 100644
index 0000000..c75f975
--- /dev/null
+++ b/src/modules/module-session-manager/client-session/endpoint-link.h
@@ -0,0 +1,65 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef MODULE_SESSION_MANAGER_ENDPOINT_LINK_H
+#define MODULE_SESSION_MANAGER_ENDPOINT_LINK_H
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct client_session;
+
+struct endpoint_link {
+ struct spa_list link;
+ struct client_session *client_sess;
+ struct pw_global *global;
+ uint32_t id; /* session-local link id */
+ uint32_t n_params;
+ struct spa_pod **params;
+ struct pw_endpoint_link_info info;
+ struct pw_properties *props; /* wrapper of info.props */
+};
+
+int endpoint_link_init(struct endpoint_link *this,
+ uint32_t id, uint32_t session_id,
+ struct client_session *client_sess,
+ struct pw_context *context,
+ struct pw_properties *properties);
+
+void endpoint_link_clear(struct endpoint_link *this);
+
+int endpoint_link_update(struct endpoint_link *this,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_link_info *info);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* MODULE_SESSION_MANAGER_ENDPOINT_LINK_H */
diff --git a/src/modules/module-session-manager/client-session/session.c b/src/modules/module-session-manager/client-session/session.c
new file mode 100644
index 0000000..87c1b96
--- /dev/null
+++ b/src/modules/module-session-manager/client-session/session.c
@@ -0,0 +1,344 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/session-manager.h>
+
+#include <spa/pod/filter.h>
+
+#include "session.h"
+#include "client-session.h"
+
+#define NAME "session"
+
+struct resource_data {
+ struct session *session;
+ struct spa_hook object_listener;
+ uint32_t n_subscribe_ids;
+ uint32_t subscribe_ids[32];
+};
+
+#define pw_session_resource(r,m,v,...) \
+ pw_resource_call(r,struct pw_session_events,m,v,__VA_ARGS__)
+#define pw_session_resource_info(r,...) \
+ pw_session_resource(r,info,0,__VA_ARGS__)
+#define pw_session_resource_param(r,...) \
+ pw_session_resource(r,param,0,__VA_ARGS__)
+
+static int session_enum_params (void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct pw_resource *resource = object;
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ struct session *this = data->session;
+ struct spa_pod *result;
+ struct spa_pod *param;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = { 0 };
+ uint32_t index;
+ uint32_t next = start;
+ uint32_t count = 0;
+
+ while (true) {
+ index = next++;
+ if (index >= this->n_params)
+ break;
+
+ param = this->params[index];
+
+ if (param == NULL || !spa_pod_is_object_id(param, id))
+ continue;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ if (spa_pod_filter(&b, &result, param, filter) != 0)
+ continue;
+
+ pw_log_debug(NAME" %p: %d param %u", this, seq, index);
+
+ pw_session_resource_param(resource, seq, id, index, next, result);
+
+ if (++count == num)
+ break;
+ }
+ return 0;
+}
+
+static int session_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_resource *resource = object;
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ uint32_t i;
+
+ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids));
+ data->n_subscribe_ids = n_ids;
+
+ for (i = 0; i < n_ids; i++) {
+ data->subscribe_ids[i] = ids[i];
+ pw_log_debug(NAME" %p: resource %d subscribe param %u",
+ data->session, pw_resource_get_id(resource), ids[i]);
+ session_enum_params(resource, 1, ids[i], 0, UINT32_MAX, NULL);
+ }
+ return 0;
+}
+
+static int session_set_param (void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = object;
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ struct session *this = data->session;
+
+ pw_client_session_resource_set_param(this->client_sess->resource,
+ id, flags, param);
+
+ return 0;
+}
+
+static const struct pw_session_methods methods = {
+ PW_VERSION_SESSION_METHODS,
+ .subscribe_params = session_subscribe_params,
+ .enum_params = session_enum_params,
+ .set_param = session_set_param,
+};
+
+struct emit_param_data {
+ struct session *this;
+ struct spa_pod *param;
+ uint32_t id;
+ uint32_t index;
+ uint32_t next;
+};
+
+static int emit_param(void *_data, struct pw_resource *resource)
+{
+ struct emit_param_data *d = _data;
+ struct resource_data *data;
+ uint32_t i;
+
+ data = pw_resource_get_user_data(resource);
+ for (i = 0; i < data->n_subscribe_ids; i++) {
+ if (data->subscribe_ids[i] == d->id) {
+ pw_session_resource_param(resource, 1,
+ d->id, d->index, d->next, d->param);
+ }
+ }
+ return 0;
+}
+
+static void session_notify_subscribed(struct session *this,
+ uint32_t index, uint32_t next)
+{
+ struct pw_global *global = this->global;
+ struct emit_param_data data;
+ struct spa_pod *param = this->params[index];
+
+ if (!param || !spa_pod_is_object (param))
+ return;
+
+ data.this = this;
+ data.param = param;
+ data.id = SPA_POD_OBJECT_ID (param);
+ data.index = index;
+ data.next = next;
+
+ pw_global_for_each_resource(global, emit_param, &data);
+}
+
+static int emit_info(void *data, struct pw_resource *resource)
+{
+ struct session *this = data;
+ pw_session_resource_info(resource, &this->info);
+ return 0;
+}
+
+int session_update(struct session *this,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_session_info *info)
+{
+ if (change_mask & PW_CLIENT_SESSION_UPDATE_PARAMS) {
+ uint32_t i;
+
+ pw_log_debug(NAME" %p: update %d params", this, n_params);
+
+ for (i = 0; i < this->n_params; i++)
+ free(this->params[i]);
+ this->n_params = n_params;
+ if (this->n_params == 0) {
+ free(this->params);
+ this->params = NULL;
+ } else {
+ void *p;
+ p = pw_reallocarray(this->params, n_params, sizeof(struct spa_pod*));
+ if (p == NULL) {
+ free(this->params);
+ this->params = NULL;
+ this->n_params = 0;
+ goto no_mem;
+ }
+ this->params = p;
+ }
+ for (i = 0; i < this->n_params; i++) {
+ this->params[i] = params[i] ? spa_pod_copy(params[i]) : NULL;
+ session_notify_subscribed(this, i, i+1);
+ }
+ }
+
+ if (change_mask & PW_CLIENT_SESSION_UPDATE_INFO) {
+ if (info->change_mask & PW_SESSION_CHANGE_MASK_PROPS)
+ pw_properties_update(this->props, info->props);
+
+ if (info->change_mask & PW_SESSION_CHANGE_MASK_PARAMS) {
+ this->info.n_params = info->n_params;
+ if (info->n_params == 0) {
+ free(this->info.params);
+ this->info.params = NULL;
+ } else {
+ void *p;
+ p = pw_reallocarray(this->info.params, info->n_params, sizeof(struct spa_param_info));
+ if (p == NULL) {
+ free(this->info.params);
+ this->info.params = NULL;
+ this->info.n_params = 0;
+ goto no_mem;
+ }
+ this->info.params = p;
+ memcpy(this->info.params, info->params, info->n_params * sizeof(struct spa_param_info));
+ }
+ }
+ this->info.change_mask = info->change_mask;
+ pw_global_for_each_resource(this->global, emit_info, this);
+ this->info.change_mask = 0;
+ }
+
+ return 0;
+
+ no_mem:
+ pw_log_error(NAME" can't update: no memory");
+ pw_resource_error(this->client_sess->resource, -ENOMEM,
+ NAME" can't update: no memory");
+ return -ENOMEM;
+}
+
+static int session_bind(void *_data, struct pw_impl_client *client,
+ uint32_t permissions, uint32_t version, uint32_t id)
+{
+ struct session *this = _data;
+ struct pw_global *global = this->global;
+ struct pw_resource *resource;
+ struct resource_data *data;
+
+ resource = pw_resource_new(client, id, permissions,
+ pw_global_get_type(global), version, sizeof(*data));
+ if (resource == NULL)
+ goto no_mem;
+
+ data = pw_resource_get_user_data(resource);
+ data->session = this;
+
+ pw_resource_add_object_listener(resource, &data->object_listener,
+ &methods, resource);
+
+ pw_log_debug(NAME" %p: bound to %d", this, pw_resource_get_id(resource));
+ pw_global_add_resource(global, resource);
+
+ this->info.change_mask = PW_SESSION_CHANGE_MASK_ALL;
+ pw_session_resource_info(resource, &this->info);
+ this->info.change_mask = 0;
+
+ return 0;
+
+ no_mem:
+ pw_log_error(NAME" can't create resource: no memory");
+ pw_resource_error(this->client_sess->resource, -ENOMEM,
+ NAME" can't create resource: no memory");
+ return -ENOMEM;
+}
+
+int session_init(struct session *this,
+ struct client_session *client_sess,
+ struct pw_context *context,
+ struct pw_properties *properties)
+{
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ PW_KEY_FACTORY_ID,
+ PW_KEY_CLIENT_ID,
+ NULL
+ };
+
+ pw_log_debug(NAME" %p: new", this);
+
+ this->client_sess = client_sess;
+ this->props = properties;
+
+ this->global = pw_global_new (context,
+ PW_TYPE_INTERFACE_Session,
+ PW_VERSION_SESSION,
+ NULL, session_bind, this);
+ if (!this->global)
+ goto no_mem;
+
+ pw_properties_setf(this->props, PW_KEY_OBJECT_ID, "%u",
+ pw_global_get_id(this->global));
+ pw_properties_setf(this->props, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
+ pw_global_get_serial(this->global));
+
+ this->info.version = PW_VERSION_SESSION_INFO;
+ this->info.id = pw_global_get_id(this->global);
+ this->info.props = &this->props->dict;
+
+ pw_global_update_keys(this->global, &this->props->dict, keys);
+
+ pw_resource_set_bound_id(client_sess->resource, this->info.id);
+
+ return pw_global_register(this->global);
+
+ no_mem:
+ pw_log_error(NAME" - can't create - out of memory");
+ return -ENOMEM;
+}
+
+void session_clear(struct session *this)
+{
+ uint32_t i;
+
+ pw_log_debug(NAME" %p: destroy", this);
+
+ pw_global_destroy(this->global);
+
+ for (i = 0; i < this->n_params; i++)
+ free(this->params[i]);
+ free(this->params);
+
+ free(this->info.params);
+
+ pw_properties_free(this->props);
+}
diff --git a/src/modules/module-session-manager/client-session/session.h b/src/modules/module-session-manager/client-session/session.h
new file mode 100644
index 0000000..a94b18f
--- /dev/null
+++ b/src/modules/module-session-manager/client-session/session.h
@@ -0,0 +1,62 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef MODULE_SESSION_MANAGER_SESSION_H
+#define MODULE_SESSION_MANAGER_SESSION_H
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct client_session;
+
+struct session {
+ struct client_session *client_sess;
+ struct pw_global *global;
+ uint32_t n_params;
+ struct spa_pod **params;
+ struct pw_session_info info;
+ struct pw_properties *props; /* wrapper of info.props */
+};
+
+int session_init(struct session *this,
+ struct client_session *client_sess,
+ struct pw_context *context,
+ struct pw_properties *properties);
+
+void session_clear(struct session *this);
+
+int session_update(struct session *this,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_session_info *info);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* MODULE_SESSION_MANAGER_SESSION_H */
diff --git a/src/modules/module-session-manager/endpoint-link.c b/src/modules/module-session-manager/endpoint-link.c
new file mode 100644
index 0000000..55ff580
--- /dev/null
+++ b/src/modules/module-session-manager/endpoint-link.c
@@ -0,0 +1,590 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/session-manager.h>
+#include <pipewire/extensions/session-manager/introspect-funcs.h>
+
+#include <spa/utils/result.h>
+#include <spa/pod/builder.h>
+#include <spa/pod/filter.h>
+
+#define MAX_PARAMS 32
+
+#define NAME "endpoint-link"
+
+struct pw_proxy *pw_core_endpoint_link_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size);
+
+struct impl
+{
+ struct pw_global *global;
+ struct spa_hook global_listener;
+
+ union {
+ struct pw_endpoint_link *link;
+ struct pw_resource *resource;
+ };
+ struct spa_hook resource_listener;
+ struct spa_hook link_listener;
+
+ struct pw_endpoint_link_info *cached_info;
+ struct spa_list cached_params;
+
+ int ping_seq;
+ bool registered;
+};
+
+struct param_data
+{
+ struct spa_list link;
+ uint32_t id;
+ struct pw_array params;
+};
+
+struct resource_data
+{
+ struct impl *impl;
+
+ struct pw_resource *resource;
+ struct spa_hook object_listener;
+
+ uint32_t n_subscribe_ids;
+ uint32_t subscribe_ids[32];
+};
+
+struct factory_data
+{
+ struct pw_impl_factory *factory;
+ struct spa_hook factory_listener;
+
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_export_type export;
+};
+
+#define pw_endpoint_link_resource(r,m,v,...) \
+ pw_resource_call(r,struct pw_endpoint_link_events,m,v,__VA_ARGS__)
+
+#define pw_endpoint_link_resource_info(r,...) \
+ pw_endpoint_link_resource(r,info,0,__VA_ARGS__)
+#define pw_endpoint_link_resource_param(r,...) \
+ pw_endpoint_link_resource(r,param,0,__VA_ARGS__)
+
+static int method_enum_params(void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ struct param_data *pdata;
+ struct spa_pod *result;
+ struct spa_pod *param;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = { 0 };
+ uint32_t index;
+ uint32_t next = start;
+ uint32_t count = 0;
+
+ pw_log_debug(NAME" %p: param %u %d/%d", impl, id, start, num);
+
+ spa_list_for_each(pdata, &impl->cached_params, link) {
+ if (pdata->id != id)
+ continue;
+
+ while (true) {
+ index = next++;
+ if (index >= pw_array_get_len(&pdata->params, void*))
+ return 0;
+
+ param = *pw_array_get_unchecked(&pdata->params, index, struct spa_pod*);
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ if (spa_pod_filter(&b, &result, param, filter) != 0)
+ continue;
+
+ pw_log_debug(NAME" %p: %d param %u", impl, seq, index);
+
+ pw_endpoint_link_resource_param(d->resource, seq, id, index, next, result);
+
+ if (++count == num)
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static int method_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ uint32_t i;
+
+ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(d->subscribe_ids));
+ d->n_subscribe_ids = n_ids;
+
+ for (i = 0; i < n_ids; i++) {
+ d->subscribe_ids[i] = ids[i];
+ pw_log_debug(NAME" %p: resource %d subscribe param %u",
+ impl, pw_resource_get_id(d->resource), ids[i]);
+ method_enum_params(object, 1, ids[i], 0, UINT32_MAX, NULL);
+ }
+ return 0;
+}
+
+static int method_set_param(void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ /* store only on the implementation; our cache will be updated
+ by the param event, since we are subscribed */
+ pw_endpoint_link_set_param(impl->link, id, flags, param);
+ return 0;
+}
+
+static int method_request_state(void *object, enum pw_endpoint_link_state state)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ pw_endpoint_link_request_state(impl->link, state);
+ return 0;
+}
+
+static const struct pw_endpoint_link_methods link_methods = {
+ PW_VERSION_ENDPOINT_LINK_METHODS,
+ .subscribe_params = method_subscribe_params,
+ .enum_params = method_enum_params,
+ .set_param = method_set_param,
+ .request_state = method_request_state,
+};
+
+static int global_bind(void *object, struct pw_impl_client *client,
+ uint32_t permissions, uint32_t version, uint32_t id)
+{
+ struct impl *impl = object;
+ struct pw_resource *resource;
+ struct resource_data *data;
+
+ resource = pw_resource_new(client, id, permissions,
+ PW_TYPE_INTERFACE_EndpointLink,
+ version, sizeof(*data));
+ if (resource == NULL)
+ return -errno;
+
+ data = pw_resource_get_user_data(resource);
+ data->impl = impl;
+ data->resource = resource;
+
+ pw_global_add_resource(impl->global, resource);
+
+ /* resource methods -> implementation */
+ pw_resource_add_object_listener(resource,
+ &data->object_listener,
+ &link_methods, data);
+
+ impl->cached_info->change_mask = PW_ENDPOINT_LINK_CHANGE_MASK_ALL;
+ pw_endpoint_link_resource_info(resource, impl->cached_info);
+ impl->cached_info->change_mask = 0;
+
+ return 0;
+}
+
+static void global_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->global_listener);
+ impl->global = NULL;
+ if (impl->resource)
+ pw_resource_destroy(impl->resource);
+ free(impl);
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+static void impl_resource_destroy(void *data)
+{
+ struct impl *impl = data;
+ struct param_data *pdata, *tmp;
+
+ spa_hook_remove(&impl->resource_listener);
+ spa_hook_remove(&impl->link_listener);
+ impl->resource = NULL;
+
+ /* clear cache */
+ if (impl->cached_info)
+ pw_endpoint_link_info_free(impl->cached_info);
+ spa_list_for_each_safe(pdata, tmp, &impl->cached_params, link) {
+ struct spa_pod **pod;
+ pw_array_for_each(pod, &pdata->params)
+ free(*pod);
+ pw_array_clear(&pdata->params);
+ spa_list_remove(&pdata->link);
+ free(pdata);
+ }
+
+ if (impl->global)
+ pw_global_destroy(impl->global);
+}
+
+static void register_global(struct impl *impl)
+{
+ impl->cached_info->id = pw_global_get_id (impl->global);
+ pw_resource_set_bound_id(impl->resource, impl->cached_info->id);
+ pw_global_register(impl->global);
+ impl->registered = true;
+}
+
+static void impl_resource_pong (void *data, int seq)
+{
+ struct impl *impl = data;
+
+ /* complete registration, if this was the initial sync */
+ if (!impl->registered && seq == impl->ping_seq) {
+ register_global(impl);
+ }
+}
+
+static const struct pw_resource_events impl_resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = impl_resource_destroy,
+ .pong = impl_resource_pong,
+};
+
+static int emit_info(void *data, struct pw_resource *resource)
+{
+ const struct pw_endpoint_link_info *info = data;
+ pw_endpoint_link_resource_info(resource, info);
+ return 0;
+}
+
+static void event_info(void *data, const struct pw_endpoint_link_info *info)
+{
+ struct impl *impl = data;
+ uint32_t changed_ids[MAX_PARAMS], n_changed_ids = 0;
+ uint32_t i;
+
+ /* figure out changes to params */
+ if (info->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ if ((!impl->cached_info ||
+ info->params[i].flags != impl->cached_info->params[i].flags)
+ && info->params[i].flags & SPA_PARAM_INFO_READ)
+ changed_ids[n_changed_ids++] = info->params[i].id;
+ }
+ }
+
+ /* cache for new clients */
+ impl->cached_info = pw_endpoint_link_info_update (impl->cached_info, info);
+
+ /* notify existing clients */
+ pw_global_for_each_resource(impl->global, emit_info, (void*) info);
+
+ /* cache params & register */
+ if (n_changed_ids > 0) {
+ /* prepare params storage */
+ for (i = 0; i < n_changed_ids; i++) {
+ struct param_data *pdata = calloc(1, sizeof(struct param_data));
+ pdata->id = changed_ids[i];
+ pw_array_init(&pdata->params, sizeof(void*));
+ spa_list_append(&impl->cached_params, &pdata->link);
+ }
+
+ /* subscribe to impl */
+ pw_endpoint_link_subscribe_params(impl->link, changed_ids, n_changed_ids);
+
+ /* register asynchronously on the pong event */
+ impl->ping_seq = pw_resource_ping(impl->resource, 0);
+ }
+ else if (!impl->registered) {
+ register_global(impl);
+ }
+}
+
+struct param_event_args
+{
+ uint32_t id, index, next;
+ const struct spa_pod *param;
+};
+
+static int emit_param(void *_data, struct pw_resource *resource)
+{
+ struct param_event_args *args = _data;
+ struct resource_data *data;
+ uint32_t i;
+
+ data = pw_resource_get_user_data(resource);
+ for (i = 0; i < data->n_subscribe_ids; i++) {
+ if (data->subscribe_ids[i] == args->id) {
+ pw_endpoint_link_resource_param(resource, 1,
+ args->id, args->index, args->next, args->param);
+ }
+ }
+ return 0;
+}
+
+static void event_param(void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct impl *impl = data;
+ struct param_data *pdata;
+ struct spa_pod **pod;
+ struct param_event_args args = { id, index, next, param };
+
+ /* cache for new requests */
+ spa_list_for_each(pdata, &impl->cached_params, link) {
+ if (pdata->id != id)
+ continue;
+
+ if (!pw_array_check_index(&pdata->params, index, void*)) {
+ while (pw_array_get_len(&pdata->params, void*) <= index)
+ pw_array_add_ptr(&pdata->params, NULL);
+ }
+
+ pod = pw_array_get_unchecked(&pdata->params, index, struct spa_pod*);
+ free(*pod);
+ *pod = spa_pod_copy(param);
+ }
+
+ /* notify existing clients */
+ pw_global_for_each_resource(impl->global, emit_param, &args);
+}
+
+static const struct pw_endpoint_link_events link_events = {
+ PW_VERSION_ENDPOINT_LINK_EVENTS,
+ .info = event_info,
+ .param = event_param,
+};
+
+static void *link_new(struct pw_context *context,
+ struct pw_resource *resource,
+ struct pw_properties *properties)
+{
+ struct impl *impl;
+ char serial_str[32];
+ struct spa_dict_item items[1] = {
+ SPA_DICT_ITEM_INIT(PW_KEY_OBJECT_SERIAL, serial_str),
+ };
+ struct spa_dict extra_props = SPA_DICT_INIT_ARRAY(items);
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ NULL
+ };
+
+ impl = calloc(1, sizeof(*impl));
+ if (impl == NULL) {
+ pw_properties_free(properties);
+ return NULL;
+ }
+
+ impl->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_EndpointLink,
+ PW_VERSION_ENDPOINT_LINK,
+ properties,
+ global_bind, impl);
+ if (impl->global == NULL) {
+ free(impl);
+ return NULL;
+ }
+ impl->resource = resource;
+
+ spa_scnprintf(serial_str, sizeof(serial_str), "%"PRIu64,
+ pw_global_get_serial(impl->global));
+ pw_global_update_keys(impl->global, &extra_props, keys);
+
+ spa_list_init(&impl->cached_params);
+
+ /* handle destroy events */
+ pw_global_add_listener(impl->global,
+ &impl->global_listener,
+ &global_events, impl);
+ pw_resource_add_listener(impl->resource,
+ &impl->resource_listener,
+ &impl_resource_events, impl);
+
+ /* handle implementation events -> cache + client resources */
+ pw_endpoint_link_add_listener(impl->link,
+ &impl->link_listener,
+ &link_events, impl);
+
+ /* global is not registered here on purpose;
+ we first cache info + params and then expose the global */
+
+ return impl;
+}
+
+static void *create_object(void *data,
+ struct pw_resource *resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id)
+{
+ struct factory_data *d = data;
+ struct pw_resource *impl_resource;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ void *result;
+ int res;
+
+ impl_resource = pw_resource_new(client, new_id, PW_PERM_ALL, type, version, 0);
+ if (impl_resource == NULL) {
+ res = -errno;
+ goto error_resource;
+ }
+
+ pw_resource_install_marshal(impl_resource, true);
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL) {
+ res = -ENOMEM;
+ goto error_link;
+ }
+
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d",
+ pw_impl_client_get_info(client)->id);
+ pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d",
+ pw_impl_factory_get_info(d->factory)->id);
+
+ result = link_new(pw_impl_client_get_context(client), impl_resource, properties);
+ if (result == NULL) {
+ res = -errno;
+ goto error_link;
+ }
+ return result;
+
+error_resource:
+ pw_log_error("can't create resource: %s", spa_strerror(res));
+ pw_resource_errorf_id(resource, new_id, res, "can't create resource: %s", spa_strerror(res));
+ goto error_exit;
+error_link:
+ pw_log_error("can't create endpoint link: %s", spa_strerror(res));
+ pw_resource_errorf_id(resource, new_id, res, "can't create endpoint link: %s", spa_strerror(res));
+ goto error_exit_free;
+
+error_exit_free:
+ pw_resource_remove(impl_resource);
+error_exit:
+ errno = -res;
+ return NULL;
+}
+
+static const struct pw_impl_factory_implementation impl_factory = {
+ PW_VERSION_IMPL_FACTORY_IMPLEMENTATION,
+ .create_object = create_object,
+};
+
+static void factory_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->factory_listener);
+ d->factory = NULL;
+ if (d->module)
+ pw_impl_module_destroy(d->module);
+}
+
+static const struct pw_impl_factory_events factory_events = {
+ PW_VERSION_IMPL_FACTORY_EVENTS,
+ .destroy = factory_destroy,
+};
+
+static void module_destroy(void *data)
+{
+ struct factory_data *d = data;
+
+ spa_hook_remove(&d->module_listener);
+ spa_list_remove(&d->export.link);
+ d->module = NULL;
+
+ if (d->factory)
+ pw_impl_factory_destroy(d->factory);
+}
+
+static void module_registered(void *data)
+{
+ struct factory_data *d = data;
+ struct pw_impl_module *module = d->module;
+ struct pw_impl_factory *factory = d->factory;
+ struct spa_dict_item items[1];
+ char id[16];
+ int res;
+
+ snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id);
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
+ pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
+
+ if ((res = pw_impl_factory_register(factory, NULL)) < 0) {
+ pw_log_error(NAME" %p: can't register factory: %s", factory, spa_strerror(res));
+ }
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+ .registered = module_registered,
+};
+
+int endpoint_link_factory_init(struct pw_impl_module *module)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_factory *factory;
+ struct factory_data *data;
+ int res;
+
+ factory = pw_context_create_factory(context,
+ "endpoint-link",
+ PW_TYPE_INTERFACE_EndpointLink,
+ PW_VERSION_ENDPOINT_LINK,
+ NULL,
+ sizeof(*data));
+ if (factory == NULL)
+ return -errno;
+
+ data = pw_impl_factory_get_user_data(factory);
+ data->factory = factory;
+ data->module = module;
+
+ pw_impl_factory_set_implementation(factory, &impl_factory, data);
+
+ data->export.type = PW_TYPE_INTERFACE_EndpointLink;
+ data->export.func = pw_core_endpoint_link_export;
+ if ((res = pw_context_register_export_type(context, &data->export)) < 0)
+ goto error;
+
+ pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data);
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ return 0;
+error:
+ pw_impl_factory_destroy(data->factory);
+ return res;
+}
diff --git a/src/modules/module-session-manager/endpoint-stream.c b/src/modules/module-session-manager/endpoint-stream.c
new file mode 100644
index 0000000..ce2abdb
--- /dev/null
+++ b/src/modules/module-session-manager/endpoint-stream.c
@@ -0,0 +1,581 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/session-manager.h>
+#include <pipewire/extensions/session-manager/introspect-funcs.h>
+
+#include <spa/utils/result.h>
+#include <spa/pod/builder.h>
+#include <spa/pod/filter.h>
+
+#define MAX_PARAMS 32
+
+#define NAME "endpoint-stream"
+
+struct pw_proxy *pw_core_endpoint_stream_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size);
+
+struct impl
+{
+ struct pw_global *global;
+ struct spa_hook global_listener;
+
+ union {
+ struct pw_endpoint_stream *stream;
+ struct pw_resource *resource;
+ };
+ struct spa_hook resource_listener;
+ struct spa_hook stream_listener;
+
+ struct pw_endpoint_stream_info *cached_info;
+ struct spa_list cached_params;
+
+ int ping_seq;
+ bool registered;
+};
+
+struct param_data
+{
+ struct spa_list link;
+ uint32_t id;
+ struct pw_array params;
+};
+
+struct resource_data
+{
+ struct impl *impl;
+
+ struct pw_resource *resource;
+ struct spa_hook object_listener;
+
+ uint32_t n_subscribe_ids;
+ uint32_t subscribe_ids[32];
+};
+
+struct factory_data
+{
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_impl_factory *factory;
+ struct spa_hook factory_listener;
+
+ struct pw_export_type export;
+};
+
+#define pw_endpoint_stream_resource(r,m,v,...) \
+ pw_resource_call(r,struct pw_endpoint_stream_events,m,v,__VA_ARGS__)
+
+#define pw_endpoint_stream_resource_info(r,...) \
+ pw_endpoint_stream_resource(r,info,0,__VA_ARGS__)
+#define pw_endpoint_stream_resource_param(r,...) \
+ pw_endpoint_stream_resource(r,param,0,__VA_ARGS__)
+
+static int method_enum_params(void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ struct param_data *pdata;
+ struct spa_pod *result;
+ struct spa_pod *param;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = { 0 };
+ uint32_t index;
+ uint32_t next = start;
+ uint32_t count = 0;
+
+ pw_log_debug(NAME" %p: param %u %d/%d", impl, id, start, num);
+
+ spa_list_for_each(pdata, &impl->cached_params, link) {
+ if (pdata->id != id)
+ continue;
+
+ while (true) {
+ index = next++;
+ if (index >= pw_array_get_len(&pdata->params, void*))
+ return 0;
+
+ param = *pw_array_get_unchecked(&pdata->params, index, struct spa_pod*);
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ if (spa_pod_filter(&b, &result, param, filter) != 0)
+ continue;
+
+ pw_log_debug(NAME" %p: %d param %u", impl, seq, index);
+
+ pw_endpoint_stream_resource_param(d->resource, seq, id, index, next, result);
+
+ if (++count == num)
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static int method_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ uint32_t i;
+
+ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(d->subscribe_ids));
+ d->n_subscribe_ids = n_ids;
+
+ for (i = 0; i < n_ids; i++) {
+ d->subscribe_ids[i] = ids[i];
+ pw_log_debug(NAME" %p: resource %d subscribe param %u",
+ impl, pw_resource_get_id(d->resource), ids[i]);
+ method_enum_params(object, 1, ids[i], 0, UINT32_MAX, NULL);
+ }
+ return 0;
+}
+
+static int method_set_param(void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ /* store only on the implementation; our cache will be updated
+ by the param event, since we are subscribed */
+ pw_endpoint_stream_set_param(impl->stream, id, flags, param);
+ return 0;
+}
+
+static const struct pw_endpoint_stream_methods stream_methods = {
+ PW_VERSION_ENDPOINT_STREAM_METHODS,
+ .subscribe_params = method_subscribe_params,
+ .enum_params = method_enum_params,
+ .set_param = method_set_param,
+};
+
+static int global_bind(void *object, struct pw_impl_client *client,
+ uint32_t permissions, uint32_t version, uint32_t id)
+{
+ struct impl *impl = object;
+ struct pw_resource *resource;
+ struct resource_data *data;
+
+ resource = pw_resource_new(client, id, permissions,
+ PW_TYPE_INTERFACE_EndpointStream,
+ version, sizeof(*data));
+ if (resource == NULL)
+ return -errno;
+
+ data = pw_resource_get_user_data(resource);
+ data->impl = impl;
+ data->resource = resource;
+
+ pw_global_add_resource(impl->global, resource);
+
+ /* resource methods -> implementation */
+ pw_resource_add_object_listener(resource,
+ &data->object_listener,
+ &stream_methods, data);
+
+ impl->cached_info->change_mask = PW_ENDPOINT_STREAM_CHANGE_MASK_ALL;
+ pw_endpoint_stream_resource_info(resource, impl->cached_info);
+ impl->cached_info->change_mask = 0;
+
+ return 0;
+}
+
+static void global_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->global_listener);
+ impl->global = NULL;
+ if (impl->resource)
+ pw_resource_destroy(impl->resource);
+ free(impl);
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+static void impl_resource_destroy(void *data)
+{
+ struct impl *impl = data;
+ struct param_data *pdata, *tmp;
+
+ spa_hook_remove(&impl->resource_listener);
+ spa_hook_remove(&impl->stream_listener);
+ impl->resource = NULL;
+
+ /* clear cache */
+ if (impl->cached_info)
+ pw_endpoint_stream_info_free(impl->cached_info);
+ spa_list_for_each_safe(pdata, tmp, &impl->cached_params, link) {
+ struct spa_pod **pod;
+ pw_array_for_each(pod, &pdata->params)
+ free(*pod);
+ pw_array_clear(&pdata->params);
+ spa_list_remove(&pdata->link);
+ free(pdata);
+ }
+
+ if (impl->global)
+ pw_global_destroy(impl->global);
+}
+
+static void register_global(struct impl *impl)
+{
+ impl->cached_info->id = pw_global_get_id (impl->global);
+ pw_resource_set_bound_id(impl->resource, impl->cached_info->id);
+ pw_global_register(impl->global);
+ impl->registered = true;
+}
+
+static void impl_resource_pong (void *data, int seq)
+{
+ struct impl *impl = data;
+
+ /* complete registration, if this was the initial sync */
+ if (!impl->registered && seq == impl->ping_seq) {
+ register_global(impl);
+ }
+}
+
+static const struct pw_resource_events impl_resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = impl_resource_destroy,
+ .pong = impl_resource_pong,
+};
+
+static int emit_info(void *data, struct pw_resource *resource)
+{
+ const struct pw_endpoint_stream_info *info = data;
+ pw_endpoint_stream_resource_info(resource, info);
+ return 0;
+}
+
+static void event_info(void *data, const struct pw_endpoint_stream_info *info)
+{
+ struct impl *impl = data;
+ uint32_t changed_ids[MAX_PARAMS], n_changed_ids = 0;
+ uint32_t i;
+
+ /* figure out changes to params */
+ if (info->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ if ((!impl->cached_info ||
+ info->params[i].flags != impl->cached_info->params[i].flags)
+ && info->params[i].flags & SPA_PARAM_INFO_READ)
+ changed_ids[n_changed_ids++] = info->params[i].id;
+ }
+ }
+
+ /* cache for new clients */
+ impl->cached_info = pw_endpoint_stream_info_update (impl->cached_info, info);
+
+ /* notify existing clients */
+ pw_global_for_each_resource(impl->global, emit_info, (void*) info);
+
+ /* cache params & register */
+ if (n_changed_ids > 0) {
+ /* prepare params storage */
+ for (i = 0; i < n_changed_ids; i++) {
+ struct param_data *pdata = calloc(1, sizeof(struct param_data));
+ pdata->id = changed_ids[i];
+ pw_array_init(&pdata->params, sizeof(void*));
+ spa_list_append(&impl->cached_params, &pdata->link);
+ }
+
+ /* subscribe to impl */
+ pw_endpoint_stream_subscribe_params(impl->stream, changed_ids, n_changed_ids);
+
+ /* register asynchronously on the pong event */
+ impl->ping_seq = pw_resource_ping(impl->resource, 0);
+ }
+ else if (!impl->registered) {
+ register_global(impl);
+ }
+}
+
+struct param_event_args
+{
+ uint32_t id, index, next;
+ const struct spa_pod *param;
+};
+
+static int emit_param(void *_data, struct pw_resource *resource)
+{
+ struct param_event_args *args = _data;
+ struct resource_data *data;
+ uint32_t i;
+
+ data = pw_resource_get_user_data(resource);
+ for (i = 0; i < data->n_subscribe_ids; i++) {
+ if (data->subscribe_ids[i] == args->id) {
+ pw_endpoint_stream_resource_param(resource, 1,
+ args->id, args->index, args->next, args->param);
+ }
+ }
+ return 0;
+}
+
+static void event_param(void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct impl *impl = data;
+ struct param_data *pdata;
+ struct spa_pod **pod;
+ struct param_event_args args = { id, index, next, param };
+
+ /* cache for new requests */
+ spa_list_for_each(pdata, &impl->cached_params, link) {
+ if (pdata->id != id)
+ continue;
+
+ if (!pw_array_check_index(&pdata->params, index, void*)) {
+ while (pw_array_get_len(&pdata->params, void*) <= index)
+ pw_array_add_ptr(&pdata->params, NULL);
+ }
+
+ pod = pw_array_get_unchecked(&pdata->params, index, struct spa_pod*);
+ free(*pod);
+ *pod = spa_pod_copy(param);
+ }
+
+ /* notify existing clients */
+ pw_global_for_each_resource(impl->global, emit_param, &args);
+}
+
+static const struct pw_endpoint_stream_events stream_events = {
+ PW_VERSION_ENDPOINT_STREAM_EVENTS,
+ .info = event_info,
+ .param = event_param,
+};
+
+static void *stream_new(struct pw_context *context,
+ struct pw_resource *resource,
+ struct pw_properties *properties)
+{
+ struct impl *impl;
+ char serial_str[32];
+ struct spa_dict_item items[1] = {
+ SPA_DICT_ITEM_INIT(PW_KEY_OBJECT_SERIAL, serial_str),
+ };
+ struct spa_dict extra_props = SPA_DICT_INIT_ARRAY(items);
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ NULL
+ };
+
+ impl = calloc(1, sizeof(*impl));
+ if (impl == NULL) {
+ pw_properties_free(properties);
+ return NULL;
+ }
+
+ impl->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_EndpointStream,
+ PW_VERSION_ENDPOINT_STREAM,
+ properties,
+ global_bind, impl);
+ if (impl->global == NULL) {
+ free(impl);
+ return NULL;
+ }
+ impl->resource = resource;
+
+ spa_scnprintf(serial_str, sizeof(serial_str), "%"PRIu64,
+ pw_global_get_serial(impl->global));
+ pw_global_update_keys(impl->global, &extra_props, keys);
+
+ spa_list_init(&impl->cached_params);
+
+ /* handle destroy events */
+ pw_global_add_listener(impl->global,
+ &impl->global_listener,
+ &global_events, impl);
+ pw_resource_add_listener(impl->resource,
+ &impl->resource_listener,
+ &impl_resource_events, impl);
+
+ /* handle implementation events -> cache + client resources */
+ pw_endpoint_stream_add_listener(impl->stream,
+ &impl->stream_listener,
+ &stream_events, impl);
+
+ /* global is not registered here on purpose;
+ we first cache info + params and then expose the global */
+
+ return impl;
+}
+
+static void *create_object(void *data,
+ struct pw_resource *resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id)
+{
+ struct factory_data *d = data;
+ struct pw_resource *impl_resource;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ void *result;
+ int res;
+
+ impl_resource = pw_resource_new(client, new_id, PW_PERM_ALL, type, version, 0);
+ if (impl_resource == NULL) {
+ res = -errno;
+ goto error_resource;
+ }
+
+ pw_resource_install_marshal(impl_resource, true);
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL) {
+ res = -ENOMEM;
+ goto error_stream;
+ }
+
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d",
+ pw_impl_client_get_info(client)->id);
+ pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d",
+ pw_impl_factory_get_info(d->factory)->id);
+
+ result = stream_new(pw_impl_client_get_context(client), impl_resource, properties);
+ if (result == NULL) {
+ res = -errno;
+ goto error_stream;
+ }
+ return result;
+
+error_resource:
+ pw_log_error("can't create resource: %s", spa_strerror(res));
+ pw_resource_errorf_id(resource, new_id, res, "can't create resource: %s", spa_strerror(res));
+ goto error_exit;
+error_stream:
+ pw_log_error("can't create endpoint stream: %s", spa_strerror(res));
+ pw_resource_errorf_id(resource, new_id, res, "can't create endpoint stream: %s", spa_strerror(res));
+ goto error_exit_free;
+
+error_exit_free:
+ pw_resource_remove(impl_resource);
+error_exit:
+ errno = -res;
+ return NULL;
+}
+
+static const struct pw_impl_factory_implementation impl_factory = {
+ PW_VERSION_IMPL_FACTORY_IMPLEMENTATION,
+ .create_object = create_object,
+};
+
+static void factory_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->factory_listener);
+ d->factory = NULL;
+ if (d->module)
+ pw_impl_module_destroy(d->module);
+}
+
+static const struct pw_impl_factory_events factory_events = {
+ PW_VERSION_IMPL_FACTORY_EVENTS,
+ .destroy = factory_destroy,
+};
+
+static void module_destroy(void *data)
+{
+ struct factory_data *d = data;
+
+ spa_hook_remove(&d->module_listener);
+ spa_list_remove(&d->export.link);
+ d->module = NULL;
+
+ if (d->factory)
+ pw_impl_factory_destroy(d->factory);
+}
+
+static void module_registered(void *data)
+{
+ struct factory_data *d = data;
+ struct pw_impl_module *module = d->module;
+ struct pw_impl_factory *factory = d->factory;
+ struct spa_dict_item items[1];
+ char id[16];
+ int res;
+
+ snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id);
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
+ pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
+
+ if ((res = pw_impl_factory_register(factory, NULL)) < 0) {
+ pw_log_error(NAME" %p: can't register factory: %s", factory, spa_strerror(res));
+ }
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+ .registered = module_registered,
+};
+
+int endpoint_stream_factory_init(struct pw_impl_module *module)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_factory *factory;
+ struct factory_data *data;
+ int res;
+
+ factory = pw_context_create_factory(context,
+ "endpoint-stream",
+ PW_TYPE_INTERFACE_EndpointStream,
+ PW_VERSION_ENDPOINT_STREAM,
+ NULL,
+ sizeof(*data));
+ if (factory == NULL)
+ return -errno;
+
+ data = pw_impl_factory_get_user_data(factory);
+ data->factory = factory;
+ data->module = module;
+
+ pw_impl_factory_set_implementation(factory, &impl_factory, data);
+
+ data->export.type = PW_TYPE_INTERFACE_EndpointStream;
+ data->export.func = pw_core_endpoint_stream_export;
+ if ((res = pw_context_register_export_type(context, &data->export)) < 0)
+ goto error;
+
+ pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data);
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ return 0;
+error:
+ pw_impl_factory_destroy(data->factory);
+ return res;
+}
diff --git a/src/modules/module-session-manager/endpoint.c b/src/modules/module-session-manager/endpoint.c
new file mode 100644
index 0000000..752a529
--- /dev/null
+++ b/src/modules/module-session-manager/endpoint.c
@@ -0,0 +1,590 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/session-manager.h>
+#include <pipewire/extensions/session-manager/introspect-funcs.h>
+
+#include <spa/utils/result.h>
+#include <spa/pod/builder.h>
+#include <spa/pod/filter.h>
+
+#define MAX_PARAMS 32
+
+#define NAME "endpoint"
+
+struct pw_proxy *pw_core_endpoint_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size);
+
+struct impl
+{
+ struct pw_global *global;
+ struct spa_hook global_listener;
+
+ union {
+ struct pw_endpoint *endpoint;
+ struct pw_resource *resource;
+ };
+ struct spa_hook resource_listener;
+ struct spa_hook endpoint_listener;
+
+ struct pw_endpoint_info *cached_info;
+ struct spa_list cached_params;
+
+ int ping_seq;
+ bool registered;
+};
+
+struct param_data
+{
+ struct spa_list link;
+ uint32_t id;
+ struct pw_array params;
+};
+
+struct resource_data
+{
+ struct impl *impl;
+
+ struct pw_resource *resource;
+ struct spa_hook object_listener;
+
+ uint32_t n_subscribe_ids;
+ uint32_t subscribe_ids[32];
+};
+
+struct factory_data
+{
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_impl_factory *factory;
+ struct spa_hook factory_listener;
+
+ struct pw_export_type export;
+};
+
+#define pw_endpoint_resource(r,m,v,...) \
+ pw_resource_call(r,struct pw_endpoint_events,m,v,__VA_ARGS__)
+
+#define pw_endpoint_resource_info(r,...) \
+ pw_endpoint_resource(r,info,0,__VA_ARGS__)
+#define pw_endpoint_resource_param(r,...) \
+ pw_endpoint_resource(r,param,0,__VA_ARGS__)
+
+static int method_enum_params(void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ struct param_data *pdata;
+ struct spa_pod *result;
+ struct spa_pod *param;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = { 0 };
+ uint32_t index;
+ uint32_t next = start;
+ uint32_t count = 0;
+
+ pw_log_debug(NAME" %p: param %u %d/%d", impl, id, start, num);
+
+ spa_list_for_each(pdata, &impl->cached_params, link) {
+ if (pdata->id != id)
+ continue;
+
+ while (true) {
+ index = next++;
+ if (index >= pw_array_get_len(&pdata->params, void*))
+ return 0;
+
+ param = *pw_array_get_unchecked(&pdata->params, index, struct spa_pod*);
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ if (spa_pod_filter(&b, &result, param, filter) != 0)
+ continue;
+
+ pw_log_debug(NAME" %p: %d param %u", impl, seq, index);
+
+ pw_endpoint_resource_param(d->resource, seq, id, index, next, result);
+
+ if (++count == num)
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static int method_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ uint32_t i;
+
+ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(d->subscribe_ids));
+ d->n_subscribe_ids = n_ids;
+
+ for (i = 0; i < n_ids; i++) {
+ d->subscribe_ids[i] = ids[i];
+ pw_log_debug(NAME" %p: resource %d subscribe param %u",
+ impl, pw_resource_get_id(d->resource), ids[i]);
+ method_enum_params(object, 1, ids[i], 0, UINT32_MAX, NULL);
+ }
+ return 0;
+}
+
+static int method_set_param(void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ /* store only on the implementation; our cache will be updated
+ by the param event, since we are subscribed */
+ pw_endpoint_set_param(impl->endpoint, id, flags, param);
+ return 0;
+}
+
+static int method_create_link(void *object, const struct spa_dict *props)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ pw_endpoint_create_link(impl->endpoint, props);
+ return 0;
+}
+
+static const struct pw_endpoint_methods endpoint_methods = {
+ PW_VERSION_ENDPOINT_METHODS,
+ .subscribe_params = method_subscribe_params,
+ .enum_params = method_enum_params,
+ .set_param = method_set_param,
+ .create_link = method_create_link,
+};
+
+static int global_bind(void *object, struct pw_impl_client *client,
+ uint32_t permissions, uint32_t version, uint32_t id)
+{
+ struct impl *impl = object;
+ struct pw_resource *resource;
+ struct resource_data *data;
+
+ resource = pw_resource_new(client, id, permissions,
+ PW_TYPE_INTERFACE_Endpoint,
+ version, sizeof(*data));
+ if (resource == NULL)
+ return -errno;
+
+ data = pw_resource_get_user_data(resource);
+ data->impl = impl;
+ data->resource = resource;
+
+ pw_global_add_resource(impl->global, resource);
+
+ /* resource methods -> implementation */
+ pw_resource_add_object_listener(resource,
+ &data->object_listener,
+ &endpoint_methods, data);
+
+ impl->cached_info->change_mask = PW_ENDPOINT_CHANGE_MASK_ALL;
+ pw_endpoint_resource_info(resource, impl->cached_info);
+ impl->cached_info->change_mask = 0;
+
+ return 0;
+}
+
+static void global_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->global_listener);
+ impl->global = NULL;
+ if (impl->resource)
+ pw_resource_destroy(impl->resource);
+ free(impl);
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+static void impl_resource_destroy(void *data)
+{
+ struct impl *impl = data;
+ struct param_data *pdata, *tmp;
+
+ spa_hook_remove(&impl->resource_listener);
+ spa_hook_remove(&impl->endpoint_listener);
+ impl->resource = NULL;
+
+ /* clear cache */
+ if (impl->cached_info)
+ pw_endpoint_info_free(impl->cached_info);
+ spa_list_for_each_safe(pdata, tmp, &impl->cached_params, link) {
+ struct spa_pod **pod;
+ pw_array_for_each(pod, &pdata->params)
+ free(*pod);
+ pw_array_clear(&pdata->params);
+ spa_list_remove(&pdata->link);
+ free(pdata);
+ }
+
+ if (impl->global)
+ pw_global_destroy(impl->global);
+}
+
+static void register_global(struct impl *impl)
+{
+ impl->cached_info->id = pw_global_get_id (impl->global);
+ pw_resource_set_bound_id(impl->resource, impl->cached_info->id);
+ pw_global_register(impl->global);
+ impl->registered = true;
+}
+
+static void impl_resource_pong (void *data, int seq)
+{
+ struct impl *impl = data;
+
+ /* complete registration, if this was the initial sync */
+ if (!impl->registered && seq == impl->ping_seq) {
+ register_global(impl);
+ }
+}
+
+static const struct pw_resource_events impl_resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = impl_resource_destroy,
+ .pong = impl_resource_pong,
+};
+
+static int emit_info(void *data, struct pw_resource *resource)
+{
+ const struct pw_endpoint_info *info = data;
+ pw_endpoint_resource_info(resource, info);
+ return 0;
+}
+
+static void event_info(void *data, const struct pw_endpoint_info *info)
+{
+ struct impl *impl = data;
+ uint32_t changed_ids[MAX_PARAMS], n_changed_ids = 0;
+ uint32_t i;
+
+ /* figure out changes to params */
+ if (info->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ if ((!impl->cached_info ||
+ info->params[i].flags != impl->cached_info->params[i].flags)
+ && info->params[i].flags & SPA_PARAM_INFO_READ)
+ changed_ids[n_changed_ids++] = info->params[i].id;
+ }
+ }
+
+ /* cache for new clients */
+ impl->cached_info = pw_endpoint_info_update (impl->cached_info, info);
+
+ /* notify existing clients */
+ pw_global_for_each_resource(impl->global, emit_info, (void*) info);
+
+ /* cache params & register */
+ if (n_changed_ids > 0) {
+ /* prepare params storage */
+ for (i = 0; i < n_changed_ids; i++) {
+ struct param_data *pdata = calloc(1, sizeof(struct param_data));
+ pdata->id = changed_ids[i];
+ pw_array_init(&pdata->params, sizeof(void*));
+ spa_list_append(&impl->cached_params, &pdata->link);
+ }
+
+ /* subscribe to impl */
+ pw_endpoint_subscribe_params(impl->endpoint, changed_ids, n_changed_ids);
+
+ /* register asynchronously on the pong event */
+ impl->ping_seq = pw_resource_ping(impl->resource, 0);
+ }
+ else if (!impl->registered) {
+ register_global(impl);
+ }
+}
+
+struct param_event_args
+{
+ uint32_t id, index, next;
+ const struct spa_pod *param;
+};
+
+static int emit_param(void *_data, struct pw_resource *resource)
+{
+ struct param_event_args *args = _data;
+ struct resource_data *data;
+ uint32_t i;
+
+ data = pw_resource_get_user_data(resource);
+ for (i = 0; i < data->n_subscribe_ids; i++) {
+ if (data->subscribe_ids[i] == args->id) {
+ pw_endpoint_resource_param(resource, 1,
+ args->id, args->index, args->next, args->param);
+ }
+ }
+ return 0;
+}
+
+static void event_param(void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct impl *impl = data;
+ struct param_data *pdata;
+ struct spa_pod **pod;
+ struct param_event_args args = { id, index, next, param };
+
+ /* cache for new requests */
+ spa_list_for_each(pdata, &impl->cached_params, link) {
+ if (pdata->id != id)
+ continue;
+
+ if (!pw_array_check_index(&pdata->params, index, void*)) {
+ while (pw_array_get_len(&pdata->params, void*) <= index)
+ pw_array_add_ptr(&pdata->params, NULL);
+ }
+
+ pod = pw_array_get_unchecked(&pdata->params, index, struct spa_pod*);
+ free(*pod);
+ *pod = spa_pod_copy(param);
+ }
+
+ /* notify existing clients */
+ pw_global_for_each_resource(impl->global, emit_param, &args);
+}
+
+static const struct pw_endpoint_events endpoint_events = {
+ PW_VERSION_ENDPOINT_EVENTS,
+ .info = event_info,
+ .param = event_param,
+};
+
+static void *endpoint_new(struct pw_context *context,
+ struct pw_resource *resource,
+ struct pw_properties *properties)
+{
+ struct impl *impl;
+ char serial_str[32];
+ struct spa_dict_item items[1] = {
+ SPA_DICT_ITEM_INIT(PW_KEY_OBJECT_SERIAL, serial_str),
+ };
+ struct spa_dict extra_props = SPA_DICT_INIT_ARRAY(items);
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ NULL
+ };
+
+ impl = calloc(1, sizeof(*impl));
+ if (impl == NULL) {
+ pw_properties_free(properties);
+ return NULL;
+ }
+
+ impl->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_Endpoint,
+ PW_VERSION_ENDPOINT,
+ properties,
+ global_bind, impl);
+ if (impl->global == NULL) {
+ free(impl);
+ return NULL;
+ }
+ impl->resource = resource;
+
+ spa_scnprintf(serial_str, sizeof(serial_str), "%"PRIu64,
+ pw_global_get_serial(impl->global));
+ pw_global_update_keys(impl->global, &extra_props, keys);
+
+ spa_list_init(&impl->cached_params);
+
+ /* handle destroy events */
+ pw_global_add_listener(impl->global,
+ &impl->global_listener,
+ &global_events, impl);
+ pw_resource_add_listener(impl->resource,
+ &impl->resource_listener,
+ &impl_resource_events, impl);
+
+ /* handle implementation events -> cache + client resources */
+ pw_endpoint_add_listener(impl->endpoint,
+ &impl->endpoint_listener,
+ &endpoint_events, impl);
+
+ /* global is not registered here on purpose;
+ we first cache info + params and then expose the global */
+
+ return impl;
+}
+
+static void *create_object(void *data,
+ struct pw_resource *resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id)
+{
+ struct factory_data *d = data;
+ struct pw_resource *impl_resource;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ void *result;
+ int res;
+
+ impl_resource = pw_resource_new(client, new_id, PW_PERM_ALL, type, version, 0);
+ if (impl_resource == NULL) {
+ res = -errno;
+ goto error_resource;
+ }
+
+ pw_resource_install_marshal(impl_resource, true);
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL) {
+ res = -ENOMEM;
+ goto error_endpoint;
+ }
+
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d",
+ pw_impl_client_get_info(client)->id);
+ pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d",
+ pw_impl_factory_get_info(d->factory)->id);
+
+ result = endpoint_new(pw_impl_client_get_context(client), impl_resource, properties);
+ if (result == NULL) {
+ res = -errno;
+ goto error_endpoint;
+ }
+ return result;
+
+error_resource:
+ pw_log_error("can't create resource: %s", spa_strerror(res));
+ pw_resource_errorf_id(resource, new_id, res, "can't create resource: %s", spa_strerror(res));
+ goto error_exit;
+error_endpoint:
+ pw_log_error("can't create endpoint: %s", spa_strerror(res));
+ pw_resource_errorf_id(resource, new_id, res, "can't create endpoint: %s", spa_strerror(res));
+ goto error_exit_free;
+
+error_exit_free:
+ pw_resource_remove(impl_resource);
+error_exit:
+ errno = -res;
+ return NULL;
+}
+
+static const struct pw_impl_factory_implementation impl_factory = {
+ PW_VERSION_IMPL_FACTORY_IMPLEMENTATION,
+ .create_object = create_object,
+};
+
+static void factory_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->factory_listener);
+ d->factory = NULL;
+ if (d->module)
+ pw_impl_module_destroy(d->module);
+}
+
+static const struct pw_impl_factory_events factory_events = {
+ PW_VERSION_IMPL_FACTORY_EVENTS,
+ .destroy = factory_destroy,
+};
+
+static void module_destroy(void *data)
+{
+ struct factory_data *d = data;
+
+ spa_hook_remove(&d->module_listener);
+ spa_list_remove(&d->export.link);
+
+ d->module = NULL;
+ if (d->factory)
+ pw_impl_factory_destroy(d->factory);
+}
+
+static void module_registered(void *data)
+{
+ struct factory_data *d = data;
+ struct pw_impl_module *module = d->module;
+ struct pw_impl_factory *factory = d->factory;
+ struct spa_dict_item items[1];
+ char id[16];
+ int res;
+
+ snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id);
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
+ pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
+
+ if ((res = pw_impl_factory_register(factory, NULL)) < 0) {
+ pw_log_error(NAME" %p: can't register factory: %s", factory, spa_strerror(res));
+ }
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+ .registered = module_registered,
+};
+
+int endpoint_factory_init(struct pw_impl_module *module)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_factory *factory;
+ struct factory_data *data;
+ int res;
+
+ factory = pw_context_create_factory(context,
+ "endpoint",
+ PW_TYPE_INTERFACE_Endpoint,
+ PW_VERSION_ENDPOINT,
+ NULL,
+ sizeof(*data));
+ if (factory == NULL)
+ return -errno;
+
+ data = pw_impl_factory_get_user_data(factory);
+ data->factory = factory;
+ data->module = module;
+
+ pw_impl_factory_set_implementation(factory, &impl_factory, data);
+
+ data->export.type = PW_TYPE_INTERFACE_Endpoint;
+ data->export.func = pw_core_endpoint_export;
+ if ((res = pw_context_register_export_type(context, &data->export)) < 0)
+ goto error;
+
+ pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data);
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ return 0;
+error:
+ pw_impl_factory_destroy(data->factory);
+ return res;
+}
diff --git a/src/modules/module-session-manager/protocol-native.c b/src/modules/module-session-manager/protocol-native.c
new file mode 100644
index 0000000..6981252
--- /dev/null
+++ b/src/modules/module-session-manager/protocol-native.c
@@ -0,0 +1,3083 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/pipewire.h>
+
+#include <spa/utils/result.h>
+#include <spa/pod/builder.h>
+#include <spa/pod/parser.h>
+
+#include <pipewire/extensions/session-manager.h>
+#include <pipewire/extensions/protocol-native.h>
+
+#define MAX_DICT 1024
+#define MAX_PARAMS 4096
+#define MAX_PARAM_INFO 128
+
+static void push_dict(struct spa_pod_builder *b, const struct spa_dict *dict)
+{
+ struct spa_pod_frame f;
+ uint32_t n_items;
+ uint32_t i;
+
+ n_items = dict ? dict->n_items : 0;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b, SPA_POD_Int(n_items), NULL);
+ for (i = 0; i < n_items; i++) {
+ spa_pod_builder_add(b,
+ SPA_POD_String(dict->items[i].key),
+ SPA_POD_String(dict->items[i].value),
+ NULL);
+ }
+ spa_pod_builder_pop(b, &f);
+}
+
+/* macro because of alloca() */
+#define parse_dict(p, f, dict) \
+do { \
+ uint32_t i; \
+ \
+ if (spa_pod_parser_push_struct(p, f) < 0 || \
+ spa_pod_parser_get(p, SPA_POD_Int(&(dict)->n_items), NULL) < 0) \
+ return -EINVAL; \
+ \
+ if ((dict)->n_items > 0) { \
+ if ((dict)->n_items > MAX_DICT) \
+ return -ENOSPC; \
+ (dict)->items = alloca((dict)->n_items * sizeof(struct spa_dict_item)); \
+ for (i = 0; i < (dict)->n_items; i++) { \
+ if (spa_pod_parser_get(p, \
+ SPA_POD_String(&(dict)->items[i].key), \
+ SPA_POD_String(&(dict)->items[i].value), \
+ NULL) < 0) \
+ return -EINVAL; \
+ } \
+ } \
+ spa_pod_parser_pop(p, f); \
+} while(0)
+
+static void push_param_infos(struct spa_pod_builder *b, uint32_t n_params,
+ const struct spa_param_info *params)
+{
+ struct spa_pod_frame f;
+ uint32_t i;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b, SPA_POD_Int(n_params), NULL);
+ for (i = 0; i < n_params; i++) {
+ spa_pod_builder_add(b,
+ SPA_POD_Id(params[i].id),
+ SPA_POD_Int(params[i].flags),
+ NULL);
+ }
+ spa_pod_builder_pop(b, &f);
+}
+
+/* macro because of alloca() */
+#define parse_param_infos(p, f, n_params_p, params_p) \
+do { \
+ uint32_t i; \
+ \
+ if (spa_pod_parser_push_struct(p, f) < 0 || \
+ spa_pod_parser_get(p, SPA_POD_Int(n_params_p), NULL) < 0) \
+ return -EINVAL; \
+ \
+ if (*(n_params_p) > 0) { \
+ if (*(n_params_p) > MAX_PARAM_INFO) \
+ return -ENOSPC; \
+ *(params_p) = alloca(*(n_params_p) * sizeof(struct spa_param_info)); \
+ for (i = 0; i < *(n_params_p); i++) { \
+ if (spa_pod_parser_get(p, \
+ SPA_POD_Id(&(*(params_p))[i].id), \
+ SPA_POD_Int(&(*(params_p))[i].flags), \
+ NULL) < 0) \
+ return -EINVAL; \
+ } \
+ } \
+ spa_pod_parser_pop(p, f); \
+} while(0)
+
+/***********************************************
+ * INFO STRUCTURES
+ ***********************************************/
+
+static void
+marshal_pw_session_info(struct spa_pod_builder *b,
+ const struct pw_session_info *info)
+{
+ struct spa_pod_frame f;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->version),
+ SPA_POD_Int(info->id),
+ SPA_POD_Long(info->change_mask),
+ NULL);
+ push_dict(b, info->props);
+ push_param_infos(b, info->n_params, info->params);
+ spa_pod_builder_pop(b, &f);
+}
+
+/* macro because of alloca() */
+#define demarshal_pw_session_info(p, f, info) \
+do { \
+ struct spa_pod_frame sub_f; \
+ uint32_t version; \
+ \
+ if (spa_pod_parser_push_struct(p, f) < 0 || \
+ spa_pod_parser_get(p, \
+ SPA_POD_Int(&version), \
+ SPA_POD_Int(&(info)->id), \
+ SPA_POD_Long(&(info)->change_mask), \
+ NULL) < 0) \
+ return -EINVAL; \
+ \
+ (info)->change_mask &= PW_SESSION_CHANGE_MASK_ALL; \
+ \
+ parse_dict(p, &sub_f, (info)->props); \
+ parse_param_infos(p, &sub_f, &(info)->n_params, &(info)->params); \
+ \
+ spa_pod_parser_pop(p, f); \
+} while(0)
+
+static void
+marshal_pw_endpoint_info(struct spa_pod_builder *b,
+ const struct pw_endpoint_info *info)
+{
+ struct spa_pod_frame f;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->version),
+ SPA_POD_Int(info->id),
+ SPA_POD_String(info->name),
+ SPA_POD_String(info->media_class),
+ SPA_POD_Int(info->direction),
+ SPA_POD_Int(info->flags),
+ SPA_POD_Long(info->change_mask),
+ SPA_POD_Int(info->n_streams),
+ SPA_POD_Int(info->session_id),
+ NULL);
+ push_dict(b, info->props);
+ push_param_infos(b, info->n_params, info->params);
+ spa_pod_builder_pop(b, &f);
+}
+
+/* macro because of alloca() */
+#define demarshal_pw_endpoint_info(p, f, info) \
+do { \
+ struct spa_pod_frame sub_f; \
+ uint32_t version; \
+ \
+ if (spa_pod_parser_push_struct(p, f) < 0 || \
+ spa_pod_parser_get(p, \
+ SPA_POD_Int(&version), \
+ SPA_POD_Int(&(info)->id), \
+ SPA_POD_String(&(info)->name), \
+ SPA_POD_String(&(info)->media_class), \
+ SPA_POD_Int(&(info)->direction), \
+ SPA_POD_Int(&(info)->flags), \
+ SPA_POD_Long(&(info)->change_mask), \
+ SPA_POD_Int(&(info)->n_streams), \
+ SPA_POD_Int(&(info)->session_id), \
+ NULL) < 0) \
+ return -EINVAL; \
+ \
+ (info)->change_mask &= PW_ENDPOINT_CHANGE_MASK_ALL; \
+ \
+ parse_dict(p, &sub_f, (info)->props); \
+ parse_param_infos(p, &sub_f, &(info)->n_params, &(info)->params); \
+ \
+ spa_pod_parser_pop(p, f); \
+} while(0)
+
+static void
+marshal_pw_endpoint_stream_info(struct spa_pod_builder *b,
+ const struct pw_endpoint_stream_info *info)
+{
+ struct spa_pod_frame f;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->version),
+ SPA_POD_Int(info->id),
+ SPA_POD_Int(info->endpoint_id),
+ SPA_POD_String(info->name),
+ SPA_POD_Long(info->change_mask),
+ SPA_POD_Pod(info->link_params),
+ NULL);
+ push_dict(b, info->props);
+ push_param_infos(b, info->n_params, info->params);
+ spa_pod_builder_pop(b, &f);
+}
+
+/* macro because of alloca() */
+#define demarshal_pw_endpoint_stream_info(p, f, info) \
+do { \
+ struct spa_pod_frame sub_f; \
+ uint32_t version; \
+ \
+ if (spa_pod_parser_push_struct(p, f) < 0 || \
+ spa_pod_parser_get(p, \
+ SPA_POD_Int(&version), \
+ SPA_POD_Int(&(info)->id), \
+ SPA_POD_Int(&(info)->endpoint_id), \
+ SPA_POD_String(&(info)->name), \
+ SPA_POD_Long(&(info)->change_mask), \
+ SPA_POD_Pod(&(info)->link_params), \
+ NULL) < 0) \
+ return -EINVAL; \
+ \
+ (info)->change_mask &= PW_ENDPOINT_STREAM_CHANGE_MASK_ALL; \
+ \
+ parse_dict(p, &sub_f, (info)->props); \
+ parse_param_infos(p, &sub_f, &(info)->n_params, &(info)->params); \
+ \
+ spa_pod_parser_pop(p, f); \
+} while(0)
+
+static void
+marshal_pw_endpoint_link_info(struct spa_pod_builder *b,
+ const struct pw_endpoint_link_info *info)
+{
+ struct spa_pod_frame f;
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(info->version),
+ SPA_POD_Int(info->id),
+ SPA_POD_Int(info->session_id),
+ SPA_POD_Int(info->output_endpoint_id),
+ SPA_POD_Int(info->output_stream_id),
+ SPA_POD_Int(info->input_endpoint_id),
+ SPA_POD_Int(info->input_stream_id),
+ SPA_POD_Long(info->change_mask),
+ SPA_POD_Int(info->state),
+ SPA_POD_String(info->error),
+ NULL);
+ push_dict(b, info->props);
+ push_param_infos(b, info->n_params, info->params);
+ spa_pod_builder_pop(b, &f);
+}
+
+/* macro because of alloca() */
+#define demarshal_pw_endpoint_link_info(p, f, info) \
+do { \
+ struct spa_pod_frame sub_f; \
+ uint32_t version; \
+ \
+ if (spa_pod_parser_push_struct(p, f) < 0 || \
+ spa_pod_parser_get(p, \
+ SPA_POD_Int(&version), \
+ SPA_POD_Int(&(info)->id), \
+ SPA_POD_Int(&(info)->session_id), \
+ SPA_POD_Int(&(info)->output_endpoint_id), \
+ SPA_POD_Int(&(info)->output_stream_id), \
+ SPA_POD_Int(&(info)->input_endpoint_id), \
+ SPA_POD_Int(&(info)->input_stream_id), \
+ SPA_POD_Long(&(info)->change_mask), \
+ SPA_POD_Int(&(info)->state), \
+ SPA_POD_String(&(info)->error), \
+ NULL) < 0) \
+ return -EINVAL; \
+ \
+ (info)->change_mask &= PW_ENDPOINT_LINK_CHANGE_MASK_ALL; \
+ \
+ parse_dict(p, &sub_f, (info)->props); \
+ parse_param_infos(p, &sub_f, &(info)->n_params, &(info)->params); \
+ \
+ spa_pod_parser_pop(p, f); \
+} while(0)
+
+
+/***********************************************
+ * COMMON
+ ***********************************************/
+
+static int demarshal_add_listener_enotsup(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ return -ENOTSUP;
+}
+
+/***********************************************
+ * CLIENT ENDPOINT
+ ***********************************************/
+
+static int client_endpoint_marshal_set_session_id (void *object, uint32_t id)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_CLIENT_ENDPOINT_EVENT_SET_SESSION_ID, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(id));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int client_endpoint_marshal_set_param (void *object,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_CLIENT_ENDPOINT_EVENT_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int client_endpoint_marshal_stream_set_param (void *object,
+ uint32_t stream_id, uint32_t id,
+ uint32_t flags, const struct spa_pod *param)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_CLIENT_ENDPOINT_EVENT_STREAM_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(stream_id),
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int client_endpoint_marshal_create_link (void *object,
+ const struct spa_dict *props)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_CLIENT_ENDPOINT_EVENT_CREATE_LINK, NULL);
+
+ push_dict(b, props);
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int client_endpoint_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_client_endpoint_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static int client_endpoint_marshal_update(void *object,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_info *info)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_CLIENT_ENDPOINT_METHOD_UPDATE, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(change_mask),
+ SPA_POD_Int(n_params),
+ NULL);
+
+ for (i = 0; i < n_params; i++)
+ spa_pod_builder_add(b, SPA_POD_Pod(params[i]), NULL);
+
+ if (info)
+ marshal_pw_endpoint_info(b, info);
+ else
+ spa_pod_builder_add(b, SPA_POD_Pod(NULL), NULL);
+
+ spa_pod_builder_pop(b, &f);
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int client_endpoint_marshal_stream_update(void *object,
+ uint32_t stream_id,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_stream_info *info)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_CLIENT_ENDPOINT_METHOD_STREAM_UPDATE, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(stream_id),
+ SPA_POD_Int(change_mask),
+ SPA_POD_Int(n_params),
+ NULL);
+
+ for (i = 0; i < n_params; i++)
+ spa_pod_builder_add(b, SPA_POD_Pod(params[i]), NULL);
+
+ if (info)
+ marshal_pw_endpoint_stream_info(b, info);
+ else
+ spa_pod_builder_add(b, SPA_POD_Pod(NULL), NULL);
+
+ spa_pod_builder_pop(b, &f);
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int client_endpoint_demarshal_set_session_id(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&id)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_client_endpoint_events,
+ set_session_id, 0, id);
+}
+
+static int client_endpoint_demarshal_set_param(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, flags;
+ const struct spa_pod *param = NULL;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_PodObject(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_client_endpoint_events,
+ set_param, 0, id, flags, param);
+}
+
+static int client_endpoint_demarshal_stream_set_param(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t stream_id, id, flags;
+ const struct spa_pod *param = NULL;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&stream_id),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_PodObject(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_client_endpoint_events,
+ stream_set_param, 0, stream_id, id, flags, param);
+}
+
+static int client_endpoint_demarshal_create_link(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+
+ parse_dict(&prs, &f, &props);
+
+ return pw_proxy_notify(proxy, struct pw_client_endpoint_events,
+ create_link, 0, &props);
+}
+
+static int client_endpoint_demarshal_update(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs[2];
+ struct spa_pod_frame f[2];
+ uint32_t change_mask, n_params;
+ const struct spa_pod **params = NULL;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_endpoint_info info = { .props = &props }, *infop = NULL;
+ struct spa_pod *ipod;
+ uint32_t i;
+
+ spa_pod_parser_init(&prs[0], msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs[0], &f[0]) < 0 ||
+ spa_pod_parser_get(&prs[0],
+ SPA_POD_Int(&change_mask),
+ SPA_POD_Int(&n_params), NULL) < 0)
+ return -EINVAL;
+
+ if (n_params > MAX_PARAMS)
+ return -ENOSPC;
+ if (n_params > 0)
+ params = alloca(n_params * sizeof(struct spa_pod *));
+ for (i = 0; i < n_params; i++)
+ if (spa_pod_parser_get(&prs[0],
+ SPA_POD_PodObject(&params[i]), NULL) < 0)
+ return -EINVAL;
+
+ if (spa_pod_parser_get(&prs[0], SPA_POD_PodStruct(&ipod), NULL) < 0)
+ return -EINVAL;
+ if (ipod) {
+ infop = &info;
+ spa_pod_parser_pod(&prs[1], ipod);
+ demarshal_pw_endpoint_info(&prs[1], &f[1], infop);
+ }
+
+ return pw_resource_notify(resource, struct pw_client_endpoint_methods,
+ update, 0, change_mask, n_params, params, infop);
+}
+
+static int client_endpoint_demarshal_stream_update(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs[2];
+ struct spa_pod_frame f[2];
+ uint32_t stream_id, change_mask, n_params;
+ const struct spa_pod **params = NULL;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_endpoint_stream_info info = { .props = &props }, *infop = NULL;
+ struct spa_pod *ipod;
+ uint32_t i;
+
+ spa_pod_parser_init(&prs[0], msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs[0], &f[0]) < 0 ||
+ spa_pod_parser_get(&prs[0],
+ SPA_POD_Int(&stream_id),
+ SPA_POD_Int(&change_mask),
+ SPA_POD_Int(&n_params), NULL) < 0)
+ return -EINVAL;
+
+ if (n_params > MAX_PARAMS)
+ return -ENOSPC;
+ if (n_params > 0)
+ params = alloca(n_params * sizeof(struct spa_pod *));
+ for (i = 0; i < n_params; i++)
+ if (spa_pod_parser_get(&prs[0],
+ SPA_POD_PodObject(&params[i]), NULL) < 0)
+ return -EINVAL;
+
+ if (spa_pod_parser_get(&prs[0], SPA_POD_PodStruct(&ipod), NULL) < 0)
+ return -EINVAL;
+ if (ipod) {
+ infop = &info;
+ spa_pod_parser_pod(&prs[1], ipod);
+ demarshal_pw_endpoint_stream_info(&prs[1], &f[1], infop);
+ }
+
+ return pw_resource_notify(resource, struct pw_client_endpoint_methods,
+ stream_update, 0, stream_id, change_mask, n_params, params, infop);
+}
+
+static const struct pw_client_endpoint_events pw_protocol_native_client_endpoint_event_marshal = {
+ PW_VERSION_CLIENT_ENDPOINT_EVENTS,
+ .set_session_id = client_endpoint_marshal_set_session_id,
+ .set_param = client_endpoint_marshal_set_param,
+ .stream_set_param = client_endpoint_marshal_stream_set_param,
+ .create_link = client_endpoint_marshal_create_link,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_client_endpoint_event_demarshal[PW_CLIENT_ENDPOINT_EVENT_NUM] =
+{
+ [PW_CLIENT_ENDPOINT_EVENT_SET_SESSION_ID] = { client_endpoint_demarshal_set_session_id, 0 },
+ [PW_CLIENT_ENDPOINT_EVENT_SET_PARAM] = { client_endpoint_demarshal_set_param, 0 },
+ [PW_CLIENT_ENDPOINT_EVENT_STREAM_SET_PARAM] = { client_endpoint_demarshal_stream_set_param, 0 },
+ [PW_CLIENT_ENDPOINT_EVENT_CREATE_LINK] = { client_endpoint_demarshal_create_link, 0 },
+};
+
+static const struct pw_client_endpoint_methods pw_protocol_native_client_endpoint_method_marshal = {
+ PW_VERSION_CLIENT_ENDPOINT_METHODS,
+ .add_listener = client_endpoint_marshal_add_listener,
+ .update = client_endpoint_marshal_update,
+ .stream_update = client_endpoint_marshal_stream_update,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_client_endpoint_method_demarshal[PW_CLIENT_ENDPOINT_METHOD_NUM] =
+{
+ [PW_CLIENT_ENDPOINT_METHOD_ADD_LISTENER] = { NULL, 0 },
+ [PW_CLIENT_ENDPOINT_METHOD_UPDATE] = { client_endpoint_demarshal_update, 0 },
+ [PW_CLIENT_ENDPOINT_METHOD_STREAM_UPDATE] = { client_endpoint_demarshal_stream_update, 0 },
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_client_endpoint_marshal = {
+ PW_TYPE_INTERFACE_ClientEndpoint,
+ PW_VERSION_CLIENT_ENDPOINT,
+ 0,
+ PW_CLIENT_ENDPOINT_METHOD_NUM,
+ PW_CLIENT_ENDPOINT_EVENT_NUM,
+ &pw_protocol_native_client_endpoint_method_marshal,
+ &pw_protocol_native_client_endpoint_method_demarshal,
+ &pw_protocol_native_client_endpoint_event_marshal,
+ &pw_protocol_native_client_endpoint_event_demarshal,
+};
+
+/***********************************************
+ * CLIENT SESSION
+ ***********************************************/
+
+static int client_session_marshal_set_param (void *data,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_CLIENT_SESSION_EVENT_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int client_session_marshal_link_set_param (void *data,
+ uint32_t link_id, uint32_t id,
+ uint32_t flags, const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_CLIENT_SESSION_EVENT_LINK_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(link_id),
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int client_session_marshal_link_request_state (void *data,
+ uint32_t link_id, uint32_t state)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_CLIENT_SESSION_EVENT_LINK_REQUEST_STATE, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(link_id),
+ SPA_POD_Int(state));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int client_session_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_client_session_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static int client_session_marshal_update(void *object,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_session_info *info)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_CLIENT_SESSION_METHOD_UPDATE, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(change_mask),
+ SPA_POD_Int(n_params),
+ NULL);
+
+ for (i = 0; i < n_params; i++)
+ spa_pod_builder_add(b, SPA_POD_Pod(params[i]), NULL);
+
+ if (info)
+ marshal_pw_session_info(b, info);
+ else
+ spa_pod_builder_add(b, SPA_POD_Pod(NULL), NULL);
+
+ spa_pod_builder_pop(b, &f);
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int client_session_marshal_link_update(void *object,
+ uint32_t link_id,
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_link_info *info)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+ struct spa_pod_frame f;
+ uint32_t i;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_CLIENT_SESSION_METHOD_LINK_UPDATE, NULL);
+
+ spa_pod_builder_push_struct(b, &f);
+ spa_pod_builder_add(b,
+ SPA_POD_Int(link_id),
+ SPA_POD_Int(change_mask),
+ SPA_POD_Int(n_params),
+ NULL);
+
+ for (i = 0; i < n_params; i++)
+ spa_pod_builder_add(b, SPA_POD_Pod(params[i]), NULL);
+
+ if (info)
+ marshal_pw_endpoint_link_info(b, info);
+ else
+ spa_pod_builder_add(b, SPA_POD_Pod(NULL), NULL);
+
+ spa_pod_builder_pop(b, &f);
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int client_session_demarshal_set_param(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, flags;
+ const struct spa_pod *param = NULL;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_PodObject(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_client_session_events,
+ set_param, 0, id, flags, param);
+}
+
+static int client_session_demarshal_link_set_param(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t link_id, id, flags;
+ const struct spa_pod *param = NULL;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&link_id),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_PodObject(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_client_session_events,
+ link_set_param, 0, link_id, id, flags, param);
+}
+
+static int client_session_demarshal_link_request_state(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t link_id, state;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&link_id),
+ SPA_POD_Int(&state)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_client_session_events,
+ link_request_state, 0, link_id, state);
+}
+
+static int client_session_demarshal_update(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs[2];
+ struct spa_pod_frame f[2];
+ uint32_t change_mask, n_params;
+ const struct spa_pod **params = NULL;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_session_info info = { .props = &props }, *infop = NULL;
+ struct spa_pod *ipod;
+ uint32_t i;
+
+ spa_pod_parser_init(&prs[0], msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs[0], &f[0]) < 0 ||
+ spa_pod_parser_get(&prs[0],
+ SPA_POD_Int(&change_mask),
+ SPA_POD_Int(&n_params), NULL) < 0)
+ return -EINVAL;
+
+ if (n_params > MAX_PARAMS)
+ return -ENOSPC;
+ if (n_params > 0)
+ params = alloca(n_params * sizeof(struct spa_pod *));
+ for (i = 0; i < n_params; i++)
+ if (spa_pod_parser_get(&prs[0],
+ SPA_POD_PodObject(&params[i]), NULL) < 0)
+ return -EINVAL;
+
+ if (spa_pod_parser_get(&prs[0], SPA_POD_PodStruct(&ipod), NULL) < 0)
+ return -EINVAL;
+ if (ipod) {
+ infop = &info;
+ spa_pod_parser_pod(&prs[1], ipod);
+ demarshal_pw_session_info(&prs[1], &f[1], infop);
+ }
+
+ return pw_resource_notify(resource, struct pw_client_session_methods,
+ update, 0, change_mask, n_params, params, infop);
+}
+
+static int client_session_demarshal_link_update(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs[2];
+ struct spa_pod_frame f[2];
+ uint32_t link_id, change_mask, n_params;
+ const struct spa_pod **params = NULL;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_endpoint_link_info info = { .props = &props }, *infop = NULL;
+ struct spa_pod *ipod;
+ uint32_t i;
+
+ spa_pod_parser_init(&prs[0], msg->data, msg->size);
+ if (spa_pod_parser_push_struct(&prs[0], &f[0]) < 0 ||
+ spa_pod_parser_get(&prs[0],
+ SPA_POD_Int(&link_id),
+ SPA_POD_Int(&change_mask),
+ SPA_POD_Int(&n_params), NULL) < 0)
+ return -EINVAL;
+
+ if (n_params > MAX_PARAMS)
+ return -ENOSPC;
+ if (n_params > 0)
+ params = alloca(n_params * sizeof(struct spa_pod *));
+ for (i = 0; i < n_params; i++)
+ if (spa_pod_parser_get(&prs[0],
+ SPA_POD_PodObject(&params[i]), NULL) < 0)
+ return -EINVAL;
+
+ if (spa_pod_parser_get(&prs[0], SPA_POD_PodStruct(&ipod), NULL) < 0)
+ return -EINVAL;
+ if (ipod) {
+ infop = &info;
+ spa_pod_parser_pod(&prs[1], ipod);
+ demarshal_pw_endpoint_link_info(&prs[1], &f[1], infop);
+ }
+
+ return pw_resource_notify(resource, struct pw_client_session_methods,
+ link_update, 0, link_id, change_mask, n_params, params, infop);
+}
+
+static const struct pw_client_session_events pw_protocol_native_client_session_event_marshal = {
+ PW_VERSION_CLIENT_SESSION_EVENTS,
+ .set_param = client_session_marshal_set_param,
+ .link_set_param = client_session_marshal_link_set_param,
+ .link_request_state = client_session_marshal_link_request_state,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_client_session_event_demarshal[PW_CLIENT_SESSION_EVENT_NUM] =
+{
+ [PW_CLIENT_SESSION_EVENT_SET_PARAM] = { client_session_demarshal_set_param, 0 },
+ [PW_CLIENT_SESSION_EVENT_LINK_SET_PARAM] = { client_session_demarshal_link_set_param, 0 },
+ [PW_CLIENT_SESSION_EVENT_LINK_REQUEST_STATE] = { client_session_demarshal_link_request_state, 0 },
+};
+
+static const struct pw_client_session_methods pw_protocol_native_client_session_method_marshal = {
+ PW_VERSION_CLIENT_SESSION_METHODS,
+ .add_listener = client_session_marshal_add_listener,
+ .update = client_session_marshal_update,
+ .link_update = client_session_marshal_link_update,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_client_session_method_demarshal[PW_CLIENT_SESSION_METHOD_NUM] =
+{
+ [PW_CLIENT_SESSION_METHOD_ADD_LISTENER] = { NULL, 0 },
+ [PW_CLIENT_SESSION_METHOD_UPDATE] = { client_session_demarshal_update, 0 },
+ [PW_CLIENT_SESSION_METHOD_LINK_UPDATE] = { client_session_demarshal_link_update, 0 },
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_client_session_marshal = {
+ PW_TYPE_INTERFACE_ClientSession,
+ PW_VERSION_CLIENT_SESSION,
+ 0,
+ PW_CLIENT_SESSION_METHOD_NUM,
+ PW_CLIENT_SESSION_EVENT_NUM,
+ &pw_protocol_native_client_session_method_marshal,
+ &pw_protocol_native_client_session_method_demarshal,
+ &pw_protocol_native_client_session_event_marshal,
+ &pw_protocol_native_client_session_event_demarshal,
+};
+
+/***********************************************
+ * ENDPOINT LINK
+ ***********************************************/
+
+static void endpoint_link_proxy_marshal_info (void *data,
+ const struct pw_endpoint_link_info *info)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_LINK_EVENT_INFO, NULL);
+
+ marshal_pw_endpoint_link_info(b, info);
+
+ pw_protocol_native_end_proxy(proxy, b);
+}
+
+static void endpoint_link_resource_marshal_info (void *data,
+ const struct pw_endpoint_link_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_LINK_EVENT_INFO, NULL);
+
+ marshal_pw_endpoint_link_info(b, info);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void endpoint_link_proxy_marshal_param (void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_LINK_EVENT_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(seq),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(next),
+ SPA_POD_Pod(param));
+
+ pw_protocol_native_end_proxy(proxy, b);
+}
+static void endpoint_link_resource_marshal_param (void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_LINK_EVENT_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(seq),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(next),
+ SPA_POD_Pod(param));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int endpoint_link_proxy_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_endpoint_link_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static int endpoint_link_resource_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_endpoint_link_events *events,
+ void *data)
+{
+ struct pw_resource *resource = object;
+ pw_resource_add_object_listener(resource, listener, events, data);
+ return 0;
+}
+
+static int endpoint_link_proxy_marshal_subscribe_params(void *object,
+ uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_LINK_METHOD_SUBSCRIBE_PARAMS, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int endpoint_link_resource_marshal_subscribe_params(void *object,
+ uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_LINK_METHOD_SUBSCRIBE_PARAMS, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int endpoint_link_proxy_marshal_enum_params(void *object,
+ int seq, uint32_t id,
+ uint32_t index, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_LINK_METHOD_ENUM_PARAMS, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(num),
+ SPA_POD_Pod(filter));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int endpoint_link_resource_marshal_enum_params(void *object,
+ int seq, uint32_t id,
+ uint32_t index, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_LINK_METHOD_ENUM_PARAMS, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(num),
+ SPA_POD_Pod(filter));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int endpoint_link_proxy_marshal_set_param(void *object,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_LINK_METHOD_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int endpoint_link_resource_marshal_set_param(void *object,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_LINK_METHOD_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int endpoint_link_proxy_marshal_request_state(void *object,
+ enum pw_endpoint_link_state state)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_LINK_METHOD_REQUEST_STATE, NULL);
+
+ spa_pod_builder_add_struct(b, SPA_POD_Int(state));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int endpoint_link_resource_marshal_request_state(void *object,
+ enum pw_endpoint_link_state state)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_LINK_METHOD_REQUEST_STATE, NULL);
+
+ spa_pod_builder_add_struct(b, SPA_POD_Int(state));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int endpoint_link_proxy_demarshal_info(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_endpoint_link_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+
+ demarshal_pw_endpoint_link_info(&prs, &f, &info);
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_link_events,
+ info, 0, &info);
+}
+
+static int endpoint_link_resource_demarshal_info(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_endpoint_link_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+
+ demarshal_pw_endpoint_link_info(&prs, &f, &info);
+
+ return pw_resource_notify(resource, struct pw_endpoint_link_events,
+ info, 0, &info);
+}
+
+static int endpoint_link_proxy_demarshal_param(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, index, next;
+ int seq;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&next),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_link_events,
+ param, 0, seq, id, index, next, param);
+}
+
+static int endpoint_link_resource_demarshal_param(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_parser prs;
+ uint32_t id, index, next;
+ int seq;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&next),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_endpoint_link_events,
+ param, 0, seq, id, index, next, param);
+}
+
+static int endpoint_link_proxy_demarshal_subscribe_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t csize, ctype, n_ids;
+ uint32_t *ids;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
+ return -EINVAL;
+
+ if (ctype != SPA_TYPE_Id)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_link_methods,
+ subscribe_params, 0, ids, n_ids);
+}
+
+static int endpoint_link_resource_demarshal_subscribe_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t csize, ctype, n_ids;
+ uint32_t *ids;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
+ return -EINVAL;
+
+ if (ctype != SPA_TYPE_Id)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_endpoint_link_methods,
+ subscribe_params, 0, ids, n_ids);
+}
+
+static int endpoint_link_proxy_demarshal_enum_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t id, index, num;
+ int seq;
+ struct spa_pod *filter;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&num),
+ SPA_POD_Pod(&filter)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_link_methods,
+ enum_params, 0, seq, id, index, num, filter);
+}
+
+static int endpoint_link_resource_demarshal_enum_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, index, num;
+ int seq;
+ struct spa_pod *filter;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&num),
+ SPA_POD_Pod(&filter)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_endpoint_link_methods,
+ enum_params, 0, seq, id, index, num, filter);
+}
+
+static int endpoint_link_proxy_demarshal_set_param(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t id, flags;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_link_methods,
+ set_param, 0, id, flags, param);
+}
+
+static int endpoint_link_resource_demarshal_set_param(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, flags;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_endpoint_link_methods,
+ set_param, 0, id, flags, param);
+}
+
+static int endpoint_link_proxy_demarshal_request_state(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ enum pw_endpoint_link_state state;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&state)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_link_methods,
+ request_state, 0, state);
+}
+
+static int endpoint_link_resource_demarshal_request_state(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ enum pw_endpoint_link_state state;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&state)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_endpoint_link_methods,
+ request_state, 0, state);
+}
+
+static const struct pw_endpoint_link_events pw_protocol_native_endpoint_link_client_event_marshal = {
+ PW_VERSION_ENDPOINT_LINK_EVENTS,
+ .info = endpoint_link_proxy_marshal_info,
+ .param = endpoint_link_proxy_marshal_param,
+};
+
+static const struct pw_endpoint_link_events pw_protocol_native_endpoint_link_server_event_marshal = {
+ PW_VERSION_ENDPOINT_LINK_EVENTS,
+ .info = endpoint_link_resource_marshal_info,
+ .param = endpoint_link_resource_marshal_param,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_endpoint_link_client_event_demarshal[PW_ENDPOINT_LINK_EVENT_NUM] =
+{
+ [PW_ENDPOINT_LINK_EVENT_INFO] = { endpoint_link_proxy_demarshal_info, 0 },
+ [PW_ENDPOINT_LINK_EVENT_PARAM] = { endpoint_link_proxy_demarshal_param, 0 },
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_endpoint_link_server_event_demarshal[PW_ENDPOINT_LINK_EVENT_NUM] =
+{
+ [PW_ENDPOINT_LINK_EVENT_INFO] = { endpoint_link_resource_demarshal_info, 0 },
+ [PW_ENDPOINT_LINK_EVENT_PARAM] = { endpoint_link_resource_demarshal_param, 0 },
+};
+
+static const struct pw_endpoint_link_methods pw_protocol_native_endpoint_link_client_method_marshal = {
+ PW_VERSION_ENDPOINT_LINK_METHODS,
+ .add_listener = endpoint_link_proxy_marshal_add_listener,
+ .subscribe_params = endpoint_link_proxy_marshal_subscribe_params,
+ .enum_params = endpoint_link_proxy_marshal_enum_params,
+ .set_param = endpoint_link_proxy_marshal_set_param,
+ .request_state = endpoint_link_proxy_marshal_request_state,
+};
+
+static const struct pw_endpoint_link_methods pw_protocol_native_endpoint_link_server_method_marshal = {
+ PW_VERSION_ENDPOINT_LINK_METHODS,
+ .add_listener = endpoint_link_resource_marshal_add_listener,
+ .subscribe_params = endpoint_link_resource_marshal_subscribe_params,
+ .enum_params = endpoint_link_resource_marshal_enum_params,
+ .set_param = endpoint_link_resource_marshal_set_param,
+ .request_state = endpoint_link_resource_marshal_request_state,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_endpoint_link_client_method_demarshal[PW_ENDPOINT_LINK_METHOD_NUM] =
+{
+ [PW_ENDPOINT_LINK_METHOD_ADD_LISTENER] = { demarshal_add_listener_enotsup, 0 },
+ [PW_ENDPOINT_LINK_METHOD_SUBSCRIBE_PARAMS] = { endpoint_link_proxy_demarshal_subscribe_params, 0 },
+ [PW_ENDPOINT_LINK_METHOD_ENUM_PARAMS] = { endpoint_link_proxy_demarshal_enum_params, 0 },
+ [PW_ENDPOINT_LINK_METHOD_SET_PARAM] = { endpoint_link_proxy_demarshal_set_param, PW_PERM_W },
+ [PW_ENDPOINT_LINK_METHOD_REQUEST_STATE] = { endpoint_link_proxy_demarshal_request_state, PW_PERM_W },
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_endpoint_link_server_method_demarshal[PW_ENDPOINT_LINK_METHOD_NUM] =
+{
+ [PW_ENDPOINT_LINK_METHOD_ADD_LISTENER] = { demarshal_add_listener_enotsup, 0 },
+ [PW_ENDPOINT_LINK_METHOD_SUBSCRIBE_PARAMS] = { endpoint_link_resource_demarshal_subscribe_params, 0 },
+ [PW_ENDPOINT_LINK_METHOD_ENUM_PARAMS] = { endpoint_link_resource_demarshal_enum_params, 0 },
+ [PW_ENDPOINT_LINK_METHOD_SET_PARAM] = { endpoint_link_resource_demarshal_set_param, PW_PERM_W },
+ [PW_ENDPOINT_LINK_METHOD_REQUEST_STATE] = { endpoint_link_resource_demarshal_request_state, PW_PERM_W },
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_endpoint_link_marshal = {
+ PW_TYPE_INTERFACE_EndpointLink,
+ PW_VERSION_ENDPOINT_LINK,
+ 0,
+ PW_ENDPOINT_LINK_METHOD_NUM,
+ PW_ENDPOINT_LINK_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_endpoint_link_client_method_marshal,
+ .server_demarshal = pw_protocol_native_endpoint_link_server_method_demarshal,
+ .server_marshal = &pw_protocol_native_endpoint_link_server_event_marshal,
+ .client_demarshal = pw_protocol_native_endpoint_link_client_event_demarshal,
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_endpoint_link_impl_marshal = {
+ PW_TYPE_INTERFACE_EndpointLink,
+ PW_VERSION_ENDPOINT_LINK,
+ PW_PROTOCOL_MARSHAL_FLAG_IMPL,
+ PW_ENDPOINT_LINK_EVENT_NUM,
+ PW_ENDPOINT_LINK_METHOD_NUM,
+ .client_marshal = &pw_protocol_native_endpoint_link_client_event_marshal,
+ .server_demarshal = pw_protocol_native_endpoint_link_server_event_demarshal,
+ .server_marshal = &pw_protocol_native_endpoint_link_server_method_marshal,
+ .client_demarshal = pw_protocol_native_endpoint_link_client_method_demarshal,
+};
+
+/***********************************************
+ * ENDPOINT STREAM
+ ***********************************************/
+
+static void endpoint_stream_proxy_marshal_info (void *data,
+ const struct pw_endpoint_stream_info *info)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_STREAM_EVENT_INFO, NULL);
+
+ marshal_pw_endpoint_stream_info(b, info);
+
+ pw_protocol_native_end_proxy(proxy, b);
+}
+
+static void endpoint_stream_resource_marshal_info (void *data,
+ const struct pw_endpoint_stream_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_STREAM_EVENT_INFO, NULL);
+
+ marshal_pw_endpoint_stream_info(b, info);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void endpoint_stream_proxy_marshal_param (void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_STREAM_EVENT_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(seq),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(next),
+ SPA_POD_Pod(param));
+
+ pw_protocol_native_end_proxy(proxy, b);
+}
+
+static void endpoint_stream_resource_marshal_param (void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_STREAM_EVENT_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(seq),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(next),
+ SPA_POD_Pod(param));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int endpoint_stream_proxy_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_endpoint_stream_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static int endpoint_stream_resource_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_endpoint_stream_events *events,
+ void *data)
+{
+ struct pw_resource *resource = object;
+ pw_resource_add_object_listener(resource, listener, events, data);
+ return 0;
+}
+
+static int endpoint_stream_proxy_marshal_subscribe_params(void *object,
+ uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_STREAM_METHOD_SUBSCRIBE_PARAMS, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int endpoint_stream_resource_marshal_subscribe_params(void *object,
+ uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_STREAM_METHOD_SUBSCRIBE_PARAMS, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int endpoint_stream_proxy_marshal_enum_params(void *object,
+ int seq, uint32_t id,
+ uint32_t index, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_STREAM_METHOD_ENUM_PARAMS, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(num),
+ SPA_POD_Pod(filter));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int endpoint_stream_resource_marshal_enum_params(void *object,
+ int seq, uint32_t id,
+ uint32_t index, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_STREAM_METHOD_ENUM_PARAMS, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(num),
+ SPA_POD_Pod(filter));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int endpoint_stream_proxy_marshal_set_param(void *object,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_STREAM_METHOD_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int endpoint_stream_resource_marshal_set_param(void *object,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_STREAM_METHOD_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int endpoint_stream_proxy_demarshal_info(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_endpoint_stream_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+
+ demarshal_pw_endpoint_stream_info(&prs, &f, &info);
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_stream_events,
+ info, 0, &info);
+}
+
+static int endpoint_stream_resource_demarshal_info(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_endpoint_stream_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+
+ demarshal_pw_endpoint_stream_info(&prs, &f, &info);
+
+ return pw_resource_notify(resource, struct pw_endpoint_stream_events,
+ info, 0, &info);
+}
+
+static int endpoint_stream_proxy_demarshal_param(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, index, next;
+ int seq;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&next),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_stream_events,
+ param, 0, seq, id, index, next, param);
+}
+
+static int endpoint_stream_resource_demarshal_param(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_parser prs;
+ uint32_t id, index, next;
+ int seq;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&next),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_endpoint_stream_events,
+ param, 0, seq, id, index, next, param);
+}
+
+static int endpoint_stream_proxy_demarshal_subscribe_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t csize, ctype, n_ids;
+ uint32_t *ids;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
+ return -EINVAL;
+
+ if (ctype != SPA_TYPE_Id)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_stream_methods,
+ subscribe_params, 0, ids, n_ids);
+}
+
+static int endpoint_stream_resource_demarshal_subscribe_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t csize, ctype, n_ids;
+ uint32_t *ids;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
+ return -EINVAL;
+
+ if (ctype != SPA_TYPE_Id)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_endpoint_stream_methods,
+ subscribe_params, 0, ids, n_ids);
+}
+
+static int endpoint_stream_proxy_demarshal_enum_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t id, index, num;
+ int seq;
+ struct spa_pod *filter;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&num),
+ SPA_POD_Pod(&filter)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_stream_methods,
+ enum_params, 0, seq, id, index, num, filter);
+}
+
+static int endpoint_stream_resource_demarshal_enum_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, index, num;
+ int seq;
+ struct spa_pod *filter;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&num),
+ SPA_POD_Pod(&filter)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_endpoint_stream_methods,
+ enum_params, 0, seq, id, index, num, filter);
+}
+
+static int endpoint_stream_proxy_demarshal_set_param(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t id, flags;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_stream_methods,
+ set_param, 0, id, flags, param);
+}
+
+static int endpoint_stream_resource_demarshal_set_param(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, flags;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_endpoint_stream_methods,
+ set_param, 0, id, flags, param);
+}
+
+static const struct pw_endpoint_stream_events pw_protocol_native_endpoint_stream_client_event_marshal = {
+ PW_VERSION_ENDPOINT_STREAM_EVENTS,
+ .info = endpoint_stream_proxy_marshal_info,
+ .param = endpoint_stream_proxy_marshal_param,
+};
+
+static const struct pw_endpoint_stream_events pw_protocol_native_endpoint_stream_server_event_marshal = {
+ PW_VERSION_ENDPOINT_STREAM_EVENTS,
+ .info = endpoint_stream_resource_marshal_info,
+ .param = endpoint_stream_resource_marshal_param,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_endpoint_stream_client_event_demarshal[PW_ENDPOINT_STREAM_EVENT_NUM] =
+{
+ [PW_ENDPOINT_STREAM_EVENT_INFO] = { endpoint_stream_proxy_demarshal_info, 0 },
+ [PW_ENDPOINT_STREAM_EVENT_PARAM] = { endpoint_stream_proxy_demarshal_param, 0 },
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_endpoint_stream_server_event_demarshal[PW_ENDPOINT_STREAM_EVENT_NUM] =
+{
+ [PW_ENDPOINT_STREAM_EVENT_INFO] = { endpoint_stream_resource_demarshal_info, 0 },
+ [PW_ENDPOINT_STREAM_EVENT_PARAM] = { endpoint_stream_resource_demarshal_param, 0 },
+};
+
+static const struct pw_endpoint_stream_methods pw_protocol_native_endpoint_stream_client_method_marshal = {
+ PW_VERSION_ENDPOINT_STREAM_METHODS,
+ .add_listener = endpoint_stream_proxy_marshal_add_listener,
+ .subscribe_params = endpoint_stream_proxy_marshal_subscribe_params,
+ .enum_params = endpoint_stream_proxy_marshal_enum_params,
+ .set_param = endpoint_stream_proxy_marshal_set_param,
+};
+
+static const struct pw_endpoint_stream_methods pw_protocol_native_endpoint_stream_server_method_marshal = {
+ PW_VERSION_ENDPOINT_STREAM_METHODS,
+ .add_listener = endpoint_stream_resource_marshal_add_listener,
+ .subscribe_params = endpoint_stream_resource_marshal_subscribe_params,
+ .enum_params = endpoint_stream_resource_marshal_enum_params,
+ .set_param = endpoint_stream_resource_marshal_set_param,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_endpoint_stream_client_method_demarshal[PW_ENDPOINT_STREAM_METHOD_NUM] =
+{
+ [PW_ENDPOINT_STREAM_METHOD_ADD_LISTENER] = { demarshal_add_listener_enotsup, 0 },
+ [PW_ENDPOINT_STREAM_METHOD_SUBSCRIBE_PARAMS] = { endpoint_stream_proxy_demarshal_subscribe_params, 0 },
+ [PW_ENDPOINT_STREAM_METHOD_ENUM_PARAMS] = { endpoint_stream_proxy_demarshal_enum_params, 0 },
+ [PW_ENDPOINT_STREAM_METHOD_SET_PARAM] = { endpoint_stream_proxy_demarshal_set_param, PW_PERM_W },
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_endpoint_stream_server_method_demarshal[PW_ENDPOINT_STREAM_METHOD_NUM] =
+{
+ [PW_ENDPOINT_STREAM_METHOD_ADD_LISTENER] = { demarshal_add_listener_enotsup, 0 },
+ [PW_ENDPOINT_STREAM_METHOD_SUBSCRIBE_PARAMS] = { endpoint_stream_resource_demarshal_subscribe_params, 0 },
+ [PW_ENDPOINT_STREAM_METHOD_ENUM_PARAMS] = { endpoint_stream_resource_demarshal_enum_params, 0 },
+ [PW_ENDPOINT_STREAM_METHOD_SET_PARAM] = { endpoint_stream_resource_demarshal_set_param, PW_PERM_W },
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_endpoint_stream_marshal = {
+ PW_TYPE_INTERFACE_EndpointStream,
+ PW_VERSION_ENDPOINT_STREAM,
+ 0,
+ PW_ENDPOINT_STREAM_METHOD_NUM,
+ PW_ENDPOINT_STREAM_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_endpoint_stream_client_method_marshal,
+ .server_demarshal = pw_protocol_native_endpoint_stream_server_method_demarshal,
+ .server_marshal = &pw_protocol_native_endpoint_stream_server_event_marshal,
+ .client_demarshal = pw_protocol_native_endpoint_stream_client_event_demarshal,
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_endpoint_stream_impl_marshal = {
+ PW_TYPE_INTERFACE_EndpointStream,
+ PW_VERSION_ENDPOINT_STREAM,
+ PW_PROTOCOL_MARSHAL_FLAG_IMPL,
+ PW_ENDPOINT_STREAM_EVENT_NUM,
+ PW_ENDPOINT_STREAM_METHOD_NUM,
+ .client_marshal = &pw_protocol_native_endpoint_stream_client_event_marshal,
+ .server_demarshal = pw_protocol_native_endpoint_stream_server_event_demarshal,
+ .server_marshal = &pw_protocol_native_endpoint_stream_server_method_marshal,
+ .client_demarshal = pw_protocol_native_endpoint_stream_client_method_demarshal,
+};
+
+/***********************************************
+ * ENDPOINT
+ ***********************************************/
+
+static void endpoint_proxy_marshal_info (void *data,
+ const struct pw_endpoint_info *info)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_EVENT_INFO, NULL);
+
+ marshal_pw_endpoint_info(b, info);
+
+ pw_protocol_native_end_proxy(proxy, b);
+}
+
+static void endpoint_resource_marshal_info (void *data,
+ const struct pw_endpoint_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_EVENT_INFO, NULL);
+
+ marshal_pw_endpoint_info(b, info);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void endpoint_proxy_marshal_param (void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_EVENT_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(seq),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(next),
+ SPA_POD_Pod(param));
+
+ pw_protocol_native_end_proxy(proxy, b);
+}
+
+static void endpoint_resource_marshal_param (void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_EVENT_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(seq),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(next),
+ SPA_POD_Pod(param));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int endpoint_proxy_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_endpoint_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static int endpoint_resource_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_endpoint_events *events,
+ void *data)
+{
+ struct pw_resource *resource = object;
+ pw_resource_add_object_listener(resource, listener, events, data);
+ return 0;
+}
+
+static int endpoint_proxy_marshal_subscribe_params(void *object,
+ uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_METHOD_SUBSCRIBE_PARAMS, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int endpoint_resource_marshal_subscribe_params(void *object,
+ uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_METHOD_SUBSCRIBE_PARAMS, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int endpoint_proxy_marshal_enum_params(void *object,
+ int seq, uint32_t id,
+ uint32_t index, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_METHOD_ENUM_PARAMS, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(num),
+ SPA_POD_Pod(filter));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int endpoint_resource_marshal_enum_params(void *object,
+ int seq, uint32_t id,
+ uint32_t index, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_METHOD_ENUM_PARAMS, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(num),
+ SPA_POD_Pod(filter));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int endpoint_proxy_marshal_set_param(void *object,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_METHOD_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int endpoint_resource_marshal_set_param(void *object,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_METHOD_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int endpoint_proxy_marshal_create_link(void *object,
+ const struct spa_dict *props)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_ENDPOINT_METHOD_CREATE_LINK, NULL);
+
+ push_dict(b, props);
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int endpoint_resource_marshal_create_link(void *object,
+ const struct spa_dict *props)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_ENDPOINT_METHOD_CREATE_LINK, NULL);
+
+ push_dict(b, props);
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int endpoint_proxy_demarshal_info(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_endpoint_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+
+ demarshal_pw_endpoint_info(&prs, &f, &info);
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_events,
+ info, 0, &info);
+}
+
+static int endpoint_resource_demarshal_info(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_endpoint_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+
+ demarshal_pw_endpoint_info(&prs, &f, &info);
+
+ return pw_resource_notify(resource, struct pw_endpoint_events,
+ info, 0, &info);
+}
+
+static int endpoint_proxy_demarshal_param(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, index, next;
+ int seq;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&next),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_events,
+ param, 0, seq, id, index, next, param);
+}
+
+static int endpoint_resource_demarshal_param(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_parser prs;
+ uint32_t id, index, next;
+ int seq;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&next),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_endpoint_events,
+ param, 0, seq, id, index, next, param);
+}
+
+static int endpoint_proxy_demarshal_subscribe_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t csize, ctype, n_ids;
+ uint32_t *ids;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
+ return -EINVAL;
+
+ if (ctype != SPA_TYPE_Id)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_methods,
+ subscribe_params, 0, ids, n_ids);
+}
+
+static int endpoint_resource_demarshal_subscribe_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t csize, ctype, n_ids;
+ uint32_t *ids;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
+ return -EINVAL;
+
+ if (ctype != SPA_TYPE_Id)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_endpoint_methods,
+ subscribe_params, 0, ids, n_ids);
+}
+
+static int endpoint_proxy_demarshal_enum_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t id, index, num;
+ int seq;
+ struct spa_pod *filter;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&num),
+ SPA_POD_Pod(&filter)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_methods,
+ enum_params, 0, seq, id, index, num, filter);
+}
+
+static int endpoint_resource_demarshal_enum_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, index, num;
+ int seq;
+ struct spa_pod *filter;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&num),
+ SPA_POD_Pod(&filter)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_endpoint_methods,
+ enum_params, 0, seq, id, index, num, filter);
+}
+
+static int endpoint_proxy_demarshal_set_param(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t id, flags;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_methods,
+ set_param, 0, id, flags, param);
+}
+
+static int endpoint_resource_demarshal_set_param(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, flags;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_endpoint_methods,
+ set_param, 0, id, flags, param);
+}
+
+static int endpoint_proxy_demarshal_create_link(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+
+ parse_dict(&prs, &f, &props);
+
+ return pw_proxy_notify(proxy, struct pw_endpoint_methods,
+ create_link, 0, &props);
+}
+
+static int endpoint_resource_demarshal_create_link(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+
+ parse_dict(&prs, &f, &props);
+
+ return pw_resource_notify(resource, struct pw_endpoint_methods,
+ create_link, 0, &props);
+}
+
+static const struct pw_endpoint_events pw_protocol_native_endpoint_client_event_marshal = {
+ PW_VERSION_ENDPOINT_EVENTS,
+ .info = endpoint_proxy_marshal_info,
+ .param = endpoint_proxy_marshal_param,
+};
+
+static const struct pw_endpoint_events pw_protocol_native_endpoint_server_event_marshal = {
+ PW_VERSION_ENDPOINT_EVENTS,
+ .info = endpoint_resource_marshal_info,
+ .param = endpoint_resource_marshal_param,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_endpoint_client_event_demarshal[PW_ENDPOINT_EVENT_NUM] =
+{
+ [PW_ENDPOINT_EVENT_INFO] = { endpoint_proxy_demarshal_info, 0 },
+ [PW_ENDPOINT_EVENT_PARAM] = { endpoint_proxy_demarshal_param, 0 },
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_endpoint_server_event_demarshal[PW_ENDPOINT_EVENT_NUM] =
+{
+ [PW_ENDPOINT_EVENT_INFO] = { endpoint_resource_demarshal_info, 0 },
+ [PW_ENDPOINT_EVENT_PARAM] = { endpoint_resource_demarshal_param, 0 },
+};
+
+static const struct pw_endpoint_methods pw_protocol_native_endpoint_client_method_marshal = {
+ PW_VERSION_ENDPOINT_METHODS,
+ .add_listener = endpoint_proxy_marshal_add_listener,
+ .subscribe_params = endpoint_proxy_marshal_subscribe_params,
+ .enum_params = endpoint_proxy_marshal_enum_params,
+ .set_param = endpoint_proxy_marshal_set_param,
+ .create_link = endpoint_proxy_marshal_create_link,
+};
+
+static const struct pw_endpoint_methods pw_protocol_native_endpoint_server_method_marshal = {
+ PW_VERSION_ENDPOINT_METHODS,
+ .add_listener = endpoint_resource_marshal_add_listener,
+ .subscribe_params = endpoint_resource_marshal_subscribe_params,
+ .enum_params = endpoint_resource_marshal_enum_params,
+ .set_param = endpoint_resource_marshal_set_param,
+ .create_link = endpoint_resource_marshal_create_link,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_endpoint_client_method_demarshal[PW_ENDPOINT_METHOD_NUM] =
+{
+ [PW_ENDPOINT_METHOD_ADD_LISTENER] = { demarshal_add_listener_enotsup, 0 },
+ [PW_ENDPOINT_METHOD_SUBSCRIBE_PARAMS] = { endpoint_proxy_demarshal_subscribe_params, 0 },
+ [PW_ENDPOINT_METHOD_ENUM_PARAMS] = { endpoint_proxy_demarshal_enum_params, 0 },
+ [PW_ENDPOINT_METHOD_SET_PARAM] = { endpoint_proxy_demarshal_set_param, PW_PERM_W },
+ [PW_ENDPOINT_METHOD_CREATE_LINK] = { endpoint_proxy_demarshal_create_link, PW_PERM_X },
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_endpoint_server_method_demarshal[PW_ENDPOINT_METHOD_NUM] =
+{
+ [PW_ENDPOINT_METHOD_ADD_LISTENER] = { demarshal_add_listener_enotsup, 0 },
+ [PW_ENDPOINT_METHOD_SUBSCRIBE_PARAMS] = { endpoint_resource_demarshal_subscribe_params, 0 },
+ [PW_ENDPOINT_METHOD_ENUM_PARAMS] = { endpoint_resource_demarshal_enum_params, 0 },
+ [PW_ENDPOINT_METHOD_SET_PARAM] = { endpoint_resource_demarshal_set_param, PW_PERM_W },
+ [PW_ENDPOINT_METHOD_CREATE_LINK] = { endpoint_resource_demarshal_create_link, PW_PERM_X },
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_endpoint_marshal = {
+ PW_TYPE_INTERFACE_Endpoint,
+ PW_VERSION_ENDPOINT,
+ 0,
+ PW_ENDPOINT_METHOD_NUM,
+ PW_ENDPOINT_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_endpoint_client_method_marshal,
+ .server_demarshal = pw_protocol_native_endpoint_server_method_demarshal,
+ .server_marshal = &pw_protocol_native_endpoint_server_event_marshal,
+ .client_demarshal = pw_protocol_native_endpoint_client_event_demarshal,
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_endpoint_impl_marshal = {
+ PW_TYPE_INTERFACE_Endpoint,
+ PW_VERSION_ENDPOINT,
+ PW_PROTOCOL_MARSHAL_FLAG_IMPL,
+ PW_ENDPOINT_EVENT_NUM,
+ PW_ENDPOINT_METHOD_NUM,
+ .client_marshal = &pw_protocol_native_endpoint_client_event_marshal,
+ .server_demarshal = pw_protocol_native_endpoint_server_event_demarshal,
+ .server_marshal = &pw_protocol_native_endpoint_server_method_marshal,
+ .client_demarshal = pw_protocol_native_endpoint_client_method_demarshal,
+};
+
+/***********************************************
+ * SESSION
+ ***********************************************/
+
+static void session_proxy_marshal_info (void *data,
+ const struct pw_session_info *info)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_SESSION_EVENT_INFO, NULL);
+
+ marshal_pw_session_info(b, info);
+
+ pw_protocol_native_end_proxy(proxy, b);
+}
+
+static void session_resource_marshal_info (void *data,
+ const struct pw_session_info *info)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_SESSION_EVENT_INFO, NULL);
+
+ marshal_pw_session_info(b, info);
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static void session_proxy_marshal_param (void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_SESSION_EVENT_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(seq),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(next),
+ SPA_POD_Pod(param));
+
+ pw_protocol_native_end_proxy(proxy, b);
+}
+
+static void session_resource_marshal_param (void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_SESSION_EVENT_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(seq),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(next),
+ SPA_POD_Pod(param));
+
+ pw_protocol_native_end_resource(resource, b);
+}
+
+static int session_proxy_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_session_events *events,
+ void *data)
+{
+ struct pw_proxy *proxy = object;
+ pw_proxy_add_object_listener(proxy, listener, events, data);
+ return 0;
+}
+
+static int session_resource_marshal_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_session_events *events,
+ void *data)
+{
+ struct pw_resource *resource = object;
+ pw_resource_add_object_listener(resource, listener, events, data);
+ return 0;
+}
+
+static int session_proxy_marshal_subscribe_params(void *object,
+ uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_SESSION_METHOD_SUBSCRIBE_PARAMS, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int session_resource_marshal_subscribe_params(void *object,
+ uint32_t *ids, uint32_t n_ids)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_SESSION_METHOD_SUBSCRIBE_PARAMS, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id, n_ids, ids));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int session_proxy_marshal_enum_params(void *object,
+ int seq, uint32_t id,
+ uint32_t index, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_SESSION_METHOD_ENUM_PARAMS, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(num),
+ SPA_POD_Pod(filter));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int session_resource_marshal_enum_params(void *object,
+ int seq, uint32_t id,
+ uint32_t index, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct pw_protocol_native_message *msg;
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_SESSION_METHOD_ENUM_PARAMS, &msg);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Int(SPA_RESULT_RETURN_ASYNC(msg->seq)),
+ SPA_POD_Id(id),
+ SPA_POD_Int(index),
+ SPA_POD_Int(num),
+ SPA_POD_Pod(filter));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int session_proxy_marshal_set_param(void *object,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_proxy(proxy,
+ PW_SESSION_METHOD_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_proxy(proxy, b);
+}
+
+static int session_resource_marshal_set_param(void *object,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_builder *b;
+
+ b = pw_protocol_native_begin_resource(resource,
+ PW_SESSION_METHOD_SET_PARAM, NULL);
+
+ spa_pod_builder_add_struct(b,
+ SPA_POD_Id(id),
+ SPA_POD_Int(flags),
+ SPA_POD_Pod(param));
+
+ return pw_protocol_native_end_resource(resource, b);
+}
+
+static int session_proxy_demarshal_info(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_session_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+
+ demarshal_pw_session_info(&prs, &f, &info);
+
+ return pw_proxy_notify(proxy, struct pw_session_events,
+ info, 0, &info);
+}
+
+static int session_resource_demarshal_info(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_parser prs;
+ struct spa_pod_frame f;
+ struct spa_dict props = SPA_DICT_INIT(NULL, 0);
+ struct pw_session_info info = { .props = &props };
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+
+ demarshal_pw_session_info(&prs, &f, &info);
+
+ return pw_resource_notify(resource, struct pw_session_events,
+ info, 0, &info);
+}
+
+static int session_proxy_demarshal_param(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = data;
+ struct spa_pod_parser prs;
+ uint32_t id, index, next;
+ int seq;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&next),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_session_events,
+ param, 0, seq, id, index, next, param);
+}
+
+static int session_resource_demarshal_param(void *data,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = data;
+ struct spa_pod_parser prs;
+ uint32_t id, index, next;
+ int seq;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&next),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_session_events,
+ param, 0, seq, id, index, next, param);
+}
+
+static int session_proxy_demarshal_subscribe_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t csize, ctype, n_ids;
+ uint32_t *ids;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
+ return -EINVAL;
+
+ if (ctype != SPA_TYPE_Id)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_session_methods,
+ subscribe_params, 0, ids, n_ids);
+}
+
+static int session_resource_demarshal_subscribe_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t csize, ctype, n_ids;
+ uint32_t *ids;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Array(&csize, &ctype, &n_ids, &ids)) < 0)
+ return -EINVAL;
+
+ if (ctype != SPA_TYPE_Id)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_session_methods,
+ subscribe_params, 0, ids, n_ids);
+}
+
+static int session_proxy_demarshal_enum_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t id, index, num;
+ int seq;
+ struct spa_pod *filter;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&num),
+ SPA_POD_Pod(&filter)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_session_methods,
+ enum_params, 0, seq, id, index, num, filter);
+}
+
+static int session_resource_demarshal_enum_params(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, index, num;
+ int seq;
+ struct spa_pod *filter;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Int(&seq),
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&index),
+ SPA_POD_Int(&num),
+ SPA_POD_Pod(&filter)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_session_methods,
+ enum_params, 0, seq, id, index, num, filter);
+}
+
+static int session_proxy_demarshal_set_param(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_proxy *proxy = object;
+ struct spa_pod_parser prs;
+ uint32_t id, flags;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_proxy_notify(proxy, struct pw_session_methods,
+ set_param, 0, id, flags, param);
+}
+
+static int session_resource_demarshal_set_param(void *object,
+ const struct pw_protocol_native_message *msg)
+{
+ struct pw_resource *resource = object;
+ struct spa_pod_parser prs;
+ uint32_t id, flags;
+ struct spa_pod *param;
+
+ spa_pod_parser_init(&prs, msg->data, msg->size);
+ if (spa_pod_parser_get_struct(&prs,
+ SPA_POD_Id(&id),
+ SPA_POD_Int(&flags),
+ SPA_POD_Pod(&param)) < 0)
+ return -EINVAL;
+
+ return pw_resource_notify(resource, struct pw_session_methods,
+ set_param, 0, id, flags, param);
+}
+
+static const struct pw_session_events pw_protocol_native_session_client_event_marshal = {
+ PW_VERSION_SESSION_EVENTS,
+ .info = session_proxy_marshal_info,
+ .param = session_proxy_marshal_param,
+};
+
+static const struct pw_session_events pw_protocol_native_session_server_event_marshal = {
+ PW_VERSION_SESSION_EVENTS,
+ .info = session_resource_marshal_info,
+ .param = session_resource_marshal_param,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_session_client_event_demarshal[PW_SESSION_EVENT_NUM] =
+{
+ [PW_SESSION_EVENT_INFO] = { session_proxy_demarshal_info, 0 },
+ [PW_SESSION_EVENT_PARAM] = { session_proxy_demarshal_param, 0 },
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_session_server_event_demarshal[PW_SESSION_EVENT_NUM] =
+{
+ [PW_SESSION_EVENT_INFO] = { session_resource_demarshal_info, 0 },
+ [PW_SESSION_EVENT_PARAM] = { session_resource_demarshal_param, 0 },
+};
+
+static const struct pw_session_methods pw_protocol_native_session_client_method_marshal = {
+ PW_VERSION_SESSION_METHODS,
+ .add_listener = session_proxy_marshal_add_listener,
+ .subscribe_params = session_proxy_marshal_subscribe_params,
+ .enum_params = session_proxy_marshal_enum_params,
+ .set_param = session_proxy_marshal_set_param,
+};
+
+static const struct pw_session_methods pw_protocol_native_session_server_method_marshal = {
+ PW_VERSION_SESSION_METHODS,
+ .add_listener = session_resource_marshal_add_listener,
+ .subscribe_params = session_resource_marshal_subscribe_params,
+ .enum_params = session_resource_marshal_enum_params,
+ .set_param = session_resource_marshal_set_param,
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_session_client_method_demarshal[PW_SESSION_METHOD_NUM] =
+{
+ [PW_SESSION_METHOD_ADD_LISTENER] = { demarshal_add_listener_enotsup, 0 },
+ [PW_SESSION_METHOD_SUBSCRIBE_PARAMS] = { session_proxy_demarshal_subscribe_params, 0 },
+ [PW_SESSION_METHOD_ENUM_PARAMS] = { session_proxy_demarshal_enum_params, 0 },
+ [PW_SESSION_METHOD_SET_PARAM] = { session_proxy_demarshal_set_param, PW_PERM_W },
+};
+
+static const struct pw_protocol_native_demarshal
+pw_protocol_native_session_server_method_demarshal[PW_SESSION_METHOD_NUM] =
+{
+ [PW_SESSION_METHOD_ADD_LISTENER] = { demarshal_add_listener_enotsup, 0 },
+ [PW_SESSION_METHOD_SUBSCRIBE_PARAMS] = { session_resource_demarshal_subscribe_params, 0 },
+ [PW_SESSION_METHOD_ENUM_PARAMS] = { session_resource_demarshal_enum_params, 0 },
+ [PW_SESSION_METHOD_SET_PARAM] = { session_resource_demarshal_set_param, PW_PERM_W },
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_session_marshal = {
+ PW_TYPE_INTERFACE_Session,
+ PW_VERSION_SESSION,
+ 0,
+ PW_SESSION_METHOD_NUM,
+ PW_SESSION_EVENT_NUM,
+ .client_marshal = &pw_protocol_native_session_client_method_marshal,
+ .server_demarshal = pw_protocol_native_session_server_method_demarshal,
+ .server_marshal = &pw_protocol_native_session_server_event_marshal,
+ .client_demarshal = pw_protocol_native_session_client_event_demarshal,
+};
+
+static const struct pw_protocol_marshal pw_protocol_native_session_impl_marshal = {
+ PW_TYPE_INTERFACE_Session,
+ PW_VERSION_SESSION,
+ PW_PROTOCOL_MARSHAL_FLAG_IMPL,
+ PW_SESSION_EVENT_NUM,
+ PW_SESSION_METHOD_NUM,
+ .client_marshal = &pw_protocol_native_session_client_event_marshal,
+ .server_demarshal = pw_protocol_native_session_server_event_demarshal,
+ .server_marshal = &pw_protocol_native_session_server_method_marshal,
+ .client_demarshal = pw_protocol_native_session_client_method_demarshal,
+};
+
+int pw_protocol_native_ext_session_manager_init(struct pw_context *context)
+{
+ struct pw_protocol *protocol;
+
+ protocol = pw_context_find_protocol(context, PW_TYPE_INFO_PROTOCOL_Native);
+ if (protocol == NULL)
+ return -EPROTO;
+
+ /* deprecated */
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_client_endpoint_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_client_session_marshal);
+
+ /* client <-> server */
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_endpoint_link_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_endpoint_stream_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_endpoint_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_session_marshal);
+
+ /* impl <-> server */
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_endpoint_link_impl_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_endpoint_stream_impl_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_endpoint_impl_marshal);
+ pw_protocol_add_marshal(protocol, &pw_protocol_native_session_impl_marshal);
+
+ return 0;
+}
diff --git a/src/modules/module-session-manager/proxy-session-manager.c b/src/modules/module-session-manager/proxy-session-manager.c
new file mode 100644
index 0000000..cd418e1
--- /dev/null
+++ b/src/modules/module-session-manager/proxy-session-manager.c
@@ -0,0 +1,188 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "pipewire/pipewire.h"
+#include "pipewire/extensions/session-manager.h"
+
+struct object_data {
+ struct spa_hook object_listener;
+ struct spa_hook object_methods;
+ struct spa_hook proxy_listener;
+};
+
+static void proxy_object_destroy(void *_data)
+{
+ struct object_data *data = _data;
+ spa_hook_remove(&data->object_listener);
+}
+
+static const struct pw_proxy_events proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .destroy = proxy_object_destroy,
+};
+
+struct pw_proxy *pw_core_endpoint_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size)
+{
+ struct pw_endpoint *endpoint = object;
+ struct spa_interface *remote_iface, *local_iface;
+ struct pw_proxy *proxy;
+ struct object_data *data;
+
+ proxy = pw_core_create_object(core,
+ "endpoint",
+ PW_TYPE_INTERFACE_Endpoint,
+ PW_VERSION_ENDPOINT,
+ props,
+ user_data_size + sizeof(struct object_data));
+ if (proxy == NULL)
+ return NULL;
+
+ data = pw_proxy_get_user_data(proxy);
+ data = SPA_PTROFF(data, user_data_size, struct object_data);
+
+ remote_iface = (struct spa_interface*)proxy;
+ local_iface = (struct spa_interface*)endpoint;
+
+ pw_proxy_install_marshal(proxy, true);
+
+ pw_proxy_add_listener(proxy, &data->proxy_listener, &proxy_events, data);
+
+ pw_proxy_add_object_listener(proxy, &data->object_methods,
+ local_iface->cb.funcs, local_iface->cb.data);
+ pw_endpoint_add_listener(endpoint, &data->object_listener,
+ remote_iface->cb.funcs, remote_iface->cb.data);
+
+ return proxy;
+}
+
+struct pw_proxy *pw_core_endpoint_stream_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size)
+{
+ struct pw_endpoint_stream *endpoint_stream = object;
+ struct spa_interface *remote_iface, *local_iface;
+ struct pw_proxy *proxy;
+ struct object_data *data;
+
+ proxy = pw_core_create_object(core,
+ "endpoint-stream",
+ PW_TYPE_INTERFACE_EndpointStream,
+ PW_VERSION_ENDPOINT_STREAM,
+ props,
+ user_data_size + sizeof(struct object_data));
+ if (proxy == NULL)
+ return NULL;
+
+ data = pw_proxy_get_user_data(proxy);
+ data = SPA_PTROFF(data, user_data_size, struct object_data);
+
+ remote_iface = (struct spa_interface*)proxy;
+ local_iface = (struct spa_interface*)endpoint_stream;
+
+ pw_proxy_install_marshal(proxy, true);
+
+ pw_proxy_add_listener(proxy, &data->proxy_listener, &proxy_events, data);
+
+ pw_proxy_add_object_listener(proxy, &data->object_methods,
+ local_iface->cb.funcs, local_iface->cb.data);
+ pw_endpoint_stream_add_listener(endpoint_stream, &data->object_listener,
+ remote_iface->cb.funcs, remote_iface->cb.data);
+
+ return proxy;
+}
+
+struct pw_proxy *pw_core_endpoint_link_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size)
+{
+ struct pw_endpoint_link *endpoint_link = object;
+ struct spa_interface *remote_iface, *local_iface;
+ struct pw_proxy *proxy;
+ struct object_data *data;
+
+ proxy = pw_core_create_object(core,
+ "endpoint-link",
+ PW_TYPE_INTERFACE_EndpointLink,
+ PW_VERSION_ENDPOINT_LINK,
+ props,
+ user_data_size + sizeof(struct object_data));
+ if (proxy == NULL)
+ return NULL;
+
+ data = pw_proxy_get_user_data(proxy);
+ data = SPA_PTROFF(data, user_data_size, struct object_data);
+
+ remote_iface = (struct spa_interface*)proxy;
+ local_iface = (struct spa_interface*)endpoint_link;
+
+ pw_proxy_install_marshal(proxy, true);
+
+ pw_proxy_add_listener(proxy, &data->proxy_listener, &proxy_events, data);
+
+ pw_proxy_add_object_listener(proxy, &data->object_methods,
+ local_iface->cb.funcs, local_iface->cb.data);
+ pw_endpoint_link_add_listener(endpoint_link, &data->object_listener,
+ remote_iface->cb.funcs, remote_iface->cb.data);
+
+ return proxy;
+}
+
+struct pw_proxy *pw_core_session_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size)
+{
+ struct pw_session *session = object;
+ struct spa_interface *remote_iface, *local_iface;
+ struct pw_proxy *proxy;
+ struct object_data *data;
+
+ proxy = pw_core_create_object(core,
+ "session",
+ PW_TYPE_INTERFACE_Session,
+ PW_VERSION_SESSION,
+ props,
+ user_data_size + sizeof(struct object_data));
+ if (proxy == NULL)
+ return NULL;
+
+ data = pw_proxy_get_user_data(proxy);
+ data = SPA_PTROFF(data, user_data_size, struct object_data);
+
+ remote_iface = (struct spa_interface*)proxy;
+ local_iface = (struct spa_interface*)session;
+
+ pw_proxy_install_marshal(proxy, true);
+
+ pw_proxy_add_listener(proxy, &data->proxy_listener, &proxy_events, data);
+
+ pw_proxy_add_object_listener(proxy, &data->object_methods,
+ local_iface->cb.funcs, local_iface->cb.data);
+ pw_session_add_listener(session, &data->object_listener,
+ remote_iface->cb.funcs, remote_iface->cb.data);
+
+ return proxy;
+}
diff --git a/src/modules/module-session-manager/session.c b/src/modules/module-session-manager/session.c
new file mode 100644
index 0000000..e3d7210
--- /dev/null
+++ b/src/modules/module-session-manager/session.c
@@ -0,0 +1,578 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/session-manager.h>
+#include <pipewire/extensions/session-manager/introspect-funcs.h>
+
+#include <spa/utils/result.h>
+#include <spa/pod/builder.h>
+#include <spa/pod/filter.h>
+
+#define MAX_PARAMS 32
+
+#define NAME "session"
+
+struct pw_proxy *pw_core_session_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size);
+
+struct impl
+{
+ struct pw_global *global;
+ struct spa_hook global_listener;
+
+ union {
+ struct pw_session *session;
+ struct pw_resource *resource;
+ };
+ struct spa_hook resource_listener;
+ struct spa_hook session_listener;
+
+ struct pw_session_info *cached_info;
+ struct spa_list cached_params;
+
+ int ping_seq;
+ bool registered;
+};
+
+struct param_data
+{
+ struct spa_list link;
+ uint32_t id;
+ struct pw_array params;
+};
+
+struct resource_data
+{
+ struct impl *impl;
+
+ struct pw_resource *resource;
+ struct spa_hook object_listener;
+
+ uint32_t n_subscribe_ids;
+ uint32_t subscribe_ids[32];
+};
+
+struct factory_data
+{
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_impl_factory *factory;
+ struct spa_hook factory_listener;
+
+ struct pw_export_type export;
+};
+
+#define pw_session_resource(r,m,v,...) \
+ pw_resource_call(r,struct pw_session_events,m,v,__VA_ARGS__)
+
+#define pw_session_resource_info(r,...) \
+ pw_session_resource(r,info,0,__VA_ARGS__)
+#define pw_session_resource_param(r,...) \
+ pw_session_resource(r,param,0,__VA_ARGS__)
+
+static int method_enum_params(void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ struct param_data *pdata;
+ struct spa_pod *result;
+ struct spa_pod *param;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = { 0 };
+ uint32_t index;
+ uint32_t next = start;
+ uint32_t count = 0;
+
+ pw_log_debug(NAME" %p: param %u %d/%d", impl, id, start, num);
+
+ spa_list_for_each(pdata, &impl->cached_params, link) {
+ if (pdata->id != id)
+ continue;
+
+ while (true) {
+ index = next++;
+ if (index >= pw_array_get_len(&pdata->params, void*))
+ return 0;
+
+ param = *pw_array_get_unchecked(&pdata->params, index, struct spa_pod*);
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ if (spa_pod_filter(&b, &result, param, filter) != 0)
+ continue;
+
+ pw_log_debug(NAME" %p: %d param %u", impl, seq, index);
+
+ pw_session_resource_param(d->resource, seq, id, index, next, result);
+
+ if (++count == num)
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+static int method_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ uint32_t i;
+
+ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(d->subscribe_ids));
+ d->n_subscribe_ids = n_ids;
+
+ for (i = 0; i < n_ids; i++) {
+ d->subscribe_ids[i] = ids[i];
+ pw_log_debug(NAME" %p: resource %d subscribe param %u",
+ impl, pw_resource_get_id(d->resource), ids[i]);
+ method_enum_params(object, 1, ids[i], 0, UINT32_MAX, NULL);
+ }
+ return 0;
+}
+
+static int method_set_param(void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct resource_data *d = object;
+ struct impl *impl = d->impl;
+ /* store only on the implementation; our cache will be updated
+ by the param event, since we are subscribed */
+ pw_session_set_param(impl->session, id, flags, param);
+ return 0;
+}
+
+static const struct pw_session_methods session_methods = {
+ PW_VERSION_SESSION_METHODS,
+ .subscribe_params = method_subscribe_params,
+ .enum_params = method_enum_params,
+ .set_param = method_set_param,
+};
+
+static int global_bind(void *object, struct pw_impl_client *client,
+ uint32_t permissions, uint32_t version, uint32_t id)
+{
+ struct impl *impl = object;
+ struct pw_resource *resource;
+ struct resource_data *data;
+
+ resource = pw_resource_new(client, id, permissions,
+ PW_TYPE_INTERFACE_Session,
+ version, sizeof(*data));
+ if (resource == NULL)
+ return -errno;
+
+ data = pw_resource_get_user_data(resource);
+ data->impl = impl;
+ data->resource = resource;
+
+ pw_global_add_resource(impl->global, resource);
+
+ /* resource methods -> implementation */
+ pw_resource_add_object_listener(resource,
+ &data->object_listener,
+ &session_methods, data);
+
+ impl->cached_info->change_mask = PW_SESSION_CHANGE_MASK_ALL;
+ pw_session_resource_info(resource, impl->cached_info);
+ impl->cached_info->change_mask = 0;
+
+ return 0;
+}
+
+static void global_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->global_listener);
+ impl->global = NULL;
+ if (impl->resource)
+ pw_resource_destroy(impl->resource);
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+static void impl_resource_destroy(void *data)
+{
+ struct impl *impl = data;
+ struct param_data *pdata, *tmp;
+
+ spa_hook_remove(&impl->resource_listener);
+ impl->resource = NULL;
+
+ /* clear cache */
+ if (impl->cached_info)
+ pw_session_info_free(impl->cached_info);
+ spa_list_for_each_safe(pdata, tmp, &impl->cached_params, link) {
+ struct spa_pod **pod;
+ pw_array_for_each(pod, &pdata->params)
+ free(*pod);
+ pw_array_clear(&pdata->params);
+ spa_list_remove(&pdata->link);
+ free(pdata);
+ }
+
+ if (impl->global)
+ pw_global_destroy(impl->global);
+}
+
+static void register_global(struct impl *impl)
+{
+ impl->cached_info->id = pw_global_get_id (impl->global);
+ pw_resource_set_bound_id(impl->resource, impl->cached_info->id);
+ pw_global_register(impl->global);
+ impl->registered = true;
+}
+
+static void impl_resource_pong (void *data, int seq)
+{
+ struct impl *impl = data;
+
+ /* complete registration, if this was the initial sync */
+ if (!impl->registered && seq == impl->ping_seq) {
+ register_global(impl);
+ }
+}
+
+static const struct pw_resource_events impl_resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = impl_resource_destroy,
+ .pong = impl_resource_pong,
+};
+
+static int emit_info(void *data, struct pw_resource *resource)
+{
+ const struct pw_session_info *info = data;
+ pw_session_resource_info(resource, info);
+ return 0;
+}
+
+static void event_info(void *data, const struct pw_session_info *info)
+{
+ struct impl *impl = data;
+ uint32_t changed_ids[MAX_PARAMS], n_changed_ids = 0;
+ uint32_t i;
+
+ /* figure out changes to params */
+ if (info->change_mask & PW_SESSION_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ if ((!impl->cached_info ||
+ info->params[i].flags != impl->cached_info->params[i].flags)
+ && info->params[i].flags & SPA_PARAM_INFO_READ)
+ changed_ids[n_changed_ids++] = info->params[i].id;
+ }
+ }
+
+ /* cache for new clients */
+ impl->cached_info = pw_session_info_update (impl->cached_info, info);
+
+ /* notify existing clients */
+ pw_global_for_each_resource(impl->global, emit_info, (void*) info);
+
+ /* cache params & register */
+ if (n_changed_ids > 0) {
+ /* prepare params storage */
+ for (i = 0; i < n_changed_ids; i++) {
+ struct param_data *pdata = calloc(1, sizeof(struct param_data));
+ pdata->id = changed_ids[i];
+ pw_array_init(&pdata->params, sizeof(void*));
+ spa_list_append(&impl->cached_params, &pdata->link);
+ }
+
+ /* subscribe to impl */
+ pw_session_subscribe_params(impl->session, changed_ids, n_changed_ids);
+
+ /* register asynchronously on the pong event */
+ impl->ping_seq = pw_resource_ping(impl->resource, 0);
+ }
+ else if (!impl->registered) {
+ register_global(impl);
+ }
+}
+
+struct param_event_args
+{
+ uint32_t id, index, next;
+ const struct spa_pod *param;
+};
+
+static int emit_param(void *_data, struct pw_resource *resource)
+{
+ struct param_event_args *args = _data;
+ struct resource_data *data;
+ uint32_t i;
+
+ data = pw_resource_get_user_data(resource);
+ for (i = 0; i < data->n_subscribe_ids; i++) {
+ if (data->subscribe_ids[i] == args->id) {
+ pw_session_resource_param(resource, 1,
+ args->id, args->index, args->next, args->param);
+ }
+ }
+ return 0;
+}
+
+static void event_param(void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct impl *impl = data;
+ struct param_data *pdata;
+ struct spa_pod **pod;
+ struct param_event_args args = { id, index, next, param };
+
+ /* cache for new requests */
+ spa_list_for_each(pdata, &impl->cached_params, link) {
+ if (pdata->id != id)
+ continue;
+
+ if (!pw_array_check_index(&pdata->params, index, void*)) {
+ while (pw_array_get_len(&pdata->params, void*) <= index)
+ pw_array_add_ptr(&pdata->params, NULL);
+ }
+
+ pod = pw_array_get_unchecked(&pdata->params, index, struct spa_pod*);
+ free(*pod);
+ *pod = spa_pod_copy(param);
+ }
+
+ /* notify existing clients */
+ pw_global_for_each_resource(impl->global, emit_param, &args);
+}
+
+static const struct pw_session_events session_events = {
+ PW_VERSION_SESSION_EVENTS,
+ .info = event_info,
+ .param = event_param,
+};
+
+static void *session_new(struct pw_context *context,
+ struct pw_resource *resource,
+ struct pw_properties *properties)
+{
+ struct impl *impl;
+ char serial_str[32];
+ struct spa_dict_item items[1] = {
+ SPA_DICT_ITEM_INIT(PW_KEY_OBJECT_SERIAL, serial_str),
+ };
+ struct spa_dict extra_props = SPA_DICT_INIT_ARRAY(items);
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ NULL
+ };
+
+ impl = calloc(1, sizeof(*impl));
+ if (impl == NULL) {
+ pw_properties_free(properties);
+ return NULL;
+ }
+
+ impl->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_Session,
+ PW_VERSION_SESSION,
+ properties,
+ global_bind, impl);
+ if (impl->global == NULL) {
+ free(impl);
+ return NULL;
+ }
+ impl->resource = resource;
+
+ spa_scnprintf(serial_str, sizeof(serial_str), "%"PRIu64,
+ pw_global_get_serial(impl->global));
+ pw_global_update_keys(impl->global, &extra_props, keys);
+
+ spa_list_init(&impl->cached_params);
+
+ /* handle destroy events */
+ pw_global_add_listener(impl->global,
+ &impl->global_listener,
+ &global_events, impl);
+ pw_resource_add_listener(impl->resource,
+ &impl->resource_listener,
+ &impl_resource_events, impl);
+
+ /* handle implementation events -> cache + client resources */
+ pw_session_add_listener(impl->session,
+ &impl->session_listener,
+ &session_events, impl);
+
+ /* global is not registered here on purpose;
+ we first cache info + params and then expose the global */
+
+ return impl;
+}
+
+static void *create_object(void *data,
+ struct pw_resource *resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id)
+{
+ struct factory_data *d = data;
+ struct pw_resource *impl_resource;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ void *result;
+ int res;
+
+ impl_resource = pw_resource_new(client, new_id, PW_PERM_ALL, type, version, 0);
+ if (impl_resource == NULL) {
+ res = -errno;
+ goto error_resource;
+ }
+
+ pw_resource_install_marshal(impl_resource, true);
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL) {
+ res = -ENOMEM;
+ goto error_session;
+ }
+
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d",
+ pw_impl_client_get_info(client)->id);
+ pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d",
+ pw_impl_factory_get_info(d->factory)->id);
+
+ result = session_new(pw_impl_client_get_context(client), impl_resource, properties);
+ if (result == NULL) {
+ res = -errno;
+ goto error_session;
+ }
+ return result;
+
+error_resource:
+ pw_log_error("can't create resource: %s", spa_strerror(res));
+ pw_resource_errorf_id(resource, new_id, res, "can't create resource: %s", spa_strerror(res));
+ goto error_exit;
+error_session:
+ pw_log_error("can't create session: %s", spa_strerror(res));
+ pw_resource_errorf_id(resource, new_id, res, "can't create session: %s", spa_strerror(res));
+ goto error_exit_free;
+
+error_exit_free:
+ pw_resource_remove(impl_resource);
+error_exit:
+ errno = -res;
+ return NULL;
+}
+
+static const struct pw_impl_factory_implementation impl_factory = {
+ PW_VERSION_IMPL_FACTORY_IMPLEMENTATION,
+ .create_object = create_object,
+};
+
+static void factory_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->factory_listener);
+ d->factory = NULL;
+ if (d->module)
+ pw_impl_module_destroy(d->module);
+}
+
+static const struct pw_impl_factory_events factory_events = {
+ PW_VERSION_IMPL_FACTORY_EVENTS,
+ .destroy = factory_destroy,
+};
+
+static void module_destroy(void *data)
+{
+ struct factory_data *d = data;
+
+ spa_hook_remove(&d->module_listener);
+ spa_list_remove(&d->export.link);
+ d->module = NULL;
+ if (d->factory)
+ pw_impl_factory_destroy(d->factory);
+}
+
+static void module_registered(void *data)
+{
+ struct factory_data *d = data;
+ struct pw_impl_module *module = d->module;
+ struct pw_impl_factory *factory = d->factory;
+ struct spa_dict_item items[1];
+ char id[16];
+ int res;
+
+ snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id);
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
+ pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
+
+ if ((res = pw_impl_factory_register(factory, NULL)) < 0) {
+ pw_log_error(NAME" %p: can't register factory: %s", factory, spa_strerror(res));
+ }
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+ .registered = module_registered,
+};
+
+int session_factory_init(struct pw_impl_module *module)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_factory *factory;
+ struct factory_data *data;
+ int res;
+
+ factory = pw_context_create_factory(context,
+ "session",
+ PW_TYPE_INTERFACE_Session,
+ PW_VERSION_SESSION,
+ NULL,
+ sizeof(*data));
+ if (factory == NULL)
+ return -errno;
+
+ data = pw_impl_factory_get_user_data(factory);
+ data->factory = factory;
+ data->module = module;
+
+ pw_impl_factory_set_implementation(factory, &impl_factory, data);
+
+ data->export.type = PW_TYPE_INTERFACE_Session;
+ data->export.func = pw_core_session_export;
+ if ((res = pw_context_register_export_type(context, &data->export)) < 0)
+ goto error;
+
+ pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data);
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ return 0;
+error:
+ pw_impl_factory_destroy(data->factory);
+ return res;
+}
diff --git a/src/modules/module-x11-bell.c b/src/modules/module-x11-bell.c
new file mode 100644
index 0000000..908f397
--- /dev/null
+++ b/src/modules/module-x11-bell.c
@@ -0,0 +1,364 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#include <spa/utils/string.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xlib-xcb.h>
+#include <X11/XKBlib.h>
+
+#ifdef HAVE_XFIXES_6
+#include <X11/extensions/Xfixes.h>
+#endif
+
+#include <canberra.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/impl.h>
+
+/** \page page_module_x11_bell PipeWire Module: X11 Bell
+ *
+ * The `x11-bell` module intercept the X11 bell events and uses libcanberra to
+ * play a sound.
+ *
+ * ## Module Options
+ *
+ * - `sink.name = <str>`: node.name of the sink to connect to
+ * - `sample.name = <str>`: the name of the sample to play, default 'bell-window-system'
+ * - `x11.display = <str>`: the X11 display to use
+ * - `x11.xauthority = <str>`: the X11 XAuthority string placed in XAUTHORITY env
+ *
+ * ## General options
+ *
+ * There are no general options for this module.
+ *
+ * ## Example configuration
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-x11-bell }
+ * args = {
+ * #sink.name = @DEFAULT_SINK@
+ * sample.name = "bell-window-system"
+ * #x11.display = ":1"
+ * #x11.xauthority = "test"
+ * ]
+ *\endcode
+ *
+ */
+
+#define NAME "x11-bell"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+struct impl {
+ struct pw_context *context;
+ struct pw_thread_loop *thread_loop;
+ struct pw_loop *thread_loop_loop;
+ struct pw_loop *loop;
+ struct spa_source *source;
+
+ struct pw_properties *properties;
+
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ Display *display;
+};
+
+static int play_sample(struct impl *impl)
+{
+ const char *sample = NULL;
+ ca_context *ca;
+ int res;
+
+ if (impl->properties)
+ sample = pw_properties_get(impl->properties, "sample.name");
+ if (sample == NULL)
+ sample = "bell-window-system";
+
+ pw_log_info("play sample %s", sample);
+
+ if ((res = ca_context_create(&ca)) < 0) {
+ pw_log_error("canberra context create error: %s", ca_strerror(res));
+ res = -EIO;
+ goto exit;
+ }
+ if ((res = ca_context_open(ca)) < 0) {
+ pw_log_error("canberra context open error: %s", ca_strerror(res));
+ res = -EIO;
+ goto exit_destroy;
+ }
+ if ((res = ca_context_play(ca, 0,
+ CA_PROP_EVENT_ID, sample,
+ CA_PROP_MEDIA_NAME, "X11 bell event",
+ CA_PROP_CANBERRA_CACHE_CONTROL, "permanent",
+ NULL)) < 0) {
+ pw_log_warn("can't play sample (%s): %s", sample, ca_strerror(res));
+ res = -EIO;
+ goto exit_destroy;
+ }
+
+exit_destroy:
+ ca_context_destroy(ca);
+exit:
+ return res;
+}
+
+static int do_play_sample(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ play_sample(user_data);
+ return 0;
+}
+
+static void display_io(void *data, int fd, uint32_t mask)
+{
+ struct impl *impl = data;
+ XEvent e;
+
+ while (XPending(impl->display)) {
+ XNextEvent(impl->display, &e);
+
+ if (((XkbEvent*) &e)->any.xkb_type != XkbBellNotify)
+ continue;
+
+ pw_loop_invoke(impl->thread_loop_loop, do_play_sample, 0, NULL, 0, false, impl);
+ }
+}
+
+#ifdef HAVE_XSETIOERROREXITHANDLER
+static void x11_io_error_exit_handler(Display *display, void *data)
+{
+ struct impl *impl = data;
+
+ spa_assert(display == impl->display);
+
+ pw_log_warn("X11 display (%s) has encountered a fatal I/O error", DisplayString(display));
+
+ pw_loop_destroy_source(impl->loop, impl->source);
+ impl->source = NULL;
+
+ pw_impl_module_schedule_destroy(impl->module);
+}
+#endif
+
+static int x11_connect(struct impl *impl, const char *name)
+{
+ int major, minor;
+ unsigned int auto_ctrls, auto_values;
+
+ if (!(impl->display = XOpenDisplay(name))) {
+ pw_log_error("XOpenDisplay() failed");
+ return -EIO;
+ }
+
+ impl->source = pw_loop_add_io(impl->loop,
+ ConnectionNumber(impl->display),
+ SPA_IO_IN, false, display_io, impl);
+ if (!impl->source)
+ return -errno;
+
+#ifdef HAVE_XSETIOERROREXITHANDLER
+ XSetIOErrorExitHandler(impl->display, x11_io_error_exit_handler, impl);
+#endif
+
+#ifdef HAVE_XFIXES_6
+ XFixesSetClientDisconnectMode(impl->display, XFixesClientDisconnectFlagTerminate);
+#endif
+
+ major = XkbMajorVersion;
+ minor = XkbMinorVersion;
+
+ if (!XkbLibraryVersion(&major, &minor)) {
+ pw_log_error("XkbLibraryVersion() failed");
+ return -EIO;
+ }
+
+ major = XkbMajorVersion;
+ minor = XkbMinorVersion;
+
+ if (!XkbQueryExtension(impl->display, NULL, NULL, NULL, &major, &minor)) {
+ pw_log_error("XkbQueryExtension() failed");
+ return -EIO;
+ }
+
+ XkbSelectEvents(impl->display, XkbUseCoreKbd, XkbBellNotifyMask, XkbBellNotifyMask);
+ auto_ctrls = auto_values = XkbAudibleBellMask;
+ XkbSetAutoResetControls(impl->display, XkbAudibleBellMask, &auto_ctrls, &auto_values);
+ XkbChangeEnabledControls(impl->display, XkbUseCoreKbd, XkbAudibleBellMask, 0);
+
+ return 0;
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+
+ if (impl->module)
+ spa_hook_remove(&impl->module_listener);
+
+ if (impl->source)
+ pw_loop_destroy_source(impl->loop, impl->source);
+
+ if (impl->display)
+ XCloseDisplay(impl->display);
+
+ if (impl->thread_loop)
+ pw_thread_loop_destroy(impl->thread_loop);
+
+ pw_properties_free(impl->properties);
+
+ free(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+static const struct spa_dict_item module_x11_bell_info[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "X11 Bell interceptor" },
+ { PW_KEY_MODULE_USAGE, "sink.name=<name for the sink> "
+ "sample.name=<the sample name> "
+ "x11.display=<the X11 display> "
+ "x11.xauthority=<the X11 XAuthority> " },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct impl *impl;
+ const char *name = NULL, *str;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ return -ENOMEM;
+
+ pw_log_debug("module %p: new", impl);
+
+ impl->context = context;
+ impl->loop = pw_context_get_main_loop(context);
+
+ impl->thread_loop = pw_thread_loop_new("X11 Bell", NULL);
+ if (impl->thread_loop == NULL) {
+ res = -errno;
+ pw_log_error("can't create thread loop: %m");
+ goto error;
+ }
+ impl->thread_loop_loop = pw_thread_loop_get_loop(impl->thread_loop);
+ impl->properties = args ? pw_properties_new_string(args) : NULL;
+
+ impl->module = module;
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_x11_bell_info));
+
+ if (impl->properties) {
+ if ((str = pw_properties_get(impl->properties, "x11.xauthority")) != NULL) {
+ if (setenv("XAUTHORITY", str, 1)) {
+ res = -errno;
+ pw_log_error("XAUTHORITY setenv failed: %m");
+ goto error;
+ }
+ }
+ name = pw_properties_get(impl->properties, "x11.display");
+ }
+
+ /* we need to use a thread loop because this module will connect
+ * to pipewire eventually and will then block the mainloop. */
+ pw_thread_loop_start(impl->thread_loop);
+
+ res = x11_connect(impl, name);
+ if (res < 0)
+ goto error;
+
+ return 0;
+error:
+ module_destroy(impl);
+ return res;
+
+}
+
+static int x11_error_handler(Display *display, XErrorEvent *error)
+{
+ pw_log_warn("X11 error handler called on display %s with error %d",
+ DisplayString(display), error->error_code);
+ return 0;
+}
+
+static int x11_io_error_handler(Display *display)
+{
+ pw_log_warn("X11 I/O error handler called on display %s", DisplayString(display));
+ return 0;
+}
+
+__attribute__((constructor))
+static void set_x11_handlers(void)
+{
+ {
+ XErrorHandler prev = XSetErrorHandler(NULL);
+ XErrorHandler def = XSetErrorHandler(x11_error_handler);
+
+ if (prev != def)
+ XSetErrorHandler(prev);
+ }
+
+ {
+ XIOErrorHandler prev = XSetIOErrorHandler(NULL);
+ XIOErrorHandler def = XSetIOErrorHandler(x11_io_error_handler);
+
+ if (prev != def)
+ XSetIOErrorHandler(prev);
+ }
+}
+
+__attribute__((destructor))
+static void restore_x11_handlers(void)
+{
+ {
+ XErrorHandler prev = XSetErrorHandler(NULL);
+ if (prev != x11_error_handler)
+ XSetErrorHandler(prev);
+ }
+
+ {
+ XIOErrorHandler prev = XSetIOErrorHandler(NULL);
+ if (prev != x11_io_error_handler)
+ XSetIOErrorHandler(prev);
+ }
+}
diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c
new file mode 100644
index 0000000..5e7436f
--- /dev/null
+++ b/src/modules/module-zeroconf-discover.c
@@ -0,0 +1,565 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/utils/json.h>
+#include <spa/param/audio/format-utils.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/i18n.h>
+
+#include <avahi-client/lookup.h>
+#include <avahi-common/error.h>
+#include <avahi-common/malloc.h>
+
+#include "module-protocol-pulse/format.h"
+#include "module-zeroconf-discover/avahi-poll.h"
+
+/** \page page_module_zeroconf_discover PipeWire Module: Zeroconf Discover
+ *
+ * Use zeroconf to detect and load module-pulse-tunnel with the right
+ * parameters. This will automatically create sinks and sources to stream
+ * audio to/from remote PulseAudio servers. It also works with
+ * module-protocol-pulse.
+ *
+ * ## Module Options
+ *
+ * - `pulse.latency`: the latency to end-to-end latency in milliseconds to
+ * maintain (Default 200ms).
+ *
+ * ## Example configuration
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-zeroconf-discover
+ * args = { }
+ * }
+ * ]
+ *\endcode
+ */
+
+#define NAME "zeroconf-discover"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define MODULE_USAGE "pulse.latency=<latency in msec> "
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Discover remote streams" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+#define SERVICE_TYPE_SINK "_pulse-sink._tcp"
+#define SERVICE_TYPE_SOURCE "_non-monitor._sub._pulse-source._tcp"
+
+
+struct impl {
+ struct pw_context *context;
+
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_properties *properties;
+
+ AvahiPoll *avahi_poll;
+ AvahiClient *client;
+ AvahiServiceBrowser *sink_browser;
+ AvahiServiceBrowser *source_browser;
+
+ struct spa_list tunnel_list;
+};
+
+struct tunnel_info {
+ AvahiIfIndex interface;
+ AvahiProtocol protocol;
+ const char *name;
+ const char *type;
+ const char *domain;
+};
+
+#define TUNNEL_INFO(...) ((struct tunnel_info){ __VA_ARGS__ })
+
+struct tunnel {
+ struct spa_list link;
+ struct tunnel_info info;
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+};
+
+static int start_client(struct impl *impl);
+
+static struct tunnel *make_tunnel(struct impl *impl, const struct tunnel_info *info)
+{
+ struct tunnel *t;
+
+ t = calloc(1, sizeof(*t));
+ if (t == NULL)
+ return NULL;
+
+ t->info.interface = info->interface;
+ t->info.protocol = info->protocol;
+ t->info.name = strdup(info->name);
+ t->info.type = strdup(info->type);
+ t->info.domain = strdup(info->domain);
+ spa_list_append(&impl->tunnel_list, &t->link);
+
+ return t;
+}
+
+static struct tunnel *find_tunnel(struct impl *impl, const struct tunnel_info *info)
+{
+ struct tunnel *t;
+ spa_list_for_each(t, &impl->tunnel_list, link) {
+ if (t->info.interface == info->interface &&
+ t->info.protocol == info->protocol &&
+ spa_streq(t->info.name, info->name) &&
+ spa_streq(t->info.type, info->type) &&
+ spa_streq(t->info.domain, info->domain))
+ return t;
+ }
+ return NULL;
+}
+
+static void free_tunnel(struct tunnel *t)
+{
+ pw_impl_module_destroy(t->module);
+}
+
+static void impl_free(struct impl *impl)
+{
+ struct tunnel *t;
+
+ spa_list_consume(t, &impl->tunnel_list, link)
+ free_tunnel(t);
+
+ if (impl->sink_browser)
+ avahi_service_browser_free(impl->sink_browser);
+ if (impl->source_browser)
+ avahi_service_browser_free(impl->source_browser);
+ if (impl->client)
+ avahi_client_free(impl->client);
+ if (impl->avahi_poll)
+ pw_avahi_poll_free(impl->avahi_poll);
+ pw_properties_free(impl->properties);
+ free(impl);
+}
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->module_listener);
+ impl_free(impl);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+static void pw_properties_from_avahi_string(const char *key, const char *value,
+ struct pw_properties *props)
+{
+ if (spa_streq(key, "device")) {
+ pw_properties_set(props, PW_KEY_TARGET_OBJECT, value);
+ }
+ else if (spa_streq(key, "rate")) {
+ pw_properties_set(props, PW_KEY_AUDIO_RATE, value);
+ }
+ else if (spa_streq(key, "channels")) {
+ pw_properties_set(props, PW_KEY_AUDIO_CHANNELS, value);
+ }
+ else if (spa_streq(key, "channel_map")) {
+ struct channel_map channel_map;
+ uint32_t i, pos[CHANNELS_MAX];
+ char *p, *s;
+
+ spa_zero(channel_map);
+ channel_map_parse(value, &channel_map);
+ channel_map_to_positions(&channel_map, pos);
+
+ p = s = alloca(4 + channel_map.channels * 8);
+ p += spa_scnprintf(p, 2, "[");
+ for (i = 0; i < channel_map.channels; i++)
+ p += spa_scnprintf(p, 8, "%s%s", i == 0 ? "" : ",",
+ channel_id2name(pos[i]));
+ p += spa_scnprintf(p, 2, "]");
+ pw_properties_set(props, SPA_KEY_AUDIO_POSITION, s);
+ }
+ else if (spa_streq(key, "format")) {
+ uint32_t fmt = format_paname2id(value, strlen(value));
+ if (fmt != SPA_AUDIO_FORMAT_UNKNOWN)
+ pw_properties_set(props, PW_KEY_AUDIO_FORMAT, format_id2name(fmt));
+ }
+ else if (spa_streq(key, "icon-name")) {
+ pw_properties_set(props, PW_KEY_DEVICE_ICON_NAME, value);
+ }
+ else if (spa_streq(key, "product-name")) {
+ pw_properties_set(props, PW_KEY_DEVICE_PRODUCT_NAME, value);
+ }
+ else if (spa_streq(key, "description")) {
+ pw_properties_set(props, "tunnel.remote.description", value);
+ }
+ else if (spa_streq(key, "fqdn")) {
+ pw_properties_set(props, "tunnel.remote.fqdn", value);
+ }
+ else if (spa_streq(key, "user-name")) {
+ pw_properties_set(props, "tunnel.remote.user", value);
+ }
+}
+
+static void submodule_destroy(void *data)
+{
+ struct tunnel *t = data;
+
+ spa_list_remove(&t->link);
+ spa_hook_remove(&t->module_listener);
+
+ free((char *) t->info.name);
+ free((char *) t->info.type);
+ free((char *) t->info.domain);
+
+ free(t);
+}
+
+static const struct pw_impl_module_events submodule_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = submodule_destroy,
+};
+
+static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiProtocol protocol,
+ AvahiResolverEvent event, const char *name, const char *type, const char *domain,
+ const char *host_name, const AvahiAddress *a, uint16_t port, AvahiStringList *txt,
+ AvahiLookupResultFlags flags, void *userdata)
+{
+ struct impl *impl = userdata;
+ struct tunnel *t;
+ struct tunnel_info tinfo;
+ const char *str, *device, *desc, *fqdn, *user;
+ char if_suffix[16] = "";
+ char at[AVAHI_ADDRESS_STR_MAX];
+ AvahiStringList *l;
+ FILE *f;
+ char *args;
+ size_t size;
+ struct pw_impl_module *mod;
+ struct pw_properties *props = NULL;
+
+ if (event != AVAHI_RESOLVER_FOUND) {
+ pw_log_error("Resolving of '%s' failed: %s", name,
+ avahi_strerror(avahi_client_errno(impl->client)));
+ goto done;
+ }
+ tinfo = TUNNEL_INFO(.interface = interface,
+ .protocol = protocol,
+ .name = name,
+ .type = type,
+ .domain = domain);
+
+ props = pw_properties_new(NULL, NULL);
+ if (props == NULL) {
+ pw_log_error("Can't allocate properties: %m");
+ goto done;
+ }
+
+ for (l = txt; l; l = l->next) {
+ char *key, *value;
+
+ if (avahi_string_list_get_pair(l, &key, &value, NULL) != 0)
+ break;
+
+ pw_properties_from_avahi_string(key, value, props);
+ avahi_free(key);
+ avahi_free(value);
+ }
+
+ if ((device = pw_properties_get(props, PW_KEY_TARGET_OBJECT)) != NULL)
+ pw_properties_setf(props, PW_KEY_NODE_NAME,
+ "tunnel.%s.%s", host_name, device);
+ else
+ pw_properties_setf(props, PW_KEY_NODE_NAME,
+ "tunnel.%s", host_name);
+
+ str = strstr(type, "sink") ? "sink" : "source";
+ pw_properties_set(props, "tunnel.mode", str);
+
+ if (a->proto == AVAHI_PROTO_INET6 &&
+ a->data.ipv6.address[0] == 0xfe &&
+ (a->data.ipv6.address[1] & 0xc0) == 0x80)
+ snprintf(if_suffix, sizeof(if_suffix), "%%%d", interface);
+
+ pw_properties_setf(props, "pulse.server.address", " [%s%s]:%u",
+ avahi_address_snprint(at, sizeof(at), a),
+ if_suffix, port);
+
+ desc = pw_properties_get(props, "tunnel.remote.description");
+ if (desc == NULL)
+ desc = pw_properties_get(props, PW_KEY_DEVICE_PRODUCT_NAME);
+ if (desc == NULL)
+ desc = pw_properties_get(props, PW_KEY_TARGET_OBJECT);
+ if (desc == NULL)
+ desc = _("Unknown device");
+
+ fqdn = pw_properties_get(props, "tunnel.remote.fqdn");
+ if (fqdn == NULL)
+ fqdn = pw_properties_get(props, "pulse.server.address");
+ if (fqdn == NULL)
+ fqdn = host_name;
+
+ user = pw_properties_get(props, "tunnel.remote.user");
+
+ if (desc != NULL && user != NULL && fqdn != NULL) {
+ pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION,
+ _("%s on %s@%s"), desc, user, fqdn);
+ }
+ else if (desc != NULL && fqdn != NULL) {
+ pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION,
+ _("%s on %s"), desc, fqdn);
+ }
+
+ if ((str = pw_properties_get(impl->properties, "pulse.latency")) != NULL)
+ pw_properties_set(props, "pulse.latency", str);
+
+ if ((f = open_memstream(&args, &size)) == NULL) {
+ pw_log_error("Can't open memstream: %m");
+ goto done;
+ }
+
+ fprintf(f, "{");
+ pw_properties_serialize_dict(f, &props->dict, 0);
+ fprintf(f, " stream.props = {");
+ fprintf(f, " }");
+ fprintf(f, "}");
+ fclose(f);
+
+ pw_properties_free(props);
+
+ pw_log_info("loading module args:'%s'", args);
+ mod = pw_context_load_module(impl->context,
+ "libpipewire-module-pulse-tunnel",
+ args, NULL);
+ free(args);
+
+ if (mod == NULL) {
+ pw_log_error("Can't load module: %m");
+ goto done;
+ }
+
+ t = make_tunnel(impl, &tinfo);
+ if (t == NULL) {
+ pw_log_error("Can't make tunnel: %m");
+ pw_impl_module_destroy(mod);
+ goto done;
+ }
+
+ pw_impl_module_add_listener(mod, &t->module_listener, &submodule_events, t);
+
+ t->module = mod;
+
+done:
+ avahi_service_resolver_free(r);
+}
+
+
+static void browser_cb(AvahiServiceBrowser *b, AvahiIfIndex interface, AvahiProtocol protocol,
+ AvahiBrowserEvent event, const char *name, const char *type, const char *domain,
+ AvahiLookupResultFlags flags, void *userdata)
+{
+ struct impl *impl = userdata;
+ struct tunnel_info info;
+ struct tunnel *t;
+
+ if (flags & AVAHI_LOOKUP_RESULT_LOCAL)
+ return;
+
+ info = TUNNEL_INFO(.interface = interface,
+ .protocol = protocol,
+ .name = name,
+ .type = type,
+ .domain = domain);
+
+ t = find_tunnel(impl, &info);
+
+ switch (event) {
+ case AVAHI_BROWSER_NEW:
+ if (t != NULL)
+ return;
+ if (!(avahi_service_resolver_new(impl->client,
+ interface, protocol,
+ name, type, domain,
+ AVAHI_PROTO_UNSPEC, 0,
+ resolver_cb, impl)))
+ pw_log_error("can't make service resolver: %s",
+ avahi_strerror(avahi_client_errno(impl->client)));
+ break;
+ case AVAHI_BROWSER_REMOVE:
+ if (t == NULL)
+ return;
+ free_tunnel(t);
+ break;
+ default:
+ break;
+ }
+}
+
+
+static struct AvahiServiceBrowser *make_browser(struct impl *impl, const char *service_type)
+{
+ struct AvahiServiceBrowser *s;
+
+ s = avahi_service_browser_new(impl->client,
+ AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
+ service_type, NULL, 0,
+ browser_cb, impl);
+ if (s == NULL) {
+ pw_log_error("can't make browser for %s: %s", service_type,
+ avahi_strerror(avahi_client_errno(impl->client)));
+ }
+ return s;
+}
+
+static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata)
+{
+ struct impl *impl = userdata;
+
+ impl->client = c;
+
+ switch (state) {
+ case AVAHI_CLIENT_S_REGISTERING:
+ case AVAHI_CLIENT_S_RUNNING:
+ case AVAHI_CLIENT_S_COLLISION:
+ if (impl->sink_browser == NULL)
+ impl->sink_browser = make_browser(impl, SERVICE_TYPE_SINK);
+ if (impl->sink_browser == NULL)
+ goto error;
+
+ if (impl->source_browser == NULL)
+ impl->source_browser = make_browser(impl, SERVICE_TYPE_SOURCE);
+ if (impl->source_browser == NULL)
+ goto error;
+
+ break;
+ case AVAHI_CLIENT_FAILURE:
+ if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED)
+ start_client(impl);
+
+ SPA_FALLTHROUGH;
+ case AVAHI_CLIENT_CONNECTING:
+ if (impl->sink_browser) {
+ avahi_service_browser_free(impl->sink_browser);
+ impl->sink_browser = NULL;
+ }
+ if (impl->source_browser) {
+ avahi_service_browser_free(impl->source_browser);
+ impl->source_browser = NULL;
+ }
+ break;
+ default:
+ break;
+ }
+ return;
+error:
+ pw_impl_module_schedule_destroy(impl->module);
+}
+
+static int start_client(struct impl *impl)
+{
+ int res;
+ if ((impl->client = avahi_client_new(impl->avahi_poll,
+ AVAHI_CLIENT_NO_FAIL,
+ client_callback, impl,
+ &res)) == NULL) {
+ pw_log_error("can't create client: %s", avahi_strerror(res));
+ pw_impl_module_schedule_destroy(impl->module);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int start_avahi(struct impl *impl)
+{
+ struct pw_loop *loop;
+
+ loop = pw_context_get_main_loop(impl->context);
+ impl->avahi_poll = pw_avahi_poll_new(loop);
+
+ return start_client(impl);
+}
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *props;
+ struct impl *impl;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ goto error_errno;
+
+ pw_log_debug("module %p: new %s", impl, args);
+
+ if (args == NULL)
+ args = "";
+
+ props = pw_properties_new_string(args);
+ if (props == NULL)
+ goto error_errno;
+
+ spa_list_init(&impl->tunnel_list);
+
+ impl->module = module;
+ impl->context = context;
+ impl->properties = props;
+
+ pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ start_avahi(impl);
+
+ return 0;
+
+error_errno:
+ res = -errno;
+ if (impl)
+ impl_free(impl);
+ return res;
+}
diff --git a/src/modules/module-zeroconf-discover/avahi-poll.c b/src/modules/module-zeroconf-discover/avahi-poll.c
new file mode 100644
index 0000000..e098646
--- /dev/null
+++ b/src/modules/module-zeroconf-discover/avahi-poll.c
@@ -0,0 +1,201 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/pipewire.h>
+
+#include "avahi-poll.h"
+
+struct impl {
+ AvahiPoll api;
+ struct pw_loop *loop;
+};
+
+struct AvahiWatch {
+ struct impl *impl;
+ struct spa_source *source;
+ AvahiWatchEvent events;
+ AvahiWatchCallback callback;
+ void *userdata;
+ unsigned int dispatching;
+};
+
+struct AvahiTimeout {
+ struct impl *impl;
+ struct spa_source *source;
+ AvahiTimeoutCallback callback;
+ void *userdata;
+};
+
+static AvahiWatchEvent from_pw_events(uint32_t mask)
+{
+ return (mask & SPA_IO_IN ? AVAHI_WATCH_IN : 0) |
+ (mask & SPA_IO_OUT ? AVAHI_WATCH_OUT : 0) |
+ (mask & SPA_IO_ERR ? AVAHI_WATCH_ERR : 0) |
+ (mask & SPA_IO_HUP ? AVAHI_WATCH_HUP : 0);
+}
+
+static uint32_t to_pw_events(AvahiWatchEvent e) {
+ return (e & AVAHI_WATCH_IN ? SPA_IO_IN : 0) |
+ (e & AVAHI_WATCH_OUT ? SPA_IO_OUT : 0) |
+ (e & AVAHI_WATCH_ERR ? SPA_IO_ERR : 0) |
+ (e & AVAHI_WATCH_HUP ? SPA_IO_HUP : 0);
+}
+
+static void watch_callback(void *data, int fd, uint32_t mask)
+{
+ AvahiWatch *w = data;
+
+ w->dispatching += 1;
+
+ w->events = from_pw_events(mask);
+ w->callback(w, fd, w->events, w->userdata);
+ w->events = 0;
+
+ if (--w->dispatching == 0 && !w->source)
+ free(w);
+}
+
+static AvahiWatch* watch_new(const AvahiPoll *api, int fd, AvahiWatchEvent event,
+ AvahiWatchCallback callback, void *userdata)
+{
+ struct impl *impl = api->userdata;
+ AvahiWatch *w;
+
+ w = calloc(1, sizeof(*w));
+ if (w == NULL)
+ return NULL;
+
+ w->impl = impl;
+ w->events = 0;
+ w->callback = callback;
+ w->userdata = userdata;
+ w->source = pw_loop_add_io(impl->loop, fd, to_pw_events(event),
+ false, watch_callback, w);
+
+ return w;
+}
+
+static void watch_update(AvahiWatch *w, AvahiWatchEvent event)
+{
+ struct impl *impl = w->impl;
+ pw_loop_update_io(impl->loop, w->source, to_pw_events(event));
+}
+
+static AvahiWatchEvent watch_get_events(AvahiWatch *w)
+{
+ return w->events;
+}
+
+static void watch_free(AvahiWatch *w)
+{
+ pw_loop_destroy_source(w->impl->loop, w->source);
+ w->source = NULL;
+
+ if (!w->dispatching)
+ free(w);
+}
+
+static void timeout_callback(void *data, uint64_t expirations)
+{
+ AvahiTimeout *w = data;
+ w->callback(w, w->userdata);
+}
+
+static AvahiTimeout* timeout_new(const AvahiPoll *api, const struct timeval *tv,
+ AvahiTimeoutCallback callback, void *userdata)
+{
+ struct impl *impl = api->userdata;
+ struct timespec value;
+ AvahiTimeout *w;
+
+ w = calloc(1, sizeof(*w));
+ if (w == NULL)
+ return NULL;
+
+ w->impl = impl;
+ w->callback = callback;
+ w->userdata = userdata;
+ w->source = pw_loop_add_timer(impl->loop, timeout_callback, w);
+
+ if (tv != NULL) {
+ value.tv_sec = tv->tv_sec;
+ value.tv_nsec = tv->tv_usec * 1000UL;
+ pw_loop_update_timer(impl->loop, w->source, &value, NULL, true);
+ }
+ return w;
+}
+
+static void timeout_update(AvahiTimeout *t, const struct timeval *tv)
+{
+ struct impl *impl = t->impl;
+ struct timespec value, *v = NULL;
+
+ if (tv != NULL) {
+ value.tv_sec = tv->tv_sec;
+ value.tv_nsec = tv->tv_usec * 1000UL;
+ if (value.tv_sec == 0 && value.tv_nsec == 0)
+ value.tv_nsec = 1;
+ v = &value;
+ }
+ pw_loop_update_timer(impl->loop, t->source, v, NULL, true);
+}
+
+static void timeout_free(AvahiTimeout *t)
+{
+ struct impl *impl = t->impl;
+ pw_loop_destroy_source(impl->loop, t->source);
+ free(t);
+}
+
+static const AvahiPoll avahi_poll_api = {
+ .watch_new = watch_new,
+ .watch_update = watch_update,
+ .watch_get_events = watch_get_events,
+ .watch_free = watch_free,
+ .timeout_new = timeout_new,
+ .timeout_update = timeout_update,
+ .timeout_free = timeout_free,
+};
+
+AvahiPoll* pw_avahi_poll_new(struct pw_loop *loop)
+{
+ struct impl *impl;
+
+ impl = calloc(1, sizeof(*impl));
+ if (impl == NULL)
+ return NULL;
+
+ impl->loop = loop;
+ impl->api = avahi_poll_api;
+ impl->api.userdata = impl;
+
+ return &impl->api;
+}
+
+void pw_avahi_poll_free(AvahiPoll *p)
+{
+ struct impl *impl = SPA_CONTAINER_OF(p, struct impl, api);
+
+ free(impl);
+}
diff --git a/src/modules/module-zeroconf-discover/avahi-poll.h b/src/modules/module-zeroconf-discover/avahi-poll.h
new file mode 100644
index 0000000..04b785d
--- /dev/null
+++ b/src/modules/module-zeroconf-discover/avahi-poll.h
@@ -0,0 +1,31 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <avahi-client/client.h>
+
+#include <pipewire/loop.h>
+
+AvahiPoll* pw_avahi_poll_new(struct pw_loop *loop);
+
+void pw_avahi_poll_free(AvahiPoll *p);
diff --git a/src/modules/spa/meson.build b/src/modules/spa/meson.build
new file mode 100644
index 0000000..8332910
--- /dev/null
+++ b/src/modules/spa/meson.build
@@ -0,0 +1,31 @@
+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/module-device-factory.c b/src/modules/spa/module-device-factory.c
new file mode 100644
index 0000000..fd712a2
--- /dev/null
+++ b/src/modules/spa/module-device-factory.c
@@ -0,0 +1,281 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+
+#include "pipewire/impl.h"
+
+#include "spa-device.h"
+
+#define NAME "spa-device-factory"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define FACTORY_USAGE SPA_KEY_FACTORY_NAME"=<factory-name> " \
+ "["SPA_KEY_LIBRARY_NAME"=<library-name>]"
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Provide a factory to make SPA devices" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct factory_data {
+ struct pw_context *context;
+
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_impl_factory *factory;
+ struct spa_hook factory_listener;
+
+ struct spa_list device_list;
+};
+
+struct device_data {
+ struct spa_list link;
+ struct pw_impl_device *device;
+ struct spa_hook device_listener;
+ struct spa_hook resource_listener;
+};
+
+static void resource_destroy(void *data)
+{
+ struct device_data *nd = data;
+ pw_log_debug("device %p", nd);
+ spa_hook_remove(&nd->resource_listener);
+ if (nd->device)
+ pw_impl_device_destroy(nd->device);
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = resource_destroy
+};
+
+static void device_destroy(void *data)
+{
+ struct device_data *nd = data;
+ spa_list_remove(&nd->link);
+ spa_hook_remove(&nd->device_listener);
+ nd->device = NULL;
+}
+
+static const struct pw_impl_device_events device_events = {
+ PW_VERSION_IMPL_DEVICE_EVENTS,
+ .destroy = device_destroy,
+};
+
+static void *create_object(void *_data,
+ struct pw_resource *resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id)
+{
+ struct factory_data *data = _data;
+ struct pw_context *context = data->context;
+ struct pw_impl_device *device;
+ const char *str;
+ char *factory_name = NULL;
+ struct device_data *nd;
+ struct pw_impl_client *client;
+ int res;
+
+ if (properties == NULL)
+ goto error_properties;
+
+ if ((str = pw_properties_get(properties, SPA_KEY_FACTORY_NAME)) == NULL)
+ goto error_properties;
+
+ if ((factory_name = strdup(str)) == NULL)
+ goto error_properties;
+
+ pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d",
+ pw_global_get_id(pw_impl_factory_get_global(data->factory)));
+
+ client = resource ? pw_resource_get_client(resource) : NULL;
+
+ if (client) {
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d",
+ pw_global_get_id(pw_impl_client_get_global(client)));
+ }
+
+ device = pw_spa_device_load(context,
+ factory_name,
+ 0,
+ properties,
+ sizeof(struct device_data));
+ if (device == NULL) {
+ res = -errno;
+ goto error_device;
+ }
+
+ nd = pw_spa_device_get_user_data(device);
+ nd->device = device;
+ spa_list_append(&data->device_list, &nd->link);
+
+ pw_impl_device_add_listener(device, &nd->device_listener, &device_events, nd);
+
+ if (client) {
+ struct pw_resource *bound_resource;
+
+ res = pw_global_bind(pw_impl_device_get_global(device),
+ client,
+ PW_PERM_ALL, version,
+ new_id);
+ if (res < 0)
+ goto error_bind;
+
+ if ((bound_resource = pw_impl_client_find_resource(client, new_id)) == NULL)
+ goto error_bind;
+
+ pw_resource_add_listener(bound_resource, &nd->resource_listener, &resource_events, nd);
+ }
+ free(factory_name);
+ return device;
+
+error_properties:
+ res = -EINVAL;
+ pw_resource_errorf_id(resource, new_id, res, "usage: "FACTORY_USAGE);
+ goto error_exit_cleanup;
+error_device:
+ pw_resource_errorf_id(resource, new_id, res,
+ "can't create device %s: %s", factory_name,
+ spa_strerror(res));
+ goto error_exit;
+error_bind:
+ pw_resource_errorf_id(resource, new_id, res, "can't bind device");
+ pw_impl_device_destroy(device);
+ goto error_exit;
+
+error_exit_cleanup:
+ pw_properties_free(properties);
+error_exit:
+ free(factory_name);
+ errno = -res;
+ return NULL;
+}
+
+static const struct pw_impl_factory_implementation factory_impl = {
+ PW_VERSION_IMPL_FACTORY_IMPLEMENTATION,
+ .create_object = create_object,
+};
+
+static void factory_destroy(void *data)
+{
+ struct factory_data *d = data;
+ struct device_data *nd;
+
+ spa_hook_remove(&d->factory_listener);
+
+ spa_list_consume(nd, &d->device_list, link)
+ pw_impl_device_destroy(nd->device);
+
+ d->factory = NULL;
+ if (d->module)
+ pw_impl_module_destroy(d->module);
+}
+
+static const struct pw_impl_factory_events factory_events = {
+ PW_VERSION_IMPL_FACTORY_EVENTS,
+ .destroy = factory_destroy,
+};
+
+static void module_destroy(void *data)
+{
+ struct factory_data *d = data;
+
+ spa_hook_remove(&d->module_listener);
+ d->module = NULL;
+ if (d->factory)
+ pw_impl_factory_destroy(d->factory);
+}
+
+static void module_registered(void *data)
+{
+ struct factory_data *d = data;
+ struct pw_impl_module *module = d->module;
+ struct pw_impl_factory *factory = d->factory;
+ struct spa_dict_item items[1];
+ char id[16];
+ int res;
+
+ snprintf(id, sizeof(id), "%d", pw_global_get_id(pw_impl_module_get_global(module)));
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
+ pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
+
+ if ((res = pw_impl_factory_register(factory, NULL)) < 0) {
+ pw_log_error("%p: can't register factory: %s", factory, spa_strerror(res));
+ }
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+ .registered = module_registered,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_factory *factory;
+ struct factory_data *data;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ factory = pw_context_create_factory(context,
+ "spa-device-factory",
+ PW_TYPE_INTERFACE_Device,
+ PW_VERSION_DEVICE,
+ NULL,
+ sizeof(*data));
+ if (factory == NULL)
+ return -errno;
+
+ data = pw_impl_factory_get_user_data(factory);
+ data->factory = factory;
+ data->module = module;
+ data->context = context;
+ spa_list_init(&data->device_list);
+
+ pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data);
+ pw_impl_factory_set_implementation(factory, &factory_impl, data);
+
+ pw_log_debug("module %p: new", module);
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ return 0;
+}
diff --git a/src/modules/spa/module-device.c b/src/modules/spa/module-device.c
new file mode 100644
index 0000000..7bd6821
--- /dev/null
+++ b/src/modules/spa/module-device.c
@@ -0,0 +1,127 @@
+/* PipeWire
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+
+#include <pipewire/impl.h>
+
+#include "spa-device.h"
+
+#define NAME "spa-device"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define MODULE_USAGE "<factory> [key=value ...]"
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Load and manage an SPA device" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct device_data {
+ struct pw_impl_device *this;
+ struct pw_context *context;
+
+ struct spa_hook module_listener;
+};
+
+static void module_destroy(void *_data)
+{
+ struct device_data *data = _data;
+
+ spa_hook_remove(&data->module_listener);
+
+ pw_impl_device_destroy(data->this);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_properties *props = NULL;
+ char **argv = NULL;
+ int n_tokens;
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_device *device;
+ struct device_data *data;
+ int res;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ if (args == NULL)
+ goto error_arguments;
+
+ argv = pw_split_strv(args, " \t", 2, &n_tokens);
+ if (n_tokens < 1)
+ goto error_arguments;
+
+ if (n_tokens == 2) {
+ props = pw_properties_new_string(argv[1]);
+ if (props == NULL) {
+ res = -errno;
+ goto error_exit_cleanup;
+ }
+ }
+
+ device = pw_spa_device_load(context,
+ argv[0],
+ 0,
+ props,
+ sizeof(struct device_data));
+ if (device == NULL) {
+ res = -errno;
+ goto error_exit_cleanup;
+ }
+
+ pw_free_strv(argv);
+
+ data = pw_spa_device_get_user_data(device);
+ data->this = device;
+ data->context = context;
+
+ pw_log_debug("module %p: new", module);
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+
+error_arguments:
+ res = -EINVAL;
+ pw_log_error("usage: module-spa-device " MODULE_USAGE);
+ goto error_exit_cleanup;
+error_exit_cleanup:
+ pw_free_strv(argv);
+ return res;
+}
diff --git a/src/modules/spa/module-node-factory.c b/src/modules/spa/module-node-factory.c
new file mode 100644
index 0000000..7fb2a42
--- /dev/null
+++ b/src/modules/spa/module-node-factory.c
@@ -0,0 +1,280 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <dlfcn.h>
+
+#include <spa/utils/result.h>
+
+#include "config.h"
+
+#include "pipewire/impl.h"
+
+#include "spa-node.h"
+
+#define NAME "spa-node-factory"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define FACTORY_USAGE SPA_KEY_FACTORY_NAME"=<factory-name> " \
+ "["SPA_KEY_LIBRARY_NAME"=<library-name>]"
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Provide a factory to make SPA nodes" },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct factory_data {
+ struct pw_context *context;
+
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ struct pw_impl_factory *factory;
+ struct spa_hook factory_listener;
+
+ struct spa_list node_list;
+};
+
+struct node_data {
+ struct factory_data *data;
+ struct spa_list link;
+ struct pw_impl_node *node;
+ struct spa_hook node_listener;
+ struct pw_resource *resource;
+ struct spa_hook resource_listener;
+ unsigned int linger:1;
+};
+
+static void resource_destroy(void *data)
+{
+ struct node_data *nd = data;
+ pw_log_debug("node %p", nd);
+ spa_hook_remove(&nd->resource_listener);
+ nd->resource = NULL;
+ if (nd->node && !nd->linger)
+ pw_impl_node_destroy(nd->node);
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = resource_destroy
+};
+
+static void node_destroy(void *data)
+{
+ struct node_data *nd = data;
+ pw_log_debug("node %p", nd);
+ spa_list_remove(&nd->link);
+ spa_hook_remove(&nd->node_listener);
+ nd->node = NULL;
+
+ if (nd->resource) {
+ spa_hook_remove(&nd->resource_listener);
+ nd->resource = NULL;
+ }
+}
+
+static const struct pw_impl_node_events node_events = {
+ PW_VERSION_IMPL_NODE_EVENTS,
+ .destroy = node_destroy,
+};
+
+static void *create_object(void *_data,
+ struct pw_resource *resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id)
+{
+ struct factory_data *data = _data;
+ struct pw_context *context = data->context;
+ struct pw_impl_node *node;
+ const char *factory_name;
+ struct node_data *nd;
+ int res;
+ struct pw_impl_client *client;
+ bool linger;
+
+ if (properties == NULL)
+ goto error_properties;
+
+ factory_name = pw_properties_get(properties, SPA_KEY_FACTORY_NAME);
+ if (factory_name == NULL)
+ goto error_properties;
+
+ pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d",
+ pw_global_get_id(pw_impl_factory_get_global(data->factory)));
+
+ linger = pw_properties_get_bool(properties, PW_KEY_OBJECT_LINGER, false);
+
+ client = resource ? pw_resource_get_client(resource) : NULL;
+ if (client && !linger) {
+ pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d",
+ pw_global_get_id(pw_impl_client_get_global(client)));
+ }
+ node = pw_spa_node_load(context,
+ factory_name,
+ PW_SPA_NODE_FLAG_ACTIVATE,
+ properties,
+ sizeof(struct node_data));
+ if (node == NULL)
+ goto error_create_node;
+
+ nd = pw_spa_node_get_user_data(node);
+ nd->data = data;
+ nd->node = node;
+ nd->linger = linger;
+ spa_list_append(&data->node_list, &nd->link);
+
+ pw_impl_node_add_listener(node, &nd->node_listener, &node_events, nd);
+
+ if (client) {
+ res = pw_global_bind(pw_impl_node_get_global(node),
+ client, PW_PERM_ALL, version, new_id);
+ if (res < 0)
+ goto error_bind;
+
+ if ((nd->resource = pw_impl_client_find_resource(client, new_id)) == NULL)
+ goto error_bind;
+
+ pw_resource_add_listener(nd->resource, &nd->resource_listener, &resource_events, nd);
+ }
+ return node;
+
+error_properties:
+ res = -EINVAL;
+ pw_resource_errorf_id(resource, new_id, res, "usage: "FACTORY_USAGE);
+ goto error_exit_cleanup;
+error_create_node:
+ res = -errno;
+ pw_resource_errorf_id(resource, new_id, res,
+ "can't create node: %s", spa_strerror(res));
+ goto error_exit;
+error_bind:
+ pw_resource_errorf_id(resource, new_id, res, "can't bind node");
+ pw_impl_node_destroy(node);
+ goto error_exit;
+
+error_exit_cleanup:
+ pw_properties_free(properties);
+error_exit:
+ errno = -res;
+ return NULL;
+}
+
+static const struct pw_impl_factory_implementation factory_impl = {
+ PW_VERSION_IMPL_FACTORY_IMPLEMENTATION,
+ .create_object = create_object,
+};
+
+static void factory_destroy(void *data)
+{
+ struct factory_data *d = data;
+ struct node_data *nd;
+
+ spa_hook_remove(&d->factory_listener);
+ spa_list_consume(nd, &d->node_list, link)
+ pw_impl_node_destroy(nd->node);
+ d->factory = NULL;
+ if (d->module)
+ pw_impl_module_destroy(d->module);
+}
+
+static const struct pw_impl_factory_events factory_events = {
+ PW_VERSION_IMPL_FACTORY_EVENTS,
+ .destroy = factory_destroy,
+};
+
+static void module_destroy(void *data)
+{
+ struct factory_data *d = data;
+ spa_hook_remove(&d->module_listener);
+ d->module = NULL;
+ if (d->factory)
+ pw_impl_factory_destroy(d->factory);
+}
+
+static void module_registered(void *data)
+{
+ struct factory_data *d = data;
+ struct pw_impl_module *module = d->module;
+ struct pw_impl_factory *factory = d->factory;
+ struct spa_dict_item items[1];
+ char id[16];
+ int res;
+
+ snprintf(id, sizeof(id), "%d", pw_global_get_id(pw_impl_module_get_global(module)));
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id);
+ pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1));
+
+ if ((res = pw_impl_factory_register(factory, NULL)) < 0) {
+ pw_log_error("%p: can't register factory: %s", factory, spa_strerror(res));
+ }
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+ .registered = module_registered,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_factory *factory;
+ struct factory_data *data;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ factory = pw_context_create_factory(context,
+ "spa-node-factory",
+ PW_TYPE_INTERFACE_Node,
+ PW_VERSION_NODE,
+ NULL,
+ sizeof(*data));
+ if (factory == NULL)
+ return -errno;
+
+ data = pw_impl_factory_get_user_data(factory);
+ data->factory = factory;
+ data->context = context;
+ data->module = module;
+ spa_list_init(&data->node_list);
+
+ pw_impl_factory_add_listener(factory, &data->factory_listener, &factory_events, data);
+ pw_impl_factory_set_implementation(factory, &factory_impl, data);
+
+ pw_log_debug("module %p: new", module);
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+}
diff --git a/src/modules/spa/module-node.c b/src/modules/spa/module-node.c
new file mode 100644
index 0000000..9f66d2b
--- /dev/null
+++ b/src/modules/spa/module-node.c
@@ -0,0 +1,129 @@
+/* PipeWire
+ * Copyright © 2016 Axis Communications <dev-gstreamer@axis.com>
+ * @author Linus Svensson <linus.svensson@axis.com>
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <getopt.h>
+#include <limits.h>
+
+#include <pipewire/impl.h>
+
+#include "spa-node.h"
+
+#define NAME "spa-node"
+
+PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
+#define PW_LOG_TOPIC_DEFAULT mod_topic
+
+#define MODULE_USAGE "<factory> [key=value ...]"
+
+static const struct spa_dict_item module_props[] = {
+ { PW_KEY_MODULE_AUTHOR, "Wim Taymans <wim.taymans@gmail.com>" },
+ { PW_KEY_MODULE_DESCRIPTION, "Load and manage an SPA node" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+struct node_data {
+ struct pw_impl_node *this;
+ struct pw_context *context;
+ struct pw_properties *properties;
+
+ struct spa_hook module_listener;
+};
+
+static void module_destroy(void *_data)
+{
+ struct node_data *data = _data;
+ spa_hook_remove(&data->module_listener);
+ pw_impl_node_destroy(data->this);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy,
+};
+
+SPA_EXPORT
+int pipewire__module_init(struct pw_impl_module *module, const char *args)
+{
+ struct pw_properties *props = NULL;
+ char **argv = NULL;
+ int n_tokens, res;
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_impl_node *node;
+ struct node_data *data;
+
+ PW_LOG_TOPIC_INIT(mod_topic);
+
+ if (args == NULL)
+ goto error_arguments;
+
+ argv = pw_split_strv(args, " \t", 2, &n_tokens);
+ if (n_tokens < 1)
+ goto error_arguments;
+
+ if (n_tokens == 2) {
+ props = pw_properties_new_string(argv[1]);
+ if (props == NULL) {
+ res = -errno;
+ goto error_exit_cleanup;
+ }
+ }
+
+ node = pw_spa_node_load(context,
+ argv[0],
+ PW_SPA_NODE_FLAG_ACTIVATE,
+ props,
+ sizeof(struct node_data));
+
+ if (node == NULL) {
+ res = -errno;
+ goto error_exit_cleanup;
+ }
+
+ pw_free_strv(argv);
+
+ data = pw_spa_node_get_user_data(node);
+ data->this = node;
+ data->context = context;
+ data->properties = props;
+
+ pw_log_debug("module %p: new", module);
+ pw_impl_module_add_listener(module, &data->module_listener, &module_events, data);
+
+ pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props));
+
+ return 0;
+
+error_arguments:
+ res = -EINVAL;
+ pw_log_error("usage: module-spa-node " MODULE_USAGE);
+ goto error_exit_cleanup;
+error_exit_cleanup:
+ pw_free_strv(argv);
+ return res;
+}
diff --git a/src/modules/spa/spa-device.c b/src/modules/spa/spa-device.c
new file mode 100644
index 0000000..936793b
--- /dev/null
+++ b/src/modules/spa/spa-device.c
@@ -0,0 +1,161 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <dlfcn.h>
+
+#include <spa/utils/result.h>
+#include <spa/param/props.h>
+#include <spa/pod/iter.h>
+#include <spa/debug/types.h>
+
+#include "spa-device.h"
+
+struct impl {
+ struct pw_impl_device *this;
+
+ enum pw_spa_device_flags flags;
+
+ void *unload;
+ struct spa_handle *handle;
+ struct spa_device *device;
+
+ struct spa_hook device_listener;
+
+ void *user_data;
+};
+
+static void device_free(void *data)
+{
+ struct impl *impl = data;
+ struct pw_impl_device *device = impl->this;
+
+ pw_log_debug("spa-device %p: free", device);
+
+ spa_hook_remove(&impl->device_listener);
+ if (impl->handle)
+ pw_unload_spa_handle(impl->handle);
+}
+
+static const struct pw_impl_device_events device_events = {
+ PW_VERSION_IMPL_DEVICE_EVENTS,
+ .free = device_free,
+};
+
+struct pw_impl_device *
+pw_spa_device_new(struct pw_context *context,
+ enum pw_spa_device_flags flags,
+ struct spa_device *device,
+ struct spa_handle *handle,
+ struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct pw_impl_device *this;
+ struct impl *impl;
+ int res;
+
+ this = pw_context_create_device(context, properties, sizeof(struct impl) + user_data_size);
+ if (this == NULL)
+ return NULL;
+
+ impl = pw_impl_device_get_user_data(this);
+ impl->this = this;
+ impl->device = device;
+ impl->handle = handle;
+ impl->flags = flags;
+
+ if (user_data_size > 0)
+ impl->user_data = SPA_PTROFF(impl, sizeof(struct impl), void);
+
+ pw_impl_device_add_listener(this, &impl->device_listener, &device_events, impl);
+ pw_impl_device_set_implementation(this, impl->device);
+
+ if (!SPA_FLAG_IS_SET(impl->flags, PW_SPA_DEVICE_FLAG_NO_REGISTER)) {
+ if ((res = pw_impl_device_register(this, NULL)) < 0)
+ goto error_register;
+ }
+ return this;
+
+error_register:
+ pw_impl_device_destroy(this);
+ errno = -res;
+ return NULL;
+}
+
+void *pw_spa_device_get_user_data(struct pw_impl_device *device)
+{
+ struct impl *impl = pw_impl_device_get_user_data(device);
+ return impl->user_data;
+}
+
+struct pw_impl_device *pw_spa_device_load(struct pw_context *context,
+ const char *factory_name,
+ enum pw_spa_device_flags flags,
+ struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct pw_impl_device *this;
+ struct spa_handle *handle;
+ void *iface;
+ int res;
+
+ handle = pw_context_load_spa_handle(context, factory_name,
+ properties ? &properties->dict : NULL);
+ if (handle == NULL)
+ goto error_load;
+
+ if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Device, &iface)) < 0)
+ goto error_interface;
+
+ this = pw_spa_device_new(context, flags,
+ iface, handle, properties, user_data_size);
+ if (this == NULL)
+ goto error_device;
+
+ return this;
+
+error_load:
+ res = -errno;
+ pw_log_debug("can't load device handle %s: %m", factory_name);
+ goto error_exit;
+error_interface:
+ pw_log_debug("can't get device interface %s: %s", factory_name,
+ spa_strerror(res));
+ goto error_exit_unload;
+error_device:
+ properties = NULL;
+ res = -errno;
+ pw_log_debug("can't create device %s: %m", factory_name);
+ goto error_exit_unload;
+
+error_exit_unload:
+ pw_unload_spa_handle(handle);
+error_exit:
+ errno = -res;
+ pw_properties_free(properties);
+ return NULL;
+}
diff --git a/src/modules/spa/spa-device.h b/src/modules/spa/spa-device.h
new file mode 100644
index 0000000..2d23b6f
--- /dev/null
+++ b/src/modules/spa/spa-device.h
@@ -0,0 +1,62 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_SPA_DEVICE_H
+#define PIPEWIRE_SPA_DEVICE_H
+
+#include <spa/monitor/device.h>
+
+#include <pipewire/impl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum pw_spa_device_flags {
+ PW_SPA_DEVICE_FLAG_DISABLE = (1 << 0),
+ PW_SPA_DEVICE_FLAG_NO_REGISTER = (1 << 1),
+};
+
+struct pw_impl_device *
+pw_spa_device_new(struct pw_context *context,
+ enum pw_spa_device_flags flags,
+ struct spa_device *device,
+ struct spa_handle *handle,
+ struct pw_properties *properties,
+ size_t user_data_size);
+
+struct pw_impl_device *
+pw_spa_device_load(struct pw_context *context,
+ const char *factory_name,
+ enum pw_spa_device_flags flags,
+ struct pw_properties *properties,
+ size_t user_data_size);
+
+void *pw_spa_device_get_user_data(struct pw_impl_device *device);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_SPA_DEVICE_H */
diff --git a/src/modules/spa/spa-node.c b/src/modules/spa/spa-node.c
new file mode 100644
index 0000000..1f8615b
--- /dev/null
+++ b/src/modules/spa/spa-node.c
@@ -0,0 +1,292 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <dlfcn.h>
+
+#include <spa/node/node.h>
+#include <spa/node/utils.h>
+#include <spa/utils/result.h>
+#include <spa/param/props.h>
+#include <spa/pod/iter.h>
+#include <spa/debug/types.h>
+
+#include "spa-node.h"
+
+struct impl {
+ struct pw_impl_node *this;
+
+ enum pw_spa_node_flags flags;
+
+ struct spa_handle *handle;
+ struct spa_node *node; /**< handle to SPA node */
+
+ struct spa_hook node_listener;
+ int init_pending;
+
+ void *user_data;
+
+ unsigned int async_init:1;
+};
+
+static void spa_node_free(void *data)
+{
+ struct impl *impl = data;
+ struct pw_impl_node *node = impl->this;
+
+ pw_log_debug("spa-node %p: free", node);
+
+ spa_hook_remove(&impl->node_listener);
+ if (impl->handle)
+ pw_unload_spa_handle(impl->handle);
+}
+
+static void complete_init(struct impl *impl)
+{
+ struct pw_impl_node *this = impl->this;
+
+ impl->init_pending = SPA_ID_INVALID;
+
+ if (SPA_FLAG_IS_SET(impl->flags, PW_SPA_NODE_FLAG_ACTIVATE))
+ pw_impl_node_set_active(this, true);
+
+ if (!SPA_FLAG_IS_SET(impl->flags, PW_SPA_NODE_FLAG_NO_REGISTER))
+ pw_impl_node_register(this, NULL);
+ else
+ pw_impl_node_initialized(this);
+}
+
+static void spa_node_result(void *data, int seq, int res, uint32_t type, const void *result)
+{
+ struct impl *impl = data;
+ struct pw_impl_node *node = impl->this;
+
+ if (seq == impl->init_pending) {
+ pw_log_debug("spa-node %p: init complete event %d %d", node, seq, res);
+ complete_init(impl);
+ }
+}
+
+static const struct pw_impl_node_events node_events = {
+ PW_VERSION_IMPL_NODE_EVENTS,
+ .free = spa_node_free,
+ .result = spa_node_result,
+};
+
+struct pw_impl_node *
+pw_spa_node_new(struct pw_context *context,
+ enum pw_spa_node_flags flags,
+ struct spa_node *node,
+ struct spa_handle *handle,
+ struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct pw_impl_node *this;
+ struct impl *impl;
+ int res;
+
+ this = pw_context_create_node(context, properties, sizeof(struct impl) + user_data_size);
+ if (this == NULL) {
+ res = -errno;
+ goto error_exit;
+ }
+
+ impl = pw_impl_node_get_user_data(this);
+ impl->this = this;
+ impl->node = node;
+ impl->handle = handle;
+ impl->flags = flags;
+
+ if (user_data_size > 0)
+ impl->user_data = SPA_PTROFF(impl, sizeof(struct impl), void);
+
+ pw_impl_node_add_listener(this, &impl->node_listener, &node_events, impl);
+ if ((res = pw_impl_node_set_implementation(this, impl->node)) < 0)
+ goto error_exit_clean_node;
+
+ if (flags & PW_SPA_NODE_FLAG_ASYNC) {
+ impl->init_pending = spa_node_sync(impl->node, res);
+ } else {
+ complete_init(impl);
+ }
+ return this;
+
+error_exit_clean_node:
+ pw_impl_node_destroy(this);
+ handle = NULL;
+error_exit:
+ if (handle)
+ pw_unload_spa_handle(handle);
+ errno = -res;
+ return NULL;
+
+}
+
+void *pw_spa_node_get_user_data(struct pw_impl_node *node)
+{
+ struct impl *impl = pw_impl_node_get_user_data(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 pw_impl_node *pw_spa_node_load(struct pw_context *context,
+ const char *factory_name,
+ enum pw_spa_node_flags flags,
+ struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct pw_impl_node *this;
+ struct spa_node *spa_node;
+ int res;
+ struct spa_handle *handle;
+ void *iface;
+ const struct pw_properties *p;
+
+ if (properties) {
+ p = pw_context_get_properties(context);
+ pw_properties_set(properties, "clock.quantum-limit",
+ pw_properties_get(p, "default.clock.quantum-limit"));
+ }
+
+ handle = pw_context_load_spa_handle(context,
+ factory_name,
+ properties ? &properties->dict : NULL);
+ if (handle == NULL) {
+ res = -errno;
+ goto error_exit;
+ }
+
+ if ((res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Node, &iface)) < 0) {
+ pw_log_error("can't get node interface %d", res);
+ goto error_exit_unload;
+ }
+ if (SPA_RESULT_IS_ASYNC(res))
+ flags |= PW_SPA_NODE_FLAG_ASYNC;
+
+ spa_node = iface;
+
+ if (properties != NULL) {
+ 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, properties, user_data_size);
+ if (this == NULL) {
+ res = -errno;
+ properties = NULL;
+ goto error_exit_unload;
+ }
+
+ return this;
+
+error_exit_unload:
+ pw_unload_spa_handle(handle);
+error_exit:
+ pw_properties_free(properties);
+ errno = -res;
+ return NULL;
+}
diff --git a/src/modules/spa/spa-node.h b/src/modules/spa/spa-node.h
new file mode 100644
index 0000000..5289a17
--- /dev/null
+++ b/src/modules/spa/spa-node.h
@@ -0,0 +1,63 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_SPA_NODE_H
+#define PIPEWIRE_SPA_NODE_H
+
+#include <spa/node/node.h>
+
+#include <pipewire/impl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum pw_spa_node_flags {
+ PW_SPA_NODE_FLAG_ACTIVATE = (1 << 0),
+ PW_SPA_NODE_FLAG_NO_REGISTER = (1 << 1),
+ PW_SPA_NODE_FLAG_ASYNC = (1 << 2),
+};
+
+struct pw_impl_node *
+pw_spa_node_new(struct pw_context *context,
+ enum pw_spa_node_flags flags,
+ struct spa_node *node,
+ struct spa_handle *handle,
+ struct pw_properties *properties,
+ size_t user_data_size);
+
+struct pw_impl_node *
+pw_spa_node_load(struct pw_context *context,
+ const char *factory_name,
+ enum pw_spa_node_flags flags,
+ struct pw_properties *properties,
+ size_t user_data_size);
+
+void *pw_spa_node_get_user_data(struct pw_impl_node *node);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_SPA_NODE_H */
diff --git a/src/pipewire/array.h b/src/pipewire/array.h
new file mode 100644
index 0000000..44f31a8
--- /dev/null
+++ b/src/pipewire/array.h
@@ -0,0 +1,176 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_ARRAY_H
+#define PIPEWIRE_ARRAY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <errno.h>
+
+#include <spa/utils/defs.h>
+
+/** \defgroup pw_array Array
+ *
+ * \brief An array object
+ *
+ * The array is a dynamically resizable data structure that can
+ * hold items of the same size.
+ */
+
+/**
+ * \addtogroup pw_array
+ * \{
+ */
+struct pw_array {
+ void *data; /**< pointer to array data */
+ size_t size; /**< length of array in bytes */
+ size_t alloc; /**< number of allocated memory in \a data */
+ size_t extend; /**< number of bytes to extend with */
+};
+
+#define PW_ARRAY_INIT(extend) ((struct pw_array) { NULL, 0, 0, (extend) })
+
+#define pw_array_get_len_s(a,s) ((a)->size / (s))
+#define pw_array_get_unchecked_s(a,idx,s,t) SPA_PTROFF((a)->data,(idx)*(s),t)
+#define pw_array_check_index_s(a,idx,s) ((idx) < pw_array_get_len_s(a,s))
+
+/** Get the number of items of type \a t in array */
+#define pw_array_get_len(a,t) pw_array_get_len_s(a,sizeof(t))
+/** Get the item with index \a idx and type \a t from array */
+#define pw_array_get_unchecked(a,idx,t) pw_array_get_unchecked_s(a,idx,sizeof(t),t)
+/** Check if an item with index \a idx and type \a t exist in array */
+#define pw_array_check_index(a,idx,t) pw_array_check_index_s(a,idx,sizeof(t))
+
+#define pw_array_first(a) ((a)->data)
+#define pw_array_end(a) SPA_PTROFF((a)->data, (a)->size, void)
+#define pw_array_check(a,p) (SPA_PTROFF(p,sizeof(*(p)),void) <= pw_array_end(a))
+
+#define pw_array_for_each(pos, array) \
+ for ((pos) = (__typeof__(pos)) pw_array_first(array); \
+ pw_array_check(array, pos); \
+ (pos)++)
+
+#define pw_array_consume(pos, array) \
+ for ((pos) = (__typeof__(pos)) pw_array_first(array); \
+ pw_array_check(array, pos); \
+ (pos) = (__typeof__(pos)) pw_array_first(array))
+
+#define pw_array_remove(a,p) \
+({ \
+ (a)->size -= sizeof(*(p)); \
+ memmove(p, SPA_PTROFF((p), sizeof(*(p)), void), \
+ SPA_PTRDIFF(pw_array_end(a),(p))); \
+})
+
+/** Initialize the array with given extend */
+static inline void pw_array_init(struct pw_array *arr, size_t extend)
+{
+ arr->data = NULL;
+ arr->size = arr->alloc = 0;
+ arr->extend = extend;
+}
+
+/** Clear the array */
+static inline void pw_array_clear(struct pw_array *arr)
+{
+ free(arr->data);
+ pw_array_init(arr, arr->extend);
+}
+
+/** Reset the array */
+static inline 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)
+{
+ size_t alloc, need;
+
+ alloc = arr->alloc;
+ need = arr->size + size;
+
+ if (SPA_UNLIKELY(alloc < need)) {
+ void *data;
+ alloc = SPA_MAX(alloc, arr->extend);
+ spa_assert(alloc != 0); /* forgot pw_array_init */
+ while (alloc < need)
+ alloc *= 2;
+ if (SPA_UNLIKELY((data = realloc(arr->data, alloc)) == NULL))
+ return -errno;
+ arr->data = data;
+ arr->alloc = alloc;
+ }
+ return 0;
+}
+
+/** Add \a ref size bytes to \a arr. A pointer to memory that can
+ * hold at least \a size bytes is returned */
+static inline void *pw_array_add(struct pw_array *arr, size_t size)
+{
+ void *p;
+
+ if (pw_array_ensure_size(arr, size) < 0)
+ return NULL;
+
+ p = SPA_PTROFF(arr->data, arr->size, void);
+ arr->size += size;
+
+ return p;
+}
+
+/** Add \a ref size bytes to \a arr. When there is not enough memory to
+ * hold \a size bytes, NULL is returned */
+static inline void *pw_array_add_fixed(struct pw_array *arr, size_t size)
+{
+ void *p;
+
+ if (SPA_UNLIKELY(arr->alloc < arr->size + size)) {
+ errno = ENOSPC;
+ return NULL;
+ }
+
+ p = SPA_PTROFF(arr->data, arr->size, void);
+ arr->size += size;
+
+ return p;
+}
+
+/** Add a pointer to array */
+#define pw_array_add_ptr(a,p) \
+ *((void**) pw_array_add(a, sizeof(void*))) = (p)
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_ARRAY_H */
diff --git a/src/pipewire/buffers.c b/src/pipewire/buffers.c
new file mode 100644
index 0000000..c3cd7d8
--- /dev/null
+++ b/src/pipewire/buffers.c
@@ -0,0 +1,368 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/node/utils.h>
+#include <spa/pod/parser.h>
+#include <spa/param/param.h>
+#include <spa/buffer/alloc.h>
+
+#include "pipewire/keys.h"
+#include "pipewire/private.h"
+
+#include "buffers.h"
+
+PW_LOG_TOPIC_EXTERN(log_buffers);
+#define PW_LOG_TOPIC_DEFAULT log_buffers
+
+#define MAX_ALIGN 32
+#define MAX_BLOCKS 64u
+
+struct port {
+ struct spa_node *node;
+ enum spa_direction direction;
+ uint32_t port_id;
+};
+
+/* Allocate an array of buffers that can be shared */
+static int alloc_buffers(struct pw_mempool *pool,
+ uint32_t n_buffers,
+ uint32_t n_params,
+ struct spa_pod **params,
+ uint32_t n_datas,
+ uint32_t *data_sizes,
+ int32_t *data_strides,
+ uint32_t *data_aligns,
+ uint32_t *data_types,
+ uint32_t flags,
+ struct pw_buffers *allocation)
+{
+ struct spa_buffer **buffers;
+ void *skel, *data;
+ uint32_t i;
+ uint32_t n_metas;
+ struct spa_meta *metas;
+ struct spa_data *datas;
+ struct pw_memblock *m;
+ struct spa_buffer_alloc_info info = { 0, };
+
+ if (!SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_SHARED))
+ SPA_FLAG_SET(info.flags, SPA_BUFFER_ALLOC_FLAG_INLINE_ALL);
+
+ n_metas = 0;
+
+ metas = alloca(sizeof(struct spa_meta) * n_params);
+ datas = alloca(sizeof(struct spa_data) * n_datas);
+
+ /* collect metadata */
+ for (i = 0; i < n_params; i++) {
+ if (spa_pod_is_object_type (params[i], SPA_TYPE_OBJECT_ParamMeta)) {
+ uint32_t type, size;
+
+ if (spa_pod_parse_object(params[i],
+ SPA_TYPE_OBJECT_ParamMeta, NULL,
+ SPA_PARAM_META_type, SPA_POD_Id(&type),
+ SPA_PARAM_META_size, SPA_POD_Int(&size)) < 0)
+ continue;
+
+ pw_log_debug("%p: enable meta %d %d", allocation, type, size);
+
+ metas[n_metas].type = type;
+ metas[n_metas].size = size;
+ n_metas++;
+ }
+ }
+
+ for (i = 0; i < n_datas; i++) {
+ struct spa_data *d = &datas[i];
+
+ spa_zero(*d);
+ if (data_sizes[i] > 0) {
+ /* we allocate memory */
+ d->type = SPA_DATA_MemPtr;
+ d->maxsize = data_sizes[i];
+ SPA_FLAG_SET(d->flags, SPA_DATA_FLAG_READWRITE);
+ } else {
+ /* client allocates memory. Set the mask of possible
+ * types in the type field */
+ d->type = data_types[i];
+ d->maxsize = 0;
+ }
+ if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_DYNAMIC))
+ SPA_FLAG_SET(d->flags, SPA_DATA_FLAG_DYNAMIC);
+ }
+
+ spa_buffer_alloc_fill_info(&info, n_metas, metas, n_datas, datas, data_aligns);
+
+ buffers = calloc(1, info.max_align + n_buffers * (sizeof(struct spa_buffer *) + info.skel_size));
+ if (buffers == NULL)
+ return -errno;
+
+ skel = SPA_PTROFF(buffers, n_buffers * sizeof(struct spa_buffer *), void);
+ skel = SPA_PTR_ALIGN(skel, info.max_align, void);
+
+ if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_SHARED)) {
+ /* pointer to buffer structures */
+ m = pw_mempool_alloc(pool,
+ PW_MEMBLOCK_FLAG_READWRITE |
+ PW_MEMBLOCK_FLAG_SEAL |
+ PW_MEMBLOCK_FLAG_MAP,
+ SPA_DATA_MemFd,
+ n_buffers * info.mem_size);
+ if (m == NULL) {
+ free(buffers);
+ return -errno;
+ }
+
+ data = m->map->ptr;
+ } else {
+ m = NULL;
+ data = NULL;
+ }
+
+ pw_log_debug("%p: layout buffers skel:%p data:%p buffers:%p",
+ allocation, skel, data, buffers);
+ spa_buffer_alloc_layout_array(&info, n_buffers, buffers, skel, data);
+
+ allocation->mem = m;
+ allocation->n_buffers = n_buffers;
+ allocation->buffers = buffers;
+ allocation->flags = flags;
+
+ return 0;
+}
+
+static int
+param_filter(struct pw_buffers *this,
+ struct port *in_port,
+ struct port *out_port,
+ uint32_t id,
+ struct spa_pod_builder *result)
+{
+ uint8_t ibuf[4096];
+ struct spa_pod_builder ib = { 0 };
+ struct spa_pod *oparam, *iparam;
+ uint32_t iidx, oidx;
+ int in_res = -EIO, out_res = -EIO, num = 0;
+
+ for (iidx = 0;;) {
+ spa_pod_builder_init(&ib, ibuf, sizeof(ibuf));
+ pw_log_debug("%p: input param %d id:%d", this, iidx, id);
+ in_res = spa_node_port_enum_params_sync(in_port->node,
+ in_port->direction, in_port->port_id,
+ id, &iidx, NULL, &iparam, &ib);
+
+ if (in_res < 1) {
+ /* in_res == -ENOENT : unknown parameter, assume NULL and we will
+ * exit the loop below.
+ * in_res < 1 : some error or no data, exit now
+ */
+ if (in_res == -ENOENT)
+ iparam = NULL;
+ else
+ break;
+ }
+
+ pw_log_pod(SPA_LOG_LEVEL_DEBUG, iparam);
+
+ for (oidx = 0;;) {
+ pw_log_debug("%p: output param %d id:%d", this, oidx, id);
+ out_res = spa_node_port_enum_params_sync(out_port->node,
+ out_port->direction, out_port->port_id,
+ id, &oidx, iparam, &oparam, result);
+
+ /* out_res < 1 : no value or error, exit now */
+ if (out_res < 1)
+ break;
+
+ pw_log_pod(SPA_LOG_LEVEL_DEBUG, oparam);
+ num++;
+ }
+ if (out_res == -ENOENT && iparam) {
+ /* no output param known but we have an input param,
+ * use that one */
+ spa_pod_builder_raw_padded(result, iparam, SPA_POD_SIZE(iparam));
+ num++;
+ }
+ /* no more input values, exit */
+ if (in_res < 1)
+ break;
+ }
+ if (num == 0) {
+ if (out_res == -ENOENT && in_res == -ENOENT)
+ return 0;
+ if (in_res < 0)
+ return in_res;
+ if (out_res < 0)
+ return out_res;
+ return -EINVAL;
+ }
+ return num;
+}
+
+static struct spa_pod *find_param(struct spa_pod **params, uint32_t n_params, uint32_t type)
+{
+ uint32_t i;
+
+ for (i = 0; i < n_params; i++) {
+ if (spa_pod_is_object_type(params[i], type))
+ return params[i];
+ }
+ return NULL;
+}
+
+SPA_EXPORT
+int pw_buffers_negotiate(struct pw_context *context, uint32_t flags,
+ struct spa_node *outnode, uint32_t out_port_id,
+ struct spa_node *innode, uint32_t in_port_id,
+ struct pw_buffers *result)
+{
+ struct spa_pod **params, *param;
+ uint8_t buffer[4096];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ uint32_t i, offset, n_params;
+ uint32_t max_buffers, blocks;
+ size_t minsize, stride, align;
+ uint32_t *data_sizes;
+ int32_t *data_strides;
+ uint32_t *data_aligns;
+ uint32_t types, *data_types;
+ struct port output = { outnode, SPA_DIRECTION_OUTPUT, out_port_id };
+ struct port input = { innode, SPA_DIRECTION_INPUT, in_port_id };
+ int res;
+
+ if (flags & PW_BUFFERS_FLAG_IN_PRIORITY) {
+ struct port tmp = output;
+ output = input;
+ input = tmp;
+ }
+
+ res = param_filter(result, &input, &output, SPA_PARAM_Buffers, &b);
+ if (res < 0) {
+ pw_context_debug_port_params(context, input.node, input.direction,
+ input.port_id, SPA_PARAM_Buffers, res,
+ "input param");
+ pw_context_debug_port_params(context, output.node, output.direction,
+ output.port_id, SPA_PARAM_Buffers, res,
+ "output param");
+ return res;
+ }
+ n_params = res;
+ if ((res = param_filter(result, &input, &output, SPA_PARAM_Meta, &b)) > 0)
+ n_params += res;
+
+ params = alloca(n_params * sizeof(struct spa_pod *));
+ for (i = 0, offset = 0; i < n_params; i++) {
+ params[i] = SPA_PTROFF(buffer, offset, struct spa_pod);
+ spa_pod_fixate(params[i]);
+ pw_log_debug("%p: fixated param %d:", result, i);
+ pw_log_pod(SPA_LOG_LEVEL_DEBUG, params[i]);
+ offset += SPA_ROUND_UP_N(SPA_POD_SIZE(params[i]), 8);
+ }
+
+ max_buffers = context->settings.link_max_buffers;
+
+ align = pw_properties_get_uint32(context->properties, PW_KEY_CPU_MAX_ALIGN, MAX_ALIGN);
+
+ minsize = stride = 0;
+ types = SPA_ID_INVALID; /* bitmask of allowed types */
+ blocks = 1;
+
+ param = find_param(params, n_params, SPA_TYPE_OBJECT_ParamBuffers);
+ if (param) {
+ uint32_t qmax_buffers = max_buffers,
+ qminsize = minsize, qstride = stride, qalign = align;
+ uint32_t qtypes = types, qblocks = blocks;
+
+ spa_pod_parse_object(param,
+ SPA_TYPE_OBJECT_ParamBuffers, NULL,
+ SPA_PARAM_BUFFERS_buffers, SPA_POD_OPT_Int(&qmax_buffers),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_OPT_Int(&qblocks),
+ SPA_PARAM_BUFFERS_size, SPA_POD_OPT_Int(&qminsize),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_OPT_Int(&qstride),
+ SPA_PARAM_BUFFERS_align, SPA_POD_OPT_Int(&qalign),
+ SPA_PARAM_BUFFERS_dataType, SPA_POD_OPT_Int(&qtypes));
+
+ max_buffers =
+ qmax_buffers == 0 ? max_buffers : SPA_MIN(qmax_buffers,
+ max_buffers);
+ blocks = SPA_CLAMP(qblocks, blocks, MAX_BLOCKS);
+ minsize = SPA_MAX(minsize, qminsize);
+ stride = SPA_MAX(stride, qstride);
+ align = SPA_MAX(align, qalign);
+ types = qtypes;
+
+ pw_log_debug("%p: %d %d %d %d %d %d -> %d %zd %zd %d %zd %d", result,
+ qblocks, qminsize, qstride, qmax_buffers, qalign, qtypes,
+ blocks, minsize, stride, max_buffers, align, types);
+ } else {
+ pw_log_warn("%p: no buffers param", result);
+ minsize = 8192;
+ max_buffers = 2;
+ }
+ if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_SHARED_MEM)) {
+ if (types != SPA_ID_INVALID)
+ SPA_FLAG_CLEAR(types, 1<<SPA_DATA_MemPtr);
+ if (types == 0 || types == SPA_ID_INVALID)
+ types = 1<<SPA_DATA_MemFd;
+ }
+
+ if (SPA_FLAG_IS_SET(flags, PW_BUFFERS_FLAG_NO_MEM))
+ minsize = 0;
+
+ data_sizes = alloca(sizeof(uint32_t) * blocks);
+ data_strides = alloca(sizeof(int32_t) * blocks);
+ data_aligns = alloca(sizeof(uint32_t) * blocks);
+ data_types = alloca(sizeof(uint32_t) * blocks);
+
+ for (i = 0; i < blocks; i++) {
+ data_sizes[i] = minsize;
+ data_strides[i] = stride;
+ data_aligns[i] = align;
+ data_types[i] = types;
+ }
+
+ if ((res = alloc_buffers(context->pool,
+ max_buffers,
+ n_params,
+ params,
+ blocks,
+ data_sizes, data_strides,
+ data_aligns, data_types,
+ flags,
+ result)) < 0) {
+ pw_log_error("%p: can't alloc buffers: %s", result, spa_strerror(res));
+ }
+
+ return res;
+}
+
+SPA_EXPORT
+void pw_buffers_clear(struct pw_buffers *buffers)
+{
+ pw_log_debug("%p: clear %d buffers:%p", buffers, buffers->n_buffers, buffers->buffers);
+ if (buffers->mem)
+ pw_memblock_unref(buffers->mem);
+ free(buffers->buffers);
+ spa_zero(*buffers);
+}
diff --git a/src/pipewire/buffers.h b/src/pipewire/buffers.h
new file mode 100644
index 0000000..abef392
--- /dev/null
+++ b/src/pipewire/buffers.h
@@ -0,0 +1,75 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_BUFFERS_H
+#define PIPEWIRE_BUFFERS_H
+
+#include <spa/node/node.h>
+
+#include <pipewire/context.h>
+#include <pipewire/mem.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \defgroup pw_buffers Buffers
+ * Buffer handling
+ */
+
+/**
+ * \addtogroup pw_buffers
+ * \{
+ */
+
+#define PW_BUFFERS_FLAG_NONE 0
+#define PW_BUFFERS_FLAG_NO_MEM (1<<0) /**< don't allocate buffer memory */
+#define PW_BUFFERS_FLAG_SHARED (1<<1) /**< buffers can be shared */
+#define PW_BUFFERS_FLAG_DYNAMIC (1<<2) /**< buffers have dynamic data */
+#define PW_BUFFERS_FLAG_SHARED_MEM (1<<3) /**< buffers need shared memory */
+#define PW_BUFFERS_FLAG_IN_PRIORITY (1<<4) /**< input parameters have priority */
+
+struct pw_buffers {
+ struct pw_memblock *mem; /**< allocated buffer memory */
+ struct spa_buffer **buffers; /**< port buffers */
+ uint32_t n_buffers; /**< number of port buffers */
+ uint32_t flags; /**< flags */
+};
+
+int pw_buffers_negotiate(struct pw_context *context, uint32_t flags,
+ struct spa_node *outnode, uint32_t out_port_id,
+ struct spa_node *innode, uint32_t in_port_id,
+ struct pw_buffers *result);
+
+void pw_buffers_clear(struct pw_buffers *buffers);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_BUFFERS_H */
diff --git a/src/pipewire/client.h b/src/pipewire/client.h
new file mode 100644
index 0000000..2ed4c5b
--- /dev/null
+++ b/src/pipewire/client.h
@@ -0,0 +1,186 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_CLIENT_H
+#define PIPEWIRE_CLIENT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/param/param.h>
+
+#include <pipewire/proxy.h>
+#include <pipewire/permission.h>
+
+/** \defgroup pw_client Client
+ * Client interface
+ */
+
+/**
+ * \addtogroup pw_client
+ * \{
+ */
+#define PW_TYPE_INTERFACE_Client PW_TYPE_INFO_INTERFACE_BASE "Client"
+
+#define PW_VERSION_CLIENT 3
+struct pw_client;
+
+/* default ID of the current client after connect */
+#define PW_ID_CLIENT 1
+
+/** The client information. Extra information can be added in later versions */
+struct pw_client_info {
+ uint32_t id; /**< id of the global */
+#define PW_CLIENT_CHANGE_MASK_PROPS (1 << 0)
+#define PW_CLIENT_CHANGE_MASK_ALL ((1 << 1)-1)
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ struct spa_dict *props; /**< extra properties */
+};
+
+/** Update an existing \ref pw_client_info with \a update with reset */
+struct pw_client_info *
+pw_client_info_update(struct pw_client_info *info,
+ const struct pw_client_info *update);
+/** Merge an existing \ref pw_client_info with \a update */
+struct pw_client_info *
+pw_client_info_merge(struct pw_client_info *info,
+ const struct pw_client_info *update, bool reset);
+/** Free a \ref pw_client_info */
+void pw_client_info_free(struct pw_client_info *info);
+
+
+#define PW_CLIENT_EVENT_INFO 0
+#define PW_CLIENT_EVENT_PERMISSIONS 1
+#define PW_CLIENT_EVENT_NUM 2
+
+/** Client events */
+struct pw_client_events {
+#define PW_VERSION_CLIENT_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify client info
+ *
+ * \param info info about the client
+ */
+ void (*info) (void *data, const struct pw_client_info *info);
+ /**
+ * Notify a client permission
+ *
+ * Event emitted as a result of the get_permissions method.
+ *
+ * \param default_permissions the default permissions
+ * \param index the index of the first permission entry
+ * \param n_permissions the number of permissions
+ * \param permissions the permissions
+ */
+ void (*permissions) (void *data,
+ uint32_t index,
+ uint32_t n_permissions,
+ const struct pw_permission *permissions);
+};
+
+
+#define PW_CLIENT_METHOD_ADD_LISTENER 0
+#define PW_CLIENT_METHOD_ERROR 1
+#define PW_CLIENT_METHOD_UPDATE_PROPERTIES 2
+#define PW_CLIENT_METHOD_GET_PERMISSIONS 3
+#define PW_CLIENT_METHOD_UPDATE_PERMISSIONS 4
+#define PW_CLIENT_METHOD_NUM 5
+
+/** Client methods */
+struct pw_client_methods {
+#define PW_VERSION_CLIENT_METHODS 0
+ uint32_t version;
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_client_events *events,
+ void *data);
+ /**
+ * Send an error to a client
+ *
+ * \param id the global id to report the error on
+ * \param res an errno style error code
+ * \param message an error string
+ */
+ int (*error) (void *object, uint32_t id, int res, const char *message);
+ /**
+ * Update client properties
+ *
+ * \param props new properties
+ */
+ int (*update_properties) (void *object, const struct spa_dict *props);
+
+ /**
+ * Get client permissions
+ *
+ * A permissions event will be emitted with the permissions.
+ *
+ * \param index the first index to query, 0 for first
+ * \param num the maximum number of items to get
+ */
+ int (*get_permissions) (void *object, uint32_t index, uint32_t num);
+ /**
+ * Manage the permissions of the global objects for this
+ * client
+ *
+ * Update the permissions of the global objects using the
+ * provided array with permissions
+ *
+ * Globals can use the default permissions or can have specific
+ * permissions assigned to them.
+ *
+ * \param n_permissions number of permissions
+ * \param permissions array of permissions
+ */
+ int (*update_permissions) (void *object, uint32_t n_permissions,
+ 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__)
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_CLIENT_H */
diff --git a/src/pipewire/conf.c b/src/pipewire/conf.c
new file mode 100644
index 0000000..700d8ee
--- /dev/null
+++ b/src/pipewire/conf.c
@@ -0,0 +1,1186 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <signal.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <regex.h>
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+#if defined(__FreeBSD__) || defined(__MidnightBSD__)
+#ifndef O_PATH
+#define O_PATH 0
+#endif
+#endif
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/utils/json.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/private.h>
+
+PW_LOG_TOPIC_EXTERN(log_conf);
+#define PW_LOG_TOPIC_DEFAULT log_conf
+
+static int make_path(char *path, size_t size, const char *paths[])
+{
+ int i, len;
+ char *p = path;
+ for (i = 0; paths[i] != NULL; i++) {
+ len = snprintf(p, size, "%s%s", i == 0 ? "" : "/", paths[i]);
+ if (len < 0)
+ return -errno;
+ if ((size_t)len >= size)
+ return -ENOSPC;
+ p += len;
+ size -= len;
+ }
+ return 0;
+}
+
+static int get_abs_path(char *path, size_t size, const char *prefix, const char *name)
+{
+ if (prefix[0] == '/') {
+ const char *paths[] = { prefix, name, NULL };
+ if (make_path(path, size, paths) == 0 &&
+ access(path, R_OK) == 0)
+ return 1;
+ return -ENOENT;
+ }
+ return 0;
+}
+
+static int get_envconf_path(char *path, size_t size, const char *prefix, const char *name)
+{
+ const char *dir;
+
+ dir = getenv("PIPEWIRE_CONFIG_DIR");
+ if (dir != NULL) {
+ const char *paths[] = { dir, prefix, name, NULL };
+ if (make_path(path, size, paths) == 0 &&
+ access(path, R_OK) == 0)
+ return 1;
+ return -ENOENT;
+ }
+ return 0;
+}
+
+static int get_homeconf_path(char *path, size_t size, const char *prefix, const char *name)
+{
+ char buffer[4096];
+ const char *dir;
+
+ dir = getenv("XDG_CONFIG_HOME");
+ if (dir != NULL) {
+ const char *paths[] = { dir, "pipewire", prefix, name, NULL };
+ if (make_path(path, size, paths) == 0 &&
+ access(path, R_OK) == 0)
+ return 1;
+ }
+ dir = getenv("HOME");
+ if (dir == NULL) {
+ struct passwd pwd, *result = NULL;
+ if (getpwuid_r(getuid(), &pwd, buffer, sizeof(buffer), &result) == 0)
+ dir = result ? result->pw_dir : NULL;
+ }
+ if (dir != NULL) {
+ const char *paths[] = { dir, ".config", "pipewire", prefix, name, NULL };
+ if (make_path(path, size, paths) == 0 &&
+ access(path, R_OK) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static int get_configdir_path(char *path, size_t size, const char *prefix, const char *name)
+{
+ const char *dir;
+ dir = PIPEWIRE_CONFIG_DIR;
+ if (dir != NULL) {
+ const char *paths[] = { dir, prefix, name, NULL };
+ if (make_path(path, size, paths) == 0 &&
+ access(path, R_OK) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static int get_confdata_path(char *path, size_t size, const char *prefix, const char *name)
+{
+ const char *dir;
+ dir = PIPEWIRE_CONFDATADIR;
+ if (dir != NULL) {
+ const char *paths[] = { dir, prefix, name, NULL };
+ if (make_path(path, size, paths) == 0 &&
+ access(path, R_OK) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static int get_config_path(char *path, size_t size, const char *prefix, const char *name)
+{
+ int res;
+
+ if (prefix == NULL) {
+ prefix = name;
+ name = NULL;
+ }
+ if ((res = get_abs_path(path, size, prefix, name)) != 0)
+ return res;
+
+ if (pw_check_option("no-config", "true"))
+ goto no_config;
+
+ if ((res = get_envconf_path(path, size, prefix, name)) != 0)
+ return res;
+
+ if ((res = get_homeconf_path(path, size, prefix, name)) != 0)
+ return res;
+
+ if ((res = get_configdir_path(path, size, prefix, name)) != 0)
+ return res;
+no_config:
+ if ((res = get_confdata_path(path, size, prefix, name)) != 0)
+ return res;
+ return 0;
+}
+
+static int get_config_dir(char *path, size_t size, const char *prefix, const char *name, int *level)
+{
+ int res;
+
+ if (prefix == NULL) {
+ prefix = name;
+ name = NULL;
+ }
+ if ((res = get_abs_path(path, size, prefix, name)) != 0) {
+ if ((*level)++ == 0)
+ return res;
+ return -ENOENT;
+ }
+
+ if (pw_check_option("no-config", "true"))
+ goto no_config;
+
+ if ((res = get_envconf_path(path, size, prefix, name)) != 0) {
+ if ((*level)++ == 0)
+ return res;
+ return -ENOENT;
+ }
+
+ if (*level == 0) {
+ (*level)++;
+ if ((res = get_homeconf_path(path, size, prefix, name)) != 0)
+ return res;
+ }
+ if (*level == 1) {
+ (*level)++;
+ if ((res = get_configdir_path(path, size, prefix, name)) != 0)
+ return res;
+ }
+ if (*level == 2) {
+no_config:
+ (*level)++;
+ if ((res = get_confdata_path(path, size, prefix, name)) != 0)
+ return res;
+ }
+ return 0;
+}
+
+static int get_envstate_path(char *path, size_t size, const char *prefix, const char *name)
+{
+ const char *dir;
+ dir = getenv("PIPEWIRE_STATE_DIR");
+ if (dir != NULL) {
+ const char *paths[] = { dir, prefix, name, NULL };
+ if (make_path(path, size, paths) == 0 &&
+ access(path, R_OK) == 0)
+ return 1;
+ return -ENOENT;
+ }
+ return 0;
+}
+
+static int get_homestate_path(char *path, size_t size, const char *prefix, const char *name)
+{
+ const char *dir;
+ char buffer[4096];
+
+ dir = getenv("XDG_STATE_HOME");
+ if (dir != NULL) {
+ const char *paths[] = { dir, "pipewire", prefix, name, NULL };
+ if (make_path(path, size, paths) == 0 &&
+ access(path, R_OK) == 0)
+ return 1;
+ }
+ dir = getenv("HOME");
+ if (dir == NULL) {
+ struct passwd pwd, *result = NULL;
+ if (getpwuid_r(getuid(), &pwd, buffer, sizeof(buffer), &result) == 0)
+ dir = result ? result->pw_dir : NULL;
+ }
+ if (dir != NULL) {
+ const char *paths[] = { dir, ".local", "state", "pipewire", prefix, name, NULL };
+ if (make_path(path, size, paths) == 0 &&
+ access(path, R_OK) == 0)
+ return 1;
+ }
+ if (dir != NULL) {
+ /* fallback for old XDG_CONFIG_HOME */
+ const char *paths[] = { dir, ".config", "pipewire", prefix, name, NULL };
+ if (make_path(path, size, paths) == 0 &&
+ access(path, R_OK) == 0)
+ return 1;
+ }
+ return 0;
+}
+
+static int get_state_path(char *path, size_t size, const char *prefix, const char *name)
+{
+ int res;
+
+ if (prefix == NULL) {
+ prefix = name;
+ name = NULL;
+ }
+ if ((res = get_abs_path(path, size, prefix, name)) != 0)
+ return res;
+
+ if ((res = get_envstate_path(path, size, prefix, name)) != 0)
+ return res;
+
+ if ((res = get_homestate_path(path, size, prefix, name)) != 0)
+ return res;
+
+ return 0;
+}
+
+static int ensure_path(char *path, int size, const char *paths[])
+{
+ int i, len, mode;
+ char *p = path;
+
+ for (i = 0; paths[i] != NULL; i++) {
+ len = snprintf(p, size, "%s/", paths[i]);
+ if (len < 0)
+ return -errno;
+ if (len >= size)
+ return -ENOSPC;
+
+ p += len;
+ size -= len;
+
+ mode = X_OK;
+ if (paths[i+1] == NULL)
+ mode |= R_OK | W_OK;
+
+ if (access(path, mode) < 0) {
+ if (errno != ENOENT)
+ return -errno;
+ if (mkdir(path, 0700) < 0) {
+ pw_log_info("Can't create directory %s: %m", path);
+ return -errno;
+ }
+ if (access(path, mode) < 0)
+ return -errno;
+
+ pw_log_info("created directory %s", path);
+ }
+ }
+ return 0;
+}
+
+static int open_write_dir(char *path, int size, const char *prefix)
+{
+ const char *dir;
+ char buffer[4096];
+ int res;
+
+ if (prefix != NULL && prefix[0] == '/') {
+ const char *paths[] = { prefix, NULL };
+ if (ensure_path(path, size, paths) == 0)
+ goto found;
+ }
+ dir = getenv("XDG_STATE_HOME");
+ if (dir != NULL) {
+ const char *paths[] = { dir, "pipewire", prefix, NULL };
+ if (ensure_path(path, size, paths) == 0)
+ goto found;
+ }
+ dir = getenv("HOME");
+ if (dir == NULL) {
+ struct passwd pwd, *result = NULL;
+ if (getpwuid_r(getuid(), &pwd, buffer, sizeof(buffer), &result) == 0)
+ dir = result ? result->pw_dir : NULL;
+ }
+ if (dir != NULL) {
+ const char *paths[] = { dir, ".local", "state", "pipewire", prefix, NULL };
+ if (ensure_path(path, size, paths) == 0)
+ goto found;
+ }
+ return -ENOENT;
+found:
+ if ((res = open(path, O_CLOEXEC | O_DIRECTORY | O_PATH)) < 0) {
+ pw_log_error("Can't open state directory %s: %m", path);
+ return -errno;
+ }
+ return res;
+}
+
+SPA_EXPORT
+int pw_conf_save_state(const char *prefix, const char *name, const struct pw_properties *conf)
+{
+ char path[PATH_MAX];
+ char *tmp_name;
+ int res, sfd, fd, count = 0;
+ FILE *f;
+
+ if ((sfd = open_write_dir(path, sizeof(path), prefix)) < 0)
+ return sfd;
+
+ tmp_name = alloca(strlen(name)+5);
+ sprintf(tmp_name, "%s.tmp", name);
+ if ((fd = openat(sfd, tmp_name, O_CLOEXEC | O_CREAT | O_WRONLY | O_TRUNC, 0600)) < 0) {
+ pw_log_error("can't open file '%s': %m", tmp_name);
+ res = -errno;
+ goto error;
+ }
+
+ f = fdopen(fd, "w");
+ fprintf(f, "{");
+ count += pw_properties_serialize_dict(f, &conf->dict, PW_PROPERTIES_FLAG_NL);
+ fprintf(f, "%s}", count == 0 ? " " : "\n");
+ fclose(f);
+
+ if (renameat(sfd, tmp_name, sfd, name) < 0) {
+ pw_log_error("can't rename temp file '%s': %m", tmp_name);
+ res = -errno;
+ goto error;
+ }
+ res = 0;
+ pw_log_info("%p: saved state '%s%s'", conf, path, name);
+error:
+ close(sfd);
+ return res;
+}
+
+static int conf_load(const char *path, struct pw_properties *conf)
+{
+ char *data;
+ struct stat sbuf;
+ int fd, count;
+
+ if ((fd = open(path, O_CLOEXEC | O_RDONLY)) < 0)
+ goto error;
+
+ if (fstat(fd, &sbuf) < 0)
+ goto error_close;
+
+ if (sbuf.st_size > 0) {
+ if ((data = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED)
+ goto error_close;
+
+ count = pw_properties_update_string(conf, data, sbuf.st_size);
+ munmap(data, sbuf.st_size);
+ } else {
+ count = 0;
+ }
+ close(fd);
+
+ pw_log_info("%p: loaded config '%s' with %d items", conf, path, count);
+
+ return 0;
+
+error_close:
+ close(fd);
+error:
+ pw_log_warn("%p: error loading config '%s': %m", conf, path);
+ return -errno;
+}
+
+static bool check_override(struct pw_properties *conf, const char *name, int level)
+{
+ const struct spa_dict_item *it;
+
+ spa_dict_for_each(it, &conf->dict) {
+ int lev, idx;
+
+ if (!spa_streq(name, it->value))
+ continue;
+ if (sscanf(it->key, "override.%d.%d.config.name", &lev, &idx) != 2)
+ continue;
+ if (lev < level)
+ return false;
+ }
+ return true;
+}
+
+static void add_override(struct pw_properties *conf, struct pw_properties *override,
+ const char *path, const char *name, int level, int index)
+{
+ const struct spa_dict_item *it;
+ char key[1024];
+
+ snprintf(key, sizeof(key), "override.%d.%d.config.path", level, index);
+ pw_properties_set(conf, key, path);
+ snprintf(key, sizeof(key), "override.%d.%d.config.name", level, index);
+ pw_properties_set(conf, key, name);
+ spa_dict_for_each(it, &override->dict) {
+ snprintf(key, sizeof(key), "override.%d.%d.%s", level, index, it->key);
+ pw_properties_set(conf, key, it->value);
+ }
+}
+
+static int conf_filter(const struct dirent *entry)
+{
+ return spa_strendswith(entry->d_name, ".conf");
+}
+
+SPA_EXPORT
+int pw_conf_load_conf(const char *prefix, const char *name, struct pw_properties *conf)
+{
+ char path[PATH_MAX];
+ char fname[PATH_MAX + 256];
+ int i, res, level = 0;
+ struct pw_properties *override = NULL;
+ const char *dname;
+
+ if (name == NULL) {
+ pw_log_debug("%p: config name must not be NULL", conf);
+ return -EINVAL;
+ }
+
+ if (get_config_path(path, sizeof(path), prefix, name) == 0) {
+ pw_log_debug("%p: can't load config '%s': %m", conf, path);
+ return -ENOENT;
+ }
+ pw_properties_set(conf, "config.prefix", prefix);
+ pw_properties_set(conf, "config.name", name);
+ pw_properties_set(conf, "config.path", path);
+
+ if ((res = conf_load(path, conf)) < 0)
+ return res;
+
+ pw_properties_setf(conf, "config.name.d", "%s.d", name);
+ dname = pw_properties_get(conf, "config.name.d");
+
+ while (true) {
+ struct dirent **entries = NULL;
+ int n;
+
+ if (get_config_dir(path, sizeof(path), prefix, dname, &level) <= 0)
+ break;
+
+ n = scandir(path, &entries, conf_filter, alphasort);
+ if (n == 0)
+ continue;
+ if (n < 0) {
+ pw_log_warn("scandir %s failed: %m", path);
+ continue;
+ }
+ if (override == NULL &&
+ (override = pw_properties_new(NULL, NULL)) == NULL)
+ return -errno;
+
+ for (i = 0; i < n; i++) {
+ const char *name = entries[i]->d_name;
+
+ snprintf(fname, sizeof(fname), "%s/%s", path, name);
+ if (check_override(conf, name, level)) {
+ if (conf_load(fname, override) >= 0)
+ add_override(conf, override, fname, name, level, i);
+ pw_properties_clear(override);
+ } else {
+ pw_log_info("skip override %s with lower priority", fname);
+ }
+ free(entries[i]);
+ }
+ free(entries);
+ }
+ pw_properties_free(override);
+ return 0;
+}
+
+SPA_EXPORT
+int pw_conf_load_state(const char *prefix, const char *name, struct pw_properties *conf)
+{
+ char path[PATH_MAX];
+
+ if (name == NULL) {
+ pw_log_debug("%p: config name must not be NULL", conf);
+ return -EINVAL;
+ }
+
+ if (get_state_path(path, sizeof(path), prefix, name) == 0) {
+ pw_log_debug("%p: can't load config '%s': %m", conf, path);
+ return -ENOENT;
+ }
+ return conf_load(path, conf);
+}
+
+struct data {
+ struct pw_context *context;
+ struct pw_properties *props;
+ int count;
+};
+
+/* context.spa-libs = {
+ * <factory-name regex> = <library-name>
+ * }
+ */
+static int parse_spa_libs(void *user_data, const char *location,
+ const char *section, const char *str, size_t len)
+{
+ struct data *d = user_data;
+ struct pw_context *context = d->context;
+ struct spa_json it[2];
+ char key[512], value[512];
+
+ spa_json_init(&it[0], str, len);
+ if (spa_json_enter_object(&it[0], &it[1]) < 0) {
+ pw_log_error("config file error: context.spa-libs is not an object");
+ return -EINVAL;
+ }
+
+ while (spa_json_get_string(&it[1], key, sizeof(key)) > 0) {
+ if (spa_json_get_string(&it[1], value, sizeof(value)) > 0) {
+ pw_context_add_spa_lib(context, key, value);
+ d->count++;
+ }
+ }
+ return 0;
+}
+
+static int load_module(struct pw_context *context, const char *key, const char *args, const char *flags)
+{
+ if (pw_context_load_module(context, key, args, NULL) == NULL) {
+ if (errno == ENOENT && flags && strstr(flags, "ifexists") != NULL) {
+ pw_log_info("%p: skipping unavailable module %s",
+ context, key);
+ } else if (flags == NULL || strstr(flags, "nofail") == NULL) {
+ pw_log_error("%p: could not load mandatory module \"%s\": %m",
+ context, key);
+ return -errno;
+ } else {
+ pw_log_info("%p: could not load optional module \"%s\": %m",
+ context, key);
+ }
+ } else {
+ pw_log_info("%p: loaded module %s", context, key);
+ }
+ return 0;
+}
+
+/*
+ * context.modules = [
+ * { name = <module-name>
+ * [ args = { <key> = <value> ... } ]
+ * [ flags = [ [ ifexists ] [ nofail ] ]
+ * }
+ * ]
+ */
+static int parse_modules(void *user_data, const char *location,
+ const char *section, const char *str, size_t len)
+{
+ struct data *d = user_data;
+ struct pw_context *context = d->context;
+ struct spa_json it[3];
+ char key[512], *s;
+ int res = 0;
+
+ s = strndup(str, len);
+ spa_json_init(&it[0], s, len);
+ if (spa_json_enter_array(&it[0], &it[1]) < 0) {
+ pw_log_error("config file error: context.modules is not an array");
+ res = -EINVAL;
+ goto exit;
+ }
+
+ while (spa_json_enter_object(&it[1], &it[2]) > 0) {
+ char *name = NULL, *args = NULL, *flags = NULL;
+
+ while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) {
+ const char *val;
+ int len;
+
+ if ((len = spa_json_next(&it[2], &val)) <= 0)
+ break;
+
+ if (spa_streq(key, "name")) {
+ name = (char*)val;
+ spa_json_parse_stringn(val, len, name, len+1);
+ } else if (spa_streq(key, "args")) {
+ if (spa_json_is_container(val, len))
+ len = spa_json_container_len(&it[2], val, len);
+
+ args = (char*)val;
+ spa_json_parse_stringn(val, len, args, len+1);
+ } else if (spa_streq(key, "flags")) {
+ if (spa_json_is_container(val, len))
+ len = spa_json_container_len(&it[2], val, len);
+ flags = (char*)val;
+ spa_json_parse_stringn(val, len, flags, len+1);
+ }
+ }
+ if (name != NULL)
+ res = load_module(context, name, args, flags);
+
+ if (res < 0)
+ break;
+
+ d->count++;
+ }
+exit:
+ free(s);
+ return res;
+}
+
+static int create_object(struct pw_context *context, const char *key, const char *args, const char *flags)
+{
+ struct pw_impl_factory *factory;
+ void *obj;
+
+ pw_log_debug("find factory %s", key);
+ factory = pw_context_find_factory(context, key);
+ if (factory == NULL) {
+ if (flags && strstr(flags, "nofail") != NULL)
+ return 0;
+ pw_log_error("can't find factory %s", key);
+ return -ENOENT;
+ }
+ pw_log_debug("create object with args %s", args);
+ obj = pw_impl_factory_create_object(factory,
+ NULL, NULL, 0,
+ args ? pw_properties_new_string(args) : NULL,
+ SPA_ID_INVALID);
+ if (obj == NULL) {
+ if (flags && strstr(flags, "nofail") != NULL)
+ return 0;
+ pw_log_error("can't create object from factory %s: %m", key);
+ return -errno;
+ }
+ return 0;
+}
+
+/*
+ * context.objects = [
+ * { factory = <factory-name>
+ * [ args = { <key> = <value> ... } ]
+ * [ flags = [ [ nofail ] ] ]
+ * }
+ * ]
+ */
+static int parse_objects(void *user_data, const char *location,
+ const char *section, const char *str, size_t len)
+{
+ struct data *d = user_data;
+ struct pw_context *context = d->context;
+ struct spa_json it[3];
+ char key[512], *s;
+ int res = 0;
+
+ s = strndup(str, len);
+ spa_json_init(&it[0], s, len);
+ if (spa_json_enter_array(&it[0], &it[1]) < 0) {
+ pw_log_error("config file error: context.objects is not an array");
+ res = -EINVAL;
+ goto exit;
+ }
+
+ while (spa_json_enter_object(&it[1], &it[2]) > 0) {
+ char *factory = NULL, *args = NULL, *flags = NULL;
+
+ while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) {
+ const char *val;
+ int len;
+
+ if ((len = spa_json_next(&it[2], &val)) <= 0)
+ break;
+
+ if (spa_streq(key, "factory")) {
+ factory = (char*)val;
+ spa_json_parse_stringn(val, len, factory, len+1);
+ } else if (spa_streq(key, "args")) {
+ if (spa_json_is_container(val, len))
+ len = spa_json_container_len(&it[2], val, len);
+
+ args = (char*)val;
+ spa_json_parse_stringn(val, len, args, len+1);
+ } else if (spa_streq(key, "flags")) {
+ if (spa_json_is_container(val, len))
+ len = spa_json_container_len(&it[2], val, len);
+
+ flags = (char*)val;
+ spa_json_parse_stringn(val, len, flags, len+1);
+ }
+ }
+ if (factory != NULL)
+ res = create_object(context, factory, args, flags);
+
+ if (res < 0)
+ break;
+ d->count++;
+ }
+exit:
+ free(s);
+ return res;
+}
+
+static int do_exec(struct pw_context *context, const char *key, const char *args)
+{
+ int pid, res, n_args;
+
+ pid = fork();
+
+ if (pid == 0) {
+ char *cmd, **argv;
+
+ /* Double fork to avoid zombies; we don't want to set SIGCHLD handler */
+ pid = fork();
+
+ if (pid < 0) {
+ pw_log_error("fork error: %m");
+ exit(1);
+ } else if (pid != 0) {
+ exit(0);
+ }
+
+ cmd = spa_aprintf("%s %s", key, args ? args : "");
+ argv = pw_split_strv(cmd, " \t", INT_MAX, &n_args);
+ free(cmd);
+
+ pw_log_info("exec %s '%s'", key, args);
+ res = execvp(key, argv);
+ pw_free_strv(argv);
+
+ if (res == -1) {
+ res = -errno;
+ pw_log_error("execvp error '%s': %m", key);
+ }
+
+ exit(1);
+ } else if (pid < 0) {
+ pw_log_error("fork error: %m");
+ } else {
+ int status = 0;
+ do {
+ errno = 0;
+ res = waitpid(pid, &status, 0);
+ } while (res < 0 && errno == EINTR);
+ pw_log_debug("exec got pid %d res:%d status:%d", (int)pid, res, status);
+ }
+ return 0;
+}
+
+/*
+ * context.exec = [
+ * { path = <program-name>
+ * [ args = "<arguments>" ]
+ * }
+ * ]
+ */
+static int parse_exec(void *user_data, const char *location,
+ const char *section, const char *str, size_t len)
+{
+ struct data *d = user_data;
+ struct pw_context *context = d->context;
+ struct spa_json it[3];
+ char key[512], *s;
+ int res = 0;
+
+ s = strndup(str, len);
+ spa_json_init(&it[0], s, len);
+ if (spa_json_enter_array(&it[0], &it[1]) < 0) {
+ pw_log_error("config file error: context.exec is not an array");
+ res = -EINVAL;
+ goto exit;
+ }
+
+ while (spa_json_enter_object(&it[1], &it[2]) > 0) {
+ char *path = NULL, *args = NULL;
+
+ while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) {
+ const char *val;
+ int len;
+
+ if ((len = spa_json_next(&it[2], &val)) <= 0)
+ break;
+
+ if (spa_streq(key, "path")) {
+ path = (char*)val;
+ spa_json_parse_stringn(val, len, path, len+1);
+ } else if (spa_streq(key, "args")) {
+ args = (char*)val;
+ spa_json_parse_stringn(val, len, args, len+1);
+ }
+ }
+ if (path != NULL)
+ res = do_exec(context, path, args);
+
+ if (res < 0)
+ break;
+
+ d->count++;
+ }
+exit:
+ free(s);
+ return res;
+}
+
+
+SPA_EXPORT
+int pw_context_conf_section_for_each(struct pw_context *context, const char *section,
+ int (*callback) (void *data, const char *location, const char *section,
+ const char *str, size_t len),
+ void *data)
+{
+ struct pw_properties *conf = context->conf;
+ const char *path = NULL;
+ const struct spa_dict_item *it;
+ int res = 0;
+
+ spa_dict_for_each(it, &conf->dict) {
+ if (spa_strendswith(it->key, "config.path")) {
+ path = it->value;
+ continue;
+
+ } else if (spa_streq(it->key, section)) {
+ pw_log_info("handle config '%s' section '%s'", path, section);
+ } else if (spa_strstartswith(it->key, "override.") &&
+ spa_strendswith(it->key, section)) {
+ pw_log_info("handle override '%s' section '%s'", path, section);
+ } else
+ continue;
+
+ res = callback(data, path, section, it->value, strlen(it->value));
+ if (res != 0)
+ break;
+ }
+ return res;
+}
+
+SPA_EXPORT
+int pw_context_parse_conf_section(struct pw_context *context,
+ struct pw_properties *conf, const char *section)
+{
+ struct data data = { .context = context };
+ int res;
+
+ if (spa_streq(section, "context.spa-libs"))
+ res = pw_context_conf_section_for_each(context, section,
+ parse_spa_libs, &data);
+ else if (spa_streq(section, "context.modules"))
+ res = pw_context_conf_section_for_each(context, section,
+ parse_modules, &data);
+ else if (spa_streq(section, "context.objects"))
+ res = pw_context_conf_section_for_each(context, section,
+ parse_objects, &data);
+ else if (spa_streq(section, "context.exec"))
+ res = pw_context_conf_section_for_each(context, section,
+ parse_exec, &data);
+ else
+ res = -EINVAL;
+
+ return res == 0 ? data.count : res;
+}
+
+static int update_props(void *user_data, const char *location, const char *key,
+ const char *val, size_t len)
+{
+ struct data *data = user_data;
+ data->count += pw_properties_update_string(data->props, val, len);
+ return 0;
+}
+
+static int try_load_conf(const char *conf_prefix, const char *conf_name,
+ struct pw_properties *conf)
+{
+ int res;
+
+ if (conf_name == NULL)
+ return -EINVAL;
+ if (spa_streq(conf_name, "null"))
+ return 0;
+ if ((res = pw_conf_load_conf(conf_prefix, conf_name, conf)) < 0) {
+ bool skip_prefix = conf_prefix == NULL || conf_name[0] == '/';
+ pw_log_warn("can't load config %s%s%s: %s",
+ skip_prefix ? "" : conf_prefix,
+ skip_prefix ? "" : "/",
+ conf_name, spa_strerror(res));
+ }
+ return res;
+}
+
+SPA_EXPORT
+int pw_conf_load_conf_for_context(struct pw_properties *props, struct pw_properties *conf)
+{
+ const char *conf_prefix, *conf_name;
+ int res;
+
+ conf_prefix = getenv("PIPEWIRE_CONFIG_PREFIX");
+ if (conf_prefix == NULL)
+ conf_prefix = pw_properties_get(props, PW_KEY_CONFIG_PREFIX);
+
+ conf_name = getenv("PIPEWIRE_CONFIG_NAME");
+ if ((res = try_load_conf(conf_prefix, conf_name, conf)) < 0) {
+ conf_name = pw_properties_get(props, PW_KEY_CONFIG_NAME);
+ if ((res = try_load_conf(conf_prefix, conf_name, conf)) < 0) {
+ conf_name = "client.conf";
+ if ((res = try_load_conf(conf_prefix, conf_name, conf)) < 0) {
+ pw_log_error("can't load default config %s: %s",
+ conf_name, spa_strerror(res));
+ return res;
+ }
+ }
+ }
+
+ conf_name = pw_properties_get(props, PW_KEY_CONFIG_OVERRIDE_NAME);
+ if (conf_name != NULL) {
+ struct pw_properties *override;
+ const char *path, *name;
+
+ override = pw_properties_new(NULL, NULL);
+ if (override == NULL) {
+ res = -errno;
+ return res;
+ }
+
+ conf_prefix = pw_properties_get(props, PW_KEY_CONFIG_OVERRIDE_PREFIX);
+ if ((res = try_load_conf(conf_prefix, conf_name, override)) < 0) {
+ pw_log_error("can't load default override config %s: %s",
+ conf_name, spa_strerror(res));
+ pw_properties_free (override);
+ return res;
+ }
+ path = pw_properties_get(override, "config.path");
+ name = pw_properties_get(override, "config.name");
+ add_override(conf, override, path, name, 0, 1);
+ pw_properties_free(override);
+ }
+
+ return res;
+}
+
+SPA_EXPORT
+int pw_context_conf_update_props(struct pw_context *context,
+ const char *section, struct pw_properties *props)
+{
+ struct data data = { .context = context, .props = props };
+ int res;
+ const char *str = pw_properties_get(props, "config.ext");
+
+ res = pw_context_conf_section_for_each(context, section,
+ update_props, &data);
+ if (res == 0 && str != NULL) {
+ char key[128];
+ snprintf(key, sizeof(key), "%s.%s", section, str);
+ res = pw_context_conf_section_for_each(context, key,
+ update_props, &data);
+ }
+ return res == 0 ? data.count : res;
+}
+
+
+/*
+ * {
+ * # all keys must match the value. ~ in value starts regex.
+ * <key> = <value>
+ * ...
+ * }
+ */
+static bool find_match(struct spa_json *arr, const struct spa_dict *props)
+{
+ struct spa_json it[1];
+
+ while (spa_json_enter_object(arr, &it[0]) > 0) {
+ char key[256], val[1024];
+ const char *str, *value;
+ int match = 0, fail = 0;
+ int len;
+
+ while (spa_json_get_string(&it[0], key, sizeof(key)) > 0) {
+ bool success = false;
+
+ if ((len = spa_json_next(&it[0], &value)) <= 0)
+ break;
+
+ str = spa_dict_lookup(props, key);
+
+ if (spa_json_is_null(value, len)) {
+ success = str == NULL;
+ } else {
+ if (spa_json_parse_stringn(value, len, val, sizeof(val)) < 0)
+ continue;
+ value = val;
+ len = strlen(val);
+ }
+ if (str != NULL) {
+ if (value[0] == '~') {
+ regex_t preg;
+ if (regcomp(&preg, value+1, REG_EXTENDED | REG_NOSUB) == 0) {
+ if (regexec(&preg, str, 0, NULL, 0) == 0)
+ success = true;
+ regfree(&preg);
+ }
+ } else if (strncmp(str, value, len) == 0 &&
+ strlen(str) == (size_t)len) {
+ success = true;
+ }
+ }
+ if (success) {
+ match++;
+ pw_log_debug("'%s' match '%s' < > '%.*s'", key, str, len, value);
+ }
+ else
+ fail++;
+ }
+ if (match > 0 && fail == 0)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * [
+ * {
+ * matches = [
+ * # any of the items in matches needs to match, if one does,
+ * # actions are emited.
+ * {
+ * # all keys must match the value. ~ in value starts regex.
+ * <key> = <value>
+ * ...
+ * }
+ * ...
+ * ]
+ * actions = {
+ * <action> = <value>
+ * ...
+ * }
+ * }
+ * ]
+ */
+SPA_EXPORT
+int pw_conf_match_rules(const char *str, size_t len, const char *location,
+ const struct spa_dict *props,
+ int (*callback) (void *data, const char *location, const char *action,
+ const char *str, size_t len),
+ void *data)
+{
+ const char *val;
+ struct spa_json it[4], actions;
+
+ spa_json_init(&it[0], str, len);
+ if (spa_json_enter_array(&it[0], &it[1]) < 0)
+ return 0;
+
+ while (spa_json_enter_object(&it[1], &it[2]) > 0) {
+ char key[64];
+ bool have_match = false, have_actions = false;
+
+ while (spa_json_get_string(&it[2], key, sizeof(key)) > 0) {
+ if (spa_streq(key, "matches")) {
+ if (spa_json_enter_array(&it[2], &it[3]) < 0)
+ break;
+
+ have_match = find_match(&it[3], props);
+ }
+ else if (spa_streq(key, "actions")) {
+ if (spa_json_enter_object(&it[2], &actions) > 0)
+ have_actions = true;
+ }
+ else if (spa_json_next(&it[2], &val) <= 0)
+ break;
+ }
+ if (!have_match || !have_actions)
+ continue;
+
+ while (spa_json_get_string(&actions, key, sizeof(key)) > 0) {
+ int res, len;
+ pw_log_debug("action %s", key);
+
+ if ((len = spa_json_next(&actions, &val)) <= 0)
+ break;
+
+ if (spa_json_is_container(val, len))
+ len = spa_json_container_len(&actions, val, len);
+
+ if ((res = callback(data, location, key, val, len)) < 0)
+ return res;
+ }
+ }
+ return 0;
+}
+
+struct match {
+ const struct spa_dict *props;
+ int (*matched) (void *data, const char *location, const char *action,
+ const char *val, size_t len);
+ void *data;
+};
+
+static int match_rules(void *data, const char *location, const char *section,
+ const char *str, size_t len)
+{
+ struct match *match = data;
+ return pw_conf_match_rules(str, len, location,
+ match->props, match->matched, match->data);
+}
+
+SPA_EXPORT
+int pw_context_conf_section_match_rules(struct pw_context *context, const char *section,
+ const struct spa_dict *props,
+ int (*callback) (void *data, const char *location, const char *action,
+ const char *str, size_t len),
+ void *data)
+{
+ struct match match = {
+ .props = props,
+ .matched = callback,
+ .data = data };
+ int res;
+ const char *str = spa_dict_lookup(props, "config.ext");
+
+ res = pw_context_conf_section_for_each(context, section,
+ match_rules, &match);
+ if (res == 0 && str != NULL) {
+ char key[128];
+ snprintf(key, sizeof(key), "%s.%s", section, str);
+ res = pw_context_conf_section_for_each(context, key,
+ match_rules, &match);
+ }
+ return res;
+}
diff --git a/src/pipewire/conf.h b/src/pipewire/conf.h
new file mode 100644
index 0000000..8ada200
--- /dev/null
+++ b/src/pipewire/conf.h
@@ -0,0 +1,49 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/context.h>
+
+/** \defgroup pw_conf Configuration
+ * Loading/saving properties from/to configuration files.
+ */
+
+/**
+ * \addtogroup pw_conf
+ * \{
+ */
+
+int pw_conf_load_conf_for_context(struct pw_properties *props, struct pw_properties *conf);
+int pw_conf_load_conf(const char *prefix, const char *name, struct pw_properties *conf);
+int pw_conf_load_state(const char *prefix, const char *name, struct pw_properties *conf);
+int pw_conf_save_state(const char *prefix, const char *name, const struct pw_properties *conf);
+
+int pw_conf_match_rules(const char *str, size_t len, const char *location,
+ const struct spa_dict *props,
+ int (*callback) (void *data, const char *location, const char *action,
+ const char *str, size_t len),
+ void *data);
+
+/**
+ * \}
+ */
diff --git a/src/pipewire/context.c b/src/pipewire/context.c
new file mode 100644
index 0000000..6ba6488
--- /dev/null
+++ b/src/pipewire/context.c
@@ -0,0 +1,1461 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <stdio.h>
+#include <regex.h>
+#include <limits.h>
+#include <sys/mman.h>
+
+#include <pipewire/log.h>
+
+#include <spa/support/cpu.h>
+#include <spa/support/dbus.h>
+#include <spa/support/plugin.h>
+#include <spa/support/plugin-loader.h>
+#include <spa/node/utils.h>
+#include <spa/utils/names.h>
+#include <spa/utils/string.h>
+#include <spa/debug/types.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/private.h>
+#include <pipewire/thread.h>
+#include <pipewire/conf.h>
+
+#include <pipewire/extensions/protocol-native.h>
+
+PW_LOG_TOPIC_EXTERN(log_context);
+#define PW_LOG_TOPIC_DEFAULT log_context
+
+/** \cond */
+struct impl {
+ struct pw_context this;
+ struct spa_handle *dbus_handle;
+ struct spa_plugin_loader plugin_loader;
+ unsigned int recalc:1;
+ unsigned int recalc_pending:1;
+};
+
+
+struct factory_entry {
+ regex_t regex;
+ char *lib;
+};
+/** \endcond */
+
+static void fill_properties(struct pw_context *context)
+{
+ struct pw_properties *properties = context->properties;
+
+ if (!pw_properties_get(properties, PW_KEY_APP_NAME))
+ pw_properties_set(properties, PW_KEY_APP_NAME, pw_get_client_name());
+
+ if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_BINARY))
+ pw_properties_set(properties, PW_KEY_APP_PROCESS_BINARY, pw_get_prgname());
+
+ if (!pw_properties_get(properties, PW_KEY_APP_LANGUAGE)) {
+ pw_properties_set(properties, PW_KEY_APP_LANGUAGE, getenv("LANG"));
+ }
+ if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_ID)) {
+ pw_properties_setf(properties, PW_KEY_APP_PROCESS_ID, "%zd", (size_t) getpid());
+ }
+ if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_USER))
+ pw_properties_set(properties, PW_KEY_APP_PROCESS_USER, pw_get_user_name());
+
+ if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_HOST))
+ pw_properties_set(properties, PW_KEY_APP_PROCESS_HOST, pw_get_host_name());
+
+ if (!pw_properties_get(properties, PW_KEY_APP_PROCESS_SESSION_ID)) {
+ pw_properties_set(properties, PW_KEY_APP_PROCESS_SESSION_ID,
+ getenv("XDG_SESSION_ID"));
+ }
+ if (!pw_properties_get(properties, PW_KEY_WINDOW_X11_DISPLAY)) {
+ pw_properties_set(properties, PW_KEY_WINDOW_X11_DISPLAY,
+ getenv("DISPLAY"));
+ }
+ pw_properties_set(properties, PW_KEY_CORE_VERSION, context->core->info.version);
+ pw_properties_set(properties, PW_KEY_CORE_NAME, context->core->info.name);
+}
+
+static int context_set_freewheel(struct pw_context *context, bool freewheel)
+{
+ struct spa_thread *thr;
+ int res = 0;
+
+ if ((thr = pw_data_loop_get_thread(context->data_loop_impl)) == NULL)
+ return -EIO;
+
+ if (freewheel) {
+ pw_log_info("%p: enter freewheel", context);
+ if (context->thread_utils)
+ res = spa_thread_utils_drop_rt(context->thread_utils, thr);
+ } else {
+ pw_log_info("%p: exit freewheel", context);
+ /* Use the priority as configured within the realtime module */
+ if (context->thread_utils)
+ res = spa_thread_utils_acquire_rt(context->thread_utils, thr, -1);
+ }
+ if (res < 0)
+ pw_log_info("%p: freewheel error:%s", context, spa_strerror(res));
+
+ context->freewheeling = freewheel;
+
+ return res;
+}
+
+static struct spa_handle *impl_plugin_loader_load(void *object, const char *factory_name, const struct spa_dict *info)
+{
+ struct impl *impl = object;
+
+ if (impl == NULL || factory_name == NULL) {
+ errno = EINVAL;
+ return NULL;
+ }
+
+ return pw_context_load_spa_handle(&impl->this, factory_name, info);
+}
+
+static int impl_plugin_loader_unload(void *object, struct spa_handle *handle)
+{
+ spa_return_val_if_fail(object != NULL, -EINVAL);
+ return pw_unload_spa_handle(handle);
+}
+
+static const struct spa_plugin_loader_methods impl_plugin_loader = {
+ SPA_VERSION_PLUGIN_LOADER_METHODS,
+ .load = impl_plugin_loader_load,
+ .unload = impl_plugin_loader_unload,
+};
+
+static void init_plugin_loader(struct impl *impl)
+{
+ impl->plugin_loader.iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_PluginLoader,
+ SPA_VERSION_PLUGIN_LOADER,
+ &impl_plugin_loader,
+ impl);
+}
+
+static int do_data_loop_setup(struct spa_loop *loop, bool async, uint32_t seq,
+ const void *data, size_t size, void *user_data)
+{
+ struct pw_context *this = user_data;
+ const char *str;
+ struct spa_cpu *cpu;
+
+ cpu = spa_support_find(this->support, this->n_support, SPA_TYPE_INTERFACE_CPU);
+
+ if ((str = pw_properties_get(this->properties, SPA_KEY_CPU_ZERO_DENORMALS)) != NULL &&
+ cpu != NULL) {
+ pw_log_info("setting zero denormals: %s", str);
+ spa_cpu_zero_denormals(cpu, spa_atob(str));
+ }
+ return 0;
+}
+
+/** Create a new context object
+ *
+ * \param main_loop the main loop to use
+ * \param properties extra properties for the context, ownership it taken
+ *
+ * \return a newly allocated context object
+ */
+SPA_EXPORT
+struct pw_context *pw_context_new(struct pw_loop *main_loop,
+ struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct impl *impl;
+ struct pw_context *this;
+ const char *lib, *str;
+ void *dbus_iface = NULL;
+ uint32_t n_support;
+ struct pw_properties *pr, *conf;
+ struct spa_cpu *cpu;
+ int res = 0;
+
+ impl = calloc(1, sizeof(struct impl) + user_data_size);
+ if (impl == NULL) {
+ res = -errno;
+ goto error_cleanup;
+ }
+
+ this = &impl->this;
+
+ pw_log_debug("%p: new", this);
+
+ if (user_data_size > 0)
+ this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void);
+
+ pw_array_init(&this->factory_lib, 32);
+ pw_array_init(&this->objects, 32);
+ pw_map_init(&this->globals, 128, 32);
+
+ spa_list_init(&this->core_impl_list);
+ spa_list_init(&this->protocol_list);
+ spa_list_init(&this->core_list);
+ spa_list_init(&this->registry_resource_list);
+ spa_list_init(&this->global_list);
+ spa_list_init(&this->module_list);
+ spa_list_init(&this->device_list);
+ spa_list_init(&this->client_list);
+ spa_list_init(&this->node_list);
+ spa_list_init(&this->factory_list);
+ spa_list_init(&this->metadata_list);
+ spa_list_init(&this->link_list);
+ spa_list_init(&this->control_list[0]);
+ spa_list_init(&this->control_list[1]);
+ spa_list_init(&this->export_list);
+ spa_list_init(&this->driver_list);
+ spa_hook_list_init(&this->listener_list);
+ spa_hook_list_init(&this->driver_listener_list);
+
+ this->sc_pagesize = sysconf(_SC_PAGESIZE);
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+ this->properties = properties;
+
+ conf = pw_properties_new(NULL, NULL);
+ if (conf == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+ this->conf = conf;
+ if ((res = pw_conf_load_conf_for_context (properties, conf)) < 0)
+ goto error_free;
+
+ n_support = pw_get_support(this->support, SPA_N_ELEMENTS(this->support) - 6);
+ cpu = spa_support_find(this->support, n_support, SPA_TYPE_INTERFACE_CPU);
+
+ res = pw_context_conf_update_props(this, "context.properties", properties);
+ pw_log_info("%p: parsed %d context.properties items", this, res);
+
+ if ((str = getenv("PIPEWIRE_CORE"))) {
+ pw_log_info("using core.name from environment: %s", str);
+ pw_properties_set(properties, PW_KEY_CORE_NAME, str);
+ }
+
+ if ((str = pw_properties_get(properties, "vm.overrides")) != NULL) {
+ if (cpu != NULL && spa_cpu_get_vm_type(cpu) != SPA_CPU_VM_NONE)
+ pw_properties_update_string(properties, str, strlen(str));
+ pw_properties_set(properties, "vm.overrides", NULL);
+ }
+ if (cpu != NULL) {
+ if (pw_properties_get(properties, PW_KEY_CPU_MAX_ALIGN) == NULL)
+ pw_properties_setf(properties, PW_KEY_CPU_MAX_ALIGN,
+ "%u", spa_cpu_get_max_align(cpu));
+ }
+
+ if (getenv("PIPEWIRE_DEBUG") == NULL &&
+ (str = pw_properties_get(properties, "log.level")) != NULL)
+ pw_log_set_level(atoi(str));
+
+ if (pw_properties_get_bool(properties, "mem.mlock-all", false)) {
+ if (mlockall(MCL_CURRENT | MCL_FUTURE) < 0)
+ pw_log_warn("%p: could not mlockall; %m", impl);
+ else
+ pw_log_info("%p: mlockall succeeded", impl);
+ }
+
+ pw_settings_init(this);
+ this->settings = this->defaults;
+
+ pr = pw_properties_copy(properties);
+ if ((str = pw_properties_get(pr, "context.data-loop." PW_KEY_LIBRARY_NAME_SYSTEM)))
+ pw_properties_set(pr, PW_KEY_LIBRARY_NAME_SYSTEM, str);
+
+ this->data_loop_impl = pw_data_loop_new(&pr->dict);
+ pw_properties_free(pr);
+ if (this->data_loop_impl == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+
+ this->pool = pw_mempool_new(NULL);
+ if (this->pool == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+
+ this->data_loop = pw_data_loop_get_loop(this->data_loop_impl);
+ this->data_system = this->data_loop->system;
+ this->main_loop = main_loop;
+
+ this->work_queue = pw_work_queue_new(this->main_loop);
+ if (this->work_queue == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+
+ init_plugin_loader(impl);
+
+ this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, this->main_loop->system);
+ this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Loop, this->main_loop->loop);
+ this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_LoopUtils, this->main_loop->utils);
+ this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataSystem, this->data_system);
+ this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DataLoop, this->data_loop->loop);
+ this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_PluginLoader, &impl->plugin_loader);
+
+ if ((str = pw_properties_get(properties, "support.dbus")) == NULL ||
+ pw_properties_parse_bool(str)) {
+ lib = pw_properties_get(properties, PW_KEY_LIBRARY_NAME_DBUS);
+ if (lib == NULL)
+ lib = "support/libspa-dbus";
+
+ impl->dbus_handle = pw_load_spa_handle(lib,
+ SPA_NAME_SUPPORT_DBUS, NULL,
+ n_support, this->support);
+
+ if (impl->dbus_handle == NULL) {
+ pw_log_warn("%p: can't load dbus library: %s", this, lib);
+ } else if ((res = spa_handle_get_interface(impl->dbus_handle,
+ SPA_TYPE_INTERFACE_DBus, &dbus_iface)) < 0) {
+ pw_log_warn("%p: can't load dbus interface: %s", this, spa_strerror(res));
+ } else {
+ this->support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_DBus, dbus_iface);
+ }
+ }
+ this->n_support = n_support;
+ spa_assert(n_support <= SPA_N_ELEMENTS(this->support));
+
+ this->core = pw_context_create_core(this, pw_properties_copy(properties), 0);
+ if (this->core == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+ pw_impl_core_register(this->core, NULL);
+
+ fill_properties(this);
+
+ if ((res = pw_context_parse_conf_section(this, conf, "context.spa-libs")) < 0)
+ goto error_free;
+ pw_log_info("%p: parsed %d context.spa-libs items", this, res);
+ if ((res = pw_context_parse_conf_section(this, conf, "context.modules")) < 0)
+ goto error_free;
+ if (res > 0)
+ pw_log_info("%p: parsed %d context.modules items", this, res);
+ else
+ pw_log_warn("%p: no modules loaded from context.modules", this);
+ if ((res = pw_context_parse_conf_section(this, conf, "context.objects")) < 0)
+ goto error_free;
+ pw_log_info("%p: parsed %d context.objects items", this, res);
+ if ((res = pw_context_parse_conf_section(this, conf, "context.exec")) < 0)
+ goto error_free;
+ pw_log_info("%p: parsed %d context.exec items", this, res);
+
+ if ((res = pw_data_loop_start(this->data_loop_impl)) < 0)
+ goto error_free;
+
+ pw_data_loop_invoke(this->data_loop_impl,
+ do_data_loop_setup, 0, NULL, 0, false, this);
+
+ pw_settings_expose(this);
+
+ pw_log_debug("%p: created", this);
+
+ return this;
+
+error_free:
+ pw_context_destroy(this);
+error_cleanup:
+ errno = -res;
+ return NULL;
+}
+
+/** Destroy a context object
+ *
+ * \param context a context to destroy
+ */
+SPA_EXPORT
+void pw_context_destroy(struct pw_context *context)
+{
+ struct impl *impl = SPA_CONTAINER_OF(context, struct impl, this);
+ struct pw_global *global;
+ struct pw_impl_client *client;
+ struct pw_impl_module *module;
+ struct pw_impl_device *device;
+ struct pw_core *core;
+ struct pw_resource *resource;
+ struct pw_impl_node *node;
+ struct factory_entry *entry;
+ struct pw_impl_metadata *metadata;
+ struct pw_impl_core *core_impl;
+
+ pw_log_debug("%p: destroy", context);
+ pw_context_emit_destroy(context);
+
+ spa_list_consume(core, &context->core_list, link)
+ pw_core_disconnect(core);
+
+ spa_list_consume(client, &context->client_list, link)
+ pw_impl_client_destroy(client);
+
+ spa_list_consume(node, &context->node_list, link)
+ pw_impl_node_destroy(node);
+
+ spa_list_consume(device, &context->device_list, link)
+ pw_impl_device_destroy(device);
+
+ spa_list_consume(resource, &context->registry_resource_list, link)
+ pw_resource_destroy(resource);
+
+ if (context->data_loop_impl)
+ pw_data_loop_stop(context->data_loop_impl);
+
+ spa_list_consume(module, &context->module_list, link)
+ pw_impl_module_destroy(module);
+
+ spa_list_consume(global, &context->global_list, link)
+ pw_global_destroy(global);
+
+ spa_list_consume(metadata, &context->metadata_list, link)
+ pw_impl_metadata_destroy(metadata);
+
+ spa_list_consume(core_impl, &context->core_impl_list, link)
+ pw_impl_core_destroy(core_impl);
+
+ pw_log_debug("%p: free", context);
+ pw_context_emit_free(context);
+
+ if (context->data_loop_impl)
+ pw_data_loop_destroy(context->data_loop_impl);
+
+ if (context->pool)
+ pw_mempool_destroy(context->pool);
+
+ if (context->work_queue)
+ pw_work_queue_destroy(context->work_queue);
+
+ pw_properties_free(context->properties);
+ pw_properties_free(context->conf);
+
+ pw_settings_clean(context);
+
+ if (impl->dbus_handle)
+ pw_unload_spa_handle(impl->dbus_handle);
+
+ pw_array_for_each(entry, &context->factory_lib) {
+ regfree(&entry->regex);
+ free(entry->lib);
+ }
+ pw_array_clear(&context->factory_lib);
+
+ pw_array_clear(&context->objects);
+
+ pw_map_clear(&context->globals);
+
+ spa_hook_list_clean(&context->listener_list);
+ spa_hook_list_clean(&context->driver_listener_list);
+
+ free(context);
+}
+
+SPA_EXPORT
+void *pw_context_get_user_data(struct pw_context *context)
+{
+ return context->user_data;
+}
+
+SPA_EXPORT
+void pw_context_add_listener(struct pw_context *context,
+ struct spa_hook *listener,
+ const struct pw_context_events *events,
+ void *data)
+{
+ spa_hook_list_append(&context->listener_list, listener, events, data);
+}
+
+SPA_EXPORT
+const struct spa_support *pw_context_get_support(struct pw_context *context, uint32_t *n_support)
+{
+ *n_support = context->n_support;
+ return context->support;
+}
+
+SPA_EXPORT
+struct pw_loop *pw_context_get_main_loop(struct pw_context *context)
+{
+ return context->main_loop;
+}
+
+SPA_EXPORT
+struct pw_data_loop *pw_context_get_data_loop(struct pw_context *context)
+{
+ return context->data_loop_impl;
+}
+
+SPA_EXPORT
+struct pw_work_queue *pw_context_get_work_queue(struct pw_context *context)
+{
+ return context->work_queue;
+}
+
+SPA_EXPORT
+const struct pw_properties *pw_context_get_properties(struct pw_context *context)
+{
+ return context->properties;
+}
+
+SPA_EXPORT
+const char *pw_context_get_conf_section(struct pw_context *context, const char *section)
+{
+ return pw_properties_get(context->conf, section);
+}
+
+/** Update context properties
+ *
+ * \param context a context
+ * \param dict properties to update
+ *
+ * Update the context object with the given properties
+ */
+SPA_EXPORT
+int pw_context_update_properties(struct pw_context *context, const struct spa_dict *dict)
+{
+ int changed;
+
+ changed = pw_properties_update(context->properties, dict);
+ pw_log_debug("%p: updated %d properties", context, changed);
+
+ return changed;
+}
+
+static bool global_can_read(struct pw_context *context, struct pw_global *global)
+{
+ if (context->current_client &&
+ !PW_PERM_IS_R(pw_global_get_permissions(global, context->current_client)))
+ return false;
+ return true;
+}
+
+SPA_EXPORT
+int pw_context_for_each_global(struct pw_context *context,
+ int (*callback) (void *data, struct pw_global *global),
+ void *data)
+{
+ struct pw_global *g, *t;
+ int res;
+
+ spa_list_for_each_safe(g, t, &context->global_list, link) {
+ if (!global_can_read(context, g))
+ continue;
+ if ((res = callback(data, g)) != 0)
+ return res;
+ }
+ return 0;
+}
+
+SPA_EXPORT
+struct pw_global *pw_context_find_global(struct pw_context *context, uint32_t id)
+{
+ struct pw_global *global;
+
+ global = pw_map_lookup(&context->globals, id);
+ if (global == NULL || !global->registered) {
+ errno = ENOENT;
+ return NULL;
+ }
+
+ if (!global_can_read(context, global)) {
+ errno = EACCES;
+ return NULL;
+ }
+ return global;
+}
+
+SPA_PRINTF_FUNC(7, 8) int pw_context_debug_port_params(struct pw_context *this,
+ struct spa_node *node, enum spa_direction direction,
+ uint32_t port_id, uint32_t id, int err, const char *debug, ...)
+{
+ struct spa_pod_builder b = { 0 };
+ uint8_t buffer[4096];
+ uint32_t state;
+ struct spa_pod *param;
+ int res;
+ va_list args;
+
+ va_start(args, debug);
+ vsnprintf((char*)buffer, sizeof(buffer), debug, args);
+ va_end(args);
+
+ pw_log_error("params %s: %d:%d %s (%s)",
+ spa_debug_type_find_name(spa_type_param, id),
+ direction, port_id, spa_strerror(err), buffer);
+
+ if (err == -EBUSY)
+ return 0;
+
+ state = 0;
+ while (true) {
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ res = spa_node_port_enum_params_sync(node,
+ direction, port_id,
+ id, &state,
+ NULL, &param, &b);
+ if (res != 1) {
+ if (res < 0)
+ pw_log_error(" error: %s", spa_strerror(res));
+ break;
+ }
+ pw_log_pod(SPA_LOG_LEVEL_ERROR, param);
+ }
+ return 0;
+}
+
+/** Find a common format between two ports
+ *
+ * \param context a context object
+ * \param output an output port
+ * \param input an input port
+ * \param props extra properties
+ * \param n_format_filters number of format filters
+ * \param format_filters array of format filters
+ * \param[out] format the common format between the ports
+ * \param builder builder to use for processing
+ * \param[out] error an error when something is wrong
+ * \return a common format of NULL on error
+ *
+ * Find a common format between the given ports. The format will
+ * be restricted to a subset given with the format filters.
+ */
+int pw_context_find_format(struct pw_context *context,
+ struct pw_impl_port *output,
+ struct pw_impl_port *input,
+ struct pw_properties *props,
+ uint32_t n_format_filters,
+ struct spa_pod **format_filters,
+ struct spa_pod **format,
+ struct spa_pod_builder *builder,
+ char **error)
+{
+ uint32_t out_state, in_state;
+ int res;
+ uint32_t iidx = 0, oidx = 0;
+ struct spa_pod_builder fb = { 0 };
+ uint8_t fbuf[4096];
+ struct spa_pod *filter;
+
+ out_state = output->state;
+ in_state = input->state;
+
+ pw_log_debug("%p: finding best format %d %d", context, out_state, in_state);
+
+ /* when a port is configured but the node is idle, we can reconfigure with a different format */
+ if (out_state > PW_IMPL_PORT_STATE_CONFIGURE && output->node->info.state == PW_NODE_STATE_IDLE)
+ out_state = PW_IMPL_PORT_STATE_CONFIGURE;
+ if (in_state > PW_IMPL_PORT_STATE_CONFIGURE && input->node->info.state == PW_NODE_STATE_IDLE)
+ in_state = PW_IMPL_PORT_STATE_CONFIGURE;
+
+ pw_log_debug("%p: states %d %d", context, out_state, in_state);
+
+ if (in_state == PW_IMPL_PORT_STATE_CONFIGURE && out_state > PW_IMPL_PORT_STATE_CONFIGURE) {
+ /* only input needs format */
+ spa_pod_builder_init(&fb, fbuf, sizeof(fbuf));
+ if ((res = spa_node_port_enum_params_sync(output->node->node,
+ output->direction, output->port_id,
+ SPA_PARAM_Format, &oidx,
+ NULL, &filter, &fb)) != 1) {
+ if (res < 0)
+ *error = spa_aprintf("error get output format: %s", spa_strerror(res));
+ else
+ *error = spa_aprintf("no output formats");
+ goto error;
+ }
+ pw_log_debug("%p: Got output format:", context);
+ pw_log_format(SPA_LOG_LEVEL_DEBUG, filter);
+
+ if ((res = spa_node_port_enum_params_sync(input->node->node,
+ input->direction, input->port_id,
+ SPA_PARAM_EnumFormat, &iidx,
+ filter, format, builder)) <= 0) {
+ if (res == -ENOENT || res == 0) {
+ pw_log_debug("%p: no input format filter, using output format: %s",
+ context, spa_strerror(res));
+ *format = filter;
+ } else {
+ *error = spa_aprintf("error input enum formats: %s", spa_strerror(res));
+ goto error;
+ }
+ }
+ } else if (out_state >= PW_IMPL_PORT_STATE_CONFIGURE && in_state > PW_IMPL_PORT_STATE_CONFIGURE) {
+ /* only output needs format */
+ spa_pod_builder_init(&fb, fbuf, sizeof(fbuf));
+ if ((res = spa_node_port_enum_params_sync(input->node->node,
+ input->direction, input->port_id,
+ SPA_PARAM_Format, &iidx,
+ NULL, &filter, &fb)) != 1) {
+ if (res < 0)
+ *error = spa_aprintf("error get input format: %s", spa_strerror(res));
+ else
+ *error = spa_aprintf("no input format");
+ goto error;
+ }
+ pw_log_debug("%p: Got input format:", context);
+ pw_log_format(SPA_LOG_LEVEL_DEBUG, filter);
+
+ if ((res = spa_node_port_enum_params_sync(output->node->node,
+ output->direction, output->port_id,
+ SPA_PARAM_EnumFormat, &oidx,
+ filter, format, builder)) <= 0) {
+ if (res == -ENOENT || res == 0) {
+ pw_log_debug("%p: no output format filter, using input format: %s",
+ context, spa_strerror(res));
+ *format = filter;
+ } else {
+ *error = spa_aprintf("error output enum formats: %s", spa_strerror(res));
+ goto error;
+ }
+ }
+ } else if (in_state == PW_IMPL_PORT_STATE_CONFIGURE && out_state == PW_IMPL_PORT_STATE_CONFIGURE) {
+ again:
+ /* both ports need a format */
+ pw_log_debug("%p: do enum input %d", context, iidx);
+ spa_pod_builder_init(&fb, fbuf, sizeof(fbuf));
+ if ((res = spa_node_port_enum_params_sync(input->node->node,
+ input->direction, input->port_id,
+ SPA_PARAM_EnumFormat, &iidx,
+ NULL, &filter, &fb)) != 1) {
+ if (res == -ENOENT) {
+ pw_log_debug("%p: no input filter", context);
+ filter = NULL;
+ } else {
+ if (res < 0)
+ *error = spa_aprintf("error input enum formats: %s", spa_strerror(res));
+ else
+ *error = spa_aprintf("no more input formats");
+ goto error;
+ }
+ }
+ pw_log_debug("%p: enum output %d with filter: %p", context, oidx, filter);
+ pw_log_format(SPA_LOG_LEVEL_DEBUG, filter);
+
+ if ((res = spa_node_port_enum_params_sync(output->node->node,
+ output->direction, output->port_id,
+ SPA_PARAM_EnumFormat, &oidx,
+ filter, format, builder)) != 1) {
+ if (res == 0 && filter != NULL) {
+ oidx = 0;
+ goto again;
+ }
+ *error = spa_aprintf("error output enum formats: %s", spa_strerror(res));
+ goto error;
+ }
+
+ pw_log_debug("%p: Got filtered:", context);
+ pw_log_format(SPA_LOG_LEVEL_DEBUG, *format);
+ } else {
+ res = -EBADF;
+ *error = spa_aprintf("error bad node state");
+ goto error;
+ }
+ return res;
+error:
+ if (res == 0)
+ res = -EINVAL;
+ return res;
+}
+
+static int ensure_state(struct pw_impl_node *node, bool running)
+{
+ enum pw_node_state state = node->info.state;
+ if (node->active && !SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_NEED_CONFIGURE) && running)
+ state = PW_NODE_STATE_RUNNING;
+ else if (state > PW_NODE_STATE_IDLE)
+ state = PW_NODE_STATE_IDLE;
+ return pw_impl_node_set_state(node, state);
+}
+
+static int collect_nodes(struct pw_context *context, struct pw_impl_node *node, struct spa_list *collect)
+{
+ struct spa_list queue;
+ struct pw_impl_node *n, *t;
+ struct pw_impl_port *p;
+ struct pw_impl_link *l;
+
+ pw_log_debug("node %p: '%s'", node, node->name);
+
+ /* start with node in the queue */
+ spa_list_init(&queue);
+ spa_list_append(&queue, &node->sort_link);
+ node->visited = true;
+
+ /* now follow all the links from the nodes in the queue
+ * and add the peers to the queue. */
+ spa_list_consume(n, &queue, sort_link) {
+ pw_log_debug(" next node %p: '%s'", n, n->name);
+
+ spa_list_remove(&n->sort_link);
+ spa_list_append(collect, &n->sort_link);
+ n->passive = true;
+
+ if (!n->active)
+ continue;
+
+ spa_list_for_each(p, &n->input_ports, link) {
+ spa_list_for_each(l, &p->links, input_link) {
+ t = l->output->node;
+
+ if (!t->active)
+ continue;
+
+ pw_impl_link_prepare(l);
+
+ if (!l->prepared)
+ continue;
+
+ if (!l->passive)
+ node->passive = n->passive = false;
+
+ if (!t->visited) {
+ t->visited = true;
+ spa_list_append(&queue, &t->sort_link);
+ }
+ }
+ }
+ spa_list_for_each(p, &n->output_ports, link) {
+ spa_list_for_each(l, &p->links, output_link) {
+ t = l->input->node;
+
+ if (!t->active)
+ continue;
+
+ pw_impl_link_prepare(l);
+
+ if (!l->prepared)
+ continue;
+
+ if (!l->passive)
+ node->passive = n->passive = false;
+
+ if (!t->visited) {
+ t->visited = true;
+ spa_list_append(&queue, &t->sort_link);
+ }
+ }
+ }
+ /* now go through all the nodes that have the same group and
+ * that are not yet visited */
+ if (n->group[0] == '\0')
+ continue;
+
+ spa_list_for_each(t, &context->node_list, link) {
+ if (t->exported || t == n || !t->active || t->visited)
+ continue;
+ if (!spa_streq(t->group, n->group))
+ continue;
+ pw_log_debug("%p join group %s: '%s'", t, t->group, n->group);
+ t->visited = true;
+ spa_list_append(&queue, &t->sort_link);
+ }
+ }
+ return 0;
+}
+
+static void move_to_driver(struct pw_context *context, struct spa_list *nodes,
+ struct pw_impl_node *driver)
+{
+ struct pw_impl_node *n;
+ pw_log_debug("driver: %p %s", driver, driver->name);
+ spa_list_consume(n, nodes, sort_link) {
+ spa_list_remove(&n->sort_link);
+ pw_log_debug(" follower: %p %s", n, n->name);
+ pw_impl_node_set_driver(n, driver);
+ }
+}
+static void remove_from_driver(struct pw_context *context, struct spa_list *nodes)
+{
+ struct pw_impl_node *n;
+ spa_list_consume(n, nodes, sort_link) {
+ spa_list_remove(&n->sort_link);
+ pw_impl_node_set_driver(n, NULL);
+ ensure_state(n, false);
+ }
+}
+
+static inline void get_quantums(struct pw_context *context, uint32_t *def,
+ uint32_t *min, uint32_t *max, uint32_t *limit, uint32_t *rate)
+{
+ struct settings *s = &context->settings;
+ if (s->clock_force_quantum != 0) {
+ *def = *min = *max = s->clock_force_quantum;
+ *rate = 0;
+ } else {
+ *def = s->clock_quantum;
+ *min = s->clock_min_quantum;
+ *max = s->clock_max_quantum;
+ *rate = s->clock_rate;
+ }
+ *limit = s->clock_quantum_limit;
+}
+
+static inline const uint32_t *get_rates(struct pw_context *context, uint32_t *def, uint32_t *n_rates,
+ bool *force)
+{
+ struct settings *s = &context->settings;
+ if (s->clock_force_rate != 0) {
+ *force = true;
+ *n_rates = 1;
+ *def = s->clock_force_rate;
+ return &s->clock_force_rate;
+ } else {
+ *force = false;
+ *n_rates = s->n_clock_rates;
+ *def = s->clock_rate;
+ return s->clock_rates;
+ }
+}
+static void reconfigure_driver(struct pw_context *context, struct pw_impl_node *n)
+{
+ struct pw_impl_node *s;
+
+ spa_list_for_each(s, &n->follower_list, follower_link) {
+ if (s == n)
+ continue;
+ pw_log_debug("%p: follower %p: '%s' suspend",
+ context, s, s->name);
+ pw_impl_node_set_state(s, PW_NODE_STATE_SUSPENDED);
+ }
+ pw_log_debug("%p: driver %p: '%s' suspend",
+ context, n, n->name);
+
+ if (n->info.state >= PW_NODE_STATE_IDLE)
+ n->reconfigure = true;
+ pw_impl_node_set_state(n, PW_NODE_STATE_SUSPENDED);
+}
+
+/* find smaller power of 2 */
+static uint32_t flp2(uint32_t x)
+{
+ x = x | (x >> 1);
+ x = x | (x >> 2);
+ x = x | (x >> 4);
+ x = x | (x >> 8);
+ x = x | (x >> 16);
+ return x - (x >> 1);
+}
+
+/* cmp fractions, avoiding overflows */
+static int fraction_compare(const struct spa_fraction *a, const struct spa_fraction *b)
+{
+ uint64_t fa = (uint64_t)a->num * (uint64_t)b->denom;
+ uint64_t fb = (uint64_t)b->num * (uint64_t)a->denom;
+ return fa < fb ? -1 : (fa > fb ? 1 : 0);
+}
+
+static uint32_t find_best_rate(const uint32_t *rates, uint32_t n_rates, uint32_t rate, uint32_t best)
+{
+ uint32_t i;
+ for (i = 0; i < n_rates; i++) {
+ if (SPA_ABS((int32_t)rate - (int32_t)rates[i]) <
+ SPA_ABS((int32_t)rate - (int32_t)best))
+ best = rates[i];
+ }
+ return best;
+}
+
+/* here we evaluate the complete state of the graph.
+ *
+ * It roughly operates in 3 stages:
+ *
+ * 1. go over all drivers and collect the nodes that need to be scheduled with the
+ * driver. This include all nodes that have an active link with the driver or
+ * with a node already scheduled with the driver.
+ *
+ * 2. go over all nodes that are not assigned to a driver. The ones that require
+ * a driver are moved to some random active driver found in step 1.
+ *
+ * 3. go over all drivers again, collect the quantum/rate of all followers, select
+ * the desired final value and activate the followers and then the driver.
+ *
+ * A complete graph evaluation is performed for each change that is made to the
+ * graph, such as making/destroying links, adding/removing nodes, property changes such
+ * as quantum/rate changes or metadata changes.
+ */
+int pw_context_recalc_graph(struct pw_context *context, const char *reason)
+{
+ struct impl *impl = SPA_CONTAINER_OF(context, struct impl, this);
+ struct settings *settings = &context->settings;
+ struct pw_impl_node *n, *s, *target, *fallback;
+ const uint32_t *rates;
+ uint32_t max_quantum, min_quantum, def_quantum, lim_quantum, rate_quantum;
+ uint32_t n_rates, def_rate;
+ bool freewheel = false, global_force_rate, global_force_quantum;
+ struct spa_list collect;
+
+ pw_log_info("%p: busy:%d reason:%s", context, impl->recalc, reason);
+
+ if (impl->recalc) {
+ impl->recalc_pending = true;
+ return -EBUSY;
+ }
+
+again:
+ impl->recalc = true;
+
+ get_quantums(context, &def_quantum, &min_quantum, &max_quantum, &lim_quantum, &rate_quantum);
+ rates = get_rates(context, &def_rate, &n_rates, &global_force_rate);
+
+ global_force_quantum = rate_quantum == 0;
+
+ /* start from all drivers and group all nodes that are linked
+ * to it. Some nodes are not (yet) linked to anything and they
+ * will end up 'unassigned' to a driver. Other nodes are drivers
+ * and if they have active followers, we can use them to schedule
+ * the unassigned nodes. */
+ target = fallback = NULL;
+ spa_list_for_each(n, &context->driver_list, driver_link) {
+ if (n->exported)
+ continue;
+
+ if (!n->visited) {
+ spa_list_init(&collect);
+ collect_nodes(context, n, &collect);
+ move_to_driver(context, &collect, n);
+ }
+ /* from now on we are only interested in active driving nodes.
+ * We're going to see if there are active followers. */
+ if (!n->driving || !n->active)
+ continue;
+
+ /* first active driving node is fallback */
+ if (fallback == NULL)
+ fallback = n;
+
+ if (n->passive)
+ continue;
+
+ spa_list_for_each(s, &n->follower_list, follower_link) {
+ pw_log_debug("%p: driver %p: follower %p %s: active:%d",
+ context, n, s, s->name, s->active);
+ if (s != n && s->active) {
+ /* if the driving node has active followers, it
+ * is a target for our unassigned nodes */
+ if (target == NULL)
+ target = n;
+ if (n->freewheel)
+ freewheel = true;
+ break;
+ }
+ }
+ }
+ /* no active node, use fallback driving node */
+ if (target == NULL)
+ target = fallback;
+
+ /* update the freewheel status */
+ if (context->freewheeling != freewheel)
+ context_set_freewheel(context, freewheel);
+
+ /* now go through all available nodes. The ones we didn't visit
+ * in collect_nodes() are not linked to any driver. We assign them
+ * to either an active driver or the first driver if they are in a
+ * group that needs a driver. Else we remove them from a driver
+ * and stop them. */
+ spa_list_for_each(n, &context->node_list, link) {
+ struct pw_impl_node *t, *driver;
+
+ if (n->exported || n->visited)
+ continue;
+
+ pw_log_debug("%p: unassigned node %p: '%s' active:%d want_driver:%d target:%p",
+ context, n, n->name, n->active, n->want_driver, target);
+
+ /* collect all nodes in this group */
+ spa_list_init(&collect);
+ collect_nodes(context, n, &collect);
+
+ driver = NULL;
+ spa_list_for_each(t, &collect, sort_link) {
+ /* is any active and want a driver or it want process */
+ if ((t->want_driver && t->active && !n->passive) ||
+ t->always_process)
+ driver = target;
+ }
+ if (driver != NULL) {
+ /* driver needed for this group */
+ driver->passive = false;
+ move_to_driver(context, &collect, driver);
+ } else {
+ /* no driver, make sure the nodes stops */
+ remove_from_driver(context, &collect);
+ }
+ }
+ /* clean up the visited flag now */
+ spa_list_for_each(n, &context->node_list, link)
+ n->visited = false;
+
+ /* assign final quantum and set state for followers and drivers */
+ spa_list_for_each(n, &context->driver_list, driver_link) {
+ bool running = false, lock_quantum = false, lock_rate = false;
+ struct spa_fraction latency = SPA_FRACTION(0, 0);
+ struct spa_fraction max_latency = SPA_FRACTION(0, 0);
+ struct spa_fraction rate = SPA_FRACTION(0, 0);
+ uint32_t quantum, target_rate, current_rate;
+ uint64_t quantum_stamp = 0, rate_stamp = 0;
+ bool force_rate, force_quantum;
+ const uint32_t *node_rates;
+ uint32_t node_n_rates, node_def_rate;
+ uint32_t node_max_quantum, node_min_quantum, node_def_quantum, node_rate_quantum;
+
+ if (!n->driving || n->exported)
+ continue;
+
+ node_def_quantum = def_quantum;
+ node_min_quantum = min_quantum;
+ node_max_quantum = max_quantum;
+ node_rate_quantum = rate_quantum;
+ force_quantum = global_force_quantum;
+
+ node_def_rate = def_rate;
+ node_n_rates = n_rates;
+ node_rates = rates;
+ force_rate = global_force_rate;
+
+ /* collect quantum and rate */
+ spa_list_for_each(s, &n->follower_list, follower_link) {
+
+ if (!s->moved) {
+ /* We only try to enforce the lock flags for nodes that
+ * are not recently moved between drivers. The nodes that
+ * are moved should try to enforce their quantum on the
+ * new driver. */
+ lock_quantum |= s->lock_quantum;
+ lock_rate |= s->lock_rate;
+ }
+ if (!global_force_quantum && s->force_quantum > 0 &&
+ s->stamp > quantum_stamp) {
+ node_def_quantum = node_min_quantum = node_max_quantum = s->force_quantum;
+ node_rate_quantum = 0;
+ quantum_stamp = s->stamp;
+ force_quantum = true;
+ }
+ if (!global_force_rate && s->force_rate > 0 &&
+ s->stamp > rate_stamp) {
+ node_def_rate = s->force_rate;
+ node_n_rates = 1;
+ node_rates = &s->force_rate;
+ force_rate = true;
+ rate_stamp = s->stamp;
+ }
+
+ /* smallest latencies */
+ if (latency.denom == 0 ||
+ (s->latency.denom > 0 &&
+ fraction_compare(&s->latency, &latency) < 0))
+ latency = s->latency;
+ if (max_latency.denom == 0 ||
+ (s->max_latency.denom > 0 &&
+ fraction_compare(&s->max_latency, &max_latency) < 0))
+ max_latency = s->max_latency;
+
+ /* largest rate */
+ if (rate.denom == 0 ||
+ (s->rate.denom > 0 &&
+ fraction_compare(&s->rate, &rate) > 0))
+ rate = s->rate;
+
+ if (s->active)
+ running = !n->passive;
+
+ pw_log_debug("%p: follower %p running:%d passive:%d rate:%u/%u latency %u/%u '%s'",
+ context, s, running, s->passive, rate.num, rate.denom,
+ latency.num, latency.denom, s->name);
+
+ s->moved = false;
+ }
+
+ if (force_quantum)
+ lock_quantum = false;
+ if (force_rate)
+ lock_rate = false;
+
+ if (n->reconfigure)
+ running = true;
+
+ current_rate = n->current_rate.denom;
+ if (lock_rate || n->reconfigure ||
+ (!force_rate &&
+ (n->info.state > PW_NODE_STATE_IDLE)))
+ /* when someone wants us to lock the rate of this driver or
+ * when the driver is busy and we don't need to force a rate,
+ * keep the current rate */
+ target_rate = current_rate;
+ else {
+ /* 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);
+ }
+
+ if (target_rate != current_rate) {
+ bool do_reconfigure = false;
+ /* we doing a rate switch */
+ pw_log_info("(%s-%u) state:%s new rate:%u->%u",
+ n->name, n->info.id,
+ pw_node_state_as_string(n->info.state),
+ n->current_rate.denom,
+ target_rate);
+
+ if (force_rate) {
+ if (settings->clock_rate_update_mode == CLOCK_RATE_UPDATE_MODE_HARD)
+ do_reconfigure = true;
+ } else {
+ if (n->info.state >= PW_NODE_STATE_SUSPENDED)
+ do_reconfigure = true;
+ }
+ if (do_reconfigure)
+ reconfigure_driver(context, n);
+
+ /* we're setting the pending rate. This will become the new
+ * current rate in the next iteration of the graph. */
+ n->current_rate = SPA_FRACTION(1, target_rate);
+ n->current_pending = true;
+ current_rate = target_rate;
+ /* we might be suspended now and the links need to be prepared again */
+ if (do_reconfigure)
+ goto 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;
+ }
+
+ /* calculate desired quantum */
+ if (max_latency.denom != 0) {
+ uint32_t tmp = (max_latency.num * current_rate / max_latency.denom);
+ if (tmp < node_max_quantum)
+ node_max_quantum = tmp;
+ }
+
+ quantum = node_def_quantum;
+ if (latency.denom != 0)
+ quantum = (latency.num * current_rate / latency.denom);
+ quantum = SPA_CLAMP(quantum, node_min_quantum, node_max_quantum);
+ quantum = SPA_MIN(quantum, lim_quantum);
+
+ if (settings->clock_power_of_two_quantum)
+ quantum = flp2(quantum);
+
+ if (running && quantum != n->current_quantum && !lock_quantum) {
+ pw_log_info("(%s-%u) new quantum:%"PRIu64"->%u",
+ n->name, n->info.id,
+ n->current_quantum,
+ quantum);
+ /* this is the new pending quantum */
+ n->current_quantum = quantum;
+ n->current_pending = true;
+ }
+
+ if (n->info.state < PW_NODE_STATE_RUNNING && n->current_pending) {
+ /* the driver node is not actually running and we have a
+ * pending change. Apply the change to the position now so
+ * that we have the right values when we change the node
+ * states of the driver and followers to RUNNING below */
+ pw_log_debug("%p: apply duration:%"PRIu64" rate:%u/%u", context,
+ n->current_quantum, n->current_rate.num,
+ n->current_rate.denom);
+ n->rt.position->clock.duration = n->current_quantum;
+ n->rt.position->clock.rate = n->current_rate;
+ n->current_pending = false;
+ }
+
+ pw_log_debug("%p: driver %p running:%d passive:%d quantum:%u '%s'",
+ context, n, running, n->passive, quantum, n->name);
+
+ /* first change the node states of the followers to the new target */
+ spa_list_for_each(s, &n->follower_list, follower_link) {
+ if (s == n)
+ continue;
+ pw_log_debug("%p: follower %p: active:%d '%s'",
+ context, s, s->active, s->name);
+ ensure_state(s, running);
+ }
+ /* now that all the followers are ready, start the driver */
+ ensure_state(n, running);
+ }
+ impl->recalc = false;
+ if (impl->recalc_pending) {
+ impl->recalc_pending = false;
+ goto again;
+ }
+
+ return 0;
+}
+
+SPA_EXPORT
+int pw_context_add_spa_lib(struct pw_context *context,
+ const char *factory_regexp, const char *lib)
+{
+ struct factory_entry *entry;
+ int err;
+
+ entry = pw_array_add(&context->factory_lib, sizeof(*entry));
+ if (entry == NULL)
+ return -errno;
+
+ if ((err = regcomp(&entry->regex, factory_regexp, REG_EXTENDED | REG_NOSUB)) != 0) {
+ char errbuf[1024];
+ regerror(err, &entry->regex, errbuf, sizeof(errbuf));
+ pw_log_error("%p: can compile regex: %s", context, errbuf);
+ pw_array_remove(&context->factory_lib, entry);
+ return -EINVAL;
+ }
+
+ entry->lib = strdup(lib);
+ pw_log_debug("%p: map factory regex '%s' to '%s", context,
+ factory_regexp, lib);
+ return 0;
+}
+
+SPA_EXPORT
+const char *pw_context_find_spa_lib(struct pw_context *context, const char *factory_name)
+{
+ struct factory_entry *entry;
+
+ pw_array_for_each(entry, &context->factory_lib) {
+ if (regexec(&entry->regex, factory_name, 0, NULL, 0) == 0)
+ return entry->lib;
+ }
+ return NULL;
+}
+
+SPA_EXPORT
+struct spa_handle *pw_context_load_spa_handle(struct pw_context *context,
+ const char *factory_name,
+ const struct spa_dict *info)
+{
+ const char *lib;
+ const struct spa_support *support;
+ uint32_t n_support;
+ struct spa_handle *handle;
+
+ pw_log_debug("%p: load factory %s", context, factory_name);
+
+ lib = pw_context_find_spa_lib(context, factory_name);
+ if (lib == NULL && info != NULL)
+ lib = spa_dict_lookup(info, SPA_KEY_LIBRARY_NAME);
+ if (lib == NULL) {
+ errno = ENOENT;
+ pw_log_warn("%p: no library for %s: %m",
+ context, factory_name);
+ return NULL;
+ }
+
+ support = pw_context_get_support(context, &n_support);
+
+ handle = pw_load_spa_handle(lib, factory_name,
+ info, n_support, support);
+
+ return handle;
+}
+
+SPA_EXPORT
+int pw_context_register_export_type(struct pw_context *context, struct pw_export_type *type)
+{
+ if (pw_context_find_export_type(context, type->type)) {
+ pw_log_warn("context %p: duplicate export type %s", context, type->type);
+ return -EEXIST;
+ }
+ pw_log_debug("context %p: Add export type %s to context", context, type->type);
+ spa_list_append(&context->export_list, &type->link);
+ return 0;
+}
+
+SPA_EXPORT
+const struct pw_export_type *pw_context_find_export_type(struct pw_context *context, const char *type)
+{
+ const struct pw_export_type *t;
+ spa_list_for_each(t, &context->export_list, link) {
+ if (spa_streq(t->type, type))
+ return t;
+ }
+ return NULL;
+}
+
+struct object_entry {
+ const char *type;
+ void *value;
+};
+
+static struct object_entry *find_object(struct pw_context *context, const char *type)
+{
+ struct object_entry *entry;
+ pw_array_for_each(entry, &context->objects) {
+ if (spa_streq(entry->type, type))
+ return entry;
+ }
+ return NULL;
+}
+
+SPA_EXPORT
+int pw_context_set_object(struct pw_context *context, const char *type, void *value)
+{
+ struct object_entry *entry;
+
+ entry = find_object(context, type);
+
+ if (value == NULL) {
+ if (entry)
+ pw_array_remove(&context->objects, entry);
+ } else {
+ if (entry == NULL) {
+ entry = pw_array_add(&context->objects, sizeof(*entry));
+ if (entry == NULL)
+ return -errno;
+ entry->type = type;
+ }
+ entry->value = value;
+ }
+ if (spa_streq(type, SPA_TYPE_INTERFACE_ThreadUtils)) {
+ context->thread_utils = value;
+ if (context->data_loop_impl)
+ pw_data_loop_set_thread_utils(context->data_loop_impl,
+ context->thread_utils);
+ }
+ return 0;
+}
+
+SPA_EXPORT
+void *pw_context_get_object(struct pw_context *context, const char *type)
+{
+ struct object_entry *entry;
+
+ if ((entry = find_object(context, type)) != NULL)
+ return entry->value;
+
+ return NULL;
+}
diff --git a/src/pipewire/context.h b/src/pipewire/context.h
new file mode 100644
index 0000000..fe658a6
--- /dev/null
+++ b/src/pipewire/context.h
@@ -0,0 +1,195 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_CONTEXT_H
+#define PIPEWIRE_CONTEXT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+/** \defgroup pw_context Context
+ *
+ * \brief The PipeWire context object manages all locally available
+ * resources. It is used by both clients and servers.
+ *
+ * The context is used to:
+ *
+ * - Load modules and extend the functionality. This includes
+ * extending the protocol with new object types or creating
+ * any of the available objects.
+ *
+ * - Create implementations of various objects like nodes,
+ * devices, factories, modules, etc.. This will usually also
+ * create pw_global objects that can then be shared with
+ * clients.
+ *
+ * - Connect to another PipeWire instance (the main daemon, for
+ * example) and interact with it (See \ref page_core_api).
+ *
+ * - Export a local implementation of an object to another
+ * instance.
+ */
+
+/**
+ * \addtogroup pw_context
+ * @{
+ */
+struct pw_context;
+
+struct pw_global;
+struct pw_impl_client;
+
+#include <pipewire/core.h>
+#include <pipewire/loop.h>
+#include <pipewire/properties.h>
+
+/** context events emitted by the context object added with \ref pw_context_add_listener */
+struct pw_context_events {
+#define PW_VERSION_CONTEXT_EVENTS 0
+ uint32_t version;
+
+ /** The context is being destroyed */
+ void (*destroy) (void *data);
+ /** The context is being freed */
+ void (*free) (void *data);
+ /** a new client object is added */
+ void (*check_access) (void *data, struct pw_impl_client *client);
+ /** a new global object was added */
+ void (*global_added) (void *data, struct pw_global *global);
+ /** a global object was removed */
+ void (*global_removed) (void *data, struct pw_global *global);
+};
+
+/** Make a new context object for a given main_loop. Ownership of the properties is taken */
+struct pw_context * pw_context_new(struct pw_loop *main_loop, /**< a main loop to run in */
+ struct pw_properties *props, /**< extra properties */
+ size_t user_data_size /**< extra user data size */);
+
+/** destroy a context object, all resources except the main_loop will be destroyed */
+void pw_context_destroy(struct pw_context *context);
+
+/** Get the context user data */
+void *pw_context_get_user_data(struct pw_context *context);
+
+/** Add a new event listener to a context */
+void pw_context_add_listener(struct pw_context *context,
+ struct spa_hook *listener,
+ const struct pw_context_events *events,
+ void *data);
+
+/** Get the context properties */
+const struct pw_properties *pw_context_get_properties(struct pw_context *context);
+
+/** Update the context properties */
+int pw_context_update_properties(struct pw_context *context, const struct spa_dict *dict);
+
+/** Get a config section for this context. Since 0.3.22, deprecated,
+ * use pw_context_conf_section_for_each(). */
+const char *pw_context_get_conf_section(struct pw_context *context, const char *section);
+/** Parse a standard config section for this context. Since 0.3.22 */
+int pw_context_parse_conf_section(struct pw_context *context,
+ struct pw_properties *conf, const char *section);
+
+/** update properties from a section into props. Since 0.3.45 */
+int pw_context_conf_update_props(struct pw_context *context, const char *section,
+ struct pw_properties *props);
+/** emit callback for all config sections. Since 0.3.45 */
+int pw_context_conf_section_for_each(struct pw_context *context, const char *section,
+ int (*callback) (void *data, const char *location, const char *section,
+ const char *str, size_t len),
+ void *data);
+/** emit callback for all matched properties. Since 0.3.46 */
+int pw_context_conf_section_match_rules(struct pw_context *context, const char *section,
+ const struct spa_dict *props,
+ int (*callback) (void *data, const char *location, const char *action,
+ const char *str, size_t len),
+ void *data);
+
+/** Get the context support objects */
+const struct spa_support *pw_context_get_support(struct pw_context *context, uint32_t *n_support);
+
+/** get the context main loop */
+struct pw_loop *pw_context_get_main_loop(struct pw_context *context);
+
+/** get the context data loop. Since 0.3.56 */
+struct pw_data_loop *pw_context_get_data_loop(struct pw_context *context);
+
+/** Get the work queue from the context: Since 0.3.26 */
+struct pw_work_queue *pw_context_get_work_queue(struct pw_context *context);
+
+/** Iterate the globals of the context. The callback should return
+ * 0 to fetch the next item, any other value stops the iteration and returns
+ * the value. When all callbacks return 0, this function returns 0 when all
+ * globals are iterated. */
+int pw_context_for_each_global(struct pw_context *context,
+ int (*callback) (void *data, struct pw_global *global),
+ void *data);
+
+/** Find a context global by id */
+struct pw_global *pw_context_find_global(struct pw_context *context, /**< the context */
+ uint32_t id /**< the global id */);
+
+/** add a spa library for the given factory_name regex */
+int pw_context_add_spa_lib(struct pw_context *context, const char *factory_regex, const char *lib);
+
+/** find the library name for a spa factory */
+const char * pw_context_find_spa_lib(struct pw_context *context, const char *factory_name);
+
+struct spa_handle *pw_context_load_spa_handle(struct pw_context *context,
+ const char *factory_name,
+ const struct spa_dict *info);
+
+
+/** data for registering export functions */
+struct pw_export_type {
+ struct spa_list link;
+ const char *type;
+ struct pw_proxy * (*func) (struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size);
+};
+
+/** register a type that can be exported on a context_proxy. This is usually used by
+ * extension modules */
+int pw_context_register_export_type(struct pw_context *context, struct pw_export_type *type);
+/** find information about registered export type */
+const struct pw_export_type *pw_context_find_export_type(struct pw_context *context, const char *type);
+
+/** add an object to the context */
+int pw_context_set_object(struct pw_context *context, const char *type, void *value);
+/** get an object from the context */
+void *pw_context_get_object(struct pw_context *context, const char *type);
+
+/**
+ * \}
+ */
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_CONTEXT_H */
diff --git a/src/pipewire/control.c b/src/pipewire/control.c
new file mode 100644
index 0000000..0e3afd7
--- /dev/null
+++ b/src/pipewire/control.c
@@ -0,0 +1,267 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <spa/pod/parser.h>
+#include <spa/debug/types.h>
+
+#include <pipewire/control.h>
+#include <pipewire/private.h>
+
+#define NAME "control"
+
+struct impl {
+ struct pw_control this;
+
+ struct pw_memblock *mem;
+};
+
+struct pw_control *
+pw_control_new(struct pw_context *context,
+ struct pw_impl_port *port,
+ uint32_t id, uint32_t size,
+ size_t user_data_size)
+{
+ struct impl *impl;
+ struct pw_control *this;
+ enum spa_direction direction;
+
+ switch (id) {
+ case SPA_IO_Control:
+ direction = SPA_DIRECTION_INPUT;
+ break;
+ case SPA_IO_Notify:
+ direction = SPA_DIRECTION_OUTPUT;
+ break;
+ default:
+ errno = ENOTSUP;
+ goto error_exit;
+ }
+
+ impl = calloc(1, sizeof(struct impl) + user_data_size);
+ if (impl == NULL)
+ goto error_exit;
+
+ this = &impl->this;
+ this->id = id;
+ this->size = size;
+
+ pw_log_debug(NAME" %p: new %s %d", this,
+ spa_debug_type_find_name(spa_type_io, this->id), direction);
+
+ this->context = context;
+ this->port = port;
+ this->direction = direction;
+
+ spa_list_init(&this->links);
+
+ if (user_data_size > 0)
+ this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void);
+
+ spa_hook_list_init(&this->listener_list);
+
+ spa_list_append(&context->control_list[direction], &this->link);
+ if (port) {
+ spa_list_append(&port->control_list[direction], &this->port_link);
+ pw_impl_port_emit_control_added(port, this);
+ }
+ return this;
+
+error_exit:
+ return NULL;
+}
+
+void pw_control_destroy(struct pw_control *control)
+{
+ struct impl *impl = SPA_CONTAINER_OF(control, struct impl, this);
+ struct pw_control_link *link;
+
+ pw_log_debug(NAME" %p: destroy", control);
+
+ pw_control_emit_destroy(control);
+
+ if (control->direction == SPA_DIRECTION_OUTPUT) {
+ spa_list_consume(link, &control->links, out_link)
+ pw_control_remove_link(link);
+ }
+ else {
+ spa_list_consume(link, &control->links, in_link)
+ pw_control_remove_link(link);
+ }
+
+ spa_list_remove(&control->link);
+
+ if (control->port) {
+ spa_list_remove(&control->port_link);
+ pw_impl_port_emit_control_removed(control->port, control);
+ }
+
+ pw_log_debug(NAME" %p: free", control);
+ pw_control_emit_free(control);
+
+ spa_hook_list_clean(&control->listener_list);
+
+ if (control->direction == SPA_DIRECTION_OUTPUT) {
+ if (impl->mem)
+ pw_memblock_unref(impl->mem);
+ }
+ free(control);
+}
+
+SPA_EXPORT
+struct pw_impl_port *pw_control_get_port(struct pw_control *control)
+{
+ return control->port;
+}
+
+SPA_EXPORT
+void pw_control_add_listener(struct pw_control *control,
+ struct spa_hook *listener,
+ const struct pw_control_events *events,
+ void *data)
+{
+ spa_hook_list_append(&control->listener_list, listener, events, data);
+}
+
+static int port_set_io(struct pw_impl_port *port, uint32_t mix, uint32_t id, void *data, uint32_t size)
+{
+ int res;
+
+ if (port->mix) {
+ res = spa_node_port_set_io(port->mix, port->direction, mix, id, data, size);
+ if (SPA_RESULT_IS_OK(res))
+ return res;
+ }
+
+ if ((res = spa_node_port_set_io(port->node->node,
+ port->direction, port->port_id,
+ id, data, size)) < 0) {
+ pw_log_warn("port %p: set io failed %d %s", port,
+ res, spa_strerror(res));
+ }
+ return res;
+}
+
+SPA_EXPORT
+int pw_control_add_link(struct pw_control *control, uint32_t cmix,
+ struct pw_control *other, uint32_t omix,
+ struct pw_control_link *link)
+{
+ int res = 0;
+ struct impl *impl;
+ uint32_t size;
+
+ if (control->direction == SPA_DIRECTION_INPUT) {
+ SPA_SWAP(control, other);
+ SPA_SWAP(cmix, omix);
+ }
+ if (control->direction != SPA_DIRECTION_OUTPUT ||
+ other->direction != SPA_DIRECTION_INPUT)
+ return -EINVAL;
+
+ impl = SPA_CONTAINER_OF(control, struct impl, this);
+
+ pw_log_debug(NAME" %p: link to %p %s", control, other,
+ spa_debug_type_find_name(spa_type_io, control->id));
+
+ size = SPA_MAX(control->size, other->size);
+
+ if (impl->mem == NULL) {
+ impl->mem = pw_mempool_alloc(control->context->pool,
+ PW_MEMBLOCK_FLAG_READWRITE |
+ PW_MEMBLOCK_FLAG_SEAL |
+ PW_MEMBLOCK_FLAG_MAP,
+ SPA_DATA_MemFd, size);
+ if (impl->mem == NULL) {
+ res = -errno;
+ goto exit;
+ }
+ }
+
+ if (spa_list_is_empty(&control->links)) {
+ if (control->port) {
+ if ((res = port_set_io(control->port, cmix,
+ control->id,
+ impl->mem->map->ptr, size)) < 0) {
+ pw_log_warn(NAME" %p: set io failed %d %s", control,
+ res, spa_strerror(res));
+ goto exit;
+ }
+ }
+ }
+
+ if (other->port) {
+ if ((res = port_set_io(other->port, omix,
+ other->id, impl->mem->map->ptr, size)) < 0) {
+ pw_log_warn(NAME" %p: set io failed %d %s", control,
+ res, spa_strerror(res));
+ goto exit;
+ }
+ }
+
+ link->output = control;
+ link->input = other;
+ link->out_port = cmix;
+ link->in_port = omix;
+ link->valid = true;
+ spa_list_append(&control->links, &link->out_link);
+ spa_list_append(&other->links, &link->in_link);
+
+ pw_control_emit_linked(control, other);
+ pw_control_emit_linked(other, control);
+exit:
+ return res;
+}
+
+SPA_EXPORT
+int pw_control_remove_link(struct pw_control_link *link)
+{
+ int res = 0;
+ struct pw_control *output = link->output;
+ struct pw_control *input = link->input;
+
+ pw_log_debug(NAME" %p: unlink from %p", output, input);
+
+ spa_list_remove(&link->in_link);
+ spa_list_remove(&link->out_link);
+ link->valid = false;
+
+ if (spa_list_is_empty(&output->links)) {
+ if ((res = port_set_io(output->port, link->out_port,
+ output->id, NULL, 0)) < 0) {
+ pw_log_warn(NAME" %p: can't unset port control io", output);
+ }
+ }
+
+ if (input->port) {
+ if ((res = port_set_io(input->port, link->in_port,
+ input->id, NULL, 0)) < 0) {
+ pw_log_warn(NAME" %p: can't unset port control io", output);
+ }
+ }
+
+ pw_control_emit_unlinked(output, input);
+ pw_control_emit_unlinked(input, output);
+
+ return res;
+}
diff --git a/src/pipewire/control.h b/src/pipewire/control.h
new file mode 100644
index 0000000..92d89a1
--- /dev/null
+++ b/src/pipewire/control.h
@@ -0,0 +1,82 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_CONTROL_H
+#define PIPEWIRE_CONTROL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/hook.h>
+
+/** \defgroup pw_control Control
+ *
+ * \brief A control can be used to control a port property.
+ */
+
+/**
+ * \addtogroup pw_control
+ * \{
+ */
+struct pw_control;
+
+#include <pipewire/impl.h>
+
+/** Port events, use \ref pw_control_add_listener */
+struct pw_control_events {
+#define PW_VERSION_CONTROL_EVENTS 0
+ uint32_t version;
+
+ /** The control is destroyed */
+ void (*destroy) (void *data);
+
+ /** The control is freed */
+ void (*free) (void *data);
+
+ /** control is linked to another control */
+ void (*linked) (void *data, struct pw_control *other);
+ /** control is unlinked from another control */
+ void (*unlinked) (void *data, struct pw_control *other);
+
+};
+
+/** Get the control parent port or NULL when not set */
+struct pw_impl_port *pw_control_get_port(struct pw_control *control);
+
+/** Add an event listener on the control */
+void pw_control_add_listener(struct pw_control *control,
+ struct spa_hook *listener,
+ const struct pw_control_events *events,
+ void *data);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_CONTROL_H */
diff --git a/src/pipewire/core.c b/src/pipewire/core.c
new file mode 100644
index 0000000..dd7e05e
--- /dev/null
+++ b/src/pipewire/core.c
@@ -0,0 +1,495 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <errno.h>
+#include <sys/mman.h>
+
+#include <spa/pod/parser.h>
+#include <spa/debug/types.h>
+
+#include "pipewire/pipewire.h"
+#include "pipewire/private.h"
+
+#include "pipewire/extensions/protocol-native.h"
+
+PW_LOG_TOPIC_EXTERN(log_core);
+#define PW_LOG_TOPIC_DEFAULT log_core
+
+static void core_event_ping(void *data, uint32_t id, int seq)
+{
+ struct pw_core *this = data;
+ pw_log_debug("%p: object %u ping %u", this, id, seq);
+ pw_core_pong(this->core, id, seq);
+}
+
+static void core_event_done(void *data, uint32_t id, int seq)
+{
+ struct pw_core *this = data;
+ struct pw_proxy *proxy;
+
+ pw_log_trace("%p: object %u done %d", this, id, seq);
+
+ proxy = pw_map_lookup(&this->objects, id);
+ if (proxy)
+ pw_proxy_emit_done(proxy, seq);
+}
+
+static void core_event_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct pw_core *this = data;
+ struct pw_proxy *proxy;
+
+ proxy = pw_map_lookup(&this->objects, id);
+
+ pw_log_debug("%p: proxy %p id:%u: bound:%d seq:%d res:%d (%s) msg:\"%s\"",
+ this, proxy, id, proxy ? proxy->bound_id : SPA_ID_INVALID,
+ seq, res, spa_strerror(res), message);
+ if (proxy)
+ pw_proxy_emit_error(proxy, seq, res, message);
+}
+
+static void core_event_remove_id(void *data, uint32_t id)
+{
+ struct pw_core *this = data;
+ struct pw_proxy *proxy;
+
+ pw_log_debug("%p: object remove %u", this, id);
+ if ((proxy = pw_map_lookup(&this->objects, id)) != NULL)
+ pw_proxy_remove(proxy);
+}
+
+static void core_event_bound_id(void *data, uint32_t id, uint32_t global_id)
+{
+ struct pw_core *this = data;
+ struct pw_proxy *proxy;
+
+ pw_log_debug("%p: proxy id %u bound %u", this, id, global_id);
+ if ((proxy = pw_map_lookup(&this->objects, id)) != NULL) {
+ pw_proxy_set_bound_id(proxy, global_id);
+ }
+}
+
+static void core_event_add_mem(void *data, uint32_t id, uint32_t type, int fd, uint32_t flags)
+{
+ struct pw_core *this = data;
+ struct pw_memblock *m;
+
+ pw_log_debug("%p: add mem %u type:%u fd:%d flags:%u", this, id, type, fd, flags);
+
+ m = pw_mempool_import(this->pool, flags, type, fd);
+ if (m->id != id) {
+ pw_log_error("%p: invalid mem id %u, fd:%d expected %u",
+ this, id, fd, m->id);
+ pw_proxy_errorf(&this->proxy, -EINVAL, "invalid mem id %u, expected %u", id, m->id);
+ pw_memblock_unref(m);
+ }
+}
+
+static void core_event_remove_mem(void *data, uint32_t id)
+{
+ struct pw_core *this = data;
+ pw_log_debug("%p: remove mem %u", this, id);
+ pw_mempool_remove_id(this->pool, id);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = core_event_error,
+ .ping = core_event_ping,
+ .done = core_event_done,
+ .remove_id = core_event_remove_id,
+ .bound_id = core_event_bound_id,
+ .add_mem = core_event_add_mem,
+ .remove_mem = core_event_remove_mem,
+};
+
+SPA_EXPORT
+struct pw_context *pw_core_get_context(struct pw_core *core)
+{
+ return core->context;
+}
+
+SPA_EXPORT
+const struct pw_properties *pw_core_get_properties(struct pw_core *core)
+{
+ return core->properties;
+}
+
+SPA_EXPORT
+int pw_core_update_properties(struct pw_core *core, const struct spa_dict *dict)
+{
+ int changed;
+
+ changed = pw_properties_update(core->properties, dict);
+
+ pw_log_debug("%p: updated %d properties", core, changed);
+
+ if (!changed)
+ return 0;
+
+ if (core->client)
+ pw_client_update_properties(core->client, &core->properties->dict);
+
+ return changed;
+}
+
+SPA_EXPORT
+void *pw_core_get_user_data(struct pw_core *core)
+{
+ return core->user_data;
+}
+
+static int remove_proxy(void *object, void *data)
+{
+ struct pw_core *core = data;
+ struct pw_proxy *p = object;
+
+ if (object == NULL)
+ return 0;
+
+ if (object != core)
+ pw_proxy_remove(p);
+
+ return 0;
+}
+
+static int destroy_proxy(void *object, void *data)
+{
+ struct pw_core *core = data;
+ struct pw_proxy *p = object;
+
+ if (object == NULL)
+ return 0;
+
+ if (object != core) {
+ pw_log_warn("%p: leaked proxy %p id:%d", core, p, p->id);
+ p->core = NULL;
+ }
+ return 0;
+}
+
+static void proxy_core_removed(void *data)
+{
+ struct pw_core *core = data;
+ struct pw_stream *stream, *s2;
+ struct pw_filter *filter, *f2;
+
+ if (core->removed)
+ return;
+
+ core->removed = true;
+
+ pw_log_debug("%p: core proxy removed", core);
+ spa_list_remove(&core->link);
+
+ spa_list_for_each_safe(stream, s2, &core->stream_list, link)
+ pw_stream_disconnect(stream);
+ spa_list_for_each_safe(filter, f2, &core->filter_list, link)
+ pw_filter_disconnect(filter);
+
+ pw_map_for_each(&core->objects, remove_proxy, core);
+}
+
+static void proxy_core_destroy(void *data)
+{
+ struct pw_core *core = data;
+ struct pw_stream *stream;
+ struct pw_filter *filter;
+
+ if (core->destroyed)
+ return;
+
+ core->destroyed = true;
+
+ pw_log_debug("%p: core proxy destroy", core);
+
+ spa_list_consume(stream, &core->stream_list, link)
+ pw_stream_destroy(stream);
+ spa_list_consume(filter, &core->filter_list, link)
+ pw_filter_destroy(filter);
+
+ pw_proxy_destroy((struct pw_proxy*)core->client);
+
+ pw_map_for_each(&core->objects, destroy_proxy, core);
+ pw_map_reset(&core->objects);
+
+ pw_protocol_client_disconnect(core->conn);
+
+ pw_mempool_destroy(core->pool);
+
+ pw_protocol_client_destroy(core->conn);
+
+ pw_map_clear(&core->objects);
+
+ pw_log_debug("%p: free", core);
+ pw_properties_free(core->properties);
+
+ spa_hook_remove(&core->core_listener);
+ spa_hook_remove(&core->proxy_core_listener);
+}
+
+static const struct pw_proxy_events proxy_core_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .removed = proxy_core_removed,
+ .destroy = proxy_core_destroy,
+};
+
+SPA_EXPORT
+struct pw_client * pw_core_get_client(struct pw_core *core)
+{
+ return core->client;
+}
+
+SPA_EXPORT
+struct pw_proxy *pw_core_find_proxy(struct pw_core *core, uint32_t id)
+{
+ return pw_map_lookup(&core->objects, id);
+}
+
+SPA_EXPORT
+struct pw_proxy *pw_core_export(struct pw_core *core,
+ const char *type, const struct spa_dict *props, void *object,
+ size_t user_data_size)
+{
+ struct pw_proxy *proxy;
+ const struct pw_export_type *t;
+ int res;
+
+ t = pw_context_find_export_type(core->context, type);
+ if (t == NULL) {
+ res = -EPROTO;
+ goto error_export_type;
+ }
+
+ proxy = t->func(core, t->type, props, object, user_data_size);
+ if (proxy == NULL) {
+ res = -errno;
+ goto error_proxy_failed;
+ }
+ pw_log_debug("%p: export:%s proxy:%p", core, type, proxy);
+ return proxy;
+
+error_export_type:
+ pw_log_error("%p: can't export type %s: %s", core, type, spa_strerror(res));
+ goto exit;
+error_proxy_failed:
+ pw_log_error("%p: failed to create proxy: %s", core, spa_strerror(res));
+ goto exit;
+exit:
+ errno = -res;
+ return NULL;
+}
+
+static struct pw_core *core_new(struct pw_context *context,
+ struct pw_properties *properties, size_t user_data_size)
+{
+ struct pw_core *p;
+ struct pw_protocol *protocol;
+ const char *protocol_name;
+ int res;
+
+ p = calloc(1, sizeof(struct pw_core) + user_data_size);
+ if (p == NULL) {
+ res = -errno;
+ goto exit_cleanup;
+ }
+ pw_log_debug("%p: new", p);
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL)
+ goto error_properties;
+
+ pw_properties_add(properties, &context->properties->dict);
+
+ p->proxy.core = p;
+ p->context = context;
+ p->properties = properties;
+ p->pool = pw_mempool_new(NULL);
+ p->core = p;
+ if (user_data_size > 0)
+ p->user_data = SPA_PTROFF(p, sizeof(struct pw_core), void);
+ p->proxy.user_data = p->user_data;
+
+ pw_map_init(&p->objects, 64, 32);
+ spa_list_init(&p->stream_list);
+ spa_list_init(&p->filter_list);
+
+ if ((protocol_name = pw_properties_get(properties, PW_KEY_PROTOCOL)) == NULL &&
+ (protocol_name = pw_properties_get(context->properties, PW_KEY_PROTOCOL)) == NULL)
+ protocol_name = PW_TYPE_INFO_PROTOCOL_Native;
+
+ protocol = pw_context_find_protocol(context, protocol_name);
+ if (protocol == NULL) {
+ res = -ENOTSUP;
+ goto error_protocol;
+ }
+
+ p->conn = pw_protocol_new_client(protocol, p, &properties->dict);
+ if (p->conn == NULL)
+ goto error_connection;
+
+ if ((res = pw_proxy_init(&p->proxy, PW_TYPE_INTERFACE_Core, PW_VERSION_CORE)) < 0)
+ goto error_proxy;
+
+ p->client = (struct pw_client*)pw_proxy_new(&p->proxy,
+ PW_TYPE_INTERFACE_Client, PW_VERSION_CLIENT, 0);
+ if (p->client == NULL) {
+ res = -errno;
+ goto error_proxy;
+ }
+
+ pw_core_add_listener(p, &p->core_listener, &core_events, p);
+ pw_proxy_add_listener(&p->proxy, &p->proxy_core_listener, &proxy_core_events, p);
+
+ pw_core_hello(p, PW_VERSION_CORE);
+ pw_client_update_properties(p->client, &p->properties->dict);
+
+ spa_list_append(&context->core_list, &p->link);
+
+ return p;
+
+error_properties:
+ res = -errno;
+ pw_log_error("%p: can't create properties: %m", p);
+ goto exit_free;
+error_protocol:
+ pw_log_error("%p: can't find protocol '%s': %s", p, protocol_name, spa_strerror(res));
+ goto exit_free;
+error_connection:
+ res = -errno;
+ pw_log_error("%p: can't create new native protocol connection: %m", p);
+ goto exit_free;
+error_proxy:
+ pw_log_error("%p: can't initialize proxy: %s", p, spa_strerror(res));
+ goto exit_free;
+
+exit_free:
+ free(p);
+exit_cleanup:
+ pw_properties_free(properties);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+struct pw_core *
+pw_context_connect(struct pw_context *context, struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct pw_core *core;
+ int res;
+
+ core = core_new(context, properties, user_data_size);
+ if (core == NULL)
+ return NULL;
+
+ pw_log_debug("%p: connect", core);
+
+ if ((res = pw_protocol_client_connect(core->conn,
+ &core->properties->dict,
+ NULL, NULL)) < 0)
+ goto error_free;
+
+ return core;
+
+error_free:
+ pw_core_disconnect(core);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+struct pw_core *
+pw_context_connect_fd(struct pw_context *context, int fd, struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct pw_core *core;
+ int res;
+
+ core = core_new(context, properties, user_data_size);
+ if (core == NULL)
+ return NULL;
+
+ pw_log_debug("%p: connect fd:%d", core, fd);
+
+ if ((res = pw_protocol_client_connect_fd(core->conn, fd, true)) < 0)
+ goto error_free;
+
+ return core;
+
+error_free:
+ pw_core_disconnect(core);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+struct pw_core *
+pw_context_connect_self(struct pw_context *context, struct pw_properties *properties,
+ size_t user_data_size)
+{
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL)
+ return NULL;
+
+ pw_properties_set(properties, PW_KEY_REMOTE_NAME, "internal");
+
+ return pw_context_connect(context, properties, user_data_size);
+}
+
+SPA_EXPORT
+int pw_core_steal_fd(struct pw_core *core)
+{
+ int fd = pw_protocol_client_steal_fd(core->conn);
+ pw_log_debug("%p: fd:%d", core, fd);
+ return fd;
+}
+
+SPA_EXPORT
+int pw_core_set_paused(struct pw_core *core, bool paused)
+{
+ pw_log_debug("%p: state:%s", core, paused ? "pause" : "resume");
+ return pw_protocol_client_set_paused(core->conn, paused);
+}
+
+SPA_EXPORT
+struct pw_mempool * pw_core_get_mempool(struct pw_core *core)
+{
+ return core->pool;
+}
+
+SPA_EXPORT
+int pw_core_disconnect(struct pw_core *core)
+{
+ pw_log_debug("%p: disconnect", core);
+ pw_proxy_remove(&core->proxy);
+ pw_proxy_destroy(&core->proxy);
+ return 0;
+}
diff --git a/src/pipewire/core.h b/src/pipewire/core.h
new file mode 100644
index 0000000..604e7cc
--- /dev/null
+++ b/src/pipewire/core.h
@@ -0,0 +1,629 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_CORE_H
+#define PIPEWIRE_CORE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+#include <errno.h>
+
+#include <spa/utils/hook.h>
+
+/** \defgroup pw_core Core
+ *
+ * \brief The core global object.
+ *
+ * This is a special singleton object. It is used for internal PipeWire
+ * protocol features. Connecting to a PipeWire instance returns one core
+ * object, the caller should then register event listeners
+ * using \ref pw_core_add_listener.
+ *
+ * Updates to the core object are then provided through the \ref
+ * pw_core_events interface. See \ref page_tutorial2 for an example.
+ */
+
+/**
+ * \addtogroup pw_core
+ * \{
+ */
+#define PW_TYPE_INTERFACE_Core PW_TYPE_INFO_INTERFACE_BASE "Core"
+#define PW_TYPE_INTERFACE_Registry PW_TYPE_INFO_INTERFACE_BASE "Registry"
+
+#define PW_VERSION_CORE 3
+struct pw_core;
+#define PW_VERSION_REGISTRY 3
+struct pw_registry;
+
+/** The default remote name to connect to */
+#define PW_DEFAULT_REMOTE "pipewire-0"
+
+/** default ID for the core object after connect */
+#define PW_ID_CORE 0
+
+/* invalid ID that matches any object when used for permissions */
+#define PW_ID_ANY (uint32_t)(0xffffffff)
+
+/** The core information. Extra information may be added in later versions,
+ * clients must not assume a constant struct size */
+struct pw_core_info {
+ uint32_t id; /**< id of the global */
+ uint32_t cookie; /**< a random cookie for identifying this instance of PipeWire */
+ const char *user_name; /**< name of the user that started the core */
+ const char *host_name; /**< name of the machine the core is running on */
+ const char *version; /**< version of the core */
+ const char *name; /**< name of the core */
+#define PW_CORE_CHANGE_MASK_PROPS (1 << 0)
+#define PW_CORE_CHANGE_MASK_ALL ((1 << 1)-1)
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ struct spa_dict *props; /**< extra properties */
+};
+
+#include <pipewire/context.h>
+#include <pipewire/properties.h>
+#include <pipewire/proxy.h>
+
+/** Update an existing \ref pw_core_info with \a update with reset */
+struct pw_core_info *
+pw_core_info_update(struct pw_core_info *info,
+ const struct pw_core_info *update);
+/** Update an existing \ref pw_core_info with \a update */
+struct pw_core_info *
+pw_core_info_merge(struct pw_core_info *info,
+ const struct pw_core_info *update, bool reset);
+/** Free a \ref pw_core_info */
+void pw_core_info_free(struct pw_core_info *info);
+
+/** Core */
+
+#define PW_CORE_EVENT_INFO 0
+#define PW_CORE_EVENT_DONE 1
+#define PW_CORE_EVENT_PING 2
+#define PW_CORE_EVENT_ERROR 3
+#define PW_CORE_EVENT_REMOVE_ID 4
+#define PW_CORE_EVENT_BOUND_ID 5
+#define PW_CORE_EVENT_ADD_MEM 6
+#define PW_CORE_EVENT_REMOVE_MEM 7
+#define PW_CORE_EVENT_NUM 8
+
+/** \struct pw_core_events
+ * \brief Core events
+ */
+struct pw_core_events {
+#define PW_VERSION_CORE_EVENTS 0
+ uint32_t version;
+
+ /**
+ * Notify new core info
+ *
+ * This event is emitted when first bound to the core or when the
+ * hello method is called.
+ *
+ * \param info new core info
+ */
+ void (*info) (void *data, const struct pw_core_info *info);
+ /**
+ * Emit a done event
+ *
+ * The done event is emitted as a result of a sync method with the
+ * same seq number.
+ *
+ * \param seq the seq number passed to the sync method call
+ */
+ void (*done) (void *data, uint32_t id, int seq);
+
+ /** Emit a ping event
+ *
+ * The client should reply with a pong reply with the same seq
+ * number.
+ */
+ void (*ping) (void *data, uint32_t id, int seq);
+
+ /**
+ * Fatal error event
+ *
+ * The error event is sent out when a fatal (non-recoverable)
+ * error has occurred. The id argument is the proxy object where
+ * the error occurred, most often in response to a request to that
+ * object. The message is a brief description of the error,
+ * for (debugging) convenience.
+ *
+ * This event is usually also emitted on the proxy object with
+ * \a id.
+ *
+ * \param id object where the error occurred
+ * \param seq the sequence number that generated the error
+ * \param res error code
+ * \param message error description
+ */
+ void (*error) (void *data, uint32_t id, int seq, int res, const char *message);
+ /**
+ * Remove an object ID
+ *
+ * This event is used internally by the object ID management
+ * logic. When a client deletes an object, the server will send
+ * this event to acknowledge that it has seen the delete request.
+ * When the client receives this event, it will know that it can
+ * safely reuse the object ID.
+ *
+ * \param id deleted object ID
+ */
+ void (*remove_id) (void *data, uint32_t id);
+
+ /**
+ * Notify an object binding
+ *
+ * This event is emitted when a local object ID is bound to a
+ * global ID. It is emitted before the global becomes visible in the
+ * registry.
+ *
+ * \param id bound object ID
+ * \param global_id the global id bound to
+ */
+ void (*bound_id) (void *data, uint32_t id, uint32_t global_id);
+
+ /**
+ * Add memory for a client
+ *
+ * Memory is given to a client as \a fd of a certain
+ * memory \a type.
+ *
+ * Further references to this fd will be made with the per memory
+ * unique identifier \a id.
+ *
+ * \param id the unique id of the memory
+ * \param type the memory type, one of enum spa_data_type
+ * \param fd the file descriptor
+ * \param flags extra flags
+ */
+ void (*add_mem) (void *data, uint32_t id, uint32_t type, int fd, uint32_t flags);
+
+ /**
+ * Remove memory for a client
+ *
+ * \param id the memory id to remove
+ */
+ void (*remove_mem) (void *data, uint32_t id);
+};
+
+#define PW_CORE_METHOD_ADD_LISTENER 0
+#define PW_CORE_METHOD_HELLO 1
+#define PW_CORE_METHOD_SYNC 2
+#define PW_CORE_METHOD_PONG 3
+#define PW_CORE_METHOD_ERROR 4
+#define PW_CORE_METHOD_GET_REGISTRY 5
+#define PW_CORE_METHOD_CREATE_OBJECT 6
+#define PW_CORE_METHOD_DESTROY 7
+#define PW_CORE_METHOD_NUM 8
+
+/**
+ * \struct pw_core_methods
+ * \brief Core methods
+ *
+ * The core global object. This is a singleton object used for
+ * creating new objects in the remote PipeWire instance. It is
+ * also used for internal features.
+ */
+struct pw_core_methods {
+#define PW_VERSION_CORE_METHODS 0
+ uint32_t version;
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_core_events *events,
+ void *data);
+ /**
+ * Start a conversation with the server. This will send
+ * the core info and will destroy all resources for the client
+ * (except the core and client resource).
+ */
+ int (*hello) (void *object, uint32_t version);
+ /**
+ * Do server roundtrip
+ *
+ * Ask the server to emit the 'done' event with \a seq.
+ *
+ * Since methods are handled in-order and events are delivered
+ * in-order, this can be used as a barrier to ensure all previous
+ * methods and the resulting events have been handled.
+ *
+ * \param seq the seq number passed to the done event
+ */
+ int (*sync) (void *object, uint32_t id, int seq);
+ /**
+ * Reply to a server ping event.
+ *
+ * Reply to the server ping event with the same seq.
+ *
+ * \param seq the seq number received in the ping event
+ */
+ int (*pong) (void *object, uint32_t id, int seq);
+ /**
+ * Fatal error event
+ *
+ * The error method is sent out when a fatal (non-recoverable)
+ * error has occurred. The id argument is the proxy object where
+ * the error occurred, most often in response to an event on that
+ * object. The message is a brief description of the error,
+ * for (debugging) convenience.
+ *
+ * This method is usually also emitted on the resource object with
+ * \a id.
+ *
+ * \param id object where the error occurred
+ * \param res error code
+ * \param message error description
+ */
+ int (*error) (void *object, uint32_t id, int seq, int res, const char *message);
+ /**
+ * Get the registry object
+ *
+ * Create a registry object that allows the client to list and bind
+ * the global objects available from the PipeWire server
+ * \param version the client version
+ * \param user_data_size extra size
+ */
+ struct pw_registry * (*get_registry) (void *object, uint32_t version,
+ size_t user_data_size);
+
+ /**
+ * Create a new object on the PipeWire server from a factory.
+ *
+ * \param factory_name the factory name to use
+ * \param type the interface to bind to
+ * \param version the version of the interface
+ * \param props extra properties
+ * \param user_data_size extra size
+ */
+ void * (*create_object) (void *object,
+ const char *factory_name,
+ const char *type,
+ uint32_t version,
+ const struct spa_dict *props,
+ size_t user_data_size);
+ /**
+ * Destroy an resource
+ *
+ * Destroy the server resource for the given proxy.
+ *
+ * \param obj the proxy to destroy
+ */
+ 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
+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)
+{
+ char buffer[1024];
+ vsnprintf(buffer, sizeof(buffer), message, args);
+ buffer[1023] = '\0';
+ return pw_core_error(core, id, seq, res, buffer);
+}
+
+static inline
+SPA_PRINTF_FUNC(5, 6) int
+pw_core_errorf(struct pw_core *core, uint32_t id, int seq,
+ int res, const char *message, ...)
+{
+ va_list args;
+ int r;
+ va_start(args, message);
+ r = pw_core_errorv(core, id, seq, res, message, args);
+ va_end(args);
+ return r;
+}
+
+static inline 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;
+}
+
+static inline void *
+pw_core_create_object(struct pw_core *core,
+ const char *factory_name,
+ const char *type,
+ uint32_t version,
+ 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;
+}
+
+#define pw_core_destroy(c,...) pw_core_method(c,destroy,0,__VA_ARGS__)
+
+/**
+ * \}
+ */
+
+/** \defgroup pw_registry Registry
+ *
+ * The registry object is a singleton object that keeps track of
+ * global objects on the PipeWire instance. See also \ref pw_global.
+ *
+ * Global objects typically represent an actual object in PipeWire
+ * (for example, a module or node) or they are singleton
+ * objects such as the core.
+ *
+ * When a client creates a registry object, the registry object
+ * will emit a global event for each global currently in the
+ * registry. Globals come and go as a result of device hotplugs or
+ * reconfiguration or other events, and the registry will send out
+ * global and global_remove events to keep the client up to date
+ * with the changes. To mark the end of the initial burst of
+ * events, the client can use the pw_core.sync methosd immediately
+ * after calling pw_core.get_registry.
+ *
+ * A client can bind to a global object by using the bind
+ * request. This creates a client-side proxy that lets the object
+ * emit events to the client and lets the client invoke methods on
+ * the object. See \ref page_proxy
+ *
+ * Clients can also change the permissions of the global objects that
+ * it can see. This is interesting when you want to configure a
+ * pipewire session before handing it to another application. You
+ * can, for example, hide certain existing or new objects or limit
+ * the access permissions on an object.
+ */
+
+/**
+ * \addtogroup pw_registry
+ * \{
+ */
+
+#define PW_REGISTRY_EVENT_GLOBAL 0
+#define PW_REGISTRY_EVENT_GLOBAL_REMOVE 1
+#define PW_REGISTRY_EVENT_NUM 2
+
+/** Registry events */
+struct pw_registry_events {
+#define PW_VERSION_REGISTRY_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify of a new global object
+ *
+ * The registry emits this event when a new global object is
+ * available.
+ *
+ * \param id the global object id
+ * \param permissions the permissions of the object
+ * \param type the type of the interface
+ * \param version the version of the interface
+ * \param props extra properties of the global
+ */
+ void (*global) (void *data, uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const struct spa_dict *props);
+ /**
+ * Notify of a global object removal
+ *
+ * Emitted when a global object was removed from the registry.
+ * If the client has any bindings to the global, it should destroy
+ * those.
+ *
+ * \param id the id of the global that was removed
+ */
+ void (*global_remove) (void *data, uint32_t id);
+};
+
+#define PW_REGISTRY_METHOD_ADD_LISTENER 0
+#define PW_REGISTRY_METHOD_BIND 1
+#define PW_REGISTRY_METHOD_DESTROY 2
+#define PW_REGISTRY_METHOD_NUM 3
+
+/** Registry methods */
+struct pw_registry_methods {
+#define PW_VERSION_REGISTRY_METHODS 0
+ uint32_t version;
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_registry_events *events,
+ void *data);
+ /**
+ * Bind to a global object
+ *
+ * Bind to the global object with \a id and use the client proxy
+ * with new_id as the proxy. After this call, methods can be
+ * send to the remote global object and events can be received
+ *
+ * \param id the global id to bind to
+ * \param type the interface type to bind to
+ * \param version the interface version to use
+ * \returns the new object
+ */
+ void * (*bind) (void *object, uint32_t id, const char *type, uint32_t version,
+ size_t use_data_size);
+
+ /**
+ * Attempt to destroy a global object
+ *
+ * Try to destroy the global object.
+ *
+ * \param id the global id to destroy
+ */
+ 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 *
+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;
+}
+
+#define pw_registry_destroy(p,...) pw_registry_method(p,destroy,0,__VA_ARGS__)
+
+/**
+ * \}
+ */
+
+/**
+ * \addtogroup pw_core
+ * \{
+ */
+
+/** Connect to a PipeWire instance
+ *
+ * \param context a \ref pw_context
+ * \param properties optional properties, ownership of the properties is
+ * taken.
+ * \param user_data_size extra user data size
+ *
+ * \return a \ref pw_core on success or NULL with errno set on error. The core
+ * will have an id of \ref PW_ID_CORE (0)
+ */
+struct pw_core *
+pw_context_connect(struct pw_context *context,
+ struct pw_properties *properties,
+ size_t user_data_size);
+
+/** Connect to a PipeWire instance on the given socket
+ *
+ * \param context a \ref pw_context
+ * \param fd the connected socket to use, the socket will be closed
+ * automatically on disconnect or error.
+ * \param properties optional properties, ownership of the properties is
+ * taken.
+ * \param user_data_size extra user data size
+ *
+ * \return a \ref pw_core on success or NULL with errno set on error */
+struct pw_core *
+pw_context_connect_fd(struct pw_context *context,
+ int fd,
+ struct pw_properties *properties,
+ size_t user_data_size);
+
+/** Connect to a given PipeWire instance
+ *
+ * \param context a \ref pw_context to connect to
+ * \param properties optional properties, ownership of the properties is
+ * taken.
+ * \param user_data_size extra user data size
+ *
+ * \return a \ref pw_core on success or NULL with errno set on error */
+struct pw_core *
+pw_context_connect_self(struct pw_context *context,
+ struct pw_properties *properties,
+ size_t user_data_size);
+
+/** Steal the fd of the core connection or < 0 on error. The core
+ * will be disconnected after this call. */
+int pw_core_steal_fd(struct pw_core *core);
+
+/** Pause or resume the core. When the core is paused, no new events
+ * will be dispatched until the core is resumed again. */
+int pw_core_set_paused(struct pw_core *core, bool paused);
+
+/** disconnect and destroy a core */
+int pw_core_disconnect(struct pw_core *core);
+
+/** Get the user_data. It is of the size specified when this object was
+ * constructed */
+void *pw_core_get_user_data(struct pw_core *core);
+
+/** Get the client proxy of the connected core. This will have the id
+ * of PW_ID_CLIENT (1) */
+struct pw_client * pw_core_get_client(struct pw_core *core);
+
+/** Get the context object used to created this core */
+struct pw_context * pw_core_get_context(struct pw_core *core);
+
+/** Get properties from the core */
+const struct pw_properties *pw_core_get_properties(struct pw_core *core);
+
+/** Update the core properties. This updates the properties
+ * of the associated client.
+ * \return the number of properties that were updated */
+int pw_core_update_properties(struct pw_core *core, const struct spa_dict *dict);
+
+/** Get the core mempool object */
+struct pw_mempool * pw_core_get_mempool(struct pw_core *core);
+
+/** Get the proxy with the given id */
+struct pw_proxy *pw_core_find_proxy(struct pw_core *core, uint32_t id);
+
+/** Export an object into the PipeWire instance associated with core */
+struct pw_proxy *pw_core_export(struct pw_core *core, /**< the core */
+ const char *type, /**< the type of object */
+ const struct spa_dict *props, /**< extra properties */
+ void *object, /**< object to export */
+ size_t user_data_size /**< extra user data */);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_CORE_H */
diff --git a/src/pipewire/data-loop.c b/src/pipewire/data-loop.c
new file mode 100644
index 0000000..4f4a2bc
--- /dev/null
+++ b/src/pipewire/data-loop.c
@@ -0,0 +1,292 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pthread.h>
+#include <errno.h>
+#include <sys/resource.h>
+
+#include "pipewire/log.h"
+#include "pipewire/data-loop.h"
+#include "pipewire/private.h"
+#include "pipewire/thread.h"
+
+PW_LOG_TOPIC_EXTERN(log_data_loop);
+#define PW_LOG_TOPIC_DEFAULT log_data_loop
+
+SPA_EXPORT
+int pw_data_loop_wait(struct pw_data_loop *this, int timeout)
+{
+ int res;
+
+ while (true) {
+ if (SPA_UNLIKELY(!this->running)) {
+ res = -ECANCELED;
+ break;
+ }
+ if (SPA_UNLIKELY((res = pw_loop_iterate(this->loop, timeout)) < 0)) {
+ if (res == -EINTR)
+ continue;
+ }
+ break;
+ }
+ return res;
+}
+
+SPA_EXPORT
+void pw_data_loop_exit(struct pw_data_loop *this)
+{
+ this->running = false;
+}
+
+static void thread_cleanup(void *arg)
+{
+ struct pw_data_loop *this = arg;
+ pw_log_debug("%p: leave thread", this);
+ this->running = false;
+ pw_loop_leave(this->loop);
+}
+
+static void *do_loop(void *user_data)
+{
+ struct pw_data_loop *this = user_data;
+ int res;
+
+ pw_log_debug("%p: enter thread", this);
+ pw_loop_enter(this->loop);
+
+ pthread_cleanup_push(thread_cleanup, this);
+
+ while (SPA_LIKELY(this->running)) {
+ if (SPA_UNLIKELY((res = pw_loop_iterate(this->loop, -1)) < 0)) {
+ if (res == -EINTR)
+ continue;
+ pw_log_error("%p: iterate error %d (%s)",
+ this, res, spa_strerror(res));
+ }
+ }
+ pthread_cleanup_pop(1);
+
+ return NULL;
+}
+
+static int do_stop(struct spa_loop *loop, bool async, uint32_t seq,
+ const void *data, size_t size, void *user_data)
+{
+ struct pw_data_loop *this = user_data;
+ pw_log_debug("%p: stopping", this);
+ this->running = false;
+ return 0;
+}
+
+static struct pw_data_loop *loop_new(struct pw_loop *loop, const struct spa_dict *props)
+{
+ struct pw_data_loop *this;
+ const char *str;
+ int res;
+
+ this = calloc(1, sizeof(struct pw_data_loop));
+ if (this == NULL) {
+ res = -errno;
+ goto error_cleanup;
+ }
+
+ pw_log_debug("%p: new", this);
+
+ if (loop == NULL) {
+ loop = pw_loop_new(props);
+ this->created = true;
+ }
+ if (loop == NULL) {
+ res = -errno;
+ pw_log_error("%p: can't create loop: %m", this);
+ goto error_free;
+ }
+ this->loop = loop;
+
+ if (props != NULL &&
+ (str = spa_dict_lookup(props, "loop.cancel")) != NULL)
+ this->cancel = pw_properties_parse_bool(str);
+
+ spa_hook_list_init(&this->listener_list);
+
+ return this;
+
+error_free:
+ free(this);
+error_cleanup:
+ errno = -res;
+ return NULL;
+}
+
+/** Create a new \ref pw_data_loop.
+ * \return a newly allocated data loop
+ *
+ */
+SPA_EXPORT
+struct pw_data_loop *pw_data_loop_new(const struct spa_dict *props)
+{
+ return loop_new(NULL, props);
+}
+
+
+/** Destroy a data loop
+ * \param loop the data loop to destroy
+ */
+SPA_EXPORT
+void pw_data_loop_destroy(struct pw_data_loop *loop)
+{
+ pw_log_debug("%p: destroy", loop);
+
+ pw_data_loop_emit_destroy(loop);
+
+ pw_data_loop_stop(loop);
+
+ if (loop->created)
+ pw_loop_destroy(loop->loop);
+
+ spa_hook_list_clean(&loop->listener_list);
+
+ free(loop);
+}
+
+SPA_EXPORT
+void pw_data_loop_add_listener(struct pw_data_loop *loop,
+ struct spa_hook *listener,
+ const struct pw_data_loop_events *events,
+ void *data)
+{
+ spa_hook_list_append(&loop->listener_list, listener, events, data);
+}
+
+SPA_EXPORT
+struct pw_loop *
+pw_data_loop_get_loop(struct pw_data_loop *loop)
+{
+ return loop->loop;
+}
+
+/** Start a data loop
+ * \param loop the data loop to start
+ * \return 0 if ok, -1 on error
+ *
+ * This will start the realtime thread that manages the loop.
+ *
+ */
+SPA_EXPORT
+int pw_data_loop_start(struct pw_data_loop *loop)
+{
+ if (!loop->running) {
+ struct spa_thread_utils *utils;
+ struct spa_thread *thr;
+
+ loop->running = true;
+
+ if ((utils = loop->thread_utils) == NULL)
+ utils = pw_thread_utils_get();
+ thr = spa_thread_utils_create(utils, NULL, do_loop, loop);
+ loop->thread = (pthread_t)thr;
+ if (thr == NULL) {
+ pw_log_error("%p: can't create thread: %m", loop);
+ loop->running = false;
+ return -errno;
+ }
+ spa_thread_utils_acquire_rt(utils, thr, -1);
+ }
+ return 0;
+}
+
+/** Stop a data loop
+ * \param loop the data loop to Stop
+ * \return 0
+ *
+ * This will stop and join the realtime thread that manages the loop.
+ *
+ */
+SPA_EXPORT
+int pw_data_loop_stop(struct pw_data_loop *loop)
+{
+ pw_log_debug("%p stopping", loop);
+ if (loop->running) {
+ struct spa_thread_utils *utils;
+ if (loop->cancel) {
+ pw_log_debug("%p cancel", loop);
+ pthread_cancel(loop->thread);
+ } else {
+ pw_log_debug("%p signal", loop);
+ pw_loop_invoke(loop->loop, do_stop, 1, NULL, 0, false, loop);
+ }
+ pw_log_debug("%p join", loop);
+ if ((utils = loop->thread_utils) == NULL)
+ utils = pw_thread_utils_get();
+ spa_thread_utils_join(utils, (struct spa_thread*)loop->thread, NULL);
+ pw_log_debug("%p joined", loop);
+ }
+ pw_log_debug("%p stopped", loop);
+ return 0;
+}
+
+/** Check if we are inside the data loop
+ * \param loop the data loop to check
+ * \return true is the current thread is the data loop thread
+ *
+ */
+SPA_EXPORT
+bool pw_data_loop_in_thread(struct pw_data_loop * loop)
+{
+ return loop->running && pthread_equal(loop->thread, pthread_self());
+}
+
+/** Get the thread object.
+ * \param loop the data loop to get the thread of
+ * \return the thread object or NULL when the thread is not running
+ *
+ * On posix based systems this returns a pthread_t
+ */
+SPA_EXPORT
+struct spa_thread *pw_data_loop_get_thread(struct pw_data_loop * loop)
+{
+ return loop->running ? (struct spa_thread*)loop->thread : NULL;
+}
+
+SPA_EXPORT
+int pw_data_loop_invoke(struct pw_data_loop *loop,
+ spa_invoke_func_t func, uint32_t seq, const void *data, size_t size,
+ bool block, void *user_data)
+{
+ return pw_loop_invoke(loop->loop, func, seq, data, size, block, user_data);
+}
+
+/** Set a thread utils implementation.
+ * \param loop the data loop to set the thread utils on
+ * \param impl the thread utils implementation
+ *
+ * This configures a custom spa_thread_utils implementation for this data
+ * loop. Use NULL to restore the system default implementation.
+ */
+SPA_EXPORT
+void pw_data_loop_set_thread_utils(struct pw_data_loop *loop,
+ struct spa_thread_utils *impl)
+{
+ loop->thread_utils = impl;
+}
diff --git a/src/pipewire/data-loop.h b/src/pipewire/data-loop.h
new file mode 100644
index 0000000..a459c19
--- /dev/null
+++ b/src/pipewire/data-loop.h
@@ -0,0 +1,113 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_DATA_LOOP_H
+#define PIPEWIRE_DATA_LOOP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/hook.h>
+#include <spa/support/thread.h>
+
+/** \defgroup pw_data_loop Data Loop
+ *
+ * \brief PipeWire rt-loop object
+ *
+ * This loop starts a new real-time thread that
+ * is designed to run the processing graph.
+ */
+
+/**
+ * \addtogroup pw_data_loop
+ * \{
+ */
+struct pw_data_loop;
+
+#include <pipewire/loop.h>
+#include <pipewire/properties.h>
+
+/** Loop events, use \ref pw_data_loop_add_listener to add a listener */
+struct pw_data_loop_events {
+#define PW_VERSION_DATA_LOOP_EVENTS 0
+ uint32_t version;
+ /** The loop is destroyed */
+ void (*destroy) (void *data);
+};
+
+/** Make a new loop. */
+struct pw_data_loop *
+pw_data_loop_new(const struct spa_dict *props);
+
+/** Add an event listener to loop */
+void pw_data_loop_add_listener(struct pw_data_loop *loop,
+ struct spa_hook *listener,
+ const struct pw_data_loop_events *events,
+ void *data);
+
+/** wait for activity on the loop up to \a timeout milliseconds.
+ * Should be called from the loop function */
+int pw_data_loop_wait(struct pw_data_loop *loop, int timeout);
+
+/** make sure the thread will exit. Can be called from a loop callback */
+void pw_data_loop_exit(struct pw_data_loop *loop);
+
+/** Get the loop implementation of this data loop */
+struct pw_loop *
+pw_data_loop_get_loop(struct pw_data_loop *loop);
+
+/** Destroy the loop */
+void pw_data_loop_destroy(struct pw_data_loop *loop);
+
+/** Start the processing thread */
+int pw_data_loop_start(struct pw_data_loop *loop);
+
+/** Stop the processing thread */
+int pw_data_loop_stop(struct pw_data_loop *loop);
+
+/** Check if the current thread is the processing thread */
+bool pw_data_loop_in_thread(struct pw_data_loop *loop);
+/** Get the thread object */
+struct spa_thread *pw_data_loop_get_thread(struct pw_data_loop *loop);
+
+/** invoke func in the context of the thread or in the caller thread when
+ * the loop is not running. Since 0.3.3 */
+int pw_data_loop_invoke(struct pw_data_loop *loop,
+ spa_invoke_func_t func, uint32_t seq, const void *data, size_t size,
+ bool block, void *user_data);
+
+/** Set a custom spa_thread_utils for this loop. Setting NULL restores the
+ * system default implementation. Since 0.3.50 */
+void pw_data_loop_set_thread_utils(struct pw_data_loop *loop,
+ struct spa_thread_utils *impl);
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_DATA_LOOP_H */
diff --git a/src/pipewire/device.h b/src/pipewire/device.h
new file mode 100644
index 0000000..5bcaf80
--- /dev/null
+++ b/src/pipewire/device.h
@@ -0,0 +1,178 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_DEVICE_H
+#define PIPEWIRE_DEVICE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+#include <pipewire/proxy.h>
+
+/** \defgroup pw_device Device
+ * Device interface
+ */
+
+/**
+ * \addtogroup pw_device
+ * \{
+ */
+
+#define PW_TYPE_INTERFACE_Device PW_TYPE_INFO_INTERFACE_BASE "Device"
+
+#define PW_VERSION_DEVICE 3
+struct pw_device;
+
+/** The device information. Extra information can be added in later versions */
+struct pw_device_info {
+ uint32_t id; /**< id of the global */
+#define PW_DEVICE_CHANGE_MASK_PROPS (1 << 0)
+#define PW_DEVICE_CHANGE_MASK_PARAMS (1 << 1)
+#define PW_DEVICE_CHANGE_MASK_ALL ((1 << 2)-1)
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ struct spa_dict *props; /**< extra properties */
+ struct spa_param_info *params; /**< parameters */
+ uint32_t n_params; /**< number of items in \a params */
+};
+
+/** Update and existing \ref pw_device_info with \a update and reset */
+struct pw_device_info *
+pw_device_info_update(struct pw_device_info *info,
+ const struct pw_device_info *update);
+/** Merge and existing \ref pw_device_info with \a update */
+struct pw_device_info *
+pw_device_info_merge(struct pw_device_info *info,
+ const struct pw_device_info *update, bool reset);
+/** Free a \ref pw_device_info */
+void pw_device_info_free(struct pw_device_info *info);
+
+#define PW_DEVICE_EVENT_INFO 0
+#define PW_DEVICE_EVENT_PARAM 1
+#define PW_DEVICE_EVENT_NUM 2
+
+/** Device events */
+struct pw_device_events {
+#define PW_VERSION_DEVICE_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify device info
+ *
+ * \param info info about the device
+ */
+ void (*info) (void *data, const struct pw_device_info *info);
+ /**
+ * Notify a device param
+ *
+ * Event emitted as a result of the enum_params method.
+ *
+ * \param seq the sequence number of the request
+ * \param id the param id
+ * \param index the param index
+ * \param next the param index of the next param
+ * \param param the parameter
+ */
+ void (*param) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param);
+};
+
+
+#define PW_DEVICE_METHOD_ADD_LISTENER 0
+#define PW_DEVICE_METHOD_SUBSCRIBE_PARAMS 1
+#define PW_DEVICE_METHOD_ENUM_PARAMS 2
+#define PW_DEVICE_METHOD_SET_PARAM 3
+#define PW_DEVICE_METHOD_NUM 4
+
+/** Device methods */
+struct pw_device_methods {
+#define PW_VERSION_DEVICE_METHODS 0
+ uint32_t version;
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_device_events *events,
+ void *data);
+ /**
+ * Subscribe to parameter changes
+ *
+ * Automatically emit param events for the given ids when
+ * they are changed.
+ *
+ * \param ids an array of param ids
+ * \param n_ids the number of ids in \a ids
+ */
+ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
+
+ /**
+ * Enumerate device parameters
+ *
+ * Start enumeration of device parameters. For each param, a
+ * param event will be emitted.
+ *
+ * \param seq a sequence number to place in the reply
+ * \param id the parameter id to enum or PW_ID_ANY for all
+ * \param start the start index or 0 for the first param
+ * \param num the maximum number of params to retrieve
+ * \param filter a param filter or NULL
+ */
+ int (*enum_params) (void *object, int seq, uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter);
+ /**
+ * Set a parameter on the device
+ *
+ * \param id the parameter id to set
+ * \param flags extra parameter flags
+ * \param param the parameter to set
+ */
+ int (*set_param) (void *object, uint32_t id, uint32_t flags,
+ 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__)
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_DEVICE_H */
diff --git a/src/pipewire/extensions/client-node.h b/src/pipewire/extensions/client-node.h
new file mode 100644
index 0000000..15dbae3
--- /dev/null
+++ b/src/pipewire/extensions/client-node.h
@@ -0,0 +1,357 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_EXT_CLIENT_NODE_H
+#define PIPEWIRE_EXT_CLIENT_NODE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/param/param.h>
+
+/** \defgroup pw_client_node Client Node
+ * Client node interface
+ */
+
+/**
+ * \addtogroup pw_client_node
+ * \{
+ */
+#define PW_TYPE_INTERFACE_ClientNode PW_TYPE_INFO_INTERFACE_BASE "ClientNode"
+
+#define PW_VERSION_CLIENT_NODE 4
+struct pw_client_node;
+
+#define PW_EXTENSION_MODULE_CLIENT_NODE PIPEWIRE_MODULE_PREFIX "module-client-node"
+
+/** information about a buffer */
+struct pw_client_node_buffer {
+ uint32_t mem_id; /**< the memory id for the metadata */
+ uint32_t offset; /**< offset in memory */
+ uint32_t size; /**< size in memory */
+ struct spa_buffer *buffer; /**< buffer describing metadata and buffer memory */
+};
+
+#define PW_CLIENT_NODE_EVENT_TRANSPORT 0
+#define PW_CLIENT_NODE_EVENT_SET_PARAM 1
+#define PW_CLIENT_NODE_EVENT_SET_IO 2
+#define PW_CLIENT_NODE_EVENT_EVENT 3
+#define PW_CLIENT_NODE_EVENT_COMMAND 4
+#define PW_CLIENT_NODE_EVENT_ADD_PORT 5
+#define PW_CLIENT_NODE_EVENT_REMOVE_PORT 6
+#define PW_CLIENT_NODE_EVENT_PORT_SET_PARAM 7
+#define PW_CLIENT_NODE_EVENT_PORT_USE_BUFFERS 8
+#define PW_CLIENT_NODE_EVENT_PORT_SET_IO 9
+#define PW_CLIENT_NODE_EVENT_SET_ACTIVATION 10
+#define PW_CLIENT_NODE_EVENT_PORT_SET_MIX_INFO 11
+#define PW_CLIENT_NODE_EVENT_NUM 12
+
+/** \ref pw_client_node events */
+struct pw_client_node_events {
+#define PW_VERSION_CLIENT_NODE_EVENTS 1
+ uint32_t version;
+ /**
+ * Notify of a new transport area
+ *
+ * The transport area is used to signal the client and the server.
+ *
+ * \param readfd fd for signal data can be read
+ * \param writefd fd for signal data can be written
+ * \param mem_id id for activation memory
+ * \param offset offset of activation memory
+ * \param size size of activation memory
+ */
+ int (*transport) (void *data,
+ int readfd,
+ int writefd,
+ uint32_t mem_id,
+ uint32_t offset,
+ uint32_t size);
+ /**
+ * Notify of a property change
+ *
+ * When the server configures the properties on the node
+ * this event is sent
+ *
+ * \param id the id of the parameter
+ * \param flags parameter flags
+ * \param param the param to set
+ */
+ int (*set_param) (void *data,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+ /**
+ * Configure an IO area for the client
+ *
+ * IO areas are identified with an id and are used to
+ * exchange state between client and server
+ *
+ * \param id the id of the io area
+ * \param mem_id the id of the memory to use
+ * \param offset offset of io area in memory
+ * \param size size of the io area
+ */
+ int (*set_io) (void *data,
+ uint32_t id,
+ uint32_t mem_id,
+ uint32_t offset,
+ uint32_t size);
+ /**
+ * Receive an event from the client node
+ * \param event the received event */
+ int (*event) (void *data, const struct spa_event *event);
+ /**
+ * Notify of a new node command
+ *
+ * \param command the command
+ */
+ int (*command) (void *data, const struct spa_command *command);
+ /**
+ * A new port was added to the node
+ *
+ * The server can at any time add a port to the node when there
+ * are free ports available.
+ *
+ * \param direction the direction of the port
+ * \param port_id the new port id
+ * \param props extra properties
+ */
+ int (*add_port) (void *data,
+ enum spa_direction direction,
+ uint32_t port_id,
+ const struct spa_dict *props);
+ /**
+ * A port was removed from the node
+ *
+ * \param direction a port direction
+ * \param port_id the remove port id
+ */
+ int (*remove_port) (void *data,
+ enum spa_direction direction,
+ uint32_t port_id);
+ /**
+ * A parameter was configured on the port
+ *
+ * \param direction a port direction
+ * \param port_id the port id
+ * \param id the id of the parameter
+ * \param flags flags used when setting the param
+ * \param param the new param
+ */
+ int (*port_set_param) (void *data,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+ /**
+ * Notify the port of buffers
+ *
+ * \param direction a port direction
+ * \param port_id the port id
+ * \param mix_id the mixer port id
+ * \param n_buffer the number of buffers
+ * \param buffers and array of buffer descriptions
+ */
+ int (*port_use_buffers) (void *data,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t mix_id,
+ uint32_t flags,
+ uint32_t n_buffers,
+ struct pw_client_node_buffer *buffers);
+ /**
+ * Configure the io area with \a id of \a port_id.
+ *
+ * \param direction the direction of the port
+ * \param port_id the port id
+ * \param mix_id the mixer port id
+ * \param id the id of the io area to set
+ * \param mem_id the id of the memory to use
+ * \param offset offset of io area in memory
+ * \param size size of the io area
+ */
+ int (*port_set_io) (void *data,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t mix_id,
+ uint32_t id,
+ uint32_t mem_id,
+ uint32_t offset,
+ uint32_t size);
+
+ /**
+ * Notify the activation record of the next
+ * node to trigger
+ *
+ * \param node_id the peer node id
+ * \param signalfd the fd to wake up the peer
+ * \param mem_id the mem id of the memory
+ * \param the offset in \a mem_id to map
+ * \param the size of \a mem_id to map
+ */
+ int (*set_activation) (void *data,
+ uint32_t node_id,
+ int signalfd,
+ uint32_t mem_id,
+ uint32_t offset,
+ uint32_t size);
+
+ /**
+ * Notify about the peer of mix_id
+ *
+ * \param direction the direction of the port
+ * \param port_id the port id
+ * \param mix_id the mix id
+ * \param peer_id the id of the peer port
+ * \param props extra properties
+ *
+ * Since version 4:1
+ */
+ int (*port_set_mix_info) (void *data,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t mix_id,
+ uint32_t peer_id,
+ const struct spa_dict *props);
+};
+
+#define PW_CLIENT_NODE_METHOD_ADD_LISTENER 0
+#define PW_CLIENT_NODE_METHOD_GET_NODE 1
+#define PW_CLIENT_NODE_METHOD_UPDATE 2
+#define PW_CLIENT_NODE_METHOD_PORT_UPDATE 3
+#define PW_CLIENT_NODE_METHOD_SET_ACTIVE 4
+#define PW_CLIENT_NODE_METHOD_EVENT 5
+#define PW_CLIENT_NODE_METHOD_PORT_BUFFERS 6
+#define PW_CLIENT_NODE_METHOD_NUM 7
+
+/** \ref pw_client_node methods */
+struct pw_client_node_methods {
+#define PW_VERSION_CLIENT_NODE_METHODS 0
+ uint32_t version;
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_client_node_events *events,
+ void *data);
+ /** get the node object
+ */
+ struct pw_node * (*get_node) (void *object, uint32_t version, size_t user_data_size);
+ /**
+ * Update the node ports and properties
+ *
+ * Update the maximum number of ports and the params of the
+ * client node.
+ * \param change_mask bitfield with changed parameters
+ * \param max_input_ports new max input ports
+ * \param max_output_ports new max output ports
+ * \param params new params
+ */
+ int (*update) (void *object,
+#define PW_CLIENT_NODE_UPDATE_PARAMS (1 << 0)
+#define PW_CLIENT_NODE_UPDATE_INFO (1 << 1)
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct spa_node_info *info);
+
+ /**
+ * Update a node port
+ *
+ * Update the information of one port of a node.
+ * \param direction the direction of the port
+ * \param port_id the port id to update
+ * \param change_mask a bitfield of changed items
+ * \param n_params number of port parameters
+ * \param params array of port parameters
+ * \param info port information
+ */
+ int (*port_update) (void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+#define PW_CLIENT_NODE_PORT_UPDATE_PARAMS (1 << 0)
+#define PW_CLIENT_NODE_PORT_UPDATE_INFO (1 << 1)
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct spa_port_info *info);
+ /**
+ * Activate or deactivate the node
+ */
+ int (*set_active) (void *object, bool active);
+ /**
+ * Send an event to the node
+ * \param event the event to send
+ */
+ int (*event) (void *object, const struct spa_event *event);
+
+ /**
+ * Send allocated buffers
+ */
+ int (*port_buffers) (void *object,
+ enum spa_direction direction,
+ uint32_t port_id,
+ uint32_t mix_id,
+ uint32_t n_buffers,
+ 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_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,
+ 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__)
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_EXT_CLIENT_NODE_H */
diff --git a/src/pipewire/extensions/meson.build b/src/pipewire/extensions/meson.build
new file mode 100644
index 0000000..983809c
--- /dev/null
+++ b/src/pipewire/extensions/meson.build
@@ -0,0 +1,21 @@
+pipewire_ext_sm_headers = [
+ 'session-manager/impl-interfaces.h',
+ 'session-manager/interfaces.h',
+ 'session-manager/introspect.h',
+ 'session-manager/introspect-funcs.h',
+ 'session-manager/keys.h',
+]
+
+pipewire_ext_headers = [
+ 'client-node.h',
+ 'metadata.h',
+ 'profiler.h',
+ 'protocol-native.h',
+ 'session-manager.h',
+]
+
+install_headers(pipewire_ext_sm_headers,
+ subdir : pipewire_headers_dir / 'extensions' / 'session-manager')
+
+install_headers(pipewire_ext_headers,
+ subdir : pipewire_headers_dir / 'extensions')
diff --git a/src/pipewire/extensions/metadata.h b/src/pipewire/extensions/metadata.h
new file mode 100644
index 0000000..9541e2a
--- /dev/null
+++ b/src/pipewire/extensions/metadata.h
@@ -0,0 +1,112 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_EXT_METADATA_H
+#define PIPEWIRE_EXT_METADATA_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+
+/** \defgroup pw_metadata Metadata
+ * Metadata interface
+ */
+
+/**
+ * \addtogroup pw_metadata
+ * \{
+ */
+#define PW_TYPE_INTERFACE_Metadata PW_TYPE_INFO_INTERFACE_BASE "Metadata"
+
+#define PW_VERSION_METADATA 3
+struct pw_metadata;
+
+#define PW_EXTENSION_MODULE_METADATA PIPEWIRE_MODULE_PREFIX "module-metadata"
+
+#define PW_METADATA_EVENT_PROPERTY 0
+#define PW_METADATA_EVENT_NUM 1
+
+/** \ref pw_metadata events */
+struct pw_metadata_events {
+#define PW_VERSION_METADATA_EVENTS 0
+ uint32_t version;
+
+ int (*property) (void *data,
+ uint32_t subject,
+ const char *key,
+ const char *type,
+ const char *value);
+};
+
+#define PW_METADATA_METHOD_ADD_LISTENER 0
+#define PW_METADATA_METHOD_SET_PROPERTY 1
+#define PW_METADATA_METHOD_CLEAR 2
+#define PW_METADATA_METHOD_NUM 3
+
+/** \ref pw_metadata methods */
+struct pw_metadata_methods {
+#define PW_VERSION_METADATA_METHODS 0
+ uint32_t version;
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_metadata_events *events,
+ void *data);
+
+ int (*set_property) (void *object,
+ uint32_t subject,
+ const char *key,
+ const char *type,
+ const char *value);
+
+ 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)
+
+#define PW_KEY_METADATA_NAME "metadata.name"
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_EXT_METADATA_H */
diff --git a/src/pipewire/extensions/profiler.h b/src/pipewire/extensions/profiler.h
new file mode 100644
index 0000000..a5940a1
--- /dev/null
+++ b/src/pipewire/extensions/profiler.h
@@ -0,0 +1,95 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_EXT_PROFILER_H
+#define PIPEWIRE_EXT_PROFILER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+
+/** \defgroup pw_profiler Profiler
+ * Profiler interface
+ */
+
+/**
+ * \addtogroup pw_profiler
+ * \{
+ */
+#define PW_TYPE_INTERFACE_Profiler PW_TYPE_INFO_INTERFACE_BASE "Profiler"
+
+#define PW_VERSION_PROFILER 3
+struct pw_profiler;
+
+#define PW_EXTENSION_MODULE_PROFILER PIPEWIRE_MODULE_PREFIX "module-profiler"
+
+#define PW_PROFILER_EVENT_PROFILE 0
+#define PW_PROFILER_EVENT_NUM 1
+
+/** \ref pw_profiler events */
+struct pw_profiler_events {
+#define PW_VERSION_PROFILER_EVENTS 0
+ uint32_t version;
+
+ void (*profile) (void *data, const struct spa_pod *pod);
+};
+
+#define PW_PROFILER_METHOD_ADD_LISTENER 0
+#define PW_PROFILER_METHOD_NUM 1
+
+/** \ref pw_profiler methods */
+struct pw_profiler_methods {
+#define PW_VERSION_PROFILER_METHODS 0
+ uint32_t version;
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_profiler_events *events,
+ 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__)
+
+#define PW_KEY_PROFILER_NAME "profiler.name"
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_EXT_PROFILER_H */
diff --git a/src/pipewire/extensions/protocol-native.h b/src/pipewire/extensions/protocol-native.h
new file mode 100644
index 0000000..4fe1905
--- /dev/null
+++ b/src/pipewire/extensions/protocol-native.h
@@ -0,0 +1,105 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_EXT_PROTOCOL_NATIVE_H
+#define PIPEWIRE_EXT_PROTOCOL_NATIVE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+
+#include <pipewire/proxy.h>
+#include <pipewire/resource.h>
+
+/** \defgroup pw_protocol_native Native Protocol
+ * PipeWire native protocol interface
+ */
+
+/**
+ * \addtogroup pw_protocol_native
+ * \{
+ */
+#define PW_TYPE_INFO_PROTOCOL_Native PW_TYPE_INFO_PROTOCOL_BASE "Native"
+
+struct pw_protocol_native_message {
+ uint32_t id;
+ uint32_t opcode;
+ void *data;
+ uint32_t size;
+ uint32_t n_fds;
+ int *fds;
+ int seq;
+};
+
+struct pw_protocol_native_demarshal {
+ int (*func) (void *object, const struct pw_protocol_native_message *msg);
+ uint32_t permissions;
+ uint32_t flags;
+};
+
+/** \ref pw_protocol_native_ext methods */
+struct pw_protocol_native_ext {
+#define PW_VERSION_PROTOCOL_NATIVE_EXT 0
+ uint32_t version;
+
+ struct spa_pod_builder * (*begin_proxy) (struct pw_proxy *proxy,
+ uint8_t opcode, struct pw_protocol_native_message **msg);
+
+ uint32_t (*add_proxy_fd) (struct pw_proxy *proxy, int fd);
+ int (*get_proxy_fd) (struct pw_proxy *proxy, uint32_t index);
+
+ int (*end_proxy) (struct pw_proxy *proxy,
+ struct spa_pod_builder *builder);
+
+ struct spa_pod_builder * (*begin_resource) (struct pw_resource *resource,
+ uint8_t opcode, struct pw_protocol_native_message **msg);
+
+ uint32_t (*add_resource_fd) (struct pw_resource *resource, int fd);
+ int (*get_resource_fd) (struct pw_resource *resource, uint32_t index);
+
+ int (*end_resource) (struct pw_resource *resource,
+ struct spa_pod_builder *builder);
+};
+
+#define pw_protocol_native_begin_proxy(p,...) pw_protocol_ext(pw_proxy_get_protocol(p),struct pw_protocol_native_ext,begin_proxy,p,__VA_ARGS__)
+#define pw_protocol_native_add_proxy_fd(p,...) pw_protocol_ext(pw_proxy_get_protocol(p),struct pw_protocol_native_ext,add_proxy_fd,p,__VA_ARGS__)
+#define pw_protocol_native_get_proxy_fd(p,...) pw_protocol_ext(pw_proxy_get_protocol(p),struct pw_protocol_native_ext,get_proxy_fd,p,__VA_ARGS__)
+#define pw_protocol_native_end_proxy(p,...) pw_protocol_ext(pw_proxy_get_protocol(p),struct pw_protocol_native_ext,end_proxy,p,__VA_ARGS__)
+
+#define pw_protocol_native_begin_resource(r,...) pw_protocol_ext(pw_resource_get_protocol(r),struct pw_protocol_native_ext,begin_resource,r,__VA_ARGS__)
+#define pw_protocol_native_add_resource_fd(r,...) pw_protocol_ext(pw_resource_get_protocol(r),struct pw_protocol_native_ext,add_resource_fd,r,__VA_ARGS__)
+#define pw_protocol_native_get_resource_fd(r,...) pw_protocol_ext(pw_resource_get_protocol(r),struct pw_protocol_native_ext,get_resource_fd,r,__VA_ARGS__)
+#define pw_protocol_native_end_resource(r,...) pw_protocol_ext(pw_resource_get_protocol(r),struct pw_protocol_native_ext,end_resource,r,__VA_ARGS__)
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_EXT_PROTOCOL_NATIVE_H */
diff --git a/src/pipewire/extensions/session-manager.h b/src/pipewire/extensions/session-manager.h
new file mode 100644
index 0000000..1226d23
--- /dev/null
+++ b/src/pipewire/extensions/session-manager.h
@@ -0,0 +1,47 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_EXT_SESSION_MANAGER_H
+#define PIPEWIRE_EXT_SESSION_MANAGER_H
+
+/** \defgroup pw_session_manager Session Manager
+ * Session manager interface
+ */
+
+/**
+ * \addtogroup pw_session_manager
+ * \{
+ */
+
+#include "session-manager/introspect.h"
+#include "session-manager/interfaces.h"
+#include "session-manager/impl-interfaces.h"
+#include "session-manager/keys.h"
+
+/**
+ * \}
+ */
+
+#endif /* PIPEWIRE_EXT_SESSION_MANAGER_H */
diff --git a/src/pipewire/extensions/session-manager/impl-interfaces.h b/src/pipewire/extensions/session-manager/impl-interfaces.h
new file mode 100644
index 0000000..20dbc11
--- /dev/null
+++ b/src/pipewire/extensions/session-manager/impl-interfaces.h
@@ -0,0 +1,298 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_EXT_SESSION_MANAGER_IMPL_INTERFACES_H
+#define PIPEWIRE_EXT_SESSION_MANAGER_IMPL_INTERFACES_H
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+#include <errno.h>
+
+#include "introspect.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \addtogroup pw_session_manager
+ * \{
+ */
+
+#define PW_TYPE_INTERFACE_ClientEndpoint PW_TYPE_INFO_INTERFACE_BASE "ClientEndpoint"
+
+#define PW_VERSION_CLIENT_ENDPOINT 0
+struct pw_client_endpoint;
+
+#define PW_CLIENT_ENDPOINT_EVENT_SET_SESSION_ID 0
+#define PW_CLIENT_ENDPOINT_EVENT_SET_PARAM 1
+#define PW_CLIENT_ENDPOINT_EVENT_STREAM_SET_PARAM 2
+#define PW_CLIENT_ENDPOINT_EVENT_CREATE_LINK 3
+#define PW_CLIENT_ENDPOINT_EVENT_NUM 4
+
+struct pw_client_endpoint_events {
+#define PW_VERSION_CLIENT_ENDPOINT_EVENTS 0
+ uint32_t version; /**< version of this structure */
+
+ /**
+ * Sets the session id of the \a endpoint.
+ *
+ * On endpoints that are not session masters, this method notifies
+ * the implementation that it has been associated with a session.
+ * The implementation is obliged to set this id in the
+ * #struct pw_endpoint_info \a session_id field.
+ *
+ * \param endpoint a #pw_endpoint
+ * \param id the session id associated with this endpoint
+ *
+ * \return 0 on success
+ * -EINVAL when the session id has already been set
+ * -ENOTSUP when the endpoint is a session master
+ */
+ int (*set_session_id) (void *data, uint32_t session_id);
+
+ /**
+ * Set the configurable parameter in \a endpoint.
+ *
+ * Usually, \a param will be obtained from enum_params and then
+ * modified but it is also possible to set another spa_pod
+ * as long as its keys and types match a supported object.
+ *
+ * Objects with property keys that are not known are ignored.
+ *
+ * This function must be called from the main thread.
+ *
+ * \param endpoint a #struct pw_endpoint
+ * \param id the parameter id to configure
+ * \param flags additional flags
+ * \param param the parameter to configure
+ *
+ * \return 0 on success
+ * -EINVAL when \a endpoint is NULL
+ * -ENOTSUP when there are no parameters implemented on \a endpoint
+ * -ENOENT the parameter is unknown
+ */
+ int (*set_param) (void *data,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+
+ /**
+ * Set a parameter on \a stream_id of \a endpoint.
+ *
+ * When \a param is NULL, the parameter will be unset.
+ *
+ * This function must be called from the main thread.
+ *
+ * \param endpoint a #struct pw_endpoint
+ * \param stream_id the stream to configure
+ * \param id the parameter id to set
+ * \param flags optional flags
+ * \param param a #struct spa_pod with the parameter to set
+ * \return 0 on success
+ * 1 on success, the value of \a param might have been
+ * changed depending on \a flags and the final value can
+ * be found by doing stream_enum_params.
+ * -EINVAL when \a endpoint is NULL or invalid arguments are given
+ * -ESRCH when the type or size of a property is not correct.
+ * -ENOENT when the param id is not found
+ */
+ int (*stream_set_param) (void *data, uint32_t stream_id,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+
+ int (*create_link) (void *data, const struct spa_dict *props);
+};
+
+#define PW_CLIENT_ENDPOINT_METHOD_ADD_LISTENER 0
+#define PW_CLIENT_ENDPOINT_METHOD_UPDATE 1
+#define PW_CLIENT_ENDPOINT_METHOD_STREAM_UPDATE 2
+#define PW_CLIENT_ENDPOINT_METHOD_NUM 3
+
+struct pw_client_endpoint_methods {
+#define PW_VERSION_CLIENT_ENDPOINT_METHODS 0
+ uint32_t version; /**< version of this structure */
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_client_endpoint_events *events,
+ void *data);
+
+ /** Update endpoint information */
+ int (*update) (void *object,
+#define PW_CLIENT_ENDPOINT_UPDATE_PARAMS (1 << 0)
+#define PW_CLIENT_ENDPOINT_UPDATE_INFO (1 << 1)
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_info *info);
+
+ /** Update stream information */
+ int (*stream_update) (void *object,
+ uint32_t stream_id,
+#define PW_CLIENT_ENDPOINT_STREAM_UPDATE_PARAMS (1 << 0)
+#define PW_CLIENT_ENDPOINT_STREAM_UPDATE_INFO (1 << 1)
+#define PW_CLIENT_ENDPOINT_STREAM_UPDATE_DESTROYED (1 << 2)
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_stream_info *info);
+};
+
+#define pw_client_endpoint_method(o,method,version,...) \
+({ \
+ int _res = -ENOTSUP; \
+ spa_interface_call_res((struct spa_interface*)o, \
+ struct pw_client_endpoint_methods, _res, \
+ method, version, ##__VA_ARGS__); \
+ _res; \
+})
+
+#define pw_client_endpoint_add_listener(o,...) pw_client_endpoint_method(o,add_listener,0,__VA_ARGS__)
+#define pw_client_endpoint_update(o,...) pw_client_endpoint_method(o,update,0,__VA_ARGS__)
+#define pw_client_endpoint_stream_update(o,...) pw_client_endpoint_method(o,stream_update,0,__VA_ARGS__)
+
+#define PW_TYPE_INTERFACE_ClientSession PW_TYPE_INFO_INTERFACE_BASE "ClientSession"
+
+#define PW_VERSION_CLIENT_SESSION 0
+struct pw_client_session;
+
+#define PW_CLIENT_SESSION_EVENT_SET_PARAM 0
+#define PW_CLIENT_SESSION_EVENT_LINK_SET_PARAM 1
+#define PW_CLIENT_SESSION_EVENT_LINK_REQUEST_STATE 2
+#define PW_CLIENT_SESSION_EVENT_NUM 3
+
+struct pw_client_session_events {
+#define PW_VERSION_CLIENT_SESSION_EVENTS 0
+ uint32_t version; /**< version of this structure */
+
+ /**
+ * Set the configurable parameter in \a session.
+ *
+ * Usually, \a param will be obtained from enum_params and then
+ * modified but it is also possible to set another spa_pod
+ * as long as its keys and types match a supported object.
+ *
+ * Objects with property keys that are not known are ignored.
+ *
+ * This function must be called from the main thread.
+ *
+ * \param session a #struct pw_session
+ * \param id the parameter id to configure
+ * \param flags additional flags
+ * \param param the parameter to configure
+ *
+ * \return 0 on success
+ * -EINVAL when \a session is NULL
+ * -ENOTSUP when there are no parameters implemented on \a session
+ * -ENOENT the parameter is unknown
+ */
+ int (*set_param) (void *data,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+
+ /**
+ * Set a parameter on \a link_id of \a session.
+ *
+ * When \a param is NULL, the parameter will be unset.
+ *
+ * This function must be called from the main thread.
+ *
+ * \param session a #struct pw_session
+ * \param link_id the link to configure
+ * \param id the parameter id to set
+ * \param flags optional flags
+ * \param param a #struct spa_pod with the parameter to set
+ * \return 0 on success
+ * 1 on success, the value of \a param might have been
+ * changed depending on \a flags and the final value can
+ * be found by doing link_enum_params.
+ * -EINVAL when \a session is NULL or invalid arguments are given
+ * -ESRCH when the type or size of a property is not correct.
+ * -ENOENT when the param id is not found
+ */
+ int (*link_set_param) (void *data, uint32_t link_id,
+ uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+
+ int (*link_request_state) (void *data, uint32_t link_id, uint32_t state);
+};
+
+#define PW_CLIENT_SESSION_METHOD_ADD_LISTENER 0
+#define PW_CLIENT_SESSION_METHOD_UPDATE 1
+#define PW_CLIENT_SESSION_METHOD_LINK_UPDATE 2
+#define PW_CLIENT_SESSION_METHOD_NUM 3
+
+struct pw_client_session_methods {
+#define PW_VERSION_CLIENT_SESSION_METHODS 0
+ uint32_t version; /**< version of this structure */
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_client_session_events *events,
+ void *data);
+
+ /** Update session information */
+ int (*update) (void *object,
+#define PW_CLIENT_SESSION_UPDATE_PARAMS (1 << 0)
+#define PW_CLIENT_SESSION_UPDATE_INFO (1 << 1)
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_session_info *info);
+
+ /** Update link information */
+ int (*link_update) (void *object,
+ uint32_t link_id,
+#define PW_CLIENT_SESSION_LINK_UPDATE_PARAMS (1 << 0)
+#define PW_CLIENT_SESSION_LINK_UPDATE_INFO (1 << 1)
+#define PW_CLIENT_SESSION_LINK_UPDATE_DESTROYED (1 << 2)
+ uint32_t change_mask,
+ uint32_t n_params,
+ const struct spa_pod **params,
+ const struct pw_endpoint_link_info *info);
+};
+
+#define pw_client_session_method(o,method,version,...) \
+({ \
+ int _res = -ENOTSUP; \
+ spa_interface_call_res((struct spa_interface*)o, \
+ struct pw_client_session_methods, _res, \
+ method, version, ##__VA_ARGS__); \
+ _res; \
+})
+
+#define pw_client_session_add_listener(o,...) pw_client_session_method(o,add_listener,0,__VA_ARGS__)
+#define pw_client_session_update(o,...) pw_client_session_method(o,update,0,__VA_ARGS__)
+#define pw_client_session_link_update(o,...) pw_client_session_method(o,link_update,0,__VA_ARGS__)
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_EXT_SESSION_MANAGER_IMPL_INTERFACES_H */
diff --git a/src/pipewire/extensions/session-manager/interfaces.h b/src/pipewire/extensions/session-manager/interfaces.h
new file mode 100644
index 0000000..0c11e1d
--- /dev/null
+++ b/src/pipewire/extensions/session-manager/interfaces.h
@@ -0,0 +1,479 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_EXT_SESSION_MANAGER_INTERFACES_H
+#define PIPEWIRE_EXT_SESSION_MANAGER_INTERFACES_H
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+#include "introspect.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \addtogroup pw_session_manager
+ * \{
+ */
+
+#define PW_TYPE_INTERFACE_Session PW_TYPE_INFO_INTERFACE_BASE "Session"
+#define PW_VERSION_SESSION 0
+struct pw_session;
+
+#define PW_TYPE_INTERFACE_Endpoint PW_TYPE_INFO_INTERFACE_BASE "Endpoint"
+#define PW_VERSION_ENDPOINT 0
+struct pw_endpoint;
+
+#define PW_TYPE_INTERFACE_EndpointStream PW_TYPE_INFO_INTERFACE_BASE "EndpointStream"
+#define PW_VERSION_ENDPOINT_STREAM 0
+struct pw_endpoint_stream;
+
+#define PW_TYPE_INTERFACE_EndpointLink PW_TYPE_INFO_INTERFACE_BASE "EndpointLink"
+#define PW_VERSION_ENDPOINT_LINK 0
+struct pw_endpoint_link;
+
+/* Session */
+
+#define PW_SESSION_EVENT_INFO 0
+#define PW_SESSION_EVENT_PARAM 1
+#define PW_SESSION_EVENT_NUM 2
+
+struct pw_session_events {
+#define PW_VERSION_SESSION_EVENTS 0
+ uint32_t version; /**< version of this structure */
+
+ /**
+ * Notify session info
+ *
+ * \param info info about the session
+ */
+ void (*info) (void *data, const struct pw_session_info *info);
+
+ /**
+ * Notify a session param
+ *
+ * Event emitted as a result of the enum_params method.
+ *
+ * \param seq the sequence number of the request
+ * \param id the param id
+ * \param index the param index
+ * \param next the param index of the next param
+ * \param param the parameter
+ */
+ void (*param) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param);
+};
+
+#define PW_SESSION_METHOD_ADD_LISTENER 0
+#define PW_SESSION_METHOD_SUBSCRIBE_PARAMS 1
+#define PW_SESSION_METHOD_ENUM_PARAMS 2
+#define PW_SESSION_METHOD_SET_PARAM 3
+#define PW_SESSION_METHOD_CREATE_LINK 4
+#define PW_SESSION_METHOD_NUM 5
+
+struct pw_session_methods {
+#define PW_VERSION_SESSION_METHODS 0
+ uint32_t version; /**< version of this structure */
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_session_events *events,
+ void *data);
+
+ /**
+ * Subscribe to parameter changes
+ *
+ * Automatically emit param events for the given ids when
+ * they are changed.
+ *
+ * \param ids an array of param ids
+ * \param n_ids the number of ids in \a ids
+ */
+ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
+
+ /**
+ * Enumerate session parameters
+ *
+ * Start enumeration of session parameters. For each param, a
+ * param event will be emitted.
+ *
+ * \param seq a sequence number returned in the reply
+ * \param id the parameter id to enumerate
+ * \param start the start index or 0 for the first param
+ * \param num the maximum number of params to retrieve
+ * \param filter a param filter or NULL
+ */
+ int (*enum_params) (void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter);
+
+ /**
+ * Set a parameter on the session
+ *
+ * \param id the parameter id to set
+ * \param flags extra parameter flags
+ * \param param the parameter to set
+ */
+ int (*set_param) (void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+};
+
+#define pw_session_method(o,method,version,...) \
+({ \
+ int _res = -ENOTSUP; \
+ spa_interface_call_res((struct spa_interface*)o, \
+ struct pw_session_methods, _res, \
+ method, version, ##__VA_ARGS__); \
+ _res; \
+})
+
+#define pw_session_add_listener(c,...) pw_session_method(c,add_listener,0,__VA_ARGS__)
+#define pw_session_subscribe_params(c,...) pw_session_method(c,subscribe_params,0,__VA_ARGS__)
+#define pw_session_enum_params(c,...) pw_session_method(c,enum_params,0,__VA_ARGS__)
+#define pw_session_set_param(c,...) pw_session_method(c,set_param,0,__VA_ARGS__)
+
+
+/* Endpoint */
+
+#define PW_ENDPOINT_EVENT_INFO 0
+#define PW_ENDPOINT_EVENT_PARAM 1
+#define PW_ENDPOINT_EVENT_NUM 2
+
+struct pw_endpoint_events {
+#define PW_VERSION_ENDPOINT_EVENTS 0
+ uint32_t version; /**< version of this structure */
+
+ /**
+ * Notify endpoint info
+ *
+ * \param info info about the endpoint
+ */
+ void (*info) (void *data, const struct pw_endpoint_info *info);
+
+ /**
+ * Notify a endpoint param
+ *
+ * Event emitted as a result of the enum_params method.
+ *
+ * \param seq the sequence number of the request
+ * \param id the param id
+ * \param index the param index
+ * \param next the param index of the next param
+ * \param param the parameter
+ */
+ void (*param) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param);
+};
+
+#define PW_ENDPOINT_METHOD_ADD_LISTENER 0
+#define PW_ENDPOINT_METHOD_SUBSCRIBE_PARAMS 1
+#define PW_ENDPOINT_METHOD_ENUM_PARAMS 2
+#define PW_ENDPOINT_METHOD_SET_PARAM 3
+#define PW_ENDPOINT_METHOD_CREATE_LINK 4
+#define PW_ENDPOINT_METHOD_NUM 5
+
+struct pw_endpoint_methods {
+#define PW_VERSION_ENDPOINT_METHODS 0
+ uint32_t version; /**< version of this structure */
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_endpoint_events *events,
+ void *data);
+
+ /**
+ * Subscribe to parameter changes
+ *
+ * Automatically emit param events for the given ids when
+ * they are changed.
+ *
+ * \param ids an array of param ids
+ * \param n_ids the number of ids in \a ids
+ */
+ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
+
+ /**
+ * Enumerate endpoint parameters
+ *
+ * Start enumeration of endpoint parameters. For each param, a
+ * param event will be emitted.
+ *
+ * \param seq a sequence number returned in the reply
+ * \param id the parameter id to enumerate
+ * \param start the start index or 0 for the first param
+ * \param num the maximum number of params to retrieve
+ * \param filter a param filter or NULL
+ */
+ int (*enum_params) (void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter);
+
+ /**
+ * Set a parameter on the endpoint
+ *
+ * \param id the parameter id to set
+ * \param flags extra parameter flags
+ * \param param the parameter to set
+ */
+ int (*set_param) (void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+
+ int (*create_link) (void *object, const struct spa_dict *props);
+};
+
+#define pw_endpoint_method(o,method,version,...) \
+({ \
+ int _res = -ENOTSUP; \
+ spa_interface_call_res((struct spa_interface*)o, \
+ struct pw_endpoint_methods, _res, \
+ method, version, ##__VA_ARGS__); \
+ _res; \
+})
+
+#define pw_endpoint_add_listener(c,...) pw_endpoint_method(c,add_listener,0,__VA_ARGS__)
+#define pw_endpoint_subscribe_params(c,...) pw_endpoint_method(c,subscribe_params,0,__VA_ARGS__)
+#define pw_endpoint_enum_params(c,...) pw_endpoint_method(c,enum_params,0,__VA_ARGS__)
+#define pw_endpoint_set_param(c,...) pw_endpoint_method(c,set_param,0,__VA_ARGS__)
+#define pw_endpoint_create_link(c,...) pw_endpoint_method(c,create_link,0,__VA_ARGS__)
+
+/* Endpoint Stream */
+
+#define PW_ENDPOINT_STREAM_EVENT_INFO 0
+#define PW_ENDPOINT_STREAM_EVENT_PARAM 1
+#define PW_ENDPOINT_STREAM_EVENT_NUM 2
+
+struct pw_endpoint_stream_events {
+#define PW_VERSION_ENDPOINT_STREAM_EVENTS 0
+ uint32_t version; /**< version of this structure */
+
+ /**
+ * Notify endpoint stream info
+ *
+ * \param info info about the endpoint stream
+ */
+ void (*info) (void *data, const struct pw_endpoint_stream_info *info);
+
+ /**
+ * Notify a endpoint stream param
+ *
+ * Event emitted as a result of the enum_params method.
+ *
+ * \param seq the sequence number of the request
+ * \param id the param id
+ * \param index the param index
+ * \param next the param index of the next param
+ * \param param the parameter
+ */
+ void (*param) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param);
+};
+
+#define PW_ENDPOINT_STREAM_METHOD_ADD_LISTENER 0
+#define PW_ENDPOINT_STREAM_METHOD_SUBSCRIBE_PARAMS 1
+#define PW_ENDPOINT_STREAM_METHOD_ENUM_PARAMS 2
+#define PW_ENDPOINT_STREAM_METHOD_SET_PARAM 3
+#define PW_ENDPOINT_STREAM_METHOD_NUM 4
+
+struct pw_endpoint_stream_methods {
+#define PW_VERSION_ENDPOINT_STREAM_METHODS 0
+ uint32_t version; /**< version of this structure */
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_endpoint_stream_events *events,
+ void *data);
+
+ /**
+ * Subscribe to parameter changes
+ *
+ * Automatically emit param events for the given ids when
+ * they are changed.
+ *
+ * \param ids an array of param ids
+ * \param n_ids the number of ids in \a ids
+ */
+ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
+
+ /**
+ * Enumerate stream parameters
+ *
+ * Start enumeration of stream parameters. For each param, a
+ * param event will be emitted.
+ *
+ * \param seq a sequence number returned in the reply
+ * \param id the parameter id to enumerate
+ * \param start the start index or 0 for the first param
+ * \param num the maximum number of params to retrieve
+ * \param filter a param filter or NULL
+ */
+ int (*enum_params) (void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter);
+
+ /**
+ * Set a parameter on the stream
+ *
+ * \param id the parameter id to set
+ * \param flags extra parameter flags
+ * \param param the parameter to set
+ */
+ int (*set_param) (void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+};
+
+#define pw_endpoint_stream_method(o,method,version,...) \
+({ \
+ int _res = -ENOTSUP; \
+ spa_interface_call_res((struct spa_interface*)o, \
+ struct pw_endpoint_stream_methods, _res, \
+ method, version, ##__VA_ARGS__); \
+ _res; \
+})
+
+#define pw_endpoint_stream_add_listener(c,...) pw_endpoint_stream_method(c,add_listener,0,__VA_ARGS__)
+#define pw_endpoint_stream_subscribe_params(c,...) pw_endpoint_stream_method(c,subscribe_params,0,__VA_ARGS__)
+#define pw_endpoint_stream_enum_params(c,...) pw_endpoint_stream_method(c,enum_params,0,__VA_ARGS__)
+#define pw_endpoint_stream_set_param(c,...) pw_endpoint_stream_method(c,set_param,0,__VA_ARGS__)
+
+/* Endpoint Link */
+
+#define PW_ENDPOINT_LINK_EVENT_INFO 0
+#define PW_ENDPOINT_LINK_EVENT_PARAM 1
+#define PW_ENDPOINT_LINK_EVENT_NUM 2
+
+struct pw_endpoint_link_events {
+#define PW_VERSION_ENDPOINT_LINK_EVENTS 0
+ uint32_t version; /**< version of this structure */
+
+ /**
+ * Notify endpoint link info
+ *
+ * \param info info about the endpoint link
+ */
+ void (*info) (void *data, const struct pw_endpoint_link_info *info);
+
+ /**
+ * Notify a endpoint link param
+ *
+ * Event emitted as a result of the enum_params method.
+ *
+ * \param seq the sequence number of the request
+ * \param id the param id
+ * \param index the param index
+ * \param next the param index of the next param
+ * \param param the parameter
+ */
+ void (*param) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param);
+};
+
+#define PW_ENDPOINT_LINK_METHOD_ADD_LISTENER 0
+#define PW_ENDPOINT_LINK_METHOD_SUBSCRIBE_PARAMS 1
+#define PW_ENDPOINT_LINK_METHOD_ENUM_PARAMS 2
+#define PW_ENDPOINT_LINK_METHOD_SET_PARAM 3
+#define PW_ENDPOINT_LINK_METHOD_REQUEST_STATE 4
+#define PW_ENDPOINT_LINK_METHOD_DESTROY 5
+#define PW_ENDPOINT_LINK_METHOD_NUM 6
+
+struct pw_endpoint_link_methods {
+#define PW_VERSION_ENDPOINT_LINK_METHODS 0
+ uint32_t version; /**< version of this structure */
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_endpoint_link_events *events,
+ void *data);
+
+ /**
+ * Subscribe to parameter changes
+ *
+ * Automatically emit param events for the given ids when
+ * they are changed.
+ *
+ * \param ids an array of param ids
+ * \param n_ids the number of ids in \a ids
+ */
+ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
+
+ /**
+ * Enumerate link parameters
+ *
+ * Start enumeration of link parameters. For each param, a
+ * param event will be emitted.
+ *
+ * \param seq a sequence number returned in the reply
+ * \param id the parameter id to enumerate
+ * \param start the start index or 0 for the first param
+ * \param num the maximum number of params to retrieve
+ * \param filter a param filter or NULL
+ */
+ int (*enum_params) (void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter);
+
+ /**
+ * Set a parameter on the link
+ *
+ * \param id the parameter id to set
+ * \param flags extra parameter flags
+ * \param param the parameter to set
+ */
+ int (*set_param) (void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+
+ int (*request_state) (void *object, enum pw_endpoint_link_state state);
+};
+
+#define pw_endpoint_link_method(o,method,version,...) \
+({ \
+ int _res = -ENOTSUP; \
+ spa_interface_call_res((struct spa_interface*)o, \
+ struct pw_endpoint_link_methods, _res, \
+ method, version, ##__VA_ARGS__); \
+ _res; \
+})
+
+#define pw_endpoint_link_add_listener(c,...) pw_endpoint_link_method(c,add_listener,0,__VA_ARGS__)
+#define pw_endpoint_link_subscribe_params(c,...) pw_endpoint_link_method(c,subscribe_params,0,__VA_ARGS__)
+#define pw_endpoint_link_enum_params(c,...) pw_endpoint_link_method(c,enum_params,0,__VA_ARGS__)
+#define pw_endpoint_link_set_param(c,...) pw_endpoint_link_method(c,set_param,0,__VA_ARGS__)
+#define pw_endpoint_link_request_state(c,...) pw_endpoint_link_method(c,request_state,0,__VA_ARGS__)
+
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_EXT_SESSION_MANAGER_INTERFACES_H */
diff --git a/src/pipewire/extensions/session-manager/introspect-funcs.h b/src/pipewire/extensions/session-manager/introspect-funcs.h
new file mode 100644
index 0000000..24df45d
--- /dev/null
+++ b/src/pipewire/extensions/session-manager/introspect-funcs.h
@@ -0,0 +1,323 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_EXT_SESSION_MANAGER_INTROSPECT_FUNCS_H
+#define PIPEWIRE_EXT_SESSION_MANAGER_INTROSPECT_FUNCS_H
+
+#include "introspect.h"
+#include <spa/pod/builder.h>
+#include <pipewire/pipewire.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \addtogroup pw_session_manager
+ * \{
+ */
+
+static inline struct pw_session_info *
+pw_session_info_update (struct pw_session_info *info,
+ const struct pw_session_info *update)
+{
+ struct extended_info {
+ struct pw_properties *props_storage;
+ struct pw_session_info info;
+ } *ext;
+
+ if (update == NULL)
+ return info;
+
+ if (info == NULL) {
+ ext = (struct extended_info *) calloc(1, sizeof(*ext));
+ if (ext == NULL)
+ return NULL;
+
+ info = &ext->info;
+ info->id = update->id;
+ } else {
+ ext = SPA_CONTAINER_OF(info, struct extended_info, info);
+ }
+
+ info->change_mask = update->change_mask;
+
+ if (update->change_mask & PW_SESSION_CHANGE_MASK_PROPS) {
+ if (!ext->props_storage) {
+ ext->props_storage = pw_properties_new(NULL, NULL);
+ info->props = &ext->props_storage->dict;
+ }
+ pw_properties_clear(ext->props_storage);
+ pw_properties_update(ext->props_storage, update->props);
+ }
+ if (update->change_mask & PW_SESSION_CHANGE_MASK_PARAMS) {
+ info->n_params = update->n_params;
+ free((void *) info->params);
+ if (update->params) {
+ size_t size = info->n_params * sizeof(struct spa_param_info);
+ info->params = (struct spa_param_info *) malloc(size);
+ memcpy(info->params, update->params, size);
+ }
+ else
+ info->params = NULL;
+ }
+ return info;
+}
+
+static inline void
+pw_session_info_free (struct pw_session_info *info)
+{
+ struct extended_info {
+ struct pw_properties *props_storage;
+ struct pw_session_info info;
+ } *ext = SPA_CONTAINER_OF(info, struct extended_info, info);
+
+ pw_properties_free(ext->props_storage);
+ free((void *) info->params);
+ free(ext);
+}
+
+static inline struct pw_endpoint_info *
+pw_endpoint_info_update (struct pw_endpoint_info *info,
+ const struct pw_endpoint_info *update)
+{
+ struct extended_info {
+ struct pw_properties *props_storage;
+ struct pw_endpoint_info info;
+ } *ext;
+
+ if (update == NULL)
+ return info;
+
+ if (info == NULL) {
+ ext = (struct extended_info *) calloc(1, sizeof(*ext));
+ if (ext == NULL)
+ return NULL;
+
+ info = &ext->info;
+ info->id = update->id;
+ info->name = strdup(update->name);
+ info->media_class = strdup(update->media_class);
+ info->direction = update->direction;
+ info->flags = update->flags;
+ } else {
+ ext = SPA_CONTAINER_OF(info, struct extended_info, info);
+ }
+
+ info->change_mask = update->change_mask;
+
+ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_STREAMS)
+ info->n_streams = update->n_streams;
+
+ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_SESSION)
+ info->session_id = update->session_id;
+
+ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) {
+ if (!ext->props_storage) {
+ ext->props_storage = pw_properties_new(NULL, NULL);
+ info->props = &ext->props_storage->dict;
+ }
+ pw_properties_clear(ext->props_storage);
+ pw_properties_update(ext->props_storage, update->props);
+ }
+ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) {
+ info->n_params = update->n_params;
+ free((void *) info->params);
+ if (update->params) {
+ size_t size = info->n_params * sizeof(struct spa_param_info);
+ info->params = (struct spa_param_info *) malloc(size);
+ memcpy(info->params, update->params, size);
+ }
+ else
+ info->params = NULL;
+ }
+ return info;
+}
+
+static inline void
+pw_endpoint_info_free (struct pw_endpoint_info *info)
+{
+ struct extended_info {
+ struct pw_properties *props_storage;
+ struct pw_endpoint_info info;
+ } *ext = SPA_CONTAINER_OF(info, struct extended_info, info);
+
+ pw_properties_free(ext->props_storage);
+ free(info->name);
+ free(info->media_class);
+ free((void *) info->params);
+ free(ext);
+}
+
+static inline struct pw_endpoint_stream_info *
+pw_endpoint_stream_info_update (struct pw_endpoint_stream_info *info,
+ const struct pw_endpoint_stream_info *update)
+{
+ struct extended_info {
+ struct pw_properties *props_storage;
+ struct pw_endpoint_stream_info info;
+ } *ext;
+
+ if (update == NULL)
+ return info;
+
+ if (info == NULL) {
+ ext = (struct extended_info *) calloc(1, sizeof(*ext));
+ if (ext == NULL)
+ return NULL;
+
+ info = &ext->info;
+ info->id = update->id;
+ info->endpoint_id = update->endpoint_id;
+ info->name = strdup(update->name);
+ } else {
+ ext = SPA_CONTAINER_OF(info, struct extended_info, info);
+ }
+
+ info->change_mask = update->change_mask;
+
+ if (update->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_LINK_PARAMS) {
+ free(info->link_params);
+ info->link_params = update->link_params ?
+ spa_pod_copy(update->link_params) : NULL;
+ }
+ if (update->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS) {
+ if (!ext->props_storage) {
+ ext->props_storage = pw_properties_new(NULL, NULL);
+ info->props = &ext->props_storage->dict;
+ }
+ pw_properties_clear(ext->props_storage);
+ pw_properties_update(ext->props_storage, update->props);
+ }
+ if (update->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PARAMS) {
+ info->n_params = update->n_params;
+ free((void *) info->params);
+ if (update->params) {
+ size_t size = info->n_params * sizeof(struct spa_param_info);
+ info->params = (struct spa_param_info *) malloc(size);
+ memcpy(info->params, update->params, size);
+ }
+ else
+ info->params = NULL;
+ }
+ return info;
+}
+
+static inline void
+pw_endpoint_stream_info_free (struct pw_endpoint_stream_info *info)
+{
+ struct extended_info {
+ struct pw_properties *props_storage;
+ struct pw_endpoint_stream_info info;
+ } *ext = SPA_CONTAINER_OF(info, struct extended_info, info);
+
+ pw_properties_free(ext->props_storage);
+ free(info->name);
+ free(info->link_params);
+ free((void *) info->params);
+ free(ext);
+}
+
+
+static inline struct pw_endpoint_link_info *
+pw_endpoint_link_info_update (struct pw_endpoint_link_info *info,
+ const struct pw_endpoint_link_info *update)
+{
+ struct extended_info {
+ struct pw_properties *props_storage;
+ struct pw_endpoint_link_info info;
+ } *ext;
+
+ if (update == NULL)
+ return info;
+
+ if (info == NULL) {
+ ext = (struct extended_info *) calloc(1, sizeof(*ext));
+ if (ext == NULL)
+ return NULL;
+
+ info = &ext->info;
+ info->id = update->id;
+ info->session_id = update->session_id;
+ info->output_endpoint_id = update->output_endpoint_id;
+ info->output_stream_id = update->output_stream_id;
+ info->input_endpoint_id = update->input_endpoint_id;
+ info->input_stream_id = update->input_stream_id;
+ } else {
+ ext = SPA_CONTAINER_OF(info, struct extended_info, info);
+ }
+
+ info->change_mask = update->change_mask;
+
+ if (update->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_STATE) {
+ info->state = update->state;
+ free(info->error);
+ info->error = update->error ? strdup(update->error) : NULL;
+ }
+ if (update->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_PROPS) {
+ if (!ext->props_storage) {
+ ext->props_storage = pw_properties_new(NULL, NULL);
+ info->props = &ext->props_storage->dict;
+ }
+ pw_properties_clear(ext->props_storage);
+ pw_properties_update(ext->props_storage, update->props);
+ }
+ if (update->change_mask & PW_ENDPOINT_LINK_CHANGE_MASK_PARAMS) {
+ info->n_params = update->n_params;
+ free((void *) info->params);
+ if (update->params) {
+ size_t size = info->n_params * sizeof(struct spa_param_info);
+ info->params = (struct spa_param_info *) malloc(size);
+ memcpy(info->params, update->params, size);
+ }
+ else
+ info->params = NULL;
+ }
+ return info;
+}
+
+static inline void
+pw_endpoint_link_info_free (struct pw_endpoint_link_info *info)
+{
+ struct extended_info {
+ struct pw_properties *props_storage;
+ struct pw_endpoint_link_info info;
+ } *ext = SPA_CONTAINER_OF(info, struct extended_info, info);
+
+ pw_properties_free(ext->props_storage);
+ free(info->error);
+ free((void *) info->params);
+ free(ext);
+}
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_EXT_SESSION_MANAGER_INTROSPECT_FUNCS_H */
diff --git a/src/pipewire/extensions/session-manager/introspect.h b/src/pipewire/extensions/session-manager/introspect.h
new file mode 100644
index 0000000..a35a70b
--- /dev/null
+++ b/src/pipewire/extensions/session-manager/introspect.h
@@ -0,0 +1,130 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_EXT_SESSION_MANAGER_INTROSPECT_H
+#define PIPEWIRE_EXT_SESSION_MANAGER_INTROSPECT_H
+
+#include <spa/utils/defs.h>
+#include <spa/utils/dict.h>
+#include <spa/param/param.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \addtogroup pw_session_manager
+ * \{
+ */
+
+enum pw_endpoint_link_state {
+ PW_ENDPOINT_LINK_STATE_ERROR = -1,
+ PW_ENDPOINT_LINK_STATE_PREPARING,
+ PW_ENDPOINT_LINK_STATE_INACTIVE,
+ PW_ENDPOINT_LINK_STATE_ACTIVE,
+};
+
+struct pw_session_info {
+#define PW_VERSION_SESSION_INFO 0
+ uint32_t version; /**< version of this structure */
+ uint32_t id; /**< the session id (global) */
+#define PW_SESSION_CHANGE_MASK_PROPS (1 << 0)
+#define PW_SESSION_CHANGE_MASK_PARAMS (1 << 1)
+#define PW_SESSION_CHANGE_MASK_ALL ((1 << 2)-1)
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ struct spa_dict *props; /**< extra properties */
+ struct spa_param_info *params; /**< parameters */
+ uint32_t n_params; /**< number of items in \a params */
+};
+
+struct pw_endpoint_info {
+#define PW_VERSION_ENDPOINT_INFO 0
+ uint32_t version; /**< version of this structure */
+ uint32_t id; /**< the endpoint id (global) */
+ char *name; /**< name of the endpoint */
+ char *media_class; /**< media class of the endpoint */
+ enum pw_direction direction; /**< direction of the endpoint */
+#define PW_ENDPOINT_FLAG_PROVIDES_SESSION (1 << 0)
+ uint32_t flags; /**< additional flags */
+#define PW_ENDPOINT_CHANGE_MASK_STREAMS (1 << 0)
+#define PW_ENDPOINT_CHANGE_MASK_SESSION (1 << 1)
+#define PW_ENDPOINT_CHANGE_MASK_PROPS (1 << 2)
+#define PW_ENDPOINT_CHANGE_MASK_PARAMS (1 << 3)
+#define PW_ENDPOINT_CHANGE_MASK_ALL ((1 << 4)-1)
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ uint32_t n_streams; /**< number of streams available */
+ uint32_t session_id; /**< the id of the controlling session */
+ struct spa_dict *props; /**< extra properties */
+ struct spa_param_info *params; /**< parameters */
+ uint32_t n_params; /**< number of items in \a params */
+};
+
+struct pw_endpoint_stream_info {
+#define PW_VERSION_ENDPOINT_STREAM_INFO 0
+ uint32_t version; /**< version of this structure */
+ uint32_t id; /**< the stream id (local or global) */
+ uint32_t endpoint_id; /**< the endpoint id (global) */
+ char *name; /**< name of the stream */
+#define PW_ENDPOINT_STREAM_CHANGE_MASK_LINK_PARAMS (1 << 0)
+#define PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS (1 << 1)
+#define PW_ENDPOINT_STREAM_CHANGE_MASK_PARAMS (1 << 2)
+#define PW_ENDPOINT_STREAM_CHANGE_MASK_ALL ((1 << 3)-1)
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ struct spa_pod *link_params; /**< information for linking this stream */
+ struct spa_dict *props; /**< extra properties */
+ struct spa_param_info *params; /**< parameters */
+ uint32_t n_params; /**< number of items in \a params */
+};
+
+struct pw_endpoint_link_info {
+#define PW_VERSION_ENDPOINT_LINK_INFO 0
+ uint32_t version; /**< version of this structure */
+ uint32_t id; /**< the link id (global) */
+ uint32_t session_id; /**< the session id (global) */
+ uint32_t output_endpoint_id; /**< the output endpoint id (global) */
+ uint32_t output_stream_id; /**< the output stream id (local or global) */
+ uint32_t input_endpoint_id; /**< the input endpoint id (global) */
+ uint32_t input_stream_id; /**< the input stream id (local or global) */
+#define PW_ENDPOINT_LINK_CHANGE_MASK_STATE (1 << 0)
+#define PW_ENDPOINT_LINK_CHANGE_MASK_PROPS (1 << 1)
+#define PW_ENDPOINT_LINK_CHANGE_MASK_PARAMS (1 << 2)
+#define PW_ENDPOINT_LINK_CHANGE_MASK_ALL ((1 << 3)-1)
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ enum pw_endpoint_link_state state; /**< the state of the link */
+ char *error; /**< error string if state == ERROR */
+ struct spa_dict *props; /**< extra properties */
+ struct spa_param_info *params; /**< parameters */
+ uint32_t n_params; /**< number of items in \a params */
+};
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_EXT_SESSION_MANAGER_INTROSPECT_H */
diff --git a/src/pipewire/extensions/session-manager/keys.h b/src/pipewire/extensions/session-manager/keys.h
new file mode 100644
index 0000000..9ff89f9
--- /dev/null
+++ b/src/pipewire/extensions/session-manager/keys.h
@@ -0,0 +1,67 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_EXT_SESSION_MANAGER_KEYS_H
+#define PIPEWIRE_EXT_SESSION_MANAGER_KEYS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \addtogroup pw_session_manager
+ * \{
+ */
+
+#define PW_KEY_SESSION_ID "session.id" /**< id of a session manager */
+
+#define PW_KEY_ENDPOINT_ID "endpoint.id" /**< id of an endpoint */
+#define PW_KEY_ENDPOINT_NAME "endpoint.name" /**< the name of an endpoint */
+#define PW_KEY_ENDPOINT_MONITOR "endpoint.monitor" /**< endpoint is monitor of given endpoint */
+#define PW_KEY_ENDPOINT_CLIENT_ID "endpoint.client.id" /**< client of the endpoint */
+#define PW_KEY_ENDPOINT_ICON_NAME "endpoint.icon-name" /**< an XDG icon name for the device.
+ * Ex. "sound-card-speakers-usb" */
+#define PW_KEY_ENDPOINT_AUTOCONNECT "endpoint.autoconnect" /**< try to automatically connect this
+ * endpoint. */
+#define PW_KEY_ENDPOINT_TARGET "endpoint.target" /**< the suggested target to connect to */
+
+#define PW_KEY_ENDPOINT_STREAM_ID "endpoint-stream.id" /**< id of a stream */
+#define PW_KEY_ENDPOINT_STREAM_NAME "endpoint-stream.name" /**< unique name of a stream */
+#define PW_KEY_ENDPOINT_STREAM_DESCRIPTION "endpoint-stream.description" /**< description of a stream */
+
+#define PW_KEY_ENDPOINT_LINK_OUTPUT_ENDPOINT "endpoint-link.output.endpoint" /**< output endpoint of link */
+#define PW_KEY_ENDPOINT_LINK_OUTPUT_STREAM "endpoint-link.output.stream" /**< output stream of link */
+#define PW_KEY_ENDPOINT_LINK_INPUT_ENDPOINT "endpoint-link.input.endpoint" /**< input endpoint of link */
+#define PW_KEY_ENDPOINT_LINK_INPUT_STREAM "endpoint-link.input.stream" /**< input stream of link */
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_EXT_SESSION_MANAGER_KEYS_H */
diff --git a/src/pipewire/factory.h b/src/pipewire/factory.h
new file mode 100644
index 0000000..c747dd9
--- /dev/null
+++ b/src/pipewire/factory.h
@@ -0,0 +1,123 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_FACTORY_H
+#define PIPEWIRE_FACTORY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+#include <errno.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+#include <pipewire/proxy.h>
+
+/** \defgroup pw_factory Factory
+ * Factory interface
+ */
+
+/**
+ * \addtogroup pw_factory
+ * \{
+ */
+#define PW_TYPE_INTERFACE_Factory PW_TYPE_INFO_INTERFACE_BASE "Factory"
+
+#define PW_VERSION_FACTORY 3
+struct pw_factory;
+
+/** The factory information. Extra information can be added in later versions */
+struct pw_factory_info {
+ uint32_t id; /**< id of the global */
+ const char *name; /**< name the factory */
+ const char *type; /**< type of the objects created by this factory */
+ uint32_t version; /**< version of the objects */
+#define PW_FACTORY_CHANGE_MASK_PROPS (1 << 0)
+#define PW_FACTORY_CHANGE_MASK_ALL ((1 << 1)-1)
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ struct spa_dict *props; /**< the properties of the factory */
+};
+
+struct pw_factory_info *
+pw_factory_info_update(struct pw_factory_info *info,
+ const struct pw_factory_info *update);
+struct pw_factory_info *
+pw_factory_info_merge(struct pw_factory_info *info,
+ const struct pw_factory_info *update, bool reset);
+void
+pw_factory_info_free(struct pw_factory_info *info);
+
+
+#define PW_FACTORY_EVENT_INFO 0
+#define PW_FACTORY_EVENT_NUM 1
+
+/** Factory events */
+struct pw_factory_events {
+#define PW_VERSION_FACTORY_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify factory info
+ *
+ * \param info info about the factory
+ */
+ void (*info) (void *data, const struct pw_factory_info *info);
+};
+
+#define PW_FACTORY_METHOD_ADD_LISTENER 0
+#define PW_FACTORY_METHOD_NUM 1
+
+/** Factory methods */
+struct pw_factory_methods {
+#define PW_VERSION_FACTORY_METHODS 0
+ uint32_t version;
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_factory_events *events,
+ 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__)
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_FACTORY_H */
diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c
new file mode 100644
index 0000000..4ddf081
--- /dev/null
+++ b/src/pipewire/filter.c
@@ -0,0 +1,1962 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <math.h>
+#include <sys/mman.h>
+#include <time.h>
+
+#include <spa/buffer/alloc.h>
+#include <spa/param/props.h>
+#include <spa/node/io.h>
+#include <spa/node/utils.h>
+#include <spa/utils/ringbuffer.h>
+#include <spa/utils/string.h>
+#include <spa/pod/filter.h>
+#include <spa/pod/dynamic.h>
+#include <spa/debug/types.h>
+
+#include "pipewire/pipewire.h"
+#include "pipewire/filter.h"
+#include "pipewire/private.h"
+
+PW_LOG_TOPIC_EXTERN(log_filter);
+#define PW_LOG_TOPIC_DEFAULT log_filter
+
+#define MAX_SAMPLES 8192
+#define MAX_BUFFERS 64
+
+#define MASK_BUFFERS (MAX_BUFFERS-1)
+
+static bool mlock_warned = false;
+
+static uint32_t mappable_dataTypes = (1<<SPA_DATA_MemFd);
+
+struct buffer {
+ struct pw_buffer this;
+ uint32_t id;
+#define BUFFER_FLAG_MAPPED (1 << 0)
+#define BUFFER_FLAG_QUEUED (1 << 1)
+#define BUFFER_FLAG_ADDED (1 << 2)
+ uint32_t flags;
+};
+
+struct queue {
+ uint32_t ids[MAX_BUFFERS];
+ struct spa_ringbuffer ring;
+ uint64_t incount;
+ uint64_t outcount;
+};
+
+struct data {
+ struct pw_context *context;
+ struct spa_hook filter_listener;
+};
+
+struct param {
+ uint32_t id;
+#define PARAM_FLAG_LOCKED (1 << 0)
+ uint32_t flags;
+ struct spa_list link;
+ struct spa_pod *param;
+};
+
+struct port {
+ struct spa_list link;
+
+ struct filter *filter;
+
+ enum spa_direction direction;
+ uint32_t id;
+ uint32_t flags;
+ struct pw_port *port;
+
+ struct pw_properties *props;
+
+ uint32_t change_mask_all;
+ struct spa_port_info info;
+ struct spa_list param_list;
+#define IDX_EnumFormat 0
+#define IDX_Meta 1
+#define IDX_IO 2
+#define IDX_Format 3
+#define IDX_Buffers 4
+#define IDX_Latency 5
+#define N_PORT_PARAMS 6
+ struct spa_param_info params[N_PORT_PARAMS];
+
+ struct spa_io_buffers *io;
+
+ struct buffer buffers[MAX_BUFFERS];
+ uint32_t n_buffers;
+
+ struct queue dequeued;
+ struct queue queued;
+
+ struct spa_latency_info latency[2];
+
+ /* from here is what the caller gets as user_data */
+ uint8_t user_data[0];
+};
+
+struct filter {
+ struct pw_filter this;
+
+ const char *path;
+
+ struct pw_context *context;
+
+ enum pw_filter_flags flags;
+
+ struct spa_node impl_node;
+ struct spa_hook_list hooks;
+ struct spa_callbacks callbacks;
+ struct spa_io_position *position;
+
+ struct {
+ struct spa_io_position *position;
+ } rt;
+
+ struct spa_list port_list;
+ struct pw_map ports[2];
+
+ uint32_t change_mask_all;
+ struct spa_node_info info;
+ struct spa_list param_list;
+#define IDX_PropInfo 0
+#define IDX_Props 1
+#define IDX_ProcessLatency 2
+#define N_NODE_PARAMS 3
+ struct spa_param_info params[N_NODE_PARAMS];
+
+ struct spa_process_latency_info process_latency;
+
+ struct data data;
+ uintptr_t seq;
+ struct pw_time time;
+ uint64_t base_pos;
+ uint32_t clock_id;
+
+ struct spa_callbacks rt_callbacks;
+
+ unsigned int disconnecting:1;
+ unsigned int disconnect_core:1;
+ unsigned int subscribe:1;
+ unsigned int draining:1;
+ unsigned int allow_mlock:1;
+ unsigned int warn_mlock:1;
+ unsigned int process_rt:1;
+};
+
+static int get_param_index(uint32_t id)
+{
+ switch (id) {
+ case SPA_PARAM_PropInfo:
+ return IDX_PropInfo;
+ case SPA_PARAM_Props:
+ return IDX_Props;
+ case SPA_PARAM_ProcessLatency:
+ return IDX_ProcessLatency;
+ default:
+ return -1;
+ }
+}
+
+static int get_port_param_index(uint32_t id)
+{
+ switch (id) {
+ case SPA_PARAM_EnumFormat:
+ return IDX_EnumFormat;
+ case SPA_PARAM_Meta:
+ return IDX_Meta;
+ case SPA_PARAM_IO:
+ return IDX_IO;
+ case SPA_PARAM_Format:
+ return IDX_Format;
+ case SPA_PARAM_Buffers:
+ return IDX_Buffers;
+ case SPA_PARAM_Latency:
+ return IDX_Latency;
+ default:
+ return -1;
+ }
+}
+
+static void fix_datatype(const struct spa_pod *param)
+{
+ const struct spa_pod_prop *pod_param;
+ const struct spa_pod *vals;
+ uint32_t dataType, n_vals, choice;
+
+ pod_param = spa_pod_find_prop(param, NULL, SPA_PARAM_BUFFERS_dataType);
+ if (pod_param == NULL)
+ return;
+
+ vals = spa_pod_get_values(&pod_param->value, &n_vals, &choice);
+ if (n_vals == 0)
+ return;
+
+ if (spa_pod_get_int(&vals[0], (int32_t*)&dataType) < 0)
+ return;
+
+ pw_log_debug("dataType: %u", dataType);
+ if (dataType & (1u << SPA_DATA_MemPtr)) {
+ SPA_POD_VALUE(struct spa_pod_int, &vals[0]) =
+ dataType | mappable_dataTypes;
+ pw_log_debug("Change dataType: %u -> %u", dataType,
+ SPA_POD_VALUE(struct spa_pod_int, &vals[0]));
+ }
+}
+
+static struct param *add_param(struct filter *impl, struct port *port,
+ uint32_t id, uint32_t flags, const struct spa_pod *param)
+{
+ struct param *p;
+ int idx;
+
+ if (param == NULL || !spa_pod_is_object(param)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (id == SPA_ID_INVALID)
+ id = SPA_POD_OBJECT_ID(param);
+
+ p = malloc(sizeof(struct param) + SPA_POD_SIZE(param));
+ if (p == NULL)
+ return NULL;
+
+ if (id == SPA_PARAM_Buffers && port != NULL &&
+ SPA_FLAG_IS_SET(port->flags, PW_FILTER_PORT_FLAG_MAP_BUFFERS) &&
+ port->direction == SPA_DIRECTION_INPUT)
+ fix_datatype(param);
+
+ if (id == SPA_PARAM_ProcessLatency && port == NULL)
+ spa_process_latency_parse(param, &impl->process_latency);
+
+ p->id = id;
+ p->flags = flags;
+ p->param = SPA_PTROFF(p, sizeof(struct param), struct spa_pod);
+ memcpy(p->param, param, SPA_POD_SIZE(param));
+ SPA_POD_OBJECT_ID(p->param) = id;
+
+ pw_log_debug("%p: port %p param id %d (%s)", impl, p, id,
+ spa_debug_type_find_name(spa_type_param, id));
+
+ if (port) {
+ idx = get_port_param_index(id);
+ spa_list_append(&port->param_list, &p->link);
+ if (idx != -1) {
+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
+ port->params[idx].flags |= SPA_PARAM_INFO_READ;
+ port->params[idx].user++;
+ }
+ } else {
+ idx = get_param_index(id);
+ spa_list_append(&impl->param_list, &p->link);
+ if (idx != -1) {
+ impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS;
+ impl->params[idx].flags |= SPA_PARAM_INFO_READ;
+ impl->params[idx].user++;
+ }
+ }
+ return p;
+}
+
+static void clear_params(struct filter *impl, struct port *port, uint32_t id)
+{
+ struct param *p, *t;
+ struct spa_list *param_list;
+
+ if (port)
+ param_list = &port->param_list;
+ else
+ param_list = &impl->param_list;
+
+ spa_list_for_each_safe(p, t, param_list, link) {
+ if (id == SPA_ID_INVALID ||
+ (p->id == id && !(p->flags & PARAM_FLAG_LOCKED))) {
+ spa_list_remove(&p->link);
+ free(p);
+ }
+ }
+}
+
+static struct port *alloc_port(struct filter *filter,
+ enum spa_direction direction, uint32_t user_data_size)
+{
+ struct port *p;
+
+ p = calloc(1, sizeof(struct port) + user_data_size);
+ p->filter = filter;
+ p->direction = direction;
+ p->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT);
+ p->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT);
+
+ spa_list_init(&p->param_list);
+ spa_ringbuffer_init(&p->dequeued.ring);
+ spa_ringbuffer_init(&p->queued.ring);
+ p->id = pw_map_insert_new(&filter->ports[direction], p);
+ spa_list_append(&filter->port_list, &p->link);
+
+ return p;
+}
+
+static inline struct port *get_port(struct filter *filter, enum spa_direction direction, uint32_t port_id)
+{
+ if ((direction != SPA_DIRECTION_INPUT && direction != SPA_DIRECTION_OUTPUT))
+ return NULL;
+ return pw_map_lookup(&filter->ports[direction], port_id);
+}
+
+static inline int push_queue(struct port *port, struct queue *queue, struct buffer *buffer)
+{
+ uint32_t index;
+
+ if (SPA_FLAG_IS_SET(buffer->flags, BUFFER_FLAG_QUEUED))
+ return -EINVAL;
+
+ SPA_FLAG_SET(buffer->flags, BUFFER_FLAG_QUEUED);
+ queue->incount += buffer->this.size;
+
+ spa_ringbuffer_get_write_index(&queue->ring, &index);
+ queue->ids[index & MASK_BUFFERS] = buffer->id;
+ spa_ringbuffer_write_update(&queue->ring, index + 1);
+
+ return 0;
+}
+
+static inline struct buffer *pop_queue(struct port *port, struct queue *queue)
+{
+ uint32_t index, id;
+ struct buffer *buffer;
+
+ if (spa_ringbuffer_get_read_index(&queue->ring, &index) < 1) {
+ errno = EPIPE;
+ return NULL;
+ }
+
+ id = queue->ids[index & MASK_BUFFERS];
+ spa_ringbuffer_read_update(&queue->ring, index + 1);
+
+ buffer = &port->buffers[id];
+ queue->outcount += buffer->this.size;
+ SPA_FLAG_CLEAR(buffer->flags, BUFFER_FLAG_QUEUED);
+
+ return buffer;
+}
+
+static inline void clear_queue(struct port *port, struct queue *queue)
+{
+ spa_ringbuffer_init(&queue->ring);
+ queue->incount = queue->outcount;
+}
+
+static bool filter_set_state(struct pw_filter *filter, enum pw_filter_state state, const char *error)
+{
+ enum pw_filter_state old = filter->state;
+ bool res = old != state;
+
+ if (res) {
+ free(filter->error);
+ filter->error = error ? strdup(error) : NULL;
+
+ pw_log_debug("%p: update state from %s -> %s (%s)", filter,
+ pw_filter_state_as_string(old),
+ pw_filter_state_as_string(state), filter->error);
+
+ if (state == PW_FILTER_STATE_ERROR)
+ pw_log_error("%p: error %s", filter, error);
+
+ filter->state = state;
+ pw_filter_emit_state_changed(filter, old, state, error);
+ }
+ return res;
+}
+
+static int enum_params(struct filter *d, struct spa_list *param_list, int seq,
+ uint32_t id, uint32_t start, uint32_t num, const struct spa_pod *filter)
+{
+ struct spa_result_node_params result;
+ uint8_t buffer[1024];
+ struct spa_pod_dynamic_builder b;
+ uint32_t count = 0;
+ struct param *p;
+ bool found = false;
+
+ spa_return_val_if_fail(num != 0, -EINVAL);
+
+ result.id = id;
+ result.next = 0;
+
+ pw_log_debug("%p: %p param id %d (%s) start:%d num:%d", d, param_list, id,
+ spa_debug_type_find_name(spa_type_param, id),
+ start, num);
+
+ spa_list_for_each(p, param_list, link) {
+ struct spa_pod *param;
+
+ param = p->param;
+ if (param == NULL || p->id != id)
+ continue;
+
+ found = true;
+
+ result.index = result.next++;
+ if (result.index < start)
+ continue;
+
+ spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
+ if (spa_pod_filter(&b.b, &result.param, param, filter) == 0) {
+ spa_node_emit_result(&d->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+ count++;
+ }
+ spa_pod_dynamic_builder_clean(&b);
+
+ if (count == num)
+ break;
+ }
+ return found ? 0 : -ENOENT;
+}
+
+static int impl_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct filter *impl = object;
+ return enum_params(impl, &impl->param_list, seq, id, start, num, filter);
+}
+
+static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param)
+{
+ struct filter *impl = object;
+ struct pw_filter *filter = &impl->this;
+
+ if (id != SPA_PARAM_Props)
+ return -ENOTSUP;
+
+ pw_filter_emit_param_changed(filter, NULL, id, param);
+ return 0;
+}
+
+static int
+do_set_position(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct filter *impl = user_data;
+ impl->rt.position = impl->position;
+ return 0;
+}
+
+static int impl_set_io(void *object, uint32_t id, void *data, size_t size)
+{
+ struct filter *impl = object;
+
+ pw_log_debug("%p: io %d %p/%zd", impl, id, data, size);
+
+ switch(id) {
+ case SPA_IO_Position:
+ if (data && size >= sizeof(struct spa_io_position))
+ impl->position = data;
+ else
+ impl->position = NULL;
+ pw_loop_invoke(impl->context->data_loop,
+ do_set_position, 1, NULL, 0, true, impl);
+ break;
+ }
+ pw_filter_emit_io_changed(&impl->this, NULL, id, data, size);
+
+ return 0;
+}
+
+static int impl_send_command(void *object, const struct spa_command *command)
+{
+ struct filter *impl = object;
+ struct pw_filter *filter = &impl->this;
+
+ switch (SPA_NODE_COMMAND_ID(command)) {
+ case SPA_NODE_COMMAND_Suspend:
+ case SPA_NODE_COMMAND_Flush:
+ case SPA_NODE_COMMAND_Pause:
+ pw_loop_invoke(impl->context->main_loop,
+ NULL, 0, NULL, 0, false, impl);
+ if (filter->state == PW_FILTER_STATE_STREAMING) {
+ pw_log_debug("%p: pause", filter);
+ filter_set_state(filter, PW_FILTER_STATE_PAUSED, NULL);
+ }
+ break;
+ case SPA_NODE_COMMAND_Start:
+ if (filter->state == PW_FILTER_STATE_PAUSED) {
+ pw_log_debug("%p: start", filter);
+ filter_set_state(filter, PW_FILTER_STATE_STREAMING, NULL);
+ }
+ break;
+ default:
+ break;
+ }
+ pw_filter_emit_command(filter, command);
+ return 0;
+}
+
+static void emit_node_info(struct filter *d, bool full)
+{
+ uint32_t i;
+ uint64_t old = full ? d->info.change_mask : 0;
+ if (full)
+ d->info.change_mask = d->change_mask_all;
+ if (d->info.change_mask != 0) {
+ if (d->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < d->info.n_params; i++) {
+ if (d->params[i].user > 0) {
+ d->params[i].flags ^= SPA_PARAM_INFO_SERIAL;
+ d->params[i].user = 0;
+ }
+ }
+ }
+ spa_node_emit_info(&d->hooks, &d->info);
+ }
+ d->info.change_mask = old;
+}
+
+static void emit_port_info(struct filter *d, struct port *p, bool full)
+{
+ uint32_t i;
+ uint64_t old = full ? p->info.change_mask : 0;
+ if (full)
+ p->info.change_mask = p->change_mask_all;
+ if (p->info.change_mask != 0) {
+ if (p->info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < p->info.n_params; i++) {
+ if (p->params[i].user > 0) {
+ p->params[i].flags ^= SPA_PARAM_INFO_SERIAL;
+ p->params[i].user = 0;
+ }
+ }
+ }
+ spa_node_emit_port_info(&d->hooks, p->direction, p->id, &p->info);
+ }
+ p->info.change_mask = old;
+}
+
+static int impl_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct spa_node_events *events,
+ void *data)
+{
+ struct filter *d = object;
+ struct spa_hook_list save;
+ struct port *p;
+
+ spa_hook_list_isolate(&d->hooks, &save, listener, events, data);
+
+ emit_node_info(d, true);
+
+ spa_list_for_each(p, &d->port_list, link)
+ emit_port_info(d, p, true);
+
+ spa_hook_list_join(&d->hooks, &save);
+
+ return 0;
+}
+
+static int impl_set_callbacks(void *object,
+ const struct spa_node_callbacks *callbacks, void *data)
+{
+ struct filter *d = object;
+
+ d->callbacks = SPA_CALLBACKS_INIT(callbacks, data);
+
+ return 0;
+}
+
+static int impl_port_set_io(void *object, enum spa_direction direction, uint32_t port_id,
+ uint32_t id, void *data, size_t size)
+{
+ struct filter *impl = object;
+ struct port *port;
+
+ pw_log_debug("%p: id:%d (%s) %p %zd", impl, id,
+ spa_debug_type_find_name(spa_type_io, id), data, size);
+
+ if ((port = get_port(impl, direction, port_id)) == NULL)
+ return -EINVAL;
+
+ switch (id) {
+ case SPA_IO_Buffers:
+ if (data && size >= sizeof(struct spa_io_buffers))
+ port->io = data;
+ else
+ port->io = NULL;
+ break;
+ }
+
+ pw_filter_emit_io_changed(&impl->this, port->user_data, id, data, size);
+
+ return 0;
+}
+
+static int impl_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 filter *d = object;
+ struct port *port;
+
+ if ((port = get_port(d, direction, port_id)) == NULL)
+ return -EINVAL;
+
+ return enum_params(d, &port->param_list, seq, id, start, num, filter);
+}
+
+static int update_params(struct filter *impl, struct port *port, uint32_t id,
+ const struct spa_pod **params, uint32_t n_params)
+{
+ uint32_t i;
+ int res = 0;
+ bool update_latency = false;
+
+ if (id != SPA_ID_INVALID) {
+ clear_params(impl, port, id);
+ } else {
+ for (i = 0; i < n_params; i++) {
+ if (params[i] == NULL || !spa_pod_is_object(params[i]))
+ continue;
+ clear_params(impl, port, SPA_POD_OBJECT_ID(params[i]));
+ }
+ }
+ for (i = 0; i < n_params; i++) {
+ if (params[i] == NULL)
+ continue;
+
+ if (port != NULL &&
+ spa_pod_is_object(params[i]) &&
+ SPA_POD_OBJECT_ID(params[i]) == SPA_PARAM_Latency) {
+ struct spa_latency_info info;
+ if (spa_latency_parse(params[i], &info) >= 0) {
+ port->latency[info.direction] = info;
+ pw_log_debug("port %p: set %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, port,
+ 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);
+ update_latency = true;
+ }
+ continue;
+ }
+ if (add_param(impl, port, id, 0, params[i]) == NULL) {
+ res = -errno;
+ break;
+ }
+ }
+ if (port != NULL && update_latency) {
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ add_param(impl, port, SPA_PARAM_Latency, 0,
+ spa_latency_build(&b, SPA_PARAM_Latency, &port->latency[0]));
+ add_param(impl, port, SPA_PARAM_Latency, 0,
+ spa_latency_build(&b, SPA_PARAM_Latency, &port->latency[1]));
+ }
+ return res;
+}
+
+static int map_data(struct filter *impl, struct spa_data *data, int prot)
+{
+ void *ptr;
+ struct pw_map_range range;
+
+ pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize);
+
+ ptr = mmap(NULL, range.size, prot, MAP_SHARED, data->fd, range.offset);
+ if (ptr == MAP_FAILED) {
+ pw_log_error("%p: failed to mmap buffer mem: %m", impl);
+ return -errno;
+ }
+ data->data = SPA_PTROFF(ptr, range.start, void);
+ pw_log_debug("%p: fd %"PRIi64" mapped %d %d %p", impl, data->fd,
+ range.offset, range.size, data->data);
+
+ if (impl->allow_mlock && mlock(data->data, data->maxsize) < 0) {
+ if (errno != ENOMEM || !mlock_warned) {
+ pw_log(impl->warn_mlock ? SPA_LOG_LEVEL_WARN : SPA_LOG_LEVEL_DEBUG,
+ "%p: Failed to mlock memory %p %u: %s", impl,
+ data->data, data->maxsize,
+ errno == ENOMEM ?
+ "This is not a problem but for best performance, "
+ "consider increasing RLIMIT_MEMLOCK" : strerror(errno));
+ mlock_warned |= errno == ENOMEM;
+ }
+ }
+ return 0;
+}
+
+static int unmap_data(struct filter *impl, struct spa_data *data)
+{
+ struct pw_map_range range;
+
+ pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize);
+
+ if (munmap(SPA_PTROFF(data->data, -range.start, void), range.size) < 0)
+ pw_log_warn("%p: failed to unmap: %m", impl);
+
+ pw_log_debug("%p: fd %"PRIi64" unmapped", impl, data->fd);
+ return 0;
+}
+
+static void clear_buffers(struct port *port)
+{
+ uint32_t i, j;
+ struct filter *impl = port->filter;
+
+ pw_log_debug("%p: clear buffers %d", impl, port->n_buffers);
+
+ for (i = 0; i < port->n_buffers; i++) {
+ struct buffer *b = &port->buffers[i];
+
+ if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_ADDED))
+ pw_filter_emit_remove_buffer(&impl->this, port->user_data, &b->this);
+
+ if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) {
+ for (j = 0; j < b->this.buffer->n_datas; j++) {
+ struct spa_data *d = &b->this.buffer->datas[j];
+ pw_log_debug("%p: clear buffer %d mem",
+ impl, b->id);
+ unmap_data(impl, d);
+ }
+ }
+ }
+ port->n_buffers = 0;
+ clear_queue(port, &port->dequeued);
+ clear_queue(port, &port->queued);
+}
+
+
+static int default_latency(struct filter *impl, struct port *port, enum spa_direction direction)
+{
+ struct pw_filter *filter = &impl->this;
+ struct spa_latency_info info;
+ struct port *p;
+
+ spa_latency_info_combine_start(&info, direction);
+ spa_list_for_each(p, &impl->port_list, link) {
+ if (p->direction == direction)
+ continue;
+ spa_latency_info_combine(&info, &p->latency[direction]);
+ }
+ spa_latency_info_combine_finish(&info);
+
+ spa_process_latency_info_add(&impl->process_latency, &info);
+
+ spa_list_for_each(p, &impl->port_list, link) {
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+ const struct spa_pod *params[1];
+
+ if (p->direction != direction)
+ continue;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ params[0] = spa_latency_build(&b, SPA_PARAM_Latency, &info);
+ pw_filter_update_params(filter, p->user_data, params, 1);
+ }
+ return 0;
+}
+
+static int handle_latency(struct filter *impl, struct port *port, const struct spa_pod *param)
+{
+ struct pw_filter *filter = &impl->this;
+ struct spa_latency_info info;
+ int res;
+
+ if (param == NULL)
+ return 0;
+
+ if ((res = spa_latency_parse(param, &info)) < 0)
+ return res;
+
+ pw_log_info("port %p: set %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, port,
+ 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 (info.direction == port->direction)
+ return 0;
+
+ if (SPA_FLAG_IS_SET(impl->flags, PW_FILTER_FLAG_CUSTOM_LATENCY)) {
+ pw_filter_emit_param_changed(filter, port->user_data,
+ SPA_PARAM_Latency, param);
+ } else {
+ default_latency(impl, port, info.direction);
+ }
+ return 0;
+}
+
+static int impl_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 filter *impl = object;
+ struct pw_filter *filter = &impl->this;
+ struct port *port;
+ int res;
+ bool emit = true;
+ const struct spa_pod *params[1];
+ uint32_t n_params = 0;
+
+ pw_log_debug("%p: port:%d.%d id:%d (%s) param:%p disconnecting:%d", impl,
+ direction, port_id, id,
+ spa_debug_type_find_name(spa_type_param, id), param,
+ impl->disconnecting);
+
+ if (impl->disconnecting && param != NULL)
+ return -EIO;
+
+ if ((port = get_port(impl, direction, port_id)) == NULL)
+ return -EINVAL;
+
+ if (param)
+ pw_log_pod(SPA_LOG_LEVEL_DEBUG, param);
+
+ params[0] = param;
+ n_params = param ? 1 : 0;
+
+ if ((res = update_params(impl, port, id, params, n_params)) < 0)
+ return res;
+
+ switch (id) {
+ case SPA_PARAM_Format:
+ clear_buffers(port);
+ break;
+ case SPA_PARAM_Latency:
+ handle_latency(impl, port, param);
+ emit = false;
+ break;
+ }
+
+ if (emit)
+ pw_filter_emit_param_changed(filter, port->user_data, id, param);
+
+ if (filter->state == PW_FILTER_STATE_ERROR)
+ return -EIO;
+
+ emit_port_info(impl, port, false);
+
+ return res;
+}
+
+static int impl_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 filter *impl = object;
+ struct port *port;
+ struct pw_filter *filter = &impl->this;
+ uint32_t i, j, impl_flags;
+ int prot, res;
+ int size = 0;
+
+ pw_log_debug("%p: port:%d.%d buffers:%u disconnecting:%d", impl,
+ direction, port_id, n_buffers, impl->disconnecting);
+
+ if ((port = get_port(impl, direction, port_id)) == NULL)
+ return -EINVAL;
+
+ if (impl->disconnecting && n_buffers > 0)
+ return -EIO;
+
+ clear_buffers(port);
+
+ impl_flags = port->flags;
+ prot = PROT_READ | (direction == SPA_DIRECTION_OUTPUT ? PROT_WRITE : 0);
+
+ if (n_buffers > MAX_BUFFERS)
+ return -ENOSPC;
+
+ for (i = 0; i < n_buffers; i++) {
+ int buf_size = 0;
+ struct buffer *b = &port->buffers[i];
+
+ b->flags = 0;
+ b->id = i;
+
+ if (SPA_FLAG_IS_SET(impl_flags, PW_FILTER_PORT_FLAG_MAP_BUFFERS)) {
+ for (j = 0; j < buffers[i]->n_datas; j++) {
+ struct spa_data *d = &buffers[i]->datas[j];
+ if ((mappable_dataTypes & (1<<d->type)) > 0) {
+ if ((res = map_data(impl, d, prot)) < 0)
+ return res;
+ SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED);
+ }
+ else if (d->type == SPA_DATA_MemPtr && d->data == NULL) {
+ pw_log_error("%p: invalid buffer mem", filter);
+ return -EINVAL;
+ }
+ buf_size += d->maxsize;
+ }
+
+ if (size > 0 && buf_size != size) {
+ pw_log_error("%p: invalid buffer size %d", filter, buf_size);
+ return -EINVAL;
+ } else
+ size = buf_size;
+ }
+ pw_log_debug("%p: got buffer %d %d datas, mapped size %d", filter, i,
+ buffers[i]->n_datas, size);
+ }
+
+ for (i = 0; i < n_buffers; i++) {
+ struct buffer *b = &port->buffers[i];
+
+ b->this.buffer = buffers[i];
+
+ if (port->direction == SPA_DIRECTION_OUTPUT) {
+ pw_log_trace("%p: recycle buffer %d", filter, b->id);
+ push_queue(port, &port->dequeued, b);
+ }
+
+ SPA_FLAG_SET(b->flags, BUFFER_FLAG_ADDED);
+ pw_filter_emit_add_buffer(filter, port->user_data, &b->this);
+ }
+
+ port->n_buffers = n_buffers;
+
+ return 0;
+}
+
+static int impl_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id)
+{
+ struct filter *impl = object;
+ struct port *port;
+
+ if ((port = get_port(impl, SPA_DIRECTION_OUTPUT, port_id)) == NULL)
+ return -EINVAL;
+
+ pw_log_trace("%p: recycle buffer %d", impl, buffer_id);
+ if (buffer_id < port->n_buffers)
+ push_queue(port, &port->queued, &port->buffers[buffer_id]);
+
+ return 0;
+}
+
+static inline void copy_position(struct filter *impl)
+{
+ struct spa_io_position *p = impl->rt.position;
+ if (SPA_UNLIKELY(p != NULL)) {
+ SEQ_WRITE(impl->seq);
+ impl->time.now = p->clock.nsec;
+ impl->time.rate = p->clock.rate;
+ if (SPA_UNLIKELY(impl->clock_id != p->clock.id)) {
+ impl->base_pos = p->clock.position - impl->time.ticks;
+ impl->clock_id = p->clock.id;
+ }
+ impl->time.ticks = p->clock.position - impl->base_pos;
+ impl->time.delay = 0;
+ SEQ_WRITE(impl->seq);
+ }
+}
+
+static int
+do_call_process(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct filter *impl = user_data;
+ struct pw_filter *filter = &impl->this;
+ pw_log_trace("%p: do process", filter);
+ pw_filter_emit_process(filter, impl->position);
+ return 0;
+}
+
+static void call_process(struct filter *impl)
+{
+ pw_log_trace("%p: call process", impl);
+ if (SPA_FLAG_IS_SET(impl->flags, PW_FILTER_FLAG_RT_PROCESS)) {
+ spa_callbacks_call(&impl->rt_callbacks, struct pw_filter_events,
+ process, 0, impl->rt.position);
+ }
+ else {
+ pw_loop_invoke(impl->context->main_loop,
+ do_call_process, 1, NULL, 0, false, impl);
+ }
+}
+
+static int
+do_call_drained(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct filter *impl = user_data;
+ struct pw_filter *filter = &impl->this;
+ pw_log_trace("%p: drained", filter);
+ pw_filter_emit_drained(filter);
+ impl->draining = false;
+ return 0;
+}
+
+static void call_drained(struct filter *impl)
+{
+ pw_loop_invoke(impl->context->main_loop,
+ do_call_drained, 1, NULL, 0, false, impl);
+}
+
+static int impl_node_process(void *object)
+{
+ struct filter *impl = object;
+ struct port *p;
+ struct buffer *b;
+ bool drained = true;
+
+ pw_log_trace("%p: do process %p", impl, impl->rt.position);
+
+ /** first dequeue and recycle buffers */
+ spa_list_for_each(p, &impl->port_list, link) {
+ struct spa_io_buffers *io = p->io;
+
+ if (io == NULL ||
+ io->buffer_id >= p->n_buffers)
+ continue;
+
+ if (p->direction == SPA_DIRECTION_INPUT) {
+ if (io->status != SPA_STATUS_HAVE_DATA)
+ continue;
+
+ /* push new buffer */
+ b = &p->buffers[io->buffer_id];
+ pw_log_trace("%p: dequeue buffer %d", impl, b->id);
+ push_queue(p, &p->dequeued, b);
+ drained = false;
+ } else {
+ if (io->status == SPA_STATUS_HAVE_DATA)
+ continue;
+
+ /* recycle old buffer */
+ b = &p->buffers[io->buffer_id];
+ pw_log_trace("%p: recycle buffer %d", impl, b->id);
+ push_queue(p, &p->dequeued, b);
+ }
+ }
+
+ copy_position(impl);
+ call_process(impl);
+
+ /** recycle/push queued buffers */
+ spa_list_for_each(p, &impl->port_list, link) {
+ struct spa_io_buffers *io = p->io;
+
+ if (io == NULL)
+ continue;
+
+ if (p->direction == SPA_DIRECTION_INPUT) {
+ if (io->status != SPA_STATUS_HAVE_DATA)
+ continue;
+
+ /* pop buffer to recycle */
+ if ((b = pop_queue(p, &p->queued)) != NULL) {
+ pw_log_trace("%p: recycle buffer %d", impl, b->id);
+ io->buffer_id = b->id;
+ } else {
+ io->buffer_id = SPA_ID_INVALID;
+ }
+ io->status = SPA_STATUS_NEED_DATA;
+ } else {
+ if (io->status == SPA_STATUS_HAVE_DATA)
+ continue;
+
+ if ((b = pop_queue(p, &p->queued)) != NULL) {
+ pw_log_trace("%p: pop %d %p", impl, b->id, io);
+ io->buffer_id = b->id;
+ io->status = SPA_STATUS_HAVE_DATA;
+ drained = false;
+ } else {
+ io->buffer_id = SPA_ID_INVALID;
+ io->status = SPA_STATUS_NEED_DATA;
+ }
+ }
+ }
+ if (drained && impl->draining)
+ call_drained(impl);
+
+ return SPA_STATUS_NEED_DATA | SPA_STATUS_HAVE_DATA;
+}
+
+static const struct spa_node_methods impl_node = {
+ SPA_VERSION_NODE_METHODS,
+ .add_listener = impl_add_listener,
+ .set_callbacks = impl_set_callbacks,
+ .enum_params = impl_enum_params,
+ .set_param = impl_set_param,
+ .set_io = impl_set_io,
+ .send_command = impl_send_command,
+ .port_set_io = impl_port_set_io,
+ .port_enum_params = impl_port_enum_params,
+ .port_set_param = impl_port_set_param,
+ .port_use_buffers = impl_port_use_buffers,
+ .port_reuse_buffer = impl_port_reuse_buffer,
+ .process = impl_node_process,
+};
+
+static void proxy_removed(void *_data)
+{
+ struct pw_filter *filter = _data;
+ pw_log_debug("%p: removed", filter);
+ spa_hook_remove(&filter->proxy_listener);
+ filter->node_id = SPA_ID_INVALID;
+ filter_set_state(filter, PW_FILTER_STATE_UNCONNECTED, NULL);
+}
+
+static void proxy_destroy(void *_data)
+{
+ struct pw_filter *filter = _data;
+ pw_log_debug("%p: destroy", filter);
+ proxy_removed(_data);
+}
+
+static void proxy_error(void *_data, int seq, int res, const char *message)
+{
+ struct pw_filter *filter = _data;
+ /* 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() */
+ pw_filter_emit_state_changed(filter, filter->state,
+ PW_FILTER_STATE_ERROR, message);
+}
+
+static void proxy_bound(void *_data, uint32_t global_id)
+{
+ struct pw_filter *filter = _data;
+ filter->node_id = global_id;
+ filter_set_state(filter, PW_FILTER_STATE_PAUSED, NULL);
+}
+
+static const struct pw_proxy_events proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .removed = proxy_removed,
+ .destroy = proxy_destroy,
+ .error = proxy_error,
+ .bound = proxy_bound,
+};
+
+static void on_core_error(void *_data, uint32_t id, int seq, int res, const char *message)
+{
+ struct pw_filter *filter = _data;
+
+ pw_log_debug("%p: error id:%u seq:%d res:%d (%s): %s", filter,
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE) {
+ filter_set_state(filter, PW_FILTER_STATE_UNCONNECTED, message);
+ }
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+};
+
+struct match {
+ struct pw_filter *filter;
+ int count;
+};
+#define MATCH_INIT(f) ((struct match){ .filter = (f) })
+
+static int execute_match(void *data, const char *location, const char *action,
+ const char *val, size_t len)
+{
+ struct match *match = data;
+ struct pw_filter *this = match->filter;
+ if (spa_streq(action, "update-props"))
+ match->count += pw_properties_update_string(this->properties, val, len);
+ return 1;
+}
+
+static struct filter *
+filter_new(struct pw_context *context, const char *name,
+ struct pw_properties *props, const struct pw_properties *extra)
+{
+ struct filter *impl;
+ struct pw_filter *this;
+ const char *str;
+ struct match match;
+ int res;
+
+ impl = calloc(1, sizeof(struct filter));
+ if (impl == NULL) {
+ res = -errno;
+ goto error_cleanup;
+ }
+
+ this = &impl->this;
+ pw_log_debug("%p: new", impl);
+
+ if (props == NULL) {
+ props = pw_properties_new(PW_KEY_MEDIA_NAME, name, NULL);
+ } else if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) {
+ pw_properties_set(props, PW_KEY_MEDIA_NAME, name);
+ }
+ if (props == NULL) {
+ res = -errno;
+ goto error_properties;
+ }
+ spa_hook_list_init(&impl->hooks);
+ this->properties = props;
+
+ pw_context_conf_update_props(context, "filter.properties", props);
+
+ match = MATCH_INIT(this);
+ pw_context_conf_section_match_rules(context, "filter.rules",
+ &this->properties->dict, execute_match, &match);
+
+ if ((str = getenv("PIPEWIRE_PROPS")) != NULL)
+ pw_properties_update_string(props, str, strlen(str));
+ if ((str = getenv("PIPEWIRE_QUANTUM")) != NULL) {
+ struct spa_fraction q;
+ if (sscanf(str, "%u/%u", &q.num, &q.denom) == 2 && q.denom != 0) {
+ pw_properties_setf(props, PW_KEY_NODE_RATE,
+ "1/%u", q.denom);
+ pw_properties_setf(props, PW_KEY_NODE_LATENCY,
+ "%u/%u", q.num, q.denom);
+ }
+ }
+ if ((str = getenv("PIPEWIRE_LATENCY")) != NULL)
+ pw_properties_set(props, PW_KEY_NODE_LATENCY, str);
+ if ((str = getenv("PIPEWIRE_RATE")) != NULL)
+ pw_properties_set(props, PW_KEY_NODE_RATE, str);
+
+ if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL && extra) {
+ str = pw_properties_get(extra, PW_KEY_APP_NAME);
+ if (str == NULL)
+ str = pw_properties_get(extra, PW_KEY_APP_PROCESS_BINARY);
+ if (str == NULL)
+ str = name;
+ pw_properties_set(props, PW_KEY_NODE_NAME, str);
+ }
+
+ this->name = name ? strdup(name) : NULL;
+ this->node_id = SPA_ID_INVALID;
+
+ spa_list_init(&impl->param_list);
+ spa_list_init(&impl->port_list);
+ pw_map_init(&impl->ports[SPA_DIRECTION_INPUT], 32, 32);
+ pw_map_init(&impl->ports[SPA_DIRECTION_OUTPUT], 32, 32);
+
+ spa_hook_list_init(&this->listener_list);
+ spa_list_init(&this->controls);
+
+ this->state = PW_FILTER_STATE_UNCONNECTED;
+
+ impl->context = context;
+ impl->allow_mlock = context->settings.mem_allow_mlock;
+ impl->warn_mlock = context->settings.mem_warn_mlock;
+
+ return impl;
+
+error_properties:
+ free(impl);
+error_cleanup:
+ pw_properties_free(props);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+struct pw_filter * pw_filter_new(struct pw_core *core, const char *name,
+ struct pw_properties *props)
+{
+ struct filter *impl;
+ struct pw_filter *this;
+ struct pw_context *context = core->context;
+
+ impl = filter_new(context, name, props, core->properties);
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+ this->core = core;
+ spa_list_append(&this->core->filter_list, &this->link);
+ pw_core_add_listener(core,
+ &this->core_listener, &core_events, this);
+
+ return this;
+}
+
+SPA_EXPORT
+struct pw_filter *
+pw_filter_new_simple(struct pw_loop *loop,
+ const char *name,
+ struct pw_properties *props,
+ const struct pw_filter_events *events,
+ void *data)
+{
+ struct pw_filter *this;
+ struct filter *impl;
+ struct pw_context *context;
+ int res;
+
+ if (props == NULL)
+ props = pw_properties_new(NULL, NULL);
+ if (props == NULL)
+ return NULL;
+
+ context = pw_context_new(loop, NULL, 0);
+ if (context == NULL) {
+ res = -errno;
+ goto error_cleanup;
+ }
+
+ impl = filter_new(context, name, props, props);
+ if (impl == NULL) {
+ props = NULL;
+ res = -errno;
+ goto error_cleanup;
+ }
+
+ this = &impl->this;
+
+ impl->data.context = context;
+ pw_filter_add_listener(this, &impl->data.filter_listener, events, data);
+
+ return this;
+
+error_cleanup:
+ if (context)
+ pw_context_destroy(context);
+ pw_properties_free(props);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+const char *pw_filter_state_as_string(enum pw_filter_state state)
+{
+ switch (state) {
+ case PW_FILTER_STATE_ERROR:
+ return "error";
+ case PW_FILTER_STATE_UNCONNECTED:
+ return "unconnected";
+ case PW_FILTER_STATE_CONNECTING:
+ return "connecting";
+ case PW_FILTER_STATE_PAUSED:
+ return "paused";
+ case PW_FILTER_STATE_STREAMING:
+ return "streaming";
+ }
+ return "invalid-state";
+}
+
+SPA_EXPORT
+void pw_filter_destroy(struct pw_filter *filter)
+{
+ struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this);
+ struct port *p;
+
+ pw_log_debug("%p: destroy", filter);
+
+ pw_filter_emit_destroy(filter);
+
+ if (!impl->disconnecting)
+ pw_filter_disconnect(filter);
+
+ spa_list_consume(p, &impl->port_list, link)
+ pw_filter_remove_port(p->user_data);
+
+ if (filter->core) {
+ spa_hook_remove(&filter->core_listener);
+ spa_list_remove(&filter->link);
+ }
+
+ clear_params(impl, NULL, SPA_ID_INVALID);
+
+ pw_log_debug("%p: free", filter);
+ free(filter->error);
+
+ pw_properties_free(filter->properties);
+
+ spa_hook_list_clean(&impl->hooks);
+ spa_hook_list_clean(&filter->listener_list);
+
+ pw_map_clear(&impl->ports[SPA_DIRECTION_INPUT]);
+ pw_map_clear(&impl->ports[SPA_DIRECTION_OUTPUT]);
+
+ free(filter->name);
+
+ if (impl->data.context)
+ pw_context_destroy(impl->data.context);
+
+ free(impl);
+}
+
+static void hook_removed(struct spa_hook *hook)
+{
+ struct filter *impl = hook->priv;
+ spa_zero(impl->rt_callbacks);
+ hook->priv = NULL;
+ hook->removed = NULL;
+}
+
+SPA_EXPORT
+void pw_filter_add_listener(struct pw_filter *filter,
+ struct spa_hook *listener,
+ const struct pw_filter_events *events,
+ void *data)
+{
+ struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this);
+ spa_hook_list_append(&filter->listener_list, listener, events, data);
+ if (events->process && impl->rt_callbacks.funcs == NULL) {
+ impl->rt_callbacks = SPA_CALLBACKS_INIT(events, data);
+ listener->removed = hook_removed;
+ listener->priv = impl;
+ }
+}
+
+SPA_EXPORT
+enum pw_filter_state pw_filter_get_state(struct pw_filter *filter, const char **error)
+{
+ if (error)
+ *error = filter->error;
+ return filter->state;
+}
+
+SPA_EXPORT
+struct pw_core *pw_filter_get_core(struct pw_filter *filter)
+{
+ return filter->core;
+}
+
+SPA_EXPORT
+const char *pw_filter_get_name(struct pw_filter *filter)
+{
+ return filter->name;
+}
+
+SPA_EXPORT
+const struct pw_properties *pw_filter_get_properties(struct pw_filter *filter, void *port_data)
+{
+ struct port *port = SPA_CONTAINER_OF(port_data, struct port, user_data);
+
+ if (port_data) {
+ return port->props;
+ } else {
+ return filter->properties;
+ }
+ return NULL;
+}
+
+SPA_EXPORT
+int pw_filter_update_properties(struct pw_filter *filter, void *port_data, const struct spa_dict *dict)
+{
+ struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this);
+ struct port *port = SPA_CONTAINER_OF(port_data, struct port, user_data);
+ int changed = 0;
+
+ if (port_data) {
+ changed = pw_properties_update(port->props, dict);
+ port->info.props = &port->props->dict;
+ if (changed > 0) {
+ port->info.change_mask |= SPA_PORT_CHANGE_MASK_PROPS;
+ emit_port_info(impl, port, false);
+ }
+ } else {
+ struct match match;
+
+ changed = pw_properties_update(filter->properties, dict);
+
+ match = MATCH_INIT(filter);
+ pw_context_conf_section_match_rules(impl->context, "filter.rules",
+ &filter->properties->dict, execute_match, &match);
+
+ impl->info.props = &filter->properties->dict;
+ if (changed > 0 || match.count > 0) {
+ impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PROPS;
+ emit_node_info(impl, false);
+ }
+ }
+ return changed;
+}
+
+SPA_EXPORT
+int
+pw_filter_connect(struct pw_filter *filter,
+ enum pw_filter_flags flags,
+ const struct spa_pod **params,
+ uint32_t n_params)
+{
+ struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this);
+ int res;
+ uint32_t i;
+ struct spa_dict_item items[1];
+
+ pw_log_debug("%p: connect", filter);
+ impl->flags = flags;
+
+ impl->process_rt = SPA_FLAG_IS_SET(flags, PW_FILTER_FLAG_RT_PROCESS);
+
+ impl->warn_mlock = pw_properties_get_bool(filter->properties, "mem.warn-mlock", impl->warn_mlock);
+
+ impl->impl_node.iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_Node,
+ SPA_VERSION_NODE,
+ &impl_node, impl);
+
+ impl->change_mask_all =
+ SPA_NODE_CHANGE_MASK_FLAGS |
+ SPA_NODE_CHANGE_MASK_PROPS |
+ SPA_NODE_CHANGE_MASK_PARAMS;
+
+ impl->info = SPA_NODE_INFO_INIT();
+ impl->info.max_input_ports = UINT32_MAX;
+ impl->info.max_output_ports = UINT32_MAX;
+ impl->info.flags = impl->process_rt ? SPA_NODE_FLAG_RT : 0;
+ impl->info.props = &filter->properties->dict;
+ impl->params[IDX_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, 0);
+ impl->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_WRITE);
+ impl->params[IDX_ProcessLatency] = SPA_PARAM_INFO(SPA_PARAM_ProcessLatency, 0);
+ impl->info.params = impl->params;
+ impl->info.n_params = N_NODE_PARAMS;
+ impl->info.change_mask = impl->change_mask_all;
+
+ clear_params(impl, NULL, SPA_ID_INVALID);
+ for (i = 0; i < n_params; i++) {
+ add_param(impl, NULL, SPA_ID_INVALID, 0, params[i]);
+ }
+
+ impl->disconnecting = false;
+ filter_set_state(filter, PW_FILTER_STATE_CONNECTING, NULL);
+
+ if (flags & PW_FILTER_FLAG_DRIVER)
+ pw_properties_set(filter->properties, PW_KEY_NODE_DRIVER, "true");
+ if ((pw_properties_get(filter->properties, PW_KEY_NODE_WANT_DRIVER) == NULL))
+ pw_properties_set(filter->properties, PW_KEY_NODE_WANT_DRIVER, "true");
+
+ if (filter->core == NULL) {
+ filter->core = pw_context_connect(impl->context,
+ pw_properties_copy(filter->properties), 0);
+ if (filter->core == NULL) {
+ res = -errno;
+ goto error_connect;
+ }
+ spa_list_append(&filter->core->filter_list, &filter->link);
+ pw_core_add_listener(filter->core,
+ &filter->core_listener, &core_events, filter);
+ impl->disconnect_core = true;
+ }
+
+ pw_log_debug("%p: export node %p", filter, &impl->impl_node);
+
+ items[0] = SPA_DICT_ITEM_INIT(PW_KEY_OBJECT_REGISTER, "false");
+ filter->proxy = pw_core_export(filter->core,
+ SPA_TYPE_INTERFACE_Node, &SPA_DICT_INIT_ARRAY(items),
+ &impl->impl_node, 0);
+ if (filter->proxy == NULL) {
+ res = -errno;
+ goto error_proxy;
+ }
+
+ pw_proxy_add_listener(filter->proxy, &filter->proxy_listener, &proxy_events, filter);
+
+ return 0;
+
+error_connect:
+ pw_log_error("%p: can't connect: %s", filter, spa_strerror(res));
+ return res;
+error_proxy:
+ pw_log_error("%p: can't make proxy: %s", filter, spa_strerror(res));
+ return res;
+}
+
+SPA_EXPORT
+uint32_t pw_filter_get_node_id(struct pw_filter *filter)
+{
+ return filter->node_id;
+}
+
+SPA_EXPORT
+int pw_filter_disconnect(struct pw_filter *filter)
+{
+ struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this);
+
+ pw_log_debug("%p: disconnect", filter);
+ impl->disconnecting = true;
+
+ if (filter->proxy) {
+ pw_proxy_destroy(filter->proxy);
+ filter->proxy = NULL;
+ }
+ if (impl->disconnect_core) {
+ impl->disconnect_core = false;
+ spa_hook_remove(&filter->core_listener);
+ spa_list_remove(&filter->link);
+ pw_core_disconnect(filter->core);
+ filter->core = NULL;
+ }
+ return 0;
+}
+
+static void add_port_params(struct filter *impl, struct port *port)
+{
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ add_param(impl, port, SPA_PARAM_IO, PARAM_FLAG_LOCKED,
+ spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamIO, SPA_PARAM_IO,
+ SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers),
+ SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))));
+}
+
+static void add_audio_dsp_port_params(struct filter *impl, struct port *port)
+{
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ 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_audio),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp),
+ SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_DSP_F32)));
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ add_param(impl, port, SPA_PARAM_Buffers, PARAM_FLAG_LOCKED,
+ 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),
+ SPA_PARAM_BUFFERS_blocks, SPA_POD_Int(1),
+ SPA_PARAM_BUFFERS_size, SPA_POD_CHOICE_STEP_Int(
+ MAX_SAMPLES * sizeof(float),
+ sizeof(float),
+ MAX_SAMPLES * sizeof(float),
+ sizeof(float)),
+ SPA_PARAM_BUFFERS_stride, SPA_POD_Int(4)));
+}
+
+static void add_video_dsp_port_params(struct filter *impl, struct port *port)
+{
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ 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_video),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_dsp),
+ 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)
+{
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ 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_EXPORT
+void *pw_filter_add_port(struct pw_filter *filter,
+ enum pw_direction direction,
+ enum pw_filter_port_flags flags,
+ size_t port_data_size,
+ struct pw_properties *props,
+ const struct spa_pod **params, uint32_t n_params)
+{
+ struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this);
+ struct port *p;
+ const char *str;
+
+ if (props == NULL)
+ props = pw_properties_new(NULL, NULL);
+ if (props == NULL)
+ return NULL;
+
+ if ((p = alloc_port(impl, direction, port_data_size)) == NULL)
+ goto error_cleanup;
+
+ p->props = props;
+ p->flags = flags;
+
+ p->change_mask_all = SPA_PORT_CHANGE_MASK_FLAGS |
+ SPA_PORT_CHANGE_MASK_PROPS;
+ p->info = SPA_PORT_INFO_INIT();
+ p->info.flags = 0;
+ if (SPA_FLAG_IS_SET(flags, PW_FILTER_PORT_FLAG_ALLOC_BUFFERS))
+ p->info.flags |= SPA_PORT_FLAG_CAN_ALLOC_BUFFERS;
+ p->info.props = &p->props->dict;
+ p->change_mask_all |= SPA_PORT_CHANGE_MASK_PARAMS;
+ p->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, 0);
+ p->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, 0);
+ p->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, 0);
+ p->params[IDX_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
+ p->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
+ p->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_WRITE);
+ p->info.params = p->params;
+ p->info.n_params = N_PORT_PARAMS;
+
+ /* first configure default params */
+ add_port_params(impl, p);
+ if ((str = pw_properties_get(props, PW_KEY_FORMAT_DSP)) != NULL) {
+ if (spa_streq(str, "32 bit float mono audio"))
+ 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);
+ }
+ /* then override with user provided if any */
+ if (update_params(impl, p, SPA_ID_INVALID, params, n_params) < 0)
+ goto error_free;
+
+ emit_port_info(impl, p, true);
+
+ return p->user_data;
+
+
+error_free:
+ clear_params(impl, p, SPA_ID_INVALID);
+ free(p);
+error_cleanup:
+ pw_properties_free(props);
+ return NULL;
+}
+
+SPA_EXPORT
+int pw_filter_remove_port(void *port_data)
+{
+ struct port *port = SPA_CONTAINER_OF(port_data, struct port, user_data);
+ struct filter *impl = port->filter;
+
+ spa_node_emit_port_info(&impl->hooks, port->direction, port->id, NULL);
+
+ spa_list_remove(&port->link);
+ pw_map_remove(&impl->ports[port->direction], port->id);
+
+ clear_buffers(port);
+ clear_params(impl, port, SPA_ID_INVALID);
+ pw_properties_free(port->props);
+ free(port);
+
+ return 0;
+}
+
+SPA_EXPORT
+int pw_filter_set_error(struct pw_filter *filter,
+ int res, const char *error, ...)
+{
+ if (res < 0) {
+ va_list args;
+ char *value;
+ int r;
+
+ va_start(args, error);
+ r = vasprintf(&value, error, args);
+ va_end(args);
+ if (r < 0)
+ return -errno;
+
+ if (filter->proxy)
+ pw_proxy_error(filter->proxy, res, value);
+ filter_set_state(filter, PW_FILTER_STATE_ERROR, value);
+
+ free(value);
+ }
+ return res;
+}
+
+SPA_EXPORT
+int pw_filter_update_params(struct pw_filter *filter,
+ void *port_data,
+ const struct spa_pod **params,
+ uint32_t n_params)
+{
+ struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this);
+ struct port *port;
+ int res;
+
+ pw_log_debug("%p: update params", filter);
+
+ port = port_data ? SPA_CONTAINER_OF(port_data, struct port, user_data) : NULL;
+
+ res = update_params(impl, port, SPA_ID_INVALID, params, n_params);
+ if (res < 0)
+ return res;
+
+ if (port)
+ emit_port_info(impl, port, false);
+ else
+ emit_node_info(impl, false);
+
+ return res;
+}
+
+SPA_EXPORT
+int pw_filter_set_active(struct pw_filter *filter, bool active)
+{
+ pw_log_debug("%p: active:%d", filter, active);
+ return 0;
+}
+
+SPA_EXPORT
+int pw_filter_get_time(struct pw_filter *filter, struct pw_time *time)
+{
+ struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this);
+ uintptr_t seq1, seq2;
+
+ do {
+ seq1 = SEQ_READ(impl->seq);
+ *time = impl->time;
+ seq2 = SEQ_READ(impl->seq);
+ } while (!SEQ_READ_SUCCESS(seq1, seq2));
+
+ pw_log_trace("%p: %"PRIi64" %"PRIi64" %"PRIu64" %d/%d ", filter,
+ time->now, time->delay, time->ticks,
+ time->rate.num, time->rate.denom);
+
+ return 0;
+}
+
+static int
+do_process(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct filter *impl = user_data;
+ int res = impl_node_process(impl);
+ return spa_node_call_ready(&impl->callbacks, res);
+}
+
+static inline int call_trigger(struct filter *impl)
+{
+ int res = 0;
+ if (SPA_FLAG_IS_SET(impl->flags, PW_FILTER_FLAG_DRIVER)) {
+ res = pw_loop_invoke(impl->context->data_loop,
+ do_process, 1, NULL, 0, false, impl);
+ }
+ return res;
+}
+
+SPA_EXPORT
+struct pw_buffer *pw_filter_dequeue_buffer(void *port_data)
+{
+ struct port *p = SPA_CONTAINER_OF(port_data, struct port, user_data);
+ struct filter *impl = p->filter;
+ struct buffer *b;
+ int res;
+
+ if ((b = pop_queue(p, &p->dequeued)) == NULL) {
+ res = -errno;
+ pw_log_trace("%p: no more buffers: %m", impl);
+ errno = -res;
+ return NULL;
+ }
+ pw_log_trace("%p: dequeue buffer %d", impl, b->id);
+
+ return &b->this;
+}
+
+SPA_EXPORT
+int pw_filter_queue_buffer(void *port_data, struct pw_buffer *buffer)
+{
+ struct port *p = SPA_CONTAINER_OF(port_data, struct port, user_data);
+ struct filter *impl = p->filter;
+ struct buffer *b = SPA_CONTAINER_OF(buffer, struct buffer, this);
+ int res;
+
+ pw_log_trace("%p: queue buffer %d", impl, b->id);
+ if ((res = push_queue(p, &p->queued, b)) < 0)
+ return res;
+
+ return call_trigger(impl);
+}
+
+SPA_EXPORT
+void *pw_filter_get_dsp_buffer(void *port_data, uint32_t n_samples)
+{
+ struct port *p = SPA_CONTAINER_OF(port_data, struct port, user_data);
+ struct pw_buffer *buf;
+ struct spa_data *d;
+
+ if ((buf = pw_filter_dequeue_buffer(port_data)) == NULL)
+ return NULL;
+
+ d = &buf->buffer->datas[0];
+
+ if (p->direction == SPA_DIRECTION_OUTPUT) {
+ d->chunk->offset = 0;
+ d->chunk->size = n_samples * sizeof(float);
+ d->chunk->stride = sizeof(float);
+ d->chunk->flags = 0;
+ }
+
+ pw_filter_queue_buffer(port_data, buf);
+
+ return d->data;
+}
+
+static int
+do_flush(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+#if 0
+ struct filter *impl = user_data;
+ struct buffer *b;
+
+ pw_log_trace("%p: flush", impl);
+ do {
+ b = pop_queue(impl, &impl->queued);
+ if (b != NULL)
+ push_queue(impl, &impl->dequeued, b);
+ }
+ while (b);
+
+ impl->time.queued = impl->queued.outcount = impl->dequeued.incount =
+ impl->dequeued.outcount = impl->queued.incount;
+
+#endif
+ return 0;
+}
+static int
+do_drain(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct filter *impl = user_data;
+ impl->draining = true;
+ return 0;
+}
+
+SPA_EXPORT
+int pw_filter_flush(struct pw_filter *filter, bool drain)
+{
+ struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this);
+ pw_loop_invoke(impl->context->data_loop,
+ drain ? do_drain : do_flush, 1, NULL, 0, true, impl);
+ return 0;
+}
diff --git a/src/pipewire/filter.h b/src/pipewire/filter.h
new file mode 100644
index 0000000..255608a
--- /dev/null
+++ b/src/pipewire/filter.h
@@ -0,0 +1,250 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_FILTER_H
+#define PIPEWIRE_FILTER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \defgroup pw_filter Filter
+ *
+ * \brief PipeWire filter object class
+ *
+ * The filter object provides a convenient way to implement
+ * processing filters.
+ *
+ * See also \ref api_pw_core
+ */
+
+/**
+ * \addtogroup pw_filter
+ * \{
+ */
+struct pw_filter;
+
+#include <spa/buffer/buffer.h>
+#include <spa/node/io.h>
+#include <spa/param/param.h>
+#include <spa/pod/command.h>
+
+#include <pipewire/core.h>
+#include <pipewire/stream.h>
+
+/** \enum pw_filter_state The state of a filter */
+enum pw_filter_state {
+ PW_FILTER_STATE_ERROR = -1, /**< the stream is in error */
+ PW_FILTER_STATE_UNCONNECTED = 0, /**< unconnected */
+ PW_FILTER_STATE_CONNECTING = 1, /**< connection is in progress */
+ PW_FILTER_STATE_PAUSED = 2, /**< filter is connected and paused */
+ PW_FILTER_STATE_STREAMING = 3 /**< filter is streaming */
+};
+
+#if 0
+struct pw_buffer {
+ struct spa_buffer *buffer; /**< the spa buffer */
+ void *user_data; /**< user data attached to the buffer */
+ uint64_t size; /**< For input ports, this field is set by pw_filter
+ * with the duration of the buffer in ticks.
+ * For output ports, this field is set by the user.
+ * This field is added for all queued buffers and
+ * returned in the time info. */
+};
+#endif
+
+/** Events for a filter. These events are always called from the mainloop
+ * unless explicitly documented otherwise. */
+struct pw_filter_events {
+#define PW_VERSION_FILTER_EVENTS 1
+ uint32_t version;
+
+ void (*destroy) (void *data);
+ /** when the filter state changes */
+ void (*state_changed) (void *data, enum pw_filter_state old,
+ enum pw_filter_state state, const char *error);
+
+ /** when io changed on a port of the filter (when port_data is NULL). */
+ void (*io_changed) (void *data, void *port_data,
+ uint32_t id, void *area, uint32_t size);
+ /** when a parameter changed on a port of the filter (when port_data is NULL). */
+ void (*param_changed) (void *data, void *port_data,
+ uint32_t id, const struct spa_pod *param);
+
+ /** when a new buffer was created for a port */
+ void (*add_buffer) (void *data, void *port_data, struct pw_buffer *buffer);
+ /** when a buffer was destroyed for a port */
+ void (*remove_buffer) (void *data, void *port_data, struct pw_buffer *buffer);
+
+ /** 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. */
+ void (*process) (void *data, struct spa_io_position *position);
+
+ /** The filter is drained */
+ void (*drained) (void *data);
+
+ /** A command notify, Since 0.3.39:1 */
+ void (*command) (void *data, const struct spa_command *command);
+};
+
+/** Convert a filter state to a readable string */
+const char * pw_filter_state_as_string(enum pw_filter_state state);
+
+/** \enum pw_filter_flags Extra flags that can be used in \ref pw_filter_connect() */
+enum pw_filter_flags {
+ PW_FILTER_FLAG_NONE = 0, /**< no flags */
+ PW_FILTER_FLAG_INACTIVE = (1 << 0), /**< start the filter inactive,
+ * pw_filter_set_active() needs to be
+ * called explicitly */
+ PW_FILTER_FLAG_DRIVER = (1 << 1), /**< be a driver */
+ PW_FILTER_FLAG_RT_PROCESS = (1 << 2), /**< call process from the realtime
+ * thread */
+ 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. */
+};
+
+enum pw_filter_port_flags {
+ PW_FILTER_PORT_FLAG_NONE = 0, /**< no flags */
+ PW_FILTER_PORT_FLAG_MAP_BUFFERS = (1 << 0), /**< mmap the buffers except DmaBuf */
+ PW_FILTER_PORT_FLAG_ALLOC_BUFFERS = (1 << 1), /**< the application will allocate buffer
+ * memory. In the add_buffer event, the
+ * data of the buffer should be set */
+};
+
+/** Create a new unconneced \ref pw_filter
+ * \return a newly allocated \ref pw_filter */
+struct pw_filter *
+pw_filter_new(struct pw_core *core, /**< a \ref pw_core */
+ const char *name, /**< a filter media name */
+ struct pw_properties *props /**< filter properties, ownership is taken */);
+
+struct pw_filter *
+pw_filter_new_simple(struct pw_loop *loop, /**< a \ref pw_loop to use */
+ const char *name, /**< a filter media name */
+ struct pw_properties *props, /**< filter properties, ownership is taken */
+ const struct pw_filter_events *events, /**< filter events */
+ void *data /**< data passed to events */);
+
+/** Destroy a filter */
+void pw_filter_destroy(struct pw_filter *filter);
+
+void pw_filter_add_listener(struct pw_filter *filter,
+ struct spa_hook *listener,
+ const struct pw_filter_events *events,
+ void *data);
+
+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);
+
+struct pw_core *pw_filter_get_core(struct pw_filter *filter);
+
+/** Connect a filter for processing.
+ * \return 0 on success < 0 on error.
+ *
+ * You should connect to the process event and use pw_filter_dequeue_buffer()
+ * to get the latest metadata and data. */
+int
+pw_filter_connect(struct pw_filter *filter, /**< a \ref pw_filter */
+ enum pw_filter_flags flags, /**< filter flags */
+ const struct spa_pod **params, /**< an array with params. */
+ uint32_t n_params /**< number of items in \a params */);
+
+/** Get the node ID of the filter.
+ * \return node ID. */
+uint32_t
+pw_filter_get_node_id(struct pw_filter *filter);
+
+/** Disconnect \a filter */
+int pw_filter_disconnect(struct pw_filter *filter);
+
+/** add a port to the filter, returns user data of port_data_size. */
+void *pw_filter_add_port(struct pw_filter *filter, /**< a \ref pw_filter */
+ enum pw_direction direction, /**< port direction */
+ enum pw_filter_port_flags flags, /**< port flags */
+ size_t port_data_size, /**< allocated and given to the user as port_data */
+ struct pw_properties *props, /**< port properties, ownership is taken */
+ const struct spa_pod **params, /**< an array of params. The params should
+ * ideally contain the supported formats */
+ uint32_t n_params /**< number of elements in \a params */);
+
+/** remove a port from the filter */
+int pw_filter_remove_port(void *port_data /**< data associated with port */);
+
+/** get properties, port_data of NULL will give global properties */
+const struct pw_properties *pw_filter_get_properties(struct pw_filter *filter,
+ void *port_data);
+
+/** Update properties, use NULL port_data for global filter properties */
+int pw_filter_update_properties(struct pw_filter *filter,
+ void *port_data, const struct spa_dict *dict);
+
+/** Set the filter in error state */
+int pw_filter_set_error(struct pw_filter *filter, /**< a \ref pw_filter */
+ int res, /**< a result code */
+ const char *error, /**< an error message */
+ ...
+ ) SPA_PRINTF_FUNC(3, 4);
+
+/** Update params, use NULL port_data for global filter params */
+int
+pw_filter_update_params(struct pw_filter *filter, /**< a \ref pw_filter */
+ void *port_data, /**< data associated with port */
+ const struct spa_pod **params, /**< an array of params. */
+ uint32_t n_params /**< number of elements in \a params */);
+
+
+/** Query the time on the filter, deprecated, use the spa_io_position in the
+ * process() method for timing information. */
+SPA_DEPRECATED
+int pw_filter_get_time(struct pw_filter *filter, struct pw_time *time);
+
+/** Get a buffer that can be filled for output ports or consumed
+ * for input ports. */
+struct pw_buffer *pw_filter_dequeue_buffer(void *port_data);
+
+/** Submit a buffer for playback or recycle a buffer for capture. */
+int pw_filter_queue_buffer(void *port_data, struct pw_buffer *buffer);
+
+/** Get a data pointer to the buffer data */
+void *pw_filter_get_dsp_buffer(void *port_data, uint32_t n_samples);
+
+/** Activate or deactivate the filter */
+int pw_filter_set_active(struct pw_filter *filter, bool active);
+
+/** Flush a filter. When \a drain is true, the drained callback will
+ * be called when all data is played or recorded */
+int pw_filter_flush(struct pw_filter *filter, bool drain);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_FILTER_H */
diff --git a/src/pipewire/global.c b/src/pipewire/global.c
new file mode 100644
index 0000000..d069ac8
--- /dev/null
+++ b/src/pipewire/global.c
@@ -0,0 +1,430 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <unistd.h>
+#include <time.h>
+#include <stdio.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/private.h>
+
+#include <spa/debug/types.h>
+#include <spa/utils/string.h>
+
+PW_LOG_TOPIC_EXTERN(log_global);
+#define PW_LOG_TOPIC_DEFAULT log_global
+
+/** \cond */
+struct impl {
+ struct pw_global this;
+};
+/** \endcond */
+
+SPA_EXPORT
+uint32_t pw_global_get_permissions(struct pw_global *global, struct pw_impl_client *client)
+{
+ if (client->permission_func == NULL)
+ return PW_PERM_ALL;
+
+ return client->permission_func(global, client, client->permission_data);
+}
+
+/** Create a new global
+ *
+ * \param context a context object
+ * \param type the type of the global
+ * \param version the version of the type
+ * \param properties extra properties
+ * \param func a function to bind to this global
+ * \param object the associated object
+ * \return a result global
+ *
+ */
+SPA_EXPORT
+struct pw_global *
+pw_global_new(struct pw_context *context,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ pw_global_bind_func_t func,
+ void *object)
+{
+ struct impl *impl;
+ struct pw_global *this;
+ int res;
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL)
+ return NULL;
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL) {
+ res = -errno;
+ goto error_cleanup;
+ }
+
+ this = &impl->this;
+
+ this->context = context;
+ this->type = type;
+ this->version = version;
+ this->func = func;
+ this->object = object;
+ this->properties = properties;
+ this->id = pw_map_insert_new(&context->globals, this);
+ if (this->id == SPA_ID_INVALID) {
+ res = -errno;
+ pw_log_error("%p: can't allocate new id: %m", this);
+ goto error_free;
+ }
+ this->serial = SPA_ID_INVALID;
+
+ spa_list_init(&this->resource_list);
+ spa_hook_list_init(&this->listener_list);
+
+ pw_log_debug("%p: new %s %d", this, this->type, this->id);
+
+ return this;
+
+error_free:
+ free(impl);
+error_cleanup:
+ pw_properties_free(properties);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+uint64_t pw_global_get_serial(struct pw_global *global)
+{
+ struct pw_context *context = global->context;
+ if (global->serial == SPA_ID_INVALID)
+ global->serial = context->serial++;
+ if ((uint32_t)context->serial == SPA_ID_INVALID)
+ context->serial++;
+ return global->serial;
+}
+
+/** register a global to the context registry
+ *
+ * \param global a global to add
+ * \return 0 on success < 0 errno value on failure
+ *
+ */
+SPA_EXPORT
+int pw_global_register(struct pw_global *global)
+{
+ struct pw_resource *registry;
+ struct pw_context *context = global->context;
+ struct pw_impl_client *client;
+
+ if (global->registered)
+ return -EEXIST;
+
+ spa_list_append(&context->global_list, &global->link);
+ global->registered = true;
+
+ global->generation = ++context->generation;
+
+ spa_list_for_each(registry, &context->registry_resource_list, link) {
+ uint32_t permissions = pw_global_get_permissions(global, registry->client);
+ pw_log_debug("registry %p: global %d %08x serial:%"PRIu64" generation:%"PRIu64,
+ registry, global->id, permissions, global->serial, global->generation);
+ if (PW_PERM_IS_R(permissions))
+ pw_registry_resource_global(registry,
+ global->id,
+ permissions,
+ global->type,
+ global->version,
+ &global->properties->dict);
+ }
+
+ /* Ensure a message is sent also to clients without registries, to force
+ * generation number update. */
+ spa_list_for_each(client, &context->client_list, link) {
+ uint32_t permissions;
+
+ if (client->sent_generation >= context->generation)
+ continue;
+ if (!client->core_resource)
+ continue;
+
+ permissions = pw_global_get_permissions(global, client);
+ if (PW_PERM_IS_R(permissions)) {
+ pw_log_debug("impl-client %p: (no registry) global %d %08x serial:%"PRIu64
+ " generation:%"PRIu64, client, global->id, permissions, global->serial,
+ global->generation);
+ pw_core_resource_done(client->core_resource, SPA_ID_INVALID, 0);
+ }
+ }
+
+ pw_log_debug("%p: registered %u", global, global->id);
+ pw_context_emit_global_added(context, global);
+
+ return 0;
+}
+
+static int global_unregister(struct pw_global *global)
+{
+ struct pw_context *context = global->context;
+ struct pw_resource *resource;
+
+ if (!global->registered)
+ return 0;
+
+ spa_list_for_each(resource, &context->registry_resource_list, link) {
+ uint32_t permissions = pw_global_get_permissions(global, resource->client);
+ pw_log_debug("registry %p: global %d %08x", resource, global->id, permissions);
+ if (PW_PERM_IS_R(permissions))
+ pw_registry_resource_global_remove(resource, global->id);
+ }
+
+ spa_list_remove(&global->link);
+ global->registered = false;
+ global->serial = SPA_ID_INVALID;
+
+ pw_log_debug("%p: unregistered %u", global, global->id);
+ pw_context_emit_global_removed(context, global);
+
+ return 0;
+}
+
+SPA_EXPORT
+struct pw_context *pw_global_get_context(struct pw_global *global)
+{
+ return global->context;
+}
+
+SPA_EXPORT
+const char * pw_global_get_type(struct pw_global *global)
+{
+ return global->type;
+}
+
+SPA_EXPORT
+bool pw_global_is_type(struct pw_global *global, const char *type)
+{
+ return spa_streq(global->type, type);
+}
+
+SPA_EXPORT
+uint32_t pw_global_get_version(struct pw_global *global)
+{
+ return global->version;
+}
+
+SPA_EXPORT
+const struct pw_properties *pw_global_get_properties(struct pw_global *global)
+{
+ return global->properties;
+}
+
+SPA_EXPORT
+int pw_global_update_keys(struct pw_global *global,
+ const struct spa_dict *dict, const char * const keys[])
+{
+ if (global->registered)
+ return -EINVAL;
+ return pw_properties_update_keys(global->properties, dict, keys);
+}
+
+SPA_EXPORT
+void * pw_global_get_object(struct pw_global *global)
+{
+ return global->object;
+}
+
+SPA_EXPORT
+uint32_t pw_global_get_id(struct pw_global *global)
+{
+ return global->id;
+}
+
+SPA_EXPORT
+int pw_global_add_resource(struct pw_global *global, struct pw_resource *resource)
+{
+ resource->global = global;
+ pw_log_debug("%p: resource %p id:%d global:%d", global, resource,
+ resource->id, global->id);
+ spa_list_append(&global->resource_list, &resource->link);
+ pw_resource_set_bound_id(resource, global->id);
+ return 0;
+}
+
+SPA_EXPORT
+int pw_global_for_each_resource(struct pw_global *global,
+ int (*callback) (void *data, struct pw_resource *resource),
+ void *data)
+{
+ struct pw_resource *resource, *t;
+ int res;
+
+ spa_list_for_each_safe(resource, t, &global->resource_list, link)
+ if ((res = callback(data, resource)) != 0)
+ return res;
+ return 0;
+}
+
+SPA_EXPORT
+void pw_global_add_listener(struct pw_global *global,
+ struct spa_hook *listener,
+ const struct pw_global_events *events,
+ void *data)
+{
+ spa_hook_list_append(&global->listener_list, listener, events, data);
+}
+
+/** Bind to a global
+ *
+ * \param global the global to bind to
+ * \param client the client that binds
+ * \param permissions the \ref pw_permission
+ * \param version the version
+ * \param id the id of the resource
+ *
+ * Let \a client bind to \a global with the given version and id.
+ * After binding, the client and the global object will be able to
+ * exchange messages on the proxy/resource with \a id.
+ *
+ */
+SPA_EXPORT int
+pw_global_bind(struct pw_global *global, struct pw_impl_client *client, uint32_t permissions,
+ uint32_t version, uint32_t id)
+{
+ int res;
+
+ if (global->version < version)
+ goto error_version;
+
+ if ((res = global->func(global->object, client, permissions, version, id)) < 0)
+ goto error_bind;
+
+ return res;
+
+error_version:
+ res = -EPROTO;
+ if (client->core_resource)
+ pw_core_resource_errorf(client->core_resource, id, client->recv_seq,
+ res, "id %d: interface version %d < %d",
+ id, global->version, version);
+ goto error_exit;
+error_bind:
+ if (client->core_resource)
+ pw_core_resource_errorf(client->core_resource, id, client->recv_seq,
+ res, "can't bind global %u/%u: %d (%s)", id, version,
+ res, spa_strerror(res));
+ goto error_exit;
+
+error_exit:
+ pw_log_error("%p: can't bind global %u/%u: %d (%s)", global, id,
+ version, res, spa_strerror(res));
+ pw_map_insert_at(&client->objects, id, NULL);
+ if (client->core_resource)
+ pw_core_resource_remove_id(client->core_resource, id);
+ return res;
+}
+
+SPA_EXPORT
+int pw_global_update_permissions(struct pw_global *global, struct pw_impl_client *client,
+ uint32_t old_permissions, uint32_t new_permissions)
+{
+ struct pw_context *context = global->context;
+ struct pw_resource *resource, *t;
+ bool do_hide, do_show;
+
+ do_hide = PW_PERM_IS_R(old_permissions) && !PW_PERM_IS_R(new_permissions);
+ do_show = !PW_PERM_IS_R(old_permissions) && PW_PERM_IS_R(new_permissions);
+
+ pw_log_debug("%p: client %p permissions changed %d %08x -> %08x",
+ global, client, global->id, old_permissions, new_permissions);
+
+ pw_global_emit_permissions_changed(global, client, old_permissions, new_permissions);
+
+ spa_list_for_each(resource, &context->registry_resource_list, link) {
+ if (resource->client != client)
+ continue;
+
+ if (do_hide) {
+ pw_log_debug("client %p: resource %p hide global %d",
+ client, resource, global->id);
+ pw_registry_resource_global_remove(resource, global->id);
+ }
+ else if (do_show) {
+ pw_log_debug("client %p: resource %p show global %d serial:%"PRIu64,
+ client, resource, global->id, global->serial);
+ pw_registry_resource_global(resource,
+ global->id,
+ new_permissions,
+ global->type,
+ global->version,
+ &global->properties->dict);
+ }
+ }
+
+ spa_list_for_each_safe(resource, t, &global->resource_list, link) {
+ if (resource->client != client)
+ continue;
+
+ /* don't ever destroy the core resource */
+ if (!PW_PERM_IS_R(new_permissions) && global->id != PW_ID_CORE)
+ pw_resource_destroy(resource);
+ else
+ resource->permissions = new_permissions;
+ }
+ return 0;
+}
+
+/** Destroy a global
+ *
+ * \param global a global to destroy
+ *
+ */
+SPA_EXPORT
+void pw_global_destroy(struct pw_global *global)
+{
+ struct pw_resource *resource;
+ struct pw_context *context = global->context;
+
+ global->destroyed = true;
+
+ pw_log_debug("%p: destroy %u", global, global->id);
+ pw_global_emit_destroy(global);
+
+ spa_list_consume(resource, &global->resource_list, link)
+ pw_resource_destroy(resource);
+
+ global_unregister(global);
+
+ pw_log_debug("%p: free", global);
+ pw_global_emit_free(global);
+
+ pw_map_remove(&context->globals, global->id);
+ spa_hook_list_clean(&global->listener_list);
+
+ pw_properties_free(global->properties);
+
+ free(global);
+}
diff --git a/src/pipewire/global.h b/src/pipewire/global.h
new file mode 100644
index 0000000..0cf2a7c
--- /dev/null
+++ b/src/pipewire/global.h
@@ -0,0 +1,165 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_GLOBAL_H
+#define PIPEWIRE_GLOBAL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \defgroup pw_global Global
+ *
+ * \brief A global object visible to remote clients
+ *
+ * A global object is visible to remote clients and represents a resource
+ * that can be used or inspected.
+ *
+ * Global objects represent resources that are available on the PipeWire
+ * context and are accessible to remote clients.
+ * Globals come and go when devices or other resources become available for
+ * clients.
+ *
+ * Remote clients receives a list of globals when it binds to the registry
+ * object. See \ref pw_registry.
+ *
+ * A client can bind to a global to send methods or receive events from
+ * the global.
+ *
+ * See \ref page_proxy
+ */
+
+/**
+ * \addtogroup pw_global
+ * \{
+ */
+struct pw_global;
+
+#include <pipewire/impl.h>
+
+typedef int (*pw_global_bind_func_t) (void *object, /**< global object, see \ref pw_global_new */
+ struct pw_impl_client *client, /**< client that binds */
+ uint32_t permissions, /**< permissions for the bind */
+ uint32_t version, /**< client interface version */
+ uint32_t id /**< client proxy id */);
+
+/** Global events, use \ref pw_global_add_listener */
+struct pw_global_events {
+#define PW_VERSION_GLOBAL_EVENTS 0
+ uint32_t version;
+
+ /** The global is destroyed */
+ void (*destroy) (void *data);
+ /** The global is freed */
+ void (*free) (void *data);
+ /** The permissions changed for a client */
+ void (*permissions_changed) (void *data,
+ struct pw_impl_client *client,
+ uint32_t old_permissions,
+ uint32_t new_permissions);
+};
+
+/** Create a new global object */
+struct pw_global *
+pw_global_new(struct pw_context *context, /**< the context */
+ const char *type, /**< the interface type of the global */
+ uint32_t version, /**< the interface version of the global */
+ struct pw_properties *properties, /**< extra properties */
+ pw_global_bind_func_t func, /**< function to bind */
+ void *object /**< global object */);
+
+/** Register a global object to the context registry */
+int pw_global_register(struct pw_global *global);
+
+/** Add an event listener on the global */
+void pw_global_add_listener(struct pw_global *global,
+ struct spa_hook *listener,
+ const struct pw_global_events *events,
+ void *data);
+
+/** Get the permissions of the global for a given client */
+uint32_t pw_global_get_permissions(struct pw_global *global, struct pw_impl_client *client);
+
+/** Get the context object of this global */
+struct pw_context *pw_global_get_context(struct pw_global *global);
+
+/** Get the global type */
+const char *pw_global_get_type(struct pw_global *global);
+
+/** Check a global type */
+bool pw_global_is_type(struct pw_global *global, const char *type);
+
+/** Get the global version */
+uint32_t pw_global_get_version(struct pw_global *global);
+
+/** Get the global properties */
+const struct pw_properties *pw_global_get_properties(struct pw_global *global);
+
+/** Update the global properties, must be done when unregistered */
+int pw_global_update_keys(struct pw_global *global,
+ const struct spa_dict *dict, const char * const keys[]);
+
+/** Get the object associated with the global. This depends on the type of the
+ * global */
+void *pw_global_get_object(struct pw_global *global);
+
+/** Get the unique id of the global */
+uint32_t pw_global_get_id(struct pw_global *global);
+
+/** Get the serial number of the global */
+uint64_t pw_global_get_serial(struct pw_global *global);
+
+/** Add a resource to a global */
+int pw_global_add_resource(struct pw_global *global, struct pw_resource *resource);
+
+/** Iterate all resources added to the global The callback should return
+ * 0 to fetch the next item, any other value stops the iteration and returns
+ * the value. When all callbacks return 0, this function returns 0 when all
+ * items are iterated. */
+int pw_global_for_each_resource(struct pw_global *global,
+ int (*callback) (void *data, struct pw_resource *resource),
+ void *data);
+
+/** Let a client bind to a global */
+int pw_global_bind(struct pw_global *global,
+ struct pw_impl_client *client,
+ uint32_t permissions,
+ uint32_t version,
+ uint32_t id);
+
+int pw_global_update_permissions(struct pw_global *global, struct pw_impl_client *client,
+ uint32_t old_permissions, uint32_t new_permissions);
+
+/** Destroy a global */
+void pw_global_destroy(struct pw_global *global);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_GLOBAL_H */
diff --git a/src/pipewire/i18n.h b/src/pipewire/i18n.h
new file mode 100644
index 0000000..aa7b0b3
--- /dev/null
+++ b/src/pipewire/i18n.h
@@ -0,0 +1,56 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_I18N_H
+#define PIPEWIRE_I18N_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \defgroup pw_gettext Internationalization
+ * Gettext interface
+ */
+
+/**
+ * \addtogroup pw_gettext
+ * \{
+ */
+#include <spa/support/i18n.h>
+
+SPA_FORMAT_ARG_FUNC(1) const char *pw_gettext(const char *msgid);
+SPA_FORMAT_ARG_FUNC(1) const char *pw_ngettext(const char *msgid, const char *msgid_plural, unsigned long int n);
+
+#define _(String) (pw_gettext(String))
+#define N_(String) (String)
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_I18N_H */
diff --git a/src/pipewire/impl-client.c b/src/pipewire/impl-client.c
new file mode 100644
index 0000000..f45e382
--- /dev/null
+++ b/src/pipewire/impl-client.c
@@ -0,0 +1,780 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+
+#include <spa/utils/string.h>
+
+#include "pipewire/impl.h"
+#include "pipewire/private.h"
+
+PW_LOG_TOPIC_EXTERN(log_client);
+#define PW_LOG_TOPIC_DEFAULT log_client
+
+/** \cond */
+struct impl {
+ struct pw_impl_client this;
+ struct spa_hook context_listener;
+ struct pw_array permissions;
+ struct spa_hook pool_listener;
+ unsigned int registered:1;
+};
+
+#define pw_client_resource(r,m,v,...) pw_resource_call(r,struct pw_client_events,m,v,__VA_ARGS__)
+#define pw_client_resource_info(r,...) pw_client_resource(r,info,0,__VA_ARGS__)
+#define pw_client_resource_permissions(r,...) pw_client_resource(r,permissions,0,__VA_ARGS__)
+
+struct resource_data {
+ struct pw_resource *resource;
+ struct spa_hook resource_listener;
+ struct spa_hook object_listener;
+ struct pw_impl_client *client;
+};
+
+/** find a specific permission for a global or the default when there is none */
+static struct pw_permission *
+find_permission(struct pw_impl_client *client, uint32_t id)
+{
+ struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this);
+ struct pw_permission *p;
+ uint32_t idx = id + 1;
+
+ if (id == PW_ID_ANY)
+ goto do_default;
+
+ if (!pw_array_check_index(&impl->permissions, idx, struct pw_permission))
+ goto do_default;
+
+ p = pw_array_get_unchecked(&impl->permissions, idx, struct pw_permission);
+ if (p->permissions == PW_PERM_INVALID)
+ goto do_default;
+
+ return p;
+
+do_default:
+ return pw_array_get_unchecked(&impl->permissions, 0, struct pw_permission);
+}
+
+static struct pw_permission *ensure_permissions(struct pw_impl_client *client, uint32_t id)
+{
+ struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this);
+ struct pw_permission *p;
+ uint32_t idx = id + 1;
+ size_t len, i;
+
+ len = pw_array_get_len(&impl->permissions, struct pw_permission);
+ if (len <= idx) {
+ size_t diff = idx - len + 1;
+
+ p = pw_array_add(&impl->permissions, diff * sizeof(struct pw_permission));
+ if (p == NULL)
+ return NULL;
+
+ for (i = 0; i < diff; i++) {
+ p[i] = PW_PERMISSION_INIT(len + i - 1, PW_PERM_INVALID);
+ }
+ }
+ p = pw_array_get_unchecked(&impl->permissions, idx, struct pw_permission);
+ return p;
+}
+
+/** \endcond */
+
+static uint32_t
+client_permission_func(struct pw_global *global,
+ struct pw_impl_client *client, void *data)
+{
+ struct pw_permission *p = find_permission(client, global->id);
+ return p->permissions;
+}
+
+struct error_data {
+ uint32_t id;
+ int res;
+ const char *error;
+};
+
+static int error_resource(void *object, void *data)
+{
+ struct pw_resource *r = object;
+ struct error_data *d = data;
+ if (r && r->bound_id == d->id)
+ pw_resource_error(r, d->res, d->error);
+ return 0;
+}
+
+static int client_error(void *object, uint32_t id, int res, const char *error)
+{
+ struct resource_data *data = object;
+ struct pw_impl_client *client = data->client;
+ struct error_data d = { id, res, error };
+
+ pw_log_debug("%p: error for global %d", client, id);
+ pw_map_for_each(&client->objects, error_resource, &d);
+ return 0;
+}
+
+static bool has_key(const char * const keys[], const char *key)
+{
+ int i;
+ for (i = 0; keys[i]; i++) {
+ if (spa_streq(keys[i], key))
+ return true;
+ }
+ return false;
+}
+
+static int update_properties(struct pw_impl_client *client, const struct spa_dict *dict, bool filter)
+{
+ static const char * const ignored[] = {
+ PW_KEY_OBJECT_ID,
+ NULL
+ };
+
+ struct pw_resource *resource;
+ int changed = 0;
+ uint32_t i;
+ const char *old;
+
+ for (i = 0; i < dict->n_items; i++) {
+ if (filter) {
+ if (spa_strstartswith(dict->items[i].key, "pipewire.") &&
+ (old = pw_properties_get(client->properties, dict->items[i].key)) != NULL &&
+ (dict->items[i].value == NULL || !spa_streq(old, dict->items[i].value))) {
+ pw_log_warn("%p: refuse property update '%s' from '%s' to '%s'",
+ client, dict->items[i].key, old,
+ dict->items[i].value);
+ continue;
+
+ }
+ if (has_key(ignored, dict->items[i].key))
+ continue;
+ }
+ changed += pw_properties_set(client->properties, dict->items[i].key, dict->items[i].value);
+ }
+ client->info.props = &client->properties->dict;
+
+ pw_log_debug("%p: updated %d properties", client, changed);
+
+ if (!changed)
+ return 0;
+
+ client->info.change_mask |= PW_CLIENT_CHANGE_MASK_PROPS;
+
+ pw_impl_client_emit_info_changed(client, &client->info);
+
+ if (client->global)
+ spa_list_for_each(resource, &client->global->resource_list, link)
+ pw_client_resource_info(resource, &client->info);
+
+ client->info.change_mask = 0;
+
+ return changed;
+}
+
+static void update_busy(struct pw_impl_client *client)
+{
+ struct pw_permission *def;
+ def = find_permission(client, PW_ID_CORE);
+ pw_impl_client_set_busy(client, (def->permissions & PW_PERM_R) ? false : true);
+}
+
+static int finish_register(struct pw_impl_client *client)
+{
+ static const char * const keys[] = {
+ PW_KEY_ACCESS,
+ PW_KEY_CLIENT_ACCESS,
+ PW_KEY_APP_NAME,
+ NULL
+ };
+
+ struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this);
+ struct pw_impl_client *current;
+
+ if (impl->registered)
+ return 0;
+
+ impl->registered = true;
+
+ current = client->context->current_client;
+ client->context->current_client = NULL;
+ pw_context_emit_check_access(client->context, client);
+ client->context->current_client = current;
+
+ update_busy(client);
+
+ pw_global_update_keys(client->global, client->info.props, keys);
+ pw_global_register(client->global);
+
+#ifdef OLD_MEDIA_SESSION_WORKAROUND
+ /*
+ * XXX: temporary workaround for pipewire-media-session, see #2159
+ */
+ if (spa_streq(spa_dict_lookup(client->info.props, PW_KEY_APP_NAME),
+ "pipewire-media-session")) {
+ client->recv_generation = UINT64_MAX;
+ pw_log_info("impl-client %p: enable old pipewire-media-session workaround",
+ client);
+ }
+#endif
+
+ return 0;
+}
+
+static int client_update_properties(void *object, const struct spa_dict *props)
+{
+ struct resource_data *data = object;
+ struct pw_impl_client *client = data->client;
+ int res = update_properties(client, props, true);
+ finish_register(client);
+ return res;
+}
+
+static int client_get_permissions(void *object, uint32_t index, uint32_t num)
+{
+ struct resource_data *data = object;
+ struct pw_resource *resource = data->resource;
+ struct pw_impl_client *client = data->client;
+ struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this);
+ size_t len;
+
+ len = pw_array_get_len(&impl->permissions, struct pw_permission);
+ if ((size_t)index >= len)
+ num = 0;
+ else if ((size_t)index + (size_t)num >= len)
+ num = len - index;
+
+ pw_client_resource_permissions(resource, index,
+ num, pw_array_get_unchecked(&impl->permissions, index, struct pw_permission));
+ return 0;
+}
+
+static int client_update_permissions(void *object,
+ uint32_t n_permissions, const struct pw_permission *permissions)
+{
+ struct resource_data *data = object;
+ struct pw_impl_client *client = data->client;
+ return pw_impl_client_update_permissions(client, n_permissions, permissions);
+}
+
+static const struct pw_client_methods client_methods = {
+ PW_VERSION_CLIENT_METHODS,
+ .error = client_error,
+ .update_properties = client_update_properties,
+ .get_permissions = client_get_permissions,
+ .update_permissions = client_update_permissions
+};
+
+static void client_unbind_func(void *data)
+{
+ struct resource_data *d = data;
+ struct pw_resource *resource = d->resource;
+ spa_hook_remove(&d->resource_listener);
+ spa_hook_remove(&d->object_listener);
+ if (resource->id == 1)
+ resource->client->client_resource = NULL;
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = client_unbind_func,
+};
+
+static int
+global_bind(void *object, struct pw_impl_client *client, uint32_t permissions,
+ uint32_t version, uint32_t id)
+{
+ struct pw_impl_client *this = object;
+ struct pw_global *global = this->global;
+ struct pw_resource *resource;
+ struct resource_data *data;
+
+ resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data));
+ if (resource == NULL)
+ goto error_resource;
+
+ data = pw_resource_get_user_data(resource);
+ data->resource = resource;
+ data->client = this;
+ pw_resource_add_listener(resource,
+ &data->resource_listener,
+ &resource_events, data);
+ pw_resource_add_object_listener(resource,
+ &data->object_listener,
+ &client_methods, data);
+
+ pw_log_debug("%p: bound to %d", this, resource->id);
+ pw_global_add_resource(global, resource);
+
+ if (resource->id == 1)
+ client->client_resource = resource;
+
+ this->info.change_mask = PW_CLIENT_CHANGE_MASK_ALL;
+ pw_client_resource_info(resource, &this->info);
+ this->info.change_mask = 0;
+
+ return 0;
+
+error_resource:
+ pw_log_error("%p: can't create client resource: %m", this);
+ return -errno;
+}
+
+static void pool_added(void *data, struct pw_memblock *block)
+{
+ struct impl *impl = data;
+ struct pw_impl_client *client = &impl->this;
+
+ pw_log_debug("%p: added block %d", client, block->id);
+ if (client->core_resource) {
+ pw_core_resource_add_mem(client->core_resource,
+ block->id, block->type, block->fd,
+ block->flags & PW_MEMBLOCK_FLAG_READWRITE);
+ }
+}
+
+static void pool_removed(void *data, struct pw_memblock *block)
+{
+ struct impl *impl = data;
+ struct pw_impl_client *client = &impl->this;
+ pw_log_debug("%p: removed block %d", client, block->id);
+ if (client->core_resource)
+ pw_core_resource_remove_mem(client->core_resource, block->id);
+}
+
+static const struct pw_mempool_events pool_events = {
+ PW_VERSION_MEMPOOL_EVENTS,
+ .added = pool_added,
+ .removed = pool_removed,
+};
+
+static void
+context_global_removed(void *data, struct pw_global *global)
+{
+ struct impl *impl = data;
+ struct pw_impl_client *client = &impl->this;
+ struct pw_permission *p;
+
+ p = find_permission(client, global->id);
+ pw_log_debug("%p: global %d removed, %p", client, global->id, p);
+ if (p->id != PW_ID_ANY)
+ p->permissions = PW_PERM_INVALID;
+}
+
+static const struct pw_context_events context_events = {
+ PW_VERSION_CONTEXT_EVENTS,
+ .global_removed = context_global_removed,
+};
+
+/** Make a new client object
+ *
+ * \param core a \ref pw_context object to register the client with
+ * \param properties optional client properties, ownership is taken
+ * \return a newly allocated client object
+ *
+ */
+SPA_EXPORT
+struct pw_impl_client *pw_context_create_client(struct pw_impl_core *core,
+ struct pw_protocol *protocol,
+ struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct pw_impl_client *this;
+ struct impl *impl;
+ struct pw_permission *p;
+ int res;
+
+ impl = calloc(1, sizeof(struct impl) + user_data_size);
+ if (impl == NULL) {
+ res = -errno;
+ goto error_cleanup;
+ }
+
+ this = &impl->this;
+ pw_log_debug("%p: new", this);
+
+ this->refcount = 1;
+ this->context = core->context;
+ this->core = core;
+ this->protocol = protocol;
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+
+ pw_array_init(&impl->permissions, 1024);
+ p = pw_array_add(&impl->permissions, sizeof(struct pw_permission));
+ if (p == NULL) {
+ res = -errno;
+ goto error_clear_array;
+ }
+ p->id = PW_ID_ANY;
+ p->permissions = 0;
+
+ this->pool = pw_mempool_new(NULL);
+ if (this->pool == NULL) {
+ res = -errno;
+ goto error_clear_array;
+ }
+ pw_mempool_add_listener(this->pool, &impl->pool_listener, &pool_events, impl);
+
+ this->properties = properties;
+ this->permission_func = client_permission_func;
+ this->permission_data = impl;
+
+ if (user_data_size > 0)
+ this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void);
+
+ spa_hook_list_init(&this->listener_list);
+
+ pw_map_init(&this->objects, 0, 32);
+
+ pw_context_add_listener(this->context, &impl->context_listener, &context_events, impl);
+
+ this->info.props = &this->properties->dict;
+
+ return this;
+
+error_clear_array:
+ pw_array_clear(&impl->permissions);
+error_free:
+ free(impl);
+error_cleanup:
+ pw_properties_free(properties);
+ errno = -res;
+ return NULL;
+}
+
+static void global_destroy(void *data)
+{
+ struct pw_impl_client *client = data;
+ spa_hook_remove(&client->global_listener);
+ client->global = NULL;
+ pw_impl_client_destroy(client);
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+SPA_EXPORT
+int pw_impl_client_register(struct pw_impl_client *client,
+ struct pw_properties *properties)
+{
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ PW_KEY_MODULE_ID,
+ PW_KEY_PROTOCOL,
+ PW_KEY_SEC_PID,
+ PW_KEY_SEC_UID,
+ PW_KEY_SEC_GID,
+ PW_KEY_SEC_LABEL,
+ NULL
+ };
+
+ struct pw_context *context = client->context;
+
+ if (client->registered)
+ goto error_existed;
+
+ pw_log_debug("%p: register", client);
+
+ client->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_Client,
+ PW_VERSION_CLIENT,
+ properties,
+ global_bind,
+ client);
+ if (client->global == NULL)
+ return -errno;
+
+ spa_list_append(&context->client_list, &client->link);
+ client->registered = true;
+
+ client->info.id = client->global->id;
+ pw_properties_setf(client->properties, PW_KEY_OBJECT_ID, "%d", client->info.id);
+ pw_properties_setf(client->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
+ pw_global_get_serial(client->global));
+ client->info.props = &client->properties->dict;
+ pw_global_add_listener(client->global, &client->global_listener, &global_events, client);
+
+ pw_global_update_keys(client->global, client->info.props, keys);
+
+ pw_impl_client_emit_initialized(client);
+
+ return 0;
+
+error_existed:
+ pw_properties_free(properties);
+ return -EEXIST;
+}
+
+SPA_EXPORT
+struct pw_context *pw_impl_client_get_context(struct pw_impl_client *client)
+{
+ return client->core->context;
+}
+
+SPA_EXPORT
+struct pw_protocol *pw_impl_client_get_protocol(struct pw_impl_client *client)
+{
+ return client->protocol;
+}
+
+SPA_EXPORT
+struct pw_resource *pw_impl_client_get_core_resource(struct pw_impl_client *client)
+{
+ return client->core_resource;
+}
+
+SPA_EXPORT
+struct pw_resource *pw_impl_client_find_resource(struct pw_impl_client *client, uint32_t id)
+{
+ return pw_map_lookup(&client->objects, id);
+}
+
+SPA_EXPORT
+struct pw_global *pw_impl_client_get_global(struct pw_impl_client *client)
+{
+ return client->global;
+}
+
+SPA_EXPORT
+const struct pw_properties *pw_impl_client_get_properties(struct pw_impl_client *client)
+{
+ return client->properties;
+}
+
+SPA_EXPORT
+void *pw_impl_client_get_user_data(struct pw_impl_client *client)
+{
+ return client->user_data;
+}
+
+static int destroy_resource(void *object, void *data)
+{
+ if (object)
+ pw_resource_destroy(object);
+ return 0;
+}
+
+
+SPA_EXPORT
+void pw_impl_client_unref(struct pw_impl_client *client)
+{
+ struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this);
+
+ assert(client->refcount > 0);
+ if (--client->refcount > 0)
+ return;
+
+ pw_log_debug("%p: free", impl);
+ assert(client->destroyed);
+
+ pw_impl_client_emit_free(client);
+
+ spa_hook_list_clean(&client->listener_list);
+
+ pw_map_clear(&client->objects);
+ pw_array_clear(&impl->permissions);
+
+ spa_hook_remove(&impl->pool_listener);
+ pw_mempool_destroy(client->pool);
+
+ pw_properties_free(client->properties);
+
+ free(impl);
+}
+
+/** Destroy a client object
+ *
+ * \param client the client to destroy
+ *
+ */
+SPA_EXPORT
+void pw_impl_client_destroy(struct pw_impl_client *client)
+{
+ struct impl *impl = SPA_CONTAINER_OF(client, struct impl, this);
+
+ pw_log_debug("%p: destroy", client);
+
+ assert(!client->destroyed);
+ client->destroyed = true;
+
+ pw_impl_client_emit_destroy(client);
+
+ spa_hook_remove(&impl->context_listener);
+
+ if (client->registered)
+ spa_list_remove(&client->link);
+
+ pw_map_for_each(&client->objects, destroy_resource, client);
+
+ if (client->global) {
+ spa_hook_remove(&client->global_listener);
+ pw_global_destroy(client->global);
+ }
+
+ pw_impl_client_unref(client);
+}
+
+SPA_EXPORT
+void pw_impl_client_add_listener(struct pw_impl_client *client,
+ struct spa_hook *listener,
+ const struct pw_impl_client_events *events,
+ void *data)
+{
+ spa_hook_list_append(&client->listener_list, listener, events, data);
+}
+
+SPA_EXPORT
+const struct pw_client_info *pw_impl_client_get_info(struct pw_impl_client *client)
+{
+ return &client->info;
+}
+
+/** Update client properties
+ *
+ * \param client the client
+ * \param dict a struct spa_dict with properties
+ *
+ * Add all properties in \a dict to the client properties. Existing
+ * properties are overwritten. Items can be removed by setting the value
+ * to NULL.
+ *
+ */
+SPA_EXPORT
+int pw_impl_client_update_properties(struct pw_impl_client *client, const struct spa_dict *dict)
+{
+ int res = update_properties(client, dict, false);
+ finish_register(client);
+ return res;
+}
+
+SPA_EXPORT
+int pw_impl_client_update_permissions(struct pw_impl_client *client,
+ uint32_t n_permissions, const struct pw_permission *permissions)
+{
+ struct pw_impl_core *core = client->core;
+ struct pw_context *context = core->context;
+ struct pw_permission *def;
+ uint32_t i;
+
+ if ((def = find_permission(client, PW_ID_ANY)) == NULL)
+ return -EIO;
+
+ for (i = 0; i < n_permissions; i++) {
+ struct pw_permission *p;
+ uint32_t old_perm, new_perm;
+ struct pw_global *global;
+
+ if (permissions[i].id == PW_ID_ANY) {
+ old_perm = def->permissions;
+ new_perm = permissions[i].permissions;
+
+ if (context->current_client == client)
+ new_perm &= old_perm;
+
+ pw_log_debug("%p: set default permissions %08x -> %08x",
+ client, old_perm, new_perm);
+
+ def->permissions = new_perm;
+
+ spa_list_for_each(global, &context->global_list, link) {
+ if (global->id == client->info.id)
+ continue;
+ p = find_permission(client, global->id);
+ if (p->id != PW_ID_ANY)
+ continue;
+ pw_global_update_permissions(global, client, old_perm, new_perm);
+ }
+ }
+ else {
+ struct pw_global *global;
+
+ global = pw_context_find_global(context, permissions[i].id);
+ if (global == NULL || global->id != permissions[i].id) {
+ pw_log_warn("%p: invalid global %d", client, permissions[i].id);
+ continue;
+ }
+ p = ensure_permissions(client, permissions[i].id);
+ if (p == NULL) {
+ pw_log_warn("%p: can't ensure permission: %m", client);
+ return -errno;
+ }
+ if ((def = find_permission(client, PW_ID_ANY)) == NULL)
+ return -EIO;
+ old_perm = p->permissions == PW_PERM_INVALID ? def->permissions : p->permissions;
+ new_perm = permissions[i].permissions;
+
+ if (context->current_client == client)
+ new_perm &= old_perm;
+
+ pw_log_debug("%p: set global %d permissions %08x -> %08x",
+ client, global->id, old_perm, new_perm);
+
+ p->permissions = new_perm;
+ pw_global_update_permissions(global, client, old_perm, new_perm);
+ }
+ }
+ update_busy(client);
+ return 0;
+}
+
+SPA_EXPORT
+void pw_impl_client_set_busy(struct pw_impl_client *client, bool busy)
+{
+ if (client->busy != busy) {
+ pw_log_debug("%p: busy %d", client, busy);
+ client->busy = busy;
+ pw_impl_client_emit_busy_changed(client, busy);
+ }
+}
+
+SPA_EXPORT
+int pw_impl_client_check_permissions(struct pw_impl_client *client,
+ uint32_t global_id, uint32_t permissions)
+{
+ struct pw_context *context = client->context;
+ struct pw_global *global;
+ uint32_t perms;
+
+ if ((global = pw_context_find_global(context, global_id)) == NULL)
+ return -ENOENT;
+
+ if (client->recv_generation != 0 && global->generation > client->recv_generation)
+ return -ESTALE;
+
+ perms = pw_global_get_permissions(global, client);
+ if ((perms & permissions) != permissions)
+ return -EPERM;
+
+ return 0;
+}
diff --git a/src/pipewire/impl-client.h b/src/pipewire/impl-client.h
new file mode 100644
index 0000000..3d20ae6
--- /dev/null
+++ b/src/pipewire/impl-client.h
@@ -0,0 +1,185 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_CLIENT_H
+#define PIPEWIRE_IMPL_CLIENT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/hook.h>
+
+/** \page page_client_impl Client Implementation
+ *
+ * \section sec_page_client_impl_overview Overview
+ *
+ * The \ref pw_impl_client object is created by a protocol implementation when
+ * a new client connects.
+ *
+ * The client is used to keep track of all resources belonging to one
+ * connection with the PipeWire server.
+ *
+ * \section sec_page_client_impl_credentials Credentials
+ *
+ * The client object will have its credentials filled in by the protocol.
+ * This information is used to check if a resource or action is available
+ * for this client.
+ *
+ * \section sec_page_client_impl_types Types
+ *
+ * The client and server maintain a mapping between the client and server
+ * types. All type ids that are in messages exchanged between the client
+ * and server will automatically be remapped.
+ *
+ * \section sec_page_client_impl_resources Resources
+ *
+ * When a client binds to context global object, a resource is made for this
+ * binding and a unique id is assigned to the resources. The client and
+ * server will use this id as the destination when exchanging messages.
+ * See also \ref pw_resource
+ */
+
+/** \defgroup pw_impl_client Client Impl
+ *
+ * \brief PipeWire client object class
+ *
+ * The client object represents a client connection with the PipeWire
+ * server.
+ *
+ * Each client has its own list of resources it is bound to along with
+ * a mapping between the client types and server types.
+ *
+ * See: \ref page_client_impl
+ */
+
+/**
+ * \addtogroup pw_impl_client
+ * \{
+ */
+struct pw_impl_client;
+
+#include <pipewire/context.h>
+#include <pipewire/global.h>
+#include <pipewire/properties.h>
+#include <pipewire/resource.h>
+#include <pipewire/permission.h>
+
+/** The events that a client can emit */
+struct pw_impl_client_events {
+#define PW_VERSION_IMPL_CLIENT_EVENTS 0
+ uint32_t version;
+
+ /** emitted when the client is destroyed */
+ void (*destroy) (void *data);
+
+ /** emitted right before the client is freed */
+ void (*free) (void *data);
+
+ /** the client is initialized */
+ void (*initialized) (void *data);
+
+ /** emitted when the client info changed */
+ void (*info_changed) (void *data, const struct pw_client_info *info);
+
+ /** emitted when a new resource is added for client */
+ void (*resource_added) (void *data, struct pw_resource *resource);
+
+ /** emitted when a resource is removed */
+ void (*resource_removed) (void *data, struct pw_resource *resource);
+
+ /** emitted when the client becomes busy processing an asynchronous
+ * message. In the busy state no messages should be processed.
+ * Processing should resume when the client becomes not busy */
+ void (*busy_changed) (void *data, bool busy);
+};
+
+/** Create a new client. This is mainly used by protocols. */
+struct pw_impl_client *
+pw_context_create_client(struct pw_impl_core *core, /**< the core object */
+ struct pw_protocol *protocol, /**< the client protocol */
+ struct pw_properties *properties, /**< client properties */
+ size_t user_data_size /**< extra user data size */);
+
+/** Destroy a previously created client */
+void pw_impl_client_destroy(struct pw_impl_client *client);
+
+/** Finish configuration and register a client */
+int pw_impl_client_register(struct pw_impl_client *client, /**< the client to register */
+ struct pw_properties *properties/**< extra properties */);
+
+/** Get the client user data */
+void *pw_impl_client_get_user_data(struct pw_impl_client *client);
+
+/** Get the client information */
+const struct pw_client_info *pw_impl_client_get_info(struct pw_impl_client *client);
+
+/** Update the client properties */
+int pw_impl_client_update_properties(struct pw_impl_client *client, const struct spa_dict *dict);
+
+/** Update the client permissions */
+int pw_impl_client_update_permissions(struct pw_impl_client *client, uint32_t n_permissions,
+ const struct pw_permission *permissions);
+
+/** check if a client has permissions for global_id, Since 0.3.9 */
+int pw_impl_client_check_permissions(struct pw_impl_client *client,
+ uint32_t global_id, uint32_t permissions);
+
+/** Get the client properties */
+const struct pw_properties *pw_impl_client_get_properties(struct pw_impl_client *client);
+
+/** Get the context used to create this client */
+struct pw_context *pw_impl_client_get_context(struct pw_impl_client *client);
+/** Get the protocol used to create this client */
+struct pw_protocol *pw_impl_client_get_protocol(struct pw_impl_client *client);
+
+/** Get the client core resource */
+struct pw_resource *pw_impl_client_get_core_resource(struct pw_impl_client *client);
+
+/** Get a resource with the given id */
+struct pw_resource *pw_impl_client_find_resource(struct pw_impl_client *client, uint32_t id);
+
+/** Get the global associated with this client */
+struct pw_global *pw_impl_client_get_global(struct pw_impl_client *client);
+
+/** listen to events from this client */
+void pw_impl_client_add_listener(struct pw_impl_client *client,
+ struct spa_hook *listener,
+ const struct pw_impl_client_events *events,
+ void *data);
+
+
+/** Mark the client busy. This can be used when an asynchronous operation is
+ * started and no further processing is allowed to happen for the client */
+void pw_impl_client_set_busy(struct pw_impl_client *client, bool busy);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_CLIENT_H */
diff --git a/src/pipewire/impl-core.c b/src/pipewire/impl-core.c
new file mode 100644
index 0000000..776f030
--- /dev/null
+++ b/src/pipewire/impl-core.c
@@ -0,0 +1,673 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#ifndef ENODATA
+#define ENODATA 9919
+#endif
+
+#include <spa/debug/types.h>
+#include <spa/utils/string.h>
+
+#include "pipewire/impl.h"
+#include "pipewire/private.h"
+
+#include "pipewire/extensions/protocol-native.h"
+
+PW_LOG_TOPIC_EXTERN(log_core);
+#define PW_LOG_TOPIC_DEFAULT log_core
+
+struct resource_data {
+ struct pw_resource *resource;
+ struct spa_hook resource_listener;
+ struct spa_hook object_listener;
+};
+
+static void * registry_bind(void *object, uint32_t id,
+ const char *type, uint32_t version, size_t user_data_size)
+{
+ struct resource_data *data = object;
+ struct pw_resource *resource = data->resource;
+ struct pw_impl_client *client = resource->client;
+ struct pw_context *context = resource->context;
+ struct pw_global *global;
+ uint32_t permissions, new_id = user_data_size;
+
+ if ((global = pw_context_find_global(context, id)) == NULL)
+ goto error_no_id;
+
+ permissions = pw_global_get_permissions(global, client);
+
+ if (!PW_PERM_IS_R(permissions))
+ goto error_no_id;
+
+ if (resource->client->recv_generation != 0 && global->generation > resource->client->recv_generation)
+ goto error_stale_id;
+
+ if (!spa_streq(global->type, type))
+ goto error_wrong_interface;
+
+ pw_log_debug("global %p: bind global id %d, iface %s/%d to %d", global, id,
+ type, version, new_id);
+
+ if (pw_global_bind(global, client, permissions, version, new_id) < 0)
+ goto error_exit_clean;
+
+ return NULL;
+
+error_stale_id:
+ pw_log_debug("registry %p: not binding stale global "
+ "id %u to %u, generation:%"PRIu64" recv-generation:%"PRIu64,
+ resource, id, new_id, global->generation, resource->client->recv_generation);
+ pw_resource_errorf_id(resource, new_id, -ESTALE, "no global %u any more", id);
+ goto error_exit_clean;
+error_no_id:
+ pw_log_debug("registry %p: no global with id %u to bind to %u", resource, id, new_id);
+ pw_resource_errorf_id(resource, new_id, -ENOENT, "no global %u", id);
+ goto error_exit_clean;
+error_wrong_interface:
+ pw_log_debug("registry %p: global with id %u has no interface %s", resource, id, type);
+ pw_resource_errorf_id(resource, new_id, -ENOSYS, "no interface %s", type);
+ goto error_exit_clean;
+error_exit_clean:
+ /* unmark the new_id the map, the client does not yet know about the failed
+ * bind and will choose the next id, which we would refuse when we don't mark
+ * new_id as 'used and freed' */
+ pw_map_insert_at(&client->objects, new_id, NULL);
+ pw_core_resource_remove_id(client->core_resource, new_id);
+ return NULL;
+}
+
+static int registry_destroy(void *object, uint32_t id)
+{
+ struct resource_data *data = object;
+ struct pw_resource *resource = data->resource;
+ struct pw_impl_client *client = resource->client;
+ struct pw_context *context = resource->context;
+ struct pw_global *global;
+ uint32_t permissions;
+ int res;
+
+ if ((global = pw_context_find_global(context, id)) == NULL)
+ goto error_no_id;
+
+ permissions = pw_global_get_permissions(global, client);
+
+ if (!PW_PERM_IS_R(permissions))
+ goto error_no_id;
+
+ if (resource->client->recv_generation != 0 && global->generation > resource->client->recv_generation)
+ goto error_stale_id;
+
+ if (id == PW_ID_CORE || !PW_PERM_IS_X(permissions))
+ goto error_not_allowed;
+
+ pw_log_debug("global %p: destroy global id %d", global, id);
+
+ pw_global_destroy(global);
+ return 0;
+
+error_stale_id:
+ pw_log_debug("registry %p: not destroying stale global "
+ "id %u, generation:%"PRIu64" recv-generation:%"PRIu64,
+ resource, id, global->generation, resource->client->recv_generation);
+ pw_resource_errorf(resource, -ESTALE, "no global %u any more", id);
+ res = -ESTALE;
+ goto error_exit;
+error_no_id:
+ pw_log_debug("registry %p: no global with id %u to destroy", resource, id);
+ pw_resource_errorf(resource, -ENOENT, "no global %u", id);
+ res = -ENOENT;
+ goto error_exit;
+error_not_allowed:
+ pw_log_debug("registry %p: destroy of id %u not allowed", resource, id);
+ pw_resource_errorf(resource, -EPERM, "no permission to destroy %u", id);
+ res = -EPERM;
+ goto error_exit;
+error_exit:
+ return res;
+}
+
+static const struct pw_registry_methods registry_methods = {
+ PW_VERSION_REGISTRY_METHODS,
+ .bind = registry_bind,
+ .destroy = registry_destroy
+};
+
+static void destroy_registry_resource(void *_data)
+{
+ struct resource_data *data = _data;
+ struct pw_resource *resource = data->resource;
+ spa_list_remove(&resource->link);
+ spa_hook_remove(&data->resource_listener);
+ spa_hook_remove(&data->object_listener);
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = destroy_registry_resource
+};
+
+static int destroy_resource(void *object, void *data)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client;
+
+ if (resource &&
+ (client = resource->client) != NULL &&
+ resource != client->core_resource) {
+ pw_resource_remove(resource);
+ }
+ return 0;
+}
+
+static int core_hello(void *object, uint32_t version)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client = resource->client;
+ struct pw_context *context = client->context;
+ struct pw_impl_core *this = client->core;
+ int res;
+
+ pw_log_debug("%p: hello %d from resource %p", context, version, resource);
+ pw_map_for_each(&client->objects, destroy_resource, client);
+
+ pw_mempool_clear(client->pool);
+
+ this->info.change_mask = PW_CORE_CHANGE_MASK_ALL;
+ pw_core_resource_info(resource, &this->info);
+
+ if (version >= 3) {
+ if ((res = pw_global_bind(client->global, client,
+ PW_PERM_ALL, PW_VERSION_CLIENT, PW_ID_CLIENT)) < 0)
+ return res;
+ }
+ return 0;
+}
+
+static int core_sync(void *object, uint32_t id, int seq)
+{
+ struct pw_resource *resource = object;
+ pw_log_trace("%p: sync %d for resource %d", resource->context, seq, id);
+ pw_core_resource_done(resource, id, seq);
+ return 0;
+}
+
+static int core_pong(void *object, uint32_t id, int seq)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client = resource->client;
+ struct pw_resource *r;
+
+ pw_log_debug("%p: pong %d for resource %d", resource->context, seq, id);
+
+ if ((r = pw_impl_client_find_resource(client, id)) == NULL)
+ return -EINVAL;
+
+ pw_resource_emit_pong(r, seq);
+ return 0;
+}
+
+static int core_error(void *object, uint32_t id, int seq, int res, const char *message)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client = resource->client;
+ struct pw_resource *r;
+
+ pw_log_error("%p: error %d for resource %d: %s", resource->context, res, id, message);
+
+ if ((r = pw_impl_client_find_resource(client, id)) == NULL)
+ return -EINVAL;
+
+ pw_resource_emit_error(r, seq, res, message);
+ return 0;
+}
+
+static struct pw_registry *core_get_registry(void *object, uint32_t version, size_t user_data_size)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client = resource->client;
+ struct pw_context *context = client->context;
+ struct pw_global *global;
+ struct pw_resource *registry_resource;
+ struct resource_data *data;
+ uint32_t new_id = user_data_size;
+ int res;
+
+ registry_resource = pw_resource_new(client,
+ new_id,
+ PW_PERM_ALL,
+ PW_TYPE_INTERFACE_Registry,
+ version,
+ sizeof(*data));
+ if (registry_resource == NULL) {
+ res = -errno;
+ goto error_resource;
+ }
+
+ data = pw_resource_get_user_data(registry_resource);
+ data->resource = registry_resource;
+ pw_resource_add_listener(registry_resource,
+ &data->resource_listener,
+ &resource_events,
+ data);
+ pw_resource_add_object_listener(registry_resource,
+ &data->object_listener,
+ &registry_methods,
+ data);
+
+ spa_list_append(&context->registry_resource_list, &registry_resource->link);
+
+ spa_list_for_each(global, &context->global_list, link) {
+ uint32_t permissions = pw_global_get_permissions(global, client);
+ if (PW_PERM_IS_R(permissions)) {
+ pw_registry_resource_global(registry_resource,
+ global->id,
+ permissions,
+ global->type,
+ global->version,
+ &global->properties->dict);
+ }
+ }
+
+ return (struct pw_registry *)registry_resource;
+
+error_resource:
+ pw_core_resource_errorf(client->core_resource, new_id,
+ client->recv_seq, res,
+ "can't create registry resource: %d (%s)",
+ res, spa_strerror(res));
+ pw_map_insert_at(&client->objects, new_id, NULL);
+ pw_core_resource_remove_id(client->core_resource, new_id);
+ errno = -res;
+ return NULL;
+}
+
+static void *
+core_create_object(void *object,
+ const char *factory_name,
+ const char *type,
+ uint32_t version,
+ const struct spa_dict *props,
+ size_t user_data_size)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client = resource->client;
+ struct pw_impl_factory *factory;
+ void *obj;
+ struct pw_properties *properties;
+ struct pw_context *context = client->context;
+ uint32_t new_id = user_data_size;
+ int res;
+
+ factory = pw_context_find_factory(context, factory_name);
+ if (factory == NULL || factory->global == NULL)
+ goto error_no_factory;
+
+ if (!PW_PERM_IS_R(pw_global_get_permissions(factory->global, client)))
+ goto error_no_factory;
+
+ if (!spa_streq(factory->info.type, type))
+ goto error_type;
+
+ if (factory->info.version < version) {
+ pw_log_info("%p: version %d < %d", context,
+ factory->info.version, version);
+ }
+
+ if (props) {
+ properties = pw_properties_new_dict(props);
+ if (properties == NULL)
+ goto error_properties;
+ } else
+ properties = NULL;
+
+ /* error will be posted */
+ obj = pw_impl_factory_create_object(factory, resource, type,
+ version, properties, new_id);
+ if (obj == NULL)
+ goto error_create_failed;
+
+ return 0;
+
+error_no_factory:
+ res = -ENOENT;
+ pw_log_debug("%p: can't find factory '%s'", context, factory_name);
+ pw_resource_errorf_id(resource, new_id, res, "unknown factory name %s", factory_name);
+ goto error_exit;
+error_type:
+ res = -EPROTO;
+ pw_log_debug("%p: invalid resource type/version", context);
+ pw_resource_errorf_id(resource, new_id, res, "wrong resource type/version");
+ goto error_exit;
+error_properties:
+ res = -errno;
+ pw_log_debug("%p: can't create properties: %m", context);
+ pw_resource_errorf_id(resource, new_id, res, "can't create properties: %s", spa_strerror(res));
+ goto error_exit;
+error_create_failed:
+ res = -errno;
+ goto error_exit;
+error_exit:
+ pw_map_insert_at(&client->objects, new_id, NULL);
+ pw_core_resource_remove_id(client->core_resource, new_id);
+ errno = -res;
+ return NULL;
+}
+
+static int core_destroy(void *object, void *proxy)
+{
+ struct pw_resource *resource = object;
+ struct pw_impl_client *client = resource->client;
+ struct pw_impl_core *this = client->core;
+ struct pw_resource *r = proxy;
+ pw_log_debug("%p: destroy resource %p from client %p", this, r, client);
+ pw_resource_destroy(r);
+ return 0;
+}
+
+static const struct pw_core_methods core_methods = {
+ PW_VERSION_CORE_METHODS,
+ .hello = core_hello,
+ .sync = core_sync,
+ .pong = core_pong,
+ .error = core_error,
+ .get_registry = core_get_registry,
+ .create_object = core_create_object,
+ .destroy = core_destroy,
+};
+
+SPA_EXPORT
+struct pw_impl_core *pw_context_create_core(struct pw_context *context,
+ struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct pw_impl_core *this;
+ const char *name;
+ int res;
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL)
+ return NULL;
+
+ this = calloc(1, sizeof(*this) + user_data_size);
+ if (this == NULL) {
+ res = -errno;
+ goto error_exit;
+ };
+
+ this->context = context;
+ this->properties = properties;
+
+ if ((name = pw_properties_get(properties, PW_KEY_CORE_NAME)) == NULL) {
+ pw_properties_setf(properties,
+ PW_KEY_CORE_NAME, "pipewire-%s-%d",
+ pw_get_user_name(), getpid());
+ name = pw_properties_get(properties, PW_KEY_CORE_NAME);
+ }
+
+ this->info.user_name = pw_get_user_name();
+ this->info.host_name = pw_get_host_name();
+ this->info.version = pw_get_library_version();
+ do {
+ res = pw_getrandom(&this->info.cookie,
+ sizeof(this->info.cookie), 0);
+ } while ((res == -1) && (errno == EINTR));
+ if (res == -1) {
+ res = -errno;
+ goto error_exit;
+ } else if (res != sizeof(this->info.cookie)) {
+ res = -ENODATA;
+ goto error_exit;
+ }
+ this->info.name = name;
+ spa_hook_list_init(&this->listener_list);
+
+ if (user_data_size > 0)
+ this->user_data = SPA_PTROFF(this, sizeof(*this), void);
+
+ pw_log_debug("%p: new %s", this, name);
+
+ return this;
+
+error_exit:
+ pw_properties_free(properties);
+ free(this);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+struct pw_impl_core *pw_context_get_default_core(struct pw_context *context)
+{
+ return context->core;
+}
+
+SPA_EXPORT
+void pw_impl_core_destroy(struct pw_impl_core *core)
+{
+ pw_log_debug("%p: destroy", core);
+ pw_impl_core_emit_destroy(core);
+
+ if (core->registered)
+ spa_list_remove(&core->link);
+
+ if (core->global) {
+ spa_hook_remove(&core->global_listener);
+ pw_global_destroy(core->global);
+ }
+
+ pw_impl_core_emit_free(core);
+ pw_log_debug("%p: free", core);
+
+ spa_hook_list_clean(&core->listener_list);
+
+ pw_properties_free(core->properties);
+
+ free(core);
+}
+
+static void core_unbind_func(void *data)
+{
+ struct resource_data *d = data;
+ struct pw_resource *resource = d->resource;
+ spa_hook_remove(&d->resource_listener);
+ spa_hook_remove(&d->object_listener);
+ if (resource->id == 0)
+ resource->client->core_resource = NULL;
+}
+
+static const struct pw_resource_events core_resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = core_unbind_func,
+};
+
+static int
+global_bind(void *object,
+ struct pw_impl_client *client,
+ uint32_t permissions,
+ uint32_t version,
+ uint32_t id)
+{
+ struct pw_impl_core *this = object;
+ struct pw_global *global = this->global;
+ struct pw_resource *resource;
+ struct resource_data *data;
+ int res;
+
+ resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data));
+ if (resource == NULL) {
+ res = -errno;
+ goto error;
+ }
+
+ data = pw_resource_get_user_data(resource);
+ data->resource = resource;
+
+ pw_resource_add_listener(resource,
+ &data->resource_listener,
+ &core_resource_events, data);
+ pw_resource_add_object_listener(resource,
+ &data->object_listener,
+ &core_methods, resource);
+
+ pw_global_add_resource(global, resource);
+
+ if (resource->id == 0) {
+ client->core_resource = resource;
+ }
+ else {
+ this->info.change_mask = PW_CORE_CHANGE_MASK_ALL;
+ pw_core_resource_info(resource, &this->info);
+ this->info.change_mask = 0;
+ }
+
+ pw_log_debug("%p: bound to %d", this, resource->id);
+
+ return 0;
+
+error:
+ pw_log_error("%p: can't create resource: %m", this);
+ return res;
+}
+
+static void global_destroy(void *data)
+{
+ struct pw_impl_core *core = data;
+ spa_hook_remove(&core->global_listener);
+ core->global = NULL;
+ pw_impl_core_destroy(core);
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+SPA_EXPORT
+const struct pw_properties *pw_impl_core_get_properties(struct pw_impl_core *core)
+{
+ return core->properties;
+}
+
+SPA_EXPORT
+int pw_impl_core_update_properties(struct pw_impl_core *core, const struct spa_dict *dict)
+{
+ struct pw_resource *resource;
+ int changed;
+
+ changed = pw_properties_update(core->properties, dict);
+ core->info.props = &core->properties->dict;
+
+ pw_log_debug("%p: updated %d properties", core, changed);
+
+ if (!changed)
+ return 0;
+
+ core->info.change_mask |= PW_CORE_CHANGE_MASK_PROPS;
+ if (core->global)
+ spa_list_for_each(resource, &core->global->resource_list, link)
+ pw_core_resource_info(resource, &core->info);
+ core->info.change_mask = 0;
+
+ return changed;
+}
+
+SPA_EXPORT
+int pw_impl_core_register(struct pw_impl_core *core,
+ struct pw_properties *properties)
+{
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ PW_KEY_USER_NAME,
+ PW_KEY_HOST_NAME,
+ PW_KEY_CORE_NAME,
+ PW_KEY_CORE_VERSION,
+ NULL
+ };
+
+ struct pw_context *context = core->context;
+ int res;
+
+ if (core->registered)
+ goto error_existed;
+
+ core->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_Core,
+ PW_VERSION_CORE,
+ properties,
+ global_bind,
+ core);
+ if (core->global == NULL)
+ return -errno;
+
+ spa_list_append(&context->core_impl_list, &core->link);
+ core->registered = true;
+
+ core->info.id = core->global->id;
+ pw_properties_setf(core->properties, PW_KEY_OBJECT_ID, "%d", core->info.id);
+ pw_properties_setf(core->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
+ pw_global_get_serial(core->global));
+ core->info.props = &core->properties->dict;
+
+ pw_global_update_keys(core->global, core->info.props, keys);
+
+ pw_impl_core_emit_initialized(core);
+
+ pw_global_add_listener(core->global, &core->global_listener, &global_events, core);
+ pw_global_register(core->global);
+
+ return 0;
+
+error_existed:
+ res = -EEXIST;
+ goto error_exit;
+error_exit:
+ pw_properties_free(properties);
+ return res;
+}
+
+SPA_EXPORT
+void *pw_impl_core_get_user_data(struct pw_impl_core *core)
+{
+ return core->user_data;
+}
+
+SPA_EXPORT
+struct pw_global *pw_impl_core_get_global(struct pw_impl_core *core)
+{
+ return core->global;
+}
+
+SPA_EXPORT
+void pw_impl_core_add_listener(struct pw_impl_core *core,
+ struct spa_hook *listener,
+ const struct pw_impl_core_events *events,
+ void *data)
+{
+ spa_hook_list_append(&core->listener_list, listener, events, data);
+}
diff --git a/src/pipewire/impl-core.h b/src/pipewire/impl-core.h
new file mode 100644
index 0000000..ec4107e
--- /dev/null
+++ b/src/pipewire/impl-core.h
@@ -0,0 +1,104 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_CORE_H
+#define PIPEWIRE_IMPL_CORE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \defgroup pw_impl_core Core Impl
+ *
+ * \brief PipeWire core interface.
+ *
+ * The core is used to make objects on demand.
+ */
+
+/**
+ * \addtogroup pw_impl_core
+ * \{
+ */
+
+struct pw_impl_core;
+
+#include <pipewire/context.h>
+#include <pipewire/global.h>
+#include <pipewire/properties.h>
+#include <pipewire/resource.h>
+
+/** Factory events, listen to them with \ref pw_impl_core_add_listener */
+struct pw_impl_core_events {
+#define PW_VERSION_IMPL_CORE_EVENTS 0
+ uint32_t version;
+
+ /** the core is destroyed */
+ void (*destroy) (void *data);
+ /** the core is freed */
+ void (*free) (void *data);
+ /** the core is initialized */
+ void (*initialized) (void *data);
+};
+
+struct pw_impl_core *pw_context_create_core(struct pw_context *context,
+ struct pw_properties *properties,
+ size_t user_data_size);
+
+/* get the default core in a context */
+struct pw_impl_core *pw_context_get_default_core(struct pw_context *context);
+
+/** Get the core properties */
+const struct pw_properties *pw_impl_core_get_properties(struct pw_impl_core *core);
+
+/** Get the core information */
+const struct pw_core_info *pw_impl_core_get_info(struct pw_impl_core *core);
+
+/** Update the core properties */
+int pw_impl_core_update_properties(struct pw_impl_core *core, const struct spa_dict *dict);
+
+int pw_impl_core_register(struct pw_impl_core *core,
+ struct pw_properties *properties);
+
+void pw_impl_core_destroy(struct pw_impl_core *core);
+
+void *pw_impl_core_get_user_data(struct pw_impl_core *core);
+
+/** Get the global of this core */
+struct pw_global *pw_impl_core_get_global(struct pw_impl_core *core);
+
+/** Add an event listener */
+void pw_impl_core_add_listener(struct pw_impl_core *core,
+ struct spa_hook *listener,
+ const struct pw_impl_core_events *events,
+ void *data);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_CORE_H */
diff --git a/src/pipewire/impl-device.c b/src/pipewire/impl-device.c
new file mode 100644
index 0000000..855b046
--- /dev/null
+++ b/src/pipewire/impl-device.c
@@ -0,0 +1,935 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+
+#include <spa/debug/types.h>
+#include <spa/monitor/utils.h>
+#include <spa/pod/filter.h>
+#include <spa/pod/dynamic.h>
+#include <spa/utils/string.h>
+
+#include "pipewire/impl.h"
+#include "pipewire/private.h"
+
+PW_LOG_TOPIC_EXTERN(log_device);
+#define PW_LOG_TOPIC_DEFAULT log_device
+
+struct impl {
+ struct pw_impl_device this;
+
+ struct spa_list param_list;
+ struct spa_list pending_list;
+
+ unsigned int cache_params:1;
+};
+
+#define pw_device_resource(r,m,v,...) pw_resource_call(r,struct pw_device_events,m,v,__VA_ARGS__)
+#define pw_device_resource_info(r,...) pw_device_resource(r,info,0,__VA_ARGS__)
+#define pw_device_resource_param(r,...) pw_device_resource(r,param,0,__VA_ARGS__)
+
+struct result_device_params_data {
+ struct impl *impl;
+ void *data;
+ int (*callback) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ struct spa_pod *param);
+ int seq;
+ uint32_t count;
+ unsigned int cache:1;
+};
+
+struct resource_data {
+ struct pw_impl_device *device;
+ struct pw_resource *resource;
+
+ struct spa_hook resource_listener;
+ struct spa_hook object_listener;
+
+ uint32_t subscribe_ids[MAX_PARAMS];
+ uint32_t n_subscribe_ids;
+
+ /* for async replies */
+ int seq;
+ int orig_seq;
+ int end;
+ struct spa_param_info *pi;
+ struct result_device_params_data data;
+ struct spa_hook listener;
+};
+
+struct object_data {
+ struct spa_list link;
+ uint32_t id;
+#define OBJECT_NODE 0
+#define OBJECT_DEVICE 1
+ uint32_t type;
+ struct spa_handle *handle;
+ void *object;
+ struct spa_hook listener;
+};
+
+static void object_destroy(struct object_data *od)
+{
+ switch (od->type) {
+ case OBJECT_NODE:
+ pw_impl_node_destroy(od->object);
+ break;
+ case OBJECT_DEVICE:
+ pw_impl_device_destroy(od->object);
+ break;
+ }
+}
+
+static void object_update(struct object_data *od, const struct spa_dict *props)
+{
+ switch (od->type) {
+ case OBJECT_NODE:
+ pw_impl_node_update_properties(od->object, props);
+ break;
+ case OBJECT_DEVICE:
+ pw_impl_device_update_properties(od->object, props);
+ break;
+ }
+}
+
+static void object_register(struct object_data *od)
+{
+ switch (od->type) {
+ case OBJECT_NODE:
+ pw_impl_node_register(od->object, NULL);
+ pw_impl_node_set_active(od->object, true);
+ break;
+ case OBJECT_DEVICE:
+ pw_impl_device_register(od->object, NULL);
+ break;
+ }
+}
+
+static void check_properties(struct pw_impl_device *device)
+{
+ const char *str;
+
+ if ((str = pw_properties_get(device->properties, PW_KEY_DEVICE_NAME)) &&
+ (device->name == NULL || !spa_streq(str, device->name))) {
+ free(device->name);
+ device->name = strdup(str);
+ pw_log_debug("%p: name '%s'", device, device->name);
+ }
+}
+
+SPA_EXPORT
+struct pw_impl_device *pw_context_create_device(struct pw_context *context,
+ struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct impl *impl;
+ struct pw_impl_device *this;
+ int res;
+
+ impl = calloc(1, sizeof(struct impl) + user_data_size);
+ if (impl == NULL) {
+ res = -errno;
+ goto error_cleanup;
+ }
+ spa_list_init(&impl->param_list);
+ spa_list_init(&impl->pending_list);
+ impl->cache_params = true;
+
+ this = &impl->this;
+ this->name = strdup("device");
+ pw_log_debug("%p: new", this);
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+
+ this->context = context;
+ this->properties = properties;
+
+ this->info.props = &properties->dict;
+ this->info.params = this->params;
+ spa_hook_list_init(&this->listener_list);
+
+ spa_list_init(&this->object_list);
+
+ if (user_data_size > 0)
+ this->user_data = SPA_PTROFF(this, sizeof(struct impl), void);
+
+ check_properties(this);
+
+ return this;
+
+error_free:
+ free(impl);
+error_cleanup:
+ pw_properties_free(properties);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+void pw_impl_device_destroy(struct pw_impl_device *device)
+{
+ struct impl *impl = SPA_CONTAINER_OF(device, struct impl, this);
+ struct object_data *od;
+
+ pw_log_debug("%p: destroy", device);
+ pw_impl_device_emit_destroy(device);
+
+ spa_list_consume(od, &device->object_list, link)
+ object_destroy(od);
+
+ if (device->registered)
+ spa_list_remove(&device->link);
+
+ if (device->device)
+ spa_hook_remove(&device->listener);
+
+ if (device->global) {
+ spa_hook_remove(&device->global_listener);
+ pw_global_destroy(device->global);
+ }
+ pw_log_debug("%p: free", device);
+ pw_impl_device_emit_free(device);
+
+ pw_param_clear(&impl->param_list, SPA_ID_INVALID);
+ pw_param_clear(&impl->pending_list, SPA_ID_INVALID);
+
+ spa_hook_list_clean(&device->listener_list);
+
+ pw_properties_free(device->properties);
+ free(device->name);
+
+ free(device);
+}
+
+static void remove_busy_resource(struct resource_data *d)
+{
+ struct pw_impl_device *device = d->device;
+ struct impl *impl = SPA_CONTAINER_OF(device, struct impl, this);
+
+ if (d->end != -1) {
+ if (d->pi && d->data.cache) {
+ pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL);
+ d->pi->user = 1;
+ d->pi = NULL;
+ }
+ spa_hook_remove(&d->listener);
+ d->end = -1;
+ pw_impl_client_set_busy(d->resource->client, false);
+ }
+}
+
+static void resource_destroy(void *data)
+{
+ struct resource_data *d = data;
+ remove_busy_resource(d);
+ spa_hook_remove(&d->resource_listener);
+ spa_hook_remove(&d->object_listener);
+}
+
+static void resource_pong(void *data, int seq)
+{
+ struct resource_data *d = data;
+ struct pw_resource *resource = d->resource;
+ pw_log_debug("%p: resource %p: got pong %d", d->device,
+ resource, seq);
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = resource_destroy,
+ .pong = resource_pong,
+};
+
+static void result_device_params(void *data, int seq, int res, uint32_t type, const void *result)
+{
+ struct result_device_params_data *d = data;
+ struct impl *impl = d->impl;
+ pw_log_debug("%p: type %d", impl, type);
+
+ switch (type) {
+ case SPA_RESULT_TYPE_DEVICE_PARAMS:
+ {
+ const struct spa_result_device_params *r = result;
+ d->callback(d->data, seq, r->id, r->index, r->next, r->param);
+ if (d->cache) {
+ pw_log_debug("%p: add param %d", impl, r->id);
+ if (d->count++ == 0)
+ pw_param_add(&impl->pending_list, seq, r->id, NULL);
+ pw_param_add(&impl->pending_list, seq, r->id, r->param);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+SPA_EXPORT
+int pw_impl_device_for_each_param(struct pw_impl_device *device,
+ int seq, uint32_t param_id,
+ uint32_t index, uint32_t max,
+ const struct spa_pod *filter,
+ int (*callback) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ struct spa_pod *param),
+ void *data)
+{
+ int res;
+ struct impl *impl = SPA_CONTAINER_OF(device, struct impl, this);
+ struct result_device_params_data user_data = { impl, data, callback, seq, 0, false };
+ struct spa_hook listener;
+ struct spa_param_info *pi;
+ static const struct spa_device_events device_events = {
+ SPA_VERSION_DEVICE_EVENTS,
+ .result = result_device_params,
+ };
+
+ pi = pw_param_info_find(device->info.params, device->info.n_params, param_id);
+ if (pi == NULL)
+ return -ENOENT;
+
+ if (max == 0)
+ max = UINT32_MAX;
+
+ pw_log_debug("%p: params id:%d (%s) index:%u max:%u cached:%d", device, param_id,
+ spa_debug_type_find_name(spa_type_param, param_id),
+ index, max, pi->user);
+
+ if (pi->user == 1) {
+ struct pw_param *p;
+ uint8_t buffer[4096];
+ struct spa_pod_dynamic_builder b;
+ struct spa_result_device_params result;
+ uint32_t count = 0;
+
+ result.id = param_id;
+ result.next = 0;
+
+ spa_list_for_each(p, &impl->param_list, link) {
+ if (p->id != param_id)
+ continue;
+
+ result.index = result.next++;
+ if (result.index < index)
+ continue;
+
+ spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
+ if (spa_pod_filter(&b.b, &result.param, p->param, filter) == 0) {
+ pw_log_debug("%p: %d param %u", device, seq, result.index);
+ result_device_params(&user_data, seq, 0, SPA_RESULT_TYPE_DEVICE_PARAMS, &result);
+ count++;
+ }
+ spa_pod_dynamic_builder_clean(&b);
+
+ if (count == max)
+ break;
+ }
+ res = 0;
+ } else {
+ user_data.cache = impl->cache_params &&
+ (filter == NULL && index == 0 && max == UINT32_MAX);
+
+ spa_zero(listener);
+ spa_device_add_listener(device->device, &listener,
+ &device_events, &user_data);
+ res = spa_device_enum_params(device->device, seq,
+ param_id, index, max, filter);
+ spa_hook_remove(&listener);
+
+ if (!SPA_RESULT_IS_ASYNC(res) && user_data.cache) {
+ pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL);
+ pi->user = 1;
+ }
+ }
+
+ return res;
+}
+
+static int reply_param(void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next, struct spa_pod *param)
+{
+ struct resource_data *d = data;
+ pw_device_resource_param(d->resource, seq, id, index, next, param);
+ return 0;
+}
+
+static void result_device_params_async(void *data, int seq, int res, uint32_t type, const void *result)
+{
+ struct resource_data *d = data;
+
+ pw_log_debug("%p: async result %d %d (%d/%d)", d->device,
+ res, seq, d->seq, d->end);
+
+ if (seq == d->seq)
+ result_device_params(&d->data, d->orig_seq, res, type, result);
+ if (seq == d->end)
+ remove_busy_resource(d);
+}
+
+static int device_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct resource_data *data = object;
+ struct pw_resource *resource = data->resource;
+ struct pw_impl_device *device = data->device;
+ struct impl *impl = SPA_CONTAINER_OF(device, struct impl, this);
+ struct pw_impl_client *client = resource->client;
+ int res;
+ static const struct spa_device_events device_events = {
+ SPA_VERSION_DEVICE_EVENTS,
+ .result = result_device_params_async,
+ };
+
+ res = pw_impl_device_for_each_param(device, seq, id, start, num,
+ filter, reply_param, data);
+
+ if (res < 0) {
+ pw_resource_errorf(resource, res,
+ "enum params id:%d (%s) failed", id,
+ spa_debug_type_find_name(spa_type_param, id));
+ } else if (SPA_RESULT_IS_ASYNC(res)) {
+ pw_impl_client_set_busy(client, true);
+ data->data.impl = impl;
+ data->data.data = data;
+ data->data.callback = reply_param;
+ data->data.count = 0;
+ data->data.cache = impl->cache_params &&
+ (filter == NULL && start == 0);
+ if (data->end == -1)
+ spa_device_add_listener(device->device, &data->listener,
+ &device_events, data);
+ data->pi = pw_param_info_find(device->info.params,
+ device->info.n_params, id);
+ data->orig_seq = seq;
+ data->seq = res;
+ data->end = spa_device_sync(device->device, res);
+ }
+
+ return res;
+}
+
+static int device_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct resource_data *data = object;
+ struct pw_resource *resource = data->resource;
+ uint32_t i;
+
+ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids));
+ data->n_subscribe_ids = n_ids;
+
+ for (i = 0; i < n_ids; i++) {
+ data->subscribe_ids[i] = ids[i];
+ pw_log_debug("%p: resource %p subscribe param id:%d (%s)",
+ data->device, resource, ids[i],
+ spa_debug_type_find_name(spa_type_param, ids[i]));
+ device_enum_params(data, 1, ids[i], 0, UINT32_MAX, NULL);
+ }
+ return 0;
+}
+
+static void result_device_done(void *data, int seq, int res, uint32_t type, const void *result)
+{
+ struct resource_data *d = data;
+
+ pw_log_debug("%p: async result %d %d (%d/%d)", d->device,
+ res, seq, d->seq, d->end);
+
+ if (seq == d->end)
+ remove_busy_resource(d);
+}
+
+static int device_set_param(void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct resource_data *data = object;
+ struct pw_resource *resource = data->resource;
+ struct pw_impl_device *device = data->device;
+ struct pw_impl_client *client = resource->client;
+ int res;
+ static const struct spa_device_events device_events = {
+ SPA_VERSION_DEVICE_EVENTS,
+ .result = result_device_done,
+ };
+
+ if ((res = spa_device_set_param(device->device, id, flags, param)) < 0) {
+ pw_resource_errorf(resource, res,
+ "set param id:%d (%s) flags:%08x failed", id,
+ spa_debug_type_find_name(spa_type_param, id), flags);
+ } else if (SPA_RESULT_IS_ASYNC(res)) {
+ pw_impl_client_set_busy(client, true);
+ data->data.data = data;
+ if (data->end == -1)
+ spa_device_add_listener(device->device, &data->listener,
+ &device_events, data);
+ data->seq = res;
+ data->end = spa_device_sync(device->device, res);
+ }
+ return res;
+}
+
+static const struct pw_device_methods device_methods = {
+ PW_VERSION_DEVICE_METHODS,
+ .subscribe_params = device_subscribe_params,
+ .enum_params = device_enum_params,
+ .set_param = device_set_param
+};
+
+static int
+global_bind(void *object, struct pw_impl_client *client, uint32_t permissions,
+ uint32_t version, uint32_t id)
+{
+ struct pw_impl_device *this = object;
+ struct pw_global *global = this->global;
+ struct pw_resource *resource;
+ struct resource_data *data;
+
+ resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data));
+ if (resource == NULL)
+ goto error_resource;
+
+ data = pw_resource_get_user_data(resource);
+ data->device = this;
+ data->resource = resource;
+ data->end = -1;
+
+ pw_resource_add_listener(resource,
+ &data->resource_listener,
+ &resource_events, data);
+ pw_resource_add_object_listener(resource,
+ &data->object_listener,
+ &device_methods, data);
+
+ pw_log_debug("%p: bound to %d", this, resource->id);
+ pw_global_add_resource(global, resource);
+
+ this->info.change_mask = PW_DEVICE_CHANGE_MASK_ALL;
+ pw_device_resource_info(resource, &this->info);
+ this->info.change_mask = 0;
+
+ return 0;
+
+error_resource:
+ pw_log_error("%p: can't create device resource: %m", this);
+ return -errno;
+}
+
+static void global_destroy(void *data)
+{
+ struct pw_impl_device *device = data;
+ spa_hook_remove(&device->global_listener);
+ device->global = NULL;
+ pw_impl_device_destroy(device);
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+SPA_EXPORT
+int pw_impl_device_register(struct pw_impl_device *device,
+ struct pw_properties *properties)
+{
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ PW_KEY_OBJECT_PATH,
+ PW_KEY_MODULE_ID,
+ PW_KEY_FACTORY_ID,
+ PW_KEY_CLIENT_ID,
+ PW_KEY_DEVICE_API,
+ PW_KEY_DEVICE_DESCRIPTION,
+ PW_KEY_DEVICE_NAME,
+ PW_KEY_DEVICE_NICK,
+ PW_KEY_MEDIA_CLASS,
+ NULL
+ };
+
+ struct pw_context *context = device->context;
+ struct object_data *od;
+
+ if (device->registered)
+ goto error_existed;
+
+ device->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_Device,
+ PW_VERSION_DEVICE,
+ properties,
+ global_bind,
+ device);
+ if (device->global == NULL)
+ return -errno;
+
+ spa_list_append(&context->device_list, &device->link);
+ device->registered = true;
+
+ device->info.id = device->global->id;
+ pw_properties_setf(device->properties, PW_KEY_OBJECT_ID, "%d", device->info.id);
+ pw_properties_setf(device->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
+ pw_global_get_serial(device->global));
+ device->info.props = &device->properties->dict;
+
+ pw_global_update_keys(device->global, device->info.props, keys);
+
+ pw_impl_device_emit_initialized(device);
+
+ pw_global_add_listener(device->global, &device->global_listener, &global_events, device);
+ pw_global_register(device->global);
+
+ spa_list_for_each(od, &device->object_list, link)
+ object_register(od);
+
+ return 0;
+
+error_existed:
+ pw_properties_free(properties);
+ return -EEXIST;
+}
+
+static void on_object_destroy(void *data)
+{
+ struct object_data *od = data;
+ spa_list_remove(&od->link);
+}
+
+static void on_object_free(void *data)
+{
+ struct object_data *od = data;
+ pw_unload_spa_handle(od->handle);
+}
+
+static const struct pw_impl_node_events node_object_events = {
+ PW_VERSION_IMPL_NODE_EVENTS,
+ .destroy = on_object_destroy,
+ .free = on_object_free,
+};
+
+static const struct pw_impl_device_events device_object_events = {
+ PW_VERSION_IMPL_DEVICE_EVENTS,
+ .destroy = on_object_destroy,
+ .free = on_object_free,
+};
+
+static void emit_info_changed(struct pw_impl_device *device)
+{
+ struct pw_resource *resource;
+
+ pw_impl_device_emit_info_changed(device, &device->info);
+
+ if (device->global)
+ spa_list_for_each(resource, &device->global->resource_list, link)
+ pw_device_resource_info(resource, &device->info);
+
+ device->info.change_mask = 0;
+}
+
+static int update_properties(struct pw_impl_device *device, const struct spa_dict *dict, bool filter)
+{
+ static const char * const ignored[] = {
+ PW_KEY_OBJECT_ID,
+ PW_KEY_MODULE_ID,
+ PW_KEY_FACTORY_ID,
+ PW_KEY_CLIENT_ID,
+ NULL
+ };
+
+ int changed;
+
+ changed = pw_properties_update_ignore(device->properties, dict, filter ? ignored : NULL);
+ device->info.props = &device->properties->dict;
+
+ pw_log_debug("%p: updated %d properties", device, changed);
+
+ if (!changed)
+ return 0;
+
+ device->info.change_mask |= PW_DEVICE_CHANGE_MASK_PROPS;
+
+ return changed;
+}
+
+static int resource_is_subscribed(struct pw_resource *resource, uint32_t id)
+{
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ uint32_t i;
+
+ for (i = 0; i < data->n_subscribe_ids; i++) {
+ if (data->subscribe_ids[i] == id)
+ return 1;
+ }
+ return 0;
+}
+
+static int notify_param(void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next, struct spa_pod *param)
+{
+ struct pw_impl_device *device = data;
+ struct pw_resource *resource;
+
+ spa_list_for_each(resource, &device->global->resource_list, link) {
+ if (!resource_is_subscribed(resource, id))
+ continue;
+
+ pw_log_debug("%p: resource %p notify param %d", device, resource, id);
+ pw_device_resource_param(resource, seq, id, index, next, param);
+ }
+ return 0;
+}
+
+static void emit_params(struct pw_impl_device *device, uint32_t *changed_ids, uint32_t n_changed_ids)
+{
+ uint32_t i;
+ int res;
+
+ if (device->global == NULL)
+ return;
+
+ pw_log_debug("%p: emit %d params", device, n_changed_ids);
+
+ for (i = 0; i < n_changed_ids; i++) {
+ struct pw_resource *resource;
+ int subscribed = 0;
+
+ /* first check if anyone is subscribed */
+ spa_list_for_each(resource, &device->global->resource_list, link) {
+ if ((subscribed = resource_is_subscribed(resource, changed_ids[i])))
+ break;
+ }
+ if (!subscribed)
+ continue;
+
+ if ((res = pw_impl_device_for_each_param(device, 1, changed_ids[i], 0, UINT32_MAX,
+ NULL, notify_param, device)) < 0) {
+ pw_log_error("%p: error %d (%s)", device, res, spa_strerror(res));
+ }
+ }
+}
+
+static void device_info(void *data, const struct spa_device_info *info)
+{
+ struct pw_impl_device *device = data;
+ uint32_t changed_ids[MAX_PARAMS], n_changed_ids = 0;
+
+ pw_log_debug("%p: flags:%08"PRIx64" change_mask:%08"PRIx64,
+ device, info->flags, info->change_mask);
+
+ if (info->change_mask & SPA_DEVICE_CHANGE_MASK_PROPS) {
+ update_properties(device, info->props, true);
+ }
+ if (info->change_mask & SPA_DEVICE_CHANGE_MASK_PARAMS) {
+ uint32_t i;
+
+ device->info.change_mask |= PW_DEVICE_CHANGE_MASK_PARAMS;
+ device->info.n_params = SPA_MIN(info->n_params, SPA_N_ELEMENTS(device->params));
+
+ for (i = 0; i < device->info.n_params; i++) {
+ uint32_t id = info->params[i].id;
+
+ pw_log_debug("%p: param %d id:%d (%s) %08x:%08x", device, i,
+ id, spa_debug_type_find_name(spa_type_param, id),
+ device->info.params[i].flags, info->params[i].flags);
+
+ device->info.params[i].id = device->params[i].id;
+ if (device->info.params[i].flags == info->params[i].flags)
+ continue;
+
+ pw_log_debug("%p: update param %d", device, id);
+ device->info.params[i] = info->params[i];
+ device->info.params[i].user = 0;
+
+ if (info->params[i].flags & SPA_PARAM_INFO_READ)
+ changed_ids[n_changed_ids++] = id;
+ }
+ }
+ emit_info_changed(device);
+
+ if (n_changed_ids > 0)
+ emit_params(device, changed_ids, n_changed_ids);
+}
+
+static void device_add_object(struct pw_impl_device *device, uint32_t id,
+ const struct spa_device_object_info *info)
+{
+ struct pw_context *context = device->context;
+ struct spa_handle *handle;
+ struct pw_properties *props;
+ int res;
+ void *iface;
+ struct object_data *od = NULL;
+
+ if (info->factory_name == NULL) {
+ pw_log_debug("%p: missing factory name", device);
+ return;
+ }
+
+ handle = pw_context_load_spa_handle(context, info->factory_name, info->props);
+ if (handle == NULL) {
+ pw_log_warn("%p: can't load handle %s: %m",
+ device, info->factory_name);
+ return;
+ }
+
+ if ((res = spa_handle_get_interface(handle, info->type, &iface)) < 0) {
+ pw_log_error("%p: can't get %s interface: %s", device, info->type,
+ spa_strerror(res));
+ return;
+ }
+
+ props = pw_properties_copy(device->properties);
+ if (info->props && props)
+ pw_properties_update(props, info->props);
+
+ if (spa_streq(info->type, SPA_TYPE_INTERFACE_Node)) {
+ struct pw_impl_node *node;
+ node = pw_context_create_node(context, props, sizeof(struct object_data));
+
+ od = pw_impl_node_get_user_data(node);
+ od->object = node;
+ od->type = OBJECT_NODE;
+ pw_impl_node_add_listener(node, &od->listener, &node_object_events, od);
+ pw_impl_node_set_implementation(node, iface);
+ } else if (spa_streq(info->type, SPA_TYPE_INTERFACE_Device)) {
+ struct pw_impl_device *dev;
+ dev = pw_context_create_device(context, props, sizeof(struct object_data));
+
+ od = pw_impl_device_get_user_data(dev);
+ od->object = dev;
+ od->type = OBJECT_DEVICE;
+ pw_impl_device_add_listener(dev, &od->listener, &device_object_events, od);
+ pw_impl_device_set_implementation(dev, iface);
+ } else {
+ pw_log_warn("%p: unknown type %s", device, info->type);
+ pw_properties_free(props);
+ }
+
+ if (od) {
+ od->id = id;
+ od->handle = handle;
+ spa_list_append(&device->object_list, &od->link);
+ if (device->global)
+ object_register(od);
+ }
+ return;
+}
+
+static struct object_data *find_object(struct pw_impl_device *device, uint32_t id)
+{
+ struct object_data *od;
+ spa_list_for_each(od, &device->object_list, link) {
+ if (od->id == id)
+ return od;
+ }
+ return NULL;
+}
+
+static void device_object_info(void *data, uint32_t id,
+ const struct spa_device_object_info *info)
+{
+ struct pw_impl_device *device = data;
+ struct object_data *od;
+
+ od = find_object(device, id);
+
+ if (info == NULL) {
+ pw_log_debug("%p: remove node %d (%p)", device, id, od);
+ if (od)
+ object_destroy(od);
+ }
+ else if (od != NULL) {
+ if (info->change_mask & SPA_DEVICE_OBJECT_CHANGE_MASK_PROPS)
+ object_update(od, info->props);
+ }
+ else {
+ device_add_object(device, id, info);
+ }
+}
+
+static const struct spa_device_events device_events = {
+ SPA_VERSION_DEVICE_EVENTS,
+ .info = device_info,
+ .object_info = device_object_info,
+};
+
+SPA_EXPORT
+int pw_impl_device_set_implementation(struct pw_impl_device *device, struct spa_device *spa_device)
+{
+ pw_log_debug("%p: implementation %p", device, spa_device);
+
+ if (device->device) {
+ pw_log_error("%p: implementation existed %p",
+ device, device->device);
+ return -EEXIST;
+ }
+ device->device = spa_device;
+ spa_device_add_listener(device->device,
+ &device->listener, &device_events, device);
+
+ return 0;
+}
+
+SPA_EXPORT
+struct spa_device *pw_impl_device_get_implementation(struct pw_impl_device *device)
+{
+ return device->device;
+}
+
+SPA_EXPORT
+const struct pw_properties *pw_impl_device_get_properties(struct pw_impl_device *device)
+{
+ return device->properties;
+}
+
+SPA_EXPORT
+int pw_impl_device_update_properties(struct pw_impl_device *device, const struct spa_dict *dict)
+{
+ int changed = update_properties(device, dict, false);
+ emit_info_changed(device);
+ return changed;
+}
+
+SPA_EXPORT
+void *pw_impl_device_get_user_data(struct pw_impl_device *device)
+{
+ return device->user_data;
+}
+
+SPA_EXPORT
+struct pw_global *pw_impl_device_get_global(struct pw_impl_device *device)
+{
+ return device->global;
+}
+
+SPA_EXPORT
+void pw_impl_device_add_listener(struct pw_impl_device *device,
+ struct spa_hook *listener,
+ const struct pw_impl_device_events *events,
+ void *data)
+{
+ spa_hook_list_append(&device->listener_list, listener, events, data);
+}
diff --git a/src/pipewire/impl-device.h b/src/pipewire/impl-device.h
new file mode 100644
index 0000000..016d6dc
--- /dev/null
+++ b/src/pipewire/impl-device.h
@@ -0,0 +1,116 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_DEVICE_H
+#define PIPEWIRE_IMPL_DEVICE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \defgroup pw_impl_device Device Impl
+ *
+ * The device is an object that manages nodes. It typically
+ * corresponds to a physical hardware device but it does not
+ * have to be.
+ *
+ * The purpose of the device is to provide an interface to
+ * dynamically create/remove/configure the nodes it manages.
+ */
+
+/**
+ * \addtogroup pw_impl_device
+ * \{
+ */
+struct pw_impl_device;
+
+#include <spa/monitor/device.h>
+
+#include <pipewire/context.h>
+#include <pipewire/global.h>
+#include <pipewire/properties.h>
+#include <pipewire/resource.h>
+
+/** Device events, listen to them with \ref pw_impl_device_add_listener */
+struct pw_impl_device_events {
+#define PW_VERSION_IMPL_DEVICE_EVENTS 0
+ uint32_t version;
+
+ /** the device is destroyed */
+ void (*destroy) (void *data);
+ /** the device is freed */
+ void (*free) (void *data);
+ /** the device is initialized */
+ void (*initialized) (void *data);
+
+ /** the device info changed */
+ void (*info_changed) (void *data, const struct pw_device_info *info);
+};
+
+struct pw_impl_device *pw_context_create_device(struct pw_context *context,
+ struct pw_properties *properties,
+ size_t user_data_size);
+
+int pw_impl_device_register(struct pw_impl_device *device,
+ struct pw_properties *properties);
+
+void pw_impl_device_destroy(struct pw_impl_device *device);
+
+void *pw_impl_device_get_user_data(struct pw_impl_device *device);
+
+/** Set the device implementation */
+int pw_impl_device_set_implementation(struct pw_impl_device *device, struct spa_device *spa_device);
+/** Get the device implementation */
+struct spa_device *pw_impl_device_get_implementation(struct pw_impl_device *device);
+
+/** Get the global of this device */
+struct pw_global *pw_impl_device_get_global(struct pw_impl_device *device);
+
+/** Add an event listener */
+void pw_impl_device_add_listener(struct pw_impl_device *device,
+ struct spa_hook *listener,
+ const struct pw_impl_device_events *events,
+ void *data);
+
+int pw_impl_device_update_properties(struct pw_impl_device *device, const struct spa_dict *dict);
+
+const struct pw_properties *pw_impl_device_get_properties(struct pw_impl_device *device);
+
+int pw_impl_device_for_each_param(struct pw_impl_device *device,
+ int seq, uint32_t param_id,
+ uint32_t index, uint32_t max,
+ const struct spa_pod *filter,
+ int (*callback) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ struct spa_pod *param),
+ void *data);
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_DEVICE_H */
diff --git a/src/pipewire/impl-factory.c b/src/pipewire/impl-factory.c
new file mode 100644
index 0000000..07572c3
--- /dev/null
+++ b/src/pipewire/impl-factory.c
@@ -0,0 +1,301 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+
+#include <spa/debug/types.h>
+#include <spa/utils/string.h>
+
+#include "pipewire/impl.h"
+#include "pipewire/private.h"
+
+PW_LOG_TOPIC_EXTERN(log_factory);
+#define PW_LOG_TOPIC_DEFAULT log_factory
+
+#define pw_factory_resource_info(r,...) pw_resource_call(r,struct pw_factory_events,info,0,__VA_ARGS__)
+
+SPA_EXPORT
+struct pw_impl_factory *pw_context_create_factory(struct pw_context *context,
+ const char *name,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct pw_impl_factory *this;
+ int res;
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL)
+ return NULL;
+
+ this = calloc(1, sizeof(*this) + user_data_size);
+ if (this == NULL) {
+ res = -errno;
+ goto error_exit;
+ };
+
+ this->context = context;
+ this->properties = properties;
+
+ this->info.name = strdup(name);
+ this->info.type = type;
+ this->info.version = version;
+ this->info.props = &properties->dict;
+ spa_hook_list_init(&this->listener_list);
+
+ if (user_data_size > 0)
+ this->user_data = SPA_PTROFF(this, sizeof(*this), void);
+
+ pw_log_debug("%p: new %s", this, name);
+
+ return this;
+
+error_exit:
+ pw_properties_free(properties);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+void pw_impl_factory_destroy(struct pw_impl_factory *factory)
+{
+ pw_log_debug("%p: destroy", factory);
+ pw_impl_factory_emit_destroy(factory);
+
+ if (factory->registered)
+ spa_list_remove(&factory->link);
+
+ if (factory->global) {
+ spa_hook_remove(&factory->global_listener);
+ pw_global_destroy(factory->global);
+ }
+
+ pw_impl_factory_emit_free(factory);
+ pw_log_debug("%p: free", factory);
+
+ spa_hook_list_clean(&factory->listener_list);
+
+ free((char *)factory->info.name);
+
+ pw_properties_free(factory->properties);
+
+ free(factory);
+}
+
+static int
+global_bind(void *object, struct pw_impl_client *client, uint32_t permissions,
+ uint32_t version, uint32_t id)
+{
+ struct pw_impl_factory *this = object;
+ struct pw_global *global = this->global;
+ struct pw_resource *resource;
+
+ resource = pw_resource_new(client, id, permissions, global->type, version, 0);
+ if (resource == NULL)
+ goto error_resource;
+
+ pw_log_debug("%p: bound to %d", this, resource->id);
+ pw_global_add_resource(global, resource);
+
+ this->info.change_mask = PW_FACTORY_CHANGE_MASK_ALL;
+ pw_factory_resource_info(resource, &this->info);
+ this->info.change_mask = 0;
+
+ return 0;
+
+error_resource:
+ pw_log_error("%p: can't create factory resource: %m", this);
+ return -errno;
+}
+
+static void global_destroy(void *data)
+{
+ struct pw_impl_factory *factory = data;
+ spa_hook_remove(&factory->global_listener);
+ factory->global = NULL;
+ pw_impl_factory_destroy(factory);
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+SPA_EXPORT
+const struct pw_properties *pw_impl_factory_get_properties(struct pw_impl_factory *factory)
+{
+ return factory->properties;
+}
+
+SPA_EXPORT
+int pw_impl_factory_update_properties(struct pw_impl_factory *factory, const struct spa_dict *dict)
+{
+ struct pw_resource *resource;
+ int changed;
+
+ changed = pw_properties_update(factory->properties, dict);
+ factory->info.props = &factory->properties->dict;
+
+ pw_log_debug("%p: updated %d properties", factory, changed);
+
+ if (!changed)
+ return 0;
+
+ factory->info.change_mask |= PW_FACTORY_CHANGE_MASK_PROPS;
+ if (factory->global)
+ spa_list_for_each(resource, &factory->global->resource_list, link)
+ pw_factory_resource_info(resource, &factory->info);
+ factory->info.change_mask = 0;
+
+ return changed;
+}
+
+SPA_EXPORT
+int pw_impl_factory_register(struct pw_impl_factory *factory,
+ struct pw_properties *properties)
+{
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ PW_KEY_MODULE_ID,
+ PW_KEY_FACTORY_NAME,
+ PW_KEY_FACTORY_TYPE_NAME,
+ PW_KEY_FACTORY_TYPE_VERSION,
+ NULL
+ };
+
+ struct pw_context *context = factory->context;
+
+ if (factory->registered)
+ goto error_existed;
+
+ factory->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_Factory,
+ PW_VERSION_FACTORY,
+ properties,
+ global_bind,
+ factory);
+ if (factory->global == NULL)
+ return -errno;
+
+ spa_list_append(&context->factory_list, &factory->link);
+ factory->registered = true;
+
+ factory->info.id = factory->global->id;
+ pw_properties_setf(factory->properties, PW_KEY_OBJECT_ID, "%d", factory->info.id);
+ pw_properties_setf(factory->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
+ pw_global_get_serial(factory->global));
+ pw_properties_set(factory->properties, PW_KEY_FACTORY_NAME, factory->info.name);
+ pw_properties_setf(factory->properties, PW_KEY_FACTORY_TYPE_NAME, "%s", factory->info.type);
+ pw_properties_setf(factory->properties, PW_KEY_FACTORY_TYPE_VERSION, "%d", factory->info.version);
+ factory->info.props = &factory->properties->dict;
+
+ pw_global_update_keys(factory->global, factory->info.props, keys);
+
+ pw_impl_factory_emit_initialized(factory);
+
+ pw_global_add_listener(factory->global, &factory->global_listener, &global_events, factory);
+ pw_global_register(factory->global);
+
+ return 0;
+
+error_existed:
+ pw_properties_free(properties);
+ return -EEXIST;
+}
+
+SPA_EXPORT
+void *pw_impl_factory_get_user_data(struct pw_impl_factory *factory)
+{
+ return factory->user_data;
+}
+
+SPA_EXPORT
+const struct pw_factory_info *pw_impl_factory_get_info(struct pw_impl_factory *factory)
+{
+ return &factory->info;
+}
+
+SPA_EXPORT
+struct pw_global *pw_impl_factory_get_global(struct pw_impl_factory *factory)
+{
+ return factory->global;
+}
+
+SPA_EXPORT
+void pw_impl_factory_add_listener(struct pw_impl_factory *factory,
+ struct spa_hook *listener,
+ const struct pw_impl_factory_events *events,
+ void *data)
+{
+ spa_hook_list_append(&factory->listener_list, listener, events, data);
+}
+
+SPA_EXPORT
+void pw_impl_factory_set_implementation(struct pw_impl_factory *factory,
+ const struct pw_impl_factory_implementation *implementation,
+ void *data)
+{
+ factory->impl = SPA_CALLBACKS_INIT(implementation, data);
+}
+
+SPA_EXPORT
+void *pw_impl_factory_create_object(struct pw_impl_factory *factory,
+ struct pw_resource *resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id)
+{
+ void *res = NULL;
+ spa_callbacks_call_res(&factory->impl,
+ struct pw_impl_factory_implementation,
+ res, create_object, 0,
+ resource, type, version, properties, new_id);
+ return res;
+}
+
+/** Find a factory by name
+ *
+ * \param context the context object
+ * \param name the name of the factory to find
+ *
+ * Find in the list of factories registered in \a context for one with
+ * the given \a name.
+ *
+ * \ingroup pw_context
+ */
+SPA_EXPORT
+struct pw_impl_factory *pw_context_find_factory(struct pw_context *context,
+ const char *name)
+{
+ struct pw_impl_factory *factory;
+
+ spa_list_for_each(factory, &context->factory_list, link) {
+ if (spa_streq(factory->info.name, name))
+ return factory;
+ }
+ return NULL;
+}
diff --git a/src/pipewire/impl-factory.h b/src/pipewire/impl-factory.h
new file mode 100644
index 0000000..f3cc546
--- /dev/null
+++ b/src/pipewire/impl-factory.h
@@ -0,0 +1,131 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_FACTORY_H
+#define PIPEWIRE_IMPL_FACTORY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \defgroup pw_impl_factory Factory Impl
+ *
+ * The factory is used to make objects on demand.
+ */
+
+/**
+ * \addtogroup pw_impl_factory
+ * \{
+ */
+struct pw_impl_factory;
+
+#include <pipewire/context.h>
+#include <pipewire/impl-client.h>
+#include <pipewire/global.h>
+#include <pipewire/properties.h>
+#include <pipewire/resource.h>
+
+/** Factory events, listen to them with \ref pw_impl_factory_add_listener */
+struct pw_impl_factory_events {
+#define PW_VERSION_IMPL_FACTORY_EVENTS 0
+ uint32_t version;
+
+ /** the factory is destroyed */
+ void (*destroy) (void *data);
+ /** the factory is freed */
+ void (*free) (void *data);
+ /** the factory is initialized */
+ void (*initialized) (void *data);
+};
+
+struct pw_impl_factory_implementation {
+#define PW_VERSION_IMPL_FACTORY_IMPLEMENTATION 0
+ uint32_t version;
+
+ /** The function to create an object from this factory */
+ void *(*create_object) (void *data,
+ struct pw_resource *resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id);
+};
+
+struct pw_impl_factory *pw_context_create_factory(struct pw_context *context,
+ const char *name,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ size_t user_data_size);
+
+/** Get the factory properties */
+const struct pw_properties *pw_impl_factory_get_properties(struct pw_impl_factory *factory);
+
+/** Get the factory info */
+const struct pw_factory_info *pw_impl_factory_get_info(struct pw_impl_factory *factory);
+
+/** Update the factory properties */
+int pw_impl_factory_update_properties(struct pw_impl_factory *factory, const struct spa_dict *dict);
+
+int pw_impl_factory_register(struct pw_impl_factory *factory,
+ struct pw_properties *properties);
+
+void pw_impl_factory_destroy(struct pw_impl_factory *factory);
+
+void *pw_impl_factory_get_user_data(struct pw_impl_factory *factory);
+
+/** Get the global of this factory */
+struct pw_global *pw_impl_factory_get_global(struct pw_impl_factory *factory);
+
+/** Add an event listener */
+void pw_impl_factory_add_listener(struct pw_impl_factory *factory,
+ struct spa_hook *listener,
+ const struct pw_impl_factory_events *events,
+ void *data);
+
+void pw_impl_factory_set_implementation(struct pw_impl_factory *factory,
+ const struct pw_impl_factory_implementation *implementation,
+ void *data);
+
+void *pw_impl_factory_create_object(struct pw_impl_factory *factory,
+ struct pw_resource *resource,
+ const char *type,
+ uint32_t version,
+ struct pw_properties *properties,
+ uint32_t new_id);
+
+/** Find a factory by name */
+struct pw_impl_factory *
+pw_context_find_factory(struct pw_context *context /**< the context */,
+ const char *name /**< the factory name */);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_FACTORY_H */
diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c
new file mode 100644
index 0000000..f773728
--- /dev/null
+++ b/src/pipewire/impl-link.c
@@ -0,0 +1,1508 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <spa/node/utils.h>
+#include <spa/pod/parser.h>
+#include <spa/pod/compare.h>
+#include <spa/param/param.h>
+#include <spa/debug/types.h>
+
+#include "pipewire/impl-link.h"
+#include "pipewire/private.h"
+
+PW_LOG_TOPIC_EXTERN(log_link);
+#define PW_LOG_TOPIC_DEFAULT log_link
+
+#define MAX_HOPS 32
+
+#define pw_link_resource_info(r,...) pw_resource_call(r,struct pw_link_events,info,0,__VA_ARGS__)
+
+/** \cond */
+struct impl {
+ struct pw_impl_link this;
+
+ unsigned int io_set:1;
+ unsigned int activated:1;
+
+ struct pw_work_queue *work;
+
+ uint32_t output_busy_id;
+ uint32_t input_busy_id;
+
+ struct spa_pod *format_filter;
+ struct pw_properties *properties;
+
+ struct spa_hook input_port_listener;
+ struct spa_hook input_node_listener;
+ struct spa_hook input_global_listener;
+ struct spa_hook output_port_listener;
+ struct spa_hook output_node_listener;
+ struct spa_hook output_global_listener;
+
+ struct spa_io_buffers io;
+
+ struct pw_impl_node *inode, *onode;
+};
+
+/** \endcond */
+
+static void info_changed(struct pw_impl_link *link)
+{
+ struct pw_resource *resource;
+
+ if (link->info.change_mask == 0)
+ return;
+
+ pw_impl_link_emit_info_changed(link, &link->info);
+
+ if (link->global)
+ spa_list_for_each(resource, &link->global->resource_list, link)
+ pw_link_resource_info(resource, &link->info);
+
+ link->info.change_mask = 0;
+}
+
+static void link_update_state(struct pw_impl_link *link, enum pw_link_state state, int res, char *error)
+{
+ struct impl *impl = SPA_CONTAINER_OF(link, struct impl, this);
+ enum pw_link_state old = link->info.state;
+
+ link->info.state = state;
+ free((char*)link->info.error);
+ link->info.error = error;
+
+ if (state == old)
+ return;
+
+ pw_log_debug("%p: %s -> %s (%s)", link,
+ pw_link_state_as_string(old),
+ pw_link_state_as_string(state), error);
+
+ if (state == PW_LINK_STATE_ERROR) {
+ pw_log_error("(%s) %s -> error (%s)", link->name,
+ pw_link_state_as_string(old), error);
+ } else {
+ pw_log_info("(%s) %s -> %s", link->name,
+ pw_link_state_as_string(old),
+ pw_link_state_as_string(state));
+ }
+
+ pw_impl_link_emit_state_changed(link, old, state, error);
+
+ link->info.change_mask |= PW_LINK_CHANGE_MASK_STATE;
+ if (state == PW_LINK_STATE_ERROR ||
+ state == PW_LINK_STATE_PAUSED ||
+ state == PW_LINK_STATE_ACTIVE)
+ info_changed(link);
+
+ if (state == PW_LINK_STATE_ERROR && link->global) {
+ struct pw_resource *resource;
+ spa_list_for_each(resource, &link->global->resource_list, link)
+ pw_resource_error(resource, res, error);
+ }
+
+ if (old < PW_LINK_STATE_PAUSED && state == PW_LINK_STATE_PAUSED) {
+ link->prepared = true;
+ link->preparing = false;
+ pw_context_recalc_graph(link->context, "link prepared");
+ } else if (old == PW_LINK_STATE_PAUSED && state < PW_LINK_STATE_PAUSED) {
+ link->prepared = false;
+ link->preparing = false;
+ pw_context_recalc_graph(link->context, "link unprepared");
+ } else if (state == PW_LINK_STATE_INIT) {
+ link->prepared = false;
+ link->preparing = false;
+ if (impl->output_busy_id != SPA_ID_INVALID) {
+ impl->output_busy_id = SPA_ID_INVALID;
+ link->output->busy_count--;
+ }
+ pw_work_queue_cancel(impl->work, &link->output_link, SPA_ID_INVALID);
+ if (impl->input_busy_id != SPA_ID_INVALID) {
+ impl->input_busy_id = SPA_ID_INVALID;
+ link->input->busy_count--;
+ }
+ pw_work_queue_cancel(impl->work, &link->input_link, SPA_ID_INVALID);
+ }
+}
+
+static void complete_ready(void *obj, void *data, int res, uint32_t id)
+{
+ struct pw_impl_port *port;
+ struct pw_impl_link *this = data;
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+
+ if (obj == &this->input_link)
+ port = this->input;
+ else
+ port = this->output;
+
+ if (id == impl->input_busy_id) {
+ impl->input_busy_id = SPA_ID_INVALID;
+ port->busy_count--;
+ } else if (id == impl->output_busy_id) {
+ impl->output_busy_id = SPA_ID_INVALID;
+ port->busy_count--;
+ } else if (id != SPA_ID_INVALID)
+ return;
+
+ pw_log_debug("%p: obj:%p port %p complete state:%d: %s", this, obj, port,
+ port->state, spa_strerror(res));
+
+ if (SPA_RESULT_IS_OK(res)) {
+ if (port->state < PW_IMPL_PORT_STATE_READY)
+ pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_READY,
+ 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)));
+ }
+ 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)
+{
+ struct pw_impl_port *port;
+ struct pw_impl_link *this = data;
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ struct pw_impl_port_mix *mix;
+
+ if (obj == &this->input_link) {
+ port = this->input;
+ mix = &this->rt.in_mix;
+ } else {
+ port = this->output;
+ mix = &this->rt.out_mix;
+ }
+
+ if (id == impl->input_busy_id) {
+ impl->input_busy_id = SPA_ID_INVALID;
+ port->busy_count--;
+ } else if (id == impl->output_busy_id) {
+ impl->output_busy_id = SPA_ID_INVALID;
+ port->busy_count--;
+ } else if (id != SPA_ID_INVALID)
+ return;
+
+ pw_log_debug("%p: obj:%p port %p complete state:%d: %s", this, obj, port,
+ port->state, spa_strerror(res));
+
+ if (SPA_RESULT_IS_OK(res)) {
+ if (port->state < PW_IMPL_PORT_STATE_PAUSED)
+ pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_PAUSED,
+ 0, NULL);
+ mix->have_buffers = true;
+ } 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;
+ }
+ 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)
+{
+ struct pw_impl_port *port;
+ struct pw_impl_link *this = data;
+
+ if (obj == &this->input_link)
+ port = this->input;
+ else
+ port = this->output;
+
+ pw_log_debug("%p: obj:%p port %p complete state:%d: %s", this, obj, port,
+ port->state, spa_strerror(res));
+}
+
+static int do_negotiate(struct pw_impl_link *this)
+{
+ struct pw_context *context = this->context;
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ int res = -EIO, res2;
+ struct spa_pod *format = NULL, *current;
+ char *error = NULL;
+ bool changed = true;
+ struct pw_impl_port *input, *output;
+ uint8_t buffer[4096];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ uint32_t index;
+ uint32_t in_state, out_state;
+
+ if (this->info.state >= PW_LINK_STATE_NEGOTIATING)
+ return 0;
+
+ input = this->input;
+ output = this->output;
+
+ in_state = input->state;
+ out_state = output->state;
+
+ pw_log_debug("%p: in_state:%d out_state:%d", this, in_state, out_state);
+
+ if (in_state != PW_IMPL_PORT_STATE_CONFIGURE && out_state != PW_IMPL_PORT_STATE_CONFIGURE)
+ return 0;
+
+ link_update_state(this, PW_LINK_STATE_NEGOTIATING, 0, NULL);
+
+ input = this->input;
+ output = this->output;
+
+ /* find a common format for the ports */
+ if ((res = pw_context_find_format(context,
+ output, input, NULL, 0, NULL,
+ &format, &b, &error)) < 0) {
+ format = NULL;
+ goto error;
+ }
+
+ format = spa_pod_copy(format);
+ spa_pod_fixate(format);
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+
+ /* if output port had format and is idle, check if it changed. If so, renegotiate */
+ if (out_state > PW_IMPL_PORT_STATE_CONFIGURE && output->node->info.state == PW_NODE_STATE_IDLE) {
+ index = 0;
+ res = spa_node_port_enum_params_sync(output->node->node,
+ output->direction, output->port_id,
+ SPA_PARAM_Format, &index,
+ NULL, &current, &b);
+ switch (res) {
+ case -EIO:
+ current = NULL;
+ res = 0;
+ SPA_FALLTHROUGH
+ case 1:
+ break;
+ case 0:
+ res = -EBADF;
+ SPA_FALLTHROUGH
+ default:
+ error = spa_aprintf("error get output format: %s", spa_strerror(res));
+ goto error;
+ }
+ if (current == NULL || spa_pod_compare(current, format) != 0) {
+ pw_log_debug("%p: output format change, renegotiate", this);
+ if (current)
+ pw_log_pod(SPA_LOG_LEVEL_DEBUG, current);
+ pw_log_pod(SPA_LOG_LEVEL_DEBUG, format);
+ pw_impl_node_set_state(output->node, PW_NODE_STATE_SUSPENDED);
+ out_state = PW_IMPL_PORT_STATE_CONFIGURE;
+ }
+ else {
+ pw_log_debug("%p: format was already set", this);
+ changed = false;
+ }
+ }
+ /* if input port had format and is idle, check if it changed. If so, renegotiate */
+ if (in_state > PW_IMPL_PORT_STATE_CONFIGURE && input->node->info.state == PW_NODE_STATE_IDLE) {
+ index = 0;
+ res = spa_node_port_enum_params_sync(input->node->node,
+ input->direction, input->port_id,
+ SPA_PARAM_Format, &index,
+ NULL, &current, &b);
+ switch (res) {
+ case -EIO:
+ current = NULL;
+ res = 0;
+ SPA_FALLTHROUGH
+ case 1:
+ break;
+ case 0:
+ res = -EBADF;
+ SPA_FALLTHROUGH
+ default:
+ error = spa_aprintf("error get input format: %s", spa_strerror(res));
+ goto error;
+ }
+ if (current == NULL || spa_pod_compare(current, format) != 0) {
+ pw_log_debug("%p: input format change, renegotiate", this);
+ if (current)
+ pw_log_pod(SPA_LOG_LEVEL_DEBUG, current);
+ pw_log_pod(SPA_LOG_LEVEL_DEBUG, format);
+ pw_impl_node_set_state(input->node, PW_NODE_STATE_SUSPENDED);
+ in_state = PW_IMPL_PORT_STATE_CONFIGURE;
+ }
+ else {
+ pw_log_debug("%p: format was already set", this);
+ changed = false;
+ }
+ }
+
+ pw_log_pod(SPA_LOG_LEVEL_DEBUG, format);
+
+ SPA_POD_OBJECT_ID(format) = SPA_PARAM_Format;
+ pw_log_debug("%p: doing set format %p fixated:%d", this,
+ format, spa_pod_is_fixated(format));
+
+ if (out_state == PW_IMPL_PORT_STATE_CONFIGURE) {
+ pw_log_debug("%p: doing set format on output", this);
+ if ((res = pw_impl_port_set_param(output,
+ SPA_PARAM_Format, 0,
+ format)) < 0) {
+ error = spa_aprintf("error set output format: %d (%s)", res, spa_strerror(res));
+ pw_log_error("tried to set output format:");
+ pw_log_pod(SPA_LOG_LEVEL_ERROR, format);
+ goto error;
+ }
+ if (SPA_RESULT_IS_ASYNC(res)) {
+ output->busy_count++;
+ res = spa_node_sync(output->node->node, res);
+ impl->output_busy_id = pw_work_queue_add(impl->work, &this->output_link, res,
+ complete_ready, this);
+ } else {
+ complete_ready(&this->output_link, this, res, SPA_ID_INVALID);
+ }
+ }
+ if (in_state == PW_IMPL_PORT_STATE_CONFIGURE) {
+ pw_log_debug("%p: doing set format on input", this);
+ if ((res2 = pw_impl_port_set_param(input,
+ SPA_PARAM_Format, 0,
+ format)) < 0) {
+ error = spa_aprintf("error set input format: %d (%s)", res2, spa_strerror(res2));
+ pw_log_error("tried to set input format:");
+ pw_log_pod(SPA_LOG_LEVEL_ERROR, format);
+ goto error;
+ }
+ if (SPA_RESULT_IS_ASYNC(res2)) {
+ input->busy_count++;
+ res2 = spa_node_sync(input->node->node, res2);
+ impl->input_busy_id = pw_work_queue_add(impl->work, &this->input_link, res2,
+ complete_ready, this);
+ if (res == 0)
+ res = res2;
+ } else {
+ complete_ready(&this->input_link, this, res2, SPA_ID_INVALID);
+ }
+ }
+
+ free(this->info.format);
+ this->info.format = format;
+
+ if (changed)
+ this->info.change_mask |= PW_LINK_CHANGE_MASK_FORMAT;
+
+ pw_log_debug("%p: result %d", this, res);
+ return res;
+
+error:
+ pw_context_debug_port_params(context, input->node->node, input->direction,
+ input->port_id, SPA_PARAM_EnumFormat, res,
+ "input format (%s)", error);
+ pw_context_debug_port_params(context, output->node->node, output->direction,
+ output->port_id, SPA_PARAM_EnumFormat, res,
+ "output format (%s)", error);
+ link_update_state(this, PW_LINK_STATE_ERROR, res, error);
+ free(format);
+ return res;
+}
+
+static int port_set_io(struct pw_impl_link *this, struct pw_impl_port *port, uint32_t id,
+ void *data, size_t size, struct pw_impl_port_mix *mix)
+{
+ int res = 0;
+
+ mix->io = data;
+ pw_log_debug("%p: %s port %p %d.%d set io: %d %p %zd", this,
+ pw_direction_as_string(port->direction),
+ port, port->port_id, mix->port.port_id, id, data, size);
+
+ if ((res = spa_node_port_set_io(port->mix,
+ mix->port.direction,
+ mix->port.port_id,
+ id, data, size)) < 0) {
+ if (res == -ENOTSUP)
+ res = 0;
+ else
+ pw_log_warn("%p: port %p can't set io:%d (%s): %s",
+ this, port, id,
+ spa_debug_type_find_name(spa_type_io, id),
+ spa_strerror(res));
+ }
+ return res;
+}
+
+static void select_io(struct pw_impl_link *this)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ struct spa_io_buffers *io;
+
+ io = this->rt.in_mix.io;
+ if (io == NULL)
+ io = this->rt.out_mix.io;
+ if (io == NULL)
+ io = &impl->io;
+
+ this->io = io;
+ *this->io = SPA_IO_BUFFERS_INIT;
+}
+
+static int do_allocation(struct pw_impl_link *this)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ int res;
+ uint32_t in_flags, out_flags;
+ char *error = NULL;
+ struct pw_impl_port *input, *output;
+
+ if (this->info.state > PW_LINK_STATE_ALLOCATING)
+ return 0;
+
+ output = this->output;
+ input = this->input;
+
+ pw_log_debug("%p: out-state:%d in-state:%d", this, output->state, input->state);
+
+ if (input->state < PW_IMPL_PORT_STATE_READY || output->state < PW_IMPL_PORT_STATE_READY)
+ return 0;
+
+ link_update_state(this, PW_LINK_STATE_ALLOCATING, 0, NULL);
+
+ out_flags = output->spa_flags;
+ in_flags = input->spa_flags;
+
+ pw_log_debug("%p: out-node:%p in-node:%p: out-flags:%08x in-flags:%08x",
+ this, output->node, input->node, out_flags, in_flags);
+
+ this->rt.in_mix.have_buffers = false;
+ this->rt.out_mix.have_buffers = false;
+
+ if (out_flags & SPA_PORT_FLAG_LIVE) {
+ pw_log_debug("%p: setting link as live", this);
+ output->node->live = true;
+ input->node->live = true;
+ }
+
+ if (output->buffers.n_buffers) {
+ pw_log_debug("%p: reusing %d output buffers %p", this,
+ output->buffers.n_buffers, output->buffers.buffers);
+ this->rt.out_mix.have_buffers = true;
+ } else {
+ uint32_t flags, alloc_flags;
+
+ flags = 0;
+ /* always shared buffers for the link */
+ alloc_flags = PW_BUFFERS_FLAG_SHARED;
+ if (output->node->remote || input->node->remote)
+ alloc_flags |= PW_BUFFERS_FLAG_SHARED_MEM;
+
+ if (output->node->driver)
+ alloc_flags |= PW_BUFFERS_FLAG_IN_PRIORITY;
+
+ /* if output port can alloc buffers, alloc skeleton buffers */
+ if (SPA_FLAG_IS_SET(out_flags, SPA_PORT_FLAG_CAN_ALLOC_BUFFERS)) {
+ SPA_FLAG_SET(alloc_flags, PW_BUFFERS_FLAG_NO_MEM);
+ flags |= SPA_NODE_BUFFERS_FLAG_ALLOC;
+ }
+
+ if ((res = pw_buffers_negotiate(this->context, alloc_flags,
+ output->node->node, output->port_id,
+ input->node->node, input->port_id,
+ &output->buffers)) < 0) {
+ error = spa_aprintf("error alloc buffers: %s", spa_strerror(res));
+ goto error;
+ }
+
+ pw_log_debug("%p: allocating %d buffers %p", this,
+ output->buffers.n_buffers, output->buffers.buffers);
+
+ if ((res = pw_impl_port_use_buffers(output, &this->rt.out_mix, flags,
+ output->buffers.buffers,
+ output->buffers.n_buffers)) < 0) {
+ error = spa_aprintf("error use output buffers: %d (%s)", res,
+ spa_strerror(res));
+ goto error_clear;
+ }
+ if (SPA_RESULT_IS_ASYNC(res)) {
+ output->busy_count++;
+ res = spa_node_sync(output->node->node, res);
+ impl->output_busy_id = pw_work_queue_add(impl->work, &this->output_link, res,
+ complete_paused, this);
+ if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC)
+ return 0;
+ } else {
+ complete_paused(&this->output_link, this, res, SPA_ID_INVALID);
+ }
+ }
+
+ pw_log_debug("%p: using %d buffers %p on input port", this,
+ output->buffers.n_buffers, output->buffers.buffers);
+
+ if ((res = pw_impl_port_use_buffers(input, &this->rt.in_mix, 0,
+ output->buffers.buffers,
+ output->buffers.n_buffers)) < 0) {
+ error = spa_aprintf("error use input buffers: %d (%s)", res,
+ spa_strerror(res));
+ goto error;
+ }
+
+ if (SPA_RESULT_IS_ASYNC(res)) {
+ input->busy_count++;
+ res = spa_node_sync(input->node->node, res);
+ impl->input_busy_id = pw_work_queue_add(impl->work, &this->input_link, res,
+ complete_paused, this);
+ } else {
+ complete_paused(&this->input_link, this, res, SPA_ID_INVALID);
+ }
+ return 0;
+
+error_clear:
+ pw_buffers_clear(&output->buffers);
+error:
+ link_update_state(this, PW_LINK_STATE_ERROR, res, error);
+ return res;
+}
+
+static int
+do_activate_link(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct pw_impl_link *this = user_data;
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+
+ pw_log_trace("%p: activate", this);
+
+ spa_list_append(&this->output->rt.mix_list, &this->rt.out_mix.rt_link);
+ spa_list_append(&this->input->rt.mix_list, &this->rt.in_mix.rt_link);
+
+ if (impl->inode != impl->onode) {
+ struct pw_node_activation_state *state;
+
+ this->rt.target.activation = impl->inode->rt.activation;
+ spa_list_append(&impl->onode->rt.target_list, &this->rt.target.link);
+
+ state = &this->rt.target.activation->state[0];
+ if (!this->rt.target.active && impl->onode->rt.driver_target.node != NULL) {
+ state->required++;
+ this->rt.target.active = true;
+ }
+
+ pw_log_trace("%p: node:%p state:%p pending:%d/%d", this, impl->inode,
+ state, state->pending, state->required);
+ }
+ return 0;
+}
+
+int pw_impl_link_activate(struct pw_impl_link *this)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ int res;
+
+ pw_log_debug("%p: activate activated:%d state:%s", this, impl->activated,
+ pw_link_state_as_string(this->info.state));
+
+ if (impl->activated || !this->prepared ||
+ !impl->inode->active || !impl->onode->active)
+ return 0;
+
+ if (!impl->io_set) {
+ if ((res = port_set_io(this, this->output, SPA_IO_Buffers, this->io,
+ sizeof(struct spa_io_buffers), &this->rt.out_mix)) < 0)
+ return res;
+
+ if ((res = port_set_io(this, this->input, SPA_IO_Buffers, this->io,
+ sizeof(struct spa_io_buffers), &this->rt.in_mix)) < 0)
+ return res;
+ impl->io_set = true;
+ }
+ pw_loop_invoke(this->output->node->data_loop,
+ do_activate_link, SPA_ID_INVALID, NULL, 0, false, this);
+
+ impl->activated = true;
+ pw_log_info("(%s) activated", this->name);
+ link_update_state(this, PW_LINK_STATE_ACTIVE, 0, NULL);
+
+ return 0;
+}
+static void check_states(void *obj, void *user_data, int res, uint32_t id)
+{
+ struct pw_impl_link *this = obj;
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ int in_state, out_state;
+ struct pw_impl_port *input, *output;
+
+ if (this->info.state == PW_LINK_STATE_ERROR)
+ return;
+
+ if (this->info.state >= PW_LINK_STATE_PAUSED)
+ return;
+
+ output = this->output;
+ input = this->input;
+
+ if (output == NULL || input == NULL) {
+ link_update_state(this, PW_LINK_STATE_ERROR, -EIO,
+ strdup("link without input or output port"));
+ return;
+ }
+
+ if (output->node->info.state == PW_NODE_STATE_ERROR ||
+ input->node->info.state == PW_NODE_STATE_ERROR) {
+ pw_log_warn("%p: one of the nodes is in error out:%s in:%s", this,
+ pw_node_state_as_string(output->node->info.state),
+ pw_node_state_as_string(input->node->info.state));
+ return;
+ }
+
+ out_state = output->state;
+ in_state = input->state;
+
+ pw_log_debug("%p: output state %d, input state %d", this, out_state, in_state);
+
+ if (out_state == PW_IMPL_PORT_STATE_ERROR || in_state == PW_IMPL_PORT_STATE_ERROR) {
+ link_update_state(this, PW_LINK_STATE_ERROR, -EIO, strdup("ports are in error"));
+ return;
+ }
+
+ if (PW_IMPL_PORT_IS_CONTROL(output) && PW_IMPL_PORT_IS_CONTROL(input)) {
+ pw_impl_port_update_state(output, PW_IMPL_PORT_STATE_PAUSED, 0, NULL);
+ pw_impl_port_update_state(input, PW_IMPL_PORT_STATE_PAUSED, 0, NULL);
+ link_update_state(this, PW_LINK_STATE_PAUSED, 0, NULL);
+ }
+
+ if (output->busy_count > 0) {
+ pw_log_debug("%p: output port %p was busy", this, output);
+ res = spa_node_sync(output->node->node, 0);
+ pw_work_queue_add(impl->work, &this->output_link, res, complete_sync, this);
+ goto exit;
+ }
+ else if (input->busy_count > 0) {
+ pw_log_debug("%p: input port %p was busy", this, input);
+ res = spa_node_sync(input->node->node, 0);
+ pw_work_queue_add(impl->work, &this->input_link, res, complete_sync, this);
+ goto exit;
+ }
+
+ if ((res = do_negotiate(this)) != 0)
+ goto exit;
+
+ if ((res = do_allocation(this)) != 0)
+ goto exit;
+
+exit:
+ if (SPA_RESULT_IS_ERROR(res)) {
+ pw_log_debug("%p: got error result %d (%s)", this, res, spa_strerror(res));
+ return;
+ }
+
+ pw_work_queue_add(impl->work,
+ this, -EBUSY, (pw_work_func_t) check_states, this);
+}
+
+static void input_remove(struct pw_impl_link *this, struct pw_impl_port *port)
+{
+ struct impl *impl = (struct impl *) this;
+ struct pw_impl_port_mix *mix = &this->rt.in_mix;
+ int res;
+
+ pw_log_debug("%p: remove input port %p", this, port);
+
+ if (impl->input_busy_id != SPA_ID_INVALID) {
+ impl->input_busy_id = SPA_ID_INVALID;
+ port->busy_count--;
+ }
+ spa_hook_remove(&impl->input_port_listener);
+ spa_hook_remove(&impl->input_node_listener);
+ spa_hook_remove(&impl->input_global_listener);
+
+ spa_list_remove(&this->input_link);
+ pw_impl_port_emit_link_removed(this->input, this);
+
+ pw_impl_port_recalc_latency(this->input);
+
+ if ((res = pw_impl_port_use_buffers(port, mix, 0, NULL, 0)) < 0) {
+ pw_log_warn("%p: port %p clear error %s", this, port, spa_strerror(res));
+ }
+ pw_impl_port_release_mix(port, mix);
+
+ pw_work_queue_cancel(impl->work, &this->input_link, SPA_ID_INVALID);
+ this->input = NULL;
+}
+
+static void output_remove(struct pw_impl_link *this, struct pw_impl_port *port)
+{
+ struct impl *impl = (struct impl *) this;
+ struct pw_impl_port_mix *mix = &this->rt.out_mix;
+
+ pw_log_debug("%p: remove output port %p", this, port);
+
+ if (impl->output_busy_id != SPA_ID_INVALID) {
+ impl->output_busy_id = SPA_ID_INVALID;
+ port->busy_count--;
+ }
+ spa_hook_remove(&impl->output_port_listener);
+ spa_hook_remove(&impl->output_node_listener);
+ spa_hook_remove(&impl->output_global_listener);
+
+ spa_list_remove(&this->output_link);
+ pw_impl_port_emit_link_removed(this->output, this);
+
+ pw_impl_port_recalc_latency(this->output);
+
+ /* we don't clear output buffers when the link goes away. They will get
+ * cleared when the node goes to suspend */
+ pw_impl_port_release_mix(port, mix);
+
+ pw_work_queue_cancel(impl->work, &this->output_link, SPA_ID_INVALID);
+ this->output = NULL;
+}
+
+int pw_impl_link_prepare(struct pw_impl_link *this)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+
+ pw_log_debug("%p: prepared:%d preparing:%d in_active:%d out_active:%d",
+ this, this->prepared, this->preparing,
+ impl->inode->active, impl->onode->active);
+
+ if (!impl->inode->active || !impl->onode->active)
+ return 0;
+
+ if (this->preparing || this->prepared)
+ return 0;
+
+ this->preparing = true;
+
+ pw_work_queue_add(impl->work,
+ this, -EBUSY, (pw_work_func_t) check_states, this);
+
+ return 0;
+}
+
+static int
+do_deactivate_link(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct pw_impl_link *this = user_data;
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+
+ pw_log_trace("%p: disable %p and %p", this, &this->rt.in_mix, &this->rt.out_mix);
+
+ spa_list_remove(&this->rt.out_mix.rt_link);
+ spa_list_remove(&this->rt.in_mix.rt_link);
+
+ if (impl->inode != impl->onode) {
+ struct pw_node_activation_state *state;
+
+ spa_list_remove(&this->rt.target.link);
+ state = &this->rt.target.activation->state[0];
+ if (this->rt.target.active) {
+ state->required--;
+ this->rt.target.active = false;
+ }
+
+ pw_log_trace("%p: node:%p state:%p pending:%d/%d", this, impl->inode,
+ state, state->pending, state->required);
+ }
+
+ return 0;
+}
+
+int pw_impl_link_deactivate(struct pw_impl_link *this)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+
+ pw_log_debug("%p: deactivate activated:%d", this, impl->activated);
+
+ if (!impl->activated)
+ return 0;
+
+ pw_loop_invoke(this->output->node->data_loop,
+ do_deactivate_link, SPA_ID_INVALID, NULL, 0, true, this);
+
+ port_set_io(this, this->output, SPA_IO_Buffers, NULL, 0,
+ &this->rt.out_mix);
+ port_set_io(this, this->input, SPA_IO_Buffers, NULL, 0,
+ &this->rt.in_mix);
+
+ impl->io_set = false;
+ impl->activated = false;
+ pw_log_info("(%s) deactivated", this->name);
+ link_update_state(this, PW_LINK_STATE_PAUSED, 0, NULL);
+
+ return 0;
+}
+
+static int
+global_bind(void *object, struct pw_impl_client *client, uint32_t permissions,
+ uint32_t version, uint32_t id)
+{
+ struct pw_impl_link *this = object;
+ struct pw_global *global = this->global;
+ struct pw_resource *resource;
+
+ resource = pw_resource_new(client, id, permissions, global->type, version, 0);
+ if (resource == NULL)
+ goto error_resource;
+
+ pw_log_debug("%p: bound to %d", this, resource->id);
+ pw_global_add_resource(global, resource);
+
+ this->info.change_mask = PW_LINK_CHANGE_MASK_ALL;
+ pw_link_resource_info(resource, &this->info);
+ this->info.change_mask = 0;
+
+ return 0;
+
+error_resource:
+ pw_log_error("%p: can't create link resource: %m", this);
+ return -errno;
+}
+
+static void port_state_changed(struct pw_impl_link *this, struct pw_impl_port *port,
+ struct pw_impl_port *other, enum pw_impl_port_state old,
+ enum pw_impl_port_state state, const char *error)
+{
+ pw_log_debug("%p: port %p old:%d -> state:%d prepared:%d preparing:%d",
+ this, port, old, state, this->prepared, this->preparing);
+
+ switch (state) {
+ case PW_IMPL_PORT_STATE_ERROR:
+ link_update_state(this, PW_LINK_STATE_ERROR, -EIO, error ? strdup(error) : NULL);
+ break;
+ case PW_IMPL_PORT_STATE_INIT:
+ case PW_IMPL_PORT_STATE_CONFIGURE:
+ if (this->prepared || state < old) {
+ this->prepared = false;
+ link_update_state(this, PW_LINK_STATE_INIT, 0, NULL);
+ }
+ break;
+ case PW_IMPL_PORT_STATE_READY:
+ if (this->prepared || state < old) {
+ this->prepared = false;
+ link_update_state(this, PW_LINK_STATE_NEGOTIATING, 0, NULL);
+ }
+ break;
+ case PW_IMPL_PORT_STATE_PAUSED:
+ break;
+ }
+}
+
+static void port_param_changed(struct pw_impl_link *this, uint32_t id,
+ struct pw_impl_port *outport, struct pw_impl_port *inport)
+{
+ enum pw_impl_port_state target;
+
+ pw_log_debug("%p: outport %p input %p param %d (%s)", this,
+ outport, inport, id, spa_debug_type_find_name(spa_type_param, id));
+
+ switch (id) {
+ case SPA_PARAM_EnumFormat:
+ target = PW_IMPL_PORT_STATE_CONFIGURE;
+ break;
+ default:
+ return;
+ }
+ if (outport)
+ pw_impl_port_update_state(outport, target, 0, NULL);
+ if (inport)
+ pw_impl_port_update_state(inport, target, 0, NULL);
+
+ this->preparing = this->prepared = false;
+ link_update_state(this, PW_LINK_STATE_INIT, 0, NULL);
+ pw_impl_link_prepare(this);
+}
+
+static void input_port_param_changed(void *data, uint32_t id)
+{
+ struct impl *impl = data;
+ struct pw_impl_link *this = &impl->this;
+ port_param_changed(this, id, this->output, this->input);
+}
+
+static void input_port_state_changed(void *data, enum pw_impl_port_state old,
+ enum pw_impl_port_state state, const char *error)
+{
+ struct impl *impl = data;
+ struct pw_impl_link *this = &impl->this;
+ port_state_changed(this, this->input, this->output, old, state, error);
+}
+
+static void output_port_param_changed(void *data, uint32_t id)
+{
+ struct impl *impl = data;
+ struct pw_impl_link *this = &impl->this;
+ port_param_changed(this, id, this->output, this->input);
+}
+
+static void output_port_state_changed(void *data, enum pw_impl_port_state old,
+ enum pw_impl_port_state state, const char *error)
+{
+ struct impl *impl = data;
+ struct pw_impl_link *this = &impl->this;
+ port_state_changed(this, this->output, this->input, old, state, error);
+}
+
+static void input_port_latency_changed(void *data)
+{
+ struct impl *impl = data;
+ struct pw_impl_link *this = &impl->this;
+ if (!this->feedback)
+ pw_impl_port_recalc_latency(this->output);
+}
+
+static void output_port_latency_changed(void *data)
+{
+ struct impl *impl = data;
+ struct pw_impl_link *this = &impl->this;
+ if (!this->feedback)
+ pw_impl_port_recalc_latency(this->input);
+}
+
+static const struct pw_impl_port_events input_port_events = {
+ PW_VERSION_IMPL_PORT_EVENTS,
+ .param_changed = input_port_param_changed,
+ .state_changed = input_port_state_changed,
+ .latency_changed = input_port_latency_changed,
+};
+
+static const struct pw_impl_port_events output_port_events = {
+ PW_VERSION_IMPL_PORT_EVENTS,
+ .param_changed = output_port_param_changed,
+ .state_changed = output_port_state_changed,
+ .latency_changed = output_port_latency_changed,
+};
+
+static void node_result(struct impl *impl, void *obj,
+ int seq, int res, uint32_t type, const void *result)
+{
+ if (SPA_RESULT_IS_ASYNC(seq))
+ pw_work_queue_complete(impl->work, obj, SPA_RESULT_ASYNC_SEQ(seq), res);
+}
+
+static void input_node_result(void *data, int seq, int res, uint32_t type, const void *result)
+{
+ 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);
+ node_result(impl, &impl->this.input_link, seq, res, type, result);
+}
+
+static void output_node_result(void *data, int seq, int res, uint32_t type, const void *result)
+{
+ 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);
+ node_result(impl, &impl->this.output_link, seq, res, type, result);
+}
+
+static void node_active_changed(void *data, bool active)
+{
+ struct impl *impl = data;
+ pw_impl_link_prepare(&impl->this);
+}
+
+static const struct pw_impl_node_events input_node_events = {
+ PW_VERSION_IMPL_NODE_EVENTS,
+ .result = input_node_result,
+ .active_changed = node_active_changed,
+};
+
+static const struct pw_impl_node_events output_node_events = {
+ PW_VERSION_IMPL_NODE_EVENTS,
+ .result = output_node_result,
+ .active_changed = node_active_changed,
+};
+
+static bool pw_impl_node_can_reach(struct pw_impl_node *output, struct pw_impl_node *input, int hop)
+{
+ struct pw_impl_port *p;
+ struct pw_impl_link *l;
+
+ output->loopchecked = true;
+
+ if (output == input)
+ return true;
+
+ if (hop == MAX_HOPS) {
+ pw_log_warn("exceeded hops (%d) %s -> %s", hop, output->name, input->name);
+ return false;
+ }
+
+ spa_list_for_each(p, &output->output_ports, link) {
+ spa_list_for_each(l, &p->links, output_link)
+ l->input->node->loopchecked = l->feedback;
+ }
+ spa_list_for_each(p, &output->output_ports, link) {
+ spa_list_for_each(l, &p->links, output_link) {
+ if (l->input->node->loopchecked)
+ continue;
+ if (pw_impl_node_can_reach(l->input->node, input, hop+1))
+ return true;
+ }
+ }
+ return false;
+}
+
+static void try_link_controls(struct impl *impl, struct pw_impl_port *output, struct pw_impl_port *input)
+{
+ struct pw_control *cin, *cout;
+ struct pw_impl_link *this = &impl->this;
+ uint32_t omix, imix;
+ int res;
+
+ imix = this->rt.in_mix.port.port_id;
+ omix = this->rt.out_mix.port.port_id;
+
+ pw_log_debug("%p: trying controls", impl);
+ spa_list_for_each(cout, &output->control_list[SPA_DIRECTION_OUTPUT], port_link) {
+ spa_list_for_each(cin, &input->control_list[SPA_DIRECTION_INPUT], port_link) {
+ if ((res = pw_control_add_link(cout, omix, cin, imix, &this->control)) < 0)
+ pw_log_error("%p: failed to link controls: %s",
+ this, spa_strerror(res));
+ break;
+ }
+ }
+ spa_list_for_each(cin, &output->control_list[SPA_DIRECTION_INPUT], port_link) {
+ spa_list_for_each(cout, &input->control_list[SPA_DIRECTION_OUTPUT], port_link) {
+ if ((res = pw_control_add_link(cout, imix, cin, omix, &this->notify)) < 0)
+ pw_log_error("%p: failed to link controls: %s",
+ this, spa_strerror(res));
+ break;
+ }
+ }
+}
+
+static void try_unlink_controls(struct impl *impl, struct pw_impl_port *output, struct pw_impl_port *input)
+{
+ struct pw_impl_link *this = &impl->this;
+ int res;
+
+ pw_log_debug("%p: unlinking controls", impl);
+ if (this->control.valid) {
+ if ((res = pw_control_remove_link(&this->control)) < 0)
+ pw_log_error("%p: failed to unlink controls: %s",
+ this, spa_strerror(res));
+ }
+ if (this->notify.valid) {
+ if ((res = pw_control_remove_link(&this->notify)) < 0)
+ pw_log_error("%p: failed to unlink controls: %s",
+ this, spa_strerror(res));
+ }
+}
+
+static int
+check_permission(struct pw_context *context,
+ struct pw_impl_port *output,
+ struct pw_impl_port *input,
+ struct pw_properties *properties)
+{
+ return 0;
+}
+
+static void permissions_changed(struct pw_impl_link *this, struct pw_impl_port *other,
+ struct pw_impl_client *client, uint32_t old, uint32_t new)
+{
+ uint32_t perm;
+
+ perm = pw_global_get_permissions(other->global, client);
+ old &= perm;
+ new &= perm;
+ pw_log_debug("%p: permissions changed %08x -> %08x", this, old, new);
+
+ if (check_permission(this->context, this->output, this->input, this->properties) < 0) {
+ pw_impl_link_destroy(this);
+ } else if (this->global != NULL) {
+ pw_global_update_permissions(this->global, client, old, new);
+ }
+}
+
+static void output_permissions_changed(void *data,
+ struct pw_impl_client *client, uint32_t old, uint32_t new)
+{
+ struct pw_impl_link *this = data;
+ permissions_changed(this, this->input, client, old, new);
+}
+
+static const struct pw_global_events output_global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .permissions_changed = output_permissions_changed,
+};
+
+static void input_permissions_changed(void *data,
+ struct pw_impl_client *client, uint32_t old, uint32_t new)
+{
+ struct pw_impl_link *this = data;
+ permissions_changed(this, this->output, client, old, new);
+}
+
+static const struct pw_global_events input_global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .permissions_changed = input_permissions_changed,
+};
+
+SPA_EXPORT
+struct pw_impl_link *pw_context_create_link(struct pw_context *context,
+ struct pw_impl_port *output,
+ struct pw_impl_port *input,
+ struct spa_pod *format_filter,
+ struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct impl *impl;
+ struct pw_impl_link *this;
+ struct pw_impl_node *input_node, *output_node;
+ int res;
+
+ if (output == input)
+ goto error_same_ports;
+
+ if (output->direction != PW_DIRECTION_OUTPUT ||
+ input->direction != PW_DIRECTION_INPUT)
+ goto error_wrong_direction;
+
+ if (pw_impl_link_find(output, input))
+ goto error_link_exists;
+
+ if (check_permission(context, output, input, properties) < 0)
+ goto error_link_not_allowed;
+
+ output_node = output->node;
+ input_node = input->node;
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL)
+ goto error_no_mem;
+
+ impl = calloc(1, sizeof(struct impl) + user_data_size);
+ if (impl == NULL)
+ goto error_no_mem;
+
+ impl->input_busy_id = SPA_ID_INVALID;
+ impl->output_busy_id = SPA_ID_INVALID;
+
+ this = &impl->this;
+ this->feedback = pw_impl_node_can_reach(input_node, output_node, 0);
+ pw_properties_set(properties, PW_KEY_LINK_FEEDBACK, this->feedback ? "true" : NULL);
+
+ pw_log_debug("%p: new out-port:%p -> in-port:%p", this, output, input);
+
+ if (user_data_size > 0)
+ this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void);
+
+ impl->work = pw_context_get_work_queue(context);
+
+ this->context = context;
+ this->properties = properties;
+ this->info.state = PW_LINK_STATE_INIT;
+
+ this->output = output;
+ this->input = input;
+
+ /* passive means that this link does not make the nodes active */
+ this->passive = pw_properties_get_bool(properties, PW_KEY_LINK_PASSIVE, false);
+
+ spa_hook_list_init(&this->listener_list);
+
+ impl->format_filter = format_filter;
+ this->info.format = NULL;
+ this->info.props = &this->properties->dict;
+
+ this->rt.out_mix.peer_id = input->global->id;
+ this->rt.in_mix.peer_id = output->global->id;
+
+ if ((res = pw_impl_port_init_mix(output, &this->rt.out_mix)) < 0)
+ goto error_output_mix;
+ if ((res = pw_impl_port_init_mix(input, &this->rt.in_mix)) < 0)
+ goto error_input_mix;
+
+ pw_impl_port_add_listener(input, &impl->input_port_listener, &input_port_events, impl);
+ pw_impl_node_add_listener(input_node, &impl->input_node_listener, &input_node_events, impl);
+ pw_global_add_listener(input->global, &impl->input_global_listener, &input_global_events, impl);
+ pw_impl_port_add_listener(output, &impl->output_port_listener, &output_port_events, impl);
+ pw_impl_node_add_listener(output_node, &impl->output_node_listener, &output_node_events, impl);
+ pw_global_add_listener(output->global, &impl->output_global_listener, &output_global_events, impl);
+
+ input_node->live = output_node->live;
+
+ pw_log_debug("%p: output node %p live %d, feedback %d",
+ this, output_node, output_node->live, this->feedback);
+
+ spa_list_append(&output->links, &this->output_link);
+ spa_list_append(&input->links, &this->input_link);
+
+ impl->io = SPA_IO_BUFFERS_INIT;
+
+ select_io(this);
+
+ if (this->feedback) {
+ impl->inode = output_node;
+ impl->onode = input_node;
+ }
+ else {
+ impl->onode = output_node;
+ impl->inode = input_node;
+ }
+
+ this->rt.target.signal_func = impl->inode->rt.target.signal_func;
+ this->rt.target.data = impl->inode->rt.target.data;
+
+ pw_log_debug("%p: constructed out:%p:%d.%d -> in:%p:%d.%d", impl,
+ output_node, output->port_id, this->rt.out_mix.port.port_id,
+ input_node, input->port_id, this->rt.in_mix.port.port_id);
+
+ if (asprintf(&this->name, "%d.%d -> %d.%d",
+ output_node->info.id, output->port_id,
+ input_node->info.id, input->port_id) < 0)
+ this->name = NULL;
+ pw_log_info("(%s) (%s) -> (%s)", this->name, output_node->name, input_node->name);
+
+ pw_impl_port_emit_link_added(output, this);
+ pw_impl_port_emit_link_added(input, this);
+
+ try_link_controls(impl, output, input);
+
+ pw_impl_port_recalc_latency(output);
+ pw_impl_port_recalc_latency(input);
+
+ pw_impl_node_emit_peer_added(impl->onode, impl->inode);
+
+ return this;
+
+error_same_ports:
+ res = -EINVAL;
+ pw_log_debug("can't link the same ports");
+ goto error_exit;
+error_wrong_direction:
+ res = -EINVAL;
+ pw_log_debug("ports have wrong direction");
+ goto error_exit;
+error_link_exists:
+ res = -EEXIST;
+ pw_log_debug("link already exists");
+ goto error_exit;
+error_link_not_allowed:
+ res = -EPERM;
+ pw_log_debug("link not allowed");
+ goto error_exit;
+error_no_mem:
+ res = -errno;
+ pw_log_debug("alloc failed: %m");
+ goto error_exit;
+error_output_mix:
+ pw_log_error("%p: can't get output mix %d (%s)", this, res, spa_strerror(res));
+ goto error_free;
+error_input_mix:
+ pw_log_error("%p: can't get input mix %d (%s)", this, res, spa_strerror(res));
+ pw_impl_port_release_mix(output, &this->rt.out_mix);
+ goto error_free;
+error_free:
+ free(impl);
+error_exit:
+ pw_properties_free(properties);
+ errno = -res;
+ return NULL;
+}
+
+static void global_destroy(void *data)
+{
+ struct pw_impl_link *link = data;
+ spa_hook_remove(&link->global_listener);
+ link->global = NULL;
+ pw_impl_link_destroy(link);
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+SPA_EXPORT
+int pw_impl_link_register(struct pw_impl_link *link,
+ struct pw_properties *properties)
+{
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ PW_KEY_OBJECT_PATH,
+ PW_KEY_MODULE_ID,
+ PW_KEY_FACTORY_ID,
+ PW_KEY_CLIENT_ID,
+ PW_KEY_LINK_OUTPUT_PORT,
+ PW_KEY_LINK_INPUT_PORT,
+ PW_KEY_LINK_OUTPUT_NODE,
+ PW_KEY_LINK_INPUT_NODE,
+ NULL
+ };
+
+ struct pw_context *context = link->context;
+ struct pw_impl_node *output_node, *input_node;
+
+ if (link->registered)
+ goto error_existed;
+
+ output_node = link->output->node;
+ input_node = link->input->node;
+
+ link->info.output_node_id = output_node->global->id;
+ link->info.output_port_id = link->output->global->id;
+ link->info.input_node_id = input_node->global->id;
+ link->info.input_port_id = link->input->global->id;
+
+ link->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_Link,
+ PW_VERSION_LINK,
+ properties,
+ global_bind,
+ link);
+ if (link->global == NULL)
+ return -errno;
+
+ spa_list_append(&context->link_list, &link->link);
+ link->registered = true;
+
+ link->info.id = link->global->id;
+ pw_properties_setf(link->properties, PW_KEY_OBJECT_ID, "%d", link->info.id);
+ pw_properties_setf(link->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
+ pw_global_get_serial(link->global));
+ pw_properties_setf(link->properties, PW_KEY_LINK_OUTPUT_NODE, "%u", link->info.output_node_id);
+ pw_properties_setf(link->properties, PW_KEY_LINK_OUTPUT_PORT, "%u", link->info.output_port_id);
+ pw_properties_setf(link->properties, PW_KEY_LINK_INPUT_NODE, "%u", link->info.input_node_id);
+ pw_properties_setf(link->properties, PW_KEY_LINK_INPUT_PORT, "%u", link->info.input_port_id);
+ link->info.props = &link->properties->dict;
+
+ pw_global_update_keys(link->global, link->info.props, keys);
+
+ pw_impl_link_emit_initialized(link);
+
+ pw_global_add_listener(link->global, &link->global_listener, &global_events, link);
+ pw_global_register(link->global);
+
+ pw_impl_link_prepare(link);
+
+ return 0;
+
+error_existed:
+ pw_properties_free(properties);
+ return -EEXIST;
+}
+
+SPA_EXPORT
+void pw_impl_link_destroy(struct pw_impl_link *link)
+{
+ struct impl *impl = SPA_CONTAINER_OF(link, struct impl, this);
+
+ pw_log_debug("%p: destroy", impl);
+ pw_log_info("(%s) destroy", link->name);
+ pw_impl_link_emit_destroy(link);
+
+ pw_impl_link_deactivate(link);
+
+ if (link->registered)
+ spa_list_remove(&link->link);
+
+ pw_impl_node_emit_peer_removed(impl->onode, impl->inode);
+
+ try_unlink_controls(impl, link->output, link->input);
+
+ output_remove(link, link->output);
+ input_remove(link, link->input);
+
+ if (link->global) {
+ spa_hook_remove(&link->global_listener);
+ pw_global_destroy(link->global);
+ }
+
+ if (link->prepared)
+ pw_context_recalc_graph(link->context, "link destroy");
+
+ pw_log_debug("%p: free", impl);
+ pw_impl_link_emit_free(link);
+
+ pw_work_queue_cancel(impl->work, link, SPA_ID_INVALID);
+
+ spa_hook_list_clean(&link->listener_list);
+
+ pw_properties_free(link->properties);
+
+ free(link->name);
+ free(link->info.format);
+ free(impl);
+}
+
+SPA_EXPORT
+void pw_impl_link_add_listener(struct pw_impl_link *link,
+ struct spa_hook *listener,
+ const struct pw_impl_link_events *events,
+ void *data)
+{
+ pw_log_debug("%p: add listener %p", link, listener);
+ spa_hook_list_append(&link->listener_list, listener, events, data);
+}
+
+struct pw_impl_link *pw_impl_link_find(struct pw_impl_port *output_port, struct pw_impl_port *input_port)
+{
+ struct pw_impl_link *pl;
+
+ spa_list_for_each(pl, &output_port->links, output_link) {
+ if (pl->input == input_port)
+ return pl;
+ }
+ return NULL;
+}
+
+SPA_EXPORT
+struct pw_context *pw_impl_link_get_context(struct pw_impl_link *link)
+{
+ return link->context;
+}
+
+SPA_EXPORT
+void *pw_impl_link_get_user_data(struct pw_impl_link *link)
+{
+ return link->user_data;
+}
+
+SPA_EXPORT
+const struct pw_link_info *pw_impl_link_get_info(struct pw_impl_link *link)
+{
+ return &link->info;
+}
+
+SPA_EXPORT
+struct pw_global *pw_impl_link_get_global(struct pw_impl_link *link)
+{
+ return link->global;
+}
+
+SPA_EXPORT
+struct pw_impl_port *pw_impl_link_get_output(struct pw_impl_link *link)
+{
+ return link->output;
+}
+
+SPA_EXPORT
+struct pw_impl_port *pw_impl_link_get_input(struct pw_impl_link *link)
+{
+ return link->input;
+}
diff --git a/src/pipewire/impl-link.h b/src/pipewire/impl-link.h
new file mode 100644
index 0000000..5a3b73f
--- /dev/null
+++ b/src/pipewire/impl-link.h
@@ -0,0 +1,126 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_LINK_H
+#define PIPEWIRE_IMPL_LINK_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \defgroup pw_impl_link Link Impl
+ *
+ * \brief PipeWire link object.
+ */
+
+/**
+ * \addtogroup pw_impl_link
+ * \{
+ */
+struct pw_impl_link;
+struct pw_impl_port;
+
+#include <pipewire/impl.h>
+
+/** link events added with \ref pw_impl_link_add_listener */
+struct pw_impl_link_events {
+#define PW_VERSION_IMPL_LINK_EVENTS 0
+ uint32_t version;
+
+ /** A link is destroyed */
+ void (*destroy) (void *data);
+
+ /** A link is freed */
+ void (*free) (void *data);
+
+ /** a Link is initialized */
+ void (*initialized) (void *data);
+
+ /** The info changed on a link */
+ void (*info_changed) (void *data, const struct pw_link_info *info);
+
+ /** The link state changed, \a error is only valid when the state is
+ * in error. */
+ void (*state_changed) (void *data, enum pw_link_state old,
+ enum pw_link_state state, const char *error);
+
+ /** A port is unlinked */
+ void (*port_unlinked) (void *data, struct pw_impl_port *port);
+};
+
+
+/** Make a new link between two ports
+ * \return a newly allocated link */
+struct pw_impl_link *
+pw_context_create_link(struct pw_context *context, /**< the context object */
+ struct pw_impl_port *output, /**< an output port */
+ struct pw_impl_port *input, /**< an input port */
+ struct spa_pod *format_filter, /**< an optional format filter */
+ struct pw_properties *properties /**< extra properties */,
+ size_t user_data_size /**< extra user data size */);
+
+/** Destroy a link */
+void pw_impl_link_destroy(struct pw_impl_link *link);
+
+/** Add an event listener to \a link */
+void pw_impl_link_add_listener(struct pw_impl_link *link,
+ struct spa_hook *listener,
+ const struct pw_impl_link_events *events,
+ void *data);
+
+/** Finish link configuration and register */
+int pw_impl_link_register(struct pw_impl_link *link, /**< the link to register */
+ struct pw_properties *properties /**< extra properties */);
+
+/** Get the context of a link */
+struct pw_context *pw_impl_link_get_context(struct pw_impl_link *link);
+
+/** Get the user_data of a link, the size of the memory is given when
+ * constructing the link */
+void *pw_impl_link_get_user_data(struct pw_impl_link *link);
+
+/** Get the link info */
+const struct pw_link_info *pw_impl_link_get_info(struct pw_impl_link *link);
+
+/** Get the global of the link */
+struct pw_global *pw_impl_link_get_global(struct pw_impl_link *link);
+
+/** Get the output port of the link */
+struct pw_impl_port *pw_impl_link_get_output(struct pw_impl_link *link);
+
+/** Get the input port of the link */
+struct pw_impl_port *pw_impl_link_get_input(struct pw_impl_link *link);
+
+/** Find the link between 2 ports */
+struct pw_impl_link *pw_impl_link_find(struct pw_impl_port *output, struct pw_impl_port *input);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_LINK_H */
diff --git a/src/pipewire/impl-metadata.c b/src/pipewire/impl-metadata.c
new file mode 100644
index 0000000..9f7aa41
--- /dev/null
+++ b/src/pipewire/impl-metadata.c
@@ -0,0 +1,627 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+
+#include <spa/debug/types.h>
+#include <spa/utils/string.h>
+
+#include "pipewire/impl.h"
+#include "pipewire/private.h"
+
+#include "pipewire/extensions/metadata.h"
+
+PW_LOG_TOPIC_EXTERN(log_metadata);
+#define PW_LOG_TOPIC_DEFAULT log_metadata
+
+#define pw_metadata_emit(hooks,method,version,...) \
+ spa_hook_list_call_simple(hooks, struct pw_metadata_events, \
+ method, version, ##__VA_ARGS__)
+
+#define pw_metadata_emit_property(hooks,...) pw_metadata_emit(hooks,property, 0, ##__VA_ARGS__)
+
+struct metadata {
+ struct spa_interface iface;
+ struct pw_array storage;
+ struct spa_hook_list hooks; /**< event listeners */
+};
+
+struct item {
+ uint32_t subject;
+ char *key;
+ char *type;
+ char *value;
+};
+
+static void clear_item(struct item *item)
+{
+ free(item->key);
+ free(item->type);
+ free(item->value);
+ spa_zero(*item);
+}
+
+static void set_item(struct item *item, uint32_t subject, const char *key, const char *type, const char *value)
+{
+ item->subject = subject;
+ item->key = strdup(key);
+ item->type = type ? strdup(type) : NULL;
+ item->value = strdup(value);
+}
+
+static int change_item(struct item *item, const char *type, const char *value)
+{
+ int changed = 0;
+ if (!spa_streq(item->type, type)) {
+ free((char*)item->type);
+ item->type = type ? strdup(type) : NULL;
+ changed++;
+ }
+ if (!spa_streq(item->value, value)) {
+ free((char*)item->value);
+ item->value = value ? strdup(value) : NULL;
+ changed++;
+ }
+ return changed;
+}
+
+static void emit_properties(struct metadata *this)
+{
+ struct item *item;
+ pw_array_for_each(item, &this->storage) {
+ pw_log_debug("metadata %p: %d %s %s %s",
+ this, item->subject, item->key, item->type, item->value);
+ pw_metadata_emit_property(&this->hooks,
+ item->subject,
+ item->key,
+ item->type,
+ item->value);
+ }
+}
+
+static int impl_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_metadata_events *events,
+ void *data)
+{
+ struct metadata *this = object;
+ struct spa_hook_list save;
+
+ spa_return_val_if_fail(this != NULL, -EINVAL);
+ spa_return_val_if_fail(events != NULL, -EINVAL);
+
+ pw_log_debug("metadata %p:", this);
+
+ spa_hook_list_isolate(&this->hooks, &save, listener, events, data);
+
+ emit_properties(this);
+
+ spa_hook_list_join(&this->hooks, &save);
+
+ return 0;
+}
+
+static struct item *find_item(struct pw_array *storage, uint32_t subject, const char *key)
+{
+ struct item *item;
+
+ pw_array_for_each(item, storage) {
+ if (item->subject == subject && (key == NULL || spa_streq(item->key, key)))
+ return item;
+ }
+ return NULL;
+}
+
+static int clear_subjects(struct metadata *this, struct pw_array *storage, uint32_t subject)
+{
+ struct item *item;
+ uint32_t removed = 0;
+
+ while (true) {
+ item = find_item(storage, subject, NULL);
+ if (item == NULL)
+ break;
+
+ pw_log_debug("%p: remove id:%d key:%s", this, subject, item->key);
+
+ clear_item(item);
+ pw_array_remove(storage, item);
+ removed++;
+ }
+ if (removed > 0)
+ pw_metadata_emit_property(&this->hooks, subject, NULL, NULL, NULL);
+
+ return 0;
+}
+
+static void clear_items(struct metadata *this)
+{
+ struct item *item;
+ struct pw_array tmp;
+
+ /* copy to tmp and reinitialize the storage so that the callbacks
+ * will operate on the new empty metadata. Otherwise, if a callbacks
+ * adds new metadata we just keep on emptying the metadata forever. */
+ tmp = this->storage;
+ pw_array_init(&this->storage, 4096);
+
+ pw_array_consume(item, &tmp)
+ clear_subjects(this, &tmp, item->subject);
+ pw_array_clear(&tmp);
+}
+
+static int impl_set_property(void *object,
+ uint32_t subject,
+ const char *key,
+ const char *type,
+ const char *value)
+{
+ struct metadata *this = object;
+ struct item *item = NULL;
+ int changed = 0;
+
+ pw_log_debug("%p: id:%d key:%s type:%s value:%s", this, subject, key, type, value);
+
+ if (key == NULL)
+ return clear_subjects(this, &this->storage, subject);
+
+ item = find_item(&this->storage, subject, key);
+ if (value == NULL) {
+ if (item != NULL) {
+ clear_item(item);
+ pw_array_remove(&this->storage, item);
+ type = NULL;
+ changed++;
+ pw_log_info("%p: remove id:%d key:%s", this,
+ subject, key);
+ }
+ } else if (item == NULL) {
+ item = pw_array_add(&this->storage, sizeof(*item));
+ if (item == NULL)
+ return -errno;
+ set_item(item, subject, key, type, value);
+ changed++;
+ pw_log_info("%p: add id:%d key:%s type:%s value:%s", this,
+ subject, key, type, value);
+ } else {
+ if (type == NULL)
+ type = item->type;
+ changed = change_item(item, type, value);
+ if (changed)
+ pw_log_info("%p: change id:%d key:%s type:%s value:%s", this,
+ subject, key, type, value);
+ }
+
+ if (changed) {
+ pw_metadata_emit_property(&this->hooks,
+ subject, key, type, value);
+ }
+ return 0;
+}
+
+static int impl_clear(void *object)
+{
+ struct metadata *this = object;
+ clear_items(this);
+ return 0;
+}
+
+static const struct pw_metadata_methods impl_metadata = {
+ PW_VERSION_METADATA_METHODS,
+ .add_listener = impl_add_listener,
+ .set_property = impl_set_property,
+ .clear = impl_clear,
+};
+
+static struct pw_metadata *metadata_init(struct metadata *this)
+{
+ this->iface = SPA_INTERFACE_INIT(
+ PW_TYPE_INTERFACE_Metadata,
+ PW_VERSION_METADATA,
+ &impl_metadata, this);
+ pw_array_init(&this->storage, 4096);
+ spa_hook_list_init(&this->hooks);
+ return (struct pw_metadata*)&this->iface;
+}
+
+static void metadata_reset(struct metadata *this)
+{
+ spa_hook_list_clean(&this->hooks);
+ clear_items(this);
+ pw_array_clear(&this->storage);
+}
+
+struct impl {
+ struct pw_impl_metadata this;
+
+ struct metadata def;
+};
+
+struct resource_data {
+ struct pw_impl_metadata *impl;
+
+ struct pw_resource *resource;
+ struct spa_hook resource_listener;
+ struct spa_hook object_listener;
+ struct spa_hook metadata_listener;
+};
+
+
+static int metadata_property(void *data, uint32_t subject, const char *key,
+ const char *type, const char *value)
+{
+ struct pw_impl_metadata *this = data;
+ pw_impl_metadata_emit_property(this, subject, key, type, value);
+ return 0;
+}
+
+static const struct pw_metadata_events metadata_events = {
+ PW_VERSION_METADATA_EVENTS,
+ .property = metadata_property,
+};
+
+SPA_EXPORT
+struct pw_impl_metadata *pw_context_create_metadata(struct pw_context *context,
+ const char *name, struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct impl *impl;
+ struct pw_impl_metadata *this;
+ int res;
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL)
+ return NULL;
+
+ impl = calloc(1, sizeof(*impl) + user_data_size);
+ if (impl == NULL) {
+ res = -errno;
+ goto error_exit;
+ };
+ this = &impl->this;
+
+ this->context = context;
+ this->properties = properties;
+
+ if (name != NULL)
+ pw_properties_set(properties, PW_KEY_METADATA_NAME, name);
+
+ spa_hook_list_init(&this->listener_list);
+
+ pw_impl_metadata_set_implementation(this, metadata_init(&impl->def));
+
+ if (user_data_size > 0)
+ this->user_data = SPA_PTROFF(this, sizeof(*this), void);
+
+ pw_log_debug("%p: new", this);
+
+ return this;
+
+error_exit:
+ pw_properties_free(properties);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+const struct pw_properties *pw_impl_metadata_get_properties(struct pw_impl_metadata *metadata)
+{
+ return metadata->properties;
+}
+
+SPA_EXPORT
+int pw_impl_metadata_set_implementation(struct pw_impl_metadata *metadata,
+ struct pw_metadata *meta)
+{
+ struct impl *impl = SPA_CONTAINER_OF(metadata, struct impl, this);
+
+ if (metadata->metadata == meta)
+ return 0;
+
+ if (metadata->metadata)
+ spa_hook_remove(&metadata->metadata_listener);
+ if (meta == NULL)
+ meta = (struct pw_metadata*)&impl->def.iface;
+
+ metadata->metadata = meta;
+ pw_metadata_add_listener(meta, &metadata->metadata_listener,
+ &metadata_events, metadata);
+
+ return 0;
+}
+SPA_EXPORT
+struct pw_metadata *pw_impl_metadata_get_implementation(struct pw_impl_metadata *metadata)
+{
+ return metadata->metadata;
+}
+
+SPA_EXPORT
+void pw_impl_metadata_destroy(struct pw_impl_metadata *metadata)
+{
+ struct impl *impl = SPA_CONTAINER_OF(metadata, struct impl, this);
+
+ pw_log_debug("%p: destroy", metadata);
+ pw_impl_metadata_emit_destroy(metadata);
+
+ if (metadata->registered)
+ spa_list_remove(&metadata->link);
+
+ if (metadata->global) {
+ spa_hook_remove(&metadata->global_listener);
+ pw_global_destroy(metadata->global);
+ }
+
+ pw_impl_metadata_emit_free(metadata);
+ pw_log_debug("%p: free", metadata);
+
+ metadata_reset(&impl->def);
+
+ spa_hook_list_clean(&metadata->listener_list);
+
+ pw_properties_free(metadata->properties);
+
+ free(metadata);
+}
+
+#define pw_metadata_resource(r,m,v,...) \
+ pw_resource_call_res(r,struct pw_metadata_events,m,v,__VA_ARGS__)
+
+#define pw_metadata_resource_property(r,...) \
+ pw_metadata_resource(r,property,0,__VA_ARGS__)
+
+static int metadata_resource_property(void *data,
+ uint32_t subject,
+ const char *key,
+ const char *type,
+ const char *value)
+{
+ struct resource_data *d = data;
+ struct pw_resource *resource = d->resource;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+
+ if (pw_impl_client_check_permissions(client, subject, PW_PERM_R) >= 0)
+ pw_metadata_resource_property(d->resource, subject, key, type, value);
+ return 0;
+}
+
+static const struct pw_metadata_events metadata_resource_events = {
+ PW_VERSION_METADATA_EVENTS,
+ .property = metadata_resource_property,
+};
+
+static int metadata_set_property(void *object,
+ uint32_t subject,
+ const char *key,
+ const char *type,
+ const char *value)
+{
+ struct resource_data *d = object;
+ struct pw_impl_metadata *impl = d->impl;
+ struct pw_resource *resource = d->resource;
+ struct pw_impl_client *client = pw_resource_get_client(resource);
+ int res;
+
+ if ((res = pw_impl_client_check_permissions(client, subject, PW_PERM_R)) < 0)
+ goto error;
+
+ pw_metadata_set_property(impl->metadata, subject, key, type, value);
+ return 0;
+
+error:
+ pw_resource_errorf(resource, res, "set property error for id %d: %s",
+ subject, spa_strerror(res));
+ return res;
+}
+
+static int metadata_clear(void *object)
+{
+ struct resource_data *d = object;
+ struct pw_impl_metadata *impl = d->impl;
+ pw_metadata_clear(impl->metadata);
+ return 0;
+}
+
+static const struct pw_metadata_methods metadata_methods = {
+ PW_VERSION_METADATA_METHODS,
+ .set_property = metadata_set_property,
+ .clear = metadata_clear,
+};
+
+static void global_unbind(void *data)
+{
+ struct resource_data *d = data;
+ if (d->resource) {
+ spa_hook_remove(&d->resource_listener);
+ spa_hook_remove(&d->object_listener);
+ spa_hook_remove(&d->metadata_listener);
+ }
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = global_unbind,
+};
+
+static int
+global_bind(void *object, struct pw_impl_client *client, uint32_t permissions,
+ uint32_t version, uint32_t id)
+{
+ struct pw_impl_metadata *this = object;
+ struct pw_global *global = this->global;
+ struct pw_resource *resource;
+ struct resource_data *data;
+
+ resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data));
+ if (resource == NULL)
+ goto error_resource;
+
+ data = pw_resource_get_user_data(resource);
+ data->impl = this;
+ data->resource = resource;
+
+ pw_log_debug("%p: bound to %d", this, resource->id);
+ pw_global_add_resource(global, resource);
+
+ /* listen for when the resource goes away */
+ pw_resource_add_listener(resource,
+ &data->resource_listener,
+ &resource_events, data);
+
+ /* resource methods -> implementation */
+ pw_resource_add_object_listener(resource,
+ &data->object_listener,
+ &metadata_methods, data);
+
+ /* implementation events -> resource */
+ pw_metadata_add_listener(this->metadata,
+ &data->metadata_listener,
+ &metadata_resource_events, data);
+
+ return 0;
+
+error_resource:
+ pw_log_error("%p: can't create metadata resource: %m", this);
+ return -errno;
+}
+
+static void global_destroy(void *data)
+{
+ struct pw_impl_metadata *metadata = data;
+ spa_hook_remove(&metadata->global_listener);
+ metadata->global = NULL;
+ pw_impl_metadata_destroy(metadata);
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+SPA_EXPORT
+int pw_impl_metadata_register(struct pw_impl_metadata *metadata,
+ struct pw_properties *properties)
+{
+ struct pw_context *context = metadata->context;
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ PW_KEY_MODULE_ID,
+ PW_KEY_FACTORY_ID,
+ PW_KEY_METADATA_NAME,
+ NULL
+ };
+
+ if (metadata->registered)
+ goto error_existed;
+
+ metadata->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_Metadata,
+ PW_VERSION_METADATA,
+ properties,
+ global_bind,
+ metadata);
+ if (metadata->global == NULL)
+ return -errno;
+
+ spa_list_append(&context->metadata_list, &metadata->link);
+ metadata->registered = true;
+
+ pw_properties_setf(metadata->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
+ pw_global_get_serial(metadata->global));
+
+ pw_global_update_keys(metadata->global, &metadata->properties->dict, keys);
+
+ pw_global_add_listener(metadata->global, &metadata->global_listener, &global_events, metadata);
+ pw_global_register(metadata->global);
+
+ return 0;
+
+error_existed:
+ pw_properties_free(properties);
+ return -EEXIST;
+}
+
+SPA_EXPORT
+void *pw_impl_metadata_get_user_data(struct pw_impl_metadata *metadata)
+{
+ return metadata->user_data;
+}
+
+SPA_EXPORT
+struct pw_global *pw_impl_metadata_get_global(struct pw_impl_metadata *metadata)
+{
+ return metadata->global;
+}
+
+SPA_EXPORT
+void pw_impl_metadata_add_listener(struct pw_impl_metadata *metadata,
+ struct spa_hook *listener,
+ const struct pw_impl_metadata_events *events,
+ void *data)
+{
+ spa_hook_list_append(&metadata->listener_list, listener, events, data);
+}
+
+SPA_EXPORT
+int pw_impl_metadata_set_property(struct pw_impl_metadata *metadata,
+ uint32_t subject, const char *key, const char *type,
+ const char *value)
+{
+ return pw_metadata_set_property(metadata->metadata, subject, key, type, value);
+}
+
+SPA_EXPORT
+int pw_impl_metadata_set_propertyf(struct pw_impl_metadata *metadata,
+ uint32_t subject, const char *key, const char *type,
+ const char *fmt, ...)
+{
+ va_list args;
+ int n = 0, res;
+ size_t size = 0;
+ char *p = NULL;
+
+ va_start(args, fmt);
+ n = vsnprintf(p, size, fmt, args);
+ va_end(args);
+ if (n < 0)
+ return -errno;
+
+ size = (size_t) n + 1;
+ p = malloc(size);
+ if (p == NULL)
+ return -errno;
+
+ va_start(args, fmt);
+ n = vsnprintf(p, size, fmt, args);
+ va_end(args);
+
+ if (n < 0) {
+ free(p);
+ return -errno;
+ }
+ res = pw_impl_metadata_set_property(metadata, subject, key, type, p);
+ free(p);
+
+ return res;
+}
diff --git a/src/pipewire/impl-metadata.h b/src/pipewire/impl-metadata.h
new file mode 100644
index 0000000..4b81cac
--- /dev/null
+++ b/src/pipewire/impl-metadata.h
@@ -0,0 +1,114 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_METADATA_H
+#define PIPEWIRE_IMPL_METADATA_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \defgroup pw_impl_metadata Metadata Impl
+ *
+ * The metadata is used to store key/type/value pairs per object id.
+ */
+
+/**
+ * \addtogroup pw_impl_metadata
+ * \{
+ */
+struct pw_impl_metadata;
+
+#include <pipewire/context.h>
+#include <pipewire/impl-client.h>
+#include <pipewire/global.h>
+#include <pipewire/properties.h>
+#include <pipewire/resource.h>
+
+#include "pipewire/extensions/metadata.h"
+
+/** Metadata events, listen to them with \ref pw_impl_metadata_add_listener */
+struct pw_impl_metadata_events {
+#define PW_VERSION_IMPL_METADATA_EVENTS 0
+ uint32_t version;
+
+ /** the metadata is destroyed */
+ void (*destroy) (void *data);
+ /** the metadata is freed */
+ void (*free) (void *data);
+
+ /** a property changed */
+ int (*property) (void *data,
+ uint32_t subject,
+ const char *key,
+ const char *type,
+ const char *value);
+};
+
+struct pw_impl_metadata *pw_context_create_metadata(struct pw_context *context,
+ const char *name, struct pw_properties *properties,
+ size_t user_data_size);
+
+/** Get the metadata properties */
+const struct pw_properties *pw_impl_metadata_get_properties(struct pw_impl_metadata *metadata);
+
+int pw_impl_metadata_register(struct pw_impl_metadata *metadata,
+ struct pw_properties *properties);
+
+void pw_impl_metadata_destroy(struct pw_impl_metadata *metadata);
+
+void *pw_impl_metadata_get_user_data(struct pw_impl_metadata *metadata);
+
+int pw_impl_metadata_set_implementation(struct pw_impl_metadata *metadata,
+ struct pw_metadata *impl);
+
+struct pw_metadata *pw_impl_metadata_get_implementation(struct pw_impl_metadata *metadata);
+
+/** Get the global of this metadata */
+struct pw_global *pw_impl_metadata_get_global(struct pw_impl_metadata *metadata);
+
+/** Add an event listener */
+void pw_impl_metadata_add_listener(struct pw_impl_metadata *metadata,
+ struct spa_hook *listener,
+ const struct pw_impl_metadata_events *events,
+ void *data);
+
+/** Set a property */
+int pw_impl_metadata_set_property(struct pw_impl_metadata *metadata,
+ uint32_t subject, const char *key, const char *type,
+ const char *value);
+
+int pw_impl_metadata_set_propertyf(struct pw_impl_metadata *metadata,
+ uint32_t subject, const char *key, const char *type,
+ const char *fmt, ...) SPA_PRINTF_FUNC(5,6);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_METADATA_H */
diff --git a/src/pipewire/impl-module.c b/src/pipewire/impl-module.c
new file mode 100644
index 0000000..282ba08
--- /dev/null
+++ b/src/pipewire/impl-module.c
@@ -0,0 +1,427 @@
+/* PipeWire
+ * Copyright © 2016 Axis Communications <dev-gstreamer@axis.com>
+ * @author Linus Svensson <linus.svensson@axis.com>
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <dlfcn.h>
+#include <dirent.h>
+#include <limits.h>
+#include <sys/stat.h>
+#include <errno.h>
+
+#include <spa/utils/string.h>
+
+#include "pipewire/impl.h"
+#include "pipewire/private.h"
+
+PW_LOG_TOPIC_EXTERN(log_module);
+#define PW_LOG_TOPIC_DEFAULT log_module
+
+/** \cond */
+struct impl {
+ struct pw_impl_module this;
+ void *hnd;
+ uint32_t destroy_work_id;
+};
+
+#define pw_module_resource_info(r,...) pw_resource_call(r,struct pw_module_events,info,0,__VA_ARGS__)
+
+
+/** \endcond */
+
+static char *find_module(const char *path, const char *name, int level)
+{
+ char *filename;
+ struct dirent *entry;
+ struct stat s;
+ DIR *dir;
+ int res;
+
+ filename = spa_aprintf("%s/%s.so", path, name);
+ if (filename == NULL)
+ return NULL;
+
+ if (stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
+ /* found a regular file with name */
+ return filename;
+ }
+
+ free(filename);
+ filename = NULL;
+
+ /* now recurse down in subdirectories and look for it there */
+ if (level <= 0)
+ return NULL;
+
+ dir = opendir(path);
+ if (dir == NULL) {
+ res = -errno;
+ pw_log_warn("could not open %s: %m", path);
+ errno = -res;
+ return NULL;
+ }
+
+ while ((entry = readdir(dir))) {
+ char *newpath;
+
+ if (spa_streq(entry->d_name, ".") || spa_streq(entry->d_name, ".."))
+ continue;
+
+ newpath = spa_aprintf("%s/%s", path, entry->d_name);
+ if (newpath == NULL)
+ break;
+
+ if (stat(newpath, &s) == 0 && S_ISDIR(s.st_mode))
+ filename = find_module(newpath, name, level - 1);
+
+ free(newpath);
+
+ if (filename != NULL)
+ break;
+ }
+
+ closedir(dir);
+
+ return filename;
+}
+
+static int
+global_bind(void *object, struct pw_impl_client *client, uint32_t permissions,
+ uint32_t version, uint32_t id)
+{
+ struct pw_impl_module *this = object;
+ struct pw_global *global = this->global;
+ struct pw_resource *resource;
+
+ resource = pw_resource_new(client, id, permissions, global->type, version, 0);
+ if (resource == NULL)
+ goto error_resource;
+
+ pw_log_debug("%p: bound to %d", this, resource->id);
+ pw_global_add_resource(global, resource);
+
+ this->info.change_mask = PW_MODULE_CHANGE_MASK_ALL;
+ pw_module_resource_info(resource, &this->info);
+ this->info.change_mask = 0;
+
+ return 0;
+
+error_resource:
+ pw_log_error("%p: can't create module resource: %m", this);
+ return -errno;
+}
+
+static void global_destroy(void *data)
+{
+ struct pw_impl_module *module = data;
+ spa_hook_remove(&module->global_listener);
+ module->global = NULL;
+ pw_impl_module_destroy(module);
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+/** Load a module
+ *
+ * \param context a \ref pw_context
+ * \param name name of the module to load
+ * \param args A string with arguments for the module
+ * \param properties extra global properties
+ * \return A \ref pw_impl_module if the module could be loaded, or NULL on failure.
+ *
+ */
+SPA_EXPORT
+struct pw_impl_module *
+pw_context_load_module(struct pw_context *context,
+ const char *name, const char *args,
+ struct pw_properties *properties)
+{
+ struct pw_impl_module *this;
+ struct impl *impl;
+ void *hnd;
+ char *filename = NULL;
+ const char *module_dir;
+ int res;
+ pw_impl_module_init_func_t init_func;
+ const char *state = NULL, *p;
+ size_t len;
+ char path_part[PATH_MAX];
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ PW_KEY_MODULE_NAME,
+ NULL
+ };
+
+ pw_log_info("%p: name:%s args:%s", context, name, args);
+
+ module_dir = getenv("PIPEWIRE_MODULE_DIR");
+ if (module_dir == NULL) {
+ module_dir = MODULEDIR;
+ pw_log_debug("moduledir set to: %s", module_dir);
+ }
+ else {
+ pw_log_debug("PIPEWIRE_MODULE_DIR set to: %s", module_dir);
+ }
+
+ while ((p = pw_split_walk(module_dir, ":", &len, &state))) {
+ if ((res = spa_scnprintf(path_part, sizeof(path_part), "%.*s", (int)len, p)) > 0) {
+ filename = find_module(path_part, name, 8);
+ if (filename != NULL) {
+ pw_log_debug("trying to load module: %s (%s) args(%s)", name, filename, args);
+
+ hnd = dlopen(filename, RTLD_NOW | RTLD_LOCAL);
+ if (hnd != NULL)
+ break;
+
+ pw_log_debug("open failed: %s", dlerror());
+ free(filename);
+ filename = NULL;
+ }
+ }
+ }
+
+ if (filename == NULL)
+ goto error_not_found;
+ if (hnd == NULL)
+ goto error_open_failed;
+
+ if ((init_func = dlsym(hnd, PIPEWIRE_SYMBOL_MODULE_INIT)) == NULL)
+ goto error_no_pw_module;
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL)
+ goto error_no_mem;
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL)
+ goto error_no_mem;
+
+ impl->hnd = hnd;
+ impl->destroy_work_id = SPA_ID_INVALID;
+ hnd = NULL;
+
+ this = &impl->this;
+ this->context = context;
+ this->properties = properties;
+ properties = NULL;
+
+ spa_hook_list_init(&this->listener_list);
+
+ pw_properties_set(this->properties, PW_KEY_MODULE_NAME, name);
+
+ this->info.name = name ? strdup(name) : NULL;
+ this->info.filename = filename;
+ filename = NULL;
+ this->info.args = args ? strdup(args) : NULL;
+
+ spa_list_prepend(&context->module_list, &this->link);
+
+ this->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_Module,
+ PW_VERSION_MODULE,
+ NULL,
+ global_bind,
+ this);
+
+ if (this->global == NULL)
+ goto error_no_global;
+
+ this->info.id = this->global->id;
+ pw_properties_setf(this->properties, PW_KEY_OBJECT_ID, "%d", this->info.id);
+ pw_properties_setf(this->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
+ pw_global_get_serial(this->global));
+ this->info.props = &this->properties->dict;
+
+ pw_global_update_keys(this->global, &this->properties->dict, keys);
+
+ pw_impl_module_emit_initialized(this);
+
+ pw_global_add_listener(this->global, &this->global_listener, &global_events, this);
+
+ if ((res = init_func(this, args)) < 0)
+ goto error_init_failed;
+
+ pw_global_register(this->global);
+
+ pw_impl_module_emit_registered(this);
+
+ pw_log_debug("%p: loaded module: %s", this, this->info.name);
+
+ return this;
+
+error_not_found:
+ res = -ENOENT;
+ pw_log_info("No module \"%s\" was found", name);
+ goto error_cleanup;
+error_open_failed:
+ res = -EIO;
+ pw_log_error("Failed to open module: \"%s\" %s", filename, dlerror());
+ goto error_free_filename;
+error_no_pw_module:
+ res = -ENOSYS;
+ pw_log_error("\"%s\": is not a pipewire module", filename);
+ goto error_close;
+error_no_mem:
+ res = -errno;
+ pw_log_error("can't allocate module: %m");
+ goto error_close;
+error_no_global:
+ res = -errno;
+ pw_log_error("\"%s\": failed to create global: %m", this->info.filename);
+ goto error_free_module;
+error_init_failed:
+ pw_log_debug("\"%s\": failed to initialize: %s", this->info.filename, spa_strerror(res));
+ goto error_free_module;
+
+error_free_module:
+ pw_impl_module_destroy(this);
+error_close:
+ if (hnd)
+ dlclose(hnd);
+error_free_filename:
+ if (filename)
+ free(filename);
+error_cleanup:
+ pw_properties_free(properties);
+ errno = -res;
+ return NULL;
+}
+
+/** Destroy a module
+ * \param module the module to destroy
+ */
+SPA_EXPORT
+void pw_impl_module_destroy(struct pw_impl_module *module)
+{
+ struct impl *impl = SPA_CONTAINER_OF(module, struct impl, this);
+
+ pw_log_debug("%p: destroy %s", module, module->info.name);
+ pw_impl_module_emit_destroy(module);
+
+ spa_list_remove(&module->link);
+
+ if (module->global) {
+ spa_hook_remove(&module->global_listener);
+ pw_global_destroy(module->global);
+ }
+
+ pw_log_debug("%p: free", module);
+ pw_impl_module_emit_free(module);
+ free((char *) module->info.name);
+ free((char *) module->info.filename);
+ free((char *) module->info.args);
+
+ pw_properties_free(module->properties);
+
+ spa_hook_list_clean(&module->listener_list);
+
+ if (impl->destroy_work_id != SPA_ID_INVALID)
+ pw_work_queue_cancel(pw_context_get_work_queue(module->context),
+ module, SPA_ID_INVALID);
+
+ if (!pw_in_valgrind() && dlclose(impl->hnd) != 0)
+ pw_log_warn("%p: dlclose failed: %s", module, dlerror());
+ free(impl);
+}
+
+SPA_EXPORT
+struct pw_context *
+pw_impl_module_get_context(struct pw_impl_module *module)
+{
+ return module->context;
+}
+
+SPA_EXPORT
+struct pw_global * pw_impl_module_get_global(struct pw_impl_module *module)
+{
+ return module->global;
+}
+
+SPA_EXPORT
+const struct pw_properties *pw_impl_module_get_properties(struct pw_impl_module *module)
+{
+ return module->properties;
+}
+
+SPA_EXPORT
+int pw_impl_module_update_properties(struct pw_impl_module *module, const struct spa_dict *dict)
+{
+ struct pw_resource *resource;
+ int changed;
+
+ changed = pw_properties_update(module->properties, dict);
+ module->info.props = &module->properties->dict;
+
+ pw_log_debug("%p: updated %d properties", module, changed);
+
+ if (!changed)
+ return 0;
+
+ module->info.change_mask |= PW_MODULE_CHANGE_MASK_PROPS;
+ if (module->global)
+ spa_list_for_each(resource, &module->global->resource_list, link)
+ pw_module_resource_info(resource, &module->info);
+ module->info.change_mask = 0;
+
+ return changed;
+}
+
+SPA_EXPORT
+const struct pw_module_info *
+pw_impl_module_get_info(struct pw_impl_module *module)
+{
+ return &module->info;
+}
+
+SPA_EXPORT
+void pw_impl_module_add_listener(struct pw_impl_module *module,
+ struct spa_hook *listener,
+ const struct pw_impl_module_events *events,
+ void *data)
+{
+ spa_hook_list_append(&module->listener_list, listener, events, data);
+}
+
+static void do_destroy_module(void *obj, void *data, int res, uint32_t id)
+{
+ pw_impl_module_destroy(obj);
+}
+
+SPA_EXPORT
+void pw_impl_module_schedule_destroy(struct pw_impl_module *module)
+{
+ struct impl *impl = SPA_CONTAINER_OF(module, struct impl, this);
+
+ if (impl->destroy_work_id != SPA_ID_INVALID)
+ return;
+
+ impl->destroy_work_id = pw_work_queue_add(pw_context_get_work_queue(module->context),
+ module, 0, do_destroy_module, NULL);
+}
diff --git a/src/pipewire/impl-module.h b/src/pipewire/impl-module.h
new file mode 100644
index 0000000..0135498
--- /dev/null
+++ b/src/pipewire/impl-module.h
@@ -0,0 +1,120 @@
+/* PipeWire
+ * Copyright © 2016 Axis Communications <dev-gstreamer@axis.com>
+ * @author Linus Svensson <linus.svensson@axis.com>
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_MODULE_H
+#define PIPEWIRE_IMPL_MODULE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/hook.h>
+
+#include <pipewire/context.h>
+
+#define PIPEWIRE_SYMBOL_MODULE_INIT "pipewire__module_init"
+#define PIPEWIRE_MODULE_PREFIX "libpipewire-"
+
+/** \defgroup pw_impl_module Module Impl
+ *
+ * A dynamically loadable module
+ */
+
+/**
+ * \addtogroup pw_impl_module
+ * \{
+ */
+struct pw_impl_module;
+
+/** Module init function signature
+ *
+ * \param module A \ref pw_impl_module
+ * \param args Arguments to the module
+ * \return 0 on success, < 0 otherwise with an errno style error
+ *
+ * A module should provide an init function with this signature. This function
+ * will be called when a module is loaded.
+ */
+typedef int (*pw_impl_module_init_func_t) (struct pw_impl_module *module, const char *args);
+
+/** Module events added with \ref pw_impl_module_add_listener */
+struct pw_impl_module_events {
+#define PW_VERSION_IMPL_MODULE_EVENTS 0
+ uint32_t version;
+
+ /** The module is destroyed */
+ void (*destroy) (void *data);
+ /** The module is freed */
+ void (*free) (void *data);
+ /** The module is initialized */
+ void (*initialized) (void *data);
+
+ /** The module is registered. This is a good time to register
+ * objects created from the module. */
+ void (*registered) (void *data);
+};
+
+struct pw_impl_module *
+pw_context_load_module(struct pw_context *context,
+ const char *name,
+ const char *args,
+ struct pw_properties *properties);
+
+/** Get the context of a module */
+struct pw_context * pw_impl_module_get_context(struct pw_impl_module *module);
+
+/** Get the global of a module */
+struct pw_global * pw_impl_module_get_global(struct pw_impl_module *module);
+
+/** Get the module properties */
+const struct pw_properties *pw_impl_module_get_properties(struct pw_impl_module *module);
+
+/** Update the module properties */
+int pw_impl_module_update_properties(struct pw_impl_module *module, const struct spa_dict *dict);
+
+/** Get the module info */
+const struct pw_module_info *pw_impl_module_get_info(struct pw_impl_module *module);
+
+/** Add an event listener to a module */
+void pw_impl_module_add_listener(struct pw_impl_module *module,
+ struct spa_hook *listener,
+ const struct pw_impl_module_events *events,
+ void *data);
+
+/** Destroy a module */
+void pw_impl_module_destroy(struct pw_impl_module *module);
+
+/** Schedule a destroy later on the main thread */
+void pw_impl_module_schedule_destroy(struct pw_impl_module *module);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_MODULE_H */
diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c
new file mode 100644
index 0000000..6200e80
--- /dev/null
+++ b/src/pipewire/impl-node.c
@@ -0,0 +1,2316 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <time.h>
+
+#include <spa/support/system.h>
+#include <spa/pod/parser.h>
+#include <spa/pod/filter.h>
+#include <spa/pod/dynamic.h>
+#include <spa/node/utils.h>
+#include <spa/debug/types.h>
+#include <spa/utils/string.h>
+
+#include "pipewire/impl-node.h"
+#include "pipewire/private.h"
+
+PW_LOG_TOPIC_EXTERN(log_node);
+#define PW_LOG_TOPIC_DEFAULT log_node
+
+#define DEFAULT_SYNC_TIMEOUT ((uint64_t)(5 * SPA_NSEC_PER_SEC))
+
+/** \cond */
+struct impl {
+ struct pw_impl_node this;
+
+ enum pw_node_state pending_state;
+ uint32_t pending_id;
+
+ struct pw_work_queue *work;
+
+ int last_error;
+
+ struct spa_list param_list;
+ struct spa_list pending_list;
+
+ unsigned int cache_params:1;
+ unsigned int pending_play:1;
+};
+
+#define pw_node_resource(r,m,v,...) pw_resource_call(r,struct pw_node_events,m,v,__VA_ARGS__)
+#define pw_node_resource_info(r,...) pw_node_resource(r,info,0,__VA_ARGS__)
+#define pw_node_resource_param(r,...) pw_node_resource(r,param,0,__VA_ARGS__)
+
+struct resource_data {
+ struct pw_impl_node *node;
+
+ struct pw_resource *resource;
+ struct spa_hook resource_listener;
+ struct spa_hook object_listener;
+
+ uint32_t subscribe_ids[MAX_PARAMS];
+ uint32_t n_subscribe_ids;
+
+ /* for async replies */
+ int seq;
+ int end;
+ struct spa_hook listener;
+};
+
+/** \endcond */
+
+static void add_node(struct pw_impl_node *this, struct pw_impl_node *driver)
+{
+ struct pw_node_activation_state *dstate, *nstate;
+ struct pw_node_target *t;
+
+ if (this->exported)
+ return;
+
+ pw_log_trace("%p: add to driver %p %p %p", this, driver,
+ driver->rt.activation, this->rt.activation);
+
+ /* signal the driver */
+ this->rt.driver_target.activation = driver->rt.activation;
+ this->rt.driver_target.node = driver;
+ this->rt.driver_target.data = driver;
+ spa_list_append(&this->rt.target_list, &this->rt.driver_target.link);
+
+ spa_list_append(&driver->rt.target_list, &this->rt.target.link);
+ nstate = &this->rt.activation->state[0];
+ if (!this->rt.target.active) {
+ nstate->required++;
+ this->rt.target.active = true;
+ }
+
+ spa_list_for_each(t, &this->rt.target_list, link) {
+ dstate = &t->activation->state[0];
+ if (!t->active) {
+ dstate->required++;
+ t->active = true;
+ }
+ pw_log_trace("%p: driver state:%p pending:%d/%d, node state:%p pending:%d/%d",
+ this, dstate, dstate->pending, dstate->required,
+ nstate, nstate->pending, nstate->required);
+ }
+}
+
+static void remove_node(struct pw_impl_node *this)
+{
+ struct pw_node_activation_state *dstate, *nstate;
+ struct pw_node_target *t;
+
+ if (this->exported)
+ return;
+
+ pw_log_trace("%p: remove from driver %p %p %p",
+ this, this->rt.driver_target.data,
+ this->rt.driver_target.activation, this->rt.activation);
+
+ spa_list_remove(&this->rt.target.link);
+
+ nstate = &this->rt.activation->state[0];
+ if (this->rt.target.active) {
+ nstate->required--;
+ this->rt.target.active = false;
+ }
+
+ spa_list_for_each(t, &this->rt.target_list, link) {
+ dstate = &t->activation->state[0];
+ if (t->active) {
+ dstate->required--;
+ t->active = false;
+ }
+ pw_log_trace("%p: driver state:%p pending:%d/%d, node state:%p pending:%d/%d",
+ this, dstate, dstate->pending, dstate->required,
+ nstate, nstate->pending, nstate->required);
+ }
+ spa_list_remove(&this->rt.driver_target.link);
+
+ this->rt.driver_target.node = NULL;
+}
+
+static int
+do_node_add(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct pw_impl_node *this = user_data;
+ struct pw_impl_node *driver = this->driver_node;
+
+ this->added = true;
+ if (this->source.loop == NULL) {
+ struct spa_system *data_system = this->context->data_system;
+ uint64_t dummy;
+ int res;
+
+ /* clear the eventfd in case it was written to while the node was stopped */
+ res = spa_system_eventfd_read(data_system, this->source.fd, &dummy);
+ if (SPA_UNLIKELY(res != -EAGAIN && res != 0))
+ pw_log_warn("%p: read failed %m", this);
+
+ spa_loop_add_source(loop, &this->source);
+ add_node(this, driver);
+ }
+ return 0;
+}
+
+static int
+do_node_remove(struct spa_loop *loop, bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct pw_impl_node *this = user_data;
+ if (this->source.loop != NULL) {
+ spa_loop_remove_source(loop, &this->source);
+ remove_node(this);
+ }
+ this->added = false;
+ return 0;
+}
+
+static void node_deactivate(struct pw_impl_node *this)
+{
+ struct pw_impl_port *port;
+ struct pw_impl_link *link;
+
+ pw_log_debug("%p: deactivate", this);
+
+ /* make sure the node doesn't get woken up while not active */
+ pw_loop_invoke(this->data_loop, do_node_remove, 1, NULL, 0, true, this);
+
+ spa_list_for_each(port, &this->input_ports, link) {
+ spa_list_for_each(link, &port->links, input_link)
+ pw_impl_link_deactivate(link);
+ }
+ spa_list_for_each(port, &this->output_ports, link) {
+ spa_list_for_each(link, &port->links, output_link)
+ pw_impl_link_deactivate(link);
+ }
+}
+
+static int idle_node(struct pw_impl_node *this)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ int res = 0;
+
+ pw_log_debug("%p: idle node state:%s pending:%s pause-on-idle:%d", this,
+ pw_node_state_as_string(this->info.state),
+ pw_node_state_as_string(impl->pending_state),
+ this->pause_on_idle);
+
+ if (impl->pending_state <= PW_NODE_STATE_IDLE)
+ return 0;
+
+ if (!this->pause_on_idle)
+ return 0;
+
+ node_deactivate(this);
+
+ res = spa_node_send_command(this->node,
+ &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause));
+ if (res < 0)
+ pw_log_debug("%p: pause node error %s", this, spa_strerror(res));
+
+ return res;
+}
+
+static void node_activate(struct pw_impl_node *this)
+{
+ struct pw_impl_port *port;
+
+ pw_log_debug("%p: activate", this);
+ spa_list_for_each(port, &this->output_ports, link) {
+ struct pw_impl_link *link;
+ spa_list_for_each(link, &port->links, output_link)
+ pw_impl_link_activate(link);
+ }
+ spa_list_for_each(port, &this->input_ports, link) {
+ struct pw_impl_link *link;
+ spa_list_for_each(link, &port->links, input_link)
+ pw_impl_link_activate(link);
+ }
+}
+
+static int start_node(struct pw_impl_node *this)
+{
+ struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
+ int res = 0;
+
+ node_activate(this);
+
+ if (impl->pending_state >= PW_NODE_STATE_RUNNING)
+ return 0;
+
+ pw_log_debug("%p: start node driving:%d driver:%d added:%d", this,
+ this->driving, this->driver, this->added);
+
+ if (!(this->driving && this->driver)) {
+ impl->pending_play = true;
+ res = spa_node_send_command(this->node,
+ &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start));
+ } else {
+ /* driver nodes will wait until all other nodes are started before
+ * they are started */
+ res = EBUSY;
+ }
+
+ if (res < 0)
+ pw_log_error("(%s-%u) start node error %d: %s", this->name, this->info.id,
+ res, spa_strerror(res));
+
+ return res;
+}
+
+static void emit_info_changed(struct pw_impl_node *node, bool flags_changed)
+{
+ if (node->info.change_mask == 0 && !flags_changed)
+ return;
+
+ pw_impl_node_emit_info_changed(node, &node->info);
+
+ if (node->global && node->info.change_mask != 0) {
+ struct pw_resource *resource;
+ spa_list_for_each(resource, &node->global->resource_list, link)
+ pw_node_resource_info(resource, &node->info);
+ }
+
+ node->info.change_mask = 0;
+}
+
+static int resource_is_subscribed(struct pw_resource *resource, uint32_t id)
+{
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ uint32_t i;
+
+ for (i = 0; i < data->n_subscribe_ids; i++) {
+ if (data->subscribe_ids[i] == id)
+ return 1;
+ }
+ return 0;
+}
+
+static int notify_param(void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next, struct spa_pod *param)
+{
+ struct pw_impl_node *node = data;
+ struct pw_resource *resource;
+
+ spa_list_for_each(resource, &node->global->resource_list, link) {
+ if (!resource_is_subscribed(resource, id))
+ continue;
+
+ pw_log_debug("%p: resource %p notify param %d", node, resource, id);
+ pw_node_resource_param(resource, seq, id, index, next, param);
+ }
+ return 0;
+}
+
+static void emit_params(struct pw_impl_node *node, uint32_t *changed_ids, uint32_t n_changed_ids)
+{
+ uint32_t i;
+ int res;
+
+ if (node->global == NULL)
+ return;
+
+ pw_log_debug("%p: emit %d params", node, n_changed_ids);
+
+ for (i = 0; i < n_changed_ids; i++) {
+ struct pw_resource *resource;
+ int subscribed = 0;
+
+ /* first check if anyone is subscribed */
+ spa_list_for_each(resource, &node->global->resource_list, link) {
+ if ((subscribed = resource_is_subscribed(resource, changed_ids[i])))
+ break;
+ }
+ if (!subscribed)
+ continue;
+
+ if ((res = pw_impl_node_for_each_param(node, 1, changed_ids[i], 0, UINT32_MAX,
+ NULL, notify_param, node)) < 0) {
+ pw_log_error("%p: error %d (%s)", node, res, spa_strerror(res));
+ }
+ }
+}
+
+static void node_update_state(struct pw_impl_node *node, enum pw_node_state state, int res, char *error)
+{
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+ enum pw_node_state old = node->info.state;
+
+ switch (state) {
+ case PW_NODE_STATE_RUNNING:
+ pw_log_debug("%p: start node driving:%d driver:%d added:%d", node,
+ node->driving, node->driver, node->added);
+
+ if (res >= 0) {
+ pw_loop_invoke(node->data_loop, do_node_add, 1, NULL, 0, true, node);
+ }
+ if (node->driving && node->driver) {
+ res = spa_node_send_command(node->node,
+ &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start));
+ if (res < 0) {
+ state = PW_NODE_STATE_ERROR;
+ error = spa_aprintf("Start error: %s", spa_strerror(res));
+ pw_loop_invoke(node->data_loop, do_node_remove, 1, NULL, 0, true, node);
+ }
+ }
+ break;
+ case PW_NODE_STATE_IDLE:
+ case PW_NODE_STATE_SUSPENDED:
+ case PW_NODE_STATE_ERROR:
+ if (state != PW_NODE_STATE_IDLE || node->pause_on_idle)
+ pw_loop_invoke(node->data_loop, do_node_remove, 1, NULL, 0, true, node);
+ break;
+ default:
+ break;
+ }
+
+ free((char*)node->info.error);
+ node->info.error = error;
+ node->info.state = state;
+ impl->pending_state = state;
+
+ pw_log_debug("%p: (%s) %s -> %s (%s)", node, node->name,
+ pw_node_state_as_string(old), pw_node_state_as_string(state), error);
+
+ if (old == state)
+ return;
+
+ if (state == PW_NODE_STATE_ERROR) {
+ pw_log_error("(%s-%u) %s -> error (%s)", node->name, node->info.id,
+ pw_node_state_as_string(old), error);
+ } else {
+ pw_log_info("(%s-%u) %s -> %s", node->name, node->info.id,
+ pw_node_state_as_string(old), pw_node_state_as_string(state));
+ }
+ pw_impl_node_emit_state_changed(node, old, state, error);
+
+ node->info.change_mask |= PW_NODE_CHANGE_MASK_STATE;
+ emit_info_changed(node, false);
+
+ if (state == PW_NODE_STATE_ERROR && node->global) {
+ struct pw_resource *resource;
+ spa_list_for_each(resource, &node->global->resource_list, link)
+ pw_resource_error(resource, res, error);
+ }
+ if (node->reconfigure) {
+ if (state == PW_NODE_STATE_SUSPENDED &&
+ node->pause_on_idle) {
+ node->reconfigure = false;
+ }
+ if (state == PW_NODE_STATE_RUNNING)
+ node->reconfigure = false;
+ }
+ if (old == PW_NODE_STATE_RUNNING &&
+ state == PW_NODE_STATE_IDLE &&
+ node->suspend_on_idle) {
+ pw_impl_node_set_state(node, PW_NODE_STATE_SUSPENDED);
+ }
+}
+
+static int suspend_node(struct pw_impl_node *this)
+{
+ int res = 0;
+ struct pw_impl_port *p;
+
+ pw_log_debug("%p: suspend node state:%s", this,
+ pw_node_state_as_string(this->info.state));
+
+ if (this->info.state > 0 && this->info.state <= PW_NODE_STATE_SUSPENDED)
+ return 0;
+
+ node_deactivate(this);
+
+ spa_list_for_each(p, &this->input_ports, link) {
+ if ((res = pw_impl_port_set_param(p, SPA_PARAM_Format, 0, NULL)) < 0)
+ pw_log_warn("%p: error unset format input: %s",
+ this, spa_strerror(res));
+ /* force CONFIGURE in case of async */
+ p->state = PW_IMPL_PORT_STATE_CONFIGURE;
+ }
+
+ spa_list_for_each(p, &this->output_ports, link) {
+ if ((res = pw_impl_port_set_param(p, SPA_PARAM_Format, 0, NULL)) < 0)
+ pw_log_warn("%p: error unset format output: %s",
+ this, spa_strerror(res));
+ /* force CONFIGURE in case of async */
+ p->state = PW_IMPL_PORT_STATE_CONFIGURE;
+ }
+
+ pw_log_debug("%p: suspend node driving:%d driver:%d added:%d", this,
+ this->driving, this->driver, this->added);
+
+ res = spa_node_send_command(this->node,
+ &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Suspend));
+ if (res == -ENOTSUP)
+ res = spa_node_send_command(this->node,
+ &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Pause));
+ if (res < 0 && res != -EIO)
+ pw_log_warn("%p: suspend node error %s", this, spa_strerror(res));
+
+ node_update_state(this, PW_NODE_STATE_SUSPENDED, 0, NULL);
+
+ return res;
+}
+
+static void
+clear_info(struct pw_impl_node *this)
+{
+ free(this->name);
+ free((char*)this->info.error);
+}
+
+static int reply_param(void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next, struct spa_pod *param)
+{
+ struct resource_data *d = data;
+ pw_log_debug("%p: resource %p reply param %d", d->node, d->resource, seq);
+ pw_node_resource_param(d->resource, seq, id, index, next, param);
+ return 0;
+}
+
+static int node_enum_params(void *object, int seq, uint32_t id,
+ uint32_t index, uint32_t num, const struct spa_pod *filter)
+{
+ struct resource_data *data = object;
+ struct pw_resource *resource = data->resource;
+ struct pw_impl_node *node = data->node;
+ int res;
+
+ pw_log_debug("%p: resource %p enum params seq:%d id:%d (%s) index:%u num:%u",
+ node, resource, seq, id,
+ spa_debug_type_find_name(spa_type_param, id), index, num);
+
+ if ((res = pw_impl_node_for_each_param(node, seq, id, index, num,
+ filter, reply_param, data)) < 0) {
+ pw_resource_errorf(resource, res,
+ "enum params id:%d (%s) failed", id,
+ spa_debug_type_find_name(spa_type_param, id));
+ }
+ return 0;
+}
+
+static int node_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct resource_data *data = object;
+ struct pw_resource *resource = data->resource;
+ uint32_t i;
+
+ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids));
+ data->n_subscribe_ids = n_ids;
+
+ for (i = 0; i < n_ids; i++) {
+ data->subscribe_ids[i] = ids[i];
+ pw_log_debug("%p: resource %p subscribe param id:%d (%s)",
+ data->node, resource, ids[i],
+ spa_debug_type_find_name(spa_type_param, ids[i]));
+ node_enum_params(data, 1, ids[i], 0, UINT32_MAX, NULL);
+ }
+ return 0;
+}
+
+static void remove_busy_resource(struct resource_data *d)
+{
+ if (d->end != -1) {
+ spa_hook_remove(&d->listener);
+ d->end = -1;
+ pw_impl_client_set_busy(d->resource->client, false);
+ }
+}
+
+static void result_node_sync(void *data, int seq, int res, uint32_t type, const void *result)
+{
+ struct resource_data *d = data;
+
+ pw_log_debug("%p: sync result %d %d (%d/%d)", d->node, res, seq, d->seq, d->end);
+ if (seq == d->end)
+ remove_busy_resource(d);
+}
+
+static int node_set_param(void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct resource_data *data = object;
+ struct pw_resource *resource = data->resource;
+ struct pw_impl_node *node = data->node;
+ struct pw_impl_client *client = resource->client;
+ int res;
+ static const struct spa_node_events node_events = {
+ SPA_VERSION_NODE_EVENTS,
+ .result = result_node_sync,
+ };
+
+ pw_log_debug("%p: resource %p set param id:%d (%s) %08x", node, resource,
+ id, spa_debug_type_find_name(spa_type_param, id), flags);
+
+ res = spa_node_set_param(node->node, id, flags, param);
+
+ if (res < 0) {
+ pw_resource_errorf(resource, res,
+ "set param id:%d (%s) flags:%08x failed", id,
+ spa_debug_type_find_name(spa_type_param, id), flags);
+
+ } else if (SPA_RESULT_IS_ASYNC(res)) {
+ pw_impl_client_set_busy(client, true);
+ if (data->end == -1)
+ spa_node_add_listener(node->node, &data->listener,
+ &node_events, data);
+ data->seq = res;
+ data->end = spa_node_sync(node->node, res);
+ }
+ return 0;
+}
+
+static int node_send_command(void *object, const struct spa_command *command)
+{
+ struct resource_data *data = object;
+ struct pw_impl_node *node = data->node;
+
+ switch (SPA_NODE_COMMAND_ID(command)) {
+ case SPA_NODE_COMMAND_Suspend:
+ suspend_node(node);
+ break;
+ default:
+ spa_node_send_command(node->node, command);
+ break;
+ }
+ return 0;
+}
+
+static const struct pw_node_methods node_methods = {
+ PW_VERSION_NODE_METHODS,
+ .subscribe_params = node_subscribe_params,
+ .enum_params = node_enum_params,
+ .set_param = node_set_param,
+ .send_command = node_send_command
+};
+
+static void resource_destroy(void *data)
+{
+ struct resource_data *d = data;
+ remove_busy_resource(d);
+ spa_hook_remove(&d->resource_listener);
+ spa_hook_remove(&d->object_listener);
+}
+
+static void resource_pong(void *data, int seq)
+{
+ struct resource_data *d = data;
+ struct pw_resource *resource = d->resource;
+ pw_log_debug("%p: resource %p: got pong %d", d->node,
+ resource, seq);
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = resource_destroy,
+ .pong = resource_pong,
+};
+
+static int
+global_bind(void *object, struct pw_impl_client *client, uint32_t permissions,
+ uint32_t version, uint32_t id)
+{
+ struct pw_impl_node *this = object;
+ struct pw_global *global = this->global;
+ struct pw_resource *resource;
+ struct resource_data *data;
+
+ resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data));
+ if (resource == NULL)
+ goto error_resource;
+
+ data = pw_resource_get_user_data(resource);
+ data->node = this;
+ data->resource = resource;
+ data->end = -1;
+
+ pw_resource_add_listener(resource,
+ &data->resource_listener,
+ &resource_events, data);
+ pw_resource_add_object_listener(resource,
+ &data->object_listener,
+ &node_methods, data);
+
+ pw_log_debug("%p: bound to %d", this, resource->id);
+ pw_global_add_resource(global, resource);
+
+ this->info.change_mask = PW_NODE_CHANGE_MASK_ALL;
+ pw_node_resource_info(resource, &this->info);
+ this->info.change_mask = 0;
+
+ return 0;
+
+error_resource:
+ pw_log_error("%p: can't create node resource: %m", this);
+ return -errno;
+}
+
+static void global_destroy(void *data)
+{
+ struct pw_impl_node *this = data;
+ spa_hook_remove(&this->global_listener);
+ this->global = NULL;
+ pw_impl_node_destroy(this);
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+static inline void insert_driver(struct pw_context *context, struct pw_impl_node *node)
+{
+ struct pw_impl_node *n, *t;
+
+ spa_list_for_each_safe(n, t, &context->driver_list, driver_link) {
+ if (n->priority_driver < node->priority_driver)
+ break;
+ }
+ spa_list_append(&n->driver_link, &node->driver_link);
+}
+
+static void update_io(struct pw_impl_node *node)
+{
+ pw_log_debug("%p: id:%d", node, node->info.id);
+
+ if (spa_node_set_io(node->node,
+ SPA_IO_Position,
+ &node->rt.activation->position,
+ sizeof(struct spa_io_position)) >= 0) {
+ pw_log_debug("%p: set position %p", node, &node->rt.activation->position);
+ node->rt.position = &node->rt.activation->position;
+
+ node->current_rate = node->rt.position->clock.rate;
+ node->current_quantum = node->rt.position->clock.duration;
+ } else if (node->driver) {
+ pw_log_warn("%p: can't set position on driver", node);
+ }
+ if (spa_node_set_io(node->node,
+ SPA_IO_Clock,
+ &node->rt.activation->position.clock,
+ sizeof(struct spa_io_clock)) >= 0) {
+ pw_log_debug("%p: set clock %p", node, &node->rt.activation->position.clock);
+ node->rt.clock = &node->rt.activation->position.clock;
+ }
+}
+
+SPA_EXPORT
+int pw_impl_node_register(struct pw_impl_node *this,
+ struct pw_properties *properties)
+{
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ PW_KEY_OBJECT_PATH,
+ PW_KEY_MODULE_ID,
+ PW_KEY_FACTORY_ID,
+ PW_KEY_CLIENT_ID,
+ PW_KEY_DEVICE_ID,
+ PW_KEY_PRIORITY_SESSION,
+ PW_KEY_PRIORITY_DRIVER,
+ PW_KEY_APP_NAME,
+ PW_KEY_NODE_DESCRIPTION,
+ PW_KEY_NODE_NAME,
+ PW_KEY_NODE_NICK,
+ PW_KEY_NODE_SESSION,
+ PW_KEY_MEDIA_CLASS,
+ PW_KEY_MEDIA_TYPE,
+ PW_KEY_MEDIA_CATEGORY,
+ PW_KEY_MEDIA_ROLE,
+ NULL
+ };
+
+ struct pw_context *context = this->context;
+ struct pw_impl_port *port;
+
+ pw_log_debug("%p: register", this);
+
+ if (this->registered)
+ goto error_existed;
+
+ this->global = pw_global_new(context,
+ PW_TYPE_INTERFACE_Node,
+ PW_VERSION_NODE,
+ properties,
+ global_bind,
+ this);
+ if (this->global == NULL)
+ return -errno;
+
+ spa_list_append(&context->node_list, &this->link);
+ if (this->driver)
+ insert_driver(context, this);
+ this->registered = true;
+
+ this->rt.activation->position.clock.id = this->global->id;
+
+ this->info.id = this->global->id;
+ pw_properties_setf(this->properties, PW_KEY_OBJECT_ID, "%d", this->info.id);
+ pw_properties_setf(this->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
+ pw_global_get_serial(this->global));
+ this->info.props = &this->properties->dict;
+
+ pw_global_update_keys(this->global, &this->properties->dict, keys);
+
+ pw_impl_node_initialized(this);
+
+ pw_global_add_listener(this->global, &this->global_listener, &global_events, this);
+ pw_global_register(this->global);
+
+ if (this->node)
+ update_io(this);
+
+ spa_list_for_each(port, &this->input_ports, link)
+ pw_impl_port_register(port, NULL);
+ spa_list_for_each(port, &this->output_ports, link)
+ pw_impl_port_register(port, NULL);
+
+ if (this->active)
+ pw_context_recalc_graph(context, "register active node");
+
+ return 0;
+
+error_existed:
+ pw_properties_free(properties);
+ return -EEXIST;
+}
+
+SPA_EXPORT
+int pw_impl_node_initialized(struct pw_impl_node *this)
+{
+ pw_log_debug("%p initialized", this);
+ pw_impl_node_emit_initialized(this);
+ node_update_state(this, PW_NODE_STATE_SUSPENDED, 0, NULL);
+ return 0;
+}
+
+static int
+do_move_nodes(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct impl *impl = user_data;
+ struct pw_impl_node *driver = *(struct pw_impl_node **)data;
+ struct pw_impl_node *node = &impl->this;
+
+ pw_log_trace("%p: driver:%p->%p", node, node->driver_node, driver);
+
+ pw_log_trace("%p: set position %p", node, &driver->rt.activation->position);
+ node->rt.position = &driver->rt.activation->position;
+
+ node->current_rate = node->rt.position->clock.rate;
+ node->current_quantum = node->rt.position->clock.duration;
+
+ if (node->source.loop != NULL) {
+ remove_node(node);
+ add_node(node, driver);
+ }
+ return 0;
+}
+
+static void remove_segment_owner(struct pw_impl_node *driver, uint32_t node_id)
+{
+ struct pw_node_activation *a = driver->rt.activation;
+ ATOMIC_CAS(a->segment_owner[0], node_id, 0);
+ ATOMIC_CAS(a->segment_owner[1], node_id, 0);
+}
+
+SPA_EXPORT
+int pw_impl_node_set_driver(struct pw_impl_node *node, struct pw_impl_node *driver)
+{
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+ struct pw_impl_node *old = node->driver_node;
+ int res;
+ bool was_driving;
+
+ if (driver == NULL)
+ driver = node;
+
+ spa_list_remove(&node->follower_link);
+ spa_list_append(&driver->follower_list, &node->follower_link);
+
+ if (old == driver)
+ return 0;
+
+ remove_segment_owner(old, node->info.id);
+
+ if (old != node && old->driving && driver->info.state < PW_NODE_STATE_RUNNING) {
+ driver->current_rate = old->current_rate;
+ driver->current_quantum = old->current_quantum;
+ driver->current_pending = true;
+ pw_log_info("move quantum:%"PRIu64" rate:%d (%s-%d -> %s-%d)",
+ driver->current_quantum,
+ driver->current_rate.denom,
+ old->name, old->info.id,
+ driver->name, driver->info.id);
+ }
+ was_driving = node->driving;
+ node->driving = node->driver && driver == node;
+
+ /* When a node was driver (and is waiting for all nodes to complete
+ * the Start command) cancel the pending state and let the new driver
+ * calculate a new state so that the Start command is sent to the
+ * node */
+ if (was_driving && !node->driving)
+ impl->pending_state = node->info.state;
+
+ pw_log_debug("%p: driver %p driving:%u", node,
+ driver, node->driving);
+ pw_log_info("(%s-%u) -> change driver (%s-%d -> %s-%d)",
+ node->name, node->info.id,
+ old->name, old->info.id, driver->name, driver->info.id);
+
+ node->driver_node = driver;
+ node->moved = true;
+
+ if ((res = spa_node_set_io(node->node,
+ SPA_IO_Position,
+ &driver->rt.activation->position,
+ sizeof(struct spa_io_position))) < 0) {
+ pw_log_debug("%p: set position: %s", node, spa_strerror(res));
+ }
+
+ pw_loop_invoke(node->data_loop,
+ do_move_nodes, SPA_ID_INVALID, &driver, sizeof(struct pw_impl_node *),
+ true, impl);
+
+ pw_impl_node_emit_driver_changed(node, old, driver);
+
+ return 0;
+}
+
+static void check_properties(struct pw_impl_node *node)
+{
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+ struct pw_context *context = node->context;
+ const char *str, *recalc_reason = NULL;
+ struct spa_fraction frac;
+ uint32_t value;
+ bool driver;
+
+ if ((str = pw_properties_get(node->properties, PW_KEY_PRIORITY_DRIVER))) {
+ node->priority_driver = pw_properties_parse_int(str);
+ pw_log_debug("%p: priority driver %d", node, node->priority_driver);
+ }
+
+ if ((str = pw_properties_get(node->properties, PW_KEY_NODE_NAME)) &&
+ (node->name == NULL || !spa_streq(node->name, str))) {
+ free(node->name);
+ node->name = strdup(str);
+ pw_log_debug("%p: name '%s'", node, node->name);
+ }
+
+ node->pause_on_idle = pw_properties_get_bool(node->properties, PW_KEY_NODE_PAUSE_ON_IDLE, true);
+ node->suspend_on_idle = pw_properties_get_bool(node->properties, PW_KEY_NODE_SUSPEND_ON_IDLE, false);
+ node->transport_sync = pw_properties_get_bool(node->properties, PW_KEY_NODE_TRANSPORT_SYNC, false);
+ impl->cache_params = pw_properties_get_bool(node->properties, PW_KEY_NODE_CACHE_PARAMS, true);
+ driver = pw_properties_get_bool(node->properties, PW_KEY_NODE_DRIVER, false);
+
+ if (node->driver != driver) {
+ pw_log_debug("%p: driver %d -> %d", node, node->driver, driver);
+ node->driver = driver;
+ if (node->registered) {
+ if (driver)
+ insert_driver(context, node);
+ else
+ spa_list_remove(&node->driver_link);
+ }
+ recalc_reason = "driver changed";
+ }
+
+ /* not scheduled automatically so we add an additional required trigger */
+ if (pw_properties_get_bool(node->properties, PW_KEY_NODE_TRIGGER, false))
+ node->rt.activation->state[0].required++;
+
+ /* group defines what nodes are scheduled together */
+ if ((str = pw_properties_get(node->properties, PW_KEY_NODE_GROUP)) == NULL)
+ str = "";
+
+ if (!spa_streq(str, node->group)) {
+ pw_log_info("%p: group '%s'->'%s'", node, node->group, str);
+ snprintf(node->group, sizeof(node->group), "%s", str);
+ node->freewheel = spa_streq(node->group, "pipewire.freewheel");
+ recalc_reason = "group changed";
+ }
+
+
+ node->want_driver = pw_properties_get_bool(node->properties, PW_KEY_NODE_WANT_DRIVER, false);
+ node->always_process = pw_properties_get_bool(node->properties, PW_KEY_NODE_ALWAYS_PROCESS, false);
+
+ if (node->always_process)
+ node->want_driver = true;
+
+ if ((str = pw_properties_get(node->properties, PW_KEY_NODE_LATENCY))) {
+ if (sscanf(str, "%u/%u", &frac.num, &frac.denom) == 2 && frac.denom != 0) {
+ if (node->latency.num != frac.num || node->latency.denom != frac.denom) {
+ pw_log_info("(%s-%u) latency:%u/%u -> %u/%u", node->name,
+ node->info.id, node->latency.num,
+ node->latency.denom, frac.num, frac.denom);
+ node->latency = frac;
+ recalc_reason = "quantum changed";
+ }
+ }
+ }
+ if ((str = pw_properties_get(node->properties, PW_KEY_NODE_MAX_LATENCY))) {
+ if (sscanf(str, "%u/%u", &frac.num, &frac.denom) == 2 && frac.denom != 0) {
+ if (node->max_latency.num != frac.num || node->max_latency.denom != frac.denom) {
+ pw_log_info("(%s-%u) max-latency:%u/%u -> %u/%u", node->name,
+ node->info.id, node->max_latency.num,
+ node->max_latency.denom, frac.num, frac.denom);
+ node->max_latency = frac;
+ recalc_reason = "max quantum changed";
+ }
+ }
+ }
+ 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";
+ }
+ }
+
+ if ((str = pw_properties_get(node->properties, PW_KEY_NODE_RATE))) {
+ if (sscanf(str, "%u/%u", &frac.num, &frac.denom) == 2 && frac.denom != 0) {
+ if (node->rate.num != frac.num || node->rate.denom != frac.denom) {
+ pw_log_info("(%s-%u) rate:%u/%u -> %u/%u", node->name,
+ node->info.id, node->rate.num,
+ node->rate.denom, frac.num, frac.denom);
+ node->rate = frac;
+ recalc_reason = "node rate changed";
+ }
+ }
+ }
+ 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) &&
+ 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,
+ recalc_reason, node->active);
+
+ if (recalc_reason != NULL && node->active)
+ pw_context_recalc_graph(context, recalc_reason);
+}
+
+static const char *str_status(uint32_t status)
+{
+ switch (status) {
+ case PW_NODE_ACTIVATION_NOT_TRIGGERED:
+ return "not-triggered";
+ case PW_NODE_ACTIVATION_TRIGGERED:
+ return "triggered";
+ case PW_NODE_ACTIVATION_AWAKE:
+ return "awake";
+ case PW_NODE_ACTIVATION_FINISHED:
+ return "finished";
+ }
+ return "unknown";
+}
+
+static void dump_states(struct pw_impl_node *driver)
+{
+ struct pw_node_target *t;
+ struct pw_node_activation *na = driver->rt.activation;
+ struct spa_io_clock *cl = &na->position.clock;
+
+ spa_list_for_each(t, &driver->rt.target_list, link) {
+ struct pw_node_activation *a = t->activation;
+ struct pw_node_activation_state *state = &a->state[0];
+ if (t->node == NULL)
+ continue;
+ if (a->status == PW_NODE_ACTIVATION_TRIGGERED ||
+ a->status == PW_NODE_ACTIVATION_AWAKE) {
+ pw_log_info("(%s-%u) client too slow! rate:%u/%u pos:%"PRIu64" status:%s",
+ t->node->name, t->node->info.id,
+ (uint32_t)(cl->rate.num * cl->duration), cl->rate.denom,
+ cl->position, str_status(a->status));
+ }
+ pw_log_debug("(%s-%u) state:%p pending:%d/%d s:%"PRIu64" a:%"PRIu64" f:%"PRIu64
+ " waiting:%"PRIu64" process:%"PRIu64" status:%s sync:%d",
+ t->node->name, t->node->info.id, state,
+ state->pending, state->required,
+ a->signal_time,
+ a->awake_time,
+ a->finish_time,
+ a->awake_time - a->signal_time,
+ a->finish_time - a->awake_time,
+ str_status(a->status), a->pending_sync);
+ }
+}
+
+static inline int resume_node(struct pw_impl_node *this, int status)
+{
+ struct pw_node_target *t;
+ struct timespec ts;
+ struct pw_node_activation *activation = this->rt.activation;
+ struct spa_system *data_system = this->context->data_system;
+ uint64_t nsec;
+
+ spa_system_clock_gettime(data_system, CLOCK_MONOTONIC, &ts);
+ nsec = SPA_TIMESPEC_TO_NSEC(&ts);
+ activation->status = PW_NODE_ACTIVATION_FINISHED;
+ activation->finish_time = nsec;
+
+ pw_log_trace_fp("%p: trigger peers %"PRIu64, this, nsec);
+
+ spa_list_for_each(t, &this->rt.target_list, link) {
+ struct pw_node_activation *a = t->activation;
+ struct pw_node_activation_state *state = &a->state[0];
+
+ pw_log_trace_fp("%p: state:%p pending:%d/%d", t->node, state,
+ state->pending, state->required);
+
+ if (pw_node_activation_state_dec(state, 1)) {
+ a->status = PW_NODE_ACTIVATION_TRIGGERED;
+ a->signal_time = nsec;
+ t->signal_func(t->data);
+ }
+ }
+ return 0;
+}
+
+static inline void calculate_stats(struct pw_impl_node *this, struct pw_node_activation *a)
+{
+ if (SPA_LIKELY(a->signal_time > a->prev_signal_time)) {
+ uint64_t process_time = a->finish_time - a->signal_time;
+ uint64_t period_time = a->signal_time - a->prev_signal_time;
+ float load = (float) process_time / (float) period_time;
+ a->cpu_load[0] = (a->cpu_load[0] + load) / 2.0f;
+ a->cpu_load[1] = (a->cpu_load[1] * 7.0f + load) / 8.0f;
+ a->cpu_load[2] = (a->cpu_load[2] * 31.0f + load) / 32.0f;
+ }
+}
+
+static inline int process_node(void *data)
+{
+ struct pw_impl_node *this = data;
+ struct timespec ts;
+ struct pw_impl_port *p;
+ struct pw_node_activation *a = this->rt.activation;
+ struct spa_system *data_system = this->context->data_system;
+ int status;
+
+ spa_system_clock_gettime(data_system, CLOCK_MONOTONIC, &ts);
+ a->status = PW_NODE_ACTIVATION_AWAKE;
+ a->awake_time = SPA_TIMESPEC_TO_NSEC(&ts);
+
+ pw_log_trace_fp("%p: process %"PRIu64, this, a->awake_time);
+
+ /* when transport sync is not supported, just clear the flag */
+ if (!this->transport_sync)
+ a->pending_sync = false;
+
+ if (this->added) {
+ spa_list_for_each(p, &this->rt.input_mix, rt.node_link)
+ spa_node_process(p->mix);
+
+ status = spa_node_process(this->node);
+
+ if (status & SPA_STATUS_HAVE_DATA) {
+ spa_list_for_each(p, &this->rt.output_mix, rt.node_link)
+ spa_node_process(p->mix);
+ }
+ } else {
+ /* This can happen when we deactivated the node but some links are
+ * still not shut down. We simply don't schedule the node and make
+ * sure we trigger the peers in resume_node below. */
+ pw_log_debug("%p: scheduling non-active node %s", this, this->name);
+ status = SPA_STATUS_HAVE_DATA;
+ }
+ a->state[0].status = status;
+
+ if (SPA_UNLIKELY(this == this->driver_node && !this->exported)) {
+ spa_system_clock_gettime(data_system, CLOCK_MONOTONIC, &ts);
+ a->status = PW_NODE_ACTIVATION_FINISHED;
+ a->signal_time = a->finish_time;
+ a->finish_time = SPA_TIMESPEC_TO_NSEC(&ts);
+
+ /* calculate CPU time */
+ calculate_stats(this, a);
+
+ pw_log_trace_fp("%p: graph completed wait:%"PRIu64" run:%"PRIu64
+ " busy:%"PRIu64" period:%"PRIu64" cpu:%f:%f:%f", this,
+ a->awake_time - a->signal_time,
+ a->finish_time - a->awake_time,
+ a->finish_time - a->signal_time,
+ a->signal_time - a->prev_signal_time,
+ a->cpu_load[0], a->cpu_load[1], a->cpu_load[2]);
+
+ pw_context_driver_emit_complete(this->context, this);
+
+ } else if (status == SPA_STATUS_OK) {
+ pw_log_trace_fp("%p: async continue", this);
+ } else {
+ resume_node(this, status);
+ }
+ if (status & SPA_STATUS_DRAINED) {
+ pw_context_driver_emit_drained(this->context, this);
+ }
+ return 0;
+}
+
+static void node_on_fd_events(struct spa_source *source)
+{
+ struct pw_impl_node *this = source->data;
+ struct spa_system *data_system = this->context->data_system;
+
+ if (SPA_UNLIKELY(source->rmask & (SPA_IO_ERR | SPA_IO_HUP))) {
+ pw_log_warn("%p: got socket error %08x", this, source->rmask);
+ return;
+ }
+
+ if (SPA_LIKELY(source->rmask & SPA_IO_IN)) {
+ uint64_t cmd;
+
+ if (SPA_UNLIKELY(spa_system_eventfd_read(data_system, this->source.fd, &cmd) < 0))
+ pw_log_warn("%p: read failed %m", this);
+ else if (SPA_UNLIKELY(cmd > 1))
+ pw_log_info("(%s-%u) client missed %"PRIu64" wakeups",
+ this->name, this->info.id, cmd - 1);
+
+ pw_log_trace_fp("%p: got process", this);
+ this->rt.target.signal_func(this->rt.target.data);
+ }
+}
+
+static void reset_segment(struct spa_io_segment *seg)
+{
+ spa_zero(*seg);
+ seg->rate = 1.0;
+}
+
+static void reset_position(struct pw_impl_node *this, struct spa_io_position *pos)
+{
+ uint32_t i;
+ struct settings *s = &this->context->settings;
+ uint32_t quantum = s->clock_force_quantum == 0 ? s->clock_quantum : s->clock_force_quantum;
+ uint32_t rate = s->clock_force_rate == 0 ? s->clock_rate : s->clock_force_rate;
+
+ this->current_rate = SPA_FRACTION(1, rate);
+ this->current_quantum = quantum;
+
+ pos->clock.rate = this->current_rate;
+ pos->clock.duration = this->current_quantum;
+ pos->video.flags = SPA_IO_VIDEO_SIZE_VALID;
+ pos->video.size = s->video_size;
+ pos->video.stride = pos->video.size.width * 16;
+ pos->video.framerate = s->video_rate;
+ pos->offset = INT64_MIN;
+
+ pos->n_segments = 1;
+ for (i = 0; i < SPA_IO_POSITION_MAX_SEGMENTS; i++)
+ reset_segment(&pos->segments[i]);
+}
+
+SPA_EXPORT
+struct pw_impl_node *pw_context_create_node(struct pw_context *context,
+ struct pw_properties *properties,
+ size_t user_data_size)
+{
+ struct impl *impl;
+ struct pw_impl_node *this;
+ size_t size;
+ struct spa_system *data_system = context->data_system;
+ int res;
+
+ impl = calloc(1, sizeof(struct impl) + user_data_size);
+ if (impl == NULL) {
+ res = -errno;
+ goto error_exit;
+ }
+
+ spa_list_init(&impl->param_list);
+ spa_list_init(&impl->pending_list);
+
+ this = &impl->this;
+ this->context = context;
+ this->name = strdup("node");
+
+ if (user_data_size > 0)
+ this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void);
+
+ if (properties == NULL)
+ properties = pw_properties_new(NULL, NULL);
+ if (properties == NULL) {
+ res = -errno;
+ goto error_clean;
+ }
+
+ this->properties = properties;
+
+ if ((res = spa_system_eventfd_create(data_system, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK)) < 0)
+ goto error_clean;
+
+ pw_log_debug("%p: new fd:%d", this, res);
+
+ this->source.fd = res;
+ this->source.func = node_on_fd_events;
+ this->source.data = this;
+ this->source.mask = SPA_IO_IN | SPA_IO_ERR | SPA_IO_HUP;
+ this->source.rmask = 0;
+
+ size = sizeof(struct pw_node_activation);
+
+ this->activation = pw_mempool_alloc(this->context->pool,
+ PW_MEMBLOCK_FLAG_READWRITE |
+ PW_MEMBLOCK_FLAG_SEAL |
+ PW_MEMBLOCK_FLAG_MAP,
+ SPA_DATA_MemFd, size);
+ if (this->activation == NULL) {
+ res = -errno;
+ goto error_clean;
+ }
+
+ impl->work = pw_context_get_work_queue(this->context);
+ impl->pending_id = SPA_ID_INVALID;
+
+ this->data_loop = context->data_loop;
+
+ spa_list_init(&this->follower_list);
+
+ spa_hook_list_init(&this->listener_list);
+
+ this->info.state = PW_NODE_STATE_CREATING;
+ this->info.props = &this->properties->dict;
+ this->info.params = this->params;
+
+ spa_list_init(&this->input_ports);
+ pw_map_init(&this->input_port_map, 64, 64);
+ spa_list_init(&this->output_ports);
+ pw_map_init(&this->output_port_map, 64, 64);
+
+ spa_list_init(&this->rt.input_mix);
+ spa_list_init(&this->rt.output_mix);
+ spa_list_init(&this->rt.target_list);
+
+ this->rt.activation = this->activation->map->ptr;
+ this->rt.target.activation = this->rt.activation;
+ this->rt.target.node = this;
+ this->rt.target.signal_func = process_node;
+ this->rt.target.data = this;
+ this->rt.driver_target.signal_func = process_node;
+
+ reset_position(this, &this->rt.activation->position);
+ this->rt.activation->sync_timeout = DEFAULT_SYNC_TIMEOUT;
+ this->rt.activation->sync_left = 0;
+
+ this->rt.rate_limit.interval = 2 * SPA_NSEC_PER_SEC;
+ this->rt.rate_limit.burst = 1;
+
+ check_properties(this);
+
+ this->driver_node = this;
+ spa_list_append(&this->follower_list, &this->follower_link);
+ this->driving = true;
+
+ return this;
+
+error_clean:
+ if (this->activation)
+ pw_memblock_unref(this->activation);
+ if (this->source.fd != -1)
+ spa_system_close(this->context->data_system, this->source.fd);
+ free(impl);
+error_exit:
+ pw_properties_free(properties);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+const struct pw_node_info *pw_impl_node_get_info(struct pw_impl_node *node)
+{
+ return &node->info;
+}
+
+SPA_EXPORT
+void * pw_impl_node_get_user_data(struct pw_impl_node *node)
+{
+ return node->user_data;
+}
+
+SPA_EXPORT
+struct pw_context * pw_impl_node_get_context(struct pw_impl_node *node)
+{
+ return node->context;
+}
+
+SPA_EXPORT
+struct pw_global *pw_impl_node_get_global(struct pw_impl_node *node)
+{
+ return node->global;
+}
+
+SPA_EXPORT
+const struct pw_properties *pw_impl_node_get_properties(struct pw_impl_node *node)
+{
+ return node->properties;
+}
+
+static int update_properties(struct pw_impl_node *node, const struct spa_dict *dict, bool filter)
+{
+ static const char * const ignored[] = {
+ PW_KEY_OBJECT_ID,
+ PW_KEY_MODULE_ID,
+ PW_KEY_FACTORY_ID,
+ PW_KEY_CLIENT_ID,
+ PW_KEY_DEVICE_ID,
+ NULL
+ };
+
+ int changed;
+
+ changed = pw_properties_update_ignore(node->properties, dict, filter ? ignored : NULL);
+ node->info.props = &node->properties->dict;
+
+ pw_log_debug("%p: updated %d properties", node, changed);
+
+ if (changed) {
+ check_properties(node);
+ node->info.change_mask |= PW_NODE_CHANGE_MASK_PROPS;
+ }
+ return changed;
+}
+
+SPA_EXPORT
+int pw_impl_node_update_properties(struct pw_impl_node *node, const struct spa_dict *dict)
+{
+ int changed = update_properties(node, dict, false);
+ emit_info_changed(node, false);
+ return changed;
+}
+
+static void node_info(void *data, const struct spa_node_info *info)
+{
+ struct pw_impl_node *node = data;
+ uint32_t changed_ids[MAX_PARAMS], n_changed_ids = 0;
+ bool flags_changed = false;
+
+ node->info.max_input_ports = info->max_input_ports;
+ node->info.max_output_ports = info->max_output_ports;
+
+ pw_log_debug("%p: flags:%08"PRIx64" change_mask:%08"PRIx64" max_in:%u max_out:%u",
+ node, info->flags, info->change_mask, info->max_input_ports,
+ info->max_output_ports);
+
+ if (info->change_mask & SPA_NODE_CHANGE_MASK_FLAGS) {
+ if (node->spa_flags != info->flags) {
+ flags_changed = node->spa_flags != 0;
+ pw_log_debug("%p: flags %"PRIu64"->%"PRIu64, node, node->spa_flags, info->flags);
+ node->spa_flags = info->flags;
+ }
+ }
+ if (info->change_mask & SPA_NODE_CHANGE_MASK_PROPS) {
+ update_properties(node, info->props, true);
+ }
+ if (info->change_mask & SPA_NODE_CHANGE_MASK_PARAMS) {
+ uint32_t i;
+
+ node->info.change_mask |= PW_NODE_CHANGE_MASK_PARAMS;
+ node->info.n_params = SPA_MIN(info->n_params, SPA_N_ELEMENTS(node->params));
+
+ for (i = 0; i < node->info.n_params; i++) {
+ uint32_t id = info->params[i].id;
+
+ pw_log_debug("%p: param %d id:%d (%s) %08x:%08x", node, i,
+ id, spa_debug_type_find_name(spa_type_param, id),
+ node->info.params[i].flags, info->params[i].flags);
+
+ node->info.params[i].id = info->params[i].id;
+ if (node->info.params[i].flags == info->params[i].flags)
+ continue;
+
+ pw_log_debug("%p: update param %d", node, id);
+ node->info.params[i] = info->params[i];
+ node->info.params[i].user = 0;
+
+ if (info->params[i].flags & SPA_PARAM_INFO_READ)
+ changed_ids[n_changed_ids++] = id;
+ }
+ }
+ emit_info_changed(node, flags_changed);
+
+ if (n_changed_ids > 0)
+ emit_params(node, changed_ids, n_changed_ids);
+
+ if (flags_changed)
+ pw_context_recalc_graph(node->context, "node flags changed");
+}
+
+static void node_port_info(void *data, enum spa_direction direction, uint32_t port_id,
+ const struct spa_port_info *info)
+{
+ struct pw_impl_node *node = data;
+ struct pw_impl_port *port = pw_impl_node_find_port(node, direction, port_id);
+
+ if (info == NULL) {
+ if (port) {
+ pw_log_debug("%p: %s port %d removed", node,
+ pw_direction_as_string(direction), port_id);
+ pw_impl_port_destroy(port);
+ } else {
+ pw_log_warn("%p: %s port %d unknown", node,
+ pw_direction_as_string(direction), port_id);
+ }
+ } else if (port) {
+ pw_log_debug("%p: %s port %d changed", node,
+ pw_direction_as_string(direction), port_id);
+ pw_impl_port_update_info(port, info);
+ } else {
+ int res;
+
+ pw_log_debug("%p: %s port %d added", node,
+ pw_direction_as_string(direction), port_id);
+
+ if ((port = pw_context_create_port(node->context, direction, port_id, info,
+ node->port_user_data_size))) {
+ if ((res = pw_impl_port_add(port, node)) < 0) {
+ pw_log_error("%p: can't add port %p: %d, %s",
+ node, port, res, spa_strerror(res));
+ pw_impl_port_destroy(port);
+ }
+ }
+ }
+}
+
+static void node_result(void *data, int seq, int res, uint32_t type, const void *result)
+{
+ struct pw_impl_node *node = data;
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+
+ pw_log_trace("%p: result seq:%d res:%d type:%u", node, seq, res, type);
+ if (res < 0)
+ impl->last_error = res;
+
+ if (SPA_RESULT_IS_ASYNC(seq))
+ pw_work_queue_complete(impl->work, &impl->this, SPA_RESULT_ASYNC_SEQ(seq), res);
+
+ pw_impl_node_emit_result(node, seq, res, type, result);
+}
+
+static void node_event(void *data, const struct spa_event *event)
+{
+ struct pw_impl_node *node = data;
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+
+ pw_log_trace("%p: event %d", node, SPA_EVENT_TYPE(event));
+
+ switch (SPA_NODE_EVENT_ID(event)) {
+ case SPA_NODE_EVENT_Error:
+ impl->last_error = -EFAULT;
+ node_update_state(node, PW_NODE_STATE_ERROR,
+ -EFAULT, strdup("Received error event"));
+ break;
+ case SPA_NODE_EVENT_RequestProcess:
+ pw_log_debug("request process");
+ if (!node->driving) {
+ pw_impl_node_send_command(node->driver_node,
+ &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_RequestProcess));
+ }
+ break;
+ default:
+ pw_log_debug("unhandled event %d", SPA_NODE_EVENT_ID(event));
+ break;
+ }
+ pw_impl_node_emit_event(node, event);
+}
+
+static const struct spa_node_events node_events = {
+ SPA_VERSION_NODE_EVENTS,
+ .info = node_info,
+ .port_info = node_port_info,
+ .result = node_result,
+ .event = node_event,
+};
+
+#define SYNC_CHECK 0
+#define SYNC_START 1
+#define SYNC_STOP 2
+
+static int check_updates(struct pw_impl_node *node, uint32_t *reposition_owner)
+{
+ int res = SYNC_CHECK;
+ struct pw_node_activation *a = node->rt.activation;
+ uint32_t command;
+
+ if (SPA_UNLIKELY(a->position.offset == INT64_MIN))
+ a->position.offset = a->position.clock.position;
+
+ command = ATOMIC_XCHG(a->command, PW_NODE_ACTIVATION_COMMAND_NONE);
+ *reposition_owner = ATOMIC_XCHG(a->reposition_owner, 0);
+
+ if (SPA_UNLIKELY(command != PW_NODE_ACTIVATION_COMMAND_NONE)) {
+ pw_log_debug("%p: update command:%u", node, command);
+ switch (command) {
+ case PW_NODE_ACTIVATION_COMMAND_STOP:
+ a->position.state = SPA_IO_POSITION_STATE_STOPPED;
+ res = SYNC_STOP;
+ break;
+ case PW_NODE_ACTIVATION_COMMAND_START:
+ a->position.state = SPA_IO_POSITION_STATE_STARTING;
+ a->sync_left = a->sync_timeout /
+ ((a->position.clock.duration * SPA_NSEC_PER_SEC) /
+ a->position.clock.rate.denom);
+ res = SYNC_START;
+ break;
+ }
+ }
+ return res;
+}
+
+static void do_reposition(struct pw_impl_node *driver, struct pw_impl_node *node)
+{
+ struct pw_node_activation *a = driver->rt.activation;
+ struct spa_io_segment *dst, *src;
+
+ src = &node->rt.activation->reposition;
+ dst = &a->position.segments[0];
+
+ pw_log_info("%p: update position:%"PRIu64, node, src->position);
+
+ dst->version = src->version;
+ dst->flags = src->flags;
+ dst->start = src->start;
+ dst->duration = src->duration;
+ dst->rate = src->rate;
+ dst->position = src->position;
+ if (src->bar.flags & SPA_IO_SEGMENT_BAR_FLAG_VALID)
+ dst->bar = src->bar;
+ if (src->video.flags & SPA_IO_SEGMENT_VIDEO_FLAG_VALID)
+ dst->video = src->video;
+
+ if (dst->start == 0)
+ dst->start = a->position.clock.position - a->position.offset;
+
+ switch (a->position.state) {
+ case SPA_IO_POSITION_STATE_RUNNING:
+ a->position.state = SPA_IO_POSITION_STATE_STARTING;
+ a->sync_left = a->sync_timeout /
+ ((a->position.clock.duration * SPA_NSEC_PER_SEC) /
+ a->position.clock.rate.denom);
+ break;
+ }
+}
+
+static void update_position(struct pw_impl_node *node, int all_ready)
+{
+ struct pw_node_activation *a = node->rt.activation;
+
+ if (a->position.state == SPA_IO_POSITION_STATE_STARTING) {
+ if (!all_ready && --a->sync_left == 0) {
+ pw_log_warn("(%s-%u) sync timeout, going to RUNNING",
+ node->name, node->info.id);
+ pw_context_driver_emit_timeout(node->context, node);
+ dump_states(node);
+ all_ready = true;
+ }
+ if (all_ready)
+ a->position.state = SPA_IO_POSITION_STATE_RUNNING;
+ }
+ if (a->position.state != SPA_IO_POSITION_STATE_RUNNING)
+ a->position.offset += a->position.clock.duration;
+}
+
+static int node_ready(void *data, int status)
+{
+ struct pw_impl_node *node = data, *reposition_node = NULL;
+ struct pw_impl_node *driver = node->driver_node;
+ struct pw_node_target *t;
+ struct pw_impl_port *p;
+
+ pw_log_trace_fp("%p: ready driver:%d exported:%d %p status:%d added:%d", node,
+ node->driver, node->exported, driver, status, node->added);
+
+ if (!node->added) {
+ /* This can happen when we are stopping a node and removed it from the
+ * graph but we still have not completed the Pause/Suspend command on
+ * the node. In that case, the node might still emit ready events,
+ * which we should simply ignore here. */
+ pw_log_info("%p: ready non-active node %s in state %d", node, node->name, node->info.state);
+ return -EIO;
+ }
+
+ if (SPA_UNLIKELY(node == driver)) {
+ struct pw_node_activation *a = node->rt.activation;
+ struct pw_node_activation_state *state = &a->state[0];
+ int sync_type, all_ready, update_sync, target_sync;
+ uint32_t owner[2], reposition_owner;
+ uint64_t min_timeout = UINT64_MAX;
+
+ if (SPA_UNLIKELY(state->pending > 0)) {
+ pw_context_driver_emit_incomplete(node->context, node);
+ if (ratelimit_test(&node->rt.rate_limit, a->signal_time, SPA_LOG_LEVEL_DEBUG)) {
+ pw_log_debug("(%s-%u) graph not finished: state:%p quantum:%"PRIu64
+ " pending %d/%d", node->name, node->info.id,
+ state, a->position.clock.duration,
+ state->pending, state->required);
+ dump_states(node);
+ }
+ node->rt.target.signal_func(node->rt.target.data);
+ }
+
+ if (node->current_pending) {
+ node->rt.position->clock.duration = node->current_quantum;
+ node->rt.position->clock.rate = node->current_rate;
+ node->current_pending = false;
+ }
+
+ sync_type = check_updates(node, &reposition_owner);
+ owner[0] = ATOMIC_LOAD(a->segment_owner[0]);
+ owner[1] = ATOMIC_LOAD(a->segment_owner[1]);
+again:
+ all_ready = sync_type == SYNC_CHECK;
+ update_sync = !all_ready;
+ target_sync = sync_type == SYNC_START ? true : false;
+
+ spa_list_for_each(t, &driver->rt.target_list, link) {
+ struct pw_node_activation *ta = t->activation;
+
+ ta->status = PW_NODE_ACTIVATION_NOT_TRIGGERED;
+ pw_node_activation_state_reset(&ta->state[0]);
+
+ if (SPA_LIKELY(t->node)) {
+ uint32_t id = t->node->info.id;
+
+ /* this is the node with reposition info */
+ if (SPA_UNLIKELY(id == reposition_owner))
+ reposition_node = t->node;
+
+ /* update extra segment info if it is the owner */
+ if (SPA_UNLIKELY(id == owner[0]))
+ a->position.segments[0].bar = ta->segment.bar;
+ if (SPA_UNLIKELY(id == owner[1]))
+ a->position.segments[0].video = ta->segment.video;
+
+ min_timeout = SPA_MIN(min_timeout, ta->sync_timeout);
+ }
+
+ if (SPA_UNLIKELY(update_sync)) {
+ ta->pending_sync = target_sync;
+ ta->pending_new_pos = target_sync;
+ } else {
+ all_ready &= ta->pending_sync == false;
+ }
+ }
+ a->prev_signal_time = a->signal_time;
+ a->sync_timeout = SPA_MIN(min_timeout, DEFAULT_SYNC_TIMEOUT);
+
+ if (SPA_UNLIKELY(reposition_node)) {
+ do_reposition(node, reposition_node);
+ sync_type = SYNC_START;
+ reposition_owner = 0;
+ reposition_node = NULL;
+ goto again;
+ }
+
+ update_position(node, all_ready);
+
+ pw_context_driver_emit_start(node->context, node);
+ }
+ if (SPA_UNLIKELY(node->driver && !node->driving))
+ return 0;
+
+ if (!node->driver) {
+ struct timespec ts;
+ struct pw_node_activation *a = node->rt.activation;
+ struct spa_system *data_system = node->context->data_system;
+
+ spa_system_clock_gettime(data_system, CLOCK_MONOTONIC, &ts);
+ a->status = PW_NODE_ACTIVATION_AWAKE;
+ a->signal_time = a->awake_time = SPA_TIMESPEC_TO_NSEC(&ts);
+ }
+
+ if (status & SPA_STATUS_HAVE_DATA) {
+ spa_list_for_each(p, &node->rt.output_mix, rt.node_link)
+ spa_node_process(p->mix);
+ }
+ return resume_node(node, status);
+}
+
+static int node_reuse_buffer(void *data, uint32_t port_id, uint32_t buffer_id)
+{
+ struct pw_impl_node *node = data;
+ struct pw_impl_port *p;
+
+ spa_list_for_each(p, &node->rt.input_mix, rt.node_link) {
+ if (p->port_id != port_id)
+ continue;
+ spa_node_port_reuse_buffer(p->mix, 0, buffer_id);
+ break;
+ }
+ return 0;
+}
+
+static void update_xrun_stats(struct pw_node_activation *a, uint64_t trigger, uint64_t delay)
+{
+ a->xrun_count++;
+ a->xrun_time = trigger;
+ a->xrun_delay = delay;
+ a->max_delay = SPA_MAX(a->max_delay, delay);
+}
+
+static int node_xrun(void *data, uint64_t trigger, uint64_t delay, struct spa_pod *info)
+{
+ struct pw_impl_node *this = data;
+ struct pw_node_activation *a = this->rt.activation;
+ struct pw_node_activation *da = this->rt.driver_target.activation;
+
+ update_xrun_stats(a, trigger, delay);
+ if (da && da != a)
+ update_xrun_stats(da, trigger, delay);
+
+ if (ratelimit_test(&this->rt.rate_limit, a->signal_time, SPA_LOG_LEVEL_INFO)) {
+ struct spa_fraction rate;
+ if (da) {
+ struct spa_io_clock *cl = &da->position.clock;
+ rate.num = cl->rate.num * cl->duration;
+ rate.denom = cl->rate.denom;
+ } else {
+ rate = SPA_FRACTION(0,0);
+ }
+ pw_log_info("(%s-%d) XRun! rate:%u/%u count:%u time:%"PRIu64
+ " delay:%"PRIu64" max:%"PRIu64,
+ this->name, this->info.id,
+ rate.num, rate.denom, a->xrun_count,
+ trigger, delay, a->max_delay);
+ }
+
+ pw_context_driver_emit_xrun(this->context, this);
+
+ return 0;
+}
+
+static const struct spa_node_callbacks node_callbacks = {
+ SPA_VERSION_NODE_CALLBACKS,
+ .ready = node_ready,
+ .reuse_buffer = node_reuse_buffer,
+ .xrun = node_xrun,
+};
+
+SPA_EXPORT
+int pw_impl_node_set_implementation(struct pw_impl_node *node,
+ struct spa_node *spa_node)
+{
+ int res;
+
+ pw_log_debug("%p: implementation %p", node, spa_node);
+
+ if (node->node) {
+ pw_log_error("%p: implementation existed %p", node, node->node);
+ return -EEXIST;
+ }
+
+ node->node = spa_node;
+ spa_node_set_callbacks(node->node, &node_callbacks, node);
+ res = spa_node_add_listener(node->node, &node->listener, &node_events, node);
+
+ if (node->registered)
+ update_io(node);
+
+ return res;
+}
+
+SPA_EXPORT
+struct spa_node *pw_impl_node_get_implementation(struct pw_impl_node *node)
+{
+ return node->node;
+}
+
+SPA_EXPORT
+void pw_impl_node_add_listener(struct pw_impl_node *node,
+ struct spa_hook *listener,
+ const struct pw_impl_node_events *events,
+ void *data)
+{
+ spa_hook_list_append(&node->listener_list, listener, events, data);
+}
+
+/** Destroy a node
+ * \param node a node to destroy
+ *
+ * Remove \a node. This will stop the transfer on the node and
+ * free the resources allocated by \a node.
+ */
+SPA_EXPORT
+void pw_impl_node_destroy(struct pw_impl_node *node)
+{
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+ struct pw_impl_port *port;
+ struct pw_impl_node *follower;
+ struct pw_context *context = node->context;
+ bool active, had_driver;
+
+ active = node->active;
+ node->active = false;
+
+ pw_log_debug("%p: destroy", impl);
+ pw_log_info("(%s-%u) destroy", node->name, node->info.id);
+
+ node_deactivate(node);
+
+ suspend_node(node);
+
+ pw_impl_node_emit_destroy(node);
+
+ pw_log_debug("%p: driver node %p", impl, node->driver_node);
+ had_driver = node != node->driver_node;
+
+ /* remove ourself as a follower from the driver node */
+ spa_list_remove(&node->follower_link);
+ remove_segment_owner(node->driver_node, node->info.id);
+
+ spa_list_consume(follower, &node->follower_list, follower_link) {
+ pw_log_debug("%p: reassign follower %p", impl, follower);
+ pw_impl_node_set_driver(follower, NULL);
+ }
+
+ if (node->registered) {
+ spa_list_remove(&node->link);
+ if (node->driver)
+ spa_list_remove(&node->driver_link);
+ }
+
+ if (node->node) {
+ spa_hook_remove(&node->listener);
+ spa_node_set_callbacks(node->node, NULL, NULL);
+ }
+
+ pw_log_debug("%p: destroy ports", node);
+ spa_list_consume(port, &node->input_ports, link)
+ pw_impl_port_destroy(port);
+ spa_list_consume(port, &node->output_ports, link)
+ pw_impl_port_destroy(port);
+
+ if (node->global) {
+ spa_hook_remove(&node->global_listener);
+ pw_global_destroy(node->global);
+ }
+
+ if (active || had_driver)
+ pw_context_recalc_graph(context,
+ "active node destroy");
+
+ pw_log_debug("%p: free", node);
+ pw_impl_node_emit_free(node);
+
+ spa_hook_list_clean(&node->listener_list);
+
+ pw_memblock_unref(node->activation);
+
+ pw_param_clear(&impl->param_list, SPA_ID_INVALID);
+ pw_param_clear(&impl->pending_list, SPA_ID_INVALID);
+
+ pw_map_clear(&node->input_port_map);
+ pw_map_clear(&node->output_port_map);
+
+ pw_work_queue_cancel(impl->work, node, SPA_ID_INVALID);
+
+ pw_properties_free(node->properties);
+
+ clear_info(node);
+
+ spa_system_close(context->data_system, node->source.fd);
+ free(impl);
+}
+
+SPA_EXPORT
+int pw_impl_node_for_each_port(struct pw_impl_node *node,
+ enum pw_direction direction,
+ int (*callback) (void *data, struct pw_impl_port *port),
+ void *data)
+{
+ struct spa_list *ports;
+ struct pw_impl_port *p, *t;
+ int res;
+
+ if (direction == PW_DIRECTION_INPUT)
+ ports = &node->input_ports;
+ else
+ ports = &node->output_ports;
+
+ spa_list_for_each_safe(p, t, ports, link)
+ if ((res = callback(data, p)) != 0)
+ return res;
+ return 0;
+}
+
+struct result_node_params_data {
+ struct impl *impl;
+ void *data;
+ int (*callback) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ struct spa_pod *param);
+ int seq;
+ uint32_t count;
+ unsigned int cache:1;
+};
+
+static void result_node_params(void *data, int seq, int res, uint32_t type, const void *result)
+{
+ struct result_node_params_data *d = data;
+ struct impl *impl = d->impl;
+ switch (type) {
+ case SPA_RESULT_TYPE_NODE_PARAMS:
+ {
+ const struct spa_result_node_params *r = result;
+ if (d->seq == seq) {
+ d->callback(d->data, seq, r->id, r->index, r->next, r->param);
+ if (d->cache) {
+ if (d->count++ == 0)
+ pw_param_add(&impl->pending_list, seq, r->id, NULL);
+ pw_param_add(&impl->pending_list, seq, r->id, r->param);
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+SPA_EXPORT
+int pw_impl_node_for_each_param(struct pw_impl_node *node,
+ int seq, uint32_t param_id,
+ uint32_t index, uint32_t max,
+ const struct spa_pod *filter,
+ int (*callback) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ struct spa_pod *param),
+ void *data)
+{
+ int res;
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+ struct result_node_params_data user_data = { impl, data, callback, seq, 0, false };
+ struct spa_hook listener;
+ struct spa_param_info *pi;
+ static const struct spa_node_events node_events = {
+ SPA_VERSION_NODE_EVENTS,
+ .result = result_node_params,
+ };
+
+ pi = pw_param_info_find(node->info.params, node->info.n_params, param_id);
+ if (pi == NULL)
+ return -ENOENT;
+
+ if (max == 0)
+ max = UINT32_MAX;
+
+ pw_log_debug("%p: params id:%d (%s) index:%u max:%u cached:%d", node, param_id,
+ spa_debug_type_find_name(spa_type_param, param_id),
+ index, max, pi->user);
+
+ if (pi->user == 1) {
+ struct pw_param *p;
+ uint8_t buffer[4096];
+ struct spa_pod_dynamic_builder b;
+ struct spa_result_node_params result;
+ uint32_t count = 0;
+
+ result.id = param_id;
+ result.next = 0;
+
+ spa_list_for_each(p, &impl->param_list, link) {
+ if (p->id != param_id)
+ continue;
+
+ result.index = result.next++;
+ if (result.index < index)
+ continue;
+
+ spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
+
+ if (spa_pod_filter(&b.b, &result.param, p->param, filter) == 0) {
+ pw_log_debug("%p: %d param %u", node, seq, result.index);
+ result_node_params(&user_data, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+ count++;
+ }
+ spa_pod_dynamic_builder_clean(&b);
+
+ if (count == max)
+ break;
+ }
+ res = 0;
+ } else {
+ user_data.cache = impl->cache_params &&
+ (filter == NULL && index == 0 && max == UINT32_MAX);
+
+ spa_zero(listener);
+ spa_node_add_listener(node->node, &listener, &node_events, &user_data);
+ res = spa_node_enum_params(node->node, seq,
+ param_id, index, max,
+ filter);
+ spa_hook_remove(&listener);
+
+ if (user_data.cache) {
+ pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL);
+ pi->user = 1;
+ }
+ }
+ return res;
+}
+
+SPA_EXPORT
+int pw_impl_node_set_param(struct pw_impl_node *node,
+ uint32_t id, uint32_t flags, const struct spa_pod *param)
+{
+ pw_log_debug("%p: set_param id:%d (%s) flags:%08x param:%p", node, id,
+ spa_debug_type_find_name(spa_type_param, id), flags, param);
+ return spa_node_set_param(node->node, id, flags, param);
+}
+
+SPA_EXPORT
+struct pw_impl_port *
+pw_impl_node_find_port(struct pw_impl_node *node, enum pw_direction direction, uint32_t port_id)
+{
+ struct pw_impl_port *port, *p;
+ struct pw_map *portmap;
+ struct spa_list *ports;
+
+ if (direction == PW_DIRECTION_INPUT) {
+ portmap = &node->input_port_map;
+ ports = &node->input_ports;
+ } else {
+ portmap = &node->output_port_map;
+ ports = &node->output_ports;
+ }
+
+ if (port_id != PW_ID_ANY)
+ port = pw_map_lookup(portmap, port_id);
+ else {
+ port = NULL;
+ /* try to find an unlinked port */
+ spa_list_for_each(p, ports, link) {
+ if (spa_list_is_empty(&p->links)) {
+ port = p;
+ break;
+ }
+ /* We can use this port if it can multiplex */
+ if (SPA_FLAG_IS_SET(p->mix_flags, PW_IMPL_PORT_MIX_FLAG_MULTI))
+ port = p;
+ }
+ }
+ pw_log_debug("%p: return %s port %d: %p", node,
+ pw_direction_as_string(direction), port_id, port);
+ return port;
+}
+
+SPA_EXPORT
+uint32_t pw_impl_node_get_free_port_id(struct pw_impl_node *node, enum pw_direction direction)
+{
+ uint32_t n_ports, max_ports;
+ struct pw_map *portmap;
+ uint32_t port_id;
+ bool dynamic;
+ int res;
+
+ if (direction == PW_DIRECTION_INPUT) {
+ max_ports = node->info.max_input_ports;
+ n_ports = node->info.n_input_ports;
+ portmap = &node->input_port_map;
+ dynamic = SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_IN_DYNAMIC_PORTS);
+ } else {
+ max_ports = node->info.max_output_ports;
+ n_ports = node->info.n_output_ports;
+ portmap = &node->output_port_map;
+ dynamic = SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_OUT_DYNAMIC_PORTS);
+ }
+ pw_log_debug("%p: direction %s n_ports:%u max_ports:%u",
+ node, pw_direction_as_string(direction), n_ports, max_ports);
+
+ if (!dynamic || n_ports >= max_ports) {
+ res = -ENOSPC;
+ goto error;
+ }
+
+ port_id = pw_map_insert_new(portmap, NULL);
+ if (port_id == SPA_ID_INVALID) {
+ res = -errno;
+ goto error;
+ }
+
+ pw_log_debug("%p: free port %d", node, port_id);
+
+ return port_id;
+
+error:
+ pw_log_warn("%p: no more port available: %s", node, spa_strerror(res));
+ errno = -res;
+ return SPA_ID_INVALID;
+}
+
+static void on_state_complete(void *obj, void *data, int res, uint32_t seq)
+{
+ struct pw_impl_node *node = obj;
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+ enum pw_node_state state = SPA_PTR_TO_INT(data);
+ char *error = NULL;
+
+ /* driver nodes added -EBUSY. This is then not an error */
+ if (res == -EBUSY)
+ res = 0;
+
+ impl->pending_id = SPA_ID_INVALID;
+ impl->pending_play = false;
+
+ pw_log_debug("%p: state complete res:%d seq:%d", node, res, seq);
+ if (impl->last_error < 0) {
+ res = impl->last_error;
+ impl->last_error = 0;
+ }
+ if (SPA_RESULT_IS_ERROR(res)) {
+ if (node->info.state == PW_NODE_STATE_SUSPENDED) {
+ state = PW_NODE_STATE_SUSPENDED;
+ res = 0;
+ } else {
+ error = spa_aprintf("error changing node state: %s", spa_strerror(res));
+ state = PW_NODE_STATE_ERROR;
+ }
+ }
+ node_update_state(node, state, res, error);
+}
+
+/** Set the node state
+ * \param node a \ref pw_impl_node
+ * \param state a \ref pw_node_state
+ * \return 0 on success < 0 on error
+ *
+ * Set the state of \a node to \a state.
+ */
+SPA_EXPORT
+int pw_impl_node_set_state(struct pw_impl_node *node, enum pw_node_state state)
+{
+ int res = 0;
+ struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this);
+ enum pw_node_state old = impl->pending_state;
+
+ pw_log_debug("%p: set state (%s) %s -> %s, active %d pause_on_idle:%d", node,
+ pw_node_state_as_string(node->info.state),
+ pw_node_state_as_string(old),
+ pw_node_state_as_string(state),
+ node->active,
+ node->pause_on_idle);
+
+ if (old != state)
+ pw_impl_node_emit_state_request(node, state);
+
+ switch (state) {
+ case PW_NODE_STATE_CREATING:
+ return -EIO;
+
+ case PW_NODE_STATE_SUSPENDED:
+ res = suspend_node(node);
+ break;
+
+ case PW_NODE_STATE_IDLE:
+ res = idle_node(node);
+ break;
+
+ case PW_NODE_STATE_RUNNING:
+ if (node->active)
+ res = start_node(node);
+ break;
+
+ case PW_NODE_STATE_ERROR:
+ break;
+ }
+ if (SPA_RESULT_IS_ERROR(res))
+ return res;
+
+ if (SPA_RESULT_IS_ASYNC(res)) {
+ res = spa_node_sync(node->node, res);
+ }
+
+ if (old != state) {
+ if (impl->pending_id != SPA_ID_INVALID) {
+ pw_log_debug("cancel state from %s to %s to %s",
+ pw_node_state_as_string(node->info.state),
+ pw_node_state_as_string(impl->pending_state),
+ pw_node_state_as_string(state));
+
+ if (impl->pending_state == PW_NODE_STATE_RUNNING &&
+ state < PW_NODE_STATE_RUNNING &&
+ impl->pending_play) {
+ impl->pending_play = false;
+ idle_node(node);
+ }
+ pw_work_queue_cancel(impl->work, node, impl->pending_id);
+ node->info.state = impl->pending_state;
+ }
+ /* driver nodes return EBUSY to add a -EBUSY to the work queue. This
+ * will wait until all previous items in the work queue are
+ * completed */
+ impl->pending_state = state;
+ impl->pending_id = pw_work_queue_add(impl->work,
+ node, res == EBUSY ? -EBUSY : res,
+ on_state_complete, SPA_INT_TO_PTR(state));
+ }
+ return res;
+}
+
+SPA_EXPORT
+int pw_impl_node_set_active(struct pw_impl_node *node, bool active)
+{
+ bool old = node->active;
+
+ if (old != active) {
+ pw_log_debug("%p: %s registered:%d exported:%d", node,
+ active ? "activate" : "deactivate",
+ node->registered, node->exported);
+
+ node->active = active;
+ pw_impl_node_emit_active_changed(node, active);
+
+ if (node->registered)
+ pw_context_recalc_graph(node->context,
+ active ? "node activate" : "node deactivate");
+ else if (!active && node->exported)
+ pw_loop_invoke(node->data_loop, do_node_remove, 1, NULL, 0, true, node);
+ }
+ return 0;
+}
+
+SPA_EXPORT
+bool pw_impl_node_is_active(struct pw_impl_node *node)
+{
+ return node->active;
+}
+
+SPA_EXPORT
+int pw_impl_node_send_command(struct pw_impl_node *node, const struct spa_command *command)
+{
+ return spa_node_send_command(node->node, command);
+}
diff --git a/src/pipewire/impl-node.h b/src/pipewire/impl-node.h
new file mode 100644
index 0000000..978a6d2
--- /dev/null
+++ b/src/pipewire/impl-node.h
@@ -0,0 +1,190 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_NODE_H
+#define PIPEWIRE_IMPL_NODE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \defgroup pw_impl_node Node Impl
+ *
+ * The node object processes data. The node has a list of
+ * input and output ports (\ref pw_impl_port) on which it
+ * will receive and send out buffers respectively.
+ */
+/**
+ * \addtogroup pw_impl_node
+ * \{
+ */
+struct pw_impl_node;
+struct pw_impl_port;
+
+#include <spa/node/node.h>
+#include <spa/node/event.h>
+
+#include <pipewire/impl.h>
+
+/** Node events, listen to them with \ref pw_impl_node_add_listener */
+struct pw_impl_node_events {
+#define PW_VERSION_IMPL_NODE_EVENTS 0
+ uint32_t version;
+
+ /** the node is destroyed */
+ void (*destroy) (void *data);
+ /** the node is about to be freed */
+ void (*free) (void *data);
+ /** the node is initialized */
+ void (*initialized) (void *data);
+
+ /** a port is being initialized on the node */
+ void (*port_init) (void *data, struct pw_impl_port *port);
+ /** a port was added */
+ void (*port_added) (void *data, struct pw_impl_port *port);
+ /** a port was removed */
+ void (*port_removed) (void *data, struct pw_impl_port *port);
+
+ /** the node info changed */
+ void (*info_changed) (void *data, const struct pw_node_info *info);
+ /** a port on the node changed info */
+ void (*port_info_changed) (void *data, struct pw_impl_port *port,
+ const struct pw_port_info *info);
+ /** the node active state changed */
+ void (*active_changed) (void *data, bool active);
+
+ /** a new state is requested on the node */
+ void (*state_request) (void *data, enum pw_node_state state);
+ /** the state of the node changed */
+ void (*state_changed) (void *data, enum pw_node_state old,
+ enum pw_node_state state, const char *error);
+
+ /** a result was received */
+ void (*result) (void *data, int seq, int res, uint32_t type, const void *result);
+
+ /** an event is emitted */
+ void (*event) (void *data, const struct spa_event *event);
+
+ /** the driver of the node changed */
+ void (*driver_changed) (void *data, struct pw_impl_node *old, struct pw_impl_node *driver);
+
+ /** a peer was added */
+ void (*peer_added) (void *data, struct pw_impl_node *peer);
+ /** a peer was removed */
+ void (*peer_removed) (void *data, struct pw_impl_node *peer);
+};
+
+/** Create a new node */
+struct pw_impl_node *
+pw_context_create_node(struct pw_context *context, /**< the context */
+ struct pw_properties *properties, /**< extra properties */
+ size_t user_data_size /**< user data size */);
+
+/** Complete initialization of the node and register */
+int pw_impl_node_register(struct pw_impl_node *node, /**< node to register */
+ struct pw_properties *properties /**< extra properties */);
+
+/** Destroy a node */
+void pw_impl_node_destroy(struct pw_impl_node *node);
+
+/** Get the node info */
+const struct pw_node_info *pw_impl_node_get_info(struct pw_impl_node *node);
+
+/** Get node user_data. The size of the memory was given in \ref pw_context_create_node */
+void * pw_impl_node_get_user_data(struct pw_impl_node *node);
+
+/** Get the context of this node */
+struct pw_context *pw_impl_node_get_context(struct pw_impl_node *node);
+
+/** Get the global of this node */
+struct pw_global *pw_impl_node_get_global(struct pw_impl_node *node);
+
+/** Get the node properties */
+const struct pw_properties *pw_impl_node_get_properties(struct pw_impl_node *node);
+
+/** Update the node properties */
+int pw_impl_node_update_properties(struct pw_impl_node *node, const struct spa_dict *dict);
+
+/** Set the node implementation */
+int pw_impl_node_set_implementation(struct pw_impl_node *node, struct spa_node *spa_node);
+
+/** Get the node implementation */
+struct spa_node *pw_impl_node_get_implementation(struct pw_impl_node *node);
+
+/** Add an event listener */
+void pw_impl_node_add_listener(struct pw_impl_node *node,
+ struct spa_hook *listener,
+ const struct pw_impl_node_events *events,
+ void *data);
+
+/** Iterate the ports in the given direction. The callback should return
+ * 0 to fetch the next item, any other value stops the iteration and returns
+ * the value. When all callbacks return 0, this function returns 0 when all
+ * items are iterated. */
+int pw_impl_node_for_each_port(struct pw_impl_node *node,
+ enum pw_direction direction,
+ int (*callback) (void *data, struct pw_impl_port *port),
+ void *data);
+
+int pw_impl_node_for_each_param(struct pw_impl_node *node,
+ int seq, uint32_t param_id,
+ uint32_t index, uint32_t max,
+ const struct spa_pod *filter,
+ int (*callback) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ struct spa_pod *param),
+ void *data);
+
+/** Find the port with direction and port_id or NULL when not found. Passing
+ * PW_ID_ANY for port_id will return any port, preferably an unlinked one. */
+struct pw_impl_port *
+pw_impl_node_find_port(struct pw_impl_node *node, enum pw_direction direction, uint32_t port_id);
+
+/** Get a free unused port_id from the node */
+uint32_t pw_impl_node_get_free_port_id(struct pw_impl_node *node, enum pw_direction direction);
+
+int pw_impl_node_initialized(struct pw_impl_node *node);
+
+/** Set a node active. This will start negotiation with all linked active
+ * nodes and start data transport */
+int pw_impl_node_set_active(struct pw_impl_node *node, bool active);
+
+/** Check if a node is active */
+bool pw_impl_node_is_active(struct pw_impl_node *node);
+
+/** Check if a node is active, Since 0.3.39 */
+int pw_impl_node_send_command(struct pw_impl_node *node, const struct spa_command *command);
+
+/** Set a param on the node, Since 0.3.65 */
+int pw_impl_node_set_param(struct pw_impl_node *node,
+ uint32_t id, uint32_t flags, const struct spa_pod *param);
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_NODE_H */
diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c
new file mode 100644
index 0000000..ef8fa06
--- /dev/null
+++ b/src/pipewire/impl-port.c
@@ -0,0 +1,1614 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <float.h>
+
+#include <spa/pod/parser.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/node/utils.h>
+#include <spa/utils/names.h>
+#include <spa/utils/string.h>
+#include <spa/debug/types.h>
+#include <spa/pod/filter.h>
+#include <spa/pod/dynamic.h>
+
+#include "pipewire/impl.h"
+#include "pipewire/private.h"
+
+PW_LOG_TOPIC_EXTERN(log_port);
+#define PW_LOG_TOPIC_DEFAULT log_port
+
+/** \cond */
+struct impl {
+ struct pw_impl_port this;
+ struct spa_node mix_node; /**< mix node implementation */
+
+ struct spa_list param_list;
+ struct spa_list pending_list;
+
+ unsigned int cache_params:1;
+};
+
+#define pw_port_resource(r,m,v,...) pw_resource_call(r,struct pw_port_events,m,v,__VA_ARGS__)
+#define pw_port_resource_info(r,...) pw_port_resource(r,info,0,__VA_ARGS__)
+#define pw_port_resource_param(r,...) pw_port_resource(r,param,0,__VA_ARGS__)
+
+struct resource_data {
+ struct pw_impl_port *port;
+ struct pw_resource *resource;
+
+ struct spa_hook resource_listener;
+ struct spa_hook object_listener;
+
+ uint32_t subscribe_ids[MAX_PARAMS];
+ uint32_t n_subscribe_ids;
+};
+
+/** \endcond */
+
+static void emit_info_changed(struct pw_impl_port *port)
+{
+ struct pw_resource *resource;
+
+ if (port->info.change_mask == 0)
+ return;
+
+ pw_impl_port_emit_info_changed(port, &port->info);
+ if (port->node)
+ pw_impl_node_emit_port_info_changed(port->node, port, &port->info);
+
+ if (port->global)
+ spa_list_for_each(resource, &port->global->resource_list, link)
+ pw_port_resource_info(resource, &port->info);
+
+ port->info.change_mask = 0;
+}
+
+static const char *port_state_as_string(enum pw_impl_port_state state)
+{
+ switch (state) {
+ case PW_IMPL_PORT_STATE_ERROR:
+ return "error";
+ case PW_IMPL_PORT_STATE_INIT:
+ return "init";
+ case PW_IMPL_PORT_STATE_CONFIGURE:
+ return "configure";
+ case PW_IMPL_PORT_STATE_READY:
+ return "ready";
+ case PW_IMPL_PORT_STATE_PAUSED:
+ return "paused";
+ }
+ return "invalid-state";
+}
+
+void pw_impl_port_update_state(struct pw_impl_port *port, enum pw_impl_port_state state, int res, char *error)
+{
+ enum pw_impl_port_state old = port->state;
+
+ port->state = state;
+ free((void*)port->error);
+ port->error = error;
+
+ if (old == state)
+ return;
+
+ pw_log(state == PW_IMPL_PORT_STATE_ERROR ?
+ SPA_LOG_LEVEL_ERROR : SPA_LOG_LEVEL_DEBUG,
+ "%p: state %s -> %s (%s)", port,
+ port_state_as_string(old), port_state_as_string(state), error);
+
+ pw_impl_port_emit_state_changed(port, old, state, error);
+
+ if (state == PW_IMPL_PORT_STATE_ERROR && port->global) {
+ struct pw_resource *resource;
+ spa_list_for_each(resource, &port->global->resource_list, link)
+ pw_resource_error(resource, res, error);
+ }
+}
+
+static int tee_process(void *object)
+{
+ struct impl *impl = object;
+ struct pw_impl_port *this = &impl->this;
+ struct pw_impl_port_mix *mix;
+ struct spa_io_buffers *io = &this->rt.io;
+
+ pw_log_trace_fp("%p: tee input %d %d", this, io->status, io->buffer_id);
+ spa_list_for_each(mix, &this->rt.mix_list, rt_link) {
+ pw_log_trace_fp("%p: port %d %p->%p %d", this,
+ mix->port.port_id, io, mix->io, mix->io->buffer_id);
+ *mix->io = *io;
+ }
+ io->status = SPA_STATUS_NEED_DATA;
+
+ return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA;
+}
+
+static int tee_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id)
+{
+ struct impl *impl = object;
+ struct pw_impl_port *this = &impl->this;
+
+ pw_log_trace_fp("%p: tee reuse buffer %d %d", this, port_id, buffer_id);
+ spa_node_port_reuse_buffer(this->node->node, this->port_id, buffer_id);
+
+ return 0;
+}
+
+static const struct spa_node_methods schedule_tee_node = {
+ SPA_VERSION_NODE_METHODS,
+ .process = tee_process,
+ .port_reuse_buffer = tee_reuse_buffer,
+};
+
+static int schedule_mix_input(void *object)
+{
+ struct impl *impl = object;
+ struct pw_impl_port *this = &impl->this;
+ struct spa_io_buffers *io = &this->rt.io;
+ struct pw_impl_port_mix *mix;
+
+ if (SPA_UNLIKELY(PW_IMPL_PORT_IS_CONTROL(this)))
+ return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA;
+
+ spa_list_for_each(mix, &this->rt.mix_list, rt_link) {
+ pw_log_trace_fp("%p: mix input %d %p->%p %d %d", this,
+ mix->port.port_id, mix->io, io, mix->io->status, mix->io->buffer_id);
+ *io = *mix->io;
+ mix->io->status = SPA_STATUS_NEED_DATA;
+ break;
+ }
+ return SPA_STATUS_HAVE_DATA | SPA_STATUS_NEED_DATA;
+}
+
+static int schedule_mix_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id)
+{
+ struct impl *impl = object;
+ struct pw_impl_port *this = &impl->this;
+ struct pw_impl_port_mix *mix;
+
+ spa_list_for_each(mix, &this->rt.mix_list, rt_link) {
+ pw_log_trace_fp("%p: reuse buffer %d %d", this, port_id, buffer_id);
+ /* FIXME send reuse buffer to peer */
+ break;
+ }
+ return 0;
+}
+
+static const struct spa_node_methods schedule_mix_node = {
+ SPA_VERSION_NODE_METHODS,
+ .process = schedule_mix_input,
+ .port_reuse_buffer = schedule_mix_reuse_buffer,
+};
+
+SPA_EXPORT
+int pw_impl_port_init_mix(struct pw_impl_port *port, struct pw_impl_port_mix *mix)
+{
+ uint32_t port_id;
+ struct pw_impl_node *node = port->node;
+ int res = 0;
+
+ port_id = pw_map_insert_new(&port->mix_port_map, mix);
+ if (port_id == SPA_ID_INVALID)
+ return -errno;
+
+ if ((res = spa_node_add_port(port->mix, port->direction, port_id, NULL)) < 0 &&
+ res != -ENOTSUP)
+ goto error_remove_map;
+
+ mix->port.direction = port->direction;
+ mix->port.port_id = port_id;
+ mix->p = port;
+
+ if ((res = pw_impl_port_call_init_mix(port, mix)) < 0)
+ goto error_remove_port;
+
+ /* set the same format on the mixer as on the port if any */
+ {
+ uint32_t idx = 0;
+ uint8_t buffer[1024];
+ struct spa_pod_dynamic_builder b;
+ struct spa_pod *param;
+
+ spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
+
+ if (spa_node_port_enum_params_sync(port->mix,
+ pw_direction_reverse(port->direction), 0,
+ SPA_PARAM_Format, &idx, NULL, &param, &b.b) == 1) {
+ spa_node_port_set_param(port->mix,
+ port->direction, port_id,
+ SPA_PARAM_Format, 0, param);
+ }
+ spa_pod_dynamic_builder_clean(&b);
+ }
+
+ spa_list_append(&port->mix_list, &mix->link);
+ port->n_mix++;
+
+ pw_log_debug("%p: init mix n_mix:%d %d.%d io:%p: (%s)", port,
+ port->n_mix, port->port_id, mix->port.port_id,
+ mix->io, spa_strerror(res));
+
+ if (port->n_mix == 1) {
+ pw_log_debug("%p: setting port io", port);
+ spa_node_port_set_io(node->node,
+ port->direction, port->port_id,
+ SPA_IO_Buffers,
+ &port->rt.io, sizeof(port->rt.io));
+ }
+ return res;
+
+error_remove_port:
+ spa_node_remove_port(port->mix, port->direction, port_id);
+error_remove_map:
+ pw_map_remove(&port->mix_port_map, port_id);
+ return res;
+}
+
+SPA_EXPORT
+int pw_impl_port_release_mix(struct pw_impl_port *port, struct pw_impl_port_mix *mix)
+{
+ int res = 0;
+ uint32_t port_id = mix->port.port_id;
+ struct pw_impl_node *node = port->node;
+
+ pw_map_remove(&port->mix_port_map, port_id);
+ spa_list_remove(&mix->link);
+ port->n_mix--;
+
+ res = pw_impl_port_call_release_mix(port, mix);
+
+ if ((res = spa_node_remove_port(port->mix, port->direction, port_id)) < 0 &&
+ res != -ENOTSUP)
+ pw_log_warn("can't remove mix port %d: %s", port_id, spa_strerror(res));
+
+ pw_log_debug("%p: release mix %d %d.%d", port,
+ port->n_mix, port->port_id, mix->port.port_id);
+
+ if (port->n_mix == 0) {
+ pw_log_debug("%p: clearing port io", port);
+ spa_node_port_set_io(node->node,
+ port->direction, port->port_id,
+ SPA_IO_Buffers,
+ NULL, sizeof(port->rt.io));
+ }
+ return res;
+}
+
+static int update_properties(struct pw_impl_port *port, const struct spa_dict *dict, bool filter)
+{
+ static const char * const ignored[] = {
+ PW_KEY_OBJECT_ID,
+ PW_KEY_PORT_DIRECTION,
+ PW_KEY_PORT_CONTROL,
+ PW_KEY_NODE_ID,
+ PW_KEY_PORT_ID,
+ NULL
+ };
+
+ int changed;
+
+ changed = pw_properties_update_ignore(port->properties, dict, filter ? ignored : NULL);
+ port->info.props = &port->properties->dict;
+
+ if (changed) {
+ pw_log_debug("%p: updated %d properties", port, changed);
+ port->info.change_mask |= PW_PORT_CHANGE_MASK_PROPS;
+ }
+ return changed;
+}
+
+static int resource_is_subscribed(struct pw_resource *resource, uint32_t id)
+{
+ struct resource_data *data = pw_resource_get_user_data(resource);
+ uint32_t i;
+
+ for (i = 0; i < data->n_subscribe_ids; i++) {
+ if (data->subscribe_ids[i] == id)
+ return 1;
+ }
+ return 0;
+}
+
+static int notify_param(void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next, struct spa_pod *param)
+{
+ struct pw_impl_port *port = data;
+ struct pw_resource *resource;
+
+ spa_list_for_each(resource, &port->global->resource_list, link) {
+ if (!resource_is_subscribed(resource, id))
+ continue;
+
+ pw_log_debug("%p: resource %p notify param %d", port, resource, id);
+ pw_port_resource_param(resource, seq, id, index, next, param);
+ }
+ return 0;
+}
+
+static void emit_params(struct pw_impl_port *port, uint32_t *changed_ids, uint32_t n_changed_ids)
+{
+ uint32_t i;
+ int res;
+
+ if (port->global == NULL)
+ return;
+
+ pw_log_debug("%p: emit %d params", port, n_changed_ids);
+
+ for (i = 0; i < n_changed_ids; i++) {
+ struct pw_resource *resource;
+ int subscribed = 0;
+
+ pw_log_debug("%p: emit param %d/%d: %d", port, i, n_changed_ids,
+ changed_ids[i]);
+
+ pw_impl_port_emit_param_changed(port, changed_ids[i]);
+
+ /* first check if anyone is subscribed */
+ spa_list_for_each(resource, &port->global->resource_list, link) {
+ if ((subscribed = resource_is_subscribed(resource, changed_ids[i])))
+ break;
+ }
+ if (!subscribed)
+ continue;
+
+ if ((res = pw_impl_port_for_each_param(port, 1, changed_ids[i], 0, UINT32_MAX,
+ NULL, notify_param, port)) < 0) {
+ pw_log_error("%p: error %d (%s)", port, res, spa_strerror(res));
+ }
+ }
+}
+
+static int process_latency_param(void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next, struct spa_pod *param)
+{
+ struct pw_impl_port *this = data;
+ struct spa_latency_info latency;
+
+ if (id != SPA_PARAM_Latency)
+ return -EINVAL;
+
+ if (spa_latency_parse(param, &latency) < 0)
+ return 0;
+ if (spa_latency_info_compare(&this->latency[latency.direction], &latency) == 0)
+ return 0;
+
+ pw_log_debug("port %p: got %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, this,
+ pw_direction_as_string(latency.direction),
+ latency.min_quantum, latency.max_quantum,
+ latency.min_rate, latency.max_rate,
+ latency.min_ns, latency.max_ns);
+
+ this->latency[latency.direction] = latency;
+ if (latency.direction == this->direction)
+ pw_impl_port_emit_latency_changed(this);
+
+ return 0;
+}
+
+static void update_info(struct pw_impl_port *port, const struct spa_port_info *info)
+{
+ uint32_t changed_ids[MAX_PARAMS], n_changed_ids = 0;
+
+ pw_log_debug("%p: %p flags:%08"PRIx64" change_mask:%08"PRIx64,
+ port, info, info->flags, info->change_mask);
+
+ if (info->change_mask & SPA_PORT_CHANGE_MASK_FLAGS) {
+ port->spa_flags = info->flags;
+ }
+ if (info->change_mask & SPA_PORT_CHANGE_MASK_PROPS) {
+ if (info->props) {
+ update_properties(port, info->props, true);
+ } else {
+ pw_log_warn("%p: port PROPS update but no properties", port);
+ }
+ }
+ if (info->change_mask & SPA_PORT_CHANGE_MASK_PARAMS) {
+ uint32_t i;
+
+ port->info.change_mask |= PW_PORT_CHANGE_MASK_PARAMS;
+ port->info.n_params = SPA_MIN(info->n_params, SPA_N_ELEMENTS(port->params));
+
+ for (i = 0; i < port->info.n_params; i++) {
+ uint32_t id = info->params[i].id;
+
+ pw_log_debug("%p: param %d id:%d (%s) %08x:%08x", port, i,
+ id, spa_debug_type_find_name(spa_type_param, id),
+ port->info.params[i].flags, info->params[i].flags);
+
+ port->info.params[i].id = info->params[i].id;
+ if (port->info.params[i].flags == info->params[i].flags)
+ continue;
+
+ pw_log_debug("%p: update param %d", port, id);
+ port->info.params[i] = info->params[i];
+ port->info.params[i].user = 0;
+
+ if (info->params[i].flags & SPA_PARAM_INFO_READ)
+ changed_ids[n_changed_ids++] = id;
+
+ switch (id) {
+ case SPA_PARAM_Latency:
+ port->have_latency_param =
+ SPA_FLAG_IS_SET(info->params[i].flags, SPA_PARAM_INFO_WRITE);
+ if (port->node != NULL)
+ pw_impl_port_for_each_param(port, 0, id, 0, UINT32_MAX,
+ NULL, process_latency_param, port);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (n_changed_ids > 0)
+ emit_params(port, changed_ids, n_changed_ids);
+}
+
+SPA_EXPORT
+struct pw_impl_port *pw_context_create_port(
+ struct pw_context *context,
+ enum pw_direction direction,
+ uint32_t port_id,
+ const struct spa_port_info *info,
+ size_t user_data_size)
+{
+ struct impl *impl;
+ struct pw_impl_port *this;
+ struct pw_properties *properties;
+ const struct spa_node_methods *mix_methods;
+ int res;
+
+ impl = calloc(1, sizeof(struct impl) + user_data_size);
+ if (impl == NULL)
+ return NULL;
+
+ spa_list_init(&impl->param_list);
+ spa_list_init(&impl->pending_list);
+ impl->cache_params = true;
+
+ this = &impl->this;
+ pw_log_debug("%p: new %s %d", this,
+ pw_direction_as_string(direction), port_id);
+
+ if (info && info->change_mask & SPA_PORT_CHANGE_MASK_PROPS && info->props)
+ properties = pw_properties_new_dict(info->props);
+ else
+ properties = pw_properties_new(NULL, NULL);
+
+ if (properties == NULL) {
+ res = -errno;
+ goto error_no_mem;
+ }
+ pw_properties_setf(properties, PW_KEY_PORT_ID, "%u", port_id);
+
+ if (info) {
+ if (SPA_FLAG_IS_SET(info->flags, SPA_PORT_FLAG_PHYSICAL))
+ pw_properties_set(properties, PW_KEY_PORT_PHYSICAL, "true");
+ if (SPA_FLAG_IS_SET(info->flags, SPA_PORT_FLAG_TERMINAL))
+ pw_properties_set(properties, PW_KEY_PORT_TERMINAL, "true");
+ this->spa_flags = info->flags;
+ }
+
+ this->direction = direction;
+ this->port_id = port_id;
+ this->properties = properties;
+ this->state = PW_IMPL_PORT_STATE_INIT;
+ this->rt.io = SPA_IO_BUFFERS_INIT;
+
+ if (user_data_size > 0)
+ this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void);
+
+ this->info.direction = direction;
+ this->info.params = this->params;
+ this->info.change_mask = PW_PORT_CHANGE_MASK_PROPS;
+ this->info.props = &this->properties->dict;
+
+ spa_list_init(&this->links);
+ spa_list_init(&this->mix_list);
+ spa_list_init(&this->rt.mix_list);
+ spa_list_init(&this->control_list[0]);
+ spa_list_init(&this->control_list[1]);
+
+ spa_hook_list_init(&this->listener_list);
+
+ if (this->direction == PW_DIRECTION_INPUT)
+ mix_methods = &schedule_mix_node;
+ else
+ mix_methods = &schedule_tee_node;
+
+ impl->mix_node.iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_Node,
+ SPA_VERSION_NODE,
+ mix_methods, impl);
+
+ pw_impl_port_set_mix(this, NULL, 0);
+
+ pw_map_init(&this->mix_port_map, 64, 64);
+
+ this->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT);
+ this->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT);
+
+ if (info)
+ update_info(this, info);
+
+ return this;
+
+error_no_mem:
+ pw_log_warn("%p: new failed", impl);
+ free(impl);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+int pw_impl_port_set_mix(struct pw_impl_port *port, struct spa_node *node, uint32_t flags)
+{
+ struct impl *impl = SPA_CONTAINER_OF(port, struct impl, this);
+ struct pw_impl_port_mix *mix;
+
+ if (node == NULL) {
+ node = &impl->mix_node;
+ flags = 0;
+ }
+
+ pw_log_debug("%p: mix node %p->%p", port, port->mix, node);
+
+ if (port->mix != NULL && port->mix != node) {
+ spa_list_for_each(mix, &port->mix_list, link)
+ spa_node_remove_port(port->mix, mix->port.direction, mix->port.port_id);
+
+ spa_node_port_set_io(port->mix,
+ pw_direction_reverse(port->direction), 0,
+ SPA_IO_Buffers, NULL, 0);
+ }
+ if (port->mix_handle != NULL) {
+ pw_unload_spa_handle(port->mix_handle);
+ port->mix_handle = NULL;
+ }
+
+ port->mix_flags = flags;
+ port->mix = node;
+
+ if (port->mix) {
+ spa_list_for_each(mix, &port->mix_list, link)
+ spa_node_add_port(port->mix, mix->port.direction, mix->port.port_id, NULL);
+
+ spa_node_port_set_io(port->mix,
+ pw_direction_reverse(port->direction), 0,
+ SPA_IO_Buffers,
+ &port->rt.io, sizeof(port->rt.io));
+ }
+ return 0;
+}
+
+static int setup_mixer(struct pw_impl_port *port, const struct spa_pod *param)
+{
+ uint32_t media_type, media_subtype;
+ int res;
+ const char *fallback_lib, *factory_name;
+ struct spa_handle *handle;
+ struct spa_dict_item items[2];
+ char quantum_limit[16];
+ void *iface;
+ struct pw_context *context = port->node->context;
+
+ if ((res = spa_format_parse(param, &media_type, &media_subtype)) < 0)
+ return res;
+
+ pw_log_debug("%p: %s/%s", port,
+ spa_debug_type_find_name(spa_type_media_type, media_type),
+ spa_debug_type_find_name(spa_type_media_subtype, media_subtype));
+
+ switch (media_type) {
+ case SPA_MEDIA_TYPE_audio:
+ switch (media_subtype) {
+ case SPA_MEDIA_SUBTYPE_dsp:
+ {
+ struct spa_audio_info_dsp info;
+ if ((res = spa_format_audio_dsp_parse(param, &info)) < 0)
+ return res;
+
+ if (info.format != SPA_AUDIO_FORMAT_DSP_F32)
+ return -ENOTSUP;
+
+ fallback_lib = "audiomixer/libspa-audiomixer";
+ factory_name = SPA_NAME_AUDIO_MIXER_DSP;
+ break;
+ }
+ case SPA_MEDIA_SUBTYPE_raw:
+ fallback_lib = "audiomixer/libspa-audiomixer";
+ factory_name = SPA_NAME_AUDIO_MIXER;
+ break;
+ default:
+ return -ENOTSUP;
+ }
+ break;
+
+ case SPA_MEDIA_TYPE_application:
+ switch (media_subtype) {
+ case SPA_MEDIA_SUBTYPE_control:
+ fallback_lib = "control/libspa-control";
+ factory_name = SPA_NAME_CONTROL_MIXER;
+ break;
+ default:
+ return -ENOTSUP;
+ }
+ break;
+
+ default:
+ return -ENOTSUP;
+ }
+
+ items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_LIBRARY_NAME, fallback_lib);
+ spa_scnprintf(quantum_limit, sizeof(quantum_limit), "%u",
+ context->settings.clock_quantum_limit);
+ items[1] = SPA_DICT_ITEM_INIT("clock.quantum-limit", quantum_limit);
+
+ handle = pw_context_load_spa_handle(context, factory_name,
+ &SPA_DICT_INIT_ARRAY(items));
+ if (handle == NULL)
+ return -errno;
+
+ if ((res = spa_handle_get_interface(handle,
+ SPA_TYPE_INTERFACE_Node, &iface)) < 0) {
+ pw_unload_spa_handle(handle);
+ return res;
+ }
+
+ pw_log_debug("mix node handle:%p iface:%p", handle, iface);
+ pw_impl_port_set_mix(port, (struct spa_node*)iface,
+ PW_IMPL_PORT_MIX_FLAG_MULTI |
+ PW_IMPL_PORT_MIX_FLAG_NEGOTIATE);
+ port->mix_handle = handle;
+
+ return 0;
+}
+
+SPA_EXPORT
+enum pw_direction pw_impl_port_get_direction(struct pw_impl_port *port)
+{
+ return port->direction;
+}
+
+SPA_EXPORT
+uint32_t pw_impl_port_get_id(struct pw_impl_port *port)
+{
+ return port->port_id;
+}
+
+SPA_EXPORT
+const struct pw_properties *pw_impl_port_get_properties(struct pw_impl_port *port)
+{
+ return port->properties;
+}
+
+SPA_EXPORT
+int pw_impl_port_update_properties(struct pw_impl_port *port, const struct spa_dict *dict)
+{
+ int changed = update_properties(port, dict, false);
+ emit_info_changed(port);
+ return changed;
+}
+
+void pw_impl_port_update_info(struct pw_impl_port *port, const struct spa_port_info *info)
+{
+ update_info(port, info);
+ emit_info_changed(port);
+}
+
+SPA_EXPORT
+struct pw_impl_node *pw_impl_port_get_node(struct pw_impl_port *port)
+{
+ return port->node;
+}
+
+SPA_EXPORT
+void pw_impl_port_add_listener(struct pw_impl_port *port,
+ struct spa_hook *listener,
+ const struct pw_impl_port_events *events,
+ void *data)
+{
+ spa_hook_list_append(&port->listener_list, listener, events, data);
+}
+
+SPA_EXPORT
+const struct pw_port_info *pw_impl_port_get_info(struct pw_impl_port *port)
+{
+ return &port->info;
+}
+
+SPA_EXPORT
+void * pw_impl_port_get_user_data(struct pw_impl_port *port)
+{
+ return port->user_data;
+}
+
+static int do_add_port(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct pw_impl_port *this = user_data;
+
+ pw_log_trace("%p: add port", this);
+ if (this->direction == PW_DIRECTION_INPUT)
+ spa_list_append(&this->node->rt.input_mix, &this->rt.node_link);
+ else
+ spa_list_append(&this->node->rt.output_mix, &this->rt.node_link);
+
+ return 0;
+}
+
+static int check_param_io(void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next, struct spa_pod *param)
+{
+ struct pw_impl_port *port = data;
+ struct pw_impl_node *node = port->node;
+ uint32_t pid, psize;
+
+ if (spa_pod_parse_object(param,
+ SPA_TYPE_OBJECT_ParamIO, NULL,
+ SPA_PARAM_IO_id, SPA_POD_Id(&pid),
+ SPA_PARAM_IO_size, SPA_POD_Int(&psize)) < 0)
+ return 0;
+
+ pw_log_debug("%p: got io id:%d (%s)", port, pid,
+ spa_debug_type_find_name(spa_type_io, pid));
+
+ switch (pid) {
+ case SPA_IO_Control:
+ case SPA_IO_Notify:
+ pw_control_new(node->context, port, pid, psize, 0);
+ SPA_FLAG_SET(port->flags, PW_IMPL_PORT_FLAG_CONTROL);
+ break;
+ case SPA_IO_Buffers:
+ SPA_FLAG_SET(port->flags, PW_IMPL_PORT_FLAG_BUFFERS);
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int reply_param(void *data, int seq, uint32_t id,
+ uint32_t index, uint32_t next, struct spa_pod *param)
+{
+ struct resource_data *d = data;
+ struct pw_resource *resource = d->resource;
+ pw_log_debug("%p: resource %p reply param %u %u %u", d->port,
+ resource, id, index, next);
+ pw_port_resource_param(resource, seq, id, index, next, param);
+ return 0;
+}
+
+static int port_enum_params(void *object, int seq, uint32_t id, uint32_t index, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct resource_data *data = object;
+ struct pw_resource *resource = data->resource;
+ struct pw_impl_port *port = data->port;
+ int res;
+
+ pw_log_debug("%p: resource %p enum params seq:%d id:%d (%s) index:%u num:%u", port,
+ resource, seq, id, spa_debug_type_find_name(spa_type_param, id),
+ index, num);
+
+ if ((res = pw_impl_port_for_each_param(port, seq, id, index, num, filter,
+ reply_param, data)) < 0)
+ pw_resource_errorf(resource, res,
+ "enum params id:%d (%s) failed", id,
+ spa_debug_type_find_name(spa_type_param, id));
+ return res;
+}
+
+static int port_subscribe_params(void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct resource_data *data = object;
+ struct pw_resource *resource = data->resource;
+ uint32_t i;
+
+ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(data->subscribe_ids));
+ data->n_subscribe_ids = n_ids;
+
+ for (i = 0; i < n_ids; i++) {
+ data->subscribe_ids[i] = ids[i];
+ pw_log_debug("%p: resource %p subscribe param id:%d (%s)", data->port,
+ resource, ids[i],
+ spa_debug_type_find_name(spa_type_param, ids[i]));
+ port_enum_params(data, 1, ids[i], 0, UINT32_MAX, NULL);
+ }
+ return 0;
+}
+
+static const struct pw_port_methods port_methods = {
+ PW_VERSION_PORT_METHODS,
+ .subscribe_params = port_subscribe_params,
+ .enum_params = port_enum_params
+};
+
+static void resource_destroy(void *data)
+{
+ struct resource_data *d = data;
+ spa_hook_remove(&d->resource_listener);
+ spa_hook_remove(&d->object_listener);
+}
+
+static const struct pw_resource_events resource_events = {
+ PW_VERSION_RESOURCE_EVENTS,
+ .destroy = resource_destroy,
+};
+
+static int
+global_bind(void *object, struct pw_impl_client *client, uint32_t permissions,
+ uint32_t version, uint32_t id)
+{
+ struct pw_impl_port *this = object;
+ struct pw_global *global = this->global;
+ struct pw_resource *resource;
+ struct resource_data *data;
+ int res;
+
+ resource = pw_resource_new(client, id, permissions, global->type, version, sizeof(*data));
+ if (resource == NULL) {
+ res = -errno;
+ goto error_resource;
+ }
+
+ data = pw_resource_get_user_data(resource);
+ data->port = this;
+ data->resource = resource;
+
+ pw_resource_add_listener(resource,
+ &data->resource_listener,
+ &resource_events, data);
+ pw_resource_add_object_listener(resource,
+ &data->object_listener,
+ &port_methods, data);
+
+ pw_log_debug("%p: bound to %d", this, resource->id);
+ pw_global_add_resource(global, resource);
+
+ this->info.change_mask = PW_PORT_CHANGE_MASK_ALL;
+ pw_port_resource_info(resource, &this->info);
+ this->info.change_mask = 0;
+ return 0;
+
+error_resource:
+ pw_log_error("%p: can't create port resource: %m", this);
+ return res;
+}
+
+static void global_destroy(void *data)
+{
+ struct pw_impl_port *port = data;
+ spa_hook_remove(&port->global_listener);
+ port->global = NULL;
+ pw_impl_port_destroy(port);
+}
+
+static const struct pw_global_events global_events = {
+ PW_VERSION_GLOBAL_EVENTS,
+ .destroy = global_destroy,
+};
+
+int pw_impl_port_register(struct pw_impl_port *port,
+ struct pw_properties *properties)
+{
+ static const char * const keys[] = {
+ PW_KEY_OBJECT_SERIAL,
+ PW_KEY_OBJECT_PATH,
+ PW_KEY_FORMAT_DSP,
+ PW_KEY_NODE_ID,
+ PW_KEY_AUDIO_CHANNEL,
+ PW_KEY_PORT_ID,
+ PW_KEY_PORT_NAME,
+ PW_KEY_PORT_DIRECTION,
+ PW_KEY_PORT_MONITOR,
+ PW_KEY_PORT_PHYSICAL,
+ PW_KEY_PORT_TERMINAL,
+ PW_KEY_PORT_CONTROL,
+ PW_KEY_PORT_ALIAS,
+ PW_KEY_PORT_EXTRA,
+ NULL
+ };
+
+ struct pw_impl_node *node = port->node;
+
+ if (node == NULL || node->global == NULL)
+ return -EIO;
+
+ port->global = pw_global_new(node->context,
+ PW_TYPE_INTERFACE_Port,
+ PW_VERSION_PORT,
+ properties,
+ global_bind,
+ port);
+ if (port->global == NULL)
+ return -errno;
+
+ pw_global_add_listener(port->global, &port->global_listener, &global_events, port);
+
+ port->info.id = port->global->id;
+ pw_properties_setf(port->properties, PW_KEY_NODE_ID, "%d", node->global->id);
+ pw_properties_setf(port->properties, PW_KEY_OBJECT_ID, "%d", port->info.id);
+ pw_properties_setf(port->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64,
+ pw_global_get_serial(port->global));
+ port->info.props = &port->properties->dict;
+
+ pw_global_update_keys(port->global, &port->properties->dict, keys);
+
+ pw_impl_port_emit_initialized(port);
+
+ return pw_global_register(port->global);
+}
+
+SPA_EXPORT
+int pw_impl_port_add(struct pw_impl_port *port, struct pw_impl_node *node)
+{
+ uint32_t port_id = port->port_id;
+ struct spa_list *ports;
+ struct pw_map *portmap;
+ struct pw_impl_port *find;
+ bool control;
+ const char *str, *dir;
+ int res;
+
+ if (port->node != NULL)
+ return -EEXIST;
+
+ if (port->direction == PW_DIRECTION_INPUT) {
+ ports = &node->input_ports;
+ portmap = &node->input_port_map;
+ } else {
+ ports = &node->output_ports;
+ portmap = &node->output_port_map;
+ }
+
+ find = pw_map_lookup(portmap, port_id);
+ if (find != NULL)
+ return -EEXIST;
+
+ if ((res = pw_map_insert_at(portmap, port_id, port)) < 0)
+ return res;
+
+ port->node = node;
+
+ pw_impl_node_emit_port_init(node, port);
+
+ pw_impl_port_for_each_param(port, 0, SPA_PARAM_IO, 0, 0, NULL, check_param_io, port);
+ pw_impl_port_for_each_param(port, 0, SPA_PARAM_Latency, 0, 0, NULL, process_latency_param, port);
+
+ control = PW_IMPL_PORT_IS_CONTROL(port);
+ if (control) {
+ dir = port->direction == PW_DIRECTION_INPUT ? "control" : "notify";
+ pw_properties_set(port->properties, PW_KEY_PORT_CONTROL, "true");
+ }
+ else {
+ dir = port->direction == PW_DIRECTION_INPUT ? "in" : "out";
+ }
+ pw_properties_set(port->properties, PW_KEY_PORT_DIRECTION, dir);
+
+ if (pw_properties_get(port->properties, PW_KEY_PORT_NAME) == NULL) {
+ if ((str = pw_properties_get(port->properties, PW_KEY_AUDIO_CHANNEL)) != NULL &&
+ !spa_streq(str, "UNK")) {
+ pw_properties_setf(port->properties, PW_KEY_PORT_NAME, "%s_%s", dir, str);
+ }
+ else {
+ pw_properties_setf(port->properties, PW_KEY_PORT_NAME, "%s_%d", dir, port->port_id);
+ }
+ }
+ if (pw_properties_get(port->properties, PW_KEY_PORT_ALIAS) == NULL) {
+ const struct pw_properties *nprops;
+ const char *node_name;
+
+ nprops = pw_impl_node_get_properties(node);
+ if ((node_name = pw_properties_get(nprops, PW_KEY_NODE_NICK)) == NULL &&
+ (node_name = pw_properties_get(nprops, PW_KEY_NODE_DESCRIPTION)) == NULL &&
+ (node_name = pw_properties_get(nprops, PW_KEY_NODE_NAME)) == NULL)
+ node_name = "node";
+
+ pw_properties_setf(port->properties, PW_KEY_PORT_ALIAS, "%s:%s",
+ node_name,
+ pw_properties_get(port->properties, PW_KEY_PORT_NAME));
+ }
+
+ port->info.props = &port->properties->dict;
+
+ if (control) {
+ pw_log_debug("%p: setting node control", port);
+ } else {
+ pw_log_debug("%p: setting mixer io", port);
+ spa_node_port_set_io(port->mix,
+ pw_direction_reverse(port->direction), 0,
+ SPA_IO_Buffers,
+ &port->rt.io, sizeof(port->rt.io));
+ }
+
+ pw_log_debug("%p: %d add to node %p", port, port_id, node);
+
+ spa_list_append(ports, &port->link);
+
+ if (port->direction == PW_DIRECTION_INPUT) {
+ node->info.n_input_ports++;
+ node->info.change_mask |= PW_NODE_CHANGE_MASK_INPUT_PORTS;
+ } else {
+ node->info.n_output_ports++;
+ node->info.change_mask |= PW_NODE_CHANGE_MASK_OUTPUT_PORTS;
+ }
+
+ if (node->global)
+ pw_impl_port_register(port, NULL);
+
+ if (port->state <= PW_IMPL_PORT_STATE_INIT)
+ pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_CONFIGURE, 0, NULL);
+
+ pw_impl_node_emit_port_added(node, port);
+ emit_info_changed(port);
+
+ return 0;
+}
+
+static int do_destroy_link(void *data, struct pw_impl_link *link)
+{
+ pw_impl_link_destroy(link);
+ return 0;
+}
+
+void pw_impl_port_unlink(struct pw_impl_port *port)
+{
+ pw_impl_port_for_each_link(port, do_destroy_link, port);
+}
+
+static int do_remove_port(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct pw_impl_port *this = user_data;
+
+ pw_log_trace("%p: remove port", this);
+ spa_list_remove(&this->rt.node_link);
+
+ return 0;
+}
+
+static void pw_impl_port_remove(struct pw_impl_port *port)
+{
+ struct pw_impl_node *node = port->node;
+ int res;
+
+ if (node == NULL)
+ return;
+
+ pw_log_debug("%p: remove added:%d", port, port->added);
+
+ if (port->added) {
+ pw_loop_invoke(node->data_loop, do_remove_port,
+ SPA_ID_INVALID, NULL, 0, true, port);
+ port->added = false;
+ }
+
+ if (SPA_FLAG_IS_SET(port->flags, PW_IMPL_PORT_FLAG_TO_REMOVE)) {
+ if ((res = spa_node_remove_port(node->node, port->direction, port->port_id)) < 0)
+ pw_log_warn("%p: can't remove: %s", port, spa_strerror(res));
+ }
+
+ if (port->direction == PW_DIRECTION_INPUT) {
+ if ((res = pw_map_insert_at(&node->input_port_map, port->port_id, NULL)) < 0)
+ pw_log_warn("%p: can't remove input port: %s", port, spa_strerror(res));
+ node->info.n_input_ports--;
+ } else {
+ if ((res = pw_map_insert_at(&node->output_port_map, port->port_id, NULL)) < 0)
+ pw_log_warn("%p: can't remove output port: %s", port, spa_strerror(res));
+ node->info.n_output_ports--;
+ }
+
+ pw_impl_port_set_mix(port, NULL, 0);
+
+ spa_list_remove(&port->link);
+ pw_impl_node_emit_port_removed(node, port);
+ port->node = NULL;
+}
+
+void pw_impl_port_destroy(struct pw_impl_port *port)
+{
+ struct impl *impl = SPA_CONTAINER_OF(port, struct impl, this);
+ struct pw_control *control;
+
+ pw_log_debug("%p: destroy", port);
+
+ port->destroying = true;
+ pw_impl_port_emit_destroy(port);
+
+ pw_impl_port_unlink(port);
+
+ pw_log_debug("%p: control destroy", port);
+ spa_list_consume(control, &port->control_list[0], port_link)
+ pw_control_destroy(control);
+ spa_list_consume(control, &port->control_list[1], port_link)
+ pw_control_destroy(control);
+
+ pw_impl_port_remove(port);
+
+ if (port->global) {
+ spa_hook_remove(&port->global_listener);
+ pw_global_destroy(port->global);
+ }
+
+ pw_log_debug("%p: free", port);
+ pw_impl_port_emit_free(port);
+
+ spa_hook_list_clean(&port->listener_list);
+
+ pw_buffers_clear(&port->buffers);
+ pw_buffers_clear(&port->mix_buffers);
+ free((void*)port->error);
+
+ pw_param_clear(&impl->param_list, SPA_ID_INVALID);
+ pw_param_clear(&impl->pending_list, SPA_ID_INVALID);
+
+ pw_map_clear(&port->mix_port_map);
+
+ pw_properties_free(port->properties);
+
+ free(port);
+}
+
+struct result_port_params_data {
+ struct impl *impl;
+ void *data;
+ int (*callback) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ struct spa_pod *param);
+ int seq;
+ uint32_t count;
+ unsigned int cache:1;
+};
+
+static void result_port_params(void *data, int seq, int res, uint32_t type, const void *result)
+{
+ struct result_port_params_data *d = data;
+ struct impl *impl = d->impl;
+ switch (type) {
+ case SPA_RESULT_TYPE_NODE_PARAMS:
+ {
+ const struct spa_result_node_params *r = result;
+ if (d->seq == seq) {
+ d->callback(d->data, seq, r->id, r->index, r->next, r->param);
+ if (d->cache) {
+ if (d->count++ == 0)
+ pw_param_add(&impl->pending_list, seq, r->id, NULL);
+ pw_param_add(&impl->pending_list, seq, r->id, r->param);
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+int pw_impl_port_for_each_param(struct pw_impl_port *port,
+ int seq,
+ uint32_t param_id,
+ uint32_t index, uint32_t max,
+ const struct spa_pod *filter,
+ int (*callback) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ struct spa_pod *param),
+ void *data)
+{
+ int res;
+ struct impl *impl = SPA_CONTAINER_OF(port, struct impl, this);
+ struct pw_impl_node *node = port->node;
+ struct result_port_params_data user_data = { impl, data, callback, seq, 0, false };
+ struct spa_hook listener;
+ struct spa_param_info *pi;
+ static const struct spa_node_events node_events = {
+ SPA_VERSION_NODE_EVENTS,
+ .result = result_port_params,
+ };
+
+ pi = pw_param_info_find(port->info.params, port->info.n_params, param_id);
+ if (pi == NULL)
+ return -ENOENT;
+
+ if (max == 0)
+ max = UINT32_MAX;
+
+ pw_log_debug("%p: params id:%d (%s) index:%u max:%u cached:%d", port, param_id,
+ spa_debug_type_find_name(spa_type_param, param_id),
+ index, max, pi->user);
+
+ if (pi->user == 1) {
+ struct pw_param *p;
+ uint8_t buffer[1024];
+ struct spa_pod_dynamic_builder b;
+ struct spa_result_node_params result;
+ uint32_t count = 0;
+
+ result.id = param_id;
+ result.next = 0;
+
+ spa_list_for_each(p, &impl->param_list, link) {
+ if (p->id != param_id)
+ continue;
+
+ result.index = result.next++;
+ if (result.index < index)
+ continue;
+
+ spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
+
+ if (spa_pod_filter(&b.b, &result.param, p->param, filter) >= 0) {
+ pw_log_debug("%p: %d param %u", port, seq, result.index);
+ result_port_params(&user_data, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+ count++;
+ }
+ spa_pod_dynamic_builder_clean(&b);
+
+ if (count == max)
+ break;
+ }
+ res = 0;
+ } else {
+ user_data.cache = impl->cache_params &&
+ (filter == NULL && index == 0 && max == UINT32_MAX);
+
+ spa_zero(listener);
+ spa_node_add_listener(node->node, &listener, &node_events, &user_data);
+ res = spa_node_port_enum_params(node->node, seq,
+ port->direction, port->port_id,
+ param_id, index, max,
+ filter);
+ spa_hook_remove(&listener);
+
+ if (user_data.cache) {
+ pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL);
+ pi->user = 1;
+ }
+ }
+
+ pw_log_debug("%p: res %d: (%s)", port, res, spa_strerror(res));
+ return res;
+}
+
+struct param_filter {
+ struct pw_impl_port *in_port;
+ struct pw_impl_port *out_port;
+ int seq;
+ uint32_t in_param_id;
+ uint32_t out_param_id;
+ int (*callback) (void *data, int seq, uint32_t id, uint32_t index,
+ uint32_t next, struct spa_pod *param);
+ void *data;
+ uint32_t n_params;
+};
+
+static int do_filter(void *data, int seq, uint32_t id, uint32_t index, uint32_t next, struct spa_pod *param)
+{
+ struct param_filter *f = data;
+ f->n_params++;
+ return pw_impl_port_for_each_param(f->out_port, seq, f->out_param_id, 0, 0, param, f->callback, f->data);
+}
+
+int pw_impl_port_for_each_filtered_param(struct pw_impl_port *in_port,
+ struct pw_impl_port *out_port,
+ int seq,
+ uint32_t in_param_id,
+ uint32_t out_param_id,
+ const struct spa_pod *filter,
+ int (*callback) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ struct spa_pod *param),
+ void *data)
+{
+ int res;
+ struct param_filter fd = { in_port, out_port, seq, in_param_id, out_param_id, callback, data, 0 };
+
+ if ((res = pw_impl_port_for_each_param(in_port, seq, in_param_id, 0, 0, filter, do_filter, &fd)) < 0)
+ return res;
+
+ if (fd.n_params == 0)
+ res = do_filter(&fd, seq, 0, 0, 0, NULL);
+
+ return res;
+}
+
+int pw_impl_port_for_each_link(struct pw_impl_port *port,
+ int (*callback) (void *data, struct pw_impl_link *link),
+ void *data)
+{
+ struct pw_impl_link *l, *t;
+ int res = 0;
+
+ if (port->direction == PW_DIRECTION_OUTPUT) {
+ spa_list_for_each_safe(l, t, &port->links, output_link)
+ if ((res = callback(data, l)) != 0)
+ break;
+ } else {
+ spa_list_for_each_safe(l, t, &port->links, input_link)
+ if ((res = callback(data, l)) != 0)
+ break;
+ }
+ return res;
+}
+
+int pw_impl_port_recalc_latency(struct pw_impl_port *port)
+{
+ struct pw_impl_link *l;
+ struct spa_latency_info latency, *current;
+ struct pw_impl_port *other;
+ struct spa_pod *param;
+ struct spa_pod_builder b = { 0 };
+ uint8_t buffer[1024];
+ bool changed;
+
+ if (port->destroying)
+ return 0;
+
+ /* given an output port, we calculate the total latency to the sinks or the input
+ * latency. */
+ spa_latency_info_combine_start(&latency, SPA_DIRECTION_REVERSE(port->direction));
+
+ if (port->direction == PW_DIRECTION_OUTPUT) {
+ spa_list_for_each(l, &port->links, output_link) {
+ other = l->input;
+ spa_latency_info_combine(&latency, &other->latency[other->direction]);
+ pw_log_debug("port %d: peer %d: latency %f-%f %d-%d %"PRIu64"-%"PRIu64,
+ port->info.id, other->info.id,
+ latency.min_quantum, latency.max_quantum,
+ latency.min_rate, latency.max_rate,
+ latency.min_ns, latency.max_ns);
+ }
+ } else {
+ spa_list_for_each(l, &port->links, input_link) {
+ other = l->output;
+ spa_latency_info_combine(&latency, &other->latency[other->direction]);
+ pw_log_debug("port %d: peer %d: latency %f-%f %d-%d %"PRIu64"-%"PRIu64,
+ port->info.id, other->info.id,
+ latency.min_quantum, latency.max_quantum,
+ latency.min_rate, latency.max_rate,
+ latency.min_ns, latency.max_ns);
+ }
+ }
+ spa_latency_info_combine_finish(&latency);
+
+ current = &port->latency[latency.direction];
+
+ changed = spa_latency_info_compare(current, &latency) != 0;
+
+ pw_log_info("port %d: %s %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64,
+ port->info.id, changed ? "set" : "keep",
+ pw_direction_as_string(latency.direction),
+ latency.min_quantum, latency.max_quantum,
+ latency.min_rate, latency.max_rate,
+ latency.min_ns, latency.max_ns);
+
+ if (!changed)
+ return 0;
+
+ *current = latency;
+
+ if (!port->have_latency_param)
+ return 0;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ param = spa_latency_build(&b, SPA_PARAM_Latency, &latency);
+ return pw_impl_port_set_param(port, SPA_PARAM_Latency, 0, param);
+}
+
+SPA_EXPORT
+int pw_impl_port_is_linked(struct pw_impl_port *port)
+{
+ return spa_list_is_empty(&port->links) ? 0 : 1;
+}
+
+SPA_EXPORT
+int pw_impl_port_set_param(struct pw_impl_port *port, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ int res;
+ struct pw_impl_node *node = port->node;
+
+ pw_log_debug("%p: %d set param %d %p", port, port->state, id, param);
+
+ /* set parameter on node */
+ res = spa_node_port_set_param(node->node,
+ port->direction, port->port_id,
+ id, flags, param);
+
+ pw_log_debug("%p: %d set param on node %d:%d id:%d (%s): %d (%s)", port, port->state,
+ port->direction, port->port_id, id,
+ spa_debug_type_find_name(spa_type_param, id),
+ res, spa_strerror(res));
+
+ /* set the parameters on all ports of the mixer node if possible */
+ if (res >= 0) {
+ struct pw_impl_port_mix *mix;
+
+ if (port->direction == PW_DIRECTION_INPUT &&
+ id == SPA_PARAM_Format && param != NULL &&
+ !SPA_FLAG_IS_SET(port->flags, PW_IMPL_PORT_FLAG_NO_MIXER)) {
+ setup_mixer(port, param);
+ }
+
+ spa_list_for_each(mix, &port->mix_list, link) {
+ spa_node_port_set_param(port->mix,
+ mix->port.direction, mix->port.port_id,
+ id, flags, param);
+ }
+ spa_node_port_set_param(port->mix,
+ pw_direction_reverse(port->direction), 0,
+ id, flags, param);
+ }
+
+ if (id == SPA_PARAM_Format) {
+ pw_log_debug("%p: %d %p %d", port, port->state, param, res);
+
+ if (port->added) {
+ pw_loop_invoke(node->data_loop, do_remove_port, SPA_ID_INVALID, NULL, 0, true, port);
+ port->added = false;
+ }
+ /* setting the format always destroys the negotiated buffers */
+ if (port->direction == PW_DIRECTION_OUTPUT) {
+ struct pw_impl_link *l;
+ /* remove all buffers shared with an output port peer */
+ spa_list_for_each(l, &port->links, output_link)
+ pw_impl_port_use_buffers(l->input, &l->rt.in_mix, 0, NULL, 0);
+ }
+ pw_buffers_clear(&port->buffers);
+ pw_buffers_clear(&port->mix_buffers);
+
+ if (param == NULL || res < 0) {
+ pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_CONFIGURE, 0, NULL);
+ }
+ else if (spa_pod_is_fixated(param) <= 0) {
+ pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_CONFIGURE, 0, NULL);
+ pw_impl_port_emit_param_changed(port, id);
+ }
+ else if (!SPA_RESULT_IS_ASYNC(res)) {
+ pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_READY, 0, NULL);
+ }
+ }
+ return res;
+}
+
+static int negotiate_mixer_buffers(struct pw_impl_port *port, uint32_t flags,
+ struct spa_buffer **buffers, uint32_t n_buffers)
+{
+ int res;
+ struct pw_impl_node *node = port->node;
+
+ if (SPA_FLAG_IS_SET(port->mix_flags, PW_IMPL_PORT_MIX_FLAG_MIX_ONLY))
+ return 0;
+
+ if (SPA_FLAG_IS_SET(port->mix_flags, PW_IMPL_PORT_MIX_FLAG_NEGOTIATE)) {
+ int alloc_flags;
+
+ /* try dynamic data */
+ alloc_flags = PW_BUFFERS_FLAG_DYNAMIC;
+
+ pw_log_debug("%p: %d.%d negotiate %d buffers on node: %p",
+ port, port->direction, port->port_id, n_buffers, node->node);
+
+ if (port->added) {
+ pw_loop_invoke(node->data_loop, do_remove_port, SPA_ID_INVALID, NULL, 0, true, port);
+ port->added = false;
+ }
+
+ pw_buffers_clear(&port->mix_buffers);
+
+ if (n_buffers > 0) {
+ if ((res = pw_buffers_negotiate(node->context, alloc_flags,
+ port->mix, 0,
+ node->node, port->port_id,
+ &port->mix_buffers)) < 0) {
+ pw_log_warn("%p: can't negotiate buffers: %s",
+ port, spa_strerror(res));
+ return res;
+ }
+ buffers = port->mix_buffers.buffers;
+ n_buffers = port->mix_buffers.n_buffers;
+ flags = 0;
+ }
+ }
+
+ pw_log_debug("%p: %d.%d use %d buffers on node: %p",
+ port, port->direction, port->port_id, n_buffers, node->node);
+
+ res = spa_node_port_use_buffers(node->node,
+ port->direction, port->port_id,
+ flags, buffers, n_buffers);
+
+ if (SPA_RESULT_IS_OK(res)) {
+ spa_node_port_use_buffers(port->mix,
+ pw_direction_reverse(port->direction), 0,
+ 0, buffers, n_buffers);
+ }
+ if (!port->added && n_buffers > 0) {
+ pw_loop_invoke(node->data_loop, do_add_port, SPA_ID_INVALID, NULL, 0, false, port);
+ port->added = true;
+ }
+ return res;
+}
+
+
+SPA_EXPORT
+int pw_impl_port_use_buffers(struct pw_impl_port *port, struct pw_impl_port_mix *mix, uint32_t flags,
+ struct spa_buffer **buffers, uint32_t n_buffers)
+{
+ int res = 0, res2;
+
+ pw_log_debug("%p: %d:%d.%d: %d buffers flags:%d state:%d n_mix:%d", port,
+ port->direction, port->port_id, mix->id,
+ n_buffers, flags, port->state, port->n_mix);
+
+ if (n_buffers == 0 && port->state <= PW_IMPL_PORT_STATE_READY)
+ return 0;
+
+ if (n_buffers > 0 && port->state < PW_IMPL_PORT_STATE_READY)
+ return -EIO;
+
+ if (n_buffers == 0) {
+ mix->have_buffers = false;
+ if (port->n_mix == 1)
+ pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_READY, 0, NULL);
+ }
+
+ /* first negotiate with the node, this makes it possible to let the
+ * node allocate buffer memory if needed */
+ if (port->state == PW_IMPL_PORT_STATE_READY) {
+ res = negotiate_mixer_buffers(port, flags, buffers, n_buffers);
+
+ if (res < 0) {
+ pw_log_error("%p: negotiate buffers on node: %d (%s)",
+ port, res, spa_strerror(res));
+ pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_ERROR, res,
+ strdup("can't negotiate buffers on port"));
+ } else if (n_buffers > 0 && !SPA_RESULT_IS_ASYNC(res)) {
+ pw_impl_port_update_state(port, PW_IMPL_PORT_STATE_PAUSED, 0, NULL);
+ }
+ }
+
+ /* then use the buffers on the mixer */
+ if (!SPA_FLAG_IS_SET(port->mix_flags, PW_IMPL_PORT_MIX_FLAG_MIX_ONLY))
+ flags &= ~SPA_NODE_BUFFERS_FLAG_ALLOC;
+
+ res2 = spa_node_port_use_buffers(port->mix,
+ mix->port.direction, mix->port.port_id, flags,
+ buffers, n_buffers);
+ if (res2 < 0) {
+ if (res2 != -ENOTSUP && n_buffers > 0) {
+ pw_log_warn("%p: mix use buffers failed: %d (%s)",
+ port, res2, spa_strerror(res2));
+ return res2;
+ }
+ }
+ else if (SPA_RESULT_IS_ASYNC(res2))
+ res = res2;
+
+ return res;
+}
diff --git a/src/pipewire/impl-port.h b/src/pipewire/impl-port.h
new file mode 100644
index 0000000..c275104
--- /dev/null
+++ b/src/pipewire/impl-port.h
@@ -0,0 +1,144 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_PORT_H
+#define PIPEWIRE_IMPL_PORT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/hook.h>
+
+/** \defgroup pw_impl_port Port Impl
+ *
+ * \brief A port can be used to link two nodes.
+ */
+
+/**
+ * \addtogroup pw_impl_port
+ * \{
+ */
+struct pw_impl_port;
+struct pw_impl_link;
+struct pw_control;
+
+#include <pipewire/impl.h>
+
+enum pw_impl_port_state {
+ PW_IMPL_PORT_STATE_ERROR = -1, /**< the port is in error */
+ PW_IMPL_PORT_STATE_INIT = 0, /**< the port is being created */
+ PW_IMPL_PORT_STATE_CONFIGURE = 1, /**< the port is ready for format negotiation */
+ PW_IMPL_PORT_STATE_READY = 2, /**< the port is ready for buffer allocation */
+ PW_IMPL_PORT_STATE_PAUSED = 3, /**< the port is paused */
+};
+
+/** Port events, use \ref pw_impl_port_add_listener */
+struct pw_impl_port_events {
+#define PW_VERSION_IMPL_PORT_EVENTS 2
+ uint32_t version;
+
+ /** The port is destroyed */
+ void (*destroy) (void *data);
+
+ /** The port is freed */
+ void (*free) (void *data);
+
+ /** The port is initialized */
+ void (*initialized) (void *data);
+
+ /** the port info changed */
+ void (*info_changed) (void *data, const struct pw_port_info *info);
+
+ /** a new link is added on this port */
+ void (*link_added) (void *data, struct pw_impl_link *link);
+
+ /** a link is removed from this port */
+ void (*link_removed) (void *data, struct pw_impl_link *link);
+
+ /** the state of the port changed */
+ void (*state_changed) (void *data, enum pw_impl_port_state old,
+ enum pw_impl_port_state state, const char *error);
+
+ /** a control was added to the port */
+ void (*control_added) (void *data, struct pw_control *control);
+
+ /** a control was removed from the port */
+ void (*control_removed) (void *data, struct pw_control *control);
+
+ /** a parameter changed, since version 1 */
+ void (*param_changed) (void *data, uint32_t id);
+
+ /** latency changed. Since version 2 */
+ void (*latency_changed) (void *data);
+};
+
+/** Create a new port
+ * \return a newly allocated port */
+struct pw_impl_port *
+pw_context_create_port(struct pw_context *context,
+ enum pw_direction direction,
+ uint32_t port_id,
+ const struct spa_port_info *info,
+ size_t user_data_size);
+
+/** Get the port direction */
+enum pw_direction pw_impl_port_get_direction(struct pw_impl_port *port);
+
+/** Get the port properties */
+const struct pw_properties *pw_impl_port_get_properties(struct pw_impl_port *port);
+
+/** Update the port properties */
+int pw_impl_port_update_properties(struct pw_impl_port *port, const struct spa_dict *dict);
+
+/** Get the port info */
+const struct pw_port_info *pw_impl_port_get_info(struct pw_impl_port *port);
+
+/** Get the port id */
+uint32_t pw_impl_port_get_id(struct pw_impl_port *port);
+
+/** Get the port parent node or NULL when not yet set */
+struct pw_impl_node *pw_impl_port_get_node(struct pw_impl_port *port);
+
+/** check is a port has links, return 0 if not, 1 if it is linked */
+int pw_impl_port_is_linked(struct pw_impl_port *port);
+
+/** Add a port to a node */
+int pw_impl_port_add(struct pw_impl_port *port, struct pw_impl_node *node);
+
+/** Add an event listener on the port */
+void pw_impl_port_add_listener(struct pw_impl_port *port,
+ struct spa_hook *listener,
+ const struct pw_impl_port_events *events,
+ void *data);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_PORT_H */
diff --git a/src/pipewire/impl.h b/src/pipewire/impl.h
new file mode 100644
index 0000000..8caa673
--- /dev/null
+++ b/src/pipewire/impl.h
@@ -0,0 +1,62 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_IMPL_H
+#define PIPEWIRE_IMPL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \addtogroup api_pw_impl
+ */
+
+struct pw_impl_client;
+struct pw_impl_module;
+struct pw_global;
+struct pw_node;
+struct pw_impl_port;
+struct pw_resource;
+
+#include <pipewire/pipewire.h>
+#include <pipewire/control.h>
+#include <pipewire/impl-core.h>
+#include <pipewire/impl-client.h>
+#include <pipewire/impl-device.h>
+#include <pipewire/impl-factory.h>
+#include <pipewire/global.h>
+#include <pipewire/impl-link.h>
+#include <pipewire/impl-metadata.h>
+#include <pipewire/impl-module.h>
+#include <pipewire/impl-node.h>
+#include <pipewire/impl-port.h>
+#include <pipewire/resource.h>
+#include <pipewire/work-queue.h>
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_IMPL_H */
diff --git a/src/pipewire/introspect.c b/src/pipewire/introspect.c
new file mode 100644
index 0000000..e52ef28
--- /dev/null
+++ b/src/pipewire/introspect.c
@@ -0,0 +1,590 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+
+#include <spa/pod/builder.h>
+
+#include "pipewire/pipewire.h"
+
+#include "pipewire/core.h"
+
+SPA_EXPORT
+const char *pw_node_state_as_string(enum pw_node_state state)
+{
+ switch (state) {
+ case PW_NODE_STATE_ERROR:
+ return "error";
+ case PW_NODE_STATE_CREATING:
+ return "creating";
+ case PW_NODE_STATE_SUSPENDED:
+ return "suspended";
+ case PW_NODE_STATE_IDLE:
+ return "idle";
+ case PW_NODE_STATE_RUNNING:
+ return "running";
+ }
+ return "invalid-state";
+}
+
+SPA_EXPORT
+const char *pw_direction_as_string(enum pw_direction direction)
+{
+ switch (direction) {
+ case PW_DIRECTION_INPUT:
+ return "input";
+ case PW_DIRECTION_OUTPUT:
+ return "output";
+ }
+ return "invalid";
+}
+
+SPA_EXPORT
+const char *pw_link_state_as_string(enum pw_link_state state)
+{
+ switch (state) {
+ case PW_LINK_STATE_ERROR:
+ return "error";
+ case PW_LINK_STATE_UNLINKED:
+ return "unlinked";
+ case PW_LINK_STATE_INIT:
+ return "init";
+ case PW_LINK_STATE_NEGOTIATING:
+ return "negotiating";
+ case PW_LINK_STATE_ALLOCATING:
+ return "allocating";
+ case PW_LINK_STATE_PAUSED:
+ return "paused";
+ case PW_LINK_STATE_ACTIVE:
+ return "active";
+ }
+ return "invalid-state";
+}
+
+static void pw_spa_dict_destroy(struct spa_dict *dict)
+{
+ const struct spa_dict_item *item;
+
+ spa_dict_for_each(item, dict) {
+ free((void *) item->key);
+ free((void *) item->value);
+ }
+ free((void*)dict->items);
+ free(dict);
+}
+
+static struct spa_dict *pw_spa_dict_copy(struct spa_dict *dict)
+{
+ struct spa_dict *copy;
+ struct spa_dict_item *items;
+ uint32_t i;
+
+ if (dict == NULL)
+ return NULL;
+
+ copy = calloc(1, sizeof(struct spa_dict));
+ if (copy == NULL)
+ goto no_mem;
+ copy->items = items = calloc(dict->n_items, sizeof(struct spa_dict_item));
+ if (copy->items == NULL)
+ goto no_items;
+ copy->n_items = dict->n_items;
+
+ for (i = 0; i < dict->n_items; i++) {
+ items[i].key = strdup(dict->items[i].key);
+ items[i].value = dict->items[i].value ? strdup(dict->items[i].value) : NULL;
+ }
+ return copy;
+
+ no_items:
+ free(copy);
+ no_mem:
+ return NULL;
+}
+
+SPA_EXPORT
+struct pw_core_info *pw_core_info_merge(struct pw_core_info *info,
+ const struct pw_core_info *update, bool reset)
+{
+ if (update == NULL)
+ return info;
+
+ if (info == NULL) {
+ info = calloc(1, sizeof(struct pw_core_info));
+ if (info == NULL)
+ return NULL;
+
+ info->id = update->id;
+ info->cookie = update->cookie;
+ info->user_name = update->user_name ? strdup(update->user_name) : NULL;
+ info->host_name = update->host_name ? strdup(update->host_name) : NULL;
+ info->version = update->version ? strdup(update->version) : NULL;
+ info->name = update->name ? strdup(update->name) : NULL;
+ }
+ if (reset)
+ info->change_mask = 0;
+ info->change_mask |= update->change_mask;
+
+ if (update->change_mask & PW_CORE_CHANGE_MASK_PROPS) {
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ info->props = pw_spa_dict_copy(update->props);
+ }
+ return info;
+}
+
+SPA_EXPORT
+struct pw_core_info *pw_core_info_update(struct pw_core_info *info,
+ const struct pw_core_info *update)
+{
+ return pw_core_info_merge(info, update, true);
+}
+
+SPA_EXPORT
+void pw_core_info_free(struct pw_core_info *info)
+{
+ free((void *) info->user_name);
+ free((void *) info->host_name);
+ free((void *) info->version);
+ free((void *) info->name);
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ free(info);
+}
+
+SPA_EXPORT
+struct pw_node_info *pw_node_info_merge(struct pw_node_info *info,
+ const struct pw_node_info *update, bool reset)
+{
+ if (update == NULL)
+ return info;
+
+ if (info == NULL) {
+ info = calloc(1, sizeof(struct pw_node_info));
+ if (info == NULL)
+ return NULL;
+
+ info->id = update->id;
+ info->max_input_ports = update->max_input_ports;
+ info->max_output_ports = update->max_output_ports;
+ }
+ if (reset)
+ info->change_mask = 0;
+ info->change_mask |= update->change_mask;
+
+ if (update->change_mask & PW_NODE_CHANGE_MASK_INPUT_PORTS) {
+ info->n_input_ports = update->n_input_ports;
+ }
+ if (update->change_mask & PW_NODE_CHANGE_MASK_OUTPUT_PORTS) {
+ info->n_output_ports = update->n_output_ports;
+ }
+ if (update->change_mask & PW_NODE_CHANGE_MASK_STATE) {
+ info->state = update->state;
+ free((void *) info->error);
+ info->error = update->error ? strdup(update->error) : NULL;
+ }
+ if (update->change_mask & PW_NODE_CHANGE_MASK_PROPS) {
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ info->props = pw_spa_dict_copy(update->props);
+ }
+ if (update->change_mask & PW_NODE_CHANGE_MASK_PARAMS) {
+ uint32_t i, n_params = update->n_params;
+ void *np;
+
+ np = pw_reallocarray(info->params, n_params, sizeof(struct spa_param_info));
+ if (np == NULL) {
+ free(info->params);
+ info->params = NULL;
+ info->n_params = n_params = 0;
+ }
+ info->params = np;
+
+ for (i = 0; i < SPA_MIN(info->n_params, n_params); i++) {
+ info->params[i].id = update->params[i].id;
+ if (reset)
+ info->params[i].user = 0;
+ if (info->params[i].flags != update->params[i].flags) {
+ info->params[i].flags = update->params[i].flags;
+ info->params[i].user++;
+ }
+ }
+ info->n_params = n_params;
+ for (; i < info->n_params; i++) {
+ info->params[i].id = update->params[i].id;
+ info->params[i].flags = update->params[i].flags;
+ info->params[i].user = 1;
+ }
+ }
+ return info;
+}
+
+SPA_EXPORT
+struct pw_node_info *pw_node_info_update(struct pw_node_info *info,
+ const struct pw_node_info *update)
+{
+ return pw_node_info_merge(info, update, true);
+}
+
+SPA_EXPORT
+void pw_node_info_free(struct pw_node_info *info)
+{
+ free((void *) info->error);
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ free((void *) info->params);
+ free(info);
+}
+
+SPA_EXPORT
+struct pw_port_info *pw_port_info_merge(struct pw_port_info *info,
+ const struct pw_port_info *update, bool reset)
+{
+
+ if (update == NULL)
+ return info;
+
+ if (info == NULL) {
+ info = calloc(1, sizeof(struct pw_port_info));
+ if (info == NULL)
+ return NULL;
+
+ info->id = update->id;
+ info->direction = update->direction;
+ }
+ if (reset)
+ info->change_mask = 0;
+ info->change_mask |= update->change_mask;
+
+ if (update->change_mask & PW_PORT_CHANGE_MASK_PROPS) {
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ info->props = pw_spa_dict_copy(update->props);
+ }
+ if (update->change_mask & PW_PORT_CHANGE_MASK_PARAMS) {
+ uint32_t i, n_params = update->n_params;
+ void *np;
+
+ np = pw_reallocarray(info->params, n_params, sizeof(struct spa_param_info));
+ if (np == NULL) {
+ free(info->params);
+ info->params = NULL;
+ info->n_params = n_params = 0;
+ }
+ info->params = np;
+
+ for (i = 0; i < SPA_MIN(info->n_params, n_params); i++) {
+ info->params[i].id = update->params[i].id;
+ if (reset)
+ info->params[i].user = 0;
+ if (info->params[i].flags != update->params[i].flags) {
+ info->params[i].flags = update->params[i].flags;
+ info->params[i].user++;
+ }
+ }
+ info->n_params = n_params;
+ for (; i < info->n_params; i++) {
+ info->params[i].id = update->params[i].id;
+ info->params[i].flags = update->params[i].flags;
+ info->params[i].user = 1;
+ }
+ }
+ return info;
+}
+
+SPA_EXPORT
+struct pw_port_info *pw_port_info_update(struct pw_port_info *info,
+ const struct pw_port_info *update)
+{
+ return pw_port_info_merge(info, update, true);
+}
+
+SPA_EXPORT
+void pw_port_info_free(struct pw_port_info *info)
+{
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ free((void *) info->params);
+ free(info);
+}
+
+SPA_EXPORT
+struct pw_factory_info *pw_factory_info_merge(struct pw_factory_info *info,
+ const struct pw_factory_info *update, bool reset)
+{
+ if (update == NULL)
+ return info;
+
+ if (info == NULL) {
+ info = calloc(1, sizeof(struct pw_factory_info));
+ if (info == NULL)
+ return NULL;
+
+ info->id = update->id;
+ info->name = update->name ? strdup(update->name) : NULL;
+ info->type = update->type ? strdup(update->type) : NULL;
+ info->version = update->version;
+ }
+ if (reset)
+ info->change_mask = 0;
+ info->change_mask |= update->change_mask;
+
+ if (update->change_mask & PW_FACTORY_CHANGE_MASK_PROPS) {
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ info->props = pw_spa_dict_copy(update->props);
+ }
+ return info;
+}
+
+SPA_EXPORT
+struct pw_factory_info *pw_factory_info_update(struct pw_factory_info *info,
+ const struct pw_factory_info *update)
+{
+ return pw_factory_info_merge(info, update, true);
+}
+
+SPA_EXPORT
+void pw_factory_info_free(struct pw_factory_info *info)
+{
+ free((void *) info->name);
+ free((void *) info->type);
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ free(info);
+}
+
+SPA_EXPORT
+struct pw_module_info *pw_module_info_merge(struct pw_module_info *info,
+ const struct pw_module_info *update, bool reset)
+{
+ if (update == NULL)
+ return info;
+
+ if (info == NULL) {
+ info = calloc(1, sizeof(struct pw_module_info));
+ if (info == NULL)
+ return NULL;
+
+ info->id = update->id;
+ info->name = update->name ? strdup(update->name) : NULL;
+ info->filename = update->filename ? strdup(update->filename) : NULL;
+ info->args = update->args ? strdup(update->args) : NULL;
+ }
+ if (reset)
+ info->change_mask = 0;
+ info->change_mask |= update->change_mask;
+
+ if (update->change_mask & PW_MODULE_CHANGE_MASK_PROPS) {
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ info->props = pw_spa_dict_copy(update->props);
+ }
+ return info;
+}
+
+SPA_EXPORT
+struct pw_module_info *pw_module_info_update(struct pw_module_info *info,
+ const struct pw_module_info *update)
+{
+ return pw_module_info_merge(info, update, true);
+}
+
+SPA_EXPORT
+void pw_module_info_free(struct pw_module_info *info)
+{
+ free((void *) info->name);
+ free((void *) info->filename);
+ free((void *) info->args);
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ free(info);
+}
+
+SPA_EXPORT
+struct pw_device_info *pw_device_info_merge(struct pw_device_info *info,
+ const struct pw_device_info *update, bool reset)
+{
+ if (update == NULL)
+ return info;
+
+ if (info == NULL) {
+ info = calloc(1, sizeof(struct pw_device_info));
+ if (info == NULL)
+ return NULL;
+
+ info->id = update->id;
+ }
+ if (reset)
+ info->change_mask = 0;
+ info->change_mask |= update->change_mask;
+
+ if (update->change_mask & PW_DEVICE_CHANGE_MASK_PROPS) {
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ info->props = pw_spa_dict_copy(update->props);
+ }
+ if (update->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS) {
+ uint32_t i, n_params = update->n_params;
+ void *np;
+
+ np = pw_reallocarray(info->params, n_params, sizeof(struct spa_param_info));
+ if (np == NULL) {
+ free(info->params);
+ info->params = NULL;
+ info->n_params = n_params = 0;
+ }
+ info->params = np;
+
+ for (i = 0; i < SPA_MIN(info->n_params, n_params); i++) {
+ info->params[i].id = update->params[i].id;
+ if (reset)
+ info->params[i].user = 0;
+ if (info->params[i].flags != update->params[i].flags) {
+ info->params[i].flags = update->params[i].flags;
+ info->params[i].user++;
+ }
+ }
+ info->n_params = n_params;
+ for (; i < info->n_params; i++) {
+ info->params[i].id = update->params[i].id;
+ info->params[i].flags = update->params[i].flags;
+ info->params[i].user = 1;
+ }
+ }
+ return info;
+}
+
+SPA_EXPORT
+struct pw_device_info *pw_device_info_update(struct pw_device_info *info,
+ const struct pw_device_info *update)
+{
+ return pw_device_info_merge(info, update, true);
+}
+
+SPA_EXPORT
+void pw_device_info_free(struct pw_device_info *info)
+{
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ free((void *) info->params);
+ free(info);
+}
+
+SPA_EXPORT
+struct pw_client_info *pw_client_info_merge(struct pw_client_info *info,
+ const struct pw_client_info *update, bool reset)
+{
+ if (update == NULL)
+ return info;
+
+ if (info == NULL) {
+ info = calloc(1, sizeof(struct pw_client_info));
+ if (info == NULL)
+ return NULL;
+
+ info->id = update->id;
+ }
+ if (reset)
+ info->change_mask = 0;
+ info->change_mask |= update->change_mask;
+
+ if (update->change_mask & PW_CLIENT_CHANGE_MASK_PROPS) {
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ info->props = pw_spa_dict_copy(update->props);
+ }
+ return info;
+}
+
+SPA_EXPORT
+struct pw_client_info *pw_client_info_update(struct pw_client_info *info,
+ const struct pw_client_info *update)
+{
+ return pw_client_info_merge(info, update, true);
+}
+
+SPA_EXPORT
+void pw_client_info_free(struct pw_client_info *info)
+{
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ free(info);
+}
+
+SPA_EXPORT
+struct pw_link_info *pw_link_info_merge(struct pw_link_info *info,
+ const struct pw_link_info *update, bool reset)
+{
+ if (update == NULL)
+ return info;
+
+ if (info == NULL) {
+ info = calloc(1, sizeof(struct pw_link_info));
+ if (info == NULL)
+ return NULL;
+
+ info->id = update->id;
+ info->output_node_id = update->output_node_id;
+ info->output_port_id = update->output_port_id;
+ info->input_node_id = update->input_node_id;
+ info->input_port_id = update->input_port_id;
+ }
+ if (reset)
+ info->change_mask = 0;
+ info->change_mask |= update->change_mask;
+
+ if (update->change_mask & PW_LINK_CHANGE_MASK_STATE) {
+ info->state = update->state;
+ free((void *) info->error);
+ info->error = update->error ? strdup(update->error) : NULL;
+ }
+ if (update->change_mask & PW_LINK_CHANGE_MASK_FORMAT) {
+ free(info->format);
+ info->format = update->format ? spa_pod_copy(update->format) : NULL;
+ }
+ if (update->change_mask & PW_LINK_CHANGE_MASK_PROPS) {
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ info->props = pw_spa_dict_copy(update->props);
+ }
+ return info;
+}
+
+SPA_EXPORT
+struct pw_link_info *pw_link_info_update(struct pw_link_info *info,
+ const struct pw_link_info *update)
+{
+ return pw_link_info_merge(info, update, true);
+}
+
+SPA_EXPORT
+void pw_link_info_free(struct pw_link_info *info)
+{
+ free((void *) info->error);
+ free(info->format);
+ if (info->props)
+ pw_spa_dict_destroy(info->props);
+ free(info);
+}
diff --git a/src/pipewire/keys.h b/src/pipewire/keys.h
new file mode 100644
index 0000000..b0faf61
--- /dev/null
+++ b/src/pipewire/keys.h
@@ -0,0 +1,356 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_KEYS_H
+#define PIPEWIRE_KEYS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pipewire/utils.h>
+/**
+ * \defgroup pw_keys Key Names
+ *
+ * A collection of keys that are used to add extra information on objects.
+ *
+ * Keys that start with "pipewire." are in general set-once and then
+ * read-only. They are usually used for security sensitive information that
+ * needs to be fixed.
+ *
+ * Properties from other objects can also appear. This usually suggests some
+ * sort of parent/child or owner/owned relationship.
+ *
+ * \addtogroup pw_keys
+ * \{
+ */
+#define PW_KEY_PROTOCOL "pipewire.protocol" /**< protocol used for connection */
+#define PW_KEY_ACCESS "pipewire.access" /**< how the client access is controlled */
+#define PW_KEY_CLIENT_ACCESS "pipewire.client.access"/**< how the client wants to be access
+ * controlled */
+
+/** Various keys related to the identity of a client process and its security.
+ * Must be obtained from trusted sources by the protocol and placed as
+ * read-only properties. */
+#define PW_KEY_SEC_PID "pipewire.sec.pid" /**< Client pid, set by protocol */
+#define PW_KEY_SEC_UID "pipewire.sec.uid" /**< Client uid, set by protocol*/
+#define PW_KEY_SEC_GID "pipewire.sec.gid" /**< client gid, set by protocol*/
+#define PW_KEY_SEC_LABEL "pipewire.sec.label" /**< client security label, set by protocol*/
+
+#define PW_KEY_LIBRARY_NAME_SYSTEM "library.name.system" /**< name of the system library to use */
+#define PW_KEY_LIBRARY_NAME_LOOP "library.name.loop" /**< name of the loop library to use */
+#define PW_KEY_LIBRARY_NAME_DBUS "library.name.dbus" /**< name of the dbus library to use */
+
+/** object properties */
+#define PW_KEY_OBJECT_PATH "object.path" /**< unique path to construct the object */
+#define PW_KEY_OBJECT_ID "object.id" /**< a global object id */
+#define PW_KEY_OBJECT_SERIAL "object.serial" /**< a 64 bit object serial number. This is a number
+ * incremented for each object that is created.
+ * The lower 32 bits are guaranteed to never be
+ * SPA_ID_INVALID. */
+#define PW_KEY_OBJECT_LINGER "object.linger" /**< the object lives on even after the client
+ * that created it has been destroyed */
+#define PW_KEY_OBJECT_REGISTER "object.register" /**< If the object should be registered. */
+
+
+/* config */
+#define PW_KEY_CONFIG_PREFIX "config.prefix" /**< a config prefix directory */
+#define PW_KEY_CONFIG_NAME "config.name" /**< a config file name */
+#define PW_KEY_CONFIG_OVERRIDE_PREFIX "config.override.prefix" /**< a config override prefix directory */
+#define PW_KEY_CONFIG_OVERRIDE_NAME "config.override.name" /**< a config override file name */
+
+/* context */
+#define PW_KEY_CONTEXT_PROFILE_MODULES "context.profile.modules" /**< a context profile for modules, deprecated */
+#define PW_KEY_USER_NAME "context.user-name" /**< The user name that runs pipewire */
+#define PW_KEY_HOST_NAME "context.host-name" /**< The host name of the machine */
+
+/* core */
+#define PW_KEY_CORE_NAME "core.name" /**< The name of the core. Default is
+ * `pipewire-<username>-<pid>`, overwritten
+ * by env(PIPEWIRE_CORE) */
+#define PW_KEY_CORE_VERSION "core.version" /**< The version of the core. */
+#define PW_KEY_CORE_DAEMON "core.daemon" /**< If the core is listening for connections. */
+
+#define PW_KEY_CORE_ID "core.id" /**< the core id */
+#define PW_KEY_CORE_MONITORS "core.monitors" /**< the apis monitored by core. */
+
+/* cpu */
+#define PW_KEY_CPU_MAX_ALIGN "cpu.max-align" /**< maximum alignment needed to support
+ * all CPU optimizations */
+#define PW_KEY_CPU_CORES "cpu.cores" /**< number of cores */
+
+/* priorities */
+#define PW_KEY_PRIORITY_SESSION "priority.session" /**< priority in session manager */
+#define PW_KEY_PRIORITY_DRIVER "priority.driver" /**< priority to be a driver */
+
+/* remote keys */
+#define PW_KEY_REMOTE_NAME "remote.name" /**< The name of the remote to connect to,
+ * default pipewire-0, overwritten by
+ * env(PIPEWIRE_REMOTE) */
+#define PW_KEY_REMOTE_INTENTION "remote.intention" /**< The intention of the remote connection,
+ * "generic", "screencast" */
+
+/** application keys */
+#define PW_KEY_APP_NAME "application.name" /**< application name. Ex: "Totem Music Player" */
+#define PW_KEY_APP_ID "application.id" /**< a textual id for identifying an
+ * application logically. Ex: "org.gnome.Totem" */
+#define PW_KEY_APP_VERSION "application.version" /**< application version. Ex: "1.2.0" */
+#define PW_KEY_APP_ICON "application.icon" /**< aa base64 blob with PNG image data */
+#define PW_KEY_APP_ICON_NAME "application.icon-name" /**< an XDG icon name for the application.
+ * Ex: "totem" */
+#define PW_KEY_APP_LANGUAGE "application.language" /**< application language if applicable, in
+ * standard POSIX format. Ex: "en_GB" */
+
+#define PW_KEY_APP_PROCESS_ID "application.process.id" /**< process id (pid)*/
+#define PW_KEY_APP_PROCESS_BINARY "application.process.binary" /**< binary name */
+#define PW_KEY_APP_PROCESS_USER "application.process.user" /**< user name */
+#define PW_KEY_APP_PROCESS_HOST "application.process.host" /**< host name */
+#define PW_KEY_APP_PROCESS_MACHINE_ID "application.process.machine-id" /**< the D-Bus host id the
+ * application runs on */
+#define PW_KEY_APP_PROCESS_SESSION_ID "application.process.session-id" /**< login session of the
+ * application, on Unix the
+ * value of $XDG_SESSION_ID. */
+/** window system */
+#define PW_KEY_WINDOW_X11_DISPLAY "window.x11.display" /**< the X11 display string. Ex. ":0.0" */
+
+/** Client properties */
+#define PW_KEY_CLIENT_ID "client.id" /**< a client id */
+#define PW_KEY_CLIENT_NAME "client.name" /**< the client name */
+#define PW_KEY_CLIENT_API "client.api" /**< the client api used to access
+ * PipeWire */
+
+/** Node keys */
+#define PW_KEY_NODE_ID "node.id" /**< node id */
+#define PW_KEY_NODE_NAME "node.name" /**< node name */
+#define PW_KEY_NODE_NICK "node.nick" /**< short node name */
+#define PW_KEY_NODE_DESCRIPTION "node.description" /**< localized human readable node one-line
+ * description. Ex. "Foobar USB Headset" */
+#define PW_KEY_NODE_PLUGGED "node.plugged" /**< when the node was created. As a uint64 in
+ * nanoseconds. */
+
+#define PW_KEY_NODE_SESSION "node.session" /**< the session id this node is part of */
+#define PW_KEY_NODE_GROUP "node.group" /**< the group id this node is part of. Nodes
+ * in the same group are always scheduled
+ * with the same driver. */
+#define PW_KEY_NODE_EXCLUSIVE "node.exclusive" /**< node wants exclusive access to resources */
+#define PW_KEY_NODE_AUTOCONNECT "node.autoconnect" /**< node wants to be automatically connected
+ * to a compatible node */
+#define PW_KEY_NODE_LATENCY "node.latency" /**< the requested latency of the node as
+ * a fraction. Ex: 128/48000 */
+#define PW_KEY_NODE_MAX_LATENCY "node.max-latency" /**< the maximum supported latency of the
+ * node as a fraction. Ex: 1024/48000 */
+#define PW_KEY_NODE_LOCK_QUANTUM "node.lock-quantum" /**< don't change quantum when this node
+ * is active */
+#define PW_KEY_NODE_FORCE_QUANTUM "node.force-quantum" /**< force a quantum while the node is
+ * active */
+#define PW_KEY_NODE_RATE "node.rate" /**< the requested rate of the graph as
+ * a fraction. Ex: 1/48000 */
+#define PW_KEY_NODE_LOCK_RATE "node.lock-rate" /**< don't change rate when this node
+ * is active */
+#define PW_KEY_NODE_FORCE_RATE "node.force-rate" /**< force a rate while the node is
+ * active */
+
+#define PW_KEY_NODE_DONT_RECONNECT "node.dont-reconnect" /**< don't reconnect this node. The node is
+ * initially linked to target.object or the
+ * default node. If the target is removed,
+ * the node is destroyed */
+#define PW_KEY_NODE_ALWAYS_PROCESS "node.always-process" /**< process even when unlinked */
+#define PW_KEY_NODE_WANT_DRIVER "node.want-driver" /**< the node wants to be grouped with a driver
+ * node in order to schedule the graph. */
+#define PW_KEY_NODE_PAUSE_ON_IDLE "node.pause-on-idle" /**< pause the node when idle */
+#define PW_KEY_NODE_SUSPEND_ON_IDLE "node.suspend-on-idle" /**< suspend the node when idle */
+#define PW_KEY_NODE_CACHE_PARAMS "node.cache-params" /**< cache the node params */
+#define PW_KEY_NODE_TRANSPORT_SYNC "node.transport.sync" /**< the node handles transport sync */
+#define PW_KEY_NODE_DRIVER "node.driver" /**< node can drive the graph */
+#define PW_KEY_NODE_STREAM "node.stream" /**< node is a stream, the server side should
+ * add a converter */
+#define PW_KEY_NODE_VIRTUAL "node.virtual" /**< the node is some sort of virtual
+ * object */
+#define PW_KEY_NODE_PASSIVE "node.passive" /**< indicate that a node wants passive links
+ * on output/input/all ports when the value is
+ * "out"/"in"/"true" respectively */
+#define PW_KEY_NODE_LINK_GROUP "node.link-group" /**< the node is internally linked to
+ * nodes with the same link-group */
+#define PW_KEY_NODE_NETWORK "node.network" /**< the node is on a network */
+#define PW_KEY_NODE_TRIGGER "node.trigger" /**< the node is not scheduled automatically
+ * based on the dependencies in the graph
+ * but it will be triggered explicitly. */
+#define PW_KEY_NODE_CHANNELNAMES "node.channel-names" /**< names of node's
+ * channels (unrelated to positions) */
+#define PW_KEY_NODE_DEVICE_PORT_NAME_PREFIX "node.device-port-name-prefix" /** override
+ * port name prefix for device ports, like capture and playback
+ * or disable the prefix completely if an empty string is provided */
+
+/** Port keys */
+#define PW_KEY_PORT_ID "port.id" /**< port id */
+#define PW_KEY_PORT_NAME "port.name" /**< port name */
+#define PW_KEY_PORT_DIRECTION "port.direction" /**< the port direction, one of "in" or "out"
+ * or "control" and "notify" for control ports */
+#define PW_KEY_PORT_ALIAS "port.alias" /**< port alias */
+#define PW_KEY_PORT_PHYSICAL "port.physical" /**< if this is a physical port */
+#define PW_KEY_PORT_TERMINAL "port.terminal" /**< if this port consumes the data */
+#define PW_KEY_PORT_CONTROL "port.control" /**< if this port is a control port */
+#define PW_KEY_PORT_MONITOR "port.monitor" /**< if this port is a monitor port */
+#define PW_KEY_PORT_CACHE_PARAMS "port.cache-params" /**< cache the node port params */
+#define PW_KEY_PORT_EXTRA "port.extra" /**< api specific extra port info, API name
+ * should be prefixed. "jack:flags:56" */
+
+/** link properties */
+#define PW_KEY_LINK_ID "link.id" /**< a link id */
+#define PW_KEY_LINK_INPUT_NODE "link.input.node" /**< input node id of a link */
+#define PW_KEY_LINK_INPUT_PORT "link.input.port" /**< input port id of a link */
+#define PW_KEY_LINK_OUTPUT_NODE "link.output.node" /**< output node id of a link */
+#define PW_KEY_LINK_OUTPUT_PORT "link.output.port" /**< output port id of a link */
+#define PW_KEY_LINK_PASSIVE "link.passive" /**< indicate that a link is passive and
+ * does not cause the graph to be
+ * runnable. */
+#define PW_KEY_LINK_FEEDBACK "link.feedback" /**< indicate that a link is a feedback
+ * link and the target will receive data
+ * in the next cycle */
+
+/** device properties */
+#define PW_KEY_DEVICE_ID "device.id" /**< device id */
+#define PW_KEY_DEVICE_NAME "device.name" /**< device name */
+#define PW_KEY_DEVICE_PLUGGED "device.plugged" /**< when the device was created. As a uint64 in
+ * nanoseconds. */
+#define PW_KEY_DEVICE_NICK "device.nick" /**< a short device nickname */
+#define PW_KEY_DEVICE_STRING "device.string" /**< device string in the underlying layer's
+ * format. Ex. "surround51:0" */
+#define PW_KEY_DEVICE_API "device.api" /**< API this device is accessed with.
+ * Ex. "alsa", "v4l2" */
+#define PW_KEY_DEVICE_DESCRIPTION "device.description" /**< localized human readable device one-line
+ * description. Ex. "Foobar USB Headset" */
+#define PW_KEY_DEVICE_BUS_PATH "device.bus-path" /**< bus path to the device in the OS'
+ * format. Ex. "pci-0000:00:14.0-usb-0:3.2:1.0" */
+#define PW_KEY_DEVICE_SERIAL "device.serial" /**< Serial number if applicable */
+#define PW_KEY_DEVICE_VENDOR_ID "device.vendor.id" /**< vendor ID if applicable */
+#define PW_KEY_DEVICE_VENDOR_NAME "device.vendor.name" /**< vendor name if applicable */
+#define PW_KEY_DEVICE_PRODUCT_ID "device.product.id" /**< product ID if applicable */
+#define PW_KEY_DEVICE_PRODUCT_NAME "device.product.name" /**< product name if applicable */
+#define PW_KEY_DEVICE_CLASS "device.class" /**< device class */
+#define PW_KEY_DEVICE_FORM_FACTOR "device.form-factor" /**< form factor if applicable. One of
+ * "internal", "speaker", "handset", "tv",
+ * "webcam", "microphone", "headset",
+ * "headphone", "hands-free", "car", "hifi",
+ * "computer", "portable" */
+#define PW_KEY_DEVICE_BUS "device.bus" /**< bus of the device if applicable. One of
+ * "isa", "pci", "usb", "firewire",
+ * "bluetooth" */
+#define PW_KEY_DEVICE_SUBSYSTEM "device.subsystem" /**< device subsystem */
+#define PW_KEY_DEVICE_SYSFS_PATH "device.sysfs.path" /**< device sysfs path */
+#define PW_KEY_DEVICE_ICON "device.icon" /**< icon for the device. A base64 blob
+ * containing PNG image data */
+#define PW_KEY_DEVICE_ICON_NAME "device.icon-name" /**< an XDG icon name for the device.
+ * Ex. "sound-card-speakers-usb" */
+#define PW_KEY_DEVICE_INTENDED_ROLES "device.intended-roles" /**< intended use. A space separated list of
+ * roles (see PW_KEY_MEDIA_ROLE) this device
+ * is particularly well suited for, due to
+ * latency, quality or form factor. */
+#define PW_KEY_DEVICE_CACHE_PARAMS "device.cache-params" /**< cache the device spa params */
+
+/** module properties */
+#define PW_KEY_MODULE_ID "module.id" /**< the module id */
+#define PW_KEY_MODULE_NAME "module.name" /**< the name of the module */
+#define PW_KEY_MODULE_AUTHOR "module.author" /**< the author's name */
+#define PW_KEY_MODULE_DESCRIPTION "module.description" /**< a human readable one-line description
+ * of the module's purpose.*/
+#define PW_KEY_MODULE_USAGE "module.usage" /**< a human readable usage description of
+ * the module's arguments. */
+#define PW_KEY_MODULE_VERSION "module.version" /**< a version string for the module. */
+
+/** Factory properties */
+#define PW_KEY_FACTORY_ID "factory.id" /**< the factory id */
+#define PW_KEY_FACTORY_NAME "factory.name" /**< the name of the factory */
+#define PW_KEY_FACTORY_USAGE "factory.usage" /**< the usage of the factory */
+#define PW_KEY_FACTORY_TYPE_NAME "factory.type.name" /**< the name of the type created by a factory */
+#define PW_KEY_FACTORY_TYPE_VERSION "factory.type.version" /**< the version of the type created by a factory */
+
+/** Stream properties */
+#define PW_KEY_STREAM_IS_LIVE "stream.is-live" /**< Indicates that the stream is live. */
+#define PW_KEY_STREAM_LATENCY_MIN "stream.latency.min" /**< The minimum latency of the stream. */
+#define PW_KEY_STREAM_LATENCY_MAX "stream.latency.max" /**< The maximum latency of the stream */
+#define PW_KEY_STREAM_MONITOR "stream.monitor" /**< Indicates that the stream is monitoring
+ * and might select a less accurate but faster
+ * conversion algorithm. */
+#define PW_KEY_STREAM_DONT_REMIX "stream.dont-remix" /**< don't remix channels */
+#define PW_KEY_STREAM_CAPTURE_SINK "stream.capture.sink" /**< Try to capture the sink output instead of
+ * source output */
+
+/** Media */
+#define PW_KEY_MEDIA_TYPE "media.type" /**< Media type, one of
+ * Audio, Video, Midi */
+#define PW_KEY_MEDIA_CATEGORY "media.category" /**< Media Category:
+ * Playback, Capture, Duplex, Monitor, Manager */
+#define PW_KEY_MEDIA_ROLE "media.role" /**< Role: Movie, Music, Camera,
+ * Screen, Communication, Game,
+ * Notification, DSP, Production,
+ * Accessibility, Test */
+#define PW_KEY_MEDIA_CLASS "media.class" /**< class Ex: "Video/Source" */
+#define PW_KEY_MEDIA_NAME "media.name" /**< media name. Ex: "Pink Floyd: Time" */
+#define PW_KEY_MEDIA_TITLE "media.title" /**< title. Ex: "Time" */
+#define PW_KEY_MEDIA_ARTIST "media.artist" /**< artist. Ex: "Pink Floyd" */
+#define PW_KEY_MEDIA_COPYRIGHT "media.copyright" /**< copyright string */
+#define PW_KEY_MEDIA_SOFTWARE "media.software" /**< generator software */
+#define PW_KEY_MEDIA_LANGUAGE "media.language" /**< language in POSIX format. Ex: en_GB */
+#define PW_KEY_MEDIA_FILENAME "media.filename" /**< filename */
+#define PW_KEY_MEDIA_ICON "media.icon" /**< icon for the media, a base64 blob with
+ * PNG image data */
+#define PW_KEY_MEDIA_ICON_NAME "media.icon-name" /**< an XDG icon name for the media.
+ * Ex: "audio-x-mp3" */
+#define PW_KEY_MEDIA_COMMENT "media.comment" /**< extra comment */
+#define PW_KEY_MEDIA_DATE "media.date" /**< date of the media */
+#define PW_KEY_MEDIA_FORMAT "media.format" /**< format of the media */
+
+/** format related properties */
+#define PW_KEY_FORMAT_DSP "format.dsp" /**< a dsp format.
+ * Ex: "32 bit float mono audio" */
+/** audio related properties */
+#define PW_KEY_AUDIO_CHANNEL "audio.channel" /**< an audio channel. Ex: "FL" */
+#define PW_KEY_AUDIO_RATE "audio.rate" /**< an audio samplerate */
+#define PW_KEY_AUDIO_CHANNELS "audio.channels" /**< number of audio channels */
+#define PW_KEY_AUDIO_FORMAT "audio.format" /**< an audio format. Ex: "S16LE" */
+#define PW_KEY_AUDIO_ALLOWED_RATES "audio.allowed-rates" /**< a list of allowed samplerates
+ * ex. "[ 44100 48000 ]" */
+
+/** video related properties */
+#define PW_KEY_VIDEO_RATE "video.framerate" /**< a video framerate */
+#define PW_KEY_VIDEO_FORMAT "video.format" /**< a video format */
+#define PW_KEY_VIDEO_SIZE "video.size" /**< a video size as "<width>x<height" */
+
+#define PW_KEY_TARGET_OBJECT "target.object" /**< a target object to link to. This can be
+ * and object name or object.serial */
+
+#ifndef PW_REMOVE_DEPRECATED
+#define PW_KEY_PRIORITY_MASTER PW_DEPRECATED("priority.master") /**< deprecated, use priority.driver */
+#define PW_KEY_NODE_TARGET PW_DEPRECATED("node.target") /**< deprecated since 0.3.64, use target.object. */
+#endif /* PW_REMOVE_DEPRECATED */
+
+/** \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_KEYS_H */
diff --git a/src/pipewire/link.h b/src/pipewire/link.h
new file mode 100644
index 0000000..8130f4b
--- /dev/null
+++ b/src/pipewire/link.h
@@ -0,0 +1,148 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_LINK_H
+#define PIPEWIRE_LINK_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+#include <pipewire/proxy.h>
+
+/** \defgroup pw_link Link
+ *
+ * A link is the connection between 2 nodes (\ref pw_node). Nodes are
+ * linked together on ports.
+ *
+ * The link is responsible for negotiating the format and buffers for
+ * the nodes.
+ *
+ */
+
+/**
+ * \addtogroup pw_link
+ * \{
+ */
+
+#define PW_TYPE_INTERFACE_Link PW_TYPE_INFO_INTERFACE_BASE "Link"
+
+#define PW_VERSION_LINK 3
+struct pw_link;
+
+/** \enum pw_link_state The different link states */
+enum pw_link_state {
+ PW_LINK_STATE_ERROR = -2, /**< the link is in error */
+ PW_LINK_STATE_UNLINKED = -1, /**< the link is unlinked */
+ PW_LINK_STATE_INIT = 0, /**< the link is initialized */
+ PW_LINK_STATE_NEGOTIATING = 1, /**< the link is negotiating formats */
+ PW_LINK_STATE_ALLOCATING = 2, /**< the link is allocating buffers */
+ PW_LINK_STATE_PAUSED = 3, /**< the link is paused */
+ PW_LINK_STATE_ACTIVE = 4, /**< the link is active */
+};
+
+/** Convert a \ref pw_link_state to a readable string */
+const char * pw_link_state_as_string(enum pw_link_state state);
+/** The link information. Extra information can be added in later versions */
+struct pw_link_info {
+ uint32_t id; /**< id of the global */
+ uint32_t output_node_id; /**< server side output node id */
+ uint32_t output_port_id; /**< output port id */
+ uint32_t input_node_id; /**< server side input node id */
+ uint32_t input_port_id; /**< input port id */
+#define PW_LINK_CHANGE_MASK_STATE (1 << 0)
+#define PW_LINK_CHANGE_MASK_FORMAT (1 << 1)
+#define PW_LINK_CHANGE_MASK_PROPS (1 << 2)
+#define PW_LINK_CHANGE_MASK_ALL ((1 << 3)-1)
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ enum pw_link_state state; /**< the current state of the link */
+ const char *error; /**< an error reason if \a state is error */
+ struct spa_pod *format; /**< format over link */
+ struct spa_dict *props; /**< the properties of the link */
+};
+
+struct pw_link_info *
+pw_link_info_update(struct pw_link_info *info,
+ const struct pw_link_info *update);
+
+struct pw_link_info *
+pw_link_info_merge(struct pw_link_info *info,
+ const struct pw_link_info *update, bool reset);
+
+void
+pw_link_info_free(struct pw_link_info *info);
+
+
+#define PW_LINK_EVENT_INFO 0
+#define PW_LINK_EVENT_NUM 1
+
+/** Link events */
+struct pw_link_events {
+#define PW_VERSION_LINK_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify link info
+ *
+ * \param info info about the link
+ */
+ void (*info) (void *data, const struct pw_link_info *info);
+};
+
+#define PW_LINK_METHOD_ADD_LISTENER 0
+#define PW_LINK_METHOD_NUM 1
+
+/** Link methods */
+struct pw_link_methods {
+#define PW_VERSION_LINK_METHODS 0
+ uint32_t version;
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_link_events *events,
+ 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__)
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_LINK_H */
diff --git a/src/pipewire/log.c b/src/pipewire/log.c
new file mode 100644
index 0000000..2484109
--- /dev/null
+++ b/src/pipewire/log.c
@@ -0,0 +1,291 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <limits.h>
+#include <fnmatch.h>
+
+#include <spa/support/log-impl.h>
+
+#include <spa/pod/pod.h>
+#include <spa/debug/types.h>
+#include <spa/pod/iter.h>
+#include <spa/utils/list.h>
+
+#include <pipewire/log.h>
+#include <pipewire/private.h>
+
+SPA_LOG_IMPL(default_log);
+
+#define DEFAULT_LOG_LEVEL SPA_LOG_LEVEL_WARN
+
+SPA_EXPORT
+enum spa_log_level pw_log_level = DEFAULT_LOG_LEVEL;
+
+static struct spa_log *global_log = &default_log.log;
+
+SPA_EXPORT
+struct spa_log_topic *PW_LOG_TOPIC_DEFAULT;
+
+PW_LOG_TOPIC_STATIC(log_topic, "pw.log"); /* log topic for this file here */
+PW_LOG_TOPIC(log_buffers, "pw.buffers");
+PW_LOG_TOPIC(log_client, "pw.client");
+PW_LOG_TOPIC(log_conf, "pw.conf");
+PW_LOG_TOPIC(log_context, "pw.context");
+PW_LOG_TOPIC(log_core, "pw.core");
+PW_LOG_TOPIC(log_data_loop, "pw.data-loop");
+PW_LOG_TOPIC(log_device, "pw.device");
+PW_LOG_TOPIC(log_factory, "pw.factory");
+PW_LOG_TOPIC(log_filter, "pw.filter");
+PW_LOG_TOPIC(log_global, "pw.global");
+PW_LOG_TOPIC(log_link, "pw.link");
+PW_LOG_TOPIC(log_loop, "pw.loop");
+PW_LOG_TOPIC(log_main_loop, "pw.main-loop");
+PW_LOG_TOPIC(log_mem, "pw.mem");
+PW_LOG_TOPIC(log_metadata, "pw.metadata");
+PW_LOG_TOPIC(log_module, "pw.module");
+PW_LOG_TOPIC(log_node, "pw.node");
+PW_LOG_TOPIC(log_port, "pw.port");
+PW_LOG_TOPIC(log_properties, "pw.props");
+PW_LOG_TOPIC(log_protocol, "pw.protocol");
+PW_LOG_TOPIC(log_proxy, "pw.proxy");
+PW_LOG_TOPIC(log_resource, "pw.resource");
+PW_LOG_TOPIC(log_stream, "pw.stream");
+PW_LOG_TOPIC(log_thread_loop, "pw.thread-loop");
+PW_LOG_TOPIC(log_work_queue, "pw.work-queue");
+
+PW_LOG_TOPIC(PW_LOG_TOPIC_DEFAULT, "default");
+
+/** Set the global log interface
+ * \param log the global log to set
+ */
+SPA_EXPORT
+void pw_log_set(struct spa_log *log)
+{
+ global_log = log ? log : &default_log.log;
+ global_log->level = pw_log_level;
+}
+
+bool pw_log_is_default(void)
+{
+ return global_log == &default_log.log;
+}
+
+/** Get the global log interface
+ * \return the global log
+ */
+SPA_EXPORT
+struct spa_log *pw_log_get(void)
+{
+ return global_log;
+}
+
+/** Set the global log level
+ * \param level the new log level
+ */
+SPA_EXPORT
+void pw_log_set_level(enum spa_log_level level)
+{
+ pw_log_level = level;
+ global_log->level = level;
+}
+
+/** Log a message for the given topic
+ * \param level the log level
+ * \param topic the topic
+ * \param file the file this message originated from
+ * \param line the line number
+ * \param func the function
+ * \param fmt the printf style format
+ * \param ... printf style arguments to log
+ *
+ */
+SPA_EXPORT
+void
+pw_log_logt(enum spa_log_level level,
+ const struct spa_log_topic *topic,
+ const char *file,
+ int line,
+ const char *func,
+ const char *fmt, ...)
+{
+ if (SPA_UNLIKELY(pw_log_topic_enabled(level, topic))) {
+ va_list args;
+ va_start(args, fmt);
+ spa_log_logtv(global_log, level, topic, file, line, func, fmt, args);
+ va_end(args);
+ }
+}
+
+/** Log a message for the given topic with va_list
+ * \param level the log level
+ * \param topic the topic
+ * \param file the file this message originated from
+ * \param line the line number
+ * \param func the function
+ * \param fmt the printf style format
+ * \param args a va_list of arguments
+ *
+ */
+SPA_EXPORT
+void
+pw_log_logtv(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)
+{
+ spa_log_logtv(global_log, level, topic, file, line, func, fmt, args);
+}
+
+
+/** Log a message for the default topic with va_list
+ * \param level the log level
+ * \param file the file this message originated from
+ * \param line the line number
+ * \param func the function
+ * \param fmt the printf style format
+ * \param args a va_list of arguments
+ *
+ */
+SPA_EXPORT
+void
+pw_log_logv(enum spa_log_level level,
+ const char *file,
+ int line,
+ const char *func,
+ const char *fmt,
+ va_list args)
+{
+ pw_log_logtv(level, PW_LOG_TOPIC_DEFAULT, file, line, func, fmt, args);
+}
+
+/** Log a message for the default topic
+ * \param level the log level
+ * \param file the file this message originated from
+ * \param line the line number
+ * \param func the function
+ * \param fmt the printf style format
+ * \param ... printf style arguments to log
+ *
+ */
+SPA_EXPORT
+void
+pw_log_log(enum spa_log_level level,
+ const char *file,
+ int line,
+ const char *func,
+ const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ pw_log_logtv(level, PW_LOG_TOPIC_DEFAULT, file, line, func, fmt, args);
+ va_end(args);
+}
+
+/** \fn void pw_log_error (const char *format, ...)
+ * Log an error message
+ * \param format a printf style format
+ * \param ... printf style arguments
+ */
+/** \fn void pw_log_warn (const char *format, ...)
+ * Log a warning message
+ * \param format a printf style format
+ * \param ... printf style arguments
+ */
+/** \fn void pw_log_info (const char *format, ...)
+ * Log an info message
+ * \param format a printf style format
+ * \param ... printf style arguments
+ */
+/** \fn void pw_log_debug (const char *format, ...)
+ * Log a debug message
+ * \param format a printf style format
+ * \param ... printf style arguments
+ */
+/** \fn void pw_log_trace (const char *format, ...)
+ * Log a trace message. Trace messages may be generated from
+ * \param format a printf style format
+ * \param ... printf style arguments
+ * realtime threads
+ */
+
+#include <spa/debug/pod.h>
+#include <spa/debug/log.h>
+
+void pw_log_log_object(enum spa_log_level level,
+ const struct spa_log_topic *topic, const char *file,
+ int line, const char *func, uint32_t flags, const void *object)
+{
+ struct spa_debug_log_ctx ctx = SPA_LOGF_DEBUG_INIT(global_log, level,
+ topic, file, line, func );
+ if (flags & PW_LOG_OBJECT_POD) {
+ const struct spa_pod *pod = object;
+ if (pod == NULL) {
+ pw_log_logt(level, topic, file, line, func, "NULL");
+ } else {
+ spa_debugc_pod(&ctx.ctx, 0, SPA_TYPE_ROOT, pod);
+ }
+ }
+}
+
+SPA_EXPORT
+void
+_pw_log_topic_new(struct spa_log_topic *topic)
+{
+ spa_log_topic_init(global_log, topic);
+}
+
+void
+pw_log_init(void)
+{
+ PW_LOG_TOPIC_INIT(PW_LOG_TOPIC_DEFAULT);
+ PW_LOG_TOPIC_INIT(log_buffers);
+ PW_LOG_TOPIC_INIT(log_client);
+ PW_LOG_TOPIC_INIT(log_conf);
+ PW_LOG_TOPIC_INIT(log_context);
+ PW_LOG_TOPIC_INIT(log_core);
+ PW_LOG_TOPIC_INIT(log_data_loop);
+ PW_LOG_TOPIC_INIT(log_device);
+ PW_LOG_TOPIC_INIT(log_factory);
+ PW_LOG_TOPIC_INIT(log_filter);
+ PW_LOG_TOPIC_INIT(log_global);
+ PW_LOG_TOPIC_INIT(log_link);
+ PW_LOG_TOPIC_INIT(log_loop);
+ PW_LOG_TOPIC_INIT(log_main_loop);
+ PW_LOG_TOPIC_INIT(log_mem);
+ PW_LOG_TOPIC_INIT(log_metadata);
+ PW_LOG_TOPIC_INIT(log_module);
+ PW_LOG_TOPIC_INIT(log_node);
+ PW_LOG_TOPIC_INIT(log_port);
+ PW_LOG_TOPIC_INIT(log_properties);
+ PW_LOG_TOPIC_INIT(log_protocol);
+ PW_LOG_TOPIC_INIT(log_proxy);
+ PW_LOG_TOPIC_INIT(log_resource);
+ PW_LOG_TOPIC_INIT(log_stream);
+ PW_LOG_TOPIC_INIT(log_thread_loop);
+ PW_LOG_TOPIC_INIT(log_topic);
+ PW_LOG_TOPIC_INIT(log_work_queue);
+}
diff --git a/src/pipewire/log.h b/src/pipewire/log.h
new file mode 100644
index 0000000..f91dc11
--- /dev/null
+++ b/src/pipewire/log.h
@@ -0,0 +1,182 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_LOG_H
+#define PIPEWIRE_LOG_H
+
+#include <spa/support/log.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \defgroup pw_log Logging
+ *
+ * \brief Logging functions of PipeWire
+ *
+ * Logging is performed to stdout and stderr. Trace logging is performed
+ * in a lockfree ringbuffer and written out from the main thread as to not
+ * block the realtime threads.
+ */
+
+/**
+ * \addtogroup pw_log
+ * \{
+ */
+/** The global log level */
+extern enum spa_log_level pw_log_level;
+
+extern struct spa_log_topic *PW_LOG_TOPIC_DEFAULT;
+
+/** Configure a logging module. This is usually done automatically
+ * in pw_init() but you can install a custom logger before calling
+ * pw_init(). */
+void pw_log_set(struct spa_log *log);
+
+/** Get the log interface */
+struct spa_log *pw_log_get(void);
+
+/** Configure the logging level */
+void pw_log_set_level(enum spa_log_level level);
+
+/** Log a message for a topic */
+void
+pw_log_logt(enum spa_log_level level,
+ const struct spa_log_topic *topic,
+ const char *file,
+ int line, const char *func,
+ const char *fmt, ...) SPA_PRINTF_FUNC(6, 7);
+
+/** Log a message for a topic */
+void
+pw_log_logtv(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) SPA_PRINTF_FUNC(6, 0);
+
+
+
+/** Log a message for the default topic */
+void
+pw_log_log(enum spa_log_level level,
+ const char *file,
+ int line, const char *func,
+ const char *fmt, ...) SPA_PRINTF_FUNC(5, 6);
+
+/** Log a message for the default topic */
+void
+pw_log_logv(enum spa_log_level level,
+ const char *file,
+ int line, const char *func,
+ const char *fmt, va_list args) SPA_PRINTF_FUNC(5, 0);
+
+/** Initialize the log topic. The returned topic is owned by the pipewire
+ * context and the topic must not be modified or freed.
+ * Do not use this function directly, use one of PW_LOG_TOPIC_* instead.
+ *
+ * \see PW_LOG_TOPIC_STATIC
+ * \see PW_LOG_TOPIC_EXTERN
+ * \see PW_LOG_TOPIC
+ */
+void
+_pw_log_topic_new(struct spa_log_topic *topic);
+
+/**
+ * Declare a static log topic named \a var. The usual usage is:
+ * \code
+ * PW_LOG_TOPIC_STATIC(my_topic);
+ * #define PW_LOG_TOPIC_DEFAULT my_topic
+ *
+ * void foo() {
+ * pw_log_debug("bar");
+ * }
+ * \endcode
+ */
+#define PW_LOG_TOPIC_STATIC(var, topic) \
+ static struct spa_log_topic var##__LINE__ = SPA_LOG_TOPIC(0, topic); \
+ static struct spa_log_topic *(var) = &(var##__LINE__)
+
+/**
+ * Declare a static log topic named \a var.
+ * See \ref PW_LOG_TOPIC_STATIC for an example usage.
+ */
+#define PW_LOG_TOPIC_EXTERN(var) \
+ extern struct spa_log_topic *var
+
+/**
+ * Declare a static log topic named \a var.
+ * See \ref PW_LOG_TOPIC_STATIC for an example usage.
+ */
+#define PW_LOG_TOPIC(var, topic) \
+ struct spa_log_topic var##__LINE__ = SPA_LOG_TOPIC(0, topic); \
+ struct spa_log_topic *(var) = &(var##__LINE__)
+
+#define PW_LOG_TOPIC_INIT(var) \
+ spa_log_topic_init(pw_log_get(), var);
+
+/** Check if a loglevel is enabled */
+#define pw_log_level_enabled(lev) (pw_log_level >= (lev))
+#define pw_log_topic_enabled(lev,t) ((t) && (t)->has_custom_level ? (t)->level >= (lev) : pw_log_level_enabled((lev)))
+
+#define pw_logtv(lev,topic,fmt,ap) \
+({ \
+ if (SPA_UNLIKELY(pw_log_topic_enabled(lev,topic))) \
+ pw_log_logtv(lev,topic,__FILE__,__LINE__,__func__,fmt,ap); \
+})
+
+#define pw_logt(lev,topic,...) \
+({ \
+ if (SPA_UNLIKELY(pw_log_topic_enabled(lev,topic))) \
+ pw_log_logt(lev,topic,__FILE__,__LINE__,__func__,__VA_ARGS__); \
+})
+
+#define pw_log(lev,...) pw_logt(lev,PW_LOG_TOPIC_DEFAULT,__VA_ARGS__)
+
+#define pw_log_error(...) pw_log(SPA_LOG_LEVEL_ERROR,__VA_ARGS__)
+#define pw_log_warn(...) pw_log(SPA_LOG_LEVEL_WARN,__VA_ARGS__)
+#define pw_log_info(...) pw_log(SPA_LOG_LEVEL_INFO,__VA_ARGS__)
+#define pw_log_debug(...) pw_log(SPA_LOG_LEVEL_DEBUG,__VA_ARGS__)
+#define pw_log_trace(...) pw_log(SPA_LOG_LEVEL_TRACE,__VA_ARGS__)
+
+#define pw_logt_error(t,...) pw_logt(SPA_LOG_LEVEL_ERROR,t,__VA_ARGS__)
+#define pw_logt_warn(t,...) pw_logt(SPA_LOG_LEVEL_WARN,t,__VA_ARGS__)
+#define pw_logt_info(t,...) pw_logt(SPA_LOG_LEVEL_INFO,t,__VA_ARGS__)
+#define pw_logt_debug(t,...) pw_logt(SPA_LOG_LEVEL_DEBUG,t,__VA_ARGS__)
+#define pw_logt_trace(t,...) pw_logt(SPA_LOG_LEVEL_TRACE,t,__VA_ARGS__)
+
+#ifndef FASTPATH
+#define pw_log_trace_fp(...) pw_log(SPA_LOG_LEVEL_TRACE,__VA_ARGS__)
+#else
+#define pw_log_trace_fp(...)
+#endif
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* PIPEWIRE_LOG_H */
diff --git a/src/pipewire/loop.c b/src/pipewire/loop.c
new file mode 100644
index 0000000..da766a6
--- /dev/null
+++ b/src/pipewire/loop.c
@@ -0,0 +1,162 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+
+#include <spa/support/loop.h>
+#include <spa/utils/names.h>
+#include <spa/utils/result.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/loop.h>
+#include <pipewire/log.h>
+#include <pipewire/type.h>
+
+PW_LOG_TOPIC_EXTERN(log_loop);
+#define PW_LOG_TOPIC_DEFAULT log_loop
+
+/** \cond */
+
+struct impl {
+ struct pw_loop this;
+
+ struct spa_handle *system_handle;
+ struct spa_handle *loop_handle;
+};
+/** \endcond */
+
+/** Create a new loop
+ * \returns a newly allocated loop
+ */
+SPA_EXPORT
+struct pw_loop *pw_loop_new(const struct spa_dict *props)
+{
+ int res;
+ struct impl *impl;
+ struct pw_loop *this;
+ void *iface;
+ struct spa_support support[32];
+ uint32_t n_support;
+ const char *lib;
+
+ n_support = pw_get_support(support, 32);
+
+ impl = calloc(1, sizeof(struct impl));
+ if (impl == NULL) {
+ res = -errno;
+ goto error_cleanup;
+ }
+
+ this = &impl->this;
+
+ if (props)
+ lib = spa_dict_lookup(props, PW_KEY_LIBRARY_NAME_SYSTEM);
+ else
+ lib = NULL;
+
+ impl->system_handle = pw_load_spa_handle(lib,
+ SPA_NAME_SUPPORT_SYSTEM,
+ props, n_support, support);
+ if (impl->system_handle == NULL) {
+ res = -errno;
+ pw_log_error("%p: can't make "SPA_NAME_SUPPORT_SYSTEM" handle: %m", this);
+ goto error_free;
+ }
+
+ if ((res = spa_handle_get_interface(impl->system_handle,
+ SPA_TYPE_INTERFACE_System,
+ &iface)) < 0) {
+ pw_log_error("%p: can't get System interface: %s", this, spa_strerror(res));
+ goto error_unload_system;
+ }
+ this->system = iface;
+
+ support[n_support++] = SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_System, iface);
+
+ if (props)
+ lib = spa_dict_lookup(props, PW_KEY_LIBRARY_NAME_LOOP);
+ else
+ lib = NULL;
+
+ impl->loop_handle = pw_load_spa_handle(lib,
+ SPA_NAME_SUPPORT_LOOP, props,
+ n_support, support);
+ if (impl->loop_handle == NULL) {
+ res = -errno;
+ pw_log_error("%p: can't make "SPA_NAME_SUPPORT_LOOP" handle: %m", this);
+ goto error_unload_system;
+ }
+
+ if ((res = spa_handle_get_interface(impl->loop_handle,
+ SPA_TYPE_INTERFACE_Loop,
+ &iface)) < 0) {
+ pw_log_error("%p: can't get Loop interface: %s",
+ this, spa_strerror(res));
+ goto error_unload_loop;
+ }
+ this->loop = iface;
+
+ if ((res = spa_handle_get_interface(impl->loop_handle,
+ SPA_TYPE_INTERFACE_LoopControl,
+ &iface)) < 0) {
+ pw_log_error("%p: can't get LoopControl interface: %s",
+ this, spa_strerror(res));
+ goto error_unload_loop;
+ }
+ this->control = iface;
+
+ if ((res = spa_handle_get_interface(impl->loop_handle,
+ SPA_TYPE_INTERFACE_LoopUtils,
+ &iface)) < 0) {
+ pw_log_error("%p: can't get LoopUtils interface: %s",
+ this, spa_strerror(res));
+ goto error_unload_loop;
+ }
+ this->utils = iface;
+
+ return this;
+
+error_unload_loop:
+ pw_unload_spa_handle(impl->loop_handle);
+error_unload_system:
+ pw_unload_spa_handle(impl->system_handle);
+error_free:
+ free(impl);
+error_cleanup:
+ errno = -res;
+ return NULL;
+}
+
+/** Destroy a loop
+ * \param loop a loop to destroy
+ */
+SPA_EXPORT
+void pw_loop_destroy(struct pw_loop *loop)
+{
+ struct impl *impl = SPA_CONTAINER_OF(loop, struct impl, this);
+
+ pw_unload_spa_handle(impl->loop_handle);
+ pw_unload_spa_handle(impl->system_handle);
+ free(impl);
+}
diff --git a/src/pipewire/loop.h b/src/pipewire/loop.h
new file mode 100644
index 0000000..42a630d
--- /dev/null
+++ b/src/pipewire/loop.h
@@ -0,0 +1,90 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_LOOP_H
+#define PIPEWIRE_LOOP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/support/loop.h>
+#include <spa/utils/dict.h>
+
+/** \defgroup pw_loop Loop
+ *
+ * PipeWire loop object provides an implementation of
+ * the spa loop interfaces. It can be used to implement various
+ * event loops.
+ */
+
+/**
+ * \addtogroup pw_loop
+ * \{
+ */
+
+struct pw_loop {
+ struct spa_system *system; /**< system utils */
+ struct spa_loop *loop; /**< wrapped loop */
+ struct spa_loop_control *control; /**< loop control */
+ struct spa_loop_utils *utils; /**< loop utils */
+};
+
+struct pw_loop *
+pw_loop_new(const struct spa_dict *props);
+
+void
+pw_loop_destroy(struct pw_loop *loop);
+
+#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_iterate(l,...) spa_loop_control_iterate((l)->control,__VA_ARGS__)
+#define pw_loop_leave(l) spa_loop_control_leave((l)->control)
+
+#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__)
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_LOOP_H */
diff --git a/src/pipewire/main-loop.c b/src/pipewire/main-loop.c
new file mode 100644
index 0000000..4e8c8aa
--- /dev/null
+++ b/src/pipewire/main-loop.c
@@ -0,0 +1,157 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "pipewire/log.h"
+#include "pipewire/main-loop.h"
+#include "pipewire/private.h"
+
+PW_LOG_TOPIC_EXTERN(log_main_loop);
+#define PW_LOG_TOPIC_DEFAULT log_main_loop
+
+static int do_stop(struct spa_loop *loop, bool async, uint32_t seq,
+ const void *data, size_t size, void *user_data)
+{
+ struct pw_main_loop *this = user_data;
+ pw_log_debug("%p: do stop", this);
+ this->running = false;
+ return 0;
+}
+
+static struct pw_main_loop *loop_new(struct pw_loop *loop, const struct spa_dict *props)
+{
+ struct pw_main_loop *this;
+ int res;
+
+ this = calloc(1, sizeof(struct pw_main_loop));
+ if (this == NULL) {
+ res = -errno;
+ goto error_cleanup;
+ }
+
+ pw_log_debug("%p: new", this);
+
+ if (loop == NULL) {
+ loop = pw_loop_new(props);
+ this->created = true;
+ }
+ if (loop == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+ this->loop = loop;
+
+ spa_hook_list_init(&this->listener_list);
+
+ return this;
+
+error_free:
+ free(this);
+error_cleanup:
+ errno = -res;
+ return NULL;
+}
+
+/** Create a new main loop
+ * \return a newly allocated \ref pw_main_loop
+ *
+ */
+SPA_EXPORT
+struct pw_main_loop *pw_main_loop_new(const struct spa_dict *props)
+{
+ return loop_new(NULL, props);
+}
+
+/** Destroy a main loop
+ * \param loop the main loop to destroy
+ *
+ */
+SPA_EXPORT
+void pw_main_loop_destroy(struct pw_main_loop *loop)
+{
+ pw_log_debug("%p: destroy", loop);
+ pw_main_loop_emit_destroy(loop);
+
+ if (loop->created)
+ pw_loop_destroy(loop->loop);
+
+ spa_hook_list_clean(&loop->listener_list);
+
+ free(loop);
+}
+
+SPA_EXPORT
+void pw_main_loop_add_listener(struct pw_main_loop *loop,
+ struct spa_hook *listener,
+ const struct pw_main_loop_events *events,
+ void *data)
+{
+ spa_hook_list_append(&loop->listener_list, listener, events, data);
+}
+
+SPA_EXPORT
+struct pw_loop * pw_main_loop_get_loop(struct pw_main_loop *loop)
+{
+ return loop->loop;
+}
+
+/** Stop a main loop
+ * \param loop a \ref pw_main_loop to stop
+ *
+ * The call to \ref pw_main_loop_run() will return
+ *
+ */
+SPA_EXPORT
+int pw_main_loop_quit(struct pw_main_loop *loop)
+{
+ pw_log_debug("%p: quit", loop);
+ return pw_loop_invoke(loop->loop, do_stop, 1, NULL, 0, false, loop);
+}
+
+/** Start a main loop
+ * \param loop the main loop to start
+ *
+ * Start running \a loop. This function blocks until \ref pw_main_loop_quit()
+ * has been called
+ *
+ */
+SPA_EXPORT
+int pw_main_loop_run(struct pw_main_loop *loop)
+{
+ int res = 0;
+
+ pw_log_debug("%p: run", loop);
+
+ loop->running = true;
+ pw_loop_enter(loop->loop);
+ while (loop->running) {
+ if ((res = pw_loop_iterate(loop->loop, -1)) < 0) {
+ if (res == -EINTR)
+ continue;
+ pw_log_warn("%p: iterate error %d (%s)",
+ loop, res, spa_strerror(res));
+ }
+ }
+ pw_loop_leave(loop->loop);
+ return res;
+}
diff --git a/src/pipewire/main-loop.h b/src/pipewire/main-loop.h
new file mode 100644
index 0000000..2225bee
--- /dev/null
+++ b/src/pipewire/main-loop.h
@@ -0,0 +1,86 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_MAIN_LOOP_H
+#define PIPEWIRE_MAIN_LOOP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \defgroup pw_main_loop Main Loop
+ *
+ * A main loop object
+ */
+
+/**
+ * \addtogroup pw_main_loop
+ * \{
+ */
+
+/** A main loop object */
+struct pw_main_loop;
+
+#include <pipewire/loop.h>
+
+/** Events of the main loop */
+struct pw_main_loop_events {
+#define PW_VERSION_MAIN_LOOP_EVENTS 0
+ uint32_t version;
+
+ /** Emitted when the main loop is destroyed */
+ void (*destroy) (void *data);
+};
+
+/** Create a new main loop. */
+struct pw_main_loop *
+pw_main_loop_new(const struct spa_dict *props);
+
+/** Add an event listener */
+void pw_main_loop_add_listener(struct pw_main_loop *loop,
+ struct spa_hook *listener,
+ const struct pw_main_loop_events *events,
+ void *data);
+
+/** Get the loop implementation */
+struct pw_loop * pw_main_loop_get_loop(struct pw_main_loop *loop);
+
+/** Destroy a loop */
+void pw_main_loop_destroy(struct pw_main_loop *loop);
+
+/** Run a main loop. This blocks until \ref pw_main_loop_quit is called */
+int pw_main_loop_run(struct pw_main_loop *loop);
+
+/** Quit a main loop */
+int pw_main_loop_quit(struct pw_main_loop *loop);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_MAIN_LOOP_H */
diff --git a/src/pipewire/map.h b/src/pipewire/map.h
new file mode 100644
index 0000000..9a46082
--- /dev/null
+++ b/src/pipewire/map.h
@@ -0,0 +1,252 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_MAP_H
+#define PIPEWIRE_MAP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <string.h>
+#include <errno.h>
+
+#include <spa/utils/defs.h>
+#include <pipewire/array.h>
+
+/** \defgroup pw_map Map
+ *
+ * \brief A map that holds pointers to objects indexed by id
+ *
+ * The map is a sparse version of the \ref pw_array "pw_array" that manages the
+ * indices of elements for the caller. Adding items with
+ * pw_map_insert_new() returns the assigned index for that item; if items
+ * are removed the map re-uses indices to keep the array at the minimum
+ * required size.
+ *
+ * \code{.c}
+ * struct pw_map map = PW_MAP_INIT(4);
+ *
+ * idx1 = pw_map_insert_new(&map, ptr1);
+ * idx2 = pw_map_insert_new(&map, ptr2);
+ * // the map is now [ptr1, ptr2], size 2
+ * pw_map_remove(&map, idx1);
+ * // the map is now [<unused>, ptr2], size 2
+ * pw_map_insert_new(&map, ptr3);
+ * // the map is now [ptr3, ptr2], size 2
+ * \endcode
+ */
+
+/**
+ * \addtogroup pw_map
+ * \{
+ */
+
+/** \private
+ * An entry in the map. This is used internally only. Each element in the
+ * backing pw_array is a union pw_map_item. For real items, the data pointer
+ * points to the item. If an element has been removed, pw_map->free_list
+ * is the index of the most recently removed item. That item contains
+ * the index of the next removed item until item->next is SPA_ID_INVALID.
+ *
+ * The free list is prepended only, the last item to be removed will be the
+ * first item to get re-used on the next insert.
+ */
+union pw_map_item {
+ uintptr_t next; /* next free index */
+ void *data; /* data of this item, must be an even address */
+};
+
+/** A map. This struct should be treated as opaque by the caller. */
+struct pw_map {
+ struct pw_array items; /* an array with the map items */
+ uint32_t free_list; /* first free index */
+};
+
+/** \param extend the amount of bytes to grow the map with when needed */
+#define PW_MAP_INIT(extend) ((struct pw_map) { PW_ARRAY_INIT(extend), SPA_ID_INVALID })
+
+/**
+ * Get the number of currently allocated elements in the map.
+ * \note pw_map_get_size() returns the currently allocated number of
+ * elements in the map, not the number of actually set elements.
+ * \return the number of available elements before the map needs to grow
+ */
+#define pw_map_get_size(m) pw_array_get_len(&(m)->items, union pw_map_item)
+#define pw_map_get_item(m,id) pw_array_get_unchecked(&(m)->items,id,union pw_map_item)
+#define pw_map_item_is_free(item) ((item)->next & 0x1)
+#define pw_map_id_is_free(m,id) (pw_map_item_is_free(pw_map_get_item(m,id)))
+/** \return true if the id fits within the current map size */
+#define pw_map_check_id(m,id) ((id) < pw_map_get_size(m))
+/** \return true if there is a valid item at \a id */
+#define pw_map_has_item(m,id) (pw_map_check_id(m,id) && !pw_map_id_is_free(m, id))
+#define pw_map_lookup_unchecked(m,id) pw_map_get_item(m,id)->data
+
+/** Convert an id to a pointer that can be inserted into the map */
+#define PW_MAP_ID_TO_PTR(id) (SPA_UINT32_TO_PTR((id)<<1))
+/** Convert a pointer to an id that can be retrieved from the map */
+#define PW_MAP_PTR_TO_ID(p) (SPA_PTR_TO_UINT32(p)>>1)
+
+/** Initialize a map
+ * \param map the map to initialize
+ * \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_array_init(&map->items, extend * sizeof(union pw_map_item));
+ pw_array_ensure_size(&map->items, size * sizeof(union pw_map_item));
+ map->free_list = SPA_ID_INVALID;
+}
+
+/** 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_array_clear(&map->items);
+}
+
+/** 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_array_reset(&map->items);
+ map->free_list = SPA_ID_INVALID;
+}
+
+/** Insert data in the map. This function causes the map to grow if required.
+ * \param map the map to insert into
+ * \param data the item to add
+ * \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)
+{
+ union pw_map_item *start, *item;
+ uint32_t id;
+
+ if (map->free_list != SPA_ID_INVALID) {
+ start = (union pw_map_item *) map->items.data;
+ item = &start[map->free_list >> 1]; /* lsb always 1, see pw_map_remove */
+ map->free_list = item->next;
+ } else {
+ item = (union pw_map_item *) pw_array_add(&map->items, sizeof(union pw_map_item));
+ if (item == NULL)
+ return SPA_ID_INVALID;
+ start = (union pw_map_item *) map->items.data;
+ }
+ item->data = data;
+ id = (item - start);
+ return id;
+}
+
+/** Replace the data in the map at an index.
+ *
+ * \param map the map to insert into
+ * \param id the index to insert at, must be less or equal to pw_map_get_size()
+ * \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)
+{
+ size_t size = pw_map_get_size(map);
+ union pw_map_item *item;
+
+ if (id > size)
+ return -ENOSPC;
+ else if (id == size) {
+ item = (union pw_map_item *) pw_array_add(&map->items, sizeof(union pw_map_item));
+ if (item == NULL)
+ return -errno;
+ } else {
+ item = pw_map_get_item(map, id);
+ if (pw_map_item_is_free(item))
+ return -EINVAL;
+ }
+ item->data = data;
+ return 0;
+}
+
+/** Remove an item at index. The id may get re-used in the future.
+ *
+ * \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)
+{
+ if (pw_map_id_is_free(map, id))
+ return;
+
+ pw_map_get_item(map, id)->next = map->free_list;
+ map->free_list = (id << 1) | 1;
+}
+
+/** Find an item in the map
+ * \param map the map to use
+ * \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(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);
+ if (!pw_map_item_is_free(item))
+ return item->data;
+ }
+ return NULL;
+}
+
+/** Iterate all map items
+ * \param map the map to iterate
+ * \param func the function to call for each item, the item data and \a data is
+ * passed to the function. When \a func returns a non-zero result,
+ * iteration ends and the result is returned.
+ * \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(struct pw_map *map,
+ int (*func) (void *item_data, void *data), void *data)
+{
+ union pw_map_item *item;
+ int res = 0;
+
+ pw_array_for_each(item, &map->items) {
+ if (!pw_map_item_is_free(item))
+ if ((res = func(item->data, data)) != 0)
+ break;
+ }
+ return res;
+}
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_MAP_H */
diff --git a/src/pipewire/mem.c b/src/pipewire/mem.c
new file mode 100644
index 0000000..ae9e1e4
--- /dev/null
+++ b/src/pipewire/mem.c
@@ -0,0 +1,808 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <errno.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/syscall.h>
+
+#include <spa/utils/list.h>
+#include <spa/buffer/buffer.h>
+
+#include <pipewire/log.h>
+#include <pipewire/map.h>
+#include <pipewire/mem.h>
+
+PW_LOG_TOPIC_EXTERN(log_mem);
+#define PW_LOG_TOPIC_DEFAULT log_mem
+
+#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) && !defined(HAVE_MEMFD_CREATE)
+/*
+ * No glibc wrappers exist for memfd_create(2), so provide our own.
+ *
+ * Also define memfd fcntl sealing macros. While they are already
+ * defined in the kernel header file <linux/fcntl.h>, that file as
+ * a whole conflicts with the original glibc header <fnctl.h>.
+ */
+
+static inline int memfd_create(const char *name, unsigned int flags)
+{
+ return syscall(SYS_memfd_create, name, flags);
+}
+
+#define HAVE_MEMFD_CREATE 1
+#endif
+
+#if defined(__FreeBSD__) || defined(__MidnightBSD__)
+#define MAP_LOCKED 0
+#endif
+
+/* memfd_create(2) flags */
+
+#ifndef MFD_CLOEXEC
+#define MFD_CLOEXEC 0x0001U
+#endif
+
+#ifndef MFD_ALLOW_SEALING
+#define MFD_ALLOW_SEALING 0x0002U
+#endif
+
+/* fcntl() seals-related flags */
+
+#ifndef F_LINUX_SPECIFIC_BASE
+#define F_LINUX_SPECIFIC_BASE 1024
+#endif
+
+#ifndef F_ADD_SEALS
+#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9)
+#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10)
+
+#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */
+#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */
+#define F_SEAL_GROW 0x0004 /* prevent file from growing */
+#define F_SEAL_WRITE 0x0008 /* prevent writes */
+#endif
+
+#define pw_mempool_emit(p,m,v,...) spa_hook_list_call(&p->listener_list, struct pw_mempool_events, m, v, ##__VA_ARGS__)
+#define pw_mempool_emit_destroy(p) pw_mempool_emit(p, destroy, 0)
+#define pw_mempool_emit_added(p,b) pw_mempool_emit(p, added, 0, b)
+#define pw_mempool_emit_removed(p,b) pw_mempool_emit(p, removed, 0, b)
+
+struct mempool {
+ struct pw_mempool this;
+
+ struct spa_hook_list listener_list;
+
+ struct pw_map map; /* map memblock to id */
+ struct spa_list blocks; /* list of memblock */
+ uint32_t pagesize;
+};
+
+struct memblock {
+ struct pw_memblock this;
+ struct spa_list link; /* link in mempool */
+ struct spa_list mappings; /* list of struct mapping */
+ struct spa_list memmaps; /* list of struct memmap */
+};
+
+/* a mapped region of a block */
+struct mapping {
+ struct memblock *block;
+ int ref;
+ uint32_t offset;
+ uint32_t size;
+ unsigned int do_unmap:1;
+ struct spa_list link;
+ void *ptr;
+};
+
+/* a reference to a (part of a) mapped region */
+struct memmap {
+ struct pw_memmap this;
+ struct mapping *mapping;
+ struct spa_list link;
+};
+
+SPA_EXPORT
+struct pw_mempool *pw_mempool_new(struct pw_properties *props)
+{
+ struct mempool *impl;
+ struct pw_mempool *this;
+
+ impl = calloc(1, sizeof(struct mempool));
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+ this->props = props;
+
+ impl->pagesize = sysconf(_SC_PAGESIZE);
+
+ pw_log_debug("%p: new", this);
+
+ spa_hook_list_init(&impl->listener_list);
+ pw_map_init(&impl->map, 64, 64);
+ spa_list_init(&impl->blocks);
+
+ return this;
+}
+
+SPA_EXPORT
+void pw_mempool_clear(struct pw_mempool *pool)
+{
+ struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this);
+ struct memblock *b;
+
+ pw_log_debug("%p: clear", pool);
+
+ spa_list_consume(b, &impl->blocks, link)
+ pw_memblock_free(&b->this);
+ pw_map_reset(&impl->map);
+}
+
+SPA_EXPORT
+void pw_mempool_destroy(struct pw_mempool *pool)
+{
+ struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this);
+
+ pw_log_debug("%p: destroy", pool);
+
+ pw_mempool_emit_destroy(impl);
+
+ pw_mempool_clear(pool);
+
+ spa_hook_list_clean(&impl->listener_list);
+
+ pw_map_clear(&impl->map);
+ pw_properties_free(pool->props);
+ free(impl);
+}
+
+SPA_EXPORT
+void pw_mempool_add_listener(struct pw_mempool *pool,
+ struct spa_hook *listener,
+ const struct pw_mempool_events *events,
+ void *data)
+{
+ struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this);
+ spa_hook_list_append(&impl->listener_list, listener, events, data);
+}
+
+#if 0
+/** Map a memblock
+ * \param mem a memblock
+ * \return 0 on success, < 0 on error
+ */
+SPA_EXPORT
+int pw_memblock_map_old(struct pw_memblock *mem)
+{
+ if (mem->ptr != NULL)
+ return 0;
+
+ if (mem->flags & PW_MEMBLOCK_FLAG_MAP_READWRITE) {
+ int prot = 0;
+
+ if (mem->flags & PW_MEMBLOCK_FLAG_MAP_READ)
+ prot |= PROT_READ;
+ if (mem->flags & PW_MEMBLOCK_FLAG_MAP_WRITE)
+ prot |= PROT_WRITE;
+
+ if (mem->flags & PW_MEMBLOCK_FLAG_MAP_TWICE) {
+ void *ptr, *wrap;
+
+ mem->ptr =
+ mmap(NULL, mem->size << 1, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1,
+ 0);
+ if (mem->ptr == MAP_FAILED)
+ return -errno;
+
+ ptr =
+ mmap(mem->ptr, mem->size, prot, MAP_FIXED | MAP_SHARED, mem->fd,
+ mem->offset);
+ if (ptr != mem->ptr) {
+ munmap(mem->ptr, mem->size << 1);
+ return -ENOMEM;
+ }
+
+ wrap = SPA_PTROFF(mem->ptr, mem->size, void);
+
+ ptr =
+ mmap(wrap, mem->size, prot, MAP_FIXED | MAP_SHARED,
+ mem->fd, mem->offset);
+ if (ptr != wrap) {
+ munmap(mem->ptr, mem->size << 1);
+ return -ENOMEM;
+ }
+ } else {
+ mem->ptr = mmap(NULL, mem->size, prot, MAP_SHARED, mem->fd, 0);
+ if (mem->ptr == MAP_FAILED)
+ return -errno;
+ }
+ } else {
+ mem->ptr = NULL;
+ }
+
+ pw_log_debug("%p: map to %p", mem, mem->ptr);
+
+ return 0;
+}
+#endif
+
+static struct mapping * memblock_find_mapping(struct memblock *b,
+ uint32_t flags, uint32_t offset, uint32_t size)
+{
+ struct mapping *m;
+ struct pw_mempool *pool = b->this.pool;
+
+ spa_list_for_each(m, &b->mappings, link) {
+ pw_log_debug("%p: check %p offset:(%u <= %u) end:(%u >= %u)",
+ pool, m, m->offset, offset, m->offset + m->size,
+ offset + size);
+ if (m->offset <= offset && (m->offset + m->size) >= (offset + size)) {
+ pw_log_debug("%p: found %p id:%u fd:%d offs:%u size:%u ref:%d",
+ pool, &b->this, b->this.id, b->this.fd,
+ offset, size, b->this.ref);
+ return m;
+ }
+ }
+ return NULL;
+}
+
+static struct mapping * memblock_map(struct memblock *b,
+ enum pw_memmap_flags flags, uint32_t offset, uint32_t size)
+{
+ struct mempool *p = SPA_CONTAINER_OF(b->this.pool, struct mempool, this);
+ struct mapping *m;
+ void *ptr;
+ int prot = 0, fl = 0;
+
+ if (flags & PW_MEMMAP_FLAG_READ)
+ prot |= PROT_READ;
+ if (flags & PW_MEMMAP_FLAG_WRITE)
+ prot |= PROT_WRITE;
+
+ if (flags & PW_MEMMAP_FLAG_PRIVATE)
+ fl |= MAP_PRIVATE;
+ else
+ fl |= MAP_SHARED;
+
+ if (flags & PW_MEMMAP_FLAG_LOCKED)
+ fl |= MAP_LOCKED;
+
+ if (flags & PW_MEMMAP_FLAG_TWICE) {
+ pw_log_error("%p: implement me PW_MEMMAP_FLAG_TWICE", p);
+ errno = ENOTSUP;
+ return NULL;
+ }
+
+
+ ptr = mmap(NULL, size, prot, fl, b->this.fd, offset);
+ if (ptr == MAP_FAILED) {
+ pw_log_error("%p: Failed to mmap memory fd:%d offset:%u size:%u: %m",
+ p, b->this.fd, offset, size);
+ return NULL;
+ }
+
+ m = calloc(1, sizeof(struct mapping));
+ if (m == NULL) {
+ munmap(ptr, size);
+ return NULL;
+ }
+ m->ptr = ptr;
+ m->do_unmap = true;
+ m->block = b;
+ m->offset = offset;
+ m->size = size;
+ b->this.ref++;
+ spa_list_append(&b->mappings, &m->link);
+
+ pw_log_debug("%p: block:%p fd:%d map:%p ptr:%p (%u %u) block-ref:%d", p, &b->this,
+ b->this.fd, m, m->ptr, offset, size, b->this.ref);
+
+ return m;
+}
+
+static void mapping_free(struct mapping *m)
+{
+ struct memblock *b = m->block;
+ struct mempool *p = SPA_CONTAINER_OF(b->this.pool, struct mempool, this);
+
+ pw_log_debug("%p: mapping:%p block:%p fd:%d ptr:%p size:%u block-ref:%d",
+ p, m, b, b->this.fd, m->ptr, m->size, b->this.ref);
+
+ if (m->do_unmap)
+ munmap(m->ptr, m->size);
+ spa_list_remove(&m->link);
+ free(m);
+}
+
+static void mapping_unmap(struct mapping *m)
+{
+ struct memblock *b = m->block;
+ struct mempool *p = SPA_CONTAINER_OF(b->this.pool, struct mempool, this);
+ pw_log_debug("%p: mapping:%p block:%p fd:%d ptr:%p size:%u block-ref:%d",
+ p, m, b, b->this.fd, m->ptr, m->size, b->this.ref);
+ mapping_free(m);
+ pw_memblock_unref(&b->this);
+}
+
+SPA_EXPORT
+struct pw_memmap * pw_memblock_map(struct pw_memblock *block,
+ enum pw_memmap_flags flags, uint32_t offset, uint32_t size, uint32_t tag[5])
+{
+ struct memblock *b = SPA_CONTAINER_OF(block, struct memblock, this);
+ struct mempool *p = SPA_CONTAINER_OF(block->pool, struct mempool, this);
+ struct mapping *m;
+ struct memmap *mm;
+ struct pw_map_range range;
+
+ pw_map_range_init(&range, offset, size, p->pagesize);
+
+ m = memblock_find_mapping(b, flags, offset, size);
+ if (m == NULL)
+ m = memblock_map(b, flags, range.offset, range.size);
+ if (m == NULL)
+ return NULL;
+
+ mm = calloc(1, sizeof(struct memmap));
+ if (mm == NULL) {
+ if (m->ref == 0)
+ mapping_unmap(m);
+ return NULL;
+ }
+
+ m->ref++;
+ mm->mapping = m;
+ mm->this.block = block;
+ mm->this.flags = flags;
+ mm->this.offset = offset;
+ mm->this.size = size;
+ mm->this.ptr = SPA_PTROFF(m->ptr, range.start, void);
+
+ pw_log_debug("%p: map:%p block:%p fd:%d ptr:%p (%u %u) mapping:%p ref:%d", p,
+ &mm->this, b, b->this.fd, mm->this.ptr, offset, size, m, m->ref);
+
+ if (tag) {
+ memcpy(mm->this.tag, tag, sizeof(mm->this.tag));
+ pw_log_debug("%p: tag:%u:%u:%u:%u:%u", p,
+ tag[0], tag[1], tag[2], tag[3], tag[4]);
+ }
+
+ spa_list_append(&b->memmaps, &mm->link);
+
+ return &mm->this;
+}
+
+SPA_EXPORT
+struct pw_memmap * pw_mempool_map_id(struct pw_mempool *pool,
+ uint32_t id, enum pw_memmap_flags flags, uint32_t offset, uint32_t size, uint32_t tag[5])
+{
+ struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this);
+ struct memblock *b;
+
+ b = pw_map_lookup(&impl->map, id);
+ if (b == NULL) {
+ errno = ENOENT;
+ return NULL;
+ }
+ return pw_memblock_map(&b->this, flags, offset, size, tag);
+}
+
+SPA_EXPORT
+int pw_memmap_free(struct pw_memmap *map)
+{
+ struct memmap *mm;
+ struct mapping *m;
+ struct memblock *b;
+ struct mempool *p;
+
+ if (map == NULL)
+ return 0;
+
+ mm = SPA_CONTAINER_OF(map, struct memmap, this);
+ m = mm->mapping;
+ b = m->block;
+ p = SPA_CONTAINER_OF(b->this.pool, struct mempool, this);
+
+ pw_log_debug("%p: map:%p block:%p fd:%d ptr:%p mapping:%p ref:%d", p,
+ &mm->this, b, b->this.fd, mm->this.ptr, m, m->ref);
+
+ spa_list_remove(&mm->link);
+
+ if (--m->ref == 0)
+ mapping_unmap(m);
+
+ free(mm);
+
+ return 0;
+}
+
+static inline enum pw_memmap_flags block_flags_to_mem(enum pw_memblock_flags flags)
+{
+ enum pw_memmap_flags fl = 0;
+
+ if (flags & PW_MEMBLOCK_FLAG_READABLE)
+ fl |= PW_MEMMAP_FLAG_READ;
+ if (flags & PW_MEMBLOCK_FLAG_WRITABLE)
+ fl |= PW_MEMMAP_FLAG_WRITE;
+
+ return fl;
+}
+
+/** Create a new memblock
+ * \param pool the pool to use
+ * \param flags memblock flags
+ * \param type the requested memory type one of enum spa_data_type
+ * \param size size to allocate
+ * \return a memblock structure or NULL with errno on error
+ */
+SPA_EXPORT
+struct pw_memblock * pw_mempool_alloc(struct pw_mempool *pool, enum pw_memblock_flags flags,
+ uint32_t type, size_t size)
+{
+ struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this);
+ struct memblock *b;
+ int res;
+
+ b = calloc(1, sizeof(struct memblock));
+ if (b == NULL)
+ return NULL;
+
+ b->this.ref = 1;
+ b->this.pool = pool;
+ b->this.flags = flags;
+ b->this.type = type;
+ b->this.size = size;
+ spa_list_init(&b->mappings);
+ spa_list_init(&b->memmaps);
+
+#ifdef HAVE_MEMFD_CREATE
+ char name[128];
+ snprintf(name, sizeof(name),
+ "pipewire-memfd:flags=0x%08x,type=%" PRIu32 ",size=%zu",
+ (unsigned int) flags, type, size);
+
+ b->this.fd = memfd_create(name, MFD_CLOEXEC | MFD_ALLOW_SEALING);
+ if (b->this.fd == -1) {
+ res = -errno;
+ pw_log_error("%p: Failed to create memfd: %m", pool);
+ goto error_free;
+ }
+#elif defined(__FreeBSD__) || defined(__MidnightBSD__)
+ b->this.fd = shm_open(SHM_ANON, O_CREAT | O_RDWR | O_CLOEXEC, 0);
+ if (b->this.fd == -1) {
+ res = -errno;
+ pw_log_error("%p: Failed to create SHM_ANON fd: %m", pool);
+ goto error_free;
+ }
+#else
+ char filename[128];
+ snprintf(filename, sizeof(filename),
+ "/dev/shm/pipewire-tmpfile:flags=0x%08x,type=%" PRIu32 ",size=%zu:XXXXXX",
+ (unsigned int) flags, type, size);
+
+ b->this.fd = mkostemp(filename, O_CLOEXEC);
+ if (b->this.fd == -1) {
+ res = -errno;
+ pw_log_error("%p: Failed to create temporary file: %m", pool);
+ goto error_free;
+ }
+ unlink(filename);
+#endif
+ pw_log_debug("%p: new fd:%d", pool, b->this.fd);
+
+ if (ftruncate(b->this.fd, size) < 0) {
+ res = -errno;
+ pw_log_warn("%p: Failed to truncate temporary file: %m", pool);
+ goto error_close;
+ }
+#ifdef HAVE_MEMFD_CREATE
+ if (flags & PW_MEMBLOCK_FLAG_SEAL) {
+ unsigned int seals = F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL;
+ if (fcntl(b->this.fd, F_ADD_SEALS, seals) == -1) {
+ pw_log_warn("%p: Failed to add seals: %m", pool);
+ }
+ }
+#endif
+ if (flags & PW_MEMBLOCK_FLAG_MAP && size > 0) {
+ b->this.map = pw_memblock_map(&b->this,
+ block_flags_to_mem(flags), 0, size, NULL);
+ if (b->this.map == NULL) {
+ res = -errno;
+ pw_log_warn("%p: Failed to map: %m", pool);
+ goto error_close;
+ }
+ b->this.ref--;
+ }
+
+ b->this.id = pw_map_insert_new(&impl->map, b);
+ spa_list_append(&impl->blocks, &b->link);
+ pw_log_debug("%p: block:%p id:%d type:%u size:%zu", pool,
+ &b->this, b->this.id, type, size);
+
+ if (!SPA_FLAG_IS_SET(flags, PW_MEMBLOCK_FLAG_DONT_NOTIFY))
+ pw_mempool_emit_added(impl, &b->this);
+
+ return &b->this;
+
+error_close:
+ pw_log_debug("%p: close fd:%d", pool, b->this.fd);
+ close(b->this.fd);
+error_free:
+ free(b);
+ errno = -res;
+ return NULL;
+}
+
+static struct memblock * mempool_find_fd(struct pw_mempool *pool, int fd)
+{
+ struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this);
+ struct memblock *b;
+
+ spa_list_for_each(b, &impl->blocks, link) {
+ if (fd == b->this.fd) {
+ pw_log_debug("%p: found %p id:%u fd:%d ref:%d",
+ pool, &b->this, b->this.id, fd, b->this.ref);
+ return b;
+ }
+ }
+ return NULL;
+}
+
+SPA_EXPORT
+struct pw_memblock * pw_mempool_import(struct pw_mempool *pool,
+ enum pw_memblock_flags flags, uint32_t type, int fd)
+{
+ struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this);
+ struct memblock *b;
+
+ b = mempool_find_fd(pool, fd);
+ if (b != NULL) {
+ b->this.ref++;
+ return &b->this;
+ }
+
+ b = calloc(1, sizeof(struct memblock));
+ if (b == NULL)
+ return NULL;
+
+ spa_list_init(&b->memmaps);
+ spa_list_init(&b->mappings);
+
+ b->this.ref = 1;
+ b->this.pool = pool;
+ b->this.type = type;
+ b->this.fd = fd;
+ b->this.flags = flags;
+ b->this.id = pw_map_insert_new(&impl->map, b);
+ spa_list_append(&impl->blocks, &b->link);
+
+ pw_log_debug("%p: block:%p id:%u flags:%08x type:%u fd:%d",
+ pool, b, b->this.id, flags, type, fd);
+
+ if (!SPA_FLAG_IS_SET(flags, PW_MEMBLOCK_FLAG_DONT_NOTIFY))
+ pw_mempool_emit_added(impl, &b->this);
+
+ return &b->this;
+}
+
+SPA_EXPORT
+struct pw_memblock * pw_mempool_import_block(struct pw_mempool *pool,
+ struct pw_memblock *mem)
+{
+ pw_log_debug("%p: import block:%p type:%d fd:%d", pool,
+ mem, mem->type, mem->fd);
+ return pw_mempool_import(pool,
+ mem->flags | PW_MEMBLOCK_FLAG_DONT_CLOSE,
+ mem->type, mem->fd);
+}
+
+SPA_EXPORT
+struct pw_memmap * pw_mempool_import_map(struct pw_mempool *pool,
+ struct pw_mempool *other, void *data, uint32_t size, uint32_t tag[5])
+{
+ struct pw_memblock *old, *block;
+ struct memblock *b;
+ struct pw_memmap *map;
+ uint32_t offset;
+
+ old = pw_mempool_find_ptr(other, data);
+ if (old == NULL || old->map == NULL) {
+ errno = EFAULT;
+ return NULL;
+ }
+
+ block = pw_mempool_import_block(pool, old);
+ if (block == NULL)
+ return NULL;
+
+ if (block->ref == 1) {
+ struct mapping *m;
+
+ b = SPA_CONTAINER_OF(block, struct memblock, this);
+
+ m = calloc(1, sizeof(struct mapping));
+ if (m == NULL) {
+ pw_memblock_unref(block);
+ return NULL;
+ }
+ m->ptr = old->map->ptr;
+ m->block = b;
+ m->offset = old->map->offset;
+ m->size = old->map->size;
+ spa_list_append(&b->mappings, &m->link);
+ pw_log_debug("%p: mapping:%p block:%p offset:%u size:%u ref:%u",
+ pool, m, block, m->offset, m->size, block->ref);
+ } else {
+ block->ref--;
+ }
+
+ offset = SPA_PTRDIFF(data, old->map->ptr);
+
+ map = pw_memblock_map(block,
+ block_flags_to_mem(block->flags), offset, size, tag);
+ if (map == NULL)
+ return NULL;
+
+ pw_log_debug("%p: from pool:%p block:%p id:%u data:%p size:%u ref:%d",
+ pool, other, block, block->id, data, size, block->ref);
+
+ return map;
+}
+
+SPA_EXPORT
+int pw_mempool_remove_id(struct pw_mempool *pool, uint32_t id)
+{
+ struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this);
+ struct memblock *b;
+
+ b = pw_map_lookup(&impl->map, id);
+ if (b == NULL)
+ return -ENOENT;
+
+ pw_log_debug("%p: block:%p id:%u fd:%d ref:%d",
+ pool, b, id, b->this.fd, b->this.ref);
+
+ b->this.id = SPA_ID_INVALID;
+ pw_map_remove(&impl->map, id);
+ pw_memblock_unref(&b->this);
+
+ return 0;
+}
+
+/** Free a memblock
+ * \param block a memblock
+ */
+SPA_EXPORT
+void pw_memblock_free(struct pw_memblock *block)
+{
+ struct memblock *b = SPA_CONTAINER_OF(block, struct memblock, this);
+ struct pw_mempool *pool = block->pool;
+ struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this);
+ struct memmap *mm;
+ struct mapping *m;
+
+ spa_return_if_fail(block != NULL);
+
+ pw_log_debug("%p: block:%p id:%d fd:%d ref:%d",
+ pool, block, block->id, block->fd, block->ref);
+
+ block->ref++;
+ if (block->map)
+ block->ref++;
+
+ if (block->id != SPA_ID_INVALID)
+ pw_map_remove(&impl->map, block->id);
+ spa_list_remove(&b->link);
+
+ if (!SPA_FLAG_IS_SET(block->flags, PW_MEMBLOCK_FLAG_DONT_NOTIFY))
+ pw_mempool_emit_removed(impl, block);
+
+ spa_list_consume(mm, &b->memmaps, link)
+ pw_memmap_free(&mm->this);
+
+ spa_list_consume(m, &b->mappings, link) {
+ pw_log_warn("%p: stray mapping:%p", pool, m);
+ mapping_free(m);
+ }
+
+ if (block->fd != -1 && !(block->flags & PW_MEMBLOCK_FLAG_DONT_CLOSE)) {
+ pw_log_debug("%p: close fd:%d", pool, block->fd);
+ close(block->fd);
+ }
+ free(b);
+}
+
+SPA_EXPORT
+struct pw_memblock * pw_mempool_find_ptr(struct pw_mempool *pool, const void *ptr)
+{
+ struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this);
+ struct memblock *b;
+ struct mapping *m;
+
+ spa_list_for_each(b, &impl->blocks, link) {
+ spa_list_for_each(m, &b->mappings, link) {
+ if (ptr >= m->ptr && ptr < SPA_PTROFF(m->ptr, m->size, void)) {
+ pw_log_debug("%p: block:%p id:%u for %p", pool,
+ b, b->this.id, ptr);
+ return &b->this;
+ }
+ }
+ }
+ return NULL;
+}
+
+SPA_EXPORT
+struct pw_memblock * pw_mempool_find_id(struct pw_mempool *pool, uint32_t id)
+{
+ struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this);
+ struct memblock *b;
+
+ b = pw_map_lookup(&impl->map, id);
+ pw_log_debug("%p: block:%p for %u", pool, b, id);
+ if (b == NULL)
+ return NULL;
+
+ return &b->this;
+}
+
+SPA_EXPORT
+struct pw_memblock * pw_mempool_find_fd(struct pw_mempool *pool, int fd)
+{
+ struct memblock *b;
+
+ b = mempool_find_fd(pool, fd);
+ if (b == NULL)
+ return NULL;
+
+ return &b->this;
+}
+
+SPA_EXPORT
+struct pw_memmap * pw_mempool_find_tag(struct pw_mempool *pool, uint32_t tag[5], size_t size)
+{
+ struct mempool *impl = SPA_CONTAINER_OF(pool, struct mempool, this);
+ struct memblock *b;
+ struct memmap *mm;
+
+ pw_log_debug("%p: find tag %u:%u:%u:%u:%u size:%zu", pool,
+ tag[0], tag[1], tag[2], tag[3], tag[4], size);
+
+ spa_list_for_each(b, &impl->blocks, link) {
+ spa_list_for_each(mm, &b->memmaps, link) {
+ if (memcmp(tag, mm->this.tag, size) == 0) {
+ pw_log_debug("%p: found %p", pool, mm);
+ return &mm->this;
+ }
+ }
+ }
+ return NULL;
+}
diff --git a/src/pipewire/mem.h b/src/pipewire/mem.h
new file mode 100644
index 0000000..4edfab6
--- /dev/null
+++ b/src/pipewire/mem.h
@@ -0,0 +1,212 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_MEM_H
+#define PIPEWIRE_MEM_H
+
+#include <pipewire/properties.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \defgroup pw_memblock Memory Blocks
+ * Memory allocation and pools.
+ */
+
+/**
+ * \addtogroup pw_memblock
+ * \{
+ */
+
+/** Flags passed to \ref pw_mempool_alloc() */
+enum pw_memblock_flags {
+ PW_MEMBLOCK_FLAG_NONE = 0,
+ PW_MEMBLOCK_FLAG_READABLE = (1 << 0), /**< memory is readable */
+ PW_MEMBLOCK_FLAG_WRITABLE = (1 << 1), /**< memory is writable */
+ PW_MEMBLOCK_FLAG_SEAL = (1 << 2), /**< seal the fd */
+ PW_MEMBLOCK_FLAG_MAP = (1 << 3), /**< mmap the fd */
+ PW_MEMBLOCK_FLAG_DONT_CLOSE = (1 << 4), /**< don't close fd */
+ PW_MEMBLOCK_FLAG_DONT_NOTIFY = (1 << 5), /**< don't notify events */
+
+ PW_MEMBLOCK_FLAG_READWRITE = PW_MEMBLOCK_FLAG_READABLE | PW_MEMBLOCK_FLAG_WRITABLE,
+};
+
+enum pw_memmap_flags {
+ PW_MEMMAP_FLAG_NONE = 0,
+ PW_MEMMAP_FLAG_READ = (1 << 0), /**< map in read mode */
+ PW_MEMMAP_FLAG_WRITE = (1 << 1), /**< map in write mode */
+ PW_MEMMAP_FLAG_TWICE = (1 << 2), /**< map the same area twice after each other,
+ * creating a circular ringbuffer */
+ PW_MEMMAP_FLAG_PRIVATE = (1 << 3), /**< writes will be private */
+ PW_MEMMAP_FLAG_LOCKED = (1 << 4), /**< lock the memory into RAM */
+ PW_MEMMAP_FLAG_READWRITE = PW_MEMMAP_FLAG_READ | PW_MEMMAP_FLAG_WRITE,
+};
+
+struct pw_memchunk;
+
+/**
+ *
+ * A memory pool is a collection of pw_memblocks */
+struct pw_mempool {
+ struct pw_properties *props;
+};
+
+/**
+ * Memory block structure */
+struct pw_memblock {
+ struct pw_mempool *pool; /**< owner pool */
+ uint32_t id; /**< unique id */
+ int ref; /**< refcount */
+ uint32_t flags; /**< flags for the memory block on of enum pw_memblock_flags */
+ uint32_t type; /**< type of the fd, one of enum spa_data_type */
+ int fd; /**< fd */
+ uint32_t size; /**< size of memory */
+ struct pw_memmap *map; /**< optional map when PW_MEMBLOCK_FLAG_MAP was given */
+};
+
+/** a mapped region of a pw_memblock */
+struct pw_memmap {
+ struct pw_memblock *block; /**< owner memblock */
+ void *ptr; /**< mapped pointer */
+ uint32_t flags; /**< flags for the mapping on of enum pw_memmap_flags */
+ uint32_t offset; /**< offset in memblock */
+ uint32_t size; /**< size in memblock */
+ uint32_t tag[5]; /**< user tag */
+};
+
+struct pw_mempool_events {
+#define PW_VERSION_MEMPOOL_EVENTS 0
+ uint32_t version;
+
+ /** the pool is destroyed */
+ void (*destroy) (void *data);
+
+ /** a new memory block is added to the pool */
+ void (*added) (void *data, struct pw_memblock *block);
+
+ /** a memory block is removed from the pool */
+ void (*removed) (void *data, struct pw_memblock *block);
+};
+
+/** Create a new memory pool */
+struct pw_mempool *pw_mempool_new(struct pw_properties *props);
+
+/** Listen for events */
+void pw_mempool_add_listener(struct pw_mempool *pool,
+ struct spa_hook *listener,
+ const struct pw_mempool_events *events,
+ void *data);
+
+/** Clear a pool */
+void pw_mempool_clear(struct pw_mempool *pool);
+
+/** Clear and destroy a pool */
+void pw_mempool_destroy(struct pw_mempool *pool);
+
+
+/** Allocate a memory block from the pool */
+struct pw_memblock * pw_mempool_alloc(struct pw_mempool *pool,
+ enum pw_memblock_flags flags, uint32_t type, size_t size);
+
+/** Import a block from another pool */
+struct pw_memblock * pw_mempool_import_block(struct pw_mempool *pool,
+ struct pw_memblock *mem);
+
+/** Import an fd into the pool */
+struct pw_memblock * pw_mempool_import(struct pw_mempool *pool,
+ enum pw_memblock_flags flags, uint32_t type, int fd);
+
+/** Free a memblock regardless of the refcount and destroy all mappings */
+void pw_memblock_free(struct pw_memblock *mem);
+
+/** Unref a memblock */
+static inline void pw_memblock_unref(struct pw_memblock *mem)
+{
+ if (--mem->ref == 0)
+ pw_memblock_free(mem);
+}
+
+/** Remove a memblock for given \a id */
+int pw_mempool_remove_id(struct pw_mempool *pool, uint32_t id);
+
+/** Find memblock for given \a ptr */
+struct pw_memblock * pw_mempool_find_ptr(struct pw_mempool *pool, const void *ptr);
+
+/** Find memblock for given \a id */
+struct pw_memblock * pw_mempool_find_id(struct pw_mempool *pool, uint32_t id);
+
+/** Find memblock for given \a fd */
+struct pw_memblock * pw_mempool_find_fd(struct pw_mempool *pool, int fd);
+
+
+/** Map a region of a memory block */
+struct pw_memmap * pw_memblock_map(struct pw_memblock *block,
+ enum pw_memmap_flags flags, uint32_t offset, uint32_t size,
+ uint32_t tag[5]);
+
+/** Map a region of a memory block with \a id */
+struct pw_memmap * pw_mempool_map_id(struct pw_mempool *pool, uint32_t id,
+ enum pw_memmap_flags flags, uint32_t offset, uint32_t size,
+ uint32_t tag[5]);
+
+struct pw_memmap * pw_mempool_import_map(struct pw_mempool *pool,
+ struct pw_mempool *other, void *data, uint32_t size, uint32_t tag[5]);
+
+/** find a map with the given tag */
+struct pw_memmap * pw_mempool_find_tag(struct pw_mempool *pool, uint32_t tag[5], size_t size);
+
+/** Unmap a region */
+int pw_memmap_free(struct pw_memmap *map);
+
+
+/** parameters to map a memory range */
+struct pw_map_range {
+ uint32_t start; /** offset in first page with start of data */
+ uint32_t offset; /** page aligned offset to map */
+ uint32_t size; /** size to map */
+};
+
+#define PW_MAP_RANGE_INIT (struct pw_map_range){ 0, }
+
+/** 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,
+ uint32_t offset, uint32_t size,
+ uint32_t page_size)
+{
+ range->offset = SPA_ROUND_DOWN_N(offset, page_size);
+ range->start = offset - range->offset;
+ range->size = SPA_ROUND_UP_N(range->start + size, page_size);
+}
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_MEM_H */
diff --git a/src/pipewire/meson.build b/src/pipewire/meson.build
new file mode 100644
index 0000000..b19631a
--- /dev/null
+++ b/src/pipewire/meson.build
@@ -0,0 +1,138 @@
+pipewire_headers = [
+ 'array.h',
+ 'buffers.h',
+ 'impl-core.h',
+ 'impl-client.h',
+ 'client.h',
+ 'conf.h',
+ 'context.h',
+ 'control.h',
+ 'core.h',
+ 'device.h',
+ 'impl-device.h',
+ 'data-loop.h',
+ 'factory.h',
+ 'impl-factory.h',
+ 'filter.h',
+ 'global.h',
+ 'keys.h',
+ 'impl.h',
+ 'i18n.h',
+ 'impl-link.h',
+ 'link.h',
+ 'log.h',
+ 'loop.h',
+ 'main-loop.h',
+ 'map.h',
+ 'mem.h',
+ 'impl-metadata.h',
+ 'impl-module.h',
+ 'module.h',
+ 'impl-node.h',
+ 'node.h',
+ 'permission.h',
+ 'pipewire.h',
+ 'impl-port.h',
+ 'port.h',
+ 'properties.h',
+ 'protocol.h',
+ 'proxy.h',
+ 'resource.h',
+ 'stream.h',
+ 'thread.h',
+ 'thread-loop.h',
+ 'type.h',
+ 'utils.h',
+ 'work-queue.h',
+]
+
+pipewire_sources = [
+ 'buffers.c',
+ 'impl-core.c',
+ 'impl-client.c',
+ 'conf.c',
+ 'context.c',
+ 'control.c',
+ 'core.c',
+ 'data-loop.c',
+ 'impl-device.c',
+ 'filter.c',
+ 'global.c',
+ 'introspect.c',
+ 'impl-link.c',
+ 'log.c',
+ 'loop.c',
+ 'main-loop.c',
+ 'mem.c',
+ 'impl-module.c',
+ 'impl-node.c',
+ 'impl-factory.c',
+ 'impl-metadata.c',
+ 'pipewire.c',
+ 'impl-port.c',
+ 'properties.c',
+ 'protocol.c',
+ 'proxy.c',
+ 'resource.c',
+ 'settings.c',
+ 'stream.c',
+ 'thread.c',
+ 'thread-loop.c',
+ 'utils.c',
+ 'work-queue.c',
+]
+
+configure_file(input : 'version.h.in',
+ output : 'version.h',
+ install_dir : get_option('includedir') / pipewire_headers_dir,
+ configuration : versiondata)
+
+
+install_headers(pipewire_headers, subdir : pipewire_headers_dir)
+
+libpipewire_c_args = [
+ '-DOLD_MEDIA_SESSION_WORKAROUND=1'
+]
+
+if host_machine.system() != 'freebsd' and host_machine.system() != 'midnightbsd'
+ libpipewire_c_args += [
+ '-D_POSIX_C_SOURCE'
+ ]
+endif
+
+libpipewire = shared_library(pipewire_name, pipewire_sources,
+ version : libversion,
+ soversion : soversion,
+ c_args : libpipewire_c_args,
+ include_directories : [pipewire_inc, configinc, includes_inc],
+ install : true,
+ dependencies : [spa_dep, dl_lib, mathlib, pthread_lib, libintl_dep, atomic_dep, ],
+)
+
+pipewire_dep = declare_dependency(link_with : libpipewire,
+ include_directories : [pipewire_inc, configinc],
+ dependencies : [pthread_lib, atomic_dep, spa_dep],
+ variables : {
+ 'moduledir' : meson.current_build_dir() / '..' / 'modules',
+ 'confdatadir' : meson.current_build_dir() / '..' / 'daemon',
+ }
+)
+
+pkgconfig.generate(libpipewire,
+ filebase : 'lib@0@'.format(pipewire_name),
+ requires : ['lib@0@'.format(spa_name)],
+ name : 'libpipewire',
+ subdirs : pipewire_name,
+ description : 'PipeWire Interface',
+ version : pipewire_version,
+ extra_cflags : '-D_REENTRANT',
+ variables : ['moduledir=${libdir}/@0@'.format(pipewire_name)],
+ uninstalled_variables : [
+ 'moduledir=${prefix}/src/modules',
+ 'confdatadir=${prefix}/src/daemon',
+ ],
+)
+
+meson.override_dependency('lib@0@'.format(pipewire_name), pipewire_dep)
+
+subdir('extensions')
diff --git a/src/pipewire/module.h b/src/pipewire/module.h
new file mode 100644
index 0000000..aba6de8
--- /dev/null
+++ b/src/pipewire/module.h
@@ -0,0 +1,121 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_MODULE_H
+#define PIPEWIRE_MODULE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+
+#include <pipewire/proxy.h>
+
+/** \defgroup pw_module Module
+ * Module interface
+ */
+
+/**
+ * \addtogroup pw_module
+ * \{
+ */
+#define PW_TYPE_INTERFACE_Module PW_TYPE_INFO_INTERFACE_BASE "Module"
+
+#define PW_VERSION_MODULE 3
+struct pw_module;
+
+/** The module information. Extra information can be added in later versions */
+struct pw_module_info {
+ uint32_t id; /**< id of the global */
+ const char *name; /**< name of the module */
+ const char *filename; /**< filename of the module */
+ const char *args; /**< arguments passed to the module */
+#define PW_MODULE_CHANGE_MASK_PROPS (1 << 0)
+#define PW_MODULE_CHANGE_MASK_ALL ((1 << 1)-1)
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ struct spa_dict *props; /**< extra properties */
+};
+
+/** Update and existing \ref pw_module_info with \a update with reset */
+struct pw_module_info *
+pw_module_info_update(struct pw_module_info *info,
+ const struct pw_module_info *update);
+/** Merge and existing \ref pw_module_info with \a update */
+struct pw_module_info *
+pw_module_info_merge(struct pw_module_info *info,
+ const struct pw_module_info *update, bool reset);
+/** Free a \ref pw_module_info */
+void pw_module_info_free(struct pw_module_info *info);
+
+#define PW_MODULE_EVENT_INFO 0
+#define PW_MODULE_EVENT_NUM 1
+
+/** Module events */
+struct pw_module_events {
+#define PW_VERSION_MODULE_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify module info
+ *
+ * \param info info about the module
+ */
+ void (*info) (void *data, const struct pw_module_info *info);
+};
+
+#define PW_MODULE_METHOD_ADD_LISTENER 0
+#define PW_MODULE_METHOD_NUM 1
+
+/** Module methods */
+struct pw_module_methods {
+#define PW_VERSION_MODULE_METHODS 0
+ uint32_t version;
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_module_events *events,
+ 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__)
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_MODULE_H */
diff --git a/src/pipewire/node.h b/src/pipewire/node.h
new file mode 100644
index 0000000..e5b8995
--- /dev/null
+++ b/src/pipewire/node.h
@@ -0,0 +1,216 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_NODE_H
+#define PIPEWIRE_NODE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+#include <errno.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+#include <spa/node/command.h>
+#include <spa/param/param.h>
+
+#include <pipewire/proxy.h>
+
+/** \defgroup pw_node Node
+ * Node interface
+ */
+
+/**
+ * \addtogroup pw_node
+ * \{
+ */
+#define PW_TYPE_INTERFACE_Node PW_TYPE_INFO_INTERFACE_BASE "Node"
+
+#define PW_VERSION_NODE 3
+struct pw_node;
+
+/** \enum pw_node_state The different node states */
+enum pw_node_state {
+ PW_NODE_STATE_ERROR = -1, /**< error state */
+ PW_NODE_STATE_CREATING = 0, /**< the node is being created */
+ PW_NODE_STATE_SUSPENDED = 1, /**< the node is suspended, the device might
+ * be closed */
+ PW_NODE_STATE_IDLE = 2, /**< the node is running but there is no active
+ * port */
+ PW_NODE_STATE_RUNNING = 3, /**< the node is running */
+};
+
+/** Convert a \ref pw_node_state to a readable string */
+const char * pw_node_state_as_string(enum pw_node_state state);
+
+/** The node information. Extra information can be added in later versions */
+struct pw_node_info {
+ uint32_t id; /**< id of the global */
+ uint32_t max_input_ports; /**< maximum number of inputs */
+ uint32_t max_output_ports; /**< maximum number of outputs */
+#define PW_NODE_CHANGE_MASK_INPUT_PORTS (1 << 0)
+#define PW_NODE_CHANGE_MASK_OUTPUT_PORTS (1 << 1)
+#define PW_NODE_CHANGE_MASK_STATE (1 << 2)
+#define PW_NODE_CHANGE_MASK_PROPS (1 << 3)
+#define PW_NODE_CHANGE_MASK_PARAMS (1 << 4)
+#define PW_NODE_CHANGE_MASK_ALL ((1 << 5)-1)
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ uint32_t n_input_ports; /**< number of inputs */
+ uint32_t n_output_ports; /**< number of outputs */
+ enum pw_node_state state; /**< the current state of the node */
+ const char *error; /**< an error reason if \a state is error */
+ struct spa_dict *props; /**< the properties of the node */
+ struct spa_param_info *params; /**< parameters */
+ uint32_t n_params; /**< number of items in \a params */
+};
+
+struct pw_node_info *
+pw_node_info_update(struct pw_node_info *info,
+ const struct pw_node_info *update);
+
+struct pw_node_info *
+pw_node_info_merge(struct pw_node_info *info,
+ const struct pw_node_info *update, bool reset);
+
+void
+pw_node_info_free(struct pw_node_info *info);
+
+#define PW_NODE_EVENT_INFO 0
+#define PW_NODE_EVENT_PARAM 1
+#define PW_NODE_EVENT_NUM 2
+
+/** Node events */
+struct pw_node_events {
+#define PW_VERSION_NODE_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify node info
+ *
+ * \param info info about the node
+ */
+ void (*info) (void *data, const struct pw_node_info *info);
+ /**
+ * Notify a node param
+ *
+ * Event emitted as a result of the enum_params method.
+ *
+ * \param seq the sequence number of the request
+ * \param id the param id
+ * \param index the param index
+ * \param next the param index of the next param
+ * \param param the parameter
+ */
+ void (*param) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param);
+};
+
+#define PW_NODE_METHOD_ADD_LISTENER 0
+#define PW_NODE_METHOD_SUBSCRIBE_PARAMS 1
+#define PW_NODE_METHOD_ENUM_PARAMS 2
+#define PW_NODE_METHOD_SET_PARAM 3
+#define PW_NODE_METHOD_SEND_COMMAND 4
+#define PW_NODE_METHOD_NUM 5
+
+/** Node methods */
+struct pw_node_methods {
+#define PW_VERSION_NODE_METHODS 0
+ uint32_t version;
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_node_events *events,
+ void *data);
+ /**
+ * Subscribe to parameter changes
+ *
+ * Automatically emit param events for the given ids when
+ * they are changed.
+ *
+ * \param ids an array of param ids
+ * \param n_ids the number of ids in \a ids
+ */
+ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
+
+ /**
+ * Enumerate node parameters
+ *
+ * Start enumeration of node parameters. For each param, a
+ * param event will be emitted.
+ *
+ * \param seq a sequence number to place in the reply
+ * \param id the parameter id to enum or PW_ID_ANY for all
+ * \param start the start index or 0 for the first param
+ * \param num the maximum number of params to retrieve
+ * \param filter a param filter or NULL
+ */
+ int (*enum_params) (void *object, int seq, uint32_t id,
+ uint32_t start, uint32_t num,
+ const struct spa_pod *filter);
+
+ /**
+ * Set a parameter on the node
+ *
+ * \param id the parameter id to set
+ * \param flags extra parameter flags
+ * \param param the parameter to set
+ */
+ int (*set_param) (void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+
+ /**
+ * Send a command to the node
+ *
+ * \param command the command to send
+ */
+ 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__)
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_NODE_H */
diff --git a/src/pipewire/permission.h b/src/pipewire/permission.h
new file mode 100644
index 0000000..1bcebc3
--- /dev/null
+++ b/src/pipewire/permission.h
@@ -0,0 +1,86 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_PERMISSION_H
+#define PIPEWIRE_PERMISSION_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/defs.h>
+
+/** \defgroup pw_permission Permission
+ *
+ * Permissions are kept for a client and describe what the client is
+ * allowed to do with an object.
+ *
+ * See \ref page_core_api
+ */
+
+/**
+ * \addtogroup pw_permission
+ * \{
+ */
+
+#define PW_PERM_R 0400 /**< object can be seen and events can be received */
+#define PW_PERM_W 0200 /**< methods can be called that modify the object */
+#define PW_PERM_X 0100 /**< methods can be called on the object. The W flag must be
+ * present in order to call methods that modify the object. */
+#define PW_PERM_M 0010 /**< metadata can be set on object, Since 0.3.9 */
+
+#define PW_PERM_RWX (PW_PERM_R|PW_PERM_W|PW_PERM_X)
+#define PW_PERM_RWXM (PW_PERM_RWX|PW_PERM_M)
+
+#define PW_PERM_IS_R(p) (((p)&PW_PERM_R) == PW_PERM_R)
+#define PW_PERM_IS_W(p) (((p)&PW_PERM_W) == PW_PERM_W)
+#define PW_PERM_IS_X(p) (((p)&PW_PERM_X) == PW_PERM_X)
+#define PW_PERM_IS_M(p) (((p)&PW_PERM_M) == PW_PERM_M)
+
+#define PW_PERM_ALL PW_PERM_RWXM
+#define PW_PERM_INVALID (uint32_t)(0xffffffff)
+
+struct pw_permission {
+ uint32_t id; /**< id of object, PW_ID_ANY for default permission */
+ uint32_t permissions; /**< bitmask of above permissions */
+};
+
+#define PW_PERMISSION_INIT(id,p) ((struct pw_permission){ (id), (p) })
+
+#define PW_PERMISSION_FORMAT "%c%c%c%c"
+#define PW_PERMISSION_ARGS(permission) \
+ (permission) & PW_PERM_R ? 'r' : '-', \
+ (permission) & PW_PERM_W ? 'w' : '-', \
+ (permission) & PW_PERM_X ? 'x' : '-', \
+ (permission) & PW_PERM_M ? 'm' : '-'
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_PERMISSION_H */
diff --git a/src/pipewire/pipewire.c b/src/pipewire/pipewire.c
new file mode 100644
index 0000000..104b52b
--- /dev/null
+++ b/src/pipewire/pipewire.c
@@ -0,0 +1,871 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <limits.h>
+#include <stdio.h>
+#if !defined(__FreeBSD__) && !defined(__MidnightBSD__)
+#include <sys/prctl.h>
+#endif
+#include <pwd.h>
+#include <errno.h>
+#include <dlfcn.h>
+#include <pthread.h>
+
+#include <locale.h>
+#include <libintl.h>
+
+#include <valgrind/valgrind.h>
+
+#include <spa/utils/names.h>
+#include <spa/utils/string.h>
+#include <spa/support/cpu.h>
+#include <spa/support/i18n.h>
+
+#include "pipewire.h"
+#include "private.h"
+
+#define MAX_SUPPORT 32
+
+#define SUPPORTLIB "support/libspa-support"
+
+PW_LOG_TOPIC_EXTERN(log_context);
+#define PW_LOG_TOPIC_DEFAULT log_context
+
+static char *prgname;
+
+static struct spa_i18n *_pipewire_i18n = NULL;
+
+struct plugin {
+ struct spa_list link;
+ char *filename;
+ void *hnd;
+ spa_handle_factory_enum_func_t enum_func;
+ int ref;
+};
+
+struct handle {
+ struct spa_list link;
+ struct plugin *plugin;
+ char *factory_name;
+ int ref;
+ struct spa_handle handle SPA_ALIGNED(8);
+};
+
+struct registry {
+ struct spa_list plugins;
+ struct spa_list handles; /* all handles across all plugins by age (youngest first) */
+};
+
+struct support {
+ const char *plugin_dir;
+ const char *support_lib;
+ struct registry registry;
+ char *i18n_domain;
+ struct spa_interface i18n_iface;
+ struct spa_support support[MAX_SUPPORT];
+ uint32_t n_support;
+ uint32_t init_count;
+ unsigned int in_valgrind:1;
+ unsigned int no_color:1;
+ unsigned int no_config:1;
+ unsigned int do_dlclose:1;
+};
+
+static pthread_mutex_t init_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t support_lock = PTHREAD_MUTEX_INITIALIZER;
+static struct support global_support;
+
+static struct plugin *
+find_plugin(struct registry *registry, const char *filename)
+{
+ struct plugin *p;
+ spa_list_for_each(p, &registry->plugins, link) {
+ if (spa_streq(p->filename, filename))
+ return p;
+ }
+ return NULL;
+}
+
+static struct plugin *
+open_plugin(struct registry *registry,
+ const char *path, size_t len, const char *lib)
+{
+ struct plugin *plugin;
+ char filename[PATH_MAX];
+ void *hnd;
+ spa_handle_factory_enum_func_t enum_func;
+ int res;
+
+ if ((res = spa_scnprintf(filename, sizeof(filename), "%.*s/%s.so", (int)len, path, lib)) < 0)
+ goto error_out;
+
+ if ((plugin = find_plugin(registry, filename)) != NULL) {
+ plugin->ref++;
+ return plugin;
+ }
+
+ if ((hnd = dlopen(filename, RTLD_NOW)) == NULL) {
+ res = -ENOENT;
+ pw_log_debug("can't load %s: %s", filename, dlerror());
+ goto error_out;
+ }
+ if ((enum_func = dlsym(hnd, SPA_HANDLE_FACTORY_ENUM_FUNC_NAME)) == NULL) {
+ res = -ENOSYS;
+ pw_log_debug("can't find enum function: %s", dlerror());
+ goto error_dlclose;
+ }
+
+ if ((plugin = calloc(1, sizeof(struct plugin))) == NULL) {
+ res = -errno;
+ goto error_dlclose;
+ }
+
+ pw_log_debug("loaded plugin:'%s'", filename);
+ plugin->ref = 1;
+ plugin->filename = strdup(filename);
+ plugin->hnd = hnd;
+ plugin->enum_func = enum_func;
+
+ spa_list_append(&registry->plugins, &plugin->link);
+
+ return plugin;
+
+error_dlclose:
+ dlclose(hnd);
+error_out:
+ errno = -res;
+ return NULL;
+}
+
+static void
+unref_plugin(struct plugin *plugin)
+{
+ if (--plugin->ref == 0) {
+ spa_list_remove(&plugin->link);
+ pw_log_debug("unloaded plugin:'%s'", plugin->filename);
+ if (global_support.do_dlclose)
+ dlclose(plugin->hnd);
+ free(plugin->filename);
+ free(plugin);
+ }
+}
+
+static const struct spa_handle_factory *find_factory(struct plugin *plugin, const char *factory_name)
+{
+ int res = -ENOENT;
+ uint32_t index;
+ const struct spa_handle_factory *factory;
+
+ for (index = 0;;) {
+ if ((res = plugin->enum_func(&factory, &index)) <= 0) {
+ if (res == 0)
+ break;
+ goto out;
+ }
+ if (factory->version < 1) {
+ pw_log_warn("factory version %d < 1 not supported",
+ factory->version);
+ continue;
+ }
+ if (spa_streq(factory->name, factory_name))
+ return factory;
+ }
+ res = -ENOENT;
+out:
+ pw_log_debug("can't find factory %s: %s", factory_name, spa_strerror(res));
+ errno = -res;
+ return NULL;
+}
+
+static void unref_handle(struct handle *handle)
+{
+ if (--handle->ref == 0) {
+ spa_list_remove(&handle->link);
+ pw_log_debug("clear handle '%s'", handle->factory_name);
+ pthread_mutex_unlock(&support_lock);
+ spa_handle_clear(&handle->handle);
+ pthread_mutex_lock(&support_lock);
+ unref_plugin(handle->plugin);
+ free(handle->factory_name);
+ free(handle);
+ }
+}
+
+SPA_EXPORT
+uint32_t pw_get_support(struct spa_support *support, uint32_t max_support)
+{
+ uint32_t i, n = SPA_MIN(global_support.n_support, max_support);
+ for (i = 0; i < n; i++)
+ support[i] = global_support.support[i];
+ return n;
+}
+
+static struct spa_handle *load_spa_handle(const char *lib,
+ const char *factory_name,
+ const struct spa_dict *info,
+ uint32_t n_support,
+ const struct spa_support support[])
+{
+ struct support *sup = &global_support;
+ struct plugin *plugin;
+ struct handle *handle;
+ const struct spa_handle_factory *factory;
+ const char *state = NULL, *p;
+ int res;
+ size_t len;
+
+ if (factory_name == NULL) {
+ res = -EINVAL;
+ goto error_out;
+ }
+
+ if (lib == NULL)
+ lib = sup->support_lib;
+
+ pw_log_debug("load lib:'%s' factory-name:'%s'", lib, factory_name);
+
+ plugin = NULL;
+ res = -ENOENT;
+
+ if (sup->plugin_dir == NULL) {
+ pw_log_error("load lib: plugin directory undefined, set SPA_PLUGIN_DIR");
+ goto error_out;
+ }
+ while ((p = pw_split_walk(sup->plugin_dir, ":", &len, &state))) {
+ if ((plugin = open_plugin(&sup->registry, p, len, lib)) != NULL)
+ break;
+ res = -errno;
+ }
+ if (plugin == NULL)
+ goto error_out;
+
+ pthread_mutex_unlock(&support_lock);
+
+ factory = find_factory(plugin, factory_name);
+ if (factory == NULL) {
+ res = -errno;
+ goto error_unref_plugin;
+ }
+
+ handle = calloc(1, sizeof(struct handle) + spa_handle_factory_get_size(factory, info));
+ if (handle == NULL) {
+ res = -errno;
+ goto error_unref_plugin;
+ }
+
+ if ((res = spa_handle_factory_init(factory,
+ &handle->handle, info,
+ support, n_support)) < 0) {
+ pw_log_debug("can't make factory instance '%s': %d (%s)",
+ factory_name, res, spa_strerror(res));
+ goto error_free_handle;
+ }
+
+ pthread_mutex_lock(&support_lock);
+ handle->ref = 1;
+ handle->plugin = plugin;
+ handle->factory_name = strdup(factory_name);
+ spa_list_prepend(&sup->registry.handles, &handle->link);
+
+ return &handle->handle;
+
+error_free_handle:
+ free(handle);
+error_unref_plugin:
+ pthread_mutex_lock(&support_lock);
+ unref_plugin(plugin);
+error_out:
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+struct spa_handle *pw_load_spa_handle(const char *lib,
+ const char *factory_name,
+ const struct spa_dict *info,
+ uint32_t n_support,
+ const struct spa_support support[])
+{
+ struct spa_handle *handle;
+ pthread_mutex_lock(&support_lock);
+ handle = load_spa_handle(lib, factory_name, info, n_support, support);
+ pthread_mutex_unlock(&support_lock);
+ return handle;
+}
+
+static struct handle *find_handle(struct spa_handle *handle)
+{
+ struct registry *registry = &global_support.registry;
+ struct handle *h;
+
+ spa_list_for_each(h, &registry->handles, link) {
+ if (&h->handle == handle)
+ return h;
+ }
+
+ return NULL;
+}
+
+SPA_EXPORT
+int pw_unload_spa_handle(struct spa_handle *handle)
+{
+ struct handle *h;
+ int res = 0;
+
+ pthread_mutex_lock(&support_lock);
+ if ((h = find_handle(handle)) == NULL)
+ res = -ENOENT;
+ else
+ unref_handle(h);
+ pthread_mutex_unlock(&support_lock);
+
+ return res;
+}
+
+static void *add_interface(struct support *support,
+ const char *factory_name,
+ const char *type,
+ const struct spa_dict *info)
+{
+ struct spa_handle *handle;
+ void *iface = NULL;
+ int res = -ENOENT;
+
+ handle = load_spa_handle(support->support_lib,
+ factory_name, info,
+ support->n_support, support->support);
+ if (handle == NULL)
+ return NULL;
+
+ pthread_mutex_unlock(&support_lock);
+ res = spa_handle_get_interface(handle, type, &iface);
+ pthread_mutex_lock(&support_lock);
+
+ if (res < 0 || iface == NULL) {
+ pw_log_error("can't get %s interface %d: %s", type, res,
+ spa_strerror(res));
+ return NULL;
+ }
+
+ support->support[support->n_support++] =
+ SPA_SUPPORT_INIT(type, iface);
+ return iface;
+}
+
+SPA_EXPORT
+int pw_set_domain(const char *domain)
+{
+ struct support *support = &global_support;
+ free(support->i18n_domain);
+ if (domain == NULL)
+ support->i18n_domain = NULL;
+ else if ((support->i18n_domain = strdup(domain)) == NULL)
+ return -errno;
+ return 0;
+}
+
+SPA_EXPORT
+const char *pw_get_domain(void)
+{
+ struct support *support = &global_support;
+ return support->i18n_domain;
+}
+
+static const char *i18n_text(void *object, const char *msgid)
+{
+ struct support *support = object;
+ return dgettext(support->i18n_domain, msgid);
+}
+
+static const char *i18n_ntext(void *object, const char *msgid, const char *msgid_plural,
+ unsigned long int n)
+{
+ struct support *support = object;
+ return dngettext(support->i18n_domain, msgid, msgid_plural, n);
+}
+
+static void init_i18n(struct support *support)
+{
+ bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+ pw_set_domain(GETTEXT_PACKAGE);
+}
+
+static void *add_i18n(struct support *support)
+{
+ static const struct spa_i18n_methods i18n_methods = {
+ SPA_VERSION_I18N_METHODS,
+ .text = i18n_text,
+ .ntext = i18n_ntext,
+ };
+
+ support->i18n_iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_I18N,
+ SPA_VERSION_I18N,
+ &i18n_methods, support);
+ _pipewire_i18n = (struct spa_i18n*) &support->i18n_iface;
+
+ support->support[support->n_support++] =
+ SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_I18N, _pipewire_i18n);
+
+ return 0;
+}
+
+SPA_EXPORT
+const char *pw_gettext(const char *msgid)
+{
+ return spa_i18n_text(_pipewire_i18n, msgid);
+}
+SPA_EXPORT
+const char *pw_ngettext(const char *msgid, const char *msgid_plural, unsigned long int n)
+{
+ return spa_i18n_ntext(_pipewire_i18n, msgid, msgid_plural, n);
+}
+
+#ifdef HAVE_SYSTEMD
+static struct spa_log *load_journal_logger(struct support *support,
+ const struct spa_dict *info)
+{
+ struct spa_handle *handle;
+ void *iface = NULL;
+ int res = -ENOENT;
+ uint32_t i;
+
+ /* is the journal even available? */
+ if (access("/run/systemd/journal/socket", F_OK) != 0)
+ return NULL;
+
+ handle = load_spa_handle("support/libspa-journal",
+ SPA_NAME_SUPPORT_LOG, info,
+ support->n_support, support->support);
+ if (handle == NULL)
+ return NULL;
+
+ pthread_mutex_unlock(&support_lock);
+ res = spa_handle_get_interface(handle, SPA_TYPE_INTERFACE_Log, &iface);
+ pthread_mutex_lock(&support_lock);
+
+ if (res < 0 || iface == NULL) {
+ pw_log_error("can't get log interface %d: %s", res,
+ spa_strerror(res));
+ return NULL;
+ }
+
+ /* look for an existing logger, and
+ * replace it with the journal logger */
+ for (i = 0; i < support->n_support; i++) {
+ if (spa_streq(support->support[i].type, SPA_TYPE_INTERFACE_Log)) {
+ support->support[i].data = iface;
+ break;
+ }
+ }
+ return (struct spa_log *) iface;
+}
+#endif
+
+static bool
+parse_log_level(const char *str, enum spa_log_level *l)
+{
+ uint32_t lvl;
+ if (strlen(str) == 1) {
+ switch(str[0]) {
+ case 'X': lvl = SPA_LOG_LEVEL_NONE; break;
+ case 'E': lvl = SPA_LOG_LEVEL_ERROR; break;
+ case 'W': lvl = SPA_LOG_LEVEL_WARN; break;
+ case 'I': lvl = SPA_LOG_LEVEL_INFO; break;
+ case 'D': lvl = SPA_LOG_LEVEL_DEBUG; break;
+ case 'T': lvl = SPA_LOG_LEVEL_TRACE; break;
+ default:
+ goto check_int;
+ }
+ } else {
+check_int:
+ if (!spa_atou32(str, &lvl, 0))
+ return false;
+ if (lvl > SPA_LOG_LEVEL_TRACE)
+ return false;
+ }
+ *l = lvl;
+ return true;
+}
+
+static char *
+parse_pw_debug_env(void)
+{
+ const char *str;
+ char **tokens;
+ int n_tokens;
+ size_t slen;
+ char json[1024] = {0};
+ char *pos = json;
+ char *end = pos + sizeof(json) - 1;
+ enum spa_log_level lvl;
+
+ str = getenv("PIPEWIRE_DEBUG");
+
+ if (!str || (slen = strlen(str)) == 0)
+ return NULL;
+
+ /* String format is PIPEWIRE_DEBUG=[<glob>:]<level>,...,
+ * converted into [{ conn.* = 0}, {glob = level}, {glob = level}, ....] ,
+ * with the connection namespace disabled by default.
+ */
+ pos += spa_scnprintf(pos, end - pos, "[ { conn.* = %d },", SPA_LOG_LEVEL_NONE);
+
+ tokens = pw_split_strv(str, ",", INT_MAX, &n_tokens);
+ if (n_tokens > 0) {
+ int i;
+ for (i = 0; i < n_tokens; i++) {
+ int n_tok;
+ char **tok;
+ char *pattern;
+
+ tok = pw_split_strv(tokens[i], ":", 2, &n_tok);
+ if (n_tok == 2 && parse_log_level(tok[1], &lvl)) {
+ pattern = tok[0];
+ pos += spa_scnprintf(pos, end - pos, "{ %s = %d },",
+ pattern, lvl);
+ } else if (n_tok == 1 && parse_log_level(tok[0], &lvl)) {
+ pw_log_set_level(lvl);
+ } else {
+ pw_log_warn("Ignoring invalid format in PIPEWIRE_DEBUG: '%s'",
+ tokens[i]);
+ }
+
+ pw_free_strv(tok);
+ }
+ }
+ pw_free_strv(tokens);
+ pos += spa_scnprintf(pos, end - pos, "]");
+ return strdup(json);
+}
+
+/** Initialize PipeWire
+ *
+ * \param argc pointer to argc
+ * \param argv pointer to argv
+ *
+ * Initialize the PipeWire system, parse and modify any parameters given
+ * by \a argc and \a argv and set up debugging.
+ *
+ * This function can be called multiple times.
+ */
+SPA_EXPORT
+void pw_init(int *argc, char **argv[])
+{
+ const char *str;
+ struct spa_dict_item items[6];
+ uint32_t n_items;
+ struct spa_dict info;
+ struct support *support = &global_support;
+ struct spa_log *log;
+ char level[32];
+
+ pthread_mutex_lock(&init_lock);
+ if (support->init_count > 0)
+ goto done;
+
+ pthread_mutex_lock(&support_lock);
+ support->in_valgrind = RUNNING_ON_VALGRIND;
+
+ support->do_dlclose = true;
+ if ((str = getenv("PIPEWIRE_DLCLOSE")) != NULL)
+ support->do_dlclose = pw_properties_parse_bool(str);
+
+ if (getenv("NO_COLOR") != NULL)
+ support->no_color = true;
+
+ if ((str = getenv("PIPEWIRE_NO_CONFIG")) != NULL)
+ support->no_config = pw_properties_parse_bool(str);
+
+ init_i18n(support);
+
+ if ((str = getenv("SPA_PLUGIN_DIR")) == NULL)
+ str = PLUGINDIR;
+ support->plugin_dir = str;
+
+ if ((str = getenv("SPA_SUPPORT_LIB")) == NULL)
+ str = SUPPORTLIB;
+ support->support_lib = str;
+
+ spa_list_init(&support->registry.plugins);
+ spa_list_init(&support->registry.handles);
+
+ if (pw_log_is_default()) {
+ char *patterns = NULL;
+
+ n_items = 0;
+ if (!support->no_color)
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_COLORS, "true");
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_TIMESTAMP, "true");
+ 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);
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_LEVEL, level);
+ if ((str = getenv("PIPEWIRE_LOG")) != NULL)
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_FILE, str);
+ if ((patterns = parse_pw_debug_env()) != NULL)
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LOG_PATTERNS, patterns);
+ info = SPA_DICT_INIT(items, n_items);
+
+ log = add_interface(support, SPA_NAME_SUPPORT_LOG, SPA_TYPE_INTERFACE_Log, &info);
+ if (log)
+ pw_log_set(log);
+
+#ifdef HAVE_SYSTEMD
+ if ((str = getenv("PIPEWIRE_LOG_SYSTEMD")) == NULL || spa_atob(str)) {
+ log = load_journal_logger(support, &info);
+ if (log)
+ pw_log_set(log);
+ }
+#endif
+ free(patterns);
+ } else {
+ support->support[support->n_support++] =
+ SPA_SUPPORT_INIT(SPA_TYPE_INTERFACE_Log, pw_log_get());
+ }
+
+ pw_log_init();
+
+ n_items = 0;
+ if ((str = getenv("PIPEWIRE_CPU")))
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_CPU_FORCE, str);
+ if ((str = getenv("PIPEWIRE_VM")))
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_CPU_VM_TYPE, str);
+ info = SPA_DICT_INIT(items, n_items);
+
+ add_interface(support, SPA_NAME_SUPPORT_CPU, SPA_TYPE_INTERFACE_CPU, &info);
+
+ add_i18n(support);
+
+ pw_log_info("version %s", pw_get_library_version());
+ pthread_mutex_unlock(&support_lock);
+done:
+ support->init_count++;
+ pthread_mutex_unlock(&init_lock);
+}
+
+/** Deinitialize PipeWire
+ *
+ * Deinitialize the PipeWire system and free up all resources allocated
+ * by pw_init().
+ *
+ * Before 0.3.49 this function can only be called once after which the pipewire
+ * library can not be used again. This is usually called by test programs to
+ * check for memory leaks.
+ *
+ * Since 0.3.49 this function must be paired with an equal amount of pw_init()
+ * calls to deinitialize the PipeWire library. The PipeWire library can be
+ * used again after being deinitialized with a new pw_init() call.
+ */
+SPA_EXPORT
+void pw_deinit(void)
+{
+ struct support *support = &global_support;
+ struct registry *registry = &support->registry;
+ struct handle *h;
+
+ pthread_mutex_lock(&init_lock);
+ if (support->init_count == 0)
+ goto done;
+ if (--support->init_count > 0)
+ goto done;
+
+ pthread_mutex_lock(&support_lock);
+ pw_log_set(NULL);
+
+ spa_list_consume(h, &registry->handles, link)
+ unref_handle(h);
+
+ free(support->i18n_domain);
+ spa_zero(global_support);
+ pthread_mutex_unlock(&support_lock);
+done:
+ pthread_mutex_unlock(&init_lock);
+
+}
+
+/** Check if a debug category is enabled
+ *
+ * \param name the name of the category to check
+ * \return true if enabled
+ *
+ * Debugging categories can be enabled by using the PIPEWIRE_DEBUG
+ * environment variable
+ */
+SPA_EXPORT
+bool pw_debug_is_category_enabled(const char *name)
+{
+ struct spa_log_topic t = SPA_LOG_TOPIC(0, name);
+ PW_LOG_TOPIC_INIT(&t);
+ return t.has_custom_level;
+}
+
+/** Get the application name */
+SPA_EXPORT
+const char *pw_get_application_name(void)
+{
+ errno = ENOTSUP;
+ return NULL;
+}
+
+static void init_prgname(void)
+{
+ static char name[PATH_MAX];
+
+ spa_memzero(name, sizeof(name));
+#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__MidnightBSD_kernel__)
+ {
+ if (readlink("/proc/self/exe", name, sizeof(name)-1) > 0) {
+ prgname = strrchr(name, '/') + 1;
+ return;
+ }
+ }
+#endif
+#if defined __FreeBSD__ || defined(__MidnightBSD__)
+ {
+ ssize_t len;
+
+ if ((len = readlink("/proc/curproc/file", name, sizeof(name)-1)) > 0) {
+ prgname = strrchr(name, '/') + 1;
+ return;
+ }
+ }
+#endif
+#if !defined(__FreeBSD__) && !defined(__MidnightBSD__)
+ {
+ if (prctl(PR_GET_NAME, (unsigned long) name, 0, 0, 0) == 0) {
+ prgname = name;
+ return;
+ }
+ }
+#endif
+ snprintf(name, sizeof(name), "pid-%d", getpid());
+ prgname = name;
+}
+
+/** Get the program name */
+SPA_EXPORT
+const char *pw_get_prgname(void)
+{
+ static pthread_once_t prgname_is_initialized = PTHREAD_ONCE_INIT;
+
+ pthread_once(&prgname_is_initialized, init_prgname);
+ return prgname;
+}
+
+/** Get the user name */
+SPA_EXPORT
+const char *pw_get_user_name(void)
+{
+ struct passwd *pw;
+
+ if ((pw = getpwuid(getuid())))
+ return pw->pw_name;
+
+ return NULL;
+}
+
+/** Get the host name */
+SPA_EXPORT
+const char *pw_get_host_name(void)
+{
+ static char hname[256];
+
+ if (gethostname(hname, 256) < 0)
+ return NULL;
+
+ hname[255] = 0;
+ return hname;
+}
+
+SPA_EXPORT
+bool pw_in_valgrind(void)
+{
+ return global_support.in_valgrind;
+}
+
+SPA_EXPORT
+bool pw_check_option(const char *option, const char *value)
+{
+ if (spa_streq(option, "in-valgrind"))
+ return global_support.in_valgrind == spa_atob(value);
+ else if (spa_streq(option, "no-color"))
+ return global_support.no_color == spa_atob(value);
+ else if (spa_streq(option, "no-config"))
+ return global_support.no_config == spa_atob(value);
+ else if (spa_streq(option, "do-dlclose"))
+ return global_support.do_dlclose == spa_atob(value);
+ return false;
+}
+
+/** Get the client name
+ *
+ * Make a new PipeWire client name that can be used to construct a remote.
+ *
+ */
+SPA_EXPORT
+const char *pw_get_client_name(void)
+{
+ const char *cc;
+ static char cname[256];
+
+ if ((cc = pw_get_application_name()) || (cc = pw_get_prgname()))
+ return cc;
+ else if (snprintf(cname, sizeof(cname), "pipewire-pid-%zd", (size_t) getpid()) < 0)
+ return NULL;
+ return cname;
+}
+
+/** Reverse the direction */
+SPA_EXPORT
+enum pw_direction pw_direction_reverse(enum pw_direction direction)
+{
+ if (direction == PW_DIRECTION_INPUT)
+ return PW_DIRECTION_OUTPUT;
+ else if (direction == PW_DIRECTION_OUTPUT)
+ return PW_DIRECTION_INPUT;
+ return direction;
+}
+
+/** Get the currently running version */
+SPA_EXPORT
+const char* pw_get_library_version(void)
+{
+ return pw_get_headers_version();
+}
+
+static const struct spa_type_info type_info[] = {
+ { SPA_ID_INVALID, SPA_ID_INVALID, "spa_types", spa_types },
+ { 0, 0, NULL, NULL },
+};
+
+SPA_EXPORT
+const struct spa_type_info * pw_type_info(void)
+{
+ return type_info;
+}
diff --git a/src/pipewire/pipewire.h b/src/pipewire/pipewire.h
new file mode 100644
index 0000000..ed24fbd
--- /dev/null
+++ b/src/pipewire/pipewire.h
@@ -0,0 +1,123 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_H
+#define PIPEWIRE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/support/plugin.h>
+
+#include <pipewire/array.h>
+#include <pipewire/client.h>
+#include <pipewire/conf.h>
+#include <pipewire/context.h>
+#include <pipewire/device.h>
+#include <pipewire/buffers.h>
+#include <pipewire/core.h>
+#include <pipewire/factory.h>
+#include <pipewire/keys.h>
+#include <pipewire/log.h>
+#include <pipewire/loop.h>
+#include <pipewire/link.h>
+#include <pipewire/main-loop.h>
+#include <pipewire/map.h>
+#include <pipewire/mem.h>
+#include <pipewire/module.h>
+#include <pipewire/node.h>
+#include <pipewire/properties.h>
+#include <pipewire/proxy.h>
+#include <pipewire/permission.h>
+#include <pipewire/protocol.h>
+#include <pipewire/port.h>
+#include <pipewire/stream.h>
+#include <pipewire/filter.h>
+#include <pipewire/thread-loop.h>
+#include <pipewire/data-loop.h>
+#include <pipewire/type.h>
+#include <pipewire/utils.h>
+#include <pipewire/version.h>
+
+/** \defgroup pw_pipewire Initialization
+ * Initializing PipeWire and loading SPA modules.
+ */
+
+/**
+ * \addtogroup pw_pipewire
+ * \{
+ */
+void
+pw_init(int *argc, char **argv[]);
+
+void pw_deinit(void);
+
+bool
+pw_debug_is_category_enabled(const char *name);
+
+const char *
+pw_get_application_name(void);
+
+const char *
+pw_get_prgname(void);
+
+const char *
+pw_get_user_name(void);
+
+const char *
+pw_get_host_name(void);
+
+const char *
+pw_get_client_name(void);
+
+bool pw_in_valgrind(void);
+
+bool pw_check_option(const char *option, const char *value);
+
+enum pw_direction
+pw_direction_reverse(enum pw_direction direction);
+
+int pw_set_domain(const char *domain);
+const char *pw_get_domain(void);
+
+uint32_t pw_get_support(struct spa_support *support, uint32_t max_support);
+
+struct spa_handle *pw_load_spa_handle(const char *lib,
+ const char *factory_name,
+ const struct spa_dict *info,
+ uint32_t n_support,
+ const struct spa_support support[]);
+
+int pw_unload_spa_handle(struct spa_handle *handle);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_H */
diff --git a/src/pipewire/port.h b/src/pipewire/port.h
new file mode 100644
index 0000000..463af23
--- /dev/null
+++ b/src/pipewire/port.h
@@ -0,0 +1,179 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_PORT_H
+#define PIPEWIRE_PORT_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+#include <errno.h>
+
+#include <spa/utils/defs.h>
+#include <spa/utils/hook.h>
+#include <spa/param/param.h>
+
+#include <pipewire/proxy.h>
+
+/** \defgroup pw_port Port
+ * Port interface
+ */
+
+/**
+ * \addtogroup pw_port
+ * \{
+ */
+
+#define PW_TYPE_INTERFACE_Port PW_TYPE_INFO_INTERFACE_BASE "Port"
+
+#define PW_VERSION_PORT 3
+struct pw_port;
+
+/** The direction of a port */
+#define pw_direction spa_direction
+#define PW_DIRECTION_INPUT SPA_DIRECTION_INPUT
+#define PW_DIRECTION_OUTPUT SPA_DIRECTION_OUTPUT
+
+/** Convert a \ref pw_direction to a readable string */
+const char * pw_direction_as_string(enum pw_direction direction);
+
+struct pw_port_info {
+ uint32_t id; /**< id of the global */
+ enum pw_direction direction; /**< port direction */
+#define PW_PORT_CHANGE_MASK_PROPS (1 << 0)
+#define PW_PORT_CHANGE_MASK_PARAMS (1 << 1)
+#define PW_PORT_CHANGE_MASK_ALL ((1 << 2)-1)
+ uint64_t change_mask; /**< bitfield of changed fields since last call */
+ struct spa_dict *props; /**< the properties of the port */
+ struct spa_param_info *params; /**< parameters */
+ uint32_t n_params; /**< number of items in \a params */
+};
+
+struct pw_port_info *
+pw_port_info_update(struct pw_port_info *info,
+ const struct pw_port_info *update);
+
+struct pw_port_info *
+pw_port_info_merge(struct pw_port_info *info,
+ const struct pw_port_info *update, bool reset);
+
+void
+pw_port_info_free(struct pw_port_info *info);
+
+#define PW_PORT_EVENT_INFO 0
+#define PW_PORT_EVENT_PARAM 1
+#define PW_PORT_EVENT_NUM 2
+
+/** Port events */
+struct pw_port_events {
+#define PW_VERSION_PORT_EVENTS 0
+ uint32_t version;
+ /**
+ * Notify port info
+ *
+ * \param info info about the port
+ */
+ void (*info) (void *data, const struct pw_port_info *info);
+ /**
+ * Notify a port param
+ *
+ * Event emitted as a result of the enum_params method.
+ *
+ * \param seq the sequence number of the request
+ * \param id the param id
+ * \param index the param index
+ * \param next the param index of the next param
+ * \param param the parameter
+ */
+ void (*param) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param);
+};
+
+#define PW_PORT_METHOD_ADD_LISTENER 0
+#define PW_PORT_METHOD_SUBSCRIBE_PARAMS 1
+#define PW_PORT_METHOD_ENUM_PARAMS 2
+#define PW_PORT_METHOD_NUM 3
+
+/** Port methods */
+struct pw_port_methods {
+#define PW_VERSION_PORT_METHODS 0
+ uint32_t version;
+
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_port_events *events,
+ void *data);
+ /**
+ * Subscribe to parameter changes
+ *
+ * Automatically emit param events for the given ids when
+ * they are changed.
+ *
+ * \param ids an array of param ids
+ * \param n_ids the number of ids in \a ids
+ */
+ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
+
+ /**
+ * Enumerate port parameters
+ *
+ * Start enumeration of port parameters. For each param, a
+ * param event will be emitted.
+ *
+ * \param seq a sequence number returned in the reply
+ * \param id the parameter id to enumerate
+ * \param start the start index or 0 for the first param
+ * \param num the maximum number of params to retrieve
+ * \param filter a param filter or NULL
+ */
+ int (*enum_params) (void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ 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__)
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_PORT_H */
diff --git a/src/pipewire/private.h b/src/pipewire/private.h
new file mode 100644
index 0000000..6fa8d77
--- /dev/null
+++ b/src/pipewire/private.h
@@ -0,0 +1,1310 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_PRIVATE_H
+#define PIPEWIRE_PRIVATE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/socket.h>
+#include <sys/types.h> /* for pthread_t */
+
+#include "pipewire/impl.h"
+
+#include <spa/support/plugin.h>
+#include <spa/pod/builder.h>
+#include <spa/param/latency-utils.h>
+#include <spa/utils/result.h>
+#include <spa/utils/type-info.h>
+
+#if defined(__FreeBSD__) || defined(__MidnightBSD__)
+struct ucred {
+};
+#endif
+
+#define MAX_RATES 32u
+#define CLOCK_MIN_QUANTUM 4u
+#define CLOCK_MAX_QUANTUM 65536u
+
+struct settings {
+ uint32_t log_level;
+ uint32_t clock_rate; /* default clock rate */
+ uint32_t clock_rates[MAX_RATES]; /* allowed clock rates */
+ uint32_t n_clock_rates; /* number of alternative clock rates */
+ uint32_t clock_quantum; /* default quantum */
+ uint32_t clock_min_quantum; /* min quantum */
+ uint32_t clock_max_quantum; /* max quantum */
+ uint32_t clock_quantum_limit; /* quantum limit */
+ struct spa_rectangle video_size;
+ struct spa_fraction video_rate;
+ uint32_t link_max_buffers;
+ unsigned int mem_warn_mlock:1;
+ unsigned int mem_allow_mlock:1;
+ unsigned int clock_power_of_two_quantum:1;
+ unsigned int check_quantum:1;
+ unsigned int check_rate:1;
+#define CLOCK_RATE_UPDATE_MODE_HARD 0
+#define CLOCK_RATE_UPDATE_MODE_SOFT 1
+ int clock_rate_update_mode;
+ uint32_t clock_force_rate; /* force a clock rate */
+ uint32_t clock_force_quantum; /* force a quantum */
+};
+
+struct ratelimit {
+ uint64_t interval;
+ uint64_t begin;
+ unsigned burst;
+ unsigned n_printed, n_missed;
+};
+
+static inline bool ratelimit_test(struct ratelimit *r, uint64_t now, enum spa_log_level level)
+{
+ if (r->begin + r->interval < now) {
+ if (r->n_missed)
+ pw_log(level, "%u events suppressed", r->n_missed);
+ r->begin = now;
+ r->n_printed = 0;
+ r->n_missed = 0;
+ } else if (r->n_printed >= r->burst) {
+ r->n_missed++;
+ return false;
+ }
+ r->n_printed++;
+ return true;
+}
+
+#define MAX_PARAMS 32
+
+struct pw_param {
+ uint32_t id;
+ int32_t seq;
+ struct spa_list link;
+ struct spa_pod *param;
+};
+
+static inline uint32_t pw_param_clear(struct spa_list *param_list, uint32_t id)
+{
+ struct pw_param *p, *t;
+ uint32_t count = 0;
+
+ spa_list_for_each_safe(p, t, param_list, link) {
+ if (id == SPA_ID_INVALID || p->id == id) {
+ spa_list_remove(&p->link);
+ free(p);
+ count++;
+ }
+ }
+ return count;
+}
+
+static inline struct pw_param *pw_param_add(struct spa_list *params, int32_t seq,
+ uint32_t id, const struct spa_pod *param)
+{
+ struct pw_param *p;
+
+ if (id == SPA_ID_INVALID) {
+ if (param == NULL || !spa_pod_is_object(param)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ id = SPA_POD_OBJECT_ID(param);
+ }
+
+ if ((p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0))) == NULL)
+ return NULL;
+
+ p->id = id;
+ p->seq = seq;
+ if (param != NULL) {
+ p->param = SPA_PTROFF(p, sizeof(*p), struct spa_pod);
+ memcpy(p->param, param, SPA_POD_SIZE(param));
+ } else {
+ pw_param_clear(params, id);
+ p->param = NULL;
+ }
+ spa_list_append(params, &p->link);
+ return p;
+}
+
+static inline void pw_param_update(struct spa_list *param_list, struct spa_list *pending_list,
+ uint32_t n_params, struct spa_param_info *params)
+{
+ struct pw_param *p, *t;
+ uint32_t i;
+
+ for (i = 0; i < n_params; i++) {
+ spa_list_for_each_safe(p, t, pending_list, link) {
+ if (p->id == params[i].id &&
+ p->seq != params[i].seq &&
+ p->param != NULL) {
+ spa_list_remove(&p->link);
+ free(p);
+ }
+ }
+ }
+ spa_list_consume(p, pending_list, link) {
+ spa_list_remove(&p->link);
+ if (p->param == NULL) {
+ pw_param_clear(param_list, p->id);
+ free(p);
+ } else {
+ spa_list_append(param_list, &p->link);
+ }
+ }
+}
+
+static inline struct spa_param_info *pw_param_info_find(struct spa_param_info info[],
+ uint32_t n_info, uint32_t id)
+{
+ uint32_t i;
+ for (i = 0; i < n_info; i++) {
+ if (info[i].id == id)
+ return &info[i];
+ }
+ return NULL;
+}
+
+#define pw_protocol_emit_destroy(p) spa_hook_list_call(&(p)->listener_list, struct pw_protocol_events, destroy, 0)
+
+struct pw_protocol {
+ struct spa_list link; /**< link in context protocol_list */
+ struct pw_context *context; /**< context for this protocol */
+
+ char *name; /**< type name of the protocol */
+
+ struct spa_list marshal_list; /**< list of marshallers for supported interfaces */
+ struct spa_list client_list; /**< list of current clients */
+ struct spa_list server_list; /**< list of current servers */
+ struct spa_hook_list listener_list; /**< event listeners */
+
+ const struct pw_protocol_implementation *implementation; /**< implementation of the protocol */
+
+ const void *extension; /**< extension API */
+
+ void *user_data; /**< user data for the implementation */
+};
+
+/** the permission function. It returns the allowed access permissions for \a global
+ * for \a client */
+typedef uint32_t (*pw_permission_func_t) (struct pw_global *global,
+ struct pw_impl_client *client, void *data);
+
+#define pw_impl_client_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_impl_client_events, m, v, ##__VA_ARGS__)
+
+#define pw_impl_client_emit_destroy(o) pw_impl_client_emit(o, destroy, 0)
+#define pw_impl_client_emit_free(o) pw_impl_client_emit(o, free, 0)
+#define pw_impl_client_emit_initialized(o) pw_impl_client_emit(o, initialized, 0)
+#define pw_impl_client_emit_info_changed(o,i) pw_impl_client_emit(o, info_changed, 0, i)
+#define pw_impl_client_emit_resource_added(o,r) pw_impl_client_emit(o, resource_added, 0, r)
+#define pw_impl_client_emit_resource_impl(o,r) pw_impl_client_emit(o, resource_impl, 0, r)
+#define pw_impl_client_emit_resource_removed(o,r) pw_impl_client_emit(o, resource_removed, 0, r)
+#define pw_impl_client_emit_busy_changed(o,b) pw_impl_client_emit(o, busy_changed, 0, b)
+
+enum spa_node0_event {
+ SPA_NODE0_EVENT_START = SPA_TYPE_VENDOR_PipeWire,
+ SPA_NODE0_EVENT_RequestClockUpdate,
+};
+
+enum spa_node0_command {
+ SPA_NODE0_COMMAND_START = SPA_TYPE_VENDOR_PipeWire,
+ SPA_NODE0_COMMAND_ClockUpdate,
+};
+
+struct protocol_compat_v2 {
+ /* v2 typemap */
+ struct pw_map types;
+ unsigned int send_types:1;
+};
+
+#define pw_impl_core_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct pw_impl_core_events, m, v, ##__VA_ARGS__)
+
+#define pw_impl_core_emit_destroy(s) pw_impl_core_emit(s, destroy, 0)
+#define pw_impl_core_emit_free(s) pw_impl_core_emit(s, free, 0)
+#define pw_impl_core_emit_initialized(s) pw_impl_core_emit(s, initialized, 0)
+
+struct pw_impl_core {
+ struct pw_context *context;
+ struct spa_list link; /**< link in context object core_impl list */
+ struct pw_global *global; /**< global object created for this core */
+ struct spa_hook global_listener;
+
+ struct pw_properties *properties; /**< core properties */
+ struct pw_core_info info; /**< core info */
+
+ struct spa_hook_list listener_list;
+ void *user_data; /**< extra user data */
+
+ unsigned int registered:1;
+};
+
+#define pw_impl_metadata_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct pw_impl_metadata_events, m, v, ##__VA_ARGS__)
+
+#define pw_impl_metadata_emit_destroy(s) pw_impl_metadata_emit(s, destroy, 0)
+#define pw_impl_metadata_emit_free(s) pw_impl_metadata_emit(s, free, 0)
+#define pw_impl_metadata_emit_property(s, ...) pw_impl_metadata_emit(s, property, 0, __VA_ARGS__)
+
+struct pw_impl_metadata {
+ struct pw_context *context; /**< the context */
+ struct spa_list link; /**< link in context metadata_list */
+ struct pw_global *global; /**< global for this metadata */
+ struct spa_hook global_listener;
+
+ struct pw_properties *properties; /**< properties of the metadata */
+
+ struct pw_metadata *metadata;
+ struct spa_hook metadata_listener;
+
+ struct spa_hook_list listener_list; /**< event listeners */
+ void *user_data;
+
+ unsigned int registered:1;
+};
+
+struct pw_impl_client {
+ struct pw_impl_core *core; /**< core object */
+ struct pw_context *context; /**< context object */
+
+ struct spa_list link; /**< link in context object client list */
+ struct pw_global *global; /**< global object created for this client */
+ struct spa_hook global_listener;
+
+ pw_permission_func_t permission_func; /**< get permissions of an object */
+ void *permission_data; /**< data passed to permission function */
+
+ struct pw_properties *properties; /**< Client properties */
+
+ struct pw_client_info info; /**< client info */
+
+ struct pw_mempool *pool; /**< client mempool */
+ struct pw_resource *core_resource; /**< core resource object */
+ struct pw_resource *client_resource; /**< client resource object */
+
+ struct pw_map objects; /**< list of resource objects */
+
+ struct spa_hook_list listener_list;
+
+ struct pw_protocol *protocol; /**< protocol in use */
+ int recv_seq; /**< last received sequence number */
+ int send_seq; /**< last sender sequence number */
+ uint64_t recv_generation; /**< last received registry generation */
+ uint64_t sent_generation; /**< last sent registry generation */
+
+ void *user_data; /**< extra user data */
+
+ struct ucred ucred; /**< ucred information */
+ unsigned int registered:1;
+ unsigned int ucred_valid:1; /**< if the ucred member is valid */
+ unsigned int busy:1;
+ unsigned int destroyed:1;
+
+ int refcount;
+
+ /* v2 compatibility data */
+ void *compat_v2;
+};
+
+#define pw_global_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_global_events, m, v, ##__VA_ARGS__)
+
+#define pw_global_emit_registering(g) pw_global_emit(g, registering, 0)
+#define pw_global_emit_destroy(g) pw_global_emit(g, destroy, 0)
+#define pw_global_emit_free(g) pw_global_emit(g, free, 0)
+#define pw_global_emit_permissions_changed(g,...) pw_global_emit(g, permissions_changed, 0, __VA_ARGS__)
+
+struct pw_global {
+ struct pw_context *context; /**< the context */
+
+ struct spa_list link; /**< link in context list of globals */
+ uint32_t id; /**< server id of the object */
+
+ struct pw_properties *properties; /**< properties of the global */
+
+ struct spa_hook_list listener_list;
+
+ const char *type; /**< type of interface */
+ uint32_t version; /**< version of interface */
+
+ pw_global_bind_func_t func; /**< bind function */
+ void *object; /**< object associated with the interface */
+ uint64_t serial; /**< increasing serial number */
+ uint64_t generation; /**< registry generation number */
+
+ struct spa_list resource_list; /**< The list of resources of this global */
+
+ unsigned int registered:1;
+ unsigned int destroyed:1;
+};
+
+#define pw_core_resource(r,m,v,...) pw_resource_call(r, struct pw_core_events, m, v, ##__VA_ARGS__)
+#define pw_core_resource_info(r,...) pw_core_resource(r,info,0,__VA_ARGS__)
+#define pw_core_resource_done(r,...) pw_core_resource(r,done,0,__VA_ARGS__)
+#define pw_core_resource_ping(r,...) pw_core_resource(r,ping,0,__VA_ARGS__)
+#define pw_core_resource_error(r,...) pw_core_resource(r,error,0,__VA_ARGS__)
+#define pw_core_resource_remove_id(r,...) pw_core_resource(r,remove_id,0,__VA_ARGS__)
+#define pw_core_resource_bound_id(r,...) pw_core_resource(r,bound_id,0,__VA_ARGS__)
+#define pw_core_resource_add_mem(r,...) pw_core_resource(r,add_mem,0,__VA_ARGS__)
+#define pw_core_resource_remove_mem(r,...) pw_core_resource(r,remove_mem,0,__VA_ARGS__)
+
+static inline SPA_PRINTF_FUNC(5,0) void
+pw_core_resource_errorv(struct pw_resource *resource, uint32_t id, int seq,
+ int res, const char *message, va_list args)
+{
+ char buffer[1024];
+ vsnprintf(buffer, sizeof(buffer), message, args);
+ buffer[1023] = '\0';
+ pw_log_debug("resource %p: id:%d seq:%d res:%d (%s) msg:\"%s\"",
+ resource, id, seq, res, spa_strerror(res), buffer);
+ if (resource)
+ pw_core_resource_error(resource, id, seq, res, buffer);
+ else
+ pw_log_error("id:%d seq:%d res:%d (%s) msg:\"%s\"",
+ id, seq, res, spa_strerror(res), buffer);
+}
+
+static inline SPA_PRINTF_FUNC(5,6) void
+pw_core_resource_errorf(struct pw_resource *resource, uint32_t id, int seq,
+ int res, const char *message, ...)
+{
+ va_list args;
+ va_start(args, message);
+ pw_core_resource_errorv(resource, id, seq, res, message, args);
+ va_end(args);
+}
+
+#define pw_context_driver_emit(c,m,v,...) spa_hook_list_call_simple(&c->driver_listener_list, struct pw_context_driver_events, m, v, ##__VA_ARGS__)
+#define pw_context_driver_emit_start(c,n) pw_context_driver_emit(c, start, 0, n)
+#define pw_context_driver_emit_xrun(c,n) pw_context_driver_emit(c, xrun, 0, n)
+#define pw_context_driver_emit_incomplete(c,n) pw_context_driver_emit(c, incomplete, 0, n)
+#define pw_context_driver_emit_timeout(c,n) pw_context_driver_emit(c, timeout, 0, n)
+#define pw_context_driver_emit_drained(c,n) pw_context_driver_emit(c, drained, 0, n)
+#define pw_context_driver_emit_complete(c,n) pw_context_driver_emit(c, complete, 0, n)
+
+struct pw_context_driver_events {
+#define PW_VERSION_CONTEXT_DRIVER_EVENTS 0
+ uint32_t version;
+
+ /** The driver graph is started */
+ void (*start) (void *data, struct pw_impl_node *node);
+ /** The driver under/overruns */
+ void (*xrun) (void *data, struct pw_impl_node *node);
+ /** The driver could not complete the graph */
+ void (*incomplete) (void *data, struct pw_impl_node *node);
+ /** The driver got a sync timeout */
+ void (*timeout) (void *data, struct pw_impl_node *node);
+ /** a node drained */
+ void (*drained) (void *data, struct pw_impl_node *node);
+ /** The driver completed the graph */
+ void (*complete) (void *data, struct pw_impl_node *node);
+};
+
+#define pw_registry_resource(r,m,v,...) pw_resource_call(r, struct pw_registry_events,m,v,##__VA_ARGS__)
+#define pw_registry_resource_global(r,...) pw_registry_resource(r,global,0,__VA_ARGS__)
+#define pw_registry_resource_global_remove(r,...) pw_registry_resource(r,global_remove,0,__VA_ARGS__)
+
+#define pw_context_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_context_events, m, v, ##__VA_ARGS__)
+#define pw_context_emit_destroy(c) pw_context_emit(c, destroy, 0)
+#define pw_context_emit_free(c) pw_context_emit(c, free, 0)
+#define pw_context_emit_info_changed(c,i) pw_context_emit(c, info_changed, 0, i)
+#define pw_context_emit_check_access(c,cl) pw_context_emit(c, check_access, 0, cl)
+#define pw_context_emit_global_added(c,g) pw_context_emit(c, global_added, 0, g)
+#define pw_context_emit_global_removed(c,g) pw_context_emit(c, global_removed, 0, g)
+
+struct pw_context {
+ struct pw_impl_core *core; /**< core object */
+
+ struct pw_properties *conf; /**< configuration of the context */
+ struct pw_properties *properties; /**< properties of the context */
+
+ struct settings defaults; /**< default parameters */
+ struct settings settings; /**< current parameters */
+
+ void *settings_impl; /**< settings metadata */
+
+ struct pw_mempool *pool; /**< global memory pool */
+
+ uint64_t stamp;
+ uint64_t serial;
+ uint64_t generation; /**< registry generation number */
+ struct pw_map globals; /**< map of globals */
+
+ struct spa_list core_impl_list; /**< list of core_imp */
+ struct spa_list protocol_list; /**< list of protocols */
+ struct spa_list core_list; /**< list of core connections */
+ struct spa_list registry_resource_list; /**< list of registry resources */
+ struct spa_list module_list; /**< list of modules */
+ struct spa_list device_list; /**< list of devices */
+ struct spa_list global_list; /**< list of globals */
+ struct spa_list client_list; /**< list of clients */
+ struct spa_list node_list; /**< list of nodes */
+ struct spa_list factory_list; /**< list of factories */
+ struct spa_list metadata_list; /**< list of metadata */
+ struct spa_list link_list; /**< list of links */
+ struct spa_list control_list[2]; /**< list of controls, indexed by direction */
+ struct spa_list export_list; /**< list of export types */
+ struct spa_list driver_list; /**< list of driver nodes */
+
+ struct spa_hook_list driver_listener_list;
+ struct spa_hook_list listener_list;
+
+ struct spa_thread_utils *thread_utils;
+ struct pw_loop *main_loop; /**< main loop for control */
+ struct pw_loop *data_loop; /**< data loop for data passing */
+ struct pw_data_loop *data_loop_impl;
+ struct spa_system *data_system; /**< data system for data passing */
+ struct pw_work_queue *work_queue; /**< work queue */
+
+ struct spa_support support[16]; /**< support for spa plugins */
+ uint32_t n_support; /**< number of support items */
+ struct pw_array factory_lib; /**< mapping of factory_name regexp to library */
+
+ struct pw_array objects; /**< objects */
+
+ struct pw_impl_client *current_client; /**< client currently executing code in mainloop */
+
+ long sc_pagesize;
+ unsigned int freewheeling:1;
+
+ void *user_data; /**< extra user data */
+};
+
+#define pw_data_loop_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_data_loop_events, m, v, ##__VA_ARGS__)
+#define pw_data_loop_emit_destroy(o) pw_data_loop_emit(o, destroy, 0)
+
+struct pw_data_loop {
+ struct pw_loop *loop;
+
+ struct spa_hook_list listener_list;
+
+ struct spa_thread_utils *thread_utils;
+
+ pthread_t thread;
+ unsigned int cancel:1;
+ unsigned int created:1;
+ unsigned int running:1;
+};
+
+#define pw_main_loop_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_main_loop_events, m, v, ##__VA_ARGS__)
+#define pw_main_loop_emit_destroy(o) pw_main_loop_emit(o, destroy, 0)
+
+struct pw_main_loop {
+ struct pw_loop *loop;
+
+ struct spa_hook_list listener_list;
+
+ unsigned int created:1;
+ unsigned int running:1;
+};
+
+#define pw_impl_device_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_impl_device_events, m, v, ##__VA_ARGS__)
+#define pw_impl_device_emit_destroy(m) pw_impl_device_emit(m, destroy, 0)
+#define pw_impl_device_emit_free(m) pw_impl_device_emit(m, free, 0)
+#define pw_impl_device_emit_initialized(m) pw_impl_device_emit(m, initialized, 0)
+#define pw_impl_device_emit_info_changed(n,i) pw_impl_device_emit(n, info_changed, 0, i)
+
+struct pw_impl_device {
+ struct pw_context *context; /**< the context object */
+ struct spa_list link; /**< link in the context device_list */
+ struct pw_global *global; /**< global object for this device */
+ struct spa_hook global_listener;
+
+ struct pw_properties *properties; /**< properties of the device */
+ struct pw_device_info info; /**< introspectable device info */
+ struct spa_param_info params[MAX_PARAMS];
+
+ char *name; /**< device name for debug */
+
+ struct spa_device *device; /**< device implementation */
+ struct spa_hook listener;
+ struct spa_hook_list listener_list;
+
+ struct spa_list object_list;
+
+ void *user_data; /**< device user_data */
+
+ unsigned int registered:1;
+};
+
+#define pw_impl_module_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_impl_module_events, m, v, ##__VA_ARGS__)
+#define pw_impl_module_emit_destroy(m) pw_impl_module_emit(m, destroy, 0)
+#define pw_impl_module_emit_free(m) pw_impl_module_emit(m, free, 0)
+#define pw_impl_module_emit_initialized(m) pw_impl_module_emit(m, initialized, 0)
+#define pw_impl_module_emit_registered(m) pw_impl_module_emit(m, registered, 0)
+
+struct pw_impl_module {
+ struct pw_context *context; /**< the context object */
+ struct spa_list link; /**< link in the context module_list */
+ struct pw_global *global; /**< global object for this module */
+ struct spa_hook global_listener;
+
+ struct pw_properties *properties; /**< properties of the module */
+ struct pw_module_info info; /**< introspectable module info */
+
+ struct spa_hook_list listener_list;
+
+ void *user_data; /**< module user_data */
+};
+
+struct pw_node_activation_state {
+ int status; /**< current status, the result of spa_node_process() */
+ int32_t required; /**< required number of signals */
+ int32_t pending; /**< number of pending signals */
+};
+
+static inline void pw_node_activation_state_reset(struct pw_node_activation_state *state)
+{
+ state->pending = state->required;
+}
+
+#define pw_node_activation_state_dec(s,c) (__atomic_sub_fetch(&(s)->pending, c, __ATOMIC_SEQ_CST) == 0)
+
+struct pw_node_target {
+ struct spa_list link;
+ struct pw_impl_node *node;
+ struct pw_node_activation *activation;
+ int (*signal_func) (void *data);
+ void *data;
+ unsigned int active:1;
+};
+
+struct pw_node_activation {
+#define PW_NODE_ACTIVATION_NOT_TRIGGERED 0
+#define PW_NODE_ACTIVATION_TRIGGERED 1
+#define PW_NODE_ACTIVATION_AWAKE 2
+#define PW_NODE_ACTIVATION_FINISHED 3
+ uint32_t status;
+
+ unsigned int version:1;
+ unsigned int pending_sync:1; /* a sync is pending */
+ unsigned int pending_new_pos:1; /* a new position is pending */
+
+ 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;
+
+ /* updates */
+ struct spa_io_segment reposition; /* reposition info, used when driver reposition_owner
+ * has this node id */
+ struct spa_io_segment segment; /* update for the extra segment info fields.
+ * used when driver segment_owner has this node id */
+
+ /* for drivers, shared with all nodes */
+ uint32_t segment_owner[32]; /* id of owners for each segment info struct.
+ * nodes that want to update segment info need to
+ * CAS their node id in this array. */
+ struct spa_io_position position; /* contains current position and segment info.
+ * extra info is updated by nodes that have set
+ * themselves as owner in the segment structs */
+
+ uint64_t sync_timeout; /* sync timeout in nanoseconds
+ * position goes to RUNNING without waiting any
+ * longer for sync clients. */
+ uint64_t sync_left; /* number of cycles before timeout */
+
+
+ float cpu_load[3]; /* averaged over short, medium, long time */
+ uint32_t xrun_count; /* number of xruns */
+ uint64_t xrun_time; /* time of last xrun in microseconds */
+ uint64_t xrun_delay; /* delay of last xrun in microseconds */
+ uint64_t max_delay; /* max of all xruns in microseconds */
+
+#define PW_NODE_ACTIVATION_COMMAND_NONE 0
+#define PW_NODE_ACTIVATION_COMMAND_START 1
+#define PW_NODE_ACTIVATION_COMMAND_STOP 2
+ uint32_t command; /* next command */
+ uint32_t reposition_owner; /* owner id with new reposition info, last one
+ * to update wins */
+};
+
+#define ATOMIC_CAS(v,ov,nv) \
+({ \
+ __typeof__(v) __ov = (ov); \
+ __atomic_compare_exchange_n(&(v), &__ov, (nv), \
+ 0, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST); \
+})
+
+#define ATOMIC_DEC(s) __atomic_sub_fetch(&(s), 1, __ATOMIC_SEQ_CST)
+#define ATOMIC_INC(s) __atomic_add_fetch(&(s), 1, __ATOMIC_SEQ_CST)
+#define ATOMIC_LOAD(s) __atomic_load_n(&(s), __ATOMIC_SEQ_CST)
+#define ATOMIC_STORE(s,v) __atomic_store_n(&(s), (v), __ATOMIC_SEQ_CST)
+#define ATOMIC_XCHG(s,v) __atomic_exchange_n(&(s), (v), __ATOMIC_SEQ_CST)
+
+#define SEQ_WRITE(s) ATOMIC_INC(s)
+#define SEQ_WRITE_SUCCESS(s1,s2) ((s1) + 1 == (s2) && ((s2) & 1) == 0)
+
+#define SEQ_READ(s) ATOMIC_LOAD(s)
+#define SEQ_READ_SUCCESS(s1,s2) ((s1) == (s2) && ((s2) & 1) == 0)
+
+#define pw_impl_node_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_impl_node_events, m, v, ##__VA_ARGS__)
+#define pw_impl_node_emit_destroy(n) pw_impl_node_emit(n, destroy, 0)
+#define pw_impl_node_emit_free(n) pw_impl_node_emit(n, free, 0)
+#define pw_impl_node_emit_initialized(n) pw_impl_node_emit(n, initialized, 0)
+#define pw_impl_node_emit_port_init(n,p) pw_impl_node_emit(n, port_init, 0, p)
+#define pw_impl_node_emit_port_added(n,p) pw_impl_node_emit(n, port_added, 0, p)
+#define pw_impl_node_emit_port_removed(n,p) pw_impl_node_emit(n, port_removed, 0, p)
+#define pw_impl_node_emit_info_changed(n,i) pw_impl_node_emit(n, info_changed, 0, i)
+#define pw_impl_node_emit_port_info_changed(n,p,i) pw_impl_node_emit(n, port_info_changed, 0, p, i)
+#define pw_impl_node_emit_active_changed(n,a) pw_impl_node_emit(n, active_changed, 0, a)
+#define pw_impl_node_emit_state_request(n,s) pw_impl_node_emit(n, state_request, 0, s)
+#define pw_impl_node_emit_state_changed(n,o,s,e) pw_impl_node_emit(n, state_changed, 0, o, s, e)
+#define pw_impl_node_emit_async_complete(n,s,r) pw_impl_node_emit(n, async_complete, 0, s, r)
+#define pw_impl_node_emit_result(n,s,r,t,result) pw_impl_node_emit(n, result, 0, s, r, t, result)
+#define pw_impl_node_emit_event(n,e) pw_impl_node_emit(n, event, 0, e)
+#define pw_impl_node_emit_driver_changed(n,o,d) pw_impl_node_emit(n, driver_changed, 0, o, d)
+#define pw_impl_node_emit_peer_added(n,p) pw_impl_node_emit(n, peer_added, 0, p)
+#define pw_impl_node_emit_peer_removed(n,p) pw_impl_node_emit(n, peer_removed, 0, p)
+
+struct pw_impl_node {
+ struct pw_context *context; /**< context object */
+ struct spa_list link; /**< link in context node_list */
+ struct pw_global *global; /**< global for this node */
+ struct spa_hook global_listener;
+
+ struct pw_properties *properties; /**< properties of the node */
+
+ struct pw_node_info info; /**< introspectable node info */
+ struct spa_param_info params[MAX_PARAMS];
+
+ char *name; /** for debug */
+
+ uint32_t priority_driver; /** priority for being driver */
+ char group[128]; /** group to schedule this node in */
+ uint64_t spa_flags;
+
+ unsigned int registered:1;
+ unsigned int active:1; /**< if the node is active */
+ unsigned int live:1; /**< if the node is live */
+ unsigned int driver:1; /**< if the node can drive the graph */
+ unsigned int exported:1; /**< if the node is exported */
+ unsigned int remote:1; /**< if the node is implemented remotely */
+ unsigned int driving:1; /**< a driving node is one of the driver nodes that
+ * is selected to drive the graph */
+ unsigned int visited:1; /**< for sorting */
+ unsigned int want_driver:1; /**< this node wants to be assigned to a driver */
+ unsigned int passive:1; /**< driver graph only has passive links */
+ unsigned int freewheel:1; /**< if this is the freewheel driver */
+ unsigned int loopchecked:1; /**< for feedback loop checking */
+ unsigned int always_process:1; /**< this node wants to always be processing, even when idle */
+ unsigned int lock_quantum:1; /**< don't change graph quantum */
+ unsigned int lock_rate:1; /**< don't change graph rate */
+ unsigned int transport_sync:1; /**< supports transport sync */
+ unsigned int current_pending:1; /**< a quantum/rate update is pending */
+ unsigned int moved:1; /**< the node was moved drivers */
+ unsigned int added:1; /**< the node was add to graph */
+ unsigned int pause_on_idle:1; /**< Pause processing when IDLE */
+ unsigned int suspend_on_idle:1;
+ unsigned int reconfigure:1;
+
+ uint32_t port_user_data_size; /**< extra size for port user data */
+
+ struct spa_list driver_link;
+ struct pw_impl_node *driver_node;
+ struct spa_list follower_list;
+ struct spa_list follower_link;
+
+ struct spa_list sort_link; /**< link used to sort nodes */
+
+ struct spa_node *node; /**< SPA node implementation */
+ struct spa_hook listener;
+
+ struct spa_list input_ports; /**< list of input ports */
+ struct pw_map input_port_map; /**< map from port_id to port */
+ struct spa_list output_ports; /**< list of output ports */
+ struct pw_map output_port_map; /**< map from port_id to port */
+
+ struct spa_hook_list listener_list;
+
+ struct pw_loop *data_loop; /**< the data loop for this node */
+
+ struct spa_fraction latency; /**< requested latency */
+ struct spa_fraction max_latency; /**< maximum latency */
+ struct spa_fraction rate; /**< requested rate */
+ uint32_t force_quantum; /**< forced quantum */
+ uint32_t force_rate; /**< forced rate */
+ uint32_t stamp; /**< stamp of last update */
+ struct spa_source source; /**< source to remotely trigger this node */
+ struct pw_memblock *activation;
+ struct {
+ struct spa_io_clock *clock; /**< io area of the clock or NULL */
+ struct spa_io_position *position;
+ struct pw_node_activation *activation;
+
+ struct spa_list target_list; /* list of targets to signal after
+ * this node */
+ struct pw_node_target driver_target; /* driver target that we signal */
+ struct spa_list input_mix; /* our input ports (and mixers) */
+ struct spa_list output_mix; /* output ports (and mixers) */
+
+ struct pw_node_target target; /* our target that is signaled by the
+ driver */
+ struct spa_list driver_link; /* our link in driver */
+
+ struct ratelimit rate_limit;
+ } rt;
+ struct spa_fraction current_rate;
+ uint64_t current_quantum;
+
+ void *user_data; /**< extra user data */
+};
+
+struct pw_impl_port_mix {
+ struct spa_list link;
+ struct spa_list rt_link;
+ struct pw_impl_port *p;
+ struct {
+ enum spa_direction direction;
+ uint32_t port_id;
+ } port;
+ struct spa_io_buffers *io;
+ uint32_t id;
+ uint32_t peer_id;
+ unsigned int have_buffers:1;
+};
+
+struct pw_impl_port_implementation {
+#define PW_VERSION_PORT_IMPLEMENTATION 0
+ uint32_t version;
+
+ int (*init_mix) (void *data, struct pw_impl_port_mix *mix);
+ int (*release_mix) (void *data, struct pw_impl_port_mix *mix);
+};
+
+#define pw_impl_port_call(p,m,v,...) \
+({ \
+ int _res = 0; \
+ spa_callbacks_call_res(&(p)->impl, \
+ struct pw_impl_port_implementation, \
+ _res, m, v, ## __VA_ARGS__); \
+ _res; \
+})
+
+#define pw_impl_port_call_init_mix(p,m) pw_impl_port_call(p,init_mix,0,m)
+#define pw_impl_port_call_release_mix(p,m) pw_impl_port_call(p,release_mix,0,m)
+
+#define pw_impl_port_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_impl_port_events, m, v, ##__VA_ARGS__)
+#define pw_impl_port_emit_destroy(p) pw_impl_port_emit(p, destroy, 0)
+#define pw_impl_port_emit_free(p) pw_impl_port_emit(p, free, 0)
+#define pw_impl_port_emit_initialized(p) pw_impl_port_emit(p, initialized, 0)
+#define pw_impl_port_emit_info_changed(p,i) pw_impl_port_emit(p, info_changed, 0, i)
+#define pw_impl_port_emit_link_added(p,l) pw_impl_port_emit(p, link_added, 0, l)
+#define pw_impl_port_emit_link_removed(p,l) pw_impl_port_emit(p, link_removed, 0, l)
+#define pw_impl_port_emit_state_changed(p,o,s,e) pw_impl_port_emit(p, state_changed, 0, o, s, e)
+#define pw_impl_port_emit_control_added(p,c) pw_impl_port_emit(p, control_added, 0, c)
+#define pw_impl_port_emit_control_removed(p,c) pw_impl_port_emit(p, control_removed, 0, c)
+#define pw_impl_port_emit_param_changed(p,i) pw_impl_port_emit(p, param_changed, 1, i)
+#define pw_impl_port_emit_latency_changed(p) pw_impl_port_emit(p, latency_changed, 2)
+
+#define PW_IMPL_PORT_IS_CONTROL(port) SPA_FLAG_MASK((port)->flags, \
+ PW_IMPL_PORT_FLAG_BUFFERS|PW_IMPL_PORT_FLAG_CONTROL,\
+ PW_IMPL_PORT_FLAG_CONTROL)
+struct pw_impl_port {
+ struct spa_list link; /**< link in node port_list */
+
+ struct pw_impl_node *node; /**< owner node */
+ struct pw_global *global; /**< global for this port */
+ struct spa_hook global_listener;
+
+#define PW_IMPL_PORT_FLAG_TO_REMOVE (1<<0) /**< if the port should be removed from the
+ * implementation when destroyed */
+#define PW_IMPL_PORT_FLAG_BUFFERS (1<<1) /**< port has data */
+#define PW_IMPL_PORT_FLAG_CONTROL (1<<2) /**< port has control */
+#define PW_IMPL_PORT_FLAG_NO_MIXER (1<<3) /**< don't try to add mixer to port */
+ uint32_t flags;
+ uint64_t spa_flags;
+
+ enum pw_direction direction; /**< port direction */
+ uint32_t port_id; /**< port id */
+
+ enum pw_impl_port_state state; /**< state of the port */
+ const char *error; /**< error state */
+
+ struct pw_properties *properties; /**< properties of the port */
+ struct pw_port_info info;
+ struct spa_param_info params[MAX_PARAMS];
+
+ struct pw_buffers buffers; /**< buffers managed by this port, only on
+ * output ports, shared with all links */
+
+ struct spa_list links; /**< list of \ref pw_impl_link */
+
+ struct spa_list control_list[2];/**< list of \ref pw_control indexed by direction */
+
+ struct spa_hook_list listener_list;
+
+ struct spa_callbacks impl;
+
+ struct spa_node *mix; /**< port buffer mix/split */
+#define PW_IMPL_PORT_MIX_FLAG_MULTI (1<<0) /**< multi input or output */
+#define PW_IMPL_PORT_MIX_FLAG_MIX_ONLY (1<<1) /**< only negotiate mix ports */
+#define PW_IMPL_PORT_MIX_FLAG_NEGOTIATE (1<<2) /**< negotiate buffers */
+ uint32_t mix_flags; /**< flags for the mixing */
+ struct spa_handle *mix_handle; /**< mix plugin handle */
+ struct pw_buffers mix_buffers; /**< buffers between mixer and node */
+
+ struct spa_list mix_list; /**< list of \ref pw_impl_port_mix */
+ struct pw_map mix_port_map; /**< map from port_id from mixer */
+ uint32_t n_mix;
+
+ struct {
+ struct spa_io_buffers io; /**< io area of the port */
+ struct spa_io_clock clock; /**< io area of the clock */
+ struct spa_list mix_list;
+ struct spa_list node_link;
+ } rt; /**< data only accessed from the data thread */
+ unsigned int added:1;
+ unsigned int destroying:1;
+ int busy_count;
+
+ struct spa_latency_info latency[2]; /**< latencies */
+ unsigned int have_latency_param:1;
+
+ void *owner_data; /**< extra owner data */
+ void *user_data; /**< extra user data */
+};
+
+struct pw_control_link {
+ struct spa_list out_link;
+ struct spa_list in_link;
+ struct pw_control *output;
+ struct pw_control *input;
+ uint32_t out_port;
+ uint32_t in_port;
+ unsigned int valid:1;
+};
+
+#define pw_impl_link_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_impl_link_events, m, v, ##__VA_ARGS__)
+#define pw_impl_link_emit_destroy(l) pw_impl_link_emit(l, destroy, 0)
+#define pw_impl_link_emit_free(l) pw_impl_link_emit(l, free, 0)
+#define pw_impl_link_emit_initialized(l) pw_impl_link_emit(l, initialized, 0)
+#define pw_impl_link_emit_info_changed(l,i) pw_impl_link_emit(l, info_changed, 0, i)
+#define pw_impl_link_emit_state_changed(l,...) pw_impl_link_emit(l, state_changed, 0, __VA_ARGS__)
+#define pw_impl_link_emit_port_unlinked(l,p) pw_impl_link_emit(l, port_unlinked, 0, p)
+
+struct pw_impl_link {
+ struct pw_context *context; /**< context object */
+ struct spa_list link; /**< link in context link_list */
+ struct pw_global *global; /**< global for this link */
+ struct spa_hook global_listener;
+
+ char *name;
+
+ struct pw_link_info info; /**< introspectable link info */
+ struct pw_properties *properties; /**< extra link properties */
+
+ struct spa_io_buffers *io; /**< link io area */
+
+ struct pw_impl_port *output; /**< output port */
+ struct spa_list output_link; /**< link in output port links */
+ struct pw_impl_port *input; /**< input port */
+ struct spa_list input_link; /**< link in input port links */
+
+ struct spa_hook_list listener_list;
+
+ struct pw_control_link control;
+ struct pw_control_link notify;
+
+ struct {
+ struct pw_impl_port_mix out_mix; /**< port added to the output mixer */
+ struct pw_impl_port_mix in_mix; /**< port added to the input mixer */
+ struct pw_node_target target; /**< target to trigger the input node */
+ } rt;
+
+ void *user_data;
+
+ unsigned int registered:1;
+ unsigned int feedback:1;
+ unsigned int preparing:1;
+ unsigned int prepared:1;
+ unsigned int passive:1;
+};
+
+#define pw_resource_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_resource_events, m, v, ##__VA_ARGS__)
+
+#define pw_resource_emit_destroy(o) pw_resource_emit(o, destroy, 0)
+#define pw_resource_emit_pong(o,s) pw_resource_emit(o, pong, 0, s)
+#define pw_resource_emit_error(o,s,r,m) pw_resource_emit(o, error, 0, s, r, m)
+
+struct pw_resource {
+ struct spa_interface impl; /**< object implementation */
+
+ struct pw_context *context; /**< the context object */
+ struct pw_global *global; /**< global of resource */
+ struct spa_list link; /**< link in global resource_list */
+
+ struct pw_impl_client *client; /**< owner client */
+
+ uint32_t id; /**< per client unique id, index in client objects */
+ uint32_t permissions; /**< resource permissions */
+ const char *type; /**< type of the client interface */
+ uint32_t version; /**< version of the client interface */
+ uint32_t bound_id; /**< global id we are bound to */
+ int refcount;
+
+ unsigned int removed:1; /**< resource was removed from server */
+ unsigned int destroyed:1; /**< resource was destroyed */
+
+ struct spa_hook_list listener_list;
+ struct spa_hook_list object_listener_list;
+
+ const struct pw_protocol_marshal *marshal;
+
+ void *user_data; /**< extra user data */
+};
+
+#define pw_proxy_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_proxy_events, m, v, ##__VA_ARGS__)
+#define pw_proxy_emit_destroy(p) pw_proxy_emit(p, destroy, 0)
+#define pw_proxy_emit_bound(p,g) pw_proxy_emit(p, bound, 0, g)
+#define pw_proxy_emit_removed(p) pw_proxy_emit(p, removed, 0)
+#define pw_proxy_emit_done(p,s) pw_proxy_emit(p, done, 0, s)
+#define pw_proxy_emit_error(p,s,r,m) pw_proxy_emit(p, error, 0, s, r, m)
+
+struct pw_proxy {
+ struct spa_interface impl; /**< object implementation */
+
+ struct pw_core *core; /**< the owner core of this proxy */
+
+ uint32_t id; /**< client side id */
+ const char *type; /**< type of the interface */
+ uint32_t version; /**< client side version */
+ uint32_t bound_id; /**< global id we are bound to */
+ int refcount;
+ unsigned int zombie:1; /**< proxy is removed locally and waiting to
+ * be removed from server */
+ unsigned int removed:1; /**< proxy was removed from server */
+ unsigned int destroyed:1; /**< proxy was destroyed by client */
+ unsigned int in_map:1; /**< proxy is in core object map */
+
+ struct spa_hook_list listener_list;
+ struct spa_hook_list object_listener_list;
+
+ const struct pw_protocol_marshal *marshal; /**< protocol specific marshal functions */
+
+ void *user_data; /**< extra user data */
+};
+
+struct pw_core {
+ struct pw_proxy proxy;
+
+ struct pw_context *context; /**< context */
+ struct spa_list link; /**< link in context core_list */
+ struct pw_properties *properties; /**< extra properties */
+
+ struct pw_mempool *pool; /**< memory pool */
+ struct pw_core *core; /**< proxy for the core object */
+ struct spa_hook core_listener;
+ struct spa_hook proxy_core_listener;
+
+ struct pw_map objects; /**< map of client side proxy objects
+ * indexed with the client id */
+ struct pw_client *client; /**< proxy for the client object */
+
+ struct spa_list stream_list; /**< list of \ref pw_stream objects */
+ struct spa_list filter_list; /**< list of \ref pw_stream objects */
+
+ struct pw_protocol_client *conn; /**< the protocol client connection */
+ int recv_seq; /**< last received sequence number */
+ int send_seq; /**< last protocol result code */
+ uint64_t recv_generation; /**< last received registry generation */
+
+ unsigned int removed:1;
+ unsigned int destroyed:1;
+
+ void *user_data; /**< extra user data */
+};
+
+#define pw_stream_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct pw_stream_events, m, v, ##__VA_ARGS__)
+#define pw_stream_emit_destroy(s) pw_stream_emit(s, destroy, 0)
+#define pw_stream_emit_state_changed(s,o,n,e) pw_stream_emit(s, state_changed,0,o,n,e)
+#define pw_stream_emit_io_changed(s,i,a,t) pw_stream_emit(s, io_changed,0,i,a,t)
+#define pw_stream_emit_param_changed(s,i,p) pw_stream_emit(s, param_changed,0,i,p)
+#define pw_stream_emit_add_buffer(s,b) pw_stream_emit(s, add_buffer, 0, b)
+#define pw_stream_emit_remove_buffer(s,b) pw_stream_emit(s, remove_buffer, 0, b)
+#define pw_stream_emit_process(s) pw_stream_emit(s, process, 0)
+#define pw_stream_emit_drained(s) pw_stream_emit(s, drained,0)
+#define pw_stream_emit_control_info(s,i,c) pw_stream_emit(s, control_info, 0, i, c)
+#define pw_stream_emit_command(s,c) pw_stream_emit(s, command,1,c)
+#define pw_stream_emit_trigger_done(s) pw_stream_emit(s, trigger_done,2)
+
+
+struct pw_stream {
+ struct pw_core *core; /**< the owner core */
+ struct spa_hook core_listener;
+
+ struct spa_list link; /**< link in the core */
+
+ char *name; /**< the name of the stream */
+ struct pw_properties *properties; /**< properties of the stream */
+
+ uint32_t node_id; /**< node id for remote node, available from
+ * CONFIGURE state and higher */
+ enum pw_stream_state state; /**< stream state */
+ char *error; /**< error reason when state is in error */
+
+ struct spa_hook_list listener_list;
+
+ struct pw_proxy *proxy;
+ struct spa_hook proxy_listener;
+
+ struct spa_hook node_listener;
+
+ struct spa_list controls;
+};
+
+#define pw_filter_emit(s,m,v,...) spa_hook_list_call(&(s)->listener_list, struct pw_filter_events, m, v, ##__VA_ARGS__)
+#define pw_filter_emit_destroy(s) pw_filter_emit(s, destroy, 0)
+#define pw_filter_emit_state_changed(s,o,n,e) pw_filter_emit(s, state_changed,0,o,n,e)
+#define pw_filter_emit_io_changed(s,p,i,d,t) pw_filter_emit(s, io_changed,0,p,i,d,t)
+#define pw_filter_emit_param_changed(s,p,i,f) pw_filter_emit(s, param_changed,0,p,i,f)
+#define pw_filter_emit_add_buffer(s,p,b) pw_filter_emit(s, add_buffer, 0, p, b)
+#define pw_filter_emit_remove_buffer(s,p,b) pw_filter_emit(s, remove_buffer, 0, p, b)
+#define pw_filter_emit_process(s,p) pw_filter_emit(s, process, 0, p)
+#define pw_filter_emit_drained(s) pw_filter_emit(s, drained, 0)
+#define pw_filter_emit_command(s,c) pw_filter_emit(s, command, 1, c)
+
+
+struct pw_filter {
+ struct pw_core *core; /**< the owner core proxy */
+ struct spa_hook core_listener;
+
+ struct spa_list link; /**< link in the core proxy */
+
+ char *name; /**< the name of the filter */
+ struct pw_properties *properties; /**< properties of the filter */
+
+ uint32_t node_id; /**< node id for remote node, available from
+ * CONFIGURE state and higher */
+ enum pw_filter_state state; /**< filter state */
+ char *error; /**< error reason when state is in error */
+
+ struct spa_hook_list listener_list;
+
+ struct pw_proxy *proxy;
+ struct spa_hook proxy_listener;
+
+ struct spa_list controls;
+};
+
+#define pw_impl_factory_emit(s,m,v,...) spa_hook_list_call(&s->listener_list, struct pw_impl_factory_events, m, v, ##__VA_ARGS__)
+
+#define pw_impl_factory_emit_destroy(s) pw_impl_factory_emit(s, destroy, 0)
+#define pw_impl_factory_emit_free(s) pw_impl_factory_emit(s, free, 0)
+#define pw_impl_factory_emit_initialized(s) pw_impl_factory_emit(s, initialized, 0)
+
+struct pw_impl_factory {
+ struct pw_context *context; /**< the context */
+ struct spa_list link; /**< link in context factory_list */
+ struct pw_global *global; /**< global for this factory */
+ struct spa_hook global_listener;
+
+ struct pw_factory_info info; /**< introspectable factory info */
+ struct pw_properties *properties; /**< properties of the factory */
+
+ struct spa_hook_list listener_list; /**< event listeners */
+
+ struct spa_callbacks impl;
+
+ void *user_data;
+
+ unsigned int registered:1;
+};
+
+#define pw_control_emit(c,m,v,...) spa_hook_list_call(&c->listener_list, struct pw_control_events, m, v, ##__VA_ARGS__)
+#define pw_control_emit_destroy(c) pw_control_emit(c, destroy, 0)
+#define pw_control_emit_free(c) pw_control_emit(c, free, 0)
+#define pw_control_emit_linked(c,o) pw_control_emit(c, linked, 0, o)
+#define pw_control_emit_unlinked(c,o) pw_control_emit(c, unlinked, 0, o)
+
+struct pw_control {
+ struct spa_list link; /**< link in context control_list */
+ struct pw_context *context; /**< the context */
+
+ struct pw_impl_port *port; /**< owner port or NULL */
+ struct spa_list port_link; /**< link in port control_list */
+
+ enum spa_direction direction; /**< the direction */
+ struct spa_list links; /**< list of pw_control_link */
+
+ uint32_t id;
+ int32_t size;
+
+ struct spa_hook_list listener_list;
+
+ void *user_data;
+};
+
+/** Find a good format between 2 ports */
+int pw_context_find_format(struct pw_context *context,
+ struct pw_impl_port *output,
+ struct pw_impl_port *input,
+ struct pw_properties *props,
+ uint32_t n_format_filters,
+ struct spa_pod **format_filters,
+ struct spa_pod **format,
+ struct spa_pod_builder *builder,
+ char **error);
+
+int pw_context_debug_port_params(struct pw_context *context,
+ struct spa_node *node, enum spa_direction direction,
+ uint32_t port_id, uint32_t id, int err, const char *debug, ...);
+
+const struct pw_export_type *pw_context_find_export_type(struct pw_context *context, const char *type);
+
+int pw_proxy_init(struct pw_proxy *proxy, const char *type, uint32_t version);
+
+void pw_proxy_remove(struct pw_proxy *proxy);
+
+int pw_context_recalc_graph(struct pw_context *context, const char *reason);
+
+void pw_impl_port_update_info(struct pw_impl_port *port, const struct spa_port_info *info);
+
+int pw_impl_port_register(struct pw_impl_port *port,
+ struct pw_properties *properties);
+
+/** Get the user data of a port, the size of the memory was given \ref in pw_context_create_port */
+void * pw_impl_port_get_user_data(struct pw_impl_port *port);
+
+int pw_impl_port_set_mix(struct pw_impl_port *port, struct spa_node *node, uint32_t flags);
+
+int pw_impl_port_init_mix(struct pw_impl_port *port, struct pw_impl_port_mix *mix);
+int pw_impl_port_release_mix(struct pw_impl_port *port, struct pw_impl_port_mix *mix);
+
+void pw_impl_port_update_state(struct pw_impl_port *port, enum pw_impl_port_state state, int res, char *error);
+
+/** Unlink a port */
+void pw_impl_port_unlink(struct pw_impl_port *port);
+
+/** Destroy a port */
+void pw_impl_port_destroy(struct pw_impl_port *port);
+
+/** Iterate the params of the given port. The callback should return
+ * 1 to fetch the next item, 0 to stop iteration or <0 on error.
+ * The function returns 0 on success or the error returned by the callback. */
+int pw_impl_port_for_each_param(struct pw_impl_port *port,
+ int seq, uint32_t param_id,
+ uint32_t index, uint32_t max,
+ const struct spa_pod *filter,
+ int (*callback) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ struct spa_pod *param),
+ void *data);
+
+int pw_impl_port_for_each_filtered_param(struct pw_impl_port *in_port,
+ struct pw_impl_port *out_port,
+ int seq,
+ uint32_t in_param_id,
+ uint32_t out_param_id,
+ const struct spa_pod *filter,
+ int (*callback) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ struct spa_pod *param),
+ void *data);
+
+/** Iterate the links of the port. The callback should return
+ * 0 to fetch the next item, any other value stops the iteration and returns
+ * the value. When all callbacks return 0, this function returns 0 when all
+ * items are iterated. */
+int pw_impl_port_for_each_link(struct pw_impl_port *port,
+ int (*callback) (void *data, struct pw_impl_link *link),
+ void *data);
+
+/** Set a param on a port, use SPA_ID_INVALID for mix_id to set
+ * the param on all mix ports */
+int pw_impl_port_set_param(struct pw_impl_port *port,
+ uint32_t id, uint32_t flags, const struct spa_pod *param);
+
+/** Use buffers on a port */
+int pw_impl_port_use_buffers(struct pw_impl_port *port, struct pw_impl_port_mix *mix, uint32_t flags,
+ struct spa_buffer **buffers, uint32_t n_buffers);
+
+int pw_impl_port_recalc_latency(struct pw_impl_port *port);
+
+/** Change the state of the node */
+int pw_impl_node_set_state(struct pw_impl_node *node, enum pw_node_state state);
+
+
+int pw_impl_node_update_ports(struct pw_impl_node *node);
+
+int pw_impl_node_set_driver(struct pw_impl_node *node, struct pw_impl_node *driver);
+
+/** Prepare a link
+ * Starts the negotiation of formats and buffers on \a link */
+int pw_impl_link_prepare(struct pw_impl_link *link);
+/** starts streaming on a link */
+int pw_impl_link_activate(struct pw_impl_link *link);
+
+/** Deactivate a link */
+int pw_impl_link_deactivate(struct pw_impl_link *link);
+
+struct pw_control *
+pw_control_new(struct pw_context *context,
+ struct pw_impl_port *owner, /**< can be NULL */
+ uint32_t id, uint32_t size,
+ size_t user_data_size /**< extra user data */);
+
+int pw_control_add_link(struct pw_control *control, uint32_t cmix,
+ struct pw_control *other, uint32_t omix,
+ struct pw_control_link *link);
+
+int pw_control_remove_link(struct pw_control_link *link);
+
+void pw_control_destroy(struct pw_control *control);
+
+void pw_impl_client_unref(struct pw_impl_client *client);
+
+#define PW_LOG_OBJECT_POD (1<<0)
+void pw_log_log_object(enum spa_log_level level, const struct spa_log_topic *topic,
+ const char *file, int line, const char *func, uint32_t flags,
+ const void *object);
+
+#define pw_log_object(lev,t,fl,obj) \
+({ \
+ if (SPA_UNLIKELY(pw_log_topic_enabled(lev,t))) \
+ pw_log_log_object(lev,t,__FILE__,__LINE__, \
+ __func__,(fl),(obj)); \
+})
+
+#define pw_log_pod(lev,pod) pw_log_object(lev,PW_LOG_TOPIC_DEFAULT,PW_LOG_OBJECT_POD,pod)
+#define pw_log_format(lev,pod) pw_log_object(lev,PW_LOG_TOPIC_DEFAULT,PW_LOG_OBJECT_POD,pod)
+
+bool pw_log_is_default(void);
+
+void pw_log_init(void);
+void pw_log_deinit(void);
+
+void pw_settings_init(struct pw_context *context);
+int pw_settings_expose(struct pw_context *context);
+void pw_settings_clean(struct pw_context *context);
+
+pthread_attr_t *pw_thread_fill_attr(const struct spa_dict *props, pthread_attr_t *attr);
+
+/** \endcond */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_PRIVATE_H */
diff --git a/src/pipewire/properties.c b/src/pipewire/properties.c
new file mode 100644
index 0000000..be52a7c
--- /dev/null
+++ b/src/pipewire/properties.c
@@ -0,0 +1,733 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <spa/utils/json.h>
+#include <spa/utils/string.h>
+
+#include "pipewire/array.h"
+#include "pipewire/log.h"
+#include "pipewire/utils.h"
+#include "pipewire/properties.h"
+
+PW_LOG_TOPIC_EXTERN(log_properties);
+#define PW_LOG_TOPIC_DEFAULT log_properties
+
+/** \cond */
+struct properties {
+ struct pw_properties this;
+
+ struct pw_array items;
+};
+/** \endcond */
+
+static int add_func(struct pw_properties *this, char *key, char *value)
+{
+ struct spa_dict_item *item;
+ struct properties *impl = SPA_CONTAINER_OF(this, struct properties, this);
+
+ item = pw_array_add(&impl->items, sizeof(struct spa_dict_item));
+ if (item == NULL) {
+ free(key);
+ free(value);
+ return -errno;
+ }
+
+ item->key = key;
+ item->value = value;
+
+ this->dict.items = impl->items.data;
+ this->dict.n_items++;
+ return 0;
+}
+
+static void clear_item(struct spa_dict_item *item)
+{
+ free((char *) item->key);
+ free((char *) item->value);
+}
+
+static int find_index(const struct pw_properties *this, const char *key)
+{
+ const struct spa_dict_item *item;
+ item = spa_dict_lookup_item(&this->dict, key);
+ if (item == NULL)
+ return -1;
+ return item - this->dict.items;
+}
+
+static struct properties *properties_new(int prealloc)
+{
+ struct properties *impl;
+
+ impl = calloc(1, sizeof(struct properties));
+ if (impl == NULL)
+ return NULL;
+
+ pw_array_init(&impl->items, 16);
+ pw_array_ensure_size(&impl->items, sizeof(struct spa_dict_item) * prealloc);
+
+ return impl;
+}
+
+/** Make a new properties object
+ *
+ * \param key a first key
+ * \param ... value and more keys NULL terminated
+ * \return a newly allocated properties object
+ */
+SPA_EXPORT
+struct pw_properties *pw_properties_new(const char *key, ...)
+{
+ struct properties *impl;
+ va_list varargs;
+ const char *value;
+
+ impl = properties_new(16);
+ if (impl == NULL)
+ return NULL;
+
+ va_start(varargs, key);
+ while (key != NULL) {
+ value = va_arg(varargs, char *);
+ if (value && key[0])
+ add_func(&impl->this, strdup(key), strdup(value));
+ key = va_arg(varargs, char *);
+ }
+ va_end(varargs);
+
+ return &impl->this;
+}
+
+/** Make a new properties object from the given dictionary
+ *
+ * \param dict a dictionary. keys and values are copied
+ * \return a new properties object
+ */
+SPA_EXPORT
+struct pw_properties *pw_properties_new_dict(const struct spa_dict *dict)
+{
+ uint32_t i;
+ struct properties *impl;
+
+ impl = properties_new(SPA_ROUND_UP_N(dict->n_items, 16));
+ if (impl == NULL)
+ return NULL;
+
+ for (i = 0; i < dict->n_items; i++) {
+ const struct spa_dict_item *it = &dict->items[i];
+ if (it->key != NULL && it->key[0] && it->value != NULL)
+ add_func(&impl->this, strdup(it->key),
+ strdup(it->value));
+ }
+
+ return &impl->this;
+}
+
+/** Update the properties from the given string, overwriting any
+ * existing keys with the new values from \a str.
+ *
+ * \a str should be a whitespace separated list of key=value
+ * strings or a json object, see pw_properties_new_string().
+ *
+ * \return The number of properties added or updated
+ */
+SPA_EXPORT
+int pw_properties_update_string(struct pw_properties *props, const char *str, size_t size)
+{
+ struct properties *impl = SPA_CONTAINER_OF(props, struct properties, this);
+ struct spa_json it[2];
+ char key[1024], *val;
+ int count = 0;
+
+ 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;
+
+ 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);
+
+ if ((val = malloc(len+1)) != NULL)
+ spa_json_parse_stringn(value, len, val, len+1);
+ }
+ count += pw_properties_set(&impl->this, key, val);
+ free(val);
+ }
+ return count;
+}
+
+/** Make a new properties object from the given str
+ *
+ * \a object should be a whitespace separated list of key=value
+ * strings or a json object.
+ *
+ * \param object a property description
+ * \return a new properties object
+ */
+SPA_EXPORT
+struct pw_properties *
+pw_properties_new_string(const char *object)
+{
+ struct properties *impl;
+ int res;
+
+ impl = properties_new(16);
+ if (impl == NULL)
+ return NULL;
+
+ if ((res = pw_properties_update_string(&impl->this, object, strlen(object))) < 0)
+ goto error;
+
+ return &impl->this;
+error:
+ pw_properties_free(&impl->this);
+ errno = -res;
+ return NULL;
+}
+
+/** Copy a properties object
+ *
+ * \param properties properties to copy
+ * \return a new properties object
+ */
+SPA_EXPORT
+struct pw_properties *pw_properties_copy(const struct pw_properties *properties)
+{
+ return pw_properties_new_dict(&properties->dict);
+}
+
+/** Copy multiple keys from one property to another
+ *
+ * \param props properties to copy to
+ * \param dict properties to copy from
+ * \param keys a NULL terminated list of keys to copy
+ * \return the number of keys changed in \a dest
+ */
+SPA_EXPORT
+int pw_properties_update_keys(struct pw_properties *props,
+ const struct spa_dict *dict, const char * const keys[])
+{
+ int i, changed = 0;
+ const char *str;
+
+ for (i = 0; keys[i]; i++) {
+ if ((str = spa_dict_lookup(dict, keys[i])) != NULL)
+ changed += pw_properties_set(props, keys[i], str);
+ }
+ return changed;
+}
+
+static bool has_key(const char * const keys[], const char *key)
+{
+ int i;
+ for (i = 0; keys[i]; i++) {
+ if (spa_streq(keys[i], key))
+ return true;
+ }
+ return false;
+}
+
+SPA_EXPORT
+int pw_properties_update_ignore(struct pw_properties *props,
+ const struct spa_dict *dict, const char * const ignore[])
+{
+ const struct spa_dict_item *it;
+ int changed = 0;
+
+ spa_dict_for_each(it, dict) {
+ if (ignore == NULL || !has_key(ignore, it->key))
+ changed += pw_properties_set(props, it->key, it->value);
+ }
+ return changed;
+}
+
+/** Clear a properties object
+ *
+ * \param properties properties to clear
+ */
+SPA_EXPORT
+void pw_properties_clear(struct pw_properties *properties)
+{
+ struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this);
+ struct spa_dict_item *item;
+
+ pw_array_for_each(item, &impl->items)
+ clear_item(item);
+ pw_array_reset(&impl->items);
+ properties->dict.n_items = 0;
+}
+
+/** Update properties
+ *
+ * \param props properties to update
+ * \param dict new properties
+ * \return the number of changed properties
+ *
+ * The properties in \a props are updated with \a dict. Keys in \a dict
+ * with NULL values are removed from \a props.
+ */
+SPA_EXPORT
+int pw_properties_update(struct pw_properties *props,
+ const struct spa_dict *dict)
+{
+ const struct spa_dict_item *it;
+ int changed = 0;
+
+ spa_dict_for_each(it, dict)
+ changed += pw_properties_set(props, it->key, it->value);
+
+ return changed;
+}
+
+/** Add properties
+ *
+ * \param props properties to add
+ * \param dict new properties
+ * \return the number of added properties
+ *
+ * The properties from \a dict that are not yet in \a props are added.
+ */
+SPA_EXPORT
+int pw_properties_add(struct pw_properties *props,
+ const struct spa_dict *dict)
+{
+ uint32_t i;
+ int added = 0;
+
+ for (i = 0; i < dict->n_items; i++) {
+ if (pw_properties_get(props, dict->items[i].key) == NULL)
+ added += pw_properties_set(props, dict->items[i].key, dict->items[i].value);
+ }
+ return added;
+}
+
+/** Add keys
+ *
+ * \param props properties to add
+ * \param dict new properties
+ * \param keys a NULL terminated list of keys to add
+ * \return the number of added properties
+ *
+ * The properties with \a keys from \a dict that are not yet
+ * in \a props are added.
+ */
+SPA_EXPORT
+int pw_properties_add_keys(struct pw_properties *props,
+ const struct spa_dict *dict, const char * const keys[])
+{
+ uint32_t i;
+ int added = 0;
+ const char *str;
+
+ for (i = 0; keys[i]; i++) {
+ if ((str = spa_dict_lookup(dict, keys[i])) == NULL)
+ continue;
+ if (pw_properties_get(props, keys[i]) == NULL)
+ added += pw_properties_set(props, keys[i], str);
+ }
+ return added;
+}
+
+/** Free a properties object
+ *
+ * \param properties the properties to free
+ */
+SPA_EXPORT
+void pw_properties_free(struct pw_properties *properties)
+{
+ struct properties *impl;
+
+ if (properties == NULL)
+ return;
+
+ impl = SPA_CONTAINER_OF(properties, struct properties, this);
+ pw_properties_clear(properties);
+ pw_array_clear(&impl->items);
+ free(impl);
+}
+
+static int do_replace(struct pw_properties *properties, const char *key, char *value, bool copy)
+{
+ struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this);
+ int index;
+
+ if (key == NULL || key[0] == 0)
+ goto exit_noupdate;
+
+ index = find_index(properties, key);
+
+ if (index == -1) {
+ if (value == NULL)
+ return 0;
+ add_func(properties, strdup(key), copy ? strdup(value) : value);
+ SPA_FLAG_CLEAR(properties->dict.flags, SPA_DICT_FLAG_SORTED);
+ } else {
+ struct spa_dict_item *item =
+ pw_array_get_unchecked(&impl->items, index, struct spa_dict_item);
+
+ if (value && spa_streq(item->value, value))
+ goto exit_noupdate;
+
+ if (value == NULL) {
+ struct spa_dict_item *last = pw_array_get_unchecked(&impl->items,
+ pw_array_get_len(&impl->items, struct spa_dict_item) - 1,
+ struct spa_dict_item);
+ clear_item(item);
+ item->key = last->key;
+ item->value = last->value;
+ impl->items.size -= sizeof(struct spa_dict_item);
+ properties->dict.n_items--;
+ SPA_FLAG_CLEAR(properties->dict.flags, SPA_DICT_FLAG_SORTED);
+ } else {
+ free((char *) item->value);
+ item->value = copy ? strdup(value) : value;
+ }
+ }
+ return 1;
+exit_noupdate:
+ if (!copy)
+ free(value);
+ return 0;
+}
+
+/** Set a property value
+ *
+ * \param properties the properties to change
+ * \param key a key
+ * \param value a value or NULL to remove the key
+ * \return 1 if the properties were changed. 0 if nothing was changed because
+ * the property already existed with the same value or because the key to remove
+ * did not exist.
+ *
+ * Set the property in \a properties with \a key to \a value. Any previous value
+ * of \a key will be overwritten. When \a value is NULL, the key will be
+ * removed.
+ */
+SPA_EXPORT
+int pw_properties_set(struct pw_properties *properties, const char *key, const char *value)
+{
+ return do_replace(properties, key, (char*)value, true);
+}
+
+SPA_EXPORT
+int pw_properties_setva(struct pw_properties *properties,
+ const char *key, const char *format, va_list args)
+{
+ char *value = NULL;
+ if (format != NULL) {
+ if (vasprintf(&value, format, args) < 0)
+ return -errno;
+ }
+ return do_replace(properties, key, value, false);
+}
+
+/** Set a property value by format
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \param format a value
+ * \param ... extra arguments
+ * \return 1 if the property was changed. 0 if nothing was changed because
+ * the property already existed with the same value or because the key to remove
+ * did not exist.
+ *
+ * Set the property in \a properties with \a key to the value in printf style \a format
+ * Any previous value of \a key will be overwritten.
+ */
+SPA_EXPORT
+int pw_properties_setf(struct pw_properties *properties, const char *key, const char *format, ...)
+{
+ int res;
+ va_list varargs;
+
+ va_start(varargs, format);
+ res = pw_properties_setva(properties, key, format, varargs);
+ va_end(varargs);
+
+ return res;
+}
+
+/** Get a property
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \return the property for \a key or NULL when the key was not found
+ *
+ * Get the property in \a properties with \a key.
+ */
+SPA_EXPORT
+const char *pw_properties_get(const struct pw_properties *properties, const char *key)
+{
+ struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this);
+ int index = find_index(properties, key);
+
+ if (index == -1)
+ return NULL;
+
+ return pw_array_get_unchecked(&impl->items, index, struct spa_dict_item)->value;
+}
+
+/** Fetch a property as uint32_t.
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \param value set to the value of the property on success, otherwise left
+ * unmodified
+ * \return 0 on success or a negative errno otherwise
+ * \retval -ENOENT The property does not exist
+ * \retval -EINVAL The property is not in the expected format
+ */
+SPA_EXPORT
+int pw_properties_fetch_uint32(const struct pw_properties *properties, const char *key,
+ uint32_t *value)
+{
+ const char *str = pw_properties_get(properties, key);
+ bool success;
+
+ if (!str)
+ return -ENOENT;
+
+ success = spa_atou32(str, value, 0);
+ if (SPA_UNLIKELY(!success))
+ pw_log_warn("Failed to parse \"%s\"=\"%s\" as int32", key, str);
+
+ return success ? 0 : -EINVAL;
+}
+
+/** Fetch a property as int32_t
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \param value set to the value of the property on success, otherwise left
+ * unmodified
+ * \return 0 on success or a negative errno otherwise
+ * \retval -ENOENT The property does not exist
+ * \retval -EINVAL The property is not in the expected format
+ */
+SPA_EXPORT
+int pw_properties_fetch_int32(const struct pw_properties *properties, const char *key,
+ int32_t *value)
+{
+ const char *str = pw_properties_get(properties, key);
+ bool success;
+
+ if (!str)
+ return -ENOENT;
+
+ success = spa_atoi32(str, value, 0);
+ if (SPA_UNLIKELY(!success))
+ pw_log_warn("Failed to parse \"%s\"=\"%s\" as int32", key, str);
+
+ return success ? 0 : -EINVAL;
+}
+
+/** Fetch a property as uint64_t.
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \param value set to the value of the property on success, otherwise left
+ * unmodified
+ * \return 0 on success or a negative errno otherwise
+ * \retval -ENOENT The property does not exist
+ * \retval -EINVAL The property is not in the expected format
+ */
+SPA_EXPORT
+int pw_properties_fetch_uint64(const struct pw_properties *properties, const char *key,
+ uint64_t *value)
+{
+ const char *str = pw_properties_get(properties, key);
+ bool success;
+
+ if (!str)
+ return -ENOENT;
+
+ success = spa_atou64(str, value, 0);
+ if (SPA_UNLIKELY(!success))
+ pw_log_warn("Failed to parse \"%s\"=\"%s\" as uint64", key, str);
+
+ return success ? 0 : -EINVAL;
+}
+
+/** Fetch a property as int64_t
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \param value set to the value of the property on success, otherwise left
+ * unmodified
+ * \return 0 on success or a negative errno otherwise
+ * \retval -ENOENT The property does not exist
+ * \retval -EINVAL The property is not in the expected format
+ */
+SPA_EXPORT
+int pw_properties_fetch_int64(const struct pw_properties *properties, const char *key,
+ int64_t *value)
+{
+ const char *str = pw_properties_get(properties, key);
+ bool success;
+
+ if (!str)
+ return -ENOENT;
+
+ success = spa_atoi64(str, value, 0);
+ if (SPA_UNLIKELY(!success))
+ pw_log_warn("Failed to parse \"%s\"=\"%s\" as int64", key, str);
+
+ return success ? 0 : -EINVAL;
+}
+
+/** Fetch a property as boolean value
+ *
+ * \param properties a \ref pw_properties
+ * \param key a key
+ * \param value set to the value of the property on success, otherwise left
+ * unmodified
+ * \return 0 on success or a negative errno otherwise
+ * \retval -ENOENT The property does not exist
+ * \retval -EINVAL The property is not in the expected format
+ */
+SPA_EXPORT
+int pw_properties_fetch_bool(const struct pw_properties *properties, const char *key,
+ bool *value)
+{
+ const char *str = pw_properties_get(properties, key);
+
+ if (!str)
+ return -ENOENT;
+
+ *value = spa_atob(str);
+ return 0;
+}
+
+/** Iterate property values
+ *
+ * \param properties a \ref pw_properties
+ * \param state state
+ * \return The next key or NULL when there are no more keys to iterate.
+ *
+ * Iterate over \a properties, returning each key in turn. \a state should point
+ * to a pointer holding NULL to get the first element and will be updated
+ * after each iteration. When NULL is returned, all elements have been
+ * iterated.
+ */
+SPA_EXPORT
+const char *pw_properties_iterate(const struct pw_properties *properties, void **state)
+{
+ struct properties *impl = SPA_CONTAINER_OF(properties, struct properties, this);
+ uint32_t index;
+
+ if (*state == NULL)
+ index = 0;
+ else
+ index = SPA_PTR_TO_INT(*state);
+
+ if (!pw_array_check_index(&impl->items, index, struct spa_dict_item))
+ return NULL;
+
+ *state = SPA_INT_TO_PTR(index + 1);
+
+ return pw_array_get_unchecked(&impl->items, index, struct spa_dict_item)->key;
+}
+
+static int encode_string(FILE *f, const char *val)
+{
+ int len = 0;
+ len += fprintf(f, "\"");
+ while (*val) {
+ switch (*val) {
+ case '\n':
+ len += fprintf(f, "\\n");
+ break;
+ case '\r':
+ len += fprintf(f, "\\r");
+ break;
+ case '\b':
+ len += fprintf(f, "\\b");
+ break;
+ case '\t':
+ len += fprintf(f, "\\t");
+ break;
+ case '\f':
+ len += fprintf(f, "\\f");
+ break;
+ case '\\':
+ case '"':
+ len += fprintf(f, "\\%c", *val);
+ break;
+ default:
+ if (*val > 0 && *val < 0x20)
+ len += fprintf(f, "\\u%04x", *val);
+ else
+ len += fprintf(f, "%c", *val);
+ break;
+ }
+ val++;
+ }
+ len += fprintf(f, "\"");
+ return len-1;
+}
+
+SPA_EXPORT
+int pw_properties_serialize_dict(FILE *f, const struct spa_dict *dict, uint32_t flags)
+{
+ const struct spa_dict_item *it;
+ int count = 0;
+ char key[1024];
+
+ spa_dict_for_each(it, dict) {
+ size_t len = it->value ? strlen(it->value) : 0;
+
+ if (spa_json_encode_string(key, sizeof(key)-1, it->key) >= (int)sizeof(key)-1)
+ continue;
+
+ fprintf(f, "%s%s %s: ",
+ count == 0 ? "" : ",",
+ flags & PW_PROPERTIES_FLAG_NL ? "\n" : "",
+ key);
+
+ if (it->value == NULL) {
+ fprintf(f, "null");
+ } else if (spa_json_is_null(it->value, len) ||
+ spa_json_is_float(it->value, len) ||
+ spa_json_is_bool(it->value, len) ||
+ spa_json_is_container(it->value, len) ||
+ spa_json_is_string(it->value, len)) {
+ fprintf(f, "%s", it->value);
+ } else {
+ encode_string(f, it->value);
+ }
+ count++;
+ }
+ return count;
+}
diff --git a/src/pipewire/properties.h b/src/pipewire/properties.h
new file mode 100644
index 0000000..e91fee5
--- /dev/null
+++ b/src/pipewire/properties.h
@@ -0,0 +1,199 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_PROPERTIES_H
+#define PIPEWIRE_PROPERTIES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdarg.h>
+
+#include <spa/utils/dict.h>
+#include <spa/utils/string.h>
+
+/** \defgroup pw_properties Properties
+ *
+ * Properties are used to pass around arbitrary key/value pairs.
+ * Both keys and values are strings which keeps things simple.
+ * Encoding of arbitrary values should be done by using a string
+ * serialization such as base64 for binary blobs.
+ */
+
+/**
+ * \addtogroup pw_properties
+ * \{
+ */
+struct pw_properties {
+ struct spa_dict dict; /**< dictionary of key/values */
+ uint32_t flags; /**< extra flags */
+};
+
+struct pw_properties *
+pw_properties_new(const char *key, ...) SPA_SENTINEL;
+
+struct pw_properties *
+pw_properties_new_dict(const struct spa_dict *dict);
+
+struct pw_properties *
+pw_properties_new_string(const char *args);
+
+struct pw_properties *
+pw_properties_copy(const struct pw_properties *properties);
+
+int pw_properties_update_keys(struct pw_properties *props,
+ const struct spa_dict *dict, const char * const keys[]);
+int pw_properties_update_ignore(struct pw_properties *props,
+ const struct spa_dict *dict, const char * const ignore[]);
+
+/* Update props with all key/value pairs from dict */
+int pw_properties_update(struct pw_properties *props,
+ const struct spa_dict *dict);
+/* Update props with all key/value pairs from str */
+int pw_properties_update_string(struct pw_properties *props,
+ const char *str, size_t size);
+
+int pw_properties_add(struct pw_properties *oldprops,
+ const struct spa_dict *dict);
+int pw_properties_add_keys(struct pw_properties *oldprops,
+ const struct spa_dict *dict, const char * const keys[]);
+
+void pw_properties_clear(struct pw_properties *properties);
+
+void
+pw_properties_free(struct pw_properties *properties);
+
+int
+pw_properties_set(struct pw_properties *properties, const char *key, const char *value);
+
+int
+pw_properties_setf(struct pw_properties *properties,
+ const char *key, const char *format, ...) SPA_PRINTF_FUNC(3, 4);
+int
+pw_properties_setva(struct pw_properties *properties,
+ const char *key, const char *format, va_list args) SPA_PRINTF_FUNC(3,0);
+const char *
+pw_properties_get(const struct pw_properties *properties, const char *key);
+
+int
+pw_properties_fetch_uint32(const struct pw_properties *properties, const char *key, uint32_t *value);
+
+int
+pw_properties_fetch_int32(const struct pw_properties *properties, const char *key, int32_t *value);
+
+int
+pw_properties_fetch_uint64(const struct pw_properties *properties, const char *key, uint64_t *value);
+
+int
+pw_properties_fetch_int64(const struct pw_properties *properties, const char *key, int64_t *value);
+
+int
+pw_properties_fetch_bool(const struct pw_properties *properties, const char *key, bool *value);
+
+static inline uint32_t
+pw_properties_get_uint32(const struct pw_properties *properties, const char *key, uint32_t deflt)
+{
+ uint32_t val = deflt;
+ pw_properties_fetch_uint32(properties, key, &val);
+ return val;
+}
+
+static inline int32_t
+pw_properties_get_int32(const struct pw_properties *properties, const char *key, int32_t deflt)
+{
+ int32_t val = deflt;
+ pw_properties_fetch_int32(properties, key, &val);
+ return val;
+}
+
+static inline uint64_t
+pw_properties_get_uint64(const struct pw_properties *properties, const char *key, uint64_t deflt)
+{
+ uint64_t val = deflt;
+ pw_properties_fetch_uint64(properties, key, &val);
+ return val;
+}
+
+static inline int64_t
+pw_properties_get_int64(const struct pw_properties *properties, const char *key, int64_t deflt)
+{
+ int64_t val = deflt;
+ pw_properties_fetch_int64(properties, key, &val);
+ return val;
+}
+
+
+static inline bool
+pw_properties_get_bool(const struct pw_properties *properties, const char *key, bool deflt)
+{
+ bool val = deflt;
+ pw_properties_fetch_bool(properties, key, &val);
+ return val;
+}
+
+const char *
+pw_properties_iterate(const struct pw_properties *properties, void **state);
+
+#define PW_PROPERTIES_FLAG_NL (1<<0)
+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) {
+ return spa_atob(value);
+}
+
+static inline 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) {
+ int64_t v;
+ return spa_atoi64(value, &v, 0) ? v : 0;
+}
+
+static inline 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) {
+ float v;
+ return spa_atof(value, &v) ? v : 0.0f;
+}
+
+static inline double pw_properties_parse_double(const char *value) {
+ double v;
+ return spa_atod(value, &v) ? v : 0.0;
+}
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_PROPERTIES_H */
diff --git a/src/pipewire/protocol.c b/src/pipewire/protocol.c
new file mode 100644
index 0000000..3092f2c
--- /dev/null
+++ b/src/pipewire/protocol.c
@@ -0,0 +1,188 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+
+#include <spa/debug/types.h>
+#include <spa/utils/string.h>
+
+#include <pipewire/protocol.h>
+#include <pipewire/private.h>
+#include <pipewire/type.h>
+
+PW_LOG_TOPIC_EXTERN(log_protocol);
+#define PW_LOG_TOPIC_DEFAULT log_protocol
+
+/** \cond */
+struct impl {
+ struct pw_protocol this;
+};
+
+struct marshal {
+ struct spa_list link;
+ const struct pw_protocol_marshal *marshal;
+};
+/** \endcond */
+
+SPA_EXPORT
+struct pw_protocol *pw_protocol_new(struct pw_context *context,
+ const char *name,
+ size_t user_data_size)
+{
+ struct pw_protocol *protocol;
+
+ protocol = calloc(1, sizeof(struct impl) + user_data_size);
+ if (protocol == NULL)
+ return NULL;
+
+ protocol->context = context;
+ protocol->name = strdup(name);
+
+ spa_list_init(&protocol->marshal_list);
+ spa_list_init(&protocol->server_list);
+ spa_list_init(&protocol->client_list);
+ spa_hook_list_init(&protocol->listener_list);
+
+ if (user_data_size > 0)
+ protocol->user_data = SPA_PTROFF(protocol, sizeof(struct impl), void);
+
+ spa_list_append(&context->protocol_list, &protocol->link);
+
+ pw_log_debug("%p: Created protocol %s", protocol, name);
+
+ return protocol;
+}
+
+SPA_EXPORT
+struct pw_context *pw_protocol_get_context(struct pw_protocol *protocol)
+{
+ return protocol->context;
+}
+
+SPA_EXPORT
+void *pw_protocol_get_user_data(struct pw_protocol *protocol)
+{
+ return protocol->user_data;
+}
+
+SPA_EXPORT
+const struct pw_protocol_implementation *
+pw_protocol_get_implementation(struct pw_protocol *protocol)
+{
+ return protocol->implementation;
+}
+
+SPA_EXPORT
+const void *
+pw_protocol_get_extension(struct pw_protocol *protocol)
+{
+ return protocol->extension;
+}
+
+SPA_EXPORT
+void pw_protocol_destroy(struct pw_protocol *protocol)
+{
+ struct impl *impl = SPA_CONTAINER_OF(protocol, struct impl, this);
+ struct marshal *marshal, *t1;
+ struct pw_protocol_server *server;
+ struct pw_protocol_client *client;
+
+ pw_log_debug("%p: destroy", protocol);
+ pw_protocol_emit_destroy(protocol);
+
+ spa_hook_list_clean(&protocol->listener_list);
+
+ spa_list_remove(&protocol->link);
+
+ spa_list_consume(server, &protocol->server_list, link)
+ pw_protocol_server_destroy(server);
+
+ spa_list_consume(client, &protocol->client_list, link)
+ pw_protocol_client_destroy(client);
+
+ spa_list_for_each_safe(marshal, t1, &protocol->marshal_list, link)
+ free(marshal);
+
+ free(protocol->name);
+
+ free(impl);
+}
+
+SPA_EXPORT
+void pw_protocol_add_listener(struct pw_protocol *protocol,
+ struct spa_hook *listener,
+ const struct pw_protocol_events *events,
+ void *data)
+{
+ spa_hook_list_append(&protocol->listener_list, listener, events, data);
+}
+
+SPA_EXPORT
+int
+pw_protocol_add_marshal(struct pw_protocol *protocol,
+ const struct pw_protocol_marshal *marshal)
+{
+ struct marshal *impl;
+
+ impl = calloc(1, sizeof(struct marshal));
+ if (impl == NULL)
+ return -errno;
+
+ impl->marshal = marshal;
+
+ spa_list_append(&protocol->marshal_list, &impl->link);
+
+ pw_log_debug("%p: Add marshal %s/%d to protocol %s", protocol,
+ marshal->type, marshal->version, protocol->name);
+
+ return 0;
+}
+
+SPA_EXPORT
+const struct pw_protocol_marshal *
+pw_protocol_get_marshal(struct pw_protocol *protocol, const char *type, uint32_t version, uint32_t flags)
+{
+ struct marshal *impl;
+
+ spa_list_for_each(impl, &protocol->marshal_list, link) {
+ if (spa_streq(impl->marshal->type, type) &&
+ (impl->marshal->flags & flags) == flags)
+ return impl->marshal;
+ }
+ pw_log_debug("%p: No marshal %s/%d for protocol %s", protocol,
+ type, version, protocol->name);
+ return NULL;
+}
+
+SPA_EXPORT
+struct pw_protocol *pw_context_find_protocol(struct pw_context *context, const char *name)
+{
+ struct pw_protocol *protocol;
+
+ spa_list_for_each(protocol, &context->protocol_list, link) {
+ if (spa_streq(protocol->name, name))
+ return protocol;
+ }
+ return NULL;
+}
diff --git a/src/pipewire/protocol.h b/src/pipewire/protocol.h
new file mode 100644
index 0000000..bb97273
--- /dev/null
+++ b/src/pipewire/protocol.h
@@ -0,0 +1,162 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_PROTOCOL_H
+#define PIPEWIRE_PROTOCOL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/list.h>
+
+/** \defgroup pw_protocol Protocol
+ *
+ * \brief Manages protocols and their implementation
+ */
+
+/**
+ * \addtogroup pw_protocol
+ * \{
+ */
+
+struct pw_protocol;
+
+#include <pipewire/context.h>
+#include <pipewire/properties.h>
+#include <pipewire/utils.h>
+
+#define PW_TYPE_INFO_Protocol "PipeWire:Protocol"
+#define PW_TYPE_INFO_PROTOCOL_BASE PW_TYPE_INFO_Protocol ":"
+
+struct pw_protocol_client {
+ struct spa_list link; /**< link in protocol client_list */
+ struct pw_protocol *protocol; /**< the owner protocol */
+
+ struct pw_core *core;
+
+ int (*connect) (struct pw_protocol_client *client,
+ const struct spa_dict *props,
+ void (*done_callback) (void *data, int result),
+ void *data);
+ int (*connect_fd) (struct pw_protocol_client *client, int fd, bool close);
+ int (*steal_fd) (struct pw_protocol_client *client);
+ void (*disconnect) (struct pw_protocol_client *client);
+ void (*destroy) (struct pw_protocol_client *client);
+ int (*set_paused) (struct pw_protocol_client *client, bool paused);
+};
+
+#define pw_protocol_client_connect(c,p,cb,d) ((c)->connect(c,p,cb,d))
+#define pw_protocol_client_connect_fd(c,fd,cl) ((c)->connect_fd(c,fd,cl))
+#define pw_protocol_client_steal_fd(c) ((c)->steal_fd(c))
+#define pw_protocol_client_disconnect(c) ((c)->disconnect(c))
+#define pw_protocol_client_destroy(c) ((c)->destroy(c))
+#define pw_protocol_client_set_paused(c,p) ((c)->set_paused(c,p))
+
+struct pw_protocol_server {
+ struct spa_list link; /**< link in protocol server_list */
+ struct pw_protocol *protocol; /**< the owner protocol */
+
+ struct pw_impl_core *core;
+
+ struct spa_list client_list; /**< list of clients of this protocol */
+
+ void (*destroy) (struct pw_protocol_server *listen);
+};
+
+#define pw_protocol_server_destroy(l) ((l)->destroy(l))
+
+struct pw_protocol_marshal {
+ const char *type; /**< interface type */
+ uint32_t version; /**< version */
+#define PW_PROTOCOL_MARSHAL_FLAG_IMPL (1 << 0) /**< marshal for implementations */
+ uint32_t flags; /**< version */
+ uint32_t n_client_methods; /**< number of client methods */
+ uint32_t n_server_methods; /**< number of server methods */
+ const void *client_marshal;
+ const void *server_demarshal;
+ const void *server_marshal;
+ const void *client_demarshal;
+};
+
+struct pw_protocol_implementation {
+#define PW_VERSION_PROTOCOL_IMPLEMENTATION 0
+ uint32_t version;
+
+ struct pw_protocol_client * (*new_client) (struct pw_protocol *protocol,
+ struct pw_core *core,
+ const struct spa_dict *props);
+ struct pw_protocol_server * (*add_server) (struct pw_protocol *protocol,
+ struct pw_impl_core *core,
+ const struct spa_dict *props);
+};
+
+struct pw_protocol_events {
+#define PW_VERSION_PROTOCOL_EVENTS 0
+ uint32_t version;
+
+ void (*destroy) (void *data);
+};
+
+#define pw_protocol_new_client(p,...) (pw_protocol_get_implementation(p)->new_client(p,__VA_ARGS__))
+#define pw_protocol_add_server(p,...) (pw_protocol_get_implementation(p)->add_server(p,__VA_ARGS__))
+#define pw_protocol_ext(p,type,method,...) (((type*)pw_protocol_get_extension(p))->method( __VA_ARGS__))
+
+struct pw_protocol *pw_protocol_new(struct pw_context *context, const char *name, size_t user_data_size);
+
+void pw_protocol_destroy(struct pw_protocol *protocol);
+
+struct pw_context *pw_protocol_get_context(struct pw_protocol *protocol);
+
+void *pw_protocol_get_user_data(struct pw_protocol *protocol);
+
+const struct pw_protocol_implementation *
+pw_protocol_get_implementation(struct pw_protocol *protocol);
+
+const void *
+pw_protocol_get_extension(struct pw_protocol *protocol);
+
+
+void pw_protocol_add_listener(struct pw_protocol *protocol,
+ struct spa_hook *listener,
+ const struct pw_protocol_events *events,
+ void *data);
+
+int pw_protocol_add_marshal(struct pw_protocol *protocol,
+ const struct pw_protocol_marshal *marshal);
+
+const struct pw_protocol_marshal *
+pw_protocol_get_marshal(struct pw_protocol *protocol, const char *type, uint32_t version, uint32_t flags);
+
+struct pw_protocol * pw_context_find_protocol(struct pw_context *context, const char *name);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_PROTOCOL_H */
diff --git a/src/pipewire/proxy.c b/src/pipewire/proxy.c
new file mode 100644
index 0000000..b3eff72
--- /dev/null
+++ b/src/pipewire/proxy.c
@@ -0,0 +1,374 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <assert.h>
+
+#include <pipewire/log.h>
+#include <pipewire/proxy.h>
+#include <pipewire/core.h>
+#include <pipewire/private.h>
+#include <pipewire/type.h>
+
+#include <spa/debug/types.h>
+
+PW_LOG_TOPIC_EXTERN(log_proxy);
+#define PW_LOG_TOPIC_DEFAULT log_proxy
+
+/** \cond */
+struct proxy {
+ struct pw_proxy this;
+};
+/** \endcond */
+
+int pw_proxy_init(struct pw_proxy *proxy, const char *type, uint32_t version)
+{
+ int res;
+
+ proxy->refcount = 1;
+ proxy->type = type;
+ proxy->version = version;
+ proxy->bound_id = SPA_ID_INVALID;
+
+ proxy->id = pw_map_insert_new(&proxy->core->objects, proxy);
+ if (proxy->id == SPA_ID_INVALID) {
+ res = -errno;
+ pw_log_error("%p: can't allocate new id: %m", proxy);
+ goto error;
+ }
+
+ spa_hook_list_init(&proxy->listener_list);
+ spa_hook_list_init(&proxy->object_listener_list);
+
+ if ((res = pw_proxy_install_marshal(proxy, false)) < 0) {
+ pw_log_error("%p: no marshal for type %s/%d: %s", proxy,
+ type, version, spa_strerror(res));
+ goto error_clean;
+ }
+ proxy->in_map = true;
+ return 0;
+
+error_clean:
+ pw_map_remove(&proxy->core->objects, proxy->id);
+error:
+ return res;
+}
+
+/** Create a proxy object with a given id and type
+ *
+ * \param factory another proxy object that serves as a factory
+ * \param type Type of the proxy object
+ * \param version Interface version
+ * \param user_data_size size of user_data
+ * \return A newly allocated proxy object or NULL on failure
+ *
+ * This function creates a new proxy object with the supplied id and type. The
+ * proxy object will have an id assigned from the client id space.
+ *
+ * \sa pw_core
+ */
+SPA_EXPORT
+struct pw_proxy *pw_proxy_new(struct pw_proxy *factory,
+ const char *type, uint32_t version,
+ size_t user_data_size)
+{
+ struct proxy *impl;
+ struct pw_proxy *this;
+ int res;
+
+ impl = calloc(1, sizeof(struct proxy) + user_data_size);
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+ this->core = factory->core;
+
+ if ((res = pw_proxy_init(this, type, version)) < 0)
+ goto error_init;
+
+ if (user_data_size > 0)
+ this->user_data = SPA_PTROFF(impl, sizeof(struct proxy), void);
+
+ pw_log_debug("%p: new %u type %s/%d core-proxy:%p, marshal:%p",
+ this, this->id, type, version, this->core, this->marshal);
+ return this;
+
+error_init:
+ free(impl);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+int pw_proxy_install_marshal(struct pw_proxy *this, bool implementor)
+{
+ struct pw_core *core = this->core;
+ const struct pw_protocol_marshal *marshal;
+
+ if (core == NULL)
+ return -EIO;
+
+ marshal = pw_protocol_get_marshal(core->conn->protocol,
+ this->type, this->version,
+ implementor ? PW_PROTOCOL_MARSHAL_FLAG_IMPL : 0);
+ if (marshal == NULL)
+ return -EPROTO;
+
+ this->marshal = marshal;
+ this->type = marshal->type;
+
+ this->impl = SPA_INTERFACE_INIT(
+ this->type,
+ this->marshal->version,
+ this->marshal->client_marshal, this);
+ return 0;
+}
+
+SPA_EXPORT
+void *pw_proxy_get_user_data(struct pw_proxy *proxy)
+{
+ return proxy->user_data;
+}
+
+SPA_EXPORT
+uint32_t pw_proxy_get_id(struct pw_proxy *proxy)
+{
+ return proxy->id;
+}
+
+SPA_EXPORT
+int pw_proxy_set_bound_id(struct pw_proxy *proxy, uint32_t global_id)
+{
+ proxy->bound_id = global_id;
+ pw_log_debug("%p: id:%d bound:%d", proxy, proxy->id, global_id);
+ pw_proxy_emit_bound(proxy, global_id);
+ return 0;
+}
+
+SPA_EXPORT
+uint32_t pw_proxy_get_bound_id(struct pw_proxy *proxy)
+{
+ return proxy->bound_id;
+}
+
+SPA_EXPORT
+const char *pw_proxy_get_type(struct pw_proxy *proxy, uint32_t *version)
+{
+ if (version)
+ *version = proxy->version;
+ return proxy->type;
+}
+
+SPA_EXPORT
+struct pw_core *pw_proxy_get_core(struct pw_proxy *proxy)
+{
+ return proxy->core;
+}
+
+SPA_EXPORT
+struct pw_protocol *pw_proxy_get_protocol(struct pw_proxy *proxy)
+{
+ if (proxy->core == NULL || proxy->core->conn == NULL)
+ return NULL;
+ return proxy->core->conn->protocol;
+}
+
+SPA_EXPORT
+void pw_proxy_add_listener(struct pw_proxy *proxy,
+ struct spa_hook *listener,
+ const struct pw_proxy_events *events,
+ void *data)
+{
+ spa_hook_list_append(&proxy->listener_list, listener, events, data);
+}
+
+SPA_EXPORT
+void pw_proxy_add_object_listener(struct pw_proxy *proxy,
+ struct spa_hook *listener,
+ const void *funcs,
+ void *data)
+{
+ spa_hook_list_append(&proxy->object_listener_list, listener, funcs, data);
+}
+
+static inline void remove_from_map(struct pw_proxy *proxy)
+{
+ if (proxy->in_map) {
+ if (proxy->core)
+ pw_map_remove(&proxy->core->objects, proxy->id);
+ proxy->in_map = false;
+ }
+}
+
+/** Destroy a proxy object
+ *
+ * \param proxy Proxy object to destroy
+ *
+ * \note This is normally called by \ref pw_core when the server
+ * decides to destroy the server side object
+ */
+SPA_EXPORT
+void pw_proxy_destroy(struct pw_proxy *proxy)
+{
+ pw_log_debug("%p: destroy id:%u removed:%u zombie:%u ref:%d", proxy,
+ proxy->id, proxy->removed, proxy->zombie, proxy->refcount);
+
+ assert(!proxy->destroyed);
+ proxy->destroyed = true;
+
+ if (!proxy->removed) {
+ /* if the server did not remove this proxy, schedule a
+ * destroy if we can */
+ if (proxy->core && !proxy->core->removed) {
+ pw_core_destroy(proxy->core, proxy);
+ proxy->refcount++;
+ } else {
+ proxy->removed = true;
+ }
+ }
+ if (proxy->removed)
+ remove_from_map(proxy);
+
+ if (!proxy->zombie) {
+ /* mark zombie and emit destroyed. No more
+ * events will be emitted on zombie objects */
+ proxy->zombie = true;
+ pw_proxy_emit_destroy(proxy);
+ }
+
+ pw_proxy_unref(proxy);
+}
+
+/** called when cleaning up or when the server removed the resource. Can
+ * be called multiple times */
+void pw_proxy_remove(struct pw_proxy *proxy)
+{
+ assert(proxy->refcount > 0);
+
+ pw_log_debug("%p: remove id:%u removed:%u destroyed:%u zombie:%u ref:%d", proxy,
+ proxy->id, proxy->removed, proxy->destroyed, proxy->zombie,
+ proxy->refcount);
+
+ if (!proxy->destroyed)
+ proxy->refcount++;
+
+ if (!proxy->removed) {
+ /* mark removed and emit the removed signal only once and
+ * only when not already destroyed */
+ proxy->removed = true;
+ if (!proxy->destroyed)
+ pw_proxy_emit_removed(proxy);
+ }
+ if (proxy->destroyed)
+ remove_from_map(proxy);
+
+ pw_proxy_unref(proxy);
+}
+
+SPA_EXPORT
+void pw_proxy_unref(struct pw_proxy *proxy)
+{
+ assert(proxy->refcount > 0);
+ if (--proxy->refcount > 0)
+ return;
+
+ pw_log_debug("%p: free %u", proxy, proxy->id);
+ /** client must explicitly destroy all proxies */
+ assert(proxy->destroyed);
+
+#if DEBUG_LISTENERS
+ {
+ struct spa_hook *h;
+ spa_list_for_each(h, &proxy->object_listener_list.list, link) {
+ pw_log_warn("%p: proxy %u: leaked object listener %p",
+ proxy, proxy->id, h);
+ break;
+ }
+ spa_list_for_each(h, &proxy->listener_list.list, link) {
+ pw_log_warn("%p: proxy %u: leaked listener %p",
+ proxy, proxy->id, h);
+ break;
+ }
+ }
+#endif
+ free(proxy);
+}
+
+SPA_EXPORT
+void pw_proxy_ref(struct pw_proxy *proxy)
+{
+ assert(proxy->refcount > 0);
+ proxy->refcount++;
+}
+
+SPA_EXPORT
+int pw_proxy_sync(struct pw_proxy *proxy, int seq)
+{
+ int res = -EIO;
+ struct pw_core *core = proxy->core;
+
+ if (core && !core->removed) {
+ res = pw_core_sync(core, proxy->id, seq);
+ pw_log_debug("%p: %u seq:%d sync %u", proxy, proxy->id, seq, res);
+ }
+ return res;
+}
+
+SPA_EXPORT
+int pw_proxy_errorf(struct pw_proxy *proxy, int res, const char *error, ...)
+{
+ va_list ap;
+ int r = -EIO;
+ struct pw_core *core = proxy->core;
+
+ va_start(ap, error);
+ if (core && !core->removed)
+ r = pw_core_errorv(core, proxy->id,
+ core->recv_seq, res, error, ap);
+ va_end(ap);
+ return r;
+}
+
+SPA_EXPORT
+int pw_proxy_error(struct pw_proxy *proxy, int res, const char *error)
+{
+ int r = -EIO;
+ struct pw_core *core = proxy->core;
+
+ if (core && !core->removed)
+ r = pw_core_error(core, proxy->id,
+ core->recv_seq, res, error);
+ return r;
+}
+
+SPA_EXPORT
+struct spa_hook_list *pw_proxy_get_object_listeners(struct pw_proxy *proxy)
+{
+ return &proxy->object_listener_list;
+}
+
+SPA_EXPORT
+const struct pw_protocol_marshal *pw_proxy_get_marshal(struct pw_proxy *proxy)
+{
+ return proxy->marshal;
+}
diff --git a/src/pipewire/proxy.h b/src/pipewire/proxy.h
new file mode 100644
index 0000000..1e15dcc
--- /dev/null
+++ b/src/pipewire/proxy.h
@@ -0,0 +1,219 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_PROXY_H
+#define PIPEWIRE_PROXY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/hook.h>
+
+/** \page page_proxy Proxy
+ *
+ * \section sec_page_proxy_overview Overview
+ *
+ * The proxy object is a client side representation of a resource
+ * that lives on a remote PipeWire instance.
+ *
+ * It is used to communicate with the remote object.
+ *
+ * \section sec_page_proxy_core Core proxy
+ *
+ * A proxy for a remote core object can be obtained by making
+ * a remote connection with \ref pw_context_connect.
+ * See \ref pw_proxy
+ *
+ * Some methods on proxy object allow creation of more proxy objects or
+ * create a binding between a local proxy and global resource.
+ *
+ * \section sec_page_proxy_create Create
+ *
+ * A client first creates a new proxy object with pw_proxy_new(). A
+ * type must be provided for this object.
+ *
+ * The protocol of the context will usually install an interface to
+ * translate method calls and events to the wire format.
+ *
+ * The creator of the proxy will usually also install an event
+ * implementation of the particular object type.
+ *
+ * \section sec_page_proxy_bind Bind
+ *
+ * To actually use the proxy object, one needs to create a server
+ * side resource for it. This can be done by, for example, binding
+ * to a global object or by calling a method that creates and binds
+ * to a new remote object. In all cases, the local id is passed to
+ * the server and is used to create a resource with the same id.
+ *
+ * \section sec_page_proxy_methods Methods
+ *
+ * To call a method on the proxy use the interface methods. Calling
+ * any interface method will result in a request to the server to
+ * perform the requested action on the corresponding resource.
+ *
+ * \section sec_page_proxy_events Events
+ *
+ * Events send from the server to the proxy will be demarshalled by
+ * the protocol and will then result in a call to the installed
+ * implementation of the proxy.
+ *
+ * \section sec_page_proxy_destroy Destroy
+ *
+ * Use pw_proxy_destroy() to destroy the client side object. This
+ * is usually done automatically when the server removes the resource
+ * associated to the proxy.
+ */
+
+/** \defgroup pw_proxy Proxy
+ *
+ * \brief Represents an object on the client side.
+ *
+ * A pw_proxy acts as a client side proxy to an object existing in a remote
+ * pipewire instance. The proxy is responsible for converting interface functions
+ * invoked by the client to PipeWire messages. Events will call the handlers
+ * set in listener.
+ *
+ * See \ref page_proxy
+ */
+
+/**
+ * \addtogroup pw_proxy
+ * \{
+ */
+struct pw_proxy;
+
+#include <pipewire/protocol.h>
+
+/** Proxy events, use \ref pw_proxy_add_listener */
+struct pw_proxy_events {
+#define PW_VERSION_PROXY_EVENTS 0
+ uint32_t version;
+
+ /** The proxy is destroyed */
+ void (*destroy) (void *data);
+
+ /** a proxy is bound to a global id */
+ void (*bound) (void *data, uint32_t global_id);
+
+ /** a proxy is removed from the server. Use pw_proxy_destroy to
+ * free the proxy. */
+ void (*removed) (void *data);
+
+ /** a reply to a sync method completed */
+ void (*done) (void *data, int seq);
+
+ /** an error occurred on the proxy */
+ void (*error) (void *data, int seq, int res, const char *message);
+};
+
+/* Make a new proxy object. The id can be used to bind to a remote object and
+ * can be retrieved with \ref pw_proxy_get_id . */
+struct pw_proxy *
+pw_proxy_new(struct pw_proxy *factory,
+ const char *type, /* interface type */
+ uint32_t version, /* interface version */
+ size_t user_data_size /* size of user data */);
+
+/** Add an event listener to proxy */
+void pw_proxy_add_listener(struct pw_proxy *proxy,
+ struct spa_hook *listener,
+ const struct pw_proxy_events *events,
+ void *data);
+
+/** Add a listener for the events received from the remote object. The
+ * events depend on the type of the remote object type. */
+void pw_proxy_add_object_listener(struct pw_proxy *proxy, /**< the proxy */
+ struct spa_hook *listener, /**< listener */
+ const void *funcs, /**< proxied functions */
+ void *data /**< data passed to events */);
+
+/** destroy a proxy */
+void pw_proxy_destroy(struct pw_proxy *proxy);
+
+void pw_proxy_ref(struct pw_proxy *proxy);
+void pw_proxy_unref(struct pw_proxy *proxy);
+
+/** Get the user_data. The size was given in \ref pw_proxy_new */
+void *pw_proxy_get_user_data(struct pw_proxy *proxy);
+
+/** Get the local id of the proxy */
+uint32_t pw_proxy_get_id(struct pw_proxy *proxy);
+
+/** Get the type and version of the proxy */
+const char *pw_proxy_get_type(struct pw_proxy *proxy, uint32_t *version);
+
+/** Get the protocol used for the proxy */
+struct pw_protocol *pw_proxy_get_protocol(struct pw_proxy *proxy);
+
+/** Generate an sync method for a proxy. This will generate a done event
+ * with the same seq number of the reply. */
+int pw_proxy_sync(struct pw_proxy *proxy, int seq);
+
+/** Set the global id this proxy is bound to. This is usually used internally
+ * and will also emit the bound event */
+int pw_proxy_set_bound_id(struct pw_proxy *proxy, uint32_t global_id);
+/** Get the global id bound to this proxy of SPA_ID_INVALID when not bound
+ * to a global */
+uint32_t pw_proxy_get_bound_id(struct pw_proxy *proxy);
+
+/** Generate an error for a proxy */
+int pw_proxy_error(struct pw_proxy *proxy, int res, const char *error);
+int pw_proxy_errorf(struct pw_proxy *proxy, int res, const char *error, ...) SPA_PRINTF_FUNC(3, 4);
+
+/** Get the listener of proxy */
+struct spa_hook_list *pw_proxy_get_object_listeners(struct pw_proxy *proxy);
+
+/** Get the marshal functions for the proxy */
+const struct pw_protocol_marshal *pw_proxy_get_marshal(struct pw_proxy *proxy);
+
+/** Install a marshal function on a proxy */
+int pw_proxy_install_marshal(struct pw_proxy *proxy, bool implementor);
+
+#define pw_proxy_notify(p,type,event,version,...) \
+ spa_hook_list_call(pw_proxy_get_object_listeners(p), \
+ type, event, version, ## __VA_ARGS__)
+
+#define pw_proxy_call(p,type,method,version,...) \
+ spa_interface_call((struct spa_interface*)p, \
+ type, method, version, ##__VA_ARGS__)
+
+#define pw_proxy_call_res(p,type,method,version,...) \
+({ \
+ int _res = -ENOTSUP; \
+ spa_interface_call_res((struct spa_interface*)p, \
+ type, _res, method, version, ##__VA_ARGS__); \
+ _res; \
+})
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_PROXY_H */
diff --git a/src/pipewire/resource.c b/src/pipewire/resource.c
new file mode 100644
index 0000000..0a6231c
--- /dev/null
+++ b/src/pipewire/resource.c
@@ -0,0 +1,351 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <assert.h>
+
+#include "pipewire/private.h"
+#include "pipewire/protocol.h"
+#include "pipewire/resource.h"
+#include "pipewire/type.h"
+
+#include <spa/debug/types.h>
+
+PW_LOG_TOPIC_EXTERN(log_resource);
+#define PW_LOG_TOPIC_DEFAULT log_resource
+
+/** \cond */
+struct impl {
+ struct pw_resource this;
+};
+/** \endcond */
+
+SPA_EXPORT
+struct pw_resource *pw_resource_new(struct pw_impl_client *client,
+ uint32_t id,
+ uint32_t permissions,
+ const char *type,
+ uint32_t version,
+ size_t user_data_size)
+{
+ struct impl *impl;
+ struct pw_resource *this;
+ int res;
+
+ impl = calloc(1, sizeof(struct impl) + user_data_size);
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+ this->refcount = 1;
+ this->context = client->context;
+ this->client = client;
+ this->permissions = permissions;
+ this->type = type;
+ this->version = version;
+ this->bound_id = SPA_ID_INVALID;
+
+ spa_hook_list_init(&this->listener_list);
+ spa_hook_list_init(&this->object_listener_list);
+
+ if (id == SPA_ID_INVALID) {
+ res = -EINVAL;
+ goto error_clean;
+ }
+
+ if ((res = pw_map_insert_at(&client->objects, id, this)) < 0) {
+ pw_log_error("%p: can't add id %u for client %p: %s",
+ this, id, client, spa_strerror(res));
+ goto error_clean;
+ }
+ this->id = id;
+
+ if ((res = pw_resource_install_marshal(this, false)) < 0) {
+ pw_log_error("%p: no marshal for type %s/%d: %s", this,
+ type, version, spa_strerror(res));
+ goto error_clean;
+ }
+
+
+ if (user_data_size > 0)
+ this->user_data = SPA_PTROFF(impl, sizeof(struct impl), void);
+
+ pw_log_debug("%p: new %u type %s/%d client:%p marshal:%p",
+ this, id, type, version, client, this->marshal);
+
+ pw_impl_client_emit_resource_added(client, this);
+
+ return this;
+
+error_clean:
+ free(impl);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+int pw_resource_install_marshal(struct pw_resource *this, bool implementor)
+{
+ struct pw_impl_client *client = this->client;
+ const struct pw_protocol_marshal *marshal;
+
+ marshal = pw_protocol_get_marshal(client->protocol,
+ this->type, this->version,
+ implementor ? PW_PROTOCOL_MARSHAL_FLAG_IMPL : 0);
+ if (marshal == NULL)
+ return -EPROTO;
+
+ this->marshal = marshal;
+ this->type = marshal->type;
+
+ this->impl = SPA_INTERFACE_INIT(
+ this->type,
+ this->marshal->version,
+ this->marshal->server_marshal, this);
+ return 0;
+}
+
+SPA_EXPORT
+struct pw_impl_client *pw_resource_get_client(struct pw_resource *resource)
+{
+ return resource->client;
+}
+
+SPA_EXPORT
+uint32_t pw_resource_get_id(struct pw_resource *resource)
+{
+ return resource->id;
+}
+
+SPA_EXPORT
+uint32_t pw_resource_get_permissions(struct pw_resource *resource)
+{
+ return resource->permissions;
+}
+
+SPA_EXPORT
+const char *pw_resource_get_type(struct pw_resource *resource, uint32_t *version)
+{
+ if (version)
+ *version = resource->version;
+ return resource->type;
+}
+
+SPA_EXPORT
+struct pw_protocol *pw_resource_get_protocol(struct pw_resource *resource)
+{
+ return resource->client->protocol;
+}
+
+SPA_EXPORT
+void *pw_resource_get_user_data(struct pw_resource *resource)
+{
+ return resource->user_data;
+}
+
+SPA_EXPORT
+void pw_resource_add_listener(struct pw_resource *resource,
+ struct spa_hook *listener,
+ const struct pw_resource_events *events,
+ void *data)
+{
+ spa_hook_list_append(&resource->listener_list, listener, events, data);
+}
+
+SPA_EXPORT
+void pw_resource_add_object_listener(struct pw_resource *resource,
+ struct spa_hook *listener,
+ const void *funcs,
+ void *data)
+{
+ spa_hook_list_append(&resource->object_listener_list, listener, funcs, data);
+}
+
+SPA_EXPORT
+struct spa_hook_list *pw_resource_get_object_listeners(struct pw_resource *resource)
+{
+ return &resource->object_listener_list;
+}
+
+SPA_EXPORT
+const struct pw_protocol_marshal *pw_resource_get_marshal(struct pw_resource *resource)
+{
+ return resource->marshal;
+}
+
+SPA_EXPORT
+int pw_resource_ping(struct pw_resource *resource, int seq)
+{
+ int res = -EIO;
+ struct pw_impl_client *client = resource->client;
+
+ if (client->core_resource != NULL) {
+ pw_core_resource_ping(client->core_resource, resource->id, seq);
+ res = client->send_seq;
+ pw_log_debug("%p: %u seq:%d ping %d", resource, resource->id, seq, res);
+ }
+ return res;
+}
+
+SPA_EXPORT
+int pw_resource_set_bound_id(struct pw_resource *resource, uint32_t global_id)
+{
+ struct pw_impl_client *client = resource->client;
+
+ resource->bound_id = global_id;
+ if (client->core_resource != NULL) {
+ pw_log_debug("%p: %u global_id:%u", resource, resource->id, global_id);
+ pw_core_resource_bound_id(client->core_resource, resource->id, global_id);
+ }
+ return 0;
+}
+
+SPA_EXPORT
+uint32_t pw_resource_get_bound_id(struct pw_resource *resource)
+{
+ return resource->bound_id;
+}
+
+static void SPA_PRINTF_FUNC(4, 0)
+pw_resource_errorv_id(struct pw_resource *resource, uint32_t id, int res, const char *error, va_list ap)
+{
+ struct pw_impl_client *client;
+
+ if (resource) {
+ client = resource->client;
+ if (client->core_resource != NULL)
+ pw_core_resource_errorv(client->core_resource,
+ id, client->recv_seq, res, error, ap);
+ } else {
+ pw_logtv(SPA_LOG_LEVEL_ERROR, PW_LOG_TOPIC_DEFAULT, error, ap);
+ }
+}
+
+SPA_EXPORT
+void pw_resource_errorf(struct pw_resource *resource, int res, const char *error, ...)
+{
+ va_list ap;
+ va_start(ap, error);
+ if (resource)
+ pw_resource_errorv_id(resource, resource->id, res, error, ap);
+ else
+ pw_logtv(SPA_LOG_LEVEL_ERROR, PW_LOG_TOPIC_DEFAULT, error, ap);
+ va_end(ap);
+}
+
+SPA_EXPORT
+void pw_resource_errorf_id(struct pw_resource *resource, uint32_t id, int res, const char *error, ...)
+{
+ va_list ap;
+ va_start(ap, error);
+ if (resource)
+ pw_resource_errorv_id(resource, id, res, error, ap);
+ else
+ pw_logtv(SPA_LOG_LEVEL_ERROR, PW_LOG_TOPIC_DEFAULT, error, ap);
+ va_end(ap);
+}
+
+SPA_EXPORT
+void pw_resource_error(struct pw_resource *resource, int res, const char *error)
+{
+ struct pw_impl_client *client;
+ if (resource) {
+ client = resource->client;
+ if (client->core_resource != NULL)
+ pw_core_resource_error(client->core_resource,
+ resource->id, client->recv_seq, res, error);
+ } else {
+ pw_log_error("%s: %s", error, spa_strerror(res));
+ }
+}
+
+SPA_EXPORT
+void pw_resource_ref(struct pw_resource *resource)
+{
+ assert(resource->refcount > 0);
+ resource->refcount++;
+}
+
+SPA_EXPORT
+void pw_resource_unref(struct pw_resource *resource)
+{
+ assert(resource->refcount > 0);
+ if (--resource->refcount > 0)
+ return;
+
+ pw_log_debug("%p: free %u", resource, resource->id);
+ assert(resource->destroyed);
+
+#if DEBUG_LISTENERS
+ {
+ struct spa_hook *h;
+ spa_list_for_each(h, &resource->object_listener_list.list, link) {
+ pw_log_warn("%p: resource %u: leaked object listener %p",
+ resource, resource->id, h);
+ break;
+ }
+ spa_list_for_each(h, &resource->listener_list.list, link) {
+ pw_log_warn("%p: resource %u: leaked listener %p",
+ resource, resource->id, h);
+ break;
+ }
+ }
+#endif
+ spa_hook_list_clean(&resource->listener_list);
+ spa_hook_list_clean(&resource->object_listener_list);
+
+ free(resource);
+}
+
+SPA_EXPORT
+void pw_resource_destroy(struct pw_resource *resource)
+{
+ struct pw_impl_client *client = resource->client;
+
+ pw_log_debug("%p: destroy %u", resource, resource->id);
+ assert(!resource->destroyed);
+ resource->destroyed = true;
+
+ if (resource->global) {
+ spa_list_remove(&resource->link);
+ resource->global = NULL;
+ }
+
+ pw_resource_emit_destroy(resource);
+
+ pw_map_insert_at(&client->objects, resource->id, NULL);
+ pw_impl_client_emit_resource_removed(client, resource);
+
+ if (client->core_resource && !resource->removed)
+ pw_core_resource_remove_id(client->core_resource, resource->id);
+
+ pw_resource_unref(resource);
+}
+
+SPA_EXPORT
+void pw_resource_remove(struct pw_resource *resource)
+{
+ resource->removed = true;
+ pw_resource_destroy(resource);
+}
diff --git a/src/pipewire/resource.h b/src/pipewire/resource.h
new file mode 100644
index 0000000..24d458c
--- /dev/null
+++ b/src/pipewire/resource.h
@@ -0,0 +1,174 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_RESOURCE_H
+#define PIPEWIRE_RESOURCE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/hook.h>
+
+/** \defgroup pw_resource Resource
+ *
+ * \brief Client owned objects
+ *
+ * Resources represent objects owned by a \ref pw_impl_client. They are
+ * the result of binding to a global resource or by calling API that
+ * creates client owned objects.
+ *
+ * The client usually has a proxy object associated with the resource
+ * that it can use to communicate with the resource. See \ref page_proxy.
+ *
+ * Resources are destroyed when the client or the bound object is
+ * destroyed.
+ *
+ */
+
+
+/**
+ * \addtogroup pw_resource
+ * \{
+ */
+struct pw_resource;
+
+#include <pipewire/impl-client.h>
+
+/** Resource events */
+struct pw_resource_events {
+#define PW_VERSION_RESOURCE_EVENTS 0
+ uint32_t version;
+
+ /** The resource is destroyed */
+ void (*destroy) (void *data);
+
+ /** a reply to a ping event completed */
+ void (*pong) (void *data, int seq);
+
+ /** an error occurred on the resource */
+ void (*error) (void *data, int seq, int res, const char *message);
+};
+
+/** Make a new resource for client */
+struct pw_resource *
+pw_resource_new(struct pw_impl_client *client, /**< the client owning the resource */
+ uint32_t id, /**< the remote per client id */
+ uint32_t permissions, /**< permissions on this resource */
+ const char *type, /**< interface of the resource */
+ uint32_t version, /**< requested interface version */
+ size_t user_data_size /**< extra user data size */);
+
+/** Destroy a resource */
+void pw_resource_destroy(struct pw_resource *resource);
+
+/** Remove a resource, like pw_resource_destroy but without sending a
+ * remove_id message to the client */
+void pw_resource_remove(struct pw_resource *resource);
+
+/** Get the client owning this resource */
+struct pw_impl_client *pw_resource_get_client(struct pw_resource *resource);
+
+/** Get the unique id of this resource */
+uint32_t pw_resource_get_id(struct pw_resource *resource);
+
+/** Get the permissions of this resource */
+uint32_t pw_resource_get_permissions(struct pw_resource *resource);
+
+/** Get the type and optionally the version of this resource */
+const char *pw_resource_get_type(struct pw_resource *resource, uint32_t *version);
+
+/** Get the protocol used for this resource */
+struct pw_protocol *pw_resource_get_protocol(struct pw_resource *resource);
+
+/** Get the user data for the resource, the size was given in \ref pw_resource_new */
+void *pw_resource_get_user_data(struct pw_resource *resource);
+
+/** Add an event listener */
+void pw_resource_add_listener(struct pw_resource *resource,
+ struct spa_hook *listener,
+ const struct pw_resource_events *events,
+ void *data);
+
+/** Set the resource implementation. */
+void pw_resource_add_object_listener(struct pw_resource *resource,
+ struct spa_hook *listener,
+ const void *funcs,
+ void *data);
+
+/** Generate an ping event for a resource. This will generate a pong event
+ * with the same \a sequence number in the return value. */
+int pw_resource_ping(struct pw_resource *resource, int seq);
+
+/** ref/unref a resource, Since 0.3.52 */
+void pw_resource_ref(struct pw_resource *resource);
+void pw_resource_unref(struct pw_resource *resource);
+
+/** Notify global id this resource is bound to */
+int pw_resource_set_bound_id(struct pw_resource *resource, uint32_t global_id);
+
+/** Get the global id this resource is bound to or SPA_ID_INVALID when not bound */
+uint32_t pw_resource_get_bound_id(struct pw_resource *resource);
+
+/** Generate an error for a resource */
+void pw_resource_error(struct pw_resource *resource, int res, const char *error);
+void pw_resource_errorf(struct pw_resource *resource, int res, const char *error, ...) SPA_PRINTF_FUNC(3, 4);
+void pw_resource_errorf_id(struct pw_resource *resource, uint32_t id, int res, const char *error, ...) SPA_PRINTF_FUNC(4, 5);
+
+/** Get the list of object listeners from a resource */
+struct spa_hook_list *pw_resource_get_object_listeners(struct pw_resource *resource);
+
+/** Get the marshal functions for the resource */
+const struct pw_protocol_marshal *pw_resource_get_marshal(struct pw_resource *resource);
+
+/** install a marshal function on a resource */
+int pw_resource_install_marshal(struct pw_resource *resource, bool implementor);
+
+#define pw_resource_notify(r,type,event,version,...) \
+ spa_hook_list_call(pw_resource_get_object_listeners(r), \
+ type, event, version, ## __VA_ARGS__)
+
+#define pw_resource_call(r,type,method,version,...) \
+ spa_interface_call((struct spa_interface*)r, \
+ type, method, version, ##__VA_ARGS__)
+
+#define pw_resource_call_res(r,type,method,version,...) \
+({ \
+ int _res = -ENOTSUP; \
+ spa_interface_call_res((struct spa_interface*)r, \
+ type, _res, method, version, ##__VA_ARGS__); \
+ _res; \
+})
+
+
+/**
+ * \}
+ */
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_RESOURCE_H */
diff --git a/src/pipewire/settings.c b/src/pipewire/settings.c
new file mode 100644
index 0000000..c512e96
--- /dev/null
+++ b/src/pipewire/settings.c
@@ -0,0 +1,337 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/monitor/device.h>
+#include <spa/monitor/utils.h>
+#include <spa/pod/filter.h>
+#include <spa/utils/json.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/private.h>
+#include "pipewire/array.h"
+#include "pipewire/core.h"
+
+#include <pipewire/extensions/metadata.h>
+
+#define NAME "settings"
+
+#define DEFAULT_CLOCK_RATE 48000u
+#define DEFAULT_CLOCK_RATES "[ 48000 ]"
+#define DEFAULT_CLOCK_QUANTUM 1024u
+#define DEFAULT_CLOCK_MIN_QUANTUM 32u
+#define DEFAULT_CLOCK_MAX_QUANTUM 2048u
+#define DEFAULT_CLOCK_QUANTUM_LIMIT 8192u
+#define DEFAULT_CLOCK_POWER_OF_TWO_QUANTUM true
+#define DEFAULT_VIDEO_WIDTH 640
+#define DEFAULT_VIDEO_HEIGHT 480
+#define DEFAULT_VIDEO_RATE_NUM 25u
+#define DEFAULT_VIDEO_RATE_DENOM 1u
+#define DEFAULT_LINK_MAX_BUFFERS 64u
+#define DEFAULT_MEM_WARN_MLOCK false
+#define DEFAULT_MEM_ALLOW_MLOCK true
+#define DEFAULT_CHECK_QUANTUM false
+#define DEFAULT_CHECK_RATE false
+
+struct impl {
+ struct pw_context *context;
+ struct pw_impl_metadata *metadata;
+
+ struct spa_hook metadata_listener;
+};
+
+static void metadata_destroy(void *data)
+{
+ struct impl *impl = data;
+ spa_hook_remove(&impl->metadata_listener);
+ impl->metadata = NULL;
+}
+
+static uint32_t get_default_int(struct pw_properties *properties, const char *name, uint32_t def)
+{
+ uint32_t val;
+ const char *str;
+ if ((str = pw_properties_get(properties, name)) != NULL)
+ val = atoi(str);
+ else {
+ val = def;
+ pw_properties_setf(properties, name, "%d", val);
+ }
+ return val;
+}
+
+static bool get_default_bool(struct pw_properties *properties, const char *name, bool def)
+{
+ bool val;
+ const char *str;
+ if ((str = pw_properties_get(properties, name)) != NULL)
+ val = pw_properties_parse_bool(str);
+ else {
+ val = def;
+ pw_properties_set(properties, name, val ? "true" : "false");
+ }
+ return val;
+}
+
+static bool uint32_array_contains(uint32_t *vals, uint32_t n_vals, uint32_t val)
+{
+ uint32_t i;
+ for (i = 0; i < n_vals; i++)
+ if (vals[i] == val)
+ return true;
+ return false;
+}
+
+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];
+ 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));
+
+ while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 &&
+ count < max) {
+ if (spa_atou32(v, &r, 0))
+ vals[count++] = r;
+ }
+ if (!uint32_array_contains(vals, count, def))
+ count = 0;
+ return count;
+}
+
+static uint32_t parse_clock_rate(struct pw_properties *properties, const char *name,
+ uint32_t *rates, const char *def_rates, uint32_t def)
+{
+ const char *str;
+ uint32_t count = 0;
+
+ if ((str = pw_properties_get(properties, name)) == NULL)
+ str = def_rates;
+
+ count = parse_uint32_array(str, rates, MAX_RATES, def);
+ if (count == 0)
+ count = parse_uint32_array(def_rates, rates, MAX_RATES, def);
+ if (count == 0)
+ goto fallback;
+
+ return count;
+fallback:
+ rates[0] = def;
+ pw_properties_setf(properties, name, "[ %u ]", def);
+ return 1;
+}
+
+static int metadata_property(void *data, uint32_t subject, const char *key,
+ const char *type, const char *value)
+{
+ struct impl *impl = data;
+ struct pw_context *context = impl->context;
+ struct settings *d = &context->defaults;
+ struct settings *s = &context->settings;
+ uint32_t v;
+ bool recalc = false;
+
+ if (subject != PW_ID_CORE)
+ return 0;
+
+ if (spa_streq(key, "log.level")) {
+ v = value ? atoi(value) : 3;
+ pw_log_set_level(v);
+ } else if (spa_streq(key, "clock.rate")) {
+ v = value ? atoi(value) : 0;
+ s->clock_rate = v == 0 ? d->clock_rate : v;
+ recalc = true;
+ } else if (spa_streq(key, "clock.allowed-rates")) {
+ s->n_clock_rates = parse_uint32_array(value,
+ s->clock_rates, MAX_RATES, s->clock_rate);
+ if (s->n_clock_rates == 0) {
+ s->n_clock_rates = d->n_clock_rates;
+ memcpy(s->clock_rates, d->clock_rates, MAX_RATES * sizeof(uint32_t));
+ }
+ recalc = true;
+ } else if (spa_streq(key, "clock.quantum")) {
+ v = value ? atoi(value) : 0;
+ s->clock_quantum = v == 0 ? d->clock_quantum : v;
+ recalc = true;
+ } else if (spa_streq(key, "clock.min-quantum")) {
+ v = value ? atoi(value) : 0;
+ s->clock_min_quantum = v == 0 ? d->clock_min_quantum : v;
+ recalc = true;
+ } else if (spa_streq(key, "clock.max-quantum")) {
+ v = value ? atoi(value) : 0;
+ s->clock_max_quantum = v == 0 ? d->clock_max_quantum : v;
+ recalc = true;
+ } else if (spa_streq(key, "clock.force-rate")) {
+ v = value ? atoi(value) : 0;
+ if (v != 0 && s->check_rate &&
+ !uint32_array_contains(s->clock_rates, s->n_clock_rates, v)) {
+ pw_log_info("invalid %s: %d not in allowed rates", key, v);
+ } else {
+ s->clock_force_rate = v;
+ recalc = true;
+ }
+ } else if (spa_streq(key, "clock.force-quantum")) {
+ v = value ? atoi(value) : 0;
+ if (v != 0 && s->check_quantum &&
+ (v < s->clock_min_quantum || v > s->clock_max_quantum)) {
+ pw_log_info("invalid %s: %d not in (%d-%d)", key, v,
+ s->clock_min_quantum, s->clock_max_quantum);
+ } else {
+ s->clock_force_quantum = v;
+ recalc = true;
+ }
+ }
+ if (recalc)
+ pw_context_recalc_graph(context, "settings changed");
+
+ return 0;
+}
+
+static const struct pw_impl_metadata_events metadata_events = {
+ PW_VERSION_IMPL_METADATA_EVENTS,
+ .destroy = metadata_destroy,
+ .property = metadata_property,
+};
+
+void pw_settings_init(struct pw_context *this)
+{
+ struct pw_properties *p = this->properties;
+ struct settings *d = &this->defaults;
+
+ d->clock_rate = get_default_int(p, "default.clock.rate", DEFAULT_CLOCK_RATE);
+ d->n_clock_rates = parse_clock_rate(p, "default.clock.allowed-rates", d->clock_rates,
+ DEFAULT_CLOCK_RATES, d->clock_rate);
+ d->clock_quantum = get_default_int(p, "default.clock.quantum", DEFAULT_CLOCK_QUANTUM);
+ d->clock_min_quantum = get_default_int(p, "default.clock.min-quantum", DEFAULT_CLOCK_MIN_QUANTUM);
+ d->clock_max_quantum = get_default_int(p, "default.clock.max-quantum", DEFAULT_CLOCK_MAX_QUANTUM);
+ d->clock_quantum_limit = get_default_int(p, "default.clock.quantum-limit", DEFAULT_CLOCK_QUANTUM_LIMIT);
+ d->video_size.width = get_default_int(p, "default.video.width", DEFAULT_VIDEO_WIDTH);
+ d->video_size.height = get_default_int(p, "default.video.height", DEFAULT_VIDEO_HEIGHT);
+ d->video_rate.num = get_default_int(p, "default.video.rate.num", DEFAULT_VIDEO_RATE_NUM);
+ d->video_rate.denom = get_default_int(p, "default.video.rate.denom", DEFAULT_VIDEO_RATE_DENOM);
+
+ d->log_level = get_default_int(p, "log.level", pw_log_level);
+ d->clock_power_of_two_quantum = get_default_bool(p, "clock.power-of-two-quantum",
+ DEFAULT_CLOCK_POWER_OF_TWO_QUANTUM);
+ d->link_max_buffers = get_default_int(p, "link.max-buffers", DEFAULT_LINK_MAX_BUFFERS);
+ d->mem_warn_mlock = get_default_bool(p, "mem.warn-mlock", DEFAULT_MEM_WARN_MLOCK);
+ d->mem_allow_mlock = get_default_bool(p, "mem.allow-mlock", DEFAULT_MEM_ALLOW_MLOCK);
+
+ d->check_quantum = get_default_bool(p, "settings.check-quantum", DEFAULT_CHECK_QUANTUM);
+ d->check_rate = get_default_bool(p, "settings.check-rate", DEFAULT_CHECK_RATE);
+
+ d->clock_quantum_limit = SPA_CLAMP(d->clock_quantum_limit,
+ CLOCK_MIN_QUANTUM, CLOCK_MAX_QUANTUM);
+ d->clock_max_quantum = SPA_CLAMP(d->clock_max_quantum,
+ CLOCK_MIN_QUANTUM, d->clock_quantum_limit);
+ d->clock_min_quantum = SPA_CLAMP(d->clock_min_quantum,
+ CLOCK_MIN_QUANTUM, d->clock_max_quantum);
+ d->clock_quantum = SPA_CLAMP(d->clock_quantum,
+ d->clock_min_quantum, d->clock_max_quantum);
+}
+
+static void expose_settings(struct pw_context *context, struct pw_impl_metadata *metadata)
+{
+ struct settings *s = &context->settings;
+ uint32_t i, o;
+ char rates[MAX_RATES*16] = "";
+
+ pw_impl_metadata_set_propertyf(metadata,
+ PW_ID_CORE, "log.level", "", "%d", s->log_level);
+ pw_impl_metadata_set_propertyf(metadata,
+ PW_ID_CORE, "clock.rate", "", "%d", s->clock_rate);
+ for (i = 0, o = 0; i < s->n_clock_rates; i++) {
+ int r = snprintf(rates+o, sizeof(rates)-o, "%s%d", i == 0 ? "" : ", ",
+ s->clock_rates[i]);
+ if (r < 0 || o + r >= (int)sizeof(rates)) {
+ snprintf(rates, sizeof(rates), "%d", s->clock_rate);
+ break;
+ }
+ o += r;
+ }
+ if (s->n_clock_rates == 0)
+ snprintf(rates, sizeof(rates), "%d", s->clock_rate);
+
+ pw_impl_metadata_set_propertyf(metadata,
+ PW_ID_CORE, "clock.allowed-rates", "", "[ %s ]", rates);
+ pw_impl_metadata_set_propertyf(metadata,
+ PW_ID_CORE, "clock.quantum", "", "%d", s->clock_quantum);
+ pw_impl_metadata_set_propertyf(metadata,
+ PW_ID_CORE, "clock.min-quantum", "", "%d", s->clock_min_quantum);
+ pw_impl_metadata_set_propertyf(metadata,
+ PW_ID_CORE, "clock.max-quantum", "", "%d", s->clock_max_quantum);
+ pw_impl_metadata_set_propertyf(metadata,
+ PW_ID_CORE, "clock.force-quantum", "", "%d", s->clock_force_quantum);
+ pw_impl_metadata_set_propertyf(metadata,
+ PW_ID_CORE, "clock.force-rate", "", "%d", s->clock_force_rate);
+}
+
+int pw_settings_expose(struct pw_context *context)
+{
+ struct impl *impl;
+
+ impl = calloc(1, sizeof(*impl));
+ if (impl == NULL)
+ return -errno;
+
+ impl->context = context;
+ impl->metadata = pw_context_create_metadata(context, "settings", NULL, 0);
+ if (impl->metadata == NULL)
+ goto error_free;
+
+ expose_settings(context, impl->metadata);
+
+ pw_impl_metadata_add_listener(impl->metadata,
+ &impl->metadata_listener,
+ &metadata_events, impl);
+
+ pw_impl_metadata_register(impl->metadata, NULL);
+
+ context->settings_impl = impl;
+
+ return 0;
+
+error_free:
+ free(impl);
+ return -errno;
+}
+
+void pw_settings_clean(struct pw_context *context)
+{
+ struct impl *impl = context->settings_impl;
+
+ if (impl == NULL)
+ return;
+
+ context->settings_impl = NULL;
+ if (impl->metadata != NULL)
+ pw_impl_metadata_destroy(impl->metadata);
+ free(impl);
+}
diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c
new file mode 100644
index 0000000..da49b66
--- /dev/null
+++ b/src/pipewire/stream.c
@@ -0,0 +1,2414 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <math.h>
+#include <sys/mman.h>
+#include <time.h>
+
+#include <spa/buffer/alloc.h>
+#include <spa/param/props.h>
+#include <spa/param/format-utils.h>
+#include <spa/node/io.h>
+#include <spa/node/utils.h>
+#include <spa/utils/ringbuffer.h>
+#include <spa/pod/filter.h>
+#include <spa/pod/dynamic.h>
+#include <spa/debug/types.h>
+
+#define PW_ENABLE_DEPRECATED
+
+#include "pipewire/pipewire.h"
+#include "pipewire/stream.h"
+#include "pipewire/private.h"
+
+PW_LOG_TOPIC_EXTERN(log_stream);
+#define PW_LOG_TOPIC_DEFAULT log_stream
+
+#define MAX_BUFFERS 64
+
+#define MASK_BUFFERS (MAX_BUFFERS-1)
+
+static bool mlock_warned = false;
+
+static uint32_t mappable_dataTypes = (1<<SPA_DATA_MemFd);
+
+struct buffer {
+ struct pw_buffer this;
+ uint32_t id;
+#define BUFFER_FLAG_MAPPED (1 << 0)
+#define BUFFER_FLAG_QUEUED (1 << 1)
+#define BUFFER_FLAG_ADDED (1 << 2)
+ uint32_t flags;
+ struct spa_meta_busy *busy;
+};
+
+struct queue {
+ uint32_t ids[MAX_BUFFERS];
+ struct spa_ringbuffer ring;
+ uint64_t incount;
+ uint64_t outcount;
+};
+
+struct data {
+ struct pw_context *context;
+ struct spa_hook stream_listener;
+};
+
+struct param {
+ uint32_t id;
+#define PARAM_FLAG_LOCKED (1 << 0)
+ uint32_t flags;
+ struct spa_list link;
+ struct spa_pod *param;
+};
+
+struct control {
+ uint32_t id;
+ uint32_t type;
+ uint32_t container;
+ struct spa_list link;
+ struct pw_stream_control control;
+ struct spa_pod *info;
+ unsigned int emitted:1;
+ float values[64];
+};
+
+struct stream {
+ struct pw_stream this;
+
+ const char *path;
+
+ struct pw_context *context;
+ struct spa_hook context_listener;
+
+ enum spa_direction direction;
+ enum pw_stream_flags flags;
+
+ struct pw_impl_node *node;
+
+ struct spa_node impl_node;
+ struct spa_node_methods node_methods;
+ struct spa_hook_list hooks;
+ struct spa_callbacks callbacks;
+
+ struct spa_io_clock *clock;
+ struct spa_io_position *position;
+ struct spa_io_buffers *io;
+ struct spa_io_rate_match *rate_match;
+ uint32_t rate_queued;
+ struct {
+ struct spa_io_position *position;
+ } rt;
+
+ uint32_t port_change_mask_all;
+ struct spa_port_info port_info;
+ struct pw_properties *port_props;
+#define PORT_EnumFormat 0
+#define PORT_Meta 1
+#define PORT_IO 2
+#define PORT_Format 3
+#define PORT_Buffers 4
+#define PORT_Latency 5
+#define N_PORT_PARAMS 6
+ struct spa_param_info port_params[N_PORT_PARAMS];
+
+ struct spa_list param_list;
+
+ uint32_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
+ struct spa_param_info params[N_NODE_PARAMS];
+
+ uint32_t media_type;
+ uint32_t media_subtype;
+
+ struct buffer buffers[MAX_BUFFERS];
+ uint32_t n_buffers;
+
+ struct queue dequeued;
+ struct queue queued;
+
+ struct data data;
+ uintptr_t seq;
+ struct pw_time time;
+ uint64_t base_pos;
+ uint32_t clock_id;
+ struct spa_latency_info latency;
+ uint64_t quantum;
+
+ struct spa_callbacks rt_callbacks;
+
+ unsigned int disconnecting:1;
+ unsigned int disconnect_core:1;
+ unsigned int draining:1;
+ unsigned int drained:1;
+ unsigned int allow_mlock:1;
+ unsigned int warn_mlock:1;
+ unsigned int process_rt:1;
+ unsigned int driving:1;
+ unsigned int using_trigger:1;
+ unsigned int trigger:1;
+ int in_set_control;
+};
+
+static int get_param_index(uint32_t id)
+{
+ switch (id) {
+ case SPA_PARAM_PropInfo:
+ return NODE_PropInfo;
+ case SPA_PARAM_Props:
+ return NODE_Props;
+ case SPA_PARAM_EnumFormat:
+ return NODE_EnumFormat;
+ case SPA_PARAM_Format:
+ return NODE_Format;
+ default:
+ return -1;
+ }
+}
+
+static int get_port_param_index(uint32_t id)
+{
+ switch (id) {
+ case SPA_PARAM_EnumFormat:
+ return PORT_EnumFormat;
+ case SPA_PARAM_Meta:
+ return PORT_Meta;
+ case SPA_PARAM_IO:
+ return PORT_IO;
+ case SPA_PARAM_Format:
+ return PORT_Format;
+ case SPA_PARAM_Buffers:
+ return PORT_Buffers;
+ case SPA_PARAM_Latency:
+ return PORT_Latency;
+ default:
+ return -1;
+ }
+}
+
+static void fix_datatype(const struct spa_pod *param)
+{
+ const struct spa_pod_prop *pod_param;
+ const struct spa_pod *vals;
+ uint32_t dataType, n_vals, choice;
+
+ pod_param = spa_pod_find_prop(param, NULL, SPA_PARAM_BUFFERS_dataType);
+ if (pod_param == NULL)
+ return;
+
+ vals = spa_pod_get_values(&pod_param->value, &n_vals, &choice);
+ if (n_vals == 0)
+ return;
+
+ if (spa_pod_get_int(&vals[0], (int32_t*)&dataType) < 0)
+ return;
+
+ pw_log_debug("dataType: %u", dataType);
+ if (dataType & (1u << SPA_DATA_MemPtr)) {
+ SPA_POD_VALUE(struct spa_pod_int, &vals[0]) =
+ dataType | mappable_dataTypes;
+ pw_log_debug("Change dataType: %u -> %u", dataType,
+ SPA_POD_VALUE(struct spa_pod_int, &vals[0]));
+ }
+}
+
+static struct param *add_param(struct stream *impl,
+ uint32_t id, uint32_t flags, const struct spa_pod *param)
+{
+ struct param *p;
+ int idx;
+
+ if (param == NULL || !spa_pod_is_object(param)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ if (id == SPA_ID_INVALID)
+ id = SPA_POD_OBJECT_ID(param);
+
+ p = malloc(sizeof(struct param) + SPA_POD_SIZE(param));
+ if (p == NULL)
+ return NULL;
+
+ if (id == SPA_PARAM_Buffers &&
+ SPA_FLAG_IS_SET(impl->flags, PW_STREAM_FLAG_MAP_BUFFERS) &&
+ impl->direction == SPA_DIRECTION_INPUT)
+ fix_datatype(param);
+
+ p->id = id;
+ p->flags = flags;
+ p->param = SPA_PTROFF(p, sizeof(struct param), struct spa_pod);
+ memcpy(p->param, param, SPA_POD_SIZE(param));
+ SPA_POD_OBJECT_ID(p->param) = id;
+
+ spa_list_append(&impl->param_list, &p->link);
+
+ if ((idx = get_param_index(id)) != -1) {
+ impl->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS;
+ impl->params[idx].flags |= SPA_PARAM_INFO_READ;
+ impl->params[idx].user++;
+ }
+ if ((idx = get_port_param_index(id)) != -1) {
+ impl->port_info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
+ impl->port_params[idx].flags |= SPA_PARAM_INFO_READ;
+ impl->port_params[idx].user++;
+ }
+ return p;
+}
+
+static void clear_params(struct stream *impl, uint32_t id)
+{
+ struct param *p, *t;
+
+ spa_list_for_each_safe(p, t, &impl->param_list, link) {
+ if (id == SPA_ID_INVALID ||
+ (p->id == id && !(p->flags & PARAM_FLAG_LOCKED))) {
+ spa_list_remove(&p->link);
+ free(p);
+ }
+ }
+}
+
+static int update_params(struct stream *impl, uint32_t id,
+ const struct spa_pod **params, uint32_t n_params)
+{
+ uint32_t i;
+ int res = 0;
+
+ if (id != SPA_ID_INVALID) {
+ clear_params(impl, id);
+ } else {
+ for (i = 0; i < n_params; i++) {
+ if (params[i] == NULL || !spa_pod_is_object(params[i]))
+ continue;
+ clear_params(impl, SPA_POD_OBJECT_ID(params[i]));
+ }
+ }
+ for (i = 0; i < n_params; i++) {
+ if (add_param(impl, id, 0, params[i]) == NULL) {
+ res = -errno;
+ break;
+ }
+ }
+ return res;
+}
+
+
+static inline int queue_push(struct stream *stream, struct queue *queue, struct buffer *buffer)
+{
+ uint32_t index;
+
+ if (SPA_FLAG_IS_SET(buffer->flags, BUFFER_FLAG_QUEUED) ||
+ buffer->id >= stream->n_buffers)
+ return -EINVAL;
+
+ SPA_FLAG_SET(buffer->flags, BUFFER_FLAG_QUEUED);
+ queue->incount += buffer->this.size;
+
+ spa_ringbuffer_get_write_index(&queue->ring, &index);
+ queue->ids[index & MASK_BUFFERS] = buffer->id;
+ spa_ringbuffer_write_update(&queue->ring, index + 1);
+
+ return 0;
+}
+
+static inline bool queue_is_empty(struct stream *stream, struct queue *queue)
+{
+ uint32_t index;
+ return spa_ringbuffer_get_read_index(&queue->ring, &index) < 1;
+}
+
+static inline struct buffer *queue_pop(struct stream *stream, struct queue *queue)
+{
+ uint32_t index, id;
+ struct buffer *buffer;
+
+ if (spa_ringbuffer_get_read_index(&queue->ring, &index) < 1) {
+ errno = EPIPE;
+ return NULL;
+ }
+
+ id = queue->ids[index & MASK_BUFFERS];
+ spa_ringbuffer_read_update(&queue->ring, index + 1);
+
+ buffer = &stream->buffers[id];
+ queue->outcount += buffer->this.size;
+ SPA_FLAG_CLEAR(buffer->flags, BUFFER_FLAG_QUEUED);
+
+ return buffer;
+}
+static inline void clear_queue(struct stream *stream, struct queue *queue)
+{
+ spa_ringbuffer_init(&queue->ring);
+ queue->incount = queue->outcount;
+}
+
+static bool stream_set_state(struct pw_stream *stream, enum pw_stream_state state, const char *error)
+{
+ enum pw_stream_state old = stream->state;
+ bool res = old != state;
+
+ if (res) {
+ free(stream->error);
+ stream->error = error ? strdup(error) : NULL;
+
+ pw_log_debug("%p: update state from %s -> %s (%s)", stream,
+ pw_stream_state_as_string(old),
+ pw_stream_state_as_string(state), stream->error);
+
+ if (state == PW_STREAM_STATE_ERROR)
+ pw_log_error("%p: error %s", stream, error);
+
+ stream->state = state;
+ pw_stream_emit_state_changed(stream, old, state, error);
+ }
+ return res;
+}
+
+static struct buffer *get_buffer(struct pw_stream *stream, uint32_t id)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ if (id < impl->n_buffers)
+ return &impl->buffers[id];
+
+ errno = EINVAL;
+ return NULL;
+}
+
+static inline uint32_t update_requested(struct stream *impl)
+{
+ uint32_t index, id, res = 0;
+ struct buffer *buffer;
+ struct spa_io_rate_match *r = impl->rate_match;
+
+ if (spa_ringbuffer_get_read_index(&impl->dequeued.ring, &index) < 1)
+ return 0;
+
+ id = impl->dequeued.ids[index & MASK_BUFFERS];
+ buffer = &impl->buffers[id];
+ if (r) {
+ buffer->this.requested = r->size;
+ res = r->size > 0 ? 1 : 0;
+ } else {
+ buffer->this.requested = impl->quantum;
+ res = 1;
+ }
+ pw_log_trace_fp("%p: update buffer:%u size:%"PRIu64, impl, id, buffer->this.requested);
+ return res;
+}
+
+static int
+do_call_process(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct stream *impl = user_data;
+ struct pw_stream *stream = &impl->this;
+ pw_log_trace_fp("%p: do process", stream);
+ pw_stream_emit_process(stream);
+ return 0;
+}
+
+static inline void call_process(struct stream *impl)
+{
+ pw_log_trace_fp("%p: call process rt:%u", impl, impl->process_rt);
+ if (impl->direction == SPA_DIRECTION_OUTPUT && update_requested(impl) <= 0)
+ return;
+ if (impl->process_rt)
+ spa_callbacks_call(&impl->rt_callbacks, struct pw_stream_events, process, 0);
+ else
+ pw_loop_invoke(impl->context->main_loop,
+ do_call_process, 1, NULL, 0, false, impl);
+}
+
+static int
+do_call_drained(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct stream *impl = user_data;
+ struct pw_stream *stream = &impl->this;
+ pw_log_trace_fp("%p: drained", stream);
+ pw_stream_emit_drained(stream);
+ return 0;
+}
+
+static void call_drained(struct stream *impl)
+{
+ pw_loop_invoke(impl->context->main_loop,
+ do_call_drained, 1, NULL, 0, false, impl);
+}
+
+static int
+do_call_trigger_done(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct stream *impl = user_data;
+ struct pw_stream *stream = &impl->this;
+ pw_log_trace_fp("%p: trigger_done", stream);
+ pw_stream_emit_trigger_done(stream);
+ return 0;
+}
+
+static void call_trigger_done(struct stream *impl)
+{
+ pw_loop_invoke(impl->context->main_loop,
+ do_call_trigger_done, 1, NULL, 0, false, impl);
+}
+
+static int
+do_set_position(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct stream *impl = user_data;
+ impl->rt.position = impl->position;
+ return 0;
+}
+
+static int impl_set_io(void *object, uint32_t id, void *data, size_t size)
+{
+ struct stream *impl = object;
+ struct pw_stream *stream = &impl->this;
+
+ pw_log_debug("%p: set io id %d (%s) %p %zd", impl, id,
+ spa_debug_type_find_name(spa_type_io, id), data, size);
+
+ switch(id) {
+ case SPA_IO_Clock:
+ if (data && size >= sizeof(struct spa_io_clock))
+ impl->clock = data;
+ else
+ impl->clock = NULL;
+ break;
+ case SPA_IO_Position:
+ if (data && size >= sizeof(struct spa_io_position))
+ impl->position = data;
+ else
+ impl->position = NULL;
+
+ pw_loop_invoke(impl->context->data_loop,
+ do_set_position, 1, NULL, 0, true, impl);
+ break;
+ default:
+ break;
+ }
+ impl->driving = impl->clock && impl->position && impl->position->clock.id == impl->clock->id;
+ pw_stream_emit_io_changed(stream, id, data, size);
+
+ return 0;
+}
+
+static int enum_params(void *object, bool is_port, int seq, uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct stream *d = object;
+ struct spa_result_node_params result;
+ uint8_t buffer[1024];
+ struct spa_pod_dynamic_builder b;
+ uint32_t count = 0;
+ struct param *p;
+ bool found = false;
+
+ spa_return_val_if_fail(num != 0, -EINVAL);
+
+ result.id = id;
+ result.next = 0;
+
+ pw_log_debug("%p: param id %d (%s) start:%d num:%d", d, id,
+ spa_debug_type_find_name(spa_type_param, id),
+ start, num);
+
+ spa_list_for_each(p, &d->param_list, link) {
+ struct spa_pod *param;
+
+ param = p->param;
+ if (param == NULL || p->id != id)
+ continue;
+
+ found = true;
+
+ result.index = result.next++;
+ if (result.index < start)
+ continue;
+
+ spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
+ if (spa_pod_filter(&b.b, &result.param, param, filter) == 0) {
+ spa_node_emit_result(&d->hooks, seq, 0, SPA_RESULT_TYPE_NODE_PARAMS, &result);
+ count++;
+ }
+ spa_pod_dynamic_builder_clean(&b);
+
+ if (count == num)
+ break;
+ }
+ return found ? 0 : -ENOENT;
+}
+
+static int impl_enum_params(void *object, int seq, uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ return enum_params(object, false, seq, id, start, num, filter);
+}
+
+static int impl_set_param(void *object, uint32_t id, uint32_t flags, const struct spa_pod *param)
+{
+ struct stream *impl = object;
+ struct pw_stream *stream = &impl->this;
+
+ if (id != SPA_PARAM_Props)
+ return -ENOTSUP;
+
+ if (impl->in_set_control == 0)
+ pw_stream_emit_param_changed(stream, id, param);
+
+ return 0;
+}
+
+static inline void copy_position(struct stream *impl, int64_t queued)
+{
+ struct spa_io_position *p = impl->rt.position;
+
+ SEQ_WRITE(impl->seq);
+ if (SPA_LIKELY(p != NULL)) {
+ impl->time.now = p->clock.nsec;
+ impl->time.rate = p->clock.rate;
+ if (SPA_UNLIKELY(impl->clock_id != p->clock.id)) {
+ impl->base_pos = p->clock.position - impl->time.ticks;
+ impl->clock_id = p->clock.id;
+ }
+ impl->time.ticks = p->clock.position - impl->base_pos;
+ impl->time.delay = 0;
+ impl->time.queued = queued;
+ impl->quantum = p->clock.duration;
+ }
+ if (SPA_LIKELY(impl->rate_match != NULL))
+ impl->rate_queued = impl->rate_match->delay;
+ SEQ_WRITE(impl->seq);
+}
+
+static int impl_send_command(void *object, const struct spa_command *command)
+{
+ struct stream *impl = object;
+ struct pw_stream *stream = &impl->this;
+ uint32_t id = SPA_NODE_COMMAND_ID(command);
+
+ pw_log_info("%p: command %s", impl,
+ spa_debug_type_find_name(spa_type_node_command_id, id));
+
+ switch (id) {
+ case SPA_NODE_COMMAND_Suspend:
+ case SPA_NODE_COMMAND_Flush:
+ case SPA_NODE_COMMAND_Pause:
+ pw_loop_invoke(impl->context->main_loop,
+ NULL, 0, NULL, 0, false, impl);
+ if (stream->state == PW_STREAM_STATE_STREAMING) {
+
+ pw_log_debug("%p: pause", stream);
+ stream_set_state(stream, PW_STREAM_STATE_PAUSED, NULL);
+ }
+ break;
+ case SPA_NODE_COMMAND_Start:
+ if (stream->state == PW_STREAM_STATE_PAUSED) {
+ pw_log_debug("%p: start %d", stream, impl->direction);
+
+ if (impl->direction == SPA_DIRECTION_INPUT) {
+ if (impl->io != NULL)
+ impl->io->status = SPA_STATUS_NEED_DATA;
+ }
+ else if (!impl->process_rt && !impl->driving) {
+ copy_position(impl, impl->queued.incount);
+ call_process(impl);
+ }
+
+ stream_set_state(stream, PW_STREAM_STATE_STREAMING, NULL);
+ }
+ break;
+ default:
+ break;
+ }
+ pw_stream_emit_command(stream, command);
+ return 0;
+}
+
+static void emit_node_info(struct stream *d, bool full)
+{
+ uint32_t i;
+ uint64_t old = full ? d->info.change_mask : 0;
+ if (full)
+ d->info.change_mask = d->change_mask_all;
+ if (d->info.change_mask != 0) {
+ if (d->info.change_mask & SPA_NODE_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < d->info.n_params; i++) {
+ if (d->params[i].user > 0) {
+ d->params[i].flags ^= SPA_PARAM_INFO_SERIAL;
+ d->params[i].user = 0;
+ }
+ }
+ }
+ spa_node_emit_info(&d->hooks, &d->info);
+ }
+ d->info.change_mask = old;
+}
+
+static void emit_port_info(struct stream *d, bool full)
+{
+ uint32_t i;
+ uint64_t old = full ? d->port_info.change_mask : 0;
+ if (full)
+ d->port_info.change_mask = d->port_change_mask_all;
+ if (d->port_info.change_mask != 0) {
+ if (d->port_info.change_mask & SPA_PORT_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < d->port_info.n_params; i++) {
+ if (d->port_params[i].user > 0) {
+ d->port_params[i].flags ^= SPA_PARAM_INFO_SERIAL;
+ d->port_params[i].user = 0;
+ }
+ }
+ }
+ spa_node_emit_port_info(&d->hooks, d->direction, 0, &d->port_info);
+ }
+ d->port_info.change_mask = old;
+}
+
+static int impl_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct spa_node_events *events,
+ void *data)
+{
+ struct stream *d = object;
+ struct spa_hook_list save;
+
+ spa_hook_list_isolate(&d->hooks, &save, listener, events, data);
+
+ emit_node_info(d, true);
+ emit_port_info(d, true);
+
+ spa_hook_list_join(&d->hooks, &save);
+
+ return 0;
+}
+
+static int impl_set_callbacks(void *object,
+ const struct spa_node_callbacks *callbacks, void *data)
+{
+ struct stream *d = object;
+
+ d->callbacks = SPA_CALLBACKS_INIT(callbacks, data);
+
+ return 0;
+}
+
+static int impl_port_set_io(void *object, enum spa_direction direction, uint32_t port_id,
+ uint32_t id, void *data, size_t size)
+{
+ struct stream *impl = object;
+ struct pw_stream *stream = &impl->this;
+
+ pw_log_debug("%p: id:%d (%s) %p %zd", impl, id,
+ spa_debug_type_find_name(spa_type_io, id), data, size);
+
+ switch (id) {
+ case SPA_IO_Buffers:
+ if (data && size >= sizeof(struct spa_io_buffers))
+ impl->io = data;
+ else
+ impl->io = NULL;
+ break;
+ case SPA_IO_RateMatch:
+ if (data && size >= sizeof(struct spa_io_rate_match))
+ impl->rate_match = data;
+ else
+ impl->rate_match = NULL;
+ break;
+ }
+ pw_stream_emit_io_changed(stream, id, data, size);
+
+ return 0;
+}
+
+static int impl_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)
+{
+ return enum_params(object, true, seq, id, start, num, filter);
+}
+
+static int map_data(struct stream *impl, struct spa_data *data, int prot)
+{
+ void *ptr;
+ struct pw_map_range range;
+
+ pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize);
+
+ ptr = mmap(NULL, range.size, prot, MAP_SHARED, data->fd, range.offset);
+ if (ptr == MAP_FAILED) {
+ pw_log_error("%p: failed to mmap buffer mem: %m", impl);
+ return -errno;
+ }
+
+ data->data = SPA_PTROFF(ptr, range.start, void);
+ pw_log_debug("%p: fd %"PRIi64" mapped %d %d %p", impl, data->fd,
+ range.offset, range.size, data->data);
+
+ if (impl->allow_mlock && mlock(data->data, data->maxsize) < 0) {
+ if (errno != ENOMEM || !mlock_warned) {
+ pw_log(impl->warn_mlock ? SPA_LOG_LEVEL_WARN : SPA_LOG_LEVEL_DEBUG,
+ "%p: Failed to mlock memory %p %u: %s", impl,
+ data->data, data->maxsize,
+ errno == ENOMEM ?
+ "This is not a problem but for best performance, "
+ "consider increasing RLIMIT_MEMLOCK" : strerror(errno));
+ mlock_warned |= errno == ENOMEM;
+ }
+ }
+ return 0;
+}
+
+static int unmap_data(struct stream *impl, struct spa_data *data)
+{
+ struct pw_map_range range;
+
+ pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize);
+
+ if (munmap(SPA_PTROFF(data->data, -range.start, void), range.size) < 0)
+ pw_log_warn("%p: failed to unmap: %m", impl);
+
+ pw_log_debug("%p: fd %"PRIi64" unmapped", impl, data->fd);
+ return 0;
+}
+
+static void clear_buffers(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ uint32_t i, j;
+
+ pw_log_debug("%p: clear buffers %d", stream, impl->n_buffers);
+
+ for (i = 0; i < impl->n_buffers; i++) {
+ struct buffer *b = &impl->buffers[i];
+
+ if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_ADDED))
+ pw_stream_emit_remove_buffer(stream, &b->this);
+
+ if (SPA_FLAG_IS_SET(b->flags, BUFFER_FLAG_MAPPED)) {
+ for (j = 0; j < b->this.buffer->n_datas; j++) {
+ struct spa_data *d = &b->this.buffer->datas[j];
+ pw_log_debug("%p: clear buffer %d mem",
+ stream, b->id);
+ unmap_data(impl, d);
+ }
+ }
+ }
+ impl->n_buffers = 0;
+ if (impl->direction == SPA_DIRECTION_INPUT) {
+ struct buffer *b;
+
+ while ((b = queue_pop(impl, &impl->dequeued))) {
+ if (b->busy)
+ ATOMIC_DEC(b->busy->count);
+ }
+ } else
+ clear_queue(impl, &impl->dequeued);
+ clear_queue(impl, &impl->queued);
+}
+
+static int parse_latency(struct pw_stream *stream, const struct spa_pod *param)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct spa_latency_info info;
+ int res;
+
+ if (param == NULL)
+ return 0;
+
+ if ((res = spa_latency_parse(param, &info)) < 0)
+ return res;
+
+ pw_log_info("stream %p: set %s latency %f-%f %d-%d %"PRIu64"-%"PRIu64, stream,
+ 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 (info.direction == impl->direction)
+ return 0;
+
+ impl->latency = info;
+ return 0;
+}
+
+static int impl_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 stream *impl = object;
+ struct pw_stream *stream = &impl->this;
+ int res;
+
+ pw_log_debug("%p: port:%d.%d id:%d (%s) param:%p disconnecting:%d", impl,
+ direction, port_id, id,
+ spa_debug_type_find_name(spa_type_param, id), param,
+ impl->disconnecting);
+
+ if (impl->disconnecting && param != NULL)
+ return -EIO;
+
+ if (param)
+ pw_log_pod(SPA_LOG_LEVEL_DEBUG, param);
+
+ if ((res = update_params(impl, id, &param, param ? 1 : 0)) < 0)
+ return res;
+
+ switch (id) {
+ case SPA_PARAM_Format:
+ clear_buffers(stream);
+ break;
+ case SPA_PARAM_Latency:
+ parse_latency(stream, param);
+ break;
+ default:
+ break;
+ }
+
+ pw_stream_emit_param_changed(stream, id, param);
+
+ if (stream->state == PW_STREAM_STATE_ERROR)
+ return -EIO;
+
+ emit_node_info(impl, false);
+ emit_port_info(impl, false);
+
+ return 0;
+}
+
+static int impl_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 stream *impl = object;
+ struct pw_stream *stream = &impl->this;
+ uint32_t i, j, impl_flags = impl->flags;
+ int prot, res;
+ int size = 0;
+
+ pw_log_debug("%p: port:%d.%d buffers:%u disconnecting:%d", impl,
+ direction, port_id, n_buffers, impl->disconnecting);
+
+ if (impl->disconnecting && n_buffers > 0)
+ return -EIO;
+
+ prot = PROT_READ | (direction == SPA_DIRECTION_OUTPUT ? PROT_WRITE : 0);
+
+ clear_buffers(stream);
+
+ if (n_buffers > MAX_BUFFERS)
+ return -ENOSPC;
+
+ for (i = 0; i < n_buffers; i++) {
+ int buf_size = 0;
+ struct buffer *b = &impl->buffers[i];
+
+ b->flags = 0;
+ b->id = i;
+
+ if (SPA_FLAG_IS_SET(impl_flags, PW_STREAM_FLAG_MAP_BUFFERS)) {
+ for (j = 0; j < buffers[i]->n_datas; j++) {
+ struct spa_data *d = &buffers[i]->datas[j];
+ if ((mappable_dataTypes & (1<<d->type)) > 0) {
+ if ((res = map_data(impl, d, prot)) < 0)
+ return res;
+ SPA_FLAG_SET(b->flags, BUFFER_FLAG_MAPPED);
+ }
+ else if (d->type == SPA_DATA_MemPtr && d->data == NULL) {
+ pw_log_error("%p: invalid buffer mem", stream);
+ return -EINVAL;
+ }
+ buf_size += d->maxsize;
+ }
+
+ if (size > 0 && buf_size != size) {
+ pw_log_error("%p: invalid buffer size %d", stream, buf_size);
+ return -EINVAL;
+ } else
+ size = buf_size;
+ }
+ pw_log_debug("%p: got buffer id:%d datas:%d, mapped size %d", stream, i,
+ buffers[i]->n_datas, size);
+ }
+ impl->n_buffers = n_buffers;
+
+ for (i = 0; i < n_buffers; i++) {
+ struct buffer *b = &impl->buffers[i];
+
+ b->this.buffer = buffers[i];
+ b->busy = spa_buffer_find_meta_data(buffers[i], SPA_META_Busy, sizeof(*b->busy));
+
+ if (impl->direction == SPA_DIRECTION_OUTPUT) {
+ pw_log_trace("%p: recycle buffer %d", stream, b->id);
+ queue_push(impl, &impl->dequeued, b);
+ }
+
+ SPA_FLAG_SET(b->flags, BUFFER_FLAG_ADDED);
+
+ pw_stream_emit_add_buffer(stream, &b->this);
+ }
+ return 0;
+}
+
+static int impl_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id)
+{
+ struct stream *d = object;
+ pw_log_trace("%p: recycle buffer %d", d, buffer_id);
+ if (buffer_id < d->n_buffers)
+ queue_push(d, &d->queued, &d->buffers[buffer_id]);
+ return 0;
+}
+
+static int impl_node_process_input(void *object)
+{
+ struct stream *impl = object;
+ struct pw_stream *stream = &impl->this;
+ struct spa_io_buffers *io = impl->io;
+ struct buffer *b;
+
+ if (io == NULL)
+ return -EIO;
+
+ pw_log_trace_fp("%p: process in status:%d id:%d ticks:%"PRIu64" delay:%"PRIi64,
+ stream, io->status, io->buffer_id, impl->time.ticks, impl->time.delay);
+
+ if (io->status == SPA_STATUS_HAVE_DATA &&
+ (b = get_buffer(stream, io->buffer_id)) != NULL) {
+ /* push new buffer */
+ pw_log_trace_fp("%p: push %d %p", stream, b->id, io);
+ if (queue_push(impl, &impl->dequeued, b) == 0) {
+ copy_position(impl, impl->dequeued.incount);
+ if (b->busy)
+ ATOMIC_INC(b->busy->count);
+ call_process(impl);
+ }
+ }
+ if (io->status != SPA_STATUS_NEED_DATA || io->buffer_id == SPA_ID_INVALID) {
+ /* pop buffer to recycle */
+ if ((b = queue_pop(impl, &impl->queued))) {
+ pw_log_trace_fp("%p: recycle buffer %d", stream, b->id);
+ io->buffer_id = b->id;
+ } else {
+ pw_log_trace_fp("%p: no buffers to recycle", stream);
+ io->buffer_id = SPA_ID_INVALID;
+ }
+ io->status = SPA_STATUS_NEED_DATA;
+ }
+ if (impl->driving && impl->using_trigger)
+ call_trigger_done(impl);
+
+ return SPA_STATUS_NEED_DATA | SPA_STATUS_HAVE_DATA;
+}
+
+static int impl_node_process_output(void *object)
+{
+ struct stream *impl = object;
+ struct pw_stream *stream = &impl->this;
+ struct spa_io_buffers *io = impl->io;
+ struct buffer *b;
+ int res;
+ bool ask_more;
+
+ if (io == NULL)
+ return -EIO;
+
+again:
+ pw_log_trace_fp("%p: process out status:%d id:%d", stream,
+ io->status, io->buffer_id);
+
+ ask_more = false;
+ if ((res = io->status) != SPA_STATUS_HAVE_DATA) {
+ /* recycle old buffer */
+ if ((b = get_buffer(stream, io->buffer_id)) != NULL) {
+ pw_log_trace_fp("%p: recycle buffer %d", stream, b->id);
+ queue_push(impl, &impl->dequeued, b);
+ }
+
+ /* pop new buffer */
+ if ((b = queue_pop(impl, &impl->queued)) != NULL) {
+ impl->drained = false;
+ io->buffer_id = b->id;
+ res = io->status = SPA_STATUS_HAVE_DATA;
+ pw_log_trace_fp("%p: pop %d %p", stream, b->id, io);
+ /* we have a buffer, if we are not rt and don't follow
+ * any rate matching and there are no more
+ * buffers queued and there is a buffer to dequeue, ask for
+ * more buffers so that we have one in the next round.
+ * If we are using rate matching we need to wait until the
+ * rate matching node (audioconvert) has been scheduled to
+ * update the values. */
+ ask_more = !impl->process_rt && impl->rate_match == NULL &&
+ queue_is_empty(impl, &impl->queued) &&
+ !queue_is_empty(impl, &impl->dequeued);
+ } else if (impl->draining || impl->drained) {
+ impl->draining = true;
+ impl->drained = true;
+ io->buffer_id = SPA_ID_INVALID;
+ res = io->status = SPA_STATUS_DRAINED;
+ pw_log_trace_fp("%p: draining", stream);
+ } else {
+ io->buffer_id = SPA_ID_INVALID;
+ res = io->status = SPA_STATUS_NEED_DATA;
+ pw_log_trace_fp("%p: no more buffers %p", stream, io);
+ ask_more = true;
+ }
+ } else {
+ ask_more = !impl->process_rt &&
+ queue_is_empty(impl, &impl->queued) &&
+ !queue_is_empty(impl, &impl->dequeued);
+ }
+
+ copy_position(impl, impl->queued.outcount);
+
+ if (!impl->draining && !impl->driving) {
+ /* we're not draining, not a driver check if we need to get
+ * more buffers */
+ if (ask_more) {
+ call_process(impl);
+ /* realtime, we can try again now if there is something.
+ * non-realtime, we will have to try in the next round */
+ if (impl->process_rt &&
+ (impl->draining || !queue_is_empty(impl, &impl->queued)))
+ goto again;
+ }
+ }
+
+ pw_log_trace_fp("%p: res %d", stream, res);
+
+ if (impl->driving && impl->using_trigger && res != SPA_STATUS_HAVE_DATA)
+ call_trigger_done(impl);
+
+ return res;
+}
+
+static const struct spa_node_methods impl_node = {
+ SPA_VERSION_NODE_METHODS,
+ .add_listener = impl_add_listener,
+ .set_callbacks = impl_set_callbacks,
+ .enum_params = impl_enum_params,
+ .set_param = impl_set_param,
+ .set_io = impl_set_io,
+ .send_command = impl_send_command,
+ .port_set_io = impl_port_set_io,
+ .port_enum_params = impl_port_enum_params,
+ .port_set_param = impl_port_set_param,
+ .port_use_buffers = impl_port_use_buffers,
+ .port_reuse_buffer = impl_port_reuse_buffer,
+};
+
+static void proxy_removed(void *_data)
+{
+ struct pw_stream *stream = _data;
+ pw_log_debug("%p: removed", stream);
+ spa_hook_remove(&stream->proxy_listener);
+ stream->node_id = SPA_ID_INVALID;
+ stream_set_state(stream, PW_STREAM_STATE_UNCONNECTED, NULL);
+}
+
+static void proxy_destroy(void *_data)
+{
+ struct pw_stream *stream = _data;
+ pw_log_debug("%p: destroy", stream);
+ proxy_removed(_data);
+}
+
+static void proxy_error(void *_data, int seq, int res, const char *message)
+{
+ struct pw_stream *stream = _data;
+ /* 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() */
+ pw_stream_emit_state_changed(stream, stream->state,
+ PW_STREAM_STATE_ERROR, message);
+}
+
+static void proxy_bound(void *data, uint32_t global_id)
+{
+ struct pw_stream *stream = data;
+ stream->node_id = global_id;
+ stream_set_state(stream, PW_STREAM_STATE_PAUSED, NULL);
+}
+
+static const struct pw_proxy_events proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .removed = proxy_removed,
+ .destroy = proxy_destroy,
+ .error = proxy_error,
+ .bound = proxy_bound,
+};
+
+static struct control *find_control(struct pw_stream *stream, uint32_t id)
+{
+ struct control *c;
+ spa_list_for_each(c, &stream->controls, link) {
+ if (c->id == id)
+ return c;
+ }
+ return NULL;
+}
+
+static int node_event_param(void *object, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ struct spa_pod *param)
+{
+ struct pw_stream *stream = object;
+
+ switch (id) {
+ case SPA_PARAM_PropInfo:
+ {
+ struct control *c;
+ const struct spa_pod *type, *pod;
+ uint32_t iid, choice, n_vals, container = SPA_ID_INVALID;
+ float *vals, bool_range[3] = { 1.0f, 0.0f, 1.0f }, dbl[3];
+
+ if (spa_pod_parse_object(param,
+ SPA_TYPE_OBJECT_PropInfo, NULL,
+ SPA_PROP_INFO_id, SPA_POD_Id(&iid)) < 0)
+ return -EINVAL;
+
+ c = find_control(stream, iid);
+ if (c != NULL)
+ return 0;
+
+ c = calloc(1, sizeof(*c) + SPA_POD_SIZE(param));
+ c->info = SPA_PTROFF(c, sizeof(*c), struct spa_pod);
+ memcpy(c->info, param, SPA_POD_SIZE(param));
+ c->control.n_values = 0;
+ c->control.max_values = 0;
+ c->control.values = c->values;
+
+ if (spa_pod_parse_object(c->info,
+ SPA_TYPE_OBJECT_PropInfo, NULL,
+ SPA_PROP_INFO_description, SPA_POD_OPT_String(&c->control.name),
+ SPA_PROP_INFO_type, SPA_POD_PodChoice(&type),
+ SPA_PROP_INFO_container, SPA_POD_OPT_Id(&container)) < 0) {
+ free(c);
+ return -EINVAL;
+ }
+
+ pod = spa_pod_get_values(type, &n_vals, &choice);
+
+ c->type = SPA_POD_TYPE(pod);
+ if (spa_pod_is_float(pod))
+ vals = SPA_POD_BODY(pod);
+ else if (spa_pod_is_double(pod)) {
+ double *v = SPA_POD_BODY(pod);
+ dbl[0] = v[0];
+ if (n_vals > 1)
+ dbl[1] = v[1];
+ if (n_vals > 2)
+ dbl[2] = v[2];
+ vals = dbl;
+ }
+ else if (spa_pod_is_bool(pod) && n_vals > 0) {
+ choice = SPA_CHOICE_Range;
+ vals = bool_range;
+ vals[0] = SPA_POD_VALUE(struct spa_pod_bool, pod);
+ n_vals = 3;
+ }
+ else {
+ free(c);
+ return -ENOTSUP;
+ }
+
+ c->container = container != SPA_ID_INVALID ? container : c->type;
+
+ switch (choice) {
+ case SPA_CHOICE_None:
+ if (n_vals < 1) {
+ free(c);
+ return -EINVAL;
+ }
+ c->control.n_values = 1;
+ c->control.max_values = 1;
+ c->control.values[0] = c->control.def = c->control.min = c->control.max = vals[0];
+ break;
+ case SPA_CHOICE_Range:
+ if (n_vals < 3) {
+ free(c);
+ return -EINVAL;
+ }
+ c->control.n_values = 1;
+ c->control.max_values = 1;
+ c->control.values[0] = vals[0];
+ c->control.def = vals[0];
+ c->control.min = vals[1];
+ c->control.max = vals[2];
+ break;
+ default:
+ free(c);
+ return -ENOTSUP;
+ }
+
+ c->id = iid;
+ spa_list_append(&stream->controls, &c->link);
+ pw_log_debug("%p: add control %d (%s) container:%d (def:%f min:%f max:%f)",
+ stream, c->id, c->control.name, c->container,
+ c->control.def, c->control.min, c->control.max);
+ break;
+ }
+ case SPA_PARAM_Props:
+ {
+ struct spa_pod_prop *prop;
+ struct spa_pod_object *obj = (struct spa_pod_object *) param;
+ float value_f;
+ double value_d;
+ bool value_b;
+ float *values;
+ uint32_t i, n_values;
+
+ SPA_POD_OBJECT_FOREACH(obj, prop) {
+ struct control *c;
+
+ c = find_control(stream, prop->key);
+ if (c == NULL)
+ continue;
+
+ switch (c->container) {
+ case SPA_TYPE_Float:
+ if (spa_pod_get_float(&prop->value, &value_f) < 0)
+ continue;
+ n_values = 1;
+ values = &value_f;
+ break;
+ case SPA_TYPE_Double:
+ if (spa_pod_get_double(&prop->value, &value_d) < 0)
+ continue;
+ n_values = 1;
+ value_f = value_d;
+ values = &value_f;
+ break;
+ case SPA_TYPE_Bool:
+ if (spa_pod_get_bool(&prop->value, &value_b) < 0)
+ continue;
+ value_f = value_b ? 1.0f : 0.0f;
+ n_values = 1;
+ values = &value_f;
+ break;
+ case SPA_TYPE_Array:
+ if ((values = spa_pod_get_array(&prop->value, &n_values)) == NULL ||
+ !spa_pod_is_float(SPA_POD_ARRAY_CHILD(&prop->value)))
+ continue;
+ break;
+ default:
+ continue;
+ }
+
+ if (c->emitted && c->control.n_values == n_values &&
+ memcmp(c->control.values, values, sizeof(float) * n_values) == 0)
+ continue;
+
+ memcpy(c->control.values, values, sizeof(float) * n_values);
+ c->control.n_values = n_values;
+ c->emitted = true;
+
+ pw_log_debug("%p: control %d (%s) changed %d:", stream,
+ prop->key, c->control.name, n_values);
+ for (i = 0; i < n_values; i++)
+ pw_log_debug("%p: value %d %f", stream, i, values[i]);
+
+ pw_stream_emit_control_info(stream, prop->key, &c->control);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return 0;
+}
+
+static void node_event_info(void *data, const struct pw_node_info *info)
+{
+ struct pw_stream *stream = data;
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ uint32_t i;
+
+ if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ switch (info->params[i].id) {
+ case SPA_PARAM_PropInfo:
+ case SPA_PARAM_Props:
+ pw_impl_node_for_each_param(impl->node,
+ 0, info->params[i].id,
+ 0, UINT32_MAX,
+ NULL,
+ node_event_param,
+ stream);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+static const struct pw_impl_node_events node_events = {
+ PW_VERSION_IMPL_NODE_EVENTS,
+ .info_changed = node_event_info,
+};
+
+static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct pw_stream *stream = data;
+
+ pw_log_debug("%p: error id:%u seq:%d res:%d (%s): %s", stream,
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE) {
+ stream_set_state(stream, PW_STREAM_STATE_UNCONNECTED, message);
+ }
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+};
+
+static void context_drained(void *data, struct pw_impl_node *node)
+{
+ struct stream *impl = data;
+ if (impl->node != node)
+ return;
+ if (impl->draining && impl->drained) {
+ impl->draining = false;
+ if (impl->io != NULL)
+ impl->io->status = SPA_STATUS_NEED_DATA;
+ call_drained(impl);
+ }
+}
+
+static const struct pw_context_driver_events context_events = {
+ PW_VERSION_CONTEXT_DRIVER_EVENTS,
+ .drained = context_drained,
+};
+
+struct match {
+ struct pw_stream *stream;
+ int count;
+};
+#define MATCH_INIT(s) ((struct match){ .stream = (s) })
+
+static int execute_match(void *data, const char *location, const char *action,
+ const char *val, size_t len)
+{
+ struct match *match = data;
+ struct pw_stream *this = match->stream;
+ if (spa_streq(action, "update-props"))
+ match->count += pw_properties_update_string(this->properties, val, len);
+ return 1;
+}
+
+static struct stream *
+stream_new(struct pw_context *context, const char *name,
+ struct pw_properties *props, const struct pw_properties *extra)
+{
+ struct stream *impl;
+ struct pw_stream *this;
+ const char *str;
+ struct match match;
+ int res;
+
+ impl = calloc(1, sizeof(struct stream));
+ if (impl == NULL) {
+ res = -errno;
+ goto error_cleanup;
+ }
+ impl->port_props = pw_properties_new(NULL, NULL);
+ if (impl->port_props == NULL) {
+ res = -errno;
+ goto error_properties;
+ }
+
+ this = &impl->this;
+ pw_log_debug("%p: new \"%s\"", impl, name);
+
+ if (props == NULL) {
+ props = pw_properties_new(PW_KEY_MEDIA_NAME, name, NULL);
+ } else if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) {
+ pw_properties_set(props, PW_KEY_MEDIA_NAME, name);
+ }
+ if (props == NULL) {
+ res = -errno;
+ goto error_properties;
+ }
+ spa_hook_list_init(&impl->hooks);
+ this->properties = props;
+
+ pw_context_conf_update_props(context, "stream.properties", props);
+
+ match = MATCH_INIT(this);
+ pw_context_conf_section_match_rules(context, "stream.rules",
+ &this->properties->dict, execute_match, &match);
+
+ if ((str = getenv("PIPEWIRE_PROPS")) != NULL)
+ pw_properties_update_string(props, str, strlen(str));
+ if ((str = getenv("PIPEWIRE_QUANTUM")) != NULL) {
+ struct spa_fraction q;
+ if (sscanf(str, "%u/%u", &q.num, &q.denom) == 2 && q.denom != 0) {
+ pw_properties_setf(props, PW_KEY_NODE_RATE,
+ "1/%u", q.denom);
+ pw_properties_setf(props, PW_KEY_NODE_LATENCY,
+ "%u/%u", q.num, q.denom);
+ }
+ }
+ if ((str = getenv("PIPEWIRE_LATENCY")) != NULL)
+ pw_properties_set(props, PW_KEY_NODE_LATENCY, str);
+ if ((str = getenv("PIPEWIRE_RATE")) != NULL)
+ pw_properties_set(props, PW_KEY_NODE_RATE, str);
+
+ if (pw_properties_get(props, PW_KEY_STREAM_IS_LIVE) == NULL)
+ pw_properties_set(props, PW_KEY_STREAM_IS_LIVE, "true");
+ if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL && extra) {
+ str = pw_properties_get(extra, PW_KEY_APP_NAME);
+ if (str == NULL)
+ str = pw_properties_get(extra, PW_KEY_APP_PROCESS_BINARY);
+ if (str == NULL)
+ str = name;
+ pw_properties_set(props, PW_KEY_NODE_NAME, str);
+ }
+
+ this->name = name ? strdup(name) : NULL;
+ this->node_id = SPA_ID_INVALID;
+
+ spa_ringbuffer_init(&impl->dequeued.ring);
+ spa_ringbuffer_init(&impl->queued.ring);
+ spa_list_init(&impl->param_list);
+
+ spa_hook_list_init(&this->listener_list);
+ spa_list_init(&this->controls);
+
+ this->state = PW_STREAM_STATE_UNCONNECTED;
+
+ impl->context = context;
+ impl->allow_mlock = context->settings.mem_allow_mlock;
+ impl->warn_mlock = context->settings.mem_warn_mlock;
+
+ spa_hook_list_append(&impl->context->driver_listener_list,
+ &impl->context_listener,
+ &context_events, impl);
+ return impl;
+
+error_properties:
+ pw_properties_free(impl->port_props);
+ free(impl);
+error_cleanup:
+ pw_properties_free(props);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+struct pw_stream * pw_stream_new(struct pw_core *core, const char *name,
+ struct pw_properties *props)
+{
+ struct stream *impl;
+ struct pw_stream *this;
+ struct pw_context *context = core->context;
+
+ impl = stream_new(context, name, props, core->properties);
+ if (impl == NULL)
+ return NULL;
+
+ this = &impl->this;
+ this->core = core;
+ spa_list_append(&core->stream_list, &this->link);
+ pw_core_add_listener(core,
+ &this->core_listener, &core_events, this);
+
+ return this;
+}
+
+SPA_EXPORT
+struct pw_stream *
+pw_stream_new_simple(struct pw_loop *loop,
+ const char *name,
+ struct pw_properties *props,
+ const struct pw_stream_events *events,
+ void *data)
+{
+ struct pw_stream *this;
+ struct stream *impl;
+ struct pw_context *context;
+ int res;
+
+ if (props == NULL)
+ props = pw_properties_new(NULL, NULL);
+ if (props == NULL)
+ return NULL;
+
+ context = pw_context_new(loop, NULL, 0);
+ if (context == NULL) {
+ res = -errno;
+ goto error_cleanup;
+ }
+
+ impl = stream_new(context, name, props, NULL);
+ if (impl == NULL) {
+ res = -errno;
+ props = NULL;
+ goto error_cleanup;
+ }
+
+ this = &impl->this;
+ impl->data.context = context;
+ pw_stream_add_listener(this, &impl->data.stream_listener, events, data);
+
+ return this;
+
+error_cleanup:
+ if (context)
+ pw_context_destroy(context);
+ pw_properties_free(props);
+ errno = -res;
+ return NULL;
+}
+
+SPA_EXPORT
+const char *pw_stream_state_as_string(enum pw_stream_state state)
+{
+ switch (state) {
+ case PW_STREAM_STATE_ERROR:
+ return "error";
+ case PW_STREAM_STATE_UNCONNECTED:
+ return "unconnected";
+ case PW_STREAM_STATE_CONNECTING:
+ return "connecting";
+ case PW_STREAM_STATE_PAUSED:
+ return "paused";
+ case PW_STREAM_STATE_STREAMING:
+ return "streaming";
+ }
+ return "invalid-state";
+}
+
+SPA_EXPORT
+void pw_stream_destroy(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct control *c;
+
+ pw_log_debug("%p: destroy", stream);
+
+ pw_stream_emit_destroy(stream);
+
+ if (!impl->disconnecting)
+ pw_stream_disconnect(stream);
+
+ if (stream->core) {
+ spa_hook_remove(&stream->core_listener);
+ spa_list_remove(&stream->link);
+ stream->core = NULL;
+ }
+
+ clear_params(impl, SPA_ID_INVALID);
+
+ pw_log_debug("%p: free", stream);
+ free(stream->error);
+
+ pw_properties_free(stream->properties);
+
+ free(stream->name);
+
+ spa_list_consume(c, &stream->controls, link) {
+ spa_list_remove(&c->link);
+ free(c);
+ }
+
+ spa_hook_list_clean(&impl->hooks);
+ spa_hook_list_clean(&stream->listener_list);
+
+ spa_hook_remove(&impl->context_listener);
+
+ if (impl->data.context)
+ pw_context_destroy(impl->data.context);
+
+ pw_properties_free(impl->port_props);
+ free(impl);
+}
+
+static void hook_removed(struct spa_hook *hook)
+{
+ struct stream *impl = hook->priv;
+ spa_zero(impl->rt_callbacks);
+ hook->priv = NULL;
+ hook->removed = NULL;
+}
+
+SPA_EXPORT
+void pw_stream_add_listener(struct pw_stream *stream,
+ struct spa_hook *listener,
+ const struct pw_stream_events *events,
+ void *data)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ spa_hook_list_append(&stream->listener_list, listener, events, data);
+
+ if (events->process && impl->rt_callbacks.funcs == NULL) {
+ impl->rt_callbacks = SPA_CALLBACKS_INIT(events, data);
+ listener->removed = hook_removed;
+ listener->priv = impl;
+ }
+}
+
+SPA_EXPORT
+enum pw_stream_state pw_stream_get_state(struct pw_stream *stream, const char **error)
+{
+ if (error)
+ *error = stream->error;
+ return stream->state;
+}
+
+SPA_EXPORT
+const char *pw_stream_get_name(struct pw_stream *stream)
+{
+ return stream->name;
+}
+
+SPA_EXPORT
+const struct pw_properties *pw_stream_get_properties(struct pw_stream *stream)
+{
+ return stream->properties;
+}
+
+SPA_EXPORT
+int pw_stream_update_properties(struct pw_stream *stream, const struct spa_dict *dict)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ int changed, res = 0;
+ struct match match;
+
+ changed = pw_properties_update(stream->properties, dict);
+ if (!changed)
+ return 0;
+
+ match = MATCH_INIT(stream);
+ pw_context_conf_section_match_rules(impl->context, "stream.rules",
+ &stream->properties->dict, execute_match, &match);
+
+ if (impl->node)
+ res = pw_impl_node_update_properties(impl->node,
+ match.count == 0 ?
+ dict :
+ &stream->properties->dict);
+
+ return res;
+}
+
+SPA_EXPORT
+struct pw_core *pw_stream_get_core(struct pw_stream *stream)
+{
+ return stream->core;
+}
+
+static void add_params(struct stream *impl)
+{
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+
+ add_param(impl, SPA_PARAM_IO, PARAM_FLAG_LOCKED,
+ spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamIO, SPA_PARAM_IO,
+ SPA_PARAM_IO_id, SPA_POD_Id(SPA_IO_Buffers),
+ SPA_PARAM_IO_size, SPA_POD_Int(sizeof(struct spa_io_buffers))));
+
+ add_param(impl, SPA_PARAM_Meta, PARAM_FLAG_LOCKED,
+ spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_ParamMeta, SPA_PARAM_Meta,
+ SPA_PARAM_META_type, SPA_POD_Id(SPA_META_Busy),
+ SPA_PARAM_META_size, SPA_POD_Int(sizeof(struct spa_meta_busy))));
+}
+
+static int find_format(struct stream *impl, enum pw_direction direction,
+ uint32_t *media_type, uint32_t *media_subtype)
+{
+ uint32_t state = 0;
+ uint8_t buffer[4096];
+ struct spa_pod_builder b;
+ int res;
+ struct spa_pod *format;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+ if (spa_node_port_enum_params_sync(&impl->impl_node,
+ impl->direction, 0,
+ SPA_PARAM_EnumFormat, &state,
+ NULL, &format, &b) != 1) {
+ pw_log_warn("%p: no format given", impl);
+ return 0;
+ }
+
+ if ((res = spa_format_parse(format, media_type, media_subtype)) < 0)
+ return res;
+
+ pw_log_debug("%p: %s/%s", impl,
+ spa_debug_type_find_name(spa_type_media_type, *media_type),
+ spa_debug_type_find_name(spa_type_media_subtype, *media_subtype));
+ return 0;
+}
+
+static const char *get_media_class(struct stream *impl)
+{
+ switch (impl->media_type) {
+ case SPA_MEDIA_TYPE_audio:
+ return "Audio";
+ case SPA_MEDIA_TYPE_video:
+ return "Video";
+ case SPA_MEDIA_TYPE_application:
+ switch(impl->media_subtype) {
+ case SPA_MEDIA_SUBTYPE_control:
+ return "Midi";
+ }
+ return "Data";
+ case SPA_MEDIA_TYPE_stream:
+ switch(impl->media_subtype) {
+ case SPA_MEDIA_SUBTYPE_midi:
+ return "Midi";
+ }
+ return "Data";
+ default:
+ return "Unknown";
+ }
+}
+
+SPA_EXPORT
+int
+pw_stream_connect(struct pw_stream *stream,
+ enum pw_direction direction,
+ uint32_t target_id,
+ enum pw_stream_flags flags,
+ const struct spa_pod **params,
+ uint32_t n_params)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct pw_impl_factory *factory;
+ struct pw_properties *props = NULL;
+ const char *str;
+ uint32_t i;
+ int res;
+
+ pw_log_debug("%p: connect target:%d", stream, target_id);
+ impl->direction =
+ direction == PW_DIRECTION_INPUT ? SPA_DIRECTION_INPUT : SPA_DIRECTION_OUTPUT;
+ impl->flags = flags;
+ impl->node_methods = impl_node;
+
+ if (impl->direction == SPA_DIRECTION_INPUT)
+ impl->node_methods.process = impl_node_process_input;
+ else
+ impl->node_methods.process = impl_node_process_output;
+
+ impl->process_rt = SPA_FLAG_IS_SET(flags, PW_STREAM_FLAG_RT_PROCESS);
+
+ impl->impl_node.iface = SPA_INTERFACE_INIT(
+ SPA_TYPE_INTERFACE_Node,
+ SPA_VERSION_NODE,
+ &impl->node_methods, impl);
+
+ impl->change_mask_all =
+ SPA_NODE_CHANGE_MASK_FLAGS |
+ SPA_NODE_CHANGE_MASK_PROPS |
+ SPA_NODE_CHANGE_MASK_PARAMS;
+
+ impl->info = SPA_NODE_INFO_INIT();
+ if (impl->direction == SPA_DIRECTION_INPUT) {
+ impl->info.max_input_ports = 1;
+ impl->info.max_output_ports = 0;
+ } else {
+ impl->info.max_input_ports = 0;
+ impl->info.max_output_ports = 1;
+ }
+ /* we're always RT safe, if the stream was marked RT_PROCESS,
+ * the callback must be RT safe */
+ impl->info.flags = SPA_NODE_FLAG_RT;
+ /* if the callback was not marked RT_PROCESS, we will offload
+ * the process callback in the main thread and we are ASYNC */
+ if (!impl->process_rt)
+ impl->info.flags |= SPA_NODE_FLAG_ASYNC;
+ impl->info.props = &stream->properties->dict;
+ impl->params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, 0);
+ impl->params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_WRITE);
+ impl->params[NODE_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, 0);
+ impl->params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
+ impl->info.params = impl->params;
+ impl->info.n_params = N_NODE_PARAMS;
+ impl->info.change_mask = impl->change_mask_all;
+
+ impl->port_change_mask_all =
+ SPA_PORT_CHANGE_MASK_FLAGS |
+ SPA_PORT_CHANGE_MASK_PROPS |
+ SPA_PORT_CHANGE_MASK_PARAMS;
+
+ impl->port_info = SPA_PORT_INFO_INIT();
+ impl->port_info.change_mask = impl->port_change_mask_all;
+ impl->port_info.flags = 0;
+ if (SPA_FLAG_IS_SET(flags, PW_STREAM_FLAG_ALLOC_BUFFERS))
+ impl->port_info.flags |= SPA_PORT_FLAG_CAN_ALLOC_BUFFERS;
+ impl->port_params[PORT_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, 0);
+ impl->port_params[PORT_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, 0);
+ impl->port_params[PORT_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, 0);
+ impl->port_params[PORT_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
+ impl->port_params[PORT_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
+ impl->port_params[PORT_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_WRITE);
+ impl->port_info.props = &impl->port_props->dict;
+ impl->port_info.params = impl->port_params;
+ impl->port_info.n_params = N_PORT_PARAMS;
+
+ clear_params(impl, SPA_ID_INVALID);
+ for (i = 0; i < n_params; i++)
+ add_param(impl, SPA_ID_INVALID, 0, params[i]);
+
+ add_params(impl);
+
+ if ((res = find_format(impl, direction, &impl->media_type, &impl->media_subtype)) < 0)
+ return res;
+
+ impl->disconnecting = false;
+ stream_set_state(stream, PW_STREAM_STATE_CONNECTING, NULL);
+
+ if ((str = getenv("PIPEWIRE_NODE")) != NULL)
+ pw_properties_set(stream->properties, PW_KEY_TARGET_OBJECT, str);
+ else if (target_id != PW_ID_ANY)
+ /* XXX this is deprecated but still used by the portal and its apps */
+ pw_properties_setf(stream->properties, PW_KEY_NODE_TARGET, "%d", target_id);
+
+ if ((flags & PW_STREAM_FLAG_AUTOCONNECT) &&
+ pw_properties_get(stream->properties, PW_KEY_NODE_AUTOCONNECT) == NULL) {
+ str = getenv("PIPEWIRE_AUTOCONNECT");
+ pw_properties_set(stream->properties, PW_KEY_NODE_AUTOCONNECT, str ? str : "true");
+ }
+ if (flags & PW_STREAM_FLAG_DRIVER)
+ pw_properties_set(stream->properties, PW_KEY_NODE_DRIVER, "true");
+ if ((pw_properties_get(stream->properties, PW_KEY_NODE_WANT_DRIVER) == NULL))
+ pw_properties_set(stream->properties, PW_KEY_NODE_WANT_DRIVER, "true");
+
+ if (flags & PW_STREAM_FLAG_EXCLUSIVE)
+ pw_properties_set(stream->properties, PW_KEY_NODE_EXCLUSIVE, "true");
+ if (flags & PW_STREAM_FLAG_DONT_RECONNECT)
+ pw_properties_set(stream->properties, PW_KEY_NODE_DONT_RECONNECT, "true");
+ if (flags & PW_STREAM_FLAG_TRIGGER) {
+ pw_properties_set(stream->properties, PW_KEY_NODE_TRIGGER, "true");
+ impl->trigger = true;
+ }
+
+ if ((str = pw_properties_get(stream->properties, "mem.warn-mlock")) != NULL)
+ impl->warn_mlock = pw_properties_parse_bool(str);
+
+ if ((pw_properties_get(stream->properties, PW_KEY_MEDIA_CLASS) == NULL)) {
+ const char *media_type = pw_properties_get(stream->properties, PW_KEY_MEDIA_TYPE);
+ pw_properties_setf(stream->properties, PW_KEY_MEDIA_CLASS, "Stream/%s/%s",
+ direction == PW_DIRECTION_INPUT ? "Input" : "Output",
+ media_type ? media_type : get_media_class(impl));
+ }
+
+ if ((str = pw_properties_get(stream->properties, PW_KEY_FORMAT_DSP)) != NULL)
+ 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");
+
+ impl->port_info.props = &impl->port_props->dict;
+
+ if (stream->core == NULL) {
+ stream->core = pw_context_connect(impl->context,
+ pw_properties_copy(stream->properties), 0);
+ if (stream->core == NULL) {
+ res = -errno;
+ goto error_connect;
+ }
+ spa_list_append(&stream->core->stream_list, &stream->link);
+ pw_core_add_listener(stream->core,
+ &stream->core_listener, &core_events, stream);
+ impl->disconnect_core = true;
+ }
+
+ pw_log_debug("%p: creating node", stream);
+ props = pw_properties_copy(stream->properties);
+ if (props == NULL) {
+ res = -errno;
+ goto error_node;
+ }
+
+ if ((str = pw_properties_get(props, PW_KEY_STREAM_MONITOR)) &&
+ pw_properties_parse_bool(str)) {
+ pw_properties_set(props, "resample.peaks", "true");
+ pw_properties_set(props, "channelmix.normalize", "true");
+ }
+
+ if (impl->media_type == SPA_MEDIA_TYPE_audio) {
+ factory = pw_context_find_factory(impl->context, "adapter");
+ if (factory == NULL) {
+ pw_log_error("%p: no adapter factory found", stream);
+ res = -ENOENT;
+ goto error_node;
+ }
+ pw_properties_setf(props, "adapt.follower.spa-node", "pointer:%p",
+ &impl->impl_node);
+ pw_properties_set(props, "object.register", "false");
+ impl->node = pw_impl_factory_create_object(factory,
+ NULL,
+ PW_TYPE_INTERFACE_Node,
+ PW_VERSION_NODE,
+ props,
+ 0);
+ props = NULL;
+ if (impl->node == NULL) {
+ res = -errno;
+ goto error_node;
+ }
+ } else {
+ impl->node = pw_context_create_node(impl->context, props, 0);
+ props = NULL;
+ if (impl->node == NULL) {
+ res = -errno;
+ goto error_node;
+ }
+ pw_impl_node_set_implementation(impl->node, &impl->impl_node);
+ }
+ pw_impl_node_set_active(impl->node,
+ !SPA_FLAG_IS_SET(impl->flags, PW_STREAM_FLAG_INACTIVE));
+
+ pw_log_debug("%p: export node %p", stream, impl->node);
+ stream->proxy = pw_core_export(stream->core,
+ PW_TYPE_INTERFACE_Node, NULL, impl->node, 0);
+ if (stream->proxy == NULL) {
+ res = -errno;
+ goto error_proxy;
+ }
+
+ pw_proxy_add_listener(stream->proxy, &stream->proxy_listener, &proxy_events, stream);
+
+ pw_impl_node_add_listener(impl->node, &stream->node_listener, &node_events, stream);
+
+ return 0;
+
+error_connect:
+ pw_log_error("%p: can't connect: %s", stream, spa_strerror(res));
+ goto exit_cleanup;
+error_node:
+ pw_log_error("%p: can't make node: %s", stream, spa_strerror(res));
+ goto exit_cleanup;
+error_proxy:
+ pw_log_error("%p: can't make proxy: %s", stream, spa_strerror(res));
+ goto exit_cleanup;
+
+exit_cleanup:
+ pw_properties_free(props);
+ return res;
+}
+
+SPA_EXPORT
+uint32_t pw_stream_get_node_id(struct pw_stream *stream)
+{
+ return stream->node_id;
+}
+
+SPA_EXPORT
+int pw_stream_disconnect(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+
+ pw_log_debug("%p: disconnect", stream);
+
+ if (impl->disconnecting)
+ return 0;
+
+ impl->disconnecting = true;
+
+ if (impl->node)
+ pw_impl_node_set_active(impl->node, false);
+
+ if (stream->proxy) {
+ pw_proxy_destroy(stream->proxy);
+ stream->proxy = NULL;
+ }
+
+ if (impl->node) {
+ pw_impl_node_destroy(impl->node);
+ impl->node = NULL;
+ }
+ if (impl->disconnect_core) {
+ impl->disconnect_core = false;
+ spa_hook_remove(&stream->core_listener);
+ spa_list_remove(&stream->link);
+ pw_core_disconnect(stream->core);
+ stream->core = NULL;
+ }
+ return 0;
+}
+
+SPA_EXPORT
+int pw_stream_set_error(struct pw_stream *stream,
+ int res, const char *error, ...)
+{
+ if (res < 0) {
+ va_list args;
+ char *value;
+ int r;
+
+ va_start(args, error);
+ r = vasprintf(&value, error, args);
+ va_end(args);
+ if (r < 0)
+ return -errno;
+
+ if (stream->proxy)
+ pw_proxy_error(stream->proxy, res, value);
+ stream_set_state(stream, PW_STREAM_STATE_ERROR, value);
+
+ free(value);
+ }
+ return res;
+}
+
+SPA_EXPORT
+int pw_stream_update_params(struct pw_stream *stream,
+ const struct spa_pod **params,
+ uint32_t n_params)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ int res;
+
+ pw_log_debug("%p: update params", stream);
+ if ((res = update_params(impl, SPA_ID_INVALID, params, n_params)) < 0)
+ return res;
+
+ emit_node_info(impl, false);
+ emit_port_info(impl, false);
+
+ return res;
+}
+
+SPA_EXPORT
+int pw_stream_set_control(struct pw_stream *stream, uint32_t id, uint32_t n_values, float *values, ...)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ va_list varargs;
+ char buf[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buf, sizeof(buf));
+ struct spa_pod_frame f[1];
+ struct spa_pod *pod;
+ struct control *c;
+
+ if (impl->node == NULL)
+ return -EIO;
+
+ va_start(varargs, values);
+
+ spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
+ while (1) {
+ pw_log_debug("%p: set control %d %d %f", stream, id, n_values, values[0]);
+
+ if ((c = find_control(stream, id))) {
+ spa_pod_builder_prop(&b, id, 0);
+ switch (c->container) {
+ case SPA_TYPE_Float:
+ spa_pod_builder_float(&b, values[0]);
+ break;
+ case SPA_TYPE_Double:
+ spa_pod_builder_double(&b, values[0]);
+ break;
+ case SPA_TYPE_Bool:
+ spa_pod_builder_bool(&b, values[0] < 0.5 ? false : true);
+ break;
+ case SPA_TYPE_Array:
+ spa_pod_builder_array(&b,
+ sizeof(float), SPA_TYPE_Float,
+ n_values, values);
+ break;
+ default:
+ spa_pod_builder_none(&b);
+ break;
+ }
+ } else {
+ pw_log_warn("%p: unknown control with id %d", stream, id);
+ }
+ if ((id = va_arg(varargs, uint32_t)) == 0)
+ break;
+ n_values = va_arg(varargs, uint32_t);
+ values = va_arg(varargs, float *);
+ }
+ pod = spa_pod_builder_pop(&b, &f[0]);
+
+ va_end(varargs);
+
+ impl->in_set_control++;
+ pw_impl_node_set_param(impl->node, SPA_PARAM_Props, 0, pod);
+ impl->in_set_control--;
+
+ return 0;
+}
+
+SPA_EXPORT
+const struct pw_stream_control *pw_stream_get_control(struct pw_stream *stream, uint32_t id)
+{
+ struct control *c;
+
+ if (id == 0)
+ return NULL;
+
+ if ((c = find_control(stream, id)))
+ return &c->control;
+
+ return NULL;
+}
+
+SPA_EXPORT
+int pw_stream_set_active(struct pw_stream *stream, bool active)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ pw_log_debug("%p: active:%d", stream, active);
+ if (impl->node)
+ pw_impl_node_set_active(impl->node, active);
+
+ if (!active || impl->drained)
+ impl->drained = impl->draining = false;
+ return 0;
+}
+
+struct old_time {
+ int64_t now;
+ struct spa_fraction rate;
+ uint64_t ticks;
+ int64_t delay;
+ uint64_t queued;
+};
+
+SPA_EXPORT
+int pw_stream_get_time(struct pw_stream *stream, struct pw_time *time)
+{
+ return pw_stream_get_time_n(stream, time, sizeof(struct old_time));
+}
+
+SPA_EXPORT
+int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t size)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ uintptr_t seq1, seq2;
+ uint32_t buffered, quantum, index;
+
+ do {
+ seq1 = SEQ_READ(impl->seq);
+ memcpy(time, &impl->time, SPA_MIN(size, sizeof(struct pw_time)));
+ buffered = impl->rate_queued;
+ quantum = impl->quantum;
+ seq2 = SEQ_READ(impl->seq);
+ } while (!SEQ_READ_SUCCESS(seq1, seq2));
+
+ if (impl->direction == SPA_DIRECTION_INPUT)
+ time->queued = (int64_t)(time->queued - impl->dequeued.outcount);
+ else
+ time->queued = (int64_t)(impl->queued.incount - time->queued);
+
+ time->delay += ((impl->latency.min_quantum + impl->latency.max_quantum) / 2) * 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;
+
+ if (size >= offsetof(struct pw_time, queued_buffers))
+ time->buffered = buffered;
+ if (size >= offsetof(struct pw_time, avail_buffers))
+ time->queued_buffers = spa_ringbuffer_get_read_index(&impl->queued.ring, &index);
+ if (size >= sizeof(struct pw_time))
+ time->avail_buffers = spa_ringbuffer_get_read_index(&impl->dequeued.ring, &index);
+
+ pw_log_trace_fp("%p: %"PRIi64" %"PRIi64" %"PRIu64" %d/%d %"PRIu64" %"
+ PRIu64" %"PRIu64" %"PRIu64" %"PRIu64, stream,
+ time->now, time->delay, time->ticks,
+ time->rate.num, time->rate.denom, time->queued,
+ impl->dequeued.outcount, impl->dequeued.incount,
+ impl->queued.outcount, impl->queued.incount);
+ return 0;
+}
+
+static int
+do_trigger_deprecated(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct stream *impl = user_data;
+ int res = impl->node_methods.process(impl);
+ return spa_node_call_ready(&impl->callbacks, res);
+}
+
+SPA_EXPORT
+struct pw_buffer *pw_stream_dequeue_buffer(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ struct buffer *b;
+ int res;
+
+ if ((b = queue_pop(impl, &impl->dequeued)) == NULL) {
+ res = -errno;
+ pw_log_trace_fp("%p: no more buffers: %m", stream);
+ errno = -res;
+ return NULL;
+ }
+ pw_log_trace_fp("%p: dequeue buffer %d size:%"PRIu64, stream, b->id, b->this.size);
+
+ if (b->busy && impl->direction == SPA_DIRECTION_OUTPUT) {
+ if (ATOMIC_INC(b->busy->count) > 1) {
+ ATOMIC_DEC(b->busy->count);
+ queue_push(impl, &impl->dequeued, b);
+ pw_log_trace_fp("%p: buffer busy", stream);
+ errno = EBUSY;
+ return NULL;
+ }
+ }
+ return &b->this;
+}
+
+SPA_EXPORT
+int pw_stream_queue_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);
+ int res;
+
+ if (b->busy)
+ ATOMIC_DEC(b->busy->count);
+
+ pw_log_trace_fp("%p: queue buffer %d", stream, b->id);
+ if ((res = queue_push(impl, &impl->queued, b)) < 0)
+ return res;
+
+ if (impl->direction == SPA_DIRECTION_OUTPUT &&
+ impl->driving && !impl->using_trigger) {
+ pw_log_debug("deprecated: use pw_stream_trigger_process() to drive the stream.");
+ res = pw_loop_invoke(impl->context->data_loop,
+ do_trigger_deprecated, 1, NULL, 0, false, impl);
+ }
+ return res;
+}
+
+static int
+do_flush(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct stream *impl = user_data;
+ struct buffer *b;
+
+ pw_log_trace_fp("%p: flush", impl);
+ do {
+ b = queue_pop(impl, &impl->queued);
+ if (b != NULL)
+ queue_push(impl, &impl->dequeued, b);
+ }
+ while (b);
+
+ impl->queued.outcount = impl->dequeued.incount =
+ impl->dequeued.outcount = impl->queued.incount = 0;
+
+ return 0;
+}
+static int
+do_drain(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct stream *impl = user_data;
+ pw_log_trace_fp("%p", impl);
+ impl->draining = true;
+ impl->drained = false;
+ return 0;
+}
+
+SPA_EXPORT
+int pw_stream_flush(struct pw_stream *stream, bool drain)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ pw_loop_invoke(impl->context->data_loop,
+ drain ? do_drain : do_flush, 1, NULL, 0, true, impl);
+ if (!drain && impl->node != NULL)
+ spa_node_send_command(impl->node->node,
+ &SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Flush));
+ return 0;
+}
+
+SPA_EXPORT
+bool pw_stream_is_driving(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ return impl->driving;
+}
+
+static int
+do_trigger_process(struct spa_loop *loop,
+ bool async, uint32_t seq, const void *data, size_t size, void *user_data)
+{
+ struct stream *impl = user_data;
+ int res;
+ if (impl->direction == SPA_DIRECTION_OUTPUT) {
+ if (impl->process_rt)
+ call_process(impl);
+ res = impl->node_methods.process(impl);
+ } else {
+ res = SPA_STATUS_NEED_DATA;
+ }
+ return spa_node_call_ready(&impl->callbacks, res);
+}
+
+static int trigger_request_process(struct stream *impl)
+{
+ 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));
+ return 0;
+}
+
+SPA_EXPORT
+int pw_stream_trigger_process(struct pw_stream *stream)
+{
+ struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
+ int res = 0;
+
+ pw_log_trace_fp("%p", impl);
+
+ /* flag to check for old or new behaviour */
+ impl->using_trigger = true;
+
+ if (!impl->driving && !impl->trigger) {
+ res = trigger_request_process(impl);
+ } else {
+ if (!impl->process_rt)
+ call_process(impl);
+
+ res = pw_loop_invoke(impl->context->data_loop,
+ do_trigger_process, 1, NULL, 0, false, impl);
+ }
+ return res;
+}
diff --git a/src/pipewire/stream.h b/src/pipewire/stream.h
new file mode 100644
index 0000000..bf08c44
--- /dev/null
+++ b/src/pipewire/stream.h
@@ -0,0 +1,529 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_STREAM_H
+#define PIPEWIRE_STREAM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \page page_streams Streams
+ *
+ * \section sec_overview Overview
+ *
+ * \ref pw_stream "Streams" are used to exchange data with the
+ * PipeWire server. A stream is a wrapper around a proxy for a pw_client_node
+ * with an adapter. This means the stream will automatically do conversion
+ * to the type required by the server.
+ *
+ * Streams can be used to:
+ *
+ * \li Consume a stream from PipeWire. This is a PW_DIRECTION_INPUT stream.
+ * \li Produce a stream to PipeWire. This is a PW_DIRECTION_OUTPUT stream
+ *
+ * You can connect the stream port to a specific server port or let PipeWire
+ * choose a port for you.
+ *
+ * For more complicated nodes such as filters or ports with multiple
+ * inputs and/or outputs you will need to use the pw_filter or make
+ * a pw_node yourself and export it with \ref pw_core_export.
+ *
+ * Streams can also be used to:
+ *
+ * \li Implement a Sink in PipeWire. This is a PW_DIRECTION_INPUT stream.
+ * \li Implement a Source in PipeWire. This is a PW_DIRECTION_OUTPUT stream
+ *
+ * In this case, the PW_KEY_MEDIA_CLASS property needs to be set to
+ * "Audio/Sink" or "Audio/Source" respectively.
+ *
+ * \section sec_create Create
+ *
+ * Make a new stream with \ref pw_stream_new(). You will need to specify
+ * a name for the stream and extra properties. The basic set of properties
+ * each stream must provide is filled in automatically.
+ *
+ * Once the stream is created, the state_changed event should be used to
+ * track the state of the stream.
+ *
+ * \section sec_connect Connect
+ *
+ * The stream is initially unconnected. To connect the stream, use
+ * \ref pw_stream_connect(). Pass the desired direction as an argument.
+ *
+ * The direction is:
+
+ * \li PW_DIRECTION_INPUT for a stream that *consumes* data. This can be a
+ * stream that captures from a Source or a when the stream is used to
+ * implement a Sink.
+ *
+ * \li PW_DIRECTION_OUTPUT for a stream that *produces* data. This can be a
+ * stream that plays to a Sink or when the stream is used to implement
+ * a Source.
+ *
+ * \subsection ssec_stream_target Stream target
+ *
+ * To make the newly connected stream automatically connect to an existing
+ * PipeWire node, use the \ref PW_STREAM_FLAG_AUTOCONNECT and set the
+ * PW_KEY_OBJECT_SERIAL or the PW_KEY_NODE_NAME value of the target node
+ * in the PW_KEY_TARGET_OBJECT property before connecting.
+ *
+ * \subsection ssec_stream_formats Stream formats
+ *
+ * An array of possible formats that this stream can consume or provide
+ * must be specified.
+ *
+ * \section sec_format Format negotiation
+ *
+ * After connecting the stream, the server will want to configure some
+ * parameters on the stream. You will be notified of these changes
+ * with the param_changed event.
+ *
+ * When a format param change is emitted, the client should now prepare
+ * itself to deal with the format and complete the negotiation procedure
+ * with a call to \ref pw_stream_update_params().
+ *
+ * As arguments to \ref pw_stream_update_params() an array of spa_param
+ * structures must be given. They contain parameters such as buffer size,
+ * number of buffers, required metadata and other parameters for the
+ * media buffers.
+ *
+ * \section sec_buffers Buffer negotiation
+ *
+ * After completing the format negotiation, PipeWire will allocate and
+ * notify the stream of the buffers that will be used to exchange data
+ * between client and server.
+ *
+ * With the add_buffer event, a stream will be notified of a new buffer
+ * that can be used for data transport. You can attach user_data to these
+ * buffers. The buffers can only be used with the stream that emitted
+ * the add_buffer event.
+ *
+ * After the buffers are negotiated, the stream will transition to the
+ * \ref PW_STREAM_STATE_PAUSED state.
+ *
+ * \section sec_streaming Streaming
+ *
+ * From the \ref PW_STREAM_STATE_PAUSED state, the stream can be set to
+ * the \ref PW_STREAM_STATE_STREAMING state by the PipeWire server when
+ * data transport is started.
+ *
+ * Depending on how the stream was connected it will need to Produce or
+ * Consume data for/from PipeWire as explained in the following
+ * subsections.
+ *
+ * \subsection ssec_consume Consume data
+ *
+ * The process event is emitted for each new buffer that can be
+ * consumed.
+ *
+ * \ref pw_stream_dequeue_buffer() should be used to get the data and
+ * metadata of the buffer.
+ *
+ * The buffer is owned by the stream and stays alive until the
+ * remove_buffer event is emitted or the stream is destroyed.
+ *
+ * When the buffer has been processed, call \ref pw_stream_queue_buffer()
+ * to let PipeWire reuse the buffer.
+ *
+ * \subsection ssec_produce Produce data
+ *
+ * \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
+ * remove_buffer event is emitted or the stream is destroyed.
+ *
+ * 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.
+ *
+ * \section sec_stream_disconnect Disconnect
+ *
+ * Use \ref pw_stream_disconnect() to disconnect a stream after use.
+ *
+ * \section sec_stream_configuration Configuration
+ *
+ * \subsection ssec_config_properties Stream Properties
+ *
+ * \subsection ssec_config_rules Stream Rules
+ *
+ * \section sec_stream_environment Environment Variables
+ *
+ */
+/** \defgroup pw_stream Stream
+ *
+ * \brief PipeWire stream objects
+ *
+ * The stream object provides a convenient way to send and
+ * receive data streams from/to PipeWire.
+ *
+ * See also \ref page_streams and \ref api_pw_core
+ */
+
+/**
+ * \addtogroup pw_stream
+ * \{
+ */
+struct pw_stream;
+
+#include <spa/buffer/buffer.h>
+#include <spa/param/param.h>
+#include <spa/pod/command.h>
+
+/** \enum pw_stream_state The state of a stream */
+enum pw_stream_state {
+ PW_STREAM_STATE_ERROR = -1, /**< the stream is in error */
+ PW_STREAM_STATE_UNCONNECTED = 0, /**< unconnected */
+ PW_STREAM_STATE_CONNECTING = 1, /**< connection is in progress */
+ PW_STREAM_STATE_PAUSED = 2, /**< paused */
+ PW_STREAM_STATE_STREAMING = 3 /**< streaming */
+};
+
+/** a buffer structure obtained from pw_stream_dequeue_buffer(). The size of this
+ * structure can grow as more field are added in the future */
+struct pw_buffer {
+ struct spa_buffer *buffer; /**< the spa buffer */
+ void *user_data; /**< user data attached to the buffer */
+ uint64_t size; /**< This field is set by the user and the sum of
+ * all queued buffer is returned in the time info.
+ * For audio, it is advised to use the number of
+ * samples in the buffer for this field. */
+ uint64_t requested; /**< For playback streams, this field contains the
+ * suggested amount of data to provide. For audio
+ * streams this will be the amount of samples
+ * required by the resampler. This field is 0
+ * when no suggestion is provided. Since 0.3.49 */
+};
+
+struct pw_stream_control {
+ const char *name; /**< name of the control */
+ uint32_t flags; /**< extra flags (unused) */
+ float def; /**< default value */
+ float min; /**< min value */
+ float max; /**< max value */
+ float *values; /**< array of values */
+ uint32_t n_values; /**< number of values in array */
+ uint32_t max_values; /**< max values that can be set on this control */
+};
+
+/** A time structure.
+ *
+ * Use pw_stream_get_time_n() to get an updated time snapshot of the stream.
+ * The time snapshot can give information about the time in the driver of the
+ * graph, the delay to the edge of the graph and the internal queuing in the
+ * stream.
+ *
+ * pw_time.ticks gives a monotonic increasing counter of the time in the graph
+ * driver. I can be used to generate a timetime to schedule samples as well
+ * as detect discontinuities in the timeline caused by xruns.
+ *
+ * pw_time.delay is expressed as pw_time.rate, the time domain of the graph. This
+ * value, and pw_time.ticks, were captured at pw_time.now and can be extrapolated
+ * to the current time like this:
+ *
+ * struct timespec ts;
+ * clock_gettime(CLOCK_MONOTONIC, &ts);
+ * int64_t diff = SPA_TIMESPEC_TO_NSEC(&ts) - pw_time.now;
+ * int64_t elapsed = (pw_time.rate.denom * diff) / (pw_time.rate.num * SPA_NSEC_PER_SEC);
+ *
+ * pw_time.delay contains the total delay that a signal will travel through the
+ * graph. This includes the delay caused by filters in the graph as well as delays
+ * caused by the hardware. The delay is usually quite stable and should only change when
+ * the topology, quantum or samplerate of the graph changes.
+ *
+ * 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.
+ *
+ * pw_time.queued is the sum of all the pw_buffer.size fields of the buffers that are
+ * currently queued in the stream but not yet processed. The application can choose
+ * the units of this value, for example, time, samples or bytes (below expressed
+ * as app.rate).
+ *
+ * pw_time.buffered is format dependent, for audio/raw it contains the number of samples
+ * that are buffered inside the resampler/converter.
+ *
+ * The total delay of data in a stream is the sum of the queued and buffered data
+ * (not yet processed data) and the delay to the edge of the graph, usually a
+ * playback or capture device.
+ *
+ * For an audio playback stream, if you were to queue a buffer, the total delay
+ * in milliseconds for the first sample in the newly queued buffer to be played
+ * by the hardware can be calculated as:
+ *
+ * (pw_time.buffered * 1000 / stream.samplerate) +
+ * (pw_time.queued * 1000 / app.rate) +
+ * ((pw_time.delay - elapsed) * 1000 * pw_time.rate.num / pw_time.rate.denom)
+ *
+ * The current extrapolated time (in ms) in the source or sink can be calculated as:
+ *
+ * (pw_time.ticks + elapsed) * 1000 * pw_time.rate.num / pw_time.rate.denom
+ *
+ *
+ * stream time domain graph time domain
+ * /-----------------------\/-----------------------------\
+ *
+ * queue +-+ +-+ +-----------+ +--------+
+ * ----> | | | |->| converter | -> graph -> | kernel | -> speaker
+ * <---- +-+ +-+ +-----------+ +--------+
+ * dequeue buffers \-------------------/\--------/
+ * graph internal
+ * latency latency
+ * \--------/\-------------/\-----------------------------/
+ * queued buffered delay
+ */
+struct pw_time {
+ int64_t now; /**< the monotonic time in nanoseconds. This is the time
+ * when this time report was updated. It is usually
+ * updated every graph cycle. You can use the current
+ * monotonic time to calculate the elapsed time between
+ * this report and the current state and calculate
+ * updated ticks and delay values. */
+ struct spa_fraction rate; /**< the rate of \a ticks and delay. This is usually
+ * expressed in 1/<samplerate>. */
+ uint64_t ticks; /**< the ticks at \a now. This is the current time that
+ * the remote end is reading/writing. This is monotonicaly
+ * increasing. */
+ int64_t delay; /**< delay to device. This is the time it will take for
+ * the next output sample of the stream to be presented by
+ * the playback device or the time a sample traveled
+ * from the capture device. This delay includes the
+ * delay introduced by all filters on the path between
+ * the stream and the device. The delay is normally
+ * constant in a graph and can change when the topology
+ * of the graph or the quantum changes. This delay does
+ * not include the delay caused by queued buffers. */
+ uint64_t queued; /**< data queued in the stream, this is the sum
+ * of the size fields in the pw_buffer that are
+ * currently queued */
+ uint64_t buffered; /**< for audio/raw streams, this contains the extra
+ * number of samples buffered in the resampler.
+ * Since 0.3.50. */
+ uint32_t queued_buffers; /**< The number of buffers that are queued. Since 0.3.50 */
+ uint32_t avail_buffers; /**< The number of buffers that can be dequeued. Since 0.3.50 */
+};
+
+#include <pipewire/port.h>
+
+/** Events for a stream. These events are always called from the mainloop
+ * unless explicitly documented otherwise. */
+struct pw_stream_events {
+#define PW_VERSION_STREAM_EVENTS 2
+ uint32_t version;
+
+ void (*destroy) (void *data);
+ /** when the stream state changes */
+ void (*state_changed) (void *data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error);
+
+ /** Notify information about a control. */
+ void (*control_info) (void *data, uint32_t id, const struct pw_stream_control *control);
+
+ /** when io changed on the stream. */
+ void (*io_changed) (void *data, uint32_t id, void *area, uint32_t size);
+ /** when a parameter changed */
+ void (*param_changed) (void *data, uint32_t id, const struct spa_pod *param);
+
+ /** when a new buffer was created for this stream */
+ void (*add_buffer) (void *data, struct pw_buffer *buffer);
+ /** when a buffer was destroyed for this stream */
+ void (*remove_buffer) (void *data, struct pw_buffer *buffer);
+
+ /** when a buffer can be queued (for playback streams) or
+ * dequeued (for capture streams). 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. */
+ void (*process) (void *data);
+
+ /** The stream is drained */
+ void (*drained) (void *data);
+
+ /** A command notify, Since 0.3.39:1 */
+ void (*command) (void *data, const struct spa_command *command);
+
+ /** a trigger_process completed. Since version 0.3.40:2 */
+ void (*trigger_done) (void *data);
+};
+
+/** Convert a stream state to a readable string */
+const char * pw_stream_state_as_string(enum pw_stream_state state);
+
+/** \enum pw_stream_flags Extra flags that can be used in \ref pw_stream_connect() */
+enum pw_stream_flags {
+ PW_STREAM_FLAG_NONE = 0, /**< no flags */
+ PW_STREAM_FLAG_AUTOCONNECT = (1 << 0), /**< try to automatically connect
+ * this stream */
+ PW_STREAM_FLAG_INACTIVE = (1 << 1), /**< start the stream inactive,
+ * pw_stream_set_active() needs to be
+ * called explicitly */
+ PW_STREAM_FLAG_MAP_BUFFERS = (1 << 2), /**< mmap the buffers except DmaBuf */
+ PW_STREAM_FLAG_DRIVER = (1 << 3), /**< be a driver */
+ PW_STREAM_FLAG_RT_PROCESS = (1 << 4), /**< call process from the realtime
+ * thread. You MUST use RT safe functions
+ * in the process callback. */
+ PW_STREAM_FLAG_NO_CONVERT = (1 << 5), /**< don't convert format */
+ PW_STREAM_FLAG_EXCLUSIVE = (1 << 6), /**< require exclusive access to the
+ * device */
+ PW_STREAM_FLAG_DONT_RECONNECT = (1 << 7), /**< don't try to reconnect this stream
+ * when the sink/source is removed */
+ PW_STREAM_FLAG_ALLOC_BUFFERS = (1 << 8), /**< the application will allocate buffer
+ * memory. In the add_buffer event, the
+ * data of the buffer should be set */
+ PW_STREAM_FLAG_TRIGGER = (1 << 9), /**< the output stream will not be scheduled
+ * automatically but _trigger_process()
+ * needs to be called. This can be used
+ * when the output of the stream depends
+ * on input from other streams. */
+};
+
+/** Create a new unconneced \ref pw_stream
+ * \return a newly allocated \ref pw_stream */
+struct pw_stream *
+pw_stream_new(struct pw_core *core, /**< a \ref pw_core */
+ const char *name, /**< a stream media name */
+ struct pw_properties *props /**< stream properties, ownership is taken */);
+
+struct pw_stream *
+pw_stream_new_simple(struct pw_loop *loop, /**< a \ref pw_loop to use */
+ const char *name, /**< a stream media name */
+ struct pw_properties *props,/**< stream properties, ownership is taken */
+ const struct pw_stream_events *events, /**< stream events */
+ void *data /**< data passed to events */);
+
+/** Destroy a stream */
+void pw_stream_destroy(struct pw_stream *stream);
+
+void pw_stream_add_listener(struct pw_stream *stream,
+ struct spa_hook *listener,
+ const struct pw_stream_events *events,
+ void *data);
+
+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);
+
+struct pw_core *pw_stream_get_core(struct pw_stream *stream);
+
+const struct pw_properties *pw_stream_get_properties(struct pw_stream *stream);
+
+int pw_stream_update_properties(struct pw_stream *stream, const struct spa_dict *dict);
+
+/** Connect a stream for input or output on \a port_path.
+ * \return 0 on success < 0 on error.
+ *
+ * You should connect to the process event and use pw_stream_dequeue_buffer()
+ * to get the latest metadata and data. */
+int
+pw_stream_connect(struct pw_stream *stream, /**< a \ref pw_stream */
+ enum pw_direction direction, /**< the stream direction */
+ uint32_t target_id, /**< should have the value PW_ID_ANY.
+ * To select a specific target
+ * node, specify the
+ * PW_KEY_OBJECT_SERIAL or the
+ * PW_KEY_NODE_NAME value of the target
+ * node in the PW_KEY_TARGET_OBJECT
+ * property of the stream.
+ * Specifying target nodes by
+ * their id is deprecated.
+ */
+ enum pw_stream_flags flags, /**< stream flags */
+ const struct spa_pod **params, /**< an array with params. The params
+ * should ideally contain supported
+ * formats. */
+ uint32_t n_params /**< number of items in \a params */);
+
+/** Get the node ID of the stream.
+ * \return node ID. */
+uint32_t
+pw_stream_get_node_id(struct pw_stream *stream);
+
+/** Disconnect \a stream */
+int pw_stream_disconnect(struct pw_stream *stream);
+
+/** Set the stream in error state */
+int pw_stream_set_error(struct pw_stream *stream, /**< a \ref pw_stream */
+ int res, /**< a result code */
+ const char *error, /**< an error message */
+ ...) SPA_PRINTF_FUNC(3, 4);
+
+/** Complete the negotiation process with result code \a res
+ *
+ * This function should be called after notification of the format.
+
+ * When \a res indicates success, \a params contain the parameters for the
+ * allocation state. */
+int
+pw_stream_update_params(struct pw_stream *stream, /**< a \ref pw_stream */
+ const struct spa_pod **params, /**< an array of params. The params should
+ * ideally contain parameters for doing
+ * buffer allocation. */
+ uint32_t n_params /**< number of elements in \a params */);
+
+/** Get control values */
+const struct pw_stream_control *pw_stream_get_control(struct pw_stream *stream, uint32_t id);
+
+/** 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 */
+int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t size);
+
+/** 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. */
+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. */
+struct pw_buffer *pw_stream_dequeue_buffer(struct pw_stream *stream);
+
+/** Submit a buffer for playback or recycle a buffer for capture. */
+int pw_stream_queue_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);
+
+/** Flush a stream. When \a drain is true, the drained callback will
+ * be called when all data is played or recorded */
+int pw_stream_flush(struct pw_stream *stream, bool drain);
+
+/** Check if the stream is driving. The stream needs to have the
+ * PW_STREAM_FLAG_DRIVER set. When the stream is driving,
+ * pw_stream_trigger_process() needs to be called when data is
+ * available (output) or needed (input). Since 0.3.34 */
+bool pw_stream_is_driving(struct pw_stream *stream);
+
+/** Trigger a push/pull on the stream. One iteration of the graph will
+ * scheduled and process() will be called. Since 0.3.34 */
+int pw_stream_trigger_process(struct pw_stream *stream);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_STREAM_H */
diff --git a/src/pipewire/thread-loop.c b/src/pipewire/thread-loop.c
new file mode 100644
index 0000000..2fdd50e
--- /dev/null
+++ b/src/pipewire/thread-loop.c
@@ -0,0 +1,469 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pthread.h>
+#include <errno.h>
+#include <sys/time.h>
+
+#include <spa/support/thread.h>
+#include <spa/utils/result.h>
+
+#include "log.h"
+#include "thread.h"
+#include "thread-loop.h"
+
+PW_LOG_TOPIC_EXTERN(log_thread_loop);
+#define PW_LOG_TOPIC_DEFAULT log_thread_loop
+
+
+#define pw_thread_loop_events_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_thread_loop_events, m, v, ##__VA_ARGS__)
+#define pw_thread_loop_events_destroy(o) pw_thread_loop_events_emit(o, destroy, 0)
+
+/** \cond */
+struct pw_thread_loop {
+ struct pw_loop *loop;
+ char name[16];
+
+ struct spa_hook_list listener_list;
+
+ pthread_mutex_t lock;
+ pthread_cond_t cond;
+ pthread_cond_t accept_cond;
+
+ pthread_t thread;
+
+ struct spa_hook hook;
+
+ struct spa_source *event;
+
+ int n_waiting;
+ int n_waiting_for_accept;
+ unsigned int created:1;
+ unsigned int running:1;
+};
+/** \endcond */
+
+static void before(void *data)
+{
+ struct pw_thread_loop *this = data;
+ pthread_mutex_unlock(&this->lock);
+}
+
+static void after(void *data)
+{
+ struct pw_thread_loop *this = data;
+ pthread_mutex_lock(&this->lock);
+}
+
+static const struct spa_loop_control_hooks impl_hooks = {
+ SPA_VERSION_LOOP_CONTROL_HOOKS,
+ before,
+ after,
+};
+
+static void do_stop(void *data, uint64_t count)
+{
+ struct pw_thread_loop *this = data;
+ pw_log_debug("stopping");
+ this->running = false;
+}
+
+#define CHECK(expression,label) \
+do { \
+ if ((errno = (expression)) != 0) { \
+ res = -errno; \
+ pw_log_error(#expression ": %s", strerror(errno)); \
+ goto label; \
+ } \
+} while(false);
+
+static struct pw_thread_loop *loop_new(struct pw_loop *loop,
+ const char *name,
+ const struct spa_dict *props)
+{
+ struct pw_thread_loop *this;
+ pthread_mutexattr_t attr;
+ pthread_condattr_t cattr;
+ int res;
+
+ this = calloc(1, sizeof(struct pw_thread_loop));
+ if (this == NULL)
+ return NULL;
+
+ pw_log_debug("%p: new name:%s", this, name);
+
+ if (loop == NULL) {
+ loop = pw_loop_new(props);
+ this->created = true;
+ }
+ if (loop == NULL) {
+ res = -errno;
+ goto clean_this;
+ }
+ this->loop = loop;
+ snprintf(this->name, sizeof(this->name), "%s", name ? name : "pw-thread-loop");
+
+ spa_hook_list_init(&this->listener_list);
+
+ CHECK(pthread_mutexattr_init(&attr), clean_this);
+ CHECK(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE), clean_this);
+ CHECK(pthread_mutex_init(&this->lock, &attr), clean_this);
+
+ CHECK(pthread_condattr_init(&cattr), clean_lock);
+ CHECK(pthread_condattr_setclock(&cattr, CLOCK_REALTIME), clean_lock);
+
+ CHECK(pthread_cond_init(&this->cond, &cattr), clean_lock);
+ CHECK(pthread_cond_init(&this->accept_cond, &cattr), clean_cond);
+
+ if ((this->event = pw_loop_add_event(this->loop, do_stop, this)) == NULL) {
+ res = -errno;
+ goto clean_acceptcond;
+ }
+
+ pw_loop_add_hook(loop, &this->hook, &impl_hooks, this);
+
+ return this;
+
+clean_acceptcond:
+ pthread_cond_destroy(&this->accept_cond);
+clean_cond:
+ pthread_cond_destroy(&this->cond);
+clean_lock:
+ pthread_mutex_destroy(&this->lock);
+clean_this:
+ if (this->created && this->loop)
+ pw_loop_destroy(this->loop);
+ free(this);
+ errno = -res;
+ return NULL;
+}
+
+/** Create a new \ref pw_thread_loop
+ *
+ * \param name the name of the thread or NULL
+ * \param props a dict of properties for the thread loop
+ * \return a newly allocated \ref pw_thread_loop
+ *
+ * Make a new \ref pw_thread_loop that will run in
+ * a thread with \a name.
+ *
+ * After this function you should probably call pw_thread_loop_start() to
+ * actually start the thread
+ *
+ */
+SPA_EXPORT
+struct pw_thread_loop *pw_thread_loop_new(const char *name,
+ const struct spa_dict *props)
+{
+ return loop_new(NULL, name, props);
+}
+
+/** Create a new \ref pw_thread_loop
+ *
+ * \param loop the loop to wrap
+ * \param name the name of the thread or NULL
+ * \param props a dict of properties for the thread loop
+ * \return a newly allocated \ref pw_thread_loop
+ *
+ * Make a new \ref pw_thread_loop that will run \a loop in
+ * a thread with \a name.
+ *
+ * After this function you should probably call pw_thread_loop_start() to
+ * actually start the thread
+ *
+ */
+SPA_EXPORT
+struct pw_thread_loop *pw_thread_loop_new_full(struct pw_loop *loop,
+ const char *name, const struct spa_dict *props)
+{
+ return loop_new(loop, name, props);
+}
+
+/** Destroy a threaded loop */
+SPA_EXPORT
+void pw_thread_loop_destroy(struct pw_thread_loop *loop)
+{
+ pw_thread_loop_events_destroy(loop);
+
+ pw_thread_loop_stop(loop);
+
+ spa_hook_remove(&loop->hook);
+
+ spa_hook_list_clean(&loop->listener_list);
+
+ pw_loop_destroy_source(loop->loop, loop->event);
+
+ if (loop->created)
+ pw_loop_destroy(loop->loop);
+
+ pthread_cond_destroy(&loop->accept_cond);
+ pthread_cond_destroy(&loop->cond);
+ pthread_mutex_destroy(&loop->lock);
+
+ free(loop);
+}
+
+SPA_EXPORT
+void pw_thread_loop_add_listener(struct pw_thread_loop *loop,
+ struct spa_hook *listener,
+ const struct pw_thread_loop_events *events,
+ void *data)
+{
+ spa_hook_list_append(&loop->listener_list, listener, events, data);
+}
+
+SPA_EXPORT
+struct pw_loop *
+pw_thread_loop_get_loop(struct pw_thread_loop *loop)
+{
+ return loop->loop;
+}
+
+static void *do_loop(void *user_data)
+{
+ struct pw_thread_loop *this = user_data;
+ int res;
+
+ pthread_mutex_lock(&this->lock);
+ pw_log_debug("%p: enter thread", this);
+ pw_loop_enter(this->loop);
+
+ while (this->running) {
+ if ((res = pw_loop_iterate(this->loop, -1)) < 0) {
+ if (res == -EINTR)
+ continue;
+ pw_log_warn("%p: iterate error %d (%s)",
+ this, res, spa_strerror(res));
+ }
+ }
+ pw_log_debug("%p: leave thread", this);
+ pw_loop_leave(this->loop);
+ pthread_mutex_unlock(&this->lock);
+
+ return NULL;
+}
+
+/** Start the thread to handle \a loop
+ *
+ * \param loop a \ref pw_thread_loop
+ * \return 0 on success
+ *
+ */
+SPA_EXPORT
+int pw_thread_loop_start(struct pw_thread_loop *loop)
+{
+ int err;
+
+ if (!loop->running) {
+ struct spa_thread *thr;
+ struct spa_dict_item items[1];
+
+ loop->running = true;
+
+ items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_THREAD_NAME, loop->name);
+ thr = pw_thread_utils_create(&SPA_DICT_INIT_ARRAY(items), do_loop, loop);
+ if (thr == NULL)
+ goto error;
+
+ loop->thread = (pthread_t)thr;
+ }
+ return 0;
+
+error:
+ err = errno;
+ pw_log_warn("%p: can't create thread: %s", loop,
+ strerror(err));
+ loop->running = false;
+ return -err;
+}
+
+/** Quit the loop and stop its thread
+ *
+ * \param loop a \ref pw_thread_loop
+ *
+ */
+SPA_EXPORT
+void pw_thread_loop_stop(struct pw_thread_loop *loop)
+{
+ pw_log_debug("%p stopping %d", loop, loop->running);
+ if (loop->running) {
+ pw_log_debug("%p signal", loop);
+ pw_loop_signal_event(loop->loop, loop->event);
+ pw_log_debug("%p join", loop);
+ pthread_join(loop->thread, NULL);
+ pw_log_debug("%p joined", loop);
+ loop->running = false;
+ }
+ pw_log_debug("%p stopped", loop);
+}
+
+/** Lock the mutex associated with \a loop
+ *
+ * \param loop a \ref pw_thread_loop
+ *
+ */
+SPA_EXPORT
+void pw_thread_loop_lock(struct pw_thread_loop *loop)
+{
+ pthread_mutex_lock(&loop->lock);
+ pw_log_trace("%p", loop);
+}
+
+/** Unlock the mutex associated with \a loop
+ *
+ * \param loop a \ref pw_thread_loop
+ *
+ */
+SPA_EXPORT
+void pw_thread_loop_unlock(struct pw_thread_loop *loop)
+{
+ pw_log_trace("%p", loop);
+ pthread_mutex_unlock(&loop->lock);
+}
+
+/** Signal the thread
+ *
+ * \param loop a \ref pw_thread_loop to signal
+ * \param wait_for_accept if we need to wait for accept
+ *
+ * Signal the thread of \a loop. If \a wait_for_accept is true,
+ * this function waits until \ref pw_thread_loop_accept() is called.
+ *
+ */
+SPA_EXPORT
+void pw_thread_loop_signal(struct pw_thread_loop *loop, bool wait_for_accept)
+{
+ pw_log_trace("%p, waiting:%d accept:%d",
+ loop, loop->n_waiting, wait_for_accept);
+ if (loop->n_waiting > 0)
+ pthread_cond_broadcast(&loop->cond);
+
+ if (wait_for_accept) {
+ loop->n_waiting_for_accept++;
+
+ while (loop->n_waiting_for_accept > 0)
+ pthread_cond_wait(&loop->accept_cond, &loop->lock);
+ }
+}
+
+/** Wait for the loop thread to call \ref pw_thread_loop_signal()
+ *
+ * \param loop a \ref pw_thread_loop to signal
+ *
+ */
+SPA_EXPORT
+void pw_thread_loop_wait(struct pw_thread_loop *loop)
+{
+ pw_log_trace("%p, waiting %d", loop, loop->n_waiting);
+ loop->n_waiting++;
+ pthread_cond_wait(&loop->cond, &loop->lock);
+ loop->n_waiting--;
+ pw_log_trace("%p, waiting done %d", loop, loop->n_waiting);
+}
+
+/** Wait for the loop thread to call \ref pw_thread_loop_signal()
+ * or time out.
+ *
+ * \param loop a \ref pw_thread_loop to signal
+ * \param wait_max_sec the maximum number of seconds to wait for a \ref pw_thread_loop_signal()
+ * \return 0 on success or ETIMEDOUT on timeout or a negative errno value.
+ *
+ */
+SPA_EXPORT
+int pw_thread_loop_timed_wait(struct pw_thread_loop *loop, int wait_max_sec)
+{
+ struct timespec timeout;
+ int ret = 0;
+ if ((ret = pw_thread_loop_get_time(loop,
+ &timeout, wait_max_sec * SPA_NSEC_PER_SEC)) < 0)
+ return ret;
+ ret = pw_thread_loop_timed_wait_full(loop, &timeout);
+ return ret == -ETIMEDOUT ? ETIMEDOUT : ret;
+}
+
+/** Get the current time of the loop + timeout. This can be used in
+ * pw_thread_loop_timed_wait_full().
+ *
+ * \param loop a \ref pw_thread_loop
+ * \param abstime the result struct timesspec
+ * \param timeout the time in nanoseconds to add to \a tp
+ * \return 0 on success or a negative errno value on error.
+ *
+ */
+SPA_EXPORT
+int pw_thread_loop_get_time(struct pw_thread_loop *loop, struct timespec *abstime, int64_t timeout)
+{
+ if (clock_gettime(CLOCK_REALTIME, abstime) < 0)
+ return -errno;
+
+ abstime->tv_sec += timeout / SPA_NSEC_PER_SEC;
+ abstime->tv_nsec += timeout % SPA_NSEC_PER_SEC;
+ if (abstime->tv_nsec >= SPA_NSEC_PER_SEC) {
+ abstime->tv_sec++;
+ abstime->tv_nsec -= SPA_NSEC_PER_SEC;
+ }
+ return 0;
+}
+
+/** Wait for the loop thread to call \ref pw_thread_loop_signal()
+ * or time out.
+ *
+ * \param loop a \ref pw_thread_loop to signal
+ * \param abstime the absolute time to wait for a \ref pw_thread_loop_signal()
+ * \return 0 on success or -ETIMEDOUT on timeout or a negative error value
+ *
+ */
+SPA_EXPORT
+int pw_thread_loop_timed_wait_full(struct pw_thread_loop *loop, struct timespec *abstime)
+{
+ int ret;
+ loop->n_waiting++;
+ ret = pthread_cond_timedwait(&loop->cond, &loop->lock, abstime);
+ loop->n_waiting--;
+ return -ret;
+}
+
+/** Signal the loop thread waiting for accept with \ref pw_thread_loop_signal()
+ *
+ * \param loop a \ref pw_thread_loop to signal
+ *
+ */
+SPA_EXPORT
+void pw_thread_loop_accept(struct pw_thread_loop *loop)
+{
+ loop->n_waiting_for_accept--;
+ pthread_cond_signal(&loop->accept_cond);
+}
+
+/** Check if we are inside the thread of the loop
+ *
+ * \param loop a \ref pw_thread_loop to signal
+ * \return true when called inside the thread of \a loop.
+ *
+ */
+SPA_EXPORT
+bool pw_thread_loop_in_thread(struct pw_thread_loop *loop)
+{
+ return loop->running && pthread_self() == loop->thread;
+}
diff --git a/src/pipewire/thread-loop.h b/src/pipewire/thread-loop.h
new file mode 100644
index 0000000..13e8532
--- /dev/null
+++ b/src/pipewire/thread-loop.h
@@ -0,0 +1,175 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_THREAD_LOOP_H
+#define PIPEWIRE_THREAD_LOOP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <pipewire/loop.h>
+
+/** \page page_thread_loop Thread Loop
+ *
+ * \section sec_thread_loop_overview Overview
+ *
+ * The threaded loop implementation is a special wrapper around the
+ * regular \ref pw_loop implementation.
+ *
+ * The added feature in the threaded loop is that it spawns a new thread
+ * that runs the wrapped loop. This allows a synchronous application to use
+ * the asynchronous API without risking to stall the PipeWire library.
+ *
+ * \section sec_thread_loop_create Creation
+ *
+ * A \ref pw_thread_loop object is created using pw_thread_loop_new().
+ * The \ref pw_loop to wrap must be given as an argument along with the name
+ * for the thread that will be spawned.
+ *
+ * After allocating the object, the thread must be started with
+ * pw_thread_loop_start()
+ *
+ * \section sec_thread_loop_destruction Destruction
+ *
+ * When the PipeWire connection has been terminated, the thread must be
+ * stopped and the resources freed. Stopping the thread is done using
+ * pw_thread_loop_stop(), which must be called without the lock (see
+ * below) held. When that function returns, the thread is stopped and the
+ * \ref pw_thread_loop object can be freed using pw_thread_loop_destroy().
+ *
+ * \section sec_thread_loop_locking Locking
+ *
+ * Since the PipeWire API doesn't allow concurrent accesses to objects,
+ * a locking scheme must be used to guarantee safe usage. The threaded
+ * loop API provides such a scheme through the functions
+ * pw_thread_loop_lock() and pw_thread_loop_unlock().
+ *
+ * The lock is recursive, so it's safe to use it multiple times from the same
+ * thread. Just make sure you call pw_thread_loop_unlock() the same
+ * number of times you called pw_thread_loop_lock().
+ *
+ * The lock needs to be held whenever you call any PipeWire function that
+ * uses an object associated with this loop. Make sure you do not hold
+ * on to the lock more than necessary though, as the threaded loop stops
+ * while the lock is held.
+ *
+ * \section sec_thread_loop_events Events and Callbacks
+ *
+ * All events and callbacks are called with the thread lock held.
+ *
+ */
+/** \defgroup pw_thread_loop Thread Loop
+ *
+ * The threaded loop object runs a \ref pw_loop in a separate thread
+ * and ensures proper locking is done.
+ *
+ * All of the loop callbacks will be executed with the loop
+ * lock held.
+ *
+ * See also \ref page_thread_loop
+ */
+
+/**
+ * \addtogroup pw_thread_loop
+ * \{
+ */
+struct pw_thread_loop;
+
+/** Thread loop events */
+struct pw_thread_loop_events {
+#define PW_VERSION_THREAD_LOOP_EVENTS 0
+ uint32_t version;
+
+ /** the loop is destroyed */
+ void (*destroy) (void *data);
+};
+
+/** Make a new thread loop with the given name and optional properties. */
+struct pw_thread_loop *
+pw_thread_loop_new(const char *name, const struct spa_dict *props);
+
+/** Make a new thread loop with the given loop, name and optional properties.
+ * When \a loop is NULL, a new loop will be created. */
+struct pw_thread_loop *
+pw_thread_loop_new_full(struct pw_loop *loop, const char *name, const struct spa_dict *props);
+
+/** Destroy a thread loop */
+void pw_thread_loop_destroy(struct pw_thread_loop *loop);
+
+/** Add an event listener */
+void pw_thread_loop_add_listener(struct pw_thread_loop *loop,
+ struct spa_hook *listener,
+ const struct pw_thread_loop_events *events,
+ void *data);
+
+/** Get the loop implementation of the thread loop */
+struct pw_loop * pw_thread_loop_get_loop(struct pw_thread_loop *loop);
+
+/** Start the thread loop */
+int pw_thread_loop_start(struct pw_thread_loop *loop);
+
+/** Stop the thread loop */
+void pw_thread_loop_stop(struct pw_thread_loop *loop);
+
+/** Lock the loop. This ensures exclusive ownership of the loop */
+void pw_thread_loop_lock(struct pw_thread_loop *loop);
+
+/** Unlock the loop */
+void pw_thread_loop_unlock(struct pw_thread_loop *loop);
+
+/** Release the lock and wait until some thread calls \ref pw_thread_loop_signal */
+void pw_thread_loop_wait(struct pw_thread_loop *loop);
+
+/** Release the lock and wait a maximum of 'wait_max_sec' seconds
+ * until some thread calls \ref pw_thread_loop_signal or time out */
+int pw_thread_loop_timed_wait(struct pw_thread_loop *loop, int wait_max_sec);
+
+/** Get a struct timespec suitable for \ref pw_thread_loop_timed_wait_full.
+ * Since: 0.3.7 */
+int pw_thread_loop_get_time(struct pw_thread_loop *loop, struct timespec *abstime, int64_t timeout);
+
+/** Release the lock and wait up to \a abstime until some thread calls
+ * \ref pw_thread_loop_signal. Use \ref pw_thread_loop_get_time to make a timeout.
+ * Since: 0.3.7 */
+int pw_thread_loop_timed_wait_full(struct pw_thread_loop *loop, struct timespec *abstime);
+
+/** Signal all threads waiting with \ref pw_thread_loop_wait */
+void pw_thread_loop_signal(struct pw_thread_loop *loop, bool wait_for_accept);
+
+/** Signal all threads executing \ref pw_thread_loop_signal with wait_for_accept */
+void pw_thread_loop_accept(struct pw_thread_loop *loop);
+
+/** Check if inside the thread */
+bool pw_thread_loop_in_thread(struct pw_thread_loop *loop);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_THREAD_LOOP_H */
diff --git a/src/pipewire/thread.c b/src/pipewire/thread.c
new file mode 100644
index 0000000..02b8b9e
--- /dev/null
+++ b/src/pipewire/thread.c
@@ -0,0 +1,160 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <pthread.h>
+
+#include <spa/utils/dict.h>
+#include <spa/utils/defs.h>
+#include <spa/utils/list.h>
+
+#include <pipewire/log.h>
+
+#include "thread.h"
+
+#define CHECK(expression,label) \
+do { \
+ if ((errno = (expression)) != 0) { \
+ res = -errno; \
+ pw_log_error(#expression ": %s", strerror(errno)); \
+ goto label; \
+ } \
+} while(false);
+
+SPA_EXPORT
+pthread_attr_t *pw_thread_fill_attr(const struct spa_dict *props, pthread_attr_t *attr)
+{
+ const char *str;
+ int res;
+
+ if (props == NULL)
+ return NULL;
+
+ pthread_attr_init(attr);
+ if ((str = spa_dict_lookup(props, SPA_KEY_THREAD_STACK_SIZE)) != NULL)
+ CHECK(pthread_attr_setstacksize(attr, atoi(str)), error);
+ return attr;
+error:
+ errno = -res;
+ return NULL;
+}
+
+#if defined(__FreeBSD__) || defined(__MidnightBSD__)
+#include <sys/param.h>
+#if __FreeBSD_version < 1202000 || defined(__MidnightBSD__)
+int pthread_setname_np(pthread_t thread, const char *name)
+{
+ pthread_set_name_np(thread, name);
+ return 0;
+}
+#endif
+#endif
+
+static struct spa_thread *impl_create(void *object,
+ const struct spa_dict *props,
+ void *(*start)(void*), void *arg)
+{
+ pthread_t pt;
+ pthread_attr_t *attr = NULL, attributes;
+ const char *str;
+ int err;
+
+ attr = pw_thread_fill_attr(props, &attributes);
+
+ err = pthread_create(&pt, attr, start, arg);
+
+ if (attr)
+ pthread_attr_destroy(attr);
+
+ if (err != 0) {
+ errno = err;
+ return NULL;
+ }
+ if (props) {
+ if ((str = spa_dict_lookup(props, SPA_KEY_THREAD_NAME)) != NULL &&
+ (err = pthread_setname_np(pt, str)) != 0)
+ pw_log_warn("pthread_setname error: %s", strerror(err));
+ }
+ return (struct spa_thread*)pt;
+}
+
+static int impl_join(void *object, struct spa_thread *thread, void **retval)
+{
+ pthread_t pt = (pthread_t)thread;
+ return pthread_join(pt, retval);
+}
+
+static int impl_get_rt_range(void *object, const struct spa_dict *props,
+ int *min, int *max)
+{
+ if (min)
+ *min = sched_get_priority_min(SCHED_OTHER);
+ if (max)
+ *max = sched_get_priority_max(SCHED_OTHER);
+ return 0;
+}
+static int impl_acquire_rt(void *object, struct spa_thread *thread, int priority)
+{
+ pw_log_warn("acquire_rt thread:%p prio:%d not implemented", thread, priority);
+ return -ENOTSUP;
+}
+
+static int impl_drop_rt(void *object, struct spa_thread *thread)
+{
+ pw_log_warn("drop_rt thread:%p not implemented", thread);
+ return -ENOTSUP;
+}
+
+static struct {
+ struct spa_thread_utils utils;
+ struct spa_thread_utils_methods methods;
+} default_impl = {
+ { { SPA_TYPE_INTERFACE_ThreadUtils,
+ SPA_VERSION_THREAD_UTILS,
+ SPA_CALLBACKS_INIT(&default_impl.methods,
+ &default_impl) } },
+ { SPA_VERSION_THREAD_UTILS_METHODS,
+ .create = impl_create,
+ .join = impl_join,
+ .get_rt_range = impl_get_rt_range,
+ .acquire_rt = impl_acquire_rt,
+ .drop_rt = impl_drop_rt,
+ }
+};
+
+static struct spa_thread_utils *global_impl = &default_impl.utils;
+
+SPA_EXPORT
+void pw_thread_utils_set(struct spa_thread_utils *impl)
+{
+ pw_log_warn("pw_thread_utils_set is deprecated and does nothing anymore");
+}
+
+SPA_EXPORT
+struct spa_thread_utils *pw_thread_utils_get(void)
+{
+ return global_impl;
+}
diff --git a/src/pipewire/thread.h b/src/pipewire/thread.h
new file mode 100644
index 0000000..ba84a5d
--- /dev/null
+++ b/src/pipewire/thread.h
@@ -0,0 +1,65 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_THREAD_H
+#define PIPEWIRE_THREAD_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <string.h>
+#include <errno.h>
+
+#include <spa/support/thread.h>
+
+/** \defgroup pw_thread Thread
+ *
+ * \brief functions to manipulate threads
+ */
+
+/**
+ * \addtogroup pw_thread
+ * \{
+ */
+
+SPA_DEPRECATED
+void pw_thread_utils_set(struct spa_thread_utils *impl);
+struct spa_thread_utils *pw_thread_utils_get(void);
+
+#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__)
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_THREAD_H */
diff --git a/src/pipewire/type.h b/src/pipewire/type.h
new file mode 100644
index 0000000..3572531
--- /dev/null
+++ b/src/pipewire/type.h
@@ -0,0 +1,65 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_TYPE_H
+#define PIPEWIRE_TYPE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <spa/utils/type.h>
+
+/** \defgroup pw_type Type info
+ * Type information
+ */
+
+/**
+ * \addtogroup pw_type
+ * \{
+ */
+
+enum {
+ PW_TYPE_FIRST = SPA_TYPE_VENDOR_PipeWire,
+};
+
+#define PW_TYPE_INFO_BASE "PipeWire:"
+
+#define PW_TYPE_INFO_Object PW_TYPE_INFO_BASE "Object"
+#define PW_TYPE_INFO_OBJECT_BASE PW_TYPE_INFO_Object ":"
+
+#define PW_TYPE_INFO_Interface PW_TYPE_INFO_BASE "Interface"
+#define PW_TYPE_INFO_INTERFACE_BASE PW_TYPE_INFO_Interface ":"
+
+const struct spa_type_info * pw_type_info(void);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_TYPE_H */
diff --git a/src/pipewire/utils.c b/src/pipewire/utils.c
new file mode 100644
index 0000000..1d02714
--- /dev/null
+++ b/src/pipewire/utils.c
@@ -0,0 +1,214 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#ifdef HAVE_SYS_RANDOM_H
+#include <sys/random.h>
+#endif
+#include <string.h>
+
+#include <pipewire/array.h>
+#include <pipewire/log.h>
+#include <pipewire/utils.h>
+
+/** Split a string based on delimiters
+ * \param str a string to split
+ * \param delimiter delimiter characters to split on
+ * \param[out] len the length of the current string
+ * \param[in,out] state a state variable
+ * \return a string or NULL when the end is reached
+ *
+ * Repeatedly call this function to split \a str into all substrings
+ * delimited by \a delimiter. \a state should be set to NULL on the first
+ * invocation and passed to the function until NULL is returned.
+ */
+SPA_EXPORT
+const char *pw_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;
+}
+
+/** Split a string based on delimiters
+ * \param str a string to split
+ * \param delimiter delimiter characters to split on
+ * \param max_tokens the max number of tokens to split
+ * \param[out] n_tokens the number of tokens
+ * \return a NULL terminated array of strings that should be
+ * freed with \ref pw_free_strv.
+ */
+SPA_EXPORT
+char **pw_split_strv(const char *str, const char *delimiter, int max_tokens, int *n_tokens)
+{
+ const char *state = NULL, *s = NULL;
+ struct pw_array arr;
+ size_t len;
+ int n = 0;
+
+ pw_array_init(&arr, 16);
+
+ s = pw_split_walk(str, delimiter, &len, &state);
+ while (s && n + 1 < max_tokens) {
+ pw_array_add_ptr(&arr, strndup(s, len));
+ s = pw_split_walk(str, delimiter, &len, &state);
+ n++;
+ }
+ if (s) {
+ pw_array_add_ptr(&arr, strdup(s));
+ n++;
+ }
+ pw_array_add_ptr(&arr, NULL);
+
+ *n_tokens = n;
+
+ return arr.data;
+}
+
+/** Split a string in-place based on delimiters
+ * \param str a string to split
+ * \param delimiter delimiter characters to split on
+ * \param max_tokens the max number of tokens to split
+ * \param[out] tokens an array to hold up to \a max_tokens of strings
+ * \return the number of tokens in \a tokens
+ *
+ * \a str will be modified in-place so that \a tokens will contain zero terminated
+ * strings split at \a delimiter characters.
+ */
+SPA_EXPORT
+int pw_split_ip(char *str, const char *delimiter, int max_tokens, char *tokens[])
+{
+ const char *state = NULL;
+ char *s, *t;
+ size_t len, l2;
+ int n = 0;
+
+ s = (char *)pw_split_walk(str, delimiter, &len, &state);
+ while (s && n + 1 < max_tokens) {
+ t = (char*)pw_split_walk(str, delimiter, &l2, &state);
+ s[len] = '\0';
+ tokens[n++] = s;
+ s = t;
+ len = l2;
+ }
+ if (s)
+ tokens[n++] = s;
+ return n;
+}
+
+/** Free a NULL terminated array of strings
+ * \param str a NULL terminated array of string
+ *
+ * Free all the strings in the array and the array
+ */
+SPA_EXPORT
+void pw_free_strv(char **str)
+{
+ int i;
+
+ if (str == NULL)
+ return;
+
+ for (i = 0; str[i]; i++)
+ free(str[i]);
+ free(str);
+}
+
+/** Strip all whitespace before and after a string
+ * \param str a string to strip
+ * \param whitespace characters to strip
+ * \return the stripped part of \a str
+ *
+ * Strip whitespace before and after \a str. \a str will be
+ * modified.
+ */
+SPA_EXPORT
+char *pw_strip(char *str, const char *whitespace)
+{
+ char *e, *l = NULL;
+
+ str += strspn(str, whitespace);
+
+ for (e = str; *e; e++)
+ if (!strchr(whitespace, *e))
+ l = e;
+
+ if (l)
+ *(l + 1) = '\0';
+ else
+ *str = '\0';
+
+ return str;
+}
+
+/** Fill a buffer with random data
+ * \param buf a buffer to fill
+ * \param buflen the number of bytes to fill
+ * \param flags optional flags
+ * \return the number of bytes filled
+ *
+ * Fill \a buf with \a buflen random bytes.
+ */
+SPA_EXPORT
+ssize_t pw_getrandom(void *buf, size_t buflen, unsigned int flags)
+{
+ ssize_t bytes;
+ int read_errno;
+
+#ifdef HAVE_GETRANDOM
+ bytes = getrandom(buf, buflen, flags);
+ if (!(bytes == -1 && errno == ENOSYS))
+ return bytes;
+#endif
+
+ int fd = open("/dev/urandom", O_CLOEXEC);
+ if (fd < 0)
+ return -1;
+ bytes = read(fd, buf, buflen);
+ read_errno = errno;
+ close(fd);
+ errno = read_errno;
+ return bytes;
+}
+
+SPA_EXPORT
+void* pw_reallocarray(void *ptr, size_t nmemb, size_t size)
+{
+#ifdef HAVE_REALLOCARRAY
+ return reallocarray(ptr, nmemb, size);
+#else
+ return realloc(ptr, nmemb * size);
+#endif
+}
diff --git a/src/pipewire/utils.h b/src/pipewire/utils.h
new file mode 100644
index 0000000..d8d45e0
--- /dev/null
+++ b/src/pipewire/utils.h
@@ -0,0 +1,111 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_UTILS_H
+#define PIPEWIRE_UTILS_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/un.h>
+#ifndef _POSIX_C_SOURCE
+# include <sys/mount.h>
+#endif
+
+#include <spa/utils/defs.h>
+#include <spa/pod/pod.h>
+
+/** \defgroup pw_utils Utilities
+ *
+ * Various utility functions
+ */
+
+/**
+ * \addtogroup pw_utils
+ * \{
+ */
+
+/** a function to destroy an item */
+typedef void (*pw_destroy_t) (void *object);
+
+const char *
+pw_split_walk(const char *str, const char *delimiter, size_t *len, const char **state);
+
+char **
+pw_split_strv(const char *str, const char *delimiter, int max_tokens, int *n_tokens);
+
+int
+pw_split_ip(char *str, const char *delimiter, int max_tokens, char *tokens[]);
+
+void
+pw_free_strv(char **str);
+
+char *
+pw_strip(char *str, const char *whitespace);
+
+#if !defined(strndupa)
+# define strndupa(s, n) \
+ ({ \
+ const char *__old = (s); \
+ size_t __len = strnlen(__old, (n)); \
+ char *__new = (char *) __builtin_alloca(__len + 1); \
+ memcpy(__new, __old, __len); \
+ __new[__len] = '\0'; \
+ __new; \
+ })
+#endif
+
+#if !defined(strdupa)
+# define strdupa(s) \
+ ({ \
+ const char *__old = (s); \
+ size_t __len = strlen(__old) + 1; \
+ char *__new = (char *) alloca(__len); \
+ (char *) memcpy(__new, __old, __len); \
+ })
+#endif
+
+SPA_WARN_UNUSED_RESULT
+ssize_t pw_getrandom(void *buf, size_t buflen, unsigned int flags);
+
+void* pw_reallocarray(void *ptr, size_t nmemb, size_t size);
+
+#ifdef PW_ENABLE_DEPRECATED
+#define PW_DEPRECATED(v) (v)
+#else
+#define PW_DEPRECATED(v) ({ __typeof__(v) _v SPA_DEPRECATED = (v); (void)_v; (v); })
+#endif /* PW_ENABLE_DEPRECATED */
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PIPEWIRE_UTILS_H */
diff --git a/src/pipewire/version.h.in b/src/pipewire/version.h.in
new file mode 100644
index 0000000..55604d0
--- /dev/null
+++ b/src/pipewire/version.h.in
@@ -0,0 +1,68 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_VERSION_H
+#define PIPEWIRE_VERSION_H
+
+/* WARNING: Make sure to edit the real source file version.h.in! */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Return the version of the header files. Keep in mind that this is
+a macro and not a function, so it is impossible to get the pointer of
+it. */
+#define pw_get_headers_version() ("@PIPEWIRE_VERSION_MAJOR@.@PIPEWIRE_VERSION_MINOR@.@PIPEWIRE_VERSION_MICRO@")
+
+/** Return the version of the library the current application is
+ * linked to. */
+const char* pw_get_library_version(void);
+
+/** The current API version. Versions prior to 0.2.0 have
+ * PW_API_VERSION undefined. Please note that this is only ever
+ * increased on incompatible API changes! */
+#define PW_API_VERSION @PIPEWIRE_API_VERSION@
+
+/** The major version of PipeWire. \since 0.2.0 */
+#define PW_MAJOR @PIPEWIRE_VERSION_MAJOR@
+
+/** The minor version of PipeWire. \since 0.2.0 */
+#define PW_MINOR @PIPEWIRE_VERSION_MINOR@
+
+/** The micro version of PipeWire. \since 0.2.0 */
+#define PW_MICRO @PIPEWIRE_VERSION_MICRO@
+
+/** Evaluates to TRUE if the PipeWire library version is equal or
+ * newer than the specified. \since 0.2.0 */
+#define PW_CHECK_VERSION(major,minor,micro) \
+ ((PW_MAJOR > (major)) || \
+ (PW_MAJOR == (major) && PW_MINOR > (minor)) || \
+ (PW_MAJOR == (major) && PW_MINOR == (minor) && PW_MICRO >= (micro)))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_VERSION_H */
diff --git a/src/pipewire/work-queue.c b/src/pipewire/work-queue.c
new file mode 100644
index 0000000..190d29f
--- /dev/null
+++ b/src/pipewire/work-queue.c
@@ -0,0 +1,269 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <spa/utils/type.h>
+#include <spa/utils/result.h>
+
+#include "pipewire/log.h"
+#include "pipewire/work-queue.h"
+
+PW_LOG_TOPIC_EXTERN(log_work_queue);
+#define PW_LOG_TOPIC_DEFAULT log_work_queue
+
+/** \cond */
+struct work_item {
+ void *obj;
+ uint32_t id;
+ uint32_t seq;
+ pw_work_func_t func;
+ void *data;
+ struct spa_list link;
+ int res;
+};
+
+struct pw_work_queue {
+ struct pw_loop *loop;
+
+ struct spa_source *wakeup;
+
+ struct spa_list work_list;
+ struct spa_list free_list;
+ uint32_t counter;
+ uint32_t n_queued;
+};
+/** \endcond */
+
+static void process_work_queue(void *data, uint64_t count)
+{
+ struct pw_work_queue *this = data;
+ struct work_item *item, *tmp;
+
+ spa_list_for_each_safe(item, tmp, &this->work_list, link) {
+ if (item->seq != SPA_ID_INVALID) {
+ pw_log_debug("%p: n_queued:%d waiting for item %p seq:%d id:%u", this,
+ this->n_queued, item->obj, item->seq, item->id);
+ continue;
+ }
+
+ if (item->res == -EBUSY &&
+ item != spa_list_first(&this->work_list, struct work_item, link)) {
+ pw_log_debug("%p: n_queued:%d sync item %p not head id:%u", this,
+ this->n_queued, item->obj, item->id);
+ continue;
+ }
+
+ spa_list_remove(&item->link);
+ this->n_queued--;
+
+ if (item->func) {
+ pw_log_debug("%p: n_queued:%d process work item %p seq:%d res:%d id:%u",
+ this, this->n_queued, item->obj, item->seq, item->res,
+ item->id);
+ item->func(item->obj, item->data, item->res, item->id);
+ }
+ spa_list_append(&this->free_list, &item->link);
+ }
+}
+
+/** Create a new \ref pw_work_queue
+ *
+ * \param loop the loop to use
+ * \return a newly allocated work queue
+ *
+ */
+struct pw_work_queue *pw_work_queue_new(struct pw_loop *loop)
+{
+ struct pw_work_queue *this;
+ int res;
+
+ this = calloc(1, sizeof(struct pw_work_queue));
+ if (this == NULL)
+ return NULL;
+
+ pw_log_debug("%p: new", this);
+
+ this->loop = loop;
+
+ this->wakeup = pw_loop_add_event(this->loop, process_work_queue, this);
+ if (this->wakeup == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+
+ spa_list_init(&this->work_list);
+ spa_list_init(&this->free_list);
+
+ return this;
+
+error_free:
+ free(this);
+ errno = -res;
+ return NULL;
+}
+
+/** Destroy a work queue
+ * \param queue the work queue to destroy
+ *
+ */
+void pw_work_queue_destroy(struct pw_work_queue *queue)
+{
+ struct work_item *item, *tmp;
+
+ pw_log_debug("%p: destroy", queue);
+
+ pw_loop_destroy_source(queue->loop, queue->wakeup);
+
+ spa_list_for_each_safe(item, tmp, &queue->work_list, link) {
+ pw_log_debug("%p: cancel work item %p seq:%d res:%d id:%u",
+ queue, item->obj, item->seq, item->res, item->id);
+ free(item);
+ }
+ spa_list_for_each_safe(item, tmp, &queue->free_list, link)
+ free(item);
+
+ free(queue);
+}
+
+/** Add an item to the work queue
+ *
+ * \param queue the work queue
+ * \param obj the object owning the work item
+ * \param res a result code
+ * \param func a work function
+ * \param data passed to \a func
+ *
+ */
+SPA_EXPORT
+uint32_t
+pw_work_queue_add(struct pw_work_queue *queue, void *obj, int res, pw_work_func_t func, void *data)
+{
+ struct work_item *item;
+ bool have_work = false;
+
+ if (!spa_list_is_empty(&queue->free_list)) {
+ item = spa_list_first(&queue->free_list, struct work_item, link);
+ spa_list_remove(&item->link);
+ } else {
+ item = malloc(sizeof(struct work_item));
+ if (item == NULL)
+ return SPA_ID_INVALID;
+ }
+ item->id = ++queue->counter;
+ if (item->id == SPA_ID_INVALID)
+ item->id = ++queue->counter;
+
+ item->obj = obj;
+ item->func = func;
+ item->data = data;
+
+ if (SPA_RESULT_IS_ASYNC(res)) {
+ item->seq = SPA_RESULT_ASYNC_SEQ(res);
+ item->res = res;
+ pw_log_debug("%p: defer async %d for object %p id:%d",
+ queue, item->seq, obj, item->id);
+ } else if (res == -EBUSY) {
+ pw_log_debug("%p: wait sync object %p id:%u",
+ queue, obj, item->id);
+ item->seq = SPA_ID_INVALID;
+ item->res = res;
+ have_work = true;
+ } else {
+ item->seq = SPA_ID_INVALID;
+ item->res = res;
+ have_work = true;
+ pw_log_debug("%p: defer object %p id:%u", queue, obj, item->id);
+ }
+ spa_list_append(&queue->work_list, &item->link);
+ queue->n_queued++;
+
+ if (have_work)
+ pw_loop_signal_event(queue->loop, queue->wakeup);
+
+ return item->id;
+}
+
+/** Cancel a work item
+ * \param queue the work queue
+ * \param obj the owner object
+ * \param id the wotk id to cancel
+ *
+ */
+SPA_EXPORT
+int pw_work_queue_cancel(struct pw_work_queue *queue, void *obj, uint32_t id)
+{
+ bool have_work = false;
+ struct work_item *item;
+
+ spa_list_for_each(item, &queue->work_list, link) {
+ if ((id == SPA_ID_INVALID || item->id == id) && (obj == NULL || item->obj == obj)) {
+ pw_log_debug("%p: cancel defer %d for object %p id:%u", queue,
+ item->seq, item->obj, id);
+ item->seq = SPA_ID_INVALID;
+ item->func = NULL;
+ have_work = true;
+ }
+ }
+ if (!have_work) {
+ pw_log_debug("%p: no deferred found for object %p id:%u", queue, obj, id);
+ return -EINVAL;
+ }
+
+ pw_loop_signal_event(queue->loop, queue->wakeup);
+ return 0;
+}
+
+/** Complete a work item
+ * \param queue the work queue
+ * \param obj the owner object
+ * \param seq the sequence number that completed
+ * \param res 0 if the item was found, < 0 on error
+ *
+ */
+SPA_EXPORT
+int pw_work_queue_complete(struct pw_work_queue *queue, void *obj, uint32_t seq, int res)
+{
+ struct work_item *item;
+ bool have_work = false;
+
+ spa_list_for_each(item, &queue->work_list, link) {
+ if (item->obj == obj && item->seq == seq) {
+ pw_log_debug("%p: found deferred %d for object %p res:%d id:%u",
+ queue, seq, obj, res, item->id);
+ item->seq = SPA_ID_INVALID;
+ item->res = res;
+ have_work = true;
+ }
+ }
+ if (!have_work) {
+ pw_log_trace("%p: no deferred %d found for object %p", queue, seq, obj);
+ return -EINVAL;
+ }
+
+ pw_loop_signal_event(queue->loop, queue->wakeup);
+ return 0;
+}
diff --git a/src/pipewire/work-queue.h b/src/pipewire/work-queue.h
new file mode 100644
index 0000000..299f8f1
--- /dev/null
+++ b/src/pipewire/work-queue.h
@@ -0,0 +1,71 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef PIPEWIRE_WORK_QUEUE_H
+#define PIPEWIRE_WORK_QUEUE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \defgroup pw_work_queue Work Queue
+ * Queued processing of work items.
+ */
+
+/**
+ * \addtogroup pw_work_queue
+ * \{
+ */
+struct pw_work_queue;
+
+#include <pipewire/loop.h>
+
+typedef void (*pw_work_func_t) (void *obj, void *data, int res, uint32_t id);
+
+struct pw_work_queue *
+pw_work_queue_new(struct pw_loop *loop);
+
+void
+pw_work_queue_destroy(struct pw_work_queue *queue);
+
+uint32_t
+pw_work_queue_add(struct pw_work_queue *queue,
+ void *obj, int res,
+ pw_work_func_t func, void *data);
+
+int
+pw_work_queue_cancel(struct pw_work_queue *queue, void *obj, uint32_t id);
+
+int
+pw_work_queue_complete(struct pw_work_queue *queue, void *obj, uint32_t seq, int res);
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_WORK_QUEUE_H */
diff --git a/src/tests/meson.build b/src/tests/meson.build
new file mode 100644
index 0000000..f7c54f2
--- /dev/null
+++ b/src/tests/meson.build
@@ -0,0 +1,52 @@
+test_apps = [
+ 'test-endpoint',
+ 'test-interfaces',
+ # 'test-remote',
+ 'test-stream',
+ 'test-filter',
+]
+
+foreach a : test_apps
+ test('pw-' + a,
+ executable('pw-' + a, a + '.c',
+ dependencies : [pipewire_dep],
+ include_directories: [includes_inc],
+ install : installed_tests_enabled,
+ install_dir : installed_tests_execdir),
+ env : [
+ 'SPA_PLUGIN_DIR=@0@'.format(spa_dep.get_variable('plugindir')),
+ 'PIPEWIRE_CONFIG_DIR=@0@'.format(pipewire_dep.get_variable('confdatadir')),
+ 'PIPEWIRE_MODULE_DIR=@0@'.format(pipewire_dep.get_variable('moduledir')),
+ ])
+
+ if installed_tests_enabled
+ test_conf = configuration_data()
+ test_conf.set('exec', installed_tests_execdir / 'pw-' + a)
+ configure_file(
+ input: installed_tests_template,
+ output: 'pw-' + a + '.test',
+ install_dir: installed_tests_metadir,
+ configuration: test_conf
+ )
+ endif
+endforeach
+
+
+if have_cpp
+ test_cpp = executable('pw-test-cpp', 'test-cpp.cpp',
+ dependencies : [pipewire_dep],
+ install : installed_tests_enabled,
+ install_dir : installed_tests_execdir)
+ test('pw-test-cpp', test_cpp)
+
+ if installed_tests_enabled
+ test_conf = configuration_data()
+ test_conf.set('exec', installed_tests_execdir / 'pw-test-cpp')
+ configure_file(
+ input: installed_tests_template,
+ output: 'pw-test-cpp.test',
+ install_dir: installed_tests_metadir,
+ configuration: test_conf
+ )
+ endif
+endif
diff --git a/src/tests/test-cpp.cpp b/src/tests/test-cpp.cpp
new file mode 100644
index 0000000..e905724
--- /dev/null
+++ b/src/tests/test-cpp.cpp
@@ -0,0 +1,35 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/pipewire.h>
+#include <pipewire/impl.h>
+#include <pipewire/extensions/client-node.h>
+#include <pipewire/extensions/protocol-native.h>
+
+
+int main(int argc, char *argv[])
+{
+ pw_init(&argc, &argv);
+ return 0;
+}
diff --git a/src/tests/test-endpoint.c b/src/tests/test-endpoint.c
new file mode 100644
index 0000000..dd516a6
--- /dev/null
+++ b/src/tests/test-endpoint.c
@@ -0,0 +1,469 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Collabora Ltd.
+ * @author George Kiagiadakis <george.kiagiadakis@collabora.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <unistd.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/extensions/session-manager.h>
+
+#include <spa/utils/string.h>
+#include <spa/pod/builder.h>
+#include <spa/pod/parser.h>
+#include <spa/pod/filter.h>
+
+#include <valgrind/valgrind.h>
+
+struct props
+{
+ float volume;
+ bool mute;
+};
+
+struct endpoint
+{
+ struct spa_interface iface;
+ struct spa_hook_list hooks;
+ struct pw_properties *properties;
+ struct pw_endpoint_info info;
+ uint32_t n_subscribe_ids;
+ uint32_t subscribe_ids[2];
+ struct props props;
+};
+
+#define pw_endpoint_emit(hooks,method,version,...) \
+ spa_hook_list_call_simple(hooks, struct pw_endpoint_events, \
+ method, version, ##__VA_ARGS__)
+
+#define pw_endpoint_emit_info(hooks,...) pw_endpoint_emit(hooks, info, 0, ##__VA_ARGS__)
+#define pw_endpoint_emit_param(hooks,...) pw_endpoint_emit(hooks, param, 0, ##__VA_ARGS__)
+
+static int
+endpoint_add_listener(void *object,
+ struct spa_hook *listener,
+ const struct pw_endpoint_events *events,
+ void *data)
+{
+ struct endpoint *self = object;
+ struct spa_hook_list save;
+
+ spa_hook_list_isolate(&self->hooks, &save, listener, events, data);
+ pw_endpoint_emit_info(&self->hooks, &self->info);
+ spa_hook_list_join(&self->hooks, &save);
+ return 0;
+}
+
+static int
+endpoint_enum_params (void *object, int seq,
+ uint32_t id, uint32_t start, uint32_t num,
+ const struct spa_pod *filter)
+{
+ struct endpoint *self = object;
+ struct spa_pod *param, *result;
+ struct spa_pod_builder b = { 0 };
+ uint8_t buffer[1024];
+ uint32_t count = 0, index, next = start;
+
+ next = start;
+ next:
+ index = next++;
+
+ spa_pod_builder_init(&b, buffer, sizeof(buffer));
+
+ switch (id) {
+ case SPA_PARAM_PropInfo:
+ {
+ struct props *p = &self->props;
+
+ switch (index) {
+ case 0:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_PropInfo, id,
+ SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_volume),
+ SPA_PROP_INFO_description, SPA_POD_String("volume"),
+ SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->volume, 0.0, 1.0));
+ break;
+ case 1:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_PropInfo, id,
+ SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_mute),
+ SPA_PROP_INFO_description, SPA_POD_String("mute"),
+ SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->mute));
+ break;
+ default:
+ return 0;
+ }
+ break;
+ }
+ case SPA_PARAM_Props:
+ {
+ struct props *p = &self->props;
+
+ switch (index) {
+ case 0:
+ param = spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_Props, id,
+ SPA_PROP_volume, SPA_POD_Float(p->volume),
+ SPA_PROP_mute, SPA_POD_Bool(p->mute));
+ break;
+ default:
+ return 0;
+ }
+ break;
+ }
+ default:
+ return -ENOENT;
+ }
+
+ if (spa_pod_filter(&b, &result, param, filter) < 0)
+ goto next;
+
+ pw_endpoint_emit_param(&self->hooks, seq, id, index, next, result);
+
+ if (++count != num)
+ goto next;
+
+ return 0;
+}
+
+static int
+endpoint_subscribe_params (void *object, uint32_t *ids, uint32_t n_ids)
+{
+ struct endpoint *self = object;
+
+ n_ids = SPA_MIN(n_ids, SPA_N_ELEMENTS(self->subscribe_ids));
+ self->n_subscribe_ids = n_ids;
+
+ for (uint32_t i = 0; i < n_ids; i++) {
+ self->subscribe_ids[i] = ids[i];
+ endpoint_enum_params(object, 1, ids[i], 0, UINT32_MAX, NULL);
+ }
+ return 0;
+}
+
+static int
+endpoint_set_param (void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param)
+{
+ struct endpoint *self = object;
+
+ if (id == SPA_PARAM_Props) {
+ struct props *p = &self->props;
+ spa_pod_parse_object(param,
+ SPA_TYPE_OBJECT_Props, NULL,
+ SPA_PROP_volume, SPA_POD_OPT_Float(&p->volume),
+ SPA_PROP_mute, SPA_POD_OPT_Bool(&p->mute));
+ }
+ else {
+ spa_assert_not_reached();
+ return -ENOENT;
+ }
+
+ for (uint32_t i = 0; i < self->n_subscribe_ids; i++) {
+ if (id == self->subscribe_ids[i])
+ endpoint_enum_params (self, 1, id, 0, UINT32_MAX, NULL);
+ }
+
+ return 0;
+}
+
+static int
+endpoint_create_link (void *object, const struct spa_dict *props)
+{
+ spa_assert_not_reached();
+ return -ENOTSUP;
+}
+
+static const struct pw_endpoint_methods endpoint_methods = {
+ PW_VERSION_ENDPOINT_METHODS,
+ .add_listener = endpoint_add_listener,
+ .subscribe_params = endpoint_subscribe_params,
+ .enum_params = endpoint_enum_params,
+ .set_param = endpoint_set_param,
+ .create_link = endpoint_create_link,
+};
+
+static struct spa_param_info param_info[] = {
+ SPA_PARAM_INFO (SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE),
+ SPA_PARAM_INFO (SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ)
+};
+
+static void
+endpoint_init(struct endpoint * self)
+{
+ self->iface = SPA_INTERFACE_INIT (
+ PW_TYPE_INTERFACE_Endpoint,
+ PW_VERSION_ENDPOINT,
+ &endpoint_methods, self);
+ spa_hook_list_init (&self->hooks);
+
+ self->info.version = PW_VERSION_ENDPOINT_INFO;
+ self->info.change_mask = PW_ENDPOINT_CHANGE_MASK_ALL;
+ self->info.name = "test-endpoint";
+ self->info.media_class = "Audio/Sink";
+ self->info.direction = PW_DIRECTION_OUTPUT;
+ self->info.n_streams = 0;
+ self->info.session_id = SPA_ID_INVALID;
+
+ self->properties = pw_properties_new(
+ PW_KEY_ENDPOINT_NAME, self->info.name,
+ PW_KEY_MEDIA_CLASS, self->info.media_class,
+ NULL);
+ self->info.props = &self->properties->dict;
+
+ self->info.params = param_info;
+ self->info.n_params = SPA_N_ELEMENTS (param_info);
+
+ self->props.volume = 0.9;
+ self->props.mute = false;
+}
+
+static void
+endpoint_clear(struct endpoint * self)
+{
+ spa_hook_list_clean(&self->hooks);
+ pw_properties_free(self->properties);
+}
+
+struct test_endpoint_data
+{
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+ struct pw_core *core;
+
+ struct pw_registry *registry;
+ struct spa_hook registry_listener;
+
+ struct endpoint endpoint;
+ struct pw_proxy *export_proxy;
+ struct pw_proxy *bound_proxy;
+ struct spa_hook object_listener;
+ struct spa_hook proxy_listener;
+
+ struct props props;
+ bool info_received;
+ int params_received;
+};
+
+static void
+endpoint_event_info(void *data, const struct pw_endpoint_info *info)
+{
+ struct test_endpoint_data *d = data;
+ const char *val;
+
+ spa_assert_se(info);
+ spa_assert_se(info->version == PW_VERSION_ENDPOINT_INFO);
+ spa_assert_se(info->id == pw_proxy_get_bound_id(d->bound_proxy));
+ spa_assert_se(info->id == pw_proxy_get_bound_id(d->export_proxy));
+ spa_assert_se(info->change_mask == PW_ENDPOINT_CHANGE_MASK_ALL);
+ spa_assert_se(spa_streq(info->name, "test-endpoint"));
+ spa_assert_se(spa_streq(info->media_class, "Audio/Sink"));
+ spa_assert_se(info->direction == PW_DIRECTION_OUTPUT);
+ spa_assert_se(info->n_streams == 0);
+ spa_assert_se(info->session_id == SPA_ID_INVALID);
+ spa_assert_se(info->n_params == SPA_N_ELEMENTS (param_info));
+ spa_assert_se(info->n_params == 2);
+ spa_assert_se(info->params[0].id == param_info[0].id);
+ spa_assert_se(info->params[0].flags == param_info[0].flags);
+ spa_assert_se(info->params[1].id == param_info[1].id);
+ spa_assert_se(info->params[1].flags == param_info[1].flags);
+ spa_assert_se(info->props != NULL);
+ val = spa_dict_lookup(info->props, PW_KEY_ENDPOINT_NAME);
+ spa_assert_se(val && spa_streq(val, "test-endpoint"));
+ val = spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS);
+ spa_assert_se(val && spa_streq(val, "Audio/Sink"));
+
+ d->info_received = true;
+ pw_main_loop_quit(d->loop);
+}
+
+static void
+endpoint_event_param(void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct test_endpoint_data *d = data;
+
+ if (id == SPA_PARAM_Props) {
+ struct props *p = &d->props;
+ spa_assert_se(param);
+ spa_pod_parse_object(param,
+ SPA_TYPE_OBJECT_Props, &id,
+ SPA_PROP_volume, SPA_POD_OPT_Float(&p->volume),
+ SPA_PROP_mute, SPA_POD_OPT_Bool(&p->mute));
+ spa_assert_se(id == SPA_PARAM_Props);
+ }
+
+ d->params_received++;
+ pw_main_loop_quit(d->loop);
+}
+
+static const struct pw_endpoint_events endpoint_events = {
+ PW_VERSION_ENDPOINT_EVENTS,
+ .info = endpoint_event_info,
+ .param = endpoint_event_param,
+};
+
+static void
+endpoint_proxy_destroy(void *data)
+{
+ struct test_endpoint_data *d = data;
+ d->bound_proxy = NULL;
+ pw_main_loop_quit(d->loop);
+}
+
+static const struct pw_proxy_events proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .destroy = endpoint_proxy_destroy,
+};
+
+static void
+test_endpoint_global(void *data, uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ struct test_endpoint_data *d = data;
+ const char *val;
+
+ if (!spa_streq(type, PW_TYPE_INTERFACE_Endpoint))
+ return;
+
+ d->bound_proxy = pw_registry_bind(d->registry, id, type,
+ PW_VERSION_ENDPOINT, 0);
+ spa_assert_se(d->bound_proxy != NULL);
+
+ spa_assert_se(props != NULL);
+ val = spa_dict_lookup(props, PW_KEY_ENDPOINT_NAME);
+ spa_assert_se(val && spa_streq(val, "test-endpoint"));
+ val = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS);
+ spa_assert_se(val && spa_streq(val, "Audio/Sink"));
+
+ pw_endpoint_add_listener(d->bound_proxy, &d->object_listener,
+ &endpoint_events, d);
+ pw_proxy_add_listener(d->bound_proxy, &d->proxy_listener,
+ &proxy_events, d);
+}
+
+static void
+test_endpoint_global_remove(void *data, uint32_t id)
+{
+ struct test_endpoint_data *d = data;
+ if (d->bound_proxy && id == pw_proxy_get_bound_id(d->bound_proxy))
+ pw_proxy_destroy(d->bound_proxy);
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = test_endpoint_global,
+ .global_remove = test_endpoint_global_remove,
+};
+
+static void test_endpoint(void)
+{
+ struct test_endpoint_data d;
+ uint32_t ids[] = { SPA_PARAM_Props };
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, 1024);
+
+ d.loop = pw_main_loop_new(NULL);
+ d.context = pw_context_new(pw_main_loop_get_loop(d.loop), NULL, 0);
+ spa_assert_se(d.context != NULL);
+
+ d.core = pw_context_connect_self(d.context, NULL, 0);
+ spa_assert_se(d.core != NULL);
+
+ d.registry = pw_core_get_registry(d.core, PW_VERSION_REGISTRY, 0);
+ pw_registry_add_listener(d.registry,
+ &d.registry_listener,
+ &registry_events, &d);
+
+ /* export and expect to get a global on the registry, along with info */
+ d.info_received = false;
+ endpoint_init(&d.endpoint);
+ d.export_proxy = pw_core_export(d.core, PW_TYPE_INTERFACE_Endpoint,
+ d.endpoint.info.props, &d.endpoint.iface, 0);
+ spa_assert_se(d.export_proxy != NULL);
+ pw_main_loop_run(d.loop);
+ spa_assert_se(d.bound_proxy);
+ spa_assert_se(d.info_received == true);
+
+ /* request params */
+ d.params_received = 0;
+ d.props.volume = 0.0;
+ d.props.mute = true;
+ pw_endpoint_subscribe_params(d.bound_proxy, ids, SPA_N_ELEMENTS(ids));
+ pw_main_loop_run(d.loop);
+ spa_assert_se(d.params_received == 1);
+ spa_assert_se(d.props.volume > 0.89 && d.props.volume < 0.91);
+ spa_assert_se(d.props.mute == false);
+
+ /* set param from the client */
+ d.params_received = 0;
+ pw_endpoint_set_param(d.bound_proxy, SPA_PARAM_Props, 0,
+ spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_Props, SPA_PARAM_Props,
+ SPA_PROP_volume, SPA_POD_Float(0.5)));
+ pw_main_loop_run(d.loop);
+ spa_assert_se(d.params_received == 1);
+ spa_assert_se(d.props.volume > 0.49 && d.props.volume < 0.51);
+ spa_assert_se(d.props.mute == false);
+
+ /* set param from the impl */
+ d.params_received = 0;
+ pw_endpoint_set_param(&d.endpoint.iface, SPA_PARAM_Props, 0,
+ spa_pod_builder_add_object(&b,
+ SPA_TYPE_OBJECT_Props, SPA_PARAM_Props,
+ SPA_PROP_volume, SPA_POD_Float(0.2),
+ SPA_PROP_mute, SPA_POD_Bool(true)));
+ pw_main_loop_run(d.loop);
+ spa_assert_se(d.params_received == 1);
+ spa_assert_se(d.props.volume > 0.19 && d.props.volume < 0.21);
+ spa_assert_se(d.props.mute == true);
+
+ /* stop exporting and expect to see that reflected on the registry */
+ pw_proxy_destroy(d.export_proxy);
+ pw_main_loop_run(d.loop);
+ spa_assert_se(!d.bound_proxy);
+
+ endpoint_clear(&d.endpoint);
+ pw_proxy_destroy((struct pw_proxy*)d.registry);
+ pw_context_destroy(d.context);
+ pw_main_loop_destroy(d.loop);
+}
+
+int main(int argc, char *argv[])
+{
+ /* FIXME: This test has a leak and a use of uninitialized buffer
+ * that needs to be debugged and fixed (or excluded). Meanwhile -
+ * skip it from valgrind so we can at least use the others. */
+ if (RUNNING_ON_VALGRIND)
+ return 77;
+
+ pw_init(&argc, &argv);
+
+ alarm(5); /* watchdog; terminate after 5 seconds */
+ test_endpoint();
+
+ return 0;
+}
diff --git a/src/tests/test-filter.c b/src/tests/test-filter.c
new file mode 100644
index 0000000..93f0037
--- /dev/null
+++ b/src/tests/test-filter.c
@@ -0,0 +1,375 @@
+/* PipeWire
+ *
+ * Copyright © 2022 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/pipewire.h>
+#include <pipewire/main-loop.h>
+#include <pipewire/filter.h>
+
+#include <spa/utils/string.h>
+
+#define TEST_FUNC(a,b,func) \
+do { \
+ a.func = b.func; \
+ spa_assert_se(SPA_PTRDIFF(&a.func, &a) == SPA_PTRDIFF(&b.func, &b)); \
+} while(0)
+
+static void test_abi(void)
+{
+ static const struct {
+ uint32_t version;
+ void (*destroy) (void *data);
+ void (*state_changed) (void *data, enum pw_filter_state old,
+ enum pw_filter_state state, const char *error);
+ void (*io_changed) (void *data, void *port_data, uint32_t id, void *area, uint32_t size);
+ void (*param_changed) (void *data, void *port_data, uint32_t id, const struct spa_pod *param);
+ void (*add_buffer) (void *data, void *port_data, struct pw_buffer *buffer);
+ void (*remove_buffer) (void *data, void *port_data, struct pw_buffer *buffer);
+ void (*process) (void *data, struct spa_io_position *position);
+ void (*drained) (void *data);
+ void (*command) (void *data, const struct spa_command *command);
+ } test = { PW_VERSION_FILTER_EVENTS, NULL };
+
+ struct pw_filter_events ev;
+
+ TEST_FUNC(ev, test, destroy);
+ TEST_FUNC(ev, test, state_changed);
+ TEST_FUNC(ev, test, io_changed);
+ TEST_FUNC(ev, test, param_changed);
+ TEST_FUNC(ev, test, add_buffer);
+ TEST_FUNC(ev, test, remove_buffer);
+ TEST_FUNC(ev, test, process);
+ TEST_FUNC(ev, test, drained);
+ TEST_FUNC(ev, test, command);
+
+ spa_assert_se(PW_VERSION_FILTER_EVENTS == 1);
+ spa_assert_se(sizeof(ev) == sizeof(test));
+
+ spa_assert_se(PW_FILTER_STATE_ERROR == -1);
+ spa_assert_se(PW_FILTER_STATE_UNCONNECTED == 0);
+ spa_assert_se(PW_FILTER_STATE_CONNECTING == 1);
+ spa_assert_se(PW_FILTER_STATE_PAUSED == 2);
+ spa_assert_se(PW_FILTER_STATE_STREAMING == 3);
+
+ spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_ERROR) != NULL);
+ spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_UNCONNECTED) != NULL);
+ spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_CONNECTING) != NULL);
+ spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_PAUSED) != NULL);
+ spa_assert_se(pw_filter_state_as_string(PW_FILTER_STATE_STREAMING) != NULL);
+}
+
+static void filter_destroy_error(void *data)
+{
+ spa_assert_not_reached();
+}
+static void filter_state_changed_error(void *data, enum pw_filter_state old,
+ enum pw_filter_state state, const char *error)
+{
+ spa_assert_not_reached();
+}
+static void filter_io_changed_error(void *data, void *port_data, uint32_t id, void *area, uint32_t size)
+{
+ spa_assert_not_reached();
+}
+static void filter_param_changed_error(void *data, void *port_data, uint32_t id, const struct spa_pod *format)
+{
+ spa_assert_not_reached();
+}
+static void filter_add_buffer_error(void *data, void *port_data, struct pw_buffer *buffer)
+{
+ spa_assert_not_reached();
+}
+static void filter_remove_buffer_error(void *data, void *port_data, struct pw_buffer *buffer)
+{
+ spa_assert_not_reached();
+}
+static void filter_process_error(void *data, struct spa_io_position *position)
+{
+ spa_assert_not_reached();
+}
+static void filter_drained_error(void *data)
+{
+ spa_assert_not_reached();
+}
+
+static const struct pw_filter_events filter_events_error =
+{
+ PW_VERSION_FILTER_EVENTS,
+ .destroy = filter_destroy_error,
+ .state_changed = filter_state_changed_error,
+ .io_changed = filter_io_changed_error,
+ .param_changed = filter_param_changed_error,
+ .add_buffer = filter_add_buffer_error,
+ .remove_buffer = filter_remove_buffer_error,
+ .process = filter_process_error,
+ .drained = filter_drained_error
+};
+
+static int destroy_count = 0;
+static void filter_destroy_count(void *data)
+{
+ destroy_count++;
+}
+static void test_create(void)
+{
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+ struct pw_core *core;
+ struct pw_filter *filter;
+ struct pw_filter_events filter_events = filter_events_error;
+ struct spa_hook listener = { 0, };
+ const char *error = NULL;
+
+ loop = pw_main_loop_new(NULL);
+ context = pw_context_new(pw_main_loop_get_loop(loop), NULL, 12);
+ spa_assert_se(context != NULL);
+ core = pw_context_connect_self(context, NULL, 0);
+ spa_assert_se(core != NULL);
+ filter = pw_filter_new(core, "test", NULL);
+ spa_assert_se(filter != NULL);
+ pw_filter_add_listener(filter, &listener, &filter_events, filter);
+
+ /* check state */
+ spa_assert_se(pw_filter_get_state(filter, &error) == PW_FILTER_STATE_UNCONNECTED);
+ spa_assert_se(error == NULL);
+ /* check name */
+ spa_assert_se(spa_streq(pw_filter_get_name(filter), "test"));
+
+ /* check id, only when connected */
+ spa_assert_se(pw_filter_get_node_id(filter) == SPA_ID_INVALID);
+
+ /* check destroy */
+ destroy_count = 0;
+ filter_events.destroy = filter_destroy_count;
+ pw_filter_destroy(filter);
+ spa_assert_se(destroy_count == 1);
+
+ pw_context_destroy(context);
+ pw_main_loop_destroy(loop);
+}
+
+static void test_properties(void)
+{
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+ struct pw_core *core;
+ const struct pw_properties *props;
+ struct pw_filter *filter;
+ struct pw_filter_events filter_events = filter_events_error;
+ struct spa_hook listener = { { NULL }, };
+ struct spa_dict_item items[3];
+
+ loop = pw_main_loop_new(NULL);
+ context = pw_context_new(pw_main_loop_get_loop(loop), NULL, 12);
+ spa_assert_se(context != NULL);
+ core = pw_context_connect_self(context, NULL, 0);
+ spa_assert_se(core != NULL);
+ filter = pw_filter_new(core, "test",
+ pw_properties_new("foo", "bar",
+ "biz", "fuzz",
+ NULL));
+ spa_assert_se(filter != NULL);
+ pw_filter_add_listener(filter, &listener, &filter_events, filter);
+
+ props = pw_filter_get_properties(filter, NULL);
+ spa_assert_se(props != NULL);
+ spa_assert_se(spa_streq(pw_properties_get(props, "foo"), "bar"));
+ spa_assert_se(spa_streq(pw_properties_get(props, "biz"), "fuzz"));
+ spa_assert_se(pw_properties_get(props, "buzz") == NULL);
+
+ /* remove foo */
+ items[0] = SPA_DICT_ITEM_INIT("foo", NULL);
+ /* change biz */
+ items[1] = SPA_DICT_ITEM_INIT("biz", "buzz");
+ /* add buzz */
+ items[2] = SPA_DICT_ITEM_INIT("buzz", "frizz");
+ pw_filter_update_properties(filter, NULL, &SPA_DICT_INIT(items, 3));
+
+ spa_assert_se(props == pw_filter_get_properties(filter, NULL));
+ spa_assert_se(pw_properties_get(props, "foo") == NULL);
+ spa_assert_se(spa_streq(pw_properties_get(props, "biz"), "buzz"));
+ spa_assert_se(spa_streq(pw_properties_get(props, "buzz"), "frizz"));
+
+ /* check destroy */
+ destroy_count = 0;
+ filter_events.destroy = filter_destroy_count;
+ pw_context_destroy(context);
+ spa_assert_se(destroy_count == 1);
+
+ pw_main_loop_destroy(loop);
+}
+
+struct roundtrip_data
+{
+ struct pw_main_loop *loop;
+ int pending;
+ int done;
+};
+
+static void core_event_done(void *object, uint32_t id, int seq)
+{
+ struct roundtrip_data *data = object;
+ if (id == PW_ID_CORE && seq == data->pending) {
+ data->done = 1;
+ printf("done %d\n", seq);
+ pw_main_loop_quit(data->loop);
+ }
+}
+
+static int roundtrip(struct pw_core *core, struct pw_main_loop *loop)
+{
+ struct spa_hook core_listener;
+ struct roundtrip_data data = { .loop = loop };
+ const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .done = core_event_done,
+ };
+ spa_zero(core_listener);
+ pw_core_add_listener(core, &core_listener,
+ &core_events, &data);
+
+ data.pending = pw_core_sync(core, PW_ID_CORE, 0);
+ printf("sync %d\n", data.pending);
+
+ while (!data.done) {
+ pw_main_loop_run(loop);
+ }
+ spa_hook_remove(&core_listener);
+ return 0;
+}
+
+static int node_count = 0;
+static int port_count = 0;
+static void registry_event_global(void *data, uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ printf("object: id:%u type:%s/%d\n", id, type, version);
+ if (spa_streq(type, PW_TYPE_INTERFACE_Port))
+ port_count++;
+ else if (spa_streq(type, PW_TYPE_INTERFACE_Node))
+ node_count++;
+
+}
+static void registry_event_global_remove(void *data, uint32_t id)
+{
+ printf("object: id:%u\n", id);
+}
+
+struct port {
+ struct pw_filter *filter;
+};
+
+static void test_create_port(void)
+{
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+ struct pw_core *core;
+ struct pw_registry *registry;
+ struct pw_filter *filter;
+ struct spa_hook registry_listener = { 0, };
+ static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+ .global_remove = registry_event_global_remove,
+ };
+ int res;
+ struct port *port;
+ enum pw_filter_state state;
+
+ loop = pw_main_loop_new(NULL);
+ context = pw_context_new(pw_main_loop_get_loop(loop), NULL, 12);
+ spa_assert_se(context != NULL);
+ core = pw_context_connect_self(context, NULL, 0);
+ spa_assert_se(core != NULL);
+ filter = pw_filter_new(core, "test", NULL);
+ spa_assert_se(filter != NULL);
+
+ registry = pw_core_get_registry(core, PW_VERSION_REGISTRY, 0);
+ spa_assert_se(registry != NULL);
+ pw_registry_add_listener(registry, &registry_listener,
+ &registry_events, NULL);
+
+ state = pw_filter_get_state(filter, NULL);
+ printf("state %s\n", pw_filter_state_as_string(state));
+ res = pw_filter_connect(filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0);
+ spa_assert_se(res >= 0);
+
+ printf("wait connect\n");
+ while (true) {
+ state = pw_filter_get_state(filter, NULL);
+ printf("state %s\n", pw_filter_state_as_string(state));
+ spa_assert_se(state != PW_FILTER_STATE_ERROR);
+
+ if (state == PW_FILTER_STATE_PAUSED)
+ break;
+
+ roundtrip(core, loop);
+ }
+ spa_assert_se(node_count == 1);
+
+ printf("add port\n");
+ /* make an audio DSP output port */
+ port = pw_filter_add_port(filter,
+ PW_DIRECTION_OUTPUT,
+ PW_FILTER_PORT_FLAG_MAP_BUFFERS,
+ sizeof(struct port),
+ pw_properties_new(
+ PW_KEY_FORMAT_DSP, "32 bit float mono audio",
+ PW_KEY_PORT_NAME, "output",
+ NULL),
+ NULL, 0);
+
+ printf("wait port\n");
+ roundtrip(core, loop);
+
+ spa_assert_se(port_count == 1);
+ printf("port added\n");
+
+ printf("remove port\n");
+ pw_filter_remove_port(port);
+ roundtrip(core, loop);
+
+ printf("destroy\n");
+ /* check destroy */
+ pw_filter_destroy(filter);
+
+ pw_proxy_destroy((struct pw_proxy*)registry);
+
+ pw_context_destroy(context);
+ pw_main_loop_destroy(loop);
+}
+
+int main(int argc, char *argv[])
+{
+ pw_init(&argc, &argv);
+
+ test_abi();
+ test_create();
+ test_properties();
+ test_create_port();
+
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/tests/test-interfaces.c b/src/tests/test-interfaces.c
new file mode 100644
index 0000000..86f772f
--- /dev/null
+++ b/src/tests/test-interfaces.c
@@ -0,0 +1,382 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/pipewire.h>
+
+#define TEST_FUNC(a,b,func) \
+do { \
+ a.func = b.func; \
+ spa_assert_se(SPA_PTRDIFF(&a.func, &a) == SPA_PTRDIFF(&b.func, &b)); \
+} while(0)
+
+static void test_core_abi(void)
+{
+ static const struct {
+ uint32_t version;
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_core_events *events,
+ void *data);
+ int (*hello) (void *object, uint32_t version);
+ int (*sync) (void *object, uint32_t id, int seq);
+ int (*pong) (void *object, uint32_t id, int seq);
+ int (*error) (void *object, uint32_t id, int seq, int res, const char *error);
+ struct pw_registry * (*get_registry) (void *object,
+ uint32_t version, size_t user_data_size);
+ void * (*create_object) (void *object,
+ const char *factory_name,
+ const char *type,
+ uint32_t version,
+ const struct spa_dict *props,
+ size_t user_data_size);
+ int (*destroy) (void *object, void *proxy);
+ } methods = { PW_VERSION_CORE_METHODS, };
+ static const struct {
+ uint32_t version;
+ void (*info) (void *data, const struct pw_core_info *info);
+ void (*done) (void *data, uint32_t id, int seq);
+ void (*ping) (void *data, uint32_t id, int seq);
+ void (*error) (void *data, uint32_t id, int seq, int res, const char *error);
+ void (*remove_id) (void *data, uint32_t id);
+ void (*bound_id) (void *data, uint32_t id, uint32_t global_id);
+ void (*add_mem) (void *data, uint32_t id, uint32_t type, int fd, uint32_t flags);
+ void (*remove_mem) (void *data, uint32_t id);
+ } events = { PW_VERSION_CORE_EVENTS, };
+
+ struct pw_core_events e;
+ struct pw_core_methods m;
+
+ TEST_FUNC(m, methods, version);
+ TEST_FUNC(m, methods, add_listener);
+ TEST_FUNC(m, methods, hello);
+ TEST_FUNC(m, methods, sync);
+ TEST_FUNC(m, methods, pong);
+ TEST_FUNC(m, methods, error);
+ TEST_FUNC(m, methods, get_registry);
+ TEST_FUNC(m, methods, create_object);
+ TEST_FUNC(m, methods, destroy);
+ spa_assert_se(PW_VERSION_CORE_METHODS == 0);
+ spa_assert_se(sizeof(m) == sizeof(methods));
+
+ TEST_FUNC(e, events, version);
+ TEST_FUNC(e, events, info);
+ TEST_FUNC(e, events, done);
+ TEST_FUNC(e, events, ping);
+ TEST_FUNC(e, events, error);
+ TEST_FUNC(e, events, remove_id);
+ TEST_FUNC(e, events, bound_id);
+ TEST_FUNC(e, events, add_mem);
+ TEST_FUNC(e, events, remove_mem);
+ spa_assert_se(PW_VERSION_CORE_EVENTS == 0);
+ spa_assert_se(sizeof(e) == sizeof(events));
+}
+
+static void test_registry_abi(void)
+{
+ struct pw_registry_methods m;
+ struct pw_registry_events e;
+ struct {
+ uint32_t version;
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_registry_events *events,
+ void *data);
+ void * (*bind) (void *object, uint32_t id, const char *type, uint32_t version,
+ size_t user_data_size);
+ int (*destroy) (void *object, uint32_t id);
+ } methods = { PW_VERSION_REGISTRY_METHODS, };
+ struct {
+ uint32_t version;
+ void (*global) (void *data, uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const struct spa_dict *props);
+ void (*global_remove) (void *data, uint32_t id);
+ } events = { PW_VERSION_REGISTRY_EVENTS, };
+
+ TEST_FUNC(m, methods, version);
+ TEST_FUNC(m, methods, add_listener);
+ TEST_FUNC(m, methods, bind);
+ TEST_FUNC(m, methods, destroy);
+ spa_assert_se(PW_VERSION_REGISTRY_METHODS == 0);
+ spa_assert_se(sizeof(m) == sizeof(methods));
+
+ TEST_FUNC(e, events, version);
+ TEST_FUNC(e, events, global);
+ TEST_FUNC(e, events, global_remove);
+ spa_assert_se(PW_VERSION_REGISTRY_EVENTS == 0);
+ spa_assert_se(sizeof(e) == sizeof(events));
+}
+
+static void test_module_abi(void)
+{
+ struct pw_module_methods m;
+ struct pw_module_events e;
+ struct {
+ uint32_t version;
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_module_events *events,
+ void *data);
+ } methods = { PW_VERSION_MODULE_METHODS, };
+ struct {
+ uint32_t version;
+ void (*info) (void *data, const struct pw_module_info *info);
+ } events = { PW_VERSION_MODULE_EVENTS, };
+
+ TEST_FUNC(m, methods, version);
+ TEST_FUNC(m, methods, add_listener);
+ spa_assert_se(PW_VERSION_MODULE_METHODS == 0);
+ spa_assert_se(sizeof(m) == sizeof(methods));
+
+ TEST_FUNC(e, events, version);
+ TEST_FUNC(e, events, info);
+ spa_assert_se(PW_VERSION_MODULE_EVENTS == 0);
+ spa_assert_se(sizeof(e) == sizeof(events));
+}
+
+static void test_device_abi(void)
+{
+ struct pw_device_methods m;
+ struct pw_device_events e;
+ struct {
+ uint32_t version;
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_device_events *events,
+ void *data);
+ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
+ int (*enum_params) (void *object, int seq, uint32_t id,
+ uint32_t start, uint32_t num,
+ const struct spa_pod *filter);
+ int (*set_param) (void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+ } methods = { PW_VERSION_DEVICE_METHODS, };
+ struct {
+ uint32_t version;
+ void (*info) (void *data, const struct pw_device_info *info);
+ void (*param) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param);
+ } events = { PW_VERSION_DEVICE_EVENTS, };
+
+ TEST_FUNC(m, methods, version);
+ TEST_FUNC(m, methods, add_listener);
+ TEST_FUNC(m, methods, subscribe_params);
+ TEST_FUNC(m, methods, enum_params);
+ TEST_FUNC(m, methods, set_param);
+ spa_assert_se(PW_VERSION_DEVICE_METHODS == 0);
+ spa_assert_se(sizeof(m) == sizeof(methods));
+
+ TEST_FUNC(e, events, version);
+ TEST_FUNC(e, events, info);
+ TEST_FUNC(e, events, param);
+ spa_assert_se(PW_VERSION_DEVICE_EVENTS == 0);
+ spa_assert_se(sizeof(e) == sizeof(events));
+}
+
+static void test_node_abi(void)
+{
+ struct pw_node_methods m;
+ struct pw_node_events e;
+ struct {
+ uint32_t version;
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_node_events *events,
+ void *data);
+ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
+ int (*enum_params) (void *object, int seq, uint32_t id,
+ uint32_t start, uint32_t num, const struct spa_pod *filter);
+ int (*set_param) (void *object, uint32_t id, uint32_t flags,
+ const struct spa_pod *param);
+ int (*send_command) (void *object, const struct spa_command *command);
+ } methods = { PW_VERSION_NODE_METHODS, };
+ struct {
+ uint32_t version;
+ void (*info) (void *data, const struct pw_node_info *info);
+ void (*param) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param);
+ } events = { PW_VERSION_NODE_EVENTS, };
+
+ TEST_FUNC(m, methods, version);
+ TEST_FUNC(m, methods, add_listener);
+ TEST_FUNC(m, methods, subscribe_params);
+ TEST_FUNC(m, methods, enum_params);
+ TEST_FUNC(m, methods, set_param);
+ TEST_FUNC(m, methods, send_command);
+ spa_assert_se(PW_VERSION_NODE_METHODS == 0);
+ spa_assert_se(sizeof(m) == sizeof(methods));
+
+ TEST_FUNC(e, events, version);
+ TEST_FUNC(e, events, info);
+ TEST_FUNC(e, events, param);
+ spa_assert_se(PW_VERSION_NODE_EVENTS == 0);
+ spa_assert_se(sizeof(e) == sizeof(events));
+}
+
+static void test_port_abi(void)
+{
+ struct pw_port_methods m;
+ struct pw_port_events e;
+ struct {
+ uint32_t version;
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_port_events *events,
+ void *data);
+ int (*subscribe_params) (void *object, uint32_t *ids, uint32_t n_ids);
+ int (*enum_params) (void *object, int seq, uint32_t id,
+ uint32_t start, uint32_t num, const struct spa_pod *filter);
+ } methods = { PW_VERSION_PORT_METHODS, };
+ struct {
+ uint32_t version;
+ void (*info) (void *data, const struct pw_port_info *info);
+ void (*param) (void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param);
+ } events = { PW_VERSION_PORT_EVENTS, };
+
+ TEST_FUNC(m, methods, version);
+ TEST_FUNC(m, methods, add_listener);
+ TEST_FUNC(m, methods, enum_params);
+ spa_assert_se(PW_VERSION_PORT_METHODS == 0);
+ spa_assert_se(sizeof(m) == sizeof(methods));
+
+ TEST_FUNC(e, events, version);
+ TEST_FUNC(e, events, info);
+ TEST_FUNC(e, events, param);
+ spa_assert_se(PW_VERSION_PORT_EVENTS == 0);
+ spa_assert_se(sizeof(e) == sizeof(events));
+}
+
+static void test_factory_abi(void)
+{
+ struct pw_factory_methods m;
+ struct pw_factory_events e;
+ struct {
+ uint32_t version;
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_factory_events *events,
+ void *data);
+ } methods = { PW_VERSION_FACTORY_METHODS, };
+ struct {
+ uint32_t version;
+ void (*info) (void *data, const struct pw_factory_info *info);
+ } events = { PW_VERSION_FACTORY_EVENTS, };
+
+ TEST_FUNC(m, methods, version);
+ TEST_FUNC(m, methods, add_listener);
+ spa_assert_se(PW_VERSION_FACTORY_METHODS == 0);
+ spa_assert_se(sizeof(m) == sizeof(methods));
+
+ TEST_FUNC(e, events, version);
+ TEST_FUNC(e, events, info);
+ spa_assert_se(PW_VERSION_FACTORY_EVENTS == 0);
+ spa_assert_se(sizeof(e) == sizeof(events));
+}
+
+static void test_client_abi(void)
+{
+ struct pw_client_methods m;
+ struct pw_client_events e;
+ struct {
+ uint32_t version;
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_client_events *events,
+ void *data);
+ int (*error) (void *object, uint32_t id, int res, const char *error);
+ int (*update_properties) (void *object, const struct spa_dict *props);
+ int (*get_permissions) (void *object, uint32_t index, uint32_t num);
+ int (*update_permissions) (void *object, uint32_t n_permissions,
+ const struct pw_permission *permissions);
+ } methods = { PW_VERSION_CLIENT_METHODS, };
+ struct {
+ uint32_t version;
+ void (*info) (void *data, const struct pw_client_info *info);
+ void (*permissions) (void *data, uint32_t index,
+ uint32_t n_permissions, const struct pw_permission *permissions);
+ } events = { PW_VERSION_CLIENT_EVENTS, };
+
+ TEST_FUNC(m, methods, version);
+ TEST_FUNC(m, methods, add_listener);
+ TEST_FUNC(m, methods, error);
+ TEST_FUNC(m, methods, update_properties);
+ TEST_FUNC(m, methods, get_permissions);
+ TEST_FUNC(m, methods, update_permissions);
+ spa_assert_se(PW_VERSION_CLIENT_METHODS == 0);
+ spa_assert_se(sizeof(m) == sizeof(methods));
+
+ TEST_FUNC(e, events, version);
+ TEST_FUNC(e, events, info);
+ TEST_FUNC(e, events, permissions);
+ spa_assert_se(PW_VERSION_CLIENT_EVENTS == 0);
+ spa_assert_se(sizeof(e) == sizeof(events));
+}
+
+static void test_link_abi(void)
+{
+ struct pw_link_methods m;
+ struct pw_link_events e;
+ struct {
+ uint32_t version;
+ int (*add_listener) (void *object,
+ struct spa_hook *listener,
+ const struct pw_link_events *events,
+ void *data);
+ } methods = { PW_VERSION_LINK_METHODS, };
+ struct {
+ uint32_t version;
+ void (*info) (void *data, const struct pw_link_info *info);
+ } events = { PW_VERSION_LINK_EVENTS, };
+
+ TEST_FUNC(m, methods, version);
+ TEST_FUNC(m, methods, add_listener);
+ spa_assert_se(PW_VERSION_LINK_METHODS == 0);
+ spa_assert_se(sizeof(m) == sizeof(methods));
+
+ TEST_FUNC(e, events, version);
+ TEST_FUNC(e, events, info);
+ spa_assert_se(PW_VERSION_LINK_EVENTS == 0);
+ spa_assert_se(sizeof(e) == sizeof(events));
+}
+
+int main(int argc, char *argv[])
+{
+ pw_init(&argc, &argv);
+
+ test_core_abi();
+ test_registry_abi();
+ test_module_abi();
+ test_device_abi();
+ test_node_abi();
+ test_port_abi();
+ test_factory_abi();
+ test_client_abi();
+ test_link_abi();
+
+ return 0;
+}
diff --git a/src/tests/test-stream.c b/src/tests/test-stream.c
new file mode 100644
index 0000000..1e54fed
--- /dev/null
+++ b/src/tests/test-stream.c
@@ -0,0 +1,257 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <pipewire/pipewire.h>
+#include <pipewire/main-loop.h>
+#include <pipewire/stream.h>
+
+#include <spa/utils/string.h>
+
+#define TEST_FUNC(a,b,func) \
+do { \
+ a.func = b.func; \
+ spa_assert_se(SPA_PTRDIFF(&a.func, &a) == SPA_PTRDIFF(&b.func, &b)); \
+} while(0)
+
+static void test_abi(void)
+{
+ static const struct {
+ uint32_t version;
+ void (*destroy) (void *data);
+ void (*state_changed) (void *data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error);
+ void (*control_info) (void *data, uint32_t id, const struct pw_stream_control *control);
+ void (*io_changed) (void *data, uint32_t id, void *area, uint32_t size);
+ void (*param_changed) (void *data, uint32_t id, const struct spa_pod *param);
+ void (*add_buffer) (void *data, struct pw_buffer *buffer);
+ void (*remove_buffer) (void *data, struct pw_buffer *buffer);
+ void (*process) (void *data);
+ void (*drained) (void *data);
+ void (*command) (void *data, const struct spa_command *command);
+ void (*trigger_done) (void *data);
+ } test = { PW_VERSION_STREAM_EVENTS, NULL };
+
+ struct pw_stream_events ev;
+
+ TEST_FUNC(ev, test, destroy);
+ TEST_FUNC(ev, test, state_changed);
+ TEST_FUNC(ev, test, control_info);
+ TEST_FUNC(ev, test, io_changed);
+ TEST_FUNC(ev, test, param_changed);
+ TEST_FUNC(ev, test, add_buffer);
+ TEST_FUNC(ev, test, remove_buffer);
+ TEST_FUNC(ev, test, process);
+ TEST_FUNC(ev, test, drained);
+ TEST_FUNC(ev, test, command);
+ TEST_FUNC(ev, test, trigger_done);
+
+#if defined(__x86_64__) && defined(__LP64__)
+ spa_assert_se(sizeof(struct pw_buffer) == 32);
+ spa_assert_se(sizeof(struct pw_time) == 56);
+#else
+ fprintf(stderr, "%zd\n", sizeof(struct pw_buffer));
+ fprintf(stderr, "%zd\n", sizeof(struct pw_time));
+#endif
+
+ spa_assert_se(PW_VERSION_STREAM_EVENTS == 2);
+ spa_assert_se(sizeof(ev) == sizeof(test));
+
+ spa_assert_se(PW_STREAM_STATE_ERROR == -1);
+ spa_assert_se(PW_STREAM_STATE_UNCONNECTED == 0);
+ spa_assert_se(PW_STREAM_STATE_CONNECTING == 1);
+ spa_assert_se(PW_STREAM_STATE_PAUSED == 2);
+ spa_assert_se(PW_STREAM_STATE_STREAMING == 3);
+
+ spa_assert_se(pw_stream_state_as_string(PW_STREAM_STATE_ERROR) != NULL);
+ spa_assert_se(pw_stream_state_as_string(PW_STREAM_STATE_UNCONNECTED) != NULL);
+ spa_assert_se(pw_stream_state_as_string(PW_STREAM_STATE_CONNECTING) != NULL);
+ spa_assert_se(pw_stream_state_as_string(PW_STREAM_STATE_PAUSED) != NULL);
+ spa_assert_se(pw_stream_state_as_string(PW_STREAM_STATE_STREAMING) != NULL);
+}
+
+static void stream_destroy_error(void *data)
+{
+ spa_assert_not_reached();
+}
+static void stream_state_changed_error(void *data, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ spa_assert_not_reached();
+}
+static void stream_io_changed_error(void *data, uint32_t id, void *area, uint32_t size)
+{
+ spa_assert_not_reached();
+}
+static void stream_param_changed_error(void *data, uint32_t id, const struct spa_pod *format)
+{
+ spa_assert_not_reached();
+}
+static void stream_add_buffer_error(void *data, struct pw_buffer *buffer)
+{
+ spa_assert_not_reached();
+}
+static void stream_remove_buffer_error(void *data, struct pw_buffer *buffer)
+{
+ spa_assert_not_reached();
+}
+static void stream_process_error(void *data)
+{
+ spa_assert_not_reached();
+}
+static void stream_drained_error(void *data)
+{
+ spa_assert_not_reached();
+}
+
+static const struct pw_stream_events stream_events_error =
+{
+ PW_VERSION_STREAM_EVENTS,
+ .destroy = stream_destroy_error,
+ .state_changed = stream_state_changed_error,
+ .io_changed = stream_io_changed_error,
+ .param_changed = stream_param_changed_error,
+ .add_buffer = stream_add_buffer_error,
+ .remove_buffer = stream_remove_buffer_error,
+ .process = stream_process_error,
+ .drained = stream_drained_error
+};
+
+static int destroy_count = 0;
+static void stream_destroy_count(void *data)
+{
+ destroy_count++;
+}
+static void test_create(void)
+{
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+ struct pw_core *core;
+ struct pw_stream *stream;
+ struct pw_stream_events stream_events = stream_events_error;
+ struct spa_hook listener = { 0, };
+ const char *error = NULL;
+ struct pw_time tm;
+
+ loop = pw_main_loop_new(NULL);
+ context = pw_context_new(pw_main_loop_get_loop(loop), NULL, 12);
+ spa_assert_se(context != NULL);
+ core = pw_context_connect_self(context, NULL, 0);
+ spa_assert_se(core != NULL);
+ stream = pw_stream_new(core, "test", NULL);
+ spa_assert_se(stream != NULL);
+ pw_stream_add_listener(stream, &listener, &stream_events, stream);
+
+ /* check state */
+ spa_assert_se(pw_stream_get_state(stream, &error) == PW_STREAM_STATE_UNCONNECTED);
+ spa_assert_se(error == NULL);
+ /* check name */
+ spa_assert_se(spa_streq(pw_stream_get_name(stream), "test"));
+
+ /* check id, only when connected */
+ spa_assert_se(pw_stream_get_node_id(stream) == SPA_ID_INVALID);
+
+ spa_assert_se(pw_stream_get_time_n(stream, &tm, sizeof(tm)) == 0);
+ spa_assert_se(tm.now == 0);
+ spa_assert_se(tm.rate.num == 0);
+ spa_assert_se(tm.rate.denom == 0);
+ spa_assert_se(tm.ticks == 0);
+ spa_assert_se(tm.delay == 0);
+ spa_assert_se(tm.queued == 0);
+ spa_assert_se(tm.buffered == 0);
+
+ spa_assert_se(pw_stream_dequeue_buffer(stream) == NULL);
+
+ /* check destroy */
+ destroy_count = 0;
+ stream_events.destroy = stream_destroy_count;
+ pw_stream_destroy(stream);
+ spa_assert_se(destroy_count == 1);
+
+ pw_context_destroy(context);
+ pw_main_loop_destroy(loop);
+}
+
+static void test_properties(void)
+{
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+ struct pw_core *core;
+ const struct pw_properties *props;
+ struct pw_stream *stream;
+ struct pw_stream_events stream_events = stream_events_error;
+ struct spa_hook listener = { { NULL }, };
+ struct spa_dict_item items[3];
+
+ loop = pw_main_loop_new(NULL);
+ context = pw_context_new(pw_main_loop_get_loop(loop), NULL, 12);
+ spa_assert_se(context != NULL);
+ core = pw_context_connect_self(context, NULL, 0);
+ spa_assert_se(core != NULL);
+ stream = pw_stream_new(core, "test",
+ pw_properties_new("foo", "bar",
+ "biz", "fuzz",
+ NULL));
+ spa_assert_se(stream != NULL);
+ pw_stream_add_listener(stream, &listener, &stream_events, stream);
+
+ props = pw_stream_get_properties(stream);
+ spa_assert_se(props != NULL);
+ spa_assert_se(spa_streq(pw_properties_get(props, "foo"), "bar"));
+ spa_assert_se(spa_streq(pw_properties_get(props, "biz"), "fuzz"));
+ spa_assert_se(pw_properties_get(props, "buzz") == NULL);
+
+ /* remove foo */
+ items[0] = SPA_DICT_ITEM_INIT("foo", NULL);
+ /* change biz */
+ items[1] = SPA_DICT_ITEM_INIT("biz", "buzz");
+ /* add buzz */
+ items[2] = SPA_DICT_ITEM_INIT("buzz", "frizz");
+ pw_stream_update_properties(stream, &SPA_DICT_INIT(items, 3));
+
+ spa_assert_se(props == pw_stream_get_properties(stream));
+ spa_assert_se(pw_properties_get(props, "foo") == NULL);
+ spa_assert_se(spa_streq(pw_properties_get(props, "biz"), "buzz"));
+ spa_assert_se(spa_streq(pw_properties_get(props, "buzz"), "frizz"));
+
+ /* check destroy */
+ destroy_count = 0;
+ stream_events.destroy = stream_destroy_count;
+ pw_context_destroy(context);
+ spa_assert_se(destroy_count == 1);
+
+ pw_main_loop_destroy(loop);
+}
+
+int main(int argc, char *argv[])
+{
+ pw_init(&argc, &argv);
+
+ test_abi();
+ test_create();
+ test_properties();
+
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/tools/dsffile.c b/src/tools/dsffile.c
new file mode 100644
index 0000000..9c6ce0c
--- /dev/null
+++ b/src/tools/dsffile.c
@@ -0,0 +1,266 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <math.h>
+
+#include <spa/utils/string.h>
+
+#include "dsffile.h"
+
+struct dsf_file {
+ uint8_t *data;
+ size_t size;
+
+ int mode;
+ int fd;
+
+ struct dsf_file_info info;
+
+ uint8_t *p;
+ size_t offset;
+};
+
+static inline uint32_t parse_le32(const uint8_t *in)
+{
+ return in[0] | (in[1] << 8) | (in[2] << 16) | (in[3] << 24);
+}
+
+static inline uint64_t parse_le64(const uint8_t *in)
+{
+ uint64_t res = in[0];
+ res |= ((uint64_t)in[1]) << 8;
+ res |= ((uint64_t)in[2]) << 16;
+ res |= ((uint64_t)in[3]) << 24;
+ res |= ((uint64_t)in[4]) << 32;
+ res |= ((uint64_t)in[5]) << 40;
+ res |= ((uint64_t)in[6]) << 48;
+ res |= ((uint64_t)in[7]) << 56;
+ return res;
+}
+
+static inline int f_avail(struct dsf_file *f)
+{
+ if (f->p < f->data + f->size)
+ return f->size + f->data - f->p;
+ return 0;
+}
+
+static int read_DSD(struct dsf_file *f)
+{
+ uint64_t size;
+
+ if (f_avail(f) < 28 ||
+ memcmp(f->p, "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;
+ return 0;
+}
+
+static int read_fmt(struct dsf_file *f)
+{
+ uint64_t size;
+
+ if (f_avail(f) < 52 ||
+ memcmp(f->p, "fmt ", 4) != 0)
+ return -EINVAL;
+
+ size = parse_le64(f->p + 4); /* size of this chunk */
+ if (parse_le32(f->p + 12) != 1) /* version */
+ return -EINVAL;
+ if (parse_le32(f->p + 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;
+ return 0;
+}
+
+static int read_data(struct dsf_file *f)
+{
+ uint64_t size;
+
+ if (f_avail(f) < 12 ||
+ memcmp(f->p, "data", 4) != 0)
+ return -EINVAL;
+
+ size = parse_le64(f->p + 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;
+ }
+
+ f->p = f->data;
+
+ if ((res = read_DSD(f)) < 0)
+ goto exit_unmap;
+ if ((res = read_fmt(f)) < 0)
+ goto exit_unmap;
+ if ((res = read_data(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);
+exit:
+ return res;
+}
+
+struct dsf_file *
+dsf_file_open(const char *filename, const char *mode, struct dsf_file_info *info)
+{
+ int res;
+ struct dsf_file *f;
+
+ f = calloc(1, sizeof(struct dsf_file));
+ if (f == NULL)
+ return NULL;
+
+ if (spa_streq(mode, "r")) {
+ if ((res = open_read(f, filename, info)) < 0)
+ goto exit_free;
+ } else {
+ res = -EINVAL;
+ goto exit_free;
+ }
+ return f;
+
+exit_free:
+ free(f);
+ errno = -res;
+ return NULL;
+}
+
+static const uint8_t bitrev[256] = {
+ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
+};
+
+ssize_t
+dsf_file_read(struct dsf_file *f, void *data, size_t samples, const struct dsf_layout *layout)
+{
+ uint8_t *d = data;
+ int step = SPA_ABS(layout->interleave);
+ bool rev = layout->lsb != f->info.lsb;
+ size_t total, block, offset, pos, scale;
+
+ block = f->offset / f->info.blocksize;
+ offset = block * f->info.blocksize * f->info.channels;
+ pos = f->offset % f->info.blocksize;
+ scale = SPA_CLAMP(f->info.rate / (44100u * 64u), 1u, 4u);
+
+ samples *= step;
+ samples *= scale;
+
+ for (total = 0; total < samples && offset + pos < f->info.length; total++) {
+ const uint8_t *s = f->p + offset + pos;
+ uint32_t i;
+
+ for (i = 0; i < layout->channels; i++) {
+ const uint8_t *c = &s[f->info.blocksize * i];
+ int j;
+
+ if (layout->interleave > 0) {
+ for (j = 0; j < step; j++)
+ *d++ = rev ? bitrev[c[j]] : c[j];
+ } else {
+ for (j = step-1; j >= 0; j--)
+ *d++ = rev ? bitrev[c[j]] : c[j];
+ }
+ }
+ pos += step;
+ if (pos == f->info.blocksize) {
+ pos = 0;
+ offset += f->info.blocksize * f->info.channels;
+ }
+ }
+ f->offset += total * step;
+
+ return total;
+}
+
+int dsf_file_close(struct dsf_file *f)
+{
+ if (f->mode == 1) {
+ munmap(f->data, f->size);
+ } else
+ return -EINVAL;
+
+ close(f->fd);
+ free(f);
+ return 0;
+}
diff --git a/src/tools/dsffile.h b/src/tools/dsffile.h
new file mode 100644
index 0000000..616dbb1
--- /dev/null
+++ b/src/tools/dsffile.h
@@ -0,0 +1,52 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+
+#include <spa/utils/defs.h>
+
+struct dsf_file;
+
+struct dsf_file_info {
+ uint32_t channel_type;
+ uint32_t channels;
+ uint32_t rate;
+ bool lsb;
+ uint64_t samples;
+ uint64_t length;
+ uint32_t blocksize;
+};
+
+struct dsf_layout {
+ int32_t interleave;
+ uint32_t channels;
+ bool lsb;
+};
+
+struct dsf_file * dsf_file_open(const char *filename, const char *mode, struct dsf_file_info *info);
+
+ssize_t dsf_file_read(struct dsf_file *f, void *data, size_t samples, const struct dsf_layout *layout);
+
+int dsf_file_close(struct dsf_file *f);
+
diff --git a/src/tools/meson.build b/src/tools/meson.build
new file mode 100644
index 0000000..6623e7a
--- /dev/null
+++ b/src/tools/meson.build
@@ -0,0 +1,88 @@
+tools_sources = [
+ [ 'pw-mon', [ 'pw-mon.c' ] ],
+ [ 'pw-dot', [ 'pw-dot.c' ] ],
+ [ 'pw-dump', [ 'pw-dump.c' ] ],
+ [ 'pw-profiler', [ 'pw-profiler.c' ] ],
+ [ 'pw-mididump', [ 'pw-mididump.c', 'midifile.c' ] ],
+ [ 'pw-metadata', [ 'pw-metadata.c' ] ],
+ [ 'pw-loopback', [ 'pw-loopback.c' ] ],
+ [ 'pw-link', [ 'pw-link.c' ] ],
+]
+
+foreach t : tools_sources
+ executable(t.get(0),
+ t.get(1),
+ install: true,
+ dependencies : [pipewire_dep, mathlib],
+ )
+endforeach
+
+executable('pw-cli',
+ 'pw-cli.c',
+ install: true,
+ dependencies: [pipewire_dep, readline_dep]
+)
+
+if ncurses_dep.found()
+ executable('pw-top',
+ 'pw-top.c',
+ install: true,
+ dependencies : [pipewire_dep, ncurses_dep],
+ )
+endif
+
+build_pw_cat = false
+build_pw_cat_with_ffmpeg = false
+pwcat_deps = [ sndfile_dep ]
+
+if get_option('pw-cat').allowed() and sndfile_dep.found()
+ build_pw_cat = true
+
+ if pw_cat_ffmpeg.allowed() and avcodec_dep.found() and avformat_dep.found()
+ pwcat_deps += avcodec_dep
+ pwcat_deps += avformat_dep
+ build_pw_cat_with_ffmpeg = true
+ endif
+
+ pwcat_sources = [
+ 'pw-cat.c',
+ 'midifile.c',
+ 'dsffile.c',
+ ]
+
+ pwcat_aliases = [
+ 'pw-play',
+ 'pw-record',
+ 'pw-midiplay',
+ 'pw-midirecord',
+ 'pw-dsdplay',
+ ]
+
+ executable('pw-cat',
+ pwcat_sources,
+ install: true,
+ dependencies : [pwcat_deps, pipewire_dep, mathlib],
+ )
+
+ foreach alias : pwcat_aliases
+ dst = pipewire_bindir / alias
+ cmd = 'ln -fs @0@ $DESTDIR@1@'.format('pw-cat', dst)
+ meson.add_install_script('sh', '-c', cmd)
+ endforeach
+elif not sndfile_dep.found() and get_option('pw-cat').enabled()
+ error('pw-cat is enabled but required dependency `sndfile` was not found.')
+endif
+summary({'Build pw-cat tool': build_pw_cat}, bool_yn: true, section: 'pw-cat/pw-play/pw-dump tool')
+if build_pw_cat
+ summary({'Build pw-cat with FFmpeg integration': build_pw_cat_with_ffmpeg}, bool_yn: true, section: 'pw-cat/pw-play/pw-dump tool')
+endif
+
+if dbus_dep.found()
+ executable('pw-reserve',
+ 'reserve.h',
+ 'reserve.c',
+ 'pw-reserve.c',
+ install: true,
+ dependencies : [dbus_dep, pipewire_dep],
+ )
+endif
diff --git a/src/tools/midifile.c b/src/tools/midifile.c
new file mode 100644
index 0000000..5276ade
--- /dev/null
+++ b/src/tools/midifile.c
@@ -0,0 +1,740 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <math.h>
+
+#include <spa/utils/string.h>
+
+#include "midifile.h"
+
+#define DEFAULT_TEMPO 500000 /* 500ms per quarter note (120 BPM) is the default */
+
+struct midi_track {
+ uint16_t id;
+
+ uint8_t *data;
+ uint32_t size;
+
+ 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;
+
+ struct midi_file_info info;
+ uint32_t length;
+ uint32_t tempo;
+
+ uint8_t *p;
+ int64_t tick;
+ double tick_sec;
+ double tick_start;
+
+ struct midi_track tracks[64];
+};
+
+static inline uint16_t parse_be16(const uint8_t *in)
+{
+ return (in[0] << 8) | in[1];
+}
+
+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)
+{
+ if (mf->p < mf->data + mf->size)
+ return mf->size + mf->data - mf->p;
+ return 0;
+}
+
+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;
+ tr->eof = true;
+ return 0;
+}
+
+static int read_mthd(struct midi_file *mf)
+{
+ if (mf_avail(mf) < 14 ||
+ memcmp(mf->p, "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->p += 14;
+ return 0;
+}
+
+static int read_mtrk(struct midi_file *mf, struct midi_track *track)
+{
+ if (mf_avail(mf) < 8 ||
+ memcmp(mf->p, "MTrk", 4) != 0)
+ return -EINVAL;
+
+ 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)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int parse_varlen(struct midi_file *mf, struct midi_track *tr, uint32_t *result)
+{
+ uint32_t value = 0;
+
+ while (tr_avail(tr) > 0) {
+ uint8_t b = *tr->p++;
+ value = (value << 7) | (b & 0x7f);
+ if ((b & 0x80) == 0)
+ break;
+ }
+ *result = value;
+ return 0;
+}
+
+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;
+ }
+
+ mf->p = mf->data;
+
+ if ((res = read_mthd(mf)) < 0)
+ goto exit_unmap;
+
+ 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;
+
+ if ((res = parse_varlen(mf, tr, &delta_time)) < 0)
+ goto exit_unmap;
+
+ tr->tick = delta_time;
+ tr->id = i;
+ }
+ mf->mode = 1;
+ *info = mf->info;
+ return 0;
+
+exit_unmap:
+ munmap(mf->data, mf->size);
+exit_close:
+ close(mf->fd);
+exit:
+ return res;
+}
+
+static inline int write_n(int fd, const void *buf, int count)
+{
+ return write(fd, buf, count) == (ssize_t)count ? count : -errno;
+}
+
+static inline int write_be16(int fd, uint16_t val)
+{
+ uint8_t buf[2] = { val >> 8, val };
+ return write_n(fd, buf, 2);
+}
+
+static inline int write_be32(int fd, uint32_t val)
+{
+ uint8_t buf[4] = { val >> 24, val >> 16, val >> 8, val };
+ return write_n(fd, buf, 4);
+}
+
+#define CHECK_RES(expr) if ((res = (expr)) < 0) return res
+
+static int write_headers(struct midi_file *mf)
+{
+ struct midi_track *tr = &mf->tracks[0];
+ int res;
+
+ lseek(mf->fd, 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->fd, "MTrk", 4));
+ CHECK_RES(write_be32(mf->fd, tr->size));
+
+ return 0;
+}
+
+static int open_write(struct midi_file *mf, const char *filename, struct midi_file_info *info)
+{
+ int res;
+
+ if (info->format != 0)
+ return -EINVAL;
+ if (info->ntracks == 0)
+ info->ntracks = 1;
+ else if (info->ntracks != 1)
+ return -EINVAL;
+ if (info->division == 0)
+ info->division = 96;
+
+ if ((mf->fd = open(filename, O_WRONLY | O_CREAT, 0660)) < 0) {
+ res = -errno;
+ goto exit;
+ }
+ mf->mode = 2;
+ mf->tempo = DEFAULT_TEMPO;
+ mf->info = *info;
+
+ res = write_headers(mf);
+exit:
+ return res;
+}
+
+struct midi_file *
+midi_file_open(const char *filename, const char *mode, struct midi_file_info *info)
+{
+ int res;
+ struct midi_file *mf;
+
+ mf = calloc(1, sizeof(struct midi_file));
+ if (mf == NULL)
+ return NULL;
+
+ if (spa_streq(mode, "r")) {
+ if ((res = open_read(mf, filename, info)) < 0)
+ goto exit_free;
+ } else if (spa_streq(mode, "w")) {
+ if ((res = open_write(mf, filename, info)) < 0)
+ goto exit_free;
+ } else {
+ res = -EINVAL;
+ goto exit_free;
+ }
+ return mf;
+
+exit_free:
+ free(mf);
+ errno = -res;
+ return NULL;
+}
+
+int midi_file_close(struct midi_file *mf)
+{
+ int res;
+
+ if (mf->mode == 1) {
+ munmap(mf->data, mf->size);
+ } else if (mf->mode == 2) {
+ uint8_t buf[4] = { 0x00, 0xff, 0x2f, 0x00 };
+ CHECK_RES(write_n(mf->fd, buf, 4));
+ mf->tracks[0].size += 4;
+ CHECK_RES(write_headers(mf));
+ } else
+ return -EINVAL;
+
+ close(mf->fd);
+ free(mf);
+ return 0;
+}
+
+static int peek_next(struct midi_file *mf, struct midi_event *ev)
+{
+ struct midi_track *tr, *found = NULL;
+ uint16_t i;
+
+ for (i = 0; i < mf->info.ntracks; i++) {
+ tr = &mf->tracks[i];
+ if (tr_avail(tr) == 0)
+ continue;
+ if (found == NULL || tr->tick < found->tick)
+ found = tr;
+ }
+ if (found == NULL)
+ return 0;
+
+ ev->track = found->id;
+ ev->sec = mf->tick_sec + ((found->tick - mf->tick_start) * (double)mf->tempo) / (1000000.0 * mf->info.division);
+ return 1;
+}
+
+int midi_file_next_time(struct midi_file *mf, double *sec)
+{
+ struct midi_event ev;
+ int res;
+
+ if ((res = peek_next(mf, &ev)) <= 0)
+ return res;
+
+ *sec = ev.sec;
+ return 1;
+}
+
+int midi_file_read_event(struct midi_file *mf, struct midi_event *event)
+{
+ struct midi_track *tr;
+ uint32_t delta_time, size;
+ uint8_t status, meta;
+ int res, running;
+
+ if ((res = peek_next(mf, event)) <= 0)
+ return res;
+
+ tr = &mf->tracks[event->track];
+ status = *tr->p;
+
+ running = (status & 0x80) == 0;
+ if (running) {
+ status = tr->event[0];
+ event->data = tr->event;
+ } else {
+ event->data = tr->p++;
+ tr->event[0] = status;
+ }
+
+ switch (status) {
+ case 0xc0 ... 0xdf:
+ size = 2;
+ break;
+
+ case 0x80 ... 0xbf:
+ case 0xe0 ... 0xef:
+ size = 3;
+ break;
+
+ case 0xff:
+ meta = *tr->p++;
+
+ if ((res = parse_varlen(mf, tr, &size)) < 0)
+ return res;
+
+ event->meta.offset = tr->p - event->data;
+ event->meta.size = size;
+
+ 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];
+ break;
+ }
+ size += tr->p - event->data;
+ break;
+
+ case 0xf0:
+ case 0xf7:
+ if ((res = parse_varlen(mf, tr, &size)) < 0)
+ return res;
+ size += tr->p - event->data;
+ 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 ((res = parse_varlen(mf, tr, &delta_time)) < 0)
+ return res;
+
+ tr->tick += delta_time;
+ return 1;
+}
+
+static int write_varlen(struct midi_file *mf, struct midi_track *tr, uint32_t value)
+{
+ uint64_t buffer;
+ uint8_t b;
+ int res;
+
+ buffer = value & 0x7f;
+ while ((value >>= 7)) {
+ buffer <<= 8;
+ buffer |= ((value & 0x7f) | 0x80);
+ }
+ do {
+ b = buffer & 0xff;
+ CHECK_RES(write_n(mf->fd, &b, 1));
+ tr->size++;
+ buffer >>= 8;
+ } while (b & 0x80);
+
+ return 0;
+}
+
+int midi_file_write_event(struct midi_file *mf, const struct midi_event *event)
+{
+ struct midi_track *tr;
+ uint32_t tick;
+ int res;
+
+ 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);
+
+ tr = &mf->tracks[event->track];
+
+ tick = event->sec * (1000000.0 * mf->info.division) / (double)mf->tempo;
+
+ 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;
+
+ return 0;
+}
+
+static const char * const event_names[] = {
+ "Text", "Copyright", "Sequence/Track Name",
+ "Instrument", "Lyric", "Marker", "Cue Point",
+ "Program Name", "Device (Port) Name"
+};
+
+static const char * const note_names[] = {
+ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
+};
+
+static const char * const controller_names[128] = {
+ [0] = "Bank Select (coarse)",
+ [1] = "Modulation Wheel (coarse)",
+ [2] = "Breath controller (coarse)",
+ [4] = "Foot Pedal (coarse)",
+ [5] = "Portamento Time (coarse)",
+ [6] = "Data Entry (coarse)",
+ [7] = "Volume (coarse)",
+ [8] = "Balance (coarse)",
+ [10] = "Pan position (coarse)",
+ [11] = "Expression (coarse)",
+ [12] = "Effect Control 1 (coarse)",
+ [13] = "Effect Control 2 (coarse)",
+ [16] = "General Purpose Slider 1",
+ [17] = "General Purpose Slider 2",
+ [18] = "General Purpose Slider 3",
+ [19] = "General Purpose Slider 4",
+ [32] = "Bank Select (fine)",
+ [33] = "Modulation Wheel (fine)",
+ [34] = "Breath (fine)",
+ [36] = "Foot Pedal (fine)",
+ [37] = "Portamento Time (fine)",
+ [38] = "Data Entry (fine)",
+ [39] = "Volume (fine)",
+ [40] = "Balance (fine)",
+ [42] = "Pan position (fine)",
+ [43] = "Expression (fine)",
+ [44] = "Effect Control 1 (fine)",
+ [45] = "Effect Control 2 (fine)",
+ [64] = "Hold Pedal (on/off)",
+ [65] = "Portamento (on/off)",
+ [66] = "Sustenuto Pedal (on/off)",
+ [67] = "Soft Pedal (on/off)",
+ [68] = "Legato Pedal (on/off)",
+ [69] = "Hold 2 Pedal (on/off)",
+ [70] = "Sound Variation",
+ [71] = "Sound Timbre",
+ [72] = "Sound Release Time",
+ [73] = "Sound Attack Time",
+ [74] = "Sound Brightness",
+ [75] = "Sound Control 6",
+ [76] = "Sound Control 7",
+ [77] = "Sound Control 8",
+ [78] = "Sound Control 9",
+ [79] = "Sound Control 10",
+ [80] = "General Purpose Button 1 (on/off)",
+ [81] = "General Purpose Button 2 (on/off)",
+ [82] = "General Purpose Button 3 (on/off)",
+ [83] = "General Purpose Button 4 (on/off)",
+ [91] = "Effects Level",
+ [92] = "Tremulo Level",
+ [93] = "Chorus Level",
+ [94] = "Celeste Level",
+ [95] = "Phaser Level",
+ [96] = "Data Button increment",
+ [97] = "Data Button decrement",
+ [98] = "Non-registered Parameter (fine)",
+ [99] = "Non-registered Parameter (coarse)",
+ [100] = "Registered Parameter (fine)",
+ [101] = "Registered Parameter (coarse)",
+ [120] = "All Sound Off",
+ [121] = "All Controllers Off",
+ [122] = "Local Keyboard (on/off)",
+ [123] = "All Notes Off",
+ [124] = "Omni Mode Off",
+ [125] = "Omni Mode On",
+ [126] = "Mono Operation",
+ [127] = "Poly Operation",
+};
+
+static const char * const program_names[] = {
+ "Acoustic Grand", "Bright Acoustic", "Electric Grand", "Honky-Tonk",
+ "Electric Piano 1", "Electric Piano 2", "Harpsichord", "Clavinet",
+ "Celesta", "Glockenspiel", "Music Box", "Vibraphone", "Marimba",
+ "Xylophone", "Tubular Bells", "Dulcimer", "Drawbar Organ", "Percussive Organ",
+ "Rock Organ", "Church Organ", "Reed Organ", "Accoridan", "Harmonica",
+ "Tango Accordion", "Nylon String Guitar", "Steel String Guitar",
+ "Electric Jazz Guitar", "Electric Clean Guitar", "Electric Muted Guitar",
+ "Overdriven Guitar", "Distortion Guitar", "Guitar Harmonics",
+ "Acoustic Bass", "Electric Bass (fingered)", "Electric Bass (picked)",
+ "Fretless Bass", "Slap Bass 1", "Slap Bass 2", "Synth Bass 1", "Synth Bass 2",
+ "Violin", "Viola", "Cello", "Contrabass", "Tremolo Strings", "Pizzicato Strings",
+ "Orchestral Strings", "Timpani", "String Ensemble 1", "String Ensemble 2",
+ "SynthStrings 1", "SynthStrings 2", "Choir Aahs", "Voice Oohs", "Synth Voice",
+ "Orchestra Hit", "Trumpet", "Trombone", "Tuba", "Muted Trumpet", "French Horn",
+ "Brass Section", "SynthBrass 1", "SynthBrass 2", "Soprano Sax", "Alto Sax",
+ "Tenor Sax", "Baritone Sax", "Oboe", "English Horn", "Bassoon", "Clarinet",
+ "Piccolo", "Flute", "Recorder", "Pan Flute", "Blown Bottle", "Skakuhachi",
+ "Whistle", "Ocarina", "Lead 1 (square)", "Lead 2 (sawtooth)", "Lead 3 (calliope)",
+ "Lead 4 (chiff)", "Lead 5 (charang)", "Lead 6 (voice)", "Lead 7 (fifths)",
+ "Lead 8 (bass+lead)", "Pad 1 (new age)", "Pad 2 (warm)", "Pad 3 (polysynth)",
+ "Pad 4 (choir)", "Pad 5 (bowed)", "Pad 6 (metallic)", "Pad 7 (halo)",
+ "Pad 8 (sweep)", "FX 1 (rain)", "FX 2 (soundtrack)", "FX 3 (crystal)",
+ "FX 4 (atmosphere)", "FX 5 (brightness)", "FX 6 (goblins)", "FX 7 (echoes)",
+ "FX 8 (sci-fi)", "Sitar", "Banjo", "Shamisen", "Koto", "Kalimba", "Bagpipe",
+ "Fiddle", "Shanai", "Tinkle Bell", "Agogo", "Steel Drums", "Woodblock",
+ "Taiko Drum", "Melodic Tom", "Synth Drum", "Reverse Cymbal", "Guitar Fret Noise",
+ "Breath Noise", "Seashore", "Bird Tweet", "Telephone Ring", "Helicopter",
+ "Applause", "Gunshot"
+};
+
+static const char * const smpte_rates[] = {
+ "24 fps",
+ "25 fps",
+ "30 fps (drop frame)",
+ "30 fps (non drop frame)"
+};
+
+static const char * const major_keys[] = {
+ "Unknown major", "Fb", "Cb", "Gb", "Db", "Ab", "Eb", "Bb", "F",
+ "C", "G", "D", "A", "E", "B", "F#", "C#", "G#", "Unknown major"
+};
+
+static const char * const minor_keys[] = {
+ "Unknown minor", "Dbm", "Abm", "Ebm", "Bbm", "Fm", "Cm", "Gm", "Dm",
+ "Am", "Em", "Bm", "F#m", "C#m", "G#m", "D#m", "A#m", "E#m", "Unknown minor"
+};
+
+static const char *controller_name(uint8_t ctrl)
+{
+ if (ctrl > 127 ||
+ controller_names[ctrl] == NULL)
+ return "Unknown";
+ return controller_names[ctrl];
+}
+
+static void dump_mem(FILE *out, const char *label, uint8_t *data, uint32_t size)
+{
+ fprintf(out, "%s: ", label);
+ while (size--)
+ fprintf(out, "%02x ", *data++);
+}
+
+int midi_file_dump_event(FILE *out, const struct midi_event *ev)
+{
+ fprintf(out, "track:%2d sec:%f ", ev->track, ev->sec);
+
+ switch (ev->data[0]) {
+ case 0x80 ... 0x8f:
+ fprintf(out, "Note Off (channel %2d): note %3s%d, velocity %3d",
+ (ev->data[0] & 0x0f) + 1,
+ note_names[ev->data[1] % 12], ev->data[1] / 12 -1,
+ ev->data[2]);
+ break;
+ case 0x90 ... 0x9f:
+ fprintf(out, "Note On (channel %2d): note %3s%d, velocity %3d",
+ (ev->data[0] & 0x0f) + 1,
+ note_names[ev->data[1] % 12], ev->data[1] / 12 -1,
+ ev->data[2]);
+ break;
+ case 0xa0 ... 0xaf:
+ fprintf(out, "Aftertouch (channel %2d): note %3s%d, pressure %3d",
+ (ev->data[0] & 0x0f) + 1,
+ note_names[ev->data[1] % 12], ev->data[1] / 12 -1,
+ ev->data[2]);
+ break;
+ case 0xb0 ... 0xbf:
+ fprintf(out, "Controller (channel %2d): controller %3d (%s), value %3d",
+ (ev->data[0] & 0x0f) + 1, ev->data[1],
+ controller_name(ev->data[1]), ev->data[2]);
+ break;
+ case 0xc0 ... 0xcf:
+ fprintf(out, "Program (channel %2d): program %3d (%s)",
+ (ev->data[0] & 0x0f) + 1, ev->data[1],
+ program_names[ev->data[1]]);
+ break;
+ case 0xd0 ... 0xdf:
+ fprintf(out, "Channel Pressure (channel %2d): pressure %3d",
+ (ev->data[0] & 0x0f) + 1, ev->data[1]);
+ break;
+ case 0xe0 ... 0xef:
+ fprintf(out, "Pitch Bend (channel %2d): value %d", (ev->data[0] & 0x0f) + 1,
+ ((int)ev->data[2] << 7 | ev->data[1]) - 0x2000);
+ break;
+ case 0xf0:
+ case 0xf7:
+ dump_mem(out, "SysEx", ev->data, ev->size);
+ break;
+ case 0xf1:
+ fprintf(out, "MIDI Time Code Quarter Frame: type %d values %d",
+ ev->data[0] >> 4, ev->data[0] & 0xf);
+ break;
+ case 0xf2:
+ fprintf(out, "Song Position Pointer: value %d",
+ ((int)ev->data[1] << 7 | ev->data[0]));
+ break;
+ case 0xf3:
+ fprintf(out, "Song Select: value %d", (ev->data[0] & 0x7f));
+ break;
+ case 0xf6:
+ fprintf(out, "Tune Request");
+ break;
+ case 0xf8:
+ fprintf(out, "Timing Clock");
+ break;
+ case 0xfa:
+ fprintf(out, "Start Sequence");
+ break;
+ case 0xfb:
+ fprintf(out, "Continue Sequence");
+ break;
+ case 0xfc:
+ fprintf(out, "Stop Sequence");
+ break;
+ case 0xfe:
+ fprintf(out, "Active Sensing");
+ break;
+ case 0xff:
+ fprintf(out, "Meta: ");
+ switch (ev->data[1]) {
+ case 0x00:
+ fprintf(out, "Sequence Number %3d %3d", ev->data[3], ev->data[4]);
+ break;
+ case 0x01 ... 0x09:
+ fprintf(out, "%s: %s", event_names[ev->data[1] - 1], &ev->data[ev->meta.offset]);
+ break;
+ case 0x20:
+ fprintf(out, "Channel Prefix: %03d", ev->data[3]);
+ break;
+ case 0x21:
+ fprintf(out, "Midi Port: %03d", ev->data[3]);
+ break;
+ case 0x2f:
+ fprintf(out, "End Of Track");
+ break;
+ case 0x51:
+ fprintf(out, "Tempo: %d microseconds per quarter note, %.2f BPM",
+ ev->meta.parsed.tempo.uspqn,
+ 60000000.0 / (double)ev->meta.parsed.tempo.uspqn);
+ 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]);
+ 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]);
+ break;
+ case 0x59:
+ {
+ int sf = ev->data[3];
+ fprintf(out, "Key Signature: %d %s: %s", abs(sf),
+ sf > 0 ? "sharps" : "flats",
+ ev->data[4] == 0 ?
+ major_keys[SPA_CLAMP(sf + 9, 0, 18)] :
+ minor_keys[SPA_CLAMP(sf + 9, 0, 18)]);
+ break;
+ }
+ case 0x7f:
+ dump_mem(out, "Sequencer", ev->data, ev->size);
+ break;
+ default:
+ dump_mem(out, "Invalid", ev->data, ev->size);
+ }
+ break;
+ default:
+ dump_mem(out, "Unknown", ev->data, ev->size);
+ break;
+ }
+ fprintf(out, "\n");
+ return 0;
+}
diff --git a/src/tools/midifile.h b/src/tools/midifile.h
new file mode 100644
index 0000000..6c69df4
--- /dev/null
+++ b/src/tools/midifile.h
@@ -0,0 +1,64 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+
+#include <spa/utils/defs.h>
+
+struct midi_file;
+
+struct midi_event {
+ uint32_t track;
+ double sec;
+ uint8_t *data;
+ uint32_t size;
+ struct {
+ uint32_t offset;
+ uint32_t size;
+ union {
+ struct {
+ uint32_t uspqn; /* microseconds per quarter note */
+ } tempo;
+ } parsed;
+ } meta;
+};
+
+struct midi_file_info {
+ uint16_t format;
+ uint16_t ntracks;
+ uint16_t division;
+};
+
+struct midi_file *
+midi_file_open(const char *filename, const char *mode, struct midi_file_info *info);
+
+int midi_file_close(struct midi_file *mf);
+
+int midi_file_next_time(struct midi_file *mf, double *sec);
+
+int midi_file_read_event(struct midi_file *mf, struct midi_event *event);
+
+int midi_file_write_event(struct midi_file *mf, const struct midi_event *event);
+
+int midi_file_dump_event(FILE *out, const struct midi_event *event);
diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c
new file mode 100644
index 0000000..068fac1
--- /dev/null
+++ b/src/tools/pw-cat.c
@@ -0,0 +1,1971 @@
+/* PipeWire - pw-cat
+ *
+ * Copyright © 2020 Konsulko Group
+
+ * Author: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <time.h>
+#include <math.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <assert.h>
+#include <ctype.h>
+#include <locale.h>
+
+#include <sndfile.h>
+
+#include <spa/param/audio/layout.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/audio/type-info.h>
+#include <spa/param/props.h>
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/utils/json.h>
+#include <spa/debug/types.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/i18n.h>
+#include <pipewire/extensions/metadata.h>
+
+#include "config.h"
+
+#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
+#include <libavformat/avformat.h>
+#include <libavcodec/avcodec.h>
+#endif
+
+#include "midifile.h"
+#include "dsffile.h"
+
+#define DEFAULT_MEDIA_TYPE "Audio"
+#define DEFAULT_MIDI_MEDIA_TYPE "Midi"
+#define DEFAULT_MEDIA_CATEGORY_PLAYBACK "Playback"
+#define DEFAULT_MEDIA_CATEGORY_RECORD "Capture"
+#define DEFAULT_MEDIA_ROLE "Music"
+#define DEFAULT_TARGET "auto"
+#define DEFAULT_LATENCY_PLAY "100ms"
+#define DEFAULT_LATENCY_REC "none"
+#define DEFAULT_RATE 48000
+#define DEFAULT_CHANNELS 2
+#define DEFAULT_FORMAT "s16"
+#define DEFAULT_VOLUME 1.0
+#define DEFAULT_QUALITY 4
+
+enum mode {
+ mode_none,
+ mode_playback,
+ mode_record
+};
+
+enum unit {
+ unit_none,
+ unit_samples,
+ unit_sec,
+ unit_msec,
+ unit_usec,
+ unit_nsec,
+};
+
+struct data;
+
+typedef int (*fill_fn)(struct data *d, void *dest, unsigned int n_frames);
+
+struct channelmap {
+ int n_channels;
+ int channels[SPA_AUDIO_MAX_CHANNELS];
+};
+
+struct data {
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ struct pw_stream *stream;
+ struct spa_hook stream_listener;
+
+ struct spa_source *timer;
+
+ enum mode mode;
+ bool verbose;
+#define TYPE_PCM 0
+#define TYPE_MIDI 1
+#define TYPE_DSD 2
+#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
+#define TYPE_ENCODED 3
+#endif
+ int data_type;
+ const char *remote_name;
+ const char *media_type;
+ const char *media_category;
+ const char *media_role;
+ const char *channel_map;
+ const char *format;
+ const char *target;
+ const char *latency;
+ struct pw_properties *props;
+
+ const char *filename;
+ SNDFILE *file;
+
+ unsigned int bitrate;
+ unsigned int rate;
+ int channels;
+ struct channelmap channelmap;
+ unsigned int stride;
+ enum unit latency_unit;
+ unsigned int latency_value;
+ int quality;
+
+ enum spa_audio_format spa_format;
+
+ float volume;
+ bool volume_is_set;
+
+ fill_fn fill;
+
+ struct spa_io_position *position;
+ bool drained;
+ uint64_t clock_time;
+
+ struct {
+ struct midi_file *file;
+ struct midi_file_info info;
+ } midi;
+ struct {
+ struct dsf_file *file;
+ struct dsf_file_info info;
+ struct dsf_layout layout;
+ } dsf;
+
+#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
+ FILE *encoded_file;
+ AVFormatContext *fmt_context;
+ AVStream *astream;
+ AVCodecContext *ctx;
+ enum AVSampleFormat sfmt;
+#endif
+};
+
+#define STR_FMTS "(ulaw|alaw|u8|s8|s16|s32|f32|f64)"
+
+static const struct format_info {
+ const char *name;
+ int sf_format;
+ uint32_t spa_format;
+ uint32_t width;
+} format_info[] = {
+ { "ulaw", SF_FORMAT_ULAW, SPA_AUDIO_FORMAT_ULAW, 1 },
+ { "alaw", SF_FORMAT_ULAW, SPA_AUDIO_FORMAT_ALAW, 1 },
+ { "s8", SF_FORMAT_PCM_S8, SPA_AUDIO_FORMAT_S8, 1 },
+ { "u8", SF_FORMAT_PCM_U8, SPA_AUDIO_FORMAT_U8, 1 },
+ { "s16", SF_FORMAT_PCM_16, SPA_AUDIO_FORMAT_S16, 2 },
+ { "s24", SF_FORMAT_PCM_24, SPA_AUDIO_FORMAT_S24, 3 },
+ { "s32", SF_FORMAT_PCM_32, SPA_AUDIO_FORMAT_S32, 4 },
+ { "f32", SF_FORMAT_FLOAT, SPA_AUDIO_FORMAT_F32, 4 },
+ { "f64", SF_FORMAT_DOUBLE, SPA_AUDIO_FORMAT_F32, 8 },
+};
+
+static const struct format_info *format_info_by_name(const char *str)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(format_info, i)
+ if (spa_streq(str, i->name))
+ return i;
+ return NULL;
+}
+
+static const struct format_info *format_info_by_sf_format(int format)
+{
+ int sub_type = (format & SF_FORMAT_SUBMASK);
+ SPA_FOR_EACH_ELEMENT_VAR(format_info, i)
+ if (i->sf_format == sub_type)
+ return i;
+ return NULL;
+}
+
+static int sf_playback_fill_x8(struct data *d, void *dest, unsigned int n_frames)
+{
+ sf_count_t rn;
+
+ rn = sf_read_raw(d->file, dest, n_frames * d->stride);
+ return (int)rn / d->stride;
+}
+
+static int sf_playback_fill_s16(struct data *d, void *dest, unsigned int n_frames)
+{
+ sf_count_t rn;
+
+ assert(sizeof(short) == sizeof(int16_t));
+ rn = sf_readf_short(d->file, dest, n_frames);
+ return (int)rn;
+}
+
+static int sf_playback_fill_s32(struct data *d, void *dest, unsigned int n_frames)
+{
+ sf_count_t rn;
+
+ assert(sizeof(int) == sizeof(int32_t));
+ rn = sf_readf_int(d->file, dest, n_frames);
+ return (int)rn;
+}
+
+static int sf_playback_fill_f32(struct data *d, void *dest, unsigned int n_frames)
+{
+ sf_count_t rn;
+
+ assert(sizeof(float) == 4);
+ rn = sf_readf_float(d->file, dest, n_frames);
+ return (int)rn;
+}
+
+static int sf_playback_fill_f64(struct data *d, void *dest, unsigned int n_frames)
+{
+ sf_count_t rn;
+
+ assert(sizeof(double) == 8);
+ rn = sf_readf_double(d->file, dest, n_frames);
+ return (int)rn;
+}
+
+#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
+static int encoded_playback_fill(struct data *d, void *dest, unsigned int n_frames)
+{
+ int ret, size = 0;
+ uint8_t buffer[16384] = { 0 };
+
+ ret = fread(buffer, 1, 16384, d->encoded_file);
+ if (ret > 0) {
+ memcpy(dest, buffer, ret);
+ size = ret;
+ }
+
+ return (int)size;
+}
+
+static int avcodec_ctx_to_info(struct data *data, AVCodecContext *ctx, struct spa_audio_info *info)
+{
+ int32_t profile;
+
+ switch (ctx->codec_id) {
+ case AV_CODEC_ID_VORBIS:
+ info->media_subtype = SPA_MEDIA_SUBTYPE_vorbis;
+ info->info.vorbis.rate = data->rate;
+ info->info.vorbis.channels = data->channels;
+ break;
+ case AV_CODEC_ID_MP3:
+ info->media_subtype = SPA_MEDIA_SUBTYPE_mp3;
+ info->info.mp3.rate = data->rate;
+ info->info.mp3.channels = data->channels;
+ break;
+ case AV_CODEC_ID_AAC:
+ info->media_subtype = SPA_MEDIA_SUBTYPE_aac;
+ info->info.aac.rate = data->rate;
+ info->info.aac.channels = data->channels;
+ info->info.aac.bitrate = data->bitrate;
+ info->info.aac.stream_format = SPA_AUDIO_AAC_STREAM_FORMAT_RAW;
+ break;
+ case AV_CODEC_ID_WMAV1:
+ case AV_CODEC_ID_WMAV2:
+ case AV_CODEC_ID_WMAPRO:
+ case AV_CODEC_ID_WMAVOICE:
+ case AV_CODEC_ID_WMALOSSLESS:
+ info->media_subtype = SPA_MEDIA_SUBTYPE_wma;
+ switch (ctx->codec_tag) {
+ /* TODO see if these hex constants can be replaced by named constants from FFmpeg */
+ case 0x161:
+ profile = SPA_AUDIO_WMA_PROFILE_WMA9;
+ break;
+ case 0x162:
+ profile = SPA_AUDIO_WMA_PROFILE_WMA9_PRO;
+ break;
+ case 0x163:
+ profile = SPA_AUDIO_WMA_PROFILE_WMA9_LOSSLESS;
+ break;
+ case 0x166:
+ profile = SPA_AUDIO_WMA_PROFILE_WMA10;
+ break;
+ case 0x167:
+ profile = SPA_AUDIO_WMA_PROFILE_WMA10_LOSSLESS;
+ break;
+ default:
+ fprintf(stderr, "error: invalid WMA profile\n");
+ return -EINVAL;
+ }
+ info->info.wma.rate = data->rate;
+ info->info.wma.channels = data->channels;
+ info->info.wma.bitrate = data->bitrate;
+ info->info.wma.block_align = ctx->block_align;
+ info->info.wma.profile = profile;
+ break;
+ case AV_CODEC_ID_FLAC:
+ info->media_subtype = SPA_MEDIA_SUBTYPE_flac;
+ info->info.flac.rate = data->rate;
+ info->info.flac.channels = data->channels;
+ break;
+ case AV_CODEC_ID_ALAC:
+ info->media_subtype = SPA_MEDIA_SUBTYPE_alac;
+ info->info.alac.rate = data->rate;
+ info->info.alac.channels = data->channels;
+ break;
+ case AV_CODEC_ID_APE:
+ info->media_subtype = SPA_MEDIA_SUBTYPE_ape;
+ info->info.ape.rate = data->rate;
+ info->info.ape.channels = data->channels;
+ break;
+ case AV_CODEC_ID_RA_144:
+ case AV_CODEC_ID_RA_288:
+ info->media_subtype = SPA_MEDIA_SUBTYPE_ra;
+ info->info.ra.rate = data->rate;
+ info->info.ra.channels = data->channels;
+ break;
+ case AV_CODEC_ID_AMR_NB:
+ info->media_subtype = SPA_MEDIA_SUBTYPE_amr;
+ info->info.amr.rate = data->rate;
+ info->info.amr.channels = data->channels;
+ info->info.amr.band_mode = SPA_AUDIO_AMR_BAND_MODE_NB;
+ break;
+ case AV_CODEC_ID_AMR_WB:
+ info->media_subtype = SPA_MEDIA_SUBTYPE_amr;
+ info->info.amr.rate = data->rate;
+ info->info.amr.channels = data->channels;
+ info->info.amr.band_mode = SPA_AUDIO_AMR_BAND_MODE_WB;
+ break;
+ default:
+ fprintf(stderr, "Unsupported encoded media subtype\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+#endif
+
+static inline fill_fn
+playback_fill_fn(uint32_t fmt)
+{
+ switch (fmt) {
+ case SPA_AUDIO_FORMAT_S8:
+ case SPA_AUDIO_FORMAT_U8:
+ case SPA_AUDIO_FORMAT_ULAW:
+ case SPA_AUDIO_FORMAT_ALAW:
+ return sf_playback_fill_x8;
+ case SPA_AUDIO_FORMAT_S16_LE:
+ case SPA_AUDIO_FORMAT_S16_BE:
+ /* sndfile check */
+ if (sizeof(int16_t) != sizeof(short))
+ return NULL;
+ return sf_playback_fill_s16;
+ case SPA_AUDIO_FORMAT_S32_LE:
+ case SPA_AUDIO_FORMAT_S32_BE:
+ /* sndfile check */
+ if (sizeof(int32_t) != sizeof(int))
+ return NULL;
+ return sf_playback_fill_s32;
+ case SPA_AUDIO_FORMAT_F32_LE:
+ case SPA_AUDIO_FORMAT_F32_BE:
+ /* sndfile check */
+ if (sizeof(float) != 4)
+ return NULL;
+ return sf_playback_fill_f32;
+ case SPA_AUDIO_FORMAT_F64_LE:
+ case SPA_AUDIO_FORMAT_F64_BE:
+ if (sizeof(double) != 8)
+ return NULL;
+ return sf_playback_fill_f64;
+#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
+ case SPA_AUDIO_FORMAT_ENCODED:
+ return encoded_playback_fill;
+#endif
+ default:
+ break;
+ }
+ return NULL;
+}
+
+static int sf_record_fill_x8(struct data *d, void *src, unsigned int n_frames)
+{
+ sf_count_t rn;
+
+ rn = sf_write_raw(d->file, src, n_frames * d->stride);
+ return (int)rn / d->stride;
+}
+
+static int sf_record_fill_s16(struct data *d, void *src, unsigned int n_frames)
+{
+ sf_count_t rn;
+
+ assert(sizeof(short) == sizeof(int16_t));
+ rn = sf_writef_short(d->file, src, n_frames);
+ return (int)rn;
+}
+
+static int sf_record_fill_s32(struct data *d, void *src, unsigned int n_frames)
+{
+ sf_count_t rn;
+
+ assert(sizeof(int) == sizeof(int32_t));
+ rn = sf_writef_int(d->file, src, n_frames);
+ return (int)rn;
+}
+
+static int sf_record_fill_f32(struct data *d, void *src, unsigned int n_frames)
+{
+ sf_count_t rn;
+
+ assert(sizeof(float) == 4);
+ rn = sf_writef_float(d->file, src, n_frames);
+ return (int)rn;
+}
+
+static int sf_record_fill_f64(struct data *d, void *src, unsigned int n_frames)
+{
+ sf_count_t rn;
+
+ assert(sizeof(double) == 8);
+ rn = sf_writef_double(d->file, src, n_frames);
+ return (int)rn;
+}
+
+static inline fill_fn
+record_fill_fn(uint32_t fmt)
+{
+ switch (fmt) {
+ case SPA_AUDIO_FORMAT_S8:
+ case SPA_AUDIO_FORMAT_U8:
+ case SPA_AUDIO_FORMAT_ULAW:
+ case SPA_AUDIO_FORMAT_ALAW:
+ return sf_record_fill_x8;
+ case SPA_AUDIO_FORMAT_S16_LE:
+ case SPA_AUDIO_FORMAT_S16_BE:
+ /* sndfile check */
+ if (sizeof(int16_t) != sizeof(short))
+ return NULL;
+ return sf_record_fill_s16;
+ case SPA_AUDIO_FORMAT_S32_LE:
+ case SPA_AUDIO_FORMAT_S32_BE:
+ /* sndfile check */
+ if (sizeof(int32_t) != sizeof(int))
+ return NULL;
+ return sf_record_fill_s32;
+ case SPA_AUDIO_FORMAT_F32_LE:
+ case SPA_AUDIO_FORMAT_F32_BE:
+ /* sndfile check */
+ if (sizeof(float) != 4)
+ return NULL;
+ return sf_record_fill_f32;
+ case SPA_AUDIO_FORMAT_F64_LE:
+ case SPA_AUDIO_FORMAT_F64_BE:
+ /* sndfile check */
+ if (sizeof(double) != 8)
+ return NULL;
+ return sf_record_fill_f64;
+ default:
+ break;
+ }
+ return NULL;
+}
+
+static int channelmap_from_sf(struct channelmap *map)
+{
+ static const enum spa_audio_channel table[] = {
+ [SF_CHANNEL_MAP_MONO] = SPA_AUDIO_CHANNEL_MONO,
+ [SF_CHANNEL_MAP_LEFT] = SPA_AUDIO_CHANNEL_FL, /* libsndfile distinguishes left and front-left, which we don't */
+ [SF_CHANNEL_MAP_RIGHT] = SPA_AUDIO_CHANNEL_FR,
+ [SF_CHANNEL_MAP_CENTER] = SPA_AUDIO_CHANNEL_FC,
+ [SF_CHANNEL_MAP_FRONT_LEFT] = SPA_AUDIO_CHANNEL_FL,
+ [SF_CHANNEL_MAP_FRONT_RIGHT] = SPA_AUDIO_CHANNEL_FR,
+ [SF_CHANNEL_MAP_FRONT_CENTER] = SPA_AUDIO_CHANNEL_FC,
+ [SF_CHANNEL_MAP_REAR_CENTER] = SPA_AUDIO_CHANNEL_RC,
+ [SF_CHANNEL_MAP_REAR_LEFT] = SPA_AUDIO_CHANNEL_RL,
+ [SF_CHANNEL_MAP_REAR_RIGHT] = SPA_AUDIO_CHANNEL_RR,
+ [SF_CHANNEL_MAP_LFE] = SPA_AUDIO_CHANNEL_LFE,
+ [SF_CHANNEL_MAP_FRONT_LEFT_OF_CENTER] = SPA_AUDIO_CHANNEL_FLC,
+ [SF_CHANNEL_MAP_FRONT_RIGHT_OF_CENTER] = SPA_AUDIO_CHANNEL_FRC,
+ [SF_CHANNEL_MAP_SIDE_LEFT] = SPA_AUDIO_CHANNEL_SL,
+ [SF_CHANNEL_MAP_SIDE_RIGHT] = SPA_AUDIO_CHANNEL_SR,
+ [SF_CHANNEL_MAP_TOP_CENTER] = SPA_AUDIO_CHANNEL_TC,
+ [SF_CHANNEL_MAP_TOP_FRONT_LEFT] = SPA_AUDIO_CHANNEL_TFL,
+ [SF_CHANNEL_MAP_TOP_FRONT_RIGHT] = SPA_AUDIO_CHANNEL_TFR,
+ [SF_CHANNEL_MAP_TOP_FRONT_CENTER] = SPA_AUDIO_CHANNEL_TFC,
+ [SF_CHANNEL_MAP_TOP_REAR_LEFT] = SPA_AUDIO_CHANNEL_TRL,
+ [SF_CHANNEL_MAP_TOP_REAR_RIGHT] = SPA_AUDIO_CHANNEL_TRR,
+ [SF_CHANNEL_MAP_TOP_REAR_CENTER] = SPA_AUDIO_CHANNEL_TRC
+ };
+ int i;
+
+ for (i = 0; i < map->n_channels; i++) {
+ if (map->channels[i] >= 0 && map->channels[i] < (int) SPA_N_ELEMENTS(table))
+ map->channels[i] = table[map->channels[i]];
+ else
+ map->channels[i] = SPA_AUDIO_CHANNEL_UNKNOWN;
+ }
+ return 0;
+}
+struct mapping {
+ const char *name;
+ unsigned int channels;
+ unsigned int values[32];
+};
+
+static const struct mapping maps[] =
+{
+ { "mono", SPA_AUDIO_LAYOUT_Mono },
+ { "stereo", SPA_AUDIO_LAYOUT_Stereo },
+ { "surround-21", SPA_AUDIO_LAYOUT_2_1 },
+ { "quad", SPA_AUDIO_LAYOUT_Quad },
+ { "surround-22", SPA_AUDIO_LAYOUT_2_2 },
+ { "surround-40", SPA_AUDIO_LAYOUT_4_0 },
+ { "surround-31", SPA_AUDIO_LAYOUT_3_1 },
+ { "surround-41", SPA_AUDIO_LAYOUT_4_1 },
+ { "surround-50", SPA_AUDIO_LAYOUT_5_0 },
+ { "surround-51", SPA_AUDIO_LAYOUT_5_1 },
+ { "surround-51r", SPA_AUDIO_LAYOUT_5_1R },
+ { "surround-70", SPA_AUDIO_LAYOUT_7_0 },
+ { "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;
+ char **ch;
+
+ SPA_FOR_EACH_ELEMENT_VAR(maps, m) {
+ if (spa_streq(m->name, channel_map)) {
+ map->n_channels = m->channels;
+ spa_memcpy(map->channels, &m->values,
+ map->n_channels * sizeof(unsigned int));
+ return 0;
+ }
+ }
+
+ 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;
+ }
+ pw_free_strv(ch);
+ return 0;
+}
+
+static int channelmap_default(struct channelmap *map, int n_channels)
+{
+ switch(n_channels) {
+ case 1:
+ parse_channelmap("mono", map);
+ break;
+ case 2:
+ parse_channelmap("stereo", map);
+ break;
+ case 3:
+ parse_channelmap("surround-21", map);
+ break;
+ case 4:
+ parse_channelmap("quad", map);
+ break;
+ case 5:
+ parse_channelmap("surround-50", map);
+ break;
+ case 6:
+ parse_channelmap("surround-51", map);
+ break;
+ case 7:
+ parse_channelmap("surround-70", map);
+ break;
+ case 8:
+ parse_channelmap("surround-71", map);
+ break;
+ default:
+ n_channels = 0;
+ break;
+ }
+ map->n_channels = n_channels;
+ return 0;
+}
+
+static void channelmap_print(struct channelmap *map)
+{
+ int 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 ? "," : "");
+ }
+}
+
+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",
+ info->id, info->name);
+}
+
+static void on_core_error(void *userdata, uint32_t id, int seq, int res, const char *message)
+{
+ struct data *data = userdata;
+
+ fprintf(stderr, "remote error: id=%"PRIu32" seq:%d res:%d (%s): %s\n",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_main_loop_quit(data->loop);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .info = on_core_info,
+ .error = on_core_error,
+};
+
+static void
+on_state_changed(void *userdata, enum pw_stream_state old,
+ enum pw_stream_state state, const char *error)
+{
+ struct data *data = userdata;
+ int ret;
+
+ if (data->verbose)
+ printf("stream state changed %s -> %s\n",
+ pw_stream_state_as_string(old),
+ pw_stream_state_as_string(state));
+
+ switch (state) {
+ case PW_STREAM_STATE_STREAMING:
+ if (!data->volume_is_set) {
+ ret = pw_stream_set_control(data->stream,
+ SPA_PROP_volume, 1, &data->volume,
+ 0);
+ if (data->verbose)
+ printf("stream set volume to %.3f - %s\n", data->volume,
+ ret == 0 ? "success" : "FAILED");
+
+ data->volume_is_set = true;
+ }
+ if (data->verbose) {
+ 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",
+ pw_stream_get_node_id(data->stream));
+ }
+ break;
+ case PW_STREAM_STATE_PAUSED:
+ if (data->verbose) {
+ struct timespec timeout = {0, 0}, interval = {0, 0};
+ struct pw_loop *l = pw_main_loop_get_loop(data->loop);
+ pw_loop_update_timer(l, data->timer, &timeout, &interval, false);
+ }
+ break;
+ case PW_STREAM_STATE_ERROR:
+ printf("stream node %"PRIu32" error: %s\n",
+ pw_stream_get_node_id(data->stream),
+ error);
+ pw_main_loop_quit(data->loop);
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+on_io_changed(void *userdata, uint32_t id, void *data, uint32_t size)
+{
+ struct data *d = userdata;
+
+ switch (id) {
+ case SPA_IO_Position:
+ d->position = data;
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+on_param_changed(void *userdata, uint32_t id, const struct spa_pod *param)
+{
+ struct data *data = userdata;
+ struct spa_audio_info info = { 0 };
+ int err;
+
+ if (data->verbose)
+ printf("stream param change: %s\n",
+ spa_debug_type_find_name(spa_type_param, id));
+
+ if (id != SPA_PARAM_Format || param == NULL)
+ return;
+
+ if ((err = spa_format_parse(param, &info.media_type, &info.media_subtype)) < 0)
+ return;
+
+ if (info.media_type != SPA_MEDIA_TYPE_audio ||
+ info.media_subtype != SPA_MEDIA_SUBTYPE_dsd)
+ return;
+
+ if (spa_format_audio_dsd_parse(param, &info.info.dsd) < 0)
+ return;
+
+ data->dsf.layout.interleave = info.info.dsd.interleave,
+ data->dsf.layout.channels = info.info.dsd.channels;
+ data->dsf.layout.lsb = info.info.dsd.bitorder == SPA_PARAM_BITORDER_lsb;
+
+ 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",
+ data->dsf.layout.channels,
+ data->dsf.layout.lsb ? "lsb" : "msb",
+ data->dsf.layout.interleave,
+ data->stride);
+ }
+}
+
+static void on_process(void *userdata)
+{
+ struct data *data = userdata;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ struct spa_data *d;
+ int n_frames, n_fill_frames;
+ uint8_t *p;
+ bool have_data;
+ uint32_t offset, size;
+
+ if ((b = pw_stream_dequeue_buffer(data->stream)) == NULL)
+ return;
+
+ buf = b->buffer;
+ d = &buf->datas[0];
+
+ have_data = false;
+
+ if ((p = d->data) == NULL)
+ return;
+
+ if (data->mode == mode_playback) {
+ n_frames = d->maxsize / data->stride;
+ n_frames = SPA_MIN(n_frames, (int)b->requested);
+
+#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
+ n_fill_frames = data->fill(data, p, n_frames);
+
+ if (n_fill_frames > 0 || n_frames == 0) {
+ d->chunk->offset = 0;
+ if (data->data_type == TYPE_ENCODED) {
+ d->chunk->stride = 0;
+ // encoded_playback_fill returns number of bytes
+ // read and not number of frames like other
+ // functions for raw audio.
+ d->chunk->size = n_fill_frames;
+ b->size = n_fill_frames;
+ } else {
+ d->chunk->stride = data->stride;
+ d->chunk->size = n_fill_frames * data->stride;
+ b->size = n_frames;
+ }
+ have_data = true;
+ } else if (n_fill_frames < 0) {
+ fprintf(stderr, "fill error %d\n", n_fill_frames);
+ } else {
+ if (data->verbose)
+ printf("drain start\n");
+ }
+#else
+ n_fill_frames = data->fill(data, p, n_frames);
+
+ if (n_fill_frames > 0 || n_frames == 0) {
+ d->chunk->offset = 0;
+ d->chunk->stride = data->stride;
+ d->chunk->size = n_fill_frames * data->stride;
+ have_data = true;
+ b->size = n_frames;
+ } else if (n_fill_frames < 0) {
+ fprintf(stderr, "fill error %d\n", n_fill_frames);
+ } else {
+ if (data->verbose)
+ printf("drain start\n");
+ }
+#endif
+ } else {
+ offset = SPA_MIN(d->chunk->offset, d->maxsize);
+ size = SPA_MIN(d->chunk->size, d->maxsize - offset);
+
+ p += offset;
+
+ n_frames = size / data->stride;
+
+ n_fill_frames = data->fill(data, p, n_frames);
+
+ have_data = true;
+ }
+
+ if (have_data) {
+ pw_stream_queue_buffer(data->stream, b);
+ return;
+ }
+
+ if (data->mode == mode_playback)
+ pw_stream_flush(data->stream, true);
+}
+
+static void on_drained(void *userdata)
+{
+ struct data *data = userdata;
+
+ if (data->verbose)
+ printf("stream drained\n");
+
+ data->drained = true;
+ pw_main_loop_quit(data->loop);
+}
+
+static const struct pw_stream_events stream_events = {
+ PW_VERSION_STREAM_EVENTS,
+ .state_changed = on_state_changed,
+ .io_changed = on_io_changed,
+ .param_changed = on_param_changed,
+ .process = on_process,
+ .drained = on_drained
+};
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_main_loop_quit(data->loop);
+}
+
+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
+ " delay:%"PRIi64" queued:%"PRIu64
+ " buffered:%"PRIi64" buffers:%u avail:%u\n",
+ time.now,
+ time.rate.num, time.rate.denom,
+ time.ticks, time.delay, time.queued, time.buffered,
+ time.queued_buffers, time.avail_buffers);
+}
+
+enum {
+ OPT_VERSION = 1000,
+ OPT_MEDIA_TYPE,
+ OPT_MEDIA_CATEGORY,
+ OPT_MEDIA_ROLE,
+ OPT_TARGET,
+ OPT_LATENCY,
+ OPT_RATE,
+ OPT_CHANNELS,
+ OPT_CHANNELMAP,
+ OPT_FORMAT,
+ OPT_VOLUME,
+};
+
+static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, OPT_VERSION},
+ { "verbose", no_argument, NULL, 'v' },
+
+ { "record", no_argument, NULL, 'r' },
+ { "playback", no_argument, NULL, 'p' },
+ { "midi", no_argument, NULL, 'm' },
+
+ { "remote", required_argument, NULL, 'R' },
+
+ { "media-type", required_argument, NULL, OPT_MEDIA_TYPE },
+ { "media-category", required_argument, NULL, OPT_MEDIA_CATEGORY },
+ { "media-role", required_argument, NULL, OPT_MEDIA_ROLE },
+ { "target", required_argument, NULL, OPT_TARGET },
+ { "latency", required_argument, NULL, OPT_LATENCY },
+ { "properties", required_argument, NULL, 'P' },
+
+ { "rate", required_argument, NULL, OPT_RATE },
+ { "channels", required_argument, NULL, OPT_CHANNELS },
+ { "channel-map", required_argument, NULL, OPT_CHANNELMAP },
+ { "format", required_argument, NULL, OPT_FORMAT },
+ { "volume", required_argument, NULL, OPT_VOLUME },
+ { "quality", required_argument, NULL, 'q' },
+
+ { 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] [<file>|-]\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " -v, --verbose Enable verbose operations\n"
+ "\n"), name);
+
+ fprintf(fp,
+ _(" -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"),
+ DEFAULT_MEDIA_TYPE,
+ DEFAULT_MEDIA_CATEGORY_PLAYBACK,
+ DEFAULT_MEDIA_ROLE,
+ DEFAULT_TARGET, DEFAULT_LATENCY_PLAY);
+
+ fprintf(fp,
+ _(" --rate Sample rate (req. for rec) (default %u)\n"
+ " --channels Number of channels (req. for rec) (default %u)\n"
+ " --channel-map Channel map\n"
+ " one of: \"stereo\", \"surround-51\",... or\n"
+ " comma separated list of channel names: eg. \"FL,FR\"\n"
+ " --format Sample format %s (req. for rec) (default %s)\n"
+ " --volume Stream volume 0-1.0 (default %.3f)\n"
+ " -q --quality Resampler quality (0 - 15) (default %d)\n"
+ "\n"),
+ DEFAULT_RATE,
+ DEFAULT_CHANNELS,
+ STR_FMTS, DEFAULT_FORMAT,
+ DEFAULT_VOLUME,
+ DEFAULT_QUALITY);
+
+ if (spa_streq(name, "pw-cat")) {
+ fputs(
+ _(" -p, --playback Playback mode\n"
+ " -r, --record Recording mode\n"
+ " -m, --midi Midi mode\n"
+ " -d, --dsd DSD mode\n"
+#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
+ " -o, --encoded Encoded mode\n"
+#endif
+ "\n"), fp);
+ }
+}
+
+static int midi_play(struct data *d, void *src, unsigned int n_frames)
+{
+ int res;
+ struct spa_pod_builder b;
+ struct spa_pod_frame f;
+ uint32_t first_frame, last_frame;
+ bool have_data = false;
+
+ spa_zero(b);
+ spa_pod_builder_init(&b, src, n_frames);
+
+ spa_pod_builder_push_sequence(&b, &f, 0);
+
+ first_frame = d->clock_time;
+ last_frame = first_frame + d->position->clock.duration;
+ d->clock_time = last_frame;
+
+ while (1) {
+ uint32_t frame;
+ struct midi_event ev;
+
+ res = midi_file_next_time(d->midi.file, &ev.sec);
+ if (res <= 0) {
+ if (have_data)
+ break;
+ return res;
+ }
+
+ frame = ev.sec * d->position->clock.rate.denom;
+ if (frame < first_frame)
+ frame = 0;
+ else if (frame < last_frame)
+ frame -= first_frame;
+ else
+ break;
+
+ midi_file_read_event(d->midi.file, &ev);
+
+ if (d->verbose)
+ midi_file_dump_event(stdout, &ev);
+
+ if (ev.data[0] == 0xff)
+ 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);
+
+ return b.state.offset;
+}
+
+static int midi_record(struct data *d, void *src, unsigned int n_frames)
+{
+ struct spa_pod *pod;
+ struct spa_pod_control *c;
+ uint32_t frame;
+
+ frame = d->clock_time;
+ d->clock_time += d->position->clock.duration;
+
+ if ((pod = spa_pod_from_data(src, n_frames, 0, n_frames)) == NULL)
+ return 0;
+ if (!spa_pod_is_sequence(pod))
+ return 0;
+
+ SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c) {
+ struct midi_event ev;
+
+ if (c->type != SPA_CONTROL_Midi)
+ 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);
+
+ if (d->verbose)
+ midi_file_dump_event(stdout, &ev);
+
+ midi_file_write_event(d->midi.file, &ev);
+ }
+ return 0;
+}
+
+static int setup_midifile(struct data *data)
+{
+ if (data->mode == mode_record) {
+ spa_zero(data->midi.info);
+ data->midi.info.format = 0;
+ data->midi.info.ntracks = 1;
+ data->midi.info.division = 0;
+ }
+
+ data->midi.file = midi_file_open(data->filename,
+ data->mode == mode_playback ? "r" : "w",
+ &data->midi.info);
+ if (data->midi.file == NULL) {
+ fprintf(stderr, "midifile: can't read midi file '%s': %m\n", data->filename);
+ return -errno;
+ }
+
+ if (data->verbose)
+ printf("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);
+
+ data->fill = data->mode == mode_playback ? midi_play : midi_record;
+ data->stride = 1;
+
+ return 0;
+}
+
+struct dsd_layout_info {
+ uint32_t type;
+ struct spa_audio_layout_info info;
+};
+static const struct dsd_layout_info dsd_layouts[] = {
+ { 1, { SPA_AUDIO_LAYOUT_Mono, }, },
+ { 2, { SPA_AUDIO_LAYOUT_Stereo, }, },
+ { 3, { SPA_AUDIO_LAYOUT_2FC }, },
+ { 4, { SPA_AUDIO_LAYOUT_Quad }, },
+ { 5, { SPA_AUDIO_LAYOUT_3_1 }, },
+ { 6, { SPA_AUDIO_LAYOUT_5_0R }, },
+ { 7, { SPA_AUDIO_LAYOUT_5_1R }, },
+};
+
+static int dsf_play(struct data *d, void *src, unsigned int n_frames)
+{
+ return dsf_file_read(d->dsf.file, src, n_frames, &d->dsf.layout);
+}
+
+static int setup_dsffile(struct data *data)
+{
+ if (data->mode == mode_record)
+ return -ENOTSUP;
+
+ data->dsf.file = dsf_file_open(data->filename, "r", &data->dsf.info);
+ if (data->dsf.file == NULL) {
+ fprintf(stderr, "dsffile: can't read dsf file '%s': %m\n", data->filename);
+ return -errno;
+ }
+
+ if (data->verbose)
+ printf("dsffile: opened file \"%s\" channels:%d rate:%d samples:%"PRIu64" bitorder:%s\n",
+ data->filename,
+ data->dsf.info.channels, data->dsf.info.rate,
+ data->dsf.info.samples,
+ data->dsf.info.lsb ? "lsb" : "msb");
+
+ data->fill = dsf_play;
+
+ return 0;
+}
+
+static int stdout_record(struct data *d, void *src, unsigned int n_frames)
+{
+ return fwrite(src, d->stride, n_frames, stdout);
+}
+
+static int stdin_play(struct data *d, void *src, unsigned int n_frames)
+{
+ return fread(src, d->stride, n_frames, stdin);
+}
+
+static int setup_pipe(struct data *data)
+{
+ const struct format_info *info;
+
+ if (data->format == NULL)
+ data->format = DEFAULT_FORMAT;
+ if (data->channels == 0)
+ data->channels = DEFAULT_CHANNELS;
+ if (data->rate == 0)
+ data->rate = DEFAULT_RATE;
+ if (data->channelmap.n_channels == 0)
+ channelmap_default(&data->channelmap, data->channels);
+
+ info = format_info_by_name(data->format);
+ if (info == NULL)
+ return -EINVAL;
+
+ data->spa_format = info->spa_format;
+ data->stride = info->width * data->channels;
+ data->fill = data->mode == mode_playback ? stdin_play : stdout_record;
+
+ if (data->verbose)
+ printf("PIPE: rate=%u channels=%u fmt=%s samplesize=%u stride=%u\n",
+ data->rate, data->channels,
+ info->name, info->width, data->stride);
+
+ return 0;
+}
+
+static int fill_properties(struct data *data)
+{
+ static const char * const table[] = {
+ [SF_STR_TITLE] = PW_KEY_MEDIA_TITLE,
+ [SF_STR_COPYRIGHT] = PW_KEY_MEDIA_COPYRIGHT,
+ [SF_STR_SOFTWARE] = PW_KEY_MEDIA_SOFTWARE,
+ [SF_STR_ARTIST] = PW_KEY_MEDIA_ARTIST,
+ [SF_STR_COMMENT] = PW_KEY_MEDIA_COMMENT,
+ [SF_STR_DATE] = PW_KEY_MEDIA_DATE
+ };
+
+ SF_INFO sfi;
+ SF_FORMAT_INFO fi;
+ int res;
+ unsigned c;
+ const char *s, *t;
+
+ for (c = 0; c < SPA_N_ELEMENTS(table); c++) {
+ if (table[c] == NULL)
+ continue;
+
+ if ((s = sf_get_string(data->file, c)) == NULL ||
+ *s == '\0')
+ continue;
+
+ pw_properties_set(data->props, table[c], s);
+ }
+
+ spa_zero(sfi);
+ if ((res = sf_command(data->file, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)))) {
+ pw_log_error("sndfile: %s", sf_error_number(res));
+ return -EIO;
+ }
+
+ spa_zero(fi);
+ fi.format = sfi.format;
+ if (sf_command(data->file, SFC_GET_FORMAT_INFO, &fi, sizeof(fi)) == 0 && fi.name)
+ pw_properties_set(data->props, PW_KEY_MEDIA_FORMAT, fi.name);
+
+ s = pw_properties_get(data->props, PW_KEY_MEDIA_TITLE);
+ t = pw_properties_get(data->props, PW_KEY_MEDIA_ARTIST);
+ if (s && t)
+ pw_properties_setf(data->props, PW_KEY_MEDIA_NAME,
+ "'%s' / '%s'", s, t);
+
+ return 0;
+}
+static void format_from_filename(SF_INFO *info, const char *filename)
+{
+ int i, count = 0;
+ int format = -1;
+
+#if __BYTE_ORDER == __BIG_ENDIAN
+ info->format |= SF_ENDIAN_BIG;
+#else
+ info->format |= SF_ENDIAN_LITTLE;
+#endif
+
+ 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(filename, fi.extension)) {
+ format = fi.format;
+ break;
+ }
+ }
+ if (format == -1)
+ format = SF_FORMAT_WAV;
+ if (format == SF_FORMAT_WAV && info->channels > 2)
+ format = SF_FORMAT_WAVEX;
+
+ info->format |= format;
+
+ if (format == SF_FORMAT_OGG || format == SF_FORMAT_FLAC)
+ info->format = (info->format & ~SF_FORMAT_ENDMASK) | SF_ENDIAN_FILE;
+ if (format == SF_FORMAT_OGG)
+ info->format = (info->format & ~SF_FORMAT_SUBMASK) | SF_FORMAT_VORBIS;
+}
+
+#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
+static int setup_encodedfile(struct data *data)
+{
+ int ret;
+ int bits_per_sample;
+ int num_channels;
+ char path[256] = { 0 };
+
+ /* We do not support record with encoded media */
+ if (data->mode == mode_record) {
+ return -EINVAL;
+ }
+
+ strcpy(path, "file:");
+ strcat(path, data->filename);
+
+ data->fmt_context = NULL;
+ ret = avformat_open_input(&data->fmt_context, path, NULL, NULL);
+ if (ret < 0) {
+ fprintf(stderr, "Failed to open input\n");
+ return -EINVAL;
+ }
+
+ avformat_find_stream_info (data->fmt_context, NULL);
+
+ data->ctx = avcodec_alloc_context3(NULL);
+ if (!data->ctx) {
+ fprintf(stderr, "Could not allocate audio codec context\n");
+ avformat_close_input(&data->fmt_context);
+ return -EINVAL;
+ }
+
+ // We expect only one stream with audio
+ data->astream = data->fmt_context->streams[0];
+ avcodec_parameters_to_context (data->ctx, data->astream->codecpar);
+
+ if (data->ctx->codec_type != AVMEDIA_TYPE_AUDIO) {
+ fprintf(stderr, "Not an audio file\n");
+ avformat_close_input(&data->fmt_context);
+ return -EINVAL;
+ }
+
+ printf("Number of streams: %d Codec id: %x\n", data->fmt_context->nb_streams,
+ data->ctx->codec_id);
+
+ /* FFmpeg 5.1 (which contains libavcodec 59.37.100) introduced
+ * a new channel layout API and deprecated the old one. */
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 37, 100)
+ num_channels = data->ctx->ch_layout.nb_channels;
+#else
+ num_channels = data->ctx->channels;
+#endif
+
+ data->rate = data->ctx->sample_rate;
+ data->channels = num_channels;
+ data->sfmt = data->ctx->sample_fmt;
+ data->stride = 1; // Don't care
+
+ bits_per_sample = av_get_bits_per_sample(data->ctx->codec_id);
+ data->bitrate = bits_per_sample ?
+ data->ctx->sample_rate * num_channels * bits_per_sample : data->ctx->bit_rate;
+
+ data->spa_format = SPA_AUDIO_FORMAT_ENCODED;
+ data->fill = playback_fill_fn(data->spa_format);
+
+ if (data->verbose)
+ printf("Opened file \"%s\" sample format %08x channels:%d rate:%d bitrate: %d\n",
+ data->filename, data->ctx->sample_fmt, data->channels,
+ data->rate, data->bitrate);
+
+ if (data->fill == NULL) {
+ fprintf(stderr, "Unhandled encoded format %d\n", data->spa_format);
+ avformat_close_input(&data->fmt_context);
+ return -EINVAL;
+ }
+
+ avformat_close_input(&data->fmt_context);
+
+ data->encoded_file = fopen(data->filename, "rb");
+ if (!data->encoded_file) {
+ fprintf(stderr, "Failed to open file\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+#endif
+
+static int setup_sndfile(struct data *data)
+{
+ const struct format_info *fi = NULL;
+ SF_INFO info;
+
+ spa_zero(info);
+ /* for record, you fill in the info first */
+ if (data->mode == mode_record) {
+ if (data->format == NULL)
+ data->format = DEFAULT_FORMAT;
+ if (data->channels == 0)
+ data->channels = DEFAULT_CHANNELS;
+ if (data->rate == 0)
+ data->rate = DEFAULT_RATE;
+ if (data->channelmap.n_channels == 0)
+ channelmap_default(&data->channelmap, data->channels);
+
+ if ((fi = format_info_by_name(data->format)) == NULL) {
+ fprintf(stderr, "error: unknown format \"%s\"\n", data->format);
+ return -EINVAL;
+ }
+ memset(&info, 0, sizeof(info));
+ info.samplerate = data->rate;
+ info.channels = data->channels;
+ info.format = fi->sf_format;
+ format_from_filename(&info, data->filename);
+ }
+
+ data->file = sf_open(data->filename,
+ data->mode == mode_playback ? SFM_READ : SFM_WRITE,
+ &info);
+ if (!data->file) {
+ fprintf(stderr, "sndfile: failed to open audio file \"%s\": %s\n",
+ data->filename, sf_strerror(NULL));
+ return -EIO;
+ }
+
+ if (data->verbose)
+ printf("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) {
+ fprintf(stderr, "sndfile: given channels (%u) don't match file channels (%d)\n",
+ data->channels, info.channels);
+ return -EINVAL;
+ }
+
+ data->rate = info.samplerate;
+ data->channels = info.channels;
+
+ if (data->mode == mode_playback) {
+ if (data->channelmap.n_channels == 0) {
+ bool def = false;
+
+ if (sf_command(data->file, SFC_GET_CHANNEL_MAP_INFO,
+ data->channelmap.channels,
+ sizeof(data->channelmap.channels[0]) * data->channels)) {
+ data->channelmap.n_channels = data->channels;
+ if (channelmap_from_sf(&data->channelmap) < 0)
+ data->channelmap.n_channels = 0;
+ }
+ if (data->channelmap.n_channels == 0) {
+ channelmap_default(&data->channelmap, data->channels);
+ def = true;
+ }
+ if (data->verbose) {
+ printf("sndfile: using %s channel map: ", def ? "default" : "file");
+ channelmap_print(&data->channelmap);
+ printf("\n");
+ }
+ }
+ fill_properties(data);
+
+ /* try native format first, else decode to float */
+ if ((fi = format_info_by_sf_format(info.format)) == NULL)
+ fi = format_info_by_sf_format(SF_FORMAT_FLOAT);
+
+ }
+ if (fi == NULL)
+ return -EIO;
+
+ if (data->verbose)
+ printf("PCM: fmt:%s rate:%u channels:%u width:%u\n",
+ fi->name, data->rate, data->channels, fi->width);
+
+ /* we read and write S24 as S32 with sndfile */
+ if (fi->spa_format == SPA_AUDIO_FORMAT_S24)
+ fi = format_info_by_sf_format(SF_FORMAT_PCM_32);
+
+ data->spa_format = fi->spa_format;
+ data->stride = fi->width * data->channels;
+ data->fill = data->mode == mode_playback ?
+ playback_fill_fn(data->spa_format) :
+ record_fill_fn(data->spa_format);
+
+ if (data->fill == NULL) {
+ fprintf(stderr, "PCM: unhandled format %d\n", data->spa_format);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int setup_properties(struct data *data)
+{
+ const char *s;
+ unsigned int nom = 0;
+
+ if (data->quality >= 0)
+ pw_properties_setf(data->props, "resample.quality", "%d", data->quality);
+
+ if (data->rate)
+ pw_properties_setf(data->props, PW_KEY_NODE_RATE, "1/%u", data->rate);
+
+ data->latency_unit = unit_none;
+
+ s = data->latency;
+ while (*s && isdigit(*s))
+ s++;
+ if (!*s)
+ data->latency_unit = unit_samples;
+ else if (spa_streq(s, "none"))
+ data->latency_unit = unit_none;
+ else if (spa_streq(s, "s") || spa_streq(s, "sec") || spa_streq(s, "secs"))
+ data->latency_unit = unit_sec;
+ else if (spa_streq(s, "ms") || spa_streq(s, "msec") || spa_streq(s, "msecs"))
+ data->latency_unit = unit_msec;
+ else if (spa_streq(s, "us") || spa_streq(s, "usec") || spa_streq(s, "usecs"))
+ data->latency_unit = unit_usec;
+ else if (spa_streq(s, "ns") || spa_streq(s, "nsec") || spa_streq(s, "nsecs"))
+ data->latency_unit = unit_nsec;
+ else {
+ fprintf(stderr, "error: bad latency value %s (bad unit)\n", data->latency);
+ return -EINVAL;
+ }
+ data->latency_value = atoi(data->latency);
+ if (!data->latency_value && data->latency_unit != unit_none) {
+ fprintf(stderr, "error: bad latency value %s (is zero)\n", data->latency);
+ return -EINVAL;
+ }
+
+ switch (data->latency_unit) {
+ case unit_sec:
+ nom = data->latency_value * data->rate;
+ break;
+ case unit_msec:
+ nom = nearbyint((data->latency_value * data->rate) / 1000.0);
+ break;
+ case unit_usec:
+ nom = nearbyint((data->latency_value * data->rate) / 1000000.0);
+ break;
+ case unit_nsec:
+ nom = nearbyint((data->latency_value * data->rate) / 1000000000.0);
+ break;
+ case unit_samples:
+ nom = data->latency_value;
+ break;
+ default:
+ nom = 0;
+ break;
+ }
+
+ if (data->verbose)
+ printf("rate:%d latency:%u (%.3fs)\n",
+ data->rate, nom, data->rate ? (double)nom/data->rate : 0.0f);
+ if (nom)
+ pw_properties_setf(data->props, PW_KEY_NODE_LATENCY, "%u/%u", nom, data->rate);
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ struct pw_loop *l;
+ const struct spa_pod *params[1];
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ const char *prog;
+ int exit_code = EXIT_FAILURE, c, ret;
+ enum pw_stream_flags flags = 0;
+
+ setlocale(LC_ALL, "");
+ pw_init(&argc, &argv);
+
+ flags |= PW_STREAM_FLAG_AUTOCONNECT;
+
+ prog = argv[0];
+ if ((prog = strrchr(argv[0], '/')) != NULL)
+ prog++;
+ else
+ prog = argv[0];
+
+ /* prime the mode from the program name */
+ if (spa_streq(prog, "pw-play")) {
+ data.mode = mode_playback;
+ data.data_type = TYPE_PCM;
+ } else if (spa_streq(prog, "pw-record")) {
+ data.mode = mode_record;
+ data.data_type = TYPE_PCM;
+ } else if (spa_streq(prog, "pw-midiplay")) {
+ data.mode = mode_playback;
+ data.data_type = TYPE_MIDI;
+ } else if (spa_streq(prog, "pw-midirecord")) {
+ data.mode = mode_record;
+ data.data_type = TYPE_MIDI;
+ } else if (spa_streq(prog, "pw-dsdplay")) {
+ data.mode = mode_playback;
+ data.data_type = TYPE_DSD;
+ } else
+ data.mode = mode_none;
+
+ /* negative means no volume adjustment */
+ data.volume = -1.0;
+ data.quality = -1;
+ data.props = pw_properties_new(
+ PW_KEY_APP_NAME, prog,
+ PW_KEY_NODE_NAME, prog,
+ NULL);
+
+ if (data.props == NULL) {
+ fprintf(stderr, "error: pw_properties_new() failed: %m\n");
+ goto error_no_props;
+ }
+
+#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
+ while ((c = getopt_long(argc, argv, "hvprmdoR:q:P:", long_options, NULL)) != -1) {
+#else
+ while ((c = getopt_long(argc, argv, "hvprmdR:q:P:", long_options, NULL)) != -1) {
+#endif
+
+ switch (c) {
+
+ case 'h':
+ show_usage(prog, false);
+ return EXIT_SUCCESS;
+
+ case OPT_VERSION:
+ printf("%s\n"
+ "Compiled with libpipewire %s\n"
+ "Linked with libpipewire %s\n",
+ prog,
+ pw_get_headers_version(),
+ pw_get_library_version());
+ return 0;
+
+ case 'v':
+ data.verbose = true;
+ break;
+
+ case 'p':
+ data.mode = mode_playback;
+ break;
+
+ case 'r':
+ data.mode = mode_record;
+ break;
+
+ case 'm':
+ data.data_type = TYPE_MIDI;
+ break;
+
+ case 'd':
+ data.data_type = TYPE_DSD;
+ break;
+
+#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
+ case 'o':
+ data.data_type = TYPE_ENCODED;
+ break;
+#endif
+
+ case 'R':
+ data.remote_name = optarg;
+ break;
+
+ case 'q':
+ data.quality = atoi(optarg);
+ break;
+
+ case OPT_MEDIA_TYPE:
+ data.media_type = optarg;
+ break;
+
+ case OPT_MEDIA_CATEGORY:
+ data.media_category = optarg;
+ break;
+
+ case OPT_MEDIA_ROLE:
+ data.media_role = optarg;
+ break;
+
+ case 'P':
+ pw_properties_update_string(data.props, optarg, strlen(optarg));
+ break;
+
+ case OPT_TARGET:
+ data.target = optarg;
+ if (spa_streq(data.target, "0")) {
+ data.target = NULL;
+ flags &= ~PW_STREAM_FLAG_AUTOCONNECT;
+ }
+ break;
+
+ case OPT_LATENCY:
+ data.latency = optarg;
+ break;
+
+ case OPT_RATE:
+ ret = atoi(optarg);
+ if (ret <= 0) {
+ fprintf(stderr, "error: bad rate %d\n", ret);
+ goto error_usage;
+ }
+ data.rate = (unsigned int)ret;
+ break;
+
+ case OPT_CHANNELS:
+ ret = atoi(optarg);
+ if (ret <= 0) {
+ fprintf(stderr, "error: bad channels %d\n", ret);
+ goto error_usage;
+ }
+ data.channels = (unsigned int)ret;
+ break;
+
+ case OPT_CHANNELMAP:
+ data.channel_map = optarg;
+ break;
+
+ case OPT_FORMAT:
+ data.format = optarg;
+ break;
+
+ case OPT_VOLUME:
+ data.volume = atof(optarg);
+ break;
+ default:
+ goto error_usage;
+ }
+ }
+
+ if (data.mode == mode_none) {
+ fprintf(stderr, "error: one of the playback/record options must be provided\n");
+ goto error_usage;
+ }
+
+ if (!data.media_type) {
+ switch (data.data_type) {
+ case TYPE_MIDI:
+ data.media_type = DEFAULT_MIDI_MEDIA_TYPE;
+ break;
+ default:
+ data.media_type = DEFAULT_MEDIA_TYPE;
+ break;
+ }
+ }
+ if (!data.media_category)
+ data.media_category = data.mode == mode_playback ?
+ DEFAULT_MEDIA_CATEGORY_PLAYBACK :
+ DEFAULT_MEDIA_CATEGORY_RECORD;
+ if (!data.media_role)
+ data.media_role = DEFAULT_MEDIA_ROLE;
+
+ if (!data.latency)
+ data.latency = data.mode == mode_playback ?
+ DEFAULT_LATENCY_PLAY :
+ DEFAULT_LATENCY_REC;
+ if (data.channel_map != NULL) {
+ if (parse_channelmap(data.channel_map, &data.channelmap) < 0) {
+ fprintf(stderr, "error: can parse channel-map \"%s\"\n", data.channel_map);
+ goto error_usage;
+
+ } else {
+ if (data.channels > 0 && data.channelmap.n_channels != data.channels) {
+ fprintf(stderr, "error: channels and channel-map incompatible\n");
+ goto error_usage;
+ }
+ data.channels = data.channelmap.n_channels;
+ }
+ }
+ if (data.volume < 0)
+ data.volume = DEFAULT_VOLUME;
+
+ if (optind >= argc) {
+ fprintf(stderr, "error: filename or - argument missing\n");
+ goto error_usage;
+ }
+ data.filename = argv[optind++];
+
+ pw_properties_set(data.props, PW_KEY_MEDIA_TYPE, data.media_type);
+ pw_properties_set(data.props, PW_KEY_MEDIA_CATEGORY, data.media_category);
+ pw_properties_set(data.props, PW_KEY_MEDIA_ROLE, data.media_role);
+ pw_properties_set(data.props, PW_KEY_MEDIA_FILENAME, data.filename);
+ pw_properties_set(data.props, PW_KEY_MEDIA_NAME, data.filename);
+ pw_properties_set(data.props, PW_KEY_TARGET_OBJECT, data.target);
+
+ /* make a main loop. If you already have another main loop, you can add
+ * the fd of this pipewire mainloop to it. */
+ data.loop = pw_main_loop_new(NULL);
+ if (!data.loop) {
+ fprintf(stderr, "error: pw_main_loop_new() failed: %m\n");
+ goto error_no_main_loop;
+ }
+
+ l = pw_main_loop_get_loop(data.loop);
+ 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);
+ if (!data.context) {
+ fprintf(stderr, "error: pw_context_new() failed: %m\n");
+ goto error_no_context;
+ }
+
+ data.core = pw_context_connect(data.context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, data.remote_name,
+ NULL),
+ 0);
+ if (!data.core) {
+ fprintf(stderr, "error: pw_context_connect() failed: %m\n");
+ goto error_ctx_connect_failed;
+ }
+ pw_core_add_listener(data.core, &data.core_listener, &core_events, &data);
+
+ if (spa_streq(data.filename, "-")) {
+ ret = setup_pipe(&data);
+ } else {
+ switch (data.data_type) {
+ case TYPE_PCM:
+ ret = setup_sndfile(&data);
+ break;
+ case TYPE_MIDI:
+ ret = setup_midifile(&data);
+ break;
+ case TYPE_DSD:
+ ret = setup_dsffile(&data);
+ break;
+#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
+ case TYPE_ENCODED:
+ ret = setup_encodedfile(&data);
+ break;
+#endif
+ default:
+ ret = -ENOTSUP;
+ break;
+ }
+ }
+ if (ret < 0) {
+ fprintf(stderr, "error: open failed: %s\n", spa_strerror(ret));
+ switch (ret) {
+ case -EIO:
+ goto error_bad_file;
+ case -EINVAL:
+ default:
+ goto error_usage;
+ }
+ }
+ ret = setup_properties(&data);
+
+ switch (data.data_type) {
+#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
+ case TYPE_ENCODED:
+ {
+ struct spa_audio_info info;
+
+ spa_zero(info);
+ info.media_type = SPA_MEDIA_TYPE_audio;
+
+ ret = avcodec_ctx_to_info(&data, data.ctx, &info);
+ if (ret < 0) {
+ if (data.encoded_file) {
+ fclose(data.encoded_file);
+ }
+ goto error_bad_file;
+ }
+ params[0] = spa_format_audio_build(&b, SPA_PARAM_EnumFormat, &info);
+ break;
+ }
+#endif
+ case TYPE_PCM:
+ {
+ struct spa_audio_info_raw info;
+ info = SPA_AUDIO_INFO_RAW_INIT(
+ .flags = data.channelmap.n_channels ? 0 : SPA_AUDIO_FLAG_UNPOSITIONED,
+ .format = data.spa_format,
+ .rate = data.rate,
+ .channels = data.channels);
+
+ if (data.channelmap.n_channels)
+ memcpy(info.position, data.channelmap.channels, data.channels * sizeof(int));
+
+ params[0] = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info);
+ break;
+ }
+ case TYPE_MIDI:
+ params[0] = 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));
+
+ pw_properties_set(data.props, PW_KEY_FORMAT_DSP, "8 bit raw midi");
+ break;
+ case TYPE_DSD:
+ {
+ struct spa_audio_info_dsd info;
+
+ spa_zero(info);
+ info.channels = data.dsf.info.channels;
+ info.rate = data.dsf.info.rate / 8;
+
+ SPA_FOR_EACH_ELEMENT_VAR(dsd_layouts, i) {
+ if (i->type != data.dsf.info.channel_type)
+ continue;
+ info.channels = i->info.n_channels;
+ memcpy(info.position, i->info.position,
+ info.channels * sizeof(uint32_t));
+ }
+ params[0] = spa_format_audio_dsd_build(&b, SPA_PARAM_EnumFormat, &info);
+ break;
+ }
+ }
+
+ data.stream = pw_stream_new(data.core, prog, data.props);
+ data.props = NULL;
+
+ if (data.stream == NULL) {
+ fprintf(stderr, "error: failed to create stream: %m\n");
+ goto error_no_stream;
+ }
+ pw_stream_add_listener(data.stream, &data.stream_listener, &stream_events, &data);
+
+ if (data.verbose)
+ printf("connecting %s stream; target=%s\n",
+ data.mode == mode_playback ? "playback" : "record",
+ data.target);
+
+ if (data.verbose)
+ data.timer = pw_loop_add_timer(l, do_print_delay, &data);
+
+ ret = pw_stream_connect(data.stream,
+ data.mode == mode_playback ? PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT,
+ PW_ID_ANY,
+ flags |
+ PW_STREAM_FLAG_MAP_BUFFERS,
+ params, 1);
+ if (ret < 0) {
+ fprintf(stderr, "error: failed connect: %s\n", spa_strerror(ret));
+ goto error_connect_fail;
+ }
+
+ if (data.verbose) {
+ const struct pw_properties *props;
+ void *pstate;
+ const char *key, *val;
+
+ if ((props = pw_stream_get_properties(data.stream)) != NULL) {
+ printf("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);
+ }
+ }
+ }
+
+ /* and wait while we let things run */
+ pw_main_loop_run(data.loop);
+
+#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
+ if (data.encoded_file)
+ fclose(data.encoded_file);
+#endif
+
+ /* we're returning OK only if got to the point to drain */
+ if (data.drained)
+ exit_code = EXIT_SUCCESS;
+
+error_connect_fail:
+ if (data.stream) {
+ spa_hook_remove(&data.stream_listener);
+ pw_stream_destroy(data.stream);
+ }
+error_no_stream:
+error_bad_file:
+ spa_hook_remove(&data.core_listener);
+ pw_core_disconnect(data.core);
+error_ctx_connect_failed:
+ pw_context_destroy(data.context);
+error_no_context:
+ pw_main_loop_destroy(data.loop);
+error_no_props:
+error_no_main_loop:
+ pw_properties_free(data.props);
+ if (data.file)
+ sf_close(data.file);
+ if (data.midi.file)
+ midi_file_close(data.midi.file);
+ pw_deinit();
+ return exit_code;
+
+error_usage:
+ show_usage(prog, true);
+ return EXIT_FAILURE;
+}
diff --git a/src/tools/pw-cli.c b/src/tools/pw-cli.c
new file mode 100644
index 0000000..53a3384
--- /dev/null
+++ b/src/tools/pw-cli.c
@@ -0,0 +1,2375 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#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
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+#include <locale.h>
+
+#if !defined(FNM_EXTMATCH)
+#define FNM_EXTMATCH 0
+#endif
+
+#define spa_debug(fmt,...) printf(fmt"\n", ## __VA_ARGS__)
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/debug/pod.h>
+#include <spa/utils/keys.h>
+#include <spa/utils/json-pod.h>
+#include <spa/pod/builder.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/i18n.h>
+
+#include <pipewire/extensions/session-manager.h>
+
+static const char WHITESPACE[] = " \t";
+static char prompt[64];
+
+struct remote_data;
+struct proxy_data;
+
+typedef void (*info_func_t) (struct proxy_data *pd);
+
+struct class {
+ const char *type;
+ uint32_t version;
+ const void *events;
+ pw_destroy_t destroy;
+ info_func_t info;
+ const char *name_key;
+};
+
+struct data {
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+
+ struct spa_list remotes;
+ struct remote_data *current;
+
+ struct pw_map vars;
+ unsigned int interactive:1;
+ unsigned int monitoring:1;
+ unsigned int quit:1;
+};
+
+struct global {
+ struct remote_data *rd;
+ uint32_t id;
+ uint32_t permissions;
+ uint32_t version;
+ char *type;
+ const struct class *class;
+ struct pw_proxy *proxy;
+ bool info_pending;
+ struct pw_properties *properties;
+};
+
+struct remote_data {
+ struct spa_list link;
+ struct data *data;
+
+ char *name;
+ uint32_t id;
+
+ int prompt_pending;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+ struct spa_hook proxy_core_listener;
+ struct pw_registry *registry;
+ struct spa_hook registry_listener;
+
+ struct pw_map globals;
+};
+
+
+struct proxy_data {
+ struct remote_data *rd;
+ struct global *global;
+ struct pw_proxy *proxy;
+ void *info;
+ const struct class *class;
+ struct spa_hook proxy_listener;
+ struct spa_hook object_listener;
+};
+
+struct command {
+ const char *name;
+ const char *alias;
+ const char *description;
+ bool (*func) (struct data *data, const char *cmd, char *args, char **error);
+};
+
+static struct spa_dict * global_props(struct global *global);
+static struct global * obj_global(struct remote_data *rd, uint32_t id);
+static int children_of(struct remote_data *rd, uint32_t parent_id,
+ const char *child_type, uint32_t **children);
+
+static void print_properties(struct spa_dict *props, char mark, bool header)
+{
+ const struct spa_dict_item *item;
+
+ if (header)
+ printf("%c\tproperties:\n", mark);
+ if (props == NULL || props->n_items == 0) {
+ if (header)
+ printf("\t\tnone\n");
+ return;
+ }
+
+ spa_dict_for_each(item, props) {
+ printf("%c\t\t%s = \"%s\"\n", mark, item->key, item->value);
+ }
+}
+
+static void print_params(struct spa_param_info *params, uint32_t n_params, char mark, bool header)
+{
+ uint32_t i;
+
+ if (header)
+ printf("%c\tparams: (%u)\n", mark, n_params);
+ if (params == NULL || n_params == 0) {
+ if (header)
+ printf("\t\tnone\n");
+ return;
+ }
+ for (i = 0; i < n_params; i++) {
+ const struct spa_type_info *type_info = spa_type_param;
+
+ printf("%c\t %d (%s) %c%c\n",
+ params[i].user > 0 ? mark : ' ', params[i].id,
+ spa_debug_type_find_name(type_info, params[i].id),
+ params[i].flags & SPA_PARAM_INFO_READ ? 'r' : '-',
+ params[i].flags & SPA_PARAM_INFO_WRITE ? 'w' : '-');
+ params[i].user = 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;
+}
+
+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_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);
+static bool do_list_remotes(struct data *data, const char *cmd, char *args, char **error);
+static bool do_switch_remote(struct data *data, const char *cmd, char *args, char **error);
+static bool do_info(struct data *data, const char *cmd, char *args, char **error);
+static bool do_create_device(struct data *data, const char *cmd, char *args, char **error);
+static bool do_create_node(struct data *data, const char *cmd, char *args, char **error);
+static bool do_destroy(struct data *data, const char *cmd, char *args, char **error);
+static bool do_create_link(struct data *data, const char *cmd, char *args, char **error);
+static bool do_export_node(struct data *data, const char *cmd, char *args, char **error);
+static bool do_enum_params(struct data *data, const char *cmd, char *args, char **error);
+static bool do_set_param(struct data *data, const char *cmd, char *args, char **error);
+static bool do_permissions(struct data *data, const char *cmd, char *args, char **error);
+static bool do_get_permissions(struct data *data, const char *cmd, char *args, char **error);
+static bool do_send_command(struct data *data, const char *cmd, char *args, char **error);
+static bool do_quit(struct data *data, const char *cmd, char *args, char **error);
+
+#define DUMP_NAMES "Core|Module|Device|Node|Port|Factory|Client|Link|Session|Endpoint|EndpointStream"
+
+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 },
+ { "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 },
+ { "switch-remote", "sr", "Switch between current remotes. [<remote-var>]", do_switch_remote },
+ { "list-objects", "ls", "List objects or current remote. [<interface>]", do_list_objects },
+ { "info", "i", "Get info about an object. <object-id>|all", do_info },
+ { "create-device", "cd", "Create a device from a factory. <factory-name> [<properties>]", do_create_device },
+ { "create-node", "cn", "Create a node from a factory. <factory-name> [<properties>]", do_create_node },
+ { "destroy", "d", "Destroy a global object. <object-id>", do_destroy },
+ { "create-link", "cl", "Create a link between nodes. <node-id> <port-id> <node-id> <port-id> [<properties>]", do_create_link },
+ { "export-node", "en", "Export a local node to the current remote. <node-id> [remote-var]", do_export_node },
+ { "enum-params", "e", "Enumerate params of an object <object-id> <param-id>", do_enum_params },
+ { "set-param", "s", "Set param of an object <object-id> <param-id> <param-json>", do_set_param },
+ { "permissions", "sp", "Set permissions for a client <client-id> <object> <permission>", do_permissions },
+ { "get-permissions", "gp", "Get permissions of a client <client-id>", do_get_permissions },
+ { "send-command", "c", "Send a command <object-id>", do_send_command },
+ { "quit", "q", "Quit", do_quit },
+};
+
+static bool do_quit(struct data *data, const char *cmd, char *args, char **error)
+{
+ pw_main_loop_quit(data->loop);
+ data->quit = true;
+ return true;
+}
+
+static bool do_help(struct data *data, const char *cmd, char *args, char **error)
+{
+ printf("Available commands:\n");
+ SPA_FOR_EACH_ELEMENT_VAR(command_list, c) {
+ char cmd[256];
+ snprintf(cmd, sizeof(cmd), "%s | %s", c->name, c->alias);
+ printf("\t%-20.20s\t%s\n", cmd, c->description);
+ }
+ return true;
+}
+
+static bool do_load_module(struct data *data, const char *cmd, char *args, char **error)
+{
+ struct pw_impl_module *module;
+ char *a[2];
+ int n;
+ uint32_t id;
+
+ n = pw_split_ip(args, WHITESPACE, 2, a);
+ if (n < 1) {
+ *error = spa_aprintf("%s <module-name> [<module-arguments>]", cmd);
+ return false;
+ }
+
+ module = pw_context_load_module(data->context, a[0], n == 2 ? a[1] : NULL, NULL);
+ if (module == NULL) {
+ *error = spa_aprintf("Could not load module");
+ return false;
+ }
+
+ id = pw_map_insert_new(&data->vars, module);
+ if (data->interactive)
+ printf("%d = @module:%d\n", id, pw_global_get_id(pw_impl_module_get_global(module)));
+
+ return true;
+}
+
+static void on_core_info(void *_data, const struct pw_core_info *info)
+{
+ struct remote_data *rd = _data;
+ free(rd->name);
+ rd->name = info->name ? strdup(info->name) : NULL;
+ if (rd->data->interactive)
+ printf("remote %d is named '%s'\n", rd->id, rd->name);
+}
+
+static void set_prompt(struct remote_data *rd)
+{
+ snprintf(prompt, sizeof(prompt), "%s>> ", rd->name);
+#ifdef HAVE_READLINE
+ rl_set_prompt(prompt);
+#else
+ printf("%s", prompt);
+ fflush(stdout);
+#endif
+}
+
+static void on_core_done(void *_data, uint32_t id, int seq)
+{
+ struct remote_data *rd = _data;
+ struct data *d = rd->data;
+
+ if (seq == rd->prompt_pending) {
+ if (d->interactive) {
+ set_prompt(rd);
+ rd->data->monitoring = true;
+ } else {
+ pw_main_loop_quit(d->loop);
+ }
+ }
+}
+
+static bool global_matches(struct global *g, const char *pattern)
+{
+ const char *str;
+
+ if (g->properties == NULL)
+ return false;
+
+ if (strstr(g->type, pattern) != NULL)
+ return true;
+ if ((str = pw_properties_get(g->properties, PW_KEY_OBJECT_PATH)) != NULL &&
+ fnmatch(pattern, str, FNM_EXTMATCH) == 0)
+ return true;
+ if ((str = pw_properties_get(g->properties, PW_KEY_OBJECT_SERIAL)) != NULL &&
+ spa_streq(pattern, str))
+ return true;
+ if (g->class != NULL && g->class->name_key != NULL &&
+ (str = pw_properties_get(g->properties, g->class->name_key)) != NULL &&
+ fnmatch(pattern, str, FNM_EXTMATCH) == 0)
+ return true;
+
+ return false;
+}
+
+static int print_global(void *obj, void *data)
+{
+ struct global *global = obj;
+ const char *filter = data;
+
+ if (global == NULL)
+ return 0;
+
+ if (filter && !global_matches(global, filter))
+ return 0;
+
+ printf("\tid %d, type %s/%d\n", global->id,
+ global->type, global->version);
+ if (global->properties)
+ print_properties(&global->properties->dict, ' ', false);
+
+ return 0;
+}
+
+
+static bool bind_global(struct remote_data *rd, struct global *global, char **error);
+
+static void registry_event_global(void *data, uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ struct remote_data *rd = data;
+ struct global *global;
+ size_t size;
+ char *error;
+ bool ret;
+
+ global = calloc(1, sizeof(struct global));
+ global->rd = rd;
+ global->id = id;
+ global->permissions = permissions;
+ global->type = strdup(type);
+ global->version = version;
+ global->properties = props ? pw_properties_new_dict(props) : NULL;
+
+ if (rd->data->monitoring) {
+ printf("remote %d added global: ", rd->id);
+ print_global(global, NULL);
+ }
+
+ size = pw_map_get_size(&rd->globals);
+ while (id > size)
+ pw_map_insert_at(&rd->globals, size++, NULL);
+ pw_map_insert_at(&rd->globals, id, global);
+
+ /* immediately bind the object always */
+ ret = bind_global(rd, global, &error);
+ if (!ret) {
+ if (rd->data->interactive)
+ fprintf(stderr, "Error: \"%s\"\n", error);
+ free(error);
+ }
+}
+
+static int destroy_global(void *obj, void *data)
+{
+ struct global *global = obj;
+
+ if (global == NULL)
+ return 0;
+
+ if (global->proxy)
+ pw_proxy_destroy(global->proxy);
+ pw_map_insert_at(&global->rd->globals, global->id, NULL);
+ pw_properties_free(global->properties);
+ free(global->type);
+ free(global);
+ return 0;
+}
+
+static void registry_event_global_remove(void *data, uint32_t id)
+{
+ struct remote_data *rd = data;
+ struct global *global;
+
+ global = pw_map_lookup(&rd->globals, id);
+ if (global == NULL) {
+ fprintf(stderr, "remote %d removed unknown global %d\n", rd->id, id);
+ return;
+ }
+
+ if (rd->data->monitoring) {
+ printf("remote %d removed global: ", rd->id);
+ print_global(global, NULL);
+ }
+
+ destroy_global(global, rd);
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+ .global_remove = registry_event_global_remove,
+};
+
+static struct global *find_global(struct remote_data *rd, const char *pattern)
+{
+ uint32_t id;
+ union pw_map_item *item;
+
+ if (spa_atou32(pattern, &id, 0))
+ return pw_map_lookup(&rd->globals, id);
+
+ pw_array_for_each(item, &rd->globals.items) {
+ struct global *g = item->data;
+ if (pw_map_item_is_free(item) || g == NULL)
+ continue;
+ if (global_matches(g, pattern))
+ return g;
+ }
+ return NULL;
+}
+
+static void on_core_error(void *_data, uint32_t id, int seq, int res, const char *message)
+{
+ struct remote_data *rd = _data;
+ struct data *data = rd->data;
+
+ pw_log_error("remote %p: error id:%u seq:%d res:%d (%s): %s", rd,
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_main_loop_quit(data->loop);
+}
+
+static const struct pw_core_events remote_core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .info = on_core_info,
+ .done = on_core_done,
+ .error = on_core_error,
+};
+
+static void on_core_destroy(void *_data)
+{
+ struct remote_data *rd = _data;
+ struct data *data = rd->data;
+
+ spa_list_remove(&rd->link);
+
+ spa_hook_remove(&rd->core_listener);
+ spa_hook_remove(&rd->proxy_core_listener);
+
+ pw_map_remove(&data->vars, rd->id);
+ pw_map_for_each(&rd->globals, destroy_global, rd);
+ pw_map_clear(&rd->globals);
+
+ if (data->current == rd)
+ data->current = NULL;
+ free(rd->name);
+}
+
+static const struct pw_proxy_events proxy_core_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .destroy = on_core_destroy,
+};
+
+static void remote_data_free(struct remote_data *rd)
+{
+ spa_hook_remove(&rd->registry_listener);
+ pw_proxy_destroy((struct pw_proxy*)rd->registry);
+ pw_core_disconnect(rd->core);
+}
+
+static bool do_connect(struct data *data, const char *cmd, char *args, char **error)
+{
+ char *a[1];
+ int n;
+ struct pw_properties *props = NULL;
+ struct pw_core *core;
+ struct remote_data *rd;
+
+ n = args ? pw_split_ip(args, WHITESPACE, 1, a) : 0;
+ if (n == 1) {
+ props = pw_properties_new(PW_KEY_REMOTE_NAME, a[0], NULL);
+ }
+ core = pw_context_connect(data->context, props, sizeof(struct remote_data));
+ if (core == NULL) {
+ *error = spa_aprintf("failed to connect: %m");
+ return false;
+ }
+
+ rd = pw_proxy_get_user_data((struct pw_proxy*)core);
+ rd->core = core;
+ rd->data = data;
+ pw_map_init(&rd->globals, 64, 16);
+ rd->id = pw_map_insert_new(&data->vars, rd);
+ spa_list_append(&data->remotes, &rd->link);
+
+ if (rd->data->interactive)
+ printf("%d = @remote:%p\n", rd->id, rd->core);
+
+ data->current = rd;
+
+ pw_core_add_listener(rd->core,
+ &rd->core_listener,
+ &remote_core_events, rd);
+ pw_proxy_add_listener((struct pw_proxy*)rd->core,
+ &rd->proxy_core_listener,
+ &proxy_core_events, rd);
+ rd->registry = pw_core_get_registry(rd->core, PW_VERSION_REGISTRY, 0);
+ pw_registry_add_listener(rd->registry,
+ &rd->registry_listener,
+ &registry_events, rd);
+ rd->prompt_pending = pw_core_sync(rd->core, 0, 0);
+
+ return true;
+}
+
+static bool do_disconnect(struct data *data, const char *cmd, char *args, char **error)
+{
+ char *a[1];
+ int n;
+ uint32_t idx;
+ struct remote_data *rd = data->current;
+
+ n = pw_split_ip(args, WHITESPACE, 1, a);
+ if (n >= 1) {
+ idx = atoi(a[0]);
+ rd = pw_map_lookup(&data->vars, idx);
+ if (rd == NULL)
+ goto no_remote;
+
+ }
+ if (rd)
+ remote_data_free(rd);
+
+ if (data->current == NULL) {
+ if (spa_list_is_empty(&data->remotes)) {
+ return true;
+ }
+ data->current = spa_list_last(&data->remotes, struct remote_data, link);
+ }
+
+ return true;
+
+ no_remote:
+ *error = spa_aprintf("Remote %d does not exist", idx);
+ return false;
+}
+
+static bool do_list_remotes(struct data *data, const char *cmd, char *args, char **error)
+{
+ struct remote_data *rd;
+
+ spa_list_for_each(rd, &data->remotes, link)
+ printf("\t%d = @remote:%p '%s'\n", rd->id, rd->core, rd->name);
+
+ return true;
+}
+
+static bool do_switch_remote(struct data *data, const char *cmd, char *args, char **error)
+{
+ char *a[1];
+ int n, idx = 0;
+ struct remote_data *rd;
+
+ n = pw_split_ip(args, WHITESPACE, 1, a);
+ if (n == 1)
+ idx = atoi(a[0]);
+
+ rd = pw_map_lookup(&data->vars, idx);
+ if (rd == NULL)
+ goto no_remote;
+
+ spa_list_remove(&rd->link);
+ spa_list_append(&data->remotes, &rd->link);
+ data->current = rd;
+
+ return true;
+
+ no_remote:
+ *error = spa_aprintf("Remote %d does not exist", idx);
+ return false;
+}
+
+#define MARK_CHANGE(f) ((((info)->change_mask & (f))) ? '*' : ' ')
+
+static void info_global(struct proxy_data *pd)
+{
+ struct global *global = pd->global;
+
+ if (global == NULL)
+ return;
+
+ printf("\tid: %d\n", global->id);
+ printf("\tpermissions: "PW_PERMISSION_FORMAT"\n",
+ PW_PERMISSION_ARGS(global->permissions));
+ printf("\ttype: %s/%d\n", global->type, global->version);
+}
+
+static void info_core(struct proxy_data *pd)
+{
+ struct pw_core_info *info = pd->info;
+
+ info_global(pd);
+ printf("\tcookie: %u\n", info->cookie);
+ printf("\tuser-name: \"%s\"\n", info->user_name);
+ printf("\thost-name: \"%s\"\n", info->host_name);
+ printf("\tversion: \"%s\"\n", info->version);
+ printf("\tname: \"%s\"\n", info->name);
+ print_properties(info->props, MARK_CHANGE(PW_CORE_CHANGE_MASK_PROPS), true);
+ info->change_mask = 0;
+}
+
+static void info_module(struct proxy_data *pd)
+{
+ struct pw_module_info *info = pd->info;
+
+ info_global(pd);
+ printf("\tname: \"%s\"\n", info->name);
+ printf("\tfilename: \"%s\"\n", info->filename);
+ printf("\targs: \"%s\"\n", info->args);
+ print_properties(info->props, MARK_CHANGE(PW_MODULE_CHANGE_MASK_PROPS), true);
+ info->change_mask = 0;
+}
+
+static void info_node(struct proxy_data *pd)
+{
+ struct pw_node_info *info = pd->info;
+
+ info_global(pd);
+ printf("%c\tinput ports: %u/%u\n", MARK_CHANGE(PW_NODE_CHANGE_MASK_INPUT_PORTS),
+ info->n_input_ports, info->max_input_ports);
+ printf("%c\toutput ports: %u/%u\n", MARK_CHANGE(PW_NODE_CHANGE_MASK_OUTPUT_PORTS),
+ info->n_output_ports, info->max_output_ports);
+ printf("%c\tstate: \"%s\"", MARK_CHANGE(PW_NODE_CHANGE_MASK_STATE),
+ pw_node_state_as_string(info->state));
+ if (info->state == PW_NODE_STATE_ERROR && info->error)
+ printf(" \"%s\"\n", info->error);
+ else
+ printf("\n");
+ print_properties(info->props, MARK_CHANGE(PW_NODE_CHANGE_MASK_PROPS), true);
+ print_params(info->params, info->n_params, MARK_CHANGE(PW_NODE_CHANGE_MASK_PARAMS), true);
+ info->change_mask = 0;
+}
+
+static void info_port(struct proxy_data *pd)
+{
+ struct pw_port_info *info = pd->info;
+
+ info_global(pd);
+ printf("\tdirection: \"%s\"\n", pw_direction_as_string(info->direction));
+ print_properties(info->props, MARK_CHANGE(PW_PORT_CHANGE_MASK_PROPS), true);
+ print_params(info->params, info->n_params, MARK_CHANGE(PW_PORT_CHANGE_MASK_PARAMS), true);
+ info->change_mask = 0;
+}
+
+static void info_factory(struct proxy_data *pd)
+{
+ struct pw_factory_info *info = pd->info;
+
+ info_global(pd);
+ printf("\tname: \"%s\"\n", info->name);
+ printf("\tobject-type: %s/%d\n", info->type, info->version);
+ print_properties(info->props, MARK_CHANGE(PW_FACTORY_CHANGE_MASK_PROPS), true);
+ info->change_mask = 0;
+}
+
+static void info_client(struct proxy_data *pd)
+{
+ struct pw_client_info *info = pd->info;
+
+ info_global(pd);
+ print_properties(info->props, MARK_CHANGE(PW_CLIENT_CHANGE_MASK_PROPS), true);
+ info->change_mask = 0;
+}
+
+static void info_link(struct proxy_data *pd)
+{
+ struct pw_link_info *info = pd->info;
+
+ info_global(pd);
+ printf("\toutput-node-id: %u\n", info->output_node_id);
+ printf("\toutput-port-id: %u\n", info->output_port_id);
+ printf("\tinput-node-id: %u\n", info->input_node_id);
+ printf("\tinput-port-id: %u\n", info->input_port_id);
+
+ printf("%c\tstate: \"%s\"", MARK_CHANGE(PW_LINK_CHANGE_MASK_STATE),
+ pw_link_state_as_string(info->state));
+ if (info->state == PW_LINK_STATE_ERROR && info->error)
+ printf(" \"%s\"\n", info->error);
+ else
+ printf("\n");
+ printf("%c\tformat:\n", MARK_CHANGE(PW_LINK_CHANGE_MASK_FORMAT));
+ if (info->format)
+ spa_debug_pod(2, NULL, info->format);
+ else
+ printf("\t\tnone\n");
+ print_properties(info->props, MARK_CHANGE(PW_LINK_CHANGE_MASK_PROPS), true);
+ info->change_mask = 0;
+}
+
+static void info_device(struct proxy_data *pd)
+{
+ struct pw_device_info *info = pd->info;
+
+ info_global(pd);
+ print_properties(info->props, MARK_CHANGE(PW_DEVICE_CHANGE_MASK_PROPS), true);
+ print_params(info->params, info->n_params, MARK_CHANGE(PW_DEVICE_CHANGE_MASK_PARAMS), true);
+ info->change_mask = 0;
+}
+
+static void info_session(struct proxy_data *pd)
+{
+ struct pw_session_info *info = pd->info;
+
+ info_global(pd);
+ print_properties(info->props, MARK_CHANGE(0), true);
+ print_params(info->params, info->n_params, MARK_CHANGE(1), true);
+ info->change_mask = 0;
+}
+
+static void info_endpoint(struct proxy_data *pd)
+{
+ struct pw_endpoint_info *info = pd->info;
+ const char *direction;
+
+ info_global(pd);
+ printf("\tname: %s\n", info->name);
+ printf("\tmedia-class: %s\n", info->media_class);
+ switch(info->direction) {
+ case PW_DIRECTION_OUTPUT:
+ direction = "source";
+ break;
+ case PW_DIRECTION_INPUT:
+ direction = "sink";
+ break;
+ default:
+ direction = "invalid";
+ break;
+ }
+ printf("\tdirection: %s\n", direction);
+ printf("\tflags: 0x%x\n", info->flags);
+ printf("%c\tstreams: %u\n", MARK_CHANGE(0), info->n_streams);
+ printf("%c\tsession: %u\n", MARK_CHANGE(1), info->session_id);
+ print_properties(info->props, MARK_CHANGE(2), true);
+ print_params(info->params, info->n_params, MARK_CHANGE(3), true);
+ info->change_mask = 0;
+}
+
+static void info_endpoint_stream(struct proxy_data *pd)
+{
+ struct pw_endpoint_stream_info *info = pd->info;
+
+ info_global(pd);
+ printf("\tid: %u\n", info->id);
+ printf("\tendpoint-id: %u\n", info->endpoint_id);
+ printf("\tname: %s\n", info->name);
+ print_properties(info->props, MARK_CHANGE(1), true);
+ print_params(info->params, info->n_params, MARK_CHANGE(2), true);
+ info->change_mask = 0;
+}
+
+static void core_event_info(void *data, const struct pw_core_info *info)
+{
+ struct proxy_data *pd = data;
+ struct remote_data *rd = pd->rd;
+ if (pd->info && rd->data->monitoring)
+ printf("remote %d core %d changed\n", rd->id, info->id);
+ pd->info = pw_core_info_update(pd->info, info);
+ if (pd->global == NULL)
+ pd->global = pw_map_lookup(&rd->globals, info->id);
+ if (pd->global && pd->global->info_pending) {
+ info_core(pd);
+ pd->global->info_pending = false;
+ }
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .info = core_event_info
+};
+
+
+static void module_event_info(void *data, const struct pw_module_info *info)
+{
+ struct proxy_data *pd = data;
+ struct remote_data *rd = pd->rd;
+ if (pd->info && rd->data->monitoring)
+ printf("remote %d module %d changed\n", rd->id, info->id);
+ pd->info = pw_module_info_update(pd->info, info);
+ if (pd->global == NULL)
+ pd->global = pw_map_lookup(&rd->globals, info->id);
+ if (pd->global && pd->global->info_pending) {
+ info_module(pd);
+ pd->global->info_pending = false;
+ }
+}
+
+static const struct pw_module_events module_events = {
+ PW_VERSION_MODULE_EVENTS,
+ .info = module_event_info
+};
+
+static void node_event_info(void *data, const struct pw_node_info *info)
+{
+ struct proxy_data *pd = data;
+ struct remote_data *rd = pd->rd;
+ if (pd->info && rd->data->monitoring)
+ printf("remote %d node %d changed\n", rd->id, info->id);
+ pd->info = pw_node_info_update(pd->info, info);
+ if (pd->global == NULL)
+ pd->global = pw_map_lookup(&rd->globals, info->id);
+ if (pd->global && pd->global->info_pending) {
+ info_node(pd);
+ pd->global->info_pending = false;
+ }
+}
+
+static void event_param(void *_data, int seq, uint32_t id,
+ uint32_t index, uint32_t next, const struct spa_pod *param)
+{
+ struct proxy_data *data = _data;
+ struct remote_data *rd = data->rd;
+
+ if (rd->data->interactive)
+ printf("remote %d object %d param %d index %d\n",
+ rd->id, data->global->id, id, index);
+
+ spa_debug_pod(2, NULL, param);
+}
+
+static const struct pw_node_events node_events = {
+ PW_VERSION_NODE_EVENTS,
+ .info = node_event_info,
+ .param = event_param
+};
+
+
+static void port_event_info(void *data, const struct pw_port_info *info)
+{
+ struct proxy_data *pd = data;
+ struct remote_data *rd = pd->rd;
+ if (pd->info && rd->data->monitoring)
+ printf("remote %d port %d changed\n", rd->id, info->id);
+ pd->info = pw_port_info_update(pd->info, info);
+ if (pd->global == NULL)
+ pd->global = pw_map_lookup(&rd->globals, info->id);
+ if (pd->global && pd->global->info_pending) {
+ info_port(pd);
+ pd->global->info_pending = false;
+ }
+}
+
+static const struct pw_port_events port_events = {
+ PW_VERSION_PORT_EVENTS,
+ .info = port_event_info,
+ .param = event_param
+};
+
+static void factory_event_info(void *data, const struct pw_factory_info *info)
+{
+ struct proxy_data *pd = data;
+ struct remote_data *rd = pd->rd;
+ if (pd->info && rd->data->monitoring)
+ printf("remote %d factory %d changed\n", rd->id, info->id);
+ pd->info = pw_factory_info_update(pd->info, info);
+ if (pd->global == NULL)
+ pd->global = pw_map_lookup(&rd->globals, info->id);
+ if (pd->global && pd->global->info_pending) {
+ info_factory(pd);
+ pd->global->info_pending = false;
+ }
+}
+
+static const struct pw_factory_events factory_events = {
+ PW_VERSION_FACTORY_EVENTS,
+ .info = factory_event_info
+};
+
+static void client_event_info(void *data, const struct pw_client_info *info)
+{
+ struct proxy_data *pd = data;
+ struct remote_data *rd = pd->rd;
+ if (pd->info && rd->data->monitoring)
+ printf("remote %d client %d changed\n", rd->id, info->id);
+ pd->info = pw_client_info_update(pd->info, info);
+ if (pd->global == NULL)
+ pd->global = pw_map_lookup(&rd->globals, info->id);
+ if (pd->global && pd->global->info_pending) {
+ info_client(pd);
+ pd->global->info_pending = false;
+ }
+}
+
+static void client_event_permissions(void *_data, uint32_t index,
+ uint32_t n_permissions, const struct pw_permission *permissions)
+{
+ struct proxy_data *data = _data;
+ struct remote_data *rd = data->rd;
+ uint32_t i;
+
+ printf("remote %d node %d index %d\n",
+ rd->id, data->global->id, index);
+
+ for (i = 0; i < n_permissions; i++) {
+ if (permissions[i].id == PW_ID_ANY)
+ printf(" default:");
+ else
+ printf(" %u:", permissions[i].id);
+ printf(" "PW_PERMISSION_FORMAT"\n",
+ PW_PERMISSION_ARGS(permissions[i].permissions));
+ }
+}
+
+static const struct pw_client_events client_events = {
+ PW_VERSION_CLIENT_EVENTS,
+ .info = client_event_info,
+ .permissions = client_event_permissions
+};
+
+static void link_event_info(void *data, const struct pw_link_info *info)
+{
+ struct proxy_data *pd = data;
+ struct remote_data *rd = pd->rd;
+ if (pd->info && rd->data->monitoring)
+ printf("remote %d link %d changed\n", rd->id, info->id);
+ pd->info = pw_link_info_update(pd->info, info);
+ if (pd->global == NULL)
+ pd->global = pw_map_lookup(&rd->globals, info->id);
+ if (pd->global && pd->global->info_pending) {
+ info_link(pd);
+ pd->global->info_pending = false;
+ }
+}
+
+static const struct pw_link_events link_events = {
+ PW_VERSION_LINK_EVENTS,
+ .info = link_event_info
+};
+
+
+static void device_event_info(void *data, const struct pw_device_info *info)
+{
+ struct proxy_data *pd = data;
+ struct remote_data *rd = pd->rd;
+ if (pd->info && rd->data->monitoring)
+ printf("remote %d device %d changed\n", rd->id, info->id);
+ pd->info = pw_device_info_update(pd->info, info);
+ if (pd->global == NULL)
+ pd->global = pw_map_lookup(&rd->globals, info->id);
+ if (pd->global && pd->global->info_pending) {
+ info_device(pd);
+ pd->global->info_pending = false;
+ }
+}
+
+static const struct pw_device_events device_events = {
+ PW_VERSION_DEVICE_EVENTS,
+ .info = device_event_info,
+ .param = event_param
+};
+
+static void session_info_free(struct pw_session_info *info)
+{
+ free(info->params);
+ pw_properties_free ((struct pw_properties *)info->props);
+ free(info);
+}
+
+static void session_event_info(void *data,
+ const struct pw_session_info *update)
+{
+ struct proxy_data *pd = data;
+ struct remote_data *rd = pd->rd;
+ struct pw_session_info *info = pd->info;
+
+ if (!info) {
+ info = pd->info = calloc(1, sizeof(*info));
+ info->id = update->id;
+ }
+ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) {
+ info->n_params = update->n_params;
+ free(info->params);
+ info->params = malloc(info->n_params * sizeof(struct spa_param_info));
+ memcpy(info->params, update->params,
+ info->n_params * sizeof(struct spa_param_info));
+ }
+ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) {
+ pw_properties_free ((struct pw_properties *)info->props);
+ info->props =
+ (struct spa_dict *) pw_properties_new_dict (update->props);
+ }
+
+ if (pd->global == NULL)
+ pd->global = pw_map_lookup(&rd->globals, info->id);
+ if (pd->global && pd->global->info_pending) {
+ info_session(pd);
+ pd->global->info_pending = false;
+ }
+}
+
+static const struct pw_session_events session_events = {
+ PW_VERSION_SESSION_EVENTS,
+ .info = session_event_info,
+ .param = event_param
+};
+
+static void endpoint_info_free(struct pw_endpoint_info *info)
+{
+ free(info->name);
+ free(info->media_class);
+ free(info->params);
+ pw_properties_free ((struct pw_properties *)info->props);
+ free(info);
+}
+
+static void endpoint_event_info(void *data,
+ const struct pw_endpoint_info *update)
+{
+ struct proxy_data *pd = data;
+ struct remote_data *rd = pd->rd;
+ struct pw_endpoint_info *info = pd->info;
+
+ if (!info) {
+ info = pd->info = calloc(1, sizeof(*info));
+ info->id = update->id;
+ info->name = update->name ? strdup(update->name) : NULL;
+ info->media_class = update->media_class ? strdup(update->media_class) : NULL;
+ info->direction = update->direction;
+ info->flags = update->flags;
+ }
+ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_STREAMS)
+ info->n_streams = update->n_streams;
+ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_SESSION)
+ info->session_id = update->session_id;
+ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PARAMS) {
+ info->n_params = update->n_params;
+ free(info->params);
+ info->params = malloc(info->n_params * sizeof(struct spa_param_info));
+ memcpy(info->params, update->params,
+ info->n_params * sizeof(struct spa_param_info));
+ }
+ if (update->change_mask & PW_ENDPOINT_CHANGE_MASK_PROPS) {
+ pw_properties_free ((struct pw_properties *)info->props);
+ info->props =
+ (struct spa_dict *) pw_properties_new_dict (update->props);
+ }
+
+ if (pd->global == NULL)
+ pd->global = pw_map_lookup(&rd->globals, info->id);
+ if (pd->global && pd->global->info_pending) {
+ info_endpoint(pd);
+ pd->global->info_pending = false;
+ }
+}
+
+static const struct pw_endpoint_events endpoint_events = {
+ PW_VERSION_ENDPOINT_EVENTS,
+ .info = endpoint_event_info,
+ .param = event_param
+};
+
+static void endpoint_stream_info_free(struct pw_endpoint_stream_info *info)
+{
+ free(info->name);
+ free(info->params);
+ pw_properties_free ((struct pw_properties *)info->props);
+ free(info);
+}
+
+static void endpoint_stream_event_info(void *data,
+ const struct pw_endpoint_stream_info *update)
+{
+ struct proxy_data *pd = data;
+ struct remote_data *rd = pd->rd;
+ struct pw_endpoint_stream_info *info = pd->info;
+
+ if (!info) {
+ info = pd->info = calloc(1, sizeof(*info));
+ info->id = update->id;
+ info->endpoint_id = update->endpoint_id;
+ info->name = update->name ? strdup(update->name) : NULL;
+ }
+ if (update->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PARAMS) {
+ info->n_params = update->n_params;
+ free(info->params);
+ info->params = malloc(info->n_params * sizeof(struct spa_param_info));
+ memcpy(info->params, update->params,
+ info->n_params * sizeof(struct spa_param_info));
+ }
+ if (update->change_mask & PW_ENDPOINT_STREAM_CHANGE_MASK_PROPS) {
+ pw_properties_free ((struct pw_properties *)info->props);
+ info->props =
+ (struct spa_dict *) pw_properties_new_dict (update->props);
+ }
+
+ if (pd->global == NULL)
+ pd->global = pw_map_lookup(&rd->globals, info->id);
+ if (pd->global && pd->global->info_pending) {
+ info_endpoint_stream(pd);
+ pd->global->info_pending = false;
+ }
+}
+
+static const struct pw_endpoint_stream_events endpoint_stream_events = {
+ PW_VERSION_ENDPOINT_STREAM_EVENTS,
+ .info = endpoint_stream_event_info,
+ .param = event_param
+};
+
+static void
+removed_proxy (void *data)
+{
+ struct proxy_data *pd = data;
+ pw_proxy_destroy(pd->proxy);
+}
+
+static void
+destroy_proxy (void *data)
+{
+ struct proxy_data *pd = data;
+
+ spa_hook_remove(&pd->proxy_listener);
+ spa_hook_remove(&pd->object_listener);
+
+ if (pd->global)
+ pd->global->proxy = NULL;
+
+ if (pd->info == NULL)
+ return;
+
+ if (pd->class->destroy)
+ pd->class->destroy(pd->info);
+ pd->info = NULL;
+}
+
+static const struct pw_proxy_events proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .removed = removed_proxy,
+ .destroy = destroy_proxy,
+};
+
+static bool do_list_objects(struct data *data, const char *cmd, char *args, char **error)
+{
+ struct remote_data *rd = data->current;
+ pw_map_for_each(&rd->globals, print_global, args);
+ return true;
+}
+
+static const struct class core_class = {
+ .type = PW_TYPE_INTERFACE_Core,
+ .version = PW_VERSION_CORE,
+ .events = &core_events,
+ .destroy = (pw_destroy_t) pw_core_info_free,
+ .info = info_core,
+ .name_key = PW_KEY_CORE_NAME,
+};
+static const struct class module_class = {
+ .type = PW_TYPE_INTERFACE_Module,
+ .version = PW_VERSION_MODULE,
+ .events = &module_events,
+ .destroy = (pw_destroy_t) pw_module_info_free,
+ .info = info_module,
+ .name_key = PW_KEY_MODULE_NAME,
+};
+
+static const struct class factory_class = {
+ .type = PW_TYPE_INTERFACE_Factory,
+ .version = PW_VERSION_FACTORY,
+ .events = &factory_events,
+ .destroy = (pw_destroy_t) pw_factory_info_free,
+ .info = info_factory,
+ .name_key = PW_KEY_FACTORY_NAME,
+};
+
+static const struct class client_class = {
+ .type = PW_TYPE_INTERFACE_Client,
+ .version = PW_VERSION_CLIENT,
+ .events = &client_events,
+ .destroy = (pw_destroy_t) pw_client_info_free,
+ .info = info_client,
+ .name_key = PW_KEY_APP_NAME,
+};
+static const struct class device_class = {
+ .type = PW_TYPE_INTERFACE_Device,
+ .version = PW_VERSION_DEVICE,
+ .events = &device_events,
+ .destroy = (pw_destroy_t) pw_device_info_free,
+ .info = info_device,
+ .name_key = PW_KEY_DEVICE_NAME,
+};
+static const struct class node_class = {
+ .type = PW_TYPE_INTERFACE_Node,
+ .version = PW_VERSION_NODE,
+ .events = &node_events,
+ .destroy = (pw_destroy_t) pw_node_info_free,
+ .info = info_node,
+ .name_key = PW_KEY_NODE_NAME,
+};
+static const struct class port_class = {
+ .type = PW_TYPE_INTERFACE_Port,
+ .version = PW_VERSION_PORT,
+ .events = &port_events,
+ .destroy = (pw_destroy_t) pw_port_info_free,
+ .info = info_port,
+ .name_key = PW_KEY_PORT_NAME,
+};
+static const struct class link_class = {
+ .type = PW_TYPE_INTERFACE_Link,
+ .version = PW_VERSION_LINK,
+ .events = &link_events,
+ .destroy = (pw_destroy_t) pw_link_info_free,
+ .info = info_link,
+};
+static const struct class session_class = {
+ .type = PW_TYPE_INTERFACE_Session,
+ .version = PW_VERSION_SESSION,
+ .events = &session_events,
+ .destroy = (pw_destroy_t) session_info_free,
+ .info = info_session,
+};
+static const struct class endpoint_class = {
+ .type = PW_TYPE_INTERFACE_Endpoint,
+ .version = PW_VERSION_ENDPOINT,
+ .events = &endpoint_events,
+ .destroy = (pw_destroy_t) endpoint_info_free,
+ .info = info_endpoint,
+};
+static const struct class endpoint_stream_class = {
+ .type = PW_TYPE_INTERFACE_EndpointStream,
+ .version = PW_VERSION_ENDPOINT_STREAM,
+ .events = &endpoint_stream_events,
+ .destroy = (pw_destroy_t) endpoint_stream_info_free,
+ .info = info_endpoint_stream,
+};
+static const struct class metadata_class = {
+ .type = PW_TYPE_INTERFACE_Metadata,
+ .version = PW_VERSION_METADATA,
+ .name_key = PW_KEY_METADATA_NAME,
+};
+
+static const struct class *classes[] =
+{
+ &core_class,
+ &module_class,
+ &factory_class,
+ &client_class,
+ &device_class,
+ &node_class,
+ &port_class,
+ &link_class,
+ &session_class,
+ &endpoint_class,
+ &endpoint_stream_class,
+ &metadata_class,
+};
+
+static const struct class *find_class(const char *type, uint32_t version)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(classes, c) {
+ if (spa_streq((*c)->type, type) &&
+ (*c)->version <= version)
+ return *c;
+ }
+ return NULL;
+}
+
+static bool bind_global(struct remote_data *rd, struct global *global, char **error)
+{
+ const struct class *class;
+ struct proxy_data *pd;
+ struct pw_proxy *proxy;
+
+ class = find_class(global->type, global->version);
+ if (class == NULL) {
+ *error = spa_aprintf("unsupported type %s", global->type);
+ return false;
+ }
+ global->class = class;
+
+ proxy = pw_registry_bind(rd->registry,
+ global->id,
+ global->type,
+ class->version,
+ sizeof(struct proxy_data));
+
+ pd = pw_proxy_get_user_data(proxy);
+ pd->rd = rd;
+ pd->global = global;
+ pd->proxy = proxy;
+ pd->class = class;
+ pw_proxy_add_object_listener(proxy, &pd->object_listener, class->events, pd);
+ pw_proxy_add_listener(proxy, &pd->proxy_listener, &proxy_events, pd);
+
+ global->proxy = proxy;
+
+ rd->prompt_pending = pw_core_sync(rd->core, 0, 0);
+
+ return true;
+}
+
+static bool do_global_info(struct global *global, char **error)
+{
+ struct remote_data *rd = global->rd;
+ struct proxy_data *pd;
+
+ if (global->proxy == NULL) {
+ if (!bind_global(rd, global, error))
+ return false;
+ global->info_pending = true;
+ } else {
+ pd = pw_proxy_get_user_data(global->proxy);
+ if (pd->class->info)
+ pd->class->info(pd);
+ }
+ return true;
+}
+static int do_global_info_all(void *obj, void *data)
+{
+ struct global *global = obj;
+ char *error;
+
+ if (global == NULL)
+ return 0;
+
+ if (!do_global_info(global, &error)) {
+ fprintf(stderr, "info: %s\n", error);
+ free(error);
+ }
+ return 0;
+}
+
+static bool do_info(struct data *data, const char *cmd, char *args, char **error)
+{
+ struct remote_data *rd = data->current;
+ char *a[1];
+ int n;
+ struct global *global;
+
+ n = pw_split_ip(args, WHITESPACE, 1, a);
+ if (n < 1) {
+ *error = spa_aprintf("%s <object-id>|all", cmd);
+ return false;
+ }
+ if (spa_streq(a[0], "all")) {
+ pw_map_for_each(&rd->globals, do_global_info_all, NULL);
+ }
+ else {
+ global = find_global(rd, a[0]);
+ if (global == NULL) {
+ *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]);
+ return false;
+ }
+ return do_global_info(global, error);
+ }
+ return true;
+}
+
+static bool do_create_device(struct data *data, const char *cmd, char *args, char **error)
+{
+ struct remote_data *rd = data->current;
+ char *a[2];
+ int n;
+ uint32_t id;
+ struct pw_proxy *proxy;
+ struct pw_properties *props = NULL;
+ struct proxy_data *pd;
+
+ n = pw_split_ip(args, WHITESPACE, 2, a);
+ if (n < 1) {
+ *error = spa_aprintf("%s <factory-name> [<properties>]", cmd);
+ return false;
+ }
+ if (n == 2)
+ props = pw_properties_new_string(a[1]);
+
+ proxy = pw_core_create_object(rd->core, a[0],
+ PW_TYPE_INTERFACE_Device,
+ PW_VERSION_DEVICE,
+ props ? &props->dict : NULL,
+ sizeof(struct proxy_data));
+
+ pw_properties_free(props);
+
+ pd = pw_proxy_get_user_data(proxy);
+ pd->rd = rd;
+ pd->proxy = proxy;
+ pd->class = &device_class;
+ pw_proxy_add_object_listener(proxy, &pd->object_listener, &device_events, pd);
+ pw_proxy_add_listener(proxy, &pd->proxy_listener, &proxy_events, pd);
+
+ id = pw_map_insert_new(&data->vars, proxy);
+ if (rd->data->interactive)
+ printf("%d = @proxy:%d\n", id, pw_proxy_get_id(proxy));
+
+ return true;
+}
+
+static bool do_create_node(struct data *data, const char *cmd, char *args, char **error)
+{
+ struct remote_data *rd = data->current;
+ char *a[2];
+ int n;
+ uint32_t id;
+ struct pw_proxy *proxy;
+ struct pw_properties *props = NULL;
+ struct proxy_data *pd;
+
+ n = pw_split_ip(args, WHITESPACE, 2, a);
+ if (n < 1) {
+ *error = spa_aprintf("%s <factory-name> [<properties>]", cmd);
+ return false;
+ }
+ if (n == 2)
+ props = pw_properties_new_string(a[1]);
+
+ proxy = pw_core_create_object(rd->core, a[0],
+ PW_TYPE_INTERFACE_Node,
+ PW_VERSION_NODE,
+ props ? &props->dict : NULL,
+ sizeof(struct proxy_data));
+
+ pw_properties_free(props);
+
+ pd = pw_proxy_get_user_data(proxy);
+ pd->rd = rd;
+ pd->proxy = proxy;
+ pd->class = &node_class;
+ pw_proxy_add_object_listener(proxy, &pd->object_listener, &node_events, pd);
+ pw_proxy_add_listener(proxy, &pd->proxy_listener, &proxy_events, pd);
+
+ id = pw_map_insert_new(&data->vars, proxy);
+ if (rd->data->interactive)
+ printf("%d = @proxy:%d\n", id, pw_proxy_get_id(proxy));
+
+ return true;
+}
+
+static bool do_destroy(struct data *data, const char *cmd, char *args, char **error)
+{
+ struct remote_data *rd = data->current;
+ char *a[1];
+ int n;
+ struct global *global;
+
+ n = pw_split_ip(args, WHITESPACE, 1, a);
+ if (n < 1) {
+ *error = spa_aprintf("%s <object-id>", cmd);
+ return false;
+ }
+ global = find_global(rd, a[0]);
+ if (global == NULL) {
+ *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]);
+ return false;
+ }
+ pw_registry_destroy(rd->registry, global->id);
+
+ return true;
+}
+
+static struct global *
+obj_global_port(struct remote_data *rd, struct global *global, const char *port_direction, const char *port_id)
+{
+ struct global *global_port_found = NULL;
+ uint32_t *ports = NULL;
+ int port_count;
+
+ port_count = children_of(rd, global->id, PW_TYPE_INTERFACE_Port, &ports);
+
+ if (port_count <= 0)
+ return NULL;
+
+ for (int i = 0; i < port_count; i++) {
+ struct global *global_port = obj_global(rd, ports[i]);
+
+ if (!global_port)
+ continue;
+
+ struct spa_dict *props_port = global_props(global_port);
+
+ if (spa_streq(spa_dict_lookup(props_port, "port.direction"), port_direction)
+ && spa_streq(spa_dict_lookup(props_port, "port.id"), port_id)) {
+ global_port_found = global_port;
+ break;
+ }
+ }
+
+ free(ports);
+ return global_port_found;
+}
+
+static void create_link_with_properties(struct data *data, struct pw_properties *props)
+{
+ struct remote_data *rd = data->current;
+ uint32_t id;
+ struct pw_proxy *proxy;
+ struct proxy_data *pd;
+
+ proxy = (struct pw_proxy*)pw_core_create_object(rd->core,
+ "link-factory",
+ PW_TYPE_INTERFACE_Link,
+ PW_VERSION_LINK,
+ props ? &props->dict : NULL,
+ sizeof(struct proxy_data));
+
+ pd = pw_proxy_get_user_data(proxy);
+ pd->rd = rd;
+ pd->proxy = proxy;
+ pd->class = &link_class;
+ pw_proxy_add_object_listener(proxy, &pd->object_listener, &link_events, pd);
+ pw_proxy_add_listener(proxy, &pd->proxy_listener, &proxy_events, pd);
+
+ id = pw_map_insert_new(&data->vars, proxy);
+ if (rd->data->interactive)
+ printf("%d = @proxy:%d\n", id, pw_proxy_get_id((struct pw_proxy*)proxy));
+}
+
+static bool do_create_link(struct data *data, const char *cmd, char *args, char **error)
+{
+ struct remote_data *rd = data->current;
+ char *a[5];
+ int n;
+ struct pw_properties *props = NULL;
+
+ n = pw_split_ip(args, WHITESPACE, 5, a);
+ if (n < 4) {
+ *error = spa_aprintf("%s <node-id> <port> <node-id> <port> [<properties>]", cmd);
+ return false;
+ }
+ if (n == 5)
+ props = pw_properties_new_string(a[4]);
+ else
+ props = pw_properties_new(NULL, NULL);
+
+ if (!spa_streq(a[0], "-"))
+ pw_properties_set(props, PW_KEY_LINK_OUTPUT_NODE, a[0]);
+ if (!spa_streq(a[1], "-"))
+ pw_properties_set(props, PW_KEY_LINK_OUTPUT_PORT, a[1]);
+ if (!spa_streq(a[2], "-"))
+ pw_properties_set(props, PW_KEY_LINK_INPUT_NODE, a[2]);
+ if (!spa_streq(a[3], "-"))
+ pw_properties_set(props, PW_KEY_LINK_INPUT_PORT, a[3]);
+
+ if (spa_streq(a[1], "*") && spa_streq(a[3], "*")) {
+ struct global *global_out, *global_in;
+ struct proxy_data *pd_out, *pd_in;
+ uint32_t n_output_ports, n_input_ports;
+
+ global_out = find_global(rd, a[0]);
+ if (global_out == NULL) {
+ *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]);
+ return false;
+ }
+ global_in = find_global(rd, a[2]);
+ if (global_in == NULL) {
+ *error = spa_aprintf("%s: unknown global '%s'", cmd, a[2]);
+ return false;
+ }
+
+ pd_out = pw_proxy_get_user_data(global_out->proxy);
+ pd_in = pw_proxy_get_user_data(global_in->proxy);
+
+ n_output_ports = ((struct pw_node_info *)pd_out->info)->n_output_ports;
+ n_input_ports = ((struct pw_node_info *)pd_in->info)->n_input_ports;
+
+ if (n_output_ports != n_input_ports) {
+ *error = spa_aprintf("%s: Number of ports don't match (%u != %u)", cmd, n_output_ports, n_input_ports);
+ return false;
+ }
+
+ for (uint32_t i = 0; i < n_output_ports; i++) {
+ char port_id[4];
+ struct global *global_port_out, *global_port_in;
+
+ snprintf(port_id, 4, "%d", i);
+
+ global_port_out = obj_global_port(rd, global_out, "out", port_id);
+ global_port_in = obj_global_port(rd, global_in, "in", port_id);
+
+ if (!global_port_out || !global_port_in)
+ continue;
+
+ pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%d", global_port_out->id);
+ pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%d", global_port_in->id);
+
+ create_link_with_properties(data, props);
+ }
+ } else
+ create_link_with_properties(data, props);
+
+ pw_properties_free(props);
+
+ return true;
+}
+
+static bool do_export_node(struct data *data, const char *cmd, char *args, char **error)
+{
+ struct remote_data *rd = data->current;
+ struct pw_global *global;
+ struct pw_node *node;
+ struct pw_proxy *proxy;
+ char *a[2];
+ int n, idx;
+ uint32_t id;
+
+ n = pw_split_ip(args, WHITESPACE, 2, a);
+ if (n < 1) {
+ *error = spa_aprintf("%s <node-id> [<remote-var>]", cmd);
+ return false;
+ }
+ if (n == 2) {
+ idx = atoi(a[1]);
+ rd = pw_map_lookup(&data->vars, idx);
+ if (rd == NULL)
+ goto no_remote;
+ }
+
+ global = pw_context_find_global(data->context, atoi(a[0]));
+ if (global == NULL) {
+ *error = spa_aprintf("object %d does not exist", atoi(a[0]));
+ return false;
+ }
+ if (!pw_global_is_type(global, PW_TYPE_INTERFACE_Node)) {
+ *error = spa_aprintf("object %d is not a node", atoi(a[0]));
+ return false;
+ }
+ node = pw_global_get_object(global);
+ proxy = pw_core_export(rd->core, PW_TYPE_INTERFACE_Node, NULL, node, 0);
+
+ id = pw_map_insert_new(&data->vars, proxy);
+ if (rd->data->interactive)
+ printf("%d = @proxy:%d\n", id, pw_proxy_get_id((struct pw_proxy*)proxy));
+
+ return true;
+
+ no_remote:
+ *error = spa_aprintf("Remote %d does not exist", idx);
+ return false;
+}
+
+static bool do_enum_params(struct data *data, const char *cmd, char *args, char **error)
+{
+ struct remote_data *rd = data->current;
+ char *a[2];
+ int n;
+ uint32_t param_id;
+ const struct spa_type_info *ti;
+ struct global *global;
+
+ n = pw_split_ip(args, WHITESPACE, 2, a);
+ if (n < 2) {
+ *error = spa_aprintf("%s <object-id> <param-id>", cmd);
+ return false;
+ }
+
+ ti = spa_debug_type_find_short(spa_type_param, a[1]);
+ if (ti == NULL) {
+ *error = spa_aprintf("%s: unknown param type: %s", cmd, a[1]);
+ return false;
+ }
+ param_id = ti->type;
+
+ global = find_global(rd, a[0]);
+ if (global == NULL) {
+ *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]);
+ return false;
+ }
+ if (global->proxy == NULL) {
+ if (!bind_global(rd, global, error))
+ return false;
+ }
+
+ if (spa_streq(global->type, PW_TYPE_INTERFACE_Node))
+ pw_node_enum_params((struct pw_node*)global->proxy, 0,
+ param_id, 0, 0, NULL);
+ else if (spa_streq(global->type, PW_TYPE_INTERFACE_Port))
+ pw_port_enum_params((struct pw_port*)global->proxy, 0,
+ param_id, 0, 0, NULL);
+ else if (spa_streq(global->type, PW_TYPE_INTERFACE_Device))
+ pw_device_enum_params((struct pw_device*)global->proxy, 0,
+ param_id, 0, 0, NULL);
+ else if (spa_streq(global->type, PW_TYPE_INTERFACE_Endpoint))
+ pw_endpoint_enum_params((struct pw_endpoint*)global->proxy, 0,
+ param_id, 0, 0, NULL);
+ else {
+ *error = spa_aprintf("enum-params not implemented on object %d type:%s",
+ atoi(a[0]), global->type);
+ return false;
+ }
+ return true;
+}
+
+static bool do_set_param(struct data *data, const char *cmd, char *args, char **error)
+{
+ struct remote_data *rd = data->current;
+ char *a[3];
+ int res, n;
+ uint32_t param_id;
+ struct global *global;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ const struct spa_type_info *ti;
+ struct spa_pod *pod;
+
+ n = pw_split_ip(args, WHITESPACE, 3, a);
+ if (n < 3) {
+ *error = spa_aprintf("%s <object-id> <param-id> <param-json>", cmd);
+ return false;
+ }
+
+ global = find_global(rd, a[0]);
+ if (global == NULL) {
+ *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]);
+ return false;
+ }
+ if (global->proxy == NULL) {
+ if (!bind_global(rd, global, error))
+ return false;
+ }
+
+ ti = spa_debug_type_find_short(spa_type_param, a[1]);
+ if (ti == NULL) {
+ *error = spa_aprintf("%s: unknown param type: %s", cmd, a[1]);
+ return false;
+ }
+ if ((res = spa_json_to_pod(&b, 0, ti, a[2], strlen(a[2]))) < 0) {
+ *error = spa_aprintf("%s: can't make pod: %s", cmd, spa_strerror(res));
+ return false;
+ }
+ if ((pod = spa_pod_builder_deref(&b, 0)) == NULL) {
+ *error = spa_aprintf("%s: can't make pod", cmd);
+ return false;
+ }
+ spa_debug_pod(0, NULL, pod);
+
+ param_id = ti->type;
+
+ if (spa_streq(global->type, PW_TYPE_INTERFACE_Node))
+ pw_node_set_param((struct pw_node*)global->proxy,
+ param_id, 0, pod);
+ else if (spa_streq(global->type, PW_TYPE_INTERFACE_Device))
+ pw_device_set_param((struct pw_device*)global->proxy,
+ param_id, 0, pod);
+ else if (spa_streq(global->type, PW_TYPE_INTERFACE_Endpoint))
+ pw_endpoint_set_param((struct pw_endpoint*)global->proxy,
+ param_id, 0, pod);
+ else {
+ *error = spa_aprintf("set-param not implemented on object %d type:%s",
+ atoi(a[0]), global->type);
+ return false;
+ }
+ return true;
+}
+
+static bool do_permissions(struct data *data, const char *cmd, char *args, char **error)
+{
+ struct remote_data *rd = data->current;
+ char *a[3];
+ int n;
+ uint32_t p;
+ struct global *global;
+ struct pw_permission permissions[1];
+
+ n = pw_split_ip(args, WHITESPACE, 3, a);
+ if (n < 3) {
+ *error = spa_aprintf("%s <client-id> <object> <permission>", cmd);
+ return false;
+ }
+
+ global = find_global(rd, a[0]);
+ if (global == NULL) {
+ *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]);
+ return false;
+ }
+ if (!spa_streq(global->type, PW_TYPE_INTERFACE_Client)) {
+ *error = spa_aprintf("object %d is not a client", atoi(a[0]));
+ return false;
+ }
+ if (global->proxy == NULL) {
+ if (!bind_global(rd, global, error))
+ return false;
+ }
+
+ p = strtol(a[2], NULL, 0);
+ if (rd->data->interactive)
+ printf("setting permissions: "PW_PERMISSION_FORMAT"\n",
+ PW_PERMISSION_ARGS(p));
+
+ permissions[0] = PW_PERMISSION_INIT(atoi(a[1]), p);
+ pw_client_update_permissions((struct pw_client*)global->proxy,
+ 1, permissions);
+
+ return true;
+}
+
+static bool do_get_permissions(struct data *data, const char *cmd, char *args, char **error)
+{
+ struct remote_data *rd = data->current;
+ char *a[3];
+ int n;
+ struct global *global;
+
+ n = pw_split_ip(args, WHITESPACE, 1, a);
+ if (n < 1) {
+ *error = spa_aprintf("%s <client-id>", cmd);
+ return false;
+ }
+
+ global = find_global(rd, a[0]);
+ if (global == NULL) {
+ *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]);
+ return false;
+ }
+ if (!spa_streq(global->type, PW_TYPE_INTERFACE_Client)) {
+ *error = spa_aprintf("object %d is not a client", atoi(a[0]));
+ return false;
+ }
+ if (global->proxy == NULL) {
+ if (!bind_global(rd, global, error))
+ return false;
+ }
+ pw_client_get_permissions((struct pw_client*)global->proxy,
+ 0, UINT32_MAX);
+
+ return true;
+}
+
+static bool do_send_command(struct data *data, const char *cmd, char *args, char **error)
+{
+ struct remote_data *rd = data->current;
+ char *a[3];
+ int res, n;
+ struct global *global;
+ uint8_t buffer[1024];
+ struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
+ const struct spa_type_info *ti;
+ struct spa_pod *pod;
+
+ n = pw_split_ip(args, WHITESPACE, 3, a);
+ if (n < 3) {
+ *error = spa_aprintf("%s <object-id> <command-id> <command-json>", cmd);
+ return false;
+ }
+
+ global = find_global(rd, a[0]);
+ if (global == NULL) {
+ *error = spa_aprintf("%s: unknown global '%s'", cmd, a[0]);
+ return false;
+ }
+ if (global->proxy == NULL) {
+ if (!bind_global(rd, global, error))
+ return false;
+ }
+
+ if (spa_streq(global->type, PW_TYPE_INTERFACE_Node)) {
+ ti = spa_debug_type_find_short(spa_type_node_command_id, a[1]);
+ } else {
+ *error = spa_aprintf("send-command not implemented on object %d type:%s",
+ atoi(a[0]), global->type);
+ return false;
+ }
+
+ if (ti == NULL) {
+ *error = spa_aprintf("%s: unknown node command type: %s", cmd, a[1]);
+ return false;
+ }
+ if ((res = spa_json_to_pod(&b, 0, ti, a[2], strlen(a[2]))) < 0) {
+ *error = spa_aprintf("%s: can't make pod: %s", cmd, spa_strerror(res));
+ return false;
+ }
+ if ((pod = spa_pod_builder_deref(&b, 0)) == NULL) {
+ *error = spa_aprintf("%s: can't make pod", cmd);
+ return false;
+ }
+ spa_debug_pod(0, NULL, pod);
+
+ pw_node_send_command((struct pw_node*)global->proxy, (struct spa_command*)pod);
+ return true;
+}
+
+static struct global *
+obj_global(struct remote_data *rd, uint32_t id)
+{
+ struct global *global;
+ struct proxy_data *pd;
+
+ if (!rd)
+ return NULL;
+
+ global = pw_map_lookup(&rd->globals, id);
+ if (!global)
+ return NULL;
+
+ pd = pw_proxy_get_user_data(global->proxy);
+ if (!pd || !pd->info)
+ return NULL;
+
+ return global;
+}
+
+static struct spa_dict *
+global_props(struct global *global)
+{
+ struct proxy_data *pd;
+
+ if (!global)
+ return NULL;
+
+ pd = pw_proxy_get_user_data(global->proxy);
+ if (!pd || !pd->info)
+ return NULL;
+
+ if (spa_streq(global->type, PW_TYPE_INTERFACE_Core))
+ return ((struct pw_core_info *)pd->info)->props;
+ if (spa_streq(global->type, PW_TYPE_INTERFACE_Module))
+ return ((struct pw_module_info *)pd->info)->props;
+ if (spa_streq(global->type, PW_TYPE_INTERFACE_Device))
+ return ((struct pw_device_info *)pd->info)->props;
+ if (spa_streq(global->type, PW_TYPE_INTERFACE_Node))
+ return ((struct pw_node_info *)pd->info)->props;
+ if (spa_streq(global->type, PW_TYPE_INTERFACE_Port))
+ return ((struct pw_port_info *)pd->info)->props;
+ if (spa_streq(global->type, PW_TYPE_INTERFACE_Factory))
+ return ((struct pw_factory_info *)pd->info)->props;
+ if (spa_streq(global->type, PW_TYPE_INTERFACE_Client))
+ return ((struct pw_client_info *)pd->info)->props;
+ if (spa_streq(global->type, PW_TYPE_INTERFACE_Link))
+ return ((struct pw_link_info *)pd->info)->props;
+ if (spa_streq(global->type, PW_TYPE_INTERFACE_Session))
+ return ((struct pw_session_info *)pd->info)->props;
+ if (spa_streq(global->type, PW_TYPE_INTERFACE_Endpoint))
+ return ((struct pw_endpoint_info *)pd->info)->props;
+ if (spa_streq(global->type, PW_TYPE_INTERFACE_EndpointStream))
+ return ((struct pw_endpoint_stream_info *)pd->info)->props;
+
+ return NULL;
+}
+
+static const char *
+global_lookup(struct global *global, const char *key)
+{
+ struct spa_dict *dict;
+
+ dict = global_props(global);
+ if (!dict)
+ return NULL;
+ return spa_dict_lookup(dict, key);
+}
+
+
+static int
+children_of(struct remote_data *rd, uint32_t parent_id,
+ const char *child_type, uint32_t **children)
+{
+ const char *parent_type;
+ union pw_map_item *item;
+ struct global *global;
+ struct proxy_data *pd;
+ const char *parent_key = NULL, *child_key = NULL;
+ const char *parent_value = NULL, *child_value = NULL;
+ int pass, i, count;
+
+ if (!rd || !children)
+ return -1;
+
+ /* get the device info */
+ global = obj_global(rd, parent_id);
+ if (!global)
+ return -1;
+ parent_type = global->type;
+ pd = pw_proxy_get_user_data(global->proxy);
+ if (!pd || !pd->info)
+ return -1;
+
+ /* supported combinations */
+ if (spa_streq(parent_type, PW_TYPE_INTERFACE_Device) &&
+ spa_streq(child_type, PW_TYPE_INTERFACE_Node)) {
+ parent_key = PW_KEY_OBJECT_ID;
+ child_key = PW_KEY_DEVICE_ID;
+ } else if (spa_streq(parent_type, PW_TYPE_INTERFACE_Node) &&
+ spa_streq(child_type, PW_TYPE_INTERFACE_Port)) {
+ parent_key = PW_KEY_OBJECT_ID;
+ child_key = PW_KEY_NODE_ID;
+ } else if (spa_streq(parent_type, PW_TYPE_INTERFACE_Module) &&
+ spa_streq(child_type, PW_TYPE_INTERFACE_Factory)) {
+ parent_key = PW_KEY_OBJECT_ID;
+ child_key = PW_KEY_MODULE_ID;
+ } else if (spa_streq(parent_type, PW_TYPE_INTERFACE_Factory) &&
+ spa_streq(child_type, PW_TYPE_INTERFACE_Device)) {
+ parent_key = PW_KEY_OBJECT_ID;
+ child_key = PW_KEY_FACTORY_ID;
+ } else
+ return -1;
+
+ /* get the parent key value */
+ if (parent_key) {
+ parent_value = global_lookup(global, parent_key);
+ if (!parent_value)
+ return -1;
+ }
+
+ count = 0;
+ *children = NULL;
+ i = 0;
+ for (pass = 1; pass <= 2; pass++) {
+ if (pass == 2) {
+ count = i;
+ if (!count)
+ return 0;
+
+ *children = malloc(sizeof(uint32_t) * count);
+ if (!*children)
+ return -1;
+ }
+ i = 0;
+ pw_array_for_each(item, &rd->globals.items) {
+ if (pw_map_item_is_free(item) || item->data == NULL)
+ continue;
+
+ global = item->data;
+
+ if (!spa_streq(global->type, child_type))
+ continue;
+
+ pd = pw_proxy_get_user_data(global->proxy);
+ if (!pd || !pd->info)
+ return -1;
+
+ if (child_key) {
+ /* get the device path */
+ child_value = global_lookup(global, child_key);
+ if (!child_value)
+ continue;
+ }
+
+ /* match? */
+ if (!spa_streq(parent_value, child_value))
+ continue;
+
+ if (*children)
+ (*children)[i] = global->id;
+ i++;
+
+ }
+ }
+ 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];
+ int n;
+ char *p, *cmd, *args;
+
+ if ((p = strchr(buf, '#')))
+ *p = '\0';
+
+ p = pw_strip(buf, "\n\r \t");
+
+ if (*p == '\0')
+ return true;
+
+ n = pw_split_ip(p, WHITESPACE, 2, a);
+ if (n < 1)
+ return true;
+
+ cmd = a[0];
+ args = n > 1 ? a[1] : "";
+
+ SPA_FOR_EACH_ELEMENT_VAR(command_list, c) {
+ if (spa_streq(c->name, cmd) ||
+ spa_streq(c->alias, cmd)) {
+ return c->func(data, cmd, args, error);
+ }
+ }
+ *error = spa_aprintf("Command \"%s\" does not exist. Type 'help' for usage.", cmd);
+ return false;
+}
+
+/* We need a global variable, readline doesn't have a closure arg */
+static struct data *input_dataptr;
+
+static void input_process_line(char *line)
+{
+ struct data *d = input_dataptr;
+ char *error;
+
+ if (!line)
+ line = strdup("quit");
+
+ if (line[0] != '\0') {
+#ifdef HAVE_READLINE
+ add_history(line);
+#endif
+ if (!parse(d, line, &error)) {
+ fprintf(stderr, "Error: \"%s\"\n", error);
+ free(error);
+ }
+ }
+ free(line);
+}
+
+static void do_input(void *data, int fd, uint32_t mask)
+{
+ struct data *d = data;
+
+ if (mask & SPA_IO_IN) {
+ input_dataptr = d;
+#ifdef HAVE_READLINE
+ rl_callback_read_char();
+#else
+ {
+ char *line = NULL;
+ size_t s = 0;
+
+ if (getline(&line, &s, stdin) < 0) {
+ free(line);
+ line = NULL;
+ }
+ input_process_line(line);
+ }
+#endif
+
+ if (d->current == NULL)
+ pw_main_loop_quit(d->loop);
+ else {
+ struct remote_data *rd = d->current;
+ if (rd->core)
+ rd->prompt_pending = pw_core_sync(rd->core, 0, 0);
+ }
+ }
+}
+
+#ifdef HAVE_READLINE
+static char *
+readline_match_command(const char *text, int state)
+{
+ static size_t idx;
+ static int len;
+
+ if (!state) {
+ idx = 0;
+ len = strlen(text);
+ }
+
+ while (idx < SPA_N_ELEMENTS(command_list)) {
+ const char *name = command_list[idx].name;
+ const char *alias = command_list[idx].alias;
+
+ idx++;
+ if (spa_strneq(name, text, len) || spa_strneq(alias, text, len))
+ return strdup(name);
+ }
+
+ return NULL;
+}
+
+static char **
+readline_command_completion(const char *text, int start, int end)
+{
+ char **matches = NULL;
+
+ /* Only try to complete the first word in a line */
+ if (start == 0)
+ matches = rl_completion_matches(text, readline_match_command);
+
+ /* Don't fall back to filename completion */
+ rl_attempted_completion_over = true;
+
+ return matches;
+}
+
+static void readline_init(void)
+{
+ rl_attempted_completion_function = readline_command_completion;
+ rl_callback_handler_install(">> ", input_process_line);
+}
+
+static void readline_cleanup(void)
+{
+ rl_callback_handler_remove();
+}
+#endif
+
+static void do_quit_on_signal(void *data, int signal_number)
+{
+ struct data *d = data;
+ d->quit = true;
+ pw_main_loop_quit(d->loop);
+}
+
+static void show_help(struct data *data, const char *name, bool error)
+{
+ fprintf(error ? stderr : stdout, _("%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"),
+ name);
+
+ do_help(data, "help", "", NULL);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0 };
+ struct pw_loop *l;
+ char *opt_remote = NULL;
+ char *error;
+ bool daemon = false, monitor = false;
+ struct remote_data *rd;
+ static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "monitor", no_argument, NULL, 'm' },
+ { "daemon", no_argument, NULL, 'd' },
+ { "remote", required_argument, NULL, 'r' },
+ { NULL, 0, NULL, 0}
+ };
+ int c, i;
+
+ setlinebuf(stdout);
+
+ setlocale(LC_ALL, "");
+ pw_init(&argc, &argv);
+
+ while ((c = getopt_long(argc, argv, "hVmdr:", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ show_help(&data, argv[0], false);
+ return 0;
+ case 'V':
+ printf("%s\n"
+ "Compiled with libpipewire %s\n"
+ "Linked with libpipewire %s\n",
+ argv[0],
+ pw_get_headers_version(),
+ pw_get_library_version());
+ return 0;
+ case 'd':
+ daemon = true;
+ break;
+ case 'm':
+ monitor = true;
+ break;
+ case 'r':
+ opt_remote = optarg;
+ break;
+ default:
+ show_help(&data, argv[0], true);
+ return -1;
+ }
+ }
+
+ data.loop = pw_main_loop_new(NULL);
+ if (data.loop == NULL) {
+ fprintf(stderr, "Broken installation: %m\n");
+ return -1;
+ }
+ l = pw_main_loop_get_loop(data.loop);
+ pw_loop_add_signal(l, SIGINT, do_quit_on_signal, &data);
+ pw_loop_add_signal(l, SIGTERM, do_quit_on_signal, &data);
+
+ spa_list_init(&data.remotes);
+ pw_map_init(&data.vars, 64, 16);
+
+ data.context = pw_context_new(l,
+ pw_properties_new(
+ PW_KEY_CORE_DAEMON, daemon ? "true" : NULL,
+ NULL),
+ 0);
+ if (data.context == NULL) {
+ fprintf(stderr, "Can't create context: %m\n");
+ return -1;
+ }
+
+ pw_context_load_module(data.context, "libpipewire-module-link-factory", NULL, NULL);
+
+ if (!do_connect(&data, "connect", opt_remote, &error)) {
+ fprintf(stderr, "Error: \"%s\"\n", error);
+ return -1;
+ }
+
+ if (optind == argc) {
+ data.interactive = true;
+
+ printf("Welcome to PipeWire version %s. Type 'help' for usage.\n",
+ pw_get_library_version());
+
+#ifdef HAVE_READLINE
+ readline_init();
+#endif
+
+ pw_loop_add_io(l, STDIN_FILENO, SPA_IO_IN|SPA_IO_HUP, false, do_input, &data);
+
+ pw_main_loop_run(data.loop);
+
+#ifdef HAVE_READLINE
+ readline_cleanup();
+#endif
+ } else {
+ char buf[4096], *p, *error;
+
+ p = buf;
+ for (i = optind; i < argc; i++) {
+ p = stpcpy(p, argv[i]);
+ p = stpcpy(p, " ");
+ }
+
+ pw_main_loop_run(data.loop);
+
+ if (!parse(&data, buf, &error)) {
+ fprintf(stderr, "Error: \"%s\"\n", error);
+ free(error);
+ }
+ data.current->prompt_pending = pw_core_sync(data.current->core, 0, 0);
+ while (!data.quit && data.current) {
+ pw_main_loop_run(data.loop);
+ if (!monitor)
+ break;
+ }
+ }
+ spa_list_consume(rd, &data.remotes, link)
+ remote_data_free(rd);
+
+ pw_context_destroy(data.context);
+ pw_main_loop_destroy(data.loop);
+ pw_map_clear(&data.vars);
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/tools/pw-dot.c b/src/tools/pw-dot.c
new file mode 100644
index 0000000..30eb780
--- /dev/null
+++ b/src/tools/pw-dot.c
@@ -0,0 +1,1169 @@
+/* PipeWire
+ *
+ * Copyright © 2019 Collabora Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <signal.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <locale.h>
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/utils/json.h>
+#include <spa/debug/types.h>
+
+#include <pipewire/pipewire.h>
+
+#define GLOBAL_ID_NONE UINT32_MAX
+#define DEFAULT_DOT_PATH "pw.dot"
+
+struct global;
+
+typedef void (*draw_t)(struct global *g);
+typedef void *(*info_update_t) (void *info, const void *update);
+
+struct data {
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ struct pw_registry *registry;
+ struct spa_hook registry_listener;
+
+ struct spa_list globals;
+ char *dot_str;
+ const char *dot_rankdir;
+ bool dot_orthoedges;
+
+ bool show_all;
+ bool show_smart;
+ bool show_detail;
+};
+
+struct global {
+ struct spa_list link;
+
+ struct data *data;
+ struct pw_proxy *proxy;
+
+ uint32_t id;
+#define INTERFACE_Port 0
+#define INTERFACE_Node 1
+#define INTERFACE_Link 2
+#define INTERFACE_Client 3
+#define INTERFACE_Device 4
+#define INTERFACE_Module 5
+#define INTERFACE_Factory 6
+ uint32_t type;
+ void *info;
+
+ pw_destroy_t info_destroy;
+ info_update_t info_update;
+ draw_t draw;
+
+ struct spa_hook proxy_listener;
+ struct spa_hook object_listener;
+};
+
+static char *dot_str_new(void)
+{
+ return strdup("");
+}
+
+static void dot_str_clear(char **str)
+{
+ if (str && *str) {
+ free(*str);
+ *str = NULL;
+ }
+}
+
+static SPA_PRINTF_FUNC(2,0) void dot_str_vadd(char **str, const char *fmt, va_list varargs)
+{
+ char *res = NULL;
+ char *fmt2 = NULL;
+
+ spa_return_if_fail(str != NULL);
+ spa_return_if_fail(fmt != NULL);
+
+ if (asprintf(&fmt2, "%s%s", *str, fmt) < 0) {
+ spa_assert_not_reached();
+ return;
+ }
+
+ if (vasprintf(&res, fmt2, varargs) < 0) {
+ free (fmt2);
+ spa_assert_not_reached();
+ return;
+ }
+ free (fmt2);
+
+ free(*str);
+ *str = res;
+}
+
+static SPA_PRINTF_FUNC(2,3) void dot_str_add(char **str, const char *fmt, ...)
+{
+ va_list varargs;
+ va_start(varargs, fmt);
+ dot_str_vadd(str, fmt, varargs);
+ va_end(varargs);
+}
+
+static void draw_dict(char **str, const char *title,
+ const struct spa_dict *props)
+{
+ const struct spa_dict_item *item;
+
+ dot_str_add(str, "%s:\\l", title);
+ if (props == NULL || props->n_items == 0) {
+ dot_str_add(str, "- none\\l");
+ return;
+ }
+
+ spa_dict_for_each(item, props) {
+ if (item->value)
+ dot_str_add(str, "- %s: %s\\l", item->key, item->value);
+ else
+ dot_str_add(str, "- %s: (null)\\l", item->key);
+ }
+}
+
+static SPA_PRINTF_FUNC(6,0) void draw_vlabel(char **str, const char *name, uint32_t id, bool detail,
+ const struct spa_dict *props, const char *fmt, va_list varargs)
+{
+ /* draw the label header */
+ dot_str_add(str, "%s_%u [label=\"", name, id);
+
+ /* draw the label body */
+ dot_str_vadd(str, fmt, varargs);
+
+ if (detail)
+ draw_dict(str, "properties", props);
+
+ /*draw the label footer */
+ dot_str_add(str, "%s", "\"];\n");
+}
+
+static SPA_PRINTF_FUNC(6,7) void draw_label(char **str, const char *name, uint32_t id, bool detail,
+ const struct spa_dict *props, const char *fmt, ...)
+{
+ va_list varargs;
+ va_start(varargs, fmt);
+ draw_vlabel(str, name, id, detail, props, fmt, varargs);
+ va_end(varargs);
+}
+
+static void draw_port(struct global *g)
+{
+ spa_assert(g != NULL);
+ spa_assert(g->info != NULL);
+ spa_assert(g->type == INTERFACE_Port);
+
+ struct pw_port_info *info = g->info;
+ char **dot_str = &g->data->dot_str;
+
+ /* draw the box */
+ dot_str_add(dot_str,
+ "port_%u [shape=box style=filled fillcolor=%s];\n",
+ g->id,
+ info->direction == PW_DIRECTION_INPUT ? "lightslateblue" : "lightcoral"
+ );
+
+ /* draw the label */
+ draw_label(dot_str,
+ "port", g->id, g->data->show_detail, info->props,
+ "port_id: %u\\lname: %s\\ldirection: %s\\l",
+ g->id,
+ spa_dict_lookup(info->props, PW_KEY_PORT_NAME),
+ pw_direction_as_string(info->direction)
+ );
+}
+
+
+static void draw_node(struct global *g)
+{
+ spa_assert(g != NULL);
+ spa_assert(g->info != NULL);
+ spa_assert(g->type == INTERFACE_Node);
+
+ struct pw_node_info *info = g->info;
+ char **dot_str = &g->data->dot_str;
+
+ const char *client_id_str, *factory_id_str;
+ uint32_t client_id, factory_id;
+
+ client_id_str = spa_dict_lookup(info->props, PW_KEY_CLIENT_ID);
+ factory_id_str = spa_dict_lookup(info->props, PW_KEY_FACTORY_ID);
+ client_id = client_id_str ? (uint32_t)atoi(client_id_str) : GLOBAL_ID_NONE;
+ factory_id = factory_id_str ? (uint32_t)atoi(factory_id_str) : GLOBAL_ID_NONE;
+
+ /* draw the node header */
+ dot_str_add(dot_str, "subgraph cluster_node_%u {\n", g->id);
+ dot_str_add(dot_str, "bgcolor=palegreen;\n");
+
+ /* draw the label header */
+ dot_str_add(dot_str, "label=\"");
+
+ /* draw the label body */
+ dot_str_add(dot_str, "node_id: %u\\lname: %s\\lmedia_class: %s\\l",
+ g->id,
+ spa_dict_lookup(info->props, PW_KEY_NODE_NAME),
+ spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS));
+
+ if (g->data->show_detail)
+ draw_dict(dot_str, "properties", info->props);
+
+ /*draw the label footer */
+ dot_str_add(dot_str, "%s", "\"\n");
+
+ /* draw all node ports */
+ struct global *p;
+ const char *prop_node_id;
+ spa_list_for_each(p, &g->data->globals, link) {
+ struct pw_port_info *pinfo;
+ if (p->info == NULL)
+ continue;
+ if (p->type != INTERFACE_Port)
+ continue;
+ pinfo = p->info;
+ prop_node_id = spa_dict_lookup(pinfo->props, PW_KEY_NODE_ID);
+ if (!prop_node_id || (uint32_t)atoi(prop_node_id) != g->id)
+ continue;
+ if (p->draw)
+ p->draw(p);
+ }
+
+ /* draw the client/factory box if all option is enabled */
+ if (g->data->show_all) {
+ dot_str_add(dot_str, "node_%u [shape=box style=filled fillcolor=white];\n", g->id);
+ dot_str_add(dot_str, "node_%u [label=\"client_id: %u\\lfactory_id: %u\\l\"];\n", g->id, client_id, factory_id);
+ }
+
+ /* draw the node footer */
+ dot_str_add(dot_str, "}\n");
+
+ /* draw the client/factory arrows if all option is enabled */
+ if (g->data->show_all) {
+ dot_str_add(dot_str, "node_%u -> client_%u [style=dashed];\n", g->id, client_id);
+ dot_str_add(dot_str, "node_%u -> factory_%u [style=dashed];\n", g->id, factory_id);
+ }
+}
+
+static void draw_link(struct global *g)
+{
+ spa_assert(g != NULL);
+ spa_assert(g->info != NULL);
+ spa_assert(g->type == INTERFACE_Link);
+
+ struct pw_link_info *info = g->info;
+ char **dot_str = &g->data->dot_str;
+
+ /* draw the box */
+ dot_str_add(dot_str, "link_%u [shape=box style=filled fillcolor=lightblue];\n", g->id);
+
+ /* draw the label */
+ draw_label(dot_str,
+ "link", g->id, g->data->show_detail, info->props,
+ "link_id: %u\\loutput_node_id: %u\\linput_node_id: %u\\loutput_port_id: %u\\linput_port_id: %u\\lstate: %s\\l",
+ g->id,
+ info->output_node_id,
+ info->input_node_id,
+ info->output_port_id,
+ info->input_port_id,
+ pw_link_state_as_string(info->state)
+ );
+
+ /* draw the arrows */
+ dot_str_add(dot_str, "port_%u -> link_%u -> port_%u;\n", info->output_port_id, g->id, info->input_port_id);
+}
+
+static void draw_client(struct global *g)
+{
+ spa_assert(g != NULL);
+ spa_assert(g->info != NULL);
+ spa_assert(g->type == INTERFACE_Client);
+
+ struct pw_client_info *info = g->info;
+ char **dot_str = &g->data->dot_str;
+
+ /* draw the box */
+ dot_str_add(dot_str, "client_%u [shape=box style=filled fillcolor=tan1];\n", g->id);
+
+ /* draw the label */
+ draw_label(dot_str,
+ "client", g->id, g->data->show_detail, info->props,
+ "client_id: %u\\lname: %s\\lpid: %s\\l",
+ g->id,
+ spa_dict_lookup(info->props, PW_KEY_APP_NAME),
+ spa_dict_lookup(info->props, PW_KEY_APP_PROCESS_ID)
+ );
+}
+
+static void draw_device(struct global *g)
+{
+ spa_assert(g != NULL);
+ spa_assert(g->info != NULL);
+ spa_assert(g->type == INTERFACE_Device);
+
+ struct pw_device_info *info = g->info;
+ char **dot_str = &g->data->dot_str;
+
+ const char *client_id_str = spa_dict_lookup(info->props, PW_KEY_CLIENT_ID);
+ const char *factory_id_str = spa_dict_lookup(info->props, PW_KEY_FACTORY_ID);
+ uint32_t client_id = client_id_str ? (uint32_t)atoi(client_id_str) : GLOBAL_ID_NONE;
+ uint32_t factory_id = factory_id_str ? (uint32_t)atoi(factory_id_str) : GLOBAL_ID_NONE;
+
+ /* draw the box */
+ dot_str_add(dot_str, "device_%u [shape=box style=filled fillcolor=lightpink];\n", g->id);
+
+ /* draw the label */
+ draw_label(dot_str,
+ "device", g->id, g->data->show_detail, info->props,
+ "device_id: %u\\lname: %s\\lmedia_class: %s\\lapi: %s\\lpath: %s\\l",
+ g->id,
+ spa_dict_lookup(info->props, PW_KEY_DEVICE_NAME),
+ spa_dict_lookup(info->props, PW_KEY_MEDIA_CLASS),
+ spa_dict_lookup(info->props, PW_KEY_DEVICE_API),
+ spa_dict_lookup(info->props, PW_KEY_OBJECT_PATH)
+ );
+
+ /* draw the arrows */
+ dot_str_add(dot_str, "device_%u -> client_%u [style=dashed];\n", g->id, client_id);
+ dot_str_add(dot_str, "device_%u -> factory_%u [style=dashed];\n", g->id, factory_id);
+}
+
+static void draw_factory(struct global *g)
+{
+ spa_assert(g != NULL);
+ spa_assert(g->info != NULL);
+ spa_assert(g->type == INTERFACE_Factory);
+
+ struct pw_factory_info *info = g->info;
+ char **dot_str = &g->data->dot_str;
+
+ const char *module_id_str = spa_dict_lookup(info->props, PW_KEY_MODULE_ID);
+ uint32_t module_id = module_id_str ? (uint32_t)atoi(module_id_str) : GLOBAL_ID_NONE;
+
+ /* draw the box */
+ dot_str_add(dot_str, "factory_%u [shape=box style=filled fillcolor=lightyellow];\n", g->id);
+
+ /* draw the label */
+ draw_label(dot_str,
+ "factory", g->id, g->data->show_detail, info->props,
+ "factory_id: %u\\lname: %s\\lmodule_id: %u\\l",
+ g->id, info->name, module_id
+ );
+
+ /* draw the arrow */
+ dot_str_add(dot_str, "factory_%u -> module_%u [style=dashed];\n", g->id, module_id);
+}
+
+static void draw_module(struct global *g)
+{
+ spa_assert(g != NULL);
+ spa_assert(g->info != NULL);
+ spa_assert(g->type == INTERFACE_Module);
+
+ struct pw_module_info *info = g->info;
+ char **dot_str = &g->data->dot_str;
+
+ /* draw the box */
+ dot_str_add(dot_str, "module_%u [shape=box style=filled fillcolor=lightgrey];\n", g->id);
+
+ /* draw the label */
+ draw_label(dot_str,
+ "module", g->id, g->data->show_detail, info->props,
+ "module_id: %u\\lname: %s\\l",
+ g->id, info->name
+ );
+}
+
+static bool is_node_id_link_referenced(uint32_t id, struct spa_list *globals)
+{
+ struct global *g;
+ struct pw_link_info *info;
+ spa_list_for_each(g, globals, link) {
+ if (g->info == NULL)
+ continue;
+ if (g->type != INTERFACE_Link)
+ continue;
+ info = g->info;
+ if (info->input_node_id == id || info->output_node_id == id)
+ return true;
+ }
+ return false;
+}
+
+static bool is_module_id_factory_referenced(uint32_t id, struct spa_list *globals)
+{
+ struct global *g;
+ struct pw_factory_info *info;
+ const char *module_id_str;
+ spa_list_for_each(g, globals, link) {
+ if (g->info == NULL)
+ continue;
+ if (g->type != INTERFACE_Factory)
+ continue;
+ info = g->info;
+ module_id_str = spa_dict_lookup(info->props, PW_KEY_MODULE_ID);
+ if (module_id_str && (uint32_t)atoi(module_id_str) == id)
+ return true;
+ }
+ return false;
+}
+
+static bool is_global_referenced(struct global *g)
+{
+ switch (g->type) {
+ case INTERFACE_Node:
+ return is_node_id_link_referenced(g->id, &g->data->globals);
+ case INTERFACE_Module:
+ return is_module_id_factory_referenced(g->id, &g->data->globals);
+ default:
+ break;
+ }
+
+ return true;
+}
+
+static int draw_graph(struct data *d, const char *path)
+{
+ FILE *fp;
+ struct global *g;
+
+ /* draw the header */
+ dot_str_add(&d->dot_str, "digraph pipewire {\n");
+
+ if (d->dot_rankdir) {
+ /* set rank direction, if provided */
+ dot_str_add(&d->dot_str, "rankdir = \"%s\";\n", d->dot_rankdir);
+ }
+
+ if (d->dot_orthoedges) {
+ /* enable orthogonal edges */
+ dot_str_add(&d->dot_str, "splines = ortho;\n");
+ }
+
+ /* iterate the globals */
+ spa_list_for_each(g, &d->globals, link) {
+ /* skip null and non-info globals */
+ if (g->info == NULL)
+ continue;
+
+ /* always skip ports since they are drawn by the nodes */
+ if (g->type == INTERFACE_Port)
+ continue;
+
+ /* skip clients, devices, factories and modules if all option is disabled */
+ if (!d->show_all) {
+ switch (g->type) {
+ case INTERFACE_Client:
+ case INTERFACE_Device:
+ case INTERFACE_Factory:
+ case INTERFACE_Module:
+ continue;
+ default:
+ break;
+ }
+ }
+
+ /* skip not referenced globals if smart option is enabled */
+ if (d->show_smart && !is_global_referenced(g))
+ continue;
+
+ /* draw the global */
+ if (g->draw)
+ g->draw(g);
+ }
+
+ /* draw the footer */
+ dot_str_add(&d->dot_str, "}\n");
+
+ if (spa_streq(path, "-")) {
+ /* wire the dot graph into to stdout */
+ fputs(d->dot_str, stdout);
+ } else {
+ /* open the file */
+ fp = fopen(path, "we");
+ if (fp == NULL) {
+ printf("open error: could not open %s for writing\n", path);
+ return -1;
+ }
+
+ /* wire the dot graph into the file */
+ fputs(d->dot_str, fp);
+ fclose(fp);
+ }
+ return 0;
+}
+
+static void global_event_info(struct global *g, const void *info)
+{
+ if (g->info_update)
+ g->info = g->info_update(g->info, info);
+}
+
+static void port_event_info(void *data, const struct pw_port_info *info)
+{
+ global_event_info(data, info);
+}
+
+static const struct pw_port_events port_events = {
+ PW_VERSION_PORT_EVENTS,
+ .info = port_event_info,
+};
+
+static void node_event_info(void *data, const struct pw_node_info *info)
+{
+ global_event_info(data, info);
+}
+
+static const struct pw_node_events node_events = {
+ PW_VERSION_NODE_EVENTS,
+ .info = node_event_info,
+};
+
+static void link_event_info(void *data, const struct pw_link_info *info)
+{
+ global_event_info(data, info);
+}
+
+static const struct pw_link_events link_events = {
+ PW_VERSION_LINK_EVENTS,
+ .info = link_event_info
+};
+
+static void client_event_info(void *data, const struct pw_client_info *info)
+{
+ global_event_info(data, info);
+}
+
+static const struct pw_client_events client_events = {
+ PW_VERSION_CLIENT_EVENTS,
+ .info = client_event_info
+};
+
+static void device_event_info(void *data, const struct pw_device_info *info)
+{
+ global_event_info(data, info);
+}
+
+static const struct pw_device_events device_events = {
+ PW_VERSION_DEVICE_EVENTS,
+ .info = device_event_info
+};
+
+static void factory_event_info(void *data, const struct pw_factory_info *info)
+{
+ global_event_info(data, info);
+}
+
+static const struct pw_factory_events factory_events = {
+ PW_VERSION_FACTORY_EVENTS,
+ .info = factory_event_info
+};
+
+static void module_event_info(void *data, const struct pw_module_info *info)
+{
+ global_event_info(data, info);
+}
+
+static const struct pw_module_events module_events = {
+ PW_VERSION_MODULE_EVENTS,
+ .info = module_event_info
+};
+
+static void removed_proxy(void *data)
+{
+ struct global *g = data;
+ pw_proxy_destroy(g->proxy);
+}
+
+static void destroy_proxy(void *data)
+{
+ struct global *g = data;
+ spa_hook_remove(&g->object_listener);
+ spa_hook_remove(&g->proxy_listener);
+}
+
+static const struct pw_proxy_events proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .removed = removed_proxy,
+ .destroy = destroy_proxy,
+};
+
+static void registry_event_global(void *data, uint32_t id, uint32_t permissions,
+ const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ struct data *d = data;
+ struct pw_proxy *proxy;
+ uint32_t client_version;
+ uint32_t object_type;
+ const void *events;
+ pw_destroy_t info_destroy;
+ info_update_t info_update;
+ draw_t draw;
+ struct global *g;
+
+ if (spa_streq(type, PW_TYPE_INTERFACE_Port)) {
+ events = &port_events;
+ info_destroy = (pw_destroy_t)pw_port_info_free;
+ info_update = (info_update_t)pw_port_info_update;
+ draw = draw_port;
+ client_version = PW_VERSION_PORT;
+ object_type = INTERFACE_Port;
+ }
+ else if (spa_streq(type, PW_TYPE_INTERFACE_Node)) {
+ events = &node_events;
+ info_destroy = (pw_destroy_t)pw_node_info_free;
+ info_update = (info_update_t)pw_node_info_update;
+ draw = draw_node;
+ client_version = PW_VERSION_NODE;
+ object_type = INTERFACE_Node;
+ }
+ else if (spa_streq(type, PW_TYPE_INTERFACE_Link)) {
+ events = &link_events;
+ info_destroy = (pw_destroy_t)pw_link_info_free;
+ info_update = (info_update_t)pw_link_info_update;
+ draw = draw_link;
+ client_version = PW_VERSION_LINK;
+ object_type = INTERFACE_Link;
+ }
+ else if (spa_streq(type, PW_TYPE_INTERFACE_Client)) {
+ events = &client_events;
+ info_destroy = (pw_destroy_t)pw_client_info_free;
+ info_update = (info_update_t)pw_client_info_update;
+ draw = draw_client;
+ client_version = PW_VERSION_CLIENT;
+ object_type = INTERFACE_Client;
+ }
+ else if (spa_streq(type, PW_TYPE_INTERFACE_Device)) {
+ events = &device_events;
+ info_destroy = (pw_destroy_t)pw_device_info_free;
+ info_update = (info_update_t)pw_device_info_update;
+ draw = draw_device;
+ client_version = PW_VERSION_DEVICE;
+ object_type = INTERFACE_Device;
+ }
+ else if (spa_streq(type, PW_TYPE_INTERFACE_Factory)) {
+ events = &factory_events;
+ info_destroy = (pw_destroy_t)pw_factory_info_free;
+ info_update = (info_update_t)pw_factory_info_update;
+ draw = draw_factory;
+ client_version = PW_VERSION_FACTORY;
+ object_type = INTERFACE_Factory;
+ }
+ else if (spa_streq(type, PW_TYPE_INTERFACE_Module)) {
+ events = &module_events;
+ info_destroy = (pw_destroy_t)pw_module_info_free;
+ info_update = (info_update_t)pw_module_info_update;
+ draw = draw_module;
+ client_version = PW_VERSION_MODULE;
+ object_type = INTERFACE_Module;
+ }
+ else if (spa_streq(type, PW_TYPE_INTERFACE_Core)) {
+ /* sync to notify we are done with globals */
+ pw_core_sync(d->core, 0, 0);
+ return;
+ }
+ else {
+ return;
+ }
+
+ proxy = pw_registry_bind(d->registry, id, type, client_version, 0);
+ if (proxy == NULL)
+ return;
+
+ /* set the global data */
+ g = calloc(1, sizeof(struct global));
+ g->data = d;
+ g->proxy = proxy;
+
+ g->id = id;
+ g->type = object_type;
+ g->info = NULL;
+
+ g->info_destroy = info_destroy;
+ g->info_update = info_update;
+ g->draw = draw;
+
+ pw_proxy_add_object_listener(proxy, &g->object_listener, events, g);
+ pw_proxy_add_listener(proxy, &g->proxy_listener, &proxy_events, g);
+
+ /* add the global to the list */
+ spa_list_insert(&d->globals, &g->link);
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+};
+
+static void on_core_done(void *data, uint32_t id, int seq)
+{
+ struct data *d = data;
+ pw_main_loop_quit(d->loop);
+}
+
+static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct data *d = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_main_loop_quit(d->loop);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .done = on_core_done,
+ .error = on_core_error,
+};
+
+static void do_quit(void *data, int signal_number)
+{
+ struct data *d = data;
+ pw_main_loop_quit(d->loop);
+}
+
+static int get_data_from_pipewire(struct data *data, const char *opt_remote)
+{
+ struct pw_loop *l;
+ struct global *g;
+
+ data->loop = pw_main_loop_new(NULL);
+ if (data->loop == NULL) {
+ fprintf(stderr, "can't create main loop: %m\n");
+ return -1;
+ }
+
+ l = pw_main_loop_get_loop(data->loop);
+ pw_loop_add_signal(l, SIGINT, do_quit, &data);
+ pw_loop_add_signal(l, SIGTERM, do_quit, &data);
+
+ data->context = pw_context_new(l, NULL, 0);
+ if (data->context == NULL) {
+ fprintf(stderr, "can't create context: %m\n");
+ pw_main_loop_destroy(data->loop);
+ return -1;
+ }
+
+ data->core = pw_context_connect(data->context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, opt_remote,
+ NULL),
+ 0);
+ if (data->core == NULL) {
+ fprintf(stderr, "can't connect: %m\n");
+ pw_context_destroy(data->context);
+ pw_main_loop_destroy(data->loop);
+ return -1;
+ }
+
+ pw_core_add_listener(data->core,
+ &data->core_listener,
+ &core_events, data);
+
+ data->registry = pw_core_get_registry(data->core,
+ PW_VERSION_REGISTRY, 0);
+ pw_registry_add_listener(data->registry,
+ &data->registry_listener,
+ &registry_events, data);
+
+ pw_main_loop_run(data->loop);
+
+ spa_hook_remove(&data->registry_listener);
+ pw_proxy_destroy((struct pw_proxy*)data->registry);
+ spa_list_for_each(g, &data->globals, link)
+ pw_proxy_destroy(g->proxy);
+ spa_hook_remove(&data->core_listener);
+ pw_context_destroy(data->context);
+ pw_main_loop_destroy(data->loop);
+
+ return 0;
+}
+
+static void handle_json_obj(struct data *data, struct pw_properties *obj)
+{
+ struct global *g;
+ struct pw_properties *info, *props;
+ const char *str;
+
+ str = pw_properties_get(obj, "type");
+ if (!str) {
+ fprintf(stderr, "invalid object without type\n");
+ return;
+ }
+
+ g = calloc(1, sizeof (struct global));
+ g->data = data;
+
+ if (spa_streq(str, PW_TYPE_INTERFACE_Port)) {
+ g->info_destroy = (pw_destroy_t)pw_port_info_free;
+ g->draw = draw_port;
+ g->type = INTERFACE_Port;
+ }
+ else if (spa_streq(str, PW_TYPE_INTERFACE_Node)) {
+ g->info_destroy = (pw_destroy_t)pw_node_info_free;
+ g->draw = draw_node;
+ g->type = INTERFACE_Node;
+ }
+ else if (spa_streq(str, PW_TYPE_INTERFACE_Link)) {
+ g->info_destroy = (pw_destroy_t)pw_link_info_free;
+ g->draw = draw_link;
+ g->type = INTERFACE_Link;
+ }
+ else if (spa_streq(str, PW_TYPE_INTERFACE_Client)) {
+ g->info_destroy = (pw_destroy_t)pw_client_info_free;
+ g->draw = draw_client;
+ g->type = INTERFACE_Client;
+ }
+ else if (spa_streq(str, PW_TYPE_INTERFACE_Device)) {
+ g->info_destroy = (pw_destroy_t)pw_device_info_free;
+ g->draw = draw_device;
+ g->type = INTERFACE_Device;
+ }
+ else if (spa_streq(str, PW_TYPE_INTERFACE_Factory)) {
+ g->info_destroy = (pw_destroy_t)pw_factory_info_free;
+ g->draw = draw_factory;
+ g->type = INTERFACE_Factory;
+ }
+ else if (spa_streq(str, PW_TYPE_INTERFACE_Module)) {
+ g->info_destroy = (pw_destroy_t)pw_module_info_free;
+ g->draw = draw_module;
+ g->type = INTERFACE_Module;
+ }
+ else {
+ free(g);
+ return;
+ }
+
+ g->id = pw_properties_get_uint32(obj, "id", 0);
+
+ str = pw_properties_get(obj, "info");
+ info = pw_properties_new_string(str);
+
+ str = pw_properties_get(info, "props");
+ props = str ? pw_properties_new_string(str) : NULL;
+
+ switch (g->type) {
+ case INTERFACE_Port: {
+ struct pw_port_info pinfo = {0};
+ pinfo.id = g->id;
+ str = pw_properties_get(info, "direction");
+ pinfo.direction = spa_streq(str, "output") ?
+ PW_DIRECTION_OUTPUT : PW_DIRECTION_INPUT;
+ pinfo.props = props ? &props->dict : NULL;
+ pinfo.change_mask = PW_PORT_CHANGE_MASK_PROPS;
+ g->info = pw_port_info_update(NULL, &pinfo);
+ break;
+ }
+ case INTERFACE_Node: {
+ struct pw_node_info ninfo = {0};
+ ninfo.id = g->id;
+ ninfo.max_input_ports =
+ pw_properties_get_uint32(info, "max-input-ports", 0);
+ ninfo.max_output_ports =
+ pw_properties_get_uint32(info, "max-output-ports", 0);
+ ninfo.n_input_ports =
+ pw_properties_get_uint32(info, "n-input-ports", 0);
+ ninfo.n_output_ports =
+ pw_properties_get_uint32(info, "n-output-ports", 0);
+
+ str = pw_properties_get(info, "state");
+ if (spa_streq(str, "running"))
+ ninfo.state = PW_NODE_STATE_RUNNING;
+ else if (spa_streq(str, "idle"))
+ ninfo.state = PW_NODE_STATE_IDLE;
+ else if (spa_streq(str, "suspended"))
+ ninfo.state = PW_NODE_STATE_SUSPENDED;
+ else if (spa_streq(str, "creating"))
+ ninfo.state = PW_NODE_STATE_CREATING;
+ else
+ ninfo.state = PW_NODE_STATE_ERROR;
+ ninfo.error = pw_properties_get(info, "error");
+
+ ninfo.props = props ? &props->dict : NULL;
+ ninfo.change_mask = PW_NODE_CHANGE_MASK_INPUT_PORTS |
+ PW_NODE_CHANGE_MASK_OUTPUT_PORTS |
+ PW_NODE_CHANGE_MASK_STATE |
+ PW_NODE_CHANGE_MASK_PROPS;
+ g->info = pw_node_info_update(NULL, &ninfo);
+ break;
+ }
+ case INTERFACE_Link: {
+ struct pw_link_info linfo = {0};
+ linfo.id = g->id;
+ linfo.output_node_id =
+ pw_properties_get_uint32(info, "output-node-id", 0);
+ linfo.output_port_id =
+ pw_properties_get_uint32(info, "output-port-id", 0);
+ linfo.input_node_id =
+ pw_properties_get_uint32(info, "input-node-id", 0);
+ linfo.input_port_id =
+ pw_properties_get_uint32(info, "input-port-id", 0);
+
+ str = pw_properties_get(info, "state");
+ if (spa_streq(str, "active"))
+ linfo.state = PW_LINK_STATE_ACTIVE;
+ else if (spa_streq(str, "paused"))
+ linfo.state = PW_LINK_STATE_PAUSED;
+ else if (spa_streq(str, "allocating"))
+ linfo.state = PW_LINK_STATE_ALLOCATING;
+ else if (spa_streq(str, "negotiating"))
+ linfo.state = PW_LINK_STATE_NEGOTIATING;
+ else if (spa_streq(str, "init"))
+ linfo.state = PW_LINK_STATE_INIT;
+ else if (spa_streq(str, "unlinked"))
+ linfo.state = PW_LINK_STATE_UNLINKED;
+ else
+ linfo.state = PW_LINK_STATE_ERROR;
+ linfo.error = pw_properties_get(info, "error");
+
+ linfo.props = props ? &props->dict : NULL;
+ linfo.change_mask = PW_LINK_CHANGE_MASK_STATE |
+ PW_LINK_CHANGE_MASK_PROPS;
+ g->info = pw_link_info_update(NULL, &linfo);
+ break;
+ }
+ case INTERFACE_Client: {
+ struct pw_client_info cinfo = {0};
+ cinfo.id = g->id;
+ cinfo.props = props ? &props->dict : NULL;
+ cinfo.change_mask = PW_CLIENT_CHANGE_MASK_PROPS;
+ g->info = pw_client_info_update(NULL, &cinfo);
+ break;
+ }
+ case INTERFACE_Device: {
+ struct pw_device_info dinfo = {0};
+ dinfo.id = g->id;
+ dinfo.props = props ? &props->dict : NULL;
+ dinfo.change_mask = PW_DEVICE_CHANGE_MASK_PROPS;
+ g->info = pw_device_info_update(NULL, &dinfo);
+ break;
+ }
+ case INTERFACE_Factory: {
+ struct pw_factory_info finfo = {0};
+ finfo.id = g->id;
+ finfo.name = pw_properties_get(info, "name");
+ finfo.type = pw_properties_get(info, "type");
+ finfo.version = pw_properties_get_uint32(info, "version", 0);
+ finfo.props = props ? &props->dict : NULL;
+ finfo.change_mask = PW_FACTORY_CHANGE_MASK_PROPS;
+ g->info = pw_factory_info_update(NULL, &finfo);
+ break;
+ }
+ case INTERFACE_Module: {
+ struct pw_module_info minfo = {0};
+ minfo.id = g->id;
+ minfo.name = pw_properties_get(info, "name");
+ minfo.filename = pw_properties_get(info, "filename");
+ minfo.args = pw_properties_get(info, "args");
+ minfo.props = props ? &props->dict : NULL;
+ minfo.change_mask = PW_MODULE_CHANGE_MASK_PROPS;
+ g->info = pw_module_info_update(NULL, &minfo);
+ break;
+ }
+ default:
+ break;
+ }
+
+ pw_properties_free(info);
+ pw_properties_free(props);
+
+ /* add the global to the list */
+ spa_list_insert(&data->globals, &g->link);
+}
+
+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];
+ const char *value;
+
+ if ((fd = open(json_path, O_CLOEXEC | O_RDONLY)) < 0) {
+ fprintf(stderr, "error opening file '%s': %m\n", json_path);
+ return -1;
+ }
+ if (fstat(fd, &sbuf) < 0) {
+ fprintf(stderr, "error statting file '%s': %m\n", json_path);
+ close(fd);
+ return -1;
+ }
+ if ((json = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) == MAP_FAILED) {
+ fprintf(stderr, "error mmapping file '%s': %m\n", json_path);
+ close(fd);
+ return -1;
+ }
+
+ close(fd);
+ spa_json_init(&it[0], json, sbuf.st_size);
+
+ if (spa_json_enter_array(&it[0], &it[1]) <= 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)) {
+ struct pw_properties *obj;
+ obj = pw_properties_new(NULL, NULL);
+ len = spa_json_container_len(&it[1], value, len);
+ pw_properties_update_string(obj, value, len);
+ handle_json_obj(data, obj);
+ pw_properties_free(obj);
+ }
+
+ munmap(json, sbuf.st_size);
+ return 0;
+}
+
+static void show_help(const char *name, bool error)
+{
+ fprintf(error ? stderr : stdout, "%s [options]\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " -a, --all Show all object types\n"
+ " -s, --smart Show linked objects only\n"
+ " -d, --detail Show all object properties\n"
+ " -r, --remote Remote daemon name\n"
+ " -o, --output Output file (Default %s)\n"
+ " -L, --lr Use left-right rank direction\n"
+ " -9, --90 Use orthogonal edges\n"
+ " -j, --json Read objects from pw-dump JSON file\n",
+ name,
+ DEFAULT_DOT_PATH);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0 };
+ struct global *g;
+ const char *opt_remote = NULL;
+ const char *dot_path = DEFAULT_DOT_PATH;
+ const char *json_path = NULL;
+ static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "all", no_argument, NULL, 'a' },
+ { "smart", no_argument, NULL, 's' },
+ { "detail", no_argument, NULL, 'd' },
+ { "remote", required_argument, NULL, 'r' },
+ { "output", required_argument, NULL, 'o' },
+ { "lr", no_argument, NULL, 'L' },
+ { "90", no_argument, NULL, '9' },
+ { "json", required_argument, NULL, 'j' },
+ { NULL, 0, NULL, 0}
+ };
+ int c;
+
+ setlocale(LC_ALL, "");
+ pw_init(&argc, &argv);
+
+ while ((c = getopt_long(argc, argv, "hVasdr:o:L9j:", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h' :
+ show_help(argv[0], false);
+ return 0;
+ case 'V' :
+ printf("%s\n"
+ "Compiled with libpipewire %s\n"
+ "Linked with libpipewire %s\n",
+ argv[0],
+ pw_get_headers_version(),
+ pw_get_library_version());
+ return 0;
+ case 'a' :
+ data.show_all = true;
+ fprintf(stderr, "all option enabled\n");
+ break;
+ case 's' :
+ data.show_smart = true;
+ fprintf(stderr, "smart option enabled\n");
+ break;
+ case 'd' :
+ data.show_detail = true;
+ fprintf(stderr, "detail option enabled\n");
+ break;
+ case 'r' :
+ opt_remote = optarg;
+ fprintf(stderr, "set remote to %s\n", opt_remote);
+ break;
+ case 'o' :
+ dot_path = optarg;
+ fprintf(stderr, "set output file %s\n", dot_path);
+ break;
+ case 'L' :
+ data.dot_rankdir = "LR";
+ fprintf(stderr, "set rank direction to LR\n");
+ break;
+ case '9' :
+ data.dot_orthoedges = true;
+ fprintf(stderr, "orthogonal edges enabled\n");
+ break;
+ case 'j' :
+ json_path = optarg;
+ fprintf(stderr, "Using JSON file %s as input\n", json_path);
+ break;
+ default:
+ show_help(argv[0], true);
+ return -1;
+ }
+ }
+
+ if (!(data.dot_str = dot_str_new()))
+ return -1;
+
+ spa_list_init(&data.globals);
+
+ if (!json_path && get_data_from_pipewire(&data, opt_remote) < 0)
+ return -1;
+ else if (json_path && get_data_from_json(&data, json_path) < 0)
+ return -1;
+
+ draw_graph(&data, dot_path);
+
+ dot_str_clear(&data.dot_str);
+ spa_list_consume(g, &data.globals, link) {
+ if (g->info && g->info_destroy)
+ g->info_destroy(g->info);
+ spa_list_remove(&g->link);
+ free(g);
+ }
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/tools/pw-dump.c b/src/tools/pw-dump.c
new file mode 100644
index 0000000..ecc2c55
--- /dev/null
+++ b/src/tools/pw-dump.c
@@ -0,0 +1,1664 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <getopt.h>
+#include <limits.h>
+#include <math.h>
+#include <fnmatch.h>
+#include <locale.h>
+
+#if !defined(FNM_EXTMATCH)
+#define FNM_EXTMATCH 0
+#endif
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/pod/iter.h>
+#include <spa/debug/types.h>
+#include <spa/utils/json.h>
+#include <spa/utils/ansi.h>
+#include <spa/utils/string.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/extensions/metadata.h>
+
+#define INDENT 2
+
+static bool colors = false;
+
+#define NORMAL (colors ? SPA_ANSI_RESET : "")
+#define LITERAL (colors ? SPA_ANSI_BRIGHT_MAGENTA : "")
+#define NUMBER (colors ? SPA_ANSI_BRIGHT_CYAN : "")
+#define STRING (colors ? SPA_ANSI_BRIGHT_GREEN : "")
+#define KEY (colors ? SPA_ANSI_BRIGHT_BLUE : "")
+
+struct data {
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+
+ struct pw_core_info *info;
+ struct pw_core *core;
+ struct spa_hook core_listener;
+ int sync_seq;
+
+ struct pw_registry *registry;
+ struct spa_hook registry_listener;
+
+ struct spa_list object_list;
+
+ const char *pattern;
+
+ FILE *out;
+ int level;
+#define STATE_KEY (1<<0)
+#define STATE_COMMA (1<<1)
+#define STATE_FIRST (1<<2)
+#define STATE_MASK 0xffff0000
+#define STATE_SIMPLE (1<<16)
+ uint32_t state;
+
+ unsigned int monitor:1;
+};
+
+struct param {
+ uint32_t id;
+ int32_t seq;
+ struct spa_list link;
+ struct spa_pod *param;
+};
+
+struct object;
+
+struct class {
+ const char *type;
+ uint32_t version;
+ const void *events;
+ void (*destroy) (struct object *object);
+ void (*dump) (struct object *object);
+ const char *name_key;
+};
+
+struct object {
+ struct spa_list link;
+
+ struct data *data;
+
+ uint32_t id;
+ uint32_t permissions;
+ char *type;
+ uint32_t version;
+ struct pw_properties *props;
+
+ const struct class *class;
+ void *info;
+ struct spa_param_info *params;
+ uint32_t n_params;
+
+ int changed;
+ struct spa_list param_list;
+ struct spa_list pending_list;
+ struct spa_list data_list;
+
+ struct pw_proxy *proxy;
+ struct spa_hook proxy_listener;
+ struct spa_hook object_listener;
+};
+
+static void core_sync(struct data *d)
+{
+ d->sync_seq = pw_core_sync(d->core, PW_ID_CORE, d->sync_seq);
+ pw_log_debug("sync start %u", d->sync_seq);
+}
+
+static uint32_t clear_params(struct spa_list *param_list, uint32_t id)
+{
+ struct param *p, *t;
+ uint32_t count = 0;
+
+ spa_list_for_each_safe(p, t, param_list, link) {
+ if (id == SPA_ID_INVALID || p->id == id) {
+ spa_list_remove(&p->link);
+ free(p);
+ count++;
+ }
+ }
+ return count;
+}
+
+static struct param *add_param(struct spa_list *params, int seq,
+ uint32_t id, const struct spa_pod *param)
+{
+ struct param *p;
+
+ if (id == SPA_ID_INVALID) {
+ if (param == NULL || !spa_pod_is_object(param)) {
+ errno = EINVAL;
+ return NULL;
+ }
+ id = SPA_POD_OBJECT_ID(param);
+ }
+
+ p = malloc(sizeof(*p) + (param != NULL ? SPA_POD_SIZE(param) : 0));
+ if (p == NULL)
+ return NULL;
+
+ p->id = id;
+ p->seq = seq;
+ if (param != NULL) {
+ p->param = SPA_PTROFF(p, sizeof(*p), struct spa_pod);
+ memcpy(p->param, param, SPA_POD_SIZE(param));
+ } else {
+ clear_params(params, id);
+ p->param = NULL;
+ }
+ spa_list_append(params, &p->link);
+
+ return p;
+}
+
+static struct object *find_object(struct data *d, uint32_t id)
+{
+ struct object *o;
+ spa_list_for_each(o, &d->object_list, link) {
+ if (o->id == id)
+ return o;
+ }
+ return NULL;
+}
+
+static void object_update_params(struct spa_list *param_list, struct spa_list *pending_list,
+ uint32_t n_params, struct spa_param_info *params)
+{
+ struct param *p, *t;
+ uint32_t i;
+
+ for (i = 0; i < n_params; i++) {
+ spa_list_for_each_safe(p, t, pending_list, link) {
+ if (p->id == params[i].id &&
+ p->seq != params[i].seq &&
+ p->param != NULL) {
+ spa_list_remove(&p->link);
+ free(p);
+ }
+ }
+ }
+
+ spa_list_consume(p, pending_list, link) {
+ spa_list_remove(&p->link);
+ if (p->param == NULL) {
+ clear_params(param_list, p->id);
+ free(p);
+ } else {
+ spa_list_append(param_list, &p->link);
+ }
+ }
+}
+
+static void object_destroy(struct object *o)
+{
+ spa_list_remove(&o->link);
+ if (o->proxy)
+ pw_proxy_destroy(o->proxy);
+ pw_properties_free(o->props);
+ clear_params(&o->param_list, SPA_ID_INVALID);
+ clear_params(&o->pending_list, SPA_ID_INVALID);
+ free(o->type);
+ free(o);
+}
+
+static void put_key(struct data *d, const char *key);
+
+static SPA_PRINTF_FUNC(3,4) void put_fmt(struct data *d, const char *key, const char *fmt, ...)
+{
+ va_list va;
+ if (key)
+ put_key(d, key);
+ fprintf(d->out, "%s%s%*s",
+ d->state & STATE_COMMA ? "," : "",
+ d->state & (STATE_MASK | STATE_KEY) ? " " : d->state & STATE_FIRST ? "" : "\n",
+ d->state & (STATE_MASK | STATE_KEY) ? 0 : d->level, "");
+ va_start(va, fmt);
+ vfprintf(d->out, fmt, va);
+ va_end(va);
+ d->state = (d->state & STATE_MASK) + STATE_COMMA;
+}
+
+static void put_key(struct data *d, const char *key)
+{
+ int size = (strlen(key) + 1) * 4;
+ char *str = alloca(size);
+ spa_json_encode_string(str, size, key);
+ put_fmt(d, NULL, "%s%s%s:", KEY, str, NORMAL);
+ d->state = (d->state & STATE_MASK) + STATE_KEY;
+}
+
+static void put_begin(struct data *d, const char *key, const char *type, uint32_t flags)
+{
+ put_fmt(d, key, "%s", type);
+ d->level += INDENT;
+ d->state = (d->state & STATE_MASK) + (flags & STATE_SIMPLE);
+}
+
+static void put_end(struct data *d, const char *type, uint32_t flags)
+{
+ d->level -= INDENT;
+ d->state = d->state & STATE_MASK;
+ put_fmt(d, NULL, "%s", type);
+ d->state = (d->state & STATE_MASK) + STATE_COMMA - (flags & STATE_SIMPLE);
+}
+
+static void put_encoded_string(struct data *d, const char *key, const char *val)
+{
+ put_fmt(d, key, "%s%s%s", STRING, val, NORMAL);
+}
+static void put_string(struct data *d, const char *key, const char *val)
+{
+ int size = (strlen(val) + 1) * 4;
+ char *str = alloca(size);
+ spa_json_encode_string(str, size, val);
+ put_encoded_string(d, key, str);
+}
+
+static void put_literal(struct data *d, const char *key, const char *val)
+{
+ put_fmt(d, key, "%s%s%s", LITERAL, val, NORMAL);
+}
+
+static void put_int(struct data *d, const char *key, int64_t val)
+{
+ put_fmt(d, key, "%s%"PRIi64"%s", NUMBER, val, NORMAL);
+}
+
+static void put_double(struct data *d, const char *key, double val)
+{
+ char buf[128];
+ put_fmt(d, key, "%s%s%s", NUMBER,
+ spa_json_format_float(buf, sizeof(buf), val), NORMAL);
+}
+
+static void put_value(struct data *d, const char *key, const char *val)
+{
+ int64_t li;
+ float fv;
+
+ if (val == NULL)
+ put_literal(d, key, "null");
+ else if (spa_streq(val, "true") || spa_streq(val, "false"))
+ put_literal(d, key, val);
+ else if (spa_atoi64(val, &li, 10))
+ put_int(d, key, li);
+ else if (spa_json_parse_float(val, strlen(val), &fv))
+ put_double(d, key, fv);
+ else
+ put_string(d, key, val);
+}
+
+static void put_dict(struct data *d, const char *key, struct spa_dict *dict)
+{
+ const struct spa_dict_item *it;
+ spa_dict_qsort(dict);
+ put_begin(d, key, "{", 0);
+ spa_dict_for_each(it, dict)
+ put_value(d, it->key, it->value);
+ put_end(d, "}", 0);
+}
+
+static void put_pod_value(struct data *d, const char *key, const struct spa_type_info *info,
+ uint32_t type, void *body, uint32_t size)
+{
+ if (key)
+ put_key(d, key);
+ switch (type) {
+ case SPA_TYPE_Bool:
+ put_value(d, NULL, *(int32_t*)body ? "true" : "false");
+ break;
+ case SPA_TYPE_Id:
+ {
+ const char *str;
+ char fallback[32];
+ uint32_t id = *(uint32_t*)body;
+ str = spa_debug_type_find_short_name(info, *(uint32_t*)body);
+ if (str == NULL) {
+ snprintf(fallback, sizeof(fallback), "id-%08x", id);
+ str = fallback;
+ }
+ put_value(d, NULL, str);
+ break;
+ }
+ case SPA_TYPE_Int:
+ put_int(d, NULL, *(int32_t*)body);
+ break;
+ case SPA_TYPE_Fd:
+ case SPA_TYPE_Long:
+ put_int(d, NULL, *(int64_t*)body);
+ break;
+ case SPA_TYPE_Float:
+ put_double(d, NULL, *(float*)body);
+ break;
+ case SPA_TYPE_Double:
+ put_double(d, NULL, *(double*)body);
+ break;
+ case SPA_TYPE_String:
+ put_string(d, NULL, (const char*)body);
+ break;
+ case SPA_TYPE_Rectangle:
+ {
+ struct spa_rectangle *r = (struct spa_rectangle *)body;
+ put_begin(d, NULL, "{", STATE_SIMPLE);
+ put_int(d, "width", r->width);
+ put_int(d, "height", r->height);
+ put_end(d, "}", STATE_SIMPLE);
+ break;
+ }
+ case SPA_TYPE_Fraction:
+ {
+ struct spa_fraction *f = (struct spa_fraction *)body;
+ put_begin(d, NULL, "{", STATE_SIMPLE);
+ put_int(d, "num", f->num);
+ put_int(d, "denom", f->denom);
+ put_end(d, "}", STATE_SIMPLE);
+ break;
+ }
+ case SPA_TYPE_Array:
+ {
+ struct spa_pod_array_body *b = (struct spa_pod_array_body *)body;
+ void *p;
+ info = info && info->values ? info->values: info;
+ put_begin(d, NULL, "[", STATE_SIMPLE);
+ SPA_POD_ARRAY_BODY_FOREACH(b, size, p)
+ put_pod_value(d, NULL, info, b->child.type, p, b->child.size);
+ put_end(d, "]", STATE_SIMPLE);
+ break;
+ }
+ case SPA_TYPE_Choice:
+ {
+ struct spa_pod_choice_body *b = (struct spa_pod_choice_body *)body;
+ int index = 0;
+
+ if (b->type == SPA_CHOICE_None) {
+ put_pod_value(d, NULL, info, b->child.type,
+ SPA_POD_CONTENTS(struct spa_pod, &b->child),
+ b->child.size);
+ } else {
+ static const char * const range_labels[] = { "default", "min", "max", NULL };
+ static const char * const step_labels[] = { "default", "min", "max", "step", NULL };
+ static const char * const enum_labels[] = { "default", "alt%u" };
+ static const char * const flags_labels[] = { "default", "flag%u" };
+
+ const char * const *labels;
+ const char *label;
+ char buffer[64];
+ int max_labels, flags = 0;
+ void *p;
+
+ switch (b->type) {
+ case SPA_CHOICE_Range:
+ labels = range_labels;
+ max_labels = 3;
+ flags |= STATE_SIMPLE;
+ break;
+ case SPA_CHOICE_Step:
+ labels = step_labels;
+ max_labels = 4;
+ flags |= STATE_SIMPLE;
+ break;
+ case SPA_CHOICE_Enum:
+ labels = enum_labels;
+ max_labels = 1;
+ break;
+ case SPA_CHOICE_Flags:
+ labels = flags_labels;
+ max_labels = 1;
+ break;
+ default:
+ labels = NULL;
+ break;
+ }
+ if (labels == NULL)
+ break;
+
+ put_begin(d, NULL, "{", flags);
+ SPA_POD_CHOICE_BODY_FOREACH(b, size, p) {
+ if ((label = labels[SPA_CLAMP(index, 0, max_labels)]) == NULL)
+ break;
+ snprintf(buffer, sizeof(buffer), label, index);
+ put_pod_value(d, buffer, info, b->child.type, p, b->child.size);
+ index++;
+ }
+ put_end(d, "}", flags);
+ }
+ break;
+ }
+ case SPA_TYPE_Object:
+ {
+ put_begin(d, NULL, "{", 0);
+ struct spa_pod_object_body *b = (struct spa_pod_object_body *)body;
+ struct spa_pod_prop *p;
+ const struct spa_type_info *ti, *ii;
+
+ ti = spa_debug_type_find(info, b->type);
+ ii = ti ? spa_debug_type_find(ti->values, 0) : NULL;
+ ii = ii ? spa_debug_type_find(ii->values, b->id) : NULL;
+
+ info = ti ? ti->values : info;
+
+ SPA_POD_OBJECT_BODY_FOREACH(b, size, p) {
+ char fallback[32];
+ const char *name;
+
+ ii = spa_debug_type_find(info, p->key);
+ name = ii ? spa_debug_type_short_name(ii->name) : NULL;
+ if (name == NULL) {
+ snprintf(fallback, sizeof(fallback), "id-%08x", p->key);
+ name = fallback;
+ }
+ put_pod_value(d, name,
+ ii ? ii->values : NULL,
+ p->value.type,
+ SPA_POD_CONTENTS(struct spa_pod_prop, p),
+ p->value.size);
+ }
+ put_end(d, "}", 0);
+ break;
+ }
+ case SPA_TYPE_Struct:
+ {
+ struct spa_pod *b = (struct spa_pod *)body, *p;
+ put_begin(d, NULL, "[", 0);
+ SPA_POD_FOREACH(b, size, p)
+ put_pod_value(d, NULL, info, p->type, SPA_POD_BODY(p), p->size);
+ put_end(d, "]", 0);
+ break;
+ }
+ case SPA_TYPE_None:
+ put_value(d, NULL, NULL);
+ break;
+ }
+}
+static void put_pod(struct data *d, const char *key, const struct spa_pod *pod)
+{
+ if (pod == NULL) {
+ put_value(d, key, NULL);
+ } else {
+ put_pod_value(d, key, SPA_TYPE_ROOT,
+ SPA_POD_TYPE(pod),
+ SPA_POD_BODY(pod),
+ SPA_POD_BODY_SIZE(pod));
+ }
+}
+
+static void put_params(struct data *d, const char *key,
+ struct spa_param_info *params, uint32_t n_params,
+ struct spa_list *list)
+{
+ uint32_t i;
+
+ put_begin(d, key, "{", 0);
+ for (i = 0; i < n_params; i++) {
+ struct spa_param_info *pi = &params[i];
+ struct param *p;
+ uint32_t flags;
+
+ flags = pi->flags & SPA_PARAM_INFO_READ ? 0 : STATE_SIMPLE;
+
+ put_begin(d, spa_debug_type_find_short_name(spa_type_param, pi->id),
+ "[", flags);
+ spa_list_for_each(p, list, link) {
+ if (p->id == pi->id)
+ put_pod(d, NULL, p->param);
+ }
+ put_end(d, "]", flags);
+ }
+ put_end(d, "}", 0);
+}
+
+struct flags_info {
+ const char *name;
+ uint64_t mask;
+};
+
+static void put_flags(struct data *d, const char *key,
+ uint64_t flags, const struct flags_info *info)
+{
+ uint32_t i;
+ put_begin(d, key, "[", STATE_SIMPLE);
+ for (i = 0; info[i].name != NULL; i++) {
+ if (info[i].mask & flags)
+ put_string(d, NULL, info[i].name);
+ }
+ put_end(d, "]", STATE_SIMPLE);
+}
+
+/* core */
+static void core_dump(struct object *o)
+{
+ static const struct flags_info fl[] = {
+ { "props", PW_CORE_CHANGE_MASK_PROPS },
+ { NULL, 0 },
+ };
+
+ struct data *d = o->data;
+ struct pw_core_info *i = d->info;
+
+ put_begin(d, "info", "{", 0);
+ put_int(d, "cookie", i->cookie);
+ put_value(d, "user-name", i->user_name);
+ put_value(d, "host-name", i->host_name);
+ put_value(d, "version", i->version);
+ put_value(d, "name", i->name);
+ put_flags(d, "change-mask", i->change_mask, fl);
+ put_dict(d, "props", i->props);
+ put_end(d, "}", 0);
+}
+
+static const struct class core_class = {
+ .type = PW_TYPE_INTERFACE_Core,
+ .version = PW_VERSION_CORE,
+ .dump = core_dump,
+ .name_key = PW_KEY_CORE_NAME,
+};
+
+/* client */
+static void client_dump(struct object *o)
+{
+ static const struct flags_info fl[] = {
+ { "props", PW_CLIENT_CHANGE_MASK_PROPS },
+ { NULL, 0 },
+ };
+
+ struct data *d = o->data;
+ struct pw_client_info *i = o->info;
+
+ put_begin(d, "info", "{", 0);
+ put_flags(d, "change-mask", i->change_mask, fl);
+ put_dict(d, "props", i->props);
+ put_end(d, "}", 0);
+}
+
+static void client_event_info(void *data, const struct pw_client_info *info)
+{
+ struct object *o = data;
+ int changed = 0;
+
+ pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask);
+
+ info = o->info = pw_client_info_update(o->info, info);
+ if (info == NULL)
+ return;
+
+ if (info->change_mask & PW_CLIENT_CHANGE_MASK_PROPS)
+ changed++;
+
+ if (changed) {
+ o->changed += changed;
+ core_sync(o->data);
+ }
+}
+
+static const struct pw_client_events client_events = {
+ PW_VERSION_CLIENT_EVENTS,
+ .info = client_event_info,
+};
+
+static void client_destroy(struct object *o)
+{
+ if (o->info) {
+ pw_client_info_free(o->info);
+ o->info = NULL;
+ }
+}
+
+static const struct class client_class = {
+ .type = PW_TYPE_INTERFACE_Client,
+ .version = PW_VERSION_CLIENT,
+ .events = &client_events,
+ .destroy = client_destroy,
+ .dump = client_dump,
+ .name_key = PW_KEY_APP_NAME,
+};
+
+/* module */
+static void module_dump(struct object *o)
+{
+ static const struct flags_info fl[] = {
+ { "props", PW_MODULE_CHANGE_MASK_PROPS },
+ { NULL, 0 },
+ };
+
+ struct data *d = o->data;
+ struct pw_module_info *i = o->info;
+
+ put_begin(d, "info", "{", 0);
+ put_value(d, "name", i->name);
+ put_value(d, "filename", i->filename);
+ put_value(d, "args", i->args);
+ put_flags(d, "change-mask", i->change_mask, fl);
+ put_dict(d, "props", i->props);
+ put_end(d, "}", 0);
+}
+
+static void module_event_info(void *data, const struct pw_module_info *info)
+{
+ struct object *o = data;
+ int changed = 0;
+
+ pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask);
+
+ info = o->info = pw_module_info_update(o->info, info);
+ if (info == NULL)
+ return;
+
+ if (info->change_mask & PW_MODULE_CHANGE_MASK_PROPS)
+ changed++;
+
+ if (changed) {
+ o->changed += changed;
+ core_sync(o->data);
+ }
+}
+
+static const struct pw_module_events module_events = {
+ PW_VERSION_MODULE_EVENTS,
+ .info = module_event_info,
+};
+
+static void module_destroy(struct object *o)
+{
+ if (o->info) {
+ pw_module_info_free(o->info);
+ o->info = NULL;
+ }
+}
+
+static const struct class module_class = {
+ .type = PW_TYPE_INTERFACE_Module,
+ .version = PW_VERSION_MODULE,
+ .events = &module_events,
+ .destroy = module_destroy,
+ .dump = module_dump,
+ .name_key = PW_KEY_MODULE_NAME,
+};
+
+/* factory */
+static void factory_dump(struct object *o)
+{
+ static const struct flags_info fl[] = {
+ { "props", PW_FACTORY_CHANGE_MASK_PROPS },
+ { NULL, 0 },
+ };
+
+ struct data *d = o->data;
+ struct pw_factory_info *i = o->info;
+
+ put_begin(d, "info", "{", 0);
+ put_value(d, "name", i->name);
+ put_value(d, "type", i->type);
+ put_int(d, "version", i->version);
+ put_flags(d, "change-mask", i->change_mask, fl);
+ put_dict(d, "props", i->props);
+ put_end(d, "}", 0);
+}
+
+static void factory_event_info(void *data, const struct pw_factory_info *info)
+{
+ struct object *o = data;
+ int changed = 0;
+
+ pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask);
+
+ info = o->info = pw_factory_info_update(o->info, info);
+ if (info == NULL)
+ return;
+
+ if (info->change_mask & PW_FACTORY_CHANGE_MASK_PROPS)
+ changed++;
+
+ if (changed) {
+ o->changed += changed;
+ core_sync(o->data);
+ }
+}
+
+static const struct pw_factory_events factory_events = {
+ PW_VERSION_FACTORY_EVENTS,
+ .info = factory_event_info,
+};
+
+static void factory_destroy(struct object *o)
+{
+ if (o->info) {
+ pw_factory_info_free(o->info);
+ o->info = NULL;
+ }
+}
+
+static const struct class factory_class = {
+ .type = PW_TYPE_INTERFACE_Factory,
+ .version = PW_VERSION_FACTORY,
+ .events = &factory_events,
+ .destroy = factory_destroy,
+ .dump = factory_dump,
+ .name_key = PW_KEY_FACTORY_NAME,
+};
+
+/* device */
+static void device_dump(struct object *o)
+{
+ static const struct flags_info fl[] = {
+ { "props", PW_DEVICE_CHANGE_MASK_PROPS },
+ { "params", PW_DEVICE_CHANGE_MASK_PARAMS },
+ { NULL, 0 },
+ };
+
+ struct data *d = o->data;
+ struct pw_device_info *i = o->info;
+
+ put_begin(d, "info", "{", 0);
+ put_flags(d, "change-mask", i->change_mask, fl);
+ put_dict(d, "props", i->props);
+ put_params(d, "params", i->params, i->n_params, &o->param_list);
+ put_end(d, "}", 0);
+}
+
+static void device_event_info(void *data, const struct pw_device_info *info)
+{
+ struct object *o = data;
+ uint32_t i, changed = 0;
+ int res;
+
+ pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask);
+
+ info = o->info = pw_device_info_update(o->info, info);
+ if (info == NULL)
+ return;
+
+ o->params = info->params;
+ o->n_params = info->n_params;
+
+ if (info->change_mask & PW_DEVICE_CHANGE_MASK_PROPS)
+ changed++;
+
+ if (info->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ uint32_t id = info->params[i].id;
+
+ if (info->params[i].user == 0)
+ continue;
+ info->params[i].user = 0;
+
+ changed++;
+ add_param(&o->pending_list, 0, id, NULL);
+ if (!(info->params[i].flags & SPA_PARAM_INFO_READ))
+ continue;
+
+ res = pw_device_enum_params((struct pw_device*)o->proxy,
+ ++info->params[i].seq, id, 0, -1, NULL);
+ if (SPA_RESULT_IS_ASYNC(res))
+ info->params[i].seq = res;
+ }
+ }
+ if (changed) {
+ o->changed += changed;
+ core_sync(o->data);
+ }
+}
+
+static void device_event_param(void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct object *o = data;
+ add_param(&o->pending_list, seq, id, param);
+}
+
+static const struct pw_device_events device_events = {
+ PW_VERSION_DEVICE_EVENTS,
+ .info = device_event_info,
+ .param = device_event_param,
+};
+
+static void device_destroy(struct object *o)
+{
+ if (o->info) {
+ pw_device_info_free(o->info);
+ o->info = NULL;
+ }
+}
+
+static const struct class device_class = {
+ .type = PW_TYPE_INTERFACE_Device,
+ .version = PW_VERSION_DEVICE,
+ .events = &device_events,
+ .destroy = device_destroy,
+ .dump = device_dump,
+ .name_key = PW_KEY_DEVICE_NAME,
+};
+
+/* node */
+static void node_dump(struct object *o)
+{
+ static const struct flags_info fl[] = {
+ { "input-ports", PW_NODE_CHANGE_MASK_INPUT_PORTS },
+ { "output-ports", PW_NODE_CHANGE_MASK_OUTPUT_PORTS },
+ { "state", PW_NODE_CHANGE_MASK_STATE },
+ { "props", PW_NODE_CHANGE_MASK_PROPS },
+ { "params", PW_NODE_CHANGE_MASK_PARAMS },
+ { NULL, 0 },
+ };
+
+ struct data *d = o->data;
+ struct pw_node_info *i = o->info;
+
+ put_begin(d, "info", "{", 0);
+ put_int(d, "max-input-ports", i->max_input_ports);
+ put_int(d, "max-output-ports", i->max_output_ports);
+ put_flags(d, "change-mask", i->change_mask, fl);
+ put_int(d, "n-input-ports", i->n_input_ports);
+ put_int(d, "n-output-ports", i->n_output_ports);
+ put_value(d, "state", pw_node_state_as_string(i->state));
+ put_value(d, "error", i->error);
+ put_dict(d, "props", i->props);
+ put_params(d, "params", i->params, i->n_params, &o->param_list);
+ put_end(d, "}", 0);
+}
+
+static void node_event_info(void *data, const struct pw_node_info *info)
+{
+ struct object *o = data;
+ uint32_t i, changed = 0;
+ int res;
+
+ pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask);
+
+ info = o->info = pw_node_info_update(o->info, info);
+ if (info == NULL)
+ return;
+
+ o->params = info->params;
+ o->n_params = info->n_params;
+
+ if (info->change_mask & PW_NODE_CHANGE_MASK_STATE)
+ changed++;
+
+ if (info->change_mask & PW_NODE_CHANGE_MASK_PROPS)
+ changed++;
+
+ if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ uint32_t id = info->params[i].id;
+
+ if (info->params[i].user == 0)
+ continue;
+ info->params[i].user = 0;
+
+ changed++;
+ add_param(&o->pending_list, 0, id, NULL);
+ if (!(info->params[i].flags & SPA_PARAM_INFO_READ))
+ continue;
+
+ res = pw_node_enum_params((struct pw_node*)o->proxy,
+ ++info->params[i].seq, id, 0, -1, NULL);
+ if (SPA_RESULT_IS_ASYNC(res))
+ info->params[i].seq = res;
+ }
+ }
+ if (changed) {
+ o->changed += changed;
+ core_sync(o->data);
+ }
+}
+
+static void node_event_param(void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct object *o = data;
+ add_param(&o->pending_list, seq, id, param);
+}
+
+static const struct pw_node_events node_events = {
+ PW_VERSION_NODE_EVENTS,
+ .info = node_event_info,
+ .param = node_event_param,
+};
+
+static void node_destroy(struct object *o)
+{
+ if (o->info) {
+ pw_node_info_free(o->info);
+ o->info = NULL;
+ }
+}
+
+static const struct class node_class = {
+ .type = PW_TYPE_INTERFACE_Node,
+ .version = PW_VERSION_NODE,
+ .events = &node_events,
+ .destroy = node_destroy,
+ .dump = node_dump,
+ .name_key = PW_KEY_NODE_NAME,
+};
+
+/* port */
+static void port_dump(struct object *o)
+{
+ static const struct flags_info fl[] = {
+ { "props", PW_PORT_CHANGE_MASK_PROPS },
+ { "params", PW_PORT_CHANGE_MASK_PARAMS },
+ { NULL, },
+ };
+
+ struct data *d = o->data;
+ struct pw_port_info *i = o->info;
+
+ put_begin(d, "info", "{", 0);
+ put_value(d, "direction", pw_direction_as_string(i->direction));
+ put_flags(d, "change-mask", i->change_mask, fl);
+ put_dict(d, "props", i->props);
+ put_params(d, "params", i->params, i->n_params, &o->param_list);
+ put_end(d, "}", 0);
+}
+
+static void port_event_info(void *data, const struct pw_port_info *info)
+{
+ struct object *o = data;
+ uint32_t i, changed = 0;
+ int res;
+
+ pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask);
+
+ info = o->info = pw_port_info_update(o->info, info);
+ if (info == NULL)
+ return;
+
+ o->params = info->params;
+ o->n_params = info->n_params;
+
+ if (info->change_mask & PW_PORT_CHANGE_MASK_PROPS)
+ changed++;
+
+ if (info->change_mask & PW_PORT_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ uint32_t id = info->params[i].id;
+
+ if (info->params[i].user == 0)
+ continue;
+ info->params[i].user = 0;
+
+ changed++;
+ add_param(&o->pending_list, 0, id, NULL);
+ if (!(info->params[i].flags & SPA_PARAM_INFO_READ))
+ continue;
+
+ res = pw_port_enum_params((struct pw_port*)o->proxy,
+ ++info->params[i].seq, id, 0, -1, NULL);
+ if (SPA_RESULT_IS_ASYNC(res))
+ info->params[i].seq = res;
+ }
+ }
+ if (changed) {
+ o->changed += changed;
+ core_sync(o->data);
+ }
+}
+
+static void port_event_param(void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct object *o = data;
+ add_param(&o->pending_list, seq, id, param);
+}
+
+static const struct pw_port_events port_events = {
+ PW_VERSION_PORT_EVENTS,
+ .info = port_event_info,
+ .param = port_event_param,
+};
+
+static void port_destroy(struct object *o)
+{
+ if (o->info) {
+ pw_port_info_free(o->info);
+ o->info = NULL;
+ }
+}
+
+static const struct class port_class = {
+ .type = PW_TYPE_INTERFACE_Port,
+ .version = PW_VERSION_PORT,
+ .events = &port_events,
+ .destroy = port_destroy,
+ .dump = port_dump,
+ .name_key = PW_KEY_PORT_NAME,
+};
+
+/* link */
+static void link_dump(struct object *o)
+{
+ static const struct flags_info fl[] = {
+ { "state", PW_LINK_CHANGE_MASK_STATE },
+ { "format", PW_LINK_CHANGE_MASK_FORMAT },
+ { "props", PW_LINK_CHANGE_MASK_PROPS },
+ { NULL, },
+ };
+
+ struct data *d = o->data;
+ struct pw_link_info *i = o->info;
+
+ put_begin(d, "info", "{", 0);
+ put_int(d, "output-node-id", i->output_node_id);
+ put_int(d, "output-port-id", i->output_port_id);
+ put_int(d, "input-node-id", i->input_node_id);
+ put_int(d, "input-port-id", i->input_port_id);
+ put_flags(d, "change-mask", i->change_mask, fl);
+ put_value(d, "state", pw_link_state_as_string(i->state));
+ put_value(d, "error", i->error);
+ put_pod(d, "format", i->format);
+ put_dict(d, "props", i->props);
+ put_end(d, "}", 0);
+}
+
+static void link_event_info(void *data, const struct pw_link_info *info)
+{
+ struct object *o = data;
+ uint32_t changed = 0;
+
+ pw_log_debug("object %p: id:%d change-mask:%08"PRIx64, o, o->id, info->change_mask);
+
+ info = o->info = pw_link_info_update(o->info, info);
+ if (info == NULL)
+ return;
+
+ if (info->change_mask & PW_LINK_CHANGE_MASK_STATE)
+ changed++;
+
+ if (info->change_mask & PW_LINK_CHANGE_MASK_FORMAT)
+ changed++;
+
+ if (info->change_mask & PW_LINK_CHANGE_MASK_PROPS)
+ changed++;
+
+ if (changed) {
+ o->changed += changed;
+ core_sync(o->data);
+ }
+}
+
+static const struct pw_link_events link_events = {
+ PW_VERSION_PORT_EVENTS,
+ .info = link_event_info,
+};
+
+static void link_destroy(struct object *o)
+{
+ if (o->info) {
+ pw_link_info_free(o->info);
+ o->info = NULL;
+ }
+}
+
+static const struct class link_class = {
+ .type = PW_TYPE_INTERFACE_Link,
+ .version = PW_VERSION_LINK,
+ .events = &link_events,
+ .destroy = link_destroy,
+ .dump = link_dump,
+};
+
+static void json_dump_val(struct data *d, const char *key, struct spa_json *it, const char *value, int len)
+{
+ struct spa_json sub;
+ if (spa_json_is_array(value, len)) {
+ put_begin(d, key, "[", STATE_SIMPLE);
+ spa_json_enter(it, &sub);
+ while ((len = spa_json_next(&sub, &value)) > 0) {
+ json_dump_val(d, NULL, &sub, value, len);
+ }
+ put_end(d, "]", STATE_SIMPLE);
+ } else if (spa_json_is_object(value, len)) {
+ 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;
+ 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));
+ } else {
+ put_value(d, key, strndupa(value, len));
+ }
+}
+
+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)
+ json_dump_val(d, key, &it[0], val, len);
+}
+
+/* metadata */
+
+struct metadata_entry {
+ struct spa_list link;
+ uint32_t changed;
+ uint32_t subject;
+ char *key;
+ char *value;
+ char *type;
+};
+
+static void metadata_dump(struct object *o)
+{
+ struct data *d = o->data;
+ struct metadata_entry *e;
+ put_dict(d, "props", &o->props->dict);
+ put_begin(d, "metadata", "[", 0);
+ spa_list_for_each(e, &o->data_list, link) {
+ if (e->changed == 0)
+ continue;
+ put_begin(d, NULL, "{", STATE_SIMPLE);
+ put_int(d, "subject", e->subject);
+ put_value(d, "key", e->key);
+ put_value(d, "type", e->type);
+ if (e->type != NULL && spa_streq(e->type, "Spa:String:JSON"))
+ json_dump(d, "value", e->value);
+ else
+ put_value(d, "value", e->value);
+ put_end(d, "}", STATE_SIMPLE);
+ e->changed = 0;
+ }
+ put_end(d, "]", 0);
+}
+
+static struct metadata_entry *metadata_find(struct object *o, uint32_t subject, const char *key)
+{
+ struct metadata_entry *e;
+ spa_list_for_each(e, &o->data_list, link) {
+ if ((e->subject == subject) &&
+ (key == NULL || spa_streq(e->key, key)))
+ return e;
+ }
+ return NULL;
+}
+
+static int metadata_property(void *data,
+ uint32_t subject,
+ const char *key,
+ const char *type,
+ const char *value)
+{
+ struct object *o = data;
+ struct metadata_entry *e;
+
+ while ((e = metadata_find(o, subject, key)) != NULL) {
+ spa_list_remove(&e->link);
+ free(e);
+ }
+ if (key != NULL && value != NULL) {
+ size_t size = strlen(key) + 1;
+ size += strlen(value) + 1;
+ size += type ? strlen(type) + 1 : 0;
+
+ e = calloc(1, sizeof(*e) + size);
+ if (e == NULL)
+ return -errno;
+
+ e->subject = subject;
+ e->key = SPA_PTROFF(e, sizeof(*e), void);
+ strcpy(e->key, key);
+ e->value = SPA_PTROFF(e->key, strlen(e->key) + 1, void);
+ strcpy(e->value, value);
+ if (type) {
+ e->type = SPA_PTROFF(e->value, strlen(e->value) + 1, void);
+ strcpy(e->type, type);
+ } else {
+ e->type = NULL;
+ }
+ spa_list_append(&o->data_list, &e->link);
+ e->changed++;
+ }
+ o->changed++;
+ return 0;
+}
+
+static const struct pw_metadata_events metadata_events = {
+ PW_VERSION_METADATA_EVENTS,
+ .property = metadata_property,
+};
+
+static void metadata_destroy(struct object *o)
+{
+ struct metadata_entry *e;
+ spa_list_consume(e, &o->data_list, link) {
+ spa_list_remove(&e->link);
+ free(e);
+ }
+}
+
+static const struct class metadata_class = {
+ .type = PW_TYPE_INTERFACE_Metadata,
+ .version = PW_VERSION_METADATA,
+ .events = &metadata_events,
+ .destroy = metadata_destroy,
+ .dump = metadata_dump,
+ .name_key = PW_KEY_METADATA_NAME,
+};
+
+static const struct class *classes[] =
+{
+ &core_class,
+ &module_class,
+ &factory_class,
+ &client_class,
+ &device_class,
+ &node_class,
+ &port_class,
+ &link_class,
+ &metadata_class,
+};
+
+static const struct class *find_class(const char *type, uint32_t version)
+{
+ SPA_FOR_EACH_ELEMENT_VAR(classes, c) {
+ if (spa_streq((*c)->type, type) &&
+ (*c)->version <= version)
+ return *c;
+ }
+ return NULL;
+}
+
+static void
+destroy_removed(void *data)
+{
+ struct object *o = data;
+ pw_proxy_destroy(o->proxy);
+}
+
+static void
+destroy_proxy(void *data)
+{
+ struct object *o = data;
+
+ spa_hook_remove(&o->proxy_listener);
+ if (o->class != NULL) {
+ if (o->class->events)
+ spa_hook_remove(&o->object_listener);
+ if (o->class->destroy)
+ o->class->destroy(o);
+ }
+ o->proxy = NULL;
+}
+
+static const struct pw_proxy_events proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .removed = destroy_removed,
+ .destroy = destroy_proxy,
+};
+
+static void registry_event_global(void *data, uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ struct data *d = data;
+ struct object *o;
+
+ o = calloc(1, sizeof(*o));
+ if (o == NULL) {
+ pw_log_error("can't alloc object for %u %s/%d: %m", id, type, version);
+ return;
+ }
+ o->data = d;
+ o->id = id;
+ o->permissions = permissions;
+ o->type = strdup(type);
+ o->version = version;
+ o->props = props ? pw_properties_new_dict(props) : NULL;
+ spa_list_init(&o->param_list);
+ spa_list_init(&o->pending_list);
+ spa_list_init(&o->data_list);
+
+ o->class = find_class(type, version);
+ if (o->class != NULL) {
+ o->proxy = pw_registry_bind(d->registry,
+ id, type, o->class->version, 0);
+ if (o->proxy == NULL)
+ goto bind_failed;
+
+ pw_proxy_add_listener(o->proxy,
+ &o->proxy_listener,
+ &proxy_events, o);
+
+ if (o->class->events)
+ pw_proxy_add_object_listener(o->proxy,
+ &o->object_listener,
+ o->class->events, o);
+ else
+ o->changed++;
+ } else {
+ o->changed++;
+ }
+ spa_list_append(&d->object_list, &o->link);
+
+ core_sync(d);
+ return;
+
+bind_failed:
+ pw_log_error("can't bind object for %u %s/%d: %m", id, type, version);
+ pw_properties_free(o->props);
+ free(o);
+ return;
+}
+
+static bool object_matches(struct object *o, const char *pattern)
+{
+ uint32_t id;
+ const char *str;
+
+ if (spa_atou32(pattern, &id, 0) && o->id == id)
+ return true;
+
+ if (o->props == NULL)
+ return false;
+
+ if (strstr(o->type, pattern) != NULL)
+ return true;
+ if ((str = pw_properties_get(o->props, PW_KEY_OBJECT_PATH)) != NULL &&
+ fnmatch(pattern, str, FNM_EXTMATCH) == 0)
+ return true;
+ if ((str = pw_properties_get(o->props, PW_KEY_OBJECT_SERIAL)) != NULL &&
+ spa_streq(pattern, str))
+ return true;
+ if (o->class != NULL && o->class->name_key != NULL &&
+ (str = pw_properties_get(o->props, o->class->name_key)) != NULL &&
+ fnmatch(pattern, str, FNM_EXTMATCH) == 0)
+ return true;
+ return false;
+}
+
+static void registry_event_global_remove(void *data, uint32_t id)
+{
+ struct data *d = data;
+ struct object *o;
+
+ if ((o = find_object(d, id)) == NULL)
+ return;
+
+ d->state = STATE_FIRST;
+ if (d->pattern != NULL && !object_matches(o, d->pattern))
+ return;
+ if (d->state == STATE_FIRST)
+ put_begin(d, NULL, "[", 0);
+ put_begin(d, NULL, "{", 0);
+ put_int(d, "id", o->id);
+ if (o->class && o->class->dump)
+ put_value(d, "info", NULL);
+ else if (o->props)
+ put_value(d, "props", NULL);
+ put_end(d, "}", 0);
+ if (d->state != STATE_FIRST)
+ put_end(d, "]\n", 0);
+
+ object_destroy(o);
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+ .global_remove = registry_event_global_remove,
+};
+
+static void dump_objects(struct data *d)
+{
+ static const struct flags_info fl[] = {
+ { "r", PW_PERM_R },
+ { "w", PW_PERM_W },
+ { "x", PW_PERM_X },
+ { "m", PW_PERM_M },
+ { NULL, },
+ };
+
+ struct object *o;
+
+ d->state = STATE_FIRST;
+ spa_list_for_each(o, &d->object_list, link) {
+ if (d->pattern != NULL && !object_matches(o, d->pattern))
+ continue;
+ if (o->changed == 0)
+ continue;
+ if (d->state == STATE_FIRST)
+ put_begin(d, NULL, "[", 0);
+ put_begin(d, NULL, "{", 0);
+ put_int(d, "id", o->id);
+ put_value(d, "type", o->type);
+ put_int(d, "version", o->version);
+ put_flags(d, "permissions", o->permissions, fl);
+ if (o->class && o->class->dump)
+ o->class->dump(o);
+ else if (o->props)
+ put_dict(d, "props", &o->props->dict);
+ put_end(d, "}", 0);
+ o->changed = 0;
+ }
+ if (d->state != STATE_FIRST)
+ put_end(d, "]\n", 0);
+}
+
+static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct data *d = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_main_loop_quit(d->loop);
+}
+
+static void on_core_info(void *data, const struct pw_core_info *info)
+{
+ struct data *d = data;
+ d->info = pw_core_info_update(d->info, info);
+}
+
+static void on_core_done(void *data, uint32_t id, int seq)
+{
+ struct data *d = data;
+ struct object *o;
+
+ if (id == PW_ID_CORE) {
+ if (d->sync_seq != seq)
+ return;
+
+ pw_log_debug("sync end %u/%u", d->sync_seq, seq);
+
+ spa_list_for_each(o, &d->object_list, link)
+ object_update_params(&o->param_list, &o->pending_list,
+ o->n_params, o->params);
+
+ dump_objects(d);
+ if (!d->monitor)
+ pw_main_loop_quit(d->loop);
+ }
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .done = on_core_done,
+ .info = on_core_info,
+ .error = on_core_error,
+};
+
+static void do_quit(void *data, int signal_number)
+{
+ struct data *d = data;
+ pw_main_loop_quit(d->loop);
+}
+
+static void show_help(struct data *data, const char *name, bool error)
+{
+ fprintf(error ? stderr : stdout, "%s [options] [<id>]\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " -r, --remote Remote daemon name\n"
+ " -m, --monitor monitor changes\n"
+ " -N, --no-colors disable color output\n"
+ " -C, --color[=WHEN] whether to enable color support. WHEN is `never`, `always`, or `auto`\n",
+ name);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0 };
+ struct object *o;
+ struct pw_loop *l;
+ const char *opt_remote = NULL;
+ static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "remote", required_argument, NULL, 'r' },
+ { "monitor", no_argument, NULL, 'm' },
+ { "no-colors", no_argument, NULL, 'N' },
+ { "color", optional_argument, NULL, 'C' },
+ { NULL, 0, NULL, 0}
+ };
+ int c;
+
+ setlocale(LC_ALL, "");
+ pw_init(&argc, &argv);
+
+ data.out = stdout;
+ if (isatty(fileno(data.out)) && getenv("NO_COLOR") == NULL)
+ colors = true;
+ setlinebuf(data.out);
+
+ while ((c = getopt_long(argc, argv, "hVr:mNC", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h' :
+ show_help(&data, argv[0], false);
+ return 0;
+ case 'V' :
+ printf("%s\n"
+ "Compiled with libpipewire %s\n"
+ "Linked with libpipewire %s\n",
+ argv[0],
+ pw_get_headers_version(),
+ pw_get_library_version());
+ return 0;
+ case 'r' :
+ opt_remote = optarg;
+ break;
+ case 'm' :
+ data.monitor = true;
+ break;
+ case 'N' :
+ colors = false;
+ break;
+ case 'C' :
+ if (optarg == NULL || !strcmp(optarg, "auto"))
+ break; /* nothing to do, tty detection was done
+ before parsing options */
+ else if (!strcmp(optarg, "never"))
+ colors = false;
+ else if (!strcmp(optarg, "always"))
+ colors = true;
+ else {
+ fprintf(stderr, "Unknown color: %s\n", optarg);
+ show_help(&data, argv[0], true);
+ return -1;
+ }
+ break;
+ default:
+ show_help(&data, argv[0], true);
+ return -1;
+ }
+ }
+
+ if (optind < argc)
+ data.pattern = argv[optind++];
+
+ data.loop = pw_main_loop_new(NULL);
+ if (data.loop == NULL) {
+ fprintf(stderr, "can't create main loop: %m\n");
+ return -1;
+ }
+
+ l = pw_main_loop_get_loop(data.loop);
+ pw_loop_add_signal(l, SIGINT, do_quit, &data);
+ pw_loop_add_signal(l, SIGTERM, do_quit, &data);
+
+ data.context = pw_context_new(l, NULL, 0);
+ if (data.context == NULL) {
+ fprintf(stderr, "can't create context: %m\n");
+ return -1;
+ }
+
+ data.core = pw_context_connect(data.context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, opt_remote,
+ NULL),
+ 0);
+ if (data.core == NULL) {
+ fprintf(stderr, "can't connect: %m\n");
+ return -1;
+ }
+
+ spa_list_init(&data.object_list);
+
+ pw_core_add_listener(data.core,
+ &data.core_listener,
+ &core_events, &data);
+ data.registry = pw_core_get_registry(data.core,
+ PW_VERSION_REGISTRY, 0);
+ pw_registry_add_listener(data.registry,
+ &data.registry_listener,
+ &registry_events, &data);
+
+ pw_main_loop_run(data.loop);
+
+ spa_list_consume(o, &data.object_list, link)
+ object_destroy(o);
+ if (data.info)
+ pw_core_info_free(data.info);
+
+ spa_hook_remove(&data.registry_listener);
+ pw_proxy_destroy((struct pw_proxy*)data.registry);
+ spa_hook_remove(&data.core_listener);
+ pw_context_destroy(data.context);
+ pw_main_loop_destroy(data.loop);
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/tools/pw-link.c b/src/tools/pw-link.c
new file mode 100644
index 0000000..8696eb2
--- /dev/null
+++ b/src/tools/pw-link.c
@@ -0,0 +1,912 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <signal.h>
+#include <math.h>
+#include <getopt.h>
+#include <regex.h>
+#include <locale.h>
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/utils/defs.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/filter.h>
+
+struct object {
+ struct spa_list link;
+
+ uint32_t id;
+#define OBJECT_ANY 0
+#define OBJECT_NODE 1
+#define OBJECT_PORT 2
+#define OBJECT_LINK 3
+ uint32_t type;
+ struct pw_properties *props;
+ uint32_t extra[2];
+};
+
+struct data {
+ struct pw_main_loop *loop;
+
+ const char *opt_remote;
+#define MODE_LIST_OUTPUT (1<<0)
+#define MODE_LIST_INPUT (1<<1)
+#define MODE_LIST_PORTS (MODE_LIST_OUTPUT|MODE_LIST_INPUT)
+#define MODE_LIST_LINKS (1<<2)
+#define MODE_LIST (MODE_LIST_PORTS|MODE_LIST_LINKS)
+#define MODE_MONITOR (1<<3)
+#define MODE_DISCONNECT (1<<4)
+ uint32_t opt_mode;
+ bool opt_id;
+ bool opt_verbose;
+ const char *opt_output;
+ const char *opt_input;
+ struct pw_properties *props;
+
+ struct pw_context *context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ struct pw_registry *registry;
+ struct spa_hook registry_listener;
+
+ struct spa_list objects;
+
+ int sync;
+ int link_res;
+ bool monitoring;
+ bool list_inputs;
+ bool list_outputs;
+ const char *prefix;
+
+ regex_t out_port_regex, *out_regex;
+ regex_t in_port_regex, *in_regex;
+};
+
+static void link_proxy_error(void *data, int seq, int res, const char *message)
+{
+ struct data *d = data;
+ d->link_res = res;
+}
+
+static const struct pw_proxy_events link_proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .error = link_proxy_error,
+};
+
+static void core_sync(struct data *data)
+{
+ data->sync = pw_core_sync(data->core, PW_ID_CORE, data->sync);
+}
+
+static int create_link(struct data *data)
+{
+ struct pw_proxy *proxy;
+ struct spa_hook listener;
+
+ data->link_res = 0;
+
+ proxy = pw_core_create_object(data->core,
+ "link-factory",
+ PW_TYPE_INTERFACE_Link,
+ PW_VERSION_LINK,
+ &data->props->dict, 0);
+ if (proxy == NULL)
+ return -errno;
+
+ spa_zero(listener);
+ pw_proxy_add_listener(proxy, &listener, &link_proxy_events, data);
+
+ core_sync(data);
+ pw_main_loop_run(data->loop);
+
+ spa_hook_remove(&listener);
+
+ pw_proxy_destroy(proxy);
+
+ return data->link_res;
+}
+
+static struct object *find_object(struct data *data, uint32_t type, uint32_t id)
+{
+ struct object *o;
+ spa_list_for_each(o, &data->objects, link)
+ if ((type == OBJECT_ANY || o->type == type) && o->id == id)
+ return o;
+ return NULL;
+}
+
+static struct object *find_node_port(struct data *data, struct object *node, enum pw_direction direction, const char *port_id)
+{
+ struct object *o;
+
+ spa_list_for_each(o, &data->objects, link) {
+ const char *o_port_id;
+ if (o->type != OBJECT_PORT)
+ continue;
+ if (o->extra[1] != node->id)
+ continue;
+ if (o->extra[0] != direction)
+ continue;
+ if ((o_port_id = pw_properties_get(o->props, PW_KEY_PORT_ID)) == NULL)
+ continue;
+ if (spa_streq(o_port_id, port_id))
+ return o;
+ }
+
+ return NULL;
+}
+
+static char *node_name(char *buffer, int size, struct object *n)
+{
+ const char *name;
+ buffer[0] = '\0';
+ if ((name = pw_properties_get(n->props, PW_KEY_NODE_NAME)) == NULL)
+ return buffer;
+ snprintf(buffer, size, "%s", name);
+ return buffer;
+}
+
+static char *node_path(char *buffer, int size, struct object *n)
+{
+ const char *name;
+ buffer[0] = '\0';
+ if ((name = pw_properties_get(n->props, PW_KEY_OBJECT_PATH)) == NULL)
+ return buffer;
+ snprintf(buffer, size, "%s", name);
+ return buffer;
+}
+
+static char *port_name(char *buffer, int size, struct object *n, struct object *p)
+{
+ const char *name1, *name2;
+ buffer[0] = '\0';
+ if ((name1 = pw_properties_get(n->props, PW_KEY_NODE_NAME)) == NULL)
+ return buffer;
+ if ((name2 = pw_properties_get(p->props, PW_KEY_PORT_NAME)) == NULL)
+ return buffer;
+ snprintf(buffer, size, "%s:%s", name1, name2);
+ return buffer;
+}
+
+static char *port_path(char *buffer, int size, struct object *n, struct object *p)
+{
+ const char *name;
+ buffer[0] = '\0';
+ if ((name = pw_properties_get(p->props, PW_KEY_OBJECT_PATH)) == NULL)
+ return buffer;
+ snprintf(buffer, size, "%s", name);
+ return buffer;
+}
+
+static char *port_alias(char *buffer, int size, struct object *n, struct object *p)
+{
+ const char *name;
+ buffer[0] = '\0';
+ if ((name = pw_properties_get(p->props, PW_KEY_PORT_ALIAS)) == NULL)
+ return buffer;
+ snprintf(buffer, size, "%s", name);
+ return buffer;
+}
+
+static void print_port(struct data *data, const char *prefix, struct object *n,
+ struct object *p, bool verbose)
+{
+ char buffer[1024], id[64] = "", *prefix2 = "";
+ if (data->opt_id) {
+ snprintf(id, sizeof(id), "%4d ", p->id);
+ prefix2 = " ";
+ }
+
+ printf("%s%s%s%s\n", data->prefix, prefix,
+ id, port_name(buffer, sizeof(buffer), n, p));
+ if (verbose) {
+ port_path(buffer, sizeof(buffer), n, p);
+ if (buffer[0] != '\0')
+ printf("%s %s%s%s\n", data->prefix, prefix2, prefix, buffer);
+ port_alias(buffer, sizeof(buffer), n, p);
+ if (buffer[0] != '\0')
+ printf("%s %s%s%s\n", data->prefix, prefix2, prefix, buffer);
+ }
+}
+
+static void print_port_id(struct data *data, const char *prefix, uint32_t peer)
+{
+ struct object *n, *p;
+ if ((p = find_object(data, OBJECT_PORT, peer)) == NULL)
+ return;
+ if ((n = find_object(data, OBJECT_NODE, p->extra[1])) == NULL)
+ return;
+ print_port(data, prefix, n, p, false);
+}
+
+static void do_list_port_links(struct data *data, struct object *node, struct object *port)
+{
+ struct object *o;
+ bool first = false;
+
+ if ((data->opt_mode & MODE_LIST_PORTS) == 0)
+ first = true;
+
+ spa_list_for_each(o, &data->objects, link) {
+ uint32_t peer;
+ char prefix[64], id[16] = "";
+
+ if (data->opt_id)
+ snprintf(id, sizeof(id), "%4d ", o->id);
+
+ if (o->type != OBJECT_LINK)
+ continue;
+
+ if (port->extra[0] == PW_DIRECTION_OUTPUT &&
+ o->extra[0] == port->id) {
+ peer = o->extra[1];
+ snprintf(prefix, sizeof(prefix), "%s |-> ", id);
+ }
+ else if (port->extra[0] == PW_DIRECTION_INPUT &&
+ o->extra[1] == port->id) {
+ peer = o->extra[0];
+ snprintf(prefix, sizeof(prefix), "%s |<- ", id);
+ }
+ else
+ continue;
+
+ if (first) {
+ print_port(data, "", node, port, data->opt_verbose);
+ first = false;
+ }
+ print_port_id(data, prefix, peer);
+ }
+}
+
+static int node_matches(struct data *data, struct object *n, const char *name)
+{
+ char buffer[1024];
+ uint32_t id = atoi(name);
+ if (n->id == id)
+ return 1;
+ if (spa_streq(node_name(buffer, sizeof(buffer), n), name))
+ return 1;
+ if (spa_streq(node_path(buffer, sizeof(buffer), n), name))
+ return 1;
+ return 0;
+}
+
+static int port_matches(struct data *data, struct object *n, struct object *p, const char *name)
+{
+ char buffer[1024];
+ uint32_t id = atoi(name);
+ if (p->id == id)
+ return 1;
+ if (spa_streq(port_name(buffer, sizeof(buffer), n, p), name))
+ return 1;
+ if (spa_streq(port_path(buffer, sizeof(buffer), n, p), name))
+ return 1;
+ if (spa_streq(port_alias(buffer, sizeof(buffer), n, p), name))
+ return 1;
+ return 0;
+}
+
+static int port_regex(struct data *data, struct object *n, struct object *p, regex_t *regex)
+{
+ char buffer[1024];
+ if (regexec(regex, port_name(buffer, sizeof(buffer), n, p), 0, NULL, 0) == 0)
+ return 1;
+ return 0;
+}
+
+static void do_list_ports(struct data *data, struct object *node,
+ enum pw_direction direction, regex_t *regex)
+{
+ struct object *o;
+ spa_list_for_each(o, &data->objects, link) {
+ if (o->type != OBJECT_PORT)
+ continue;
+ if (o->extra[1] != node->id)
+ continue;
+ if (o->extra[0] != direction)
+ continue;
+
+ if (regex && !port_regex(data, node, o, regex))
+ continue;
+
+ if (data->opt_mode & MODE_LIST_PORTS)
+ print_port(data, "", node, o, data->opt_verbose);
+ if (data->opt_mode & MODE_LIST_LINKS)
+ do_list_port_links(data, node, o);
+ }
+}
+
+static void do_list(struct data *data)
+{
+ struct object *n;
+
+ spa_list_for_each(n, &data->objects, link) {
+ if (n->type != OBJECT_NODE)
+ continue;
+ if (data->list_outputs)
+ do_list_ports(data, n, PW_DIRECTION_OUTPUT, data->out_regex);
+ if (data->list_inputs)
+ do_list_ports(data, n, PW_DIRECTION_INPUT, data->in_regex);
+ }
+}
+
+static int do_link_ports(struct data *data)
+{
+ uint32_t in_port = 0, out_port = 0;
+ struct object *n, *p;
+ struct object *in_node = NULL, *out_node = NULL;
+
+ spa_list_for_each(n, &data->objects, link) {
+ if (n->type != OBJECT_NODE)
+ continue;
+
+ if (out_node == NULL && node_matches(data, n, data->opt_output)) {
+ out_node = n;
+ continue;
+ } else if (in_node == NULL && node_matches(data, n, data->opt_input)) {
+ in_node = n;
+ continue;
+ }
+
+ spa_list_for_each(p, &data->objects, link) {
+ if (p->type != OBJECT_PORT)
+ continue;
+ if (p->extra[1] != n->id)
+ continue;
+
+ if (out_port == 0 && p->extra[0] == PW_DIRECTION_OUTPUT &&
+ port_matches(data, n, p, data->opt_output))
+ out_port = p->id;
+ else if (in_port == 0 && p->extra[0] == PW_DIRECTION_INPUT &&
+ port_matches(data, n, p, data->opt_input))
+ in_port = p->id;
+ }
+ }
+
+ if (in_node && out_node) {
+ int i, ret;
+ char port_id[32];
+ bool all_links_exist = true;
+
+ for (i=0;; i++) {
+ snprintf(port_id, sizeof(port_id), "%d", i);
+
+ struct object *port_out = find_node_port(data, out_node, PW_DIRECTION_OUTPUT, port_id);
+ struct object *port_in = find_node_port(data, in_node, PW_DIRECTION_INPUT, port_id);
+
+ if (!port_out && !port_in) {
+ fprintf(stderr, "Input & output port do not exist\n");
+ goto no_port;
+ } else if (!port_in) {
+ fprintf(stderr, "Input port does not exist\n");
+ goto no_port;
+ } else if (!port_out) {
+ fprintf(stderr, "Output port does not exist\n");
+ goto no_port;
+ }
+
+ pw_properties_setf(data->props, PW_KEY_LINK_OUTPUT_PORT, "%u", port_out->id);
+ pw_properties_setf(data->props, PW_KEY_LINK_INPUT_PORT, "%u", port_in->id);
+
+ if ((ret = create_link(data)) < 0 && ret != -EEXIST)
+ return ret;
+
+ if (ret >= 0)
+ all_links_exist = false;
+ }
+ return (all_links_exist ? -EEXIST : 0);
+ }
+
+ if (in_port == 0 || out_port == 0)
+ return -ENOENT;
+
+ pw_properties_setf(data->props, PW_KEY_LINK_OUTPUT_PORT, "%u", out_port);
+ pw_properties_setf(data->props, PW_KEY_LINK_INPUT_PORT, "%u", in_port);
+
+ return create_link(data);
+
+no_port:
+ return -ENOENT;
+}
+
+static int do_unlink_ports(struct data *data)
+{
+ struct object *l, *n, *p;
+ bool found_any = false;
+ struct object *in_node = NULL, *out_node = NULL;
+
+ if (data->opt_input != NULL) {
+ /* 2 args, check if they are node names */
+ spa_list_for_each(n, &data->objects, link) {
+ if (n->type != OBJECT_NODE)
+ continue;
+
+ if (out_node == NULL && node_matches(data, n, data->opt_output)) {
+ out_node = n;
+ continue;
+ } else if (in_node == NULL && node_matches(data, n, data->opt_input)) {
+ in_node = n;
+ continue;
+ }
+ }
+ }
+
+ spa_list_for_each(l, &data->objects, link) {
+ if (l->type != OBJECT_LINK)
+ continue;
+
+ if (data->opt_input == NULL) {
+ /* 1 arg, check link id */
+ if (l->id != (uint32_t)atoi(data->opt_output))
+ continue;
+ } else if (out_node && in_node) {
+ /* 2 args, check nodes */
+ if ((p = find_object(data, OBJECT_PORT, l->extra[0])) == NULL)
+ continue;
+ if ((n = find_object(data, OBJECT_NODE, p->extra[1])) == NULL)
+ continue;
+ if (n->id != out_node->id)
+ continue;
+
+ if ((p = find_object(data, OBJECT_PORT, l->extra[1])) == NULL)
+ continue;
+ if ((n = find_object(data, OBJECT_NODE, p->extra[1])) == NULL)
+ continue;
+ if (n->id != in_node->id)
+ continue;
+ } else {
+ /* 2 args, check port names */
+ if ((p = find_object(data, OBJECT_PORT, l->extra[0])) == NULL)
+ continue;
+ if ((n = find_object(data, OBJECT_NODE, p->extra[1])) == NULL)
+ continue;
+ if (!port_matches(data, n, p, data->opt_output))
+ continue;
+
+ if ((p = find_object(data, OBJECT_PORT, l->extra[1])) == NULL)
+ continue;
+ if ((n = find_object(data, OBJECT_NODE, p->extra[1])) == NULL)
+ continue;
+ if (!port_matches(data, n, p, data->opt_input))
+ continue;
+ }
+ pw_registry_destroy(data->registry, l->id);
+ found_any = true;
+ }
+ if (!found_any)
+ return -ENOENT;
+
+ core_sync(data);
+ pw_main_loop_run(data->loop);
+
+ return 0;
+}
+
+static int do_monitor_port(struct data *data, struct object *port)
+{
+ regex_t *regex = NULL;
+ bool do_print = false;
+ struct object *node;
+
+ if (port->extra[0] == PW_DIRECTION_OUTPUT && data->list_outputs) {
+ regex = data->out_regex;
+ do_print = true;
+ }
+ if (port->extra[0] == PW_DIRECTION_INPUT && data->list_inputs) {
+ regex = data->in_regex;
+ do_print = true;
+ }
+ if (!do_print)
+ return 0;
+
+ if ((node = find_object(data, OBJECT_NODE, port->extra[1])) == NULL)
+ return -ENOENT;
+
+ if (regex && !port_regex(data, node, port, regex))
+ return 0;
+
+ print_port(data, "", node, port, data->opt_verbose);
+ return 0;
+}
+
+static int do_monitor_link(struct data *data, struct object *link)
+{
+ char buffer1[1024], buffer2[1024], id[64] = "";
+ struct object *n1, *n2, *p1, *p2;
+
+ if (!(data->opt_mode & MODE_LIST_LINKS))
+ return 0;
+
+ if ((p1 = find_object(data, OBJECT_PORT, link->extra[0])) == NULL)
+ return -ENOENT;
+ if ((n1 = find_object(data, OBJECT_NODE, p1->extra[1])) == NULL)
+ return -ENOENT;
+ if (data->out_regex && !port_regex(data, n1, p1, data->out_regex))
+ return 0;
+
+ if ((p2 = find_object(data, OBJECT_PORT, link->extra[1])) == NULL)
+ return -ENOENT;
+ if ((n2 = find_object(data, OBJECT_NODE, p2->extra[1])) == NULL)
+ return -ENOENT;
+ if (data->in_regex && !port_regex(data, n2, p2, data->in_regex))
+ return 0;
+
+ if (data->opt_id)
+ snprintf(id, sizeof(id), "%4d ", link->id);
+
+ printf("%s%s%s -> %s\n", data->prefix, id,
+ port_name(buffer1, sizeof(buffer1), n1, p1),
+ port_name(buffer2, sizeof(buffer2), n2, p2));
+ return 0;
+}
+
+static void registry_event_global(void *data, uint32_t id, uint32_t permissions,
+ const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ struct data *d = data;
+ uint32_t t, extra[2];
+ struct object *obj;
+ const char *str;
+
+ if (props == NULL)
+ return;
+
+ spa_zero(extra);
+ if (spa_streq(type, PW_TYPE_INTERFACE_Node)) {
+ t = OBJECT_NODE;
+ } else if (spa_streq(type, PW_TYPE_INTERFACE_Port)) {
+ t = OBJECT_PORT;
+ if ((str = spa_dict_lookup(props, PW_KEY_PORT_DIRECTION)) == NULL)
+ return;
+ if (spa_streq(str, "in"))
+ extra[0] = PW_DIRECTION_INPUT;
+ else if (spa_streq(str, "out"))
+ extra[0] = PW_DIRECTION_OUTPUT;
+ else
+ return;
+ if ((str = spa_dict_lookup(props, PW_KEY_NODE_ID)) == NULL)
+ return;
+ extra[1] = atoi(str);
+ } else if (spa_streq(type, PW_TYPE_INTERFACE_Link)) {
+ t = OBJECT_LINK;
+ if ((str = spa_dict_lookup(props, PW_KEY_LINK_OUTPUT_PORT)) == NULL)
+ return;
+ extra[0] = atoi(str);
+ if ((str = spa_dict_lookup(props, PW_KEY_LINK_INPUT_PORT)) == NULL)
+ return;
+ extra[1] = atoi(str);
+ } else
+ return;
+
+ obj = calloc(1, sizeof(*obj));
+ obj->type = t;
+ obj->id = id;
+ obj->props = pw_properties_new_dict(props);
+ memcpy(obj->extra, extra, sizeof(extra));
+ spa_list_append(&d->objects, &obj->link);
+
+ if (d->monitoring) {
+ d->prefix = "+ ";
+ switch (obj->type) {
+ case OBJECT_PORT:
+ do_monitor_port(d, obj);
+ break;
+ case OBJECT_LINK:
+ do_monitor_link(d, obj);
+ break;
+ }
+ }
+}
+
+static void registry_event_global_remove(void *data, uint32_t id)
+{
+ struct data *d = data;
+ struct object *obj;
+
+ if ((obj = find_object(d, OBJECT_ANY, id)) == NULL)
+ return;
+
+ if (d->monitoring) {
+ d->prefix = "- ";
+ switch (obj->type) {
+ case OBJECT_PORT:
+ do_monitor_port(d, obj);
+ break;
+ case OBJECT_LINK:
+ do_monitor_link(d, obj);
+ break;
+ }
+ }
+
+ spa_list_remove(&obj->link);
+ pw_properties_free(obj->props);
+ free(obj);
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+ .global_remove = registry_event_global_remove,
+};
+
+static void on_core_done(void *data, uint32_t id, int seq)
+{
+ struct data *d = data;
+ if (d->sync == seq)
+ pw_main_loop_quit(d->loop);
+}
+
+static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct data *d = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_main_loop_quit(d->loop);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .done = on_core_done,
+ .error = on_core_error,
+};
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_main_loop_quit(data->loop);
+}
+
+static void show_help(struct data *data, const char *name, bool error)
+{
+ fprintf(error ? stderr : stdout, "%1$s : PipeWire port and link manager.\n"
+ "Generic: %1$s [options]\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " -r, --remote=NAME Remote daemon name\n"
+ "List: %1$s [options] [out-pattern] [in-pattern]\n"
+ " -o, --output List output ports\n"
+ " -i, --input List input ports\n"
+ " -l, --links List links\n"
+ " -m, --monitor Monitor links and ports\n"
+ " -I, --id List IDs\n"
+ " -v, --verbose Verbose port properties\n"
+ "Connect: %1$s [options] output input\n"
+ " -L, --linger Linger (default, unless -m is used)\n"
+ " -P, --passive Passive link\n"
+ " -p, --props=PROPS Properties as JSON object\n"
+ "Disconnect: %1$s -d [options] output input\n"
+ " %1$s -d [options] link-id\n"
+ " -d, --disconnect Disconnect ports\n",
+ name);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ int res = 0, c;
+ regex_t out_port_regex;
+ regex_t in_port_regex;
+ static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "remote", required_argument, NULL, 'r' },
+ { "output", no_argument, NULL, 'o' },
+ { "input", no_argument, NULL, 'i' },
+ { "links", no_argument, NULL, 'l' },
+ { "monitor", no_argument, NULL, 'm' },
+ { "id", no_argument, NULL, 'I' },
+ { "verbose", no_argument, NULL, 'v' },
+ { "linger", no_argument, NULL, 'L' },
+ { "passive", no_argument, NULL, 'P' },
+ { "props", required_argument, NULL, 'p' },
+ { "disconnect", no_argument, NULL, 'd' },
+ { NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ pw_init(&argc, &argv);
+ spa_list_init(&data.objects);
+
+ setlinebuf(stdout);
+
+ data.props = pw_properties_new(NULL, NULL);
+ if (data.props == NULL) {
+ fprintf(stderr, "can't create properties: %m\n");
+ return -1;
+ }
+
+ while ((c = getopt_long(argc, argv, "hVr:oilmIvLPp:d", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ show_help(&data, argv[0], NULL);
+ return 0;
+ case 'V':
+ printf("%s\n"
+ "Compiled with libpipewire %s\n"
+ "Linked with libpipewire %s\n",
+ argv[0],
+ pw_get_headers_version(),
+ pw_get_library_version());
+ return 0;
+ case 'r':
+ data.opt_remote = optarg;
+ break;
+ case 'o':
+ data.opt_mode |= MODE_LIST_OUTPUT;
+ break;
+ case 'i':
+ data.opt_mode |= MODE_LIST_INPUT;
+ break;
+ case 'l':
+ data.opt_mode |= MODE_LIST_LINKS;
+ break;
+ case 'm':
+ data.opt_mode |= MODE_MONITOR;
+ break;
+ case 'I':
+ data.opt_id = true;
+ break;
+ case 'v':
+ data.opt_verbose = true;
+ break;
+ case 'L':
+ pw_properties_set(data.props, PW_KEY_OBJECT_LINGER, "true");
+ break;
+ case 'P':
+ pw_properties_set(data.props, PW_KEY_LINK_PASSIVE, "true");
+ break;
+ case 'p':
+ pw_properties_update_string(data.props, optarg, strlen(optarg));
+ break;
+ case 'd':
+ data.opt_mode |= MODE_DISCONNECT;
+ break;
+ default:
+ show_help(&data, argv[0], true);
+ return -1;
+ }
+ }
+ if (argc == 1)
+ show_help(&data, argv[0], true);
+
+ if (data.opt_id && (data.opt_mode & MODE_LIST) == 0) {
+ fprintf(stderr, "-I option needs one or more of -l, -i or -o\n");
+ return -1;
+ }
+
+ if ((data.opt_mode & MODE_MONITOR) == 0)
+ pw_properties_set(data.props, PW_KEY_OBJECT_LINGER, "true");
+
+ if (optind < argc)
+ data.opt_output = argv[optind++];
+ if (optind < argc)
+ data.opt_input = argv[optind++];
+
+ data.loop = pw_main_loop_new(NULL);
+ if (data.loop == NULL) {
+ fprintf(stderr, "can't create mainloop: %m\n");
+ return -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);
+
+ data.context = pw_context_new(pw_main_loop_get_loop(data.loop), NULL, 0);
+ if (data.context == NULL) {
+ fprintf(stderr, "can't create context: %m\n");
+ return -1;
+ }
+
+ data.core = pw_context_connect(data.context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, data.opt_remote,
+ NULL),
+ 0);
+ if (data.core == NULL) {
+ fprintf(stderr, "can't connect: %m\n");
+ return -1;
+ }
+
+ pw_core_add_listener(data.core,
+ &data.core_listener,
+ &core_events, &data);
+
+ data.registry = pw_core_get_registry(data.core,
+ PW_VERSION_REGISTRY, 0);
+ pw_registry_add_listener(data.registry,
+ &data.registry_listener,
+ &registry_events, &data);
+
+ data.prefix = (data.opt_mode & MODE_MONITOR) ? "= " : "";
+
+ core_sync(&data);
+ pw_main_loop_run(data.loop);
+
+ if ((data.opt_mode & (MODE_LIST_PORTS|MODE_LIST_LINKS)) == MODE_LIST_LINKS)
+ data.list_inputs = data.list_outputs = true;
+ if ((data.opt_mode & MODE_LIST_INPUT) == MODE_LIST_INPUT)
+ data.list_inputs = true;
+ if ((data.opt_mode & MODE_LIST_OUTPUT) == MODE_LIST_OUTPUT)
+ data.list_outputs = true;
+
+ if (data.opt_output) {
+ if (regcomp(&out_port_regex, data.opt_output, REG_EXTENDED | REG_NOSUB) == 0)
+ data.out_regex = &out_port_regex;
+ }
+ if (data.opt_input) {
+ if (regcomp(&in_port_regex, data.opt_input, REG_EXTENDED | REG_NOSUB) == 0)
+ data.in_regex = &in_port_regex;
+ }
+
+ if (data.opt_mode & (MODE_LIST)) {
+ do_list(&data);
+ } else if (data.opt_mode & MODE_DISCONNECT) {
+ if (data.opt_output == NULL) {
+ fprintf(stderr, "missing link-id or output and input port names to disconnect\n");
+ return -1;
+ }
+ if ((res = do_unlink_ports(&data)) < 0) {
+ fprintf(stderr, "failed to unlink ports: %s\n", spa_strerror(res));
+ return -1;
+ }
+ } else {
+ if (data.opt_output == NULL ||
+ data.opt_input == NULL) {
+ fprintf(stderr, "missing output and input port names to connect\n");
+ return -1;
+ }
+ if ((res = do_link_ports(&data)) < 0) {
+ fprintf(stderr, "failed to link ports: %s\n", spa_strerror(res));
+ return -1;
+ }
+ }
+
+ if (data.opt_mode & MODE_MONITOR) {
+ data.monitoring = true;
+ pw_main_loop_run(data.loop);
+ data.monitoring = false;
+ }
+
+ if (data.out_regex)
+ regfree(data.out_regex);
+ if (data.in_regex)
+ regfree(data.in_regex);
+ spa_hook_remove(&data.registry_listener);
+ pw_proxy_destroy((struct pw_proxy*)data.registry);
+ spa_hook_remove(&data.core_listener);
+ pw_core_disconnect(data.core);
+ pw_context_destroy(data.context);
+ pw_main_loop_destroy(data.loop);
+ pw_deinit();
+
+ return res;
+}
diff --git a/src/tools/pw-loopback.c b/src/tools/pw-loopback.c
new file mode 100644
index 0000000..5f39be6
--- /dev/null
+++ b/src/tools/pw-loopback.c
@@ -0,0 +1,284 @@
+/* PipeWire
+ *
+ * Copyright © 2021 Wim Taymans <wim.taymans@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <getopt.h>
+#include <limits.h>
+#include <math.h>
+#include <locale.h>
+
+#include <spa/utils/result.h>
+#include <spa/pod/builder.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/audio/raw.h>
+#include <spa/utils/json.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/impl.h>
+
+#define DEFAULT_RATE 48000
+#define DEFAULT_CHANNELS 2
+#define DEFAULT_CHANNEL_MAP "[ FL, FR ]"
+
+struct data {
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+
+ struct pw_impl_module *module;
+ struct spa_hook module_listener;
+
+ const char *opt_node_name;
+ const char *opt_group_name;
+ const char *opt_channel_map;
+
+ uint32_t channels;
+ uint32_t latency;
+ float delay;
+
+ struct pw_properties *capture_props;
+ struct pw_properties *playback_props;
+};
+
+static void do_quit(void *data, int signal_number)
+{
+ struct data *d = data;
+ pw_main_loop_quit(d->loop);
+}
+
+static void module_destroy(void *data)
+{
+ struct data *d = data;
+ spa_hook_remove(&d->module_listener);
+ d->module = NULL;
+ pw_main_loop_quit(d->loop);
+}
+
+static const struct pw_impl_module_events module_events = {
+ PW_VERSION_IMPL_MODULE_EVENTS,
+ .destroy = module_destroy
+};
+
+
+static void show_help(struct data *data, const char *name, bool error)
+{
+ fprintf(error ? stderr : stdout, "%s [options]\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " -r, --remote Remote daemon name\n"
+ " -n, --name Node name (default '%s')\n"
+ " -g, --group Node group (default '%s')\n"
+ " -c, --channels Number of channels (default %d)\n"
+ " -m, --channel-map Channel map (default '%s')\n"
+ " -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"
+ " -P --playback Playback sink to connect to (name or serial)\n"
+ " --playback-props Playback stream properties\n",
+ name,
+ data->opt_node_name,
+ data->opt_group_name,
+ data->channels,
+ data->opt_channel_map);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0 };
+ struct pw_loop *l;
+ const char *opt_remote = NULL;
+ char cname[256], value[256];
+ char *args;
+ size_t size;
+ FILE *f;
+ static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "remote", required_argument, NULL, 'r' },
+ { "group", required_argument, NULL, 'g' },
+ { "name", required_argument, NULL, 'n' },
+ { "channels", required_argument, NULL, 'c' },
+ { "latency", required_argument, NULL, 'l' },
+ { "delay", required_argument, NULL, 'd' },
+ { "capture", required_argument, NULL, 'C' },
+ { "playback", required_argument, NULL, 'P' },
+ { "capture-props", required_argument, NULL, 'i' },
+ { "playback-props", required_argument, NULL, 'o' },
+ { NULL, 0, NULL, 0}
+ };
+ int c, res = -1;
+
+ setlocale(LC_ALL, "");
+ pw_init(&argc, &argv);
+
+ data.channels = DEFAULT_CHANNELS;
+ data.opt_channel_map = DEFAULT_CHANNEL_MAP;
+ data.opt_group_name = pw_get_client_name();
+ if (snprintf(cname, sizeof(cname), "%s-%zd", argv[0], (size_t) getpid()) > 0)
+ data.opt_group_name = cname;
+ data.opt_node_name = data.opt_group_name;
+
+ data.capture_props = pw_properties_new(NULL, NULL);
+ data.playback_props = pw_properties_new(NULL, NULL);
+ if (data.capture_props == NULL || data.playback_props == NULL) {
+ fprintf(stderr, "can't create properties: %m\n");
+ goto exit;
+ }
+
+ while ((c = getopt_long(argc, argv, "hVr:n:g:c:m:l:d:C:P:i:o:", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ show_help(&data, argv[0], false);
+ return 0;
+ case 'V':
+ printf("%s\n"
+ "Compiled with libpipewire %s\n"
+ "Linked with libpipewire %s\n",
+ argv[0],
+ pw_get_headers_version(),
+ pw_get_library_version());
+ return 0;
+ case 'r':
+ opt_remote = optarg;
+ break;
+ case 'n':
+ data.opt_node_name = optarg;
+ break;
+ case 'g':
+ data.opt_group_name = optarg;
+ break;
+ case 'c':
+ data.channels = atoi(optarg);
+ break;
+ case 'm':
+ data.opt_channel_map = optarg;
+ break;
+ case 'l':
+ data.latency = atoi(optarg) * DEFAULT_RATE / SPA_MSEC_PER_SEC;
+ break;
+ case 'd':
+ data.delay = atof(optarg);
+ break;
+ case 'C':
+ pw_properties_set(data.capture_props, PW_KEY_TARGET_OBJECT, optarg);
+ break;
+ case 'P':
+ pw_properties_set(data.playback_props, PW_KEY_TARGET_OBJECT, optarg);
+ break;
+ case 'i':
+ pw_properties_update_string(data.capture_props, optarg, strlen(optarg));
+ break;
+ case 'o':
+ pw_properties_update_string(data.playback_props, optarg, strlen(optarg));
+ break;
+ default:
+ show_help(&data, argv[0], true);
+ return -1;
+ }
+ }
+
+ data.loop = pw_main_loop_new(NULL);
+ if (data.loop == NULL) {
+ fprintf(stderr, "can't create main loop: %m\n");
+ goto exit;
+ }
+
+ l = pw_main_loop_get_loop(data.loop);
+ pw_loop_add_signal(l, SIGINT, do_quit, &data);
+ pw_loop_add_signal(l, SIGTERM, do_quit, &data);
+
+ data.context = pw_context_new(l, NULL, 0);
+ if (data.context == NULL) {
+ fprintf(stderr, "can't create context: %m\n");
+ goto exit;
+ }
+
+
+ if ((f = open_memstream(&args, &size)) == NULL) {
+ fprintf(stderr, "can't open memstream: %m\n");
+ goto exit;
+ }
+
+ fprintf(f, "{");
+
+ if (opt_remote != NULL)
+ fprintf(f, " remote.name = \"%s\"", opt_remote);
+ if (data.latency != 0)
+ fprintf(f, " node.latency = %u/%u", data.latency, DEFAULT_RATE);
+ if (data.delay != 0.0f)
+ fprintf(f, " target.delay.sec = %s",
+ spa_json_format_float(value, sizeof(value), data.delay));
+ if (data.channels != 0)
+ fprintf(f, " audio.channels = %u", data.channels);
+ if (data.opt_channel_map != NULL)
+ fprintf(f, " audio.position = %s", data.opt_channel_map);
+ if (data.opt_node_name != NULL)
+ fprintf(f, " node.name = %s", data.opt_node_name);
+
+ if (data.opt_group_name != NULL) {
+ pw_properties_set(data.capture_props, PW_KEY_NODE_GROUP, data.opt_group_name);
+ pw_properties_set(data.playback_props, PW_KEY_NODE_GROUP, data.opt_group_name);
+ }
+
+ fprintf(f, " capture.props = {");
+ pw_properties_serialize_dict(f, &data.capture_props->dict, 0);
+ fprintf(f, " } playback.props = {");
+ pw_properties_serialize_dict(f, &data.playback_props->dict, 0);
+ fprintf(f, " } }");
+ fclose(f);
+
+ pw_log_info("loading module with %s", args);
+
+ data.module = pw_context_load_module(data.context,
+ "libpipewire-module-loopback", args,
+ NULL);
+ free(args);
+
+ if (data.module == NULL) {
+ fprintf(stderr, "can't load module: %m\n");
+ goto exit;
+ }
+
+ pw_impl_module_add_listener(data.module,
+ &data.module_listener, &module_events, &data);
+
+ pw_main_loop_run(data.loop);
+
+ res = 0;
+exit:
+ if (data.module)
+ pw_impl_module_destroy(data.module);
+ if (data.context)
+ pw_context_destroy(data.context);
+ if (data.loop)
+ pw_main_loop_destroy(data.loop);
+ pw_properties_free(data.capture_props);
+ pw_properties_free(data.playback_props);
+ pw_deinit();
+
+ return res;
+}
diff --git a/src/tools/pw-metadata.c b/src/tools/pw-metadata.c
new file mode 100644
index 0000000..768bd91
--- /dev/null
+++ b/src/tools/pw-metadata.c
@@ -0,0 +1,297 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <signal.h>
+#include <math.h>
+#include <getopt.h>
+#include <locale.h>
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/utils/defs.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/filter.h>
+#include <pipewire/extensions/metadata.h>
+
+struct data {
+ struct pw_main_loop *loop;
+
+ const char *opt_remote;
+ const char *opt_name;
+ bool opt_monitor;
+ bool opt_delete;
+ uint32_t opt_id;
+ const char *opt_key;
+ const char *opt_value;
+ const char *opt_type;
+
+ struct pw_context *context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ struct pw_registry *registry;
+ struct spa_hook registry_listener;
+
+ struct pw_metadata *metadata;
+ struct spa_hook metadata_listener;
+
+ int sync;
+};
+
+
+static int metadata_property(void *data, uint32_t id,
+ const char *key, const char *type, const char *value)
+{
+ struct data *d = data;
+
+ if ((d->opt_id == SPA_ID_INVALID || d->opt_id == id) &&
+ (d->opt_key == NULL || spa_streq(d->opt_key, key))) {
+ if (key == NULL) {
+ printf("remove: id:%u all keys\n", id);
+ } else if (value == NULL) {
+ printf("remove: id:%u key:'%s'\n", id, key);
+ } else {
+ printf("update: id:%u key:'%s' value:'%s' type:'%s'\n", id, key, value, type);
+ }
+ }
+
+ 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)
+{
+ struct data *d = data;
+ const char *str;
+
+ if (!spa_streq(type, PW_TYPE_INTERFACE_Metadata))
+ return;
+
+ if (props != NULL &&
+ (str = spa_dict_lookup(props, PW_KEY_METADATA_NAME)) != NULL &&
+ !spa_streq(str, d->opt_name))
+ return;
+
+ if (d->metadata != NULL) {
+ pw_log_warn("Multiple metadata: ignoring metadata %d", id);
+ return;
+ }
+
+ printf("Found \"%s\" metadata %d\n", d->opt_name, id);
+ d->metadata = pw_registry_bind(d->registry,
+ id, type, PW_VERSION_METADATA, 0);
+
+ if (d->opt_delete) {
+ if (d->opt_id != SPA_ID_INVALID) {
+ if (d->opt_key != NULL)
+ printf("delete property: id:%u key:%s\n", d->opt_id, d->opt_key);
+ else
+ printf("delete properties: id:%u\n", d->opt_id);
+ pw_metadata_set_property(d->metadata, d->opt_id, d->opt_key, NULL, NULL);
+ } else {
+ printf("delete all properties\n");
+ pw_metadata_clear(d->metadata);
+ }
+ } else if (d->opt_id != SPA_ID_INVALID && d->opt_key != NULL && d->opt_value != NULL) {
+ printf("set property: id:%u key:%s value:%s type:%s\n",
+ d->opt_id, d->opt_key, d->opt_value, d->opt_type);
+ pw_metadata_set_property(d->metadata, d->opt_id, d->opt_key, d->opt_type, d->opt_value);
+ } else {
+ pw_metadata_add_listener(d->metadata,
+ &d->metadata_listener,
+ &metadata_events, d);
+ }
+
+ d->sync = pw_core_sync(d->core, PW_ID_CORE, d->sync);
+}
+
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+};
+
+static void on_core_done(void *data, uint32_t id, int seq)
+{
+ struct data *d = data;
+ if (d->sync == seq && !d->opt_monitor)
+ pw_main_loop_quit(d->loop);
+}
+
+static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message)
+{
+ struct data *d = data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_main_loop_quit(d->loop);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .done = on_core_done,
+ .error = on_core_error,
+};
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_main_loop_quit(data->loop);
+}
+
+static void show_help(struct data *data, const char *name, bool error)
+{
+ fprintf(error ? stderr : stdout, "%s [options] [ id [ key [ value [ type ] ] ] ]\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " -r, --remote Remote daemon name\n"
+ " -m, --monitor Monitor metadata\n"
+ " -d, --delete Delete metadata\n"
+ " -n, --name Metadata name (default: \"%s\")\n",
+ name, data->opt_name);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ int res = 0, c;
+ static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "remote", required_argument, NULL, 'r' },
+ { "monitor", no_argument, NULL, 'm' },
+ { "delete", no_argument, NULL, 'd' },
+ { "name", required_argument, NULL, 'n' },
+ { NULL, 0, NULL, 0}
+ };
+
+ setlinebuf(stdout);
+
+ setlocale(LC_ALL, "");
+ pw_init(&argc, &argv);
+
+ data.opt_name = "default";
+
+ while ((c = getopt_long(argc, argv, "hVr:mdn:", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ show_help(&data, argv[0], false);
+ return 0;
+ case 'V':
+ printf("%s\n"
+ "Compiled with libpipewire %s\n"
+ "Linked with libpipewire %s\n",
+ argv[0],
+ pw_get_headers_version(),
+ pw_get_library_version());
+ return 0;
+ case 'r':
+ data.opt_remote = optarg;
+ break;
+ case 'm':
+ data.opt_monitor = true;
+ break;
+ case 'd':
+ data.opt_delete = true;
+ break;
+ case 'n':
+ data.opt_name = optarg;
+ break;
+ default:
+ show_help(&data, argv[0], true);
+ return -1;
+ }
+ }
+
+ data.opt_id = SPA_ID_INVALID;
+ if (optind < argc)
+ data.opt_id = atoi(argv[optind++]);
+ if (optind < argc)
+ data.opt_key = argv[optind++];
+ if (optind < argc)
+ data.opt_value = argv[optind++];
+ if (optind < argc)
+ data.opt_type = argv[optind++];
+
+ data.loop = pw_main_loop_new(NULL);
+ if (data.loop == NULL) {
+ fprintf(stderr, "can't create mainloop: %m\n");
+ return -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);
+
+ data.context = pw_context_new(pw_main_loop_get_loop(data.loop), NULL, 0);
+ if (data.context == NULL) {
+ fprintf(stderr, "can't create context: %m\n");
+ return -1;
+ }
+
+ data.core = pw_context_connect(data.context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, data.opt_remote,
+ NULL),
+ 0);
+ if (data.core == NULL) {
+ fprintf(stderr, "can't connect: %m\n");
+ return -1;
+ }
+
+ pw_core_add_listener(data.core,
+ &data.core_listener,
+ &core_events, &data);
+
+ data.registry = pw_core_get_registry(data.core,
+ PW_VERSION_REGISTRY, 0);
+ pw_registry_add_listener(data.registry,
+ &data.registry_listener,
+ &registry_events, &data);
+
+ data.sync = pw_core_sync(data.core, PW_ID_CORE, data.sync);
+
+ pw_main_loop_run(data.loop);
+
+ if (data.metadata)
+ pw_proxy_destroy((struct pw_proxy*)data.metadata);
+ spa_hook_remove(&data.registry_listener);
+ pw_proxy_destroy((struct pw_proxy*)data.registry);
+ spa_hook_remove(&data.core_listener);
+ pw_core_disconnect(data.core);
+ pw_context_destroy(data.context);
+ pw_main_loop_destroy(data.loop);
+ pw_deinit();
+
+ return res;
+}
diff --git a/src/tools/pw-mididump.c b/src/tools/pw-mididump.c
new file mode 100644
index 0000000..d6b803f
--- /dev/null
+++ b/src/tools/pw-mididump.c
@@ -0,0 +1,233 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <signal.h>
+#include <math.h>
+#include <getopt.h>
+#include <locale.h>
+
+#include <spa/utils/result.h>
+#include <spa/utils/defs.h>
+#include <spa/control/control.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/props.h>
+
+#include <pipewire/pipewire.h>
+#include <pipewire/filter.h>
+
+#include "midifile.h"
+
+struct data;
+
+struct port {
+ struct data *data;
+};
+
+struct data {
+ struct pw_main_loop *loop;
+ const char *opt_remote;
+ struct pw_filter *filter;
+ struct port *in_port;
+ int64_t clock_time;
+};
+
+static int dump_file(const char *filename)
+{
+ struct midi_file *file;
+ struct midi_file_info info;
+ struct midi_event ev;
+
+ file = midi_file_open(filename, "r", &info);
+ if (file == NULL) {
+ fprintf(stderr, "error opening %s: %m\n", filename);
+ return -1;
+ }
+
+ printf("opened %s\n", filename);
+
+ while (midi_file_read_event(file, &ev) == 1) {
+ midi_file_dump_event(stdout, &ev);
+ }
+ midi_file_close(file);
+
+ return 0;
+}
+
+static void on_process(void *_data, struct spa_io_position *position)
+{
+ struct data *data = _data;
+ struct pw_buffer *b;
+ struct spa_buffer *buf;
+ struct spa_data *d;
+ struct spa_pod *pod;
+ struct spa_pod_control *c;
+ uint64_t frame;
+
+ frame = data->clock_time;
+ data->clock_time += position->clock.duration;
+
+ b = pw_filter_dequeue_buffer(data->in_port);
+ if (b == NULL)
+ return;
+
+ buf = b->buffer;
+ d = &buf->datas[0];
+
+ if (d->data == NULL)
+ return;
+
+ if ((pod = spa_pod_from_data(d->data, d->maxsize, d->chunk->offset, d->chunk->size)) == NULL)
+ return;
+ if (!spa_pod_is_sequence(pod))
+ return;
+
+ SPA_POD_SEQUENCE_FOREACH((struct spa_pod_sequence*)pod, c) {
+ struct midi_event ev;
+
+ if (c->type != SPA_CONTROL_Midi)
+ 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);
+
+ printf("%4d: ", c->offset);
+ midi_file_dump_event(stdout, &ev);
+ }
+
+ pw_filter_queue_buffer(data->in_port, b);
+}
+
+static const struct pw_filter_events filter_events = {
+ PW_VERSION_FILTER_EVENTS,
+ .process = on_process,
+};
+
+static void do_quit(void *userdata, int signal_number)
+{
+ struct data *data = userdata;
+ pw_main_loop_quit(data->loop);
+}
+
+static int dump_filter(struct data *data)
+{
+ data->loop = pw_main_loop_new(NULL);
+ if (data->loop == NULL)
+ return -errno;
+
+ 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);
+
+ data->filter = pw_filter_new_simple(
+ pw_main_loop_get_loop(data->loop),
+ "midi-dump",
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, data->opt_remote,
+ PW_KEY_MEDIA_TYPE, "Midi",
+ PW_KEY_MEDIA_CATEGORY, "Filter",
+ PW_KEY_MEDIA_ROLE, "DSP",
+ NULL),
+ &filter_events,
+ data);
+
+ data->in_port = pw_filter_add_port(data->filter,
+ PW_DIRECTION_INPUT,
+ PW_FILTER_PORT_FLAG_MAP_BUFFERS,
+ sizeof(struct port),
+ pw_properties_new(
+ PW_KEY_FORMAT_DSP, "8 bit raw midi",
+ PW_KEY_PORT_NAME, "input",
+ NULL),
+ NULL, 0);
+
+ if (pw_filter_connect(data->filter, PW_FILTER_FLAG_RT_PROCESS, NULL, 0) < 0) {
+ fprintf(stderr, "can't connect\n");
+ return -1;
+ }
+
+ pw_main_loop_run(data->loop);
+
+ pw_filter_destroy(data->filter);
+ pw_main_loop_destroy(data->loop);
+
+ return 0;
+}
+
+static void show_help(const char *name, bool error)
+{
+ fprintf(error ? stderr : stdout, "%s [options] [FILE]\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " -r, --remote Remote daemon name\n",
+ name);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0, };
+ int res = 0, c;
+ static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "remote", required_argument, NULL, 'r' },
+ { NULL, 0, NULL, 0}
+ };
+
+ setlocale(LC_ALL, "");
+ pw_init(&argc, &argv);
+
+ setlinebuf(stdout);
+
+ while ((c = getopt_long(argc, argv, "hVr:", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ show_help(argv[0], false);
+ return 0;
+ case 'V':
+ printf("%s\n"
+ "Compiled with libpipewire %s\n"
+ "Linked with libpipewire %s\n",
+ argv[0],
+ pw_get_headers_version(),
+ pw_get_library_version());
+ return 0;
+ case 'r':
+ data.opt_remote = optarg;
+ break;
+ default:
+ show_help(argv[0], true);
+ return -1;
+ }
+ }
+
+ if (optind < argc) {
+ res = dump_file(argv[optind]);
+ } else {
+ res = dump_filter(&data);
+ }
+ pw_deinit();
+ return res;
+}
diff --git a/src/tools/pw-mon.c b/src/tools/pw-mon.c
new file mode 100644
index 0000000..2eeb4dd
--- /dev/null
+++ b/src/tools/pw-mon.c
@@ -0,0 +1,875 @@
+/* PipeWire
+ *
+ * Copyright © 2018 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <signal.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <locale.h>
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/utils/ansi.h>
+#include <spa/debug/pod.h>
+#include <spa/debug/format.h>
+#include <spa/debug/types.h>
+
+#include <pipewire/pipewire.h>
+
+struct proxy_data;
+
+typedef void (*print_func_t) (struct proxy_data *data);
+
+static struct pprefix {
+ const char *prefix;
+ const char *suffix;
+} pprefix[2] = {
+ { .prefix = " ", .suffix = "" },
+ { .prefix = "*", .suffix = "" },
+};
+
+#define with_prefix(use_prefix_) \
+ for (bool once_ = !!printf("%s", (pprefix[!!(use_prefix_)]).prefix); \
+ once_; \
+ once_ = false, printf("%s", (pprefix[!!(use_prefix_)]).suffix))
+
+
+struct param {
+ struct spa_list link;
+ uint32_t id;
+ int seq;
+ struct spa_pod *param;
+ unsigned int changed:1;
+};
+
+struct data {
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ struct pw_registry *registry;
+ struct spa_hook registry_listener;
+
+ struct spa_list pending_list;
+ struct spa_list global_list;
+};
+
+struct proxy_data {
+ struct data *data;
+ bool first;
+ struct pw_proxy *proxy;
+ uint32_t id;
+ uint32_t permissions;
+ uint32_t version;
+ char *type;
+ void *info;
+ pw_destroy_t destroy;
+ struct spa_hook proxy_listener;
+ struct spa_hook object_listener;
+ int pending_seq;
+ struct spa_list global_link;
+ struct spa_list pending_link;
+ print_func_t print_func;
+ struct spa_list param_list;
+};
+
+static void add_pending(struct proxy_data *pd)
+{
+ struct data *d = pd->data;
+
+ if (pd->pending_seq == 0) {
+ spa_list_append(&d->pending_list, &pd->pending_link);
+ }
+ pd->pending_seq = pw_core_sync(d->core, 0, pd->pending_seq);
+}
+
+static void remove_pending(struct proxy_data *pd)
+{
+ if (pd->pending_seq != 0) {
+ spa_list_remove(&pd->pending_link);
+ pd->pending_seq = 0;
+ }
+}
+
+static void on_core_done(void *data, uint32_t id, int seq)
+{
+ struct data *d = data;
+ struct proxy_data *pd, *t;
+
+ spa_list_for_each_safe(pd, t, &d->pending_list, pending_link) {
+ if (pd->pending_seq == seq) {
+ remove_pending(pd);
+ pd->print_func(pd);
+ }
+ }
+}
+
+static void clear_params(struct proxy_data *data)
+{
+ struct param *p;
+ spa_list_consume(p, &data->param_list, link) {
+ spa_list_remove(&p->link);
+ free(p);
+ }
+}
+
+static void remove_params(struct proxy_data *data, uint32_t id, int seq)
+{
+ struct param *p, *t;
+
+ spa_list_for_each_safe(p, t, &data->param_list, link) {
+ if (p->id == id && seq != p->seq) {
+ spa_list_remove(&p->link);
+ free(p);
+ }
+ }
+}
+
+static void event_param(void *_data, int seq, uint32_t id,
+ uint32_t index, uint32_t next, const struct spa_pod *param)
+{
+ struct proxy_data *data = _data;
+ struct param *p;
+
+ /* remove all params with the same id and older seq */
+ remove_params(data, id, seq);
+
+ /* add new param */
+ p = malloc(sizeof(struct param) + SPA_POD_SIZE(param));
+ if (p == NULL) {
+ pw_log_error("can't add param: %m");
+ return;
+ }
+
+ p->id = id;
+ p->seq = seq;
+ p->param = SPA_PTROFF(p, sizeof(struct param), struct spa_pod);
+ p->changed = true;
+ memcpy(p->param, param, SPA_POD_SIZE(param));
+ spa_list_append(&data->param_list, &p->link);
+}
+
+static void print_params(struct proxy_data *data, bool use_prefix)
+{
+ struct param *p;
+
+ with_prefix(use_prefix) {
+ printf("\tparams:\n");
+ }
+
+ spa_list_for_each(p, &data->param_list, link) {
+ with_prefix(p->changed) {
+ printf("\t id:%u (%s)\n",
+ p->id,
+ spa_debug_type_find_name(spa_type_param, p->id));
+ if (spa_pod_is_object_type(p->param, SPA_TYPE_OBJECT_Format))
+ spa_debug_format(10, NULL, p->param);
+ else
+ spa_debug_pod(10, NULL, p->param);
+ }
+ p->changed = false;
+ }
+}
+
+static void print_properties(const struct spa_dict *props, bool use_prefix)
+{
+ const struct spa_dict_item *item;
+
+ with_prefix(use_prefix) {
+ printf("\tproperties:\n");
+ if (props == NULL || props->n_items == 0) {
+ printf("\t\tnone\n");
+ return;
+ }
+ }
+
+ spa_dict_for_each(item, props) {
+ with_prefix(use_prefix) {
+ if (item->value)
+ printf("\t\t%s = \"%s\"\n", item->key, item->value);
+ else
+ printf("\t\t%s = (null)\n", item->key);
+ }
+ }
+}
+
+#define MARK_CHANGE(f) (!!(print_mark && ((info)->change_mask & (f))))
+
+static void on_core_info(void *data, const struct pw_core_info *info)
+{
+ bool print_all = true, print_mark = true;
+
+ printf("\ttype: %s\n", PW_TYPE_INTERFACE_Core);
+ printf("\tcookie: %u\n", info->cookie);
+ printf("\tuser-name: \"%s\"\n", info->user_name);
+ printf("\thost-name: \"%s\"\n", info->host_name);
+ printf("\tversion: \"%s\"\n", info->version);
+ printf("\tname: \"%s\"\n", info->name);
+ if (print_all) {
+ print_properties(info->props, MARK_CHANGE(PW_CORE_CHANGE_MASK_PROPS));
+ }
+}
+
+static void module_event_info(void *_data, const struct pw_module_info *info)
+{
+ struct proxy_data *data = _data;
+ bool print_all, print_mark;
+
+ print_all = true;
+ if (data->info == NULL) {
+ printf("added:\n");
+ print_mark = false;
+ }
+ else {
+ printf("changed:\n");
+ print_mark = true;
+ }
+
+ info = data->info = pw_module_info_update(data->info, info);
+
+ printf("\tid: %d\n", data->id);
+ printf("\tpermissions: "PW_PERMISSION_FORMAT"\n",
+ PW_PERMISSION_ARGS(data->permissions));
+ printf("\ttype: %s (version %d)\n", data->type, data->version);
+ printf("\tname: \"%s\"\n", info->name);
+ printf("\tfilename: \"%s\"\n", info->filename);
+ printf("\targs: \"%s\"\n", info->args);
+ if (print_all) {
+ print_properties(info->props, MARK_CHANGE(PW_MODULE_CHANGE_MASK_PROPS));
+ }
+}
+
+static const struct pw_module_events module_events = {
+ PW_VERSION_MODULE_EVENTS,
+ .info = module_event_info,
+};
+
+static void print_node(struct proxy_data *data)
+{
+ struct pw_node_info *info = data->info;
+ bool print_all, print_mark;
+
+ print_all = true;
+ if (data->first) {
+ printf("added:\n");
+ print_mark = false;
+ data->first = false;
+ }
+ else {
+ printf("changed:\n");
+ print_mark = true;
+ }
+
+ printf("\tid: %d\n", data->id);
+ printf("\tpermissions: "PW_PERMISSION_FORMAT"\n",
+ PW_PERMISSION_ARGS(data->permissions));
+ printf("\ttype: %s (version %d)\n", data->type, data->version);
+ if (print_all) {
+ print_params(data, MARK_CHANGE(PW_NODE_CHANGE_MASK_PARAMS));
+ with_prefix(MARK_CHANGE(PW_NODE_CHANGE_MASK_INPUT_PORTS)) {
+ printf("\tinput ports: %u/%u\n",
+ info->n_input_ports, info->max_input_ports);
+ }
+ with_prefix(MARK_CHANGE(PW_NODE_CHANGE_MASK_OUTPUT_PORTS)) {
+ printf("\toutput ports: %u/%u\n",
+ info->n_output_ports, info->max_output_ports);
+ }
+ with_prefix(MARK_CHANGE(PW_NODE_CHANGE_MASK_STATE)) {
+ printf("\tstate: \"%s\"",
+ pw_node_state_as_string(info->state));
+ }
+ if (info->state == PW_NODE_STATE_ERROR && info->error)
+ printf(" \"%s\"\n", info->error);
+ else
+ printf("\n");
+ print_properties(info->props, MARK_CHANGE(PW_NODE_CHANGE_MASK_PROPS));
+ }
+}
+
+static void node_event_info(void *_data, const struct pw_node_info *info)
+{
+ struct proxy_data *data = _data;
+ uint32_t i;
+
+ info = data->info = pw_node_info_update(data->info, info);
+
+ if (info->change_mask & PW_NODE_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ if (info->params[i].user == 0)
+ continue;
+ remove_params(data, info->params[i].id, 0);
+ if (!SPA_FLAG_IS_SET(info->params[i].flags, SPA_PARAM_INFO_READ))
+ continue;
+ pw_node_enum_params((struct pw_node*)data->proxy,
+ 0, info->params[i].id, 0, 0, NULL);
+ info->params[i].user = 0;
+ }
+ add_pending(data);
+ }
+
+ if (data->pending_seq == 0)
+ data->print_func(data);
+}
+
+static const struct pw_node_events node_events = {
+ PW_VERSION_NODE_EVENTS,
+ .info = node_event_info,
+ .param = event_param
+};
+
+static void print_port(struct proxy_data *data)
+{
+ struct pw_port_info *info = data->info;
+ bool print_all, print_mark;
+
+ print_all = true;
+ if (data->first) {
+ printf("added:\n");
+ print_mark = false;
+ data->first = false;
+ }
+ else {
+ printf("changed:\n");
+ print_mark = true;
+ }
+
+ printf("\tid: %d\n", data->id);
+ printf("\tpermissions: "PW_PERMISSION_FORMAT"\n",
+ PW_PERMISSION_ARGS(data->permissions));
+ printf("\ttype: %s (version %d)\n", data->type, data->version);
+
+ printf("\tdirection: \"%s\"\n", pw_direction_as_string(info->direction));
+ if (print_all) {
+ print_params(data, MARK_CHANGE(PW_PORT_CHANGE_MASK_PARAMS));
+ print_properties(info->props, MARK_CHANGE(PW_PORT_CHANGE_MASK_PROPS));
+ }
+}
+
+static void port_event_info(void *_data, const struct pw_port_info *info)
+{
+ struct proxy_data *data = _data;
+ uint32_t i;
+
+ info = data->info = pw_port_info_update(data->info, info);
+
+ if (info->change_mask & PW_PORT_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ if (info->params[i].user == 0)
+ continue;
+ remove_params(data, info->params[i].id, 0);
+ if (!SPA_FLAG_IS_SET(info->params[i].flags, SPA_PARAM_INFO_READ))
+ continue;
+ pw_port_enum_params((struct pw_port*)data->proxy,
+ 0, info->params[i].id, 0, 0, NULL);
+ info->params[i].user = 0;
+ }
+ add_pending(data);
+ }
+
+ if (data->pending_seq == 0)
+ data->print_func(data);
+}
+
+static const struct pw_port_events port_events = {
+ PW_VERSION_PORT_EVENTS,
+ .info = port_event_info,
+ .param = event_param
+};
+
+static void factory_event_info(void *_data, const struct pw_factory_info *info)
+{
+ struct proxy_data *data = _data;
+ bool print_all, print_mark;
+
+ print_all = true;
+ if (data->info == NULL) {
+ printf("added:\n");
+ print_mark = false;
+ }
+ else {
+ printf("changed:\n");
+ print_mark = true;
+ }
+
+ info = data->info = pw_factory_info_update(data->info, info);
+
+ printf("\tid: %d\n", data->id);
+ printf("\tpermissions: "PW_PERMISSION_FORMAT"\n",
+ PW_PERMISSION_ARGS(data->permissions));
+ printf("\ttype: %s (version %d)\n", data->type, data->version);
+
+ printf("\tname: \"%s\"\n", info->name);
+ printf("\tobject-type: %s/%d\n", info->type, info->version);
+ if (print_all) {
+ print_properties(info->props, MARK_CHANGE(PW_FACTORY_CHANGE_MASK_PROPS));
+ }
+}
+
+static const struct pw_factory_events factory_events = {
+ PW_VERSION_FACTORY_EVENTS,
+ .info = factory_event_info
+};
+
+static void client_event_info(void *_data, const struct pw_client_info *info)
+{
+ struct proxy_data *data = _data;
+ bool print_all, print_mark;
+
+ print_all = true;
+ if (data->info == NULL) {
+ printf("added:\n");
+ print_mark = false;
+ }
+ else {
+ printf("changed:\n");
+ print_mark = true;
+ }
+
+ info = data->info = pw_client_info_update(data->info, info);
+
+ printf("\tid: %d\n", data->id);
+ printf("\tpermissions: "PW_PERMISSION_FORMAT"\n",
+ PW_PERMISSION_ARGS(data->permissions));
+ printf("\ttype: %s (version %d)\n", data->type, data->version);
+
+ if (print_all) {
+ print_properties(info->props, MARK_CHANGE(PW_CLIENT_CHANGE_MASK_PROPS));
+ }
+}
+
+static const struct pw_client_events client_events = {
+ PW_VERSION_CLIENT_EVENTS,
+ .info = client_event_info
+};
+
+static void link_event_info(void *_data, const struct pw_link_info *info)
+{
+ struct proxy_data *data = _data;
+ bool print_all, print_mark;
+
+ print_all = true;
+ if (data->info == NULL) {
+ printf("added:\n");
+ print_mark = false;
+ }
+ else {
+ printf("changed:\n");
+ print_mark = true;
+ }
+
+ info = data->info = pw_link_info_update(data->info, info);
+
+ printf("\tid: %d\n", data->id);
+ printf("\tpermissions: "PW_PERMISSION_FORMAT"\n",
+ PW_PERMISSION_ARGS(data->permissions));
+ printf("\ttype: %s (version %d)\n", data->type, data->version);
+
+ printf("\toutput-node-id: %u\n", info->output_node_id);
+ printf("\toutput-port-id: %u\n", info->output_port_id);
+ printf("\tinput-node-id: %u\n", info->input_node_id);
+ printf("\tinput-port-id: %u\n", info->input_port_id);
+ if (print_all) {
+ with_prefix(MARK_CHANGE(PW_LINK_CHANGE_MASK_STATE)) {
+ printf("\tstate: \"%s\"",
+ pw_link_state_as_string(info->state));
+ }
+ if (info->state == PW_LINK_STATE_ERROR && info->error)
+ printf(" \"%s\"\n", info->error);
+ else
+ printf("\n");
+ with_prefix(MARK_CHANGE(PW_LINK_CHANGE_MASK_FORMAT)) {
+ printf("\tformat:\n");
+ if (info->format)
+ spa_debug_format(2, NULL, info->format);
+ else
+ printf("\t\tnone\n");
+ }
+ print_properties(info->props, MARK_CHANGE(PW_LINK_CHANGE_MASK_PROPS));
+ }
+}
+
+static const struct pw_link_events link_events = {
+ PW_VERSION_LINK_EVENTS,
+ .info = link_event_info
+};
+
+static void print_device(struct proxy_data *data)
+{
+ struct pw_device_info *info = data->info;
+ bool print_all, print_mark;
+
+ print_all = true;
+ if (data->first) {
+ printf("added:\n");
+ print_mark = false;
+ data->first = false;
+ }
+ else {
+ printf("changed:\n");
+ print_mark = true;
+ }
+
+ printf("\tid: %d\n", data->id);
+ printf("\tpermissions: "PW_PERMISSION_FORMAT"\n",
+ PW_PERMISSION_ARGS(data->permissions));
+ printf("\ttype: %s (version %d)\n", data->type, data->version);
+
+ if (print_all) {
+ print_params(data, MARK_CHANGE(PW_DEVICE_CHANGE_MASK_PARAMS));
+ print_properties(info->props, MARK_CHANGE(PW_DEVICE_CHANGE_MASK_PROPS));
+ }
+}
+
+
+static void device_event_info(void *_data, const struct pw_device_info *info)
+{
+ struct proxy_data *data = _data;
+ uint32_t i;
+
+ info = data->info = pw_device_info_update(data->info, info);
+
+ if (info->change_mask & PW_DEVICE_CHANGE_MASK_PARAMS) {
+ for (i = 0; i < info->n_params; i++) {
+ if (info->params[i].user == 0)
+ continue;
+ remove_params(data, info->params[i].id, 0);
+ if (!SPA_FLAG_IS_SET(info->params[i].flags, SPA_PARAM_INFO_READ))
+ continue;
+ pw_device_enum_params((struct pw_device*)data->proxy,
+ 0, info->params[i].id, 0, 0, NULL);
+ info->params[i].user = 0;
+ }
+ add_pending(data);
+ }
+ if (data->pending_seq == 0)
+ data->print_func(data);
+}
+
+static const struct pw_device_events device_events = {
+ PW_VERSION_DEVICE_EVENTS,
+ .info = device_event_info,
+ .param = event_param
+};
+
+static void
+removed_proxy (void *data)
+{
+ struct proxy_data *pd = data;
+ pw_proxy_destroy(pd->proxy);
+}
+
+static void
+destroy_proxy (void *data)
+{
+ struct proxy_data *pd = data;
+
+ spa_list_remove(&pd->global_link);
+
+ spa_hook_remove(&pd->object_listener);
+ spa_hook_remove(&pd->proxy_listener);
+
+ clear_params(pd);
+ remove_pending(pd);
+ free(pd->type);
+
+ if (pd->info == NULL)
+ return;
+ if (pd->destroy)
+ pd->destroy(pd->info);
+ pd->info = NULL;
+}
+
+static const struct pw_proxy_events proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .removed = removed_proxy,
+ .destroy = destroy_proxy,
+};
+
+static void registry_event_global(void *data, uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ struct data *d = data;
+ struct pw_proxy *proxy;
+ uint32_t client_version;
+ const void *events;
+ struct proxy_data *pd;
+ pw_destroy_t destroy;
+ print_func_t print_func = NULL;
+
+ if (spa_streq(type, PW_TYPE_INTERFACE_Node)) {
+ events = &node_events;
+ client_version = PW_VERSION_NODE;
+ destroy = (pw_destroy_t) pw_node_info_free;
+ print_func = print_node;
+ } else if (spa_streq(type, PW_TYPE_INTERFACE_Port)) {
+ events = &port_events;
+ client_version = PW_VERSION_PORT;
+ destroy = (pw_destroy_t) pw_port_info_free;
+ print_func = print_port;
+ } else if (spa_streq(type, PW_TYPE_INTERFACE_Module)) {
+ events = &module_events;
+ client_version = PW_VERSION_MODULE;
+ destroy = (pw_destroy_t) pw_module_info_free;
+ } else if (spa_streq(type, PW_TYPE_INTERFACE_Device)) {
+ events = &device_events;
+ client_version = PW_VERSION_DEVICE;
+ destroy = (pw_destroy_t) pw_device_info_free;
+ print_func = print_device;
+ } else if (spa_streq(type, PW_TYPE_INTERFACE_Factory)) {
+ events = &factory_events;
+ client_version = PW_VERSION_FACTORY;
+ destroy = (pw_destroy_t) pw_factory_info_free;
+ } else if (spa_streq(type, PW_TYPE_INTERFACE_Client)) {
+ events = &client_events;
+ client_version = PW_VERSION_CLIENT;
+ destroy = (pw_destroy_t) pw_client_info_free;
+ } else if (spa_streq(type, PW_TYPE_INTERFACE_Link)) {
+ events = &link_events;
+ client_version = PW_VERSION_LINK;
+ destroy = (pw_destroy_t) pw_link_info_free;
+ } else {
+ printf("added:\n");
+ printf("\tid: %u\n", id);
+ printf("\tpermissions: "PW_PERMISSION_FORMAT"\n",
+ PW_PERMISSION_ARGS(permissions));
+ printf("\ttype: %s (version %d)\n", type, version);
+ print_properties(props, false);
+ return;
+ }
+
+ proxy = pw_registry_bind(d->registry, id, type,
+ client_version,
+ sizeof(struct proxy_data));
+ if (proxy == NULL)
+ goto no_mem;
+
+ pd = pw_proxy_get_user_data(proxy);
+ pd->data = d;
+ pd->first = true;
+ pd->proxy = proxy;
+ pd->id = id;
+ pd->permissions = permissions;
+ pd->version = version;
+ pd->type = strdup(type);
+ pd->destroy = destroy;
+ pd->pending_seq = 0;
+ pd->print_func = print_func;
+ spa_list_init(&pd->param_list);
+ pw_proxy_add_object_listener(proxy, &pd->object_listener, events, pd);
+ pw_proxy_add_listener(proxy, &pd->proxy_listener, &proxy_events, pd);
+ spa_list_append(&d->global_list, &pd->global_link);
+
+ return;
+
+no_mem:
+ fprintf(stderr, "failed to create proxy");
+ return;
+}
+
+static struct proxy_data *find_proxy(struct data *d, uint32_t id)
+{
+ struct proxy_data *pd;
+ spa_list_for_each(pd, &d->global_list, global_link) {
+ if (pd->id == id)
+ return pd;
+ }
+ return NULL;
+}
+
+static void registry_event_global_remove(void *data, uint32_t id)
+{
+ struct data *d = data;
+ struct proxy_data *pd;
+
+ printf("removed:\n");
+ printf("\tid: %u\n", id);
+
+ pd = find_proxy(d, id);
+ if (pd == NULL)
+ return;
+ if (pd->proxy)
+ pw_proxy_destroy(pd->proxy);
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+ .global_remove = registry_event_global_remove,
+};
+
+static void on_core_error(void *_data, uint32_t id, int seq, int res, const char *message)
+{
+ struct data *data = _data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_main_loop_quit(data->loop);
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .info = on_core_info,
+ .done = on_core_done,
+ .error = on_core_error,
+};
+
+static void do_quit(void *data, int signal_number)
+{
+ struct data *d = data;
+ pw_main_loop_quit(d->loop);
+}
+
+static void show_help(const char *name, bool error)
+{
+ fprintf(error ? stderr : stdout, "%s [options]\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " -r, --remote Remote daemon name\n"
+ " -N, --no-colors disable color output\n"
+ " -C, --color[=WHEN] whether to enable color support. WHEN is `never`, `always`, or `auto`\n",
+ name);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0 };
+ struct pw_loop *l;
+ const char *opt_remote = NULL;
+ static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "remote", required_argument, NULL, 'r' },
+ { "no-colors", no_argument, NULL, 'N' },
+ { "color", optional_argument, NULL, 'C' },
+ { NULL, 0, NULL, 0}
+ };
+ int c;
+ bool colors = false;
+
+ setlocale(LC_ALL, "");
+ pw_init(&argc, &argv);
+
+ setlinebuf(stdout);
+
+ if (isatty(STDERR_FILENO) && getenv("NO_COLOR") == NULL)
+ colors = true;
+
+ while ((c = getopt_long(argc, argv, "hVr:NC", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ show_help(argv[0], false);
+ return 0;
+ case 'V':
+ printf("%s\n"
+ "Compiled with libpipewire %s\n"
+ "Linked with libpipewire %s\n",
+ argv[0],
+ pw_get_headers_version(),
+ pw_get_library_version());
+ return 0;
+ case 'r':
+ opt_remote = optarg;
+ break;
+ case 'N' :
+ colors = false;
+ break;
+ case 'C' :
+ if (optarg == NULL || !strcmp(optarg, "auto"))
+ break; /* nothing to do, tty detection was done
+ before parsing options */
+ else if (!strcmp(optarg, "never"))
+ colors = false;
+ else if (!strcmp(optarg, "always"))
+ colors = true;
+ else {
+ fprintf(stderr, "Invalid color: %s\n", optarg);
+ show_help(argv[0], true);
+ return -1;
+ }
+ break;
+ default:
+ show_help(argv[0], true);
+ return -1;
+ }
+ }
+
+ if (colors) {
+ pprefix[1].prefix = SPA_ANSI_RED "*";
+ pprefix[1].suffix = SPA_ANSI_RESET;
+ }
+
+ data.loop = pw_main_loop_new(NULL);
+ if (data.loop == NULL) {
+ fprintf(stderr, "can't create main loop: %m\n");
+ return -1;
+ }
+
+ l = pw_main_loop_get_loop(data.loop);
+ pw_loop_add_signal(l, SIGINT, do_quit, &data);
+ pw_loop_add_signal(l, SIGTERM, do_quit, &data);
+
+ data.context = pw_context_new(l, NULL, 0);
+ if (data.context == NULL) {
+ fprintf(stderr, "can't create context: %m\n");
+ return -1;
+ }
+
+ spa_list_init(&data.pending_list);
+ spa_list_init(&data.global_list);
+
+ data.core = pw_context_connect(data.context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, opt_remote,
+ NULL),
+ 0);
+ if (data.core == NULL) {
+ fprintf(stderr, "can't connect: %m\n");
+ return -1;
+ }
+
+ pw_core_add_listener(data.core,
+ &data.core_listener,
+ &core_events, &data);
+ data.registry = pw_core_get_registry(data.core,
+ PW_VERSION_REGISTRY, 0);
+ pw_registry_add_listener(data.registry,
+ &data.registry_listener,
+ &registry_events, &data);
+
+ pw_main_loop_run(data.loop);
+
+ spa_hook_remove(&data.registry_listener);
+ pw_proxy_destroy((struct pw_proxy*)data.registry);
+ spa_hook_remove(&data.core_listener);
+ pw_context_destroy(data.context);
+ pw_main_loop_destroy(data.loop);
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/tools/pw-profiler.c b/src/tools/pw-profiler.c
new file mode 100644
index 0000000..7ee85bb
--- /dev/null
+++ b/src/tools/pw-profiler.c
@@ -0,0 +1,665 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <signal.h>
+#include <getopt.h>
+#include <locale.h>
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/pod/parser.h>
+#include <spa/debug/types.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/profiler.h>
+
+#define MAX_NAME 128
+#define MAX_FOLLOWERS 64
+#define DEFAULT_FILENAME "profiler.log"
+
+struct follower {
+ uint32_t id;
+ char name[MAX_NAME];
+};
+
+struct data {
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ struct pw_registry *registry;
+ struct spa_hook registry_listener;
+
+ const char *filename;
+ FILE *output;
+
+ int64_t count;
+ int64_t start_status;
+ int64_t last_status;
+
+ struct pw_proxy *profiler;
+ struct spa_hook profiler_listener;
+ int check_profiler;
+
+ uint32_t driver_id;
+
+ int n_followers;
+ struct follower followers[MAX_FOLLOWERS];
+};
+
+struct measurement {
+ int64_t period;
+ int64_t prev_signal;
+ int64_t signal;
+ int64_t awake;
+ int64_t finish;
+ int32_t status;
+};
+
+struct point {
+ int64_t count;
+ float cpu_load[3];
+ struct spa_io_clock clock;
+ struct measurement driver;
+ struct measurement follower[MAX_FOLLOWERS];
+};
+
+static int process_info(struct data *d, const struct spa_pod *pod, struct point *point)
+{
+ return 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]));
+}
+
+static int process_clock(struct data *d, const struct spa_pod *pod, struct point *point)
+{
+ return 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)),
+ SPA_POD_Long(&point->clock.nsec),
+ SPA_POD_Fraction(&point->clock.rate),
+ SPA_POD_Long(&point->clock.position),
+ 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));
+}
+
+static int process_driver_block(struct data *d, const struct spa_pod *pod, struct point *point)
+{
+ char *name = NULL;
+ uint32_t driver_id = 0;
+ struct measurement driver;
+ int res;
+
+ spa_zero(driver);
+ if ((res = spa_pod_parse_struct(pod,
+ SPA_POD_Int(&driver_id),
+ SPA_POD_String(&name),
+ SPA_POD_Long(&driver.prev_signal),
+ SPA_POD_Long(&driver.signal),
+ SPA_POD_Long(&driver.awake),
+ SPA_POD_Long(&driver.finish),
+ SPA_POD_Int(&driver.status))) < 0)
+ return res;
+
+ if (d->driver_id == 0) {
+ d->driver_id = driver_id;
+ printf("logging driver %u\n", driver_id);
+ }
+ else if (d->driver_id != driver_id)
+ return -1;
+
+ point->driver = driver;
+ return 0;
+}
+
+static int find_follower(struct data *d, uint32_t id, const char *name)
+{
+ int i;
+ for (i = 0; i < d->n_followers; i++) {
+ if (d->followers[i].id == id && spa_streq(d->followers[i].name, name))
+ return i;
+ }
+ return -1;
+}
+
+static int add_follower(struct data *d, uint32_t id, const char *name)
+{
+ int idx = d->n_followers;
+
+ if (idx == MAX_FOLLOWERS)
+ return -1;
+
+ d->n_followers++;
+
+ 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);
+
+ return idx;
+}
+
+static int process_follower_block(struct data *d, const struct spa_pod *pod, struct point *point)
+{
+ uint32_t id = 0;
+ const char *name = NULL;
+ struct measurement m;
+ int res, idx;
+
+ spa_zero(m);
+ if ((res = spa_pod_parse_struct(pod,
+ SPA_POD_Int(&id),
+ SPA_POD_String(&name),
+ SPA_POD_Long(&m.prev_signal),
+ SPA_POD_Long(&m.signal),
+ SPA_POD_Long(&m.awake),
+ SPA_POD_Long(&m.finish),
+ SPA_POD_Int(&m.status))) < 0)
+ return res;
+
+ if ((idx = find_follower(d, id, name)) < 0) {
+ if ((idx = add_follower(d, id, name)) < 0) {
+ pw_log_warn("too many followers");
+ return -ENOSPC;
+ }
+ }
+ point->follower[idx] = m;
+ return 0;
+}
+
+static void dump_point(struct data *d, struct point *point)
+{
+ int i;
+ int64_t d1, d2;
+ int64_t delay, period_usecs;
+
+#define CLOCK_AS_USEC(cl,val) (val * (float)SPA_USEC_PER_SEC / (cl)->rate.denom)
+#define CLOCK_AS_SUSEC(cl,val) (val * (float)SPA_USEC_PER_SEC / ((cl)->rate.denom * (cl)->rate_diff))
+
+ delay = CLOCK_AS_USEC(&point->clock, point->clock.delay);
+ period_usecs = CLOCK_AS_SUSEC(&point->clock, point->clock.duration);
+
+ d1 = (point->driver.signal - point->driver.prev_signal) / 1000;
+ d2 = (point->driver.finish - point->driver.signal) / 1000;
+
+ if (d1 > period_usecs * 1.3 ||
+ d2 > period_usecs * 1.3)
+ d1 = d2 = period_usecs * 1.4;
+
+ /* 4 columns for the driver */
+ fprintf(d->output, "%"PRIi64"\t%"PRIi64"\t%"PRIi64"\t%"PRIi64"\t",
+ d1 > 0 ? d1 : 0, d2 > 0 ? d2 : 0, delay, period_usecs);
+
+ for (i = 0; i < MAX_FOLLOWERS; i++) {
+ /* 8 columns for each follower */
+ if (point->follower[i].status == 0) {
+ fprintf(d->output, " \t \t \t \t \t \t \t \t");
+ } else {
+ int64_t d4 = (point->follower[i].signal - point->driver.signal) / 1000;
+ int64_t d5 = (point->follower[i].awake - point->driver.signal) / 1000;
+ int64_t d6 = (point->follower[i].finish - point->driver.signal) / 1000;
+
+ fprintf(d->output, "%u\t%"PRIi64"\t%"PRIi64"\t%"PRIi64"\t%"PRIi64"\t%"PRIi64"\t%d\t0\t",
+ d->followers[i].id,
+ d4 > 0 ? d4 : 0,
+ d5 > 0 ? d5 : 0,
+ d6 > 0 ? d6 : 0,
+ (d5 > 0 && d4 > 0 && d5 > d4) ? d5 - d4 : 0,
+ (d6 > 0 && d5 > 0 && d6 > d5) ? d6 - d5 : 0,
+ point->follower[i].status);
+ }
+ }
+ fprintf(d->output, "\n");
+ if (d->count == 0) {
+ d->start_status = point->clock.nsec;
+ 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",
+ 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;
+ }
+ d->count++;
+}
+
+static void dump_scripts(struct data *d)
+{
+ FILE *out;
+ int i;
+
+ if (d->driver_id == 0)
+ return;
+
+ printf("\ndumping scripts for %d followers\n", d->n_followers);
+
+ out = fopen("Timing1.plot", "we");
+ if (out == NULL) {
+ pw_log_error("Can't open Timing1.plot: %m");
+ } else {
+ fprintf(out,
+ "set output 'Timing1.svg\n"
+ "set terminal svg\n"
+ "set multiplot\n"
+ "set grid\n"
+ "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"
+ "unset multiplot\n"
+ "unset output\n", d->filename);
+ fclose(out);
+ }
+
+ out = fopen("Timing2.plot", "we");
+ if (out == NULL) {
+ pw_log_error("Can't open Timing2.plot: %m");
+ } else {
+ fprintf(out,
+ "set output 'Timing2.svg\n"
+ "set terminal svg\n"
+ "set grid\n"
+ "set title \"Driver end date\"\n"
+ "set xlabel \"audio cycles\"\n"
+ "set ylabel \"usec\"\n"
+ "plot \"%s\" using 2 title \"Driver end date\" with lines \n"
+ "unset output\n", d->filename);
+ fclose(out);
+ }
+
+ out = fopen("Timing3.plot", "we");
+ if (out == NULL) {
+ pw_log_error("Can't open Timing3.plot: %m");
+ } else {
+ fprintf(out,
+ "set output 'Timing3.svg\n"
+ "set terminal svg\n"
+ "set multiplot\n"
+ "set grid\n"
+ "set title \"Clients end date\"\n"
+ "set xlabel \"audio cycles\"\n"
+ "set ylabel \"usec\"\n"
+ "plot "
+ "\"%s\" using 1 title \"Audio period\" with lines%s",
+ d->filename,
+ d->n_followers > 0 ? ", " : "");
+
+ for (i = 0; i < d->n_followers; i++) {
+ fprintf(out,
+ "\"%s\" using %d title \"%s/%u\" with lines%s",
+ d->filename, 4 + (i * 8) + 4,
+ d->followers[i].name, d->followers[i].id,
+ i+1 < d->n_followers ? ", " : "");
+ }
+ fprintf(out,
+ "\nunset multiplot\n"
+ "unset output\n");
+ fclose(out);
+ }
+
+ out = fopen("Timing4.plot", "we");
+ if (out == NULL) {
+ pw_log_error("Can't open Timing4.plot: %m");
+ } else {
+ fprintf(out,
+ "set output 'Timing4.svg\n"
+ "set terminal svg\n"
+ "set multiplot\n"
+ "set grid\n"
+ "set title \"Clients scheduling latency\"\n"
+ "set xlabel \"audio cycles\"\n"
+ "set ylabel \"usec\"\n"
+ "plot ");
+
+ for (i = 0; i < d->n_followers; i++) {
+ fprintf(out,
+ "\"%s\" using %d title \"%s/%u\" with lines%s",
+ d->filename, 4 + (i * 8) + 5,
+ d->followers[i].name, d->followers[i].id,
+ i+1 < d->n_followers ? ", " : "");
+ }
+ fprintf(out,
+ "\nunset multiplot\n"
+ "unset output\n");
+ fclose(out);
+ }
+
+ out = fopen("Timing5.plot", "we");
+ if (out == NULL) {
+ pw_log_error("Can't open Timing5.plot: %m");
+ } else {
+ fprintf(out,
+ "set output 'Timing5.svg\n"
+ "set terminal svg\n"
+ "set multiplot\n"
+ "set grid\n"
+ "set title \"Clients duration\"\n"
+ "set xlabel \"audio cycles\"\n"
+ "set ylabel \"usec\"\n"
+ "plot ");
+
+ for (i = 0; i < d->n_followers; i++) {
+ fprintf(out,
+ "\"%s\" using %d title \"%s/%u\" with lines%s",
+ d->filename, 4 + (i * 8) + 6,
+ d->followers[i].name, d->followers[i].id,
+ i+1 < d->n_followers ? ", " : "");
+ }
+ fprintf(out,
+ "\nunset multiplot\n"
+ "unset output\n");
+ fclose(out);
+ }
+ out = fopen("Timings.html", "we");
+ if (out == NULL) {
+ pw_log_error("Can't open Timings.html: %m");
+ } else {
+ fprintf(out,
+ "<?xml version='1.0' encoding='utf-8'?>\n"
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
+ "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "<html xmlns='http://www.w3.org/1999/xhtml' lang='en'>\n"
+ " <head>\n"
+ " <title>PipeWire profiling</title>\n"
+ " <!-- assuming that images are 600px wide -->\n"
+ " <style media='all' type='text/css'>\n"
+ " .center { margin-left:auto ; margin-right: auto; width: 650px; height: 550px }\n"
+ " </style>\n"
+ " </head>\n"
+ " <body>\n"
+ " <h2 style='text-align:center'>PipeWire profiling</h2>\n"
+ " <div class='center'><object class='center' type='image/svg+xml' data='Timing1.svg'>Timing1</object></div>"
+ " <div class='center'><object class='center' type='image/svg+xml' data='Timing2.svg'>Timing2</object></div>"
+ " <div class='center'><object class='center' type='image/svg+xml' data='Timing3.svg'>Timing3</object></div>"
+ " <div class='center'><object class='center' type='image/svg+xml' data='Timing4.svg'>Timing4</object></div>"
+ " <div class='center'><object class='center' type='image/svg+xml' data='Timing5.svg'>Timing5</object></div>"
+ " </body>\n"
+ "</html>\n");
+ fclose(out);
+ }
+
+ out = fopen("generate_timings.sh", "we");
+ if (out == NULL) {
+ pw_log_error("Can't open generate_timings.sh: %m");
+ } else {
+ fprintf(out,
+ "gnuplot Timing1.plot\n"
+ "gnuplot Timing2.plot\n"
+ "gnuplot Timing3.plot\n"
+ "gnuplot Timing4.plot\n"
+ "gnuplot Timing5.plot\n");
+ fclose(out);
+ }
+ printf("run 'sh generate_timings.sh' and load Timings.html in a browser\n");
+}
+
+static void profiler_profile(void *data, const struct spa_pod *pod)
+{
+ struct data *d = data;
+ struct spa_pod *o;
+ struct spa_pod_prop *p;
+ struct point point;
+
+ SPA_POD_STRUCT_FOREACH(pod, o) {
+ int res = 0;
+ if (!spa_pod_is_object_type(o, SPA_TYPE_OBJECT_Profiler))
+ continue;
+
+ spa_zero(point);
+ SPA_POD_OBJECT_FOREACH((struct spa_pod_object*)o, p) {
+ switch(p->key) {
+ case SPA_PROFILER_info:
+ res = process_info(d, &p->value, &point);
+ break;
+ case SPA_PROFILER_clock:
+ res = process_clock(d, &p->value, &point);
+ break;
+ case SPA_PROFILER_driverBlock:
+ res = process_driver_block(d, &p->value, &point);
+ break;
+ case SPA_PROFILER_followerBlock:
+ process_follower_block(d, &p->value, &point);
+ break;
+ default:
+ break;
+ }
+ if (res < 0)
+ break;
+ }
+ if (res < 0)
+ continue;
+
+ dump_point(d, &point);
+ }
+}
+
+static const struct pw_profiler_events profiler_events = {
+ PW_VERSION_PROFILER_EVENTS,
+ .profile = profiler_profile,
+};
+
+static void registry_event_global(void *data, uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ struct data *d = data;
+ struct pw_proxy *proxy;
+
+ if (!spa_streq(type, PW_TYPE_INTERFACE_Profiler))
+ return;
+
+ if (d->profiler != NULL) {
+ fprintf(stderr, "Ignoring profiler %d: already attached\n", id);
+ return;
+ }
+
+ proxy = pw_registry_bind(d->registry, id, type, PW_VERSION_PROFILER, 0);
+ if (proxy == NULL)
+ goto error_proxy;
+
+ printf("Attaching to Profiler id:%d\n", id);
+ d->profiler = proxy;
+ pw_proxy_add_object_listener(proxy, &d->profiler_listener, &profiler_events, d);
+
+ return;
+
+error_proxy:
+ pw_log_error("failed to create proxy: %m");
+ return;
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+};
+
+static void on_core_error(void *_data, uint32_t id, int seq, int res, const char *message)
+{
+ struct data *data = _data;
+
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+
+ if (id == PW_ID_CORE && res == -EPIPE)
+ pw_main_loop_quit(data->loop);
+}
+
+static void on_core_done(void *_data, uint32_t id, int seq)
+{
+ struct data *d = _data;
+
+ if (seq == d->check_profiler) {
+ if (d->profiler == NULL) {
+ pw_log_error("no Profiler Interface found, please load one in the server");
+ pw_main_loop_quit(d->loop);
+ }
+ }
+}
+
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+ .done = on_core_done,
+};
+
+static void do_quit(void *data, int signal_number)
+{
+ struct data *d = data;
+ pw_main_loop_quit(d->loop);
+}
+
+static void show_help(const char *name, bool error)
+{
+ fprintf(error ? stderr : stdout, "%s [options]\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " -r, --remote Remote daemon name\n"
+ " -o, --output Profiler output name (default \"%s\")\n",
+ name,
+ DEFAULT_FILENAME);
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0 };
+ struct pw_loop *l;
+ const char *opt_remote = NULL;
+ const char *opt_output = DEFAULT_FILENAME;
+ static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "remote", required_argument, NULL, 'r' },
+ { "output", required_argument, NULL, 'o' },
+ { NULL, 0, NULL, 0}
+ };
+ int c;
+
+ setlocale(LC_ALL, "");
+ pw_init(&argc, &argv);
+
+ while ((c = getopt_long(argc, argv, "hVr:o:", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ show_help(argv[0], false);
+ return 0;
+ case 'V':
+ printf("%s\n"
+ "Compiled with libpipewire %s\n"
+ "Linked with libpipewire %s\n",
+ argv[0],
+ pw_get_headers_version(),
+ pw_get_library_version());
+ return 0;
+ case 'o':
+ opt_output = optarg;
+ break;
+ case 'r':
+ opt_remote = optarg;
+ break;
+ default:
+ show_help(argv[0], true);
+ return -1;
+ }
+ }
+
+ data.loop = pw_main_loop_new(NULL);
+ if (data.loop == NULL) {
+ fprintf(stderr, "Can't create data loop: %m\n");
+ return -1;
+ }
+
+ l = pw_main_loop_get_loop(data.loop);
+ pw_loop_add_signal(l, SIGINT, do_quit, &data);
+ pw_loop_add_signal(l, SIGTERM, do_quit, &data);
+
+ data.context = pw_context_new(l, NULL, 0);
+ if (data.context == NULL) {
+ fprintf(stderr, "Can't create context: %m\n");
+ return -1;
+ }
+
+ pw_context_load_module(data.context, PW_EXTENSION_MODULE_PROFILER, NULL, NULL);
+
+ data.core = pw_context_connect(data.context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, opt_remote,
+ NULL),
+ 0);
+ if (data.core == NULL) {
+ fprintf(stderr, "Can't connect: %m\n");
+ return -1;
+ }
+
+ 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;
+ }
+
+ printf("Logging to %s\n", data.filename);
+
+ pw_core_add_listener(data.core,
+ &data.core_listener,
+ &core_events, &data);
+ data.registry = pw_core_get_registry(data.core,
+ PW_VERSION_REGISTRY, 0);
+ pw_registry_add_listener(data.registry,
+ &data.registry_listener,
+ &registry_events, &data);
+
+ data.check_profiler = pw_core_sync(data.core, 0, 0);
+
+ pw_main_loop_run(data.loop);
+
+ if (data.profiler) {
+ spa_hook_remove(&data.profiler_listener);
+ pw_proxy_destroy((struct pw_proxy*)data.profiler);
+ }
+ spa_hook_remove(&data.registry_listener);
+ pw_proxy_destroy((struct pw_proxy*)data.registry);
+ spa_hook_remove(&data.core_listener);
+ pw_context_destroy(data.context);
+ pw_main_loop_destroy(data.loop);
+
+ fclose(data.output);
+
+ dump_scripts(&data);
+
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/tools/pw-reserve.c b/src/tools/pw-reserve.c
new file mode 100644
index 0000000..45a3c45
--- /dev/null
+++ b/src/tools/pw-reserve.c
@@ -0,0 +1,253 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include "config.h"
+
+#include <getopt.h>
+#include <signal.h>
+#include <locale.h>
+
+#include <dbus/dbus.h>
+
+#include <spa/utils/result.h>
+#include <spa/support/dbus.h>
+
+#include "pipewire/pipewire.h"
+#include "pipewire/log.h"
+
+#include "reserve.h"
+
+struct impl {
+ struct pw_main_loop *mainloop;
+ struct pw_loop *loop;
+ struct pw_context *context;
+
+ struct spa_dbus *dbus;
+ struct spa_dbus_connection *dbus_connection;
+ DBusConnection *conn;
+
+ struct rd_device *device;
+};
+
+static void reserve_acquired(void *data, struct rd_device *d)
+{
+ printf("reserve acquired\n");
+}
+
+static void reserve_release(void *data, struct rd_device *d, int forced)
+{
+ struct impl *impl = data;
+ printf("reserve release\n");
+ rd_device_complete_release(impl->device, true);
+}
+
+static void reserve_busy(void *data, struct rd_device *d, const char *name, int32_t prio)
+{
+ printf("reserve busy %s, prio %d\n", name, prio);
+}
+
+static void reserve_available(void *data, struct rd_device *d, const char *name)
+{
+ printf("reserve available %s\n", name);
+}
+
+static const struct rd_device_callbacks reserve_callbacks = {
+ .acquired = reserve_acquired,
+ .release = reserve_release,
+ .busy = reserve_busy,
+ .available = reserve_available,
+};
+
+static void do_quit(void *data, int signal_number)
+{
+ struct impl *impl = data;
+ pw_main_loop_quit(impl->mainloop);
+}
+
+#define DEFAULT_APPNAME "pw-reserve"
+#define DEFAULT_PRIORITY 0
+
+static void show_help(const char *name, bool error)
+{
+ fprintf(error ? stderr : stdout, "%s [options]\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " -n, --name Name to reserve (Audio0, Midi0, Video0, ..)\n"
+ " -a, --appname Application Name (default %s)\n"
+ " -p, --priority Priority (default %d)\n"
+ " -m, --monitor Monitor only, don't try to acquire\n"
+ " -r, --release Request release when busy\n",
+ name, DEFAULT_APPNAME, DEFAULT_PRIORITY);
+}
+
+int main(int argc, char *argv[])
+{
+ struct impl impl = { 0, };
+ const struct spa_support *support;
+ uint32_t n_support;
+ const char *opt_name = NULL;
+ const char *opt_appname = DEFAULT_APPNAME;
+ bool opt_monitor = false;
+ bool opt_release = false;
+ int opt_priority= DEFAULT_PRIORITY;
+
+ int res = 0, c;
+ static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "name", required_argument, NULL, 'n' },
+ { "app", required_argument, NULL, 'a' },
+ { "priority", required_argument, NULL, 'p' },
+ { "monitor", no_argument, NULL, 'm' },
+ { "release", no_argument, NULL, 'r' },
+ { NULL, 0, NULL, 0}
+ };
+
+ setlinebuf(stdout);
+
+ setlocale(LC_ALL, "");
+ pw_init(&argc, &argv);
+
+ while ((c = getopt_long(argc, argv, "hVn:a:p:mr", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ show_help(argv[0], false);
+ return 0;
+ case 'V':
+ printf("%s\n"
+ "Compiled with libpipewire %s\n"
+ "Linked with libpipewire %s\n",
+ argv[0],
+ pw_get_headers_version(),
+ pw_get_library_version());
+ return 0;
+ case 'n':
+ opt_name = optarg;
+ break;
+ case 'a':
+ opt_appname = optarg;
+ break;
+ case 'p':
+ opt_priority = atoi(optarg);
+ break;
+ case 'm':
+ opt_monitor = true;
+ break;
+ case 'r':
+ opt_release = true;
+ break;
+ default:
+ show_help(argv[0], true);
+ return -1;
+ }
+ }
+ if (opt_name == NULL) {
+ fprintf(stderr, "name must be given\n");
+ return -1;
+ }
+
+ impl.mainloop = pw_main_loop_new(NULL);
+ if (impl.mainloop == NULL) {
+ fprintf(stderr, "can't create mainloop: %m\n");
+ res = -errno;
+ goto exit;
+ }
+ impl.loop = pw_main_loop_get_loop(impl.mainloop);
+
+ pw_loop_add_signal(impl.loop, SIGINT, do_quit, &impl);
+ pw_loop_add_signal(impl.loop, SIGTERM, do_quit, &impl);
+
+ impl.context = pw_context_new(impl.loop, NULL, 0);
+ if (impl.context == NULL) {
+ fprintf(stderr, "can't create context: %m\n");
+ res = -errno;
+ goto exit;
+ }
+
+ support = pw_context_get_support(impl.context, &n_support);
+
+ impl.dbus = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DBus);
+ if (impl.dbus)
+ impl.dbus_connection = spa_dbus_get_connection(impl.dbus, SPA_DBUS_TYPE_SESSION);
+ if (impl.dbus_connection == NULL) {
+ fprintf(stderr, "no dbus connection: %m\n");
+ res = -errno;
+ goto exit;
+ }
+ impl.conn = spa_dbus_connection_get(impl.dbus_connection);
+ if (impl.conn == NULL) {
+ fprintf(stderr, "no dbus connection: %m\n");
+ res = -errno;
+ goto exit;
+ }
+
+ /* XXX: we don't handle dbus reconnection yet, so ref the handle instead */
+ dbus_connection_ref(impl.conn);
+
+ impl.device = rd_device_new(impl.conn,
+ opt_name,
+ opt_appname,
+ opt_priority,
+ &reserve_callbacks, &impl);
+
+ if (!opt_monitor) {
+ res = rd_device_acquire(impl.device);
+ if (res == -EBUSY) {
+ printf("device %s is busy\n", opt_name);
+ if (opt_release) {
+ printf("doing RequestRelease on %s\n", opt_name);
+ res = rd_device_request_release(impl.device);
+ } else {
+ printf("use -r to attempt to release\n");
+ }
+ } else if (res < 0) {
+ printf("Device %s can not be acquired: %s\n", opt_name,
+ spa_strerror(res));
+ }
+ }
+
+ if (res >= 0)
+ pw_main_loop_run(impl.mainloop);
+
+ if (!opt_monitor) {
+ if (opt_release) {
+ printf("doing Release on %s\n", opt_name);
+ rd_device_release(impl.device);
+ }
+ }
+
+exit:
+ if (impl.conn)
+ dbus_connection_unref(impl.conn);
+ if (impl.dbus)
+ spa_dbus_connection_destroy(impl.dbus_connection);
+ if (impl.context)
+ pw_context_destroy(impl.context);
+ if (impl.mainloop)
+ pw_main_loop_destroy(impl.mainloop);
+
+ pw_deinit();
+
+ return res;
+}
diff --git a/src/tools/pw-top.c b/src/tools/pw-top.c
new file mode 100644
index 0000000..a20bdf9
--- /dev/null
+++ b/src/tools/pw-top.c
@@ -0,0 +1,862 @@
+/* PipeWire
+ *
+ * Copyright © 2020 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <signal.h>
+#include <getopt.h>
+#include <locale.h>
+#include <ncurses.h>
+
+#include <spa/utils/result.h>
+#include <spa/utils/string.h>
+#include <spa/pod/parser.h>
+#include <spa/debug/types.h>
+#include <spa/param/format-utils.h>
+#include <spa/param/audio/format-utils.h>
+#include <spa/param/video/format-utils.h>
+
+#include <pipewire/impl.h>
+#include <pipewire/extensions/profiler.h>
+
+#define MAX_FORMAT 16
+#define MAX_NAME 128
+
+struct driver {
+ int64_t count;
+ float cpu_load[3];
+ struct spa_io_clock clock;
+ uint32_t xrun_count;
+};
+
+struct measurement {
+ int32_t index;
+ int32_t status;
+ int64_t quantum;
+ int64_t prev_signal;
+ int64_t signal;
+ int64_t awake;
+ int64_t finish;
+ struct spa_fraction latency;
+};
+
+struct node {
+ struct spa_list link;
+ struct data *data;
+ uint32_t id;
+ char name[MAX_NAME+1];
+ enum pw_node_state state;
+ struct measurement measurement;
+ struct driver info;
+ struct node *driver;
+ uint32_t errors;
+ int32_t last_error_status;
+ uint32_t generation;
+ char format[MAX_FORMAT+1];
+ struct pw_proxy *proxy;
+ struct spa_hook proxy_listener;
+ unsigned int inactive:1;
+ struct spa_hook object_listener;
+};
+
+struct data {
+ struct pw_main_loop *loop;
+ struct pw_context *context;
+
+ struct pw_core *core;
+ struct spa_hook core_listener;
+
+ struct pw_registry *registry;
+ struct spa_hook registry_listener;
+
+ struct pw_proxy *profiler;
+ struct spa_hook profiler_listener;
+ int check_profiler;
+
+ struct spa_source *timer;
+
+ int n_nodes;
+ struct spa_list node_list;
+ uint32_t generation;
+ unsigned pending_refresh:1;
+
+ WINDOW *win;
+};
+
+struct point {
+ struct node *driver;
+ struct driver info;
+};
+
+static int process_info(struct data *d, const struct spa_pod *pod, struct driver *info)
+{
+ return spa_pod_parse_struct(pod,
+ SPA_POD_Long(&info->count),
+ SPA_POD_Float(&info->cpu_load[0]),
+ SPA_POD_Float(&info->cpu_load[1]),
+ SPA_POD_Float(&info->cpu_load[2]),
+ SPA_POD_Int(&info->xrun_count));
+}
+
+static int process_clock(struct data *d, const struct spa_pod *pod, struct driver *info)
+{
+ return spa_pod_parse_struct(pod,
+ SPA_POD_Int(&info->clock.flags),
+ SPA_POD_Int(&info->clock.id),
+ SPA_POD_Stringn(info->clock.name, sizeof(info->clock.name)),
+ SPA_POD_Long(&info->clock.nsec),
+ SPA_POD_Fraction(&info->clock.rate),
+ SPA_POD_Long(&info->clock.position),
+ SPA_POD_Long(&info->clock.duration),
+ SPA_POD_Long(&info->clock.delay),
+ SPA_POD_Double(&info->clock.rate_diff),
+ SPA_POD_Long(&info->clock.next_nsec));
+}
+
+static struct node *find_node(struct data *d, uint32_t id)
+{
+ struct node *n;
+ spa_list_for_each(n, &d->node_list, link) {
+ if (n->id == id)
+ return n;
+ }
+ return NULL;
+}
+
+static void on_node_removed(void *data)
+{
+ struct node *n = data;
+ pw_proxy_destroy(n->proxy);
+}
+
+static void on_node_destroy(void *data)
+{
+ struct node *n = data;
+ n->proxy = NULL;
+ spa_hook_remove(&n->proxy_listener);
+ spa_hook_remove(&n->object_listener);
+}
+
+static const struct pw_proxy_events proxy_events = {
+ PW_VERSION_PROXY_EVENTS,
+ .removed = on_node_removed,
+ .destroy = on_node_destroy,
+};
+
+static void do_refresh(struct data *d);
+
+static void node_info(void *data, const struct pw_node_info *info)
+{
+ struct node *n = data;
+
+ if (n->state != info->state) {
+ n->state = info->state;
+ do_refresh(n->data);
+ }
+}
+
+static void node_param(void *data, int seq,
+ uint32_t id, uint32_t index, uint32_t next,
+ const struct spa_pod *param)
+{
+ struct node *n = data;
+
+ if (param == NULL) {
+ spa_zero(n->format);
+ goto done;
+ }
+
+ switch (id) {
+ case SPA_PARAM_Format:
+ {
+ uint32_t media_type, media_subtype;
+
+ spa_format_parse(param, &media_type, &media_subtype);
+
+ switch(media_type) {
+ case SPA_MEDIA_TYPE_audio:
+ switch(media_subtype) {
+ case SPA_MEDIA_SUBTYPE_raw:
+ {
+ struct spa_audio_info_raw info = { 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),
+ info.channels, info.rate);
+ }
+ break;
+ }
+ case SPA_MEDIA_SUBTYPE_dsd:
+ {
+ struct spa_audio_info_dsd info = { 0 };
+ if (spa_format_audio_dsd_parse(param, &info) >= 0) {
+ snprintf(n->format, sizeof(n->format), "DSD%d %d ",
+ 8 * info.rate / 44100, info.channels);
+
+ }
+ break;
+ }
+ case SPA_MEDIA_SUBTYPE_iec958:
+ {
+ struct spa_audio_info_iec958 info = { 0 };
+ if (spa_format_audio_iec958_parse(param, &info) >= 0) {
+ snprintf(n->format, sizeof(n->format), "IEC958 %s %d",
+ spa_debug_type_find_short_name(
+ spa_type_audio_iec958_codec, info.codec),
+ info.rate);
+
+ }
+ break;
+ }
+ }
+ break;
+ case SPA_MEDIA_TYPE_video:
+ switch(media_subtype) {
+ case SPA_MEDIA_SUBTYPE_raw:
+ {
+ 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),
+ info.size.width, info.size.height);
+ }
+ break;
+ }
+ case SPA_MEDIA_SUBTYPE_mjpg:
+ {
+ struct spa_video_info_mjpg info = { 0 };
+ if (spa_format_video_mjpg_parse(param, &info) >= 0) {
+ snprintf(n->format, sizeof(n->format), "MJPG %dx%d",
+ info.size.width, info.size.height);
+ }
+ break;
+ }
+ case SPA_MEDIA_SUBTYPE_h264:
+ {
+ struct spa_video_info_h264 info = { 0 };
+ if (spa_format_video_h264_parse(param, &info) >= 0) {
+ snprintf(n->format, sizeof(n->format), "H264 %dx%d",
+ info.size.width, info.size.height);
+ }
+ break;
+ }
+ }
+ break;
+ case SPA_MEDIA_TYPE_application:
+ switch(media_subtype) {
+ case SPA_MEDIA_SUBTYPE_control:
+ snprintf(n->format, sizeof(n->format), "%s", "CONTROL");
+ break;
+ }
+ break;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+done:
+ do_refresh(n->data);
+}
+
+static const struct pw_node_events node_events = {
+ PW_VERSION_NODE,
+ .info = node_info,
+ .param = node_param,
+};
+
+static struct node *add_node(struct data *d, uint32_t id, const char *name)
+{
+ struct node *n;
+
+ if ((n = calloc(1, sizeof(*n))) == NULL)
+ return NULL;
+
+ if (name)
+ strncpy(n->name, name, MAX_NAME);
+ else
+ snprintf(n->name, sizeof(n->name), "%u", id);
+ n->data = d;
+ n->id = id;
+ n->driver = n;
+ n->proxy = pw_registry_bind(d->registry, id, PW_TYPE_INTERFACE_Node, PW_VERSION_NODE, 0);
+ if (n->proxy) {
+ uint32_t ids[1] = { SPA_PARAM_Format };
+
+ pw_proxy_add_listener(n->proxy,
+ &n->proxy_listener, &proxy_events, n);
+ pw_proxy_add_object_listener(n->proxy,
+ &n->object_listener, &node_events, n);
+
+ pw_node_subscribe_params((struct pw_node*)n->proxy,
+ ids, 1);
+ }
+ spa_list_append(&d->node_list, &n->link);
+ d->n_nodes++;
+ d->pending_refresh = true;
+
+ return n;
+}
+
+static void remove_node(struct data *d, struct node *n)
+{
+ if (n->proxy)
+ pw_proxy_destroy(n->proxy);
+ spa_list_remove(&n->link);
+ d->n_nodes--;
+ d->pending_refresh = true;
+ free(n);
+}
+
+static int process_driver_block(struct data *d, const struct spa_pod *pod, struct point *point)
+{
+ char *name = NULL;
+ uint32_t id = 0;
+ struct measurement m;
+ struct node *n;
+ int res;
+
+ spa_zero(m);
+ if ((res = spa_pod_parse_struct(pod,
+ SPA_POD_Int(&id),
+ SPA_POD_String(&name),
+ SPA_POD_Long(&m.prev_signal),
+ SPA_POD_Long(&m.signal),
+ SPA_POD_Long(&m.awake),
+ SPA_POD_Long(&m.finish),
+ SPA_POD_Int(&m.status),
+ SPA_POD_Fraction(&m.latency))) < 0)
+ return res;
+
+ if ((n = find_node(d, id)) == NULL)
+ return -ENOENT;
+
+ n->driver = n;
+ n->measurement = m;
+ n->info = point->info;
+ point->driver = n;
+ n->generation = d->generation;
+
+ if (m.status != 3) {
+ n->errors++;
+ if (n->last_error_status == -1)
+ n->last_error_status = m.status;
+ }
+ return 0;
+}
+
+static int process_follower_block(struct data *d, const struct spa_pod *pod, struct point *point)
+{
+ uint32_t id = 0;
+ const char *name = NULL;
+ struct measurement m;
+ struct node *n;
+ int res;
+
+ spa_zero(m);
+ if ((res = spa_pod_parse_struct(pod,
+ SPA_POD_Int(&id),
+ SPA_POD_String(&name),
+ SPA_POD_Long(&m.prev_signal),
+ SPA_POD_Long(&m.signal),
+ SPA_POD_Long(&m.awake),
+ SPA_POD_Long(&m.finish),
+ SPA_POD_Int(&m.status),
+ SPA_POD_Fraction(&m.latency))) < 0)
+ return res;
+
+ if ((n = find_node(d, id)) == NULL)
+ return -ENOENT;
+
+ n->measurement = m;
+ if (n->driver != point->driver) {
+ n->driver = point->driver;
+ d->pending_refresh = true;
+ }
+ n->generation = d->generation;
+ if (m.status != 3) {
+ n->errors++;
+ if (n->last_error_status == -1)
+ n->last_error_status = m.status;
+ }
+ return 0;
+}
+
+static const char *print_time(char *buf, bool active, size_t len, uint64_t val)
+{
+ if (val == (uint64_t)-1 || !active)
+ snprintf(buf, len, " --- ");
+ else if (val == (uint64_t)-2)
+ snprintf(buf, len, " +++ ");
+ else if (val < 1000000llu)
+ snprintf(buf, len, "%5.1fus", val/1000.f);
+ else if (val < 1000000000llu)
+ snprintf(buf, len, "%5.1fms", val/1000000.f);
+ else
+ snprintf(buf, len, "%5.1fs", val/1000000000.f);
+ return buf;
+}
+
+static const char *print_perc(char *buf, bool active, size_t len, uint64_t val, float quantum)
+{
+ if (val == (uint64_t)-1 || !active) {
+ snprintf(buf, len, " --- ");
+ } else if (val == (uint64_t)-2) {
+ snprintf(buf, len, " +++ ");
+ } else {
+ float frac = val / 1000000000.f;
+ snprintf(buf, len, "%5.2f", quantum == 0.0f ? 0.0f : frac/quantum);
+ }
+ return buf;
+}
+
+static const char *state_as_string(enum pw_node_state state)
+{
+ switch (state) {
+ case PW_NODE_STATE_ERROR:
+ return "E";
+ case PW_NODE_STATE_CREATING:
+ return "C";
+ case PW_NODE_STATE_SUSPENDED:
+ return "S";
+ case PW_NODE_STATE_IDLE:
+ return "I";
+ case PW_NODE_STATE_RUNNING:
+ return "R";
+ }
+ return "!";
+}
+
+static void print_node(struct data *d, struct driver *i, struct node *n, int y)
+{
+ char buf1[64];
+ char buf2[64];
+ char buf3[64];
+ char buf4[64];
+ uint64_t waiting, busy;
+ float quantum;
+ struct spa_fraction frac;
+ bool active;
+
+ active = n->state == PW_NODE_STATE_RUNNING || n->state == PW_NODE_STATE_IDLE;
+
+ if (!active)
+ frac = SPA_FRACTION(0, 0);
+ else if (n->driver == n)
+ frac = SPA_FRACTION((uint32_t)(i->clock.duration * i->clock.rate.num), i->clock.rate.denom);
+ else
+ frac = SPA_FRACTION(n->measurement.latency.num, n->measurement.latency.denom);
+
+ if (i->clock.rate.denom)
+ quantum = (float)i->clock.duration * i->clock.rate.num / (float)i->clock.rate.denom;
+ else
+ quantum = 0.0;
+
+ if (n->measurement.awake >= n->measurement.signal)
+ waiting = n->measurement.awake - n->measurement.signal;
+ else if (n->measurement.signal > n->measurement.prev_signal)
+ waiting = -2;
+ else
+ waiting = -1;
+
+ if (n->measurement.finish >= n->measurement.awake)
+ busy = n->measurement.finish - n->measurement.awake;
+ else if (n->measurement.awake > n->measurement.prev_signal)
+ busy = -2;
+ else
+ busy = -1;
+
+ mvwprintw(d->win, y, 0, "%s %4.1u %6.1u %6.1u %s %s %s %s %3.1u %16.16s %s%s",
+ state_as_string(n->state),
+ n->id,
+ frac.num, frac.denom,
+ print_time(buf1, active, 64, waiting),
+ print_time(buf2, active, 64, busy),
+ print_perc(buf3, active, 64, waiting, quantum),
+ print_perc(buf4, active, 64, busy, quantum),
+ i->xrun_count + n->errors,
+ active ? n->format : "",
+ n->driver == n ? "" : " + ",
+ n->name);
+}
+
+static void clear_node(struct node *n)
+{
+ n->driver = n;
+ spa_zero(n->measurement);
+ spa_zero(n->info);
+ n->errors = 0;
+ n->last_error_status = 0;
+}
+
+static void do_refresh(struct data *d)
+{
+ struct node *n, *t, *f;
+ int y = 1;
+
+ wclear(d->win);
+ wattron(d->win, A_REVERSE);
+ wprintw(d->win, "%-*.*s", COLS, COLS, "S ID QUANT RATE WAIT BUSY W/Q B/Q ERR FORMAT NAME ");
+ wattroff(d->win, A_REVERSE);
+ wprintw(d->win, "\n");
+
+ spa_list_for_each_safe(n, t, &d->node_list, link) {
+ if (n->driver != n)
+ continue;
+
+ print_node(d, &n->info, n, y++);
+ if(y > LINES)
+ break;
+
+ spa_list_for_each(f, &d->node_list, link) {
+ if (d->generation > f->generation + 22)
+ clear_node(f);
+
+ if (f->driver != n || f == n)
+ continue;
+
+ print_node(d, &n->info, f, y++);
+ if(y > LINES)
+ break;
+
+ }
+ }
+
+ // Clear from last line to the end of the window to hide text wrapping from the last node
+ wmove(d->win, y, 0);
+ wclrtobot(d->win);
+
+ wrefresh(d->win);
+ d->pending_refresh = false;
+}
+
+static void do_timeout(void *data, uint64_t expirations)
+{
+ struct data *d = data;
+ d->generation++;
+ do_refresh(d);
+}
+
+static void profiler_profile(void *data, const struct spa_pod *pod)
+{
+ struct data *d = data;
+ struct spa_pod *o;
+ struct spa_pod_prop *p;
+ struct point point;
+
+ SPA_POD_STRUCT_FOREACH(pod, o) {
+ int res = 0;
+ if (!spa_pod_is_object_type(o, SPA_TYPE_OBJECT_Profiler))
+ continue;
+
+ spa_zero(point);
+ SPA_POD_OBJECT_FOREACH((struct spa_pod_object*)o, p) {
+ switch(p->key) {
+ case SPA_PROFILER_info:
+ res = process_info(d, &p->value, &point.info);
+ break;
+ case SPA_PROFILER_clock:
+ res = process_clock(d, &p->value, &point.info);
+ break;
+ case SPA_PROFILER_driverBlock:
+ res = process_driver_block(d, &p->value, &point);
+ break;
+ case SPA_PROFILER_followerBlock:
+ process_follower_block(d, &p->value, &point);
+ break;
+ default:
+ break;
+ }
+ if (res < 0)
+ break;
+ }
+ if (res < 0)
+ continue;
+ }
+ if (d->pending_refresh)
+ do_refresh(d);
+}
+
+static const struct pw_profiler_events profiler_events = {
+ PW_VERSION_PROFILER_EVENTS,
+ .profile = profiler_profile,
+};
+
+static void registry_event_global(void *data, uint32_t id,
+ uint32_t permissions, const char *type, uint32_t version,
+ const struct spa_dict *props)
+{
+ struct data *d = data;
+ struct pw_proxy *proxy;
+
+ if (spa_streq(type, PW_TYPE_INTERFACE_Node)) {
+ const char *str;
+
+ if ((str = spa_dict_lookup(props, PW_KEY_NODE_NAME)) == NULL &&
+ (str = spa_dict_lookup(props, PW_KEY_NODE_DESCRIPTION)) == NULL) {
+ str = spa_dict_lookup(props, PW_KEY_APP_NAME);
+ }
+
+ if (add_node(d, id, str) == NULL) {
+ pw_log_warn("can add node %u: %m", id);
+ }
+ } else if (spa_streq(type, PW_TYPE_INTERFACE_Profiler)) {
+ if (d->profiler != NULL) {
+ printf("Ignoring profiler %d: already attached\n", id);
+ return;
+ }
+
+ proxy = pw_registry_bind(d->registry, id, type, PW_VERSION_PROFILER, 0);
+ if (proxy == NULL)
+ goto error_proxy;
+
+ d->profiler = proxy;
+ pw_proxy_add_object_listener(proxy, &d->profiler_listener, &profiler_events, d);
+ }
+ if (d->pending_refresh)
+ do_refresh(d);
+ return;
+
+error_proxy:
+ pw_log_error("failed to create proxy: %m");
+ return;
+}
+
+static void registry_event_global_remove(void *data, uint32_t id)
+{
+ struct data *d = data;
+ struct node *n;
+ if ((n = find_node(d, id)) != NULL)
+ remove_node(d, n);
+ if (d->pending_refresh)
+ do_refresh(d);
+}
+
+static const struct pw_registry_events registry_events = {
+ PW_VERSION_REGISTRY_EVENTS,
+ .global = registry_event_global,
+ .global_remove = registry_event_global_remove,
+};
+
+static void on_core_error(void *_data, uint32_t id, int seq, int res, const char *message)
+{
+ struct data *data = _data;
+
+ if (id == PW_ID_CORE) {
+ switch (res) {
+ case -EPIPE:
+ pw_main_loop_quit(data->loop);
+ break;
+ default:
+ pw_log_error("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+ break;
+ }
+ } else {
+ pw_log_info("error id:%u seq:%d res:%d (%s): %s",
+ id, seq, res, spa_strerror(res), message);
+ }
+}
+
+static void on_core_done(void *_data, uint32_t id, int seq)
+{
+ struct data *d = _data;
+
+ if (seq == d->check_profiler) {
+ if (d->profiler == NULL) {
+ pw_log_error("no Profiler Interface found, please load one in the server");
+ pw_main_loop_quit(d->loop);
+ } else {
+ do_refresh(d);
+ }
+ }
+}
+
+static const struct pw_core_events core_events = {
+ PW_VERSION_CORE_EVENTS,
+ .error = on_core_error,
+ .done = on_core_done,
+};
+
+static void do_quit(void *data, int signal_number)
+{
+ struct data *d = data;
+ pw_main_loop_quit(d->loop);
+}
+
+static void show_help(const char *name, bool error)
+{
+ fprintf(error ? stderr : stdout, "%s [options]\n"
+ " -h, --help Show this help\n"
+ " --version Show version\n"
+ " -r, --remote Remote daemon name\n",
+ name);
+}
+
+static void terminal_start(void)
+{
+ initscr();
+ cbreak();
+ noecho();
+ refresh();
+}
+
+static void terminal_stop(void)
+{
+ endwin();
+}
+
+static void do_handle_io(void *data, int fd, uint32_t mask)
+{
+ struct data *d = data;
+
+ if (mask & SPA_IO_IN) {
+ int ch = getch();
+
+ switch(ch) {
+ case 'q':
+ pw_main_loop_quit(d->loop);
+ break;
+ default:
+ do_refresh(d);
+ break;
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ struct data data = { 0 };
+ struct pw_loop *l;
+ const char *opt_remote = NULL;
+ static const struct option long_options[] = {
+ { "help", no_argument, NULL, 'h' },
+ { "version", no_argument, NULL, 'V' },
+ { "remote", required_argument, NULL, 'r' },
+ { NULL, 0, NULL, 0}
+ };
+ int c;
+ struct timespec value, interval;
+ struct node *n;
+
+ setlocale(LC_ALL, "");
+ pw_init(&argc, &argv);
+
+ spa_list_init(&data.node_list);
+
+ while ((c = getopt_long(argc, argv, "hVr:o:", long_options, NULL)) != -1) {
+ switch (c) {
+ case 'h':
+ show_help(argv[0], false);
+ return 0;
+ case 'V':
+ printf("%s\n"
+ "Compiled with libpipewire %s\n"
+ "Linked with libpipewire %s\n",
+ argv[0],
+ pw_get_headers_version(),
+ pw_get_library_version());
+ return 0;
+ case 'r':
+ opt_remote = optarg;
+ break;
+ default:
+ show_help(argv[0], true);
+ return -1;
+ }
+ }
+
+ data.loop = pw_main_loop_new(NULL);
+ if (data.loop == NULL) {
+ fprintf(stderr, "Can't create data loop: %m\n");
+ return -1;
+ }
+
+ l = pw_main_loop_get_loop(data.loop);
+ pw_loop_add_signal(l, SIGINT, do_quit, &data);
+ pw_loop_add_signal(l, SIGTERM, do_quit, &data);
+
+ data.context = pw_context_new(l, NULL, 0);
+ if (data.context == NULL) {
+ fprintf(stderr, "Can't create context: %m\n");
+ return -1;
+ }
+
+ pw_context_load_module(data.context, PW_EXTENSION_MODULE_PROFILER, NULL, NULL);
+
+ data.core = pw_context_connect(data.context,
+ pw_properties_new(
+ PW_KEY_REMOTE_NAME, opt_remote,
+ NULL),
+ 0);
+ if (data.core == NULL) {
+ fprintf(stderr, "Can't connect: %m\n");
+ return -1;
+ }
+
+ pw_core_add_listener(data.core,
+ &data.core_listener,
+ &core_events, &data);
+ data.registry = pw_core_get_registry(data.core,
+ PW_VERSION_REGISTRY, 0);
+ pw_registry_add_listener(data.registry,
+ &data.registry_listener,
+ &registry_events, &data);
+
+ data.check_profiler = pw_core_sync(data.core, 0, 0);
+
+ terminal_start();
+
+ data.win = newwin(LINES, COLS, 0, 0);
+
+ data.timer = pw_loop_add_timer(l, do_timeout, &data);
+ value.tv_sec = 1;
+ value.tv_nsec = 0;
+ interval.tv_sec = 1;
+ interval.tv_nsec = 0;
+ pw_loop_update_timer(l, data.timer, &value, &interval, false);
+
+ pw_loop_add_io(l, fileno(stdin), SPA_IO_IN, false, do_handle_io, &data);
+
+ pw_main_loop_run(data.loop);
+
+ terminal_stop();
+
+ spa_list_consume(n, &data.node_list, link)
+ remove_node(&data, n);
+
+ if (data.profiler) {
+ spa_hook_remove(&data.profiler_listener);
+ pw_proxy_destroy((struct pw_proxy*)data.profiler);
+ }
+ spa_hook_remove(&data.registry_listener);
+ pw_proxy_destroy((struct pw_proxy*)data.registry);
+ spa_hook_remove(&data.core_listener);
+ pw_context_destroy(data.context);
+ pw_main_loop_destroy(data.loop);
+
+ pw_deinit();
+
+ return 0;
+}
diff --git a/src/tools/reserve.c b/src/tools/reserve.c
new file mode 100644
index 0000000..2329a14
--- /dev/null
+++ b/src/tools/reserve.c
@@ -0,0 +1,527 @@
+/* DBus device reservation API
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef NAME
+#define NAME "reserve"
+#endif
+
+#include "reserve.h"
+
+#include <spa/utils/string.h>
+#include <pipewire/log.h>
+
+#define SERVICE_PREFIX "org.freedesktop.ReserveDevice1."
+#define OBJECT_PREFIX "/org/freedesktop/ReserveDevice1/"
+
+static const char introspection[] =
+ DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE
+ "<node>"
+ " <!-- If you are looking for documentation make sure to check out\n"
+ " http://git.0pointer.de/?p=reserve.git;a=blob;f=reserve.txt -->\n"
+ " <interface name=\"org.freedesktop.ReserveDevice1\">"
+ " <method name=\"RequestRelease\">"
+ " <arg name=\"priority\" type=\"i\" direction=\"in\"/>"
+ " <arg name=\"result\" type=\"b\" direction=\"out\"/>"
+ " </method>"
+ " <property name=\"Priority\" type=\"i\" access=\"read\"/>"
+ " <property name=\"ApplicationName\" type=\"s\" access=\"read\"/>"
+ " <property name=\"ApplicationDeviceName\" type=\"s\" access=\"read\"/>"
+ " </interface>"
+ " <interface name=\"org.freedesktop.DBus.Properties\">"
+ " <method name=\"Get\">"
+ " <arg name=\"interface\" direction=\"in\" type=\"s\"/>"
+ " <arg name=\"property\" direction=\"in\" type=\"s\"/>"
+ " <arg name=\"value\" direction=\"out\" type=\"v\"/>"
+ " </method>"
+ " </interface>"
+ " <interface name=\"org.freedesktop.DBus.Introspectable\">"
+ " <method name=\"Introspect\">"
+ " <arg name=\"data\" type=\"s\" direction=\"out\"/>"
+ " </method>"
+ " </interface>"
+ "</node>";
+
+struct rd_device {
+ DBusConnection *connection;
+
+ int32_t priority;
+ char *service_name;
+ char *object_path;
+ char *application_name;
+ char *application_device_name;
+
+ const struct rd_device_callbacks *callbacks;
+ void *data;
+
+ DBusMessage *reply;
+
+ unsigned int filtering:1;
+ unsigned int registered:1;
+ unsigned int acquiring:1;
+ unsigned int owning:1;
+};
+
+static dbus_bool_t add_variant(DBusMessage *m, int type, const void *data)
+{
+ DBusMessageIter iter, sub;
+ char t[2];
+
+ t[0] = (char) type;
+ t[1] = 0;
+
+ dbus_message_iter_init_append(m, &iter);
+
+ if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, t, &sub))
+ return false;
+
+ if (!dbus_message_iter_append_basic(&sub, type, data))
+ return false;
+
+ if (!dbus_message_iter_close_container(&iter, &sub))
+ return false;
+
+ return true;
+}
+
+static DBusHandlerResult object_handler(DBusConnection *c, DBusMessage *m, void *userdata)
+{
+ struct rd_device *d = userdata;
+ DBusError error;
+ DBusMessage *reply = NULL;
+
+ dbus_error_init(&error);
+
+ if (dbus_message_is_method_call(m, "org.freedesktop.ReserveDevice1",
+ "RequestRelease")) {
+ int32_t priority;
+
+ if (!dbus_message_get_args(m, &error,
+ DBUS_TYPE_INT32, &priority,
+ DBUS_TYPE_INVALID))
+ goto invalid;
+
+ pw_log_debug("%p: request release priority:%d", d, priority);
+
+ if (!(reply = dbus_message_new_method_return(m)))
+ goto oom;
+
+ if (d->reply)
+ rd_device_complete_release(d, false);
+ d->reply = reply;
+
+ if (priority > d->priority && d->callbacks->release)
+ d->callbacks->release(d->data, d, 0);
+ else
+ rd_device_complete_release(d, false);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+ } else if (dbus_message_is_method_call(
+ m,
+ "org.freedesktop.DBus.Properties",
+ "Get")) {
+
+ const char *interface, *property;
+
+ if (!dbus_message_get_args( m, &error,
+ DBUS_TYPE_STRING, &interface,
+ DBUS_TYPE_STRING, &property,
+ DBUS_TYPE_INVALID))
+ goto invalid;
+
+ if (spa_streq(interface, "org.freedesktop.ReserveDevice1")) {
+ const char *empty = "";
+
+ if (spa_streq(property, "ApplicationName") && d->application_name) {
+ if (!(reply = dbus_message_new_method_return(m)))
+ goto oom;
+
+ if (!add_variant(reply,
+ DBUS_TYPE_STRING,
+ d->application_name ? (const char**) &d->application_name : &empty))
+ goto oom;
+
+ } else if (spa_streq(property, "ApplicationDeviceName")) {
+ if (!(reply = dbus_message_new_method_return(m)))
+ goto oom;
+
+ if (!add_variant(reply,
+ DBUS_TYPE_STRING,
+ d->application_device_name ? (const char**) &d->application_device_name : &empty))
+ goto oom;
+
+ } else if (spa_streq(property, "Priority")) {
+ if (!(reply = dbus_message_new_method_return(m)))
+ goto oom;
+
+ if (!add_variant(reply,
+ DBUS_TYPE_INT32, &d->priority))
+ goto oom;
+ } else {
+ if (!(reply = dbus_message_new_error_printf(m,
+ DBUS_ERROR_UNKNOWN_METHOD,
+ "Unknown property %s", property)))
+ goto oom;
+ }
+
+ if (!dbus_connection_send(c, reply, NULL))
+ goto oom;
+
+ dbus_message_unref(reply);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+ } else if (dbus_message_is_method_call(
+ m,
+ "org.freedesktop.DBus.Introspectable",
+ "Introspect")) {
+ const char *i = introspection;
+
+ if (!(reply = dbus_message_new_method_return(m)))
+ goto oom;
+
+ if (!dbus_message_append_args(reply,
+ DBUS_TYPE_STRING, &i,
+ DBUS_TYPE_INVALID))
+ goto oom;
+
+ if (!dbus_connection_send(c, reply, NULL))
+ goto oom;
+
+ dbus_message_unref(reply);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+ }
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+invalid:
+ if (!(reply = dbus_message_new_error(m,
+ DBUS_ERROR_INVALID_ARGS,
+ "Invalid arguments")))
+ goto oom;
+
+ if (!dbus_connection_send(c, reply, NULL))
+ goto oom;
+
+ dbus_message_unref(reply);
+
+ dbus_error_free(&error);
+
+ return DBUS_HANDLER_RESULT_HANDLED;
+
+oom:
+ if (reply)
+ dbus_message_unref(reply);
+
+ dbus_error_free(&error);
+
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+}
+
+static const struct DBusObjectPathVTable vtable ={
+ .message_function = object_handler
+};
+
+static DBusHandlerResult filter_handler(DBusConnection *c, DBusMessage *m, void *userdata)
+{
+ struct rd_device *d = userdata;
+ DBusError error;
+ const char *name;
+
+ dbus_error_init(&error);
+
+ if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameAcquired")) {
+ if (!dbus_message_get_args( m, &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID))
+ goto invalid;
+
+ if (!spa_streq(name, d->service_name))
+ goto invalid;
+
+ pw_log_debug("%p: acquired %s, %s", d, name, d->service_name);
+
+ d->owning = true;
+
+ if (!d->registered) {
+ if (!(dbus_connection_register_object_path(d->connection,
+ d->object_path,
+ &vtable,
+ d)))
+ goto invalid;
+
+ if (!spa_streq(name, d->service_name))
+ goto invalid;
+
+ d->registered = true;
+
+ if (d->callbacks->acquired)
+ d->callbacks->acquired(d->data, d);
+ }
+ } else if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameLost")) {
+ if (!dbus_message_get_args( m, &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID))
+ goto invalid;
+
+ if (!spa_streq(name, d->service_name))
+ goto invalid;
+
+ pw_log_debug("%p: lost %s", d, name);
+
+ d->owning = false;
+
+ if (d->registered) {
+ dbus_connection_unregister_object_path(d->connection,
+ d->object_path);
+ d->registered = false;
+ }
+ }
+ if (dbus_message_is_signal(m, "org.freedesktop.DBus", "NameOwnerChanged")) {
+ const char *old, *new;
+ if (!dbus_message_get_args( m, &error,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_STRING, &old,
+ DBUS_TYPE_STRING, &new,
+ DBUS_TYPE_INVALID))
+ goto invalid;
+
+ if (!spa_streq(name, d->service_name) || d->owning)
+ goto invalid;
+
+ pw_log_debug("%p: changed %s: %s -> %s", d, name, old, new);
+
+ if (old == NULL || *old == 0) {
+ if (d->callbacks->busy && !d->acquiring)
+ d->callbacks->busy(d->data, d, name, 0);
+ } else {
+ if (d->callbacks->available)
+ d->callbacks->available(d->data, d, name);
+ }
+ }
+
+invalid:
+ dbus_error_free(&error);
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+struct rd_device *
+rd_device_new(DBusConnection *connection, const char *device_name, const char *application_name,
+ int32_t priority, const struct rd_device_callbacks *callbacks, void *data)
+{
+ struct rd_device *d;
+ int res;
+
+ d = calloc(1, sizeof(struct rd_device));
+ if (d == NULL)
+ return NULL;
+
+ d->connection = connection;
+ d->priority = priority;
+ d->callbacks = callbacks;
+ d->data = data;
+
+ d->application_name = strdup(application_name);
+
+ d->object_path = spa_aprintf(OBJECT_PREFIX "%s", device_name);
+ if (d->object_path == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+ d->service_name = spa_aprintf(SERVICE_PREFIX "%s", device_name);
+ if (d->service_name == NULL) {
+ res = -errno;
+ goto error_free;
+ }
+
+ if (!dbus_connection_add_filter(d->connection,
+ filter_handler,
+ d,
+ NULL)) {
+ res = -ENOMEM;
+ goto error_free;
+ }
+ dbus_bus_add_match(d->connection,
+ "type='signal',sender='org.freedesktop.DBus',"
+ "interface='org.freedesktop.DBus',member='NameLost'", NULL);
+ dbus_bus_add_match(d->connection,
+ "type='signal',sender='org.freedesktop.DBus',"
+ "interface='org.freedesktop.DBus',member='NameAcquired'", NULL);
+ dbus_bus_add_match(d->connection,
+ "type='signal',sender='org.freedesktop.DBus',"
+ "interface='org.freedesktop.DBus',member='NameOwnerChanged'", NULL);
+
+ dbus_connection_ref(d->connection);
+
+ pw_log_debug("%p: new device %s", d, device_name);
+
+ return d;
+
+error_free:
+ free(d->service_name);
+ free(d->object_path);
+ free(d);
+ errno = -res;
+ return NULL;
+}
+
+int rd_device_acquire(struct rd_device *d)
+{
+ int res;
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ pw_log_debug("%p: reserve %s", d, d->service_name);
+
+ d->acquiring = true;
+
+ if ((res = dbus_bus_request_name(d->connection,
+ d->service_name,
+ (d->priority < INT32_MAX ? DBUS_NAME_FLAG_ALLOW_REPLACEMENT : 0),
+ &error)) < 0) {
+ pw_log_warn("%p: reserve failed: %s", d, error.message);
+ dbus_error_free(&error);
+ return -EIO;
+ }
+
+ pw_log_debug("%p: reserve result: %d", d, res);
+
+ if (res == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER ||
+ res == DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER)
+ return 0;
+
+ if (res == DBUS_REQUEST_NAME_REPLY_EXISTS ||
+ res == DBUS_REQUEST_NAME_REPLY_IN_QUEUE)
+ return -EBUSY;
+
+ return -EIO;
+}
+
+int rd_device_request_release(struct rd_device *d)
+{
+ DBusMessage *m = NULL;
+
+ if (d->priority <= INT32_MIN)
+ return -EBUSY;
+
+ if ((m = dbus_message_new_method_call(d->service_name,
+ d->object_path,
+ "org.freedesktop.ReserveDevice1",
+ "RequestRelease")) == NULL) {
+ return -ENOMEM;
+ }
+ if (!dbus_message_append_args(m,
+ DBUS_TYPE_INT32, &d->priority,
+ DBUS_TYPE_INVALID)) {
+ dbus_message_unref(m);
+ return -ENOMEM;
+ }
+ if (!dbus_connection_send(d->connection, m, NULL)) {
+ return -EIO;
+ }
+ return 0;
+}
+
+int rd_device_complete_release(struct rd_device *d, int res)
+{
+ dbus_bool_t ret = res != 0;
+
+ if (d->reply == NULL)
+ return -EINVAL;
+
+ pw_log_debug("%p: complete release %d", d, res);
+
+ if (!dbus_message_append_args(d->reply,
+ DBUS_TYPE_BOOLEAN, &ret,
+ DBUS_TYPE_INVALID)) {
+ res = -ENOMEM;
+ goto exit;
+ }
+
+ if (!dbus_connection_send(d->connection, d->reply, NULL)) {
+ res = -EIO;
+ goto exit;
+ }
+ res = 0;
+exit:
+ dbus_message_unref(d->reply);
+ d->reply = NULL;
+ return res;
+}
+
+void rd_device_release(struct rd_device *d)
+{
+ pw_log_debug("%p: release %d", d, d->owning);
+
+ if (d->owning) {
+ DBusError error;
+ dbus_error_init(&error);
+
+ dbus_bus_release_name(d->connection,
+ d->service_name, &error);
+ dbus_error_free(&error);
+ }
+ d->acquiring = false;
+}
+
+void rd_device_destroy(struct rd_device *d)
+{
+ dbus_connection_remove_filter(d->connection,
+ filter_handler, d);
+
+ if (d->registered)
+ dbus_connection_unregister_object_path(d->connection,
+ d->object_path);
+
+ rd_device_release(d);
+
+ free(d->service_name);
+ free(d->object_path);
+ free(d->application_name);
+ free(d->application_device_name);
+ if (d->reply)
+ dbus_message_unref(d->reply);
+
+ dbus_connection_unref(d->connection);
+
+ free(d);
+}
+
+int rd_device_set_application_device_name(struct rd_device *d, const char *name)
+{
+ char *t;
+
+ if (!d)
+ return -EINVAL;
+
+ if (!(t = strdup(name)))
+ return -ENOMEM;
+
+ free(d->application_device_name);
+ d->application_device_name = t;
+
+ return 0;
+}
diff --git a/src/tools/reserve.h b/src/tools/reserve.h
new file mode 100644
index 0000000..c31e2d0
--- /dev/null
+++ b/src/tools/reserve.h
@@ -0,0 +1,82 @@
+/* DBus device reservation API
+ *
+ * Copyright © 2019 Wim Taymans
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef DEVICE_RESERVE_H
+#define DEVICE_RESERVE_H
+
+#include <dbus/dbus.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct rd_device;
+
+struct rd_device_callbacks {
+ /** the device is acquired by us */
+ void (*acquired) (void *data, struct rd_device *d);
+ /** request a release of the device */
+ void (*release) (void *data, struct rd_device *d, int forced);
+ /** the device is busy by someone else */
+ void (*busy) (void *data, struct rd_device *d, const char *name, int32_t priority);
+ /** the device is made available by someone else */
+ void (*available) (void *data, struct rd_device *d, const char *name);
+};
+
+/* create a new device and start watching */
+struct rd_device *
+rd_device_new(DBusConnection *connection, /**< Bus to watch */
+ const char *device_name, /**< The device to lock, e.g. "Audio0" */
+ const char *application_name, /**< A human readable name of the application,
+ * e.g. "PipeWire Server" */
+ int32_t priority, /**< The priority for this application.
+ * If unsure use 0 */
+ const struct rd_device_callbacks *callbacks, /**< Called when device name is acquired/released */
+ void *data);
+
+/** try to acquire the device */
+int rd_device_acquire(struct rd_device *d);
+
+/** request the owner to release the device */
+int rd_device_request_release(struct rd_device *d);
+
+/** complete the release of the device */
+int rd_device_complete_release(struct rd_device *d, int res);
+
+/** release a device */
+void rd_device_release(struct rd_device *d);
+
+/** destroy a device */
+void rd_device_destroy(struct rd_device *d);
+
+/* Set the application device name for an rd_device object. Returns 0
+ * on success, a negative errno style return value on error. */
+int rd_device_set_application_device_name(struct rd_device *d, const char *name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif