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..3acc6e6be 100644 --- a/NEWS +++ b/NEWS @@ -1,102 +1,3 @@ -# PipeWire 1.6.0 (2026-02-19) - -This is the 1.6 release that is API and ABI compatible with previous -1.4.x releases. - -This release contains some of the bigger changes that happened since -the 1.4 release last year, including: - - * An LDAC decoder was added for bluetooth. - * SpanDSP for bluetooth packet loss concealment. - * Safe parsing and building of PODs in shared memory. - * Added support for metadata features. This is used to signal that - the sync_timeline metadata supports the RELEASE operation. - * Node commands and events can contain extra user data. - * Support for more compressed format helper functions to create - and parse formats. - * Support for compile time max channels. The max channels was - increased to 128. - * Support for audio channel layouts was added. This makes it possible - to set "audio.layout" = "5.1" instead of the more verbose - audio.position = [ FL, FR, FC, LFE, SL, SR ] - * Support for Capability Params was added. This can be used to - negotiate capabilities on a link before format and buffer - negotiation takes place. - * More HDR colortypes are added. - * Loops now have locking with priority inversion. Most code was adapted - to use the faster locks instead of epoll/eventfd to update shared state. - * Channel position are parsed from EDID data. - * Channel maps are now set on ALSA. - * The resampler now supports configurable window functions such - as blackman and kaiser windows. The phases are now also calculated - with fixed point math, which makes it more accurate. - * Many bluetooth updates and improvements. - * The filter-graph has an ffmpeg and ONNX plugin. The ffmpeg plugin - can run an audio AVFilterGraph. The ONNX plugin can run some models - such as the silero VAD. - * Many AVB updates. Work is ongoing to merge the Milan protocol. - * Support for v0 clients was removed. - * The jack-tunnel module can now autoconnect ports. - * ROC support multitrack layouts now. - * Many RTP updates. - * rlimits can now be set in the config file. - * Thread reset on fork can now be configured. JACK clients expect this - to be disabled. - * node.exclusive is now enforced. - * node.reliable enables reliable transport. - * pw-cat supports sysex and midiclip as well as some more uncompressed - formats. Options were added to set the container and codec formats - as well as list the supported containers, codecs, layouts and channel - names. - * Documentation updates. - - -## Highlights (since the previous 1.5.85 prerelease) - - Fix a 64 channel limit in the channel mixer. - - Fix an fd leak in pulse-server in some error cases. - - Some small fixes and improvements. - - -## PipeWire - - Fix Capability leaks. - - Return an error in pw-stream get-time when not STREAMING. - - Set the current time in the driver position before starting. - Some followers might look at it. - -## Modules - - Improve default channel handling in module-filter-chain. - - Support source and sink only module-filter-chain. - - Tweak the filter-chain spatializer example gains. - - Handle new snapcast service type. (#5104) - - Implement socket activation without depending on libsystemd. - - Support ipv4 link-local addresses in RAOP and snapcast. (#4830) - - Forward ROC-toolkit logs to pipewire. - -## SPA - - Improve default channel handling in filter-graph. (#5084) - - Clamp control values to min/max. (#5088) - - Support mode JBL gaming headsets. - - Handle some SOFA errors and add gain option. - - Really handle more than 64 channels in the channelmixer. (#5118) - - Allow removal in ALSA-udev of ignored cards. - -# pulse-server - - Fix mono mixdown query. - - Expose headset autoswitch message. - - Handle EPROTO errors by disconnecting. - - Handle timeouts in play-sample streams. (#5099) - -## GStreamer - - Fix crop metadata. - - Fix a race in the buffer release function. - -## Tools - - Improve format support and detection in pw-cat. - - 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 @@ -156,6 +57,9 @@ releases. ## Docs - Document the resampler properties better. + +Older versions: + # PipeWire 1.5.84 (2025-11-27) This is the fourth 1.6 release candidate that is API and ABI diff --git a/doc/DoxygenLayout.xml b/doc/DoxygenLayout.xml index 10da55da5..300c8400e 100644 --- a/doc/DoxygenLayout.xml +++ b/doc/DoxygenLayout.xml @@ -44,7 +44,6 @@ - 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..ed82b2f41 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. @@ -1194,15 +1171,6 @@ in a platform-specific way. See `tests/examples/bt-pinephone.lua` in WirePlumber Do not enable this setting if you don't know what all this means, as it won't work. \endparblock -@PAR@ monitor-prop bluez5.hw-offload-datapath # integer -\parblock -HFP/HSP hardware offload data path ID (default: 0). - -This feature configures the SCO hardware‑offload data path for HFP/HSP using the Bluetooth -SIG–specified procedure. It is intended for advanced setups and vendor integrations. Do not -edit this unless required; incorrect values can disable SCO offload. -\endparblock - @PAR@ monitor-prop bluez5.a2dp.opus.pro.channels = 3 # integer PipeWire Opus Pro audio profile channel count. @@ -1234,7 +1202,6 @@ PipeWire Opus Pro audio profile duplex max bitrate. PipeWire Opus Pro audio profile duplex frame duration (1/10 ms). @PAR@ monitor-prop bluez5.bcast_source.config = [] # JSON -For a per-adapter configuration of multiple BIGs use an "adapter" entry in the BIG with the BD address. \parblock Example: ``` @@ -1396,12 +1363,6 @@ BAP QoS framing that needs to be applied for vendor defined preset This property is experimental. 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. -This property is experimental. -Available: low-latency, high-reliability, balanced - ## Node properties @PAR@ node-prop bluez5.media-source-role # string @@ -1443,11 +1404,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/dma-buf.dox b/doc/dox/internals/dma-buf.dox index ec02b9da7..273be930e 100644 --- a/doc/dox/internals/dma-buf.dox +++ b/doc/dox/internals/dma-buf.dox @@ -313,13 +313,12 @@ performed. Device ID negotiation needs explicit support by both end points of a stream, thus, the first step of negotiation is discovering whether other peer has support for it. This is done by advertising a \ref SPA_PARAM_Capability with the key \ref -PW_CAPABILITY_DEVICE_ID_NEGOTIATION and value `1` which corresponds to the -current negotiation API version. +PW_CAPABILITY_DEVICE_ID_NEGOTIATION and value `true` ``` spa_param_dict_build_dict(&b, SPA_PARAM_Capability, &SPA_DICT_ITEMS( - SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "1"))); + SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "true"))); ``` To do this, when connecting to the stream, the \ref PW_STREAM_FLAG_INACTIVE flag must be @@ -349,10 +348,10 @@ rectangles. For example params[n_params++] = spa_pod_builder_pop(&b, &f); ``` -After having received the first \ref SPA_PARAM_PeerCapability param, if it contained the -\ref PW_CAPABILITY_DEVICE_ID_NEGOTIATION set to a supported API version number, the full -set of formats can be sent using \ref pw_stream_update_params following by activating the -stream usina supported API version numberstream_set_active(stream, true)`. +After having received the first \ref SPA_PARAM_PeerCapability param, if it contained the \ref +PW_CAPABILITY_DEVICE_ID set to `true`, the full set of formats can be sent using \ref +pw_stream_update_params following by activating the stream using +`pw_stream_set_active(stream, true)`. Note that the first \ref SPA_PARAM_Format received may be the result of the initial format negotian with bare minimum parameters, and will be superseded by the result of the format @@ -365,15 +364,12 @@ with. This can be used to reduce the amount of devices that are queried for form metadata, which can be a time consuming task, if devices needs to be woken up. To achieve this, the consumer adds another \ref SPA_PARAM_PeerCapability item with the key -\ref PW_CAPABILITY_DEVICE_IDS set to a JSON object describing what device IDs are supported. - -This JSON object as of version 1 contains a single key "available-devices" that contain -a list of hexadecimal encoded `dev_t` device IDs. +\ref PW_CAPABILITY_DEVICE_IDS set to a string of base 64 encoded `dev_t` device IDs. ``` - char *device_ids = "{\"available-devices\": [\"6464000000000000\",\"c8c8000000000000\"]}"; + char *device_ids = ...; /* Base 64 encoding of a dev_t. */. &SPA_DICT_ITEMS( - SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "1"), + SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "true"), SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_IDS, device_ids))); ``` diff --git a/doc/dox/internals/driver.dox b/doc/dox/internals/driver.dox index fb717ab8b..4ab0d229b 100644 --- a/doc/dox/internals/driver.dox +++ b/doc/dox/internals/driver.dox @@ -32,11 +32,8 @@ updated as follows: - \ref spa_io_clock::nsec : Must be set to the time (according to the monotonic system clock) when the cycle that the driver is about to trigger started. To - minimize jitter, it is usually a good idea to increment this by a computed - amount (instead of sampling a timestamp from the monotonic system clock) + minimize jitter, it is usually a good idea to increment this by a fixed amount except for when the driver starts and when discontinuities occur in its clock. - The computed amount can be fixed, or varying over time, for example due to - adjustments made by a DLL (more on that below). - \ref spa_io_clock::rate : Set to a value that can translate samples to nanoseconds. - \ref spa_io_clock::position : Current cycle position, in samples. This is the ideal position of the graph cycle (this is explained in greater detail further below). @@ -55,7 +52,7 @@ updated as follows: some cases, this may actually be in the past relative to nsec, for example, when some internal driver clock experienced a discontinuity. Consider setting the \ref SPA_IO_CLOCK_FLAG_DISCONT flag in such a case. Just like with nsec, to - minimize jitter, it is usually a good idea to increment this by a computed amount + minimize jitter, it is usually a good idea to increment this by a fixed amount except for when the driver starts and when discontinuities occur in its clock. The driver node signals the start of the graph cycle by calling \ref spa_node_call_ready @@ -63,11 +60,6 @@ with the \ref SPA_STATUS_HAVE_DATA and \ref SPA_STATUS_NEED_DATA flags passed to that function call. That call must happen inside the thread that runs the data loop assigned to the driver node. -Drivers must make sure that the next cycle is started at the time indicated by -the \ref spa_io_clock::next_nsec timestamp. They do not have to use the monotonic -clock itself for scheduling the next cycle. If for example the internal clock -can directly be used with \c timerfd , the next cycle can be triggered that way. - As mentioned above, the \ref spa_io_clock::position field is the _ideal_ position of the graph cycle, in samples. This contrasts with \ref spa_io_clock::nsec, which is the moment in monotonic clock time when the cycle _actually_ happens. This is an @@ -111,12 +103,11 @@ expected position (in samples) and the actual position (derived from the current of the driver's internal clock), passes the delta between these two quantities into the DLL, and the DLL computes a correction factor (2.0 in the above example) which is used for scaling durations between \c timerfd timeouts. This forms a control loop, since the -correction factor causes the \ref spa_io_clock::next_nsec increments (that is, the -durations between the timerfd timeouts) to be adjusted such that, over time, the difference -between the expected position and the actual position reaches zero. Keep in mind the -notes above about \ref spa_io_clock::position being the ideal position of the graph -cycle, meaning that even in this case, the duration it is incremented by is _not_ -scaled by the correction factor; the duration in samples remains unchanged. +correction factor causes the durations between the timeouts to be adjusted such that the +difference between the expected position and the actual position reaches zero. Keep in +mind the notes above about \ref spa_io_clock::position being the ideal position of the +graph cycle, meaning that even in this case, the duration it is incremented by is +_not_ scaled by the correction factor; the duration in samples remains unchanged. (Other popular control loop mechanisms that are suitable alternatives to the DLL are PID controllers and Kalman filters.) 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/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/pw-cat.1.md b/doc/dox/programs/pw-cat.1.md index 8ec02c711..b6e259a6f 100644 --- a/doc/dox/programs/pw-cat.1.md +++ b/doc/dox/programs/pw-cat.1.md @@ -24,9 +24,9 @@ Play and record media with PipeWire **pw-cat** is a simple tool for playing back or capturing raw or encoded media files on a PipeWire server. It understands all audio file formats -supported by `libsndfile` for PCM capture and playback. When no container -is specified for capturing PCM, the filename extension is used to guess -the file format with the WAV file format as the default. +supported by `libsndfile` for PCM capture and playback. When capturing +PCM, the filename extension is used to guess the file format with the +WAV file format as the default. It understands standard MIDI files and MIDI 2.0 clip files for playback and recording. This tool will not render MIDI files, it will simply make @@ -37,15 +37,8 @@ DSD playback is supported with the DSF file format. This tool will only work with native DSD capable hardware and will produce an error when no such hardware was found. -When the *FILE* is - input will be from STDIN. If no format is specified, -libsndfile will attempt to parse and stream the format from STDIN. For -some formats, this is not possible and libsndfile will give an error. -Raw, MIDI and DSD formats are all streamable from STDIN. - -When the *FILE* is - output will be to STDOUT. If no format is specified, -libsndfile is instructed to output the .au format, which is streamble and -preserves the format, rate and channels. -Raw and DSD formats are all streamable to STDOUT. +When the *FILE* is - input and output will be raw data from STDIN and +STDOUT respectively. # OPTIONS @@ -94,11 +87,6 @@ DSD mode. *FILE* is a DSF file. If the tool is called under the name render the DSD audio. You need a DSD capable device to play DSD content or this program will exit with an error. -\par -s | \--sysex -SysEx mode. *FILE* is a File that contains a raw SysEx MIDI message. -If the tool is called under the name **pw-sysex** this is the default. -The File is read and sent as a MIDI control message into the graph. - \par \--media-type=VALUE Set the media type property (default Audio/Midi depending on mode). The media type is used by the session manager to select a suitable target to @@ -124,9 +112,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) @@ -153,17 +138,6 @@ does not match the samplerate of the server, the data will be resampled. Higher quality uses more CPU. Values between 0 and 15 are allowed, the default quality is 4. -\par -a | \--raw -Raw samples will be read or written. The \--rate, \--format, \--channels -and \--channelmap can be used to specify the raw format. - -\par -M | \--force-midi -Force midi format, one of "midi" or "ump", (default ump). -When reading or writing midi, for one of midi or UMP. - -\par -n | \--sample-count=COUNT -Stop after COUNT samples. - \par \--rate=VALUE The sample rate, default 48000. @@ -171,38 +145,19 @@ The sample rate, default 48000. The number of channels, default 2. \par \--channel-map=VALUE -The channelmap. Possible values include are either a channel layout -such as **mono**, **stereo**, +The channelmap. Possible values include: **mono**, **stereo**, **surround-21**, **quad**, **surround-22**, **surround-40**, -or comma separated array of channel names such as **FL,FR**. -See \--list-layouts and \--list-channel-names to get a complete -list of possible values. - -\par \--list-layouts -List all known channel layouts. One of these can be used as the -\--channel-map value. - -\par \--list-channel-names -List all known channel names. An array of these can be used as the -\--channel-map value. +**surround-31**, **surround-41**, **surround-50**, **surround-51**, +**surround-51r**, **surround-70**, **surround-71** or a comma separated +list of channel names: **FL**, **FR**, **FC**, **LFE**, **SL**, **SR**, +**FLC**, **FRC**, **RC**, **RL**, **RR**, **TC**, **TFL**, **TFC**, +**TFR**, **TRL**, **TRC**, **TRR**, **RLC**, **RRC**, **FLW**, **FRW**, +**LFE2**, **FLH**, **FCH**, **FRH**, **TFLC**, **TFRC**, **TSL**, +**TSR**, **LLFR**, **RLFE**, **BC**, **BLC**, **BRC** \par \--format=VALUE -The sample format to use. Some possible values include: **u8**, **s8**, -**s16** (default), **s24**, **s32**, **f32**, **f64**. See -\--list-formats to get a complete list of values. - -\par \--list-formats -List all known format values. - -\par \--container=VALUE -Specify the container to use when saving. This is usually guessed from -the filename extension but can be specified explicitly. When using -STDOUT and no container is specified, the AU container will be used. -Then using a filename and the container was not specified and it could -not be derived from the filename, the WAV container is used. - -\par \--list-containers -List all known container values. +The sample format to use. One of: **u8**, **s8**, **s16** (default), +**s24**, **s32**, **f32**, **f64**. \par \--volume=VALUE The stream volume, default 1.000. Depending on the locale you have diff --git a/doc/dox/programs/pw-top.1.md b/doc/dox/programs/pw-top.1.md index 1207e3205..e1a866b55 100644 --- a/doc/dox/programs/pw-top.1.md +++ b/doc/dox/programs/pw-top.1.md @@ -13,7 +13,7 @@ node and device statistics. A hierarchical view is shown of Driver nodes and follower nodes. The Driver nodes are actively using a timer to schedule dataflow in the -followers. The followers of a driver node are shown below their driver +followers. The followers of a driver node as shown below their driver with a + sign (or = for async nodes) in a tree-like representation. The columns presented are as follows: diff --git a/meson.build b/meson.build index 37badca51..e8b6f38dd 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('pipewire', ['c' ], - version : '1.7.0', + version : '1.5.85', license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], meson_version : '>= 0.61.1', default_options : [ 'warning_level=3', @@ -82,7 +82,6 @@ common_flags = [ '-fvisibility=hidden', '-fno-strict-aliasing', '-fno-strict-overflow', - '-DSPA_AUDIO_MAX_CHANNELS=128u', '-Werror=suggest-attribute=format', '-Wsign-compare', '-Wpointer-arith', @@ -116,7 +115,7 @@ cc_flags = common_flags + [ '-Werror=old-style-definition', '-Werror=missing-parameter-type', '-Werror=strict-prototypes', - '-Werror=discarded-qualifiers', + '-DSPA_AUDIO_MAX_CHANNELS=128u', ] add_project_arguments(cc.get_supported_arguments(cc_flags), language: 'c') add_project_arguments(cc_native.get_supported_arguments(cc_flags), @@ -368,7 +367,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 +411,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/pipewire-jack/src/meson.build b/pipewire-jack/src/meson.build index 639405bb9..0630d96a8 100644 --- a/pipewire-jack/src/meson.build +++ b/pipewire-jack/src/meson.build @@ -55,7 +55,7 @@ pipewire_jackserver = shared_library('jackserver', pipewire_jackserver_sources, soversion : soversion, version : libjackversion, - c_args : pipewire_jack_c_args + '-DLIBJACKSERVER', + c_args : pipewire_jack_c_args, include_directories : [configinc, jack_inc], dependencies : [pipewire_dep, mathlib], install : true, diff --git a/pipewire-jack/src/pipewire-jack.c b/pipewire-jack/src/pipewire-jack.c index 5ae097ba8..1bef74283 100644 --- a/pipewire-jack/src/pipewire-jack.c +++ b/pipewire-jack/src/pipewire-jack.c @@ -86,7 +86,7 @@ PW_LOG_TOPIC_STATIC(jack_log_topic, "jack"); #define OTHER_CONNECT_FAIL -1 #define OTHER_CONNECT_IGNORE 0 -#define NOTIFY_BUFFER_SIZE (1u<<16) +#define NOTIFY_BUFFER_SIZE (1u<<13) #define NOTIFY_BUFFER_MASK (NOTIFY_BUFFER_SIZE-1) struct notify { @@ -104,8 +104,8 @@ struct notify { #define NOTIFY_TYPE_TOTAL_LATENCY ((9<<4)|NOTIFY_ACTIVE_FLAG) #define NOTIFY_TYPE_PORT_RENAME ((10<<4)|NOTIFY_ACTIVE_FLAG) int type; - int arg1; struct object *object; + int arg1; const char *msg; }; @@ -492,8 +492,6 @@ struct client { jack_position_t jack_position; jack_transport_state_t jack_state; struct frame_times jack_times; - - struct object dummy_port; }; #define return_val_if_fail(expr, val) \ @@ -1448,9 +1446,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: @@ -1467,15 +1464,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; @@ -2199,7 +2208,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); @@ -4459,11 +4468,6 @@ jack_client_t * jack_client_open (const char *client_name, 0, NULL, &client->info); client->info.change_mask = 0; - client->dummy_port.type = INTERFACE_Port; - snprintf(client->dummy_port.port.name, sizeof(client->dummy_port.port.name), "%s:dummy", client_name); - snprintf(client->dummy_port.port.alias1, sizeof(client->dummy_port.port.alias1), "%s:dummy", client_name); - snprintf(client->dummy_port.port.alias2, sizeof(client->dummy_port.port.alias2), "%s:dummy", client_name); - client->show_monitor = pw_properties_get_bool(client->props, "jack.show-monitor", true); client->show_midi = pw_properties_get_bool(client->props, "jack.show-midi", true); client->merge_monitor = pw_properties_get_bool(client->props, "jack.merge-monitor", true); @@ -4864,7 +4868,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; @@ -4876,21 +4880,9 @@ int jack_activate (jack_client_t *client) c->activation->pending_sync = true; spa_list_for_each(o, &c->context.objects, link) { -#if !defined(LIBJACKSERVER) if (o->type != INTERFACE_Port || o->port.port == NULL || o->port.port->client != c || !o->port.port->valid) continue; -#else - /* emits all foreign active ports, skips own (already announced via jack_port_register) */ - if (o->type != INTERFACE_Port || o->removed) - continue; - /* own ports are handled by jack_port_register */ - if (o->port.port != NULL && o->port.port->client == c) - continue; - /* only announce ports whose node is active */ - if (o->port.node != NULL && !node_is_active(c, o->port.node)) - continue; -#endif o->registered = 0; queue_notify(c, NOTIFY_TYPE_PORTREGISTRATION, o, 1, NULL); } @@ -5326,7 +5318,7 @@ int jack_set_freewheel(jack_client_t* client, int onoff) pw_thread_loop_lock(c->context.loop); str = pw_properties_get(c->props, PW_KEY_NODE_GROUP); if (str != NULL) { - const char *p = strstr(str, ",pipewire.freewheel"); + char *p = strstr(str, ",pipewire.freewheel"); if (p == NULL) p = strstr(str, "pipewire.freewheel"); if (p == NULL && onoff) @@ -5445,7 +5437,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 +5454,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 @@ -5959,7 +5951,9 @@ static const char *port_name(struct object *o) { const char *name; struct client *c = o->client; - if (c != NULL && c->default_as_system && is_port_default(c, o)) + if (c == NULL) + return NULL; + if (c->default_as_system && is_port_default(c, o)) name = o->port.system; else name = o->port.name; @@ -6013,16 +6007,7 @@ jack_port_type_id_t jack_port_type_id (const jack_port_t *port) return_val_if_fail(o != NULL, 0); if (o->type != INTERFACE_Port) return TYPE_ID_OTHER; - - /* map internal type IDs to jack1/jack2 compatible public values */ - switch (o->port.type_id) { - case TYPE_ID_AUDIO: return 0; - case TYPE_ID_MIDI: - case TYPE_ID_OSC: - case TYPE_ID_UMP: return 1; /* all MIDI variants map to 1 */ - case TYPE_ID_VIDEO: return 3; /* video maps to 3 */ - default: return o->port.type_id; - } + return o->port.type_id; } SPA_EXPORT @@ -7014,11 +6999,13 @@ jack_port_t * jack_port_by_id (jack_client_t *client, pthread_mutex_lock(&c->context.lock); res = find_by_serial(c, port_id); - pthread_mutex_unlock(&c->context.lock); - if (res == NULL || res->type != INTERFACE_Port) - res = &c->dummy_port; - + if (res && res->type != INTERFACE_Port) + res = NULL; pw_log_debug("%p: port %d -> %p", c, port_id, res); + pthread_mutex_unlock(&c->context.lock); + + if (res == NULL) + pw_log_info("%p: port %d not found", c, port_id); return object_to_port(res); } 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/sl.po b/po/sl.po index cf92eaffc..ae85f043f 100644 --- a/po/sl.po +++ b/po/sl.po @@ -2,21 +2,23 @@ # Copyright (C) 2024 PipeWire's COPYRIGHT HOLDER # This file is distributed under the same license as the PipeWire package. # -# Martin , 2024, 2025, 2026. +# Martin , 2024, 2025. # msgid "" msgstr "" "Project-Id-Version: PipeWire master\n" -"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues\n" -"POT-Creation-Date: 2026-02-09 12:55+0000\n" -"PO-Revision-Date: 2026-02-15 16:18+0100\n" +"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" +"issues\n" +"POT-Creation-Date: 2025-12-04 15:34+0000\n" +"PO-Revision-Date: 2025-12-07 08:53+0100\n" "Last-Translator: Martin Srebotnjak \n" "Language-Team: Slovenian \n" "Language: sl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100>=3 && n%100<=4 ? 2 : 3);\n" +"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100>=3 && n" +"%100<=4 ? 2 : 3);\n" "X-Generator: Poedit 2.2.1\n" #: src/daemon/pipewire.c:29 @@ -33,7 +35,8 @@ msgstr "" " -h, --help Pokaži to pomoč\n" " -v, --verbose Povečaj opisnost za eno raven\n" " --version Pokaži različico\n" -" -c, --config Naloži prilagoditev config (privzeto %s)\n" +" -c, --config Naloži prilagoditev config (privzeto " +"%s)\n" " -P --properties Določi lastnosti konteksta\n" #: src/daemon/pipewire.desktop.in:3 @@ -59,46 +62,21 @@ msgstr "Lažni izhod" msgid "Tunnel for %s@%s" msgstr "Prehod za %s@%s" -#: src/modules/module-zeroconf-discover.c:326 +#: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "Neznana naprava" -#: src/modules/module-zeroconf-discover.c:338 +#: src/modules/module-zeroconf-discover.c:332 #, c-format msgid "%s on %s@%s" msgstr "%s na %s@%s" -#: src/modules/module-zeroconf-discover.c:342 +#: src/modules/module-zeroconf-discover.c:336 #, c-format msgid "%s on %s" msgstr "%s na %s" -#: src/tools/pw-cat.c:264 -#, c-format -msgid "Supported formats:\n" -msgstr "Podprti zapisi:\n" - -#: src/tools/pw-cat.c:749 -#, c-format -msgid "Supported channel layouts:\n" -msgstr "Podprte postavitve kanalov:\n" - -#: src/tools/pw-cat.c:759 -#, c-format -msgid "Supported channel layout aliases:\n" -msgstr "Podprti vzdevki postavitev kanalov:\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 "Podprta imena kanalov:\n" - -#: src/tools/pw-cat.c:1177 +#: src/tools/pw-cat.c:1103 #, c-format msgid "" "%s [options] [|-]\n" @@ -114,7 +92,7 @@ msgstr "" "\n" "\n" -#: src/tools/pw-cat.c:1184 +#: src/tools/pw-cat.c:1110 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -151,23 +129,20 @@ msgstr "" " -P --properties Nastavi lastnosti vozlišča\n" "\n" -#: src/tools/pw-cat.c:1202 +#: src/tools/pw-cat.c:1128 #, 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\", \"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" +" --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" @@ -186,13 +161,8 @@ msgstr "" "\"5.1\",... ali\n" " seznam imen kanalov, ločen z " "vejico: npr. \"FL,FR\"\n" -" —list-layouts Izpiše podprte postavitve kanalov\n" -" —list-channel-names Izpiše podprte preslikave kanalov\n" -" --format Oblika zapisa vzorcev (privzeto %s)\n" -" —list-formats Izpiše podprte zapise vzorcev\n" -" —container Oblika vsebnika\n" -" —list-containers Seznam podprtih vsebnikov in " -"razširitev\n" +" --format Vzorčne oblike zapisa %s (zahtevano " +"za rec) (privzeto %s)\n" " --volume Glasnost toka 0-1.0 (privzeto %.3f)\n" " -q --quality Kakovost prevzorčenja (0 - 15) " "(privzeto %d)\n" @@ -202,7 +172,7 @@ msgstr "" " -n, --sample-count ŠTEVEC Ustavi po ŠTEVEC vzorcih\n" "\n" -#: src/tools/pw-cat.c:1227 +#: src/tools/pw-cat.c:1148 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" @@ -222,11 +192,6 @@ msgstr "" " -c, --midi-clip Način posnetka MIDI\n" "\n" -#: src/tools/pw-cat.c:1827 -#, c-format -msgid "Supported containers and extensions:\n" -msgstr "Podprti vsebniki in razširitve:\n" - #: src/tools/pw-cli.c:2386 #, c-format msgid "" @@ -252,7 +217,7 @@ msgid "Pro Audio" msgstr "Profesionalni zvok" #: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699 -#: spa/plugins/bluez5/bluez5-device.c:2021 +#: spa/plugins/bluez5/bluez5-device.c:1976 msgid "Off" msgstr "Izklopljeno" @@ -284,7 +249,7 @@ msgstr "Linijski vhod" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 #: spa/plugins/alsa/acp/alsa-mixer.c:2810 -#: spa/plugins/bluez5/bluez5-device.c:2422 +#: spa/plugins/bluez5/bluez5-device.c:2374 msgid "Microphone" msgstr "Mikrofon" @@ -350,15 +315,15 @@ msgid "No Bass Boost" msgstr "Brez ojačitve nizkih tonov" #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:2428 +#: spa/plugins/bluez5/bluez5-device.c:2380 msgid "Speaker" msgstr "Zvočnik" #. Don't call it "headset", the HF one has the mic #: spa/plugins/alsa/acp/alsa-mixer.c:2742 #: spa/plugins/alsa/acp/alsa-mixer.c:2820 -#: spa/plugins/bluez5/bluez5-device.c:2434 -#: spa/plugins/bluez5/bluez5-device.c:2501 +#: spa/plugins/bluez5/bluez5-device.c:2386 +#: spa/plugins/bluez5/bluez5-device.c:2453 msgid "Headphones" msgstr "Slušalke" @@ -468,7 +433,7 @@ 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/bluez5/bluez5-device.c:2362 msgid "Headset" msgstr "Slušalka" @@ -715,100 +680,100 @@ msgstr "Vgrajen zvok" msgid "Modem" msgstr "Modem" -#: spa/plugins/bluez5/bluez5-device.c:2032 +#: spa/plugins/bluez5/bluez5-device.c:1987 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "Zvožni prehod (vir A2DP in HSP/HFP AG)" -#: spa/plugins/bluez5/bluez5-device.c:2061 +#: spa/plugins/bluez5/bluez5-device.c:2016 msgid "Audio Streaming for Hearing Aids (ASHA Sink)" msgstr "Pretakanje zvoka za slušne aparate (ponor ASHA)" -#: spa/plugins/bluez5/bluez5-device.c:2104 +#: spa/plugins/bluez5/bluez5-device.c:2059 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "Predvajanje visoke ločljivosti (ponor A2DP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:2107 +#: spa/plugins/bluez5/bluez5-device.c:2062 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "Dupleks visoke ločljivosti (vir/ponor A2DP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:2115 +#: spa/plugins/bluez5/bluez5-device.c:2070 msgid "High Fidelity Playback (A2DP Sink)" msgstr "Predvajanje visoke ločljivosti (ponor A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:2117 +#: spa/plugins/bluez5/bluez5-device.c:2072 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "Dupleks visoke ločljivosti (vir/ponor A2DP)" -#: spa/plugins/bluez5/bluez5-device.c:2194 +#: spa/plugins/bluez5/bluez5-device.c:2146 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "Predvajanje visoke ločljivosti (ponor BAP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:2199 +#: spa/plugins/bluez5/bluez5-device.c:2151 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "Vhod visoke ločljivosti (vir BAP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:2203 +#: spa/plugins/bluez5/bluez5-device.c:2155 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "Dupleks visoke ločljivosti (vir/ponor BAP, kodek %s)" -#: spa/plugins/bluez5/bluez5-device.c:2212 +#: spa/plugins/bluez5/bluez5-device.c:2164 msgid "High Fidelity Playback (BAP Sink)" msgstr "Predvajanje visoke ločljivosti (ponor BAP)" -#: spa/plugins/bluez5/bluez5-device.c:2216 +#: spa/plugins/bluez5/bluez5-device.c:2168 msgid "High Fidelity Input (BAP Source)" msgstr "Vhod visoke ločljivosti (vir BAP)" -#: spa/plugins/bluez5/bluez5-device.c:2219 +#: spa/plugins/bluez5/bluez5-device.c:2171 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "Dupleks visoke ločljivosti (vir/ponor BAP)" -#: spa/plugins/bluez5/bluez5-device.c:2259 +#: spa/plugins/bluez5/bluez5-device.c:2211 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "Naglavna enota slušalk (HSP/HFP, kodek %s)" +#: spa/plugins/bluez5/bluez5-device.c:2363 +#: spa/plugins/bluez5/bluez5-device.c:2368 +#: spa/plugins/bluez5/bluez5-device.c:2375 +#: spa/plugins/bluez5/bluez5-device.c:2381 +#: spa/plugins/bluez5/bluez5-device.c:2387 +#: spa/plugins/bluez5/bluez5-device.c:2393 +#: spa/plugins/bluez5/bluez5-device.c:2399 +#: spa/plugins/bluez5/bluez5-device.c:2405 #: 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 "Prostoročno telefoniranje" -#: spa/plugins/bluez5/bluez5-device.c:2417 +#: spa/plugins/bluez5/bluez5-device.c:2369 msgid "Handsfree (HFP)" msgstr "Prostoročno telefoniranje (HFP)" -#: spa/plugins/bluez5/bluez5-device.c:2440 +#: spa/plugins/bluez5/bluez5-device.c:2392 msgid "Portable" msgstr "Prenosna naprava" -#: spa/plugins/bluez5/bluez5-device.c:2446 +#: spa/plugins/bluez5/bluez5-device.c:2398 msgid "Car" msgstr "Avtomobil" -#: spa/plugins/bluez5/bluez5-device.c:2452 +#: spa/plugins/bluez5/bluez5-device.c:2404 msgid "HiFi" msgstr "HiFi" -#: spa/plugins/bluez5/bluez5-device.c:2458 +#: spa/plugins/bluez5/bluez5-device.c:2410 msgid "Phone" msgstr "Telefon" -#: spa/plugins/bluez5/bluez5-device.c:2465 +#: spa/plugins/bluez5/bluez5-device.c:2417 msgid "Bluetooth" msgstr "Bluetooth" -#: spa/plugins/bluez5/bluez5-device.c:2466 +#: spa/plugins/bluez5/bluez5-device.c:2418 msgid "Bluetooth Handsfree" msgstr "Bluetooth - prostoročno" 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 3c82c60e7..4181ef3df 100644 --- a/po/zh_CN.po +++ b/po/zh_CN.po @@ -6,15 +6,15 @@ # Cheng-Chia Tseng , 2010, 2012. # Frank Hill , 2015. # Mingye Wang (Arthur2e5) , 2015. -# lumingzh , 2024-2026. +# lumingzh , 2024-2025. # msgid "" msgstr "" "Project-Id-Version: pipewire.master-tx\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "issues\n" -"POT-Creation-Date: 2026-03-19 15:38+0000\n" -"PO-Revision-Date: 2026-03-23 08:48+0800\n" +"POT-Creation-Date: 2025-11-25 15:35+0000\n" +"PO-Revision-Date: 2025-11-26 10:19+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,21 @@ msgstr "虚拟输出" msgid "Tunnel for %s@%s" msgstr "用于 %s@%s 的隧道" -#: src/modules/module-zeroconf-discover.c:290 +#: src/modules/module-zeroconf-discover.c:320 msgid "Unknown device" msgstr "未知设备" -#: src/modules/module-zeroconf-discover.c:302 +#: src/modules/module-zeroconf-discover.c:332 #, 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:336 #, c-format msgid "%s on %s" msgstr "%2$s 上的 %1$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:1088 #, c-format msgid "" "%s [options] [|-]\n" @@ -119,7 +94,7 @@ msgstr "" " -v, --verbose 输出详细操作\n" "\n" -#: src/tools/pw-cat.c:1189 +#: src/tools/pw-cat.c:1095 #, c-format msgid "" " -R, --remote Remote daemon name\n" @@ -151,23 +126,20 @@ msgstr "" " -P --properties 设置节点属性\n" "\n" -#: src/tools/pw-cat.c:1207 +#: src/tools/pw-cat.c:1113 #, 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\", \"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" +" --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" @@ -177,19 +149,15 @@ msgid "" " -n, --sample-count COUNT Stop after COUNT samples\n" "\n" msgstr "" -" --rate 采样率 (默认 %u)\n" -" --channels 声道数 (默认 %u)\n" -" --channel-map 声道映射\n" -" 声道布局:\"stereo\", " -"\"5.1\",... 或\n" -" 以英文逗号分隔的声道名列表: 如 " +" --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" +" --format 采样格式 %s (录制模式需要) (默认 " +"%s)\n" " --volume 媒体流音量 0-1.0 (默认 %.3f)\n" " -q --quality 重采样质量 (0 - 15) (默认 %d)\n" " -a, --raw 原生模式\n" @@ -198,7 +166,7 @@ msgstr "" " -n, --sample-count COUNT 计数采样后停止\n" "\n" -#: src/tools/pw-cat.c:1232 +#: src/tools/pw-cat.c:1133 msgid "" " -p, --playback Playback mode\n" " -r, --record Recording mode\n" @@ -218,11 +186,6 @@ msgstr "" " -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 #, c-format msgid "" @@ -245,12 +208,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:2165 +#: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699 +#: spa/plugins/bluez5/bluez5-device.c:1976 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 错误]" @@ -278,7 +241,7 @@ msgstr "输入插孔" #: spa/plugins/alsa/acp/alsa-mixer.c:2726 #: spa/plugins/alsa/acp/alsa-mixer.c:2810 -#: spa/plugins/bluez5/bluez5-device.c:2598 +#: spa/plugins/bluez5/bluez5-device.c:2374 msgid "Microphone" msgstr "话筒" @@ -344,15 +307,15 @@ msgid "No Bass Boost" msgstr "无重低音增强" #: spa/plugins/alsa/acp/alsa-mixer.c:2741 -#: spa/plugins/bluez5/bluez5-device.c:2604 +#: spa/plugins/bluez5/bluez5-device.c:2380 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:2610 -#: spa/plugins/bluez5/bluez5-device.c:2677 +#: spa/plugins/bluez5/bluez5-device.c:2386 +#: spa/plugins/bluez5/bluez5-device.c:2453 msgid "Headphones" msgstr "模拟耳机" @@ -462,7 +425,7 @@ msgstr "立体声" #: spa/plugins/alsa/acp/alsa-mixer.c:4535 #: spa/plugins/alsa/acp/alsa-mixer.c:4693 -#: spa/plugins/bluez5/bluez5-device.c:2586 +#: spa/plugins/bluez5/bluez5-device.c:2362 msgid "Headset" msgstr "耳机" @@ -657,109 +620,101 @@ msgstr "内置音频" msgid "Modem" msgstr "调制解调器" -#: spa/plugins/bluez5/bluez5-device.c:2176 +#: spa/plugins/bluez5/bluez5-device.c:1987 msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgstr "音频网关 (A2DP 信源 或 HSP/HFP 网关)" -#: spa/plugins/bluez5/bluez5-device.c:2205 +#: spa/plugins/bluez5/bluez5-device.c:2016 msgid "Audio Streaming for Hearing Aids (ASHA Sink)" msgstr "助听器音频流 (ASHA 信宿)" -#: spa/plugins/bluez5/bluez5-device.c:2248 +#: spa/plugins/bluez5/bluez5-device.c:2059 #, c-format msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgstr "高保真回放 (A2DP 信宿, 编码 %s)" -#: spa/plugins/bluez5/bluez5-device.c:2251 +#: spa/plugins/bluez5/bluez5-device.c:2062 #, c-format msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgstr "高保真双工 (A2DP 信源/信宿, 编码 %s)" -#: spa/plugins/bluez5/bluez5-device.c:2259 +#: spa/plugins/bluez5/bluez5-device.c:2070 msgid "High Fidelity Playback (A2DP Sink)" msgstr "高保真回放 (A2DP 信宿)" -#: spa/plugins/bluez5/bluez5-device.c:2261 +#: spa/plugins/bluez5/bluez5-device.c:2072 msgid "High Fidelity Duplex (A2DP Source/Sink)" msgstr "高保真双工 (A2DP 信源/信宿)" -#: spa/plugins/bluez5/bluez5-device.c:2283 -msgid "Auto: Prefer Quality (A2DP)" -msgstr "自动:质量优先 (A2DP)" - -#: spa/plugins/bluez5/bluez5-device.c:2287 -msgid "Auto: Prefer Latency (A2DP)" -msgstr "自动:延迟优先 (A2DP)" - -#: spa/plugins/bluez5/bluez5-device.c:2368 +#: spa/plugins/bluez5/bluez5-device.c:2146 #, c-format msgid "High Fidelity Playback (BAP Sink, codec %s)" msgstr "高保真回放 (BAP 信宿, 编码 %s)" -#: spa/plugins/bluez5/bluez5-device.c:2373 +#: spa/plugins/bluez5/bluez5-device.c:2151 #, c-format msgid "High Fidelity Input (BAP Source, codec %s)" msgstr "高保真输入 (BAP 信源, 编码 %s)" -#: spa/plugins/bluez5/bluez5-device.c:2377 +#: spa/plugins/bluez5/bluez5-device.c:2155 #, c-format msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgstr "高保真双工 (BAP 信源/信宿, 编码 %s)" -#: spa/plugins/bluez5/bluez5-device.c:2386 +#: spa/plugins/bluez5/bluez5-device.c:2164 msgid "High Fidelity Playback (BAP Sink)" msgstr "高保真回放 (BAP 信宿)" -#: spa/plugins/bluez5/bluez5-device.c:2390 +#: spa/plugins/bluez5/bluez5-device.c:2168 msgid "High Fidelity Input (BAP Source)" msgstr "高保真输入 (BAP 信源)" -#: spa/plugins/bluez5/bluez5-device.c:2393 +#: spa/plugins/bluez5/bluez5-device.c:2171 msgid "High Fidelity Duplex (BAP Source/Sink)" msgstr "高保真双工 (BAP 信源/信宿)" -#: spa/plugins/bluez5/bluez5-device.c:2433 +#: spa/plugins/bluez5/bluez5-device.c:2211 #, c-format msgid "Headset Head Unit (HSP/HFP, codec %s)" msgstr "头戴式耳机单元 (HSP/HFP, 编码 %s)" -#: spa/plugins/bluez5/bluez5-device.c:2587 -#: spa/plugins/bluez5/bluez5-device.c:2592 -#: spa/plugins/bluez5/bluez5-device.c:2599 -#: spa/plugins/bluez5/bluez5-device.c:2605 -#: spa/plugins/bluez5/bluez5-device.c:2611 -#: spa/plugins/bluez5/bluez5-device.c:2617 -#: spa/plugins/bluez5/bluez5-device.c:2623 -#: spa/plugins/bluez5/bluez5-device.c:2629 -#: spa/plugins/bluez5/bluez5-device.c:2635 +#: spa/plugins/bluez5/bluez5-device.c:2363 +#: spa/plugins/bluez5/bluez5-device.c:2368 +#: spa/plugins/bluez5/bluez5-device.c:2375 +#: spa/plugins/bluez5/bluez5-device.c:2381 +#: spa/plugins/bluez5/bluez5-device.c:2387 +#: spa/plugins/bluez5/bluez5-device.c:2393 +#: spa/plugins/bluez5/bluez5-device.c:2399 +#: spa/plugins/bluez5/bluez5-device.c:2405 +#: spa/plugins/bluez5/bluez5-device.c:2411 msgid "Handsfree" msgstr "免提" -#: spa/plugins/bluez5/bluez5-device.c:2593 +#: spa/plugins/bluez5/bluez5-device.c:2369 msgid "Handsfree (HFP)" msgstr "免提(HFP)" -#: spa/plugins/bluez5/bluez5-device.c:2616 +#: spa/plugins/bluez5/bluez5-device.c:2392 msgid "Portable" msgstr "便携式" -#: spa/plugins/bluez5/bluez5-device.c:2622 +#: spa/plugins/bluez5/bluez5-device.c:2398 msgid "Car" msgstr "车内" -#: spa/plugins/bluez5/bluez5-device.c:2628 +#: spa/plugins/bluez5/bluez5-device.c:2404 msgid "HiFi" msgstr "高保真" -#: spa/plugins/bluez5/bluez5-device.c:2634 +#: spa/plugins/bluez5/bluez5-device.c:2410 msgid "Phone" msgstr "电话" -#: spa/plugins/bluez5/bluez5-device.c:2641 +#: spa/plugins/bluez5/bluez5-device.c:2417 msgid "Bluetooth" msgstr "蓝牙" -#: spa/plugins/bluez5/bluez5-device.c:2642 +#: spa/plugins/bluez5/bluez5-device.c:2418 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/90-pipewire-alsa.rules b/spa/plugins/alsa/90-pipewire-alsa.rules index 8c986d070..b2e1f6886 100644 --- a/spa/plugins/alsa/90-pipewire-alsa.rules +++ b/spa/plugins/alsa/90-pipewire-alsa.rules @@ -187,11 +187,6 @@ ATTRS{idVendor}=="1395", ATTRS{idProduct}=="0300", ENV{ACP_PROFILE_SET}="usb-gam # Sennheiser GSP 670 USB headset ATTRS{idVendor}=="1395", ATTRS{idProduct}=="008a", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" -# JBL Quantum One -ATTRS{idVendor}=="0ecb", ATTRS{idProduct}=="203a", ENV{ACP_PROFILE_SET}="usb-gaming-headset-gamefirst.conf" -# JBL Quantum 810 Wireless -ATTRS{idVendor}=="0ecb", ATTRS{idProduct}=="2069", ENV{ACP_PROFILE_SET}="usb-gaming-headset-gamefirst.conf" - # Audioengine HD3 powered speakers support IEC958 but don't actually # have any digital outputs. ATTRS{idVendor}=="0a12", ATTRS{idProduct}=="4007", ENV{ACP_PROFILE_SET}="analog-only.conf" 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/acp.c b/spa/plugins/alsa/acp/acp.c index 8f5ea18c2..bee8d1ef4 100644 --- a/spa/plugins/alsa/acp/acp.c +++ b/spa/plugins/alsa/acp/acp.c @@ -485,11 +485,13 @@ static int add_pro_profile(pa_card *impl, uint32_t index) if ((n_capture == 1 && n_playback == 1) || is_firewire) { PA_IDXSET_FOREACH(m, ap->output_mappings, idx) { pa_proplist_setf(m->output_proplist, "node.group", "pro-audio-%u", index); + pa_proplist_setf(m->output_proplist, "node.link-group", "pro-audio-%u", index); pa_proplist_setf(m->output_proplist, "api.alsa.auto-link", "true"); pa_proplist_setf(m->output_proplist, "api.alsa.disable-tsched", "true"); } PA_IDXSET_FOREACH(m, ap->input_mappings, idx) { pa_proplist_setf(m->input_proplist, "node.group", "pro-audio-%u", index); + pa_proplist_setf(m->input_proplist, "node.link-group", "pro-audio-%u", index); pa_proplist_setf(m->input_proplist, "api.alsa.auto-link", "true"); pa_proplist_setf(m->input_proplist, "api.alsa.disable-tsched", "true"); } diff --git a/spa/plugins/alsa/acp/compat.h b/spa/plugins/alsa/acp/compat.h index 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-pcm.c b/spa/plugins/alsa/alsa-pcm.c index ca31366dc..a12b2601a 100644 --- a/spa/plugins/alsa/alsa-pcm.c +++ b/spa/plugins/alsa/alsa-pcm.c @@ -3040,17 +3040,10 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram } if (state->rate_match) { - /* Only set rate_match rate when matching is active. When not matching, - * set it to 1.0 to indicate no rate adjustment needed, even though DLL - * may still be running for buffer level management. */ - if (state->matching) { - if (state->stream == SND_PCM_STREAM_PLAYBACK) - state->rate_match->rate = corr; - else - state->rate_match->rate = 1.0/corr; - } else { - state->rate_match->rate = 1.0; - } + if (state->stream == SND_PCM_STREAM_PLAYBACK) + state->rate_match->rate = corr; + else + state->rate_match->rate = 1.0/corr; if (state->pitch_elem && state->matching) spa_alsa_update_rate_match(state); diff --git a/spa/plugins/alsa/alsa-seq-bridge.c b/spa/plugins/alsa/alsa-seq-bridge.c index d5b0019a8..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; @@ -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, @@ -504,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; @@ -528,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: @@ -547,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: @@ -975,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..7d1ae9a3a 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) { @@ -626,9 +538,6 @@ static int emit_added_object_info(struct impl *this, struct card *card) if ((str = udev_device_get_property_value(udev_device, "ACP_PROFILE_SET")) && *str) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_PROFILE_SET, str); - if ((str = udev_device_get_property_value(udev_device, "ACP_IGNORE_DB")) && *str) - items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_API_ALSA_IGNORE_DB, str); - if ((str = udev_device_get_property_value(udev_device, "SOUND_CLASS")) && *str) items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_DEVICE_CLASS, str); @@ -816,17 +725,13 @@ static bool check_access(struct impl *this, struct card *card) static void process_card(struct impl *this, enum action action, struct card *card) { + if (card->ignored) + return; + switch (action) { case ACTION_CHANGE: { - if (card->ignored) - 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 +750,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 +787,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 +915,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 +938,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 +955,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/profile-sets/usb-gaming-headset-gamefirst.conf b/spa/plugins/alsa/mixer/profile-sets/usb-gaming-headset-gamefirst.conf deleted file mode 100644 index 9192c6864..000000000 --- a/spa/plugins/alsa/mixer/profile-sets/usb-gaming-headset-gamefirst.conf +++ /dev/null @@ -1,70 +0,0 @@ -# This file is part of PulseAudio. -# -# PulseAudio is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as -# published by the Free Software Foundation; either version 2.1 of the -# License, or (at your option) any later version. -# -# PulseAudio is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with PulseAudio; if not, see . - -; USB gaming headset. -; These headsets usually have two output devices. The first one is meant -; for general audio, and the second one is meant for chat. There is also -; a single input device for chat. -; The purpose of this unusual design is to provide separate volume -; controls for voice and other audio, which can be useful in gaming. -; -; Works with: -; JBL Quantum 810 Wireless -; JBL Quantum One -; -; Based on usb-gaming-headset.conf. -; -; See default.conf for an explanation on the directives used here. - -[General] -auto-profiles = yes - -[Mapping mono-chat-output] -description-key = gaming-headset-chat -device-strings = hw:%f,1,0 -channel-map = mono -paths-output = usb-gaming-headset-output-mono -intended-roles = phone - -[Mapping stereo-chat-output] -description-key = gaming-headset-chat -device-strings = hw:%f,1,0 -channel-map = left,right -paths-output = usb-gaming-headset-output-stereo -intended-roles = phone - -[Mapping mono-chat-input] -description-key = gaming-headset-chat -device-strings = hw:%f,0,0 -channel-map = mono -paths-input = usb-gaming-headset-input -intended-roles = phone - -[Mapping stereo-game-output] -description-key = gaming-headset-game -device-strings = hw:%f,0,0 -channel-map = left,right -paths-output = usb-gaming-headset-output-stereo -direction = output - -[Profile output:mono-chat+output:stereo-game+input:mono-chat] -output-mappings = mono-chat-output stereo-game-output -input-mappings = mono-chat-input -priority = 5100 - -[Profile output:stereo-game+output:stereo-chat+input:mono-chat] -output-mappings = stereo-game-output stereo-chat-output -input-mappings = mono-chat-input -priority = 5100 diff --git a/spa/plugins/audioconvert/audioconvert.c b/spa/plugins/audioconvert/audioconvert.c index 482200854..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; @@ -4284,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); @@ -4296,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); @@ -4354,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..4aa9e58c8 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; i < CHANNEL_BITS; i++) { float sum = 0.0f; char str1[1024], str2[1024]; struct spa_strbuf sb1, sb2; @@ -733,10 +728,12 @@ done: spa_strbuf_init(&sb1, str1, sizeof(str1)); spa_strbuf_init(&sb2, str2, sizeof(str2)); - if (i < CHANNEL_BITS && (dst_paired & (1UL << i)) == 0) + if ((dst_paired & (1UL << i)) == 0) continue; - for (jc = 0, j = 0; jc < src_chan && j < MAX_CHANNELS; j++) { - if (j < CHANNEL_BITS && (src_paired & (1UL << j)) == 0) + for (jc = 0, j = 0; j < CHANNEL_BITS; j++) { + if ((src_paired & (1UL << j)) == 0) + continue; + if (ic >= dst_chan || jc >= src_chan) continue; if (ic == 0) @@ -755,7 +752,7 @@ done: if (sb2.pos > 0) spa_log_info(mix->log, " %s", str2); if (sb1.pos > 0) { - spa_log_info(mix->log, "%03d %-4.4s %s %f", ic, + spa_log_info(mix->log, "%-4.4s %s %f", dst_mask == 0 ? "UNK" : spa_debug_type_find_short_name(spa_type_audio_channel, i + _SH), str1, sum); @@ -828,6 +825,7 @@ static void impl_channelmix_set_volume(struct channelmix *mix, float volume, boo for (i = 0; i < dst_chan; i++) { for (j = 0; j < src_chan; j++) { float v = mix->matrix[i][j]; + spa_log_debug(mix->log, "%d %d: %f", i, j, v); if (i == 0 && j == 0) t = v; else if (t != v) @@ -842,31 +840,7 @@ static void impl_channelmix_set_volume(struct channelmix *mix, float volume, boo SPA_FLAG_UPDATE(mix->flags, CHANNELMIX_FLAG_IDENTITY, dst_chan == src_chan && SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_COPY)); - if (SPA_UNLIKELY(spa_log_level_topic_enabled(mix->log, - SPA_LOG_TOPIC_DEFAULT, SPA_LOG_LEVEL_DEBUG))) { - char str1[1024], str2[1024]; - struct spa_strbuf sb1, sb2; - spa_strbuf_init(&sb2, str2, sizeof(str2)); - for (i = 0; i < dst_chan; i++) { - spa_strbuf_init(&sb1, str1, sizeof(str1)); - for (j = 0; j < src_chan; j++) { - float v = mix->matrix[i][j]; - if (i == 0) - spa_strbuf_append(&sb2, " %03d ", j); - if (v == 0.0f) - spa_strbuf_append(&sb1, " "); - else - spa_strbuf_append(&sb1, "%1.3f ", v); - } - if (i == 0 && sb2.pos > 0) - spa_log_debug(mix->log, " %s", str2); - if (sb1.pos > 0) - spa_log_debug(mix->log, "%03d %s %03d", i, str1, i); - } - if (sb2.pos > 0) - spa_log_debug(mix->log, " %s", str2); - spa_log_debug(mix->log, "flags:%08x", mix->flags); - } + spa_log_debug(mix->log, "flags:%08x", mix->flags); } static void impl_channelmix_free(struct channelmix *mix) @@ -874,21 +848,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 +864,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/audiomixer/audiomixer.c b/spa/plugins/audiomixer/audiomixer.c index 54549bce8..99b0658a0 100644 --- a/spa/plugins/audiomixer/audiomixer.c +++ b/spa/plugins/audiomixer/audiomixer.c @@ -725,7 +725,7 @@ static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq, port->io[0] = info->data; port->io[1] = info->data; } - if (port->direction == SPA_DIRECTION_INPUT && !port->active) { + if (!port->active) { spa_list_append(&info->impl->mix_list, &port->mix_link); port->active = true; } diff --git a/spa/plugins/audiomixer/mixer-dsp.c b/spa/plugins/audiomixer/mixer-dsp.c index 698426d21..5bd4e1a1e 100644 --- a/spa/plugins/audiomixer/mixer-dsp.c +++ b/spa/plugins/audiomixer/mixer-dsp.c @@ -718,7 +718,7 @@ static int do_port_set_io(struct spa_loop *loop, bool async, uint32_t seq, port->io[0] = info->data; port->io[1] = info->data; } - if (port->direction == SPA_DIRECTION_INPUT && !port->active) { + if (!port->active) { spa_list_append(&info->impl->mix_list, &port->mix_link); port->active = true; } diff --git a/spa/plugins/audiotestsrc/audiotestsrc.c b/spa/plugins/audiotestsrc/audiotestsrc.c index 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..5c4a2b785 100644 --- a/spa/plugins/bluez5/backend-native.c +++ b/spa/plugins/bluez5/backend-native.c @@ -63,9 +63,6 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.native"); #define RFCOMM_MESSAGE_MAX_LENGTH 256 -#define BT_CODEC_CVSD 0x02 -#define BT_CODEC_MSBC 0x05 - enum { HFP_AG_INITIAL_CODEC_SETUP_NONE = 0, HFP_AG_INITIAL_CODEC_SETUP_SEND, @@ -115,7 +112,6 @@ struct impl { int hfp_default_speaker_volume; struct spa_source sco; - unsigned int hfphsp_sco_datapath; const struct spa_bt_quirks *quirks; @@ -301,33 +297,6 @@ static const struct media_codec *codec_list_best(struct impl *backend, struct sp return NULL; } -static void sco_offload_btcodec(struct impl *backend, int sock, bool msbc) -{ - int err; - char buffer[255]; - struct bt_codecs *codecs; - - if (backend->hfphsp_sco_datapath == HFP_SCO_DEFAULT_DATAPATH) - return; - - spa_log_info(backend->log, "sock(%d) msbc(%d)", sock, msbc); - - memset(buffer, 0, sizeof(buffer)); - codecs = (void *)buffer; - if (msbc) - codecs->codecs[0].id = BT_CODEC_MSBC; - else - codecs->codecs[0].id = BT_CODEC_CVSD; - codecs->num_codecs = 1; - codecs->codecs[0].data_path_id = backend->hfphsp_sco_datapath; - - err = setsockopt(sock, SOL_BLUETOOTH, BT_CODEC, codecs, sizeof(buffer)); - if (err < 0) - spa_log_error(backend->log, "ERROR: %s (%d)", strerror(errno), errno); - else - spa_log_info(backend->log, "set offload codec succeeded"); -} - static DBusHandlerResult profile_release(DBusConnection *conn, DBusMessage *m, void *userdata) { if (!reply_with_error(conn, m, BLUEZ_PROFILE_INTERFACE ".Error.NotImplemented", "Method not implemented")) @@ -1969,9 +1938,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 +2066,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 +2214,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 +2225,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 +2242,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 +2390,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 +2447,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) { @@ -2635,8 +2595,6 @@ static int sco_create_socket(struct impl *backend, struct spa_bt_adapter *adapte } } - sco_offload_btcodec(backend, sock, transparent); - return spa_steal_fd(sock); } @@ -4143,14 +4101,6 @@ static void parse_hfp_default_volumes(struct impl *backend, const struct spa_dic backend->hfp_default_speaker_volume = SPA_BT_VOLUME_HS_MAX; } -static void parse_sco_datapath(struct impl *backend, const struct spa_dict *info) -{ - backend->hfphsp_sco_datapath = HFP_SCO_DEFAULT_DATAPATH; - - spa_atou32(spa_dict_lookup(info, "bluez5.hw-offload-datapath"), - &backend->hfphsp_sco_datapath, 10); -} - static const struct spa_bt_backend_implementation backend_impl = { SPA_VERSION_BT_BACKEND_IMPLEMENTATION, .free = backend_native_free, @@ -4213,7 +4163,6 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor, parse_hfp_disable_nrec(backend, info); parse_hfp_default_volumes(backend, info); parse_hfp_pts(backend, info); - parse_sco_datapath(backend, info); #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE if (!dbus_connection_register_object_path(backend->conn, diff --git a/spa/plugins/bluez5/bap-codec-lc3.c b/spa/plugins/bluez5/bap-codec-lc3.c index 881af4e14..be08fd5a5 100644 --- a/spa/plugins/bluez5/bap-codec-lc3.c +++ b/spa/plugins/bluez5/bap-codec-lc3.c @@ -55,7 +55,6 @@ struct settings { int latency; int64_t delay; int framing; - char *force_target_latency; }; struct pac_data { @@ -77,7 +76,6 @@ struct bap_qos { uint16_t latency; uint32_t delay; unsigned int priority; - char *tag; }; typedef struct { @@ -98,50 +96,50 @@ struct config_data { struct settings settings; }; -#define BAP_QOS(name_, rate_, duration_, framing_, framelen_, rtn_, latency_, delay_, priority_, tag_) \ +#define BAP_QOS(name_, rate_, duration_, framing_, framelen_, rtn_, latency_, delay_, priority_) \ ((struct bap_qos){ .name = (name_), .rate = (rate_), .frame_duration = (duration_), .framing = (framing_), \ .framelen = (framelen_), .retransmission = (rtn_), .latency = (latency_), \ - .delay = (delay_), .priority = (priority_), .tag = (tag_) }) + .delay = (delay_), .priority = (priority_) }) static const struct bap_qos bap_qos_configs[] = { /* Priority: low-latency > high-reliability, 7.5ms > 10ms, * bigger frequency and sdu better */ /* BAP v1.0.1 Table 5.2; low-latency */ - BAP_QOS("8_1_1", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_7_5, false, 26, 2, 8, 40000, 30, "low-latency"), /* 8_1_1 */ - BAP_QOS("8_2_1", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_10, false, 30, 2, 10, 40000, 20, "low-latency"), /* 8_2_1 */ - BAP_QOS("16_1_1", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_7_5, false, 30, 2, 8, 40000, 31, "low-latency"), /* 16_1_1 */ - BAP_QOS("16_2_1", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_10, false, 40, 2, 10, 40000, 21, "low-latency"), /* 16_2_1 (mandatory) */ - BAP_QOS("24_1_1", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_7_5, false, 45, 2, 8, 40000, 32, "low-latency"), /* 24_1_1 */ - BAP_QOS("24_2_1", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_10, false, 60, 2, 10, 40000, 22, "low-latency"), /* 24_2_1 */ - BAP_QOS("32_1_1", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_7_5, false, 60, 2, 8, 40000, 33, "low-latency"), /* 32_1_1 */ - BAP_QOS("32_2_1", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_10, false, 80, 2, 10, 40000, 23, "low-latency"), /* 32_2_1 */ - BAP_QOS("441_1_1", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_7_5, true, 97, 5, 24, 40000, 34, "low-latency"), /* 441_1_1 */ - BAP_QOS("441_2_1", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_10, true, 130, 5, 31, 40000, 24, "low-latency"), /* 441_2_1 */ - BAP_QOS("48_1_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 75, 5, 15, 40000, 35, "low-latency"), /* 48_1_1 */ - BAP_QOS("48_2_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 100, 5, 20, 40000, 25, "low-latency"), /* 48_2_1 */ - BAP_QOS("48_3_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 90, 5, 15, 40000, 36, "low-latency"), /* 48_3_1 */ - BAP_QOS("48_4_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 120, 5, 20, 40000, 26, "low-latency"), /* 48_4_1 */ - BAP_QOS("48_5_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 117, 5, 15, 40000, 37, "low-latency"), /* 48_5_1 */ - 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_QOS("8_1_1", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_7_5, false, 26, 2, 8, 40000, 30), /* 8_1_1 */ + BAP_QOS("8_2_1", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_10, false, 30, 2, 10, 40000, 20), /* 8_2_1 */ + BAP_QOS("16_1_1", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_7_5, false, 30, 2, 8, 40000, 31), /* 16_1_1 */ + BAP_QOS("16_2_1", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_10, false, 40, 2, 10, 40000, 21), /* 16_2_1 (mandatory) */ + BAP_QOS("24_1_1", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_7_5, false, 45, 2, 8, 40000, 32), /* 24_1_1 */ + BAP_QOS("24_2_1", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_10, false, 60, 2, 10, 40000, 22), /* 24_2_1 */ + BAP_QOS("32_1_1", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_7_5, false, 60, 2, 8, 40000, 33), /* 32_1_1 */ + BAP_QOS("32_2_1", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_10, false, 80, 2, 10, 40000, 23), /* 32_2_1 */ + BAP_QOS("441_1_1", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_7_5, true, 97, 5, 24, 40000, 34), /* 441_1_1 */ + BAP_QOS("441_2_1", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_10, true, 130, 5, 31, 40000, 24), /* 441_2_1 */ + BAP_QOS("48_1_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 75, 5, 15, 40000, 35), /* 48_1_1 */ + BAP_QOS("48_2_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 100, 5, 20, 40000, 25), /* 48_2_1 */ + BAP_QOS("48_3_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 90, 5, 15, 40000, 36), /* 48_3_1 */ + BAP_QOS("48_4_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 120, 5, 20, 40000, 26), /* 48_4_1 */ + BAP_QOS("48_5_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 117, 5, 15, 40000, 37), /* 48_5_1 */ + BAP_QOS("48_6_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 155, 5, 20, 40000, 27), /* 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), /* 8_1_2 */ + BAP_QOS("8_2_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_10, false, 30, 13, 95, 40000, 0), /* 8_2_2 */ + BAP_QOS("16_1_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_7_5, false, 30, 13, 75, 40000, 11), /* 16_1_2 */ + BAP_QOS("16_2_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_10, false, 40, 13, 95, 40000, 1), /* 16_2_2 */ + BAP_QOS("24_1_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_7_5, false, 45, 13, 75, 40000, 12), /* 24_1_2 */ + BAP_QOS("24_2_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_10, false, 60, 13, 95, 40000, 2), /* 24_2_2 */ + BAP_QOS("32_1_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_7_5, false, 60, 13, 75, 40000, 13), /* 32_1_2 */ + BAP_QOS("32_2_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_10, false, 80, 13, 95, 40000, 3), /* 32_2_2 */ + BAP_QOS("441_1_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_7_5, true, 97, 13, 80, 40000, 14), /* 441_1_2 */ + BAP_QOS("441_2_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_10, true, 130, 13, 85, 40000, 4), /* 441_2_2 */ + BAP_QOS("48_1_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 75, 13, 75, 40000, 15), /* 48_1_2 */ + BAP_QOS("48_2_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 100, 13, 95, 40000, 5), /* 48_2_2 */ + BAP_QOS("48_3_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 90, 13, 75, 40000, 16), /* 48_3_2 */ + BAP_QOS("48_4_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 120, 13, 100, 40000, 6), /* 48_4_2 */ + BAP_QOS("48_5_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 117, 13, 75, 40000, 17), /* 48_5_2 */ + BAP_QOS("48_6_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 155, 13, 100, 40000, 7), /* 48_6_2 */ }; static const struct bap_qos bap_bcast_qos_configs[] = { @@ -149,40 +147,40 @@ static const struct bap_qos bap_bcast_qos_configs[] = { * bigger frequency and sdu better */ /* BAP v1.0.1 Table 6.4; low-latency */ - BAP_QOS("8_1_1", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_7_5, false, 26, 2, 8, 40000, 30, "low-latency"), /* 8_1_1 */ - BAP_QOS("8_2_1", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_10, false, 30, 2, 10, 40000, 20, "low-latency"), /* 8_2_1 */ - BAP_QOS("16_1_1", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_7_5, false, 30, 2, 8, 40000, 31, "low-latency"), /* 16_1_1 */ - BAP_QOS("16_2_1", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_10, false, 40, 2, 10, 40000, 21, "low-latency"), /* 16_2_1 (mandatory) */ - BAP_QOS("24_1_1", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_7_5, false, 45, 2, 8, 40000, 32, "low-latency"), /* 24_1_1 */ - BAP_QOS("24_2_1", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_10, false, 60, 2, 10, 40000, 22, "low-latency"), /* 24_2_1 */ - BAP_QOS("32_1_1", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_7_5, false, 60, 2, 8, 40000, 33, "low-latency"), /* 32_1_1 */ - BAP_QOS("32_2_1", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_10, false, 80, 2, 10, 40000, 23, "low-latency"), /* 32_2_1 */ - BAP_QOS("441_1_1", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_7_5, true, 97, 4, 24, 40000, 34, "low-latency"), /* 441_1_1 */ - BAP_QOS("441_2_1", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_10, true, 130, 4, 31, 40000, 24, "low-latency"), /* 441_2_1 */ - BAP_QOS("48_1_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 75, 4, 15, 40000, 35, "low-latency"), /* 48_1_1 */ - BAP_QOS("48_2_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 100, 4, 20, 40000, 25, "low-latency"), /* 48_2_1 */ - BAP_QOS("48_3_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 90, 4, 15, 40000, 36, "low-latency"), /* 48_3_1 */ - BAP_QOS("48_4_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 120, 4, 20, 40000, 26, "low-latency"), /* 48_4_1 */ - BAP_QOS("48_5_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 117, 4, 15, 40000, 37, "low-latency"), /* 48_5_1 */ - 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_QOS("8_1_1", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_7_5, false, 26, 2, 8, 40000, 30), /* 8_1_1 */ + BAP_QOS("8_2_1", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_10, false, 30, 2, 10, 40000, 20), /* 8_2_1 */ + BAP_QOS("16_1_1", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_7_5, false, 30, 2, 8, 40000, 31), /* 16_1_1 */ + BAP_QOS("16_2_1", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_10, false, 40, 2, 10, 40000, 21), /* 16_2_1 (mandatory) */ + BAP_QOS("24_1_1", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_7_5, false, 45, 2, 8, 40000, 32), /* 24_1_1 */ + BAP_QOS("24_2_1", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_10, false, 60, 2, 10, 40000, 22), /* 24_2_1 */ + BAP_QOS("32_1_1", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_7_5, false, 60, 2, 8, 40000, 33), /* 32_1_1 */ + BAP_QOS("32_2_1", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_10, false, 80, 2, 10, 40000, 23), /* 32_2_1 */ + BAP_QOS("441_1_1", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_7_5, true, 97, 4, 24, 40000, 34), /* 441_1_1 */ + BAP_QOS("441_2_1", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_10, true, 130, 4, 31, 40000, 24), /* 441_2_1 */ + BAP_QOS("48_1_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 75, 4, 15, 40000, 35), /* 48_1_1 */ + BAP_QOS("48_2_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 100, 4, 20, 40000, 25), /* 48_2_1 */ + BAP_QOS("48_3_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 90, 4, 15, 40000, 36), /* 48_3_1 */ + BAP_QOS("48_4_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 120, 4, 20, 40000, 26), /* 48_4_1 */ + BAP_QOS("48_5_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 117, 4, 15, 40000, 37), /* 48_5_1 */ + BAP_QOS("48_6_1", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 155, 4, 20, 40000, 27), /* 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), /* 8_1_2 */ + BAP_QOS("8_2_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_10, false, 30, 4, 60, 40000, 0), /* 8_2_2 */ + BAP_QOS("16_1_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_7_5, false, 30, 4, 45, 40000, 11), /* 16_1_2 */ + BAP_QOS("16_2_2", LC3_CONFIG_FREQ_16KHZ, LC3_CONFIG_DURATION_10, false, 40, 4, 60, 40000, 1), /* 16_2_2 */ + BAP_QOS("24_1_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_7_5, false, 45, 4, 45, 40000, 12), /* 24_1_2 */ + BAP_QOS("24_2_2", LC3_CONFIG_FREQ_24KHZ, LC3_CONFIG_DURATION_10, false, 60, 4, 60, 40000, 2), /* 24_2_2 */ + BAP_QOS("32_1_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_7_5, false, 60, 4, 45, 40000, 13), /* 32_1_2 */ + BAP_QOS("32_2_2", LC3_CONFIG_FREQ_32KHZ, LC3_CONFIG_DURATION_10, false, 80, 4, 60, 40000, 3), /* 32_2_2 */ + BAP_QOS("441_1_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_7_5, true, 97, 4, 54, 40000, 14), /* 441_1_2 */ + BAP_QOS("441_2_2", LC3_CONFIG_FREQ_44KHZ, LC3_CONFIG_DURATION_10, true, 130, 4, 60, 40000, 4), /* 441_2_2 */ + BAP_QOS("48_1_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 75, 4, 50, 40000, 15), /* 48_1_2 */ + BAP_QOS("48_2_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 100, 4, 65, 40000, 5), /* 48_2_2 */ + BAP_QOS("48_3_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 90, 4, 50, 40000, 16), /* 48_3_2 */ + BAP_QOS("48_4_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 120, 4, 65, 40000, 6), /* 48_4_2 */ + BAP_QOS("48_5_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_7_5, false, 117, 4, 50, 40000, 17), /* 48_5_2 */ + BAP_QOS("48_6_2", LC3_CONFIG_FREQ_48KHZ, LC3_CONFIG_DURATION_10, false, 155, 4, 65, 40000, 7), /* 48_6_2 */ }; static unsigned int get_rate_mask(uint8_t rate) { @@ -478,9 +476,6 @@ static bool select_bap_qos(struct bap_qos *conf, else if (c.priority < conf->priority) continue; - if (s->force_target_latency && !spa_streq(s->force_target_latency, c.tag)) - continue; - if (s->retransmission >= 0) c.retransmission = s->retransmission; if (s->latency >= 0) @@ -849,9 +844,6 @@ static void parse_settings(struct settings *s, const struct spa_dict *settings, if ((str = spa_dict_lookup(settings, "bluez5.bap.preset"))) s->qos_name = strdup(str); - if ((str = spa_dict_lookup(settings, "bluez5.bap.force-target-latency"))) - s->force_target_latency = strdup(str); - if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.rtn"), &value, 0)) s->retransmission = value; @@ -889,11 +881,11 @@ static void parse_settings(struct settings *s, const struct spa_dict *settings, spa_debugc(&debug_ctx->ctx, "BAP LC3 settings: preset:%s rtn:%d latency:%d delay:%d framing:%d " - "locations:%x chnalloc:%x sink:%d duplex:%d force-target-latency:%s", + "locations:%x chnalloc:%x sink:%d duplex:%d", s->qos_name ? s->qos_name : "auto", s->retransmission, s->latency, (int)s->delay, s->framing, (unsigned int)s->locations, (unsigned int)s->channel_allocation, - (int)s->sink, (int)s->duplex, s->force_target_latency); + (int)s->sink, (int)s->duplex); } static void free_config_data(struct config_data *d) @@ -901,7 +893,6 @@ static void free_config_data(struct config_data *d) if (!d) return; free(d->settings.qos_name); - free(d->settings.force_target_latency); free(d); } @@ -1503,10 +1494,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 +1502,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 +1528,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..926b1b3c4 100644 --- a/spa/plugins/bluez5/bluez5-dbus.c +++ b/spa/plugins/bluez5/bluez5-dbus.c @@ -148,7 +148,6 @@ struct spa_bt_monitor { struct spa_bt_remote_endpoint { struct spa_list link; struct spa_list device_link; - struct spa_list adapter_link; struct spa_bt_monitor *monitor; char *path; char *transport_path; @@ -156,7 +155,6 @@ struct spa_bt_remote_endpoint { char *uuid; unsigned int codec; struct spa_bt_device *device; - struct spa_bt_adapter *adapter; uint8_t *capabilities; size_t capabilities_len; uint8_t *metadata; @@ -188,22 +186,14 @@ 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; }; #define BROADCAST_CODE_LEN 16 -#define BD_ADDR_STR_LEN 17 struct spa_bt_big { struct spa_list link; @@ -212,7 +202,6 @@ struct spa_bt_big { struct spa_list bis_list; int big_id; int sync_factor; - char adapter[BD_ADDR_STR_LEN + 3]; }; /* @@ -594,35 +583,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(); } @@ -744,12 +716,14 @@ static const char *bap_features_get_uuid(struct bap_features *feat, size_t i) /** Get feature name at \a i, or NULL if uuid doesn't match */ static const char *bap_features_get_name(struct bap_features *feat, size_t i, const char *uuid) { + char *pos; + if (i >= feat->dict.n_items) return NULL; if (!spa_streq(feat->dict.items[i].value, uuid)) return NULL; - const char *pos = strchr(feat->dict.items[i].key, ':'); + pos = strchr(feat->dict.items[i].key, ':'); if (!pos) return NULL; return pos + 1; @@ -760,11 +734,6 @@ static void bap_features_clear(struct bap_features *feat) spa_zero(*feat); } -const struct spa_dict *get_device_codec_settings(struct spa_bt_device *device, bool bap) -{ - return bap ? device->settings : &device->monitor->global_settings; -} - static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata) { struct spa_bt_monitor *monitor = userdata; @@ -1358,6 +1327,7 @@ static struct spa_bt_adapter *adapter_find(struct spa_bt_monitor *monitor, const static int parse_modalias(const char *modalias, uint16_t *source, uint16_t *vendor, uint16_t *product, uint16_t *version) { + char *pos; unsigned int src, i, j, k; if (spa_strstartswith(modalias, "bluetooth:")) @@ -1367,7 +1337,7 @@ static int parse_modalias(const char *modalias, uint16_t *source, uint16_t *vend else return -EINVAL; - const char *pos = strchr(modalias, ':'); + pos = strchr(modalias, ':'); if (pos == NULL) return -EINVAL; @@ -1664,8 +1634,6 @@ static struct spa_bt_adapter *adapter_create(struct spa_bt_monitor *monitor, con d->monitor = monitor; d->path = strdup(path); - spa_list_init(&d->remote_endpoint_list); - spa_list_prepend(&monitor->adapter_list, &d->link); adapter_init_bus_type(monitor, d); @@ -1680,7 +1648,6 @@ static void adapter_free(struct spa_bt_adapter *adapter) { struct spa_bt_monitor *monitor = adapter->monitor; struct spa_bt_device *d, *td; - struct spa_bt_remote_endpoint *ep, *tep; spa_log_debug(monitor->log, "%p", adapter); @@ -1689,13 +1656,6 @@ static void adapter_free(struct spa_bt_adapter *adapter) if (d->adapter == adapter) device_free(d); - spa_list_for_each_safe(ep, tep, &adapter->remote_endpoint_list, adapter_link) { - if (ep->adapter == adapter) { - spa_list_remove(&ep->adapter_link); - ep->adapter = NULL; - } - } - spa_bt_player_destroy(adapter->dummy_player); spa_list_remove(&adapter->link); @@ -2798,21 +2758,18 @@ bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const stru { SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, SPA_BT_FEATURE_A2DP_DUPLEX }, }; bool is_a2dp = codec->kind == MEDIA_CODEC_A2DP; - 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") && @@ -2842,8 +2799,7 @@ bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const stru continue; if (media_codec_check_caps(codec, ep->codec, ep->capabilities, ep->capabilities_len, - &ep->monitor->default_audio_info, - get_device_codec_settings(device, is_bap))) + &ep->monitor->default_audio_info, &monitor->global_settings)) return true; } @@ -3059,30 +3015,19 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en } else if (spa_streq(key, "Device")) { struct spa_bt_device *device; - struct spa_bt_adapter *adapter; device = spa_bt_device_find(monitor, value); - adapter = adapter_find(monitor, value); + if (device == NULL) + goto next; - if (device != NULL) { - spa_log_debug(monitor->log, "remote_endpoint %p: device -> %p", remote_endpoint, device); + spa_log_debug(monitor->log, "remote_endpoint %p: device -> %p", remote_endpoint, device); - if (remote_endpoint->device != device) { - if (remote_endpoint->device != NULL) - spa_list_remove(&remote_endpoint->device_link); - remote_endpoint->device = device; + if (remote_endpoint->device != device) { + if (remote_endpoint->device != NULL) + spa_list_remove(&remote_endpoint->device_link); + remote_endpoint->device = device; + if (device != NULL) spa_list_append(&device->remote_endpoint_list, &remote_endpoint->device_link); - } - } - if (adapter != NULL) { - spa_log_debug(monitor->log, "remote_endpoint %p: adapter -> %p", remote_endpoint, adapter); - - if (remote_endpoint->adapter != adapter) { - if (remote_endpoint->adapter != NULL) - spa_list_remove(&remote_endpoint->adapter_link); - remote_endpoint->adapter = adapter; - spa_list_append(&adapter->remote_endpoint_list, &remote_endpoint->adapter_link); - } } } else if (spa_streq(key, "Transport")) { /* For ASHA */ @@ -3157,13 +3102,11 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en spa_log_debug(monitor->log, "remote_endpoint %p: %s=%"PRIu64, remote_endpoint, key, remote_endpoint->hisyncid); } else if (spa_streq(key, "SupportedFeatures")) { - DBusMessageIter iter; - if (!check_iter_signature(&it[1], "a{sv}")) goto next; - dbus_message_iter_recurse(&it[1], &iter); - parse_supported_features(monitor, &iter, &remote_endpoint->bap_features); + dbus_message_iter_recurse(&it[1], &it[2]); + parse_supported_features(monitor, &it[2], &remote_endpoint->bap_features); } else { unhandled: spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key); @@ -3219,9 +3162,6 @@ static void remote_endpoint_free(struct spa_bt_remote_endpoint *remote_endpoint) if (remote_endpoint->device) spa_list_remove(&remote_endpoint->device_link); - if (remote_endpoint->adapter) - spa_list_remove(&remote_endpoint->adapter_link); - bap_features_clear(&remote_endpoint->bap_features); spa_list_remove(&remote_endpoint->link); @@ -3442,12 +3382,8 @@ int spa_bt_transport_acquire(struct spa_bt_transport *transport, bool optional) if (!transport->acquired) res = spa_bt_transport_impl(transport, acquire, 0, optional); - else { - /* keepalive */ - transport->acquire_refcount = 1; - spa_bt_transport_emit_state_changed(transport, transport->state, transport->state); - return 0; - } + else + res = 0; if (res >= 0) { transport->acquire_refcount = 1; @@ -4715,7 +4651,7 @@ static bool codec_switch_check_endpoint(struct spa_bt_remote_endpoint *ep, if (!media_codec_check_caps(codec, ep->codec, ep->capabilities, ep->capabilities_len, &ep->device->monitor->default_audio_info, - get_device_codec_settings(ep->device, codec->kind == MEDIA_CODEC_BAP))) + &ep->device->monitor->global_settings)) return false; if (ep_profile & (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_BAP_SINK)) { @@ -6199,11 +6135,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 +6161,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); @@ -6316,7 +6234,6 @@ static void configure_bis(struct spa_bt_monitor *monitor, } static void configure_bcast_source(struct spa_bt_monitor *monitor, - struct spa_bt_remote_endpoint *ep, const struct media_codec *codec, DBusConnection *conn, const char *object_path, @@ -6325,24 +6242,8 @@ static void configure_bcast_source(struct spa_bt_monitor *monitor, { struct spa_bt_big *big; struct spa_bt_bis *bis; - /* Configure each BIS from a BIG */ spa_list_for_each(big, &monitor->bcast_source_config_list, link) { - /* Apply per adapter configuration if BIG has an adapter value stated, - * otherwise apply the BIG config angnostically to each adapter - */ - if ((strlen(big->adapter) > 0) && (ep->adapter != NULL)) { - if (!ep->adapter->address) { - spa_log_warn(monitor->log, "this adapter is not associated with any BD address. BIG config will applied agnostically to any adapter!"); - continue; - } - - if (strcasecmp(ep->adapter->address, big->adapter)) - continue; - - spa_log_debug(monitor->log, "configuring BIG for adapter=%s", big->adapter); - } - spa_list_for_each(bis, &big->bis_list, link) { configure_bis(monitor, codec, conn, object_path, interface_name, big, bis, local_endpoint); @@ -6466,7 +6367,7 @@ static void interface_added(struct spa_bt_monitor *monitor, } if (local_endpoint != NULL) - configure_bcast_source(monitor, ep, monitor->media_codecs[i], conn, object_path, interface_name, local_endpoint); + configure_bcast_source(monitor, monitor->media_codecs[i], conn, object_path, interface_name, local_endpoint); } } } @@ -7121,10 +7022,6 @@ static void parse_broadcast_source_config(struct spa_bt_monitor *monitor, const goto parse_failed; memcpy(big_entry->broadcast_code, bcode, strlen(bcode)); spa_log_debug(monitor->log, "big_entry->broadcast_code %s", big_entry->broadcast_code); - } else if (spa_streq(key, "adapter")) { - if (spa_json_get_string(&it[0], big_entry->adapter, sizeof(big_entry->adapter)) <= 0) - goto parse_failed; - spa_log_debug(monitor->log, "big_entry->adapter %s", big_entry->adapter); } else if (spa_streq(key, "encryption")) { if (spa_json_get_bool(&it[0], &big_entry->encryption) <= 0) goto parse_failed; @@ -7151,20 +7048,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..a63361146 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; @@ -1589,15 +1485,7 @@ static int set_profile(struct impl *this, uint32_t profile, enum spa_bluetooth_a profiles = this->bt_dev->profiles & SPA_BT_PROFILE_BAP_DUPLEX; break; 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; + profiles = this->bt_dev->profiles & SPA_BT_PROFILE_A2DP_DUPLEX; break; default: profiles = 0; @@ -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/defs.h b/spa/plugins/bluez5/defs.h index 24a85a5c9..3efec465a 100644 --- a/spa/plugins/bluez5/defs.h +++ b/spa/plugins/bluez5/defs.h @@ -135,8 +135,7 @@ extern "C" { #define PROFILE_HFP_AG "/Profile/HFPAG" #define PROFILE_HFP_HF "/Profile/HFPHF" -#define HSP_HS_DEFAULT_CHANNEL 3 -#define HFP_SCO_DEFAULT_DATAPATH 0 +#define HSP_HS_DEFAULT_CHANNEL 3 #define SOURCE_ID_BLUETOOTH 0x1 /* Bluetooth SIG */ #define SOURCE_ID_USB 0x2 /* USB Implementer's Forum */ @@ -379,7 +378,6 @@ struct spa_bt_adapter { unsigned int has_media1_interface:1; unsigned int le_audio_bcast_supported:1; unsigned int tx_timestamping_supported:1; - struct spa_list remote_endpoint_list; }; enum spa_bt_form_factor { 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/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-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/filter-graph.c b/spa/plugins/filter-graph/filter-graph.c index c806da591..9271ace34 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 @@ -81,6 +80,7 @@ struct descriptor { unsigned long *output; unsigned long *control; unsigned long *notify; + float *default_control; }; struct port { @@ -94,9 +94,6 @@ struct port { uint32_t n_links; uint32_t external; - bool control_initialized; - - float control_current; float control_data[MAX_HNDL]; float *audio_data[MAX_HNDL]; void *audio_mem[MAX_HNDL]; @@ -196,9 +193,6 @@ struct graph { struct volume volume[2]; - uint32_t default_inputs; - uint32_t default_outputs; - uint32_t n_inputs; uint32_t n_outputs; uint32_t inputs_position[MAX_CHANNELS]; @@ -222,7 +216,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; @@ -264,23 +257,16 @@ static void emit_filter_graph_info(struct impl *impl, bool full) impl->info.change_mask = impl->info_all; if (impl->info.change_mask || full) { char n_inputs[64], n_outputs[64], latency[64]; - char n_default_inputs[64], n_default_outputs[64]; struct spa_dict_item items[6]; struct spa_dict dict = SPA_DICT(items, 0); char in_pos[MAX_CHANNELS * 8]; char out_pos[MAX_CHANNELS * 8]; - /* these are the current graph inputs/outputs */ snprintf(n_inputs, sizeof(n_inputs), "%d", impl->graph.n_inputs); snprintf(n_outputs, sizeof(n_outputs), "%d", impl->graph.n_outputs); - /* these are the default number of graph inputs/outputs */ - snprintf(n_default_inputs, sizeof(n_default_inputs), "%d", impl->graph.default_inputs); - snprintf(n_default_outputs, sizeof(n_default_outputs), "%d", impl->graph.default_outputs); items[dict.n_items++] = SPA_DICT_ITEM("n_inputs", n_inputs); items[dict.n_items++] = SPA_DICT_ITEM("n_outputs", n_outputs); - items[dict.n_items++] = SPA_DICT_ITEM("n_default_inputs", n_default_inputs); - items[dict.n_items++] = SPA_DICT_ITEM("n_default_outputs", n_default_outputs); if (graph->n_inputs_position) { print_channels(in_pos, sizeof(in_pos), graph->n_inputs_position, graph->inputs_position); @@ -353,6 +339,12 @@ static int impl_process(void *object, return 0; } +static float get_default(struct impl *impl, struct descriptor *desc, uint32_t p) +{ + struct spa_fga_port *port = &desc->desc->ports[p]; + return port->def; +} + static struct node *find_node(struct graph *graph, const char *name) { struct node *node; @@ -441,20 +433,6 @@ static struct port *find_port(struct node *node, const char *name, int descripto return NULL; } -static void get_ranges(struct impl *impl, struct spa_fga_port *p, - float *def, float *min, float *max) -{ - uint32_t rate = impl->rate ? impl->rate : DEFAULT_RATE; - *def = p->def; - *min = p->min; - *max = p->max; - if (p->hint & SPA_FGA_HINT_SAMPLE_RATE) { - *def *= rate; - *min *= rate; - *max *= rate; - } -} - static int impl_enum_prop_info(void *object, uint32_t idx, struct spa_pod_builder *b, struct spa_pod **param) { @@ -469,6 +447,7 @@ static int impl_enum_prop_info(void *object, uint32_t idx, struct spa_pod_builde struct spa_fga_port *p; float def, min, max; char name[512]; + uint32_t rate = impl->rate ? impl->rate : DEFAULT_RATE; if (idx >= graph->n_control) return 0; @@ -479,7 +458,15 @@ static int impl_enum_prop_info(void *object, uint32_t idx, struct spa_pod_builde d = desc->desc; p = &d->ports[port->p]; - get_ranges(impl, p, &def, &min, &max); + if (p->hint & SPA_FGA_HINT_SAMPLE_RATE) { + def = p->def * rate; + min = p->min * rate; + max = p->max * rate; + } else { + def = p->def; + min = p->min; + max = p->max; + } if (node->name[0] != '\0') snprintf(name, sizeof(name), "%s:%s", node->name, p->name); @@ -578,58 +565,41 @@ static int impl_get_props(void *object, struct spa_pod_builder *b, struct spa_po return 1; } -static int port_id_set_control_value(struct port *port, uint32_t id, float value) +static int port_set_control_value(struct port *port, float *value, uint32_t id) { struct node *node = port->node; struct impl *impl = node->graph->impl; + struct descriptor *desc = node->desc; - struct spa_fga_port *p = &desc->desc->ports[port->p]; float old; bool changed; old = port->control_data[id]; - port->control_data[id] = value; - + port->control_data[id] = value ? *value : desc->default_control[port->idx]; spa_log_info(impl->log, "control %d %d ('%s') from %f to %f", port->idx, id, - p->name, old, value); - + desc->desc->ports[port->p].name, old, port->control_data[id]); changed = old != port->control_data[id]; node->control_changed |= changed; - return changed ? 1 : 0; } -static int port_set_control_value(struct port *port, float *value) -{ - struct node *node = port->node; - struct impl *impl = node->graph->impl; - struct spa_fga_port *p; - float v, def, min, max; - uint32_t i; - int count = 0; - - p = &node->desc->desc->ports[port->p]; - get_ranges(impl, p, &def, &min, &max); - v = SPA_CLAMP(value ? *value : def, min, max); - - port->control_current = v; - port->control_initialized = true; - - for (i = 0; i < node->n_hndl; i++) - count += port_id_set_control_value(port, i, v); - - return count; -} - static int set_control_value(struct node *node, const char *name, float *value) { struct port *port; + int count = 0; + uint32_t i, n_hndl; port = find_port(node, name, SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL); if (port == NULL) return -ENOENT; - return port_set_control_value(port, value); + /* if we don't have any instances yet, set the first control value, we will + * copy to other instances later */ + n_hndl = SPA_MAX(1u, port->node->n_hndl); + for (i = 0; i < n_hndl; i++) + count += port_set_control_value(port, value, i); + + return count; } static int parse_params(struct graph *graph, const struct spa_pod *pod) @@ -700,46 +670,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) @@ -761,7 +706,7 @@ static int sync_volume(struct graph *graph, struct volume *vol) v = v * (vol->max[n_port] - vol->min[n_port]) + vol->min[n_port]; n_hndl = SPA_MAX(1u, p->node->n_hndl); - res += port_id_set_control_value(p, i % n_hndl, v); + res += port_set_control_value(p, &v, i % n_hndl); } return res; } @@ -853,7 +798,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; @@ -976,6 +925,7 @@ static void descriptor_unref(struct descriptor *desc) free(desc->input); free(desc->output); free(desc->control); + free(desc->default_control); free(desc->notify); free(desc); } @@ -986,7 +936,7 @@ static struct descriptor *descriptor_load(struct impl *impl, const char *type, struct plugin *pl; struct descriptor *desc; const struct spa_fga_descriptor *d; - uint32_t n_input, n_output, n_control, n_notify; + uint32_t i, n_input, n_output, n_control, n_notify; unsigned long p; int res; @@ -1040,6 +990,7 @@ static struct descriptor *descriptor_load(struct impl *impl, const char *type, desc->input = calloc(n_input, sizeof(unsigned long)); desc->output = calloc(n_output, sizeof(unsigned long)); desc->control = calloc(n_control, sizeof(unsigned long)); + desc->default_control = calloc(n_control, sizeof(float)); desc->notify = calloc(n_notify, sizeof(unsigned long)); for (p = 0; p < d->n_ports; p++) { @@ -1058,8 +1009,8 @@ static struct descriptor *descriptor_load(struct impl *impl, const char *type, } } else if (SPA_FGA_IS_PORT_CONTROL(fp->flags)) { if (SPA_FGA_IS_PORT_INPUT(fp->flags)) { - spa_log_info(impl->log, "using port %lu ('%s') as control %d %f/%f/%f", p, - fp->name, desc->n_control, fp->def, fp->min, fp->max); + spa_log_info(impl->log, "using port %lu ('%s') as control %d", p, + fp->name, desc->n_control); desc->control[desc->n_control++] = p; } else if (SPA_FGA_IS_PORT_OUTPUT(fp->flags)) { @@ -1069,6 +1020,17 @@ static struct descriptor *descriptor_load(struct impl *impl, const char *type, } } } + if (desc->n_input == 0 && desc->n_output == 0 && desc->n_control == 0 && desc->n_notify == 0) { + spa_log_error(impl->log, "plugin has no input and no output ports"); + res = -ENOTSUP; + goto exit; + } + for (i = 0; i < desc->n_control; i++) { + p = desc->control[i]; + desc->default_control[i] = get_default(impl, desc, p); + spa_log_info(impl->log, "control %d ('%s') default to %f", i, + d->ports[p].name, desc->default_control[i]); + } spa_list_append(&pl->descriptor_list, &desc->link); return desc; @@ -1448,6 +1410,7 @@ static int load_node(struct graph *graph, struct spa_json *json) port->external = SPA_ID_INVALID; port->p = desc->control[i]; spa_list_init(&port->link_list); + port->control_data[0] = desc->default_control[i]; } for (i = 0; i < desc->n_notify; i++) { struct port *port = &node->notify_port[i]; @@ -1645,7 +1608,6 @@ static int impl_activate(void *object, const struct spa_dict *props) goto error; } } - node->control_changed = true; } /* then link ports */ @@ -1719,10 +1681,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) { @@ -1822,7 +1784,7 @@ static int setup_graph(struct graph *graph) struct port *port; struct graph_port *gp; struct graph_hndl *gh; - uint32_t i, j, n, n_input, n_output, n_hndl = 0, n_out_hndl; + uint32_t i, j, n, n_input, n_output, n_hndl = 0; int res; struct descriptor *desc; const struct spa_fga_descriptor *d; @@ -1834,8 +1796,19 @@ static int setup_graph(struct graph *graph) first = spa_list_first(&graph->node_list, struct node, link); last = spa_list_last(&graph->node_list, struct node, link); - n_input = graph->default_inputs; - n_output = graph->default_outputs; + /* calculate the number of inputs and outputs into the graph. + * If we have a list of inputs/outputs, just use them. Otherwise + * we count all input ports of the first node and all output + * ports of the last node */ + if (graph->n_input_names != 0) + n_input = graph->n_input_names; + else + n_input = first->desc->n_input; + + if (graph->n_output_names != 0) + n_output = graph->n_output_names; + else + n_output = last->desc->n_output; /* we allow unconnected ports when not explicitly given and the nodes support * NULL data */ @@ -1843,11 +1816,16 @@ static int setup_graph(struct graph *graph) SPA_FLAG_IS_SET(first->desc->desc->flags, SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA) && SPA_FLAG_IS_SET(last->desc->desc->flags, SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA); - if (n_input == 0) - n_input = n_output; - if (n_output == 0) - n_output = n_input; - + if (n_input == 0) { + spa_log_error(impl->log, "no inputs"); + res = -EINVAL; + goto error; + } + if (n_output == 0) { + spa_log_error(impl->log, "no outputs"); + res = -EINVAL; + goto error; + } if (graph->n_inputs == 0) graph->n_inputs = impl->info.n_inputs; if (graph->n_inputs == 0) @@ -1858,14 +1836,12 @@ static int setup_graph(struct graph *graph) /* compare to the requested number of inputs and duplicate the * graph n_hndl times when needed. */ - n_hndl = n_input ? graph->n_inputs / n_input : 1; + n_hndl = graph->n_inputs / n_input; if (graph->n_outputs == 0) graph->n_outputs = n_output * n_hndl; - n_out_hndl = n_output ? graph->n_outputs / n_output : 1; - - if (n_hndl != n_out_hndl) { + if (n_hndl != graph->n_outputs / n_output) { spa_log_error(impl->log, "invalid ports. The input stream has %1$d ports and " "the filter has %2$d inputs. The output stream has %3$d ports " "and the filter has %4$d outputs. input:%1$d / input:%2$d != " @@ -2053,9 +2029,11 @@ static int setup_graph(struct graph *graph) } } for (i = 0; i < desc->n_control; i++) { + /* any default values for the controls are set in the first instance + * of the control data. Duplicate this to the other instances now. */ struct port *port = &node->control_port[i]; - port_set_control_value(port, - port->control_initialized ? &port->control_current : NULL); + for (j = 1; j < n_hndl; j++) + port->control_data[j] = port->control_data[0]; } } res = 0; @@ -2105,7 +2083,6 @@ static int load_graph(struct graph *graph, const struct spa_dict *props) struct spa_json inputs, outputs, *pinputs = NULL, *poutputs = NULL; struct spa_json ivolumes, ovolumes, *pivolumes = NULL, *povolumes = NULL; struct spa_json nodes, *pnodes = NULL, links, *plinks = NULL; - struct node *first, *last; const char *json, *val; char key[256]; int res, len; @@ -2255,25 +2232,6 @@ static int load_graph(struct graph *graph, const struct spa_dict *props) } if ((res = setup_graph_controls(graph)) < 0) return res; - - first = spa_list_first(&graph->node_list, struct node, link); - last = spa_list_last(&graph->node_list, struct node, link); - - /* calculate the number of inputs and outputs into the graph. - * If we have a list of inputs/outputs, just use them. Otherwise - * we count all input ports of the first node and all output - * ports of the last node */ - if (graph->n_input_names != 0) - graph->default_inputs = graph->n_input_names; - else - graph->default_inputs = first->desc->n_input; - - if (graph->n_output_names != 0) - graph->default_outputs = graph->n_output_names; - else - graph->default_outputs = last->desc->n_output; - - return 0; } @@ -2370,7 +2328,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/plugin_builtin.c b/spa/plugins/filter-graph/plugin_builtin.c index 3c15673db..8a964ccd7 100644 --- a/spa/plugins/filter-graph/plugin_builtin.c +++ b/spa/plugins/filter-graph/plugin_builtin.c @@ -14,14 +14,12 @@ #include #include #include -#include #include #include #include #include #include -#include #include #include @@ -543,12 +541,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 +561,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 +573,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 +587,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 +601,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 +615,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 +629,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 +643,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 +657,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 +672,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 +686,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 +1452,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 +1509,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 +1576,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 +1625,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 +1682,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 +2471,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 +2483,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 +2642,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 +2988,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", @@ -3139,137 +3115,6 @@ static const struct spa_fga_descriptor noisegate_desc = { .cleanup = builtin_cleanup, }; -/* busy */ -struct busy_impl { - struct plugin *plugin; - - struct spa_fga_dsp *dsp; - struct spa_log *log; - - unsigned long rate; - - float wait_scale; - float cpu_scale; -}; - -static void *busy_instantiate(const struct spa_fga_plugin *plugin, const struct spa_fga_descriptor * Descriptor, - unsigned long SampleRate, int index, const char *config) -{ - struct plugin *pl = SPA_CONTAINER_OF(plugin, struct plugin, plugin); - struct busy_impl *impl; - struct spa_json it[1]; - const char *val; - char key[256]; - float wait_percent = 0.0f, cpu_percent = 0.0f; - int len; - - if (config != NULL) { - if (spa_json_begin_object(&it[0], config, strlen(config)) <= 0) { - spa_log_error(pl->log, "busy:config must be an object"); - return NULL; - } - - while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { - if (spa_streq(key, "wait-percent")) { - if (spa_json_parse_float(val, len, &wait_percent) <= 0) { - spa_log_error(pl->log, "busy:wait-percent requires a number"); - return NULL; - } - } else if (spa_streq(key, "cpu-percent")) { - if (spa_json_parse_float(val, len, &cpu_percent) <= 0) { - spa_log_error(pl->log, "busy:cpu-percent requires a number"); - return NULL; - } - } else { - spa_log_warn(pl->log, "busy: ignoring config key: '%s'", key); - } - } - if (wait_percent <= 0.0f) - wait_percent = 0.0f; - if (cpu_percent <= 0.0f) - cpu_percent = 0.0f; - } - - impl = calloc(1, sizeof(*impl)); - if (impl == NULL) - return NULL; - - impl->plugin = pl; - impl->dsp = pl->dsp; - impl->log = pl->log; - impl->rate = SampleRate; - impl->wait_scale = wait_percent * SPA_NSEC_PER_SEC / (100.0f * SampleRate); - impl->cpu_scale = cpu_percent * SPA_NSEC_PER_SEC / (100.0f * SampleRate); - spa_log_info(impl->log, "wait-percent:%f cpu-percent:%f", wait_percent, cpu_percent); - - return impl; -} - -static void busy_run(void * Instance, unsigned long SampleCount) -{ - struct busy_impl *impl = Instance; - struct timespec ts; - uint64_t busy_nsec; - - if (impl->wait_scale > 0.0f) { - busy_nsec = (uint64_t)(impl->wait_scale * SampleCount); - ts.tv_sec = busy_nsec / SPA_NSEC_PER_SEC; - ts.tv_nsec = busy_nsec % SPA_NSEC_PER_SEC; - clock_nanosleep(CLOCK_MONOTONIC, 0, &ts, NULL); - } - if (impl->cpu_scale > 0.0f) { - clock_gettime(CLOCK_MONOTONIC, &ts); - busy_nsec = SPA_TIMESPEC_TO_NSEC(&ts); - busy_nsec += (uint64_t)(impl->cpu_scale * SampleCount); - do { - clock_gettime(CLOCK_MONOTONIC, &ts); - } while ((uint64_t)SPA_TIMESPEC_TO_NSEC(&ts) < busy_nsec); - } -} - -static const struct spa_fga_descriptor busy_desc = { - .name = "busy", - .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, - - .n_ports = 0, - .ports = NULL, - - .instantiate = busy_instantiate, - .connect_port = builtin_connect_port, - .run = busy_run, - .cleanup = builtin_cleanup, -}; - -/* null */ -static void null_run(void * Instance, unsigned long SampleCount) -{ -} - -static struct spa_fga_port null_ports[] = { - { .index = 0, - .name = "In", - .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_AUDIO, - }, - { .index = 1, - .name = "Control", - .flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL, - .def = 0.0f, .min = -FLT_MAX, .max = FLT_MAX - }, -}; - -static const struct spa_fga_descriptor null_desc = { - .name = "null", - .flags = SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA, - - .n_ports = SPA_N_ELEMENTS(null_ports), - .ports = null_ports, - - .instantiate = builtin_instantiate, - .connect_port = builtin_connect_port, - .run = null_run, - .cleanup = builtin_cleanup, -}; - static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) { switch(Index) { @@ -3335,10 +3180,6 @@ static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index) return &zeroramp_desc; case 30: return &noisegate_desc; - case 31: - return &busy_desc; - case 32: - return &null_desc; } return NULL; } 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_ladspa.c b/spa/plugins/filter-graph/plugin_ladspa.c index 1aebafe35..d5c8ef488 100644 --- a/spa/plugins/filter-graph/plugin_ladspa.c +++ b/spa/plugins/filter-graph/plugin_ladspa.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include @@ -114,14 +113,8 @@ static void ladspa_port_update_ranges(struct descriptor *dd, struct spa_fga_port LADSPA_PortRangeHintDescriptor hint = d->PortRangeHints[p].HintDescriptor; LADSPA_Data lower, upper; - if (hint & LADSPA_HINT_BOUNDED_BELOW) - lower = d->PortRangeHints[p].LowerBound; - else - lower = -FLT_MAX; - if (hint & LADSPA_HINT_BOUNDED_ABOVE) - upper = d->PortRangeHints[p].UpperBound; - else - upper = FLT_MAX; + lower = d->PortRangeHints[p].LowerBound; + upper = d->PortRangeHints[p].UpperBound; port->hint = 0; if (hint & LADSPA_HINT_TOGGLED) diff --git a/spa/plugins/filter-graph/plugin_lv2.c b/spa/plugins/filter-graph/plugin_lv2.c index b2d3fc6cc..712b728e2 100644 --- a/spa/plugins/filter-graph/plugin_lv2.c +++ b/spa/plugins/filter-graph/plugin_lv2.c @@ -560,17 +560,6 @@ static const struct spa_fga_descriptor *lv2_plugin_make_desc(void *plugin, const fp->min = mins[i]; fp->max = maxes[i]; fp->def = controls[i]; - - if (isnan(fp->min)) - fp->min = -FLT_MAX; - if (isnan(fp->max)) - fp->max = FLT_MAX; - if (isnan(fp->def)) - fp->def = 0.0f; - if (fp->max <= fp->min) - fp->max = FLT_MAX; - if (fp->def <= fp->min) - fp->min = -FLT_MAX; } return &desc->desc; } diff --git a/spa/plugins/filter-graph/plugin_onnx.c b/spa/plugins/filter-graph/plugin_onnx.c index 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/filter-graph/plugin_sofa.c b/spa/plugins/filter-graph/plugin_sofa.c index 2e3a1ea3b..7ec73ea2b 100644 --- a/spa/plugins/filter-graph/plugin_sofa.c +++ b/spa/plugins/filter-graph/plugin_sofa.c @@ -32,7 +32,6 @@ struct spatializer_impl { unsigned long rate; float *port[7]; int n_samples, blocksize, tailsize; - float gain; float *tmp[2]; struct MYSOFA_EASY *sofa; @@ -72,7 +71,6 @@ static void * spatializer_instantiate(const struct spa_fga_plugin *plugin, const impl->plugin = pl; impl->dsp = pl->dsp; impl->log = pl->log; - impl->gain = 1.0f; while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { if (spa_streq(key, "blocksize")) { @@ -96,13 +94,6 @@ static void * spatializer_instantiate(const struct spa_fga_plugin *plugin, const goto error; } } - else if (spa_streq(key, "gain")) { - if (spa_json_parse_float(val, len, &impl->gain) <= 0) { - spa_log_error(impl->log, "spatializer:gain requires a number"); - errno = EINVAL; - goto error; - } - } } if (!filename[0]) { spa_log_error(impl->log, "spatializer:filename was not given"); @@ -177,14 +168,11 @@ static void * spatializer_instantiate(const struct spa_fga_plugin *plugin, const reason = "Only sources with MC supported"; errno = ENOTSUP; break; + default: case MYSOFA_INTERNAL_ERROR: errno = EIO; reason = "Internal error"; break; - default: - errno = ret; - reason = strerror(errno); - break; } spa_log_error(impl->log, "Unable to load HRTF from %s: %s (%d)", filename, reason, ret); goto error; @@ -195,8 +183,8 @@ static void * spatializer_instantiate(const struct spa_fga_plugin *plugin, const if (impl->tailsize <= 0) impl->tailsize = SPA_CLAMP(4096, impl->blocksize, 32768); - spa_log_info(impl->log, "using n_samples:%u %d:%d blocksize gain:%f sofa:%s", impl->n_samples, - impl->blocksize, impl->tailsize, impl->gain, filename); + spa_log_info(impl->log, "using n_samples:%u %d:%d blocksize sofa:%s", impl->n_samples, + impl->blocksize, impl->tailsize, filename); impl->tmp[0] = calloc(impl->plugin->quantum_limit, sizeof(float)); impl->tmp[1] = calloc(impl->plugin->quantum_limit, sizeof(float)); @@ -262,13 +250,6 @@ static void spatializer_reload(void * Instance) if (impl->r_conv[2]) convolver_free(impl->r_conv[2]); - if (impl->gain != 1.0f) { - for (int i = 0; i < impl->n_samples; i++) { - left_ir[i] *= impl->gain; - right_ir[i] *= impl->gain; - } - } - impl->l_conv[2] = convolver_new(impl->dsp, impl->blocksize, impl->tailsize, left_ir, impl->n_samples); impl->r_conv[2] = convolver_new(impl->dsp, impl->blocksize, impl->tailsize, 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/libcamera/libcamera-source.cpp b/spa/plugins/libcamera/libcamera-source.cpp index fba3d8b0b..f0eaa68f0 100644 --- a/spa/plugins/libcamera/libcamera-source.cpp +++ b/spa/plugins/libcamera/libcamera-source.cpp @@ -184,6 +184,9 @@ struct impl { 0, nullptr, 0, this ); + if (source.fd >= 0) + spa_system_close(system, std::exchange(source.fd, -1)); + camera->requestCompleted.disconnect(this, &impl::requestComplete); if (int res = camera->stop(); res < 0) { @@ -191,9 +194,6 @@ struct impl { camera->id().c_str(), spa_strerror(res)); } - if (source.fd >= 0) - spa_system_close(system, std::exchange(source.fd, -1)); - completed_requests_rb = SPA_RINGBUFFER_INIT(); active = false; @@ -2163,7 +2163,7 @@ impl::impl(spa_log *log, spa_loop *data_loop, spa_system *system, &impl_node, this); params[NODE_PropInfo] = SPA_PARAM_INFO(SPA_PARAM_PropInfo, SPA_PARAM_INFO_READ); - params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_WRITE); + params[NODE_Props] = SPA_PARAM_INFO(SPA_PARAM_Props, SPA_PARAM_INFO_READWRITE); params[NODE_EnumFormat] = SPA_PARAM_INFO(SPA_PARAM_EnumFormat, SPA_PARAM_INFO_READ); params[NODE_Format] = SPA_PARAM_INFO(SPA_PARAM_Format, SPA_PARAM_INFO_WRITE); diff --git a/spa/plugins/meson.build b/spa/plugins/meson.build index 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..c6e6ca4b8 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, *s; 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()]", + s = strrchr(file, '/'); + 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/v4l2/v4l2-device.c b/spa/plugins/v4l2/v4l2-device.c index f29252d16..2379d2105 100644 --- a/spa/plugins/v4l2/v4l2-device.c +++ b/spa/plugins/v4l2/v4l2-device.c @@ -98,9 +98,9 @@ static int emit_info(struct impl *this, bool full) (this->dev.cap.version >> 8) & 0xFF, (this->dev.cap.version) & 0xFF); ADD_ITEM(SPA_KEY_API_V4L2_CAP_VERSION, version); - snprintf(capabilities, sizeof(capabilities), "0x%08x", this->dev.cap.capabilities); + snprintf(capabilities, sizeof(capabilities), "%08x", this->dev.cap.capabilities); ADD_ITEM(SPA_KEY_API_V4L2_CAP_CAPABILITIES, capabilities); - snprintf(device_caps, sizeof(device_caps), "0x%08x", this->dev.cap.device_caps); + snprintf(device_caps, sizeof(device_caps), "%08x", this->dev.cap.device_caps); ADD_ITEM(SPA_KEY_API_V4L2_CAP_DEVICE_CAPS, device_caps); #undef ADD_ITEM info.props = &SPA_DICT_INIT(items, n_items); diff --git a/spa/plugins/videotestsrc/videotestsrc.c b/spa/plugins/videotestsrc/videotestsrc.c index 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/spatializer-7.1.conf b/src/daemon/filter-chain/spatializer-7.1.conf index bb19d5443..944ed6205 100644 --- a/src/daemon/filter-chain/spatializer-7.1.conf +++ b/src/daemon/filter-chain/spatializer-7.1.conf @@ -19,8 +19,6 @@ context.modules = [ name = spFL config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" - # The gain depends on the .sofa file in use - gain = 0.5 } control = { "Azimuth" = 30.0 @@ -34,7 +32,6 @@ context.modules = [ name = spFR config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" - gain = 0.5 } control = { "Azimuth" = 330.0 @@ -48,7 +45,6 @@ context.modules = [ name = spFC config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" - gain = 0.5 } control = { "Azimuth" = 0.0 @@ -62,7 +58,6 @@ context.modules = [ name = spRL config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" - gain = 0.5 } control = { "Azimuth" = 150.0 @@ -76,7 +71,6 @@ context.modules = [ name = spRR config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" - gain = 0.5 } control = { "Azimuth" = 210.0 @@ -90,7 +84,6 @@ context.modules = [ name = spSL config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" - gain = 0.5 } control = { "Azimuth" = 90.0 @@ -104,7 +97,6 @@ context.modules = [ name = spSR config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" - gain = 0.5 } control = { "Azimuth" = 270.0 @@ -118,7 +110,6 @@ context.modules = [ name = spLFE config = { filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" - gain = 0.5 } control = { "Azimuth" = 0.0 @@ -127,32 +118,8 @@ context.modules = [ } } - { type = builtin label = mixer name = mixL - control = { - # Set individual left mixer gain if needed - #"Gain 1" = 1.0 - #"Gain 2" = 1.0 - #"Gain 3" = 1.0 - #"Gain 4" = 1.0 - #"Gain 5" = 1.0 - #"Gain 6" = 1.0 - #"Gain 7" = 1.0 - #"Gain 8" = 1.0 - } - } - { type = builtin label = mixer name = mixR - control = { - # Set individual right mixer gain if needed - #"Gain 1" = 1.0 - #"Gain 2" = 1.0 - #"Gain 3" = 1.0 - #"Gain 4" = 1.0 - #"Gain 5" = 1.0 - #"Gain 6" = 1.0 - #"Gain 7" = 1.0 - #"Gain 8" = 1.0 - } - } + { type = builtin label = mixer name = mixL } + { type = builtin label = mixer name = mixR } ] links = [ # output 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/modules/module-raop/base64.h b/src/examples/base64.h similarity index 77% rename from src/modules/module-raop/base64.h rename to src/examples/base64.h index d8906c287..50e6d64b2 100644 --- a/src/modules/module-raop/base64.h +++ b/src/examples/base64.h @@ -1,17 +1,8 @@ /* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2026 Wim Taymans */ +/* SPDX-FileCopyrightText: Copyright © 2021 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 inline void base64_encode(const uint8_t *data, size_t len, char *enc, char pad) { static const char tab[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; @@ -29,7 +20,7 @@ static inline void pw_base64_encode(const uint8_t *data, size_t len, char *enc, *enc = '\0'; } -static inline size_t pw_base64_decode(const char *data, size_t len, uint8_t *dec) +static inline 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, @@ -53,10 +44,3 @@ static inline size_t pw_base64_decode(const char *data, size_t len, uint8_t *dec } return j; } - - -#ifdef __cplusplus -} -#endif - -#endif /* PIPEWIRE_BASE64_H */ diff --git a/src/examples/utils.h b/src/examples/utils.h deleted file mode 100644 index 079f22d55..000000000 --- a/src/examples/utils.h +++ /dev/null @@ -1,60 +0,0 @@ -/* PipeWire */ -/* SPDX-FileCopyrightText: Copyright © 2026 Red Hat */ -/* SPDX-License-Identifier: MIT */ - -static inline char * -encode_hex(const uint8_t *data, size_t size) -{ - FILE *ms; - char *encoded = NULL; - size_t encoded_size = 0; - size_t i; - - ms = open_memstream(&encoded, &encoded_size); - for (i = 0; i < size; i++) { - fprintf(ms, "%02x", data[i]); - } - fclose(ms); - - return encoded; -} - -static inline int8_t -ascii_hex_to_hex(uint8_t ascii_hex) -{ - if (ascii_hex >= '0' && ascii_hex <= '9') - return ascii_hex - '0'; - else if (ascii_hex >= 'a' && ascii_hex <= 'f') - return ascii_hex - 'a' + 10; - else if (ascii_hex >= 'A' && ascii_hex <= 'F') - return ascii_hex - 'A' + 10; - else - return -1; -} - -static inline int -decode_hex(const char *encoded, uint8_t *data, size_t size) -{ - size_t length; - size_t i; - - length = strlen(encoded); - - if (size < (length / 2) * sizeof(uint8_t)) - return -1; - - i = 0; - while (i < length) { - int8_t top = ascii_hex_to_hex(encoded[i]); - int8_t bottom = ascii_hex_to_hex(encoded[i + 1]); - - if (top == -1 || bottom == -1) - return -1; - - uint8_t el = top << 4 | bottom; - data[i / 2] = el; - i += 2; - } - - return 1; -} diff --git a/src/examples/video-play-fixate.c b/src/examples/video-play-fixate.c index 13b617a59..6fdc561e6 100644 --- a/src/examples/video-play-fixate.c +++ b/src/examples/video-play-fixate.c @@ -29,7 +29,7 @@ #include #include -#include "utils.h" +#include "base64.h" /* Comment out to test device ID negotation backward compatibility. */ #define SUPPORT_DEVICE_ID_NEGOTIATION 1 @@ -372,56 +372,46 @@ collect_device_ids(struct data *data, const char *json) int len; const char *value; struct spa_json sub; - char key[1024]; if ((len = spa_json_begin(&it, json, strlen(json), &value)) <= 0) { fprintf(stderr, "invalid device IDs value\n"); return; } - if (!spa_json_is_object(value, len)) { - fprintf(stderr, "device IDs not object\n"); + if (!spa_json_is_array(value, len)) { + fprintf(stderr, "device IDs not array\n"); return; } spa_json_enter(&it, &sub); - while ((len = spa_json_object_next(&sub, key, sizeof(key), &value)) > 0) { - struct spa_json devices_sub; + while ((len = spa_json_next(&sub, &value)) > 0) { + char *string; + union { + dev_t device_id; + uint8_t buffer[1024]; + } dec; - if (!spa_json_is_array(value, len)) { - fprintf(stderr, "available-devices not array\n"); + string = alloca(len + 1); + + if (!spa_json_is_string(value, len)) { + fprintf(stderr, "device ID not string\n"); return; } - spa_json_enter(&sub, &devices_sub); - while ((len = spa_json_next(&devices_sub, &value)) > 0) { - char *string; - union { - dev_t device_id; - uint8_t buffer[1024]; - } dec; - - string = alloca(len + 1); - - if (!spa_json_is_string(value, len)) { - fprintf(stderr, "device ID not string\n"); - return; - } - - if (spa_json_parse_string(value, len, string) <= 0) { - fprintf(stderr, "invalid device ID string\n"); - return; - } - - if (decode_hex(string, dec.buffer, sizeof (dec.buffer)) < 0) { - fprintf(stderr, "invalid device ID string\n"); - return; - } - - fprintf(stderr, "discovered device ID %u:%u\n", - major(dec.device_id), minor(dec.device_id)); - - data->device_ids[data->n_device_ids++] = dec.device_id; + if (spa_json_parse_string(value, len, string) <= 0) { + fprintf(stderr, "invalid device ID string\n"); + return; } + + if (base64_decode(string, strlen(string), + (uint8_t *)&dec.device_id) < sizeof(dev_t)) { + fprintf(stderr, "invalid device ID\n"); + return; + } + + fprintf(stderr, "discovered device ID %u:%u\n", + major(dec.device_id), minor(dec.device_id)); + + data->device_ids[data->n_device_ids++] = dec.device_id; } } @@ -448,9 +438,8 @@ discover_capabilities(struct data *data, const struct spa_pod *param) return; spa_dict_for_each(it, &dict) { - if (spa_streq(it->key, PW_CAPABILITY_DEVICE_ID_NEGOTIATION)) { - int version = atoi(it->value); - if (version >= 1) + if (spa_streq(it->key, PW_CAPABILITY_DEVICE_ID_NEGOTIATION) && + spa_streq(it->value, "true")) { data->device_negotiation_supported = true; } else if (spa_streq(it->key, PW_CAPABILITY_DEVICE_IDS)) { collect_device_ids(data, it->value); @@ -798,7 +787,7 @@ int main(int argc, char *argv[]) params[n_params++] = spa_param_dict_build_dict(&b, SPA_PARAM_Capability, &SPA_DICT_ITEMS( - SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "1"))); + SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "true"))); #endif /* now connect the stream, we need a direction (input/output), 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..0671ab1be 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 @@ -34,7 +30,7 @@ #include #include -#include "utils.h" +#include "base64.h" /* Comment out to test device ID negotation backward compatibility. */ #define SUPPORT_DEVICE_ID_NEGOTIATION 1 @@ -454,9 +450,8 @@ discover_capabilities(struct data *data, const struct spa_pod *param) return; spa_dict_for_each(it, &dict) { - if (spa_streq(it->key, PW_CAPABILITY_DEVICE_ID_NEGOTIATION)) { - int version = atoi(it->value); - if (version >= 1) + if (spa_streq(it->key, PW_CAPABILITY_DEVICE_ID_NEGOTIATION) && + spa_streq(it->value, "true")) { data->device_negotiation_supported = true; } } @@ -788,26 +783,23 @@ int main(int argc, char *argv[]) size_t i; ms = open_memstream(&device_ids, &device_ids_size); - fprintf(ms, "{\"available-devices\": ["); + fprintf(ms, "["); for (i = 0; i < SPA_N_ELEMENTS(devices); i++) { dev_t device_id = makedev(devices[i].major, devices[i].minor); - char *device_id_encoded; - - device_id_encoded = encode_hex((const uint8_t *) &device_id, sizeof (device_id)); + char device_id_encoded[256]; + base64_encode((const uint8_t *) &device_id, sizeof (device_id), device_id_encoded, '\0'); if (i > 0) fprintf(ms, ","); fprintf(ms, "\"%s\"", device_id_encoded); - - free(device_id_encoded); } - fprintf(ms, "]}"); + fprintf(ms, "]"); fclose(ms); #endif /* SUPPORT_DEVICE_IDS_LIST */ params[n_params++] = spa_param_dict_build_dict(&b, SPA_PARAM_Capability, - &SPA_DICT_ITEMS(SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "1"), + &SPA_DICT_ITEMS(SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "true"), #ifdef SUPPORT_DEVICE_IDS_LIST SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_IDS, device_ids) #endif /* SUPPORT_DEVICE_IDS_LIST */ diff --git a/src/gst/gstpipewirepool.c b/src/gst/gstpipewirepool.c index 5e201d315..5581c64ac 100644 --- a/src/gst/gstpipewirepool.c +++ b/src/gst/gstpipewirepool.c @@ -209,6 +209,8 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b) data->b = b; data->buf = buf; data->crop = spa_buffer_find_meta_data (b->buffer, SPA_META_VideoCrop, sizeof(*data->crop)); + if (data->crop) + gst_buffer_add_video_crop_meta(buf); data->videotransform = spa_buffer_find_meta_data (b->buffer, SPA_META_VideoTransform, sizeof(*data->videotransform)); data->cursor = spa_buffer_find_meta_data (b->buffer, SPA_META_Cursor, sizeof(*data->cursor)); @@ -432,25 +434,26 @@ release_buffer (GstBufferPool * pool, GstBuffer *buffer) GST_LOG_OBJECT (pool, "release buffer %p", buffer); GstPipeWirePoolData *data = gst_pipewire_pool_get_data(buffer); - GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool); - g_autoptr (GstPipeWireStream) s = g_weak_ref_get (&p->stream); GST_OBJECT_LOCK (pool); - pw_thread_loop_lock (s->core->loop); if (!data->queued && data->b != NULL) { + GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool); + g_autoptr (GstPipeWireStream) s = g_weak_ref_get (&p->stream); int res; + pw_thread_loop_lock (s->core->loop); + if ((res = pw_stream_return_buffer (s->pwstream, data->b)) < 0) { GST_ERROR_OBJECT (pool,"can't return buffer %p; gstbuffer : %p, %s",data->b, buffer, spa_strerror(res)); } else { data->queued = TRUE; GST_DEBUG_OBJECT (pool, "returned buffer %p; gstbuffer:%p", data->b, buffer); } - } - pw_thread_loop_unlock (s->core->loop); + pw_thread_loop_unlock (s->core->loop); + } GST_OBJECT_UNLOCK (pool); } diff --git a/src/gst/gstpipewiresrc.c b/src/gst/gstpipewiresrc.c index 3c57028b4..8bfd799a1 100644 --- a/src/gst/gstpipewiresrc.c +++ b/src/gst/gstpipewiresrc.c @@ -531,7 +531,6 @@ gst_pipewire_src_init (GstPipeWireSrc * src) src->autoconnect = DEFAULT_AUTOCONNECT; src->min_latency = 0; src->max_latency = GST_CLOCK_TIME_NONE; - src->last_buffer_clock_time = GST_CLOCK_TIME_NONE; src->n_buffers = 0; src->flushing_on_remove_buffer = FALSE; src->on_disconnect = DEFAULT_ON_DISCONNECT; @@ -782,7 +781,7 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc) crop = data->crop; if (crop) { - GstVideoCropMeta *meta = gst_buffer_add_video_crop_meta(buf); + GstVideoCropMeta *meta = gst_buffer_get_video_crop_meta(buf); if (meta) { meta->x = crop->region.position.x; meta->y = crop->region.position.y; @@ -839,17 +838,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 +1073,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; @@ -1606,31 +1598,16 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) GST_LOG_OBJECT (pwsrc, "EOS, send last buffer"); break; } else if (timeout && pwsrc->last_buffer != NULL) { - buf = gst_buffer_copy (pwsrc->last_buffer); update_time = TRUE; + buf = gst_buffer_ref(pwsrc->last_buffer); GST_LOG_OBJECT (pwsrc, "timeout, send keepalive buffer"); break; } else { buf = dequeue_buffer (pwsrc); GST_LOG_OBJECT (pwsrc, "popped buffer %p", buf); if (buf != NULL) { - if (pwsrc->resend_last || pwsrc->keepalive_time > 0) { - GstClock *clock; - GstBuffer *old; - - old = pwsrc->last_buffer; - pwsrc->last_buffer = gst_buffer_copy (buf); - gst_buffer_unref (old); - gst_buffer_add_parent_buffer_meta (pwsrc->last_buffer, buf); - - clock = gst_element_get_clock (GST_ELEMENT_CAST (pwsrc)); - if (clock != NULL) { - pwsrc->last_buffer_clock_time = gst_clock_get_time (clock); - gst_object_unref (clock); - } else { - pwsrc->last_buffer_clock_time = GST_CLOCK_TIME_NONE; - } - } + if (pwsrc->resend_last || pwsrc->keepalive_time > 0) + gst_buffer_replace (&pwsrc->last_buffer, buf); break; } } @@ -1655,33 +1632,21 @@ gst_pipewire_src_create (GstPushSrc * psrc, GstBuffer ** buffer) if (update_time) { GstClock *clock; - GstClockTime current_clock_time; + GstClockTime pts, dts; clock = gst_element_get_clock (GST_ELEMENT_CAST (pwsrc)); if (clock != NULL) { - current_clock_time = gst_clock_get_time (clock); + pts = dts = gst_clock_get_time (clock); gst_object_unref (clock); } else { - current_clock_time = GST_CLOCK_TIME_NONE; + pts = dts = GST_CLOCK_TIME_NONE; } - if (GST_CLOCK_TIME_IS_VALID (current_clock_time) && - GST_CLOCK_TIME_IS_VALID (pwsrc->last_buffer_clock_time) && - GST_CLOCK_TIME_IS_VALID (GST_BUFFER_PTS (*buffer)) && - GST_CLOCK_TIME_IS_VALID (GST_BUFFER_DTS (*buffer))) { - GstClockTime diff; - - diff = current_clock_time - pwsrc->last_buffer_clock_time; - - GST_BUFFER_PTS (*buffer) += diff; - GST_BUFFER_DTS (*buffer) += diff; - } else { - GST_BUFFER_PTS (*buffer) = GST_BUFFER_DTS (*buffer) = current_clock_time; - } + GST_BUFFER_PTS (*buffer) = pts; + GST_BUFFER_DTS (*buffer) = dts; GST_LOG_OBJECT (pwsrc, "Sending keepalive buffer pts/dts: %" GST_TIME_FORMAT - " (%" G_GUINT64_FORMAT ")", GST_TIME_ARGS (current_clock_time), - current_clock_time); + " (%" G_GUINT64_FORMAT ")", GST_TIME_ARGS (pts), pts); } return GST_FLOW_OK; diff --git a/src/gst/gstpipewiresrc.h b/src/gst/gstpipewiresrc.h index 4b0f57e0e..869877fcb 100644 --- a/src/gst/gstpipewiresrc.h +++ b/src/gst/gstpipewiresrc.h @@ -83,7 +83,6 @@ struct _GstPipeWireSrc { GstClockTime max_latency; GstBuffer *last_buffer; - GstClockTime last_buffer_clock_time; enum spa_meta_videotransform_value transform_value; diff --git a/src/modules/meson.build b/src/modules/meson.build index 11b29a117..0605900e5 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', @@ -277,6 +276,10 @@ pipewire_module_link_factory = shared_library('pipewire-module-link-factory', pipewire_module_protocol_deps = [mathlib, dl_lib, pipewire_dep] +if systemd_dep.found() + pipewire_module_protocol_deps += systemd_dep +endif + if selinux_dep.found() pipewire_module_protocol_deps += selinux_dep endif @@ -296,17 +299,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 +375,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 +536,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,42 +563,26 @@ 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') -# Several modules (rtp-sink, rtp-source, raop-sink) use the same code -# for actual RTP transport. To not have to recompile the same code -# multiple times, and to make the build script a little more robust -# (by avoiding build script code duplication), create a static library -# that contains that common code. -pipewire_module_rtp_common_lib = static_library('pipewire-module-rtp-common-lib', - [ 'module-rtp/stream.c' ], - include_directories : [configinc], - install : false, - dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, opus_dep], -) -pipewire_module_rtp_common_dep = declare_dependency( - link_with: pipewire_module_rtp_common_lib, - dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, opus_dep], -) - 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 +591,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') @@ -637,12 +605,13 @@ build_module_raop = openssl_lib.found() if build_module_raop pipewire_module_raop_sink = shared_library('pipewire-module-raop-sink', [ 'module-raop-sink.c', - 'module-raop/rtsp-client.c' ], + 'module-raop/rtsp-client.c', + 'module-rtp/stream.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, - dependencies : [pipewire_module_rtp_common_dep, openssl_lib], + dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, opus_dep, openssl_lib], ) endif summary({'raop-sink (requires OpenSSL)': build_module_raop}, bool_yn: true, section: 'Optional Modules') @@ -651,33 +620,36 @@ roc_dep = dependency('roc', version: '>= 0.4.0', required: get_option('roc')) summary({'ROC': roc_dep.found()}, bool_yn: true, section: 'Streaming between daemons') pipewire_module_rtp_source = shared_library('pipewire-module-rtp-source', - [ 'module-rtp-source.c' ], + [ 'module-rtp-source.c', + 'module-rtp/stream.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, - dependencies : [pipewire_module_rtp_common_dep], + dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, opus_dep], ) pipewire_module_rtp_sink = shared_library('pipewire-module-rtp-sink', - [ 'module-rtp-sink.c' ], + [ 'module-rtp-sink.c', + 'module-rtp/stream.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, install_rpath: modules_install_dir, - dependencies : [pipewire_module_rtp_common_dep], + dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, opus_dep], ) 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-rtp/stream.c', + '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 : [mathlib, dl_lib, rt_lib, pipewire_dep, avahi_dep, opus_dep], ) endif @@ -710,39 +682,10 @@ 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', - [ 'module-roc-sink.c', 'module-roc/common.c'], + [ 'module-roc-sink.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, @@ -751,7 +694,7 @@ if build_module_roc ) pipewire_module_roc_source = shared_library('pipewire-module-roc-source', - [ 'module-roc-source.c', 'module-roc/common.c' ], + [ 'module-roc-source.c' ], include_directories : [configinc], install : true, install_dir : modules_install_dir, diff --git a/src/modules/module-client-node/client-node.c b/src/modules/module-client-node/client-node.c index 14fda3a77..0253591cf 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; @@ -263,8 +264,7 @@ static void clear_data(struct impl *impl, struct spa_data *d) case SPA_DATA_DmaBuf: case SPA_DATA_SyncObj: pw_log_debug("%p: close fd:%d", impl, (int)d->fd); - if (d->fd != -1) - close(d->fd); + close(d->fd); break; } } @@ -282,7 +282,7 @@ static int clear_buffers(struct impl *impl, struct mix *mix) for (i = 0; i < mix->n_buffers; i++) { struct buffer *b = &mix->buffers[i]; - 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 +299,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 +330,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 +350,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 +366,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 +508,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 +542,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 +598,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 +609,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 +624,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 +640,7 @@ node_port_enum_params(struct impl *impl, int seq, if (count == num) break; } - return 0; + return found ? 0 : -ENOENT; } static int @@ -782,7 +781,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 +851,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); @@ -865,11 +864,8 @@ do_port_use_buffers(struct impl *impl, memcpy(&b->datas[j], d, sizeof(struct spa_data)); - if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) { - b->datas[j].fd = -1; - b->datas[j].data = SPA_UINT32_TO_PTR(SPA_ID_INVALID); + if (flags & SPA_NODE_BUFFERS_FLAG_ALLOC) continue; - } switch (d->type) { case SPA_DATA_DmaBuf: @@ -885,7 +881,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 +892,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 +935,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 +948,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 +1009,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 +1047,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 +1070,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 +1098,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 +1107,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 +1122,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 +1149,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 +1166,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); } } @@ -1774,6 +1770,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..f6a76bed0 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); @@ -1540,6 +1556,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); if (pw_properties_get(props, PW_KEY_NODE_GROUP) == NULL) pw_properties_set(props, PW_KEY_NODE_GROUP, "ffado-group"); + if (pw_properties_get(props, PW_KEY_NODE_LINK_GROUP) == NULL) + pw_properties_set(props, PW_KEY_NODE_LINK_GROUP, "ffado-group"); if (pw_properties_get(props, PW_KEY_NODE_PAUSE_ON_IDLE) == NULL) pw_properties_set(props, PW_KEY_NODE_PAUSE_ON_IDLE, "false"); diff --git a/src/modules/module-filter-chain.c b/src/modules/module-filter-chain.c index 64a92c5ac..eb6a6c8ef 100644 --- a/src/modules/module-filter-chain.c +++ b/src/modules/module-filter-chain.c @@ -193,10 +193,6 @@ extern struct spa_handle_factory spa_filter_graph_factory; * graph will then be duplicated as many times to match the number of input/output * channels of the streams. * - * If the graph has no inputs and the capture channels is set as 0, only the - * playback stream will be created. Likewise, if there are no outputs and the - * playback channels is 0, there will be no capture stream created. - * * ### Volumes * * Normally the volume of the sink/source is handled by the stream software volume. @@ -656,40 +652,6 @@ extern struct spa_handle_factory spa_filter_graph_factory; * of "Attack (s)" seconds. The noise gate stays open for at least "Hold (s)" * seconds before it can close again. * - * ### Busy - * - * The `busy` plugin has no input or output ports and it can be used to keep the - * CPU or graph busy for the given percent of time. - * - * The node requires a `config` section with extra configuration: - * - *\code{.unparsed} - * filter.graph = { - * nodes = [ - * { - * type = builtin - * name = ... - * label = busy - * config = { - * wait-percent = 0.0 - * cpu-percent = 50.0 - * } - * ... - * } - * } - * ... - * } - *\endcode - * - * - `wait-percent` the percentage of time to wait. This keeps the graph busy but - * not the CPU. Default 0.0 - * - `cpu-percent` the percentage of time to keep the CPU busy. This keeps both the - * graph and CPU busy. Default 0.0 - * - * ### Null - * - * The `null` plugin has one data input "In" and one control input "Control" that - * simply discards the data. * * ## SOFA filters * @@ -717,7 +679,6 @@ extern struct spa_handle_factory spa_filter_graph_factory; * blocksize = ... * tailsize = ... * filename = ... - * gain = ... * } * control = { * "Azimuth" = ... @@ -734,10 +695,9 @@ extern struct spa_handle_factory spa_filter_graph_factory; * - `blocksize` specifies the size of the blocks to use in the FFT. It is a value * between 64 and 256. When not specified, this value is * computed automatically from the number of samples in the file. - * - `tailsize` specifies the size of the tail blocks to use in the FFT. - * - `filename` The SOFA file to load. SOFA files usually end in the .sofa extension - * and contain the HRTF for the various spatial positions. - * - `gain` the overall gain to apply to the IR file. + * - `tailsize` specifies the size of the tail blocks to use in the FFT. + * - `filename` The SOFA file to load. SOFA files usually end in the .sofa extension + * and contain the HRTF for the various spatial positions. * * - `Azimuth` controls the azimuth, this is the direction the sound is coming from * in degrees between 0 and 360. 0 is straight ahead. 90 is left, 180 @@ -1255,105 +1215,92 @@ static void capture_destroy(void *d) impl->capture = NULL; } -static void do_process(struct impl *impl) -{ - struct pw_buffer *in, *out; - uint32_t i, n_in = 0, n_out = 0, data_size = 0; - struct spa_data *bd; - const void *cin[128]; - void *cout[128]; - - in = out = NULL; - if (impl->capture) { - while (true) { - struct pw_buffer *t; - if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL) - break; - if (in) - pw_stream_queue_buffer(impl->capture, in); - in = t; - } - if (in == NULL) { - pw_log_debug("%p: out of capture buffers: %m", impl); - } else { - for (i = 0; i < in->buffer->n_datas; i++) { - uint32_t offs, size; - - bd = &in->buffer->datas[i]; - - offs = SPA_MIN(bd->chunk->offset, bd->maxsize); - size = SPA_MIN(bd->chunk->size, bd->maxsize - offs); - - cin[n_in++] = SPA_PTROFF(bd->data, offs, void); - - data_size = i == 0 ? size : SPA_MIN(data_size, size); - } - } - } - if (impl->playback) { - out = pw_stream_dequeue_buffer(impl->playback); - if (out == NULL) { - pw_log_debug("%p: out of playback buffers: %m", impl); - } else { - if (data_size == 0) - data_size = out->requested * sizeof(float); - - for (i = 0; i < out->buffer->n_datas; i++) { - bd = &out->buffer->datas[i]; - - data_size = SPA_MIN(data_size, bd->maxsize); - - cout[n_out++] = bd->data; - - bd->chunk->offset = 0; - bd->chunk->size = data_size; - bd->chunk->stride = sizeof(float); - } - } - pw_log_trace_fp("%p: size:%d requested:%"PRIu64, impl, - data_size, out->requested); - } - - for (; n_in < impl->n_inputs; i++) - cin[n_in++] = NULL; - for (; n_out < impl->n_outputs; i++) - cout[n_out++] = NULL; - - if (impl->graph_active) - spa_filter_graph_process(impl->graph, cin, cout, data_size / sizeof(float)); - - if (in != NULL) - pw_stream_queue_buffer(impl->capture, in); - if (out != NULL) - pw_stream_queue_buffer(impl->playback, out); -} - static void capture_process(void *d) { struct impl *impl = d; int res; - - if (impl->playback) { - if ((res = pw_stream_trigger_process(impl->playback)) < 0) { - pw_log_debug("playback trigger error: %s", spa_strerror(res)); - while (impl->capture) { - struct pw_buffer *t; - if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL) - break; - /* playback part is not ready, consume, discard and recycle - * the capture buffers */ - pw_stream_queue_buffer(impl->capture, t); - } + if ((res = pw_stream_trigger_process(impl->playback)) < 0) { + pw_log_debug("playback trigger error: %s", spa_strerror(res)); + while (true) { + struct pw_buffer *t; + if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL) + break; + /* playback part is not ready, consume, discard and recycle + * the capture buffers */ + pw_stream_queue_buffer(impl->capture, t); } - } else { - do_process(impl); } } static void playback_process(void *d) { struct impl *impl = d; - do_process(impl); + struct pw_buffer *in, *out; + uint32_t i, data_size = 0; + int32_t stride = 0; + struct spa_data *bd; + const void *cin[128]; + void *cout[128]; + + in = NULL; + while (true) { + struct pw_buffer *t; + if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL) + break; + if (in) + pw_stream_queue_buffer(impl->capture, in); + in = t; + } + if (in == NULL) + pw_log_debug("%p: out of capture buffers: %m", impl); + + if ((out = pw_stream_dequeue_buffer(impl->playback)) == NULL) + pw_log_debug("%p: out of playback buffers: %m", impl); + + if (in == NULL || out == NULL) + goto done; + + for (i = 0; i < in->buffer->n_datas; i++) { + uint32_t offs, size; + + bd = &in->buffer->datas[i]; + + offs = SPA_MIN(bd->chunk->offset, bd->maxsize); + size = SPA_MIN(bd->chunk->size, bd->maxsize - offs); + + cin[i] = SPA_PTROFF(bd->data, offs, void); + + data_size = i == 0 ? size : SPA_MIN(data_size, size); + stride = SPA_MAX(stride, bd->chunk->stride); + } + for (; i < impl->n_inputs; i++) + cin[i] = NULL; + + for (i = 0; i < out->buffer->n_datas; i++) { + bd = &out->buffer->datas[i]; + + data_size = SPA_MIN(data_size, bd->maxsize); + + cout[i] = bd->data; + + bd->chunk->offset = 0; + bd->chunk->size = data_size; + bd->chunk->stride = stride; + } + for (; i < impl->n_outputs; i++) + cout[i] = NULL; + + pw_log_trace_fp("%p: stride:%d size:%d requested:%"PRIu64" (%"PRIu64")", impl, + stride, data_size, out->requested, out->requested * stride); + + if (impl->graph_active) + spa_filter_graph_process(impl->graph, cin, cout, data_size / sizeof(float)); + +done: + if (in != NULL) + pw_stream_queue_buffer(impl->capture, in); + if (out != NULL) + pw_stream_queue_buffer(impl->playback, out); } static int activate_graph(struct impl *impl) @@ -1422,9 +1369,6 @@ static void update_latency(struct impl *impl, enum spa_direction direction, bool struct pw_stream *s = direction == SPA_DIRECTION_OUTPUT ? impl->playback : impl->capture; - if (s == NULL) - return; - spa_pod_builder_init(&b, buffer, sizeof(buffer)); latency = impl->latency[direction]; spa_process_latency_info_add(&impl->process_latency, &latency); @@ -1481,13 +1425,10 @@ static void param_tag_changed(struct impl *impl, const struct spa_pod *param, if (param == 0 || spa_tag_parse(param, &tag, &state) < 0) return; - if (tag.direction == SPA_DIRECTION_INPUT) { - if (impl->capture) - pw_stream_update_params(impl->capture, params, 1); - } else { - if (impl->playback) - pw_stream_update_params(impl->playback, params, 1); - } + if (tag.direction == SPA_DIRECTION_INPUT) + pw_stream_update_params(impl->capture, params, 1); + else + pw_stream_update_params(impl->playback, params, 1); } static void capture_state_changed(void *data, enum pw_stream_state old, @@ -1564,7 +1505,8 @@ static void param_changed(struct impl *impl, uint32_t id, const struct spa_pod * return; error: - pw_stream_set_error(stream, res, "can't start graph: %s", spa_strerror(res)); + pw_stream_set_error(direction == SPA_DIRECTION_INPUT ? impl->capture : impl->playback, + res, "can't start graph: %s", spa_strerror(res)); } static void capture_param_changed(void *data, uint32_t id, const struct spa_pod *param) @@ -1623,7 +1565,7 @@ static void playback_state_changed(void *data, enum pw_stream_state old, } return; error: - pw_stream_set_error(impl->playback, res, "can't start graph: %s", + pw_stream_set_error(impl->capture, res, "can't start graph: %s", spa_strerror(res)); } @@ -1652,39 +1594,43 @@ static const struct pw_stream_events out_stream_events = { static int setup_streams(struct impl *impl) { int res; - uint32_t i, n_params, *offs, flags; + uint32_t i, n_params, *offs; struct pw_array offsets; const struct spa_pod **params = NULL; struct spa_pod_dynamic_builder b; struct spa_filter_graph *graph = impl->graph; - if (impl->capture_info.channels > 0) { - impl->capture = pw_stream_new(impl->core, - "filter capture", impl->capture_props); - impl->capture_props = NULL; - if (impl->capture == NULL) - return -errno; + impl->capture = pw_stream_new(impl->core, + "filter capture", impl->capture_props); + impl->capture_props = NULL; + if (impl->capture == NULL) + return -errno; - pw_stream_add_listener(impl->capture, - &impl->capture_listener, - &in_stream_events, impl); - } + pw_stream_add_listener(impl->capture, + &impl->capture_listener, + &in_stream_events, impl); - if (impl->playback_info.channels > 0) { - impl->playback = pw_stream_new(impl->core, - "filter playback", impl->playback_props); - impl->playback_props = NULL; - if (impl->playback == NULL) - return -errno; + impl->playback = pw_stream_new(impl->core, + "filter playback", impl->playback_props); + impl->playback_props = NULL; + if (impl->playback == NULL) + return -errno; - pw_stream_add_listener(impl->playback, - &impl->playback_listener, - &out_stream_events, impl); - } + pw_stream_add_listener(impl->playback, + &impl->playback_listener, + &out_stream_events, impl); spa_pod_dynamic_builder_init(&b, NULL, 0, 4096); pw_array_init(&offsets, 512); + if ((offs = pw_array_add(&offsets, sizeof(uint32_t))) == NULL) { + res = -errno; + goto done; + } + *offs = b.b.state.offset; + spa_format_audio_raw_build(&b.b, + SPA_PARAM_EnumFormat, &impl->capture_info); + for (i = 0;; i++) { uint32_t save = b.b.state.offset; if (spa_filter_graph_enum_prop_info(graph, i, &b.b, NULL) != 1) @@ -1707,7 +1653,7 @@ static int setup_streams(struct impl *impl) res = -ENOMEM; goto done; } - if ((params = calloc(n_params+1, sizeof(struct spa_pod*))) == NULL) { + if ((params = calloc(n_params, sizeof(struct spa_pod*))) == NULL) { res = -errno; goto done; } @@ -1716,44 +1662,32 @@ static int setup_streams(struct impl *impl) for (i = 0; i < n_params; i++) 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 | + res = pw_stream_connect(impl->capture, + PW_DIRECTION_INPUT, + PW_ID_ANY, + PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | - PW_STREAM_FLAG_RT_PROCESS; - if (impl->playback) - flags |= PW_STREAM_FLAG_ASYNC; + PW_STREAM_FLAG_RT_PROCESS | + PW_STREAM_FLAG_ASYNC, + params, n_params); - res = pw_stream_connect(impl->capture, - PW_DIRECTION_INPUT, - PW_ID_ANY, - flags, - params, n_params); + spa_pod_dynamic_builder_clean(&b); + if (res < 0) + goto done; - spa_pod_dynamic_builder_clean(&b); - if (res < 0) - goto done; + n_params = 0; + spa_pod_dynamic_builder_init(&b, NULL, 0, 4096); + params[n_params++] = spa_format_audio_raw_build(&b.b, + SPA_PARAM_EnumFormat, &impl->playback_info); - n_params = 0; - spa_pod_dynamic_builder_init(&b, NULL, 0, 4096); - } - if (impl->playback) { - params[n_params++] = spa_format_audio_raw_build(&b.b, - SPA_PARAM_EnumFormat, &impl->playback_info); - - flags = PW_STREAM_FLAG_AUTOCONNECT | + res = pw_stream_connect(impl->playback, + PW_DIRECTION_OUTPUT, + PW_ID_ANY, + PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | - PW_STREAM_FLAG_RT_PROCESS; - if (impl->capture) - flags |= PW_STREAM_FLAG_TRIGGER; - - res = pw_stream_connect(impl->playback, - PW_DIRECTION_OUTPUT, - PW_ID_ANY, - flags, - params, n_params); - } + PW_STREAM_FLAG_RT_PROCESS | + PW_STREAM_FLAG_TRIGGER, + params, n_params); spa_pod_dynamic_builder_clean(&b); done: @@ -1777,11 +1711,20 @@ static void graph_info(void *object, const struct spa_filter_graph_info *info) { struct impl *impl = object; struct spa_dict *props = info->props; - uint32_t i, val = 0; + uint32_t i; + + if (impl->capture_info.channels == 0) + impl->capture_info.channels = info->n_inputs; + if (impl->playback_info.channels == 0) + impl->playback_info.channels = info->n_outputs; impl->n_inputs = info->n_inputs; impl->n_outputs = info->n_outputs; + if (impl->capture_info.channels == impl->playback_info.channels) { + copy_position(&impl->capture_info, &impl->playback_info); + copy_position(&impl->playback_info, &impl->capture_info); + } for (i = 0; props && i < props->n_items; i++) { const char *k = props->items[i].key; const char *s = props->items[i].value; @@ -1795,22 +1738,6 @@ static void graph_info(void *object, const struct spa_filter_graph_info *info) } } } - else if (spa_streq(k, "n_default_inputs") && - impl->capture_info.channels == 0 && - spa_atou32(s, &val, 0)) { - pw_log_info("using default inputs %d", val); - impl->capture_info.channels = val; - } - else if (spa_streq(k, "n_default_outputs") && - impl->playback_info.channels == 0 && - spa_atou32(s, &val, 0)) { - pw_log_info("using default outputs %d", val); - impl->playback_info.channels = val; - } - } - if (impl->capture_info.channels == impl->playback_info.channels) { - copy_position(&impl->capture_info, &impl->playback_info); - copy_position(&impl->playback_info, &impl->capture_info); } } @@ -1833,11 +1760,7 @@ static void graph_props_changed(void *object, enum spa_direction direction) spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096); spa_filter_graph_get_props(graph, &b.b, (struct spa_pod **)¶ms[0]); - if (impl->capture) - pw_stream_update_params(impl->capture, params, 1); - else if (impl->playback) - pw_stream_update_params(impl->playback, params, 1); - + pw_stream_update_params(impl->capture, params, 1); spa_pod_dynamic_builder_clean(&b); } diff --git a/src/modules/module-jack-tunnel.c b/src/modules/module-jack-tunnel.c index 4b22f7883..760da1669 100644 --- a/src/modules/module-jack-tunnel.c +++ b/src/modules/module-jack-tunnel.c @@ -243,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) @@ -263,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) @@ -274,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)); } } @@ -303,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); } @@ -1133,13 +1157,11 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_set(impl->sink.props, PW_KEY_MEDIA_CLASS, "Audio/Sink"); pw_properties_set(impl->sink.props, PW_KEY_PRIORITY_DRIVER, "30001"); - pw_properties_set(impl->sink.props, PW_KEY_PRIORITY_SESSION, "2001"); pw_properties_set(impl->sink.props, PW_KEY_NODE_NAME, "jack_sink"); pw_properties_set(impl->sink.props, PW_KEY_NODE_DESCRIPTION, "JACK Sink"); pw_properties_set(impl->source.props, PW_KEY_MEDIA_CLASS, "Audio/Source"); pw_properties_set(impl->source.props, PW_KEY_PRIORITY_DRIVER, "30000"); - pw_properties_set(impl->source.props, PW_KEY_PRIORITY_SESSION, "2000"); pw_properties_set(impl->source.props, PW_KEY_NODE_NAME, "jack_source"); pw_properties_set(impl->source.props, PW_KEY_NODE_DESCRIPTION, "JACK Source"); diff --git a/src/modules/module-netjack2-driver.c b/src/modules/module-netjack2-driver.c index ca4e70195..07a756266 100644 --- a/src/modules/module-netjack2-driver.c +++ b/src/modules/module-netjack2-driver.c @@ -1319,11 +1319,9 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true"); pw_properties_set(impl->sink.props, PW_KEY_PRIORITY_DRIVER, "40000"); - pw_properties_set(impl->sink.props, PW_KEY_PRIORITY_SESSION, "2000"); pw_properties_set(impl->sink.props, PW_KEY_NODE_NAME, "netjack2_driver_send"); pw_properties_set(impl->source.props, PW_KEY_PRIORITY_DRIVER, "40001"); - pw_properties_set(impl->source.props, PW_KEY_PRIORITY_SESSION, "2001"); pw_properties_set(impl->source.props, PW_KEY_NODE_NAME, "netjack2_driver_receive"); if ((str = pw_properties_get(props, "sink.props")) != NULL) diff --git a/src/modules/module-netjack2-manager.c b/src/modules/module-netjack2-manager.c index a9af6a9b0..0ff62d93b 100644 --- a/src/modules/module-netjack2-manager.c +++ b/src/modules/module-netjack2-manager.c @@ -1393,6 +1393,8 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) pw_properties_set(props, PW_KEY_NODE_VIRTUAL, "true"); if (pw_properties_get(props, PW_KEY_NODE_NETWORK) == NULL) pw_properties_set(props, PW_KEY_NODE_NETWORK, "true"); + if (pw_properties_get(props, PW_KEY_NODE_LINK_GROUP) == NULL) + pw_properties_set(props, PW_KEY_NODE_LINK_GROUP, "jack-group"); if (pw_properties_get(props, PW_KEY_NODE_ALWAYS_PROCESS) == NULL) pw_properties_set(props, PW_KEY_NODE_ALWAYS_PROCESS, "true"); if (pw_properties_get(props, PW_KEY_NODE_LOCK_QUANTUM) == NULL) diff --git a/src/modules/module-netjack2/peer.c b/src/modules/module-netjack2/peer.c index 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 1709ec9a5..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); diff --git a/src/modules/module-protocol-native.c b/src/modules/module-protocol-native.c index 98a43829b..96e99f35e 100644 --- a/src/modules/module-protocol-native.c +++ b/src/modules/module-protocol-native.c @@ -34,6 +34,10 @@ #include #include +#ifdef HAVE_SYSTEMD +#include +#endif + #ifdef HAVE_SELINUX #include #endif @@ -41,7 +45,6 @@ #include #include -#include "network-utils.h" #include "pipewire/private.h" #include "modules/module-protocol-native/connection.h" @@ -906,12 +909,13 @@ static int add_socket(struct pw_protocol *protocol, struct server *s, struct soc int fd = -1, res; bool activated = false; +#ifdef HAVE_SYSTEMD { - int i, n = listen_fds(); + int i, n = sd_listen_fds(0); for (i = 0; i < n; ++i) { - if (is_socket_unix(LISTEN_FDS_START + i, SOCK_STREAM, - s->addr.sun_path) > 0) { - fd = LISTEN_FDS_START + i; + if (sd_is_socket_unix(SD_LISTEN_FDS_START + i, SOCK_STREAM, + 1, s->addr.sun_path, 0) > 0) { + fd = SD_LISTEN_FDS_START + i; activated = true; pw_log_info("server %p: Found socket activation socket for '%s'", s, s->addr.sun_path); @@ -919,6 +923,7 @@ static int add_socket(struct pw_protocol *protocol, struct server *s, struct soc } } } +#endif if (fd < 0) { struct stat socket_stat; diff --git a/src/modules/module-protocol-pulse/client.h b/src/modules/module-protocol-pulse/client.h index 81d790b51..2c413e51c 100644 --- a/src/modules/module-protocol-pulse/client.h +++ b/src/modules/module-protocol-pulse/client.h @@ -62,12 +62,8 @@ struct client { struct pw_manager_object *metadata_schema_sm_settings; bool have_force_mono_audio; - bool default_force_mono_audio; - bool have_bluetooth_headset_autoswitch; - bool default_bluetooth_headset_autoswitch; struct pw_manager_object *metadata_sm_settings; bool force_mono_audio; - bool bluetooth_headset_autoswitch; uint32_t connect_tag; diff --git a/src/modules/module-protocol-pulse/defs.h b/src/modules/module-protocol-pulse/defs.h index f3e25f137..51e2453ff 100644 --- a/src/modules/module-protocol-pulse/defs.h +++ b/src/modules/module-protocol-pulse/defs.h @@ -39,8 +39,6 @@ #define MODULE_INDEX_MASK 0xfffffffu #define MODULE_FLAG (1u << 29) -#define STREAM_CREATE_TIMEOUT (35 * SPA_NSEC_PER_SEC) - #define DEFAULT_SINK "@DEFAULT_SINK@" #define DEFAULT_SOURCE "@DEFAULT_SOURCE@" #define DEFAULT_MONITOR "@DEFAULT_MONITOR@" @@ -326,6 +324,5 @@ static inline uint32_t port_type_value(const char *port_type) #define METADATA_TARGET_NODE "target.node" #define METADATA_TARGET_OBJECT "target.object" #define METADATA_FEATURES_AUDIO_MONO "node.features.audio.mono" -#define METADATA_BLUETOOTH_HEADSET_AUTOSWITCH "bluetooth.autoswitch-to-headset-profile" #endif /* PULSE_SERVER_DEFS_H */ diff --git a/src/modules/module-protocol-pulse/manager.c b/src/modules/module-protocol-pulse/manager.c index 72d51b020..5b597f4eb 100644 --- a/src/modules/module-protocol-pulse/manager.c +++ b/src/modules/module-protocol-pulse/manager.c @@ -718,7 +718,7 @@ static void on_core_error(void *data, uint32_t id, int seq, int res, const char { struct manager *m = data; - if (id == PW_ID_CORE && (res == -EPIPE || res == -EPROTO)) { + if (id == PW_ID_CORE && res == -EPIPE) { pw_log_debug("connection error: %d, %s", res, message); manager_emit_disconnect(m); } diff --git a/src/modules/module-protocol-pulse/message-handler.c b/src/modules/module-protocol-pulse/message-handler.c index b44d4f422..48c7c7be3 100644 --- a/src/modules/module-protocol-pulse/message-handler.c +++ b/src/modules/module-protocol-pulse/message-handler.c @@ -110,59 +110,14 @@ static int core_object_force_mono_output(struct client *client, const char *para if (spa_streq(params, "true")) { ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE, METADATA_FEATURES_AUDIO_MONO, "Spa:String:JSON", "true"); - client->force_mono_audio = true; } else if (spa_streq(params, "false")) { ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE, METADATA_FEATURES_AUDIO_MONO, "Spa:String:JSON", "false"); - client->force_mono_audio = false; } else if (spa_streq(params, "null")) { ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE, METADATA_FEATURES_AUDIO_MONO, NULL, NULL); - client->force_mono_audio = client->default_force_mono_audio; } else { - fprintf(response, "Value must be true, false, or null"); - return -EINVAL; - } - - if (ret < 0) - fprintf(response, "Could not set metadata: %s", spa_strerror(ret)); - else - fprintf(response, "%s", params); - - return ret; - } -} - -static int core_object_bluetooth_headset_autoswitch(struct client *client, const char *params, FILE *response) -{ - if (!client->have_bluetooth_headset_autoswitch) { - /* Not supported, return a null value to indicate that */ - fprintf(response, "null"); - return 0; - } - - if (!params || params[0] == '\0') { - /* No parameter => query the current value */ - fprintf(response, "%s", client->bluetooth_headset_autoswitch ? "true" : "false"); - return 0; - } else { - /* The caller is trying to set a value or clear with a null */ - int ret; - - if (spa_streq(params, "true")) { - ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE, - METADATA_BLUETOOTH_HEADSET_AUTOSWITCH, "Spa:String:JSON", "true"); - client->bluetooth_headset_autoswitch = true; - } else if (spa_streq(params, "false")) { - ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE, - METADATA_BLUETOOTH_HEADSET_AUTOSWITCH, "Spa:String:JSON", "false"); - client->bluetooth_headset_autoswitch = false; - } else if (spa_streq(params, "null")) { - ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE, - METADATA_BLUETOOTH_HEADSET_AUTOSWITCH, NULL, NULL); - client->bluetooth_headset_autoswitch = client->default_bluetooth_headset_autoswitch; - } else { - fprintf(response, "Value must be true, false, or null"); + fprintf(response, "Value must be true, false, or clear"); return -EINVAL; } @@ -183,15 +138,14 @@ static int core_object_message_handler(struct client *client, struct pw_manager_ fprintf(response, "/core []\n" "available commands:\n" - " help this help\n" - " list-handlers show available object handlers\n" - " pipewire-pulse:malloc-info show malloc_info\n" - " pipewire-pulse:malloc-trim run malloc_trim\n" - " pipewire-pulse:log-level update log level with \n" - " pipewire-pulse:list-modules list all module names\n" - " pipewire-pulse:describe-module describe module info for \n" - " pipewire-pulse:force-mono-output force mono mixdown on all hardware outputs\n" - " pipewire-pulse:bluetooth-headset-autoswitch use bluetooth headset mic if available" + " help this help\n" + " list-handlers show available object handlers\n" + " pipewire-pulse:malloc-info show malloc_info\n" + " pipewire-pulse:malloc-trim run malloc_trim\n" + " pipewire-pulse:log-level update log level with \n" + " pipewire-pulse:list-modules list all module names\n" + " pipewire-pulse:describe-module describe module info for \n" + " pipewire-pulse:force-mono-output force mono mixdown on all hardware outputs" ); } else if (spa_streq(message, "list-handlers")) { bool first = true; @@ -254,8 +208,6 @@ static int core_object_message_handler(struct client *client, struct pw_manager_ } } else if (spa_streq(message, "pipewire-pulse:force-mono-output")) { return core_object_force_mono_output(client, params, response); - } else if (spa_streq(message, "pipewire-pulse:bluetooth-headset-autoswitch")) { - return core_object_bluetooth_headset_autoswitch(client, params, response); } else { return -ENOSYS; } diff --git a/src/modules/module-protocol-pulse/modules/module-pipe-source.c b/src/modules/module-protocol-pulse/modules/module-pipe-source.c index ff44f3c38..c9c31f106 100644 --- a/src/modules/module-protocol-pulse/modules/module-pipe-source.c +++ b/src/modules/module-protocol-pulse/modules/module-pipe-source.c @@ -172,8 +172,6 @@ static int module_pipe_source_prepare(struct module * const module) pw_properties_set(stream_props, PW_KEY_NODE_DRIVER, "true"); if ((str = pw_properties_get(stream_props, PW_KEY_PRIORITY_DRIVER)) == NULL) pw_properties_set(stream_props, PW_KEY_PRIORITY_DRIVER, "50000"); - if ((str = pw_properties_get(stream_props, PW_KEY_PRIORITY_SESSION)) == NULL) - pw_properties_set(stream_props, PW_KEY_PRIORITY_SESSION, "2000"); d->module = module; d->stream_props = stream_props; 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 24251942a..c22499718 100644 --- a/src/modules/module-protocol-pulse/pulse-server.c +++ b/src/modules/module-protocol-pulse/pulse-server.c @@ -973,33 +973,12 @@ static void manager_metadata(void *data, struct pw_manager_object *o, if (subject == PW_ID_CORE && o == client->metadata_routes) client_update_routes(client, key, value); if (subject == PW_ID_CORE && o == client->metadata_schema_sm_settings) { - char default_[16]; - - if (spa_streq(key, METADATA_FEATURES_AUDIO_MONO)) { + if (spa_streq(key, METADATA_FEATURES_AUDIO_MONO)) client->have_force_mono_audio = true; - - if (spa_json_str_object_find(value, strlen(value), - "default", default_, sizeof(default_)) < 0) - client->default_force_mono_audio = false; - else - client->default_force_mono_audio = spa_streq(default_, "true"); - } - - if (spa_streq(key, METADATA_BLUETOOTH_HEADSET_AUTOSWITCH)) { - client->have_bluetooth_headset_autoswitch = true; - - if (spa_json_str_object_find(value, strlen(value), - "default", default_, sizeof(default_)) < 0) - client->default_bluetooth_headset_autoswitch = false; - else - client->default_bluetooth_headset_autoswitch = spa_streq(default_, "true"); - } } if (subject == PW_ID_CORE && o == client->metadata_sm_settings) { if (spa_streq(key, METADATA_FEATURES_AUDIO_MONO)) client->force_mono_audio = spa_streq(value, "true"); - if (spa_streq(key, METADATA_BLUETOOTH_HEADSET_AUTOSWITCH)) - client->bluetooth_headset_autoswitch = spa_streq(value, "true"); } } @@ -1621,7 +1600,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; @@ -1907,7 +1886,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; @@ -2298,7 +2277,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, @@ -4068,45 +4047,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) { @@ -4167,16 +4107,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 */ @@ -4252,16 +4186,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 */ diff --git a/src/modules/module-protocol-pulse/sample-play.c b/src/modules/module-protocol-pulse/sample-play.c index 09b0e75cc..db3893c0f 100644 --- a/src/modules/module-protocol-pulse/sample-play.c +++ b/src/modules/module-protocol-pulse/sample-play.c @@ -17,12 +17,10 @@ #include #include -#include "defs.h" #include "format.h" #include "log.h" #include "sample.h" #include "sample-play.h" -#include "internal.h" static void sample_play_stream_state_changed(void *data, enum pw_stream_state old, enum pw_stream_state state, const char *error) @@ -32,32 +30,17 @@ static void sample_play_stream_state_changed(void *data, enum pw_stream_state ol switch (state) { case PW_STREAM_STATE_UNCONNECTED: case PW_STREAM_STATE_ERROR: - pw_timer_queue_cancel(&p->timer); sample_play_emit_done(p, -EIO); break; case PW_STREAM_STATE_PAUSED: p->id = pw_stream_get_node_id(p->stream); sample_play_emit_ready(p, p->id); break; - case PW_STREAM_STATE_STREAMING: - pw_timer_queue_cancel(&p->timer); - break; default: break; } } -static void sample_play_start_timeout(void *user_data) -{ - struct sample_play *p = user_data; - - pw_log_info("timeout on sample %s", p->sample->name); - - if (p->stream) - pw_stream_set_active(p->stream, false); - sample_play_emit_done(p, -ETIMEDOUT); -} - static void sample_play_stream_destroy(void *data) { struct sample_play *p = data; @@ -180,10 +163,6 @@ struct sample_play *sample_play_new(struct pw_core *core, if (res < 0) goto error_cleanup; - /* Time out if we don't get a link; same timeout as for normal streams */ - pw_timer_queue_add(sample->impl->timer_queue, &p->timer, NULL, - STREAM_CREATE_TIMEOUT, sample_play_start_timeout, p); - return p; error_cleanup: @@ -202,8 +181,6 @@ void sample_play_destroy(struct sample_play *p) spa_hook_list_clean(&p->hooks); - pw_timer_queue_cancel(&p->timer); - free(p); } diff --git a/src/modules/module-protocol-pulse/sample-play.h b/src/modules/module-protocol-pulse/sample-play.h index 98af7ee29..a34ca9213 100644 --- a/src/modules/module-protocol-pulse/sample-play.h +++ b/src/modules/module-protocol-pulse/sample-play.h @@ -11,8 +11,6 @@ #include #include -#include - struct sample; struct pw_core; struct pw_loop; @@ -43,7 +41,6 @@ struct sample_play { uint32_t offset; uint32_t stride; struct spa_hook_list hooks; - struct pw_timer timer; void *user_data; }; diff --git a/src/modules/module-protocol-pulse/server.c b/src/modules/module-protocol-pulse/server.c index 637757dfd..4e744e33f 100644 --- a/src/modules/module-protocol-pulse/server.c +++ b/src/modules/module-protocol-pulse/server.c @@ -21,6 +21,9 @@ #include #include +#ifdef HAVE_SYSTEMD +#include +#endif #include #include @@ -574,19 +577,26 @@ static bool is_stale_socket(int fd, const struct sockaddr_un *addr_un) return false; } -static int check_socket_activation(const char *path) +#ifdef HAVE_SYSTEMD +static int check_systemd_activation(const char *path) { - const int n = listen_fds(); + const int n = sd_listen_fds(0); for (int i = 0; i < n; i++) { - const int fd = LISTEN_FDS_START + i; + const int fd = SD_LISTEN_FDS_START + i; - if (is_socket_unix(fd, SOCK_STREAM, path) > 0) + if (sd_is_socket_unix(fd, SOCK_STREAM, 1, path, 0) > 0) return fd; } return -1; } +#else +static inline int check_systemd_activation(SPA_UNUSED const char *path) +{ + return -1; +} +#endif static int start_unix_server(struct server *server, const struct sockaddr_storage *addr) { @@ -596,10 +606,10 @@ static int start_unix_server(struct server *server, const struct sockaddr_storag spa_assert(addr_un->sun_family == AF_UNIX); - fd = check_socket_activation(addr_un->sun_path); + fd = check_systemd_activation(addr_un->sun_path); if (fd >= 0) { server->activated = true; - pw_log_info("server %p: found socket activation socket for '%s'", + pw_log_info("server %p: found systemd socket activation socket for '%s'", server, addr_un->sun_path); goto done; } diff --git a/src/modules/module-protocol-pulse/stream.c b/src/modules/module-protocol-pulse/stream.c index a6beb9fd7..496bcb52c 100644 --- a/src/modules/module-protocol-pulse/stream.c +++ b/src/modules/module-protocol-pulse/stream.c @@ -107,7 +107,7 @@ struct stream *stream_new(struct client *client, enum stream_type type, uint32_t /* Time out if we don't get a link and can't send a reply to create in 35s. Client will time out in * 30s and clean up its stream anyway. */ pw_timer_queue_add(stream->impl->timer_queue, &stream->timer, NULL, - STREAM_CREATE_TIMEOUT, create_stream_timeout, stream); + 35 * SPA_NSEC_PER_SEC, create_stream_timeout, stream); return stream; diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c index 3675b003e..53032d0be 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,73 @@ 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 - skipping tunnel creation", at); + goto done; + } + + 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); + + 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 +449,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 +573,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 +596,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/rtsp-client.c b/src/modules/module-raop/rtsp-client.c index ee3ef43ae..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) { diff --git a/src/modules/module-roc-sink.c b/src/modules/module-roc-sink.c index 66a2c716b..1cca69592 100644 --- a/src/modules/module-roc-sink.c +++ b/src/modules/module-roc-sink.c @@ -46,9 +46,6 @@ * - `remote.repair.port = `: remote receiver TCP/UDP port for receiver packets * - `remote.control.port = `: remote receiver TCP/UDP port for control packets * - `fec.code = `: Possible values: `disable`, `rs8m`, `ldpc` - * - `log.level = `: log level for roc-toolkit. Possible values: `DEFAULT`, - * `NONE`, `ERROR`, `INFO`, `DEBUG`, `TRACE`; `DEFAULT` follows the log - * level of the PipeWire context. * * ## General options * @@ -78,7 +75,6 @@ * node.name = "roc-sink" * } * audio.position = [ FL FR ] - * log.level = DEFAULT * } * } *] @@ -88,9 +84,8 @@ #define NAME "roc-sink" -PW_LOG_TOPIC(mod_topic, "mod." NAME); +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic -PW_LOG_TOPIC_EXTERN(roc_log_topic); struct module_roc_sink_data { struct pw_impl_module *module; @@ -305,8 +300,6 @@ static int roc_sink_setup(struct module_roc_sink_data *data) pw_properties_setf(data->capture_props, PW_KEY_NODE_RATE, "1/%d", info.rate); - pw_roc_log_init(); - res = roc_sender_open(data->context, &sender_config, &data->sender); if (res) { pw_log_error("failed to create roc sender: %d", res); @@ -403,7 +396,6 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args) int res = 0; PW_LOG_TOPIC_INIT(mod_topic); - PW_LOG_TOPIC_INIT(roc_log_topic); data = calloc(1, sizeof(struct module_roc_sink_data)); if (data == NULL) diff --git a/src/modules/module-roc-source.c b/src/modules/module-roc-source.c index a46189e5d..6c67a037c 100644 --- a/src/modules/module-roc-source.c +++ b/src/modules/module-roc-source.c @@ -56,9 +56,6 @@ * - `fec.code = `: Possible values: `default`, `disable`, `rs8m`, `ldpc` * * - `resampler.profile = `: Deprecated, use roc.resampler.profile - * - `log.level = `: log level for roc-toolkit. Possible values: `DEFAULT`, - * `NONE`, `ERROR`, `INFO`, `DEBUG`, `TRACE`; `DEFAULT` follows the log - * level of the PipeWire context. * * ## General options * @@ -92,7 +89,6 @@ * node.name = "roc-source" * } * audio.position = [ FL FR ] - * log.level = DEFAULT * } * } *] @@ -102,9 +98,8 @@ #define NAME "roc-source" -PW_LOG_TOPIC(mod_topic, "mod." NAME); +PW_LOG_TOPIC_STATIC(mod_topic, "mod." NAME); #define PW_LOG_TOPIC_DEFAULT mod_topic -PW_LOG_TOPIC_EXTERN(roc_log_topic); struct module_roc_source_data { struct pw_impl_module *module; @@ -338,8 +333,6 @@ static int roc_source_setup(struct module_roc_source_data *data) */ receiver_config.target_latency = (unsigned long long)data->sess_latency_msec * SPA_NSEC_PER_MSEC; - pw_roc_log_init(); - res = roc_receiver_open(data->context, &receiver_config, &data->receiver); if (res) { pw_log_error("failed to create roc receiver: %d", res); diff --git a/src/modules/module-roc/common.c b/src/modules/module-roc/common.c deleted file mode 100644 index 475c5f40f..000000000 --- a/src/modules/module-roc/common.c +++ /dev/null @@ -1,58 +0,0 @@ -#include -#include - -#include "common.h" - -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)); -} diff --git a/src/modules/module-roc/common.h b/src/modules/module-roc/common.h index d49e392fe..69153659e 100644 --- a/src/modules/module-roc/common.h +++ b/src/modules/module-roc/common.h @@ -5,7 +5,6 @@ #include #include -#include #define PW_ROC_DEFAULT_IP "0.0.0.0" #define PW_ROC_DEFAULT_SOURCE_PORT 10001 @@ -19,8 +18,6 @@ #define PW_ROC_MULTITRACK_ENCODING_ID 100 #define PW_ROC_STEREO_POSITIONS "[ FL FR ]" -void pw_roc_log_init(void); - static inline int pw_roc_parse_fec_encoding(roc_fec_encoding *out, const char *str) { if (!str || !*str || spa_streq(str, "default")) 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..13873f29c 100644 --- a/src/modules/module-rtp/audio.c +++ b/src/modules/module-rtp/audio.c @@ -22,15 +22,6 @@ static void ringbuffer_clear(struct spa_ringbuffer *rbuf SPA_UNUSED, memset(iov[1].iov_base, 0, iov[1].iov_len); } -static inline uint64_t scale_u64(uint64_t val, uint32_t num, uint32_t denom) -{ -#if 0 - return ((__uint128_t)val * num) / denom; -#else - return (uint64_t)((double)val / denom * num); -#endif -} - static void rtp_audio_process_playback(void *data) { struct impl *impl = data; @@ -70,9 +61,6 @@ static void rtp_audio_process_playback(void *data) * read or write index itself.) */ if (impl->direct_timestamp) { - uint32_t num_samples_to_read; - uint32_t read_index; - /* In direct timestamp mode, the focus lies on synchronized playback, not * on a constant latency. The ring buffer fill level is not of interest * here. The code in rtp_audio_receive() writes to the ring buffer at @@ -101,32 +89,22 @@ static void rtp_audio_process_playback(void *data) * timestamp mode, since all of them shift the timestamp by the same * `sess.latency.msec` into the future. * - * Since in this mode, a constant latency is not important, tracking - * the fill level to keep it steady makes no sense. Consequently, - * no DLL is needed. Also, matching the pace of the synchronized clock - * is done by having the graph driver be synchronized to that clock, - * which will in turn cause any output sinks to adjust their DLLs - * (or similar control loop mechanisms) to match the pace of their - * data consumption with the pace of the driver. - * - * The fill level is still important though to correctly handle corner - * cases where the ring buffer is (almost) empty. If fewer samples - * are available than what the read operation wants, the deficit - * has to be compensated with nullbytes. To that end, the "avail" - * quantity tracks how many samples are actually available. */ + * "Fill level" makes no sense in this mode, since a constant latency + * is not important in this mode, so no DLL is needed. Also, matching + * the pace of the synchronized clock is done by having the graph + * driver be synchronized to that clock, which will in turn cause + * any output sinks to adjust their DLLs (or similar control loop + * mechanisms) to match the pace of their data consumption with the + * pace of the driver. */ if (impl->io_position) { - uint32_t clock_rate = impl->io_position->clock.rate.denom; - - /* Translate the clock position to an RTP timestamp and - * shift it to compensate for device delay and ASRC delay. - * The device delay is scaled along with the clock position, - * since both are expressed in clock sample units, while - * pwt.buffered is expressed in stream time. */ - timestamp = scale_u64(impl->io_position->clock.position + device_delay, - impl->rate, clock_rate) + pwt.buffered; + /* Use the clock position directly as the read index. + * Do NOT add device_delay here - the sink's DLL handles + * matching its hardware clock to the driver pace. Adding + * device_delay would create a feedback loop since rate + * adjustments affect both ringbuffer and device buffer. */ + timestamp = impl->io_position->clock.position; spa_ringbuffer_read_update(&impl->ring, timestamp); - avail = spa_ringbuffer_get_read_index(&impl->ring, &read_index); } else { /* In the unlikely case that no spa_io_position pointer * was passed yet by PipeWire to this node, resort to a @@ -134,72 +112,26 @@ static void rtp_audio_process_playback(void *data) * This most likely is not in sync with other nodes, * but _something_ is needed as read index until the * spa_io_position is available. */ - avail = spa_ringbuffer_get_read_index(&impl->ring, ×tamp); - read_index = timestamp; + spa_ringbuffer_get_read_index(&impl->ring, ×tamp); } - /* If avail is 0, it means that the ring buffer is empty. <0 means - * that there is an underrun, typically because the PTP time now - * is ahead of the RTP data (this can happen when the PTP master - * changes for example). And in cases where only a little bit of - * data is left, it is important to not try to use more than what - * is actually available. - * Overruns would happen if the write pointer is further ahead than - * what the ringbuffer size actually allows. This too can happen - * if the PTP time jumps. No actual buffer overflow would happen - * then, since the write operations always apply modulo to the - * timestamps to wrap around the ringbuffer borders. - */ - bool has_underrun = (avail < 0); - bool has_overrun = !has_underrun && ((uint32_t)avail) > impl->actual_max_buffer_size; - num_samples_to_read = has_underrun ? 0 : SPA_MIN((uint32_t)avail, wanted); + spa_ringbuffer_read_data(&impl->ring, + impl->buffer, + impl->actual_max_buffer_size, + ((uint64_t)timestamp * stride) % impl->actual_max_buffer_size, + d[0].data, wanted * stride); - /* Do some additional logging in the under/overrun cases. */ - if (SPA_UNLIKELY(pw_log_topic_enabled(SPA_LOG_LEVEL_TRACE, PW_LOG_TOPIC_DEFAULT))) - { - uint32_t write_index; - int32_t filled = spa_ringbuffer_get_write_index(&impl->ring, &write_index); - - if (has_underrun) { - pw_log_trace("Direct timestamp mode: Read index underrun: write_index: %" - PRIu32 ", read_index: %" PRIu32 ", wanted: %u - filled: %" PRIi32, - write_index, read_index, wanted, filled); - } else if (has_overrun) { - pw_log_trace("Direct timestamp mode: Read index overrun: write_index: %" - PRIu32 ", read_index: %" PRIu32 ", wanted: %u - filled: %" PRIi32 - ", buffer size: %u", write_index, read_index, wanted, filled, - impl->actual_max_buffer_size); - } - } - - if (num_samples_to_read > 0) { - spa_ringbuffer_read_data(&impl->ring, - impl->buffer, - impl->actual_max_buffer_size, - ((uint64_t)timestamp * stride) % impl->actual_max_buffer_size, - d[0].data, num_samples_to_read * stride); - - /* Clear the bytes that were just retrieved. Since the fill level - * is not tracked in this buffer mode, it is possible that as soon - * as actual playback ends, the RTP source node re-reads old data. - * Make sure it reads silence when no actual new data is present - * and the RTP source node still runs. Do this by filling the - * region of the retrieved data with null bytes. */ - ringbuffer_clear(&impl->ring, - impl->buffer, - impl->actual_max_buffer_size, - ((uint64_t)timestamp * stride) % impl->actual_max_buffer_size, - num_samples_to_read * stride); - } - - if (num_samples_to_read < wanted) { - /* If fewer samples were available than what was wanted, - * fill the remaining space in the destination memory - * with nullsamples. */ - void *bytes_to_clear = SPA_PTROFF(d[0].data, num_samples_to_read * stride, void); - size_t num_bytes_to_clear = (wanted - num_samples_to_read) * stride; - spa_memzero(bytes_to_clear, num_bytes_to_clear); - } + /* Clear the bytes that were just retrieved. Since the fill level + * is not tracked in this buffer mode, it is possible that as soon + * as actual playback ends, the RTP source node re-reads old data. + * Make sure it reads silence when no actual new data is present + * and the RTP source node still runs. Do this by filling the + * region of the retrieved data with null bytes. */ + ringbuffer_clear(&impl->ring, + impl->buffer, + impl->actual_max_buffer_size, + ((uint64_t)timestamp * stride) % impl->actual_max_buffer_size, + wanted * stride); if (!impl->io_position) { /* In the unlikely case that no spa_io_position pointer @@ -290,25 +222,6 @@ static void rtp_audio_process_playback(void *data) ((uint64_t)timestamp * stride) % impl->actual_max_buffer_size, d[0].data, wanted * stride); - /* Clear the bytes that were just retrieved. Unlike in the - * direct timestamp mode, here, bytes are always read out - * of the ring buffer in sequence - the read pointer does - * not "jump around" (which can happen in direct timestamp - * mode if the last iteration has been a while ago and the - * driver clock time advanced significantly, or if the driver - * time experienced a discontinuity). However, should there - * be packet loss, it could lead to segments in the ring - * buffer that should have been written to but weren't written - * to. These segments would then contain old stale data. By - * clearing data out of the ring buffer after reading it, it - * is ensured that no stale data can exist - in the packet loss - * case, the outcome would be a gap made of nullsamples instead. */ - ringbuffer_clear(&impl->ring, - impl->buffer, - impl->actual_max_buffer_size, - ((uint64_t)timestamp * stride) % impl->actual_max_buffer_size, - wanted * stride); - timestamp += wanted; spa_ringbuffer_read_update(&impl->ring, timestamp); } @@ -421,43 +334,17 @@ static int rtp_audio_receive(struct impl *impl, uint8_t *buffer, ssize_t len, * and not _appended_. In this example, `expected_write` would * be 100 (since `expected_write` is the current write index), * `write` would be 90, `samples` would be 10. In this case, - * the (expected_write < (write + samples)) inequality does - * not hold, so data is being _inserted_. By contrast, during - * normal operation, `write` and `expected_write` are equal, - * so the aforementioned inequality _does_ hold, meaning that - * data is being appended. - * - * The code below handles this, and also handles a 32-bit - * integer overflow corner case where the comparison has - * to be done differently to account for the wrap-around. + * the inequality below does not hold, so data is being + * _inserted_. By contrast, during normal operation, `write` + * and `expected_write` are equal, so the inequality below + * _does_ hold, meaning that data is being appended. * * (Note that this write index update is only important if * the constant delay mode is active, or if no spa_io_position * was not provided yet. See the rtp_audio_process_playback() * code for more about this.) */ - - /* Compute new_write, handling potential 32-bit overflow. - * In unsigned arithmetic, if write + samples exceeds UINT32_MAX, - * it wraps around to a smaller value. We detect this by checking - * if new_write < write (which can only happen on overflow). */ - const uint32_t new_write = write + samples; - const bool wrapped_around = new_write < write; - - /* Determine if new_write is ahead of expected_write. - * We're appending (ahead) if: - * - * 1. Normal case: new_write > expected_write (forward progress) - * 2. Wrap-around case: new_write wrapped around (wrapped_around == true), - * meaning we've cycled through the 32-bit index space and are - * continuing from the beginning. In this case, we're always ahead. - * - * We're NOT appending (inserting/behind) if: - * - new_write <= expected_write AND no wrap-around occurred - * (we're filling a gap or writing behind the current position) */ - const bool is_appending = wrapped_around || (new_write > expected_write); - - if (is_appending) { - write = new_write; + if (expected_write < (write + samples)) { + write += samples; spa_ringbuffer_write_update(&impl->ring, write); } } @@ -539,29 +426,22 @@ static void rtp_audio_flush_packets(struct impl *impl, uint32_t num_packets, uin iov[0].iov_len = sizeof(header); while (num_packets > 0) { - uint32_t rtp_timestamp; - if (impl->marker_on_first && impl->first) header.m = 1; else header.m = 0; - - rtp_timestamp = impl->ts_offset + impl->ts_align + (set_timestamp ? set_timestamp : timestamp); - header.sequence_number = htons(impl->seq); - header.timestamp = htonl(rtp_timestamp); + header.timestamp = htonl(impl->ts_offset + (set_timestamp ? set_timestamp : timestamp)); set_iovec(&impl->ring, impl->buffer, impl->actual_max_buffer_size, ((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)", - tosend, num_packets, impl->ts_offset, timestamp, - (double)timestamp * impl->io_position->clock.rate.num / - impl->io_position->clock.rate.denom); + pw_log_trace("sending %d packet:%d ts_offset:%d timestamp:%d", + tosend, num_packets, impl->ts_offset, timestamp); - rtp_stream_call_send_packet(impl, iov, 3); + rtp_stream_emit_send_packet(impl, iov, 3); impl->seq++; impl->first = false; @@ -606,7 +486,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); } @@ -620,7 +500,6 @@ static void rtp_audio_process_capture(void *data) uint32_t pending, num_queued; struct spa_io_position *pos; uint64_t next_nsec, quantum; - struct pw_time pwt; if (impl->separate_sender) { /* apply the DLL rate */ @@ -638,8 +517,6 @@ static void rtp_audio_process_capture(void *data) stride = impl->stride; wanted = size / stride; - pw_stream_get_time_n(impl->stream, &pwt, sizeof(pwt)); - filled = spa_ringbuffer_get_write_index(&impl->ring, &expected_timestamp); pos = impl->io_position; @@ -656,21 +533,6 @@ static void rtp_audio_process_capture(void *data) impl->sink_resamp_delay = impl->io_rate_match->delay; impl->sink_quantum = (uint64_t)(pos->clock.duration * SPA_NSEC_PER_SEC / rate); } - - /* Compensate for the stream resampler's delay. */ - actual_timestamp -= pwt.buffered; - - /* If we got a request for less than quantum worth of samples, it indicates that there - * is a gap created by the resampler. We have to skip it to avoid timestamp discontinuity. */ - if (pwt.buffered > 0) { - int32_t ideal_quantum = (int32_t)scale_u64(pos->clock.duration, impl->rate, rate); - if (wanted < ideal_quantum) { - int32_t num_samples_to_skip = ideal_quantum - wanted; - pw_log_info("wanted: %" PRId32 " < ideal quantum: %" PRId32 " - skipping %" - PRId32" samples", wanted, ideal_quantum, num_samples_to_skip); - actual_timestamp += num_samples_to_skip; - } - } } else { actual_timestamp = expected_timestamp; next_nsec = 0; @@ -705,12 +567,9 @@ 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); - spa_ringbuffer_read_update(&impl->ring, actual_timestamp); - spa_ringbuffer_write_update(&impl->ring, actual_timestamp); + pw_log_info("(re)sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u", + actual_timestamp, impl->seq, impl->ts_offset, impl->ssrc); + impl->ring.readindex = impl->ring.writeindex = actual_timestamp; memset(impl->buffer, 0, BUFFER_SIZE); impl->have_sync = true; expected_timestamp = actual_timestamp; 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..e19d88a1f 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) { @@ -460,10 +454,6 @@ static int stream_stop(struct impl *impl) * meaning that the timer was no longer running, and the connection * could be closed. */ if (!timer_running) { - /* Clear the ringbuffer to prevent old invalid packets from being - * sent when processing resumes via rtp_audio_flush_packets() */ - if (impl->reset_ringbuffer) - impl->reset_ringbuffer(impl); set_internal_stream_state(impl, RTP_STREAM_INTERNAL_STATE_STOPPED); pw_log_info("stream stopped"); } @@ -1008,7 +998,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 719a7c956..000000000 --- a/src/modules/module-scheduler-v1.c +++ /dev/null @@ -1,1034 +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) -{ - enum pw_node_state state = node->info.state; - if (node->active && node->runnable && - !SPA_FLAG_IS_SET(node->spa_flags, SPA_NODE_FLAG_NEED_CONFIGURE) && running) - state = PW_NODE_STATE_RUNNING; - else if (state > PW_NODE_STATE_IDLE) - state = PW_NODE_STATE_IDLE; - return pw_impl_node_set_state(node, state); -} - -/* Make a node runnable. Peer nodes are also made runnable when the passive_mode - * of the peer port is !TRUE. - * - * A (*) -> B if A running -> B set to running - * A (*) -> (f) B if A running -> B set to running - * A (*) -> (fs) B if A running -> B set to running - * A (*) -> (p) B if A running -> B no change - */ -static inline bool makes_runnable(struct pw_impl_port *a, struct pw_impl_port *b) -{ - return b->passive_mode != PASSIVE_MODE_TRUE; -} -static void make_runnable(struct pw_context *context, struct pw_impl_node *node) -{ - struct pw_impl_port *p; - struct pw_impl_link *l; - struct pw_impl_node *n; - - if (!node->runnable) { - pw_log_debug("%s is runnable", node->name); - node->runnable = true; - } - - spa_list_for_each(p, &node->output_ports, link) { - spa_list_for_each(l, &p->links, output_link) { - n = l->input->node; - pw_log_trace(" out-port %p: link %p passive:%d prepared:%d active:%d runn:%d", p, - l, l->input->passive_mode, l->prepared, n->active, n->runnable); - if (!n->active || !makes_runnable(p, l->input)) - continue; - pw_impl_link_prepare(l); - if (!l->prepared) - continue; - if (!n->runnable) - make_runnable(context, n); - } - } - spa_list_for_each(p, &node->input_ports, link) { - spa_list_for_each(l, &p->links, input_link) { - n = l->output->node; - pw_log_trace(" in-port %p: link %p passive:%d prepared:%d active:%d runn:%d", p, - l, l->output->passive_mode, l->prepared, n->active, n->runnable); - if (!n->active || !makes_runnable(p, l->output)) - continue; - pw_impl_link_prepare(l); - if (!l->prepared) - continue; - if (!n->runnable) - make_runnable(context, n); - } - } - /* now go through all the nodes that share groups and link_groups - * that are not yet runnable. We don't include sync-groups because they - * are only used to group the node with a driver, not to determine the - * runnable state of a node. */ - if (node->groups != NULL || node->link_groups != NULL) { - spa_list_for_each(n, &context->node_list, link) { - if (n->exported || !n->active || n->runnable) - continue; - /* the other node will be scheduled with this one if it's in - * the same group or link group */ - if (pw_strv_find_common(n->groups, node->groups) < 0 && - pw_strv_find_common(n->link_groups, node->link_groups) < 0) - continue; - - make_runnable(context, n); - } - } -} - -/* check if a node and its peer can run. - * - * Only consider ports that have a PASSIVE_MODE_FALSE link. - * All other port modes don't make A and B runnable. - * - * A -> B A + B both set to running - * A -> (p) B A + B both set to running - * A -> (f) B A + B both set to running - * A (fs) -> (fs) B A + B both set to running - * A (p) -> (*) B A + B no change - * A (f) -> (*) B A + B no change - * A (fs) -> (*) B A + B no change - * - * There is a special case for FOLLOW_SUSPEND<->FOLLOW_SUSPEND to make - * it possible to manually link a source to a sink - */ -static inline bool runnable_pair(struct pw_impl_port *a, struct pw_impl_port *b) -{ - if (a->passive_mode == PASSIVE_MODE_FALSE) - return true; - if (a->passive_mode == PASSIVE_MODE_FOLLOW_SUSPEND && - b->passive_mode == PASSIVE_MODE_FOLLOW_SUSPEND) - return true; - return false; -} -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 passive:%d prepared:%d active:%d", p, - l, p->passive_mode, 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 passive:%d prepared:%d active:%d", p, - l, p->passive_mode, l->prepared, n->active); - if (!n->active || !runnable_pair(p, l->output)) - continue; - pw_impl_link_prepare(l); - if (!l->prepared) - continue; - make_runnable(context, node); - make_runnable(context, n); - } - } -} - -/* Follow all links and groups from node. - * - * After this is done, we end up with a list of nodes in collect that are all - * linked to node. - * - * We don't need to care about active nodes or links, we just follow and group everything. - * The inactive nodes or links will simply not be runnable but will already be grouped - * correctly when they do become active and prepared. - */ -static int collect_nodes(struct pw_context *context, struct pw_impl_node *node, struct spa_list *collect) -{ - struct spa_list queue; - struct pw_impl_node *n, *t; - struct pw_impl_port *p; - struct pw_impl_link *l; - uint32_t n_sync; - char *sync[MAX_SYNC+1]; - - pw_log_debug("node %p: '%s'", node, node->name); - - /* start with node in the queue */ - spa_list_init(&queue); - spa_list_append(&queue, &node->sort_link); - node->visited = true; - - n_sync = 0; - sync[0] = NULL; - - /* now follow all the links from the nodes in the queue - * and add the peers to the queue. */ - spa_list_consume(n, &queue, sort_link) { - spa_list_remove(&n->sort_link); - spa_list_append(collect, &n->sort_link); - - pw_log_debug(" next node %p: '%s' runnable:%u active:%d", - n, n->name, n->runnable, n->active); - - if (n->sync) { - for (uint32_t i = 0; n->sync_groups[i]; i++) { - if (n_sync >= MAX_SYNC) - break; - if (pw_strv_find(sync, n->sync_groups[i]) >= 0) - continue; - sync[n_sync++] = n->sync_groups[i]; - sync[n_sync] = NULL; - } - } - - spa_list_for_each(p, &n->input_ports, link) { - spa_list_for_each(l, &p->links, input_link) { - t = l->output->node; - if (!t->visited) { - t->visited = true; - spa_list_append(&queue, &t->sort_link); - } - } - } - spa_list_for_each(p, &n->output_ports, link) { - spa_list_for_each(l, &p->links, output_link) { - t = l->input->node; - if (!t->visited) { - t->visited = true; - spa_list_append(&queue, &t->sort_link); - } - } - } - /* now go through all the nodes that have the same groups and - * that are not yet visited */ - if (n->groups != NULL || n->link_groups != NULL || sync[0] != NULL) { - spa_list_for_each(t, &context->node_list, link) { - if (t->exported || t->visited) - continue; - /* the other node will be scheduled with this one if it's in - * the same group, link group or sync group */ - if (pw_strv_find_common(t->groups, n->groups) < 0 && - pw_strv_find_common(t->link_groups, n->link_groups) < 0 && - pw_strv_find_common(t->sync_groups, sync) < 0) - continue; - - pw_log_debug("%p: %s join group of %s", - t, t->name, n->name); - t->visited = true; - spa_list_append(&queue, &t->sort_link); - } - } - pw_log_debug(" next node %p: '%s' runnable:%u %p %p %p", n, n->name, n->runnable, - n->groups, n->link_groups, sync); - } - return 0; -} - -static void move_to_driver(struct pw_context *context, struct spa_list *nodes, - struct pw_impl_node *driver) -{ - struct pw_impl_node *n; - pw_log_debug("driver: %p %s runnable:%u", driver, driver->name, driver->runnable); - spa_list_consume(n, nodes, sort_link) { - spa_list_remove(&n->sort_link); - - driver->runnable |= n->runnable; - - pw_log_debug(" follower: %p %s runnable:%u driver-runnable:%u", n, n->name, - n->runnable, driver->runnable); - pw_impl_node_set_driver(n, driver); - } -} -static void remove_from_driver(struct pw_context *context, struct spa_list *nodes) -{ - struct pw_impl_node *n; - spa_list_consume(n, nodes, sort_link) { - spa_list_remove(&n->sort_link); - pw_impl_node_set_driver(n, NULL); - ensure_state(n, false); - } -} - -static inline void get_quantums(struct pw_context *context, uint32_t *def, - uint32_t *min, uint32_t *max, uint32_t *rate, uint32_t *floor, uint32_t *ceil) -{ - struct settings *s = &context->settings; - if (s->clock_force_quantum != 0) { - *def = *min = *max = s->clock_force_quantum; - *rate = 0; - } else { - *def = s->clock_quantum; - *min = s->clock_min_quantum; - *max = s->clock_max_quantum; - *rate = s->clock_rate; - } - *floor = s->clock_quantum_floor; - *ceil = s->clock_quantum_limit; -} - -static inline const uint32_t *get_rates(struct pw_context *context, uint32_t *def, uint32_t *n_rates, - bool *force) -{ - struct settings *s = &context->settings; - if (s->clock_force_rate != 0) { - *force = true; - *n_rates = 1; - *def = s->clock_force_rate; - return &s->clock_force_rate; - } else { - *force = false; - *n_rates = s->n_clock_rates; - *def = s->clock_rate; - return s->clock_rates; - } -} -static void reconfigure_driver(struct pw_context *context, struct pw_impl_node *n) -{ - struct pw_impl_node *s; - - spa_list_for_each(s, &n->follower_list, follower_link) { - if (s == n) - continue; - pw_log_debug("%p: follower %p: '%s' suspend", - context, s, s->name); - pw_impl_node_set_state(s, PW_NODE_STATE_SUSPENDED); - } - pw_log_debug("%p: driver %p: '%s' suspend", - context, n, n->name); - - if (n->info.state >= PW_NODE_STATE_IDLE) - n->need_resume = !n->pause_on_idle; - pw_impl_node_set_state(n, PW_NODE_STATE_SUSPENDED); -} - -/* find smaller power of 2 */ -static uint32_t flp2(uint32_t x) -{ - x = x | (x >> 1); - x = x | (x >> 2); - x = x | (x >> 4); - x = x | (x >> 8); - x = x | (x >> 16); - return x - (x >> 1); -} - -/* cmp fractions, avoiding overflows */ -static int fraction_compare(const struct spa_fraction *a, const struct spa_fraction *b) -{ - uint64_t fa = (uint64_t)a->num * (uint64_t)b->denom; - uint64_t fb = (uint64_t)b->num * (uint64_t)a->denom; - return fa < fb ? -1 : (fa > fb ? 1 : 0); -} - -static inline uint32_t calc_gcd(uint32_t a, uint32_t b) -{ - while (b != 0) { - uint32_t temp = a; - a = b; - b = temp % b; - } - return a; -} - -struct rate_info { - uint32_t rate; - uint32_t gcd; - uint32_t diff; -}; - -static inline void update_highest_rate(struct rate_info *best, struct rate_info *current) -{ - /* find highest rate */ - if (best->rate == 0 || best->rate < current->rate) - *best = *current; -} - -static inline void update_nearest_gcd(struct rate_info *best, struct rate_info *current) -{ - /* find nearest GCD */ - if (best->rate == 0 || - (best->gcd < current->gcd) || - (best->gcd == current->gcd && best->diff > current->diff)) - *best = *current; -} -static inline void update_nearest_rate(struct rate_info *best, struct rate_info *current) -{ - /* find nearest rate */ - if (best->rate == 0 || best->diff > current->diff) - *best = *current; -} - -static uint32_t find_best_rate(const uint32_t *rates, uint32_t n_rates, uint32_t rate, uint32_t def) -{ - uint32_t i, limit; - struct rate_info best; - struct rate_info info[n_rates]; - - for (i = 0; i < n_rates; i++) { - info[i].rate = rates[i]; - info[i].gcd = calc_gcd(rate, rates[i]); - info[i].diff = SPA_ABS((int32_t)rate - (int32_t)rates[i]); - } - - /* first find higher nearest GCD. This tries to find next bigest rate that - * requires the least amount of resample filter banks. Usually these are - * rates that are multiples of each other or multiples of a common rate. - * - * 44100 and [ 32000 56000 88200 96000 ] -> 88200 - * 48000 and [ 32000 56000 88200 96000 ] -> 96000 - * 88200 and [ 44100 48000 96000 192000 ] -> 96000 - * 32000 and [ 44100 192000 ] -> 44100 - * 8000 and [ 44100 48000 ] -> 48000 - * 8000 and [ 44100 192000 ] -> 44100 - * 11025 and [ 44100 48000 ] -> 44100 - * 44100 and [ 48000 176400 ] -> 48000 - * 144 and [ 44100 48000 88200 96000] -> 48000 - */ - spa_zero(best); - /* Don't try to do excessive upsampling by limiting the max rate - * for desired < default to default*2. For other rates allow - * a x3 upsample rate max. For values lower than half of the default, - * limit to the default. */ - limit = rate < def/2 ? def : rate < def ? def*2 : rate*3; - for (i = 0; i < n_rates; i++) { - if (info[i].rate >= rate && info[i].rate <= limit) - update_nearest_gcd(&best, &info[i]); - } - if (best.rate != 0) - return best.rate; - - /* we would need excessive upsampling, pick a nearest higher rate */ - spa_zero(best); - for (i = 0; i < n_rates; i++) { - if (info[i].rate >= rate) - update_nearest_rate(&best, &info[i]); - } - if (best.rate != 0) - return best.rate; - - /* There is nothing above the rate, we need to downsample. Try to downsample - * but only to something that is from a common rate family. Also don't - * try to downsample to something that will sound worse (< 44100). - * - * 88200 and [ 22050 44100 48000 ] -> 44100 - * 88200 and [ 22050 48000 ] -> 48000 - */ - spa_zero(best); - for (i = 0; i < n_rates; i++) { - if (info[i].rate >= 44100) - update_nearest_gcd(&best, &info[i]); - } - if (best.rate != 0) - return best.rate; - - /* There is nothing to downsample above our threshold. Downsample to whatever - * is the highest rate then. */ - spa_zero(best); - for (i = 0; i < n_rates; i++) - update_highest_rate(&best, &info[i]); - if (best.rate != 0) - return best.rate; - - return def; -} - -/* here we evaluate the complete state of the graph. - * - * It roughly operates in 4 stages: - * - * 1. go over all nodes and check if they should be scheduled (runnable) or not. - * - * 2. go over all drivers and collect the nodes that need to be scheduled with the - * driver. This include all nodes that have an active link with the driver or - * with a node already scheduled with the driver. - * - * 3. go over all nodes that are not assigned to a driver. The ones that require - * a driver are moved to some random active driver found in step 2. - * - * 4. go over all drivers again, collect the quantum/rate of all followers, select - * the desired final value and activate the followers and then the driver. - * - * A complete graph evaluation is performed for each change that is made to the - * graph, such as making/destroying links, adding/removing nodes, property changes such - * as quantum/rate changes or metadata changes. - */ -static void context_recalc_graph(void *data) -{ - struct impl *impl = data; - struct pw_context *context = impl->context; - struct settings *settings = &context->settings; - struct pw_impl_node *n, *s, *target, *fallback; - const uint32_t *rates; - uint32_t max_quantum, min_quantum, def_quantum, rate_quantum, floor_quantum, ceil_quantum; - uint32_t n_rates, def_rate, transport; - bool freewheel, global_force_rate, global_force_quantum; - struct spa_list collect; - -again: - freewheel = false; - - /* clean up the flags first */ - spa_list_for_each(n, &context->node_list, link) { - n->visited = false; - n->checked = 0; - n->runnable = false; - } - - get_quantums(context, &def_quantum, &min_quantum, &max_quantum, &rate_quantum, - &floor_quantum, &ceil_quantum); - rates = get_rates(context, &def_rate, &n_rates, &global_force_rate); - - global_force_quantum = rate_quantum == 0; - - /* first look at all nodes and decide which one should be runnable */ - spa_list_for_each(n, &context->node_list, link) { - if (n->exported || !n->active) - continue; - check_runnable(context, n); - } - - /* start from all drivers and group all nodes that are linked - * to it. Some nodes are not (yet) linked to anything and they - * will end up 'unassigned' to a driver. Other nodes are drivers - * and if they have active followers, we can use them to schedule - * the unassigned nodes. */ - target = fallback = NULL; - spa_list_for_each(n, &context->driver_list, driver_link) { - if (n->exported) - continue; - - if (!n->visited) { - spa_list_init(&collect); - collect_nodes(context, n, &collect); - move_to_driver(context, &collect, n); - } - /* from now on we are only interested in active driving nodes - * with a driver_priority. We're going to see if there are - * active followers. */ - if (!n->driving || !n->active || n->priority_driver <= 0) - continue; - - /* first active driving node is fallback */ - if (fallback == NULL) - fallback = n; - - if (!n->runnable) - continue; - - spa_list_for_each(s, &n->follower_list, follower_link) { - pw_log_debug("%p: driver %p: follower %p %s: active:%d", - context, n, s, s->name, s->active); - if (s != n && s->active) { - /* if the driving node has active followers, it - * is a target for our unassigned nodes */ - if (target == NULL) - target = n; - if (n->freewheel) - freewheel = true; - break; - } - } - } - /* no active node, use fallback driving node */ - if (target == NULL) - target = fallback; - - /* update the freewheel status */ - pw_context_set_freewheel(context, freewheel); - - /* now go through all available nodes. The ones we didn't visit - * in collect_nodes() are not linked to any driver. We assign them - * to either an active driver or the first driver if they are in a - * group that needs a driver. Else we remove them from a driver - * and stop them. */ - spa_list_for_each(n, &context->node_list, link) { - struct pw_impl_node *t, *driver; - - if (n->exported || n->visited) - continue; - - pw_log_debug("%p: unassigned node %p: '%s' active:%d want_driver:%d target:%p", - context, n, n->name, n->active, n->want_driver, target); - - /* collect all nodes in this group */ - spa_list_init(&collect); - collect_nodes(context, n, &collect); - - driver = NULL; - spa_list_for_each(t, &collect, sort_link) { - /* is any active and want a driver */ - if ((t->want_driver && t->active && t->runnable) || - t->always_process) { - driver = target; - break; - } - } - if (driver != NULL) { - driver->runnable = true; - /* driver needed for this group */ - move_to_driver(context, &collect, driver); - } else { - /* no driver, make sure the nodes stop */ - remove_from_driver(context, &collect); - } - } - - /* assign final quantum and set state for followers and drivers */ - spa_list_for_each(n, &context->driver_list, driver_link) { - bool running = false, lock_quantum = false, lock_rate = false; - struct spa_fraction latency = SPA_FRACTION(0, 0); - struct spa_fraction max_latency = SPA_FRACTION(0, 0); - struct spa_fraction rate = SPA_FRACTION(0, 0); - uint32_t target_quantum, target_rate, current_rate, current_quantum; - uint64_t quantum_stamp = 0, rate_stamp = 0; - bool force_rate, force_quantum, restore_rate = false, restore_quantum = false; - bool do_reconfigure = false, need_resume, was_target_pending; - bool have_request = false; - const uint32_t *node_rates; - uint32_t node_n_rates, node_def_rate; - uint32_t node_max_quantum, node_min_quantum, node_def_quantum, node_rate_quantum; - - if (!n->driving || n->exported) - continue; - - node_def_quantum = def_quantum; - node_min_quantum = min_quantum; - node_max_quantum = max_quantum; - node_rate_quantum = rate_quantum; - force_quantum = global_force_quantum; - - node_def_rate = def_rate; - node_n_rates = n_rates; - node_rates = rates; - force_rate = global_force_rate; - - /* collect quantum and rate */ - spa_list_for_each(s, &n->follower_list, follower_link) { - - if (!s->moved) { - /* We only try to enforce the lock flags for nodes that - * are not recently moved between drivers. The nodes that - * are moved should try to enforce their quantum on the - * new driver. */ - lock_quantum |= s->lock_quantum; - lock_rate |= s->lock_rate; - } - if (!global_force_quantum && s->force_quantum > 0 && - s->stamp > quantum_stamp) { - node_def_quantum = node_min_quantum = node_max_quantum = s->force_quantum; - node_rate_quantum = 0; - quantum_stamp = s->stamp; - force_quantum = true; - } - if (!global_force_rate && s->force_rate > 0 && - s->stamp > rate_stamp) { - node_def_rate = s->force_rate; - node_n_rates = 1; - node_rates = &s->force_rate; - force_rate = true; - rate_stamp = s->stamp; - } - - /* smallest latencies */ - if (latency.denom == 0 || - (s->latency.denom > 0 && - fraction_compare(&s->latency, &latency) < 0)) - latency = s->latency; - if (max_latency.denom == 0 || - (s->max_latency.denom > 0 && - fraction_compare(&s->max_latency, &max_latency) < 0)) - max_latency = s->max_latency; - - /* largest rate, which is in fact the smallest fraction */ - if (rate.denom == 0 || - (s->rate.denom > 0 && - fraction_compare(&s->rate, &rate) < 0)) - rate = s->rate; - - if (s->active) - running = n->runnable; - - pw_log_debug("%p: follower %p running:%d runnable:%d rate:%u/%u latency %u/%u '%s'", - context, s, running, s->runnable, rate.num, rate.denom, - latency.num, latency.denom, s->name); - - if (running && s != n && s->supports_request > 0) - have_request = true; - - s->moved = false; - } - - if (n->forced_rate && !force_rate && n->runnable) { - /* A node that was forced to a rate but is no longer being - * forced can restore its rate */ - pw_log_info("(%s-%u) restore rate", n->name, n->info.id); - restore_rate = true; - } - if (n->forced_quantum && !force_quantum && n->runnable) { - /* A node that was forced to a quantum but is no longer being - * forced can restore its quantum */ - pw_log_info("(%s-%u) restore quantum", n->name, n->info.id); - restore_quantum = true; - } - - if (force_quantum) - lock_quantum = false; - if (force_rate) - lock_rate = false; - - need_resume = n->need_resume; - if (need_resume) { - running = true; - n->need_resume = false; - } - - current_rate = n->target_rate.denom; - if (!restore_rate && - (lock_rate || need_resume || !running || - (!force_rate && (n->info.state > PW_NODE_STATE_IDLE)))) { - pw_log_debug("%p: keep rate:1/%u restore:%u lock:%u resume:%u " - "running:%u force:%u state:%s", context, - current_rate, restore_rate, lock_rate, need_resume, - running, force_rate, - pw_node_state_as_string(n->info.state)); - - /* when we don't need to restore or rate and - * when someone wants us to lock the rate of this driver or - * when we are in the process of reconfiguring the driver or - * when we are not running any followers or - * when the driver is busy and we don't need to force a rate, - * keep the current rate */ - target_rate = current_rate; - } - else { - /* Here we are allowed to change the rate of the driver. - * Start with the default rate. If the desired rate is - * allowed, switch to it */ - if (rate.denom != 0 && rate.num == 1) - target_rate = rate.denom; - else - target_rate = node_def_rate; - - target_rate = find_best_rate(node_rates, node_n_rates, - target_rate, node_def_rate); - - pw_log_debug("%p: def_rate:%d target_rate:%d rate:%d/%d", context, - node_def_rate, target_rate, rate.num, rate.denom); - } - - was_target_pending = n->target_pending; - - if (target_rate != current_rate) { - /* we doing a rate switch */ - pw_log_info("(%s-%u) state:%s new rate:%u/(%u)->%u", - n->name, n->info.id, - pw_node_state_as_string(n->info.state), - n->target_rate.denom, current_rate, - target_rate); - - if (force_rate) { - if (settings->clock_rate_update_mode == CLOCK_RATE_UPDATE_MODE_HARD) - do_reconfigure |= !was_target_pending; - } else { - if (n->info.state >= PW_NODE_STATE_SUSPENDED) - do_reconfigure |= !was_target_pending; - } - /* we're setting the pending rate. This will become the new - * current rate in the next iteration of the graph. */ - n->target_rate = SPA_FRACTION(1, target_rate); - n->forced_rate = force_rate; - n->target_pending = true; - current_rate = target_rate; - } - - if (node_rate_quantum != 0 && current_rate != node_rate_quantum) { - /* the quantum values are scaled with the current rate */ - node_def_quantum = SPA_SCALE32(node_def_quantum, current_rate, node_rate_quantum); - node_min_quantum = SPA_SCALE32(node_min_quantum, current_rate, node_rate_quantum); - node_max_quantum = SPA_SCALE32(node_max_quantum, current_rate, node_rate_quantum); - } - - /* calculate desired quantum. Don't limit to the max_latency when we are - * going to force a quantum or rate and reconfigure the nodes. */ - if (max_latency.denom != 0 && !force_quantum && !force_rate) { - uint32_t tmp = SPA_SCALE32(max_latency.num, current_rate, max_latency.denom); - if (tmp < node_max_quantum) - node_max_quantum = tmp; - } - - current_quantum = n->target_quantum; - if (!restore_quantum && (lock_quantum || need_resume || !running)) { - pw_log_debug("%p: keep quantum:%u restore:%u lock:%u resume:%u " - "running:%u force:%u state:%s", context, - current_quantum, restore_quantum, lock_quantum, need_resume, - running, force_quantum, - pw_node_state_as_string(n->info.state)); - target_quantum = current_quantum; - } - else { - target_quantum = node_def_quantum; - if (latency.denom != 0) - target_quantum = SPA_SCALE32(latency.num, current_rate, latency.denom); - target_quantum = SPA_CLAMP(target_quantum, node_min_quantum, node_max_quantum); - target_quantum = SPA_CLAMP(target_quantum, floor_quantum, ceil_quantum); - - if (settings->clock_power_of_two_quantum && !force_quantum) - target_quantum = flp2(target_quantum); - } - - if (target_quantum != current_quantum) { - pw_log_info("(%s-%u) new quantum:%"PRIu64"->%u", - n->name, n->info.id, - n->target_quantum, - target_quantum); - /* this is the new pending quantum */ - n->target_quantum = target_quantum; - n->forced_quantum = force_quantum; - n->target_pending = true; - - if (force_quantum) - do_reconfigure |= !was_target_pending; - } - - if (n->target_pending) { - if (do_reconfigure) { - reconfigure_driver(context, n); - /* we might be suspended now and the links need to be prepared again */ - goto again; - } - /* we have a pending change. We place the new values in the - * pending fields so that they are picked up by the driver in - * the next cycle */ - pw_log_debug("%p: apply duration:%"PRIu64" rate:%u/%u", context, - n->target_quantum, n->target_rate.num, - n->target_rate.denom); - SPA_SEQ_WRITE(n->rt.position->clock.target_seq); - n->rt.position->clock.target_duration = n->target_quantum; - n->rt.position->clock.target_rate = n->target_rate; - SPA_SEQ_WRITE(n->rt.position->clock.target_seq); - - if (n->info.state < PW_NODE_STATE_RUNNING) { - n->rt.position->clock.duration = n->target_quantum; - n->rt.position->clock.rate = n->target_rate; - } - n->target_pending = false; - } else { - n->target_quantum = n->rt.position->clock.target_duration; - n->target_rate = n->rt.position->clock.target_rate; - } - - if (n->info.state < PW_NODE_STATE_RUNNING) - n->rt.position->clock.nsec = get_time_ns(n->rt.target.system); - - SPA_FLAG_UPDATE(n->rt.position->clock.flags, - SPA_IO_CLOCK_FLAG_LAZY, have_request && n->supports_lazy > 0); - - pw_log_debug("%p: driver %p running:%d runnable:%d quantum:%u rate:%u (%"PRIu64"/%u)'%s'", - context, n, running, n->runnable, target_quantum, target_rate, - n->rt.position->clock.target_duration, - n->rt.position->clock.target_rate.denom, n->name); - - transport = PW_NODE_ACTIVATION_COMMAND_NONE; - - /* first change the node states of the followers to the new target */ - spa_list_for_each(s, &n->follower_list, follower_link) { - if (s->transport != PW_NODE_ACTIVATION_COMMAND_NONE) { - transport = s->transport; - s->transport = PW_NODE_ACTIVATION_COMMAND_NONE; - } - if (s == n) - continue; - pw_log_debug("%p: follower %p: active:%d '%s'", - context, s, s->active, s->name); - ensure_state(s, running); - } - - if (transport != PW_NODE_ACTIVATION_COMMAND_NONE) { - pw_log_info("%s: transport %d", n->name, transport); - SPA_ATOMIC_STORE(n->rt.target.activation->command, transport); - } - - /* now that all the followers are ready, start the driver */ - ensure_state(n, running); - } -} - -static const struct pw_context_events context_events = { - PW_VERSION_CONTEXT_EVENTS, - .recalc_graph = context_recalc_graph, -}; - -static void module_destroy(void *data) -{ - struct impl *impl = data; - - if (impl->context) { - spa_hook_remove(&impl->context_listener); - spa_hook_remove(&impl->module_listener); - } - - pw_properties_free(impl->props); - - free(impl); -} - -static const struct pw_impl_module_events module_events = { - PW_VERSION_IMPL_MODULE_EVENTS, - .destroy = module_destroy, -}; - -SPA_EXPORT -int pipewire__module_init(struct pw_impl_module *module, const char *args_str) -{ - struct pw_context *context = pw_impl_module_get_context(module); - struct pw_properties *args; - struct impl *impl; - int res; - - PW_LOG_TOPIC_INIT(mod_topic); - - impl = calloc(1, sizeof(struct impl)); - if (impl == NULL) - return -errno; - - pw_log_debug("module %p: new %s", impl, args_str); - - if (args_str) - args = pw_properties_new_string(args_str); - else - args = pw_properties_new(NULL, NULL); - - if (!args) { - res = -errno; - goto error; - } - - pw_context_conf_update_props(context, "module."NAME".args", args); - - impl->props = args; - impl->context = context; - - pw_context_add_listener(context, &impl->context_listener, &context_events, impl); - pw_impl_module_add_listener(module, &impl->module_listener, &module_events, impl); - - pw_impl_module_update_properties(module, &SPA_DICT_INIT_ARRAY(module_props)); - - return 0; - -error: - module_destroy(impl); - return res; -} diff --git a/src/modules/module-sendspin-recv.c b/src/modules/module-sendspin-recv.c 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 7deb57e89..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; - 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-snapcast-discover.c b/src/modules/module-snapcast-discover.c index 5fe2dedd9..596d5677b 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" @@ -158,8 +162,7 @@ static const struct spa_dict_item module_props[] = { { PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, }; -#define SERVICE_TYPE_JSONRPC "_snapcast-jsonrpc._tcp" -#define SERVICE_TYPE_CONTROL "_snapcast-ctrl._tcp" +#define SERVICE_TYPE_CONTROL "_snapcast-jsonrpc._tcp" struct impl { struct pw_context *context; @@ -171,8 +174,9 @@ struct impl { bool discover_local; struct pw_loop *loop; - struct pw_zeroconf *zeroconf; - struct spa_hook zeroconf_listener; + AvahiPoll *avahi_poll; + AvahiClient *client; + AvahiServiceBrowser *sink_browser; struct spa_list tunnel_list; uint32_t id; @@ -200,6 +204,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 +252,12 @@ 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->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); } @@ -264,7 +274,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 +391,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) { @@ -605,26 +612,35 @@ 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 - skipping tunnel creation", at); + goto done; + } + pw_log_info("%s %s", name, at); tinfo = TUNNEL_INFO(.name = name, .port = port); @@ -636,8 +652,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 +662,28 @@ 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); + + 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 +717,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 +744,125 @@ 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->sink_browser == NULL) + impl->sink_browser = make_browser(impl, SERVICE_TYPE_CONTROL); + 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 ((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 +896,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-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..16f9d9273 100644 --- a/src/modules/network-utils.h +++ b/src/modules/network-utils.h @@ -7,13 +7,6 @@ #include #include #include -#include -#include -#include -#include -#include - -#include #ifdef __FreeBSD__ #define ifr_ifindex ifr_index @@ -87,64 +80,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) @@ -196,66 +131,5 @@ static inline bool pw_net_addr_is_any(struct sockaddr_storage *addr) return false; } -#ifndef LISTEN_FDS_START -#define LISTEN_FDS_START 3 -#endif - -/* Returns the number of file descriptors passed for socket activation. - * Returns 0 if none, -1 on error. */ -static inline int listen_fds(void) -{ - uint32_t n; - int i, flags; - - if (!spa_atou32(getenv("LISTEN_FDS"), &n, 10) || n > INT_MAX - LISTEN_FDS_START) { - errno = EINVAL; - return -1; - } - - for (i = 0; i < (int)n; i++) { - flags = fcntl(LISTEN_FDS_START + i, F_GETFD); - if (flags == -1) - return -1; - if (fcntl(LISTEN_FDS_START + i, F_SETFD, flags | FD_CLOEXEC) == -1) - return -1; - } - - return (int)n; -} - -/* Check if the fd is a listening unix socket of the given type, - * optionally bound to the given path. */ -static inline int is_socket_unix(int fd, int type, const char *path) -{ - struct sockaddr_un addr; - int val; - socklen_t len = sizeof(val); - - if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &val, &len) < 0) - return -errno; - if (val != type) - return 0; - - if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &val, &len) < 0) - return -errno; - if (!val) - return 0; - - if (path) { - len = sizeof(addr); - memset(&addr, 0, sizeof(addr)); - if (getsockname(fd, (struct sockaddr *)&addr, &len) < 0) - return -errno; - if (addr.sun_family != AF_UNIX) - return 0; - size_t length = strlen(path); - if (len < offsetof(struct sockaddr_un, sun_path) + length + 1) - return 0; - if (memcmp(addr.sun_path, path, length + 1) != 0) - return 0; - } - - return 1; -} #endif /* NETWORK_UTILS_H */ 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..d3040b761 100644 --- a/src/pipewire/capabilities.h +++ b/src/pipewire/capabilities.h @@ -21,16 +21,15 @@ extern "C" { * \{ */ -/**< Link capable of device ID negotiation. The value is to the version of the - * API specification. */ +/**< Link capable of device ID negotiation. The value is either "true" or "false" */ #define PW_CAPABILITY_DEVICE_ID_NEGOTIATION "pipewire.device-id-negotiation" /**< Link with device ID negotition capability supports negotiating with - * a specific set of devices. The value of API version 1 consists of a JSON - * object containing a single key "available-devices" that contain a list of - * hexadecimal encoded `dev_t` device IDs. - */ + * provided list of devices. The value consists of a JSON encoded string array + * of base64 encoded dev_t values. */ #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..ddcdd2cba 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,389 @@ 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; + } + + 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 1b8297935..bb23c9d5c 100644 --- a/src/pipewire/filter.c +++ b/src/pipewire/filter.c @@ -1793,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])); } @@ -1859,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"); } } @@ -1991,8 +1986,7 @@ int pw_filter_get_time(struct pw_filter *filter, struct pw_time *time) pw_log_trace("%p: %"PRIi64" %"PRIi64" %"PRIu64" %d/%d ", filter, time->now, time->delay, time->ticks, time->rate.num, time->rate.denom); - - return filter->state == PW_FILTER_STATE_STREAMING ? 0 : -EIO; + return 0; } SPA_EXPORT diff --git a/src/pipewire/impl-link.c b/src/pipewire/impl-link.c index 6eae5e6c7..a77dcf35f 100644 --- a/src/pipewire/impl-link.c +++ b/src/pipewire/impl-link.c @@ -701,10 +701,7 @@ static int do_allocation(struct pw_impl_link *this) /* always enable async mode */ alloc_flags = PW_BUFFERS_FLAG_ASYNC; - /* shared mem can only be used if both nodes are in the same process - * and we are sure that the buffers are never going to be shared - * because of the exclusive flag */ - if (output->node->remote || input->node->remote || !output->exclusive) + if (output->node->remote || input->node->remote) alloc_flags |= PW_BUFFERS_FLAG_SHARED; if (output->node->driver) @@ -972,14 +969,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 +1053,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 +1219,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 +1239,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 +1476,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 +1526,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); diff --git a/src/pipewire/impl-node.c b/src/pipewire/impl-node.c index c72dc7dcd..85bf452b0 100644 --- a/src/pipewire/impl-node.c +++ b/src/pipewire/impl-node.c @@ -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, @@ -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,39 +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, "/Sink") != NULL || strstr(str, "/Source") != NULL)) - str = "follow-suspend"; - else - str = "false"; - } - while ((s = pw_split_walk(str, ",\0", &len, &state))) { - char v[16] = { 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, "out-follow")) - node->passive_mode[SPA_DIRECTION_OUTPUT] = PASSIVE_MODE_FOLLOW; - else if (spa_streq(v, "in-follow")) - node->passive_mode[SPA_DIRECTION_INPUT] = PASSIVE_MODE_FOLLOW; - else if (spa_streq(v, "in")) - node->passive_mode[SPA_DIRECTION_INPUT] = PASSIVE_MODE_TRUE; - else if (spa_streq(v, "follow-suspend")) { - node->passive_mode[SPA_DIRECTION_INPUT] = PASSIVE_MODE_FOLLOW_SUSPEND; - node->passive_mode[SPA_DIRECTION_OUTPUT] = PASSIVE_MODE_FOLLOW_SUSPEND; - } - else if (spa_streq(v, "follow")) { - node->passive_mode[SPA_DIRECTION_INPUT] = PASSIVE_MODE_FOLLOW; - node->passive_mode[SPA_DIRECTION_OUTPUT] = PASSIVE_MODE_FOLLOW; - } - else { - node->passive_mode[SPA_DIRECTION_OUTPUT] = - node->passive_mode[SPA_DIRECTION_INPUT] = - spa_atob(v) ? PASSIVE_MODE_TRUE : PASSIVE_MODE_FALSE; - } + 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); @@ -1332,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) @@ -1346,8 +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:%d:%d", node, node->driver, - recalc_reason, node->active, node->passive_mode[0], 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); @@ -2638,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 25dbdf718..9d49a8097 100644 --- a/src/pipewire/impl-port.c +++ b/src/pipewire/impl-port.c @@ -537,17 +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 { - if (spa_streq(str, "follow")) { - port->passive_mode = PASSIVE_MODE_FOLLOW; - } else { - port->passive_mode = spa_atob(str) ? - PASSIVE_MODE_TRUE : PASSIVE_MODE_FALSE; - } - } + /* 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 || @@ -1094,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; @@ -1150,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; @@ -1605,8 +1595,6 @@ void pw_impl_port_destroy(struct pw_impl_port *port) pw_param_clear(&impl->pending_list, SPA_ID_INVALID); free(port->tag[SPA_DIRECTION_INPUT]); free(port->tag[SPA_DIRECTION_OUTPUT]); - free(port->cap[SPA_DIRECTION_INPUT]); - free(port->cap[SPA_DIRECTION_OUTPUT]); pw_map_clear(&port->mix_port_map); @@ -1735,13 +1723,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; } } @@ -1949,7 +1932,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 e398a8f09..642a6b78e 100644 --- a/src/pipewire/mem.c +++ b/src/pipewire/mem.c @@ -15,7 +15,6 @@ #include #include -#include #include #include @@ -857,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/private.h b/src/pipewire/private.h index 27eaaeb96..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 */ @@ -1044,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; }; @@ -1272,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 4ee2138bc..477861f61 100644 --- a/src/pipewire/stream.c +++ b/src/pipewire/stream.c @@ -2504,8 +2504,7 @@ int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t impl->dequeued.outcount, impl->dequeued.incount, impl->queued.outcount, impl->queued.incount, avail_buffers, impl->n_buffers); - - return stream->state == PW_STREAM_STATE_STREAMING ? 0 : -EIO; + return 0; } SPA_EXPORT diff --git a/src/pipewire/stream.h b/src/pipewire/stream.h index d5cd87188..28b4eba1f 100644 --- a/src/pipewire/stream.h +++ b/src/pipewire/stream.h @@ -293,8 +293,7 @@ struct pw_stream_control { * Use pw_stream_get_time_n() to get an updated time snapshot of the stream. * The time snapshot can give information about the time in the driver of the * graph, the delay to the edge of the graph and the internal queuing in the - * stream. This function should only be called in the STREAMING state and will - * return an error when called in any other state. + * stream. * * pw_time.ticks gives a monotonic increasing counter of the time in the graph * driver. I can be used to generate a timeline to schedule samples as well @@ -595,7 +594,7 @@ const struct pw_stream_control *pw_stream_get_control(struct pw_stream *stream, /** Set control values */ int pw_stream_set_control(struct pw_stream *stream, uint32_t id, uint32_t n_values, float *values, ...); -/** Query the time on the stream. Returns an error when the stream is not running. RT safe */ +/** Query the time on the stream, RT safe */ int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t size); /** Get the current time in nanoseconds. This value can be compared with 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/tests/test-stream.c b/src/tests/test-stream.c index b5061e927..d56b6c85b 100644 --- a/src/tests/test-stream.c +++ b/src/tests/test-stream.c @@ -151,7 +151,7 @@ static void test_create(void) /* check id, only when connected */ spa_assert_se(pw_stream_get_node_id(stream) == SPA_ID_INVALID); - spa_assert_se(pw_stream_get_time_n(stream, &tm, sizeof(tm)) == -EIO); + spa_assert_se(pw_stream_get_time_n(stream, &tm, sizeof(tm)) == 0); spa_assert_se(tm.now == 0); spa_assert_se(tm.rate.num == 0); spa_assert_se(tm.rate.denom == 0); diff --git a/src/tools/pw-cat.c b/src/tools/pw-cat.c index c8ee6a195..bdd44a255 100644 --- a/src/tools/pw-cat.c +++ b/src/tools/pw-cat.c @@ -120,7 +120,6 @@ struct data { const char *media_role; const char *channel_map; const char *format; - const char *container; const char *target; const char *latency; struct pw_properties *props; @@ -195,61 +194,29 @@ struct data { uint64_t samples_processed; }; +#define STR_FMTS "(ulaw|alaw|u8|s8|s16|s32|f32|f64)" + static const struct format_info { - const char *sf_name; + const char *name; int sf_format; - uint32_t sf_width; - const char *spa_name; uint32_t spa_format; - uint32_t spa_width; -#define FORMAT_ENCODED (1<<0) - uint32_t flags; + uint32_t width; } format_info[] = { - { "ulaw", SF_FORMAT_ULAW, 1, "ulaw", SPA_AUDIO_FORMAT_ULAW, 1, 0 }, - { "alaw", SF_FORMAT_ULAW, 1, "alaw", SPA_AUDIO_FORMAT_ALAW, 1, 0 }, - { "s8", SF_FORMAT_PCM_S8, 1, "s8", SPA_AUDIO_FORMAT_S8, 1, 0 }, - { "u8", SF_FORMAT_PCM_U8, 1, "u8", SPA_AUDIO_FORMAT_U8, 1, 0 }, - { "s16", SF_FORMAT_PCM_16, 2, "s16", SPA_AUDIO_FORMAT_S16, 2, 0 }, - /* we read and write S24 as S32 with sndfile */ - { "s24", SF_FORMAT_PCM_24, 3, "s32", SPA_AUDIO_FORMAT_S32, 4, 0 }, - { "s32", SF_FORMAT_PCM_32, 4, "s32", SPA_AUDIO_FORMAT_S32, 4, 0 }, - { "f32", SF_FORMAT_FLOAT, 4, "f32", SPA_AUDIO_FORMAT_F32, 4, 0 }, - { "f64", SF_FORMAT_DOUBLE, 8, "f64", SPA_AUDIO_FORMAT_F32, 8, 0 }, - - { "mp1", SF_FORMAT_MPEG_LAYER_I, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "mp2", SF_FORMAT_MPEG_LAYER_II, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "mp3", SF_FORMAT_MPEG_LAYER_III, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "vorbis", SF_FORMAT_VORBIS, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "opus", SF_FORMAT_OPUS, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - - { "ima-adpcm", SF_FORMAT_IMA_ADPCM, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "ms-adpcm", SF_FORMAT_MS_ADPCM, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "nms-adpcm-16", SF_FORMAT_NMS_ADPCM_16, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "nms-adpcm-24", SF_FORMAT_NMS_ADPCM_24, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "nms-adpcm-32", SF_FORMAT_NMS_ADPCM_32, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - - { "alac-16", SF_FORMAT_ALAC_16, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "alac-20", SF_FORMAT_ALAC_20, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "alac-24", SF_FORMAT_ALAC_24, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "alac-32", SF_FORMAT_ALAC_32, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - - { "gsm610", SF_FORMAT_GSM610, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "g721-32", SF_FORMAT_G721_32, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "g723-24", SF_FORMAT_G723_24, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "g723-40", SF_FORMAT_G723_40, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "dwvw-12", SF_FORMAT_DWVW_12, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "dwvw-16", SF_FORMAT_DWVW_16, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "dwvw-24", SF_FORMAT_DWVW_24, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "vox", SF_FORMAT_VOX_ADPCM, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "dpcm-16", SF_FORMAT_DPCM_16, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - { "dpcm-8", SF_FORMAT_DPCM_8, 1, "f32", SPA_AUDIO_FORMAT_F32, 4, FORMAT_ENCODED }, - + { "ulaw", SF_FORMAT_ULAW, SPA_AUDIO_FORMAT_ULAW, 1 }, + { "alaw", SF_FORMAT_ULAW, SPA_AUDIO_FORMAT_ALAW, 1 }, + { "s8", SF_FORMAT_PCM_S8, SPA_AUDIO_FORMAT_S8, 1 }, + { "u8", SF_FORMAT_PCM_U8, SPA_AUDIO_FORMAT_U8, 1 }, + { "s16", SF_FORMAT_PCM_16, SPA_AUDIO_FORMAT_S16, 2 }, + { "s24", SF_FORMAT_PCM_24, SPA_AUDIO_FORMAT_S24, 3 }, + { "s32", SF_FORMAT_PCM_32, SPA_AUDIO_FORMAT_S32, 4 }, + { "f32", SF_FORMAT_FLOAT, SPA_AUDIO_FORMAT_F32, 4 }, + { "f64", SF_FORMAT_DOUBLE, SPA_AUDIO_FORMAT_F32, 8 }, }; static const struct format_info *format_info_by_name(const char *str) { SPA_FOR_EACH_ELEMENT_VAR(format_info, i) - if (spa_streq(str, i->sf_name)) + if (spa_streq(str, i->name)) return i; return NULL; } @@ -263,14 +230,6 @@ static const struct format_info *format_info_by_sf_format(int format) return NULL; } -static void list_formats(struct data *d) -{ - - fprintf(stdout, _("Supported formats:\n")); - SPA_FOR_EACH_ELEMENT_VAR(format_info, i) - fprintf(stdout, " %s\n", i->sf_name); -} - static int sf_playback_fill_x8(struct data *d, void *dest, unsigned int n_frames, bool *null_frame) { sf_count_t rn; @@ -749,34 +708,6 @@ static int parse_channelmap(const char *channel_map, struct spa_audio_layout_inf return 0; } -static void list_layouts(struct data *d) -{ - fprintf(stderr, _("Supported channel layouts:\n")); - SPA_FOR_EACH_ELEMENT_VAR(spa_type_audio_layout_info, i) { - if (i->name == NULL) - break; - fprintf(stdout, " %s: [", i->name); - for (uint32_t j = 0; j < i->layout.n_channels; j++) - fprintf(stdout, "%s%s", j == 0 ? " " : ", ", - spa_type_audio_channel_to_short_name(i->layout.position[j])); - fprintf(stdout, " ]\n"); - } - fprintf(stderr, _("Supported channel layout aliases:\n")); - SPA_FOR_EACH_ELEMENT_VAR(maps, m) - fprintf(stdout, _(" %s -> %s\n"), m->name, m->alias); -} - -static void list_channel_names(struct data *d) -{ - fprintf(stderr, _("Supported channel names:\n")); - SPA_FOR_EACH_ELEMENT_VAR(spa_type_audio_channel, i) { - if (i->name == NULL || SPA_AUDIO_CHANNEL_IS_AUX(i->type)) - break; - fprintf(stdout, " %s\n", spa_type_short_name(i->name)); - } - fprintf(stderr, " AUX0 ... AUX4095\n"); -} - static int channelmap_default(struct spa_audio_layout_info *map, int n_channels) { switch(n_channels) { @@ -1117,17 +1048,12 @@ enum { OPT_CHANNELMAP, OPT_FORMAT, OPT_VOLUME, - OPT_CONTAINER, - OPT_LISTFORMATS, - OPT_LISTCONTAINERS, - OPT_LISTLAYOUTS, - OPT_LISTCHANNELNAMES, }; #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[] = { @@ -1156,19 +1082,13 @@ static const struct option long_options[] = { { "rate", required_argument, NULL, OPT_RATE }, { "channels", required_argument, NULL, OPT_CHANNELS }, { "channel-map", required_argument, NULL, OPT_CHANNELMAP }, - { "list-layouts", no_argument, NULL, OPT_LISTLAYOUTS }, - { "list-channel-names", no_argument, NULL, OPT_LISTCHANNELNAMES }, { "format", required_argument, NULL, OPT_FORMAT }, - { "list-formats", no_argument, NULL, OPT_LISTFORMATS }, - { "container", required_argument, NULL, OPT_CONTAINER }, - { "list-containers", no_argument, NULL, OPT_LISTCONTAINERS }, { "volume", required_argument, NULL, OPT_VOLUME }, { "quality", required_argument, NULL, 'q' }, - { "raw", no_argument, NULL, 'a' }, + { "raw", no_argument, NULL, 'a' }, { "force-midi", required_argument, NULL, 'M' }, { "sample-count", required_argument, NULL, 'n' }, - { "midi-clip", no_argument, NULL, 'c' }, - { "monitor", no_argument, NULL, 'C' }, + { "midi-clip", no_argument, NULL, 'c' }, { NULL, 0, NULL, 0 } }; @@ -1193,7 +1113,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" @@ -1206,17 +1125,12 @@ static void show_usage(const char *name, bool is_error) DEFAULT_TARGET, DEFAULT_LATENCY_PLAY); fprintf(fp, - _(" --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\", \"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" + " --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" @@ -1225,7 +1139,7 @@ static void show_usage(const char *name, bool is_error) "\n"), DEFAULT_RATE, DEFAULT_CHANNELS, - DEFAULT_FORMAT, + STR_FMTS, DEFAULT_FORMAT, DEFAULT_VOLUME, DEFAULT_QUALITY); @@ -1683,13 +1597,8 @@ static int setup_raw(struct data *data) if (info == NULL) return -EINVAL; - if (info->flags & FORMAT_ENCODED) { - fprintf(stderr, "raw: raw encoded format %s not supported\n", info->sf_name); - return -ENOTSUP; - } - data->spa_format = info->spa_format; - data->stride = info->spa_width * data->channels; + data->stride = info->width * data->channels; data->fill = data->mode == mode_playback ? raw_play : raw_record; if (spa_streq(data->filename, "-")) { @@ -1708,7 +1617,7 @@ static int setup_raw(struct data *data) if (data->verbose) fprintf(stderr, "raw: rate=%u channels=%u fmt=%s samplesize=%u stride=%u\n", data->rate, data->channels, - info->spa_name, info->spa_width, data->stride); + info->name, info->width, data->stride); return 0; } @@ -1764,20 +1673,17 @@ static int fill_properties(struct data *data) return 0; } -static void format_from_filename(SF_INFO *info, const char *filename, const char *container) +static void format_from_filename(SF_INFO *info, const char *filename) { int i, count = 0; int format = -1; - const char *extension; - if (spa_streq(filename, "-")) - extension = container ? container : "au"; - else if (container) - extension = container; - else - extension = filename; +#if __BYTE_ORDER == __BIG_ENDIAN + info->format |= SF_ENDIAN_BIG; +#else + info->format |= SF_ENDIAN_LITTLE; +#endif - fprintf(stderr, "%s\n", filename); if (sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) != 0) count = 0; @@ -1789,67 +1695,22 @@ static void format_from_filename(SF_INFO *info, const char *filename, const char if (sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) != 0) continue; - if (spa_strendswith(extension, fi.extension)) { + if (spa_strendswith(filename, fi.extension)) { format = fi.format; break; } } - if (format == -1) { - if (sf_command(NULL, SFC_GET_SIMPLE_FORMAT_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_SIMPLE_FORMAT, &fi, sizeof(fi)) != 0) - continue; - - if (spa_strendswith(extension, fi.extension)) { - format = fi.format; - info->format = 0; - break; - } - } - } if (format == -1) - format = SF_FORMAT_WAV; + format = spa_streq(filename, "-") ? SF_FORMAT_AU : SF_FORMAT_WAV; if (format == SF_FORMAT_WAV && info->channels > 2) format = SF_FORMAT_WAVEX; - switch (format & SF_FORMAT_TYPEMASK) { - case SF_FORMAT_OGG: - case SF_FORMAT_FLAC: - case SF_FORMAT_MPEG: - case SF_FORMAT_AIFF: - info->format |= SF_ENDIAN_FILE; - break; - default: - info->format |= SF_ENDIAN_CPU; - break; - } info->format |= format; -} -static void list_containers(struct data *d) -{ - int i, count = 0; - - fprintf(stderr, _("Supported containers and extensions:\n")); - 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; - - fprintf(stderr, " %s: %s\n", fi.extension, fi.name); - } + if (format == SF_FORMAT_OGG || format == SF_FORMAT_FLAC) + info->format = (info->format & ~SF_FORMAT_ENDMASK) | SF_ENDIAN_FILE; + if (format == SF_FORMAT_OGG) + info->format = (info->format & ~SF_FORMAT_SUBMASK) | SF_FORMAT_VORBIS; } #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION @@ -1973,7 +1834,7 @@ static int setup_sndfile(struct data *data) info.samplerate = data->rate; info.channels = data->channels; info.format = fi->sf_format; - format_from_filename(&info, data->filename, data->container); + format_from_filename(&info, data->filename); } data->sndfile.file = sf_open(data->filename, @@ -2046,10 +1907,14 @@ static int setup_sndfile(struct data *data) if (data->verbose) fprintf(stderr, "PCM: fmt:%s rate:%u channels:%u width:%u\n", - fi->spa_name, data->rate, data->channels, fi->spa_width); + fi->name, data->rate, data->channels, fi->width); + + /* we read and write S24 as S32 with sndfile */ + if (fi->spa_format == SPA_AUDIO_FORMAT_S24) + fi = format_info_by_sf_format(SF_FORMAT_PCM_32); data->spa_format = fi->spa_format; - data->stride = fi->spa_width * data->channels; + data->stride = fi->width * data->channels; data->fill = data->mode == mode_playback ? playback_fill_fn(data->spa_format) : record_fill_fn(data->spa_format); @@ -2330,9 +2195,6 @@ int main(int argc, char *argv[]) case OPT_FORMAT: data.format = optarg; break; - case OPT_CONTAINER: - data.container = optarg; - break; case OPT_VOLUME: if (!spa_atof(optarg, &data.volume)) @@ -2344,21 +2206,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; - case OPT_LISTCONTAINERS: - list_containers(&data); - return EXIT_SUCCESS; - case OPT_LISTLAYOUTS: - list_layouts(&data); - return EXIT_SUCCESS; - case OPT_LISTCHANNELNAMES: - list_channel_names(&data); - return EXIT_SUCCESS; default: goto error_usage; } 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-dump.c b/src/tools/pw-dump.c index c939b7302..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); } @@ -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++]; diff --git a/src/tools/pw-mididump.c b/src/tools/pw-mididump.c index 882bd4702..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; }; @@ -174,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); @@ -199,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); } @@ -239,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 b4601b906..e66de979c 100644 --- a/src/tools/pw-mon.c +++ b/src/tools/pw-mon.c @@ -780,7 +780,7 @@ static void show_help(const char *name, bool error) " -N, --no-colors disable color output\n" " -C, --color[=WHEN] whether to enable color support. WHEN is `never`, `always`, or `auto`\n" " -o, --hide-props hide node properties\n" - " -a, --hide-params hide node parameters\n" + " -a, --hide-params hide node properties\n" " -p, --print-separator print empty line after every event to help streaming parser\n", name); } 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-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",