Compare commits

..

No commits in common. "master" and "1.5.85" have entirely different histories.

76 changed files with 1610 additions and 3240 deletions

View file

@ -658,7 +658,7 @@ doccheck:
- cat pipewire_module_pages - cat pipewire_module_pages
- | - |
for page in $(cat pipewire_module_pages); do 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 done
check_missing_headers: check_missing_headers:

102
NEWS
View file

@ -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) # PipeWire 1.5.85 (2026-01-19)
This is the fifth and hopefully last 1.6 release candidate that This is the fifth and hopefully last 1.6 release candidate that
@ -156,6 +57,9 @@ releases.
## Docs ## Docs
- Document the resampler properties better. - Document the resampler properties better.
Older versions:
# PipeWire 1.5.84 (2025-11-27) # PipeWire 1.5.84 (2025-11-27)
This is the fourth 1.6 release candidate that is API and ABI This is the fourth 1.6 release candidate that is API and ABI

View file

@ -1171,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. Do not enable this setting if you don't know what all this means, as it won't work.
\endparblock \endparblock
@PAR@ monitor-prop bluez5.hw-offload-datapath # integer
\parblock
HFP/HSP hardware offload data path ID (default: 0).
This feature configures the SCO hardwareoffload data path for HFP/HSP using the Bluetooth
SIGspecified 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 @PAR@ monitor-prop bluez5.a2dp.opus.pro.channels = 3 # integer
PipeWire Opus Pro audio profile channel count. PipeWire Opus Pro audio profile channel count.
@ -1211,7 +1202,6 @@ PipeWire Opus Pro audio profile duplex max bitrate.
PipeWire Opus Pro audio profile duplex frame duration (1/10 ms). PipeWire Opus Pro audio profile duplex frame duration (1/10 ms).
@PAR@ monitor-prop bluez5.bcast_source.config = [] # JSON @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 \parblock
Example: Example:
``` ```
@ -1373,12 +1363,6 @@ BAP QoS framing that needs to be applied for vendor defined preset
This property is experimental. This property is experimental.
Default: as per QoS preset. Default: as per QoS preset.
@PAR@ device-prop bluez5.bap.force-target-latency = "balanced" # string
BAP QoS target latency profile forced for QoS configuration selection.
If not set or set to "balanced", both low-latency and high-reliabilty QoS configuration table are used.
This property is experimental.
Available: low-latency, high-reliabilty, balanced
## Node properties ## Node properties
@PAR@ node-prop bluez5.media-source-role # string @PAR@ node-prop bluez5.media-source-role # string

View file

@ -313,13 +313,12 @@ performed.
Device ID negotiation needs explicit support by both end points of a stream, thus, the 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 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 done by advertising a \ref SPA_PARAM_Capability with the key \ref
PW_CAPABILITY_DEVICE_ID_NEGOTIATION and value `1` which corresponds to the PW_CAPABILITY_DEVICE_ID_NEGOTIATION and value `true`
current negotiation API version.
``` ```
spa_param_dict_build_dict(&b, SPA_PARAM_Capability, spa_param_dict_build_dict(&b, SPA_PARAM_Capability,
&SPA_DICT_ITEMS( &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 To do this, when connecting to the stream, the \ref PW_STREAM_FLAG_INACTIVE flag must be
@ -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. 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 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. \ref PW_CAPABILITY_DEVICE_IDS set to a string of base 64 encoded `dev_t` device IDs.
This JSON object as of version 1 contains a single key "available-devices" that contain
a list of hexadecimal 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_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))); SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_IDS, device_ids)));
``` ```

View file

@ -32,11 +32,8 @@ updated as follows:
- \ref spa_io_clock::nsec : Must be set to the time (according to the monotonic - \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 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 minimize jitter, it is usually a good idea to increment this by a fixed amount
amount (instead of sampling a timestamp from the monotonic system clock)
except for when the driver starts and when discontinuities occur in its clock. 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::rate : Set to a value that can translate samples to nanoseconds.
- \ref spa_io_clock::position : Current cycle position, in samples. This is the - \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). 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 cases, this may actually be in the past relative to nsec, for example, when
some internal driver clock experienced a discontinuity. Consider setting the 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 \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. 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 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 to that function call. That call must happen inside the thread that runs the
data loop assigned to the driver node. 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 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 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 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 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 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 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 correction factor causes the durations between the timeouts to be adjusted such that the
durations between the timerfd timeouts) to be adjusted such that, over time, the difference difference between the expected position and the actual position reaches zero. Keep in
between the expected position and the actual position reaches zero. Keep in mind the mind the notes above about \ref spa_io_clock::position being the ideal position of the
notes above about \ref spa_io_clock::position being the ideal position of the graph graph cycle, meaning that even in this case, the duration it is incremented by is
cycle, meaning that even in this case, the duration it is incremented by is _not_ _not_ scaled by the correction factor; the duration in samples remains unchanged.
scaled by the correction factor; the duration in samples remains unchanged.
(Other popular control loop mechanisms that are suitable alternatives to the DLL are (Other popular control loop mechanisms that are suitable alternatives to the DLL are
PID controllers and Kalman filters.) PID controllers and Kalman filters.)

View file

@ -81,7 +81,6 @@ List of known modules:
- \subpage page_module_raop_discover - \subpage page_module_raop_discover
- \subpage page_module_roc_sink - \subpage page_module_roc_sink
- \subpage page_module_roc_source - \subpage page_module_roc_source
- \subpage page_module_scheduler_v1
- \subpage page_module_rtp_sap - \subpage page_module_rtp_sap
- \subpage page_module_rtp_sink - \subpage page_module_rtp_sink
- \subpage page_module_rtp_source - \subpage page_module_rtp_source

View file

@ -24,9 +24,9 @@ Play and record media with PipeWire
**pw-cat** is a simple tool for playing back or capturing raw or encoded **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 media files on a PipeWire server. It understands all audio file formats
supported by `libsndfile` for PCM capture and playback. When no container supported by `libsndfile` for PCM capture and playback. When capturing
is specified for capturing PCM, the filename extension is used to guess PCM, the filename extension is used to guess the file format with the
the file format with the WAV file format as the default. WAV file format as the default.
It understands standard MIDI files and MIDI 2.0 clip files for playback 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 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 work with native DSD capable hardware and will produce an error when no
such hardware was found. such hardware was found.
When the *FILE* is - input will be from STDIN. If no format is specified, When the *FILE* is - input and output will be raw data from STDIN and
libsndfile will attempt to parse and stream the format from STDIN. For STDOUT respectively.
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.
# OPTIONS # 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 render the DSD audio. You need a DSD capable device to play DSD content
or this program will exit with an error. 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 \par \--media-type=VALUE
Set the media type property (default Audio/Midi depending on mode). The 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 media type is used by the session manager to select a suitable target to
@ -150,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 Higher quality uses more CPU. Values between 0 and 15 are allowed, the
default quality is 4. 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 \par \--rate=VALUE
The sample rate, default 48000. The sample rate, default 48000.
@ -168,38 +145,19 @@ The sample rate, default 48000.
The number of channels, default 2. The number of channels, default 2.
\par \--channel-map=VALUE \par \--channel-map=VALUE
The channelmap. Possible values include are either a channel layout The channelmap. Possible values include: **mono**, **stereo**,
such as **mono**, **stereo**,
**surround-21**, **quad**, **surround-22**, **surround-40**, **surround-21**, **quad**, **surround-22**, **surround-40**,
or comma separated array of channel names such as **FL,FR**. **surround-31**, **surround-41**, **surround-50**, **surround-51**,
See \--list-layouts and \--list-channel-names to get a complete **surround-51r**, **surround-70**, **surround-71** or a comma separated
list of possible values. list of channel names: **FL**, **FR**, **FC**, **LFE**, **SL**, **SR**,
**FLC**, **FRC**, **RC**, **RL**, **RR**, **TC**, **TFL**, **TFC**,
\par \--list-layouts **TFR**, **TRL**, **TRC**, **TRR**, **RLC**, **RRC**, **FLW**, **FRW**,
List all known channel layouts. One of these can be used as the **LFE2**, **FLH**, **FCH**, **FRH**, **TFLC**, **TFRC**, **TSL**,
\--channel-map value. **TSR**, **LLFR**, **RLFE**, **BC**, **BLC**, **BRC**
\par \--list-channel-names
List all known channel names. An array of these can be used as the
\--channel-map value.
\par \--format=VALUE \par \--format=VALUE
The sample format to use. Some possible values include: **u8**, **s8**, The sample format to use. One of: **u8**, **s8**, **s16** (default),
**s16** (default), **s24**, **s32**, **f32**, **f64**. See **s24**, **s32**, **f32**, **f64**.
\--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.
\par \--volume=VALUE \par \--volume=VALUE
The stream volume, default 1.000. Depending on the locale you have The stream volume, default 1.000. Depending on the locale you have

View file

@ -13,7 +13,7 @@ node and device statistics.
A hierarchical view is shown of Driver nodes and follower nodes. The A hierarchical view is shown of Driver nodes and follower nodes. The
Driver nodes are actively using a timer to schedule dataflow in 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. with a + sign (or = for async nodes) in a tree-like representation.
The columns presented are as follows: The columns presented are as follows:

View file

