diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 448bfa06e..2918ce62e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -410,13 +410,15 @@ build_on_fedora_html_docs:
-Dsndfile=enabled
-Dsession-managers=[]
before_script:
- - git fetch origin 1.0 1.2 1.4 master
+ - git fetch origin 1.0 1.2 1.4 1.6 master
- git branch -f 1.0 origin/1.0
- git clone -b 1.0 . branch-1.0
- git branch -f 1.2 origin/1.2
- git clone -b 1.2 . branch-1.2
- git branch -f 1.4 origin/1.4
- git clone -b 1.4 . branch-1.4
+ - git branch -f 1.6 origin/1.6
+ - git clone -b 1.6 . branch-1.6
- git branch -f master origin/master
- git clone -b master . branch-master
- !reference [.build, before_script]
@@ -433,6 +435,10 @@ build_on_fedora_html_docs:
- meson setup builddir $MESON_OPTIONS
- meson compile -C builddir doc/pipewire-docs
- cd ..
+ - cd branch-1.6
+ - meson setup builddir $MESON_OPTIONS
+ - meson compile -C builddir doc/pipewire-docs
+ - cd ..
- cd branch-master
- meson setup builddir $MESON_OPTIONS
- meson compile -C builddir doc/pipewire-docs
@@ -658,7 +664,7 @@ doccheck:
- cat pipewire_module_pages
- |
for page in $(cat pipewire_module_pages); do
- git grep -q -e "\\\subpage $page" || (echo "\\page $page is missing \\subpage entry in doc/pipewire-modules.dox" && false)
+ git grep -q -e "\\\subpage $page" || (echo "\\page $page is missing \\subpage entry in doc/dox/modules.dox" && false)
done
check_missing_headers:
@@ -682,12 +688,13 @@ pages:
- job: build_on_fedora_html_docs
artifacts: true
script:
- - mkdir public public/1.0 public/1.2 public/1.4 public/devel
+ - mkdir public public/1.0 public/1.2 public/1.4 public/1.6 public/devel
- cp -R branch-1.0/builddir/doc/html/* public/1.0/
- cp -R branch-1.2/builddir/doc/html/* public/1.2/
- cp -R branch-1.4/builddir/doc/html/* public/1.4/
+ - cp -R branch-1.6/builddir/doc/html/* public/1.6/
- cp -R branch-master/builddir/doc/html/* public/devel/
- - (cd public && ln -s 1.4/* .)
+ - (cd public && ln -s 1.6/* .)
artifacts:
paths:
- public
diff --git a/doc/DoxygenLayout.xml b/doc/DoxygenLayout.xml
index 300c8400e..10da55da5 100644
--- a/doc/DoxygenLayout.xml
+++ b/doc/DoxygenLayout.xml
@@ -44,6 +44,7 @@
+
diff --git a/doc/dox/config/pipewire-client.conf.5.md b/doc/dox/config/pipewire-client.conf.5.md
index db43839a0..321538a79 100644
--- a/doc/dox/config/pipewire-client.conf.5.md
+++ b/doc/dox/config/pipewire-client.conf.5.md
@@ -80,6 +80,9 @@ stream.properties = {
#channelmix.fc-cutoff = 12000.0
#channelmix.rear-delay = 12.0
#channelmix.stereo-widen = 0.0
+ #channelmix.center-level = 0.707106781
+ #channelmix.surround-level = 0.707106781
+ #channelmix.lfe-level = 0.5
#channelmix.hilbert-taps = 0
#dither.noise = 0
#dither.method = none # rectangular, triangular, triangular-hf, wannamaker3, shaped5
diff --git a/doc/dox/config/pipewire-props.7.md b/doc/dox/config/pipewire-props.7.md
index e561979d5..44b16f505 100644
--- a/doc/dox/config/pipewire-props.7.md
+++ b/doc/dox/config/pipewire-props.7.md
@@ -450,11 +450,25 @@ Whether the node target may be changed using metadata.
@PAR@ node-prop node.passive = false
\parblock
-This is a passive node and so it should not keep sinks/sources busy. This property makes the session manager create passive links to the sink/sources. If the node is not otherwise linked (via a non-passive link), the node and the sink it is linked to are idle (and eventually suspended).
+This can be used to configure the port.passive property for all ports of this node.
+
+Possible values are:
+
+ * "out": output ports are passive, They will not make the peers active and active peers will
+ not make this node active.
+ * "in": input ports are passive, They will not make the peers active and active peers will
+ not make this node active.
+ * "true": A combination in "in" and "out", both input and output ports are passive.
+ * "out-follow": output ports will not make the peer active but when the peer is activated via
+ some other way, this node will also become active.
+ * "in-follow": input ports will not make the peer active but when the peer is activated via
+ some other way, this node will also become active.
+ * "follow": A combination of "in-follow" and "out-follow".
This is used for filter nodes that sit in front of sinks/sources and need to suspend together with the sink/source.
\endparblock
+
@PAR@ node-prop node.link-group = ID
Add the node to a certain link group. Nodes from the same link group are not automatically linked to each other by the session manager. And example is a coupled stream where you don't want the output to link to the input streams, making a useless loop.
@@ -769,6 +783,15 @@ more to the center speaker and leaves the ambient sound in the stereo channels.
This is only active when up-mix is enabled and a Front Center channel is mixed.
\endparblock
+@PAR@ node-prop channelmix.center-level = 0.707106781
+The level of the center channel when up/downmixing.
+
+@PAR@ node-prop channelmix.surround-level = 0.707106781
+The level of the surround channels when up/downmixing.
+
+@PAR@ node-prop channelmix.lfe-level = 0.5
+The level of the LFE channel when up/downmixing.
+
@PAR@ node-prop channelmix.hilbert-taps = 0
\parblock
This option will apply a 90 degree phase shift to the rear channels to improve specialization.
@@ -1375,9 +1398,9 @@ Default: as per QoS preset.
@PAR@ device-prop bluez5.bap.force-target-latency = "balanced" # string
BAP QoS target latency profile forced for QoS configuration selection.
-If not set or set to "balanced", both low-latency and high-reliabilty QoS configuration table are used.
+If not set or set to "balanced", both low-latency and high-reliability QoS configuration table are used.
This property is experimental.
-Available: low-latency, high-reliabilty, balanced
+Available: low-latency, high-reliability, balanced
## Node properties
@@ -1420,6 +1443,11 @@ them. Below are some port properties may interesting for users:
\copydoc PW_KEY_PORT_ALIAS
\endparblock
+@PAR@ port-prop port.passive # string
+\parblock
+\copydoc PW_KEY_PORT_PASSIVE
+\endparblock
+
\see pw_keys in the API documentation for a full list.
# LINK PROPERTIES @IDX@ props
diff --git a/doc/dox/config/pipewire-pulse.conf.5.md b/doc/dox/config/pipewire-pulse.conf.5.md
index ad1b213c7..9cc8d4c48 100644
--- a/doc/dox/config/pipewire-pulse.conf.5.md
+++ b/doc/dox/config/pipewire-pulse.conf.5.md
@@ -93,6 +93,9 @@ stream.properties = {
#channelmix.fc-cutoff = 12000.0
#channelmix.rear-delay = 12.0
#channelmix.stereo-widen = 0.0
+ #channelmix.center-level = 0.707106781
+ #channelmix.surround-level = 0.707106781
+ #channelmix.lfe-level = 0.5
#channelmix.hilbert-taps = 0
#dither.noise = 0
#dither.method = none # rectangular, triangular, triangular-hf, wannamaker3, shaped5
diff --git a/doc/dox/internals/dma-buf.dox b/doc/dox/internals/dma-buf.dox
index 5042f285c..ec02b9da7 100644
--- a/doc/dox/internals/dma-buf.dox
+++ b/doc/dox/internals/dma-buf.dox
@@ -349,10 +349,10 @@ rectangles. For example
params[n_params++] = spa_pod_builder_pop(&b, &f);
```
-After having received the first \ref SPA_PARAM_PeerCapability param, if it contained the \ref
-PW_CAPABILITY_DEVICE_ID set to `true`, the full set of formats can be sent using \ref
-pw_stream_update_params following by activating the stream using
-`pw_stream_set_active(stream, true)`.
+After having received the first \ref SPA_PARAM_PeerCapability param, if it contained the
+\ref PW_CAPABILITY_DEVICE_ID_NEGOTIATION set to a supported API version number, the full
+set of formats can be sent using \ref pw_stream_update_params following by activating the
+stream usina supported API version numberstream_set_active(stream, true)`.
Note that the first \ref SPA_PARAM_Format received may be the result of the initial format
negotian with bare minimum parameters, and will be superseded by the result of the format
diff --git a/doc/dox/internals/index.dox b/doc/dox/internals/index.dox
index 89d2e9da3..357e2f126 100644
--- a/doc/dox/internals/index.dox
+++ b/doc/dox/internals/index.dox
@@ -10,6 +10,7 @@
- \subpage page_objects_design
- \subpage page_library
- \subpage page_dma_buf
+- \subpage page_running
- \subpage page_scheduling
- \subpage page_driver
- \subpage page_latency
diff --git a/doc/dox/internals/latency.dox b/doc/dox/internals/latency.dox
index 0ff2cbe95..efc2b4c8b 100644
--- a/doc/dox/internals/latency.dox
+++ b/doc/dox/internals/latency.dox
@@ -103,7 +103,7 @@ down and upstream.
# Async nodes
When a node has the node.async property set to true, it will be considered an async
-node and will be scheduled differently, see scheduling.dox.
+node and will be scheduled differently, see \ref page_scheduling .
A link between a port of an async node and another port (async or not) is called an
async link and will have the link.async=true property.
diff --git a/doc/dox/internals/midi.dox b/doc/dox/internals/midi.dox
index 4c86c516b..e89b24578 100644
--- a/doc/dox/internals/midi.dox
+++ b/doc/dox/internals/midi.dox
@@ -62,6 +62,13 @@ As of 1.4, SPA_CONTROL_UMP (Universal Midi Packet) is the prefered format
for the MIDI 1.0 and 2.0 messages in the \ref spa_pod_sequence. Conversion
to SPA_CONTROL_Midi is performed for legacy applications.
+As of 1.7 the prefered format is Midi1 again because most devices and
+applications are still Midi1 and conversions between Midi1 and UMP are not
+completely transparent in ALSA and PipeWire. UMP in the ALSA sequencer
+and consumers must be enabled explicitly. UMP in producers is supported
+still and will be converted to Midi1 by all consumers that did not explicitly
+enable UMP support.
+
## The PipeWire Daemon
Nothing special is implemented for MIDI. Negotiation of formats
@@ -104,13 +111,14 @@ filtering out the \ref SPA_CONTROL_Midi, \ref SPA_CONTROL_OSC and
\ref SPA_CONTROL_UMP types. On output ports, the JACK event stream is
converted to control messages in a similar way.
-Normally, all MIDI and UMP messages are converted to MIDI1 jack events unless
-the JACK port was created with an explcit "32 bit raw UMP" format or with
-the JackPortIsMIDI2 flag, in which case the raw UMP is passed to the JACK
-application directly. For output ports,
-the JACK events are assumed to be MIDI1 and converted to UMP unless the port
-has the "32 bit raw UMP" format or the JackPortIsMIDI2 flag, in which case
-the UMP messages are simply passed on.
+Normally, all MIDI and UMP input messages are converted to MIDI1 jack
+events unless the JACK port was created with an explcit "32 bit raw UMP"
+format or with the JackPortIsMIDI2 flag, in which case the messages are
+converted to UMP or passed on directly.
+
+For output ports, the JACK events are assumed to be
+MIDI1 unless the port has the "32 bit raw UMP" format or the JackPortIsMIDI2
+flag, in which case the control messages are assumed to be UMP.
There is a 1 to 1 mapping between the JACK events and control
messages so there is no information loss or need for complicated
diff --git a/doc/dox/internals/running.dox b/doc/dox/internals/running.dox
new file mode 100644
index 000000000..e7ffe06db
--- /dev/null
+++ b/doc/dox/internals/running.dox
@@ -0,0 +1,393 @@
+/** \page page_running Node running
+
+This document tries to explain how the Nodes in a PipeWire graph become
+runnable so that they can be scheduled later.
+
+It also describes how nodes are grouped together and scheduled together.
+
+# Runnable Nodes
+
+A runnable node is a node that will participate in the dataflow in the
+PipeWire graph.
+
+Not all nodes should participate by default. For example, filters or device
+nodes that are not linked to any runnable nodes should not do useless
+processing.
+
+Nodes form one or more groups depending on properties and how they are
+linked. For each group, one driver is selected to run the group. Inside the
+group, all runnable nodes are scheduled by the group driver. If there are no
+runnable nodes in a group, the driver is not started.
+
+PipeWire provides mechanisms to precisely describe how and when nodes should
+be scheduled and grouped using:
+
+ - port properties to control how port links control runnable state
+ - node properties to control processing
+ - grouping of nodes and internal links between nodes
+
+# Port passive modes
+
+A Port has 4 passive modes, this depends on the value of the `port.passive` property:
+
+ - `false`, the port will make the peer active and an active peer will make this port
+ active.
+ - `true`, the port will not make the peer active and an active peer will not make this
+ port active.
+ - `follow`, the port will not make the peer active but an active peer will make this
+ port active.
+ - `follow-suspend`, the port will only make another follow-suspend peer active but any
+ active peer will make this port active.
+
+The combination of these 4 modes on the output and input ports of a link results in a
+wide range of use cases.
+
+# Node passive modes
+
+A Node can have 10 passive modes, `node.passive` can be set to a comma separated list
+of the following values:
+
+ - `false`, both input and output ports have `port.passive = false`
+ - `in`, input ports have `port.passive = true`
+ - `out`, output ports have `port.passive = true`
+ - `true`, both input and output ports have `port.passive = true`. This is the same
+ as `in,out`.
+ - `in-follow`, input ports have `port.passive = follow`
+ - `out-follow`, output ports have `port.passive = follow`
+ - `follow`, input and output ports have `port.passive = follow`. This is the same
+ as `in-follow,out-follow`.
+ - `in-follow-suspend`, input ports have `port.passive = follow-suspend`
+ - `out-follow-suspend`, output ports have `port.passive = follow-suspend`
+ - `follow-suspend`, input and output ports have `port.passive = follow-suspend`.
+ This is the same as `in-follow-suspend,out-follow-suspend`.
+
+Nodes by default have the `false` mode but nodes with the `media.class` property
+containing `Sink`, `Source` or `Duplex` receive the `follow-suspend` mode by default.
+
+Unless explicitly configured, ports inherit the mode from their parent node.
+
+# Updating the node runnable state
+
+We iterate all nodes A in the graph and look at its peers B.
+
+Based on the port passive modes of the port links we can decide if the nodes are
+runnable or not. A link will always make both nodes runnable or none.
+
+The following table decides the runnability of the 2 nodes based on the port.passive
+mode of the link between the 2 ports:
+
+```
+ B-false B-true B-follow B-follow-suspend
+
+A-false X X X X
+A-true
+A-follow
+A-follow-suspend X
+ Table 1
+```
+
+
+When a node is made runnable, the port passive mode will then decide if the peer ports
+should become active as well with the following table.
+
+```
+ B-false B-true B-follow B-follow-suspend
+
+A-false X X X
+A-true X X X
+A-follow X X X
+A-follow-suspend X X X
+ Table 2
+```
+
+So when A is runnable, all peers are activated except those with `port.passive=true`.
+
+When A is runnable, all the nodes that share the same group or link-group will also
+be made runnable.
+
+# Use cases
+
+Let's check some cases that we want to solve with these node and port properties.
+
+## Device nodes
+
+```
+ +--------+ +--------+
+ | ALSA | | ALSA |
+ | Source | | Sink |
+ | FL FL |
+ | FR FR |
+ +--------+ +--------+
+```
+
+Unlinked device nodes are supposed to stay suspended when nothing is linked to
+them.
+
+```
+ +----------+ +--------+
+ | playback | | ALSA |
+ | | | Sink |
+ | FL ------ FL |
+ | FR ------ FR |
+ +----------+ +--------+
+```
+
+An (active) player node linked to a device node should make both nodes runnable.
+
+Device nodes have the `port.passive = follow-suspend` property by default. The
+playback node has the `port.passive = false` by default.
+
+If we look at the playback node as A and the sink as B, both nodes will be made
+runnable according to Table 1.
+
+The two runnable nodes form a group and will be scheduled together. One of the
+nodes of a group with the `node.driver = true` property is selected as the
+driver. In the above case, that will be the ALSA Sink.
+
+Likewise, a capture node linked to an ALSA Source should make both nodes runnable.
+
+```
+ +--------+ +---------+
+ | ALSA | | capture |
+ | Source | | |
+ | FL ------ FL |
+ | FR ------ FR |
+ +--------+ +---------+
+```
+
+The ALSA Source is now the driver.
+
+Also, linking 2 device nodes together should make them runnable:
+
+```
+ +--------+ +--------+
+ | ALSA | | ALSA |
+ | Source | | Sink |
+ | FL ----------------------- FL |
+ | FR ----------------------- FR |
+ +--------+ +--------+
+```
+
+This is the case because in Table 1, the two `port.passive = follow-suspend` ports
+from the Source and Sink activate each other.
+
+## Filter nodes
+
+When there is a filter in front of the ALSA Sink, it should not make the filter and
+sink runnable.
+
+```
+ +--------+ +--------+
+ | filter | | ALSA |
+ | | | Sink |
+ FL FL ------ FL |
+ FR FR ------ FR |
+ +--------+ +--------+
+```
+
+The links between the filter and ALSA Sink are `port.passive = true` and don't make
+the nodes runnable.
+
+The filter needs to be made runnable via some other means to also make the ALSA
+Sink runnable, for example by linking a playback node:
+
+```
+ +----------+ +--------+ +--------+
+ | playback | | filter | | ALSA |
+ | | | | | Sink |
+ | FL ------ FL FL ------ FL |
+ | FR ------ FR FR ------ FR |
+ +----------+ +--------+ +--------+
+```
+
+The input port of the filter is `port.passive = follow-suspend' and so it can be
+activated by the playback node.
+
+Likewise, if the ALSA Sink is runnable, it should not automatically make the
+filter runnable. For example:
+
+```
+ +--------+ +--------+
+ | filter | | ALSA |
+ | | | Sink |
+ FL FL ---+-- FL |
+ FR FR ---|+- FR |
+ +--------+ || +--------+
+ ||
+ +----------+ ||
+ | playback | ||
+ | | ||
+ | FL ---+|
+ | FR ----+
+ +----------+
+```
+
+Here the playback node makes the ALSA Sink runnable but the filter
+stays not-runnable because the output port is `port.passive = true`.
+
+## Device node monitor
+
+Consider the case where we have an ALSA Sink and a monitor stream
+connected to the sink monitor ports.
+
+```
+ +-------+ +--------++
+ | ALSA | | monitor |
+ | Sink | | |
+ FL FL ------ FL |
+ FR FR ------ FR |
+ +-------+ +---------+
+```
+
+We would like to keep the monitor stream and the ALSA sink suspended
+unless something else activates the ALSA Sink:
+
+
+```
+ +----------+ +-------+ +---------+
+ | playback | | ALSA | | monitor |
+ | | | Sink | | |
+ | FL ------ FL FL ------ FL |
+ | FR ------ FR FR ------ FR |
+ +----------+ +-------+ +---------+
+```
+
+We can do this by making the monitor stream input ports `port.passive = follow`
+and leave the ALSA Sink monitor output ports as `port.passive = follow-suspend`.
+
+According to Table 1, both nodes will not activate each other but when ALSA Sink
+becomes runnable because of playback, according to Table 2, the monitor will
+become runnable as well.
+
+Note how we need the distinction between `follow` and `follow-suspend` for this
+use case.
+
+## Node groups
+
+Normally when an application makes a capture and playback node, both nodes will
+be scheduled in different groups, consider:
+
+
+```
+ +--------+ +---------+
+ | ALSA | | capture |
+ | Source | | |
+ | FL ------ FL |
+ | FR ------ FR |
+ +--------+ +---------+
+
+ +----------+ +--------+
+ | playback | | ALSA |
+ | | | Sink |
+ | FL ------ FL |
+ | FR ------ FR |
+ +----------+ +--------+
+```
+
+Here we see 2 groups with the ALSA Source and ALSA Sink respectively as the
+drivers. Depending on the clocks of the nodes, the capture and playback will not
+be in sync. They will each run in their own time domain depending on the rate of
+the drivers.
+
+When we place a node.group property with the same value on the capture and playback
+nodes, they will be grouped together and this whole graph becomes one single group.
+
+Because there are 2 potential drivers in the group, the one with the highest
+`priority.driver` property is selected as the driver in the group. The other nodes
+in the group (including the other driver) become followers in the group.
+
+When a node becomes runnable, all other nodes with the same node.group property
+become runnable as well.
+
+## Node link groups
+
+When we have a filter that is constructed from two nodes, an input and an output
+node, we could use the `node.group` property to make sure they are both scheduled
+and made runnable together.
+
+```
+ +--------+ +-------+ +--------+ +-------+
+ | ALSA | | input | | output | | ALSA |
+ | Source | | | | | | Sink |
+ | FL ------ FL -- processing-- FL ------ FL |
+ | FR ------ FR | | FR ------ FR |
+ +--------+ +-------+ +--------+ +-------+
+```
+
+This would work fine but it does not describe that there is an implicit internal
+link between the input and output node. This information is important for the
+session manager to avoid linking the output node to the input node and make a
+loop.
+
+The `node.link-group` property can be used to both group the nodes together and
+descibe that they are internally linked together.
+
+When a node becomes runnable, all other nodes with the same node.link-group property
+become runnable as well.
+
+For the 2 node filters, like loopback and filter-chain, the same `port.passive`
+property rules apply as for the filter nodes. Note that for the virtual devices,
+the Source/Sink nodes will be `follow-suspend` by default and the other node should
+be set to `node.passive = true` to make the ports passive.
+
+## Want driver
+
+When there is no driver node in the group, nothing should be scheduled. This can
+happen when a playback node is linked to a capture node:
+
+```
+ +--------+ +---------+
+ | player | | capture |
+ | | | |
+ | FL ----------- FL |
+ | FR ----------- FR |
+ +--------+ +---------+
+```
+
+None of these nodes is a driver so there is no driver in the group and nothing
+will be scheduled.
+
+When one of the nodes has `node.want-driver = true` they are grouped and
+scheduled with a random driver node. This is often the driver node with the
+highest priority (usually the Dummy-Driver) or otherwise a driver that is already
+scheduling some other nodes.
+
+## Always process nodes
+
+A simple node, unlinked to anything should normally not run.
+
+```
+ +--------+
+ | player |
+ | |
+ | FL
+ | FR
+ +--------+
+```
+
+When the `node.always-process = true` property is set, the node will however be
+made runnable even if unlinked. This is done by adding the node to a random driver.
+
+`node.always-process = true` implies the `node.want-driver = true` property.
+
+## Sync groups
+
+In some cases, you only want to group nodes together depending on some condition.
+
+For example, when the JACK transport is activated, all nodes in the graph should share
+the same driver node, regardless of the grouping or linking of the nodes.
+
+This is done by setting the same node.sync-group property on all nodes (by default all
+nodes have `node.sync-group = group.sync.0`). When a node sets `node.sync = true` all
+the other nodes with the same `node.sync-group` property are grouped together.
+
+This can be used to implement the JACK transport. When the transport is started, the
+`node.sync=true` property is set and all nodes join one group with a shared driver
+and timing information.
+
+
+
+
+*/
+
+
diff --git a/doc/dox/internals/scheduling.dox b/doc/dox/internals/scheduling.dox
index 38b05596b..c74124480 100644
--- a/doc/dox/internals/scheduling.dox
+++ b/doc/dox/internals/scheduling.dox
@@ -23,6 +23,9 @@ node is scheduled to run.
This document describes the processing that happens in the data processing
thread after the main thread has configured it.
+Before scheduling of the node happens, the scheduler will collect a list of
+nodes that are runnable, see \ref page_running
+
# Nodes
Nodes are objects with 0 or more input and output ports.
diff --git a/doc/dox/modules.dox b/doc/dox/modules.dox
index 4e9358197..85b0a1418 100644
--- a/doc/dox/modules.dox
+++ b/doc/dox/modules.dox
@@ -81,6 +81,7 @@ List of known modules:
- \subpage page_module_raop_discover
- \subpage page_module_roc_sink
- \subpage page_module_roc_source
+- \subpage page_module_scheduler_v1
- \subpage page_module_rtp_sap
- \subpage page_module_rtp_sink
- \subpage page_module_rtp_source
@@ -90,6 +91,8 @@ List of known modules:
- \subpage page_module_spa_node_factory
- \subpage page_module_spa_device
- \subpage page_module_spa_device_factory
+- \subpage page_module_sendspin_recv
+- \subpage page_module_sendspin_send
- \subpage page_module_session_manager
- \subpage page_module_snapcast_discover
- \subpage page_module_vban_recv
diff --git a/doc/dox/overview.dox b/doc/dox/overview.dox
index 1490cd444..9e2530163 100644
--- a/doc/dox/overview.dox
+++ b/doc/dox/overview.dox
@@ -77,7 +77,7 @@ Certain properties are, by convention, expected for specific object types.
Each object type has a list of methods that it needs to implement.
-The session manager is responsible for defining the list of permissions each client has. Each permission entry is an object ID and four flags. The four flags are:
+The session manager is responsible for defining the list of permissions each client has. Each permission entry is an object ID and five flags. The five flags are:
- Read: the object can be seen and events can be received;
- Write: the object can be modified, usually through methods (which requires the execute flag)
@@ -109,7 +109,7 @@ Modules in PipeWire can only be loaded in their own process. A client, for examp
Nodes are the core data processing entities in PipeWire.
They may produce data (capture devices, signal generators, ...), consume data (playback devices, network endpoints, ...) or both (filters).
-Notes have a method `process`, which eats up data from input ports and provides data for each output port.
+Nodes have a method `process`, which eats up data from input ports and provides data for each output port.
#### Ports
diff --git a/doc/dox/programs/pw-cat.1.md b/doc/dox/programs/pw-cat.1.md
index b681e54a1..8ec02c711 100644
--- a/doc/dox/programs/pw-cat.1.md
+++ b/doc/dox/programs/pw-cat.1.md
@@ -124,6 +124,9 @@ Set a node target (default auto). The value can be:
- \: The object.serial or the node.name of a target node
\endparblock
+\par -C | \--monitor
+In recording mode, record from monitor ports.
+
\par \--latency=VALUE\[*units*\]
\parblock
Set the node latency (default 100ms)
diff --git a/doc/dox/programs/pw-top.1.md b/doc/dox/programs/pw-top.1.md
index 1207e3205..353015d1a 100644
--- a/doc/dox/programs/pw-top.1.md
+++ b/doc/dox/programs/pw-top.1.md
@@ -188,6 +188,11 @@ Quit
Clear the ERR counters. This does *not* clear the counters globally,
it will only reset the counters in this instance of *pw-top*.
+\par [f|F]
+Cycle through filter presets. If any nodes are filtered from view,
+the current preset will be indicated in the header bar. Only nodes
+with the indicated state or higher, will be printed.
+
# OPTIONS
\par -h | \--help
@@ -199,6 +204,9 @@ Run in non-interactive batch mode, similar to top\'s batch mode.
\par -n | \--iterations=NUMBER
Exit after NUMBER of batch iterations. Only used in batch mode.
+\par -f | \--filter=NUMBER
+Start with filter preset NUMBER selected.
+
\par -r | \--remote=NAME
The name the *remote* instance to monitor. If left unspecified, a
connection is made to the default PipeWire instance.
diff --git a/doc/meson.build b/doc/meson.build
index f4aa4ba6a..d014d227d 100644
--- a/doc/meson.build
+++ b/doc/meson.build
@@ -67,6 +67,7 @@ extra_docs = [
'dox/internals/session-manager.dox',
'dox/internals/objects.dox',
'dox/internals/audio.dox',
+ 'dox/internals/running.dox',
'dox/internals/scheduling.dox',
'dox/internals/driver.dox',
'dox/internals/protocol.dox',
diff --git a/meson.build b/meson.build
index c954a644c..37badca51 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
project('pipewire', ['c' ],
- version : '1.6.0',
+ version : '1.7.0',
license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ],
meson_version : '>= 0.61.1',
default_options : [ 'warning_level=3',
@@ -116,6 +116,7 @@ cc_flags = common_flags + [
'-Werror=old-style-definition',
'-Werror=missing-parameter-type',
'-Werror=strict-prototypes',
+ '-Werror=discarded-qualifiers',
]
add_project_arguments(cc.get_supported_arguments(cc_flags), language: 'c')
add_project_arguments(cc_native.get_supported_arguments(cc_flags),
@@ -367,7 +368,7 @@ cdata.set('HAVE_OPUS', opus_dep.found())
summary({'readline (for pw-cli)': readline_dep.found()}, bool_yn: true, section: 'Misc dependencies')
cdata.set('HAVE_READLINE', readline_dep.found())
ncurses_dep = dependency('ncursesw', required : false)
-sndfile_dep = dependency('sndfile', version : '>= 1.0.20', required : get_option('sndfile'))
+sndfile_dep = dependency('sndfile', version : '>= 1.1.0', required : get_option('sndfile'))
summary({'sndfile': sndfile_dep.found()}, bool_yn: true, section: 'pw-cat/pw-play/pw-dump/filter-chain')
cdata.set('HAVE_SNDFILE', sndfile_dep.found())
pulseaudio_dep = dependency('libpulse', required : get_option('libpulse'))
@@ -411,7 +412,7 @@ gst_deps_def = {
'gio-unix-2.0': {},
'gstreamer-1.0': {'version': '>= 1.10.0'},
'gstreamer-base-1.0': {},
- 'gstreamer-video-1.0': {},
+ 'gstreamer-video-1.0': {'version': '>= 1.22.0'},
'gstreamer-audio-1.0': {},
'gstreamer-allocators-1.0': {},
}
diff --git a/pipewire-jack/src/meson.build b/pipewire-jack/src/meson.build
index 0630d96a8..639405bb9 100644
--- a/pipewire-jack/src/meson.build
+++ b/pipewire-jack/src/meson.build
@@ -55,7 +55,7 @@ pipewire_jackserver = shared_library('jackserver',
pipewire_jackserver_sources,
soversion : soversion,
version : libjackversion,
- c_args : pipewire_jack_c_args,
+ c_args : pipewire_jack_c_args + '-DLIBJACKSERVER',
include_directories : [configinc, jack_inc],
dependencies : [pipewire_dep, mathlib],
install : true,
diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c
index 1bef74283..5ae097ba8 100644
--- a/pipewire-jack/src/pipewire-jack.c
+++ b/pipewire-jack/src/pipewire-jack.c
@@ -86,7 +86,7 @@ PW_LOG_TOPIC_STATIC(jack_log_topic, "jack");
#define OTHER_CONNECT_FAIL -1
#define OTHER_CONNECT_IGNORE 0
-#define NOTIFY_BUFFER_SIZE (1u<<13)
+#define NOTIFY_BUFFER_SIZE (1u<<16)
#define NOTIFY_BUFFER_MASK (NOTIFY_BUFFER_SIZE-1)
struct notify {
@@ -104,8 +104,8 @@ struct notify {
#define NOTIFY_TYPE_TOTAL_LATENCY ((9<<4)|NOTIFY_ACTIVE_FLAG)
#define NOTIFY_TYPE_PORT_RENAME ((10<<4)|NOTIFY_ACTIVE_FLAG)
int type;
- struct object *object;
int arg1;
+ struct object *object;
const char *msg;
};
@@ -492,6 +492,8 @@ struct client {
jack_position_t jack_position;
jack_transport_state_t jack_state;
struct frame_times jack_times;
+
+ struct object dummy_port;
};
#define return_val_if_fail(expr, val) \
@@ -1446,8 +1448,9 @@ static size_t convert_from_event(void *midi, void *buffer, size_t size, uint32_t
switch (type) {
case TYPE_ID_MIDI:
+ event_type = SPA_CONTROL_Midi;
+ break;
case TYPE_ID_OSC:
- /* we handle MIDI as OSC, check below */
event_type = SPA_CONTROL_OSC;
break;
case TYPE_ID_UMP:
@@ -1464,27 +1467,15 @@ static size_t convert_from_event(void *midi, void *buffer, size_t size, uint32_t
for (i = 0; i < count; i++) {
jack_midi_event_t ev;
jack_midi_event_get(&ev, midi, i);
+ uint32_t ev_type;
- if (type != TYPE_ID_MIDI || is_osc(&ev)) {
- /* no midi port or it's OSC */
- spa_pod_builder_control(&b, ev.time, event_type);
- spa_pod_builder_bytes(&b, ev.buffer, ev.size);
- } else {
- /* midi port and it's not OSC, convert to UMP */
- uint8_t *data = ev.buffer;
- size_t size = ev.size;
- uint64_t state = 0;
+ if (type == TYPE_ID_MIDI && is_osc(&ev))
+ ev_type = SPA_CONTROL_OSC;
+ else
+ ev_type = event_type;
- while (size > 0) {
- uint32_t ump[4];
- int ump_size = spa_ump_from_midi(&data, &size,
- ump, sizeof(ump), 0, &state);
- if (ump_size <= 0)
- break;
- spa_pod_builder_control(&b, ev.time, SPA_CONTROL_UMP);
- spa_pod_builder_bytes(&b, ump, ump_size);
- }
- }
+ spa_pod_builder_control(&b, ev.time, ev_type);
+ spa_pod_builder_bytes(&b, ev.buffer, ev.size);
}
spa_pod_builder_pop(&b, &f);
return b.state.offset;
@@ -2208,7 +2199,7 @@ on_rtsocket_condition(void *data, int fd, uint32_t mask)
}
} else if (SPA_LIKELY(mask & SPA_IO_IN)) {
uint32_t buffer_frames;
- int status = 0;
+ int status = -EBUSY;
buffer_frames = cycle_run(c);
@@ -4468,6 +4459,11 @@ jack_client_t * jack_client_open (const char *client_name,
0, NULL, &client->info);
client->info.change_mask = 0;
+ client->dummy_port.type = INTERFACE_Port;
+ snprintf(client->dummy_port.port.name, sizeof(client->dummy_port.port.name), "%s:dummy", client_name);
+ snprintf(client->dummy_port.port.alias1, sizeof(client->dummy_port.port.alias1), "%s:dummy", client_name);
+ snprintf(client->dummy_port.port.alias2, sizeof(client->dummy_port.port.alias2), "%s:dummy", client_name);
+
client->show_monitor = pw_properties_get_bool(client->props, "jack.show-monitor", true);
client->show_midi = pw_properties_get_bool(client->props, "jack.show-midi", true);
client->merge_monitor = pw_properties_get_bool(client->props, "jack.merge-monitor", true);
@@ -4868,7 +4864,7 @@ int jack_activate (jack_client_t *client)
freeze_callbacks(c);
/* reemit buffer_frames */
- c->buffer_frames = 0;
+ c->buffer_frames = (uint32_t)-1;
pw_data_loop_start(c->loop);
c->active = true;
@@ -4880,9 +4876,21 @@ int jack_activate (jack_client_t *client)
c->activation->pending_sync = true;
spa_list_for_each(o, &c->context.objects, link) {
+#if !defined(LIBJACKSERVER)
if (o->type != INTERFACE_Port || o->port.port == NULL ||
o->port.port->client != c || !o->port.port->valid)
continue;
+#else
+ /* emits all foreign active ports, skips own (already announced via jack_port_register) */
+ if (o->type != INTERFACE_Port || o->removed)
+ continue;
+ /* own ports are handled by jack_port_register */
+ if (o->port.port != NULL && o->port.port->client == c)
+ continue;
+ /* only announce ports whose node is active */
+ if (o->port.node != NULL && !node_is_active(c, o->port.node))
+ continue;
+#endif
o->registered = 0;
queue_notify(c, NOTIFY_TYPE_PORTREGISTRATION, o, 1, NULL);
}
@@ -5318,7 +5326,7 @@ int jack_set_freewheel(jack_client_t* client, int onoff)
pw_thread_loop_lock(c->context.loop);
str = pw_properties_get(c->props, PW_KEY_NODE_GROUP);
if (str != NULL) {
- char *p = strstr(str, ",pipewire.freewheel");
+ const char *p = strstr(str, ",pipewire.freewheel");
if (p == NULL)
p = strstr(str, "pipewire.freewheel");
if (p == NULL && onoff)
@@ -5437,7 +5445,7 @@ SPA_EXPORT
jack_nframes_t jack_get_buffer_size (jack_client_t *client)
{
struct client *c = (struct client *) client;
- jack_nframes_t res = -1;
+ uint32_t res = -1;
return_val_if_fail(c != NULL, 0);
@@ -5454,7 +5462,7 @@ jack_nframes_t jack_get_buffer_size (jack_client_t *client)
}
c->buffer_frames = res;
pw_log_debug("buffer_frames: %u", res);
- return res;
+ return (jack_nframes_t)res;
}
SPA_EXPORT
@@ -5951,9 +5959,7 @@ static const char *port_name(struct object *o)
{
const char *name;
struct client *c = o->client;
- if (c == NULL)
- return NULL;
- if (c->default_as_system && is_port_default(c, o))
+ if (c != NULL && c->default_as_system && is_port_default(c, o))
name = o->port.system;
else
name = o->port.name;
@@ -6007,7 +6013,16 @@ jack_port_type_id_t jack_port_type_id (const jack_port_t *port)
return_val_if_fail(o != NULL, 0);
if (o->type != INTERFACE_Port)
return TYPE_ID_OTHER;
- return o->port.type_id;
+
+ /* map internal type IDs to jack1/jack2 compatible public values */
+ switch (o->port.type_id) {
+ case TYPE_ID_AUDIO: return 0;
+ case TYPE_ID_MIDI:
+ case TYPE_ID_OSC:
+ case TYPE_ID_UMP: return 1; /* all MIDI variants map to 1 */
+ case TYPE_ID_VIDEO: return 3; /* video maps to 3 */
+ default: return o->port.type_id;
+ }
}
SPA_EXPORT
@@ -6999,13 +7014,11 @@ jack_port_t * jack_port_by_id (jack_client_t *client,
pthread_mutex_lock(&c->context.lock);
res = find_by_serial(c, port_id);
- if (res && res->type != INTERFACE_Port)
- res = NULL;
- pw_log_debug("%p: port %d -> %p", c, port_id, res);
pthread_mutex_unlock(&c->context.lock);
+ if (res == NULL || res->type != INTERFACE_Port)
+ res = &c->dummy_port;
- if (res == NULL)
- pw_log_info("%p: port %d not found", c, port_id);
+ pw_log_debug("%p: port %d -> %p", c, port_id, res);
return object_to_port(res);
}
diff --git a/pipewire-v4l2/src/pipewire-v4l2.c b/pipewire-v4l2/src/pipewire-v4l2.c
index 8fc07151a..7a5e5c057 100644
--- a/pipewire-v4l2/src/pipewire-v4l2.c
+++ b/pipewire-v4l2/src/pipewire-v4l2.c
@@ -2570,7 +2570,10 @@ static void *v4l2_mmap(void *addr, size_t length, int prot,
buf = &file->buffers[id];
data = &buf->buf->buffer->datas[0];
- pw_map_range_init(&range, data->mapoffset, data->maxsize, 1024);
+ if (pw_map_range_init(&range, data->mapoffset, data->maxsize, 1024) < 0) {
+ res = MAP_FAILED;
+ goto error_unlock;
+ }
if (!SPA_FLAG_IS_SET(data->flags, SPA_DATA_FLAG_READABLE))
prot &= ~PROT_READ;
diff --git a/po/kk.po b/po/kk.po
index 6a00211f3..507c967e0 100644
--- a/po/kk.po
+++ b/po/kk.po
@@ -1,15 +1,14 @@
# Kazakh translation of pipewire.
# Copyright (C) 2020 The pipewire authors.
# This file is distributed under the same license as the pipewire package.
-# Baurzhan Muftakhidinov , 2020.
+# Baurzhan Muftakhidinov , 2020-2026.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
-"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/"
-"issues/new\n"
-"POT-Creation-Date: 2021-04-18 16:54+0800\n"
-"PO-Revision-Date: 2020-06-30 08:04+0500\n"
+"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues\n"
+"POT-Creation-Date: 2026-03-09 12:19+0000\n"
+"PO-Revision-Date: 2026-03-17 00:04+0500\n"
"Last-Translator: Baurzhan Muftakhidinov \n"
"Language-Team: \n"
"Language: kk\n"
@@ -17,96 +16,199 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
-"X-Generator: Poedit 2.3.1\n"
+"X-Generator: Poedit 3.9\n"
-#: src/daemon/pipewire.c:43
+#: src/daemon/pipewire.c:29
#, c-format
msgid ""
"%s [options]\n"
" -h, --help Show this help\n"
+" -v, --verbose Increase verbosity by one level\n"
" --version Show version\n"
" -c, --config Load config (Default %s)\n"
+" -P --properties Set context properties\n"
msgstr ""
+"%s [опциялар]\n"
+" -h, --help Осы көмекті көрсету\n"
+" -v, --verbose Ақпараттылығын бір деңгейге арттыру\n"
+" --version Нұсқасын көрсету\n"
+" -c, --config Конфигурацияны жүктеу (Бастапқы %s)\n"
+" -P --properties Контекст қасиеттерін орнату\n"
+
+#: src/daemon/pipewire.desktop.in:3
+msgid "PipeWire Media System"
+msgstr "PipeWire медиа жүйесі"
#: src/daemon/pipewire.desktop.in:4
-msgid "PipeWire Media System"
-msgstr ""
-
-#: src/daemon/pipewire.desktop.in:5
msgid "Start the PipeWire Media System"
-msgstr ""
+msgstr "PipeWire медиа жүйесін іске қосу"
-#: src/examples/media-session/alsa-monitor.c:526
-#: spa/plugins/alsa/acp/compat.c:187
-msgid "Built-in Audio"
-msgstr "Құрамындағы аудио"
+#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159
+#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159
+#, c-format
+msgid "Tunnel to %s%s%s"
+msgstr "%s%s%s бағытына туннель"
-#: src/examples/media-session/alsa-monitor.c:530
-#: spa/plugins/alsa/acp/compat.c:192
-msgid "Modem"
-msgstr "Модем"
+#: src/modules/module-fallback-sink.c:40
+msgid "Dummy Output"
+msgstr "Жалған шығыс"
-#: src/examples/media-session/alsa-monitor.c:539
+#: src/modules/module-pulse-tunnel.c:761
+#, c-format
+msgid "Tunnel for %s@%s"
+msgstr "%s@%s үшін туннель"
+
+#: src/modules/module-zeroconf-discover.c:290
msgid "Unknown device"
-msgstr ""
+msgstr "Белгісіз құрылғы"
-#: src/tools/pw-cat.c:991
+#: src/modules/module-zeroconf-discover.c:302
+#, c-format
+msgid "%s on %s@%s"
+msgstr "%s, %s@%s ішінде"
+
+#: src/modules/module-zeroconf-discover.c:306
+#, c-format
+msgid "%s on %s"
+msgstr "%s, %s ішінде"
+
+#: src/tools/pw-cat.c:269
+#, c-format
+msgid "Supported formats:\n"
+msgstr "Қолдау көрсетілетін пішімдер:\n"
+
+#: src/tools/pw-cat.c:754
+#, c-format
+msgid "Supported channel layouts:\n"
+msgstr "Қолдау көрсетілетін арна жаймалары:\n"
+
+#: src/tools/pw-cat.c:764
+#, c-format
+msgid "Supported channel layout aliases:\n"
+msgstr "Қолдау көрсетілетін арна жаймаларының алиастары:\n"
+
+#: src/tools/pw-cat.c:766
+#, c-format
+msgid " %s -> %s\n"
+msgstr " %s -> %s\n"
+
+#: src/tools/pw-cat.c:771
+#, c-format
+msgid "Supported channel names:\n"
+msgstr "Қолдау көрсетілетін арна атаулары:\n"
+
+#: src/tools/pw-cat.c:1182
#, c-format
msgid ""
-"%s [options] \n"
+"%s [options] [|-]\n"
" -h, --help Show this help\n"
" --version Show version\n"
" -v, --verbose Enable verbose operations\n"
"\n"
msgstr ""
+"%s [опциялар] [<файл>|-]\n"
+" -h, --help Осы көмекті көрсету\n"
+" --version Нұсқасын көрсету\n"
+" -v, --verbose Толық ақпаратты іске қосу\n"
+"\n"
-#: src/tools/pw-cat.c:998
+#: src/tools/pw-cat.c:1189
#, c-format
msgid ""
" -R, --remote Remote daemon name\n"
" --media-type Set media type (default %s)\n"
" --media-category Set media category (default %s)\n"
" --media-role Set media role (default %s)\n"
-" --target Set node target (default %s)\n"
+" --target Set node target serial or name (default %s)\n"
" 0 means don't link\n"
" --latency Set node latency (default %s)\n"
" Xunit (unit = s, ms, us, ns)\n"
" or direct samples (256)\n"
-" the rate is the one of the source "
-"file\n"
-" --list-targets List available targets for --target\n"
+" the rate is the one of the source file\n"
+" -P --properties Set node properties\n"
"\n"
msgstr ""
+" -R, --remote Қашықтағы қызмет атауы\n"
+" --media-type Медиа түрін орнату (бастапқы %s)\n"
+" --media-category Медиа категориясын орнату (бастапқы %s)\n"
+" --media-role Медиа рөлін орнату (бастапқы %s)\n"
+" --target Түйін мақсатының сериялық нөмірін немесе атын орнату (бастапқы "
+"%s)\n"
+" 0 байланыстырмауды білдіреді\n"
+" --latency Түйін кідірісін орнату (бастапқы %s)\n"
+" Xюнит (юнит = с, мс, мкс, нс)\n"
+" немесе тікелей үлгілер (256)\n"
+" жиілік бастапқы файлдың жиілігі болып табылады\n"
+" -P --properties Түйін қасиеттерін орнату\n"
+"\n"
-#: src/tools/pw-cat.c:1016
+#: src/tools/pw-cat.c:1207
#, c-format
msgid ""
-" --rate Sample rate (req. for rec) (default "
-"%u)\n"
-" --channels Number of channels (req. for rec) "
-"(default %u)\n"
+" --rate Sample rate (default %u)\n"
+" --channels Number of channels (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"
+" a channel layout: \"Stereo\", \"5.1\",... or\n"
+" comma separated list of channel names: eg. \"FL,FR\"\n"
+" --list-layouts List supported channel layouts\n"
+" --list-channel-names List supported channel maps\n"
+" --format Sample format (default %s)\n"
+" --list-formats List supported sample formats\n"
+" --container Container format\n"
+" --list-containers List supported containers and extensions\n"
" --volume Stream volume 0-1.0 (default %.3f)\n"
-" -q --quality Resampler quality (0 - 15) (default "
-"%d)\n"
+" -q --quality Resampler quality (0 - 15) (default %d)\n"
+" -a, --raw RAW mode\n"
+" -M, --force-midi Force midi format, one of \"midi\" or \"ump\", (default ump)\n"
+" -n, --sample-count COUNT Stop after COUNT samples\n"
"\n"
msgstr ""
+" --rate Дискреттеу жиілігі (бастапқы %u)\n"
+" --channels Арналар саны (бастапқы %u)\n"
+" --channel-map Арналар картасы\n"
+" арна жаймасы: «Stereo», «5.1»,... немесе\n"
+" үтірмен ажыратылған арна атауларының тізімі: мысалы, "
+"«FL,FR»\n"
+" --list-layouts Қолдау көрсетілетін арна жаймаларын тізімдеу\n"
+" --list-channel-names Қолдау көрсетілетін арналар картасын тізімдеу\n"
+" --format Дискреттеу пішімі (бастапқы %s)\n"
+" --list-formats Қолдау көрсетілетін дискреттеу пішімдерін тізімдеу\n"
+" --container Контейнер пішімі\n"
+" --list-containers Қолдау көрсетілетін контейнерлер мен кеңейтулерді тізімдеу\n"
+" --volume Ағын дыбыс деңгейі 0-1.0 (бастапқы %.3f)\n"
+" -q --quality Қайта дискреттеу сапасы (0 - 15) (бастапқы %d)\n"
+" -a, --raw RAW режимі\n"
+" -M, --force-midi Midi пішімін мәжбүрлеу, «midi» немесе «ump» біреуі, (бастапқы "
+"ump)\n"
+" -n, --sample-count COUNT COUNT үлгісінен кейін тоқтату\n"
+"\n"
-#: src/tools/pw-cat.c:1033
+#: src/tools/pw-cat.c:1232
msgid ""
" -p, --playback Playback mode\n"
" -r, --record Recording mode\n"
" -m, --midi Midi mode\n"
+" -d, --dsd DSD mode\n"
+" -o, --encoded Encoded mode\n"
+" -s, --sysex SysEx mode\n"
+" -c, --midi-clip MIDI clip mode\n"
"\n"
msgstr ""
+" -p, --playback Ойнату режимі\n"
+" -r, --record Жазу режимі\n"
+" -m, --midi Midi режимі\n"
+" -d, --dsd DSD режимі\n"
+" -o, --encoded Шифрленген режим\n"
+" -s, --sysex SysEx режимі\n"
+" -c, --midi-clip MIDI clip режимі\n"
+"\n"
-#: src/tools/pw-cli.c:2932
+#: src/tools/pw-cat.c:1837
+#, c-format
+msgid "Supported containers and extensions:\n"
+msgstr "Қолдау көрсетілетін контейнерлер мен кеңейтулер:\n"
+
+#: src/tools/pw-cli.c:2386
#, c-format
msgid ""
"%s [options] [command]\n"
@@ -114,465 +216,506 @@ msgid ""
" --version Show version\n"
" -d, --daemon Start as daemon (Default false)\n"
" -r, --remote Remote daemon name\n"
+" -m, --monitor Monitor activity\n"
"\n"
msgstr ""
+"%s [опциялар] [команда]\n"
+" -h, --help Осы көмекті көрсету\n"
+" --version Нұсқасын көрсету\n"
+" -d, --daemon Қызмет ретінде іске қосу (Бастапқы false)\n"
+" -r, --remote Қашықтағы қызмет атауы\n"
+" -m, --monitor Белсенділікті бақылау\n"
+"\n"
-#: spa/plugins/alsa/acp/acp.c:290
+#: spa/plugins/alsa/acp/acp.c:361
msgid "Pro Audio"
-msgstr ""
+msgstr "Кәсіби аудио"
-#: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704
-#: spa/plugins/bluez5/bluez5-device.c:1000
+#: spa/plugins/alsa/acp/acp.c:535 spa/plugins/alsa/acp/alsa-mixer.c:4699
+#: spa/plugins/bluez5/bluez5-device.c:2021
msgid "Off"
msgstr "Сөнд."
-#: spa/plugins/alsa/acp/channelmap.h:466
-msgid "(invalid)"
-msgstr "(жарамсыз)"
+#: spa/plugins/alsa/acp/acp.c:618
+#, c-format
+msgid "%s [ALSA UCM error]"
+msgstr "%s [ALSA UCM қатесі]"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2709
+#: spa/plugins/alsa/acp/alsa-mixer.c:2721
msgid "Input"
msgstr "Кіріс"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2710
+#: spa/plugins/alsa/acp/alsa-mixer.c:2722
msgid "Docking Station Input"
msgstr "Док-станция кірісі"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2711
+#: spa/plugins/alsa/acp/alsa-mixer.c:2723
msgid "Docking Station Microphone"
msgstr "Док-станция микрофоны"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2712
+#: spa/plugins/alsa/acp/alsa-mixer.c:2724
msgid "Docking Station Line In"
msgstr "Док-станцияның сызықтық кірісі"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2713
-#: spa/plugins/alsa/acp/alsa-mixer.c:2804
+#: spa/plugins/alsa/acp/alsa-mixer.c:2725 spa/plugins/alsa/acp/alsa-mixer.c:2816
msgid "Line In"
msgstr "Сызықтық кіріс"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2714
-#: spa/plugins/alsa/acp/alsa-mixer.c:2798
-#: spa/plugins/bluez5/bluez5-device.c:1145
+#: spa/plugins/alsa/acp/alsa-mixer.c:2726 spa/plugins/alsa/acp/alsa-mixer.c:2810
+#: spa/plugins/bluez5/bluez5-device.c:2422
msgid "Microphone"
msgstr "Микрофон"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2715
-#: spa/plugins/alsa/acp/alsa-mixer.c:2799
+#: spa/plugins/alsa/acp/alsa-mixer.c:2727 spa/plugins/alsa/acp/alsa-mixer.c:2811
msgid "Front Microphone"
msgstr "Алдыңғы микрофон"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2716
-#: spa/plugins/alsa/acp/alsa-mixer.c:2800
+#: spa/plugins/alsa/acp/alsa-mixer.c:2728 spa/plugins/alsa/acp/alsa-mixer.c:2812
msgid "Rear Microphone"
msgstr "Артқы микрофон"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2717
+#: spa/plugins/alsa/acp/alsa-mixer.c:2729
msgid "External Microphone"
msgstr "Сыртқы микрофон"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2718
-#: spa/plugins/alsa/acp/alsa-mixer.c:2802
+#: spa/plugins/alsa/acp/alsa-mixer.c:2730 spa/plugins/alsa/acp/alsa-mixer.c:2814
msgid "Internal Microphone"
msgstr "Ішкі микрофон"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2719
-#: spa/plugins/alsa/acp/alsa-mixer.c:2805
+#: spa/plugins/alsa/acp/alsa-mixer.c:2731 spa/plugins/alsa/acp/alsa-mixer.c:2817
msgid "Radio"
msgstr "Радио"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2720
-#: spa/plugins/alsa/acp/alsa-mixer.c:2806
+#: spa/plugins/alsa/acp/alsa-mixer.c:2732 spa/plugins/alsa/acp/alsa-mixer.c:2818
msgid "Video"
msgstr "Видео"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2721
+#: spa/plugins/alsa/acp/alsa-mixer.c:2733
msgid "Automatic Gain Control"
msgstr "Күшейтуді автореттеу"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2722
+#: spa/plugins/alsa/acp/alsa-mixer.c:2734
msgid "No Automatic Gain Control"
msgstr "Күшейтуді автореттеу жоқ"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2723
+#: spa/plugins/alsa/acp/alsa-mixer.c:2735
msgid "Boost"
msgstr "Күшейту"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2724
+#: spa/plugins/alsa/acp/alsa-mixer.c:2736
msgid "No Boost"
msgstr "Күшейту жоқ"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2725
+#: spa/plugins/alsa/acp/alsa-mixer.c:2737
msgid "Amplifier"
msgstr "Күшейткіш"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2726
+#: spa/plugins/alsa/acp/alsa-mixer.c:2738
msgid "No Amplifier"
msgstr "Күшейткіш жоқ"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2727
+#: spa/plugins/alsa/acp/alsa-mixer.c:2739
msgid "Bass Boost"
msgstr "Бас күшейту"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2728
+#: spa/plugins/alsa/acp/alsa-mixer.c:2740
msgid "No Bass Boost"
msgstr "Бас күшейту жоқ"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2729
-#: spa/plugins/bluez5/bluez5-device.c:1150
+#: spa/plugins/alsa/acp/alsa-mixer.c:2741 spa/plugins/bluez5/bluez5-device.c:2428
msgid "Speaker"
msgstr "Динамик"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2730
-#: spa/plugins/alsa/acp/alsa-mixer.c:2808
+#. Don't call it "headset", the HF one has the mic
+#: spa/plugins/alsa/acp/alsa-mixer.c:2742 spa/plugins/alsa/acp/alsa-mixer.c:2820
+#: spa/plugins/bluez5/bluez5-device.c:2434 spa/plugins/bluez5/bluez5-device.c:2501
msgid "Headphones"
msgstr "Құлаққаптар"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2797
+#: spa/plugins/alsa/acp/alsa-mixer.c:2809
msgid "Analog Input"
msgstr "Аналогтық кіріс"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2801
+#: spa/plugins/alsa/acp/alsa-mixer.c:2813
msgid "Dock Microphone"
msgstr "Док-станция микрофоны"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2803
+#: spa/plugins/alsa/acp/alsa-mixer.c:2815
msgid "Headset Microphone"
msgstr "Гарнитура микрофоны"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2807
+#: spa/plugins/alsa/acp/alsa-mixer.c:2819
msgid "Analog Output"
msgstr "Аналогтық шығыс"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2809
-#, fuzzy
+#: spa/plugins/alsa/acp/alsa-mixer.c:2821
msgid "Headphones 2"
-msgstr "Құлаққаптар"
+msgstr "Құлаққап 2"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2810
+#: spa/plugins/alsa/acp/alsa-mixer.c:2822
msgid "Headphones Mono Output"
msgstr "Құлаққаптардың моно шығысы"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2811
+#: spa/plugins/alsa/acp/alsa-mixer.c:2823
msgid "Line Out"
msgstr "Сызықтық шығыс"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2812
+#: spa/plugins/alsa/acp/alsa-mixer.c:2824
msgid "Analog Mono Output"
msgstr "Аналогтық моно шығысы"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2813
+#: spa/plugins/alsa/acp/alsa-mixer.c:2825
msgid "Speakers"
msgstr "Динамиктер"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2814
+#: spa/plugins/alsa/acp/alsa-mixer.c:2826
msgid "HDMI / DisplayPort"
msgstr "HDMI / DisplayPort"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2815
+#: spa/plugins/alsa/acp/alsa-mixer.c:2827
msgid "Digital Output (S/PDIF)"
msgstr "Цифрлық шығыс (S/PDIF)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2816
+#: spa/plugins/alsa/acp/alsa-mixer.c:2828
msgid "Digital Input (S/PDIF)"
msgstr "Цифрлық кіріс (S/PDIF)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2817
+#: spa/plugins/alsa/acp/alsa-mixer.c:2829
msgid "Multichannel Input"
msgstr "Көпарналы кіріс"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2818
+#: spa/plugins/alsa/acp/alsa-mixer.c:2830
msgid "Multichannel Output"
msgstr "Көпарналы шығыс"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2819
+#: spa/plugins/alsa/acp/alsa-mixer.c:2831
msgid "Game Output"
msgstr "Ойын шығысы"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2820
-#: spa/plugins/alsa/acp/alsa-mixer.c:2821
+#: spa/plugins/alsa/acp/alsa-mixer.c:2832 spa/plugins/alsa/acp/alsa-mixer.c:2833
msgid "Chat Output"
msgstr "Чат шығысы"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2822
-#, fuzzy
+#: spa/plugins/alsa/acp/alsa-mixer.c:2834
msgid "Chat Input"
-msgstr "Чат шығысы"
+msgstr "Чат кірісі"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2823
-#, fuzzy
+#: spa/plugins/alsa/acp/alsa-mixer.c:2835
msgid "Virtual Surround 7.1"
-msgstr "Виртуалды көлемді аудиоқабылдағыш"
+msgstr "Виртуалды көлемді дыбыс 7.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4527
+#: spa/plugins/alsa/acp/alsa-mixer.c:4522
msgid "Analog Mono"
msgstr "Аналогтық моно"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4528
-#, fuzzy
+#: spa/plugins/alsa/acp/alsa-mixer.c:4523
msgid "Analog Mono (Left)"
-msgstr "Аналогтық моно"
+msgstr "Аналогты моно (Сол жақ)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4529
-#, fuzzy
+#: spa/plugins/alsa/acp/alsa-mixer.c:4524
msgid "Analog Mono (Right)"
-msgstr "Аналогтық моно"
+msgstr "Аналогты моно (Оң жақ)"
#. Note: Not translated to "Analog Stereo Input", because the source
#. * name gets "Input" appended to it automatically, so adding "Input"
#. * here would lead to the source name to become "Analog Stereo Input
#. * Input". The same logic applies to analog-stereo-output,
#. * multichannel-input and multichannel-output.
-#: spa/plugins/alsa/acp/alsa-mixer.c:4530
-#: spa/plugins/alsa/acp/alsa-mixer.c:4538
-#: spa/plugins/alsa/acp/alsa-mixer.c:4539
+#: spa/plugins/alsa/acp/alsa-mixer.c:4525 spa/plugins/alsa/acp/alsa-mixer.c:4533
+#: spa/plugins/alsa/acp/alsa-mixer.c:4534
msgid "Analog Stereo"
msgstr "Аналогтық стерео"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4531
+#: spa/plugins/alsa/acp/alsa-mixer.c:4526
msgid "Mono"
msgstr "Моно"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4532
+#: spa/plugins/alsa/acp/alsa-mixer.c:4527
msgid "Stereo"
msgstr "Стерео"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4540
-#: spa/plugins/alsa/acp/alsa-mixer.c:4698
-#: spa/plugins/bluez5/bluez5-device.c:1135
+#: spa/plugins/alsa/acp/alsa-mixer.c:4535 spa/plugins/alsa/acp/alsa-mixer.c:4693
+#: spa/plugins/bluez5/bluez5-device.c:2410
msgid "Headset"
msgstr "Гарнитура"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4541
-#: spa/plugins/alsa/acp/alsa-mixer.c:4699
-#, fuzzy
+#: spa/plugins/alsa/acp/alsa-mixer.c:4536 spa/plugins/alsa/acp/alsa-mixer.c:4694
msgid "Speakerphone"
msgstr "Динамик"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4542
-#: spa/plugins/alsa/acp/alsa-mixer.c:4543
+#: spa/plugins/alsa/acp/alsa-mixer.c:4537 spa/plugins/alsa/acp/alsa-mixer.c:4538
msgid "Multichannel"
msgstr "Көпарналы"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4544
+#: spa/plugins/alsa/acp/alsa-mixer.c:4539
msgid "Analog Surround 2.1"
msgstr "Аналогтық көлемді 2.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4545
+#: spa/plugins/alsa/acp/alsa-mixer.c:4540
msgid "Analog Surround 3.0"
msgstr "Аналогтық көлемді 3.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4546
+#: spa/plugins/alsa/acp/alsa-mixer.c:4541
msgid "Analog Surround 3.1"
msgstr "Аналогтық көлемді 3.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4547
+#: spa/plugins/alsa/acp/alsa-mixer.c:4542
msgid "Analog Surround 4.0"
msgstr "Аналогтық көлемді 4.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4548
+#: spa/plugins/alsa/acp/alsa-mixer.c:4543
msgid "Analog Surround 4.1"
msgstr "Аналогтық көлемді 4.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4549
+#: spa/plugins/alsa/acp/alsa-mixer.c:4544
msgid "Analog Surround 5.0"
msgstr "Аналогтық көлемді 5.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4550
+#: spa/plugins/alsa/acp/alsa-mixer.c:4545
msgid "Analog Surround 5.1"
msgstr "Аналогтық көлемді 5.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4551
+#: spa/plugins/alsa/acp/alsa-mixer.c:4546
msgid "Analog Surround 6.0"
msgstr "Аналогтық көлемді 6.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4552
+#: spa/plugins/alsa/acp/alsa-mixer.c:4547
msgid "Analog Surround 6.1"
msgstr "Аналогтық көлемді 6.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4553
+#: spa/plugins/alsa/acp/alsa-mixer.c:4548
msgid "Analog Surround 7.0"
msgstr "Аналогтық көлемді 7.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4554
+#: spa/plugins/alsa/acp/alsa-mixer.c:4549
msgid "Analog Surround 7.1"
msgstr "Аналогтық көлемді 7.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4555
+#: spa/plugins/alsa/acp/alsa-mixer.c:4550
msgid "Digital Stereo (IEC958)"
msgstr "Цифрлық стерео (IEC958)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4556
+#: spa/plugins/alsa/acp/alsa-mixer.c:4551
msgid "Digital Surround 4.0 (IEC958/AC3)"
msgstr "Цифрлық көлемді 4.0 (IEC958/AC3)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4557
+#: spa/plugins/alsa/acp/alsa-mixer.c:4552
msgid "Digital Surround 5.1 (IEC958/AC3)"
msgstr "Цифрлық көлемді 5.1 (IEC958/AC3)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4558
+#: spa/plugins/alsa/acp/alsa-mixer.c:4553
msgid "Digital Surround 5.1 (IEC958/DTS)"
msgstr "Цифрлық көлемді 5.1 (IEC958/DTS)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4559
+#: spa/plugins/alsa/acp/alsa-mixer.c:4554
msgid "Digital Stereo (HDMI)"
msgstr "Цифрлық стерео (HDMI)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4560
+#: spa/plugins/alsa/acp/alsa-mixer.c:4555
msgid "Digital Surround 5.1 (HDMI)"
msgstr "Цифрлық көлемді 5.1 (HDMI)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4561
+#: spa/plugins/alsa/acp/alsa-mixer.c:4556
msgid "Chat"
-msgstr ""
+msgstr "Чат"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4562
+#: spa/plugins/alsa/acp/alsa-mixer.c:4557
msgid "Game"
-msgstr ""
+msgstr "Ойын"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4696
+#: spa/plugins/alsa/acp/alsa-mixer.c:4691
msgid "Analog Mono Duplex"
msgstr "Аналогтық моно дуплекс"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4697
+#: spa/plugins/alsa/acp/alsa-mixer.c:4692
msgid "Analog Stereo Duplex"
msgstr "Аналогтық стерео дуплекс"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4700
+#: spa/plugins/alsa/acp/alsa-mixer.c:4695
msgid "Digital Stereo Duplex (IEC958)"
msgstr "Цифрлық стерео дуплекс (IEC958)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4701
+#: spa/plugins/alsa/acp/alsa-mixer.c:4696
msgid "Multichannel Duplex"
msgstr "Көпарналы дуплекс"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4702
+#: spa/plugins/alsa/acp/alsa-mixer.c:4697
msgid "Stereo Duplex"
msgstr "Стерео дуплекс"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4703
+#: spa/plugins/alsa/acp/alsa-mixer.c:4698
msgid "Mono Chat + 7.1 Surround"
-msgstr ""
+msgstr "Моно чат + 7.1 көлемді дыбыс"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4806
+#: spa/plugins/alsa/acp/alsa-mixer.c:4799
#, c-format
msgid "%s Output"
msgstr "%s шығысы"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4813
+#: spa/plugins/alsa/acp/alsa-mixer.c:4807
#, c-format
msgid "%s Input"
msgstr "%s кірісі"
-#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269
+#: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327
#, c-format
msgid ""
-"snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu "
-"ms).\n"
-"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
-"to the ALSA developers."
+"snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu ms).\n"
+"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."
msgid_plural ""
-"snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu "
-"ms).\n"
-"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
-"to the ALSA developers."
+"snd_pcm_avail() returned a value that is exceptionally large: %lu bytes (%lu ms).\n"
+"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."
msgstr[0] ""
+"snd_pcm_avail() өте үлкен мән қайтарды: %lu байт (%lu мс).\n"
+"Бұл ALSA драйверіндегі («%s») қате болуы әбден мүмкін. Бұл мәселе туралы ALSA әзірлеушілеріне хабарлаңыз."
msgstr[1] ""
+"snd_pcm_avail() өте үлкен мән қайтарды: %lu байт (%lu мс).\n"
+"Бұл ALSA драйверіндегі («%s») қате болуы әбден мүмкін. Бұл мәселе туралы ALSA әзірлеушілеріне хабарлаңыз."
-#: spa/plugins/alsa/acp/alsa-util.c:1241
+#: spa/plugins/alsa/acp/alsa-util.c:1299
#, c-format
msgid ""
-"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s"
-"%lu ms).\n"
-"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
-"to the ALSA developers."
+"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s%lu ms).\n"
+"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."
msgid_plural ""
-"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s"
-"%lu ms).\n"
-"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
-"to the ALSA developers."
+"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s%lu ms).\n"
+"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."
msgstr[0] ""
+"snd_pcm_delay() өте үлкен мән қайтарды: %li байт (%s%lu мс).\n"
+"Бұл ALSA драйверіндегі («%s») қате болуы әбден мүмкін. Бұл мәселе туралы ALSA әзірлеушілеріне хабарлаңыз."
msgstr[1] ""
+"snd_pcm_delay() өте үлкен мән қайтарды: %li байт (%s%lu мс).\n"
+"Бұл ALSA драйверіндегі («%s») қате болуы әбден мүмкін. Бұл мәселе туралы ALSA әзірлеушілеріне хабарлаңыз."
-#: spa/plugins/alsa/acp/alsa-util.c:1288
+#: spa/plugins/alsa/acp/alsa-util.c:1346
#, c-format
msgid ""
-"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail "
-"%lu.\n"
-"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
-"to the ALSA developers."
+"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail %lu.\n"
+"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."
msgstr ""
+"snd_pcm_avail_delay() оғаш мәндер қайтарды: кідіріс %lu мәні қолжетімді %lu мәнінен аз.\n"
+"Бұл ALSA драйверіндегі («%s») қате болуы әбден мүмкін. Бұл мәселе туралы ALSA әзірлеушілеріне хабарлаңыз."
-#: spa/plugins/alsa/acp/alsa-util.c:1331
+#: spa/plugins/alsa/acp/alsa-util.c:1389
#, c-format
msgid ""
-"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte "
-"(%lu ms).\n"
-"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
-"to the ALSA developers."
+"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte (%lu ms).\n"
+"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."
msgid_plural ""
-"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes "
-"(%lu ms).\n"
-"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
-"to the ALSA developers."
+"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes (%lu ms).\n"
+"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers."
msgstr[0] ""
+"snd_pcm_mmap_begin() өте үлкен мән қайтарды: %lu байт (%lu мс).\n"
+"Бұл ALSA драйверіндегі («%s») қате болуы әбден мүмкін. Бұл мәселе туралы ALSA әзірлеушілеріне хабарлаңыз."
msgstr[1] ""
+"snd_pcm_mmap_begin() өте үлкен мән қайтарды: %lu байт (%lu мс).\n"
+"Бұл ALSA драйверіндегі («%s») қате болуы әбден мүмкін. Бұл мәселе туралы ALSA әзірлеушілеріне хабарлаңыз."
-#: spa/plugins/bluez5/bluez5-device.c:1010
+#: spa/plugins/alsa/acp/channelmap.h:460
+msgid "(invalid)"
+msgstr "(жарамсыз)"
+
+#: spa/plugins/alsa/acp/compat.c:194
+msgid "Built-in Audio"
+msgstr "Құрамындағы аудио"
+
+#: spa/plugins/alsa/acp/compat.c:199
+msgid "Modem"
+msgstr "Модем"
+
+#: spa/plugins/bluez5/bluez5-device.c:2032
msgid "Audio Gateway (A2DP Source & HSP/HFP AG)"
-msgstr ""
+msgstr "Аудио шлюзі (A2DP бастапқы көзі және HSP/HFP AG)"
-#: spa/plugins/bluez5/bluez5-device.c:1033
+#: spa/plugins/bluez5/bluez5-device.c:2061
+msgid "Audio Streaming for Hearing Aids (ASHA Sink)"
+msgstr "Есту аппараттарына арналған аудио ағыны (ASHA қабылдағышы)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2104
#, c-format
msgid "High Fidelity Playback (A2DP Sink, codec %s)"
-msgstr ""
+msgstr "Жоғары сапалы ойнату (A2DP қабылдағышы, кодек %s)"
-#: spa/plugins/bluez5/bluez5-device.c:1035
+#: spa/plugins/bluez5/bluez5-device.c:2107
#, c-format
msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)"
-msgstr ""
+msgstr "Жоғары сапалы дуплекс (A2DP бастапқы көзі/қабылдағышы, кодек %s)"
-#: spa/plugins/bluez5/bluez5-device.c:1041
+#: spa/plugins/bluez5/bluez5-device.c:2115
msgid "High Fidelity Playback (A2DP Sink)"
-msgstr ""
+msgstr "Жоғары сапалы ойнату (A2DP қабылдағышы)"
-#: spa/plugins/bluez5/bluez5-device.c:1043
+#: spa/plugins/bluez5/bluez5-device.c:2117
msgid "High Fidelity Duplex (A2DP Source/Sink)"
-msgstr ""
+msgstr "Жоғары сапалы дуплекс (A2DP бастапқы көзі/қабылдағышы)"
-#: spa/plugins/bluez5/bluez5-device.c:1070
+#: spa/plugins/bluez5/bluez5-device.c:2194
+#, c-format
+msgid "High Fidelity Playback (BAP Sink, codec %s)"
+msgstr "Жоғары сапалы ойнату (BAP қабылдағышы, кодек %s)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2199
+#, c-format
+msgid "High Fidelity Input (BAP Source, codec %s)"
+msgstr "Жоғары сапалы кіріс (BAP бастапқы көзі, кодек %s)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2203
+#, c-format
+msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)"
+msgstr "Жоғары сапалы дуплекс (BAP бастапқы көзі/қабылдағышы, кодек %s)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2212
+msgid "High Fidelity Playback (BAP Sink)"
+msgstr "Жоғары сапалы ойнату (BAP қабылдағышы)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2216
+msgid "High Fidelity Input (BAP Source)"
+msgstr "Жоғары сапалы кіріс (BAP бастапқы көзі)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2219
+msgid "High Fidelity Duplex (BAP Source/Sink)"
+msgstr "Жоғары сапалы дуплекс (BAP бастапқы көзі/қабылдағышы)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2259
#, c-format
msgid "Headset Head Unit (HSP/HFP, codec %s)"
-msgstr ""
+msgstr "Гарнитура (HSP/HFP, кодек %s)"
-#: spa/plugins/bluez5/bluez5-device.c:1074
-msgid "Headset Head Unit (HSP/HFP)"
-msgstr ""
-
-#: spa/plugins/bluez5/bluez5-device.c:1140
+#: spa/plugins/bluez5/bluez5-device.c:2411 spa/plugins/bluez5/bluez5-device.c:2416
+#: spa/plugins/bluez5/bluez5-device.c:2423 spa/plugins/bluez5/bluez5-device.c:2429
+#: spa/plugins/bluez5/bluez5-device.c:2435 spa/plugins/bluez5/bluez5-device.c:2441
+#: spa/plugins/bluez5/bluez5-device.c:2447 spa/plugins/bluez5/bluez5-device.c:2453
+#: spa/plugins/bluez5/bluez5-device.c:2459
msgid "Handsfree"
msgstr "Хендс-фри"
-#: spa/plugins/bluez5/bluez5-device.c:1155
-msgid "Headphone"
-msgstr "Құлаққап"
+#: spa/plugins/bluez5/bluez5-device.c:2417
+msgid "Handsfree (HFP)"
+msgstr "Гарнитура (HFP)"
-#: spa/plugins/bluez5/bluez5-device.c:1160
+#: spa/plugins/bluez5/bluez5-device.c:2440
msgid "Portable"
msgstr "Портативті динамик"
-#: spa/plugins/bluez5/bluez5-device.c:1165
+#: spa/plugins/bluez5/bluez5-device.c:2446
msgid "Car"
msgstr "Автомобильдік динамик"
-#: spa/plugins/bluez5/bluez5-device.c:1170
+#: spa/plugins/bluez5/bluez5-device.c:2452
msgid "HiFi"
msgstr "HiFi"
-#: spa/plugins/bluez5/bluez5-device.c:1175
+#: spa/plugins/bluez5/bluez5-device.c:2458
msgid "Phone"
msgstr "Телефон"
-#: spa/plugins/bluez5/bluez5-device.c:1181
+#: spa/plugins/bluez5/bluez5-device.c:2465
msgid "Bluetooth"
msgstr "Bluetooth"
+
+#: spa/plugins/bluez5/bluez5-device.c:2466
+msgid "Bluetooth Handsfree"
+msgstr "Bluetooth гарнитурасы"
+
+#~ msgid "Headphone"
+#~ msgstr "Құлаққап"
diff --git a/po/sv.po b/po/sv.po
index 0bc2796a4..1474f5084 100644
--- a/po/sv.po
+++ b/po/sv.po
@@ -1,9 +1,9 @@
# Swedish translation for pipewire.
-# Copyright © 2008-2025 Free Software Foundation, Inc.
+# Copyright © 2008-2026 Free Software Foundation, Inc.
# This file is distributed under the same license as the pipewire package.
# Daniel Nylander , 2008, 2012.
# Josef Andersson , 2014, 2017.
-# Anders Jonsson , 2021, 2022, 2023, 2024, 2025.
+# Anders Jonsson , 2021, 2022, 2023, 2024, 2025, 2026.
#
# Termer:
# input/output: ingång/utgång (det handlar om ljud)
@@ -19,8 +19,8 @@ msgstr ""
"Project-Id-Version: pipewire\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/"
"issues\n"
-"POT-Creation-Date: 2025-04-16 15:31+0000\n"
-"PO-Revision-Date: 2025-04-22 10:43+0200\n"
+"POT-Creation-Date: 2026-02-09 12:55+0000\n"
+"PO-Revision-Date: 2026-02-22 21:48+0100\n"
"Last-Translator: Anders Jonsson \n"
"Language-Team: Swedish \n"
"Language: sv\n"
@@ -28,7 +28,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
-"X-Generator: Poedit 3.5\n"
+"X-Generator: Poedit 3.8\n"
#: src/daemon/pipewire.c:29
#, c-format
@@ -47,11 +47,11 @@ msgstr ""
" -c, --config Läs in konfig (standard %s)\n"
" -P --properties Ställ in kontextegenskaper\n"
-#: src/daemon/pipewire.desktop.in:4
+#: src/daemon/pipewire.desktop.in:3
msgid "PipeWire Media System"
msgstr "PipeWire mediasystem"
-#: src/daemon/pipewire.desktop.in:5
+#: src/daemon/pipewire.desktop.in:4
msgid "Start the PipeWire Media System"
msgstr "Starta mediasystemet PipeWire"
@@ -65,26 +65,51 @@ msgstr "Tunnel till %s%s%s"
msgid "Dummy Output"
msgstr "Attrapputgång"
-#: src/modules/module-pulse-tunnel.c:760
+#: src/modules/module-pulse-tunnel.c:761
#, c-format
msgid "Tunnel for %s@%s"
msgstr "Tunnel för %s@%s"
-#: src/modules/module-zeroconf-discover.c:320
+#: src/modules/module-zeroconf-discover.c:326
msgid "Unknown device"
msgstr "Okänd enhet"
-#: src/modules/module-zeroconf-discover.c:332
+#: src/modules/module-zeroconf-discover.c:338
#, c-format
msgid "%s on %s@%s"
msgstr "%s på %s@%s"
-#: src/modules/module-zeroconf-discover.c:336
+#: src/modules/module-zeroconf-discover.c:342
#, c-format
msgid "%s on %s"
msgstr "%s på %s"
-#: src/tools/pw-cat.c:973
+#: src/tools/pw-cat.c:264
+#, c-format
+msgid "Supported formats:\n"
+msgstr "Format som stöds:\n"
+
+#: src/tools/pw-cat.c:749
+#, c-format
+msgid "Supported channel layouts:\n"
+msgstr "Kanallayouter som stöds:\n"
+
+#: src/tools/pw-cat.c:759
+#, c-format
+msgid "Supported channel layout aliases:\n"
+msgstr "Kanallayoutalias som stöds:\n"
+
+#: src/tools/pw-cat.c:761
+#, c-format
+msgid " %s -> %s\n"
+msgstr " %s -> %s\n"
+
+#: src/tools/pw-cat.c:766
+#, c-format
+msgid "Supported channel names:\n"
+msgstr "Kanalnamn som stöds:\n"
+
+#: src/tools/pw-cat.c:1177
#, c-format
msgid ""
"%s [options] [|-]\n"
@@ -99,7 +124,7 @@ msgstr ""
" -v, --verbose Aktivera utförliga operationer\n"
"\n"
-#: src/tools/pw-cat.c:980
+#: src/tools/pw-cat.c:1184
#, c-format
msgid ""
" -R, --remote Remote daemon name\n"
@@ -131,50 +156,64 @@ msgstr ""
" -P --properties Sätt nodegenskaper\n"
"\n"
-#: src/tools/pw-cat.c:998
+#: src/tools/pw-cat.c:1202
#, c-format
msgid ""
-" --rate Sample rate (req. for rec) (default "
-"%u)\n"
-" --channels Number of channels (req. for rec) "
-"(default %u)\n"
+" --rate Sample rate (default %u)\n"
+" --channels Number of channels (default %u)\n"
" --channel-map Channel map\n"
-" one of: \"stereo\", "
-"\"surround-51\",... or\n"
+" a channel layout: \"Stereo\", "
+"\"5.1\",... or\n"
" comma separated list of channel "
"names: eg. \"FL,FR\"\n"
-" --format Sample format %s (req. for rec) "
-"(default %s)\n"
+" --list-layouts List supported channel layouts\n"
+" --list-channel-names List supported channel maps\n"
+" --format Sample format (default %s)\n"
+" --list-formats List supported sample formats\n"
+" --container Container format\n"
+" --list-containers List supported containers and "
+"extensions\n"
" --volume Stream volume 0-1.0 (default %.3f)\n"
" -q --quality Resampler quality (0 - 15) (default "
"%d)\n"
" -a, --raw RAW mode\n"
+" -M, --force-midi Force midi format, one of \"midi\" "
+"or \"ump\", (default ump)\n"
+" -n, --sample-count COUNT Stop after COUNT samples\n"
"\n"
msgstr ""
-" --rate Samplingsfrekvens (krävs för insp.) "
-"(standard %u)\n"
-" --channels Antal kanaler (krävs för insp.) "
-"(standard %u)\n"
+" --rate Samplingsfrekvens (standard %u)\n"
+" --channels Antal kanaler (standard %u)\n"
" --channel-map Kanalmappning\n"
-" en av: \"stereo\", "
-"\"surround-51\",... eller\n"
+" en kanallayout: \"Stereo\", "
+"\"5.1\",... eller\n"
" kommaseparerad lista av "
"kanalnamn: t.ex. \"FL,FR\"\n"
-" --format Samplingsformat %s (krävs för insp.) "
-"(standard %s)\n"
+" --list-layouts Lista kanallayouter som stöds\n"
+" --list-channel-names Lista kanalmappningar som stöds\n"
+" --format Samplingsformat (standard %s)\n"
+" --list-formats Lista samplingsformat som stöds\n"
+" --container Behållarformat\n"
+" --list-containers Lista behållare och tillägg som "
+"stöds\n"
" --volume Strömvolym 0-1.0 (standard %.3f)\n"
" -q --quality Omsamplarkvalitet (0 - 15) (standard "
"%d)\n"
" -a, --raw RAW-läge\n"
+" -M, --force-midi Tvinga midi-format, en av \"midi\" "
+"eller \"ump\", (standard ump)\n"
+" -n, --sample-count ANTAL Stoppa efter ANTAL samplar\n"
"\n"
-#: src/tools/pw-cat.c:1016
+#: src/tools/pw-cat.c:1227
msgid ""
" -p, --playback Playback mode\n"
" -r, --record Recording mode\n"
" -m, --midi Midi mode\n"
" -d, --dsd DSD mode\n"
" -o, --encoded Encoded mode\n"
+" -s, --sysex SysEx mode\n"
+" -c, --midi-clip MIDI clip mode\n"
"\n"
msgstr ""
" -p, --playback Uppspelningsläge\n"
@@ -182,9 +221,16 @@ msgstr ""
" -m, --midi Midiläge\n"
" -d, --dsd DSD-läge\n"
" -o, --encoded Kodat läge\n"
+" -s, --sysex SysEx-läge\n"
+" -c, --midi-clip MIDI-klippläge\n"
"\n"
-#: src/tools/pw-cli.c:2306
+#: src/tools/pw-cat.c:1827
+#, c-format
+msgid "Supported containers and extensions:\n"
+msgstr "Behållare och tillägg som stöds:\n"
+
+#: src/tools/pw-cli.c:2386
#, c-format
msgid ""
"%s [options] [command]\n"
@@ -203,200 +249,203 @@ msgstr ""
" -m, --monitor Övervaka aktivitet\n"
"\n"
-#: spa/plugins/alsa/acp/acp.c:350
+#: spa/plugins/alsa/acp/acp.c:361
msgid "Pro Audio"
msgstr "Professionellt ljud"
-#: spa/plugins/alsa/acp/acp.c:511 spa/plugins/alsa/acp/alsa-mixer.c:4635
-#: spa/plugins/bluez5/bluez5-device.c:1802
+#: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699
+#: spa/plugins/bluez5/bluez5-device.c:2021
msgid "Off"
msgstr "Av"
-#: spa/plugins/alsa/acp/acp.c:593
+#: spa/plugins/alsa/acp/acp.c:620
#, c-format
msgid "%s [ALSA UCM error]"
msgstr "%s [ALSA UCM-fel]"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2652
+#: spa/plugins/alsa/acp/alsa-mixer.c:2721
msgid "Input"
msgstr "Ingång"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2653
+#: spa/plugins/alsa/acp/alsa-mixer.c:2722
msgid "Docking Station Input"
msgstr "Ingång för dockningsstation"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2654
+#: spa/plugins/alsa/acp/alsa-mixer.c:2723
msgid "Docking Station Microphone"
msgstr "Mikrofon för dockningsstation"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2655
+#: spa/plugins/alsa/acp/alsa-mixer.c:2724
msgid "Docking Station Line In"
msgstr "Linje in för dockningsstation"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2656
-#: spa/plugins/alsa/acp/alsa-mixer.c:2747
+#: spa/plugins/alsa/acp/alsa-mixer.c:2725
+#: spa/plugins/alsa/acp/alsa-mixer.c:2816
msgid "Line In"
msgstr "Linje in"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2657
-#: spa/plugins/alsa/acp/alsa-mixer.c:2741
-#: spa/plugins/bluez5/bluez5-device.c:2146
+#: spa/plugins/alsa/acp/alsa-mixer.c:2726
+#: spa/plugins/alsa/acp/alsa-mixer.c:2810
+#: spa/plugins/bluez5/bluez5-device.c:2422
msgid "Microphone"
msgstr "Mikrofon"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2658
-#: spa/plugins/alsa/acp/alsa-mixer.c:2742
+#: spa/plugins/alsa/acp/alsa-mixer.c:2727
+#: spa/plugins/alsa/acp/alsa-mixer.c:2811
msgid "Front Microphone"
msgstr "Frontmikrofon"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2659
-#: spa/plugins/alsa/acp/alsa-mixer.c:2743
+#: spa/plugins/alsa/acp/alsa-mixer.c:2728
+#: spa/plugins/alsa/acp/alsa-mixer.c:2812
msgid "Rear Microphone"
msgstr "Bakre mikrofon"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2660
+#: spa/plugins/alsa/acp/alsa-mixer.c:2729
msgid "External Microphone"
msgstr "Extern mikrofon"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2661
-#: spa/plugins/alsa/acp/alsa-mixer.c:2745
+#: spa/plugins/alsa/acp/alsa-mixer.c:2730
+#: spa/plugins/alsa/acp/alsa-mixer.c:2814
msgid "Internal Microphone"
msgstr "Intern mikrofon"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2662
-#: spa/plugins/alsa/acp/alsa-mixer.c:2748
+#: spa/plugins/alsa/acp/alsa-mixer.c:2731
+#: spa/plugins/alsa/acp/alsa-mixer.c:2817
msgid "Radio"
msgstr "Radio"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2663
-#: spa/plugins/alsa/acp/alsa-mixer.c:2749
+#: spa/plugins/alsa/acp/alsa-mixer.c:2732
+#: spa/plugins/alsa/acp/alsa-mixer.c:2818
msgid "Video"
msgstr "Video"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2664
+#: spa/plugins/alsa/acp/alsa-mixer.c:2733
msgid "Automatic Gain Control"
msgstr "Automatisk förstärkningskontroll"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2665
+#: spa/plugins/alsa/acp/alsa-mixer.c:2734
msgid "No Automatic Gain Control"
msgstr "Ingen automatisk förstärkningskontroll"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2666
+#: spa/plugins/alsa/acp/alsa-mixer.c:2735
msgid "Boost"
msgstr "Ökning"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2667
+#: spa/plugins/alsa/acp/alsa-mixer.c:2736
msgid "No Boost"
msgstr "Ingen ökning"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2668
+#: spa/plugins/alsa/acp/alsa-mixer.c:2737
msgid "Amplifier"
msgstr "Förstärkare"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2669
+#: spa/plugins/alsa/acp/alsa-mixer.c:2738
msgid "No Amplifier"
msgstr "Ingen förstärkare"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2670
+#: spa/plugins/alsa/acp/alsa-mixer.c:2739
msgid "Bass Boost"
msgstr "Basökning"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2671
+#: spa/plugins/alsa/acp/alsa-mixer.c:2740
msgid "No Bass Boost"
msgstr "Ingen basökning"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2672
-#: spa/plugins/bluez5/bluez5-device.c:2152
+#: spa/plugins/alsa/acp/alsa-mixer.c:2741
+#: spa/plugins/bluez5/bluez5-device.c:2428
msgid "Speaker"
msgstr "Högtalare"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2673
-#: spa/plugins/alsa/acp/alsa-mixer.c:2751
+#. Don't call it "headset", the HF one has the mic
+#: spa/plugins/alsa/acp/alsa-mixer.c:2742
+#: spa/plugins/alsa/acp/alsa-mixer.c:2820
+#: spa/plugins/bluez5/bluez5-device.c:2434
+#: spa/plugins/bluez5/bluez5-device.c:2501
msgid "Headphones"
msgstr "Hörlurar"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2740
+#: spa/plugins/alsa/acp/alsa-mixer.c:2809
msgid "Analog Input"
msgstr "Analog ingång"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2744
+#: spa/plugins/alsa/acp/alsa-mixer.c:2813
msgid "Dock Microphone"
msgstr "Dockmikrofon"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2746
+#: spa/plugins/alsa/acp/alsa-mixer.c:2815
msgid "Headset Microphone"
msgstr "Headset-mikrofon"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2750
+#: spa/plugins/alsa/acp/alsa-mixer.c:2819
msgid "Analog Output"
msgstr "Analog utgång"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2752
+#: spa/plugins/alsa/acp/alsa-mixer.c:2821
msgid "Headphones 2"
msgstr "Hörlurar 2"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2753
+#: spa/plugins/alsa/acp/alsa-mixer.c:2822
msgid "Headphones Mono Output"
msgstr "Monoutgång för hörlurar"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2754
+#: spa/plugins/alsa/acp/alsa-mixer.c:2823
msgid "Line Out"
msgstr "Linje ut"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2755
+#: spa/plugins/alsa/acp/alsa-mixer.c:2824
msgid "Analog Mono Output"
msgstr "Analog monoutgång"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2756
+#: spa/plugins/alsa/acp/alsa-mixer.c:2825
msgid "Speakers"
msgstr "Högtalare"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2757
+#: spa/plugins/alsa/acp/alsa-mixer.c:2826
msgid "HDMI / DisplayPort"
msgstr "HDMI / DisplayPort"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2758
+#: spa/plugins/alsa/acp/alsa-mixer.c:2827
msgid "Digital Output (S/PDIF)"
msgstr "Digital utgång (S/PDIF)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2759
+#: spa/plugins/alsa/acp/alsa-mixer.c:2828
msgid "Digital Input (S/PDIF)"
msgstr "Digital ingång (S/PDIF)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2760
+#: spa/plugins/alsa/acp/alsa-mixer.c:2829
msgid "Multichannel Input"
msgstr "Multikanalingång"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2761
+#: spa/plugins/alsa/acp/alsa-mixer.c:2830
msgid "Multichannel Output"
msgstr "Multikanalutgång"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2762
+#: spa/plugins/alsa/acp/alsa-mixer.c:2831
msgid "Game Output"
msgstr "Spelutgång"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2763
-#: spa/plugins/alsa/acp/alsa-mixer.c:2764
+#: spa/plugins/alsa/acp/alsa-mixer.c:2832
+#: spa/plugins/alsa/acp/alsa-mixer.c:2833
msgid "Chat Output"
msgstr "Chatt-utgång"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2765
+#: spa/plugins/alsa/acp/alsa-mixer.c:2834
msgid "Chat Input"
msgstr "Chatt-ingång"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2766
+#: spa/plugins/alsa/acp/alsa-mixer.c:2835
msgid "Virtual Surround 7.1"
msgstr "Virtual surround 7.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4458
+#: spa/plugins/alsa/acp/alsa-mixer.c:4522
msgid "Analog Mono"
msgstr "Analog mono"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4459
+#: spa/plugins/alsa/acp/alsa-mixer.c:4523
msgid "Analog Mono (Left)"
msgstr "Analog mono (vänster)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4460
+#: spa/plugins/alsa/acp/alsa-mixer.c:4524
msgid "Analog Mono (Right)"
msgstr "Analog mono (höger)"
@@ -405,142 +454,142 @@ msgstr "Analog mono (höger)"
#. * here would lead to the source name to become "Analog Stereo Input
#. * Input". The same logic applies to analog-stereo-output,
#. * multichannel-input and multichannel-output.
-#: spa/plugins/alsa/acp/alsa-mixer.c:4461
-#: spa/plugins/alsa/acp/alsa-mixer.c:4469
-#: spa/plugins/alsa/acp/alsa-mixer.c:4470
+#: spa/plugins/alsa/acp/alsa-mixer.c:4525
+#: spa/plugins/alsa/acp/alsa-mixer.c:4533
+#: spa/plugins/alsa/acp/alsa-mixer.c:4534
msgid "Analog Stereo"
msgstr "Analog stereo"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4462
+#: spa/plugins/alsa/acp/alsa-mixer.c:4526
msgid "Mono"
msgstr "Mono"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4463
+#: spa/plugins/alsa/acp/alsa-mixer.c:4527
msgid "Stereo"
msgstr "Stereo"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4471
-#: spa/plugins/alsa/acp/alsa-mixer.c:4629
-#: spa/plugins/bluez5/bluez5-device.c:2134
+#: spa/plugins/alsa/acp/alsa-mixer.c:4535
+#: spa/plugins/alsa/acp/alsa-mixer.c:4693
+#: spa/plugins/bluez5/bluez5-device.c:2410
msgid "Headset"
msgstr "Headset"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4472
-#: spa/plugins/alsa/acp/alsa-mixer.c:4630
+#: spa/plugins/alsa/acp/alsa-mixer.c:4536
+#: spa/plugins/alsa/acp/alsa-mixer.c:4694
msgid "Speakerphone"
msgstr "Högtalartelefon"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4473
-#: spa/plugins/alsa/acp/alsa-mixer.c:4474
+#: spa/plugins/alsa/acp/alsa-mixer.c:4537
+#: spa/plugins/alsa/acp/alsa-mixer.c:4538
msgid "Multichannel"
msgstr "Multikanal"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4475
+#: spa/plugins/alsa/acp/alsa-mixer.c:4539
msgid "Analog Surround 2.1"
msgstr "Analog surround 2.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4476
+#: spa/plugins/alsa/acp/alsa-mixer.c:4540
msgid "Analog Surround 3.0"
msgstr "Analog surround 3.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4477
+#: spa/plugins/alsa/acp/alsa-mixer.c:4541
msgid "Analog Surround 3.1"
msgstr "Analog surround 3.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4478
+#: spa/plugins/alsa/acp/alsa-mixer.c:4542
msgid "Analog Surround 4.0"
msgstr "Analog surround 4.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4479
+#: spa/plugins/alsa/acp/alsa-mixer.c:4543
msgid "Analog Surround 4.1"
msgstr "Analog surround 4.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4480
+#: spa/plugins/alsa/acp/alsa-mixer.c:4544
msgid "Analog Surround 5.0"
msgstr "Analog surround 5.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4481
+#: spa/plugins/alsa/acp/alsa-mixer.c:4545
msgid "Analog Surround 5.1"
msgstr "Analog surround 5.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4482
+#: spa/plugins/alsa/acp/alsa-mixer.c:4546
msgid "Analog Surround 6.0"
msgstr "Analog surround 6.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4483
+#: spa/plugins/alsa/acp/alsa-mixer.c:4547
msgid "Analog Surround 6.1"
msgstr "Analog surround 6.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4484
+#: spa/plugins/alsa/acp/alsa-mixer.c:4548
msgid "Analog Surround 7.0"
msgstr "Analog surround 7.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4485
+#: spa/plugins/alsa/acp/alsa-mixer.c:4549
msgid "Analog Surround 7.1"
msgstr "Analog surround 7.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4486
+#: spa/plugins/alsa/acp/alsa-mixer.c:4550
msgid "Digital Stereo (IEC958)"
msgstr "Digital stereo (IEC958)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4487
+#: spa/plugins/alsa/acp/alsa-mixer.c:4551
msgid "Digital Surround 4.0 (IEC958/AC3)"
msgstr "Digital surround 4.0 (IEC958/AC3)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4488
+#: spa/plugins/alsa/acp/alsa-mixer.c:4552
msgid "Digital Surround 5.1 (IEC958/AC3)"
msgstr "Digital surround 5.1 (IEC958/AC3)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4489
+#: spa/plugins/alsa/acp/alsa-mixer.c:4553
msgid "Digital Surround 5.1 (IEC958/DTS)"
msgstr "Digital surround 5.1 (IEC958/DTS)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4490
+#: spa/plugins/alsa/acp/alsa-mixer.c:4554
msgid "Digital Stereo (HDMI)"
msgstr "Digital stereo (HDMI)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4491
+#: spa/plugins/alsa/acp/alsa-mixer.c:4555
msgid "Digital Surround 5.1 (HDMI)"
msgstr "Digital surround 5.1 (HDMI)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4492
+#: spa/plugins/alsa/acp/alsa-mixer.c:4556
msgid "Chat"
msgstr "Chatt"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4493
+#: spa/plugins/alsa/acp/alsa-mixer.c:4557
msgid "Game"
msgstr "Spel"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4627
+#: spa/plugins/alsa/acp/alsa-mixer.c:4691
msgid "Analog Mono Duplex"
msgstr "Analog mono duplex"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4628
+#: spa/plugins/alsa/acp/alsa-mixer.c:4692
msgid "Analog Stereo Duplex"
msgstr "Analog stereo duplex"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4631
+#: spa/plugins/alsa/acp/alsa-mixer.c:4695
msgid "Digital Stereo Duplex (IEC958)"
msgstr "Digital stereo duplex (IEC958)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4632
+#: spa/plugins/alsa/acp/alsa-mixer.c:4696
msgid "Multichannel Duplex"
msgstr "Multikanalduplex"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4633
+#: spa/plugins/alsa/acp/alsa-mixer.c:4697
msgid "Stereo Duplex"
msgstr "Stereo duplex"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4634
+#: spa/plugins/alsa/acp/alsa-mixer.c:4698
msgid "Mono Chat + 7.1 Surround"
msgstr "Mono Chatt + 7.1 Surround"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4735
+#: spa/plugins/alsa/acp/alsa-mixer.c:4799
#, c-format
msgid "%s Output"
msgstr "%s-utgång"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4743
+#: spa/plugins/alsa/acp/alsa-mixer.c:4807
#, c-format
msgid "%s Input"
msgstr "%s-ingång"
@@ -627,116 +676,115 @@ msgstr[1] ""
"Förmodligen är detta ett fel i ALSA-drivrutinen ”%s”. Vänligen rapportera "
"problemet till ALSA-utvecklarna."
-#: spa/plugins/alsa/acp/channelmap.h:457
+#: spa/plugins/alsa/acp/channelmap.h:460
msgid "(invalid)"
msgstr "(ogiltig)"
-#: spa/plugins/alsa/acp/compat.c:193
+#: spa/plugins/alsa/acp/compat.c:194
msgid "Built-in Audio"
msgstr "Inbyggt ljud"
-#: spa/plugins/alsa/acp/compat.c:198
+#: spa/plugins/alsa/acp/compat.c:199
msgid "Modem"
msgstr "Modem"
-#: spa/plugins/bluez5/bluez5-device.c:1813
+#: spa/plugins/bluez5/bluez5-device.c:2032
msgid "Audio Gateway (A2DP Source & HSP/HFP AG)"
msgstr "Audio gateway (A2DP-källa & HSP/HFP AG)"
-#: spa/plugins/bluez5/bluez5-device.c:1841
+#: spa/plugins/bluez5/bluez5-device.c:2061
msgid "Audio Streaming for Hearing Aids (ASHA Sink)"
msgstr "Ljudströmning för hörhjälpmedel (ASHA-utgång)"
-#: spa/plugins/bluez5/bluez5-device.c:1881
+#: spa/plugins/bluez5/bluez5-device.c:2104
#, c-format
msgid "High Fidelity Playback (A2DP Sink, codec %s)"
msgstr "High fidelity-uppspelning (A2DP-utgång, kodek %s)"
-#: spa/plugins/bluez5/bluez5-device.c:1884
+#: spa/plugins/bluez5/bluez5-device.c:2107
#, c-format
msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)"
msgstr "High fidelity duplex (A2DP-källa/utgång, kodek %s)"
-#: spa/plugins/bluez5/bluez5-device.c:1892
+#: spa/plugins/bluez5/bluez5-device.c:2115
msgid "High Fidelity Playback (A2DP Sink)"
msgstr "High fidelity-uppspelning (A2DP-utgång)"
-#: spa/plugins/bluez5/bluez5-device.c:1894
+#: spa/plugins/bluez5/bluez5-device.c:2117
msgid "High Fidelity Duplex (A2DP Source/Sink)"
msgstr "High fidelity duplex (A2DP-källa/utgång)"
-#: spa/plugins/bluez5/bluez5-device.c:1944
+#: spa/plugins/bluez5/bluez5-device.c:2194
#, c-format
msgid "High Fidelity Playback (BAP Sink, codec %s)"
msgstr "High fidelity-uppspelning (BAP-utgång, kodek %s)"
-#: spa/plugins/bluez5/bluez5-device.c:1949
+#: spa/plugins/bluez5/bluez5-device.c:2199
#, c-format
msgid "High Fidelity Input (BAP Source, codec %s)"
msgstr "High fidelity-ingång (BAP-källa, kodek %s)"
-#: spa/plugins/bluez5/bluez5-device.c:1953
+#: spa/plugins/bluez5/bluez5-device.c:2203
#, c-format
msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)"
msgstr "High fidelity duplex (BAP-källa/utgång, kodek %s)"
-#: spa/plugins/bluez5/bluez5-device.c:1962
+#: spa/plugins/bluez5/bluez5-device.c:2212
msgid "High Fidelity Playback (BAP Sink)"
msgstr "High fidelity-uppspelning (BAP-utgång)"
-#: spa/plugins/bluez5/bluez5-device.c:1966
+#: spa/plugins/bluez5/bluez5-device.c:2216
msgid "High Fidelity Input (BAP Source)"
msgstr "High fidelity-ingång (BAP-källa)"
-#: spa/plugins/bluez5/bluez5-device.c:1969
+#: spa/plugins/bluez5/bluez5-device.c:2219
msgid "High Fidelity Duplex (BAP Source/Sink)"
msgstr "High fidelity duplex (BAP-källa/utgång)"
-#: spa/plugins/bluez5/bluez5-device.c:2015
+#: spa/plugins/bluez5/bluez5-device.c:2259
#, c-format
msgid "Headset Head Unit (HSP/HFP, codec %s)"
msgstr "Headset-huvudenhet (HSP/HFP, kodek %s)"
-#: spa/plugins/bluez5/bluez5-device.c:2135
-#: spa/plugins/bluez5/bluez5-device.c:2140
-#: spa/plugins/bluez5/bluez5-device.c:2147
-#: spa/plugins/bluez5/bluez5-device.c:2153
-#: spa/plugins/bluez5/bluez5-device.c:2159
-#: spa/plugins/bluez5/bluez5-device.c:2165
-#: spa/plugins/bluez5/bluez5-device.c:2171
-#: spa/plugins/bluez5/bluez5-device.c:2177
-#: spa/plugins/bluez5/bluez5-device.c:2183
+#: spa/plugins/bluez5/bluez5-device.c:2411
+#: spa/plugins/bluez5/bluez5-device.c:2416
+#: spa/plugins/bluez5/bluez5-device.c:2423
+#: spa/plugins/bluez5/bluez5-device.c:2429
+#: spa/plugins/bluez5/bluez5-device.c:2435
+#: spa/plugins/bluez5/bluez5-device.c:2441
+#: spa/plugins/bluez5/bluez5-device.c:2447
+#: spa/plugins/bluez5/bluez5-device.c:2453
+#: spa/plugins/bluez5/bluez5-device.c:2459
msgid "Handsfree"
msgstr "Handsfree"
-#: spa/plugins/bluez5/bluez5-device.c:2141
+#: spa/plugins/bluez5/bluez5-device.c:2417
msgid "Handsfree (HFP)"
msgstr "Handsfree (HFP)"
-#: spa/plugins/bluez5/bluez5-device.c:2158
-msgid "Headphone"
-msgstr "Hörlurar"
-
-#: spa/plugins/bluez5/bluez5-device.c:2164
+#: spa/plugins/bluez5/bluez5-device.c:2440
msgid "Portable"
msgstr "Bärbar"
-#: spa/plugins/bluez5/bluez5-device.c:2170
+#: spa/plugins/bluez5/bluez5-device.c:2446
msgid "Car"
msgstr "Bil"
-#: spa/plugins/bluez5/bluez5-device.c:2176
+#: spa/plugins/bluez5/bluez5-device.c:2452
msgid "HiFi"
msgstr "HiFi"
-#: spa/plugins/bluez5/bluez5-device.c:2182
+#: spa/plugins/bluez5/bluez5-device.c:2458
msgid "Phone"
msgstr "Telefon"
-#: spa/plugins/bluez5/bluez5-device.c:2189
+#: spa/plugins/bluez5/bluez5-device.c:2465
msgid "Bluetooth"
msgstr "Bluetooth"
-#: spa/plugins/bluez5/bluez5-device.c:2190
-msgid "Bluetooth (HFP)"
-msgstr "Bluetooth (HFP)"
+#: spa/plugins/bluez5/bluez5-device.c:2466
+msgid "Bluetooth Handsfree"
+msgstr "Bluetooth-handsfree"
+
+#~ msgid "Headphone"
+#~ msgstr "Hörlurar"
diff --git a/po/zh_CN.po b/po/zh_CN.po
index 8be588b03..3236996bb 100644
--- a/po/zh_CN.po
+++ b/po/zh_CN.po
@@ -13,8 +13,8 @@ msgstr ""
"Project-Id-Version: pipewire.master-tx\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/"
"issues\n"
-"POT-Creation-Date: 2026-02-11 16:53+0000\n"
-"PO-Revision-Date: 2026-02-13 09:36+0800\n"
+"POT-Creation-Date: 2026-04-08 13:01+0000\n"
+"PO-Revision-Date: 2026-04-09 08:06+0800\n"
"Last-Translator: lumingzh \n"
"Language-Team: Chinese (China) \n"
"Language: zh_CN\n"
@@ -22,7 +22,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2016-03-22 13:23+0000\n"
-"X-Generator: Gtranslator 49.0\n"
+"X-Generator: Gtranslator 50.0\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: src/daemon/pipewire.c:29
@@ -65,46 +65,46 @@ msgstr "虚拟输出"
msgid "Tunnel for %s@%s"
msgstr "用于 %s@%s 的隧道"
-#: src/modules/module-zeroconf-discover.c:326
+#: src/modules/module-zeroconf-discover.c:290
msgid "Unknown device"
msgstr "未知设备"
-#: src/modules/module-zeroconf-discover.c:338
+#: src/modules/module-zeroconf-discover.c:302
#, c-format
msgid "%s on %s@%s"
msgstr "%2$s@%3$s 上的 %1$s"
-#: src/modules/module-zeroconf-discover.c:342
+#: src/modules/module-zeroconf-discover.c:306
#, c-format
msgid "%s on %s"
msgstr "%2$s 上的 %1$s"
-#: src/tools/pw-cat.c:264
+#: src/tools/pw-cat.c:269
#, c-format
msgid "Supported formats:\n"
msgstr "支持的格式:\n"
-#: src/tools/pw-cat.c:749
+#: src/tools/pw-cat.c:754
#, c-format
msgid "Supported channel layouts:\n"
msgstr "支持的声道布局:\n"
-#: src/tools/pw-cat.c:759
+#: src/tools/pw-cat.c:764
#, c-format
msgid "Supported channel layout aliases:\n"
msgstr "支持的声道布局别名:\n"
-#: src/tools/pw-cat.c:761
+#: src/tools/pw-cat.c:766
#, c-format
msgid " %s -> %s\n"
msgstr " %s -> %s\n"
-#: src/tools/pw-cat.c:766
+#: src/tools/pw-cat.c:771
#, c-format
msgid "Supported channel names:\n"
msgstr "支持的声道名称:\n"
-#: src/tools/pw-cat.c:1177
+#: src/tools/pw-cat.c:1183
#, c-format
msgid ""
"%s [options] [|-]\n"
@@ -119,7 +119,7 @@ msgstr ""
" -v, --verbose 输出详细操作\n"
"\n"
-#: src/tools/pw-cat.c:1184
+#: src/tools/pw-cat.c:1190
#, c-format
msgid ""
" -R, --remote Remote daemon name\n"
@@ -129,6 +129,8 @@ msgid ""
" --target Set node target serial or name "
"(default %s)\n"
" 0 means don't link\n"
+" -C --monitor Capture monitor ports (in recording "
+"mode)\n"
" --latency Set node latency (default %s)\n"
" Xunit (unit = s, ms, us, ns)\n"
" or direct samples (256)\n"
@@ -143,6 +145,7 @@ msgstr ""
" --media-role 设置媒体角色 (默认 %s)\n"
" --target 设置节点目标序列或名称 (默认 %s)\n"
" 设为 0 则不链接节点\n"
+" -C --monitor 捕获监视器端口 (录制模式)\n"
" --latency 设置节点延迟 (默认 %s)\n"
" 时间 (单位可为 s, ms, us, ns)\n"
" 或样本数 (如256)\n"
@@ -151,7 +154,7 @@ msgstr ""
" -P --properties 设置节点属性\n"
"\n"
-#: src/tools/pw-cat.c:1202
+#: src/tools/pw-cat.c:1209
#, c-format
msgid ""
" --rate Sample rate (default %u)\n"
@@ -198,7 +201,7 @@ msgstr ""
" -n, --sample-count COUNT 计数采样后停止\n"
"\n"
-#: src/tools/pw-cat.c:1227
+#: src/tools/pw-cat.c:1234
msgid ""
" -p, --playback Playback mode\n"
" -r, --record Recording mode\n"
@@ -218,7 +221,7 @@ msgstr ""
" -c, --midi-clip MIDI 剪辑模式\n"
"\n"
-#: src/tools/pw-cat.c:1827
+#: src/tools/pw-cat.c:1839
#, c-format
msgid "Supported containers and extensions:\n"
msgstr "支持的容器和扩展:\n"
@@ -245,12 +248,12 @@ msgstr ""
msgid "Pro Audio"
msgstr "专业音频"
-#: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699
-#: spa/plugins/bluez5/bluez5-device.c:2021
+#: spa/plugins/alsa/acp/acp.c:535 spa/plugins/alsa/acp/alsa-mixer.c:4699
+#: spa/plugins/bluez5/bluez5-device.c:2163
msgid "Off"
msgstr "关"
-#: spa/plugins/alsa/acp/acp.c:620
+#: spa/plugins/alsa/acp/acp.c:618
#, c-format
msgid "%s [ALSA UCM error]"
msgstr "%s [ALSA UCM 错误]"
@@ -278,7 +281,7 @@ msgstr "输入插孔"
#: spa/plugins/alsa/acp/alsa-mixer.c:2726
#: spa/plugins/alsa/acp/alsa-mixer.c:2810
-#: spa/plugins/bluez5/bluez5-device.c:2422
+#: spa/plugins/bluez5/bluez5-device.c:2596
msgid "Microphone"
msgstr "话筒"
@@ -344,15 +347,15 @@ msgid "No Bass Boost"
msgstr "无重低音增强"
#: spa/plugins/alsa/acp/alsa-mixer.c:2741
-#: spa/plugins/bluez5/bluez5-device.c:2428
+#: spa/plugins/bluez5/bluez5-device.c:2602
msgid "Speaker"
msgstr "扬声器"
#. Don't call it "headset", the HF one has the mic
#: spa/plugins/alsa/acp/alsa-mixer.c:2742
#: spa/plugins/alsa/acp/alsa-mixer.c:2820
-#: spa/plugins/bluez5/bluez5-device.c:2434
-#: spa/plugins/bluez5/bluez5-device.c:2501
+#: spa/plugins/bluez5/bluez5-device.c:2608
+#: spa/plugins/bluez5/bluez5-device.c:2675
msgid "Headphones"
msgstr "模拟耳机"
@@ -462,7 +465,7 @@ msgstr "立体声"
#: spa/plugins/alsa/acp/alsa-mixer.c:4535
#: spa/plugins/alsa/acp/alsa-mixer.c:4693
-#: spa/plugins/bluez5/bluez5-device.c:2410
+#: spa/plugins/bluez5/bluez5-device.c:2584
msgid "Headset"
msgstr "耳机"
@@ -657,101 +660,109 @@ msgstr "内置音频"
msgid "Modem"
msgstr "调制解调器"
-#: spa/plugins/bluez5/bluez5-device.c:2032
+#: spa/plugins/bluez5/bluez5-device.c:2174
msgid "Audio Gateway (A2DP Source & HSP/HFP AG)"
msgstr "音频网关 (A2DP 信源 或 HSP/HFP 网关)"
-#: spa/plugins/bluez5/bluez5-device.c:2061
+#: spa/plugins/bluez5/bluez5-device.c:2203
msgid "Audio Streaming for Hearing Aids (ASHA Sink)"
msgstr "助听器音频流 (ASHA 信宿)"
-#: spa/plugins/bluez5/bluez5-device.c:2104
+#: spa/plugins/bluez5/bluez5-device.c:2246
#, c-format
msgid "High Fidelity Playback (A2DP Sink, codec %s)"
msgstr "高保真回放 (A2DP 信宿, 编码 %s)"
-#: spa/plugins/bluez5/bluez5-device.c:2107
+#: spa/plugins/bluez5/bluez5-device.c:2249
#, c-format
msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)"
msgstr "高保真双工 (A2DP 信源/信宿, 编码 %s)"
-#: spa/plugins/bluez5/bluez5-device.c:2115
+#: spa/plugins/bluez5/bluez5-device.c:2257
msgid "High Fidelity Playback (A2DP Sink)"
msgstr "高保真回放 (A2DP 信宿)"
-#: spa/plugins/bluez5/bluez5-device.c:2117
+#: spa/plugins/bluez5/bluez5-device.c:2259
msgid "High Fidelity Duplex (A2DP Source/Sink)"
msgstr "高保真双工 (A2DP 信源/信宿)"
-#: spa/plugins/bluez5/bluez5-device.c:2194
+#: spa/plugins/bluez5/bluez5-device.c:2281
+msgid "Auto: Prefer Quality (A2DP)"
+msgstr "自动:质量优先 (A2DP)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2286
+msgid "Auto: Prefer Latency (A2DP)"
+msgstr "自动:延迟优先 (A2DP)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2366
#, c-format
msgid "High Fidelity Playback (BAP Sink, codec %s)"
msgstr "高保真回放 (BAP 信宿, 编码 %s)"
-#: spa/plugins/bluez5/bluez5-device.c:2199
+#: spa/plugins/bluez5/bluez5-device.c:2371
#, c-format
msgid "High Fidelity Input (BAP Source, codec %s)"
msgstr "高保真输入 (BAP 信源, 编码 %s)"
-#: spa/plugins/bluez5/bluez5-device.c:2203
+#: spa/plugins/bluez5/bluez5-device.c:2375
#, c-format
msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)"
msgstr "高保真双工 (BAP 信源/信宿, 编码 %s)"
-#: spa/plugins/bluez5/bluez5-device.c:2212
+#: spa/plugins/bluez5/bluez5-device.c:2384
msgid "High Fidelity Playback (BAP Sink)"
msgstr "高保真回放 (BAP 信宿)"
-#: spa/plugins/bluez5/bluez5-device.c:2216
+#: spa/plugins/bluez5/bluez5-device.c:2388
msgid "High Fidelity Input (BAP Source)"
msgstr "高保真输入 (BAP 信源)"
-#: spa/plugins/bluez5/bluez5-device.c:2219
+#: spa/plugins/bluez5/bluez5-device.c:2391
msgid "High Fidelity Duplex (BAP Source/Sink)"
msgstr "高保真双工 (BAP 信源/信宿)"
-#: spa/plugins/bluez5/bluez5-device.c:2259
+#: spa/plugins/bluez5/bluez5-device.c:2431
#, c-format
msgid "Headset Head Unit (HSP/HFP, codec %s)"
msgstr "头戴式耳机单元 (HSP/HFP, 编码 %s)"
-#: spa/plugins/bluez5/bluez5-device.c:2411
-#: spa/plugins/bluez5/bluez5-device.c:2416
-#: spa/plugins/bluez5/bluez5-device.c:2423
-#: spa/plugins/bluez5/bluez5-device.c:2429
-#: spa/plugins/bluez5/bluez5-device.c:2435
-#: spa/plugins/bluez5/bluez5-device.c:2441
-#: spa/plugins/bluez5/bluez5-device.c:2447
-#: spa/plugins/bluez5/bluez5-device.c:2453
-#: spa/plugins/bluez5/bluez5-device.c:2459
+#: spa/plugins/bluez5/bluez5-device.c:2585
+#: spa/plugins/bluez5/bluez5-device.c:2590
+#: spa/plugins/bluez5/bluez5-device.c:2597
+#: spa/plugins/bluez5/bluez5-device.c:2603
+#: spa/plugins/bluez5/bluez5-device.c:2609
+#: spa/plugins/bluez5/bluez5-device.c:2615
+#: spa/plugins/bluez5/bluez5-device.c:2621
+#: spa/plugins/bluez5/bluez5-device.c:2627
+#: spa/plugins/bluez5/bluez5-device.c:2633
msgid "Handsfree"
msgstr "免提"
-#: spa/plugins/bluez5/bluez5-device.c:2417
+#: spa/plugins/bluez5/bluez5-device.c:2591
msgid "Handsfree (HFP)"
msgstr "免提(HFP)"
-#: spa/plugins/bluez5/bluez5-device.c:2440
+#: spa/plugins/bluez5/bluez5-device.c:2614
msgid "Portable"
msgstr "便携式"
-#: spa/plugins/bluez5/bluez5-device.c:2446
+#: spa/plugins/bluez5/bluez5-device.c:2620
msgid "Car"
msgstr "车内"
-#: spa/plugins/bluez5/bluez5-device.c:2452
+#: spa/plugins/bluez5/bluez5-device.c:2626
msgid "HiFi"
msgstr "高保真"
-#: spa/plugins/bluez5/bluez5-device.c:2458
+#: spa/plugins/bluez5/bluez5-device.c:2632
msgid "Phone"
msgstr "电话"
-#: spa/plugins/bluez5/bluez5-device.c:2465
+#: spa/plugins/bluez5/bluez5-device.c:2639
msgid "Bluetooth"
msgstr "蓝牙"
-#: spa/plugins/bluez5/bluez5-device.c:2466
+#: spa/plugins/bluez5/bluez5-device.c:2640
msgid "Bluetooth Handsfree"
msgstr "蓝牙免提"
diff --git a/po/zh_TW.po b/po/zh_TW.po
index 1a768a992..ab41369cf 100644
--- a/po/zh_TW.po
+++ b/po/zh_TW.po
@@ -4,110 +4,221 @@
#
# Cheng-Chia Tseng , 2010, 2012.
# pan93412 , 2020.
+# SPDX-FileCopyrightText: 2026 Kisaragi Hiu
msgid ""
msgstr ""
"Project-Id-Version: PipeWire Volume Control\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/"
"issues/new\n"
-"POT-Creation-Date: 2021-04-18 16:54+0800\n"
-"PO-Revision-Date: 2020-01-11 13:49+0800\n"
-"Last-Translator: pan93412 \n"
-"Language-Team: Chinese \n"
+"POT-Creation-Date: 2026-03-11 22:03+0900\n"
+"PO-Revision-Date: 2026-03-11 21:24+0900\n"
+"Last-Translator: Kisaragi Hiu \n"
+"Language-Team: Chinese (Taiwan) \n"
"Language: zh_TW\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Lokalize 19.12.0\n"
+"X-Generator: Lokalize 26.03.70\n"
"Plural-Forms: nplurals=1; plural=0;\n"
-#: src/daemon/pipewire.c:43
+#: src/daemon/pipewire.c:29
#, c-format
msgid ""
"%s [options]\n"
" -h, --help Show this help\n"
+" -v, --verbose Increase verbosity by one level\n"
" --version Show version\n"
" -c, --config Load config (Default %s)\n"
+" -P --properties Set context properties\n"
msgstr ""
+"%s [選項]\n"
+" -h, --help 顯示此說明\n"
+" -v, --verbose 提高訊息詳細程度一階\n"
+" --version 顯示版本\n"
+" -c, --config 載入設定檔案(預設 %s)\n"
+" -P --properties 設定前後文屬性\n"
#: src/daemon/pipewire.desktop.in:4
msgid "PipeWire Media System"
-msgstr ""
+msgstr "PipeWire 媒體系統"
#: src/daemon/pipewire.desktop.in:5
msgid "Start the PipeWire Media System"
-msgstr ""
+msgstr "啟動 PipeWire 媒體系統"
-#: src/examples/media-session/alsa-monitor.c:526
-#: spa/plugins/alsa/acp/compat.c:187
-msgid "Built-in Audio"
-msgstr "內部音效"
+#: src/modules/module-protocol-pulse/modules/module-tunnel-sink.c:159
+#: src/modules/module-protocol-pulse/modules/module-tunnel-source.c:159
+#, c-format
+msgid "Tunnel to %s%s%s"
+msgstr "穿隧到 %s%s%s"
-#: src/examples/media-session/alsa-monitor.c:530
-#: spa/plugins/alsa/acp/compat.c:192
-msgid "Modem"
-msgstr "數據機"
+#: src/modules/module-fallback-sink.c:40
+msgid "Dummy Output"
+msgstr "虛擬輸出"
-#: src/examples/media-session/alsa-monitor.c:539
+#: src/modules/module-pulse-tunnel.c:761
+#, c-format
+msgid "Tunnel for %s@%s"
+msgstr "%s@%s 的穿隧道"
+
+#: src/modules/module-zeroconf-discover.c:290
msgid "Unknown device"
-msgstr ""
+msgstr "未知裝置"
-#: src/tools/pw-cat.c:991
+#: src/modules/module-zeroconf-discover.c:302
+#, c-format
+msgid "%s on %s@%s"
+msgstr "%s 於 %s@%s"
+
+#: src/modules/module-zeroconf-discover.c:306
+#, c-format
+msgid "%s on %s"
+msgstr "%s 於 %s"
+
+#: src/tools/pw-cat.c:269
+#, c-format
+msgid "Supported formats:\n"
+msgstr "支援的格式:\n"
+
+#: src/tools/pw-cat.c:754
+#, c-format
+msgid "Supported channel layouts:\n"
+msgstr "支援的頻道配置:\n"
+
+#: src/tools/pw-cat.c:764
+#, c-format
+msgid "Supported channel layout aliases:\n"
+msgstr "支援的頻道配置別名:\n"
+
+#: src/tools/pw-cat.c:766
+#, c-format
+msgid " %s -> %s\n"
+msgstr " %s -> %s\n"
+
+#: src/tools/pw-cat.c:771
+#, c-format
+msgid "Supported channel names:\n"
+msgstr "支援的頻道名稱:\n"
+
+#: src/tools/pw-cat.c:1182
#, c-format
msgid ""
-"%s [options] \n"
+"%s [options] [|-]\n"
" -h, --help Show this help\n"
" --version Show version\n"
" -v, --verbose Enable verbose operations\n"
"\n"
msgstr ""
+"%s [選項] [<檔案>|-]\n"
+" -h, --help 顯示此說明\n"
+" --version 顯示版本\n"
+" -v, --verbose 操作進行時顯示詳細訊息\n"
+"\n"
-#: src/tools/pw-cat.c:998
+#: src/tools/pw-cat.c:1189
#, c-format
msgid ""
" -R, --remote Remote daemon name\n"
" --media-type Set media type (default %s)\n"
" --media-category Set media category (default %s)\n"
" --media-role Set media role (default %s)\n"
-" --target Set node target (default %s)\n"
+" --target Set node target serial or name "
+"(default %s)\n"
" 0 means don't link\n"
" --latency Set node latency (default %s)\n"
" Xunit (unit = s, ms, us, ns)\n"
" or direct samples (256)\n"
" the rate is the one of the source "
"file\n"
-" --list-targets List available targets for --target\n"
+" -P --properties Set node properties\n"
"\n"
msgstr ""
+" -R, --remote 遠端守護程式名稱\n"
+" --media-type 設定媒體型態(預設 %s)\n"
+" --media-category 設定媒體分類(預設 %s)\n"
+" --media-role 設定媒體角色(預設 %s)\n"
+" --target 設定節點目標序號或名稱(預設 %s)\n"
+" 0 代表不要連結\n"
+" --latency 設定節點延遲(預設 %s)\n"
+" X單位(單位為 s, ms, us 或 ns)\n"
+" 或直接指定樣本數 (256)\n"
+" 取樣率取自來源檔案\n"
+" -P --properties 設定節點屬性\n"
+"\n"
-#: src/tools/pw-cat.c:1016
+#: src/tools/pw-cat.c:1207
#, c-format
msgid ""
-" --rate Sample rate (req. for rec) (default "
-"%u)\n"
-" --channels Number of channels (req. for rec) "
-"(default %u)\n"
+" --rate Sample rate (default %u)\n"
+" --channels Number of channels (default %u)\n"
" --channel-map Channel map\n"
-" one of: \"stereo\", "
-"\"surround-51\",... or\n"
+" a channel layout: \"Stereo\", "
+"\"5.1\",... or\n"
" comma separated list of channel "
"names: eg. \"FL,FR\"\n"
-" --format Sample format %s (req. for rec) "
-"(default %s)\n"
+" --list-layouts List supported channel layouts\n"
+" --list-channel-names List supported channel maps\n"
+" --format Sample format (default %s)\n"
+" --list-formats List supported sample formats\n"
+" --container Container format\n"
+" --list-containers List supported containers and "
+"extensions\n"
" --volume Stream volume 0-1.0 (default %.3f)\n"
" -q --quality Resampler quality (0 - 15) (default "
"%d)\n"
+" -a, --raw RAW mode\n"
+" -M, --force-midi Force midi format, one of \"midi\" "
+"or \"ump\", (default ump)\n"
+" -n, --sample-count COUNT Stop after COUNT samples\n"
"\n"
msgstr ""
+" --rate 取樣率(預設 %u)\n"
+" --channels 頻道數量(預設 %u)\n"
+" --channel-map 頻道映射\n"
+" 可以是頻道配置名稱,像是 "
+"\"Stereo\"、\"5.1\" 等等,或是\n"
+" 以逗號分隔的頻道名稱清單,像是 "
+"\"FL,FR\"\n"
+" --list-layouts 列出支援的頻道配置\n"
+" --list-channel-names 列出支援的頻道映射\n"
+" --format 取樣格式(預設 %s)\n"
+" --list-formats 列出支援的取樣格式\n"
+" --container 容器格式\n"
+" --list-containers 列出支援的容器與副檔名\n"
+" --volume 串流音量 0-1.0(預設 %.3f)\n"
+" -q --quality 重新取樣器品質 0-15(預設 %d)\n"
+" -a, --raw 原始模式\n"
+" -M, --force-midi 強制使用 midi 格式,可選擇 \"midi\" "
+"或 \"ump\"(預設 ump)\n"
+" -n, --sample-count 樣本數 在「樣本數」個樣本之後停止\n"
+"\n"
-#: src/tools/pw-cat.c:1033
+#: src/tools/pw-cat.c:1232
msgid ""
" -p, --playback Playback mode\n"
" -r, --record Recording mode\n"
" -m, --midi Midi mode\n"
+" -d, --dsd DSD mode\n"
+" -o, --encoded Encoded mode\n"
+" -s, --sysex SysEx mode\n"
+" -c, --midi-clip MIDI clip mode\n"
"\n"
msgstr ""
+" -p, --playback 播放模式\n"
+" -r, --record 錄製模式\n"
+" -m, --midi Midi 模式\n"
+" -d, --dsd DSD 模式\n"
+" -o, --encoded 已編碼模式\n"
+" -s, --sysex SysEx 模式\n"
+" -c, --midi-clip MIDI 素材模式\n"
+"\n"
-#: src/tools/pw-cli.c:2932
+#: src/tools/pw-cat.c:1837
+#, c-format
+msgid "Supported containers and extensions:\n"
+msgstr "支援的容器與副檔名:\n"
+
+#: src/tools/pw-cli.c:2386
#, c-format
msgid ""
"%s [options] [command]\n"
@@ -115,357 +226,363 @@ msgid ""
" --version Show version\n"
" -d, --daemon Start as daemon (Default false)\n"
" -r, --remote Remote daemon name\n"
+" -m, --monitor Monitor activity\n"
"\n"
msgstr ""
+"%s [選項] [指令]\n"
+" -h, --help 顯示此說明\n"
+" --version 顯示版本\n"
+" -d, --daemon 作為守護程式啟動(預設為否)\n"
+" -r, --remote 遠端守護程式名稱\n"
+" -m, --monitor 監控活動\n"
+"\n"
-#: spa/plugins/alsa/acp/acp.c:290
+#: spa/plugins/alsa/acp/acp.c:361
msgid "Pro Audio"
-msgstr ""
+msgstr "Pro Audio"
-#: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704
-#: spa/plugins/bluez5/bluez5-device.c:1000
+#: spa/plugins/alsa/acp/acp.c:535 spa/plugins/alsa/acp/alsa-mixer.c:4699
+#: spa/plugins/bluez5/bluez5-device.c:2021
msgid "Off"
msgstr "關閉"
-#: spa/plugins/alsa/acp/channelmap.h:466
-msgid "(invalid)"
-msgstr "(無效)"
+#: spa/plugins/alsa/acp/acp.c:618
+#, c-format
+msgid "%s [ALSA UCM error]"
+msgstr "%s [ALSA UCM 錯誤]"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2709
+#: spa/plugins/alsa/acp/alsa-mixer.c:2721
msgid "Input"
msgstr "輸入"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2710
+#: spa/plugins/alsa/acp/alsa-mixer.c:2722
msgid "Docking Station Input"
msgstr "Docking Station 輸入"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2711
+#: spa/plugins/alsa/acp/alsa-mixer.c:2723
msgid "Docking Station Microphone"
msgstr "Docking Station 麥克風"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2712
+#: spa/plugins/alsa/acp/alsa-mixer.c:2724
msgid "Docking Station Line In"
msgstr "Docking Station 線路輸入"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2713
-#: spa/plugins/alsa/acp/alsa-mixer.c:2804
+#: spa/plugins/alsa/acp/alsa-mixer.c:2725
+#: spa/plugins/alsa/acp/alsa-mixer.c:2816
msgid "Line In"
msgstr "線路輸入"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2714
-#: spa/plugins/alsa/acp/alsa-mixer.c:2798
-#: spa/plugins/bluez5/bluez5-device.c:1145
+#: spa/plugins/alsa/acp/alsa-mixer.c:2726
+#: spa/plugins/alsa/acp/alsa-mixer.c:2810
+#: spa/plugins/bluez5/bluez5-device.c:2422
msgid "Microphone"
msgstr "麥克風"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2715
-#: spa/plugins/alsa/acp/alsa-mixer.c:2799
+#: spa/plugins/alsa/acp/alsa-mixer.c:2727
+#: spa/plugins/alsa/acp/alsa-mixer.c:2811
msgid "Front Microphone"
msgstr "前方麥克風"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2716
-#: spa/plugins/alsa/acp/alsa-mixer.c:2800
+#: spa/plugins/alsa/acp/alsa-mixer.c:2728
+#: spa/plugins/alsa/acp/alsa-mixer.c:2812
msgid "Rear Microphone"
msgstr "後方麥克風"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2717
+#: spa/plugins/alsa/acp/alsa-mixer.c:2729
msgid "External Microphone"
msgstr "外接麥克風"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2718
-#: spa/plugins/alsa/acp/alsa-mixer.c:2802
+#: spa/plugins/alsa/acp/alsa-mixer.c:2730
+#: spa/plugins/alsa/acp/alsa-mixer.c:2814
msgid "Internal Microphone"
msgstr "內建麥克風"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2719
-#: spa/plugins/alsa/acp/alsa-mixer.c:2805
+#: spa/plugins/alsa/acp/alsa-mixer.c:2731
+#: spa/plugins/alsa/acp/alsa-mixer.c:2817
msgid "Radio"
msgstr "無線電"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2720
-#: spa/plugins/alsa/acp/alsa-mixer.c:2806
+#: spa/plugins/alsa/acp/alsa-mixer.c:2732
+#: spa/plugins/alsa/acp/alsa-mixer.c:2818
msgid "Video"
msgstr "視訊"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2721
+#: spa/plugins/alsa/acp/alsa-mixer.c:2733
msgid "Automatic Gain Control"
msgstr "自動增益控制"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2722
+#: spa/plugins/alsa/acp/alsa-mixer.c:2734
msgid "No Automatic Gain Control"
msgstr "無自動增益控制"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2723
+#: spa/plugins/alsa/acp/alsa-mixer.c:2735
msgid "Boost"
msgstr "增強"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2724
+#: spa/plugins/alsa/acp/alsa-mixer.c:2736
msgid "No Boost"
msgstr "無增強"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2725
+#: spa/plugins/alsa/acp/alsa-mixer.c:2737
msgid "Amplifier"
msgstr "擴大器"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2726
+#: spa/plugins/alsa/acp/alsa-mixer.c:2738
msgid "No Amplifier"
msgstr "無擴大器"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2727
+#: spa/plugins/alsa/acp/alsa-mixer.c:2739
msgid "Bass Boost"
msgstr "低音增強"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2728
+#: spa/plugins/alsa/acp/alsa-mixer.c:2740
msgid "No Bass Boost"
msgstr "無低音增強"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2729
-#: spa/plugins/bluez5/bluez5-device.c:1150
+#: spa/plugins/alsa/acp/alsa-mixer.c:2741
+#: spa/plugins/bluez5/bluez5-device.c:2428
msgid "Speaker"
msgstr "喇叭"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2730
-#: spa/plugins/alsa/acp/alsa-mixer.c:2808
+#. Don't call it "headset", the HF one has the mic
+#: spa/plugins/alsa/acp/alsa-mixer.c:2742
+#: spa/plugins/alsa/acp/alsa-mixer.c:2820
+#: spa/plugins/bluez5/bluez5-device.c:2434
+#: spa/plugins/bluez5/bluez5-device.c:2501
msgid "Headphones"
msgstr "頭戴式耳機"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2797
+#: spa/plugins/alsa/acp/alsa-mixer.c:2809
msgid "Analog Input"
msgstr "類比輸入"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2801
+#: spa/plugins/alsa/acp/alsa-mixer.c:2813
msgid "Dock Microphone"
msgstr "臺座麥克風"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2803
+#: spa/plugins/alsa/acp/alsa-mixer.c:2815
msgid "Headset Microphone"
msgstr "耳麥麥克風"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2807
+#: spa/plugins/alsa/acp/alsa-mixer.c:2819
msgid "Analog Output"
msgstr "類比輸出"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2809
-#, fuzzy
+#: spa/plugins/alsa/acp/alsa-mixer.c:2821
msgid "Headphones 2"
-msgstr "頭戴式耳機"
+msgstr "頭戴式耳機 2"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2810
+#: spa/plugins/alsa/acp/alsa-mixer.c:2822
msgid "Headphones Mono Output"
msgstr "頭戴式耳機單聲道輸出"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2811
+#: spa/plugins/alsa/acp/alsa-mixer.c:2823
msgid "Line Out"
msgstr "線路輸出"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2812
+#: spa/plugins/alsa/acp/alsa-mixer.c:2824
msgid "Analog Mono Output"
msgstr "類比單聲道輸出"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2813
+#: spa/plugins/alsa/acp/alsa-mixer.c:2825
msgid "Speakers"
msgstr "喇叭"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2814
+#: spa/plugins/alsa/acp/alsa-mixer.c:2826
msgid "HDMI / DisplayPort"
msgstr "HDMI / DisplayPort"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2815
+#: spa/plugins/alsa/acp/alsa-mixer.c:2827
msgid "Digital Output (S/PDIF)"
msgstr "數位輸出 (S/PDIF)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2816
+#: spa/plugins/alsa/acp/alsa-mixer.c:2828
msgid "Digital Input (S/PDIF)"
msgstr "數位輸入 (S/PDIF)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2817
+#: spa/plugins/alsa/acp/alsa-mixer.c:2829
msgid "Multichannel Input"
msgstr "多聲道輸入"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2818
+#: spa/plugins/alsa/acp/alsa-mixer.c:2830
msgid "Multichannel Output"
msgstr "多聲道輸出"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2819
+#: spa/plugins/alsa/acp/alsa-mixer.c:2831
msgid "Game Output"
msgstr "遊戲輸出"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2820
-#: spa/plugins/alsa/acp/alsa-mixer.c:2821
+#: spa/plugins/alsa/acp/alsa-mixer.c:2832
+#: spa/plugins/alsa/acp/alsa-mixer.c:2833
msgid "Chat Output"
msgstr "聊天輸出"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2822
-#, fuzzy
+#: spa/plugins/alsa/acp/alsa-mixer.c:2834
msgid "Chat Input"
-msgstr "聊天輸出"
+msgstr "聊天輸入"
-#: spa/plugins/alsa/acp/alsa-mixer.c:2823
-#, fuzzy
+#: spa/plugins/alsa/acp/alsa-mixer.c:2835
msgid "Virtual Surround 7.1"
-msgstr "虛擬環繞聲 sink"
+msgstr "虛擬環繞聲 7.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4527
+#: spa/plugins/alsa/acp/alsa-mixer.c:4522
msgid "Analog Mono"
msgstr "類比單聲道"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4528
-#, fuzzy
+#: spa/plugins/alsa/acp/alsa-mixer.c:4523
msgid "Analog Mono (Left)"
-msgstr "類比單聲道"
+msgstr "類比單聲道(左)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4529
-#, fuzzy
+#: spa/plugins/alsa/acp/alsa-mixer.c:4524
msgid "Analog Mono (Right)"
-msgstr "類比單聲道"
+msgstr "類比單聲道(右)"
#. Note: Not translated to "Analog Stereo Input", because the source
#. * name gets "Input" appended to it automatically, so adding "Input"
#. * here would lead to the source name to become "Analog Stereo Input
#. * Input". The same logic applies to analog-stereo-output,
#. * multichannel-input and multichannel-output.
-#: spa/plugins/alsa/acp/alsa-mixer.c:4530
-#: spa/plugins/alsa/acp/alsa-mixer.c:4538
-#: spa/plugins/alsa/acp/alsa-mixer.c:4539
+#: spa/plugins/alsa/acp/alsa-mixer.c:4525
+#: spa/plugins/alsa/acp/alsa-mixer.c:4533
+#: spa/plugins/alsa/acp/alsa-mixer.c:4534
msgid "Analog Stereo"
msgstr "類比立體聲"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4531
+#: spa/plugins/alsa/acp/alsa-mixer.c:4526
msgid "Mono"
msgstr "單聲道"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4532
+#: spa/plugins/alsa/acp/alsa-mixer.c:4527
msgid "Stereo"
msgstr "立體聲"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4540
-#: spa/plugins/alsa/acp/alsa-mixer.c:4698
-#: spa/plugins/bluez5/bluez5-device.c:1135
+#: spa/plugins/alsa/acp/alsa-mixer.c:4535
+#: spa/plugins/alsa/acp/alsa-mixer.c:4693
+#: spa/plugins/bluez5/bluez5-device.c:2410
msgid "Headset"
msgstr "耳麥"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4541
-#: spa/plugins/alsa/acp/alsa-mixer.c:4699
-#, fuzzy
+#: spa/plugins/alsa/acp/alsa-mixer.c:4536
+#: spa/plugins/alsa/acp/alsa-mixer.c:4694
msgid "Speakerphone"
-msgstr "喇叭"
+msgstr "會議揚聲器"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4542
-#: spa/plugins/alsa/acp/alsa-mixer.c:4543
+#: spa/plugins/alsa/acp/alsa-mixer.c:4537
+#: spa/plugins/alsa/acp/alsa-mixer.c:4538
msgid "Multichannel"
msgstr "多聲道"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4544
+#: spa/plugins/alsa/acp/alsa-mixer.c:4539
msgid "Analog Surround 2.1"
msgstr "類比環繞聲 2.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4545
+#: spa/plugins/alsa/acp/alsa-mixer.c:4540
msgid "Analog Surround 3.0"
msgstr "類比環繞聲 3.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4546
+#: spa/plugins/alsa/acp/alsa-mixer.c:4541
msgid "Analog Surround 3.1"
msgstr "類比環繞聲 3.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4547
+#: spa/plugins/alsa/acp/alsa-mixer.c:4542
msgid "Analog Surround 4.0"
msgstr "類比環繞聲 4.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4548
+#: spa/plugins/alsa/acp/alsa-mixer.c:4543
msgid "Analog Surround 4.1"
msgstr "類比環繞聲 4.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4549
+#: spa/plugins/alsa/acp/alsa-mixer.c:4544
msgid "Analog Surround 5.0"
msgstr "類比環繞聲 5.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4550
+#: spa/plugins/alsa/acp/alsa-mixer.c:4545
msgid "Analog Surround 5.1"
msgstr "類比環繞聲 5.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4551
+#: spa/plugins/alsa/acp/alsa-mixer.c:4546
msgid "Analog Surround 6.0"
msgstr "類比環繞聲 6.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4552
+#: spa/plugins/alsa/acp/alsa-mixer.c:4547
msgid "Analog Surround 6.1"
msgstr "類比環繞聲 6.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4553
+#: spa/plugins/alsa/acp/alsa-mixer.c:4548
msgid "Analog Surround 7.0"
msgstr "類比環繞聲 7.0"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4554
+#: spa/plugins/alsa/acp/alsa-mixer.c:4549
msgid "Analog Surround 7.1"
msgstr "類比環繞聲 7.1"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4555
+#: spa/plugins/alsa/acp/alsa-mixer.c:4550
msgid "Digital Stereo (IEC958)"
msgstr "數位立體聲 (IEC958)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4556
+#: spa/plugins/alsa/acp/alsa-mixer.c:4551
msgid "Digital Surround 4.0 (IEC958/AC3)"
msgstr "數位環繞聲 4.0 (IEC958/AC3)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4557
+#: spa/plugins/alsa/acp/alsa-mixer.c:4552
msgid "Digital Surround 5.1 (IEC958/AC3)"
msgstr "數位環繞聲 5.1 (IEC958/AC3)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4558
+#: spa/plugins/alsa/acp/alsa-mixer.c:4553
msgid "Digital Surround 5.1 (IEC958/DTS)"
msgstr "數位環繞聲 5.1 (IEC958/DTS)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4559
+#: spa/plugins/alsa/acp/alsa-mixer.c:4554
msgid "Digital Stereo (HDMI)"
msgstr "數位立體聲 (HDMI)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4560
+#: spa/plugins/alsa/acp/alsa-mixer.c:4555
msgid "Digital Surround 5.1 (HDMI)"
msgstr "數位環繞聲 5.1 (HDMI)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4561
+#: spa/plugins/alsa/acp/alsa-mixer.c:4556
msgid "Chat"
-msgstr ""
+msgstr "聊天"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4562
+#: spa/plugins/alsa/acp/alsa-mixer.c:4557
msgid "Game"
-msgstr ""
+msgstr "遊戲"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4696
+#: spa/plugins/alsa/acp/alsa-mixer.c:4691
msgid "Analog Mono Duplex"
msgstr "類比單聲道雙工"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4697
+#: spa/plugins/alsa/acp/alsa-mixer.c:4692
msgid "Analog Stereo Duplex"
msgstr "類比立體聲雙工"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4700
+#: spa/plugins/alsa/acp/alsa-mixer.c:4695
msgid "Digital Stereo Duplex (IEC958)"
msgstr "數位立體聲雙工 (IEC958)"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4701
+#: spa/plugins/alsa/acp/alsa-mixer.c:4696
msgid "Multichannel Duplex"
msgstr "多聲道雙工"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4702
+#: spa/plugins/alsa/acp/alsa-mixer.c:4697
msgid "Stereo Duplex"
msgstr "立體聲雙工"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4703
+#: spa/plugins/alsa/acp/alsa-mixer.c:4698
msgid "Mono Chat + 7.1 Surround"
-msgstr ""
+msgstr "單聲道聊天 + 7.1 立體聲"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4806
+#: spa/plugins/alsa/acp/alsa-mixer.c:4799
#, c-format
msgid "%s Output"
msgstr "%s 輸出"
-#: spa/plugins/alsa/acp/alsa-mixer.c:4813
+#: spa/plugins/alsa/acp/alsa-mixer.c:4807
#, c-format
msgid "%s Input"
msgstr "%s 輸入"
-#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269
+#: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327
#, c-format
msgid ""
"snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu "
@@ -481,23 +598,23 @@ msgstr[0] ""
"snd_pcm_avail() 傳回超出預期的大值:%lu bytes (%lu ms)。\n"
"這很能是 ALSA 驅動程式「%s」的臭蟲。請回報這個問題給 ALSA 開發者。"
-#: spa/plugins/alsa/acp/alsa-util.c:1241
+#: spa/plugins/alsa/acp/alsa-util.c:1299
#, c-format
msgid ""
-"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s"
-"%lu ms).\n"
+"snd_pcm_delay() returned a value that is exceptionally large: %li byte "
+"(%s%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
"to the ALSA developers."
msgid_plural ""
-"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s"
-"%lu ms).\n"
+"snd_pcm_delay() returned a value that is exceptionally large: %li bytes "
+"(%s%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
"to the ALSA developers."
msgstr[0] ""
"snd_pcm_delay() 傳回超出預期的大值:%li bytes (%s%lu ms)。\n"
"這很能是 ALSA 驅動程式「%s」的臭蟲。請回報這個問題給 ALSA 開發者。"
-#: spa/plugins/alsa/acp/alsa-util.c:1288
+#: spa/plugins/alsa/acp/alsa-util.c:1346
#, c-format
msgid ""
"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail "
@@ -508,7 +625,7 @@ msgstr ""
"snd_pcm_avail_delay() 傳回超出預期的大值:延遲 %lu 少於可用的 %lu。\n"
"這很能是 ALSA 驅動程式「%s」的臭蟲。請回報這個問題給 ALSA 開發者。"
-#: spa/plugins/alsa/acp/alsa-util.c:1331
+#: spa/plugins/alsa/acp/alsa-util.c:1389
#, c-format
msgid ""
"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte "
@@ -524,62 +641,115 @@ msgstr[0] ""
"snd_pcm_mmap_begin() 傳回超出預期的大值:%lu bytes (%lu ms)。\n"
"這很能是 ALSA 驅動程式「%s」的臭蟲。請回報這個問題給 ALSA 開發者。"
-#: spa/plugins/bluez5/bluez5-device.c:1010
-msgid "Audio Gateway (A2DP Source & HSP/HFP AG)"
-msgstr ""
+#: spa/plugins/alsa/acp/channelmap.h:460
+msgid "(invalid)"
+msgstr "(無效)"
-#: spa/plugins/bluez5/bluez5-device.c:1033
+#: spa/plugins/alsa/acp/compat.c:194
+msgid "Built-in Audio"
+msgstr "內部音效"
+
+#: spa/plugins/alsa/acp/compat.c:199
+msgid "Modem"
+msgstr "數據機"
+
+#: spa/plugins/bluez5/bluez5-device.c:2032
+msgid "Audio Gateway (A2DP Source & HSP/HFP AG)"
+msgstr "音訊閘道 (A2DP Source & HSP/HFP AG)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2061
+msgid "Audio Streaming for Hearing Aids (ASHA Sink)"
+msgstr "助聽器的音訊串流 (ASHA Sink)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2104
#, c-format
msgid "High Fidelity Playback (A2DP Sink, codec %s)"
-msgstr ""
+msgstr "高傳真播放裝置 (A2DP Sink,編碼器 %s)"
-#: spa/plugins/bluez5/bluez5-device.c:1035
+#: spa/plugins/bluez5/bluez5-device.c:2107
#, c-format
msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)"
-msgstr ""
+msgstr "高傳真雙工裝置 (A2DP Source/Sink,編碼器 %s)"
-#: spa/plugins/bluez5/bluez5-device.c:1041
+#: spa/plugins/bluez5/bluez5-device.c:2115
msgid "High Fidelity Playback (A2DP Sink)"
-msgstr ""
+msgstr "高傳真播放裝置 (A2DP Sink)"
-#: spa/plugins/bluez5/bluez5-device.c:1043
+#: spa/plugins/bluez5/bluez5-device.c:2117
msgid "High Fidelity Duplex (A2DP Source/Sink)"
-msgstr ""
+msgstr "高傳真雙工裝置 (A2DP Source/Sink)"
-#: spa/plugins/bluez5/bluez5-device.c:1070
+#: spa/plugins/bluez5/bluez5-device.c:2194
+#, c-format
+msgid "High Fidelity Playback (BAP Sink, codec %s)"
+msgstr "高傳真播放裝置 (BAP Sink,編碼器 %s)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2199
+#, c-format
+msgid "High Fidelity Input (BAP Source, codec %s)"
+msgstr "高傳真輸入裝置 (BAP Source,編碼器 %s)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2203
+#, c-format
+msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)"
+msgstr "高傳真雙工裝置 (BAP Source/Sink,編碼器 %s)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2212
+msgid "High Fidelity Playback (BAP Sink)"
+msgstr "高傳真播放裝置 (BAP Sink)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2216
+msgid "High Fidelity Input (BAP Source)"
+msgstr "高傳真輸入裝置 (BAP Source)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2219
+msgid "High Fidelity Duplex (BAP Source/Sink)"
+msgstr "高傳真雙工裝置 (BAP Source/Sink)"
+
+#: spa/plugins/bluez5/bluez5-device.c:2259
#, c-format
msgid "Headset Head Unit (HSP/HFP, codec %s)"
-msgstr ""
+msgstr "耳麥頭戴單元 (HSP/HFP,編碼器 %s)"
-#: spa/plugins/bluez5/bluez5-device.c:1074
-msgid "Headset Head Unit (HSP/HFP)"
-msgstr ""
-
-#: spa/plugins/bluez5/bluez5-device.c:1140
+#: spa/plugins/bluez5/bluez5-device.c:2411
+#: spa/plugins/bluez5/bluez5-device.c:2416
+#: spa/plugins/bluez5/bluez5-device.c:2423
+#: spa/plugins/bluez5/bluez5-device.c:2429
+#: spa/plugins/bluez5/bluez5-device.c:2435
+#: spa/plugins/bluez5/bluez5-device.c:2441
+#: spa/plugins/bluez5/bluez5-device.c:2447
+#: spa/plugins/bluez5/bluez5-device.c:2453
+#: spa/plugins/bluez5/bluez5-device.c:2459
msgid "Handsfree"
msgstr "免持裝置"
-#: spa/plugins/bluez5/bluez5-device.c:1155
-msgid "Headphone"
-msgstr "頭戴式耳機"
+#: spa/plugins/bluez5/bluez5-device.c:2417
+msgid "Handsfree (HFP)"
+msgstr "免持裝置 (HFP)"
-#: spa/plugins/bluez5/bluez5-device.c:1160
+#: spa/plugins/bluez5/bluez5-device.c:2440
msgid "Portable"
msgstr "可攜裝置"
-#: spa/plugins/bluez5/bluez5-device.c:1165
+#: spa/plugins/bluez5/bluez5-device.c:2446
msgid "Car"
msgstr "汽車"
-#: spa/plugins/bluez5/bluez5-device.c:1170
+#: spa/plugins/bluez5/bluez5-device.c:2452
msgid "HiFi"
msgstr "HiFi"
-#: spa/plugins/bluez5/bluez5-device.c:1175
+#: spa/plugins/bluez5/bluez5-device.c:2458
msgid "Phone"
msgstr "手機"
-#: spa/plugins/bluez5/bluez5-device.c:1181
-#, fuzzy
+#: spa/plugins/bluez5/bluez5-device.c:2465
msgid "Bluetooth"
-msgstr "藍牙輸入"
+msgstr "藍牙"
+
+#: spa/plugins/bluez5/bluez5-device.c:2466
+msgid "Bluetooth Handsfree"
+msgstr "藍牙免持裝置"
+
+#~ msgid "Headphone"
+#~ msgstr "頭戴式耳機"
diff --git a/spa/include/spa/param/audio/dsd-utils.h b/spa/include/spa/param/audio/dsd-utils.h
index 2dab7cc19..9af62d99d 100644
--- a/spa/include/spa/param/audio/dsd-utils.h
+++ b/spa/include/spa/param/audio/dsd-utils.h
@@ -44,7 +44,7 @@ spa_format_audio_dsd_parse(const struct spa_pod *format, struct spa_audio_info_d
SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels),
SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position));
if (info->channels > max_position)
- return -ECHRNG;
+ return -EINVAL;
if (position == NULL ||
spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels) {
SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
diff --git a/spa/include/spa/param/audio/layout-types.h b/spa/include/spa/param/audio/layout-types.h
index 33650202a..45cca3a6f 100644
--- a/spa/include/spa/param/audio/layout-types.h
+++ b/spa/include/spa/param/audio/layout-types.h
@@ -87,7 +87,7 @@ spa_audio_layout_info_parse_name(struct spa_audio_layout_info *layout, size_t si
uint32_t i, n_pos;
if (spa_atou32(name+3, &n_pos, 10)) {
if (n_pos > max_position)
- return -ECHRNG;
+ return -EINVAL;
for (i = 0; i < 0x1000 && i < n_pos; i++)
layout->position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
for (; i < n_pos; i++)
@@ -99,7 +99,7 @@ spa_audio_layout_info_parse_name(struct spa_audio_layout_info *layout, size_t si
SPA_FOR_EACH_ELEMENT_VAR(spa_type_audio_layout_info, i) {
if (spa_streq(name, i->name)) {
if (i->layout.n_channels > max_position)
- return -ECHRNG;
+ return -EINVAL;
*layout = i->layout;
return i->layout.n_channels;
}
diff --git a/spa/include/spa/param/audio/raw-json.h b/spa/include/spa/param/audio/raw-json.h
index 6b1b25164..11540a6b0 100644
--- a/spa/include/spa/param/audio/raw-json.h
+++ b/spa/include/spa/param/audio/raw-json.h
@@ -88,14 +88,14 @@ spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size,
} else if (spa_streq(key, SPA_KEY_AUDIO_CHANNELS)) {
if (spa_atou32(val, &v, 0) && (force || info->channels == 0)) {
if (v > max_position)
- return -ECHRNG;
+ return -EINVAL;
info->channels = v;
}
} else if (spa_streq(key, SPA_KEY_AUDIO_LAYOUT)) {
if (force || info->channels == 0) {
if (spa_audio_parse_layout(val, info->position, max_position, &v) > 0) {
if (v > max_position)
- return -ECHRNG;
+ return -EINVAL;
info->channels = v;
SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
}
@@ -105,7 +105,7 @@ spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size,
if (spa_audio_parse_position_n(val, strlen(val), info->position,
max_position, &v) > 0) {
if (v > max_position)
- return -ECHRNG;
+ return -EINVAL;
info->channels = v;
SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
}
diff --git a/spa/include/spa/param/audio/raw-utils.h b/spa/include/spa/param/audio/raw-utils.h
index 3f81b4a92..1e4ae2758 100644
--- a/spa/include/spa/param/audio/raw-utils.h
+++ b/spa/include/spa/param/audio/raw-utils.h
@@ -46,7 +46,7 @@ spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_in
SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels),
SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position));
if (info->channels > max_position)
- return -ECHRNG;
+ return -EINVAL;
if (position == NULL ||
spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels) {
SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
diff --git a/spa/include/spa/pod/body.h b/spa/include/spa/pod/body.h
index 4ab5d8e5f..90eadb82e 100644
--- a/spa/include/spa/pod/body.h
+++ b/spa/include/spa/pod/body.h
@@ -111,8 +111,15 @@ SPA_API_POD_BODY int spa_pod_choice_n_values(uint32_t choice_type, uint32_t *min
SPA_API_POD_BODY int spa_pod_body_from_data(void *data, size_t maxsize, off_t offset, size_t size,
struct spa_pod *pod, const void **body)
{
- if (offset < 0 || offset > (int64_t)UINT32_MAX)
+ if (offset < 0)
return -EINVAL;
+ /* On 32-bit platforms, off_t is a signed 32-bit type (since it tracks pointer
+ * width), and consequently can never exceed UINT32_MAX. Skip the upper-bound
+ * check on 32-bit platforms to avoid a compiler warning. */
+#if __SIZEOF_POINTER__ > 4
+ if (offset > (int64_t)UINT32_MAX)
+ return -EINVAL;
+#endif
if (size < sizeof(struct spa_pod) ||
size > maxsize ||
maxsize - size < (uint32_t)offset)
diff --git a/spa/include/spa/support/cpu.h b/spa/include/spa/support/cpu.h
index c69338855..2faf42154 100644
--- a/spa/include/spa/support/cpu.h
+++ b/spa/include/spa/support/cpu.h
@@ -62,6 +62,7 @@ struct spa_cpu { struct spa_interface iface; };
#define SPA_CPU_FLAG_BMI2 (1<<18) /**< Bit Manipulation Instruction Set 2 */
#define SPA_CPU_FLAG_AVX512 (1<<19) /**< AVX-512 */
#define SPA_CPU_FLAG_SLOW_UNALIGNED (1<<20) /**< unaligned loads/stores are slow */
+#define SPA_CPU_FLAG_SLOW_GATHER (1<<21) /**< gather functions are slow */
/* PPC specific */
#define SPA_CPU_FLAG_ALTIVEC (1<<0) /**< standard */
diff --git a/spa/include/spa/support/system.h b/spa/include/spa/support/system.h
index 07a31a55f..56ceea648 100644
--- a/spa/include/spa/support/system.h
+++ b/spa/include/spa/support/system.h
@@ -41,7 +41,7 @@ struct itimerspec;
#define SPA_TYPE_INTERFACE_System SPA_TYPE_INFO_INTERFACE_BASE "System"
#define SPA_TYPE_INTERFACE_DataSystem SPA_TYPE_INFO_INTERFACE_BASE "DataSystem"
-#define SPA_VERSION_SYSTEM 0
+#define SPA_VERSION_SYSTEM 1
struct spa_system { struct spa_interface iface; };
/* IO events */
@@ -59,11 +59,18 @@ struct spa_system { struct spa_interface iface; };
struct spa_poll_event {
uint32_t events;
- void *data;
+ union {
+ void *data;
+ uint64_t data_u64;
+ };
+#ifdef __x86_64__
+} __attribute__((packed));
+#else
};
+#endif
struct spa_system_methods {
-#define SPA_VERSION_SYSTEM_METHODS 0
+#define SPA_VERSION_SYSTEM_METHODS 1
uint32_t version;
/* read/write/ioctl */
@@ -151,7 +158,7 @@ SPA_API_SYSTEM int spa_system_pollfd_del(struct spa_system *object, int pfd, int
SPA_API_SYSTEM int spa_system_pollfd_wait(struct spa_system *object, int pfd,
struct spa_poll_event *ev, int n_ev, int timeout)
{
- return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, pollfd_wait, 0, pfd, ev, n_ev, timeout);
+ return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, pollfd_wait, 1, pfd, ev, n_ev, timeout);
}
SPA_API_SYSTEM int spa_system_timerfd_create(struct spa_system *object, int clockid, int flags)
diff --git a/spa/include/spa/utils/endian.h b/spa/include/spa/utils/endian.h
index 2d002d453..0793ed412 100644
--- a/spa/include/spa/utils/endian.h
+++ b/spa/include/spa/utils/endian.h
@@ -5,7 +5,7 @@
#ifndef SPA_ENDIAN_H
#define SPA_ENDIAN_H
-#if defined(__FreeBSD__) || defined(__MidnightBSD__)
+#if defined(__MidnightBSD__)
#include
#define bswap_16 bswap16
#define bswap_32 bswap32
diff --git a/spa/include/spa/utils/json-builder.h b/spa/include/spa/utils/json-builder.h
new file mode 100644
index 000000000..b41ea9774
--- /dev/null
+++ b/spa/include/spa/utils/json-builder.h
@@ -0,0 +1,445 @@
+/* Simple Plugin API */
+/* SPDX-FileCopyrightText: Copyright © 2026 Wim Taymans */
+/* SPDX-License-Identifier: MIT */
+
+#ifndef SPA_UTILS_JSON_BUILDER_H
+#define SPA_UTILS_JSON_BUILDER_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#else
+#include
+#endif
+
+#ifndef SPA_API_JSON_BUILDER
+ #ifdef SPA_API_IMPL
+ #define SPA_API_JSON_BUILDER SPA_API_IMPL
+ #else
+ #define SPA_API_JSON_BUILDER static inline
+ #endif
+#endif
+
+/** \defgroup spa_json_builder JSON builder
+ * JSON builder functions
+ */
+
+/**
+ * \addtogroup spa_json_builder
+ * \{
+ */
+
+struct spa_json_builder {
+ FILE *f;
+#define SPA_JSON_BUILDER_FLAG_CLOSE (1<<0)
+#define SPA_JSON_BUILDER_FLAG_INDENT (1<<1)
+#define SPA_JSON_BUILDER_FLAG_SPACE (1<<2)
+#define SPA_JSON_BUILDER_FLAG_PRETTY (SPA_JSON_BUILDER_FLAG_INDENT|SPA_JSON_BUILDER_FLAG_SPACE)
+#define SPA_JSON_BUILDER_FLAG_COLOR (1<<3)
+#define SPA_JSON_BUILDER_FLAG_SIMPLE (1<<4)
+#define SPA_JSON_BUILDER_FLAG_RAW (1<<5)
+ uint32_t flags;
+ uint32_t indent_off;
+ uint32_t level;
+ uint32_t indent;
+ uint32_t count;
+ const char *delim;
+ const char *comma;
+ const char *key_sep;
+#define SPA_JSON_BUILDER_COLOR_NORMAL 0
+#define SPA_JSON_BUILDER_COLOR_KEY 1
+#define SPA_JSON_BUILDER_COLOR_LITERAL 2
+#define SPA_JSON_BUILDER_COLOR_NUMBER 3
+#define SPA_JSON_BUILDER_COLOR_STRING 4
+#define SPA_JSON_BUILDER_COLOR_CONTAINER 5
+ const char *color[8];
+};
+
+SPA_API_JSON_BUILDER int spa_json_builder_file(struct spa_json_builder *b, FILE *f, uint32_t flags)
+{
+ bool color = flags & SPA_JSON_BUILDER_FLAG_COLOR;
+ bool simple = flags & SPA_JSON_BUILDER_FLAG_SIMPLE;
+ bool space = flags & SPA_JSON_BUILDER_FLAG_SPACE;
+ spa_zero(*b);
+ b->f = f;
+ b->flags = flags;
+ b->indent = 2;
+ b->delim = "";
+ b->comma = simple ? space ? "" : " " : ",";
+ b->key_sep = simple ? space ? " =" : "=" : ":";
+ b->color[0] = (color ? SPA_ANSI_RESET : "");
+ b->color[1] = (color ? SPA_ANSI_BRIGHT_BLUE : "");
+ b->color[2] = (color ? SPA_ANSI_BRIGHT_MAGENTA : "");
+ b->color[3] = (color ? SPA_ANSI_BRIGHT_CYAN : "");
+ b->color[4] = (color ? SPA_ANSI_BRIGHT_GREEN : "");
+ b->color[5] = (color ? SPA_ANSI_BRIGHT_YELLOW : "");
+ return 0;
+}
+
+SPA_API_JSON_BUILDER int spa_json_builder_memstream(struct spa_json_builder *b,
+ char **mem, size_t *size, uint32_t flags)
+{
+ FILE *f;
+ spa_zero(*b);
+ if ((f = open_memstream(mem, size)) == NULL)
+ return -errno;
+ return spa_json_builder_file(b, f, flags | SPA_JSON_BUILDER_FLAG_CLOSE);
+}
+
+SPA_API_JSON_BUILDER int spa_json_builder_membuf(struct spa_json_builder *b,
+ char *mem, size_t size, uint32_t flags)
+{
+ FILE *f;
+ spa_zero(*b);
+ if ((f = fmemopen(mem, size, "w")) == NULL)
+ return -errno;
+ return spa_json_builder_file(b, f, flags | SPA_JSON_BUILDER_FLAG_CLOSE);
+}
+
+SPA_API_JSON_BUILDER void spa_json_builder_close(struct spa_json_builder *b)
+{
+ if (b->flags & SPA_JSON_BUILDER_FLAG_CLOSE)
+ fclose(b->f);
+}
+
+SPA_API_JSON_BUILDER int spa_json_builder_encode_string(struct spa_json_builder *b,
+ bool raw, const char *before, const char *val, int size, const char *after)
+{
+ FILE *f = b->f;
+ int i, len;
+ if (raw) {
+ len = fprintf(f, "%s%.*s%s", before, size, val, after) - 1;
+ } else {
+ len = fprintf(f, "%s\"", before);
+ for (i = 0; i < size && val[i]; i++) {
+ char v = val[i];
+ switch (v) {
+ 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", v); break;
+ default:
+ if (v > 0 && v < 0x20)
+ len += fprintf(f, "\\u%04x", v);
+ else
+ len += fprintf(f, "%c", v);
+ break;
+ }
+ }
+ len += fprintf(f, "\"%s", after);
+ }
+ return len-1;
+}
+
+SPA_API_JSON_BUILDER
+void spa_json_builder_add_simple(struct spa_json_builder *b, const char *key, int key_len,
+ char type, const char *val, int val_len)
+{
+ bool indent = b->indent_off == 0 && (b->flags & SPA_JSON_BUILDER_FLAG_INDENT);
+ bool space = b->flags & SPA_JSON_BUILDER_FLAG_SPACE;
+ bool force_raw = b->flags & SPA_JSON_BUILDER_FLAG_RAW;
+ bool raw = true, simple = b->flags & SPA_JSON_BUILDER_FLAG_SIMPLE;
+ int color;
+
+ if (val == NULL || val_len == 0) {
+ val = "null";
+ val_len = 4;
+ type = 'l';
+ }
+ if (type == 0) {
+ if (spa_json_is_container(val, val_len))
+ type = simple ? 'C' : 'S';
+ else if (val_len > 0 && (*val == '}' || *val == ']'))
+ type = 'e';
+ else if (spa_json_is_null(val, val_len) ||
+ spa_json_is_bool(val, val_len))
+ type = 'l';
+ else if (spa_json_is_string(val, val_len))
+ type = 's';
+ else if (spa_json_is_json_number(val, val_len))
+ type = 'd';
+ else if (simple && (spa_json_is_float(val, val_len) ||
+ spa_json_is_int(val, val_len)))
+ type = 'd';
+ else
+ type = 'S';
+ }
+ switch (type) {
+ case 'e':
+ b->level -= b->indent;
+ b->delim = "";
+ break;
+ }
+
+ fprintf(b->f, "%s%s%*s", b->delim, b->count == 0 ? "" : indent ? "\n" : space ? " " : "",
+ indent ? b->level : 0, "");
+ if (key) {
+ bool key_raw = force_raw || (simple && spa_json_make_simple_string(&key, &key_len)) ||
+ spa_json_is_string(key, key_len);
+ spa_json_builder_encode_string(b, key_raw,
+ b->color[1], key, key_len, b->color[0]);
+ fprintf(b->f, "%s%s", b->key_sep, space ? " " : "");
+ }
+ b->delim = b->comma;
+ switch (type) {
+ case 'c':
+ color = SPA_JSON_BUILDER_COLOR_NORMAL;
+ val_len = 1;
+ b->delim = "";
+ b->level += b->indent;
+ if (val[1] == '-') b->indent_off++;
+ break;
+ case 'e':
+ color = SPA_JSON_BUILDER_COLOR_NORMAL;
+ val_len = 1;
+ if (val[1] == '-') b->indent_off--;
+ break;
+ case 'l':
+ color = SPA_JSON_BUILDER_COLOR_LITERAL;
+ break;
+ case 'd':
+ color = SPA_JSON_BUILDER_COLOR_NUMBER;
+ break;
+ case 's':
+ color = SPA_JSON_BUILDER_COLOR_STRING;
+ break;
+ case 'C':
+ color = SPA_JSON_BUILDER_COLOR_CONTAINER;
+ break;
+ default:
+ color = SPA_JSON_BUILDER_COLOR_STRING;
+ raw = force_raw || (simple && spa_json_make_simple_string(&val, &val_len));
+ break;
+ }
+ spa_json_builder_encode_string(b, raw, b->color[color], val, val_len, b->color[0]);
+ b->count++;
+}
+
+SPA_API_JSON_BUILDER void spa_json_builder_object_push(struct spa_json_builder *b,
+ const char *key, const char *val)
+{
+ spa_json_builder_add_simple(b, key, INT_MAX, 'c', val, INT_MAX);
+}
+SPA_API_JSON_BUILDER void spa_json_builder_pop(struct spa_json_builder *b,
+ const char *val)
+{
+ spa_json_builder_add_simple(b, NULL, 0, 'e', val, INT_MAX);
+}
+SPA_API_JSON_BUILDER void spa_json_builder_object_null(struct spa_json_builder *b,
+ const char *key)
+{
+ spa_json_builder_add_simple(b, key, INT_MAX, 'l', "null", 4);
+}
+SPA_API_JSON_BUILDER void spa_json_builder_object_bool(struct spa_json_builder *b,
+ const char *key, bool val)
+{
+ spa_json_builder_add_simple(b, key, INT_MAX, 'l', val ? "true" : "false", INT_MAX);
+}
+SPA_API_JSON_BUILDER void spa_json_builder_object_int(struct spa_json_builder *b,
+ const char *key, int64_t val)
+{
+ char str[128];
+ snprintf(str, sizeof(str), "%" PRIi64, val);
+ spa_json_builder_add_simple(b, key, INT_MAX, 'd', str, INT_MAX);
+}
+SPA_API_JSON_BUILDER void spa_json_builder_object_uint(struct spa_json_builder *b,
+ const char *key, uint64_t val)
+{
+ char str[128];
+ snprintf(str, sizeof(str), "%" PRIu64, val);
+ spa_json_builder_add_simple(b, key, INT_MAX, 'd', str, INT_MAX);
+}
+SPA_API_JSON_BUILDER void spa_json_builder_object_double(struct spa_json_builder *b,
+ const char *key, double val)
+{
+ char str[64];
+ spa_json_format_float(str, sizeof(str), (float)val);
+ spa_json_builder_add_simple(b, key, INT_MAX, 'd', str, INT_MAX);
+}
+SPA_API_JSON_BUILDER void spa_json_builder_object_string(struct spa_json_builder *b,
+ const char *key, const char *val)
+{
+ spa_json_builder_add_simple(b, key, INT_MAX, 'S', val, INT_MAX);
+}
+SPA_API_JSON_BUILDER SPA_PRINTF_FUNC(3,0)
+void spa_json_builder_object_stringv(struct spa_json_builder *b,
+ const char *key, const char *fmt, va_list va)
+{
+ char *val;
+ if (vasprintf(&val, fmt, va) > 0) {
+ spa_json_builder_object_string(b, key, val);
+ free(val);
+ }
+}
+
+SPA_API_JSON_BUILDER SPA_PRINTF_FUNC(3,4)
+void spa_json_builder_object_stringf(struct spa_json_builder *b,
+ const char *key, const char *fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ spa_json_builder_object_stringv(b, key, fmt, va);
+ va_end(va);
+}
+
+SPA_API_JSON_BUILDER void spa_json_builder_object_value_iter(struct spa_json_builder *b,
+ struct spa_json *it, const char *key, int key_len, const char *val, int len)
+{
+ struct spa_json sub;
+ if (spa_json_is_array(val, len)) {
+ spa_json_builder_add_simple(b, key, key_len, 'c', "[", 1);
+ spa_json_enter(it, &sub);
+ while ((len = spa_json_next(&sub, &val)) > 0)
+ spa_json_builder_object_value_iter(b, &sub, NULL, 0, val, len);
+ spa_json_builder_pop(b, "]");
+ }
+ else if (spa_json_is_object(val, len)) {
+ const char *k;
+ int kl;
+ spa_json_builder_add_simple(b, key, key_len, 'c', "{", 1);
+ spa_json_enter(it, &sub);
+ while ((kl = spa_json_next(&sub, &k)) > 0) {
+ if ((len = spa_json_next(&sub, &val)) < 0)
+ break;
+ spa_json_builder_object_value_iter(b, &sub, k, kl, val, len);
+ }
+ spa_json_builder_pop(b, "}");
+ }
+ else {
+ spa_json_builder_add_simple(b, key, key_len, 0, val, len);
+ }
+}
+SPA_API_JSON_BUILDER void spa_json_builder_object_value_full(struct spa_json_builder *b,
+ bool recurse, const char *key, int key_len, const char *val, int val_len)
+{
+ if (!recurse || val == NULL) {
+ spa_json_builder_add_simple(b, key, key_len, 0, val, val_len);
+ } else {
+ struct spa_json it[1];
+ const char *v;
+ if (spa_json_begin(&it[0], val, val_len, &v) >= 0)
+ spa_json_builder_object_value_iter(b, &it[0], key, key_len, val, val_len);
+ }
+}
+SPA_API_JSON_BUILDER void spa_json_builder_object_value(struct spa_json_builder *b,
+ bool recurse, const char *key, const char *val)
+{
+ spa_json_builder_object_value_full(b, recurse, key, key ? strlen(key) : 0,
+ val, val ? strlen(val) : 0);
+}
+SPA_API_JSON_BUILDER SPA_PRINTF_FUNC(4,5)
+void spa_json_builder_object_valuef(struct spa_json_builder *b,
+ bool recurse, const char *key, const char *fmt, ...)
+{
+ va_list va;
+ char *val;
+ va_start(va, fmt);
+ if (vasprintf(&val, fmt, va) > 0) {
+ spa_json_builder_object_value(b, recurse, key, val);
+ free(val);
+ }
+ va_end(va);
+}
+
+
+/* array functions */
+SPA_API_JSON_BUILDER void spa_json_builder_array_push(struct spa_json_builder *b,
+ const char *val)
+{
+ spa_json_builder_object_push(b, NULL, val);
+}
+SPA_API_JSON_BUILDER void spa_json_builder_array_null(struct spa_json_builder *b)
+{
+ spa_json_builder_object_null(b, NULL);
+}
+SPA_API_JSON_BUILDER void spa_json_builder_array_bool(struct spa_json_builder *b,
+ bool val)
+{
+ spa_json_builder_object_bool(b, NULL, val);
+}
+SPA_API_JSON_BUILDER void spa_json_builder_array_int(struct spa_json_builder *b,
+ int64_t val)
+{
+ spa_json_builder_object_int(b, NULL, val);
+}
+SPA_API_JSON_BUILDER void spa_json_builder_array_uint(struct spa_json_builder *b,
+ uint64_t val)
+{
+ spa_json_builder_object_uint(b, NULL, val);
+}
+SPA_API_JSON_BUILDER void spa_json_builder_array_double(struct spa_json_builder *b,
+ double val)
+{
+ spa_json_builder_object_double(b, NULL, val);
+}
+SPA_API_JSON_BUILDER void spa_json_builder_array_string(struct spa_json_builder *b,
+ const char *val)
+{
+ spa_json_builder_object_string(b, NULL, val);
+}
+SPA_API_JSON_BUILDER SPA_PRINTF_FUNC(2,3)
+void spa_json_builder_array_stringf(struct spa_json_builder *b,
+ const char *fmt, ...)
+{
+ va_list va;
+ va_start(va, fmt);
+ spa_json_builder_object_stringv(b, NULL, fmt, va);
+ va_end(va);
+}
+SPA_API_JSON_BUILDER void spa_json_builder_array_value(struct spa_json_builder *b,
+ bool recurse, const char *val)
+{
+ spa_json_builder_object_value(b, recurse, NULL, val);
+}
+SPA_API_JSON_BUILDER SPA_PRINTF_FUNC(3,4)
+void spa_json_builder_array_valuef(struct spa_json_builder *b, bool recurse, const char *fmt, ...)
+{
+ va_list va;
+ char *val;
+ va_start(va, fmt);
+ if (vasprintf(&val, fmt, va) > 0) {
+ spa_json_builder_object_value(b, recurse, NULL, val);
+ free(val);
+ }
+ va_end(va);
+}
+
+SPA_API_JSON_BUILDER char *spa_json_builder_reformat(const char *json, uint32_t flags)
+{
+ struct spa_json_builder b;
+ char *mem;
+ size_t size;
+ int res;
+ if ((res = spa_json_builder_memstream(&b, &mem, &size, flags)) < 0) {
+ errno = -res;
+ return NULL;
+ }
+ spa_json_builder_array_value(&b, true, json);
+ spa_json_builder_close(&b);
+ return mem;
+}
+
+/**
+ * \}
+ */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* SPA_UTILS_JSON_BUILDER_H */
diff --git a/spa/include/spa/utils/json-core.h b/spa/include/spa/utils/json-core.h
index 5616bffe1..9745000cf 100644
--- a/spa/include/spa/utils/json-core.h
+++ b/spa/include/spa/utils/json-core.h
@@ -232,7 +232,7 @@ SPA_API_JSON int spa_json_next(struct spa_json * iter, const char **value)
switch (cur) {
case '\0':
case '\t': case ' ': case '\r': case '\n':
- case '"': case '#':
+ case '"': case '#': case '{': case '[':
case ':': case ',': case '=': case ']': case '}':
iter->state = __STRUCT | flag;
if (iter->depth > 0)
@@ -399,6 +399,10 @@ SPA_API_JSON int spa_json_is_container(const char *val, int len)
{
return len > 0 && (*val == '{' || *val == '[');
}
+SPA_API_JSON int spa_json_is_container_end(const char *val, int len)
+{
+ return len > 0 && (*val == '}' || *val == ']');
+}
/* object */
SPA_API_JSON int spa_json_is_object(const char *val, int len)
@@ -421,20 +425,11 @@ SPA_API_JSON bool spa_json_is_null(const char *val, int len)
/* float */
SPA_API_JSON int spa_json_parse_float(const char *val, int len, float *result)
{
- char buf[96];
- char *end;
- int pos;
+ char buf[96], *end;
if (len <= 0 || len >= (int)sizeof(buf))
return 0;
- for (pos = 0; pos < len; ++pos) {
- switch (val[pos]) {
- case '+': case '-': case '0' ... '9': case '.': case 'e': case 'E': break;
- default: return 0;
- }
- }
-
memcpy(buf, val, len);
buf[len] = '\0';
@@ -462,8 +457,7 @@ SPA_API_JSON char *spa_json_format_float(char *str, int size, float val)
/* int */
SPA_API_JSON int spa_json_parse_int(const char *val, int len, int *result)
{
- char buf[64];
- char *end;
+ char buf[64], *end;
if (len <= 0 || len >= (int)sizeof(buf))
return 0;
@@ -480,6 +474,39 @@ SPA_API_JSON bool spa_json_is_int(const char *val, int len)
return spa_json_parse_int(val, len, &dummy);
}
+SPA_API_JSON bool spa_json_is_json_number(const char *val, int len)
+{
+ static const int8_t trans[9][7] = {
+ /* '1-9' '0' '-' '+' '.' 'eE' other */
+ /* 0 */ {-1, -1, -1, -1, 6, 7, -1 }, /* after '0' */
+ /* 1 */ { 1, 1, -1, -1, 6, 7, -1 }, /* in integer */
+ /* 2 */ { 2, 2, -1, -1, -1, 7, -1 }, /* in fraction */
+ /* 3 */ { 3, 3, -1, -1, -1, -1, -1 }, /* in exponent */
+ /* 4 */ { 1, 0, 5, -1, -1, -1, -1 }, /* start */
+ /* 5 */ { 1, 0, -1, -1, -1, -1, -1 }, /* after '-' */
+ /* 6 */ { 2, 2, -1, -1, -1, -1, -1 }, /* after '.' */
+ /* 7 */ { 3, 3, 8, 8, -1, -1, -1 }, /* after 'e'/'E' */
+ /* 8 */ { 3, 3, -1, -1, -1, -1, -1 }, /* after exp sign */
+ };
+ static const int8_t char_class[128] = {
+ 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, /* 0-15 */
+ 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, /* 16-31 */
+ 6,6,6,6,6,6,6,6,6,6,6,3,6,2,4,6, /* 32-47: + - . */
+ 1,0,0,0,0,0,0,0,0,0,6,6,6,6,6,6, /* 48-63: 0-9 */
+ 6,6,6,6,6,5,6,6,6,6,6,6,6,6,6,6, /* 64-79: E */
+ 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, /* 80-95 */
+ 6,6,6,6,6,5,6,6,6,6,6,6,6,6,6,6, /* 96-111: e */
+ 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, /* 112-127 */
+ };
+ int i, state = 4;
+
+ for (i = 0; i < len; i++) {
+ if ((state = trans[state][char_class[val[i]&0x7f]]) < 0)
+ return false;
+ }
+ return state < 4;
+}
+
/* bool */
SPA_API_JSON bool spa_json_is_true(const char *val, int len)
{
@@ -510,6 +537,46 @@ SPA_API_JSON bool spa_json_is_string(const char *val, int len)
{
return len > 1 && *val == '"';
}
+SPA_API_JSON bool spa_json_is_simple_string(const char *val, int size)
+{
+ int i;
+ static const char *REJECT = "\"\\'=:,{}[]()#";
+ for (i = 0; i < size && val[i]; i++) {
+ if (val[i] <= 0x20 || strchr(REJECT, val[i]) != NULL)
+ return false;
+ }
+ return true;
+}
+SPA_API_JSON bool spa_json_make_simple_string(const char **val, int *len)
+{
+ int i, l = *len;
+ const char *v = *val;
+ static const char *REJECT = "\"\\'=:,{}[]()#";
+ int trimmed = 0, bad = 0;
+ for (i = 0; i < l && v[i]; i++) {
+ if (i == 0 && v[0] == '\"')
+ trimmed++;
+ else if ((i+1 == l || !v[i+1]) && v[i] == '\"')
+ trimmed++;
+ else if (v[i] <= 0x20 || strchr(REJECT, v[i]) != NULL)
+ bad++;
+ }
+ if (trimmed == 0 && bad == 0 && i > 0)
+ return true;
+ else if (trimmed == 2) {
+ if (bad == 0 && i > 2 &&
+ !spa_json_is_null(&v[1], i-2) &&
+ !spa_json_is_bool(&v[1], i-2) &&
+ !spa_json_is_float(&v[1], i-2) &&
+ !spa_json_is_container(&v[1], i-2) &&
+ !spa_json_is_container_end(&v[1], i-2)) {
+ (*len) = i-2;
+ (*val)++;
+ }
+ return true;
+ }
+ return false;
+}
SPA_API_JSON int spa_json_parse_hex(const char *p, int num, uint32_t *res)
{
diff --git a/spa/include/spa/utils/string.h b/spa/include/spa/utils/string.h
index bab60de2b..35e4ea026 100644
--- a/spa/include/spa/utils/string.h
+++ b/spa/include/spa/utils/string.h
@@ -380,17 +380,26 @@ SPA_API_STRING void spa_strbuf_init(struct spa_strbuf *buf, char *buffer, size_t
buf->buffer[0] = '\0';
}
+SPA_PRINTF_FUNC(2, 0)
+SPA_API_STRING int spa_strbuf_appendv(struct spa_strbuf *buf, const char *fmt, va_list args)
+{
+ size_t remain = buf->maxsize - buf->pos;
+ int written = vsnprintf(&buf->buffer[buf->pos], remain, fmt, args);
+ if (written > 0)
+ buf->pos += SPA_MIN(remain, (size_t)written);
+ return written;
+}
+
SPA_PRINTF_FUNC(2, 3)
SPA_API_STRING int spa_strbuf_append(struct spa_strbuf *buf, const char *fmt, ...)
{
- size_t remain = buf->maxsize - buf->pos;
- ssize_t written;
va_list args;
+ int written;
+
va_start(args, fmt);
- written = vsnprintf(&buf->buffer[buf->pos], remain, fmt, args);
+ written = spa_strbuf_appendv(buf, fmt, args);
va_end(args);
- if (written > 0)
- buf->pos += SPA_MIN(remain, (size_t)written);
+
return written;
}
diff --git a/spa/lib/lib.c b/spa/lib/lib.c
index 0aa35fae3..3ded776ad 100644
--- a/spa/lib/lib.c
+++ b/spa/lib/lib.c
@@ -2,7 +2,6 @@
#undef SPA_AUDIO_MAX_CHANNELS
#define SPA_API_IMPL SPA_EXPORT
-#include
#include
#include
#include
@@ -126,16 +125,17 @@
#include
#include
#include
+#include
#include
#include
#include
#include
#include
#include
-#include
#include
#include
#include
+#include
#include
#include
#include
@@ -158,6 +158,7 @@
#include
#include
#include
+#include
#include
#include
#include
diff --git a/spa/plugins/aec/aec-webrtc.cpp b/spa/plugins/aec/aec-webrtc.cpp
index 7b8cafcde..2e451d851 100644
--- a/spa/plugins/aec/aec-webrtc.cpp
+++ b/spa/plugins/aec/aec-webrtc.cpp
@@ -39,7 +39,7 @@ struct impl_data {
std::unique_ptr play_buffer, rec_buffer, out_buffer;
};
-SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.eac.webrtc");
+SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.aec.webrtc");
#undef SPA_LOG_TOPIC_DEFAULT
#define SPA_LOG_TOPIC_DEFAULT &log_topic
@@ -221,6 +221,11 @@ static int webrtc_init2(void *object, const struct spa_dict *args,
}};
#endif
+ if (out_info->channels != 1 && rec_info->channels != out_info->channels) {
+ spa_log_error(impl->log, "Source channels must be equal to capture channels or 1");
+ return -EINVAL;
+ }
+
#if defined(HAVE_WEBRTC)
auto apm = std::unique_ptr(webrtc::AudioProcessing::Create(config));
#elif defined(HAVE_WEBRTC1)
diff --git a/spa/plugins/alsa/acp-tool.c b/spa/plugins/alsa/acp-tool.c
index c12401100..824d69df6 100644
--- a/spa/plugins/alsa/acp-tool.c
+++ b/spa/plugins/alsa/acp-tool.c
@@ -10,7 +10,9 @@
#include
#include
#include
+#ifdef __linux__
#include
+#endif
#include
#include
diff --git a/spa/plugins/alsa/acp/acp.c b/spa/plugins/alsa/acp/acp.c
index bee8d1ef4..8f5ea18c2 100644
--- a/spa/plugins/alsa/acp/acp.c
+++ b/spa/plugins/alsa/acp/acp.c
@@ -485,13 +485,11 @@ static int add_pro_profile(pa_card *impl, uint32_t index)
if ((n_capture == 1 && n_playback == 1) || is_firewire) {
PA_IDXSET_FOREACH(m, ap->output_mappings, idx) {
pa_proplist_setf(m->output_proplist, "node.group", "pro-audio-%u", index);
- pa_proplist_setf(m->output_proplist, "node.link-group", "pro-audio-%u", index);
pa_proplist_setf(m->output_proplist, "api.alsa.auto-link", "true");
pa_proplist_setf(m->output_proplist, "api.alsa.disable-tsched", "true");
}
PA_IDXSET_FOREACH(m, ap->input_mappings, idx) {
pa_proplist_setf(m->input_proplist, "node.group", "pro-audio-%u", index);
- pa_proplist_setf(m->input_proplist, "node.link-group", "pro-audio-%u", index);
pa_proplist_setf(m->input_proplist, "api.alsa.auto-link", "true");
pa_proplist_setf(m->input_proplist, "api.alsa.disable-tsched", "true");
}
diff --git a/spa/plugins/alsa/acp/compat.h b/spa/plugins/alsa/acp/compat.h
index f7592e1a6..87151d197 100644
--- a/spa/plugins/alsa/acp/compat.h
+++ b/spa/plugins/alsa/acp/compat.h
@@ -429,14 +429,14 @@ static PA_PRINTF_FUNC(1,0) inline char *pa_vsprintf_malloc(const char *fmt, va_l
#define pa_fopen_cloexec(f,m) fopen(f,m"e")
-static inline char *pa_path_get_filename(const char *p)
+static inline const char *pa_path_get_filename(const char *p)
{
- char *fn;
+ const char *fn;
if (!p)
return NULL;
if ((fn = strrchr(p, PA_PATH_SEP_CHAR)))
return fn+1;
- return (char*) p;
+ return p;
}
static inline bool pa_is_path_absolute(const char *fn)
diff --git a/spa/plugins/alsa/alsa-seq-bridge.c b/spa/plugins/alsa/alsa-seq-bridge.c
index 06c5bc28f..d5b0019a8 100644
--- a/spa/plugins/alsa/alsa-seq-bridge.c
+++ b/spa/plugins/alsa/alsa-seq-bridge.c
@@ -227,7 +227,7 @@ static void emit_port_info(struct seq_state *this, struct seq_port *port, bool f
if (full)
port->info.change_mask = port->info_all;
if (port->info.change_mask) {
- struct spa_dict_item items[6];
+ struct spa_dict_item items[7];
uint32_t n_items = 0;
int card_id;
snd_seq_port_info_t *info;
@@ -284,6 +284,9 @@ static void emit_port_info(struct seq_state *this, struct seq_port *port, bool f
snprintf(card, sizeof(card), "%d", card_id);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_CARD, card);
}
+ if (this->ump)
+ items[n_items++] = SPA_DICT_ITEM_INIT("control.ump", "true");
+
port->info.props = &SPA_DICT_INIT(items, n_items);
spa_node_emit_port_info(&this->hooks,
@@ -501,6 +504,7 @@ impl_node_port_enum_params(void *object, int seq,
struct seq_state *this = object;
struct seq_port *port;
struct spa_pod *param;
+ struct spa_pod_frame f[1];
struct spa_pod_builder b = { 0 };
uint8_t buffer[1024];
struct spa_result_node_params result;
@@ -524,10 +528,18 @@ impl_node_port_enum_params(void *object, int seq,
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_pod_builder_push_object(&b, &f[0],
+ SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
+ spa_pod_builder_add(&b,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application),
- SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control));
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control),
+ 0);
+ if (port->control_types != 0) {
+ spa_pod_builder_add(&b,
+ SPA_FORMAT_CONTROL_types, SPA_POD_Int(port->control_types),
+ 0);
+ }
+ param = spa_pod_builder_pop(&b, &f[0]);
break;
case SPA_PARAM_Format:
@@ -535,10 +547,18 @@ impl_node_port_enum_params(void *object, int seq,
return -EIO;
if (result.index > 0)
return 0;
- param = spa_pod_builder_add_object(&b,
- SPA_TYPE_OBJECT_Format, SPA_PARAM_Format,
+ spa_pod_builder_push_object(&b, &f[0],
+ SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat);
+ spa_pod_builder_add(&b,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application),
- SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control));
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control),
+ 0);
+ if (port->control_types != 0) {
+ spa_pod_builder_add(&b,
+ SPA_FORMAT_CONTROL_types, SPA_POD_Int(port->control_types),
+ 0);
+ }
+ param = spa_pod_builder_pop(&b, &f[0]);
break;
case SPA_PARAM_Buffers:
@@ -955,7 +975,7 @@ impl_init(const struct spa_handle_factory *factory,
this->quantum_limit = 8192;
this->min_pool_size = 500;
this->max_pool_size = 2000;
- this->ump = true;
+ this->ump = false;
for (i = 0; info && i < info->n_items; i++) {
const char *k = info->items[i].key;
diff --git a/spa/plugins/alsa/alsa-seq.c b/spa/plugins/alsa/alsa-seq.c
index 8a4ba369c..c25b49420 100644
--- a/spa/plugins/alsa/alsa-seq.c
+++ b/spa/plugins/alsa/alsa-seq.c
@@ -620,9 +620,8 @@ static int process_read(struct seq_state *state)
{
struct seq_stream *stream = &state->streams[SPA_DIRECTION_OUTPUT];
const bool ump = state->ump;
- uint32_t *data;
+ void *data;
uint8_t midi1_data[MAX_EVENT_SIZE];
- uint32_t ump_data[MAX_EVENT_SIZE];
long size;
int res = -1;
struct seq_port *port;
@@ -633,9 +632,6 @@ static int process_read(struct seq_state *state)
uint64_t ev_time, diff;
uint32_t offset;
void *event;
- uint8_t *midi1_ptr;
- size_t midi1_size = 0;
- uint64_t ump_state = 0;
snd_seq_event_type_t SPA_UNUSED type;
if (ump) {
@@ -702,7 +698,7 @@ static int process_read(struct seq_state *state)
#ifdef HAVE_ALSA_UMP
snd_seq_ump_event_t *ev = event;
- data = (uint32_t*)&ev->ump[0];
+ data = &ev->ump[0];
size = spa_ump_message_size(snd_ump_msg_hdr_type(ev->ump[0])) * 4;
#else
spa_assert_not_reached();
@@ -715,34 +711,21 @@ static int process_read(struct seq_state *state)
spa_log_warn(state->log, "decode failed: %s", snd_strerror(size));
continue;
}
-
- midi1_ptr = midi1_data;
- midi1_size = size;
+ data = midi1_data;
}
- do {
- if (!ump) {
- data = ump_data;
- size = spa_ump_from_midi(&midi1_ptr, &midi1_size,
- ump_data, sizeof(ump_data), 0, &ump_state);
- if (size <= 0)
- break;
- }
+ spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d",
+ type, ev_time, offset, size, addr->client, addr->port);
- spa_log_trace_fp(state->log, "event %d time:%"PRIu64" offset:%d size:%ld port:%d.%d",
- type, ev_time, offset, size, addr->client, addr->port);
+ spa_pod_builder_control(&port->builder, offset, ump ? SPA_CONTROL_UMP : SPA_CONTROL_Midi );
+ spa_pod_builder_bytes(&port->builder, data, size);
- spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP);
- spa_pod_builder_bytes(&port->builder, data, size);
-
- /* make sure we can fit at least one control event of max size otherwise
- * we keep the event in the queue and try to copy it in the next cycle */
- if (port->builder.state.offset +
- sizeof(struct spa_pod_control) +
- MAX_EVENT_SIZE > port->buffer->buf->datas[0].maxsize)
- goto done;
-
- } while (!ump);
+ /* make sure we can fit at least one control event of max size otherwise
+ * we keep the event in the queue and try to copy it in the next cycle */
+ if (port->builder.state.offset +
+ sizeof(struct spa_pod_control) +
+ MAX_EVENT_SIZE > port->buffer->buf->datas[0].maxsize)
+ goto done;
}
done:
@@ -819,7 +802,6 @@ static int process_write(struct seq_state *state)
const void *c_body;
uint64_t out_time;
snd_seq_real_time_t out_rt;
- bool first = true;
if (io->status != SPA_STATUS_HAVE_DATA ||
io->buffer_id >= port->n_buffers)
@@ -844,9 +826,6 @@ static int process_write(struct seq_state *state)
size_t body_size;
uint8_t *body;
- if (c.type != SPA_CONTROL_UMP)
- continue;
-
body = (uint8_t*)c_body;
body_size = c.value.size;
@@ -861,6 +840,9 @@ static int process_write(struct seq_state *state)
#ifdef HAVE_ALSA_UMP
snd_seq_ump_event_t ev;
+ if (c.type != SPA_CONTROL_UMP)
+ continue;
+
snd_seq_ump_ev_clear(&ev);
snd_seq_ev_set_ump_data(&ev, body, SPA_MIN(sizeof(ev.ump), (size_t)body_size));
snd_seq_ev_set_source(&ev, state->event.addr.port);
@@ -878,26 +860,26 @@ static int process_write(struct seq_state *state)
#endif
} else {
snd_seq_event_t ev;
- uint8_t data[MAX_EVENT_SIZE];
- int size;
- uint64_t st = 0;
+ int size = 0;
+ long s;
+
+ if (c.type != SPA_CONTROL_Midi)
+ continue;
while (body_size > 0) {
- if ((size = spa_ump_to_midi((const uint32_t **)&body, &body_size,
- data, sizeof(data), &st)) <= 0)
- break;
-
- if (first)
+ if (size == 0)
snd_seq_ev_clear(&ev);
- if ((size = snd_midi_event_encode(stream->codec, data, size, &ev)) < 0) {
+ if ((s = snd_midi_event_encode(stream->codec, body, body_size, &ev)) < 0) {
spa_log_warn(state->log, "failed to encode event: %s",
snd_strerror(size));
snd_midi_event_reset_encode(stream->codec);
- first = true;
+ size = 0;
continue;
}
- first = false;
+ body += s;
+ body_size -= s;
+ size += s;
if (ev.type == SND_SEQ_EVENT_NONE)
/* this can happen when the event is not complete yet, like
* a sysex message and we need to encode some more data. */
@@ -913,7 +895,7 @@ static int process_write(struct seq_state *state)
spa_log_warn(state->log, "failed to output event: %s",
snd_strerror(err));
}
- first = true;
+ size = 0;
}
}
}
diff --git a/spa/plugins/alsa/alsa-seq.h b/spa/plugins/alsa/alsa-seq.h
index 354fa1d5d..3100b9587 100644
--- a/spa/plugins/alsa/alsa-seq.h
+++ b/spa/plugins/alsa/alsa-seq.h
@@ -82,6 +82,8 @@ struct seq_port {
struct spa_pod_builder builder;
struct spa_pod_frame frame;
+ uint32_t control_types;
+
struct spa_audio_info current_format;
unsigned int have_format:1;
unsigned int active:1;
diff --git a/spa/plugins/alsa/alsa-udev.c b/spa/plugins/alsa/alsa-udev.c
index 8537c9760..9c3e12f20 100644
--- a/spa/plugins/alsa/alsa-udev.c
+++ b/spa/plugins/alsa/alsa-udev.c
@@ -48,6 +48,7 @@ struct card {
unsigned int accessible:1;
unsigned int ignored:1;
unsigned int emitted:1;
+ unsigned int wireless_disconnected:1;
/* Local SPA object IDs. (Global IDs are produced by PipeWire
* out of this using its registry.) Compress-Offload or PCM
@@ -59,6 +60,10 @@ struct card {
* is used because 0 is a valid ALSA card number. */
uint32_t pcm_device_id;
uint32_t compress_offload_device_id;
+
+ /* Syspath of the USB interface that has wireless_status (e.g.
+ * /sys/devices/.../1-5:1.3). Empty string when not applicable. */
+ char wireless_status_syspath[256];
};
static uint32_t calc_pcm_device_id(struct card *card)
@@ -92,6 +97,8 @@ struct impl {
struct spa_source source;
struct spa_source notify;
+ struct udev_monitor *usb_umonitor;
+ struct spa_source usb_source;
unsigned int use_acp:1;
unsigned int expose_busy:1;
int use_ucm;
@@ -353,6 +360,87 @@ static int check_udev_environment(struct udev *udev, const char *devname)
return ret;
}
+static void check_wireless_status(struct impl *this, struct card *card)
+{
+ char path[PATH_MAX];
+ char buf[32];
+ size_t sz;
+ bool was_disconnected;
+
+ if (card->wireless_status_syspath[0] == '\0')
+ return;
+
+ was_disconnected = card->wireless_disconnected;
+
+ spa_scnprintf(path, sizeof(path), "%s/wireless_status", card->wireless_status_syspath);
+
+ spa_autoptr(FILE) f = fopen(path, "re");
+ if (f == NULL)
+ return;
+
+ sz = fread(buf, 1, sizeof(buf) - 1, f);
+ buf[sz] = '\0';
+
+ card->wireless_disconnected = spa_strstartswith(buf, "disconnected");
+
+ if (card->wireless_disconnected != was_disconnected)
+ spa_log_info(this->log, "card %u: wireless headset %s",
+ card->card_nr,
+ card->wireless_disconnected ? "disconnected" : "connected");
+}
+
+static void find_wireless_status(struct impl *this, struct card *card)
+{
+ const char *bus, *parent_syspath, *parent_sysname;
+ struct udev_device *parent;
+ struct dirent *entry;
+ char path[PATH_MAX];
+ size_t sysname_len;
+
+ bus = udev_device_get_property_value(card->udev_device, "ID_BUS");
+ if (!spa_streq(bus, "usb"))
+ return;
+
+ /* udev_device_get_parent_* returns a borrowed reference owned by the child; do not unref it. */
+ parent = udev_device_get_parent_with_subsystem_devtype(card->udev_device, "usb", "usb_device");
+ if (parent == NULL)
+ return;
+
+ parent_syspath = udev_device_get_syspath(parent);
+ parent_sysname = udev_device_get_sysname(parent);
+ if (parent_syspath == NULL || parent_sysname == NULL)
+ return;
+
+ sysname_len = strlen(parent_sysname);
+
+ spa_autoptr(DIR) dir = opendir(parent_syspath);
+ if (dir == NULL)
+ return;
+
+ while ((entry = readdir(dir)) != NULL) {
+ /* USB interface directories are named ":." */
+ if (strncmp(entry->d_name, parent_sysname, sysname_len) != 0 ||
+ entry->d_name[sysname_len] != ':')
+ continue;
+
+ spa_scnprintf(path, sizeof(path), "%s/%s/wireless_status",
+ parent_syspath, entry->d_name);
+ if (access(path, R_OK) < 0)
+ continue;
+
+ spa_scnprintf(card->wireless_status_syspath,
+ sizeof(card->wireless_status_syspath),
+ "%s/%s", parent_syspath, entry->d_name);
+
+ check_wireless_status(this, card);
+
+ spa_log_debug(this->log, "card %u: found wireless_status at %s (%s)",
+ card->card_nr, card->wireless_status_syspath,
+ card->wireless_disconnected ? "disconnected" : "connected");
+ return;
+ }
+}
+
static int check_pcm_device_availability(struct impl *this, struct card *card,
int *num_pcm_devices)
{
@@ -538,6 +626,9 @@ static int emit_added_object_info(struct impl *this, struct card *card)
if ((str = udev_device_get_property_value(udev_device, "ACP_PROFILE_SET")) && *str)
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PROFILE_SET, str);
+ if ((str = udev_device_get_property_value(udev_device, "ACP_IGNORE_DB")) && *str)
+ items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_IGNORE_DB, str);
+
if ((str = udev_device_get_property_value(udev_device, "SOUND_CLASS")) && *str)
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_CLASS, str);
@@ -731,7 +822,11 @@ static void process_card(struct impl *this, enum action action, struct card *car
return;
check_access(this, card);
- if (card->accessible && !card->emitted) {
+ check_wireless_status(this, card);
+
+ bool effective_accessible = card->accessible && !card->wireless_disconnected;
+
+ if (effective_accessible && !card->emitted) {
int res = emit_added_object_info(this, card);
if (res < 0) {
if (card->ignored)
@@ -750,7 +845,7 @@ static void process_card(struct impl *this, enum action action, struct card *car
card->card_nr);
card->unavailable = false;
}
- } else if (!card->accessible && card->emitted) {
+ } else if (!effective_accessible && card->emitted) {
card->emitted = false;
if (card->pcm_device_id != ID_DEVICE_NOT_SUPPORTED)
@@ -787,8 +882,11 @@ static void process_udev_device(struct impl *this, enum action action, struct ud
return;
card = find_card(this, card_nr);
- if (action == ACTION_CHANGE && !card)
+ if (action == ACTION_CHANGE && !card) {
card = add_card(this, card_nr, udev_device);
+ if (card)
+ find_wireless_status(this, card);
+ }
if (!card)
return;
@@ -915,6 +1013,41 @@ static void impl_on_fd_events(struct spa_source *source)
udev_device_unref(udev_device);
}
+static void impl_on_usb_events(struct spa_source *source)
+{
+ struct impl *this = source->data;
+ struct udev_device *udev_device;
+ const char *action, *syspath;
+ unsigned int i;
+
+ udev_device = udev_monitor_receive_device(this->usb_umonitor);
+ if (udev_device == NULL)
+ return;
+
+ if ((action = udev_device_get_action(udev_device)) == NULL)
+ action = "change";
+
+ if (!spa_streq(action, "change"))
+ goto done;
+
+ syspath = udev_device_get_syspath(udev_device);
+ if (syspath == NULL)
+ goto done;
+
+ for (i = 0; i < this->n_cards; i++) {
+ struct card *card = &this->cards[i];
+ if (card->wireless_status_syspath[0] == '\0')
+ continue;
+ if (!spa_streq(card->wireless_status_syspath, syspath))
+ continue;
+ spa_log_debug(this->log, "wireless_status change for card %u", card->card_nr);
+ process_card(this, ACTION_CHANGE, card);
+ }
+
+done:
+ udev_device_unref(udev_device);
+}
+
static int start_monitor(struct impl *this)
{
int res;
@@ -938,6 +1071,20 @@ static int start_monitor(struct impl *this)
spa_log_debug(this->log, "monitor %p", this->umonitor);
spa_loop_add_source(this->main_loop, &this->source);
+ this->usb_umonitor = udev_monitor_new_from_netlink(this->udev, "udev");
+ if (this->usb_umonitor != NULL) {
+ udev_monitor_filter_add_match_subsystem_devtype(this->usb_umonitor,
+ "usb", "usb_interface");
+ udev_monitor_enable_receiving(this->usb_umonitor);
+
+ this->usb_source.func = impl_on_usb_events;
+ this->usb_source.data = this;
+ this->usb_source.fd = udev_monitor_get_fd(this->usb_umonitor);
+ this->usb_source.mask = SPA_IO_IN | SPA_IO_ERR;
+
+ spa_loop_add_source(this->main_loop, &this->usb_source);
+ }
+
if ((res = start_inotify(this)) < 0)
return res;
@@ -955,6 +1102,12 @@ static int stop_monitor(struct impl *this)
udev_monitor_unref(this->umonitor);
this->umonitor = NULL;
+ if (this->usb_umonitor != NULL) {
+ spa_loop_remove_source(this->main_loop, &this->usb_source);
+ udev_monitor_unref(this->usb_umonitor);
+ this->usb_umonitor = NULL;
+ }
+
stop_inotify(this);
return 0;
diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c
index e9355ca41..482200854 100644
--- a/spa/plugins/audioconvert/audioconvert.c
+++ b/spa/plugins/audioconvert/audioconvert.c
@@ -16,6 +16,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -268,6 +269,7 @@ struct impl {
struct spa_list active_graphs;
struct filter_graph graphs[MAX_GRAPH];
struct spa_process_latency_info latency;
+ char *graph_descs[MAX_GRAPH];
int in_filter_props;
int filter_props_count;
@@ -722,6 +724,34 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index,
SPA_PROP_INFO_params, SPA_POD_Bool(true));
break;
case 19:
+ *param = spa_pod_builder_add_object(b,
+ SPA_TYPE_OBJECT_PropInfo, id,
+ SPA_PROP_INFO_name, SPA_POD_String("channelmix.center-level"),
+ SPA_PROP_INFO_description, SPA_POD_String("Center up/downmix level"),
+ SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(
+ this->mix.center_level, 0.0, 10.0),
+ SPA_PROP_INFO_params, SPA_POD_Bool(true));
+ break;
+ case 20:
+ *param = spa_pod_builder_add_object(b,
+ SPA_TYPE_OBJECT_PropInfo, id,
+ SPA_PROP_INFO_name, SPA_POD_String("channelmix.surround-level"),
+ SPA_PROP_INFO_description, SPA_POD_String("Surround up/downmix level"),
+ SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(
+ this->mix.surround_level, 0.0, 10.0),
+ SPA_PROP_INFO_params, SPA_POD_Bool(true));
+ break;
+ case 21:
+ *param = spa_pod_builder_add_object(b,
+ SPA_TYPE_OBJECT_PropInfo, id,
+ SPA_PROP_INFO_name, SPA_POD_String("channelmix.lfe-level"),
+ SPA_PROP_INFO_description, SPA_POD_String("LFE up/downmix level"),
+ SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(
+ this->mix.lfe_level, 0.0, 10.0),
+ SPA_PROP_INFO_params, SPA_POD_Bool(true));
+ break;
+
+ case 22:
*param = spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_name, SPA_POD_String("channelmix.hilbert-taps"),
@@ -730,7 +760,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index,
this->mix.hilbert_taps, 0, MAX_TAPS),
SPA_PROP_INFO_params, SPA_POD_Bool(true));
break;
- case 20:
+ case 23:
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id);
spa_pod_builder_add(b,
SPA_PROP_INFO_name, SPA_POD_String("channelmix.upmix-method"),
@@ -749,14 +779,14 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index,
spa_pod_builder_pop(b, &f[1]);
*param = spa_pod_builder_pop(b, &f[0]);
break;
- case 21:
+ case 24:
*param = spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_rate),
SPA_PROP_INFO_description, SPA_POD_String("Rate scaler"),
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Double(p->rate, 0.0, 10.0));
break;
- case 22:
+ case 25:
*param = spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_quality),
@@ -765,7 +795,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index,
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(p->resample_quality, 0, 14),
SPA_PROP_INFO_params, SPA_POD_Bool(true));
break;
- case 23:
+ case 26:
*param = spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_name, SPA_POD_String("resample.disable"),
@@ -773,7 +803,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index,
SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->resample_disabled),
SPA_PROP_INFO_params, SPA_POD_Bool(true));
break;
- case 24:
+ case 27:
*param = spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_name, SPA_POD_String("dither.noise"),
@@ -781,7 +811,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index,
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Int(this->dir[1].conv.noise_bits, 0, 16),
SPA_PROP_INFO_params, SPA_POD_Bool(true));
break;
- case 25:
+ case 28:
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_PropInfo, id);
spa_pod_builder_add(b,
SPA_PROP_INFO_name, SPA_POD_String("dither.method"),
@@ -799,7 +829,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index,
spa_pod_builder_pop(b, &f[1]);
*param = spa_pod_builder_pop(b, &f[0]);
break;
- case 26:
+ case 29:
*param = spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_name, SPA_POD_String("debug.wav-path"),
@@ -807,7 +837,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index,
SPA_PROP_INFO_type, SPA_POD_String(p->wav_path),
SPA_PROP_INFO_params, SPA_POD_Bool(true));
break;
- case 27:
+ case 30:
*param = spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_name, SPA_POD_String("channelmix.lock-volumes"),
@@ -815,7 +845,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index,
SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->lock_volumes),
SPA_PROP_INFO_params, SPA_POD_Bool(true));
break;
- case 28:
+ case 31:
*param = spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph.disable"),
@@ -823,7 +853,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index,
SPA_PROP_INFO_type, SPA_POD_CHOICE_Bool(p->filter_graph_disabled),
SPA_PROP_INFO_params, SPA_POD_Bool(true));
break;
- case 29:
+ case 32:
*param = spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph.N"),
@@ -834,7 +864,7 @@ static int node_param_prop_info(struct impl *this, uint32_t id, uint32_t index,
default:
if (this->filter_graph[0] && this->filter_graph[0]->graph) {
return spa_filter_graph_enum_prop_info(this->filter_graph[0]->graph,
- index - 30, b, param);
+ index - 33, b, param);
}
return 0;
}
@@ -846,6 +876,7 @@ static int node_param_props(struct impl *this, uint32_t id, uint32_t index,
{
struct props *p = &this->props;
struct spa_pod_frame f[2];
+ struct filter_graph *g;
switch (index) {
case 0:
@@ -900,6 +931,12 @@ static int node_param_props(struct impl *this, uint32_t id, uint32_t index,
spa_pod_builder_float(b, this->mix.rear_delay);
spa_pod_builder_string(b, "channelmix.stereo-widen");
spa_pod_builder_float(b, this->mix.widen);
+ spa_pod_builder_string(b, "channelmix.center-level");
+ spa_pod_builder_float(b, this->mix.center_level);
+ spa_pod_builder_string(b, "channelmix.surround-level");
+ spa_pod_builder_float(b, this->mix.surround_level);
+ spa_pod_builder_string(b, "channelmix.lfe-level");
+ spa_pod_builder_float(b, this->mix.lfe_level);
spa_pod_builder_string(b, "channelmix.hilbert-taps");
spa_pod_builder_int(b, this->mix.hilbert_taps);
spa_pod_builder_string(b, "channelmix.upmix-method");
@@ -918,8 +955,12 @@ static int node_param_props(struct impl *this, uint32_t id, uint32_t index,
spa_pod_builder_bool(b, p->lock_volumes);
spa_pod_builder_string(b, "audioconvert.filter-graph.disable");
spa_pod_builder_bool(b, p->filter_graph_disabled);
- spa_pod_builder_string(b, "audioconvert.filter-graph");
- spa_pod_builder_string(b, "");
+ spa_list_for_each(g, &this->active_graphs, link) {
+ char key[64];
+ snprintf(key, sizeof(key), "audioconvert.filter-graph.%d", g->order);
+ spa_pod_builder_string(b, key);
+ spa_pod_builder_string(b, this->graph_descs[g->order]);
+ }
spa_pod_builder_pop(b, &f[1]);
*param = spa_pod_builder_pop(b, &f[0]);
break;
@@ -953,7 +994,7 @@ static int impl_node_enum_params(void *object, int seq,
struct impl *this = object;
struct spa_pod *param;
struct spa_pod_builder b = { 0 };
- uint8_t buffer[4096];
+ uint8_t buffer[16384];
struct spa_result_node_params result;
uint32_t count = 0;
int res = 0;
@@ -1409,6 +1450,7 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order)
g->removing = true;
spa_log_info(impl->log, "removing filter-graph order:%d", order);
}
+ free(impl->graph_descs[order]);
}
if (graph != NULL && graph[0] != '\0') {
@@ -1434,6 +1476,8 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order)
spa_list_remove(&pending->link);
insert_graph(&impl->active_graphs, pending);
+ impl->graph_descs[order] = spa_json_builder_reformat(graph, 0);
+
spa_log_info(impl->log, "loading filter-graph order:%d", order);
}
if (impl->setup)
@@ -1480,6 +1524,12 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
spa_atof(s, &this->mix.rear_delay);
else if (spa_streq(k, "channelmix.stereo-widen"))
spa_atof(s, &this->mix.widen);
+ else if (spa_streq(k, "channelmix.center-level"))
+ spa_atof(s, &this->mix.center_level);
+ else if (spa_streq(k, "channelmix.surround-level"))
+ spa_atof(s, &this->mix.surround_level);
+ else if (spa_streq(k, "channelmix.lfe-level"))
+ spa_atof(s, &this->mix.lfe_level);
else if (spa_streq(k, "channelmix.hilbert-taps"))
spa_atou32(s, &this->mix.hilbert_taps, 0);
else if (spa_streq(k, "channelmix.upmix-method"))
@@ -2115,7 +2165,7 @@ static int setup_in_convert(struct impl *this)
return res;
spa_log_debug(this->log, "%p: got converter features %08x:%08x passthrough:%d remap:%d %s", this,
- this->cpu_flags, in->conv.cpu_flags, in->conv.is_passthrough,
+ this->cpu_flags, in->conv.func_cpu_flags, in->conv.is_passthrough,
remap, in->conv.func_name);
return 0;
@@ -2272,7 +2322,7 @@ static int setup_channelmix(struct impl *this, uint32_t channels, uint32_t *posi
set_volume(this);
spa_log_debug(this->log, "%p: got channelmix features %08x:%08x flags:%08x %s",
- this, this->cpu_flags, this->mix.cpu_flags,
+ this, this->cpu_flags, this->mix.func_cpu_flags,
this->mix.flags, this->mix.func_name);
return 0;
}
@@ -2320,7 +2370,7 @@ static int setup_resample(struct impl *this)
res = resample_native_init(&this->resample);
spa_log_debug(this->log, "%p: got resample features %08x:%08x %s",
- this, this->cpu_flags, this->resample.cpu_flags,
+ this, this->cpu_flags, this->resample.func_cpu_flags,
this->resample.func_name);
return res;
}
@@ -2412,7 +2462,7 @@ static int setup_out_convert(struct impl *this)
spa_log_debug(this->log, "%p: got converter features %08x:%08x quant:%d:%d"
" passthrough:%d remap:%d %s", this,
- this->cpu_flags, out->conv.cpu_flags, out->conv.method,
+ this->cpu_flags, out->conv.func_cpu_flags, out->conv.method,
out->conv.noise_bits, out->conv.is_passthrough, remap, out->conv.func_name);
return 0;
@@ -4234,6 +4284,7 @@ static void free_dir(struct dir *dir)
static int impl_clear(struct spa_handle *handle)
{
struct impl *this;
+ int i;
spa_return_val_if_fail(handle != NULL, -EINVAL);
@@ -4245,6 +4296,10 @@ static int impl_clear(struct spa_handle *handle)
free_tmp(this);
clean_filter_handles(this, true);
+ for (i = 0; i < MAX_GRAPH; i++) {
+ if (this->graph_descs[i])
+ free(this->graph_descs[i]);
+ }
if (this->resample.free)
resample_free(&this->resample);
@@ -4299,18 +4354,13 @@ impl_init(const struct spa_handle_factory *factory,
struct filter_graph *g = &this->graphs[i];
g->impl = this;
spa_list_append(&this->free_graphs, &g->link);
+ this->graph_descs[i] = NULL;
}
this->rate_limit.interval = 2 * SPA_NSEC_PER_SEC;
this->rate_limit.burst = 1;
- this->mix.options = CHANNELMIX_OPTION_UPMIX | CHANNELMIX_OPTION_MIX_LFE;
- this->mix.upmix = CHANNELMIX_UPMIX_NONE;
- this->mix.log = this->log;
- this->mix.lfe_cutoff = 0.0f;
- this->mix.fc_cutoff = 0.0f;
- this->mix.rear_delay = 0.0f;
- this->mix.widen = 0.0f;
+ channelmix_reset(&this->mix);
for (i = 0; info && i < info->n_items; i++) {
const char *k = info->items[i].key;
diff --git a/spa/plugins/audioconvert/benchmark-fmt-ops.c b/spa/plugins/audioconvert/benchmark-fmt-ops.c
index 9ea43ec65..e59f1f56b 100644
--- a/spa/plugins/audioconvert/benchmark-fmt-ops.c
+++ b/spa/plugins/audioconvert/benchmark-fmt-ops.c
@@ -51,9 +51,9 @@ static void run_test1(const char *name, const char *impl, bool in_packed, bool o
void *op[n_channels];
struct timespec ts;
uint64_t count, t1, t2;
- struct convert conv;
-
- conv.n_channels = n_channels;
+ struct convert conv = {
+ .n_channels = n_channels,
+ };
for (j = 0; j < n_channels; j++) {
ip[j] = &samp_in[j * n_samples * 4];
diff --git a/spa/plugins/audioconvert/channelmix-ops-avx.c b/spa/plugins/audioconvert/channelmix-ops-avx.c
new file mode 100644
index 000000000..08d8e2b00
--- /dev/null
+++ b/spa/plugins/audioconvert/channelmix-ops-avx.c
@@ -0,0 +1,63 @@
+/* Spa */
+/* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */
+/* SPDX-License-Identifier: MIT */
+
+#include "channelmix-ops.h"
+
+#include
+#include
+#include
+
+static inline void clear_avx(float *d, uint32_t n_samples)
+{
+ memset(d, 0, n_samples * sizeof(float));
+}
+
+static inline void copy_avx(float *d, const float *s, uint32_t n_samples)
+{
+ spa_memcpy(d, s, n_samples * sizeof(float));
+}
+
+static inline void vol_avx(float *d, const float *s, float vol, uint32_t n_samples)
+{
+ uint32_t n, unrolled;
+ if (vol == 0.0f) {
+ clear_avx(d, n_samples);
+ } else if (vol == 1.0f) {
+ copy_avx(d, s, n_samples);
+ } else {
+ __m256 t[4];
+ const __m256 v = _mm256_set1_ps(vol);
+
+ if (SPA_IS_ALIGNED(d, 32) &&
+ SPA_IS_ALIGNED(s, 32))
+ unrolled = n_samples & ~31;
+ else
+ unrolled = 0;
+
+ for(n = 0; n < unrolled; n += 32) {
+ t[0] = _mm256_load_ps(&s[n]);
+ t[1] = _mm256_load_ps(&s[n+8]);
+ t[2] = _mm256_load_ps(&s[n+16]);
+ t[3] = _mm256_load_ps(&s[n+24]);
+ _mm256_store_ps(&d[n], _mm256_mul_ps(t[0], v));
+ _mm256_store_ps(&d[n+8], _mm256_mul_ps(t[1], v));
+ _mm256_store_ps(&d[n+16], _mm256_mul_ps(t[2], v));
+ _mm256_store_ps(&d[n+24], _mm256_mul_ps(t[3], v));
+ }
+ for(; n < n_samples; n++) {
+ __m128 v = _mm_set1_ps(vol);
+ _mm_store_ss(&d[n], _mm_mul_ss(_mm_load_ss(&s[n]), v));
+ }
+ }
+}
+
+void channelmix_copy_avx(struct channelmix *mix, void * SPA_RESTRICT dst[],
+ const void * SPA_RESTRICT src[], uint32_t n_samples)
+{
+ uint32_t i, n_dst = mix->dst_chan;
+ float **d = (float **)dst;
+ const float **s = (const float **)src;
+ for (i = 0; i < n_dst; i++)
+ vol_avx(d[i], s[i], mix->matrix[i][i], n_samples);
+}
diff --git a/spa/plugins/audioconvert/channelmix-ops.c b/spa/plugins/audioconvert/channelmix-ops.c
index 12edb4b5a..574d0dd1b 100644
--- a/spa/plugins/audioconvert/channelmix-ops.c
+++ b/spa/plugins/audioconvert/channelmix-ops.c
@@ -36,6 +36,11 @@ static const struct channelmix_info {
uint32_t cpu_flags;
} channelmix_table[] =
{
+#if defined (HAVE_AVX)
+ MAKE(2, MASK_MONO, 2, MASK_MONO, channelmix_copy_avx, SPA_CPU_FLAG_AVX),
+ MAKE(2, MASK_STEREO, 2, MASK_STEREO, channelmix_copy_avx, SPA_CPU_FLAG_AVX),
+ MAKE(EQ, 0, EQ, 0, channelmix_copy_avx, SPA_CPU_FLAG_AVX),
+#endif
#if defined (HAVE_SSE)
MAKE(2, MASK_MONO, 2, MASK_MONO, channelmix_copy_sse, SPA_CPU_FLAG_SSE),
MAKE(2, MASK_STEREO, 2, MASK_STEREO, channelmix_copy_sse, SPA_CPU_FLAG_SSE),
@@ -193,9 +198,9 @@ static int make_matrix(struct channelmix *mix)
uint32_t dst_chan = mix->dst_chan;
uint64_t unassigned, keep;
uint32_t i, j, ic, jc, matrix_encoding = MATRIX_NORMAL;
- float clev = SQRT1_2;
- float slev = SQRT1_2;
- float llev = 0.5f;
+ float clev = mix->center_level;
+ float slev = mix->surround_level;
+ float llev = mix->lfe_level;
float maxsum = 0.0f;
bool filter_fc = false, filter_lfe = false, matched = false, normalize;
#define _MATRIX(s,d) matrix[_CH(s)][_CH(d)]
@@ -720,7 +725,7 @@ done:
if (src_paired == 0)
src_paired = ~0LU;
- for (jc = 0, ic = 0, i = 0; ic < dst_chan; i++) {
+ for (jc = 0, ic = 0, i = 0; ic < dst_chan && i < MAX_CHANNELS; i++) {
float sum = 0.0f;
char str1[1024], str2[1024];
struct spa_strbuf sb1, sb2;
@@ -730,7 +735,7 @@ done:
if (i < CHANNEL_BITS && (dst_paired & (1UL << i)) == 0)
continue;
- for (jc = 0, j = 0; jc < src_chan; j++) {
+ for (jc = 0, j = 0; jc < src_chan && j < MAX_CHANNELS; j++) {
if (j < CHANNEL_BITS && (src_paired & (1UL << j)) == 0)
continue;
@@ -869,6 +874,21 @@ static void impl_channelmix_free(struct channelmix *mix)
mix->process = NULL;
}
+void channelmix_reset(struct channelmix *mix)
+{
+ spa_zero(*mix);
+ mix->options = CHANNELMIX_DEFAULT_OPTIONS;
+ mix->upmix = CHANNELMIX_DEFAULT_UPMIX;
+ mix->lfe_cutoff = CHANNELMIX_DEFAULT_LFE_CUTOFF;
+ mix->fc_cutoff = CHANNELMIX_DEFAULT_FC_CUTOFF;
+ mix->rear_delay = CHANNELMIX_DEFAULT_REAR_DELAY;
+ mix->center_level = CHANNELMIX_DEFAULT_CENTER_LEVEL;
+ mix->surround_level = CHANNELMIX_DEFAULT_SURROUND_LEVEL;
+ mix->lfe_level = CHANNELMIX_DEFAULT_LFE_LEVEL;
+ mix->widen = CHANNELMIX_DEFAULT_WIDEN;
+ mix->hilbert_taps = CHANNELMIX_DEFAULT_HILBERT_TAPS;
+}
+
int channelmix_init(struct channelmix *mix)
{
const struct channelmix_info *info;
@@ -885,8 +905,8 @@ int channelmix_init(struct channelmix *mix)
mix->free = impl_channelmix_free;
mix->process = info->process;
mix->set_volume = impl_channelmix_set_volume;
- mix->cpu_flags = info->cpu_flags;
mix->delay = (uint32_t)(mix->rear_delay * mix->freq / 1000.0f);
+ mix->func_cpu_flags = info->cpu_flags;
mix->func_name = info->name;
spa_zero(mix->taps_mem);
diff --git a/spa/plugins/audioconvert/channelmix-ops.h b/spa/plugins/audioconvert/channelmix-ops.h
index 6ea2b9451..c66eaddd3 100644
--- a/spa/plugins/audioconvert/channelmix-ops.h
+++ b/spa/plugins/audioconvert/channelmix-ops.h
@@ -28,6 +28,17 @@
#define CHANNELMIX_OPS_MAX_ALIGN 16
+#define CHANNELMIX_DEFAULT_OPTIONS (CHANNELMIX_OPTION_UPMIX | CHANNELMIX_OPTION_MIX_LFE)
+#define CHANNELMIX_DEFAULT_UPMIX CHANNELMIX_UPMIX_NONE
+#define CHANNELMIX_DEFAULT_LFE_CUTOFF 0.0f
+#define CHANNELMIX_DEFAULT_FC_CUTOFF 0.0f
+#define CHANNELMIX_DEFAULT_REAR_DELAY 0.0f
+#define CHANNELMIX_DEFAULT_CENTER_LEVEL 0.707106781f
+#define CHANNELMIX_DEFAULT_SURROUND_LEVEL 0.707106781f
+#define CHANNELMIX_DEFAULT_LFE_LEVEL 0.5f
+#define CHANNELMIX_DEFAULT_WIDEN 0.0f
+#define CHANNELMIX_DEFAULT_HILBERT_TAPS 0
+
struct channelmix {
uint32_t src_chan;
uint32_t dst_chan;
@@ -44,6 +55,7 @@ struct channelmix {
uint32_t upmix;
struct spa_log *log;
+ uint32_t func_cpu_flags;
const char *func_name;
#define CHANNELMIX_FLAG_ZERO (1<<0) /**< all zero components */
@@ -59,6 +71,9 @@ struct channelmix {
float fc_cutoff; /* in Hz, 0 is disabled */
float rear_delay; /* in ms, 0 is disabled */
float widen; /* stereo widen. 0 is disabled */
+ float center_level; /* center down/upmix level, sqrt(1/2) */
+ float lfe_level; /* lfe down/upmix level, 1/2 */
+ float surround_level; /* surround down/upmix level, sqrt(1/2) */
uint32_t hilbert_taps; /* to phase shift, 0 disabled */
struct lr4 lr4[MAX_CHANNELS];
@@ -79,6 +94,7 @@ struct channelmix {
void *data;
};
+void channelmix_reset(struct channelmix *mix);
int channelmix_init(struct channelmix *mix);
static const struct channelmix_upmix_info {
@@ -139,4 +155,8 @@ DEFINE_FUNCTION(f32_5p1_4, sse);
DEFINE_FUNCTION(f32_7p1_4, sse);
#endif
+#if defined (HAVE_AVX)
+DEFINE_FUNCTION(copy, avx);
+#endif
+
#undef DEFINE_FUNCTION
diff --git a/spa/plugins/audioconvert/fmt-ops-avx2.c b/spa/plugins/audioconvert/fmt-ops-avx2.c
index a939da458..9c3dce52d 100644
--- a/spa/plugins/audioconvert/fmt-ops-avx2.c
+++ b/spa/plugins/audioconvert/fmt-ops-avx2.c
@@ -4,6 +4,8 @@
#include "fmt-ops.h"
+#include
+
#include
// GCC: workaround for missing AVX intrinsic: "_mm256_setr_m128()"
// (see https://stackoverflow.com/questions/32630458/setting-m256i-to-the-value-of-two-m128i-values)
@@ -30,6 +32,38 @@
_mm256_srli_epi16(x, 8)); \
})
+#define _MM_TRANS_1x4_PS(v0,v1,v2,v3) \
+({ \
+ v1 = _mm_shuffle_ps(v0, v0, _MM_SHUFFLE(0, 3, 2, 1)); \
+ v2 = _mm_shuffle_ps(v0, v0, _MM_SHUFFLE(1, 0, 3, 2)); \
+ v3 = _mm_shuffle_ps(v0, v0, _MM_SHUFFLE(2, 1, 0, 3)); \
+})
+#define _MM_TRANS_1x4_EPI32(v0,v1,v2,v3) \
+({ \
+ v1 = _mm_shuffle_epi32(v0, _MM_SHUFFLE(0, 3, 2, 1)); \
+ v2 = _mm_shuffle_epi32(v0, _MM_SHUFFLE(1, 0, 3, 2)); \
+ v3 = _mm_shuffle_epi32(v0, _MM_SHUFFLE(2, 1, 0, 3)); \
+})
+
+#define _MM_STOREM_PS(d0,d1,d2,d3,v) \
+({ \
+ __m128 o[3]; \
+ _MM_TRANS_1x4_PS(v, o[0], o[1], o[2]); \
+ _mm_store_ss(d0, v); \
+ _mm_store_ss(d1, o[0]); \
+ _mm_store_ss(d2, o[1]); \
+ _mm_store_ss(d3, o[2]); \
+})
+#define _MM_STOREM_EPI32(d0,d1,d2,d3,v) \
+({ \
+ __m128i o[3]; \
+ _MM_TRANS_1x4_EPI32(v, o[0], o[1], o[2]); \
+ *d0 = _mm_cvtsi128_si32(v); \
+ *d1 = _mm_cvtsi128_si32(o[0]); \
+ *d2 = _mm_cvtsi128_si32(o[1]); \
+ *d3 = _mm_cvtsi128_si32(o[2]); \
+})
+
static void
conv_s16_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
uint32_t n_channels, uint32_t n_samples)
@@ -253,7 +287,7 @@ conv_s16s_to_f32d_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const
}
static void
-conv_s24_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
+conv_s24_to_f32d_1s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
uint32_t n_channels, uint32_t n_samples)
{
const int8_t *s = src;
@@ -289,7 +323,7 @@ conv_s24_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA
}
static void
-conv_s24_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
+conv_s24_to_f32d_2s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
uint32_t n_channels, uint32_t n_samples)
{
const int8_t *s = src;
@@ -341,7 +375,7 @@ conv_s24_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA
}
}
static void
-conv_s24_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
+conv_s24_to_f32d_4s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
uint32_t n_channels, uint32_t n_samples)
{
const int8_t *s = src;
@@ -397,18 +431,13 @@ conv_s24_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA
s += 12 * n_channels;
}
for(; n < n_samples; n++) {
- out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+0)));
- out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+1)));
- out[2] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+2)));
- out[3] = _mm_cvtsi32_ss(factor, s24_to_s32(*((int24_t*)s+3)));
- out[0] = _mm_mul_ss(out[0], factor);
- out[1] = _mm_mul_ss(out[1], factor);
- out[2] = _mm_mul_ss(out[2], factor);
- out[3] = _mm_mul_ss(out[3], factor);
- _mm_store_ss(&d0[n], out[0]);
- _mm_store_ss(&d1[n], out[1]);
- _mm_store_ss(&d2[n], out[2]);
- _mm_store_ss(&d3[n], out[3]);
+ in[0] = _mm_setr_epi32(s24_to_s32(*((int24_t*)s+0)),
+ s24_to_s32(*((int24_t*)s+1)),
+ s24_to_s32(*((int24_t*)s+2)),
+ s24_to_s32(*((int24_t*)s+3)));
+ out[0] = _mm_cvtepi32_ps(in[0]);
+ out[0] = _mm_mul_ps(out[0], factor);
+ _MM_STOREM_PS(&d0[n], &d1[n], &d2[n], &d3[n], out[0]);
s += 3 * n_channels;
}
}
@@ -420,16 +449,22 @@ conv_s24_to_f32d_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const voi
const int8_t *s = src[0];
uint32_t i = 0, n_channels = conv->n_channels;
- for(; i + 3 < n_channels; i += 4)
- conv_s24_to_f32d_4s_avx2(conv, &dst[i], &s[3*i], n_channels, n_samples);
- for(; i + 1 < n_channels; i += 2)
- conv_s24_to_f32d_2s_avx2(conv, &dst[i], &s[3*i], n_channels, n_samples);
- for(; i < n_channels; i++)
- conv_s24_to_f32d_1s_avx2(conv, &dst[i], &s[3*i], n_channels, n_samples);
+ if (conv->cpu_flags & SPA_CPU_FLAG_SLOW_GATHER) {
+#if defined (HAVE_SSE2)
+ conv_s24_to_f32d_sse2(conv, dst, src, n_samples);
+#endif
+ } else {
+ for(; i + 3 < n_channels; i += 4)
+ conv_s24_to_f32d_4s_gather_avx2(conv, &dst[i], &s[3*i], n_channels, n_samples);
+ for(; i + 1 < n_channels; i += 2)
+ conv_s24_to_f32d_2s_gather_avx2(conv, &dst[i], &s[3*i], n_channels, n_samples);
+ for(; i < n_channels; i++)
+ conv_s24_to_f32d_1s_gather_avx2(conv, &dst[i], &s[3*i], n_channels, n_samples);
+ }
}
static void
-conv_s32_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
+conv_s32_to_f32d_4s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
uint32_t n_channels, uint32_t n_samples)
{
const int32_t *s = src;
@@ -473,24 +508,17 @@ conv_s32_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA
}
for(; n < n_samples; n++) {
__m128 out[4], factor = _mm_set1_ps(1.0f / S32_SCALE_I2F);
- out[0] = _mm_cvtsi32_ss(factor, s[0]);
- out[1] = _mm_cvtsi32_ss(factor, s[1]);
- out[2] = _mm_cvtsi32_ss(factor, s[2]);
- out[3] = _mm_cvtsi32_ss(factor, s[3]);
- out[0] = _mm_mul_ss(out[0], factor);
- out[1] = _mm_mul_ss(out[1], factor);
- out[2] = _mm_mul_ss(out[2], factor);
- out[3] = _mm_mul_ss(out[3], factor);
- _mm_store_ss(&d0[n], out[0]);
- _mm_store_ss(&d1[n], out[1]);
- _mm_store_ss(&d2[n], out[2]);
- _mm_store_ss(&d3[n], out[3]);
+ __m128i in[1];
+ in[0] = _mm_setr_epi32(s[0], s[1], s[2], s[3]);
+ out[0] = _mm_cvtepi32_ps(in[0]);
+ out[0] = _mm_mul_ps(out[0], factor);
+ _MM_STOREM_PS(&d0[n], &d1[n], &d2[n], &d3[n], out[0]);
s += n_channels;
}
}
static void
-conv_s32_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
+conv_s32_to_f32d_2s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
uint32_t n_channels, uint32_t n_samples)
{
const int32_t *s = src;
@@ -535,7 +563,7 @@ conv_s32_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA
}
static void
-conv_s32_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
+conv_s32_to_f32d_1s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
uint32_t n_channels, uint32_t n_samples)
{
const int32_t *s = src;
@@ -575,6 +603,169 @@ conv_s32_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA
}
}
+
+static void
+conv_s32_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
+ uint32_t n_channels, uint32_t n_samples)
+{
+ const int32_t *s = src;
+ float *d0 = dst[0], *d1 = dst[1];
+ uint32_t n, unrolled;
+ __m256i in[4];
+ __m256 out[4], t[4], factor = _mm256_set1_ps(1.0f / S32_SCALE_I2F);
+
+ if (SPA_IS_ALIGNED(d0, 32) &&
+ SPA_IS_ALIGNED(d1, 32))
+ unrolled = n_samples & ~7;
+ else
+ unrolled = 0;
+
+ for(n = 0; n < unrolled; n += 8) {
+ in[0] = _mm256_setr_epi64x(
+ *((uint64_t*)&s[0*n_channels]),
+ *((uint64_t*)&s[1*n_channels]),
+ *((uint64_t*)&s[4*n_channels]),
+ *((uint64_t*)&s[5*n_channels]));
+ in[1] = _mm256_setr_epi64x(
+ *((uint64_t*)&s[2*n_channels]),
+ *((uint64_t*)&s[3*n_channels]),
+ *((uint64_t*)&s[6*n_channels]),
+ *((uint64_t*)&s[7*n_channels]));
+
+ out[0] = _mm256_cvtepi32_ps(in[0]);
+ out[1] = _mm256_cvtepi32_ps(in[1]);
+
+ out[0] = _mm256_mul_ps(out[0], factor); /* a0 b0 a1 b1 a4 b4 a5 b5 */
+ out[1] = _mm256_mul_ps(out[1], factor); /* a2 b2 a3 b3 a6 b6 a7 b7 */
+
+ t[0] = _mm256_unpacklo_ps(out[0], out[1]); /* a0 a2 b0 b2 a4 a6 b4 b6 */
+ t[1] = _mm256_unpackhi_ps(out[0], out[1]); /* a1 a3 b1 b3 a5 a7 b5 b7 */
+
+ out[0] = _mm256_unpacklo_ps(t[0], t[1]); /* a0 a1 a2 a3 a4 a5 a6 a7 */
+ out[1] = _mm256_unpackhi_ps(t[0], t[1]); /* b0 b1 b2 b3 b4 b5 b6 b7 */
+
+ _mm256_store_ps(&d0[n], out[0]);
+ _mm256_store_ps(&d1[n], out[1]);
+
+ s += 8*n_channels;
+ }
+ for(; n < n_samples; n++) {
+ __m128 out[2], factor = _mm_set1_ps(1.0f / S32_SCALE_I2F);
+ out[0] = _mm_cvtsi32_ss(factor, s[0]);
+ out[1] = _mm_cvtsi32_ss(factor, s[1]);
+ out[0] = _mm_mul_ss(out[0], factor);
+ out[1] = _mm_mul_ss(out[1], factor);
+ _mm_store_ss(&d0[n], out[0]);
+ _mm_store_ss(&d1[n], out[1]);
+ s += n_channels;
+ }
+}
+
+static void
+conv_s32_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
+ uint32_t n_channels, uint32_t n_samples)
+{
+ const int32_t *s = src;
+ float *d0 = dst[0];
+ uint32_t n, unrolled;
+ __m256i in[2];
+ __m256 out[2], factor = _mm256_set1_ps(1.0f / S32_SCALE_I2F);
+
+ if (SPA_IS_ALIGNED(d0, 32))
+ unrolled = n_samples & ~7;
+ else
+ unrolled = 0;
+
+ for(n = 0; n < unrolled; n += 8) {
+ in[0] = _mm256_setr_epi32(
+ s[0*n_channels], s[1*n_channels],
+ s[2*n_channels], s[3*n_channels],
+ s[4*n_channels], s[5*n_channels],
+ s[6*n_channels], s[7*n_channels]);
+ out[0] = _mm256_cvtepi32_ps(in[0]);
+ out[0] = _mm256_mul_ps(out[0], factor);
+ _mm256_store_ps(&d0[n+0], out[0]);
+ s += 8*n_channels;
+ }
+ for(; n < n_samples; n++) {
+ __m128 out, factor = _mm_set1_ps(1.0f / S32_SCALE_I2F);
+ out = _mm_cvtsi32_ss(factor, s[0]);
+ out = _mm_mul_ss(out, factor);
+ _mm_store_ss(&d0[n], out);
+ s += n_channels;
+ }
+}
+
+static void
+conv_s32_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
+ uint32_t n_channels, uint32_t n_samples)
+{
+ const int32_t *s = src;
+ float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3];
+ uint32_t n, unrolled;
+ __m256i in[4];
+ __m256 out[4], t[4], factor = _mm256_set1_ps(1.0f / S32_SCALE_I2F);
+
+ if (SPA_IS_ALIGNED(d0, 32) &&
+ SPA_IS_ALIGNED(d1, 32) &&
+ SPA_IS_ALIGNED(d2, 32) &&
+ SPA_IS_ALIGNED(d3, 32))
+ unrolled = n_samples & ~7;
+ else
+ unrolled = 0;
+
+ for(n = 0; n < unrolled; n += 8) {
+ in[0] = _mm256_setr_m128i(
+ _mm_loadu_si128((__m128i*)&s[0*n_channels]),
+ _mm_loadu_si128((__m128i*)&s[4*n_channels]));
+ in[1] = _mm256_setr_m128i(
+ _mm_loadu_si128((__m128i*)&s[1*n_channels]),
+ _mm_loadu_si128((__m128i*)&s[5*n_channels]));
+ in[2] = _mm256_setr_m128i(
+ _mm_loadu_si128((__m128i*)&s[2*n_channels]),
+ _mm_loadu_si128((__m128i*)&s[6*n_channels]));
+ in[3] = _mm256_setr_m128i(
+ _mm_loadu_si128((__m128i*)&s[3*n_channels]),
+ _mm_loadu_si128((__m128i*)&s[7*n_channels]));
+
+ out[0] = _mm256_cvtepi32_ps(in[0]); /* a0 b0 c0 d0 a4 b4 c4 d4 */
+ out[1] = _mm256_cvtepi32_ps(in[1]); /* a1 b1 c1 d1 a5 b5 c5 d5 */
+ out[2] = _mm256_cvtepi32_ps(in[2]); /* a2 b2 c2 d2 a6 b6 c6 d6 */
+ out[3] = _mm256_cvtepi32_ps(in[3]); /* a3 b3 c3 d3 a7 b7 c7 d7 */
+
+ out[0] = _mm256_mul_ps(out[0], factor);
+ out[1] = _mm256_mul_ps(out[1], factor);
+ out[2] = _mm256_mul_ps(out[2], factor);
+ out[3] = _mm256_mul_ps(out[3], factor);
+
+ t[0] = _mm256_unpacklo_ps(out[0], out[2]); /* a0 a2 b0 b2 a4 a6 b4 b6 */
+ t[1] = _mm256_unpackhi_ps(out[0], out[2]); /* c0 c2 d0 d2 c4 c6 d4 d6 */
+ t[2] = _mm256_unpacklo_ps(out[1], out[3]); /* a1 a3 b1 b3 a5 a7 b5 b7 */
+ t[3] = _mm256_unpackhi_ps(out[1], out[3]); /* c1 c3 d1 d3 c5 c7 d5 d7 */
+
+ out[0] = _mm256_unpacklo_ps(t[0], t[2]); /* a0 a1 a2 a3 a4 a5 a6 a7 */
+ out[1] = _mm256_unpackhi_ps(t[0], t[2]); /* b0 b1 b2 b3 b4 b5 b6 b7 */
+ out[2] = _mm256_unpacklo_ps(t[1], t[3]); /* c0 c1 c2 c3 c4 c5 c6 c7 */
+ out[3] = _mm256_unpackhi_ps(t[1], t[3]); /* d0 d1 d2 d3 d4 d5 d6 d7 */
+
+ _mm256_store_ps(&d0[n], out[0]);
+ _mm256_store_ps(&d1[n], out[1]);
+ _mm256_store_ps(&d2[n], out[2]);
+ _mm256_store_ps(&d3[n], out[3]);
+
+ s += 8*n_channels;
+ }
+ for(; n < n_samples; n++) {
+ __m128 out[4], factor = _mm_set1_ps(1.0f / S32_SCALE_I2F);
+ __m128i in[1];
+ in[0] = _mm_setr_epi32(s[0], s[1], s[2], s[3]);
+ out[0] = _mm_cvtepi32_ps(in[0]);
+ out[0] = _mm_mul_ps(out[0], factor);
+ _MM_STOREM_PS(&d0[n], &d1[n], &d2[n], &d3[n], out[0]);
+ s += n_channels;
+ }
+}
+
void
conv_s32_to_f32d_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[],
uint32_t n_samples)
@@ -582,12 +773,21 @@ conv_s32_to_f32d_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const voi
const int32_t *s = src[0];
uint32_t i = 0, n_channels = conv->n_channels;
- for(; i + 3 < n_channels; i += 4)
- conv_s32_to_f32d_4s_avx2(conv, &dst[i], &s[i], n_channels, n_samples);
- for(; i + 1 < n_channels; i += 2)
- conv_s32_to_f32d_2s_avx2(conv, &dst[i], &s[i], n_channels, n_samples);
- for(; i < n_channels; i++)
- conv_s32_to_f32d_1s_avx2(conv, &dst[i], &s[i], n_channels, n_samples);
+ if (conv->cpu_flags & SPA_CPU_FLAG_SLOW_GATHER) {
+ for(; i + 3 < n_channels; i += 4)
+ conv_s32_to_f32d_4s_avx2(conv, &dst[i], &s[i], n_channels, n_samples);
+ for(; i + 1 < n_channels; i += 2)
+ conv_s32_to_f32d_2s_avx2(conv, &dst[i], &s[i], n_channels, n_samples);
+ for(; i < n_channels; i++)
+ conv_s32_to_f32d_1s_avx2(conv, &dst[i], &s[i], n_channels, n_samples);
+ } else {
+ for(; i + 3 < n_channels; i += 4)
+ conv_s32_to_f32d_4s_gather_avx2(conv, &dst[i], &s[i], n_channels, n_samples);
+ for(; i + 1 < n_channels; i += 2)
+ conv_s32_to_f32d_2s_gather_avx2(conv, &dst[i], &s[i], n_channels, n_samples);
+ for(; i < n_channels; i++)
+ conv_s32_to_f32d_1s_gather_avx2(conv, &dst[i], &s[i], n_channels, n_samples);
+ }
}
static void
@@ -612,14 +812,10 @@ conv_f32d_to_s32_1s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R
in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale);
in[0] = _MM_CLAMP_PS(in[0], int_min, int_max);
out[0] = _mm_cvtps_epi32(in[0]);
- out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1));
- out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2));
- out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3));
-
- d[0*n_channels] = _mm_cvtsi128_si32(out[0]);
- d[1*n_channels] = _mm_cvtsi128_si32(out[1]);
- d[2*n_channels] = _mm_cvtsi128_si32(out[2]);
- d[3*n_channels] = _mm_cvtsi128_si32(out[3]);
+ _MM_STOREM_EPI32(&d[0*n_channels],
+ &d[1*n_channels],
+ &d[2*n_channels],
+ &d[3*n_channels], out[0]);
d += 4*n_channels;
}
for(; n < n_samples; n++) {
@@ -774,15 +970,7 @@ conv_f32d_to_s32_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R
__m128 int_min = _mm_set1_ps(S32_MIN_F2I);
__m128 int_max = _mm_set1_ps(S32_MAX_F2I);
- in[0] = _mm_load_ss(&s0[n]);
- in[1] = _mm_load_ss(&s1[n]);
- in[2] = _mm_load_ss(&s2[n]);
- in[3] = _mm_load_ss(&s3[n]);
-
- in[0] = _mm_unpacklo_ps(in[0], in[2]);
- in[1] = _mm_unpacklo_ps(in[1], in[3]);
- in[0] = _mm_unpacklo_ps(in[0], in[1]);
-
+ in[0] = _mm_setr_ps(s0[n], s1[n], s2[n], s3[n]);
in[0] = _mm_mul_ps(in[0], scale);
in[0] = _MM_CLAMP_PS(in[0], int_min, int_max);
out[0] = _mm_cvtps_epi32(in[0]);
@@ -972,18 +1160,16 @@ conv_f32d_to_s16_4s_avx2(void *data, void * SPA_RESTRICT dst, const void * SPA_R
__m128 int_max = _mm_set1_ps(S16_MAX);
__m128 int_min = _mm_set1_ps(S16_MIN);
- in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale);
- in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale);
- in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_scale);
- in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_scale);
- in[0] = _MM_CLAMP_SS(in[0], int_min, int_max);
- in[1] = _MM_CLAMP_SS(in[1], int_min, int_max);
- in[2] = _MM_CLAMP_SS(in[2], int_min, int_max);
- in[3] = _MM_CLAMP_SS(in[3], int_min, int_max);
+ in[0] = _mm_setr_ps(s0[n], s1[n], s2[n], s3[n]);
+ in[0] = _mm_mul_ps(in[0], int_scale);
+ in[0] = _MM_CLAMP_PS(in[0], int_min, int_max);
+
+ _MM_TRANS_1x4_PS(in[0], in[1], in[2], in[3]);
d[0] = _mm_cvtss_si32(in[0]);
d[1] = _mm_cvtss_si32(in[1]);
d[2] = _mm_cvtss_si32(in[2]);
d[3] = _mm_cvtss_si32(in[3]);
+
d += n_channels;
}
}
@@ -1055,14 +1241,10 @@ conv_f32d_to_s16_4_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const v
__m128 int_max = _mm_set1_ps(S16_MAX);
__m128 int_min = _mm_set1_ps(S16_MIN);
- in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale);
- in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale);
- in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_scale);
- in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_scale);
- in[0] = _MM_CLAMP_SS(in[0], int_min, int_max);
- in[1] = _MM_CLAMP_SS(in[1], int_min, int_max);
- in[2] = _MM_CLAMP_SS(in[2], int_min, int_max);
- in[3] = _MM_CLAMP_SS(in[3], int_min, int_max);
+ in[0] = _mm_setr_ps(s0[n], s1[n], s2[n], s3[n]);
+ in[0] = _mm_mul_ps(in[0], int_scale);
+ in[0] = _MM_CLAMP_PS(in[0], int_min, int_max);
+ _MM_TRANS_1x4_PS(in[0], in[1], in[2], in[3]);
d[0] = _mm_cvtss_si32(in[0]);
d[1] = _mm_cvtss_si32(in[1]);
d[2] = _mm_cvtss_si32(in[2]);
@@ -1185,3 +1367,4 @@ conv_f32d_to_s16s_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const
d += 2;
}
}
+
diff --git a/spa/plugins/audioconvert/fmt-ops-sse2.c b/spa/plugins/audioconvert/fmt-ops-sse2.c
index ee5c89c06..dcba6d2e1 100644
--- a/spa/plugins/audioconvert/fmt-ops-sse2.c
+++ b/spa/plugins/audioconvert/fmt-ops-sse2.c
@@ -26,6 +26,72 @@
a = _mm_shufflehi_epi16(a, _MM_SHUFFLE(2, 3, 0, 1)); \
})
+#define spa_read_unaligned(ptr, type) \
+__extension__ ({ \
+ __typeof__(type) _val; \
+ memcpy(&_val, (ptr), sizeof(_val)); \
+ _val; \
+})
+
+#define spa_write_unaligned(ptr, type, val) \
+__extension__ ({ \
+ __typeof__(type) _val = (val); \
+ memcpy((ptr), &_val, sizeof(_val)); \
+})
+
+#define _MM_TRANS_1x4_PS(v0,v1,v2,v3) \
+({ \
+ v1 = _mm_shuffle_ps(v0, v0, _MM_SHUFFLE(0, 3, 2, 1)); \
+ v2 = _mm_shuffle_ps(v0, v0, _MM_SHUFFLE(1, 0, 3, 2)); \
+ v3 = _mm_shuffle_ps(v0, v0, _MM_SHUFFLE(2, 1, 0, 3)); \
+})
+
+#define _MM_TRANS_1x4_EPI32(v0,v1,v2,v3) \
+({ \
+ v1 = _mm_shuffle_epi32(v0, _MM_SHUFFLE(0, 3, 2, 1)); \
+ v2 = _mm_shuffle_epi32(v0, _MM_SHUFFLE(1, 0, 3, 2)); \
+ v3 = _mm_shuffle_epi32(v0, _MM_SHUFFLE(2, 1, 0, 3)); \
+})
+#if 0
+#define _MM_STOREM_PS(d0,d1,d2,d3,v) \
+({ \
+ *d0 = v[0]; \
+ *d1 = v[1]; \
+ *d2 = v[2]; \
+ *d3 = v[3]; \
+})
+#else
+#define _MM_STOREM_PS(d0,d1,d2,d3,v) \
+({ \
+ __m128 o[3]; \
+ _MM_TRANS_1x4_PS(v, o[0], o[1], o[2]); \
+ _mm_store_ss(d0, v); \
+ _mm_store_ss(d1, o[0]); \
+ _mm_store_ss(d2, o[1]); \
+ _mm_store_ss(d3, o[2]); \
+})
+#endif
+
+#define _MM_STOREM_EPI32(d0,d1,d2,d3,v) \
+({ \
+ __m128i o[3]; \
+ _MM_TRANS_1x4_EPI32(v, o[0], o[1], o[2]); \
+ *d0 = _mm_cvtsi128_si32(v); \
+ *d1 = _mm_cvtsi128_si32(o[0]); \
+ *d2 = _mm_cvtsi128_si32(o[1]); \
+ *d3 = _mm_cvtsi128_si32(o[2]); \
+})
+
+#define _MM_STOREUM_EPI32(d0,d1,d2,d3,v) \
+({ \
+ __m128i o[3]; \
+ _MM_TRANS_1x4_EPI32(v, o[0], o[1], o[2]); \
+ spa_write_unaligned(d0, uint32_t, _mm_cvtsi128_si32(v)); \
+ spa_write_unaligned(d1, uint32_t, _mm_cvtsi128_si32(o[0])); \
+ spa_write_unaligned(d2, uint32_t, _mm_cvtsi128_si32(o[1])); \
+ spa_write_unaligned(d3, uint32_t, _mm_cvtsi128_si32(o[2])); \
+})
+
static void
conv_s16_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
uint32_t n_channels, uint32_t n_samples)
@@ -233,18 +299,6 @@ conv_s16s_to_f32d_2_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const
}
}
-#define spa_read_unaligned(ptr, type) \
-__extension__ ({ \
- __typeof__(type) _val; \
- memcpy(&_val, (ptr), sizeof(_val)); \
- _val; \
-})
-
-#define spa_write_unaligned(ptr, type, val) \
-__extension__ ({ \
- __typeof__(type) _val = (val); \
- memcpy((ptr), &_val, sizeof(_val)); \
-})
void
conv_s24_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
uint32_t n_channels, uint32_t n_samples)
@@ -416,18 +470,13 @@ conv_s24_to_f32d_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA
s += 4 * n_channels;
}
for(; n < n_samples; n++) {
- out[0] = _mm_cvtsi32_ss(factor, s24_to_s32(*s));
- out[1] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+1)));
- out[2] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+2)));
- out[3] = _mm_cvtsi32_ss(factor, s24_to_s32(*(s+3)));
- out[0] = _mm_mul_ss(out[0], factor);
- out[1] = _mm_mul_ss(out[1], factor);
- out[2] = _mm_mul_ss(out[2], factor);
- out[3] = _mm_mul_ss(out[3], factor);
- _mm_store_ss(&d0[n], out[0]);
- _mm_store_ss(&d1[n], out[1]);
- _mm_store_ss(&d2[n], out[2]);
- _mm_store_ss(&d3[n], out[3]);
+ in[0] = _mm_setr_epi32(s24_to_s32(*s),
+ s24_to_s32(*(s+1)),
+ s24_to_s32(*(s+2)),
+ s24_to_s32(*(s+3)));
+ out[0] = _mm_cvtepi32_ps(in[0]);
+ out[0] = _mm_mul_ps(out[0], factor);
+ _MM_STOREM_PS(&d0[n], &d1[n], &d2[n], &d3[n], out[0]);
s += n_channels;
}
}
@@ -447,6 +496,59 @@ conv_s24_to_f32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const voi
conv_s24_to_f32d_1s_sse2(conv, &dst[i], &s[3*i], n_channels, n_samples);
}
+static void
+conv_s32_to_f32d_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
+ uint32_t n_channels, uint32_t n_samples)
+{
+ const int32_t *s = src;
+ float *d0 = dst[0], *d1 = dst[1], *d2 = dst[2], *d3 = dst[3];
+ uint32_t n, unrolled;
+ __m128i in[4];
+ __m128 out[4], factor = _mm_set1_ps(1.0f / S32_SCALE_I2F);
+
+ if (SPA_IS_ALIGNED(d0, 16) &&
+ SPA_IS_ALIGNED(d1, 16) &&
+ SPA_IS_ALIGNED(d2, 16) &&
+ SPA_IS_ALIGNED(d3, 16) &&
+ SPA_IS_ALIGNED(s, 16) && (n_channels & 3) == 0)
+ unrolled = n_samples & ~3;
+ else
+ unrolled = 0;
+
+ for(n = 0; n < unrolled; n += 4) {
+ in[0] = _mm_load_si128((__m128i*)(s + 0*n_channels));
+ in[1] = _mm_load_si128((__m128i*)(s + 1*n_channels));
+ in[2] = _mm_load_si128((__m128i*)(s + 2*n_channels));
+ in[3] = _mm_load_si128((__m128i*)(s + 3*n_channels));
+
+ out[0] = _mm_cvtepi32_ps(in[0]);
+ out[1] = _mm_cvtepi32_ps(in[1]);
+ out[2] = _mm_cvtepi32_ps(in[2]);
+ out[3] = _mm_cvtepi32_ps(in[3]);
+
+ out[0] = _mm_mul_ps(out[0], factor);
+ out[1] = _mm_mul_ps(out[1], factor);
+ out[2] = _mm_mul_ps(out[2], factor);
+ out[3] = _mm_mul_ps(out[3], factor);
+
+ _MM_TRANSPOSE4_PS(out[0], out[1], out[2], out[3]);
+
+ _mm_store_ps(&d0[n], out[0]);
+ _mm_store_ps(&d1[n], out[1]);
+ _mm_store_ps(&d2[n], out[2]);
+ _mm_store_ps(&d3[n], out[3]);
+
+ s += 4*n_channels;
+ }
+ for(; n < n_samples; n++) {
+ in[0] = _mm_setr_epi32(s[0], s[1], s[2], s[3]);
+ out[0] = _mm_cvtepi32_ps(in[0]);
+ out[0] = _mm_mul_ps(out[0], factor);
+ _MM_STOREM_PS(&d0[n], &d1[n], &d2[n], &d3[n], out[0]);
+ s += n_channels;
+ }
+}
+
static void
conv_s32_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
uint32_t n_channels, uint32_t n_samples)
@@ -487,6 +589,8 @@ conv_s32_to_f32d_sse2(struct convert *conv, void * SPA_RESTRICT dst[], const voi
const int32_t *s = src[0];
uint32_t i = 0, n_channels = conv->n_channels;
+ for(; i + 3 < n_channels; i += 4)
+ conv_s32_to_f32d_4s_sse2(conv, &dst[i], &s[i], n_channels, n_samples);
for(; i < n_channels; i++)
conv_s32_to_f32d_1s_sse2(conv, &dst[i], &s[i], n_channels, n_samples);
}
@@ -513,14 +617,10 @@ conv_f32d_to_s32_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R
in[0] = _mm_mul_ps(_mm_load_ps(&s0[n]), scale);
in[0] = _MM_CLAMP_PS(in[0], int_min, int_max);
out[0] = _mm_cvtps_epi32(in[0]);
- out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1));
- out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2));
- out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3));
-
- d[0*n_channels] = _mm_cvtsi128_si32(out[0]);
- d[1*n_channels] = _mm_cvtsi128_si32(out[1]);
- d[2*n_channels] = _mm_cvtsi128_si32(out[2]);
- d[3*n_channels] = _mm_cvtsi128_si32(out[3]);
+ _MM_STOREM_EPI32(&d[0*n_channels],
+ &d[1*n_channels],
+ &d[2*n_channels],
+ &d[3*n_channels], out[0]);
d += 4*n_channels;
}
for(; n < n_samples; n++) {
@@ -630,15 +730,7 @@ conv_f32d_to_s32_4s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R
d += 4*n_channels;
}
for(; n < n_samples; n++) {
- in[0] = _mm_load_ss(&s0[n]);
- in[1] = _mm_load_ss(&s1[n]);
- in[2] = _mm_load_ss(&s2[n]);
- in[3] = _mm_load_ss(&s3[n]);
-
- in[0] = _mm_unpacklo_ps(in[0], in[2]);
- in[1] = _mm_unpacklo_ps(in[1], in[3]);
- in[0] = _mm_unpacklo_ps(in[0], in[1]);
-
+ in[0] = _mm_setr_ps(s0[n], s1[n], s2[n], s3[n]);
in[0] = _mm_mul_ps(in[0], scale);
in[0] = _MM_CLAMP_PS(in[0], int_min, int_max);
out[0] = _mm_cvtps_epi32(in[0]);
@@ -754,14 +846,10 @@ conv_f32d_to_s32_1s_noise_sse2(struct convert *conv, void * SPA_RESTRICT dst, co
in[0] = _mm_add_ps(in[0], _mm_load_ps(&noise[n]));
in[0] = _MM_CLAMP_PS(in[0], int_min, int_max);
out[0] = _mm_cvtps_epi32(in[0]);
- out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1));
- out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2));
- out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3));
-
- d[0*n_channels] = _mm_cvtsi128_si32(out[0]);
- d[1*n_channels] = _mm_cvtsi128_si32(out[1]);
- d[2*n_channels] = _mm_cvtsi128_si32(out[2]);
- d[3*n_channels] = _mm_cvtsi128_si32(out[3]);
+ _MM_STOREM_EPI32(&d[0*n_channels],
+ &d[1*n_channels],
+ &d[2*n_channels],
+ &d[3*n_channels], out[0]);
d += 4*n_channels;
}
for(; n < n_samples; n++) {
@@ -810,14 +898,10 @@ conv_interleave_32_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA
for(n = 0; n < unrolled; n += 4) {
out[0] = _mm_load_si128((__m128i*)&s0[n]);
- out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1));
- out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2));
- out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3));
-
- d[0*n_channels] = _mm_cvtsi128_si32(out[0]);
- d[1*n_channels] = _mm_cvtsi128_si32(out[1]);
- d[2*n_channels] = _mm_cvtsi128_si32(out[2]);
- d[3*n_channels] = _mm_cvtsi128_si32(out[3]);
+ _MM_STOREM_EPI32(&d[0*n_channels],
+ &d[1*n_channels],
+ &d[2*n_channels],
+ &d[3*n_channels], out[0]);
d += 4*n_channels;
}
for(; n < n_samples; n++) {
@@ -893,14 +977,10 @@ conv_interleave_32s_1s_sse2(void *data, void * SPA_RESTRICT dst, const void * SP
for(n = 0; n < unrolled; n += 4) {
out[0] = _mm_load_si128((__m128i*)&s0[n]);
out[0] = _MM_BSWAP_EPI32(out[0]);
- out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1));
- out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2));
- out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3));
-
- d[0*n_channels] = _mm_cvtsi128_si32(out[0]);
- d[1*n_channels] = _mm_cvtsi128_si32(out[1]);
- d[2*n_channels] = _mm_cvtsi128_si32(out[2]);
- d[3*n_channels] = _mm_cvtsi128_si32(out[3]);
+ _MM_STOREM_EPI32(&d[0*n_channels],
+ &d[1*n_channels],
+ &d[2*n_channels],
+ &d[3*n_channels], out[0]);
d += 4*n_channels;
}
for(; n < n_samples; n++) {
@@ -1257,14 +1337,10 @@ conv_f32d_to_s16_2s_sse2(void *data, void * SPA_RESTRICT dst, const void * SPA_R
t[1] = _mm_packs_epi32(t[1], t[1]);
out[0] = _mm_unpacklo_epi16(t[0], t[1]);
- out[1] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(0, 3, 2, 1));
- out[2] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(1, 0, 3, 2));
- out[3] = _mm_shuffle_epi32(out[0], _MM_SHUFFLE(2, 1, 0, 3));
-
- spa_write_unaligned(d + 0*n_channels, uint32_t, _mm_cvtsi128_si32(out[0]));
- spa_write_unaligned(d + 1*n_channels, uint32_t, _mm_cvtsi128_si32(out[1]));
- spa_write_unaligned(d + 2*n_channels, uint32_t, _mm_cvtsi128_si32(out[2]));
- spa_write_unaligned(d + 3*n_channels, uint32_t, _mm_cvtsi128_si32(out[3]));
+ _MM_STOREUM_EPI32(&d[0*n_channels],
+ &d[1*n_channels],
+ &d[2*n_channels],
+ &d[3*n_channels], out[0]);
d += 4*n_channels;
}
for(; n < n_samples; n++) {
diff --git a/spa/plugins/audioconvert/fmt-ops.c b/spa/plugins/audioconvert/fmt-ops.c
index 3fc2c5f0a..34de40445 100644
--- a/spa/plugins/audioconvert/fmt-ops.c
+++ b/spa/plugins/audioconvert/fmt-ops.c
@@ -631,7 +631,7 @@ int convert_init(struct convert *conv)
conv->random[i] = random();
conv->is_passthrough = conv->src_fmt == conv->dst_fmt;
- conv->cpu_flags = info->cpu_flags;
+ conv->func_cpu_flags = info->cpu_flags;
conv->update_noise = ninfo->noise;
conv->process = info->process;
conv->clear = cinfo ? cinfo->clear : NULL;
diff --git a/spa/plugins/audioconvert/fmt-ops.h b/spa/plugins/audioconvert/fmt-ops.h
index f738e3858..24b4b1aaf 100644
--- a/spa/plugins/audioconvert/fmt-ops.h
+++ b/spa/plugins/audioconvert/fmt-ops.h
@@ -219,6 +219,7 @@ struct convert {
uint32_t n_channels;
uint32_t rate;
uint32_t cpu_flags;
+ uint32_t func_cpu_flags;
const char *func_name;
unsigned int is_passthrough:1;
diff --git a/spa/plugins/audioconvert/meson.build b/spa/plugins/audioconvert/meson.build
index bd60872b6..71d5d56cd 100644
--- a/spa/plugins/audioconvert/meson.build
+++ b/spa/plugins/audioconvert/meson.build
@@ -44,7 +44,7 @@ endif
if have_sse2
audioconvert_sse2 = static_library('audioconvert_sse2',
['fmt-ops-sse2.c' ],
- c_args : [sse2_args, '-O3', '-DHAVE_SSE2'],
+ c_args : [sse2_args, '-O3', '-DHAVE_SSE2', simd_cargs],
dependencies : [ spa_dep ],
install : false
)
@@ -55,7 +55,7 @@ if have_ssse3
audioconvert_ssse3 = static_library('audioconvert_ssse3',
['fmt-ops-ssse3.c',
'resample-native-ssse3.c' ],
- c_args : [ssse3_args, '-O3', '-DHAVE_SSSE3'],
+ c_args : [ssse3_args, '-O3', '-DHAVE_SSSE3', simd_cargs],
dependencies : [ spa_dep ],
install : false
)
@@ -65,17 +65,27 @@ endif
if have_sse41
audioconvert_sse41 = static_library('audioconvert_sse41',
['fmt-ops-sse41.c'],
- c_args : [sse41_args, '-O3', '-DHAVE_SSE41'],
+ c_args : [sse41_args, '-O3', '-DHAVE_SSE41', simd_cargs],
dependencies : [ spa_dep ],
install : false
)
simd_cargs += ['-DHAVE_SSE41']
simd_dependencies += audioconvert_sse41
endif
+if have_avx
+ audioconvert_avx = static_library('audioconvert_avx',
+ ['channelmix-ops-avx.c'],
+ c_args : [avx_args, '-O3', '-DHAVE_AVX', simd_cargs],
+ dependencies : [ spa_dep ],
+ install : false
+ )
+ simd_cargs += ['-DHAVE_AVX']
+ simd_dependencies += audioconvert_avx
+endif
if have_avx2 and have_fma
audioconvert_avx2_fma = static_library('audioconvert_avx2_fma',
['resample-native-avx2.c'],
- c_args : [avx2_args, fma_args, '-O3', '-DHAVE_AVX2', '-DHAVE_FMA'],
+ c_args : [avx2_args, fma_args, '-O3', '-DHAVE_AVX2', '-DHAVE_FMA', simd_cargs],
dependencies : [ spa_dep ],
install : false
)
@@ -85,7 +95,7 @@ endif
if have_avx2
audioconvert_avx2 = static_library('audioconvert_avx2',
['fmt-ops-avx2.c'],
- c_args : [avx2_args, '-O3', '-DHAVE_AVX2'],
+ c_args : [avx2_args, '-O3', '-DHAVE_AVX2', simd_cargs],
dependencies : [ spa_dep ],
install : false
)
diff --git a/spa/plugins/audioconvert/peaks-ops.c b/spa/plugins/audioconvert/peaks-ops.c
index 29b93a081..f7a897f90 100644
--- a/spa/plugins/audioconvert/peaks-ops.c
+++ b/spa/plugins/audioconvert/peaks-ops.c
@@ -60,7 +60,7 @@ int peaks_init(struct peaks *peaks)
if (info == NULL)
return -ENOTSUP;
- peaks->cpu_flags = info->cpu_flags;
+ peaks->func_cpu_flags = info->cpu_flags;
peaks->func_name = info->name;
peaks->free = impl_peaks_free;
peaks->min_max = info->min_max;
diff --git a/spa/plugins/audioconvert/peaks-ops.h b/spa/plugins/audioconvert/peaks-ops.h
index 24092a4f7..40b20cfbc 100644
--- a/spa/plugins/audioconvert/peaks-ops.h
+++ b/spa/plugins/audioconvert/peaks-ops.h
@@ -14,6 +14,7 @@ extern struct spa_log_topic resample_log_topic;
struct peaks {
uint32_t cpu_flags;
+ uint32_t func_cpu_flags;
const char *func_name;
struct spa_log *log;
diff --git a/spa/plugins/audioconvert/resample-native.c b/spa/plugins/audioconvert/resample-native.c
index 3604c5b45..5bb33ffc1 100644
--- a/spa/plugins/audioconvert/resample-native.c
+++ b/spa/plugins/audioconvert/resample-native.c
@@ -576,7 +576,7 @@ int resample_native_init(struct resample *r)
r, c->cutoff, r->quality, c->window, r->i_rate, r->o_rate, gcd, n_taps, n_phases,
r->cpu_flags, d->info->cpu_flags);
- r->cpu_flags = d->info->cpu_flags;
+ r->func_cpu_flags = d->info->cpu_flags;
impl_native_reset(r);
impl_native_update_rate(r, 1.0);
diff --git a/spa/plugins/audioconvert/resample.h b/spa/plugins/audioconvert/resample.h
index fec3bf963..7b6e58415 100644
--- a/spa/plugins/audioconvert/resample.h
+++ b/spa/plugins/audioconvert/resample.h
@@ -38,6 +38,7 @@ struct resample {
#define RESAMPLE_OPTION_PREFILL (1<<0)
uint32_t options;
uint32_t cpu_flags;
+ uint32_t func_cpu_flags;
const char *func_name;
uint32_t channels;
diff --git a/spa/plugins/audioconvert/test-audioconvert.c b/spa/plugins/audioconvert/test-audioconvert.c
index de3ebb8b5..de18d524b 100644
--- a/spa/plugins/audioconvert/test-audioconvert.c
+++ b/spa/plugins/audioconvert/test-audioconvert.c
@@ -54,7 +54,7 @@ static int setup_context(struct context *ctx)
size_t size;
int res;
struct spa_support support[1];
- struct spa_dict_item items[6];
+ struct spa_dict_item items[9];
const struct spa_handle_factory *factory;
void *iface;
@@ -76,10 +76,13 @@ static int setup_context(struct context *ctx)
items[3] = SPA_DICT_ITEM_INIT("channelmix.lfe-cutoff", "150");
items[4] = SPA_DICT_ITEM_INIT("channelmix.fc-cutoff", "12000");
items[5] = SPA_DICT_ITEM_INIT("channelmix.rear-delay", "12.0");
+ items[6] = SPA_DICT_ITEM_INIT("channelmix.center-level", "0.707106781");
+ items[7] = SPA_DICT_ITEM_INIT("channelmix.surround-level", "0.707106781");
+ items[8] = SPA_DICT_ITEM_INIT("channelmix.lfe-level", "0.5");
res = spa_handle_factory_init(factory,
ctx->convert_handle,
- &SPA_DICT_INIT(items, 6),
+ &SPA_DICT_INIT(items, 9),
support, 1);
spa_assert_se(res >= 0);
diff --git a/spa/plugins/audioconvert/test-channelmix.c b/spa/plugins/audioconvert/test-channelmix.c
index b68c956bf..529db880f 100644
--- a/spa/plugins/audioconvert/test-channelmix.c
+++ b/spa/plugins/audioconvert/test-channelmix.c
@@ -45,7 +45,7 @@ static void test_mix(uint32_t src_chan, uint32_t src_mask, uint32_t dst_chan, ui
spa_log_debug(&logger.log, "start %d->%d (%08x -> %08x)", src_chan, dst_chan, src_mask, dst_mask);
- spa_zero(mix);
+ channelmix_reset(&mix);
mix.options = options;
mix.src_chan = src_chan;
mix.dst_chan = dst_chan;
@@ -340,7 +340,7 @@ static void test_n_m_impl(void)
src[i] = src_data[i];
}
- spa_zero(mix);
+ channelmix_reset(&mix);
mix.src_chan = 16;
mix.dst_chan = 12;
mix.log = &logger.log;
diff --git a/spa/plugins/audioconvert/test-fmt-ops.c b/spa/plugins/audioconvert/test-fmt-ops.c
index 17a26a351..d5ca414ef 100644
--- a/spa/plugins/audioconvert/test-fmt-ops.c
+++ b/spa/plugins/audioconvert/test-fmt-ops.c
@@ -45,9 +45,9 @@ static void run_test(const char *name,
void *tp[N_CHANNELS];
int i, j;
const uint8_t *in8 = in, *out8 = out;
- struct convert conv;
-
- conv.n_channels = N_CHANNELS;
+ struct convert conv = {
+ .n_channels = N_CHANNELS,
+ };
for (j = 0; j < N_SAMPLES; j++) {
memcpy(&samp_in[j * in_size], &in8[(j % n_samples) * in_size], in_size);
diff --git a/spa/plugins/audioconvert/volume-ops.c b/spa/plugins/audioconvert/volume-ops.c
index bf6aa6909..b76ab4bec 100644
--- a/spa/plugins/audioconvert/volume-ops.c
+++ b/spa/plugins/audioconvert/volume-ops.c
@@ -56,7 +56,7 @@ int volume_init(struct volume *vol)
if (info == NULL)
return -ENOTSUP;
- vol->cpu_flags = info->cpu_flags;
+ vol->func_cpu_flags = info->cpu_flags;
vol->func_name = info->name;
vol->free = impl_volume_free;
vol->process = info->process;
diff --git a/spa/plugins/audioconvert/volume-ops.h b/spa/plugins/audioconvert/volume-ops.h
index a50ee9a6f..51642110f 100644
--- a/spa/plugins/audioconvert/volume-ops.h
+++ b/spa/plugins/audioconvert/volume-ops.h
@@ -13,6 +13,7 @@
struct volume {
uint32_t cpu_flags;
+ uint32_t func_cpu_flags;
const char *func_name;
struct spa_log *log;
diff --git a/spa/plugins/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c
index 99b0658a0..54549bce8 100644
--- a/spa/plugins/audiomixer/audiomixer.c
+++ b/spa/plugins/audiomixer/audiomixer.c
@@ -725,7 +725,7 @@ static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq,
port->io[0] = info->data;
port->io[1] = info->data;
}
- if (!port->active) {
+ if (port->direction == SPA_DIRECTION_INPUT && !port->active) {
spa_list_append(&info->impl->mix_list, &port->mix_link);
port->active = true;
}
diff --git a/spa/plugins/audiomixer/mixer-dsp.c b/spa/plugins/audiomixer/mixer-dsp.c
index 5bd4e1a1e..698426d21 100644
--- a/spa/plugins/audiomixer/mixer-dsp.c
+++ b/spa/plugins/audiomixer/mixer-dsp.c
@@ -718,7 +718,7 @@ static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq,
port->io[0] = info->data;
port->io[1] = info->data;
}
- if (!port->active) {
+ if (port->direction == SPA_DIRECTION_INPUT && !port->active) {
spa_list_append(&info->impl->mix_list, &port->mix_link);
port->active = true;
}
diff --git a/spa/plugins/audiotestsrc/audiotestsrc.c b/spa/plugins/audiotestsrc/audiotestsrc.c
index 5e7c521b8..3414e8b18 100644
--- a/spa/plugins/audiotestsrc/audiotestsrc.c
+++ b/spa/plugins/audiotestsrc/audiotestsrc.c
@@ -41,13 +41,11 @@ enum wave_type {
#define DEFAULT_RATE 48000
#define DEFAULT_CHANNELS 2
-#define DEFAULT_LIVE true
#define DEFAULT_WAVE WAVE_SINE
#define DEFAULT_FREQ 440.0
#define DEFAULT_VOLUME 1.0
struct props {
- bool live;
uint32_t wave;
float freq;
float volume;
@@ -55,7 +53,6 @@ struct props {
static void reset_props(struct props *props)
{
- props->live = DEFAULT_LIVE;
props->wave = DEFAULT_WAVE;
props->freq = DEFAULT_FREQ;
props->volume = DEFAULT_VOLUME;
@@ -118,9 +115,7 @@ struct impl {
struct spa_hook_list hooks;
struct spa_callbacks callbacks;
- bool async;
struct spa_source timer_source;
- struct itimerspec timerspec;
bool started;
uint64_t start_time;
@@ -162,13 +157,6 @@ static int impl_node_enum_params(void *object, int seq,
switch (result.index) {
case 0:
- param = spa_pod_builder_add_object(&b,
- SPA_TYPE_OBJECT_PropInfo, id,
- SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_live),
- SPA_PROP_INFO_description, SPA_POD_String("Configure live mode of the source"),
- SPA_PROP_INFO_type, SPA_POD_Bool(p->live));
- break;
- case 1:
spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, id);
spa_pod_builder_add(&b,
SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_waveType),
@@ -184,14 +172,14 @@ static int impl_node_enum_params(void *object, int seq,
spa_pod_builder_pop(&b, &f[1]);
param = spa_pod_builder_pop(&b, &f[0]);
break;
- case 2:
+ case 1:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_frequency),
SPA_PROP_INFO_description, SPA_POD_String("Select the frequency"),
SPA_PROP_INFO_type, SPA_POD_CHOICE_RANGE_Float(p->freq, 0.0, 50000000.0));
break;
- case 3:
+ case 2:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_PropInfo, id,
SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_volume),
@@ -211,7 +199,6 @@ static int impl_node_enum_params(void *object, int seq,
case 0:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, id,
- SPA_PROP_live, SPA_POD_Bool(p->live),
SPA_PROP_waveType, SPA_POD_Int(p->wave),
SPA_PROP_frequency, SPA_POD_Float(p->freq),
SPA_PROP_volume, SPA_POD_Float(p->volume));
@@ -265,7 +252,6 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
if (id == SPA_PARAM_Props) {
struct props *p = &this->props;
- struct port *port = &this->port;
if (param == NULL) {
reset_props(p);
@@ -273,15 +259,9 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
}
spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL,
- SPA_PROP_live, SPA_POD_OPT_Bool(&p->live),
SPA_PROP_waveType, SPA_POD_OPT_Int(&p->wave),
SPA_PROP_frequency, SPA_POD_OPT_Float(&p->freq),
SPA_PROP_volume, SPA_POD_OPT_Float(&p->volume));
-
- if (p->live)
- port->info.flags |= SPA_PORT_FLAG_LIVE;
- else
- port->info.flags &= ~SPA_PORT_FLAG_LIVE;
}
else
return -ENOENT;
@@ -316,23 +296,15 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
static void set_timer(struct impl *this, bool enabled)
{
- if (this->async || this->props.live) {
- if (enabled) {
- if (this->props.live) {
- uint64_t next_time = this->start_time + this->elapsed_time;
- this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC;
- this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC;
- } else {
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 1;
- }
- } else {
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 0;
- }
- spa_system_timerfd_settime(this->data_system,
- this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL);
+ struct itimerspec ts = {0};
+
+ if (enabled) {
+ uint64_t next_time = this->start_time + this->elapsed_time;
+ ts.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC;
+ ts.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC;
}
+
+ spa_system_timerfd_settime(this->data_system, this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &ts, NULL);
}
static int read_timer(struct impl *this)
@@ -340,14 +312,12 @@ static int read_timer(struct impl *this)
uint64_t expirations;
int res = 0;
- if (this->async || this->props.live) {
- if ((res = spa_system_timerfd_read(this->data_system,
- this->timer_source.fd, &expirations)) < 0) {
- if (res != -EAGAIN)
- spa_log_error(this->log, "%p: timerfd error: %s",
- this, spa_strerror(res));
- }
+ if ((res = spa_system_timerfd_read(this->data_system, this->timer_source.fd, &expirations)) < 0) {
+ if (res != -EAGAIN)
+ spa_log_error(this->log, "%p: timerfd error: %s",
+ this, spa_strerror(res));
}
+
return 0;
}
@@ -471,10 +441,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
return 0;
clock_gettime(CLOCK_MONOTONIC, &now);
- if (this->props.live)
- this->start_time = SPA_TIMESPEC_TO_NSEC(&now);
- else
- this->start_time = 0;
+ this->start_time = SPA_TIMESPEC_TO_NSEC(&now);
this->sample_count = 0;
this->elapsed_time = 0;
@@ -895,9 +862,6 @@ static inline void reuse_buffer(struct impl *this, struct port *port, uint32_t i
b->outstanding = false;
spa_list_append(&port->empty, &b->link);
-
- if (!this->props.live && !this->following)
- set_timer(this, true);
}
static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id)
@@ -971,7 +935,7 @@ static int impl_node_process(void *object)
io->buffer_id = SPA_ID_INVALID;
}
- if (!this->props.live || this->following)
+ if (this->following)
return make_buffer(this);
else
return SPA_STATUS_OK;
@@ -1105,10 +1069,6 @@ impl_init(const struct spa_handle_factory *factory,
CLOCK_MONOTONIC, SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);
this->timer_source.mask = SPA_IO_IN;
this->timer_source.rmask = 0;
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 0;
- this->timerspec.it_interval.tv_sec = 0;
- this->timerspec.it_interval.tv_nsec = 0;
if (this->data_loop)
spa_loop_add_source(this->data_loop, &this->timer_source);
@@ -1117,9 +1077,7 @@ impl_init(const struct spa_handle_factory *factory,
port->info_all = SPA_PORT_CHANGE_MASK_FLAGS |
SPA_PORT_CHANGE_MASK_PARAMS;
port->info = SPA_PORT_INFO_INIT();
- port->info.flags = SPA_PORT_FLAG_NO_REF;
- if (this->props.live)
- port->info.flags |= SPA_PORT_FLAG_LIVE;
+ port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_LIVE;
port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ);
port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ);
port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ);
diff --git a/spa/plugins/bluez5/a2dp-codec-aac.c b/spa/plugins/bluez5/a2dp-codec-aac.c
index c49f7a616..f4cbecd9b 100644
--- a/spa/plugins/bluez5/a2dp-codec-aac.c
+++ b/spa/plugins/bluez5/a2dp-codec-aac.c
@@ -106,8 +106,8 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
static const struct media_codec_config
aac_frequencies[] = {
- { AAC_SAMPLING_FREQ_48000, 48000, 11 },
{ AAC_SAMPLING_FREQ_44100, 44100, 10 },
+ { AAC_SAMPLING_FREQ_48000, 48000, 11 },
{ AAC_SAMPLING_FREQ_96000, 96000, 9 },
{ AAC_SAMPLING_FREQ_88200, 88200, 8 },
{ AAC_SAMPLING_FREQ_64000, 64000, 7 },
@@ -194,75 +194,6 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags,
return sizeof(conf);
}
-static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
- const void *caps, size_t caps_size, uint32_t id, uint32_t idx,
- struct spa_pod_builder *b, struct spa_pod **param)
-{
- a2dp_aac_t conf;
- struct spa_pod_frame f[2];
- struct spa_pod_choice *choice;
- uint32_t position[2];
- uint32_t i = 0;
-
- if (caps_size < sizeof(conf))
- return -EINVAL;
-
- memcpy(&conf, caps, sizeof(conf));
-
- if (idx > 0)
- return 0;
-
- spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id);
- spa_pod_builder_add(b,
- SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
- SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
- SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16),
- 0);
- spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0);
-
- spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0);
- choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]);
- i = 0;
- SPA_FOR_EACH_ELEMENT_VAR(aac_frequencies, f) {
- if (AAC_GET_FREQUENCY(conf) & f->config) {
- if (i++ == 0)
- spa_pod_builder_int(b, f->value);
- spa_pod_builder_int(b, f->value);
- }
- }
- if (i > 1)
- choice->body.type = SPA_CHOICE_Enum;
- spa_pod_builder_pop(b, &f[1]);
-
- if (i == 0)
- return -EINVAL;
-
- if (SPA_FLAG_IS_SET(conf.channels, AAC_CHANNELS_1 | AAC_CHANNELS_2)) {
- spa_pod_builder_add(b,
- SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, 2),
- 0);
- } else if (conf.channels & AAC_CHANNELS_1) {
- position[0] = SPA_AUDIO_CHANNEL_MONO;
- spa_pod_builder_add(b,
- SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1),
- SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t),
- SPA_TYPE_Id, 1, position),
- 0);
- } else if (conf.channels & AAC_CHANNELS_2) {
- position[0] = SPA_AUDIO_CHANNEL_FL;
- position[1] = SPA_AUDIO_CHANNEL_FR;
- spa_pod_builder_add(b,
- SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2),
- SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t),
- SPA_TYPE_Id, 2, position),
- 0);
- } else
- return -EINVAL;
-
- *param = spa_pod_builder_pop(b, &f[0]);
- return *param == NULL ? -EIO : 1;
-}
-
static int codec_validate_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
struct spa_audio_info *info)
@@ -283,8 +214,10 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags
/*
* A2DP v1.3.2, 4.5.2: only one bit shall be set in bitfields.
* However, there is a report (#1342) of device setting multiple
- * bits for AAC object type. It's not clear if this was due to
- * a BlueZ bug, but we can be lax here and below in codec_init.
+ * bits for AAC object type. In addition AirPods set multiple bits.
+ *
+ * Some devices also set multiple bits in frequencies & channels.
+ * For these, pick a "preferred" choice.
*/
if (!(conf.object_type & (AAC_OBJECT_TYPE_MPEG2_AAC_LC |
AAC_OBJECT_TYPE_MPEG4_AAC_LC |
@@ -315,6 +248,35 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags
return 0;
}
+static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
+ const void *caps, size_t caps_size, uint32_t id, uint32_t idx,
+ struct spa_pod_builder *b, struct spa_pod **param)
+{
+ struct spa_audio_info info;
+ struct spa_pod_frame f[1];
+ int res;
+
+ if ((res = codec_validate_config(codec, flags, caps, caps_size, &info)) < 0)
+ return res;
+
+ if (idx > 0)
+ return 0;
+
+ spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id);
+ spa_pod_builder_add(b,
+ SPA_FORMAT_mediaType, SPA_POD_Id(info.media_type),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(info.media_subtype),
+ 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_Array(sizeof(uint32_t),
+ SPA_TYPE_Id, info.info.raw.channels, info.info.raw.position),
+ 0);
+
+ *param = spa_pod_builder_pop(b, &f[0]);
+ return *param == NULL ? -EIO : 1;
+}
+
static void *codec_init_props(const struct media_codec *codec, uint32_t flags, const struct spa_dict *settings)
{
struct props *p = calloc(1, sizeof(struct props));
@@ -324,7 +286,7 @@ static void *codec_init_props(const struct media_codec *codec, uint32_t flags, c
return NULL;
if (settings == NULL || (str = spa_dict_lookup(settings, "bluez5.a2dp.aac.bitratemode")) == NULL)
- str = "0";
+ str = "5";
p->bitratemode = SPA_CLAMP(atoi(str), 0, 5);
return p;
@@ -369,14 +331,14 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags,
goto error;
/* If object type has multiple bits set (invalid per spec, see above),
- * assume the device usually means AAC-LC.
+ * assume the device usually means MPEG2 AAC LC which is mandatory.
*/
- if (conf->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC) {
- res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_AAC_LC);
+ if (conf->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC) {
+ res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_MP2_AAC_LC);
if (res != AACENC_OK)
goto error;
- } else if (conf->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC) {
- res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_MP2_AAC_LC);
+ } else if (conf->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_LC) {
+ res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_AAC_LC);
if (res != AACENC_OK)
goto error;
} else if (conf->object_type & AAC_OBJECT_TYPE_MPEG4_AAC_ELD) {
@@ -418,8 +380,12 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags,
// Fragmentation is not implemented yet,
// so make sure every encoded AAC frame fits in (mtu - header)
this->max_bitrate = ((this->mtu - sizeof(struct rtp_header)) * 8 * this->rate) / 1024;
- this->max_bitrate = SPA_MIN(this->max_bitrate, get_valid_aac_bitrate(conf));
- this->cur_bitrate = this->max_bitrate;
+ this->cur_bitrate = SPA_MIN(this->max_bitrate, get_valid_aac_bitrate(conf));
+ spa_log_debug(log, "AAC: max (peak) bitrate: %d, cur bitrate: %d, mode: %d (vbr: %d)",
+ this->max_bitrate,
+ this->cur_bitrate,
+ bitratemode,
+ conf->vbr);
res = aacEncoder_SetParam(this->aacenc, AACENC_BITRATE, this->cur_bitrate);
if (res != AACENC_OK)
@@ -429,6 +395,15 @@ static void *codec_init(const struct media_codec *codec, uint32_t flags,
if (res != AACENC_OK)
goto error;
+ // Assume >110 kbit/s as a "high bitrate" CBR and increase the
+ // band pass cutout up to 19.3 kHz (as in mode 5 VBR).
+ if (!conf->vbr && this->cur_bitrate > 110000) {
+ res = aacEncoder_SetParam(this->aacenc, AACENC_BANDWIDTH,
+ 19293);
+ if (res != AACENC_OK)
+ goto error;
+ }
+
res = aacEncoder_SetParam(this->aacenc, AACENC_TRANSMUX, TT_MP4_LATM_MCP1);
if (res != AACENC_OK)
goto error;
diff --git a/spa/plugins/bluez5/a2dp-codec-sbc.c b/spa/plugins/bluez5/a2dp-codec-sbc.c
index b4ebfc2a2..f1b86e76b 100644
--- a/spa/plugins/bluez5/a2dp-codec-sbc.c
+++ b/spa/plugins/bluez5/a2dp-codec-sbc.c
@@ -343,77 +343,27 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size, uint32_t id, uint32_t idx,
struct spa_pod_builder *b, struct spa_pod **param)
{
- a2dp_sbc_t conf;
- struct spa_pod_frame f[2];
- struct spa_pod_choice *choice;
- uint32_t i = 0;
- uint32_t position[2];
+ struct spa_audio_info info;
+ struct spa_pod_frame f[1];
+ int res;
- if (caps_size < sizeof(conf))
- return -EINVAL;
-
- memcpy(&conf, caps, sizeof(conf));
+ if ((res = codec_validate_config(codec, flags, caps, caps_size, &info)) < 0)
+ return res;
if (idx > 0)
return 0;
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Format, id);
spa_pod_builder_add(b,
- SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
- SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
- SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16),
+ SPA_FORMAT_mediaType, SPA_POD_Id(info.media_type),
+ SPA_FORMAT_mediaSubtype, SPA_POD_Id(info.media_subtype),
+ 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_Array(sizeof(uint32_t),
+ SPA_TYPE_Id, info.info.raw.channels, info.info.raw.position),
0);
- spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_rate, 0);
- spa_pod_builder_push_choice(b, &f[1], SPA_CHOICE_None, 0);
- choice = (struct spa_pod_choice*)spa_pod_builder_frame(b, &f[1]);
- i = 0;
- if (conf.frequency & SBC_SAMPLING_FREQ_48000) {
- if (i++ == 0)
- spa_pod_builder_int(b, 48000);
- spa_pod_builder_int(b, 48000);
- }
- if (conf.frequency & SBC_SAMPLING_FREQ_44100) {
- if (i++ == 0)
- spa_pod_builder_int(b, 44100);
- spa_pod_builder_int(b, 44100);
- }
- if (conf.frequency & SBC_SAMPLING_FREQ_32000) {
- if (i++ == 0)
- spa_pod_builder_int(b, 32000);
- spa_pod_builder_int(b, 32000);
- }
- if (conf.frequency & SBC_SAMPLING_FREQ_16000) {
- if (i++ == 0)
- spa_pod_builder_int(b, 16000);
- spa_pod_builder_int(b, 16000);
- }
- if (i > 1)
- choice->body.type = SPA_CHOICE_Enum;
- spa_pod_builder_pop(b, &f[1]);
-
- if (conf.channel_mode & SBC_CHANNEL_MODE_MONO &&
- conf.channel_mode & (SBC_CHANNEL_MODE_JOINT_STEREO |
- SBC_CHANNEL_MODE_STEREO | SBC_CHANNEL_MODE_DUAL_CHANNEL)) {
- spa_pod_builder_add(b,
- SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(2, 1, 2),
- 0);
- } else if (conf.channel_mode & SBC_CHANNEL_MODE_MONO) {
- position[0] = SPA_AUDIO_CHANNEL_MONO;
- spa_pod_builder_add(b,
- SPA_FORMAT_AUDIO_channels, SPA_POD_Int(1),
- SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t),
- SPA_TYPE_Id, 1, position),
- 0);
- } else {
- position[0] = SPA_AUDIO_CHANNEL_FL;
- position[1] = SPA_AUDIO_CHANNEL_FR;
- spa_pod_builder_add(b,
- SPA_FORMAT_AUDIO_channels, SPA_POD_Int(2),
- SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t),
- SPA_TYPE_Id, 2, position),
- 0);
- }
*param = spa_pod_builder_pop(b, &f[0]);
return *param == NULL ? -EIO : 1;
}
diff --git a/spa/plugins/bluez5/backend-native.c b/spa/plugins/bluez5/backend-native.c
index 4d14183e7..64a4c25c1 100644
--- a/spa/plugins/bluez5/backend-native.c
+++ b/spa/plugins/bluez5/backend-native.c
@@ -1969,6 +1969,9 @@ static void hfp_hf_remove_disconnected_calls(struct rfcomm *rfcomm)
struct updated_call *updated_call;
bool found;
+ if (!rfcomm->telephony_ag)
+ return;
+
spa_list_for_each_safe(call, call_tmp, &rfcomm->telephony_ag->call_list, link) {
found = false;
spa_list_for_each(updated_call, &rfcomm->updated_call_list, link) {
@@ -2097,6 +2100,8 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
if (spa_streq(rfcomm->hf_indicators[indicator], "battchg")) {
spa_bt_device_report_battery_level(rfcomm->device, value * 100 / 5);
+ } else if (!rfcomm->telephony_ag) {
+ /* noop */
} else if (spa_streq(rfcomm->hf_indicators[indicator], "callsetup")) {
if (rfcomm->hfp_hf_clcc) {
rfcomm_send_cmd(rfcomm, hfp_hf_clcc_update, NULL, "AT+CLCC");
@@ -2245,7 +2250,8 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
rfcomm->hfp_hf_in_progress = false;
}
}
- } else if (sscanf(token, "+CLIP: \"%16[^\"]\",%u", number, &type) == 2) {
+ } else if (sscanf(token, "+CLIP: \"%16[^\"]\",%u", number, &type) == 2
+ && rfcomm->telephony_ag) {
struct spa_bt_telephony_call *call;
spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) {
if (call->state == CALL_STATE_INCOMING && !spa_streq(number, call->line_identification)) {
@@ -2256,7 +2262,8 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
break;
}
}
- } else if (sscanf(token, "+CCWA: \"%16[^\"]\",%u", number, &type) == 2) {
+ } else if (sscanf(token, "+CCWA: \"%16[^\"]\",%u", number, &type) == 2
+ && rfcomm->telephony_ag) {
struct spa_bt_telephony_call *call;
bool found = false;
@@ -2273,7 +2280,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
if (call == NULL)
spa_log_warn(backend->log, "failed to create waiting call");
}
- } else if (spa_strstartswith(token, "+CLCC:")) {
+ } else if (spa_strstartswith(token, "+CLCC:") && rfcomm->telephony_ag) {
struct spa_bt_telephony_call *call;
size_t pos;
char *token_end;
@@ -2421,17 +2428,19 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
}
}
- rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0);
- rfcomm->telephony_ag->address = strdup(rfcomm->device->address);
- rfcomm->telephony_ag->volume[SPA_BT_VOLUME_ID_RX] = rfcomm->volumes[SPA_BT_VOLUME_ID_RX].hw_volume = backend->hfp_default_speaker_volume;
- rfcomm->telephony_ag->volume[SPA_BT_VOLUME_ID_TX] = rfcomm->volumes[SPA_BT_VOLUME_ID_TX].hw_volume = backend->hfp_default_mic_volume;
- telephony_ag_set_callbacks(rfcomm->telephony_ag,
+ if (backend->telephony) {
+ rfcomm->telephony_ag = telephony_ag_new(backend->telephony, 0);
+ rfcomm->telephony_ag->address = strdup(rfcomm->device->address);
+ rfcomm->telephony_ag->volume[SPA_BT_VOLUME_ID_RX] = rfcomm->volumes[SPA_BT_VOLUME_ID_RX].hw_volume = backend->hfp_default_speaker_volume;
+ rfcomm->telephony_ag->volume[SPA_BT_VOLUME_ID_TX] = rfcomm->volumes[SPA_BT_VOLUME_ID_TX].hw_volume = backend->hfp_default_mic_volume;
+ telephony_ag_set_callbacks(rfcomm->telephony_ag,
&telephony_ag_callbacks, rfcomm);
- if (rfcomm->transport) {
- rfcomm->telephony_ag->transport.codec = rfcomm->transport->media_codec->codec_id;
- rfcomm->telephony_ag->transport.state = rfcomm->transport->state;
+ if (rfcomm->transport) {
+ rfcomm->telephony_ag->transport.codec = rfcomm->transport->media_codec->codec_id;
+ rfcomm->telephony_ag->transport.state = rfcomm->transport->state;
+ }
+ telephony_ag_register(rfcomm->telephony_ag);
}
- telephony_ag_register(rfcomm->telephony_ag);
rfcomm_send_cmd(rfcomm, hfp_hf_clip, NULL, "AT+CLIP=1");
break;
@@ -2478,7 +2487,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token)
break;
case hfp_hf_chld1_hangup:
/* For HFP/HF/TWC/BV-03-C - see 0e92ab9307e05758b3f70b4c0648e29c1d1e50be */
- if (!rfcomm->hfp_hf_clcc) {
+ if (!rfcomm->hfp_hf_clcc && rfcomm->telephony_ag) {
struct spa_bt_telephony_call *call, *tcall;
spa_list_for_each_safe(call, tcall, &rfcomm->telephony_ag->call_list, link) {
if (call->state == CALL_STATE_ACTIVE) {
diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c
index 74761f26b..881af4e14 100644
--- a/spa/plugins/bluez5/bap-codec-lc3.c
+++ b/spa/plugins/bluez5/bap-codec-lc3.c
@@ -126,22 +126,22 @@ static const struct bap_qos bap_qos_configs[] = {
BAP_QOS("48_6_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 155, 5, 20, 40000, 27, "low-latency"), /* 48_6_1 */
/* BAP v1.0.1 Table 5.2; high-reliability */
- BAP_QOS("8_1_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_7_5, false, 26, 13, 75, 40000, 10, "high-reliabilty"), /* 8_1_2 */
- BAP_QOS("8_2_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_10, false, 30, 13, 95, 40000, 0, "high-reliabilty"), /* 8_2_2 */
- BAP_QOS("16_1_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_7_5, false, 30, 13, 75, 40000, 11, "high-reliabilty"), /* 16_1_2 */
- BAP_QOS("16_2_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_10, false, 40, 13, 95, 40000, 1, "high-reliabilty"), /* 16_2_2 */
- BAP_QOS("24_1_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_7_5, false, 45, 13, 75, 40000, 12, "high-reliabilty"), /* 24_1_2 */
- BAP_QOS("24_2_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_10, false, 60, 13, 95, 40000, 2, "high-reliabilty"), /* 24_2_2 */
- BAP_QOS("32_1_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_7_5, false, 60, 13, 75, 40000, 13, "high-reliabilty"), /* 32_1_2 */
- BAP_QOS("32_2_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_10, false, 80, 13, 95, 40000, 3, "high-reliabilty"), /* 32_2_2 */
- BAP_QOS("441_1_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_7_5, true, 97, 13, 80, 40000, 54, "high-reliabilty"), /* 441_1_2 */
- BAP_QOS("441_2_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_10, true, 130, 13, 85, 40000, 44, "high-reliabilty"), /* 441_2_2 */
- BAP_QOS("48_1_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 75, 13, 75, 40000, 55, "high-reliabilty"), /* 48_1_2 */
- BAP_QOS("48_2_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 100, 13, 95, 40000, 45, "high-reliabilty"), /* 48_2_2 */
- BAP_QOS("48_3_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 90, 13, 75, 40000, 56, "high-reliabilty"), /* 48_3_2 */
- BAP_QOS("48_4_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 120, 13, 100, 40000, 46, "high-reliabilty"), /* 48_4_2 */
- BAP_QOS("48_5_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 117, 13, 75, 40000, 57, "high-reliabilty"), /* 48_5_2 */
- BAP_QOS("48_6_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 155, 13, 100, 40000, 47, "high-reliabilty"), /* 48_6_2 */
+ BAP_QOS("8_1_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_7_5, false, 26, 13, 75, 40000, 10, "high-reliability"), /* 8_1_2 */
+ BAP_QOS("8_2_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_10, false, 30, 13, 95, 40000, 0, "high-reliability"), /* 8_2_2 */
+ BAP_QOS("16_1_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_7_5, false, 30, 13, 75, 40000, 11, "high-reliability"), /* 16_1_2 */
+ BAP_QOS("16_2_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_10, false, 40, 13, 95, 40000, 1, "high-reliability"), /* 16_2_2 */
+ BAP_QOS("24_1_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_7_5, false, 45, 13, 75, 40000, 12, "high-reliability"), /* 24_1_2 */
+ BAP_QOS("24_2_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_10, false, 60, 13, 95, 40000, 2, "high-reliability"), /* 24_2_2 */
+ BAP_QOS("32_1_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_7_5, false, 60, 13, 75, 40000, 13, "high-reliability"), /* 32_1_2 */
+ BAP_QOS("32_2_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_10, false, 80, 13, 95, 40000, 3, "high-reliability"), /* 32_2_2 */
+ BAP_QOS("441_1_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_7_5, true, 97, 13, 80, 40000, 54, "high-reliability"), /* 441_1_2 */
+ BAP_QOS("441_2_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_10, true, 130, 13, 85, 40000, 44, "high-reliability"), /* 441_2_2 */
+ BAP_QOS("48_1_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 75, 13, 75, 40000, 55, "high-reliability"), /* 48_1_2 */
+ BAP_QOS("48_2_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 100, 13, 95, 40000, 45, "high-reliability"), /* 48_2_2 */
+ BAP_QOS("48_3_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 90, 13, 75, 40000, 56, "high-reliability"), /* 48_3_2 */
+ BAP_QOS("48_4_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 120, 13, 100, 40000, 46, "high-reliability"), /* 48_4_2 */
+ BAP_QOS("48_5_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 117, 13, 75, 40000, 57, "high-reliability"), /* 48_5_2 */
+ BAP_QOS("48_6_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 155, 13, 100, 40000, 47, "high-reliability"), /* 48_6_2 */
};
static const struct bap_qos bap_bcast_qos_configs[] = {
@@ -167,22 +167,22 @@ static const struct bap_qos bap_bcast_qos_configs[] = {
BAP_QOS("48_6_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 155, 4, 20, 40000, 27, "low-latency"), /* 48_6_1 */
/* BAP v1.0.1 Table 6.4; high-reliability */
- BAP_QOS("8_1_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_7_5, false, 26, 4, 45, 40000, 10, "high-reliabilty"), /* 8_1_2 */
- BAP_QOS("8_2_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_10, false, 30, 4, 60, 40000, 0, "high-reliabilty"), /* 8_2_2 */
- BAP_QOS("16_1_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_7_5, false, 30, 4, 45, 40000, 11, "high-reliabilty"), /* 16_1_2 */
- BAP_QOS("16_2_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_10, false, 40, 4, 60, 40000, 1, "high-reliabilty"), /* 16_2_2 */
- BAP_QOS("24_1_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_7_5, false, 45, 4, 45, 40000, 12, "high-reliabilty"), /* 24_1_2 */
- BAP_QOS("24_2_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_10, false, 60, 4, 60, 40000, 2, "high-reliabilty"), /* 24_2_2 */
- BAP_QOS("32_1_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_7_5, false, 60, 4, 45, 40000, 13, "high-reliabilty"), /* 32_1_2 */
- BAP_QOS("32_2_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_10, false, 80, 4, 60, 40000, 3, "high-reliabilty"), /* 32_2_2 */
- BAP_QOS("441_1_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_7_5, true, 97, 4, 54, 40000, 14, "high-reliabilty"), /* 441_1_2 */
- BAP_QOS("441_2_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_10, true, 130, 4, 60, 40000, 4, "high-reliabilty"), /* 441_2_2 */
- BAP_QOS("48_1_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 75, 4, 50, 40000, 15, "high-reliabilty"), /* 48_1_2 */
- BAP_QOS("48_2_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 100, 4, 65, 40000, 5, "high-reliabilty"), /* 48_2_2 */
- BAP_QOS("48_3_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 90, 4, 50, 40000, 16, "high-reliabilty"), /* 48_3_2 */
- BAP_QOS("48_4_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 120, 4, 65, 40000, 6, "high-reliabilty"), /* 48_4_2 */
- BAP_QOS("48_5_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 117, 4, 50, 40000, 17, "high-reliabilty"), /* 48_5_2 */
- BAP_QOS("48_6_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 155, 4, 65, 40000, 7, "high-reliabilty"), /* 48_6_2 */
+ BAP_QOS("8_1_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_7_5, false, 26, 4, 45, 40000, 10, "high-reliability"), /* 8_1_2 */
+ BAP_QOS("8_2_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_10, false, 30, 4, 60, 40000, 0, "high-reliability"), /* 8_2_2 */
+ BAP_QOS("16_1_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_7_5, false, 30, 4, 45, 40000, 11, "high-reliability"), /* 16_1_2 */
+ BAP_QOS("16_2_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_10, false, 40, 4, 60, 40000, 1, "high-reliability"), /* 16_2_2 */
+ BAP_QOS("24_1_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_7_5, false, 45, 4, 45, 40000, 12, "high-reliability"), /* 24_1_2 */
+ BAP_QOS("24_2_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_10, false, 60, 4, 60, 40000, 2, "high-reliability"), /* 24_2_2 */
+ BAP_QOS("32_1_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_7_5, false, 60, 4, 45, 40000, 13, "high-reliability"), /* 32_1_2 */
+ BAP_QOS("32_2_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_10, false, 80, 4, 60, 40000, 3, "high-reliability"), /* 32_2_2 */
+ BAP_QOS("441_1_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_7_5, true, 97, 4, 54, 40000, 14, "high-reliability"), /* 441_1_2 */
+ BAP_QOS("441_2_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_10, true, 130, 4, 60, 40000, 4, "high-reliability"), /* 441_2_2 */
+ BAP_QOS("48_1_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 75, 4, 50, 40000, 15, "high-reliability"), /* 48_1_2 */
+ BAP_QOS("48_2_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 100, 4, 65, 40000, 5, "high-reliability"), /* 48_2_2 */
+ BAP_QOS("48_3_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 90, 4, 50, 40000, 16, "high-reliability"), /* 48_3_2 */
+ BAP_QOS("48_4_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 120, 4, 65, 40000, 6, "high-reliability"), /* 48_4_2 */
+ BAP_QOS("48_5_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 117, 4, 50, 40000, 17, "high-reliability"), /* 48_5_2 */
+ BAP_QOS("48_6_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 155, 4, 65, 40000, 7, "high-reliability"), /* 48_6_2 */
};
static unsigned int get_rate_mask(uint8_t rate) {
@@ -1503,6 +1503,10 @@ static int codec_get_bis_config(const struct media_codec *codec, uint8_t *caps,
struct ltv_writer writer = LTV_WRITER(caps, *caps_size);
const struct bap_qos *preset = NULL;
+ uint32_t retransmissions = 0;
+ uint8_t rtn_manual_set = 0;
+ uint32_t max_transport_latency = 0;
+ uint32_t presentation_delay = 0;
*caps_size = 0;
if (settings) {
@@ -1511,6 +1515,14 @@ static int codec_get_bis_config(const struct media_codec *codec, uint8_t *caps,
sscanf(settings->items[i].value, "%"PRIu32, &channel_allocation);
if (spa_streq(settings->items[i].key, "preset"))
preset_name = settings->items[i].value;
+ if (spa_streq(settings->items[i].key, "max_transport_latency"))
+ spa_atou32(settings->items[i].value, &max_transport_latency, 0);
+ if (spa_streq(settings->items[i].key, "presentation_delay"))
+ spa_atou32(settings->items[i].value, &presentation_delay, 0);
+ if (spa_streq(settings->items[i].key, "retransmissions")) {
+ spa_atou32(settings->items[i].value, &retransmissions, 0);
+ rtn_manual_set = 1;
+ }
}
}
@@ -1537,9 +1549,9 @@ static int codec_get_bis_config(const struct media_codec *codec, uint8_t *caps,
else
qos->framing = 0;
qos->sdu = preset->framelen * get_channel_count(channel_allocation);
- qos->retransmission = preset->retransmission;
- qos->latency = preset->latency;
- qos->delay = preset->delay;
+ qos->retransmission = rtn_manual_set ? retransmissions : preset->retransmission;
+ qos->latency = max_transport_latency ? max_transport_latency : preset->latency;
+ qos->delay = presentation_delay ? presentation_delay : preset->delay;
qos->phy = 2;
qos->interval = (preset->frame_duration == LC3_CONFIG_DURATION_7_5 ? 7500 : 10000);
diff --git a/spa/plugins/bluez5/bluez5-dbus.c b/spa/plugins/bluez5/bluez5-dbus.c
index 21a5e53de..22d971c37 100644
--- a/spa/plugins/bluez5/bluez5-dbus.c
+++ b/spa/plugins/bluez5/bluez5-dbus.c
@@ -188,9 +188,16 @@ struct spa_bt_metadata {
uint8_t value[METADATA_MAX_LEN - 1];
};
+#define RTN_MAX 0x1E
+#define MAX_TRANSPORT_LATENCY_MIN 0x5
+#define MAX_TRANSPORT_LATENCY_MAX 0x0FA0
+
struct spa_bt_bis {
struct spa_list link;
char qos_preset[255];
+ int retransmissions;
+ int rtn_manual_set;
+ int max_transport_latency;
int channel_allocation;
struct spa_list metadata_list;
};
@@ -587,18 +594,35 @@ static enum spa_bt_profile get_codec_profile(const struct media_codec *codec,
{
switch (direction) {
case SPA_BT_MEDIA_SOURCE:
- return codec->kind == MEDIA_CODEC_BAP ? SPA_BT_PROFILE_BAP_SOURCE : SPA_BT_PROFILE_A2DP_SOURCE;
+ if (codec->kind == MEDIA_CODEC_A2DP)
+ return SPA_BT_PROFILE_A2DP_SOURCE;
+ else if (codec->kind == MEDIA_CODEC_BAP)
+ return SPA_BT_PROFILE_BAP_SOURCE;
+ else if (codec->kind == MEDIA_CODEC_HFP)
+ return SPA_BT_PROFILE_HEADSET_AUDIO;
+ else
+ return SPA_BT_PROFILE_NULL;
case SPA_BT_MEDIA_SINK:
- if (codec->kind == MEDIA_CODEC_ASHA)
+ if (codec->kind == MEDIA_CODEC_A2DP)
+ return SPA_BT_PROFILE_A2DP_SINK;
+ else if (codec->kind == MEDIA_CODEC_ASHA)
return SPA_BT_PROFILE_ASHA_SINK;
else if (codec->kind == MEDIA_CODEC_BAP)
return SPA_BT_PROFILE_BAP_SINK;
+ else if (codec->kind == MEDIA_CODEC_HFP)
+ return SPA_BT_PROFILE_HEADSET_AUDIO;
else
- return SPA_BT_PROFILE_A2DP_SINK;
+ return SPA_BT_PROFILE_NULL;
case SPA_BT_MEDIA_SOURCE_BROADCAST:
- return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE;
+ if (codec->kind == MEDIA_CODEC_BAP)
+ return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE;
+ else
+ return SPA_BT_PROFILE_NULL;
case SPA_BT_MEDIA_SINK_BROADCAST:
- return SPA_BT_PROFILE_BAP_BROADCAST_SINK;
+ if (codec->kind == MEDIA_CODEC_BAP)
+ return SPA_BT_PROFILE_BAP_BROADCAST_SINK;
+ else
+ return SPA_BT_PROFILE_NULL;
default:
spa_assert_not_reached();
}
@@ -720,14 +744,12 @@ static const char *bap_features_get_uuid(struct bap_features *feat, size_t i)
/** Get feature name at \a i, or NULL if uuid doesn't match */
static const char *bap_features_get_name(struct bap_features *feat, size_t i, const char *uuid)
{
- char *pos;
-
if (i >= feat->dict.n_items)
return NULL;
if (!spa_streq(feat->dict.items[i].value, uuid))
return NULL;
- pos = strchr(feat->dict.items[i].key, ':');
+ const char *pos = strchr(feat->dict.items[i].key, ':');
if (!pos)
return NULL;
return pos + 1;
@@ -1336,7 +1358,6 @@ static struct spa_bt_adapter *adapter_find(struct spa_bt_monitor *monitor, const
static int parse_modalias(const char *modalias, uint16_t *source, uint16_t *vendor,
uint16_t *product, uint16_t *version)
{
- char *pos;
unsigned int src, i, j, k;
if (spa_strstartswith(modalias, "bluetooth:"))
@@ -1346,7 +1367,7 @@ static int parse_modalias(const char *modalias, uint16_t *source, uint16_t *vend
else
return -EINVAL;
- pos = strchr(modalias, ':');
+ const char *pos = strchr(modalias, ':');
if (pos == NULL)
return -EINVAL;
@@ -2780,16 +2801,18 @@ bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const stru
bool is_bap = codec->kind == MEDIA_CODEC_BAP;
size_t i;
- codec_target_profile = get_codec_target_profile(monitor, codec);
- if (!codec_target_profile)
- return false;
-
if (codec->kind == MEDIA_CODEC_HFP) {
if (!(profile & SPA_BT_PROFILE_HEADSET_AUDIO))
return false;
+ if (!is_media_codec_enabled(monitor, codec))
+ return false;
return spa_bt_backend_supports_codec(monitor->backend, device, codec->codec_id) == 1;
}
+ codec_target_profile = get_codec_target_profile(monitor, codec);
+ if (!codec_target_profile)
+ return false;
+
if (!device->adapter->a2dp_application_registered && is_a2dp) {
/* Codec switching not supported: only plain SBC allowed */
return (codec->codec_id == A2DP_CODEC_SBC && spa_streq(codec->name, "sbc") &&
@@ -6176,8 +6199,11 @@ static void configure_bis(struct spa_bt_monitor *monitor,
struct bap_codec_qos qos;
struct spa_bt_metadata *metadata_entry;
struct spa_dict settings;
- struct spa_dict_item setting_items[2];
+ struct spa_dict_item setting_items[4];
+ uint32_t n_items = 0;
char channel_allocation[64] = {0};
+ char retransmissions[3] = {0};
+ char max_transport_latency[5] = {0};
int mse = 0;
int options = 0;
@@ -6202,12 +6228,27 @@ static void configure_bis(struct spa_bt_monitor *monitor,
metadata_size += metadata_entry->length - 1;
}
+
spa_log_debug(monitor->log, "bis->channel_allocation %d", bis->channel_allocation);
- if (bis->channel_allocation)
+ if (bis->channel_allocation) {
spa_scnprintf(channel_allocation, sizeof(channel_allocation), "%"PRIu32, bis->channel_allocation);
- setting_items[0] = SPA_DICT_ITEM_INIT("channel_allocation", channel_allocation);
- setting_items[1] = SPA_DICT_ITEM_INIT("preset", bis->qos_preset);
- settings = SPA_DICT_INIT(setting_items, 2);
+ }
+ spa_log_debug(monitor->log, "bis->rtn_manual_set %d", bis->rtn_manual_set);
+ spa_log_debug(monitor->log, "bis->retransmissions %d", bis->retransmissions);
+ if (bis->rtn_manual_set) {
+ spa_scnprintf(retransmissions, sizeof(retransmissions), "%"PRIu8, bis->retransmissions);
+ setting_items[n_items++] = SPA_DICT_ITEM_INIT("retransmissions", retransmissions);
+ }
+ spa_log_debug(monitor->log, "bis->max_transport_latency %d", bis->max_transport_latency);
+ if (bis->max_transport_latency) {
+ spa_scnprintf(max_transport_latency, sizeof(max_transport_latency), "%"PRIu32, bis->max_transport_latency);
+ setting_items[n_items++] = SPA_DICT_ITEM_INIT("max_transport_latency", max_transport_latency);
+ }
+
+ setting_items[n_items++] = SPA_DICT_ITEM_INIT("preset", bis->qos_preset);
+ setting_items[n_items++] = SPA_DICT_ITEM_INIT("channel_allocation", channel_allocation);
+
+ settings = SPA_DICT_INIT(setting_items, n_items);
caps_size = sizeof(caps);
ret = codec->get_bis_config(codec, caps, &caps_size, &settings, &qos);
@@ -7081,7 +7122,7 @@ static void parse_broadcast_source_config(struct spa_bt_monitor *monitor, const
memcpy(big_entry->broadcast_code, bcode, strlen(bcode));
spa_log_debug(monitor->log, "big_entry->broadcast_code %s", big_entry->broadcast_code);
} else if (spa_streq(key, "adapter")) {
- if (spa_json_get_string(&it[1], big_entry->adapter, sizeof(big_entry->adapter)) <= 0)
+ if (spa_json_get_string(&it[0], big_entry->adapter, sizeof(big_entry->adapter)) <= 0)
goto parse_failed;
spa_log_debug(monitor->log, "big_entry->adapter %s", big_entry->adapter);
} else if (spa_streq(key, "encryption")) {
@@ -7110,6 +7151,20 @@ static void parse_broadcast_source_config(struct spa_bt_monitor *monitor, const
if (spa_json_get_string(&it[1], bis_entry->qos_preset, sizeof(bis_entry->qos_preset)) <= 0)
goto parse_failed;
spa_log_debug(monitor->log, "bis_entry->qos_preset %s", bis_entry->qos_preset);
+ } else if (spa_streq(bis_key, "retransmissions")) {
+ if (spa_json_get_int(&it[2], &bis_entry->retransmissions) <= 0)
+ goto parse_failed;
+ if (bis_entry->retransmissions > RTN_MAX)
+ goto parse_failed;
+ bis_entry->rtn_manual_set = 1;
+ spa_log_debug(monitor->log, "bis_entry->retransmissions %d", bis_entry->retransmissions);
+ } else if (spa_streq(bis_key, "max_transport_latency")) {
+ if (spa_json_get_int(&it[2], &bis_entry->max_transport_latency) <= 0)
+ goto parse_failed;
+ if (bis_entry->max_transport_latency < MAX_TRANSPORT_LATENCY_MIN &&
+ bis_entry->max_transport_latency > MAX_TRANSPORT_LATENCY_MAX)
+ goto parse_failed;
+ spa_log_debug(monitor->log, "bis_entry->max_transport_latency %d", bis_entry->max_transport_latency);
} else if (spa_streq(bis_key, "audio_channel_allocation")) {
if (spa_json_get_int(&it[1], &bis_entry->channel_allocation) <= 0)
goto parse_failed;
diff --git a/spa/plugins/bluez5/bluez5-device.c b/spa/plugins/bluez5/bluez5-device.c
index fc659f655..897241954 100644
--- a/spa/plugins/bluez5/bluez5-device.c
+++ b/spa/plugins/bluez5/bluez5-device.c
@@ -59,6 +59,8 @@ enum device_profile {
DEVICE_PROFILE_OFF = 0,
DEVICE_PROFILE_AG,
DEVICE_PROFILE_A2DP,
+ DEVICE_PROFILE_A2DP_AUTO_PREFER_QUALITY,
+ DEVICE_PROFILE_A2DP_AUTO_PREFER_LATENCY,
DEVICE_PROFILE_HSP_HFP,
DEVICE_PROFILE_BAP,
DEVICE_PROFILE_BAP_SINK,
@@ -67,6 +69,12 @@ enum device_profile {
DEVICE_PROFILE_LAST,
};
+enum codec_order {
+ CODEC_ORDER_NONE = 0,
+ CODEC_ORDER_QUALITY,
+ CODEC_ORDER_LATENCY,
+};
+
enum {
ROUTE_INPUT = 0,
ROUTE_OUTPUT,
@@ -204,9 +212,89 @@ static bool profile_is_bap(enum device_profile profile)
return false;
}
-static void get_media_codecs(struct impl *this, enum spa_bluetooth_audio_codec id, const struct media_codec **codecs, size_t size)
+static bool profile_is_a2dp(enum device_profile profile)
+{
+ switch (profile) {
+ case DEVICE_PROFILE_A2DP:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_QUALITY:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_LATENCY:
+ return true;
+ default:
+ break;
+ }
+ return false;
+}
+
+static size_t get_media_codec_quality_priority (const struct media_codec *mc)
+{
+ /* From lowest quality to highest quality */
+ static const enum spa_bluetooth_audio_codec quality_priorities[] = {
+ SPA_BLUETOOTH_AUDIO_CODEC_START,
+ SPA_BLUETOOTH_AUDIO_CODEC_SBC,
+ SPA_BLUETOOTH_AUDIO_CODEC_APTX,
+ SPA_BLUETOOTH_AUDIO_CODEC_AAC,
+ SPA_BLUETOOTH_AUDIO_CODEC_OPUS_G,
+ SPA_BLUETOOTH_AUDIO_CODEC_LC3PLUS_HR,
+ SPA_BLUETOOTH_AUDIO_CODEC_APTX_HD,
+ SPA_BLUETOOTH_AUDIO_CODEC_LDAC,
+ };
+ size_t i;
+
+ for (i = 0; i < SPA_N_ELEMENTS(quality_priorities); ++i) {
+ if (quality_priorities[i] == mc->id)
+ return i;
+ }
+
+ return 0;
+}
+
+static size_t get_media_codec_latency_priority (const struct media_codec *mc)
+{
+ /* From highest latency to lowest latency */
+ static const enum spa_bluetooth_audio_codec latency_priorities[] = {
+ SPA_BLUETOOTH_AUDIO_CODEC_START,
+ SPA_BLUETOOTH_AUDIO_CODEC_SBC,
+ SPA_BLUETOOTH_AUDIO_CODEC_APTX,
+ SPA_BLUETOOTH_AUDIO_CODEC_OPUS_G,
+ SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX,
+ SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM,
+ SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL_DUPLEX,
+ SPA_BLUETOOTH_AUDIO_CODEC_APTX_LL,
+ };
+ size_t i;
+
+ for (i = 0; i < SPA_N_ELEMENTS(latency_priorities); ++i) {
+ if (latency_priorities[i] == mc->id)
+ return i;
+ }
+
+ return 0;
+}
+
+static int media_codec_quality_cmp(const void *a, const void *b) {
+ const struct media_codec *ca = *(const struct media_codec **)a;
+ const struct media_codec *cb = *(const struct media_codec **)b;
+ size_t ca_prio = get_media_codec_quality_priority (ca);
+ size_t cb_prio = get_media_codec_quality_priority (cb);
+ if (ca_prio > cb_prio) return -1;
+ if (ca_prio < cb_prio) return 1;
+ return 0;
+}
+
+static int media_codec_latency_cmp(const void *a, const void *b) {
+ const struct media_codec *ca = *(const struct media_codec **)a;
+ const struct media_codec *cb = *(const struct media_codec **)b;
+ size_t ca_prio = get_media_codec_latency_priority (ca);
+ size_t cb_prio = get_media_codec_latency_priority (cb);
+ if (ca_prio > cb_prio) return -1;
+ if (ca_prio < cb_prio) return 1;
+ return 0;
+}
+
+static void get_media_codecs(struct impl *this, enum codec_order order, enum spa_bluetooth_audio_codec id, const struct media_codec **codecs, size_t size)
{
const struct media_codec * const *c;
+ size_t n = 0;
spa_assert(size > 0);
spa_assert(this->supported_codecs);
@@ -216,12 +304,24 @@ static void get_media_codecs(struct impl *this, enum spa_bluetooth_audio_codec i
continue;
if ((*c)->id == id || id == 0) {
- *codecs++ = *c;
+ codecs[n++] = *c;
--size;
}
}
- *codecs = NULL;
+ codecs[n] = NULL;
+
+ switch (order) {
+ case CODEC_ORDER_QUALITY:
+ qsort(codecs, n, sizeof(struct media_codec *), media_codec_quality_cmp);
+ break;
+ case CODEC_ORDER_LATENCY:
+ qsort(codecs, n, sizeof(struct media_codec *), media_codec_latency_cmp);
+ break;
+ case CODEC_ORDER_NONE:
+ default:
+ break;
+ }
}
static const struct media_codec *get_supported_media_codec(struct impl *this, enum spa_bluetooth_audio_codec id,
@@ -380,6 +480,8 @@ static bool node_update_volume_from_transport(struct node *node, bool reset)
/* PW is the controller for remote device. */
if (impl->profile != DEVICE_PROFILE_A2DP
+ && impl->profile != DEVICE_PROFILE_A2DP_AUTO_PREFER_QUALITY
+ && impl->profile != DEVICE_PROFILE_A2DP_AUTO_PREFER_LATENCY
&& impl->profile != DEVICE_PROFILE_BAP
&& impl->profile != DEVICE_PROFILE_BAP_SINK
&& impl->profile != DEVICE_PROFILE_BAP_SOURCE
@@ -1262,6 +1364,8 @@ static int emit_nodes(struct impl *this)
}
break;
case DEVICE_PROFILE_A2DP:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_QUALITY:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_LATENCY:
if (this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE) {
t = find_transport(this, SPA_BT_PROFILE_A2DP_SOURCE);
if (t) {
@@ -1464,13 +1568,13 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a
* XXX: source-only case, as it will only switch the sink, and we only
* XXX: list the sink codecs here. TODO: fix this
*/
- if ((profile == DEVICE_PROFILE_A2DP || (profile_is_bap(profile) && is_bap_client(this)))
+ if ((profile_is_a2dp (profile) || (profile_is_bap(profile) && is_bap_client(this)))
&& !(this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_SOURCE)) {
int ret;
const struct media_codec *codecs[64];
uint32_t profiles;
- get_media_codecs(this, codec, codecs, SPA_N_ELEMENTS(codecs));
+ get_media_codecs(this, CODEC_ORDER_NONE, codec, codecs, SPA_N_ELEMENTS(codecs));
this->switching_codec = true;
@@ -1487,6 +1591,14 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a
case DEVICE_PROFILE_A2DP:
profiles = this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_DUPLEX;
break;
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_QUALITY:
+ get_media_codecs(this, CODEC_ORDER_QUALITY, 0, codecs, SPA_N_ELEMENTS(codecs));
+ profiles = this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_DUPLEX;
+ break;
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_LATENCY:
+ get_media_codecs(this, CODEC_ORDER_LATENCY, 0, codecs, SPA_N_ELEMENTS(codecs));
+ profiles = this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_DUPLEX;
+ break;
default:
profiles = 0;
break;
@@ -1646,6 +1758,8 @@ static void profiles_changed(void *userdata, uint32_t connected_change)
nodes_changed);
break;
case DEVICE_PROFILE_A2DP:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_QUALITY:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_LATENCY:
nodes_changed = (connected_change & SPA_BT_PROFILE_A2DP_DUPLEX);
spa_log_debug(this->log, "profiles changed: A2DP nodes changed: %d",
nodes_changed);
@@ -1801,6 +1915,8 @@ static uint32_t profile_direction_mask(struct impl *this, uint32_t index, enum s
switch (index) {
case DEVICE_PROFILE_A2DP:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_QUALITY:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_LATENCY:
if (device->connected_profiles & SPA_BT_PROFILE_A2DP_SINK)
have_output = true;
@@ -1850,6 +1966,8 @@ static uint32_t get_profile_from_index(struct impl *this, uint32_t index, uint32
switch (profile) {
case DEVICE_PROFILE_OFF:
case DEVICE_PROFILE_AG:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_QUALITY:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_LATENCY:
*codec = 0;
*next = (profile + 1) << 16;
return profile;
@@ -1884,6 +2002,8 @@ static uint32_t get_index_from_profile(struct impl *this, uint32_t profile, enum
switch (profile) {
case DEVICE_PROFILE_OFF:
case DEVICE_PROFILE_AG:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_QUALITY:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_LATENCY:
return (profile << 16);
case DEVICE_PROFILE_ASHA:
@@ -1977,15 +2097,37 @@ static void set_initial_profile(struct impl *this)
t = find_transport(this, i);
if (t) {
- if (i == SPA_BT_PROFILE_A2DP_SOURCE || i == SPA_BT_PROFILE_BAP_SOURCE)
+ if (i == SPA_BT_PROFILE_A2DP_SOURCE || i == SPA_BT_PROFILE_BAP_SOURCE) {
this->profile = DEVICE_PROFILE_AG;
- else if (i == SPA_BT_PROFILE_BAP_SINK)
+ this->props.codec = t->media_codec->id;
+ } else if (i == SPA_BT_PROFILE_BAP_SINK) {
this->profile = DEVICE_PROFILE_BAP;
- else if (i == SPA_BT_PROFILE_ASHA_SINK)
+ this->props.codec = t->media_codec->id;
+ } else if (i == SPA_BT_PROFILE_ASHA_SINK) {
this->profile = DEVICE_PROFILE_ASHA;
- else
- this->profile = DEVICE_PROFILE_A2DP;
- this->props.codec = t->media_codec->id;
+ this->props.codec = t->media_codec->id;
+ } else {
+ const struct media_codec *codecs[64];
+ const struct media_codec *quality_codec = NULL;
+ int j;
+
+ get_media_codecs(this, CODEC_ORDER_QUALITY, 0, codecs, SPA_N_ELEMENTS(codecs));
+ for (j = 0; codecs[j] != NULL; ++j) {
+ if (codecs[j]->kind == MEDIA_CODEC_A2DP) {
+ quality_codec = codecs[j];
+ break;
+ }
+ }
+
+ if (quality_codec) {
+ this->profile = DEVICE_PROFILE_A2DP_AUTO_PREFER_QUALITY;
+ this->props.codec = quality_codec->id;
+ } else {
+ this->profile = DEVICE_PROFILE_A2DP;
+ this->props.codec = t->media_codec->id;
+ }
+ }
+
spa_log_debug(this->log, "initial profile media profile:%d codec:%d",
this->profile, this->props.codec);
return;
@@ -2123,6 +2265,36 @@ static struct spa_pod *build_profile(struct impl *this, struct spa_pod_builder *
n_source++;
break;
}
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_QUALITY:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_LATENCY:
+ {
+ uint32_t profile;
+
+ /* make this device profile visible only if there is an A2DP sink */
+ profile = device->connected_profiles & (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_A2DP_SOURCE);
+ if (!(profile & SPA_BT_PROFILE_A2DP_SINK))
+ return NULL;
+
+ switch (profile_index) {
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_QUALITY:
+ name = "a2dp-auto-prefer-quality";
+ desc = _("Auto: Prefer Quality (A2DP)");
+ priority = 255;
+ break;
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_LATENCY:
+ name = "a2dp-auto-prefer-latency";
+ desc = _("Auto: Prefer Latency (A2DP)");
+ priority = 254;
+ break;
+ default:
+ return NULL;
+ }
+
+ n_sink++;
+ if (this->autoswitch_routes && (device->connected_profiles & SPA_BT_PROFILE_HEADSET_HEAD_UNIT))
+ n_source++;
+ break;
+ }
case DEVICE_PROFILE_BAP_SINK:
case DEVICE_PROFILE_BAP_SOURCE:
/* These are client-only */
@@ -2324,6 +2496,8 @@ static bool profile_has_route(uint32_t profile, uint32_t route)
case DEVICE_PROFILE_AG:
break;
case DEVICE_PROFILE_A2DP:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_QUALITY:
+ case DEVICE_PROFILE_A2DP_AUTO_PREFER_LATENCY:
switch (route) {
case ROUTE_INPUT:
case ROUTE_OUTPUT:
@@ -2623,7 +2797,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
spa_pod_builder_array(b, sizeof(uint32_t), SPA_TYPE_Id,
node->n_channels, node->channels);
- if ((this->profile == DEVICE_PROFILE_A2DP || profile_is_bap(this->profile)) &&
+ if ((profile_is_a2dp (this->profile) || profile_is_bap(this->profile)) &&
(dev & SINK_ID_FLAG)) {
spa_pod_builder_prop(b, SPA_PROP_latencyOffsetNsec, 0);
spa_pod_builder_long(b, node->latency_offset);
@@ -2659,7 +2833,7 @@ next:
c = this->supported_codecs[*j];
- if (!(this->profile == DEVICE_PROFILE_A2DP && c->kind == MEDIA_CODEC_A2DP) &&
+ if (!(profile_is_a2dp (this->profile) && c->kind == MEDIA_CODEC_A2DP) &&
!(profile_is_bap(this->profile) && c->kind == MEDIA_CODEC_BAP) &&
!(this->profile == DEVICE_PROFILE_HSP_HFP && c->kind == MEDIA_CODEC_HFP) &&
!(this->profile == DEVICE_PROFILE_ASHA && c->kind == MEDIA_CODEC_ASHA))
@@ -3240,7 +3414,7 @@ static int impl_set_param(void *object,
return 0;
}
- if (this->profile == DEVICE_PROFILE_A2DP || profile_is_bap(this->profile) ||
+ if (profile_is_a2dp (this->profile) || profile_is_bap(this->profile) ||
this->profile == DEVICE_PROFILE_ASHA || this->profile == DEVICE_PROFILE_HSP_HFP) {
size_t j;
for (j = 0; j < this->supported_codec_count; ++j) {
diff --git a/spa/plugins/bluez5/iso-io.c b/spa/plugins/bluez5/iso-io.c
index 2cc65a2bf..ce1fd7d0c 100644
--- a/spa/plugins/bluez5/iso-io.c
+++ b/spa/plugins/bluez5/iso-io.c
@@ -411,7 +411,7 @@ static void group_on_timeout(struct spa_source *source)
/* Ensure controller fill level */
fill_count = UINT_MAX;
spa_list_for_each(stream, &group->streams, link) {
- if (!stream->sink || !group->started)
+ if (!stream->sink || !group->started || !stream->tx_latency.enabled)
continue;
if (stream->tx_latency.queue < MIN_FILL)
fill_count = SPA_MIN(fill_count, MIN_FILL - stream->tx_latency.queue);
diff --git a/spa/plugins/bluez5/media-source.c b/spa/plugins/bluez5/media-source.c
index da2e57b5a..e1c01d90c 100644
--- a/spa/plugins/bluez5/media-source.c
+++ b/spa/plugins/bluez5/media-source.c
@@ -1784,7 +1784,7 @@ static uint32_t get_samples(struct impl *this, int64_t *duration_ns)
static void update_target_latency(struct impl *this)
{
struct port *port = &this->port;
- int32_t target;
+ int32_t target = 0;
int samples;
if (this->transport == NULL || !port->have_format)
@@ -1803,7 +1803,7 @@ static void update_target_latency(struct impl *this)
*/
if (this->decode_buffer_target)
target = this->decode_buffer_target;
- else
+ else if (this->transport->iso_io)
target = spa_bt_iso_io_get_source_target_latency(this->transport->iso_io);
spa_bt_decode_buffer_set_target_latency(&port->buffer, target);
diff --git a/spa/plugins/bluez5/midi-node.c b/spa/plugins/bluez5/midi-node.c
index 7146d6f8a..671035b34 100644
--- a/spa/plugins/bluez5/midi-node.c
+++ b/spa/plugins/bluez5/midi-node.c
@@ -23,7 +23,6 @@
#include
#include
#include
-#include
#include
#include
@@ -450,7 +449,7 @@ static void midi_event_recv(void *user_data, uint16_t timestamp, uint8_t *data,
struct impl *this = user_data;
struct port *port = &this->ports[PORT_OUT];
struct time_sync *sync = &port->sync;
- uint64_t time, state = 0;
+ uint64_t time;
int res;
spa_assert(size > 0);
@@ -460,19 +459,11 @@ static void midi_event_recv(void *user_data, uint16_t timestamp, uint8_t *data,
spa_log_trace(this->log, "%p: event:0x%x size:%d timestamp:%d time:%"PRIu64"",
this, (int)data[0], (int)size, (int)timestamp, (uint64_t)time);
- while (size > 0) {
- uint32_t ump[4];
- int ump_size = spa_ump_from_midi(&data, &size,
- ump, sizeof(ump), 0, &state);
- if (ump_size <= 0)
- break;
-
- res = midi_event_ringbuffer_push(&this->event_rbuf, time, (uint8_t*)ump, ump_size);
- if (res < 0) {
- midi_event_ringbuffer_init(&this->event_rbuf);
- spa_log_warn(this->log, "%p: MIDI receive buffer overflow: %s",
- this, spa_strerror(res));
- }
+ res = midi_event_ringbuffer_push(&this->event_rbuf, time, data, size);
+ if (res < 0) {
+ midi_event_ringbuffer_init(&this->event_rbuf);
+ spa_log_warn(this->log, "%p: MIDI receive buffer overflow: %s",
+ this, spa_strerror(res));
}
}
@@ -713,7 +704,7 @@ static int process_output(struct impl *this)
offset = time * this->rate / SPA_NSEC_PER_SEC;
offset = SPA_CLAMP(offset, 0u, this->duration - 1);
- spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP);
+ spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_Midi);
buf = spa_pod_builder_reserve_bytes(&port->builder, size);
if (buf) {
midi_event_ringbuffer_pop(&this->event_rbuf, buf, size);
@@ -786,37 +777,28 @@ static int write_data(struct impl *this, struct spa_data *d)
time = 0;
while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) {
- int size;
- uint8_t event[32];
- const uint32_t *ump = c_body;
- size_t ump_size = c.value.size;
- uint64_t state = 0;
+ const uint8_t *event = c_body;
+ uint32_t size = c.value.size;
- if (c.type != SPA_CONTROL_UMP)
+ if (c.type != SPA_CONTROL_Midi)
continue;
time = SPA_MAX(time, this->current_time + c.offset * SPA_NSEC_PER_SEC / this->rate);
- while (ump_size > 0) {
- size = spa_ump_to_midi(&ump, &ump_size, event, sizeof(event), &state);
- if (size <= 0)
- break;
+ spa_log_trace(this->log, "%p: output event:0x%x time:%"PRIu64, this,
+ (size > 0) ? event[0] : 0, time);
- spa_log_trace(this->log, "%p: output event:0x%x time:%"PRIu64, this,
- (size > 0) ? event[0] : 0, time);
-
- do {
- res = spa_bt_midi_writer_write(&this->writer,
- time, event, size);
- if (res < 0) {
- return res;
- } else if (res) {
- int res2;
- if ((res2 = flush_packet(this)) < 0)
- return res2;
- }
- } while (res);
- }
+ do {
+ res = spa_bt_midi_writer_write(&this->writer,
+ time, event, size);
+ if (res < 0) {
+ return res;
+ } else if (res) {
+ int res2;
+ if ((res2 = flush_packet(this)) < 0)
+ return res2;
+ }
+ } while (res);
}
if ((res = flush_packet(this)) < 0)
diff --git a/spa/plugins/control/mixer.c b/spa/plugins/control/mixer.c
index caf3a6b06..1b106d3f7 100644
--- a/spa/plugins/control/mixer.c
+++ b/spa/plugins/control/mixer.c
@@ -72,6 +72,7 @@ struct impl {
struct spa_node node;
uint32_t quantum_limit;
+ uint32_t control_types;
struct spa_log *log;
@@ -473,9 +474,9 @@ static int port_set_format(void *object,
if (!port->have_format) {
this->n_formats++;
port->have_format = true;
- port->types = types;
- spa_log_debug(this->log, "%p: set format on port %d:%d",
- this, direction, port_id);
+ port->types = types == 0 ? this->control_types : types;
+ spa_log_debug(this->log, "%p: set format on port %d:%d types:%08x %08x",
+ this, direction, port_id, port->types, this->control_types);
}
}
port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
@@ -589,7 +590,7 @@ static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq,
port->io[0] = info->data;
port->io[1] = info->data;
}
- if (!port->active) {
+ if (port->direction == SPA_DIRECTION_INPUT && !port->active) {
spa_list_append(&info->impl->mix_list, &port->mix_link);
port->active = true;
}
@@ -955,6 +956,8 @@ impl_init(const struct spa_handle_factory *factory,
}
this->quantum_limit = 8192;
+ /* by default we convert to midi1 */
+ this->control_types = 1u<n_items; i++) {
const char *k = info->items[i].key;
@@ -962,6 +965,14 @@ impl_init(const struct spa_handle_factory *factory,
if (spa_streq(k, "clock.quantum-limit")) {
spa_atou32(s, &this->quantum_limit, 0);
}
+ else if (spa_streq(k, "control.ump")) {
+ if (spa_atob(s))
+ /* we convert to UMP when forced */
+ this->control_types = 1u<control_types = 0;
+ }
}
spa_hook_list_init(&this->hooks);
diff --git a/spa/plugins/filter-graph/audio-dsp-avx2.c b/spa/plugins/filter-graph/audio-dsp-avx2.c
index 76c7b17d5..346b26ab3 100644
--- a/spa/plugins/filter-graph/audio-dsp-avx2.c
+++ b/spa/plugins/filter-graph/audio-dsp-avx2.c
@@ -140,10 +140,10 @@ static void dsp_add_n_gain_avx2(void *obj, float *dst,
for (i = 1; i < n_src; i++) {
g = _mm256_set1_ps(gain[i]);
- in[0] = _mm256_add_ps(in[0], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+ 0])));
- in[1] = _mm256_add_ps(in[1], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+ 8])));
- in[2] = _mm256_add_ps(in[2], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+16])));
- in[3] = _mm256_add_ps(in[3], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+24])));
+ in[0] = _mm256_fmadd_ps(g, _mm256_load_ps(&s[i][n+ 0]), in[0]);
+ in[1] = _mm256_fmadd_ps(g, _mm256_load_ps(&s[i][n+ 8]), in[1]);
+ in[2] = _mm256_fmadd_ps(g, _mm256_load_ps(&s[i][n+16]), in[2]);
+ in[3] = _mm256_fmadd_ps(g, _mm256_load_ps(&s[i][n+24]), in[3]);
}
_mm256_store_ps(&d[n+ 0], in[0]);
_mm256_store_ps(&d[n+ 8], in[1]);
@@ -237,13 +237,12 @@ void dsp_sum_avx2(void *obj, float *r, const float *a, const float *b, uint32_t
inline static __m256 _mm256_mul_pz(__m256 ab, __m256 cd)
{
- __m256 aa, bb, dc, x0, x1;
+ __m256 aa, bb, dc, x1;
aa = _mm256_moveldup_ps(ab);
bb = _mm256_movehdup_ps(ab);
- x0 = _mm256_mul_ps(aa, cd);
dc = _mm256_shuffle_ps(cd, cd, _MM_SHUFFLE(2,3,0,1));
x1 = _mm256_mul_ps(bb, dc);
- return _mm256_addsub_ps(x0, x1);
+ return _mm256_fmaddsub_ps(aa, cd, x1);
}
void dsp_fft_cmul_avx2(void *obj, void *fft,
@@ -308,12 +307,10 @@ void dsp_fft_cmuladd_avx2(void *obj, void *fft,
bb[1] = _mm256_load_ps(&b[2*i+8]); /* br2 bi2 br3 bi3 */
dd[0] = _mm256_mul_pz(aa[0], bb[0]);
dd[1] = _mm256_mul_pz(aa[1], bb[1]);
- dd[0] = _mm256_mul_ps(dd[0], s);
- dd[1] = _mm256_mul_ps(dd[1], s);
t[0] = _mm256_load_ps(&src[2*i]);
t[1] = _mm256_load_ps(&src[2*i+8]);
- t[0] = _mm256_add_ps(t[0], dd[0]);
- t[1] = _mm256_add_ps(t[1], dd[1]);
+ t[0] = _mm256_fmadd_ps(dd[0], s, t[0]);
+ t[1] = _mm256_fmadd_ps(dd[1], s, t[1]);
_mm256_store_ps(&dst[2*i], t[0]);
_mm256_store_ps(&dst[2*i+8], t[1]);
}
diff --git a/spa/plugins/filter-graph/audio-dsp.c b/spa/plugins/filter-graph/audio-dsp.c
index 133b53db5..d0c4ef008 100644
--- a/spa/plugins/filter-graph/audio-dsp.c
+++ b/spa/plugins/filter-graph/audio-dsp.c
@@ -24,7 +24,7 @@ struct dsp_info {
static const struct dsp_info dsp_table[] =
{
#if defined (HAVE_AVX2)
- { SPA_CPU_FLAG_AVX2,
+ { SPA_CPU_FLAG_AVX2 | SPA_CPU_FLAG_FMA3,
.funcs.clear = dsp_clear_c,
.funcs.copy = dsp_copy_c,
.funcs.mix_gain = dsp_mix_gain_avx2,
diff --git a/spa/plugins/filter-graph/audio-plugin.h b/spa/plugins/filter-graph/audio-plugin.h
index 9f9d1bce6..fffdc0de5 100644
--- a/spa/plugins/filter-graph/audio-plugin.h
+++ b/spa/plugins/filter-graph/audio-plugin.h
@@ -69,6 +69,7 @@ struct spa_fga_descriptor {
void (*connect_port) (void *instance, unsigned long port, void *data);
void (*control_changed) (void *instance);
+ void (*control_sync) (void *instance);
void (*activate) (void *instance);
void (*deactivate) (void *instance);
diff --git a/spa/plugins/filter-graph/convolver.c b/spa/plugins/filter-graph/convolver.c
index a077c6ec1..788b118e3 100644
--- a/spa/plugins/filter-graph/convolver.c
+++ b/spa/plugins/filter-graph/convolver.c
@@ -171,7 +171,10 @@ static int convolver1_run(struct spa_fga_dsp *dsp, struct convolver1 *conv, cons
if (conv->segCount > 1) {
if (inputBufferFill == 0) {
- int indexAudio = (conv->current + 1) % conv->segCount;
+ int indexAudio = conv->current;
+
+ if (++indexAudio == conv->segCount)
+ indexAudio = 0;
spa_fga_dsp_fft_cmul(dsp, conv->fft, conv->pre_mult,
conv->segmentsIr[1],
@@ -179,7 +182,8 @@ static int convolver1_run(struct spa_fga_dsp *dsp, struct convolver1 *conv, cons
conv->fftComplexSize, conv->scale);
for (i = 2; i < conv->segCount; i++) {
- indexAudio = (conv->current + i) % conv->segCount;
+ if (++indexAudio == conv->segCount)
+ indexAudio = 0;
spa_fga_dsp_fft_cmuladd(dsp, conv->fft,
conv->pre_mult,
@@ -214,9 +218,10 @@ static int convolver1_run(struct spa_fga_dsp *dsp, struct convolver1 *conv, cons
SPA_SWAP(conv->fft_buffer[0], conv->fft_buffer[1]);
- conv->current = (conv->current > 0) ? (conv->current - 1) : (conv->segCount - 1);
+ if (conv->current == 0)
+ conv->current = conv->segCount;
+ conv->current--;
}
-
processed += processing;
}
conv->inputBufferFill = inputBufferFill;
diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c
index 52609f2c6..4b47d0c9f 100644
--- a/spa/plugins/filter-graph/filter-graph.c
+++ b/spa/plugins/filter-graph/filter-graph.c
@@ -19,6 +19,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -221,6 +222,7 @@ struct impl {
struct spa_cpu *cpu;
struct spa_fga_dsp *dsp;
struct spa_plugin_loader *loader;
+ struct spa_loop *data_loop;
uint64_t info_all;
struct spa_filter_graph_info info;
@@ -552,19 +554,25 @@ static int impl_get_props(void *object, struct spa_pod_builder *b, struct spa_po
struct descriptor *desc = node->desc;
const struct spa_fga_descriptor *d = desc->desc;
struct spa_fga_port *p = &d->ports[port->p];
+ float v, min, max;
if (node->name[0] != '\0')
snprintf(name, sizeof(name), "%s:%s", node->name, p->name);
else
snprintf(name, sizeof(name), "%s", p->name);
+ if (port->control_initialized)
+ v = port->control_current;
+ else
+ get_ranges(impl, p, &v, &min, &max);
+
spa_pod_builder_string(b, name);
if (p->hint & SPA_FGA_HINT_BOOLEAN) {
- spa_pod_builder_bool(b, port->control_data[0] <= 0.0f ? false : true);
+ spa_pod_builder_bool(b, v <= 0.0f ? false : true);
} else if (p->hint & SPA_FGA_HINT_INTEGER) {
- spa_pod_builder_int(b, (int32_t)port->control_data[0]);
+ spa_pod_builder_int(b, (int32_t)v);
} else {
- spa_pod_builder_float(b, port->control_data[0]);
+ spa_pod_builder_float(b, v);
}
}
spa_pod_builder_pop(b, &f[1]);
@@ -698,21 +706,46 @@ static int impl_reset(void *object)
return 0;
}
-static void node_control_changed(struct node *node)
+static int
+do_emit_node_control_sync(struct spa_loop *loop, bool async, uint32_t seq, const void *data,
+ size_t size, void *user_data)
{
- const struct spa_fga_descriptor *d = node->desc->desc;
+ struct impl *impl = user_data;
+ struct graph *graph = &impl->graph;
+ struct node *node;
+ uint32_t i;
+ spa_list_for_each(node, &graph->node_list, link) {
+ const struct spa_fga_descriptor *d = node->desc->desc;
+ if (!node->control_changed || d->control_sync == NULL)
+ continue;
+ for (i = 0; i < node->n_hndl; i++) {
+ if (node->hndl[i] != NULL)
+ d->control_sync(node->hndl[i]);
+ }
+ }
+ return 0;
+}
+
+static void emit_node_control_changed(struct impl *impl)
+{
+ struct graph *graph = &impl->graph;
+ struct node *node;
uint32_t i;
- if (!node->control_changed)
- return;
+ spa_loop_locked(impl->data_loop, do_emit_node_control_sync, 1, NULL, 0, impl);
- for (i = 0; i < node->n_hndl; i++) {
- if (node->hndl[i] == NULL)
+ spa_list_for_each(node, &graph->node_list, link) {
+ const struct spa_fga_descriptor *d = node->desc->desc;
+ if (!node->control_changed)
continue;
- if (d->control_changed)
- d->control_changed(node->hndl[i]);
+ if (d->control_changed != NULL) {
+ for (i = 0; i < node->n_hndl; i++) {
+ if (node->hndl[i] != NULL)
+ d->control_changed(node->hndl[i]);
+ }
+ }
+ node->control_changed = false;
}
- node->control_changed = false;
}
static int sync_volume(struct graph *graph, struct volume *vol)
@@ -826,11 +859,7 @@ static int impl_set_props(void *object, enum spa_direction direction, const stru
spa_pod_dynamic_builder_clean(&b);
if (changed > 0) {
- struct node *node;
-
- spa_list_for_each(node, &graph->node_list, link)
- node_control_changed(node);
-
+ emit_node_control_changed(impl);
spa_filter_graph_emit_props_changed(&impl->hooks, SPA_DIRECTION_INPUT);
}
return 0;
@@ -1035,8 +1064,8 @@ static struct descriptor *descriptor_load(struct impl *impl, const char *type,
}
} else if (SPA_FGA_IS_PORT_CONTROL(fp->flags)) {
if (SPA_FGA_IS_PORT_INPUT(fp->flags)) {
- spa_log_info(impl->log, "using port %lu ('%s') as control %d", p,
- fp->name, desc->n_control);
+ spa_log_info(impl->log, "using port %lu ('%s') as control %d %f/%f/%f", p,
+ fp->name, desc->n_control, fp->def, fp->min, fp->max);
desc->control[desc->n_control++] = p;
}
else if (SPA_FGA_IS_PORT_OUTPUT(fp->flags)) {
@@ -1622,6 +1651,7 @@ static int impl_activate(void *object, const struct spa_dict *props)
goto error;
}
}
+ node->control_changed = true;
}
/* then link ports */
@@ -1695,10 +1725,10 @@ static int impl_activate(void *object, const struct spa_dict *props)
for (i = 0; i < node->n_hndl; i++) {
if (d->activate)
d->activate(node->hndl[i]);
- if (node->control_changed && d->control_changed)
- d->control_changed(node->hndl[i]);
}
}
+ emit_node_control_changed(impl);
+
/* calculate latency */
sort_reset(graph);
while ((node = sort_next_node(graph)) != NULL) {
@@ -2346,6 +2376,7 @@ impl_init(const struct spa_handle_factory *factory,
spa_log_topic_init(impl->log, &log_topic);
impl->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU);
+ impl->data_loop = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_DataLoop);
impl->max_align = spa_cpu_get_max_align(impl->cpu);
impl->dsp = spa_fga_dsp_new(impl->cpu ? spa_cpu_get_flags(impl->cpu) : 0);
diff --git a/spa/plugins/filter-graph/meson.build b/spa/plugins/filter-graph/meson.build
index 94ee0bd25..20b90f4c4 100644
--- a/spa/plugins/filter-graph/meson.build
+++ b/spa/plugins/filter-graph/meson.build
@@ -18,16 +18,16 @@ if have_sse
simd_cargs += ['-DHAVE_SSE']
simd_dependencies += filter_graph_sse
endif
-if have_avx2
- filter_graph_avx2 = static_library('filter_graph_avx2',
+if have_avx2 and have_fma
+ filter_graph_avx2_fma = static_library('filter_graph_avx2_fma',
['audio-dsp-avx2.c' ],
include_directories : [configinc],
- c_args : [avx2_args, fma_args,'-O3', '-DHAVE_AVX2'],
+ c_args : [avx2_args, fma_args, '-O3', '-DHAVE_AVX2', '-DHAVE_FMA'],
dependencies : [ spa_dep ],
install : false
)
- simd_cargs += ['-DHAVE_AVX2']
- simd_dependencies += filter_graph_avx2
+ simd_cargs += ['-DHAVE_AVX2', '-DHAVE_FMA']
+ simd_dependencies += filter_graph_avx2_fma
endif
if have_neon
filter_graph_neon = static_library('filter_graph_neon',
diff --git a/spa/plugins/filter-graph/plugin_builtin.c b/spa/plugins/filter-graph/plugin_builtin.c
index 3bcde30c9..3c15673db 100644
--- a/spa/plugins/filter-graph/plugin_builtin.c
+++ b/spa/plugins/filter-graph/plugin_builtin.c
@@ -21,6 +21,7 @@
#include
#include
#include
+#include
#include
#include
@@ -542,7 +543,12 @@ static void bq_run(void *Instance, unsigned long samples)
struct biquad *bq = &impl->bq;
float *out = impl->port[0];
float *in = impl->port[1];
+ spa_fga_dsp_biquad_run(impl->dsp, bq, 1, 0, &out, (const float **)&in, 1, samples);
+}
+static void bq_control_sync(void * Instance)
+{
+ struct builtin *impl = Instance;
if (impl->type == BQ_NONE) {
float b0, b1, b2, a0, a1, a2;
b0 = impl->port[5][0];
@@ -562,7 +568,6 @@ static void bq_run(void *Instance, unsigned long samples)
if (impl->freq != freq || impl->Q != Q || impl->gain != gain)
bq_freq_update(impl, impl->type, freq, Q, gain);
}
- spa_fga_dsp_biquad_run(impl->dsp, bq, 1, 0, &out, (const float **)&in, 1, samples);
}
/** bq_lowpass */
@@ -574,6 +579,7 @@ static const struct spa_fga_descriptor bq_lowpass_desc = {
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
+ .control_sync = bq_control_sync,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
@@ -588,6 +594,7 @@ static const struct spa_fga_descriptor bq_highpass_desc = {
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
+ .control_sync = bq_control_sync,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
@@ -602,6 +609,7 @@ static const struct spa_fga_descriptor bq_bandpass_desc = {
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
+ .control_sync = bq_control_sync,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
@@ -616,6 +624,7 @@ static const struct spa_fga_descriptor bq_lowshelf_desc = {
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
+ .control_sync = bq_control_sync,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
@@ -630,6 +639,7 @@ static const struct spa_fga_descriptor bq_highshelf_desc = {
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
+ .control_sync = bq_control_sync,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
@@ -644,6 +654,7 @@ static const struct spa_fga_descriptor bq_peaking_desc = {
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
+ .control_sync = bq_control_sync,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
@@ -658,6 +669,7 @@ static const struct spa_fga_descriptor bq_notch_desc = {
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
+ .control_sync = bq_control_sync,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
@@ -673,6 +685,7 @@ static const struct spa_fga_descriptor bq_allpass_desc = {
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
+ .control_sync = bq_control_sync,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
@@ -687,6 +700,7 @@ static const struct spa_fga_descriptor bq_raw_desc = {
.instantiate = bq_instantiate,
.connect_port = builtin_connect_port,
+ .control_sync = bq_control_sync,
.activate = bq_activate,
.run = bq_run,
.cleanup = builtin_cleanup,
@@ -1453,6 +1467,7 @@ static struct spa_fga_port clamp_ports[] = {
{ .index = 3,
.name = "Control",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
+ .def = 0.0f, .min = -FLT_MAX, .max = FLT_MAX
},
{ .index = 4,
.name = "Min",
@@ -1510,6 +1525,7 @@ static struct spa_fga_port linear_ports[] = {
{ .index = 3,
.name = "Control",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
+ .def = 0.0f, .min = -FLT_MAX, .max = FLT_MAX
},
{ .index = 4,
.name = "Mult",
@@ -1577,6 +1593,7 @@ static struct spa_fga_port recip_ports[] = {
{ .index = 3,
.name = "Control",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
+ .def = 0.0f, .min = -FLT_MAX, .max = FLT_MAX
},
};
@@ -1626,6 +1643,7 @@ static struct spa_fga_port exp_ports[] = {
{ .index = 3,
.name = "Control",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
+ .def = 0.0f, .min = -FLT_MAX, .max = FLT_MAX
},
{ .index = 4,
.name = "Base",
@@ -1683,6 +1701,7 @@ static struct spa_fga_port log_ports[] = {
{ .index = 3,
.name = "Control",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
+ .def = 0.0f, .min = -FLT_MAX, .max = FLT_MAX
},
{ .index = 4,
.name = "Base",
@@ -2472,10 +2491,12 @@ static struct spa_fga_port ramp_ports[] = {
{ .index = 1,
.name = "Start",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
+ .def = 0.0f, .min = -FLT_MAX, .max = FLT_MAX
},
{ .index = 2,
.name = "Stop",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
+ .def = 1.0f, .min = -FLT_MAX, .max = FLT_MAX
},
{ .index = 3,
.name = "Current",
@@ -2484,6 +2505,7 @@ static struct spa_fga_port ramp_ports[] = {
{ .index = 4,
.name = "Duration (s)",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
+ .def = 1.0f, .min = 0.0f, .max = FLT_MAX
},
};
@@ -2643,6 +2665,7 @@ static struct spa_fga_port debug_ports[] = {
{ .index = 2,
.name = "Control",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
+ .def = 0.0f, .min = -FLT_MAX, .max = FLT_MAX
},
{ .index = 3,
.name = "Notify",
@@ -2989,7 +3012,7 @@ static struct spa_fga_port noisegate_ports[] = {
{ .index = 2,
.name = "Level",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
- .def = NAN
+ .def = NAN, .min = -FLT_MAX, .max = FLT_MAX
},
{ .index = 3,
.name = "Open Threshold",
@@ -3230,6 +3253,7 @@ static struct spa_fga_port null_ports[] = {
{ .index = 1,
.name = "Control",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
+ .def = 0.0f, .min = -FLT_MAX, .max = FLT_MAX
},
};
diff --git a/spa/plugins/filter-graph/plugin_ebur128.c b/spa/plugins/filter-graph/plugin_ebur128.c
index fb79de590..02fbcbe53 100644
--- a/spa/plugins/filter-graph/plugin_ebur128.c
+++ b/spa/plugins/filter-graph/plugin_ebur128.c
@@ -399,6 +399,7 @@ static struct spa_fga_port lufs2gain_ports[] = {
{ .index = 0,
.name = "LUFS",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
+ .def = 0.0f, .min = 0.0f, .max = FLT_MAX
},
{ .index = 1,
.name = "Gain",
diff --git a/spa/plugins/filter-graph/plugin_ladspa.c b/spa/plugins/filter-graph/plugin_ladspa.c
index d5c8ef488..6335d002f 100644
--- a/spa/plugins/filter-graph/plugin_ladspa.c
+++ b/spa/plugins/filter-graph/plugin_ladspa.c
@@ -7,6 +7,7 @@
#include
#include
#include
+#include
#include
#include
@@ -113,8 +114,14 @@ static void ladspa_port_update_ranges(struct descriptor *dd, struct spa_fga_port
LADSPA_PortRangeHintDescriptor hint = d->PortRangeHints[p].HintDescriptor;
LADSPA_Data lower, upper;
- lower = d->PortRangeHints[p].LowerBound;
- upper = d->PortRangeHints[p].UpperBound;
+ if (hint & LADSPA_HINT_BOUNDED_BELOW)
+ lower = d->PortRangeHints[p].LowerBound;
+ else
+ lower = -FLT_MAX;
+ if (hint & LADSPA_HINT_BOUNDED_ABOVE)
+ upper = d->PortRangeHints[p].UpperBound;
+ else
+ upper = FLT_MAX;
port->hint = 0;
if (hint & LADSPA_HINT_TOGGLED)
@@ -226,43 +233,49 @@ static inline const char *split_walk(const char *str, const char *delimiter, siz
return s;
}
-static int load_ladspa_plugin(struct plugin *impl, const char *path)
+static void make_search_paths(const char **path, const char **search_dirs)
+{
+ const char *p;
+
+ while ((p = strstr(*path, "../")) != NULL)
+ *path = p + 3;
+
+ *search_dirs = getenv("LADSPA_PATH");
+ if (!*search_dirs)
+ *search_dirs = "/usr/lib64/ladspa:/usr/lib/ladspa:" LIBDIR;
+}
+
+static int load_ladspa_plugin(struct plugin *impl, const char *path, const char *search_dirs)
{
int res = -ENOENT;
+ const char *p, *state = NULL;
+ char filename[PATH_MAX];
+ size_t len;
- if (path[0] != '/') {
- const char *search_dirs, *p, *state = NULL;
- char filename[PATH_MAX];
- size_t len;
+ /*
+ * set the errno for the case when `ladspa_handle_load_by_path()`
+ * is never called, which can only happen if the supplied
+ * LADSPA_PATH contains too long paths
+ */
+ res = -ENAMETOOLONG;
- search_dirs = getenv("LADSPA_PATH");
- if (!search_dirs)
- search_dirs = "/usr/lib64/ladspa:/usr/lib/ladspa:" LIBDIR;
+ while ((p = split_walk(search_dirs, ":", &len, &state))) {
+ int namelen;
- /*
- * set the errno for the case when `ladspa_handle_load_by_path()`
- * is never called, which can only happen if the supplied
- * LADSPA_PATH contains too long paths
- */
- res = -ENAMETOOLONG;
-
- while ((p = split_walk(search_dirs, ":", &len, &state))) {
- int namelen;
-
- if (len >= sizeof(filename))
- continue;
+ if (len == 0 || len >= sizeof(filename))
+ continue;
+ if (strncmp(path, p, len) == 0 && path[len] == '/')
+ namelen = snprintf(filename, sizeof(filename), "%s", path);
+ else
namelen = snprintf(filename, sizeof(filename), "%.*s/%s.so", (int) len, p, path);
- if (namelen < 0 || (size_t) namelen >= sizeof(filename))
- continue;
- res = ladspa_handle_load_by_path(impl, filename);
- if (res >= 0)
- break;
- }
- }
- else {
- res = ladspa_handle_load_by_path(impl, path);
+ if (namelen < 0 || (size_t) namelen >= sizeof(filename))
+ continue;
+
+ res = ladspa_handle_load_by_path(impl, filename);
+ if (res >= 0)
+ break;
}
return res;
}
@@ -310,7 +323,7 @@ impl_init(const struct spa_handle_factory *factory,
struct plugin *impl;
uint32_t i;
int res;
- const char *path = NULL;
+ const char *path = NULL, *search_dirs;
handle->get_interface = impl_get_interface;
handle->clear = impl_clear;
@@ -328,9 +341,11 @@ impl_init(const struct spa_handle_factory *factory,
if (path == NULL)
return -EINVAL;
- if ((res = load_ladspa_plugin(impl, path)) < 0) {
- spa_log_error(impl->log, "failed to load plugin '%s': %s",
- path, spa_strerror(res));
+ make_search_paths(&path, &search_dirs);
+
+ if ((res = load_ladspa_plugin(impl, path, search_dirs)) < 0) {
+ spa_log_error(impl->log, "failed to load plugin '%s' in '%s': %s",
+ path, search_dirs, spa_strerror(res));
return res;
}
diff --git a/spa/plugins/filter-graph/plugin_lv2.c b/spa/plugins/filter-graph/plugin_lv2.c
index 712b728e2..b2d3fc6cc 100644
--- a/spa/plugins/filter-graph/plugin_lv2.c
+++ b/spa/plugins/filter-graph/plugin_lv2.c
@@ -560,6 +560,17 @@ static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const
fp->min = mins[i];
fp->max = maxes[i];
fp->def = controls[i];
+
+ if (isnan(fp->min))
+ fp->min = -FLT_MAX;
+ if (isnan(fp->max))
+ fp->max = FLT_MAX;
+ if (isnan(fp->def))
+ fp->def = 0.0f;
+ if (fp->max <= fp->min)
+ fp->max = FLT_MAX;
+ if (fp->def <= fp->min)
+ fp->min = -FLT_MAX;
}
return &desc->desc;
}
diff --git a/spa/plugins/filter-graph/plugin_onnx.c b/spa/plugins/filter-graph/plugin_onnx.c
index 3ef12e963..13fccee56 100644
--- a/spa/plugins/filter-graph/plugin_onnx.c
+++ b/spa/plugins/filter-graph/plugin_onnx.c
@@ -593,6 +593,10 @@ static const struct spa_fga_descriptor *onnx_plugin_make_desc(void *plugin, cons
fp->flags |= SPA_FGA_PORT_OUTPUT;
fp->name = ti->data_name;
+ fp->min = -FLT_MAX;
+ fp->max = FLT_MAX;
+ fp->def = 0.0f;
+
ti->data_index = desc->desc.n_ports;
desc->desc.n_ports++;
diff --git a/spa/plugins/libcamera/libcamera-device.cpp b/spa/plugins/libcamera/libcamera-device.cpp
index 2d6532f82..6517860fc 100644
--- a/spa/plugins/libcamera/libcamera-device.cpp
+++ b/spa/plugins/libcamera/libcamera-device.cpp
@@ -5,6 +5,7 @@
/* SPDX-License-Identifier: MIT */
#include
+#include
#include
#include
@@ -25,7 +26,6 @@
#include
#include
-#include
using namespace libcamera;
@@ -50,7 +50,7 @@ struct impl {
std::string device_id);
};
-const libcamera::Span cameraDevice(const Camera& camera)
+std::span cameraDevice(const Camera& camera)
{
if (auto devices = camera.properties().get(properties::SystemDevices))
return devices.value();
diff --git a/spa/plugins/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp
index f0eaa68f0..fba3d8b0b 100644
--- a/spa/plugins/libcamera/libcamera-source.cpp
+++ b/spa/plugins/libcamera/libcamera-source.cpp
@@ -184,9 +184,6 @@ struct impl {
0, nullptr, 0, this
);
- if (source.fd >= 0)
- spa_system_close(system, std::exchange(source.fd, -1));
-
camera->requestCompleted.disconnect(this, &impl::requestComplete);
if (int res = camera->stop(); res < 0) {
@@ -194,6 +191,9 @@ struct impl {
camera->id().c_str(), spa_strerror(res));
}
+ if (source.fd >= 0)
+ spa_system_close(system, std::exchange(source.fd, -1));
+
completed_requests_rb = SPA_RINGBUFFER_INIT();
active = false;
@@ -2163,7 +2163,7 @@ impl::impl(spa_log *log, spa_loop *data_loop, spa_system *system,
&impl_node, this);
params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ);
- params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE);
+ params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_WRITE);
params[NODE_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ);
params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
diff --git a/spa/plugins/meson.build b/spa/plugins/meson.build
index 42aec7ed3..f774c1036 100644
--- a/spa/plugins/meson.build
+++ b/spa/plugins/meson.build
@@ -1,4 +1,4 @@
-if alsa_dep.found() and host_machine.system() == 'linux'
+if alsa_dep.found()
subdir('alsa')
endif
if get_option('avb').require(host_machine.system() == 'linux', error_message: 'AVB support is only available on Linux').allowed()
diff --git a/spa/plugins/support/cpu-x86.c b/spa/plugins/support/cpu-x86.c
index c1c53855d..0fb866671 100644
--- a/spa/plugins/support/cpu-x86.c
+++ b/spa/plugins/support/cpu-x86.c
@@ -78,6 +78,8 @@ x86_init(struct impl *impl)
if ((ebx & AVX512_BITS) == AVX512_BITS)
flags |= SPA_CPU_FLAG_AVX512;
}
+ if (max_level < 0x16)
+ flags |= SPA_CPU_FLAG_SLOW_GATHER;
/* Check cpuid level of extended features. */
__cpuid (0x80000000, ext_level, ebx, ecx, edx);
diff --git a/spa/plugins/support/logger.c b/spa/plugins/support/logger.c
index c6e6ca4b8..7d0b14c1f 100644
--- a/spa/plugins/support/logger.c
+++ b/spa/plugins/support/logger.c
@@ -2,12 +2,16 @@
/* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */
/* SPDX-License-Identifier: MIT */
+#include "config.h"
+
#include
+#include
#include
#include
#include
#include
#include
+#include
#include
#include
@@ -67,16 +71,10 @@ impl_log_logtv(void *object,
const char *fmt,
va_list args)
{
-#define RESERVED_LENGTH 24
-
struct impl *impl = object;
- char timestamp[18] = {0};
- char topicstr[32] = {0};
- char filename[64] = {0};
- char location[1000 + RESERVED_LENGTH], *p, *s;
+ char location[1024];
static const char * const levels[] = { "-", "E", "W", "I", "D", "T", "*T*" };
const char *prefix = "", *suffix = "";
- int size, len;
bool do_trace;
if ((do_trace = (level == SPA_LOG_LEVEL_TRACE && impl->have_source)))
@@ -93,8 +91,18 @@ impl_log_logtv(void *object,
suffix = SPA_ANSI_RESET;
}
- p = location;
- len = sizeof(location) - RESERVED_LENGTH;
+ struct spa_strbuf msg;
+ spa_strbuf_init(&msg, location, sizeof(location));
+
+ spa_strbuf_append(&msg, "%s[%s]", prefix, levels[level]);
+
+#ifdef HAVE_GETTID
+ static thread_local pid_t tid;
+ if (SPA_UNLIKELY(tid == 0))
+ tid = gettid();
+
+ spa_strbuf_append(&msg, "[%jd]", (intmax_t) tid);
+#endif
if (impl->local_timestamp) {
char buf[64];
@@ -104,67 +112,52 @@ impl_log_logtv(void *object,
clock_gettime(impl->clock_id, &now);
localtime_r(&now.tv_sec, &now_tm);
strftime(buf, sizeof(buf), "%H:%M:%S", &now_tm);
- spa_scnprintf(timestamp, sizeof(timestamp), "[%s.%06d]", buf,
+ spa_strbuf_append(&msg, "[%s.%06d]", buf,
(int)(now.tv_nsec / SPA_NSEC_PER_USEC));
} else if (impl->timestamp) {
struct timespec now;
clock_gettime(impl->clock_id, &now);
- spa_scnprintf(timestamp, sizeof(timestamp), "[%05jd.%06jd]",
+ spa_strbuf_append(&msg, "[%05jd.%06jd]",
(intmax_t) (now.tv_sec & 0x1FFFFFFF) % 100000, (intmax_t) now.tv_nsec / 1000);
}
if (topic && topic->topic)
- spa_scnprintf(topicstr, sizeof(topicstr), " %-12s | ", topic->topic);
+ spa_strbuf_append(&msg, " %-12s | ", topic->topic);
if (impl->line && line != 0) {
- s = strrchr(file, '/');
- spa_scnprintf(filename, sizeof(filename), "[%16.16s:%5i %s()]",
+ const char *s = strrchr(file, '/');
+ spa_strbuf_append(&msg, "[%16.16s:%5i %s()]",
s ? s + 1 : file, line, func);
}
- size = spa_scnprintf(p, len, "%s[%s]%s%s%s ", prefix, levels[level],
- timestamp, topicstr, filename);
- /*
- * it is assumed that at this point `size` <= `len`,
- * which is reasonable as long as file names and function names
- * don't become very long
- */
- size += spa_vscnprintf(p + size, len - size, fmt, args);
+ spa_strbuf_append(&msg, " ");
+ spa_strbuf_appendv(&msg, fmt, args);
+ spa_strbuf_append(&msg, "%s\n", suffix);
- /*
- * `RESERVED_LENGTH` bytes are reserved for printing the suffix
- * (at the moment it's "... (truncated)\x1B[0m\n" at its longest - 21 bytes),
- * its length must be less than `RESERVED_LENGTH` (including the null byte),
- * otherwise a stack buffer overrun could ensue
- */
+ if (SPA_UNLIKELY(msg.pos >= msg.maxsize)) {
+ static const char truncated_text[] = "... (truncated)";
+ size_t suffix_length = strlen(suffix) + strlen(truncated_text) + 1 + 1;
- /* if the message could not fit entirely... */
- if (size >= len - 1) {
- size = len - 1; /* index of the null byte */
- len = sizeof(location);
- size += spa_scnprintf(p + size, len - size, "... (truncated)");
+ spa_assert(msg.maxsize >= suffix_length);
+ msg.pos = msg.maxsize - suffix_length;
+
+ spa_strbuf_append(&msg, "%s%s\n", truncated_text, suffix);
+ spa_assert(msg.pos < msg.maxsize);
}
- else {
- len = sizeof(location);
- }
-
- size += spa_scnprintf(p + size, len - size, "%s\n", suffix);
if (SPA_UNLIKELY(do_trace)) {
uint32_t index;
spa_ringbuffer_get_write_index(&impl->trace_rb, &index);
spa_ringbuffer_write_data(&impl->trace_rb, impl->trace_data, TRACE_BUFFER,
- index & (TRACE_BUFFER - 1), location, size);
- spa_ringbuffer_write_update(&impl->trace_rb, index + size);
+ index & (TRACE_BUFFER - 1), msg.buffer, msg.pos);
+ spa_ringbuffer_write_update(&impl->trace_rb, index + msg.pos);
if (spa_system_eventfd_write(impl->system, impl->source.fd, 1) < 0)
fprintf(impl->file, "error signaling eventfd: %s\n", strerror(errno));
} else
- fputs(location, impl->file);
-
-#undef RESERVED_LENGTH
+ fputs(msg.buffer, impl->file);
}
static SPA_PRINTF_FUNC(6,0) void
diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c
index e5e49849f..5e35f6dcd 100644
--- a/spa/plugins/support/loop.c
+++ b/spa/plugins/support/loop.c
@@ -462,12 +462,12 @@ again:
* this invoking thread but we need to serialize the flushing here with
* a mutex */
if (loop_thread == 0)
- pthread_mutex_lock(&impl->lock);
+ spa_assert_se(pthread_mutex_lock(&impl->lock) == 0);
flush_all_queues(impl);
if (loop_thread == 0)
- pthread_mutex_unlock(&impl->lock);
+ spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0);
res = item->res;
} else {
@@ -482,9 +482,9 @@ again:
recurse = impl->recurse;
while (impl->recurse > 0) {
impl->recurse--;
- pthread_mutex_unlock(&impl->lock);
+ spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0);
}
- pthread_mutex_unlock(&impl->lock);
+ spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0);
}
if ((res = spa_system_eventfd_read(impl->system, queue->ack_fd, &count)) < 0)
@@ -492,7 +492,7 @@ again:
queue, queue->ack_fd, spa_strerror(res));
for (i = 0; i < recurse; i++) {
- pthread_mutex_lock(&impl->lock);
+ spa_assert_se(pthread_mutex_lock(&impl->lock) == 0);
impl->recurse++;
}
@@ -569,9 +569,14 @@ static int loop_locked(void *object, spa_invoke_func_t func, uint32_t seq,
{
struct impl *impl = object;
int res;
- pthread_mutex_lock(&impl->lock);
+
+ res = pthread_mutex_lock(&impl->lock);
+ if (res)
+ return -res;
+
res = func(&impl->loop, false, seq, data, size, user_data);
- pthread_mutex_unlock(&impl->lock);
+ spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0);
+
return res;
}
@@ -598,7 +603,7 @@ static void loop_enter(void *object)
struct impl *impl = object;
pthread_t thread_id = pthread_self();
- pthread_mutex_lock(&impl->lock);
+ spa_assert_se(pthread_mutex_lock(&impl->lock) == 0);
if (impl->enter_count == 0) {
spa_return_if_fail(impl->thread == 0);
impl->thread = thread_id;
@@ -625,7 +630,7 @@ static void loop_leave(void *object)
impl->thread = 0;
flush_all_queues(impl);
}
- pthread_mutex_unlock(&impl->lock);
+ spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0);
}
static int loop_check(void *object)
@@ -644,7 +649,7 @@ static int loop_check(void *object)
/* we could take the lock, check if we actually locked it somewhere */
res = impl->recurse > 0 ? 1 : -EPERM;
- pthread_mutex_unlock(&impl->lock);
+ spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0);
return res;
}
static int loop_lock(void *object)
@@ -722,13 +727,20 @@ static int loop_accept(void *object)
}
struct cancellation_handler_data {
- struct spa_poll_event *ep;
- int ep_count;
+ struct impl *impl;
+ const struct spa_poll_event *ep;
+ volatile int ep_count;
+ volatile int unlocked;
+ volatile int locked;
};
static void cancellation_handler(void *closure)
{
const struct cancellation_handler_data *data = closure;
+ struct impl *impl = data->impl;
+
+ if (data->unlocked && !data->locked)
+ spa_assert_se(pthread_mutex_lock(&impl->lock) == 0);
for (int i = 0; i < data->ep_count; i++) {
struct spa_source *s = data->ep[i].data;
@@ -745,20 +757,24 @@ static int loop_iterate_cancel(void *object, int timeout)
struct spa_poll_event ep[MAX_EP], *e;
int i, nfds;
uint32_t remove_count;
+ struct cancellation_handler_data cdata = { impl, ep, 0, 0, 0 };
+
+ spa_return_val_if_fail(impl->enter_count > 0, -EPERM);
+
+ pthread_cleanup_push(cancellation_handler, &cdata);
remove_count = impl->remove_count;
spa_loop_control_hook_before(&impl->hooks_list);
- pthread_mutex_unlock(&impl->lock);
+ spa_assert_se((cdata.unlocked = (pthread_mutex_unlock(&impl->lock) == 0)));
nfds = spa_system_pollfd_wait(impl->system, impl->poll_fd, ep, SPA_N_ELEMENTS(ep), timeout);
- pthread_mutex_lock(&impl->lock);
+ spa_assert_se((cdata.locked = (pthread_mutex_lock(&impl->lock) == 0)));
spa_loop_control_hook_after(&impl->hooks_list);
if (remove_count != impl->remove_count)
nfds = 0;
- struct cancellation_handler_data cdata = { ep, nfds };
- pthread_cleanup_push(cancellation_handler, &cdata);
+ cdata.ep_count = nfds;
/* first we set all the rmasks, then call the callbacks. The reason is that
* some callback might also want to look at other sources it manages and
@@ -794,13 +810,15 @@ static int loop_iterate(void *object, int timeout)
int i, nfds;
uint32_t remove_count;
+ spa_return_val_if_fail(impl->enter_count > 0, -EPERM);
+
remove_count = impl->remove_count;
spa_loop_control_hook_before(&impl->hooks_list);
- pthread_mutex_unlock(&impl->lock);
+ spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0);
nfds = spa_system_pollfd_wait(impl->system, impl->poll_fd, ep, SPA_N_ELEMENTS(ep), timeout);
- pthread_mutex_lock(&impl->lock);
+ spa_assert_se(pthread_mutex_lock(&impl->lock) == 0);
spa_loop_control_hook_after(&impl->hooks_list);
if (remove_count != impl->remove_count)
return 0;
diff --git a/spa/plugins/support/node-driver.c b/spa/plugins/support/node-driver.c
index fa9cf3426..f0585a711 100644
--- a/spa/plugins/support/node-driver.c
+++ b/spa/plugins/support/node-driver.c
@@ -91,7 +91,6 @@ struct impl {
struct spa_io_clock *clock;
struct spa_source timer_source;
- struct itimerspec timerspec;
int clock_fd;
bool started;
@@ -182,13 +181,16 @@ static void set_timeout(struct impl *this, uint64_t next_time)
* returning -ECANCELED.)
* If timerfd is used with a non-realtime clock, the flag is ignored.
* (Note that the flag only works in combination with SPA_FD_TIMER_ABSTIME.) */
+ struct itimerspec ts;
spa_log_trace(this->log, "set timeout %"PRIu64, next_time);
- this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC;
- this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC;
+ ts.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC;
+ ts.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC;
+ ts.it_interval.tv_sec = 0;
+ ts.it_interval.tv_nsec = 0;
spa_system_timerfd_settime(this->data_system,
this->timer_source.fd, SPA_FD_TIMER_ABSTIME |
- SPA_FD_TIMER_CANCEL_ON_SET, &this->timerspec, NULL);
+ SPA_FD_TIMER_CANCEL_ON_SET, &ts, NULL);
}
static inline uint64_t gettime_nsec(struct impl *this, clockid_t clock_id)
@@ -1043,10 +1045,6 @@ impl_init(const struct spa_handle_factory *factory,
this->timer_source.mask = SPA_IO_IN;
this->timer_source.rmask = 0;
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 0;
- this->timerspec.it_interval.tv_sec = 0;
- this->timerspec.it_interval.tv_nsec = 0;
spa_loop_add_source(this->data_loop, &this->timer_source);
diff --git a/spa/plugins/support/null-audio-sink.c b/spa/plugins/support/null-audio-sink.c
index b804023b3..610adf9ce 100644
--- a/spa/plugins/support/null-audio-sink.c
+++ b/spa/plugins/support/null-audio-sink.c
@@ -114,7 +114,6 @@ struct impl {
unsigned int started:1;
unsigned int following:1;
struct spa_source timer_source;
- struct itimerspec timerspec;
uint64_t next_time;
};
@@ -179,11 +178,15 @@ static int impl_node_enum_params(void *object, int seq,
static void set_timeout(struct impl *this, uint64_t next_time)
{
+ struct itimerspec ts;
+
spa_log_trace(this->log, "set timeout %"PRIu64, next_time);
- this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC;
- this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC;
+ ts.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC;
+ ts.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC;
+ ts.it_interval.tv_sec = 0;
+ ts.it_interval.tv_nsec = 0;
spa_system_timerfd_settime(this->data_system,
- this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL);
+ this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &ts, NULL);
}
static int set_timers(struct impl *this)
@@ -929,10 +932,6 @@ impl_init(const struct spa_handle_factory *factory,
SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);
this->timer_source.mask = SPA_IO_IN;
this->timer_source.rmask = 0;
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 0;
- this->timerspec.it_interval.tv_sec = 0;
- this->timerspec.it_interval.tv_nsec = 0;
spa_loop_add_source(this->data_loop, &this->timer_source);
diff --git a/spa/plugins/support/system.c b/spa/plugins/support/system.c
index 767e0b43d..cd9e1c6eb 100644
--- a/spa/plugins/support/system.c
+++ b/spa/plugins/support/system.c
@@ -30,6 +30,10 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.system");
# define TFD_TIMER_CANCEL_ON_SET (1 << 1)
#endif
+SPA_STATIC_ASSERT(sizeof(struct spa_poll_event) == sizeof(struct epoll_event));
+SPA_STATIC_ASSERT(offsetof(struct spa_poll_event, events) == offsetof(struct epoll_event, events));
+SPA_STATIC_ASSERT(offsetof(struct spa_poll_event, data) == offsetof(struct epoll_event, data.ptr));
+
struct impl {
struct spa_handle handle;
struct spa_system system;
@@ -132,16 +136,9 @@ static int impl_pollfd_del(void *object, int pfd, int fd)
static int impl_pollfd_wait(void *object, int pfd,
struct spa_poll_event *ev, int n_ev, int timeout)
{
- struct epoll_event ep[n_ev];
- int i, nfds;
-
- if (SPA_UNLIKELY((nfds = epoll_wait(pfd, ep, n_ev, timeout)) < 0))
+ int nfds;
+ if (SPA_UNLIKELY((nfds = epoll_wait(pfd, (struct epoll_event*)ev, n_ev, timeout)) < 0))
return -errno;
-
- for (i = 0; i < nfds; i++) {
- ev[i].events = ep[i].events;
- ev[i].data = ep[i].data.ptr;
- }
return nfds;
}
diff --git a/spa/plugins/test/fakesink.c b/spa/plugins/test/fakesink.c
index 71550300c..31667a1de 100644
--- a/spa/plugins/test/fakesink.c
+++ b/spa/plugins/test/fakesink.c
@@ -26,10 +26,6 @@
#define SPA_LOG_TOPIC_DEFAULT &log_topic
SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.fakesink");
-struct props {
- bool live;
-};
-
#define MAX_BUFFERS 16
#define MAX_PORTS 1
@@ -68,13 +64,11 @@ struct impl {
uint64_t info_all;
struct spa_node_info info;
struct spa_param_info params[1];
- struct props props;
struct spa_hook_list hooks;
struct spa_callbacks callbacks;
struct spa_source timer_source;
- struct itimerspec timerspec;
bool started;
uint64_t start_time;
@@ -87,13 +81,6 @@ struct impl {
#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_INPUT && (p) < MAX_PORTS)
-#define DEFAULT_LIVE false
-
-static void reset_props(struct impl *this, struct props *props)
-{
- props->live = DEFAULT_LIVE;
-}
-
static int impl_node_enum_params(void *object, int seq,
uint32_t id, uint32_t start, uint32_t num,
const struct spa_pod *filter)
@@ -116,14 +103,6 @@ static int impl_node_enum_params(void *object, int seq,
spa_pod_builder_init(&b, buffer, sizeof(buffer));
switch (id) {
- case SPA_PARAM_Props:
- if (result.index > 0)
- return 0;
-
- param = spa_pod_builder_add_object(&b,
- SPA_TYPE_OBJECT_Props, id,
- SPA_PROP_live, SPA_POD_Bool(this->props.live));
- break;
default:
return -ENOENT;
}
@@ -151,26 +130,7 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
spa_return_val_if_fail(this != NULL, -EINVAL);
-
switch (id) {
- case SPA_PARAM_Props:
- {
- struct port *port = &this->port;
-
- if (param == NULL) {
- reset_props(this, &this->props);
- return 0;
- }
- spa_pod_parse_object(param,
- SPA_TYPE_OBJECT_Props, NULL,
- SPA_PROP_live, SPA_POD_OPT_Bool(&this->props.live));
-
- if (this->props.live)
- port->info.flags |= SPA_PORT_FLAG_LIVE;
- else
- port->info.flags &= ~SPA_PORT_FLAG_LIVE;
- break;
- }
default:
return -ENOENT;
}
@@ -179,23 +139,15 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
static void set_timer(struct impl *this, bool enabled)
{
- if (this->callbacks.funcs || this->props.live) {
- if (enabled) {
- if (this->props.live) {
- uint64_t next_time = this->start_time + this->elapsed_time;
- this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC;
- this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC;
- } else {
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 1;
- }
- } else {
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 0;
- }
- spa_system_timerfd_settime(this->data_system,
- this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL);
+ struct itimerspec ts = {0};
+
+ if (enabled) {
+ uint64_t next_time = this->start_time + this->elapsed_time;
+ ts.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC;
+ ts.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC;
}
+
+ spa_system_timerfd_settime(this->data_system, this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &ts, NULL);
}
static inline int read_timer(struct impl *this)
@@ -203,14 +155,12 @@ static inline int read_timer(struct impl *this)
uint64_t expirations;
int res = 0;
- if (this->callbacks.funcs || this->props.live) {
- if ((res = spa_system_timerfd_read(this->data_system,
- this->timer_source.fd, &expirations)) < 0) {
- if (res != -EAGAIN)
- spa_log_error(this->log, "%p: timerfd error: %s",
- this, spa_strerror(res));
- }
+ if ((res = spa_system_timerfd_read(this->data_system, this->timer_source.fd, &expirations)) < 0) {
+ if (res != -EAGAIN)
+ spa_log_error(this->log, "%p: timerfd error: %s",
+ this, spa_strerror(res));
}
+
return res;
}
@@ -298,10 +248,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
return 0;
clock_gettime(CLOCK_MONOTONIC, &now);
- if (this->props.live)
- this->start_time = SPA_TIMESPEC_TO_NSEC(&now);
- else
- this->start_time = 0;
+ this->start_time = SPA_TIMESPEC_TO_NSEC(&now);
this->buffer_count = 0;
this->elapsed_time = 0;
@@ -651,10 +598,8 @@ static int impl_node_process(void *object)
io->buffer_id = SPA_ID_INVALID;
io->status = SPA_STATUS_OK;
}
- if (this->callbacks.funcs == NULL)
- return consume_buffer(this);
- else
- return SPA_STATUS_OK;
+
+ return SPA_STATUS_OK;
}
static const struct spa_node_methods impl_node = {
@@ -758,8 +703,6 @@ impl_init(const struct spa_handle_factory *factory,
this->params[0] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE);
this->info.params = this->params;
this->info.n_params = 1;
- reset_props(this, &this->props);
-
this->timer_source.func = on_input;
this->timer_source.data = this;
@@ -767,10 +710,6 @@ impl_init(const struct spa_handle_factory *factory,
SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);
this->timer_source.mask = SPA_IO_IN;
this->timer_source.rmask = 0;
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 0;
- this->timerspec.it_interval.tv_sec = 0;
- this->timerspec.it_interval.tv_nsec = 0;
if (this->data_loop)
spa_loop_add_source(this->data_loop, &this->timer_source);
@@ -779,9 +718,7 @@ impl_init(const struct spa_handle_factory *factory,
port->info_all = SPA_PORT_CHANGE_MASK_FLAGS |
SPA_PORT_CHANGE_MASK_PARAMS;
port->info = SPA_PORT_INFO_INIT();
- port->info.flags = SPA_PORT_FLAG_NO_REF;
- if (this->props.live)
- port->info.flags |= SPA_PORT_FLAG_LIVE;
+ port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_LIVE;
port->params[0] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ);
port->params[1] = SPA_PARAM_INFO(SPA_PARAM_IO, 0);
port->params[2] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
diff --git a/spa/plugins/test/fakesrc.c b/spa/plugins/test/fakesrc.c
index 28b37dab4..db28d9c3c 100644
--- a/spa/plugins/test/fakesrc.c
+++ b/spa/plugins/test/fakesrc.c
@@ -27,7 +27,6 @@
SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.fakesrc");
struct props {
- bool live;
uint32_t pattern;
};
@@ -75,7 +74,6 @@ struct impl {
struct spa_callbacks callbacks;
struct spa_source timer_source;
- struct itimerspec timerspec;
bool started;
uint64_t start_time;
@@ -89,12 +87,10 @@ struct impl {
#define CHECK_PORT(this,d,p) ((d) == SPA_DIRECTION_OUTPUT && (p) < MAX_PORTS)
-#define DEFAULT_LIVE false
#define DEFAULT_PATTERN 0
static void reset_props(struct impl *this, struct props *props)
{
- props->live = DEFAULT_LIVE;
props->pattern = DEFAULT_PATTERN;
}
@@ -129,7 +125,6 @@ static int impl_node_enum_params(void *object, int seq,
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, id,
- SPA_PROP_live, SPA_POD_Bool(p->live),
SPA_PROP_patternType, SPA_POD_CHOICE_ENUM_Int(2, p->pattern, p->pattern));
break;
}
@@ -164,7 +159,6 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
case SPA_PARAM_Props:
{
struct props *p = &this->props;
- struct port *port = &this->port;
if (param == NULL) {
reset_props(this, p);
@@ -172,13 +166,7 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
}
spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL,
- SPA_PROP_live, SPA_POD_OPT_Bool(&p->live),
SPA_PROP_patternType, SPA_POD_OPT_Int(&p->pattern));
-
- if (p->live)
- port->info.flags |= SPA_PORT_FLAG_LIVE;
- else
- port->info.flags &= ~SPA_PORT_FLAG_LIVE;
break;
}
default:
@@ -194,23 +182,15 @@ static int fill_buffer(struct impl *this, struct buffer *b)
static void set_timer(struct impl *this, bool enabled)
{
- if (this->callbacks.funcs || this->props.live) {
- if (enabled) {
- if (this->props.live) {
- uint64_t next_time = this->start_time + this->elapsed_time;
- this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC;
- this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC;
- } else {
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 1;
- }
- } else {
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 0;
- }
- spa_system_timerfd_settime(this->data_system,
- this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL);
+ struct itimerspec ts = {0};
+
+ if (enabled) {
+ uint64_t next_time = this->start_time + this->elapsed_time;
+ ts.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC;
+ ts.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC;
}
+
+ spa_system_timerfd_settime(this->data_system, this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &ts, NULL);
}
static inline int read_timer(struct impl *this)
@@ -218,14 +198,12 @@ static inline int read_timer(struct impl *this)
uint64_t expirations;
int res = 0;
- if (this->callbacks.funcs || this->props.live) {
- if ((res = spa_system_timerfd_read(this->data_system,
- this->timer_source.fd, &expirations)) < 0) {
- if (res != -EAGAIN)
- spa_log_error(this->log, "%p: timerfd error: %s",
- this, spa_strerror(res));
- }
+ if ((res = spa_system_timerfd_read(this->data_system, this->timer_source.fd, &expirations)) < 0) {
+ if (res != -EAGAIN)
+ spa_log_error(this->log, "%p: timerfd error: %s",
+ this, spa_strerror(res));
}
+
return res;
}
@@ -311,10 +289,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
return 0;
clock_gettime(CLOCK_MONOTONIC, &now);
- if (this->props.live)
- this->start_time = SPA_TIMESPEC_TO_NSEC(&now);
- else
- this->start_time = 0;
+ this->start_time = SPA_TIMESPEC_TO_NSEC(&now);
this->buffer_count = 0;
this->elapsed_time = 0;
@@ -682,10 +657,7 @@ static int impl_node_process(void *object)
io->buffer_id = SPA_ID_INVALID;
}
- if (this->callbacks.funcs == NULL)
- return make_buffer(this);
- else
- return SPA_STATUS_OK;
+ return SPA_STATUS_OK;
}
static const struct spa_node_methods impl_node = {
@@ -797,10 +769,6 @@ impl_init(const struct spa_handle_factory *factory,
SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);
this->timer_source.mask = SPA_IO_IN;
this->timer_source.rmask = 0;
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 0;
- this->timerspec.it_interval.tv_sec = 0;
- this->timerspec.it_interval.tv_nsec = 0;
if (this->data_loop)
spa_loop_add_source(this->data_loop, &this->timer_source);
@@ -809,9 +777,7 @@ impl_init(const struct spa_handle_factory *factory,
port->info_all = SPA_PORT_CHANGE_MASK_FLAGS |
SPA_PORT_CHANGE_MASK_PARAMS;
port->info = SPA_PORT_INFO_INIT();
- port->info.flags = SPA_PORT_FLAG_NO_REF;
- if (this->props.live)
- port->info.flags |= SPA_PORT_FLAG_LIVE;
+ port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_LIVE;
port->params[0] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ);
port->params[1] = SPA_PARAM_INFO(SPA_PARAM_IO, 0);
port->params[2] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE);
diff --git a/spa/plugins/v4l2/v4l2-device.c b/spa/plugins/v4l2/v4l2-device.c
index 2379d2105..f29252d16 100644
--- a/spa/plugins/v4l2/v4l2-device.c
+++ b/spa/plugins/v4l2/v4l2-device.c
@@ -98,9 +98,9 @@ static int emit_info(struct impl *this, bool full)
(this->dev.cap.version >> 8) & 0xFF,
(this->dev.cap.version) & 0xFF);
ADD_ITEM(SPA_KEY_API_V4L2_CAP_VERSION, version);
- snprintf(capabilities, sizeof(capabilities), "%08x", this->dev.cap.capabilities);
+ snprintf(capabilities, sizeof(capabilities), "0x%08x", this->dev.cap.capabilities);
ADD_ITEM(SPA_KEY_API_V4L2_CAP_CAPABILITIES, capabilities);
- snprintf(device_caps, sizeof(device_caps), "%08x", this->dev.cap.device_caps);
+ snprintf(device_caps, sizeof(device_caps), "0x%08x", this->dev.cap.device_caps);
ADD_ITEM(SPA_KEY_API_V4L2_CAP_DEVICE_CAPS, device_caps);
#undef ADD_ITEM
info.props = &SPA_DICT_INIT(items, n_items);
diff --git a/spa/plugins/videotestsrc/videotestsrc.c b/spa/plugins/videotestsrc/videotestsrc.c
index db5c90113..a8d7059c8 100644
--- a/spa/plugins/videotestsrc/videotestsrc.c
+++ b/spa/plugins/videotestsrc/videotestsrc.c
@@ -35,17 +35,14 @@ enum pattern {
PATTERN_SNOW,
};
-#define DEFAULT_LIVE true
#define DEFAULT_PATTERN PATTERN_SMPTE_SNOW
struct props {
- bool live;
uint32_t pattern;
};
static void reset_props(struct props *props)
{
- props->live = DEFAULT_LIVE;
props->pattern = DEFAULT_PATTERN;
}
@@ -97,9 +94,7 @@ struct impl {
struct spa_hook_list hooks;
struct spa_callbacks callbacks;
- bool async;
struct spa_source *timer_source;
- struct itimerspec timerspec;
bool started;
uint64_t start_time;
@@ -141,13 +136,6 @@ static int impl_node_enum_params(void *object, int seq,
switch (result.index) {
case 0:
- param = spa_pod_builder_add_object(&b,
- SPA_TYPE_OBJECT_PropInfo, id,
- SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_live),
- SPA_PROP_INFO_description, SPA_POD_String("Configure live mode of the source"),
- SPA_PROP_INFO_type, SPA_POD_Bool(p->live));
- break;
- case 1:
spa_pod_builder_push_object(&b, &f[0], SPA_TYPE_OBJECT_PropInfo, id);
spa_pod_builder_add(&b,
SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_patternType),
@@ -176,7 +164,6 @@ static int impl_node_enum_params(void *object, int seq,
case 0:
param = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Props, id,
- SPA_PROP_live, SPA_POD_Bool(p->live),
SPA_PROP_patternType, SPA_POD_Int(p->pattern));
break;
default:
@@ -231,7 +218,6 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
case SPA_PARAM_Props:
{
struct props *p = &this->props;
- struct port *port = &this->port;
if (param == NULL) {
reset_props(p);
@@ -239,13 +225,8 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
}
spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL,
- SPA_PROP_live, SPA_POD_OPT_Bool(&p->live),
SPA_PROP_patternType, SPA_POD_OPT_Int(&p->pattern));
- if (p->live)
- port->info.flags |= SPA_PORT_FLAG_LIVE;
- else
- port->info.flags &= ~SPA_PORT_FLAG_LIVE;
break;
}
default:
@@ -263,22 +244,15 @@ static int fill_buffer(struct impl *this, struct buffer *b)
static void set_timer(struct impl *this, bool enabled)
{
- if (this->async || this->props.live) {
- if (enabled) {
- if (this->props.live) {
- uint64_t next_time = this->start_time + this->elapsed_time;
- this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC;
- this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC;
- } else {
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 1;
- }
- } else {
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 0;
- }
- spa_loop_utils_update_timer(this->loop_utils, this->timer_source, &this->timerspec.it_value, &this->timerspec.it_interval, true);
+ struct timespec ts = {0};
+
+ if (enabled) {
+ uint64_t next_time = this->start_time + this->elapsed_time;
+ ts.tv_sec = next_time / SPA_NSEC_PER_SEC;
+ ts.tv_nsec = next_time % SPA_NSEC_PER_SEC;
}
+
+ spa_loop_utils_update_timer(this->loop_utils, this->timer_source, &ts, NULL, true);
}
static int make_buffer(struct impl *this)
@@ -358,10 +332,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
return 0;
clock_gettime(CLOCK_MONOTONIC, &now);
- if (this->props.live)
- this->start_time = SPA_TIMESPEC_TO_NSEC(&now);
- else
- this->start_time = 0;
+ this->start_time = SPA_TIMESPEC_TO_NSEC(&now);
this->frame_count = 0;
this->elapsed_time = 0;
@@ -755,9 +726,6 @@ static inline void reuse_buffer(struct impl *this, struct port *port, uint32_t i
b->outstanding = false;
spa_list_append(&port->empty, &b->link);
-
- if (!this->props.live)
- set_timer(this, true);
}
static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id)
@@ -795,10 +763,7 @@ static int impl_node_process(void *object)
io->buffer_id = SPA_ID_INVALID;
}
- if (!this->props.live)
- return make_buffer(this);
- else
- return SPA_STATUS_OK;
+ return SPA_STATUS_OK;
}
static const struct spa_node_methods impl_node = {
@@ -907,18 +872,12 @@ impl_init(const struct spa_handle_factory *factory,
reset_props(&this->props);
this->timer_source = spa_loop_utils_add_timer(this->loop_utils, on_output, this);
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 0;
- this->timerspec.it_interval.tv_sec = 0;
- this->timerspec.it_interval.tv_nsec = 0;
port = &this->port;
port->info_all = SPA_PORT_CHANGE_MASK_FLAGS |
SPA_PORT_CHANGE_MASK_PARAMS;
port->info = SPA_PORT_INFO_INIT();
- port->info.flags = SPA_PORT_FLAG_NO_REF;
- if (this->props.live)
- port->info.flags |= SPA_PORT_FLAG_LIVE;
+ port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_LIVE;
port->params[0] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ);
port->params[1] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ);
port->params[2] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ);
diff --git a/spa/plugins/vulkan/vulkan-blit-utils.c b/spa/plugins/vulkan/vulkan-blit-utils.c
index f8a60497e..e0bd6cfe5 100644
--- a/spa/plugins/vulkan/vulkan-blit-utils.c
+++ b/spa/plugins/vulkan/vulkan-blit-utils.c
@@ -12,8 +12,10 @@
#include
#include
#include
-#if !defined(__FreeBSD__) && !defined(__MidnightBSD__)
+#ifdef __linux__
#include
+#else
+#include
#endif
#include
#include
diff --git a/spa/plugins/vulkan/vulkan-compute-source.c b/spa/plugins/vulkan/vulkan-compute-source.c
index 1daf3b47c..aa6f4a60f 100644
--- a/spa/plugins/vulkan/vulkan-compute-source.c
+++ b/spa/plugins/vulkan/vulkan-compute-source.c
@@ -33,17 +33,6 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.vulkan.compute-source");
#define FRAMES_TO_TIME(this,f) ((this->position->video.framerate.denom * (f) * SPA_NSEC_PER_SEC) / \
(this->position->video.framerate.num))
-#define DEFAULT_LIVE true
-
-struct props {
- bool live;
-};
-
-static void reset_props(struct props *props)
-{
- props->live = DEFAULT_LIVE;
-}
-
struct buffer {
uint32_t id;
#define BUFFER_FLAG_OUT (1<<0)
@@ -95,14 +84,11 @@ struct impl {
#define IDX_Props 1
#define N_NODE_PARAMS 2
struct spa_param_info params[N_NODE_PARAMS];
- struct props props;
struct spa_hook_list hooks;
struct spa_callbacks callbacks;
- bool async;
struct spa_source timer_source;
- struct itimerspec timerspec;
bool started;
uint64_t start_time;
@@ -138,38 +124,6 @@ static int impl_node_enum_params(void *object, int seq,
spa_pod_builder_init(&b, buffer, sizeof(buffer));
switch (id) {
- case SPA_PARAM_PropInfo:
- {
- struct props *p = &this->props;
-
- switch (result.index) {
- case 0:
- param = spa_pod_builder_add_object(&b,
- SPA_TYPE_OBJECT_PropInfo, id,
- SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_live),
- SPA_PROP_INFO_description, SPA_POD_String("Configure live mode of the source"),
- SPA_PROP_INFO_type, SPA_POD_Bool(p->live));
- break;
- default:
- return 0;
- }
- break;
- }
- case SPA_PARAM_Props:
- {
- struct props *p = &this->props;
-
- switch (result.index) {
- case 0:
- param = spa_pod_builder_add_object(&b,
- SPA_TYPE_OBJECT_Props, id,
- SPA_PROP_live, SPA_POD_Bool(p->live));
- break;
- default:
- return 0;
- }
- break;
- }
default:
return -ENOENT;
}
@@ -214,25 +168,6 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
spa_return_val_if_fail(this != NULL, -EINVAL);
switch (id) {
- case SPA_PARAM_Props:
- {
- struct props *p = &this->props;
- struct port *port = &this->port;
-
- if (param == NULL) {
- reset_props(p);
- return 0;
- }
- spa_pod_parse_object(param,
- SPA_TYPE_OBJECT_Props, NULL,
- SPA_PROP_live, SPA_POD_OPT_Bool(&p->live));
-
- if (p->live)
- port->info.flags |= SPA_PORT_FLAG_LIVE;
- else
- port->info.flags &= ~SPA_PORT_FLAG_LIVE;
- break;
- }
default:
return -ENOENT;
}
@@ -242,23 +177,15 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
static void set_timer(struct impl *this, bool enabled)
{
- if (this->async || this->props.live) {
- if (enabled) {
- if (this->props.live) {
- uint64_t next_time = this->start_time + this->elapsed_time;
- this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC;
- this->timerspec.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC;
- } else {
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 1;
- }
- } else {
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 0;
- }
- spa_system_timerfd_settime(this->data_system,
- this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL);
+ struct itimerspec ts = {0};
+
+ if (enabled) {
+ uint64_t next_time = this->start_time + this->elapsed_time;
+ ts.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC;
+ ts.it_value.tv_nsec = next_time % SPA_NSEC_PER_SEC;
}
+
+ spa_system_timerfd_settime(this->data_system, this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &ts, NULL);
}
static int read_timer(struct impl *this)
@@ -266,14 +193,12 @@ static int read_timer(struct impl *this)
uint64_t expirations;
int res = 0;
- if (this->async || this->props.live) {
- if ((res = spa_system_timerfd_read(this->data_system,
- this->timer_source.fd, &expirations)) < 0) {
- if (res != -EAGAIN)
- spa_log_error(this->log, "%p: timerfd error: %s",
- this, spa_strerror(res));
- }
+ if ((res = spa_system_timerfd_read(this->data_system, this->timer_source.fd, &expirations)) < 0) {
+ if (res != -EAGAIN)
+ spa_log_error(this->log, "%p: timerfd error: %s",
+ this, spa_strerror(res));
}
+
return res;
}
@@ -348,9 +273,6 @@ static inline void reuse_buffer(struct impl *this, struct port *port, uint32_t i
SPA_FLAG_CLEAR(b->flags, BUFFER_FLAG_OUT);
spa_list_append(&port->empty, &b->link);
-
- if (!this->props.live)
- set_timer(this, true);
}
}
@@ -409,10 +331,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
return 0;
clock_gettime(CLOCK_MONOTONIC, &now);
- if (this->props.live)
- this->start_time = SPA_TIMESPEC_TO_NSEC(&now);
- else
- this->start_time = 0;
+ this->start_time = SPA_TIMESPEC_TO_NSEC(&now);
this->frame_count = 0;
this->elapsed_time = 0;
@@ -874,10 +793,7 @@ static int impl_node_process(void *object)
io->buffer_id = SPA_ID_INVALID;
}
- if (!this->props.live)
- return make_buffer(this);
- else
- return SPA_STATUS_OK;
+ return SPA_STATUS_OK;
}
static const struct spa_node_methods impl_node = {
@@ -985,7 +901,6 @@ impl_init(const struct spa_handle_factory *factory,
this->params[IDX_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE);
this->info.params = this->params;
this->info.n_params = N_NODE_PARAMS;
- reset_props(&this->props);
this->timer_source.func = on_output;
this->timer_source.data = this;
@@ -993,10 +908,6 @@ impl_init(const struct spa_handle_factory *factory,
SPA_FD_CLOEXEC | SPA_FD_NONBLOCK);
this->timer_source.mask = SPA_IO_IN;
this->timer_source.rmask = 0;
- this->timerspec.it_value.tv_sec = 0;
- this->timerspec.it_value.tv_nsec = 0;
- this->timerspec.it_interval.tv_sec = 0;
- this->timerspec.it_interval.tv_nsec = 0;
if (this->data_loop)
spa_loop_add_source(this->data_loop, &this->timer_source);
@@ -1006,9 +917,7 @@ impl_init(const struct spa_handle_factory *factory,
SPA_PORT_CHANGE_MASK_PARAMS |
SPA_PORT_CHANGE_MASK_PROPS;
port->info = SPA_PORT_INFO_INIT();
- port->info.flags = SPA_PORT_FLAG_NO_REF;
- if (this->props.live)
- port->info.flags |= SPA_PORT_FLAG_LIVE;
+ port->info.flags = SPA_PORT_FLAG_NO_REF | SPA_PORT_FLAG_LIVE;
port->params[IDX_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ);
port->params[IDX_Meta] = SPA_PARAM_INFO(SPA_PARAM_Meta, SPA_PARAM_INFO_READ);
port->params[IDX_IO] = SPA_PARAM_INFO(SPA_PARAM_IO, SPA_PARAM_INFO_READ);
diff --git a/spa/plugins/vulkan/vulkan-compute-utils.c b/spa/plugins/vulkan/vulkan-compute-utils.c
index 49705bee8..503542483 100644
--- a/spa/plugins/vulkan/vulkan-compute-utils.c
+++ b/spa/plugins/vulkan/vulkan-compute-utils.c
@@ -11,8 +11,10 @@
#include
#include
#include
-#if !defined(__FreeBSD__) && !defined(__MidnightBSD__)
+#ifdef __linux__
#include
+#else
+#include
#endif
#include
#include
diff --git a/spa/plugins/vulkan/vulkan-utils.c b/spa/plugins/vulkan/vulkan-utils.c
index 6a0f693dc..88a230dd6 100644
--- a/spa/plugins/vulkan/vulkan-utils.c
+++ b/spa/plugins/vulkan/vulkan-utils.c
@@ -11,8 +11,10 @@
#include
#include
#include
-#if !defined(__FreeBSD__) && !defined(__MidnightBSD__)
+#ifdef __linux__
#include
+#else
+#include
#endif
#include
#include
diff --git a/spa/tests/benchmark-aec.c b/spa/tests/benchmark-aec.c
index 3ac0fc62e..37d4a8375 100644
--- a/spa/tests/benchmark-aec.c
+++ b/spa/tests/benchmark-aec.c
@@ -6,7 +6,6 @@
#include
#include
-#include
#include
#include
#include
diff --git a/spa/tools/spa-json-dump.c b/spa/tools/spa-json-dump.c
index ee3b42da7..ed81330d4 100644
--- a/spa/tools/spa-json-dump.c
+++ b/spa/tools/spa-json-dump.c
@@ -15,27 +15,28 @@
#include
#include
+#include
#include
#define DEFAULT_INDENT 2
struct data {
const char *filename;
- FILE *file;
+
+ FILE *out;
+ struct spa_json_builder builder;
void *data;
size_t size;
-
- int indent;
- bool simple_string;
- const char *comma;
- const char *key_sep;
};
-#define OPTIONS "hi:s"
+#define OPTIONS "hNC:Ri:s"
static const struct option long_options[] = {
{ "help", no_argument, NULL, 'h'},
+ { "no-colors", no_argument, NULL, 'N' },
+ { "color", optional_argument, NULL, 'C' },
+ { "raw", no_argument, NULL, 'R' },
{ "indent", required_argument, NULL, 'i' },
{ "spa", no_argument, NULL, 's' },
@@ -53,72 +54,21 @@ static void show_usage(struct data *d, const char *name, bool is_error)
" -h, --help Show this help\n"
"\n");
fprintf(fp,
+ " -N, --no-colors disable color output\n"
+ " -C, --color[=WHEN] whether to enable color support. WHEN is `never`, `always`, or `auto`\n"
+ " -R, --raw force raw output\n"
" -i --indent set indent (default %d)\n"
" -s --spa use simplified SPA JSON\n"
"\n",
DEFAULT_INDENT);
}
-#define REJECT "\"\\'=:,{}[]()#"
-
-static bool is_simple_string(const char *val, int len)
+static int dump(struct data *d, struct spa_json *it, const char *key, const char *value, int len)
{
- int i;
- for (i = 0; i < len; i++) {
- if (val[i] < 0x20 || strchr(REJECT, val[i]) != NULL)
- return false;
- }
- return true;
-}
-
-static void encode_string(struct data *d, const char *val, int len)
-{
- FILE *f = d->file;
- int i;
- if (d->simple_string && is_simple_string(val, len)) {
- fprintf(f, "%.*s", len, val);
- return;
- }
- fprintf(f, "\"");
- for (i = 0; i < len; i++) {
- char v = val[i];
- switch (v) {
- case '\n':
- fprintf(f, "\\n");
- break;
- case '\r':
- fprintf(f, "\\r");
- break;
- case '\b':
- fprintf(f, "\\b");
- break;
- case '\t':
- fprintf(f, "\\t");
- break;
- case '\f':
- fprintf(f, "\\f");
- break;
- case '\\': case '"':
- fprintf(f, "\\%c", v);
- break;
- default:
- if (v > 0 && v < 0x20)
- fprintf(f, "\\u%04x", v);
- else
- fprintf(f, "%c", v);
- break;
- }
- }
- fprintf(f, "\"");
-}
-
-static int dump(struct data *d, int indent, struct spa_json *it, const char *value, int len)
-{
- FILE *file = d->file;
+ struct spa_json_builder *b = &d->builder;
struct spa_json sub;
bool toplevel = false;
- int count = 0, res;
- char key[1024];
+ int res;
if (!value) {
toplevel = true;
@@ -127,29 +77,22 @@ static int dump(struct data *d, int indent, struct spa_json *it, const char *val
}
if (spa_json_is_array(value, len)) {
- fprintf(file, "[");
+ spa_json_builder_object_push(b, key, "[");
spa_json_enter(it, &sub);
while ((len = spa_json_next(&sub, &value)) > 0) {
- fprintf(file, "%s\n%*s", count++ > 0 ? d->comma : "",
- indent+d->indent, "");
- if ((res = dump(d, indent+d->indent, &sub, value, len)) < 0)
+ if ((res = dump(d, &sub, NULL, value, len)) < 0)
return res;
}
- fprintf(file, "%s%*s]", count > 0 ? "\n" : "",
- count > 0 ? indent : 0, "");
+ spa_json_builder_pop(b, "]");
} else if (spa_json_is_object(value, len)) {
- fprintf(file, "{");
+ char k[1024];
+ spa_json_builder_object_push(b, key, "{");
if (!toplevel)
spa_json_enter(it, &sub);
else
sub = *it;
- while ((len = spa_json_object_next(&sub, key, sizeof(key), &value)) > 0) {
- fprintf(file, "%s\n%*s",
- count++ > 0 ? d->comma : "",
- indent+d->indent, "");
- encode_string(d, key, strlen(key));
- fprintf(file, "%s ", d->key_sep);
- res = dump(d, indent+d->indent, &sub, value, len);
+ while ((len = spa_json_object_next(&sub, k, sizeof(k), &value)) > 0) {
+ res = dump(d, &sub, k, value, len);
if (res < 0) {
if (toplevel)
*it = sub;
@@ -158,18 +101,10 @@ static int dump(struct data *d, int indent, struct spa_json *it, const char *val
}
if (toplevel)
*it = sub;
- fprintf(file, "%s%*s}", count > 0 ? "\n" : "",
- count > 0 ? indent : 0, "");
- } else if (spa_json_is_string(value, len) ||
- spa_json_is_null(value, len) ||
- spa_json_is_bool(value, len) ||
- spa_json_is_int(value, len) ||
- spa_json_is_float(value, len)) {
- fprintf(file, "%.*s", len, value);
+ spa_json_builder_pop(b, "}");
} else {
- encode_string(d, value, len);
+ spa_json_builder_add_simple(b, key, INT_MAX, 0, value, len);
}
-
if (spa_json_get_error(it, NULL, NULL))
return -EINVAL;
@@ -192,12 +127,12 @@ static int process_json(struct data *d)
len = 0;
}
- res = dump(d, 0, &it, value, len);
+ res = dump(d, &it, NULL, value, len);
if (spa_json_next(&it, &value) < 0)
res = -EINVAL;
- fprintf(d->file, "\n");
- fflush(d->file);
+ fprintf(d->builder.f, "\n");
+ fflush(d->builder.f);
if (res < 0) {
struct spa_error_location loc;
@@ -257,30 +192,50 @@ int main(int argc, char *argv[])
int c;
int longopt_index = 0;
int fd, res, exit_code = EXIT_FAILURE;
+ int flags = 0, indent = -1;
struct data d;
struct stat sbuf;
+ bool raw = false, colors = false;
spa_zero(d);
- d.file = stdout;
d.filename = "-";
- d.simple_string = false;
- d.comma = ",";
- d.key_sep = ":";
- d.indent = DEFAULT_INDENT;
+ d.out = stdout;
+
+ if (getenv("NO_COLOR") == NULL && isatty(fileno(d.out)))
+ colors = true;
+ setlinebuf(d.out);
while ((c = getopt_long(argc, argv, OPTIONS, long_options, &longopt_index)) != -1) {
switch (c) {
case 'h' :
show_usage(&d, argv[0], false);
return 0;
+ 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_usage(&d, argv[0], true);
+ return -1;
+ }
+ break;
+ case 'R':
+ raw = true;
+ break;
case 'i':
- d.indent = atoi(optarg);
+ indent = atoi(optarg);
break;
case 's':
- d.simple_string = true;
- d.comma = "";
- d.key_sep = " =";
+ flags |= SPA_JSON_BUILDER_FLAG_SIMPLE;
break;
default:
show_usage(&d, argv[0], true);
@@ -308,6 +263,15 @@ int main(int argc, char *argv[])
}
d.size = sbuf.st_size;
+ if (!raw)
+ flags |= SPA_JSON_BUILDER_FLAG_PRETTY;
+ if (colors)
+ flags |= SPA_JSON_BUILDER_FLAG_COLOR;
+
+ spa_json_builder_file(&d.builder, d.out, flags);
+ if (indent >= 0)
+ d.builder.indent = indent;
+
res = process_json(&d);
if (res < 0)
exit_code = EXIT_FAILURE;
diff --git a/src/daemon/client.conf.in b/src/daemon/client.conf.in
index 46874af93..e0baeda92 100644
--- a/src/daemon/client.conf.in
+++ b/src/daemon/client.conf.in
@@ -101,6 +101,9 @@ stream.properties = {
#channelmix.lfe-cutoff = 150
#channelmix.fc-cutoff = 12000
#channelmix.rear-delay = 12.0
+ #channelmix.center-level = 0.707106781
+ #channelmix.surround-level = 0.707106781
+ #channelmix.lfe-level = 0.5
#channelmix.stereo-widen = 0.0
#channelmix.hilbert-taps = 0
#dither.noise = 0
diff --git a/src/daemon/filter-chain/source-rnnoise.conf b/src/daemon/filter-chain/source-rnnoise.conf
index f3c2c71be..83142dd29 100644
--- a/src/daemon/filter-chain/source-rnnoise.conf
+++ b/src/daemon/filter-chain/source-rnnoise.conf
@@ -21,7 +21,6 @@ context.modules = [
# listed in the environment variable LADSPA_PATH or
# /usr/lib64/ladspa, /usr/lib/ladspa or the system library directory
# as a fallback.
- # You might want to use an absolute path here to avoid problems.
plugin = "librnnoise_ladspa"
label = noise_suppressor_stereo
control = {
diff --git a/src/daemon/minimal.conf.in b/src/daemon/minimal.conf.in
index 82647e9ca..7ab93e92b 100644
--- a/src/daemon/minimal.conf.in
+++ b/src/daemon/minimal.conf.in
@@ -100,6 +100,10 @@ context.modules = [
}
flags = [ ifexists nofail ]
}
+ # the graph scheduler
+ { name = libpipewire-module-scheduler-v1
+ condition = [ { module.scheduler-v1 = !false } ]
+ }
# The native communication protocol.
{ name = libpipewire-module-protocol-native }
@@ -328,6 +332,9 @@ context.objects = [
#channelmix.fc-cutoff = 12000
#channelmix.rear-delay = 12.0
#channelmix.stereo-widen = 0.0
+ #channelmix.center-level = 0.707106781
+ #channelmix.surround-level = 0.707106781
+ #channelmix.lfe-level = 0.5
#channelmix.hilbert-taps = 0
#channelmix.disable = false
#dither.noise = 0
diff --git a/src/daemon/pipewire-avb.conf.in b/src/daemon/pipewire-avb.conf.in
index da158f212..e43015780 100644
--- a/src/daemon/pipewire-avb.conf.in
+++ b/src/daemon/pipewire-avb.conf.in
@@ -60,6 +60,9 @@ stream.properties = {
#channelmix.fc-cutoff = 6000
#channelmix.rear-delay = 12.0
#channelmix.stereo-widen = 0.1
+ #channelmix.center-level = 0.707106781
+ #channelmix.surround-level = 0.707106781
+ #channelmix.lfe-level = 0.5
#channelmix.hilbert-taps = 0
}
diff --git a/src/daemon/pipewire-pulse.conf.in b/src/daemon/pipewire-pulse.conf.in
index 8c21e37df..db646db0b 100644
--- a/src/daemon/pipewire-pulse.conf.in
+++ b/src/daemon/pipewire-pulse.conf.in
@@ -88,6 +88,9 @@ stream.properties = {
#channelmix.fc-cutoff = 12000
#channelmix.rear-delay = 12.0
#channelmix.stereo-widen = 0.0
+ #channelmix.center-level = 0.707106781
+ #channelmix.surround-level = 0.707106781
+ #channelmix.lfe-level = 0.5
#channelmix.hilbert-taps = 0
#dither.noise = 0
}
diff --git a/src/daemon/pipewire.conf.in b/src/daemon/pipewire.conf.in
index c3eb7120f..a9142cede 100644
--- a/src/daemon/pipewire.conf.in
+++ b/src/daemon/pipewire.conf.in
@@ -121,6 +121,10 @@ context.modules = [
flags = [ ifexists nofail ]
condition = [ { module.rt = !false } ]
}
+ # the graph scheduler
+ { name = libpipewire-module-scheduler-v1
+ condition = [ { module.scheduler-v1 = !false } ]
+ }
# The native communication protocol.
{ name = libpipewire-module-protocol-native
diff --git a/src/examples/audio-dsp-filter.c b/src/examples/audio-dsp-filter.c
index 8a43147a6..589646d61 100644
--- a/src/examples/audio-dsp-filter.c
+++ b/src/examples/audio-dsp-filter.c
@@ -108,6 +108,7 @@ int main(int argc, char *argv[])
PW_KEY_MEDIA_TYPE, "Audio",
PW_KEY_MEDIA_CATEGORY, "Filter",
PW_KEY_MEDIA_ROLE, "DSP",
+ PW_KEY_NODE_PASSIVE, "follow",
NULL),
&filter_events,
&data);
diff --git a/src/examples/audio-dsp-src.c b/src/examples/audio-dsp-src.c
index 135d2e27e..0ef4a0e53 100644
--- a/src/examples/audio-dsp-src.c
+++ b/src/examples/audio-dsp-src.c
@@ -18,7 +18,6 @@
#define M_PI_M2f (float)(M_PI+M_PI)
-#define DEFAULT_RATE 44100
#define DEFAULT_FREQ 440
#define DEFAULT_VOLUME 0.7f
@@ -61,7 +60,9 @@ static void on_process(void *userdata, struct spa_io_position *position)
return;
for (i = 0; i < n_samples; i++) {
- out_port->accumulator += M_PI_M2f * DEFAULT_FREQ / DEFAULT_RATE;
+ out_port->accumulator += M_PI_M2f * DEFAULT_FREQ *
+ position->clock.rate.num / position->clock.rate.denom;
+
if (out_port->accumulator >= M_PI_M2f)
out_port->accumulator -= M_PI_M2f;
diff --git a/src/examples/video-src-alloc.c b/src/examples/video-src-alloc.c
index add9597ed..c89ce8d78 100644
--- a/src/examples/video-src-alloc.c
+++ b/src/examples/video-src-alloc.c
@@ -192,7 +192,11 @@ static void on_stream_state_changed(void *_data, enum pw_stream_state old, enum
interval.tv_sec = 0;
interval.tv_nsec = 40 * SPA_NSEC_PER_MSEC;
- if (pw_stream_is_driving(data->stream))
+ printf("driving:%d lazy:%d\n",
+ pw_stream_is_driving(data->stream),
+ pw_stream_is_lazy(data->stream));
+
+ if (pw_stream_is_driving(data->stream) != pw_stream_is_lazy(data->stream))
pw_loop_update_timer(pw_thread_loop_get_loop(data->loop),
data->timer, &timeout, &interval, false);
break;
@@ -390,6 +394,7 @@ int main(int argc, char *argv[])
"video-src-alloc",
pw_properties_new(
PW_KEY_MEDIA_CLASS, "Video/Source",
+ PW_KEY_NODE_SUPPORTS_REQUEST, "1",
NULL),
&stream_events,
&data);
diff --git a/src/examples/video-src-fixate.c b/src/examples/video-src-fixate.c
index 6d08074de..b16d810ad 100644
--- a/src/examples/video-src-fixate.c
+++ b/src/examples/video-src-fixate.c
@@ -18,7 +18,11 @@
#include
#include
#include
+#ifdef __linux__
#include
+#else
+#include
+#endif
#include
#include
diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c
index b0de17dfd..3c57028b4 100644
--- a/src/gst/gstpipewiresrc.c
+++ b/src/gst/gstpipewiresrc.c
@@ -531,6 +531,7 @@ gst_pipewire_src_init (GstPipeWireSrc * src)
src->autoconnect = DEFAULT_AUTOCONNECT;
src->min_latency = 0;
src->max_latency = GST_CLOCK_TIME_NONE;
+ src->last_buffer_clock_time = GST_CLOCK_TIME_NONE;
src->n_buffers = 0;
src->flushing_on_remove_buffer = FALSE;
src->on_disconnect = DEFAULT_ON_DISCONNECT;
@@ -838,6 +839,17 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc)
video_size += d->chunk->size;
}
+
+ /* If the buffer number is smaller than the plane number,
+ * update the stride and offset for the remaining planes.
+ */
+ if (n_datas && n_datas < n_planes) {
+ for (i = n_datas; i < n_planes; i++) {
+ meta->stride[i] = gst_video_format_info_extrapolate_stride (info->finfo, i, b->buffer->datas[0].chunk->stride);
+ meta->offset[i] = meta->offset[i-1] +
+ meta->stride[i-1] * GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (info->finfo, i-1, GST_VIDEO_INFO_HEIGHT(info));
+ }
+ }
}
if (b->buffer->n_datas != gst_buffer_n_memory(data->buf)) {
@@ -1073,10 +1085,6 @@ wait_negotiated (GstPipeWireSrc *this)
GST_DEBUG_OBJECT (this, "waiting for NEGOTIATED, 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->negotiated)
break;
@@ -1598,16 +1606,31 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer)
GST_LOG_OBJECT (pwsrc, "EOS, send last buffer");
break;
} else if (timeout && pwsrc->last_buffer != NULL) {
+ buf = gst_buffer_copy (pwsrc->last_buffer);
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);
+ if (pwsrc->resend_last || pwsrc->keepalive_time > 0) {
+ GstClock *clock;
+ GstBuffer *old;
+
+ old = pwsrc->last_buffer;
+ pwsrc->last_buffer = gst_buffer_copy (buf);
+ gst_buffer_unref (old);
+ gst_buffer_add_parent_buffer_meta (pwsrc->last_buffer, buf);
+
+ clock = gst_element_get_clock (GST_ELEMENT_CAST (pwsrc));
+ if (clock != NULL) {
+ pwsrc->last_buffer_clock_time = gst_clock_get_time (clock);
+ gst_object_unref (clock);
+ } else {
+ pwsrc->last_buffer_clock_time = GST_CLOCK_TIME_NONE;
+ }
+ }
break;
}
}
@@ -1632,21 +1655,33 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer)
if (update_time) {
GstClock *clock;
- GstClockTime pts, dts;
+ GstClockTime current_clock_time;
clock = gst_element_get_clock (GST_ELEMENT_CAST (pwsrc));
if (clock != NULL) {
- pts = dts = gst_clock_get_time (clock);
+ current_clock_time = gst_clock_get_time (clock);
gst_object_unref (clock);
} else {
- pts = dts = GST_CLOCK_TIME_NONE;
+ current_clock_time = GST_CLOCK_TIME_NONE;
}
- GST_BUFFER_PTS (*buffer) = pts;
- GST_BUFFER_DTS (*buffer) = dts;
+ if (GST_CLOCK_TIME_IS_VALID (current_clock_time) &&
+ GST_CLOCK_TIME_IS_VALID (pwsrc->last_buffer_clock_time) &&
+ GST_CLOCK_TIME_IS_VALID (GST_BUFFER_PTS (*buffer)) &&
+ GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (*buffer))) {
+ GstClockTime diff;
+
+ diff = current_clock_time - pwsrc->last_buffer_clock_time;
+
+ GST_BUFFER_PTS (*buffer) += diff;
+ GST_BUFFER_DTS (*buffer) += diff;
+ } else {
+ GST_BUFFER_PTS (*buffer) = GST_BUFFER_DTS (*buffer) = current_clock_time;
+ }
GST_LOG_OBJECT (pwsrc, "Sending keepalive buffer pts/dts: %" GST_TIME_FORMAT
- " (%" G_GUINT64_FORMAT ")", GST_TIME_ARGS (pts), pts);
+ " (%" G_GUINT64_FORMAT ")", GST_TIME_ARGS (current_clock_time),
+ current_clock_time);
}
return GST_FLOW_OK;
diff --git a/src/gst/gstpipewiresrc.h b/src/gst/gstpipewiresrc.h
index 869877fcb..4b0f57e0e 100644
--- a/src/gst/gstpipewiresrc.h
+++ b/src/gst/gstpipewiresrc.h
@@ -83,6 +83,7 @@ struct _GstPipeWireSrc {
GstClockTime max_latency;
GstBuffer *last_buffer;
+ GstClockTime last_buffer_clock_time;
enum spa_meta_videotransform_value transform_value;
diff --git a/src/modules/meson.build b/src/modules/meson.build
index 59f46ae13..11b29a117 100644
--- a/src/modules/meson.build
+++ b/src/modules/meson.build
@@ -46,6 +46,7 @@ module_sources = [
'module-vban-recv.c',
'module-vban-send.c',
'module-session-manager.c',
+ 'module-scheduler-v1.c',
'module-zeroconf-discover.c',
'module-roc-source.c',
'module-roc-sink.c',
@@ -295,6 +296,17 @@ pipewire_module_protocol_native = shared_library('pipewire-module-protocol-nativ
dependencies : pipewire_module_protocol_deps,
)
+zeroconf_sources = []
+zeroconf_deps = []
+if avahi_dep.found()
+ zeroconf_sources += [
+ 'zeroconf-utils/zeroconf.c',
+ 'zeroconf-utils/avahi-poll.c',
+ ]
+ zeroconf_deps += avahi_dep
+ cdata.set('HAVE_AVAHI', true)
+endif
+
pipewire_module_protocol_pulse_deps = pipewire_module_protocol_deps
pipewire_module_protocol_pulse_sources = [
@@ -371,10 +383,9 @@ 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)
+ pipewire_module_protocol_pulse_sources += zeroconf_sources
+ pipewire_module_protocol_pulse_deps += zeroconf_deps
endif
if gsettings_gio_dep.found()
@@ -532,6 +543,15 @@ pipewire_module_adapter = shared_library('pipewire-module-adapter',
dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep],
)
+pipewire_module_scheduler_v1 = shared_library('pipewire-module-scheduler-v1',
+ [ 'module-scheduler-v1.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',
@@ -559,12 +579,12 @@ 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' ],
+ zeroconf_sources ],
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],
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, zeroconf_deps],
)
endif
summary({'zeroconf-discover': build_module_zeroconf_discover}, bool_yn: true, section: 'Optional Modules')
@@ -589,12 +609,12 @@ 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' ],
+ zeroconf_sources ],
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],
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, zeroconf_deps],
)
endif
summary({'raop-discover (needs Avahi)': build_module_raop_discover}, bool_yn: true, section: 'Optional Modules')
@@ -603,12 +623,12 @@ build_module_snapcast_discover = avahi_dep.found()
if build_module_snapcast_discover
pipewire_module_snapcast_discover = shared_library('pipewire-module-snapcast-discover',
[ 'module-snapcast-discover.c',
- 'module-zeroconf-discover/avahi-poll.c' ],
+ zeroconf_sources ],
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],
+ dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, zeroconf_deps],
)
endif
summary({'snapcast-discover (needs Avahi)': build_module_snapcast_discover}, bool_yn: true, section: 'Optional Modules')
@@ -651,13 +671,13 @@ pipewire_module_rtp_sink = shared_library('pipewire-module-rtp-sink',
build_module_rtp_session = avahi_dep.found()
if build_module_rtp_session
pipewire_module_rtp_session = shared_library('pipewire-module-rtp-session',
- [ 'module-zeroconf-discover/avahi-poll.c',
- 'module-rtp-session.c' ],
+ [ 'module-rtp-session.c',
+ zeroconf_sources ],
include_directories : [configinc],
install : true,
install_dir : modules_install_dir,
install_rpath: modules_install_dir,
- dependencies : [pipewire_module_rtp_common_dep, avahi_dep],
+ dependencies : [pipewire_module_rtp_common_dep, zeroconf_deps],
)
endif
@@ -690,6 +710,35 @@ pipewire_module_vban_recv = shared_library('pipewire-module-vban-recv',
dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep],
)
+pipewire_module_sendspin_sources = []
+pipewire_module_sendspin_deps = [ mathlib, dl_lib, rt_lib, pipewire_dep ]
+
+if avahi_dep.found()
+ pipewire_module_sendspin_sources += zeroconf_sources
+ pipewire_module_sendspin_deps += zeroconf_deps
+endif
+
+pipewire_module_sendspin_recv = shared_library('pipewire-module-sendspin-recv',
+ [ 'module-sendspin-recv.c',
+ 'module-sendspin/websocket.c',
+ pipewire_module_sendspin_sources ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : pipewire_module_sendspin_deps,
+)
+pipewire_module_sendspin_send = shared_library('pipewire-module-sendspin-send',
+ [ 'module-sendspin-send.c',
+ 'module-sendspin/websocket.c',
+ pipewire_module_sendspin_sources ],
+ include_directories : [configinc],
+ install : true,
+ install_dir : modules_install_dir,
+ install_rpath: modules_install_dir,
+ dependencies : pipewire_module_sendspin_deps,
+)
+
build_module_roc = roc_dep.found()
if build_module_roc
pipewire_module_roc_sink = shared_library('pipewire-module-roc-sink',
diff --git a/src/modules/module-avb/acmp.c b/src/modules/module-avb/acmp.c
index eacfb6133..73a84ba89 100644
--- a/src/modules/module-avb/acmp.c
+++ b/src/modules/module-avb/acmp.c
@@ -174,13 +174,14 @@ static int handle_connect_tx_command(struct acmp *acmp, uint64_t now, const void
return 0;
memcpy(buf, m, len);
+ AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE);
+
stream = find_stream(server, SPA_DIRECTION_OUTPUT, ntohs(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, ntohs(reply->talker_unique_id), now);
@@ -251,14 +252,14 @@ static int handle_disconnect_tx_command(struct acmp *acmp, uint64_t now, const v
return 0;
memcpy(buf, m, len);
+ AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_RESPONSE);
+
stream = find_stream(server, SPA_DIRECTION_OUTPUT, ntohs(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:
diff --git a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c
index 0149d633b..73b7d2548 100644
--- a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c
+++ b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c
@@ -99,7 +99,7 @@ int handle_cmd_lock_entity_milan_v12(struct aecp *aecp, int64_t now, const void
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);
+ return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
entity_state = desc->ptr;
lock = &entity_state->state.lock_state;
@@ -148,7 +148,7 @@ int handle_cmd_lock_entity_milan_v12(struct aecp *aecp, int64_t now, const void
// If the lock is taken again by device
if (ctrler_id == lock->locked_id) {
lock->base_info.expire_timeout +=
- AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND;
+ AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND * SPA_NSEC_PER_SEC;
lock->is_locked = true;
} else {
diff --git a/src/modules/module-avb/aecp-aem.c b/src/modules/module-avb/aecp-aem.c
index fccf6b178..e655f02c5 100644
--- a/src/modules/module-avb/aecp-aem.c
+++ b/src/modules/module-avb/aecp-aem.c
@@ -27,7 +27,8 @@ static int handle_acquire_entity_avb_legacy(struct aecp *aecp, int64_t now,
const void *m, int len)
{
struct server *server = aecp->server;
- const struct avb_packet_aecp_aem *p = m;
+ const struct avb_ethernet_header *h = m;
+ const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
const struct avb_packet_aecp_aem_acquire *ae;
const struct descriptor *desc;
uint16_t desc_type, desc_id;
@@ -53,7 +54,8 @@ static int handle_lock_entity_avb_legacy(struct aecp *aecp, int64_t now,
const void *m, int len)
{
struct server *server = aecp->server;
- const struct avb_packet_aecp_aem *p = m;
+ const struct avb_ethernet_header *h = m;
+ const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
const struct avb_packet_aecp_aem_acquire *ae;
const struct descriptor *desc;
uint16_t desc_type, desc_id;
diff --git a/src/modules/module-avb/avb-transport-loopback.h b/src/modules/module-avb/avb-transport-loopback.h
new file mode 100644
index 000000000..18508b99a
--- /dev/null
+++ b/src/modules/module-avb/avb-transport-loopback.h
@@ -0,0 +1,184 @@
+/* AVB support */
+/* SPDX-FileCopyrightText: Copyright © 2026 PipeWire contributors */
+/* SPDX-License-Identifier: MIT */
+
+#ifndef AVB_TRANSPORT_LOOPBACK_H
+#define AVB_TRANSPORT_LOOPBACK_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "internal.h"
+#include "packets.h"
+
+#define AVB_LOOPBACK_MAX_PACKETS 64
+#define AVB_LOOPBACK_MAX_PACKET_SIZE 2048
+
+struct avb_loopback_packet {
+ uint8_t dest[6];
+ uint16_t type;
+ size_t size;
+ uint8_t data[AVB_LOOPBACK_MAX_PACKET_SIZE];
+};
+
+struct avb_loopback_transport {
+ struct avb_loopback_packet packets[AVB_LOOPBACK_MAX_PACKETS];
+ int packet_count;
+ int packet_read;
+};
+
+static inline int avb_loopback_setup(struct server *server)
+{
+ struct avb_loopback_transport *t;
+ static const uint8_t test_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x01 };
+
+ t = calloc(1, sizeof(*t));
+ if (t == NULL)
+ return -errno;
+
+ server->transport_data = t;
+
+ memcpy(server->mac_addr, test_mac, 6);
+ server->ifindex = 1;
+ 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];
+
+ return 0;
+}
+
+static inline int avb_loopback_send_packet(struct server *server,
+ const uint8_t dest[6], uint16_t type, void *data, size_t size)
+{
+ struct avb_loopback_transport *t = server->transport_data;
+ struct avb_loopback_packet *pkt;
+ struct avb_ethernet_header *hdr = (struct avb_ethernet_header*)data;
+
+ if (t->packet_count >= AVB_LOOPBACK_MAX_PACKETS)
+ return -ENOSPC;
+ if (size > AVB_LOOPBACK_MAX_PACKET_SIZE)
+ return -EMSGSIZE;
+
+ /* Fill in the ethernet header like the raw transport does */
+ memcpy(hdr->dest, dest, 6);
+ memcpy(hdr->src, server->mac_addr, 6);
+ hdr->type = htons(type);
+
+ pkt = &t->packets[t->packet_count % AVB_LOOPBACK_MAX_PACKETS];
+ memcpy(pkt->dest, dest, 6);
+ pkt->type = type;
+ pkt->size = size;
+ memcpy(pkt->data, data, size);
+ t->packet_count++;
+
+ return 0;
+}
+
+/**
+ * Return a dummy fd for protocol handlers that create their own sockets.
+ * Uses eventfd so pw_loop_add_io() has a valid fd to work with.
+ */
+static inline int avb_loopback_make_socket(struct server *server,
+ uint16_t type, const uint8_t mac[6])
+{
+ int fd;
+
+ fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ return fd;
+}
+
+static inline void avb_loopback_destroy(struct server *server)
+{
+ free(server->transport_data);
+ server->transport_data = NULL;
+}
+
+/**
+ * Create a dummy stream socket using eventfd.
+ * No AF_PACKET, no ioctls, no privileges needed.
+ */
+static inline int avb_loopback_stream_setup_socket(struct server *server,
+ struct stream *stream)
+{
+ int fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ spa_zero(stream->sock_addr);
+ stream->sock_addr.sll_family = AF_PACKET;
+ stream->sock_addr.sll_halen = ETH_ALEN;
+
+ return fd;
+}
+
+/**
+ * No-op stream send — pretend the send succeeded.
+ * Audio data is consumed from the ringbuffer but goes nowhere.
+ */
+static inline ssize_t avb_loopback_stream_send(struct server *server,
+ struct stream *stream, struct msghdr *msg, int flags)
+{
+ ssize_t total = 0;
+ for (size_t i = 0; i < msg->msg_iovlen; i++)
+ total += msg->msg_iov[i].iov_len;
+ return total;
+}
+
+static const struct avb_transport_ops avb_transport_loopback = {
+ .setup = avb_loopback_setup,
+ .send_packet = avb_loopback_send_packet,
+ .make_socket = avb_loopback_make_socket,
+ .destroy = avb_loopback_destroy,
+ .stream_setup_socket = avb_loopback_stream_setup_socket,
+ .stream_send = avb_loopback_stream_send,
+};
+
+/** Get the number of captured sent packets */
+static inline int avb_loopback_get_packet_count(struct server *server)
+{
+ struct avb_loopback_transport *t = server->transport_data;
+ return t->packet_count - t->packet_read;
+}
+
+/** Read the next captured sent packet, returns packet size or -1 */
+static inline int avb_loopback_get_packet(struct server *server,
+ void *buf, size_t bufsize)
+{
+ struct avb_loopback_transport *t = server->transport_data;
+ struct avb_loopback_packet *pkt;
+
+ if (t->packet_read >= t->packet_count)
+ return -1;
+
+ pkt = &t->packets[t->packet_read % AVB_LOOPBACK_MAX_PACKETS];
+ t->packet_read++;
+
+ if (pkt->size > bufsize)
+ return -1;
+
+ memcpy(buf, pkt->data, pkt->size);
+ return pkt->size;
+}
+
+/** Clear all captured packets */
+static inline void avb_loopback_clear_packets(struct server *server)
+{
+ struct avb_loopback_transport *t = server->transport_data;
+ t->packet_count = 0;
+ t->packet_read = 0;
+}
+
+#endif /* AVB_TRANSPORT_LOOPBACK_H */
diff --git a/src/modules/module-avb/avdecc.c b/src/modules/module-avb/avdecc.c
index 7cd7e8e7a..8729d6421 100644
--- a/src/modules/module-avb/avdecc.c
+++ b/src/modules/module-avb/avdecc.c
@@ -84,7 +84,7 @@ static void on_socket_data(void *data, int fd, uint32_t mask)
}
}
-int avb_server_send_packet(struct server *server, const uint8_t dest[6],
+static int raw_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;
@@ -101,6 +101,12 @@ int avb_server_send_packet(struct server *server, const uint8_t dest[6],
return res;
}
+int avb_server_send_packet(struct server *server, const uint8_t dest[6],
+ uint16_t type, void *data, size_t size)
+{
+ return server->transport->send_packet(server, dest, type, data, size);
+}
+
static int load_filter(int fd, uint16_t eth, const uint8_t dest[6], const uint8_t mac[6])
{
struct sock_fprog filter;
@@ -136,7 +142,7 @@ static int load_filter(int fd, uint16_t eth, const uint8_t dest[6], const uint8_
return 0;
}
-int avb_server_make_socket(struct server *server, uint16_t type, const uint8_t mac[6])
+static int raw_make_socket(struct server *server, uint16_t type, const uint8_t mac[6])
{
int fd, res;
struct ifreq req;
@@ -209,13 +215,20 @@ error_close:
return res;
}
-static int setup_socket(struct server *server)
+int avb_server_make_socket(struct server *server, uint16_t type, const uint8_t mac[6])
+{
+ if (server->transport && server->transport->make_socket)
+ return server->transport->make_socket(server, type, mac);
+ return raw_make_socket(server, type, mac);
+}
+
+static int raw_transport_setup(struct server *server)
{
struct impl *impl = server->impl;
int fd, res;
static const uint8_t bmac[6] = AVB_BROADCAST_MAC;
- fd = avb_server_make_socket(server, AVB_TSN_ETH, bmac);
+ fd = raw_make_socket(server, AVB_TSN_ETH, bmac);
if (fd < 0)
return fd;
@@ -244,6 +257,119 @@ error_no_source:
return res;
}
+static int raw_stream_setup_socket(struct server *server, struct stream *stream)
+{
+ 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 ssize_t raw_stream_send(struct server *server, struct stream *stream,
+ struct msghdr *msg, int flags)
+{
+ return sendmsg(stream->source->fd, msg, flags);
+}
+
+int avb_server_stream_setup_socket(struct server *server, struct stream *stream)
+{
+ return server->transport->stream_setup_socket(server, stream);
+}
+
+ssize_t avb_server_stream_send(struct server *server, struct stream *stream,
+ struct msghdr *msg, int flags)
+{
+ return server->transport->stream_send(server, stream, msg, flags);
+}
+
+static void raw_transport_destroy(struct server *server)
+{
+ struct impl *impl = server->impl;
+ if (server->source)
+ pw_loop_destroy_source(impl->loop, server->source);
+ server->source = NULL;
+}
+
+const struct avb_transport_ops avb_transport_raw = {
+ .setup = raw_transport_setup,
+ .send_packet = raw_send_packet,
+ .make_socket = raw_make_socket,
+ .destroy = raw_transport_destroy,
+ .stream_setup_socket = raw_stream_setup_socket,
+ .stream_send = raw_stream_send,
+};
+
struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props)
{
struct server *server;
@@ -266,10 +392,14 @@ struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props)
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)
+ if (server->transport == NULL)
+ server->transport = &avb_transport_raw;
+
+ if ((res = server->transport->setup(server)) < 0)
goto error_free;
@@ -315,12 +445,10 @@ void avdecc_server_add_listener(struct server *server, struct spa_hook *listener
void avdecc_server_free(struct server *server)
{
- struct impl *impl = server->impl;
-
server_destroy_descriptors(server);
spa_list_remove(&server->link);
- if (server->source)
- pw_loop_destroy_source(impl->loop, server->source);
+ if (server->transport)
+ server->transport->destroy(server);
pw_timer_queue_cancel(&server->timer);
spa_hook_list_clean(&server->listener_list);
free(server->ifname);
diff --git a/src/modules/module-avb/internal.h b/src/modules/module-avb/internal.h
index 82ced2f21..bb4961674 100644
--- a/src/modules/module-avb/internal.h
+++ b/src/modules/module-avb/internal.h
@@ -5,6 +5,8 @@
#ifndef AVB_INTERNAL_H
#define AVB_INTERNAL_H
+#include
+
#include
#ifdef __cplusplus
@@ -17,6 +19,22 @@ struct avb_mrp;
#define AVB_TSN_ETH 0x22f0
#define AVB_BROADCAST_MAC { 0x91, 0xe0, 0xf0, 0x01, 0x00, 0x00 };
+struct stream;
+
+struct avb_transport_ops {
+ int (*setup)(struct server *server);
+ int (*send_packet)(struct server *server, const uint8_t dest[6],
+ uint16_t type, void *data, size_t size);
+ int (*make_socket)(struct server *server, uint16_t type,
+ const uint8_t mac[6]);
+ void (*destroy)(struct server *server);
+
+ /* stream data plane ops */
+ int (*stream_setup_socket)(struct server *server, struct stream *stream);
+ ssize_t (*stream_send)(struct server *server, struct stream *stream,
+ struct msghdr *msg, int flags);
+};
+
struct impl {
struct pw_loop *loop;
struct pw_timer_queue *timer_queue;
@@ -77,12 +95,16 @@ struct server {
uint64_t entity_id;
int ifindex;
+ const struct avb_transport_ops *transport;
+ void *transport_data;
+
struct spa_source *source;
struct pw_timer timer;
struct spa_hook_list listener_list;
struct spa_list descriptors;
+ struct spa_list streams;
unsigned debug_messages:1;
@@ -102,7 +124,6 @@ static inline void server_destroy_descriptors(struct server *server)
struct descriptor *d, *t;
spa_list_for_each_safe(d, t, &server->descriptors, link) {
- free(d->ptr);
spa_list_remove(&d->link);
free(d);
}
@@ -145,11 +166,17 @@ 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);
+extern const struct avb_transport_ops avb_transport_raw;
+
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);
+int avb_server_stream_setup_socket(struct server *server, struct stream *stream);
+ssize_t avb_server_stream_send(struct server *server, struct stream *stream,
+ struct msghdr *msg, int flags);
+
struct aecp {
struct server *server;
struct spa_hook server_listener;
diff --git a/src/modules/module-avb/mrp.c b/src/modules/module-avb/mrp.c
index 73d5275ca..c6505d41b 100644
--- a/src/modules/module-avb/mrp.c
+++ b/src/modules/module-avb/mrp.c
@@ -302,6 +302,8 @@ const char *avb_mrp_notify_name(uint8_t notify)
const char *avb_mrp_send_name(uint8_t send)
{
switch(send) {
+ case 0:
+ return "none";
case AVB_MRP_SEND_NEW:
return "new";
case AVB_MRP_SEND_JOININ:
diff --git a/src/modules/module-avb/mrp.h b/src/modules/module-avb/mrp.h
index 78683412c..399343267 100644
--- a/src/modules/module-avb/mrp.h
+++ b/src/modules/module-avb/mrp.h
@@ -88,13 +88,13 @@ struct avb_packet_mrp_footer {
#define AVB_MRP_ATTRIBUTE_EVENT_LV 5
#define AVB_MRP_ATTRIBUTE_EVENT_LVA 6
-#define AVB_MRP_SEND_NEW 0
-#define AVB_MRP_SEND_JOININ 1
-#define AVB_MRP_SEND_IN 2
-#define AVB_MRP_SEND_JOINMT 3
-#define AVB_MRP_SEND_MT 4
-#define AVB_MRP_SEND_LV 5
-#define AVB_MRP_SEND_LVA 6
+#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_SEND_LVA 7
#define AVB_MRP_NOTIFY_NEW 1
#define AVB_MRP_NOTIFY_JOIN 2
diff --git a/src/modules/module-avb/msrp.c b/src/modules/module-avb/msrp.c
index 92d1e65b4..611ddd537 100644
--- a/src/modules/module-avb/msrp.c
+++ b/src/modules/module-avb/msrp.c
@@ -91,7 +91,7 @@ static int encode_talker(struct msrp *msrp, struct attr *a, void *m)
*t = a->attr.attr.talker;
ev = SPA_PTROFF(t, sizeof(*t), uint8_t);
- *ev = a->attr.mrp->pending_send * 6 * 6;
+ *ev = (a->attr.mrp->pending_send - 1) * 6 * 6;
f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer);
f->end_mark = 0;
@@ -170,7 +170,7 @@ static int encode_listener(struct msrp *msrp, struct attr *a, void *m)
*l = a->attr.attr.listener;
ev = SPA_PTROFF(l, sizeof(*l), uint8_t);
- *ev = a->attr.mrp->pending_send * 6 * 6;
+ *ev = (a->attr.mrp->pending_send - 1) * 6 * 6;
ev = SPA_PTROFF(ev, sizeof(*ev), uint8_t);
*ev = a->attr.param * 4 * 4 * 4;
@@ -226,7 +226,7 @@ static int encode_domain(struct msrp *msrp, struct attr *a, void *m)
*d = a->attr.attr.domain;
ev = SPA_PTROFF(d, sizeof(*d), uint8_t);
- *ev = a->attr.mrp->pending_send * 36;
+ *ev = (a->attr.mrp->pending_send - 1) * 36;
f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer);
f->end_mark = 0;
@@ -332,7 +332,8 @@ 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);
+ if (dispatch[a->attr.type].notify)
+ dispatch[a->attr.type].notify(msrp, now, a, notify);
}
static const struct avb_mrp_attribute_events mrp_attr_events = {
diff --git a/src/modules/module-avb/mvrp.c b/src/modules/module-avb/mvrp.c
index 20862c2ae..e2e501a9e 100644
--- a/src/modules/module-avb/mvrp.c
+++ b/src/modules/module-avb/mvrp.c
@@ -84,7 +84,7 @@ static int encode_vid(struct mvrp *mvrp, struct attr *a, void *m)
*d = a->attr.attr.vid;
ev = SPA_PTROFF(d, sizeof(*d), uint8_t);
- *ev = a->attr.mrp->pending_send * 36;
+ *ev = (a->attr.mrp->pending_send - 1) * 36;
f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer);
f->end_mark = 0;
@@ -171,7 +171,8 @@ 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);
+ if (dispatch[a->attr.type].notify)
+ dispatch[a->attr.type].notify(mvrp, now, a, notify);
}
static const struct avb_mrp_attribute_events mrp_attr_events = {
diff --git a/src/modules/module-avb/stream.c b/src/modules/module-avb/stream.c
index f7101bdf0..26a3a795b 100644
--- a/src/modules/module-avb/stream.c
+++ b/src/modules/module-avb/stream.c
@@ -116,9 +116,10 @@ static int flush_write(struct stream *stream, uint64_t current_time)
p->timestamp = ptime;
p->dbc = dbc;
- n = sendmsg(stream->source->fd, &stream->msg, MSG_NOSIGNAL);
+ n = avb_server_stream_send(stream->server, stream,
+ &stream->msg, MSG_NOSIGNAL);
if (n < 0 || n != (ssize_t)stream->pdu_size) {
- pw_log_error("sendmsg() failed %zd != %zd: %m",
+ pw_log_error("stream send failed %zd != %zd: %m",
n, stream->pdu_size);
}
txtime += stream->pdu_period;
@@ -331,6 +332,8 @@ struct stream *server_create_stream(struct server *server, struct stream *stream
stream->talker_attr->attr.talker.rank = AVB_MSRP_RANK_DEFAULT;
stream->talker_attr->attr.talker.accumulated_latency = htonl(95);
+ spa_list_append(&server->streams, &stream->link);
+
return stream;
error_free_stream:
@@ -348,82 +351,7 @@ void stream_destroy(struct stream *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;
+ return avb_server_stream_setup_socket(stream->server, stream);
}
static void handle_iec61883_packet(struct stream *stream,
@@ -548,3 +476,24 @@ int stream_deactivate(struct stream *stream, uint64_t now)
}
return 0;
}
+
+int stream_activate_virtual(struct stream *stream, uint16_t index)
+{
+ struct server *server = stream->server;
+ int fd;
+
+ if (stream->source == NULL) {
+ fd = setup_socket(stream);
+ if (fd < 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) {
+ close(fd);
+ return -errno;
+ }
+ }
+ pw_stream_set_active(stream->stream, true);
+ return 0;
+}
diff --git a/src/modules/module-avb/stream.h b/src/modules/module-avb/stream.h
index f650cc216..4cc02ddd3 100644
--- a/src/modules/module-avb/stream.h
+++ b/src/modules/module-avb/stream.h
@@ -78,5 +78,6 @@ void stream_destroy(struct stream *stream);
int stream_activate(struct stream *stream, uint16_t index, uint64_t now);
int stream_deactivate(struct stream *stream, uint64_t now);
+int stream_activate_virtual(struct stream *stream, uint16_t index);
#endif /* AVB_STREAM_H */
diff --git a/src/modules/module-client-node/client-node.c b/src/modules/module-client-node/client-node.c
index 0253591cf..14fda3a77 100644
--- a/src/modules/module-client-node/client-node.c
+++ b/src/modules/module-client-node/client-node.c
@@ -92,7 +92,6 @@ struct impl {
struct spa_node node;
- struct spa_log *log;
struct spa_loop *data_loop;
struct spa_system *data_system;
@@ -264,7 +263,8 @@ static void clear_data(struct impl *impl, struct spa_data *d)
case SPA_DATA_DmaBuf:
case SPA_DATA_SyncObj:
pw_log_debug("%p: close fd:%d", impl, (int)d->fd);
- close(d->fd);
+ if (d->fd != -1)
+ close(d->fd);
break;
}
}
@@ -282,7 +282,7 @@ static int clear_buffers(struct impl *impl, struct mix *mix)
for (i = 0; i < mix->n_buffers; i++) {
struct buffer *b = &mix->buffers[i];
- spa_log_debug(impl->log, "%p: clear buffer %d", impl, i);
+ pw_log_debug("%p: clear buffer %d", impl, i);
clear_buffer(impl, &b->buffer);
pw_memblock_unref(b->mem);
}
@@ -299,7 +299,7 @@ static void free_mix(struct port *p, struct mix *mix)
if (mix->n_buffers) {
/* this shouldn't happen */
- spa_log_warn(impl->log, "%p: mix port-id:%u freeing leaked buffers", impl, mix->mix_id - 1u);
+ pw_log_warn("%p: mix port-id:%u freeing leaked buffers", impl, mix->mix_id - 1u);
}
clear_buffers(impl, mix);
@@ -330,11 +330,13 @@ static int impl_node_enum_params(void *object, int seq,
struct spa_pod_dynamic_builder b;
struct spa_result_node_params result;
uint32_t count = 0;
- bool found = false;
spa_return_val_if_fail(impl != NULL, -EINVAL);
spa_return_val_if_fail(num != 0, -EINVAL);
+ if (!pw_param_info_find(impl->this.node->info.params, impl->this.node->info.n_params, id))
+ return -ENOENT;
+
result.id = id;
result.next = 0;
@@ -350,8 +352,6 @@ static int impl_node_enum_params(void *object, int seq,
if (param == NULL || !spa_pod_is_object_id(param, id))
continue;
- found = true;
-
if (result.index < start)
continue;
@@ -366,7 +366,8 @@ static int impl_node_enum_params(void *object, int seq,
if (count == num)
break;
}
- return found ? 0 : -ENOENT;
+
+ return 0;
}
static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
@@ -508,7 +509,7 @@ do_update_port(struct impl *impl,
const struct spa_port_info *info)
{
if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_PARAMS) {
- spa_log_debug(impl->log, "%p: port %u update %d params", impl, port->id, n_params);
+ pw_log_debug("%p: port %u update %d params", impl, port->id, n_params);
update_params(&port->params, n_params, params);
}
@@ -542,7 +543,7 @@ static int mix_clear_cb(void *item, void *data)
static void
clear_port(struct impl *impl, struct port *port)
{
- spa_log_debug(impl->log, "%p: clear port %p", impl, port);
+ pw_log_debug("%p: clear port %p", impl, port);
do_update_port(impl, port,
PW_CLIENT_NODE_PORT_UPDATE_PARAMS |
@@ -598,7 +599,6 @@ node_port_enum_params(struct impl *impl, int seq,
struct spa_pod_dynamic_builder b;
struct spa_result_node_params result;
uint32_t count = 0;
- bool found = false;
spa_return_val_if_fail(impl != NULL, -EINVAL);
spa_return_val_if_fail(num != 0, -EINVAL);
@@ -609,6 +609,9 @@ node_port_enum_params(struct impl *impl, int seq,
pw_log_debug("%p: seq:%d port %d.%d id:%u start:%u num:%u n_params:%d",
impl, seq, direction, port_id, id, start, num, port->params.n_params);
+ if (!pw_param_info_find(port->port->info.params, port->port->info.n_params, id))
+ return -ENOENT;
+
result.id = id;
result.next = 0;
@@ -624,8 +627,6 @@ node_port_enum_params(struct impl *impl, int seq,
if (param == NULL || !spa_pod_is_object_id(param, id))
continue;
- found = true;
-
if (result.index < start)
continue;
@@ -640,7 +641,7 @@ node_port_enum_params(struct impl *impl, int seq,
if (count == num)
break;
}
- return found ? 0 : -ENOENT;
+ return 0;
}
static int
@@ -781,7 +782,7 @@ do_port_use_buffers(struct impl *impl,
if (n_buffers > MAX_BUFFERS)
return -ENOSPC;
- spa_log_debug(impl->log, "%p: %s port %d.%d use buffers %p %u flags:%08x", impl,
+ pw_log_debug("%p: %s port %d.%d use buffers %p %u flags:%08x", impl,
direction == SPA_DIRECTION_INPUT ? "input" : "output",
port_id, mix_id, buffers, n_buffers, flags);
@@ -851,7 +852,7 @@ do_port_use_buffers(struct impl *impl,
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(impl->log, "%p: buffer %d %d %d %d", impl, i, mb[i].mem_id,
+ pw_log_debug("%p: buffer %d %d %d %d", impl, i, mb[i].mem_id,
mb[i].offset, mb[i].size);
b->buffer.n_metas = SPA_MIN(buffers[i]->n_metas, MAX_METAS);
@@ -864,8 +865,11 @@ do_port_use_buffers(struct impl *impl,
memcpy(&b->datas[j], d, sizeof(struct spa_data));
- if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC)
+ if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) {
+ b->datas[j].fd = -1;
+ b->datas[j].data = SPA_UINT32_TO_PTR(SPA_ID_INVALID);
continue;
+ }
switch (d->type) {
case SPA_DATA_DmaBuf:
@@ -881,7 +885,7 @@ do_port_use_buffers(struct impl *impl,
if (d->flags & SPA_DATA_FLAG_WRITABLE)
flags |= PW_MEMBLOCK_FLAG_WRITABLE;
- spa_log_debug(impl->log, "mem %d type:%d fd:%d", j, d->type, (int)d->fd);
+ pw_log_debug("mem %d type:%d fd:%d", j, d->type, (int)d->fd);
m = pw_mempool_import(impl->client_pool,
flags, d->type, d->fd);
if (m == NULL)
@@ -892,14 +896,14 @@ do_port_use_buffers(struct impl *impl,
break;
}
case SPA_DATA_MemPtr:
- spa_log_debug(impl->log, "mem %d %zd", j, SPA_PTRDIFF(d->data, baseptr));
+ pw_log_debug("mem %d %zd", j, SPA_PTRDIFF(d->data, baseptr));
b->datas[j].data = SPA_INT_TO_PTR(SPA_PTRDIFF(d->data, baseptr));
SPA_FLAG_CLEAR(b->datas[j].flags, SPA_DATA_FLAG_MAPPABLE);
break;
default:
b->datas[j].type = SPA_ID_INVALID;
b->datas[j].data = NULL;
- spa_log_error(impl->log, "invalid memory type %d", d->type);
+ pw_log_error("invalid memory type %d", d->type);
break;
}
}
@@ -935,7 +939,7 @@ impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t buffer_id)
spa_return_val_if_fail(impl != NULL, -EINVAL);
spa_return_val_if_fail(CHECK_PORT(impl, SPA_DIRECTION_OUTPUT, port_id), -EINVAL);
- spa_log_trace_fp(impl->log, "reuse buffer %d", buffer_id);
+ pw_log_trace_fp("reuse buffer %d", buffer_id);
return -ENOTSUP;
}
@@ -948,7 +952,7 @@ static int impl_node_process(void *object)
/* this should not be called, we call the exported node
* directly */
- spa_log_warn(impl->log, "exported node activation");
+ pw_log_warn("exported node activation");
spa_system_clock_gettime(impl->data_system, CLOCK_MONOTONIC, &ts);
n->rt.target.activation->status = PW_NODE_ACTIVATION_TRIGGERED;
n->rt.target.activation->signal_time = SPA_TIMESPEC_TO_NSEC(&ts);
@@ -1009,7 +1013,7 @@ client_node_port_update(void *data,
struct port *port;
bool remove;
- spa_log_debug(impl->log, "%p: got port update change:%08x params:%d",
+ pw_log_debug("%p: got port update change:%08x params:%d",
impl, change_mask, n_params);
remove = (change_mask == 0);
@@ -1047,7 +1051,7 @@ client_node_port_update(void *data,
static int client_node_set_active(void *data, bool active)
{
struct impl *impl = data;
- spa_log_debug(impl->log, "%p: active:%d", impl, active);
+ pw_log_debug("%p: active:%d", impl, active);
return pw_impl_node_set_active(impl->this.node, active);
}
@@ -1070,7 +1074,7 @@ static int client_node_port_buffers(void *data,
struct mix *mix;
uint32_t i, j;
- spa_log_debug(impl->log, "%p: %s port %d.%d buffers %p %u", impl,
+ pw_log_debug("%p: %s port %d.%d buffers %p %u", impl,
direction == SPA_DIRECTION_INPUT ? "input" : "output",
port_id, mix_id, buffers, n_buffers);
@@ -1098,7 +1102,7 @@ static int client_node_port_buffers(void *data,
oldbuf = b->outbuf;
newbuf = buffers[i];
- spa_log_debug(impl->log, "buffer %d n_datas:%d", i, newbuf->n_datas);
+ pw_log_debug("buffer %d n_datas:%d", i, newbuf->n_datas);
for (j = 0; j < b->buffer.n_datas; j++) {
struct spa_chunk *oldchunk = oldbuf->datas[j].chunk;
@@ -1107,7 +1111,7 @@ static int client_node_port_buffers(void *data,
if (d->type == SPA_DATA_MemFd &&
!SPA_FLAG_IS_SET(flags, SPA_DATA_FLAG_MAPPABLE)) {
- spa_log_debug(impl->log, "buffer:%d data:%d has non mappable MemFd, "
+ pw_log_debug("buffer:%d data:%d has non mappable MemFd, "
"fixing to ensure backwards compatibility.",
i, j);
flags |= SPA_DATA_FLAG_MAPPABLE;
@@ -1122,7 +1126,7 @@ static int client_node_port_buffers(void *data,
b->datas[j].flags = flags;
b->datas[j].fd = d->fd;
- spa_log_debug(impl->log, " data %d type:%d fl:%08x fd:%d, offs:%d max:%d",
+ pw_log_debug(" data %d type:%d fl:%08x fd:%d, offs:%d max:%d",
j, d->type, flags, (int) d->fd, d->mapoffset,
d->maxsize);
}
@@ -1149,7 +1153,7 @@ static void node_on_data_fd_events(struct spa_source *source)
struct impl *impl = source->data;
if (SPA_UNLIKELY(source->rmask & (SPA_IO_ERR | SPA_IO_HUP))) {
- spa_log_warn(impl->log, "%p: got error", impl);
+ pw_log_warn("%p: got error", impl);
return;
}
if (SPA_LIKELY(source->rmask & SPA_IO_IN)) {
@@ -1166,10 +1170,10 @@ static void node_on_data_fd_events(struct spa_source *source)
if (impl->resource && impl->resource->version < 5) {
struct pw_node_activation *a = node->rt.target.activation;
int status = a->state[0].status;
- spa_log_trace_fp(impl->log, "%p: got ready %d", impl, status);
+ pw_log_trace_fp("%p: got ready %d", impl, status);
spa_node_call_ready(&impl->callbacks, status);
} else {
- spa_log_trace_fp(impl->log, "%p: got complete", impl);
+ pw_log_trace_fp("%p: got complete", impl);
pw_impl_node_rt_emit_complete(node);
}
}
@@ -1770,7 +1774,6 @@ struct pw_impl_client_node *pw_impl_client_node_new(struct pw_resource *resource
pw_log_debug("%p: new", &impl->node);
impl_init(impl, NULL);
- impl->log = pw_log_get();
impl->resource = resource;
impl->client = client;
impl->client_pool = pw_impl_client_get_mempool(client);
diff --git a/src/modules/module-ffado-driver.c b/src/modules/module-ffado-driver.c
index f6a76bed0..c98b0efcd 100644
--- a/src/modules/module-ffado-driver.c
+++ b/src/modules/module-ffado-driver.c
@@ -345,34 +345,26 @@ static void midi_to_ffado(struct port *p, float *src, uint32_t n_samples)
p->event_pos = 0;
while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) {
- uint8_t data[16];
- int j, size;
- size_t c_size = c.value.size;
- uint64_t state = 0;
+ uint32_t j, size = c.value.size;
+ const uint8_t *data = c_body;
- if (c.type != SPA_CONTROL_UMP)
+ if (c.type != SPA_CONTROL_Midi)
continue;
if (index < c.offset)
index = SPA_ROUND_UP_N(c.offset, 8);
- while (c_size > 0) {
- size = spa_ump_to_midi((const uint32_t**)&c_body, &c_size, data, sizeof(data), &state);
- if (size <= 0)
- break;
-
- for (j = 0; j < size; j++) {
- if (index >= n_samples) {
- /* keep events that don't fit for the next cycle */
- if (p->event_pos < sizeof(p->event_buffer))
- p->event_buffer[p->event_pos++] = data[j];
- else
- unhandled++;
- }
+ for (j = 0; j < size; j++) {
+ if (index >= n_samples) {
+ /* keep events that don't fit for the next cycle */
+ if (p->event_pos < sizeof(p->event_buffer))
+ p->event_buffer[p->event_pos++] = data[j];
else
- dst[index] = 0x01000000 | (uint32_t) data[j];
- index += 8;
+ unhandled++;
}
+ else
+ dst[index] = 0x01000000 | (uint32_t) data[j];
+ index += 8;
}
}
if (unhandled > 0)
@@ -497,16 +489,8 @@ static void ffado_to_midi(struct port *p, float *dst, uint32_t *src, uint32_t si
continue;
if (process_byte(p, i, data & 0xff, &frame, &bytes, &size)) {
- uint64_t state = 0;
- while (size > 0) {
- uint32_t ev[4];
- int ev_size = spa_ump_from_midi(&bytes, &size, ev, sizeof(ev), 0, &state);
- if (ev_size <= 0)
- break;
-
- spa_pod_builder_control(&b, frame, SPA_CONTROL_UMP);
- spa_pod_builder_bytes(&b, ev, ev_size);
- }
+ spa_pod_builder_control(&b, frame, SPA_CONTROL_Midi);
+ spa_pod_builder_bytes(&b, bytes, size);
}
}
spa_pod_builder_pop(&b, &f);
@@ -1556,8 +1540,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true");
if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL)
pw_properties_set(props, PW_KEY_NODE_GROUP, "ffado-group");
- if (pw_properties_get(props, PW_KEY_NODE_LINK_GROUP) == NULL)
- pw_properties_set(props, PW_KEY_NODE_LINK_GROUP, "ffado-group");
if (pw_properties_get(props, PW_KEY_NODE_PAUSE_ON_IDLE) == NULL)
pw_properties_set(props, PW_KEY_NODE_PAUSE_ON_IDLE, "false");
diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c
index 64a92c5ac..c369810fe 100644
--- a/src/modules/module-filter-chain.c
+++ b/src/modules/module-filter-chain.c
@@ -128,7 +128,7 @@ extern struct spa_handle_factory spa_filter_graph_factory;
* # an example ladspa plugin
* type = ladspa
* name = pitch
- * plugin = "/usr/lib64/ladspa/ladspa-rubberband.so"
+ * plugin = "ladspa-rubberband"
* label = "rubberband-r3-pitchshifter-mono"
* control = {
* # controls are using the ladspa port names as seen in analyseplugin
@@ -398,10 +398,10 @@ extern struct spa_handle_factory spa_filter_graph_factory;
* 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.
+ * - `gain` the overall gain to apply to the IR file. Default 1.0
* - `delay` The extra delay to add to the IR. A float number will be interpreted as seconds,
* and integer as samples. Using the delay in seconds is independent of the graph
- * and IR rate and is recommended.
+ * and IR rate and is recommended. Default 0
* - `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
@@ -1651,7 +1651,7 @@ static const struct pw_stream_events out_stream_events = {
static int setup_streams(struct impl *impl)
{
- int res;
+ int res = 0;
uint32_t i, n_params, *offs, flags;
struct pw_array offsets;
const struct spa_pod **params = NULL;
@@ -1702,6 +1702,19 @@ static int setup_streams(struct impl *impl)
spa_process_latency_build(&b.b,
SPA_PARAM_ProcessLatency, &impl->process_latency);
+
+ if (impl->capture || impl->playback) {
+ if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) != NULL)
+ *offs = b.b.state.offset;
+
+ if (impl->capture)
+ spa_format_audio_raw_build(&b.b,
+ SPA_PARAM_EnumFormat, &impl->capture_info);
+ else
+ spa_format_audio_raw_build(&b.b,
+ SPA_PARAM_EnumFormat, &impl->playback_info);
+ }
+
n_params = pw_array_get_len(&offsets, uint32_t);
if (n_params == 0) {
res = -ENOMEM;
@@ -1717,8 +1730,6 @@ static int setup_streams(struct impl *impl)
params[i] = spa_pod_builder_deref(&b.b, offs[i]);
if (impl->capture) {
- params[n_params++] = spa_format_audio_raw_build(&b.b,
- SPA_PARAM_EnumFormat, &impl->capture_info);
flags = PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS;
@@ -1739,8 +1750,9 @@ static int setup_streams(struct impl *impl)
spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
}
if (impl->playback) {
- params[n_params++] = spa_format_audio_raw_build(&b.b,
- SPA_PARAM_EnumFormat, &impl->playback_info);
+ if (n_params == 0)
+ params[n_params++] = spa_format_audio_raw_build(&b.b,
+ SPA_PARAM_EnumFormat, &impl->playback_info);
flags = PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_MAP_BUFFERS |
diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c
index 0c0cee034..bee37da4f 100644
--- a/src/modules/module-jack-tunnel.c
+++ b/src/modules/module-jack-tunnel.c
@@ -50,7 +50,6 @@
*
* - `jack.library`: the libjack to load, by default libjack.so.0 is searched in
* LIBJACK_PATH directories and then some standard library paths.
- * Can be an absolute path.
* - `jack.server`: the name of the JACK server to tunnel to.
* - `jack.client-name`: the name of the JACK client.
* - `jack.connect`: if jack ports should be connected automatically. Can also be
@@ -243,13 +242,16 @@ static inline void do_volume(float *dst, const float *src, struct volume *vol, u
}
}
-static inline void fix_midi_event(uint8_t *data, size_t size)
+static inline bool fix_midi_event(const uint8_t *data, size_t size, uint8_t dst[3])
{
/* fixup NoteOn with vel 0 */
if (size > 2 && (data[0] & 0xF0) == 0x90 && data[2] == 0x00) {
- data[0] = 0x80 + (data[0] & 0x0F);
- data[2] = 0x40;
+ dst[0] = 0x80 + (data[0] & 0x0F);
+ dst[1] = data[1];
+ dst[2] = 0x40;
+ return true;
}
+ return false;
}
static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_samples)
@@ -260,9 +262,6 @@ static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_s
struct spa_pod_control c;
const void *seq_body, *c_body;
int res;
- bool in_sysex = false;
- uint8_t tmp[n_samples * 4];
- size_t tmp_size = 0;
jack.midi_clear_buffer(dst);
if (src == NULL)
@@ -274,36 +273,20 @@ static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_s
return;
while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) {
- int size;
- size_t c_size = c.value.size;
- uint64_t state = 0;
+ uint32_t size = c.value.size;
+ const uint8_t *data = c_body;
+ uint8_t tmp[3];
- if (c.type != SPA_CONTROL_UMP)
+ if (c.type != SPA_CONTROL_Midi)
continue;
- while (c_size > 0) {
- size = spa_ump_to_midi((const uint32_t**)&c_body, &c_size,
- &tmp[tmp_size], sizeof(tmp) - tmp_size, &state);
- if (size <= 0)
- break;
-
- if (impl->fix_midi)
- fix_midi_event(&tmp[tmp_size], size);
-
- if (!in_sysex && tmp[tmp_size] == 0xf0)
- in_sysex = true;
-
- tmp_size += size;
- if (in_sysex && tmp[tmp_size-1] == 0xf7)
- in_sysex = false;
-
- if (!in_sysex) {
- if ((res = jack.midi_event_write(dst, c.offset, tmp, tmp_size)) < 0)
- pw_log_warn("midi %p: can't write event: %s", dst,
- spa_strerror(res));
- tmp_size = 0;
- }
+ if (impl->fix_midi && fix_midi_event(data, size, tmp)) {
+ data = tmp;
+ size = 3;
}
+ if ((res = jack.midi_event_write(dst, c.offset, data, size)) < 0)
+ pw_log_warn("midi %p: can't write event: %s", dst,
+ spa_strerror(res));
}
}
@@ -319,19 +302,11 @@ static void jack_to_midi(float *dst, float *src, uint32_t size)
spa_pod_builder_push_sequence(&b, &f, 0);
for (i = 0; i < count; i++) {
jack_midi_event_t ev;
- uint64_t state = 0;
jack.midi_event_get(&ev, src, i);
- while (ev.size > 0) {
- uint32_t ump[4];
- int ump_size = spa_ump_from_midi(&ev.buffer, &ev.size, ump, sizeof(ump), 0, &state);
- if (ump_size <= 0)
- break;
-
- spa_pod_builder_control(&b, ev.time, SPA_CONTROL_UMP);
- spa_pod_builder_bytes(&b, ump, ump_size);
- }
+ spa_pod_builder_control(&b, ev.time, SPA_CONTROL_Midi);
+ spa_pod_builder_bytes(&b, ev.buffer, ev.size);
}
spa_pod_builder_pop(&b, &f);
}
diff --git a/src/modules/module-jack-tunnel/weakjack.h b/src/modules/module-jack-tunnel/weakjack.h
index 6d5b5503e..472adb253 100644
--- a/src/modules/module-jack-tunnel/weakjack.h
+++ b/src/modules/module-jack-tunnel/weakjack.h
@@ -158,34 +158,36 @@ static inline int weakjack_load_by_path(struct weakjack *jack, const char *path)
static inline int weakjack_load(struct weakjack *jack, const char *lib)
{
int res = -ENOENT;
+ const char *search_dirs, *p, *state = NULL;
+ char path[PATH_MAX];
+ size_t len;
- if (lib[0] != '/') {
- const char *search_dirs, *p, *state = NULL;
- char path[PATH_MAX];
- size_t len;
+ while ((p = strstr(lib, "../")) != NULL)
+ lib = p + 3;
- search_dirs = getenv("LIBJACK_PATH");
- if (!search_dirs)
- search_dirs = PREFIX "/lib64/:" PREFIX "/lib/:"
- "/usr/lib64/:/usr/lib/:" LIBDIR;
+ search_dirs = getenv("LIBJACK_PATH");
+ if (!search_dirs)
+ search_dirs = PREFIX "/lib64/:" PREFIX "/lib/:"
+ "/usr/lib64/:/usr/lib/:" LIBDIR;
- while ((p = pw_split_walk(search_dirs, ":", &len, &state))) {
- int pathlen;
+ res = -ENAMETOOLONG;
- if (len >= sizeof(path)) {
- res = -ENAMETOOLONG;
- continue;
- }
+ while ((p = pw_split_walk(search_dirs, ":", &len, &state))) {
+ int pathlen;
+
+ if (len == 0 || len >= sizeof(path))
+ continue;
+
+ if (strncmp(lib, p, len) == 0 && lib[len] == '/')
+ pathlen = snprintf(path, sizeof(path), "%s", lib);
+ else
pathlen = snprintf(path, sizeof(path), "%.*s/%s", (int) len, p, lib);
- if (pathlen < 0 || (size_t) pathlen >= sizeof(path)) {
- res = -ENAMETOOLONG;
- continue;
- }
- if ((res = weakjack_load_by_path(jack, path)) == 0)
- break;
- }
- } else {
- res = weakjack_load_by_path(jack, lib);
+
+ if (pathlen < 0 || (size_t) pathlen >= sizeof(path))
+ continue;
+
+ if ((res = weakjack_load_by_path(jack, path)) == 0)
+ break;
}
return res;
}
diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c
index 0ff62d93b..a9af6a9b0 100644
--- a/src/modules/module-netjack2-manager.c
+++ b/src/modules/module-netjack2-manager.c
@@ -1393,8 +1393,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
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_NODE_LINK_GROUP) == NULL)
- pw_properties_set(props, PW_KEY_NODE_LINK_GROUP, "jack-group");
if (pw_properties_get(props, PW_KEY_NODE_ALWAYS_PROCESS) == NULL)
pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true");
if (pw_properties_get(props, PW_KEY_NODE_LOCK_QUANTUM) == NULL)
diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c
index eacc1c95b..7547cc5b2 100644
--- a/src/modules/module-netjack2/peer.c
+++ b/src/modules/module-netjack2/peer.c
@@ -273,39 +273,18 @@ static inline void *n2j_midi_buffer_reserve(struct nj2_midi_buffer *buf,
}
static inline void n2j_midi_buffer_write(struct nj2_midi_buffer *buf,
- uint32_t offset, void *data, uint32_t size)
+ uint32_t offset, const void *data, uint32_t size, bool fix)
{
- void *ptr = n2j_midi_buffer_reserve(buf, offset, size);
- if (ptr != NULL)
+ uint8_t *ptr = n2j_midi_buffer_reserve(buf, offset, size);
+ if (ptr != NULL) {
memcpy(ptr, data, size);
+ if (fix)
+ fix_midi_event(ptr, size);
+ }
else
buf->lost_events++;
}
-static inline void n2j_midi_buffer_append(struct nj2_midi_buffer *buf,
- void *data, uint32_t size)
-{
- struct nj2_midi_event *ev;
- uint32_t old_size;
- uint8_t *old_ptr, *new_ptr;
-
- ev = &buf->event[--buf->event_count];
- old_size = ev->size;
- if (old_size <= MIDI_INLINE_MAX) {
- old_ptr = ev->buffer;
- } else {
- buf->write_pos -= old_size;
- old_ptr = SPA_PTROFF(buf, ev->offset, void);
- }
- new_ptr = n2j_midi_buffer_reserve(buf, ev->time, old_size + size);
- if (new_ptr == NULL) {
- buf->lost_events++;
- } else {
- memmove(new_ptr, old_ptr, old_size);
- memcpy(new_ptr+old_size, data, size);
- }
-}
-
static void midi_to_netjack2(struct netjack2_peer *peer,
struct nj2_midi_buffer *buf, float *src, uint32_t n_samples)
{
@@ -314,7 +293,6 @@ static void midi_to_netjack2(struct netjack2_peer *peer,
struct spa_pod_sequence seq;
struct spa_pod_control c;
const void *seq_body, *c_body;
- bool in_sysex = false;
buf->magic = MIDI_BUFFER_MAGIC;
buf->buffer_size = peer->params.period_size * sizeof(float);
@@ -332,39 +310,17 @@ static void midi_to_netjack2(struct netjack2_peer *peer,
return;
while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) {
- int size;
- uint8_t data[16];
- bool was_sysex = in_sysex;
- size_t c_size = c.value.size;
- uint64_t state = 0;
+ uint32_t size = c.value.size;
+ const uint8_t *data = c_body;
- if (c.type != SPA_CONTROL_UMP)
+ if (c.type != SPA_CONTROL_Midi)
continue;
- while (c_size > 0) {
- size = spa_ump_to_midi((const uint32_t**)&c_body, &c_size, data, sizeof(data), &state);
- if (size <= 0)
- break;
-
- if (c.offset >= n_samples) {
- buf->lost_events++;
- continue;
- }
-
- if (!in_sysex && data[0] == 0xf0)
- in_sysex = true;
-
- if (!in_sysex && peer->fix_midi)
- fix_midi_event(data, size);
-
- if (in_sysex && data[size-1] == 0xf7)
- in_sysex = false;
-
- if (was_sysex)
- n2j_midi_buffer_append(buf, data, size);
- else
- n2j_midi_buffer_write(buf, c.offset, data, size);
+ if (c.offset >= n_samples) {
+ buf->lost_events++;
+ continue;
}
+ n2j_midi_buffer_write(buf, c.offset, data, size, peer->fix_midi);
}
if (buf->write_pos > 0)
memmove(SPA_PTROFF(buf, sizeof(*buf) + buf->event_count * sizeof(struct nj2_midi_event), void),
@@ -395,8 +351,6 @@ static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_b
for (i = 0; i < buf->event_count; i++) {
struct nj2_midi_event *ev = &buf->event[i];
uint8_t *data;
- size_t s;
- uint64_t state = 0;
if (ev->size <= MIDI_INLINE_MAX)
data = ev->buffer;
@@ -405,17 +359,8 @@ static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_b
else
continue;
- s = ev->size;
- while (s > 0) {
- uint32_t ump[4];
- int ump_size = spa_ump_from_midi(&data, &s, ump, sizeof(ump), 0, &state);
- if (ump_size <= 0) {
- pw_log_warn("invalid MIDI received: %s", spa_strerror(ump_size));
- break;
- }
- spa_pod_builder_control(&b, ev->time, SPA_CONTROL_UMP);
- spa_pod_builder_bytes(&b, ump, ump_size);
- }
+ spa_pod_builder_control(&b, ev->time, SPA_CONTROL_Midi);
+ spa_pod_builder_bytes(&b, data, ev->size);
}
spa_pod_builder_pop(&b, &f);
}
diff --git a/src/modules/module-profiler.c b/src/modules/module-profiler.c
index b49629ba9..1709ec9a5 100644
--- a/src/modules/module-profiler.c
+++ b/src/modules/module-profiler.c
@@ -166,7 +166,7 @@ static void do_flush_event(void *data, uint64_t count)
avail = spa_ringbuffer_get_read_index(&n->buffer, &idx);
- pw_log_trace("%p: avail %d", impl, avail);
+ pw_log_trace("%p: node:%p avail %d", n, impl, avail);
if (avail > 0) {
size_t size = total + avail + sizeof(struct spa_pod_struct);
diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c
index 2be92a847..98a43829b 100644
--- a/src/modules/module-protocol-native.c
+++ b/src/modules/module-protocol-native.c
@@ -907,7 +907,7 @@ static int add_socket(struct pw_protocol *protocol, struct server *s, struct soc
bool activated = false;
{
- int i, n = listen_fd();
+ int i, n = listen_fds();
for (i = 0; i < n; ++i) {
if (is_socket_unix(LISTEN_FDS_START + i, SOCK_STREAM,
s->addr.sun_path) > 0) {
diff --git a/src/modules/module-protocol-native/connection.c b/src/modules/module-protocol-native/connection.c
index b376a0f69..15c739428 100644
--- a/src/modules/module-protocol-native/connection.c
+++ b/src/modules/module-protocol-native/connection.c
@@ -536,7 +536,7 @@ static int prepare_packet(struct pw_protocol_native_connection *conn, struct buf
size -= impl->hdr_size;
buf->msg.fds = &buf->fds[buf->fds_offset];
- if (buf->msg.n_fds + buf->fds_offset > MAX_FDS)
+ if (buf->msg.n_fds + buf->fds_offset > buf->n_fds)
return -EPROTO;
if (size < len)
diff --git a/src/modules/module-protocol-native/test-connection.c b/src/modules/module-protocol-native/test-connection.c
index eabcbbd67..0c82d2da8 100644
--- a/src/modules/module-protocol-native/test-connection.c
+++ b/src/modules/module-protocol-native/test-connection.c
@@ -3,6 +3,7 @@
/* SPDX-License-Identifier: MIT */
#include
+#include
#include
#include
@@ -165,6 +166,87 @@ static void test_reentering(struct pw_protocol_native_connection *in,
}
}
+/*
+ * Test that a packet claiming more FDs in its header than were actually
+ * sent via SCM_RIGHTS is rejected. Without the n_fds validation this
+ * would cause the receiver to read uninitialised / stale FD values.
+ */
+static void test_spoofed_fds(struct pw_protocol_native_connection *in,
+ struct pw_protocol_native_connection *out)
+{
+ const struct pw_protocol_native_message *msg;
+ int res;
+
+ /*
+ * First, send a valid message through the normal API so that the
+ * receiver's version handshake happens (it switches to HDR_SIZE=16
+ * on the first message). Use a message with 0 FDs.
+ */
+ {
+ struct spa_pod_builder *b;
+ struct pw_protocol_native_message *wmsg;
+
+ b = pw_protocol_native_connection_begin(out, 0, 1, &wmsg);
+ spa_assert_se(b != NULL);
+ spa_pod_builder_add_struct(b, SPA_POD_Int(0));
+ pw_protocol_native_connection_end(out, b);
+ pw_protocol_native_connection_flush(out);
+
+ /* Consume it on the reading side */
+ res = pw_protocol_native_connection_get_next(in, &msg);
+ spa_assert_se(res == 1);
+ }
+
+ /*
+ * Now craft a raw packet on the wire that claims n_fds=5 in the
+ * header but send 0 actual FDs via SCM_RIGHTS.
+ *
+ * v3 header layout (16 bytes / 4 uint32s):
+ * p[0] = id
+ * p[1] = (opcode << 24) | (payload_size & 0xffffff)
+ * p[2] = seq
+ * p[3] = n_fds
+ *
+ * We need a minimal valid SPA pod as payload.
+ */
+ {
+ /* Build a tiny SPA pod: struct { Int(0) } */
+ uint8_t payload[32];
+ struct spa_pod_builder pb;
+
+ spa_pod_builder_init(&pb, payload, sizeof(payload));
+ spa_pod_builder_add_struct(&pb, SPA_POD_Int(0));
+
+ uint32_t payload_size = pb.state.offset;
+ uint32_t header[4];
+
+ spa_assert_se(payload_size <= sizeof(payload));
+
+ header[0] = 1; /* id */
+ header[1] = (5u << 24) | (payload_size & 0xffffff); /* opcode=5, size */
+ header[2] = 0; /* seq */
+ header[3] = 5; /* SPOOFED: claim 5 fds, send 0 */
+
+ struct iovec iov[2];
+ struct msghdr mh = { 0 };
+
+ iov[0].iov_base = header;
+ iov[0].iov_len = sizeof(header);
+ iov[1].iov_base = payload;
+ iov[1].iov_len = payload_size;
+ mh.msg_iov = iov;
+ mh.msg_iovlen = 2;
+ /* No msg_control — 0 FDs via SCM_RIGHTS */
+
+ ssize_t sent = sendmsg(out->fd, &mh, MSG_NOSIGNAL);
+ spa_assert_se(sent == (ssize_t)(sizeof(header) + payload_size));
+ }
+
+ /* The receiver must reject this packet */
+ res = pw_protocol_native_connection_get_next(in, &msg);
+ spa_assert_se(res == -EPROTO);
+}
+
int main(int argc, char *argv[])
{
struct pw_main_loop *loop;
@@ -198,6 +280,26 @@ int main(int argc, char *argv[])
pw_protocol_native_connection_destroy(in);
pw_protocol_native_connection_destroy(out);
+
+ /* test_spoofed_fds needs its own connection pair */
+ {
+ int fds2[2];
+ struct pw_protocol_native_connection *in2, *out2;
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds2) < 0)
+ spa_assert_not_reached();
+
+ in2 = pw_protocol_native_connection_new(context, fds2[0]);
+ spa_assert_se(in2 != NULL);
+ out2 = pw_protocol_native_connection_new(context, fds2[1]);
+ spa_assert_se(out2 != NULL);
+
+ test_spoofed_fds(in2, out2);
+
+ pw_protocol_native_connection_destroy(in2);
+ pw_protocol_native_connection_destroy(out2);
+ }
+
pw_context_destroy(context);
pw_main_loop_destroy(loop);
diff --git a/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c
index d4425a3fe..ffb5aa0fe 100644
--- a/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c
+++ b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c
@@ -15,14 +15,7 @@
#include "../module.h"
#include "../pulse-server.h"
#include "../server.h"
-#include "../../module-zeroconf-discover/avahi-poll.h"
-
-#include
-#include
-#include
-#include
-#include
-#include
+#include "../../zeroconf-utils/zeroconf.h"
/** \page page_pulse_module_zeroconf_publish Zeroconf Publish
*
@@ -52,32 +45,17 @@ PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME);
#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;
};
@@ -91,8 +69,8 @@ struct module_zeroconf_publish_data {
struct spa_hook manager_listener;
struct spa_hook impl_listener;
- AvahiPoll *avahi_poll;
- AvahiClient *client;
+ struct pw_zeroconf *zeroconf;
+ struct spa_hook zeroconf_listener;
/* lists of services */
struct spa_list pending;
@@ -116,47 +94,43 @@ static const struct pw_core_events core_events = {
.error = on_core_error,
};
-static void get_service_name(struct pw_manager_object *o, char *buf, size_t length)
+static void unpublish_service(struct service *s)
{
- const char *hn, *un, *n;
+ const char *device;
- hn = pw_get_host_name();
- un = pw_get_user_name();
- n = pw_properties_get(o->props, PW_KEY_NODE_DESCRIPTION);
+ spa_list_remove(&s->link);
+ spa_list_append(&s->userdata->pending, &s->link);
+ s->published = false;
+ s->server = NULL;
- snprintf(buf, length, "%s@%s: %s", un, hn, n);
+ device = pw_properties_get(s->props, "device");
+
+ pw_log_info("unpublished service: %s", device);
+
+ pw_zeroconf_set_announce(s->userdata->zeroconf, s, 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 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);
+ if (s->published)
+ unpublish_service(s);
pw_properties_free(s->props);
- avahi_string_list_free(s->txt);
spa_list_remove(&s->link);
+ /* no need to free, the service is added as custom
+ * data on the object */
}
-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);
-}
+#define PA_CHANNEL_MAP_SNPRINT_MAX (CHANNELS_MAX * 32)
static char* channel_map_snprint(char *s, size_t l, const struct channel_map *map)
{
@@ -188,6 +162,39 @@ static char* channel_map_snprint(char *s, size_t l, const struct channel_map *ma
return s;
}
+static void txt_record_server_data(struct pw_core_info *info, struct pw_properties *props)
+{
+ struct utsname u;
+
+ spa_assert(info);
+
+ pw_properties_set(props, "server-version", PACKAGE_NAME" "PACKAGE_VERSION);
+ pw_properties_set(props, "user-name", pw_get_user_name());
+ pw_properties_set(props, "fqdn", pw_get_host_name());
+ pw_properties_setf(props, "cookie", "0x%08x", info->cookie);
+ if (uname(&u) >= 0)
+ pw_properties_setf(props, "uname", "%s %s %s", u.sysname, u.machine, u.release);
+}
+
+static void fill_service_txt(const struct service *s, const struct pw_properties *props)
+{
+ 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" },
+ };
+ SPA_FOR_EACH_ELEMENT_VAR(mappings, m) {
+ const char *value = pw_properties_get(props, m->pw_key);
+ if (value != NULL)
+ pw_properties_set(s->props, m->txt_key, value);
+ }
+}
+
static void fill_service_data(struct module_zeroconf_publish_data *d, struct service *s,
struct pw_manager_object *o)
{
@@ -200,6 +207,10 @@ static void fill_service_data(struct module_zeroconf_publish_data *d, struct ser
struct card_info card_info = CARD_INFO_INIT;
struct device_info dev_info;
uint32_t flags = 0;
+ const char *service_type, *subtype, *subtype_service[2];
+ uint32_t n_subtype = 0;
+ char cm[PA_CHANNEL_MAP_SNPRINT_MAX];
+
if (info == NULL || info->props == NULL)
return;
@@ -228,19 +239,49 @@ static void fill_service_data(struct module_zeroconf_publish_data *d, struct ser
s->ss = dev_info.ss;
s->cm = dev_info.map;
- s->name = strdup(name);
- s->props = pw_properties_copy(o->props);
+
+ s->props = pw_properties_new(NULL, NULL);
+
+ txt_record_server_data(s->userdata->manager->info, s->props);
if (is_sink) {
- s->is_sink = true;
- s->service_type = SERVICE_TYPE_SINK;
- s->subtype = flags & SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;
+ service_type = SERVICE_TYPE_SINK;
+ if (flags & SINK_HARDWARE) {
+ subtype = "hardware";
+ subtype_service[n_subtype++] = SERVICE_SUBTYPE_SINK_HARDWARE;
+ } else {
+ subtype = "virtual";
+ subtype_service[n_subtype++] = SERVICE_SUBTYPE_SINK_VIRTUAL;
+ }
} else if (is_source) {
- s->is_sink = false;
- s->service_type = SERVICE_TYPE_SOURCE;
- s->subtype = flags & SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL;
+ service_type = SERVICE_TYPE_SOURCE;
+ if (flags & SOURCE_HARDWARE) {
+ subtype = "hardware";
+ subtype_service[n_subtype++] = SERVICE_SUBTYPE_SOURCE_HARDWARE;
+ } else {
+ subtype = "virtual";
+ subtype_service[n_subtype++] = SERVICE_SUBTYPE_SOURCE_VIRTUAL;
+ }
+ subtype_service[n_subtype++] = SERVICE_SUBTYPE_SOURCE_NON_MONITOR;
} else
spa_assert_not_reached();
+
+ pw_properties_set(s->props, "device", name);
+ pw_properties_setf(s->props, "rate", "%u", s->ss.rate);
+ pw_properties_setf(s->props, "channels", "%u", s->ss.channels);
+ pw_properties_set(s->props, "format", format_id2paname(s->ss.format));
+ pw_properties_set(s->props, "channel_map", channel_map_snprint(cm, sizeof(cm), &s->cm));
+ pw_properties_set(s->props, "subtype", subtype);
+
+ pw_properties_setf(s->props, PW_KEY_ZEROCONF_NAME, "%s@%s: %s",
+ pw_get_user_name(), pw_get_host_name(), desc);
+ pw_properties_set(s->props, PW_KEY_ZEROCONF_TYPE, service_type);
+ pw_properties_setf(s->props, PW_KEY_ZEROCONF_SUBTYPES, "[ %s%s%s ]",
+ n_subtype > 0 ? subtype_service[0] : "",
+ n_subtype > 1 ? ", " : "",
+ n_subtype > 1 ? subtype_service[1] : "");
+
+ fill_service_txt(s, o->props);
}
static struct service *create_service(struct module_zeroconf_publish_data *d, struct pw_manager_object *o)
@@ -252,8 +293,6 @@ static struct service *create_service(struct module_zeroconf_publish_data *d, st
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);
@@ -263,127 +302,6 @@ static struct service *create_service(struct module_zeroconf_publish_data *d, st
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;
@@ -392,109 +310,47 @@ static struct server *find_server(struct service *s, int *proto, uint16_t *port)
spa_list_for_each(server, &impl->servers, link) {
if (server->addr.ss_family == AF_INET) {
- *proto = AVAHI_PROTO_INET;
+ *proto = 4;
*port = ntohs(((struct sockaddr_in*) &server->addr)->sin_port);
return server;
} else if (server->addr.ss_family == AF_INET6) {
- *proto = AVAHI_PROTO_INET6;
+ *proto = 6;
*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;
+ int proto, res;
uint16_t port;
-
struct server *server = find_server(s, &proto, &port);
+ const char *device;
+
if (!server)
return;
+ device = pw_properties_get(s->props, "device");
+
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)
+ pw_properties_setf(s->props, PW_KEY_ZEROCONF_PROTO, "%d", proto);
+ pw_properties_setf(s->props, PW_KEY_ZEROCONF_PORT, "%d", port);
+
+ if ((res = pw_zeroconf_set_announce(s->userdata->zeroconf, s, &s->props->dict)) < 0) {
+ pw_log_error("failed to announce service %s: %s", device, spa_strerror(res));
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->published = true;
s->server = server;
- pw_log_info("created service: %s", s->service_name);
- return;
-
-error:
- s->published = false;
+ pw_log_info("published service: %s", device);
return;
}
@@ -506,62 +362,6 @@ static void publish_pending(struct module_zeroconf_publish_data *data)
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))
@@ -624,7 +424,6 @@ static void impl_server_stopped(void *data, struct server *server)
if (s->server == server)
unpublish_service(s);
}
-
publish_pending(d);
}
@@ -634,10 +433,18 @@ static const struct impl_events impl_events = {
.server_stopped = impl_server_stopped,
};
+static void on_zeroconf_error(void *data, int err, const char *message)
+{
+ pw_log_error("got zeroconf error %d: %s", err, message);
+}
+static const struct pw_zeroconf_events zeroconf_events = {
+ PW_VERSION_ZEROCONF_EVENTS,
+ .error = on_zeroconf_error,
+};
+
static int module_zeroconf_publish_load(struct module *module)
{
struct module_zeroconf_publish_data *data = module->user_data;
- int error;
data->core = pw_context_connect(module->impl->context, NULL, 0);
if (data->core == NULL) {
@@ -649,24 +456,22 @@ static int module_zeroconf_publish_load(struct module *module)
&data->core_listener,
&core_events, data);
- data->avahi_poll = pw_avahi_poll_new(module->impl->context);
-
- 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);
+ data->zeroconf = pw_zeroconf_new(module->impl->context, NULL);
+ if (!data->zeroconf) {
+ pw_log_error("failed to create zeroconf: %m");
+ return -errno;
+ }
+ pw_zeroconf_add_listener(data->zeroconf, &data->zeroconf_listener,
+ &zeroconf_events, data);
+
impl_add_listener(module->impl, &data->impl_listener, &impl_events, data);
return 0;
@@ -684,22 +489,18 @@ static int module_zeroconf_publish_unload(struct module *module)
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->zeroconf) {
+ spa_hook_remove(&d->zeroconf_listener);
+ pw_zeroconf_destroy(d->zeroconf);
+ }
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;
}
diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c
index 59610ef57..2006a01cc 100644
--- a/src/modules/module-protocol-pulse/pulse-server.c
+++ b/src/modules/module-protocol-pulse/pulse-server.c
@@ -1621,7 +1621,7 @@ static int do_create_playback_stream(struct client *client, uint32_t command, ui
struct pw_manager_object *o;
bool is_monitor;
- props = pw_properties_copy(client->props);
+ props = pw_properties_new(NULL, NULL);
if (props == NULL)
goto error_errno;
@@ -1907,7 +1907,7 @@ static int do_create_record_stream(struct client *client, uint32_t command, uint
struct pw_manager_object *o;
bool is_monitor = false;
- props = pw_properties_copy(client->props);
+ props = pw_properties_new(NULL, NULL);
if (props == NULL)
goto error_errno;
@@ -2298,7 +2298,7 @@ static int do_create_upload_stream(struct client *client, uint32_t command, uint
struct message *reply;
int res;
- if ((props = pw_properties_copy(client->props)) == NULL)
+ if ((props = pw_properties_new(NULL, NULL)) == NULL)
goto error_errno;
if ((res = message_get(m,
@@ -4068,6 +4068,45 @@ static const char *get_media_name(struct pw_node_info *info)
return media_name;
}
+static int fill_node_info_proplist(struct message *m, const struct spa_dict *node_props,
+ const struct pw_manager_object *client)
+{
+ struct pw_client_info *client_info = client ? client->info : NULL;
+ uint32_t n_items, n;
+ struct spa_dict dict, *client_props = NULL;
+ const struct spa_dict_item *it;
+ struct spa_dict_item *items, *it2;
+
+ n_items = node_props->n_items;
+ if (client_info && client_info->props) {
+ client_props = client_info->props;
+ n_items += client_props->n_items;
+ }
+
+ dict.n_items = n = 0;
+ dict.items = items = alloca(n_items * sizeof(struct spa_dict_item));
+
+ spa_dict_for_each(it, node_props)
+ items[n++] = *it;
+ dict.n_items = n;
+
+ if (client_props) {
+ spa_dict_for_each(it, client_props) {
+ if (spa_streq(it->key, PW_KEY_OBJECT_ID) ||
+ spa_streq(it->key, PW_KEY_OBJECT_SERIAL))
+ continue;
+
+ if ((it2 = (struct spa_dict_item*)spa_dict_lookup_item(&dict, it->key)))
+ it2->value = it->value;
+ else
+ items[n++] = *it;
+ }
+ dict.n_items = n;
+ }
+ message_put(m, TAG_PROPLIST, &dict, TAG_INVALID);
+ return 0;
+}
+
static int fill_sink_input_info(struct client *client, struct message *m,
struct pw_manager_object *o)
{
@@ -4128,10 +4167,16 @@ static int fill_sink_input_info(struct client *client, struct message *m,
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 >= 13) {
+ int res;
+ struct pw_manager_object *c = NULL;
+ if (client_id != SPA_ID_INVALID) {
+ struct selector sel = { .id = client_id, .type = pw_manager_object_is_client, };
+ c = select_object(manager, &sel);
+ }
+ if ((res = fill_node_info_proplist(m, info->props, c)) < 0)
+ return res;
+ }
if (client->version >= 19)
message_put(m,
TAG_BOOLEAN, corked, /* corked */
@@ -4207,10 +4252,16 @@ static int fill_source_output_info(struct client *client, struct message *m,
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 >= 13) {
+ int res;
+ struct pw_manager_object *c = NULL;
+ if (client_id != SPA_ID_INVALID) {
+ struct selector sel = { .id = client_id, .type = pw_manager_object_is_client, };
+ c = select_object(manager, &sel);
+ }
+ if ((res = fill_node_info_proplist(m, info->props, c)) < 0)
+ return res;
+ }
if (client->version >= 19)
message_put(m,
TAG_BOOLEAN, corked, /* corked */
@@ -4699,7 +4750,6 @@ static int do_set_profile(struct client *client, uint32_t command, uint32_t 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;
@@ -4716,10 +4766,10 @@ static int do_set_default(struct client *client, uint32_t command, uint32_t tag,
if (spa_streq(name, "@NONE@"))
name = NULL;
- if (name != NULL && (o = find_device(client, SPA_ID_INVALID, name, sink, NULL)) == NULL)
- return -ENOENT;
-
if (name != NULL) {
+ struct pw_manager_object *o;
+ if ((o = find_device(client, SPA_ID_INVALID, name, sink, NULL)) == NULL)
+ return -ENOENT;
if (o->props && (str = pw_properties_get(o->props, PW_KEY_NODE_NAME)) != NULL)
name = str;
else if (spa_strendswith(name, ".monitor"))
diff --git a/src/modules/module-protocol-pulse/server.c b/src/modules/module-protocol-pulse/server.c
index aeab710b0..637757dfd 100644
--- a/src/modules/module-protocol-pulse/server.c
+++ b/src/modules/module-protocol-pulse/server.c
@@ -576,7 +576,7 @@ static bool is_stale_socket(int fd, const struct sockaddr_un *addr_un)
static int check_socket_activation(const char *path)
{
- const int n = listen_fd();
+ const int n = listen_fds();
for (int i = 0; i < n; i++) {
const int fd = LISTEN_FDS_START + i;
diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c
index 436972ac0..3675b003e 100644
--- a/src/modules/module-raop-discover.c
+++ b/src/modules/module-raop-discover.c
@@ -19,12 +19,8 @@
#include
#include
-#include
-#include
-#include
-
+#include "zeroconf-utils/zeroconf.h"
#include "module-protocol-pulse/format.h"
-#include "module-zeroconf-discover/avahi-poll.h"
/** \page page_module_raop_discover RAOP Discover
*
@@ -129,29 +125,20 @@ struct impl {
struct pw_properties *properties;
- AvahiPoll *avahi_poll;
- AvahiClient *client;
- AvahiServiceBrowser *sink_browser;
+ struct pw_zeroconf *zeroconf;
+ struct spa_hook zeroconf_listener;
struct spa_list tunnel_list;
};
-struct tunnel_info {
- const char *name;
-};
-
-#define TUNNEL_INFO(...) ((struct tunnel_info){ __VA_ARGS__ })
-
struct tunnel {
struct spa_list link;
- struct tunnel_info info;
+ char *name;
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)
+static struct tunnel *tunnel_new(struct impl *impl, const char *name)
{
struct tunnel *t;
@@ -159,28 +146,28 @@ static struct tunnel *make_tunnel(struct impl *impl, const struct tunnel_info *i
if (t == NULL)
return NULL;
- t->info.name = strdup(info->name);
+ t->name = strdup(name);
spa_list_append(&impl->tunnel_list, &t->link);
return t;
}
-static struct tunnel *find_tunnel(struct impl *impl, const struct tunnel_info *info)
+static struct tunnel *find_tunnel(struct impl *impl, const char *name)
{
struct tunnel *t;
spa_list_for_each(t, &impl->tunnel_list, link) {
- if (spa_streq(t->info.name, info->name))
+ if (spa_streq(t->name, name))
return t;
}
return NULL;
}
-static void free_tunnel(struct tunnel *t)
+static void tunnel_free(struct tunnel *t)
{
spa_list_remove(&t->link);
if (t->module)
pw_impl_module_destroy(t->module);
- free((char *) t->info.name);
+ free(t->name);
free(t);
}
@@ -189,14 +176,9 @@ 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);
+ tunnel_free(t);
+ if (impl->zeroconf)
+ pw_zeroconf_destroy(impl->zeroconf);
pw_properties_free(impl->properties);
free(impl);
}
@@ -224,75 +206,6 @@ static bool str_in_list(const char *haystack, const char *delimiters, const char
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")) {
- /* RAOP encryption types:
- * 0 = none,
- * 1 = RSA,
- * 3 = FairPlay,
- * 4 = MFiSAP (/auth-setup),
- * 5 = FairPlay SAPv2.5 */
- if (str_in_list(value, ",", "5"))
- value = "fp_sap25";
- else if (str_in_list(value, ",", "4"))
- value = "auth_setup";
- else if (str_in_list(value, ",", "1"))
- value = "RSA";
- else
- value = "none";
- pw_properties_set(props, "raop.encryption.type", value);
- } else if (spa_streq(key, "cn")) {
- /* Supported 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, ",", "3"))
- 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;
@@ -364,76 +277,124 @@ static int rule_matched(void *data, const char *location, const char *action,
return res;
}
-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)
+static void pw_properties_from_zeroconf(const char *key, const char *value,
+ struct pw_properties *props)
{
- struct impl *impl = userdata;
- struct tunnel_info tinfo;
- struct tunnel *t;
- const char *str, *link_local_range = "169.254.";
- AvahiStringList *l;
- struct pw_properties *props = NULL;
- char at[AVAHI_ADDRESS_STR_MAX], if_suffix[16] = "";
-
- if (event != AVAHI_RESOLVER_FOUND) {
- pw_log_error("Resolving of '%s' failed: %s", name,
- avahi_strerror(avahi_client_errno(impl->client)));
- goto done;
+ if (spa_streq(key, PW_KEY_ZEROCONF_IFINDEX)) {
+ pw_properties_set(props, "raop.ifindex", value);
}
+ else if (spa_streq(key, PW_KEY_ZEROCONF_ADDRESS)) {
+ pw_properties_set(props, "raop.ip", value);
+ }
+ else if (spa_streq(key, PW_KEY_ZEROCONF_PORT)) {
+ pw_properties_set(props, "raop.port", value);
+ }
+ else if (spa_streq(key, PW_KEY_ZEROCONF_NAME)) {
+ pw_properties_set(props, "raop.name", value);
+ }
+ else if (spa_streq(key, PW_KEY_ZEROCONF_HOSTNAME)) {
+ pw_properties_set(props, "raop.hostname", value);
+ }
+ else if (spa_streq(key, PW_KEY_ZEROCONF_DOMAIN)) {
+ pw_properties_set(props, "raop.domain", value);
+ }
+ else 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")) {
+ /* RAOP encryption types:
+ * 0 = none,
+ * 1 = RSA,
+ * 3 = FairPlay,
+ * 4 = MFiSAP (/auth-setup),
+ * 5 = FairPlay SAPv2.5 */
+ if (str_in_list(value, ",", "5"))
+ value = "fp_sap25";
+ else if (str_in_list(value, ",", "4"))
+ value = "auth_setup";
+ else if (str_in_list(value, ",", "1"))
+ value = "RSA";
+ else
+ value = "none";
+ pw_properties_set(props, "raop.encryption.type", value);
+ } else if (spa_streq(key, "cn")) {
+ /* Supported 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, ",", "3"))
+ 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);
+ }
+}
- avahi_address_snprint(at, sizeof(at), a);
- if (spa_strstartswith(at, link_local_range))
- pw_log_info("found link-local ip address %s for '%s'", at, name);
+static void on_zeroconf_added(void *data, const void *user, const struct spa_dict *info)
+{
+ struct impl *impl = data;
+ const char *name, *str;
+ struct tunnel *t;
+ const struct spa_dict_item *it;
+ struct pw_properties *props = NULL;
- tinfo = TUNNEL_INFO(.name = name);
+ name = spa_dict_lookup(info, PW_KEY_ZEROCONF_NAME);
- t = find_tunnel(impl, &tinfo);
- if (t == NULL)
- t = make_tunnel(impl, &tinfo);
+ t = find_tunnel(impl, name);
if (t == NULL) {
- pw_log_error("Can't make tunnel: %m");
- goto done;
+ if ((t = tunnel_new(impl, name)) == NULL) {
+ pw_log_error("Can't make tunnel: %m");
+ goto done;
+ }
}
if (t->module != NULL) {
- pw_log_info("found duplicate mdns entry for %s on IP %s - skipping tunnel creation", name, at);
+ pw_log_info("found duplicate mdns entry for %s on IP %s - "
+ "skipping tunnel creation", name,
+ spa_dict_lookup(info, PW_KEY_ZEROCONF_ADDRESS));
goto done;
}
- props = pw_properties_new(NULL, NULL);
- if (props == NULL) {
+ if ((props = pw_properties_new(NULL, NULL)) == NULL) {
pw_log_error("Can't allocate properties: %m");
goto done;
}
- 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);
-
- /* For IPv4 link-local, bind to the discovery interface */
- if (a->proto == AVAHI_PROTO_INET &&
- spa_strstartswith(at, link_local_range))
- snprintf(if_suffix, sizeof(if_suffix), "%%%d", interface);
-
- pw_properties_setf(props, "raop.ip", "%s%s", at, if_suffix);
- pw_properties_setf(props, "raop.ifindex", "%d", interface);
- pw_properties_setf(props, "raop.port", "%u", port);
- pw_properties_setf(props, "raop.name", "%s", name);
- pw_properties_setf(props, "raop.hostname", "%s", host_name);
- pw_properties_setf(props, "raop.domain", "%s", domain);
-
- 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);
- }
+ spa_dict_for_each(it, info)
+ pw_properties_from_zeroconf(it->key, it->value, props);
if ((str = pw_properties_get(impl->properties, "raop.latency.ms")) != NULL)
pw_properties_set(props, "raop.latency.ms", str);
@@ -452,123 +413,28 @@ static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiPr
if (!minfo.matched)
pw_log_info("unmatched service found %s", str);
}
-
done:
- avahi_service_resolver_free(r);
pw_properties_free(props);
}
-
-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)
+static void on_zeroconf_removed(void *data, const void *user, const struct spa_dict *info)
{
- struct impl *impl = userdata;
- struct tunnel_info info;
+ struct impl *impl = data;
+ const char *name;
struct tunnel *t;
- if ((flags & AVAHI_LOOKUP_RESULT_LOCAL) && !impl->discover_local)
+ name = spa_dict_lookup(info, PW_KEY_ZEROCONF_NAME);
+
+ if ((t = find_tunnel(impl, name)) == NULL)
return;
- info = TUNNEL_INFO(.name = name);
-
- t = find_tunnel(impl, &info);
-
- switch (event) {
- case AVAHI_BROWSER_NEW:
- if (t != NULL) {
- pw_log_info("found duplicate mdns entry - skipping tunnel creation");
- 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)
-{
-
- impl->avahi_poll = pw_avahi_poll_new(impl->context);
-
- return start_client(impl);
+ tunnel_free(t);
}
+static const struct pw_zeroconf_events zeroconf_events = {
+ PW_VERSION_ZEROCONF_EVENTS,
+ .added = on_zeroconf_added,
+ .removed = on_zeroconf_removed,
+};
SPA_EXPORT
int pipewire__module_init(struct pw_impl_module *module, const char *args)
@@ -576,6 +442,7 @@ 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;
+ const char *local;
int res;
PW_LOG_TOPIC_INIT(mod_topic);
@@ -599,15 +466,24 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
impl->context = context;
impl->properties = props;
- impl->discover_local = pw_properties_get_bool(impl->properties,
- "raop.discover-local", false);
+ if ((local = pw_properties_get(impl->properties, "raop.discover-local")) == NULL)
+ local = "false";
+ pw_properties_set(impl->properties, PW_KEY_ZEROCONF_DISCOVER_LOCAL, local);
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);
+ if ((impl->zeroconf = pw_zeroconf_new(context, &props->dict)) == NULL) {
+ pw_log_error("can't create zeroconf: %m");
+ goto error_errno;
+ }
+ pw_zeroconf_add_listener(impl->zeroconf, &impl->zeroconf_listener,
+ &zeroconf_events, impl);
+ pw_zeroconf_set_browse(impl->zeroconf, NULL,
+ &SPA_DICT_ITEMS(
+ SPA_DICT_ITEM(PW_KEY_ZEROCONF_TYPE, SERVICE_TYPE_SINK)));
return 0;
error_errno:
diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c
index e217ff5b2..5e25b3089 100644
--- a/src/modules/module-raop-sink.c
+++ b/src/modules/module-raop-sink.c
@@ -45,6 +45,7 @@
#include "network-utils.h"
#include "module-raop/rtsp-client.h"
+#include "module-raop/base64.h"
#include "module-rtp/rtp.h"
#include "module-rtp/stream.h"
@@ -130,11 +131,6 @@
PW_LOG_TOPIC(mod_topic, "mod." NAME);
#define PW_LOG_TOPIC_DEFAULT mod_topic
-#define BUFFER_SIZE (1u<<22)
-#define BUFFER_MASK (BUFFER_SIZE-1)
-#define BUFFER_SIZE2 (BUFFER_SIZE>>1)
-#define BUFFER_MASK2 (BUFFER_SIZE2-1)
-
#define FRAMES_PER_TCP_PACKET 4096
#define FRAMES_PER_UDP_PACKET 352
@@ -273,13 +269,6 @@ struct impl {
bool mute;
float volume;
-
- struct spa_ringbuffer ring;
- uint8_t buffer[BUFFER_SIZE];
-
- struct spa_io_position *io_position;
-
- uint32_t filled;
};
static inline void bit_writer(uint8_t **p, int *pos, uint8_t data, int len)
@@ -357,7 +346,7 @@ static int send_udp_sync_packet(struct impl *impl, uint32_t rtptime, unsigned in
res = sendmsg(impl->control_fd, &msg, MSG_NOSIGNAL);
if (res < 0) {
res = -errno;
- pw_log_warn("error sending control packet: %d", res);
+ pw_log_warn("error sending control packet: %d (%m)", res);
}
pw_log_debug("raop control sync: first:%d latency:%u now:%"PRIx64" rtptime:%u",
@@ -703,49 +692,6 @@ on_control_source_io(void *data, int fd, uint32_t mask)
}
}
-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;
-}
-
SPA_PRINTF_FUNC(2,3)
static int MD5_hash(char hash[MD5_HASH_LENGTH+1], const char *fmt, ...)
{
@@ -778,7 +724,7 @@ static int rtsp_add_raop_auth_header(struct impl *impl, const char *method)
char buf[256];
char enc[512];
spa_scnprintf(buf, sizeof(buf), "%s:%s", RAOP_AUTH_USER_NAME, impl->password);
- base64_encode((uint8_t*)buf, strlen(buf), enc, '=');
+ pw_base64_encode((uint8_t*)buf, strlen(buf), enc, '=');
spa_scnprintf(auth, sizeof(auth), "Basic %s", enc);
}
else if (spa_streq(impl->auth_method, "Digest")) {
@@ -1136,8 +1082,8 @@ static int rsa_encrypt(uint8_t *data, int len, uint8_t *enc)
"imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew==";
char e[] = "AQAB";
- msize = base64_decode(n, strlen(n), modulus);
- esize = base64_decode(e, strlen(e), exponent);
+ msize = pw_base64_decode(n, strlen(n), modulus);
+ esize = pw_base64_decode(e, strlen(e), exponent);
#if OPENSSL_API_LEVEL >= 30000
EVP_PKEY *pkey = NULL;
@@ -1263,15 +1209,15 @@ static int rtsp_do_announce(struct impl *impl)
(res = pw_getrandom(impl->aes_iv, sizeof(impl->aes_iv), 0)) < 0)
return res;
- base64_encode(rac, sizeof(rac), sac, '\0');
+ pw_base64_encode(rac, sizeof(rac), sac, '\0');
pw_properties_set(impl->headers, "Apple-Challenge", sac);
rsa_len = rsa_encrypt(impl->aes_key, 16, rsakey);
if (rsa_len < 0)
return -rsa_len;
- base64_encode(rsakey, rsa_len, key, '=');
- base64_encode(impl->aes_iv, 16, iv, '=');
+ pw_base64_encode(rsakey, rsa_len, key, '=');
+ pw_base64_encode(impl->aes_iv, 16, iv, '=');
sdp = spa_aprintf("v=0\r\n"
"o=iTunes %s 0 IN IP%d %s\r\n"
diff --git a/src/modules/module-raop/base64.h b/src/modules/module-raop/base64.h
new file mode 100644
index 000000000..d8906c287
--- /dev/null
+++ b/src/modules/module-raop/base64.h
@@ -0,0 +1,62 @@
+/* PipeWire */
+/* SPDX-FileCopyrightText: Copyright © 2026 Wim Taymans */
+/* SPDX-License-Identifier: MIT */
+
+#ifndef PIPEWIRE_BASE64_H
+#define PIPEWIRE_BASE64_H
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static inline void pw_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 inline size_t pw_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;
+}
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* PIPEWIRE_BASE64_H */
diff --git a/src/modules/module-raop/rtsp-client.c b/src/modules/module-raop/rtsp-client.c
index fae71977c..4bcff8b88 100644
--- a/src/modules/module-raop/rtsp-client.c
+++ b/src/modules/module-raop/rtsp-client.c
@@ -445,7 +445,10 @@ on_source_io(void *data, int fd, uint32_t mask)
int res;
if (mask & (SPA_IO_ERR | SPA_IO_HUP)) {
- res = -EPIPE;
+ socklen_t len = sizeof(res);
+ if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &res, &len) < 0)
+ res = errno;
+ res = -res;
goto error;
}
if (mask & SPA_IO_IN) {
@@ -478,7 +481,7 @@ int pw_rtsp_client_connect(struct pw_rtsp_client *client,
{
struct addrinfo hints;
struct addrinfo *result, *rp;
- int res, fd;
+ int res, fd = -1;
char port_str[12];
if (client->source != NULL)
diff --git a/src/modules/module-roc-sink.c b/src/modules/module-roc-sink.c
index 39ca2bce1..66a2c716b 100644
--- a/src/modules/module-roc-sink.c
+++ b/src/modules/module-roc-sink.c
@@ -120,8 +120,6 @@ struct module_roc_sink_data {
roc_endpoint *remote_control_addr;
int remote_control_port;
-
- roc_log_level loglevel;
};
static void stream_destroy(void *d)
@@ -391,8 +389,7 @@ static const struct spa_dict_item module_roc_sink_info[] = {
"( remote.repair.port= ) "
"( remote.control.port= ) "
"( audio.position= ) "
- "( sink.props= { key=val ... } ) "
- "( log.level=|DEFAULT|NONE|RROR|INFO|DEBUG|TRACE ) " },
+ "( sink.props= { key=val ... } ) " },
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
};
@@ -513,14 +510,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
pw_log_error("can't connect: %m");
goto out;
}
- if ((str = pw_properties_get(props, "log.level")) != NULL) {
- const struct spa_log *log_conf = pw_log_get();
- const roc_log_level default_level = pw_roc_log_level_pw_2_roc(log_conf->level);
- if (pw_roc_parse_log_level(&data->loglevel, str, default_level)) {
- pw_log_error("Invalid log level %s, using default", str);
- data->loglevel = default_level;
- }
- }
pw_proxy_add_listener((struct pw_proxy*)data->core,
&data->core_proxy_listener,
diff --git a/src/modules/module-roc-source.c b/src/modules/module-roc-source.c
index 2173c6af1..a46189e5d 100644
--- a/src/modules/module-roc-source.c
+++ b/src/modules/module-roc-source.c
@@ -140,8 +140,6 @@ struct module_roc_source_data {
roc_endpoint *local_control_addr;
int local_control_port;
-
- roc_log_level loglevel;
};
static void stream_destroy(void *d)
@@ -430,8 +428,7 @@ static const struct spa_dict_item module_roc_source_info[] = {
"( local.repair.port= ) "
"( local.control.port= ) "
"( audio.position= ) "
- "( source.props= { key=value ... } ) "
- "( log.level=|DEFAULT|NONE|RROR|INFO|DEBUG|TRACE ) " },
+ "( source.props= { key=value ... } ) " },
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
};
@@ -567,14 +564,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
} else {
data->fec_code = ROC_FEC_ENCODING_DEFAULT;
}
- if ((str = pw_properties_get(props, "log.level")) != NULL) {
- const struct spa_log *log_conf = pw_log_get();
- const roc_log_level default_level = pw_roc_log_level_pw_2_roc(log_conf->level);
- if (pw_roc_parse_log_level(&data->loglevel, str, default_level)) {
- pw_log_error("Invalid log level %s, using default", str);
- data->loglevel = default_level;
- }
- }
data->core = pw_context_get_object(data->module_context, PW_TYPE_INTERFACE_Core);
if (data->core == NULL) {
diff --git a/src/modules/module-roc/common.c b/src/modules/module-roc/common.c
index 244c203dd..475c5f40f 100644
--- a/src/modules/module-roc/common.c
+++ b/src/modules/module-roc/common.c
@@ -5,17 +5,54 @@
PW_LOG_TOPIC(roc_log_topic, "mod.roc.lib");
+static inline roc_log_level pw_roc_log_level_pw_2_roc(const enum spa_log_level pw_log_level)
+{
+ switch (pw_log_level) {
+ case SPA_LOG_LEVEL_NONE:
+ return ROC_LOG_NONE;
+ case SPA_LOG_LEVEL_ERROR:
+ return ROC_LOG_ERROR;
+ case SPA_LOG_LEVEL_WARN:
+ return ROC_LOG_ERROR;
+ case SPA_LOG_LEVEL_INFO:
+ return ROC_LOG_INFO;
+ case SPA_LOG_LEVEL_DEBUG:
+ return ROC_LOG_DEBUG;
+ case SPA_LOG_LEVEL_TRACE:
+ return ROC_LOG_TRACE;
+ default:
+ return ROC_LOG_NONE;
+ }
+}
+
+static inline enum spa_log_level pw_roc_log_level_roc_2_pw(const roc_log_level roc_log_level)
+{
+ switch (roc_log_level) {
+ case ROC_LOG_NONE:
+ return SPA_LOG_LEVEL_NONE;
+ case ROC_LOG_ERROR:
+ return SPA_LOG_LEVEL_ERROR;
+ case ROC_LOG_INFO:
+ return SPA_LOG_LEVEL_INFO;
+ case ROC_LOG_DEBUG:
+ return SPA_LOG_LEVEL_DEBUG;
+ case ROC_LOG_TRACE:
+ return SPA_LOG_LEVEL_TRACE;
+ default:
+ return SPA_LOG_LEVEL_NONE;
+ }
+}
+
+static void pw_roc_log_handler(const roc_log_message *message, void *argument)
+{
+ const enum spa_log_level log_level = pw_roc_log_level_roc_2_pw(message->level);
+ if (SPA_UNLIKELY(pw_log_topic_enabled(log_level, roc_log_topic))) {
+ pw_log_logt(log_level, roc_log_topic, message->file, message->line, message->module, "%s", message->text);
+ }
+}
+
void pw_roc_log_init(void)
{
roc_log_set_handler(pw_roc_log_handler, NULL);
roc_log_set_level(pw_roc_log_level_pw_2_roc(roc_log_topic->has_custom_level ? roc_log_topic->level : pw_log_level));
}
-
-void pw_roc_log_handler(const roc_log_message *message, void *argument)
-{
- const enum spa_log_level log_level = pw_roc_log_level_roc_2_pw(message->level);
- if (SPA_UNLIKELY(pw_log_topic_enabled(log_level, roc_log_topic))) {
- pw_log_logt(log_level, roc_log_topic, message->file, message->line, message->module, message->text, "");
- }
-}
-
diff --git a/src/modules/module-roc/common.h b/src/modules/module-roc/common.h
index c94ac69a8..d49e392fe 100644
--- a/src/modules/module-roc/common.h
+++ b/src/modules/module-roc/common.h
@@ -3,7 +3,6 @@
#include
#include
-#include
#include
#include
@@ -21,7 +20,6 @@
#define PW_ROC_STEREO_POSITIONS "[ FL FR ]"
void pw_roc_log_init(void);
-void pw_roc_log_handler(const roc_log_message *message, void *argument);
static inline int pw_roc_parse_fec_encoding(roc_fec_encoding *out, const char *str)
{
@@ -137,62 +135,4 @@ static inline void pw_roc_fec_encoding_to_proto(roc_fec_encoding fec_code, roc_p
}
}
-static inline roc_log_level pw_roc_log_level_pw_2_roc(const enum spa_log_level pw_log_level)
-{
- switch (pw_log_level) {
- case SPA_LOG_LEVEL_NONE:
- return ROC_LOG_NONE;
- case SPA_LOG_LEVEL_ERROR:
- return ROC_LOG_ERROR;
- case SPA_LOG_LEVEL_WARN:
- return ROC_LOG_ERROR;
- case SPA_LOG_LEVEL_INFO:
- return ROC_LOG_INFO;
- case SPA_LOG_LEVEL_DEBUG:
- return ROC_LOG_DEBUG;
- case SPA_LOG_LEVEL_TRACE:
- return ROC_LOG_TRACE;
- default:
- return ROC_LOG_NONE;
- }
-}
-
-static inline enum spa_log_level pw_roc_log_level_roc_2_pw(const roc_log_level roc_log_level)
-{
- switch (roc_log_level) {
- case ROC_LOG_NONE:
- return SPA_LOG_LEVEL_NONE;
- case ROC_LOG_ERROR:
- return SPA_LOG_LEVEL_ERROR;
- case ROC_LOG_INFO:
- return SPA_LOG_LEVEL_INFO;
- case ROC_LOG_DEBUG:
- return SPA_LOG_LEVEL_DEBUG;
- case ROC_LOG_TRACE:
- return SPA_LOG_LEVEL_TRACE;
- default:
- return SPA_LOG_LEVEL_NONE;
- }
-}
-
-static inline int pw_roc_parse_log_level(roc_log_level *loglevel, const char *str,
- roc_log_level default_level)
-{
- if (spa_streq(str, "DEFAULT"))
- *loglevel = default_level;
- else if (spa_streq(str, "NONE"))
- *loglevel = ROC_LOG_NONE;
- else if (spa_streq(str, "ERROR"))
- *loglevel = ROC_LOG_ERROR;
- else if (spa_streq(str, "INFO"))
- *loglevel = ROC_LOG_INFO;
- else if (spa_streq(str, "DEBUG"))
- *loglevel = ROC_LOG_DEBUG;
- else if (spa_streq(str, "TRACE"))
- *loglevel = ROC_LOG_TRACE;
- else
- return -EINVAL;
- return 0;
-}
-
#endif /* MODULE_ROC_COMMON_H */
diff --git a/src/modules/module-rtp-sap.c b/src/modules/module-rtp-sap.c
index c734758c3..cfbcbf419 100644
--- a/src/modules/module-rtp-sap.c
+++ b/src/modules/module-rtp-sap.c
@@ -1235,8 +1235,11 @@ static struct session *session_new_announce(struct impl *impl, struct node *node
sess->has_sdp = true;
}
+ pw_log_debug("sending out initial SAP");
send_sap(impl, sess, 0);
+ pw_log_debug("new announcement session up and running");
+
return sess;
error_free:
@@ -1835,6 +1838,7 @@ static int start_sap(struct impl *impl)
pw_timer_queue_add(impl->timer_queue, &impl->start_sap_retry_timer,
NULL, 1 * SPA_NSEC_PER_SEC,
on_start_sap_retry_timer_event, impl);
+ pw_log_info("starting SAP retry timer");
/* It is important to return 0 in this case. Otherwise, the nonzero return
* value will later be propagated through the core as an error. */
@@ -2005,8 +2009,10 @@ static void impl_destroy(struct impl *impl)
pw_timer_queue_cancel(&impl->sap_send_timer);
pw_timer_queue_cancel(&impl->start_sap_retry_timer);
pw_timer_queue_cancel(&impl->igmp_recovery.timer);
- if (impl->sap_source)
+ if (impl->sap_source) {
+ pw_log_info("destroying SAP source");
pw_loop_destroy_source(impl->loop, impl->sap_source);
+ }
if (impl->sap_fd != -1)
close(impl->sap_fd);
diff --git a/src/modules/module-rtp-session.c b/src/modules/module-rtp-session.c
index 63470c539..2dca681d6 100644
--- a/src/modules/module-rtp-session.c
+++ b/src/modules/module-rtp-session.c
@@ -27,12 +27,7 @@
#include
#include
-#include
-#include
-#include
-#include
-
-#include "module-zeroconf-discover/avahi-poll.h"
+#include "zeroconf-utils/zeroconf.h"
#include
#include
@@ -160,31 +155,21 @@ static const struct spa_dict_item module_info[] = {
};
struct service_info {
- AvahiIfIndex interface;
- AvahiProtocol protocol;
+ int ifindex;
+ int protocol;
const char *name;
const char *type;
const char *domain;
- const char *host_name;
- AvahiAddress address;
- uint16_t port;
};
#define SERVICE_INFO(...) ((struct service_info){ __VA_ARGS__ })
-struct service {
- struct service_info info;
-
- struct spa_list link;
- struct impl *impl;
-
- struct session *sess;
-};
-
struct session {
struct impl *impl;
struct spa_list link;
+ struct service_info info;
+
struct sockaddr_storage ctrl_addr;
socklen_t ctrl_len;
struct sockaddr_storage data_addr;
@@ -229,11 +214,8 @@ struct impl {
struct pw_properties *props;
bool discover_local;
- AvahiPoll *avahi_poll;
- AvahiClient *client;
- AvahiServiceBrowser *browser;
- AvahiEntryGroup *group;
- struct spa_list service_list;
+ struct pw_zeroconf *zeroconf;
+ struct spa_hook zeroconf_listener;
struct pw_properties *stream_props;
@@ -595,6 +577,9 @@ static void free_session(struct session *sess)
if (sess->recv)
rtp_stream_destroy(sess->recv);
free(sess->name);
+ free((char *) sess->info.name);
+ free((char *) sess->info.type);
+ free((char *) sess->info.domain);
free(sess);
}
@@ -612,7 +597,8 @@ static bool cmp_ip(const struct sockaddr_storage *sa, const struct sockaddr_stor
return false;
}
-static struct session *make_session(struct impl *impl, struct pw_properties *props)
+static struct session *make_session(struct impl *impl, struct service_info *info,
+ struct pw_properties *props)
{
struct session *sess;
const char *str;
@@ -627,6 +613,11 @@ static struct session *make_session(struct impl *impl, struct pw_properties *pro
sess->impl = impl;
sess->ssrc = pw_rand32();
+ sess->info.ifindex = info->ifindex;
+ sess->info.protocol = info->protocol;
+ sess->info.name = strdup(info->name);
+ sess->info.type = strdup(info->type);
+ sess->info.domain = strdup(info->domain);
str = pw_properties_get(props, "sess.name");
sess->name = str ? strdup(str) : strdup("RTP Session");
@@ -669,6 +660,21 @@ error:
return NULL;
}
+static struct session *find_session_by_info(struct impl *impl,
+ const struct service_info *info)
+{
+ struct session *s;
+ spa_list_for_each(s, &impl->sessions, link) {
+ if (s->info.ifindex == info->ifindex &&
+ s->info.protocol == info->protocol &&
+ spa_streq(s->info.name, info->name) &&
+ spa_streq(s->info.type, info->type) &&
+ spa_streq(s->info.domain, info->domain))
+ return s;
+ }
+ return NULL;
+}
+
static struct session *find_session_by_addr_name(struct impl *impl,
const struct sockaddr_storage *sa, const char *name)
{
@@ -1220,8 +1226,8 @@ static void impl_destroy(struct impl *impl)
if (impl->data_source)
pw_loop_destroy_source(impl->data_loop, impl->data_source);
- if (impl->client)
- avahi_client_free(impl->client);
+ if (impl->zeroconf)
+ pw_zeroconf_destroy(impl->zeroconf);
if (impl->data_loop)
pw_context_release_loop(impl->context, impl->data_loop);
@@ -1263,21 +1269,7 @@ static const struct pw_core_events core_events = {
.error = on_core_error,
};
-static void free_service(struct service *s)
-{
- spa_list_remove(&s->link);
-
- if (s->sess)
- free_session(s->sess);
-
- free((char *) s->info.name);
- free((char *) s->info.type);
- free((char *) s->info.domain);
- free((char *) s->info.host_name);
- free(s);
-}
-
-static const char *get_service_name(struct impl *impl)
+static const char *get_service_type(struct impl *impl)
{
const char *str;
str = pw_properties_get(impl->props, "sess.media");
@@ -1288,21 +1280,36 @@ static const char *get_service_name(struct impl *impl)
return NULL;
}
-static struct service *make_service(struct impl *impl, const struct service_info *info,
- AvahiStringList *txt)
+static void on_zeroconf_added(void *data, const void *user, const struct spa_dict *info)
{
- struct service *s = NULL;
- char at[AVAHI_ADDRESS_STR_MAX], if_suffix[16] = "";
+ struct impl *impl = data;
+ const char *str, *service_type, *address, *hostname;
+ struct service_info sinfo;
struct session *sess;
- int res, ipv;
+ int ifindex = -1, protocol = 0, res, port = 0;
struct pw_properties *props = NULL;
- const char *service_name, *str;
- AvahiStringList *l;
bool compatible = true;
+ if ((str = spa_dict_lookup(info, PW_KEY_ZEROCONF_IFINDEX)))
+ ifindex = atoi(str);
+ if ((str = spa_dict_lookup(info, PW_KEY_ZEROCONF_PROTO)))
+ protocol = atoi(str);
+ if ((str = spa_dict_lookup(info, PW_KEY_ZEROCONF_PORT)))
+ port = atoi(str);
+
+ sinfo = SERVICE_INFO(.ifindex = ifindex,
+ .protocol = protocol,
+ .name = spa_dict_lookup(info, PW_KEY_ZEROCONF_NAME),
+ .type = spa_dict_lookup(info, PW_KEY_ZEROCONF_TYPE),
+ .domain = spa_dict_lookup(info, PW_KEY_ZEROCONF_DOMAIN));
+
+ sess = find_session_by_info(impl, &sinfo);
+ if (sess != NULL)
+ return;
+
/* check for compatible session */
- service_name = get_service_name(impl);
- compatible = spa_streq(service_name, info->type);
+ service_type = get_service_type(impl);
+ compatible = spa_streq(service_type, sinfo.type);
props = pw_properties_copy(impl->stream_props);
if (props == NULL) {
@@ -1310,55 +1317,53 @@ static struct service *make_service(struct impl *impl, const struct service_info
goto error;
}
- if (spa_streq(service_name, "_pipewire-audio._udp")) {
+ if (spa_streq(service_type, "_pipewire-audio._udp")) {
uint32_t mask = 0;
- for (l = txt; l && compatible; l = l->next) {
+ const struct spa_dict_item *it;
+ spa_dict_for_each(it, info) {
const char *k = NULL;
- char *key, *value;
- if (avahi_string_list_get_pair(l, &key, &value, NULL) != 0)
+ if (!compatible)
break;
- if (spa_streq(key, "subtype")) {
+ if (spa_streq(it->key, "subtype")) {
k = "sess.media";
mask |= 1<<0;
- } else if (spa_streq(key, "format")) {
+ } else if (spa_streq(it->key, "format")) {
k = PW_KEY_AUDIO_FORMAT;
mask |= 1<<1;
- } else if (spa_streq(key, "rate")) {
+ } else if (spa_streq(it->key, "rate")) {
k = PW_KEY_AUDIO_RATE;
mask |= 1<<2;
- } else if (spa_streq(key, "channels")) {
+ } else if (spa_streq(it->key, "channels")) {
k = PW_KEY_AUDIO_CHANNELS;
mask |= 1<<3;
- } else if (spa_streq(key, "position")) {
+ } else if (spa_streq(it->key, "position")) {
pw_properties_set(props,
- SPA_KEY_AUDIO_POSITION, value);
- } else if (spa_streq(key, "layout")) {
+ SPA_KEY_AUDIO_POSITION, it->value);
+ } else if (spa_streq(it->key, "layout")) {
pw_properties_set(props,
- SPA_KEY_AUDIO_LAYOUT, value);
- } else if (spa_streq(key, "channelnames")) {
+ SPA_KEY_AUDIO_LAYOUT, it->value);
+ } else if (spa_streq(it->key, "channelnames")) {
pw_properties_set(props,
- PW_KEY_NODE_CHANNELNAMES, value);
- } else if (spa_streq(key, "ts-refclk")) {
+ PW_KEY_NODE_CHANNELNAMES, it->value);
+ } else if (spa_streq(it->key, "ts-refclk")) {
pw_properties_set(props,
- "sess.ts-refclk", value);
- if (spa_streq(value, impl->ts_refclk))
+ "sess.ts-refclk", it->value);
+ if (spa_streq(it->value, impl->ts_refclk))
pw_properties_set(props,
"sess.ts-direct", "true");
- } else if (spa_streq(key, "ts-offset")) {
+ } else if (spa_streq(it->key, "ts-offset")) {
uint32_t v;
- if (spa_atou32(value, &v, 0))
+ if (spa_atou32(it->value, &v, 0))
pw_properties_setf(props,
"rtp.receiver-ts-offset", "%u", v);
}
if (k != NULL) {
str = pw_properties_get(props, k);
- if (str == NULL || !spa_streq(str, value))
+ if (str == NULL || !spa_streq(str, it->value))
compatible = false;
}
- avahi_free(key);
- avahi_free(value);
}
str = pw_properties_get(props, "sess.media");
if (spa_streq(str, "opus") && mask != 0xd)
@@ -1368,281 +1373,147 @@ static struct service *make_service(struct impl *impl, const struct service_info
}
if (!compatible) {
pw_log_info("found incompatible session IP%d:%s",
- info->protocol == AVAHI_PROTO_INET ? 4 : 6,
- info->name);
+ sinfo.protocol, sinfo.name);
res = 0;
goto error;
}
- s = calloc(1, sizeof(*s));
- if (s == NULL) {
- res = -errno;
- goto error;
- }
+ address = spa_dict_lookup(info, PW_KEY_ZEROCONF_ADDRESS);
+ hostname = spa_dict_lookup(info, PW_KEY_ZEROCONF_HOSTNAME);
- s->impl = impl;
- spa_list_append(&impl->service_list, &s->link);
+ pw_log_info("create session: %s %s:%u %s", sinfo.name, address, port, sinfo.type);
- s->info.interface = info->interface;
- s->info.protocol = info->protocol;
- s->info.name = strdup(info->name);
- s->info.type = strdup(info->type);
- s->info.domain = strdup(info->domain);
- s->info.host_name = strdup(info->host_name);
- s->info.address = info->address;
- s->info.port = info->port;
-
- avahi_address_snprint(at, sizeof(at), &s->info.address);
- pw_log_info("create session: %s %s:%u %s", s->info.name, at, s->info.port, s->info.type);
-
- if (s->info.protocol == AVAHI_PROTO_INET6 &&
- s->info.address.data.ipv6.address[0] == 0xfe &&
- (s->info.address.data.ipv6.address[1] & 0xc0) == 0x80)
- snprintf(if_suffix, sizeof(if_suffix), "%%%d", s->info.interface);
-
- ipv = s->info.protocol == AVAHI_PROTO_INET ? 4 : 6;
- pw_properties_set(props, "sess.name", s->info.name);
- pw_properties_setf(props, "destination.ip", "%s%s", at, if_suffix);
- pw_properties_setf(props, "destination.ifindex", "%u", s->info.interface);
- pw_properties_setf(props, "destination.port", "%u", s->info.port);
+ pw_properties_set(props, "sess.name", sinfo.name);
+ pw_properties_set(props, "destination.ip", address);
+ pw_properties_setf(props, "destination.ifindex", "%u", sinfo.ifindex);
+ pw_properties_setf(props, "destination.port", "%u", port);
if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL)
pw_properties_setf(props, PW_KEY_NODE_NAME, "rtp_session.%s.%s.ipv%d",
- s->info.name, s->info.host_name, ipv);
+ sinfo.name, hostname, sinfo.protocol);
if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL)
pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "%s (IPv%d)",
- s->info.name, ipv);
+ sinfo.name, sinfo.protocol);
if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL)
pw_properties_setf(props, PW_KEY_MEDIA_NAME, "RTP Session with %s (IPv%d)",
- s->info.name, ipv);
+ sinfo.name, sinfo.protocol);
- sess = make_session(impl, props);
- props = NULL;
+ sess = make_session(impl, &sinfo, spa_steal_ptr(props));
if (sess == NULL) {
res = -errno;
pw_log_error("can't create session: %m");
goto error;
}
- s->sess = sess;
- if ((res = pw_net_parse_address(at, s->info.port, &sess->ctrl_addr, &sess->ctrl_len)) < 0) {
- pw_log_error("invalid address %s: %s", at, spa_strerror(res));
+ if ((res = pw_net_parse_address(address, port, &sess->ctrl_addr, &sess->ctrl_len)) < 0) {
+ pw_log_error("invalid address %s: %s", address, spa_strerror(res));
}
- if ((res = pw_net_parse_address(at, s->info.port+1, &sess->data_addr, &sess->data_len)) < 0) {
- pw_log_error("invalid address %s: %s", at, spa_strerror(res));
+ if ((res = pw_net_parse_address(address, port+1, &sess->data_addr, &sess->data_len)) < 0) {
+ pw_log_error("invalid address %s: %s", address, spa_strerror(res));
}
- return s;
+ return;
error:
pw_properties_free(props);
- if (s != NULL)
- free_service(s);
- errno = -res;
- return NULL;
+ return;
}
-static struct service *find_service(struct impl *impl, const struct service_info *info)
+static void on_zeroconf_removed(void *data, const void *user, const struct spa_dict *info)
{
- struct service *s;
- spa_list_for_each(s, &impl->service_list, link) {
- if (s->info.interface == info->interface &&
- s->info.protocol == info->protocol &&
- spa_streq(s->info.name, info->name) &&
- spa_streq(s->info.type, info->type) &&
- spa_streq(s->info.domain, info->domain))
- return s;
- }
- return NULL;
-}
-
-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 impl *impl = data;
struct service_info sinfo;
+ struct session *sess;
+ const char *str;
+ int ifindex = -1, protocol = 0;
- if (event != AVAHI_RESOLVER_FOUND) {
- pw_log_error("Resolving of '%s' failed: %s", name,
- avahi_strerror(avahi_client_errno(impl->client)));
- goto done;
- }
+ if ((str = spa_dict_lookup(info, PW_KEY_ZEROCONF_IFINDEX)))
+ ifindex = atoi(str);
+ if ((str = spa_dict_lookup(info, PW_KEY_ZEROCONF_PROTO)))
+ protocol = atoi(str);
- sinfo = SERVICE_INFO(.interface = interface,
+ sinfo = SERVICE_INFO(.ifindex = ifindex,
.protocol = protocol,
- .name = name,
- .type = type,
- .domain = domain,
- .host_name = host_name,
- .address = *a,
- .port = port);
+ .name = spa_dict_lookup(info, PW_KEY_ZEROCONF_NAME),
+ .type = spa_dict_lookup(info, PW_KEY_ZEROCONF_TYPE),
+ .domain = spa_dict_lookup(info, PW_KEY_ZEROCONF_DOMAIN));
- make_service(impl, &sinfo, txt);
-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 service_info info;
- struct service *s;
-
- if ((flags & AVAHI_LOOKUP_RESULT_LOCAL) && !impl->discover_local)
+ sess = find_session_by_info(impl, &sinfo);
+ if (sess == NULL)
return;
- info = SERVICE_INFO(.interface = interface,
- .protocol = protocol,
- .name = name,
- .type = type,
- .domain = domain);
-
- s = find_service(impl, &info);
-
- switch (event) {
- case AVAHI_BROWSER_NEW:
- if (s != 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 (s == NULL)
- return;
- free_service(s);
- break;
- default:
- break;
- }
+ free_session(sess);
}
static int make_browser(struct impl *impl)
{
- const char *service_name;
+ const char *service_type;
+ int res;
- service_name = get_service_name(impl);
- if (service_name == NULL)
+ service_type = get_service_type(impl);
+ if (service_type == NULL)
return -EINVAL;
- if (impl->browser == NULL) {
- impl->browser = avahi_service_browser_new(impl->client,
- AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
- service_name, NULL, 0,
- browser_cb, impl);
- }
- if (impl->browser == NULL) {
- pw_log_error("can't make browser: %s",
- avahi_strerror(avahi_client_errno(impl->client)));
- return -EIO;
+ if ((res = pw_zeroconf_set_browse(impl->zeroconf, impl,
+ &SPA_DICT_ITEMS(
+ SPA_DICT_ITEM(PW_KEY_ZEROCONF_TYPE, service_type)))) < 0) {
+ pw_log_error("can't make browser for %s: %s",
+ service_type, spa_strerror(res));
+ return res;
}
return 0;
}
-static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata)
-{
- switch (state) {
- case AVAHI_ENTRY_GROUP_ESTABLISHED:
- pw_log_info("Service successfully established");
- break;
- case AVAHI_ENTRY_GROUP_COLLISION:
- pw_log_error("Service name collision");
- break;
- case AVAHI_ENTRY_GROUP_FAILURE:
- pw_log_error("Entry group failure: %s",
- avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))));
- break;
- case AVAHI_ENTRY_GROUP_UNCOMMITED:
- case AVAHI_ENTRY_GROUP_REGISTERING:;
- break;
- }
-}
-
static int make_announce(struct impl *impl)
{
int res;
- const char *service_name, *str;
- AvahiStringList *txt = NULL;
+ const char *service_type, *str;
+ struct pw_properties *props;
- if ((service_name = get_service_name(impl)) == NULL)
+ props = pw_properties_new(NULL, NULL);
+
+ if ((service_type = get_service_type(impl)) == NULL)
return -ENOTSUP;
- if (impl->group == NULL) {
- impl->group = avahi_entry_group_new(impl->client,
- entry_group_callback, impl);
- }
- if (impl->group == NULL) {
- pw_log_error("can't make group: %s",
- avahi_strerror(avahi_client_errno(impl->client)));
- return -EIO;
- }
- avahi_entry_group_reset(impl->group);
-
- if (spa_streq(service_name, "_pipewire-audio._udp")) {
+ if (spa_streq(service_type, "_pipewire-audio._udp")) {
str = pw_properties_get(impl->props, "sess.media");
- txt = avahi_string_list_add_pair(txt, "subtype", str);
+ pw_properties_set(props, "subtype", str);
if ((str = pw_properties_get(impl->stream_props, PW_KEY_AUDIO_FORMAT)) != NULL)
- txt = avahi_string_list_add_pair(txt, "format", str);
+ pw_properties_set(props, "format", str);
if ((str = pw_properties_get(impl->stream_props, PW_KEY_AUDIO_RATE)) != NULL)
- txt = avahi_string_list_add_pair(txt, "rate", str);
+ pw_properties_set(props, "rate", str);
if ((str = pw_properties_get(impl->stream_props, PW_KEY_AUDIO_CHANNELS)) != NULL)
- txt = avahi_string_list_add_pair(txt, "channels", str);
+ pw_properties_set(props, "channels", str);
if ((str = pw_properties_get(impl->stream_props, SPA_KEY_AUDIO_POSITION)) != NULL)
- txt = avahi_string_list_add_pair(txt, "position", str);
+ pw_properties_set(props, "position", str);
if ((str = pw_properties_get(impl->stream_props, SPA_KEY_AUDIO_LAYOUT)) != NULL)
- txt = avahi_string_list_add_pair(txt, "layout", str);
+ pw_properties_set(props, "layout", str);
if ((str = pw_properties_get(impl->stream_props, PW_KEY_NODE_CHANNELNAMES)) != NULL)
- txt = avahi_string_list_add_pair(txt, "channelnames", str);
+ pw_properties_set(props, "channelnames", str);
if (impl->ts_refclk != NULL) {
- txt = avahi_string_list_add_pair(txt, "ts-refclk", impl->ts_refclk);
- txt = avahi_string_list_add_printf(txt, "ts-offset=%u", impl->ts_offset);
+ pw_properties_set(props, "ts-refclk", impl->ts_refclk);
+ pw_properties_setf(props, "ts-offset", "%u", impl->ts_offset);
}
}
- res = avahi_entry_group_add_service_strlst(impl->group,
- AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
- (AvahiPublishFlags)0, impl->session_name,
- service_name, NULL, NULL,
- impl->ctrl_port, txt);
- avahi_string_list_free(txt);
+ pw_properties_set(props, PW_KEY_ZEROCONF_NAME, impl->session_name);
+ pw_properties_set(props, PW_KEY_ZEROCONF_TYPE, service_type);
+ pw_properties_setf(props, PW_KEY_ZEROCONF_PORT, "%u", impl->ctrl_port);
+
+ res = pw_zeroconf_set_announce(impl->zeroconf, impl, &props->dict);
+
+ pw_properties_free(props);
if (res < 0) {
- pw_log_error("can't add service: %s",
- avahi_strerror(avahi_client_errno(impl->client)));
- return -EIO;
- }
- if ((res = avahi_entry_group_commit(impl->group)) < 0) {
- pw_log_error("can't commit group: %s",
- avahi_strerror(avahi_client_errno(impl->client)));
- return -EIO;
+ pw_log_error("can't add service: %s", spa_strerror(res));
+ return res;
}
return 0;
}
-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:
- make_browser(impl);
- make_announce(impl);
- break;
- case AVAHI_CLIENT_FAILURE:
- case AVAHI_CLIENT_CONNECTING:
- break;
- default:
- break;
- }
-}
+static const struct pw_zeroconf_events zeroconf_events = {
+ PW_VERSION_ZEROCONF_EVENTS,
+ .added = on_zeroconf_added,
+ .removed = on_zeroconf_removed,
+};
static void copy_props(struct impl *impl, struct pw_properties *props, const char *key)
{
@@ -1673,7 +1544,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
args = "";
spa_list_init(&impl->sessions);
- spa_list_init(&impl->service_list);
props = pw_properties_new_string(args);
if (props == NULL) {
@@ -1685,6 +1555,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
impl->discover_local = pw_properties_get_bool(impl->props,
"sess.discover-local", false);
+ pw_properties_set(impl->props, PW_KEY_ZEROCONF_DISCOVER_LOCAL,
+ impl->discover_local ? "true" : "false");
stream_props = pw_properties_new(NULL, NULL);
if (stream_props == NULL) {
@@ -1804,14 +1676,17 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
if ((res = setup_apple_session(impl)) < 0)
goto out;
- impl->avahi_poll = pw_avahi_poll_new(impl->context);
- if ((impl->client = avahi_client_new(impl->avahi_poll,
- AVAHI_CLIENT_NO_FAIL,
- client_callback, impl,
- &res)) == NULL) {
- pw_log_error("can't create avahi client: %s", avahi_strerror(res));
+ impl->zeroconf = pw_zeroconf_new(impl->context, &impl->props->dict);
+ if (impl->zeroconf == NULL) {
+ res = -errno;
+ pw_log_error("can't create zeroconf: %m");
goto out;
}
+ pw_zeroconf_add_listener(impl->zeroconf, &impl->zeroconf_listener,
+ &zeroconf_events, impl);
+
+ make_browser(impl);
+ make_announce(impl);
pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl);
diff --git a/src/modules/module-rtp-source.c b/src/modules/module-rtp-source.c
index 8f911c62a..b0b52ed50 100644
--- a/src/modules/module-rtp-source.c
+++ b/src/modules/module-rtp-source.c
@@ -231,7 +231,7 @@ struct impl {
/* Monotonic timestamp of the last time a packet was
* received. This is accessed with atomic accessors
* to avoid race conditions. */
- uint64_t last_packet_time;
+ SPA_ALIGNED(8) uint64_t last_packet_time;
struct pw_timer standby_timer;
/* This timer is used when the first stream_start() call fails because
@@ -246,6 +246,9 @@ struct impl {
socklen_t src_len;
struct spa_source *source;
+ bool is_multicast;
+ bool filter_by_address;
+
uint8_t *buffer;
size_t buffer_size;
@@ -300,14 +303,41 @@ on_rtp_io(void *data, int fd, uint32_t mask)
ssize_t len;
int suppressed;
uint64_t current_time;
+ struct sockaddr_storage recvaddr;
+ socklen_t recvaddr_len = sizeof(recvaddr);
current_time = get_time_ns(impl);
if (mask & SPA_IO_IN) {
-
- if ((len = recv(fd, impl->buffer, impl->buffer_size, 0)) < 0)
+ if ((len = recvfrom(fd, impl->buffer, impl->buffer_size, 0, (struct sockaddr *)(&recvaddr), &recvaddr_len)) < 0)
goto receive_error;
+ /* Filter the packets to exclude those with source addresses
+ * that do not match the expected one. Only used with unicast.
+ * (The bind() call in make_socket takes care of only
+ * receiving packets that target the specified port.) */
+ if (impl->filter_by_address && !pw_net_are_addresses_equal(&recvaddr, &(impl->src_addr), false)) {
+ /* In the IPv6 case, pw_net_get_ip() produces output formatted
+ * as "%". Both constants
+ * INET6_ADDRSTRLEN and IFNAMSIZ include the null terminator
+ * in their respective length values. This works out well for
+ * the formatted output, since this ensures there is one extra
+ * character for the % delimiter and another extra character
+ * for the null terminator of the entire string.
+ *
+ * (In the IPv4 case, pw_net_get_ip() just outputs the address.) */
+ char address_str[INET6_ADDRSTRLEN + IFNAMSIZ];
+ int res;
+
+ res = pw_net_get_ip(&recvaddr, address_str, sizeof(address_str), NULL, NULL);
+ if (SPA_LIKELY(res == 0))
+ pw_log_trace("Filtering out packet with mismatching address %s", address_str);
+ else
+ pw_log_warn("Filtering out packet with unrecognized address");
+
+ return;
+ }
+
if (len < 12)
goto short_packet;
@@ -474,12 +504,12 @@ finish:
}
static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname,
- struct igmp_recovery *igmp_recovery)
+ struct igmp_recovery *igmp_recovery, bool *is_multicast,
+ bool *filter_by_address)
{
int af, fd, val, res;
struct ifreq req;
struct sockaddr_storage ba = *(struct sockaddr_storage *)sa;
- bool do_connect = false;
char addr[128];
af = sa->sa_family;
@@ -522,12 +552,16 @@ static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname,
pw_net_get_ip((struct sockaddr_storage*)sa, addr, sizeof(addr), NULL, NULL);
pw_log_info("join IPv4 group: %s", addr);
res = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mr4, sizeof(mr4));
+ *filter_by_address = false;
+ *is_multicast = true;
} else {
struct sockaddr_in *ba4 = (struct sockaddr_in*)&ba;
- if (ba4->sin_addr.s_addr != INADDR_ANY) {
- ba4->sin_addr.s_addr = INADDR_ANY;
- do_connect = true;
- }
+ *filter_by_address = (ba4->sin_addr.s_addr != INADDR_ANY);
+ *is_multicast = false;
+ /* Make sure the ANY address is always used. This is important
+ * for the bind() call below - with unicast, it shall only filter
+ * by port number (address filtering is done by recvfrom()). */
+ ba4->sin_addr.s_addr = INADDR_ANY;
}
} else if (af == AF_INET6) {
struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)sa;
@@ -539,8 +573,15 @@ static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname,
pw_net_get_ip((struct sockaddr_storage*)sa, addr, sizeof(addr), NULL, NULL);
pw_log_info("join IPv6 group: %s", addr);
res = setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mr6, sizeof(mr6));
+ *filter_by_address = false;
+ *is_multicast = true;
} else {
struct sockaddr_in6 *ba6 = (struct sockaddr_in6*)&ba;
+ *filter_by_address = !IN6_IS_ADDR_UNSPECIFIED(&(ba6->sin6_addr.s6_addr));
+ *is_multicast = false;
+ /* Make sure the ANY address is always used. This is important
+ * for the bind() call below - with unicast, it shall only filter
+ * by port number (address filtering is done by recvfrom()). */
ba6->sin6_addr = in6addr_any;
}
} else {
@@ -569,13 +610,6 @@ static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname,
pw_log_error("bind() failed: %m");
goto error;
}
- if (do_connect) {
- if (connect(fd, sa, salen) < 0) {
- res = -errno;
- pw_log_error("connect() failed: %m");
- goto error;
- }
- }
return fd;
error:
close(fd);
@@ -613,7 +647,9 @@ static void stream_open_connection(void *data, int *result)
if ((fd = make_socket((const struct sockaddr *)&impl->src_addr,
impl->src_len, impl->ifname,
- &(impl->igmp_recovery))) < 0) {
+ &(impl->igmp_recovery),
+ &(impl->is_multicast),
+ &(impl->filter_by_address))) < 0) {
/* If make_socket() tries to create a socket and join to a multicast
* group while the network interfaces are not ready yet to do so
* (usually because a network manager component is still setting up
@@ -663,11 +699,13 @@ static void stream_open_connection(void *data, int *result)
goto finish;
}
- if ((res = pw_timer_queue_add(impl->timer_queue, &impl->igmp_recovery.timer,
- NULL, impl->igmp_recovery.check_interval * SPA_NSEC_PER_SEC,
- on_igmp_recovery_timer_event, impl)) < 0) {
- pw_log_error("can't add timer: %s", spa_strerror(res));
- goto finish;
+ if (impl->is_multicast) {
+ if ((res = pw_timer_queue_add(impl->timer_queue, &impl->igmp_recovery.timer,
+ NULL, impl->igmp_recovery.check_interval * SPA_NSEC_PER_SEC,
+ on_igmp_recovery_timer_event, impl)) < 0) {
+ pw_log_error("can't add timer: %s", spa_strerror(res));
+ goto finish;
+ }
}
finish:
diff --git a/src/modules/module-rtp/audio.c b/src/modules/module-rtp/audio.c
index d20e9a37c..085f3bae8 100644
--- a/src/modules/module-rtp/audio.c
+++ b/src/modules/module-rtp/audio.c
@@ -546,7 +546,7 @@ static void rtp_audio_flush_packets(struct impl *impl, uint32_t num_packets, uin
else
header.m = 0;
- rtp_timestamp = impl->ts_offset + (set_timestamp ? set_timestamp : timestamp);
+ rtp_timestamp = impl->ts_offset + impl->ts_align + (set_timestamp ? set_timestamp : timestamp);
header.sequence_number = htons(impl->seq);
header.timestamp = htonl(rtp_timestamp);
@@ -556,12 +556,12 @@ static void rtp_audio_flush_packets(struct impl *impl, uint32_t num_packets, uin
((uint64_t)timestamp * stride) % impl->actual_max_buffer_size,
&iov[1], tosend * stride);
- pw_log_trace("sending %d packet:%d ts_offset:%d timestamp:%u (%f s)",
+ pw_log_trace_fp("sending %d packet:%d ts_offset:%d timestamp:%u (%f s)",
tosend, num_packets, impl->ts_offset, timestamp,
(double)timestamp * impl->io_position->clock.rate.num /
impl->io_position->clock.rate.denom);
- rtp_stream_emit_send_packet(impl, iov, 3);
+ rtp_stream_call_send_packet(impl, iov, 3);
impl->seq++;
impl->first = false;
@@ -606,7 +606,7 @@ static void rtp_audio_stop_timer(struct impl *impl)
static void rtp_audio_flush_timeout(struct impl *impl, uint64_t expirations)
{
if (expirations > 1)
- pw_log_warn("missing timeout %"PRIu64, expirations);
+ pw_log_trace("missing timeout %"PRIu64, expirations);
rtp_audio_flush_packets(impl, expirations, 0);
}
@@ -705,8 +705,10 @@ static void rtp_audio_process_capture(void *data)
* that resynchronization is needed, then this will be done immediately below. */
if (!impl->have_sync) {
- pw_log_info("(re)sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u",
- actual_timestamp, impl->seq, impl->ts_offset, impl->ssrc);
+ if (!impl->direct_timestamp)
+ impl->ts_align = actual_timestamp - impl->ring.readindex;
+ pw_log_info("(re)sync to timestamp:%u seq:%u ts_offset:%u ts_align:%u SSRC:%u",
+ actual_timestamp, impl->seq, impl->ts_offset, impl->ts_align, impl->ssrc);
spa_ringbuffer_read_update(&impl->ring, actual_timestamp);
spa_ringbuffer_write_update(&impl->ring, actual_timestamp);
memset(impl->buffer, 0, BUFFER_SIZE);
diff --git a/src/modules/module-rtp/midi.c b/src/modules/module-rtp/midi.c
index 5fbdf3b63..1237f66c6 100644
--- a/src/modules/module-rtp/midi.c
+++ b/src/modules/module-rtp/midi.c
@@ -151,7 +151,7 @@ static int parse_journal(struct impl *impl, uint8_t *packet, uint16_t seq, uint3
return -EINVAL;
j = (struct rtp_midi_journal*)packet;
uint16_t seqnum = ntohs(j->checkpoint_seqnum);
- rtp_stream_emit_send_feedback(impl, seqnum);
+ rtp_stream_call_send_feedback(impl, seqnum);
return 0;
}
@@ -271,9 +271,6 @@ static int rtp_midi_receive_midi(struct impl *impl, uint8_t *packet, uint32_t ti
while (offs < end) {
uint32_t delta;
int size;
- uint64_t state = 0;
- uint8_t *d;
- size_t s;
if (first && !hdr.z)
delta = 0;
@@ -294,17 +291,9 @@ static int rtp_midi_receive_midi(struct impl *impl, uint8_t *packet, uint32_t ti
return -EINVAL;
}
- d = &packet[offs];
- s = size;
- while (s > 0) {
- uint32_t ump[4];
- int ump_size = spa_ump_from_midi(&d, &s, ump, sizeof(ump), 0, &state);
- if (ump_size <= 0)
- break;
+ spa_pod_builder_control(&b, timestamp, SPA_CONTROL_Midi);
+ spa_pod_builder_bytes(&b, &packet[offs], size);
- spa_pod_builder_control(&b, timestamp, SPA_CONTROL_UMP);
- spa_pod_builder_bytes(&b, ump, ump_size);
- }
offs += size;
first = false;
}
@@ -378,7 +367,7 @@ unexpected_ssrc:
return -EINVAL;
}
-static int write_event(uint8_t *p, uint32_t buffer_size, uint32_t value, void *ev, uint32_t size)
+static int write_event(uint8_t *p, uint32_t buffer_size, uint32_t value, const void *ev, uint32_t size)
{
uint64_t buffer;
uint8_t b;
@@ -437,62 +426,54 @@ static void rtp_midi_flush_packets(struct impl *impl,
while (spa_pod_parser_get_control_body(parser, &c, &c_body) >= 0) {
uint32_t delta, offset;
- uint8_t event[16];
- int size;
- size_t c_size = c.value.size;
- uint64_t state = 0;
+ uint32_t size = c.value.size;
+ const uint8_t *data = c_body;
- if (c.type != SPA_CONTROL_UMP)
+ if (c.type != SPA_CONTROL_Midi)
continue;
- while (c_size > 0) {
- size = spa_ump_to_midi((const uint32_t **)&c_body, &c_size, event, sizeof(event), &state);
- if (size <= 0)
- break;
+ offset = c.offset * impl->rate / rate;
- offset = c.offset * impl->rate / rate;
-
- if (len > 0 && (len + size > max_size ||
- offset - base > impl->psamples)) {
- /* flush packet when we have one and when it's either
- * too large or has too much data. */
- if (len < 16) {
- midi_header.b = 0;
- midi_header.len = len;
- iov[1].iov_len = sizeof(midi_header) - 1;
- } else {
- midi_header.b = 1;
- midi_header.len = (len >> 8) & 0xf;
- midi_header.len_b = len & 0xff;
- iov[1].iov_len = sizeof(midi_header);
- }
- iov[2].iov_len = len;
-
- pw_log_trace("sending %d timestamp:%d %u %u",
- len, timestamp + base,
- offset, impl->psamples);
- rtp_stream_emit_send_packet(impl, iov, 3);
-
- impl->seq++;
- len = 0;
- }
- if ((unsigned int)size > BUFFER_SIZE || len > BUFFER_SIZE - size) {
- pw_log_error("Buffer overflow prevented!");
- return; // FIXME: what to do instead?
- }
- if (len == 0) {
- /* start new packet */
- base = prev_offset = offset;
- header.sequence_number = htons(impl->seq);
- header.timestamp = htonl(impl->ts_offset + timestamp + base);
-
- memcpy(&impl->buffer[len], event, size);
- len += size;
+ if (len > 0 && (len + size > max_size ||
+ offset - base > impl->psamples)) {
+ /* flush packet when we have one and when it's either
+ * too large or has too much data. */
+ if (len < 16) {
+ midi_header.b = 0;
+ midi_header.len = len;
+ iov[1].iov_len = sizeof(midi_header) - 1;
} else {
- delta = offset - prev_offset;
- prev_offset = offset;
- len += write_event(&impl->buffer[len], BUFFER_SIZE - len, delta, event, size);
+ midi_header.b = 1;
+ midi_header.len = (len >> 8) & 0xf;
+ midi_header.len_b = len & 0xff;
+ iov[1].iov_len = sizeof(midi_header);
}
+ iov[2].iov_len = len;
+
+ pw_log_trace("sending %d timestamp:%d %u %u",
+ len, timestamp + base,
+ offset, impl->psamples);
+ rtp_stream_call_send_packet(impl, iov, 3);
+
+ impl->seq++;
+ len = 0;
+ }
+ if ((unsigned int)size > BUFFER_SIZE || len > BUFFER_SIZE - size) {
+ pw_log_error("Buffer overflow prevented!");
+ return; // FIXME: what to do instead?
+ }
+ if (len == 0) {
+ /* start new packet */
+ base = prev_offset = offset;
+ header.sequence_number = htons(impl->seq);
+ header.timestamp = htonl(impl->ts_offset + timestamp + base);
+
+ memcpy(&impl->buffer[len], data, size);
+ len += size;
+ } else {
+ delta = offset - prev_offset;
+ prev_offset = offset;
+ len += write_event(&impl->buffer[len], BUFFER_SIZE - len, delta, data, size);
}
}
if (len > 0) {
@@ -510,7 +491,7 @@ static void rtp_midi_flush_packets(struct impl *impl,
iov[2].iov_len = len;
pw_log_trace("sending %d timestamp:%d", len, base);
- rtp_stream_emit_send_packet(impl, iov, 3);
+ rtp_stream_call_send_packet(impl, iov, 3);
impl->seq++;
}
}
diff --git a/src/modules/module-rtp/opus.c b/src/modules/module-rtp/opus.c
index d13a4efaf..9175d4a31 100644
--- a/src/modules/module-rtp/opus.c
+++ b/src/modules/module-rtp/opus.c
@@ -252,7 +252,7 @@ static void rtp_opus_flush_packets(struct impl *impl)
pw_log_trace("sending %d len:%d timestamp:%d", tosend, res, timestamp);
iov[1].iov_len = res;
- rtp_stream_emit_send_packet(impl, iov, 2);
+ rtp_stream_call_send_packet(impl, iov, 2);
impl->seq++;
timestamp += tosend;
diff --git a/src/modules/module-rtp/stream.c b/src/modules/module-rtp/stream.c
index d69b16524..11bba4f98 100644
--- a/src/modules/module-rtp/stream.c
+++ b/src/modules/module-rtp/stream.c
@@ -48,8 +48,11 @@ PW_LOG_TOPIC_EXTERN(mod_topic);
#define rtp_stream_emit_open_connection(s,r) rtp_stream_emit(s, open_connection, 0,r)
#define rtp_stream_emit_close_connection(s,r) rtp_stream_emit(s, close_connection, 0,r)
#define rtp_stream_emit_param_changed(s,i,p) rtp_stream_emit(s, param_changed,0,i,p)
-#define rtp_stream_emit_send_packet(s,i,l) rtp_stream_emit(s, send_packet,0,i,l)
-#define rtp_stream_emit_send_feedback(s,seq) rtp_stream_emit(s, send_feedback,0,seq)
+
+#define rtp_stream_call(s,m,v,...) spa_callbacks_call_fast(&s->rtp_callbacks, \
+ struct rtp_stream_events, m, v, ##__VA_ARGS__)
+#define rtp_stream_call_send_packet(s,i,l) rtp_stream_call(s, send_packet,0,i,l)
+#define rtp_stream_call_send_feedback(s,seq) rtp_stream_call(s, send_feedback,0,seq)
enum rtp_stream_internal_state {
/* The state when the stream is idle / stopped. The background
@@ -85,6 +88,8 @@ struct impl {
struct spa_hook stream_listener;
struct pw_stream_events stream_events;
+ struct spa_callbacks rtp_callbacks;
+
struct spa_hook_list listener_list;
struct spa_hook listener;
@@ -109,6 +114,7 @@ struct impl {
uint32_t mtu;
uint32_t header_size;
uint32_t payload_size;
+ uint32_t ts_align;
struct spa_ringbuffer ring;
uint8_t buffer[BUFFER_SIZE];
@@ -426,7 +432,7 @@ static int stream_stop(struct impl *impl)
* because a stop involves closing the connection. If the timer is still
* running, it needs an open connection for sending out remaining packets. */
if (!timer_running) {
- int res;
+ int res = 0;
pw_log_info("closing connection as part of stopping the stream");
rtp_stream_emit_close_connection(impl, &res);
if (res > 0) {
@@ -1002,6 +1008,7 @@ struct rtp_stream *rtp_stream_new(struct pw_core *core,
(res = stream_start(impl)) < 0)
goto out;
+ impl->rtp_callbacks = SPA_CALLBACKS_INIT(events, data);
spa_hook_list_append(&impl->listener_list, &impl->listener, events, data);
return (struct rtp_stream*)impl;
diff --git a/src/modules/module-scheduler-v1.c b/src/modules/module-scheduler-v1.c
new file mode 100644
index 000000000..0bfe43a4a
--- /dev/null
+++ b/src/modules/module-scheduler-v1.c
@@ -0,0 +1,1038 @@
+/* PipeWire */
+/* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */
+/* SPDX-License-Identifier: MIT */
+
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef HAVE_SYS_VFS_H
+#include
+#endif
+#ifdef HAVE_SYS_MOUNT_H
+#include
+#endif
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+/** \page page_module_scheduler_v1 SchedulerV1
+ *
+ *
+ * ## Module Name
+ *
+ * `libpipewire-module-scheduler-v1`
+ *
+ * ## Module Options
+ *
+ * Options specific to the behavior of this module
+ *
+ * ## General options
+ *
+ * Options with well-known behavior:
+ *
+ * ## Config override
+ *
+ * A `module.scheduler-v1.args` config section can be added
+ * to override the module arguments.
+ *
+ *\code{.unparsed}
+ * # ~/.config/pipewire/pipewire.conf.d/my-scheduler-v1-args.conf
+ *
+ * module.scheduler-v1.args = {
+ * }
+ *\endcode
+ *
+ * ## Example configuration
+ *
+ *\code{.unparsed}
+ * context.modules = [
+ * { name = libpipewire-module-scheduler-v1
+ * args = {
+ * }
+ * }
+ *]
+ *\endcode
+ *
+ * Since: 1.7.0
+ */
+
+#define NAME "scheduler-v1"
+
+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 " },
+ { PW_KEY_MODULE_DESCRIPTION, "Implement the Scheduler V1" },
+ { PW_KEY_MODULE_USAGE, MODULE_USAGE },
+ { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
+};
+
+#define MAX_HOPS 64
+#define MAX_SYNC 4u
+
+struct impl {
+ struct pw_context *context;
+
+ struct pw_properties *props;
+
+ struct spa_hook context_listener;
+ struct spa_hook module_listener;
+};
+
+static int ensure_state(struct pw_impl_node *node, bool running)
+{
+ enum pw_node_state state = node->info.state;
+ if (node->active && node->runnable &&
+ !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);
+}
+
+/* Make a node runnable. Peer nodes are also made runnable when the passive_mode
+ * of the peer port is !TRUE.
+ *
+ * A (*) -> B if A running -> B set to running
+ * A (*) -> (f) B if A running -> B set to running
+ * A (*) -> (fs) B if A running -> B set to running
+ * A (*) -> (p) B if A running -> B no change
+ */
+static inline bool makes_runnable(struct pw_impl_port *a, struct pw_impl_port *b)
+{
+ return b->passive_mode != PASSIVE_MODE_TRUE;
+}
+static void make_runnable(struct pw_context *context, struct pw_impl_node *node)
+{
+ struct pw_impl_port *p;
+ struct pw_impl_link *l;
+ struct pw_impl_node *n;
+
+ if (!node->runnable) {
+ pw_log_debug("%s is runnable", node->name);
+ node->runnable = true;
+ }
+
+ spa_list_for_each(p, &node->output_ports, link) {
+ spa_list_for_each(l, &p->links, output_link) {
+ n = l->input->node;
+ pw_log_trace(" out-port %p: link %p passive:%d prepared:%d active:%d runn:%d", p,
+ l, l->input->passive_mode, l->prepared, n->active, n->runnable);
+ if (!n->active || !makes_runnable(p, l->input))
+ continue;
+ pw_impl_link_prepare(l);
+ if (!l->prepared)
+ continue;
+ if (!n->runnable)
+ make_runnable(context, n);
+ }
+ }
+ spa_list_for_each(p, &node->input_ports, link) {
+ spa_list_for_each(l, &p->links, input_link) {
+ n = l->output->node;
+ pw_log_trace(" in-port %p: link %p passive:%d prepared:%d active:%d runn:%d", p,
+ l, l->output->passive_mode, l->prepared, n->active, n->runnable);
+ if (!n->active || !makes_runnable(p, l->output))
+ continue;
+ pw_impl_link_prepare(l);
+ if (!l->prepared)
+ continue;
+ if (!n->runnable)
+ make_runnable(context, n);
+ }
+ }
+ /* now go through all the nodes that share groups and link_groups
+ * that are not yet runnable. We don't include sync-groups because they
+ * are only used to group the node with a driver, not to determine the
+ * runnable state of a node. */
+ if (node->groups != NULL || node->link_groups != NULL) {
+ spa_list_for_each(n, &context->node_list, link) {
+ if (n->exported || !n->active || n->runnable)
+ continue;
+ /* the other node will be scheduled with this one if it's in
+ * the same group or link group */
+ if (pw_strv_find_common(n->groups, node->groups) < 0 &&
+ pw_strv_find_common(n->link_groups, node->link_groups) < 0)
+ continue;
+
+ make_runnable(context, n);
+ }
+ }
+}
+
+/* check if a node and its peer can run.
+ *
+ * Only consider ports that have a PASSIVE_MODE_FALSE link.
+ * All other port modes don't make A and B runnable.
+ *
+ * A -> B A + B both set to running
+ * A -> (p) B A + B both set to running
+ * A -> (f) B A + B both set to running
+ * A (fs) -> (fs) B A + B both set to running
+ * A (p) -> (*) B A + B no change
+ * A (f) -> (*) B A + B no change
+ * A (fs) -> (*) B A + B no change
+ *
+ * There is a special case for FOLLOW_SUSPEND<->FOLLOW_SUSPEND to make
+ * it possible to manually link a source to a sink
+ */
+static inline bool runnable_pair(struct pw_impl_port *a, struct pw_impl_port *b)
+{
+ bool res = false;
+ if (a->passive_mode == PASSIVE_MODE_FALSE)
+ res = true;
+ if (a->passive_mode == PASSIVE_MODE_FOLLOW_SUSPEND &&
+ b->passive_mode == PASSIVE_MODE_FOLLOW_SUSPEND)
+ res = true;
+ pw_log_trace(" port %p <-> %p: %s <> %s -> %d", a, b,
+ passive_mode_to_string(a->passive_mode),
+ passive_mode_to_string(b->passive_mode), res);
+ return res;
+}
+static void check_runnable(struct pw_context *context, struct pw_impl_node *node)
+{
+ struct pw_impl_port *p;
+ struct pw_impl_link *l;
+ struct pw_impl_node *n;
+
+ pw_log_trace("node %p: '%s' always-process:%d runnable:%u active:%d", node,
+ node->name, node->always_process, node->runnable, node->active);
+
+ if (node->always_process && !node->runnable)
+ make_runnable(context, node);
+
+ spa_list_for_each(p, &node->output_ports, link) {
+ spa_list_for_each(l, &p->links, output_link) {
+ n = l->input->node;
+ /* the peer needs to be active and we are linked to it
+ * with a non-passive link */
+ pw_log_trace(" out-port %p: link %p prepared:%d active:%d", p,
+ l, l->prepared, n->active);
+ if (!n->active || !runnable_pair(p, l->input))
+ continue;
+ /* explicitly prepare the link in case it was suspended */
+ pw_impl_link_prepare(l);
+ if (!l->prepared)
+ continue;
+ make_runnable(context, node);
+ make_runnable(context, n);
+ }
+ }
+ spa_list_for_each(p, &node->input_ports, link) {
+ spa_list_for_each(l, &p->links, input_link) {
+ n = l->output->node;
+ pw_log_trace(" in-port %p: link %p prepared:%d active:%d", p,
+ l, l->prepared, n->active);
+ if (!n->active || !runnable_pair(p, l->output))
+ continue;
+ pw_impl_link_prepare(l);
+ if (!l->prepared)
+ continue;
+ make_runnable(context, node);
+ make_runnable(context, n);
+ }
+ }
+}
+
+/* Follow all links and groups from node.
+ *
+ * After this is done, we end up with a list of nodes in collect that are all
+ * linked to node.
+ *
+ * We don't need to care about active nodes or links, we just follow and group everything.
+ * The inactive nodes or links will simply not be runnable but will already be grouped
+ * correctly when they do become active and prepared.
+ */
+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;
+ uint32_t n_sync;
+ char *sync[MAX_SYNC+1];
+
+ 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;
+
+ n_sync = 0;
+ sync[0] = NULL;
+
+ /* 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) {
+ spa_list_remove(&n->sort_link);
+ spa_list_append(collect, &n->sort_link);
+
+ pw_log_debug(" next node %p: '%s' runnable:%u active:%d",
+ n, n->name, n->runnable, n->active);
+
+ if (n->sync) {
+ for (uint32_t i = 0; n->sync_groups[i]; i++) {
+ if (n_sync >= MAX_SYNC)
+ break;
+ if (pw_strv_find(sync, n->sync_groups[i]) >= 0)
+ continue;
+ sync[n_sync++] = n->sync_groups[i];
+ sync[n_sync] = NULL;
+ }
+ }
+
+ spa_list_for_each(p, &n->input_ports, link) {
+ spa_list_for_each(l, &p->links, input_link) {
+ t = l->output->node;
+ 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->visited) {
+ t->visited = true;
+ spa_list_append(&queue, &t->sort_link);
+ }
+ }
+ }
+ /* now go through all the nodes that have the same groups and
+ * that are not yet visited */
+ if (n->groups != NULL || n->link_groups != NULL || sync[0] != NULL) {
+ spa_list_for_each(t, &context->node_list, link) {
+ if (t->exported || t->visited)
+ continue;
+ /* the other node will be scheduled with this one if it's in
+ * the same group, link group or sync group */
+ if (pw_strv_find_common(t->groups, n->groups) < 0 &&
+ pw_strv_find_common(t->link_groups, n->link_groups) < 0 &&
+ pw_strv_find_common(t->sync_groups, sync) < 0)
+ continue;
+
+ pw_log_debug("%p: %s join group of %s",
+ t, t->name, n->name);
+ t->visited = true;
+ spa_list_append(&queue, &t->sort_link);
+ }
+ }
+ pw_log_debug(" next node %p: '%s' runnable:%u %p %p %p", n, n->name, n->runnable,
+ n->groups, n->link_groups, sync);
+ }
+ 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 runnable:%u", driver, driver->name, driver->runnable);
+ spa_list_consume(n, nodes, sort_link) {
+ spa_list_remove(&n->sort_link);
+
+ driver->runnable |= n->runnable;
+
+ pw_log_debug(" follower: %p %s runnable:%u driver-runnable:%u", n, n->name,
+ n->runnable, driver->runnable);
+ 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 *rate, uint32_t *floor, uint32_t *ceil)
+{
+ 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;
+ }
+ *floor = s->clock_quantum_floor;
+ *ceil = 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->need_resume = !n->pause_on_idle;
+ 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 inline uint32_t calc_gcd(uint32_t a, uint32_t b)
+{
+ while (b != 0) {
+ uint32_t temp = a;
+ a = b;
+ b = temp % b;
+ }
+ return a;
+}
+
+struct rate_info {
+ uint32_t rate;
+ uint32_t gcd;
+ uint32_t diff;
+};
+
+static inline void update_highest_rate(struct rate_info *best, struct rate_info *current)
+{
+ /* find highest rate */
+ if (best->rate == 0 || best->rate < current->rate)
+ *best = *current;
+}
+
+static inline void update_nearest_gcd(struct rate_info *best, struct rate_info *current)
+{
+ /* find nearest GCD */
+ if (best->rate == 0 ||
+ (best->gcd < current->gcd) ||
+ (best->gcd == current->gcd && best->diff > current->diff))
+ *best = *current;
+}
+static inline void update_nearest_rate(struct rate_info *best, struct rate_info *current)
+{
+ /* find nearest rate */
+ if (best->rate == 0 || best->diff > current->diff)
+ *best = *current;
+}
+
+static uint32_t find_best_rate(const uint32_t *rates, uint32_t n_rates, uint32_t rate, uint32_t def)
+{
+ uint32_t i, limit;
+ struct rate_info best;
+ struct rate_info info[n_rates];
+
+ for (i = 0; i < n_rates; i++) {
+ info[i].rate = rates[i];
+ info[i].gcd = calc_gcd(rate, rates[i]);
+ info[i].diff = SPA_ABS((int32_t)rate - (int32_t)rates[i]);
+ }
+
+ /* first find higher nearest GCD. This tries to find next bigest rate that
+ * requires the least amount of resample filter banks. Usually these are
+ * rates that are multiples of each other or multiples of a common rate.
+ *
+ * 44100 and [ 32000 56000 88200 96000 ] -> 88200
+ * 48000 and [ 32000 56000 88200 96000 ] -> 96000
+ * 88200 and [ 44100 48000 96000 192000 ] -> 96000
+ * 32000 and [ 44100 192000 ] -> 44100
+ * 8000 and [ 44100 48000 ] -> 48000
+ * 8000 and [ 44100 192000 ] -> 44100
+ * 11025 and [ 44100 48000 ] -> 44100
+ * 44100 and [ 48000 176400 ] -> 48000
+ * 144 and [ 44100 48000 88200 96000] -> 48000
+ */
+ spa_zero(best);
+ /* Don't try to do excessive upsampling by limiting the max rate
+ * for desired < default to default*2. For other rates allow
+ * a x3 upsample rate max. For values lower than half of the default,
+ * limit to the default. */
+ limit = rate < def/2 ? def : rate < def ? def*2 : rate*3;
+ for (i = 0; i < n_rates; i++) {
+ if (info[i].rate >= rate && info[i].rate <= limit)
+ update_nearest_gcd(&best, &info[i]);
+ }
+ if (best.rate != 0)
+ return best.rate;
+
+ /* we would need excessive upsampling, pick a nearest higher rate */
+ spa_zero(best);
+ for (i = 0; i < n_rates; i++) {
+ if (info[i].rate >= rate)
+ update_nearest_rate(&best, &info[i]);
+ }
+ if (best.rate != 0)
+ return best.rate;
+
+ /* There is nothing above the rate, we need to downsample. Try to downsample
+ * but only to something that is from a common rate family. Also don't
+ * try to downsample to something that will sound worse (< 44100).
+ *
+ * 88200 and [ 22050 44100 48000 ] -> 44100
+ * 88200 and [ 22050 48000 ] -> 48000
+ */
+ spa_zero(best);
+ for (i = 0; i < n_rates; i++) {
+ if (info[i].rate >= 44100)
+ update_nearest_gcd(&best, &info[i]);
+ }
+ if (best.rate != 0)
+ return best.rate;
+
+ /* There is nothing to downsample above our threshold. Downsample to whatever
+ * is the highest rate then. */
+ spa_zero(best);
+ for (i = 0; i < n_rates; i++)
+ update_highest_rate(&best, &info[i]);
+ if (best.rate != 0)
+ return best.rate;
+
+ return def;
+}
+
+/* here we evaluate the complete state of the graph.
+ *
+ * It roughly operates in 4 stages:
+ *
+ * 1. go over all nodes and check if they should be scheduled (runnable) or not.
+ *
+ * 2. 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.
+ *
+ * 3. 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 2.
+ *
+ * 4. 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.
+ */
+static void context_recalc_graph(void *data)
+{
+ struct impl *impl = data;
+ struct pw_context *context = impl->context;
+ 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, rate_quantum, floor_quantum, ceil_quantum;
+ uint32_t n_rates, def_rate, transport;
+ bool freewheel, global_force_rate, global_force_quantum;
+ struct spa_list collect;
+
+again:
+ freewheel = false;
+
+ /* clean up the flags first */
+ spa_list_for_each(n, &context->node_list, link) {
+ n->visited = false;
+ n->checked = 0;
+ n->runnable = false;
+ }
+
+ get_quantums(context, &def_quantum, &min_quantum, &max_quantum, &rate_quantum,
+ &floor_quantum, &ceil_quantum);
+ rates = get_rates(context, &def_rate, &n_rates, &global_force_rate);
+
+ global_force_quantum = rate_quantum == 0;
+
+ /* first look at all nodes and decide which one should be runnable */
+ spa_list_for_each(n, &context->node_list, link) {
+ if (n->exported || !n->active)
+ continue;
+ check_runnable(context, n);
+ }
+
+ /* 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
+ * with a driver_priority. We're going to see if there are
+ * active followers. */
+ if (!n->driving || !n->active || n->priority_driver <= 0)
+ continue;
+
+ /* first active driving node is fallback */
+ if (fallback == NULL)
+ fallback = n;
+
+ if (!n->runnable)
+ 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 */
+ pw_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 */
+ if ((t->want_driver && t->active && t->runnable) ||
+ t->always_process) {
+ driver = target;
+ break;
+ }
+ }
+ if (driver != NULL) {
+ driver->runnable = true;
+ /* driver needed for this group */
+ move_to_driver(context, &collect, driver);
+ } else {
+ /* no driver, make sure the nodes stop */
+ remove_from_driver(context, &collect);
+ }
+ }
+
+ /* 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 target_quantum, target_rate, current_rate, current_quantum;
+ uint64_t quantum_stamp = 0, rate_stamp = 0;
+ bool force_rate, force_quantum, restore_rate = false, restore_quantum = false;
+ bool do_reconfigure = false, need_resume, was_target_pending;
+ bool have_request = false;
+ 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, which is in fact the smallest fraction */
+ if (rate.denom == 0 ||
+ (s->rate.denom > 0 &&
+ fraction_compare(&s->rate, &rate) < 0))
+ rate = s->rate;
+
+ if (s->active)
+ running = n->runnable;
+
+ pw_log_debug("%p: follower %p running:%d runnable:%d rate:%u/%u latency %u/%u '%s'",
+ context, s, running, s->runnable, rate.num, rate.denom,
+ latency.num, latency.denom, s->name);
+
+ if (running && s != n && s->supports_request > 0)
+ have_request = true;
+
+ s->moved = false;
+ }
+
+ if (n->forced_rate && !force_rate && n->runnable) {
+ /* A node that was forced to a rate but is no longer being
+ * forced can restore its rate */
+ pw_log_info("(%s-%u) restore rate", n->name, n->info.id);
+ restore_rate = true;
+ }
+ if (n->forced_quantum && !force_quantum && n->runnable) {
+ /* A node that was forced to a quantum but is no longer being
+ * forced can restore its quantum */
+ pw_log_info("(%s-%u) restore quantum", n->name, n->info.id);
+ restore_quantum = true;
+ }
+
+ if (force_quantum)
+ lock_quantum = false;
+ if (force_rate)
+ lock_rate = false;
+
+ need_resume = n->need_resume;
+ if (need_resume) {
+ running = true;
+ n->need_resume = false;
+ }
+
+ current_rate = n->target_rate.denom;
+ if (!restore_rate &&
+ (lock_rate || need_resume || !running ||
+ (!force_rate && (n->info.state > PW_NODE_STATE_IDLE)))) {
+ pw_log_debug("%p: keep rate:1/%u restore:%u lock:%u resume:%u "
+ "running:%u force:%u state:%s", context,
+ current_rate, restore_rate, lock_rate, need_resume,
+ running, force_rate,
+ pw_node_state_as_string(n->info.state));
+
+ /* when we don't need to restore or rate and
+ * when someone wants us to lock the rate of this driver or
+ * when we are in the process of reconfiguring the driver or
+ * when we are not running any followers 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 */
+ if (rate.denom != 0 && rate.num == 1)
+ target_rate = rate.denom;
+ else
+ target_rate = node_def_rate;
+
+ target_rate = find_best_rate(node_rates, node_n_rates,
+ target_rate, node_def_rate);
+
+ pw_log_debug("%p: def_rate:%d target_rate:%d rate:%d/%d", context,
+ node_def_rate, target_rate, rate.num, rate.denom);
+ }
+
+ was_target_pending = n->target_pending;
+
+ if (target_rate != current_rate) {
+ /* we doing a rate switch */
+ pw_log_info("(%s-%u) state:%s new rate:%u/(%u)->%u",
+ n->name, n->info.id,
+ pw_node_state_as_string(n->info.state),
+ n->target_rate.denom, current_rate,
+ target_rate);
+
+ if (force_rate) {
+ if (settings->clock_rate_update_mode == CLOCK_RATE_UPDATE_MODE_HARD)
+ do_reconfigure |= !was_target_pending;
+ } else {
+ if (n->info.state >= PW_NODE_STATE_SUSPENDED)
+ do_reconfigure |= !was_target_pending;
+ }
+ /* we're setting the pending rate. This will become the new
+ * current rate in the next iteration of the graph. */
+ n->target_rate = SPA_FRACTION(1, target_rate);
+ n->forced_rate = force_rate;
+ n->target_pending = true;
+ current_rate = target_rate;
+ }
+
+ if (node_rate_quantum != 0 && current_rate != node_rate_quantum) {
+ /* the quantum values are scaled with the current rate */
+ node_def_quantum = SPA_SCALE32(node_def_quantum, current_rate, node_rate_quantum);
+ node_min_quantum = SPA_SCALE32(node_min_quantum, current_rate, node_rate_quantum);
+ node_max_quantum = SPA_SCALE32(node_max_quantum, current_rate, node_rate_quantum);
+ }
+
+ /* calculate desired quantum. Don't limit to the max_latency when we are
+ * going to force a quantum or rate and reconfigure the nodes. */
+ if (max_latency.denom != 0 && !force_quantum && !force_rate) {
+ uint32_t tmp = SPA_SCALE32(max_latency.num, current_rate, max_latency.denom);
+ if (tmp < node_max_quantum)
+ node_max_quantum = tmp;
+ }
+
+ current_quantum = n->target_quantum;
+ if (!restore_quantum && (lock_quantum || need_resume || !running)) {
+ pw_log_debug("%p: keep quantum:%u restore:%u lock:%u resume:%u "
+ "running:%u force:%u state:%s", context,
+ current_quantum, restore_quantum, lock_quantum, need_resume,
+ running, force_quantum,
+ pw_node_state_as_string(n->info.state));
+ target_quantum = current_quantum;
+ }
+ else {
+ target_quantum = node_def_quantum;
+ if (latency.denom != 0)
+ target_quantum = SPA_SCALE32(latency.num, current_rate, latency.denom);
+ target_quantum = SPA_CLAMP(target_quantum, node_min_quantum, node_max_quantum);
+ target_quantum = SPA_CLAMP(target_quantum, floor_quantum, ceil_quantum);
+
+ if (settings->clock_power_of_two_quantum && !force_quantum)
+ target_quantum = flp2(target_quantum);
+ }
+
+ if (target_quantum != current_quantum) {
+ pw_log_info("(%s-%u) new quantum:%"PRIu64"->%u",
+ n->name, n->info.id,
+ n->target_quantum,
+ target_quantum);
+ /* this is the new pending quantum */
+ n->target_quantum = target_quantum;
+ n->forced_quantum = force_quantum;
+ n->target_pending = true;
+
+ if (force_quantum)
+ do_reconfigure |= !was_target_pending;
+ }
+
+ if (n->target_pending) {
+ if (do_reconfigure) {
+ reconfigure_driver(context, n);
+ /* we might be suspended now and the links need to be prepared again */
+ goto again;
+ }
+ /* we have a pending change. We place the new values in the
+ * pending fields so that they are picked up by the driver in
+ * the next cycle */
+ pw_log_debug("%p: apply duration:%"PRIu64" rate:%u/%u", context,
+ n->target_quantum, n->target_rate.num,
+ n->target_rate.denom);
+ SPA_SEQ_WRITE(n->rt.position->clock.target_seq);
+ n->rt.position->clock.target_duration = n->target_quantum;
+ n->rt.position->clock.target_rate = n->target_rate;
+ SPA_SEQ_WRITE(n->rt.position->clock.target_seq);
+
+ if (n->info.state < PW_NODE_STATE_RUNNING) {
+ n->rt.position->clock.duration = n->target_quantum;
+ n->rt.position->clock.rate = n->target_rate;
+ }
+ n->target_pending = false;
+ } else {
+ n->target_quantum = n->rt.position->clock.target_duration;
+ n->target_rate = n->rt.position->clock.target_rate;
+ }
+
+ if (n->info.state < PW_NODE_STATE_RUNNING)
+ n->rt.position->clock.nsec = get_time_ns(n->rt.target.system);
+
+ SPA_FLAG_UPDATE(n->rt.position->clock.flags,
+ SPA_IO_CLOCK_FLAG_LAZY, have_request && n->supports_lazy > 0);
+
+ pw_log_debug("%p: driver %p running:%d runnable:%d quantum:%u rate:%u (%"PRIu64"/%u)'%s'",
+ context, n, running, n->runnable, target_quantum, target_rate,
+ n->rt.position->clock.target_duration,
+ n->rt.position->clock.target_rate.denom, n->name);
+
+ transport = PW_NODE_ACTIVATION_COMMAND_NONE;
+
+ /* first change the node states of the followers to the new target */
+ spa_list_for_each(s, &n->follower_list, follower_link) {
+ if (s->transport != PW_NODE_ACTIVATION_COMMAND_NONE) {
+ transport = s->transport;
+ s->transport = PW_NODE_ACTIVATION_COMMAND_NONE;
+ }
+ if (s == n)
+ continue;
+ pw_log_debug("%p: follower %p: active:%d '%s'",
+ context, s, s->active, s->name);
+ ensure_state(s, running);
+ }
+
+ if (transport != PW_NODE_ACTIVATION_COMMAND_NONE) {
+ pw_log_info("%s: transport %d", n->name, transport);
+ SPA_ATOMIC_STORE(n->rt.target.activation->command, transport);
+ }
+
+ /* now that all the followers are ready, start the driver */
+ ensure_state(n, running);
+ }
+}
+
+static const struct pw_context_events context_events = {
+ PW_VERSION_CONTEXT_EVENTS,
+ .recalc_graph = context_recalc_graph,
+};
+
+static void module_destroy(void *data)
+{
+ struct impl *impl = data;
+
+ if (impl->context) {
+ spa_hook_remove(&impl->context_listener);
+ spa_hook_remove(&impl->module_listener);
+ }
+
+ pw_properties_free(impl->props);
+
+ 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_str)
+{
+ struct pw_context *context = pw_impl_module_get_context(module);
+ struct pw_properties *args;
+ 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_str);
+
+ if (args_str)
+ args = pw_properties_new_string(args_str);
+ else
+ args = pw_properties_new(NULL, NULL);
+
+ if (!args) {
+ res = -errno;
+ goto error;
+ }
+
+ pw_context_conf_update_props(context, "module."NAME".args", args);
+
+ impl->props = args;
+ impl->context = context;
+
+ 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;
+
+error:
+ module_destroy(impl);
+ return res;
+}
diff --git a/src/modules/module-sendspin-recv.c b/src/modules/module-sendspin-recv.c
new file mode 100644
index 000000000..fe6525767
--- /dev/null
+++ b/src/modules/module-sendspin-recv.c
@@ -0,0 +1,1401 @@
+/* PipeWire */
+/* SPDX-FileCopyrightText: Copyright © 2026 Wim Taymans */
+/* SPDX-License-Identifier: MIT */
+
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include