diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2918ce62e..448bfa06e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -410,15 +410,13 @@ build_on_fedora_html_docs: -Dsndfile=enabled -Dsession-managers=[] before_script: - - git fetch origin 1.0 1.2 1.4 1.6 master + - git fetch origin 1.0 1.2 1.4 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] @@ -435,10 +433,6 @@ 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 @@ -664,7 +658,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/dox/modules.dox" && false) + git grep -q -e "\\\subpage $page" || (echo "\\page $page is missing \\subpage entry in doc/pipewire-modules.dox" && false) done check_missing_headers: @@ -688,13 +682,12 @@ pages: - job: build_on_fedora_html_docs artifacts: true script: - - mkdir public public/1.0 public/1.2 public/1.4 public/1.6 public/devel + - mkdir public public/1.0 public/1.2 public/1.4 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.6/* .) + - (cd public && ln -s 1.4/* .) artifacts: paths: - public diff --git a/NEWS b/NEWS index 1f0a39a28..6e8b7f6f5 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,69 @@ +# PipeWire 1.6.2 (2026-03-16) + +This is a bugfix release that is API and ABI compatible with the previous +1.6.x releases. + +## Highlights + - Fix a potential crash when the wrong memory was freed. + - Fix a optimization with shared memory over some links that could + cause errors later on. + - Fix SOFA filter and default control input in LADSPA and LV2. + - Some other small fixes and improvements. + + +## PipeWire + - Remove an optimization to skip share mem in links, it causes problems + later on. (#5159) + +## Modules + - Don't try to free invalid memory or close invalid fds when the client + aborted before allocating buffer memory. (#5162) + +## SPA + - support ACP_IGNORE_DB in udev. + - Use 0x as a prefix for hex values. + - Mark Props as write-only in libcamera. + - Small optimization in the audio mixer. + - Fix initialization of control properties for SOFA and biquads in the + filter-graph. (#5152) + - Fix min/max default values for LADSPA and LV2. + +## JACK + - Fix jack_port_type_id(). Return values that are compatible with JACK1/2. + + +Older versions: + +# PipeWire 1.6.1 (2026-03-09) + +This is a bugfix release that is API and ABI compatible with the previous +1.6.x releases. + +## Highlights + - Fix socket activation, which could cause a failure to start PipeWire in + some setups. + - Fix crashes in many JACK apps when nodes/ports are quickly added/removed + such as when there are notifications (like when changing the volume in + KDE). + - Fix playback of encoded formats in pw-cat again. + - Some other smaller fixes and improvements. + +## Modules + - Fix socket activation. (#5140) + - Remove node.link-group from driver nodes. + +## SPA + - Fix the libcamera stop sequence. + +## JACK + - Never return NULL from jack_port_by_id(). (#3512) + +## GStreamer + - Improve the timestamps on buffers. + +## Tools + - Fix playback of encoded formats. (#5155) + # PipeWire 1.6.0 (2026-02-19) This is the 1.6 release that is API and ABI compatible with previous @@ -95,8 +161,6 @@ the 1.4 release last year, including: - Add some more options to pw-cat to list supported containers and formats. (#5117) -Older versions: - # PipeWire 1.5.85 (2026-01-19) This is the fifth and hopefully last 1.6 release candidate that diff --git a/doc/dox/config/pipewire-client.conf.5.md b/doc/dox/config/pipewire-client.conf.5.md index 321538a79..db43839a0 100644 --- a/doc/dox/config/pipewire-client.conf.5.md +++ b/doc/dox/config/pipewire-client.conf.5.md @@ -80,9 +80,6 @@ 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 44b16f505..e561979d5 100644 --- a/doc/dox/config/pipewire-props.7.md +++ b/doc/dox/config/pipewire-props.7.md @@ -450,25 +450,11 @@ Whether the node target may be changed using metadata. @PAR@ node-prop node.passive = false \parblock -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 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 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. @@ -783,15 +769,6 @@ 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. @@ -1398,9 +1375,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-reliability QoS configuration table are used. +If not set or set to "balanced", both low-latency and high-reliabilty QoS configuration table are used. This property is experimental. -Available: low-latency, high-reliability, balanced +Available: low-latency, high-reliabilty, balanced ## Node properties @@ -1443,11 +1420,6 @@ 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 9cc8d4c48..ad1b213c7 100644 --- a/doc/dox/config/pipewire-pulse.conf.5.md +++ b/doc/dox/config/pipewire-pulse.conf.5.md @@ -93,9 +93,6 @@ 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/index.dox b/doc/dox/internals/index.dox index 357e2f126..89d2e9da3 100644 --- a/doc/dox/internals/index.dox +++ b/doc/dox/internals/index.dox @@ -10,7 +10,6 @@ - \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 efc2b4c8b..0ff2cbe95 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 \ref page_scheduling . +node and will be scheduled differently, see scheduling.dox. 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 e89b24578..4c86c516b 100644 --- a/doc/dox/internals/midi.dox +++ b/doc/dox/internals/midi.dox @@ -62,13 +62,6 @@ 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 @@ -111,14 +104,13 @@ 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 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. +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. 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 deleted file mode 100644 index e7ffe06db..000000000 --- a/doc/dox/internals/running.dox +++ /dev/null @@ -1,393 +0,0 @@ -/** \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 c74124480..38b05596b 100644 --- a/doc/dox/internals/scheduling.dox +++ b/doc/dox/internals/scheduling.dox @@ -23,9 +23,6 @@ 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 85b0a1418..4e9358197 100644 --- a/doc/dox/modules.dox +++ b/doc/dox/modules.dox @@ -81,7 +81,6 @@ 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 @@ -91,8 +90,6 @@ 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 9e2530163..1490cd444 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 five flags. The five flags are: +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: - 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). -Nodes have a method `process`, which eats up data from input ports and provides data for each output port. +Notes 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/index.md b/doc/dox/programs/index.md index 01ba07720..991ec7263 100644 --- a/doc/dox/programs/index.md +++ b/doc/dox/programs/index.md @@ -4,7 +4,6 @@ Manual pages: - \subpage page_man_pipewire_1 - \subpage page_man_pipewire-pulse_1 -- \subpage page_man_pw-audioconvert_1 - \subpage page_man_pw-cat_1 - \subpage page_man_pw-cli_1 - \subpage page_man_pw-config_1 diff --git a/doc/dox/programs/pw-audioconvert.1.md b/doc/dox/programs/pw-audioconvert.1.md deleted file mode 100644 index e78e4c7ec..000000000 --- a/doc/dox/programs/pw-audioconvert.1.md +++ /dev/null @@ -1,62 +0,0 @@ -\page page_man_pw-audioconvert_1 pw-audioconvert - -The PipeWire audioconvert utility - -# SYNOPSIS - -**pw-audioconvert** \[*OPTIONS*\] *INFILE* *OUTFILE* - -# DESCRIPTION - -Use the PipeWire audioconvert to convert input file to output file, -following the given options. - -This is useful only for doing audio conversion but also apply effects -on the audio using a filter-graph. - -It understands all audio file formats supported by `libsndfile` for input -and output. The filename extension is used to guess the output file -container and format with the WAV file format as the default. - -# OPTIONS - -\par -r RATE | \--rate=RATE -Output sample rate. Default the same as the input sample rate. - -\par -f FORMAT | \--format=FORMAT -Output sample format (s8 | s16 | s32 | f32 | f64). Default the same -as the input format. - -\par -b BLOCKSIZE | \--blocksize=BLOCKSIZE -Number of samples per iteration (default 4096) - -\par -P PROPERTIES | \--properties=PROPERTIES -Set extra stream properties as a JSON object. One can also use @filename to -read the JSON object with properties from filename. - -\par -c CHANNELS | \--channels=CHANNELS -The number of output channels, default the same as the input. - -\par \--channel-map=VALUE -The channelmap. Possible values include are either a predefined channel layout -such as **Mono**, **Stereo**, **2.1**, **Quad**, **2.2**, **5.1**, -or comma separated array of channel names such as **FL,FR**. - -\par -h -Show help. - -\par -v -Verbose operation. - -# EXAMPLES - -**pw-audioconvert** -r 48000 -f s32 in.wav out.wav - -# AUTHORS - -The PipeWire Developers <$(PACKAGE_BUGREPORT)>; -PipeWire is available from <$(PACKAGE_URL)> - -# SEE ALSO - -\ref page_man_pipewire_1 "pipewire(1)" diff --git a/doc/dox/programs/pw-cat.1.md b/doc/dox/programs/pw-cat.1.md index 8ec02c711..b681e54a1 100644 --- a/doc/dox/programs/pw-cat.1.md +++ b/doc/dox/programs/pw-cat.1.md @@ -124,9 +124,6 @@ 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 353015d1a..1207e3205 100644 --- a/doc/dox/programs/pw-top.1.md +++ b/doc/dox/programs/pw-top.1.md @@ -188,11 +188,6 @@ 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 @@ -204,9 +199,6 @@ 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 645b4b26a..f4aa4ba6a 100644 --- a/doc/meson.build +++ b/doc/meson.build @@ -67,7 +67,6 @@ 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', @@ -100,7 +99,6 @@ manpage_docs = [ 'dox/config/libpipewire-modules.7.md', 'dox/programs/pipewire-pulse.1.md', 'dox/programs/pipewire.1.md', - 'dox/programs/pw-audioconvert.1.md', 'dox/programs/pw-cat.1.md', 'dox/programs/pw-cli.1.md', 'dox/programs/pw-config.1.md', diff --git a/meson.build b/meson.build index 37badca51..536502a68 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('pipewire', ['c' ], - version : '1.7.0', + version : '1.6.2', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.61.1', default_options : [ 'warning_level=3', @@ -368,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.1.0', required : get_option('sndfile')) +sndfile_dep = dependency('sndfile', version : '>= 1.0.20', required : get_option('sndfile')) summary({'sndfile': sndfile_dep.found()}, bool_yn: true, section: 'pw-cat/pw-play/pw-dump/filter-chain') cdata.set('HAVE_SNDFILE', sndfile_dep.found()) pulseaudio_dep = dependency('libpulse', required : get_option('libpulse')) @@ -412,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': {'version': '>= 1.22.0'}, + 'gstreamer-video-1.0': {}, 'gstreamer-audio-1.0': {}, 'gstreamer-allocators-1.0': {}, } diff --git a/meson_options.txt b/meson_options.txt index 7c8cc15ba..206d68659 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -294,10 +294,6 @@ option('legacy-rtkit', description: 'Build legacy rtkit module', type: 'boolean', value: true) -option('avb-virtual', - description: 'Enable AVB Virtual code for testing', - type: 'feature', - value: 'disabled') option('avb', description: 'Enable AVB code', type: 'feature', diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index a84e4254c..c7017e936 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -73,6 +73,7 @@ PW_LOG_TOPIC_STATIC(jack_log_topic, "jack"); #define TYPE_ID_IS_EVENT(t) ((t) >= TYPE_ID_MIDI && (t) <= TYPE_ID_UMP) #define TYPE_ID_CAN_OSC(t) ((t) == TYPE_ID_MIDI || (t) == TYPE_ID_OSC) +#define TYPE_ID_IS_HIDDEN(t) ((t) >= TYPE_ID_OTHER) #define TYPE_ID_IS_COMPATIBLE(a,b)(((a) == (b)) || (TYPE_ID_IS_EVENT(a) && TYPE_ID_IS_EVENT(b))) #define SELF_CONNECT_ALLOW 0 @@ -85,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<<16) +#define NOTIFY_BUFFER_SIZE (1u<<13) #define NOTIFY_BUFFER_MASK (NOTIFY_BUFFER_SIZE-1) struct notify { @@ -103,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; - int arg1; struct object *object; + int arg1; const char *msg; }; @@ -1447,9 +1448,8 @@ 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: @@ -1466,15 +1466,27 @@ 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)) - ev_type = SPA_CONTROL_OSC; - else - ev_type = event_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; - spa_pod_builder_control(&b, ev.time, ev_type); - spa_pod_builder_bytes(&b, ev.buffer, ev.size); + while (size > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&data, &size, + ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + break; + spa_pod_builder_control(&b, ev.time, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ump, ump_size); + } + } } spa_pod_builder_pop(&b, &f); return b.state.offset; @@ -1766,7 +1778,7 @@ static inline void process_empty(struct port *p, uint32_t frames) case TYPE_ID_AUDIO: ptr = get_buffer_output(p, frames, sizeof(float), NULL); if (SPA_LIKELY(ptr != NULL)) - spa_memcpy(ptr, src, frames * sizeof(float)); + memcpy(ptr, src, frames * sizeof(float)); break; case TYPE_ID_MIDI: case TYPE_ID_OSC: @@ -1780,7 +1792,7 @@ static inline void process_empty(struct port *p, uint32_t frames) * to do this concurrently */ b->datas[0].chunk->size = convert_from_event(src, midi_scratch, MIDI_SCRATCH_FRAMES * sizeof(float), type); - spa_memcpy(ptr, midi_scratch, b->datas[0].chunk->size); + memcpy(ptr, midi_scratch, b->datas[0].chunk->size); } break; } @@ -2198,7 +2210,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 = -EBUSY; + int status = 0; buffer_frames = cycle_run(c); @@ -3893,9 +3905,8 @@ static void registry_event_global(void *data, uint32_t id, const char *name; if ((str = spa_dict_lookup(props, PW_KEY_FORMAT_DSP)) == NULL) - goto exit; - if ((type_id = string_to_type(str)) == SPA_ID_INVALID || - !type_is_dsp(type_id)) + str = "other"; + if ((type_id = string_to_type(str)) == SPA_ID_INVALID) goto exit; if ((str = spa_dict_lookup(props, PW_KEY_NODE_ID)) == NULL) @@ -4864,7 +4875,7 @@ int jack_activate (jack_client_t *client) freeze_callbacks(c); /* reemit buffer_frames */ - c->buffer_frames = (uint32_t)-1; + c->buffer_frames = 0; pw_data_loop_start(c->loop); c->active = true; @@ -5445,7 +5456,7 @@ SPA_EXPORT jack_nframes_t jack_get_buffer_size (jack_client_t *client) { struct client *c = (struct client *) client; - uint32_t res = -1; + jack_nframes_t res = -1; return_val_if_fail(c != NULL, 0); @@ -5462,7 +5473,7 @@ jack_nframes_t jack_get_buffer_size (jack_client_t *client) } c->buffer_frames = res; pw_log_debug("buffer_frames: %u", res); - return (jack_nframes_t)res; + return res; } SPA_EXPORT @@ -5531,8 +5542,7 @@ jack_port_t * jack_port_register (jack_client_t *client, return NULL; } - if ((type_id = string_to_type(port_type)) == SPA_ID_INVALID || - !type_is_dsp(type_id)) { + if ((type_id = string_to_type(port_type)) == SPA_ID_INVALID) { pw_log_warn("unknown port type %s", port_type); return NULL; } @@ -5851,11 +5861,11 @@ static void *get_buffer_input_midi(struct port *p, jack_nframes_t frames) * the per port buffer. This makes it possible to call this function concurrently * but also have different pointers per port */ convert_to_event(mix_info, n_mix_info, mb, p->client->fix_midi_events, p->object->port.type_id); - spa_memcpy(ptr, mb, sizeof(struct midi_buffer) + (mb->event_count + memcpy(ptr, mb, sizeof(struct midi_buffer) + (mb->event_count * sizeof(struct midi_event))); if (mb->write_pos > 0) { size_t offs = mb->buffer_size - mb->write_pos; - spa_memcpy(SPA_PTROFF(ptr, offs, void), SPA_PTROFF(mb, offs, void), mb->write_pos); + memcpy(SPA_PTROFF(ptr, offs, void), SPA_PTROFF(mb, offs, void), mb->write_pos); } return ptr; } @@ -6514,10 +6524,10 @@ int jack_connect (jack_client_t *client, if ((res = check_connect(c, src, dst)) != 1) goto exit; - snprintf(val[0], sizeof(val[0]), "%u", src->port.node_id); - snprintf(val[1], sizeof(val[1]), "%u", src->id); - snprintf(val[2], sizeof(val[2]), "%u", dst->port.node_id); - snprintf(val[3], sizeof(val[3]), "%u", dst->id); + snprintf(val[0], sizeof(val[0]), "%d", src->port.node_id); + snprintf(val[1], sizeof(val[1]), "%d", src->id); + snprintf(val[2], sizeof(val[2]), "%d", dst->port.node_id); + snprintf(val[3], sizeof(val[3]), "%d", dst->id); props = SPA_DICT_INIT(items, 0); items[props.n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_LINK_OUTPUT_NODE, val[0]); @@ -6936,6 +6946,8 @@ const char ** jack_get_ports (jack_client_t *client, continue; pw_log_debug("%p: check port type:%d flags:%08lx name:\"%s\"", c, o->port.type_id, o->port.flags, o->port.name); + if (TYPE_ID_IS_HIDDEN(o->port.type_id)) + continue; if (!SPA_FLAG_IS_SET(o->port.flags, flags)) continue; if (str != NULL && o->port.node != NULL) { diff --git a/pipewire-jack/src/ringbuffer.c b/pipewire-jack/src/ringbuffer.c index be4014c23..ba1cc23f3 100644 --- a/pipewire-jack/src/ringbuffer.c +++ b/pipewire-jack/src/ringbuffer.c @@ -132,10 +132,10 @@ size_t jack_ringbuffer_read(jack_ringbuffer_t *rb, char *dest, size_t cnt) n2 = 0; } - spa_memcpy (dest, &(rb->buf[rb->read_ptr]), n1); + memcpy (dest, &(rb->buf[rb->read_ptr]), n1); rb->read_ptr = (rb->read_ptr + n1) & rb->size_mask; if (n2) { - spa_memcpy (dest + n1, &(rb->buf[rb->read_ptr]), n2); + memcpy (dest + n1, &(rb->buf[rb->read_ptr]), n2); rb->read_ptr = (rb->read_ptr + n2) & rb->size_mask; } return to_read; @@ -167,11 +167,11 @@ size_t jack_ringbuffer_peek(jack_ringbuffer_t *rb, char *dest, size_t cnt) n2 = 0; } - spa_memcpy (dest, &(rb->buf[tmp_read_ptr]), n1); + memcpy (dest, &(rb->buf[tmp_read_ptr]), n1); tmp_read_ptr = (tmp_read_ptr + n1) & rb->size_mask; if (n2) - spa_memcpy (dest + n1, &(rb->buf[tmp_read_ptr]), n2); + memcpy (dest + n1, &(rb->buf[tmp_read_ptr]), n2); return to_read; } @@ -249,10 +249,10 @@ size_t jack_ringbuffer_write(jack_ringbuffer_t *rb, const char *src, n2 = 0; } - spa_memcpy (&(rb->buf[rb->write_ptr]), src, n1); + memcpy (&(rb->buf[rb->write_ptr]), src, n1); rb->write_ptr = (rb->write_ptr + n1) & rb->size_mask; if (n2) { - spa_memcpy (&(rb->buf[rb->write_ptr]), src + n1, n2); + memcpy (&(rb->buf[rb->write_ptr]), src + n1, n2); rb->write_ptr = (rb->write_ptr + n2) & rb->size_mask; } return to_write; diff --git a/pipewire-v4l2/src/pipewire-v4l2.c b/pipewire-v4l2/src/pipewire-v4l2.c index 7a5e5c057..8fc07151a 100644 --- a/pipewire-v4l2/src/pipewire-v4l2.c +++ b/pipewire-v4l2/src/pipewire-v4l2.c @@ -2570,10 +2570,7 @@ static void *v4l2_mmap(void *addr, size_t length, int prot, buf = &file->buffers[id]; data = &buf->buf->buffer->datas[0]; - if (pw_map_range_init(&range, data->mapoffset, data->maxsize, 1024) < 0) { - res = MAP_FAILED; - goto error_unlock; - } + pw_map_range_init(&range, data->mapoffset, data->maxsize, 1024); if (!SPA_FLAG_IS_SET(data->flags, SPA_DATA_FLAG_READABLE)) prot &= ~PROT_READ; diff --git a/po/LINGUAS b/po/LINGUAS index f5ef59cf7..8ebdbea2e 100644 --- a/po/LINGUAS +++ b/po/LINGUAS @@ -7,10 +7,9 @@ bn_IN ca cs da -de de_CH +de el -eo es fi fr @@ -37,15 +36,14 @@ oc or pa pl -pt pt_BR +pt ro ru -si sk sl -sr sr@latin +sr sv ta te @@ -53,3 +51,5 @@ tr uk zh_CN zh_TW +eo +si diff --git a/po/kk.po b/po/kk.po index 507c967e0..6a00211f3 100644 --- a/po/kk.po +++ b/po/kk.po @@ -1,14 +1,15 @@ # 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-2026. +# Baurzhan Muftakhidinov , 2020. # msgid "" msgstr "" "Project-Id-Version: \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" +"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" "Last-Translator: Baurzhan Muftakhidinov \n" "Language-Team: \n" "Language: kk\n" @@ -16,199 +17,96 @@ 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.9\n" +"X-Generator: Poedit 2.3.1\n" -#: src/daemon/pipewire.c:29 +#: src/daemon/pipewire.c:43 #, 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 "PipeWire медиа жүйесін іске қосу" +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:526 +#: spa/plugins/alsa/acp/compat.c:187 +msgid "Built-in Audio" +msgstr "Құрамындағы аудио" -#: src/modules/module-fallback-sink.c:40 -msgid "Dummy Output" -msgstr "Жалған шығыс" +#: src/examples/media-session/alsa-monitor.c:530 +#: spa/plugins/alsa/acp/compat.c:192 +msgid "Modem" +msgstr "Модем" -#: src/modules/module-pulse-tunnel.c:761 -#, c-format -msgid "Tunnel for %s@%s" -msgstr "%s@%s үшін туннель" - -#: src/modules/module-zeroconf-discover.c:290 +#: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" -msgstr "Белгісіз құрылғы" +msgstr "" -#: 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 +#: src/tools/pw-cat.c:991 #, 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:1189 +#: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" -" --target Set node target serial or name (default %s)\n" +" --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" -" the rate is the one of the source file\n" -" -P --properties Set node properties\n" +" the rate is the one of the source " +"file\n" +" --list-targets List available targets for --target\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:1207 +#: src/tools/pw-cat.c:1016 #, c-format msgid "" -" --rate Sample rate (default %u)\n" -" --channels Number of channels (default %u)\n" +" --rate Sample rate (req. for rec) (default " +"%u)\n" +" --channels Number of channels (req. for rec) " +"(default %u)\n" " --channel-map Channel map\n" -" 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" +" one of: \"stereo\", " +"\"surround-51\",... or\n" +" comma separated list of channel " +"names: eg. \"FL,FR\"\n" +" --format Sample format %s (req. for rec) " +"(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" -" -q --quality Resampler quality (0 - 15) (default %d)\n" -" -a, --raw RAW mode\n" -" -M, --force-midi Force midi format, one of \"midi\" or \"ump\", (default ump)\n" -" -n, --sample-count COUNT Stop after COUNT samples\n" +" -q --quality Resampler quality (0 - 15) (default " +"%d)\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:1232 +#: src/tools/pw-cat.c:1033 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-cat.c:1837 -#, c-format -msgid "Supported containers and extensions:\n" -msgstr "Қолдау көрсетілетін контейнерлер мен кеңейтулер:\n" - -#: src/tools/pw-cli.c:2386 +#: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" @@ -216,506 +114,465 @@ 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:361 +#: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" -msgstr "Кәсіби аудио" +msgstr "" -#: spa/plugins/alsa/acp/acp.c:535 spa/plugins/alsa/acp/alsa-mixer.c:4699 -#: spa/plugins/bluez5/bluez5-device.c:2021 +#: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 +#: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Сөнд." -#: spa/plugins/alsa/acp/acp.c:618 -#, c-format -msgid "%s [ALSA UCM error]" -msgstr "%s [ALSA UCM қатесі]" +#: spa/plugins/alsa/acp/channelmap.h:466 +msgid "(invalid)" +msgstr "(жарамсыз)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2721 +#: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Кіріс" -#: spa/plugins/alsa/acp/alsa-mixer.c:2722 +#: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Док-станция кірісі" -#: spa/plugins/alsa/acp/alsa-mixer.c:2723 +#: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Док-станция микрофоны" -#: spa/plugins/alsa/acp/alsa-mixer.c:2724 +#: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Док-станцияның сызықтық кірісі" -#: spa/plugins/alsa/acp/alsa-mixer.c:2725 spa/plugins/alsa/acp/alsa-mixer.c:2816 +#: spa/plugins/alsa/acp/alsa-mixer.c:2713 +#: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" 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/alsa/acp/alsa-mixer.c:2714 +#: spa/plugins/alsa/acp/alsa-mixer.c:2798 +#: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2727 spa/plugins/alsa/acp/alsa-mixer.c:2811 +#: spa/plugins/alsa/acp/alsa-mixer.c:2715 +#: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "Алдыңғы микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2728 spa/plugins/alsa/acp/alsa-mixer.c:2812 +#: spa/plugins/alsa/acp/alsa-mixer.c:2716 +#: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "Артқы микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2729 +#: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Сыртқы микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2730 spa/plugins/alsa/acp/alsa-mixer.c:2814 +#: spa/plugins/alsa/acp/alsa-mixer.c:2718 +#: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Ішкі микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2731 spa/plugins/alsa/acp/alsa-mixer.c:2817 +#: spa/plugins/alsa/acp/alsa-mixer.c:2719 +#: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Радио" -#: spa/plugins/alsa/acp/alsa-mixer.c:2732 spa/plugins/alsa/acp/alsa-mixer.c:2818 +#: spa/plugins/alsa/acp/alsa-mixer.c:2720 +#: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Видео" -#: spa/plugins/alsa/acp/alsa-mixer.c:2733 +#: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Күшейтуді автореттеу" -#: spa/plugins/alsa/acp/alsa-mixer.c:2734 +#: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Күшейтуді автореттеу жоқ" -#: spa/plugins/alsa/acp/alsa-mixer.c:2735 +#: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Күшейту" -#: spa/plugins/alsa/acp/alsa-mixer.c:2736 +#: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Күшейту жоқ" -#: spa/plugins/alsa/acp/alsa-mixer.c:2737 +#: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Күшейткіш" -#: spa/plugins/alsa/acp/alsa-mixer.c:2738 +#: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Күшейткіш жоқ" -#: spa/plugins/alsa/acp/alsa-mixer.c:2739 +#: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "Бас күшейту" -#: spa/plugins/alsa/acp/alsa-mixer.c:2740 +#: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "Бас күшейту жоқ" -#: spa/plugins/alsa/acp/alsa-mixer.c:2741 spa/plugins/bluez5/bluez5-device.c:2428 +#: spa/plugins/alsa/acp/alsa-mixer.c:2729 +#: spa/plugins/bluez5/bluez5-device.c:1150 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/alsa/acp/alsa-mixer.c:2730 +#: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Құлаққаптар" -#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Аналогтық кіріс" -#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Док-станция микрофоны" -#: spa/plugins/alsa/acp/alsa-mixer.c:2815 +#: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "Гарнитура микрофоны" -#: spa/plugins/alsa/acp/alsa-mixer.c:2819 +#: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Аналогтық шығыс" -#: spa/plugins/alsa/acp/alsa-mixer.c:2821 +#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#, fuzzy msgid "Headphones 2" -msgstr "Құлаққап 2" +msgstr "Құлаққаптар" -#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#: spa/plugins/alsa/acp/alsa-mixer.c:2810 msgid "Headphones Mono Output" msgstr "Құлаққаптардың моно шығысы" -#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "Сызықтық шығыс" -#: spa/plugins/alsa/acp/alsa-mixer.c:2824 +#: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Аналогтық моно шығысы" -#: spa/plugins/alsa/acp/alsa-mixer.c:2825 +#: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "Динамиктер" -#: spa/plugins/alsa/acp/alsa-mixer.c:2826 +#: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" -#: spa/plugins/alsa/acp/alsa-mixer.c:2827 +#: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "Цифрлық шығыс (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2828 +#: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "Цифрлық кіріс (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2829 +#: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "Көпарналы кіріс" -#: spa/plugins/alsa/acp/alsa-mixer.c:2830 +#: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "Көпарналы шығыс" -#: spa/plugins/alsa/acp/alsa-mixer.c:2831 +#: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Game Output" msgstr "Ойын шығысы" -#: spa/plugins/alsa/acp/alsa-mixer.c:2832 spa/plugins/alsa/acp/alsa-mixer.c:2833 +#: spa/plugins/alsa/acp/alsa-mixer.c:2820 +#: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Chat Output" msgstr "Чат шығысы" -#: spa/plugins/alsa/acp/alsa-mixer.c:2834 +#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#, fuzzy msgid "Chat Input" -msgstr "Чат кірісі" +msgstr "Чат шығысы" -#: spa/plugins/alsa/acp/alsa-mixer.c:2835 +#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#, fuzzy msgid "Virtual Surround 7.1" -msgstr "Виртуалды көлемді дыбыс 7.1" +msgstr "Виртуалды көлемді аудиоқабылдағыш" -#: spa/plugins/alsa/acp/alsa-mixer.c:4522 +#: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Аналогтық моно" -#: spa/plugins/alsa/acp/alsa-mixer.c:4523 +#: spa/plugins/alsa/acp/alsa-mixer.c:4528 +#, fuzzy msgid "Analog Mono (Left)" -msgstr "Аналогты моно (Сол жақ)" +msgstr "Аналогтық моно" -#: spa/plugins/alsa/acp/alsa-mixer.c:4524 +#: spa/plugins/alsa/acp/alsa-mixer.c:4529 +#, fuzzy 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:4525 spa/plugins/alsa/acp/alsa-mixer.c:4533 -#: spa/plugins/alsa/acp/alsa-mixer.c:4534 +#: spa/plugins/alsa/acp/alsa-mixer.c:4530 +#: spa/plugins/alsa/acp/alsa-mixer.c:4538 +#: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Аналогтық стерео" -#: spa/plugins/alsa/acp/alsa-mixer.c:4526 +#: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Моно" -#: spa/plugins/alsa/acp/alsa-mixer.c:4527 +#: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" 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/alsa/acp/alsa-mixer.c:4540 +#: spa/plugins/alsa/acp/alsa-mixer.c:4698 +#: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "Гарнитура" -#: spa/plugins/alsa/acp/alsa-mixer.c:4536 spa/plugins/alsa/acp/alsa-mixer.c:4694 +#: spa/plugins/alsa/acp/alsa-mixer.c:4541 +#: spa/plugins/alsa/acp/alsa-mixer.c:4699 +#, fuzzy msgid "Speakerphone" msgstr "Динамик" -#: spa/plugins/alsa/acp/alsa-mixer.c:4537 spa/plugins/alsa/acp/alsa-mixer.c:4538 +#: spa/plugins/alsa/acp/alsa-mixer.c:4542 +#: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "Көпарналы" -#: spa/plugins/alsa/acp/alsa-mixer.c:4539 +#: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Аналогтық көлемді 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4540 +#: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Аналогтық көлемді 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4541 +#: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Аналогтық көлемді 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4542 +#: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Аналогтық көлемді 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4543 +#: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Аналогтық көлемді 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4544 +#: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Аналогтық көлемді 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4545 +#: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Аналогтық көлемді 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4546 +#: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Аналогтық көлемді 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4547 +#: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Аналогтық көлемді 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4548 +#: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Аналогтық көлемді 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4549 +#: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Аналогтық көлемді 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4550 +#: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Цифрлық стерео (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4551 +#: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Цифрлық көлемді 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4552 +#: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Цифрлық көлемді 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4553 +#: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Цифрлық көлемді 5.1 (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4554 +#: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Цифрлық стерео (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4555 +#: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "Цифрлық көлемді 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4556 +#: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" -msgstr "Чат" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4557 +#: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" -msgstr "Ойын" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4691 +#: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Аналогтық моно дуплекс" -#: spa/plugins/alsa/acp/alsa-mixer.c:4692 +#: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Аналогтық стерео дуплекс" -#: spa/plugins/alsa/acp/alsa-mixer.c:4695 +#: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Цифрлық стерео дуплекс (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4696 +#: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "Көпарналы дуплекс" -#: spa/plugins/alsa/acp/alsa-mixer.c:4697 +#: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" msgstr "Стерео дуплекс" -#: spa/plugins/alsa/acp/alsa-mixer.c:4698 +#: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" -msgstr "Моно чат + 7.1 көлемді дыбыс" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4799 +#: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "%s шығысы" -#: spa/plugins/alsa/acp/alsa-mixer.c:4807 +#: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "%s кірісі" -#: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 +#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, 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:1299 +#: spa/plugins/alsa/acp/alsa-util.c:1241 #, 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:1346 +#: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" -"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail %lu.\n" -"Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers." +"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " +"%lu.\n" +"Most likely this is a bug in the ALSA driver '%s'. Please report this issue " +"to the ALSA developers." msgstr "" -"snd_pcm_avail_delay() оғаш мәндер қайтарды: кідіріс %lu мәні қолжетімді %lu мәнінен аз.\n" -"Бұл ALSA драйверіндегі («%s») қате болуы әбден мүмкін. Бұл мәселе туралы ALSA әзірлеушілеріне хабарлаңыз." -#: spa/plugins/alsa/acp/alsa-util.c:1389 +#: spa/plugins/alsa/acp/alsa-util.c:1331 #, 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/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 +#: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" -msgstr "Аудио шлюзі (A2DP бастапқы көзі және HSP/HFP AG)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2061 -msgid "Audio Streaming for Hearing Aids (ASHA Sink)" -msgstr "Есту аппараттарына арналған аудио ағыны (ASHA қабылдағышы)" - -#: spa/plugins/bluez5/bluez5-device.c:2104 +#: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" -msgstr "Жоғары сапалы ойнату (A2DP қабылдағышы, кодек %s)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2107 +#: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" -msgstr "Жоғары сапалы дуплекс (A2DP бастапқы көзі/қабылдағышы, кодек %s)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2115 +#: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" -msgstr "Жоғары сапалы ойнату (A2DP қабылдағышы)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2117 +#: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" -msgstr "Жоғары сапалы дуплекс (A2DP бастапқы көзі/қабылдағышы)" +msgstr "" -#: 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 +#: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" -msgstr "Гарнитура (HSP/HFP, кодек %s)" +msgstr "" -#: 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:1074 +msgid "Headset Head Unit (HSP/HFP)" +msgstr "" + +#: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "Хендс-фри" -#: spa/plugins/bluez5/bluez5-device.c:2417 -msgid "Handsfree (HFP)" -msgstr "Гарнитура (HFP)" +#: spa/plugins/bluez5/bluez5-device.c:1155 +msgid "Headphone" +msgstr "Құлаққап" -#: spa/plugins/bluez5/bluez5-device.c:2440 +#: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "Портативті динамик" -#: spa/plugins/bluez5/bluez5-device.c:2446 +#: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "Автомобильдік динамик" -#: spa/plugins/bluez5/bluez5-device.c:2452 +#: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:2458 +#: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "Телефон" -#: spa/plugins/bluez5/bluez5-device.c:2465 +#: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" msgstr "Bluetooth" - -#: spa/plugins/bluez5/bluez5-device.c:2466 -msgid "Bluetooth Handsfree" -msgstr "Bluetooth гарнитурасы" - -#~ msgid "Headphone" -#~ msgstr "Құлаққап" diff --git a/po/sr.po b/po/sr.po index 9ae7491dc..247b58701 100644 --- a/po/sr.po +++ b/po/sr.po @@ -3,239 +3,111 @@ # This file is distributed under the same license as the pipewire package. # Igor Miletic (Игор Милетић) , 2009. # Miloš Komarčević , 2009, 2012. -# Марко Костић , 2026 # msgid "" msgstr "" "Project-Id-Version: pipewire\n" -"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" -"issues\n" -"POT-Creation-Date: 2026-04-09 14:32+0000\n" -"PO-Revision-Date: 2026-04-11 15:05+0200\n" -"Last-Translator: Марко Костић \n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" +"issues/new\n" +"POT-Creation-Date: 2021-04-18 16:54+0800\n" +"PO-Revision-Date: 2012-01-30 09:55+0000\n" +"Last-Translator: Miloš Komarčević \n" "Language-Team: Serbian (sr) \n" -"Language: sr\n" +"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " -"n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" -"X-Generator: Poedit 3.9\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -#: src/daemon/pipewire.c:29 +#: src/daemon/pipewire.c:43 #, 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 "Пајпвајер медијски систем" #: 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 "" -#: 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:526 +#: spa/plugins/alsa/acp/compat.c:187 +msgid "Built-in Audio" +msgstr "Унутрашњи звук" -#: src/modules/module-fallback-sink.c:40 -msgid "Dummy Output" -msgstr "Лажан излаз" +#: src/examples/media-session/alsa-monitor.c:530 +#: spa/plugins/alsa/acp/compat.c:192 +msgid "Modem" +msgstr "Модем" -#: src/modules/module-pulse-tunnel.c:761 -#, c-format -msgid "Tunnel for %s@%s" -msgstr "Тунел за %s@%s" - -#: src/modules/module-zeroconf-discover.c:290 +#: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" -msgstr "Непознати уређај" +msgstr "" -#: 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:1183 +#: src/tools/pw-cat.c:991 #, 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:1190 +#: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" -" --target Set node target serial or name " -"(default %s)\n" +" --target Set node target (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" " the rate is the one of the source " "file\n" -" -P --properties Set node properties\n" +" --list-targets List available targets for --target\n" "\n" msgstr "" -" -R, --remote Назив удаљеног услужног програма\n" -" --media-type Постави врсту медија (подразумевано " -"%s)\n" -" --media-category Постави категорију медија " -"(подразумевано %s)\n" -" --media-role Постави улогу медија (подразумевано " -"%s)\n" -" --target Постави циљни серијски број или " -"назив чвора (подразумевано %s)\n" -" 0 значи без повезивања\n" -" -C --monitor Хватај прикључнике за надзор (у " -"режиму снимања)\n" -" --latency Постави латентност чвора " -"(подразумевано %s)\n" -" Xјединица (јединица = s, ms, us, " -"ns)\n" -" или директни узорци (256)\n" -" проток је онај из изворне " -"датотеке\n" -" -P --properties Постави својства чвора\n" -"\n" -#: src/tools/pw-cat.c:1209 +#: src/tools/pw-cat.c:1016 #, c-format msgid "" -" --rate Sample rate (default %u)\n" -" --channels Number of channels (default %u)\n" +" --rate Sample rate (req. for rec) (default " +"%u)\n" +" --channels Number of channels (req. for rec) " +"(default %u)\n" " --channel-map Channel map\n" -" a channel layout: \"Stereo\", " -"\"5.1\",... or\n" +" one of: \"stereo\", " +"\"surround-51\",... 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" +" --format Sample format %s (req. for rec) " +"(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" -" -a, --raw RAW mode\n" -" -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“ или „ump“, (подразумевано ump)\n" -" -n, --sample-count БРОЈ Заустави након БРОЈ узорака\n" -"\n" -#: src/tools/pw-cat.c:1234 +#: src/tools/pw-cat.c:1033 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 МИДИ режим\n" -" -d, --dsd DSD режим\n" -" -o, --encoded Кодирани режим\n" -" -s, --sysex SysEx режим\n" -" -c, --midi-clip Режим МИДИ одсечка\n" -"\n" -#: src/tools/pw-cat.c:1839 -#, c-format -msgid "Supported containers and extensions:\n" -msgstr "Подржани контејнери и проширења:\n" - -#: src/tools/pw-cli.c:2386 +#: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" @@ -243,365 +115,376 @@ 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:361 +#: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" -msgstr "Професионални звук" +msgstr "" -#: spa/plugins/alsa/acp/acp.c:535 spa/plugins/alsa/acp/alsa-mixer.c:4699 -#: spa/plugins/bluez5/bluez5-device.c:2163 +#: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 +#: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Искључено" -#: spa/plugins/alsa/acp/acp.c:618 -#, c-format -msgid "%s [ALSA UCM error]" -msgstr "%s [ALSA UCM грешка]" +#: spa/plugins/alsa/acp/channelmap.h:466 +msgid "(invalid)" +msgstr "(неисправно)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2721 +#: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Улаз" -#: spa/plugins/alsa/acp/alsa-mixer.c:2722 +#: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Улаз прикључне станице" -#: spa/plugins/alsa/acp/alsa-mixer.c:2723 +#: spa/plugins/alsa/acp/alsa-mixer.c:2711 +#, fuzzy msgid "Docking Station Microphone" msgstr "Микрофон прикључне станице" -#: spa/plugins/alsa/acp/alsa-mixer.c:2724 +#: spa/plugins/alsa/acp/alsa-mixer.c:2712 +#, fuzzy msgid "Docking Station Line In" msgstr "Улаз прикључне станице" -#: spa/plugins/alsa/acp/alsa-mixer.c:2725 -#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#: spa/plugins/alsa/acp/alsa-mixer.c:2713 +#: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Линија у" -#: spa/plugins/alsa/acp/alsa-mixer.c:2726 -#: spa/plugins/alsa/acp/alsa-mixer.c:2810 -#: spa/plugins/bluez5/bluez5-device.c:2596 +#: spa/plugins/alsa/acp/alsa-mixer.c:2714 +#: spa/plugins/alsa/acp/alsa-mixer.c:2798 +#: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2727 -#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#: spa/plugins/alsa/acp/alsa-mixer.c:2715 +#: spa/plugins/alsa/acp/alsa-mixer.c:2799 +#, fuzzy msgid "Front Microphone" -msgstr "Предњи микрофон" +msgstr "Микрофон прикључне станице" -#: spa/plugins/alsa/acp/alsa-mixer.c:2728 -#: spa/plugins/alsa/acp/alsa-mixer.c:2812 +#: spa/plugins/alsa/acp/alsa-mixer.c:2716 +#: spa/plugins/alsa/acp/alsa-mixer.c:2800 +#, fuzzy msgid "Rear Microphone" -msgstr "Задњи микрофон" +msgstr "Микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2729 +#: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Спољни микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2730 -#: spa/plugins/alsa/acp/alsa-mixer.c:2814 +#: spa/plugins/alsa/acp/alsa-mixer.c:2718 +#: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Унутрашњи микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2731 -#: spa/plugins/alsa/acp/alsa-mixer.c:2817 +#: spa/plugins/alsa/acp/alsa-mixer.c:2719 +#: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Радио" -#: spa/plugins/alsa/acp/alsa-mixer.c:2732 -#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#: spa/plugins/alsa/acp/alsa-mixer.c:2720 +#: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Видео" -#: spa/plugins/alsa/acp/alsa-mixer.c:2733 +#: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Самостална контрола појачања" -#: spa/plugins/alsa/acp/alsa-mixer.c:2734 +#: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Без самосталне контроле појачања" -#: spa/plugins/alsa/acp/alsa-mixer.c:2735 +#: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Подизање" -#: spa/plugins/alsa/acp/alsa-mixer.c:2736 +#: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Без подизања" -#: spa/plugins/alsa/acp/alsa-mixer.c:2737 +#: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Појачало" -#: spa/plugins/alsa/acp/alsa-mixer.c:2738 +#: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Без појачала" -#: spa/plugins/alsa/acp/alsa-mixer.c:2739 +#: spa/plugins/alsa/acp/alsa-mixer.c:2727 +#, fuzzy msgid "Bass Boost" -msgstr "Појачање баса" +msgstr "Подизање" -#: spa/plugins/alsa/acp/alsa-mixer.c:2740 +#: spa/plugins/alsa/acp/alsa-mixer.c:2728 +#, fuzzy msgid "No Bass Boost" -msgstr "Без појачања баса" +msgstr "Без подизања" -#: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:2602 +#: spa/plugins/alsa/acp/alsa-mixer.c:2729 +#: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" -msgstr "Звучник" +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:2608 -#: spa/plugins/bluez5/bluez5-device.c:2675 +#: spa/plugins/alsa/acp/alsa-mixer.c:2730 +#: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Аналогне слушалице" -#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Аналогни улаз" -#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Микрофон прикључне станице" -#: spa/plugins/alsa/acp/alsa-mixer.c:2815 +#: spa/plugins/alsa/acp/alsa-mixer.c:2803 +#, fuzzy msgid "Headset Microphone" -msgstr "Микрофон са слушалицама" +msgstr "Микрофон" -#: spa/plugins/alsa/acp/alsa-mixer.c:2819 +#: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Аналогни излаз" -#: spa/plugins/alsa/acp/alsa-mixer.c:2821 +#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#, fuzzy msgid "Headphones 2" -msgstr "Слушалице 2" +msgstr "Аналогне слушалице" -#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#: spa/plugins/alsa/acp/alsa-mixer.c:2810 +#, fuzzy msgid "Headphones Mono Output" -msgstr "Моно излаз за слушалице" +msgstr "Аналогни моно излаз" -#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#, fuzzy msgid "Line Out" -msgstr "Линијски излаз" +msgstr "Линија у" -#: spa/plugins/alsa/acp/alsa-mixer.c:2824 +#: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Аналогни моно излаз" -#: spa/plugins/alsa/acp/alsa-mixer.c:2825 +#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#, fuzzy msgid "Speakers" -msgstr "Говорници" +msgstr "Аналогни стерео" -#: spa/plugins/alsa/acp/alsa-mixer.c:2826 +#: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" -msgstr "ХДМИ / Дисплеј-порт" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:2827 +#: spa/plugins/alsa/acp/alsa-mixer.c:2815 +#, fuzzy msgid "Digital Output (S/PDIF)" -msgstr "Дигитални излаз (С/ПДИФ)" +msgstr "Дигитални стерео (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2828 +#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#, fuzzy msgid "Digital Input (S/PDIF)" -msgstr "Дигитални улаз (С/ПДИФ)" +msgstr "Дигитални стерео (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2829 +#: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" -msgstr "Вишеканални улаз" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:2830 +#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#, fuzzy msgid "Multichannel Output" -msgstr "Вишеканални излаз" +msgstr "Празан излаз" -#: spa/plugins/alsa/acp/alsa-mixer.c:2831 +#: spa/plugins/alsa/acp/alsa-mixer.c:2819 +#, fuzzy msgid "Game Output" -msgstr "Излаз за игре" +msgstr "Празан излаз" -#: spa/plugins/alsa/acp/alsa-mixer.c:2832 -#: spa/plugins/alsa/acp/alsa-mixer.c:2833 +#: spa/plugins/alsa/acp/alsa-mixer.c:2820 +#: spa/plugins/alsa/acp/alsa-mixer.c:2821 +#, fuzzy msgid "Chat Output" -msgstr "Излаз за ћаскање" +msgstr "Празан излаз" -#: spa/plugins/alsa/acp/alsa-mixer.c:2834 +#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#, fuzzy msgid "Chat Input" -msgstr "Улаз за ћаскање" +msgstr "Улаз" -#: spa/plugins/alsa/acp/alsa-mixer.c:2835 +#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#, fuzzy msgid "Virtual Surround 7.1" -msgstr "Виртуелни окружујући звук 7.1" +msgstr "Аналогни окружујући 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4522 +#: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Аналогни моно" -#: spa/plugins/alsa/acp/alsa-mixer.c:4523 +#: spa/plugins/alsa/acp/alsa-mixer.c:4528 +#, fuzzy msgid "Analog Mono (Left)" -msgstr "Аналогни моно (леви)" +msgstr "Аналогни моно" -#: spa/plugins/alsa/acp/alsa-mixer.c:4524 +#: spa/plugins/alsa/acp/alsa-mixer.c:4529 +#, fuzzy 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:4525 -#: spa/plugins/alsa/acp/alsa-mixer.c:4533 -#: spa/plugins/alsa/acp/alsa-mixer.c:4534 +#: spa/plugins/alsa/acp/alsa-mixer.c:4530 +#: spa/plugins/alsa/acp/alsa-mixer.c:4538 +#: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Аналогни стерео" -#: spa/plugins/alsa/acp/alsa-mixer.c:4526 +#: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Моно" -#: spa/plugins/alsa/acp/alsa-mixer.c:4527 +#: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Стерео" -#: spa/plugins/alsa/acp/alsa-mixer.c:4535 -#: spa/plugins/alsa/acp/alsa-mixer.c:4693 -#: spa/plugins/bluez5/bluez5-device.c:2584 +#: spa/plugins/alsa/acp/alsa-mixer.c:4540 +#: spa/plugins/alsa/acp/alsa-mixer.c:4698 +#: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" -msgstr "Слушалице са микрофоном" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4536 -#: spa/plugins/alsa/acp/alsa-mixer.c:4694 +#: spa/plugins/alsa/acp/alsa-mixer.c:4541 +#: spa/plugins/alsa/acp/alsa-mixer.c:4699 +#, fuzzy msgid "Speakerphone" -msgstr "Спикерфон" +msgstr "Аналогни стерео" -#: spa/plugins/alsa/acp/alsa-mixer.c:4537 -#: spa/plugins/alsa/acp/alsa-mixer.c:4538 +#: spa/plugins/alsa/acp/alsa-mixer.c:4542 +#: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" -msgstr "Вишеканални" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4539 +#: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Аналогни окружујући 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4540 +#: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Аналогни окружујући 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4541 +#: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Аналогни окружујући 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4542 +#: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Аналогни окружујући 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4543 +#: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Аналогни окружујући 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4544 +#: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Аналогни окружујући 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4545 +#: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Аналогни окружујући 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4546 +#: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Аналогни окружујући 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4547 +#: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Аналогни окружујући 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4548 +#: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Аналогни окружујући 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4549 +#: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Аналогни окружујући 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4550 +#: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Дигитални стерео (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4551 +#: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Дигитални окружујући 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4552 +#: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Дигитални окружујући 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4553 +#: spa/plugins/alsa/acp/alsa-mixer.c:4558 +#, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" -msgstr "Дигитални окружујући 5.1 (IEC958/DTS)" +msgstr "Дигитални окружујући 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4554 +#: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Дигитални стерео (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4555 +#: spa/plugins/alsa/acp/alsa-mixer.c:4560 +#, fuzzy msgid "Digital Surround 5.1 (HDMI)" -msgstr "Дигитални окружујући 5.1 (HDMI)" +msgstr "Дигитални окружујући 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4556 +#: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" -msgstr "Ћаскање" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4557 +#: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" -msgstr "Игрица" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4691 +#: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Двосмерни аналогни моно" -#: spa/plugins/alsa/acp/alsa-mixer.c:4692 +#: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Двосмерни аналогни стерео" -#: spa/plugins/alsa/acp/alsa-mixer.c:4695 +#: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Двосмерни дигитални стерео (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4696 +#: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" -msgstr "Вишеканални дуплекс" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4697 +#: spa/plugins/alsa/acp/alsa-mixer.c:4702 +#, fuzzy msgid "Stereo Duplex" -msgstr "Стерео дуплекс" +msgstr "Двосмерни аналогни стерео" -#: spa/plugins/alsa/acp/alsa-mixer.c:4698 +#: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" -msgstr "Моно ћаскање + 7.1 окружујући" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4799 -#, c-format +#: spa/plugins/alsa/acp/alsa-mixer.c:4806 +#, fuzzy, c-format msgid "%s Output" -msgstr "%s излаз" +msgstr "Празан излаз" -#: spa/plugins/alsa/acp/alsa-mixer.c:4807 -#, c-format +#: spa/plugins/alsa/acp/alsa-mixer.c:4813 +#, fuzzy, c-format msgid "%s Input" -msgstr "%s улаз" +msgstr "Улаз" -#: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 -#, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 +#, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" @@ -613,9 +496,9 @@ msgid_plural "" "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 " +"snd_pcm_avail() је вратио вредност која је необично велика: %lu бајтова (%lu " "ms).\n" -"Највероватније је ово грешка у ALSA управљачком програму „%s“. Пријавите " +"Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." msgstr[1] "" "snd_pcm_avail() је вратио вредност која је необично велика: %lu бајтова (%lu " @@ -628,49 +511,49 @@ msgstr[2] "" "Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." -#: spa/plugins/alsa/acp/alsa-util.c:1299 -#, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1241 +#, fuzzy, c-format msgid "" -"snd_pcm_delay() returned a value that is exceptionally large: %li byte " -"(%s%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s" +"%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgid_plural "" -"snd_pcm_delay() returned a value that is exceptionally large: %li bytes " -"(%s%lu ms).\n" +"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s" +"%lu ms).\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" -"snd_pcm_delay() је вратио вредност која је изузетно велика: %li бајт (%s%lu " -"ms).\n" -"Највероватније је ово грешка у ALSA управљачком програму „%s“. Пријавите " +"snd_pcm_delay() је вратио вредност која је необично велика: %li бајтова (%s" +"%lu ms).\n" +"Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." msgstr[1] "" -"snd_pcm_delay() је вратио вредност која је необично велика: %li бајтова " -"(%s%lu ms).\n" +"snd_pcm_delay() је вратио вредност која је необично велика: %li бајтова (%s" +"%lu ms).\n" "Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." msgstr[2] "" -"snd_pcm_delay() је вратио вредност која је необично велика: %li бајтова " -"(%s%lu ms).\n" +"snd_pcm_delay() је вратио вредност која је необично велика: %li бајтова (%s" +"%lu ms).\n" "Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." -#: spa/plugins/alsa/acp/alsa-util.c:1346 -#, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1288 +#, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" -"snd_pcm_avail_delay() је вратио чудне вредности: кашњење %lu је мање од " -"доступног %lu.\n" -"Највероватније је ово грешка у ALSA управљачком програму „%s“. Пријавите " +"snd_pcm_avail() је вратио вредност која је необично велика: %lu бајтова (%lu " +"ms).\n" +"Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." -#: spa/plugins/alsa/acp/alsa-util.c:1389 -#, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1331 +#, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" @@ -682,9 +565,9 @@ msgid_plural "" "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 ms).\n" -"Највероватније је ово грешка у ALSA управљачком програму „%s“. Пријавите " +"snd_pcm_mmap_begin() је вратио вредност која је необично велика: %lu " +"бајтова (%lu ms).\n" +"Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." msgstr[1] "" "snd_pcm_mmap_begin() је вратио вредност која је необично велика: %lu " @@ -697,120 +580,62 @@ msgstr[2] "" "Ово је највероватније грешка у „%s“ ALSA управљачком програму. Пријавите " "овај проблем ALSA програмерима." -#: 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:2174 +#: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" -msgstr "Звучни мрежни пролаз (А2ДП извор и HSP/HFP AG)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2203 -msgid "Audio Streaming for Hearing Aids (ASHA Sink)" -msgstr "Струјање звука за слушне апарате (ASHA сливник)" - -#: spa/plugins/bluez5/bluez5-device.c:2246 +#: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" -msgstr "Пуштање високе верности (А2ДП сливник, кодек %s)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2249 +#: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" -msgstr "Двосмерно високе верности (А2ДП извор/сливник, кодек %s)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2257 +#: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" -msgstr "Пуштање високе верности (А2ДП сливник)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2259 +#: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" -msgstr "Двосмерно високе верности (А2ДП извор/сливник)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2281 -msgid "Auto: Prefer Quality (A2DP)" -msgstr "Ауто: предност квалитету (А2ДП)" - -#: spa/plugins/bluez5/bluez5-device.c:2286 -msgid "Auto: Prefer Latency (A2DP)" -msgstr "Ауто: предност кашњењу (А2ДП)" - -#: spa/plugins/bluez5/bluez5-device.c:2366 -#, c-format -msgid "High Fidelity Playback (BAP Sink, codec %s)" -msgstr "Пуштање високе верности (БАП сливник, кодек %s)" - -#: spa/plugins/bluez5/bluez5-device.c:2371 -#, c-format -msgid "High Fidelity Input (BAP Source, codec %s)" -msgstr "Улаз високе верности (БАП извор, кодек %s)" - -#: spa/plugins/bluez5/bluez5-device.c:2375 -#, c-format -msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" -msgstr "Двосмерно високе верности (БАП извор/сливник, кодек %s)" - -#: spa/plugins/bluez5/bluez5-device.c:2384 -msgid "High Fidelity Playback (BAP Sink)" -msgstr "Пуштање високе верности (БАП сливник)" - -#: spa/plugins/bluez5/bluez5-device.c:2388 -msgid "High Fidelity Input (BAP Source)" -msgstr "Улаз високе верности (БАП извор)" - -#: spa/plugins/bluez5/bluez5-device.c:2391 -msgid "High Fidelity Duplex (BAP Source/Sink)" -msgstr "Двосмерно високе верности (БАП извор/сливник)" - -#: spa/plugins/bluez5/bluez5-device.c:2431 +#: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" -msgstr "Слушалице са микрофоном (ХСП/ХФП, кодек %s)" +msgstr "" -#: 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 +#: spa/plugins/bluez5/bluez5-device.c:1074 +msgid "Headset Head Unit (HSP/HFP)" +msgstr "" + +#: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" -msgstr "Хендсфри" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2591 -msgid "Handsfree (HFP)" -msgstr "Хендсфри (ХФП)" +#: spa/plugins/bluez5/bluez5-device.c:1155 +#, fuzzy +msgid "Headphone" +msgstr "Аналогне слушалице" -#: spa/plugins/bluez5/bluez5-device.c:2614 +#: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" -msgstr "Преносно" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2620 +#: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" -msgstr "Кола" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2626 +#: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" -msgstr "Хи-фи" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2632 +#: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" -msgstr "Телефон" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2639 +#: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" -msgstr "Блутут" - -#: spa/plugins/bluez5/bluez5-device.c:2640 -msgid "Bluetooth Handsfree" -msgstr "Блутут хендсфри" +msgstr "" diff --git a/po/sr@latin.po b/po/sr@latin.po index bc7572621..6f56d8608 100644 --- a/po/sr@latin.po +++ b/po/sr@latin.po @@ -1,241 +1,113 @@ -# Serbian translations for pipewire +# Serbian(Latin) translations for pipewire # Copyright (C) 2006 Lennart Poettering # This file is distributed under the same license as the pipewire package. # Igor Miletic (Igor Miletić) , 2009. # Miloš Komarčević , 2009, 2012. -# Marko Kostić , 2026 # msgid "" msgstr "" "Project-Id-Version: pipewire\n" -"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" -"issues\n" -"POT-Creation-Date: 2026-04-09 14:32+0000\n" -"PO-Revision-Date: 2026-04-11 15:05+0200\n" -"Last-Translator: Marko Kostić \n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/" +"issues/new\n" +"POT-Creation-Date: 2021-04-18 16:54+0800\n" +"PO-Revision-Date: 2012-01-30 09:55+0000\n" +"Last-Translator: Miloš Komarčević \n" "Language-Team: Serbian (sr) \n" -"Language: sr\n" +"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " -"n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" -"X-Generator: Poedit 3.9\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" -#: src/daemon/pipewire.c:29 +#: src/daemon/pipewire.c:43 #, 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 [opcije]\n" -" -h, --help Prikaži ovu pomoć\n" -" -v, --verbose Povećaj opširnost za jedan nivo\n" -" --version Prikaži izdanje\n" -" -c, --config Učitaj podešavanja (podrazumevano " -"%s)\n" -" -P --properties Postavi svojstva konteksta\n" - -#: src/daemon/pipewire.desktop.in:3 -msgid "PipeWire Media System" -msgstr "Pajpvajer medijski sistem" #: src/daemon/pipewire.desktop.in:4 +msgid "PipeWire Media System" +msgstr "" + +#: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" -msgstr "Pokreni Pajpvajer medijski sistem" +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 "Tunel do %s%s%s" +#: src/examples/media-session/alsa-monitor.c:526 +#: spa/plugins/alsa/acp/compat.c:187 +msgid "Built-in Audio" +msgstr "Unutrašnji zvuk" -#: src/modules/module-fallback-sink.c:40 -msgid "Dummy Output" -msgstr "Lažan izlaz" +#: src/examples/media-session/alsa-monitor.c:530 +#: spa/plugins/alsa/acp/compat.c:192 +msgid "Modem" +msgstr "Modem" -#: src/modules/module-pulse-tunnel.c:761 -#, c-format -msgid "Tunnel for %s@%s" -msgstr "Tunel za %s@%s" - -#: src/modules/module-zeroconf-discover.c:290 +#: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" -msgstr "Nepoznati uređaj" +msgstr "" -#: src/modules/module-zeroconf-discover.c:302 -#, c-format -msgid "%s on %s@%s" -msgstr "%s na %s@%s" - -#: src/modules/module-zeroconf-discover.c:306 -#, c-format -msgid "%s on %s" -msgstr "%s na %s" - -#: src/tools/pw-cat.c:269 -#, c-format -msgid "Supported formats:\n" -msgstr "Podržani formati:\n" - -#: src/tools/pw-cat.c:754 -#, c-format -msgid "Supported channel layouts:\n" -msgstr "Podržani rasporedi kanala:\n" - -#: src/tools/pw-cat.c:764 -#, c-format -msgid "Supported channel layout aliases:\n" -msgstr "Podržani nadimci rasporeda kanala:\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 "Podržani nazivi kanala:\n" - -#: src/tools/pw-cat.c:1183 +#: src/tools/pw-cat.c:991 #, 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 [opcije] [|-]\n" -" -h, --help Prikaži ovu pomoć\n" -" --version Prikaži izdanje\n" -" -v, --verbose Omogući opširan rad\n" -"\n" -#: src/tools/pw-cat.c:1190 +#: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" -" --target Set node target serial or name " -"(default %s)\n" +" --target Set node target (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" " the rate is the one of the source " "file\n" -" -P --properties Set node properties\n" +" --list-targets List available targets for --target\n" "\n" msgstr "" -" -R, --remote Naziv udaljenog uslužnog programa\n" -" --media-type Postavi vrstu medija (podrazumevano " -"%s)\n" -" --media-category Postavi kategoriju medija " -"(podrazumevano %s)\n" -" --media-role Postavi ulogu medija (podrazumevano " -"%s)\n" -" --target Postavi ciljni serijski broj ili " -"naziv čvora (podrazumevano %s)\n" -" 0 znači bez povezivanja\n" -" -C --monitor Hvataj priključnike za nadzor (u " -"režimu snimanja)\n" -" --latency Postavi latentnost čvora " -"(podrazumevano %s)\n" -" Xjedinica (jedinica = s, ms, us, " -"ns)\n" -" ili direktni uzorci (256)\n" -" protok je onaj iz izvorne " -"datoteke\n" -" -P --properties Postavi svojstva čvora\n" -"\n" -#: src/tools/pw-cat.c:1209 +#: src/tools/pw-cat.c:1016 #, c-format msgid "" -" --rate Sample rate (default %u)\n" -" --channels Number of channels (default %u)\n" +" --rate Sample rate (req. for rec) (default " +"%u)\n" +" --channels Number of channels (req. for rec) " +"(default %u)\n" " --channel-map Channel map\n" -" a channel layout: \"Stereo\", " -"\"5.1\",... or\n" +" one of: \"stereo\", " +"\"surround-51\",... 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" +" --format Sample format %s (req. for rec) " +"(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" -" -a, --raw RAW mode\n" -" -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čestalost uzorkovanja (podrazumevano " -"%u)\n" -" --channels Broj kanala (podrazumevano %u)\n" -" --channel-map Mapa kanala\n" -" raspored kanala: „Stereo“, " -"„5.1“,... ili\n" -" zarezom razdvojen spisak naziva " -"kanala: npr. „FL,FR“\n" -" --list-layouts Ispiši podržane rasporede kanala\n" -" --list-channel-names Ispiši podržane mape kanala\n" -" --format Format uzorka (podrazumevano %s)\n" -" --list-formats Ispiši podržane formate uzoraka\n" -" --container Format kontejnera\n" -" --list-containers Ispiši podržane kontejnere i " -"proširenja\n" -" --volume Jačina toka 0-1.0 (podrazumevano " -"%.3f)\n" -" -q --quality Kvalitet promene uzorkovanja (0 - 15) " -"(podrazumevano %d)\n" -" -a, --raw Sirovi (RAW) režim\n" -" -M, --force-midi Primoraj MIDI format, jedan od " -"„midi“ ili „ump“, (podrazumevano ump)\n" -" -n, --sample-count BROJ Zaustavi nakon BROJ uzoraka\n" -"\n" -#: src/tools/pw-cat.c:1234 +#: src/tools/pw-cat.c:1033 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 Režim puštanja\n" -" -r, --record Režim snimanja\n" -" -m, --midi MIDI režim\n" -" -d, --dsd DSD režim\n" -" -o, --encoded Kodirani režim\n" -" -s, --sysex SysEx režim\n" -" -c, --midi-clip Režim MIDI odsečka\n" -"\n" -#: src/tools/pw-cat.c:1839 -#, c-format -msgid "Supported containers and extensions:\n" -msgstr "Podržani kontejneri i proširenja:\n" - -#: src/tools/pw-cli.c:2386 +#: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" @@ -243,365 +115,376 @@ 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 [opcije] [naredba]\n" -" -h, --help Prikaži ovu pomoć\n" -" --version Prikaži izdanje\n" -" -d, --daemon Pokreni kao uslužni program " -"(podrazumevano netačno)\n" -" -r, --remote Naziv udaljenog uslužnog programa\n" -" -m, --monitor Nadgledaj aktivnost\n" -"\n" -#: spa/plugins/alsa/acp/acp.c:361 +#: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" -msgstr "Profesionalni zvuk" +msgstr "" -#: spa/plugins/alsa/acp/acp.c:535 spa/plugins/alsa/acp/alsa-mixer.c:4699 -#: spa/plugins/bluez5/bluez5-device.c:2163 +#: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 +#: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "Isključeno" -#: spa/plugins/alsa/acp/acp.c:618 -#, c-format -msgid "%s [ALSA UCM error]" -msgstr "%s [ALSA UCM greška]" +#: spa/plugins/alsa/acp/channelmap.h:466 +msgid "(invalid)" +msgstr "(neispravno)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2721 +#: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "Ulaz" -#: spa/plugins/alsa/acp/alsa-mixer.c:2722 +#: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Ulaz priključne stanice" -#: spa/plugins/alsa/acp/alsa-mixer.c:2723 +#: spa/plugins/alsa/acp/alsa-mixer.c:2711 +#, fuzzy msgid "Docking Station Microphone" msgstr "Mikrofon priključne stanice" -#: spa/plugins/alsa/acp/alsa-mixer.c:2724 +#: spa/plugins/alsa/acp/alsa-mixer.c:2712 +#, fuzzy msgid "Docking Station Line In" msgstr "Ulaz priključne stanice" -#: spa/plugins/alsa/acp/alsa-mixer.c:2725 -#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#: spa/plugins/alsa/acp/alsa-mixer.c:2713 +#: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" msgstr "Linija u" -#: spa/plugins/alsa/acp/alsa-mixer.c:2726 -#: spa/plugins/alsa/acp/alsa-mixer.c:2810 -#: spa/plugins/bluez5/bluez5-device.c:2596 +#: spa/plugins/alsa/acp/alsa-mixer.c:2714 +#: spa/plugins/alsa/acp/alsa-mixer.c:2798 +#: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "Mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2727 -#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#: spa/plugins/alsa/acp/alsa-mixer.c:2715 +#: spa/plugins/alsa/acp/alsa-mixer.c:2799 +#, fuzzy msgid "Front Microphone" -msgstr "Prednji mikrofon" +msgstr "Mikrofon priključne stanice" -#: spa/plugins/alsa/acp/alsa-mixer.c:2728 -#: spa/plugins/alsa/acp/alsa-mixer.c:2812 +#: spa/plugins/alsa/acp/alsa-mixer.c:2716 +#: spa/plugins/alsa/acp/alsa-mixer.c:2800 +#, fuzzy msgid "Rear Microphone" -msgstr "Zadnji mikrofon" +msgstr "Mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2729 +#: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "Spoljni mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2730 -#: spa/plugins/alsa/acp/alsa-mixer.c:2814 +#: spa/plugins/alsa/acp/alsa-mixer.c:2718 +#: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "Unutrašnji mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2731 -#: spa/plugins/alsa/acp/alsa-mixer.c:2817 +#: spa/plugins/alsa/acp/alsa-mixer.c:2719 +#: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "Radio" -#: spa/plugins/alsa/acp/alsa-mixer.c:2732 -#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#: spa/plugins/alsa/acp/alsa-mixer.c:2720 +#: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "Video" -#: spa/plugins/alsa/acp/alsa-mixer.c:2733 +#: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "Samostalna kontrola pojačanja" -#: spa/plugins/alsa/acp/alsa-mixer.c:2734 +#: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "Bez samostalne kontrole pojačanja" -#: spa/plugins/alsa/acp/alsa-mixer.c:2735 +#: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "Podizanje" -#: spa/plugins/alsa/acp/alsa-mixer.c:2736 +#: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "Bez podizanja" -#: spa/plugins/alsa/acp/alsa-mixer.c:2737 +#: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "Pojačalo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2738 +#: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "Bez pojačala" -#: spa/plugins/alsa/acp/alsa-mixer.c:2739 +#: spa/plugins/alsa/acp/alsa-mixer.c:2727 +#, fuzzy msgid "Bass Boost" -msgstr "Pojačanje basa" +msgstr "Podizanje" -#: spa/plugins/alsa/acp/alsa-mixer.c:2740 +#: spa/plugins/alsa/acp/alsa-mixer.c:2728 +#, fuzzy msgid "No Bass Boost" -msgstr "Bez pojačanja basa" +msgstr "Bez podizanja" -#: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:2602 +#: spa/plugins/alsa/acp/alsa-mixer.c:2729 +#: spa/plugins/bluez5/bluez5-device.c:1150 msgid "Speaker" -msgstr "Zvučnik" +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:2608 -#: spa/plugins/bluez5/bluez5-device.c:2675 +#: spa/plugins/alsa/acp/alsa-mixer.c:2730 +#: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "Analogne slušalice" -#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "Analogni ulaz" -#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "Mikrofon priključne stanice" -#: spa/plugins/alsa/acp/alsa-mixer.c:2815 +#: spa/plugins/alsa/acp/alsa-mixer.c:2803 +#, fuzzy msgid "Headset Microphone" -msgstr "Mikrofon sa slušalicama" +msgstr "Mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2819 +#: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "Analogni izlaz" -#: spa/plugins/alsa/acp/alsa-mixer.c:2821 +#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#, fuzzy msgid "Headphones 2" -msgstr "Slušalice 2" +msgstr "Analogne slušalice" -#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#: spa/plugins/alsa/acp/alsa-mixer.c:2810 +#, fuzzy msgid "Headphones Mono Output" -msgstr "Mono izlaz za slušalice" +msgstr "Analogni mono izlaz" -#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#, fuzzy msgid "Line Out" -msgstr "Linijski izlaz" +msgstr "Linija u" -#: spa/plugins/alsa/acp/alsa-mixer.c:2824 +#: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "Analogni mono izlaz" -#: spa/plugins/alsa/acp/alsa-mixer.c:2825 +#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#, fuzzy msgid "Speakers" -msgstr "Govornici" +msgstr "Analogni stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:2826 +#: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" -msgstr "HDMI / Displej-port" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:2827 +#: spa/plugins/alsa/acp/alsa-mixer.c:2815 +#, fuzzy msgid "Digital Output (S/PDIF)" -msgstr "Digitalni izlaz (S/PDIF)" +msgstr "Digitalni stereo (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2828 +#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#, fuzzy msgid "Digital Input (S/PDIF)" -msgstr "Digitalni ulaz (S/PDIF)" +msgstr "Digitalni stereo (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2829 +#: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" -msgstr "Višekanalni ulaz" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:2830 +#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#, fuzzy msgid "Multichannel Output" -msgstr "Višekanalni izlaz" +msgstr "Prazan izlaz" -#: spa/plugins/alsa/acp/alsa-mixer.c:2831 +#: spa/plugins/alsa/acp/alsa-mixer.c:2819 +#, fuzzy msgid "Game Output" -msgstr "Izlaz za igre" +msgstr "Prazan izlaz" -#: spa/plugins/alsa/acp/alsa-mixer.c:2832 -#: spa/plugins/alsa/acp/alsa-mixer.c:2833 +#: spa/plugins/alsa/acp/alsa-mixer.c:2820 +#: spa/plugins/alsa/acp/alsa-mixer.c:2821 +#, fuzzy msgid "Chat Output" -msgstr "Izlaz za ćaskanje" +msgstr "Prazan izlaz" -#: spa/plugins/alsa/acp/alsa-mixer.c:2834 +#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#, fuzzy msgid "Chat Input" -msgstr "Ulaz za ćaskanje" +msgstr "Ulaz" -#: spa/plugins/alsa/acp/alsa-mixer.c:2835 +#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#, fuzzy msgid "Virtual Surround 7.1" -msgstr "Virtuelni okružujući zvuk 7.1" +msgstr "Analogni okružujući 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4522 +#: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "Analogni mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4523 +#: spa/plugins/alsa/acp/alsa-mixer.c:4528 +#, fuzzy msgid "Analog Mono (Left)" -msgstr "Analogni mono (levi)" +msgstr "Analogni mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4524 +#: spa/plugins/alsa/acp/alsa-mixer.c:4529 +#, fuzzy msgid "Analog Mono (Right)" -msgstr "Analogni mono (desni)" +msgstr "Analogni mono" #. 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:4525 -#: spa/plugins/alsa/acp/alsa-mixer.c:4533 -#: spa/plugins/alsa/acp/alsa-mixer.c:4534 +#: spa/plugins/alsa/acp/alsa-mixer.c:4530 +#: spa/plugins/alsa/acp/alsa-mixer.c:4538 +#: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "Analogni stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4526 +#: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "Mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4527 +#: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" msgstr "Stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4535 -#: spa/plugins/alsa/acp/alsa-mixer.c:4693 -#: spa/plugins/bluez5/bluez5-device.c:2584 +#: spa/plugins/alsa/acp/alsa-mixer.c:4540 +#: spa/plugins/alsa/acp/alsa-mixer.c:4698 +#: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" -msgstr "Slušalice sa mikrofonom" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4536 -#: spa/plugins/alsa/acp/alsa-mixer.c:4694 +#: spa/plugins/alsa/acp/alsa-mixer.c:4541 +#: spa/plugins/alsa/acp/alsa-mixer.c:4699 +#, fuzzy msgid "Speakerphone" -msgstr "Spikerfon" +msgstr "Analogni stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4537 -#: spa/plugins/alsa/acp/alsa-mixer.c:4538 +#: spa/plugins/alsa/acp/alsa-mixer.c:4542 +#: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" -msgstr "Višekanalni" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4539 +#: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "Analogni okružujući 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4540 +#: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "Analogni okružujući 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4541 +#: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "Analogni okružujući 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4542 +#: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "Analogni okružujući 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4543 +#: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "Analogni okružujući 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4544 +#: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "Analogni okružujući 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4545 +#: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "Analogni okružujući 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4546 +#: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "Analogni okružujući 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4547 +#: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "Analogni okružujući 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4548 +#: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "Analogni okružujući 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4549 +#: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "Analogni okružujući 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4550 +#: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "Digitalni stereo (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4551 +#: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digitalni okružujući 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4552 +#: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digitalni okružujući 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4553 +#: spa/plugins/alsa/acp/alsa-mixer.c:4558 +#, fuzzy msgid "Digital Surround 5.1 (IEC958/DTS)" -msgstr "Digitalni okružujući 5.1 (IEC958/DTS)" +msgstr "Digitalni okružujući 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4554 +#: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "Digitalni stereo (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4555 +#: spa/plugins/alsa/acp/alsa-mixer.c:4560 +#, fuzzy msgid "Digital Surround 5.1 (HDMI)" -msgstr "Digitalni okružujući 5.1 (HDMI)" +msgstr "Digitalni okružujući 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4556 +#: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" -msgstr "Ćaskanje" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4557 +#: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" -msgstr "Igrica" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4691 +#: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "Dvosmerni analogni mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4692 +#: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "Dvosmerni analogni stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4695 +#: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "Dvosmerni digitalni stereo (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4696 +#: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" -msgstr "Višekanalni dupleks" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4697 +#: spa/plugins/alsa/acp/alsa-mixer.c:4702 +#, fuzzy msgid "Stereo Duplex" -msgstr "Stereo dupleks" +msgstr "Dvosmerni analogni stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4698 +#: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" -msgstr "Mono ćaskanje + 7.1 okružujući" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4799 -#, c-format +#: spa/plugins/alsa/acp/alsa-mixer.c:4806 +#, fuzzy, c-format msgid "%s Output" -msgstr "%s izlaz" +msgstr "Prazan izlaz" -#: spa/plugins/alsa/acp/alsa-mixer.c:4807 -#, c-format +#: spa/plugins/alsa/acp/alsa-mixer.c:4813 +#, fuzzy, c-format msgid "%s Input" -msgstr "%s ulaz" +msgstr "Ulaz" -#: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 -#, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 +#, fuzzy, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " "ms).\n" @@ -613,9 +496,9 @@ msgid_plural "" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" -"snd_pcm_avail() je vratio vrednost koja je izuzetno velika: %lu bajt (%lu " +"snd_pcm_avail() je vratio vrednost koja je neobično velika: %lu bajtova (%lu " "ms).\n" -"Najverovatnije je ovo greška u ALSA upravljačkom programu „%s“. Prijavite " +"Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." msgstr[1] "" "snd_pcm_avail() je vratio vrednost koja je neobično velika: %lu bajtova (%lu " @@ -628,49 +511,49 @@ msgstr[2] "" "Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." -#: spa/plugins/alsa/acp/alsa-util.c:1299 -#, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1241 +#, fuzzy, c-format msgid "" -"snd_pcm_delay() returned a value that is exceptionally large: %li byte " -"(%s%lu ms).\n" +"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() je vratio vrednost koja je izuzetno velika: %li bajt (%s%lu " -"ms).\n" -"Najverovatnije je ovo greška u ALSA upravljačkom programu „%s“. Prijavite " +"snd_pcm_delay() je vratio vrednost koja je neobično velika: %li bajtova (%s" +"%lu ms).\n" +"Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." msgstr[1] "" -"snd_pcm_delay() je vratio vrednost koja je neobično velika: %li bajtova " -"(%s%lu ms).\n" +"snd_pcm_delay() je vratio vrednost koja je neobično velika: %li bajtova (%s" +"%lu ms).\n" "Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." msgstr[2] "" -"snd_pcm_delay() je vratio vrednost koja je neobično velika: %li bajtova " -"(%s%lu ms).\n" +"snd_pcm_delay() je vratio vrednost koja je neobično velika: %li bajtova (%s" +"%lu ms).\n" "Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." -#: spa/plugins/alsa/acp/alsa-util.c:1346 -#, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1288 +#, fuzzy, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " "%lu.\n" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr "" -"snd_pcm_avail_delay() je vratio čudne vrednosti: kašnjenje %lu je manje od " -"dostupnog %lu.\n" -"Najverovatnije je ovo greška u ALSA upravljačkom programu „%s“. Prijavite " +"snd_pcm_avail() je vratio vrednost koja je neobično velika: %lu bajtova (%lu " +"ms).\n" +"Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." -#: spa/plugins/alsa/acp/alsa-util.c:1389 -#, c-format +#: spa/plugins/alsa/acp/alsa-util.c:1331 +#, fuzzy, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " "(%lu ms).\n" @@ -682,9 +565,9 @@ msgid_plural "" "Most likely this is a bug in the ALSA driver '%s'. Please report this issue " "to the ALSA developers." msgstr[0] "" -"snd_pcm_mmap_begin() je vratio vrednost koja je izuzetno velika: %lu bajt " -"(%lu ms).\n" -"Najverovatnije je ovo greška u ALSA upravljačkom programu „%s“. Prijavite " +"snd_pcm_mmap_begin() je vratio vrednost koja je neobično velika: %lu " +"bajtova (%lu ms).\n" +"Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." msgstr[1] "" "snd_pcm_mmap_begin() je vratio vrednost koja je neobično velika: %lu " @@ -697,120 +580,62 @@ msgstr[2] "" "Ovo je najverovatnije greška u „%s“ ALSA upravljačkom programu. Prijavite " "ovaj problem ALSA programerima." -#: spa/plugins/alsa/acp/channelmap.h:460 -msgid "(invalid)" -msgstr "(neispravno)" - -#: spa/plugins/alsa/acp/compat.c:194 -msgid "Built-in Audio" -msgstr "Unutrašnji zvuk" - -#: spa/plugins/alsa/acp/compat.c:199 -msgid "Modem" -msgstr "Modem" - -#: spa/plugins/bluez5/bluez5-device.c:2174 +#: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" -msgstr "Zvučni mrežni prolaz (A2DP izvor i HSP/HFP AG)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2203 -msgid "Audio Streaming for Hearing Aids (ASHA Sink)" -msgstr "Strujanje zvuka za slušne aparate (ASHA slivnik)" - -#: spa/plugins/bluez5/bluez5-device.c:2246 +#: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" -msgstr "Puštanje visoke vernosti (A2DP slivnik, kodek %s)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2249 +#: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" -msgstr "Dvosmerno visoke vernosti (A2DP izvor/slivnik, kodek %s)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2257 +#: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" -msgstr "Puštanje visoke vernosti (A2DP slivnik)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2259 +#: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" -msgstr "Dvosmerno visoke vernosti (A2DP izvor/slivnik)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2281 -msgid "Auto: Prefer Quality (A2DP)" -msgstr "Auto: prednost kvalitetu (A2DP)" - -#: spa/plugins/bluez5/bluez5-device.c:2286 -msgid "Auto: Prefer Latency (A2DP)" -msgstr "Auto: prednost kašnjenju (A2DP)" - -#: spa/plugins/bluez5/bluez5-device.c:2366 -#, c-format -msgid "High Fidelity Playback (BAP Sink, codec %s)" -msgstr "Puštanje visoke vernosti (BAP slivnik, kodek %s)" - -#: spa/plugins/bluez5/bluez5-device.c:2371 -#, c-format -msgid "High Fidelity Input (BAP Source, codec %s)" -msgstr "Ulaz visoke vernosti (BAP izvor, kodek %s)" - -#: spa/plugins/bluez5/bluez5-device.c:2375 -#, c-format -msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" -msgstr "Dvosmerno visoke vernosti (BAP izvor/slivnik, kodek %s)" - -#: spa/plugins/bluez5/bluez5-device.c:2384 -msgid "High Fidelity Playback (BAP Sink)" -msgstr "Puštanje visoke vernosti (BAP slivnik)" - -#: spa/plugins/bluez5/bluez5-device.c:2388 -msgid "High Fidelity Input (BAP Source)" -msgstr "Ulaz visoke vernosti (BAP izvor)" - -#: spa/plugins/bluez5/bluez5-device.c:2391 -msgid "High Fidelity Duplex (BAP Source/Sink)" -msgstr "Dvosmerno visoke vernosti (BAP izvor/slivnik)" - -#: spa/plugins/bluez5/bluez5-device.c:2431 +#: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" -msgstr "Slušalice sa mikrofonom (HSP/HFP, kodek %s)" +msgstr "" -#: 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 +#: spa/plugins/bluez5/bluez5-device.c:1074 +msgid "Headset Head Unit (HSP/HFP)" +msgstr "" + +#: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" -msgstr "Hendsfri" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2591 -msgid "Handsfree (HFP)" -msgstr "Hendsfri (HFP)" +#: spa/plugins/bluez5/bluez5-device.c:1155 +#, fuzzy +msgid "Headphone" +msgstr "Analogne slušalice" -#: spa/plugins/bluez5/bluez5-device.c:2614 +#: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" -msgstr "Prenosno" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2620 +#: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" -msgstr "Kola" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2626 +#: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" -msgstr "Hi-fi" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2632 +#: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" -msgstr "Telefon" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2639 +#: spa/plugins/bluez5/bluez5-device.c:1181 msgid "Bluetooth" -msgstr "Blutut" - -#: spa/plugins/bluez5/bluez5-device.c:2640 -msgid "Bluetooth Handsfree" -msgstr "Blutut hendsfri" +msgstr "" diff --git a/po/sv.po b/po/sv.po index 1474f5084..0bc2796a4 100644 --- a/po/sv.po +++ b/po/sv.po @@ -1,9 +1,9 @@ # Swedish translation for pipewire. -# Copyright © 2008-2026 Free Software Foundation, Inc. +# Copyright © 2008-2025 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, 2026. +# Anders Jonsson , 2021, 2022, 2023, 2024, 2025. # # 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: 2026-02-09 12:55+0000\n" -"PO-Revision-Date: 2026-02-22 21:48+0100\n" +"POT-Creation-Date: 2025-04-16 15:31+0000\n" +"PO-Revision-Date: 2025-04-22 10:43+0200\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.8\n" +"X-Generator: Poedit 3.5\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:3 +#: src/daemon/pipewire.desktop.in:4 msgid "PipeWire Media System" msgstr "PipeWire mediasystem" -#: src/daemon/pipewire.desktop.in:4 +#: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" msgstr "Starta mediasystemet PipeWire" @@ -65,51 +65,26 @@ msgstr "Tunnel till %s%s%s" msgid "Dummy Output" msgstr "Attrapputgång" -#: src/modules/module-pulse-tunnel.c:761 +#: src/modules/module-pulse-tunnel.c:760 #, c-format msgid "Tunnel for %s@%s" msgstr "Tunnel för %s@%s" -#: src/modules/module-zeroconf-discover.c:326 +#: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Okänd enhet" -#: src/modules/module-zeroconf-discover.c:338 +#: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%s på %s@%s" -#: src/modules/module-zeroconf-discover.c:342 +#: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%s på %s" -#: 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 +#: src/tools/pw-cat.c:973 #, c-format msgid "" "%s [options] [|-]\n" @@ -124,7 +99,7 @@ msgstr "" " -v, --verbose Aktivera utförliga operationer\n" "\n" -#: src/tools/pw-cat.c:1184 +#: src/tools/pw-cat.c:980 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -156,64 +131,50 @@ msgstr "" " -P --properties Sätt nodegenskaper\n" "\n" -#: src/tools/pw-cat.c:1202 +#: src/tools/pw-cat.c:998 #, c-format msgid "" -" --rate Sample rate (default %u)\n" -" --channels Number of channels (default %u)\n" +" --rate Sample rate (req. for rec) (default " +"%u)\n" +" --channels Number of channels (req. for rec) " +"(default %u)\n" " --channel-map Channel map\n" -" a channel layout: \"Stereo\", " -"\"5.1\",... or\n" +" one of: \"stereo\", " +"\"surround-51\",... 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" +" --format Sample format %s (req. for rec) " +"(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" " -a, --raw RAW mode\n" -" -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 (standard %u)\n" -" --channels Antal kanaler (standard %u)\n" +" --rate Samplingsfrekvens (krävs för insp.) " +"(standard %u)\n" +" --channels Antal kanaler (krävs för insp.) " +"(standard %u)\n" " --channel-map Kanalmappning\n" -" en kanallayout: \"Stereo\", " -"\"5.1\",... eller\n" +" en av: \"stereo\", " +"\"surround-51\",... eller\n" " kommaseparerad lista av " "kanalnamn: t.ex. \"FL,FR\"\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" +" --format Samplingsformat %s (krävs för insp.) " +"(standard %s)\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:1227 +#: src/tools/pw-cat.c:1016 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" " -m, --midi Midi mode\n" " -d, --dsd DSD mode\n" " -o, --encoded Encoded mode\n" -" -s, --sysex SysEx mode\n" -" -c, --midi-clip MIDI clip mode\n" "\n" msgstr "" " -p, --playback Uppspelningsläge\n" @@ -221,16 +182,9 @@ 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-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 +#: src/tools/pw-cli.c:2306 #, c-format msgid "" "%s [options] [command]\n" @@ -249,203 +203,200 @@ msgstr "" " -m, --monitor Övervaka aktivitet\n" "\n" -#: spa/plugins/alsa/acp/acp.c:361 +#: spa/plugins/alsa/acp/acp.c:350 msgid "Pro Audio" msgstr "Professionellt ljud" -#: 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:511 spa/plugins/alsa/acp/alsa-mixer.c:4635 +#: spa/plugins/bluez5/bluez5-device.c:1802 msgid "Off" msgstr "Av" -#: spa/plugins/alsa/acp/acp.c:620 +#: spa/plugins/alsa/acp/acp.c:593 #, c-format msgid "%s [ALSA UCM error]" msgstr "%s [ALSA UCM-fel]" -#: spa/plugins/alsa/acp/alsa-mixer.c:2721 +#: spa/plugins/alsa/acp/alsa-mixer.c:2652 msgid "Input" msgstr "Ingång" -#: spa/plugins/alsa/acp/alsa-mixer.c:2722 +#: spa/plugins/alsa/acp/alsa-mixer.c:2653 msgid "Docking Station Input" msgstr "Ingång för dockningsstation" -#: spa/plugins/alsa/acp/alsa-mixer.c:2723 +#: spa/plugins/alsa/acp/alsa-mixer.c:2654 msgid "Docking Station Microphone" msgstr "Mikrofon för dockningsstation" -#: spa/plugins/alsa/acp/alsa-mixer.c:2724 +#: spa/plugins/alsa/acp/alsa-mixer.c:2655 msgid "Docking Station Line In" msgstr "Linje in för dockningsstation" -#: spa/plugins/alsa/acp/alsa-mixer.c:2725 -#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#: spa/plugins/alsa/acp/alsa-mixer.c:2656 +#: spa/plugins/alsa/acp/alsa-mixer.c:2747 msgid "Line In" msgstr "Linje in" -#: 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/alsa/acp/alsa-mixer.c:2657 +#: spa/plugins/alsa/acp/alsa-mixer.c:2741 +#: spa/plugins/bluez5/bluez5-device.c:2146 msgid "Microphone" msgstr "Mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2727 -#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#: spa/plugins/alsa/acp/alsa-mixer.c:2658 +#: spa/plugins/alsa/acp/alsa-mixer.c:2742 msgid "Front Microphone" msgstr "Frontmikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2728 -#: spa/plugins/alsa/acp/alsa-mixer.c:2812 +#: spa/plugins/alsa/acp/alsa-mixer.c:2659 +#: spa/plugins/alsa/acp/alsa-mixer.c:2743 msgid "Rear Microphone" msgstr "Bakre mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2729 +#: spa/plugins/alsa/acp/alsa-mixer.c:2660 msgid "External Microphone" msgstr "Extern mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2730 -#: spa/plugins/alsa/acp/alsa-mixer.c:2814 +#: spa/plugins/alsa/acp/alsa-mixer.c:2661 +#: spa/plugins/alsa/acp/alsa-mixer.c:2745 msgid "Internal Microphone" msgstr "Intern mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2731 -#: spa/plugins/alsa/acp/alsa-mixer.c:2817 +#: spa/plugins/alsa/acp/alsa-mixer.c:2662 +#: spa/plugins/alsa/acp/alsa-mixer.c:2748 msgid "Radio" msgstr "Radio" -#: spa/plugins/alsa/acp/alsa-mixer.c:2732 -#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#: spa/plugins/alsa/acp/alsa-mixer.c:2663 +#: spa/plugins/alsa/acp/alsa-mixer.c:2749 msgid "Video" msgstr "Video" -#: spa/plugins/alsa/acp/alsa-mixer.c:2733 +#: spa/plugins/alsa/acp/alsa-mixer.c:2664 msgid "Automatic Gain Control" msgstr "Automatisk förstärkningskontroll" -#: spa/plugins/alsa/acp/alsa-mixer.c:2734 +#: spa/plugins/alsa/acp/alsa-mixer.c:2665 msgid "No Automatic Gain Control" msgstr "Ingen automatisk förstärkningskontroll" -#: spa/plugins/alsa/acp/alsa-mixer.c:2735 +#: spa/plugins/alsa/acp/alsa-mixer.c:2666 msgid "Boost" msgstr "Ökning" -#: spa/plugins/alsa/acp/alsa-mixer.c:2736 +#: spa/plugins/alsa/acp/alsa-mixer.c:2667 msgid "No Boost" msgstr "Ingen ökning" -#: spa/plugins/alsa/acp/alsa-mixer.c:2737 +#: spa/plugins/alsa/acp/alsa-mixer.c:2668 msgid "Amplifier" msgstr "Förstärkare" -#: spa/plugins/alsa/acp/alsa-mixer.c:2738 +#: spa/plugins/alsa/acp/alsa-mixer.c:2669 msgid "No Amplifier" msgstr "Ingen förstärkare" -#: spa/plugins/alsa/acp/alsa-mixer.c:2739 +#: spa/plugins/alsa/acp/alsa-mixer.c:2670 msgid "Bass Boost" msgstr "Basökning" -#: spa/plugins/alsa/acp/alsa-mixer.c:2740 +#: spa/plugins/alsa/acp/alsa-mixer.c:2671 msgid "No Bass Boost" msgstr "Ingen basökning" -#: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:2428 +#: spa/plugins/alsa/acp/alsa-mixer.c:2672 +#: spa/plugins/bluez5/bluez5-device.c:2152 msgid "Speaker" msgstr "Högtalare" -#. 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/alsa/acp/alsa-mixer.c:2673 +#: spa/plugins/alsa/acp/alsa-mixer.c:2751 msgid "Headphones" msgstr "Hörlurar" -#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#: spa/plugins/alsa/acp/alsa-mixer.c:2740 msgid "Analog Input" msgstr "Analog ingång" -#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#: spa/plugins/alsa/acp/alsa-mixer.c:2744 msgid "Dock Microphone" msgstr "Dockmikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2815 +#: spa/plugins/alsa/acp/alsa-mixer.c:2746 msgid "Headset Microphone" msgstr "Headset-mikrofon" -#: spa/plugins/alsa/acp/alsa-mixer.c:2819 +#: spa/plugins/alsa/acp/alsa-mixer.c:2750 msgid "Analog Output" msgstr "Analog utgång" -#: spa/plugins/alsa/acp/alsa-mixer.c:2821 +#: spa/plugins/alsa/acp/alsa-mixer.c:2752 msgid "Headphones 2" msgstr "Hörlurar 2" -#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#: spa/plugins/alsa/acp/alsa-mixer.c:2753 msgid "Headphones Mono Output" msgstr "Monoutgång för hörlurar" -#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#: spa/plugins/alsa/acp/alsa-mixer.c:2754 msgid "Line Out" msgstr "Linje ut" -#: spa/plugins/alsa/acp/alsa-mixer.c:2824 +#: spa/plugins/alsa/acp/alsa-mixer.c:2755 msgid "Analog Mono Output" msgstr "Analog monoutgång" -#: spa/plugins/alsa/acp/alsa-mixer.c:2825 +#: spa/plugins/alsa/acp/alsa-mixer.c:2756 msgid "Speakers" msgstr "Högtalare" -#: spa/plugins/alsa/acp/alsa-mixer.c:2826 +#: spa/plugins/alsa/acp/alsa-mixer.c:2757 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" -#: spa/plugins/alsa/acp/alsa-mixer.c:2827 +#: spa/plugins/alsa/acp/alsa-mixer.c:2758 msgid "Digital Output (S/PDIF)" msgstr "Digital utgång (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2828 +#: spa/plugins/alsa/acp/alsa-mixer.c:2759 msgid "Digital Input (S/PDIF)" msgstr "Digital ingång (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2829 +#: spa/plugins/alsa/acp/alsa-mixer.c:2760 msgid "Multichannel Input" msgstr "Multikanalingång" -#: spa/plugins/alsa/acp/alsa-mixer.c:2830 +#: spa/plugins/alsa/acp/alsa-mixer.c:2761 msgid "Multichannel Output" msgstr "Multikanalutgång" -#: spa/plugins/alsa/acp/alsa-mixer.c:2831 +#: spa/plugins/alsa/acp/alsa-mixer.c:2762 msgid "Game Output" msgstr "Spelutgång" -#: spa/plugins/alsa/acp/alsa-mixer.c:2832 -#: spa/plugins/alsa/acp/alsa-mixer.c:2833 +#: spa/plugins/alsa/acp/alsa-mixer.c:2763 +#: spa/plugins/alsa/acp/alsa-mixer.c:2764 msgid "Chat Output" msgstr "Chatt-utgång" -#: spa/plugins/alsa/acp/alsa-mixer.c:2834 +#: spa/plugins/alsa/acp/alsa-mixer.c:2765 msgid "Chat Input" msgstr "Chatt-ingång" -#: spa/plugins/alsa/acp/alsa-mixer.c:2835 +#: spa/plugins/alsa/acp/alsa-mixer.c:2766 msgid "Virtual Surround 7.1" msgstr "Virtual surround 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4522 +#: spa/plugins/alsa/acp/alsa-mixer.c:4458 msgid "Analog Mono" msgstr "Analog mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4523 +#: spa/plugins/alsa/acp/alsa-mixer.c:4459 msgid "Analog Mono (Left)" msgstr "Analog mono (vänster)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4524 +#: spa/plugins/alsa/acp/alsa-mixer.c:4460 msgid "Analog Mono (Right)" msgstr "Analog mono (höger)" @@ -454,142 +405,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:4525 -#: spa/plugins/alsa/acp/alsa-mixer.c:4533 -#: spa/plugins/alsa/acp/alsa-mixer.c:4534 +#: spa/plugins/alsa/acp/alsa-mixer.c:4461 +#: spa/plugins/alsa/acp/alsa-mixer.c:4469 +#: spa/plugins/alsa/acp/alsa-mixer.c:4470 msgid "Analog Stereo" msgstr "Analog stereo" -#: spa/plugins/alsa/acp/alsa-mixer.c:4526 +#: spa/plugins/alsa/acp/alsa-mixer.c:4462 msgid "Mono" msgstr "Mono" -#: spa/plugins/alsa/acp/alsa-mixer.c:4527 +#: spa/plugins/alsa/acp/alsa-mixer.c:4463 msgid "Stereo" msgstr "Stereo" -#: 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/alsa/acp/alsa-mixer.c:4471 +#: spa/plugins/alsa/acp/alsa-mixer.c:4629 +#: spa/plugins/bluez5/bluez5-device.c:2134 msgid "Headset" msgstr "Headset" -#: spa/plugins/alsa/acp/alsa-mixer.c:4536 -#: spa/plugins/alsa/acp/alsa-mixer.c:4694 +#: spa/plugins/alsa/acp/alsa-mixer.c:4472 +#: spa/plugins/alsa/acp/alsa-mixer.c:4630 msgid "Speakerphone" msgstr "Högtalartelefon" -#: spa/plugins/alsa/acp/alsa-mixer.c:4537 -#: spa/plugins/alsa/acp/alsa-mixer.c:4538 +#: spa/plugins/alsa/acp/alsa-mixer.c:4473 +#: spa/plugins/alsa/acp/alsa-mixer.c:4474 msgid "Multichannel" msgstr "Multikanal" -#: spa/plugins/alsa/acp/alsa-mixer.c:4539 +#: spa/plugins/alsa/acp/alsa-mixer.c:4475 msgid "Analog Surround 2.1" msgstr "Analog surround 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4540 +#: spa/plugins/alsa/acp/alsa-mixer.c:4476 msgid "Analog Surround 3.0" msgstr "Analog surround 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4541 +#: spa/plugins/alsa/acp/alsa-mixer.c:4477 msgid "Analog Surround 3.1" msgstr "Analog surround 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4542 +#: spa/plugins/alsa/acp/alsa-mixer.c:4478 msgid "Analog Surround 4.0" msgstr "Analog surround 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4543 +#: spa/plugins/alsa/acp/alsa-mixer.c:4479 msgid "Analog Surround 4.1" msgstr "Analog surround 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4544 +#: spa/plugins/alsa/acp/alsa-mixer.c:4480 msgid "Analog Surround 5.0" msgstr "Analog surround 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4545 +#: spa/plugins/alsa/acp/alsa-mixer.c:4481 msgid "Analog Surround 5.1" msgstr "Analog surround 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4546 +#: spa/plugins/alsa/acp/alsa-mixer.c:4482 msgid "Analog Surround 6.0" msgstr "Analog surround 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4547 +#: spa/plugins/alsa/acp/alsa-mixer.c:4483 msgid "Analog Surround 6.1" msgstr "Analog surround 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4548 +#: spa/plugins/alsa/acp/alsa-mixer.c:4484 msgid "Analog Surround 7.0" msgstr "Analog surround 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4549 +#: spa/plugins/alsa/acp/alsa-mixer.c:4485 msgid "Analog Surround 7.1" msgstr "Analog surround 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4550 +#: spa/plugins/alsa/acp/alsa-mixer.c:4486 msgid "Digital Stereo (IEC958)" msgstr "Digital stereo (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4551 +#: spa/plugins/alsa/acp/alsa-mixer.c:4487 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "Digital surround 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4552 +#: spa/plugins/alsa/acp/alsa-mixer.c:4488 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "Digital surround 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4553 +#: spa/plugins/alsa/acp/alsa-mixer.c:4489 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "Digital surround 5.1 (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4554 +#: spa/plugins/alsa/acp/alsa-mixer.c:4490 msgid "Digital Stereo (HDMI)" msgstr "Digital stereo (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4555 +#: spa/plugins/alsa/acp/alsa-mixer.c:4491 msgid "Digital Surround 5.1 (HDMI)" msgstr "Digital surround 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4556 +#: spa/plugins/alsa/acp/alsa-mixer.c:4492 msgid "Chat" msgstr "Chatt" -#: spa/plugins/alsa/acp/alsa-mixer.c:4557 +#: spa/plugins/alsa/acp/alsa-mixer.c:4493 msgid "Game" msgstr "Spel" -#: spa/plugins/alsa/acp/alsa-mixer.c:4691 +#: spa/plugins/alsa/acp/alsa-mixer.c:4627 msgid "Analog Mono Duplex" msgstr "Analog mono duplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4692 +#: spa/plugins/alsa/acp/alsa-mixer.c:4628 msgid "Analog Stereo Duplex" msgstr "Analog stereo duplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4695 +#: spa/plugins/alsa/acp/alsa-mixer.c:4631 msgid "Digital Stereo Duplex (IEC958)" msgstr "Digital stereo duplex (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4696 +#: spa/plugins/alsa/acp/alsa-mixer.c:4632 msgid "Multichannel Duplex" msgstr "Multikanalduplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4697 +#: spa/plugins/alsa/acp/alsa-mixer.c:4633 msgid "Stereo Duplex" msgstr "Stereo duplex" -#: spa/plugins/alsa/acp/alsa-mixer.c:4698 +#: spa/plugins/alsa/acp/alsa-mixer.c:4634 msgid "Mono Chat + 7.1 Surround" msgstr "Mono Chatt + 7.1 Surround" -#: spa/plugins/alsa/acp/alsa-mixer.c:4799 +#: spa/plugins/alsa/acp/alsa-mixer.c:4735 #, c-format msgid "%s Output" msgstr "%s-utgång" -#: spa/plugins/alsa/acp/alsa-mixer.c:4807 +#: spa/plugins/alsa/acp/alsa-mixer.c:4743 #, c-format msgid "%s Input" msgstr "%s-ingång" @@ -676,115 +627,116 @@ 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:460 +#: spa/plugins/alsa/acp/channelmap.h:457 msgid "(invalid)" msgstr "(ogiltig)" -#: spa/plugins/alsa/acp/compat.c:194 +#: spa/plugins/alsa/acp/compat.c:193 msgid "Built-in Audio" msgstr "Inbyggt ljud" -#: spa/plugins/alsa/acp/compat.c:199 +#: spa/plugins/alsa/acp/compat.c:198 msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:2032 +#: spa/plugins/bluez5/bluez5-device.c:1813 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Audio gateway (A2DP-källa & HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:2061 +#: spa/plugins/bluez5/bluez5-device.c:1841 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:2104 +#: spa/plugins/bluez5/bluez5-device.c:1881 #, 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:2107 +#: spa/plugins/bluez5/bluez5-device.c:1884 #, 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:2115 +#: spa/plugins/bluez5/bluez5-device.c:1892 msgid "High Fidelity Playback (A2DP Sink)" msgstr "High fidelity-uppspelning (A2DP-utgång)" -#: spa/plugins/bluez5/bluez5-device.c:2117 +#: spa/plugins/bluez5/bluez5-device.c:1894 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "High fidelity duplex (A2DP-källa/utgång)" -#: spa/plugins/bluez5/bluez5-device.c:2194 +#: spa/plugins/bluez5/bluez5-device.c:1944 #, 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:2199 +#: spa/plugins/bluez5/bluez5-device.c:1949 #, 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:2203 +#: spa/plugins/bluez5/bluez5-device.c:1953 #, 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:2212 +#: spa/plugins/bluez5/bluez5-device.c:1962 msgid "High Fidelity Playback (BAP Sink)" msgstr "High fidelity-uppspelning (BAP-utgång)" -#: spa/plugins/bluez5/bluez5-device.c:2216 +#: spa/plugins/bluez5/bluez5-device.c:1966 msgid "High Fidelity Input (BAP Source)" msgstr "High fidelity-ingång (BAP-källa)" -#: spa/plugins/bluez5/bluez5-device.c:2219 +#: spa/plugins/bluez5/bluez5-device.c:1969 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "High fidelity duplex (BAP-källa/utgång)" -#: spa/plugins/bluez5/bluez5-device.c:2259 +#: spa/plugins/bluez5/bluez5-device.c:2015 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Headset-huvudenhet (HSP/HFP, kodek %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: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 msgid "Handsfree" msgstr "Handsfree" -#: spa/plugins/bluez5/bluez5-device.c:2417 +#: spa/plugins/bluez5/bluez5-device.c:2141 msgid "Handsfree (HFP)" msgstr "Handsfree (HFP)" -#: spa/plugins/bluez5/bluez5-device.c:2440 +#: spa/plugins/bluez5/bluez5-device.c:2158 +msgid "Headphone" +msgstr "Hörlurar" + +#: spa/plugins/bluez5/bluez5-device.c:2164 msgid "Portable" msgstr "Bärbar" -#: spa/plugins/bluez5/bluez5-device.c:2446 +#: spa/plugins/bluez5/bluez5-device.c:2170 msgid "Car" msgstr "Bil" -#: spa/plugins/bluez5/bluez5-device.c:2452 +#: spa/plugins/bluez5/bluez5-device.c:2176 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:2458 +#: spa/plugins/bluez5/bluez5-device.c:2182 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:2465 +#: spa/plugins/bluez5/bluez5-device.c:2189 msgid "Bluetooth" msgstr "Bluetooth" -#: spa/plugins/bluez5/bluez5-device.c:2466 -msgid "Bluetooth Handsfree" -msgstr "Bluetooth-handsfree" - -#~ msgid "Headphone" -#~ msgstr "Hörlurar" +#: spa/plugins/bluez5/bluez5-device.c:2190 +msgid "Bluetooth (HFP)" +msgstr "Bluetooth (HFP)" diff --git a/po/zh_CN.po b/po/zh_CN.po index 3236996bb..8be588b03 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-04-08 13:01+0000\n" -"PO-Revision-Date: 2026-04-09 08:06+0800\n" +"POT-Creation-Date: 2026-02-11 16:53+0000\n" +"PO-Revision-Date: 2026-02-13 09:36+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 50.0\n" +"X-Generator: Gtranslator 49.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:290 +#: src/modules/module-zeroconf-discover.c:326 msgid "Unknown device" msgstr "未知设备" -#: src/modules/module-zeroconf-discover.c:302 +#: src/modules/module-zeroconf-discover.c:338 #, c-format msgid "%s on %s@%s" msgstr "%2$s@%3$s 上的 %1$s" -#: src/modules/module-zeroconf-discover.c:306 +#: src/modules/module-zeroconf-discover.c:342 #, c-format msgid "%s on %s" msgstr "%2$s 上的 %1$s" -#: src/tools/pw-cat.c:269 +#: src/tools/pw-cat.c:264 #, c-format msgid "Supported formats:\n" msgstr "支持的格式:\n" -#: src/tools/pw-cat.c:754 +#: src/tools/pw-cat.c:749 #, c-format msgid "Supported channel layouts:\n" msgstr "支持的声道布局:\n" -#: src/tools/pw-cat.c:764 +#: src/tools/pw-cat.c:759 #, c-format msgid "Supported channel layout aliases:\n" msgstr "支持的声道布局别名:\n" -#: src/tools/pw-cat.c:766 +#: src/tools/pw-cat.c:761 #, c-format msgid " %s -> %s\n" msgstr " %s -> %s\n" -#: src/tools/pw-cat.c:771 +#: src/tools/pw-cat.c:766 #, c-format msgid "Supported channel names:\n" msgstr "支持的声道名称:\n" -#: src/tools/pw-cat.c:1183 +#: src/tools/pw-cat.c:1177 #, c-format msgid "" "%s [options] [|-]\n" @@ -119,7 +119,7 @@ msgstr "" " -v, --verbose 输出详细操作\n" "\n" -#: src/tools/pw-cat.c:1190 +#: src/tools/pw-cat.c:1184 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -129,8 +129,6 @@ 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" @@ -145,7 +143,6 @@ msgstr "" " --media-role 设置媒体角色 (默认 %s)\n" " --target 设置节点目标序列或名称 (默认 %s)\n" " 设为 0 则不链接节点\n" -" -C --monitor 捕获监视器端口 (录制模式)\n" " --latency 设置节点延迟 (默认 %s)\n" " 时间 (单位可为 s, ms, us, ns)\n" " 或样本数 (如256)\n" @@ -154,7 +151,7 @@ msgstr "" " -P --properties 设置节点属性\n" "\n" -#: src/tools/pw-cat.c:1209 +#: src/tools/pw-cat.c:1202 #, c-format msgid "" " --rate Sample rate (default %u)\n" @@ -201,7 +198,7 @@ msgstr "" " -n, --sample-count COUNT 计数采样后停止\n" "\n" -#: src/tools/pw-cat.c:1234 +#: src/tools/pw-cat.c:1227 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" @@ -221,7 +218,7 @@ msgstr "" " -c, --midi-clip MIDI 剪辑模式\n" "\n" -#: src/tools/pw-cat.c:1839 +#: src/tools/pw-cat.c:1827 #, c-format msgid "Supported containers and extensions:\n" msgstr "支持的容器和扩展:\n" @@ -248,12 +245,12 @@ msgstr "" msgid "Pro Audio" msgstr "专业音频" -#: spa/plugins/alsa/acp/acp.c:535 spa/plugins/alsa/acp/alsa-mixer.c:4699 -#: spa/plugins/bluez5/bluez5-device.c:2163 +#: 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 "关" -#: spa/plugins/alsa/acp/acp.c:618 +#: spa/plugins/alsa/acp/acp.c:620 #, c-format msgid "%s [ALSA UCM error]" msgstr "%s [ALSA UCM 错误]" @@ -281,7 +278,7 @@ msgstr "输入插孔" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 #: spa/plugins/alsa/acp/alsa-mixer.c:2810 -#: spa/plugins/bluez5/bluez5-device.c:2596 +#: spa/plugins/bluez5/bluez5-device.c:2422 msgid "Microphone" msgstr "话筒" @@ -347,15 +344,15 @@ msgid "No Bass Boost" msgstr "无重低音增强" #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:2602 +#: spa/plugins/bluez5/bluez5-device.c:2428 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:2608 -#: spa/plugins/bluez5/bluez5-device.c:2675 +#: spa/plugins/bluez5/bluez5-device.c:2434 +#: spa/plugins/bluez5/bluez5-device.c:2501 msgid "Headphones" msgstr "模拟耳机" @@ -465,7 +462,7 @@ msgstr "立体声" #: spa/plugins/alsa/acp/alsa-mixer.c:4535 #: spa/plugins/alsa/acp/alsa-mixer.c:4693 -#: spa/plugins/bluez5/bluez5-device.c:2584 +#: spa/plugins/bluez5/bluez5-device.c:2410 msgid "Headset" msgstr "耳机" @@ -660,109 +657,101 @@ msgstr "内置音频" msgid "Modem" msgstr "调制解调器" -#: spa/plugins/bluez5/bluez5-device.c:2174 +#: spa/plugins/bluez5/bluez5-device.c:2032 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "音频网关 (A2DP 信源 或 HSP/HFP 网关)" -#: spa/plugins/bluez5/bluez5-device.c:2203 +#: spa/plugins/bluez5/bluez5-device.c:2061 msgid "Audio Streaming for Hearing Aids (ASHA Sink)" msgstr "助听器音频流 (ASHA 信宿)" -#: spa/plugins/bluez5/bluez5-device.c:2246 +#: spa/plugins/bluez5/bluez5-device.c:2104 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "高保真回放 (A2DP 信宿, 编码 %s)" -#: spa/plugins/bluez5/bluez5-device.c:2249 +#: spa/plugins/bluez5/bluez5-device.c:2107 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "高保真双工 (A2DP 信源/信宿, 编码 %s)" -#: spa/plugins/bluez5/bluez5-device.c:2257 +#: spa/plugins/bluez5/bluez5-device.c:2115 msgid "High Fidelity Playback (A2DP Sink)" msgstr "高保真回放 (A2DP 信宿)" -#: spa/plugins/bluez5/bluez5-device.c:2259 +#: spa/plugins/bluez5/bluez5-device.c:2117 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "高保真双工 (A2DP 信源/信宿)" -#: 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 +#: 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:2371 +#: 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:2375 +#: 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:2384 +#: spa/plugins/bluez5/bluez5-device.c:2212 msgid "High Fidelity Playback (BAP Sink)" msgstr "高保真回放 (BAP 信宿)" -#: spa/plugins/bluez5/bluez5-device.c:2388 +#: spa/plugins/bluez5/bluez5-device.c:2216 msgid "High Fidelity Input (BAP Source)" msgstr "高保真输入 (BAP 信源)" -#: spa/plugins/bluez5/bluez5-device.c:2391 +#: spa/plugins/bluez5/bluez5-device.c:2219 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "高保真双工 (BAP 信源/信宿)" -#: spa/plugins/bluez5/bluez5-device.c:2431 +#: spa/plugins/bluez5/bluez5-device.c:2259 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "头戴式耳机单元 (HSP/HFP, 编码 %s)" -#: 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 +#: 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:2591 +#: spa/plugins/bluez5/bluez5-device.c:2417 msgid "Handsfree (HFP)" msgstr "免提(HFP)" -#: spa/plugins/bluez5/bluez5-device.c:2614 +#: spa/plugins/bluez5/bluez5-device.c:2440 msgid "Portable" msgstr "便携式" -#: spa/plugins/bluez5/bluez5-device.c:2620 +#: spa/plugins/bluez5/bluez5-device.c:2446 msgid "Car" msgstr "车内" -#: spa/plugins/bluez5/bluez5-device.c:2626 +#: spa/plugins/bluez5/bluez5-device.c:2452 msgid "HiFi" msgstr "高保真" -#: spa/plugins/bluez5/bluez5-device.c:2632 +#: spa/plugins/bluez5/bluez5-device.c:2458 msgid "Phone" msgstr "电话" -#: spa/plugins/bluez5/bluez5-device.c:2639 +#: spa/plugins/bluez5/bluez5-device.c:2465 msgid "Bluetooth" msgstr "蓝牙" -#: spa/plugins/bluez5/bluez5-device.c:2640 +#: spa/plugins/bluez5/bluez5-device.c:2466 msgid "Bluetooth Handsfree" msgstr "蓝牙免提" diff --git a/po/zh_TW.po b/po/zh_TW.po index ab41369cf..1a768a992 100644 --- a/po/zh_TW.po +++ b/po/zh_TW.po @@ -4,221 +4,110 @@ # # 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: 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" +"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" "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 26.03.70\n" +"X-Generator: Lokalize 19.12.0\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: src/daemon/pipewire.c:29 +#: src/daemon/pipewire.c:43 #, 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 "PipeWire 媒體系統" +msgstr "" #: src/daemon/pipewire.desktop.in:5 msgid "Start the PipeWire Media System" -msgstr "啟動 PipeWire 媒體系統" +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:526 +#: spa/plugins/alsa/acp/compat.c:187 +msgid "Built-in Audio" +msgstr "內部音效" -#: src/modules/module-fallback-sink.c:40 -msgid "Dummy Output" -msgstr "虛擬輸出" +#: src/examples/media-session/alsa-monitor.c:530 +#: spa/plugins/alsa/acp/compat.c:192 +msgid "Modem" +msgstr "數據機" -#: src/modules/module-pulse-tunnel.c:761 -#, c-format -msgid "Tunnel for %s@%s" -msgstr "%s@%s 的穿隧道" - -#: src/modules/module-zeroconf-discover.c:290 +#: src/examples/media-session/alsa-monitor.c:539 msgid "Unknown device" -msgstr "未知裝置" +msgstr "" -#: 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 +#: src/tools/pw-cat.c:991 #, 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:1189 +#: src/tools/pw-cat.c:998 #, c-format msgid "" " -R, --remote Remote daemon name\n" " --media-type Set media type (default %s)\n" " --media-category Set media category (default %s)\n" " --media-role Set media role (default %s)\n" -" --target Set node target serial or name " -"(default %s)\n" +" --target Set node target (default %s)\n" " 0 means don't link\n" " --latency Set node latency (default %s)\n" " Xunit (unit = s, ms, us, ns)\n" " or direct samples (256)\n" " the rate is the one of the source " "file\n" -" -P --properties Set node properties\n" +" --list-targets List available targets for --target\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:1207 +#: src/tools/pw-cat.c:1016 #, c-format msgid "" -" --rate Sample rate (default %u)\n" -" --channels Number of channels (default %u)\n" +" --rate Sample rate (req. for rec) (default " +"%u)\n" +" --channels Number of channels (req. for rec) " +"(default %u)\n" " --channel-map Channel map\n" -" a channel layout: \"Stereo\", " -"\"5.1\",... or\n" +" one of: \"stereo\", " +"\"surround-51\",... 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" +" --format Sample format %s (req. for rec) " +"(default %s)\n" " --volume Stream volume 0-1.0 (default %.3f)\n" " -q --quality Resampler quality (0 - 15) (default " "%d)\n" -" -a, --raw RAW mode\n" -" -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:1232 +#: src/tools/pw-cat.c:1033 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-cat.c:1837 -#, c-format -msgid "Supported containers and extensions:\n" -msgstr "支援的容器與副檔名:\n" - -#: src/tools/pw-cli.c:2386 +#: src/tools/pw-cli.c:2932 #, c-format msgid "" "%s [options] [command]\n" @@ -226,363 +115,357 @@ 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:361 +#: spa/plugins/alsa/acp/acp.c:290 msgid "Pro Audio" -msgstr "Pro Audio" +msgstr "" -#: spa/plugins/alsa/acp/acp.c:535 spa/plugins/alsa/acp/alsa-mixer.c:4699 -#: spa/plugins/bluez5/bluez5-device.c:2021 +#: spa/plugins/alsa/acp/acp.c:411 spa/plugins/alsa/acp/alsa-mixer.c:4704 +#: spa/plugins/bluez5/bluez5-device.c:1000 msgid "Off" msgstr "關閉" -#: spa/plugins/alsa/acp/acp.c:618 -#, c-format -msgid "%s [ALSA UCM error]" -msgstr "%s [ALSA UCM 錯誤]" +#: spa/plugins/alsa/acp/channelmap.h:466 +msgid "(invalid)" +msgstr "(無效)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2721 +#: spa/plugins/alsa/acp/alsa-mixer.c:2709 msgid "Input" msgstr "輸入" -#: spa/plugins/alsa/acp/alsa-mixer.c:2722 +#: spa/plugins/alsa/acp/alsa-mixer.c:2710 msgid "Docking Station Input" msgstr "Docking Station 輸入" -#: spa/plugins/alsa/acp/alsa-mixer.c:2723 +#: spa/plugins/alsa/acp/alsa-mixer.c:2711 msgid "Docking Station Microphone" msgstr "Docking Station 麥克風" -#: spa/plugins/alsa/acp/alsa-mixer.c:2724 +#: spa/plugins/alsa/acp/alsa-mixer.c:2712 msgid "Docking Station Line In" msgstr "Docking Station 線路輸入" -#: spa/plugins/alsa/acp/alsa-mixer.c:2725 -#: spa/plugins/alsa/acp/alsa-mixer.c:2816 +#: spa/plugins/alsa/acp/alsa-mixer.c:2713 +#: spa/plugins/alsa/acp/alsa-mixer.c:2804 msgid "Line In" 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/alsa/acp/alsa-mixer.c:2714 +#: spa/plugins/alsa/acp/alsa-mixer.c:2798 +#: spa/plugins/bluez5/bluez5-device.c:1145 msgid "Microphone" msgstr "麥克風" -#: spa/plugins/alsa/acp/alsa-mixer.c:2727 -#: spa/plugins/alsa/acp/alsa-mixer.c:2811 +#: spa/plugins/alsa/acp/alsa-mixer.c:2715 +#: spa/plugins/alsa/acp/alsa-mixer.c:2799 msgid "Front Microphone" msgstr "前方麥克風" -#: spa/plugins/alsa/acp/alsa-mixer.c:2728 -#: spa/plugins/alsa/acp/alsa-mixer.c:2812 +#: spa/plugins/alsa/acp/alsa-mixer.c:2716 +#: spa/plugins/alsa/acp/alsa-mixer.c:2800 msgid "Rear Microphone" msgstr "後方麥克風" -#: spa/plugins/alsa/acp/alsa-mixer.c:2729 +#: spa/plugins/alsa/acp/alsa-mixer.c:2717 msgid "External Microphone" msgstr "外接麥克風" -#: spa/plugins/alsa/acp/alsa-mixer.c:2730 -#: spa/plugins/alsa/acp/alsa-mixer.c:2814 +#: spa/plugins/alsa/acp/alsa-mixer.c:2718 +#: spa/plugins/alsa/acp/alsa-mixer.c:2802 msgid "Internal Microphone" msgstr "內建麥克風" -#: spa/plugins/alsa/acp/alsa-mixer.c:2731 -#: spa/plugins/alsa/acp/alsa-mixer.c:2817 +#: spa/plugins/alsa/acp/alsa-mixer.c:2719 +#: spa/plugins/alsa/acp/alsa-mixer.c:2805 msgid "Radio" msgstr "無線電" -#: spa/plugins/alsa/acp/alsa-mixer.c:2732 -#: spa/plugins/alsa/acp/alsa-mixer.c:2818 +#: spa/plugins/alsa/acp/alsa-mixer.c:2720 +#: spa/plugins/alsa/acp/alsa-mixer.c:2806 msgid "Video" msgstr "視訊" -#: spa/plugins/alsa/acp/alsa-mixer.c:2733 +#: spa/plugins/alsa/acp/alsa-mixer.c:2721 msgid "Automatic Gain Control" msgstr "自動增益控制" -#: spa/plugins/alsa/acp/alsa-mixer.c:2734 +#: spa/plugins/alsa/acp/alsa-mixer.c:2722 msgid "No Automatic Gain Control" msgstr "無自動增益控制" -#: spa/plugins/alsa/acp/alsa-mixer.c:2735 +#: spa/plugins/alsa/acp/alsa-mixer.c:2723 msgid "Boost" msgstr "增強" -#: spa/plugins/alsa/acp/alsa-mixer.c:2736 +#: spa/plugins/alsa/acp/alsa-mixer.c:2724 msgid "No Boost" msgstr "無增強" -#: spa/plugins/alsa/acp/alsa-mixer.c:2737 +#: spa/plugins/alsa/acp/alsa-mixer.c:2725 msgid "Amplifier" msgstr "擴大器" -#: spa/plugins/alsa/acp/alsa-mixer.c:2738 +#: spa/plugins/alsa/acp/alsa-mixer.c:2726 msgid "No Amplifier" msgstr "無擴大器" -#: spa/plugins/alsa/acp/alsa-mixer.c:2739 +#: spa/plugins/alsa/acp/alsa-mixer.c:2727 msgid "Bass Boost" msgstr "低音增強" -#: spa/plugins/alsa/acp/alsa-mixer.c:2740 +#: spa/plugins/alsa/acp/alsa-mixer.c:2728 msgid "No Bass Boost" msgstr "無低音增強" -#: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:2428 +#: spa/plugins/alsa/acp/alsa-mixer.c:2729 +#: spa/plugins/bluez5/bluez5-device.c:1150 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/alsa/acp/alsa-mixer.c:2730 +#: spa/plugins/alsa/acp/alsa-mixer.c:2808 msgid "Headphones" msgstr "頭戴式耳機" -#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#: spa/plugins/alsa/acp/alsa-mixer.c:2797 msgid "Analog Input" msgstr "類比輸入" -#: spa/plugins/alsa/acp/alsa-mixer.c:2813 +#: spa/plugins/alsa/acp/alsa-mixer.c:2801 msgid "Dock Microphone" msgstr "臺座麥克風" -#: spa/plugins/alsa/acp/alsa-mixer.c:2815 +#: spa/plugins/alsa/acp/alsa-mixer.c:2803 msgid "Headset Microphone" msgstr "耳麥麥克風" -#: spa/plugins/alsa/acp/alsa-mixer.c:2819 +#: spa/plugins/alsa/acp/alsa-mixer.c:2807 msgid "Analog Output" msgstr "類比輸出" -#: spa/plugins/alsa/acp/alsa-mixer.c:2821 +#: spa/plugins/alsa/acp/alsa-mixer.c:2809 +#, fuzzy msgid "Headphones 2" -msgstr "頭戴式耳機 2" +msgstr "頭戴式耳機" -#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#: spa/plugins/alsa/acp/alsa-mixer.c:2810 msgid "Headphones Mono Output" msgstr "頭戴式耳機單聲道輸出" -#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#: spa/plugins/alsa/acp/alsa-mixer.c:2811 msgid "Line Out" msgstr "線路輸出" -#: spa/plugins/alsa/acp/alsa-mixer.c:2824 +#: spa/plugins/alsa/acp/alsa-mixer.c:2812 msgid "Analog Mono Output" msgstr "類比單聲道輸出" -#: spa/plugins/alsa/acp/alsa-mixer.c:2825 +#: spa/plugins/alsa/acp/alsa-mixer.c:2813 msgid "Speakers" msgstr "喇叭" -#: spa/plugins/alsa/acp/alsa-mixer.c:2826 +#: spa/plugins/alsa/acp/alsa-mixer.c:2814 msgid "HDMI / DisplayPort" msgstr "HDMI / DisplayPort" -#: spa/plugins/alsa/acp/alsa-mixer.c:2827 +#: spa/plugins/alsa/acp/alsa-mixer.c:2815 msgid "Digital Output (S/PDIF)" msgstr "數位輸出 (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2828 +#: spa/plugins/alsa/acp/alsa-mixer.c:2816 msgid "Digital Input (S/PDIF)" msgstr "數位輸入 (S/PDIF)" -#: spa/plugins/alsa/acp/alsa-mixer.c:2829 +#: spa/plugins/alsa/acp/alsa-mixer.c:2817 msgid "Multichannel Input" msgstr "多聲道輸入" -#: spa/plugins/alsa/acp/alsa-mixer.c:2830 +#: spa/plugins/alsa/acp/alsa-mixer.c:2818 msgid "Multichannel Output" msgstr "多聲道輸出" -#: spa/plugins/alsa/acp/alsa-mixer.c:2831 +#: spa/plugins/alsa/acp/alsa-mixer.c:2819 msgid "Game Output" msgstr "遊戲輸出" -#: spa/plugins/alsa/acp/alsa-mixer.c:2832 -#: spa/plugins/alsa/acp/alsa-mixer.c:2833 +#: spa/plugins/alsa/acp/alsa-mixer.c:2820 +#: spa/plugins/alsa/acp/alsa-mixer.c:2821 msgid "Chat Output" msgstr "聊天輸出" -#: spa/plugins/alsa/acp/alsa-mixer.c:2834 +#: spa/plugins/alsa/acp/alsa-mixer.c:2822 +#, fuzzy msgid "Chat Input" -msgstr "聊天輸入" +msgstr "聊天輸出" -#: spa/plugins/alsa/acp/alsa-mixer.c:2835 +#: spa/plugins/alsa/acp/alsa-mixer.c:2823 +#, fuzzy msgid "Virtual Surround 7.1" -msgstr "虛擬環繞聲 7.1" +msgstr "虛擬環繞聲 sink" -#: spa/plugins/alsa/acp/alsa-mixer.c:4522 +#: spa/plugins/alsa/acp/alsa-mixer.c:4527 msgid "Analog Mono" msgstr "類比單聲道" -#: spa/plugins/alsa/acp/alsa-mixer.c:4523 +#: spa/plugins/alsa/acp/alsa-mixer.c:4528 +#, fuzzy msgid "Analog Mono (Left)" -msgstr "類比單聲道(左)" +msgstr "類比單聲道" -#: spa/plugins/alsa/acp/alsa-mixer.c:4524 +#: spa/plugins/alsa/acp/alsa-mixer.c:4529 +#, fuzzy 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:4525 -#: spa/plugins/alsa/acp/alsa-mixer.c:4533 -#: spa/plugins/alsa/acp/alsa-mixer.c:4534 +#: spa/plugins/alsa/acp/alsa-mixer.c:4530 +#: spa/plugins/alsa/acp/alsa-mixer.c:4538 +#: spa/plugins/alsa/acp/alsa-mixer.c:4539 msgid "Analog Stereo" msgstr "類比立體聲" -#: spa/plugins/alsa/acp/alsa-mixer.c:4526 +#: spa/plugins/alsa/acp/alsa-mixer.c:4531 msgid "Mono" msgstr "單聲道" -#: spa/plugins/alsa/acp/alsa-mixer.c:4527 +#: spa/plugins/alsa/acp/alsa-mixer.c:4532 msgid "Stereo" 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/alsa/acp/alsa-mixer.c:4540 +#: spa/plugins/alsa/acp/alsa-mixer.c:4698 +#: spa/plugins/bluez5/bluez5-device.c:1135 msgid "Headset" msgstr "耳麥" -#: spa/plugins/alsa/acp/alsa-mixer.c:4536 -#: spa/plugins/alsa/acp/alsa-mixer.c:4694 +#: spa/plugins/alsa/acp/alsa-mixer.c:4541 +#: spa/plugins/alsa/acp/alsa-mixer.c:4699 +#, fuzzy msgid "Speakerphone" -msgstr "會議揚聲器" +msgstr "喇叭" -#: spa/plugins/alsa/acp/alsa-mixer.c:4537 -#: spa/plugins/alsa/acp/alsa-mixer.c:4538 +#: spa/plugins/alsa/acp/alsa-mixer.c:4542 +#: spa/plugins/alsa/acp/alsa-mixer.c:4543 msgid "Multichannel" msgstr "多聲道" -#: spa/plugins/alsa/acp/alsa-mixer.c:4539 +#: spa/plugins/alsa/acp/alsa-mixer.c:4544 msgid "Analog Surround 2.1" msgstr "類比環繞聲 2.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4540 +#: spa/plugins/alsa/acp/alsa-mixer.c:4545 msgid "Analog Surround 3.0" msgstr "類比環繞聲 3.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4541 +#: spa/plugins/alsa/acp/alsa-mixer.c:4546 msgid "Analog Surround 3.1" msgstr "類比環繞聲 3.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4542 +#: spa/plugins/alsa/acp/alsa-mixer.c:4547 msgid "Analog Surround 4.0" msgstr "類比環繞聲 4.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4543 +#: spa/plugins/alsa/acp/alsa-mixer.c:4548 msgid "Analog Surround 4.1" msgstr "類比環繞聲 4.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4544 +#: spa/plugins/alsa/acp/alsa-mixer.c:4549 msgid "Analog Surround 5.0" msgstr "類比環繞聲 5.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4545 +#: spa/plugins/alsa/acp/alsa-mixer.c:4550 msgid "Analog Surround 5.1" msgstr "類比環繞聲 5.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4546 +#: spa/plugins/alsa/acp/alsa-mixer.c:4551 msgid "Analog Surround 6.0" msgstr "類比環繞聲 6.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4547 +#: spa/plugins/alsa/acp/alsa-mixer.c:4552 msgid "Analog Surround 6.1" msgstr "類比環繞聲 6.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4548 +#: spa/plugins/alsa/acp/alsa-mixer.c:4553 msgid "Analog Surround 7.0" msgstr "類比環繞聲 7.0" -#: spa/plugins/alsa/acp/alsa-mixer.c:4549 +#: spa/plugins/alsa/acp/alsa-mixer.c:4554 msgid "Analog Surround 7.1" msgstr "類比環繞聲 7.1" -#: spa/plugins/alsa/acp/alsa-mixer.c:4550 +#: spa/plugins/alsa/acp/alsa-mixer.c:4555 msgid "Digital Stereo (IEC958)" msgstr "數位立體聲 (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4551 +#: spa/plugins/alsa/acp/alsa-mixer.c:4556 msgid "Digital Surround 4.0 (IEC958/AC3)" msgstr "數位環繞聲 4.0 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4552 +#: spa/plugins/alsa/acp/alsa-mixer.c:4557 msgid "Digital Surround 5.1 (IEC958/AC3)" msgstr "數位環繞聲 5.1 (IEC958/AC3)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4553 +#: spa/plugins/alsa/acp/alsa-mixer.c:4558 msgid "Digital Surround 5.1 (IEC958/DTS)" msgstr "數位環繞聲 5.1 (IEC958/DTS)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4554 +#: spa/plugins/alsa/acp/alsa-mixer.c:4559 msgid "Digital Stereo (HDMI)" msgstr "數位立體聲 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4555 +#: spa/plugins/alsa/acp/alsa-mixer.c:4560 msgid "Digital Surround 5.1 (HDMI)" msgstr "數位環繞聲 5.1 (HDMI)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4556 +#: spa/plugins/alsa/acp/alsa-mixer.c:4561 msgid "Chat" -msgstr "聊天" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4557 +#: spa/plugins/alsa/acp/alsa-mixer.c:4562 msgid "Game" -msgstr "遊戲" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4691 +#: spa/plugins/alsa/acp/alsa-mixer.c:4696 msgid "Analog Mono Duplex" msgstr "類比單聲道雙工" -#: spa/plugins/alsa/acp/alsa-mixer.c:4692 +#: spa/plugins/alsa/acp/alsa-mixer.c:4697 msgid "Analog Stereo Duplex" msgstr "類比立體聲雙工" -#: spa/plugins/alsa/acp/alsa-mixer.c:4695 +#: spa/plugins/alsa/acp/alsa-mixer.c:4700 msgid "Digital Stereo Duplex (IEC958)" msgstr "數位立體聲雙工 (IEC958)" -#: spa/plugins/alsa/acp/alsa-mixer.c:4696 +#: spa/plugins/alsa/acp/alsa-mixer.c:4701 msgid "Multichannel Duplex" msgstr "多聲道雙工" -#: spa/plugins/alsa/acp/alsa-mixer.c:4697 +#: spa/plugins/alsa/acp/alsa-mixer.c:4702 msgid "Stereo Duplex" msgstr "立體聲雙工" -#: spa/plugins/alsa/acp/alsa-mixer.c:4698 +#: spa/plugins/alsa/acp/alsa-mixer.c:4703 msgid "Mono Chat + 7.1 Surround" -msgstr "單聲道聊天 + 7.1 立體聲" +msgstr "" -#: spa/plugins/alsa/acp/alsa-mixer.c:4799 +#: spa/plugins/alsa/acp/alsa-mixer.c:4806 #, c-format msgid "%s Output" msgstr "%s 輸出" -#: spa/plugins/alsa/acp/alsa-mixer.c:4807 +#: spa/plugins/alsa/acp/alsa-mixer.c:4813 #, c-format msgid "%s Input" msgstr "%s 輸入" -#: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327 +#: spa/plugins/alsa/acp/alsa-util.c:1175 spa/plugins/alsa/acp/alsa-util.c:1269 #, c-format msgid "" "snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu " @@ -598,23 +481,23 @@ msgstr[0] "" "snd_pcm_avail() 傳回超出預期的大值:%lu bytes (%lu ms)。\n" "這很能是 ALSA 驅動程式「%s」的臭蟲。請回報這個問題給 ALSA 開發者。" -#: spa/plugins/alsa/acp/alsa-util.c:1299 +#: spa/plugins/alsa/acp/alsa-util.c:1241 #, 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:1346 +#: spa/plugins/alsa/acp/alsa-util.c:1288 #, c-format msgid "" "snd_pcm_avail_delay() returned strange values: delay %lu is less than avail " @@ -625,7 +508,7 @@ msgstr "" "snd_pcm_avail_delay() 傳回超出預期的大值:延遲 %lu 少於可用的 %lu。\n" "這很能是 ALSA 驅動程式「%s」的臭蟲。請回報這個問題給 ALSA 開發者。" -#: spa/plugins/alsa/acp/alsa-util.c:1389 +#: spa/plugins/alsa/acp/alsa-util.c:1331 #, c-format msgid "" "snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte " @@ -641,115 +524,62 @@ msgstr[0] "" "snd_pcm_mmap_begin() 傳回超出預期的大值:%lu bytes (%lu ms)。\n" "這很能是 ALSA 驅動程式「%s」的臭蟲。請回報這個問題給 ALSA 開發者。" -#: 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 +#: spa/plugins/bluez5/bluez5-device.c:1010 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" -msgstr "音訊閘道 (A2DP Source & HSP/HFP AG)" +msgstr "" -#: 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 +#: spa/plugins/bluez5/bluez5-device.c:1033 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" -msgstr "高傳真播放裝置 (A2DP Sink,編碼器 %s)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2107 +#: spa/plugins/bluez5/bluez5-device.c:1035 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" -msgstr "高傳真雙工裝置 (A2DP Source/Sink,編碼器 %s)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2115 +#: spa/plugins/bluez5/bluez5-device.c:1041 msgid "High Fidelity Playback (A2DP Sink)" -msgstr "高傳真播放裝置 (A2DP Sink)" +msgstr "" -#: spa/plugins/bluez5/bluez5-device.c:2117 +#: spa/plugins/bluez5/bluez5-device.c:1043 msgid "High Fidelity Duplex (A2DP Source/Sink)" -msgstr "高傳真雙工裝置 (A2DP Source/Sink)" +msgstr "" -#: 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 +#: spa/plugins/bluez5/bluez5-device.c:1070 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" -msgstr "耳麥頭戴單元 (HSP/HFP,編碼器 %s)" +msgstr "" -#: 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:1074 +msgid "Headset Head Unit (HSP/HFP)" +msgstr "" + +#: spa/plugins/bluez5/bluez5-device.c:1140 msgid "Handsfree" msgstr "免持裝置" -#: spa/plugins/bluez5/bluez5-device.c:2417 -msgid "Handsfree (HFP)" -msgstr "免持裝置 (HFP)" +#: spa/plugins/bluez5/bluez5-device.c:1155 +msgid "Headphone" +msgstr "頭戴式耳機" -#: spa/plugins/bluez5/bluez5-device.c:2440 +#: spa/plugins/bluez5/bluez5-device.c:1160 msgid "Portable" msgstr "可攜裝置" -#: spa/plugins/bluez5/bluez5-device.c:2446 +#: spa/plugins/bluez5/bluez5-device.c:1165 msgid "Car" msgstr "汽車" -#: spa/plugins/bluez5/bluez5-device.c:2452 +#: spa/plugins/bluez5/bluez5-device.c:1170 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:2458 +#: spa/plugins/bluez5/bluez5-device.c:1175 msgid "Phone" msgstr "手機" -#: spa/plugins/bluez5/bluez5-device.c:2465 +#: spa/plugins/bluez5/bluez5-device.c:1181 +#, fuzzy msgid "Bluetooth" -msgstr "藍牙" - -#: spa/plugins/bluez5/bluez5-device.c:2466 -msgid "Bluetooth Handsfree" -msgstr "藍牙免持裝置" - -#~ msgid "Headphone" -#~ msgstr "頭戴式耳機" +msgstr "藍牙輸入" diff --git a/spa/include/spa/param/audio/dsd-utils.h b/spa/include/spa/param/audio/dsd-utils.h index 9af62d99d..2dab7cc19 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 -EINVAL; + return -ECHRNG; 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 45cca3a6f..33650202a 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 -EINVAL; + return -ECHRNG; 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 -EINVAL; + return -ECHRNG; *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 11540a6b0..6b1b25164 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 -EINVAL; + return -ECHRNG; 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 -EINVAL; + return -ECHRNG; 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 -EINVAL; + return -ECHRNG; 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 1e4ae2758..3f81b4a92 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 -EINVAL; + return -ECHRNG; 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 90eadb82e..4ab5d8e5f 100644 --- a/spa/include/spa/pod/body.h +++ b/spa/include/spa/pod/body.h @@ -111,15 +111,8 @@ 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) + if (offset < 0 || offset > (int64_t)UINT32_MAX) 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 2faf42154..c69338855 100644 --- a/spa/include/spa/support/cpu.h +++ b/spa/include/spa/support/cpu.h @@ -62,7 +62,6 @@ 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 56ceea648..07a31a55f 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 1 +#define SPA_VERSION_SYSTEM 0 struct spa_system { struct spa_interface iface; }; /* IO events */ @@ -59,18 +59,11 @@ struct spa_system { struct spa_interface iface; }; struct spa_poll_event { uint32_t events; - union { - void *data; - uint64_t data_u64; - }; -#ifdef __x86_64__ -} __attribute__((packed)); -#else + void *data; }; -#endif struct spa_system_methods { -#define SPA_VERSION_SYSTEM_METHODS 1 +#define SPA_VERSION_SYSTEM_METHODS 0 uint32_t version; /* read/write/ioctl */ @@ -158,7 +151,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, 1, pfd, ev, n_ev, timeout); + return spa_api_method_fast_r(int, -ENOTSUP, spa_system, &object->iface, pollfd_wait, 0, pfd, ev, n_ev, timeout); } SPA_API_SYSTEM int spa_system_timerfd_create(struct spa_system *object, int clockid, int flags) diff --git a/spa/include/spa/utils/endian.h b/spa/include/spa/utils/endian.h index 0793ed412..2d002d453 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(__MidnightBSD__) +#if defined(__FreeBSD__) || 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 deleted file mode 100644 index b41ea9774..000000000 --- a/spa/include/spa/utils/json-builder.h +++ /dev/null @@ -1,445 +0,0 @@ -/* 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 9745000cf..5616bffe1 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,10 +399,6 @@ 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) @@ -425,11 +421,20 @@ 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], *end; + char buf[96]; + char *end; + int pos; if (len <= 0 || len >= (int)sizeof(buf)) return 0; + for (pos = 0; pos < len; ++pos) { + switch (val[pos]) { + case '+': case '-': case '0' ... '9': case '.': case 'e': case 'E': break; + default: return 0; + } + } + memcpy(buf, val, len); buf[len] = '\0'; @@ -457,7 +462,8 @@ 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], *end; + char buf[64]; + char *end; if (len <= 0 || len >= (int)sizeof(buf)) return 0; @@ -474,39 +480,6 @@ 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) { @@ -537,46 +510,6 @@ 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 35e4ea026..bab60de2b 100644 --- a/spa/include/spa/utils/string.h +++ b/spa/include/spa/utils/string.h @@ -380,26 +380,17 @@ 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 = spa_strbuf_appendv(buf, fmt, args); + written = vsnprintf(&buf->buffer[buf->pos], remain, 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 3ded776ad..0aa35fae3 100644 --- a/spa/lib/lib.c +++ b/spa/lib/lib.c @@ -2,6 +2,7 @@ #undef SPA_AUDIO_MAX_CHANNELS #define SPA_API_IMPL SPA_EXPORT +#include #include #include #include @@ -125,17 +126,16 @@ #include #include #include -#include #include #include #include #include #include #include +#include #include #include #include -#include #include #include #include @@ -158,7 +158,6 @@ #include #include #include -#include #include #include #include diff --git a/spa/plugins/aec/aec-webrtc.cpp b/spa/plugins/aec/aec-webrtc.cpp index 2e451d851..7b8cafcde 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.aec.webrtc"); +SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.eac.webrtc"); #undef SPA_LOG_TOPIC_DEFAULT #define SPA_LOG_TOPIC_DEFAULT &log_topic @@ -221,11 +221,6 @@ 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 824d69df6..c12401100 100644 --- a/spa/plugins/alsa/acp-tool.c +++ b/spa/plugins/alsa/acp-tool.c @@ -10,9 +10,7 @@ #include #include #include -#ifdef __linux__ #include -#endif #include #include diff --git a/spa/plugins/alsa/acp/compat.h b/spa/plugins/alsa/acp/compat.h index 87151d197..f7592e1a6 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 const char *pa_path_get_filename(const char *p) +static inline char *pa_path_get_filename(const char *p) { - const char *fn; + char *fn; if (!p) return NULL; if ((fn = strrchr(p, PA_PATH_SEP_CHAR))) return fn+1; - return p; + return (char*) p; } static inline bool pa_is_path_absolute(const char *fn) diff --git a/spa/plugins/alsa/alsa-acp-device.c b/spa/plugins/alsa/alsa-acp-device.c index eb38b3f9e..44342a7a3 100644 --- a/spa/plugins/alsa/alsa-acp-device.c +++ b/spa/plugins/alsa/alsa-acp-device.c @@ -990,22 +990,11 @@ static void card_port_available(void *data, uint32_t index, for (i = 0; i < p->n_devices; i++) { struct acp_device *d = p->devices[i]; - struct acp_port *active_port = NULL; - uint32_t j; uint32_t best; if (!(d->flags & ACP_DEVICE_ACTIVE)) continue; - for (j = 0; j < d->n_ports; j++) { - if (d->ports[j]->flags & ACP_PORT_ACTIVE) { - active_port = d->ports[j]; - break; - } - } - if (active_port != NULL && active_port->available != ACP_AVAILABLE_NO) - continue; - best = acp_device_find_best_port_index(d, NULL); acp_device_set_port(d, best, 0); } diff --git a/spa/plugins/alsa/alsa-seq-bridge.c b/spa/plugins/alsa/alsa-seq-bridge.c index 977c74e0a..06c5bc28f 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[7]; + struct spa_dict_item items[6]; uint32_t n_items = 0; int card_id; snd_seq_port_info_t *info; @@ -261,7 +261,7 @@ static void emit_port_info(struct seq_state *this, struct seq_port *port, bool f if (spa_strstartswith(pn, client_name)) pn += strlen(client_name); - snprintf(name, sizeof(name), "%s%s:%s (%s)", prefix, + snprintf(name, sizeof(name), "%s%s%s (%s)", prefix, client_name, pn, dir); clean_name(name); @@ -284,9 +284,6 @@ 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, @@ -388,29 +385,13 @@ static struct seq_port *alloc_port(struct seq_state *state, struct seq_stream *s return port; } -static int do_port_clear(struct spa_loop *loop, bool async, uint32_t seq, - const void *data, size_t size, void *user_data) -{ - struct seq_port *port = user_data; - port->io = NULL; - if (port->mixing) { - spa_list_remove(&port->mix_link); - port->mixing = false; - } - return 0; -} - static void free_port(struct seq_state *state, struct seq_stream *stream, struct seq_port *port) { stream->ports[port->id] = NULL; spa_list_remove(&port->link); - spa_loop_locked(state->data_loop, - do_port_clear, SPA_ID_INVALID, NULL, 0, port); - spa_node_emit_port_info(&state->hooks, port->direction, port->id, NULL); - spa_zero(*port); spa_list_append(&state->free_list, &port->link); } @@ -457,7 +438,7 @@ static void update_stream_port(struct seq_state *state, struct seq_stream *strea struct seq_port *port = find_port(state, stream, addr); if (info == NULL) { - spa_log_debug(state->log, "free port %d.%d %p", addr->client, addr->port, port); + spa_log_debug(state->log, "free port %d.%d", addr->client, addr->port); if (port) free_port(state, stream, port); } else { @@ -469,7 +450,7 @@ static void update_stream_port(struct seq_state *state, struct seq_stream *strea init_port(state, port, addr, snd_seq_port_info_get_type(info)); } else if (port != NULL) { if ((caps & stream->caps) != stream->caps) { - spa_log_debug(state->log, "free port %d.%d %p", addr->client, addr->port, port); + spa_log_debug(state->log, "free port %d.%d", addr->client, addr->port); free_port(state, stream, port); } else { @@ -486,8 +467,8 @@ static int on_port_info(void *data, const snd_seq_addr_t *addr, const snd_seq_po struct seq_state *state = data; if (info == NULL) { - update_stream_port(state, &state->streams[SPA_DIRECTION_INPUT], addr, 0, NULL); - update_stream_port(state, &state->streams[SPA_DIRECTION_OUTPUT], addr, 0, NULL); + update_stream_port(state, &state->streams[SPA_DIRECTION_INPUT], addr, 0, info); + update_stream_port(state, &state->streams[SPA_DIRECTION_OUTPUT], addr, 0, info); } else { unsigned int caps = snd_seq_port_info_get_capability(info); @@ -520,7 +501,6 @@ 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; @@ -544,18 +524,10 @@ impl_node_port_enum_params(void *object, int seq, case SPA_PARAM_EnumFormat: if (result.index > 0) return 0; - spa_pod_builder_push_object(&b, &f[0], - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(&b, + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), - 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]); + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); break; case SPA_PARAM_Format: @@ -563,18 +535,10 @@ impl_node_port_enum_params(void *object, int seq, return -EIO; if (result.index > 0) return 0; - spa_pod_builder_push_object(&b, &f[0], - SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat); - spa_pod_builder_add(&b, + param = spa_pod_builder_add_object(&b, + SPA_TYPE_OBJECT_Format, SPA_PARAM_Format, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), - SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), - 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]); + SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control)); break; case SPA_PARAM_Buffers: @@ -836,7 +800,7 @@ impl_node_port_set_io(void *object, info.data = data; info.size = size; - spa_log_debug(this->log, "%p: %p: io %d.%d %d %p %zd", this, port, + spa_log_debug(this->log, "%p: io %d.%d %d %p %zd", this, direction, port_id, id, data, size); switch (id) { @@ -857,7 +821,7 @@ static int impl_node_port_reuse_buffer(void *object, uint32_t port_id, uint32_t spa_return_val_if_fail(this != NULL, -EINVAL); - spa_return_val_if_fail(CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); + spa_return_val_if_fail(!CHECK_PORT(this, SPA_DIRECTION_OUTPUT, port_id), -EINVAL); port = GET_PORT(this, SPA_DIRECTION_OUTPUT, port_id); @@ -991,7 +955,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 = false; + this->ump = true; 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 c25b49420..8a4ba369c 100644 --- a/spa/plugins/alsa/alsa-seq.c +++ b/spa/plugins/alsa/alsa-seq.c @@ -620,8 +620,9 @@ static int process_read(struct seq_state *state) { struct seq_stream *stream = &state->streams[SPA_DIRECTION_OUTPUT]; const bool ump = state->ump; - void *data; + uint32_t *data; uint8_t midi1_data[MAX_EVENT_SIZE]; + uint32_t ump_data[MAX_EVENT_SIZE]; long size; int res = -1; struct seq_port *port; @@ -632,6 +633,9 @@ 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) { @@ -698,7 +702,7 @@ static int process_read(struct seq_state *state) #ifdef HAVE_ALSA_UMP snd_seq_ump_event_t *ev = event; - data = &ev->ump[0]; + data = (uint32_t*)&ev->ump[0]; size = spa_ump_message_size(snd_ump_msg_hdr_type(ev->ump[0])) * 4; #else spa_assert_not_reached(); @@ -711,21 +715,34 @@ static int process_read(struct seq_state *state) spa_log_warn(state->log, "decode failed: %s", snd_strerror(size)); continue; } - data = midi1_data; + + midi1_ptr = midi1_data; + midi1_size = size; } - 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); + 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_pod_builder_control(&port->builder, offset, ump ? SPA_CONTROL_UMP : SPA_CONTROL_Midi ); - spa_pod_builder_bytes(&port->builder, data, size); + 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); - /* 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; + 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); } done: @@ -802,6 +819,7 @@ 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) @@ -826,6 +844,9 @@ 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; @@ -840,9 +861,6 @@ 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); @@ -860,26 +878,26 @@ static int process_write(struct seq_state *state) #endif } else { snd_seq_event_t ev; - int size = 0; - long s; - - if (c.type != SPA_CONTROL_Midi) - continue; + uint8_t data[MAX_EVENT_SIZE]; + int size; + uint64_t st = 0; while (body_size > 0) { - if (size == 0) + if ((size = spa_ump_to_midi((const uint32_t **)&body, &body_size, + data, sizeof(data), &st)) <= 0) + break; + + if (first) snd_seq_ev_clear(&ev); - if ((s = snd_midi_event_encode(stream->codec, body, body_size, &ev)) < 0) { + if ((size = snd_midi_event_encode(stream->codec, data, size, &ev)) < 0) { spa_log_warn(state->log, "failed to encode event: %s", snd_strerror(size)); snd_midi_event_reset_encode(stream->codec); - size = 0; + first = true; continue; } - body += s; - body_size -= s; - size += s; + first = false; 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. */ @@ -895,7 +913,7 @@ static int process_write(struct seq_state *state) spa_log_warn(state->log, "failed to output event: %s", snd_strerror(err)); } - size = 0; + first = true; } } } diff --git a/spa/plugins/alsa/alsa-seq.h b/spa/plugins/alsa/alsa-seq.h index 3100b9587..354fa1d5d 100644 --- a/spa/plugins/alsa/alsa-seq.h +++ b/spa/plugins/alsa/alsa-seq.h @@ -82,8 +82,6 @@ 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 9c3e12f20..fdc255864 100644 --- a/spa/plugins/alsa/alsa-udev.c +++ b/spa/plugins/alsa/alsa-udev.c @@ -48,7 +48,6 @@ 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 @@ -60,10 +59,6 @@ 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) @@ -97,8 +92,6 @@ 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; @@ -360,87 +353,6 @@ 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) { @@ -822,11 +734,7 @@ static void process_card(struct impl *this, enum action action, struct card *car return; check_access(this, card); - check_wireless_status(this, card); - - bool effective_accessible = card->accessible && !card->wireless_disconnected; - - if (effective_accessible && !card->emitted) { + if (card->accessible && !card->emitted) { int res = emit_added_object_info(this, card); if (res < 0) { if (card->ignored) @@ -845,7 +753,7 @@ static void process_card(struct impl *this, enum action action, struct card *car card->card_nr); card->unavailable = false; } - } else if (!effective_accessible && card->emitted) { + } else if (!card->accessible && card->emitted) { card->emitted = false; if (card->pcm_device_id != ID_DEVICE_NOT_SUPPORTED) @@ -882,11 +790,8 @@ 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; @@ -1013,41 +918,6 @@ 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; @@ -1071,20 +941,6 @@ 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; @@ -1102,12 +958,6 @@ 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/alsa/mixer/paths/analog-output-headphones.conf b/spa/plugins/alsa/mixer/paths/analog-output-headphones.conf index 0e698bd9c..3c62c5e67 100644 --- a/spa/plugins/alsa/mixer/paths/analog-output-headphones.conf +++ b/spa/plugins/alsa/mixer/paths/analog-output-headphones.conf @@ -93,12 +93,6 @@ volume = merge override-map.1 = all override-map.2 = all-left,all-right -; Keep Line Out disabled in the headphones path so selecting headphones -; does not also drive speaker/line outputs on shared controls. -[Element Line Out] -switch = off -volume = off - ; This path is intended to control the first headphones, not ; the second headphones. But it should not hurt if we leave the second ; headphone jack enabled nonetheless. diff --git a/spa/plugins/alsa/mixer/paths/analog-output-lineout.conf b/spa/plugins/alsa/mixer/paths/analog-output-lineout.conf index a6fcfcb83..1ffce2225 100644 --- a/spa/plugins/alsa/mixer/paths/analog-output-lineout.conf +++ b/spa/plugins/alsa/mixer/paths/analog-output-lineout.conf @@ -113,23 +113,10 @@ override-map.1 = all override-map.2 = all-left,all-right required-any = any -[Element Line Out] -switch = mute -volume = merge -override-map.1 = all -override-map.2 = all-left,all-right - [Element Master Mono] switch = off volume = off -; Prefer manual routing decisions by disabling codec auto-mute here. -[Element Auto-Mute Mode] -enumeration = select - -[Option Auto-Mute Mode:Disabled] -name = analog-output-lineout - [Element Line HP Swap] switch = off required-any = any diff --git a/spa/plugins/audioconvert/audioadapter.c b/spa/plugins/audioconvert/audioadapter.c index 51d87d4e3..ccc9f48df 100644 --- a/spa/plugins/audioconvert/audioadapter.c +++ b/spa/plugins/audioconvert/audioadapter.c @@ -1086,8 +1086,6 @@ static int impl_node_send_command(void *object, const struct spa_command *comman spa_log_debug(this->log, "%p: suspending", this); break; case SPA_NODE_COMMAND_Pause: - if ((res = negotiate_format(this)) < 0) - return res; spa_log_debug(this->log, "%p: pausing", this); break; case SPA_NODE_COMMAND_Flush: diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index d1b5cd683..e9355ca41 100644 --- a/spa/plugins/audioconvert/audioconvert.c +++ b/spa/plugins/audioconvert/audioconvert.c @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -269,7 +268,6 @@ 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; @@ -724,34 +722,6 @@ 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"), @@ -760,7 +730,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 23: + case 20: 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"), @@ -779,14 +749,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 24: + case 21: *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 25: + case 22: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_quality), @@ -795,7 +765,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 26: + case 23: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("resample.disable"), @@ -803,7 +773,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 27: + case 24: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("dither.noise"), @@ -811,7 +781,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 28: + case 25: 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"), @@ -829,7 +799,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 29: + case 26: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("debug.wav-path"), @@ -837,7 +807,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 30: + case 27: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("channelmix.lock-volumes"), @@ -845,7 +815,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 31: + case 28: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph.disable"), @@ -853,7 +823,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 32: + case 29: *param = spa_pod_builder_add_object(b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_name, SPA_POD_String("audioconvert.filter-graph.N"), @@ -864,7 +834,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 - 33, b, param); + index - 30, b, param); } return 0; } @@ -876,7 +846,6 @@ 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: @@ -931,12 +900,6 @@ 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"); @@ -955,12 +918,8 @@ 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_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_string(b, "audioconvert.filter-graph"); + spa_pod_builder_string(b, ""); spa_pod_builder_pop(b, &f[1]); *param = spa_pod_builder_pop(b, &f[0]); break; @@ -994,7 +953,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[16384]; + uint8_t buffer[4096]; struct spa_result_node_params result; uint32_t count = 0; int res = 0; @@ -1450,7 +1409,6 @@ 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') { @@ -1476,8 +1434,6 @@ 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) @@ -1524,12 +1480,6 @@ 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")) @@ -2165,7 +2115,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.func_cpu_flags, in->conv.is_passthrough, + this->cpu_flags, in->conv.cpu_flags, in->conv.is_passthrough, remap, in->conv.func_name); return 0; @@ -2322,7 +2272,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.func_cpu_flags, + this, this->cpu_flags, this->mix.cpu_flags, this->mix.flags, this->mix.func_name); return 0; } @@ -2370,7 +2320,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.func_cpu_flags, + this, this->cpu_flags, this->resample.cpu_flags, this->resample.func_name); return res; } @@ -2462,7 +2412,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.func_cpu_flags, out->conv.method, + this->cpu_flags, out->conv.cpu_flags, out->conv.method, out->conv.noise_bits, out->conv.is_passthrough, remap, out->conv.func_name); return 0; @@ -2642,11 +2592,8 @@ static int impl_node_send_command(void *object, const struct spa_command *comman break; case SPA_NODE_COMMAND_Suspend: reset_node(this); - this->started = false; - break; + SPA_FALLTHROUGH; case SPA_NODE_COMMAND_Pause: - if ((res = setup_convert(this)) < 0) - return res; this->started = false; break; case SPA_NODE_COMMAND_Flush: @@ -4287,7 +4234,6 @@ 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); @@ -4299,10 +4245,6 @@ 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); @@ -4357,13 +4299,18 @@ 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; - channelmix_reset(&this->mix); + 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; 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 e59f1f56b..9ea43ec65 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 = { - .n_channels = n_channels, - }; + struct convert conv; + + 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 deleted file mode 100644 index 08d8e2b00..000000000 --- a/spa/plugins/audioconvert/channelmix-ops-avx.c +++ /dev/null @@ -1,63 +0,0 @@ -/* 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 574d0dd1b..12edb4b5a 100644 --- a/spa/plugins/audioconvert/channelmix-ops.c +++ b/spa/plugins/audioconvert/channelmix-ops.c @@ -36,11 +36,6 @@ 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), @@ -198,9 +193,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 = mix->center_level; - float slev = mix->surround_level; - float llev = mix->lfe_level; + float clev = SQRT1_2; + float slev = SQRT1_2; + float llev = 0.5f; float maxsum = 0.0f; bool filter_fc = false, filter_lfe = false, matched = false, normalize; #define _MATRIX(s,d) matrix[_CH(s)][_CH(d)] @@ -725,7 +720,7 @@ done: if (src_paired == 0) src_paired = ~0LU; - for (jc = 0, ic = 0, i = 0; ic < dst_chan && i < MAX_CHANNELS; i++) { + for (jc = 0, ic = 0, i = 0; ic < dst_chan; i++) { float sum = 0.0f; char str1[1024], str2[1024]; struct spa_strbuf sb1, sb2; @@ -735,7 +730,7 @@ done: if (i < CHANNEL_BITS && (dst_paired & (1UL << i)) == 0) continue; - for (jc = 0, j = 0; jc < src_chan && j < MAX_CHANNELS; j++) { + for (jc = 0, j = 0; jc < src_chan; j++) { if (j < CHANNEL_BITS && (src_paired & (1UL << j)) == 0) continue; @@ -874,21 +869,6 @@ 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; @@ -905,8 +885,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 c66eaddd3..6ea2b9451 100644 --- a/spa/plugins/audioconvert/channelmix-ops.h +++ b/spa/plugins/audioconvert/channelmix-ops.h @@ -28,17 +28,6 @@ #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; @@ -55,7 +44,6 @@ 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 */ @@ -71,9 +59,6 @@ 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]; @@ -94,7 +79,6 @@ struct channelmix { void *data; }; -void channelmix_reset(struct channelmix *mix); int channelmix_init(struct channelmix *mix); static const struct channelmix_upmix_info { @@ -155,8 +139,4 @@ 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 9c3dce52d..a939da458 100644 --- a/spa/plugins/audioconvert/fmt-ops-avx2.c +++ b/spa/plugins/audioconvert/fmt-ops-avx2.c @@ -4,8 +4,6 @@ #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) @@ -32,38 +30,6 @@ _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) @@ -287,7 +253,7 @@ conv_s16s_to_f32d_2_avx2(struct convert *conv, void * SPA_RESTRICT dst[], const } static void -conv_s24_to_f32d_1s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, +conv_s24_to_f32d_1s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int8_t *s = src; @@ -323,7 +289,7 @@ conv_s24_to_f32d_1s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const voi } static void -conv_s24_to_f32d_2s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, +conv_s24_to_f32d_2s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int8_t *s = src; @@ -375,7 +341,7 @@ conv_s24_to_f32d_2s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const voi } } static void -conv_s24_to_f32d_4s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, +conv_s24_to_f32d_4s_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, uint32_t n_channels, uint32_t n_samples) { const int8_t *s = src; @@ -431,13 +397,18 @@ conv_s24_to_f32d_4s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const voi s += 12 * n_channels; } for(; n < n_samples; n++) { - 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]); + 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]); s += 3 * n_channels; } } @@ -449,22 +420,16 @@ 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; - 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); - } + 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); } static void -conv_s32_to_f32d_4s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, +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; @@ -508,17 +473,24 @@ conv_s32_to_f32d_4s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const voi } 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]); + 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]); s += n_channels; } } static void -conv_s32_to_f32d_2s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, +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; @@ -563,7 +535,7 @@ conv_s32_to_f32d_2s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const voi } static void -conv_s32_to_f32d_1s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src, +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; @@ -603,169 +575,6 @@ conv_s32_to_f32d_1s_gather_avx2(void *data, void * SPA_RESTRICT dst[], const voi } } - -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) @@ -773,21 +582,12 @@ 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; - 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); - } + 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); } static void @@ -812,10 +612,14 @@ 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]); - _MM_STOREM_EPI32(&d[0*n_channels], - &d[1*n_channels], - &d[2*n_channels], - &d[3*n_channels], 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]); d += 4*n_channels; } for(; n < n_samples; n++) { @@ -970,7 +774,15 @@ 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_setr_ps(s0[n], s1[n], s2[n], s3[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_mul_ps(in[0], scale); in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); @@ -1160,16 +972,18 @@ 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_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]); + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_scale); + in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); + in[2] = _MM_CLAMP_SS(in[2], int_min, int_max); + in[3] = _MM_CLAMP_SS(in[3], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d[2] = _mm_cvtss_si32(in[2]); d[3] = _mm_cvtss_si32(in[3]); - d += n_channels; } } @@ -1241,10 +1055,14 @@ 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_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]); + in[0] = _mm_mul_ss(_mm_load_ss(&s0[n]), int_scale); + in[1] = _mm_mul_ss(_mm_load_ss(&s1[n]), int_scale); + in[2] = _mm_mul_ss(_mm_load_ss(&s2[n]), int_scale); + in[3] = _mm_mul_ss(_mm_load_ss(&s3[n]), int_scale); + in[0] = _MM_CLAMP_SS(in[0], int_min, int_max); + in[1] = _MM_CLAMP_SS(in[1], int_min, int_max); + in[2] = _MM_CLAMP_SS(in[2], int_min, int_max); + in[3] = _MM_CLAMP_SS(in[3], int_min, int_max); d[0] = _mm_cvtss_si32(in[0]); d[1] = _mm_cvtss_si32(in[1]); d[2] = _mm_cvtss_si32(in[2]); @@ -1367,4 +1185,3 @@ 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 dcba6d2e1..ee5c89c06 100644 --- a/spa/plugins/audioconvert/fmt-ops-sse2.c +++ b/spa/plugins/audioconvert/fmt-ops-sse2.c @@ -26,72 +26,6 @@ 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) @@ -299,6 +233,18 @@ 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) @@ -470,13 +416,18 @@ conv_s24_to_f32d_4s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA s += 4 * n_channels; } for(; n < n_samples; n++) { - 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]); + 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]); s += n_channels; } } @@ -496,59 +447,6 @@ 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) @@ -589,8 +487,6 @@ 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); } @@ -617,10 +513,14 @@ 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]); - _MM_STOREM_EPI32(&d[0*n_channels], - &d[1*n_channels], - &d[2*n_channels], - &d[3*n_channels], 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]); d += 4*n_channels; } for(; n < n_samples; n++) { @@ -730,7 +630,15 @@ 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_setr_ps(s0[n], s1[n], s2[n], s3[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_mul_ps(in[0], scale); in[0] = _MM_CLAMP_PS(in[0], int_min, int_max); out[0] = _mm_cvtps_epi32(in[0]); @@ -846,10 +754,14 @@ 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]); - _MM_STOREM_EPI32(&d[0*n_channels], - &d[1*n_channels], - &d[2*n_channels], - &d[3*n_channels], 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]); d += 4*n_channels; } for(; n < n_samples; n++) { @@ -898,10 +810,14 @@ 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]); - _MM_STOREM_EPI32(&d[0*n_channels], - &d[1*n_channels], - &d[2*n_channels], - &d[3*n_channels], 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]); d += 4*n_channels; } for(; n < n_samples; n++) { @@ -977,10 +893,14 @@ 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]); - _MM_STOREM_EPI32(&d[0*n_channels], - &d[1*n_channels], - &d[2*n_channels], - &d[3*n_channels], 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]); d += 4*n_channels; } for(; n < n_samples; n++) { @@ -1337,10 +1257,14 @@ 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]); - _MM_STOREUM_EPI32(&d[0*n_channels], - &d[1*n_channels], - &d[2*n_channels], - &d[3*n_channels], 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)); + + 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])); 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 34de40445..3fc2c5f0a 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->func_cpu_flags = info->cpu_flags; + conv->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 24b4b1aaf..f738e3858 100644 --- a/spa/plugins/audioconvert/fmt-ops.h +++ b/spa/plugins/audioconvert/fmt-ops.h @@ -219,7 +219,6 @@ 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 71d5d56cd..bd60872b6 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', simd_cargs], + c_args : [sse2_args, '-O3', '-DHAVE_SSE2'], 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', simd_cargs], + c_args : [ssse3_args, '-O3', '-DHAVE_SSSE3'], dependencies : [ spa_dep ], install : false ) @@ -65,27 +65,17 @@ endif if have_sse41 audioconvert_sse41 = static_library('audioconvert_sse41', ['fmt-ops-sse41.c'], - c_args : [sse41_args, '-O3', '-DHAVE_SSE41', simd_cargs], + c_args : [sse41_args, '-O3', '-DHAVE_SSE41'], 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', simd_cargs], + c_args : [avx2_args, fma_args, '-O3', '-DHAVE_AVX2', '-DHAVE_FMA'], dependencies : [ spa_dep ], install : false ) @@ -95,7 +85,7 @@ endif if have_avx2 audioconvert_avx2 = static_library('audioconvert_avx2', ['fmt-ops-avx2.c'], - c_args : [avx2_args, '-O3', '-DHAVE_AVX2', simd_cargs], + c_args : [avx2_args, '-O3', '-DHAVE_AVX2'], dependencies : [ spa_dep ], install : false ) diff --git a/spa/plugins/audioconvert/peaks-ops.c b/spa/plugins/audioconvert/peaks-ops.c index f7a897f90..29b93a081 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->func_cpu_flags = info->cpu_flags; + peaks->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 40b20cfbc..24092a4f7 100644 --- a/spa/plugins/audioconvert/peaks-ops.h +++ b/spa/plugins/audioconvert/peaks-ops.h @@ -14,7 +14,6 @@ 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 5bb33ffc1..3604c5b45 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->func_cpu_flags = d->info->cpu_flags; + r->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 7b6e58415..fec3bf963 100644 --- a/spa/plugins/audioconvert/resample.h +++ b/spa/plugins/audioconvert/resample.h @@ -38,7 +38,6 @@ 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 de18d524b..de3ebb8b5 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[9]; + struct spa_dict_item items[6]; const struct spa_handle_factory *factory; void *iface; @@ -76,13 +76,10 @@ 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, 9), + &SPA_DICT_INIT(items, 6), support, 1); spa_assert_se(res >= 0); diff --git a/spa/plugins/audioconvert/test-channelmix.c b/spa/plugins/audioconvert/test-channelmix.c index 529db880f..b68c956bf 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); - channelmix_reset(&mix); + spa_zero(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]; } - channelmix_reset(&mix); + spa_zero(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 d5ca414ef..17a26a351 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 = { - .n_channels = N_CHANNELS, - }; + struct convert conv; + + 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 b76ab4bec..bf6aa6909 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->func_cpu_flags = info->cpu_flags; + vol->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 51642110f..a50ee9a6f 100644 --- a/spa/plugins/audioconvert/volume-ops.h +++ b/spa/plugins/audioconvert/volume-ops.h @@ -13,7 +13,6 @@ struct volume { uint32_t cpu_flags; - uint32_t func_cpu_flags; const char *func_name; struct spa_log *log; diff --git a/spa/plugins/audiotestsrc/audiotestsrc.c b/spa/plugins/audiotestsrc/audiotestsrc.c index 3414e8b18..5e7c521b8 100644 --- a/spa/plugins/audiotestsrc/audiotestsrc.c +++ b/spa/plugins/audiotestsrc/audiotestsrc.c @@ -41,11 +41,13 @@ 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; @@ -53,6 +55,7 @@ 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; @@ -115,7 +118,9 @@ 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; @@ -157,6 +162,13 @@ 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), @@ -172,14 +184,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 1: + case 2: 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 2: + case 3: param = spa_pod_builder_add_object(&b, SPA_TYPE_OBJECT_PropInfo, id, SPA_PROP_INFO_id, SPA_POD_Id(SPA_PROP_volume), @@ -199,6 +211,7 @@ 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)); @@ -252,6 +265,7 @@ 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); @@ -259,9 +273,15 @@ 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; @@ -296,15 +316,23 @@ 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) { - 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; + 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); } - - spa_system_timerfd_settime(this->data_system, this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &ts, NULL); } static int read_timer(struct impl *this) @@ -312,12 +340,14 @@ static int read_timer(struct impl *this) uint64_t expirations; int res = 0; - 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 (this->async || this->props.live) { + if ((res = spa_system_timerfd_read(this->data_system, + this->timer_source.fd, &expirations)) < 0) { + if (res != -EAGAIN) + spa_log_error(this->log, "%p: timerfd error: %s", + this, spa_strerror(res)); + } } - return 0; } @@ -441,7 +471,10 @@ static int impl_node_send_command(void *object, const struct spa_command *comman return 0; clock_gettime(CLOCK_MONOTONIC, &now); - this->start_time = SPA_TIMESPEC_TO_NSEC(&now); + if (this->props.live) + this->start_time = SPA_TIMESPEC_TO_NSEC(&now); + else + this->start_time = 0; this->sample_count = 0; this->elapsed_time = 0; @@ -862,6 +895,9 @@ 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) @@ -935,7 +971,7 @@ static int impl_node_process(void *object) io->buffer_id = SPA_ID_INVALID; } - if (this->following) + if (!this->props.live || this->following) return make_buffer(this); else return SPA_STATUS_OK; @@ -1069,6 +1105,10 @@ 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); @@ -1077,7 +1117,9 @@ 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 | SPA_PORT_FLAG_LIVE; + port->info.flags = SPA_PORT_FLAG_NO_REF; + if (this->props.live) + port->info.flags |= 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 f4cbecd9b..c49f7a616 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_44100, 44100, 10 }, { AAC_SAMPLING_FREQ_48000, 48000, 11 }, + { AAC_SAMPLING_FREQ_44100, 44100, 10 }, { AAC_SAMPLING_FREQ_96000, 96000, 9 }, { AAC_SAMPLING_FREQ_88200, 88200, 8 }, { AAC_SAMPLING_FREQ_64000, 64000, 7 }, @@ -194,6 +194,75 @@ 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) @@ -214,10 +283,8 @@ 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. In addition AirPods set multiple bits. - * - * Some devices also set multiple bits in frequencies & channels. - * For these, pick a "preferred" choice. + * 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. */ if (!(conf.object_type & (AAC_OBJECT_TYPE_MPEG2_AAC_LC | AAC_OBJECT_TYPE_MPEG4_AAC_LC | @@ -248,35 +315,6 @@ 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)); @@ -286,7 +324,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 = "5"; + str = "0"; p->bitratemode = SPA_CLAMP(atoi(str), 0, 5); return p; @@ -331,14 +369,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 MPEG2 AAC LC which is mandatory. + * assume the device usually means AAC-LC. */ - if (conf->object_type & AAC_OBJECT_TYPE_MPEG2_AAC_LC) { - res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_MP2_AAC_LC); + 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_LC) { - res = aacEncoder_SetParam(this->aacenc, AACENC_AOT, AOT_AAC_LC); + } else 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_MPEG4_AAC_ELD) { @@ -380,12 +418,8 @@ 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->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); + this->max_bitrate = SPA_MIN(this->max_bitrate, get_valid_aac_bitrate(conf)); + this->cur_bitrate = this->max_bitrate; res = aacEncoder_SetParam(this->aacenc, AACENC_BITRATE, this->cur_bitrate); if (res != AACENC_OK) @@ -395,15 +429,6 @@ 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 f1b86e76b..b4ebfc2a2 100644 --- a/spa/plugins/bluez5/a2dp-codec-sbc.c +++ b/spa/plugins/bluez5/a2dp-codec-sbc.c @@ -343,27 +343,77 @@ 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; + a2dp_sbc_t conf; + struct spa_pod_frame f[2]; + struct spa_pod_choice *choice; + uint32_t i = 0; + uint32_t position[2]; - if ((res = codec_validate_config(codec, flags, caps, caps_size, &info)) < 0) - return res; + 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(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), + 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; + 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 64a4c25c1..4d14183e7 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -1969,9 +1969,6 @@ 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) { @@ -2100,8 +2097,6 @@ 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"); @@ -2250,8 +2245,7 @@ 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 - && rfcomm->telephony_ag) { + } else if (sscanf(token, "+CLIP: \"%16[^\"]\",%u", number, &type) == 2) { struct spa_bt_telephony_call *call; spa_list_for_each(call, &rfcomm->telephony_ag->call_list, link) { if (call->state == CALL_STATE_INCOMING && !spa_streq(number, call->line_identification)) { @@ -2262,8 +2256,7 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) break; } } - } else if (sscanf(token, "+CCWA: \"%16[^\"]\",%u", number, &type) == 2 - && rfcomm->telephony_ag) { + } else if (sscanf(token, "+CCWA: \"%16[^\"]\",%u", number, &type) == 2) { struct spa_bt_telephony_call *call; bool found = false; @@ -2280,7 +2273,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:") && rfcomm->telephony_ag) { + } else if (spa_strstartswith(token, "+CLCC:")) { struct spa_bt_telephony_call *call; size_t pos; char *token_end; @@ -2428,19 +2421,17 @@ static bool rfcomm_hfp_hf(struct rfcomm *rfcomm, char* token) } } - 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, + 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; - } - telephony_ag_register(rfcomm->telephony_ag); + 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); rfcomm_send_cmd(rfcomm, hfp_hf_clip, NULL, "AT+CLIP=1"); break; @@ -2487,7 +2478,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 && rfcomm->telephony_ag) { + if (!rfcomm->hfp_hf_clcc) { 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 881af4e14..74761f26b 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-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 */ + 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 */ }; 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-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 */ + 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 */ }; static unsigned int get_rate_mask(uint8_t rate) { @@ -1503,10 +1503,6 @@ 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) { @@ -1515,14 +1511,6 @@ 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; - } } } @@ -1549,9 +1537,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 = 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->retransmission = preset->retransmission; + qos->latency = preset->latency; + qos->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 22d971c37..7dfe45911 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -188,16 +188,9 @@ 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; }; @@ -594,35 +587,18 @@ static enum spa_bt_profile get_codec_profile(const struct media_codec *codec, { switch (direction) { case SPA_BT_MEDIA_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; + return codec->kind == MEDIA_CODEC_BAP ? SPA_BT_PROFILE_BAP_SOURCE : SPA_BT_PROFILE_A2DP_SOURCE; case SPA_BT_MEDIA_SINK: - if (codec->kind == MEDIA_CODEC_A2DP) - return SPA_BT_PROFILE_A2DP_SINK; - else if (codec->kind == MEDIA_CODEC_ASHA) + 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_NULL; + return SPA_BT_PROFILE_A2DP_SINK; case SPA_BT_MEDIA_SOURCE_BROADCAST: - if (codec->kind == MEDIA_CODEC_BAP) - return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; - else - return SPA_BT_PROFILE_NULL; + return SPA_BT_PROFILE_BAP_BROADCAST_SOURCE; case SPA_BT_MEDIA_SINK_BROADCAST: - if (codec->kind == MEDIA_CODEC_BAP) - return SPA_BT_PROFILE_BAP_BROADCAST_SINK; - else - return SPA_BT_PROFILE_NULL; + return SPA_BT_PROFILE_BAP_BROADCAST_SINK; default: spa_assert_not_reached(); } @@ -2801,18 +2777,16 @@ 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; - 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 (codec->kind == MEDIA_CODEC_HFP) { + if (!(profile & SPA_BT_PROFILE_HEADSET_AUDIO)) + return false; + return spa_bt_backend_supports_codec(monitor->backend, device, codec->codec_id) == 1; + } + 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") && @@ -6199,11 +6173,8 @@ 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[4]; - uint32_t n_items = 0; + struct spa_dict_item setting_items[2]; char channel_allocation[64] = {0}; - char retransmissions[3] = {0}; - char max_transport_latency[5] = {0}; int mse = 0; int options = 0; @@ -6228,27 +6199,12 @@ 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); - } - 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); + 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); caps_size = sizeof(caps); ret = codec->get_bis_config(codec, caps, &caps_size, &settings, &qos); @@ -7122,7 +7078,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[0], big_entry->adapter, sizeof(big_entry->adapter)) <= 0) + if (spa_json_get_string(&it[1], 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")) { @@ -7151,20 +7107,6 @@ 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 897241954..fc659f655 100644 --- a/spa/plugins/bluez5/bluez5-device.c +++ b/spa/plugins/bluez5/bluez5-device.c @@ -59,8 +59,6 @@ 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, @@ -69,12 +67,6 @@ 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, @@ -212,89 +204,9 @@ static bool profile_is_bap(enum device_profile profile) return false; } -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) +static void get_media_codecs(struct impl *this, 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); @@ -304,24 +216,12 @@ static void get_media_codecs(struct impl *this, enum codec_order order, enum spa continue; if ((*c)->id == id || id == 0) { - codecs[n++] = *c; + *codecs++ = *c; --size; } } - 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; - } + *codecs = NULL; } static const struct media_codec *get_supported_media_codec(struct impl *this, enum spa_bluetooth_audio_codec id, @@ -480,8 +380,6 @@ 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 @@ -1364,8 +1262,6 @@ 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) { @@ -1568,13 +1464,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_is_a2dp (profile) || (profile_is_bap(profile) && is_bap_client(this))) + if ((profile == DEVICE_PROFILE_A2DP || (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_ORDER_NONE, codec, codecs, SPA_N_ELEMENTS(codecs)); + get_media_codecs(this, codec, codecs, SPA_N_ELEMENTS(codecs)); this->switching_codec = true; @@ -1591,14 +1487,6 @@ 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; @@ -1758,8 +1646,6 @@ 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); @@ -1915,8 +1801,6 @@ 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; @@ -1966,8 +1850,6 @@ 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; @@ -2002,8 +1884,6 @@ 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: @@ -2097,37 +1977,15 @@ 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; - this->props.codec = t->media_codec->id; - } else if (i == SPA_BT_PROFILE_BAP_SINK) { + else if (i == SPA_BT_PROFILE_BAP_SINK) this->profile = DEVICE_PROFILE_BAP; - this->props.codec = t->media_codec->id; - } else if (i == SPA_BT_PROFILE_ASHA_SINK) { + else if (i == SPA_BT_PROFILE_ASHA_SINK) this->profile = DEVICE_PROFILE_ASHA; - 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; - } - } - + 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; @@ -2265,36 +2123,6 @@ 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 */ @@ -2496,8 +2324,6 @@ 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: @@ -2797,7 +2623,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 ((profile_is_a2dp (this->profile) || profile_is_bap(this->profile)) && + if ((this->profile == DEVICE_PROFILE_A2DP || 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); @@ -2833,7 +2659,7 @@ next: c = this->supported_codecs[*j]; - if (!(profile_is_a2dp (this->profile) && c->kind == MEDIA_CODEC_A2DP) && + if (!(this->profile == DEVICE_PROFILE_A2DP && 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)) @@ -3414,7 +3240,7 @@ static int impl_set_param(void *object, return 0; } - if (profile_is_a2dp (this->profile) || profile_is_bap(this->profile) || + if (this->profile == DEVICE_PROFILE_A2DP || 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 ce1fd7d0c..2cc65a2bf 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 || !stream->tx_latency.enabled) + if (!stream->sink || !group->started) 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 e1c01d90c..da2e57b5a 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 = 0; + int32_t target; 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 if (this->transport->iso_io) + else 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 671035b34..7146d6f8a 100644 --- a/spa/plugins/bluez5/midi-node.c +++ b/spa/plugins/bluez5/midi-node.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -449,7 +450,7 @@ static void midi_event_recv(void *user_data, uint16_t timestamp, uint8_t *data, struct impl *this = user_data; struct port *port = &this->ports[PORT_OUT]; struct time_sync *sync = &port->sync; - uint64_t time; + uint64_t time, state = 0; int res; spa_assert(size > 0); @@ -459,11 +460,19 @@ static void midi_event_recv(void *user_data, uint16_t timestamp, uint8_t *data, spa_log_trace(this->log, "%p: event:0x%x size:%d timestamp:%d time:%"PRIu64"", this, (int)data[0], (int)size, (int)timestamp, (uint64_t)time); - res = midi_event_ringbuffer_push(&this->event_rbuf, time, data, size); - if (res < 0) { - midi_event_ringbuffer_init(&this->event_rbuf); - spa_log_warn(this->log, "%p: MIDI receive buffer overflow: %s", - this, spa_strerror(res)); + while (size > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&data, &size, + ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + break; + + res = midi_event_ringbuffer_push(&this->event_rbuf, time, (uint8_t*)ump, ump_size); + if (res < 0) { + midi_event_ringbuffer_init(&this->event_rbuf); + spa_log_warn(this->log, "%p: MIDI receive buffer overflow: %s", + this, spa_strerror(res)); + } } } @@ -704,7 +713,7 @@ static int process_output(struct impl *this) offset = time * this->rate / SPA_NSEC_PER_SEC; offset = SPA_CLAMP(offset, 0u, this->duration - 1); - spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_Midi); + spa_pod_builder_control(&port->builder, offset, SPA_CONTROL_UMP); buf = spa_pod_builder_reserve_bytes(&port->builder, size); if (buf) { midi_event_ringbuffer_pop(&this->event_rbuf, buf, size); @@ -777,28 +786,37 @@ 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) { - const uint8_t *event = c_body; - uint32_t size = c.value.size; + int size; + uint8_t event[32]; + const uint32_t *ump = c_body; + size_t ump_size = c.value.size; + uint64_t state = 0; - if (c.type != SPA_CONTROL_Midi) + if (c.type != SPA_CONTROL_UMP) continue; time = SPA_MAX(time, this->current_time + c.offset * SPA_NSEC_PER_SEC / this->rate); - spa_log_trace(this->log, "%p: output event:0x%x time:%"PRIu64, this, - (size > 0) ? event[0] : 0, time); + while (ump_size > 0) { + size = spa_ump_to_midi(&ump, &ump_size, event, sizeof(event), &state); + if (size <= 0) + break; - 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); + 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); + } } if ((res = flush_packet(this)) < 0) diff --git a/spa/plugins/bluez5/rate-control.h b/spa/plugins/bluez5/rate-control.h index 6837c2fa5..d1a36f193 100644 --- a/spa/plugins/bluez5/rate-control.h +++ b/spa/plugins/bluez5/rate-control.h @@ -128,7 +128,7 @@ static inline bool spa_bt_ptp_valid(struct spa_bt_ptp *p) * in 1/z expansion. This guarantees f(z) is causal, and G(z) = (z-1) q(z) / p(z). * We can choose p(z) and q(z) to improve low-pass properties of F(z). * - * Simplest choice is p(z)=(z-1)^2 and q(z)=1, but that does not suppress + * Simplest choice is p(z)=(z-1)^2 and q(z)=1, but that does not supress * high frequency response in F(z). Better choice is p(z) = (z-u)*(z-v)*(z-w) * and q(z) = z - r. Causality requires r = u + v + w - 2. * Then, diff --git a/spa/plugins/control/mixer.c b/spa/plugins/control/mixer.c index 1b106d3f7..caf3a6b06 100644 --- a/spa/plugins/control/mixer.c +++ b/spa/plugins/control/mixer.c @@ -72,7 +72,6 @@ struct impl { struct spa_node node; uint32_t quantum_limit; - uint32_t control_types; struct spa_log *log; @@ -474,9 +473,9 @@ static int port_set_format(void *object, if (!port->have_format) { this->n_formats++; port->have_format = true; - 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->types = types; + spa_log_debug(this->log, "%p: set format on port %d:%d", + this, direction, port_id); } } port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS; @@ -590,7 +589,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->direction == SPA_DIRECTION_INPUT && !port->active) { + if (!port->active) { spa_list_append(&info->impl->mix_list, &port->mix_link); port->active = true; } @@ -956,8 +955,6 @@ 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; @@ -965,14 +962,6 @@ 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 346b26ab3..76c7b17d5 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_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]); + in[0] = _mm256_add_ps(in[0], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+ 0]))); + in[1] = _mm256_add_ps(in[1], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+ 8]))); + in[2] = _mm256_add_ps(in[2], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+16]))); + in[3] = _mm256_add_ps(in[3], _mm256_mul_ps(g, _mm256_load_ps(&s[i][n+24]))); } _mm256_store_ps(&d[n+ 0], in[0]); _mm256_store_ps(&d[n+ 8], in[1]); @@ -237,12 +237,13 @@ 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, x1; + __m256 aa, bb, dc, x0, x1; aa = _mm256_moveldup_ps(ab); bb = _mm256_movehdup_ps(ab); + x0 = _mm256_mul_ps(aa, cd); dc = _mm256_shuffle_ps(cd, cd, _MM_SHUFFLE(2,3,0,1)); x1 = _mm256_mul_ps(bb, dc); - return _mm256_fmaddsub_ps(aa, cd, x1); + return _mm256_addsub_ps(x0, x1); } void dsp_fft_cmul_avx2(void *obj, void *fft, @@ -307,10 +308,12 @@ 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_fmadd_ps(dd[0], s, t[0]); - t[1] = _mm256_fmadd_ps(dd[1], s, t[1]); + t[0] = _mm256_add_ps(t[0], dd[0]); + t[1] = _mm256_add_ps(t[1], dd[1]); _mm256_store_ps(&dst[2*i], t[0]); _mm256_store_ps(&dst[2*i+8], t[1]); } diff --git a/spa/plugins/filter-graph/audio-dsp.c b/spa/plugins/filter-graph/audio-dsp.c index d0c4ef008..133b53db5 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_FMA3, + { SPA_CPU_FLAG_AVX2, .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 fffdc0de5..9f9d1bce6 100644 --- a/spa/plugins/filter-graph/audio-plugin.h +++ b/spa/plugins/filter-graph/audio-plugin.h @@ -69,7 +69,6 @@ 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 26ea5fa33..a077c6ec1 100644 --- a/spa/plugins/filter-graph/convolver.c +++ b/spa/plugins/filter-graph/convolver.c @@ -10,7 +10,7 @@ #include -struct partition { +struct convolver1 { int blockSize; int segSize; int segCount; @@ -27,6 +27,7 @@ struct partition { float *pre_mult; float *conv; + float *inputBuffer; int inputBufferFill; int current; @@ -41,46 +42,48 @@ static int next_power_of_two(int val) return r; } -static void partition_reset(struct spa_fga_dsp *dsp, struct partition *part) +static void convolver1_reset(struct spa_fga_dsp *dsp, struct convolver1 *conv) { int i; - for (i = 0; i < part->segCount; i++) - spa_fga_dsp_fft_memclear(dsp, part->segments[i], part->fftComplexSize, false); - spa_fga_dsp_fft_memclear(dsp, part->fft_buffer[0], part->segSize, true); - spa_fga_dsp_fft_memclear(dsp, part->fft_buffer[1], part->segSize, true); - spa_fga_dsp_fft_memclear(dsp, part->pre_mult, part->fftComplexSize, false); - spa_fga_dsp_fft_memclear(dsp, part->conv, part->fftComplexSize, false); - part->inputBufferFill = 0; - part->current = 0; + for (i = 0; i < conv->segCount; i++) + spa_fga_dsp_fft_memclear(dsp, conv->segments[i], conv->fftComplexSize, false); + spa_fga_dsp_fft_memclear(dsp, conv->fft_buffer[0], conv->segSize, true); + spa_fga_dsp_fft_memclear(dsp, conv->fft_buffer[1], conv->segSize, true); + spa_fga_dsp_fft_memclear(dsp, conv->inputBuffer, conv->segSize, true); + spa_fga_dsp_fft_memclear(dsp, conv->pre_mult, conv->fftComplexSize, false); + spa_fga_dsp_fft_memclear(dsp, conv->conv, conv->fftComplexSize, false); + conv->inputBufferFill = 0; + conv->current = 0; } -static void partition_free(struct spa_fga_dsp *dsp, struct partition *part) +static void convolver1_free(struct spa_fga_dsp *dsp, struct convolver1 *conv) { int i; - for (i = 0; i < part->segCount; i++) { - if (part->segments) - spa_fga_dsp_fft_memfree(dsp, part->segments[i]); - if (part->segmentsIr) - spa_fga_dsp_fft_memfree(dsp, part->segmentsIr[i]); + for (i = 0; i < conv->segCount; i++) { + if (conv->segments) + spa_fga_dsp_fft_memfree(dsp, conv->segments[i]); + if (conv->segmentsIr) + spa_fga_dsp_fft_memfree(dsp, conv->segmentsIr[i]); } - if (part->fft) - spa_fga_dsp_fft_free(dsp, part->fft); - if (part->ifft) - spa_fga_dsp_fft_free(dsp, part->ifft); - if (part->fft_buffer[0]) - spa_fga_dsp_fft_memfree(dsp, part->fft_buffer[0]); - if (part->fft_buffer[1]) - spa_fga_dsp_fft_memfree(dsp, part->fft_buffer[1]); - free(part->segments); - free(part->segmentsIr); - spa_fga_dsp_fft_memfree(dsp, part->pre_mult); - spa_fga_dsp_fft_memfree(dsp, part->conv); - free(part); + if (conv->fft) + spa_fga_dsp_fft_free(dsp, conv->fft); + if (conv->ifft) + spa_fga_dsp_fft_free(dsp, conv->ifft); + if (conv->fft_buffer[0]) + spa_fga_dsp_fft_memfree(dsp, conv->fft_buffer[0]); + if (conv->fft_buffer[1]) + spa_fga_dsp_fft_memfree(dsp, conv->fft_buffer[1]); + free(conv->segments); + free(conv->segmentsIr); + spa_fga_dsp_fft_memfree(dsp, conv->pre_mult); + spa_fga_dsp_fft_memfree(dsp, conv->conv); + spa_fga_dsp_fft_memfree(dsp, conv->inputBuffer); + free(conv); } -static struct partition *partition_new(struct spa_fga_dsp *dsp, int block, const float *ir, int irlen) +static struct convolver1 *convolver1_new(struct spa_fga_dsp *dsp, int block, const float *ir, int irlen) { - struct partition *part; + struct convolver1 *conv; int i; if (block == 0) @@ -89,130 +92,134 @@ static struct partition *partition_new(struct spa_fga_dsp *dsp, int block, const while (irlen > 0 && fabs(ir[irlen-1]) < 0.000001f) irlen--; - part = calloc(1, sizeof(*part)); - if (part == NULL) + conv = calloc(1, sizeof(*conv)); + if (conv == NULL) return NULL; if (irlen == 0) - return part; + return conv; - part->blockSize = next_power_of_two(block); - part->segSize = 2 * part->blockSize; - part->segCount = (irlen + part->blockSize-1) / part->blockSize; - part->fftComplexSize = (part->segSize / 2) + 1; + conv->blockSize = next_power_of_two(block); + conv->segSize = 2 * conv->blockSize; + conv->segCount = (irlen + conv->blockSize-1) / conv->blockSize; + conv->fftComplexSize = (conv->segSize / 2) + 1; - part->fft = spa_fga_dsp_fft_new(dsp, part->segSize, true); - if (part->fft == NULL) + conv->fft = spa_fga_dsp_fft_new(dsp, conv->segSize, true); + if (conv->fft == NULL) goto error; - part->ifft = spa_fga_dsp_fft_new(dsp, part->segSize, true); - if (part->ifft == NULL) + conv->ifft = spa_fga_dsp_fft_new(dsp, conv->segSize, true); + if (conv->ifft == NULL) goto error; - part->fft_buffer[0] = spa_fga_dsp_fft_memalloc(dsp, part->segSize, true); - part->fft_buffer[1] = spa_fga_dsp_fft_memalloc(dsp, part->segSize, true); - if (part->fft_buffer[0] == NULL || part->fft_buffer[1] == NULL) + conv->fft_buffer[0] = spa_fga_dsp_fft_memalloc(dsp, conv->segSize, true); + conv->fft_buffer[1] = spa_fga_dsp_fft_memalloc(dsp, conv->segSize, true); + if (conv->fft_buffer[0] == NULL || conv->fft_buffer[1] == NULL) goto error; - part->segments = calloc(part->segCount, sizeof(float*)); - part->segmentsIr = calloc(part->segCount, sizeof(float*)); - if (part->segments == NULL || part->segmentsIr == NULL) + conv->segments = calloc(conv->segCount, sizeof(float*)); + conv->segmentsIr = calloc(conv->segCount, sizeof(float*)); + if (conv->segments == NULL || conv->segmentsIr == NULL) goto error; - for (i = 0; i < part->segCount; i++) { - int left = irlen - (i * part->blockSize); - int copy = SPA_MIN(part->blockSize, left); + for (i = 0; i < conv->segCount; i++) { + int left = irlen - (i * conv->blockSize); + int copy = SPA_MIN(conv->blockSize, left); - part->segments[i] = spa_fga_dsp_fft_memalloc(dsp, part->fftComplexSize, false); - part->segmentsIr[i] = spa_fga_dsp_fft_memalloc(dsp, part->fftComplexSize, false); - if (part->segments[i] == NULL || part->segmentsIr[i] == NULL) + conv->segments[i] = spa_fga_dsp_fft_memalloc(dsp, conv->fftComplexSize, false); + conv->segmentsIr[i] = spa_fga_dsp_fft_memalloc(dsp, conv->fftComplexSize, false); + if (conv->segments[i] == NULL || conv->segmentsIr[i] == NULL) goto error; - spa_fga_dsp_copy(dsp, part->fft_buffer[0], &ir[i * part->blockSize], copy); - if (copy < part->segSize) - spa_fga_dsp_fft_memclear(dsp, part->fft_buffer[0] + copy, part->segSize - copy, true); + spa_fga_dsp_copy(dsp, conv->fft_buffer[0], &ir[i * conv->blockSize], copy); + if (copy < conv->segSize) + spa_fga_dsp_fft_memclear(dsp, conv->fft_buffer[0] + copy, conv->segSize - copy, true); - spa_fga_dsp_fft_run(dsp, part->fft, 1, part->fft_buffer[0], part->segmentsIr[i]); + spa_fga_dsp_fft_run(dsp, conv->fft, 1, conv->fft_buffer[0], conv->segmentsIr[i]); } - part->pre_mult = spa_fga_dsp_fft_memalloc(dsp, part->fftComplexSize, false); - part->conv = spa_fga_dsp_fft_memalloc(dsp, part->fftComplexSize, false); - if (part->pre_mult == NULL || part->conv == NULL) + conv->pre_mult = spa_fga_dsp_fft_memalloc(dsp, conv->fftComplexSize, false); + conv->conv = spa_fga_dsp_fft_memalloc(dsp, conv->fftComplexSize, false); + conv->inputBuffer = spa_fga_dsp_fft_memalloc(dsp, conv->segSize, true); + if (conv->pre_mult == NULL || conv->conv == NULL || conv->inputBuffer == NULL) goto error; - part->scale = 1.0f / part->segSize; - partition_reset(dsp, part); + conv->scale = 1.0f / conv->segSize; + convolver1_reset(dsp, conv); - return part; + return conv; error: - partition_free(dsp, part); + convolver1_free(dsp, conv); return NULL; } -static int partition_run(struct spa_fga_dsp *dsp, struct partition *part, const float *input, float *output, int len) +static int convolver1_run(struct spa_fga_dsp *dsp, struct convolver1 *conv, const float *input, float *output, int len) { - int i; + int i, processed = 0; - if (part == NULL || part->segCount == 0) { + if (conv == NULL || conv->segCount == 0) { spa_fga_dsp_fft_memclear(dsp, output, len, true); return len; } - int inputBufferFill = part->inputBufferFill; + int inputBufferFill = conv->inputBufferFill; + while (processed < len) { + const int processing = SPA_MIN(len - processed, conv->blockSize - inputBufferFill); - spa_fga_dsp_fft_run(dsp, part->fft, 1, input, part->segments[part->current]); + spa_fga_dsp_copy(dsp, conv->inputBuffer + inputBufferFill, input + processed, processing); + if (inputBufferFill == 0 && processing < conv->blockSize) + spa_fga_dsp_fft_memclear(dsp, conv->inputBuffer + processing, + conv->blockSize - processing, true); + spa_fga_dsp_fft_run(dsp, conv->fft, 1, conv->inputBuffer, conv->segments[conv->current]); - if (part->segCount > 1) { - if (inputBufferFill == 0) { - int indexAudio = part->current; + if (conv->segCount > 1) { + if (inputBufferFill == 0) { + int indexAudio = (conv->current + 1) % conv->segCount; - if (++indexAudio == part->segCount) - indexAudio = 0; + spa_fga_dsp_fft_cmul(dsp, conv->fft, conv->pre_mult, + conv->segmentsIr[1], + conv->segments[indexAudio], + conv->fftComplexSize, conv->scale); - spa_fga_dsp_fft_cmul(dsp, part->fft, part->pre_mult, - part->segmentsIr[1], - part->segments[indexAudio], - part->fftComplexSize, part->scale); + for (i = 2; i < conv->segCount; i++) { + indexAudio = (conv->current + i) % conv->segCount; - for (i = 2; i < part->segCount; i++) { - if (++indexAudio == part->segCount) - indexAudio = 0; - - spa_fga_dsp_fft_cmuladd(dsp, part->fft, - part->pre_mult, - part->pre_mult, - part->segmentsIr[i], - part->segments[indexAudio], - part->fftComplexSize, part->scale); + spa_fga_dsp_fft_cmuladd(dsp, conv->fft, + conv->pre_mult, + conv->pre_mult, + conv->segmentsIr[i], + conv->segments[indexAudio], + conv->fftComplexSize, conv->scale); + } } + spa_fga_dsp_fft_cmuladd(dsp, conv->fft, + conv->conv, + conv->pre_mult, + conv->segments[conv->current], + conv->segmentsIr[0], + conv->fftComplexSize, conv->scale); + } else { + spa_fga_dsp_fft_cmul(dsp, conv->fft, + conv->conv, + conv->segments[conv->current], + conv->segmentsIr[0], + conv->fftComplexSize, conv->scale); } - spa_fga_dsp_fft_cmuladd(dsp, part->fft, - part->conv, - part->pre_mult, - part->segments[part->current], - part->segmentsIr[0], - part->fftComplexSize, part->scale); - } else { - spa_fga_dsp_fft_cmul(dsp, part->fft, - part->conv, - part->segments[part->current], - part->segmentsIr[0], - part->fftComplexSize, part->scale); + + spa_fga_dsp_fft_run(dsp, conv->ifft, -1, conv->conv, conv->fft_buffer[0]); + + spa_fga_dsp_sum(dsp, output + processed, conv->fft_buffer[0] + inputBufferFill, + conv->fft_buffer[1] + conv->blockSize + inputBufferFill, processing); + + inputBufferFill += processing; + if (inputBufferFill == conv->blockSize) { + inputBufferFill = 0; + + SPA_SWAP(conv->fft_buffer[0], conv->fft_buffer[1]); + + conv->current = (conv->current > 0) ? (conv->current - 1) : (conv->segCount - 1); + } + + processed += processing; } - - spa_fga_dsp_fft_run(dsp, part->ifft, -1, part->conv, part->fft_buffer[0]); - - spa_fga_dsp_sum(dsp, output, part->fft_buffer[0] + inputBufferFill, - part->fft_buffer[1] + part->blockSize + inputBufferFill, len); - - inputBufferFill += len; - if (inputBufferFill == part->blockSize) { - inputBufferFill = 0; - - SPA_SWAP(part->fft_buffer[0], part->fft_buffer[1]); - - if (part->current == 0) - part->current = part->segCount; - part->current--; - } - part->inputBufferFill = inputBufferFill; + conv->inputBufferFill = inputBufferFill; return len; } @@ -221,8 +228,11 @@ struct convolver struct spa_fga_dsp *dsp; int headBlockSize; int tailBlockSize; - struct partition *headPartition; - struct partition *tailPartition; + struct convolver1 *headConvolver; + struct convolver1 *tailConvolver0; + float *tailOutput0; + float *tailPrecalculated0; + struct convolver1 *tailConvolver; float *tailOutput; float *tailPrecalculated; float *tailInput; @@ -233,10 +243,15 @@ void convolver_reset(struct convolver *conv) { struct spa_fga_dsp *dsp = conv->dsp; - if (conv->headPartition) - partition_reset(dsp, conv->headPartition); - if (conv->tailPartition) { - partition_reset(dsp, conv->tailPartition); + if (conv->headConvolver) + convolver1_reset(dsp, conv->headConvolver); + if (conv->tailConvolver0) { + convolver1_reset(dsp, conv->tailConvolver0); + spa_fga_dsp_fft_memclear(dsp, conv->tailOutput0, conv->tailBlockSize, true); + spa_fga_dsp_fft_memclear(dsp, conv->tailPrecalculated0, conv->tailBlockSize, true); + } + if (conv->tailConvolver) { + convolver1_reset(dsp, conv->tailConvolver); spa_fga_dsp_fft_memclear(dsp, conv->tailOutput, conv->tailBlockSize, true); spa_fga_dsp_fft_memclear(dsp, conv->tailPrecalculated, conv->tailBlockSize, true); } @@ -270,19 +285,34 @@ struct convolver *convolver_new(struct spa_fga_dsp *dsp, int head_block, int tai conv->headBlockSize = next_power_of_two(head_block); conv->tailBlockSize = next_power_of_two(tail_block); - head_ir_len = SPA_MIN(irlen, 2 * conv->tailBlockSize); - conv->headPartition = partition_new(dsp, conv->headBlockSize, ir, head_ir_len); - if (conv->headPartition == NULL) + head_ir_len = SPA_MIN(irlen, conv->tailBlockSize); + conv->headConvolver = convolver1_new(dsp, conv->headBlockSize, ir, head_ir_len); + if (conv->headConvolver == NULL) goto error; + if (irlen > conv->tailBlockSize) { + int conv1IrLen = SPA_MIN(irlen - conv->tailBlockSize, conv->tailBlockSize); + conv->tailConvolver0 = convolver1_new(dsp, conv->headBlockSize, ir + conv->tailBlockSize, conv1IrLen); + conv->tailOutput0 = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); + conv->tailPrecalculated0 = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); + if (conv->tailConvolver0 == NULL || conv->tailOutput0 == NULL || + conv->tailPrecalculated0 == NULL) + goto error; + } + if (irlen > 2 * conv->tailBlockSize) { int tailIrLen = irlen - (2 * conv->tailBlockSize); - conv->tailPartition = partition_new(dsp, conv->tailBlockSize, ir + (2 * conv->tailBlockSize), tailIrLen); + conv->tailConvolver = convolver1_new(dsp, conv->tailBlockSize, ir + (2 * conv->tailBlockSize), tailIrLen); conv->tailOutput = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); conv->tailPrecalculated = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); - conv->tailInput = spa_fga_dsp_fft_memalloc(dsp, 2 * conv->tailBlockSize, true); - if (conv->tailPartition == NULL || conv->tailOutput == NULL || - conv->tailPrecalculated == NULL || conv->tailInput == NULL) + if (conv->tailConvolver == NULL || conv->tailOutput == NULL || + conv->tailPrecalculated == NULL) + goto error; + } + + if (conv->tailConvolver0 || conv->tailConvolver) { + conv->tailInput = spa_fga_dsp_fft_memalloc(dsp, conv->tailBlockSize, true); + if (conv->tailInput == NULL) goto error; } @@ -298,10 +328,14 @@ void convolver_free(struct convolver *conv) { struct spa_fga_dsp *dsp = conv->dsp; - if (conv->headPartition) - partition_free(dsp, conv->headPartition); - if (conv->tailPartition) - partition_free(dsp, conv->tailPartition); + if (conv->headConvolver) + convolver1_free(dsp, conv->headConvolver); + if (conv->tailConvolver0) + convolver1_free(dsp, conv->tailConvolver0); + if (conv->tailConvolver) + convolver1_free(dsp, conv->tailConvolver); + spa_fga_dsp_fft_memfree(dsp, conv->tailOutput0); + spa_fga_dsp_fft_memfree(dsp, conv->tailPrecalculated0); spa_fga_dsp_fft_memfree(dsp, conv->tailOutput); spa_fga_dsp_fft_memfree(dsp, conv->tailPrecalculated); spa_fga_dsp_fft_memfree(dsp, conv->tailInput); @@ -310,37 +344,50 @@ void convolver_free(struct convolver *conv) int convolver_run(struct convolver *conv, const float *input, float *output, int length) { - int processed = 0; struct spa_fga_dsp *dsp = conv->dsp; - while (processed < length) { - int remaining = length - processed; - int blockRemain = conv->tailInputFill % conv->headBlockSize; - int processing = SPA_MIN(remaining, conv->headBlockSize - blockRemain); + convolver1_run(dsp, conv->headConvolver, input, output, length); - spa_memcpy(conv->tailInput + conv->tailInputFill, input + processed, processing * sizeof(float)); - memset(conv->tailInput + conv->tailInputFill + processing, 0, - (2 * conv->headBlockSize - processing) * sizeof(float)); + if (conv->tailInput) { + int processed = 0; - partition_run(dsp, conv->headPartition, conv->tailInput + conv->tailInputFill, - &output[processed], processing); + while (processed < length) { + int remaining = length - processed; + int processing = SPA_MIN(remaining, conv->headBlockSize - (conv->tailInputFill % conv->headBlockSize)); - if (conv->tailPrecalculated) - spa_fga_dsp_sum(dsp, &output[processed], &output[processed], - &conv->tailPrecalculated[conv->tailInputFill], - processing); + if (conv->tailPrecalculated0) + spa_fga_dsp_sum(dsp, &output[processed], &output[processed], + &conv->tailPrecalculated0[conv->tailInputFill], + processing); + if (conv->tailPrecalculated) + spa_fga_dsp_sum(dsp, &output[processed], &output[processed], + &conv->tailPrecalculated[conv->tailInputFill], + processing); - conv->tailInputFill += processing; + spa_fga_dsp_copy(dsp, conv->tailInput + conv->tailInputFill, input + processed, processing); + conv->tailInputFill += processing; - if (conv->tailInputFill == conv->tailBlockSize) { - if (conv->tailPrecalculated) { + if (conv->tailPrecalculated0 && (conv->tailInputFill % conv->headBlockSize == 0)) { + int blockOffset = conv->tailInputFill - conv->headBlockSize; + convolver1_run(dsp, conv->tailConvolver0, + conv->tailInput + blockOffset, + conv->tailOutput0 + blockOffset, + conv->headBlockSize); + if (conv->tailInputFill == conv->tailBlockSize) + SPA_SWAP(conv->tailPrecalculated0, conv->tailOutput0); + } + + if (conv->tailPrecalculated && + conv->tailInputFill == conv->tailBlockSize) { SPA_SWAP(conv->tailPrecalculated, conv->tailOutput); - partition_run(dsp, conv->tailPartition, conv->tailInput, + convolver1_run(dsp, conv->tailConvolver, conv->tailInput, conv->tailOutput, conv->tailBlockSize); } - conv->tailInputFill = 0; + if (conv->tailInputFill == conv->tailBlockSize) + conv->tailInputFill = 0; + + processed += processing; } - processed += processing; } return 0; } diff --git a/spa/plugins/filter-graph/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index 4b47d0c9f..05ba7b3fb 100644 --- a/spa/plugins/filter-graph/filter-graph.c +++ b/spa/plugins/filter-graph/filter-graph.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -222,7 +221,6 @@ 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; @@ -554,25 +552,19 @@ 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, v <= 0.0f ? false : true); + spa_pod_builder_bool(b, port->control_data[0] <= 0.0f ? false : true); } else if (p->hint & SPA_FGA_HINT_INTEGER) { - spa_pod_builder_int(b, (int32_t)v); + spa_pod_builder_int(b, (int32_t)port->control_data[0]); } else { - spa_pod_builder_float(b, v); + spa_pod_builder_float(b, port->control_data[0]); } } spa_pod_builder_pop(b, &f[1]); @@ -706,46 +698,21 @@ static int impl_reset(void *object) return 0; } -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) +static void node_control_changed(struct node *node) { - 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; + const struct spa_fga_descriptor *d = node->desc->desc; uint32_t i; - spa_loop_locked(impl->data_loop, do_emit_node_control_sync, 1, NULL, 0, impl); + if (!node->control_changed) + return; - spa_list_for_each(node, &graph->node_list, link) { - const struct spa_fga_descriptor *d = node->desc->desc; - if (!node->control_changed) + for (i = 0; i < node->n_hndl; i++) { + if (node->hndl[i] == NULL) continue; - 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; + if (d->control_changed) + d->control_changed(node->hndl[i]); } + node->control_changed = false; } static int sync_volume(struct graph *graph, struct volume *vol) @@ -859,7 +826,11 @@ static int impl_set_props(void *object, enum spa_direction direction, const stru spa_pod_dynamic_builder_clean(&b); if (changed > 0) { - emit_node_control_changed(impl); + struct node *node; + + spa_list_for_each(node, &graph->node_list, link) + node_control_changed(node); + spa_filter_graph_emit_props_changed(&impl->hooks, SPA_DIRECTION_INPUT); } return 0; @@ -1725,10 +1696,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) { @@ -2376,7 +2347,6 @@ 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 20b90f4c4..94ee0bd25 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 and have_fma - filter_graph_avx2_fma = static_library('filter_graph_avx2_fma', +if have_avx2 + filter_graph_avx2 = static_library('filter_graph_avx2', ['audio-dsp-avx2.c' ], include_directories : [configinc], - c_args : [avx2_args, fma_args, '-O3', '-DHAVE_AVX2', '-DHAVE_FMA'], + c_args : [avx2_args, fma_args,'-O3', '-DHAVE_AVX2'], dependencies : [ spa_dep ], install : false ) - simd_cargs += ['-DHAVE_AVX2', '-DHAVE_FMA'] - simd_dependencies += filter_graph_avx2_fma + simd_cargs += ['-DHAVE_AVX2'] + simd_dependencies += filter_graph_avx2 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 b4882010c..3bcde30c9 100644 --- a/spa/plugins/filter-graph/plugin_builtin.c +++ b/spa/plugins/filter-graph/plugin_builtin.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include @@ -202,42 +201,42 @@ static struct spa_fga_port mixer_ports[] = { { .index = 9, .name = "Gain 1", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, - .def = 1.0f, .min = -10.0f, .max = 10.0f + .def = 1.0f, .min = 0.0f, .max = 10.0f }, { .index = 10, .name = "Gain 2", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, - .def = 1.0f, .min = -10.0f, .max = 10.0f + .def = 1.0f, .min = 0.0f, .max = 10.0f }, { .index = 11, .name = "Gain 3", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, - .def = 1.0f, .min = -10.0f, .max = 10.0f + .def = 1.0f, .min = 0.0f, .max = 10.0f }, { .index = 12, .name = "Gain 4", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, - .def = 1.0f, .min = -10.0f, .max = 10.0f + .def = 1.0f, .min = 0.0f, .max = 10.0f }, { .index = 13, .name = "Gain 5", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, - .def = 1.0f, .min = -10.0f, .max = 10.0f + .def = 1.0f, .min = 0.0f, .max = 10.0f }, { .index = 14, .name = "Gain 6", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, - .def = 1.0f, .min = -10.0f, .max = 10.0f + .def = 1.0f, .min = 0.0f, .max = 10.0f }, { .index = 15, .name = "Gain 7", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, - .def = 1.0f, .min = -10.0f, .max = 10.0f + .def = 1.0f, .min = 0.0f, .max = 10.0f }, { .index = 16, .name = "Gain 8", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, - .def = 1.0f, .min = -10.0f, .max = 10.0f + .def = 1.0f, .min = 0.0f, .max = 10.0f }, }; @@ -543,12 +542,7 @@ 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]; @@ -568,6 +562,7 @@ static void bq_control_sync(void * Instance) 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 */ @@ -579,7 +574,6 @@ 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, @@ -594,7 +588,6 @@ 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, @@ -609,7 +602,6 @@ 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, @@ -624,7 +616,6 @@ 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, @@ -639,7 +630,6 @@ 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, @@ -654,7 +644,6 @@ 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, @@ -669,7 +658,6 @@ 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, @@ -685,7 +673,6 @@ 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, @@ -700,7 +687,6 @@ 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, @@ -1467,7 +1453,6 @@ 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", @@ -1525,7 +1510,6 @@ 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", @@ -1593,7 +1577,6 @@ 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 }, }; @@ -1643,7 +1626,6 @@ 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", @@ -1701,7 +1683,6 @@ 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", @@ -2491,12 +2472,10 @@ 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", @@ -2505,7 +2484,6 @@ 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 }, }; @@ -2665,7 +2643,6 @@ 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", @@ -3012,7 +2989,7 @@ static struct spa_fga_port noisegate_ports[] = { { .index = 2, .name = "Level", .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, - .def = NAN, .min = -FLT_MAX, .max = FLT_MAX + .def = NAN }, { .index = 3, .name = "Open Threshold", @@ -3253,7 +3230,6 @@ 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 02fbcbe53..fb79de590 100644 --- a/spa/plugins/filter-graph/plugin_ebur128.c +++ b/spa/plugins/filter-graph/plugin_ebur128.c @@ -399,7 +399,6 @@ 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_ffmpeg.c b/spa/plugins/filter-graph/plugin_ffmpeg.c index 86001c0fd..4e4b9f4a0 100644 --- a/spa/plugins/filter-graph/plugin_ffmpeg.c +++ b/spa/plugins/filter-graph/plugin_ffmpeg.c @@ -243,7 +243,7 @@ static void ffmpeg_run(void *instance, unsigned long SampleCount) delay); for (j = 0; j < desc->layout[c].nb_channels; j++) - spa_memcpy(i->data[d++], i->frame->data[j], SampleCount * sizeof(float)); + memcpy(i->data[d++], i->frame->data[j], SampleCount * sizeof(float)); av_frame_unref(i->frame); } diff --git a/spa/plugins/filter-graph/plugin_ladspa.c b/spa/plugins/filter-graph/plugin_ladspa.c index 54315861b..1aebafe35 100644 --- a/spa/plugins/filter-graph/plugin_ladspa.c +++ b/spa/plugins/filter-graph/plugin_ladspa.c @@ -233,49 +233,43 @@ static inline const char *split_walk(const char *str, const char *delimiter, siz return s; } -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) +static int load_ladspa_plugin(struct plugin *impl, const char *path) { int res = -ENOENT; - const char *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; + if (path[0] != '/') { + const char *search_dirs, *p, *state = NULL; + char filename[PATH_MAX]; + size_t len; - while ((p = split_walk(search_dirs, ":", &len, &state))) { - int namelen; + search_dirs = getenv("LADSPA_PATH"); + if (!search_dirs) + search_dirs = "/usr/lib64/ladspa:/usr/lib/ladspa:" LIBDIR; - if (len == 0 || len >= sizeof(filename)) - continue; + /* + * 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 (strncmp(path, p, len) == 0 && (path[len-1] == '/' || 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; - if (namelen < 0 || (size_t) namelen >= sizeof(filename)) - continue; - - res = ladspa_handle_load_by_path(impl, filename); - if (res >= 0) - break; + res = ladspa_handle_load_by_path(impl, filename); + if (res >= 0) + break; + } + } + else { + res = ladspa_handle_load_by_path(impl, path); } return res; } @@ -323,7 +317,7 @@ impl_init(const struct spa_handle_factory *factory, struct plugin *impl; uint32_t i; int res; - const char *path = NULL, *search_dirs; + const char *path = NULL; handle->get_interface = impl_get_interface; handle->clear = impl_clear; @@ -341,11 +335,9 @@ impl_init(const struct spa_handle_factory *factory, if (path == NULL) return -EINVAL; - 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)); + if ((res = load_ladspa_plugin(impl, path)) < 0) { + spa_log_error(impl->log, "failed to load plugin '%s': %s", + path, spa_strerror(res)); return res; } diff --git a/spa/plugins/filter-graph/plugin_onnx.c b/spa/plugins/filter-graph/plugin_onnx.c index 13fccee56..3ef12e963 100644 --- a/spa/plugins/filter-graph/plugin_onnx.c +++ b/spa/plugins/filter-graph/plugin_onnx.c @@ -593,10 +593,6 @@ 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 6517860fc..2d6532f82 100644 --- a/spa/plugins/libcamera/libcamera-device.cpp +++ b/spa/plugins/libcamera/libcamera-device.cpp @@ -5,7 +5,6 @@ /* SPDX-License-Identifier: MIT */ #include -#include #include #include @@ -26,6 +25,7 @@ #include #include +#include using namespace libcamera; @@ -50,7 +50,7 @@ struct impl { std::string device_id); }; -std::span cameraDevice(const Camera& camera) +const libcamera::Span cameraDevice(const Camera& camera) { if (auto devices = camera.properties().get(properties::SystemDevices)) return devices.value(); diff --git a/spa/plugins/meson.build b/spa/plugins/meson.build index f774c1036..42aec7ed3 100644 --- a/spa/plugins/meson.build +++ b/spa/plugins/meson.build @@ -1,4 +1,4 @@ -if alsa_dep.found() +if alsa_dep.found() and host_machine.system() == 'linux' 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 0fb866671..c1c53855d 100644 --- a/spa/plugins/support/cpu-x86.c +++ b/spa/plugins/support/cpu-x86.c @@ -78,8 +78,6 @@ 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 7d0b14c1f..6ea5f31b5 100644 --- a/spa/plugins/support/logger.c +++ b/spa/plugins/support/logger.c @@ -2,16 +2,12 @@ /* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */ /* SPDX-License-Identifier: MIT */ -#include "config.h" - #include -#include #include #include #include #include #include -#include #include #include @@ -71,10 +67,16 @@ impl_log_logtv(void *object, const char *fmt, va_list args) { +#define RESERVED_LENGTH 24 + struct impl *impl = object; - char location[1024]; + char timestamp[18] = {0}; + char topicstr[32] = {0}; + char filename[64] = {0}; + char location[1000 + RESERVED_LENGTH], *p; 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))) @@ -91,18 +93,8 @@ impl_log_logtv(void *object, suffix = SPA_ANSI_RESET; } - 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 + p = location; + len = sizeof(location) - RESERVED_LENGTH; if (impl->local_timestamp) { char buf[64]; @@ -112,52 +104,67 @@ 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_strbuf_append(&msg, "[%s.%06d]", buf, + spa_scnprintf(timestamp, sizeof(timestamp), "[%s.%06d]", buf, (int)(now.tv_nsec / SPA_NSEC_PER_USEC)); } else if (impl->timestamp) { struct timespec now; clock_gettime(impl->clock_id, &now); - spa_strbuf_append(&msg, "[%05jd.%06jd]", + spa_scnprintf(timestamp, sizeof(timestamp), "[%05jd.%06jd]", (intmax_t) (now.tv_sec & 0x1FFFFFFF) % 100000, (intmax_t) now.tv_nsec / 1000); } if (topic && topic->topic) - spa_strbuf_append(&msg, " %-12s | ", topic->topic); + spa_scnprintf(topicstr, sizeof(topicstr), " %-12s | ", topic->topic); if (impl->line && line != 0) { const char *s = strrchr(file, '/'); - spa_strbuf_append(&msg, "[%16.16s:%5i %s()]", + spa_scnprintf(filename, sizeof(filename), "[%16.16s:%5i %s()]", s ? s + 1 : file, line, func); } - spa_strbuf_append(&msg, " "); - spa_strbuf_appendv(&msg, fmt, args); - spa_strbuf_append(&msg, "%s\n", suffix); + 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); - if (SPA_UNLIKELY(msg.pos >= msg.maxsize)) { - static const char truncated_text[] = "... (truncated)"; - size_t suffix_length = strlen(suffix) + strlen(truncated_text) + 1 + 1; + /* + * `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 + */ - 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); + /* 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)"); } + 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), msg.buffer, msg.pos); - spa_ringbuffer_write_update(&impl->trace_rb, index + msg.pos); + index & (TRACE_BUFFER - 1), location, size); + spa_ringbuffer_write_update(&impl->trace_rb, index + size); if (spa_system_eventfd_write(impl->system, impl->source.fd, 1) < 0) fprintf(impl->file, "error signaling eventfd: %s\n", strerror(errno)); } else - fputs(msg.buffer, impl->file); + fputs(location, impl->file); + +#undef RESERVED_LENGTH } static SPA_PRINTF_FUNC(6,0) void diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index 5e35f6dcd..e5e49849f 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) - spa_assert_se(pthread_mutex_lock(&impl->lock) == 0); + pthread_mutex_lock(&impl->lock); flush_all_queues(impl); if (loop_thread == 0) - spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0); + pthread_mutex_unlock(&impl->lock); res = item->res; } else { @@ -482,9 +482,9 @@ again: recurse = impl->recurse; while (impl->recurse > 0) { impl->recurse--; - spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0); + pthread_mutex_unlock(&impl->lock); } - spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0); + pthread_mutex_unlock(&impl->lock); } 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++) { - spa_assert_se(pthread_mutex_lock(&impl->lock) == 0); + pthread_mutex_lock(&impl->lock); impl->recurse++; } @@ -569,14 +569,9 @@ static int loop_locked(void *object, spa_invoke_func_t func, uint32_t seq, { struct impl *impl = object; int res; - - res = pthread_mutex_lock(&impl->lock); - if (res) - return -res; - + pthread_mutex_lock(&impl->lock); res = func(&impl->loop, false, seq, data, size, user_data); - spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0); - + pthread_mutex_unlock(&impl->lock); return res; } @@ -603,7 +598,7 @@ static void loop_enter(void *object) struct impl *impl = object; pthread_t thread_id = pthread_self(); - spa_assert_se(pthread_mutex_lock(&impl->lock) == 0); + pthread_mutex_lock(&impl->lock); if (impl->enter_count == 0) { spa_return_if_fail(impl->thread == 0); impl->thread = thread_id; @@ -630,7 +625,7 @@ static void loop_leave(void *object) impl->thread = 0; flush_all_queues(impl); } - spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0); + pthread_mutex_unlock(&impl->lock); } static int loop_check(void *object) @@ -649,7 +644,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; - spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0); + pthread_mutex_unlock(&impl->lock); return res; } static int loop_lock(void *object) @@ -727,20 +722,13 @@ static int loop_accept(void *object) } struct cancellation_handler_data { - struct impl *impl; - const struct spa_poll_event *ep; - volatile int ep_count; - volatile int unlocked; - volatile int locked; + struct spa_poll_event *ep; + int ep_count; }; 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; @@ -757,24 +745,20 @@ 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); - spa_assert_se((cdata.unlocked = (pthread_mutex_unlock(&impl->lock) == 0))); + pthread_mutex_unlock(&impl->lock); nfds = spa_system_pollfd_wait(impl->system, impl->poll_fd, ep, SPA_N_ELEMENTS(ep), timeout); - spa_assert_se((cdata.locked = (pthread_mutex_lock(&impl->lock) == 0))); + pthread_mutex_lock(&impl->lock); spa_loop_control_hook_after(&impl->hooks_list); if (remove_count != impl->remove_count) nfds = 0; - cdata.ep_count = nfds; + struct cancellation_handler_data cdata = { ep, nfds }; + pthread_cleanup_push(cancellation_handler, &cdata); /* 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 @@ -810,15 +794,13 @@ 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); - spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0); + pthread_mutex_unlock(&impl->lock); nfds = spa_system_pollfd_wait(impl->system, impl->poll_fd, ep, SPA_N_ELEMENTS(ep), timeout); - spa_assert_se(pthread_mutex_lock(&impl->lock) == 0); + pthread_mutex_lock(&impl->lock); 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 f0585a711..fa9cf3426 100644 --- a/spa/plugins/support/node-driver.c +++ b/spa/plugins/support/node-driver.c @@ -91,6 +91,7 @@ struct impl { struct spa_io_clock *clock; struct spa_source timer_source; + struct itimerspec timerspec; int clock_fd; bool started; @@ -181,16 +182,13 @@ 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); - 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; + this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC; + this->timerspec.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 | - SPA_FD_TIMER_CANCEL_ON_SET, &ts, NULL); + SPA_FD_TIMER_CANCEL_ON_SET, &this->timerspec, NULL); } static inline uint64_t gettime_nsec(struct impl *this, clockid_t clock_id) @@ -1045,6 +1043,10 @@ 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 610adf9ce..b804023b3 100644 --- a/spa/plugins/support/null-audio-sink.c +++ b/spa/plugins/support/null-audio-sink.c @@ -114,6 +114,7 @@ struct impl { unsigned int started:1; unsigned int following:1; struct spa_source timer_source; + struct itimerspec timerspec; uint64_t next_time; }; @@ -178,15 +179,11 @@ 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); - 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; + this->timerspec.it_value.tv_sec = next_time / SPA_NSEC_PER_SEC; + this->timerspec.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); + this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &this->timerspec, NULL); } static int set_timers(struct impl *this) @@ -932,6 +929,10 @@ 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 cd9e1c6eb..767e0b43d 100644 --- a/spa/plugins/support/system.c +++ b/spa/plugins/support/system.c @@ -30,10 +30,6 @@ 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; @@ -136,9 +132,16 @@ 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) { - int nfds; - if (SPA_UNLIKELY((nfds = epoll_wait(pfd, (struct epoll_event*)ev, n_ev, timeout)) < 0)) + struct epoll_event ep[n_ev]; + int i, nfds; + + if (SPA_UNLIKELY((nfds = epoll_wait(pfd, ep, 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 31667a1de..71550300c 100644 --- a/spa/plugins/test/fakesink.c +++ b/spa/plugins/test/fakesink.c @@ -26,6 +26,10 @@ #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 @@ -64,11 +68,13 @@ 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; @@ -81,6 +87,13 @@ 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) @@ -103,6 +116,14 @@ 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; } @@ -130,7 +151,26 @@ 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; } @@ -139,15 +179,23 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, static void set_timer(struct impl *this, bool enabled) { - 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; + 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); } - - 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) @@ -155,12 +203,14 @@ static inline int read_timer(struct impl *this) uint64_t expirations; int res = 0; - 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 (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)); + } } - return res; } @@ -248,7 +298,10 @@ static int impl_node_send_command(void *object, const struct spa_command *comman return 0; clock_gettime(CLOCK_MONOTONIC, &now); - this->start_time = SPA_TIMESPEC_TO_NSEC(&now); + if (this->props.live) + this->start_time = SPA_TIMESPEC_TO_NSEC(&now); + else + this->start_time = 0; this->buffer_count = 0; this->elapsed_time = 0; @@ -598,8 +651,10 @@ static int impl_node_process(void *object) io->buffer_id = SPA_ID_INVALID; io->status = SPA_STATUS_OK; } - - return SPA_STATUS_OK; + if (this->callbacks.funcs == NULL) + return consume_buffer(this); + else + return SPA_STATUS_OK; } static const struct spa_node_methods impl_node = { @@ -703,6 +758,8 @@ 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; @@ -710,6 +767,10 @@ 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); @@ -718,7 +779,9 @@ 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 | SPA_PORT_FLAG_LIVE; + port->info.flags = SPA_PORT_FLAG_NO_REF; + if (this->props.live) + port->info.flags |= 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 db28d9c3c..28b37dab4 100644 --- a/spa/plugins/test/fakesrc.c +++ b/spa/plugins/test/fakesrc.c @@ -27,6 +27,7 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.fakesrc"); struct props { + bool live; uint32_t pattern; }; @@ -74,6 +75,7 @@ struct impl { struct spa_callbacks callbacks; struct spa_source timer_source; + struct itimerspec timerspec; bool started; uint64_t start_time; @@ -87,10 +89,12 @@ 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; } @@ -125,6 +129,7 @@ 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; } @@ -159,6 +164,7 @@ 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); @@ -166,7 +172,13 @@ 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: @@ -182,15 +194,23 @@ static int fill_buffer(struct impl *this, struct buffer *b) static void set_timer(struct impl *this, bool enabled) { - 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; + 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); } - - 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) @@ -198,12 +218,14 @@ static inline int read_timer(struct impl *this) uint64_t expirations; int res = 0; - 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 (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)); + } } - return res; } @@ -289,7 +311,10 @@ static int impl_node_send_command(void *object, const struct spa_command *comman return 0; clock_gettime(CLOCK_MONOTONIC, &now); - this->start_time = SPA_TIMESPEC_TO_NSEC(&now); + if (this->props.live) + this->start_time = SPA_TIMESPEC_TO_NSEC(&now); + else + this->start_time = 0; this->buffer_count = 0; this->elapsed_time = 0; @@ -657,7 +682,10 @@ static int impl_node_process(void *object) io->buffer_id = SPA_ID_INVALID; } - return SPA_STATUS_OK; + if (this->callbacks.funcs == NULL) + return make_buffer(this); + else + return SPA_STATUS_OK; } static const struct spa_node_methods impl_node = { @@ -769,6 +797,10 @@ 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); @@ -777,7 +809,9 @@ 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 | SPA_PORT_FLAG_LIVE; + port->info.flags = SPA_PORT_FLAG_NO_REF; + if (this->props.live) + port->info.flags |= 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/videotestsrc/videotestsrc.c b/spa/plugins/videotestsrc/videotestsrc.c index a8d7059c8..db5c90113 100644 --- a/spa/plugins/videotestsrc/videotestsrc.c +++ b/spa/plugins/videotestsrc/videotestsrc.c @@ -35,14 +35,17 @@ 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; } @@ -94,7 +97,9 @@ 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; @@ -136,6 +141,13 @@ 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), @@ -164,6 +176,7 @@ 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: @@ -218,6 +231,7 @@ 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); @@ -225,8 +239,13 @@ 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: @@ -244,15 +263,22 @@ static int fill_buffer(struct impl *this, struct buffer *b) static void set_timer(struct impl *this, bool enabled) { - 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; + 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); } - - spa_loop_utils_update_timer(this->loop_utils, this->timer_source, &ts, NULL, true); } static int make_buffer(struct impl *this) @@ -332,7 +358,10 @@ static int impl_node_send_command(void *object, const struct spa_command *comman return 0; clock_gettime(CLOCK_MONOTONIC, &now); - this->start_time = SPA_TIMESPEC_TO_NSEC(&now); + if (this->props.live) + this->start_time = SPA_TIMESPEC_TO_NSEC(&now); + else + this->start_time = 0; this->frame_count = 0; this->elapsed_time = 0; @@ -726,6 +755,9 @@ 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) @@ -763,7 +795,10 @@ static int impl_node_process(void *object) io->buffer_id = SPA_ID_INVALID; } - return SPA_STATUS_OK; + if (!this->props.live) + return make_buffer(this); + else + return SPA_STATUS_OK; } static const struct spa_node_methods impl_node = { @@ -872,12 +907,18 @@ 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 | SPA_PORT_FLAG_LIVE; + port->info.flags = SPA_PORT_FLAG_NO_REF; + if (this->props.live) + port->info.flags |= 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 e0bd6cfe5..f8a60497e 100644 --- a/spa/plugins/vulkan/vulkan-blit-utils.c +++ b/spa/plugins/vulkan/vulkan-blit-utils.c @@ -12,10 +12,8 @@ #include #include #include -#ifdef __linux__ +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) #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 aa6f4a60f..1daf3b47c 100644 --- a/spa/plugins/vulkan/vulkan-compute-source.c +++ b/spa/plugins/vulkan/vulkan-compute-source.c @@ -33,6 +33,17 @@ 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) @@ -84,11 +95,14 @@ 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; @@ -124,6 +138,38 @@ 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; } @@ -168,6 +214,25 @@ 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; } @@ -177,15 +242,23 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, static void set_timer(struct impl *this, bool enabled) { - 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; + 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); } - - spa_system_timerfd_settime(this->data_system, this->timer_source.fd, SPA_FD_TIMER_ABSTIME, &ts, NULL); } static int read_timer(struct impl *this) @@ -193,12 +266,14 @@ static int read_timer(struct impl *this) uint64_t expirations; int res = 0; - 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 (this->async || this->props.live) { + if ((res = spa_system_timerfd_read(this->data_system, + this->timer_source.fd, &expirations)) < 0) { + if (res != -EAGAIN) + spa_log_error(this->log, "%p: timerfd error: %s", + this, spa_strerror(res)); + } } - return res; } @@ -273,6 +348,9 @@ 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); } } @@ -331,7 +409,10 @@ static int impl_node_send_command(void *object, const struct spa_command *comman return 0; clock_gettime(CLOCK_MONOTONIC, &now); - this->start_time = SPA_TIMESPEC_TO_NSEC(&now); + if (this->props.live) + this->start_time = SPA_TIMESPEC_TO_NSEC(&now); + else + this->start_time = 0; this->frame_count = 0; this->elapsed_time = 0; @@ -793,7 +874,10 @@ static int impl_node_process(void *object) io->buffer_id = SPA_ID_INVALID; } - return SPA_STATUS_OK; + if (!this->props.live) + return make_buffer(this); + else + return SPA_STATUS_OK; } static const struct spa_node_methods impl_node = { @@ -901,6 +985,7 @@ 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; @@ -908,6 +993,10 @@ 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); @@ -917,7 +1006,9 @@ 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 | SPA_PORT_FLAG_LIVE; + port->info.flags = SPA_PORT_FLAG_NO_REF; + if (this->props.live) + port->info.flags |= 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 503542483..49705bee8 100644 --- a/spa/plugins/vulkan/vulkan-compute-utils.c +++ b/spa/plugins/vulkan/vulkan-compute-utils.c @@ -11,10 +11,8 @@ #include #include #include -#ifdef __linux__ +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) #include -#else -#include #endif #include #include diff --git a/spa/plugins/vulkan/vulkan-utils.c b/spa/plugins/vulkan/vulkan-utils.c index 88a230dd6..6a0f693dc 100644 --- a/spa/plugins/vulkan/vulkan-utils.c +++ b/spa/plugins/vulkan/vulkan-utils.c @@ -11,10 +11,8 @@ #include #include #include -#ifdef __linux__ +#if !defined(__FreeBSD__) && !defined(__MidnightBSD__) #include -#else -#include #endif #include #include diff --git a/spa/tests/benchmark-aec.c b/spa/tests/benchmark-aec.c index 37d4a8375..3ac0fc62e 100644 --- a/spa/tests/benchmark-aec.c +++ b/spa/tests/benchmark-aec.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include diff --git a/spa/tools/spa-json-dump.c b/spa/tools/spa-json-dump.c index ed81330d4..ee3b42da7 100644 --- a/spa/tools/spa-json-dump.c +++ b/spa/tools/spa-json-dump.c @@ -15,28 +15,27 @@ #include #include -#include #include #define DEFAULT_INDENT 2 struct data { const char *filename; - - FILE *out; - struct spa_json_builder builder; + FILE *file; void *data; size_t size; + + int indent; + bool simple_string; + const char *comma; + const char *key_sep; }; -#define OPTIONS "hNC:Ri:s" +#define OPTIONS "hi: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' }, @@ -54,21 +53,72 @@ 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); } -static int dump(struct data *d, struct spa_json *it, const char *key, const char *value, int len) +#define REJECT "\"\\'=:,{}[]()#" + +static bool is_simple_string(const char *val, int len) { - struct spa_json_builder *b = &d->builder; + 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 sub; bool toplevel = false; - int res; + int count = 0, res; + char key[1024]; if (!value) { toplevel = true; @@ -77,22 +127,29 @@ static int dump(struct data *d, struct spa_json *it, const char *key, const char } if (spa_json_is_array(value, len)) { - spa_json_builder_object_push(b, key, "["); + fprintf(file, "["); spa_json_enter(it, &sub); while ((len = spa_json_next(&sub, &value)) > 0) { - if ((res = dump(d, &sub, NULL, value, len)) < 0) + fprintf(file, "%s\n%*s", count++ > 0 ? d->comma : "", + indent+d->indent, ""); + if ((res = dump(d, indent+d->indent, &sub, value, len)) < 0) return res; } - spa_json_builder_pop(b, "]"); + fprintf(file, "%s%*s]", count > 0 ? "\n" : "", + count > 0 ? indent : 0, ""); } else if (spa_json_is_object(value, len)) { - char k[1024]; - spa_json_builder_object_push(b, key, "{"); + fprintf(file, "{"); if (!toplevel) spa_json_enter(it, &sub); else sub = *it; - while ((len = spa_json_object_next(&sub, k, sizeof(k), &value)) > 0) { - res = dump(d, &sub, k, value, len); + 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); if (res < 0) { if (toplevel) *it = sub; @@ -101,10 +158,18 @@ static int dump(struct data *d, struct spa_json *it, const char *key, const char } if (toplevel) *it = sub; - spa_json_builder_pop(b, "}"); + 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); } else { - spa_json_builder_add_simple(b, key, INT_MAX, 0, value, len); + encode_string(d, value, len); } + if (spa_json_get_error(it, NULL, NULL)) return -EINVAL; @@ -127,12 +192,12 @@ static int process_json(struct data *d) len = 0; } - res = dump(d, &it, NULL, value, len); + res = dump(d, 0, &it, value, len); if (spa_json_next(&it, &value) < 0) res = -EINVAL; - fprintf(d->builder.f, "\n"); - fflush(d->builder.f); + fprintf(d->file, "\n"); + fflush(d->file); if (res < 0) { struct spa_error_location loc; @@ -192,50 +257,30 @@ 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.out = stdout; - - if (getenv("NO_COLOR") == NULL && isatty(fileno(d.out))) - colors = true; - setlinebuf(d.out); + d.simple_string = false; + d.comma = ","; + d.key_sep = ":"; + d.indent = DEFAULT_INDENT; 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': - indent = atoi(optarg); + d.indent = atoi(optarg); break; case 's': - flags |= SPA_JSON_BUILDER_FLAG_SIMPLE; + d.simple_string = true; + d.comma = ""; + d.key_sep = " ="; break; default: show_usage(&d, argv[0], true); @@ -263,15 +308,6 @@ 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 e0baeda92..46874af93 100644 --- a/src/daemon/client.conf.in +++ b/src/daemon/client.conf.in @@ -101,9 +101,6 @@ 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 83142dd29..f3c2c71be 100644 --- a/src/daemon/filter-chain/source-rnnoise.conf +++ b/src/daemon/filter-chain/source-rnnoise.conf @@ -21,6 +21,7 @@ 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 7ab93e92b..82647e9ca 100644 --- a/src/daemon/minimal.conf.in +++ b/src/daemon/minimal.conf.in @@ -100,10 +100,6 @@ 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 } @@ -332,9 +328,6 @@ 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 e43015780..da158f212 100644 --- a/src/daemon/pipewire-avb.conf.in +++ b/src/daemon/pipewire-avb.conf.in @@ -60,9 +60,6 @@ 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 db646db0b..8c21e37df 100644 --- a/src/daemon/pipewire-pulse.conf.in +++ b/src/daemon/pipewire-pulse.conf.in @@ -88,9 +88,6 @@ 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 a9142cede..c3eb7120f 100644 --- a/src/daemon/pipewire.conf.in +++ b/src/daemon/pipewire.conf.in @@ -121,10 +121,6 @@ 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 589646d61..8a43147a6 100644 --- a/src/examples/audio-dsp-filter.c +++ b/src/examples/audio-dsp-filter.c @@ -108,7 +108,6 @@ 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 0ef4a0e53..135d2e27e 100644 --- a/src/examples/audio-dsp-src.c +++ b/src/examples/audio-dsp-src.c @@ -18,6 +18,7 @@ #define M_PI_M2f (float)(M_PI+M_PI) +#define DEFAULT_RATE 44100 #define DEFAULT_FREQ 440 #define DEFAULT_VOLUME 0.7f @@ -60,9 +61,7 @@ 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 * - position->clock.rate.num / position->clock.rate.denom; - + out_port->accumulator += M_PI_M2f * DEFAULT_FREQ / DEFAULT_RATE; 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 c89ce8d78..add9597ed 100644 --- a/src/examples/video-src-alloc.c +++ b/src/examples/video-src-alloc.c @@ -192,11 +192,7 @@ 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; - 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)) + if (pw_stream_is_driving(data->stream)) pw_loop_update_timer(pw_thread_loop_get_loop(data->loop), data->timer, &timeout, &interval, false); break; @@ -394,7 +390,6 @@ 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 b16d810ad..6d08074de 100644 --- a/src/examples/video-src-fixate.c +++ b/src/examples/video-src-fixate.c @@ -18,11 +18,7 @@ #include #include #include -#ifdef __linux__ #include -#else -#include -#endif #include #include diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 3c57028b4..6ca9599d6 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -839,17 +839,6 @@ 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)) { @@ -1085,6 +1074,10 @@ 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; diff --git a/src/modules/meson.build b/src/modules/meson.build index 11b29a117..59f46ae13 100644 --- a/src/modules/meson.build +++ b/src/modules/meson.build @@ -46,7 +46,6 @@ 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', @@ -296,17 +295,6 @@ 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 = [ @@ -383,9 +371,10 @@ 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_sources += zeroconf_sources - pipewire_module_protocol_pulse_deps += zeroconf_deps + pipewire_module_protocol_pulse_deps += avahi_dep + cdata.set('HAVE_AVAHI', true) endif if gsettings_gio_dep.found() @@ -543,15 +532,6 @@ 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', @@ -579,12 +559,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', - zeroconf_sources ], + 'module-zeroconf-discover/avahi-poll.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, - dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, zeroconf_deps], + dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, avahi_dep], ) endif summary({'zeroconf-discover': build_module_zeroconf_discover}, bool_yn: true, section: 'Optional Modules') @@ -609,12 +589,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', - zeroconf_sources ], + 'module-zeroconf-discover/avahi-poll.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, - dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, zeroconf_deps], + dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, avahi_dep], ) endif summary({'raop-discover (needs Avahi)': build_module_raop_discover}, bool_yn: true, section: 'Optional Modules') @@ -623,12 +603,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', - zeroconf_sources ], + 'module-zeroconf-discover/avahi-poll.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, - dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, zeroconf_deps], + dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, avahi_dep], ) endif summary({'snapcast-discover (needs Avahi)': build_module_snapcast_discover}, bool_yn: true, section: 'Optional Modules') @@ -671,13 +651,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-rtp-session.c', - zeroconf_sources ], + [ 'module-zeroconf-discover/avahi-poll.c', + 'module-rtp-session.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, - dependencies : [pipewire_module_rtp_common_dep, zeroconf_deps], + dependencies : [pipewire_module_rtp_common_dep, avahi_dep], ) endif @@ -710,35 +690,6 @@ 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-adapter.c b/src/modules/module-adapter.c index 3325602ac..9f5474a08 100644 --- a/src/modules/module-adapter.c +++ b/src/modules/module-adapter.c @@ -184,7 +184,7 @@ static void *create_object(void *_data, if (properties == NULL) goto error_properties; - pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%u", + pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", pw_impl_factory_get_info(d->factory)->id); linger = pw_properties_get_bool(properties, PW_KEY_OBJECT_LINGER, false); @@ -196,7 +196,7 @@ static void *create_object(void *_data, client = resource ? pw_resource_get_client(resource): NULL; if (client && !linger) { - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%u", + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_impl_client_get_info(client)->id); } @@ -343,7 +343,7 @@ static void module_registered(void *data) char id[16]; int res; - snprintf(id, sizeof(id), "%u", pw_impl_module_get_info(module)->id); + snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id); items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id); pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1)); diff --git a/src/modules/module-avb/acmp.c b/src/modules/module-avb/acmp.c index 73a84ba89..eacfb6133 100644 --- a/src/modules/module-avb/acmp.c +++ b/src/modules/module-avb/acmp.c @@ -174,14 +174,13 @@ 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); @@ -252,14 +251,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 4b271b7fc..0149d633b 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 @@ -71,41 +71,6 @@ static int handle_unsol_lock_entity_milanv12(struct aecp *aecp, struct descripto } -void handle_cmd_lock_entity_expired_milan_v12(struct aecp *aecp, int64_t now) -{ - struct server *server = aecp->server; - struct descriptor *desc; - struct aecp_aem_entity_milan_state *entity_state; - struct aecp_aem_lock_state *lock; - - desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0); - if (desc == NULL) - return; - - entity_state = desc->ptr; - lock = &entity_state->state.lock_state; - - if (!lock->is_locked) - return; - - if (lock->base_info.expire_timeout >= now) - return; - - pw_log_info("entity lock held by %" PRIx64 " expired after %lus, releasing", - lock->locked_id, AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND); - - lock->is_locked = false; - lock->locked_id = 0; - /* - * No specific triggering controller (this is a timeout, not a command). - * Setting controller_entity_id to 0 combined with internal=true ensures - * reply_unsol_send notifies ALL registered controllers, including the - * one whose lock just expired. - */ - lock->base_info.controller_entity_id = 0; - handle_unsol_lock_common(aecp, lock, true); -} - /* LOCK_ENTITY */ /* Milan v1.2, Sec. 5.4.2.2; IEEE 1722.1-2021, Sec. 7.4.2*/ int handle_cmd_lock_entity_milan_v12(struct aecp *aecp, int64_t now, const void *m, int len) @@ -134,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, m, len); + return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len); entity_state = desc->ptr; lock = &entity_state->state.lock_state; @@ -183,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 * SPA_NSEC_PER_SEC; + AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND; lock->is_locked = true; } else { diff --git a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.h b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.h index c83bc464d..dee877a7f 100644 --- a/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.h +++ b/src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.h @@ -12,15 +12,6 @@ #include -/** - * @brief Checks whether the Milan entity lock has expired and releases it. - * - * Called once per second from the AECP periodic handler. If the lock is - * active and its timeout (set at lock time) is earlier than @p now, the - * lock is cleared. - */ -void handle_cmd_lock_entity_expired_milan_v12(struct aecp *aecp, int64_t now); - /** * @brief Command handling will generate the response for the lock command */ diff --git a/src/modules/module-avb/aecp-aem.c b/src/modules/module-avb/aecp-aem.c index 78b3eb818..fccf6b178 100644 --- a/src/modules/module-avb/aecp-aem.c +++ b/src/modules/module-avb/aecp-aem.c @@ -5,7 +5,6 @@ #include "aecp-aem.h" #include "aecp-aem-descriptors.h" -#include "aecp-aem-state.h" #include "aecp-aem-cmds-resps/cmd-resp-helpers.h" #include "utils.h" @@ -28,8 +27,7 @@ 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_ethernet_header *h = m; - const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); + const struct avb_packet_aecp_aem *p = m; const struct avb_packet_aecp_aem_acquire *ae; const struct descriptor *desc; uint16_t desc_type, desc_id; @@ -55,8 +53,7 @@ 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_ethernet_header *h = m; - const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); + const struct avb_packet_aecp_aem *p = m; const struct avb_packet_aecp_aem_acquire *ae; const struct descriptor *desc; uint16_t desc_type, desc_id; @@ -371,44 +368,6 @@ static const struct { }, }; -/** - * \brief Stub that queries the AECP entity lock state. - * - * Returns true when the entity is currently locked by a *different* controller - * than the one sending the command, meaning the command must be rejected with - * ENTITY_LOCKED. Returns false in all other cases (not locked, lock expired, - * or requester is the lock owner). - * - * Only Milan V1.2 entities maintain a lock state; legacy AVB entities always - * return false (unlocked). - */ -static bool check_locked(struct aecp *aecp, int64_t now, - const struct avb_packet_aecp_aem *p) -{ - struct server *server = aecp->server; - const struct descriptor *desc; - const struct aecp_aem_entity_milan_state *entity_state; - const struct aecp_aem_lock_state *lock; - - if (server->avb_mode != AVB_MODE_MILAN_V12) - return false; - - desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0); - if (desc == NULL) - return false; - - entity_state = desc->ptr; - lock = &entity_state->state.lock_state; - - /* A lock that has expired is treated as if the entity is unlocked. */ - if (lock->base_info.expire_timeout < now) - return false; - - /* Locked by a different controller → reject. */ - return lock->is_locked && - (lock->locked_id != htobe64(p->aecp.controller_guid)); -} - int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len) { const struct avb_ethernet_header *h = m; @@ -441,30 +400,9 @@ int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len) now = SPA_TIMESPEC_TO_NSEC(&ts_now); - /* - * For write (non-readonly) commands, check whether the entity is locked - * by a different controller before dispatching the handler. Readonly - * commands are always allowed regardless of lock state. - */ - if (!info->is_readonly && check_locked(aecp, now, p)) { - pw_log_info("aem command %s rejected: entity locked", - cmd_names[cmd_type]); - return reply_entity_locked(aecp, m, len); - } - return info->handle_command(aecp, now, m, len); } -void avb_aecp_aem_periodic(struct aecp *aecp, int64_t now) -{ - struct server *server = aecp->server; - - if (server->avb_mode != AVB_MODE_MILAN_V12) - return; - - handle_cmd_lock_entity_expired_milan_v12(aecp, now); -} - int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len) { return 0; diff --git a/src/modules/module-avb/aecp-aem.h b/src/modules/module-avb/aecp-aem.h index e4e120452..507d0f868 100644 --- a/src/modules/module-avb/aecp-aem.h +++ b/src/modules/module-avb/aecp-aem.h @@ -249,6 +249,5 @@ struct avb_packet_aecp_aem { int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len); int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len); -void avb_aecp_aem_periodic(struct aecp *aecp, int64_t now); #endif /* AVB_AEM_H */ diff --git a/src/modules/module-avb/aecp.c b/src/modules/module-avb/aecp.c index 6a15f87a9..6e6a1ba43 100644 --- a/src/modules/module-avb/aecp.c +++ b/src/modules/module-avb/aecp.c @@ -86,12 +86,6 @@ static int aecp_message(void *data, uint64_t now, const void *message, int len) return info->handle(aecp, message, len); } -static void aecp_periodic(void *data, uint64_t now) -{ - struct aecp *aecp = data; - avb_aecp_aem_periodic(aecp, (int64_t)now); -} - static void aecp_destroy(void *data) { struct aecp *aecp = data; @@ -130,7 +124,6 @@ static const struct server_events server_events = { AVB_VERSION_SERVER_EVENTS, .destroy = aecp_destroy, .message = aecp_message, - .periodic = aecp_periodic, .command = aecp_command }; diff --git a/src/modules/module-avb/avb-transport-loopback.h b/src/modules/module-avb/avb-transport-loopback.h deleted file mode 100644 index 18508b99a..000000000 --- a/src/modules/module-avb/avb-transport-loopback.h +++ /dev/null @@ -1,184 +0,0 @@ -/* 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 8729d6421..7cd7e8e7a 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) } } -static int raw_send_packet(struct server *server, const uint8_t dest[6], +int avb_server_send_packet(struct server *server, const uint8_t dest[6], uint16_t type, void *data, size_t size) { struct avb_ethernet_header *hdr = (struct avb_ethernet_header*)data; @@ -101,12 +101,6 @@ static int raw_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; @@ -142,7 +136,7 @@ static int load_filter(int fd, uint16_t eth, const uint8_t dest[6], const uint8_ return 0; } -static int raw_make_socket(struct server *server, uint16_t type, const uint8_t mac[6]) +int avb_server_make_socket(struct server *server, uint16_t type, const uint8_t mac[6]) { int fd, res; struct ifreq req; @@ -215,20 +209,13 @@ error_close: return res; } -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) +static int setup_socket(struct server *server) { struct impl *impl = server->impl; int fd, res; static const uint8_t bmac[6] = AVB_BROADCAST_MAC; - fd = raw_make_socket(server, AVB_TSN_ETH, bmac); + fd = avb_server_make_socket(server, AVB_TSN_ETH, bmac); if (fd < 0) return fd; @@ -257,119 +244,6 @@ 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; @@ -392,14 +266,10 @@ 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 (server->transport == NULL) - server->transport = &avb_transport_raw; - - if ((res = server->transport->setup(server)) < 0) + if ((res = setup_socket(server)) < 0) goto error_free; @@ -445,10 +315,12 @@ 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->transport) - server->transport->destroy(server); + if (server->source) + pw_loop_destroy_source(impl->loop, server->source); pw_timer_queue_cancel(&server->timer); spa_hook_list_clean(&server->listener_list); free(server->ifname); diff --git a/src/modules/module-avb/es-builder.c b/src/modules/module-avb/es-builder.c index 6234e1b92..adcc6b930 100644 --- a/src/modules/module-avb/es-builder.c +++ b/src/modules/module-avb/es-builder.c @@ -50,7 +50,7 @@ static void *es_builder_desc_entity_milan_v12(struct server *server, &entity_state); if (!ptr_alloc) { - pw_log_error("Error during allocation\n"); + pw_log_error("Error durring allocation\n"); spa_assert(0); } diff --git a/src/modules/module-avb/internal.h b/src/modules/module-avb/internal.h index bb4961674..82ced2f21 100644 --- a/src/modules/module-avb/internal.h +++ b/src/modules/module-avb/internal.h @@ -5,8 +5,6 @@ #ifndef AVB_INTERNAL_H #define AVB_INTERNAL_H -#include - #include #ifdef __cplusplus @@ -19,22 +17,6 @@ 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; @@ -95,16 +77,12 @@ 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; @@ -124,6 +102,7 @@ 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); } @@ -166,17 +145,11 @@ 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 c6505d41b..73d5275ca 100644 --- a/src/modules/module-avb/mrp.c +++ b/src/modules/module-avb/mrp.c @@ -302,8 +302,6 @@ 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 399343267..78683412c 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 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_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_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 611ddd537..92d1e65b4 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 - 1) * 6 * 6; + *ev = a->attr.mrp->pending_send * 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 - 1) * 6 * 6; + *ev = a->attr.mrp->pending_send * 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 - 1) * 36; + *ev = a->attr.mrp->pending_send * 36; f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer); f->end_mark = 0; @@ -332,8 +332,7 @@ static void msrp_notify(void *data, uint64_t now, uint8_t notify) { struct attr *a = data; struct msrp *msrp = a->msrp; - if (dispatch[a->attr.type].notify) - dispatch[a->attr.type].notify(msrp, now, a, notify); + return 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 e2e501a9e..20862c2ae 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 - 1) * 36; + *ev = a->attr.mrp->pending_send * 36; f = SPA_PTROFF(ev, sizeof(*ev), struct avb_packet_mrp_footer); f->end_mark = 0; @@ -171,8 +171,7 @@ static void mvrp_notify(void *data, uint64_t now, uint8_t notify) { struct attr *a = data; struct mvrp *mvrp = a->mvrp; - if (dispatch[a->attr.type].notify) - dispatch[a->attr.type].notify(mvrp, now, a, notify); + return 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 26a3a795b..f7101bdf0 100644 --- a/src/modules/module-avb/stream.c +++ b/src/modules/module-avb/stream.c @@ -116,10 +116,9 @@ static int flush_write(struct stream *stream, uint64_t current_time) p->timestamp = ptime; p->dbc = dbc; - n = avb_server_stream_send(stream->server, stream, - &stream->msg, MSG_NOSIGNAL); + n = sendmsg(stream->source->fd, &stream->msg, MSG_NOSIGNAL); if (n < 0 || n != (ssize_t)stream->pdu_size) { - pw_log_error("stream send failed %zd != %zd: %m", + pw_log_error("sendmsg() failed %zd != %zd: %m", n, stream->pdu_size); } txtime += stream->pdu_period; @@ -332,8 +331,6 @@ 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: @@ -351,7 +348,82 @@ void stream_destroy(struct stream *stream) static int setup_socket(struct stream *stream) { - return avb_server_stream_setup_socket(stream->server, stream); + struct server *server = stream->server; + int fd, res; + char buf[128]; + struct ifreq req; + + fd = socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, htons(ETH_P_ALL)); + if (fd < 0) { + pw_log_error("socket() failed: %m"); + return -errno; + } + + spa_zero(req); + snprintf(req.ifr_name, sizeof(req.ifr_name), "%s", server->ifname); + res = ioctl(fd, SIOCGIFINDEX, &req); + if (res < 0) { + pw_log_error("SIOCGIFINDEX %s failed: %m", server->ifname); + res = -errno; + goto error_close; + } + + spa_zero(stream->sock_addr); + stream->sock_addr.sll_family = AF_PACKET; + stream->sock_addr.sll_protocol = htons(ETH_P_TSN); + stream->sock_addr.sll_ifindex = req.ifr_ifindex; + + if (stream->direction == SPA_DIRECTION_OUTPUT) { + struct sock_txtime txtime_cfg; + + res = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &stream->prio, + sizeof(stream->prio)); + if (res < 0) { + pw_log_error("setsockopt(SO_PRIORITY %d) failed: %m", stream->prio); + res = -errno; + goto error_close; + } + + txtime_cfg.clockid = CLOCK_TAI; + txtime_cfg.flags = 0; + res = setsockopt(fd, SOL_SOCKET, SO_TXTIME, &txtime_cfg, + sizeof(txtime_cfg)); + if (res < 0) { + pw_log_error("setsockopt(SO_TXTIME) failed: %m"); + res = -errno; + goto error_close; + } + } else { + struct packet_mreq mreq; + + res = bind(fd, (struct sockaddr *) &stream->sock_addr, sizeof(stream->sock_addr)); + if (res < 0) { + pw_log_error("bind() failed: %m"); + res = -errno; + goto error_close; + } + + spa_zero(mreq); + mreq.mr_ifindex = req.ifr_ifindex; + mreq.mr_type = PACKET_MR_MULTICAST; + mreq.mr_alen = ETH_ALEN; + memcpy(&mreq.mr_address, stream->addr, ETH_ALEN); + res = setsockopt(fd, SOL_PACKET, PACKET_ADD_MEMBERSHIP, + &mreq, sizeof(struct packet_mreq)); + + pw_log_info("join %s", avb_utils_format_addr(buf, 128, stream->addr)); + + if (res < 0) { + pw_log_error("setsockopt(ADD_MEMBERSHIP) failed: %m"); + res = -errno; + goto error_close; + } + } + return fd; + +error_close: + close(fd); + return res; } static void handle_iec61883_packet(struct stream *stream, @@ -476,24 +548,3 @@ 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 4cc02ddd3..f650cc216 100644 --- a/src/modules/module-avb/stream.h +++ b/src/modules/module-avb/stream.h @@ -78,6 +78,5 @@ 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-device.c b/src/modules/module-client-device.c index 658276eb9..c02a4058f 100644 --- a/src/modules/module-client-device.c +++ b/src/modules/module-client-device.c @@ -135,9 +135,9 @@ static void *create_object(void *_data, goto error_properties; } - pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%u", + pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", pw_global_get_id(pw_impl_factory_get_global(factory))); - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%u", + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_global_get_id(pw_impl_client_get_global(client))); result = pw_client_device_new(device_resource, properties); diff --git a/src/modules/module-client-device/resource-device.c b/src/modules/module-client-device/resource-device.c index 8b3ddea99..ca00bfef9 100644 --- a/src/modules/module-client-device/resource-device.c +++ b/src/modules/module-client-device/resource-device.c @@ -108,7 +108,7 @@ struct pw_impl_device *pw_client_device_new(struct pw_resource *resource, if (properties == NULL) return NULL; - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%u", + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_impl_client_get_info(client)->id); device = pw_context_create_device(context, properties, sizeof(struct impl)); diff --git a/src/modules/module-client-node/client-node.c b/src/modules/module-client-node/client-node.c index 1e104429c..9740e1c89 100644 --- a/src/modules/module-client-node/client-node.c +++ b/src/modules/module-client-node/client-node.c @@ -92,6 +92,7 @@ struct impl { struct spa_node node; + struct spa_log *log; struct spa_loop *data_loop; struct spa_system *data_system; @@ -282,7 +283,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]; - pw_log_debug("%p: clear buffer %d", impl, i); + spa_log_debug(impl->log, "%p: clear buffer %d", impl, i); clear_buffer(impl, &b->buffer); pw_memblock_unref(b->mem); } @@ -299,7 +300,7 @@ static void free_mix(struct port *p, struct mix *mix) if (mix->n_buffers) { /* this shouldn't happen */ - pw_log_warn("%p: mix port-id:%u freeing leaked buffers", impl, mix->mix_id - 1u); + spa_log_warn(impl->log, "%p: mix port-id:%u freeing leaked buffers", impl, mix->mix_id - 1u); } clear_buffers(impl, mix); @@ -330,13 +331,11 @@ 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; @@ -352,6 +351,8 @@ 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,8 +367,7 @@ static int impl_node_enum_params(void *object, int seq, if (count == num) break; } - - return 0; + return found ? 0 : -ENOENT; } static int impl_node_set_param(void *object, uint32_t id, uint32_t flags, @@ -509,7 +509,7 @@ do_update_port(struct impl *impl, const struct spa_port_info *info) { if (change_mask & PW_CLIENT_NODE_PORT_UPDATE_PARAMS) { - pw_log_debug("%p: port %u update %d params", impl, port->id, n_params); + spa_log_debug(impl->log, "%p: port %u update %d params", impl, port->id, n_params); update_params(&port->params, n_params, params); } @@ -543,7 +543,7 @@ static int mix_clear_cb(void *item, void *data) static void clear_port(struct impl *impl, struct port *port) { - pw_log_debug("%p: clear port %p", impl, port); + spa_log_debug(impl->log, "%p: clear port %p", impl, port); do_update_port(impl, port, PW_CLIENT_NODE_PORT_UPDATE_PARAMS | @@ -599,6 +599,7 @@ 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,9 +610,6 @@ 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; @@ -627,6 +625,8 @@ 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; @@ -641,7 +641,7 @@ node_port_enum_params(struct impl *impl, int seq, if (count == num) break; } - return 0; + return found ? 0 : -ENOENT; } static int @@ -782,7 +782,7 @@ do_port_use_buffers(struct impl *impl, if (n_buffers > MAX_BUFFERS) return -ENOSPC; - pw_log_debug("%p: %s port %d.%d use buffers %p %u flags:%08x", impl, + spa_log_debug(impl->log, "%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); @@ -852,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); - pw_log_debug("%p: buffer %d %d %d %d", impl, i, mb[i].mem_id, + spa_log_debug(impl->log, "%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); @@ -885,7 +885,7 @@ do_port_use_buffers(struct impl *impl, if (d->flags & SPA_DATA_FLAG_WRITABLE) flags |= PW_MEMBLOCK_FLAG_WRITABLE; - pw_log_debug("mem %d type:%d fd:%d", j, d->type, (int)d->fd); + spa_log_debug(impl->log, "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) @@ -896,14 +896,14 @@ do_port_use_buffers(struct impl *impl, break; } case SPA_DATA_MemPtr: - pw_log_debug("mem %d %zd", j, SPA_PTRDIFF(d->data, baseptr)); + spa_log_debug(impl->log, "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; - pw_log_error("invalid memory type %d", d->type); + spa_log_error(impl->log, "invalid memory type %d", d->type); break; } } @@ -939,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); - pw_log_trace_fp("reuse buffer %d", buffer_id); + spa_log_trace_fp(impl->log, "reuse buffer %d", buffer_id); return -ENOTSUP; } @@ -952,7 +952,7 @@ static int impl_node_process(void *object) /* this should not be called, we call the exported node * directly */ - pw_log_warn("exported node activation"); + spa_log_warn(impl->log, "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); @@ -1013,7 +1013,7 @@ client_node_port_update(void *data, struct port *port; bool remove; - pw_log_debug("%p: got port update change:%08x params:%d", + spa_log_debug(impl->log, "%p: got port update change:%08x params:%d", impl, change_mask, n_params); remove = (change_mask == 0); @@ -1051,7 +1051,7 @@ client_node_port_update(void *data, static int client_node_set_active(void *data, bool active) { struct impl *impl = data; - pw_log_debug("%p: active:%d", impl, active); + spa_log_debug(impl->log, "%p: active:%d", impl, active); return pw_impl_node_set_active(impl->this.node, active); } @@ -1074,7 +1074,7 @@ static int client_node_port_buffers(void *data, struct mix *mix; uint32_t i, j; - pw_log_debug("%p: %s port %d.%d buffers %p %u", impl, + spa_log_debug(impl->log, "%p: %s port %d.%d buffers %p %u", impl, direction == SPA_DIRECTION_INPUT ? "input" : "output", port_id, mix_id, buffers, n_buffers); @@ -1102,7 +1102,7 @@ static int client_node_port_buffers(void *data, oldbuf = b->outbuf; newbuf = buffers[i]; - pw_log_debug("buffer %d n_datas:%d", i, newbuf->n_datas); + spa_log_debug(impl->log, "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; @@ -1111,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)) { - pw_log_debug("buffer:%d data:%d has non mappable MemFd, " + spa_log_debug(impl->log, "buffer:%d data:%d has non mappable MemFd, " "fixing to ensure backwards compatibility.", i, j); flags |= SPA_DATA_FLAG_MAPPABLE; @@ -1126,7 +1126,7 @@ static int client_node_port_buffers(void *data, b->datas[j].flags = flags; b->datas[j].fd = d->fd; - pw_log_debug(" data %d type:%d fl:%08x fd:%d, offs:%d max:%d", + spa_log_debug(impl->log, " data %d type:%d fl:%08x fd:%d, offs:%d max:%d", j, d->type, flags, (int) d->fd, d->mapoffset, d->maxsize); } @@ -1153,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))) { - pw_log_warn("%p: got error", impl); + spa_log_warn(impl->log, "%p: got error", impl); return; } if (SPA_LIKELY(source->rmask & SPA_IO_IN)) { @@ -1170,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; - pw_log_trace_fp("%p: got ready %d", impl, status); + spa_log_trace_fp(impl->log, "%p: got ready %d", impl, status); spa_node_call_ready(&impl->callbacks, status); } else { - pw_log_trace_fp("%p: got complete", impl); + spa_log_trace_fp(impl->log, "%p: got complete", impl); pw_impl_node_rt_emit_complete(node); } } @@ -1764,7 +1764,7 @@ struct pw_impl_client_node *pw_impl_client_node_new(struct pw_resource *resource goto error_exit_free; } - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%u", client->global->id); + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", client->global->id); this = &impl->this; @@ -1774,6 +1774,7 @@ 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 c98b0efcd..05b9218ce 100644 --- a/src/modules/module-ffado-driver.c +++ b/src/modules/module-ffado-driver.c @@ -345,26 +345,34 @@ 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) { - uint32_t j, size = c.value.size; - const uint8_t *data = c_body; + uint8_t data[16]; + int j, size; + size_t c_size = c.value.size; + uint64_t state = 0; - if (c.type != SPA_CONTROL_Midi) + if (c.type != SPA_CONTROL_UMP) continue; if (index < c.offset) index = SPA_ROUND_UP_N(c.offset, 8); - for (j = 0; j < size; j++) { - if (index >= n_samples) { - /* keep events that don't fit for the next cycle */ - if (p->event_pos < sizeof(p->event_buffer)) - p->event_buffer[p->event_pos++] = data[j]; + 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++; + } else - unhandled++; + dst[index] = 0x01000000 | (uint32_t) data[j]; + index += 8; } - else - dst[index] = 0x01000000 | (uint32_t) data[j]; - index += 8; } } if (unhandled > 0) @@ -489,8 +497,16 @@ static void ffado_to_midi(struct port *p, float *dst, uint32_t *src, uint32_t si continue; if (process_byte(p, i, data & 0xff, &frame, &bytes, &size)) { - spa_pod_builder_control(&b, frame, SPA_CONTROL_Midi); - spa_pod_builder_bytes(&b, bytes, size); + uint64_t state = 0; + while (size > 0) { + uint32_t ev[4]; + int ev_size = spa_ump_from_midi(&bytes, &size, ev, sizeof(ev), 0, &state); + if (ev_size <= 0) + break; + + spa_pod_builder_control(&b, frame, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ev, ev_size); + } } } spa_pod_builder_pop(&b, &f); diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index c369810fe..64a92c5ac 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 = "ladspa-rubberband" + * plugin = "/usr/lib64/ladspa/ladspa-rubberband.so" * 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. Default 1.0 + * - `gain` the overall gain to apply to the IR file. * - `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. Default 0 + * and IR rate and is recommended. * - `filename` The IR to load or create. Possible values are: * - `/hilbert` creates a [hilbert function](https://en.wikipedia.org/wiki/Hilbert_transform) * that can be used to phase shift the signal by +/-90 degrees. The @@ -1651,7 +1651,7 @@ static const struct pw_stream_events out_stream_events = { static int setup_streams(struct impl *impl) { - int res = 0; + int res; uint32_t i, n_params, *offs, flags; struct pw_array offsets; const struct spa_pod **params = NULL; @@ -1702,19 +1702,6 @@ 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; @@ -1730,6 +1717,8 @@ 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; @@ -1750,9 +1739,8 @@ static int setup_streams(struct impl *impl) spa_pod_dynamic_builder_init(&b, NULL, 0, 4096); } if (impl->playback) { - if (n_params == 0) - params[n_params++] = spa_format_audio_raw_build(&b.b, - SPA_PARAM_EnumFormat, &impl->playback_info); + 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 bee37da4f..0c0cee034 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -50,6 +50,7 @@ * * - `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 @@ -242,16 +243,13 @@ static inline void do_volume(float *dst, const float *src, struct volume *vol, u } } -static inline bool fix_midi_event(const uint8_t *data, size_t size, uint8_t dst[3]) +static inline void fix_midi_event(uint8_t *data, size_t size) { /* fixup NoteOn with vel 0 */ if (size > 2 && (data[0] & 0xF0) == 0x90 && data[2] == 0x00) { - dst[0] = 0x80 + (data[0] & 0x0F); - dst[1] = data[1]; - dst[2] = 0x40; - return true; + data[0] = 0x80 + (data[0] & 0x0F); + data[2] = 0x40; } - return false; } static void midi_to_jack(struct impl *impl, float *dst, float *src, uint32_t n_samples) @@ -262,6 +260,9 @@ 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) @@ -273,20 +274,36 @@ 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) { - uint32_t size = c.value.size; - const uint8_t *data = c_body; - uint8_t tmp[3]; + int size; + size_t c_size = c.value.size; + uint64_t state = 0; - if (c.type != SPA_CONTROL_Midi) + if (c.type != SPA_CONTROL_UMP) continue; - if (impl->fix_midi && fix_midi_event(data, size, tmp)) { - data = tmp; - size = 3; + 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 ((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)); } } @@ -302,11 +319,19 @@ static void jack_to_midi(float *dst, float *src, uint32_t size) spa_pod_builder_push_sequence(&b, &f, 0); for (i = 0; i < count; i++) { jack_midi_event_t ev; + uint64_t state = 0; jack.midi_event_get(&ev, src, i); - spa_pod_builder_control(&b, ev.time, SPA_CONTROL_Midi); - spa_pod_builder_bytes(&b, ev.buffer, ev.size); + while (ev.size > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&ev.buffer, &ev.size, ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + break; + + spa_pod_builder_control(&b, ev.time, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ump, ump_size); + } } spa_pod_builder_pop(&b, &f); } diff --git a/src/modules/module-jack-tunnel/weakjack.h b/src/modules/module-jack-tunnel/weakjack.h index 6a3d9bc67..6d5b5503e 100644 --- a/src/modules/module-jack-tunnel/weakjack.h +++ b/src/modules/module-jack-tunnel/weakjack.h @@ -158,36 +158,34 @@ 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; - while ((p = strstr(lib, "../")) != NULL) - lib = p + 3; + if (lib[0] != '/') { + const char *search_dirs, *p, *state = NULL; + char path[PATH_MAX]; + size_t len; - 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; - res = -ENAMETOOLONG; + while ((p = pw_split_walk(search_dirs, ":", &len, &state))) { + int pathlen; - 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-1] == '/' || lib[len] == '/')) - pathlen = snprintf(path, sizeof(path), "%s", lib); - else + if (len >= sizeof(path)) { + res = -ENAMETOOLONG; + continue; + } pathlen = snprintf(path, sizeof(path), "%.*s/%s", (int) len, p, lib); - - if (pathlen < 0 || (size_t) pathlen >= sizeof(path)) - continue; - - if ((res = weakjack_load_by_path(jack, path)) == 0) - break; + 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); } return res; } diff --git a/src/modules/module-link-factory.c b/src/modules/module-link-factory.c index 1f01078e8..d54df2758 100644 --- a/src/modules/module-link-factory.c +++ b/src/modules/module-link-factory.c @@ -485,12 +485,12 @@ static void *create_object(void *_data, linger = pw_properties_get_bool(properties, PW_KEY_OBJECT_LINGER, false); - pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%u", + pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", pw_impl_factory_get_info(d->factory)->id); client = resource ? pw_resource_get_client(resource) : NULL; if (client && !linger) - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%u", + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_impl_client_get_info(client)->id); if (!d->allow_passive) diff --git a/src/modules/module-metadata.c b/src/modules/module-metadata.c index 84b98a7ce..79d80d1b4 100644 --- a/src/modules/module-metadata.c +++ b/src/modules/module-metadata.c @@ -203,9 +203,9 @@ static void *create_object(void *_data, if (properties == NULL) return NULL; - pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%u", + pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", pw_impl_factory_get_info(data->factory)->id); - pw_properties_setf(properties, PW_KEY_MODULE_ID, "%u", + pw_properties_setf(properties, PW_KEY_MODULE_ID, "%d", pw_impl_module_get_info(data->module)->id); if (pw_properties_get(properties, PW_KEY_METADATA_NAME) == NULL) @@ -218,7 +218,7 @@ static void *create_object(void *_data, goto error_resource; } - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%u", + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_impl_client_get_info(client)->id); result = pw_metadata_new(context, metadata_resource, properties); diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index 7547cc5b2..eacc1c95b 100644 --- a/src/modules/module-netjack2/peer.c +++ b/src/modules/module-netjack2/peer.c @@ -273,18 +273,39 @@ 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, const void *data, uint32_t size, bool fix) + uint32_t offset, void *data, uint32_t size) { - uint8_t *ptr = n2j_midi_buffer_reserve(buf, offset, size); - if (ptr != NULL) { + void *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) { @@ -293,6 +314,7 @@ 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); @@ -310,17 +332,39 @@ static void midi_to_netjack2(struct netjack2_peer *peer, return; while (spa_pod_parser_get_control_body(&parser, &c, &c_body) >= 0) { - uint32_t size = c.value.size; - const uint8_t *data = c_body; + int size; + uint8_t data[16]; + bool was_sysex = in_sysex; + size_t c_size = c.value.size; + uint64_t state = 0; - if (c.type != SPA_CONTROL_Midi) + if (c.type != SPA_CONTROL_UMP) continue; - if (c.offset >= n_samples) { - buf->lost_events++; - 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); } - 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), @@ -351,6 +395,8 @@ 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; @@ -359,8 +405,17 @@ static inline void netjack2_to_midi(float *dst, uint32_t size, struct nj2_midi_b else continue; - spa_pod_builder_control(&b, ev->time, SPA_CONTROL_Midi); - spa_pod_builder_bytes(&b, data, ev->size); + s = ev->size; + while (s > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&data, &s, ump, sizeof(ump), 0, &state); + if (ump_size <= 0) { + 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_pop(&b, &f); } diff --git a/src/modules/module-profiler.c b/src/modules/module-profiler.c index 557ca2649..b49629ba9 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: node:%p avail %d", n, impl, avail); + pw_log_trace("%p: avail %d", impl, avail); if (avail > 0) { size_t size = total + avail + sizeof(struct spa_pod_struct); @@ -559,7 +559,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) free(impl); return -errno; } - pw_properties_setf(impl->properties, PW_KEY_OBJECT_ID, "%u", pw_global_get_id(impl->global)); + pw_properties_setf(impl->properties, PW_KEY_OBJECT_ID, "%d", pw_global_get_id(impl->global)); pw_properties_setf(impl->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(impl->global)); diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c index 2a0b2f438..98a43829b 100644 --- a/src/modules/module-protocol-native.c +++ b/src/modules/module-protocol-native.c @@ -663,7 +663,7 @@ static struct client_data *client_new(struct server *s, int fd) } #endif - pw_properties_setf(props, PW_KEY_MODULE_ID, "%u", d->module->global->id); + pw_properties_setf(props, PW_KEY_MODULE_ID, "%d", d->module->global->id); client = pw_context_create_client(s->this.core, protocol, props, sizeof(struct client_data)); diff --git a/src/modules/module-protocol-native/connection.c b/src/modules/module-protocol-native/connection.c index 15c739428..b376a0f69 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 > buf->n_fds) + if (buf->msg.n_fds + buf->fds_offset > MAX_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 0c82d2da8..eabcbbd67 100644 --- a/src/modules/module-protocol-native/test-connection.c +++ b/src/modules/module-protocol-native/test-connection.c @@ -3,7 +3,6 @@ /* SPDX-License-Identifier: MIT */ #include -#include #include #include @@ -166,87 +165,6 @@ 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; @@ -280,26 +198,6 @@ 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 ffb5aa0fe..d4425a3fe 100644 --- a/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c +++ b/src/modules/module-protocol-pulse/modules/module-zeroconf-publish.c @@ -15,7 +15,14 @@ #include "../module.h" #include "../pulse-server.h" #include "../server.h" -#include "../../zeroconf-utils/zeroconf.h" +#include "../../module-zeroconf-discover/avahi-poll.h" + +#include +#include +#include +#include +#include +#include /** \page page_pulse_module_zeroconf_publish Zeroconf Publish * @@ -45,17 +52,32 @@ 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; }; @@ -69,8 +91,8 @@ struct module_zeroconf_publish_data { struct spa_hook manager_listener; struct spa_hook impl_listener; - struct pw_zeroconf *zeroconf; - struct spa_hook zeroconf_listener; + AvahiPoll *avahi_poll; + AvahiClient *client; /* lists of services */ struct spa_list pending; @@ -94,43 +116,47 @@ static const struct pw_core_events core_events = { .error = on_core_error, }; -static void unpublish_service(struct service *s) +static void get_service_name(struct pw_manager_object *o, char *buf, size_t length) { - const char *device; + const char *hn, *un, *n; - spa_list_remove(&s->link); - spa_list_append(&s->userdata->pending, &s->link); - s->published = false; - s->server = NULL; + hn = pw_get_host_name(); + un = pw_get_user_name(); + n = pw_properties_get(o->props, PW_KEY_NODE_DESCRIPTION); - 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); + snprintf(buf, length, "%s@%s: %s", un, hn, n); } static void service_free(struct service *s) { pw_log_debug("service %p: free", s); - if (s->published) - unpublish_service(s); + if (s->entry_group) + avahi_entry_group_free(s->entry_group); + + if (s->name) + free(s->name); pw_properties_free(s->props); + avahi_string_list_free(s->txt); spa_list_remove(&s->link); - /* no need to free, the service is added as custom - * data on the object */ } -#define PA_CHANNEL_MAP_SNPRINT_MAX (CHANNELS_MAX * 32) +static void unpublish_service(struct service *s) +{ + spa_list_remove(&s->link); + spa_list_append(&s->userdata->pending, &s->link); + s->published = false; + s->server = NULL; +} + +static void unpublish_all_services(struct module_zeroconf_publish_data *d) +{ + struct service *s; + + spa_list_consume(s, &d->published, link) + unpublish_service(s); +} static char* channel_map_snprint(char *s, size_t l, const struct channel_map *map) { @@ -162,39 +188,6 @@ 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) { @@ -207,10 +200,6 @@ 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; @@ -239,49 +228,19 @@ static void fill_service_data(struct module_zeroconf_publish_data *d, struct ser s->ss = dev_info.ss; s->cm = dev_info.map; - - s->props = pw_properties_new(NULL, NULL); - - txt_record_server_data(s->userdata->manager->info, s->props); + s->name = strdup(name); + s->props = pw_properties_copy(o->props); if (is_sink) { - 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; - } + s->is_sink = true; + s->service_type = SERVICE_TYPE_SINK; + s->subtype = flags & SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL; } else if (is_source) { - 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; + s->is_sink = false; + s->service_type = SERVICE_TYPE_SOURCE; + s->subtype = flags & SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL; } 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) @@ -293,6 +252,8 @@ 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); @@ -302,6 +263,127 @@ 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; @@ -310,47 +392,109 @@ 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 = 4; + *proto = AVAHI_PROTO_INET; *port = ntohs(((struct sockaddr_in*) &server->addr)->sin_port); return server; } else if (server->addr.ss_family == AF_INET6) { - *proto = 6; + *proto = AVAHI_PROTO_INET6; *port = ntohs(((struct sockaddr_in6*) &server->addr)->sin6_port); return server; } } + return NULL; } static void publish_service(struct service *s) { struct module_zeroconf_publish_data *d = s->userdata; - int proto, res; + int proto; uint16_t port; - struct server *server = find_server(s, &proto, &port); - const char *device; + struct server *server = find_server(s, &proto, &port); if (!server) return; - device = pw_properties_get(s->props, "device"); - pw_log_debug("found server:%p proto:%d port:%d", server, proto, port); - 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)); + if (!d->client || avahi_client_get_state(d->client) != AVAHI_CLIENT_S_RUNNING) return; + + s->published = true; + if (!s->entry_group) { + s->entry_group = avahi_entry_group_new(d->client, service_entry_group_callback, s); + if (s->entry_group == NULL) { + pw_log_error("avahi_entry_group_new(): %s", + avahi_strerror(avahi_client_errno(d->client))); + goto error; + } + } else { + avahi_entry_group_reset(s->entry_group); + } + + if (s->txt == NULL) + s->txt = get_service_txt(s); + + if (avahi_entry_group_add_service_strlst( + s->entry_group, + AVAHI_IF_UNSPEC, proto, + 0, + s->service_name, + s->service_type, + NULL, + NULL, + port, + s->txt) < 0) { + pw_log_error("avahi_entry_group_add_service_strlst(): %s", + avahi_strerror(avahi_client_errno(d->client))); + goto error; + } + + if (avahi_entry_group_add_service_subtype( + s->entry_group, + AVAHI_IF_UNSPEC, proto, + 0, + s->service_name, + s->service_type, + NULL, + s->is_sink ? (s->subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SINK_HARDWARE : SERVICE_SUBTYPE_SINK_VIRTUAL) : + (s->subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SOURCE_HARDWARE : (s->subtype == SUBTYPE_VIRTUAL ? SERVICE_SUBTYPE_SOURCE_VIRTUAL : SERVICE_SUBTYPE_SOURCE_MONITOR))) < 0) { + + pw_log_error("avahi_entry_group_add_service_subtype(): %s", + avahi_strerror(avahi_client_errno(d->client))); + goto error; + } + + if (!s->is_sink && s->subtype != SUBTYPE_MONITOR) { + if (avahi_entry_group_add_service_subtype( + s->entry_group, + AVAHI_IF_UNSPEC, proto, + 0, + s->service_name, + SERVICE_TYPE_SOURCE, + NULL, + SERVICE_SUBTYPE_SOURCE_NON_MONITOR) < 0) { + pw_log_error("avahi_entry_group_add_service_subtype(): %s", + avahi_strerror(avahi_client_errno(d->client))); + goto error; + } + } + + if (avahi_entry_group_commit(s->entry_group) < 0) { + pw_log_error("avahi_entry_group_commit(): %s", + avahi_strerror(avahi_client_errno(d->client))); + goto error; } spa_list_remove(&s->link); spa_list_append(&d->published, &s->link); - s->published = true; s->server = server; - pw_log_info("published service: %s", device); + pw_log_info("created service: %s", s->service_name); + return; + +error: + s->published = false; return; } @@ -362,6 +506,62 @@ 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)) @@ -424,6 +624,7 @@ static void impl_server_stopped(void *data, struct server *server) if (s->server == server) unpublish_service(s); } + publish_pending(d); } @@ -433,18 +634,10 @@ 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) { @@ -456,22 +649,24 @@ 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; @@ -489,18 +684,22 @@ static int module_zeroconf_publish_unload(struct module *module) spa_list_consume(s, &d->pending, link) service_free(s); - if (d->zeroconf) { - spa_hook_remove(&d->zeroconf_listener); - pw_zeroconf_destroy(d->zeroconf); - } + if (d->client) + avahi_client_free(d->client); + + if (d->avahi_poll) + pw_avahi_poll_free(d->avahi_poll); + if (d->manager != NULL) { spa_hook_remove(&d->manager_listener); pw_manager_destroy(d->manager); } + if (d->core != NULL) { spa_hook_remove(&d->core_listener); pw_core_disconnect(d->core); } + return 0; } diff --git a/src/modules/module-protocol-pulse/pulse-server.c b/src/modules/module-protocol-pulse/pulse-server.c index 38d967777..59610ef57 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -1258,6 +1258,8 @@ static void stream_param_changed(void *data, uint32_t id, const struct spa_pod * pw_stream_set_control(stream->stream, SPA_PROP_mute, 1, &val, 0); } + if (stream->corked) + stream_set_paused(stream, true, "cork after create"); /* if peer exists, reply immediately, otherwise reply when the link is created */ peer = find_linked(stream->client->manager, stream->id, stream->direction); @@ -1619,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_new(NULL, NULL); + props = pw_properties_copy(client->props); if (props == NULL) goto error_errno; @@ -1779,7 +1781,6 @@ static int do_create_playback_stream(struct client *client, uint32_t command, ui goto error_errno; stream->corked = corked; - stream->is_paused = corked; stream->adjust_latency = adjust_latency; stream->early_requests = early_requests; stream->volume = volume; @@ -1804,8 +1805,6 @@ static int do_create_playback_stream(struct client *client, uint32_t command, ui flags = 0; if (no_move) flags |= PW_STREAM_FLAG_DONT_RECONNECT; - if (corked) - flags |= PW_STREAM_FLAG_INACTIVE; if (sink_name != NULL) { if (o != NULL) @@ -1908,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_new(NULL, NULL); + props = pw_properties_copy(client->props); if (props == NULL) goto error_errno; @@ -2060,7 +2059,6 @@ static int do_create_record_stream(struct client *client, uint32_t command, uint goto error_errno; stream->corked = corked; - stream->is_paused = corked; stream->adjust_latency = adjust_latency; stream->early_requests = early_requests; stream->volume = volume; @@ -2088,8 +2086,6 @@ static int do_create_record_stream(struct client *client, uint32_t command, uint flags = 0; if (no_move) flags |= PW_STREAM_FLAG_DONT_RECONNECT; - if (corked) - flags |= PW_STREAM_FLAG_INACTIVE; if (direct_on_input_idx != SPA_ID_INVALID) { dont_inhibit_auto_suspend = false; @@ -2302,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_new(NULL, NULL)) == NULL) + if ((props = pw_properties_copy(client->props)) == NULL) goto error_errno; if ((res = message_get(m, @@ -4072,45 +4068,6 @@ 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) { @@ -4171,16 +4128,10 @@ 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) { - 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 >= 13) + message_put(m, + TAG_PROPLIST, info->props, + TAG_INVALID); if (client->version >= 19) message_put(m, TAG_BOOLEAN, corked, /* corked */ @@ -4256,16 +4207,10 @@ 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) { - 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 >= 13) + message_put(m, + TAG_PROPLIST, info->props, + TAG_INVALID); if (client->version >= 19) message_put(m, TAG_BOOLEAN, corked, /* corked */ @@ -4754,6 +4699,7 @@ 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; @@ -4770,10 +4716,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-raop-discover.c b/src/modules/module-raop-discover.c index 3675b003e..436972ac0 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/module-raop-discover.c @@ -19,8 +19,12 @@ #include #include -#include "zeroconf-utils/zeroconf.h" +#include +#include +#include + #include "module-protocol-pulse/format.h" +#include "module-zeroconf-discover/avahi-poll.h" /** \page page_module_raop_discover RAOP Discover * @@ -125,20 +129,29 @@ struct impl { struct pw_properties *properties; - struct pw_zeroconf *zeroconf; - struct spa_hook zeroconf_listener; + AvahiPoll *avahi_poll; + AvahiClient *client; + AvahiServiceBrowser *sink_browser; 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; - char *name; + struct tunnel_info info; struct pw_impl_module *module; struct spa_hook module_listener; }; -static struct tunnel *tunnel_new(struct impl *impl, const char *name) +static int start_client(struct impl *impl); + +static struct tunnel *make_tunnel(struct impl *impl, const struct tunnel_info *info) { struct tunnel *t; @@ -146,28 +159,28 @@ static struct tunnel *tunnel_new(struct impl *impl, const char *name) if (t == NULL) return NULL; - t->name = strdup(name); + t->info.name = strdup(info->name); spa_list_append(&impl->tunnel_list, &t->link); return t; } -static struct tunnel *find_tunnel(struct impl *impl, const char *name) +static struct tunnel *find_tunnel(struct impl *impl, const struct tunnel_info *info) { struct tunnel *t; spa_list_for_each(t, &impl->tunnel_list, link) { - if (spa_streq(t->name, name)) + if (spa_streq(t->info.name, info->name)) return t; } return NULL; } -static void tunnel_free(struct tunnel *t) +static void free_tunnel(struct tunnel *t) { spa_list_remove(&t->link); if (t->module) pw_impl_module_destroy(t->module); - free(t->name); + free((char *) t->info.name); free(t); } @@ -176,9 +189,14 @@ static void impl_free(struct impl *impl) struct tunnel *t; spa_list_consume(t, &impl->tunnel_list, link) - tunnel_free(t); - if (impl->zeroconf) - pw_zeroconf_destroy(impl->zeroconf); + free_tunnel(t); + + if (impl->sink_browser) + avahi_service_browser_free(impl->sink_browser); + if (impl->client) + avahi_client_free(impl->client); + if (impl->avahi_poll) + pw_avahi_poll_free(impl->avahi_poll); pw_properties_free(impl->properties); free(impl); } @@ -206,6 +224,75 @@ 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; @@ -277,124 +364,76 @@ static int rule_matched(void *data, const char *location, const char *action, return res; } -static void pw_properties_from_zeroconf(const char *key, const char *value, - struct pw_properties *props) +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) { - 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); - } -} - -static void on_zeroconf_added(void *data, const void *user, const struct spa_dict *info) -{ - struct impl *impl = data; - const char *name, *str; + struct impl *impl = userdata; + struct tunnel_info tinfo; struct tunnel *t; - const struct spa_dict_item *it; + const char *str, *link_local_range = "169.254."; + AvahiStringList *l; struct pw_properties *props = NULL; + char at[AVAHI_ADDRESS_STR_MAX], if_suffix[16] = ""; - name = spa_dict_lookup(info, PW_KEY_ZEROCONF_NAME); - - t = find_tunnel(impl, name); - if (t == NULL) { - 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, - spa_dict_lookup(info, PW_KEY_ZEROCONF_ADDRESS)); + if (event != AVAHI_RESOLVER_FOUND) { + pw_log_error("Resolving of '%s' failed: %s", name, + avahi_strerror(avahi_client_errno(impl->client))); goto done; } - if ((props = pw_properties_new(NULL, NULL)) == NULL) { + 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); + + tinfo = TUNNEL_INFO(.name = name); + + t = find_tunnel(impl, &tinfo); + if (t == NULL) + t = make_tunnel(impl, &tinfo); + if (t == 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); + goto done; + } + + props = pw_properties_new(NULL, NULL); + if (props == NULL) { pw_log_error("Can't allocate properties: %m"); goto done; } - spa_dict_for_each(it, info) - pw_properties_from_zeroconf(it->key, it->value, props); + 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); + } if ((str = pw_properties_get(impl->properties, "raop.latency.ms")) != NULL) pw_properties_set(props, "raop.latency.ms", str); @@ -413,28 +452,123 @@ static void on_zeroconf_added(void *data, const void *user, const struct spa_dic if (!minfo.matched) pw_log_info("unmatched service found %s", str); } + done: + avahi_service_resolver_free(r); pw_properties_free(props); } -static void on_zeroconf_removed(void *data, const void *user, const struct spa_dict *info) + +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 = data; - const char *name; + struct impl *impl = userdata; + struct tunnel_info info; struct tunnel *t; - name = spa_dict_lookup(info, PW_KEY_ZEROCONF_NAME); - - if ((t = find_tunnel(impl, name)) == NULL) + if ((flags & AVAHI_LOOKUP_RESULT_LOCAL) && !impl->discover_local) return; - tunnel_free(t); + 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); } -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) @@ -442,7 +576,6 @@ 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); @@ -466,24 +599,15 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->context = context; impl->properties = props; - if ((local = pw_properties_get(impl->properties, "raop.discover-local")) == NULL) - local = "false"; - pw_properties_set(impl->properties, PW_KEY_ZEROCONF_DISCOVER_LOCAL, local); + impl->discover_local = pw_properties_get_bool(impl->properties, + "raop.discover-local", false); pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); - 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); + start_avahi(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 5e25b3089..e217ff5b2 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -45,7 +45,6 @@ #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" @@ -131,6 +130,11 @@ 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 @@ -269,6 +273,13 @@ 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) @@ -346,7 +357,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 (%m)", res); + pw_log_warn("error sending control packet: %d", res); } pw_log_debug("raop control sync: first:%d latency:%u now:%"PRIx64" rtptime:%u", @@ -692,6 +703,49 @@ 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, ...) { @@ -724,7 +778,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); - pw_base64_encode((uint8_t*)buf, strlen(buf), enc, '='); + base64_encode((uint8_t*)buf, strlen(buf), enc, '='); spa_scnprintf(auth, sizeof(auth), "Basic %s", enc); } else if (spa_streq(impl->auth_method, "Digest")) { @@ -1082,8 +1136,8 @@ static int rsa_encrypt(uint8_t *data, int len, uint8_t *enc) "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; char e[] = "AQAB"; - msize = pw_base64_decode(n, strlen(n), modulus); - esize = pw_base64_decode(e, strlen(e), exponent); + msize = base64_decode(n, strlen(n), modulus); + esize = base64_decode(e, strlen(e), exponent); #if OPENSSL_API_LEVEL >= 30000 EVP_PKEY *pkey = NULL; @@ -1209,15 +1263,15 @@ static int rtsp_do_announce(struct impl *impl) (res = pw_getrandom(impl->aes_iv, sizeof(impl->aes_iv), 0)) < 0) return res; - pw_base64_encode(rac, sizeof(rac), sac, '\0'); + 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; - pw_base64_encode(rsakey, rsa_len, key, '='); - pw_base64_encode(impl->aes_iv, 16, iv, '='); + base64_encode(rsakey, rsa_len, key, '='); + 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 deleted file mode 100644 index d8906c287..000000000 --- a/src/modules/module-raop/base64.h +++ /dev/null @@ -1,62 +0,0 @@ -/* 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 4bcff8b88..fae71977c 100644 --- a/src/modules/module-raop/rtsp-client.c +++ b/src/modules/module-raop/rtsp-client.c @@ -445,10 +445,7 @@ on_source_io(void *data, int fd, uint32_t mask) int res; if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { - socklen_t len = sizeof(res); - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &res, &len) < 0) - res = errno; - res = -res; + res = -EPIPE; goto error; } if (mask & SPA_IO_IN) { @@ -481,7 +478,7 @@ int pw_rtsp_client_connect(struct pw_rtsp_client *client, { struct addrinfo hints; struct addrinfo *result, *rp; - int res, fd = -1; + int res, fd; 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 66a2c716b..39ca2bce1 100644 --- a/src/modules/module-roc-sink.c +++ b/src/modules/module-roc-sink.c @@ -120,6 +120,8 @@ struct module_roc_sink_data { roc_endpoint *remote_control_addr; int remote_control_port; + + roc_log_level loglevel; }; static void stream_destroy(void *d) @@ -389,7 +391,8 @@ static const struct spa_dict_item module_roc_sink_info[] = { "( remote.repair.port= ) " "( remote.control.port= ) " "( audio.position= ) " - "( sink.props= { key=val ... } ) " }, + "( sink.props= { key=val ... } ) " + "( log.level=|DEFAULT|NONE|RROR|INFO|DEBUG|TRACE ) " }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; @@ -510,6 +513,14 @@ 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 a46189e5d..2173c6af1 100644 --- a/src/modules/module-roc-source.c +++ b/src/modules/module-roc-source.c @@ -140,6 +140,8 @@ struct module_roc_source_data { roc_endpoint *local_control_addr; int local_control_port; + + roc_log_level loglevel; }; static void stream_destroy(void *d) @@ -428,7 +430,8 @@ static const struct spa_dict_item module_roc_source_info[] = { "( local.repair.port= ) " "( local.control.port= ) " "( audio.position= ) " - "( source.props= { key=value ... } ) " }, + "( source.props= { key=value ... } ) " + "( log.level=|DEFAULT|NONE|RROR|INFO|DEBUG|TRACE ) " }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; @@ -564,6 +567,14 @@ 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 475c5f40f..244c203dd 100644 --- a/src/modules/module-roc/common.c +++ b/src/modules/module-roc/common.c @@ -5,54 +5,17 @@ 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 d49e392fe..c94ac69a8 100644 --- a/src/modules/module-roc/common.h +++ b/src/modules/module-roc/common.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -20,6 +21,7 @@ #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) { @@ -135,4 +137,62 @@ 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 cfbcbf419..c734758c3 100644 --- a/src/modules/module-rtp-sap.c +++ b/src/modules/module-rtp-sap.c @@ -1235,11 +1235,8 @@ 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: @@ -1838,7 +1835,6 @@ 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. */ @@ -2009,10 +2005,8 @@ 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) { - pw_log_info("destroying SAP source"); + if (impl->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 2dca681d6..63470c539 100644 --- a/src/modules/module-rtp-session.c +++ b/src/modules/module-rtp-session.c @@ -27,7 +27,12 @@ #include #include -#include "zeroconf-utils/zeroconf.h" +#include +#include +#include +#include + +#include "module-zeroconf-discover/avahi-poll.h" #include #include @@ -155,21 +160,31 @@ static const struct spa_dict_item module_info[] = { }; struct service_info { - int ifindex; - int protocol; + AvahiIfIndex interface; + AvahiProtocol 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; @@ -214,8 +229,11 @@ struct impl { struct pw_properties *props; bool discover_local; - struct pw_zeroconf *zeroconf; - struct spa_hook zeroconf_listener; + AvahiPoll *avahi_poll; + AvahiClient *client; + AvahiServiceBrowser *browser; + AvahiEntryGroup *group; + struct spa_list service_list; struct pw_properties *stream_props; @@ -577,9 +595,6 @@ 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); } @@ -597,8 +612,7 @@ static bool cmp_ip(const struct sockaddr_storage *sa, const struct sockaddr_stor return false; } -static struct session *make_session(struct impl *impl, struct service_info *info, - struct pw_properties *props) +static struct session *make_session(struct impl *impl, struct pw_properties *props) { struct session *sess; const char *str; @@ -613,11 +627,6 @@ static struct session *make_session(struct impl *impl, struct service_info *info 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"); @@ -660,21 +669,6 @@ 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) { @@ -1226,8 +1220,8 @@ static void impl_destroy(struct impl *impl) if (impl->data_source) pw_loop_destroy_source(impl->data_loop, impl->data_source); - if (impl->zeroconf) - pw_zeroconf_destroy(impl->zeroconf); + if (impl->client) + avahi_client_free(impl->client); if (impl->data_loop) pw_context_release_loop(impl->context, impl->data_loop); @@ -1269,7 +1263,21 @@ static const struct pw_core_events core_events = { .error = on_core_error, }; -static const char *get_service_type(struct impl *impl) +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) { const char *str; str = pw_properties_get(impl->props, "sess.media"); @@ -1280,36 +1288,21 @@ static const char *get_service_type(struct impl *impl) return NULL; } -static void on_zeroconf_added(void *data, const void *user, const struct spa_dict *info) +static struct service *make_service(struct impl *impl, const struct service_info *info, + AvahiStringList *txt) { - struct impl *impl = data; - const char *str, *service_type, *address, *hostname; - struct service_info sinfo; + struct service *s = NULL; + char at[AVAHI_ADDRESS_STR_MAX], if_suffix[16] = ""; struct session *sess; - int ifindex = -1, protocol = 0, res, port = 0; + int res, ipv; 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_type = get_service_type(impl); - compatible = spa_streq(service_type, sinfo.type); + service_name = get_service_name(impl); + compatible = spa_streq(service_name, info->type); props = pw_properties_copy(impl->stream_props); if (props == NULL) { @@ -1317,53 +1310,55 @@ static void on_zeroconf_added(void *data, const void *user, const struct spa_dic goto error; } - if (spa_streq(service_type, "_pipewire-audio._udp")) { + if (spa_streq(service_name, "_pipewire-audio._udp")) { uint32_t mask = 0; - const struct spa_dict_item *it; - spa_dict_for_each(it, info) { + for (l = txt; l && compatible; l = l->next) { const char *k = NULL; + char *key, *value; - if (!compatible) + if (avahi_string_list_get_pair(l, &key, &value, NULL) != 0) break; - if (spa_streq(it->key, "subtype")) { + if (spa_streq(key, "subtype")) { k = "sess.media"; mask |= 1<<0; - } else if (spa_streq(it->key, "format")) { + } else if (spa_streq(key, "format")) { k = PW_KEY_AUDIO_FORMAT; mask |= 1<<1; - } else if (spa_streq(it->key, "rate")) { + } else if (spa_streq(key, "rate")) { k = PW_KEY_AUDIO_RATE; mask |= 1<<2; - } else if (spa_streq(it->key, "channels")) { + } else if (spa_streq(key, "channels")) { k = PW_KEY_AUDIO_CHANNELS; mask |= 1<<3; - } else if (spa_streq(it->key, "position")) { + } else if (spa_streq(key, "position")) { pw_properties_set(props, - SPA_KEY_AUDIO_POSITION, it->value); - } else if (spa_streq(it->key, "layout")) { + SPA_KEY_AUDIO_POSITION, value); + } else if (spa_streq(key, "layout")) { pw_properties_set(props, - SPA_KEY_AUDIO_LAYOUT, it->value); - } else if (spa_streq(it->key, "channelnames")) { + SPA_KEY_AUDIO_LAYOUT, value); + } else if (spa_streq(key, "channelnames")) { pw_properties_set(props, - PW_KEY_NODE_CHANNELNAMES, it->value); - } else if (spa_streq(it->key, "ts-refclk")) { + PW_KEY_NODE_CHANNELNAMES, value); + } else if (spa_streq(key, "ts-refclk")) { pw_properties_set(props, - "sess.ts-refclk", it->value); - if (spa_streq(it->value, impl->ts_refclk)) + "sess.ts-refclk", value); + if (spa_streq(value, impl->ts_refclk)) pw_properties_set(props, "sess.ts-direct", "true"); - } else if (spa_streq(it->key, "ts-offset")) { + } else if (spa_streq(key, "ts-offset")) { uint32_t v; - if (spa_atou32(it->value, &v, 0)) + if (spa_atou32(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, it->value)) + if (str == NULL || !spa_streq(str, value)) compatible = false; } + avahi_free(key); + avahi_free(value); } str = pw_properties_get(props, "sess.media"); if (spa_streq(str, "opus") && mask != 0xd) @@ -1373,147 +1368,281 @@ static void on_zeroconf_added(void *data, const void *user, const struct spa_dic } if (!compatible) { pw_log_info("found incompatible session IP%d:%s", - sinfo.protocol, sinfo.name); + info->protocol == AVAHI_PROTO_INET ? 4 : 6, + info->name); res = 0; goto error; } - address = spa_dict_lookup(info, PW_KEY_ZEROCONF_ADDRESS); - hostname = spa_dict_lookup(info, PW_KEY_ZEROCONF_HOSTNAME); + s = calloc(1, sizeof(*s)); + if (s == NULL) { + res = -errno; + goto error; + } - pw_log_info("create session: %s %s:%u %s", sinfo.name, address, port, sinfo.type); + s->impl = impl; + spa_list_append(&impl->service_list, &s->link); - 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); + 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); if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) pw_properties_setf(props, PW_KEY_NODE_NAME, "rtp_session.%s.%s.ipv%d", - sinfo.name, hostname, sinfo.protocol); + s->info.name, s->info.host_name, ipv); if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "%s (IPv%d)", - sinfo.name, sinfo.protocol); + s->info.name, ipv); if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) pw_properties_setf(props, PW_KEY_MEDIA_NAME, "RTP Session with %s (IPv%d)", - sinfo.name, sinfo.protocol); + s->info.name, ipv); - sess = make_session(impl, &sinfo, spa_steal_ptr(props)); + sess = make_session(impl, props); + props = NULL; if (sess == NULL) { res = -errno; pw_log_error("can't create session: %m"); goto error; } + s->sess = sess; - 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, &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+1, &sess->data_addr, &sess->data_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)); } - return; + return s; error: pw_properties_free(props); - return; + if (s != NULL) + free_service(s); + errno = -res; + return NULL; } -static void on_zeroconf_removed(void *data, const void *user, const struct spa_dict *info) +static struct service *find_service(struct impl *impl, const struct service_info *info) { - struct impl *impl = data; + 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 service_info sinfo; - struct session *sess; - const char *str; - int ifindex = -1, protocol = 0; - 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 (event != AVAHI_RESOLVER_FOUND) { + pw_log_error("Resolving of '%s' failed: %s", name, + avahi_strerror(avahi_client_errno(impl->client))); + goto done; + } - sinfo = SERVICE_INFO(.ifindex = ifindex, + sinfo = SERVICE_INFO(.interface = interface, .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)); + .name = name, + .type = type, + .domain = domain, + .host_name = host_name, + .address = *a, + .port = port); - sess = find_session_by_info(impl, &sinfo); - if (sess == NULL) + 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) return; - free_session(sess); + 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; + } } static int make_browser(struct impl *impl) { - const char *service_type; - int res; + const char *service_name; - service_type = get_service_type(impl); - if (service_type == NULL) + service_name = get_service_name(impl); + if (service_name == NULL) return -EINVAL; - 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; + 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; } 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_type, *str; - struct pw_properties *props; + const char *service_name, *str; + AvahiStringList *txt = NULL; - props = pw_properties_new(NULL, NULL); - - if ((service_type = get_service_type(impl)) == NULL) + if ((service_name = get_service_name(impl)) == NULL) return -ENOTSUP; - if (spa_streq(service_type, "_pipewire-audio._udp")) { + 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")) { str = pw_properties_get(impl->props, "sess.media"); - pw_properties_set(props, "subtype", str); + txt = avahi_string_list_add_pair(txt, "subtype", str); if ((str = pw_properties_get(impl->stream_props, PW_KEY_AUDIO_FORMAT)) != NULL) - pw_properties_set(props, "format", str); + txt = avahi_string_list_add_pair(txt, "format", str); if ((str = pw_properties_get(impl->stream_props, PW_KEY_AUDIO_RATE)) != NULL) - pw_properties_set(props, "rate", str); + txt = avahi_string_list_add_pair(txt, "rate", str); if ((str = pw_properties_get(impl->stream_props, PW_KEY_AUDIO_CHANNELS)) != NULL) - pw_properties_set(props, "channels", str); + txt = avahi_string_list_add_pair(txt, "channels", str); if ((str = pw_properties_get(impl->stream_props, SPA_KEY_AUDIO_POSITION)) != NULL) - pw_properties_set(props, "position", str); + txt = avahi_string_list_add_pair(txt, "position", str); if ((str = pw_properties_get(impl->stream_props, SPA_KEY_AUDIO_LAYOUT)) != NULL) - pw_properties_set(props, "layout", str); + txt = avahi_string_list_add_pair(txt, "layout", str); if ((str = pw_properties_get(impl->stream_props, PW_KEY_NODE_CHANNELNAMES)) != NULL) - pw_properties_set(props, "channelnames", str); + txt = avahi_string_list_add_pair(txt, "channelnames", str); if (impl->ts_refclk != NULL) { - pw_properties_set(props, "ts-refclk", impl->ts_refclk); - pw_properties_setf(props, "ts-offset", "%u", impl->ts_offset); + 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); } } + 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); - 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); + avahi_string_list_free(txt); if (res < 0) { - pw_log_error("can't add service: %s", spa_strerror(res)); - return res; + 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; } return 0; } +static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) +{ + struct impl *impl = userdata; + impl->client = c; -static const struct pw_zeroconf_events zeroconf_events = { - PW_VERSION_ZEROCONF_EVENTS, - .added = on_zeroconf_added, - .removed = on_zeroconf_removed, -}; + 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 void copy_props(struct impl *impl, struct pw_properties *props, const char *key) { @@ -1544,6 +1673,7 @@ 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) { @@ -1555,8 +1685,6 @@ 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) { @@ -1676,17 +1804,14 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) if ((res = setup_apple_session(impl)) < 0) goto out; - impl->zeroconf = pw_zeroconf_new(impl->context, &impl->props->dict); - if (impl->zeroconf == NULL) { - res = -errno; - pw_log_error("can't create zeroconf: %m"); + 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)); 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 b0b52ed50..8f911c62a 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. */ - SPA_ALIGNED(8) uint64_t last_packet_time; + uint64_t last_packet_time; struct pw_timer standby_timer; /* This timer is used when the first stream_start() call fails because @@ -246,9 +246,6 @@ struct impl { socklen_t src_len; struct spa_source *source; - bool is_multicast; - bool filter_by_address; - uint8_t *buffer; size_t buffer_size; @@ -303,41 +300,14 @@ 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 = recvfrom(fd, impl->buffer, impl->buffer_size, 0, (struct sockaddr *)(&recvaddr), &recvaddr_len)) < 0) + + if ((len = recv(fd, impl->buffer, impl->buffer_size, 0)) < 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; @@ -504,12 +474,12 @@ finish: } static int make_socket(const struct sockaddr* sa, socklen_t salen, char *ifname, - struct igmp_recovery *igmp_recovery, bool *is_multicast, - bool *filter_by_address) + struct igmp_recovery *igmp_recovery) { 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; @@ -552,16 +522,12 @@ 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; - *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; + if (ba4->sin_addr.s_addr != INADDR_ANY) { + ba4->sin_addr.s_addr = INADDR_ANY; + do_connect = true; + } } } else if (af == AF_INET6) { struct sockaddr_in6 *sa6 = (struct sockaddr_in6*)sa; @@ -573,15 +539,8 @@ 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 { @@ -610,6 +569,13 @@ 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); @@ -647,9 +613,7 @@ 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), - &(impl->is_multicast), - &(impl->filter_by_address))) < 0) { + &(impl->igmp_recovery))) < 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 @@ -699,13 +663,11 @@ static void stream_open_connection(void *data, int *result) 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; - } + 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 085f3bae8..d20e9a37c 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 + impl->ts_align + (set_timestamp ? set_timestamp : timestamp); + rtp_timestamp = impl->ts_offset + (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_fp("sending %d packet:%d ts_offset:%d timestamp:%u (%f s)", + pw_log_trace("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_call_send_packet(impl, iov, 3); + rtp_stream_emit_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_trace("missing timeout %"PRIu64, expirations); + pw_log_warn("missing timeout %"PRIu64, expirations); rtp_audio_flush_packets(impl, expirations, 0); } @@ -705,10 +705,8 @@ static void rtp_audio_process_capture(void *data) * that resynchronization is needed, then this will be done immediately below. */ if (!impl->have_sync) { - 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); + pw_log_info("(re)sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u", + actual_timestamp, impl->seq, impl->ts_offset, 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 1237f66c6..5fbdf3b63 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_call_send_feedback(impl, seqnum); + rtp_stream_emit_send_feedback(impl, seqnum); return 0; } @@ -271,6 +271,9 @@ 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; @@ -291,9 +294,17 @@ static int rtp_midi_receive_midi(struct impl *impl, uint8_t *packet, uint32_t ti return -EINVAL; } - spa_pod_builder_control(&b, timestamp, SPA_CONTROL_Midi); - spa_pod_builder_bytes(&b, &packet[offs], size); + d = &packet[offs]; + s = size; + while (s > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&d, &s, ump, sizeof(ump), 0, &state); + if (ump_size <= 0) + break; + spa_pod_builder_control(&b, timestamp, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ump, ump_size); + } offs += size; first = false; } @@ -367,7 +378,7 @@ unexpected_ssrc: return -EINVAL; } -static int write_event(uint8_t *p, uint32_t buffer_size, uint32_t value, const void *ev, uint32_t size) +static int write_event(uint8_t *p, uint32_t buffer_size, uint32_t value, void *ev, uint32_t size) { uint64_t buffer; uint8_t b; @@ -426,54 +437,62 @@ 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; - uint32_t size = c.value.size; - const uint8_t *data = c_body; + uint8_t event[16]; + int size; + size_t c_size = c.value.size; + uint64_t state = 0; - if (c.type != SPA_CONTROL_Midi) + if (c.type != SPA_CONTROL_UMP) continue; - offset = c.offset * impl->rate / rate; + while (c_size > 0) { + size = spa_ump_to_midi((const uint32_t **)&c_body, &c_size, event, sizeof(event), &state); + if (size <= 0) + break; - 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); + 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; } - iov[2].iov_len = len; + 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); - 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); + memcpy(&impl->buffer[len], event, size); + len += size; + } else { + delta = offset - prev_offset; + prev_offset = offset; + len += write_event(&impl->buffer[len], BUFFER_SIZE - len, delta, event, size); + } } } if (len > 0) { @@ -491,7 +510,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_call_send_packet(impl, iov, 3); + rtp_stream_emit_send_packet(impl, iov, 3); impl->seq++; } } diff --git a/src/modules/module-rtp/opus.c b/src/modules/module-rtp/opus.c index 9175d4a31..d13a4efaf 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_call_send_packet(impl, iov, 2); + rtp_stream_emit_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 11bba4f98..d69b16524 100644 --- a/src/modules/module-rtp/stream.c +++ b/src/modules/module-rtp/stream.c @@ -48,11 +48,8 @@ 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_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) +#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) enum rtp_stream_internal_state { /* The state when the stream is idle / stopped. The background @@ -88,8 +85,6 @@ 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; @@ -114,7 +109,6 @@ 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]; @@ -432,7 +426,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 = 0; + int res; pw_log_info("closing connection as part of stopping the stream"); rtp_stream_emit_close_connection(impl, &res); if (res > 0) { @@ -1008,7 +1002,6 @@ 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 deleted file mode 100644 index 5756f7a56..000000000 --- a/src/modules/module-scheduler-v1.c +++ /dev/null @@ -1,1040 +0,0 @@ -/* 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, bool idle) -{ - enum pw_node_state state = node->info.state; - bool need_config = SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_NEED_CONFIGURE); - if (node->active && node->runnable && !need_config & running) - state = PW_NODE_STATE_RUNNING; - else if (!node->active && !need_config & idle) - state = PW_NODE_STATE_IDLE; - 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, 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, true); - } - - 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, false); - } -} - -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 deleted file mode 100644 index fe6525767..000000000 --- a/src/modules/module-sendspin-recv.c +++ /dev/null @@ -1,1401 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include - -#include -#include - -#include "module-sendspin/sendspin.h" -#include "module-sendspin/websocket.h" -#include "module-sendspin/regress.h" -#include "network-utils.h" - -#ifdef HAVE_AVAHI -#include "zeroconf-utils/zeroconf.h" -#endif - -/** \page page_module_sendspin_recv sendspin receiver - * - * The `sendspin-recv` module creates a PipeWire source that receives audio - * packets using the sendspin protocol. - * - * The receive will listen on a specific port (8928) and create a stream for the - * data on the port. - * - * ## Module Name - * - * `libpipewire-module-sendspin-recv` - * - * ## Module Options - * - * Options specific to the behavior of this module - * - * - `local.ifname = `: interface name to use - * - `source.ip = `: the source ip address to listen on, default 127.0.0.1 - * - `source.port = `: the source port to listen on, default 8928 - * - `source.path = `: the path to listen on, default "/sendspin" - * - `sendspin.ip`: the IP address of the sendspin server - * - `sendspin.port`: the port of the sendspin server, default 8927 - * - `sendspin.path`: the path on the sendspin server, default "/sendspin" - * - `sendspin.client-id`: the client id, default "pipewire-$(hostname)" - * - `sendspin.client-name`: the client name, default "$(hostname)" - * - `sendspin.autoconnect`: Use zeroconf to connect to an available server, default false. - * - `sendspin.announce`: Use zeroconf to announce the client, default true unless - * sendspin.autoconnect or sendspin.ip is given. - * - `sendspin.single-server`: Allow only a single server to connect, default true - * - `node.always-process = `: true to receive even when not running - * - `stream.props = {}`: properties to be passed to all the stream - * - * ## General options - * - * Options with well-known behavior: - * - * - \ref PW_KEY_REMOTE_NAME - * - \ref SPA_KEY_AUDIO_LAYOUT - * - \ref SPA_KEY_AUDIO_POSITION - * - \ref PW_KEY_MEDIA_NAME - * - \ref PW_KEY_MEDIA_CLASS - * - \ref PW_KEY_NODE_NAME - * - \ref PW_KEY_NODE_DESCRIPTION - * - \ref PW_KEY_NODE_GROUP - * - \ref PW_KEY_NODE_LATENCY - * - \ref PW_KEY_NODE_VIRTUAL - * - * ## Example configuration - *\code{.unparsed} - * # ~/.config/pipewire/pipewire.conf.d/my-sendspin-recv.conf - * - * context.modules = [ - * { name = libpipewire-module-sendspin-recv - * args = { - * #local.ifname = eth0 - * #source.ip = 127.0.0.1 - * #source.port = 8928 - * #source.path = "/sendspin" - * #sendspin.ip = 127.0.0.1 - * #sendspin.port = 8927 - * #sendspin.path = "/sendspin" - * #sendspin.client-id = "pipewire-test" - * #sendspin.client-name = "PipeWire Test" - * #sendspin.autoconnect = false - * #sendspin.announce = true - * #sendspin.single-server = true - * #node.always-process = false - * #audio.position = [ FL FR ] - * stream.props = { - * #media.class = "Audio/Source" - * #node.name = "sendspin-receiver" - * } - * } - * } - * ] - *\endcode - * - * \since 1.6.0 - */ - -#define NAME "sendspin-recv" - -PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -#define DEFAULT_SOURCE_IP "127.0.0.1" -#define DEFAULT_SOURCE_PORT PW_SENDSPIN_DEFAULT_CLIENT_PORT -#define DEFAULT_SOURCE_PATH PW_SENDSPIN_DEFAULT_PATH - -#define DEFAULT_SERVER_PORT PW_SENDSPIN_DEFAULT_SERVER_PORT -#define DEFAULT_SENDSPIN_PATH PW_SENDSPIN_DEFAULT_PATH - -#define DEFAULT_CREATE_RULES \ - "[ { matches = [ { sendspin.ip = \"~.*\" } ] actions = { create-stream = { } } } ] " - -#define DEFAULT_POSITION "[ FL FR ]" - -#define USAGE "( local.ifname= ) " \ - "( source.ip= ) " \ - "( source.port= " \ - "( audio.position= ) " \ - "( stream.props= { key=value ... } ) " - -static const struct spa_dict_item module_info[] = { - { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, - { PW_KEY_MODULE_DESCRIPTION, "sendspin Receiver" }, - { PW_KEY_MODULE_USAGE, USAGE }, - { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, -}; - -struct client { - struct impl *impl; - struct spa_list link; - - char *name; - struct pw_properties *props; - struct pw_websocket_connection *conn; - struct spa_hook conn_listener; - - struct spa_audio_info info; - struct pw_stream *stream; - struct spa_hook stream_listener; - - struct pw_timer timer; - int timeout_count; - - uint32_t stride; - struct spa_ringbuffer ring; - void *buffer; - uint32_t buffer_size; - -#define ROLE_PLAYER (1<<0) -#define ROLE_METADATA (1<<1) - uint32_t active_roles; -#define REASON_DISCOVERY (0) -#define REASON_PLAYBACK (1) - uint32_t connection_reason; - - struct spa_regress regress_index; - struct spa_regress regress_time; - - bool resync; - struct spa_dll dll; -}; - -struct impl { - struct pw_impl_module *module; - struct spa_hook module_listener; - struct pw_properties *props; - struct pw_context *context; - - struct pw_loop *main_loop; - struct pw_loop *data_loop; - struct pw_timer_queue *timer_queue; - - struct pw_core *core; - struct spa_hook core_listener; - struct spa_hook core_proxy_listener; - unsigned int do_disconnect:1; - -#ifdef HAVE_AVAHI - struct pw_zeroconf *zeroconf; - struct spa_hook zeroconf_listener; -#endif - - bool always_process; - bool single_server; - - struct pw_properties *stream_props; - - struct pw_websocket *websocket; - struct spa_hook websocket_listener; - - struct spa_list clients; -}; - -static void on_stream_destroy(void *d) -{ - struct client *client = d; - spa_hook_remove(&client->stream_listener); - client->stream = NULL; -} - -static void on_stream_state_changed(void *d, enum pw_stream_state old, - enum pw_stream_state state, const char *error) -{ - struct client *client = d; - switch (state) { - case PW_STREAM_STATE_ERROR: - case PW_STREAM_STATE_UNCONNECTED: - pw_impl_module_schedule_destroy(client->impl->module); - break; - case PW_STREAM_STATE_PAUSED: - case PW_STREAM_STATE_STREAMING: - break; - default: - break; - } -} - -static void on_capture_stream_process(void *d) -{ - struct client *client = d; - struct pw_buffer *b; - struct spa_buffer *buf; - uint8_t *p; - uint32_t index = 0, n_frames, n_bytes; - int32_t avail, stride; - struct pw_time ts; - double err, corr, target, current_time; - - if ((b = pw_stream_dequeue_buffer(client->stream)) == NULL) { - pw_log_debug("out of buffers: %m"); - return; - } - - buf = b->buffer; - if ((p = buf->datas[0].data) == NULL) - return; - - stride = client->stride; - n_frames = buf->datas[0].maxsize / stride; - if (b->requested) - n_frames = SPA_MIN(b->requested, n_frames); - n_bytes = n_frames * stride; - - avail = spa_ringbuffer_get_read_index(&client->ring, &index); - - if (client->timeout_count > 4) { - pw_stream_get_time_n(client->stream, &ts, sizeof(ts)); - - /* index to server time */ - target = spa_regress_calc_y(&client->regress_index, index); - /* server time to client time */ - target = spa_regress_calc_y(&client->regress_time, target); - - current_time = ts.now / 1000.0; - current_time -= (ts.buffered * 1000000.0 / client->info.info.raw.rate) + - ((ts.delay) * 1000000.0 * ts.rate.num / ts.rate.denom); - err = target - (double)current_time; - - if (client->resync) { - if (target < current_time) { - target = spa_regress_calc_x(&client->regress_time, current_time); - index = (uint32_t)spa_regress_calc_x(&client->regress_index, target); - index = SPA_ROUND_DOWN(index, stride); - - pw_log_info("resync %u %f %f %f", index, target, - current_time, target - current_time); - - spa_ringbuffer_read_update(&client->ring, index); - avail = spa_ringbuffer_get_read_index(&client->ring, &index); - - err = 0.0; - client->resync = false; - } else { - avail = 0; - } - } - - corr = spa_dll_update(&client->dll, SPA_CLAMPD(err, -1000, 1000)); - - pw_stream_set_rate(client->stream, 1.0 / corr); - - pw_log_trace("%u %f %f %f %f", index, current_time, target, err, corr); - } else { - avail = 0; - } - if (avail < (int32_t)n_bytes) { - avail = 0; - client->resync = true; - } - else if (avail > (int32_t)client->buffer_size) { - index += avail - client->buffer_size; - avail = client->buffer_size; - client->resync = true; - } - if (avail > 0) { - n_bytes = SPA_MIN(n_bytes, (uint32_t)avail); - - spa_ringbuffer_read_data(&client->ring, - client->buffer, client->buffer_size, - index % client->buffer_size, - p, n_bytes); - spa_ringbuffer_read_update(&client->ring, index + n_bytes); - } else { - memset(p, 0, n_bytes); - } - - buf->datas[0].chunk->offset = 0; - buf->datas[0].chunk->stride = stride; - buf->datas[0].chunk->size = n_bytes; - - pw_stream_queue_buffer(client->stream, b); -} - -static const struct pw_stream_events capture_stream_events = { - PW_VERSION_STREAM_EVENTS, - .destroy = on_stream_destroy, - .state_changed = on_stream_state_changed, - .process = on_capture_stream_process -}; - -static int create_stream(struct client *client) -{ - struct impl *impl = client->impl; - int res; - uint32_t n_params; - const struct spa_pod *params[1]; - uint8_t buffer[1024]; - struct spa_pod_builder b; - const char *server_id, *ip, *port, *server_name; - struct pw_properties *props = pw_properties_copy(client->props); - - ip = pw_properties_get(props, "sendspin.ip"); - port = pw_properties_get(props, "sendspin.port"); - server_id = pw_properties_get(props, "sendspin.server-id"); - server_name = pw_properties_get(props, "sendspin.server-name"); - - if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) - pw_properties_setf(props, PW_KEY_NODE_NAME, "sendspin.%s.%s.%s", server_id, ip, port); - if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) - pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "Sendspin from %s", server_name); - if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) - pw_properties_setf(props, PW_KEY_MEDIA_NAME, "Sendspin from %s", server_name); - - client->stream = pw_stream_new(impl->core, "sendspin receiver", props); - if (client->stream == NULL) - return -errno; - - spa_ringbuffer_init(&client->ring); - client->buffer_size = 1024 * 1024; - client->buffer = calloc(1, client->buffer_size * client->stride); - - pw_stream_add_listener(client->stream, - &client->stream_listener, - &capture_stream_events, client); - - n_params = 0; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - params[n_params++] = spa_format_audio_build(&b, - SPA_PARAM_EnumFormat, &client->info); - - if ((res = pw_stream_connect(client->stream, - PW_DIRECTION_OUTPUT, - PW_ID_ANY, - PW_STREAM_FLAG_AUTOCONNECT | - PW_STREAM_FLAG_MAP_BUFFERS | - PW_STREAM_FLAG_RT_PROCESS, - params, n_params)) < 0) - return res; - - return 0; -} - -static void add_format(struct spa_json_builder *b, const char *codec, int channels, int rate, int depth) -{ - spa_json_builder_array_push(b, "{"); - spa_json_builder_object_string(b, "codec", codec); - spa_json_builder_object_int(b, "channels", channels); - spa_json_builder_object_int(b, "sample_rate", rate); - spa_json_builder_object_int(b, "bit_depth", depth); - spa_json_builder_pop(b, "}"); -} -static void add_playerv1_support(struct client *client, struct spa_json_builder *b) -{ - spa_json_builder_object_push(b, "player@v1_support", "{"); - spa_json_builder_object_push(b, "supported_formats", "["); - add_format(b, "pcm", 2, 48000, 16); - add_format(b, "pcm", 1, 48000, 16); - spa_json_builder_pop(b, "]"); - spa_json_builder_object_int(b, "buffer_capacity", 32000000); - spa_json_builder_object_push(b, "supported_commands", "["); - spa_json_builder_array_string(b, "volume"); - spa_json_builder_array_string(b, "mute"); - spa_json_builder_pop(b, "]"); - spa_json_builder_pop(b, "}"); -} -static int send_client_hello(struct client *client) -{ - struct impl *impl = client->impl; - struct spa_json_builder b; - int res; - char *mem; - size_t size; - - spa_json_builder_memstream(&b, &mem, &size, 0); - spa_json_builder_array_push(&b, "{"); - spa_json_builder_object_string(&b, "type", "client/hello"); - spa_json_builder_object_push(&b, "payload", "{"); - spa_json_builder_object_string(&b, "client_id", pw_properties_get(impl->props, "sendspin.client-id")); - spa_json_builder_object_string(&b, "name", pw_properties_get(impl->props, "sendspin.client-name")); - spa_json_builder_object_int(&b, "version", 1); - spa_json_builder_object_push(&b, "supported_roles", "["); - spa_json_builder_array_string(&b, "player@v1"); - spa_json_builder_array_string(&b, "metadata@v1"); - spa_json_builder_pop(&b, "]"); - spa_json_builder_object_push(&b, "device_info", "{"); - spa_json_builder_object_string(&b, "product_name", "Linux"); /* Use os-release */ - spa_json_builder_object_stringf(&b, "software_version", "PipeWire %s", pw_get_library_version()); - spa_json_builder_pop(&b, "}"); - add_playerv1_support(client, &b); - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_close(&b); - - res = pw_websocket_connection_send_text(client->conn, mem, size); - free(mem); - - return res; -} - -static int send_client_state(struct client *client) -{ - struct spa_json_builder b; - int res; - char *mem; - size_t size; - - spa_json_builder_memstream(&b, &mem, &size, 0); - spa_json_builder_array_push(&b, "{"); - spa_json_builder_object_string(&b, "type", "client/state"); - spa_json_builder_object_push(&b, "payload", "{"); - spa_json_builder_object_push(&b, "player", "{"); - spa_json_builder_object_string(&b, "state", "synchronized"); - spa_json_builder_object_int(&b, "volume", 100); - spa_json_builder_object_bool(&b, "muted", false); - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_close(&b); - - res = pw_websocket_connection_send_text(client->conn, mem, size); - free(mem); - return res; -} - -static uint64_t get_time_us(struct client *client) -{ - struct timespec now; - if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) - return 0; - return SPA_TIMESPEC_TO_USEC(&now); -} - -static int send_client_time(struct client *client) -{ - struct spa_json_builder b; - int res; - uint64_t now; - char *mem; - size_t size; - - now = get_time_us(client); - - spa_json_builder_memstream(&b, &mem, &size, 0); - spa_json_builder_array_push(&b, "{"); - spa_json_builder_object_string(&b, "type", "client/time"); - spa_json_builder_object_push(&b, "payload", "{"); - spa_json_builder_object_uint(&b, "client_transmitted", now); - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_close(&b); - - res = pw_websocket_connection_send_text(client->conn, mem, size); - free(mem); - return res; -} - -static void do_client_timer(void *data) -{ - struct client *client = data; - send_client_time(client); -} - -#if 0 -static int send_client_command(struct client *client) -{ - return 0; -} -#endif -static int send_client_goodbye(struct client *client, const char *reason) -{ - struct spa_json_builder b; - int res; - char *mem; - size_t size; - - spa_json_builder_memstream(&b, &mem, &size, 0); - spa_json_builder_array_push(&b, "{"); - spa_json_builder_object_string(&b, "type", "client/goodbye"); - spa_json_builder_object_push(&b, "payload", "{"); - spa_json_builder_object_string(&b, "reason", reason); - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_close(&b); - - res = pw_websocket_connection_send_text(client->conn, mem, size); - pw_websocket_connection_disconnect(client->conn, true); - free(mem); - return res; -} - -#if 0 -static int send_stream_request_format(struct client *client) -{ - return 0; -} -#endif - -static int handle_server_hello(struct client *client, struct spa_json *payload) -{ - struct impl *impl = client->impl; - struct spa_json it[1]; - char key[256], *t; - const char *v; - int l, version = 0; - struct client *c, *ct; - - while ((l = spa_json_object_next(payload, key, sizeof(key), &v)) > 0) { - if (spa_streq(key, "server_id")) { - t = alloca(l+1); - spa_json_parse_stringn(v, l, t, l+1); - pw_properties_set(client->props, "sendspin.server-id", t); - } - else if (spa_streq(key, "name")) { - t = alloca(l+1); - spa_json_parse_stringn(v, l, t, l+1); - pw_properties_set(client->props, "sendspin.server-name", t); - } - else if (spa_streq(key, "version")) { - if (spa_json_parse_int(v, l, &version) <= 0) - return -EINVAL; - } - else if (spa_streq(key, "active_roles")) { - if (!spa_json_is_array(v, l)) - return -EPROTO; - - spa_json_enter(payload, &it[0]); - while ((l = spa_json_next(&it[0], &v)) > 0) { - t = alloca(l+1); - spa_json_parse_stringn(v, l, t, l+1); - - if (spa_streq(t, "player@v1")) - client->active_roles |= ROLE_PLAYER; - else if (spa_streq(t, "metadata@v1")) - client->active_roles |= ROLE_METADATA; - } - } - else if (spa_streq(key, "connection_reason")) { - t = alloca(l+1); - spa_json_parse_stringn(v, l, t, l+1); - - if (spa_streq(t, "discovery")) - client->connection_reason = REASON_DISCOVERY; - else if (spa_streq(t, "playback")) - client->connection_reason = REASON_PLAYBACK; - - pw_properties_set(client->props, "sendspin.connection-reason", t); - } - } - if (version != 1) - return -ENOTSUP; - - if (impl->single_server) { - if (client->connection_reason == REASON_PLAYBACK) { - /* keep this server, destroy others */ - spa_list_for_each_safe(c, ct, &impl->clients, link) { - if (c == client) - continue; - send_client_goodbye(c, "another_server"); - } - } else { - /* keep other servers, destroy this one */ - spa_list_for_each_safe(c, ct, &impl->clients, link) { - if (c == client) - continue; - return send_client_goodbye(client, "another_server"); - } - } - } - return send_client_state(client); -} - -static int handle_server_state(struct client *client, struct spa_json *payload) -{ - return 0; -} - -static int parse_uint64(const char *val, int len, uint64_t *result) -{ - char buf[64]; - char *end; - - if (len <= 0 || len >= (int)sizeof(buf)) - return 0; - - memcpy(buf, val, len); - buf[len] = '\0'; - - *result = strtoull(buf, &end, 0); - return len > 0 && end == buf + len; -} - -static int handle_server_time(struct client *client, struct spa_json *payload) -{ - struct impl *impl = client->impl; - char key[256]; - const char *v; - int l; - uint64_t t1 = 0, t2 = 0, t3 = 0, t4 = 0, timeout; - - t4 = get_time_us(client); - - while ((l = spa_json_object_next(payload, key, sizeof(key), &v)) > 0) { - if (spa_streq(key, "client_transmitted")) { - if (parse_uint64(v, l, &t1) <= 0) - return -EINVAL; - } - else if (spa_streq(key, "server_received")) { - if (parse_uint64(v, l, &t2) <= 0) - return -EINVAL; - } - else if (spa_streq(key, "server_transmitted")) { - if (parse_uint64(v, l, &t3) <= 0) - return -EINVAL; - } - } - - spa_regress_update(&client->regress_time, (t2+t3)/2, (t1+t4)/2); - - if (client->timeout_count < 4) - timeout = 200 * SPA_MSEC_PER_SEC; - else if (client->timeout_count < 10) - timeout = SPA_NSEC_PER_SEC; - else if (client->timeout_count < 20) - timeout = 2 * SPA_NSEC_PER_SEC; - else - timeout = 5 * SPA_NSEC_PER_SEC; - - client->timeout_count++; - pw_timer_queue_add(impl->timer_queue, &client->timer, - &client->timer.timeout, timeout, - do_client_timer, client); - return 0; -} - -static int handle_server_command(struct client *client, struct spa_json *payload) -{ - return 0; -} - -/* {"codec":"pcm","sample_rate":44100,"channels":2,"bit_depth":16} */ -static int parse_player(struct client *client, struct spa_json *player) -{ - char key[256], codec[64] = ""; - const char *v; - int l, sample_rate = 0, channels = 0, bit_depth = 0; - - spa_zero(client->info); - client->info.media_type = SPA_MEDIA_TYPE_audio; - while ((l = spa_json_object_next(player, key, sizeof(key), &v)) > 0) { - if (spa_streq(key, "codec")) { - if (spa_json_parse_stringn(v, l, codec, sizeof(codec)) <= 0) - return -EINVAL; - } - else if (spa_streq(key, "sample_rate")) { - if (spa_json_parse_int(v, l, &sample_rate) <= 0) - return -EINVAL; - } - else if (spa_streq(key, "channels")) { - if (spa_json_parse_int(v, l, &channels) <= 0) - return -EINVAL; - } - else if (spa_streq(key, "bit_depth")) { - if (spa_json_parse_int(v, l, &bit_depth) <= 0) - return -EINVAL; - } - else if (spa_streq(key, "codec_header")) { - } - } - if (sample_rate == 0 || channels == 0) - return -EINVAL; - - if (spa_streq(codec, "pcm")) { - client->info.media_subtype = SPA_MEDIA_SUBTYPE_raw; - client->info.info.raw.rate = sample_rate; - client->info.info.raw.channels = channels; - switch (bit_depth) { - case 16: - client->info.info.raw.format = SPA_AUDIO_FORMAT_S16_LE; - client->stride = 2 * channels; - break; - case 24: - client->info.info.raw.format = SPA_AUDIO_FORMAT_S24_LE; - client->stride = 3 * channels; - break; - default: - return -EINVAL; - } - } - else if (spa_streq(codec, "opus")) { - client->info.media_subtype = SPA_MEDIA_SUBTYPE_opus; - client->info.info.opus.rate = sample_rate; - client->info.info.opus.channels = channels; - } - else if (spa_streq(codec, "flac")) { - client->info.media_subtype = SPA_MEDIA_SUBTYPE_flac; - client->info.info.flac.rate = sample_rate; - client->info.info.flac.channels = channels; - } - else - return -EINVAL; - - spa_dll_set_bw(&client->dll, SPA_DLL_BW_MIN, 1000, sample_rate); - - return 0; -} - -/* {"player":{}} */ -static int handle_stream_start(struct client *client, struct spa_json *payload) -{ - struct impl *impl = client->impl; - struct spa_json it[1]; - char key[256]; - const char *v; - int l; - - while ((l = spa_json_object_next(payload, key, sizeof(key), &v)) > 0) { - if (spa_streq(key, "player")) { - if (!spa_json_is_object(v, l)) - return -EPROTO; - spa_json_enter(payload, &it[0]); - parse_player(client, &it[0]); - } - } - - if (client->stream == NULL) { - create_stream(client); - - pw_timer_queue_cancel(&client->timer); - pw_timer_queue_add(impl->timer_queue, &client->timer, - NULL, 0, do_client_timer, client); - } else { - } - - return 0; -} - -static void stream_clear(struct client *client) -{ - spa_ringbuffer_init(&client->ring); - memset(client->buffer, 0, client->buffer_size); -} - -static int handle_stream_clear(struct client *client, struct spa_json *payload) -{ - stream_clear(client); - return 0; -} -static int handle_stream_end(struct client *client, struct spa_json *payload) -{ - if (client->stream != NULL) { - pw_stream_destroy(client->stream); - client->stream = NULL; - stream_clear(client); - } - return 0; -} - -static int handle_group_update(struct client *client, struct spa_json *payload) -{ - return 0; -} - -/* { "type":... "payload":{...} } */ -static int do_parse_text(struct client *client, const char *content, int size) -{ - struct spa_json it[2], *payload = NULL; - char key[256], type[256] = ""; - const char *v; - int res, l; - - pw_log_info("received text %.*s", size, content); - - if (spa_json_begin_object(&it[0], content, size) <= 0) - return -EINVAL; - - while ((l = spa_json_object_next(&it[0], key, sizeof(key), &v)) > 0) { - if (spa_streq(key, "payload")) { - if (!spa_json_is_object(v, l)) - return -EPROTO; - - spa_json_enter(&it[0], &it[1]); - payload = &it[1]; - } - else if (spa_streq(key, "type")) { - if (spa_json_parse_stringn(v, l, type, sizeof(type)) <= 0) - continue; - } - } - if (spa_streq(type, "server/hello")) - res = handle_server_hello(client, payload); - else if (spa_streq(type, "server/state")) - res = handle_server_state(client, payload); - else if (spa_streq(type, "server/time")) - res = handle_server_time(client, payload); - else if (spa_streq(type, "server/command")) - res = handle_server_command(client, payload); - else if (spa_streq(type, "stream/start")) - res = handle_stream_start(client, payload); - else if (spa_streq(type, "stream/end")) - res = handle_stream_end(client, payload); - else if (spa_streq(type, "stream/clear")) - res = handle_stream_clear(client, payload); - else if (spa_streq(type, "group/update")) - res = handle_group_update(client, payload); - else - res = 0; - - return res; -} - -static int do_handle_binary(struct client *client, const uint8_t *payload, int size) -{ - struct impl *impl = client->impl; - int32_t filled; - uint32_t index, length = size - 9; - uint64_t timestamp; - - if (payload[0] != 4 || client->stream == NULL) - return 0; - - timestamp = ((uint64_t)payload[1]) << 56; - timestamp |= ((uint64_t)payload[2]) << 48; - timestamp |= ((uint64_t)payload[3]) << 40; - timestamp |= ((uint64_t)payload[4]) << 32; - timestamp |= ((uint64_t)payload[5]) << 24; - timestamp |= ((uint64_t)payload[6]) << 16; - timestamp |= ((uint64_t)payload[7]) << 8; - timestamp |= ((uint64_t)payload[8]); - - filled = spa_ringbuffer_get_write_index(&client->ring, &index); - if (filled < 0) { - pw_log_warn("%p: underrun write:%u filled:%d", - client, index, filled); - } else if (filled + length > client->buffer_size) { - pw_log_debug("%p: overrun write:%u filled:%d", - client, index, filled); - } - - spa_ringbuffer_write_data(&client->ring, - client->buffer, client->buffer_size, - index % client->buffer_size, - &payload[9], length); - - spa_ringbuffer_write_update(&client->ring, index + length); - - pw_loop_lock(impl->data_loop); - spa_regress_update(&client->regress_index, index, timestamp); - pw_loop_unlock(impl->data_loop); - - return 0; -} - -static void on_connection_message(void *data, int opcode, void *payload, size_t size) -{ - struct client *client = data; - if (opcode == PW_WEBSOCKET_OPCODE_TEXT) { - do_parse_text(client, payload, size); - } else if (opcode == PW_WEBSOCKET_OPCODE_BINARY) { - do_handle_binary(client, payload, size); - } else { - pw_log_warn("%02x unknown %08x", opcode, (int)size); - } -} - -static void client_free(struct client *client) -{ - struct impl *impl = client->impl; - - spa_list_remove(&client->link); - - handle_stream_end(client, NULL); - if (client->conn) { - spa_hook_remove(&client->conn_listener); - pw_websocket_connection_destroy(client->conn); - } else { - pw_websocket_cancel(impl->websocket, client); - } - pw_timer_queue_cancel(&client->timer); - - pw_properties_free(client->props); - free(client->buffer); - free(client->name); - free(client); -} - -static void on_connection_destroy(void *data) -{ - struct client *client = data; - client->conn = NULL; - pw_log_info("connection %p destroy", client); -} -static void on_connection_error(void *data, int res, const char *reason) -{ - struct client *client = data; - pw_log_error("connection %p error %d %s", client, res, reason); -} - -static void on_connection_disconnected(void *data) -{ - struct client *client = data; - client_free(client); -} - -static const struct pw_websocket_connection_events websocket_connection_events = { - PW_VERSION_WEBSOCKET_CONNECTION_EVENTS, - .destroy = on_connection_destroy, - .error = on_connection_error, - .disconnected = on_connection_disconnected, - .message = on_connection_message, -}; - -static struct client *client_new(struct impl *impl, const char *name, struct pw_properties *props) -{ - struct client *client; - - client = calloc(1, sizeof(*client)); - if (client == NULL) - goto error; - - client->impl = impl; - spa_list_append(&impl->clients, &client->link); - - client->name = name ? strdup(name) : NULL; - client->props = props; - spa_regress_init(&client->regress_index, 5); - spa_regress_init(&client->regress_time, 5); - - spa_dll_init(&client->dll); - client->resync = true; - - return client; -error: - pw_properties_free(props); - return NULL; -} - -static int client_connect(struct client *c) -{ - struct impl *impl = c->impl; - const char *addr, *port, *path; - addr = pw_properties_get(c->props, "sendspin.ip"); - port = pw_properties_get(c->props, "sendspin.port"); - path = pw_properties_get(c->props, "sendspin.path"); - return pw_websocket_connect(impl->websocket, c, addr, port, path); -} - -static void client_connected(struct client *c, struct pw_websocket_connection *conn) -{ - if (c->conn) { - spa_hook_remove(&c->conn_listener); - pw_websocket_connection_destroy(c->conn); - } - c->conn = conn; - if (conn) - pw_websocket_connection_add_listener(c->conn, &c->conn_listener, - &websocket_connection_events, c); -} - -struct match_info { - struct impl *impl; - const char *name; - struct pw_properties *props; - struct pw_websocket_connection *conn; - bool matched; -}; - -static int rule_matched(void *data, const char *location, const char *action, - const char *str, size_t len) -{ - struct match_info *i = data; - struct impl *impl = i->impl; - int res = 0; - - i->matched = true; - if (spa_streq(action, "create-stream")) { - struct client *c; - - pw_properties_update_string(i->props, str, len); - if ((c = client_new(impl, i->name, spa_steal_ptr(i->props))) == NULL) - return -errno; - if (i->conn) - client_connected(c, i->conn); - else - client_connect(c); - } - return res; -} - -static inline int match_client(struct impl *impl, const char *name, struct pw_properties *props, - struct pw_websocket_connection *conn) -{ - const char *str; - struct match_info minfo = { - .impl = impl, - .name = name, - .props = props, - .conn = conn, - }; - - if ((str = pw_properties_get(impl->props, "stream.rules")) == NULL) - str = DEFAULT_CREATE_RULES; - - pw_conf_match_rules(str, strlen(str), NAME, &props->dict, - rule_matched, &minfo); - - if (!minfo.matched) { - pw_log_info("unmatched client found %s", str); - if (conn) - pw_websocket_connection_destroy(conn); - pw_properties_free(props); - } - return minfo.matched; -} - -static void on_websocket_connected(void *data, void *user, - struct pw_websocket_connection *conn, const char *path) -{ - struct impl *impl = data; - struct client *c = user; - - pw_log_info("connected to %s", path); - if (c == NULL) { - struct sockaddr_storage addr; - char ip[128]; - uint16_t port = 0; - bool ipv4; - struct pw_properties *props; - - pw_websocket_connection_address(conn, - (struct sockaddr*)&addr, sizeof(addr)); - - props = pw_properties_copy(impl->stream_props); - if (pw_net_get_ip(&addr, ip, sizeof(ip), &ipv4, &port) >= 0) { - pw_properties_set(props, "sendspin.ip", ip); - pw_properties_setf(props, "sendspin.port", "%u", port); - } - pw_properties_set(props, "sendspin.path", path); - - if ((c = client_new(impl, "", props)) == NULL) { - pw_log_error("can't create new client: %m"); - return; - } - } - client_connected(c, conn); - send_client_hello(c); -} - -static const struct pw_websocket_events websocket_events = { - PW_VERSION_WEBSOCKET_EVENTS, - .connected = on_websocket_connected, -}; - -#ifdef HAVE_AVAHI -static struct client *client_find(struct impl *impl, const char *name) -{ - struct client *c; - spa_list_for_each(c, &impl->clients, link) { - if (spa_streq(c->name, name)) - return c; - } - return NULL; -} - -static void on_zeroconf_added(void *data, const void *user, const struct spa_dict *info) -{ - struct impl *impl = data; - const char *name, *addr, *port, *path; - struct client *c; - struct pw_properties *props; - - name = spa_dict_lookup(info, PW_KEY_ZEROCONF_NAME); - - if (impl->single_server && !spa_list_is_empty(&impl->clients)) - return; - - if ((c = client_find(impl, name)) != NULL) - return; - - props = pw_properties_copy(impl->stream_props); - pw_properties_update(props, info); - - addr = spa_dict_lookup(info, PW_KEY_ZEROCONF_ADDRESS); - port = spa_dict_lookup(info, PW_KEY_ZEROCONF_PORT); - path = spa_dict_lookup(info, "path"); - - pw_properties_set(props, "sendspin.ip", addr); - pw_properties_set(props, "sendspin.port", port); - pw_properties_set(props, "sendspin.path", path); - - match_client(impl, name, props, NULL); -} - -static void on_zeroconf_removed(void *data, const void *user, const struct spa_dict *info) -{ - struct impl *impl = data; - const char *name; - struct client *c; - - name = spa_dict_lookup(info, PW_KEY_ZEROCONF_NAME); - - if ((c = client_find(impl, name)) == NULL) - return; - - client_free(c); -} - -static const struct pw_zeroconf_events zeroconf_events = { - PW_VERSION_ZEROCONF_EVENTS, - .added = on_zeroconf_added, - .removed = on_zeroconf_removed, -}; -#endif - -static void core_destroy(void *d) -{ - struct impl *impl = d; - spa_hook_remove(&impl->core_listener); - impl->core = NULL; - pw_impl_module_schedule_destroy(impl->module); -} - -static const struct pw_proxy_events core_proxy_events = { - .destroy = core_destroy, -}; - -static void impl_destroy(struct impl *impl) -{ - struct client *c; - - spa_list_consume(c, &impl->clients, link) - client_free(c); - - if (impl->core && impl->do_disconnect) - pw_core_disconnect(impl->core); - - if (impl->data_loop) - pw_context_release_loop(impl->context, impl->data_loop); - - pw_properties_free(impl->stream_props); - pw_properties_free(impl->props); - - free(impl); -} - -static void module_destroy(void *d) -{ - struct impl *impl = d; - spa_hook_remove(&impl->module_listener); - impl_destroy(impl); -} - -static const struct pw_impl_module_events module_events = { - PW_VERSION_IMPL_MODULE_EVENTS, - .destroy = module_destroy, -}; - -static void on_core_error(void *d, uint32_t id, int seq, int res, const char *message) -{ - struct impl *impl = d; - - pw_log_error("error id:%u seq:%d res:%d (%s): %s", - id, seq, res, spa_strerror(res), message); - - if (id == PW_ID_CORE && res == -EPIPE) - pw_impl_module_schedule_destroy(impl->module); -} - -static const struct pw_core_events core_events = { - PW_VERSION_CORE_EVENTS, - .error = on_core_error, -}; - -static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) -{ - const char *str; - if ((str = pw_properties_get(props, key)) != NULL) { - if (pw_properties_get(impl->stream_props, key) == NULL) - pw_properties_set(impl->stream_props, key, str); - } -} - -SPA_EXPORT -int pipewire__module_init(struct pw_impl_module *module, const char *args) -{ - struct pw_context *context = pw_impl_module_get_context(module); - struct impl *impl; - const char *str, *hostname, *port, *path; - struct pw_properties *props, *stream_props; - int res = 0; - bool autoconnect; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - if (args == NULL) - args = ""; - - props = impl->props = pw_properties_new_string(args); - stream_props = impl->stream_props = pw_properties_new(NULL, NULL); - if (props == NULL || stream_props == NULL) { - res = -errno; - pw_log_error( "can't create properties: %m"); - goto out; - } - - impl->module = module; - impl->context = context; - impl->main_loop = pw_context_get_main_loop(context); - impl->data_loop = pw_context_acquire_loop(context, &props->dict); - impl->timer_queue = pw_context_get_timer_queue(context); - spa_list_init(&impl->clients); - - pw_properties_set(props, PW_KEY_NODE_LOOP_NAME, impl->data_loop->name); - - if ((str = pw_properties_get(props, "stream.props")) != NULL) - pw_properties_update_string(stream_props, str, strlen(str)); - - copy_props(impl, props, PW_KEY_NODE_LOOP_NAME); - copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); - copy_props(impl, props, SPA_KEY_AUDIO_POSITION); - copy_props(impl, props, PW_KEY_NODE_NAME); - copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); - copy_props(impl, props, PW_KEY_NODE_GROUP); - copy_props(impl, props, PW_KEY_NODE_LATENCY); - copy_props(impl, props, PW_KEY_NODE_VIRTUAL); - copy_props(impl, props, PW_KEY_NODE_CHANNELNAMES); - copy_props(impl, props, PW_KEY_MEDIA_NAME); - copy_props(impl, props, PW_KEY_MEDIA_CLASS); - - impl->always_process = pw_properties_get_bool(stream_props, - PW_KEY_NODE_ALWAYS_PROCESS, true); - - autoconnect = pw_properties_get_bool(props, "sendspin.autoconnect", false); - impl->single_server = pw_properties_get_bool(props, - "sendspin.single-server", true); - - if ((str = pw_properties_get(props, "sendspin.client-name")) == NULL) - pw_properties_set(props, "sendspin.client-name", pw_get_host_name()); - if ((str = pw_properties_get(props, "sendspin.client-id")) == NULL) - pw_properties_setf(props, "sendspin.client-id", "pipewire-%s", pw_get_host_name()); - - impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); - if (impl->core == NULL) { - str = pw_properties_get(props, PW_KEY_REMOTE_NAME); - impl->core = pw_context_connect(impl->context, - pw_properties_new( - PW_KEY_REMOTE_NAME, str, - NULL), - 0); - impl->do_disconnect = true; - } - if (impl->core == NULL) { - res = -errno; - pw_log_error("can't connect: %m"); - goto out; - } - - pw_proxy_add_listener((struct pw_proxy*)impl->core, - &impl->core_proxy_listener, - &core_proxy_events, impl); - pw_core_add_listener(impl->core, - &impl->core_listener, - &core_events, impl); - - impl->websocket = pw_websocket_new(impl->main_loop, &props->dict); - pw_websocket_add_listener(impl->websocket, &impl->websocket_listener, - &websocket_events, impl); - -#ifdef HAVE_AVAHI - if ((impl->zeroconf = pw_zeroconf_new(context, NULL)) != NULL) { - pw_zeroconf_add_listener(impl->zeroconf, &impl->zeroconf_listener, - &zeroconf_events, impl); - } -#endif - - hostname = pw_properties_get(props, "sendspin.ip"); - /* a client should either connect itself or advertize itself and listen - * for connections, not both */ - if (!autoconnect && hostname == NULL){ - /* listen for server connection */ - if ((hostname = pw_properties_get(props, "source.ip")) == NULL) - hostname = DEFAULT_SOURCE_IP; - if ((port = pw_properties_get(props, "source.port")) == NULL) - port = SPA_STRINGIFY(DEFAULT_SOURCE_PORT); - if ((path = pw_properties_get(props, "source.path")) == NULL) - path = DEFAULT_SOURCE_PATH; - - pw_websocket_listen(impl->websocket, NULL, hostname, port, path); - -#ifdef HAVE_AVAHI - bool announce = pw_properties_get_bool(props, "sendspin.announce", true); - if (impl->zeroconf && announce) { - /* optionally announce ourselves */ - str = pw_properties_get(props, "sendspin.client-id"); - pw_zeroconf_set_announce(impl->zeroconf, NULL, - &SPA_DICT_ITEMS( - SPA_DICT_ITEM(PW_KEY_ZEROCONF_TYPE, PW_SENDSPIN_CLIENT_SERVICE_TYPE), - SPA_DICT_ITEM(PW_KEY_ZEROCONF_NAME, str), - SPA_DICT_ITEM(PW_KEY_ZEROCONF_PORT, port), - SPA_DICT_ITEM("path", path))); - } -#endif - } - else { - if (hostname != NULL) { - struct client *c; - struct pw_properties *p; - - /* connect to hardcoded server */ - port = pw_properties_get(props, "sendspin.port"); - if (port == NULL) - port = SPA_STRINGIFY(DEFAULT_SERVER_PORT); - if ((path = pw_properties_get(props, "sendspin.path")) == NULL) - path = DEFAULT_SENDSPIN_PATH; - - p = pw_properties_copy(impl->stream_props); - pw_properties_set(p, "sendspin.ip", hostname); - pw_properties_set(p, "sendspin.port", port); - pw_properties_set(p, "sendspin.path", path); - - if ((c = client_new(impl, "", p)) != NULL) - client_connect(c); - } - /* connect to zeroconf server if we can */ -#ifdef HAVE_AVAHI - if (impl->zeroconf) { - pw_zeroconf_set_browse(impl->zeroconf, NULL, - &SPA_DICT_ITEMS( - SPA_DICT_ITEM(PW_KEY_ZEROCONF_TYPE, PW_SENDSPIN_SERVER_SERVICE_TYPE))); - } -#endif - } - - pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); - - pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_info)); - - pw_log_info("Successfully loaded module-sendspin-recv"); - - return 0; -out: - impl_destroy(impl); - return res; -} diff --git a/src/modules/module-sendspin-send.c b/src/modules/module-sendspin-send.c deleted file mode 100644 index 2c9f6df22..000000000 --- a/src/modules/module-sendspin-send.c +++ /dev/null @@ -1,1451 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include - -#include -#include - -#include "module-sendspin/sendspin.h" -#include "module-sendspin/websocket.h" -#include "network-utils.h" - -#ifdef HAVE_AVAHI -#include "zeroconf-utils/zeroconf.h" -#endif - -/** \page page_module_sendspin_send sendspin sender - * - * The `sendspin-send` module creates a PipeWire sink that sends audio - * packets using the sendspin protocol to a client. - * - * The sender will listen on a specific port (8927) and create a stream for - * each connection. - * - * In combination with a virtual sink, each of the client streams can be sent - * the same data in the client specific format. - * - * ## Module Name - * - * `libpipewire-module-sendspin-send` - * - * ## Module Options - * - * Options specific to the behavior of this module - * - * - `local.ifname = `: interface name to use - * - `local.ifaddress = `: interface address to use - * - `source.ip = `: the source ip address to listen on, default "127.0.0.1" - * - `source.port = `: the source port to listen on, default 8927 - * - `source.path = `: comma separated list of paths to listen on, - * default "/sendspin" - * - `sendspin.ip`: an array of IP addresses of sendspin clients to connect to - * - `sendspin.port`: the port of the sendspin client to connect to, default 8928 - * - `sendspin.path`: the path of the sendspin client to connect to, default "/sendspin" - * - `sendspin.group-id`: the group-id of the server, default random - * - `sendspin.group-name`: the group-name of the server, default "PipeWire" - * - `sendspin.delay`: the delay to add to clients in seconds. Default 5.0 - * - `node.always-process = `: true to send silence even when not connected. - * - `stream.props = {}`: properties to be passed to all the stream - * - `stream.rules` = : match rules, use the create-stream action to - * make a stream for the client. - * - * ## General options - * - * Options with well-known behavior: - * - * - \ref PW_KEY_REMOTE_NAME - * - \ref SPA_KEY_AUDIO_LAYOUT - * - \ref SPA_KEY_AUDIO_POSITION - * - \ref PW_KEY_MEDIA_NAME - * - \ref PW_KEY_MEDIA_CLASS - * - \ref PW_KEY_NODE_NAME - * - \ref PW_KEY_NODE_DESCRIPTION - * - \ref PW_KEY_NODE_GROUP - * - \ref PW_KEY_NODE_LATENCY - * - \ref PW_KEY_NODE_VIRTUAL - * - * ## Example configuration - *\code{.unparsed} - * # ~/.config/pipewire/pipewire.conf.d/my-sendspin-send.conf - * - * context.modules = [ - * { name = libpipewire-module-sendspin-send - * args = { - * #local.ifname = eth0 - * #source.ip = 127.0.0.1 - * #source.port = 8927 - * #source.path = "/sendspin" - * #sendspin.ip = [ 127.0.0.1 ] - * #sendspin.port = 8928 - * #sendspin.path = "/sendspin" - * #sendspin.group-id = "abcded" - * #sendspin.group-name = "PipeWire" - * #sendspin.delay = 5.0 - * #node.always-process = false - * #audio.position = [ FL FR ] - * stream.props = { - * #media.class = "Audio/sink" - * #node.name = "sendspin-send" - * } - * stream.rules = [ - * { matches = [ - * { sendspin.ip = "~.*" - * #sendspin.port = 8928 - * #sendspin.path = "/sendspin" - * #zeroconf.ifindex = 0 - * #zeroconf.name = "" - * #zeroconf.type = "_sendspin._tcp" - * #zeroconf.domain = "local" - * #zeroconf.hostname = "" - * } - * ] - * actions = { - * create-stream = { - * stream.props = { - * #target.object = "" - * #media.class = "Audio/Sink" - * } - * } - * } - * } - * ] - * } - * } - * ] - *\endcode - * - * \since 1.6.0 - */ - -#define NAME "sendspin-send" - -PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); -#define PW_LOG_TOPIC_DEFAULT mod_topic - -#define DEFAULT_SOURCE_IP "127.0.0.1" -#define DEFAULT_SOURCE_PORT PW_SENDSPIN_DEFAULT_SERVER_PORT -#define DEFAULT_SOURCE_PATH PW_SENDSPIN_DEFAULT_PATH - -#define DEFAULT_CLIENT_PORT PW_SENDSPIN_DEFAULT_CLIENT_PORT -#define DEFAULT_SENDSPIN_PATH PW_SENDSPIN_DEFAULT_PATH - -#define DEFAULT_SENDSPIN_DELAY 5.0 - -#define DEFAULT_POSITION "[ FL FR ]" - -#define DEFAULT_CREATE_RULES \ - "[ { matches = [ { sendspin.ip = \"~.*\" } ] actions = { create-stream = { } } } ] " - -#define USAGE "( local.ifname= ) " \ - "( source.ip= ) " \ - "( source.port= " \ - "( audio.position= ) " \ - "( stream.props= { key=value ... } ) " - -static const struct spa_dict_item module_info[] = { - { PW_KEY_MODULE_AUTHOR, "Wim Taymans " }, - { PW_KEY_MODULE_DESCRIPTION, "Sendspin sender" }, - { PW_KEY_MODULE_USAGE, USAGE }, - { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, -}; - -struct client { - struct impl *impl; - struct spa_list link; - - char *name; - struct pw_properties *props; - - struct pw_websocket_connection *conn; - struct spa_hook conn_listener; - - struct spa_audio_info info; - struct pw_stream *stream; - struct spa_hook stream_listener; - - struct spa_io_position *io_position; - struct pw_timer timer; - - uint64_t delay_usec; - uint32_t stride; - - int buffer_capacity; -#define ROLE_PLAYER (1<<0) -#define ROLE_METADATA (1<<1) - uint32_t supported_roles; -#define COMMAND_VOLUME (1<<0) -#define COMMAND_MUTE (1<<1) - uint32_t supported_commands; -#define STATE_UNKNOWN 0 -#define STATE_SYNCHRONIZED 1 -#define STATE_ERROR 2 - uint32_t state; - - int volume; - bool mute; - - bool playing; -}; - -struct impl { - struct pw_impl_module *module; - struct spa_hook module_listener; - struct pw_properties *props; - struct pw_context *context; - - struct pw_loop *main_loop; - struct pw_loop *data_loop; - struct pw_timer_queue *timer_queue; - - struct pw_core *core; - struct spa_hook core_listener; - struct spa_hook core_proxy_listener; - unsigned int do_disconnect:1; - -#ifdef HAVE_AVAHI - struct pw_zeroconf *zeroconf; - struct spa_hook zeroconf_listener; -#endif - - float delay; - bool always_process; - - struct pw_properties *stream_props; - - struct pw_websocket *websocket; - struct spa_hook websocket_listener; - - struct spa_list clients; - -}; - -static int send_group_update(struct client *c, bool playing); -static int send_stream_start(struct client *c); -static int send_server_state(struct client *c); - -static void on_stream_destroy(void *d) -{ - struct client *c = d; - spa_hook_remove(&c->stream_listener); - c->stream = NULL; -} - -static void on_stream_state_changed(void *d, enum pw_stream_state old, - enum pw_stream_state state, const char *error) -{ - struct client *c = d; - switch (state) { - case PW_STREAM_STATE_ERROR: - case PW_STREAM_STATE_UNCONNECTED: - //pw_impl_module_schedule_destroy(c->impl->module); - break; - case PW_STREAM_STATE_PAUSED: - send_group_update(c, false); - break; - case PW_STREAM_STATE_STREAMING: - send_group_update(c, true); - break; - default: - break; - } -} - -static uint64_t get_time_us(struct client *c) -{ - struct timespec now; - if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) - return 0; - return SPA_TIMESPEC_TO_USEC(&now); -} - -static void on_playback_stream_process(void *d) -{ - struct client *c = d; - struct pw_buffer *b; - struct spa_buffer *buf; - uint8_t *p; - struct iovec iov[2]; - uint8_t header[9]; - uint64_t timestamp; - - if ((b = pw_stream_dequeue_buffer(c->stream)) == NULL) { - pw_log_debug("out of buffers: %m"); - return; - } - - if (c->playing) { - buf = b->buffer; - if ((p = buf->datas[0].data) == NULL) - return; - - timestamp = c->io_position ? - c->io_position->clock.nsec / 1000 : - get_time_us(c); - timestamp += c->delay_usec; - - header[0] = 4; - header[1] = (timestamp >> 56) & 0xff; - header[2] = (timestamp >> 48) & 0xff; - header[3] = (timestamp >> 40) & 0xff; - header[4] = (timestamp >> 32) & 0xff; - header[5] = (timestamp >> 24) & 0xff; - header[6] = (timestamp >> 16) & 0xff; - header[7] = (timestamp >> 8) & 0xff; - header[8] = (timestamp ) & 0xff; - - iov[0].iov_base = header; - iov[0].iov_len = sizeof(header); - iov[1].iov_base = p; - iov[1].iov_len = buf->datas[0].chunk->size; - - pw_websocket_connection_send(c->conn, - PW_WEBSOCKET_OPCODE_BINARY, iov, 2); - } - pw_stream_queue_buffer(c->stream, b); -} - -static void -on_stream_param_changed(void *d, uint32_t id, const struct spa_pod *param) -{ - struct client *c = d; - - if (param == NULL) - return; - - switch (id) { - case SPA_PARAM_Format: - if (spa_format_audio_parse(param, &c->info) < 0) - return; - send_stream_start(c); - break; - case SPA_PARAM_Tag: - send_server_state(c); - break; - } -} - -static void on_stream_io_changed(void *d, uint32_t id, void *area, uint32_t size) -{ - struct client *c = d; - switch (id) { - case SPA_IO_Position: - c->io_position = area; - break; - } -} - -static const struct pw_stream_events playback_stream_events = { - PW_VERSION_STREAM_EVENTS, - .destroy = on_stream_destroy, - .io_changed = on_stream_io_changed, - .state_changed = on_stream_state_changed, - .param_changed = on_stream_param_changed, - .process = on_playback_stream_process -}; - -static int create_stream(struct client *c) -{ - struct impl *impl = c->impl; - int res; - uint32_t n_params; - const struct spa_pod *params[1]; - uint8_t buffer[1024]; - struct spa_pod_builder b; - const char *client_id, *ip, *port, *client_name; - struct pw_properties *props = pw_properties_copy(c->props); - - ip = pw_properties_get(props, "sendspin.ip"); - port = pw_properties_get(props, "sendspin.port"); - client_id = pw_properties_get(props, "sendspin.client-id"); - client_name = pw_properties_get(props, "sendspin.client-name"); - - if (pw_properties_get(props, PW_KEY_NODE_NAME) == NULL) - pw_properties_setf(props, PW_KEY_NODE_NAME, "sendspin.%s.%s.%s", client_id, ip, port); - if (pw_properties_get(props, PW_KEY_NODE_DESCRIPTION) == NULL) - pw_properties_setf(props, PW_KEY_NODE_DESCRIPTION, "Sendspin to %s", client_name); - if (pw_properties_get(props, PW_KEY_MEDIA_NAME) == NULL) - pw_properties_setf(props, PW_KEY_MEDIA_NAME, "Sendspin to %s", client_name); - - - c->stream = pw_stream_new(impl->core, "sendspin sender", props); - if (c->stream == NULL) - return -errno; - - pw_stream_add_listener(c->stream, - &c->stream_listener, - &playback_stream_events, c); - - n_params = 0; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - params[n_params++] = spa_format_audio_build(&b, - SPA_PARAM_EnumFormat, &c->info); - - if ((res = pw_stream_connect(c->stream, - PW_DIRECTION_INPUT, - PW_ID_ANY, - PW_STREAM_FLAG_AUTOCONNECT | - PW_STREAM_FLAG_MAP_BUFFERS | - PW_STREAM_FLAG_RT_PROCESS, - params, n_params)) < 0) - return res; - - return 0; -} - -static int send_server_hello(struct client *c) -{ - struct impl *impl = c->impl; - struct spa_json_builder b; - int res; - size_t size; - char *mem; - - spa_json_builder_memstream(&b, &mem, &size, 0); - spa_json_builder_array_push(&b, "{"); - spa_json_builder_object_string(&b, "type", "server/hello"); - spa_json_builder_object_push(&b, "payload", "{"); - spa_json_builder_object_string(&b, "server_id", pw_properties_get(impl->props, "sendspin.server-id")); - spa_json_builder_object_string(&b, "name", pw_properties_get(impl->props, "sendspin.server-name")); - spa_json_builder_object_int(&b, "version", 1); - spa_json_builder_object_push(&b, "active_roles", "["); - if (c->supported_roles & ROLE_PLAYER) - spa_json_builder_array_string(&b, "player@v1"); - if (c->supported_roles & ROLE_METADATA) - spa_json_builder_array_string(&b, "metadata@v1"); - spa_json_builder_pop(&b, "]"); - spa_json_builder_object_string(&b, "connection_reason", "discovery"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_close(&b); - - res = pw_websocket_connection_send_text(c->conn, mem, size); - free(mem); - - return res; -} - -static int send_server_state(struct client *c) -{ - struct spa_json_builder b; - int res; - size_t size; - char *mem; - - if (!SPA_FLAG_IS_SET(c->supported_roles, ROLE_METADATA)) - return 0; - - spa_json_builder_memstream(&b, &mem, &size, 0); - spa_json_builder_array_push(&b, "{"); - spa_json_builder_object_string(&b, "type", "server/state"); - spa_json_builder_object_push(&b, "payload", "{"); - spa_json_builder_object_push(&b, "metadata", "{"); - spa_json_builder_object_uint(&b, "timestamp", get_time_us(c)); - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_close(&b); - - res = pw_websocket_connection_send_text(c->conn, mem, size); - free(mem); - return res; -} - -static int send_server_time(struct client *c, uint64_t t1, uint64_t t2) -{ - struct spa_json_builder b; - int res; - uint64_t t3; - size_t size; - char *mem; - - t3 = get_time_us(c); - - spa_json_builder_memstream(&b, &mem, &size, 0); - spa_json_builder_array_push(&b, "{"); - spa_json_builder_object_string(&b, "type", "server/time"); - spa_json_builder_object_push(&b, "payload", "{"); - spa_json_builder_object_uint(&b, "client_transmitted", t1); - spa_json_builder_object_uint(&b, "server_received", t2); - spa_json_builder_object_uint(&b, "server_transmitted", t3); - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_close(&b); - - res = pw_websocket_connection_send_text(c->conn, mem, size); - free(mem); - return res; -} - -#if 0 -static int send_server_command(struct client *c, int command, int value) -{ - struct spa_json_builder b; - size_t size; - char *mem; - int res; - - spa_json_builder_memstream(&b, &mem, &size, 0); - spa_json_builder_array_push(&b, "{"); - spa_json_builder_object_string(&b, "type", "server/command"); - spa_json_builder_object_push(&b, "payload", "{"); - spa_json_builder_object_push(&b, "player", "{"); - if (command == COMMAND_VOLUME) { - spa_json_builder_object_string(&b, "command", "volume"); - spa_json_builder_object_int(&b, "volume", value); - } else { - spa_json_builder_object_string(&b, "command", "mute"); - spa_json_builder_object_int(&b, "mute", value ? true : false); - } - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_close(&b); - - res = pw_websocket_connection_send_text(c->conn, mem, size); - free(mem); - return res; -} -#endif - -static int send_stream_start(struct client *c) -{ - struct spa_json_builder b; - int res, channels, rate, depth = 0; - const char *codec; - size_t size; - char *mem; - - switch (c->info.media_subtype) { - case SPA_MEDIA_SUBTYPE_raw: - codec = "pcm"; - channels = c->info.info.raw.channels; - rate = c->info.info.raw.rate; - switch (c->info.info.raw.format) { - case SPA_AUDIO_FORMAT_S16_LE: - depth = 16; - break; - case SPA_AUDIO_FORMAT_S24_LE: - depth = 24; - break; - default: - return -ENOTSUP; - } - break; - case SPA_MEDIA_SUBTYPE_opus: - codec = "opus"; - channels = c->info.info.opus.channels; - rate = c->info.info.opus.rate; - break; - case SPA_MEDIA_SUBTYPE_flac: - codec = "flac"; - channels = c->info.info.flac.channels; - rate = c->info.info.flac.rate; - break; - default: - return -ENOTSUP; - } - - spa_json_builder_memstream(&b, &mem, &size, 0); - spa_json_builder_array_push(&b, "{"); - spa_json_builder_object_string(&b, "type", "stream/start"); - spa_json_builder_object_push(&b, "payload", "{"); - spa_json_builder_object_push(&b, "player", "{"); - spa_json_builder_object_string(&b, "codec", codec); - spa_json_builder_object_int(&b, "channels", channels); - spa_json_builder_object_int(&b, "sample_rate", rate); - if (depth) - spa_json_builder_object_int(&b, "bit_depth", depth); - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_close(&b); - - res = pw_websocket_connection_send_text(c->conn, mem, size); - free(mem); - return res; -} - -#if 0 -static int send_stream_end(struct client *c) -{ - struct spa_json_builder b; - int res; - size_t size; - char *mem; - - spa_json_builder_memstream(&b, &mem, &size, 0); - spa_json_builder_array_push(&b, "{"); - spa_json_builder_object_string(&b, "type", "stream/end"); - spa_json_builder_object_push(&b, "payload", "{"); - spa_json_builder_object_push(&b, "roles", "["); - spa_json_builder_array_string(&b, "player"); - spa_json_builder_array_string(&b, "metadata"); - spa_json_builder_pop(&b, "]"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_close(&b); - - res = pw_websocket_connection_send_text(c->conn, mem, size); - free(mem); - return res; -} -#endif - -static int send_group_update(struct client *c, bool playing) -{ - struct impl *impl = c->impl; - struct spa_json_builder b; - int res; - char *mem; - size_t size; - - spa_json_builder_memstream(&b, &mem, &size, 0); - spa_json_builder_array_push(&b, "{"); - spa_json_builder_object_string(&b, "type", "group/update"); - spa_json_builder_object_push(&b, "payload", "{"); - spa_json_builder_object_string(&b, "playback_state", playing ? "playing" : "stopped"); - spa_json_builder_object_string(&b, "group_id", pw_properties_get(impl->props, "sendspin.group-id")); - spa_json_builder_object_string(&b, "group_name", pw_properties_get(impl->props, "sendspin.group-name")); - spa_json_builder_pop(&b, "}"); - spa_json_builder_pop(&b, "}"); - spa_json_builder_close(&b); - - c->playing = playing; - - res = pw_websocket_connection_send_text(c->conn, mem, size); - free(mem); - return res; -} - -/* {"codec":"pcm","sample_rate":44100,"channels":2,"bit_depth":16} */ -static int parse_codec(struct client *c, struct spa_json *object, struct spa_audio_info *info) -{ - char key[256], codec[64] = ""; - const char *v; - int l, sample_rate = 0, channels = 0, bit_depth = 0; - - spa_zero(*info); - info->media_type = SPA_MEDIA_TYPE_audio; - while ((l = spa_json_object_next(object, key, sizeof(key), &v)) > 0) { - if (spa_streq(key, "codec")) { - if (spa_json_parse_stringn(v, l, codec, sizeof(codec)) <= 0) - return -EINVAL; - } - else if (spa_streq(key, "sample_rate")) { - if (spa_json_parse_int(v, l, &sample_rate) <= 0) - return -EINVAL; - } - else if (spa_streq(key, "channels")) { - if (spa_json_parse_int(v, l, &channels) <= 0) - return -EINVAL; - } - else if (spa_streq(key, "bit_depth")) { - if (spa_json_parse_int(v, l, &bit_depth) <= 0) - return -EINVAL; - } - else if (spa_streq(key, "codec_header")) { - } - } - if (sample_rate == 0 || channels == 0) - return -EINVAL; - - if (spa_streq(codec, "pcm")) { - info->media_subtype = SPA_MEDIA_SUBTYPE_raw; - info->info.raw.rate = sample_rate; - info->info.raw.channels = channels; - switch (bit_depth) { - case 16: - info->info.raw.format = SPA_AUDIO_FORMAT_S16_LE; - break; - case 24: - info->info.raw.format = SPA_AUDIO_FORMAT_S24_LE; - break; - default: - return -EINVAL; - } - } - else if (spa_streq(codec, "opus")) { - info->media_subtype = SPA_MEDIA_SUBTYPE_opus; - info->info.opus.rate = sample_rate; - info->info.opus.channels = channels; - } - else if (spa_streq(codec, "flac")) { - info->media_subtype = SPA_MEDIA_SUBTYPE_flac; - info->info.flac.rate = sample_rate; - info->info.flac.channels = channels; - } - else - return -EINVAL; - - return 0; -} - -static int parse_player_v1_support(struct client *c, struct spa_json *payload) -{ - struct spa_json it[2]; - char key[256], *t; - const char *v; - int l, res; - - while ((l = spa_json_object_next(payload, key, sizeof(key), &v)) > 0) { - if (spa_streq(key, "supported_formats")) { - int count = 0; - - if (!spa_json_is_array(v, l)) - return -EPROTO; - spa_json_enter(payload, &it[0]); - - while ((l = spa_json_next(&it[0], &v)) > 0) { - struct spa_audio_info info; - if (!spa_json_is_object(v, l)) - return -EPROTO; - - spa_json_enter(&it[0], &it[1]); - if ((res = parse_codec(c, &it[1], &info)) < 0) - return res; - - if (count == 0 && info.media_subtype == SPA_MEDIA_SUBTYPE_raw) { - c->info = info; - count++; - } - } - } - else if (spa_streq(key, "buffer_capacity")) { - if (spa_json_parse_int(v, l, &c->buffer_capacity) <= 0) - return -EINVAL; - } - else if (spa_streq(key, "supported_commands")) { - if (!spa_json_is_array(v, l)) - return -EPROTO; - spa_json_enter(payload, &it[0]); - - while ((l = spa_json_next(&it[0], &v)) > 0) { - t = alloca(l+1); - spa_json_parse_stringn(v, l, t, l+1); - if (spa_streq(t, "volume")) - c->supported_commands |= COMMAND_VOLUME; - else if (spa_streq(t, "mute")) - c->supported_commands |= COMMAND_MUTE; - } - } - } - return 0; -} - -static int handle_client_hello(struct client *c, struct spa_json *payload) -{ - struct spa_json it[1]; - char key[256], *t; - const char *v; - int res, l, version = 0; - - while ((l = spa_json_object_next(payload, key, sizeof(key), &v)) > 0) { - if (spa_streq(key, "client_id")) { - t = alloca(l+1); - spa_json_parse_stringn(v, l, t, l+1); - pw_properties_set(c->props, "sendspin.client-id", t); - } - else if (spa_streq(key, "name")) { - t = alloca(l+1); - spa_json_parse_stringn(v, l, t, l+1); - pw_properties_set(c->props, "sendspin.client-name", t); - } - else if (spa_streq(key, "version")) { - if (spa_json_parse_int(v, l, &version) <= 0) - return -EINVAL; - } - else if (spa_streq(key, "supported_roles")) { - if (!spa_json_is_array(v, l)) - return -EPROTO; - - spa_json_enter(payload, &it[0]); - while ((l = spa_json_next(&it[0], &v)) > 0) { - t = alloca(l+1); - spa_json_parse_stringn(v, l, t, l+1); - - if (spa_streq(t, "player@v1")) - c->supported_roles |= ROLE_PLAYER; - else if (spa_streq(t, "metadata@v1")) - c->supported_roles |= ROLE_METADATA; - } - } - else if (spa_streq(key, "player_support") || - spa_streq(key, "player@v1_support")) { - if (!spa_json_is_object(v, l)) - return -EPROTO; - spa_json_enter(payload, &it[0]); - if ((res = parse_player_v1_support(c, &it[0])) < 0) - return res; - } - } - if (version != 1) - return -ENOTSUP; - - return send_server_hello(c); -} - -static int handle_client_state(struct client *c, struct spa_json *payload) -{ - struct spa_json it[1]; - char key[256], val[128]; - const char *v; - int l; - - while ((l = spa_json_object_next(payload, key, sizeof(key), &v)) > 0) { - if (spa_streq(key, "player")) { - if (!spa_json_is_object(v, l)) - return -EPROTO; - spa_json_enter(payload, &it[0]); - while ((l = spa_json_object_next(&it[0], key, sizeof(key), &v)) > 0) { - if (spa_streq(key, "state")) { - spa_json_parse_stringn(v, l, val, sizeof(val)); - if (spa_streq(val, "synchronized")) - c->state = STATE_SYNCHRONIZED; - else if (spa_streq(val, "error")) - c->state = STATE_ERROR; - else - c->state = STATE_UNKNOWN; - } - else if (spa_streq(key, "volume")) { - if (spa_json_parse_int(v, l, &c->volume) <= 0) - return -EINVAL; - } - else if (spa_streq(key, "mute")) { - if (spa_json_parse_bool(v, l, &c->mute) <= 0) - return -EINVAL; - } - } - } - } - if (c->stream == NULL) - create_stream(c); - return 0; -} - -static int parse_uint64(const char *val, int len, uint64_t *result) -{ - char buf[64]; - char *end; - - if (len <= 0 || len >= (int)sizeof(buf)) - return 0; - - memcpy(buf, val, len); - buf[len] = '\0'; - - *result = strtoull(buf, &end, 0); - return len > 0 && end == buf + len; -} - -static int handle_client_time(struct client *c, struct spa_json *payload) -{ - char key[256]; - const char *v; - int l; - uint64_t t1 = 0,t2; - - t2 = get_time_us(c); - - while ((l = spa_json_object_next(payload, key, sizeof(key), &v)) > 0) { - if (spa_streq(key, "client_transmitted")) { - if (parse_uint64(v, l, &t1) <= 0) - return -EINVAL; - } - } - if (t1 == 0) - return -EPROTO; - - return send_server_time(c, t1, t2); -} - -static int handle_client_command(struct client *c, struct spa_json *payload) -{ - return 0; -} - -/* {"player":{}} */ -static int handle_stream_request_format(struct client *c, struct spa_json *payload) -{ - struct spa_json it[1]; - char key[256]; - const char *v; - int l; - - while ((l = spa_json_object_next(payload, key, sizeof(key), &v)) > 0) { - if (spa_streq(key, "player")) { - if (!spa_json_is_object(v, l)) - return -EPROTO; - spa_json_enter(payload, &it[0]); - parse_codec(c, &it[0], &c->info); - } - } - return 0; -} - -static int handle_client_goodbye(struct client *c, struct spa_json *payload) -{ - if (c->stream != NULL) { - pw_stream_destroy(c->stream); - c->stream = NULL; - } - return 0; -} - -/* { "type":... "payload":{...} } */ -static int do_parse_text(struct client *c, const char *content, int size) -{ - struct spa_json it[2], *payload = NULL; - char key[256], type[256] = ""; - const char *v; - int res, l; - - pw_log_info("received text %.*s", size, content); - - if (spa_json_begin_object(&it[0], content, size) <= 0) - return -EINVAL; - - while ((l = spa_json_object_next(&it[0], key, sizeof(key), &v)) > 0) { - if (spa_streq(key, "payload")) { - if (!spa_json_is_object(v, l)) - return -EPROTO; - - spa_json_enter(&it[0], &it[1]); - payload = &it[1]; - } - else if (spa_streq(key, "type")) { - if (spa_json_parse_stringn(v, l, type, sizeof(type)) <= 0) - continue; - } - } - if (spa_streq(type, "client/hello")) - res = handle_client_hello(c, payload); - else if (spa_streq(type, "client/state")) - res = handle_client_state(c, payload); - else if (spa_streq(type, "client/time")) - res = handle_client_time(c, payload); - else if (spa_streq(type, "client/command")) - res = handle_client_command(c, payload); - else if (spa_streq(type, "client/goodbye")) - res = handle_client_goodbye(c, payload); - else if (spa_streq(type, "stream/request-format")) - res = handle_stream_request_format(c, payload); - else - res = 0; - - return res; -} - -static void on_connection_message(void *data, int opcode, void *payload, size_t size) -{ - struct client *c = data; - if (opcode == PW_WEBSOCKET_OPCODE_TEXT) { - do_parse_text(c, payload, size); - } else { - pw_log_warn("%02x unknown %08x", opcode, (int)size); - } -} - -static void client_free(struct client *c) -{ - struct impl *impl = c->impl; - - spa_list_remove(&c->link); - - handle_client_goodbye(c, NULL); - if (c->conn) { - spa_hook_remove(&c->conn_listener); - pw_websocket_connection_destroy(c->conn); - } else { - pw_websocket_cancel(impl->websocket, c); - } - pw_timer_queue_cancel(&c->timer); - pw_properties_free(c->props); - free(c->name); - free(c); -} - -static void on_connection_destroy(void *data) -{ - struct client *c = data; - c->conn = NULL; - pw_log_info("connection %p destroy", c); -} - -static void on_connection_error(void *data, int res, const char *reason) -{ - struct client *c = data; - pw_log_error("connection %p error %d %s", c, res, reason); -} - -static void on_connection_disconnected(void *data) -{ - struct client *c = data; - client_free(c); -} - -static const struct pw_websocket_connection_events websocket_connection_events = { - PW_VERSION_WEBSOCKET_CONNECTION_EVENTS, - .destroy = on_connection_destroy, - .error = on_connection_error, - .disconnected = on_connection_disconnected, - .message = on_connection_message, -}; - -static struct client *client_new(struct impl *impl, const char *name, struct pw_properties *props) -{ - struct client *c; - - if ((c = calloc(1, sizeof(*c))) == NULL) - goto error; - - c->impl = impl; - spa_list_append(&impl->clients, &c->link); - - c->props = props; - c->name = name ? strdup(name) : NULL; - c->delay_usec = (uint64_t)(impl->delay * SPA_USEC_PER_SEC); - - return c; -error: - pw_properties_free(props); - return NULL; -} - -static int client_connect(struct client *c) -{ - struct impl *impl = c->impl; - const char *addr, *port, *path; - addr = pw_properties_get(c->props, "sendspin.ip"); - port = pw_properties_get(c->props, "sendspin.port"); - path = pw_properties_get(c->props, "sendspin.path"); - return pw_websocket_connect(impl->websocket, c, addr, port, path); -} - -static void client_connected(struct client *c, struct pw_websocket_connection *conn) -{ - if (c->conn) { - spa_hook_remove(&c->conn_listener); - pw_websocket_connection_destroy(c->conn); - } - c->conn = conn; - if (conn) - pw_websocket_connection_add_listener(c->conn, &c->conn_listener, - &websocket_connection_events, c); -} - -struct match_info { - struct impl *impl; - const char *name; - struct pw_properties *props; - struct pw_websocket_connection *conn; - bool matched; -}; - -static int rule_matched(void *data, const char *location, const char *action, - const char *str, size_t len) -{ - struct match_info *i = data; - struct impl *impl = i->impl; - int res = 0; - - i->matched = true; - if (spa_streq(action, "create-stream")) { - struct client *c; - - pw_properties_update_string(i->props, str, len); - if ((c = client_new(impl, i->name, spa_steal_ptr(i->props))) == NULL) - return -errno; - if (i->conn) - client_connected(c, i->conn); - else - client_connect(c); - } - return res; -} - -static int match_client(struct impl *impl, const char *name, struct pw_properties *props, - struct pw_websocket_connection *conn) -{ - const char *str; - struct match_info minfo = { - .impl = impl, - .name = name, - .props = props, - .conn = conn, - }; - - if ((str = pw_properties_get(impl->props, "stream.rules")) == NULL) - str = DEFAULT_CREATE_RULES; - - pw_conf_match_rules(str, strlen(str), NAME, &props->dict, - rule_matched, &minfo); - - if (!minfo.matched) { - pw_log_info("unmatched client found %s", str); - if (conn) - pw_websocket_connection_destroy(conn); - pw_properties_free(props); - } - return minfo.matched; -} - -static void on_websocket_connected(void *data, void *user, - struct pw_websocket_connection *conn, const char *path) -{ - struct impl *impl = data; - struct client *c = user; - - pw_log_info("connected to %s", path); - if (c == NULL) { - struct sockaddr_storage addr; - char ip[128]; - uint16_t port = 0; - bool ipv4; - struct pw_properties *props; - - pw_websocket_connection_address(conn, - (struct sockaddr*)&addr, sizeof(addr)); - - props = pw_properties_copy(impl->stream_props); - if (pw_net_get_ip(&addr, ip, sizeof(ip), &ipv4, &port) >= 0) { - pw_properties_set(props, "sendspin.ip", ip); - pw_properties_setf(props, "sendspin.port", "%u", port); - } - pw_properties_set(props, "sendspin.path", path); - - match_client(impl, "", props, conn); - } else { - client_connected(c, conn); - } -} - -static const struct pw_websocket_events websocket_events = { - PW_VERSION_WEBSOCKET_EVENTS, - .connected = on_websocket_connected, -}; - -#ifdef HAVE_AVAHI -static struct client *client_find(struct impl *impl, const char *name) -{ - struct client *c; - spa_list_for_each(c, &impl->clients, link) { - if (spa_streq(c->name, name)) - return c; - } - return NULL; -} - -static void on_zeroconf_added(void *data, const void *user, const struct spa_dict *info) -{ - struct impl *impl = data; - const char *name, *addr, *port, *path; - struct client *c; - struct pw_properties *props; - - name = spa_dict_lookup(info, PW_KEY_ZEROCONF_NAME); - - if ((c = client_find(impl, name)) != NULL) - return; - - props = pw_properties_copy(impl->stream_props); - pw_properties_update(props, info); - - addr = spa_dict_lookup(info, PW_KEY_ZEROCONF_ADDRESS); - port = spa_dict_lookup(info, PW_KEY_ZEROCONF_PORT); - path = spa_dict_lookup(info, "path"); - - pw_properties_set(props, "sendspin.ip", addr); - pw_properties_set(props, "sendspin.port", port); - pw_properties_set(props, "sendspin.path", path); - - match_client(impl, name, props, NULL); -} - -static void on_zeroconf_removed(void *data, const void *user, const struct spa_dict *info) -{ - struct impl *impl = data; - const char *name; - struct client *c; - - name = spa_dict_lookup(info, PW_KEY_ZEROCONF_NAME); - - if ((c = client_find(impl, name)) == NULL) - return; - - client_free(c); -} - -static const struct pw_zeroconf_events zeroconf_events = { - PW_VERSION_ZEROCONF_EVENTS, - .added = on_zeroconf_added, - .removed = on_zeroconf_removed, -}; -#endif - -static void core_destroy(void *d) -{ - struct impl *impl = d; - spa_hook_remove(&impl->core_listener); - impl->core = NULL; - pw_impl_module_schedule_destroy(impl->module); -} - -static const struct pw_proxy_events core_proxy_events = { - .destroy = core_destroy, -}; - -static void impl_destroy(struct impl *impl) -{ - struct client *c; - spa_list_consume(c, &impl->clients, link) - client_free(c); - - if (impl->core && impl->do_disconnect) - pw_core_disconnect(impl->core); - - if (impl->data_loop) - pw_context_release_loop(impl->context, impl->data_loop); - -#ifdef HAVE_AVAHI - if (impl->zeroconf) - pw_zeroconf_destroy(impl->zeroconf); -#endif - - pw_properties_free(impl->stream_props); - pw_properties_free(impl->props); - - free(impl); -} - -static void module_destroy(void *d) -{ - struct impl *impl = d; - spa_hook_remove(&impl->module_listener); - impl_destroy(impl); -} - -static const struct pw_impl_module_events module_events = { - PW_VERSION_IMPL_MODULE_EVENTS, - .destroy = module_destroy, -}; - -static void on_core_error(void *d, uint32_t id, int seq, int res, const char *message) -{ - struct impl *impl = d; - - pw_log_error("error id:%u seq:%d res:%d (%s): %s", - id, seq, res, spa_strerror(res), message); - - if (id == PW_ID_CORE && res == -EPIPE) - pw_impl_module_schedule_destroy(impl->module); -} - -static const struct pw_core_events core_events = { - PW_VERSION_CORE_EVENTS, - .error = on_core_error, -}; - -static void copy_props(struct impl *impl, struct pw_properties *props, const char *key) -{ - const char *str; - if ((str = pw_properties_get(props, key)) != NULL) { - if (pw_properties_get(impl->stream_props, key) == NULL) - pw_properties_set(impl->stream_props, key, str); - } -} - -SPA_EXPORT -int pipewire__module_init(struct pw_impl_module *module, const char *args) -{ - struct pw_context *context = pw_impl_module_get_context(module); - struct impl *impl; - const char *str, *hostname, *port, *path; - struct pw_properties *props, *stream_props; - int res = 0; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - if (args == NULL) - args = ""; - - props = impl->props = pw_properties_new_string(args); - stream_props = impl->stream_props = pw_properties_new(NULL, NULL); - if (props == NULL || stream_props == NULL) { - res = -errno; - pw_log_error( "can't create properties: %m"); - goto out; - } - - impl->module = module; - impl->context = context; - impl->main_loop = pw_context_get_main_loop(context); - impl->data_loop = pw_context_acquire_loop(context, &props->dict); - impl->timer_queue = pw_context_get_timer_queue(context); - spa_list_init(&impl->clients); - - pw_properties_set(props, PW_KEY_NODE_LOOP_NAME, impl->data_loop->name); - - if ((str = pw_properties_get(props, "stream.props")) != NULL) - pw_properties_update_string(stream_props, str, strlen(str)); - - copy_props(impl, props, PW_KEY_NODE_LOOP_NAME); - copy_props(impl, props, SPA_KEY_AUDIO_LAYOUT); - copy_props(impl, props, SPA_KEY_AUDIO_POSITION); - copy_props(impl, props, PW_KEY_NODE_NAME); - copy_props(impl, props, PW_KEY_NODE_DESCRIPTION); - copy_props(impl, props, PW_KEY_NODE_GROUP); - copy_props(impl, props, PW_KEY_NODE_LATENCY); - copy_props(impl, props, PW_KEY_NODE_VIRTUAL); - copy_props(impl, props, PW_KEY_NODE_CHANNELNAMES); - copy_props(impl, props, PW_KEY_MEDIA_NAME); - copy_props(impl, props, PW_KEY_MEDIA_CLASS); - - impl->always_process = pw_properties_get_bool(stream_props, - PW_KEY_NODE_ALWAYS_PROCESS, true); - - if ((str = pw_properties_get(props, "sendspin.group-id")) == NULL) { - uint64_t group_id; - pw_random(&group_id, sizeof(group_id)); - pw_properties_setf(props, "sendspin.group-id", "%016"PRIx64, group_id); - } - if ((str = pw_properties_get(props, "sendspin.group-name")) == NULL) - pw_properties_set(props, "sendspin.group-name", "PipeWire"); - if ((str = pw_properties_get(props, "sendspin.server-name")) == NULL) - pw_properties_set(props, "sendspin.server-name", pw_get_host_name()); - if ((str = pw_properties_get(props, "sendspin.server-id")) == NULL) - pw_properties_setf(props, "sendspin.server-id", "pipewire-%s", pw_get_host_name()); - - impl->core = pw_context_get_object(impl->context, PW_TYPE_INTERFACE_Core); - if (impl->core == NULL) { - str = pw_properties_get(props, PW_KEY_REMOTE_NAME); - impl->core = pw_context_connect(impl->context, - pw_properties_new( - PW_KEY_REMOTE_NAME, str, - NULL), - 0); - impl->do_disconnect = true; - } - if (impl->core == NULL) { - res = -errno; - pw_log_error("can't connect: %m"); - goto out; - } - - pw_proxy_add_listener((struct pw_proxy*)impl->core, - &impl->core_proxy_listener, - &core_proxy_events, impl); - pw_core_add_listener(impl->core, - &impl->core_listener, - &core_events, impl); - - impl->websocket = pw_websocket_new(impl->main_loop, &props->dict); - pw_websocket_add_listener(impl->websocket, &impl->websocket_listener, - &websocket_events, impl); - - if ((str = pw_properties_get(props, "sendspin.delay")) == NULL) - str = SPA_STRINGIFY(DEFAULT_SENDSPIN_DELAY); - impl->delay = pw_properties_parse_float(str); - -#ifdef HAVE_AVAHI - if ((impl->zeroconf = pw_zeroconf_new(context, NULL)) != NULL) { - pw_zeroconf_add_listener(impl->zeroconf, &impl->zeroconf_listener, - &zeroconf_events, impl); - } -#endif - - hostname = pw_properties_get(props, "sendspin.ip"); - if (hostname != NULL) { - struct spa_json iter; - char v[256]; - - port = pw_properties_get(props, "sendspin.port"); - if (port == NULL) - port = SPA_STRINGIFY(DEFAULT_CLIENT_PORT); - if ((path = pw_properties_get(props, "sendspin.path")) == NULL) - path = DEFAULT_SENDSPIN_PATH; - - if (spa_json_begin_array_relax(&iter, hostname, strlen(hostname)) <= 0) { - res = -EINVAL; - pw_log_error("can't parse sendspin.ip %s", hostname); - goto out; - } - while (spa_json_get_string(&iter, v, sizeof(v)) > 0) { - struct client *c; - struct pw_properties *p = pw_properties_copy(impl->stream_props); - - pw_properties_set(p, "sendspin.ip", v); - pw_properties_set(p, "sendspin.port", port); - pw_properties_set(p, "sendspin.path", path); - - if ((c = client_new(impl, "", p)) != NULL) - client_connect(c); - } - } else { - if ((hostname = pw_properties_get(props, "source.ip")) == NULL) - hostname = DEFAULT_SOURCE_IP; - if ((port = pw_properties_get(props, "source.port")) == NULL) - port = SPA_STRINGIFY(DEFAULT_SOURCE_PORT); - if ((path = pw_properties_get(props, "source.path")) == NULL) - path = DEFAULT_SOURCE_PATH; - - pw_websocket_listen(impl->websocket, NULL, hostname, port, path); - -#ifdef HAVE_AVAHI - if (impl->zeroconf) { - str = pw_properties_get(props, "sendspin.group-name"); - pw_zeroconf_set_announce(impl->zeroconf, NULL, - &SPA_DICT_ITEMS( - SPA_DICT_ITEM(PW_KEY_ZEROCONF_TYPE, PW_SENDSPIN_SERVER_SERVICE_TYPE), - SPA_DICT_ITEM(PW_KEY_ZEROCONF_NAME, str), - SPA_DICT_ITEM(PW_KEY_ZEROCONF_PORT, port), - SPA_DICT_ITEM("path", path))); - } -#endif - } -#ifdef HAVE_AVAHI - if (impl->zeroconf) { - pw_zeroconf_set_browse(impl->zeroconf, NULL, - &SPA_DICT_ITEMS( - SPA_DICT_ITEM(PW_KEY_ZEROCONF_TYPE, PW_SENDSPIN_CLIENT_SERVICE_TYPE))); - } -#endif - pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); - - pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_info)); - - pw_log_info("Successfully loaded module-sendspin-send"); - - return 0; -out: - impl_destroy(impl); - return res; -} diff --git a/src/modules/module-sendspin/regress.h b/src/modules/module-sendspin/regress.h deleted file mode 100644 index 1b6919906..000000000 --- a/src/modules/module-sendspin/regress.h +++ /dev/null @@ -1,58 +0,0 @@ - -struct spa_regress { - double meanX; - double meanY; - double varX; - double covXY; - uint32_t n; - uint32_t m; - double a; -}; - -static inline void spa_regress_init(struct spa_regress *r, uint32_t m) -{ - memset(r, 0, sizeof(*r)); - r->m = m; - r->a = 1.0/m; -} -static inline void spa_regress_update(struct spa_regress *r, double x, double y) -{ - double a, dx, dy; - - if (r->n == 0) { - r->meanX = x; - r->meanY = y; - r->n++; - a = 1.0; - } else if (r->n < r->m) { - a = 1.0/r->n; - r->n++; - } else { - a = r->a; - } - dx = x - r->meanX; - dy = y - r->meanY; - - r->varX += ((1.0 - a) * dx * dx - r->varX) * a; - r->covXY += ((1.0 - a) * dx * dy - r->covXY) * a; - r->meanX += dx * a; - r->meanY += dy * a; -} -static inline void spa_regress_get(struct spa_regress *r, double *a, double *b) -{ - *a = r->covXY/r->varX; - *b = r->meanY - *a * r->meanX; -} -static inline double spa_regress_calc_y(struct spa_regress *r, double x) -{ - double a, b; - spa_regress_get(r, &a, &b); - return x * a + b; -} -static inline double spa_regress_calc_x(struct spa_regress *r, double y) -{ - double a, b; - spa_regress_get(r, &a, &b); - return (y - b) / a; -} - diff --git a/src/modules/module-sendspin/sendspin.h b/src/modules/module-sendspin/sendspin.h deleted file mode 100644 index dab9d36f3..000000000 --- a/src/modules/module-sendspin/sendspin.h +++ /dev/null @@ -1,27 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2026 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#ifndef PIPEWIRE_SENDSPIN_H -#define PIPEWIRE_SENDSPIN_H - -#include - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define PW_SENDSPIN_SERVER_SERVICE_TYPE "_sendspin-server._tcp" -#define PW_SENDSPIN_CLIENT_SERVICE_TYPE "_sendspin._tcp" - -#define PW_SENDSPIN_DEFAULT_SERVER_PORT 8927 -#define PW_SENDSPIN_DEFAULT_CLIENT_PORT 8928 -#define PW_SENDSPIN_DEFAULT_PATH "/sendspin" - -#ifdef __cplusplus -} -#endif - -#endif /* PIPEWIRE_SENDSPIN_H */ diff --git a/src/modules/module-sendspin/teeny-sha1.c b/src/modules/module-sendspin/teeny-sha1.c deleted file mode 100644 index fa5a56753..000000000 --- a/src/modules/module-sendspin/teeny-sha1.c +++ /dev/null @@ -1,201 +0,0 @@ -/******************************************************************************* - * Teeny SHA-1 - * - * The below sha1digest() calculates a SHA-1 hash value for a - * specified data buffer and generates a hex representation of the - * result. This implementation is a re-forming of the SHA-1 code at - * https://github.com/jinqiangshou/EncryptionLibrary. - * - * Copyright (c) 2017 CTrabant - * - * License: MIT, see included LICENSE file for details. - * - * To use the sha1digest() function either copy it into an existing - * project source code file or include this file in a project and put - * the declaration (example below) in the sources files where needed. - ******************************************************************************/ - -#include -#include -#include -#include - -/* Declaration: -extern int sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, size_t databytes); -*/ - -/******************************************************************************* - * sha1digest: https://github.com/CTrabant/teeny-sha1 - * - * Calculate the SHA-1 value for supplied data buffer and generate a - * text representation in hexadecimal. - * - * Based on https://github.com/jinqiangshou/EncryptionLibrary, credit - * goes to @jinqiangshou, all new bugs are mine. - * - * @input: - * data -- data to be hashed - * databytes -- bytes in data buffer to be hashed - * - * @output: - * digest -- the result, MUST be at least 20 bytes - * hexdigest -- the result in hex, MUST be at least 41 bytes - * - * At least one of the output buffers must be supplied. The other, if not - * desired, may be set to NULL. - * - * @return: 0 on success and non-zero on error. - ******************************************************************************/ -static inline int -sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, size_t databytes) -{ -#define SHA1ROTATELEFT(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) - - uint32_t W[80]; - uint32_t H[] = {0x67452301, - 0xEFCDAB89, - 0x98BADCFE, - 0x10325476, - 0xC3D2E1F0}; - uint32_t a; - uint32_t b; - uint32_t c; - uint32_t d; - uint32_t e; - uint32_t f = 0; - uint32_t k = 0; - - uint32_t idx; - uint32_t lidx; - uint32_t widx; - uint32_t didx = 0; - - int32_t wcount; - uint32_t temp; - uint64_t databits = ((uint64_t)databytes) * 8; - uint32_t loopcount = (databytes + 8) / 64 + 1; - uint32_t tailbytes = 64 * loopcount - databytes; - uint8_t datatail[128] = {0}; - - if (!digest && !hexdigest) - return -1; - - if (!data) - return -1; - - /* Pre-processing of data tail (includes padding to fill out 512-bit chunk): - Add bit '1' to end of message (big-endian) - Add 64-bit message length in bits at very end (big-endian) */ - datatail[0] = 0x80; - datatail[tailbytes - 8] = (uint8_t) (databits >> 56 & 0xFF); - datatail[tailbytes - 7] = (uint8_t) (databits >> 48 & 0xFF); - datatail[tailbytes - 6] = (uint8_t) (databits >> 40 & 0xFF); - datatail[tailbytes - 5] = (uint8_t) (databits >> 32 & 0xFF); - datatail[tailbytes - 4] = (uint8_t) (databits >> 24 & 0xFF); - datatail[tailbytes - 3] = (uint8_t) (databits >> 16 & 0xFF); - datatail[tailbytes - 2] = (uint8_t) (databits >> 8 & 0xFF); - datatail[tailbytes - 1] = (uint8_t) (databits >> 0 & 0xFF); - - /* Process each 512-bit chunk */ - for (lidx = 0; lidx < loopcount; lidx++) - { - /* Compute all elements in W */ - memset (W, 0, 80 * sizeof (uint32_t)); - - /* Break 512-bit chunk into sixteen 32-bit, big endian words */ - for (widx = 0; widx <= 15; widx++) - { - wcount = 24; - - /* Copy byte-per byte from specified buffer */ - while (didx < databytes && wcount >= 0) - { - W[widx] += (((uint32_t)data[didx]) << wcount); - didx++; - wcount -= 8; - } - /* Fill out W with padding as needed */ - while (wcount >= 0) - { - W[widx] += (((uint32_t)datatail[didx - databytes]) << wcount); - didx++; - wcount -= 8; - } - } - - /* Extend the sixteen 32-bit words into eighty 32-bit words, with potential optimization from: - "Improving the Performance of the Secure Hash Algorithm (SHA-1)" by Max Locktyukhin */ - for (widx = 16; widx <= 31; widx++) - { - W[widx] = SHA1ROTATELEFT ((W[widx - 3] ^ W[widx - 8] ^ W[widx - 14] ^ W[widx - 16]), 1); - } - for (widx = 32; widx <= 79; widx++) - { - W[widx] = SHA1ROTATELEFT ((W[widx - 6] ^ W[widx - 16] ^ W[widx - 28] ^ W[widx - 32]), 2); - } - - /* Main loop */ - a = H[0]; - b = H[1]; - c = H[2]; - d = H[3]; - e = H[4]; - - for (idx = 0; idx <= 79; idx++) - { - if (idx <= 19) - { - f = (b & c) | ((~b) & d); - k = 0x5A827999; - } - else if (idx >= 20 && idx <= 39) - { - f = b ^ c ^ d; - k = 0x6ED9EBA1; - } - else if (idx >= 40 && idx <= 59) - { - f = (b & c) | (b & d) | (c & d); - k = 0x8F1BBCDC; - } - else if (idx >= 60 && idx <= 79) - { - f = b ^ c ^ d; - k = 0xCA62C1D6; - } - temp = SHA1ROTATELEFT (a, 5) + f + e + k + W[idx]; - e = d; - d = c; - c = SHA1ROTATELEFT (b, 30); - b = a; - a = temp; - } - - H[0] += a; - H[1] += b; - H[2] += c; - H[3] += d; - H[4] += e; - } - - /* Store binary digest in supplied buffer */ - if (digest) - { - for (idx = 0; idx < 5; idx++) - { - digest[idx * 4 + 0] = (uint8_t) (H[idx] >> 24); - digest[idx * 4 + 1] = (uint8_t) (H[idx] >> 16); - digest[idx * 4 + 2] = (uint8_t) (H[idx] >> 8); - digest[idx * 4 + 3] = (uint8_t) (H[idx]); - } - } - - /* Store hex version of digest in supplied buffer */ - if (hexdigest) - { - snprintf (hexdigest, 41, "%08x%08x%08x%08x%08x", - H[0],H[1],H[2],H[3],H[4]); - } - - return 0; -} /* End of sha1digest() */ diff --git a/src/modules/module-sendspin/websocket.c b/src/modules/module-sendspin/websocket.c deleted file mode 100644 index 5730a5c87..000000000 --- a/src/modules/module-sendspin/websocket.c +++ /dev/null @@ -1,1072 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "config.h" - -#include "websocket.h" -#include "teeny-sha1.c" -#include "../network-utils.h" -#include "../module-raop/base64.h" - -#define pw_websocket_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_websocket_events, m, v, ##__VA_ARGS__) -#define pw_websocket_emit_destroy(w) pw_websocket_emit(w, destroy, 0) -#define pw_websocket_emit_connected(w,u,c,p) pw_websocket_emit(w, connected, 0, u, c, p) - -#define pw_websocket_connection_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_websocket_connection_events, m, v, ##__VA_ARGS__) -#define pw_websocket_connection_emit_destroy(w) pw_websocket_connection_emit(w, destroy, 0) -#define pw_websocket_connection_emit_error(w,r,m) pw_websocket_connection_emit(w, error, 0, r, m) -#define pw_websocket_connection_emit_disconnected(w) pw_websocket_connection_emit(w, disconnected, 0) -#define pw_websocket_connection_emit_drained(w) pw_websocket_connection_emit(w, drained, 0) -#define pw_websocket_connection_emit_message(w,...) pw_websocket_connection_emit(w, message, 0, __VA_ARGS__) - -#define MAX_CONNECTIONS 64 - -struct message { - struct spa_list link; - size_t len; - size_t offset; - uint32_t seq; - int (*reply) (void *user_data, int status); - void *user_data; - unsigned char data[]; -}; - -struct server { - struct pw_websocket *ws; - struct spa_list link; - - struct sockaddr_storage addr; - struct spa_source *source; - - void *user; - char **paths; - - struct spa_list connections; - uint32_t n_connections; -}; - -struct pw_websocket_connection { - struct pw_websocket *ws; - struct spa_list link; - - int refcount; - - void *user; - struct server *server; - - struct spa_hook_list listener_list; - - struct spa_source *source; - unsigned int connecting:1; - unsigned int need_flush:1; - - char *host; - char *path; - char name[128]; - bool ipv4; - uint16_t port; - - struct sockaddr_storage addr; - - uint8_t maskbit; - - int status; - char message[128]; - char key[25]; - size_t content_length; - - uint32_t send_seq; - uint32_t recv_seq; - bool draining; - - struct spa_list messages; - struct spa_list pending; - - struct pw_array data; - size_t data_wanted; - size_t data_cursor; - size_t data_state; - int (*have_data) (struct pw_websocket_connection *conn, - void *data, size_t size, size_t current); -}; - -struct pw_websocket { - struct pw_loop *loop; - - struct spa_hook_list listener_list; - - struct spa_source *source; - - char *ifname; - char *ifaddress; - char *user_agent; - char *server_name; - - struct spa_list connections; - struct spa_list servers; -}; - -void pw_websocket_connection_disconnect(struct pw_websocket_connection *conn, bool drain) -{ - struct message *msg; - - if (drain && !spa_list_is_empty(&conn->messages)) { - conn->draining = true; - return; - } - - if (conn->source != NULL) { - pw_loop_destroy_source(conn->ws->loop, conn->source); - conn->source = NULL; - } - spa_list_insert_list(&conn->messages, &conn->pending); - spa_list_consume(msg, &conn->messages, link) { - spa_list_remove(&msg->link); - free(msg); - } - if (conn->server) { - conn->server->n_connections--; - conn->server = NULL; - } - pw_websocket_connection_emit_disconnected(conn); -} - -static void websocket_connection_unref(struct pw_websocket_connection *conn) -{ - if (--conn->refcount > 0) - return; - pw_array_clear(&conn->data); - free(conn->host); - free(conn->path); - free(conn); -} - -void pw_websocket_connection_destroy(struct pw_websocket_connection *conn) -{ - pw_log_debug("destroy connection %p", conn); - spa_list_remove(&conn->link); - - pw_websocket_connection_emit_destroy(conn); - - pw_websocket_connection_disconnect(conn, false); - spa_hook_list_clean(&conn->listener_list); - - websocket_connection_unref(conn); -} - -void pw_websocket_connection_add_listener(struct pw_websocket_connection *conn, - struct spa_hook *listener, - const struct pw_websocket_connection_events *events, void *data) -{ - spa_hook_list_append(&conn->listener_list, listener, events, data); -} - -struct pw_websocket *pw_websocket_new(struct pw_loop *main_loop, struct spa_dict *props) -{ - struct pw_websocket *ws; - uint32_t i; - - if ((ws = calloc(1, sizeof(*ws))) == NULL) - return NULL; - - for (i = 0; props && i < props->n_items; i++) { - const char *k = props->items[i].key; - const char *s = props->items[i].value; - if (spa_streq(k, "local.ifname")) - ws->ifname = s ? strdup(s) : NULL; - if (spa_streq(k, "local.ifaddress")) - ws->ifaddress = s ? strdup(s) : NULL; - if (spa_streq(k, "http.user-agent")) - ws->user_agent = s ? strdup(s) : NULL; - if (spa_streq(k, "http.server-name")) - ws->server_name = s ? strdup(s) : NULL; - } - if (ws->user_agent == NULL) - ws->user_agent = spa_aprintf("PipeWire/%s", PACKAGE_VERSION); - if (ws->server_name == NULL) - ws->server_name = spa_aprintf("PipeWire/%s", PACKAGE_VERSION); - - ws->loop = main_loop; - spa_hook_list_init(&ws->listener_list); - - spa_list_init(&ws->connections); - spa_list_init(&ws->servers); - return ws; -} - -static void server_free(struct server *server) -{ - struct pw_websocket *ws = server->ws; - struct pw_websocket_connection *conn; - - pw_log_debug("%p: free server %p", ws, server); - - spa_list_remove(&server->link); - spa_list_consume(conn, &server->connections, link) - pw_websocket_connection_destroy(conn); - if (server->source) - pw_loop_destroy_source(ws->loop, server->source); - pw_free_strv(server->paths); - free(server); -} - -void pw_websocket_destroy(struct pw_websocket *ws) -{ - struct server *server; - struct pw_websocket_connection *conn; - - pw_log_info("destroy sebsocket %p", ws); - pw_websocket_emit_destroy(ws); - - spa_list_consume(server, &ws->servers, link) - server_free(server); - spa_list_consume(conn, &ws->connections, link) - pw_websocket_connection_destroy(conn); - - spa_hook_list_clean(&ws->listener_list); - free(ws->ifname); - free(ws->ifaddress); - free(ws->user_agent); - free(ws->server_name); - free(ws); -} - -void pw_websocket_add_listener(struct pw_websocket *ws, - struct spa_hook *listener, - const struct pw_websocket_events *events, void *data) -{ - spa_hook_list_append(&ws->listener_list, listener, events, data); -} - -static int update_io(struct pw_websocket_connection *conn, int io, bool active) -{ - if (conn->source) { - uint32_t mask = conn->source->mask; - SPA_FLAG_UPDATE(mask, io, active); - if (mask != conn->source->mask) - pw_loop_update_io(conn->ws->loop, conn->source, mask); - } - return 0; -} - -static int receiver_expect(struct pw_websocket_connection *conn, size_t wanted, - int (*have_data) (struct pw_websocket_connection *conn, - void *data, size_t size, size_t current)) -{ - pw_array_reset(&conn->data); - conn->data_wanted = wanted; - conn->data_cursor = 0; - conn->data_state = 0; - conn->have_data = have_data; - return update_io(conn, SPA_IO_IN, wanted); -} - -static int queue_message(struct pw_websocket_connection *conn, struct message *msg) -{ - spa_list_append(&conn->messages, &msg->link); - conn->need_flush = true; - return update_io(conn, SPA_IO_OUT, true); -} - -static int receive_websocket(struct pw_websocket_connection *conn, - void *data, size_t size, size_t current) -{ - uint8_t *d = data; - int need = 0, header = 0, i; - if (conn->data_state == 0) { - /* header done */ - conn->status = d[0] & 0xf; - if (d[1] & 0x80) - header += 4; - if ((d[1] & 0x7f) == 126) - header += 2; - else if ((d[1] & 0x7f) == 127) - header += 8; - else - need += d[1] & 0x7f; - conn->data_cursor = 2 + header; - need += header; - conn->data_state++; - } - else if (conn->data_state == 1) { - /* extra length and mask */ - size_t payload_len = 0; - if ((d[1] & 0x7f) == 126) - header = 2; - else if ((d[1] & 0x7f) == 127) - header = 8; - for (i = 0; i < header; i++) - payload_len = (payload_len << 8) | d[i + 2]; - if (payload_len > (size_t)(INT_MAX - need)) - return -EMSGSIZE; - need += (int)payload_len; - conn->data_state++; - } - if (need == 0) { - uint8_t *payload = &d[conn->data_cursor]; - size_t i, payload_size = conn->data.size - conn->data_cursor; - struct iovec iov[1] = {{ payload, payload_size }}; - - if (d[1] & 0x80) { - uint8_t *mask = &d[conn->data_cursor - 4]; - for (i = 0; i < payload_size; i++) - payload[i] ^= mask[i & 3]; - } - - switch (conn->status) { - case PW_WEBSOCKET_OPCODE_PING: - pw_log_info("received ping"); - pw_websocket_connection_send(conn, PW_WEBSOCKET_OPCODE_PONG, iov, 1); - break; - case PW_WEBSOCKET_OPCODE_CLOSE: - pw_log_info("received close"); - pw_websocket_connection_send(conn, PW_WEBSOCKET_OPCODE_CLOSE, iov, 1); - pw_websocket_connection_disconnect(conn, true); - break; - default: - pw_log_debug("received message %02x", conn->status); - pw_websocket_connection_emit_message(conn, conn->status, - payload, payload_size); - } - receiver_expect(conn, 2, receive_websocket); - } - return need; -} - -static int connection_upgrade_failed(struct pw_websocket_connection *conn, - int status, const char *message) -{ - FILE *f; - size_t len; - struct message *msg; - - if ((f = open_memstream((char**)&msg, &len)) == NULL) - return -errno; - - fseek(f, offsetof(struct message, data), SEEK_SET); - fprintf(f, "HTTP/1.1 %d %s\r\n", status, message); - fprintf(f, "Transfer-Encoding: chunked\r\n"); - fprintf(f, "Content-Type: application/octet-stream\r\n"); - fprintf(f, "Server: %s\r\n", conn->ws->server_name); - fprintf(f, "\r\n"); - fclose(f); - - msg->len = len - offsetof(struct message, data); - pw_log_info("send error %d %s", status, message); - return queue_message(conn, msg); -} - -static void make_accept(const char *key, char *accept) -{ - static const char *str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - uint8_t tmp[24 + 36], sha1[20]; - memcpy(&tmp[ 0], key, 24); - memcpy(&tmp[24], str, 36); - sha1digest(sha1, NULL, tmp, sizeof(tmp)); - pw_base64_encode(sha1, sizeof(sha1), accept, '='); -} - -static int connection_upgraded_send(struct pw_websocket_connection *conn) -{ - FILE *f; - size_t len; - struct message *msg; - char accept[29]; - - if ((f = open_memstream((char**)&msg, &len)) == NULL) - return -errno; - - make_accept(conn->key, accept); - - fseek(f, offsetof(struct message, data), SEEK_SET); - fprintf(f, "HTTP/1.1 101 Switching Protocols\r\n"); - fprintf(f, "Upgrade: websocket\r\n"); - fprintf(f, "Connection: Upgrade\r\n"); - fprintf(f, "Sec-WebSocket-Accept: %s\r\n", accept); - fprintf(f, "\r\n"); - fclose(f); - - msg->len = len - offsetof(struct message, data); - pw_log_info("send upgrade %s", msg->data); - return queue_message(conn, msg); -} - -static int complete_upgrade(struct pw_websocket_connection *conn) -{ - pw_websocket_emit_connected(conn->ws, conn->user, conn, conn->path); - return receiver_expect(conn, 2, receive_websocket); -} - -static int header_key_val(char *buf, char **key, char **val) -{ - char *v; - *key = buf; - if ((v = strstr(buf, ":")) == NULL) - return -EPROTO; - *v++ = '\0'; - *val = pw_strip(v, " "); - return 0; -} - -static int receive_http_request(struct pw_websocket_connection *conn, - void *data, size_t size, size_t current) -{ - char *d = data, *l; - char c = d[current]; - int need = 1; - - if (conn->data_state == 0) { - if (c == '\n') { - int v1, v2; - d[current] = '\0'; - l = pw_strip(&d[conn->data_cursor], "\n\r "); - conn->data_cursor = current+1; - if (sscanf(l, "GET %ms HTTP/%d.%d", &conn->path, &v1, &v2) != 3) - return -EPROTO; - conn->data_state++; - } - } - else if (conn->data_state == 1) { - if (c == '\n') { - char *key, *val; - d[current] = '\0'; - l = pw_strip(&d[conn->data_cursor], "\n\r "); - if (strlen(l) > 0) { - conn->data_cursor = current+1; - if (header_key_val(l, &key, &val) < 0) - return -EPROTO; - if (spa_streq(key, "Sec-WebSocket-Key")) - strncpy(conn->key, val, sizeof(conn->key)-1); - } else { - conn->data_state++; - need = 0; - } - } - } - if (need == 0) { - if (conn->server && conn->server->paths && - pw_strv_find(conn->server->paths, conn->path) < 0) { - connection_upgrade_failed(conn, 404, "Not Found"); - } else { - connection_upgraded_send(conn); - complete_upgrade(conn); - } - } - return need; -} - -static struct message *find_pending(struct pw_websocket_connection *conn, uint32_t seq) -{ - struct message *msg; - spa_list_for_each(msg, &conn->pending, link) { - if (msg->seq == seq) - return msg; - } - return NULL; -} - -static int receive_http_reply(struct pw_websocket_connection *conn, - void *data, size_t size, size_t current) -{ - char *d = data, *l; - char c = d[current]; - int need = 1; - - if (conn->data_state == 0) { - if (c == '\n') { - int v1, v2, status, message; - /* status complete */ - d[current] = '\0'; - l = pw_strip(&d[conn->data_cursor], "\n\r "); - conn->data_cursor = current+1; - if (sscanf(l, "HTTP/%d.%d %n%d", &v1, &v2, &message, &status) != 3) - return -EPROTO; - conn->status = status; - snprintf(conn->message, sizeof(conn->message), "%s", &l[message]); - conn->content_length = 0; - conn->data_state++; - } - } - else if (conn->data_state == 1) { - if (c == '\n') { - /* header line complete */ - d[current] = '\0'; - l = pw_strip(&d[conn->data_cursor], "\n\r "); - conn->data_cursor = current+1; - if (strlen(l) > 0) { - char *key, *value; - if (header_key_val(l, &key, &value) < 0) - return -EPROTO; - if (spa_streq(key, "Sec-WebSocket-Accept")) { - char accept[29]; - make_accept(conn->key, accept); - if (!spa_streq(value, accept)) { - pw_log_error("got Accept:%s expected:%s", value, accept); - return -EPROTO; - } - } - else if (spa_streq(key, "Content-Length")) - conn->content_length = atoi(value); - } else { - conn->data_state++; - need = conn->content_length; - } - } - } - if (need == 0) { - /* message completed */ - uint32_t seq; - int res; - struct message *msg; - - seq = conn->recv_seq++; - - pw_log_info("received reply to request with seq:%" PRIu32, seq); - - if ((msg = find_pending(conn, seq)) != NULL) { - res = msg->reply(msg->user_data, conn->status); - spa_list_remove(&msg->link); - free(msg); - - if (res < 0) - pw_websocket_connection_emit_error(conn, res, conn->message); - } - } - return need; -} - -static int on_upgrade_reply(void *user_data, int status) -{ - struct pw_websocket_connection *conn = user_data; - if (status != 101) - return -EPROTO; - return complete_upgrade(conn); -} - -static int handle_connect(struct pw_websocket_connection *conn, int fd) -{ - int res = 0; - socklen_t res_len; - FILE *f; - size_t len; - struct message *msg; - uint8_t key[16]; - - len = sizeof(res); - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &res, &res_len) < 0) { - pw_log_error("getsockopt: %m"); - return -errno; - } - if (res != 0) - return -res; - - pw_log_info("connected to %s:%u", conn->name, conn->port); - - conn->connecting = false; - conn->status = 0; - - if ((f = open_memstream((char**)&msg, &len)) == NULL) - return -errno; - - fseek(f, offsetof(struct message, data), SEEK_SET); - - /* make a key */ - pw_random(key, sizeof(key)); - pw_base64_encode(key, sizeof(key), conn->key, '='); - - fprintf(f, "GET %s HTTP/1.1\r\n", conn->path); - fprintf(f, "Host: %s\r\n", conn->host); - fprintf(f, "Upgrade: websocket\r\n"); - fprintf(f, "Connection: Upgrade\r\n"); - fprintf(f, "Sec-WebSocket-Version: 13\r\n"); - fprintf(f, "Sec-WebSocket-Key: %s\r\n", conn->key); - fprintf(f, "Accept: */*\r\n"); - fprintf(f, "User-Agent: %s\r\n", conn->ws->user_agent); - fprintf(f, "\r\n"); - fclose(f); - - msg->len = len - offsetof(struct message, data); - msg->reply = on_upgrade_reply; - msg->user_data = conn; - msg->seq = conn->send_seq++; - - pw_log_info("%s", msg->data); - - receiver_expect(conn, 1, receive_http_reply); - - return queue_message(conn, msg); -} - -static int handle_input(struct pw_websocket_connection *conn) -{ - int res; - - while (conn->data.size < conn->data_wanted) { - size_t current = conn->data.size; - size_t pending = conn->data_wanted - current; - void *b; - - if (conn->source == NULL) - return -EPIPE; - - if ((res = pw_array_ensure_size(&conn->data, pending)) < 0) - return res; - b = SPA_PTROFF(conn->data.data, current, void); - - res = read(conn->source->fd, b, pending); - if (res == 0) - return 0; - if (res < 0) { - res = -errno; - if (res == -EINTR) - continue; - if (res != -EAGAIN && res != -EWOULDBLOCK) - return res; - return -EAGAIN; - } - conn->data.size += res; - if (conn->data.size == conn->data_wanted) { - if ((res = conn->have_data(conn, - conn->data.data, - conn->data.size, - current)) < 0) - return res; - - if (conn->data_wanted > SIZE_MAX - res) - return -EOVERFLOW; - conn->data_wanted += res; - } - } - return 0; -} - -static int flush_output(struct pw_websocket_connection *conn) -{ - int res; - - conn->need_flush = false; - - if (conn->source == NULL) - return -EPIPE; - - while (true) { - struct message *msg; - void *data; - size_t size; - - if (spa_list_is_empty(&conn->messages)) { - if (conn->draining) - pw_websocket_connection_disconnect(conn, false); - break; - } - msg = spa_list_first(&conn->messages, struct message, link); - - if (msg->offset < msg->len) { - data = SPA_PTROFF(msg->data, msg->offset, void); - size = msg->len - msg->offset; - } else { - spa_list_remove(&msg->link); - if (msg->reply != NULL) - spa_list_append(&conn->pending, &msg->link); - else - free(msg); - continue; - } - - while (true) { - res = send(conn->source->fd, data, size, MSG_NOSIGNAL | MSG_DONTWAIT); - if (res < 0) { - res = -errno; - if (res == -EINTR) - continue; - if (res != -EAGAIN && res != -EWOULDBLOCK) - pw_log_warn("conn %p: send %zu, error %d: %m", - conn, size, res); - return res; - } - msg->offset += res; - break; - } - } - return 0; -} - -static void -on_source_io(void *data, int fd, uint32_t mask) -{ - struct pw_websocket_connection *conn = data; - int res; - - conn->refcount++; - - if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { - 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) { - if ((res = handle_input(conn)) != -EAGAIN) - goto error; - } - if (mask & SPA_IO_OUT || conn->need_flush) { - if (conn->connecting) { - if ((res = handle_connect(conn, fd)) < 0) - goto error; - } - res = flush_output(conn); - if (res >= 0) { - if (conn->source) - pw_loop_update_io(conn->ws->loop, conn->source, - conn->source->mask & ~SPA_IO_OUT); - } else if (res != -EAGAIN) - goto error; - } -done: - websocket_connection_unref(conn); - return; -error: - if (res < 0) { - pw_log_error("%p: %s got connection error %d (%s)", conn, - conn->name, res, spa_strerror(res)); - snprintf(conn->message, sizeof(conn->message), "%s", spa_strerror(res)); - pw_websocket_connection_emit_error(conn, res, conn->message); - } else { - pw_log_info("%p: %s connection closed", conn, conn->name); - } - pw_websocket_connection_disconnect(conn, false); - goto done; -} - -int pw_websocket_connection_address(struct pw_websocket_connection *conn, - struct sockaddr *addr, socklen_t addr_len) -{ - memcpy(addr, &conn->addr, SPA_MIN(addr_len, sizeof(conn->addr))); - return 0; -} - -static struct pw_websocket_connection *connection_new(struct pw_websocket *ws, void *user, - struct sockaddr *addr, socklen_t addr_len, int fd, struct server *server) -{ - struct pw_websocket_connection *conn; - - if ((conn = calloc(1, sizeof(*conn))) == NULL) - goto error; - - if ((conn->source = pw_loop_add_io(ws->loop, spa_steal_fd(fd), - SPA_IO_ERR | SPA_IO_HUP | SPA_IO_OUT, - true, on_source_io, conn)) == NULL) - goto error; - - memcpy(&conn->addr, addr, SPA_MIN(addr_len, sizeof(conn->addr))); - conn->ws = ws; - conn->server = server; - conn->user = user; - if (server) - spa_list_append(&server->connections, &conn->link); - else - spa_list_append(&ws->connections, &conn->link); - - conn->refcount = 1; - if (pw_net_get_ip(&conn->addr, conn->name, sizeof(conn->name), - &conn->ipv4, &conn->port) < 0) - snprintf(conn->name, sizeof(conn->name), "connection %p", conn); - - spa_list_init(&conn->messages); - spa_list_init(&conn->pending); - spa_hook_list_init(&conn->listener_list); - pw_array_init(&conn->data, 4096); - - pw_log_debug("new websocket %p connection %p %s:%u", ws, - conn, conn->name, conn->port); - - return conn; -error: - if (fd != -1) - close(fd); - free(conn); - return NULL; -} - -static int make_tcp_socket(struct server *server, const char *name, uint16_t port, const char *ifname, - const char *ifaddress) -{ - struct sockaddr_storage addr; - int res, on; - socklen_t len = 0; - spa_autoclose int fd = -1; - - if ((res = pw_net_parse_address_port(name, ifaddress, port, &addr, &len)) < 0) { - pw_log_error("%p: can't parse address %s: %s", server, - name, spa_strerror(res)); - goto error; - } - - if ((fd = socket(addr.ss_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) < 0) { - res = -errno; - pw_log_error("%p: socket() failed: %m", server); - goto error; - } -#ifdef SO_BINDTODEVICE - if (ifname && setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)) < 0) { - res = -errno; - pw_log_error("%p: setsockopt(SO_BINDTODEVICE) failed: %m", server); - goto error; - } -#endif - on = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void *) &on, sizeof(on)) < 0) - pw_log_warn("%p: setsockopt(): %m", server); - - if (bind(fd, (struct sockaddr *) &addr, len) < 0) { - res = -errno; - pw_log_error("%p: bind() failed: %m", server); - goto error; - } - if (listen(fd, 5) < 0) { - res = -errno; - pw_log_error("%p: listen() failed: %m", server); - goto error; - } - if (getsockname(fd, (struct sockaddr *)&addr, &len) < 0) { - res = -errno; - pw_log_error("%p: getsockname() failed: %m", server); - goto error; - } - - server->addr = addr; - - return spa_steal_fd(fd); -error: - return res; -} - -static void -on_server_connect(void *data, int fd, uint32_t mask) -{ - struct server *server = data; - struct pw_websocket *ws = server->ws; - struct sockaddr_storage addr; - socklen_t addrlen; - spa_autoclose int conn_fd = -1; - int val; - struct pw_websocket_connection *conn = NULL; - - addrlen = sizeof(addr); - if ((conn_fd = accept4(fd, (struct sockaddr*)&addr, &addrlen, - SOCK_NONBLOCK | SOCK_CLOEXEC)) < 0) - goto error; - - if (server->n_connections >= MAX_CONNECTIONS) - goto error_refused; - - if ((conn = connection_new(ws, server->user, (struct sockaddr*)&addr, sizeof(addr), - spa_steal_fd(conn_fd), server)) == NULL) - goto error; - - server->n_connections++; - - pw_log_info("%p: connection:%p %s:%u connected", ws, - conn, conn->name, conn->port); - - val = 1; - if (setsockopt(conn->source->fd, IPPROTO_TCP, TCP_NODELAY, - (const void *) &val, sizeof(val)) < 0) - pw_log_warn("TCP_NODELAY failed: %m"); - - val = IPTOS_LOWDELAY; - if (setsockopt(conn->source->fd, IPPROTO_IP, IP_TOS, - (const void *) &val, sizeof(val)) < 0) - pw_log_warn("IP_TOS failed: %m"); - - receiver_expect(conn, 1, receive_http_request); - return; - -error_refused: - errno = ECONNREFUSED; -error: - pw_log_error("%p: failed to create connection: %m", ws); - return; -} - -int pw_websocket_listen(struct pw_websocket *ws, void *user, - const char *hostname, const char *service, const char *paths) -{ - int res; - struct server *server; - uint16_t port = atoi(service); - - if ((server = calloc(1, sizeof(struct server))) == NULL) - return -errno; - - server->ws = ws; - spa_list_append(&ws->servers, &server->link); - - server->user = user; - spa_list_init(&server->connections); - - if ((res = make_tcp_socket(server, hostname, port, ws->ifname, ws->ifaddress)) < 0) - goto error; - - if ((server->source = pw_loop_add_io(ws->loop, res, SPA_IO_IN, - true, on_server_connect, server)) == NULL) { - res = -errno; - goto error; - } - if (paths) - server->paths = pw_strv_parse(paths, strlen(paths), INT_MAX, NULL); - - pw_log_info("%p: listen %s:%u %s", ws, hostname, port, paths); - return 0; -error: - pw_log_error("%p: can't create server: %s", ws, spa_strerror(res)); - server_free(server); - return res; -} - -int pw_websocket_cancel(struct pw_websocket *ws, void *user) -{ - struct server *s, *ts; - struct pw_websocket_connection *c, *tc; - int count = 0; - - spa_list_for_each_safe(s, ts, &ws->servers, link) { - if (s->user == user) { - server_free(s); - count++; - } - } - spa_list_for_each_safe(c, tc, &ws->connections, link) { - if (c->user == user) { - pw_websocket_connection_destroy(c); - count++; - } - } - return count; -} - -int pw_websocket_connect(struct pw_websocket *ws, void *user, - const char *hostname, const char *service, const char *path) -{ - struct addrinfo hints; - struct addrinfo *result, *rp; - int res, fd = -1; - struct pw_websocket_connection *conn = NULL; - - memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = 0; - hints.ai_protocol = 0; - - if ((res = getaddrinfo(hostname, service, &hints, &result)) != 0) { - pw_log_error("getaddrinfo: %s", gai_strerror(res)); - return -EINVAL; - } - res = -ENOENT; - for (rp = result; rp != NULL; rp = rp->ai_next) { - if ((fd = socket(rp->ai_family, - rp->ai_socktype | SOCK_CLOEXEC | SOCK_NONBLOCK, - rp->ai_protocol)) == -1) - continue; - - res = connect(fd, rp->ai_addr, rp->ai_addrlen); - if (res == 0 || (res < 0 && errno == EINPROGRESS)) - break; - - res = -errno; - close(fd); - } - if (rp == NULL) { - pw_log_error("Could not connect to %s:%s: %s", hostname, service, - spa_strerror(res)); - } else { - if ((conn = connection_new(ws, user, rp->ai_addr, rp->ai_addrlen, fd, NULL)) == NULL) - res = -errno; - } - freeaddrinfo(result); - if (conn == NULL) - return res; - - conn->connecting = true; - conn->maskbit = 0x80; - conn->path = strdup(path); - asprintf(&conn->host, "%s:%s", hostname, service); - - pw_log_info("%p: connecting to %s:%u path:%s", conn, - conn->name, conn->port, path); - return 0; -} - -int pw_websocket_connection_send(struct pw_websocket_connection *conn, uint8_t opcode, - const struct iovec *iov, size_t iov_len) -{ - struct message *msg; - size_t len = 2, i, j, k; - uint8_t *d, *mask = NULL, maskbit = conn->maskbit; - size_t payload_length = 0; - - for (i = 0; i < iov_len; i++) { - if (payload_length > SIZE_MAX - iov[i].iov_len) - return -EOVERFLOW; - payload_length += iov[i].iov_len; - } - if (payload_length > SIZE_MAX - sizeof(*msg) - 14) - return -EOVERFLOW; - - if ((msg = calloc(1, sizeof(*msg) + 14 + payload_length)) == NULL) - return -errno; - - d = msg->data; - d[0] = 0x80 | opcode; - - if (payload_length < 126) - k = 0; - else if (payload_length < 65536) - k = 2; - else - k = 8; - - d[1] = maskbit | (k == 0 ? payload_length : (k == 2 ? 126 : 127)); - for (i = 0, j = (k-1)*8 ; i < k; i++, j -= 8) - d[len++] = (payload_length >> j) & 0xff; - - if (maskbit) { - mask = &d[len]; - pw_random(mask, 4); - len += 4; - } - for (i = 0, k = 0; i < iov_len; i++) { - if (maskbit) - for (j = 0; j < iov[i].iov_len; j++, k++) - d[len+j] = ((uint8_t*)iov[i].iov_base)[j] ^ mask[k & 3]; - else - memcpy(&d[len], iov[i].iov_base, iov[i].iov_len); - - len += iov[i].iov_len; - } - msg->len = len; - - return queue_message(conn, msg); -} - -int pw_websocket_connection_send_text(struct pw_websocket_connection *conn, - const char *payload, size_t payload_len) -{ - struct iovec iov[1] = {{ (void*)payload, payload_len }}; - pw_log_info("send text %.*s", (int)payload_len, payload); - return pw_websocket_connection_send(conn, PW_WEBSOCKET_OPCODE_TEXT, iov, 1); -} diff --git a/src/modules/module-sendspin/websocket.h b/src/modules/module-sendspin/websocket.h deleted file mode 100644 index 0f0da36e7..000000000 --- a/src/modules/module-sendspin/websocket.h +++ /dev/null @@ -1,85 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2026 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#ifndef PIPEWIRE_WEBSOCKET_H -#define PIPEWIRE_WEBSOCKET_H - -#include - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -struct pw_websocket; -struct pw_websocket_connection; - -#define PW_WEBSOCKET_OPCODE_TEXT 0x1 -#define PW_WEBSOCKET_OPCODE_BINARY 0x2 -#define PW_WEBSOCKET_OPCODE_CLOSE 0x8 -#define PW_WEBSOCKET_OPCODE_PING 0x9 -#define PW_WEBSOCKET_OPCODE_PONG 0xa - -struct pw_websocket_connection_events { -#define PW_VERSION_WEBSOCKET_CONNECTION_EVENTS 0 - uint32_t version; - - void (*destroy) (void *data); - void (*error) (void *data, int res, const char *reason); - void (*disconnected) (void *data); - - void (*message) (void *data, - int opcode, void *payload, size_t size); -}; - -void pw_websocket_connection_add_listener(struct pw_websocket_connection *conn, - struct spa_hook *listener, - const struct pw_websocket_connection_events *events, void *data); - -void pw_websocket_connection_destroy(struct pw_websocket_connection *conn); -void pw_websocket_connection_disconnect(struct pw_websocket_connection *conn, bool drain); - -int pw_websocket_connection_address(struct pw_websocket_connection *conn, - struct sockaddr *addr, socklen_t addr_len); - -int pw_websocket_connection_send(struct pw_websocket_connection *conn, - uint8_t opcode, const struct iovec *iov, size_t iov_len); - -int pw_websocket_connection_send_text(struct pw_websocket_connection *conn, - const char *payload, size_t payload_len); - - -struct pw_websocket_events { -#define PW_VERSION_WEBSOCKET_EVENTS 0 - uint32_t version; - - void (*destroy) (void *data); - - void (*connected) (void *data, void *user, - struct pw_websocket_connection *conn, const char *path); -}; - -struct pw_websocket * pw_websocket_new(struct pw_loop *main_loop, - struct spa_dict *props); - -void pw_websocket_destroy(struct pw_websocket *ws); - -void pw_websocket_add_listener(struct pw_websocket *ws, - struct spa_hook *listener, - const struct pw_websocket_events *events, void *data); - -int pw_websocket_connect(struct pw_websocket *ws, void *user, - const char *hostname, const char *service, const char *path); - -int pw_websocket_listen(struct pw_websocket *ws, void *user, - const char *hostname, const char *service, const char *paths); - -int pw_websocket_cancel(struct pw_websocket *ws, void *user); - -#ifdef __cplusplus -} -#endif - -#endif /* PIPEWIRE_WEBSOCKET_H */ diff --git a/src/modules/module-session-manager/client-endpoint/client-endpoint.c b/src/modules/module-session-manager/client-endpoint/client-endpoint.c index 2a64443e3..4eca40fd8 100644 --- a/src/modules/module-session-manager/client-endpoint/client-endpoint.c +++ b/src/modules/module-session-manager/client-endpoint/client-endpoint.c @@ -166,9 +166,9 @@ static void *create_object(void *data, if (!properties) goto no_mem; - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%u", + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_impl_client_get_info(owner)->id); - pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%u", + pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", pw_impl_factory_get_info(factory)->id); this->resource = pw_resource_new(owner, new_id, PW_PERM_ALL, type, version, 0); @@ -233,7 +233,7 @@ static void module_registered(void *data) char id[16]; int res; - snprintf(id, sizeof(id), "%u", pw_impl_module_get_info(module)->id); + snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id); items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id); pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1)); diff --git a/src/modules/module-session-manager/client-session/client-session.c b/src/modules/module-session-manager/client-session/client-session.c index fc6f5857b..bb0f011ea 100644 --- a/src/modules/module-session-manager/client-session/client-session.c +++ b/src/modules/module-session-manager/client-session/client-session.c @@ -165,9 +165,9 @@ static void *create_object(void *data, if (!properties) goto no_mem; - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%u", + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_impl_client_get_info(owner)->id); - pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%u", + pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", pw_impl_factory_get_info(factory)->id); this->resource = pw_resource_new(owner, new_id, PW_PERM_ALL, type, version, 0); @@ -232,7 +232,7 @@ static void module_registered(void *data) char id[16]; int res; - snprintf(id, sizeof(id), "%u", pw_impl_module_get_info(module)->id); + snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id); items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id); pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1)); diff --git a/src/modules/module-session-manager/endpoint-link.c b/src/modules/module-session-manager/endpoint-link.c index 7b21e0071..964c5d140 100644 --- a/src/modules/module-session-manager/endpoint-link.c +++ b/src/modules/module-session-manager/endpoint-link.c @@ -439,9 +439,9 @@ static void *create_object(void *data, goto error_link; } - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%u", + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_impl_client_get_info(client)->id); - pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%u", + pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", pw_impl_factory_get_info(d->factory)->id); result = link_new(pw_impl_client_get_context(client), impl_resource, properties); @@ -507,7 +507,7 @@ static void module_registered(void *data) char id[16]; int res; - snprintf(id, sizeof(id), "%u", pw_impl_module_get_info(module)->id); + snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id); items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id); pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1)); diff --git a/src/modules/module-session-manager/endpoint-stream.c b/src/modules/module-session-manager/endpoint-stream.c index 2ae855da5..c93db3767 100644 --- a/src/modules/module-session-manager/endpoint-stream.c +++ b/src/modules/module-session-manager/endpoint-stream.c @@ -430,9 +430,9 @@ static void *create_object(void *data, goto error_stream; } - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%u", + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_impl_client_get_info(client)->id); - pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%u", + pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", pw_impl_factory_get_info(d->factory)->id); result = stream_new(pw_impl_client_get_context(client), impl_resource, properties); @@ -498,7 +498,7 @@ static void module_registered(void *data) char id[16]; int res; - snprintf(id, sizeof(id), "%u", pw_impl_module_get_info(module)->id); + snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id); items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id); pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1)); diff --git a/src/modules/module-session-manager/endpoint.c b/src/modules/module-session-manager/endpoint.c index f67598bf1..32aa1b16b 100644 --- a/src/modules/module-session-manager/endpoint.c +++ b/src/modules/module-session-manager/endpoint.c @@ -439,9 +439,9 @@ static void *create_object(void *data, goto error_endpoint; } - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%u", + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_impl_client_get_info(client)->id); - pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%u", + pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", pw_impl_factory_get_info(d->factory)->id); result = endpoint_new(pw_impl_client_get_context(client), impl_resource, properties); @@ -507,7 +507,7 @@ static void module_registered(void *data) char id[16]; int res; - snprintf(id, sizeof(id), "%u", pw_impl_module_get_info(module)->id); + snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id); items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id); pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1)); diff --git a/src/modules/module-session-manager/session.c b/src/modules/module-session-manager/session.c index 6a0ed02d8..de1fb4360 100644 --- a/src/modules/module-session-manager/session.c +++ b/src/modules/module-session-manager/session.c @@ -428,9 +428,9 @@ static void *create_object(void *data, goto error_session; } - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%u", + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_impl_client_get_info(client)->id); - pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%u", + pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", pw_impl_factory_get_info(d->factory)->id); result = session_new(pw_impl_client_get_context(client), impl_resource, properties); @@ -495,7 +495,7 @@ static void module_registered(void *data) char id[16]; int res; - snprintf(id, sizeof(id), "%u", pw_impl_module_get_info(module)->id); + snprintf(id, sizeof(id), "%d", pw_impl_module_get_info(module)->id); items[0] = SPA_DICT_ITEM_INIT(PW_KEY_MODULE_ID, id); pw_impl_factory_update_properties(factory, &SPA_DICT_INIT(items, 1)); diff --git a/src/modules/module-snapcast-discover.c b/src/modules/module-snapcast-discover.c index 5c7896bcf..e929be9c0 100644 --- a/src/modules/module-snapcast-discover.c +++ b/src/modules/module-snapcast-discover.c @@ -29,8 +29,12 @@ #include #include +#include +#include +#include + #include "module-protocol-pulse/format.h" -#include "zeroconf-utils/zeroconf.h" +#include "module-zeroconf-discover/avahi-poll.h" #include "network-utils.h" @@ -171,8 +175,10 @@ struct impl { bool discover_local; struct pw_loop *loop; - struct pw_zeroconf *zeroconf; - struct spa_hook zeroconf_listener; + AvahiPoll *avahi_poll; + AvahiClient *client; + AvahiServiceBrowser *jsonrpc_browser; + AvahiServiceBrowser *ctrl_browser; struct spa_list tunnel_list; uint32_t id; @@ -200,6 +206,8 @@ struct tunnel { bool need_flush; }; +static int start_client(struct impl *impl); + static struct tunnel *make_tunnel(struct impl *impl, const struct tunnel_info *info) { struct tunnel *t; @@ -246,8 +254,14 @@ static void impl_free(struct impl *impl) spa_list_consume(t, &impl->tunnel_list, link) free_tunnel(t); - if (impl->zeroconf) - pw_zeroconf_destroy(impl->zeroconf); + if (impl->jsonrpc_browser) + avahi_service_browser_free(impl->jsonrpc_browser); + if (impl->ctrl_browser) + avahi_service_browser_free(impl->ctrl_browser); + if (impl->client) + avahi_client_free(impl->client); + if (impl->avahi_poll) + pw_avahi_poll_free(impl->avahi_poll); pw_properties_free(impl->properties); free(impl); } @@ -264,7 +278,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void pw_properties_from_zeroconf(const char *key, const char *value, +static void pw_properties_from_avahi_string(const char *key, const char *value, struct pw_properties *props) { } @@ -381,10 +395,7 @@ on_source_io(void *data, int fd, uint32_t mask) int res; if (mask & (SPA_IO_ERR | SPA_IO_HUP)) { - socklen_t len = sizeof(res); - if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &res, &len) < 0) - res = errno; - res = -res; + res = -EPIPE; goto error; } if (mask & SPA_IO_IN) { @@ -416,7 +427,7 @@ static int snapcast_connect(struct tunnel *t) { struct addrinfo hints; struct addrinfo *result, *rp; - int res, fd = -1; + int res, fd; char port_str[12]; if (t->server_address == NULL) @@ -605,26 +616,34 @@ static int rule_matched(void *data, const char *location, const char *action, return res; } -static void on_zeroconf_added(void *data, const void *user, const struct spa_dict *info) +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 = data; + struct impl *impl = userdata; struct tunnel_info tinfo; struct tunnel *t; - const char *name, *address, *str; + const char *str, *link_local_range = "169.254."; + AvahiStringList *l; struct pw_properties *props = NULL; + char at[AVAHI_ADDRESS_STR_MAX]; char hbuf[NI_MAXHOST]; + char if_suffix[16] = ""; struct ifreq ifreq; - int res, family, port = 0, ifindex = 0, protocol = 4; - const struct spa_dict_item *it; + int res, family; - 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); - name = spa_dict_lookup(info, PW_KEY_ZEROCONF_NAME); - address = spa_dict_lookup(info, PW_KEY_ZEROCONF_ADDRESS); - if ((str = spa_dict_lookup(info, PW_KEY_ZEROCONF_PORT))) - port = atoi(str); + if (event != AVAHI_RESOLVER_FOUND) { + pw_log_error("Resolving of '%s' failed: %s", name, + avahi_strerror(avahi_client_errno(impl->client))); + goto done; + } + + 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); + + pw_log_info("%s %s", name, at); tinfo = TUNNEL_INFO(.name = name, .port = port); @@ -636,8 +655,7 @@ static void on_zeroconf_added(void *data, const void *user, const struct spa_dic goto done; } if (t->module != NULL) { - pw_log_info("found duplicate mdns entry for %s on IP %s - skipping tunnel creation", - name, address); + pw_log_info("found duplicate mdns entry for %s on IP %s - skipping tunnel creation", name, at); goto done; } @@ -647,23 +665,33 @@ static void on_zeroconf_added(void *data, const void *user, const struct spa_dic goto done; } - pw_properties_set(props, "snapcast.ip", address); - pw_properties_setf(props, "snapcast.ifindex", "%u", ifindex); + 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, "snapcast.ip", "%s%s", at, if_suffix); + pw_properties_setf(props, "snapcast.ifindex", "%d", interface); pw_properties_setf(props, "snapcast.port", "%u", port); - pw_properties_set(props, "snapcast.name", name); - pw_properties_set(props, "snapcast.hostname", spa_dict_lookup(info, PW_KEY_ZEROCONF_HOSTNAME)); - pw_properties_set(props, "snapcast.domain", spa_dict_lookup(info, PW_KEY_ZEROCONF_DOMAIN)); + pw_properties_setf(props, "snapcast.name", "%s", name); + pw_properties_setf(props, "snapcast.hostname", "%s", host_name); + pw_properties_setf(props, "snapcast.domain", "%s", domain); free((char*)t->info.host); t->info.host = strdup(pw_properties_get(props, "snapcast.ip")); - family = protocol == 4 ? AF_INET : AF_INET6; + family = protocol == AVAHI_PROTO_INET ? AF_INET : AF_INET6; spa_zero(ifreq); - ifreq.ifr_ifindex = ifindex; - if_indextoname(ifindex, ifreq.ifr_name); - pw_properties_set(props, "snapcast.ifname", ifreq.ifr_name); - pw_properties_set(props, "local.ifname", ifreq.ifr_name); + ifreq.ifr_ifindex = interface; + if_indextoname(interface, ifreq.ifr_name); + pw_properties_setf(props, "snapcast.ifname", "%s", ifreq.ifr_name); + pw_properties_setf(props, "local.ifname", "%s", ifreq.ifr_name); struct ifaddrs *if_addr, *ifp; if (getifaddrs(&if_addr) < 0) @@ -697,8 +725,16 @@ static void on_zeroconf_added(void *data, const void *user, const struct spa_dic } freeifaddrs(if_addr); - spa_dict_for_each(it, info) - pw_properties_from_zeroconf(it->key, it->value, props); + for (l = txt; l; l = l->next) { + char *key, *value; + + if (avahi_string_list_get_pair(l, &key, &value, NULL) != 0) + break; + + pw_properties_from_avahi_string(key, value, props); + avahi_free(key); + avahi_free(value); + } if ((str = pw_properties_get(impl->properties, "stream.rules")) == NULL) str = DEFAULT_CREATE_RULES; @@ -716,46 +752,133 @@ static void on_zeroconf_added(void *data, const void *user, const struct spa_dic } done: + avahi_service_resolver_free(r); pw_properties_free(props); } -static void on_zeroconf_removed(void *data, const void *user, const struct spa_dict *info) +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 = data; + struct impl *impl = userdata; + struct tunnel_info info; struct tunnel *t; - struct tunnel_info tinfo; - const char *name; - name = spa_dict_lookup(info, PW_KEY_ZEROCONF_NAME); - - tinfo = TUNNEL_INFO(.name = name); - - t = find_tunnel(impl, &tinfo); - if (t == NULL) + if ((flags & AVAHI_LOOKUP_RESULT_LOCAL) && !impl->discover_local) return; - free_tunnel(t); + /* snapcast does not seem to work well with IPv6 */ + if (protocol == AVAHI_PROTO_INET6) + 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 int make_browser(struct impl *impl, const char *service_type) + +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->ctrl_browser == NULL) + impl->ctrl_browser = make_browser(impl, SERVICE_TYPE_CONTROL); + if (impl->ctrl_browser == NULL) + goto error; + if (impl->jsonrpc_browser == NULL) + impl->jsonrpc_browser = make_browser(impl, SERVICE_TYPE_JSONRPC); + if (impl->jsonrpc_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->ctrl_browser) { + avahi_service_browser_free(impl->ctrl_browser); + impl->ctrl_browser = NULL; + } + if (impl->jsonrpc_browser) { + avahi_service_browser_free(impl->jsonrpc_browser); + impl->jsonrpc_browser = NULL; + } + break; + default: + break; + } + return; +error: + pw_impl_module_schedule_destroy(impl->module); +} + +static int start_client(struct impl *impl) { int res; - if ((res = pw_zeroconf_set_browse(impl->zeroconf, service_type, - &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; + 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 const struct pw_zeroconf_events zeroconf_events = { - PW_VERSION_ZEROCONF_EVENTS, - .added = on_zeroconf_added, - .removed = on_zeroconf_removed, -}; +static int start_avahi(struct impl *impl) +{ + + impl->avahi_poll = pw_avahi_poll_new(impl->context); + + return start_client(impl); +} SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) @@ -789,24 +912,13 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->discover_local = pw_properties_get_bool(impl->properties, "snapcast.discover-local", false); - pw_properties_set(props, PW_KEY_ZEROCONF_DISCOVER_LOCAL, - impl->discover_local ? "true" : "false"); - - impl->zeroconf = pw_zeroconf_new(impl->context, &props->dict); - if (impl->zeroconf == NULL) { - pw_log_error("can't create zeroconf: %m"); - goto error_errno; - } - pw_zeroconf_add_listener(impl->zeroconf, &impl->zeroconf_listener, - &zeroconf_events, impl); - - make_browser(impl, SERVICE_TYPE_CONTROL); - make_browser(impl, SERVICE_TYPE_JSONRPC); pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); + start_avahi(impl); + return 0; error_errno: diff --git a/src/modules/module-spa-device-factory.c b/src/modules/module-spa-device-factory.c index 1acc63dd7..a3b08a285 100644 --- a/src/modules/module-spa-device-factory.c +++ b/src/modules/module-spa-device-factory.c @@ -104,13 +104,13 @@ static void *create_object(void *_data, if ((factory_name = strdup(str)) == NULL) goto error_properties; - pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%u", + pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", pw_global_get_id(pw_impl_factory_get_global(data->factory))); client = resource ? pw_resource_get_client(resource) : NULL; if (client) { - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%u", + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_global_get_id(pw_impl_client_get_global(client))); } diff --git a/src/modules/module-spa-node-factory.c b/src/modules/module-spa-node-factory.c index 87ff3fd45..c7fedd47e 100644 --- a/src/modules/module-spa-node-factory.c +++ b/src/modules/module-spa-node-factory.c @@ -209,14 +209,14 @@ static void *create_object(void *_data, if (factory_name == NULL) goto error_properties; - pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%u", + pw_properties_setf(properties, PW_KEY_FACTORY_ID, "%d", pw_global_get_id(pw_impl_factory_get_global(data->factory))); linger = pw_properties_get_bool(properties, PW_KEY_OBJECT_LINGER, false); client = resource ? pw_resource_get_client(resource) : NULL; if (client && !linger) { - pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%u", + pw_properties_setf(properties, PW_KEY_CLIENT_ID, "%d", pw_global_get_id(pw_impl_client_get_global(client))); } diff --git a/src/modules/module-vban/audio.c b/src/modules/module-vban/audio.c index 40a6415dc..099246895 100644 --- a/src/modules/module-vban/audio.c +++ b/src/modules/module-vban/audio.c @@ -98,43 +98,38 @@ static int vban_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len) samples = SPA_MIN(hdr->format_nbs+1, plen / stride); n_frames = hdr->n_frames; - - if (impl->samples_per_frame == 0) { - impl->samples_per_frame = samples; - } else if (samples != impl->samples_per_frame) { - pw_log_warn("samples_per_frame changed (%u != %u)", - samples, impl->samples_per_frame); - impl->samples_per_frame = samples; - impl->have_sync = false; - } - if (impl->have_sync && impl->n_frames != n_frames) { - pw_log_info("unexpected frame (%u != %u)", + pw_log_info("unexpected frame (%d != %d)", n_frames, impl->n_frames); + impl->have_sync = false; } impl->n_frames = n_frames + 1; - /* derive write position from frame counter, like module-rtp */ - timestamp = n_frames * impl->samples_per_frame; - write = timestamp + impl->target_buffer; + timestamp = impl->timestamp; + impl->timestamp += samples; filled = spa_ringbuffer_get_write_index(&impl->ring, &expected_write); + /* we always write to timestamp + delay */ + write = timestamp + impl->target_buffer; + if (!impl->have_sync) { - pw_log_info("sync to n_frames:%u timestamp:%u target:%u", - n_frames, timestamp, impl->target_buffer); + pw_log_info("sync to timestamp:%u target:%u", + timestamp, impl->target_buffer); /* we read from timestamp, keeping target_buffer of data * in the ringbuffer. */ impl->ring.readindex = timestamp; impl->ring.writeindex = write; filled = impl->target_buffer; - expected_write = write; spa_dll_init(&impl->dll); spa_dll_set_bw(&impl->dll, SPA_DLL_BW_MAX, 128, impl->rate); memset(impl->buffer, 0, BUFFER_SIZE); impl->have_sync = true; + } else if (expected_write != write) { + pw_log_debug("unexpected write (%u != %u)", + write, expected_write); } if (filled + samples > BUFFER_SIZE / stride) { @@ -142,17 +137,14 @@ static int vban_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len) BUFFER_SIZE / stride); impl->have_sync = false; } else { - pw_log_trace("got n_frames:%u samples:%u", n_frames, samples); + pw_log_trace("got samples:%u", samples); spa_ringbuffer_write_data(&impl->ring, impl->buffer, BUFFER_SIZE, (write * stride) & BUFFER_MASK, &buffer[hlen], (samples * stride)); - - /* only advance writeindex if this extends the frontier */ write += samples; - if ((int32_t)(write - expected_write) > 0) - spa_ringbuffer_write_update(&impl->ring, write); + spa_ringbuffer_write_update(&impl->ring, write); } return 0; } @@ -270,6 +262,5 @@ static int vban_audio_init(struct impl *impl, enum spa_direction direction) else impl->stream_events.process = vban_audio_process_playback; impl->receive_vban = vban_audio_receive; - impl->samples_per_frame = 0; return 0; } diff --git a/src/modules/module-vban/midi.c b/src/modules/module-vban/midi.c index 484902448..f460bd274 100644 --- a/src/modules/module-vban/midi.c +++ b/src/modules/module-vban/midi.c @@ -163,6 +163,9 @@ static int vban_midi_receive_midi(struct impl *impl, uint8_t *packet, while (offs < plen) { int size; + uint8_t *midi_data; + size_t midi_size; + uint64_t midi_state = 0; size = get_midi_size(&packet[offs], plen - offs); if (size <= 0 || offs + size > plen) { @@ -171,9 +174,18 @@ static int vban_midi_receive_midi(struct impl *impl, uint8_t *packet, break; } - spa_pod_builder_control(&b, timestamp, SPA_CONTROL_Midi); - spa_pod_builder_bytes(&b, &packet[offs], size); + midi_data = &packet[offs]; + midi_size = size; + while (midi_size > 0) { + uint32_t ump[4]; + int ump_size = spa_ump_from_midi(&midi_data, &midi_size, + ump, sizeof(ump), 0, &midi_state); + if (ump_size <= 0) + break; + spa_pod_builder_control(&b, timestamp, SPA_CONTROL_UMP); + spa_pod_builder_bytes(&b, ump, ump_size); + } offs += size; } spa_pod_builder_pop(&b, &f[0]); @@ -225,25 +237,34 @@ static void vban_midi_flush_packets(struct impl *impl, len = 0; while (spa_pod_parser_get_control_body(parser, &c, &c_body) >= 0) { - uint32_t size = c.value.size; - const void *data = c_body; + int size; + uint8_t event[16]; + uint64_t state = 0; + size_t c_size = c.value.size; - if (c.type != SPA_CONTROL_Midi) + if (c.type != SPA_CONTROL_UMP) continue; - if (len == 0) { - /* start new packet */ - header.n_frames++; - } else if (len + size > impl->mtu) { - /* flush packet when we have one and when it's too large */ - iov[1].iov_len = len; + while (c_size > 0) { + size = spa_ump_to_midi((const uint32_t**)&c_body, + &c_size, event, sizeof(event), &state); + if (size <= 0) + break; - pw_log_debug("sending %d", len); - vban_stream_emit_send_packet(impl, iov, 2); - len = 0; + if (len == 0) { + /* start new packet */ + header.n_frames++; + } else if (len + size > impl->mtu) { + /* flush packet when we have one and when it's too large */ + iov[1].iov_len = len; + + pw_log_debug("sending %d", len); + vban_stream_emit_send_packet(impl, iov, 2); + len = 0; + } + memcpy(&impl->buffer[len], event, size); + len += size; } - memcpy(&impl->buffer[len], data, size); - len += size; } if (len > 0) { /* flush last packet */ diff --git a/src/modules/module-vban/stream.c b/src/modules/module-vban/stream.c index 168ecc837..da3469583 100644 --- a/src/modules/module-vban/stream.c +++ b/src/modules/module-vban/stream.c @@ -61,7 +61,6 @@ struct impl { struct vban_header header; uint32_t timestamp; uint32_t n_frames; - uint32_t samples_per_frame; struct spa_ringbuffer ring; uint8_t buffer[BUFFER_SIZE]; diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c index 61108d424..177556637 100644 --- a/src/modules/module-zeroconf-discover.c +++ b/src/modules/module-zeroconf-discover.c @@ -20,8 +20,12 @@ #include #include +#include +#include +#include + #include "module-protocol-pulse/format.h" -#include "zeroconf-utils/zeroconf.h" +#include "module-zeroconf-discover/avahi-poll.h" /** \page page_module_zeroconf_discover Zeroconf Discover * @@ -80,8 +84,11 @@ struct impl { struct pw_properties *properties; - struct pw_zeroconf *zeroconf; - struct spa_hook zeroconf_listener; + bool discover_local; + AvahiPoll *avahi_poll; + AvahiClient *client; + AvahiServiceBrowser *sink_browser; + AvahiServiceBrowser *source_browser; struct spa_list tunnel_list; }; @@ -100,7 +107,9 @@ struct tunnel { struct spa_hook module_listener; }; -static struct tunnel *tunnel_new(struct impl *impl, const struct tunnel_info *info) +static int start_client(struct impl *impl); + +static struct tunnel *make_tunnel(struct impl *impl, const struct tunnel_info *info) { struct tunnel *t; @@ -126,7 +135,7 @@ static struct tunnel *find_tunnel(struct impl *impl, const struct tunnel_info *i return NULL; } -static void tunnel_free(struct tunnel *t) +static void free_tunnel(struct tunnel *t) { spa_list_remove(&t->link); if (t->module) @@ -142,10 +151,16 @@ static void impl_free(struct impl *impl) struct tunnel *t; spa_list_consume(t, &impl->tunnel_list, link) - tunnel_free(t); + free_tunnel(t); - if (impl->zeroconf) - pw_zeroconf_destroy(impl->zeroconf); + if (impl->sink_browser) + avahi_service_browser_free(impl->sink_browser); + if (impl->source_browser) + avahi_service_browser_free(impl->source_browser); + if (impl->client) + avahi_client_free(impl->client); + if (impl->avahi_poll) + pw_avahi_poll_free(impl->avahi_poll); pw_properties_free(impl->properties); free(impl); } @@ -162,7 +177,7 @@ static const struct pw_impl_module_events module_events = { .destroy = module_destroy, }; -static void pw_properties_from_zeroconf(const char *key, const char *value, +static void pw_properties_from_avahi_string(const char *key, const char *value, struct pw_properties *props) { if (spa_streq(key, "device")) { @@ -226,28 +241,38 @@ static const struct pw_impl_module_events submodule_events = { .destroy = submodule_destroy, }; -static void on_zeroconf_added(void *data, const void *user_data, const struct spa_dict *info) +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 = data; - const char *name, *type, *mode, *device, *host_name, *desc, *fqdn, *user, *str; + struct impl *impl = userdata; struct tunnel *t; struct tunnel_info tinfo; - const struct spa_dict_item *it; + const char *str, *device, *desc, *fqdn, *user, *mode; + char if_suffix[16] = ""; + char at[AVAHI_ADDRESS_STR_MAX]; + AvahiStringList *l; FILE *f; char *args; size_t size; struct pw_impl_module *mod; struct pw_properties *props = NULL; - name = spa_dict_lookup(info, PW_KEY_ZEROCONF_NAME); - type = spa_dict_lookup(info, PW_KEY_ZEROCONF_TYPE); + + if (event != AVAHI_RESOLVER_FOUND) { + pw_log_error("Resolving of '%s' failed: %s", name, + avahi_strerror(avahi_client_errno(impl->client))); + goto done; + } + mode = strstr(type, "sink") ? "sink" : "source"; tinfo = TUNNEL_INFO(.name = name, .mode = mode); t = find_tunnel(impl, &tinfo); if (t == NULL) - t = tunnel_new(impl, &tinfo); + t = make_tunnel(impl, &tinfo); if (t == NULL) { pw_log_error("Can't make tunnel: %m"); goto done; @@ -263,10 +288,16 @@ static void on_zeroconf_added(void *data, const void *user_data, const struct sp goto done; } - spa_dict_for_each(it, info) - pw_properties_from_zeroconf(it->key, it->value, props); + for (l = txt; l; l = l->next) { + char *key, *value; - host_name = spa_dict_lookup(info, PW_KEY_ZEROCONF_HOSTNAME); + if (avahi_string_list_get_pair(l, &key, &value, NULL) != 0) + break; + + pw_properties_from_avahi_string(key, value, props); + avahi_free(key); + avahi_free(value); + } if ((device = pw_properties_get(props, PW_KEY_TARGET_OBJECT)) != NULL) pw_properties_setf(props, PW_KEY_NODE_NAME, @@ -277,9 +308,14 @@ static void on_zeroconf_added(void *data, const void *user_data, const struct sp pw_properties_set(props, "tunnel.mode", mode); - pw_properties_setf(props, "pulse.server.address", " [%s]:%s", - spa_dict_lookup(info, PW_KEY_ZEROCONF_ADDRESS), - spa_dict_lookup(info, PW_KEY_ZEROCONF_PORT)); + if (a->proto == AVAHI_PROTO_INET6 && + a->data.ipv6.address[0] == 0xfe && + (a->data.ipv6.address[1] & 0xc0) == 0x80) + snprintf(if_suffix, sizeof(if_suffix), "%%%d", interface); + + pw_properties_setf(props, "pulse.server.address", " [%s%s]:%u", + avahi_address_snprint(at, sizeof(at), a), + if_suffix, port); desc = pw_properties_get(props, "tunnel.remote.description"); if (desc == NULL) @@ -337,33 +373,130 @@ static void on_zeroconf_added(void *data, const void *user_data, const struct sp t->module = mod; done: + avahi_service_resolver_free(r); pw_properties_free(props); } -static void on_zeroconf_removed(void *data, const void *user, const struct spa_dict *info) + +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 = data; - const char *name, *type, *mode; + struct impl *impl = userdata; + struct tunnel_info info; struct tunnel *t; - struct tunnel_info tinfo; - name = spa_dict_lookup(info, PW_KEY_ZEROCONF_NAME); - type = spa_dict_lookup(info, PW_KEY_ZEROCONF_TYPE); - mode = strstr(type, "sink") ? "sink" : "source"; - - tinfo = TUNNEL_INFO(.name = name, .mode = mode); - - if ((t = find_tunnel(impl, &tinfo)) == NULL) + if ((flags & AVAHI_LOOKUP_RESULT_LOCAL) && !impl->discover_local) return; - tunnel_free(t); + 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 const struct pw_zeroconf_events zeroconf_events = { - PW_VERSION_ZEROCONF_EVENTS, - .added = on_zeroconf_added, - .removed = on_zeroconf_removed, -}; + +static struct AvahiServiceBrowser *make_browser(struct impl *impl, const char *service_type) +{ + struct AvahiServiceBrowser *s; + + s = avahi_service_browser_new(impl->client, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + service_type, NULL, 0, + browser_cb, impl); + if (s == NULL) { + pw_log_error("can't make browser for %s: %s", service_type, + avahi_strerror(avahi_client_errno(impl->client))); + } + return s; +} + +static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) +{ + struct impl *impl = userdata; + + impl->client = c; + + switch (state) { + case AVAHI_CLIENT_S_REGISTERING: + case AVAHI_CLIENT_S_RUNNING: + case AVAHI_CLIENT_S_COLLISION: + if (impl->sink_browser == NULL) + impl->sink_browser = make_browser(impl, SERVICE_TYPE_SINK); + if (impl->sink_browser == NULL) + goto error; + + if (impl->source_browser == NULL) + impl->source_browser = make_browser(impl, SERVICE_TYPE_SOURCE); + if (impl->source_browser == NULL) + goto error; + + break; + case AVAHI_CLIENT_FAILURE: + if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) + start_client(impl); + + SPA_FALLTHROUGH; + case AVAHI_CLIENT_CONNECTING: + if (impl->sink_browser) { + avahi_service_browser_free(impl->sink_browser); + impl->sink_browser = NULL; + } + if (impl->source_browser) { + avahi_service_browser_free(impl->source_browser); + impl->source_browser = NULL; + } + break; + default: + break; + } + return; +error: + pw_impl_module_schedule_destroy(impl->module); +} + +static int start_client(struct impl *impl) +{ + int res; + if ((impl->client = avahi_client_new(impl->avahi_poll, + AVAHI_CLIENT_NO_FAIL, + client_callback, impl, + &res)) == NULL) { + pw_log_error("can't create client: %s", avahi_strerror(res)); + pw_impl_module_schedule_destroy(impl->module); + return -EIO; + } + return 0; +} + +static int start_avahi(struct impl *impl) +{ + impl->avahi_poll = pw_avahi_poll_new(impl->context); + + return start_client(impl); +} SPA_EXPORT int pipewire__module_init(struct pw_impl_module *module, const char *args) @@ -371,7 +504,6 @@ 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; - bool discover_local; int res; PW_LOG_TOPIC_INIT(mod_topic); @@ -395,29 +527,14 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) impl->context = context; impl->properties = props; - discover_local = pw_properties_get_bool(impl->properties, + impl->discover_local = pw_properties_get_bool(impl->properties, "pulse.discover-local", false); - pw_properties_setf(impl->properties, PW_KEY_ZEROCONF_DISCOVER_LOCAL, - discover_local ? "true" : "false"); pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); - 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, SERVICE_TYPE_SINK, - &SPA_DICT_ITEMS( - SPA_DICT_ITEM(PW_KEY_ZEROCONF_TYPE, SERVICE_TYPE_SINK))); - - pw_zeroconf_set_browse(impl->zeroconf, SERVICE_TYPE_SOURCE, - &SPA_DICT_ITEMS( - SPA_DICT_ITEM(PW_KEY_ZEROCONF_TYPE, SERVICE_TYPE_SOURCE))); + start_avahi(impl); return 0; diff --git a/src/modules/zeroconf-utils/avahi-poll.c b/src/modules/module-zeroconf-discover/avahi-poll.c similarity index 100% rename from src/modules/zeroconf-utils/avahi-poll.c rename to src/modules/module-zeroconf-discover/avahi-poll.c diff --git a/src/modules/zeroconf-utils/avahi-poll.h b/src/modules/module-zeroconf-discover/avahi-poll.h similarity index 100% rename from src/modules/zeroconf-utils/avahi-poll.h rename to src/modules/module-zeroconf-discover/avahi-poll.h diff --git a/src/modules/network-utils.h b/src/modules/network-utils.h index 568e9cb19..6ff80dd7a 100644 --- a/src/modules/network-utils.h +++ b/src/modules/network-utils.h @@ -11,7 +11,6 @@ #include #include #include -#include #include @@ -87,64 +86,6 @@ static inline int pw_net_parse_address_port(const char *address, return pw_net_parse_address(n, port, addr, len); } -static inline bool pw_net_are_addresses_equal(const struct sockaddr_storage *addr1, - const struct sockaddr_storage *addr2, - bool compare_ports) -{ - /* IPv6 addresses might actually be mapped IPv4 ones. In cases where - * such mapped IPv4 addresses are compared against plain IPv4 ones - * (that is, ss_family == AF_INET), special handling is required. */ - bool addr1_is_mapped_ipv4 = (addr1->ss_family == AF_INET6) && - IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6*)addr1)->sin6_addr); - bool addr2_is_mapped_ipv4 = (addr2->ss_family == AF_INET6) && - IN6_IS_ADDR_V4MAPPED(&((struct sockaddr_in6*)addr2)->sin6_addr); - - if ((addr1->ss_family == AF_INET) && (addr2->ss_family == AF_INET)) { - /* Both addresses are plain IPv4 addresses. */ - - const struct sockaddr_in *addr1_in = (const struct sockaddr_in *)addr1; - const struct sockaddr_in *addr2_in = (const struct sockaddr_in *)addr2; - return (!compare_ports || (addr1_in->sin_port == addr2_in->sin_port)) && - (addr1_in->sin_addr.s_addr == addr2_in->sin_addr.s_addr); - } else if ((addr1->ss_family == AF_INET6) && (addr2->ss_family == AF_INET6)) { - /* Both addresses are IPv6 addresses. (Note that this logic here - * works correctly even if both are actually mapped IPv4 addresses.) */ - - const struct sockaddr_in6 *addr1_in6 = (const struct sockaddr_in6 *)addr1; - const struct sockaddr_in6 *addr2_in6 = (const struct sockaddr_in6 *)addr2; - return (!compare_ports || (addr1_in6->sin6_port == addr2_in6->sin6_port)) && - (addr1_in6->sin6_scope_id == addr2_in6->sin6_scope_id) && - (memcmp(&addr1_in6->sin6_addr, &addr2_in6->sin6_addr, sizeof(struct in6_addr)) == 0); - } else if ((addr1->ss_family == AF_INET) && addr2_is_mapped_ipv4) { - /* addr1 is a plain IPv4 address, addr2 is a mapped IPv4 address. - * Extract the IPv4 portion of addr2 to form a plain IPv4 address - * out of it, then compare the two plain IPv4 addresses. */ - - struct in_addr addr2_as_ipv4; - const struct sockaddr_in *addr1_in = (const struct sockaddr_in *)addr1; - const struct sockaddr_in6 *addr2_in6 = (const struct sockaddr_in6 *)addr2; - - memcpy(&addr2_as_ipv4, &(addr2_in6->sin6_addr.s6_addr[12]), 4); - - return (!compare_ports || (addr1_in->sin_port == addr2_in6->sin6_port)) && - (addr1_in->sin_addr.s_addr == addr2_as_ipv4.s_addr); - } else if (addr1_is_mapped_ipv4 && (addr2->ss_family == AF_INET)) { - /* addr2 is a plain IPv4 address, addr1 is a mapped IPv4 address. - * Extract the IPv4 portion of addr1 to form a plain IPv4 address - * out of it, then compare the two plain IPv4 addresses. */ - - struct in_addr addr1_as_ipv4; - const struct sockaddr_in6 *addr1_in6 = (const struct sockaddr_in6 *)addr1; - const struct sockaddr_in *addr2_in = (const struct sockaddr_in *)addr2; - - memcpy(&addr1_as_ipv4, &(addr1_in6->sin6_addr.s6_addr[12]), 4); - - return (!compare_ports || (addr1_in6->sin6_port == addr2_in->sin_port)) && - (addr1_as_ipv4.s_addr == addr2_in->sin_addr.s_addr); - } else - return false; -} - static inline int pw_net_get_ip(const struct sockaddr_storage *sa, char *ip, size_t len, bool *ip4, uint16_t *port) { if (ip4) diff --git a/src/modules/zeroconf-utils/zeroconf.c b/src/modules/zeroconf-utils/zeroconf.c deleted file mode 100644 index a3ec5f4a8..000000000 --- a/src/modules/zeroconf-utils/zeroconf.c +++ /dev/null @@ -1,622 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#include -#include -#include -#include -#include -#include -#include - -#include "config.h" - -#include -#include - -#include -#include -#include -#include - -#include "avahi-poll.h" -#include "zeroconf.h" - -#define pw_zeroconf_emit(o,m,v,...) spa_hook_list_call(&o->listener_list, struct pw_zeroconf_events, m, v, ##__VA_ARGS__) -#define pw_zeroconf_emit_destroy(c) pw_zeroconf_emit(c, destroy, 0) -#define pw_zeroconf_emit_error(c,e,m) pw_zeroconf_emit(c, error, 0, e, m) -#define pw_zeroconf_emit_added(c,id,i) pw_zeroconf_emit(c, added, 0, id, i) -#define pw_zeroconf_emit_removed(c,id,i) pw_zeroconf_emit(c, removed, 0, id, i) - -struct service_info { - AvahiIfIndex interface; - AvahiProtocol 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__ }) - -#define STR_TO_PROTO(s) (atoi(s) == 6 ? AVAHI_PROTO_INET6 : AVAHI_PROTO_INET) - -struct entry { - struct pw_zeroconf *zc; - struct spa_list link; - -#define TYPE_ANNOUNCE 0 -#define TYPE_BROWSE 1 - uint32_t type; - const void *user; - - struct pw_properties *props; - - AvahiEntryGroup *group; - AvahiServiceBrowser *browser; - - struct spa_list services; -}; - -struct service { - struct entry *e; - struct spa_list link; - - struct service_info info; - - struct pw_properties *props; -}; - -struct pw_zeroconf { - int refcount; - struct pw_context *context; - - struct pw_properties *props; - - struct spa_hook_list listener_list; - - AvahiPoll *poll; - AvahiClient *client; - AvahiClientState state; - - struct spa_list entries; - - bool discover_local; -}; - -static struct service *service_find(struct entry *e, const struct service_info *info) -{ - struct service *s; - spa_list_for_each(s, &e->services, 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 service_free(struct service *s) -{ - spa_list_remove(&s->link); - free((void*)s->info.name); - free((void*)s->info.type); - free((void*)s->info.domain); - free((void*)s->info.host_name); - pw_properties_free(s->props); - free(s); -} - -struct entry *entry_find(struct pw_zeroconf *zc, uint32_t type, const void *user) -{ - struct entry *e; - spa_list_for_each(e, &zc->entries, link) - if (e->type == type && e->user == user) - return e; - return NULL; -} - -static void entry_free(struct entry *e) -{ - struct service *s; - - spa_list_remove(&e->link); - if (e->group) - avahi_entry_group_free(e->group); - spa_list_consume(s, &e->services, link) - service_free(s); - pw_properties_free(e->props); - free(e); -} - -static void zeroconf_free(struct pw_zeroconf *zc) -{ - struct entry *a; - - spa_list_consume(a, &zc->entries, link) - entry_free(a); - - if (zc->client) - avahi_client_free(zc->client); - if (zc->poll) - pw_avahi_poll_free(zc->poll); - pw_properties_free(zc->props); - free(zc); -} - -static void zeroconf_unref(struct pw_zeroconf *zc) -{ - if (--zc->refcount == 0) - zeroconf_free(zc); -} - -void pw_zeroconf_destroy(struct pw_zeroconf *zc) -{ - pw_zeroconf_emit_destroy(zc); - - zeroconf_unref(zc); -} - -static struct service *service_new(struct entry *e, - const struct service_info *info, AvahiStringList *txt) -{ - struct service *s; - struct pw_zeroconf *zc = e->zc; - const AvahiAddress *a = &info->address; - static const char *link_local_range = "169.254."; - AvahiStringList *l; - char at[AVAHI_ADDRESS_STR_MAX], if_suffix[16] = ""; - - if ((s = calloc(1, sizeof(*s))) == NULL) - goto error; - - s->e = e; - spa_list_append(&e->services, &s->link); - - 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; - - if ((s->props = pw_properties_new(NULL, NULL)) == NULL) - goto error; - - if (a->proto == AVAHI_PROTO_INET6 && info->interface != AVAHI_IF_UNSPEC && - a->data.ipv6.address[0] == 0xfe && - (a->data.ipv6.address[1] & 0xc0) == 0x80) - snprintf(if_suffix, sizeof(if_suffix), "%%%d", info->interface); - - avahi_address_snprint(at, sizeof(at), a); - if (a->proto == AVAHI_PROTO_INET && info->interface != AVAHI_IF_UNSPEC && - spa_strstartswith(at, link_local_range)) - snprintf(if_suffix, sizeof(if_suffix), "%%%d", info->interface); - - if (info->interface != AVAHI_IF_UNSPEC) - pw_properties_setf(s->props, PW_KEY_ZEROCONF_IFINDEX, "%d", info->interface); - if (a->proto != AVAHI_PROTO_UNSPEC) - pw_properties_set(s->props, PW_KEY_ZEROCONF_PROTO, - a->proto == AVAHI_PROTO_INET ? "4" : "6"); - - pw_properties_set(s->props, PW_KEY_ZEROCONF_NAME, info->name); - pw_properties_set(s->props, PW_KEY_ZEROCONF_TYPE, info->type); - pw_properties_set(s->props, PW_KEY_ZEROCONF_DOMAIN, info->domain); - pw_properties_set(s->props, PW_KEY_ZEROCONF_HOSTNAME, info->host_name); - pw_properties_setf(s->props, PW_KEY_ZEROCONF_ADDRESS, "%s%s", at, if_suffix); - pw_properties_setf(s->props, PW_KEY_ZEROCONF_PORT, "%u", info->port); - - for (l = txt; l; l = l->next) { - char *key, *value; - - if (avahi_string_list_get_pair(l, &key, &value, NULL) != 0) - break; - - pw_properties_set(s->props, key, value); - avahi_free(key); - avahi_free(value); - } - - pw_log_info("new %s %s %s %s", info->name, info->type, info->domain, info->host_name); - pw_zeroconf_emit_added(zc, e->user, &s->props->dict); - - return s; - -error: - if (s) - service_free(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 entry *e = userdata; - struct pw_zeroconf *zc = e->zc; - struct service_info info; - - if (event != AVAHI_RESOLVER_FOUND) { - pw_log_error("Resolving of '%s' failed: %s", name, - avahi_strerror(avahi_client_errno(zc->client))); - goto done; - } - - info = SERVICE_INFO(.interface = interface, - .protocol = protocol, - .name = name, - .type = type, - .domain = domain, - .host_name = host_name, - .address = *a, - .port = port); - - service_new(e, &info, 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 entry *e = userdata; - struct pw_zeroconf *zc = e->zc; - struct service_info info; - struct service *s; - int aproto = AVAHI_PROTO_UNSPEC; - const char *str; - - if ((flags & AVAHI_LOOKUP_RESULT_LOCAL) && !zc->discover_local) - return; - - info = SERVICE_INFO(.interface = interface, - .protocol = protocol, - .name = name, - .type = type, - .domain = domain); - - s = service_find(e, &info); - - switch (event) { - case AVAHI_BROWSER_NEW: - if (s != NULL) - return; - - if ((str = pw_properties_get(e->props, PW_KEY_ZEROCONF_RESOLVE_PROTO))) - aproto = STR_TO_PROTO(str); - - if (!(avahi_service_resolver_new(zc->client, - interface, protocol, - name, type, domain, - aproto, 0, - resolver_cb, e))) { - int res = avahi_client_errno(zc->client); - pw_log_error("can't make service resolver: %s", avahi_strerror(res)); - pw_zeroconf_emit_error(zc, res, avahi_strerror(res)); - } - break; - case AVAHI_BROWSER_REMOVE: - if (s == NULL) - return; - pw_log_info("removed %s %s %s", name, type, domain); - pw_zeroconf_emit_removed(zc, e->user, &s->props->dict); - service_free(s); - break; - default: - break; - } -} - -static int do_browse(struct pw_zeroconf *zc, struct entry *e) -{ - const struct spa_dict_item *it; - const char *type = NULL, *domain = NULL; - int res, ifindex = AVAHI_IF_UNSPEC, proto = AVAHI_PROTO_UNSPEC; - - if (e->browser == NULL) { - spa_dict_for_each(it, &e->props->dict) { - if (spa_streq(it->key, PW_KEY_ZEROCONF_IFINDEX)) - ifindex = atoi(it->value); - else if (spa_streq(it->key, PW_KEY_ZEROCONF_PROTO)) - proto = STR_TO_PROTO(it->value); - else if (spa_streq(it->key, PW_KEY_ZEROCONF_TYPE)) - type = it->value; - else if (spa_streq(it->key, PW_KEY_ZEROCONF_DOMAIN)) - domain = it->value; - } - if (type == NULL) { - res = -EINVAL; - pw_log_error("can't make browser: no "PW_KEY_ZEROCONF_TYPE" provided"); - pw_zeroconf_emit_error(zc, res, spa_strerror(res)); - return res; - } - e->browser = avahi_service_browser_new(zc->client, - ifindex, proto, type, domain, 0, - browser_cb, e); - if (e->browser == NULL) { - res = avahi_client_errno(zc->client); - pw_log_error("can't make browser: %s", avahi_strerror(res)); - pw_zeroconf_emit_error(zc, res, avahi_strerror(res)); - return -EIO; - } - } - return 0; -} - -static void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) -{ - struct entry *e = userdata; - struct pw_zeroconf *zc = e->zc; - const char *name; - int res; - - zc->refcount++; - - name = pw_properties_get(e->props, PW_KEY_ZEROCONF_NAME); - - switch (state) { - case AVAHI_ENTRY_GROUP_ESTABLISHED: - pw_log_debug("Entry \"%s\" added", name); - break; - case AVAHI_ENTRY_GROUP_COLLISION: - pw_log_error("Entry \"%s\" name collision", name); - break; - case AVAHI_ENTRY_GROUP_FAILURE: - res = avahi_client_errno(zc->client); - pw_log_error("Entry \"%s\" failure: %s", name, avahi_strerror(res)); - pw_zeroconf_emit_error(zc, res, avahi_strerror(res)); - break; - case AVAHI_ENTRY_GROUP_UNCOMMITED: - case AVAHI_ENTRY_GROUP_REGISTERING: - break; - } - zeroconf_unref(zc); -} - -static int do_announce(struct pw_zeroconf *zc, struct entry *e) -{ - AvahiStringList *txt = NULL; - int res, ifindex = AVAHI_IF_UNSPEC, proto = AVAHI_PROTO_UNSPEC; - const struct spa_dict_item *it; - const char *name = "unnamed", *type = NULL, *subtypes = NULL; - const char *domain = NULL, *host = NULL; - uint16_t port = 0; - - if (e->group == NULL) { - e->group = avahi_entry_group_new(zc->client, - entry_group_callback, e); - if (e->group == NULL) { - res = avahi_client_errno(zc->client); - pw_log_error("can't make group: %s", avahi_strerror(res)); - pw_zeroconf_emit_error(zc, res, avahi_strerror(res)); - return -EIO; - } - } - avahi_entry_group_reset(e->group); - - spa_dict_for_each(it, &e->props->dict) { - if (spa_streq(it->key, PW_KEY_ZEROCONF_IFINDEX)) - ifindex = atoi(it->value); - else if (spa_streq(it->key, PW_KEY_ZEROCONF_PROTO)) - proto = STR_TO_PROTO(it->value); - else if (spa_streq(it->key, PW_KEY_ZEROCONF_NAME)) - name = it->value; - else if (spa_streq(it->key, PW_KEY_ZEROCONF_TYPE)) - type = it->value; - else if (spa_streq(it->key, PW_KEY_ZEROCONF_DOMAIN)) - domain = it->value; - else if (spa_streq(it->key, PW_KEY_ZEROCONF_HOST)) - host = it->value; - else if (spa_streq(it->key, PW_KEY_ZEROCONF_PORT)) - port = atoi(it->value); - else if (spa_streq(it->key, PW_KEY_ZEROCONF_SUBTYPES)) - subtypes = it->value; - else if (!spa_strstartswith(it->key, "zeroconf.")) - txt = avahi_string_list_add_pair(txt, it->key, it->value); - } - if (type == NULL) { - res = -EINVAL; - pw_log_error("can't announce: no "PW_KEY_ZEROCONF_TYPE" provided"); - pw_zeroconf_emit_error(zc, res, spa_strerror(res)); - avahi_string_list_free(txt); - return res; - } - res = avahi_entry_group_add_service_strlst(e->group, - ifindex, proto, - (AvahiPublishFlags)0, name, - type, domain, host, port, txt); - avahi_string_list_free(txt); - - if (res < 0) { - res = avahi_client_errno(zc->client); - pw_log_error("can't add service: %s", avahi_strerror(res)); - pw_zeroconf_emit_error(zc, res, avahi_strerror(res)); - return -EIO; - } - - if (subtypes) { - struct spa_json iter; - char v[512]; - - if (spa_json_begin_array_relax(&iter, subtypes, strlen(subtypes)) <= 0) { - res = -EINVAL; - pw_log_error("invalid subtypes: %s", subtypes); - pw_zeroconf_emit_error(zc, res, spa_strerror(res)); - return res; - } - while (spa_json_get_string(&iter, v, sizeof(v)) > 0) { - res = avahi_entry_group_add_service_subtype(e->group, - ifindex, proto, - (AvahiPublishFlags)0, name, - type, domain, v); - if (res < 0) { - res = avahi_client_errno(zc->client); - pw_log_error("can't add subtype %s: %s", v, avahi_strerror(res)); - pw_zeroconf_emit_error(zc, res, avahi_strerror(res)); - return -EIO; - } - } - } - if ((res = avahi_entry_group_commit(e->group)) < 0) { - res = avahi_client_errno(zc->client); - pw_log_error("can't commit group: %s", avahi_strerror(res)); - pw_zeroconf_emit_error(zc, res, avahi_strerror(res)); - return -EIO; - } - return 0; -} - -static int entry_start(struct pw_zeroconf *zc, struct entry *e) -{ - if (zc->state != AVAHI_CLIENT_S_REGISTERING && - zc->state != AVAHI_CLIENT_S_RUNNING && - zc->state != AVAHI_CLIENT_S_COLLISION) - return 0; - - if (e->type == TYPE_ANNOUNCE) - return do_announce(zc, e); - else - return do_browse(zc, e); -} - -static void client_callback(AvahiClient *c, AvahiClientState state, void *d) -{ - struct pw_zeroconf *zc = d; - struct entry *e; - - zc->client = c; - zc->refcount++; - zc->state = state; - - switch (state) { - case AVAHI_CLIENT_S_REGISTERING: - case AVAHI_CLIENT_S_RUNNING: - case AVAHI_CLIENT_S_COLLISION: - spa_list_for_each(e, &zc->entries, link) - entry_start(zc, e); - break; - case AVAHI_CLIENT_FAILURE: - { - int err = avahi_client_errno(c); - pw_zeroconf_emit_error(zc, err, avahi_strerror(err)); - break; - } - case AVAHI_CLIENT_CONNECTING: - default: - break; - } - zeroconf_unref(zc); -} - -static struct entry *entry_new(struct pw_zeroconf *zc, uint32_t type, const void *user, const struct spa_dict *info) -{ - struct entry *e; - - if ((e = calloc(1, sizeof(*e))) == NULL) - return NULL; - - e->zc = zc; - e->type = type; - e->user = user; - e->props = pw_properties_new_dict(info); - spa_list_append(&zc->entries, &e->link); - spa_list_init(&e->services); - - if (type == TYPE_ANNOUNCE) - pw_log_debug("created announce for \"%s\"", - pw_properties_get(e->props, PW_KEY_ZEROCONF_NAME)); - else - pw_log_debug("created browse for \"%s\"", - pw_properties_get(e->props, PW_KEY_ZEROCONF_TYPE)); - return e; -} - -static int set_entry(struct pw_zeroconf *zc, uint32_t type, const void *user, const struct spa_dict *info) -{ - struct entry *e; - int res = 0; - - e = entry_find(zc, type, user); - if (e == NULL) { - if (info == NULL) - return 0; - if ((e = entry_new(zc, type, user, info)) == NULL) - return -errno; - res = entry_start(zc, e); - } else { - if (info == NULL) - entry_free(e); - else { - pw_properties_update(e->props, info); - res = entry_start(zc, e); - } - } - return res; -} -int pw_zeroconf_set_announce(struct pw_zeroconf *zc, const void *user, const struct spa_dict *info) -{ - return set_entry(zc, TYPE_ANNOUNCE, user, info); -} -int pw_zeroconf_set_browse(struct pw_zeroconf *zc, const void *user, const struct spa_dict *info) -{ - return set_entry(zc, TYPE_BROWSE, user, info); -} - -struct pw_zeroconf * pw_zeroconf_new(struct pw_context *context, - struct spa_dict *props) -{ - struct pw_zeroconf *zc; - uint32_t i; - int res; - - if ((zc = calloc(1, sizeof(*zc))) == NULL) - return NULL; - - zc->refcount = 1; - zc->context = context; - spa_hook_list_init(&zc->listener_list); - spa_list_init(&zc->entries); - zc->props = props ? pw_properties_new_dict(props) : pw_properties_new(NULL, NULL); - zc->discover_local = true; - - for (i = 0; props && i < props->n_items; i++) { - const char *k = props->items[i].key; - const char *v = props->items[i].value; - - if (spa_streq(k, PW_KEY_ZEROCONF_DISCOVER_LOCAL) && v) - zc->discover_local = spa_atob(v); - } - - zc->poll = pw_avahi_poll_new(context); - if (zc->poll == NULL) - goto error; - - zc->client = avahi_client_new(zc->poll, AVAHI_CLIENT_NO_FAIL, - client_callback, zc, &res); - if (!zc->client) { - pw_log_error("failed to create avahi client: %s", avahi_strerror(res)); - goto error; - } - return zc; -error: - zeroconf_free(zc); - return NULL; -} - -void pw_zeroconf_add_listener(struct pw_zeroconf *zc, - struct spa_hook *listener, - const struct pw_zeroconf_events *events, void *data) -{ - spa_hook_list_append(&zc->listener_list, listener, events, data); -} diff --git a/src/modules/zeroconf-utils/zeroconf.h b/src/modules/zeroconf-utils/zeroconf.h deleted file mode 100644 index 3fbd0bde8..000000000 --- a/src/modules/zeroconf-utils/zeroconf.h +++ /dev/null @@ -1,59 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2026 Wim Taymans */ -/* SPDX-License-Identifier: MIT */ - -#ifndef PIPEWIRE_ZEROCONF_H -#define PIPEWIRE_ZEROCONF_H - -#include - -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define PW_KEY_ZEROCONF_DISCOVER_LOCAL "zeroconf.discover-local" /* discover local services, true by default */ - -#define PW_KEY_ZEROCONF_IFINDEX "zeroconf.ifindex" /* interface index */ -#define PW_KEY_ZEROCONF_PROTO "zeroconf.proto" /* protocol version, "4" ot "6" */ -#define PW_KEY_ZEROCONF_NAME "zeroconf.name" /* session name */ -#define PW_KEY_ZEROCONF_TYPE "zeroconf.type" /* service type, like "_http._tcp", not NULL */ -#define PW_KEY_ZEROCONF_DOMAIN "zeroconf.domain" /* domain to register in, recommended NULL */ -#define PW_KEY_ZEROCONF_HOST "zeroconf.host" /* host to register on, recommended NULL */ -#define PW_KEY_ZEROCONF_SUBTYPES "zeroconf.subtypes" /* subtypes to register, array of strings */ -#define PW_KEY_ZEROCONF_RESOLVE_PROTO "zeroconf.resolve-proto" /* protocol to resolve to, "4" or "6" */ -#define PW_KEY_ZEROCONF_HOSTNAME "zeroconf.hostname" /* hostname of resolved service */ -#define PW_KEY_ZEROCONF_PORT "zeroconf.port" /* port of resolved service */ -#define PW_KEY_ZEROCONF_ADDRESS "zeroconf.address" /* address of resolved service */ - -struct pw_zeroconf; - -struct pw_zeroconf_events { -#define PW_VERSION_ZEROCONF_EVENTS 0 - uint32_t version; - - void (*destroy) (void *data); - void (*error) (void *data, int err, const char *message); - - void (*added) (void *data, const void *user, const struct spa_dict *info); - void (*removed) (void *data, const void *user, const struct spa_dict *info); -}; - -struct pw_zeroconf * pw_zeroconf_new(struct pw_context *context, - struct spa_dict *props); - -void pw_zeroconf_destroy(struct pw_zeroconf *zc); - -int pw_zeroconf_set_announce(struct pw_zeroconf *zc, const void *user, const struct spa_dict *info); -int pw_zeroconf_set_browse(struct pw_zeroconf *zc, const void *user, const struct spa_dict *info); - -void pw_zeroconf_add_listener(struct pw_zeroconf *zc, - struct spa_hook *listener, - const struct pw_zeroconf_events *events, void *data); - -#ifdef __cplusplus -} -#endif - -#endif /* PIPEWIRE_ZEROCONF_H */ diff --git a/src/pipewire/buffers.c b/src/pipewire/buffers.c index ac3911f33..db1a01551 100644 --- a/src/pipewire/buffers.c +++ b/src/pipewire/buffers.c @@ -146,11 +146,8 @@ param_filter(struct pw_buffers *this, if (in_res < 1) { /* in_res == -ENOENT : unknown parameter, assume NULL and we will * exit the loop below. - * in_res == 0 : no data, assume NULL - * in_res < 0 : some error, exit now + * in_res < 1 : some error or no data, exit now */ - if (in_res == 0) - in_res = -ENOENT; if (in_res == -ENOENT) iparam = NULL; else @@ -166,8 +163,6 @@ param_filter(struct pw_buffers *this, id, &oidx, iparam, &oparam, result); /* out_res < 1 : no value or error, exit now */ - if (out_res == 0) - out_res = -ENOENT; if (out_res < 1) break; diff --git a/src/pipewire/capabilities.h b/src/pipewire/capabilities.h index 3bb6c7b20..b8431ac85 100644 --- a/src/pipewire/capabilities.h +++ b/src/pipewire/capabilities.h @@ -31,6 +31,8 @@ extern "C" { */ #define PW_CAPABILITY_DEVICE_IDS "pipewire.device-ids" +#define PW_CAPABILITY_DEVICE_ID "pipewire.device-id" /**< Link capable of device Id negotation */ + /** \} */ diff --git a/src/pipewire/context.c b/src/pipewire/context.c index e3f65ad41..1c30be70a 100644 --- a/src/pipewire/context.c +++ b/src/pipewire/context.c @@ -36,6 +36,8 @@ PW_LOG_TOPIC_EXTERN(log_context); #define PW_LOG_TOPIC_DEFAULT log_context +#define MAX_HOPS 64 +#define MAX_SYNC 4u #define MAX_LOOPS 64u #define DEFAULT_DATA_LOOPS 1 @@ -110,17 +112,13 @@ static void fill_core_properties(struct pw_context *context) pw_properties_set(properties, PW_KEY_CORE_NAME, context->core->info.name); } -SPA_EXPORT -int pw_context_set_freewheel(struct pw_context *context, bool freewheel) +static int context_set_freewheel(struct pw_context *context, bool freewheel) { struct impl *impl = SPA_CONTAINER_OF(context, struct impl, this); struct spa_thread *thr; uint32_t i; int res = 0; - if (context->freewheeling == freewheel) - return 0; - for (i = 0; i < impl->n_data_loops; i++) { if (impl->data_loops[i].impl == NULL || (thr = pw_data_loop_get_thread(impl->data_loops[i].impl)) == NULL) @@ -353,19 +351,17 @@ static int adjust_rlimits(const struct spa_dict *dict) [RLIMIT_CPU] = "cpu", [RLIMIT_DATA] = "data", [RLIMIT_FSIZE] = "fsize", + [RLIMIT_LOCKS] = "locks", [RLIMIT_MEMLOCK] = "memlock", + [RLIMIT_MSGQUEUE] = "msgqueue", + [RLIMIT_NICE] = "nice", [RLIMIT_NOFILE] = "nofile", [RLIMIT_NPROC] = "nproc", [RLIMIT_RSS] = "rss", - [RLIMIT_STACK] = "stack", -#ifdef __linux__ - [RLIMIT_LOCKS] = "locks", - [RLIMIT_MSGQUEUE] = "msgqueue", - [RLIMIT_NICE] = "nice", [RLIMIT_RTPRIO] = "rtprio", [RLIMIT_RTTIME] = "rttime", [RLIMIT_SIGPENDING] = "sigpending", -#endif + [RLIMIT_STACK] = "stack", }; int res; spa_dict_for_each(it, dict) { @@ -986,9 +982,468 @@ SPA_PRINTF_FUNC(7, 8) int pw_context_debug_port_params(struct pw_context *this, return 0; } +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); +} + +/* From a node (that is runnable) follow all prepared links in the given direction + * and groups to active nodes and make them recursively runnable as well. + */ +static inline int run_nodes(struct pw_context *context, struct pw_impl_node *node, + struct spa_list *nodes, enum pw_direction direction, int hop) +{ + struct pw_impl_node *t; + struct pw_impl_port *p; + struct pw_impl_link *l; + + if (hop == MAX_HOPS) { + pw_log_warn("exceeded hops (%d)", hop); + return -EIO; + } + + pw_log_debug("node %p: '%s' direction:%s", node, node->name, + pw_direction_as_string(direction)); + + SPA_FLAG_SET(node->checked, 1u<input_ports, link) { + spa_list_for_each(l, &p->links, input_link) { + t = l->output->node; + + if (!t->active || !l->prepared || + (!t->driving && SPA_FLAG_IS_SET(t->checked, 1u<driving && p->node == t) + continue; + + pw_log_debug(" peer %p: '%s'", t, t->name); + t->runnable = true; + run_nodes(context, t, nodes, direction, hop + 1); + } + } + } else { + spa_list_for_each(p, &node->output_ports, link) { + spa_list_for_each(l, &p->links, output_link) { + t = l->input->node; + + if (!t->active || !l->prepared || + (!t->driving && SPA_FLAG_IS_SET(t->checked, 1u<driving && p->node == t) + continue; + + pw_log_debug(" peer %p: '%s'", t, t->name); + t->runnable = true; + run_nodes(context, t, nodes, direction, hop + 1); + } + } + } + /* now go through all the nodes that have the same link group and + * that are not yet visited. Note how nodes with the same group + * don't get included here. They were added to the same driver but + * need to otherwise stay idle unless some non-passive link activates + * them. */ + if (node->link_groups != NULL) { + spa_list_for_each(t, nodes, sort_link) { + if (t->exported || !t->active || + SPA_FLAG_IS_SET(t->checked, 1u<link_groups, node->link_groups) < 0) + continue; + + pw_log_debug(" group %p: '%s'", t, t->name); + t->runnable = true; + if (!t->driving) + run_nodes(context, t, nodes, direction, hop + 1); + } + } + return 0; +} + +/* Follow all prepared links and groups from node, activate the links. + * If a non-passive link is found, we set the peer runnable flag. + * + * After this is done, we end up with a list of nodes in collect that are all + * linked to node. + * Some of the nodes have the runnable flag set. We then start from those nodes + * and make all linked nodes and groups runnable as well. (see run_nodes). + * + * This ensures that we only activate the paths from the runnable nodes to the + * driver nodes and leave the other nodes idle. + */ +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->active) + continue; + + 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->active) + continue; + + pw_impl_link_prepare(l); + + if (!l->prepared) + continue; + + if (!l->passive) + t->runnable = true; + + if (!t->visited) { + t->visited = true; + spa_list_append(&queue, &t->sort_link); + } + } + } + spa_list_for_each(p, &n->output_ports, link) { + spa_list_for_each(l, &p->links, output_link) { + t = l->input->node; + + if (!t->active) + continue; + + pw_impl_link_prepare(l); + + if (!l->prepared) + continue; + + if (!l->passive) + t->runnable = true; + + if (!t->visited) { + t->visited = true; + spa_list_append(&queue, &t->sort_link); + } + } + } + /* now go through all the nodes that have the same group and + * that are not yet visited */ + if (n->groups != NULL || n->link_groups != NULL || sync[0] != NULL) { + spa_list_for_each(t, &context->node_list, link) { + if (t->exported || !t->active || t->visited) + 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(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); + } + /* All non-driver runnable nodes (ie. reachable with a non-passive link) now make + * all linked nodes up and downstream runnable as well */ + spa_list_for_each(n, collect, sort_link) { + if (!n->driver && n->runnable) { + run_nodes(context, n, collect, PW_DIRECTION_OUTPUT, 0); + run_nodes(context, n, collect, PW_DIRECTION_INPUT, 0); + } + } + /* now we might have made a driver runnable, if the node is not runnable at this point + * it means it was linked to the driver with passives links and some other node + * made the driver active. If the node is a leaf it can not be activated in any other + * way and we will also make it, and all its peers, runnable */ + spa_list_for_each(n, collect, sort_link) { + if (!n->driver && n->driver_node->runnable && !n->runnable && n->leaf && n->active) { + n->runnable = true; + run_nodes(context, n, collect, PW_DIRECTION_OUTPUT, 0); + run_nodes(context, n, collect, PW_DIRECTION_INPUT, 0); + } + } + + 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 3 stages: + * + * 1. go over all drivers and collect the nodes that need to be scheduled with the + * driver. This include all nodes that have an active link with the driver or + * with a node already scheduled with the driver. + * + * 2. go over all nodes that are not assigned to a driver. The ones that require + * a driver are moved to some random active driver found in step 1. + * + * 3. go over all drivers again, collect the quantum/rate of all followers, select + * the desired final value and activate the followers and then the driver. + * + * A complete graph evaluation is performed for each change that is made to the + * graph, such as making/destroying links, adding/removing nodes, property changes such + * as quantum/rate changes or metadata changes. + */ int pw_context_recalc_graph(struct pw_context *context, const char *reason) { struct impl *impl = SPA_CONTAINER_OF(context, struct impl, this); + struct settings *settings = &context->settings; + struct pw_impl_node *n, *s, *target, *fallback; + const uint32_t *rates; + uint32_t max_quantum, min_quantum, def_quantum, 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; pw_log_info("%p: busy:%d reason:%s", context, impl->recalc, reason); @@ -999,14 +1454,392 @@ int pw_context_recalc_graph(struct pw_context *context, const char *reason) again: impl->recalc = true; + freewheel = false; - pw_context_emit_recalc_graph(context); + /* clean up the flags first */ + spa_list_for_each(n, &context->node_list, link) { + n->visited = false; + n->checked = 0; + n->runnable = n->always_process && n->active; + } + 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; + + /* 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 */ + if (context->freewheeling != freewheel) + context_set_freewheel(context, freewheel); + + /* now go through all available nodes. The ones we didn't visit + * in collect_nodes() are not linked to any driver. We assign them + * to either an active driver or the first driver if they are in a + * group that needs a driver. Else we remove them from a driver + * and stop them. */ + spa_list_for_each(n, &context->node_list, link) { + struct pw_impl_node *t, *driver; + + if (n->exported || n->visited) + continue; + + pw_log_debug("%p: unassigned node %p: '%s' active:%d want_driver:%d target:%p", + context, n, n->name, n->active, n->want_driver, target); + + /* collect all nodes in this group */ + spa_list_init(&collect); + collect_nodes(context, n, &collect); + + driver = NULL; + spa_list_for_each(t, &collect, sort_link) { + /* is any active and want a driver */ + 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); + } impl->recalc = false; if (impl->recalc_pending) { impl->recalc_pending = false; goto again; } + return 0; } diff --git a/src/pipewire/context.h b/src/pipewire/context.h index 5eaa8de30..61c6662c4 100644 --- a/src/pipewire/context.h +++ b/src/pipewire/context.h @@ -51,7 +51,7 @@ struct pw_impl_node; /** context events emitted by the context object added with \ref pw_context_add_listener */ struct pw_context_events { -#define PW_VERSION_CONTEXT_EVENTS 2 +#define PW_VERSION_CONTEXT_EVENTS 1 uint32_t version; /** The context is being destroyed */ @@ -69,9 +69,6 @@ struct pw_context_events { void (*driver_added) (void *data, struct pw_impl_node *node); /** a driver was removed, since 0.3.75 version:1 */ void (*driver_removed) (void *data, struct pw_impl_node *node); - - /** recalculate the graph state, since 1.7.0 version:2 */ - void (*recalc_graph) (void *data); }; /** Make a new context object for a given main_loop. Ownership of the properties is taken, even diff --git a/src/pipewire/filter.c b/src/pipewire/filter.c index e88a5db0a..85d16a6b1 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -692,10 +692,7 @@ static int map_data(struct filter *impl, struct spa_data *data, int prot) void *ptr; struct pw_map_range range; - if (pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize) < 0) { - pw_log_error("%p: invalid buffer map range", impl); - return -EOVERFLOW; - } + pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize); ptr = mmap(NULL, range.size, prot, MAP_SHARED, data->fd, range.offset); if (ptr == MAP_FAILED) { @@ -724,8 +721,7 @@ static int unmap_data(struct filter *impl, struct spa_data *data) { struct pw_map_range range; - if (pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize) < 0) - return -EOVERFLOW; + pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize); if (munmap(SPA_PTROFF(data->data, -range.start, void), range.size) < 0) pw_log_warn("%p: failed to unmap: %m", impl); @@ -1797,13 +1793,11 @@ static void add_control_dsp_port_params(struct filter *impl, struct port *port, SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_application), SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_control), 0); -#if 0 if (types != 0) { spa_pod_builder_add(&b, SPA_FORMAT_CONTROL_types, SPA_POD_CHOICE_FLAGS_Int(types), 0); } -#endif add_param(impl, port, SPA_PARAM_EnumFormat, PARAM_FLAG_LOCKED, spa_pod_builder_pop(&b, &f[0])); } @@ -1863,13 +1857,10 @@ void *pw_filter_add_port(struct pw_filter *filter, add_video_dsp_port_params(impl, p); else if (spa_streq(str, "8 bit raw midi")) add_control_dsp_port_params(impl, p, 1u << SPA_CONTROL_Midi); - else if (spa_streq(str, "8 bit raw control")) { + else if (spa_streq(str, "8 bit raw control")) add_control_dsp_port_params(impl, p, 0); - pw_properties_set(props, "control.ump", "false"); - } else if (spa_streq(str, "32 bit raw UMP")) { add_control_dsp_port_params(impl, p, 1u << SPA_CONTROL_UMP); - pw_properties_set(props, "control.ump", "true"); pw_properties_set(props, PW_KEY_FORMAT_DSP, "8 bit raw midi"); } } diff --git a/src/pipewire/impl-client.c b/src/pipewire/impl-client.c index 6566d1a51..a13a40a5a 100644 --- a/src/pipewire/impl-client.c +++ b/src/pipewire/impl-client.c @@ -557,7 +557,7 @@ int pw_impl_client_register(struct pw_impl_client *client, client->registered = true; client->info.id = client->global->id; - pw_properties_setf(client->properties, PW_KEY_OBJECT_ID, "%u", client->info.id); + pw_properties_setf(client->properties, PW_KEY_OBJECT_ID, "%d", client->info.id); pw_properties_setf(client->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(client->global)); pw_global_add_listener(client->global, &client->global_listener, &global_events, client); diff --git a/src/pipewire/impl-core.c b/src/pipewire/impl-core.c index 3e5f0db8e..3a43a8e88 100644 --- a/src/pipewire/impl-core.c +++ b/src/pipewire/impl-core.c @@ -600,7 +600,7 @@ int pw_impl_core_register(struct pw_impl_core *core, core->registered = true; core->info.id = core->global->id; - pw_properties_setf(core->properties, PW_KEY_OBJECT_ID, "%u", core->info.id); + pw_properties_setf(core->properties, PW_KEY_OBJECT_ID, "%d", core->info.id); pw_properties_setf(core->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(core->global)); diff --git a/src/pipewire/impl-device.c b/src/pipewire/impl-device.c index 281199519..53b76b236 100644 --- a/src/pipewire/impl-device.c +++ b/src/pipewire/impl-device.c @@ -612,7 +612,7 @@ int pw_impl_device_register(struct pw_impl_device *device, device->registered = true; device->info.id = device->global->id; - pw_properties_setf(device->properties, PW_KEY_OBJECT_ID, "%u", device->info.id); + pw_properties_setf(device->properties, PW_KEY_OBJECT_ID, "%d", device->info.id); pw_properties_setf(device->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(device->global)); diff --git a/src/pipewire/impl-factory.c b/src/pipewire/impl-factory.c index 99900db47..9a8e1c508 100644 --- a/src/pipewire/impl-factory.c +++ b/src/pipewire/impl-factory.c @@ -185,7 +185,7 @@ int pw_impl_factory_register(struct pw_impl_factory *factory, factory->registered = true; factory->info.id = factory->global->id; - pw_properties_setf(factory->properties, PW_KEY_OBJECT_ID, "%u", factory->info.id); + pw_properties_setf(factory->properties, PW_KEY_OBJECT_ID, "%d", factory->info.id); pw_properties_setf(factory->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(factory->global)); pw_properties_set(factory->properties, PW_KEY_FACTORY_NAME, factory->info.name); diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index 9abc7c481..a7ea65cb2 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -972,14 +972,16 @@ static void output_remove(struct pw_impl_link *this) this->output = NULL; } -SPA_EXPORT int pw_impl_link_prepare(struct pw_impl_link *this) { struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); - pw_log_debug("%p: prepared:%d preparing:%d in_active:%d out_active:%d", + pw_log_debug("%p: prepared:%d preparing:%d in_active:%d out_active:%d passive:%u", this, this->prepared, this->preparing, - impl->input.node->active, impl->output.node->active); + impl->input.node->active, impl->output.node->active, this->passive); + + if (!impl->input.node->active || !impl->output.node->active) + return 0; if (this->destroyed || this->preparing || this->prepared) return 0; @@ -1054,13 +1056,13 @@ static void port_state_changed(struct pw_impl_link *this, struct pw_impl_port *p case PW_IMPL_PORT_STATE_INIT: case PW_IMPL_PORT_STATE_CONFIGURE: if (this->prepared || state < old) { - this->prepared = this->preparing = false; + this->prepared = false; link_update_state(this, PW_LINK_STATE_INIT, 0, NULL); } break; case PW_IMPL_PORT_STATE_READY: if (this->prepared || state < old) { - this->prepared = this->preparing = false; + this->prepared = false; link_update_state(this, PW_LINK_STATE_NEGOTIATING, 0, NULL); } break; @@ -1220,6 +1222,12 @@ static void output_node_result(void *data, int seq, int res, uint32_t type, cons node_result(impl, &impl->output, seq, res, type, result); } +static void node_active_changed(void *data, bool active) +{ + struct impl *impl = data; + pw_impl_link_prepare(&impl->this); +} + static void node_driver_changed(void *data, struct pw_impl_node *old, struct pw_impl_node *driver) { struct impl *impl = data; @@ -1234,12 +1242,14 @@ static void node_driver_changed(void *data, struct pw_impl_node *old, struct pw_ static const struct pw_impl_node_events input_node_events = { PW_VERSION_IMPL_NODE_EVENTS, .result = input_node_result, + .active_changed = node_active_changed, .driver_changed = node_driver_changed, }; static const struct pw_impl_node_events output_node_events = { PW_VERSION_IMPL_NODE_EVENTS, .result = output_node_result, + .active_changed = node_active_changed, .driver_changed = node_driver_changed, }; @@ -1469,6 +1479,7 @@ struct pw_impl_link *pw_context_create_link(struct pw_context *context, struct impl *impl; struct pw_impl_link *this; struct pw_impl_node *input_node, *output_node; + const char *str; int res; if (output == input) @@ -1518,6 +1529,15 @@ struct pw_impl_link *pw_context_create_link(struct pw_context *context, this->output = output; this->input = input; + /* passive means that this link does not make the nodes active */ + str = pw_properties_get(properties, PW_KEY_LINK_PASSIVE); + this->passive = str ? spa_atob(str) : + (output->passive && input_node->can_suspend) || + (input->passive && output_node->can_suspend) || + (input->passive && output->passive); + if (this->passive && str == NULL) + pw_properties_set(properties, PW_KEY_LINK_PASSIVE, "true"); + this->async = (output_node->async || input_node->async) && SPA_FLAG_IS_SET(output->flags, PW_IMPL_PORT_FLAG_ASYNC) && SPA_FLAG_IS_SET(input->flags, PW_IMPL_PORT_FLAG_ASYNC); @@ -1687,7 +1707,7 @@ int pw_impl_link_register(struct pw_impl_link *link, link->registered = true; link->info.id = link->global->id; - pw_properties_setf(link->properties, PW_KEY_OBJECT_ID, "%u", link->info.id); + pw_properties_setf(link->properties, PW_KEY_OBJECT_ID, "%d", link->info.id); pw_properties_setf(link->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(link->global)); pw_properties_setf(link->properties, PW_KEY_LINK_OUTPUT_NODE, "%u", link->info.output_node_id); diff --git a/src/pipewire/impl-module.c b/src/pipewire/impl-module.c index f754dd9b5..22c8e91fa 100644 --- a/src/pipewire/impl-module.c +++ b/src/pipewire/impl-module.c @@ -141,7 +141,7 @@ pw_context_load_module(struct pw_context *context, { struct pw_impl_module *this; struct impl *impl; - void *hnd = NULL; + void *hnd; char *filename = NULL; const char *module_dir; int res; @@ -154,9 +154,6 @@ pw_context_load_module(struct pw_context *context, NULL }; - while ((p = strstr(name, "../")) != NULL) - name = p + 3; - pw_log_info("%p: name:%s args:%s", context, name, args); module_dir = getenv("PIPEWIRE_MODULE_DIR"); @@ -235,7 +232,7 @@ pw_context_load_module(struct pw_context *context, goto error_no_global; this->info.id = this->global->id; - pw_properties_setf(this->properties, PW_KEY_OBJECT_ID, "%u", this->info.id); + pw_properties_setf(this->properties, PW_KEY_OBJECT_ID, "%d", this->info.id); pw_properties_setf(this->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(this->global)); diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index e38b1640e..85bf452b0 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -306,7 +306,7 @@ static int idle_node(struct pw_impl_node *this) pw_node_state_as_string(impl->pending_state), this->pause_on_idle); - if (impl->pending_state == PW_NODE_STATE_IDLE) + if (impl->pending_state <= PW_NODE_STATE_IDLE) return 0; if (!this->pause_on_idle) @@ -528,17 +528,30 @@ static void node_update_state(struct pw_impl_node *node, enum pw_node_state stat static int suspend_node(struct pw_impl_node *this) { - struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); int res = 0; struct pw_impl_port *p; pw_log_debug("%p: suspend node state:%s", this, pw_node_state_as_string(this->info.state)); - if ((this->info.state > 0 && this->info.state < PW_NODE_STATE_SUSPENDED) || - (this->info.state == PW_NODE_STATE_SUSPENDED && impl->pending_state == PW_NODE_STATE_SUSPENDED)) + if (this->info.state > 0 && this->info.state <= PW_NODE_STATE_SUSPENDED) return 0; + spa_list_for_each(p, &this->input_ports, link) { + if (p->busy_count > 0) { + pw_log_debug("%p: can't suspend, input port %d busy:%d", + this, p->port_id, p->busy_count); + return -EBUSY; + } + } + spa_list_for_each(p, &this->output_ports, link) { + if (p->busy_count > 0) { + pw_log_debug("%p: can't suspend, output port %d busy:%d", + this, p->port_id, p->busy_count); + return -EBUSY; + } + } + node_deactivate(this); pw_log_debug("%p: suspend node driving:%d driver:%d prepared:%d", this, @@ -988,7 +1001,7 @@ int pw_impl_node_register(struct pw_impl_node *this, this->from_driver_peer = pw_node_peer_ref(this, this); this->to_driver_peer = pw_node_peer_ref(this, this); - pw_properties_setf(this->properties, PW_KEY_OBJECT_ID, "%u", this->global->id); + pw_properties_setf(this->properties, PW_KEY_OBJECT_ID, "%d", this->global->id); pw_properties_setf(this->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(this->global)); @@ -1123,12 +1136,11 @@ static void check_properties(struct pw_impl_node *node) { struct impl *impl = SPA_CONTAINER_OF(node, struct impl, this); struct pw_context *context = node->context; - const char *str, *recalc_reason = NULL, *state = NULL, *s; + const char *str, *recalc_reason = NULL; struct spa_fraction frac; uint32_t value; bool driver, trigger, sync, async; struct match match; - size_t len; match = MATCH_INIT(node); pw_context_conf_section_match_rules(context, "node.rules", @@ -1249,29 +1261,20 @@ static void check_properties(struct pw_impl_node *node) SPA_FLAG_UPDATE(node->rt.target.activation->flags, PW_NODE_ACTIVATION_FLAG_ASYNC, async); } - if ((str = pw_properties_get(node->properties, PW_KEY_NODE_PASSIVE)) == NULL) { - if ((str = pw_properties_get(node->properties, PW_KEY_MEDIA_CLASS)) != NULL && - (strstr(str, "/Duplex") || strstr(str, "/Sink") || strstr(str, "/Source"))) - str = "follow-suspend"; - else - str = "false"; - } - while ((s = pw_split_walk(str, ",\0", &len, &state))) { - char v[32] = { 0 }; - snprintf(v, sizeof(v), "%.*s", (int)len, s); - - if (spa_streq(v, "out")) - node->passive_mode[SPA_DIRECTION_OUTPUT] = PASSIVE_MODE_TRUE; - else if (spa_streq(v, "in")) - node->passive_mode[SPA_DIRECTION_INPUT] = PASSIVE_MODE_TRUE; - else if (spa_strstartswith(v, "out-")) - node->passive_mode[SPA_DIRECTION_OUTPUT] = passive_mode_from_string(v+4); - else if (spa_strstartswith(v, "in-")) - node->passive_mode[SPA_DIRECTION_INPUT] = passive_mode_from_string(v+3); - else - node->passive_mode[SPA_DIRECTION_INPUT] = - node->passive_mode[SPA_DIRECTION_OUTPUT] = passive_mode_from_string(v); + if ((str = pw_properties_get(node->properties, PW_KEY_MEDIA_CLASS)) != NULL && + (strstr(str, "/Sink") != NULL || strstr(str, "/Source") != NULL)) { + node->can_suspend = true; + } else { + node->can_suspend = false; } + if ((str = pw_properties_get(node->properties, PW_KEY_NODE_PASSIVE)) == NULL) + str = "false"; + if (spa_streq(str, "out")) + node->out_passive = true; + else if (spa_streq(str, "in")) + node->in_passive = true; + else + node->in_passive = node->out_passive = spa_atob(str); node->want_driver = pw_properties_get_bool(node->properties, PW_KEY_NODE_WANT_DRIVER, false); node->always_process = pw_properties_get_bool(node->properties, PW_KEY_NODE_ALWAYS_PROCESS, false); @@ -1322,6 +1325,10 @@ static void check_properties(struct pw_impl_node *node) } } node->lock_rate = pw_properties_get_bool(node->properties, PW_KEY_NODE_LOCK_RATE, false); + /* the leaf node is one that only produces/consumes the data. We can deduce this from the + * absence of a link-group and the fact that it has no output/input ports. */ + node->leaf = node->link_groups == NULL && + (node->info.max_input_ports == 0 || node->info.max_output_ports == 0); value = pw_properties_get_uint32(node->properties, PW_KEY_NODE_FORCE_RATE, SPA_ID_INVALID); if (value == 0) @@ -1336,10 +1343,8 @@ static void check_properties(struct pw_impl_node *node) recalc_reason = "force rate changed"; } - pw_log_debug("%p: driver:%d recalc:%s active:%d passive:%s:%s", node, node->driver, - recalc_reason, node->active, - passive_mode_to_string(node->passive_mode[0]), - passive_mode_to_string(node->passive_mode[1])); + pw_log_debug("%p: driver:%d recalc:%s active:%d", node, node->driver, + recalc_reason, node->active); if (recalc_reason != NULL && node->active) pw_context_recalc_graph(context, recalc_reason); @@ -2630,13 +2635,8 @@ int pw_impl_node_for_each_param(struct pw_impl_node *node, spa_hook_remove(&listener); if (user_data.cache) { - if (SPA_RESULT_IS_OK(res) && !SPA_RESULT_IS_ASYNC(res)) { - pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL); - pi->user = 1; - } - else { - pw_param_clear(&impl->pending_list, SPA_ID_INVALID); - } + pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL); + pi->user = 1; } } return res; diff --git a/src/pipewire/impl-port.c b/src/pipewire/impl-port.c index 19bd631e2..ed6fe9f1e 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -537,12 +537,10 @@ static int check_properties(struct pw_impl_port *port) &schedule_tee_node; } - if ((str = pw_properties_get(port->properties, PW_KEY_PORT_PASSIVE)) == NULL) { - /* inherit passive state from parent node */ - port->passive_mode = node->passive_mode[port->direction]; - } else { - port->passive_mode = passive_mode_from_string(str); - } + /* inherit passive state from parent node */ + port->passive = pw_properties_get_bool(port->properties, PW_KEY_PORT_PASSIVE, + port->direction == PW_DIRECTION_INPUT ? + node->in_passive : node->out_passive); if (media_class != NULL && (strstr(media_class, "Sink") != NULL || @@ -782,7 +780,7 @@ static int process_capability_param(void *data, int seq, if (spa_param_dict_compare(old, param) == 0) return 0; - pw_log_debug("port %p: got %s capability %p", this, + pw_log_debug("port %p: got %s capabilty %p", this, pw_direction_as_string(this->direction), param); if (param) pw_log_pod(SPA_LOG_LEVEL_DEBUG, param); @@ -1089,11 +1087,11 @@ int pw_impl_port_set_mix(struct pw_impl_port *port, struct spa_node *node, uint3 static int setup_mixer(struct pw_impl_port *port, const struct spa_pod *param) { - uint32_t media_type, media_subtype, n_items; + uint32_t media_type, media_subtype; int res; - const char *fallback_lib, *factory_name, *str; + const char *fallback_lib, *factory_name; struct spa_handle *handle; - struct spa_dict_item items[4]; + struct spa_dict_item items[3]; char quantum_limit[16]; void *iface; struct pw_context *context = port->node->context; @@ -1145,17 +1143,14 @@ static int setup_mixer(struct pw_impl_port *port, const struct spa_pod *param) return -ENOTSUP; } - n_items = 0; - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_LIBRARY_NAME, fallback_lib); + items[0] = SPA_DICT_ITEM_INIT(SPA_KEY_LIBRARY_NAME, fallback_lib); spa_scnprintf(quantum_limit, sizeof(quantum_limit), "%u", context->settings.clock_quantum_limit); - items[n_items++] = SPA_DICT_ITEM_INIT("clock.quantum-limit", quantum_limit); - items[n_items++] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_LOOP_NAME, port->node->data_loop->name); - if ((str = pw_properties_get(port->properties, "control.ump")) != NULL) - items[n_items++] = SPA_DICT_ITEM_INIT("control.ump", str); + items[1] = SPA_DICT_ITEM_INIT("clock.quantum-limit", quantum_limit); + items[2] = SPA_DICT_ITEM_INIT(PW_KEY_NODE_LOOP_NAME, port->node->data_loop->name); handle = pw_context_load_spa_handle(context, factory_name, - &SPA_DICT_INIT(items, n_items)); + &SPA_DICT_INIT_ARRAY(items)); if (handle == NULL) return -errno; @@ -1395,8 +1390,8 @@ int pw_impl_port_register(struct pw_impl_port *port, pw_global_add_listener(port->global, &port->global_listener, &global_events, port); port->info.id = port->global->id; - pw_properties_setf(port->properties, PW_KEY_NODE_ID, "%u", node->global->id); - pw_properties_setf(port->properties, PW_KEY_OBJECT_ID, "%u", port->info.id); + pw_properties_setf(port->properties, PW_KEY_NODE_ID, "%d", node->global->id); + pw_properties_setf(port->properties, PW_KEY_OBJECT_ID, "%d", port->info.id); pw_properties_setf(port->properties, PW_KEY_OBJECT_SERIAL, "%"PRIu64, pw_global_get_serial(port->global)); @@ -1730,13 +1725,8 @@ int pw_impl_port_for_each_param(struct pw_impl_port *port, spa_hook_remove(&listener); if (user_data.cache) { - if (SPA_RESULT_IS_OK(res) && !SPA_RESULT_IS_ASYNC(res)) { - pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL); - pi->user = 1; - } - else { - pw_param_clear(&impl->pending_list, SPA_ID_INVALID); - } + pw_param_update(&impl->param_list, &impl->pending_list, 0, NULL); + pi->user = 1; } } @@ -1944,7 +1934,7 @@ int pw_impl_port_recalc_tag(struct pw_impl_port *port) count++; } } - param = spa_tag_build_end(&b.b, &f); + param = count == 0 ? NULL : spa_tag_build_end(&b.b, &f); old = port->tag[direction]; diff --git a/src/pipewire/keys.h b/src/pipewire/keys.h index 08e6fb2df..13694bc29 100644 --- a/src/pipewire/keys.h +++ b/src/pipewire/keys.h @@ -212,9 +212,8 @@ extern "C" { #define PW_KEY_NODE_VIRTUAL "node.virtual" /**< the node is some sort of virtual * object */ #define PW_KEY_NODE_PASSIVE "node.passive" /**< indicate that a node wants passive links - * on output/input/all ports. A ','-separated - * array of values "out"/"out-follow"/"in"/ - * "in-follow"/"true"/"follow" is accepted. */ + * on output/input/all ports when the value is + * "out"/"in"/"true" respectively */ #define PW_KEY_NODE_LINK_GROUP "node.link-group" /**< the node is internally linked to * nodes with the same link-group. Can be an * array of group names. */ @@ -248,8 +247,7 @@ extern "C" { #define PW_KEY_PORT_CACHE_PARAMS "port.cache-params" /**< cache the node port params */ #define PW_KEY_PORT_EXTRA "port.extra" /**< api specific extra port info, API name * should be prefixed. "jack:flags:56" */ -#define PW_KEY_PORT_PASSIVE "port.passive" /**< the ports wants passive links. Values - * can be "true" or "follow". Since 0.3.67 */ +#define PW_KEY_PORT_PASSIVE "port.passive" /**< the ports wants passive links, since 0.3.67 */ #define PW_KEY_PORT_IGNORE_LATENCY "port.ignore-latency" /**< latency ignored by peers, since 0.3.71 */ #define PW_KEY_PORT_GROUP "port.group" /**< the port group of the port 1.2.0 */ #define PW_KEY_PORT_EXCLUSIVE "port.exclusive" /**< link port only once 1.6.0 */ @@ -263,8 +261,7 @@ extern "C" { #define PW_KEY_LINK_OUTPUT_PORT "link.output.port" /**< output port id of a link */ #define PW_KEY_LINK_PASSIVE "link.passive" /**< indicate that a link is passive and * does not cause the graph to be - * runnable. Deprecated look at - * port.passive properties. */ + * runnable. */ #define PW_KEY_LINK_FEEDBACK "link.feedback" /**< indicate that a link is a feedback * link and the target will receive data * in the next cycle */ diff --git a/src/pipewire/mem.c b/src/pipewire/mem.c index 32389ed85..642a6b78e 100644 --- a/src/pipewire/mem.c +++ b/src/pipewire/mem.c @@ -15,7 +15,6 @@ #include #include -#include #include #include @@ -421,10 +420,7 @@ struct pw_memmap * pw_memblock_map(struct pw_memblock *block, m = memblock_find_mapping(b, flags, offset, size); if (m == NULL) { struct pw_map_range range; - if (pw_map_range_init(&range, offset, size, p->pagesize) < 0) { - errno = EOVERFLOW; - return NULL; - } + pw_map_range_init(&range, offset, size, p->pagesize); m = memblock_map(b, flags, range.offset, range.size); if (m == NULL) @@ -860,10 +856,8 @@ void pw_memblock_free(struct pw_memblock *block) } if (block->fd != -1 && !(block->flags & PW_MEMBLOCK_FLAG_DONT_CLOSE)) { - int fd = spa_steal_fd(block->fd); - pw_log_debug("%p: block:%p close fd:%d", pool, block, fd); - if (close(fd) < 0) - pw_log_error("%p: block:%p close fd:%d failed: %m", pool, block, fd); + pw_log_debug("%p: close fd:%d", pool, block->fd); + close(block->fd); } spa_hook_list_clean(&b->listener_list); diff --git a/src/pipewire/mem.h b/src/pipewire/mem.h index 0c3ce73bc..52abbd54d 100644 --- a/src/pipewire/mem.h +++ b/src/pipewire/mem.h @@ -178,21 +178,14 @@ struct pw_map_range { #define PW_MAP_RANGE_INIT (struct pw_map_range){ 0, } /** Calculate parameters to mmap() memory into \a range so that - * \a size bytes at \a offset can be mapped with mmap(). - * Returns 0 on success, -EOVERFLOW if offset + size overflows. */ -PW_API_MEM int pw_map_range_init(struct pw_map_range *range, + * \a size bytes at \a offset can be mapped with mmap(). */ +PW_API_MEM void pw_map_range_init(struct pw_map_range *range, uint32_t offset, uint32_t size, uint32_t page_size) { range->offset = SPA_ROUND_DOWN_N(offset, page_size); range->start = offset - range->offset; - if (size > UINT32_MAX - range->start) - return -EOVERFLOW; - /* Check that rounding up to page_size won't overflow */ - if (range->start + size > UINT32_MAX - (page_size - 1)) - return -EOVERFLOW; range->size = SPA_ROUND_UP_N(range->start + size, page_size); - return 0; } /** diff --git a/src/pipewire/pipewire.c b/src/pipewire/pipewire.c index 89d0518b8..4451fa525 100644 --- a/src/pipewire/pipewire.c +++ b/src/pipewire/pipewire.c @@ -232,9 +232,6 @@ static struct spa_handle *load_spa_handle(const char *lib, if (lib == NULL) lib = sup->support_lib; - while ((p = strstr(lib, "../")) != NULL) - lib = p + 3; - pw_log_debug("load lib:'%s' factory-name:'%s'", lib, factory_name); plugin = NULL; @@ -300,18 +297,10 @@ struct spa_handle *pw_load_spa_handle(const char *lib, const struct spa_support support[]) { struct spa_handle *handle; - struct support *sup = &global_support; pthread_mutex_lock(&support_lock); - if (sup->init_count == 0) - goto error; handle = load_spa_handle(lib, factory_name, info, n_support, support); pthread_mutex_unlock(&support_lock); return handle; -error: - pw_log_error("load lib: pw_init() was not called"); - pthread_mutex_unlock(&support_lock); - errno = EBADFD; - return NULL; } static struct handle *find_handle(struct spa_handle *handle) diff --git a/src/pipewire/private.h b/src/pipewire/private.h index dd81178ff..5582709ad 100644 --- a/src/pipewire/private.h +++ b/src/pipewire/private.h @@ -364,7 +364,6 @@ pw_core_resource_errorf(struct pw_resource *resource, uint32_t id, int seq, #define pw_context_emit_global_removed(c,g) pw_context_emit(c, global_removed, 0, g) #define pw_context_emit_driver_added(c,n) pw_context_emit(c, driver_added, 1, n) #define pw_context_emit_driver_removed(c,n) pw_context_emit(c, driver_removed, 1, n) -#define pw_context_emit_recalc_graph(c) pw_context_emit(c, recalc_graph, 2) struct pw_context { struct pw_impl_core *core; /**< core object */ @@ -762,6 +761,8 @@ struct pw_impl_node { * is selected to drive the graph */ unsigned int visited:1; /**< for sorting */ unsigned int want_driver:1; /**< this node wants to be assigned to a driver */ + unsigned int in_passive:1; /**< node input links should be passive */ + unsigned int out_passive:1; /**< node output links should be passive */ unsigned int runnable:1; /**< node is runnable */ unsigned int freewheel:1; /**< if this is the freewheel driver */ unsigned int loopchecked:1; /**< for feedback loop checking */ @@ -778,19 +779,15 @@ struct pw_impl_node { unsigned int forced_quantum:1; unsigned int trigger:1; /**< has the TRIGGER property and needs an extra * trigger to start processing. */ + unsigned int can_suspend:1; unsigned int checked; /**< for sorting */ unsigned int sync:1; /**< the sync-groups are active */ unsigned int async:1; /**< async processing, one cycle latency */ unsigned int lazy:1; /**< the graph is lazy scheduling */ unsigned int exclusive:1; /**< ports can only be linked once */ + unsigned int leaf:1; /**< node only produces/consumes data */ unsigned int reliable:1; /**< ports need reliable tee */ -#define PASSIVE_MODE_FALSE 0 -#define PASSIVE_MODE_TRUE 1 -#define PASSIVE_MODE_FOLLOW 2 -#define PASSIVE_MODE_FOLLOW_SUSPEND 3 - int passive_mode[2]; /**< node input links should be passive */ - uint32_t transport; /**< latest transport request */ uint32_t port_user_data_size; /**< extra size for port user data */ @@ -964,9 +961,8 @@ struct pw_impl_port { struct spa_list node_link; bool added; } rt; /**< data only accessed from the data thread */ - int passive_mode; - unsigned int destroying:1; + unsigned int passive:1; unsigned int auto_path:1; /* path was automatically generated */ unsigned int auto_name:1; /* name was automatically generated */ unsigned int auto_alias:1; /* alias was automatically generated */ @@ -989,30 +985,6 @@ struct pw_impl_port { void *user_data; /**< extra user data */ }; -static inline const char* passive_mode_to_string(uint32_t passive_mode) -{ - switch (passive_mode) { - case PASSIVE_MODE_FALSE: - return "false"; - case PASSIVE_MODE_TRUE: - return "true"; - case PASSIVE_MODE_FOLLOW: - return "follow"; - case PASSIVE_MODE_FOLLOW_SUSPEND: - return "follow"; - } - return "unknown"; -} - -static inline uint32_t passive_mode_from_string(const char *str) -{ - if (spa_streq(str, "follow")) - return PASSIVE_MODE_FOLLOW; - else if (spa_streq(str, "follow-suspend")) - return PASSIVE_MODE_FOLLOW_SUSPEND; - return spa_atob(str); -} - struct pw_control_link { struct spa_list out_link; struct spa_list in_link; @@ -1068,6 +1040,7 @@ struct pw_impl_link { unsigned int feedback:1; unsigned int preparing:1; unsigned int prepared:1; + unsigned int passive:1; unsigned int destroyed:1; }; @@ -1296,8 +1269,6 @@ int pw_context_debug_port_params(struct pw_context *context, struct spa_node *node, enum spa_direction direction, uint32_t port_id, uint32_t id, int err, const char *debug, ...); -int pw_context_set_freewheel(struct pw_context *context, bool freewheel); - int pw_proxy_init(struct pw_proxy *proxy, struct pw_core *core, const char *type, uint32_t version); void pw_proxy_remove(struct pw_proxy *proxy); diff --git a/src/pipewire/properties.c b/src/pipewire/properties.c index 3d7686f7a..ac0aac0d0 100644 --- a/src/pipewire/properties.c +++ b/src/pipewire/properties.c @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include @@ -800,32 +800,156 @@ const char *pw_properties_iterate(const struct pw_properties *properties, void * return pw_array_get_unchecked(&impl->items, index, struct spa_dict_item)->key; } +#define NORMAL(c) ((c)->colors ? SPA_ANSI_RESET : "") +#define LITERAL(c) ((c)->colors ? SPA_ANSI_BRIGHT_MAGENTA : "") +#define NUMBER(c) ((c)->colors ? SPA_ANSI_BRIGHT_CYAN : "") +#define STRING(c) ((c)->colors ? SPA_ANSI_BRIGHT_GREEN : "") +#define KEY(c) ((c)->colors ? SPA_ANSI_BRIGHT_BLUE : "") +#define CONTAINER(c) ((c)->colors ? SPA_ANSI_BRIGHT_YELLOW : "") + +struct dump_config { + FILE *file; + int indent; + const char *sep; + bool colors; + bool recurse; +}; + +static int encode_string(struct dump_config *c, const char *before, + const char *val, int size, const char *after) +{ + FILE *f = c->file; + int i, len = 0; + len += fprintf(f, "%s\"", before); + for (i = 0; i < size; 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; +} + +static int dump(struct dump_config *c, int indent, struct spa_json *it, const char *value, int len) +{ + FILE *file = c->file; + struct spa_json sub; + int count = 0; + char key[1024]; + + if (value == NULL || len == 0) { + fprintf(file, "%snull%s", LITERAL(c), NORMAL(c)); + } else if (spa_json_is_container(value, len) && !c->recurse) { + spa_json_enter_container(it, &sub, value[0]); + if (spa_json_container_len(&sub, value, len) == len) + fprintf(file, "%s%.*s%s", CONTAINER(c), len, value, NORMAL(c)); + else + encode_string(c, STRING(c), value, len, NORMAL(c)); + } else if (spa_json_is_array(value, len)) { + fprintf(file, "["); + spa_json_enter(it, &sub); + indent += c->indent; + while ((len = spa_json_next(&sub, &value)) > 0) { + fprintf(file, "%s%s%*s", count++ > 0 ? "," : "", + c->sep, indent, ""); + dump(c, indent, &sub, value, len); + } + indent -= c->indent; + fprintf(file, "%s%*s]", count > 0 ? c->sep : "", + count > 0 ? indent : 0, ""); + } else if (spa_json_is_object(value, len)) { + fprintf(file, "{"); + spa_json_enter(it, &sub); + indent += c->indent; + while ((len = spa_json_object_next(&sub, key, sizeof(key), &value)) > 0) { + fprintf(file, "%s%s%*s", + count++ > 0 ? "," : "", + c->sep, indent, ""); + encode_string(c, KEY(c), key, strlen(key), NORMAL(c)); + fprintf(file, ": "); + dump(c, indent, &sub, value, len); + } + indent -= c->indent; + fprintf(file, "%s%*s}", count > 0 ? c->sep : "", + count > 0 ? indent : 0, ""); + } else if (spa_json_is_null(value, len) || + spa_json_is_bool(value, len)) { + fprintf(file, "%s%.*s%s", LITERAL(c), len, value, NORMAL(c)); + } else if (spa_json_is_int(value, len) || + spa_json_is_float(value, len)) { + fprintf(file, "%s%.*s%s", NUMBER(c), len, value, NORMAL(c)); + } else if (spa_json_is_string(value, len)) { + fprintf(file, "%s%.*s%s", STRING(c), len, value, NORMAL(c)); + } else { + encode_string(c, STRING(c), value, len, NORMAL(c)); + } + return 0; +} + SPA_EXPORT int pw_properties_serialize_dict(FILE *f, const struct spa_dict *dict, uint32_t flags) { const struct spa_dict_item *it; - int count = 0, fl = 0; - struct spa_json_builder b; - bool array = flags & PW_PROPERTIES_FLAG_ARRAY; - bool recurse = flags & PW_PROPERTIES_FLAG_RECURSE; - - if (flags & PW_PROPERTIES_FLAG_NL) - fl |= SPA_JSON_BUILDER_FLAG_PRETTY; - if (flags & PW_PROPERTIES_FLAG_COLORS) - fl |= SPA_JSON_BUILDER_FLAG_COLOR; - if (flags & PW_PROPERTIES_FLAG_SIMPLE) - fl |= SPA_JSON_BUILDER_FLAG_SIMPLE; - - spa_json_builder_file(&b, f, fl); + int count = 0; + struct dump_config cfg = { + .file = f, + .indent = flags & PW_PROPERTIES_FLAG_NL ? 2 : 0, + .sep = flags & PW_PROPERTIES_FLAG_NL ? "\n" : " ", + .colors = SPA_FLAG_IS_SET(flags, PW_PROPERTIES_FLAG_COLORS), + .recurse = SPA_FLAG_IS_SET(flags, PW_PROPERTIES_FLAG_RECURSE), + }, *c = &cfg; + const char *enc = flags & PW_PROPERTIES_FLAG_ARRAY ? "[]" : "{}"; if (SPA_FLAG_IS_SET(flags, PW_PROPERTIES_FLAG_ENCLOSE)) - spa_json_builder_array_push(&b, array ? "[" : "{"); + fprintf(f, "%c", enc[0]); spa_dict_for_each(it, dict) { - spa_json_builder_object_value(&b, recurse, array ? NULL : it->key, it->value); + char key[1024]; + int len; + const char *value; + struct spa_json sub; + + fprintf(f, "%s%s%*s", count == 0 ? "" : ",", c->sep, c->indent, ""); + + if (!(flags & PW_PROPERTIES_FLAG_ARRAY)) { + if (spa_json_encode_string(key, sizeof(key)-1, it->key) >= (int)sizeof(key)-1) + continue; + fprintf(f, "%s%s%s: ", KEY(c), key, NORMAL(c)); + } + value = it->value; + len = value ? strlen(value) : 0; + spa_json_init(&sub, value, len); + if (c->recurse && spa_json_next(&sub, &value) < 0) + break; + + dump(c, c->indent, &sub, value, len); count++; } if (SPA_FLAG_IS_SET(flags, PW_PROPERTIES_FLAG_ENCLOSE)) - spa_json_builder_pop(&b, array ? "]" : "}"); + fprintf(f, "%s%c", c->sep, enc[1]); return count; } diff --git a/src/pipewire/properties.h b/src/pipewire/properties.h index 3138a8e15..5dbcc283f 100644 --- a/src/pipewire/properties.h +++ b/src/pipewire/properties.h @@ -154,7 +154,6 @@ pw_properties_iterate(const struct pw_properties *properties, void **state); #define PW_PROPERTIES_FLAG_ENCLOSE (1<<2) #define PW_PROPERTIES_FLAG_ARRAY (1<<3) #define PW_PROPERTIES_FLAG_COLORS (1<<4) -#define PW_PROPERTIES_FLAG_SIMPLE (1<<5) int pw_properties_serialize_dict(FILE *f, const struct spa_dict *dict, uint32_t flags); PW_API_PROPERTIES bool pw_properties_parse_bool(const char *value) { diff --git a/src/pipewire/stream.c b/src/pipewire/stream.c index 36b63c000..4ee2138bc 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -807,10 +807,7 @@ static int map_data(struct stream *impl, struct spa_data *data, int prot) void *ptr; struct pw_map_range range; - if (pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize) < 0) { - pw_log_error("%p: invalid buffer map range", impl); - return -EOVERFLOW; - } + pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize); ptr = mmap(NULL, range.size, prot, MAP_SHARED, data->fd, range.offset); if (ptr == MAP_FAILED) { @@ -840,8 +837,7 @@ static int unmap_data(struct stream *impl, struct spa_data *data) { struct pw_map_range range; - if (pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize) < 0) - return -EOVERFLOW; + pw_map_range_init(&range, data->mapoffset, data->maxsize, impl->context->sc_pagesize); if (munmap(SPA_PTROFF(data->data, -range.start, void), range.size) < 0) pw_log_warn("%p: failed to unmap: %m", impl); diff --git a/src/pipewire/thread.c b/src/pipewire/thread.c index 1637a8af7..3af0b4a44 100644 --- a/src/pipewire/thread.c +++ b/src/pipewire/thread.c @@ -92,8 +92,9 @@ static struct spa_thread *impl_create(void *object, pthread_t pt; pthread_attr_t *attr = NULL, attributes; const char *str; - int err; + int err, old_policy, new_policy; int (*create_func)(pthread_t *, const pthread_attr_t *attr, void *(*start)(void*), void *) = NULL; + struct sched_param sp; bool reset_on_fork = true; attr = pw_thread_fill_attr(props, &attributes); @@ -123,19 +124,11 @@ static struct spa_thread *impl_create(void *object, reset_on_fork = spa_atob(str); } -#ifdef SCHED_RESET_ON_FORK - int old_policy, new_policy; - struct sched_param sp; - pthread_getschedparam(pt, &old_policy, &sp); new_policy = old_policy; SPA_FLAG_UPDATE(new_policy, SCHED_RESET_ON_FORK, reset_on_fork); if (old_policy != new_policy) pthread_setschedparam(pt, new_policy, &sp); -#else - if (reset_on_fork) - pw_log_debug("SCHED_RESET_ON_FORK is not supported on this platform"); -#endif return (struct spa_thread*)pt; } diff --git a/src/tools/meson.build b/src/tools/meson.build index a9a112aa0..8147906fb 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -95,58 +95,6 @@ if build_pw_cat summary({'Build pw-cat with FFmpeg integration': build_pw_cat_with_ffmpeg}, bool_yn: true, section: 'pw-cat/pw-play/pw-dump tool') endif -if sndfile_dep.found() - executable('pw-audioconvert', - 'pw-audioconvert.c', - install: true, - dependencies : [pipewire_dep, sndfile_dep, mathlib], - ) -endif - -build_avb_virtual = get_option('avb-virtual').require( - host_machine.system() == 'linux', - error_message: 'AVB support is only available on Linux' -).allowed() - -if build_avb_virtual - avb_tool_inc = include_directories('../modules') - avb_tool_sources = [ - 'pw-avb-virtual.c', - '../modules/module-avb/avb.c', - '../modules/module-avb/adp.c', - '../modules/module-avb/acmp.c', - '../modules/module-avb/aecp.c', - '../modules/module-avb/aecp-aem.c', - '../modules/module-avb/aecp-aem-cmds-resps/cmd-available.c', - '../modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-control.c', - '../modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-name.c', - '../modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-clock-source.c', - '../modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-sampling-rate.c', - '../modules/module-avb/aecp-aem-cmds-resps/cmd-deregister-unsolicited-notifications.c', - '../modules/module-avb/aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.c', - '../modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-stream-format.c', - '../modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c', - '../modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-configuration.c', - '../modules/module-avb/aecp-aem-cmds-resps/reply-unsol-helpers.c', - '../modules/module-avb/es-builder.c', - '../modules/module-avb/avdecc.c', - '../modules/module-avb/descriptors.c', - '../modules/module-avb/maap.c', - '../modules/module-avb/mmrp.c', - '../modules/module-avb/mrp.c', - '../modules/module-avb/msrp.c', - '../modules/module-avb/mvrp.c', - '../modules/module-avb/srp.c', - '../modules/module-avb/stream.c', - ] - executable('pw-avb-virtual', - avb_tool_sources, - install: true, - include_directories: [configinc, avb_tool_inc], - dependencies: [mathlib, dl_lib, rt_lib, pipewire_dep], - ) -endif - if dbus_dep.found() executable('pw-reserve', 'reserve.h', diff --git a/src/tools/midifile.c b/src/tools/midifile.c index 6bb70f1c0..974cec6e5 100644 --- a/src/tools/midifile.c +++ b/src/tools/midifile.c @@ -152,12 +152,7 @@ static uint8_t *ensure_buffer(struct midi_file *mf, struct midi_track *tr, size_ return tr->event; if (size > mf->buffer_size) { - uint8_t *newbuf = realloc(mf->buffer, size); - - if (newbuf == NULL) - return NULL; - - mf->buffer = newbuf; + mf->buffer = realloc(mf->buffer, size); mf->buffer_size = size; } return mf->buffer; diff --git a/src/tools/pw-audioconvert.c b/src/tools/pw-audioconvert.c deleted file mode 100644 index 8bc5a0de9..000000000 --- a/src/tools/pw-audioconvert.c +++ /dev/null @@ -1,672 +0,0 @@ -/* PipeWire - pw-filter-graph */ -/* 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 -#include - -#include - -#define MAX_SAMPLES 4096u - -enum { - OPT_CHANNELMAP = 1000, -}; - -struct data { - bool verbose; - int rate; - int format; - uint32_t blocksize; - int out_channels; - const char *channel_map; - struct pw_properties *props; - - const char *iname; - SF_INFO iinfo; - SNDFILE *ifile; - - const char *oname; - SF_INFO oinfo; - SNDFILE *ofile; - - struct pw_main_loop *loop; - struct pw_context *context; - - struct spa_handle *handle; - struct spa_node *node; -}; - -#define STR_FMTS "(s8|s16|s32|f32|f64)" - -#define OPTIONS "hvr:f:b:P:c:" -static const struct option long_options[] = { - { "help", no_argument, NULL, 'h' }, - { "verbose", no_argument, NULL, 'v' }, - - { "rate", required_argument, NULL, 'r' }, - { "format", required_argument, NULL, 'f' }, - { "blocksize", required_argument, NULL, 'b' }, - { "properties", required_argument, NULL, 'P' }, - { "channels", required_argument, NULL, 'c' }, - { "channel-map", required_argument, NULL, OPT_CHANNELMAP }, - - { NULL, 0, NULL, 0 } -}; - -static void show_usage(const char *name, bool is_error) -{ - FILE *fp; - - fp = is_error ? stderr : stdout; - - fprintf(fp, "%s [options] \n", name); - fprintf(fp, - " -h, --help Show this help\n" - " -v --verbose Be verbose\n" - "\n"); - fprintf(fp, - " -r --rate Output sample rate (default as input)\n" - " -f --format Output sample format %s (default as input)\n" - " -b --blocksize Number of samples per iteration (default %u)\n" - " -P --properties Set node properties (optional)\n" - " Use @filename to read from file\n" - " -c --channels Output channel count\n" - " --channel-map Output channel layout (e.g. \"stereo\", \"5.1\",\n" - " \"FL,FR,FC,LFE,SL,SR\")\n", - STR_FMTS, MAX_SAMPLES); - fprintf(fp, "\n"); -} - -static inline const char * -sf_fmt_to_str(int fmt) -{ - switch(fmt & SF_FORMAT_SUBMASK) { - case SF_FORMAT_PCM_S8: - return "s8"; - case SF_FORMAT_PCM_16: - return "s16"; - case SF_FORMAT_PCM_24: - return "s24"; - case SF_FORMAT_PCM_32: - return "s32"; - case SF_FORMAT_FLOAT: - return "f32"; - case SF_FORMAT_DOUBLE: - return "f64"; - default: - return "unknown"; - } -} - -static inline int -sf_str_to_fmt(const char *str) -{ - if (!str) - return -1; - if (spa_streq(str, "s8")) - return SF_FORMAT_PCM_S8; - if (spa_streq(str, "s16")) - return SF_FORMAT_PCM_16; - if (spa_streq(str, "s24")) - return SF_FORMAT_PCM_24; - if (spa_streq(str, "s32")) - return SF_FORMAT_PCM_32; - if (spa_streq(str, "f32")) - return SF_FORMAT_FLOAT; - if (spa_streq(str, "f64")) - return SF_FORMAT_DOUBLE; - return -1; -} - -static int parse_channelmap(const char *channel_map, struct spa_audio_layout_info *map) -{ - if (spa_audio_layout_info_parse_name(map, sizeof(*map), channel_map) >= 0) - return 0; - - spa_audio_parse_position_n(channel_map, strlen(channel_map), - map->position, SPA_N_ELEMENTS(map->position), &map->n_channels); - return map->n_channels > 0 ? 0 : -EINVAL; -} - -static int channelmap_default(struct spa_audio_layout_info *map, int n_channels) -{ - switch(n_channels) { - case 1: parse_channelmap("Mono", map); break; - case 2: parse_channelmap("Stereo", map); break; - case 3: parse_channelmap("2.1", map); break; - case 4: parse_channelmap("Quad", map); break; - case 5: parse_channelmap("5.0", map); break; - case 6: parse_channelmap("5.1", map); break; - case 7: parse_channelmap("7.0", map); break; - case 8: parse_channelmap("7.1", map); break; - default: n_channels = 0; break; - } - map->n_channels = n_channels; - return 0; -} - -static int open_input(struct data *d) -{ - d->ifile = sf_open(d->iname, SFM_READ, &d->iinfo); - if (d->ifile == NULL) { - fprintf(stderr, "error: failed to open input file \"%s\": %s\n", - d->iname, sf_strerror(NULL)); - return -EIO; - } - if (d->verbose) - fprintf(stdout, "input '%s': channels:%d rate:%d format:%s\n", - d->iname, d->iinfo.channels, d->iinfo.samplerate, - sf_fmt_to_str(d->iinfo.format)); - return 0; -} - -static int open_output(struct data *d, int channels) -{ - int i, count = 0, format = -1; - - d->oinfo.channels = channels; - d->oinfo.samplerate = d->rate > 0 ? d->rate : d->iinfo.samplerate; - d->oinfo.format = d->format > 0 ? d->format : (d->iinfo.format & SF_FORMAT_SUBMASK); - - /* try to guess the format from the extension */ - if (sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) != 0) - count = 0; - - for (i = 0; i < count; i++) { - SF_FORMAT_INFO fi; - - spa_zero(fi); - fi.format = i; - if (sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) != 0) - continue; - - if (spa_strendswith(d->oname, fi.extension)) { - format = fi.format; - break; - } - } - if (format == -1) - format = d->iinfo.format & ~SF_FORMAT_SUBMASK; - if (format == SF_FORMAT_WAV && d->oinfo.channels > 2) - format = SF_FORMAT_WAVEX; - - d->oinfo.format |= format; - - d->ofile = sf_open(d->oname, SFM_WRITE, &d->oinfo); - if (d->ofile == NULL) { - fprintf(stderr, "error: failed to open output file \"%s\": %s\n", - d->oname, sf_strerror(NULL)); - return -EIO; - } - sf_command(d->ofile, SFC_SET_CLIPPING, NULL, 1); - - if (d->verbose) - fprintf(stdout, "output '%s': channels:%d rate:%d format:%s\n", - d->oname, d->oinfo.channels, d->oinfo.samplerate, - sf_fmt_to_str(d->oinfo.format)); - return 0; -} - -static int setup_convert_direction(struct spa_node *node, - enum spa_direction direction, struct spa_audio_info_raw *info) -{ - struct spa_pod_builder b = { 0 }; - uint8_t buffer[1024]; - struct spa_pod *param, *format; - int res; - - /* set port config to convert mode */ - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - param = spa_pod_builder_add_object(&b, - SPA_TYPE_OBJECT_ParamPortConfig, SPA_PARAM_PortConfig, - SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(direction), - SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_convert)); - - res = spa_node_set_param(node, SPA_PARAM_PortConfig, 0, param); - if (res < 0) - return res; - - /* set format on port 0 */ - spa_pod_builder_init(&b, buffer, sizeof(buffer)); - format = spa_format_audio_raw_build(&b, SPA_PARAM_Format, info); - - res = spa_node_port_set_param(node, direction, 0, - SPA_PARAM_Format, 0, format); - return res; -} - -static int do_filter(struct data *d) -{ - void *iface; - struct spa_node *node; - int in_channels = d->iinfo.channels; - int out_channels; - int in_rate = d->iinfo.samplerate; - int out_rate = d->rate > 0 ? d->rate : in_rate; - uint32_t blocksize = d->blocksize > 0 ? d->blocksize : MAX_SAMPLES; - int res; - struct spa_audio_info_raw in_info, out_info; - struct spa_audio_layout_info in_layout, out_layout; - - /* determine output channels */ - out_channels = d->out_channels > 0 ? d->out_channels : in_channels; - - /* set up input channel layout */ - spa_zero(in_layout); - channelmap_default(&in_layout, in_channels); - - /* set up output channel layout */ - spa_zero(out_layout); - if (d->channel_map != NULL) { - if (parse_channelmap(d->channel_map, &out_layout) < 0) { - fprintf(stderr, "error: can't parse channel-map '%s'\n", - d->channel_map); - return -EINVAL; - } - if (d->out_channels > 0 && - out_layout.n_channels != (uint32_t)d->out_channels) { - fprintf(stderr, "error: channel-map has %u channels " - "but -c specifies %d\n", - out_layout.n_channels, d->out_channels); - return -EINVAL; - } - out_channels = out_layout.n_channels; - } else { - channelmap_default(&out_layout, out_channels); - } - - /* open the output file */ - res = open_output(d, out_channels); - if (res < 0) - return res; - - /* calculate output buffer size accounting for resampling */ - uint32_t out_blocksize = (uint32_t)((uint64_t)blocksize * - out_rate / in_rate) + 64; - - uint32_t quant_limit = SPA_ROUND_UP_N(SPA_MAX(out_blocksize, blocksize), 4096); - - pw_properties_setf(d->props, "clock.quantum-limit", "%u", quant_limit); - pw_properties_set(d->props, "convert.direction", "output"); - - d->handle = pw_context_load_spa_handle(d->context, - SPA_NAME_AUDIO_CONVERT, &d->props->dict); - - if (d->handle == NULL) { - fprintf(stderr, "can't load %s: %m\n", SPA_NAME_AUDIO_CONVERT); - return -errno; - } - - res = spa_handle_get_interface(d->handle, - SPA_TYPE_INTERFACE_Node, &iface); - if (res < 0 || iface == NULL) { - fprintf(stderr, "can't get Node interface: %s\n", - spa_strerror(res)); - return res; - } - node = d->node = iface; - - /* build input format: interleaved F32 */ - spa_zero(in_info); - in_info.format = SPA_AUDIO_FORMAT_F32; - in_info.rate = in_rate; - in_info.channels = in_channels; - for (uint32_t i = 0; i < in_layout.n_channels && - i < SPA_AUDIO_MAX_CHANNELS; i++) - in_info.position[i] = in_layout.position[i]; - - /* build output format: interleaved F32 */ - spa_zero(out_info); - out_info.format = SPA_AUDIO_FORMAT_F32; - out_info.rate = out_rate; - out_info.channels = out_channels; - for (uint32_t i = 0; i < out_layout.n_channels && - i < SPA_AUDIO_MAX_CHANNELS; i++) - out_info.position[i] = out_layout.position[i]; - - /* set up convert directions */ - res = setup_convert_direction(node, SPA_DIRECTION_INPUT, &in_info); - if (res < 0) { - fprintf(stderr, "can't set input format: %s\n", - spa_strerror(res)); - return res; - } - res = setup_convert_direction(node, SPA_DIRECTION_OUTPUT, &out_info); - if (res < 0) { - fprintf(stderr, "can't set output format: %s\n", - spa_strerror(res)); - return res; - } - - /* send Start command */ - { - struct spa_command cmd = - SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Start); - res = spa_node_send_command(node, &cmd); - if (res < 0) { - fprintf(stderr, "can't start node: %s\n", - spa_strerror(res)); - return res; - } - } - - if (d->verbose) - fprintf(stdout, "convert: in:%dch@%dHz -> out:%dch@%dHz " - "blocksize:%u\n", - in_channels, in_rate, - out_channels, out_rate, blocksize); - - /* process audio */ - { - float ibuf[blocksize * in_channels] SPA_ALIGNED(64); - float obuf[out_blocksize * out_channels] SPA_ALIGNED(64); - - struct spa_chunk in_chunk, out_chunk; - struct spa_data in_sdata, out_sdata; - struct spa_buffer in_buffer, out_buffer; - struct spa_buffer *in_buffers[1] = { &in_buffer }; - struct spa_buffer *out_buffers[1] = { &out_buffer }; - struct spa_io_buffers in_io, out_io; - size_t read_total = 0, written_total = 0; - - /* setup input buffer */ - spa_zero(in_chunk); - spa_zero(in_sdata); - in_sdata.type = SPA_DATA_MemPtr; - in_sdata.flags = SPA_DATA_FLAG_READABLE; - in_sdata.fd = -1; - in_sdata.maxsize = sizeof(ibuf); - in_sdata.data = ibuf; - in_sdata.chunk = &in_chunk; - - spa_zero(in_buffer); - in_buffer.datas = &in_sdata; - in_buffer.n_datas = 1; - - res = spa_node_port_use_buffers(node, - SPA_DIRECTION_INPUT, 0, 0, - in_buffers, 1); - if (res < 0) { - fprintf(stderr, "can't set input buffers: %s\n", - spa_strerror(res)); - goto stop; - } - - /* setup output buffer */ - spa_zero(out_chunk); - spa_zero(out_sdata); - out_sdata.type = SPA_DATA_MemPtr; - out_sdata.flags = SPA_DATA_FLAG_READWRITE; - out_sdata.fd = -1; - out_sdata.maxsize = sizeof(obuf); - out_sdata.data = obuf; - out_sdata.chunk = &out_chunk; - - spa_zero(out_buffer); - out_buffer.datas = &out_sdata; - out_buffer.n_datas = 1; - - res = spa_node_port_use_buffers(node, - SPA_DIRECTION_OUTPUT, 0, 0, - out_buffers, 1); - if (res < 0) { - fprintf(stderr, "can't set output buffers: %s\n", - spa_strerror(res)); - goto stop; - } - - /* setup IO */ - res = spa_node_port_set_io(node, SPA_DIRECTION_INPUT, 0, - SPA_IO_Buffers, &in_io, sizeof(in_io)); - if (res < 0) { - fprintf(stderr, "can't set input IO: %s\n", - spa_strerror(res)); - goto stop; - } - res = spa_node_port_set_io(node, SPA_DIRECTION_OUTPUT, 0, - SPA_IO_Buffers, &out_io, sizeof(out_io)); - if (res < 0) { - fprintf(stderr, "can't set output IO: %s\n", - spa_strerror(res)); - goto stop; - } - - /* process loop */ - while (true) { - sf_count_t n_read; - - n_read = sf_readf_float(d->ifile, ibuf, blocksize); - - read_total += n_read; - - in_chunk.offset = 0; - in_chunk.size = n_read * in_channels * sizeof(float); - in_chunk.stride = 0; - - out_chunk.offset = 0; - out_chunk.size = 0; - out_chunk.stride = 0; - - in_io.status = n_read > 0 ? SPA_STATUS_HAVE_DATA : SPA_STATUS_DRAINED; - in_io.buffer_id = 0; - out_io.status = SPA_STATUS_NEED_DATA; - out_io.buffer_id = 0; - - res = spa_node_process(node); - if (res < 0) { - fprintf(stderr, "process error: %s\n", - spa_strerror(res)); - break; - } - - if (out_io.status == SPA_STATUS_HAVE_DATA && - out_io.buffer_id == 0) { - uint32_t out_frames = out_chunk.size / - (out_channels * sizeof(float)); - if (out_frames > 0) - written_total += sf_writef_float( - d->ofile, obuf, - out_frames); - } - if (n_read == 0) - break; - } - if (d->verbose) - fprintf(stdout, "read %zu samples, wrote %zu samples\n", - read_total, written_total); - } - - res = 0; - -stop: - { - struct spa_command cmd = - SPA_NODE_COMMAND_INIT(SPA_NODE_COMMAND_Suspend); - spa_node_send_command(node, &cmd); - } - return res; -} - -static char *read_file(const char *path) -{ - FILE *f; - long size; - char *buf; - - f = fopen(path, "r"); - if (f == NULL) - return NULL; - - fseek(f, 0, SEEK_END); - size = ftell(f); - fseek(f, 0, SEEK_SET); - - buf = malloc(size + 1); - if (buf == NULL) { - fclose(f); - return NULL; - } - - if ((long)fread(buf, 1, size, f) != size) { - free(buf); - fclose(f); - return NULL; - } - buf[size] = '\0'; - fclose(f); - return buf; -} - -int main(int argc, char *argv[]) -{ - int c; - int longopt_index = 0, ret; - struct data data; - struct spa_error_location loc; - char *file_content = NULL, *str; - - spa_zero(data); - data.props = pw_properties_new(NULL, NULL); - - pw_init(&argc, &argv); - - while ((c = getopt_long(argc, argv, OPTIONS, long_options, &longopt_index)) != -1) { - switch (c) { - case 'h': - show_usage(argv[0], false); - ret = EXIT_SUCCESS; - goto done; - case 'v': - data.verbose = true; - break; - case 'r': - ret = atoi(optarg); - if (ret <= 0) { - fprintf(stderr, "error: bad rate %s\n", optarg); - goto error_usage; - } - data.rate = ret; - break; - case 'f': - ret = sf_str_to_fmt(optarg); - if (ret < 0) { - fprintf(stderr, "error: bad format %s\n", optarg); - goto error_usage; - } - data.format = ret; - break; - case 'b': - ret = atoi(optarg); - if (ret <= 0) { - fprintf(stderr, "error: bad blocksize %s\n", optarg); - goto error_usage; - } - data.blocksize = ret; - break; - case 'P': - if (optarg[0] == '@') { - file_content = read_file(optarg + 1); - if (file_content == NULL) { - fprintf(stderr, "error: can't read graph file '%s': %m\n", - optarg + 1); - ret = EXIT_FAILURE; - goto done; - } - str = file_content; - } else { - str = optarg; - } - if (pw_properties_update_string_checked(data.props, str, strlen(str), &loc) < 0) { - spa_debug_file_error_location(stderr, &loc, - "error: syntax error in --properties: %s", - loc.reason); - goto error_usage; - } - break; - case 'c': - ret = atoi(optarg); - if (ret <= 0) { - fprintf(stderr, "error: bad channel count %s\n", optarg); - goto error_usage; - } - data.out_channels = ret; - break; - case OPT_CHANNELMAP: - data.channel_map = optarg; - break; - default: - fprintf(stderr, "error: unknown option '%c'\n", c); - goto error_usage; - } - } - if (optind + 1 >= argc) { - fprintf(stderr, "error: filename arguments missing\n"); - goto error_usage; - } - data.iname = argv[optind++]; - data.oname = argv[optind++]; - - data.loop = pw_main_loop_new(NULL); - if (data.loop == NULL) { - fprintf(stderr, "error: can't create main loop: %m\n"); - ret = EXIT_FAILURE; - goto done; - } - - data.context = pw_context_new(pw_main_loop_get_loop(data.loop), NULL, 0); - if (data.context == NULL) { - fprintf(stderr, "error: can't create context: %m\n"); - ret = EXIT_FAILURE; - goto done; - } - - if (open_input(&data) < 0) { - ret = EXIT_FAILURE; - goto done; - } - - ret = do_filter(&data); - -done: - if (data.ifile) - sf_close(data.ifile); - if (data.ofile) - sf_close(data.ofile); - if (data.props) - pw_properties_free(data.props); - if (data.handle) - pw_unload_spa_handle(data.handle); - if (data.context) - pw_context_destroy(data.context); - if (data.loop) - pw_main_loop_destroy(data.loop); - free(file_content); - pw_deinit(); - - return ret < 0 ? EXIT_FAILURE : EXIT_SUCCESS; - -error_usage: - show_usage(argv[0], true); - ret = EXIT_FAILURE; - goto done; -} diff --git a/src/tools/pw-avb-virtual.c b/src/tools/pw-avb-virtual.c deleted file mode 100644 index 6b123949e..000000000 --- a/src/tools/pw-avb-virtual.c +++ /dev/null @@ -1,283 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2026 PipeWire contributors */ -/* SPDX-License-Identifier: MIT */ - -/** - * pw-avb-virtual: Create virtual AVB audio devices in the PipeWire graph. - * - * This tool creates virtual AVB talker/listener endpoints that appear - * as Audio/Source and Audio/Sink nodes in the PipeWire graph (visible - * in tools like Helvum). No AVB hardware or network access is needed — - * the loopback transport is used for all protocol and stream operations. - * - * The sink node consumes audio silently (data goes nowhere). - * The source node produces silence (no network data to receive). - */ - -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include "module-avb/internal.h" -#include "module-avb/stream.h" -#include "module-avb/avb-transport-loopback.h" -#include "module-avb/descriptors.h" -#include "module-avb/mrp.h" -#include "module-avb/adp.h" -#include "module-avb/acmp.h" -#include "module-avb/aecp.h" -#include "module-avb/maap.h" -#include "module-avb/mmrp.h" -#include "module-avb/msrp.h" -#include "module-avb/mvrp.h" - -struct data { - struct pw_main_loop *loop; - struct pw_context *context; - struct pw_core *core; - struct spa_hook core_listener; - - struct impl impl; - struct server *server; - - const char *opt_remote; - const char *opt_name; - bool opt_milan; -}; - -static void do_quit(void *data, int signal_number) -{ - struct data *d = data; - pw_main_loop_quit(d->loop); -} - -static void on_core_error(void *data, uint32_t id, int seq, - int res, const char *message) -{ - struct data *d = data; - - pw_log_error("error id:%u seq:%d res:%d (%s): %s", - id, seq, res, spa_strerror(res), message); - - if (id == PW_ID_CORE && res == -EPIPE) - pw_main_loop_quit(d->loop); -} - -static const struct pw_core_events core_events = { - PW_VERSION_CORE_EVENTS, - .error = on_core_error, -}; - -static struct server *create_virtual_server(struct data *data) -{ - struct impl *impl = &data->impl; - struct server *server; - struct stream *stream; - uint16_t idx; - char name_buf[256]; - - server = calloc(1, sizeof(*server)); - if (server == NULL) - return NULL; - - server->impl = impl; - server->ifname = strdup("virtual0"); - server->avb_mode = data->opt_milan ? AVB_MODE_MILAN_V12 : AVB_MODE_LEGACY; - server->transport = &avb_transport_loopback; - - spa_list_append(&impl->servers, &server->link); - spa_hook_list_init(&server->listener_list); - spa_list_init(&server->descriptors); - spa_list_init(&server->streams); - - if (server->transport->setup(server) < 0) - goto error; - - server->mrp = avb_mrp_new(server); - if (server->mrp == NULL) - goto error; - - avb_aecp_register(server); - server->maap = avb_maap_register(server); - server->mmrp = avb_mmrp_register(server); - server->msrp = avb_msrp_register(server); - server->mvrp = avb_mvrp_register(server); - avb_adp_register(server); - avb_acmp_register(server); - - server->domain_attr = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN); - server->domain_attr->attr.domain.sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT; - server->domain_attr->attr.domain.sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT; - server->domain_attr->attr.domain.sr_class_vid = htons(AVB_DEFAULT_VLAN); - - avb_maap_reserve(server->maap, 1); - - init_descriptors(server); - - /* Update stream properties and activate */ - idx = 0; - spa_list_for_each(stream, &server->streams, link) { - if (stream->direction == SPA_DIRECTION_INPUT) { - snprintf(name_buf, sizeof(name_buf), "%s.source.%u", - data->opt_name, idx); - pw_stream_update_properties(stream->stream, - &SPA_DICT_INIT_ARRAY(((struct spa_dict_item[]) { - { PW_KEY_NODE_NAME, name_buf }, - { PW_KEY_NODE_DESCRIPTION, "AVB Virtual Source" }, - { PW_KEY_NODE_VIRTUAL, "true" }, - }))); - } else { - snprintf(name_buf, sizeof(name_buf), "%s.sink.%u", - data->opt_name, idx); - pw_stream_update_properties(stream->stream, - &SPA_DICT_INIT_ARRAY(((struct spa_dict_item[]) { - { PW_KEY_NODE_NAME, name_buf }, - { PW_KEY_NODE_DESCRIPTION, "AVB Virtual Sink" }, - { PW_KEY_NODE_VIRTUAL, "true" }, - }))); - } - - if (stream_activate_virtual(stream, idx) < 0) - pw_log_warn("failed to activate stream %u", idx); - - idx++; - } - - return server; - -error: - spa_list_remove(&server->link); - free(server->ifname); - free(server); - return NULL; -} - -static void show_help(const char *name) -{ - printf("%s [options]\n" - " -h, --help Show this help\n" - " --version Show version\n" - " -r, --remote NAME Remote daemon name\n" - " -n, --name PREFIX Node name prefix (default: avb-virtual)\n" - " -m, --milan Use Milan v1.2 mode (default: legacy)\n", - name); -} - -int main(int argc, char *argv[]) -{ - struct data data = { 0 }; - struct pw_loop *l; - int res = -1; - static const struct option long_options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, 'V' }, - { "remote", required_argument, NULL, 'r' }, - { "name", required_argument, NULL, 'n' }, - { "milan", no_argument, NULL, 'm' }, - { NULL, 0, NULL, 0 } - }; - int c; - - setlocale(LC_ALL, ""); - pw_init(&argc, &argv); - - data.opt_name = "avb-virtual"; - - while ((c = getopt_long(argc, argv, "hVr:n:m", long_options, NULL)) != -1) { - switch (c) { - case 'h': - show_help(argv[0]); - return 0; - case 'V': - printf("%s\n" - "Compiled with libpipewire %s\n" - "Linked with libpipewire %s\n", - argv[0], - pw_get_headers_version(), - pw_get_library_version()); - return 0; - case 'r': - data.opt_remote = optarg; - break; - case 'n': - data.opt_name = optarg; - break; - case 'm': - data.opt_milan = true; - break; - default: - show_help(argv[0]); - return -1; - } - } - - data.loop = pw_main_loop_new(NULL); - if (data.loop == NULL) { - fprintf(stderr, "can't create main loop: %m\n"); - goto exit; - } - - l = pw_main_loop_get_loop(data.loop); - pw_loop_add_signal(l, SIGINT, do_quit, &data); - pw_loop_add_signal(l, SIGTERM, do_quit, &data); - - data.context = pw_context_new(l, NULL, 0); - if (data.context == NULL) { - fprintf(stderr, "can't create context: %m\n"); - goto exit; - } - - data.core = pw_context_connect(data.context, - pw_properties_new( - PW_KEY_REMOTE_NAME, data.opt_remote, - NULL), - 0); - if (data.core == NULL) { - fprintf(stderr, "can't connect to PipeWire: %m\n"); - goto exit; - } - - pw_core_add_listener(data.core, &data.core_listener, - &core_events, &data); - - /* Initialize the AVB impl */ - data.impl.loop = l; - data.impl.timer_queue = pw_context_get_timer_queue(data.context); - data.impl.context = data.context; - data.impl.core = data.core; - spa_list_init(&data.impl.servers); - - /* Create the virtual AVB server with streams */ - data.server = create_virtual_server(&data); - if (data.server == NULL) { - fprintf(stderr, "can't create virtual AVB server: %m\n"); - goto exit; - } - - fprintf(stdout, "Virtual AVB device running (%s mode). Press Ctrl-C to stop.\n", - data.opt_milan ? "Milan v1.2" : "legacy"); - - pw_main_loop_run(data.loop); - - res = 0; -exit: - if (data.server) - avdecc_server_free(data.server); - if (data.core) - pw_core_disconnect(data.core); - if (data.context) - pw_context_destroy(data.context); - if (data.loop) - pw_main_loop_destroy(data.loop); - pw_deinit(); - - return res; -} diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index 17e6a9929..6b1916d55 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -389,7 +389,7 @@ static int encoded_playback_fill(struct data *d, void *dest, unsigned int n_fram break; } - spa_memcpy(dest_ptr, packet->data, packet->size); + memcpy(dest_ptr, packet->data, packet->size); accumulated_duration += packet->duration; dest_ptr += packet->size; @@ -1125,9 +1125,9 @@ enum { }; #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION -#define OPTIONS "hvprmdosR:P:q:aM:n:cC" +#define OPTIONS "hvprmdosR:P:q:aM:n:c" #else -#define OPTIONS "hvprmdsR:P:q:aM:n:cC" +#define OPTIONS "hvprmdsR:P:q:aM:n:c" #endif static const struct option long_options[] = { @@ -1168,7 +1168,6 @@ static const struct option long_options[] = { { "force-midi", required_argument, NULL, 'M' }, { "sample-count", required_argument, NULL, 'n' }, { "midi-clip", no_argument, NULL, 'c' }, - { "monitor", no_argument, NULL, 'C' }, { NULL, 0, NULL, 0 } }; @@ -1193,7 +1192,6 @@ static void show_usage(const char *name, bool is_error) " --media-role Set media role (default %s)\n" " --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" @@ -1823,7 +1821,6 @@ static void format_from_filename(SF_INFO *info, const char *filename, const char case SF_FORMAT_FLAC: case SF_FORMAT_MPEG: case SF_FORMAT_AIFF: - case SF_FORMAT_WAVEX: info->format |= SF_ENDIAN_FILE; break; default: @@ -1861,14 +1858,15 @@ static int setup_encodedfile(struct data *data) int num_channels; unsigned int stream_index; const AVCodecParameters *codecpar; - char path[PATH_MAX]; + char path[256] = { 0 }; /* We do not support record with encoded media */ if (data->mode == mode_record) { return -EINVAL; } - spa_scnprintf(path, sizeof(path), "file:%s", data->filename); + strcpy(path, "file:"); + strcat(path, data->filename); data->encoded.format_context = NULL; if ((ret = avformat_open_input(&data->encoded.format_context, path, NULL, NULL)) < 0) { @@ -2344,9 +2342,6 @@ int main(int argc, char *argv[]) case 'c': data.data_type = TYPE_MIDI2; break; - case 'C': - pw_properties_set(data.props, PW_KEY_STREAM_CAPTURE_SINK, "true"); - break; case OPT_LISTFORMATS: list_formats(&data); return EXIT_SUCCESS; diff --git a/src/tools/pw-cli.c b/src/tools/pw-cli.c index b731e0acb..88356cb8f 100644 --- a/src/tools/pw-cli.c +++ b/src/tools/pw-cli.c @@ -1760,8 +1760,8 @@ static bool do_create_link(struct data *data, const char *cmd, char *args, char if (!global_port_out || !global_port_in) continue; - pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%u", global_port_out->id); - pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%u", global_port_in->id); + pw_properties_setf(props, PW_KEY_LINK_OUTPUT_PORT, "%d", global_port_out->id); + pw_properties_setf(props, PW_KEY_LINK_INPUT_PORT, "%d", global_port_in->id); create_link_with_properties(data, props); } diff --git a/src/tools/pw-config.c b/src/tools/pw-config.c index ef8a345fd..bfc64e427 100644 --- a/src/tools/pw-config.c +++ b/src/tools/pw-config.c @@ -25,7 +25,6 @@ struct data { bool opt_recurse; bool opt_newline; bool opt_colors; - bool opt_simple; struct pw_properties *conf; struct pw_properties *assemble; int count; @@ -40,7 +39,6 @@ static void print_all_properties(struct data *d, struct pw_properties *props) (d->opt_recurse ? PW_PROPERTIES_FLAG_RECURSE : 0) | (d->opt_colors ? PW_PROPERTIES_FLAG_COLORS : 0) | (d->array ? PW_PROPERTIES_FLAG_ARRAY : 0) | - (d->opt_simple ? PW_PROPERTIES_FLAG_SIMPLE : 0) | PW_PROPERTIES_FLAG_ENCLOSE); fprintf(stdout, "\n"); } @@ -130,8 +128,7 @@ static void show_help(const char *name, bool error) " -L, --no-newline Omit newlines after values\n" " -r, --recurse Reformat config sections recursively\n" " -N, --no-colors disable color output\n" - " -C, --color[=WHEN] whether to enable color support. WHEN is `never`, `always`, or `auto`\n" - " -s, --spa SPA JSON output\n", + " -C, --color[=WHEN] whether to enable color support. WHEN is `never`, `always`, or `auto`\n", name, DEFAULT_NAME, DEFAULT_PREFIX); } @@ -149,7 +146,6 @@ int main(int argc, char *argv[]) { "recurse", no_argument, NULL, 'r' }, { "no-colors", no_argument, NULL, 'N' }, { "color", optional_argument, NULL, 'C' }, - { "spa", no_argument, NULL, 's' }, { NULL, 0, NULL, 0} }; @@ -157,14 +153,13 @@ int main(int argc, char *argv[]) d.opt_prefix = NULL; d.opt_recurse = false; d.opt_newline = true; - d.opt_simple = false; if (getenv("NO_COLOR") == NULL && isatty(fileno(stdout))) d.opt_colors = true; d.opt_cmd = "paths"; pw_init(&argc, &argv); - while ((c = getopt_long(argc, argv, "hVn:p:LrNCs", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hVn:p:LrNC", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(argv[0], false); @@ -206,9 +201,6 @@ int main(int argc, char *argv[]) return -1; } break; - case 's' : - d.opt_simple = true; - break; default: show_help(argv[0], true); return -1; diff --git a/src/tools/pw-dot.c b/src/tools/pw-dot.c index 8491e1279..8536e1303 100644 --- a/src/tools/pw-dot.c +++ b/src/tools/pw-dot.c @@ -1025,7 +1025,6 @@ static int get_data_from_pipewire(struct data *data, const char *opt_remote) { struct pw_loop *l; struct global *g; - const char *remote_name; data->loop = pw_main_loop_new(NULL); if (data->loop == NULL) { @@ -1044,13 +1043,9 @@ static int get_data_from_pipewire(struct data *data, const char *opt_remote) return -1; } - remote_name = "[" PW_DEFAULT_REMOTE "-manager," PW_DEFAULT_REMOTE "]"; - if (opt_remote) - remote_name = opt_remote; - data->core = pw_context_connect(data->context, pw_properties_new( - PW_KEY_REMOTE_NAME, remote_name, + PW_KEY_REMOTE_NAME, opt_remote, NULL), 0); if (data->core == NULL) { diff --git a/src/tools/pw-dump.c b/src/tools/pw-dump.c index 09643aa43..e72e7d257 100644 --- a/src/tools/pw-dump.c +++ b/src/tools/pw-dump.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include @@ -31,6 +30,15 @@ #define INDENT 2 +static bool colors = false; +static bool raw = false; + +#define NORMAL (colors ? SPA_ANSI_RESET : "") +#define LITERAL (colors ? SPA_ANSI_BRIGHT_MAGENTA : "") +#define NUMBER (colors ? SPA_ANSI_BRIGHT_CYAN : "") +#define STRING (colors ? SPA_ANSI_BRIGHT_GREEN : "") +#define KEY (colors ? SPA_ANSI_BRIGHT_BLUE : "") + struct data { struct pw_main_loop *loop; struct pw_context *context; @@ -47,13 +55,20 @@ struct data { const char *pattern; - struct spa_json_builder builder; - FILE *out; + int level; + int indent; +#define STATE_KEY (1<<0) +#define STATE_COMMA (1<<1) +#define STATE_FIRST (1<<2) +#define STATE_MASK 0xffff0000 +#define STATE_SIMPLE (1<<16) uint32_t state; + const char *comma_char; + const char *keysep_char; + bool simple_string; unsigned int monitor:1; - unsigned int recurse:1; }; struct param { @@ -202,25 +217,133 @@ static void object_destroy(struct object *o) free(o); } +static void put_key(struct data *d, const char *key); + +#define REJECT "\"\\'=:,{}[]()#" + +static bool is_simple_string(const char *val) +{ + int i; + for (i = 0; val[i]; i++) { + if (val[i] < 0x20 || strchr(REJECT, val[i]) != NULL) + return false; + } + return true; +} + +static SPA_PRINTF_FUNC(3,4) void put_fmt(struct data *d, const char *key, const char *fmt, ...) +{ + va_list va; + if (key) + put_key(d, key); + fprintf(d->out, "%s%s%*s", + d->state & STATE_COMMA ? d->comma_char : "", + d->state & (STATE_MASK | STATE_KEY) ? " " : (d->state & STATE_FIRST) || raw ? "" : "\n", + d->state & (STATE_MASK | STATE_KEY) ? 0 : d->level, ""); + va_start(va, fmt); + vfprintf(d->out, fmt, va); + va_end(va); + d->state = (d->state & STATE_MASK) + STATE_COMMA; +} + +static void put_key(struct data *d, const char *key) +{ + int size = (strlen(key) + 1) * 4; + if (d->simple_string && is_simple_string(key)) { + put_fmt(d, NULL, "%s%s%s%s", KEY, key, NORMAL, d->keysep_char); + } else { + char *str = alloca(size); + spa_json_encode_string(str, size, key); + put_fmt(d, NULL, "%s%s%s%s", KEY, str, NORMAL, d->keysep_char); + } + d->state = (d->state & STATE_MASK) + STATE_KEY; +} + +static void put_begin(struct data *d, const char *key, const char *type, uint32_t flags) +{ + put_fmt(d, key, "%s", type); + d->level += d->indent; + d->state = (d->state & STATE_MASK) + (flags & STATE_SIMPLE); +} + +static void put_end(struct data *d, const char *type, uint32_t flags) +{ + d->level -= d->indent; + d->state = d->state & STATE_MASK; + put_fmt(d, NULL, "%s", type); + d->state = (d->state & STATE_MASK) + STATE_COMMA - (flags & STATE_SIMPLE); +} + +static void put_encoded_string(struct data *d, const char *key, const char *val) +{ + put_fmt(d, key, "%s%s%s", STRING, val, NORMAL); +} +static void put_string(struct data *d, const char *key, const char *val) +{ + int size = (strlen(val) + 1) * 4; + if (d->simple_string && is_simple_string(val)) { + put_encoded_string(d, key, val); + } else { + char *str = alloca(size); + spa_json_encode_string(str, size, val); + put_encoded_string(d, key, str); + } +} + +static void put_literal(struct data *d, const char *key, const char *val) +{ + put_fmt(d, key, "%s%s%s", LITERAL, val, NORMAL); +} + +static void put_int(struct data *d, const char *key, int64_t val) +{ + put_fmt(d, key, "%s%"PRIi64"%s", NUMBER, val, NORMAL); +} + +static void put_double(struct data *d, const char *key, double val) +{ + char buf[128]; + put_fmt(d, key, "%s%s%s", NUMBER, + spa_json_format_float(buf, sizeof(buf), (float)val), NORMAL); +} + +static void put_value(struct data *d, const char *key, const char *val) +{ + int64_t li; + float fv; + + if (val == NULL) + put_literal(d, key, "null"); + else if (spa_streq(val, "true") || spa_streq(val, "false")) + put_literal(d, key, val); + else if (spa_atoi64(val, &li, 10)) + put_int(d, key, li); + else if (spa_json_parse_float(val, strlen(val), &fv)) + put_double(d, key, fv); + else + put_string(d, key, val); +} static void put_dict(struct data *d, const char *key, struct spa_dict *dict) { const struct spa_dict_item *it; spa_dict_qsort(dict); - spa_json_builder_object_push(&d->builder, key, "{"); + put_begin(d, key, "{", 0); spa_dict_for_each(it, dict) - spa_json_builder_object_value(&d->builder, d->recurse, it->key, it->value); - spa_json_builder_pop(&d->builder, "}"); + put_value(d, it->key, it->value); + put_end(d, "}", 0); } static void put_pod_value(struct data *d, const char *key, const struct spa_type_info *info, uint32_t type, void *body, uint32_t size) { + if (key) + put_key(d, key); switch (type) { case SPA_TYPE_Bool: if (size < sizeof(int32_t)) break; - spa_json_builder_object_bool(&d->builder, key, *(int32_t*)body); + put_value(d, NULL, *(int32_t*)body ? "true" : "false"); break; case SPA_TYPE_Id: { @@ -234,34 +357,34 @@ static void put_pod_value(struct data *d, const char *key, const struct spa_type snprintf(fallback, sizeof(fallback), "id-%08x", id); str = fallback; } - spa_json_builder_object_string(&d->builder, key, str); + put_value(d, NULL, str); break; } case SPA_TYPE_Int: if (size < sizeof(int32_t)) break; - spa_json_builder_object_int(&d->builder, key, *(int32_t*)body); + put_int(d, NULL, *(int32_t*)body); break; case SPA_TYPE_Fd: case SPA_TYPE_Long: if (size < sizeof(int64_t)) break; - spa_json_builder_object_int(&d->builder, key, *(int64_t*)body); + put_int(d, NULL, *(int64_t*)body); break; case SPA_TYPE_Float: if (size < sizeof(float)) break; - spa_json_builder_object_double(&d->builder, key, *(float*)body); + put_double(d, NULL, *(float*)body); break; case SPA_TYPE_Double: if (size < sizeof(double)) break; - spa_json_builder_object_double(&d->builder, key, *(double*)body); + put_double(d, NULL, *(double*)body); break; case SPA_TYPE_String: if (size < 1 || ((const char *)body)[size - 1]) break; - spa_json_builder_object_string(&d->builder, key, (const char*)body); + put_string(d, NULL, (const char*)body); break; case SPA_TYPE_Rectangle: { @@ -270,10 +393,10 @@ static void put_pod_value(struct data *d, const char *key, const struct spa_type if (size < sizeof(*r)) break; r = (struct spa_rectangle *)body; - spa_json_builder_object_push(&d->builder, key, "{-"); - spa_json_builder_object_int(&d->builder, "width", r->width); - spa_json_builder_object_int(&d->builder, "height", r->height); - spa_json_builder_pop(&d->builder, "}-"); + put_begin(d, NULL, "{", STATE_SIMPLE); + put_int(d, "width", r->width); + put_int(d, "height", r->height); + put_end(d, "}", STATE_SIMPLE); break; } case SPA_TYPE_Fraction: @@ -283,10 +406,10 @@ static void put_pod_value(struct data *d, const char *key, const struct spa_type if (size < sizeof(*f)) break; f = (struct spa_fraction *)body; - spa_json_builder_object_push(&d->builder, key, "{-"); - spa_json_builder_object_int(&d->builder, "num", f->num); - spa_json_builder_object_int(&d->builder, "denom", f->denom); - spa_json_builder_pop(&d->builder, "}-"); + put_begin(d, NULL, "{", STATE_SIMPLE); + put_int(d, "num", f->num); + put_int(d, "denom", f->denom); + put_end(d, "}", STATE_SIMPLE); break; } case SPA_TYPE_Array: @@ -298,10 +421,10 @@ static void put_pod_value(struct data *d, const char *key, const struct spa_type break; b = (struct spa_pod_array_body *)body; info = info && info->values ? info->values: info; - spa_json_builder_object_push(&d->builder, key, "[-"); + put_begin(d, NULL, "[", STATE_SIMPLE); SPA_POD_ARRAY_BODY_FOREACH(b, size, p) put_pod_value(d, NULL, info, b->child.type, p, b->child.size); - spa_json_builder_pop(&d->builder, "]-"); + put_end(d, "]", STATE_SIMPLE); break; } case SPA_TYPE_Choice: @@ -310,7 +433,7 @@ static void put_pod_value(struct data *d, const char *key, const struct spa_type int index = 0; if (b->type == SPA_CHOICE_None) { - put_pod_value(d, key, info, b->child.type, + put_pod_value(d, NULL, info, b->child.type, SPA_POD_CONTENTS(struct spa_pod, &b->child), b->child.size); } else { @@ -329,12 +452,12 @@ static void put_pod_value(struct data *d, const char *key, const struct spa_type case SPA_CHOICE_Range: labels = range_labels; max_labels = 3; - flags++; + flags |= STATE_SIMPLE; break; case SPA_CHOICE_Step: labels = step_labels; max_labels = 4; - flags++; + flags |= STATE_SIMPLE; break; case SPA_CHOICE_Enum: labels = enum_labels; @@ -351,7 +474,7 @@ static void put_pod_value(struct data *d, const char *key, const struct spa_type if (labels == NULL) break; - spa_json_builder_object_push(&d->builder, key, flags ? "{-" : "{"); + put_begin(d, NULL, "{", flags); SPA_POD_CHOICE_BODY_FOREACH(b, size, p) { if ((label = labels[SPA_CLAMP(index, 0, max_labels)]) == NULL) break; @@ -359,13 +482,13 @@ static void put_pod_value(struct data *d, const char *key, const struct spa_type put_pod_value(d, buffer, info, b->child.type, p, b->child.size); index++; } - spa_json_builder_pop(&d->builder, flags ? "}-" : "}"); + put_end(d, "}", flags); } break; } case SPA_TYPE_Object: { - spa_json_builder_object_push(&d->builder, key, "{"); + put_begin(d, NULL, "{", 0); struct spa_pod_object_body *b = (struct spa_pod_object_body *)body; struct spa_pod_prop *p; const struct spa_type_info *ti, *ii; @@ -392,27 +515,27 @@ static void put_pod_value(struct data *d, const char *key, const struct spa_type SPA_POD_CONTENTS(struct spa_pod_prop, p), p->value.size); } - spa_json_builder_pop(&d->builder, "}"); + put_end(d, "}", 0); break; } case SPA_TYPE_Struct: { struct spa_pod *b = (struct spa_pod *)body, *p; - spa_json_builder_object_push(&d->builder, key, "["); + put_begin(d, NULL, "[", 0); SPA_POD_FOREACH(b, size, p) put_pod_value(d, NULL, info, p->type, SPA_POD_BODY(p), p->size); - spa_json_builder_pop(&d->builder, "]"); + put_end(d, "]", 0); break; } case SPA_TYPE_None: - spa_json_builder_object_null(&d->builder, key); + put_value(d, NULL, NULL); break; } } static void put_pod(struct data *d, const char *key, const struct spa_pod *pod) { if (pod == NULL) { - spa_json_builder_object_null(&d->builder, key); + put_value(d, key, NULL); } else { put_pod_value(d, key, SPA_TYPE_ROOT, pod->type, SPA_POD_BODY(pod), pod->size); @@ -425,24 +548,23 @@ static void put_params(struct data *d, const char *key, { uint32_t i; - spa_json_builder_object_push(&d->builder, key, "{"); + put_begin(d, key, "{", 0); for (i = 0; i < n_params; i++) { struct spa_param_info *pi = ¶ms[i]; struct param *p; uint32_t flags; - flags = pi->flags & SPA_PARAM_INFO_READ ? 0 : 1; + flags = pi->flags & SPA_PARAM_INFO_READ ? 0 : STATE_SIMPLE; - spa_json_builder_object_push(&d->builder, - spa_debug_type_find_short_name(spa_type_param, pi->id), - flags ? "[-" : "["); + put_begin(d, spa_debug_type_find_short_name(spa_type_param, pi->id), + "[", flags); spa_list_for_each(p, list, link) { if (p->id == pi->id) put_pod(d, NULL, p->param); } - spa_json_builder_pop(&d->builder, flags ? "]-" : "]"); + put_end(d, "]", flags); } - spa_json_builder_pop(&d->builder, "}"); + put_end(d, "}", 0); } struct flags_info { @@ -454,12 +576,12 @@ static void put_flags(struct data *d, const char *key, uint64_t flags, const struct flags_info *info) { uint32_t i; - spa_json_builder_object_push(&d->builder, key, "[-"); + put_begin(d, key, "[", STATE_SIMPLE); for (i = 0; info[i].name != NULL; i++) { if (info[i].mask & flags) - spa_json_builder_array_string(&d->builder, info[i].name); + put_string(d, NULL, info[i].name); } - spa_json_builder_pop(&d->builder, "]-"); + put_end(d, "]", STATE_SIMPLE); } /* core */ @@ -473,15 +595,15 @@ static void core_dump(struct object *o) struct data *d = o->data; struct pw_core_info *i = d->info; - spa_json_builder_object_push(&d->builder, "info", "{"); - spa_json_builder_object_int(&d->builder, "cookie", i->cookie); - spa_json_builder_object_string(&d->builder, "user-name", i->user_name); - spa_json_builder_object_string(&d->builder, "host-name", i->host_name); - spa_json_builder_object_string(&d->builder, "version", i->version); - spa_json_builder_object_string(&d->builder, "name", i->name); + put_begin(d, "info", "{", 0); + put_int(d, "cookie", i->cookie); + put_value(d, "user-name", i->user_name); + put_value(d, "host-name", i->host_name); + put_value(d, "version", i->version); + put_value(d, "name", i->name); put_flags(d, "change-mask", i->change_mask, fl); put_dict(d, "props", i->props); - spa_json_builder_pop(&d->builder, "}"); + put_end(d, "}", 0); } static const struct class core_class = { @@ -502,10 +624,10 @@ static void client_dump(struct object *o) struct data *d = o->data; struct pw_client_info *i = o->info; - spa_json_builder_object_push(&d->builder, "info", "{"); + put_begin(d, "info", "{", 0); put_flags(d, "change-mask", i->change_mask, fl); put_dict(d, "props", i->props); - spa_json_builder_pop(&d->builder, "}"); + put_end(d, "}", 0); } static void client_event_info(void *data, const struct pw_client_info *info) @@ -561,13 +683,13 @@ static void module_dump(struct object *o) struct data *d = o->data; struct pw_module_info *i = o->info; - spa_json_builder_object_push(&d->builder, "info", "{"); - spa_json_builder_object_string(&d->builder, "name", i->name); - spa_json_builder_object_string(&d->builder, "filename", i->filename); - spa_json_builder_object_value(&d->builder, d->recurse, "args", i->args); + put_begin(d, "info", "{", 0); + put_value(d, "name", i->name); + put_value(d, "filename", i->filename); + put_value(d, "args", i->args); put_flags(d, "change-mask", i->change_mask, fl); put_dict(d, "props", i->props); - spa_json_builder_pop(&d->builder, "}"); + put_end(d, "}", 0); } static void module_event_info(void *data, const struct pw_module_info *info) @@ -623,13 +745,13 @@ static void factory_dump(struct object *o) struct data *d = o->data; struct pw_factory_info *i = o->info; - spa_json_builder_object_push(&d->builder, "info", "{"); - spa_json_builder_object_string(&d->builder, "name", i->name); - spa_json_builder_object_string(&d->builder, "type", i->type); - spa_json_builder_object_int(&d->builder, "version", i->version); + put_begin(d, "info", "{", 0); + put_value(d, "name", i->name); + put_value(d, "type", i->type); + put_int(d, "version", i->version); put_flags(d, "change-mask", i->change_mask, fl); put_dict(d, "props", i->props); - spa_json_builder_pop(&d->builder, "}"); + put_end(d, "}", 0); } static void factory_event_info(void *data, const struct pw_factory_info *info) @@ -686,11 +808,11 @@ static void device_dump(struct object *o) struct data *d = o->data; struct pw_device_info *i = o->info; - spa_json_builder_object_push(&d->builder, "info", "{"); + put_begin(d, "info", "{", 0); put_flags(d, "change-mask", i->change_mask, fl); put_dict(d, "props", i->props); put_params(d, "params", i->params, i->n_params, &o->param_list); - spa_json_builder_pop(&d->builder, "}"); + put_end(d, "}", 0); } static void device_event_info(void *data, const struct pw_device_info *info) @@ -782,17 +904,17 @@ static void node_dump(struct object *o) struct data *d = o->data; struct pw_node_info *i = o->info; - spa_json_builder_object_push(&d->builder, "info", "{"); - spa_json_builder_object_int(&d->builder, "max-input-ports", i->max_input_ports); - spa_json_builder_object_int(&d->builder, "max-output-ports", i->max_output_ports); + put_begin(d, "info", "{", 0); + put_int(d, "max-input-ports", i->max_input_ports); + put_int(d, "max-output-ports", i->max_output_ports); put_flags(d, "change-mask", i->change_mask, fl); - spa_json_builder_object_int(&d->builder, "n-input-ports", i->n_input_ports); - spa_json_builder_object_int(&d->builder, "n-output-ports", i->n_output_ports); - spa_json_builder_object_string(&d->builder, "state", pw_node_state_as_string(i->state)); - spa_json_builder_object_string(&d->builder, "error", i->error); + put_int(d, "n-input-ports", i->n_input_ports); + put_int(d, "n-output-ports", i->n_output_ports); + put_value(d, "state", pw_node_state_as_string(i->state)); + put_value(d, "error", i->error); put_dict(d, "props", i->props); put_params(d, "params", i->params, i->n_params, &o->param_list); - spa_json_builder_pop(&d->builder, "}"); + put_end(d, "}", 0); } static void node_event_info(void *data, const struct pw_node_info *info) @@ -884,12 +1006,12 @@ static void port_dump(struct object *o) struct data *d = o->data; struct pw_port_info *i = o->info; - spa_json_builder_object_push(&d->builder, "info", "{"); - spa_json_builder_object_string(&d->builder, "direction", pw_direction_as_string(i->direction)); + put_begin(d, "info", "{", 0); + put_value(d, "direction", pw_direction_as_string(i->direction)); put_flags(d, "change-mask", i->change_mask, fl); put_dict(d, "props", i->props); put_params(d, "params", i->params, i->n_params, &o->param_list); - spa_json_builder_pop(&d->builder, "}"); + put_end(d, "}", 0); } static void port_event_info(void *data, const struct pw_port_info *info) @@ -979,17 +1101,17 @@ static void link_dump(struct object *o) struct data *d = o->data; struct pw_link_info *i = o->info; - spa_json_builder_object_push(&d->builder, "info", "{"); - spa_json_builder_object_int(&d->builder, "output-node-id", i->output_node_id); - spa_json_builder_object_int(&d->builder, "output-port-id", i->output_port_id); - spa_json_builder_object_int(&d->builder, "input-node-id", i->input_node_id); - spa_json_builder_object_int(&d->builder, "input-port-id", i->input_port_id); + put_begin(d, "info", "{", 0); + put_int(d, "output-node-id", i->output_node_id); + put_int(d, "output-port-id", i->output_port_id); + put_int(d, "input-node-id", i->input_node_id); + put_int(d, "input-port-id", i->input_port_id); put_flags(d, "change-mask", i->change_mask, fl); - spa_json_builder_object_string(&d->builder, "state", pw_link_state_as_string(i->state)); - spa_json_builder_object_string(&d->builder, "error", i->error); + put_value(d, "state", pw_link_state_as_string(i->state)); + put_value(d, "error", i->error); put_pod(d, "format", i->format); put_dict(d, "props", i->props); - spa_json_builder_pop(&d->builder, "}"); + put_end(d, "}", 0); } static void link_event_info(void *data, const struct pw_link_info *info) @@ -1039,6 +1161,39 @@ static const struct class link_class = { .dump = link_dump, }; +static void json_dump_val(struct data *d, const char *key, struct spa_json *it, const char *value, int len) +{ + struct spa_json sub; + if (spa_json_is_array(value, len)) { + put_begin(d, key, "[", STATE_SIMPLE); + spa_json_enter(it, &sub); + while ((len = spa_json_next(&sub, &value)) > 0) { + json_dump_val(d, NULL, &sub, value, len); + } + put_end(d, "]", STATE_SIMPLE); + } else if (spa_json_is_object(value, len)) { + char val[1024]; + put_begin(d, key, "{", STATE_SIMPLE); + spa_json_enter(it, &sub); + while ((len = spa_json_object_next(&sub, val, sizeof(val), &value)) > 0) + json_dump_val(d, val, &sub, value, len); + put_end(d, "}", STATE_SIMPLE); + } else if (spa_json_is_string(value, len)) { + put_encoded_string(d, key, strndupa(value, len)); + } else { + put_value(d, key, strndupa(value, len)); + } +} + +static void json_dump(struct data *d, const char *key, const char *value) +{ + struct spa_json it[1]; + int len; + const char *val; + if ((len = spa_json_begin(&it[0], value, strlen(value), &val)) >= 0) + json_dump_val(d, key, &it[0], val, len); +} + /* metadata */ struct metadata_entry { @@ -1055,21 +1210,22 @@ static void metadata_dump(struct object *o) struct data *d = o->data; struct metadata_entry *e; put_dict(d, "props", &o->props->dict); - spa_json_builder_object_push(&d->builder, "metadata", "["); + put_begin(d, "metadata", "[", 0); spa_list_for_each(e, &o->data_list, link) { - bool recurse = false; if (e->changed == 0) continue; - spa_json_builder_array_push(&d->builder, "{-"); - spa_json_builder_object_int(&d->builder, "subject", e->subject); - spa_json_builder_object_string(&d->builder, "key", e->key); - spa_json_builder_object_string(&d->builder, "type", e->type); - recurse = (e->type != NULL && spa_streq(e->type, "Spa:String:JSON")); - spa_json_builder_object_value(&d->builder, recurse, "value", e->value); - spa_json_builder_pop(&d->builder, "}-"); + put_begin(d, NULL, "{", STATE_SIMPLE); + put_int(d, "subject", e->subject); + put_value(d, "key", e->key); + put_value(d, "type", e->type); + if (e->type != NULL && spa_streq(e->type, "Spa:String:JSON")) + json_dump(d, "value", e->value); + else + put_value(d, "value", e->value); + put_end(d, "}", STATE_SIMPLE); e->changed = 0; } - spa_json_builder_pop(&d->builder, "]"); + put_end(d, "]", 0); } static struct metadata_entry *metadata_find(struct object *o, uint32_t subject, const char *key) @@ -1286,20 +1442,18 @@ static void registry_event_global_remove(void *data, uint32_t id) return; if (d->monitor && (!d->pattern || object_matches(o, d->pattern))) { - d->state = 0; - if (d->state++ == 0) - spa_json_builder_array_push(&d->builder, "["); - spa_json_builder_array_push(&d->builder, "{"); - spa_json_builder_object_int(&d->builder, "id", o->id); + d->state = STATE_FIRST; + if (d->state == STATE_FIRST) + put_begin(d, NULL, "[", 0); + put_begin(d, NULL, "{", 0); + put_int(d, "id", o->id); if (o->class && o->class->dump) - spa_json_builder_object_null(&d->builder, "info"); + put_value(d, "info", NULL); else if (o->props) - spa_json_builder_object_null(&d->builder, "props"); - spa_json_builder_pop(&d->builder, "}"); - if (d->state != 0) { - spa_json_builder_pop(&d->builder, "]"); - fputs("\n", d->builder.f); - } + put_value(d, "props", NULL); + put_end(d, "}", 0); + if (d->state != STATE_FIRST) + put_end(d, "]\n", 0); } object_destroy(o); @@ -1324,30 +1478,28 @@ static void dump_objects(struct data *d) struct object *o; - d->state = 0; + d->state = STATE_FIRST; spa_list_for_each(o, &d->object_list, link) { if (d->pattern != NULL && !object_matches(o, d->pattern)) continue; if (o->changed == 0) continue; - if (d->state++ == 0) - spa_json_builder_array_push(&d->builder, "["); - spa_json_builder_array_push(&d->builder, "{"); - spa_json_builder_object_int(&d->builder, "id", o->id); - spa_json_builder_object_string(&d->builder, "type", o->type); - spa_json_builder_object_int(&d->builder, "version", o->version); + if (d->state == STATE_FIRST) + put_begin(d, NULL, "[", 0); + put_begin(d, NULL, "{", 0); + put_int(d, "id", o->id); + put_value(d, "type", o->type); + put_int(d, "version", o->version); put_flags(d, "permissions", o->permissions, fl); if (o->class && o->class->dump) o->class->dump(o); else if (o->props) put_dict(d, "props", &o->props->dict); - spa_json_builder_pop(&d->builder, "}"); + put_end(d, "}", 0); o->changed = 0; } - if (d->state != 0) { - spa_json_builder_pop(&d->builder, "]"); - fputs("\n", d->builder.f); - } + if (d->state != STATE_FIRST) + put_end(d, "]\n", 0); } static void on_core_error(void *data, uint32_t id, int seq, int res, const char *message) @@ -1412,8 +1564,7 @@ static void show_help(struct data *data, const char *name, bool error) " -C, --color[=WHEN] whether to enable color support. WHEN is `never`, `always`, or `auto`\n" " -R, --raw force raw output\n" " -i, --indent indentation amount (default 2)\n" - " -s, --spa SPA JSON output\n" - " -c, --recurse Reformat values recursively\n", + " -s, --spa SPA JSON output\n", name); } @@ -1422,7 +1573,7 @@ int main(int argc, char *argv[]) struct data data = { 0 }; struct object *o; struct pw_loop *l; - const char *opt_remote = NULL, *remote_name; + const char *opt_remote = NULL; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, @@ -1433,11 +1584,9 @@ int main(int argc, char *argv[]) { "raw", no_argument, NULL, 'R' }, { "indent", required_argument, NULL, 'i' }, { "spa", no_argument, NULL, 's' }, - { "recurse", no_argument, NULL, 'c' }, { NULL, 0, NULL, 0} }; - int c, flags = 0, indent = -1; - bool colors = false, raw = false; + int c; setlocale(LC_ALL, ""); pw_init(&argc, &argv); @@ -1447,7 +1596,11 @@ int main(int argc, char *argv[]) colors = true; setlinebuf(data.out); - while ((c = getopt_long(argc, argv, "hVr:mNC::Ri:sc", long_options, NULL)) != -1) { + data.comma_char = ","; + data.keysep_char = ":"; + data.indent = INDENT; + + while ((c = getopt_long(argc, argv, "hVr:mNC::Ri:s", long_options, NULL)) != -1) { switch (c) { case 'h' : show_help(&data, argv[0], false); @@ -1487,27 +1640,20 @@ int main(int argc, char *argv[]) } break; case 'i' : - indent = atoi(optarg); + data.indent = atoi(optarg); break; case 's' : - flags |= SPA_JSON_BUILDER_FLAG_SIMPLE; - break; - case 'c' : - data.recurse = true; + data.comma_char = ""; + data.keysep_char = " ="; + data.simple_string = true; break; default: show_help(&data, argv[0], true); return -1; } } - if (!raw) - flags |= SPA_JSON_BUILDER_FLAG_PRETTY; - if (colors) - flags |= SPA_JSON_BUILDER_FLAG_COLOR; - - spa_json_builder_file(&data.builder, data.out, flags); - if (indent >= 0) - data.builder.indent = indent; + if (raw) + data.indent = 0; if (optind < argc) data.pattern = argv[optind++]; @@ -1528,14 +1674,10 @@ int main(int argc, char *argv[]) return -1; } - remote_name = "[" PW_DEFAULT_REMOTE "-manager," PW_DEFAULT_REMOTE "]"; - if (opt_remote) - remote_name = opt_remote; - data.core = pw_context_connect(data.context, pw_properties_new( PW_KEY_REMOTE_INTENTION, "manager", - PW_KEY_REMOTE_NAME, remote_name, + PW_KEY_REMOTE_NAME, opt_remote, NULL), 0); if (data.core == NULL) { diff --git a/src/tools/pw-link.c b/src/tools/pw-link.c index ee5a76cc7..e33ceda7f 100644 --- a/src/tools/pw-link.c +++ b/src/tools/pw-link.c @@ -932,7 +932,6 @@ static int run(int argc, char *argv[]) .target_links = SPA_LIST_INIT(&data.target_links), }; int res = 0, c; - const char *remote_name; struct spa_error_location loc; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, @@ -1071,13 +1070,9 @@ static int run(int argc, char *argv[]) return -1; } - remote_name = "[" PW_DEFAULT_REMOTE "-manager," PW_DEFAULT_REMOTE "]"; - if (data.opt_remote) - remote_name = data.opt_remote; - data.core = pw_context_connect(data.context, pw_properties_new( - PW_KEY_REMOTE_NAME, remote_name, + PW_KEY_REMOTE_NAME, data.opt_remote, NULL), 0); if (data.core == NULL) { diff --git a/src/tools/pw-loopback.c b/src/tools/pw-loopback.c index 991934a37..1d76eb284 100644 --- a/src/tools/pw-loopback.c +++ b/src/tools/pw-loopback.c @@ -91,7 +91,7 @@ int main(int argc, char *argv[]) { struct data data = { 0 }; struct pw_loop *l; - const char *opt_remote = NULL, *remote_name; + const char *opt_remote = NULL; char cname[256], value[256]; char *args; size_t size; @@ -221,12 +221,8 @@ int main(int argc, char *argv[]) fprintf(f, "{"); - remote_name = "[" PW_DEFAULT_REMOTE "-manager," PW_DEFAULT_REMOTE "]"; - if (opt_remote) - remote_name = opt_remote; - - fprintf(f, " remote.name = \"%s\"", remote_name); - + if (opt_remote != NULL) + fprintf(f, " remote.name = \"%s\"", opt_remote); if (data.latency != 0) fprintf(f, " node.latency = %u/%u", data.latency, DEFAULT_RATE); if (data.delay != 0.0f) diff --git a/src/tools/pw-metadata.c b/src/tools/pw-metadata.c index 8abcc3bb3..15236cc91 100644 --- a/src/tools/pw-metadata.c +++ b/src/tools/pw-metadata.c @@ -180,7 +180,6 @@ int main(int argc, char *argv[]) { struct data data = { 0, }; int res = 0, c; - const char *remote_name; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, @@ -258,13 +257,9 @@ int main(int argc, char *argv[]) return -1; } - remote_name = "[" PW_DEFAULT_REMOTE "-manager," PW_DEFAULT_REMOTE "]"; - if (data.opt_remote) - remote_name = data.opt_remote; - data.core = pw_context_connect(data.context, pw_properties_new( - PW_KEY_REMOTE_NAME, remote_name, + PW_KEY_REMOTE_NAME, data.opt_remote, NULL), 0); if (data.core == NULL) { diff --git a/src/tools/pw-mididump.c b/src/tools/pw-mididump.c index 9c5858750..277784bb1 100644 --- a/src/tools/pw-mididump.c +++ b/src/tools/pw-mididump.c @@ -33,7 +33,7 @@ struct data { struct pw_filter *filter; struct port *in_port; int64_t clock_time; - bool force_ump; + bool opt_midi1; }; @@ -150,8 +150,6 @@ static void do_quit(void *userdata, int signal_number) static int dump_filter(struct data *data) { - const char *remote_name; - data->loop = pw_main_loop_new(NULL); if (data->loop == NULL) return -errno; @@ -159,15 +157,11 @@ static int dump_filter(struct data *data) pw_loop_add_signal(pw_main_loop_get_loop(data->loop), SIGINT, do_quit, data); pw_loop_add_signal(pw_main_loop_get_loop(data->loop), SIGTERM, do_quit, data); - remote_name = "[" PW_DEFAULT_REMOTE "-manager," PW_DEFAULT_REMOTE "]"; - if (data->opt_remote) - remote_name = data->opt_remote; - data->filter = pw_filter_new_simple( pw_main_loop_get_loop(data->loop), "midi-dump", pw_properties_new( - PW_KEY_REMOTE_NAME, remote_name, + PW_KEY_REMOTE_NAME, data->opt_remote, PW_KEY_MEDIA_TYPE, "Midi", PW_KEY_MEDIA_CATEGORY, "Filter", PW_KEY_MEDIA_ROLE, "DSP", @@ -180,8 +174,7 @@ static int dump_filter(struct data *data) PW_FILTER_PORT_FLAG_MAP_BUFFERS, sizeof(struct port), pw_properties_new( - PW_KEY_FORMAT_DSP, - data->force_ump ? "32 bit raw UMP" : "8 bit raw midi", + PW_KEY_FORMAT_DSP, data->opt_midi1 ? "8 bit raw midi" : "32 bit raw UMP", PW_KEY_PORT_NAME, "input", NULL), NULL, 0); @@ -205,7 +198,7 @@ static void show_help(const char *name, bool error) " -h, --help Show this help\n" " --version Show version\n" " -r, --remote Remote daemon name\n" - " -M, --force-midi Force midi format, one of \"midi\" or \"ump\",(default midi)\n", + " -M, --force-midi Force midi format, one of \"midi\" or \"ump\",(default ump)\n", name); } @@ -245,9 +238,9 @@ int main(int argc, char *argv[]) case 'M': if (spa_streq(optarg, "midi")) - data.force_ump = false; + data.opt_midi1 = true; else if (spa_streq(optarg, "ump")) - data.force_ump = true; + data.opt_midi1 = false; else { fprintf(stderr, "error: bad force-midi %s\n", optarg); show_help(argv[0], true); diff --git a/src/tools/pw-mon.c b/src/tools/pw-mon.c index 62bd63313..b4601b906 100644 --- a/src/tools/pw-mon.c +++ b/src/tools/pw-mon.c @@ -789,7 +789,7 @@ int main(int argc, char *argv[]) { struct data data = { 0 }; struct pw_loop *l; - const char *opt_remote = NULL, *remote_name; + const char *opt_remote = NULL; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, @@ -884,14 +884,10 @@ int main(int argc, char *argv[]) spa_list_init(&data.pending_list); spa_list_init(&data.global_list); - remote_name = "[" PW_DEFAULT_REMOTE "-manager," PW_DEFAULT_REMOTE "]"; - if (opt_remote) - remote_name = opt_remote; - data.core = pw_context_connect(data.context, pw_properties_new( PW_KEY_REMOTE_INTENTION, "manager", - PW_KEY_REMOTE_NAME, remote_name, + PW_KEY_REMOTE_NAME, opt_remote, NULL), 0); if (data.core == NULL) { diff --git a/src/tools/pw-profiler.c b/src/tools/pw-profiler.c index bc9f2a583..2115a0899 100644 --- a/src/tools/pw-profiler.c +++ b/src/tools/pw-profiler.c @@ -678,7 +678,7 @@ int main(int argc, char *argv[]) { struct data data = { 0 }; struct pw_loop *l; - const char *opt_remote = NULL, *remote_name; + const char *opt_remote = NULL; const char *opt_output = DEFAULT_FILENAME; static const struct option long_options[] = { { "help", no_argument, NULL, 'h' }, @@ -743,13 +743,9 @@ int main(int argc, char *argv[]) pw_context_load_module(data.context, PW_EXTENSION_MODULE_PROFILER, NULL, NULL); - remote_name = "[" PW_DEFAULT_REMOTE "-manager," PW_DEFAULT_REMOTE "]"; - if (opt_remote) - remote_name = opt_remote; - data.core = pw_context_connect(data.context, pw_properties_new( - PW_KEY_REMOTE_NAME, remote_name, + PW_KEY_REMOTE_NAME, opt_remote, NULL), 0); if (data.core == NULL) { diff --git a/src/tools/pw-top.c b/src/tools/pw-top.c index 4c8036622..6b936a9bf 100644 --- a/src/tools/pw-top.c +++ b/src/tools/pw-top.c @@ -66,20 +66,6 @@ struct node { struct spa_hook object_listener; }; -struct filter_preset { - enum pw_node_state state; - enum pw_node_state followers; -}; - -struct filter_preset filter_presets[] = { - {PW_NODE_STATE_ERROR, PW_NODE_STATE_ERROR}, - {PW_NODE_STATE_IDLE, PW_NODE_STATE_ERROR}, - {PW_NODE_STATE_RUNNING, PW_NODE_STATE_ERROR}, - {PW_NODE_STATE_RUNNING, PW_NODE_STATE_IDLE}, -}; - -#define N_FILTER_PRESETS SPA_N_ELEMENTS(filter_presets) - struct data { struct pw_main_loop *loop; struct pw_context *context; @@ -105,8 +91,6 @@ struct data { unsigned int batch_mode:1; int iterations; - - int32_t filter_preset; }; struct point { @@ -575,38 +559,14 @@ static void do_refresh(struct data *d, bool force_refresh) { struct node *n, *t, *f; int y = 1; - struct filter_preset *filter = &filter_presets[d->filter_preset]; if (!d->pending_refresh && !force_refresh) return; if (!d->batch_mode) { - char statusbar[255] = { 0 }; - - if (!((filter->state == PW_NODE_STATE_ERROR) && - (filter->followers == PW_NODE_STATE_ERROR))) { - - strcpy(statusbar, "FILTER: "); - if (filter->state == PW_NODE_STATE_ERROR) - strcat(statusbar, "ALL"); - else for (enum pw_node_state showstate = PW_NODE_STATE_RUNNING; showstate >= PW_NODE_STATE_ERROR; showstate--) { - if (showstate >= filter->state) - strcat(statusbar, state_as_string(showstate, SPA_IO_POSITION_STATE_STOPPED)); - } - strcat(statusbar, "+"); - if (filter->followers == PW_NODE_STATE_ERROR) - strcat(statusbar, "ALL"); - else for (enum pw_node_state showstate = PW_NODE_STATE_RUNNING; showstate >= PW_NODE_STATE_ERROR; showstate--) { - if (showstate >= filter->followers) - strcat(statusbar, state_as_string(showstate, SPA_IO_POSITION_STATE_STOPPED)); - } - } - werase(d->win); wattron(d->win, A_REVERSE); wprintw(d->win, "%-*.*s", COLS, COLS, HEADER); - if ((size_t)COLS >= strlen(HEADER) + strlen(statusbar)) - mvwprintw(d->win, 0, COLS - strlen(statusbar), "%s", statusbar); wattroff(d->win, A_REVERSE); wprintw(d->win, "\n"); } else @@ -615,8 +575,6 @@ static void do_refresh(struct data *d, bool force_refresh) spa_list_for_each_safe(n, t, &d->node_list, link) { if (n->driver != n) continue; - if (n->state < filter->state) - continue; print_node(d, n, n, y++); if(!d->batch_mode && y > LINES) @@ -629,9 +587,6 @@ static void do_refresh(struct data *d, bool force_refresh) if (f->driver != n || f == n) continue; - if (f->state < filter->followers) - continue; - print_node(d, n, f, y++); if(!d->batch_mode && y > LINES) break; @@ -816,9 +771,8 @@ static void show_help(const char *name, bool error) { fprintf(error ? stderr : stdout, "Usage:\n%s [options]\n\n" "Options:\n" - " -b, --batch-mode run in non-interactive batch mode\n" + " -b, --batch-mode run in non-interactive batch mode\n" " -n, --iterations = NUMBER exit after NUMBER batch iterations\n" - " -f, --filter = NUMBER start with filter preset NUMBER selected\n" " -r, --remote Remote daemon name\n" "\n" " -h, --help Show this help\n" @@ -853,14 +807,6 @@ static void do_handle_io(void *data, int fd, uint32_t mask) case 'c': reset_xruns(d); break; - case 'f': - d->filter_preset = (d->filter_preset + 1) % N_FILTER_PRESETS; - do_refresh(d, !d->batch_mode); - break; - case 'F': - d->filter_preset = (d->filter_preset - 1 + N_FILTER_PRESETS) % N_FILTER_PRESETS; - do_refresh(d, !d->batch_mode); - break; default: do_refresh(d, !d->batch_mode); break; @@ -872,11 +818,10 @@ int main(int argc, char *argv[]) { struct data data = { 0 }; struct pw_loop *l; - const char *opt_remote = NULL, *remote_name; + const char *opt_remote = NULL; static const struct option long_options[] = { { "batch-mode", no_argument, NULL, 'b' }, { "iterations", required_argument, NULL, 'n' }, - { "filter", required_argument, NULL, 'f' }, { "remote", required_argument, NULL, 'r' }, { "help", no_argument, NULL, 'h' }, { "version", no_argument, NULL, 'V' }, @@ -890,11 +835,10 @@ int main(int argc, char *argv[]) pw_init(&argc, &argv); data.iterations = -1; - data.filter_preset = 0; spa_list_init(&data.node_list); - while ((c = getopt_long(argc, argv, "hVr:o:bn:f:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "hVr:o:bn:", long_options, NULL)) != -1) { switch (c) { case 'h': show_help(argv[0], false); @@ -913,10 +857,6 @@ int main(int argc, char *argv[]) case 'b': data.batch_mode = 1; break; - case 'f': - spa_atoi32(optarg, &data.filter_preset, 10); - data.filter_preset %= N_FILTER_PRESETS; - break; case 'n': spa_atoi32(optarg, &data.iterations, 10); break; @@ -947,14 +887,10 @@ int main(int argc, char *argv[]) pw_context_load_module(data.context, PW_EXTENSION_MODULE_PROFILER, NULL, NULL); - remote_name = "[" PW_DEFAULT_REMOTE "-manager," PW_DEFAULT_REMOTE "]"; - if (opt_remote) - remote_name = opt_remote; - data.core = pw_context_connect(data.context, pw_properties_new( PW_KEY_REMOTE_INTENTION, "manager", - PW_KEY_REMOTE_NAME, remote_name, + PW_KEY_REMOTE_NAME, opt_remote, NULL), 0); if (data.core == NULL) { diff --git a/test/avb-bugs.md b/test/avb-bugs.md deleted file mode 100644 index eaafe4c6c..000000000 --- a/test/avb-bugs.md +++ /dev/null @@ -1,143 +0,0 @@ -# AVB Module Bugs Found via Test Suite - -The following bugs were discovered by building a software test harness -for the AVB protocol stack. All have been fixed in the accompanying -patch series. - -## 1. Heap corruption in server_destroy_descriptors - -**File:** `src/modules/module-avb/internal.h` -**Commit:** `69c721006` - -`server_destroy_descriptors()` called `free(d->ptr)` followed by -`free(d)`, but `d->ptr` points into the same allocation as `d` -(set via `SPA_PTROFF(d, sizeof(struct descriptor), void)` in -`server_add_descriptor()`). This is a double-free / heap corruption -that could cause crashes or memory corruption when tearing down an -AVB server. - -**Fix:** Remove the erroneous `free(d->ptr)` call. - -## 2. NULL pointer dereference in MSRP notify dispatch - -**File:** `src/modules/module-avb/msrp.c`, `src/modules/module-avb/mvrp.c` -**Commit:** `b056e9f85` - -`msrp_notify()` unconditionally calls `dispatch[a->attr.type].notify()` -but the dispatch table entry for `AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED` -has `notify = NULL`. If a talker-failed attribute receives a registrar -state change (e.g., `RX_NEW` triggers `NOTIFY_NEW`), this crashes with -a NULL pointer dereference. The same unguarded pattern exists in -`mvrp_notify()`. - -**Fix:** Add `if (dispatch[a->attr.type].notify)` NULL check before -calling, matching the defensive pattern already used in the encode path. - -## 3. MRP NEW messages never transmitted - -**File:** `src/modules/module-avb/mrp.h`, `src/modules/module-avb/mrp.c`, -`src/modules/module-avb/msrp.c`, `src/modules/module-avb/mvrp.c` -**Commit:** `bc2c41daa` - -`AVB_MRP_SEND_NEW` was defined as `0`. The MSRP and MVRP event handlers -skip attributes with `if (!a->attr.mrp->pending_send)`, treating `0` as -"no pending send". Since the MRP state machine sets `pending_send` to -`AVB_MRP_SEND_NEW` (0) when an attribute in state VN or AN receives a -TX event, NEW messages were silently dropped instead of being -transmitted. This violates IEEE 802.1Q which requires NEW messages to -be sent when an attribute is first declared. - -In practice, the attribute would cycle through VN -> AN -> AA over -successive TX events, eventually sending a JOINMT instead of the -initial NEW. The protocol still functioned because JOINMT also -registers the attribute, but the initial declaration was lost. - -**Fix:** Shift all `AVB_MRP_SEND_*` values to start at 1, so that 0 -unambiguously means "no send pending". Update MSRP and MVRP encoders -to subtract 1 when encoding to the IEEE 802.1Q wire format. - -## 4. ACMP error responses sent with wrong message type - -**File:** `src/modules/module-avb/acmp.c` -**Commit:** `9f4147104` - -In `handle_connect_tx_command()` and `handle_disconnect_tx_command()`, -`AVB_PACKET_ACMP_SET_MESSAGE_TYPE()` is called after the `goto done` -jump target. When `find_stream()` fails (returns NULL), the code jumps -to `done:` without setting the message type, so the error response is -sent with the original command message type (e.g., -`CONNECT_TX_COMMAND = 0`) instead of the correct response type -(`CONNECT_TX_RESPONSE = 1`). - -A controller receiving this malformed response would not recognize it -as a response to its command and would eventually time out. - -**Fix:** Move `AVB_PACKET_ACMP_SET_MESSAGE_TYPE()` before the -`find_stream()` call so the response type is always set correctly. - -## 5. ACMP pending_destroy skips controller cleanup - -**File:** `src/modules/module-avb/acmp.c` -**Commit:** `9f4147104` - -`pending_destroy()` iterates with `list_id < PENDING_CONTROLLER` -(where `PENDING_CONTROLLER = 2`), which only cleans up -`PENDING_TALKER` (0) and `PENDING_LISTENER` (1) lists, skipping -`PENDING_CONTROLLER` (2). Any pending controller requests leak on -shutdown. - -**Fix:** Change `<` to `<=` to include the controller list. - -## 6. Legacy AECP handlers read payload at wrong offset - -**File:** `src/modules/module-avb/aecp-aem.c` - -`handle_acquire_entity_avb_legacy()` and `handle_lock_entity_avb_legacy()` -assign `const struct avb_packet_aecp_aem *p = m;` where `m` is the full -ethernet frame (starting with `struct avb_ethernet_header`). The handlers -then access `p->payload` to read the acquire/lock fields, but this reads -from `m + offsetof(avb_packet_aecp_aem, payload)` instead of the correct -`m + sizeof(avb_ethernet_header) + offsetof(avb_packet_aecp_aem, payload)`. -This causes `descriptor_type` and `descriptor_id` to be read from the -wrong position, leading to incorrect descriptor lookups. - -All other AEM command handlers (e.g., `handle_read_descriptor_common`) -correctly derive `p` via `SPA_PTROFF(h, sizeof(*h), void)`. - -**Fix:** Change `const struct avb_packet_aecp_aem *p = m;` to properly -skip the ethernet header: -```c -const struct avb_ethernet_header *h = m; -const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); -``` - -## 7. Milan LOCK_ENTITY error response uses wrong packet pointer - -**File:** `src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c` - -In `handle_cmd_lock_entity_milan_v12()`, when `server_find_descriptor()` -returns NULL, `reply_status()` is called with `p` (the AEM packet pointer -past the ethernet header) instead of `m` (the full ethernet frame). -`reply_status()` assumes its third argument is the full frame and casts -it as `struct avb_ethernet_header *`. With the wrong pointer, the -response ethernet header (including destination MAC) is corrupted. - -**Fix:** Change `reply_status(aecp, ..., p, len)` to -`reply_status(aecp, ..., m, len)`. - -## 8. Lock entity re-lock timeout uses wrong units - -**File:** `src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c` - -When a controller that already holds the lock sends another lock request -(to refresh it), the expire timeout is extended by: -```c -lock->base_info.expire_timeout += - AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND; -``` -`AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND` is `60` (raw seconds), -but `expire_timeout` is in nanoseconds. This adds only 60 nanoseconds -instead of 60 seconds. The initial lock correctly uses -`AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND * SPA_NSEC_PER_SEC`. - -**Fix:** Multiply by `SPA_NSEC_PER_SEC` to match the nanosecond units. diff --git a/test/meson.build b/test/meson.build index de67ca70b..5e38db383 100644 --- a/test/meson.build +++ b/test/meson.build @@ -163,42 +163,3 @@ if valgrind.found() env : valgrind_env, timeout_multiplier : 3) endif - -if build_avb_virtual - avb_test_inc = [pwtest_inc, include_directories('../src/modules')] - avb_module_sources = [ - '../src/modules/module-avb/avb.c', - '../src/modules/module-avb/adp.c', - '../src/modules/module-avb/acmp.c', - '../src/modules/module-avb/aecp.c', - '../src/modules/module-avb/aecp-aem.c', - '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-available.c', - '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-control.c', - '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-name.c', - '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-clock-source.c', - '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-sampling-rate.c', - '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-deregister-unsolicited-notifications.c', - '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.c', - '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-stream-format.c', - '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c', - '../src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-configuration.c', - '../src/modules/module-avb/aecp-aem-cmds-resps/reply-unsol-helpers.c', - '../src/modules/module-avb/es-builder.c', - '../src/modules/module-avb/avdecc.c', - '../src/modules/module-avb/descriptors.c', - '../src/modules/module-avb/maap.c', - '../src/modules/module-avb/mmrp.c', - '../src/modules/module-avb/mrp.c', - '../src/modules/module-avb/msrp.c', - '../src/modules/module-avb/mvrp.c', - '../src/modules/module-avb/srp.c', - '../src/modules/module-avb/stream.c', - ] - test('test-avb', - executable('test-avb', - ['test-avb.c'] + avb_module_sources, - include_directories: avb_test_inc, - dependencies: [spa_dep, pipewire_dep, mathlib, dl_lib, rt_lib], - link_with: pwtest_lib) - ) -endif diff --git a/test/test-avb-utils.h b/test/test-avb-utils.h deleted file mode 100644 index d4552f985..000000000 --- a/test/test-avb-utils.h +++ /dev/null @@ -1,356 +0,0 @@ -/* AVB test utilities */ -/* SPDX-FileCopyrightText: Copyright © 2026 PipeWire contributors */ -/* SPDX-License-Identifier: MIT */ - -#ifndef TEST_AVB_UTILS_H -#define TEST_AVB_UTILS_H - -#include - -#include "module-avb/internal.h" -#include "module-avb/packets.h" -#include "module-avb/adp.h" -#include "module-avb/acmp.h" -#include "module-avb/mrp.h" -#include "module-avb/msrp.h" -#include "module-avb/mvrp.h" -#include "module-avb/mmrp.h" -#include "module-avb/maap.h" -#include "module-avb/aecp.h" -#include "module-avb/aecp-aem.h" -#include "module-avb/aecp-aem-descriptors.h" -#include "module-avb/aecp-aem-state.h" -#include "module-avb/iec61883.h" -#include "module-avb/aaf.h" -#include "module-avb/stream.h" -#include "module-avb/descriptors.h" -#include "module-avb/avb-transport-loopback.h" - -#define server_emit_message(s,n,m,l) \ - spa_hook_list_call(&(s)->listener_list, struct server_events, message, 0, n, m, l) -#define server_emit_periodic(s,n) \ - spa_hook_list_call(&(s)->listener_list, struct server_events, periodic, 0, n) - -/** - * Create a test AVB server with loopback transport. - * All protocol handlers are registered. No network access required. - */ -static inline struct server *avb_test_server_new(struct impl *impl) -{ - struct server *server; - - server = calloc(1, sizeof(*server)); - if (server == NULL) - return NULL; - - server->impl = impl; - server->ifname = strdup("test0"); - server->avb_mode = AVB_MODE_LEGACY; - server->transport = &avb_transport_loopback; - - spa_list_append(&impl->servers, &server->link); - spa_hook_list_init(&server->listener_list); - spa_list_init(&server->descriptors); - spa_list_init(&server->streams); - - if (server->transport->setup(server) < 0) - goto error; - - server->mrp = avb_mrp_new(server); - if (server->mrp == NULL) - goto error; - - avb_aecp_register(server); - server->maap = avb_maap_register(server); - server->mmrp = avb_mmrp_register(server); - server->msrp = avb_msrp_register(server); - server->mvrp = avb_mvrp_register(server); - avb_adp_register(server); - avb_acmp_register(server); - - server->domain_attr = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN); - server->domain_attr->attr.domain.sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT; - server->domain_attr->attr.domain.sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT; - server->domain_attr->attr.domain.sr_class_vid = htons(AVB_DEFAULT_VLAN); - - avb_mrp_attribute_begin(server->domain_attr->mrp, 0); - avb_mrp_attribute_join(server->domain_attr->mrp, 0, true); - - /* Add a minimal entity descriptor so ADP can advertise. - * We skip init_descriptors() because it creates streams that - * need a pw_core connection. */ - { - struct avb_aem_desc_entity entity; - memset(&entity, 0, sizeof(entity)); - entity.entity_id = htobe64(server->entity_id); - entity.entity_model_id = htobe64(0x0001000000000001ULL); - entity.entity_capabilities = htonl( - AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED | - AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED | - AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED); - entity.talker_stream_sources = htons(1); - entity.talker_capabilities = htons( - AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED | - AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE); - entity.listener_stream_sinks = htons(1); - entity.listener_capabilities = htons( - AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED | - AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK); - entity.configurations_count = htons(1); - server_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0, - sizeof(entity), &entity); - } - - return server; - -error: - free(server->ifname); - free(server); - return NULL; -} - -static inline void avb_test_server_free(struct server *server) -{ - avdecc_server_free(server); -} - -/** - * Inject a raw packet into the server's protocol dispatch. - * This simulates receiving a packet from the network. - */ -static inline void avb_test_inject_packet(struct server *server, - uint64_t now, const void *data, int len) -{ - server_emit_message(server, now, data, len); -} - -/** - * Trigger the periodic callback with a given timestamp. - * Use this to advance time and test timeout/readvertise logic. - */ -static inline void avb_test_tick(struct server *server, uint64_t now) -{ - server_emit_periodic(server, now); -} - -/** - * Build an ADP entity available packet. - * Returns the packet size, or -1 on error. - */ -static inline int avb_test_build_adp_entity_available( - uint8_t *buf, size_t bufsize, - const uint8_t src_mac[6], - uint64_t entity_id, - int valid_time) -{ - struct avb_ethernet_header *h; - struct avb_packet_adp *p; - size_t len = sizeof(*h) + sizeof(*p); - static const uint8_t bmac[6] = AVB_BROADCAST_MAC; - - if (bufsize < len) - return -1; - - memset(buf, 0, len); - - h = (struct avb_ethernet_header *)buf; - memcpy(h->dest, bmac, 6); - memcpy(h->src, src_mac, 6); - h->type = htons(AVB_TSN_ETH); - - p = (struct avb_packet_adp *)(buf + sizeof(*h)); - AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP); - AVB_PACKET_SET_LENGTH(&p->hdr, AVB_ADP_CONTROL_DATA_LENGTH); - AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_AVAILABLE); - AVB_PACKET_ADP_SET_VALID_TIME(p, valid_time); - p->entity_id = htobe64(entity_id); - - return len; -} - -/** - * Build an ADP entity departing packet. - */ -static inline int avb_test_build_adp_entity_departing( - uint8_t *buf, size_t bufsize, - const uint8_t src_mac[6], - uint64_t entity_id) -{ - struct avb_ethernet_header *h; - struct avb_packet_adp *p; - size_t len = sizeof(*h) + sizeof(*p); - static const uint8_t bmac[6] = AVB_BROADCAST_MAC; - - if (bufsize < len) - return -1; - - memset(buf, 0, len); - - h = (struct avb_ethernet_header *)buf; - memcpy(h->dest, bmac, 6); - memcpy(h->src, src_mac, 6); - h->type = htons(AVB_TSN_ETH); - - p = (struct avb_packet_adp *)(buf + sizeof(*h)); - AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP); - AVB_PACKET_SET_LENGTH(&p->hdr, AVB_ADP_CONTROL_DATA_LENGTH); - AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_DEPARTING); - p->entity_id = htobe64(entity_id); - - return len; -} - -/** - * Build an ADP entity discover packet. - */ -static inline int avb_test_build_adp_entity_discover( - uint8_t *buf, size_t bufsize, - const uint8_t src_mac[6], - uint64_t entity_id) -{ - struct avb_ethernet_header *h; - struct avb_packet_adp *p; - size_t len = sizeof(*h) + sizeof(*p); - static const uint8_t bmac[6] = AVB_BROADCAST_MAC; - - if (bufsize < len) - return -1; - - memset(buf, 0, len); - - h = (struct avb_ethernet_header *)buf; - memcpy(h->dest, bmac, 6); - memcpy(h->src, src_mac, 6); - h->type = htons(AVB_TSN_ETH); - - p = (struct avb_packet_adp *)(buf + sizeof(*h)); - AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP); - AVB_PACKET_SET_LENGTH(&p->hdr, AVB_ADP_CONTROL_DATA_LENGTH); - AVB_PACKET_ADP_SET_MESSAGE_TYPE(p, AVB_ADP_MESSAGE_TYPE_ENTITY_DISCOVER); - p->entity_id = htobe64(entity_id); - - return len; -} - -/** - * Build an AECP AEM command packet for injection. - * Returns packet size, or -1 on error. - */ -static inline int avb_test_build_aecp_aem( - uint8_t *buf, size_t bufsize, - const uint8_t src_mac[6], - uint64_t target_guid, - uint64_t controller_guid, - uint16_t sequence_id, - uint16_t command_type, - const void *payload, size_t payload_size) -{ - struct avb_ethernet_header *h; - struct avb_packet_aecp_aem *p; - size_t len = sizeof(*h) + sizeof(*p) + payload_size; - - if (bufsize < len) - return -1; - - memset(buf, 0, len); - - h = (struct avb_ethernet_header *)buf; - memcpy(h->dest, (uint8_t[]){ 0x91, 0xe0, 0xf0, 0x01, 0x00, 0x00 }, 6); - memcpy(h->src, src_mac, 6); - h->type = htons(AVB_TSN_ETH); - - p = SPA_PTROFF(h, sizeof(*h), void); - AVB_PACKET_SET_SUBTYPE(&p->aecp.hdr, AVB_SUBTYPE_AECP); - AVB_PACKET_AECP_SET_MESSAGE_TYPE(&p->aecp, AVB_AECP_MESSAGE_TYPE_AEM_COMMAND); - AVB_PACKET_AECP_SET_STATUS(&p->aecp, 0); - AVB_PACKET_SET_LENGTH(&p->aecp.hdr, payload_size + 12); - p->aecp.target_guid = htobe64(target_guid); - p->aecp.controller_guid = htobe64(controller_guid); - p->aecp.sequence_id = htons(sequence_id); - AVB_PACKET_AEM_SET_COMMAND_TYPE(p, command_type); - - if (payload && payload_size > 0) - memcpy(p->payload, payload, payload_size); - - return len; -} - -/** - * Create a test AVB server in Milan v1.2 mode with loopback transport. - * The entity descriptor is properly sized for Milan state (lock, unsol). - */ -static inline struct server *avb_test_server_new_milan(struct impl *impl) -{ - struct server *server; - - server = calloc(1, sizeof(*server)); - if (server == NULL) - return NULL; - - server->impl = impl; - server->ifname = strdup("test0"); - server->avb_mode = AVB_MODE_MILAN_V12; - server->transport = &avb_transport_loopback; - - spa_list_append(&impl->servers, &server->link); - spa_hook_list_init(&server->listener_list); - spa_list_init(&server->descriptors); - spa_list_init(&server->streams); - - if (server->transport->setup(server) < 0) - goto error; - - server->mrp = avb_mrp_new(server); - if (server->mrp == NULL) - goto error; - - avb_aecp_register(server); - server->maap = avb_maap_register(server); - server->mmrp = avb_mmrp_register(server); - server->msrp = avb_msrp_register(server); - server->mvrp = avb_mvrp_register(server); - avb_adp_register(server); - avb_acmp_register(server); - - server->domain_attr = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN); - server->domain_attr->attr.domain.sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT; - server->domain_attr->attr.domain.sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT; - server->domain_attr->attr.domain.sr_class_vid = htons(AVB_DEFAULT_VLAN); - - avb_mrp_attribute_begin(server->domain_attr->mrp, 0); - avb_mrp_attribute_join(server->domain_attr->mrp, 0, true); - - /* Add Milan-sized entity descriptor with lock/unsol state */ - { - struct aecp_aem_entity_milan_state entity_state; - memset(&entity_state, 0, sizeof(entity_state)); - entity_state.state.desc.entity_id = htobe64(server->entity_id); - entity_state.state.desc.entity_model_id = htobe64(0x0001000000000001ULL); - entity_state.state.desc.entity_capabilities = htonl( - AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED | - AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED | - AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED); - entity_state.state.desc.talker_stream_sources = htons(1); - entity_state.state.desc.talker_capabilities = htons( - AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED | - AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE); - entity_state.state.desc.listener_stream_sinks = htons(1); - entity_state.state.desc.listener_capabilities = htons( - AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED | - AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK); - entity_state.state.desc.configurations_count = htons(1); - server_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0, - sizeof(entity_state), &entity_state); - } - - return server; - -error: - free(server->ifname); - free(server); - return NULL; -} - -#endif /* TEST_AVB_UTILS_H */ diff --git a/test/test-avb.c b/test/test-avb.c deleted file mode 100644 index ac1966a68..000000000 --- a/test/test-avb.c +++ /dev/null @@ -1,4017 +0,0 @@ -/* AVB tests */ -/* SPDX-FileCopyrightText: Copyright © 2026 PipeWire contributors */ -/* SPDX-License-Identifier: MIT */ - -#include "pwtest.h" - -#include - -#include "module-avb/aecp-aem-descriptors.h" -#include "module-avb/aecp-aem.h" -#include "module-avb/aecp-aem-types.h" -#include "module-avb/aecp-aem-cmds-resps/cmd-lock-entity.h" -#include "test-avb-utils.h" - -static struct impl *test_impl_new(void) -{ - struct impl *impl; - struct pw_main_loop *ml; - struct pw_context *context; - - pw_init(0, NULL); - - ml = pw_main_loop_new(NULL); - pwtest_ptr_notnull(ml); - - context = pw_context_new(pw_main_loop_get_loop(ml), - pw_properties_new( - PW_KEY_CONFIG_NAME, "null", - NULL), 0); - pwtest_ptr_notnull(context); - - impl = calloc(1, sizeof(*impl)); - pwtest_ptr_notnull(impl); - - impl->loop = pw_main_loop_get_loop(ml); - impl->timer_queue = pw_context_get_timer_queue(context); - impl->context = context; - spa_list_init(&impl->servers); - - return impl; -} - -static void test_impl_free(struct impl *impl) -{ - struct server *s; - spa_list_consume(s, &impl->servers, link) - avb_test_server_free(s); - free(impl); - pw_deinit(); -} - -/* - * Test: inject an ADP ENTITY_AVAILABLE packet and verify - * that the server processes it without error. - */ -PWTEST(avb_adp_entity_available) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x02 }; - uint64_t remote_entity_id = 0x020000fffe000002ULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Build and inject an entity available packet from a remote device */ - len = avb_test_build_adp_entity_available(pkt, sizeof(pkt), - remote_mac, remote_entity_id, 10); - pwtest_int_gt(len, 0); - - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* The packet should have been processed without crashing. - * We can't easily inspect ADP internal state without exposing it, - * but we can verify the server is still functional by doing another - * inject and triggering periodic. */ - avb_test_tick(server, 2 * SPA_NSEC_PER_SEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: inject ENTITY_AVAILABLE then ENTITY_DEPARTING for the same entity. - */ -PWTEST(avb_adp_entity_departing) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x03 }; - uint64_t remote_entity_id = 0x020000fffe000003ULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* First make the entity known */ - len = avb_test_build_adp_entity_available(pkt, sizeof(pkt), - remote_mac, remote_entity_id, 10); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Now send departing */ - len = avb_test_build_adp_entity_departing(pkt, sizeof(pkt), - remote_mac, remote_entity_id); - pwtest_int_gt(len, 0); - avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len); - - avb_test_tick(server, 3 * SPA_NSEC_PER_SEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: inject ENTITY_DISCOVER with entity_id=0 (discover all). - * The server should respond with its own entity advertisement - * once it has one (after periodic runs check_advertise). - */ -PWTEST(avb_adp_entity_discover) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x04 }; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Trigger periodic to let the server advertise its own entity - * (check_advertise reads the entity descriptor) */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - avb_loopback_clear_packets(server); - - /* Send discover-all (entity_id = 0) */ - len = avb_test_build_adp_entity_discover(pkt, sizeof(pkt), - remote_mac, 0); - pwtest_int_gt(len, 0); - avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len); - - /* The server should have sent an advertise response */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: entity timeout — add an entity, then advance time past - * valid_time + 2 seconds and verify periodic cleans it up. - */ -PWTEST(avb_adp_entity_timeout) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x05 }; - uint64_t remote_entity_id = 0x020000fffe000005ULL; - int valid_time = 10; /* seconds */ - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Add entity */ - len = avb_test_build_adp_entity_available(pkt, sizeof(pkt), - remote_mac, remote_entity_id, valid_time); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Tick at various times before timeout — entity should survive */ - avb_test_tick(server, 5 * SPA_NSEC_PER_SEC); - avb_test_tick(server, 10 * SPA_NSEC_PER_SEC); - - /* Tick past valid_time + 2 seconds from last_time (1s + 12s = 13s) */ - avb_test_tick(server, 14 * SPA_NSEC_PER_SEC); - - /* The entity should have been timed out and cleaned up. - * If the entity was still present and had advertise=true, a departing - * packet would be sent. Inject a discover to verify: if the entity - * is gone, no response for that specific entity_id. */ - - avb_loopback_clear_packets(server); - len = avb_test_build_adp_entity_discover(pkt, sizeof(pkt), - remote_mac, remote_entity_id); - avb_test_inject_packet(server, 15 * SPA_NSEC_PER_SEC, pkt, len); - - /* Remote entities don't have advertise=true, so even before timeout - * a discover for them wouldn't generate a response. But at least - * the timeout path was exercised without crashes. */ - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: basic MRP attribute lifecycle — create, begin, join. - */ -PWTEST(avb_mrp_attribute_lifecycle) -{ - struct impl *impl; - struct server *server; - struct avb_msrp_attribute *attr; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Create an MSRP talker attribute */ - attr = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE); - pwtest_ptr_notnull(attr); - pwtest_ptr_notnull(attr->mrp); - - /* Begin and join the attribute */ - avb_mrp_attribute_begin(attr->mrp, 0); - avb_mrp_attribute_join(attr->mrp, 0, true); - - /* Tick to process the MRP state machine */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - avb_test_tick(server, 2 * SPA_NSEC_PER_SEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: server with Milan v1.2 mode. - */ -PWTEST(avb_milan_server_create) -{ - struct impl *impl; - struct server *server; - - impl = test_impl_new(); - - /* Create a Milan-mode server manually */ - server = calloc(1, sizeof(*server)); - pwtest_ptr_notnull(server); - - server->impl = impl; - server->ifname = strdup("test0"); - server->avb_mode = AVB_MODE_MILAN_V12; - server->transport = &avb_transport_loopback; - - spa_list_append(&impl->servers, &server->link); - spa_hook_list_init(&server->listener_list); - spa_list_init(&server->descriptors); - - pwtest_int_eq(server->transport->setup(server), 0); - - server->mrp = avb_mrp_new(server); - pwtest_ptr_notnull(server->mrp); - - avb_aecp_register(server); - server->maap = avb_maap_register(server); - server->mmrp = avb_mmrp_register(server); - server->msrp = avb_msrp_register(server); - server->mvrp = avb_mvrp_register(server); - avb_adp_register(server); - avb_acmp_register(server); - - server->domain_attr = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN); - server->domain_attr->attr.domain.sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT; - server->domain_attr->attr.domain.sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT; - server->domain_attr->attr.domain.sr_class_vid = htons(AVB_DEFAULT_VLAN); - - avb_mrp_attribute_begin(server->domain_attr->mrp, 0); - avb_mrp_attribute_join(server->domain_attr->mrp, 0, true); - - /* Add minimal entity descriptor (skip init_descriptors which needs pw_core) */ - { - struct avb_aem_desc_entity entity; - memset(&entity, 0, sizeof(entity)); - entity.entity_id = htobe64(server->entity_id); - entity.entity_model_id = htobe64(0x0001000000000001ULL); - entity.configurations_count = htons(1); - server_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0, - sizeof(entity), &entity); - } - - /* Verify Milan mode was set correctly */ - pwtest_str_eq(get_avb_mode_str(server->avb_mode), "Milan V1.2"); - - /* Tick to exercise periodic handlers with Milan descriptors */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * ===================================================================== - * Phase 3: MRP State Machine Tests - * ===================================================================== - */ - -/* - * Test: MRP attribute begin sets initial state, join(new=true) enables - * pending_send after TX event via periodic tick. - */ -PWTEST(avb_mrp_begin_join_new_tx) -{ - struct impl *impl; - struct server *server; - struct avb_msrp_attribute *attr; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Create a talker attribute */ - attr = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE); - pwtest_ptr_notnull(attr); - - /* After begin, pending_send should be 0 */ - avb_mrp_attribute_begin(attr->mrp, 0); - pwtest_int_eq(attr->mrp->pending_send, 0); - - /* Join with new=true */ - avb_mrp_attribute_join(attr->mrp, 0, true); - - /* Tick to let timers initialize (first periodic skips events) */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - - /* Tick past join timer (100ms) to trigger TX event */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC); - - /* After TX, pending_send should be set (NEW=0 encoded as non-zero - * only if the state machine decided to send). The VN state on TX - * produces SEND_NEW. But pending_send is only written if joined=true. */ - /* We mainly verify no crash and that the state machine ran. */ - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: MRP attribute join then leave cycle. - * After leave, the attribute should eventually stop sending. - */ -PWTEST(avb_mrp_join_leave_cycle) -{ - struct impl *impl; - struct server *server; - struct avb_msrp_attribute *attr; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - attr = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE); - pwtest_ptr_notnull(attr); - - avb_mrp_attribute_begin(attr->mrp, 0); - avb_mrp_attribute_join(attr->mrp, 0, true); - - /* Let the state machine run a few cycles */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC); - - /* Now leave */ - avb_mrp_attribute_leave(attr->mrp, 2 * SPA_NSEC_PER_SEC); - - /* After leave, pending_send should reflect leaving state. - * The next TX event should send LV and transition to VO. */ - avb_test_tick(server, 2 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC); - - /* After the TX, pending_send should be 0 (joined is false, - * so pending_send is not updated by the state machine). */ - pwtest_int_eq(attr->mrp->pending_send, 0); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: MRP attribute receives RX_NEW, which triggers a registrar - * notification (NOTIFY_NEW). Verify via a notification tracker. - */ -struct notify_tracker { - int new_count; - int join_count; - int leave_count; - uint8_t last_notify; -}; - -static void track_mrp_notify(void *data, uint64_t now, - struct avb_mrp_attribute *attr, uint8_t notify) -{ - struct notify_tracker *t = data; - t->last_notify = notify; - switch (notify) { - case AVB_MRP_NOTIFY_NEW: - t->new_count++; - break; - case AVB_MRP_NOTIFY_JOIN: - t->join_count++; - break; - case AVB_MRP_NOTIFY_LEAVE: - t->leave_count++; - break; - } -} - -static const struct avb_mrp_events test_mrp_events = { - AVB_VERSION_MRP_EVENTS, - .notify = track_mrp_notify, -}; - -PWTEST(avb_mrp_rx_new_notification) -{ - struct impl *impl; - struct server *server; - struct avb_msrp_attribute *attr; - struct spa_hook listener; - struct notify_tracker tracker = { 0 }; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Register a global MRP listener to track notifications */ - avb_mrp_add_listener(server->mrp, &listener, &test_mrp_events, &tracker); - - attr = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE); - pwtest_ptr_notnull(attr); - - avb_mrp_attribute_begin(attr->mrp, 0); - avb_mrp_attribute_join(attr->mrp, 0, true); - - /* Simulate receiving NEW from a peer */ - avb_mrp_attribute_rx_event(attr->mrp, 1 * SPA_NSEC_PER_SEC, - AVB_MRP_ATTRIBUTE_EVENT_NEW); - - /* RX_NEW should trigger NOTIFY_NEW on the registrar */ - pwtest_int_eq(tracker.new_count, 1); - pwtest_int_eq(tracker.last_notify, AVB_MRP_NOTIFY_NEW); - - /* Simulate receiving JOININ from a peer (already IN, no new notification) */ - avb_mrp_attribute_rx_event(attr->mrp, 2 * SPA_NSEC_PER_SEC, - AVB_MRP_ATTRIBUTE_EVENT_JOININ); - /* Registrar was already IN, so no additional JOIN notification */ - pwtest_int_eq(tracker.join_count, 0); - - spa_hook_remove(&listener); - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: MRP registrar leave timer — after RX_LV, the registrar enters - * LV state. After MRP_LVTIMER_MS (1000ms), LV_TIMER fires and - * registrar transitions to MT with NOTIFY_LEAVE. - */ -PWTEST(avb_mrp_registrar_leave_timer) -{ - struct impl *impl; - struct server *server; - struct avb_msrp_attribute *attr; - struct spa_hook listener; - struct notify_tracker tracker = { 0 }; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - avb_mrp_add_listener(server->mrp, &listener, &test_mrp_events, &tracker); - - attr = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE); - avb_mrp_attribute_begin(attr->mrp, 0); - avb_mrp_attribute_join(attr->mrp, 0, true); - - /* Get registrar to IN state via RX_NEW */ - avb_mrp_attribute_rx_event(attr->mrp, 1 * SPA_NSEC_PER_SEC, - AVB_MRP_ATTRIBUTE_EVENT_NEW); - pwtest_int_eq(tracker.new_count, 1); - - /* RX_LV transitions registrar IN -> LV, sets leave_timeout */ - avb_mrp_attribute_rx_event(attr->mrp, 2 * SPA_NSEC_PER_SEC, - AVB_MRP_ATTRIBUTE_EVENT_LV); - - /* Tick before the leave timer expires — no LEAVE notification yet */ - avb_test_tick(server, 2 * SPA_NSEC_PER_SEC + 500 * SPA_NSEC_PER_MSEC); - pwtest_int_eq(tracker.leave_count, 0); - - /* Tick after the leave timer expires (1000ms after RX_LV at 2s = 3s) */ - avb_test_tick(server, 3 * SPA_NSEC_PER_SEC + 100 * SPA_NSEC_PER_MSEC); - pwtest_int_eq(tracker.leave_count, 1); - - spa_hook_remove(&listener); - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: Multiple MRP attributes coexist — events applied to all. - */ -PWTEST(avb_mrp_multiple_attributes) -{ - struct impl *impl; - struct server *server; - struct avb_msrp_attribute *attr1, *attr2; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - attr1 = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE); - attr2 = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_LISTENER); - pwtest_ptr_notnull(attr1); - pwtest_ptr_notnull(attr2); - - avb_mrp_attribute_begin(attr1->mrp, 0); - avb_mrp_attribute_join(attr1->mrp, 0, true); - - avb_mrp_attribute_begin(attr2->mrp, 0); - avb_mrp_attribute_join(attr2->mrp, 0, false); - - /* Periodic tick should apply to both attributes without crash */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC); - avb_test_tick(server, 2 * SPA_NSEC_PER_SEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * ===================================================================== - * Phase 3: MSRP Tests - * ===================================================================== - */ - -/* - * Test: Create each MSRP attribute type and verify fields. - */ -PWTEST(avb_msrp_attribute_types) -{ - struct impl *impl; - struct server *server; - struct avb_msrp_attribute *talker, *talker_fail, *listener_attr, *domain; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Create all four MSRP attribute types */ - talker = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE); - pwtest_ptr_notnull(talker); - pwtest_int_eq(talker->type, AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE); - - talker_fail = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED); - pwtest_ptr_notnull(talker_fail); - pwtest_int_eq(talker_fail->type, AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED); - - listener_attr = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_LISTENER); - pwtest_ptr_notnull(listener_attr); - pwtest_int_eq(listener_attr->type, AVB_MSRP_ATTRIBUTE_TYPE_LISTENER); - - domain = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN); - pwtest_ptr_notnull(domain); - pwtest_int_eq(domain->type, AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN); - - /* Configure talker with stream parameters */ - talker->attr.talker.stream_id = htobe64(0x020000fffe000001ULL); - talker->attr.talker.vlan_id = htons(AVB_DEFAULT_VLAN); - talker->attr.talker.tspec_max_frame_size = htons(256); - talker->attr.talker.tspec_max_interval_frames = htons( - AVB_MSRP_TSPEC_MAX_INTERVAL_FRAMES_DEFAULT); - talker->attr.talker.priority = AVB_MSRP_PRIORITY_DEFAULT; - talker->attr.talker.rank = AVB_MSRP_RANK_DEFAULT; - - /* Configure listener for same stream */ - listener_attr->attr.listener.stream_id = htobe64(0x020000fffe000001ULL); - listener_attr->param = AVB_MSRP_LISTENER_PARAM_READY; - - /* Begin and join all attributes */ - avb_mrp_attribute_begin(talker->mrp, 0); - avb_mrp_attribute_join(talker->mrp, 0, true); - avb_mrp_attribute_begin(talker_fail->mrp, 0); - avb_mrp_attribute_join(talker_fail->mrp, 0, true); - avb_mrp_attribute_begin(listener_attr->mrp, 0); - avb_mrp_attribute_join(listener_attr->mrp, 0, true); - avb_mrp_attribute_begin(domain->mrp, 0); - avb_mrp_attribute_join(domain->mrp, 0, true); - - /* Tick to exercise all attribute types through the state machine */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: MSRP domain attribute encode/transmit via loopback. - * After join+TX, the domain attribute should produce a packet. - */ -PWTEST(avb_msrp_domain_transmit) -{ - struct impl *impl; - struct server *server; - struct avb_msrp_attribute *domain; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* The test server already has a domain_attr, but create another - * to test independent domain attribute behavior */ - domain = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN); - domain->attr.domain.sr_class_id = 7; - domain->attr.domain.sr_class_priority = 2; - domain->attr.domain.sr_class_vid = htons(100); - - avb_mrp_attribute_begin(domain->mrp, 0); - avb_mrp_attribute_join(domain->mrp, 0, true); - - /* Let timers initialize and then trigger TX */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - avb_loopback_clear_packets(server); - - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC); - - /* MSRP should have transmitted a packet with domain data */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: MSRP talker advertise encode/transmit via loopback. - */ -PWTEST(avb_msrp_talker_transmit) -{ - struct impl *impl; - struct server *server; - struct avb_msrp_attribute *talker; - uint64_t stream_id = 0x020000fffe000001ULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - talker = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE); - pwtest_ptr_notnull(talker); - - talker->attr.talker.stream_id = htobe64(stream_id); - talker->attr.talker.vlan_id = htons(AVB_DEFAULT_VLAN); - talker->attr.talker.tspec_max_frame_size = htons(256); - talker->attr.talker.tspec_max_interval_frames = htons(1); - talker->attr.talker.priority = AVB_MSRP_PRIORITY_DEFAULT; - talker->attr.talker.rank = AVB_MSRP_RANK_DEFAULT; - - avb_mrp_attribute_begin(talker->mrp, 0); - avb_mrp_attribute_join(talker->mrp, 0, true); - - /* Let timers initialize */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - avb_loopback_clear_packets(server); - - /* Trigger TX */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC); - - /* Should have transmitted the talker advertise */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - - /* Read the packet and verify it contains valid MSRP data */ - { - uint8_t buf[2048]; - int len; - struct avb_packet_mrp *mrp_pkt; - - len = avb_loopback_get_packet(server, buf, sizeof(buf)); - pwtest_int_gt(len, (int)sizeof(struct avb_packet_mrp)); - - mrp_pkt = (struct avb_packet_mrp *)buf; - pwtest_int_eq(mrp_pkt->version, AVB_MRP_PROTOCOL_VERSION); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * ===================================================================== - * Phase 3: MRP Packet Parsing Tests - * ===================================================================== - */ - -struct parse_tracker { - int check_header_count; - int attr_event_count; - int process_count; - uint8_t last_attr_type; - uint8_t last_event; - uint8_t last_param; -}; - -static bool test_check_header(void *data, const void *hdr, - size_t *hdr_size, bool *has_params) -{ - struct parse_tracker *t = data; - const struct avb_packet_mrp_hdr *h = hdr; - t->check_header_count++; - - /* Accept attribute types 1-4 (MSRP-like) */ - if (h->attribute_type < 1 || h->attribute_type > 4) - return false; - - *hdr_size = sizeof(struct avb_packet_msrp_msg); - *has_params = (h->attribute_type == AVB_MSRP_ATTRIBUTE_TYPE_LISTENER); - return true; -} - -static int test_attr_event(void *data, uint64_t now, - uint8_t attribute_type, uint8_t event) -{ - struct parse_tracker *t = data; - t->attr_event_count++; - return 0; -} - -static int test_process(void *data, uint64_t now, - uint8_t attribute_type, const void *value, - uint8_t event, uint8_t param, int index) -{ - struct parse_tracker *t = data; - t->process_count++; - t->last_attr_type = attribute_type; - t->last_event = event; - t->last_param = param; - return 0; -} - -static const struct avb_mrp_parse_info test_parse_info = { - AVB_VERSION_MRP_PARSE_INFO, - .check_header = test_check_header, - .attr_event = test_attr_event, - .process = test_process, -}; - -/* - * Test: Parse a minimal MRP packet with a single domain value. - */ -PWTEST(avb_mrp_parse_single_domain) -{ - struct impl *impl; - struct server *server; - struct parse_tracker tracker = { 0 }; - uint8_t buf[256]; - int pos = 0; - int res; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - memset(buf, 0, sizeof(buf)); - - /* Build MRP packet manually: - * [ethernet header + version] already at offset 0 */ - { - struct avb_packet_mrp *mrp = (struct avb_packet_mrp *)buf; - mrp->version = AVB_MRP_PROTOCOL_VERSION; - pos = sizeof(struct avb_packet_mrp); - } - - /* MSRP message header for domain (type=4, length=4) */ - { - struct avb_packet_msrp_msg *msg = - (struct avb_packet_msrp_msg *)(buf + pos); - struct avb_packet_mrp_vector *v; - struct avb_packet_msrp_domain *d; - uint8_t *ev; - - msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN; - msg->attribute_length = sizeof(struct avb_packet_msrp_domain); - - v = (struct avb_packet_mrp_vector *)msg->attribute_list; - v->lva = 0; - AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); - - d = (struct avb_packet_msrp_domain *)v->first_value; - d->sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT; - d->sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT; - d->sr_class_vid = htons(AVB_DEFAULT_VLAN); - - /* Event byte: 1 value, event=JOININ(1), packed as 1*36 = 36 */ - ev = (uint8_t *)(d + 1); - *ev = AVB_MRP_ATTRIBUTE_EVENT_JOININ * 36; - - msg->attribute_list_length = htons( - sizeof(*v) + sizeof(*d) + 1 + 2); /* +2 for vector end mark */ - - /* Vector end mark */ - pos += sizeof(*msg) + sizeof(*v) + sizeof(*d) + 1; - buf[pos++] = 0; - buf[pos++] = 0; - - /* Attribute end mark */ - buf[pos++] = 0; - buf[pos++] = 0; - } - - res = avb_mrp_parse_packet(server->mrp, 1 * SPA_NSEC_PER_SEC, - buf, pos, &test_parse_info, &tracker); - - pwtest_int_eq(res, 0); - pwtest_int_eq(tracker.check_header_count, 1); - pwtest_int_eq(tracker.process_count, 1); - pwtest_int_eq(tracker.last_attr_type, AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN); - pwtest_int_eq(tracker.last_event, AVB_MRP_ATTRIBUTE_EVENT_JOININ); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: Parse MRP packet with LVA (leave-all) flag set. - */ -PWTEST(avb_mrp_parse_with_lva) -{ - struct impl *impl; - struct server *server; - struct parse_tracker tracker = { 0 }; - uint8_t buf[256]; - int pos = 0; - int res; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - memset(buf, 0, sizeof(buf)); - - { - struct avb_packet_mrp *mrp = (struct avb_packet_mrp *)buf; - mrp->version = AVB_MRP_PROTOCOL_VERSION; - pos = sizeof(struct avb_packet_mrp); - } - - { - struct avb_packet_msrp_msg *msg = - (struct avb_packet_msrp_msg *)(buf + pos); - struct avb_packet_mrp_vector *v; - struct avb_packet_msrp_domain *d; - uint8_t *ev; - - msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN; - msg->attribute_length = sizeof(struct avb_packet_msrp_domain); - - v = (struct avb_packet_mrp_vector *)msg->attribute_list; - v->lva = 1; /* Set LVA flag */ - AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); - - d = (struct avb_packet_msrp_domain *)v->first_value; - d->sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT; - d->sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT; - d->sr_class_vid = htons(AVB_DEFAULT_VLAN); - - ev = (uint8_t *)(d + 1); - *ev = AVB_MRP_ATTRIBUTE_EVENT_NEW * 36; - - msg->attribute_list_length = htons( - sizeof(*v) + sizeof(*d) + 1 + 2); - - pos += sizeof(*msg) + sizeof(*v) + sizeof(*d) + 1; - buf[pos++] = 0; - buf[pos++] = 0; - buf[pos++] = 0; - buf[pos++] = 0; - } - - res = avb_mrp_parse_packet(server->mrp, 1 * SPA_NSEC_PER_SEC, - buf, pos, &test_parse_info, &tracker); - - pwtest_int_eq(res, 0); - pwtest_int_eq(tracker.check_header_count, 1); - pwtest_int_eq(tracker.attr_event_count, 1); /* LVA event fired */ - pwtest_int_eq(tracker.process_count, 1); - pwtest_int_eq(tracker.last_event, AVB_MRP_ATTRIBUTE_EVENT_NEW); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: Parse MRP packet with multiple values (3 values per event byte). - * Verifies the base-6 event decoding logic. - */ -PWTEST(avb_mrp_parse_three_values) -{ - struct impl *impl; - struct server *server; - struct parse_tracker tracker = { 0 }; - uint8_t buf[256]; - int pos = 0; - int res; - uint8_t ev0 = AVB_MRP_ATTRIBUTE_EVENT_NEW; /* 0 */ - uint8_t ev1 = AVB_MRP_ATTRIBUTE_EVENT_JOININ; /* 1 */ - uint8_t ev2 = AVB_MRP_ATTRIBUTE_EVENT_MT; /* 4 */ - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - memset(buf, 0, sizeof(buf)); - - { - struct avb_packet_mrp *mrp = (struct avb_packet_mrp *)buf; - mrp->version = AVB_MRP_PROTOCOL_VERSION; - pos = sizeof(struct avb_packet_mrp); - } - - { - struct avb_packet_msrp_msg *msg = - (struct avb_packet_msrp_msg *)(buf + pos); - struct avb_packet_mrp_vector *v; - struct avb_packet_msrp_domain *d; - uint8_t *ev; - - msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_DOMAIN; - msg->attribute_length = sizeof(struct avb_packet_msrp_domain); - - v = (struct avb_packet_mrp_vector *)msg->attribute_list; - v->lva = 0; - AVB_MRP_VECTOR_SET_NUM_VALUES(v, 3); - - /* First value (domain data) — all 3 values share the same - * first_value pointer in the parse callback */ - d = (struct avb_packet_msrp_domain *)v->first_value; - d->sr_class_id = AVB_MSRP_CLASS_ID_DEFAULT; - d->sr_class_priority = AVB_MSRP_PRIORITY_DEFAULT; - d->sr_class_vid = htons(AVB_DEFAULT_VLAN); - - /* Pack 3 events into 1 byte: ev0*36 + ev1*6 + ev2 */ - ev = (uint8_t *)(d + 1); - *ev = ev0 * 36 + ev1 * 6 + ev2; - - msg->attribute_list_length = htons( - sizeof(*v) + sizeof(*d) + 1 + 2); - - pos += sizeof(*msg) + sizeof(*v) + sizeof(*d) + 1; - buf[pos++] = 0; - buf[pos++] = 0; - buf[pos++] = 0; - buf[pos++] = 0; - } - - res = avb_mrp_parse_packet(server->mrp, 1 * SPA_NSEC_PER_SEC, - buf, pos, &test_parse_info, &tracker); - - pwtest_int_eq(res, 0); - pwtest_int_eq(tracker.process_count, 3); - /* The last value processed should have event MT (4) */ - pwtest_int_eq(tracker.last_event, AVB_MRP_ATTRIBUTE_EVENT_MT); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: MSRP talker-failed attribute with notification. - * This tests the NULL notify crash that was fixed. - */ -PWTEST(avb_msrp_talker_failed_notify) -{ - struct impl *impl; - struct server *server; - struct avb_msrp_attribute *talker_fail; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - talker_fail = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED); - pwtest_ptr_notnull(talker_fail); - - talker_fail->attr.talker_fail.talker.stream_id = - htobe64(0x020000fffe000001ULL); - talker_fail->attr.talker_fail.failure_code = AVB_MRP_FAIL_BANDWIDTH; - - avb_mrp_attribute_begin(talker_fail->mrp, 0); - avb_mrp_attribute_join(talker_fail->mrp, 0, true); - - /* Simulate receiving NEW from a peer — this triggers NOTIFY_NEW - * which calls msrp_notify -> dispatch[TALKER_FAILED].notify. - * Before the fix, this would crash with NULL pointer dereference. */ - avb_mrp_attribute_rx_event(talker_fail->mrp, 1 * SPA_NSEC_PER_SEC, - AVB_MRP_ATTRIBUTE_EVENT_NEW); - - /* If we get here without crashing, the NULL check fix works */ - - /* Also exercise periodic to verify full lifecycle */ - avb_test_tick(server, 2 * SPA_NSEC_PER_SEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * ===================================================================== - * Phase 4: ACMP Integration Tests - * ===================================================================== - */ - -/** - * Build an ACMP packet for injection into a server. - * Returns packet size, or -1 on error. - */ -static int avb_test_build_acmp(uint8_t *buf, size_t bufsize, - const uint8_t src_mac[6], - uint8_t message_type, - uint64_t controller_guid, - uint64_t talker_guid, - uint64_t listener_guid, - uint16_t talker_unique_id, - uint16_t listener_unique_id, - uint16_t sequence_id) -{ - struct avb_ethernet_header *h; - struct avb_packet_acmp *p; - size_t len = sizeof(*h) + sizeof(*p); - static const uint8_t acmp_mac[6] = AVB_BROADCAST_MAC; - - if (bufsize < len) - return -1; - - memset(buf, 0, len); - - h = (struct avb_ethernet_header *)buf; - memcpy(h->dest, acmp_mac, 6); - memcpy(h->src, src_mac, 6); - h->type = htons(AVB_TSN_ETH); - - p = (struct avb_packet_acmp *)(buf + sizeof(*h)); - AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ACMP); - AVB_PACKET_ACMP_SET_MESSAGE_TYPE(p, message_type); - AVB_PACKET_ACMP_SET_STATUS(p, AVB_ACMP_STATUS_SUCCESS); - p->controller_guid = htobe64(controller_guid); - p->talker_guid = htobe64(talker_guid); - p->listener_guid = htobe64(listener_guid); - p->talker_unique_id = htons(talker_unique_id); - p->listener_unique_id = htons(listener_unique_id); - p->sequence_id = htons(sequence_id); - - return len; -} - -/* - * Test: ACMP GET_TX_STATE_COMMAND should respond with NOT_SUPPORTED. - */ -PWTEST(avb_acmp_not_supported) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x10 }; - uint64_t remote_entity_id = 0x020000fffe000010ULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - avb_loopback_clear_packets(server); - - /* Send GET_TX_STATE_COMMAND to our server as talker */ - len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac, - AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND, - remote_entity_id, /* controller */ - server->entity_id, /* talker = us */ - 0, /* listener */ - 0, 0, 42); - pwtest_int_gt(len, 0); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Server should respond with NOT_SUPPORTED */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - - /* Read response and verify it's a GET_TX_STATE_RESPONSE with NOT_SUPPORTED */ - { - uint8_t rbuf[256]; - int rlen; - struct avb_packet_acmp *resp; - - rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header)); - - resp = (struct avb_packet_acmp *)(rbuf + sizeof(struct avb_ethernet_header)); - pwtest_int_eq((int)AVB_PACKET_ACMP_GET_MESSAGE_TYPE(resp), - AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_RESPONSE); - pwtest_int_eq((int)AVB_PACKET_ACMP_GET_STATUS(resp), - AVB_ACMP_STATUS_NOT_SUPPORTED); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: ACMP CONNECT_TX_COMMAND to our server with no streams - * should respond with TALKER_NO_STREAM_INDEX. - */ -PWTEST(avb_acmp_connect_tx_no_stream) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x11 }; - uint64_t remote_entity_id = 0x020000fffe000011ULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - avb_loopback_clear_packets(server); - - /* Send CONNECT_TX_COMMAND — we have no streams configured */ - len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac, - AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND, - remote_entity_id, /* controller */ - server->entity_id, /* talker = us */ - remote_entity_id, /* listener */ - 0, 0, 1); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should respond with CONNECT_TX_RESPONSE + TALKER_NO_STREAM_INDEX */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[256]; - int rlen; - struct avb_packet_acmp *resp; - - rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header)); - - resp = (struct avb_packet_acmp *)(rbuf + sizeof(struct avb_ethernet_header)); - pwtest_int_eq((int)AVB_PACKET_ACMP_GET_MESSAGE_TYPE(resp), - AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE); - pwtest_int_eq((int)AVB_PACKET_ACMP_GET_STATUS(resp), - AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: ACMP message addressed to a different entity_id is ignored. - */ -PWTEST(avb_acmp_wrong_entity_ignored) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x12 }; - uint64_t other_entity = 0xDEADBEEFCAFE0001ULL; - uint64_t controller_entity = 0x020000fffe000012ULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - avb_loopback_clear_packets(server); - - /* CONNECT_TX_COMMAND addressed to a different talker — should be ignored */ - len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac, - AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND, - controller_entity, - other_entity, /* talker = NOT us */ - controller_entity, /* listener */ - 0, 0, 1); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* No response should be sent since the GUID doesn't match */ - pwtest_int_eq(avb_loopback_get_packet_count(server), 0); - - /* CONNECT_RX_COMMAND addressed to a different listener — also ignored */ - len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac, - AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_COMMAND, - controller_entity, - other_entity, /* talker */ - other_entity, /* listener = NOT us */ - 0, 0, 2); - avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len); - - /* Still no response */ - pwtest_int_eq(avb_loopback_get_packet_count(server), 0); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: ACMP CONNECT_RX_COMMAND to our server as listener. - * Should create a pending request and forward CONNECT_TX_COMMAND to talker. - */ -PWTEST(avb_acmp_connect_rx_forward) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x20 }; - uint64_t controller_entity = 0x020000fffe000020ULL; - uint64_t talker_entity = 0x020000fffe000030ULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - avb_loopback_clear_packets(server); - - /* Send CONNECT_RX_COMMAND to us as listener */ - len = avb_test_build_acmp(pkt, sizeof(pkt), controller_mac, - AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_COMMAND, - controller_entity, - talker_entity, /* talker = remote */ - server->entity_id, /* listener = us */ - 0, 0, 100); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* We should have forwarded a CONNECT_TX_COMMAND to the talker */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[256]; - int rlen; - struct avb_packet_acmp *cmd; - - rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header)); - - cmd = (struct avb_packet_acmp *)(rbuf + sizeof(struct avb_ethernet_header)); - pwtest_int_eq((int)AVB_PACKET_ACMP_GET_MESSAGE_TYPE(cmd), - AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_COMMAND); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: ACMP pending timeout and retry behavior. - * After CONNECT_RX_COMMAND, the listener creates a pending request. - * After timeout (2000ms for CONNECT_TX), it should retry once. - * After second timeout, it should be cleaned up. - */ -PWTEST(avb_acmp_pending_timeout) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x21 }; - uint64_t controller_entity = 0x020000fffe000021ULL; - uint64_t talker_entity = 0x020000fffe000031ULL; - int pkt_count_after_forward; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - avb_loopback_clear_packets(server); - - /* Create a pending request via CONNECT_RX_COMMAND */ - len = avb_test_build_acmp(pkt, sizeof(pkt), controller_mac, - AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_COMMAND, - controller_entity, - talker_entity, - server->entity_id, - 0, 0, 200); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Count packets after initial forward */ - pkt_count_after_forward = avb_loopback_get_packet_count(server); - pwtest_int_gt(pkt_count_after_forward, 0); - - /* Drain the packet queue */ - avb_loopback_clear_packets(server); - - /* Tick before timeout (2000ms) — no retry yet */ - avb_test_tick(server, 2 * SPA_NSEC_PER_SEC); - pwtest_int_eq(avb_loopback_get_packet_count(server), 0); - - /* Tick after timeout (1s + 2000ms = 3s) — should retry */ - avb_test_tick(server, 3 * SPA_NSEC_PER_SEC + 100 * SPA_NSEC_PER_MSEC); - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - - avb_loopback_clear_packets(server); - - /* Tick after second timeout — should give up (no more retries) */ - avb_test_tick(server, 5 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC); - /* The pending was freed, no more retries */ - - /* Tick again — should be clean, no crashes */ - avb_test_tick(server, 6 * SPA_NSEC_PER_SEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: ACMP message with wrong EtherType or subtype is filtered. - */ -PWTEST(avb_acmp_packet_filtering) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x13 }; - struct avb_ethernet_header *h; - struct avb_packet_acmp *p; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Build a valid-looking ACMP packet but with wrong EtherType */ - len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac, - AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND, - 0, server->entity_id, 0, 0, 0, 1); - h = (struct avb_ethernet_header *)pkt; - h->type = htons(0x1234); /* Wrong EtherType */ - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - pwtest_int_eq(avb_loopback_get_packet_count(server), 0); - - /* Build packet with wrong subtype */ - len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac, - AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND, - 0, server->entity_id, 0, 0, 0, 2); - p = (struct avb_packet_acmp *)(pkt + sizeof(struct avb_ethernet_header)); - AVB_PACKET_SET_SUBTYPE(&p->hdr, AVB_SUBTYPE_ADP); /* Wrong subtype */ - - avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len); - pwtest_int_eq(avb_loopback_get_packet_count(server), 0); - - /* Build packet with correct parameters — should get response */ - len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac, - AVB_ACMP_MESSAGE_TYPE_GET_TX_STATE_COMMAND, - 0, server->entity_id, 0, 0, 0, 3); - avb_test_inject_packet(server, 3 * SPA_NSEC_PER_SEC, pkt, len); - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * ===================================================================== - * Phase 5: AECP/AEM Entity Model Tests - * ===================================================================== - */ - -/* - * Test: AECP READ_DESCRIPTOR for the entity descriptor. - * Verifies that a valid READ_DESCRIPTOR command returns SUCCESS - * with the entity descriptor data. - */ -PWTEST(avb_aecp_read_descriptor_entity) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x40 }; - uint64_t controller_id = 0x020000fffe000040ULL; - struct avb_packet_aecp_aem_read_descriptor rd; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - memset(&rd, 0, sizeof(rd)); - rd.configuration = 0; - rd.descriptor_type = htons(AVB_AEM_DESC_ENTITY); - rd.descriptor_id = htons(0); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_READ_DESCRIPTOR, - &rd, sizeof(rd)); - pwtest_int_gt(len, 0); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should get a response */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - - { - uint8_t rbuf[2048]; - int rlen; - struct avb_packet_aecp_aem *resp; - - rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - pwtest_int_gt(rlen, (int)(sizeof(struct avb_ethernet_header) + - sizeof(struct avb_packet_aecp_aem))); - - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - - /* Should be AEM_RESPONSE with SUCCESS */ - pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp), - AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_SUCCESS); - - /* Response should include the descriptor data, making it - * larger than just the header + read_descriptor payload */ - pwtest_int_gt(rlen, (int)(sizeof(struct avb_ethernet_header) + - sizeof(struct avb_packet_aecp_aem) + - sizeof(struct avb_packet_aecp_aem_read_descriptor))); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: AECP READ_DESCRIPTOR for a non-existent descriptor. - * Should return NO_SUCH_DESCRIPTOR error. - */ -PWTEST(avb_aecp_read_descriptor_not_found) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x41 }; - uint64_t controller_id = 0x020000fffe000041ULL; - struct avb_packet_aecp_aem_read_descriptor rd; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Request a descriptor type that doesn't exist */ - memset(&rd, 0, sizeof(rd)); - rd.descriptor_type = htons(AVB_AEM_DESC_AUDIO_UNIT); - rd.descriptor_id = htons(0); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_READ_DESCRIPTOR, - &rd, sizeof(rd)); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - - { - uint8_t rbuf[2048]; - int rlen; - struct avb_packet_aecp_aem *resp; - - rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header)); - - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - - pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp), - AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: AECP message filtering — wrong EtherType and subtype. - */ -PWTEST(avb_aecp_packet_filtering) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x42 }; - uint64_t controller_id = 0x020000fffe000042ULL; - struct avb_packet_aecp_aem_read_descriptor rd; - struct avb_ethernet_header *h; - struct avb_packet_aecp_aem *p; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - memset(&rd, 0, sizeof(rd)); - rd.descriptor_type = htons(AVB_AEM_DESC_ENTITY); - rd.descriptor_id = htons(0); - - /* Wrong EtherType — should be filtered */ - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_READ_DESCRIPTOR, - &rd, sizeof(rd)); - h = (struct avb_ethernet_header *)pkt; - h->type = htons(0x1234); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - pwtest_int_eq(avb_loopback_get_packet_count(server), 0); - - /* Wrong subtype — should be filtered */ - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 2, - AVB_AECP_AEM_CMD_READ_DESCRIPTOR, - &rd, sizeof(rd)); - p = SPA_PTROFF(pkt, sizeof(struct avb_ethernet_header), void); - AVB_PACKET_SET_SUBTYPE(&p->aecp.hdr, AVB_SUBTYPE_ADP); - - avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len); - pwtest_int_eq(avb_loopback_get_packet_count(server), 0); - - /* Correct packet — should get a response */ - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 3, - AVB_AECP_AEM_CMD_READ_DESCRIPTOR, - &rd, sizeof(rd)); - avb_test_inject_packet(server, 3 * SPA_NSEC_PER_SEC, pkt, len); - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: AECP unsupported message types (ADDRESS_ACCESS, AVC, VENDOR_UNIQUE). - * Should return NOT_IMPLEMENTED. - */ -PWTEST(avb_aecp_unsupported_message_types) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x43 }; - uint64_t controller_id = 0x020000fffe000043ULL; - struct avb_packet_aecp_aem *p; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Build a basic AECP packet, then change message type to ADDRESS_ACCESS */ - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_READ_DESCRIPTOR, - NULL, 0); - - p = SPA_PTROFF(pkt, sizeof(struct avb_ethernet_header), void); - AVB_PACKET_AECP_SET_MESSAGE_TYPE(&p->aecp, - AVB_AECP_MESSAGE_TYPE_ADDRESS_ACCESS_COMMAND); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - int rlen; - struct avb_packet_aecp_header *resp; - - rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header)); - - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(resp), - AVB_AECP_STATUS_NOT_IMPLEMENTED); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: AEM command not in the legacy command table. - * Should return NOT_IMPLEMENTED. - */ -PWTEST(avb_aecp_aem_not_implemented) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x44 }; - uint64_t controller_id = 0x020000fffe000044ULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* REBOOT command is not in the legacy table */ - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_REBOOT, - NULL, 0); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - int rlen; - struct avb_packet_aecp_aem *resp; - - rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header)); - - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: AECP ACQUIRE_ENTITY (legacy) with valid entity descriptor. - * Tests the fix for the pointer offset bug in handle_acquire_entity_avb_legacy. - */ -PWTEST(avb_aecp_acquire_entity_legacy) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x45 }; - uint64_t controller_id = 0x020000fffe000045ULL; - struct avb_packet_aecp_aem_acquire acq; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Acquire the entity descriptor */ - memset(&acq, 0, sizeof(acq)); - acq.flags = 0; - acq.owner_guid = htobe64(controller_id); - acq.descriptor_type = htons(AVB_AEM_DESC_ENTITY); - acq.descriptor_id = htons(0); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, - &acq, sizeof(acq)); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - int rlen; - struct avb_packet_aecp_aem *resp; - - rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header)); - - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp), - AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_SUCCESS); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: AECP LOCK_ENTITY (legacy) with valid entity descriptor. - * Tests the fix for the pointer offset bug in handle_lock_entity_avb_legacy. - */ -PWTEST(avb_aecp_lock_entity_legacy) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x46 }; - uint64_t controller_id = 0x020000fffe000046ULL; - struct avb_packet_aecp_aem_acquire lock_pkt; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Lock the entity descriptor (lock uses same struct as acquire) */ - memset(&lock_pkt, 0, sizeof(lock_pkt)); - lock_pkt.flags = 0; - lock_pkt.owner_guid = htobe64(controller_id); - lock_pkt.descriptor_type = htons(AVB_AEM_DESC_ENTITY); - lock_pkt.descriptor_id = htons(0); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_LOCK_ENTITY, - &lock_pkt, sizeof(lock_pkt)); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - int rlen; - struct avb_packet_aecp_aem *resp; - - rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header)); - - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp), - AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_SUCCESS); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: Milan ENTITY_AVAILABLE command. - * Verifies the entity available handler returns lock status. - */ -PWTEST(avb_aecp_entity_available_milan) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x47 }; - uint64_t controller_id = 0x020000fffe000047ULL; - - impl = test_impl_new(); - server = avb_test_server_new_milan(impl); - pwtest_ptr_notnull(server); - - /* ENTITY_AVAILABLE has no payload */ - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_ENTITY_AVAILABLE, - NULL, 0); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - int rlen; - struct avb_packet_aecp_aem *resp; - - rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header)); - - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp), - AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_SUCCESS); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: Milan LOCK_ENTITY — lock, verify locked, unlock. - * Tests lock semantics and the reply_status pointer fix. - */ -PWTEST(avb_aecp_lock_entity_milan) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x48 }; - uint64_t controller_id = 0x020000fffe000048ULL; - uint64_t other_controller = 0x020000fffe000049ULL; - struct avb_packet_aecp_aem_lock lock_payload; - - impl = test_impl_new(); - server = avb_test_server_new_milan(impl); - pwtest_ptr_notnull(server); - - /* Lock the entity */ - memset(&lock_payload, 0, sizeof(lock_payload)); - lock_payload.descriptor_type = htons(AVB_AEM_DESC_ENTITY); - lock_payload.descriptor_id = htons(0); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_LOCK_ENTITY, - &lock_payload, sizeof(lock_payload)); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should get SUCCESS for the lock */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - struct avb_packet_aecp_aem *resp; - - avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_SUCCESS); - } - - /* Another controller tries to lock — should get ENTITY_LOCKED */ - avb_loopback_clear_packets(server); - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, other_controller, 2, - AVB_AECP_AEM_CMD_LOCK_ENTITY, - &lock_payload, sizeof(lock_payload)); - avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len); - - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - struct avb_packet_aecp_aem *resp; - - avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_ENTITY_LOCKED); - } - - /* Original controller unlocks */ - avb_loopback_clear_packets(server); - lock_payload.flags = htonl(AECP_AEM_LOCK_ENTITY_FLAG_UNLOCK); - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 3, - AVB_AECP_AEM_CMD_LOCK_ENTITY, - &lock_payload, sizeof(lock_payload)); - avb_test_inject_packet(server, 3 * SPA_NSEC_PER_SEC, pkt, len); - - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - struct avb_packet_aecp_aem *resp; - - avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_SUCCESS); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: Milan LOCK_ENTITY for non-entity descriptor returns NOT_SUPPORTED. - */ -PWTEST(avb_aecp_lock_non_entity_milan) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x4A }; - uint64_t controller_id = 0x020000fffe00004AULL; - struct avb_packet_aecp_aem_lock lock_payload; - - impl = test_impl_new(); - server = avb_test_server_new_milan(impl); - pwtest_ptr_notnull(server); - - /* Try to lock AUDIO_UNIT descriptor (not entity) */ - memset(&lock_payload, 0, sizeof(lock_payload)); - lock_payload.descriptor_type = htons(AVB_AEM_DESC_AUDIO_UNIT); - lock_payload.descriptor_id = htons(0); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_LOCK_ENTITY, - &lock_payload, sizeof(lock_payload)); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should get NO_SUCH_DESCRIPTOR (audio_unit doesn't exist) */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - struct avb_packet_aecp_aem *resp; - - avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - /* Bug fix verified: reply_status now gets the full frame pointer, - * so the response is correctly formed */ - pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp), - AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: Milan ACQUIRE_ENTITY returns NOT_SUPPORTED. - * Milan v1.2 does not implement acquire. - */ -PWTEST(avb_aecp_acquire_entity_milan) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x4B }; - uint64_t controller_id = 0x020000fffe00004BULL; - struct avb_packet_aecp_aem_acquire acq; - - impl = test_impl_new(); - server = avb_test_server_new_milan(impl); - pwtest_ptr_notnull(server); - - memset(&acq, 0, sizeof(acq)); - acq.descriptor_type = htons(AVB_AEM_DESC_ENTITY); - acq.descriptor_id = htons(0); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, - &acq, sizeof(acq)); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - struct avb_packet_aecp_aem *resp; - - avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_NOT_SUPPORTED); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: Milan READ_DESCRIPTOR works the same as legacy. - */ -PWTEST(avb_aecp_read_descriptor_milan) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x4C }; - uint64_t controller_id = 0x020000fffe00004CULL; - struct avb_packet_aecp_aem_read_descriptor rd; - - impl = test_impl_new(); - server = avb_test_server_new_milan(impl); - pwtest_ptr_notnull(server); - - memset(&rd, 0, sizeof(rd)); - rd.descriptor_type = htons(AVB_AEM_DESC_ENTITY); - rd.descriptor_id = htons(0); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_READ_DESCRIPTOR, - &rd, sizeof(rd)); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - struct avb_packet_aecp_aem *resp; - - avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_SUCCESS); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * ===================================================================== - * Phase 7: Additional Protocol Coverage Tests - * ===================================================================== - */ - -/* - * Test: MAAP conflict detection — verify the 4 overlap cases in - * maap_check_conflict(). MAAP uses the pool 91:e0:f0:00:xx:xx, - * so only the last 2 bytes (offset) matter for overlap checks. - * - * We can't call maap_check_conflict() directly (it's static), but - * we can test the MAAP state machine via packet injection. - * When a PROBE packet is received that conflicts with our reservation - * in ANNOUNCE state, the server should send a DEFEND packet. - * When in PROBE state, a conflict causes re-probing (new address). - */ -PWTEST(avb_maap_conflict_probe_in_announce) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - struct avb_ethernet_header *h; - struct avb_packet_maap *p; - size_t len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x50 }; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* avb_maap_reserve(server->maap, 1) was called in avb_test_server_new - * via the test-avb-utils.h helper (through init of domain_attr etc). - * The MAAP starts in STATE_PROBE. We need to advance it to STATE_ANNOUNCE - * by ticking through the probe retransmits (3 probes). */ - - /* Tick through probe retransmits — probe_count starts at 3, - * each tick past timeout sends a probe and decrements. - * PROBE_INTERVAL_MS = 500, so tick at 600ms intervals. - * After 3 probes, state transitions to ANNOUNCE. */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - avb_test_tick(server, 2 * SPA_NSEC_PER_SEC); - avb_test_tick(server, 3 * SPA_NSEC_PER_SEC); - avb_test_tick(server, 4 * SPA_NSEC_PER_SEC); - - avb_loopback_clear_packets(server); - - /* Build a MAAP PROBE packet that overlaps with our reserved range. - * We use the base pool address with our server's MAAP offset. - * Since we can't read the internal offset, we use the full pool - * range to guarantee overlap. */ - memset(pkt, 0, sizeof(pkt)); - h = (struct avb_ethernet_header *)pkt; - p = SPA_PTROFF(h, sizeof(*h), void); - len = sizeof(*h) + sizeof(*p); - - memcpy(h->dest, (uint8_t[]){ 0x91, 0xe0, 0xf0, 0x00, 0xff, 0x00 }, 6); - memcpy(h->src, remote_mac, 6); - h->type = htons(AVB_TSN_ETH); - - p->hdr.subtype = AVB_SUBTYPE_MAAP; - AVB_PACKET_SET_LENGTH(&p->hdr, sizeof(*p)); - AVB_PACKET_MAAP_SET_MAAP_VERSION(p, 1); - AVB_PACKET_MAAP_SET_MESSAGE_TYPE(p, AVB_MAAP_MESSAGE_TYPE_PROBE); - - /* Request the entire pool — guaranteed to overlap with any reservation */ - AVB_PACKET_MAAP_SET_REQUEST_START(p, - ((uint8_t[]){ 0x91, 0xe0, 0xf0, 0x00, 0x00, 0x00 })); - AVB_PACKET_MAAP_SET_REQUEST_COUNT(p, 0xFE00); - - /* Inject — in ANNOUNCE state, a conflicting PROBE should trigger DEFEND */ - avb_test_inject_packet(server, 5 * SPA_NSEC_PER_SEC, pkt, len); - - /* The server should NOT crash. If it was in ANNOUNCE state, - * it sends a DEFEND. If still in PROBE, it picks a new address. - * Either way, the conflict detection logic was exercised. */ - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: MAAP DEFEND packet causes re-probing when conflict overlaps - * with our address during PROBE state. - */ -PWTEST(avb_maap_defend_causes_reprobe) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - struct avb_ethernet_header *h; - struct avb_packet_maap *p; - size_t len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x51 }; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* MAAP is in PROBE state after reserve. Send a DEFEND packet - * with conflict range covering the entire pool. */ - memset(pkt, 0, sizeof(pkt)); - h = (struct avb_ethernet_header *)pkt; - p = SPA_PTROFF(h, sizeof(*h), void); - len = sizeof(*h) + sizeof(*p); - - memcpy(h->dest, (uint8_t[]){ 0x91, 0xe0, 0xf0, 0x00, 0xff, 0x00 }, 6); - memcpy(h->src, remote_mac, 6); - h->type = htons(AVB_TSN_ETH); - - p->hdr.subtype = AVB_SUBTYPE_MAAP; - AVB_PACKET_SET_LENGTH(&p->hdr, sizeof(*p)); - AVB_PACKET_MAAP_SET_MAAP_VERSION(p, 1); - AVB_PACKET_MAAP_SET_MESSAGE_TYPE(p, AVB_MAAP_MESSAGE_TYPE_DEFEND); - - /* Set conflict range to cover the whole pool */ - AVB_PACKET_MAAP_SET_CONFLICT_START(p, - ((uint8_t[]){ 0x91, 0xe0, 0xf0, 0x00, 0x00, 0x00 })); - AVB_PACKET_MAAP_SET_CONFLICT_COUNT(p, 0xFE00); - - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should have re-probed — exercise the state machine without crash */ - avb_test_tick(server, 2 * SPA_NSEC_PER_SEC); - avb_test_tick(server, 3 * SPA_NSEC_PER_SEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: MAAP ANNOUNCE packet with conflict triggers re-probe. - * ANNOUNCE is handled via handle_defend() in the code. - */ -PWTEST(avb_maap_announce_conflict) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - struct avb_ethernet_header *h; - struct avb_packet_maap *p; - size_t len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x52 }; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - memset(pkt, 0, sizeof(pkt)); - h = (struct avb_ethernet_header *)pkt; - p = SPA_PTROFF(h, sizeof(*h), void); - len = sizeof(*h) + sizeof(*p); - - memcpy(h->dest, (uint8_t[]){ 0x91, 0xe0, 0xf0, 0x00, 0xff, 0x00 }, 6); - memcpy(h->src, remote_mac, 6); - h->type = htons(AVB_TSN_ETH); - - p->hdr.subtype = AVB_SUBTYPE_MAAP; - AVB_PACKET_SET_LENGTH(&p->hdr, sizeof(*p)); - AVB_PACKET_MAAP_SET_MAAP_VERSION(p, 1); - AVB_PACKET_MAAP_SET_MESSAGE_TYPE(p, AVB_MAAP_MESSAGE_TYPE_ANNOUNCE); - - /* Conflict range covers entire pool */ - AVB_PACKET_MAAP_SET_CONFLICT_START(p, - ((uint8_t[]){ 0x91, 0xe0, 0xf0, 0x00, 0x00, 0x00 })); - AVB_PACKET_MAAP_SET_CONFLICT_COUNT(p, 0xFE00); - - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - avb_test_tick(server, 2 * SPA_NSEC_PER_SEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: MAAP no-conflict — PROBE packet with non-overlapping range. - */ -PWTEST(avb_maap_no_conflict) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - struct avb_ethernet_header *h; - struct avb_packet_maap *p; - size_t len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x53 }; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - memset(pkt, 0, sizeof(pkt)); - h = (struct avb_ethernet_header *)pkt; - p = SPA_PTROFF(h, sizeof(*h), void); - len = sizeof(*h) + sizeof(*p); - - memcpy(h->dest, (uint8_t[]){ 0x91, 0xe0, 0xf0, 0x00, 0xff, 0x00 }, 6); - memcpy(h->src, remote_mac, 6); - h->type = htons(AVB_TSN_ETH); - - p->hdr.subtype = AVB_SUBTYPE_MAAP; - AVB_PACKET_SET_LENGTH(&p->hdr, sizeof(*p)); - AVB_PACKET_MAAP_SET_MAAP_VERSION(p, 1); - AVB_PACKET_MAAP_SET_MESSAGE_TYPE(p, AVB_MAAP_MESSAGE_TYPE_PROBE); - - /* Use a different base prefix — won't match (memcmp of first 4 bytes fails) */ - AVB_PACKET_MAAP_SET_REQUEST_START(p, - ((uint8_t[]){ 0x91, 0xe0, 0xf1, 0x00, 0x00, 0x00 })); - AVB_PACKET_MAAP_SET_REQUEST_COUNT(p, 1); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* No conflict — no DEFEND should be sent (even if in ANNOUNCE state) */ - /* We can't check packet count reliably since MAAP uses send() on its - * own fd, not the loopback transport. But the path was exercised. */ - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: ACMP DISCONNECT_RX_COMMAND flow. - * Send DISCONNECT_RX_COMMAND to our server as listener. - * Should forward DISCONNECT_TX_COMMAND to the talker. - */ -PWTEST(avb_acmp_disconnect_rx_forward) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x60 }; - uint64_t controller_entity = 0x020000fffe000060ULL; - uint64_t talker_entity = 0x020000fffe000070ULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - avb_loopback_clear_packets(server); - - /* Send DISCONNECT_RX_COMMAND to us as listener */ - len = avb_test_build_acmp(pkt, sizeof(pkt), controller_mac, - AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_COMMAND, - controller_entity, - talker_entity, /* talker = remote */ - server->entity_id, /* listener = us */ - 0, 0, 300); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should forward a DISCONNECT_TX_COMMAND to the talker */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[256]; - int rlen; - struct avb_packet_acmp *cmd; - - rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - pwtest_int_gt(rlen, (int)sizeof(struct avb_ethernet_header)); - - cmd = (struct avb_packet_acmp *)(rbuf + sizeof(struct avb_ethernet_header)); - pwtest_int_eq((int)AVB_PACKET_ACMP_GET_MESSAGE_TYPE(cmd), - AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_COMMAND); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: ACMP DISCONNECT_TX_COMMAND to our server as talker with no streams. - * Should respond with TALKER_NO_STREAM_INDEX. - */ -PWTEST(avb_acmp_disconnect_tx_no_stream) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x61 }; - uint64_t remote_entity_id = 0x020000fffe000061ULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - avb_loopback_clear_packets(server); - - /* Send DISCONNECT_TX_COMMAND — we have no streams */ - len = avb_test_build_acmp(pkt, sizeof(pkt), remote_mac, - AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_COMMAND, - remote_entity_id, /* controller */ - server->entity_id, /* talker = us */ - remote_entity_id, /* listener */ - 0, 0, 1); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should respond with DISCONNECT_TX_RESPONSE + TALKER_NO_STREAM_INDEX */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[256]; - struct avb_packet_acmp *resp; - - avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - resp = (struct avb_packet_acmp *)(rbuf + sizeof(struct avb_ethernet_header)); - pwtest_int_eq((int)AVB_PACKET_ACMP_GET_MESSAGE_TYPE(resp), - AVB_ACMP_MESSAGE_TYPE_DISCONNECT_TX_RESPONSE); - pwtest_int_eq((int)AVB_PACKET_ACMP_GET_STATUS(resp), - AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: ACMP disconnect pending timeout. - * DISCONNECT_TX_COMMAND timeout is 200ms, much shorter than CONNECT_TX (2000ms). - * After DISCONNECT_RX_COMMAND, the pending should timeout faster. - */ -PWTEST(avb_acmp_disconnect_pending_timeout) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x62 }; - uint64_t controller_entity = 0x020000fffe000062ULL; - uint64_t talker_entity = 0x020000fffe000072ULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - avb_loopback_clear_packets(server); - - /* Create pending via DISCONNECT_RX_COMMAND */ - len = avb_test_build_acmp(pkt, sizeof(pkt), controller_mac, - AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_COMMAND, - controller_entity, - talker_entity, - server->entity_id, - 0, 0, 400); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - avb_loopback_clear_packets(server); - - /* Tick before timeout (200ms) — no retry yet */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 100 * SPA_NSEC_PER_MSEC); - pwtest_int_eq(avb_loopback_get_packet_count(server), 0); - - /* Tick after timeout (200ms) — should retry */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 300 * SPA_NSEC_PER_MSEC); - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - - avb_loopback_clear_packets(server); - - /* Tick again after second timeout — should be freed */ - avb_test_tick(server, 2 * SPA_NSEC_PER_SEC); - - /* No crash — pending was cleaned up */ - avb_test_tick(server, 3 * SPA_NSEC_PER_SEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: AECP GET_AVB_INFO command with AVB_INTERFACE descriptor. - * Adds an AVB_INTERFACE descriptor, injects GET_AVB_INFO, and - * verifies the response contains gptp_grandmaster_id and domain_number. - */ -PWTEST(avb_aecp_get_avb_info) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x70 }; - uint64_t controller_id = 0x020000fffe000070ULL; - struct avb_packet_aecp_aem_get_avb_info avb_info_req; - uint64_t test_clock_id = 0x0200000000000042ULL; - uint8_t test_domain = 7; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Add an AVB_INTERFACE descriptor to the server */ - { - struct avb_aem_desc_avb_interface avb_iface; - memset(&avb_iface, 0, sizeof(avb_iface)); - avb_iface.clock_identity = htobe64(test_clock_id); - avb_iface.domain_number = test_domain; - avb_iface.interface_flags = htons( - AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED); - server_add_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0, - sizeof(avb_iface), &avb_iface); - } - - /* Build GET_AVB_INFO command */ - memset(&avb_info_req, 0, sizeof(avb_info_req)); - avb_info_req.descriptor_type = htons(AVB_AEM_DESC_AVB_INTERFACE); - avb_info_req.descriptor_id = htons(0); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_GET_AVB_INFO, - &avb_info_req, sizeof(avb_info_req)); - pwtest_int_gt(len, 0); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should get a SUCCESS response */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - int rlen; - struct avb_packet_aecp_aem *resp; - struct avb_packet_aecp_aem_get_avb_info *info; - - rlen = avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - pwtest_int_gt(rlen, (int)(sizeof(struct avb_ethernet_header) + - sizeof(struct avb_packet_aecp_aem))); - - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp), - AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_SUCCESS); - - /* Verify the response payload */ - info = (struct avb_packet_aecp_aem_get_avb_info *)resp->payload; - pwtest_int_eq(be64toh(info->gptp_grandmaster_id), test_clock_id); - pwtest_int_eq(info->gptp_domain_number, test_domain); - pwtest_int_eq(ntohl(info->propagation_delay), 0u); - pwtest_int_eq(ntohs(info->msrp_mappings_count), 0); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: AECP GET_AVB_INFO with wrong descriptor type returns NOT_IMPLEMENTED. - * The handler requires AVB_AEM_DESC_AVB_INTERFACE specifically. - */ -PWTEST(avb_aecp_get_avb_info_wrong_type) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x71 }; - uint64_t controller_id = 0x020000fffe000071ULL; - struct avb_packet_aecp_aem_get_avb_info avb_info_req; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Request GET_AVB_INFO for entity descriptor (wrong type) */ - memset(&avb_info_req, 0, sizeof(avb_info_req)); - avb_info_req.descriptor_type = htons(AVB_AEM_DESC_ENTITY); - avb_info_req.descriptor_id = htons(0); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_GET_AVB_INFO, - &avb_info_req, sizeof(avb_info_req)); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should get NOT_IMPLEMENTED (descriptor exists but is wrong type) */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - struct avb_packet_aecp_aem *resp; - - avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp), - AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: MRP leave-all timer fires and triggers global LVA event. - * After LVA_TIMER_MS (10000ms), the leave-all timer fires, sending - * RX_LVA to all attributes and setting leave_all=true for the next TX. - */ -PWTEST(avb_mrp_leave_all_timer) -{ - struct impl *impl; - struct server *server; - struct avb_msrp_attribute *attr; - struct spa_hook listener; - struct notify_tracker tracker = { 0 }; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - avb_mrp_add_listener(server->mrp, &listener, &test_mrp_events, &tracker); - - /* Create and join an attribute */ - attr = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE); - pwtest_ptr_notnull(attr); - - avb_mrp_attribute_begin(attr->mrp, 0); - avb_mrp_attribute_join(attr->mrp, 0, true); - - /* Get registrar to IN state */ - avb_mrp_attribute_rx_event(attr->mrp, 1 * SPA_NSEC_PER_SEC, - AVB_MRP_ATTRIBUTE_EVENT_NEW); - pwtest_int_eq(tracker.new_count, 1); - - /* Initialize timers with first tick */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - - /* Tick at various times before LVA timeout — no leave-all yet */ - avb_test_tick(server, 5 * SPA_NSEC_PER_SEC); - avb_test_tick(server, 9 * SPA_NSEC_PER_SEC); - - /* Tick past LVA timeout (10000ms from the first tick at 1s = 11s) */ - avb_test_tick(server, 12 * SPA_NSEC_PER_SEC); - - /* The LVA event should have been processed without crash. - * The TX_LVA event is combined with the join timer TX, - * which may produce SEND_LVA type transmissions. */ - - spa_hook_remove(&listener); - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: MRP periodic timer fires at 1000ms intervals. - * The periodic event is applied globally to all attributes. - */ -PWTEST(avb_mrp_periodic_timer) -{ - struct impl *impl; - struct server *server; - struct avb_msrp_attribute *attr; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - attr = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_TALKER_ADVERTISE); - avb_mrp_attribute_begin(attr->mrp, 0); - avb_mrp_attribute_join(attr->mrp, 0, true); - - /* First tick initializes timers */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - - /* Tick just before periodic timeout (1000ms) — no periodic event yet */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 500 * SPA_NSEC_PER_MSEC); - - /* Tick past periodic timeout */ - avb_test_tick(server, 2 * SPA_NSEC_PER_SEC + 100 * SPA_NSEC_PER_MSEC); - - /* Tick multiple periodic intervals to exercise repeated timer */ - avb_test_tick(server, 3 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC); - avb_test_tick(server, 4 * SPA_NSEC_PER_SEC + 300 * SPA_NSEC_PER_MSEC); - - /* No crash — periodic timer logic works correctly */ - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: MSRP talker-failed processing via MRP packet parsing. - * Build an MSRP packet containing a talker-failed attribute and - * inject it. Verifies process_talker_fail() processes correctly. - */ -PWTEST(avb_msrp_talker_failed_process) -{ - struct impl *impl; - struct server *server; - struct avb_msrp_attribute *talker_fail; - uint64_t stream_id = 0x020000fffe000080ULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Create a talker-failed attribute that matches the stream_id - * we'll send in the MSRP packet. This ensures process_talker_fail() - * finds a matching attribute and calls avb_mrp_attribute_rx_event(). */ - talker_fail = avb_msrp_attribute_new(server->msrp, - AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED); - pwtest_ptr_notnull(talker_fail); - - talker_fail->attr.talker_fail.talker.stream_id = htobe64(stream_id); - talker_fail->attr.talker_fail.failure_code = AVB_MRP_FAIL_BANDWIDTH; - - avb_mrp_attribute_begin(talker_fail->mrp, 0); - avb_mrp_attribute_join(talker_fail->mrp, 0, true); - - /* Build an MSRP packet with a talker-failed message. - * The MSRP packet parser will dispatch to process_talker_fail() - * when it sees attribute_type = TALKER_FAILED. */ - { - uint8_t buf[512]; - int pos = 0; - struct avb_packet_mrp *mrp_pkt; - struct avb_packet_msrp_msg *msg; - struct avb_packet_mrp_vector *v; - struct avb_packet_msrp_talker_fail *tf; - struct avb_packet_mrp_footer *f; - uint8_t *ev; - size_t attr_list_length; - - memset(buf, 0, sizeof(buf)); - - /* MRP header */ - mrp_pkt = (struct avb_packet_mrp *)buf; - mrp_pkt->version = AVB_MRP_PROTOCOL_VERSION; - /* Fill in the ethernet header part */ - { - static const uint8_t msrp_mac[6] = { 0x91, 0xe0, 0xf0, 0x00, 0xe5, 0x00 }; - memcpy(mrp_pkt->eth.dest, msrp_mac, 6); - memcpy(mrp_pkt->eth.src, server->mac_addr, 6); - mrp_pkt->eth.type = htons(AVB_TSN_ETH); - } - pos = sizeof(struct avb_packet_mrp); - - /* MSRP talker-failed message */ - msg = (struct avb_packet_msrp_msg *)(buf + pos); - msg->attribute_type = AVB_MSRP_ATTRIBUTE_TYPE_TALKER_FAILED; - msg->attribute_length = sizeof(struct avb_packet_msrp_talker_fail); - - v = (struct avb_packet_mrp_vector *)msg->attribute_list; - v->lva = 0; - AVB_MRP_VECTOR_SET_NUM_VALUES(v, 1); - - tf = (struct avb_packet_msrp_talker_fail *)v->first_value; - tf->talker.stream_id = htobe64(stream_id); - tf->talker.vlan_id = htons(AVB_DEFAULT_VLAN); - tf->talker.tspec_max_frame_size = htons(256); - tf->talker.tspec_max_interval_frames = htons(1); - tf->talker.priority = AVB_MSRP_PRIORITY_DEFAULT; - tf->talker.rank = AVB_MSRP_RANK_DEFAULT; - tf->failure_code = AVB_MRP_FAIL_BANDWIDTH; - - ev = (uint8_t *)(tf + 1); - *ev = AVB_MRP_ATTRIBUTE_EVENT_NEW * 36; /* single value, NEW event */ - - attr_list_length = sizeof(*v) + sizeof(*tf) + 1 + sizeof(*f); - msg->attribute_list_length = htons(attr_list_length); - - f = SPA_PTROFF(ev, 1, void); - f->end_mark = 0; - - pos += sizeof(*msg) + sizeof(*v) + sizeof(*tf) + 1 + sizeof(*f); - - /* Attribute end mark */ - buf[pos++] = 0; - buf[pos++] = 0; - - /* Inject the MSRP packet */ - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, buf, pos); - } - - /* If we get here, process_talker_fail() was invoked without crash. - * The attribute's RX_NEW event would have been applied. */ - - /* Exercise periodic to verify ongoing stability */ - avb_test_tick(server, 2 * SPA_NSEC_PER_SEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * ===================================================================== - * Phase 8: MVRP/MMRP, ADP Edge Cases, Descriptor, and AECP Command Tests - * ===================================================================== - */ - -/* - * Test: MVRP attribute creation and lifecycle. - * Create a VID attribute, begin, join, and exercise the state machine. - */ -PWTEST(avb_mvrp_attribute_lifecycle) -{ - struct impl *impl; - struct server *server; - struct avb_mvrp_attribute *vid; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - vid = avb_mvrp_attribute_new(server->mvrp, - AVB_MVRP_ATTRIBUTE_TYPE_VID); - pwtest_ptr_notnull(vid); - pwtest_int_eq(vid->type, AVB_MVRP_ATTRIBUTE_TYPE_VID); - - /* Configure VLAN ID */ - vid->attr.vid.vlan = htons(AVB_DEFAULT_VLAN); - - /* Begin and join */ - avb_mrp_attribute_begin(vid->mrp, 0); - avb_mrp_attribute_join(vid->mrp, 0, true); - - /* Tick through MRP state machine */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC); - avb_test_tick(server, 2 * SPA_NSEC_PER_SEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: MVRP VID attribute transmit via loopback. - * After join + TX timer, MVRP should encode and send a VID packet. - */ -PWTEST(avb_mvrp_vid_transmit) -{ - struct impl *impl; - struct server *server; - struct avb_mvrp_attribute *vid; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - vid = avb_mvrp_attribute_new(server->mvrp, - AVB_MVRP_ATTRIBUTE_TYPE_VID); - pwtest_ptr_notnull(vid); - - vid->attr.vid.vlan = htons(100); - - avb_mrp_attribute_begin(vid->mrp, 0); - avb_mrp_attribute_join(vid->mrp, 0, true); - - /* Initialize timers */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - avb_loopback_clear_packets(server); - - /* Trigger TX */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC); - - /* MVRP should have sent a packet */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: MMRP attribute creation — both SERVICE_REQUIREMENT and MAC types. - */ -PWTEST(avb_mmrp_attribute_types) -{ - struct impl *impl; - struct server *server; - struct avb_mmrp_attribute *svc, *mac_attr; - static const uint8_t test_mac[6] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Create service requirement attribute */ - svc = avb_mmrp_attribute_new(server->mmrp, - AVB_MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT); - pwtest_ptr_notnull(svc); - pwtest_int_eq(svc->type, AVB_MMRP_ATTRIBUTE_TYPE_SERVICE_REQUIREMENT); - - memcpy(svc->attr.service_requirement.addr, test_mac, 6); - - /* Create MAC attribute */ - mac_attr = avb_mmrp_attribute_new(server->mmrp, - AVB_MMRP_ATTRIBUTE_TYPE_MAC); - pwtest_ptr_notnull(mac_attr); - pwtest_int_eq(mac_attr->type, AVB_MMRP_ATTRIBUTE_TYPE_MAC); - - memcpy(mac_attr->attr.mac.addr, test_mac, 6); - - /* Begin and join both */ - avb_mrp_attribute_begin(svc->mrp, 0); - avb_mrp_attribute_join(svc->mrp, 0, true); - avb_mrp_attribute_begin(mac_attr->mrp, 0); - avb_mrp_attribute_join(mac_attr->mrp, 0, true); - - /* Tick to exercise MRP state machine with both types */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC + 200 * SPA_NSEC_PER_MSEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: ADP duplicate ENTITY_AVAILABLE is idempotent. - * Injecting the same entity_id twice should not create duplicate entries; - * last_time is updated. - */ -PWTEST(avb_adp_duplicate_entity_available) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x80 }; - uint64_t remote_entity_id = 0x020000fffe000080ULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Inject entity available twice with same entity_id */ - len = avb_test_build_adp_entity_available(pkt, sizeof(pkt), - remote_mac, remote_entity_id, 10); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should not crash, and entity list should be consistent */ - avb_test_tick(server, 3 * SPA_NSEC_PER_SEC); - - /* Inject departing — only one entity to remove */ - len = avb_test_build_adp_entity_departing(pkt, sizeof(pkt), - remote_mac, remote_entity_id); - avb_test_inject_packet(server, 4 * SPA_NSEC_PER_SEC, pkt, len); - - avb_test_tick(server, 5 * SPA_NSEC_PER_SEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: ADP targeted discover for a specific entity_id. - * Only the entity with matching ID should respond. - */ -PWTEST(avb_adp_targeted_discover) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x81 }; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Let the server advertise first */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - avb_loopback_clear_packets(server); - - /* Send targeted discover for our own entity */ - len = avb_test_build_adp_entity_discover(pkt, sizeof(pkt), - remote_mac, server->entity_id); - pwtest_int_gt(len, 0); - avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should respond since the entity_id matches ours */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - - avb_loopback_clear_packets(server); - - /* Send targeted discover for a non-existent entity */ - len = avb_test_build_adp_entity_discover(pkt, sizeof(pkt), - remote_mac, 0xDEADBEEFCAFE0001ULL); - avb_test_inject_packet(server, 3 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should NOT respond — entity doesn't exist */ - pwtest_int_eq(avb_loopback_get_packet_count(server), 0); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: ADP re-advertisement timing — the server should re-advertise - * at valid_time/2 intervals when ticked periodically. - */ -PWTEST(avb_adp_readvertise_timing) -{ - struct impl *impl; - struct server *server; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* First tick — should advertise (check_advertise creates entity) */ - avb_test_tick(server, 1 * SPA_NSEC_PER_SEC); - avb_loopback_clear_packets(server); - - /* Tick at 3s — too early for re-advertise (valid_time=10, re-adv at 5s) */ - avb_test_tick(server, 3 * SPA_NSEC_PER_SEC); - /* Might or might not have packets depending on other protocols */ - - avb_loopback_clear_packets(server); - - /* Tick at 7s — past re-advertise interval (valid_time/2 = 5s from 1s = 6s) */ - avb_test_tick(server, 7 * SPA_NSEC_PER_SEC); - - /* Should have re-advertised */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: ADP entity departure before timeout removes entity immediately. - */ -PWTEST(avb_adp_departure_before_timeout) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[256]; - int len; - static const uint8_t remote_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x82 }; - uint64_t remote_entity_id = 0x020000fffe000082ULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Add entity */ - len = avb_test_build_adp_entity_available(pkt, sizeof(pkt), - remote_mac, remote_entity_id, 30); /* long valid_time */ - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Immediate departing — before any timeout */ - len = avb_test_build_adp_entity_departing(pkt, sizeof(pkt), - remote_mac, remote_entity_id); - avb_test_inject_packet(server, 2 * SPA_NSEC_PER_SEC, pkt, len); - - /* Entity should be removed immediately, not waiting for timeout */ - avb_test_tick(server, 3 * SPA_NSEC_PER_SEC); - - /* Re-add the same entity — should work if old one was properly removed */ - len = avb_test_build_adp_entity_available(pkt, sizeof(pkt), - remote_mac, remote_entity_id, 10); - avb_test_inject_packet(server, 4 * SPA_NSEC_PER_SEC, pkt, len); - - avb_test_tick(server, 5 * SPA_NSEC_PER_SEC); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: Descriptor lookup edge cases — find existing, missing, multiple types. - */ -PWTEST(avb_descriptor_lookup_edge_cases) -{ - struct impl *impl; - struct server *server; - const struct descriptor *desc; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Entity descriptor should exist (added by avb_test_server_new) */ - desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0); - pwtest_ptr_notnull(desc); - pwtest_int_eq(desc->type, AVB_AEM_DESC_ENTITY); - pwtest_int_eq(desc->index, 0); - pwtest_int_gt((int)desc->size, 0); - - /* Non-existent descriptor type */ - desc = server_find_descriptor(server, AVB_AEM_DESC_AUDIO_UNIT, 0); - pwtest_ptr_null(desc); - - /* Non-existent index for existing type */ - desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 1); - pwtest_ptr_null(desc); - - /* Add multiple descriptors and verify independent lookup */ - { - struct avb_aem_desc_avb_interface avb_iface; - memset(&avb_iface, 0, sizeof(avb_iface)); - server_add_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0, - sizeof(avb_iface), &avb_iface); - } - - /* Both descriptors should be findable */ - desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0); - pwtest_ptr_notnull(desc); - desc = server_find_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0); - pwtest_ptr_notnull(desc); - - /* Invalid descriptor type still returns NULL */ - desc = server_find_descriptor(server, AVB_AEM_DESC_INVALID, 0); - pwtest_ptr_null(desc); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: server_add_descriptor with data copy — verify data is correctly - * stored and retrievable. - */ -PWTEST(avb_descriptor_data_integrity) -{ - struct impl *impl; - struct server *server; - const struct descriptor *desc; - struct avb_aem_desc_entity entity; - struct avb_aem_desc_entity *retrieved; - uint64_t test_entity_id = 0x0123456789ABCDEFULL; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Remove existing entity descriptor via server_destroy_descriptors - * and add a new one with known data */ - server_destroy_descriptors(server); - - memset(&entity, 0, sizeof(entity)); - entity.entity_id = htobe64(test_entity_id); - entity.entity_model_id = htobe64(0x0001000000000002ULL); - entity.configurations_count = htons(2); - strncpy(entity.entity_name, "Test Entity", sizeof(entity.entity_name) - 1); - - server_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0, - sizeof(entity), &entity); - - /* Retrieve and verify */ - desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0); - pwtest_ptr_notnull(desc); - pwtest_int_eq((int)desc->size, (int)sizeof(entity)); - - retrieved = desc->ptr; - pwtest_int_eq(be64toh(retrieved->entity_id), test_entity_id); - pwtest_int_eq(ntohs(retrieved->configurations_count), 2); - pwtest_int_eq(strncmp(retrieved->entity_name, "Test Entity", 11), 0); - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: AECP GET_CONFIGURATION command. - * Verify it returns the current_configuration from the entity descriptor. - */ -PWTEST(avb_aecp_get_configuration) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x90 }; - uint64_t controller_id = 0x020000fffe000090ULL; - struct avb_packet_aecp_aem_setget_configuration cfg_req; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Build GET_CONFIGURATION command — no descriptor type/id needed, - * it always looks up ENTITY descriptor 0 internally */ - memset(&cfg_req, 0, sizeof(cfg_req)); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_GET_CONFIGURATION, - &cfg_req, sizeof(cfg_req)); - pwtest_int_gt(len, 0); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should get SUCCESS response */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - struct avb_packet_aecp_aem *resp; - struct avb_packet_aecp_aem_setget_configuration *cfg_resp; - - avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_SUCCESS); - - /* Verify configuration_index is 0 (default) */ - cfg_resp = (struct avb_packet_aecp_aem_setget_configuration *)resp->payload; - pwtest_int_eq(ntohs(cfg_resp->configuration_index), 0); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: AECP GET_SAMPLING_RATE command. - * Add an AUDIO_UNIT descriptor with a known sampling rate and verify - * the response contains it. - */ -PWTEST(avb_aecp_get_sampling_rate) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x91 }; - uint64_t controller_id = 0x020000fffe000091ULL; - struct avb_packet_aecp_aem_setget_sampling_rate sr_req; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Add an AUDIO_UNIT descriptor with a sampling rate */ - { - /* Allocate space for audio_unit + 1 sampling rate entry */ - uint8_t au_buf[sizeof(struct avb_aem_desc_audio_unit) + - sizeof(union avb_aem_desc_sampling_rate)]; - struct avb_aem_desc_audio_unit *au; - union avb_aem_desc_sampling_rate *sr; - - memset(au_buf, 0, sizeof(au_buf)); - au = (struct avb_aem_desc_audio_unit *)au_buf; - au->sampling_rates_count = htons(1); - au->sampling_rates_offset = htons(sizeof(*au)); - - /* Set current sampling rate to 48000 Hz - * pull_frequency is a uint32_t with frequency in bits [31:3] and pull in [2:0] */ - au->current_sampling_rate.pull_frequency = htonl(48000 << 3); - - /* Add one supported rate */ - sr = (union avb_aem_desc_sampling_rate *)(au_buf + sizeof(*au)); - sr->pull_frequency = htonl(48000 << 3); - - server_add_descriptor(server, AVB_AEM_DESC_AUDIO_UNIT, 0, - sizeof(au_buf), au_buf); - } - - /* Build GET_SAMPLING_RATE command */ - memset(&sr_req, 0, sizeof(sr_req)); - sr_req.descriptor_type = htons(AVB_AEM_DESC_AUDIO_UNIT); - sr_req.descriptor_id = htons(0); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_GET_SAMPLING_RATE, - &sr_req, sizeof(sr_req)); - pwtest_int_gt(len, 0); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should get SUCCESS */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - struct avb_packet_aecp_aem *resp; - - avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_SUCCESS); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: AECP GET_SAMPLING_RATE with wrong descriptor type. - * Should return NOT_IMPLEMENTED. - */ -PWTEST(avb_aecp_get_sampling_rate_wrong_type) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x92 }; - uint64_t controller_id = 0x020000fffe000092ULL; - struct avb_packet_aecp_aem_setget_sampling_rate sr_req; - - impl = test_impl_new(); - server = avb_test_server_new(impl); - pwtest_ptr_notnull(server); - - /* Request GET_SAMPLING_RATE for entity descriptor (wrong type) */ - memset(&sr_req, 0, sizeof(sr_req)); - sr_req.descriptor_type = htons(AVB_AEM_DESC_ENTITY); - sr_req.descriptor_id = htons(0); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_GET_SAMPLING_RATE, - &sr_req, sizeof(sr_req)); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - struct avb_packet_aecp_aem *resp; - - avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: AECP GET_NAME for entity descriptor — retrieves entity_name. - */ -PWTEST(avb_aecp_get_name_entity) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x93 }; - uint64_t controller_id = 0x020000fffe000093ULL; - struct avb_packet_aecp_aem_setget_name name_req; - - impl = test_impl_new(); - server = avb_test_server_new_milan(impl); - pwtest_ptr_notnull(server); - - /* GET_NAME for entity descriptor, name_index=0 (entity_name) */ - memset(&name_req, 0, sizeof(name_req)); - name_req.descriptor_type = htons(AVB_AEM_DESC_ENTITY); - name_req.descriptor_index = htons(0); - name_req.name_index = htons(0); - name_req.configuration_index = htons(0); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_GET_NAME, - &name_req, sizeof(name_req)); - pwtest_int_gt(len, 0); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - /* Should get SUCCESS */ - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - struct avb_packet_aecp_aem *resp; - - avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_MESSAGE_TYPE(&resp->aecp), - AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_SUCCESS); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * Test: AECP GET_NAME with missing descriptor returns NO_SUCH_DESCRIPTOR. - */ -PWTEST(avb_aecp_get_name_missing_descriptor) -{ - struct impl *impl; - struct server *server; - uint8_t pkt[512]; - int len; - static const uint8_t controller_mac[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x94 }; - uint64_t controller_id = 0x020000fffe000094ULL; - struct avb_packet_aecp_aem_setget_name name_req; - - impl = test_impl_new(); - server = avb_test_server_new_milan(impl); - pwtest_ptr_notnull(server); - - /* GET_NAME for AUDIO_UNIT which doesn't exist in test server */ - memset(&name_req, 0, sizeof(name_req)); - name_req.descriptor_type = htons(AVB_AEM_DESC_AUDIO_UNIT); - name_req.descriptor_index = htons(0); - name_req.name_index = htons(0); - - len = avb_test_build_aecp_aem(pkt, sizeof(pkt), controller_mac, - server->entity_id, controller_id, 1, - AVB_AECP_AEM_CMD_GET_NAME, - &name_req, sizeof(name_req)); - - avb_loopback_clear_packets(server); - avb_test_inject_packet(server, 1 * SPA_NSEC_PER_SEC, pkt, len); - - pwtest_int_gt(avb_loopback_get_packet_count(server), 0); - { - uint8_t rbuf[2048]; - struct avb_packet_aecp_aem *resp; - - avb_loopback_get_packet(server, rbuf, sizeof(rbuf)); - resp = SPA_PTROFF(rbuf, sizeof(struct avb_ethernet_header), void); - pwtest_int_eq((int)AVB_PACKET_AECP_GET_STATUS(&resp->aecp), - AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR); - } - - test_impl_free(impl); - - return PWTEST_PASS; -} - -/* - * ===================================================================== - * Phase 6: AVTP Audio Data Path Tests - * ===================================================================== - */ - -/* - * Test: Verify IEC61883 packet struct layout and size. - * The struct must be exactly 24 bytes (packed) for the header, - * followed by the flexible payload array. - */ -PWTEST(avb_iec61883_packet_layout) -{ - struct avb_packet_iec61883 pkt; - struct avb_frame_header fh; - - /* IEC61883 header (packed) with CIP fields = 32 bytes */ - pwtest_int_eq((int)sizeof(struct avb_packet_iec61883), 32); - - /* Frame header with 802.1Q tag should be 18 bytes */ - pwtest_int_eq((int)sizeof(struct avb_frame_header), 18); - - /* Total PDU header = frame_header + iec61883 = 50 bytes */ - pwtest_int_eq((int)(sizeof(fh) + sizeof(pkt)), 50); - - /* Verify critical field positions by setting and reading */ - memset(&pkt, 0, sizeof(pkt)); - pkt.subtype = AVB_SUBTYPE_61883_IIDC; - pwtest_int_eq(pkt.subtype, 0x00); - - pkt.sv = 1; - pkt.tv = 1; - pkt.seq_num = 42; - pkt.stream_id = htobe64(0x020000fffe000001ULL); - pkt.timestamp = htonl(1000000); - pkt.data_len = htons(200); - pkt.tag = 0x1; - pkt.channel = 0x1f; - pkt.tcode = 0xa; - pkt.sid = 0x3f; - pkt.dbs = 8; - pkt.qi2 = 0x2; - pkt.format_id = 0x10; - pkt.fdf = 0x2; - pkt.syt = htons(0x0008); - pkt.dbc = 0; - - /* Read back and verify */ - pwtest_int_eq(pkt.seq_num, 42); - pwtest_int_eq(pkt.dbs, 8); - pwtest_int_eq(be64toh(pkt.stream_id), 0x020000fffe000001ULL); - pwtest_int_eq(ntohs(pkt.data_len), 200); - pwtest_int_eq((int)pkt.sv, 1); - pwtest_int_eq((int)pkt.tv, 1); - - return PWTEST_PASS; -} - -/* - * Test: Verify AAF packet struct layout. - */ -PWTEST(avb_aaf_packet_layout) -{ - struct avb_packet_aaf pkt; - - /* AAF header should be 24 bytes (same as IEC61883) */ - pwtest_int_eq((int)sizeof(struct avb_packet_aaf), 24); - - memset(&pkt, 0, sizeof(pkt)); - pkt.subtype = AVB_SUBTYPE_AAF; - pkt.sv = 1; - pkt.tv = 1; - pkt.seq_num = 99; - pkt.stream_id = htobe64(0x020000fffe000002ULL); - pkt.timestamp = htonl(2000000); - pkt.format = AVB_AAF_FORMAT_INT_24BIT; - pkt.nsr = AVB_AAF_PCM_NSR_48KHZ; - pkt.chan_per_frame = 8; - pkt.bit_depth = 24; - pkt.data_len = htons(192); /* 6 frames * 8 channels * 4 bytes */ - pkt.sp = AVB_AAF_PCM_SP_NORMAL; - - pwtest_int_eq(pkt.subtype, AVB_SUBTYPE_AAF); - pwtest_int_eq(pkt.seq_num, 99); - pwtest_int_eq(pkt.format, AVB_AAF_FORMAT_INT_24BIT); - pwtest_int_eq((int)pkt.nsr, AVB_AAF_PCM_NSR_48KHZ); - pwtest_int_eq(pkt.chan_per_frame, 8); - pwtest_int_eq(pkt.bit_depth, 24); - pwtest_int_eq(ntohs(pkt.data_len), 192); - - return PWTEST_PASS; -} - -/* - * Test: 802.1Q frame header construction for AVB. - */ -PWTEST(avb_frame_header_construction) -{ - struct avb_frame_header h; - static const uint8_t dest[6] = { 0x91, 0xe0, 0xf0, 0x00, 0x01, 0x00 }; - static const uint8_t src[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x01 }; - int prio = 3; - int vlan_id = 2; - - memset(&h, 0, sizeof(h)); - memcpy(h.dest, dest, 6); - memcpy(h.src, src, 6); - h.type = htons(0x8100); /* 802.1Q VLAN tag */ - h.prio_cfi_id = htons((prio << 13) | vlan_id); - h.etype = htons(0x22f0); /* AVB/TSN EtherType */ - - /* Verify the 802.1Q header */ - pwtest_int_eq(ntohs(h.type), 0x8100); - pwtest_int_eq(ntohs(h.etype), 0x22f0); - - /* Extract priority from prio_cfi_id */ - pwtest_int_eq((ntohs(h.prio_cfi_id) >> 13) & 0x7, prio); - /* Extract VLAN ID (lower 12 bits) */ - pwtest_int_eq(ntohs(h.prio_cfi_id) & 0xFFF, vlan_id); - - return PWTEST_PASS; -} - -/* - * Test: PDU size calculations for various audio configurations. - * Verifies the math used in setup_pdu(). - */ -PWTEST(avb_pdu_size_calculations) -{ - size_t hdr_size, payload_size, pdu_size; - int64_t pdu_period; - - /* Default config: 8 channels, S24_32_BE (4 bytes), 6 frames/PDU, 48kHz */ - int channels = 8; - int sample_size = 4; /* S24_32_BE */ - int frames_per_pdu = 6; - int rate = 48000; - int stride = channels * sample_size; - - hdr_size = sizeof(struct avb_frame_header) + sizeof(struct avb_packet_iec61883); - payload_size = stride * frames_per_pdu; - pdu_size = hdr_size + payload_size; - pdu_period = SPA_NSEC_PER_SEC * frames_per_pdu / rate; - - /* Header: 18 (frame) + 32 (iec61883) = 50 bytes */ - pwtest_int_eq((int)hdr_size, 50); - - /* Payload: 8 ch * 4 bytes * 6 frames = 192 bytes */ - pwtest_int_eq((int)payload_size, 192); - - /* Total PDU: 50 + 192 = 242 bytes */ - pwtest_int_eq((int)pdu_size, 242); - - /* PDU period: 6/48000 seconds = 125000 ns = 125 us */ - pwtest_int_eq((int)pdu_period, 125000); - - /* Stride: 8 * 4 = 32 bytes per frame */ - pwtest_int_eq(stride, 32); - - /* IEC61883 data_len field = payload + 8 CIP header bytes */ - pwtest_int_eq((int)(payload_size + 8), 200); - - /* 2-channel configuration */ - channels = 2; - stride = channels * sample_size; - payload_size = stride * frames_per_pdu; - pwtest_int_eq((int)payload_size, 48); - pwtest_int_eq(stride, 8); - - return PWTEST_PASS; -} - -/* - * Test: Ringbuffer audio data round-trip. - * Write audio frames to the ringbuffer, read them back, verify integrity. - */ -PWTEST(avb_ringbuffer_audio_roundtrip) -{ - struct spa_ringbuffer ring; - uint8_t buffer[BUFFER_SIZE]; - int stride = 32; /* 8 channels * 4 bytes */ - int frames = 48; /* 48 frames = 1ms at 48kHz */ - int n_bytes = frames * stride; - uint8_t write_data[2048]; - uint8_t read_data[2048]; - uint32_t index; - int32_t avail; - - spa_ringbuffer_init(&ring); - - /* Fill write_data with a recognizable pattern */ - for (int i = 0; i < n_bytes; i++) - write_data[i] = (uint8_t)(i & 0xFF); - - /* Write to ringbuffer */ - avail = spa_ringbuffer_get_write_index(&ring, &index); - pwtest_int_eq(avail, 0); - - spa_ringbuffer_write_data(&ring, buffer, sizeof(buffer), - index % sizeof(buffer), write_data, n_bytes); - index += n_bytes; - spa_ringbuffer_write_update(&ring, index); - - /* Read back from ringbuffer */ - avail = spa_ringbuffer_get_read_index(&ring, &index); - pwtest_int_eq(avail, n_bytes); - - spa_ringbuffer_read_data(&ring, buffer, sizeof(buffer), - index % sizeof(buffer), read_data, n_bytes); - index += n_bytes; - spa_ringbuffer_read_update(&ring, index); - - /* Verify data integrity */ - pwtest_int_eq(memcmp(write_data, read_data, n_bytes), 0); - - /* After read, buffer should be empty */ - avail = spa_ringbuffer_get_read_index(&ring, &index); - pwtest_int_eq(avail, 0); - - return PWTEST_PASS; -} - -/* - * Test: Ringbuffer wrap-around behavior with multiple writes. - * Simulates multiple PDU-sized writes filling past the buffer end. - */ -PWTEST(avb_ringbuffer_wraparound) -{ - struct spa_ringbuffer ring; - uint8_t *buffer; - int stride = 32; - int frames_per_pdu = 6; - int payload_size = stride * frames_per_pdu; /* 192 bytes */ - int num_writes = (BUFFER_SIZE / payload_size) + 5; /* Write past buffer end */ - uint8_t write_data[192]; - uint8_t read_data[192]; - uint32_t w_index, r_index; - int32_t avail; - - buffer = calloc(1, BUFFER_SIZE); - pwtest_ptr_notnull(buffer); - - spa_ringbuffer_init(&ring); - - /* Write many PDU payloads, reading as we go to prevent overrun */ - for (int i = 0; i < num_writes; i++) { - /* Fill with per-PDU pattern */ - memset(write_data, (uint8_t)(i + 1), payload_size); - - avail = spa_ringbuffer_get_write_index(&ring, &w_index); - spa_ringbuffer_write_data(&ring, buffer, BUFFER_SIZE, - w_index % BUFFER_SIZE, write_data, payload_size); - w_index += payload_size; - spa_ringbuffer_write_update(&ring, w_index); - - /* Read it back immediately */ - avail = spa_ringbuffer_get_read_index(&ring, &r_index); - pwtest_int_eq(avail, payload_size); - - spa_ringbuffer_read_data(&ring, buffer, BUFFER_SIZE, - r_index % BUFFER_SIZE, read_data, payload_size); - r_index += payload_size; - spa_ringbuffer_read_update(&ring, r_index); - - /* Verify the pattern survived the wrap-around */ - for (int j = 0; j < payload_size; j++) { - if (read_data[j] != (uint8_t)(i + 1)) { - free(buffer); - return PWTEST_FAIL; - } - } - } - - free(buffer); - - return PWTEST_PASS; -} - -/* - * Test: IEC61883 packet receive simulation. - * Builds IEC61883 packets and writes their payload into a ringbuffer, - * mirroring the logic of handle_iec61883_packet(). - */ -PWTEST(avb_iec61883_receive_simulation) -{ - struct spa_ringbuffer ring; - uint8_t *rb_buffer; - uint8_t pkt_buf[2048]; - struct avb_frame_header *h; - struct avb_packet_iec61883 *p; - int channels = 8; - int sample_size = 4; - int stride = channels * sample_size; - int frames_per_pdu = 6; - int payload_size = stride * frames_per_pdu; /* 192 bytes */ - int n_packets = 10; - uint32_t index; - int32_t filled; - uint8_t read_data[192]; - - rb_buffer = calloc(1, BUFFER_SIZE); - pwtest_ptr_notnull(rb_buffer); - spa_ringbuffer_init(&ring); - - for (int i = 0; i < n_packets; i++) { - /* Build a receive packet like on_socket_data() would see */ - memset(pkt_buf, 0, sizeof(pkt_buf)); - h = (struct avb_frame_header *)pkt_buf; - p = SPA_PTROFF(h, sizeof(*h), void); - - p->subtype = AVB_SUBTYPE_61883_IIDC; - p->sv = 1; - p->tv = 1; - p->seq_num = i; - p->stream_id = htobe64(0x020000fffe000001ULL); - p->timestamp = htonl(i * 125000); - p->data_len = htons(payload_size + 8); /* payload + 8 CIP bytes */ - p->tag = 0x1; - p->dbs = channels; - p->dbc = i * frames_per_pdu; - - /* Fill payload with audio-like pattern */ - for (int j = 0; j < payload_size; j++) - p->payload[j] = (uint8_t)((i * payload_size + j) & 0xFF); - - /* Simulate handle_iec61883_packet() logic */ - { - int n_bytes = ntohs(p->data_len) - 8; - pwtest_int_eq(n_bytes, payload_size); - - filled = spa_ringbuffer_get_write_index(&ring, &index); - - if (filled + (int32_t)n_bytes <= (int32_t)BUFFER_SIZE) { - spa_ringbuffer_write_data(&ring, rb_buffer, BUFFER_SIZE, - index % BUFFER_SIZE, p->payload, n_bytes); - index += n_bytes; - spa_ringbuffer_write_update(&ring, index); - } - } - } - - /* Verify all packets were received */ - filled = spa_ringbuffer_get_read_index(&ring, &index); - pwtest_int_eq(filled, n_packets * payload_size); - - /* Read back first packet's data and verify */ - spa_ringbuffer_read_data(&ring, rb_buffer, BUFFER_SIZE, - index % BUFFER_SIZE, read_data, payload_size); - - for (int j = 0; j < payload_size; j++) { - if (read_data[j] != (uint8_t)(j & 0xFF)) { - free(rb_buffer); - return PWTEST_FAIL; - } - } - - free(rb_buffer); - - return PWTEST_PASS; -} - -/* - * Test: IEC61883 transmit PDU construction simulation. - * Builds PDU like setup_pdu() + flush_write() would, verifies structure. - */ -PWTEST(avb_iec61883_transmit_pdu) -{ - uint8_t pdu[2048]; - struct avb_frame_header *h; - struct avb_packet_iec61883 *p; - static const uint8_t dest[6] = { 0x91, 0xe0, 0xf0, 0x00, 0x01, 0x00 }; - static const uint8_t src[6] = { 0x02, 0x00, 0x00, 0x00, 0x00, 0x01 }; - int channels = 8; - int stride = channels * 4; - int frames_per_pdu = 6; - int payload_size = stride * frames_per_pdu; - int prio = 3; - int vlan_id = 2; - uint64_t stream_id = 0x020000fffe000001ULL; - - /* Simulate setup_pdu() */ - memset(pdu, 0, sizeof(pdu)); - h = (struct avb_frame_header *)pdu; - p = SPA_PTROFF(h, sizeof(*h), void); - - memcpy(h->dest, dest, 6); - memcpy(h->src, src, 6); - h->type = htons(0x8100); - h->prio_cfi_id = htons((prio << 13) | vlan_id); - h->etype = htons(0x22f0); - - p->subtype = AVB_SUBTYPE_61883_IIDC; - p->sv = 1; - p->stream_id = htobe64(stream_id); - p->data_len = htons(payload_size + 8); - p->tag = 0x1; - p->channel = 0x1f; - p->tcode = 0xa; - p->sid = 0x3f; - p->dbs = channels; - p->qi2 = 0x2; - p->format_id = 0x10; - p->fdf = 0x2; - p->syt = htons(0x0008); - - /* Simulate flush_write() per-PDU setup */ - p->seq_num = 0; - p->tv = 1; - p->timestamp = htonl(125000); - p->dbc = 0; - - /* Verify the PDU */ - pwtest_int_eq(p->subtype, AVB_SUBTYPE_61883_IIDC); - pwtest_int_eq(be64toh(p->stream_id), stream_id); - pwtest_int_eq(ntohs(p->data_len), payload_size + 8); - pwtest_int_eq(p->dbs, channels); - pwtest_int_eq(p->seq_num, 0); - pwtest_int_eq((int)ntohl(p->timestamp), 125000); - pwtest_int_eq(p->dbc, 0); - pwtest_int_eq(ntohs(h->etype), 0x22f0); - - /* Simulate second PDU — verify sequence and DBC advance */ - p->seq_num = 1; - p->timestamp = htonl(250000); - p->dbc = frames_per_pdu; - - pwtest_int_eq(p->seq_num, 1); - pwtest_int_eq(p->dbc, frames_per_pdu); - pwtest_int_eq((int)ntohl(p->timestamp), 250000); - - return PWTEST_PASS; -} - -/* - * Test: Ringbuffer overrun detection. - * Simulates the overrun check in handle_iec61883_packet(). - */ -PWTEST(avb_ringbuffer_overrun) -{ - struct spa_ringbuffer ring; - uint8_t *buffer; - uint8_t data[256]; - uint32_t index; - int32_t filled; - int payload_size = 192; - int overrun_count = 0; - - buffer = calloc(1, BUFFER_SIZE); - pwtest_ptr_notnull(buffer); - spa_ringbuffer_init(&ring); - - memset(data, 0xAA, sizeof(data)); - - /* Fill the buffer to capacity */ - int max_writes = BUFFER_SIZE / payload_size; - for (int i = 0; i < max_writes; i++) { - filled = spa_ringbuffer_get_write_index(&ring, &index); - if (filled + payload_size > (int32_t)BUFFER_SIZE) { - overrun_count++; - break; - } - spa_ringbuffer_write_data(&ring, buffer, BUFFER_SIZE, - index % BUFFER_SIZE, data, payload_size); - index += payload_size; - spa_ringbuffer_write_update(&ring, index); - } - - /* Try one more write — should detect overrun */ - filled = spa_ringbuffer_get_write_index(&ring, &index); - if (filled + payload_size > (int32_t)BUFFER_SIZE) - overrun_count++; - - /* Should have hit at least one overrun */ - pwtest_int_gt(overrun_count, 0); - - /* Verify data still readable from the full buffer */ - filled = spa_ringbuffer_get_read_index(&ring, &index); - pwtest_int_gt(filled, 0); - - free(buffer); - - return PWTEST_PASS; -} - -/* - * Test: Sequence number wrapping at 256 (uint8_t). - * Verifies that sequence numbers wrap correctly as in flush_write(). - */ -PWTEST(avb_sequence_number_wrapping) -{ - uint8_t seq = 0; - uint8_t dbc = 0; - int frames_per_pdu = 6; - - /* Simulate 300 PDU transmissions — seq wraps at 256 */ - for (int i = 0; i < 300; i++) { - pwtest_int_eq(seq, (uint8_t)(i & 0xFF)); - seq++; - dbc += frames_per_pdu; - } - - /* After 300 PDUs: seq = 300 & 0xFF = 44, dbc = 300*6 = 1800 & 0xFF = 8 */ - pwtest_int_eq(seq, (uint8_t)(300 & 0xFF)); - pwtest_int_eq(dbc, (uint8_t)(300 * frames_per_pdu)); - - return PWTEST_PASS; -} - -PWTEST_SUITE(avb) -{ - /* Phase 2: ADP and basic tests */ - pwtest_add(avb_adp_entity_available, PWTEST_NOARG); - pwtest_add(avb_adp_entity_departing, PWTEST_NOARG); - pwtest_add(avb_adp_entity_discover, PWTEST_NOARG); - pwtest_add(avb_adp_entity_timeout, PWTEST_NOARG); - pwtest_add(avb_mrp_attribute_lifecycle, PWTEST_NOARG); - pwtest_add(avb_milan_server_create, PWTEST_NOARG); - - /* Phase 3: MRP state machine tests */ - pwtest_add(avb_mrp_begin_join_new_tx, PWTEST_NOARG); - pwtest_add(avb_mrp_join_leave_cycle, PWTEST_NOARG); - pwtest_add(avb_mrp_rx_new_notification, PWTEST_NOARG); - pwtest_add(avb_mrp_registrar_leave_timer, PWTEST_NOARG); - pwtest_add(avb_mrp_multiple_attributes, PWTEST_NOARG); - - /* Phase 3: MSRP tests */ - pwtest_add(avb_msrp_attribute_types, PWTEST_NOARG); - pwtest_add(avb_msrp_domain_transmit, PWTEST_NOARG); - pwtest_add(avb_msrp_talker_transmit, PWTEST_NOARG); - pwtest_add(avb_msrp_talker_failed_notify, PWTEST_NOARG); - - /* Phase 3: MRP packet parsing tests */ - pwtest_add(avb_mrp_parse_single_domain, PWTEST_NOARG); - pwtest_add(avb_mrp_parse_with_lva, PWTEST_NOARG); - pwtest_add(avb_mrp_parse_three_values, PWTEST_NOARG); - - /* Phase 4: ACMP integration tests */ - pwtest_add(avb_acmp_not_supported, PWTEST_NOARG); - pwtest_add(avb_acmp_connect_tx_no_stream, PWTEST_NOARG); - pwtest_add(avb_acmp_wrong_entity_ignored, PWTEST_NOARG); - pwtest_add(avb_acmp_connect_rx_forward, PWTEST_NOARG); - pwtest_add(avb_acmp_pending_timeout, PWTEST_NOARG); - pwtest_add(avb_acmp_packet_filtering, PWTEST_NOARG); - - /* Phase 5: AECP/AEM entity model tests */ - pwtest_add(avb_aecp_read_descriptor_entity, PWTEST_NOARG); - pwtest_add(avb_aecp_read_descriptor_not_found, PWTEST_NOARG); - pwtest_add(avb_aecp_packet_filtering, PWTEST_NOARG); - pwtest_add(avb_aecp_unsupported_message_types, PWTEST_NOARG); - pwtest_add(avb_aecp_aem_not_implemented, PWTEST_NOARG); - pwtest_add(avb_aecp_acquire_entity_legacy, PWTEST_NOARG); - pwtest_add(avb_aecp_lock_entity_legacy, PWTEST_NOARG); - pwtest_add(avb_aecp_entity_available_milan, PWTEST_NOARG); - pwtest_add(avb_aecp_lock_entity_milan, PWTEST_NOARG); - pwtest_add(avb_aecp_lock_non_entity_milan, PWTEST_NOARG); - pwtest_add(avb_aecp_acquire_entity_milan, PWTEST_NOARG); - pwtest_add(avb_aecp_read_descriptor_milan, PWTEST_NOARG); - - /* Phase 7: Additional protocol coverage tests */ - pwtest_add(avb_maap_conflict_probe_in_announce, PWTEST_NOARG); - pwtest_add(avb_maap_defend_causes_reprobe, PWTEST_NOARG); - pwtest_add(avb_maap_announce_conflict, PWTEST_NOARG); - pwtest_add(avb_maap_no_conflict, PWTEST_NOARG); - pwtest_add(avb_acmp_disconnect_rx_forward, PWTEST_NOARG); - pwtest_add(avb_acmp_disconnect_tx_no_stream, PWTEST_NOARG); - pwtest_add(avb_acmp_disconnect_pending_timeout, PWTEST_NOARG); - pwtest_add(avb_aecp_get_avb_info, PWTEST_NOARG); - pwtest_add(avb_aecp_get_avb_info_wrong_type, PWTEST_NOARG); - pwtest_add(avb_mrp_leave_all_timer, PWTEST_NOARG); - pwtest_add(avb_mrp_periodic_timer, PWTEST_NOARG); - pwtest_add(avb_msrp_talker_failed_process, PWTEST_NOARG); - - /* Phase 8: MVRP/MMRP, ADP edge cases, descriptor, AECP command tests */ - pwtest_add(avb_mvrp_attribute_lifecycle, PWTEST_NOARG); - pwtest_add(avb_mvrp_vid_transmit, PWTEST_NOARG); - pwtest_add(avb_mmrp_attribute_types, PWTEST_NOARG); - pwtest_add(avb_adp_duplicate_entity_available, PWTEST_NOARG); - pwtest_add(avb_adp_targeted_discover, PWTEST_NOARG); - pwtest_add(avb_adp_readvertise_timing, PWTEST_NOARG); - pwtest_add(avb_adp_departure_before_timeout, PWTEST_NOARG); - pwtest_add(avb_descriptor_lookup_edge_cases, PWTEST_NOARG); - pwtest_add(avb_descriptor_data_integrity, PWTEST_NOARG); - pwtest_add(avb_aecp_get_configuration, PWTEST_NOARG); - pwtest_add(avb_aecp_get_sampling_rate, PWTEST_NOARG); - pwtest_add(avb_aecp_get_sampling_rate_wrong_type, PWTEST_NOARG); - pwtest_add(avb_aecp_get_name_entity, PWTEST_NOARG); - pwtest_add(avb_aecp_get_name_missing_descriptor, PWTEST_NOARG); - - /* Phase 6: AVTP audio data path tests */ - pwtest_add(avb_iec61883_packet_layout, PWTEST_NOARG); - pwtest_add(avb_aaf_packet_layout, PWTEST_NOARG); - pwtest_add(avb_frame_header_construction, PWTEST_NOARG); - pwtest_add(avb_pdu_size_calculations, PWTEST_NOARG); - pwtest_add(avb_ringbuffer_audio_roundtrip, PWTEST_NOARG); - pwtest_add(avb_ringbuffer_wraparound, PWTEST_NOARG); - pwtest_add(avb_iec61883_receive_simulation, PWTEST_NOARG); - pwtest_add(avb_iec61883_transmit_pdu, PWTEST_NOARG); - pwtest_add(avb_ringbuffer_overrun, PWTEST_NOARG); - pwtest_add(avb_sequence_number_wrapping, PWTEST_NOARG); - - return PWTEST_PASS; -} diff --git a/test/test-context.c b/test/test-context.c index 967c631fb..093e7b388 100644 --- a/test/test-context.c +++ b/test/test-context.c @@ -29,7 +29,6 @@ PWTEST(context_abi) void (*global_removed) (void *data, struct pw_global *global); void (*driver_added) (void *data, struct pw_impl_node *node); void (*driver_removed) (void *data, struct pw_impl_node *node); - void (*recalc_graph) (void *data); } test = { PW_VERSION_CONTEXT_EVENTS, NULL }; pw_init(0, NULL); @@ -41,9 +40,8 @@ PWTEST(context_abi) TEST_FUNC(ev, test, global_removed); TEST_FUNC(ev, test, driver_added); TEST_FUNC(ev, test, driver_removed); - TEST_FUNC(ev, test, recalc_graph); - pwtest_int_eq(PW_VERSION_CONTEXT_EVENTS, 2); + pwtest_int_eq(PW_VERSION_CONTEXT_EVENTS, 1); pwtest_int_eq(sizeof(ev), sizeof(test)); pw_deinit(); diff --git a/test/test-mempool.c b/test/test-mempool.c index 3793b23c4..36f69c571 100644 --- a/test/test-mempool.c +++ b/test/test-mempool.c @@ -41,74 +41,9 @@ PWTEST(mempool_issue4884) return PWTEST_PASS; } -PWTEST(map_range_overflow) -{ - /* - * Test that pw_map_range_init rejects offset + size combinations - * that would overflow uint32_t, which could cause mmap with a - * truncated size and subsequent out-of-bounds access. - */ - struct pw_map_range range; - uint32_t page_size = 4096; - int res; - - /* Normal case: should succeed */ - res = pw_map_range_init(&range, 0, 4096, page_size); - pwtest_int_eq(res, 0); - pwtest_int_eq(range.offset, 0u); - pwtest_int_eq(range.start, 0u); - pwtest_int_eq(range.size, 4096u); - - /* Page-aligned offset: should succeed */ - res = pw_map_range_init(&range, 4096, 4096, page_size); - pwtest_int_eq(res, 0); - pwtest_int_eq(range.offset, 4096u); - pwtest_int_eq(range.start, 0u); - pwtest_int_eq(range.size, 4096u); - - /* Non-aligned offset: start gets the remainder */ - res = pw_map_range_init(&range, 100, 4096, page_size); - pwtest_int_eq(res, 0); - pwtest_int_eq(range.offset, 0u); - pwtest_int_eq(range.start, 100u); - - /* size=0: should succeed */ - res = pw_map_range_init(&range, 0, 0, page_size); - pwtest_int_eq(res, 0); - - /* Overflow: non-aligned offset causes start > 0, then start + size wraps */ - res = pw_map_range_init(&range, 4095, 0xFFFFF002, page_size); - pwtest_int_lt(res, 0); - - /* Overflow: max size with any non-zero start */ - res = pw_map_range_init(&range, 1, UINT32_MAX, page_size); - pwtest_int_lt(res, 0); - - /* Both large but page-aligned: start=0, start+size=0x80000000, - * round-up doesn't overflow, so this should succeed */ - res = pw_map_range_init(&range, 0x80000000, 0x80000000, page_size); - pwtest_int_eq(res, 0); - - /* Non-aligned offset but still fits: start=1, start+size=0x80000001 */ - res = pw_map_range_init(&range, 0x80000001, 0x80000000, page_size); - pwtest_int_eq(res, 0); - - /* Overflow: round-up of start+size would exceed uint32 */ - res = pw_map_range_init(&range, 1, UINT32_MAX - 1, page_size); - pwtest_int_lt(res, 0); - - /* start=0, size=UINT32_MAX: start + size doesn't wrap, but - * SPA_ROUND_UP_N to page_size would overflow, so must fail */ - res = pw_map_range_init(&range, 0, UINT32_MAX, page_size); - pwtest_int_lt(res, 0); - - return PWTEST_PASS; -} - PWTEST_SUITE(pw_mempool) { pwtest_add(mempool_issue4884, PWTEST_NOARG); - pwtest_add(map_range_overflow, PWTEST_NOARG); return PWTEST_PASS; } diff --git a/test/test-properties.c b/test/test-properties.c index 48d7858db..459e1e15e 100644 --- a/test/test-properties.c +++ b/test/test-properties.c @@ -466,7 +466,7 @@ PWTEST(properties_serialize_dict_stack_overflow) fp = fopen(tmpfile, "we"); pwtest_ptr_notnull(fp); r = pw_properties_serialize_dict(fp, &dict, 0); - pwtest_int_eq(r, 2); + pwtest_int_eq(r, 1); fclose(fp); free(long_value); diff --git a/test/test-spa-json.c b/test/test-spa-json.c index 1c0aada16..0c3c46f59 100644 --- a/test/test-spa-json.c +++ b/test/test-spa-json.c @@ -350,12 +350,6 @@ PWTEST(json_parse) expect_string(&it[0], "hello"); expect_end(&it[0]); - json = "xy{}"; - spa_json_init(&it[0], json, strlen(json)); - expect_string_or_bare(&it[0], "xy"); - expect_object(&it[0], &it[1]); - expect_end(&it[0]); - /* top-level context */ json = "x y x y"; spa_json_init(&it[0], json, strlen(json)); @@ -708,40 +702,21 @@ PWTEST(json_float_check) struct { const char *str; int res; - int jsonres; } val[] = { - { "0.0", 1, 1}, - { ".0", 1, 0 }, - { "+.0E0", 1, 0 }, - { "-.0e0", 1, 0 }, - - { "0,0", 0, 0 }, - { "0.0.5", 0, 0 }, - { "0x0", 1, 0 }, - { "0x0.0", 1, 0 }, - { "E10", 0, 0 }, - { "e20", 0, 0 }, - { " 0.0", 1, 0 }, - { "0.0 ", 0, 0 }, - { " 0.0 ", 0, 0 }, - - { "+", 0, 0 }, - { "+0", 1, 0 }, - { "-", 0, 0 }, - { "-0", 1, 1 }, - { "-0", 1, 1 }, - { "-01", 1, 0 }, - { "-00", 1, 0 }, - { "-1", 1, 1 }, - { "-10", 1, 1 }, - { "-.", 0, 0 }, - { "-0.", 1, 0 }, - { "-01.", 1, 0 }, - { "-1.", 1, 0 }, - - { "-.0", 1, 0 }, - { "-1E10", 1, 1 }, + { "0.0", 1 }, + { ".0", 1 }, + { "+.0E0", 1 }, + { "-.0e0", 1 }, + { "0,0", 0 }, + { "0.0.5", 0 }, + { "0x0", 0 }, + { "0x0.0", 0 }, + { "E10", 0 }, + { "e20", 0 }, + { " 0.0", 0 }, + { "0.0 ", 0 }, + { " 0.0 ", 0 }, }; unsigned i; float v; @@ -749,9 +724,6 @@ PWTEST(json_float_check) for (i = 0; i < SPA_N_ELEMENTS(val); i++) { pwtest_int_eq(spa_json_parse_float(val[i].str, strlen(val[i].str), &v), val[i].res); } - for (i = 0; i < SPA_N_ELEMENTS(val); i++) { - pwtest_int_eq(spa_json_is_json_number(val[i].str, strlen(val[i].str)), val[i].jsonres); - } return PWTEST_PASS; } @@ -972,7 +944,6 @@ PWTEST(json_data) "n_array_missing_value.json", "n_array_number_and_comma.json", "n_array_number_and_several_commas.json", - "n_array_inner_array_no_comma.json", "n_object_comma_instead_of_colon.json", "n_object_double_colon.json", "n_object_missing_semicolon.json", @@ -1003,12 +974,6 @@ PWTEST(json_data) "n_number_-2..json", "n_number_hex_1_digit.json", "n_number_hex_2_digits.json", - "n_number_infinity.json", - "n_number_+Inf.json", - "n_number_Inf.json", - "n_number_minus_infinity.json", - "n_number_-NaN.json", - "n_number_NaN.json", "n_number_neg_int_starting_with_zero.json", "n_number_neg_real_without_int_part.json", "n_number_real_without_fractional_part.json",