@ -1,5 +1,5 @@
project('pipewire', ['c' ], project('pipewire', ['c' ],
version : '1.7.0', version : '1.5.85',
license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ], license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ],
meson_version : '>= 0.61.1', meson_version : '>= 0.61.1',
default_options : [ 'warning_level=3', default_options : [ 'warning_level=3',
@ -82,7 +82,6 @@ common_flags = [
'-fvisibility=hidden', '-fvisibility=hidden',
'-fno-strict-aliasing', '-fno-strict-aliasing',
'-fno-strict-overflow', '-fno-strict-overflow',
'-DSPA_AUDIO_MAX_CHANNELS=128u',
'-Werror=suggest-attribute=format', '-Werror=suggest-attribute=format',
'-Wsign-compare', '-Wsign-compare',
'-Wpointer-arith', '-Wpointer-arith',
@ -116,7 +115,7 @@ cc_flags = common_flags + [
'-Werror=old-style-definition', '-Werror=old-style-definition',
'-Werror=missing-parameter-type', '-Werror=missing-parameter-type',
'-Werror=strict-prototypes', '-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.get_supported_arguments(cc_flags), language: 'c')
add_project_arguments(cc_native.get_supported_arguments(cc_flags), add_project_arguments(cc_native.get_supported_arguments(cc_flags),

View file

@ -5318,7 +5318,7 @@ int jack_set_freewheel(jack_client_t* client, int onoff)
pw_thread_loop_lock(c->context.loop); pw_thread_loop_lock(c->context.loop);
str = pw_properties_get(c->props, PW_KEY_NODE_GROUP); str = pw_properties_get(c->props, PW_KEY_NODE_GROUP);
if (str != NULL) { if (str != NULL) {
const char *p = strstr(str, ",pipewire.freewheel"); char *p = strstr(str, ",pipewire.freewheel");
if (p == NULL) if (p == NULL)
p = strstr(str, "pipewire.freewheel"); p = strstr(str, "pipewire.freewheel");
if (p == NULL && onoff) if (p == NULL && onoff)

155
po/sl.po
View file

@ -2,21 +2,23 @@
# Copyright (C) 2024 PipeWire's COPYRIGHT HOLDER # Copyright (C) 2024 PipeWire's COPYRIGHT HOLDER
# This file is distributed under the same license as the PipeWire package. # This file is distributed under the same license as the PipeWire package.
# #
# Martin <miles@filmsi.net>, 2024, 2025, 2026. # Martin <miles@filmsi.net>, 2024, 2025.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PipeWire master\n" "Project-Id-Version: PipeWire master\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues\n" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/"
"POT-Creation-Date: 2026-02-09 12:55+0000\n" "issues\n"
"PO-Revision-Date: 2026-02-15 16:18+0100\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 <miles@filmsi.net>\n" "Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n"
"Language-Team: Slovenian <gnome-si@googlegroups.com>\n" "Language-Team: Slovenian <gnome-si@googlegroups.com>\n"
"Language: sl\n" "Language: sl\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\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" "X-Generator: Poedit 2.2.1\n"
#: src/daemon/pipewire.c:29 #: src/daemon/pipewire.c:29
@ -33,7 +35,8 @@ msgstr ""
" -h, --help Pokaži to pomoč\n" " -h, --help Pokaži to pomoč\n"
" -v, --verbose Povečaj opisnost za eno raven\n" " -v, --verbose Povečaj opisnost za eno raven\n"
" --version Pokaži različico\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" " -P --properties Določi lastnosti konteksta\n"
#: src/daemon/pipewire.desktop.in:3 #: src/daemon/pipewire.desktop.in:3
@ -59,46 +62,21 @@ msgstr "Lažni izhod"
msgid "Tunnel for %s@%s" msgid "Tunnel for %s@%s"
msgstr "Prehod za %s@%s" msgstr "Prehod za %s@%s"
#: src/modules/module-zeroconf-discover.c:326 #: src/modules/module-zeroconf-discover.c:320
msgid "Unknown device" msgid "Unknown device"
msgstr "Neznana naprava" msgstr "Neznana naprava"
#: src/modules/module-zeroconf-discover.c:338 #: src/modules/module-zeroconf-discover.c:332
#, c-format #, c-format
msgid "%s on %s@%s" msgid "%s on %s@%s"
msgstr "%s na %s@%s" msgstr "%s na %s@%s"
#: src/modules/module-zeroconf-discover.c:342 #: src/modules/module-zeroconf-discover.c:336
#, c-format #, c-format
msgid "%s on %s" msgid "%s on %s"
msgstr "%s na %s" msgstr "%s na %s"
#: src/tools/pw-cat.c:264 #: src/tools/pw-cat.c:1103
#, 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
#, c-format #, c-format
msgid "" msgid ""
"%s [options] [<file>|-]\n" "%s [options] [<file>|-]\n"
@ -114,7 +92,7 @@ msgstr ""
"\n" "\n"
"</file>\n" "</file>\n"
#: src/tools/pw-cat.c:1184 #: src/tools/pw-cat.c:1110
#, c-format #, c-format
msgid "" msgid ""
" -R, --remote Remote daemon name\n" " -R, --remote Remote daemon name\n"
@ -151,23 +129,20 @@ msgstr ""
" -P --properties Nastavi lastnosti vozlišča\n" " -P --properties Nastavi lastnosti vozlišča\n"
"\n" "\n"
#: src/tools/pw-cat.c:1202 #: src/tools/pw-cat.c:1128
#, c-format #, c-format
msgid "" msgid ""
" --rate Sample rate (default %u)\n" " --rate Sample rate (req. for rec) (default "
" --channels Number of channels (default %u)\n" "%u)\n"
" --channels Number of channels (req. for rec) "
"(default %u)\n"
" --channel-map Channel map\n" " --channel-map Channel map\n"
" a channel layout: \"Stereo\", " " one of: \"Stereo\", \"5.1\",... "
"\"5.1\",... or\n" "or\n"
" comma separated list of channel " " comma separated list of channel "
"names: eg. \"FL,FR\"\n" "names: eg. \"FL,FR\"\n"
" --list-layouts List supported channel layouts\n" " --format Sample format %s (req. for rec) "
" --list-channel-names List supported channel maps\n" "(default %s)\n"
" --format Sample format (default %s)\n"
" --list-formats List supported sample formats\n"
" --container Container format\n"
" --list-containers List supported containers and "
"extensions\n"
" --volume Stream volume 0-1.0 (default %.3f)\n" " --volume Stream volume 0-1.0 (default %.3f)\n"
" -q --quality Resampler quality (0 - 15) (default " " -q --quality Resampler quality (0 - 15) (default "
"%d)\n" "%d)\n"
@ -186,13 +161,8 @@ msgstr ""
"\"5.1\",... ali\n" "\"5.1\",... ali\n"
" seznam imen kanalov, ločen z " " seznam imen kanalov, ločen z "
"vejico: npr. \"FL,FR\"\n" "vejico: npr. \"FL,FR\"\n"
" —list-layouts Izpiše podprte postavitve kanalov\n" " --format Vzorčne oblike zapisa %s (zahtevano "
" —list-channel-names Izpiše podprte preslikave kanalov\n" "za rec) (privzeto %s)\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"
" --volume Glasnost toka 0-1.0 (privzeto %.3f)\n" " --volume Glasnost toka 0-1.0 (privzeto %.3f)\n"
" -q --quality Kakovost prevzorčenja (0 - 15) " " -q --quality Kakovost prevzorčenja (0 - 15) "
"(privzeto %d)\n" "(privzeto %d)\n"
@ -202,7 +172,7 @@ msgstr ""
" -n, --sample-count ŠTEVEC Ustavi po ŠTEVEC vzorcih\n" " -n, --sample-count ŠTEVEC Ustavi po ŠTEVEC vzorcih\n"
"\n" "\n"
#: src/tools/pw-cat.c:1227 #: src/tools/pw-cat.c:1148
msgid "" msgid ""
" -p, --playback Playback mode\n" " -p, --playback Playback mode\n"
" -r, --record Recording mode\n" " -r, --record Recording mode\n"
@ -222,11 +192,6 @@ msgstr ""
" -c, --midi-clip Način posnetka MIDI\n" " -c, --midi-clip Način posnetka MIDI\n"
"\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 #: src/tools/pw-cli.c:2386
#, c-format #, c-format
msgid "" msgid ""
@ -252,7 +217,7 @@ msgid "Pro Audio"
msgstr "Profesionalni zvok" msgstr "Profesionalni zvok"
#: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699 #: 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" msgid "Off"
msgstr "Izklopljeno" msgstr "Izklopljeno"
@ -284,7 +249,7 @@ msgstr "Linijski vhod"
#: spa/plugins/alsa/acp/alsa-mixer.c:2726 #: spa/plugins/alsa/acp/alsa-mixer.c:2726
#: spa/plugins/alsa/acp/alsa-mixer.c:2810 #: spa/plugins/alsa/acp/alsa-mixer.c:2810
#: spa/plugins/bluez5/bluez5-device.c:2422 #: spa/plugins/bluez5/bluez5-device.c:2374
msgid "Microphone" msgid "Microphone"
msgstr "Mikrofon" msgstr "Mikrofon"
@ -350,15 +315,15 @@ msgid "No Bass Boost"
msgstr "Brez ojačitve nizkih tonov" msgstr "Brez ojačitve nizkih tonov"
#: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/alsa/acp/alsa-mixer.c:2741
#: spa/plugins/bluez5/bluez5-device.c:2428 #: spa/plugins/bluez5/bluez5-device.c:2380
msgid "Speaker" msgid "Speaker"
msgstr "Zvočnik" msgstr "Zvočnik"
#. Don't call it "headset", the HF one has the mic #. 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:2742
#: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2820
#: spa/plugins/bluez5/bluez5-device.c:2434 #: spa/plugins/bluez5/bluez5-device.c:2386
#: spa/plugins/bluez5/bluez5-device.c:2501 #: spa/plugins/bluez5/bluez5-device.c:2453
msgid "Headphones" msgid "Headphones"
msgstr "Slušalke" msgstr "Slušalke"
@ -468,7 +433,7 @@ msgstr "Stereo"
#: spa/plugins/alsa/acp/alsa-mixer.c:4535 #: spa/plugins/alsa/acp/alsa-mixer.c:4535
#: spa/plugins/alsa/acp/alsa-mixer.c:4693 #: spa/plugins/alsa/acp/alsa-mixer.c:4693
#: spa/plugins/bluez5/bluez5-device.c:2410 #: spa/plugins/bluez5/bluez5-device.c:2362
msgid "Headset" msgid "Headset"
msgstr "Slušalka" msgstr "Slušalka"
@ -715,100 +680,100 @@ msgstr "Vgrajen zvok"
msgid "Modem" msgid "Modem"
msgstr "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)" msgid "Audio Gateway (A2DP Source & HSP/HFP AG)"
msgstr "Zvožni prehod (vir A2DP in 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)" msgid "Audio Streaming for Hearing Aids (ASHA Sink)"
msgstr "Pretakanje zvoka za slušne aparate (ponor ASHA)" 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 #, c-format
msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgid "High Fidelity Playback (A2DP Sink, codec %s)"
msgstr "Predvajanje visoke ločljivosti (ponor A2DP, kodek %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 #, c-format
msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)"
msgstr "Dupleks visoke ločljivosti (vir/ponor A2DP, kodek %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)" msgid "High Fidelity Playback (A2DP Sink)"
msgstr "Predvajanje visoke ločljivosti (ponor A2DP)" 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)" msgid "High Fidelity Duplex (A2DP Source/Sink)"
msgstr "Dupleks visoke ločljivosti (vir/ponor A2DP)" msgstr "Dupleks visoke ločljivosti (vir/ponor A2DP)"
#: spa/plugins/bluez5/bluez5-device.c:2194 #: spa/plugins/bluez5/bluez5-device.c:2146
#, c-format #, c-format
msgid "High Fidelity Playback (BAP Sink, codec %s)" msgid "High Fidelity Playback (BAP Sink, codec %s)"
msgstr "Predvajanje visoke ločljivosti (ponor BAP, kodek %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 #, c-format
msgid "High Fidelity Input (BAP Source, codec %s)" msgid "High Fidelity Input (BAP Source, codec %s)"
msgstr "Vhod visoke ločljivosti (vir BAP, kodek %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 #, c-format
msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)"
msgstr "Dupleks visoke ločljivosti (vir/ponor BAP, kodek %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)" msgid "High Fidelity Playback (BAP Sink)"
msgstr "Predvajanje visoke ločljivosti (ponor BAP)" 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)" msgid "High Fidelity Input (BAP Source)"
msgstr "Vhod visoke ločljivosti (vir BAP)" 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)" msgid "High Fidelity Duplex (BAP Source/Sink)"
msgstr "Dupleks visoke ločljivosti (vir/ponor BAP)" msgstr "Dupleks visoke ločljivosti (vir/ponor BAP)"
#: spa/plugins/bluez5/bluez5-device.c:2259 #: spa/plugins/bluez5/bluez5-device.c:2211
#, c-format #, c-format
msgid "Headset Head Unit (HSP/HFP, codec %s)" msgid "Headset Head Unit (HSP/HFP, codec %s)"
msgstr "Naglavna enota slušalk (HSP/HFP, kodek %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: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" msgid "Handsfree"
msgstr "Prostoročno telefoniranje" msgstr "Prostoročno telefoniranje"
#: spa/plugins/bluez5/bluez5-device.c:2417 #: spa/plugins/bluez5/bluez5-device.c:2369
msgid "Handsfree (HFP)" msgid "Handsfree (HFP)"
msgstr "Prostoročno telefoniranje (HFP)" msgstr "Prostoročno telefoniranje (HFP)"
#: spa/plugins/bluez5/bluez5-device.c:2440 #: spa/plugins/bluez5/bluez5-device.c:2392
msgid "Portable" msgid "Portable"
msgstr "Prenosna naprava" msgstr "Prenosna naprava"
#: spa/plugins/bluez5/bluez5-device.c:2446 #: spa/plugins/bluez5/bluez5-device.c:2398
msgid "Car" msgid "Car"
msgstr "Avtomobil" msgstr "Avtomobil"
#: spa/plugins/bluez5/bluez5-device.c:2452 #: spa/plugins/bluez5/bluez5-device.c:2404
msgid "HiFi" msgid "HiFi"
msgstr "HiFi" msgstr "HiFi"
#: spa/plugins/bluez5/bluez5-device.c:2458 #: spa/plugins/bluez5/bluez5-device.c:2410
msgid "Phone" msgid "Phone"
msgstr "Telefon" msgstr "Telefon"
#: spa/plugins/bluez5/bluez5-device.c:2465 #: spa/plugins/bluez5/bluez5-device.c:2417
msgid "Bluetooth" msgid "Bluetooth"
msgstr "Bluetooth" msgstr "Bluetooth"
#: spa/plugins/bluez5/bluez5-device.c:2466 #: spa/plugins/bluez5/bluez5-device.c:2418
msgid "Bluetooth Handsfree" msgid "Bluetooth Handsfree"
msgstr "Bluetooth - prostoročno" msgstr "Bluetooth - prostoročno"

View file

@ -6,15 +6,15 @@
# Cheng-Chia Tseng <pswo10680@gmail.com>, 2010, 2012. # Cheng-Chia Tseng <pswo10680@gmail.com>, 2010, 2012.
# Frank Hill <hxf.prc@gmail.com>, 2015. # Frank Hill <hxf.prc@gmail.com>, 2015.
# Mingye Wang (Arthur2e5) <arthur200126@gmail.com>, 2015. # Mingye Wang (Arthur2e5) <arthur200126@gmail.com>, 2015.
# lumingzh <lumingzh@qq.com>, 2024-2026. # lumingzh <lumingzh@qq.com>, 2024-2025.
# #
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: pipewire.master-tx\n" "Project-Id-Version: pipewire.master-tx\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/"
"issues\n" "issues\n"
"POT-Creation-Date: 2026-02-11 16:53+0000\n" "POT-Creation-Date: 2025-11-25 15:35+0000\n"
"PO-Revision-Date: 2026-02-13 09:36+0800\n" "PO-Revision-Date: 2025-11-26 10:19+0800\n"
"Last-Translator: lumingzh <lumingzh@qq.com>\n" "Last-Translator: lumingzh <lumingzh@qq.com>\n"
"Language-Team: Chinese (China) <i18n-zh@googlegroups.com>\n" "Language-Team: Chinese (China) <i18n-zh@googlegroups.com>\n"
"Language: zh_CN\n" "Language: zh_CN\n"
@ -65,46 +65,21 @@ msgstr "虚拟输出"
msgid "Tunnel for %s@%s" msgid "Tunnel for %s@%s"
msgstr "用于 %s@%s 的隧道" msgstr "用于 %s@%s 的隧道"
#: src/modules/module-zeroconf-discover.c:326 #: src/modules/module-zeroconf-discover.c:320
msgid "Unknown device" msgid "Unknown device"
msgstr "未知设备" msgstr "未知设备"
#: src/modules/module-zeroconf-discover.c:338 #: src/modules/module-zeroconf-discover.c:332
#, c-format #, c-format
msgid "%s on %s@%s" msgid "%s on %s@%s"
msgstr "%2$s@%3$s 上的 %1$s" msgstr "%2$s@%3$s 上的 %1$s"
#: src/modules/module-zeroconf-discover.c:342 #: src/modules/module-zeroconf-discover.c:336
#, c-format #, c-format
msgid "%s on %s" msgid "%s on %s"
msgstr "%2$s 上的 %1$s" msgstr "%2$s 上的 %1$s"
#: src/tools/pw-cat.c:264 #: src/tools/pw-cat.c:1088
#, c-format
msgid "Supported formats:\n"
msgstr "支持的格式:\n"
#: src/tools/pw-cat.c:749
#, c-format
msgid "Supported channel layouts:\n"
msgstr "支持的声道布局:\n"
#: src/tools/pw-cat.c:759
#, c-format
msgid "Supported channel layout aliases:\n"
msgstr "支持的声道布局别名:\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 "支持的声道名称:\n"
#: src/tools/pw-cat.c:1177
#, c-format #, c-format
msgid "" msgid ""
"%s [options] [<file>|-]\n" "%s [options] [<file>|-]\n"
@ -119,7 +94,7 @@ msgstr ""
" -v, --verbose 输出详细操作\n" " -v, --verbose 输出详细操作\n"
"\n" "\n"
#: src/tools/pw-cat.c:1184 #: src/tools/pw-cat.c:1095
#, c-format #, c-format
msgid "" msgid ""
" -R, --remote Remote daemon name\n" " -R, --remote Remote daemon name\n"
@ -151,23 +126,20 @@ msgstr ""
" -P --properties 设置节点属性\n" " -P --properties 设置节点属性\n"
"\n" "\n"
#: src/tools/pw-cat.c:1202 #: src/tools/pw-cat.c:1113
#, c-format #, c-format
msgid "" msgid ""
" --rate Sample rate (default %u)\n" " --rate Sample rate (req. for rec) (default "
" --channels Number of channels (default %u)\n" "%u)\n"
" --channels Number of channels (req. for rec) "
"(default %u)\n"
" --channel-map Channel map\n" " --channel-map Channel map\n"
" a channel layout: \"Stereo\", " " one of: \"Stereo\", \"5.1\",... "
"\"5.1\",... or\n" "or\n"
" comma separated list of channel " " comma separated list of channel "
"names: eg. \"FL,FR\"\n" "names: eg. \"FL,FR\"\n"
" --list-layouts List supported channel layouts\n" " --format Sample format %s (req. for rec) "
" --list-channel-names List supported channel maps\n" "(default %s)\n"
" --format Sample format (default %s)\n"
" --list-formats List supported sample formats\n"
" --container Container format\n"
" --list-containers List supported containers and "
"extensions\n"
" --volume Stream volume 0-1.0 (default %.3f)\n" " --volume Stream volume 0-1.0 (default %.3f)\n"
" -q --quality Resampler quality (0 - 15) (default " " -q --quality Resampler quality (0 - 15) (default "
"%d)\n" "%d)\n"
@ -177,19 +149,15 @@ msgid ""
" -n, --sample-count COUNT Stop after COUNT samples\n" " -n, --sample-count COUNT Stop after COUNT samples\n"
"\n" "\n"
msgstr "" msgstr ""
" --rate 采样率 (默认 %u)\n" " --rate 采样率 (录制模式需要) (默认 %u)\n"
" --channels 声道数 (默认 %u)\n" " --channels 通道数 (录制模式需要) (默认 %u)\n"
" --channel-map 道映射\n" " --channel-map 道映射\n"
" 声道布局:\"stereo\", " " \"stereo\", \"5.1\",... 中的其一"
"\"5.1\",... 或\n" "或\n"
" 以英文逗号分隔的道名列表: 如 " " 以英文逗号分隔的道名列表: 如 "
"\"FL,FR\"\n" "\"FL,FR\"\n"
" --list-layouts 列出支持的声道布局\n" " --format 采样格式 %s (录制模式需要) (默认 "
" --list-channel-names 列出支持的声道映射\n" "%s)\n"
" --format 采样格式 (默认 %s)\n"
" --list-formats 列出支持的采样格式\n"
" --container 容器格式\n"
" --list-containers 列出支持的容器和扩展\n"
" --volume 媒体流音量 0-1.0 (默认 %.3f)\n" " --volume 媒体流音量 0-1.0 (默认 %.3f)\n"
" -q --quality 重采样质量 (0 - 15) (默认 %d)\n" " -q --quality 重采样质量 (0 - 15) (默认 %d)\n"
" -a, --raw 原生模式\n" " -a, --raw 原生模式\n"
@ -198,7 +166,7 @@ msgstr ""
" -n, --sample-count COUNT 计数采样后停止\n" " -n, --sample-count COUNT 计数采样后停止\n"
"\n" "\n"
#: src/tools/pw-cat.c:1227 #: src/tools/pw-cat.c:1133
msgid "" msgid ""
" -p, --playback Playback mode\n" " -p, --playback Playback mode\n"
" -r, --record Recording mode\n" " -r, --record Recording mode\n"
@ -218,11 +186,6 @@ msgstr ""
" -c, --midi-clip MIDI 剪辑模式\n" " -c, --midi-clip MIDI 剪辑模式\n"
"\n" "\n"
#: src/tools/pw-cat.c:1827
#, c-format
msgid "Supported containers and extensions:\n"
msgstr "支持的容器和扩展:\n"
#: src/tools/pw-cli.c:2386 #: src/tools/pw-cli.c:2386
#, c-format #, c-format
msgid "" msgid ""
@ -246,7 +209,7 @@ msgid "Pro Audio"
msgstr "专业音频" msgstr "专业音频"
#: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699 #: 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" msgid "Off"
msgstr "关" msgstr "关"
@ -278,7 +241,7 @@ msgstr "输入插孔"
#: spa/plugins/alsa/acp/alsa-mixer.c:2726 #: spa/plugins/alsa/acp/alsa-mixer.c:2726
#: spa/plugins/alsa/acp/alsa-mixer.c:2810 #: spa/plugins/alsa/acp/alsa-mixer.c:2810
#: spa/plugins/bluez5/bluez5-device.c:2422 #: spa/plugins/bluez5/bluez5-device.c:2374
msgid "Microphone" msgid "Microphone"
msgstr "话筒" msgstr "话筒"
@ -344,15 +307,15 @@ msgid "No Bass Boost"
msgstr "无重低音增强" msgstr "无重低音增强"
#: spa/plugins/alsa/acp/alsa-mixer.c:2741 #: spa/plugins/alsa/acp/alsa-mixer.c:2741
#: spa/plugins/bluez5/bluez5-device.c:2428 #: spa/plugins/bluez5/bluez5-device.c:2380
msgid "Speaker" msgid "Speaker"
msgstr "扬声器" msgstr "扬声器"
#. Don't call it "headset", the HF one has the mic #. 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:2742
#: spa/plugins/alsa/acp/alsa-mixer.c:2820 #: spa/plugins/alsa/acp/alsa-mixer.c:2820
#: spa/plugins/bluez5/bluez5-device.c:2434 #: spa/plugins/bluez5/bluez5-device.c:2386
#: spa/plugins/bluez5/bluez5-device.c:2501 #: spa/plugins/bluez5/bluez5-device.c:2453
msgid "Headphones" msgid "Headphones"
msgstr "模拟耳机" msgstr "模拟耳机"
@ -462,7 +425,7 @@ msgstr "立体声"
#: spa/plugins/alsa/acp/alsa-mixer.c:4535 #: spa/plugins/alsa/acp/alsa-mixer.c:4535
#: spa/plugins/alsa/acp/alsa-mixer.c:4693 #: spa/plugins/alsa/acp/alsa-mixer.c:4693
#: spa/plugins/bluez5/bluez5-device.c:2410 #: spa/plugins/bluez5/bluez5-device.c:2362
msgid "Headset" msgid "Headset"
msgstr "耳机" msgstr "耳机"
@ -657,101 +620,101 @@ msgstr "内置音频"
msgid "Modem" msgid "Modem"
msgstr "调制解调器" msgstr "调制解调器"
#: spa/plugins/bluez5/bluez5-device.c:2032 #: spa/plugins/bluez5/bluez5-device.c:1987
msgid "Audio Gateway (A2DP Source & HSP/HFP AG)" msgid "Audio Gateway (A2DP Source & HSP/HFP AG)"
msgstr "音频网关 (A2DP 信源 或 HSP/HFP 网关)" msgstr "音频网关 (A2DP 信源 或 HSP/HFP 网关)"
#: spa/plugins/bluez5/bluez5-device.c:2061 #: spa/plugins/bluez5/bluez5-device.c:2016
msgid "Audio Streaming for Hearing Aids (ASHA Sink)" msgid "Audio Streaming for Hearing Aids (ASHA Sink)"
msgstr "助听器音频流 (ASHA 信宿)" msgstr "助听器音频流 (ASHA 信宿)"
#: spa/plugins/bluez5/bluez5-device.c:2104 #: spa/plugins/bluez5/bluez5-device.c:2059
#, c-format #, c-format
msgid "High Fidelity Playback (A2DP Sink, codec %s)" msgid "High Fidelity Playback (A2DP Sink, codec %s)"
msgstr "高保真回放 (A2DP 信宿, 编码 %s)" msgstr "高保真回放 (A2DP 信宿, 编码 %s)"
#: spa/plugins/bluez5/bluez5-device.c:2107 #: spa/plugins/bluez5/bluez5-device.c:2062
#, c-format #, c-format
msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)" msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)"
msgstr "高保真双工 (A2DP 信源/信宿, 编码 %s)" msgstr "高保真双工 (A2DP 信源/信宿, 编码 %s)"
#: spa/plugins/bluez5/bluez5-device.c:2115 #: spa/plugins/bluez5/bluez5-device.c:2070
msgid "High Fidelity Playback (A2DP Sink)" msgid "High Fidelity Playback (A2DP Sink)"
msgstr "高保真回放 (A2DP 信宿)" msgstr "高保真回放 (A2DP 信宿)"
#: spa/plugins/bluez5/bluez5-device.c:2117 #: spa/plugins/bluez5/bluez5-device.c:2072
msgid "High Fidelity Duplex (A2DP Source/Sink)" msgid "High Fidelity Duplex (A2DP Source/Sink)"
msgstr "高保真双工 (A2DP 信源/信宿)" msgstr "高保真双工 (A2DP 信源/信宿)"
#: spa/plugins/bluez5/bluez5-device.c:2194 #: spa/plugins/bluez5/bluez5-device.c:2146
#, c-format #, c-format
msgid "High Fidelity Playback (BAP Sink, codec %s)" msgid "High Fidelity Playback (BAP Sink, codec %s)"
msgstr "高保真回放 (BAP 信宿, 编码 %s)" msgstr "高保真回放 (BAP 信宿, 编码 %s)"
#: spa/plugins/bluez5/bluez5-device.c:2199 #: spa/plugins/bluez5/bluez5-device.c:2151
#, c-format #, c-format
msgid "High Fidelity Input (BAP Source, codec %s)" msgid "High Fidelity Input (BAP Source, codec %s)"
msgstr "高保真输入 (BAP 信源, 编码 %s)" msgstr "高保真输入 (BAP 信源, 编码 %s)"
#: spa/plugins/bluez5/bluez5-device.c:2203 #: spa/plugins/bluez5/bluez5-device.c:2155
#, c-format #, c-format
msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)" msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)"
msgstr "高保真双工 (BAP 信源/信宿, 编码 %s)" msgstr "高保真双工 (BAP 信源/信宿, 编码 %s)"
#: spa/plugins/bluez5/bluez5-device.c:2212 #: spa/plugins/bluez5/bluez5-device.c:2164
msgid "High Fidelity Playback (BAP Sink)" msgid "High Fidelity Playback (BAP Sink)"
msgstr "高保真回放 (BAP 信宿)" msgstr "高保真回放 (BAP 信宿)"
#: spa/plugins/bluez5/bluez5-device.c:2216 #: spa/plugins/bluez5/bluez5-device.c:2168
msgid "High Fidelity Input (BAP Source)" msgid "High Fidelity Input (BAP Source)"
msgstr "高保真输入 (BAP 信源)" msgstr "高保真输入 (BAP 信源)"
#: spa/plugins/bluez5/bluez5-device.c:2219 #: spa/plugins/bluez5/bluez5-device.c:2171
msgid "High Fidelity Duplex (BAP Source/Sink)" msgid "High Fidelity Duplex (BAP Source/Sink)"
msgstr "高保真双工 (BAP 信源/信宿)" msgstr "高保真双工 (BAP 信源/信宿)"
#: spa/plugins/bluez5/bluez5-device.c:2259 #: spa/plugins/bluez5/bluez5-device.c:2211
#, c-format #, c-format
msgid "Headset Head Unit (HSP/HFP, codec %s)" msgid "Headset Head Unit (HSP/HFP, codec %s)"
msgstr "头戴式耳机单元 (HSP/HFP, 编码 %s)" msgstr "头戴式耳机单元 (HSP/HFP, 编码 %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: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" msgid "Handsfree"
msgstr "免提" msgstr "免提"
#: spa/plugins/bluez5/bluez5-device.c:2417 #: spa/plugins/bluez5/bluez5-device.c:2369
msgid "Handsfree (HFP)" msgid "Handsfree (HFP)"
msgstr "免提HFP" msgstr "免提HFP"
#: spa/plugins/bluez5/bluez5-device.c:2440 #: spa/plugins/bluez5/bluez5-device.c:2392
msgid "Portable" msgid "Portable"
msgstr "便携式" msgstr "便携式"
#: spa/plugins/bluez5/bluez5-device.c:2446 #: spa/plugins/bluez5/bluez5-device.c:2398
msgid "Car" msgid "Car"
msgstr "车内" msgstr "车内"
#: spa/plugins/bluez5/bluez5-device.c:2452 #: spa/plugins/bluez5/bluez5-device.c:2404
msgid "HiFi" msgid "HiFi"
msgstr "高保真" msgstr "高保真"
#: spa/plugins/bluez5/bluez5-device.c:2458 #: spa/plugins/bluez5/bluez5-device.c:2410
msgid "Phone" msgid "Phone"
msgstr "电话" msgstr "电话"
#: spa/plugins/bluez5/bluez5-device.c:2465 #: spa/plugins/bluez5/bluez5-device.c:2417
msgid "Bluetooth" msgid "Bluetooth"
msgstr "蓝牙" msgstr "蓝牙"
#: spa/plugins/bluez5/bluez5-device.c:2466 #: spa/plugins/bluez5/bluez5-device.c:2418
msgid "Bluetooth Handsfree" msgid "Bluetooth Handsfree"
msgstr "蓝牙免提" msgstr "蓝牙免提"

View file

@ -187,11 +187,6 @@ ATTRS{idVendor}=="1395", ATTRS{idProduct}=="0300", ENV{ACP_PROFILE_SET}="usb-gam
# Sennheiser GSP 670 USB headset # Sennheiser GSP 670 USB headset
ATTRS{idVendor}=="1395", ATTRS{idProduct}=="008a", ENV{ACP_PROFILE_SET}="usb-gaming-headset.conf" 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 # Audioengine HD3 powered speakers support IEC958 but don't actually
# have any digital outputs. # have any digital outputs.
ATTRS{idVendor}=="0a12", ATTRS{idProduct}=="4007", ENV{ACP_PROFILE_SET}="analog-only.conf" ATTRS{idVendor}=="0a12", ATTRS{idProduct}=="4007", ENV{ACP_PROFILE_SET}="analog-only.conf"

View file

@ -429,9 +429,9 @@ 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") #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) if (!p)
return NULL; return NULL;
if ((fn = strrchr(p, PA_PATH_SEP_CHAR))) if ((fn = strrchr(p, PA_PATH_SEP_CHAR)))

View file

@ -3040,17 +3040,10 @@ static int update_time(struct state *state, uint64_t current_time, snd_pcm_sfram
} }
if (state->rate_match) { 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) if (state->stream == SND_PCM_STREAM_PLAYBACK)
state->rate_match->rate = corr; state->rate_match->rate = corr;
else else
state->rate_match->rate = 1.0/corr; state->rate_match->rate = 1.0/corr;
} else {
state->rate_match->rate = 1.0;
}
if (state->pitch_elem && state->matching) if (state->pitch_elem && state->matching)
spa_alsa_update_rate_match(state); spa_alsa_update_rate_match(state);

View file

@ -725,11 +725,11 @@ static bool check_access(struct impl *this, struct card *card)
static void process_card(struct impl *this, enum action action, struct card *card) static void process_card(struct impl *this, enum action action, struct card *card)
{ {
switch (action) {
case ACTION_CHANGE: {
if (card->ignored) if (card->ignored)
return; return;
switch (action) {
case ACTION_CHANGE: {
check_access(this, card); check_access(this, card);
if (card->accessible && !card->emitted) { if (card->accessible && !card->emitted) {
int res = emit_added_object_info(this, card); int res = emit_added_object_info(this, card);

View file

@ -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 <http://www.gnu.org/licenses/>.
; 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

View file

@ -268,7 +268,6 @@ struct impl {
struct spa_list active_graphs; struct spa_list active_graphs;
struct filter_graph graphs[MAX_GRAPH]; struct filter_graph graphs[MAX_GRAPH];
struct spa_process_latency_info latency; struct spa_process_latency_info latency;
char *graph_descs[MAX_GRAPH];
int in_filter_props; int in_filter_props;
int filter_props_count; int filter_props_count;
@ -847,7 +846,6 @@ static int node_param_props(struct impl *this, uint32_t id, uint32_t index,
{ {
struct props *p = &this->props; struct props *p = &this->props;
struct spa_pod_frame f[2]; struct spa_pod_frame f[2];
struct filter_graph *g;
switch (index) { switch (index) {
case 0: case 0:
@ -920,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_bool(b, p->lock_volumes);
spa_pod_builder_string(b, "audioconvert.filter-graph.disable"); spa_pod_builder_string(b, "audioconvert.filter-graph.disable");
spa_pod_builder_bool(b, p->filter_graph_disabled); spa_pod_builder_bool(b, p->filter_graph_disabled);
spa_list_for_each(g, &this->active_graphs, link) { spa_pod_builder_string(b, "audioconvert.filter-graph");
char key[64]; spa_pod_builder_string(b, "");
snprintf(key, sizeof(key), "audioconvert.filter-graph.%d", g->order);
spa_pod_builder_string(b, key);
spa_pod_builder_string(b, this->graph_descs[g->order]);
}
spa_pod_builder_pop(b, &f[1]); spa_pod_builder_pop(b, &f[1]);
*param = spa_pod_builder_pop(b, &f[0]); *param = spa_pod_builder_pop(b, &f[0]);
break; break;
@ -959,7 +953,7 @@ static int impl_node_enum_params(void *object, int seq,
struct impl *this = object; struct impl *this = object;
struct spa_pod *param; struct spa_pod *param;
struct spa_pod_builder b = { 0 }; struct spa_pod_builder b = { 0 };
uint8_t buffer[16384]; uint8_t buffer[4096];
struct spa_result_node_params result; struct spa_result_node_params result;
uint32_t count = 0; uint32_t count = 0;
int res = 0; int res = 0;
@ -1415,7 +1409,6 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order)
g->removing = true; g->removing = true;
spa_log_info(impl->log, "removing filter-graph order:%d", order); spa_log_info(impl->log, "removing filter-graph order:%d", order);
} }
free(impl->graph_descs[order]);
} }
if (graph != NULL && graph[0] != '\0') { if (graph != NULL && graph[0] != '\0') {
@ -1441,8 +1434,6 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order)
spa_list_remove(&pending->link); spa_list_remove(&pending->link);
insert_graph(&impl->active_graphs, pending); insert_graph(&impl->active_graphs, pending);
impl->graph_descs[order] = strdup(graph);
spa_log_info(impl->log, "loading filter-graph order:%d", order); spa_log_info(impl->log, "loading filter-graph order:%d", order);
} }
if (impl->setup) if (impl->setup)
@ -4243,7 +4234,6 @@ static void free_dir(struct dir *dir)
static int impl_clear(struct spa_handle *handle) static int impl_clear(struct spa_handle *handle)
{ {
struct impl *this; struct impl *this;
int i;
spa_return_val_if_fail(handle != NULL, -EINVAL); spa_return_val_if_fail(handle != NULL, -EINVAL);
@ -4255,10 +4245,6 @@ static int impl_clear(struct spa_handle *handle)
free_tmp(this); free_tmp(this);
clean_filter_handles(this, true); 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) if (this->resample.free)
resample_free(&this->resample); resample_free(&this->resample);
@ -4313,7 +4299,6 @@ impl_init(const struct spa_handle_factory *factory,
struct filter_graph *g = &this->graphs[i]; struct filter_graph *g = &this->graphs[i];
g->impl = this; g->impl = this;
spa_list_append(&this->free_graphs, &g->link); 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.interval = 2 * SPA_NSEC_PER_SEC;

View file

@ -720,7 +720,7 @@ done:
if (src_paired == 0) if (src_paired == 0)
src_paired = ~0LU; src_paired = ~0LU;
for (jc = 0, ic = 0, i = 0; ic < dst_chan; i++) { for (jc = 0, ic = 0, i = 0; i < CHANNEL_BITS; i++) {
float sum = 0.0f; float sum = 0.0f;
char str1[1024], str2[1024]; char str1[1024], str2[1024];
struct spa_strbuf sb1, sb2; struct spa_strbuf sb1, sb2;
@ -728,10 +728,12 @@ done:
spa_strbuf_init(&sb1, str1, sizeof(str1)); spa_strbuf_init(&sb1, str1, sizeof(str1));
spa_strbuf_init(&sb2, str2, sizeof(str2)); spa_strbuf_init(&sb2, str2, sizeof(str2));
if (i < CHANNEL_BITS && (dst_paired & (1UL << i)) == 0) if ((dst_paired & (1UL << i)) == 0)
continue; continue;
for (jc = 0, j = 0; jc < src_chan; j++) { for (jc = 0, j = 0; j < CHANNEL_BITS; j++) {
if (j < CHANNEL_BITS && (src_paired & (1UL << j)) == 0) if ((src_paired & (1UL << j)) == 0)
continue;
if (ic >= dst_chan || jc >= src_chan)
continue; continue;
if (ic == 0) if (ic == 0)
@ -750,7 +752,7 @@ done:
if (sb2.pos > 0) if (sb2.pos > 0)
spa_log_info(mix->log, " %s", str2); spa_log_info(mix->log, " %s", str2);
if (sb1.pos > 0) { 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" : dst_mask == 0 ? "UNK" :
spa_debug_type_find_short_name(spa_type_audio_channel, i + _SH), spa_debug_type_find_short_name(spa_type_audio_channel, i + _SH),
str1, sum); str1, sum);
@ -823,6 +825,7 @@ static void impl_channelmix_set_volume(struct channelmix *mix, float volume, boo
for (i = 0; i < dst_chan; i++) { for (i = 0; i < dst_chan; i++) {
for (j = 0; j < src_chan; j++) { for (j = 0; j < src_chan; j++) {
float v = mix->matrix[i][j]; float v = mix->matrix[i][j];
spa_log_debug(mix->log, "%d %d: %f", i, j, v);
if (i == 0 && j == 0) if (i == 0 && j == 0)
t = v; t = v;
else if (t != v) else if (t != v)
@ -837,32 +840,8 @@ static void impl_channelmix_set_volume(struct channelmix *mix, float volume, boo
SPA_FLAG_UPDATE(mix->flags, CHANNELMIX_FLAG_IDENTITY, SPA_FLAG_UPDATE(mix->flags, CHANNELMIX_FLAG_IDENTITY,
dst_chan == src_chan && SPA_FLAG_IS_SET(mix->flags, CHANNELMIX_FLAG_COPY)); 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) static void impl_channelmix_free(struct channelmix *mix)
{ {

View file

@ -63,9 +63,6 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.native");
#define RFCOMM_MESSAGE_MAX_LENGTH 256 #define RFCOMM_MESSAGE_MAX_LENGTH 256
#define BT_CODEC_CVSD 0x02
#define BT_CODEC_MSBC 0x05
enum { enum {
HFP_AG_INITIAL_CODEC_SETUP_NONE = 0, HFP_AG_INITIAL_CODEC_SETUP_NONE = 0,
HFP_AG_INITIAL_CODEC_SETUP_SEND, HFP_AG_INITIAL_CODEC_SETUP_SEND,
@ -115,7 +112,6 @@ struct impl {
int hfp_default_speaker_volume; int hfp_default_speaker_volume;
struct spa_source sco; struct spa_source sco;
unsigned int hfphsp_sco_datapath;
const struct spa_bt_quirks *quirks; 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; 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) static DBusHandlerResult profile_release(DBusConnection *conn, DBusMessage *m, void *userdata)
{ {
if (!reply_with_error(conn, m, BLUEZ_PROFILE_INTERFACE ".Error.NotImplemented", "Method not implemented")) if (!reply_with_error(conn, m, BLUEZ_PROFILE_INTERFACE ".Error.NotImplemented", "Method not implemented"))
@ -2626,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); return spa_steal_fd(sock);
} }
@ -4134,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; 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 = { static const struct spa_bt_backend_implementation backend_impl = {
SPA_VERSION_BT_BACKEND_IMPLEMENTATION, SPA_VERSION_BT_BACKEND_IMPLEMENTATION,
.free = backend_native_free, .free = backend_native_free,
@ -4204,7 +4163,6 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
parse_hfp_disable_nrec(backend, info); parse_hfp_disable_nrec(backend, info);
parse_hfp_default_volumes(backend, info); parse_hfp_default_volumes(backend, info);
parse_hfp_pts(backend, info); parse_hfp_pts(backend, info);
parse_sco_datapath(backend, info);
#ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE
if (!dbus_connection_register_object_path(backend->conn, if (!dbus_connection_register_object_path(backend->conn,

View file

@ -55,7 +55,6 @@ struct settings {
int latency; int latency;
int64_t delay; int64_t delay;
int framing; int framing;
char *force_target_latency;
}; };
struct pac_data { struct pac_data {
@ -77,7 +76,6 @@ struct bap_qos {
uint16_t latency; uint16_t latency;
uint32_t delay; uint32_t delay;
unsigned int priority; unsigned int priority;
char *tag;
}; };
typedef struct { typedef struct {
@ -98,50 +96,50 @@ struct config_data {
struct settings settings; 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_), \ ((struct bap_qos){ .name = (name_), .rate = (rate_), .frame_duration = (duration_), .framing = (framing_), \
.framelen = (framelen_), .retransmission = (rtn_), .latency = (latency_), \ .framelen = (framelen_), .retransmission = (rtn_), .latency = (latency_), \
.delay = (delay_), .priority = (priority_), .tag = (tag_) }) .delay = (delay_), .priority = (priority_) })
static const struct bap_qos bap_qos_configs[] = { static const struct bap_qos bap_qos_configs[] = {
/* Priority: low-latency > high-reliability, 7.5ms > 10ms, /* Priority: low-latency > high-reliability, 7.5ms > 10ms,
* bigger frequency and sdu better */ * bigger frequency and sdu better */
/* BAP v1.0.1 Table 5.2; low-latency */ /* 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_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, "low-latency"), /* 8_2_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, "low-latency"), /* 16_1_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, "low-latency"), /* 16_2_1 (mandatory) */ 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, "low-latency"), /* 24_1_1 */ 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, "low-latency"), /* 24_2_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, "low-latency"), /* 32_1_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, "low-latency"), /* 32_2_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, "low-latency"), /* 441_1_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, "low-latency"), /* 441_2_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, "low-latency"), /* 48_1_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, "low-latency"), /* 48_2_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, "low-latency"), /* 48_3_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, "low-latency"), /* 48_4_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, "low-latency"), /* 48_5_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, "low-latency"), /* 48_6_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 v1.0.1 Table 5.2; high-reliability */
BAP_QOS("8_1_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_7_5, false, 26, 13, 75, 40000, 10, "high-reliabilty"), /* 8_1_2 */ BAP_QOS("8_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, "high-reliabilty"), /* 8_2_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, "high-reliabilty"), /* 16_1_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, "high-reliabilty"), /* 16_2_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, "high-reliabilty"), /* 24_1_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, "high-reliabilty"), /* 24_2_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, "high-reliabilty"), /* 32_1_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, "high-reliabilty"), /* 32_2_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, 54, "high-reliabilty"), /* 441_1_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, 44, "high-reliabilty"), /* 441_2_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, 55, "high-reliabilty"), /* 48_1_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, 45, "high-reliabilty"), /* 48_2_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, 56, "high-reliabilty"), /* 48_3_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, 46, "high-reliabilty"), /* 48_4_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, 57, "high-reliabilty"), /* 48_5_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, 47, "high-reliabilty"), /* 48_6_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[] = { 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 */ * bigger frequency and sdu better */
/* BAP v1.0.1 Table 6.4; low-latency */ /* 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_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, "low-latency"), /* 8_2_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, "low-latency"), /* 16_1_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, "low-latency"), /* 16_2_1 (mandatory) */ 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, "low-latency"), /* 24_1_1 */ 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, "low-latency"), /* 24_2_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, "low-latency"), /* 32_1_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, "low-latency"), /* 32_2_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, "low-latency"), /* 441_1_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, "low-latency"), /* 441_2_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, "low-latency"), /* 48_1_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, "low-latency"), /* 48_2_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, "low-latency"), /* 48_3_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, "low-latency"), /* 48_4_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, "low-latency"), /* 48_5_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, "low-latency"), /* 48_6_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 v1.0.1 Table 6.4; high-reliability */
BAP_QOS("8_1_2", LC3_CONFIG_FREQ_8KHZ, LC3_CONFIG_DURATION_7_5, false, 26, 4, 45, 40000, 10, "high-reliabilty"), /* 8_1_2 */ BAP_QOS("8_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, "high-reliabilty"), /* 8_2_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, "high-reliabilty"), /* 16_1_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, "high-reliabilty"), /* 16_2_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, "high-reliabilty"), /* 24_1_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, "high-reliabilty"), /* 24_2_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, "high-reliabilty"), /* 32_1_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, "high-reliabilty"), /* 32_2_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, "high-reliabilty"), /* 441_1_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, "high-reliabilty"), /* 441_2_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, "high-reliabilty"), /* 48_1_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, "high-reliabilty"), /* 48_2_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, "high-reliabilty"), /* 48_3_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, "high-reliabilty"), /* 48_4_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, "high-reliabilty"), /* 48_5_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, "high-reliabilty"), /* 48_6_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) { 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) else if (c.priority < conf->priority)
continue; continue;
if (s->force_target_latency && !spa_streq(s->force_target_latency, c.tag))
continue;
if (s->retransmission >= 0) if (s->retransmission >= 0)
c.retransmission = s->retransmission; c.retransmission = s->retransmission;
if (s->latency >= 0) 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"))) if ((str = spa_dict_lookup(settings, "bluez5.bap.preset")))
s->qos_name = strdup(str); 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)) if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.rtn"), &value, 0))
s->retransmission = value; s->retransmission = value;
@ -889,11 +881,11 @@ static void parse_settings(struct settings *s, const struct spa_dict *settings,
spa_debugc(&debug_ctx->ctx, spa_debugc(&debug_ctx->ctx,
"BAP LC3 settings: preset:%s rtn:%d latency:%d delay:%d framing:%d " "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->qos_name ? s->qos_name : "auto",
s->retransmission, s->latency, (int)s->delay, s->framing, s->retransmission, s->latency, (int)s->delay, s->framing,
(unsigned int)s->locations, (unsigned int)s->channel_allocation, (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) static void free_config_data(struct config_data *d)
@ -901,7 +893,6 @@ static void free_config_data(struct config_data *d)
if (!d) if (!d)
return; return;
free(d->settings.qos_name); free(d->settings.qos_name);
free(d->settings.force_target_latency);
free(d); free(d);
} }

View file

@ -148,7 +148,6 @@ struct spa_bt_monitor {
struct spa_bt_remote_endpoint { struct spa_bt_remote_endpoint {
struct spa_list link; struct spa_list link;
struct spa_list device_link; struct spa_list device_link;
struct spa_list adapter_link;
struct spa_bt_monitor *monitor; struct spa_bt_monitor *monitor;
char *path; char *path;
char *transport_path; char *transport_path;
@ -156,7 +155,6 @@ struct spa_bt_remote_endpoint {
char *uuid; char *uuid;
unsigned int codec; unsigned int codec;
struct spa_bt_device *device; struct spa_bt_device *device;
struct spa_bt_adapter *adapter;
uint8_t *capabilities; uint8_t *capabilities;
size_t capabilities_len; size_t capabilities_len;
uint8_t *metadata; uint8_t *metadata;
@ -196,7 +194,6 @@ struct spa_bt_bis {
}; };
#define BROADCAST_CODE_LEN 16 #define BROADCAST_CODE_LEN 16
#define BD_ADDR_STR_LEN 17
struct spa_bt_big { struct spa_bt_big {
struct spa_list link; struct spa_list link;
@ -205,7 +202,6 @@ struct spa_bt_big {
struct spa_list bis_list; struct spa_list bis_list;
int big_id; int big_id;
int sync_factor; int sync_factor;
char adapter[BD_ADDR_STR_LEN + 3];
}; };
/* /*
@ -720,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 */ /** 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) 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) if (i >= feat->dict.n_items)
return NULL; return NULL;
if (!spa_streq(feat->dict.items[i].value, uuid)) if (!spa_streq(feat->dict.items[i].value, uuid))
return NULL; return NULL;
const char *pos = strchr(feat->dict.items[i].key, ':'); pos = strchr(feat->dict.items[i].key, ':');
if (!pos) if (!pos)
return NULL; return NULL;
return pos + 1; return pos + 1;
@ -736,11 +734,6 @@ static void bap_features_clear(struct bap_features *feat)
spa_zero(*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) static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata)
{ {
struct spa_bt_monitor *monitor = userdata; struct spa_bt_monitor *monitor = userdata;
@ -1334,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, static int parse_modalias(const char *modalias, uint16_t *source, uint16_t *vendor,
uint16_t *product, uint16_t *version) uint16_t *product, uint16_t *version)
{ {
char *pos;
unsigned int src, i, j, k; unsigned int src, i, j, k;
if (spa_strstartswith(modalias, "bluetooth:")) if (spa_strstartswith(modalias, "bluetooth:"))
@ -1343,7 +1337,7 @@ static int parse_modalias(const char *modalias, uint16_t *source, uint16_t *vend
else else
return -EINVAL; return -EINVAL;
const char *pos = strchr(modalias, ':'); pos = strchr(modalias, ':');
if (pos == NULL) if (pos == NULL)
return -EINVAL; return -EINVAL;
@ -1640,8 +1634,6 @@ static struct spa_bt_adapter *adapter_create(struct spa_bt_monitor *monitor, con
d->monitor = monitor; d->monitor = monitor;
d->path = strdup(path); d->path = strdup(path);
spa_list_init(&d->remote_endpoint_list);
spa_list_prepend(&monitor->adapter_list, &d->link); spa_list_prepend(&monitor->adapter_list, &d->link);
adapter_init_bus_type(monitor, d); adapter_init_bus_type(monitor, d);
@ -1656,7 +1648,6 @@ static void adapter_free(struct spa_bt_adapter *adapter)
{ {
struct spa_bt_monitor *monitor = adapter->monitor; struct spa_bt_monitor *monitor = adapter->monitor;
struct spa_bt_device *d, *td; struct spa_bt_device *d, *td;
struct spa_bt_remote_endpoint *ep, *tep;
spa_log_debug(monitor->log, "%p", adapter); spa_log_debug(monitor->log, "%p", adapter);
@ -1665,13 +1656,6 @@ static void adapter_free(struct spa_bt_adapter *adapter)
if (d->adapter == adapter) if (d->adapter == adapter)
device_free(d); 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_bt_player_destroy(adapter->dummy_player);
spa_list_remove(&adapter->link); spa_list_remove(&adapter->link);
@ -2774,7 +2758,6 @@ 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 }, { SPA_BLUETOOTH_AUDIO_CODEC_FASTSTREAM_DUPLEX, SPA_BT_FEATURE_A2DP_DUPLEX },
}; };
bool is_a2dp = codec->kind == MEDIA_CODEC_A2DP; bool is_a2dp = codec->kind == MEDIA_CODEC_A2DP;
bool is_bap = codec->kind == MEDIA_CODEC_BAP;
size_t i; size_t i;
codec_target_profile = get_codec_target_profile(monitor, codec); codec_target_profile = get_codec_target_profile(monitor, codec);
@ -2816,8 +2799,7 @@ bool spa_bt_device_supports_media_codec(struct spa_bt_device *device, const stru
continue; continue;
if (media_codec_check_caps(codec, ep->codec, ep->capabilities, ep->capabilities_len, if (media_codec_check_caps(codec, ep->codec, ep->capabilities, ep->capabilities_len,
&ep->monitor->default_audio_info, &ep->monitor->default_audio_info, &monitor->global_settings))
get_device_codec_settings(device, is_bap)))
return true; return true;
} }
@ -3033,31 +3015,20 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en
} }
else if (spa_streq(key, "Device")) { else if (spa_streq(key, "Device")) {
struct spa_bt_device *device; struct spa_bt_device *device;
struct spa_bt_adapter *adapter;
device = spa_bt_device_find(monitor, value); 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 != device) {
if (remote_endpoint->device != NULL) if (remote_endpoint->device != NULL)
spa_list_remove(&remote_endpoint->device_link); spa_list_remove(&remote_endpoint->device_link);
remote_endpoint->device = device; remote_endpoint->device = device;
if (device != NULL)
spa_list_append(&device->remote_endpoint_list, &remote_endpoint->device_link); 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")) { } else if (spa_streq(key, "Transport")) {
/* For ASHA */ /* For ASHA */
free(remote_endpoint->transport_path); free(remote_endpoint->transport_path);
@ -3131,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); spa_log_debug(monitor->log, "remote_endpoint %p: %s=%"PRIu64, remote_endpoint, key, remote_endpoint->hisyncid);
} else if (spa_streq(key, "SupportedFeatures")) { } else if (spa_streq(key, "SupportedFeatures")) {
DBusMessageIter iter;
if (!check_iter_signature(&it[1], "a{sv}")) if (!check_iter_signature(&it[1], "a{sv}"))
goto next; goto next;
dbus_message_iter_recurse(&it[1], &iter); dbus_message_iter_recurse(&it[1], &it[2]);
parse_supported_features(monitor, &iter, &remote_endpoint->bap_features); parse_supported_features(monitor, &it[2], &remote_endpoint->bap_features);
} else { } else {
unhandled: unhandled:
spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key); spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key);
@ -3193,9 +3162,6 @@ static void remote_endpoint_free(struct spa_bt_remote_endpoint *remote_endpoint)
if (remote_endpoint->device) if (remote_endpoint->device)
spa_list_remove(&remote_endpoint->device_link); 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); bap_features_clear(&remote_endpoint->bap_features);
spa_list_remove(&remote_endpoint->link); spa_list_remove(&remote_endpoint->link);
@ -3416,12 +3382,8 @@ int spa_bt_transport_acquire(struct spa_bt_transport *transport, bool optional)
if (!transport->acquired) if (!transport->acquired)
res = spa_bt_transport_impl(transport, acquire, 0, optional); res = spa_bt_transport_impl(transport, acquire, 0, optional);
else { else
/* keepalive */ res = 0;
transport->acquire_refcount = 1;
spa_bt_transport_emit_state_changed(transport, transport->state, transport->state);
return 0;
}
if (res >= 0) { if (res >= 0) {
transport->acquire_refcount = 1; transport->acquire_refcount = 1;
@ -4689,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, if (!media_codec_check_caps(codec, ep->codec, ep->capabilities, ep->capabilities_len,
&ep->device->monitor->default_audio_info, &ep->device->monitor->default_audio_info,
get_device_codec_settings(ep->device, codec->kind == MEDIA_CODEC_BAP))) &ep->device->monitor->global_settings))
return false; return false;
if (ep_profile & (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_BAP_SINK)) { if (ep_profile & (SPA_BT_PROFILE_A2DP_SINK | SPA_BT_PROFILE_BAP_SINK)) {
@ -6272,7 +6234,6 @@ static void configure_bis(struct spa_bt_monitor *monitor,
} }
static void configure_bcast_source(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, const struct media_codec *codec,
DBusConnection *conn, DBusConnection *conn,
const char *object_path, const char *object_path,
@ -6281,24 +6242,8 @@ static void configure_bcast_source(struct spa_bt_monitor *monitor,
{ {
struct spa_bt_big *big; struct spa_bt_big *big;
struct spa_bt_bis *bis; struct spa_bt_bis *bis;
/* Configure each BIS from a BIG */ /* Configure each BIS from a BIG */
spa_list_for_each(big, &monitor->bcast_source_config_list, link) { 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) { spa_list_for_each(bis, &big->bis_list, link) {
configure_bis(monitor, codec, conn, object_path, interface_name, configure_bis(monitor, codec, conn, object_path, interface_name,
big, bis, local_endpoint); big, bis, local_endpoint);
@ -6422,7 +6367,7 @@ static void interface_added(struct spa_bt_monitor *monitor,
} }
if (local_endpoint != NULL) 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);
} }
} }
} }
@ -7077,10 +7022,6 @@ static void parse_broadcast_source_config(struct spa_bt_monitor *monitor, const
goto parse_failed; goto parse_failed;
memcpy(big_entry->broadcast_code, bcode, strlen(bcode)); memcpy(big_entry->broadcast_code, bcode, strlen(bcode));
spa_log_debug(monitor->log, "big_entry->broadcast_code %s", big_entry->broadcast_code); spa_log_debug(monitor->log, "big_entry->broadcast_code %s", big_entry->broadcast_code);
} else if (spa_streq(key, "adapter")) {
if (spa_json_get_string(&it[1], big_entry->adapter, sizeof(big_entry->adapter)) <= 0)
goto parse_failed;
spa_log_debug(monitor->log, "big_entry->adapter %s", big_entry->adapter);
} else if (spa_streq(key, "encryption")) { } else if (spa_streq(key, "encryption")) {
if (spa_json_get_bool(&it[0], &big_entry->encryption) <= 0) if (spa_json_get_bool(&it[0], &big_entry->encryption) <= 0)
goto parse_failed; goto parse_failed;

View file

@ -1485,7 +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; profiles = this->bt_dev->profiles & SPA_BT_PROFILE_BAP_DUPLEX;
break; break;
case DEVICE_PROFILE_A2DP: case DEVICE_PROFILE_A2DP:
profiles = this->bt_dev->connected_profiles & SPA_BT_PROFILE_A2DP_DUPLEX; profiles = this->bt_dev->profiles & SPA_BT_PROFILE_A2DP_DUPLEX;
break; break;
default: default:
profiles = 0; profiles = 0;

View file

@ -136,7 +136,6 @@ extern "C" {
#define PROFILE_HFP_HF "/Profile/HFPHF" #define PROFILE_HFP_HF "/Profile/HFPHF"
#define HSP_HS_DEFAULT_CHANNEL 3 #define HSP_HS_DEFAULT_CHANNEL 3
#define HFP_SCO_DEFAULT_DATAPATH 0
#define SOURCE_ID_BLUETOOTH 0x1 /* Bluetooth SIG */ #define SOURCE_ID_BLUETOOTH 0x1 /* Bluetooth SIG */
#define SOURCE_ID_USB 0x2 /* USB Implementer's Forum */ #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 has_media1_interface:1;
unsigned int le_audio_bcast_supported:1; unsigned int le_audio_bcast_supported:1;
unsigned int tx_timestamping_supported:1; unsigned int tx_timestamping_supported:1;
struct spa_list remote_endpoint_list;
}; };
enum spa_bt_form_factor { enum spa_bt_form_factor {

View file

@ -69,7 +69,6 @@ struct spa_fga_descriptor {
void (*connect_port) (void *instance, unsigned long port, void *data); void (*connect_port) (void *instance, unsigned long port, void *data);
void (*control_changed) (void *instance); void (*control_changed) (void *instance);
void (*control_sync) (void *instance);
void (*activate) (void *instance); void (*activate) (void *instance);
void (*deactivate) (void *instance); void (*deactivate) (void *instance);

View file

@ -19,7 +19,6 @@
#include <spa/utils/string.h> #include <spa/utils/string.h>
#include <spa/utils/json.h> #include <spa/utils/json.h>
#include <spa/support/cpu.h> #include <spa/support/cpu.h>
#include <spa/support/loop.h>
#include <spa/support/plugin-loader.h> #include <spa/support/plugin-loader.h>
#include <spa/param/latency-utils.h> #include <spa/param/latency-utils.h>
#include <spa/param/tag-utils.h> #include <spa/param/tag-utils.h>
@ -81,6 +80,7 @@ struct descriptor {
unsigned long *output; unsigned long *output;
unsigned long *control; unsigned long *control;
unsigned long *notify; unsigned long *notify;
float *default_control;
}; };
struct port { struct port {
@ -94,9 +94,6 @@ struct port {
uint32_t n_links; uint32_t n_links;
uint32_t external; uint32_t external;
bool control_initialized;
float control_current;
float control_data[MAX_HNDL]; float control_data[MAX_HNDL];
float *audio_data[MAX_HNDL]; float *audio_data[MAX_HNDL];
void *audio_mem[MAX_HNDL]; void *audio_mem[MAX_HNDL];
@ -196,9 +193,6 @@ struct graph {
struct volume volume[2]; struct volume volume[2];
uint32_t default_inputs;
uint32_t default_outputs;
uint32_t n_inputs; uint32_t n_inputs;
uint32_t n_outputs; uint32_t n_outputs;
uint32_t inputs_position[MAX_CHANNELS]; uint32_t inputs_position[MAX_CHANNELS];
@ -222,7 +216,6 @@ struct impl {
struct spa_cpu *cpu; struct spa_cpu *cpu;
struct spa_fga_dsp *dsp; struct spa_fga_dsp *dsp;
struct spa_plugin_loader *loader; struct spa_plugin_loader *loader;
struct spa_loop *data_loop;
uint64_t info_all; uint64_t info_all;
struct spa_filter_graph_info info; 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; impl->info.change_mask = impl->info_all;
if (impl->info.change_mask || full) { if (impl->info.change_mask || full) {
char n_inputs[64], n_outputs[64], latency[64]; 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_item items[6];
struct spa_dict dict = SPA_DICT(items, 0); struct spa_dict dict = SPA_DICT(items, 0);
char in_pos[MAX_CHANNELS * 8]; char in_pos[MAX_CHANNELS * 8];
char out_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_inputs, sizeof(n_inputs), "%d", impl->graph.n_inputs);
snprintf(n_outputs, sizeof(n_outputs), "%d", impl->graph.n_outputs); 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_inputs", n_inputs);
items[dict.n_items++] = SPA_DICT_ITEM("n_outputs", n_outputs); 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) { if (graph->n_inputs_position) {
print_channels(in_pos, sizeof(in_pos), print_channels(in_pos, sizeof(in_pos),
graph->n_inputs_position, graph->inputs_position); graph->n_inputs_position, graph->inputs_position);
@ -353,6 +339,12 @@ static int impl_process(void *object,
return 0; 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) static struct node *find_node(struct graph *graph, const char *name)
{ {
struct node *node; struct node *node;
@ -441,20 +433,6 @@ static struct port *find_port(struct node *node, const char *name, int descripto
return NULL; 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, static int impl_enum_prop_info(void *object, uint32_t idx, struct spa_pod_builder *b,
struct spa_pod **param) 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; struct spa_fga_port *p;
float def, min, max; float def, min, max;
char name[512]; char name[512];
uint32_t rate = impl->rate ? impl->rate : DEFAULT_RATE;
if (idx >= graph->n_control) if (idx >= graph->n_control)
return 0; return 0;
@ -479,7 +458,15 @@ static int impl_enum_prop_info(void *object, uint32_t idx, struct spa_pod_builde
d = desc->desc; d = desc->desc;
p = &d->ports[port->p]; 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') if (node->name[0] != '\0')
snprintf(name, sizeof(name), "%s:%s", node->name, p->name); 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; 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 node *node = port->node;
struct impl *impl = node->graph->impl; struct impl *impl = node->graph->impl;
struct descriptor *desc = node->desc; struct descriptor *desc = node->desc;
struct spa_fga_port *p = &desc->desc->ports[port->p];
float old; float old;
bool changed; bool changed;
old = port->control_data[id]; 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, 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]; changed = old != port->control_data[id];
node->control_changed |= changed; node->control_changed |= changed;
return changed ? 1 : 0; 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) static int set_control_value(struct node *node, const char *name, float *value)
{ {
struct port *port; struct port *port;
int count = 0;
uint32_t i, n_hndl;
port = find_port(node, name, SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL); port = find_port(node, name, SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL);
if (port == NULL) if (port == NULL)
return -ENOENT; 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) static int parse_params(struct graph *graph, const struct spa_pod *pod)
@ -700,47 +670,22 @@ static int impl_reset(void *object)
return 0; return 0;
} }
static int static void node_control_changed(struct node *node)
do_emit_node_control_sync(struct spa_loop *loop, bool async, uint32_t seq, const void *data,
size_t size, void *user_data)
{ {
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; const struct spa_fga_descriptor *d = node->desc->desc;
if (!node->control_changed || d->control_sync == NULL)
continue;
for (i = 0; i < node->n_hndl; i++) {
if (node->hndl[i] != NULL)
d->control_sync(node->hndl[i]);
}
}
return 0;
}
static void emit_node_control_changed(struct impl *impl)
{
struct graph *graph = &impl->graph;
struct node *node;
uint32_t i; uint32_t i;
spa_loop_locked(impl->data_loop, do_emit_node_control_sync, 1, NULL, 0, impl);
spa_list_for_each(node, &graph->node_list, link) {
const struct spa_fga_descriptor *d = node->desc->desc;
if (!node->control_changed) if (!node->control_changed)
continue; return;
if (d->control_changed != NULL) {
for (i = 0; i < node->n_hndl; i++) { for (i = 0; i < node->n_hndl; i++) {
if (node->hndl[i] != NULL) if (node->hndl[i] == NULL)
continue;
if (d->control_changed)
d->control_changed(node->hndl[i]); d->control_changed(node->hndl[i]);
} }
}
node->control_changed = false; node->control_changed = false;
} }
}
static int sync_volume(struct graph *graph, struct volume *vol) 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]; v = v * (vol->max[n_port] - vol->min[n_port]) + vol->min[n_port];
n_hndl = SPA_MAX(1u, p->node->n_hndl); 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; 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); spa_pod_dynamic_builder_clean(&b);
if (changed > 0) { 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); spa_filter_graph_emit_props_changed(&impl->hooks, SPA_DIRECTION_INPUT);
} }
return 0; return 0;
@ -976,6 +925,7 @@ static void descriptor_unref(struct descriptor *desc)
free(desc->input); free(desc->input);
free(desc->output); free(desc->output);
free(desc->control); free(desc->control);
free(desc->default_control);
free(desc->notify); free(desc->notify);
free(desc); free(desc);
} }
@ -986,7 +936,7 @@ static struct descriptor *descriptor_load(struct impl *impl, const char *type,
struct plugin *pl; struct plugin *pl;
struct descriptor *desc; struct descriptor *desc;
const struct spa_fga_descriptor *d; 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; unsigned long p;
int res; 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->input = calloc(n_input, sizeof(unsigned long));
desc->output = calloc(n_output, sizeof(unsigned long)); desc->output = calloc(n_output, sizeof(unsigned long));
desc->control = calloc(n_control, 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)); desc->notify = calloc(n_notify, sizeof(unsigned long));
for (p = 0; p < d->n_ports; p++) { for (p = 0; p < d->n_ports; p++) {
@ -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); spa_list_append(&pl->descriptor_list, &desc->link);
return desc; return desc;
@ -1448,6 +1410,7 @@ static int load_node(struct graph *graph, struct spa_json *json)
port->external = SPA_ID_INVALID; port->external = SPA_ID_INVALID;
port->p = desc->control[i]; port->p = desc->control[i];
spa_list_init(&port->link_list); spa_list_init(&port->link_list);
port->control_data[0] = desc->default_control[i];
} }
for (i = 0; i < desc->n_notify; i++) { for (i = 0; i < desc->n_notify; i++) {
struct port *port = &node->notify_port[i]; struct port *port = &node->notify_port[i];
@ -1718,10 +1681,10 @@ static int impl_activate(void *object, const struct spa_dict *props)
for (i = 0; i < node->n_hndl; i++) { for (i = 0; i < node->n_hndl; i++) {
if (d->activate) if (d->activate)
d->activate(node->hndl[i]); 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 */ /* calculate latency */
sort_reset(graph); sort_reset(graph);
while ((node = sort_next_node(graph)) != NULL) { while ((node = sort_next_node(graph)) != NULL) {
@ -1821,7 +1784,7 @@ static int setup_graph(struct graph *graph)
struct port *port; struct port *port;
struct graph_port *gp; struct graph_port *gp;
struct graph_hndl *gh; 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; int res;
struct descriptor *desc; struct descriptor *desc;
const struct spa_fga_descriptor *d; const struct spa_fga_descriptor *d;
@ -1833,8 +1796,19 @@ static int setup_graph(struct graph *graph)
first = spa_list_first(&graph->node_list, struct node, link); first = spa_list_first(&graph->node_list, struct node, link);
last = spa_list_last(&graph->node_list, struct node, link); last = spa_list_last(&graph->node_list, struct node, link);
n_input = graph->default_inputs; /* calculate the number of inputs and outputs into the graph.
n_output = graph->default_outputs; * 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 /* we allow unconnected ports when not explicitly given and the nodes support
* NULL data */ * NULL data */
@ -1842,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(first->desc->desc->flags, SPA_FGA_DESCRIPTOR_SUPPORTS_NULL_DATA) &&
SPA_FLAG_IS_SET(last->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) if (n_input == 0) {
n_input = n_output; spa_log_error(impl->log, "no inputs");
if (n_output == 0) res = -EINVAL;
n_output = n_input; goto error;
}
if (n_output == 0) {
spa_log_error(impl->log, "no outputs");
res = -EINVAL;
goto error;
}
if (graph->n_inputs == 0) if (graph->n_inputs == 0)
graph->n_inputs = impl->info.n_inputs; graph->n_inputs = impl->info.n_inputs;
if (graph->n_inputs == 0) if (graph->n_inputs == 0)
@ -1857,14 +1836,12 @@ static int setup_graph(struct graph *graph)
/* compare to the requested number of inputs and duplicate the /* compare to the requested number of inputs and duplicate the
* graph n_hndl times when needed. */ * 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) if (graph->n_outputs == 0)
graph->n_outputs = n_output * n_hndl; graph->n_outputs = n_output * n_hndl;
n_out_hndl = n_output ? graph->n_outputs / n_output : 1; if (n_hndl != graph->n_outputs / n_output) {
if (n_hndl != n_out_hndl) {
spa_log_error(impl->log, "invalid ports. The input stream has %1$d ports and " 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 " "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 != " "and the filter has %4$d outputs. input:%1$d / input:%2$d != "
@ -2052,9 +2029,11 @@ static int setup_graph(struct graph *graph)
} }
} }
for (i = 0; i < desc->n_control; i++) { 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]; struct port *port = &node->control_port[i];
port_set_control_value(port, for (j = 1; j < n_hndl; j++)
port->control_initialized ? &port->control_current : NULL); port->control_data[j] = port->control_data[0];
} }
} }
res = 0; res = 0;
@ -2104,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 inputs, outputs, *pinputs = NULL, *poutputs = NULL;
struct spa_json ivolumes, ovolumes, *pivolumes = NULL, *povolumes = NULL; struct spa_json ivolumes, ovolumes, *pivolumes = NULL, *povolumes = NULL;
struct spa_json nodes, *pnodes = NULL, links, *plinks = NULL; struct spa_json nodes, *pnodes = NULL, links, *plinks = NULL;
struct node *first, *last;
const char *json, *val; const char *json, *val;
char key[256]; char key[256];
int res, len; int res, len;
@ -2254,25 +2232,6 @@ static int load_graph(struct graph *graph, const struct spa_dict *props)
} }
if ((res = setup_graph_controls(graph)) < 0) if ((res = setup_graph_controls(graph)) < 0)
return res; 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; return 0;
} }
@ -2369,7 +2328,6 @@ impl_init(const struct spa_handle_factory *factory,
spa_log_topic_init(impl->log, &log_topic); spa_log_topic_init(impl->log, &log_topic);
impl->cpu = spa_support_find(support, n_support, SPA_TYPE_INTERFACE_CPU); 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->max_align = spa_cpu_get_max_align(impl->cpu);
impl->dsp = spa_fga_dsp_new(impl->cpu ? spa_cpu_get_flags(impl->cpu) : 0); impl->dsp = spa_fga_dsp_new(impl->cpu ? spa_cpu_get_flags(impl->cpu) : 0);

View file

@ -14,14 +14,12 @@
#include <sys/wait.h> #include <sys/wait.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <fcntl.h> #include <fcntl.h>
#include <time.h>
#include <spa/utils/json.h> #include <spa/utils/json.h>
#include <spa/utils/result.h> #include <spa/utils/result.h>
#include <spa/utils/cleanup.h> #include <spa/utils/cleanup.h>
#include <spa/support/cpu.h> #include <spa/support/cpu.h>
#include <spa/support/log.h> #include <spa/support/log.h>
#include <spa/support/loop.h>
#include <spa/plugins/audioconvert/resample.h> #include <spa/plugins/audioconvert/resample.h>
#include <spa/debug/log.h> #include <spa/debug/log.h>
@ -543,12 +541,7 @@ static void bq_run(void *Instance, unsigned long samples)
struct biquad *bq = &impl->bq; struct biquad *bq = &impl->bq;
float *out = impl->port[0]; float *out = impl->port[0];
float *in = impl->port[1]; 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) { if (impl->type == BQ_NONE) {
float b0, b1, b2, a0, a1, a2; float b0, b1, b2, a0, a1, a2;
b0 = impl->port[5][0]; 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) if (impl->freq != freq || impl->Q != Q || impl->gain != gain)
bq_freq_update(impl, impl->type, freq, Q, 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 */ /** bq_lowpass */
@ -579,7 +573,6 @@ static const struct spa_fga_descriptor bq_lowpass_desc = {
.instantiate = bq_instantiate, .instantiate = bq_instantiate,
.connect_port = builtin_connect_port, .connect_port = builtin_connect_port,
.control_sync = bq_control_sync,
.activate = bq_activate, .activate = bq_activate,
.run = bq_run, .run = bq_run,
.cleanup = builtin_cleanup, .cleanup = builtin_cleanup,
@ -594,7 +587,6 @@ static const struct spa_fga_descriptor bq_highpass_desc = {
.instantiate = bq_instantiate, .instantiate = bq_instantiate,
.connect_port = builtin_connect_port, .connect_port = builtin_connect_port,
.control_sync = bq_control_sync,
.activate = bq_activate, .activate = bq_activate,
.run = bq_run, .run = bq_run,
.cleanup = builtin_cleanup, .cleanup = builtin_cleanup,
@ -609,7 +601,6 @@ static const struct spa_fga_descriptor bq_bandpass_desc = {
.instantiate = bq_instantiate, .instantiate = bq_instantiate,
.connect_port = builtin_connect_port, .connect_port = builtin_connect_port,
.control_sync = bq_control_sync,
.activate = bq_activate, .activate = bq_activate,
.run = bq_run, .run = bq_run,
.cleanup = builtin_cleanup, .cleanup = builtin_cleanup,
@ -624,7 +615,6 @@ static const struct spa_fga_descriptor bq_lowshelf_desc = {
.instantiate = bq_instantiate, .instantiate = bq_instantiate,
.connect_port = builtin_connect_port, .connect_port = builtin_connect_port,
.control_sync = bq_control_sync,
.activate = bq_activate, .activate = bq_activate,
.run = bq_run, .run = bq_run,
.cleanup = builtin_cleanup, .cleanup = builtin_cleanup,
@ -639,7 +629,6 @@ static const struct spa_fga_descriptor bq_highshelf_desc = {
.instantiate = bq_instantiate, .instantiate = bq_instantiate,
.connect_port = builtin_connect_port, .connect_port = builtin_connect_port,
.control_sync = bq_control_sync,
.activate = bq_activate, .activate = bq_activate,
.run = bq_run, .run = bq_run,
.cleanup = builtin_cleanup, .cleanup = builtin_cleanup,
@ -654,7 +643,6 @@ static const struct spa_fga_descriptor bq_peaking_desc = {
.instantiate = bq_instantiate, .instantiate = bq_instantiate,
.connect_port = builtin_connect_port, .connect_port = builtin_connect_port,
.control_sync = bq_control_sync,
.activate = bq_activate, .activate = bq_activate,
.run = bq_run, .run = bq_run,
.cleanup = builtin_cleanup, .cleanup = builtin_cleanup,
@ -669,7 +657,6 @@ static const struct spa_fga_descriptor bq_notch_desc = {
.instantiate = bq_instantiate, .instantiate = bq_instantiate,
.connect_port = builtin_connect_port, .connect_port = builtin_connect_port,
.control_sync = bq_control_sync,
.activate = bq_activate, .activate = bq_activate,
.run = bq_run, .run = bq_run,
.cleanup = builtin_cleanup, .cleanup = builtin_cleanup,
@ -685,7 +672,6 @@ static const struct spa_fga_descriptor bq_allpass_desc = {
.instantiate = bq_instantiate, .instantiate = bq_instantiate,
.connect_port = builtin_connect_port, .connect_port = builtin_connect_port,
.control_sync = bq_control_sync,
.activate = bq_activate, .activate = bq_activate,
.run = bq_run, .run = bq_run,
.cleanup = builtin_cleanup, .cleanup = builtin_cleanup,
@ -700,7 +686,6 @@ static const struct spa_fga_descriptor bq_raw_desc = {
.instantiate = bq_instantiate, .instantiate = bq_instantiate,
.connect_port = builtin_connect_port, .connect_port = builtin_connect_port,
.control_sync = bq_control_sync,
.activate = bq_activate, .activate = bq_activate,
.run = bq_run, .run = bq_run,
.cleanup = builtin_cleanup, .cleanup = builtin_cleanup,
@ -3130,136 +3115,6 @@ static const struct spa_fga_descriptor noisegate_desc = {
.cleanup = builtin_cleanup, .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,
},
};
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) static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index)
{ {
switch(Index) { switch(Index) {
@ -3325,10 +3180,6 @@ static const struct spa_fga_descriptor * builtin_descriptor(unsigned long Index)
return &zeroramp_desc; return &zeroramp_desc;
case 30: case 30:
return &noisegate_desc; return &noisegate_desc;
case 31:
return &busy_desc;
case 32:
return &null_desc;
} }
return NULL; return NULL;
} }

View file

@ -32,7 +32,6 @@ struct spatializer_impl {
unsigned long rate; unsigned long rate;
float *port[7]; float *port[7];
int n_samples, blocksize, tailsize; int n_samples, blocksize, tailsize;
float gain;
float *tmp[2]; float *tmp[2];
struct MYSOFA_EASY *sofa; struct MYSOFA_EASY *sofa;
@ -72,7 +71,6 @@ static void * spatializer_instantiate(const struct spa_fga_plugin *plugin, const
impl->plugin = pl; impl->plugin = pl;
impl->dsp = pl->dsp; impl->dsp = pl->dsp;
impl->log = pl->log; impl->log = pl->log;
impl->gain = 1.0f;
while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) { while ((len = spa_json_object_next(&it[0], key, sizeof(key), &val)) > 0) {
if (spa_streq(key, "blocksize")) { if (spa_streq(key, "blocksize")) {
@ -96,13 +94,6 @@ static void * spatializer_instantiate(const struct spa_fga_plugin *plugin, const
goto error; 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]) { if (!filename[0]) {
spa_log_error(impl->log, "spatializer:filename was not given"); 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"; reason = "Only sources with MC supported";
errno = ENOTSUP; errno = ENOTSUP;
break; break;
default:
case MYSOFA_INTERNAL_ERROR: case MYSOFA_INTERNAL_ERROR:
errno = EIO; errno = EIO;
reason = "Internal error"; reason = "Internal error";
break; break;
default:
errno = ret;
reason = strerror(errno);
break;
} }
spa_log_error(impl->log, "Unable to load HRTF from %s: %s (%d)", filename, reason, ret); spa_log_error(impl->log, "Unable to load HRTF from %s: %s (%d)", filename, reason, ret);
goto error; goto error;
@ -195,8 +183,8 @@ static void * spatializer_instantiate(const struct spa_fga_plugin *plugin, const
if (impl->tailsize <= 0) if (impl->tailsize <= 0)
impl->tailsize = SPA_CLAMP(4096, impl->blocksize, 32768); 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, spa_log_info(impl->log, "using n_samples:%u %d:%d blocksize sofa:%s", impl->n_samples,
impl->blocksize, impl->tailsize, impl->gain, filename); impl->blocksize, impl->tailsize, filename);
impl->tmp[0] = calloc(impl->plugin->quantum_limit, sizeof(float)); impl->tmp[0] = calloc(impl->plugin->quantum_limit, sizeof(float));
impl->tmp[1] = 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]) if (impl->r_conv[2])
convolver_free(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, impl->l_conv[2] = convolver_new(impl->dsp, impl->blocksize, impl->tailsize,
left_ir, impl->n_samples); left_ir, impl->n_samples);
impl->r_conv[2] = convolver_new(impl->dsp, impl->blocksize, impl->tailsize, impl->r_conv[2] = convolver_new(impl->dsp, impl->blocksize, impl->tailsize,

View file

@ -73,7 +73,7 @@ impl_log_logtv(void *object,
char timestamp[18] = {0}; char timestamp[18] = {0};
char topicstr[32] = {0}; char topicstr[32] = {0};
char filename[64] = {0}; char filename[64] = {0};
char location[1000 + RESERVED_LENGTH], *p; char location[1000 + RESERVED_LENGTH], *p, *s;
static const char * const levels[] = { "-", "E", "W", "I", "D", "T", "*T*" }; static const char * const levels[] = { "-", "E", "W", "I", "D", "T", "*T*" };
const char *prefix = "", *suffix = ""; const char *prefix = "", *suffix = "";
int size, len; int size, len;
@ -118,7 +118,7 @@ impl_log_logtv(void *object,
if (impl->line && line != 0) { if (impl->line && line != 0) {
const char *s = strrchr(file, '/'); s = strrchr(file, '/');
spa_scnprintf(filename, sizeof(filename), "[%16.16s:%5i %s()]", spa_scnprintf(filename, sizeof(filename), "[%16.16s:%5i %s()]",
s ? s + 1 : file, line, func); s ? s + 1 : file, line, func);
} }

View file

@ -19,8 +19,6 @@ context.modules = [
name = spFL name = spFL
config = { config = {
filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa"
# The gain depends on the .sofa file in use
gain = 0.5
} }
control = { control = {
"Azimuth" = 30.0 "Azimuth" = 30.0
@ -34,7 +32,6 @@ context.modules = [
name = spFR name = spFR
config = { config = {
filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa"
gain = 0.5
} }
control = { control = {
"Azimuth" = 330.0 "Azimuth" = 330.0
@ -48,7 +45,6 @@ context.modules = [
name = spFC name = spFC
config = { config = {
filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa"
gain = 0.5
} }
control = { control = {
"Azimuth" = 0.0 "Azimuth" = 0.0
@ -62,7 +58,6 @@ context.modules = [
name = spRL name = spRL
config = { config = {
filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa"
gain = 0.5
} }
control = { control = {
"Azimuth" = 150.0 "Azimuth" = 150.0
@ -76,7 +71,6 @@ context.modules = [
name = spRR name = spRR
config = { config = {
filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa"
gain = 0.5
} }
control = { control = {
"Azimuth" = 210.0 "Azimuth" = 210.0
@ -90,7 +84,6 @@ context.modules = [
name = spSL name = spSL
config = { config = {
filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa"
gain = 0.5
} }
control = { control = {
"Azimuth" = 90.0 "Azimuth" = 90.0
@ -104,7 +97,6 @@ context.modules = [
name = spSR name = spSR
config = { config = {
filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa"
gain = 0.5
} }
control = { control = {
"Azimuth" = 270.0 "Azimuth" = 270.0
@ -118,7 +110,6 @@ context.modules = [
name = spLFE name = spLFE
config = { config = {
filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa" filename = "~/.config/hrtf-sofa/hrtf b_nh724.sofa"
gain = 0.5
} }
control = { control = {
"Azimuth" = 0.0 "Azimuth" = 0.0
@ -127,32 +118,8 @@ context.modules = [
} }
} }
{ type = builtin label = mixer name = mixL { type = builtin label = mixer name = mixL }
control = { { type = builtin label = mixer name = mixR }
# 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
}
}
] ]
links = [ links = [
# output # output

View file

@ -100,10 +100,6 @@ context.modules = [
} }
flags = [ ifexists nofail ] flags = [ ifexists nofail ]
} }
# the graph scheduler
{ name = libpipewire-module-scheduler-v1
condition = [ { module.scheduler-v1 = !false } ]
}
# The native communication protocol. # The native communication protocol.
{ name = libpipewire-module-protocol-native } { name = libpipewire-module-protocol-native }

View file

@ -121,10 +121,6 @@ context.modules = [
flags = [ ifexists nofail ] flags = [ ifexists nofail ]
condition = [ { module.rt = !false } ] condition = [ { module.rt = !false } ]
} }
# the graph scheduler
{ name = libpipewire-module-scheduler-v1
condition = [ { module.scheduler-v1 = !false } ]
}
# The native communication protocol. # The native communication protocol.
{ name = libpipewire-module-protocol-native { name = libpipewire-module-protocol-native

46
src/examples/base64.h Normal file
View file

@ -0,0 +1,46 @@
/* PipeWire */
/* SPDX-FileCopyrightText: Copyright © 2021 Wim Taymans */
/* SPDX-License-Identifier: MIT */
static inline 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 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,
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;
}

View file

@ -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;
}

View file

@ -29,7 +29,7 @@
#include <pipewire/pipewire.h> #include <pipewire/pipewire.h>
#include <pipewire/capabilities.h> #include <pipewire/capabilities.h>
#include "utils.h" #include "base64.h"
/* Comment out to test device ID negotation backward compatibility. */ /* Comment out to test device ID negotation backward compatibility. */
#define SUPPORT_DEVICE_ID_NEGOTIATION 1 #define SUPPORT_DEVICE_ID_NEGOTIATION 1
@ -372,28 +372,18 @@ collect_device_ids(struct data *data, const char *json)
int len; int len;
const char *value; const char *value;
struct spa_json sub; struct spa_json sub;
char key[1024];
if ((len = spa_json_begin(&it, json, strlen(json), &value)) <= 0) { if ((len = spa_json_begin(&it, json, strlen(json), &value)) <= 0) {
fprintf(stderr, "invalid device IDs value\n"); fprintf(stderr, "invalid device IDs value\n");
return; return;
} }
if (!spa_json_is_object(value, len)) { if (!spa_json_is_array(value, len)) {
fprintf(stderr, "device IDs not object\n"); fprintf(stderr, "device IDs not array\n");
return; return;
} }
spa_json_enter(&it, &sub); spa_json_enter(&it, &sub);
while ((len = spa_json_object_next(&sub, key, sizeof(key), &value)) > 0) { while ((len = spa_json_next(&sub, &value)) > 0) {
struct spa_json devices_sub;
if (!spa_json_is_array(value, len)) {
fprintf(stderr, "available-devices not array\n");
return;
}
spa_json_enter(&sub, &devices_sub);
while ((len = spa_json_next(&devices_sub, &value)) > 0) {
char *string; char *string;
union { union {
dev_t device_id; dev_t device_id;
@ -412,8 +402,9 @@ collect_device_ids(struct data *data, const char *json)
return; return;
} }
if (decode_hex(string, dec.buffer, sizeof (dec.buffer)) < 0) { if (base64_decode(string, strlen(string),
fprintf(stderr, "invalid device ID string\n"); (uint8_t *)&dec.device_id) < sizeof(dev_t)) {
fprintf(stderr, "invalid device ID\n");
return; return;
} }
@ -423,7 +414,6 @@ collect_device_ids(struct data *data, const char *json)
data->device_ids[data->n_device_ids++] = dec.device_id; data->device_ids[data->n_device_ids++] = dec.device_id;
} }
} }
}
static void static void
discover_capabilities(struct data *data, const struct spa_pod *param) discover_capabilities(struct data *data, const struct spa_pod *param)
@ -448,9 +438,8 @@ discover_capabilities(struct data *data, const struct spa_pod *param)
return; return;
spa_dict_for_each(it, &dict) { spa_dict_for_each(it, &dict) {
if (spa_streq(it->key, PW_CAPABILITY_DEVICE_ID_NEGOTIATION)) { if (spa_streq(it->key, PW_CAPABILITY_DEVICE_ID_NEGOTIATION) &&
int version = atoi(it->value); spa_streq(it->value, "true")) {
if (version >= 1)
data->device_negotiation_supported = true; data->device_negotiation_supported = true;
} else if (spa_streq(it->key, PW_CAPABILITY_DEVICE_IDS)) { } else if (spa_streq(it->key, PW_CAPABILITY_DEVICE_IDS)) {
collect_device_ids(data, it->value); collect_device_ids(data, it->value);
@ -798,7 +787,7 @@ int main(int argc, char *argv[])
params[n_params++] = params[n_params++] =
spa_param_dict_build_dict(&b, SPA_PARAM_Capability, spa_param_dict_build_dict(&b, SPA_PARAM_Capability,
&SPA_DICT_ITEMS( &SPA_DICT_ITEMS(
SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "1"))); SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_ID_NEGOTIATION, "true")));
#endif #endif
/* now connect the stream, we need a direction (input/output), /* now connect the stream, we need a direction (input/output),

View file

@ -30,7 +30,7 @@
#include <pipewire/pipewire.h> #include <pipewire/pipewire.h>
#include <pipewire/capabilities.h> #include <pipewire/capabilities.h>
#include "utils.h" #include "base64.h"
/* Comment out to test device ID negotation backward compatibility. */ /* Comment out to test device ID negotation backward compatibility. */
#define SUPPORT_DEVICE_ID_NEGOTIATION 1 #define SUPPORT_DEVICE_ID_NEGOTIATION 1
@ -450,9 +450,8 @@ discover_capabilities(struct data *data, const struct spa_pod *param)
return; return;
spa_dict_for_each(it, &dict) { spa_dict_for_each(it, &dict) {
if (spa_streq(it->key, PW_CAPABILITY_DEVICE_ID_NEGOTIATION)) { if (spa_streq(it->key, PW_CAPABILITY_DEVICE_ID_NEGOTIATION) &&
int version = atoi(it->value); spa_streq(it->value, "true")) {
if (version >= 1)
data->device_negotiation_supported = true; data->device_negotiation_supported = true;
} }
} }
@ -784,26 +783,23 @@ int main(int argc, char *argv[])
size_t i; size_t i;
ms = open_memstream(&device_ids, &device_ids_size); ms = open_memstream(&device_ids, &device_ids_size);
fprintf(ms, "{\"available-devices\": ["); fprintf(ms, "[");
for (i = 0; i < SPA_N_ELEMENTS(devices); i++) { for (i = 0; i < SPA_N_ELEMENTS(devices); i++) {
dev_t device_id = makedev(devices[i].major, devices[i].minor); dev_t device_id = makedev(devices[i].major, devices[i].minor);
char *device_id_encoded; char device_id_encoded[256];
device_id_encoded = encode_hex((const uint8_t *) &device_id, sizeof (device_id));
base64_encode((const uint8_t *) &device_id, sizeof (device_id), device_id_encoded, '\0');
if (i > 0) if (i > 0)
fprintf(ms, ","); fprintf(ms, ",");
fprintf(ms, "\"%s\"", device_id_encoded); fprintf(ms, "\"%s\"", device_id_encoded);
free(device_id_encoded);
} }
fprintf(ms, "]}"); fprintf(ms, "]");
fclose(ms); fclose(ms);
#endif /* SUPPORT_DEVICE_IDS_LIST */ #endif /* SUPPORT_DEVICE_IDS_LIST */
params[n_params++] = params[n_params++] =
spa_param_dict_build_dict(&b, SPA_PARAM_Capability, 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 #ifdef SUPPORT_DEVICE_IDS_LIST
SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_IDS, device_ids) SPA_DICT_ITEM(PW_CAPABILITY_DEVICE_IDS, device_ids)
#endif /* SUPPORT_DEVICE_IDS_LIST */ #endif /* SUPPORT_DEVICE_IDS_LIST */

View file

@ -209,6 +209,8 @@ void gst_pipewire_pool_wrap_buffer (GstPipeWirePool *pool, struct pw_buffer *b)
data->b = b; data->b = b;
data->buf = buf; data->buf = buf;
data->crop = spa_buffer_find_meta_data (b->buffer, SPA_META_VideoCrop, sizeof(*data->crop)); 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 = data->videotransform =
spa_buffer_find_meta_data (b->buffer, SPA_META_VideoTransform, sizeof(*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)); 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); GST_LOG_OBJECT (pool, "release buffer %p", buffer);
GstPipeWirePoolData *data = gst_pipewire_pool_get_data(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); GST_OBJECT_LOCK (pool);
pw_thread_loop_lock (s->core->loop);
if (!data->queued && data->b != NULL) if (!data->queued && data->b != NULL)
{ {
GstPipeWirePool *p = GST_PIPEWIRE_POOL (pool);
g_autoptr (GstPipeWireStream) s = g_weak_ref_get (&p->stream);
int res; int res;
pw_thread_loop_lock (s->core->loop);
if ((res = pw_stream_return_buffer (s->pwstream, data->b)) < 0) { 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)); GST_ERROR_OBJECT (pool,"can't return buffer %p; gstbuffer : %p, %s",data->b, buffer, spa_strerror(res));
} else { } else {
data->queued = TRUE; data->queued = TRUE;
GST_DEBUG_OBJECT (pool, "returned buffer %p; gstbuffer:%p", data->b, buffer); 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); GST_OBJECT_UNLOCK (pool);
} }

View file

@ -781,7 +781,7 @@ static GstBuffer *dequeue_buffer(GstPipeWireSrc *pwsrc)
crop = data->crop; crop = data->crop;
if (crop) { if (crop) {
GstVideoCropMeta *meta = gst_buffer_add_video_crop_meta(buf); GstVideoCropMeta *meta = gst_buffer_get_video_crop_meta(buf);
if (meta) { if (meta) {
meta->x = crop->region.position.x; meta->x = crop->region.position.x;
meta->y = crop->region.position.y; meta->y = crop->region.position.y;

View file

@ -46,7 +46,6 @@ module_sources = [
'module-vban-recv.c', 'module-vban-recv.c',
'module-vban-send.c', 'module-vban-send.c',
'module-session-manager.c', 'module-session-manager.c',
'module-scheduler-v1.c',
'module-zeroconf-discover.c', 'module-zeroconf-discover.c',
'module-roc-source.c', 'module-roc-source.c',
'module-roc-sink.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] pipewire_module_protocol_deps = [mathlib, dl_lib, pipewire_dep]
if systemd_dep.found()
pipewire_module_protocol_deps += systemd_dep
endif
if selinux_dep.found() if selinux_dep.found()
pipewire_module_protocol_deps += selinux_dep pipewire_module_protocol_deps += selinux_dep
endif endif
@ -533,15 +536,6 @@ pipewire_module_adapter = shared_library('pipewire-module-adapter',
dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep], 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', pipewire_module_session_manager = shared_library('pipewire-module-session-manager',
[ 'module-session-manager.c', [ 'module-session-manager.c',
'module-session-manager/client-endpoint/client-endpoint.c', 'module-session-manager/client-endpoint/client-endpoint.c',
@ -579,22 +573,6 @@ if build_module_zeroconf_discover
endif endif
summary({'zeroconf-discover': build_module_zeroconf_discover}, bool_yn: true, section: 'Optional Modules') 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() build_module_raop_discover = avahi_dep.found()
if build_module_raop_discover if build_module_raop_discover
pipewire_module_raop_discover = shared_library('pipewire-module-raop-discover', pipewire_module_raop_discover = shared_library('pipewire-module-raop-discover',
@ -627,12 +605,13 @@ build_module_raop = openssl_lib.found()
if build_module_raop if build_module_raop
pipewire_module_raop_sink = shared_library('pipewire-module-raop-sink', pipewire_module_raop_sink = shared_library('pipewire-module-raop-sink',
[ 'module-raop-sink.c', [ 'module-raop-sink.c',
'module-raop/rtsp-client.c' ], 'module-raop/rtsp-client.c',
'module-rtp/stream.c' ],
include_directories : [configinc], include_directories : [configinc],
install : true, install : true,
install_dir : modules_install_dir, install_dir : modules_install_dir,
install_rpath: 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 endif
summary({'raop-sink (requires OpenSSL)': build_module_raop}, bool_yn: true, section: 'Optional Modules') summary({'raop-sink (requires OpenSSL)': build_module_raop}, bool_yn: true, section: 'Optional Modules')
@ -641,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') summary({'ROC': roc_dep.found()}, bool_yn: true, section: 'Streaming between daemons')
pipewire_module_rtp_source = shared_library('pipewire-module-rtp-source', 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], include_directories : [configinc],
install : true, install : true,
install_dir : modules_install_dir, install_dir : modules_install_dir,
install_rpath: 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', 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], include_directories : [configinc],
install : true, install : true,
install_dir : modules_install_dir, install_dir : modules_install_dir,
install_rpath: 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() build_module_rtp_session = avahi_dep.found()
if build_module_rtp_session if build_module_rtp_session
pipewire_module_rtp_session = shared_library('pipewire-module-rtp-session', pipewire_module_rtp_session = shared_library('pipewire-module-rtp-session',
[ 'module-zeroconf-discover/avahi-poll.c', [ 'module-rtp/stream.c',
'module-zeroconf-discover/avahi-poll.c',
'module-rtp-session.c' ], 'module-rtp-session.c' ],
include_directories : [configinc], include_directories : [configinc],
install : true, install : true,
install_dir : modules_install_dir, install_dir : modules_install_dir,
install_rpath: modules_install_dir, install_rpath: modules_install_dir,
dependencies : [pipewire_module_rtp_common_dep, avahi_dep], dependencies : [mathlib, dl_lib, rt_lib, pipewire_dep, avahi_dep, opus_dep],
) )
endif endif
@ -703,7 +685,7 @@ pipewire_module_vban_recv = shared_library('pipewire-module-vban-recv',
build_module_roc = roc_dep.found() build_module_roc = roc_dep.found()
if build_module_roc if build_module_roc
pipewire_module_roc_sink = shared_library('pipewire-module-roc-sink', 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], include_directories : [configinc],
install : true, install : true,
install_dir : modules_install_dir, install_dir : modules_install_dir,
@ -712,7 +694,7 @@ if build_module_roc
) )
pipewire_module_roc_source = shared_library('pipewire-module-roc-source', 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], include_directories : [configinc],
install : true, install : true,
install_dir : modules_install_dir, install_dir : modules_install_dir,

View file

@ -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 * graph will then be duplicated as many times to match the number of input/output
* channels of the streams. * 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 * ### Volumes
* *
* Normally the volume of the sink/source is handled by the stream software volume. * 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)" * of "Attack (s)" seconds. The noise gate stays open for at least "Hold (s)"
* seconds before it can close again. * 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 * ## SOFA filters
* *
@ -717,7 +679,6 @@ extern struct spa_handle_factory spa_filter_graph_factory;
* blocksize = ... * blocksize = ...
* tailsize = ... * tailsize = ...
* filename = ... * filename = ...
* gain = ...
* } * }
* control = { * control = {
* "Azimuth" = ... * "Azimuth" = ...
@ -737,7 +698,6 @@ extern struct spa_handle_factory spa_filter_graph_factory;
* - `tailsize` specifies the size of the tail blocks to use in the FFT. * - `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 * - `filename` The SOFA file to load. SOFA files usually end in the .sofa extension
* and contain the HRTF for the various spatial positions. * and contain the HRTF for the various spatial positions.
* - `gain` the overall gain to apply to the IR file.
* *
* - `Azimuth` controls the azimuth, this is the direction the sound is coming from * - `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 * in degrees between 0 and 360. 0 is straight ahead. 90 is left, 180
@ -1255,88 +1215,13 @@ static void capture_destroy(void *d)
impl->capture = NULL; 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) static void capture_process(void *d)
{ {
struct impl *impl = d; struct impl *impl = d;
int res; int res;
if (impl->playback) {
if ((res = pw_stream_trigger_process(impl->playback)) < 0) { if ((res = pw_stream_trigger_process(impl->playback)) < 0) {
pw_log_debug("playback trigger error: %s", spa_strerror(res)); pw_log_debug("playback trigger error: %s", spa_strerror(res));
while (impl->capture) { while (true) {
struct pw_buffer *t; struct pw_buffer *t;
if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL) if ((t = pw_stream_dequeue_buffer(impl->capture)) == NULL)
break; break;
@ -1345,15 +1230,77 @@ static void capture_process(void *d)
pw_stream_queue_buffer(impl->capture, t); pw_stream_queue_buffer(impl->capture, t);
} }
} }
} else {
do_process(impl);
}
} }
static void playback_process(void *d) static void playback_process(void *d)
{ {
struct impl *impl = 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) 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 ? struct pw_stream *s = direction == SPA_DIRECTION_OUTPUT ?
impl->playback : impl->capture; impl->playback : impl->capture;
if (s == NULL)
return;
spa_pod_builder_init(&b, buffer, sizeof(buffer)); spa_pod_builder_init(&b, buffer, sizeof(buffer));
latency = impl->latency[direction]; latency = impl->latency[direction];
spa_process_latency_info_add(&impl->process_latency, &latency); spa_process_latency_info_add(&impl->process_latency, &latency);
@ -1481,14 +1425,11 @@ static void param_tag_changed(struct impl *impl, const struct spa_pod *param,
if (param == 0 || spa_tag_parse(param, &tag, &state) < 0) if (param == 0 || spa_tag_parse(param, &tag, &state) < 0)
return; return;
if (tag.direction == SPA_DIRECTION_INPUT) { if (tag.direction == SPA_DIRECTION_INPUT)
if (impl->capture)
pw_stream_update_params(impl->capture, params, 1); pw_stream_update_params(impl->capture, params, 1);
} else { else
if (impl->playback)
pw_stream_update_params(impl->playback, params, 1); pw_stream_update_params(impl->playback, params, 1);
} }
}
static void capture_state_changed(void *data, enum pw_stream_state old, static void capture_state_changed(void *data, enum pw_stream_state old,
enum pw_stream_state state, const char *error) enum pw_stream_state state, const char *error)
@ -1564,7 +1505,8 @@ static void param_changed(struct impl *impl, uint32_t id, const struct spa_pod *
return; return;
error: 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) 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; return;
error: 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)); spa_strerror(res));
} }
@ -1652,13 +1594,12 @@ static const struct pw_stream_events out_stream_events = {
static int setup_streams(struct impl *impl) static int setup_streams(struct impl *impl)
{ {
int res; int res;
uint32_t i, n_params, *offs, flags; uint32_t i, n_params, *offs;
struct pw_array offsets; struct pw_array offsets;
const struct spa_pod **params = NULL; const struct spa_pod **params = NULL;
struct spa_pod_dynamic_builder b; struct spa_pod_dynamic_builder b;
struct spa_filter_graph *graph = impl->graph; struct spa_filter_graph *graph = impl->graph;
if (impl->capture_info.channels > 0) {
impl->capture = pw_stream_new(impl->core, impl->capture = pw_stream_new(impl->core,
"filter capture", impl->capture_props); "filter capture", impl->capture_props);
impl->capture_props = NULL; impl->capture_props = NULL;
@ -1668,9 +1609,7 @@ static int setup_streams(struct impl *impl)
pw_stream_add_listener(impl->capture, pw_stream_add_listener(impl->capture,
&impl->capture_listener, &impl->capture_listener,
&in_stream_events, impl); &in_stream_events, impl);
}
if (impl->playback_info.channels > 0) {
impl->playback = pw_stream_new(impl->core, impl->playback = pw_stream_new(impl->core,
"filter playback", impl->playback_props); "filter playback", impl->playback_props);
impl->playback_props = NULL; impl->playback_props = NULL;
@ -1680,11 +1619,18 @@ static int setup_streams(struct impl *impl)
pw_stream_add_listener(impl->playback, pw_stream_add_listener(impl->playback,
&impl->playback_listener, &impl->playback_listener,
&out_stream_events, impl); &out_stream_events, impl);
}
spa_pod_dynamic_builder_init(&b, NULL, 0, 4096); spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
pw_array_init(&offsets, 512); 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++) { for (i = 0;; i++) {
uint32_t save = b.b.state.offset; uint32_t save = b.b.state.offset;
if (spa_filter_graph_enum_prop_info(graph, i, &b.b, NULL) != 1) 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; res = -ENOMEM;
goto done; 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; res = -errno;
goto done; goto done;
} }
@ -1716,19 +1662,13 @@ static int setup_streams(struct impl *impl)
for (i = 0; i < n_params; i++) for (i = 0; i < n_params; i++)
params[i] = spa_pod_builder_deref(&b.b, offs[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 |
PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS;
if (impl->playback)
flags |= PW_STREAM_FLAG_ASYNC;
res = pw_stream_connect(impl->capture, res = pw_stream_connect(impl->capture,
PW_DIRECTION_INPUT, PW_DIRECTION_INPUT,
PW_ID_ANY, PW_ID_ANY,
flags, PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS |
PW_STREAM_FLAG_ASYNC,
params, n_params); params, n_params);
spa_pod_dynamic_builder_clean(&b); spa_pod_dynamic_builder_clean(&b);
@ -1737,23 +1677,17 @@ static int setup_streams(struct impl *impl)
n_params = 0; n_params = 0;
spa_pod_dynamic_builder_init(&b, NULL, 0, 4096); spa_pod_dynamic_builder_init(&b, NULL, 0, 4096);
}
if (impl->playback) {
params[n_params++] = spa_format_audio_raw_build(&b.b, params[n_params++] = spa_format_audio_raw_build(&b.b,
SPA_PARAM_EnumFormat, &impl->playback_info); SPA_PARAM_EnumFormat, &impl->playback_info);
flags = 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, res = pw_stream_connect(impl->playback,
PW_DIRECTION_OUTPUT, PW_DIRECTION_OUTPUT,
PW_ID_ANY, PW_ID_ANY,
flags, PW_STREAM_FLAG_AUTOCONNECT |
PW_STREAM_FLAG_MAP_BUFFERS |
PW_STREAM_FLAG_RT_PROCESS |
PW_STREAM_FLAG_TRIGGER,
params, n_params); params, n_params);
}
spa_pod_dynamic_builder_clean(&b); spa_pod_dynamic_builder_clean(&b);
done: done:
@ -1777,11 +1711,20 @@ static void graph_info(void *object, const struct spa_filter_graph_info *info)
{ {
struct impl *impl = object; struct impl *impl = object;
struct spa_dict *props = info->props; 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_inputs = info->n_inputs;
impl->n_outputs = info->n_outputs; 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++) { for (i = 0; props && i < props->n_items; i++) {
const char *k = props->items[i].key; const char *k = props->items[i].key;
const char *s = props->items[i].value; 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_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
spa_filter_graph_get_props(graph, &b.b, (struct spa_pod **)&params[0]); spa_filter_graph_get_props(graph, &b.b, (struct spa_pod **)&params[0]);
if (impl->capture)
pw_stream_update_params(impl->capture, params, 1); pw_stream_update_params(impl->capture, params, 1);
else if (impl->playback)
pw_stream_update_params(impl->playback, params, 1);
spa_pod_dynamic_builder_clean(&b); spa_pod_dynamic_builder_clean(&b);
} }

View file

@ -1157,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_MEDIA_CLASS, "Audio/Sink");
pw_properties_set(impl->sink.props, PW_KEY_PRIORITY_DRIVER, "30001"); 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_NAME, "jack_sink");
pw_properties_set(impl->sink.props, PW_KEY_NODE_DESCRIPTION, "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_MEDIA_CLASS, "Audio/Source");
pw_properties_set(impl->source.props, PW_KEY_PRIORITY_DRIVER, "30000"); 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_NAME, "jack_source");
pw_properties_set(impl->source.props, PW_KEY_NODE_DESCRIPTION, "JACK Source"); pw_properties_set(impl->source.props, PW_KEY_NODE_DESCRIPTION, "JACK Source");

View file

@ -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(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_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->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_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"); pw_properties_set(impl->source.props, PW_KEY_NODE_NAME, "netjack2_driver_receive");
if ((str = pw_properties_get(props, "sink.props")) != NULL) if ((str = pw_properties_get(props, "sink.props")) != NULL)

View file

@ -34,6 +34,10 @@
#include <spa/utils/json.h> #include <spa/utils/json.h>
#include <spa/debug/log.h> #include <spa/debug/log.h>
#ifdef HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif
#ifdef HAVE_SELINUX #ifdef HAVE_SELINUX
#include <selinux/selinux.h> #include <selinux/selinux.h>
#endif #endif
@ -41,7 +45,6 @@
#include <pipewire/impl.h> #include <pipewire/impl.h>
#include <pipewire/extensions/protocol-native.h> #include <pipewire/extensions/protocol-native.h>
#include "network-utils.h"
#include "pipewire/private.h" #include "pipewire/private.h"
#include "modules/module-protocol-native/connection.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; int fd = -1, res;
bool activated = false; bool activated = false;
#ifdef HAVE_SYSTEMD
{ {
int i, n = listen_fd(); int i, n = sd_listen_fds(0);
for (i = 0; i < n; ++i) { for (i = 0; i < n; ++i) {
if (is_socket_unix(LISTEN_FDS_START + i, SOCK_STREAM, if (sd_is_socket_unix(SD_LISTEN_FDS_START + i, SOCK_STREAM,
s->addr.sun_path) > 0) { 1, s->addr.sun_path, 0) > 0) {
fd = LISTEN_FDS_START + i; fd = SD_LISTEN_FDS_START + i;
activated = true; activated = true;
pw_log_info("server %p: Found socket activation socket for '%s'", pw_log_info("server %p: Found socket activation socket for '%s'",
s, s->addr.sun_path); 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) { if (fd < 0) {
struct stat socket_stat; struct stat socket_stat;

View file

@ -62,12 +62,8 @@ struct client {
struct pw_manager_object *metadata_schema_sm_settings; struct pw_manager_object *metadata_schema_sm_settings;
bool have_force_mono_audio; 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; struct pw_manager_object *metadata_sm_settings;
bool force_mono_audio; bool force_mono_audio;
bool bluetooth_headset_autoswitch;
uint32_t connect_tag; uint32_t connect_tag;

View file

@ -39,8 +39,6 @@
#define MODULE_INDEX_MASK 0xfffffffu #define MODULE_INDEX_MASK 0xfffffffu
#define MODULE_FLAG (1u << 29) #define MODULE_FLAG (1u << 29)
#define STREAM_CREATE_TIMEOUT (35 * SPA_NSEC_PER_SEC)
#define DEFAULT_SINK "@DEFAULT_SINK@" #define DEFAULT_SINK "@DEFAULT_SINK@"
#define DEFAULT_SOURCE "@DEFAULT_SOURCE@" #define DEFAULT_SOURCE "@DEFAULT_SOURCE@"
#define DEFAULT_MONITOR "@DEFAULT_MONITOR@" #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_NODE "target.node"
#define METADATA_TARGET_OBJECT "target.object" #define METADATA_TARGET_OBJECT "target.object"
#define METADATA_FEATURES_AUDIO_MONO "node.features.audio.mono" #define METADATA_FEATURES_AUDIO_MONO "node.features.audio.mono"
#define METADATA_BLUETOOTH_HEADSET_AUTOSWITCH "bluetooth.autoswitch-to-headset-profile"
#endif /* PULSE_SERVER_DEFS_H */ #endif /* PULSE_SERVER_DEFS_H */

View file

@ -718,7 +718,7 @@ static void on_core_error(void *data, uint32_t id, int seq, int res, const char
{ {
struct manager *m = data; 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); pw_log_debug("connection error: %d, %s", res, message);
manager_emit_disconnect(m); manager_emit_disconnect(m);
} }

View file

@ -110,59 +110,14 @@ static int core_object_force_mono_output(struct client *client, const char *para
if (spa_streq(params, "true")) { if (spa_streq(params, "true")) {
ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE, ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE,
METADATA_FEATURES_AUDIO_MONO, "Spa:String:JSON", "true"); METADATA_FEATURES_AUDIO_MONO, "Spa:String:JSON", "true");
client->force_mono_audio = true;
} else if (spa_streq(params, "false")) { } else if (spa_streq(params, "false")) {
ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE, ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE,
METADATA_FEATURES_AUDIO_MONO, "Spa:String:JSON", "false"); METADATA_FEATURES_AUDIO_MONO, "Spa:String:JSON", "false");
client->force_mono_audio = false;
} else if (spa_streq(params, "null")) { } else if (spa_streq(params, "null")) {
ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE, ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE,
METADATA_FEATURES_AUDIO_MONO, NULL, NULL); METADATA_FEATURES_AUDIO_MONO, NULL, NULL);
client->force_mono_audio = client->default_force_mono_audio;
} else { } else {
fprintf(response, "Value must be true, false, or null"); fprintf(response, "Value must be true, false, or clear");
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");
return -EINVAL; return -EINVAL;
} }
@ -190,8 +145,7 @@ static int core_object_message_handler(struct client *client, struct pw_manager_
" pipewire-pulse:log-level update log level with <params>\n" " pipewire-pulse:log-level update log level with <params>\n"
" pipewire-pulse:list-modules list all module names\n" " pipewire-pulse:list-modules list all module names\n"
" pipewire-pulse:describe-module describe module info for <params>\n" " pipewire-pulse:describe-module describe module info for <params>\n"
" pipewire-pulse:force-mono-output force mono mixdown on all hardware outputs\n" " pipewire-pulse:force-mono-output force mono mixdown on all hardware outputs"
" pipewire-pulse:bluetooth-headset-autoswitch use bluetooth headset mic if available"
); );
} else if (spa_streq(message, "list-handlers")) { } else if (spa_streq(message, "list-handlers")) {
bool first = true; 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")) { } else if (spa_streq(message, "pipewire-pulse:force-mono-output")) {
return core_object_force_mono_output(client, params, response); 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 { } else {
return -ENOSYS; return -ENOSYS;
} }

View file

@ -172,8 +172,6 @@ static int module_pipe_source_prepare(struct module * const module)
pw_properties_set(stream_props, PW_KEY_NODE_DRIVER, "true"); pw_properties_set(stream_props, PW_KEY_NODE_DRIVER, "true");
if ((str = pw_properties_get(stream_props, PW_KEY_PRIORITY_DRIVER)) == NULL) if ((str = pw_properties_get(stream_props, PW_KEY_PRIORITY_DRIVER)) == NULL)
pw_properties_set(stream_props, PW_KEY_PRIORITY_DRIVER, "50000"); 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->module = module;
d->stream_props = stream_props; d->stream_props = stream_props;

View file

@ -973,33 +973,12 @@ static void manager_metadata(void *data, struct pw_manager_object *o,
if (subject == PW_ID_CORE && o == client->metadata_routes) if (subject == PW_ID_CORE && o == client->metadata_routes)
client_update_routes(client, key, value); client_update_routes(client, key, value);
if (subject == PW_ID_CORE && o == client->metadata_schema_sm_settings) { 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; 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 (subject == PW_ID_CORE && o == client->metadata_sm_settings) {
if (spa_streq(key, METADATA_FEATURES_AUDIO_MONO)) if (spa_streq(key, METADATA_FEATURES_AUDIO_MONO))
client->force_mono_audio = spa_streq(value, "true"); 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; struct pw_manager_object *o;
bool is_monitor; bool is_monitor;
props = pw_properties_new(NULL, NULL); props = pw_properties_copy(client->props);
if (props == NULL) if (props == NULL)
goto error_errno; 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; struct pw_manager_object *o;
bool is_monitor = false; bool is_monitor = false;
props = pw_properties_new(NULL, NULL); props = pw_properties_copy(client->props);
if (props == NULL) if (props == NULL)
goto error_errno; goto error_errno;
@ -2298,7 +2277,7 @@ static int do_create_upload_stream(struct client *client, uint32_t command, uint
struct message *reply; struct message *reply;
int res; int res;
if ((props = pw_properties_new(NULL, NULL)) == NULL) if ((props = pw_properties_copy(client->props)) == NULL)
goto error_errno; goto error_errno;
if ((res = message_get(m, if ((res = message_get(m,
@ -4068,45 +4047,6 @@ static const char *get_media_name(struct pw_node_info *info)
return media_name; 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, static int fill_sink_input_info(struct client *client, struct message *m,
struct pw_manager_object *o) struct pw_manager_object *o)
{ {
@ -4167,16 +4107,10 @@ static int fill_sink_input_info(struct client *client, struct message *m,
message_put(m, message_put(m,
TAG_BOOLEAN, dev_info.volume_info.mute, /* muted */ TAG_BOOLEAN, dev_info.volume_info.mute, /* muted */
TAG_INVALID); TAG_INVALID);
if (client->version >= 13) { if (client->version >= 13)
int res; message_put(m,
struct pw_manager_object *c = NULL; TAG_PROPLIST, info->props,
if (client_id != SPA_ID_INVALID) { TAG_INVALID);
struct selector sel = { .id = client_id, .type = pw_manager_object_is_client, };
c = select_object(manager, &sel);
}
if ((res = fill_node_info_proplist(m, info->props, c)) < 0)
return res;
}
if (client->version >= 19) if (client->version >= 19)
message_put(m, message_put(m,
TAG_BOOLEAN, corked, /* corked */ 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", /* resample method */
TAG_STRING, "PipeWire", /* driver */ TAG_STRING, "PipeWire", /* driver */
TAG_INVALID); TAG_INVALID);
if (client->version >= 13) { if (client->version >= 13)
int res; message_put(m,
struct pw_manager_object *c = NULL; TAG_PROPLIST, info->props,
if (client_id != SPA_ID_INVALID) { TAG_INVALID);
struct selector sel = { .id = client_id, .type = pw_manager_object_is_client, };
c = select_object(manager, &sel);
}
if ((res = fill_node_info_proplist(m, info->props, c)) < 0)
return res;
}
if (client->version >= 19) if (client->version >= 19)
message_put(m, message_put(m,
TAG_BOOLEAN, corked, /* corked */ TAG_BOOLEAN, corked, /* corked */

View file

@ -17,12 +17,10 @@
#include <pipewire/properties.h> #include <pipewire/properties.h>
#include <pipewire/stream.h> #include <pipewire/stream.h>
#include "defs.h"
#include "format.h" #include "format.h"
#include "log.h" #include "log.h"
#include "sample.h" #include "sample.h"
#include "sample-play.h" #include "sample-play.h"
#include "internal.h"
static void sample_play_stream_state_changed(void *data, enum pw_stream_state old, static void sample_play_stream_state_changed(void *data, enum pw_stream_state old,
enum pw_stream_state state, const char *error) 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) { switch (state) {
case PW_STREAM_STATE_UNCONNECTED: case PW_STREAM_STATE_UNCONNECTED:
case PW_STREAM_STATE_ERROR: case PW_STREAM_STATE_ERROR:
pw_timer_queue_cancel(&p->timer);
sample_play_emit_done(p, -EIO); sample_play_emit_done(p, -EIO);
break; break;
case PW_STREAM_STATE_PAUSED: case PW_STREAM_STATE_PAUSED:
p->id = pw_stream_get_node_id(p->stream); p->id = pw_stream_get_node_id(p->stream);
sample_play_emit_ready(p, p->id); sample_play_emit_ready(p, p->id);
break; break;
case PW_STREAM_STATE_STREAMING:
pw_timer_queue_cancel(&p->timer);
break;
default: default:
break; 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) static void sample_play_stream_destroy(void *data)
{ {
struct sample_play *p = data; struct sample_play *p = data;
@ -180,10 +163,6 @@ struct sample_play *sample_play_new(struct pw_core *core,
if (res < 0) if (res < 0)
goto error_cleanup; 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; return p;
error_cleanup: error_cleanup:
@ -202,8 +181,6 @@ void sample_play_destroy(struct sample_play *p)
spa_hook_list_clean(&p->hooks); spa_hook_list_clean(&p->hooks);
pw_timer_queue_cancel(&p->timer);
free(p); free(p);
} }

View file

@ -11,8 +11,6 @@
#include <spa/utils/list.h> #include <spa/utils/list.h>
#include <spa/utils/hook.h> #include <spa/utils/hook.h>
#include <pipewire/pipewire.h>
struct sample; struct sample;
struct pw_core; struct pw_core;
struct pw_loop; struct pw_loop;
@ -43,7 +41,6 @@ struct sample_play {
uint32_t offset; uint32_t offset;
uint32_t stride; uint32_t stride;
struct spa_hook_list hooks; struct spa_hook_list hooks;
struct pw_timer timer;
void *user_data; void *user_data;
}; };

View file

@ -21,6 +21,9 @@
#include <netinet/ip.h> #include <netinet/ip.h>
#include <unistd.h> #include <unistd.h>
#ifdef HAVE_SYSTEMD
#include <systemd/sd-daemon.h>
#endif
#include <spa/utils/cleanup.h> #include <spa/utils/cleanup.h>
#include <spa/utils/defs.h> #include <spa/utils/defs.h>
@ -574,19 +577,26 @@ static bool is_stale_socket(int fd, const struct sockaddr_un *addr_un)
return false; 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_fd(); const int n = sd_listen_fds(0);
for (int i = 0; i < n; i++) { 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 fd;
} }
return -1; 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) 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); 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) { if (fd >= 0) {
server->activated = true; 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); server, addr_un->sun_path);
goto done; goto done;
} }

View file

@ -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 /* 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. */ * 30s and clean up its stream anyway. */
pw_timer_queue_add(stream->impl->timer_queue, &stream->timer, NULL, 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; return stream;

View file

@ -384,8 +384,10 @@ static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiPr
} }
avahi_address_snprint(at, sizeof(at), a); avahi_address_snprint(at, sizeof(at), a);
if (spa_strstartswith(at, link_local_range)) if (spa_strstartswith(at, link_local_range)) {
pw_log_info("found link-local ip address %s for '%s'", at, name); pw_log_info("found link-local ip address %s - skipping tunnel creation", at);
goto done;
}
tinfo = TUNNEL_INFO(.name = name); tinfo = TUNNEL_INFO(.name = name);
@ -412,11 +414,6 @@ static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiPr
(a->data.ipv6.address[1] & 0xc0) == 0x80) (a->data.ipv6.address[1] & 0xc0) == 0x80)
snprintf(if_suffix, sizeof(if_suffix), "%%%d", interface); snprintf(if_suffix, sizeof(if_suffix), "%%%d", interface);
/* For IPv4 link-local, bind to the discovery interface */
if (a->proto == AVAHI_PROTO_INET &&
spa_strstartswith(at, link_local_range))
snprintf(if_suffix, sizeof(if_suffix), "%%%d", interface);
pw_properties_setf(props, "raop.ip", "%s%s", at, if_suffix); pw_properties_setf(props, "raop.ip", "%s%s", at, if_suffix);
pw_properties_setf(props, "raop.ifindex", "%d", interface); pw_properties_setf(props, "raop.ifindex", "%d", interface);
pw_properties_setf(props, "raop.port", "%u", port); pw_properties_setf(props, "raop.port", "%u", port);

View file

@ -46,9 +46,6 @@
* - `remote.repair.port = <str>`: remote receiver TCP/UDP port for receiver packets * - `remote.repair.port = <str>`: remote receiver TCP/UDP port for receiver packets
* - `remote.control.port = <str>`: remote receiver TCP/UDP port for control packets * - `remote.control.port = <str>`: remote receiver TCP/UDP port for control packets
* - `fec.code = <str>`: Possible values: `disable`, `rs8m`, `ldpc` * - `fec.code = <str>`: Possible values: `disable`, `rs8m`, `ldpc`
* - `log.level = <str>`: log level for roc-toolkit. Possible values: `DEFAULT`,
* `NONE`, `ERROR`, `INFO`, `DEBUG`, `TRACE`; `DEFAULT` follows the log
* level of the PipeWire context.
* *
* ## General options * ## General options
* *
@ -78,7 +75,6 @@
* node.name = "roc-sink" * node.name = "roc-sink"
* } * }
* audio.position = [ FL FR ] * audio.position = [ FL FR ]
* log.level = DEFAULT
* } * }
* } * }
*] *]
@ -88,9 +84,8 @@
#define NAME "roc-sink" #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 #define PW_LOG_TOPIC_DEFAULT mod_topic
PW_LOG_TOPIC_EXTERN(roc_log_topic);
struct module_roc_sink_data { struct module_roc_sink_data {
struct pw_impl_module *module; 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_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); res = roc_sender_open(data->context, &sender_config, &data->sender);
if (res) { if (res) {
pw_log_error("failed to create roc sender: %d", 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; int res = 0;
PW_LOG_TOPIC_INIT(mod_topic); PW_LOG_TOPIC_INIT(mod_topic);
PW_LOG_TOPIC_INIT(roc_log_topic);
data = calloc(1, sizeof(struct module_roc_sink_data)); data = calloc(1, sizeof(struct module_roc_sink_data));
if (data == NULL) if (data == NULL)

View file

@ -56,9 +56,6 @@
* - `fec.code = <str>`: Possible values: `default`, `disable`, `rs8m`, `ldpc` * - `fec.code = <str>`: Possible values: `default`, `disable`, `rs8m`, `ldpc`
* *
* - `resampler.profile = <str>`: Deprecated, use roc.resampler.profile * - `resampler.profile = <str>`: Deprecated, use roc.resampler.profile
* - `log.level = <str>`: log level for roc-toolkit. Possible values: `DEFAULT`,
* `NONE`, `ERROR`, `INFO`, `DEBUG`, `TRACE`; `DEFAULT` follows the log
* level of the PipeWire context.
* *
* ## General options * ## General options
* *
@ -92,7 +89,6 @@
* node.name = "roc-source" * node.name = "roc-source"
* } * }
* audio.position = [ FL FR ] * audio.position = [ FL FR ]
* log.level = DEFAULT
* } * }
* } * }
*] *]
@ -102,9 +98,8 @@
#define NAME "roc-source" #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 #define PW_LOG_TOPIC_DEFAULT mod_topic
PW_LOG_TOPIC_EXTERN(roc_log_topic);
struct module_roc_source_data { struct module_roc_source_data {
struct pw_impl_module *module; 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; 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); res = roc_receiver_open(data->context, &receiver_config, &data->receiver);
if (res) { if (res) {
pw_log_error("failed to create roc receiver: %d", res); pw_log_error("failed to create roc receiver: %d", res);

View file

@ -1,58 +0,0 @@
#include <pipewire/log.h>
#include <roc/log.h>
#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));
}

View file

@ -5,7 +5,6 @@
#include <roc/endpoint.h> #include <roc/endpoint.h>
#include <spa/utils/string.h> #include <spa/utils/string.h>
#include <spa/support/log.h>
#define PW_ROC_DEFAULT_IP "0.0.0.0" #define PW_ROC_DEFAULT_IP "0.0.0.0"
#define PW_ROC_DEFAULT_SOURCE_PORT 10001 #define PW_ROC_DEFAULT_SOURCE_PORT 10001
@ -19,8 +18,6 @@
#define PW_ROC_MULTITRACK_ENCODING_ID 100 #define PW_ROC_MULTITRACK_ENCODING_ID 100
#define PW_ROC_STEREO_POSITIONS "[ FL FR ]" #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) static inline int pw_roc_parse_fec_encoding(roc_fec_encoding *out, const char *str)
{ {
if (!str || !*str || spa_streq(str, "default")) if (!str || !*str || spa_streq(str, "default"))

View file

@ -231,7 +231,7 @@ struct impl {
/* Monotonic timestamp of the last time a packet was /* Monotonic timestamp of the last time a packet was
* received. This is accessed with atomic accessors * received. This is accessed with atomic accessors
* to avoid race conditions. */ * to avoid race conditions. */
SPA_ALIGNED(8) uint64_t last_packet_time; uint64_t last_packet_time;
struct pw_timer standby_timer; struct pw_timer standby_timer;
/* This timer is used when the first stream_start() call fails because /* This timer is used when the first stream_start() call fails because

View file

@ -22,15 +22,6 @@ static void ringbuffer_clear(struct spa_ringbuffer *rbuf SPA_UNUSED,
memset(iov[1].iov_base, 0, iov[1].iov_len); 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) static void rtp_audio_process_playback(void *data)
{ {
struct impl *impl = data; struct impl *impl = data;
@ -70,9 +61,6 @@ static void rtp_audio_process_playback(void *data)
* read or write index itself.) */ * read or write index itself.) */
if (impl->direct_timestamp) { 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 /* In direct timestamp mode, the focus lies on synchronized playback, not
* on a constant latency. The ring buffer fill level is not of interest * 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 * 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 * timestamp mode, since all of them shift the timestamp by the same
* `sess.latency.msec` into the future. * `sess.latency.msec` into the future.
* *
* Since in this mode, a constant latency is not important, tracking * "Fill level" makes no sense in this mode, since a constant latency
* the fill level to keep it steady makes no sense. Consequently, * is not important in this mode, so no DLL is needed. Also, matching
* no DLL is needed. Also, matching the pace of the synchronized clock * the pace of the synchronized clock is done by having the graph
* is done by having the graph driver be synchronized to that clock, * driver be synchronized to that clock, which will in turn cause
* which will in turn cause any output sinks to adjust their DLLs * any output sinks to adjust their DLLs (or similar control loop
* (or similar control loop mechanisms) to match the pace of their * mechanisms) to match the pace of their data consumption with the
* data consumption with the pace of the driver. * 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. */
if (impl->io_position) { if (impl->io_position) {
uint32_t clock_rate = impl->io_position->clock.rate.denom; /* Use the clock position directly as the read index.
* Do NOT add device_delay here - the sink's DLL handles
/* Translate the clock position to an RTP timestamp and * matching its hardware clock to the driver pace. Adding
* shift it to compensate for device delay and ASRC delay. * device_delay would create a feedback loop since rate
* The device delay is scaled along with the clock position, * adjustments affect both ringbuffer and device buffer. */
* since both are expressed in clock sample units, while timestamp = impl->io_position->clock.position;
* pwt.buffered is expressed in stream time. */
timestamp = scale_u64(impl->io_position->clock.position + device_delay,
impl->rate, clock_rate) + pwt.buffered;
spa_ringbuffer_read_update(&impl->ring, timestamp); spa_ringbuffer_read_update(&impl->ring, timestamp);
avail = spa_ringbuffer_get_read_index(&impl->ring, &read_index);
} else { } else {
/* In the unlikely case that no spa_io_position pointer /* In the unlikely case that no spa_io_position pointer
* was passed yet by PipeWire to this node, resort to a * was passed yet by PipeWire to this node, resort to a
@ -134,50 +112,14 @@ static void rtp_audio_process_playback(void *data)
* This most likely is not in sync with other nodes, * This most likely is not in sync with other nodes,
* but _something_ is needed as read index until the * but _something_ is needed as read index until the
* spa_io_position is available. */ * spa_io_position is available. */
avail = spa_ringbuffer_get_read_index(&impl->ring, &timestamp); spa_ringbuffer_get_read_index(&impl->ring, &timestamp);
read_index = timestamp;
} }
/* 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);
/* 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, spa_ringbuffer_read_data(&impl->ring,
impl->buffer, impl->buffer,
impl->actual_max_buffer_size, impl->actual_max_buffer_size,
((uint64_t)timestamp * stride) % impl->actual_max_buffer_size, ((uint64_t)timestamp * stride) % impl->actual_max_buffer_size,
d[0].data, num_samples_to_read * stride); d[0].data, wanted * stride);
/* Clear the bytes that were just retrieved. Since the fill level /* Clear the bytes that were just retrieved. Since the fill level
* is not tracked in this buffer mode, it is possible that as soon * is not tracked in this buffer mode, it is possible that as soon
@ -189,17 +131,7 @@ static void rtp_audio_process_playback(void *data)
impl->buffer, impl->buffer,
impl->actual_max_buffer_size, impl->actual_max_buffer_size,
((uint64_t)timestamp * stride) % impl->actual_max_buffer_size, ((uint64_t)timestamp * stride) % impl->actual_max_buffer_size,
num_samples_to_read * stride); wanted * 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);
}
if (!impl->io_position) { if (!impl->io_position) {
/* In the unlikely case that no spa_io_position pointer /* 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, ((uint64_t)timestamp * stride) % impl->actual_max_buffer_size,
d[0].data, wanted * stride); 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; timestamp += wanted;
spa_ringbuffer_read_update(&impl->ring, timestamp); 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 * and not _appended_. In this example, `expected_write` would
* be 100 (since `expected_write` is the current write index), * be 100 (since `expected_write` is the current write index),
* `write` would be 90, `samples` would be 10. In this case, * `write` would be 90, `samples` would be 10. In this case,
* the (expected_write < (write + samples)) inequality does * the inequality below does not hold, so data is being
* not hold, so data is being _inserted_. By contrast, during * _inserted_. By contrast, during normal operation, `write`
* normal operation, `write` and `expected_write` are equal, * and `expected_write` are equal, so the inequality below
* so the aforementioned inequality _does_ hold, meaning that * _does_ hold, meaning that data is being appended.
* 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.
* *
* (Note that this write index update is only important if * (Note that this write index update is only important if
* the constant delay mode is active, or if no spa_io_position * the constant delay mode is active, or if no spa_io_position
* was not provided yet. See the rtp_audio_process_playback() * was not provided yet. See the rtp_audio_process_playback()
* code for more about this.) */ * code for more about this.) */
if (expected_write < (write + samples)) {
/* Compute new_write, handling potential 32-bit overflow. write += samples;
* 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;
spa_ringbuffer_write_update(&impl->ring, write); spa_ringbuffer_write_update(&impl->ring, write);
} }
} }
@ -539,27 +426,20 @@ static void rtp_audio_flush_packets(struct impl *impl, uint32_t num_packets, uin
iov[0].iov_len = sizeof(header); iov[0].iov_len = sizeof(header);
while (num_packets > 0) { while (num_packets > 0) {
uint32_t rtp_timestamp;
if (impl->marker_on_first && impl->first) if (impl->marker_on_first && impl->first)
header.m = 1; header.m = 1;
else else
header.m = 0; header.m = 0;
rtp_timestamp = impl->ts_offset + (set_timestamp ? set_timestamp : timestamp);
header.sequence_number = htons(impl->seq); 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, set_iovec(&impl->ring,
impl->buffer, impl->actual_max_buffer_size, impl->buffer, impl->actual_max_buffer_size,
((uint64_t)timestamp * stride) % impl->actual_max_buffer_size, ((uint64_t)timestamp * stride) % impl->actual_max_buffer_size,
&iov[1], tosend * stride); &iov[1], tosend * stride);
pw_log_trace("sending %d packet:%d ts_offset:%d timestamp:%u (%f s)", pw_log_trace("sending %d packet:%d ts_offset:%d timestamp:%d",
tosend, num_packets, impl->ts_offset, timestamp, tosend, num_packets, impl->ts_offset, timestamp);
(double)timestamp * impl->io_position->clock.rate.num /
impl->io_position->clock.rate.denom);
rtp_stream_emit_send_packet(impl, iov, 3); rtp_stream_emit_send_packet(impl, iov, 3);
@ -620,7 +500,6 @@ static void rtp_audio_process_capture(void *data)
uint32_t pending, num_queued; uint32_t pending, num_queued;
struct spa_io_position *pos; struct spa_io_position *pos;
uint64_t next_nsec, quantum; uint64_t next_nsec, quantum;
struct pw_time pwt;
if (impl->separate_sender) { if (impl->separate_sender) {
/* apply the DLL rate */ /* apply the DLL rate */
@ -638,8 +517,6 @@ static void rtp_audio_process_capture(void *data)
stride = impl->stride; stride = impl->stride;
wanted = size / stride; wanted = size / stride;
pw_stream_get_time_n(impl->stream, &pwt, sizeof(pwt));
filled = spa_ringbuffer_get_write_index(&impl->ring, &expected_timestamp); filled = spa_ringbuffer_get_write_index(&impl->ring, &expected_timestamp);
pos = impl->io_position; 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_resamp_delay = impl->io_rate_match->delay;
impl->sink_quantum = (uint64_t)(pos->clock.duration * SPA_NSEC_PER_SEC / rate); 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 { } else {
actual_timestamp = expected_timestamp; actual_timestamp = expected_timestamp;
next_nsec = 0; next_nsec = 0;
@ -707,8 +569,7 @@ static void rtp_audio_process_capture(void *data)
if (!impl->have_sync) { if (!impl->have_sync) {
pw_log_info("(re)sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u", pw_log_info("(re)sync to timestamp:%u seq:%u ts_offset:%u SSRC:%u",
actual_timestamp, impl->seq, impl->ts_offset, impl->ssrc); actual_timestamp, impl->seq, impl->ts_offset, impl->ssrc);
spa_ringbuffer_read_update(&impl->ring, actual_timestamp); impl->ring.readindex = impl->ring.writeindex = actual_timestamp;
spa_ringbuffer_write_update(&impl->ring, actual_timestamp);
memset(impl->buffer, 0, BUFFER_SIZE); memset(impl->buffer, 0, BUFFER_SIZE);
impl->have_sync = true; impl->have_sync = true;
expected_timestamp = actual_timestamp; expected_timestamp = actual_timestamp;

View file

@ -454,10 +454,6 @@ static int stream_stop(struct impl *impl)
* meaning that the timer was no longer running, and the connection * meaning that the timer was no longer running, and the connection
* could be closed. */ * could be closed. */
if (!timer_running) { 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); set_internal_stream_state(impl, RTP_STREAM_INTERNAL_STATE_STOPPED);
pw_log_info("stream stopped"); pw_log_info("stream stopped");
} }

File diff suppressed because it is too large Load diff

View file

@ -162,8 +162,7 @@ static const struct spa_dict_item module_props[] = {
{ PW_KEY_MODULE_VERSION, PACKAGE_VERSION }, { PW_KEY_MODULE_VERSION, PACKAGE_VERSION },
}; };
#define SERVICE_TYPE_JSONRPC "_snapcast-jsonrpc._tcp" #define SERVICE_TYPE_CONTROL "_snapcast-jsonrpc._tcp"
#define SERVICE_TYPE_CONTROL "_snapcast-ctrl._tcp"
struct impl { struct impl {
struct pw_context *context; struct pw_context *context;
@ -177,8 +176,7 @@ struct impl {
AvahiPoll *avahi_poll; AvahiPoll *avahi_poll;
AvahiClient *client; AvahiClient *client;
AvahiServiceBrowser *jsonrpc_browser; AvahiServiceBrowser *sink_browser;
AvahiServiceBrowser *ctrl_browser;
struct spa_list tunnel_list; struct spa_list tunnel_list;
uint32_t id; uint32_t id;
@ -254,10 +252,8 @@ static void impl_free(struct impl *impl)
spa_list_consume(t, &impl->tunnel_list, link) spa_list_consume(t, &impl->tunnel_list, link)
free_tunnel(t); free_tunnel(t);
if (impl->jsonrpc_browser) if (impl->sink_browser)
avahi_service_browser_free(impl->jsonrpc_browser); avahi_service_browser_free(impl->sink_browser);
if (impl->ctrl_browser)
avahi_service_browser_free(impl->ctrl_browser);
if (impl->client) if (impl->client)
avahi_client_free(impl->client); avahi_client_free(impl->client);
if (impl->avahi_poll) if (impl->avahi_poll)
@ -640,9 +636,10 @@ static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiPr
} }
avahi_address_snprint(at, sizeof(at), a); avahi_address_snprint(at, sizeof(at), a);
if (spa_strstartswith(at, link_local_range)) if (spa_strstartswith(at, link_local_range)) {
pw_log_info("found link-local ip address %s for '%s'", at, name); pw_log_info("found link-local ip address %s - skipping tunnel creation", at);
goto done;
}
pw_log_info("%s %s", name, at); pw_log_info("%s %s", name, at);
tinfo = TUNNEL_INFO(.name = name, .port = port); tinfo = TUNNEL_INFO(.name = name, .port = port);
@ -670,11 +667,6 @@ static void resolver_cb(AvahiServiceResolver *r, AvahiIfIndex interface, AvahiPr
(a->data.ipv6.address[1] & 0xc0) == 0x80) (a->data.ipv6.address[1] & 0xc0) == 0x80)
snprintf(if_suffix, sizeof(if_suffix), "%%%d", interface); snprintf(if_suffix, sizeof(if_suffix), "%%%d", interface);
/* For IPv4 link-local, bind to the discovery interface */
if (a->proto == AVAHI_PROTO_INET &&
spa_strstartswith(at, link_local_range))
snprintf(if_suffix, sizeof(if_suffix), "%%%d", interface);
pw_properties_setf(props, "snapcast.ip", "%s%s", at, if_suffix); pw_properties_setf(props, "snapcast.ip", "%s%s", at, if_suffix);
pw_properties_setf(props, "snapcast.ifindex", "%d", interface); pw_properties_setf(props, "snapcast.ifindex", "%d", interface);
pw_properties_setf(props, "snapcast.port", "%u", port); pw_properties_setf(props, "snapcast.port", "%u", port);
@ -826,13 +818,9 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda
case AVAHI_CLIENT_S_REGISTERING: case AVAHI_CLIENT_S_REGISTERING:
case AVAHI_CLIENT_S_RUNNING: case AVAHI_CLIENT_S_RUNNING:
case AVAHI_CLIENT_S_COLLISION: case AVAHI_CLIENT_S_COLLISION:
if (impl->ctrl_browser == NULL) if (impl->sink_browser == NULL)
impl->ctrl_browser = make_browser(impl, SERVICE_TYPE_CONTROL); impl->sink_browser = make_browser(impl, SERVICE_TYPE_CONTROL);
if (impl->ctrl_browser == NULL) if (impl->sink_browser == NULL)
goto error;
if (impl->jsonrpc_browser == NULL)
impl->jsonrpc_browser = make_browser(impl, SERVICE_TYPE_JSONRPC);
if (impl->jsonrpc_browser == NULL)
goto error; goto error;
break; break;
case AVAHI_CLIENT_FAILURE: case AVAHI_CLIENT_FAILURE:
@ -841,13 +829,9 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda
SPA_FALLTHROUGH; SPA_FALLTHROUGH;
case AVAHI_CLIENT_CONNECTING: case AVAHI_CLIENT_CONNECTING:
if (impl->ctrl_browser) { if (impl->sink_browser) {
avahi_service_browser_free(impl->ctrl_browser); avahi_service_browser_free(impl->sink_browser);
impl->ctrl_browser = NULL; impl->sink_browser = NULL;
}
if (impl->jsonrpc_browser) {
avahi_service_browser_free(impl->jsonrpc_browser);
impl->jsonrpc_browser = NULL;
} }
break; break;
default: default:

View file

@ -7,12 +7,6 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <net/if.h> #include <net/if.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <limits.h>
#include <sys/un.h>
#include <spa/utils/string.h>
#ifdef __FreeBSD__ #ifdef __FreeBSD__
#define ifr_ifindex ifr_index #define ifr_ifindex ifr_index
@ -137,70 +131,5 @@ static inline bool pw_net_addr_is_any(struct sockaddr_storage *addr)
return false; 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_fd(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;
}
unsetenv("LISTEN_FDS");
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 (length > 0) {
if (len < offsetof(struct sockaddr_un, sun_path) + length)
return 0;
if (memcmp(addr.sun_path, path, length) != 0)
return 0;
}
}
return 1;
}
#endif /* NETWORK_UTILS_H */ #endif /* NETWORK_UTILS_H */

View file

@ -36,6 +36,8 @@
PW_LOG_TOPIC_EXTERN(log_context); PW_LOG_TOPIC_EXTERN(log_context);
#define PW_LOG_TOPIC_DEFAULT log_context #define PW_LOG_TOPIC_DEFAULT log_context
#define MAX_HOPS 64
#define MAX_SYNC 4u
#define MAX_LOOPS 64u #define MAX_LOOPS 64u
#define DEFAULT_DATA_LOOPS 1 #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); pw_properties_set(properties, PW_KEY_CORE_NAME, context->core->info.name);
} }
SPA_EXPORT static int context_set_freewheel(struct pw_context *context, bool freewheel)
int pw_context_set_freewheel(struct pw_context *context, bool freewheel)
{ {
struct impl *impl = SPA_CONTAINER_OF(context, struct impl, this); struct impl *impl = SPA_CONTAINER_OF(context, struct impl, this);
struct spa_thread *thr; struct spa_thread *thr;
uint32_t i; uint32_t i;
int res = 0; int res = 0;
if (context->freewheeling == freewheel)
return 0;
for (i = 0; i < impl->n_data_loops; i++) { for (i = 0; i < impl->n_data_loops; i++) {
if (impl->data_loops[i].impl == NULL || if (impl->data_loops[i].impl == NULL ||
(thr = pw_data_loop_get_thread(impl->data_loops[i].impl)) == NULL) (thr = pw_data_loop_get_thread(impl->data_loops[i].impl)) == NULL)
@ -984,9 +982,468 @@ SPA_PRINTF_FUNC(7, 8) int pw_context_debug_port_params(struct pw_context *this,
return 0; 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<<direction);
if (direction == PW_DIRECTION_INPUT) {
spa_list_for_each(p, &node->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<<direction)))
continue;
if (t->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<<direction)))
continue;
if (t->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<<direction))
continue;
if (pw_strv_find_common(t->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) int pw_context_recalc_graph(struct pw_context *context, const char *reason)
{ {
struct impl *impl = SPA_CONTAINER_OF(context, struct impl, this); 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); pw_log_info("%p: busy:%d reason:%s", context, impl->recalc, reason);
@ -997,14 +1454,389 @@ int pw_context_recalc_graph(struct pw_context *context, const char *reason)
again: again:
impl->recalc = true; 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; impl->recalc = false;
if (impl->recalc_pending) { if (impl->recalc_pending) {
impl->recalc_pending = false; impl->recalc_pending = false;
goto again; goto again;
} }
return 0; return 0;
} }

View file

@ -51,7 +51,7 @@ struct pw_impl_node;
/** context events emitted by the context object added with \ref pw_context_add_listener */ /** context events emitted by the context object added with \ref pw_context_add_listener */
struct pw_context_events { struct pw_context_events {
#define PW_VERSION_CONTEXT_EVENTS 2 #define PW_VERSION_CONTEXT_EVENTS 1
uint32_t version; uint32_t version;
/** The context is being destroyed */ /** The context is being destroyed */
@ -69,9 +69,6 @@ struct pw_context_events {
void (*driver_added) (void *data, struct pw_impl_node *node); void (*driver_added) (void *data, struct pw_impl_node *node);
/** a driver was removed, since 0.3.75 version:1 */ /** a driver was removed, since 0.3.75 version:1 */
void (*driver_removed) (void *data, struct pw_impl_node *node); 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 /** Make a new context object for a given main_loop. Ownership of the properties is taken, even

View file

@ -1986,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, pw_log_trace("%p: %"PRIi64" %"PRIi64" %"PRIu64" %d/%d ", filter,
time->now, time->delay, time->ticks, time->now, time->delay, time->ticks,
time->rate.num, time->rate.denom); time->rate.num, time->rate.denom);
return 0;
return filter->state == PW_FILTER_STATE_STREAMING ? 0 : -EIO;
} }
SPA_EXPORT SPA_EXPORT

View file

@ -969,7 +969,6 @@ static void output_remove(struct pw_impl_link *this)
this->output = NULL; this->output = NULL;
} }
SPA_EXPORT
int pw_impl_link_prepare(struct pw_impl_link *this) int pw_impl_link_prepare(struct pw_impl_link *this)
{ {
struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this); struct impl *impl = SPA_CONTAINER_OF(this, struct impl, this);
@ -978,6 +977,9 @@ int pw_impl_link_prepare(struct pw_impl_link *this)
this, this->prepared, this->preparing, this, this->prepared, this->preparing,
impl->input.node->active, impl->output.node->active, this->passive); 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) if (this->destroyed || this->preparing || this->prepared)
return 0; return 0;
@ -1217,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); 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) static void node_driver_changed(void *data, struct pw_impl_node *old, struct pw_impl_node *driver)
{ {
struct impl *impl = data; struct impl *impl = data;
@ -1231,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 = { static const struct pw_impl_node_events input_node_events = {
PW_VERSION_IMPL_NODE_EVENTS, PW_VERSION_IMPL_NODE_EVENTS,
.result = input_node_result, .result = input_node_result,
.active_changed = node_active_changed,
.driver_changed = node_driver_changed, .driver_changed = node_driver_changed,
}; };
static const struct pw_impl_node_events output_node_events = { static const struct pw_impl_node_events output_node_events = {
PW_VERSION_IMPL_NODE_EVENTS, PW_VERSION_IMPL_NODE_EVENTS,
.result = output_node_result, .result = output_node_result,
.active_changed = node_active_changed,
.driver_changed = node_driver_changed, .driver_changed = node_driver_changed,
}; };

View file

@ -1595,8 +1595,6 @@ void pw_impl_port_destroy(struct pw_impl_port *port)
pw_param_clear(&impl->pending_list, SPA_ID_INVALID); pw_param_clear(&impl->pending_list, SPA_ID_INVALID);
free(port->tag[SPA_DIRECTION_INPUT]); free(port->tag[SPA_DIRECTION_INPUT]);
free(port->tag[SPA_DIRECTION_OUTPUT]); 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); pw_map_clear(&port->mix_port_map);

View file

@ -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_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_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_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_context {
struct pw_impl_core *core; /**< core object */ struct pw_impl_core *core; /**< core object */
@ -1270,8 +1269,6 @@ int pw_context_debug_port_params(struct pw_context *context,
struct spa_node *node, enum spa_direction direction, struct spa_node *node, enum spa_direction direction,
uint32_t port_id, uint32_t id, int err, const char *debug, ...); 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); 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); void pw_proxy_remove(struct pw_proxy *proxy);

View file

@ -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->dequeued.outcount, impl->dequeued.incount,
impl->queued.outcount, impl->queued.incount, impl->queued.outcount, impl->queued.incount,
avail_buffers, impl->n_buffers); avail_buffers, impl->n_buffers);
return 0;
return stream->state == PW_STREAM_STATE_STREAMING ? 0 : -EIO;
} }
SPA_EXPORT SPA_EXPORT

View file

@ -293,8 +293,7 @@ struct pw_stream_control {
* Use pw_stream_get_time_n() to get an updated time snapshot of the stream. * 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 * 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 * 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 * stream.
* return an error when called in any other state.
* *
* pw_time.ticks gives a monotonic increasing counter of the time in the graph * 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 * 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 */ /** Set control values */
int pw_stream_set_control(struct pw_stream *stream, uint32_t id, uint32_t n_values, float *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); 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 /** Get the current time in nanoseconds. This value can be compared with

View file

@ -151,7 +151,7 @@ static void test_create(void)
/* check id, only when connected */ /* check id, only when connected */
spa_assert_se(pw_stream_get_node_id(stream) == SPA_ID_INVALID); 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.now == 0);
spa_assert_se(tm.rate.num == 0); spa_assert_se(tm.rate.num == 0);
spa_assert_se(tm.rate.denom == 0); spa_assert_se(tm.rate.denom == 0);

View file

@ -120,7 +120,6 @@ struct data {
const char *media_role; const char *media_role;
const char *channel_map; const char *channel_map;
const char *format; const char *format;
const char *container;
const char *target; const char *target;
const char *latency; const char *latency;
struct pw_properties *props; struct pw_properties *props;
@ -195,6 +194,8 @@ struct data {
uint64_t samples_processed; uint64_t samples_processed;
}; };
#define STR_FMTS "(ulaw|alaw|u8|s8|s16|s32|f32|f64)"
static const struct format_info { static const struct format_info {
const char *name; const char *name;
int sf_format; int sf_format;
@ -210,35 +211,6 @@ static const struct format_info {
{ "s32", SF_FORMAT_PCM_32, SPA_AUDIO_FORMAT_S32, 4 }, { "s32", SF_FORMAT_PCM_32, SPA_AUDIO_FORMAT_S32, 4 },
{ "f32", SF_FORMAT_FLOAT, SPA_AUDIO_FORMAT_F32, 4 }, { "f32", SF_FORMAT_FLOAT, SPA_AUDIO_FORMAT_F32, 4 },
{ "f64", SF_FORMAT_DOUBLE, SPA_AUDIO_FORMAT_F32, 8 }, { "f64", SF_FORMAT_DOUBLE, SPA_AUDIO_FORMAT_F32, 8 },
{ "mp1", SF_FORMAT_MPEG_LAYER_I, SPA_AUDIO_FORMAT_F32, 1 },
{ "mp2", SF_FORMAT_MPEG_LAYER_II, SPA_AUDIO_FORMAT_F32, 1 },
{ "mp3", SF_FORMAT_MPEG_LAYER_III, SPA_AUDIO_FORMAT_F32, 1 },
{ "vorbis", SF_FORMAT_VORBIS, SPA_AUDIO_FORMAT_F32, 1 },
{ "opus", SF_FORMAT_OPUS, SPA_AUDIO_FORMAT_F32, 1 },
{ "ima-adpcm", SF_FORMAT_IMA_ADPCM, SPA_AUDIO_FORMAT_F32, 1 },
{ "ms-adpcm", SF_FORMAT_MS_ADPCM, SPA_AUDIO_FORMAT_F32, 1 },
{ "nms-adpcm-16", SF_FORMAT_NMS_ADPCM_16, SPA_AUDIO_FORMAT_F32, 1 },
{ "nms-adpcm-24", SF_FORMAT_NMS_ADPCM_24, SPA_AUDIO_FORMAT_F32, 1 },
{ "nms-adpcm-32", SF_FORMAT_NMS_ADPCM_32, SPA_AUDIO_FORMAT_F32, 1 },
{ "alac-16", SF_FORMAT_ALAC_16, SPA_AUDIO_FORMAT_F32, 1 },
{ "alac-20", SF_FORMAT_ALAC_20, SPA_AUDIO_FORMAT_F32, 1 },
{ "alac-24", SF_FORMAT_ALAC_24, SPA_AUDIO_FORMAT_F32, 1 },
{ "alac-32", SF_FORMAT_ALAC_32, SPA_AUDIO_FORMAT_F32, 1 },
{ "gsm610", SF_FORMAT_GSM610, SPA_AUDIO_FORMAT_F32, 1 },
{ "g721-32", SF_FORMAT_G721_32, SPA_AUDIO_FORMAT_F32, 1 },
{ "g723-24", SF_FORMAT_G723_24, SPA_AUDIO_FORMAT_F32, 1 },
{ "g723-40", SF_FORMAT_G723_40, SPA_AUDIO_FORMAT_F32, 1 },
{ "dwvw-12", SF_FORMAT_DWVW_12, SPA_AUDIO_FORMAT_F32, 1 },
{ "dwvw-16", SF_FORMAT_DWVW_16, SPA_AUDIO_FORMAT_F32, 1 },
{ "dwvw-24", SF_FORMAT_DWVW_24, SPA_AUDIO_FORMAT_F32, 1 },
{ "vox", SF_FORMAT_VOX_ADPCM, SPA_AUDIO_FORMAT_F32, 1 },
{ "dpcm-16", SF_FORMAT_DPCM_16, SPA_AUDIO_FORMAT_F32, 1 },
{ "dpcm-8", SF_FORMAT_DPCM_8, SPA_AUDIO_FORMAT_F32, 1 },
}; };
static const struct format_info *format_info_by_name(const char *str) static const struct format_info *format_info_by_name(const char *str)
@ -258,14 +230,6 @@ static const struct format_info *format_info_by_sf_format(int format)
return NULL; 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->name);
}
static int sf_playback_fill_x8(struct data *d, void *dest, unsigned int n_frames, bool *null_frame) static int sf_playback_fill_x8(struct data *d, void *dest, unsigned int n_frames, bool *null_frame)
{ {
sf_count_t rn; sf_count_t rn;
@ -744,34 +708,6 @@ static int parse_channelmap(const char *channel_map, struct spa_audio_layout_inf
return 0; 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) static int channelmap_default(struct spa_audio_layout_info *map, int n_channels)
{ {
switch(n_channels) { switch(n_channels) {
@ -1112,11 +1048,6 @@ enum {
OPT_CHANNELMAP, OPT_CHANNELMAP,
OPT_FORMAT, OPT_FORMAT,
OPT_VOLUME, OPT_VOLUME,
OPT_CONTAINER,
OPT_LISTFORMATS,
OPT_LISTCONTAINERS,
OPT_LISTLAYOUTS,
OPT_LISTCHANNELNAMES,
}; };
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
@ -1151,12 +1082,7 @@ static const struct option long_options[] = {
{ "rate", required_argument, NULL, OPT_RATE }, { "rate", required_argument, NULL, OPT_RATE },
{ "channels", required_argument, NULL, OPT_CHANNELS }, { "channels", required_argument, NULL, OPT_CHANNELS },
{ "channel-map", required_argument, NULL, OPT_CHANNELMAP }, { "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 }, { "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 }, { "volume", required_argument, NULL, OPT_VOLUME },
{ "quality", required_argument, NULL, 'q' }, { "quality", required_argument, NULL, 'q' },
{ "raw", no_argument, NULL, 'a' }, { "raw", no_argument, NULL, 'a' },
@ -1199,17 +1125,12 @@ static void show_usage(const char *name, bool is_error)
DEFAULT_TARGET, DEFAULT_LATENCY_PLAY); DEFAULT_TARGET, DEFAULT_LATENCY_PLAY);
fprintf(fp, fprintf(fp,
_(" --rate Sample rate (default %u)\n" _(" --rate Sample rate (req. for rec) (default %u)\n"
" --channels Number of channels (default %u)\n" " --channels Number of channels (req. for rec) (default %u)\n"
" --channel-map Channel map\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" " comma separated list of channel names: eg. \"FL,FR\"\n"
" --list-layouts List supported channel layouts\n" " --format Sample format %s (req. for rec) (default %s)\n"
" --list-channel-names List supported channel maps\n"
" --format Sample format (default %s)\n"
" --list-formats List supported sample formats\n"
" --container Container format\n"
" --list-containers List supported containers and extensions\n"
" --volume Stream volume 0-1.0 (default %.3f)\n" " --volume Stream volume 0-1.0 (default %.3f)\n"
" -q --quality Resampler quality (0 - 15) (default %d)\n" " -q --quality Resampler quality (0 - 15) (default %d)\n"
" -a, --raw RAW mode\n" " -a, --raw RAW mode\n"
@ -1218,7 +1139,7 @@ static void show_usage(const char *name, bool is_error)
"\n"), "\n"),
DEFAULT_RATE, DEFAULT_RATE,
DEFAULT_CHANNELS, DEFAULT_CHANNELS,
DEFAULT_FORMAT, STR_FMTS, DEFAULT_FORMAT,
DEFAULT_VOLUME, DEFAULT_VOLUME,
DEFAULT_QUALITY); DEFAULT_QUALITY);
@ -1752,20 +1673,17 @@ static int fill_properties(struct data *data)
return 0; 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 i, count = 0;
int format = -1; int format = -1;
const char *extension;
if (spa_streq(filename, "-")) #if __BYTE_ORDER == __BIG_ENDIAN
extension = container ? container : "au"; info->format |= SF_ENDIAN_BIG;
else if (container) #else
extension = container; info->format |= SF_ENDIAN_LITTLE;
else #endif
extension = filename;
fprintf(stderr, "%s\n", filename);
if (sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) != 0) if (sf_command(NULL, SFC_GET_FORMAT_MAJOR_COUNT, &count, sizeof(int)) != 0)
count = 0; count = 0;
@ -1777,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) if (sf_command(NULL, SFC_GET_FORMAT_MAJOR, &fi, sizeof(fi)) != 0)
continue; continue;
if (spa_strendswith(extension, fi.extension)) { if (spa_strendswith(filename, fi.extension)) {
format = fi.format; format = fi.format;
break; 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) 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) if (format == SF_FORMAT_WAV && info->channels > 2)
format = SF_FORMAT_WAVEX; 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; info->format |= format;
}
static void list_containers(struct data *d) if (format == SF_FORMAT_OGG || format == SF_FORMAT_FLAC)
{ info->format = (info->format & ~SF_FORMAT_ENDMASK) | SF_ENDIAN_FILE;
int i, count = 0; if (format == SF_FORMAT_OGG)
info->format = (info->format & ~SF_FORMAT_SUBMASK) | SF_FORMAT_VORBIS;
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);
}
} }
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
@ -1961,7 +1834,7 @@ static int setup_sndfile(struct data *data)
info.samplerate = data->rate; info.samplerate = data->rate;
info.channels = data->channels; info.channels = data->channels;
info.format = fi->sf_format; 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, data->sndfile.file = sf_open(data->filename,
@ -2322,9 +2195,6 @@ int main(int argc, char *argv[])
case OPT_FORMAT: case OPT_FORMAT:
data.format = optarg; data.format = optarg;
break; break;
case OPT_CONTAINER:
data.container = optarg;
break;
case OPT_VOLUME: case OPT_VOLUME:
if (!spa_atof(optarg, &data.volume)) if (!spa_atof(optarg, &data.volume))
@ -2336,18 +2206,6 @@ int main(int argc, char *argv[])
case 'c': case 'c':
data.data_type = TYPE_MIDI2; data.data_type = TYPE_MIDI2;
break; 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: default:
goto error_usage; goto error_usage;
} }

View file

@ -780,7 +780,7 @@ static void show_help(const char *name, bool error)
" -N, --no-colors disable color output\n" " -N, --no-colors disable color output\n"
" -C, --color[=WHEN] whether to enable color support. WHEN is `never`, `always`, or `auto`\n" " -C, --color[=WHEN] whether to enable color support. WHEN is `never`, `always`, or `auto`\n"
" -o, --hide-props hide node properties\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", " -p, --print-separator print empty line after every event to help streaming parser\n",
name); name);
} }

View file

@ -29,7 +29,6 @@ PWTEST(context_abi)
void (*global_removed) (void *data, struct pw_global *global); void (*global_removed) (void *data, struct pw_global *global);
void (*driver_added) (void *data, struct pw_impl_node *node); void (*driver_added) (void *data, struct pw_impl_node *node);
void (*driver_removed) (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 }; } test = { PW_VERSION_CONTEXT_EVENTS, NULL };
pw_init(0, NULL); pw_init(0, NULL);
@ -41,9 +40,8 @@ PWTEST(context_abi)
TEST_FUNC(ev, test, global_removed); TEST_FUNC(ev, test, global_removed);
TEST_FUNC(ev, test, driver_added); TEST_FUNC(ev, test, driver_added);
TEST_FUNC(ev, test, driver_removed); 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)); pwtest_int_eq(sizeof(ev), sizeof(test));
pw_deinit(); pw_deinit();