Compare commits

...

248 commits

Author SHA1 Message Date
Dimitris Papaioannou
1f2a5d99b1 module-eq: Unload filter-chain on destruction
Make the parametric-equalizer module destroy the underlying filter-chain
module on destruction. This makes the EQ nodes get destroyed on unload.
Fixes #5045
2025-12-26 18:53:48 +00:00
Barnabás Pőcze
29bbd79830 gst: deviceprovider: unregister pw_core event hooks
A `pw_core` may be shared between multiple streams, device provider
instances, thus when the reference of the given component to the core
is dropped, the event handlers must be unregistered so as to avoid
use-after-free and similar issues.

Fixes #5030
Fixes: 2bc3e0ca10 ("gst: deviceprodiver: Use GstPipeWireCore and some cleanups")
2025-12-25 14:57:21 +01:00
Wim Taymans
3738c3fc38 tools: only print latency when we print the port
Move the latency print code after where we print the port. That way
we only get the latency when we first print the port.

Avoid -lt from printing latencies for ports without a link.
2025-12-20 18:02:36 +01:00
Robert Rosengren
165bd7b219 pipewiresrc: fix race when node suspended moving from PAUSED to PLAYING
If in PAUSED state, the node can move from idle to suspended resulting
in format cleared and state is no longer negotiated. To avoid returning
not-negotiated error upon basesrc calling create callback, wait for new
format to be provided and negotiated state is back.
2025-12-19 07:15:08 +00:00
Frédéric Danis
e15e50c5ee spa: bluez: backend-native: Prevent HSP/HFP connection in both directions
The HSP and HFP profiles expect that a device function only as an audio
gateway or as an headset, which is the normal behavior for a headset,
a hands-free car unit or a phone.

In case of a desktop, it can perform both functionalities, but there's
no interest to get them at the same time as the bidirectional audio
is already supported.
2025-12-19 07:12:46 +00:00
Frédéric Danis
f468529084 spa: bluez: backend-native: Fix audio connection policy for HSP/HFP 2025-12-19 07:12:46 +00:00
Arun Raghavan
385161b12a pulse-server: Add a message to enable/disable mono mixdown
WirePlumber recently added a mechanism to force mono mixdown on audio
outputs, which is a useful feature for accessibility. Let's also expose
that setting via libpulse for existing audio settings UIs to be able to
use.
2025-12-18 16:35:35 -08:00
Martin Geier
8c7890eb52 spa: fix missing member initialization
Signed-off-by: Martin Geier <martin.geier@streamunlimited.com>
2025-12-18 16:53:54 +01:00
Arun Raghavan
e2262617aa spa: alsa: Fix off-by-one check in ELD channel position parsing 2025-12-17 11:08:44 -08:00
Jonas Holmberg
12b2e5d67c audiotestsrc: Operate as follower too
Don't start the timer but fill buffers when following another driver.
2025-12-16 13:15:00 +01:00
Arun Raghavan
ee42b18226 spa: alsa: Guard against mismatched LPCM channel count in ELD parsing
With wonky EDID states, we want to make sure we don't overflow the
available positions.
2025-12-15 12:47:49 -08:00
Frédéric Danis
d89d1668dc spa: bluez: backend-native: Add support for AT+BLDN for PTS tests
This allows to fake Last Dial Number call by calling the first memory.

This allows to pass PTS tests HFP/AG/OCL/BV-01-C and HFP/AG/OCL/BV-02-C.
2025-12-15 08:56:03 +00:00
Frédéric Danis
9a48bbaa36 spa: bluez: modemmanager: Add support for memory dialing for PTS tests
This add a new property to to allow to fake memory dialing for PTS tests
HFP/AG/OCM/BV-01-C and HFP/AG/OCM/BV-02-C.
2025-12-15 08:56:03 +00:00
Frédéric Danis
04cf29f7cd doc: Add property documentation for bluez5.disable-dummy-call 2025-12-15 08:56:03 +00:00
Wim Taymans
bb564d5eb6 avb: fix compilation 2025-12-15 09:27:10 +01:00
Janne Grunau
f03021edd1 stream: Fix pw_time.delay calculation for rate.num > 1
Pipewire uses a rate of 256/7680 with the integrated camera of Apple
silicon Macbooks. To calculate pw_time.delay correctly in this case it
has to be divided by time->rate.num. Without this division the delay
contribution of the `((latency->min_ns + latency->max_ns) / 2)` term
ends up as 255 which are 8.5 seconds.
pipewiresrc reports the delay as latency in the gstreamer pipeline which
results in rendering a frame every 8.5 seconds.
I suspect the non-normalized rate of 256/7680 is another bug in
pipewire. The rate for an UVC webcam is reported as 1/30. Both
Video4Linux2 devices report a discrete frame interval of 0.033s (30fps).

Fixes #4957
2025-12-15 08:22:07 +00:00
Mason Remaley
c7ebc66e64 Adds explanation to reduce chance of regressing the fix 2025-12-15 08:20:24 +00:00
Mason Remaley
a6f8e209ac These two functions were marked as static, but referened by the SSE41 implementation in a separate file
I'm not 100% sure if this was breaking SSE41 builds on the official build system (I'm building Pipewire
with a different process), but I suspect it was, because you can't combine these into a single translation
unit to sidestep it without including multiple copies of resample-native-impl.h which isn't desirable.
2025-12-15 08:20:24 +00:00
hackerman-kl
6f1938d501 milan-avb: milan: adding set/get clock-source for a clock-domain 2025-12-15 08:18:30 +00:00
hackerman-kl
bb1ef8ea5e module-avb: milan: introducing full entity model for mlian v1.2 2025-12-15 08:17:50 +00:00
hackerman-kl
b22e442b10 module-avb: milan: adding get/set for configuration 2025-12-15 08:17:50 +00:00
Tyler
43bf1b8f7c module-rt: warn if setting niceness fails with rtlimit 2025-12-11 16:38:00 -08:00
hackerman-kl
ba8c6154a0 milan-avb: silent gcc warning as the variable will be used 2025-12-11 08:13:20 +01:00
Wim Taymans
548f26882f avb: fix compilation 2025-12-10 11:33:06 +01:00
hackerman-kl
63abd4e71c milan-avb: cmds-get-set-name: fix unused variable warning 2025-12-10 07:11:16 +01:00
hackerman-kl
c2ada3175e module-avb: aecp-aem: SET/GET STREAM_FORMAT answer implemented.
In the current state the GET/SET stream format can handle the commands
response however, yet, it does not take care of checking that:

 * A bound input stream cannot have it set, should reply accordingly
 * A STREAMING_STREAM output stream cannot have it set, should reply
   accordingly.
2025-12-10 07:07:24 +01:00
Wim Taymans
8153efc6ed audioconvert: refactor some code
We sync the filter graph in two places, make a function so that both
places do the same thing.

Make node_reset clear the setup flag so that we don't have to do that
twice.
2025-12-09 21:01:11 +01:00
Torkel Niklasson
40aa6fbb64 audioconvert: Sync filter graphs in setup_convert
If the the audioconvert.filter-chain.N property is set early, they will
be added to the active_graphs list but with setup = false. When the node
starts, setup_convert is called, but the graphs aren't added to
filter_graphs. Run the do_sync_filter_graph at the end of setup_convert
to add them.
2025-12-09 19:52:12 +00:00
Torkel Niklasson
34122b4bf3 audioconvert: Set this->setup to false on flush command
If flush is called, the active graphs are deactivated but this->setup
remains true.
2025-12-09 19:52:12 +00:00
hackerman-kl
d9fa0629f6 milan-avb: milan: adding set/get name command handler 2025-12-09 19:40:59 +00:00
Frédéric Danis
25a6fdcdb1 spa: bluez: device: Add SPA_PROP_params to disable dummy call state
The current implementation only send the +CIEV:<call>,<active> event
if there's an active modem in ModemManager. This may lead to headset
disconnection as in (1) if the profile is by another application than
telephony one, e.g. a conference application/website.

This commit improves dummy call status update by adding a new
"bluez5.disable-dummy-call" props param in bluez5 device, allowing
external application like WirePlumber to set it dynamically.

(1) https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/1744

Fixes: https://gitlab.freedesktop.org/pipewire/pipewire/-/merge_requests/2606
2025-12-09 16:08:31 +01:00
Wim Taymans
7aa8d8d628 spa-resample: enable clipping in sndfile
We can actually use a command to enable clipping in sndfile instead
of doing it ourselves.
2025-12-09 13:55:37 +01:00
Wim Taymans
8e6945c496 spa-resample: clamp float values
sndfile does not seem to correctly clip samples so we have to do it
ourselves.
2025-12-09 13:38:46 +01:00
Timon
e1392cec0e Fix disabling of filter chains 2025-12-08 14:19:15 +01:00
hackerman-kl
13def13f01 module-avb: milan: aecp-aem handle de/registration for unsolicited notification, and add avb info common 2025-12-07 16:08:36 +00:00
hackerman-kl
ea653a52e3 module-avb: milan: lock command handles unsolicited notifications 2025-12-07 16:08:36 +00:00
hackerman-kl
6054c1a12b module-avb: introducing unsolicited notification reply 2025-12-07 16:08:36 +00:00
hackerman-kl
b43d915e71 module-avb: milan: make lock state as part of the generic entity state structure 2025-12-07 16:08:36 +00:00
hackerman-kl
4f8f7980f0 module-avb: milan: add default CONTROL_DATA_OFFSET for aem parsing 2025-12-07 16:08:36 +00:00
hackerman-kl
a6d7e98db3 module-avb: milan: introducing controller deregister unsolicited notification 2025-12-07 16:08:36 +00:00
hackerman-kl
ad43eba25c module-avb: milan: introducing controller registered unsolicited notifications 2025-12-07 16:08:36 +00:00
hackerman-kl
a97abf10ab module-avb: state variable entity id name fixing 2025-12-07 16:08:36 +00:00
hackerman-kl
86168ab1e2 module-avb: aecp-aem: move type into their own dedicated header file 2025-12-07 16:07:56 +00:00
hackerman-kl
2f83c5dab5 module-avb: descriptors: adding control specific defines 2025-12-07 16:07:56 +00:00
hackerman-kl
63a37e4947 module-avb: descriptors: introducing control and value format used by control descriptors 2025-12-07 16:07:56 +00:00
hackerman-kl
a1a33141d7 module-avb: descriptors: introducing clock domain descriptor 2025-12-07 16:07:56 +00:00
hackerman-kl
82fe584f51 module-avb: descriptors: introducing port flags 2025-12-07 16:07:56 +00:00
hackerman-kl
b90bd2c528 module-avb: descriptors: adding mapping and audio mapping format 2025-12-07 16:07:56 +00:00
hackerman-kl
43448f147c module-avb: descriptors: adding cluster descriptor for milan and legacy AVB 2025-12-07 16:07:56 +00:00
hackerman-kl
1b39e7836d module-avb: state: fix header includes 2025-12-07 16:07:56 +00:00
filmsi
034e8683c8 Updated sl.po 2025-12-07 08:00:37 +00:00
Pauli Virtanen
2942bae034 bluez5: parse and enable configuration of TMAP / GMAP features
Parse TMAP / GMAP features from MediaEndpoint:SupportedFeatures and pass
them onto the codec in SelectProperties, so it can determine which
mandatory features the device supports.

Add configuration option for specifying which TMAP / GMAP feature bits
we advertise to remote side.

Although some of these could be determined automatically, for production
systems it's better to have explicit option to specify which ones should
be advertised as this may depend on HW capabilities.
2025-12-06 11:23:48 +00:00
Pauli Virtanen
c623886625 bluez5: fix parsing of 0-element dbus arrays
Type checking by recurse + get_arg_type is wrong for 0-element arrays.
Just check from iterator signature.

Also avoid relying on malloc(0) != NULL
2025-12-06 11:21:56 +00:00
hackerman-kl
f65d5654d3 module-avb: milan: acquiring not supported directly called 2025-12-06 08:34:19 +01:00
hackerman-kl
198f4a92f5 module-avb: milan: introducing direct reply calls 2025-12-06 08:32:32 +01:00
Wim Taymans
57e589f2e1 stream: avoid flushing invoke before state change
A flushing invoke is dangerous because the application might have queued
a destroy, which could then be executed right while we do things.

Avoid doing the flushing invoke from the state change function. This was
done because previously we would invoke a process call when we were
working in non-RT mode. Nowadays we run the process function right from
the main thread and we don't need to invoke anymore. This also means
that we can't have pending process calls to flush out when we go to
paused. We do queue other calls, like drained and trigger-done but it
should not cause problems to let those through after the state change.
If this causes problems in the future, we can check the state before
emiting them.

Do a flushing invoke right before freeing the stream. This should be ok
because we removed all signal hooks so that the pending invokes would
not get to the app.

Fixes #5010
2025-12-04 10:16:33 +01:00
Wim Taymans
e30ee9c846 tools: support filenames in raw mode
The raw mode -a only supported reading raw data from stdin/stdout and
simply ignored the filename. Make it use the filename to determine
where to read from instead.

Support stdin/stdout for sysex mode as well and close the file when we
are done.

Fixes #5012
2025-12-04 10:13:13 +01:00
Wim Taymans
b68698a086 stream: refactor the free function
In the destroy we first remove all listeners and then we clean up the
memory. Move the memory cleanup to a separate function to make it easier
to refcount later.
2025-12-02 12:37:59 +01:00
Wim Taymans
0d7cb9b39f client-node: remove the MAPPABLE flag for MemPtr data
af3ad7bf9f set the fd and MAPPABLE
flag on buffer data when it was allocated inline in shared mem, even
for MemPtr data. In the client-node however we don't pass along this
fd and so we also should not pass the MAPPABLE flag for MemPtr data.

This fixes an issue in older clients that blindly try to map the fd when
the MAPPABLE flag is set, even when there is no fd (and there is already
a data pointer).

Fixes #5003
2025-12-02 12:07:47 +01:00
Mason Remaley
c81ee31c3b Fixes parameter type that trips ubsan 2025-12-02 00:52:57 +00:00
Wim Taymans
a172bf0f55 audiomixer: only passthrough on dynamic data
When the dynamic data flag is set on the buffer data, it means the
consumer can deal with any data pointer set on the buffer and we can
simply pass the one from upstream to downstream. If the flag is not set,
we need to copy the buffer data.

See #5009
2025-12-01 16:33:50 +01:00
Wim Taymans
4f39329ca9 alsa: use graph rate for rate latency
When we recalculate the headroom we also update the latency in frames.
We should express this latency in the graph rate. This is usually the
rate that is suggested in the target_rate but when we are forcing our
own rate (mostly when using IRQ or when in DSD/IEC mode) we should
ignore that value and use our own rate that we will force instead.

Fixes #4977
2025-12-01 14:20:12 +01:00
Wim Taymans
4152c5d292 alsa: add firewire latency before we scale with rate
We first add all the latency in the rate of the pcm device and then
convert it to the rate of the graph.

See #4977
2025-12-01 12:52:37 +01:00
hackerman-kl
52f2137397 module-avb: milan: aecp-aem: introducing available command handler 2025-12-01 09:16:06 +00:00
hackerman-kl
6619aba582 module-avb: milan: aecp-aem: adding lock-entity handler 2025-12-01 09:16:06 +00:00
hackerman-kl
1aacf8d15a module-avb: milan: aecp-aem: introducing the response to the lock-entity command 2025-12-01 09:16:06 +00:00
hackerman-kl
93b59609a8 module-avb: aecp-aem: invalid helper response status return fixed 2025-12-01 09:16:06 +00:00
hackerman-kl
e7c7b5058d module-avb: milan: aecp-aem: introducing entity_lock response helper 2025-12-01 09:16:06 +00:00
hackerman-kl
986254f56f module-avb: milan: es_builder: introducing entity milan descriptor with necessary information about the state of the descriptor 2025-12-01 09:16:06 +00:00
hackerman-kl
32ceb47937 module-avb: aecp-aem: adding struct base_info. The structure provides
information about the controller that last accessed, the time when when
it actually accessed, and the expiring time if a timer has to be
implemented
2025-12-01 09:16:06 +00:00
hackerman-kl
354006a699 module-avb: aecp-aem: adding available pdu 2025-12-01 09:16:06 +00:00
hackerman-kl
17812c33cc module-avb: aecp-aem: adding entity state for legacy AVB and milan 2025-12-01 09:16:06 +00:00
hackerman-kl
2673558a52 module-avb: aecp-aem: add Milan specific aecp aem into its own header 2025-12-01 09:16:06 +00:00
hackerman-kl
a1b829997e module-avb: es_builder: setup the callback count to use only necessary
memory
2025-12-01 09:16:06 +00:00
hackerman-kl
bcf6b185d7 module-avb: es_builder: splitting avb/milan es_builder 2025-12-01 09:16:06 +00:00
hackerman-kl
8e870c809c module-avb: aecp-aem: introducing seperation between milan v1.2 and legacy milan.
This introduces the following changes:

 * Using the time at which the command was received
 * Preparation for the unsollicited notifications
 * New folder holding all the AECP_AEM commands/responses and utils
 * Improving the code-reusability by using common handlers
2025-12-01 09:16:06 +00:00
hackerman-kl
5eea411a3c module-avb: avdecc: use enum instead of a boolean for avb mode (milan vs legacy avb) 2025-12-01 09:16:06 +00:00
hackerman-kl
8e135c1015 module-avb: utils: introduce array size for static arrays 2025-12-01 09:16:06 +00:00
hackerman-kl
cdf1ebe861 module-avb: aecp-aem: moving responses status in their own file 2025-12-01 09:16:06 +00:00
hackerman-kl
99a131a91d module-avb: aecp-aem: introducing seperation between milan v1.2 and legacy milan.
This introduces the following changes:

 * Using the time at which the command was received
 * Preparation for the unsollicited notifications
 * New folder holding all the AECP_AEM commands/responses and utils
 * Improving the code-reusability by using common handlers
2025-12-01 09:15:07 +00:00
hackerman-kl
929ac1f09f module-avb: avdecc: provide stringifier of avb enum 2025-12-01 09:15:07 +00:00
hackerman-kl
f3d0642994 module-avb: avdecc: use enum instead of a boolean for avb mode (milan vs legacy avb) 2025-12-01 09:15:07 +00:00
hackerman-kl
9f1c11ac34 module-avb: aecp-aem: moving responses status in their own file 2025-12-01 09:15:07 +00:00
hackerman-kl
c48e835d0c module-avb: internal: make sure that the descriptor are modifiable at runtime 2025-12-01 09:08:26 +00:00
hackerman-kl
af62143327 module-avb: remove duplicated string 2025-12-01 09:08:26 +00:00
hackerman-kl
b60623df4d module-avb: internal: destroy internal descriptors 2025-12-01 09:08:26 +00:00
hackerman-kl
a3ce0f3e28 module-avb: avdecc: destroy stream 2025-12-01 09:08:26 +00:00
hackerman-kl
c10f869836 module-avb: mrp: cleaning allocated attribute on destroy 2025-12-01 09:08:26 +00:00
hackerman-kl
c1dbba1a31 module-avb: acmp: cleaning pending allocated resources on destroy 2025-12-01 09:08:26 +00:00
hackerman-kl
8d99bf66bd module-avb: adp: clean the allocated resources if any 2025-12-01 09:08:26 +00:00
Wim Taymans
2ff45313de resample: allow compilation with custom default quality 2025-12-01 09:52:54 +01:00
Wim Taymans
52ec847cbd filter-graph: add feedback and feedforward to delay
Add feedback and feedforward controls to the delay. This makes it
possible to make comb and allpass filters with the delay to build
custom reverb effects.
2025-11-28 17:10:55 +01:00
Wim Taymans
933ac4be43 doc: use parblock 2025-11-28 11:53:07 +01:00
Wim Taymans
8c698366b8 doc: decreasing the transition band increases ringing 2025-11-28 11:42:16 +01:00
hackerman-kl
21bb281c75 module-avb: internal: the add descriptor return the descriptor 2025-11-28 10:12:32 +00:00
hackerman-kl
831357ee88 module-avb: es_builder: fix invalid return check 2025-11-28 10:12:32 +00:00
hackerman-kl
f30a0c1864 module-avb: milan: adding mode selection 2025-11-28 10:11:07 +00:00
hackerman-kl
76f8ebb1f2 module-avb: configuration adding milan boolean 2025-11-28 10:11:07 +00:00
Wim Taymans
a13d5eeccb doc: improve resampler properties docs a little 2025-11-28 11:10:00 +01:00
hackerman-kl
875dd91bc2 module-avb: Introduce changes in the mechanisms how the stream are
built:
* es_builder: create stream with state variables and counters
* acmp: do not use the stream list, go through the descriptor to find
  the index
* stream: do not store redundant information such as the index and
  descriptor
* internal: removing the stream server and function associated to it

module-avb: internal, stream: removing server_find_stream
2025-11-27 17:47:28 +00:00
hackerman-kl
546dafa0b0 module-avb: adding state base and stream specific information 2025-11-27 17:47:28 +00:00
hackerman-kl
a88b4bfecd module-avb: descriptor may be modified 2025-11-27 17:47:28 +00:00
hackerman-kl
8593235571 module-avb: init descriptor happens after everything is set up 2025-11-27 17:47:28 +00:00
Wim Taymans
98b4693525 1.5.84 2025-11-27 13:50:37 +01:00
Wim Taymans
94c05e9e2d spa: make sure unpositioned raw audio has unknown channels
Otherwise we might end up with partial channels when code doesn't
check the unpositioned flag.  It's better to set everything to unknown
when there is a mismatch between channel count and layout.
2025-11-27 11:45:21 +01:00
Wim Taymans
172a2af982 pw-cat: move optstring closer to the longopts definition 2025-11-26 15:23:33 +01:00
lumingzh
1efa2bda30 update Chinese translation 2025-11-26 08:33:10 +00:00
Frédéric Danis
6ced56e11d spa: bluez: backend-native: Fix BIEV HF indicators support for HFP AG
The "+BIND: <a>,<state>" reply to AT+BIND? should be sent for every
supported indicator.

"AT+BIEV= <assigned number>,<value>" should only be provided for
enabled indicators. The AG shall respond with an ERROR response code
if it receives updates for disabled or unknown HF indicators or values
that are out of bounds.

This allows to pass PTs tests:
- HFP/AG/HFI/BV-02-C
  AG receives an updated HF Indicator value
- HFP/AG/HFI/BI-03-C
  AG receives invalid updated HF Indicator values
2025-11-25 16:20:02 +01:00
Wim Taymans
1408dd5245 tools: use audio_layout in pw-cat
Use the standard layout names for channel map option. Provide aliases
for the old names.

Make sure we don't have too many channels for DSD.
2025-11-25 14:01:48 +01:00
Pauli Virtanen
3b6609f13a bluez5: release transports in CIG/BIG simultaneously
When releasing multiple transports, call Release() simultaneously
instead of serializing the calls.

This operations still needs to be blocking currently, as all releases
have to finish before we do other state-modifying ops.

This works around broken firmware on Creative Zen Hybrid Pro with BAP,
whose Disable command misbehaves when shutting down sink + source CIS
otherwise. It's also anyway better to shut down everything at once.
2025-11-25 09:23:13 +00:00
Wim Taymans
2c6aa8e0d0 pulse-server: only use passive for devices
The dont-inhibit-auto-suspend flag does not do anything when using
direct-on-input-idx (capturing from a stream) in pulseaudio, so also
make it do nothing on pulse-server.

See #4991
2025-11-25 10:17:14 +01:00
Wim Taymans
fc26e6321b spa: make cutoff configurable in spa-resample 2025-11-24 16:49:25 +01:00
Wim Taymans
ed2889cecf filter-graph: improve latency reporting of convolver
The latency of the convolver depends on the IR used. It's 0 for /dirac,
len/2 for /hilbert and let's assume it is 0 for file IRs.

Fixes #4980
2025-11-24 13:56:18 +01:00
Wim Taymans
33c7d9cba5 audioconvert: move warn to debug
changing the clock rate is not uncommon and does not need a warning.
2025-11-24 11:03:22 +01:00
Wim Taymans
7d5940101b resample: tweak cutoff some more
Increase the cutoff frequency for the lower quality modes. This should
give significantly better high frequency preservation at the expense of
more (but likely inaudible) aliasing.

Reduce the cutoff for the higher qualities in blackman and exp to
compensate the the wider transition band in those windows.

Increase cutoff for kaiser because of the sharper rolloff.
2025-11-24 10:35:53 +01:00
Wim Taymans
4760fd7f52 resample: tweak kaiser cutoff a little more 2025-11-21 11:42:34 +01:00
Wim Taymans
3f292e3ce3 stream: generate PeerCapbility for old servers
Automatically make an empty PeerCapability param when we receive a
Latency event without a PeerCapability. This makes new client always
receive a PeerCapability param, even when the other side did not provide
anything or when the server is too old to collect them.
2025-11-21 10:19:36 +01:00
Wim Taymans
941fc5f51c spa: add Capability and PeerCapability
Add a new SPA_TYPE_OBJECT_ParamDict object that contains a struct with
key/value pairs. We're using something similar for Tags but this is a
more generic version.

Make a new Capability param that uses the ParamDict object. This is
meant to be used to describe capabilities with the generic key/value
struct.

Make a new PeerParam object where the keys are generic ids of the peer
objects and the values any Pod. The idea is to use this object to store
a peer objects. Make some helpers to iterate the peers and their
objects.

Add a new PeerCapability param that uses the PeerParam object with
Capability objects. This can be used to send the collection of
Capabilities from all peers to a port. This is a bit like the tags but
in a more generic way. The tags could also be implemented in this new
generic way later.

Make the PeerFormats use the same PeerParam of Format objects.

The Capability param is set on ports. impl-link will collect all
Capability objects into a PeerCapability object and send this to the
peer. The difference with the Tag param is that these Capability params
are not in any way forwared on the node automatically (like what is done
in the loopback module) because they represent the capabilities of the
ports betweem the link.
2025-11-21 10:08:46 +01:00
Pauli Virtanen
8d59ad2713 bluez5: set some BAP Context metadata value on streams
Some devices refuse to enable microphone if Streaming Context metadata
is just Unspecified.

Set some reasonable values for the stream context we create along TMAP,
and try follow CAP rules for selecting the PAC.
2025-11-21 08:33:14 +00:00
Pauli Virtanen
2010a525d3 bluez5: simplify ltv parsing 2025-11-21 08:33:14 +00:00
Pauli Virtanen
ff6db3e08e bluez5: add codec_data for codec-private configuration data
With BAP codec configuration selection goes via multiple functions,
which will need to maintain some private state.

Adjust media_codec to allow for that.

Use it for get_qos().
2025-11-21 08:33:14 +00:00
Pauli Virtanen
914e8c6c7a bluez5: parse BAP endpoint Metadata field 2025-11-21 08:33:14 +00:00
Pauli Virtanen
bf801f4f7f bluez5: clean up LTV writing
Add ltv_writer that checks bounds, and use it. Simplify code a bit.
2025-11-21 08:33:14 +00:00
Frédéric Danis
f9f08f7f5c spa: bluez: backend-native: Fix CIEV call status support for HFP AG
Based on HFP specs, the audio connection is independent of the active
call status, which should be managed by the ModemManager part of the
plugin.
But when using HFP AG without modem attached, e.g. during zoom meeting,
the connection will be closed after a while unless call status has been
forced to active,
cf. https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/1744.

Currently and for HFP AG PTS tests requesting to get an audio connection
in 3 seconds after a call activates, this prevent to start audio
connection before starting a call.

This commit prevents to force the call status during audio (dis)connection
if a modem is available.
2025-11-21 08:27:44 +00:00
Frédéric Danis
b940d9f3a1 spa: bluez: modemmanager: Fix NameOwnerChanged
Get the ModemManager interfaces when the ModemManager starts after native
HFP has started.

Also add log topic to be able to select log level for modemmanager plugin
part.
2025-11-21 08:26:40 +00:00
hackerman-kl
3372d8f102 module-avb: descriptor: moving to endstation builder (i.e es_builder) 2025-11-21 08:26:15 +00:00
hackerman-kl
55e4c7e4cb module-avb: descriptor: adding todo for simplification 2025-11-21 08:26:15 +00:00
hackerman-kl
57af462ecf modules-avb: Introducing entity builder. The entity builder is necessary
to attach ressource to the descriptors instead of having them splitted.

It is the case for the avb-streams which in a seperated list. Instead they
should be encapsulated within the descriptor itself, as one cannot leave
without the other.
2025-11-21 08:26:15 +00:00
hackerman-kl
8ea56477d9 module-avb: aecp-aem-descriptors: adding upper limit 2025-11-21 08:26:15 +00:00
hackerman-kl
f2093a3f76 module-avb: mrp: fix leavall timer issue, introducing lva state machine 2025-11-21 08:24:44 +00:00
hackerman-kl
03428f3380 module-avb: mrp: add human readable m*rp states 2025-11-21 08:24:44 +00:00
Frédéric Danis
5d39e1357e spa: bluez: backend-native: Fix +CNUM reply
PTS test HFP/AG/NUM/BV-01-C expects to receive 5 items, the last one
representing service type which could be 4 for voice or 5 for fax.
2025-11-21 08:22:55 +00:00
filmsi
99dbd109ce Replace sl.po with updated translation 2025-11-21 08:22:02 +00:00
Daniel Tang
a7735677ae systemd: Bind pipewire-pulse to pipewire
Like filter-chain.service. No point in keeping pipewire-pulse.service
unusably running when pipewire.service is masked for a non-interactive
user.
2025-11-21 08:21:22 +00:00
Barnabás Pőcze
ae7ac460b9 spa: libcamera: source: ignore frame rate
Do not parse, and do not report the negotiated frame rate. It is
completely ignored. Until support for the `FrameDurationLimits`
libcamera control is implemented, ignore any and all frame rates.
2025-11-21 08:20:30 +00:00
Barnabás Pőcze
ead1c144b2 spa: libcamera: source: remove port::memtype
This field is only used during the setup of buffers, so it can be
transformed into a local variable, but even that is not needed
because the data type can be chosen on a per-plane basis.
2025-11-21 08:20:30 +00:00
Barnabás Pőcze
207421cb7b spa: libcamera: source: remove SPA_VIDEO_FORMAT_UNKNOWN check
It is not used anywhere, so remove it.
2025-11-21 08:20:30 +00:00
Wim Taymans
f4efb37b03 doc: update docs with new resampler parameters 2025-11-20 17:44:58 +01:00
Wim Taymans
df075e6628 resample: tweak kaiser resample defaults
Ensure a 110 dB stopband. Set the alpha values accordingly.
2025-11-20 16:55:09 +01:00
Wim Taymans
faf4641625 resample: add some more options to spa-resample
Make it possible to set the params by name. Dump the resampler config
in verbose mode.
2025-11-20 14:07:26 +01:00
Wim Taymans
e8268969ea resample: update params by name
Make the params introspectable, use the names to configure them in
audioconvert.

Only use precomputed filters when using the defaults.

Tweak the kaiser filter alpha and n_taps based on values calculated from
stopband attenuation and transition bandwidth.
2025-11-20 14:07:26 +01:00
Wim Taymans
bfd26c98e3 resample: make window configurable
Add kaiser window
2025-11-20 14:07:14 +01:00
Wim Taymans
18ff08243b spa: add spa_pod_memcmp
Add a helper to memcmp two pods and use it in some places.
2025-11-13 18:13:02 +01:00
Wim Taymans
b9a895f825 adapter: don't recheck formats on driver change
We need to leave this to the converter, like we do for audio.
2025-11-13 18:10:11 +01:00
Wim Taymans
dabd2af828 modules: handle NULL stream when getting time
The stream can be destroyed when requesting the time, fall back to
MONOTONIC time then.

Fixes #4970
2025-11-10 18:33:21 +01:00
Jan Grulich
954f76d107 Remove unused <threads.h> header
This was introduced before for some threading implementation to have
definitions for 'tss_t' and 'tss_set()' and similar, but these were
later removed, while the header was still kept.

This header doesn't exist on older systems with older glibc and is the
reason for the only patch we need to have in Chromium to make PipeWire
to build.
2025-11-10 16:58:34 +00:00
Wim Taymans
fb20b96024 filter-graph: support inline convolver IRs
Refactor the IR loading code, Make some generic open/load/close
functions and handle the different filenames in the open call.

This makes it possible to reuse some of the delay and alloc code.

It also makes it easier to add new IRs loading code.

Support /IR:<rate>,<value1>,<value2>,... as an inline IR definition.
2025-11-10 16:42:29 +01:00
Wim Taymans
f322a8b159 filter-graph: support longer filenames
Support longer filenames by not assuming a max length.
2025-11-10 16:42:29 +01:00
Carlos Rafael Giani
60c47e96a8 module-rtp: Change IGMP recovery log line levels from info to debug
The associated cases where IGMP membership is silently dropped may happen
infrequently. But when they do, these log lines can occur at a large
volume. To not spam the logs in such cases, change the log level from
info to debug.

Signed-off-by: Carlos Rafael Giani <crg7475@mailbox.org>
2025-11-10 14:55:21 +01:00
Pauli Virtanen
8df58db415 bluez5: read errqueue also from media-source handler
Flush errqueue for iso-io also from the media-source handler, to avoid
dropping packet tx reports.

This applies to bidirectional streams. The sink/source handlers poll on
different fd, one being dup'd, and epoll does not trigger them in any
specific interleaved order.
2025-11-09 02:34:54 +02:00
Pauli Virtanen
878dd7a0c9 bluez5: default to FL,FR channels for BAP server
As server, it's not possible to expose all locations as supported
because BlueZ only exposes two ASE.

Default to just FL,FR
2025-11-09 01:47:17 +02:00
Pauli Virtanen
567d5181ca bluez5: iso-io: force resync after underrun
If stream underruns we should resync playback position on next cycle as
there is anyway a sound glitch.
2025-11-09 01:45:21 +02:00
Barnabás Pőcze
3413ca9617 spa: bluez: backend-native: fix libusb device leak
`libusb_free_device_list()` should be passed `true` as its second
argument to unreference every single device in the list.

Fixes: 5e0b63b149 ("bluez5: backend-native: use quirks + usb adapter caps for checking msbc")
2025-11-07 12:28:16 +00:00
Barnabás Pőcze
78b6df769b spa: bluez: telephony: reject double registration 2025-11-07 12:28:16 +00:00
Barnabás Pőcze
9a0053a501 spa: bluez: telephony: fix string leaks
`dbus_connection_register_object_path()` does not take ownership of the
path passed to it, so currently the callers `telephony_{ag,call}_register()`
leak a copy of the path string. Fix that by not making an unnecessary copy.

Fixes: b28399ac57 ("bluez5: add telephony D-Bus service implementation")
2025-11-07 12:28:16 +00:00
Barnabás Pőcze
963d10f1ac spa: bluez: mark dbus vtables static 2025-11-07 12:28:16 +00:00
Arun Raghavan
3337af64ca pulse-server: Support clearing default sink/source
The PulseAudio way of doing this is to accept @NONE@ to clear the
default setting, so let's accept that.
2025-11-06 17:23:16 -08:00
Wim Taymans
2374d034d7 1.5.83 2025-11-06 16:29:05 +01:00
Wim Taymans
7a8ecbf41d 1.5.82 2025-11-06 15:26:08 +01:00
Wim Taymans
7bd65cfe93 pulse-server: increase min quantum values
The default values of 128/48000 seem to be too low as a good default.
Bump them to 256/48000.

Fixes #4875
2025-11-06 12:52:48 +01:00
Wim Taymans
eec5eaf8df spa: fix some -Wdeclaration-after-statement errors
Fixes #4960
2025-11-06 12:39:16 +01:00
Wim Taymans
8f6566422f include <sys/types.h> for ssize_t
Fixes #4961
2025-11-06 12:33:54 +01:00
Wim Taymans
f4b3536b9b spa: enforce max values for choice as well
Make sure we don't return more values than expected for the choice
type.

Fixes #4964
2025-11-06 12:25:55 +01:00
Wim Taymans
1191c18641 thread: add thread.reset-on-fork
Add a new thread.reset-on-fork property for the thread creator. when set
to false, it will clear the default SCHED_RESET_ON_FORK flag and new RT
threads will be able to fork and inherit the rt policy and priority.

When creating a thread make sure we set SCHED_RESET_ON_FORK when the
thread.reset-on-fork property is not explicitly false;

module-rt needs to preserve the SCHED_RESET_ON_FORK flag when changing
the policy.

Set thread.reset-on-fork=false explicitly for JACK clients to restore
the JACK behaviour where implementations can fork and inherit the RT
policy and priority by default.

Fixes #4966
2025-11-06 11:55:20 +01:00
lumingzh
1f3bc6eff5 update Chinese translation 2025-11-05 15:19:52 +00:00
Wim Taymans
fc55ceb2f4 impl-port: handle tags like latency
No links on the port will result in a NULL tag and >0 links will always
result in a non-NULL tag (that could be empty).

This makes it easier to see when a port is linked or not.
2025-11-05 11:26:23 +01:00
Wim Taymans
ffb7663f4d Revert "stream: add peer_added and peer_removed signals"
This reverts commit 3eb011c9d1.

Not a good idea, the internal signals are for scheduling peers and so
some peers are simply not signaled when they don't need to be scheduled,
like for async nodes or upstream nodes.
2025-11-04 13:52:35 +01:00
Wim Taymans
d610c84e31 Revert "test: fix test and indentation"
This reverts commit b0fe422b4f.
2025-11-04 13:50:35 +01:00
Wim Taymans
3aa931fdb0 Revert "stream: avoid emitting peer_added/removed for our id"
This reverts commit 78f5df2846.
2025-11-04 13:50:29 +01:00
Wim Taymans
78f5df2846 stream: avoid emitting peer_added/removed for our id
We internally also add ourselves as a peer when we are a driver. Avoid
emitting events for this.
2025-11-04 12:18:33 +01:00
Wim Taymans
b0fe422b4f test: fix test and indentation 2025-11-04 11:47:05 +01:00
Wim Taymans
3eb011c9d1 stream: add peer_added and peer_removed signals
Forward existing impl-node peer_added and peer_removed signals on the
stream.

Because the stream is not on the server, there is no impl_node in the
node target. Add the node id to the peer_added/removed signal and use
that for the stream event argument. Implementations can then look up the
details on the global.
2025-11-04 11:29:52 +01:00
Wim Taymans
41cdd82291 spa: ensure enum always has 2 values
We need at least a default value and the default value as a possible
value.

For enum type in the PropInfo we usually list just the basic type and
then use labels to list alternatives.
2025-11-04 09:23:26 +01:00
Shengjiu Wang
ef7d4a3fc3 avb: fix frame header of listener
The vlan tag has been stripped by the kernel, the header should be
avb_ethernet_header.

Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com>
2025-11-04 15:22:55 +08:00
Pauli Virtanen
296abbf7ca bluez5: fix wrong use of SPA_POD_CHOICE_ENUM_Int
SPA_POD_CHOICE_ENUM_Int must always take at least 2 values as first one
is default. (Just 1 value no longer works on current master, and it's
anyway incorrect.)

Replace with just SPA_POD_Int, as there's just one choice.
2025-11-03 22:11:04 +00:00
Arun Raghavan
49a803eaa0 spa: bluez5: Make HFP description slightly more user-friendly
In the unknown form-factor case (which I saw on a set of headphones
here, Creative Zen Hybrid Pro), let's avoid an apocryphal acronym in
favour of a term that might be more familiar to the user.
2025-11-03 13:57:24 -08:00
Bernhard Sulzer
5bca3731a3 module-roc: Add support for multi-channel audio 2025-11-03 15:36:34 +00:00
fuleyi
23f0acfd1d pulse-server: Fix missing subscription events on device port changes
When a device profile changes (e.g., Bluetooth headset switching from
a2dp-sink to headset-head-unit), the active port information changes
but PulseAudio compatibility layer clients don't receive the expected
PA_SUBSCRIPTION_EVENT_SOURCE or PA_SUBSCRIPTION_EVENT_SINK change events.

Root cause:
The collect_device_info() function updates the active_port index from
SPA_PARAM_Route parameters, but doesn't update the corresponding
active_port_name field. When update_device_info() uses memcmp() to
detect changes in the device_info structure, it compares the entire
structure including active_port_name. If the pointer value doesn't
change (even though the actual port changed), no change is detected,
and the change_mask flag (PW_MANAGER_OBJECT_FLAG_SOURCE/SINK) is not
set, preventing subscription events from being sent.

Solution:
After setting active_port in collect_device_info(), look up the
corresponding port name from SPA_PARAM_EnumRoute parameters by
matching both the port index and direction. Initialize active_port_name
to NULL at the start to ensure it's always recalculated.

This fix applies to all device types (Bluetooth, USB, PCI sound cards)
and all profile switching scenarios, ensuring applications using the
PulseAudio compatibility layer receive proper device change notifications.

Tested with:
- Bluetooth headset profile switching (a2dp-sink ↔ headset-head-unit)
- Applications subscribing to PA_SUBSCRIPTION_MASK_SOURCE/SINK events
- Verified no regression in audio initialization
2025-11-03 15:31:15 +00:00
Barnabás Pőcze
fa8b0ba018 pipewiresrc: Fix renegotiation
Some downstream elements trigger re-negotiations when the stream is
running, e.g. due to output window resizing for the below pipeline.

gst-launch-1.0 pipewiresrc ! videoconvert ! video/x-raw ! gtk4paintablesink

This fails due to a side effect introduced by commit 77143e54 with
handle_format_change() setting `is_video` to `false` when param was
NULL. Fix this by explicitly tracking media type and not just video
using a boolean.
2025-11-03 15:27:46 +00:00
Barnabás Pőcze
d3368ee0d5 spa: utils: cleanup: fix __has_attribute() usage
Unknown preprocessor symbols are replaced with 0 in preprocessor
conditionals, so if `__has_attribute()` is not supported, then
the condition will be the following:

  if 0 && 0(__cleanup__)

which is syntactically incorrect.

So split the condition and only check `__has_attribute()` if
it exists.

Fixes: 65d949558b ("spa: utils: add scope based resource cleanup")
Fixes #4962
2025-11-02 19:10:25 +01:00
Demi Marie Obenour
62022ce623 pod: doc: Deprecate unused types
I found no uses of SPA_TYPE_Bitmap outside of the SPA code itself and
the now-removed v0 protocol support.  Wim Taymans confirmed that it has
no uses and is not usable.  Deprecate it so it can hopefully be removed.
2025-10-31 11:06:45 +00:00
Wim Taymans
c63c100c4b pulse: use more specific type when parsing params
We expect a Struct for the classes and a Object for the props so we can
use the right POD type when persing.
2025-10-31 12:04:00 +01:00
Wim Taymans
d89c85d374 doc: improve spa_pod_get_values() docs 2025-10-31 11:51:32 +01:00
Wim Taymans
a60eb4fe64 stream: add some redundant checks
Remove some redundant checks and handle calloc failure.
2025-10-31 11:48:22 +01:00
Wim Taymans
95fb03c8e3 spa: add some more POD tests
Check if the pod size is at least big enough to hold 1 item when getting
array or choice items.

Check that the number of choice values is at least enough to handle the
given choice type. Remove some redundant checks.
2025-10-31 11:39:41 +01:00
Robert Mader
77a5100280 gst: Use gst_util_uint64_scale instead of scale_int
GST_SECOND * t.rate.num can turn into a negative gint, resulting in
assertions like:
_gst_util_uint64_scale_int: assertion 'num >= 0' failed

Just use the 64bit version instead.
2025-10-30 16:46:58 +00:00
Wim Taymans
e9a89822f8 adapter: only recheck formats when convert EnumFormat changed
Instead of always renegotiating a new format and buffers when the
driver changes, only renegotiate when the converter notifies us
of an EnumFormat change. It's possible that changing the driver does
not actually change the result of EnumFormat and then we would simply
renegotiate to the same format and buffers for nothing.

In the audioconvert, check if the target_rate of the new driver has
changed. We keep the last value in our own clock so that we don't
renegotiate when we are removed from a driver (which sets our own
clock as the position). The target rate influences the result of the
EnumFormat params when we are resampling and so we need to emit a
EnumFormat change.
2025-10-30 17:41:24 +01:00
Pauli Virtanen
ece9545695 NEWS: update Bluetooth items
Update changelog for 1.5.81 Bluetooth items.
2025-10-30 13:31:41 +00:00
Wim Taymans
ff0bc22cb1 modules: support audio.layout where we can 2025-10-30 12:29:31 +01:00
Wim Taymans
8ba08f3029 spa: add audio.layout property
Makes it possible to define audio channels and position with a
predefined layout string.

It is easier and less error prone to say "5.1" than to spell out
[ FL FR FC LFE RL RR ].

AUX channels have a special syntax. AUX<N> will make <N> AUX
channels. Easier to say AUX128 than to write an array with the
128 AUX channels.
2025-10-30 12:28:07 +01:00
Wim Taymans
056f257058 spa: fix max_position check 2025-10-30 11:26:32 +01:00
Wim Taymans
7706ca6361 spa: fix Cube layout define 2025-10-30 10:10:10 +01:00
Wim Taymans
9f3c553298 tools: add -t option to the help 2025-10-28 10:29:01 +01:00
Wim Taymans
a813830024 po: update Turkish translation 2025-10-28 08:48:18 +01:00
Wim Taymans
a837dcd40b audioadapter: renegotiate when driver changes
The renegotiated format can depend on the clock rate of the new
driver.

See #4933
2025-10-28 08:32:03 +01:00
Rui Matos
752de866ae spa: node-driver: Expose the clock id as param properties 2025-10-28 07:18:59 +00:00
Rui Matos
ec11859a48 spa: Add predefined properties for clock identifiers 2025-10-28 07:18:59 +00:00
Carlos Rafael Giani
1096d63468 module-rtp-source: implement IGMP recovery for multicast subscription loss
Add IGMP recovery mechanism that monitors RTP packet reception and
triggers multicast group refresh when no packets are received if
a deadline is reached. The deadline is configurable via a new stream
property "igmp.deadline.sec" (in seconds), with the default value
being 30 seconds (and a minimum of 5 seconds).

A timer checks regularly if the deadline was reached. That timer's
interval is set by the igmp.check.interval.sec property (in seconds),
with the default value being 5 seconds (and a minimum of 1 second).

When the deadline is reached, the mechanism performs IGMP leave/rejoin
operations to refresh multicast group membership. This ensures RTP
data continues to be received when network conditions cause IGMP
membership to expire or become stale due to router timeouts or
network issues.
2025-10-27 22:40:22 +01:00
Carlos Rafael Giani
955c9ae837 module-rtp: Get the current stream time in a reusable manner
That way, redundant pw_stream_get_nsec() and clock_gettime()
calls can be avoided.
2025-10-27 22:40:22 +01:00
Carlos Rafael Giani
3e0f4daf60 module-rtp-sap: implement IGMP recovery for multicast subscription loss
Add IGMP recovery mechanism that monitors SAP packet reception and
triggers multicast group refresh when no packets are received if
a deadline is reached. The deadline is set to half of the cleanup
interval, with a minimum of 1 second.

When the deadline is reached, the mechanism performs IGMP leave/rejoin
operations to refresh multicast group membership. This ensures SAP
announcements continue to be received when network conditions cause
IGMP membership to expire or become stale due to router timeouts or
network issues.
2025-10-27 22:40:22 +01:00
Carlos Rafael Giani
5d21e12658 module-rtp-source: Use make_socket() error value instead of errno
make_socket() already returns the negative errno.
2025-10-27 22:14:09 +01:00
Carlos Rafael Giani
f1ffd5e5e8 module-rtp-source: Read cleanup.sec property from stream properties
This allows for setting the cleanup.sec value in the create-stream
block in the module-rtp-sap configuration.
2025-10-27 22:14:09 +01:00
Carlos Rafael Giani
80e7302a05 module-rtp-sap: Add retry code for when start_sap() fails due to ENODEV 2025-10-27 22:14:09 +01:00
Carlos Rafael Giani
b57bd00be0 module-rtp-sap: Improve names for clearer code 2025-10-27 22:14:09 +01:00
Rui Matos
c1e737bbe4 module-rtp: Attempt to reconnect the ptp management socket
This should gracefully recover the cases where the other end of the
socket isn't ready yet when we start or terminates and gets restarted.
2025-10-27 18:08:00 +01:00
Jonas Holmberg
76a31a47c2 module-echo-cancel: Avoid discontinuity
Keep the samples in the ringbuffer that are needed the next cycle to
avoid discontinuity when the aec blocksize is not equal to or divisible
by quantum.
2025-10-27 14:39:29 +01:00
Wim Taymans
23c449af5d test: add test for an array with odd number of items
We have to use the relax version to get the expected container type
correct.
2025-10-27 14:20:25 +01:00
Wim Taymans
94d0d8bc09 spa: add spa_json_init_relax
spa_json_init assumes that we start in an object and always requires a
key/value pair. If the last part is a key, it returns and error and does
not want to return the key value.

This causes problems when parsing AUX0,AUX1,AUX2 or any relaxed array
withand odd number of elements.

Make a new spa_json_init_relax that takes the type of the container
we're assuming we're in and set the state of the parser to array when we
are parsing a relaxed array.

Fixes #4944
2025-10-27 13:32:03 +01:00
Wim Taymans
0276bb5b06 modules: ringbuffer avail is signed 2025-10-27 11:43:04 +01:00
Jonas Holmberg
614186a590 module-echo-cancel: Sync capture and sink buffers
Call process() when capture and sink ringbuffers contain data from the
same graph cycle and only process the latest block from them to avoid
adding latency that can accumulate if one of the streams gets more than
one buffer before the other gets its first buffer when starting up.
2025-10-27 08:43:08 +01:00
Pauli Virtanen
c6d0b364ab spa: param: add size checks for spa_audio_info* structs
In the API that take struct size for spa_audio_info*, also check the
struct size.
2025-10-26 18:23:17 +02:00
Pauli Virtanen
8a23b13798 spa: param: pass correct struct size to spa_format_audio_raw_ext_parse/build 2025-10-26 17:44:03 +02:00
Pauli Virtanen
3d08c0557f properties: fix assign + conditional expression 2025-10-26 14:12:19 +00:00
Pauli Virtanen
68dc45cc62 audioconvert: simplify volume ramp generation
Don't use floating point accumulators, interpolate from sample position.
2025-10-26 14:12:19 +00:00
Pauli Virtanen
b0e308e0dc spa: examples: fix getopt usage + typos in adapter-control 2025-10-26 14:12:19 +00:00
Pauli Virtanen
fe2c62b9b1 meson.build: set project cc flags also for native builds
Use the build flags also for all native build targets.
Avoids spurious warnings in spa-json-dump
2025-10-26 14:12:19 +00:00
Pauli Virtanen
3febf09b85 alsa: fix typoed braces in condition + assign 2025-10-26 14:12:19 +00:00
Pauli Virtanen
93495d3a75 spa: param: infer raw audio channels from position if unset
The behavior before b8eeb2db45 was that spa_audio_info_raw_update()
always sets audio.channels when audio.position is updated.  The new
behavior does not set audio.channels when parsing audio.position.

This breaks things e.g. when combine-sink and loopback nodes are created
with only audio.position specified.

Restore the previous behavior.
2025-10-26 15:47:48 +02:00
Pauli Virtanen
9f1a149876 ci: add file package, for coverity
Try to fix coverity by adding missing 'file' package to container
2025-10-25 12:42:45 +03:00
Wim Taymans
88c65932d8 acp: use global max channels if defined 2025-10-24 17:16:03 +02:00
Wim Taymans
c8d4de5e77 acp: bump max channels to 128 2025-10-24 17:00:42 +02:00
Wim Taymans
c4244a6cf3 string: use spa_strbuf instead of snprintf magic 2025-10-24 17:00:11 +02:00
Wim Taymans
f7c3d37969 fmt-ops: allocate shaper memory dynamically
It is based on the number of channels so allocate it dynamically.
2025-10-24 12:46:38 +02:00
Wim Taymans
d18670d7bb pw-cat: improve channel checks
Make sure we don't use too many channels.
2025-10-24 10:42:05 +02:00
Wim Taymans
aa0272f6f3 treewide: remove some obsolete channel checks
The spa_audio_info can not be parsed with too many channels so there
is always enough space for the positions.
2025-10-24 10:31:45 +02:00
Wim Taymans
78219471ff spa: remove some obsolete functions
The spa_audio_info array now always holds enough positions for all
channels and we don't need to wrap around.
2025-10-24 09:35:59 +02:00
Wim Taymans
6d74eee874 spa: bump channels to 128 again
Remove the SPA_AUDIO_MAX_POSITION define and use the
SPA_AUDIO_MAX_CHANNELS again.

Make a compile time define to override the default max channels of 64.

Make sure we compile the SPA library with the default 64 channels. If
you use the SPA library on a spa_audio_info you will get 64 channel
support, like before. If you want more channels, you will need to make
a padded structure or redefine the MAX_CHANNELS before you use the
spa_audio_info structures. You can use the padded structure with the
new functions that take the structure size.

With the extra checks in the parsing code, we avoid making a
valid spa_audio_info with too many channels that don't fit in the
structure. This means that code that receives a spa_audio_info can
assume there is enough padding for all the channels.
2025-10-24 08:53:21 +02:00
Wim Taymans
be29ae4ef6 audioadapter: add some more debug info when parsing fails 2025-10-23 18:05:22 +02:00
Wim Taymans
5e1e3fca1e modules: handle format parsing errors 2025-10-23 18:01:35 +02:00
Wim Taymans
b8eeb2db45 spa: make it possible to extend the spa_audio_info struct
Add functions that take the size of the spa_audio_info struct in various
functions. We can use this to determine how many channels and channel
positions we can store.

Error out if we try to use more channels than we can fit positions. This
is probably the safest thing to do because most code will blindly try to
get the positions without checking the channel count.

Make sure we also propagate errors to the callers.
2025-10-23 17:59:51 +02:00
Wim Taymans
c5533b3c32 spa: add all channel positions to the params
Pass all the positions of all channels to the format param, even when
there are more channels then positions.
2025-10-22 13:26:52 +02:00
Wim Taymans
11f1298f53 spa: make a function to make a channel short name
Make a function that can generate and parse a short name for
the positions that are not in the type list, like the AUX channels.
2025-10-22 13:04:53 +02:00
Wim Taymans
7177f8269d bluez: use function to get the channel position from a name 2025-10-22 12:54:30 +02:00
Wim Taymans
6465a63bf6 spa: parse the audio.position completetly
Parse the audio.position spec completely so that we have the right
number of channels but only store the first max_position channels.

Also rename some field to make it clear that this is about the max
number of channel positions.
2025-10-22 12:47:00 +02:00
Wim Taymans
ae50bb5dc0 audio: don't limit channels to max positions
We can have more channels than we have positions.
2025-10-22 09:39:15 +02:00
Wim Taymans
99bbac9cbf spa: increase SPA_AUDIO_MAX_CHANNELS to 128
This should now not change the ABI because the position array size is
now controlled with the SPA_AUDIO_MAX_POSITION constant.
2025-10-21 17:01:31 +02:00
Wim Taymans
f19b075306 spa: add SPA_AUDIO_MAX_POSITION
Add a new SPA_AUDIO_MAX_POSITION constant with the maximum number of
channel positions that can be kept in the various audio_info structures.

Repurpose the SPA_AUDIO_MAX_CHANNELS as a suggestion for applications
for the max allowed number of channels in the system. Make it possible
to make this a compile time constant.
2025-10-21 16:08:24 +02:00
Wim Taymans
dbc5c81e4a spa: avoid using SPA_AUDIO_MAX_CHANNELS
Use SPA_N_ELEMENTS instead of the array we try to handle.
2025-10-21 16:05:33 +02:00
Wim Taymans
818d1435ce treewide: access the position information using helpers
Make sure we don't access out of bounds and that we use the helpers
wherever we can to access the position information.
2025-10-21 13:06:25 +02:00
Wim Taymans
8bbca3b8f3 spa: add spa_audio_parse_position_n
Add a function that accepts the size of the position array when reading
the audio positions. This makes it possible to decouple the position
array size from SPA_AUDIO_MAX_CHANNELS.

Also use SPA_N_ELEMENTS to pass the number of array elements to
functions instead of a fixed constant. This makes it easier to change
the array size later to a different constant without having to patch up
all the places where the size is used.
2025-10-21 09:59:13 +02:00
Wim Taymans
9e7cae13df alsa: use the amount of positions we will write 2025-10-21 09:43:59 +02:00
Wim Taymans
13b8c23767 Don't use SPA_AUDIO_MAX_CHANNELS directly
Make a MAX_CHANNELS define and use that one in code. This makes it
easier to change the constant later.
2025-10-21 09:43:06 +02:00
Wim Taymans
eb096bfb62 spa: provide information about channels > SPA_AUDIO_MAX_CHANNELS
Define some rules for how the position information works for channels >
SPA_AUDIO_MAX_CHANNELS. We basically wrap around and incrementing the
AUX channel counters. Make a function to implement this.
2025-10-21 09:40:08 +02:00
Wim Taymans
ede13a8cb5 spa: don't add more channels than we have positions 2025-10-20 18:31:35 +02:00
Wim Taymans
f453b1545d audio: don't use SPA_AUDIO_MAX_CHANNELS in some places
When we know the max size of the array, just use this instead of the
SPA_AUDIO_MAX_CHANNELS constant.
2025-10-20 18:31:17 +02:00
Wim Taymans
c94aff8cae Revert "audio: bump max channels to 128"
This reverts commit c91f75ae2e.

This change causes a subtle ABI change and also breaks the Rust
bindings.
2025-10-20 09:17:26 +02:00
Wim Taymans
fb49759d1f module-echo-cancel: drop samples when source not ready
When we can't dequeue a buffer from the source stream, drop the samples
instead of leaving them queued in the ringbuffer.
2025-10-17 14:51:14 +02:00
Wim Taymans
f70b0892ea doc: swap the name and id of the device.product
Fixes #4935
2025-10-17 12:28:15 +02:00
236 changed files with 10247 additions and 2567 deletions

View file

@ -38,7 +38,7 @@ include:
.fedora:
variables:
# Update this tag when you want to trigger a rebuild
FDO_DISTRIBUTION_TAG: '2025-10-15.0'
FDO_DISTRIBUTION_TAG: '2025-10-22.0'
FDO_DISTRIBUTION_VERSION: '42'
FDO_DISTRIBUTION_PACKAGES: >-
alsa-lib-devel
@ -48,6 +48,7 @@ include:
dbus-devel
doxygen
fdk-aac-free-devel
file
findutils
gcc
gcc-c++

128
NEWS
View file

@ -1,3 +1,121 @@
# PipeWire 1.5.84 (2025-11-27)
This is the fourth 1.6 release candidate that is API and ABI
compatible with previous 1.4.x, 1.2.x and 1.0.x releases.
Changes since the last pre-release:
## Highlights
- Capabilities wer added to improve negotiation over links.
- The audio resampler now has a configurable window function to better
tune the resampler quality. A kaiser and blackman window was added
and the default parameters were tuned.
- Various small fixes and improvements.
## PipeWire
- Capabilities and PeerCapabilities were added to exchange key/value
pairs between consumer and producer right after a link is made. This
can be used to detect how the negotiation of formats and buffers
should be done.
## Modules
- Avoid segfaults in RTP source. (#4970)
- The AVB module has seen some improvements.
## Pulse-server
- @NONE@ can now be used to clear the default sink/source.
## SPA
- Support longer convolver filenames and also support inline
IRs.
- The audio resampler window function is now selectable and
configurable. A kaiser window and blackman window was added
and the default qualities were tweaked to improve quality.
- The filter-graph convolver latency is now set by default to
something more sensible. (0 by default and N/2 for hilbert).
(#4980)
## Bluetooth
- Better xrun and error handling for iso streams.
- The +CNUM reply was fixed.
- The CIEC call status was fixed. (#1744)
- Add BAP context metadata to improve compatibility.
- Improve compatiblity with Creative Zen Hybrid Pro by releasing
transports simultaneously.
Older versions:
# PipeWire 1.5.83 (2025-11-06)
This is the third 1.6 release candidate that is API and ABI
compatible with previous 1.4.x, 1.2.x and 1.0.x releases.
Changes since the last pre-release:
## Highlights
- Include the NEWS and updated version number.
# PipeWire 1.5.82 (2025-11-06)
This is the second 1.6 release candidate that is API and ABI
compatible with previous 1.4.x, 1.2.x and 1.0.x releases.
Changes since the last pre-release:
## Highlights
- The max channel limit is now a compile time option.
- The SAP and RTP module have seen some robustness improvements.
- Add audio.layout propperty.
- Cleanups to the code here and there.
## PipeWire
- Handle Tags more like Latency with a NULL param when no ports are linked
and some sort of (empty) Tag when the ports are linked.
## Modules
- Improve the echo-cancel module to keep the streams more aligned
and cause less latency.
- Improve format parsing errors in most modules.
- The RTP module now has extra code for better network robustness, including
cases when network interfaces are not yet up and running, and multicast
sockets are silently kicked out of IGMP groups.
- The direct timestamp mode in the RTP module was effectively broken and is
now fixed.
- Add support for audio.layout.
- Add multichannel support to ROC.
## SPA
- Rework the maximum number of channel handling. Because this is a
potential ABI break, it is now a compile time option with new
functions to handle more than the previous 64 channels.
- The 64 channel limit was removed from the noise shaper.
- spa_strbuf is used in more places instead of custom snprintf code.
- The volume ramp code was simplified.
- The driver node now has properties to configure the clock.
- The adapter will try to renegotiate when the driver changes.
- Fix relaxed array parsing with od number of elements. (#4944)
- audio.layout was added to set the channel positions to some
predefined layouts.
- Added more POD choice checks to ensure the right amount of values
are present in the choice.
- Fix __has_attribute usage. (#4962)
- Thread RESET_ON_FORK is now disabled for JACK application so that
forking will preserve any real-time thread priorities, like JACK.
(#4966)
- Fix some compilation issues. (#4960 and #4961).
## Pulse-server
- Fix missing subscription events on device port changes.
- Increase min.quantum to 256/48000. (#4875)
## GStreamer
- Avoid overflow in clock time calculations.
- Fix renegotiation.
## Docs
- Swap the name and id of device.product
# PipeWire 1.5.81 (2025-10-16)
This is the first 1.6 release candidate that is API and ABI
@ -166,8 +284,11 @@ also contains some new features:
## Bluetooth
- Telephony improvements.
- ASHA support was added.
- Add packet loss correction with spandsp for some codecs.
- Synchronisation between ISO streams in the same group.
- Packet loss concealment was added.
- Improved synchronisation between LE Audio streams in the same group.
- Improved LE Audio device compatibility.
- LC3-24kHz voice codec was added (used by Airpods)
- LDAC decoding support added (requires separate decoder library)
## Pulse-server
- The SUSPEND event is now correctly generated. fail-on-suspend is
@ -193,9 +314,6 @@ also contains some new features:
## Docs
- Document the client-node flow a bit more.
Older versions:
# PipeWire 1.4.0 (2025-03-06)
This is the 1.4 release that is API and ABI compatible with previous

View file

@ -33,7 +33,7 @@ POD's can contain a number of basic SPA types:
- `SPA_TYPE_Bytes`: A byte array.
- `SPA_TYPE_Rectangle`: A rectangle with width and height.
- `SPA_TYPE_Fraction`: A fraction with numerator and denominator.
- `SPA_TYPE_Bitmap`: An array of bits.
- `SPA_TYPE_Bitmap`: An array of bits. Deprecated and unused.
POD's can be grouped together in these container types:
@ -435,10 +435,11 @@ spa_pod_parser_get_object(&p,
\endcode
`spa_pod_get_values()` is a useful function. It returns a
`struct spa_pod*` with and array of values. For normal POD's
and choice none values, it simply returns the POD and one value.
For other choice values it returns the choice type and an array
of values:
`struct spa_pod*` with and array of values. For invalid PODs
it returns the POD and no values. For normal PODs it returns
the POD and one value. For choice values it returns the choice
type and an array of values. If the choice doesn't fit even a
single value, the array will have no values.
\code{.c}
struct spa_pod *value;

View file

@ -59,6 +59,14 @@ stream.properties = {
#node.autoconnect = true
#resample.disable = false
#resample.quality = 4
#resample.window = exp # blackman kaiser
#resample.cutoff = 0.0
#resample.n-taps = 0
#resample.param.exp.A = 0.0
#resample.param.blackman.alpha = 0.0
#resample.param.kaiser.alpha = 0.0
#resample.param.kaiser.stopband-attenuation = 0.0
#resample.param.kaiser.transition-bandwidth = 0.0
#monitor.channel-volumes = false
#channelmix.disable = false
#channelmix.min-volume = 0.0

View file

@ -174,12 +174,12 @@ ie. for example `device.Param.Props = { ... }` to set `Props`.
@PAR@ device-prop device.product.id # integer
\parblock
\copydoc PW_KEY_DEVICE_PRODUCT_NAME
\copydoc PW_KEY_DEVICE_PRODUCT_ID
\endparblock
@PAR@ device-prop device.product.name # string
\parblock
\copydoc PW_KEY_DEVICE_PRODUCT_ID
\copydoc PW_KEY_DEVICE_PRODUCT_NAME
\endparblock
@PAR@ device-prop device.class # string
@ -547,17 +547,137 @@ Below is an explanation of the options that can be tuned in the sample converter
\parblock
The quality of the resampler. from 0 to 14, the default is 4.
The quality of a resampler depends on multiple factors:
1. Anti-Aliasing, how well are unwanted frequencies filtered out. Poor anti-aliasing
will make the original inaudible frequencies audible as distortion and noise.
2. Cutoff frequence. At what frequency the transition band will start. This is the
frequency where the signal will start to fade out. A too low cutoff might remove too
much of the high frequencies and make the sound dull. A too high cutoff might cause
aliasing. The cutoff frequency is usually expressed as a ratio of the Nyquist
frequency, 1.0 being the Nyquist frequency (frequency/2).
3. Transition band length. How quickly the unwanted frequencies are filtered out. A
shorter transition band requires longer filters with more CPU and latency but
causes less aliasing. The transition band length is expressed as a ratio of the
Nyquist frequency.
4. Stopband attenuation. How well the unwanted frequencies are filtered out. This is
usually measured in dB. 96dB is below audidle on CD quality audio, 150dB is below
the precision of floating point values.
5. CPU usage. Better anti-aliasing needs longer filters and is therefore more CPU
intensive.
6. Latency. Longer filters have a higher Latency. In real-time application the latency
should be kept as low as possible.
7. Ringing. A too short transition band length might cause ringing because of how the
sinc filters work. This can sound like flutter on sharp attacks in the audio signal.
Increasing the quality will result in better cutoff and less aliasing at the expense of
(much) more CPU consumption. The default quality of 4 has been selected as a good compromise
between quality and performance with no artifacts that are well below the audible range.
(much) more CPU consumption and more ringing. The default quality of 4 has been selected
as a good compromise between quality and performance with no artifacts that are well
below the audible range.
The default resampler quality for the exp window results in a cutoff of 0.87 and a
filter size of about 48 taps. It has a Stopband attenuation of about 150 dB.
See [Infinite Wave](https://src.infinitewave.ca/) for a comparison of the performance.
You can tune the resampler in a variaty of ways:
* Tune the cutoff frequency. Increase the cutoff to preserve more high frequencies at the
expense of more aliasing.
* Tune the transition band. Reduce the transition band to better filter out the frequencies
around the cutoff frequency and get less aliasing at the expense of more ringing, CPU and
Latency. This can be done by increasing the number of taps.
* Tune the stopband attenuation. Increase the attenuation to reduce aliasing at the expense
of a wider transition band. This can only be done on the kaiser window, the exp and
blackman window have 150dB and 96dB attenuation respecively.
\endparblock
@PAR@ node-prop resample.disable = false
Disable the resampler entirely. The node will only be able to negotiate with the graph
when the samplerates are compatible.
@PAR@ node-prop resample.window = exp
\parblock
The resampler window function to use. By default an exponential window function is used
that gives a good balance between complexitiy and quality.
You can also specify a blackman or kaiser window, both with different tradeoffs. The
kaiser window has some extra tunable parameters for the specific use cases.
\endparblock
@PAR@ node-prop resample.cutoff = 0.0
\parblock
The resampler cutoff frequency. This is a value between 0.0 and 1.0. A value of 0.0 will
use a predefined value based on the resampler quality.
A higher cutoff value will preserve more high frequency content but depending on the
size of the transition band will cause more aliasing.
The default quality 4 setting for all windows is 0.87.
\endparblock
@PAR@ node-prop resample.n-taps = 0
\parblock
The resampler number of taps. A value of 0 will use a predefined value based on
the resampler quality or other window function parameters.
A higher number of taps will use more CPU, Latency and cause more ringing but will
reduce aliasing.
The default quality setting for the exp window is 48.
\endparblock
@PAR@ node-prop resample.param.exp.A = 0.0
\parblock
The A parameter for the exponential window function. A value of 0.0 will use a predefined
value based on the quality when the exp window is in use.
The default setting for the exp window is 16.97789.
\endparblock
@PAR@ node-prop resample.param.blackman.alpha = 0.0
\parblock
The alpha value of the blackman function. A value of 0.0 will use a predefined
value based on the quality when the blackman window is in use.
The default quality setting for the blackman window is 0.16.
\endparblock
@PAR@ node-prop resample.param.kaiser.stopband-attenuation = 0.0
\parblock
The kaiser window stopband attenuation parameter in dB. A default value of 0.0 will use a
predefined value based on the quality.
A higher value will filter out more of the unwanted frequencies and reduce aliasing at the
expense of a larger transition band. A value of 96dB is below the dynamic range of CD quality
audio. 150dB is the limit of the precision of the resampler.
The default quality setting for the kaiser window is 130.000000.
\endparblock
@PAR@ node-prop resample.param.kaiser.transition-bandwidth = 0.0
\parblock
The kaiser window transition bandwidth parameter. A default value of 0.0 will use a
predefined value based on the quality.
A smaller transition band will cause a steeper cutoff with less unwanted frequencies
in the final signal at the expense of more a larger filter and more CPU usage and
latency. A smaller transition band can also cause more ringing.
The default quality setting for the kaiser window is 0.177032
\endparblock
@PAR@ node-prop resample.param.kaiser.alpha = 0.0
\parblock
The kaiser window alpha parameter. A default value of 0.0 will calculate an alpha value
based on the stopband-attenuation and transition-bandwidth parameters.
This value is usually calculated from the other parameters but can be set explicitly
with this property.
The default quality setting for the kaiser window is 4.254931.
\endparblock
## Channel Mixer Parameters
Source, sinks, capture and playback streams can apply channel mixing on the incoming signal.
@ -843,6 +963,9 @@ The audio format to open the device in. By default this is "UNKNOWN", which will
@PAR@ node-prop audio.position # JSON array of strings
The audio position of the channels in the device. This is auto detected based on the profile. You can configure an array of channel positions, like "[ FL, FR ]".
@PAR@ node-prop audio.layout # string
The audio layout of the channels in the device. You can use any of the predefined layouts, like "Stereo", "5.1" etc.
@PAR@ node-prop audio.allowed-rates # JSON array of integers
\parblock
The allowed audio rates to open the device with. Default is "[ ]", which means the device can be opened in any supported rate.
@ -1015,6 +1138,15 @@ HFP/HSP backend (default: native). Available values: any, none, hsphfpd, ofono,
@PAR@ monitor-prop bluez5.hfphsp-backend-native-modem # string
@PAR@ monitor-prop bluez5.hfphsp-backend-native-pts # boolean
Enable specific workarounds for Bluetooth qualification.
@PAR@ monitor-prop bluez5.disable-dummy-call # boolean
By default a call status event is sent on audio stream connection/disconnection to
workaround some headset timeout disconnection when the HFP HF is used by another
application than telephony one, e.g. a conference application/website.
This prevent to send this event.
@PAR@ monitor-prop bluez5.dummy-avrcp player # boolean
Register dummy AVRCP player. Some devices have wrongly functioning
volume or playback controls if this is not enabled. Default: false
@ -1134,6 +1266,18 @@ Available source contexts PACS bitmask of the the server.
@PAR@ monitor-prop bluez5.bap-server-capabilities.source.supported-contexts # integer
Supported source contexts PACS bitmask of the the server.
@PAR@ monitor-prop bluez5.bap-server-tmap-features = null # array of string
Override advertised TMAP service features. See TMAP specification for their meaning.
Possible values: "cg", "ct", "ums", "umr", "bms", "bmr".
Default: none.
@PAR@ monitor-prop bluez5.bap-server-gmap-features = null # array of string
Override advertised GMAP service features. See GMAP specification for their meaning.
Possible values: "ugg", "ugt", "bgs", "bgr", "ugg-multiplex", "ugg-96kbps-source", "ugg-multisink",
"ugt-source", "ugt-80kbps-source", "ugt-sink", "ugt-64kbps-sink", "ugt-multiplex", "ugt-multisink",
"ugt-multisource", "bgs-96kbps", "bgr-multisink", "bgr-multiplex".
Default: none.
## Device properties
@PAR@ device-prop bluez5.auto-connect # boolean

View file

@ -1130,6 +1130,8 @@ follows:
|<----------------------------------------|
| ClientNode::PortSetMixInfo | mixer inputs for each linked port
|<----------------------------------------|
| ClientNode::PortSetParam | PeerCapability of the ports
|<----------------------------------------|
| ClientNode::PortSetParam | Latency of the ports
|<----------------------------------------|
| ClientNode::SetActivation | activation of port peers

View file

@ -1,5 +1,5 @@
project('pipewire', ['c' ],
version : '1.5.81',
version : '1.5.84',
license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ],
meson_version : '>= 0.61.1',
default_options : [ 'warning_level=3',
@ -115,10 +115,11 @@ cc_flags = common_flags + [
'-Werror=old-style-definition',
'-Werror=missing-parameter-type',
'-Werror=strict-prototypes',
'-DSPA_AUDIO_MAX_CHANNELS=128u',
]
add_project_arguments(cc.get_supported_arguments(cc_flags), language: 'c')
cc_flags_native = cc_native.get_supported_arguments(cc_flags)
add_project_arguments(cc_native.get_supported_arguments(cc_flags),
language: 'c', native: true)
have_cpp = add_languages('cpp', native: false, required : false)

View file

@ -22,9 +22,11 @@ PW_LOG_TOPIC_STATIC(alsa_log_topic, "alsa.ctl");
#define VOLUME_MIN ((uint32_t) 0U)
#define VOLUME_MAX ((uint32_t) 0x10000U)
#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS
struct volume {
uint32_t channels;
long values[SPA_AUDIO_MAX_CHANNELS];
long values[MAX_CHANNELS];
};
typedef struct {
@ -498,7 +500,7 @@ static struct spa_pod *build_volume_mute(struct spa_pod_builder *b, struct volum
spa_pod_builder_push_object(b, &f[0],
SPA_TYPE_OBJECT_Props, SPA_PARAM_Props);
if (volume) {
float volumes[SPA_AUDIO_MAX_CHANNELS];
float volumes[MAX_CHANNELS];
uint32_t i, n_volumes = 0;
n_volumes = volume->channels;
@ -850,11 +852,11 @@ static void parse_props(struct global *g, const struct spa_pod *param, bool devi
break;
case SPA_PROP_channelVolumes:
{
float volumes[SPA_AUDIO_MAX_CHANNELS];
float volumes[MAX_CHANNELS];
uint32_t n_volumes, i;
n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float,
volumes, SPA_AUDIO_MAX_CHANNELS);
volumes, SPA_N_ELEMENTS(volumes));
g->node.channel_volume.channels = n_volumes;
for (i = 0; i < n_volumes; i++)

View file

@ -31,6 +31,7 @@ PW_LOG_TOPIC_STATIC(alsa_log_topic, "alsa.pcm");
#define MAX_BUFFERS 64u
#define MAX_RATE (48000*8)
#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS
#define MIN_PERIOD 64
@ -642,7 +643,7 @@ static int snd_pcm_pipewire_pause(snd_pcm_ioplug_t * io, int enable)
#define _FORMAT_BE(p, fmt) p ? SPA_AUDIO_FORMAT_UNKNOWN : SPA_AUDIO_FORMAT_ ## fmt ## _OE
#endif
static int set_default_channels(uint32_t channels, uint32_t position[SPA_AUDIO_MAX_CHANNELS])
static int set_default_channels(uint32_t channels, uint32_t position[MAX_CHANNELS])
{
switch (channels) {
case 8:
@ -915,12 +916,16 @@ static int snd_pcm_pipewire_set_chmap(snd_pcm_ioplug_t * io,
default:
return -EINVAL;
}
if (map->channels > MAX_CHANNELS)
return -ENOTSUP;
for (i = 0; i < map->channels; i++) {
char buf[8];
position[i] = chmap_to_channel(map->pos[i]);
pw_log_debug("map %d: %s / %s", i,
snd_pcm_chmap_name(map->pos[i]),
spa_debug_type_find_short_name(spa_type_audio_channel,
position[i]));
spa_type_audio_channel_make_short_name(position[i],
buf, sizeof(buf), "UNK"));
}
return 1;
}
@ -1097,7 +1102,7 @@ struct param_info infos[] = {
{ "alsa.rate", SND_PCM_IOPLUG_HW_RATE, TYPE_MIN_MAX,
{ 1, MAX_RATE }, 2, collect_int },
{ "alsa.channels", SND_PCM_IOPLUG_HW_CHANNELS, TYPE_MIN_MAX,
{ 1, SPA_AUDIO_MAX_CHANNELS }, 2, collect_int },
{ 1, MAX_CHANNELS }, 2, collect_int },
{ "alsa.buffer-bytes", SND_PCM_IOPLUG_HW_BUFFER_BYTES, TYPE_MIN_MAX,
{ MIN_BUFFER_BYTES, MAX_BUFFER_BYTES }, 2, collect_int },
{ "alsa.period-bytes", SND_PCM_IOPLUG_HW_PERIOD_BYTES, TYPE_MIN_MAX,

View file

@ -4299,6 +4299,7 @@ jack_client_t * jack_client_open (const char *client_name,
client->props = pw_properties_new(
PW_KEY_LOOP_CANCEL, "true",
SPA_KEY_THREAD_RESET_ON_FORK, "false",
PW_KEY_REMOTE_NAME, client->server_name,
PW_KEY_CLIENT_NAME, client_name,
PW_KEY_CLIENT_API, "jack",

304
po/sl.po
View file

@ -9,16 +9,16 @@ msgstr ""
"Project-Id-Version: PipeWire master\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/"
"issues\n"
"POT-Creation-Date: 2025-09-11 03:34+0000\n"
"PO-Revision-Date: 2025-09-11 11:47+0200\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"
"Language-Team: Slovenian <gnome-si@googlegroups.com>\n"
"Language: sl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100>=3 && "
"n%100<=4 ? 2 : 3);\n"
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100>=3 && n"
"%100<=4 ? 2 : 3);\n"
"X-Generator: Poedit 2.2.1\n"
#: src/daemon/pipewire.c:29
@ -57,7 +57,7 @@ msgstr "Prehod do %s%s%s"
msgid "Dummy Output"
msgstr "Lažni izhod"
#: src/modules/module-pulse-tunnel.c:760
#: src/modules/module-pulse-tunnel.c:761
#, c-format
msgid "Tunnel for %s@%s"
msgstr "Prehod za %s@%s"
@ -76,7 +76,7 @@ msgstr "%s na %s@%s"
msgid "%s on %s"
msgstr "%s na %s"
#: src/tools/pw-cat.c:1084
#: src/tools/pw-cat.c:1103
#, c-format
msgid ""
"%s [options] [<file>|-]\n"
@ -92,7 +92,7 @@ msgstr ""
"\n"
"</file>\n"
#: src/tools/pw-cat.c:1091
#: src/tools/pw-cat.c:1110
#, c-format
msgid ""
" -R, --remote Remote daemon name\n"
@ -129,7 +129,7 @@ msgstr ""
" -P --properties Nastavi lastnosti vozlišča\n"
"\n"
#: src/tools/pw-cat.c:1109
#: src/tools/pw-cat.c:1128
#, c-format
msgid ""
" --rate Sample rate (req. for rec) (default "
@ -137,8 +137,8 @@ msgid ""
" --channels Number of channels (req. for rec) "
"(default %u)\n"
" --channel-map Channel map\n"
" one of: \"stereo\", "
"\"surround-51\",... or\n"
" one of: \"Stereo\", \"5.1\",... "
"or\n"
" comma separated list of channel "
"names: eg. \"FL,FR\"\n"
" --format Sample format %s (req. for rec) "
@ -157,8 +157,8 @@ msgstr ""
" --channels Število kanalov (zaht. za snemanje) "
"(privzeto %u)\n"
" --channel-map Preslikava kanalov\n"
" Ena izmed: \"stereo\", "
"\"surround-51\",... ali\n"
" Ena izmed: \"Stereo\", "
"\"5.1\",... ali\n"
" seznam imen kanalov, ločen z "
"vejico: npr. \"FL,FR\"\n"
" --format Vzorčne oblike zapisa %s (zahtevano "
@ -167,12 +167,12 @@ msgstr ""
" -q --quality Kakovost prevzorčenja (0 - 15) "
"(privzeto %d)\n"
" -a, --raw neobdelan način (RAW)\n"
" -M, --force-midi Vsili zapis midi, eden izmed "
"\"midi\" ali \"ump\" (privzeto ump)\n"
" -M, --force-midi Vsili zapis midi, eden izmed \"midi"
"\" ali \"ump\" (privzeto ump)\n"
" -n, --sample-count ŠTEVEC Ustavi po ŠTEVEC vzorcih\n"
"\n"
#: src/tools/pw-cat.c:1129
#: src/tools/pw-cat.c:1148
msgid ""
" -p, --playback Playback mode\n"
" -r, --record Recording mode\n"
@ -212,203 +212,203 @@ msgstr ""
" -m, --monitor Spremljaj dejavnosti\n"
"\n"
#: spa/plugins/alsa/acp/acp.c:351
#: spa/plugins/alsa/acp/acp.c:361
msgid "Pro Audio"
msgstr "Profesionalni zvok"
#: spa/plugins/alsa/acp/acp.c:527 spa/plugins/alsa/acp/alsa-mixer.c:4635
#: spa/plugins/bluez5/bluez5-device.c:1974
#: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699
#: spa/plugins/bluez5/bluez5-device.c:1976
msgid "Off"
msgstr "Izklopljeno"
#: spa/plugins/alsa/acp/acp.c:610
#: spa/plugins/alsa/acp/acp.c:620
#, c-format
msgid "%s [ALSA UCM error]"
msgstr "%s [napaka ALSA UCM]"
#: spa/plugins/alsa/acp/alsa-mixer.c:2652
#: spa/plugins/alsa/acp/alsa-mixer.c:2721
msgid "Input"
msgstr "Vhod"
#: spa/plugins/alsa/acp/alsa-mixer.c:2653
#: spa/plugins/alsa/acp/alsa-mixer.c:2722
msgid "Docking Station Input"
msgstr "Vhod priklopne postaje"
#: spa/plugins/alsa/acp/alsa-mixer.c:2654
#: spa/plugins/alsa/acp/alsa-mixer.c:2723
msgid "Docking Station Microphone"
msgstr "Mikrofon priklopne postaje"
#: spa/plugins/alsa/acp/alsa-mixer.c:2655
#: spa/plugins/alsa/acp/alsa-mixer.c:2724
msgid "Docking Station Line In"
msgstr "Linijski vhod priklopne postaje"
#: spa/plugins/alsa/acp/alsa-mixer.c:2656
#: spa/plugins/alsa/acp/alsa-mixer.c:2747
#: spa/plugins/alsa/acp/alsa-mixer.c:2725
#: spa/plugins/alsa/acp/alsa-mixer.c:2816
msgid "Line In"
msgstr "Linijski vhod"
#: spa/plugins/alsa/acp/alsa-mixer.c:2657
#: spa/plugins/alsa/acp/alsa-mixer.c:2741
#: spa/plugins/bluez5/bluez5-device.c:2372
#: spa/plugins/alsa/acp/alsa-mixer.c:2726
#: spa/plugins/alsa/acp/alsa-mixer.c:2810
#: spa/plugins/bluez5/bluez5-device.c:2374
msgid "Microphone"
msgstr "Mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2658
#: spa/plugins/alsa/acp/alsa-mixer.c:2742
#: spa/plugins/alsa/acp/alsa-mixer.c:2727
#: spa/plugins/alsa/acp/alsa-mixer.c:2811
msgid "Front Microphone"
msgstr "Sprednji mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2659
#: spa/plugins/alsa/acp/alsa-mixer.c:2743
#: spa/plugins/alsa/acp/alsa-mixer.c:2728
#: spa/plugins/alsa/acp/alsa-mixer.c:2812
msgid "Rear Microphone"
msgstr "Zadnji mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2660
#: spa/plugins/alsa/acp/alsa-mixer.c:2729
msgid "External Microphone"
msgstr "Zunanji mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2661
#: spa/plugins/alsa/acp/alsa-mixer.c:2745
#: spa/plugins/alsa/acp/alsa-mixer.c:2730
#: spa/plugins/alsa/acp/alsa-mixer.c:2814
msgid "Internal Microphone"
msgstr "Notranji mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2662
#: spa/plugins/alsa/acp/alsa-mixer.c:2748
#: spa/plugins/alsa/acp/alsa-mixer.c:2731
#: spa/plugins/alsa/acp/alsa-mixer.c:2817
msgid "Radio"
msgstr "Radio"
#: spa/plugins/alsa/acp/alsa-mixer.c:2663
#: spa/plugins/alsa/acp/alsa-mixer.c:2749
#: spa/plugins/alsa/acp/alsa-mixer.c:2732
#: spa/plugins/alsa/acp/alsa-mixer.c:2818
msgid "Video"
msgstr "Video"
#: spa/plugins/alsa/acp/alsa-mixer.c:2664
#: spa/plugins/alsa/acp/alsa-mixer.c:2733
msgid "Automatic Gain Control"
msgstr "Samodejni nadzor ojačanja"
#: spa/plugins/alsa/acp/alsa-mixer.c:2665
#: spa/plugins/alsa/acp/alsa-mixer.c:2734
msgid "No Automatic Gain Control"
msgstr "Brez samodejnega nadzora ojačanja"
#: spa/plugins/alsa/acp/alsa-mixer.c:2666
#: spa/plugins/alsa/acp/alsa-mixer.c:2735
msgid "Boost"
msgstr "Ojačitev"
#: spa/plugins/alsa/acp/alsa-mixer.c:2667
#: spa/plugins/alsa/acp/alsa-mixer.c:2736
msgid "No Boost"
msgstr "Brez ojačitve"
#: spa/plugins/alsa/acp/alsa-mixer.c:2668
#: spa/plugins/alsa/acp/alsa-mixer.c:2737
msgid "Amplifier"
msgstr "Ojačevalnik"
#: spa/plugins/alsa/acp/alsa-mixer.c:2669
#: spa/plugins/alsa/acp/alsa-mixer.c:2738
msgid "No Amplifier"
msgstr "Brez ojačevalnika"
#: spa/plugins/alsa/acp/alsa-mixer.c:2670
#: spa/plugins/alsa/acp/alsa-mixer.c:2739
msgid "Bass Boost"
msgstr "Ojačitev nizkih tonov"
#: spa/plugins/alsa/acp/alsa-mixer.c:2671
#: spa/plugins/alsa/acp/alsa-mixer.c:2740
msgid "No Bass Boost"
msgstr "Brez ojačitve nizkih tonov"
#: spa/plugins/alsa/acp/alsa-mixer.c:2672
#: spa/plugins/bluez5/bluez5-device.c:2378
#: spa/plugins/alsa/acp/alsa-mixer.c:2741
#: spa/plugins/bluez5/bluez5-device.c:2380
msgid "Speaker"
msgstr "Zvočnik"
#. Don't call it "headset", the HF one has the mic
#: spa/plugins/alsa/acp/alsa-mixer.c:2673
#: spa/plugins/alsa/acp/alsa-mixer.c:2751
#: spa/plugins/bluez5/bluez5-device.c:2384
#: spa/plugins/bluez5/bluez5-device.c:2451
#: spa/plugins/alsa/acp/alsa-mixer.c:2742
#: spa/plugins/alsa/acp/alsa-mixer.c:2820
#: spa/plugins/bluez5/bluez5-device.c:2386
#: spa/plugins/bluez5/bluez5-device.c:2453
msgid "Headphones"
msgstr "Slušalke"
#: spa/plugins/alsa/acp/alsa-mixer.c:2740
#: spa/plugins/alsa/acp/alsa-mixer.c:2809
msgid "Analog Input"
msgstr "Analogni vhod"
#: spa/plugins/alsa/acp/alsa-mixer.c:2744
#: spa/plugins/alsa/acp/alsa-mixer.c:2813
msgid "Dock Microphone"
msgstr "Priklopni mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2746
#: spa/plugins/alsa/acp/alsa-mixer.c:2815
msgid "Headset Microphone"
msgstr "Mikrofon s slušalkami"
#: spa/plugins/alsa/acp/alsa-mixer.c:2750
#: spa/plugins/alsa/acp/alsa-mixer.c:2819
msgid "Analog Output"
msgstr "Analogni izhod"
#: spa/plugins/alsa/acp/alsa-mixer.c:2752
#: spa/plugins/alsa/acp/alsa-mixer.c:2821
msgid "Headphones 2"
msgstr "Slušalke 2"
#: spa/plugins/alsa/acp/alsa-mixer.c:2753
#: spa/plugins/alsa/acp/alsa-mixer.c:2822
msgid "Headphones Mono Output"
msgstr "Mono izhod slušalk"
#: spa/plugins/alsa/acp/alsa-mixer.c:2754
#: spa/plugins/alsa/acp/alsa-mixer.c:2823
msgid "Line Out"
msgstr "Linijsk izhod"
msgstr "Linijski izhod"
#: spa/plugins/alsa/acp/alsa-mixer.c:2755
#: spa/plugins/alsa/acp/alsa-mixer.c:2824
msgid "Analog Mono Output"
msgstr "Analogni mono izhod"
#: spa/plugins/alsa/acp/alsa-mixer.c:2756
#: spa/plugins/alsa/acp/alsa-mixer.c:2825
msgid "Speakers"
msgstr "Govorniki"
msgstr "Zvočniki"
#: spa/plugins/alsa/acp/alsa-mixer.c:2757
#: spa/plugins/alsa/acp/alsa-mixer.c:2826
msgid "HDMI / DisplayPort"
msgstr "HDMI / DisplayPort"
#: spa/plugins/alsa/acp/alsa-mixer.c:2758
#: spa/plugins/alsa/acp/alsa-mixer.c:2827
msgid "Digital Output (S/PDIF)"
msgstr "Digitalni izhod (S/PDIF)"
#: spa/plugins/alsa/acp/alsa-mixer.c:2759
#: spa/plugins/alsa/acp/alsa-mixer.c:2828
msgid "Digital Input (S/PDIF)"
msgstr "Digitalni vhod (S/PDIF)"
#: spa/plugins/alsa/acp/alsa-mixer.c:2760
#: spa/plugins/alsa/acp/alsa-mixer.c:2829
msgid "Multichannel Input"
msgstr "Večkanalni vhod"
#: spa/plugins/alsa/acp/alsa-mixer.c:2761
#: spa/plugins/alsa/acp/alsa-mixer.c:2830
msgid "Multichannel Output"
msgstr "Večkanalni izhod"
#: spa/plugins/alsa/acp/alsa-mixer.c:2762
#: spa/plugins/alsa/acp/alsa-mixer.c:2831
msgid "Game Output"
msgstr "Vhod igre"
#: spa/plugins/alsa/acp/alsa-mixer.c:2763
#: spa/plugins/alsa/acp/alsa-mixer.c:2764
#: spa/plugins/alsa/acp/alsa-mixer.c:2832
#: spa/plugins/alsa/acp/alsa-mixer.c:2833
msgid "Chat Output"
msgstr "Izhod klepeta"
#: spa/plugins/alsa/acp/alsa-mixer.c:2765
#: spa/plugins/alsa/acp/alsa-mixer.c:2834
msgid "Chat Input"
msgstr "Vhod klepeta"
#: spa/plugins/alsa/acp/alsa-mixer.c:2766
#: spa/plugins/alsa/acp/alsa-mixer.c:2835
msgid "Virtual Surround 7.1"
msgstr "Navidezni prostorski zvok 7.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4458
#: spa/plugins/alsa/acp/alsa-mixer.c:4522
msgid "Analog Mono"
msgstr "Analogni mono"
#: spa/plugins/alsa/acp/alsa-mixer.c:4459
#: spa/plugins/alsa/acp/alsa-mixer.c:4523
msgid "Analog Mono (Left)"
msgstr "Analogni mono (levo)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4460
#: spa/plugins/alsa/acp/alsa-mixer.c:4524
msgid "Analog Mono (Right)"
msgstr "Analogni mono (desno)"
@ -417,142 +417,142 @@ msgstr "Analogni mono (desno)"
#. * here would lead to the source name to become "Analog Stereo Input
#. * Input". The same logic applies to analog-stereo-output,
#. * multichannel-input and multichannel-output.
#: spa/plugins/alsa/acp/alsa-mixer.c:4461
#: spa/plugins/alsa/acp/alsa-mixer.c:4469
#: spa/plugins/alsa/acp/alsa-mixer.c:4470
#: spa/plugins/alsa/acp/alsa-mixer.c:4525
#: spa/plugins/alsa/acp/alsa-mixer.c:4533
#: spa/plugins/alsa/acp/alsa-mixer.c:4534
msgid "Analog Stereo"
msgstr "Analogni stereo"
#: spa/plugins/alsa/acp/alsa-mixer.c:4462
#: spa/plugins/alsa/acp/alsa-mixer.c:4526
msgid "Mono"
msgstr "Mono"
#: spa/plugins/alsa/acp/alsa-mixer.c:4463
#: spa/plugins/alsa/acp/alsa-mixer.c:4527
msgid "Stereo"
msgstr "Stereo"
#: spa/plugins/alsa/acp/alsa-mixer.c:4471
#: spa/plugins/alsa/acp/alsa-mixer.c:4629
#: spa/plugins/bluez5/bluez5-device.c:2360
#: spa/plugins/alsa/acp/alsa-mixer.c:4535
#: spa/plugins/alsa/acp/alsa-mixer.c:4693
#: spa/plugins/bluez5/bluez5-device.c:2362
msgid "Headset"
msgstr "Slušalka"
#: spa/plugins/alsa/acp/alsa-mixer.c:4472
#: spa/plugins/alsa/acp/alsa-mixer.c:4630
#: spa/plugins/alsa/acp/alsa-mixer.c:4536
#: spa/plugins/alsa/acp/alsa-mixer.c:4694
msgid "Speakerphone"
msgstr "Zvočnik telefona"
#: spa/plugins/alsa/acp/alsa-mixer.c:4473
#: spa/plugins/alsa/acp/alsa-mixer.c:4474
#: spa/plugins/alsa/acp/alsa-mixer.c:4537
#: spa/plugins/alsa/acp/alsa-mixer.c:4538
msgid "Multichannel"
msgstr "Večkanalno"
#: spa/plugins/alsa/acp/alsa-mixer.c:4475
#: spa/plugins/alsa/acp/alsa-mixer.c:4539
msgid "Analog Surround 2.1"
msgstr "Analogni prostorski zvok 2.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4476
#: spa/plugins/alsa/acp/alsa-mixer.c:4540
msgid "Analog Surround 3.0"
msgstr "Analogni prostorski zvok 3.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4477
#: spa/plugins/alsa/acp/alsa-mixer.c:4541
msgid "Analog Surround 3.1"
msgstr "Analogni prostorski zvok 3.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4478
#: spa/plugins/alsa/acp/alsa-mixer.c:4542
msgid "Analog Surround 4.0"
msgstr "Analogni prostorski zvok 4.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4479
#: spa/plugins/alsa/acp/alsa-mixer.c:4543
msgid "Analog Surround 4.1"
msgstr "Analogni prostorski zvok 4.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4480
#: spa/plugins/alsa/acp/alsa-mixer.c:4544
msgid "Analog Surround 5.0"
msgstr "Analogni prostorski zvok 5.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4481
#: spa/plugins/alsa/acp/alsa-mixer.c:4545
msgid "Analog Surround 5.1"
msgstr "Analogni prostorski zvok 5.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4482
#: spa/plugins/alsa/acp/alsa-mixer.c:4546
msgid "Analog Surround 6.0"
msgstr "Analogni prostorski zvok 6.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4483
#: spa/plugins/alsa/acp/alsa-mixer.c:4547
msgid "Analog Surround 6.1"
msgstr "Analogni prostorski zvok 6.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4484
#: spa/plugins/alsa/acp/alsa-mixer.c:4548
msgid "Analog Surround 7.0"
msgstr "Analogni prostorski zvok 7.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4485
#: spa/plugins/alsa/acp/alsa-mixer.c:4549
msgid "Analog Surround 7.1"
msgstr "Analogni prostorski zvok 7.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4486
#: spa/plugins/alsa/acp/alsa-mixer.c:4550
msgid "Digital Stereo (IEC958)"
msgstr "Digitalni stereo (IEC958)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4487
#: spa/plugins/alsa/acp/alsa-mixer.c:4551
msgid "Digital Surround 4.0 (IEC958/AC3)"
msgstr "Digitalni prostorski zvok 4.0 (IEC958/AC3)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4488
#: spa/plugins/alsa/acp/alsa-mixer.c:4552
msgid "Digital Surround 5.1 (IEC958/AC3)"
msgstr "Digitalni prostorski zvok 5.1 (IEC958/AC3)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4489
#: spa/plugins/alsa/acp/alsa-mixer.c:4553
msgid "Digital Surround 5.1 (IEC958/DTS)"
msgstr "Digitalni prostorski zvok 5.1 (IEC958/DTS)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4490
#: spa/plugins/alsa/acp/alsa-mixer.c:4554
msgid "Digital Stereo (HDMI)"
msgstr "Digitalni stereo (HDMI)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4491
#: spa/plugins/alsa/acp/alsa-mixer.c:4555
msgid "Digital Surround 5.1 (HDMI)"
msgstr "Digitalni prostorski zvok 5.1 (HDMI)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4492
#: spa/plugins/alsa/acp/alsa-mixer.c:4556
msgid "Chat"
msgstr "Klepet"
#: spa/plugins/alsa/acp/alsa-mixer.c:4493
#: spa/plugins/alsa/acp/alsa-mixer.c:4557
msgid "Game"
msgstr "Igra"
#: spa/plugins/alsa/acp/alsa-mixer.c:4627
#: spa/plugins/alsa/acp/alsa-mixer.c:4691
msgid "Analog Mono Duplex"
msgstr "Analogni mono dupleks"
#: spa/plugins/alsa/acp/alsa-mixer.c:4628
#: spa/plugins/alsa/acp/alsa-mixer.c:4692
msgid "Analog Stereo Duplex"
msgstr "Analogni stereo dupleks"
#: spa/plugins/alsa/acp/alsa-mixer.c:4631
#: spa/plugins/alsa/acp/alsa-mixer.c:4695
msgid "Digital Stereo Duplex (IEC958)"
msgstr "Digitalni stereo dupleks (IEC958)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4632
#: spa/plugins/alsa/acp/alsa-mixer.c:4696
msgid "Multichannel Duplex"
msgstr "Večkanalni dupleks"
#: spa/plugins/alsa/acp/alsa-mixer.c:4633
#: spa/plugins/alsa/acp/alsa-mixer.c:4697
msgid "Stereo Duplex"
msgstr "Stereo dupleks"
#: spa/plugins/alsa/acp/alsa-mixer.c:4634
#: spa/plugins/alsa/acp/alsa-mixer.c:4698
msgid "Mono Chat + 7.1 Surround"
msgstr "Mono klepet + prostorski zvok 7.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4735
#: spa/plugins/alsa/acp/alsa-mixer.c:4799
#, c-format
msgid "%s Output"
msgstr "Izhod %s"
#: spa/plugins/alsa/acp/alsa-mixer.c:4743
#: spa/plugins/alsa/acp/alsa-mixer.c:4807
#, c-format
msgid "%s Input"
msgstr "Vhod %s"
@ -592,13 +592,13 @@ msgstr[3] ""
#: spa/plugins/alsa/acp/alsa-util.c:1299
#, c-format
msgid ""
"snd_pcm_delay() returned a value that is exceptionally large: %li byte "
"(%s%lu ms).\n"
"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s"
"%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
"to the ALSA developers."
msgid_plural ""
"snd_pcm_delay() returned a value that is exceptionally large: %li bytes "
"(%s%lu ms).\n"
"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s"
"%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
"to the ALSA developers."
msgstr[0] ""
@ -668,7 +668,7 @@ msgstr[3] ""
"Najverjetneje je to napaka v gonilniku ALSA »%s«. O tej težavi obvestite "
"razvijalce ALSA."
#: spa/plugins/alsa/acp/channelmap.h:457
#: spa/plugins/alsa/acp/channelmap.h:460
msgid "(invalid)"
msgstr "(neveljavno)"
@ -680,100 +680,100 @@ msgstr "Vgrajen zvok"
msgid "Modem"
msgstr "Modem"
#: spa/plugins/bluez5/bluez5-device.c:1985
#: spa/plugins/bluez5/bluez5-device.c:1987
msgid "Audio Gateway (A2DP Source & HSP/HFP AG)"
msgstr "Zvožni prehod (vir A2DP in HSP/HFP AG)"
#: spa/plugins/bluez5/bluez5-device.c:2014
#: spa/plugins/bluez5/bluez5-device.c:2016
msgid "Audio Streaming for Hearing Aids (ASHA Sink)"
msgstr "Pretakanje zvoka za slušne aparate (ponor ASHA)"
#: spa/plugins/bluez5/bluez5-device.c:2057
#: spa/plugins/bluez5/bluez5-device.c:2059
#, c-format
msgid "High Fidelity Playback (A2DP Sink, codec %s)"
msgstr "Predvajanje visoke ločljivosti (ponor A2DP, kodek %s)"
#: spa/plugins/bluez5/bluez5-device.c:2060
#: spa/plugins/bluez5/bluez5-device.c:2062
#, c-format
msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)"
msgstr "Dupleks visoke ločljivosti (vir/ponor A2DP, kodek %s)"
#: spa/plugins/bluez5/bluez5-device.c:2068
#: spa/plugins/bluez5/bluez5-device.c:2070
msgid "High Fidelity Playback (A2DP Sink)"
msgstr "Predvajanje visoke ločljivosti (ponor A2DP)"
#: spa/plugins/bluez5/bluez5-device.c:2070
#: spa/plugins/bluez5/bluez5-device.c:2072
msgid "High Fidelity Duplex (A2DP Source/Sink)"
msgstr "Dupleks visoke ločljivosti (vir/ponor A2DP)"
#: spa/plugins/bluez5/bluez5-device.c:2144
#: spa/plugins/bluez5/bluez5-device.c:2146
#, c-format
msgid "High Fidelity Playback (BAP Sink, codec %s)"
msgstr "Predvajanje visoke ločljivosti (ponor BAP, kodek %s)"
#: spa/plugins/bluez5/bluez5-device.c:2149
#: spa/plugins/bluez5/bluez5-device.c:2151
#, c-format
msgid "High Fidelity Input (BAP Source, codec %s)"
msgstr "Vhod visoke ločljivosti (vir BAP, kodek %s)"
#: spa/plugins/bluez5/bluez5-device.c:2153
#: spa/plugins/bluez5/bluez5-device.c:2155
#, c-format
msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)"
msgstr "Dupleks visoke ločljivosti (vir/ponor BAP, kodek %s)"
#: spa/plugins/bluez5/bluez5-device.c:2162
#: spa/plugins/bluez5/bluez5-device.c:2164
msgid "High Fidelity Playback (BAP Sink)"
msgstr "Predvajanje visoke ločljivosti (ponor BAP)"
#: spa/plugins/bluez5/bluez5-device.c:2166
#: spa/plugins/bluez5/bluez5-device.c:2168
msgid "High Fidelity Input (BAP Source)"
msgstr "Vhod visoke ločljivosti (vir BAP)"
#: spa/plugins/bluez5/bluez5-device.c:2169
#: spa/plugins/bluez5/bluez5-device.c:2171
msgid "High Fidelity Duplex (BAP Source/Sink)"
msgstr "Dupleks visoke ločljivosti (vir/ponor BAP)"
#: spa/plugins/bluez5/bluez5-device.c:2209
#: spa/plugins/bluez5/bluez5-device.c:2211
#, c-format
msgid "Headset Head Unit (HSP/HFP, codec %s)"
msgstr "Naglavna enota slušalk (HSP/HFP, kodek %s)"
#: spa/plugins/bluez5/bluez5-device.c:2361
#: spa/plugins/bluez5/bluez5-device.c:2366
#: spa/plugins/bluez5/bluez5-device.c:2373
#: spa/plugins/bluez5/bluez5-device.c:2379
#: spa/plugins/bluez5/bluez5-device.c:2385
#: spa/plugins/bluez5/bluez5-device.c:2391
#: spa/plugins/bluez5/bluez5-device.c:2397
#: spa/plugins/bluez5/bluez5-device.c:2403
#: spa/plugins/bluez5/bluez5-device.c:2409
#: spa/plugins/bluez5/bluez5-device.c:2363
#: spa/plugins/bluez5/bluez5-device.c:2368
#: spa/plugins/bluez5/bluez5-device.c:2375
#: spa/plugins/bluez5/bluez5-device.c:2381
#: spa/plugins/bluez5/bluez5-device.c:2387
#: spa/plugins/bluez5/bluez5-device.c:2393
#: spa/plugins/bluez5/bluez5-device.c:2399
#: spa/plugins/bluez5/bluez5-device.c:2405
#: spa/plugins/bluez5/bluez5-device.c:2411
msgid "Handsfree"
msgstr "Prostoročno telefoniranje"
#: spa/plugins/bluez5/bluez5-device.c:2367
#: spa/plugins/bluez5/bluez5-device.c:2369
msgid "Handsfree (HFP)"
msgstr "Prostoročno telefoniranje (HFP)"
#: spa/plugins/bluez5/bluez5-device.c:2390
#: spa/plugins/bluez5/bluez5-device.c:2392
msgid "Portable"
msgstr "Prenosna naprava"
#: spa/plugins/bluez5/bluez5-device.c:2396
#: spa/plugins/bluez5/bluez5-device.c:2398
msgid "Car"
msgstr "Avtomobil"
#: spa/plugins/bluez5/bluez5-device.c:2402
#: spa/plugins/bluez5/bluez5-device.c:2404
msgid "HiFi"
msgstr "HiFi"
#: spa/plugins/bluez5/bluez5-device.c:2408
#: spa/plugins/bluez5/bluez5-device.c:2410
msgid "Phone"
msgstr "Telefon"
#: spa/plugins/bluez5/bluez5-device.c:2415
#: spa/plugins/bluez5/bluez5-device.c:2417
msgid "Bluetooth"
msgstr "Bluetooth"
#: spa/plugins/bluez5/bluez5-device.c:2416
msgid "Bluetooth (HFP)"
msgstr "Bluetooth (HFP)"
#: spa/plugins/bluez5/bluez5-device.c:2418
msgid "Bluetooth Handsfree"
msgstr "Bluetooth - prostoročno"

341
po/tr.po
View file

@ -1,46 +1,51 @@
# Turkish translation for PipeWire.
# Copyright (C) 2014-2024 PipeWire's COPYRIGHT HOLDER
# Copyright (C) 2014-2025 PipeWire's COPYRIGHT HOLDER
# This file is distributed under the same license as the PipeWire package.
#
# Necdet Yücel <necdetyucel@gmail.com>, 2014.
# Kaan Özdinçer <kaanozdincer@gmail.com>, 2014.
# Muhammet Kara <muhammetk@gmail.com>, 2015, 2016, 2017.
# Oğuz Ersen <oguz@ersen.moe>, 2021-2022.
# Sabri Ünal <libreajans@gmail.com>, 2024.
# Sabri Ünal <yakushabb@gmail.com>, 2024, 2025.
#
msgid ""
msgstr ""
"Project-Id-Version: PipeWire master\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-02-25 03:43+0300\n"
"PO-Revision-Date: 2024-02-25 03:49+0300\n"
"Last-Translator: Sabri Ünal <libreajans@gmail.com>\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/"
"issues\n"
"POT-Creation-Date: 2025-10-24 15:37+0000\n"
"PO-Revision-Date: 2025-10-24 20:15+0300\n"
"Last-Translator: Sabri Ünal <yakushabb@gmail.com>\n"
"Language-Team: Türkçe <takim@gnome.org.tr>\n"
"Language: tr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 3.4.2\n"
"X-Generator: Poedit 3.8\n"
#: src/daemon/pipewire.c:26
#: src/daemon/pipewire.c:29
#, c-format
msgid ""
"%s [options]\n"
" -h, --help Show this help\n"
" -v, --verbose Increase verbosity by one level\n"
" --version Show version\n"
" -c, --config Load config (Default %s)\n"
" -P --properties Set context properties\n"
msgstr ""
"%s [seçenekler]\n"
" -h, --help Bu yardımı göster\n"
" -v, --verbose Ayrıntı düzeyini bir düzey artır\n"
" --version Sürümü göster\n"
" -c, --config Yapılandırmayı yükle (Öntanımlı %s)\n"
" -P --properties Bağlam özelliklerini ayarla\n"
#: src/daemon/pipewire.desktop.in:4
#: src/daemon/pipewire.desktop.in:3
msgid "PipeWire Media System"
msgstr "PipeWire Ortam Sistemi"
#: src/daemon/pipewire.desktop.in:5
#: src/daemon/pipewire.desktop.in:4
msgid "Start the PipeWire Media System"
msgstr "PipeWire Ortam Sistemini Başlat"
@ -54,26 +59,26 @@ msgstr "%s%s%s tüneli"
msgid "Dummy Output"
msgstr "Temsili Çıkış"
#: src/modules/module-pulse-tunnel.c:774
#: src/modules/module-pulse-tunnel.c:760
#, c-format
msgid "Tunnel for %s@%s"
msgstr "%s@%s için tünel"
#: src/modules/module-zeroconf-discover.c:315
#: src/modules/module-zeroconf-discover.c:320
msgid "Unknown device"
msgstr "Bilinmeyen aygıt"
#: src/modules/module-zeroconf-discover.c:327
#: src/modules/module-zeroconf-discover.c:332
#, c-format
msgid "%s on %s@%s"
msgstr "%s, %s@%s"
#: src/modules/module-zeroconf-discover.c:331
#: src/modules/module-zeroconf-discover.c:336
#, c-format
msgid "%s on %s"
msgstr "%s, %s"
#: src/tools/pw-cat.c:991
#: src/tools/pw-cat.c:1096
#, c-format
msgid ""
"%s [options] [<file>|-]\n"
@ -88,7 +93,7 @@ msgstr ""
" -v, --verbose Ayrıntılı işlemleri etkinleştir\n"
"\n"
#: src/tools/pw-cat.c:998
#: src/tools/pw-cat.c:1103
#, c-format
msgid ""
" -R, --remote Remote daemon name\n"
@ -122,7 +127,7 @@ msgstr ""
" -P --properties Düğüm özelliklerini ayarla\n"
"\n"
#: src/tools/pw-cat.c:1016
#: src/tools/pw-cat.c:1121
#, c-format
msgid ""
" --rate Sample rate (req. for rec) (default "
@ -139,6 +144,10 @@ msgid ""
" --volume Stream volume 0-1.0 (default %.3f)\n"
" -q --quality Resampler quality (0 - 15) (default "
"%d)\n"
" -a, --raw RAW mode\n"
" -M, --force-midi Force midi format, one of \"midi\" "
"or \"ump\", (default ump)\n"
" -n, --sample-count COUNT Stop after COUNT samples\n"
"\n"
msgstr ""
" --rate Örnekleme oranı (kayıt için gerekli) "
@ -156,15 +165,21 @@ msgstr ""
"%.3f)\n"
" -q --quality Yeniden örnekleyici kalitesi (0 - "
"15) (öntanımlı %d)\n"
" -a, --raw HAM kipi\n"
" -M, --force-midi Midi biçimini zorla, ikisinden "
"birisi \"midi\" ya da\"ump\", (öntanımlı ump)\n"
" -n, --sample-count COUNT COUNT örnekleme sonrası dur\n"
"\n"
#: src/tools/pw-cat.c:1033
#: src/tools/pw-cat.c:1141
msgid ""
" -p, --playback Playback mode\n"
" -r, --record Recording mode\n"
" -m, --midi Midi mode\n"
" -d, --dsd DSD mode\n"
" -o, --encoded Encoded mode\n"
" -s, --sysex SysEx mode\n"
" -c, --midi-clip MIDI clip mode\n"
"\n"
msgstr ""
" -p, --playback Çalma kipi\n"
@ -172,9 +187,11 @@ msgstr ""
" -m, --midi Midi kipi\n"
" -d, --dsd DSD kipi\n"
" -o, --encoded Kodlanmış kip\n"
" -s, --sysex SysEx kipi\n"
" -c, --midi-clip MIDI klip kipi\n"
"\n"
#: src/tools/pw-cli.c:2252
#: src/tools/pw-cli.c:2386
#, c-format
msgid ""
"%s [options] [command]\n"
@ -193,195 +210,203 @@ msgstr ""
" -r, --remote Uzak arka plan programı adı\n"
" -m, --monitor Etkinliği izle\n"
#: spa/plugins/alsa/acp/acp.c:327
#: spa/plugins/alsa/acp/acp.c:361
msgid "Pro Audio"
msgstr "Profesyonel Ses"
#: spa/plugins/alsa/acp/acp.c:488 spa/plugins/alsa/acp/alsa-mixer.c:4633
#: spa/plugins/bluez5/bluez5-device.c:1701
#: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699
#: spa/plugins/bluez5/bluez5-device.c:1976
msgid "Off"
msgstr "Kapalı"
#: spa/plugins/alsa/acp/alsa-mixer.c:2652
#: spa/plugins/alsa/acp/acp.c:620
#, c-format
msgid "%s [ALSA UCM error]"
msgstr "%s [ALSA UCM hatası]"
#: spa/plugins/alsa/acp/alsa-mixer.c:2721
msgid "Input"
msgstr "Giriş"
#: spa/plugins/alsa/acp/alsa-mixer.c:2653
#: spa/plugins/alsa/acp/alsa-mixer.c:2722
msgid "Docking Station Input"
msgstr "Yerleştirme İstasyonu Girişi"
#: spa/plugins/alsa/acp/alsa-mixer.c:2654
#: spa/plugins/alsa/acp/alsa-mixer.c:2723
msgid "Docking Station Microphone"
msgstr "Yerleştirme İstasyonu Mikrofonu"
#: spa/plugins/alsa/acp/alsa-mixer.c:2655
#: spa/plugins/alsa/acp/alsa-mixer.c:2724
msgid "Docking Station Line In"
msgstr "Yerleştirme İstasyonu Hat Girişi"
#: spa/plugins/alsa/acp/alsa-mixer.c:2656
#: spa/plugins/alsa/acp/alsa-mixer.c:2747
#: spa/plugins/alsa/acp/alsa-mixer.c:2725
#: spa/plugins/alsa/acp/alsa-mixer.c:2816
msgid "Line In"
msgstr "Hat Girişi"
#: spa/plugins/alsa/acp/alsa-mixer.c:2657
#: spa/plugins/alsa/acp/alsa-mixer.c:2741
#: spa/plugins/bluez5/bluez5-device.c:1989
#: spa/plugins/alsa/acp/alsa-mixer.c:2726
#: spa/plugins/alsa/acp/alsa-mixer.c:2810
#: spa/plugins/bluez5/bluez5-device.c:2374
msgid "Microphone"
msgstr "Mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2658
#: spa/plugins/alsa/acp/alsa-mixer.c:2742
#: spa/plugins/alsa/acp/alsa-mixer.c:2727
#: spa/plugins/alsa/acp/alsa-mixer.c:2811
msgid "Front Microphone"
msgstr "Ön Mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2659
#: spa/plugins/alsa/acp/alsa-mixer.c:2743
#: spa/plugins/alsa/acp/alsa-mixer.c:2728
#: spa/plugins/alsa/acp/alsa-mixer.c:2812
msgid "Rear Microphone"
msgstr "Arka Mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2660
#: spa/plugins/alsa/acp/alsa-mixer.c:2729
msgid "External Microphone"
msgstr "Harici Mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2661
#: spa/plugins/alsa/acp/alsa-mixer.c:2745
#: spa/plugins/alsa/acp/alsa-mixer.c:2730
#: spa/plugins/alsa/acp/alsa-mixer.c:2814
msgid "Internal Microphone"
msgstr "Dahili Mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2662
#: spa/plugins/alsa/acp/alsa-mixer.c:2748
#: spa/plugins/alsa/acp/alsa-mixer.c:2731
#: spa/plugins/alsa/acp/alsa-mixer.c:2817
msgid "Radio"
msgstr "Radyo"
#: spa/plugins/alsa/acp/alsa-mixer.c:2663
#: spa/plugins/alsa/acp/alsa-mixer.c:2749
#: spa/plugins/alsa/acp/alsa-mixer.c:2732
#: spa/plugins/alsa/acp/alsa-mixer.c:2818
msgid "Video"
msgstr "Video"
#: spa/plugins/alsa/acp/alsa-mixer.c:2664
#: spa/plugins/alsa/acp/alsa-mixer.c:2733
msgid "Automatic Gain Control"
msgstr "Otomatik Kazanç Denetimi"
#: spa/plugins/alsa/acp/alsa-mixer.c:2665
#: spa/plugins/alsa/acp/alsa-mixer.c:2734
msgid "No Automatic Gain Control"
msgstr "Otomatik Kazanç Denetimi Yok"
#: spa/plugins/alsa/acp/alsa-mixer.c:2666
#: spa/plugins/alsa/acp/alsa-mixer.c:2735
msgid "Boost"
msgstr "Artır"
#: spa/plugins/alsa/acp/alsa-mixer.c:2667
#: spa/plugins/alsa/acp/alsa-mixer.c:2736
msgid "No Boost"
msgstr "Artırma Yok"
#: spa/plugins/alsa/acp/alsa-mixer.c:2668
#: spa/plugins/alsa/acp/alsa-mixer.c:2737
msgid "Amplifier"
msgstr "Yükseltici"
#: spa/plugins/alsa/acp/alsa-mixer.c:2669
#: spa/plugins/alsa/acp/alsa-mixer.c:2738
msgid "No Amplifier"
msgstr "Yükseltici Yok"
#: spa/plugins/alsa/acp/alsa-mixer.c:2670
#: spa/plugins/alsa/acp/alsa-mixer.c:2739
msgid "Bass Boost"
msgstr "Bas Artır"
#: spa/plugins/alsa/acp/alsa-mixer.c:2671
#: spa/plugins/alsa/acp/alsa-mixer.c:2740
msgid "No Bass Boost"
msgstr "Bas Artırma Yok"
#: spa/plugins/alsa/acp/alsa-mixer.c:2672
#: spa/plugins/bluez5/bluez5-device.c:1995
#: spa/plugins/alsa/acp/alsa-mixer.c:2741
#: spa/plugins/bluez5/bluez5-device.c:2380
msgid "Speaker"
msgstr "Hoparlör"
#: spa/plugins/alsa/acp/alsa-mixer.c:2673
#: spa/plugins/alsa/acp/alsa-mixer.c:2751
#. Don't call it "headset", the HF one has the mic
#: spa/plugins/alsa/acp/alsa-mixer.c:2742
#: spa/plugins/alsa/acp/alsa-mixer.c:2820
#: spa/plugins/bluez5/bluez5-device.c:2386
#: spa/plugins/bluez5/bluez5-device.c:2453
msgid "Headphones"
msgstr "Kulaklık"
#: spa/plugins/alsa/acp/alsa-mixer.c:2740
#: spa/plugins/alsa/acp/alsa-mixer.c:2809
msgid "Analog Input"
msgstr "Analog Giriş"
#: spa/plugins/alsa/acp/alsa-mixer.c:2744
#: spa/plugins/alsa/acp/alsa-mixer.c:2813
msgid "Dock Microphone"
msgstr "Yapışık Mikrofon"
#: spa/plugins/alsa/acp/alsa-mixer.c:2746
#: spa/plugins/alsa/acp/alsa-mixer.c:2815
msgid "Headset Microphone"
msgstr "Mikrofonlu Kulaklık"
#: spa/plugins/alsa/acp/alsa-mixer.c:2750
#: spa/plugins/alsa/acp/alsa-mixer.c:2819
msgid "Analog Output"
msgstr "Analog Çıkış"
#: spa/plugins/alsa/acp/alsa-mixer.c:2752
#: spa/plugins/alsa/acp/alsa-mixer.c:2821
msgid "Headphones 2"
msgstr "Kulaklık 2"
#: spa/plugins/alsa/acp/alsa-mixer.c:2753
#: spa/plugins/alsa/acp/alsa-mixer.c:2822
msgid "Headphones Mono Output"
msgstr "Kulaklık Tek Kanallı Çıkış"
#: spa/plugins/alsa/acp/alsa-mixer.c:2754
#: spa/plugins/alsa/acp/alsa-mixer.c:2823
msgid "Line Out"
msgstr "Hat Çıkışı"
#: spa/plugins/alsa/acp/alsa-mixer.c:2755
#: spa/plugins/alsa/acp/alsa-mixer.c:2824
msgid "Analog Mono Output"
msgstr "Analog Tek Kanallı Çıkış"
#: spa/plugins/alsa/acp/alsa-mixer.c:2756
#: spa/plugins/alsa/acp/alsa-mixer.c:2825
msgid "Speakers"
msgstr "Hoparlörler"
#: spa/plugins/alsa/acp/alsa-mixer.c:2757
#: spa/plugins/alsa/acp/alsa-mixer.c:2826
msgid "HDMI / DisplayPort"
msgstr "HDMI / DisplayPort"
#: spa/plugins/alsa/acp/alsa-mixer.c:2758
#: spa/plugins/alsa/acp/alsa-mixer.c:2827
msgid "Digital Output (S/PDIF)"
msgstr "Sayısal Çıkış (S/PDIF)"
#: spa/plugins/alsa/acp/alsa-mixer.c:2759
#: spa/plugins/alsa/acp/alsa-mixer.c:2828
msgid "Digital Input (S/PDIF)"
msgstr "Sayısal Giriş (S/PDIF)"
#: spa/plugins/alsa/acp/alsa-mixer.c:2760
#: spa/plugins/alsa/acp/alsa-mixer.c:2829
msgid "Multichannel Input"
msgstr "Çok Kanallı Giriş"
#: spa/plugins/alsa/acp/alsa-mixer.c:2761
#: spa/plugins/alsa/acp/alsa-mixer.c:2830
msgid "Multichannel Output"
msgstr "Çok Kanallı Çıkış"
#: spa/plugins/alsa/acp/alsa-mixer.c:2762
#: spa/plugins/alsa/acp/alsa-mixer.c:2831
msgid "Game Output"
msgstr "Oyun Çıkışı"
#: spa/plugins/alsa/acp/alsa-mixer.c:2763
#: spa/plugins/alsa/acp/alsa-mixer.c:2764
#: spa/plugins/alsa/acp/alsa-mixer.c:2832
#: spa/plugins/alsa/acp/alsa-mixer.c:2833
msgid "Chat Output"
msgstr "Sohbet Çıkışı"
#: spa/plugins/alsa/acp/alsa-mixer.c:2765
#: spa/plugins/alsa/acp/alsa-mixer.c:2834
msgid "Chat Input"
msgstr "Sohbet Girişi"
#: spa/plugins/alsa/acp/alsa-mixer.c:2766
#: spa/plugins/alsa/acp/alsa-mixer.c:2835
msgid "Virtual Surround 7.1"
msgstr "Sanal Çevresel Ses 7.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4456
#: spa/plugins/alsa/acp/alsa-mixer.c:4522
msgid "Analog Mono"
msgstr "Analog Tek Kanallı"
#: spa/plugins/alsa/acp/alsa-mixer.c:4457
#: spa/plugins/alsa/acp/alsa-mixer.c:4523
msgid "Analog Mono (Left)"
msgstr "Analog Tek Kanallı (Sol)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4458
#: spa/plugins/alsa/acp/alsa-mixer.c:4524
msgid "Analog Mono (Right)"
msgstr "Analog Tek Kanallı (Sağ)"
@ -390,147 +415,147 @@ msgstr "Analog Tek Kanallı (Sağ)"
#. * here would lead to the source name to become "Analog Stereo Input
#. * Input". The same logic applies to analog-stereo-output,
#. * multichannel-input and multichannel-output.
#: spa/plugins/alsa/acp/alsa-mixer.c:4459
#: spa/plugins/alsa/acp/alsa-mixer.c:4467
#: spa/plugins/alsa/acp/alsa-mixer.c:4468
#: spa/plugins/alsa/acp/alsa-mixer.c:4525
#: spa/plugins/alsa/acp/alsa-mixer.c:4533
#: spa/plugins/alsa/acp/alsa-mixer.c:4534
msgid "Analog Stereo"
msgstr "Analog Stereo"
#: spa/plugins/alsa/acp/alsa-mixer.c:4460
#: spa/plugins/alsa/acp/alsa-mixer.c:4526
msgid "Mono"
msgstr "Tek Kanallı"
#: spa/plugins/alsa/acp/alsa-mixer.c:4461
#: spa/plugins/alsa/acp/alsa-mixer.c:4527
msgid "Stereo"
msgstr "Stereo"
#: spa/plugins/alsa/acp/alsa-mixer.c:4469
#: spa/plugins/alsa/acp/alsa-mixer.c:4627
#: spa/plugins/bluez5/bluez5-device.c:1977
#: spa/plugins/alsa/acp/alsa-mixer.c:4535
#: spa/plugins/alsa/acp/alsa-mixer.c:4693
#: spa/plugins/bluez5/bluez5-device.c:2362
msgid "Headset"
msgstr "Kulaklık"
#: spa/plugins/alsa/acp/alsa-mixer.c:4470
#: spa/plugins/alsa/acp/alsa-mixer.c:4628
#: spa/plugins/alsa/acp/alsa-mixer.c:4536
#: spa/plugins/alsa/acp/alsa-mixer.c:4694
msgid "Speakerphone"
msgstr "Hoparlör"
#: spa/plugins/alsa/acp/alsa-mixer.c:4471
#: spa/plugins/alsa/acp/alsa-mixer.c:4472
#: spa/plugins/alsa/acp/alsa-mixer.c:4537
#: spa/plugins/alsa/acp/alsa-mixer.c:4538
msgid "Multichannel"
msgstr "Çok kanallı"
#: spa/plugins/alsa/acp/alsa-mixer.c:4473
#: spa/plugins/alsa/acp/alsa-mixer.c:4539
msgid "Analog Surround 2.1"
msgstr "Analog Çevresel Ses 2.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4474
#: spa/plugins/alsa/acp/alsa-mixer.c:4540
msgid "Analog Surround 3.0"
msgstr "Analog Çevresel Ses 3.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4475
#: spa/plugins/alsa/acp/alsa-mixer.c:4541
msgid "Analog Surround 3.1"
msgstr "Analog Çevresel Ses 3.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4476
#: spa/plugins/alsa/acp/alsa-mixer.c:4542
msgid "Analog Surround 4.0"
msgstr "Analog Çevresel Ses 4.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4477
#: spa/plugins/alsa/acp/alsa-mixer.c:4543
msgid "Analog Surround 4.1"
msgstr "Analog Çevresel Ses 4.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4478
#: spa/plugins/alsa/acp/alsa-mixer.c:4544
msgid "Analog Surround 5.0"
msgstr "Analog Çevresel Ses 5.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4479
#: spa/plugins/alsa/acp/alsa-mixer.c:4545
msgid "Analog Surround 5.1"
msgstr "Analog Çevresel Ses 5.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4480
#: spa/plugins/alsa/acp/alsa-mixer.c:4546
msgid "Analog Surround 6.0"
msgstr "Analog Çevresel Ses 6.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4481
#: spa/plugins/alsa/acp/alsa-mixer.c:4547
msgid "Analog Surround 6.1"
msgstr "Analog Çevresel Ses 6.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4482
#: spa/plugins/alsa/acp/alsa-mixer.c:4548
msgid "Analog Surround 7.0"
msgstr "Analog Çevresel Ses 7.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4483
#: spa/plugins/alsa/acp/alsa-mixer.c:4549
msgid "Analog Surround 7.1"
msgstr "Analog Çevresel Ses 7.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4484
#: spa/plugins/alsa/acp/alsa-mixer.c:4550
msgid "Digital Stereo (IEC958)"
msgstr "Sayısal Stereo (IEC958)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4485
#: spa/plugins/alsa/acp/alsa-mixer.c:4551
msgid "Digital Surround 4.0 (IEC958/AC3)"
msgstr "Sayısal Çevresel Ses 4.0 (IEC958/AC3)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4486
#: spa/plugins/alsa/acp/alsa-mixer.c:4552
msgid "Digital Surround 5.1 (IEC958/AC3)"
msgstr "Sayısal Çevresel Ses 5.1 (IEC958/AC3)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4487
#: spa/plugins/alsa/acp/alsa-mixer.c:4553
msgid "Digital Surround 5.1 (IEC958/DTS)"
msgstr "Sayısal Çevresel Ses 5.1 (IEC958/DTS)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4488
#: spa/plugins/alsa/acp/alsa-mixer.c:4554
msgid "Digital Stereo (HDMI)"
msgstr "Sayısal Stereo (HDMI)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4489
#: spa/plugins/alsa/acp/alsa-mixer.c:4555
msgid "Digital Surround 5.1 (HDMI)"
msgstr "Sayısal Çevresel Ses 5.1 (HDMI)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4490
#: spa/plugins/alsa/acp/alsa-mixer.c:4556
msgid "Chat"
msgstr "Sohbet"
#: spa/plugins/alsa/acp/alsa-mixer.c:4491
#: spa/plugins/alsa/acp/alsa-mixer.c:4557
msgid "Game"
msgstr "Oyun"
#: spa/plugins/alsa/acp/alsa-mixer.c:4625
#: spa/plugins/alsa/acp/alsa-mixer.c:4691
msgid "Analog Mono Duplex"
msgstr "Analog Tek Kanallı İkili"
#: spa/plugins/alsa/acp/alsa-mixer.c:4626
#: spa/plugins/alsa/acp/alsa-mixer.c:4692
msgid "Analog Stereo Duplex"
msgstr "Analog İkili Stereo"
#: spa/plugins/alsa/acp/alsa-mixer.c:4629
#: spa/plugins/alsa/acp/alsa-mixer.c:4695
msgid "Digital Stereo Duplex (IEC958)"
msgstr "Sayısal İkili Stereo (IEC958)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4630
#: spa/plugins/alsa/acp/alsa-mixer.c:4696
msgid "Multichannel Duplex"
msgstr "Çok Kanallı İkili"
#: spa/plugins/alsa/acp/alsa-mixer.c:4631
#: spa/plugins/alsa/acp/alsa-mixer.c:4697
msgid "Stereo Duplex"
msgstr "İkili Stereo"
#: spa/plugins/alsa/acp/alsa-mixer.c:4632
#: spa/plugins/alsa/acp/alsa-mixer.c:4698
msgid "Mono Chat + 7.1 Surround"
msgstr "Tek Kanallı Sohbet + 7.1 Çevresel Ses"
#: spa/plugins/alsa/acp/alsa-mixer.c:4733
#: spa/plugins/alsa/acp/alsa-mixer.c:4799
#, c-format
msgid "%s Output"
msgstr "%s Çıkışı"
#: spa/plugins/alsa/acp/alsa-mixer.c:4741
#: spa/plugins/alsa/acp/alsa-mixer.c:4807
#, c-format
msgid "%s Input"
msgstr "%s Girişi"
#: spa/plugins/alsa/acp/alsa-util.c:1220 spa/plugins/alsa/acp/alsa-util.c:1314
#: spa/plugins/alsa/acp/alsa-util.c:1233 spa/plugins/alsa/acp/alsa-util.c:1327
#, c-format
msgid ""
"snd_pcm_avail() returned a value that is exceptionally large: %lu byte (%lu "
@ -547,16 +572,16 @@ msgstr[0] ""
"Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA "
"geliştiricilerine bildirin."
#: spa/plugins/alsa/acp/alsa-util.c:1286
#: spa/plugins/alsa/acp/alsa-util.c:1299
#, c-format
msgid ""
"snd_pcm_delay() returned a value that is exceptionally large: %li byte (%s"
"%lu ms).\n"
"snd_pcm_delay() returned a value that is exceptionally large: %li byte "
"(%s%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
"to the ALSA developers."
msgid_plural ""
"snd_pcm_delay() returned a value that is exceptionally large: %li bytes (%s"
"%lu ms).\n"
"snd_pcm_delay() returned a value that is exceptionally large: %li bytes "
"(%s%lu ms).\n"
"Most likely this is a bug in the ALSA driver '%s'. Please report this issue "
"to the ALSA developers."
msgstr[0] ""
@ -564,7 +589,7 @@ msgstr[0] ""
"Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA "
"geliştiricilerine bildirin."
#: spa/plugins/alsa/acp/alsa-util.c:1333
#: spa/plugins/alsa/acp/alsa-util.c:1346
#, c-format
msgid ""
"snd_pcm_avail_delay() returned strange values: delay %lu is less than avail "
@ -577,7 +602,7 @@ msgstr ""
"Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA "
"geliştiricilerine bildirin."
#: spa/plugins/alsa/acp/alsa-util.c:1376
#: spa/plugins/alsa/acp/alsa-util.c:1389
#, c-format
msgid ""
"snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu byte "
@ -595,112 +620,112 @@ msgstr[0] ""
"Büyük ihtimalle bu bir ALSA sürücüsü '%s' hatasıdır. Lütfen bu sorunu ALSA "
"geliştiricilerine bildirin."
#: spa/plugins/alsa/acp/channelmap.h:457
#: spa/plugins/alsa/acp/channelmap.h:460
msgid "(invalid)"
msgstr "(geçersiz)"
#: spa/plugins/alsa/acp/compat.c:193
#: spa/plugins/alsa/acp/compat.c:194
msgid "Built-in Audio"
msgstr "Dahili Ses"
#: spa/plugins/alsa/acp/compat.c:198
#: spa/plugins/alsa/acp/compat.c:199
msgid "Modem"
msgstr "Modem"
#: spa/plugins/bluez5/bluez5-device.c:1712
#: spa/plugins/bluez5/bluez5-device.c:1987
msgid "Audio Gateway (A2DP Source & HSP/HFP AG)"
msgstr "Ses Geçidi (A2DP Kaynak & HSP/HFP AG)"
#: spa/plugins/bluez5/bluez5-device.c:1760
#: spa/plugins/bluez5/bluez5-device.c:2016
msgid "Audio Streaming for Hearing Aids (ASHA Sink)"
msgstr "İşitme Aygıtları İçin Ses Akışı (ASHA Alıcı)"
#: spa/plugins/bluez5/bluez5-device.c:2059
#, c-format
msgid "High Fidelity Playback (A2DP Sink, codec %s)"
msgstr "Yüksek Kaliteli Çalma (A2DP Alıcı, çözücü %s)"
#: spa/plugins/bluez5/bluez5-device.c:1763
#: spa/plugins/bluez5/bluez5-device.c:2062
#, c-format
msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)"
msgstr "Yüksek Kaliteli İkili (A2DP Kaynak/Alıcı, çözücü %s)"
#: spa/plugins/bluez5/bluez5-device.c:1771
#: spa/plugins/bluez5/bluez5-device.c:2070
msgid "High Fidelity Playback (A2DP Sink)"
msgstr "Yüksek Kaliteli Çalma (A2DP Alıcı)"
#: spa/plugins/bluez5/bluez5-device.c:1773
#: spa/plugins/bluez5/bluez5-device.c:2072
msgid "High Fidelity Duplex (A2DP Source/Sink)"
msgstr "Yüksek Kaliteli İkili (A2DP Kaynak/Alıcı)"
#: spa/plugins/bluez5/bluez5-device.c:1823
#: spa/plugins/bluez5/bluez5-device.c:2146
#, c-format
msgid "High Fidelity Playback (BAP Sink, codec %s)"
msgstr "Yüksek Kaliteli Çalma (BAP Alıcı, çözücü %s)"
#: spa/plugins/bluez5/bluez5-device.c:1828
#: spa/plugins/bluez5/bluez5-device.c:2151
#, c-format
msgid "High Fidelity Input (BAP Source, codec %s)"
msgstr "Yüksek Kaliteli Giriş (BAP Kaynak, çözücü %s)"
#: spa/plugins/bluez5/bluez5-device.c:1832
#: spa/plugins/bluez5/bluez5-device.c:2155
#, c-format
msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)"
msgstr "Yüksek Kaliteli İkili (BAP Kaynak/Alıcı, çözücü %s)"
#: spa/plugins/bluez5/bluez5-device.c:1841
#: spa/plugins/bluez5/bluez5-device.c:2164
msgid "High Fidelity Playback (BAP Sink)"
msgstr "Yüksek Kaliteli Çalma (BAP Alıcı)"
#: spa/plugins/bluez5/bluez5-device.c:1845
#: spa/plugins/bluez5/bluez5-device.c:2168
msgid "High Fidelity Input (BAP Source)"
msgstr "Yüksek Kaliteli Giriş (BAP Kaynak)"
#: spa/plugins/bluez5/bluez5-device.c:1848
#: spa/plugins/bluez5/bluez5-device.c:2171
msgid "High Fidelity Duplex (BAP Source/Sink)"
msgstr "Yüksek Kaliteli İkili (BAP Kaynak/Alıcı)"
#: spa/plugins/bluez5/bluez5-device.c:1897
#: spa/plugins/bluez5/bluez5-device.c:2211
#, c-format
msgid "Headset Head Unit (HSP/HFP, codec %s)"
msgstr "Kulaklık Ana Birimi (HSP/HFP, çözücü %s)"
#: spa/plugins/bluez5/bluez5-device.c:1978
#: spa/plugins/bluez5/bluez5-device.c:1983
#: spa/plugins/bluez5/bluez5-device.c:1990
#: spa/plugins/bluez5/bluez5-device.c:1996
#: spa/plugins/bluez5/bluez5-device.c:2002
#: spa/plugins/bluez5/bluez5-device.c:2008
#: spa/plugins/bluez5/bluez5-device.c:2014
#: spa/plugins/bluez5/bluez5-device.c:2020
#: spa/plugins/bluez5/bluez5-device.c:2026
#: spa/plugins/bluez5/bluez5-device.c:2363
#: spa/plugins/bluez5/bluez5-device.c:2368
#: spa/plugins/bluez5/bluez5-device.c:2375
#: spa/plugins/bluez5/bluez5-device.c:2381
#: spa/plugins/bluez5/bluez5-device.c:2387
#: spa/plugins/bluez5/bluez5-device.c:2393
#: spa/plugins/bluez5/bluez5-device.c:2399
#: spa/plugins/bluez5/bluez5-device.c:2405
#: spa/plugins/bluez5/bluez5-device.c:2411
msgid "Handsfree"
msgstr "Ahizesiz"
#: spa/plugins/bluez5/bluez5-device.c:1984
#: spa/plugins/bluez5/bluez5-device.c:2369
msgid "Handsfree (HFP)"
msgstr "Ahizesiz (HFP)"
#: spa/plugins/bluez5/bluez5-device.c:2001
msgid "Headphone"
msgstr "Kulaklık"
#: spa/plugins/bluez5/bluez5-device.c:2007
#: spa/plugins/bluez5/bluez5-device.c:2392
msgid "Portable"
msgstr "Taşınabilir"
#: spa/plugins/bluez5/bluez5-device.c:2013
#: spa/plugins/bluez5/bluez5-device.c:2398
msgid "Car"
msgstr "Araba"
#: spa/plugins/bluez5/bluez5-device.c:2019
#: spa/plugins/bluez5/bluez5-device.c:2404
msgid "HiFi"
msgstr "Yüksek Kalite"
#: spa/plugins/bluez5/bluez5-device.c:2025
#: spa/plugins/bluez5/bluez5-device.c:2410
msgid "Phone"
msgstr "Telefon"
#: spa/plugins/bluez5/bluez5-device.c:2032
#: spa/plugins/bluez5/bluez5-device.c:2417
msgid "Bluetooth"
msgstr "Bluetooth"
#: spa/plugins/bluez5/bluez5-device.c:2033
#: spa/plugins/bluez5/bluez5-device.c:2418
msgid "Bluetooth (HFP)"
msgstr "Bluetooth (HFP)"

View file

@ -13,8 +13,8 @@ msgstr ""
"Project-Id-Version: pipewire.master-tx\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/"
"issues\n"
"POT-Creation-Date: 2025-09-21 15:33+0000\n"
"PO-Revision-Date: 2025-09-22 08:53+0800\n"
"POT-Creation-Date: 2025-11-25 15:35+0000\n"
"PO-Revision-Date: 2025-11-26 10:19+0800\n"
"Last-Translator: lumingzh <lumingzh@qq.com>\n"
"Language-Team: Chinese (China) <i18n-zh@googlegroups.com>\n"
"Language: zh_CN\n"
@ -60,7 +60,7 @@ msgstr "至 %s%s%s 的隧道"
msgid "Dummy Output"
msgstr "虚拟输出"
#: src/modules/module-pulse-tunnel.c:760
#: src/modules/module-pulse-tunnel.c:761
#, c-format
msgid "Tunnel for %s@%s"
msgstr "用于 %s@%s 的隧道"
@ -79,7 +79,7 @@ msgstr "%2$s@%3$s 上的 %1$s"
msgid "%s on %s"
msgstr "%2$s 上的 %1$s"
#: src/tools/pw-cat.c:1084
#: src/tools/pw-cat.c:1088
#, c-format
msgid ""
"%s [options] [<file>|-]\n"
@ -94,7 +94,7 @@ msgstr ""
" -v, --verbose 输出详细操作\n"
"\n"
#: src/tools/pw-cat.c:1091
#: src/tools/pw-cat.c:1095
#, c-format
msgid ""
" -R, --remote Remote daemon name\n"
@ -126,7 +126,7 @@ msgstr ""
" -P --properties 设置节点属性\n"
"\n"
#: src/tools/pw-cat.c:1109
#: src/tools/pw-cat.c:1113
#, c-format
msgid ""
" --rate Sample rate (req. for rec) (default "
@ -134,8 +134,8 @@ msgid ""
" --channels Number of channels (req. for rec) "
"(default %u)\n"
" --channel-map Channel map\n"
" one of: \"stereo\", "
"\"surround-51\",... or\n"
" one of: \"Stereo\", \"5.1\",... "
"or\n"
" comma separated list of channel "
"names: eg. \"FL,FR\"\n"
" --format Sample format %s (req. for rec) "
@ -152,9 +152,9 @@ msgstr ""
" --rate 采样率 (录制模式需要) (默认 %u)\n"
" --channels 通道数 (录制模式需要) (默认 %u)\n"
" --channel-map 通道映射\n"
" \"stereo\", \"surround-51\",... "
"中的其一或\n"
" 以\",\"分隔的通道名列表: 如 "
" \"stereo\", \"5.1\",... 中的其一"
"或\n"
" 以英文逗号分隔的通道名列表: 如 "
"\"FL,FR\"\n"
" --format 采样格式 %s (录制模式需要) (默认 "
"%s)\n"
@ -166,7 +166,7 @@ msgstr ""
" -n, --sample-count COUNT 计数采样后停止\n"
"\n"
#: src/tools/pw-cat.c:1129
#: src/tools/pw-cat.c:1133
msgid ""
" -p, --playback Playback mode\n"
" -r, --record Recording mode\n"
@ -204,203 +204,203 @@ msgstr ""
" -m, --monitor 监视器活动\n"
"\n"
#: spa/plugins/alsa/acp/acp.c:351
#: spa/plugins/alsa/acp/acp.c:361
msgid "Pro Audio"
msgstr "专业音频"
#: spa/plugins/alsa/acp/acp.c:527 spa/plugins/alsa/acp/alsa-mixer.c:4635
#: spa/plugins/bluez5/bluez5-device.c:1974
#: spa/plugins/alsa/acp/acp.c:537 spa/plugins/alsa/acp/alsa-mixer.c:4699
#: spa/plugins/bluez5/bluez5-device.c:1976
msgid "Off"
msgstr "关"
#: spa/plugins/alsa/acp/acp.c:610
#: spa/plugins/alsa/acp/acp.c:620
#, c-format
msgid "%s [ALSA UCM error]"
msgstr "%s [ALSA UCM 错误]"
#: spa/plugins/alsa/acp/alsa-mixer.c:2652
#: spa/plugins/alsa/acp/alsa-mixer.c:2721
msgid "Input"
msgstr "输入"
#: spa/plugins/alsa/acp/alsa-mixer.c:2653
#: spa/plugins/alsa/acp/alsa-mixer.c:2722
msgid "Docking Station Input"
msgstr "扩展坞输入"
#: spa/plugins/alsa/acp/alsa-mixer.c:2654
#: spa/plugins/alsa/acp/alsa-mixer.c:2723
msgid "Docking Station Microphone"
msgstr "扩展坞话筒"
#: spa/plugins/alsa/acp/alsa-mixer.c:2655
#: spa/plugins/alsa/acp/alsa-mixer.c:2724
msgid "Docking Station Line In"
msgstr "扩展坞线输入"
#: spa/plugins/alsa/acp/alsa-mixer.c:2656
#: spa/plugins/alsa/acp/alsa-mixer.c:2747
#: spa/plugins/alsa/acp/alsa-mixer.c:2725
#: spa/plugins/alsa/acp/alsa-mixer.c:2816
msgid "Line In"
msgstr "输入插孔"
#: spa/plugins/alsa/acp/alsa-mixer.c:2657
#: spa/plugins/alsa/acp/alsa-mixer.c:2741
#: spa/plugins/bluez5/bluez5-device.c:2372
#: spa/plugins/alsa/acp/alsa-mixer.c:2726
#: spa/plugins/alsa/acp/alsa-mixer.c:2810
#: spa/plugins/bluez5/bluez5-device.c:2374
msgid "Microphone"
msgstr "话筒"
#: spa/plugins/alsa/acp/alsa-mixer.c:2658
#: spa/plugins/alsa/acp/alsa-mixer.c:2742
#: spa/plugins/alsa/acp/alsa-mixer.c:2727
#: spa/plugins/alsa/acp/alsa-mixer.c:2811
msgid "Front Microphone"
msgstr "前麦克风"
#: spa/plugins/alsa/acp/alsa-mixer.c:2659
#: spa/plugins/alsa/acp/alsa-mixer.c:2743
#: spa/plugins/alsa/acp/alsa-mixer.c:2728
#: spa/plugins/alsa/acp/alsa-mixer.c:2812
msgid "Rear Microphone"
msgstr "后麦克风"
#: spa/plugins/alsa/acp/alsa-mixer.c:2660
#: spa/plugins/alsa/acp/alsa-mixer.c:2729
msgid "External Microphone"
msgstr "外部话筒"
#: spa/plugins/alsa/acp/alsa-mixer.c:2661
#: spa/plugins/alsa/acp/alsa-mixer.c:2745
#: spa/plugins/alsa/acp/alsa-mixer.c:2730
#: spa/plugins/alsa/acp/alsa-mixer.c:2814
msgid "Internal Microphone"
msgstr "内部话筒"
#: spa/plugins/alsa/acp/alsa-mixer.c:2662
#: spa/plugins/alsa/acp/alsa-mixer.c:2748
#: spa/plugins/alsa/acp/alsa-mixer.c:2731
#: spa/plugins/alsa/acp/alsa-mixer.c:2817
msgid "Radio"
msgstr "无线电"
#: spa/plugins/alsa/acp/alsa-mixer.c:2663
#: spa/plugins/alsa/acp/alsa-mixer.c:2749
#: spa/plugins/alsa/acp/alsa-mixer.c:2732
#: spa/plugins/alsa/acp/alsa-mixer.c:2818
msgid "Video"
msgstr "视频"
#: spa/plugins/alsa/acp/alsa-mixer.c:2664
#: spa/plugins/alsa/acp/alsa-mixer.c:2733
msgid "Automatic Gain Control"
msgstr "自动增益控制"
#: spa/plugins/alsa/acp/alsa-mixer.c:2665
#: spa/plugins/alsa/acp/alsa-mixer.c:2734
msgid "No Automatic Gain Control"
msgstr "无自动增益控制"
#: spa/plugins/alsa/acp/alsa-mixer.c:2666
#: spa/plugins/alsa/acp/alsa-mixer.c:2735
msgid "Boost"
msgstr "增强"
#: spa/plugins/alsa/acp/alsa-mixer.c:2667
#: spa/plugins/alsa/acp/alsa-mixer.c:2736
msgid "No Boost"
msgstr "无增强"
#: spa/plugins/alsa/acp/alsa-mixer.c:2668
#: spa/plugins/alsa/acp/alsa-mixer.c:2737
msgid "Amplifier"
msgstr "功放"
#: spa/plugins/alsa/acp/alsa-mixer.c:2669
#: spa/plugins/alsa/acp/alsa-mixer.c:2738
msgid "No Amplifier"
msgstr "无功放"
#: spa/plugins/alsa/acp/alsa-mixer.c:2670
#: spa/plugins/alsa/acp/alsa-mixer.c:2739
msgid "Bass Boost"
msgstr "重低音增强"
#: spa/plugins/alsa/acp/alsa-mixer.c:2671
#: spa/plugins/alsa/acp/alsa-mixer.c:2740
msgid "No Bass Boost"
msgstr "无重低音增强"
#: spa/plugins/alsa/acp/alsa-mixer.c:2672
#: spa/plugins/bluez5/bluez5-device.c:2378
#: spa/plugins/alsa/acp/alsa-mixer.c:2741
#: spa/plugins/bluez5/bluez5-device.c:2380
msgid "Speaker"
msgstr "扬声器"
#. Don't call it "headset", the HF one has the mic
#: spa/plugins/alsa/acp/alsa-mixer.c:2673
#: spa/plugins/alsa/acp/alsa-mixer.c:2751
#: spa/plugins/bluez5/bluez5-device.c:2384
#: spa/plugins/bluez5/bluez5-device.c:2451
#: spa/plugins/alsa/acp/alsa-mixer.c:2742
#: spa/plugins/alsa/acp/alsa-mixer.c:2820
#: spa/plugins/bluez5/bluez5-device.c:2386
#: spa/plugins/bluez5/bluez5-device.c:2453
msgid "Headphones"
msgstr "模拟耳机"
#: spa/plugins/alsa/acp/alsa-mixer.c:2740
#: spa/plugins/alsa/acp/alsa-mixer.c:2809
msgid "Analog Input"
msgstr "模拟输入"
#: spa/plugins/alsa/acp/alsa-mixer.c:2744
#: spa/plugins/alsa/acp/alsa-mixer.c:2813
msgid "Dock Microphone"
msgstr "扩展坞麦克风"
#: spa/plugins/alsa/acp/alsa-mixer.c:2746
#: spa/plugins/alsa/acp/alsa-mixer.c:2815
msgid "Headset Microphone"
msgstr "头挂麦克风"
#: spa/plugins/alsa/acp/alsa-mixer.c:2750
#: spa/plugins/alsa/acp/alsa-mixer.c:2819
msgid "Analog Output"
msgstr "模拟输出"
#: spa/plugins/alsa/acp/alsa-mixer.c:2752
#: spa/plugins/alsa/acp/alsa-mixer.c:2821
msgid "Headphones 2"
msgstr "模拟耳机 2"
#: spa/plugins/alsa/acp/alsa-mixer.c:2753
#: spa/plugins/alsa/acp/alsa-mixer.c:2822
msgid "Headphones Mono Output"
msgstr "模拟单声道输出"
#: spa/plugins/alsa/acp/alsa-mixer.c:2754
#: spa/plugins/alsa/acp/alsa-mixer.c:2823
msgid "Line Out"
msgstr "线缆输出"
#: spa/plugins/alsa/acp/alsa-mixer.c:2755
#: spa/plugins/alsa/acp/alsa-mixer.c:2824
msgid "Analog Mono Output"
msgstr "模拟单声道输出"
#: spa/plugins/alsa/acp/alsa-mixer.c:2756
#: spa/plugins/alsa/acp/alsa-mixer.c:2825
msgid "Speakers"
msgstr "扬声器"
#: spa/plugins/alsa/acp/alsa-mixer.c:2757
#: spa/plugins/alsa/acp/alsa-mixer.c:2826
msgid "HDMI / DisplayPort"
msgstr "HDMI / DisplayPort"
#: spa/plugins/alsa/acp/alsa-mixer.c:2758
#: spa/plugins/alsa/acp/alsa-mixer.c:2827
msgid "Digital Output (S/PDIF)"
msgstr "数字输出 (S/PDIF)"
#: spa/plugins/alsa/acp/alsa-mixer.c:2759
#: spa/plugins/alsa/acp/alsa-mixer.c:2828
msgid "Digital Input (S/PDIF)"
msgstr "数字输入 (S/PDIF)"
#: spa/plugins/alsa/acp/alsa-mixer.c:2760
#: spa/plugins/alsa/acp/alsa-mixer.c:2829
msgid "Multichannel Input"
msgstr "多声道输入"
#: spa/plugins/alsa/acp/alsa-mixer.c:2761
#: spa/plugins/alsa/acp/alsa-mixer.c:2830
msgid "Multichannel Output"
msgstr "多声道输出"
#: spa/plugins/alsa/acp/alsa-mixer.c:2762
#: spa/plugins/alsa/acp/alsa-mixer.c:2831
msgid "Game Output"
msgstr "游戏输出"
#: spa/plugins/alsa/acp/alsa-mixer.c:2763
#: spa/plugins/alsa/acp/alsa-mixer.c:2764
#: spa/plugins/alsa/acp/alsa-mixer.c:2832
#: spa/plugins/alsa/acp/alsa-mixer.c:2833
msgid "Chat Output"
msgstr "语音输出"
#: spa/plugins/alsa/acp/alsa-mixer.c:2765
#: spa/plugins/alsa/acp/alsa-mixer.c:2834
msgid "Chat Input"
msgstr "语音输入"
#: spa/plugins/alsa/acp/alsa-mixer.c:2766
#: spa/plugins/alsa/acp/alsa-mixer.c:2835
msgid "Virtual Surround 7.1"
msgstr "虚拟环绕 7.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4458
#: spa/plugins/alsa/acp/alsa-mixer.c:4522
msgid "Analog Mono"
msgstr "模拟单声道"
#: spa/plugins/alsa/acp/alsa-mixer.c:4459
#: spa/plugins/alsa/acp/alsa-mixer.c:4523
msgid "Analog Mono (Left)"
msgstr "模拟单声道 (左声道)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4460
#: spa/plugins/alsa/acp/alsa-mixer.c:4524
msgid "Analog Mono (Right)"
msgstr "模拟单声道 (右声道)"
@ -409,142 +409,142 @@ msgstr "模拟单声道 (右声道)"
#. * here would lead to the source name to become "Analog Stereo Input
#. * Input". The same logic applies to analog-stereo-output,
#. * multichannel-input and multichannel-output.
#: spa/plugins/alsa/acp/alsa-mixer.c:4461
#: spa/plugins/alsa/acp/alsa-mixer.c:4469
#: spa/plugins/alsa/acp/alsa-mixer.c:4470
#: spa/plugins/alsa/acp/alsa-mixer.c:4525
#: spa/plugins/alsa/acp/alsa-mixer.c:4533
#: spa/plugins/alsa/acp/alsa-mixer.c:4534
msgid "Analog Stereo"
msgstr "模拟立体声"
#: spa/plugins/alsa/acp/alsa-mixer.c:4462
#: spa/plugins/alsa/acp/alsa-mixer.c:4526
msgid "Mono"
msgstr "单声道"
#: spa/plugins/alsa/acp/alsa-mixer.c:4463
#: spa/plugins/alsa/acp/alsa-mixer.c:4527
msgid "Stereo"
msgstr "立体声"
#: spa/plugins/alsa/acp/alsa-mixer.c:4471
#: spa/plugins/alsa/acp/alsa-mixer.c:4629
#: spa/plugins/bluez5/bluez5-device.c:2360
#: spa/plugins/alsa/acp/alsa-mixer.c:4535
#: spa/plugins/alsa/acp/alsa-mixer.c:4693
#: spa/plugins/bluez5/bluez5-device.c:2362
msgid "Headset"
msgstr "耳机"
#: spa/plugins/alsa/acp/alsa-mixer.c:4472
#: spa/plugins/alsa/acp/alsa-mixer.c:4630
#: spa/plugins/alsa/acp/alsa-mixer.c:4536
#: spa/plugins/alsa/acp/alsa-mixer.c:4694
msgid "Speakerphone"
msgstr "扬声麦克风"
#: spa/plugins/alsa/acp/alsa-mixer.c:4473
#: spa/plugins/alsa/acp/alsa-mixer.c:4474
#: spa/plugins/alsa/acp/alsa-mixer.c:4537
#: spa/plugins/alsa/acp/alsa-mixer.c:4538
msgid "Multichannel"
msgstr "多声道"
#: spa/plugins/alsa/acp/alsa-mixer.c:4475
#: spa/plugins/alsa/acp/alsa-mixer.c:4539
msgid "Analog Surround 2.1"
msgstr "模拟环绕 2.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4476
#: spa/plugins/alsa/acp/alsa-mixer.c:4540
msgid "Analog Surround 3.0"
msgstr "模拟环绕 3.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4477
#: spa/plugins/alsa/acp/alsa-mixer.c:4541
msgid "Analog Surround 3.1"
msgstr "模拟环绕 3.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4478
#: spa/plugins/alsa/acp/alsa-mixer.c:4542
msgid "Analog Surround 4.0"
msgstr "模拟环绕 4.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4479
#: spa/plugins/alsa/acp/alsa-mixer.c:4543
msgid "Analog Surround 4.1"
msgstr "模拟环绕 4.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4480
#: spa/plugins/alsa/acp/alsa-mixer.c:4544
msgid "Analog Surround 5.0"
msgstr "模拟环绕 5.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4481
#: spa/plugins/alsa/acp/alsa-mixer.c:4545
msgid "Analog Surround 5.1"
msgstr "模拟环绕 5.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4482
#: spa/plugins/alsa/acp/alsa-mixer.c:4546
msgid "Analog Surround 6.0"
msgstr "模拟环绕 6.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4483
#: spa/plugins/alsa/acp/alsa-mixer.c:4547
msgid "Analog Surround 6.1"
msgstr "模拟环绕 6.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4484
#: spa/plugins/alsa/acp/alsa-mixer.c:4548
msgid "Analog Surround 7.0"
msgstr "模拟环绕 7.0"
#: spa/plugins/alsa/acp/alsa-mixer.c:4485
#: spa/plugins/alsa/acp/alsa-mixer.c:4549
msgid "Analog Surround 7.1"
msgstr "模拟环绕 7.1"
#: spa/plugins/alsa/acp/alsa-mixer.c:4486
#: spa/plugins/alsa/acp/alsa-mixer.c:4550
msgid "Digital Stereo (IEC958)"
msgstr "数字立体声 (IEC958)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4487
#: spa/plugins/alsa/acp/alsa-mixer.c:4551
msgid "Digital Surround 4.0 (IEC958/AC3)"
msgstr "数字环绕 4.0 (IEC958/AC3)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4488
#: spa/plugins/alsa/acp/alsa-mixer.c:4552
msgid "Digital Surround 5.1 (IEC958/AC3)"
msgstr "数字环绕 5.1 (IEC958/AC3)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4489
#: spa/plugins/alsa/acp/alsa-mixer.c:4553
msgid "Digital Surround 5.1 (IEC958/DTS)"
msgstr "数字环绕 5.1 (IEC958/DTS)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4490
#: spa/plugins/alsa/acp/alsa-mixer.c:4554
msgid "Digital Stereo (HDMI)"
msgstr "数字立体声 (HDMI)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4491
#: spa/plugins/alsa/acp/alsa-mixer.c:4555
msgid "Digital Surround 5.1 (HDMI)"
msgstr "数字环绕 5.1 (HDMI)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4492
#: spa/plugins/alsa/acp/alsa-mixer.c:4556
msgid "Chat"
msgstr "语音"
#: spa/plugins/alsa/acp/alsa-mixer.c:4493
#: spa/plugins/alsa/acp/alsa-mixer.c:4557
msgid "Game"
msgstr "游戏"
#: spa/plugins/alsa/acp/alsa-mixer.c:4627
#: spa/plugins/alsa/acp/alsa-mixer.c:4691
msgid "Analog Mono Duplex"
msgstr "模拟单声道双工"
#: spa/plugins/alsa/acp/alsa-mixer.c:4628
#: spa/plugins/alsa/acp/alsa-mixer.c:4692
msgid "Analog Stereo Duplex"
msgstr "模拟立体声双工"
#: spa/plugins/alsa/acp/alsa-mixer.c:4631
#: spa/plugins/alsa/acp/alsa-mixer.c:4695
msgid "Digital Stereo Duplex (IEC958)"
msgstr "数字立体声双工 (IEC958)"
#: spa/plugins/alsa/acp/alsa-mixer.c:4632
#: spa/plugins/alsa/acp/alsa-mixer.c:4696
msgid "Multichannel Duplex"
msgstr "多声道双工"
#: spa/plugins/alsa/acp/alsa-mixer.c:4633
#: spa/plugins/alsa/acp/alsa-mixer.c:4697
msgid "Stereo Duplex"
msgstr "模拟立体声双工"
#: spa/plugins/alsa/acp/alsa-mixer.c:4634
#: spa/plugins/alsa/acp/alsa-mixer.c:4698
msgid "Mono Chat + 7.1 Surround"
msgstr "单声道语音 + 7.1 环绕声"
#: spa/plugins/alsa/acp/alsa-mixer.c:4735
#: spa/plugins/alsa/acp/alsa-mixer.c:4799
#, c-format
msgid "%s Output"
msgstr "%s 输出"
#: spa/plugins/alsa/acp/alsa-mixer.c:4743
#: spa/plugins/alsa/acp/alsa-mixer.c:4807
#, c-format
msgid "%s Input"
msgstr "%s 输入"
@ -608,7 +608,7 @@ msgstr[0] ""
"snd_pcm_mmap_begin() 返回的值非常大:%lu 字节(%lu ms)。\n"
"这很可能是由 ALSA 驱动程序 %s 的缺陷导致的。请向 ALSA 开发者报告这个问题。"
#: spa/plugins/alsa/acp/channelmap.h:457
#: spa/plugins/alsa/acp/channelmap.h:460
msgid "(invalid)"
msgstr "(无效)"
@ -620,103 +620,103 @@ msgstr "内置音频"
msgid "Modem"
msgstr "调制解调器"
#: spa/plugins/bluez5/bluez5-device.c:1985
#: spa/plugins/bluez5/bluez5-device.c:1987
msgid "Audio Gateway (A2DP Source & HSP/HFP AG)"
msgstr "音频网关 (A2DP 信源 或 HSP/HFP 网关)"
#: spa/plugins/bluez5/bluez5-device.c:2014
#: spa/plugins/bluez5/bluez5-device.c:2016
msgid "Audio Streaming for Hearing Aids (ASHA Sink)"
msgstr "助听器音频流 (ASHA 信宿)"
#: spa/plugins/bluez5/bluez5-device.c:2057
#: spa/plugins/bluez5/bluez5-device.c:2059
#, c-format
msgid "High Fidelity Playback (A2DP Sink, codec %s)"
msgstr "高保真回放 (A2DP 信宿, 编码 %s)"
#: spa/plugins/bluez5/bluez5-device.c:2060
#: spa/plugins/bluez5/bluez5-device.c:2062
#, c-format
msgid "High Fidelity Duplex (A2DP Source/Sink, codec %s)"
msgstr "高保真双工 (A2DP 信源/信宿, 编码 %s)"
#: spa/plugins/bluez5/bluez5-device.c:2068
#: spa/plugins/bluez5/bluez5-device.c:2070
msgid "High Fidelity Playback (A2DP Sink)"
msgstr "高保真回放 (A2DP 信宿)"
#: spa/plugins/bluez5/bluez5-device.c:2070
#: spa/plugins/bluez5/bluez5-device.c:2072
msgid "High Fidelity Duplex (A2DP Source/Sink)"
msgstr "高保真双工 (A2DP 信源/信宿)"
#: spa/plugins/bluez5/bluez5-device.c:2144
#: spa/plugins/bluez5/bluez5-device.c:2146
#, c-format
msgid "High Fidelity Playback (BAP Sink, codec %s)"
msgstr "高保真回放 (BAP 信宿, 编码 %s)"
#: spa/plugins/bluez5/bluez5-device.c:2149
#: spa/plugins/bluez5/bluez5-device.c:2151
#, c-format
msgid "High Fidelity Input (BAP Source, codec %s)"
msgstr "高保真输入 (BAP 信源, 编码 %s)"
#: spa/plugins/bluez5/bluez5-device.c:2153
#: spa/plugins/bluez5/bluez5-device.c:2155
#, c-format
msgid "High Fidelity Duplex (BAP Source/Sink, codec %s)"
msgstr "高保真双工 (BAP 信源/信宿, 编码 %s)"
#: spa/plugins/bluez5/bluez5-device.c:2162
#: spa/plugins/bluez5/bluez5-device.c:2164
msgid "High Fidelity Playback (BAP Sink)"
msgstr "高保真回放 (BAP 信宿)"
#: spa/plugins/bluez5/bluez5-device.c:2166
#: spa/plugins/bluez5/bluez5-device.c:2168
msgid "High Fidelity Input (BAP Source)"
msgstr "高保真输入 (BAP 信源)"
#: spa/plugins/bluez5/bluez5-device.c:2169
#: spa/plugins/bluez5/bluez5-device.c:2171
msgid "High Fidelity Duplex (BAP Source/Sink)"
msgstr "高保真双工 (BAP 信源/信宿)"
#: spa/plugins/bluez5/bluez5-device.c:2209
#: spa/plugins/bluez5/bluez5-device.c:2211
#, c-format
msgid "Headset Head Unit (HSP/HFP, codec %s)"
msgstr "头戴式耳机单元 (HSP/HFP, 编码 %s)"
#: spa/plugins/bluez5/bluez5-device.c:2361
#: spa/plugins/bluez5/bluez5-device.c:2366
#: spa/plugins/bluez5/bluez5-device.c:2373
#: spa/plugins/bluez5/bluez5-device.c:2379
#: spa/plugins/bluez5/bluez5-device.c:2385
#: spa/plugins/bluez5/bluez5-device.c:2391
#: spa/plugins/bluez5/bluez5-device.c:2397
#: spa/plugins/bluez5/bluez5-device.c:2403
#: spa/plugins/bluez5/bluez5-device.c:2409
#: spa/plugins/bluez5/bluez5-device.c:2363
#: spa/plugins/bluez5/bluez5-device.c:2368
#: spa/plugins/bluez5/bluez5-device.c:2375
#: spa/plugins/bluez5/bluez5-device.c:2381
#: spa/plugins/bluez5/bluez5-device.c:2387
#: spa/plugins/bluez5/bluez5-device.c:2393
#: spa/plugins/bluez5/bluez5-device.c:2399
#: spa/plugins/bluez5/bluez5-device.c:2405
#: spa/plugins/bluez5/bluez5-device.c:2411
msgid "Handsfree"
msgstr "免手操作"
msgstr "免"
#: spa/plugins/bluez5/bluez5-device.c:2367
#: spa/plugins/bluez5/bluez5-device.c:2369
msgid "Handsfree (HFP)"
msgstr "免手操作 (HFP)"
msgstr "免HFP"
#: spa/plugins/bluez5/bluez5-device.c:2390
#: spa/plugins/bluez5/bluez5-device.c:2392
msgid "Portable"
msgstr "便携式"
#: spa/plugins/bluez5/bluez5-device.c:2396
#: spa/plugins/bluez5/bluez5-device.c:2398
msgid "Car"
msgstr "车内"
#: spa/plugins/bluez5/bluez5-device.c:2402
#: spa/plugins/bluez5/bluez5-device.c:2404
msgid "HiFi"
msgstr "高保真"
#: spa/plugins/bluez5/bluez5-device.c:2408
#: spa/plugins/bluez5/bluez5-device.c:2410
msgid "Phone"
msgstr "电话"
#: spa/plugins/bluez5/bluez5-device.c:2415
#: spa/plugins/bluez5/bluez5-device.c:2417
msgid "Bluetooth"
msgstr "蓝牙"
#: spa/plugins/bluez5/bluez5-device.c:2416
msgid "Bluetooth (HFP)"
msgstr "蓝牙 (HFP)"
#: spa/plugins/bluez5/bluez5-device.c:2418
msgid "Bluetooth Handsfree"
msgstr "蓝牙免提"
#~ msgid "Headphone"
#~ msgstr "头戴耳机"

View file

@ -578,7 +578,7 @@ static int make_nodes(struct data *data)
SPA_PARAM_PORT_CONFIG_direction, SPA_POD_Id(SPA_DIRECTION_OUTPUT),
SPA_PARAM_PORT_CONFIG_mode, SPA_POD_Id(SPA_PARAM_PORT_CONFIG_MODE_dsp),
SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param));
if ((res = spa_node_set_param(data->source_node, SPA_PARAM_PortConfig, 0, param) < 0)) {
if ((res = spa_node_set_param(data->source_node, SPA_PARAM_PortConfig, 0, param)) < 0) {
printf("can't setup source node %d\n", res);
return res;
}
@ -647,7 +647,7 @@ static int make_nodes(struct data *data)
SPA_PARAM_PORT_CONFIG_format, SPA_POD_Pod(param));
if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_PortConfig, 0, param) < 0)) {
if ((res = spa_node_set_param(data->sink_node, SPA_PARAM_PortConfig, 0, param)) < 0) {
printf("can't setup sink node %d\n", res);
return res;
}
@ -987,7 +987,7 @@ int main(int argc, char *argv[])
setlocale(LC_ALL, "");
while ((c = getopt_long(argc, argv, "hdmstiac:", long_options, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "hd:m:s:t:i:a:c:", long_options, NULL)) != -1) {
switch (c) {
case 'h':
show_help(&data, argv[0], false);

View file

@ -164,8 +164,7 @@ SPA_API_DEBUG_FORMAT int spa_debugc_format(struct spa_debug_context *ctx, int in
type = val->type;
size = val->size;
if (type < SPA_TYPE_None || type >= _SPA_TYPE_LAST || n_vals < 1 ||
size < spa_pod_type_size(type))
if (type < SPA_TYPE_None || type >= _SPA_TYPE_LAST || n_vals < 1)
continue;
vals = SPA_POD_BODY(val);

View file

@ -33,6 +33,8 @@ spa_format_audio_dsd_parse(const struct spa_pod *format, struct spa_audio_info_d
{
struct spa_pod *position = NULL;
int res;
uint32_t max_position = SPA_N_ELEMENTS(info->position);
info->flags = 0;
res = spa_pod_parse_object(format,
SPA_TYPE_OBJECT_Format, NULL,
@ -41,10 +43,13 @@ spa_format_audio_dsd_parse(const struct spa_pod *format, struct spa_audio_info_d
SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate),
SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels),
SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position));
if (info->channels > max_position)
return -ECHRNG;
if (position == NULL ||
!spa_pod_copy_array(position, SPA_TYPE_Id, info->position, SPA_AUDIO_MAX_CHANNELS))
spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels) {
SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
spa_memzero(info->position, max_position * sizeof(info->position[0]));
}
return res;
}

View file

@ -46,20 +46,61 @@ extern "C" {
#endif
#endif
SPA_API_AUDIO_FORMAT_UTILS bool
spa_format_audio_ext_valid_size(uint32_t media_subtype, size_t size)
{
switch (media_subtype) {
case SPA_MEDIA_SUBTYPE_raw:
return size >= offsetof(struct spa_audio_info, info.raw) &&
SPA_AUDIO_INFO_RAW_VALID_SIZE(size - offsetof(struct spa_audio_info, info.raw));
#define _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(format) \
case SPA_MEDIA_SUBTYPE_ ## format: \
return size >= offsetof(struct spa_audio_info, info.format) + sizeof(struct spa_audio_info_ ## format);
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dsp)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(iec958)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dsd)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(mp3)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(aac)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(vorbis)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(wma)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ra)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(amr)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(alac)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(flac)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ape)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(ac3)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(eac3)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(truehd)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(dts)
_SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE(mpegh)
#undef _SPA_FORMAT_AUDIO_EXT_VALID_SIZE_CASE
}
return false;
}
SPA_API_AUDIO_FORMAT_UTILS int
spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info)
spa_format_audio_ext_parse(const struct spa_pod *format, struct spa_audio_info *info, size_t size)
{
int res;
uint32_t media_type, media_subtype;
if ((res = spa_format_parse(format, &info->media_type, &info->media_subtype)) < 0)
if ((res = spa_format_parse(format, &media_type, &media_subtype)) < 0)
return res;
if (info->media_type != SPA_MEDIA_TYPE_audio)
if (media_type != SPA_MEDIA_TYPE_audio)
return -EINVAL;
switch (info->media_subtype) {
if (!spa_format_audio_ext_valid_size(media_subtype, size))
return -EINVAL;
info->media_type = media_type;
info->media_subtype = media_subtype;
switch (media_subtype) {
case SPA_MEDIA_SUBTYPE_raw:
return spa_format_audio_raw_parse(format, &info->info.raw);
return spa_format_audio_raw_ext_parse(format, &info->info.raw,
size - offsetof(struct spa_audio_info, info.raw));
case SPA_MEDIA_SUBTYPE_dsp:
return spa_format_audio_dsp_parse(format, &info->info.dsp);
case SPA_MEDIA_SUBTYPE_iec958:
@ -98,13 +139,25 @@ spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info
return -ENOTSUP;
}
SPA_API_AUDIO_FORMAT_UTILS struct spa_pod *
spa_format_audio_build(struct spa_pod_builder *builder, uint32_t id,
const struct spa_audio_info *info)
SPA_API_AUDIO_FORMAT_UTILS int
spa_format_audio_parse(const struct spa_pod *format, struct spa_audio_info *info)
{
return spa_format_audio_ext_parse(format, info, sizeof(*info));
}
SPA_API_AUDIO_FORMAT_UTILS struct spa_pod *
spa_format_audio_ext_build(struct spa_pod_builder *builder, uint32_t id,
const struct spa_audio_info *info, size_t size)
{
if (!spa_format_audio_ext_valid_size(info->media_subtype, size)) {
errno = EINVAL;
return NULL;
}
switch (info->media_subtype) {
case SPA_MEDIA_SUBTYPE_raw:
return spa_format_audio_raw_build(builder, id, &info->info.raw);
return spa_format_audio_raw_ext_build(builder, id, &info->info.raw,
size - offsetof(struct spa_audio_info, info.raw));
case SPA_MEDIA_SUBTYPE_dsp:
return spa_format_audio_dsp_build(builder, id, &info->info.dsp);
case SPA_MEDIA_SUBTYPE_iec958:
@ -143,6 +196,13 @@ spa_format_audio_build(struct spa_pod_builder *builder, uint32_t id,
errno = ENOTSUP;
return NULL;
}
SPA_API_AUDIO_FORMAT_UTILS struct spa_pod *
spa_format_audio_build(struct spa_pod_builder *builder, uint32_t id,
const struct spa_audio_info *info)
{
return spa_format_audio_ext_build(builder, id, info, sizeof(*info));
}
/**
* \}
*/

View file

@ -59,6 +59,8 @@ struct spa_audio_info {
struct spa_audio_info_dts dts;
struct spa_audio_info_mpegh mpegh;
} info;
/* padding follows here when info has flexible size */
};
/**

View file

@ -0,0 +1,118 @@
/* Simple Plugin API */
/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#ifndef SPA_AUDIO_LAYOUT_TYPES_H
#define SPA_AUDIO_LAYOUT_TYPES_H
#include <spa/utils/type.h>
#include <spa/utils/string.h>
#include <spa/param/audio/layout.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* \addtogroup spa_param
* \{
*/
#ifndef SPA_API_AUDIO_LAYOUT_TYPES
#ifdef SPA_API_IMPL
#define SPA_API_AUDIO_LAYOUT_TYPES SPA_API_IMPL
#else
#define SPA_API_AUDIO_LAYOUT_TYPES static inline
#endif
#endif
static const struct spa_type_audio_layout_info {
const char *name;
struct spa_audio_layout_info layout;
} spa_type_audio_layout_info[] = {
{ "Mono", { SPA_AUDIO_LAYOUT_Mono } },
{ "Stereo", { SPA_AUDIO_LAYOUT_Stereo } },
{ "Quad", { SPA_AUDIO_LAYOUT_Quad } },
{ "Pentagonal", { SPA_AUDIO_LAYOUT_Pentagonal } },
{ "Hexagonal", { SPA_AUDIO_LAYOUT_Hexagonal } },
{ "Octagonal", { SPA_AUDIO_LAYOUT_Octagonal } },
{ "Cube", { SPA_AUDIO_LAYOUT_Cube } },
{ "MPEG-1.0", { SPA_AUDIO_LAYOUT_MPEG_1_0 } },
{ "MPEG-2.0", { SPA_AUDIO_LAYOUT_MPEG_2_0 } },
{ "MPEG-3.0A", { SPA_AUDIO_LAYOUT_MPEG_3_0A } },
{ "MPEG-3.0B", { SPA_AUDIO_LAYOUT_MPEG_3_0B } },
{ "MPEG-4.0A", { SPA_AUDIO_LAYOUT_MPEG_4_0A } },
{ "MPEG-4.0B", { SPA_AUDIO_LAYOUT_MPEG_4_0B } },
{ "MPEG-5.0A", { SPA_AUDIO_LAYOUT_MPEG_5_0A } },
{ "MPEG-5.0B", { SPA_AUDIO_LAYOUT_MPEG_5_0B } },
{ "MPEG-5.0C", { SPA_AUDIO_LAYOUT_MPEG_5_0C } },
{ "MPEG-5.0D", { SPA_AUDIO_LAYOUT_MPEG_5_0D } },
{ "MPEG-5.1A", { SPA_AUDIO_LAYOUT_MPEG_5_1A } },
{ "MPEG-5.1B", { SPA_AUDIO_LAYOUT_MPEG_5_1B } },
{ "MPEG-5.1C", { SPA_AUDIO_LAYOUT_MPEG_5_1C } },
{ "MPEG-5.1D", { SPA_AUDIO_LAYOUT_MPEG_5_1D } },
{ "MPEG-6.1A", { SPA_AUDIO_LAYOUT_MPEG_6_1A } },
{ "MPEG-7.1A", { SPA_AUDIO_LAYOUT_MPEG_7_1A } },
{ "MPEG-7.1B", { SPA_AUDIO_LAYOUT_MPEG_7_1B } },
{ "MPEG-7.1C", { SPA_AUDIO_LAYOUT_MPEG_7_1C } },
{ "2.1", { SPA_AUDIO_LAYOUT_2_1 } },
{ "2RC", { SPA_AUDIO_LAYOUT_2RC } },
{ "2FC", { SPA_AUDIO_LAYOUT_2FC } },
{ "3.1", { SPA_AUDIO_LAYOUT_3_1 } },
{ "4.0", { SPA_AUDIO_LAYOUT_4_0 } },
{ "2.2", { SPA_AUDIO_LAYOUT_2_2 } },
{ "4.1", { SPA_AUDIO_LAYOUT_4_1 } },
{ "5.0", { SPA_AUDIO_LAYOUT_5_0 } },
{ "5.0R", { SPA_AUDIO_LAYOUT_5_0R } },
{ "5.1", { SPA_AUDIO_LAYOUT_5_1 } },
{ "5.1R", { SPA_AUDIO_LAYOUT_5_1R } },
{ "6.0", { SPA_AUDIO_LAYOUT_6_0 } },
{ "6.0F", { SPA_AUDIO_LAYOUT_6_0F } },
{ "6.1", { SPA_AUDIO_LAYOUT_6_1 } },
{ "6.1F", { SPA_AUDIO_LAYOUT_6_1F } },
{ "7.0", { SPA_AUDIO_LAYOUT_7_0 } },
{ "7.0F", { SPA_AUDIO_LAYOUT_7_0F } },
{ "7.1", { SPA_AUDIO_LAYOUT_7_1 } },
{ "7.1W", { SPA_AUDIO_LAYOUT_7_1W } },
{ "7.1WR", { SPA_AUDIO_LAYOUT_7_1WR } },
{ NULL, { 0, { SPA_AUDIO_CHANNEL_UNKNOWN } } },
};
SPA_API_AUDIO_LAYOUT_TYPES int
spa_audio_layout_info_parse_name(struct spa_audio_layout_info *layout, size_t size,
const char *name)
{
uint32_t max_position = SPA_AUDIO_LAYOUT_INFO_MAX_POSITION(size);
if (spa_strstartswith(name, "AUX")) {
uint32_t i, n_pos;
if (spa_atou32(name+3, &n_pos, 10)) {
if (n_pos > max_position)
return -ECHRNG;
for (i = 0; i < 0x1000 && i < n_pos; i++)
layout->position[i] = SPA_AUDIO_CHANNEL_AUX0 + i;
for (; i < n_pos; i++)
layout->position[i] = SPA_AUDIO_CHANNEL_UNKNOWN;
layout->n_channels = n_pos;
return n_pos;
}
}
SPA_FOR_EACH_ELEMENT_VAR(spa_type_audio_layout_info, i) {
if (spa_streq(name, i->name)) {
if (i->layout.n_channels > max_position)
return -ECHRNG;
*layout = i->layout;
return i->layout.n_channels;
}
}
return -ENOTSUP;
}
/**
* \}
*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* SPA_AUDIO_LAYOUT_TYPES_H */

View file

@ -21,8 +21,11 @@ extern "C" {
struct spa_audio_layout_info {
uint32_t n_channels;
uint32_t position[SPA_AUDIO_MAX_CHANNELS];
/* padding may follow to allow more channels */
};
#define SPA_AUDIO_LAYOUT_INFO_MAX_POSITION(size) (((size)-offsetof(struct spa_audio_layout_info,position))/sizeof(uint32_t))
#define SPA_AUDIO_LAYOUT_Mono 1, { SPA_AUDIO_CHANNEL_MONO, }
#define SPA_AUDIO_LAYOUT_Stereo 2, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, }
#define SPA_AUDIO_LAYOUT_Quad 4, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
@ -37,7 +40,7 @@ struct spa_audio_layout_info {
SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \
SPA_AUDIO_CHANNEL_FC, SPA_AUDIO_CHANNEL_RC, \
SPA_AUDIO_CHANNEL_SL, SPA_AUDIO_CHANNEL_SR, }
#define SPA_AUDIO_LAYOUT_Cube 8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR }, \
#define SPA_AUDIO_LAYOUT_Cube 8, { SPA_AUDIO_CHANNEL_FL, SPA_AUDIO_CHANNEL_FR, \
SPA_AUDIO_CHANNEL_RL, SPA_AUDIO_CHANNEL_RR, \
SPA_AUDIO_CHANNEL_TFL, SPA_AUDIO_CHANNEL_TFR, \
SPA_AUDIO_CHANNEL_TRL, SPA_AUDIO_CHANNEL_TRR, }

View file

@ -9,6 +9,7 @@
#include <spa/utils/json.h>
#include <spa/param/audio/raw.h>
#include <spa/param/audio/raw-types.h>
#include <spa/param/audio/layout-types.h>
#ifdef __cplusplus
extern "C" {
@ -28,8 +29,8 @@ extern "C" {
#endif
SPA_API_AUDIO_RAW_JSON int
spa_audio_parse_position(const char *str, size_t len,
uint32_t *position, uint32_t *n_channels)
spa_audio_parse_position_n(const char *str, size_t len,
uint32_t *position, uint32_t max_position, uint32_t *n_channels)
{
struct spa_json iter;
char v[256];
@ -38,18 +39,46 @@ spa_audio_parse_position(const char *str, size_t len,
if (spa_json_begin_array_relax(&iter, str, len) <= 0)
return 0;
while (spa_json_get_string(&iter, v, sizeof(v)) > 0 &&
channels < SPA_AUDIO_MAX_CHANNELS) {
position[channels++] = spa_type_audio_channel_from_short_name(v);
while (spa_json_get_string(&iter, v, sizeof(v)) > 0) {
if (channels < max_position)
position[channels] = spa_type_audio_channel_from_short_name(v);
channels++;
}
*n_channels = channels;
return channels;
}
SPA_API_AUDIO_RAW_JSON int
spa_audio_info_raw_update(struct spa_audio_info_raw *info, const char *key, const char *val, bool force)
spa_audio_parse_position(const char *str, size_t len,
uint32_t *position, uint32_t *n_channels)
{
return spa_audio_parse_position_n(str, len, position, SPA_AUDIO_MAX_CHANNELS, n_channels);
}
SPA_API_AUDIO_RAW_JSON int
spa_audio_parse_layout(const char *str, uint32_t *position, uint32_t max_position,
uint32_t *n_channels)
{
struct spa_audio_layout_info l;
uint32_t i;
if (spa_audio_layout_info_parse_name(&l, sizeof(l), str) <= 0)
return 0;
for (i = 0; i < l.n_channels && i < max_position; i++)
position[i] = l.position[i];
*n_channels = l.n_channels;
return l.n_channels;
}
SPA_API_AUDIO_RAW_JSON int
spa_audio_info_raw_ext_update(struct spa_audio_info_raw *info, size_t size,
const char *key, const char *val, bool force)
{
uint32_t v;
uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size);
if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size))
return -EINVAL;
if (spa_streq(key, SPA_KEY_AUDIO_FORMAT)) {
if (force || info->format == 0)
info->format = (enum spa_audio_format)spa_type_audio_format_from_short_name(val);
@ -57,41 +86,97 @@ spa_audio_info_raw_update(struct spa_audio_info_raw *info, const char *key, cons
if (spa_atou32(val, &v, 0) && (force || info->rate == 0))
info->rate = v;
} else if (spa_streq(key, SPA_KEY_AUDIO_CHANNELS)) {
if (spa_atou32(val, &v, 0) && (force || info->channels == 0))
info->channels = SPA_MIN(v, SPA_AUDIO_MAX_CHANNELS);
if (spa_atou32(val, &v, 0) && (force || info->channels == 0)) {
if (v > max_position)
return -ECHRNG;
info->channels = v;
}
} else if (spa_streq(key, SPA_KEY_AUDIO_LAYOUT)) {
if (force || info->channels == 0) {
if (spa_audio_parse_layout(val, info->position, max_position, &v) > 0) {
if (v > max_position)
return -ECHRNG;
info->channels = v;
SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
}
}
} else if (spa_streq(key, SPA_KEY_AUDIO_POSITION)) {
if (force || info->channels == 0) {
if (spa_audio_parse_position(val, strlen(val), info->position, &info->channels) > 0)
if (spa_audio_parse_position_n(val, strlen(val), info->position,
max_position, &v) > 0) {
if (v > max_position)
return -ECHRNG;
info->channels = v;
SPA_FLAG_CLEAR(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
}
}
}
return 0;
}
SPA_API_AUDIO_RAW_JSON int
spa_audio_info_raw_update(struct spa_audio_info_raw *info,
const char *key, const char *val, bool force)
{
return spa_audio_info_raw_ext_update(info, sizeof(*info), key, val, force);
}
SPA_API_AUDIO_RAW_JSON int
spa_audio_info_raw_ext_init_dict_keys_va(struct spa_audio_info_raw *info, size_t size,
const struct spa_dict *defaults,
const struct spa_dict *dict, va_list args)
{
int res;
if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size))
return -EINVAL;
memset(info, 0, size);
SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
if (dict) {
const char *val, *key;
while ((key = va_arg(args, const char *))) {
if ((val = spa_dict_lookup(dict, key)) == NULL)
continue;
if ((res = spa_audio_info_raw_ext_update(info, size,
key, val, true)) < 0)
return res;
}
}
if (defaults) {
const struct spa_dict_item *it;
spa_dict_for_each(it, defaults)
if ((res = spa_audio_info_raw_ext_update(info, size,
it->key, it->value, false)) < 0)
return res;
}
return 0;
}
SPA_API_AUDIO_RAW_JSON int SPA_SENTINEL
spa_audio_info_raw_ext_init_dict_keys(struct spa_audio_info_raw *info, size_t size,
const struct spa_dict *defaults,
const struct spa_dict *dict, ...)
{
va_list args;
int res;
va_start(args, dict);
res = spa_audio_info_raw_ext_init_dict_keys_va(info, size, defaults, dict, args);
va_end(args);
return res;
}
SPA_API_AUDIO_RAW_JSON int SPA_SENTINEL
spa_audio_info_raw_init_dict_keys(struct spa_audio_info_raw *info,
const struct spa_dict *defaults,
const struct spa_dict *dict, ...)
{
spa_zero(*info);
SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
if (dict) {
const char *val, *key;
va_list args;
va_start(args, dict);
while ((key = va_arg(args, const char *))) {
if ((val = spa_dict_lookup(dict, key)) == NULL)
continue;
spa_audio_info_raw_update(info, key, val, true);
}
va_end(args);
}
if (defaults) {
const struct spa_dict_item *it;
spa_dict_for_each(it, defaults)
spa_audio_info_raw_update(info, it->key, it->value, false);
}
return 0;
va_list args;
int res;
va_start(args, dict);
res = spa_audio_info_raw_ext_init_dict_keys_va(info, sizeof(*info), defaults, dict, args);
va_end(args);
return res;
}
/**

View file

@ -267,12 +267,34 @@ static const struct spa_type_info spa_type_audio_channel[] = {
SPA_API_AUDIO_RAW_TYPES uint32_t spa_type_audio_channel_from_short_name(const char *name)
{
return spa_type_from_short_name(name, spa_type_audio_channel, SPA_AUDIO_CHANNEL_UNKNOWN);
uint32_t res;
if (spa_strstartswith(name, "AUX")) {
if (spa_atou32(name+3, &res, 10) && res < 0x1000)
res = SPA_AUDIO_CHANNEL_AUX0 + res;
else
res = SPA_AUDIO_CHANNEL_UNKNOWN;
} else {
res = spa_type_from_short_name(name, spa_type_audio_channel, SPA_AUDIO_CHANNEL_UNKNOWN);
}
return res;
}
SPA_API_AUDIO_RAW_TYPES const char * spa_type_audio_channel_to_short_name(uint32_t type)
{
return spa_type_to_short_name(type, spa_type_audio_channel, "UNK");
}
SPA_API_AUDIO_RAW_TYPES const char * spa_type_audio_channel_make_short_name(uint32_t type,
char *buf, size_t size, const char *unknown)
{
if (SPA_AUDIO_CHANNEL_IS_AUX(type)) {
snprintf(buf, size, "AUX%u", type - SPA_AUDIO_CHANNEL_AUX0);
} else {
const char *str = spa_type_to_short_name(type, spa_type_audio_channel, NULL);
if (str == NULL)
return unknown;
snprintf(buf, size, "%.7s", str);
}
return buf;
}
#define SPA_TYPE_INFO_AudioVolumeRampScale SPA_TYPE_INFO_ENUM_BASE "AudioVolumeRampScale"
#define SPA_TYPE_INFO_AUDIO_VOLUME_RAMP_SCALE_BASE SPA_TYPE_INFO_AudioVolumeRampScale ":"
@ -292,4 +314,4 @@ static const struct spa_type_info spa_type_audio_volume_ramp_scale[] = {
} /* extern "C" */
#endif
#endif /* SPA_AUDIO_RAW_RAW_TYPES_H */
#endif /* SPA_AUDIO_RAW_TYPES_H */

View file

@ -29,10 +29,15 @@ extern "C" {
#endif
SPA_API_AUDIO_RAW_UTILS int
spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_raw *info)
spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_info_raw *info, size_t size)
{
struct spa_pod *position = NULL;
int res;
uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size);
if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size))
return -EINVAL;
info->flags = 0;
res = spa_pod_parse_object(format,
SPA_TYPE_OBJECT_Format, NULL,
@ -40,18 +45,35 @@ spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_r
SPA_FORMAT_AUDIO_rate, SPA_POD_OPT_Int(&info->rate),
SPA_FORMAT_AUDIO_channels, SPA_POD_OPT_Int(&info->channels),
SPA_FORMAT_AUDIO_position, SPA_POD_OPT_Pod(&position));
if (info->channels > max_position)
return -ECHRNG;
if (position == NULL ||
!spa_pod_copy_array(position, SPA_TYPE_Id, info->position, SPA_AUDIO_MAX_CHANNELS))
spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels) {
SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
spa_memzero(info->position, max_position * sizeof(info->position[0]));
}
return res;
}
SPA_API_AUDIO_RAW_UTILS int
spa_format_audio_raw_parse(const struct spa_pod *format, struct spa_audio_info_raw *info)
{
return spa_format_audio_raw_ext_parse(format, info, sizeof(*info));
}
SPA_API_AUDIO_RAW_UTILS struct spa_pod *
spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id,
const struct spa_audio_info_raw *info)
spa_format_audio_raw_ext_build(struct spa_pod_builder *builder, uint32_t id,
const struct spa_audio_info_raw *info, size_t size)
{
struct spa_pod_frame f;
uint32_t max_position = SPA_AUDIO_INFO_RAW_MAX_POSITION(size);
if (!SPA_AUDIO_INFO_RAW_VALID_SIZE(size)) {
errno = EINVAL;
return NULL;
}
spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_Format, id);
spa_pod_builder_add(builder,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
@ -66,7 +88,10 @@ spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id,
if (info->channels != 0) {
spa_pod_builder_add(builder,
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(info->channels), 0);
if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED)) {
/* we drop the positions here when we can't read all of them. This is
* really a malformed spa_audio_info structure. */
if (!SPA_FLAG_IS_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED) &&
info->channels <= max_position) {
spa_pod_builder_add(builder, SPA_FORMAT_AUDIO_position,
SPA_POD_Array(sizeof(uint32_t), SPA_TYPE_Id,
info->channels, info->position), 0);
@ -75,6 +100,13 @@ spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id,
return (struct spa_pod*)spa_pod_builder_pop(builder, &f);
}
SPA_API_AUDIO_RAW_UTILS struct spa_pod *
spa_format_audio_raw_build(struct spa_pod_builder *builder, uint32_t id,
const struct spa_audio_info_raw *info)
{
return spa_format_audio_raw_ext_build(builder, id, info, sizeof(*info));
}
/**
* \}
*/

View file

@ -18,7 +18,11 @@ extern "C" {
* \{
*/
#define SPA_AUDIO_MAX_CHANNELS 128u
/* This is the max number of channels, changing this will change the
* size of some helper structures. This value should be at least 64 */
#ifndef SPA_AUDIO_MAX_CHANNELS
#define SPA_AUDIO_MAX_CHANNELS 64u
#endif
enum spa_audio_format {
SPA_AUDIO_FORMAT_UNKNOWN,
@ -259,6 +263,8 @@ enum spa_audio_channel {
SPA_AUDIO_CHANNEL_START_Custom = 0x10000,
};
#define SPA_AUDIO_CHANNEL_IS_AUX(ch) ((ch)>=SPA_AUDIO_CHANNEL_START_Aux && (ch)<=SPA_AUDIO_CHANNEL_LAST_Aux)
enum spa_audio_volume_ramp_scale {
SPA_AUDIO_VOLUME_RAMP_INVALID,
SPA_AUDIO_VOLUME_RAMP_LINEAR,
@ -269,23 +275,34 @@ enum spa_audio_volume_ramp_scale {
#define SPA_AUDIO_FLAG_NONE (0) /*< no valid flag */
#define SPA_AUDIO_FLAG_UNPOSITIONED (1 << 0) /*< the position array explicitly
* contains unpositioned channels. */
/** Audio information description */
/** Audio information description. You can assume when you receive this structure
* that there is enought padding to accomodate all channel positions in case the
* channel count is more than SPA_AUDIO_MAX_CHANNELS. */
struct spa_audio_info_raw {
enum spa_audio_format format; /*< format, one of enum spa_audio_format */
uint32_t flags; /*< extra flags */
uint32_t rate; /*< sample rate */
uint32_t channels; /*< number of channels */
uint32_t channels; /*< number of channels. This can be more than SPA_AUDIO_MAX_CHANNELS
* and you may assume there is enough padding for the extra
* channel positions. */
uint32_t position[SPA_AUDIO_MAX_CHANNELS]; /*< channel position from enum spa_audio_channel */
/* padding follows here when channels > SPA_AUDIO_MAX_CHANNELS */
};
#define SPA_AUDIO_INFO_RAW_INIT(...) ((struct spa_audio_info_raw) { __VA_ARGS__ })
#define SPA_AUDIO_INFO_RAW_MAX_POSITION(size) (((size)-offsetof(struct spa_audio_info_raw,position))/sizeof(uint32_t))
#define SPA_AUDIO_INFO_RAW_VALID_SIZE(size) ((size) >= offsetof(struct spa_audio_info_raw, position))
#define SPA_KEY_AUDIO_FORMAT "audio.format" /**< an audio format as string,
* Ex. "S16LE" */
#define SPA_KEY_AUDIO_CHANNEL "audio.channel" /**< an audio channel as string,
* Ex. "FL" */
#define SPA_KEY_AUDIO_CHANNELS "audio.channels" /**< an audio channel count as int */
#define SPA_KEY_AUDIO_RATE "audio.rate" /**< an audio sample rate as int */
#define SPA_KEY_AUDIO_LAYOUT "audio.layout" /**< channel positions as predefined layout */
#define SPA_KEY_AUDIO_POSITION "audio.position" /**< channel positions as comma separated list
* of channels ex. "FL,FR" */
#define SPA_KEY_AUDIO_ALLOWED_RATES "audio.allowed-rates" /**< a list of allowed samplerates

View file

@ -6,6 +6,7 @@
#define SPA_AUDIO_TYPES_H
#include <spa/param/audio/raw-types.h>
#include <spa/param/audio/layout-types.h>
#include <spa/param/audio/iec958-types.h>
#include <spa/param/audio/mp3-types.h>
#include <spa/param/audio/aac-types.h>

View file

@ -0,0 +1,38 @@
/* Simple Plugin API */
/* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#ifndef SPA_PARAM_DICT_TYPES_H
#define SPA_PARAM_DICT_TYPES_H
#include <spa/utils/enum-types.h>
#include <spa/param/param-types.h>
#include <spa/param/dict.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* \addtogroup spa_param
* \{
*/
#define SPA_TYPE_INFO_PARAM_Dict SPA_TYPE_INFO_PARAM_BASE "Dict"
#define SPA_TYPE_INFO_PARAM_DICT_BASE SPA_TYPE_INFO_PARAM_Dict ":"
static const struct spa_type_info spa_type_param_dict[] = {
{ SPA_PARAM_DICT_START, SPA_TYPE_Id, SPA_TYPE_INFO_PARAM_DICT_BASE, spa_type_param, },
{ SPA_PARAM_DICT_info, SPA_TYPE_Struct, SPA_TYPE_INFO_PARAM_DICT_BASE "info", NULL, },
{ 0, 0, NULL, NULL },
};
/**
* \}
*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* SPA_PARAM_DICT_TYPES_H */

View file

@ -0,0 +1,125 @@
/* Simple Plugin API */
/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#ifndef SPA_PARAM_DICT_UTILS_H
#define SPA_PARAM_DICT_UTILS_H
#include <float.h>
#include <spa/utils/dict.h>
#include <spa/pod/builder.h>
#include <spa/pod/iter.h>
#include <spa/pod/parser.h>
#include <spa/pod/compare.h>
#include <spa/param/dict.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* \addtogroup spa_param
* \{
*/
#ifndef SPA_API_DICT_UTILS
#ifdef SPA_API_IMPL
#define SPA_API_DICT_UTILS SPA_API_IMPL
#else
#define SPA_API_DICT_UTILS static inline
#endif
#endif
SPA_API_DICT_UTILS int
spa_param_dict_compare(const struct spa_pod *a, const struct spa_pod *b)
{
return spa_pod_memcmp(a, b);
}
SPA_API_DICT_UTILS struct spa_pod *
spa_param_dict_build_dict(struct spa_pod_builder *builder, uint32_t id, struct spa_dict *dict)
{
struct spa_pod_frame f[2];
uint32_t i, n_items;
spa_pod_builder_push_object(builder, &f[0], SPA_TYPE_OBJECT_ParamDict, id);
n_items = dict ? dict->n_items : 0;
spa_pod_builder_prop(builder, SPA_PARAM_DICT_info, SPA_POD_PROP_FLAG_HINT_DICT);
spa_pod_builder_push_struct(builder, &f[1]);
spa_pod_builder_int(builder, n_items);
for (i = 0; i < n_items; i++) {
spa_pod_builder_string(builder, dict->items[i].key);
spa_pod_builder_string(builder, dict->items[i].value);
}
spa_pod_builder_pop(builder, &f[1]);
return (struct spa_pod*)spa_pod_builder_pop(builder, &f[0]);
}
SPA_API_DICT_UTILS struct spa_pod *
spa_param_dict_build_info(struct spa_pod_builder *builder, uint32_t id, struct spa_param_dict_info *info)
{
struct spa_pod_frame f;
spa_pod_builder_push_object(builder, &f, SPA_TYPE_OBJECT_ParamDict, id);
spa_pod_builder_add(builder, SPA_PARAM_DICT_info, SPA_POD_PROP_FLAG_HINT_DICT);
spa_pod_builder_primitive(builder, info->info);
return (struct spa_pod*)spa_pod_builder_pop(builder, &f);
}
SPA_API_DICT_UTILS int
spa_param_dict_parse(const struct spa_pod *dict, struct spa_param_dict_info *info, size_t size)
{
memset(info, 0, size);
return spa_pod_parse_object(dict,
SPA_TYPE_OBJECT_ParamDict, NULL,
SPA_PARAM_DICT_info, SPA_POD_PodStruct(&info->info));
}
SPA_API_DICT_UTILS int
spa_param_dict_info_parse(const struct spa_param_dict_info *info, size_t size,
struct spa_dict *dict, struct spa_dict_item *items)
{
struct spa_pod_parser prs;
uint32_t n, n_items;
const char *key, *value;
struct spa_pod_frame f[1];
spa_pod_parser_pod(&prs, info->info);
if (spa_pod_parser_push_struct(&prs, &f[0]) < 0 ||
spa_pod_parser_get_int(&prs, (int32_t*)&n_items) < 0)
return -EINVAL;
if (items == NULL) {
dict->n_items = n_items;
return 0;
}
n_items = SPA_MIN(dict->n_items, n_items);
for (n = 0; n < n_items; n++) {
if (spa_pod_parser_get(&prs,
SPA_POD_String(&key),
SPA_POD_String(&value),
NULL) < 0)
break;
if (key == NULL || value == NULL)
return -EINVAL;
items[n].key = key;
items[n].value = value;
}
dict->items = items;
spa_pod_parser_pop(&prs, &f[0]);
return 0;
}
/**
* \}
*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* SPA_PARAM_DICT_UTILS_H */

View file

@ -0,0 +1,42 @@
/* Simple Plugin API */
/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#ifndef SPA_PARAM_DICT_H
#define SPA_PARAM_DICT_H
#include <spa/param/param.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* \addtogroup spa_param
* \{
*/
/** properties for SPA_TYPE_OBJECT_ParamDict */
enum spa_param_dict {
SPA_PARAM_DICT_START,
SPA_PARAM_DICT_info, /**< Struct(
* Int: n_items
* (String: key
* String: value)*
* ) */
};
/** helper structure for managing info objects */
struct spa_param_dict_info {
const struct spa_pod *info;
};
/**
* \}
*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* SPA_PARAM_DICT_H */

View file

@ -41,7 +41,9 @@ static const struct spa_type_info spa_type_param[] = {
{ SPA_PARAM_Latency, SPA_TYPE_OBJECT_ParamLatency, SPA_TYPE_INFO_PARAM_ID_BASE "Latency", NULL },
{ SPA_PARAM_ProcessLatency, SPA_TYPE_OBJECT_ParamProcessLatency, SPA_TYPE_INFO_PARAM_ID_BASE "ProcessLatency", NULL },
{ SPA_PARAM_Tag, SPA_TYPE_OBJECT_ParamTag, SPA_TYPE_INFO_PARAM_ID_BASE "Tag", NULL },
{ SPA_PARAM_PeerFormats, SPA_TYPE_Struct, SPA_TYPE_INFO_PARAM_ID_BASE "PeerFormats", NULL },
{ SPA_PARAM_PeerEnumFormat, SPA_TYPE_OBJECT_PeerParam, SPA_TYPE_INFO_PARAM_ID_BASE "PeerEnumFormat", NULL },
{ SPA_PARAM_Capability, SPA_TYPE_OBJECT_ParamDict, SPA_TYPE_INFO_PARAM_ID_BASE "Capability", NULL },
{ SPA_PARAM_PeerCapability, SPA_TYPE_OBJECT_PeerParam, SPA_TYPE_INFO_PARAM_ID_BASE "PeerCapability", NULL },
{ 0, 0, NULL, NULL },
};

View file

@ -40,7 +40,11 @@ enum spa_param_type {
SPA_PARAM_Latency, /**< latency reporting, a SPA_TYPE_OBJECT_ParamLatency */
SPA_PARAM_ProcessLatency, /**< processing latency, a SPA_TYPE_OBJECT_ParamProcessLatency */
SPA_PARAM_Tag, /**< tag reporting, a SPA_TYPE_OBJECT_ParamTag. Since 0.3.79 */
SPA_PARAM_PeerFormats, /**< peer formats, a SPA_TYPE_Struct of SPA_TYPE_OBJECT_Format. Since 1.5.0 */
SPA_PARAM_PeerEnumFormat, /**< peer formats, a SPA_TYPE_OBJECT_PeerParam with
* SPA_TYPE_OBJECT_Format. Since 1.5.0 */
SPA_PARAM_Capability, /**< capability info, a SPA_TYPE_OBJECT_ParamDict, Since 1.5.84 */
SPA_PARAM_PeerCapability, /**< peer capabilities, a SPA_TYPE_OBJECT_PeerParam with
* SPA_TYPE_OBJECT_ParamDict, since 1.5.84 */
};
/** information about a parameter */

View file

@ -0,0 +1,38 @@
/* Simple Plugin API */
/* SPDX-FileCopyrightText: Copyright © 2018 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#ifndef SPA_PARAM_PEER_TYPES_H
#define SPA_PARAM_PEER_TYPES_H
#include <spa/utils/enum-types.h>
#include <spa/param/param-types.h>
#include <spa/param/peer.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* \addtogroup spa_param
* \{
*/
#define SPA_TYPE_INFO_PeerParam SPA_TYPE_INFO_OBJECT_BASE "PeerParam"
#define SPA_TYPE_INFO_PEER_PARAM_BASE SPA_TYPE_INFO_PeerParam ":"
static const struct spa_type_info spa_type_peer_param[] = {
{ SPA_PEER_PARAM_START, SPA_TYPE_Id, SPA_TYPE_INFO_PEER_PARAM_BASE, spa_type_param, },
{ SPA_ID_INVALID, SPA_TYPE_Id, SPA_TYPE_INFO_PEER_PARAM_BASE, NULL, },
{ 0, 0, NULL, NULL },
};
/**
* \}
*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* SPA_PARAM_PEER_TYPES_H */

View file

@ -0,0 +1,92 @@
/* Simple Plugin API */
/* SPDX-FileCopyrightText: Copyright © 2023 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#ifndef SPA_PARAM_PEER_PARAM_UTILS_H
#define SPA_PARAM_PEER_PARAM_UTILS_H
#include <float.h>
#include <spa/utils/dict.h>
#include <spa/pod/builder.h>
#include <spa/pod/iter.h>
#include <spa/pod/parser.h>
#include <spa/pod/compare.h>
#include <spa/param/peer.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* \addtogroup spa_param
* \{
*/
#ifndef SPA_API_PEER_PARAM_UTILS
#ifdef SPA_API_IMPL
#define SPA_API_PEER_PARAM_UTILS SPA_API_IMPL
#else
#define SPA_API_PEER_PARAM_UTILS static inline
#endif
#endif
SPA_API_PEER_PARAM_UTILS int
spa_peer_param_parse(const struct spa_pod *param, struct spa_peer_param_info *info,
size_t size, void **state)
{
int res;
const struct spa_pod_object *obj = (const struct spa_pod_object*)param;
const struct spa_pod_prop *first, *start, *cur;
if ((res = spa_pod_parse_object(param,
SPA_TYPE_OBJECT_PeerParam, NULL)) < 0)
return res;
first = spa_pod_prop_first(&obj->body);
start = *state ? spa_pod_prop_next((struct spa_pod_prop*)*state) : first;
res = 0;
for (cur = start; spa_pod_prop_is_inside(&obj->body, obj->pod.size, cur);
cur = spa_pod_prop_next(cur)) {
info->peer_id = cur->key;
info->param = &cur->value;
*state = (void*)cur;
return 1;
}
return 0;
}
SPA_API_PEER_PARAM_UTILS void
spa_peer_param_build_start(struct spa_pod_builder *builder, struct spa_pod_frame *f, uint32_t id)
{
spa_pod_builder_push_object(builder, f, SPA_TYPE_OBJECT_PeerParam, id);
}
SPA_API_PEER_PARAM_UTILS void
spa_peer_param_build_add_param(struct spa_pod_builder *builder, uint32_t peer_id,
const struct spa_pod *param)
{
spa_pod_builder_prop(builder, peer_id, 0);
if (param)
spa_pod_builder_primitive(builder, param);
else
spa_pod_builder_none(builder);
}
SPA_API_PEER_PARAM_UTILS struct spa_pod *
spa_peer_param_build_end(struct spa_pod_builder *builder, struct spa_pod_frame *f)
{
return (struct spa_pod*)spa_pod_builder_pop(builder, f);
}
/**
* \}
*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* SPA_PARAM_PEER_PARAM_UTILS_H */

View file

@ -0,0 +1,37 @@
/* Simple Plugin API */
/* SPDX-FileCopyrightText: Copyright © 2025 Wim Taymans */
/* SPDX-License-Identifier: MIT */
#ifndef SPA_PARAM_PEER_PARAM_H
#define SPA_PARAM_PEER_PARAM_H
#include <spa/param/param.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* \addtogroup spa_param
* \{
*/
/** properties for SPA_TYPE_OBJECT_PeerParam */
enum spa_peer_param {
SPA_PEER_PARAM_START, /**< id of peer as key, SPA_TYPE_Pod as value */
SPA_PEER_PARAM_END = 0xfffffffe,
};
struct spa_peer_param_info {
uint32_t peer_id;
const struct spa_pod *param;
};
/**
* \}
*/
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* SPA_PARAM_PEER_PARAM_H */

View file

@ -40,6 +40,9 @@ static const struct spa_type_info spa_type_props[] = {
{ SPA_PROP_quality, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "quality", NULL },
{ SPA_PROP_bluetoothAudioCodec, SPA_TYPE_Id, SPA_TYPE_INFO_PROPS_BASE "bluetoothAudioCodec", spa_type_bluetooth_audio_codec },
{ SPA_PROP_bluetoothOffloadActive, SPA_TYPE_Bool, SPA_TYPE_INFO_PROPS_BASE "bluetoothOffloadActive", NULL },
{ SPA_PROP_clockId, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockId", NULL },
{ SPA_PROP_clockDevice, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockDevice", NULL },
{ SPA_PROP_clockInterface, SPA_TYPE_String, SPA_TYPE_INFO_PROPS_BASE "clockInterface", NULL },
{ SPA_PROP_waveType, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "waveType", NULL },
{ SPA_PROP_frequency, SPA_TYPE_Int, SPA_TYPE_INFO_PROPS_BASE "frequency", NULL },

View file

@ -55,6 +55,9 @@ enum spa_prop {
SPA_PROP_quality,
SPA_PROP_bluetoothAudioCodec,
SPA_PROP_bluetoothOffloadActive,
SPA_PROP_clockId,
SPA_PROP_clockDevice,
SPA_PROP_clockInterface,
SPA_PROP_START_Audio = 0x10000, /**< audio related properties */
SPA_PROP_waveType,

View file

@ -11,6 +11,7 @@
#include <spa/pod/builder.h>
#include <spa/pod/iter.h>
#include <spa/pod/parser.h>
#include <spa/pod/compare.h>
#include <spa/param/tag.h>
#ifdef __cplusplus
@ -33,8 +34,7 @@ extern "C" {
SPA_API_TAG_UTILS int
spa_tag_compare(const struct spa_pod *a, const struct spa_pod *b)
{
return ((a == b) || (a && b && SPA_POD_SIZE(a) == SPA_POD_SIZE(b) &&
memcmp(a, b, SPA_POD_SIZE(b)) == 0)) ? 0 : 1;
return spa_pod_memcmp(a, b);
}
SPA_API_TAG_UTILS int

View file

@ -15,5 +15,7 @@
#include <spa/param/profile-types.h>
#include <spa/param/route-types.h>
#include <spa/param/tag-types.h>
#include <spa/param/dict-types.h>
#include <spa/param/peer-types.h>
#endif /* SPA_PARAM_TYPE_INFO_H */

View file

@ -78,6 +78,36 @@ SPA_API_POD_BODY uint32_t spa_pod_type_size(uint32_t type)
return 0;
}
SPA_API_POD_BODY int spa_pod_choice_n_values(uint32_t choice_type, uint32_t *min, uint32_t *max)
{
switch (choice_type) {
case SPA_CHOICE_Enum:
*min = 2;
*max = UINT32_MAX;
break;
case SPA_CHOICE_Range:
*min = *max = 3;
break;
case SPA_CHOICE_Step:
*min = *max = 4;
break;
case SPA_CHOICE_None:
case SPA_CHOICE_Flags:
*min = *max = 1;
break;
default:
/*
* This must always return at least 1, because callers
* assume that n_vals >= spa_pod_choice_n_values()
* mean that n_vals is at least 1.
*/
*min = 1;
*max = UINT32_MAX;
return 0;
}
return 1;
}
SPA_API_POD_BODY int spa_pod_body_from_data(void *data, size_t maxsize, off_t offset, size_t size,
struct spa_pod *pod, const void **body)
{
@ -243,9 +273,9 @@ SPA_API_POD_BODY int spa_pod_is_pointer(const struct spa_pod *pod)
SPA_API_POD_BODY int spa_pod_body_get_pointer(const struct spa_pod *pod, const void *body,
uint32_t *type, const void **value)
{
struct spa_pod_pointer_body b;
if (!spa_pod_is_pointer(pod))
return -EINVAL;
struct spa_pod_pointer_body b;
SPA_POD_BODY_LOAD_FIELD_ONCE(&b, body, type);
SPA_POD_BODY_LOAD_FIELD_ONCE(&b, body, value);
*type = b.type;
@ -333,6 +363,8 @@ SPA_API_POD_BODY const void *spa_pod_array_body_get_values(const struct spa_pod_
*n_values = child_size ? (arr->pod.size - sizeof(arr->body)) / child_size : 0;
*val_size = child_size;
*val_type = arr->body.child.type;
if (*val_size < spa_pod_type_size(*val_type))
*n_values = 0;
return body;
}
@ -366,13 +398,16 @@ SPA_API_POD_BODY const void *spa_pod_choice_body_get_values(const struct spa_pod
const void *body, uint32_t *n_values, uint32_t *choice,
uint32_t *val_size, uint32_t *val_type)
{
uint32_t child_size = pod->body.child.size;
uint32_t child_size = pod->body.child.size, min, max;
*val_size = child_size;
*val_type = pod->body.child.type;
*n_values = child_size ? (pod->pod.size - sizeof(pod->body)) / child_size : 0;
*choice = pod->body.type;
if (*choice == SPA_CHOICE_None)
*n_values = SPA_MIN(1u, *n_values);
spa_pod_choice_n_values(*choice, &min, &max);
if (*n_values < min || *val_size < spa_pod_type_size(*val_type))
*n_values = 0;
else if (*n_values > max)
*n_values = max;
return body;
}

View file

@ -80,6 +80,13 @@ SPA_API_POD_COMPARE int spa_pod_compare_value(uint32_t type, const void *r1, con
return 0;
}
SPA_API_POD_COMPARE int spa_pod_memcmp(const struct spa_pod *a,
const struct spa_pod *b)
{
return ((a == b) || (a && b && SPA_POD_SIZE(a) == SPA_POD_SIZE(b) &&
memcmp(a, b, SPA_POD_SIZE(b)) == 0)) ? 0 : 1;
}
SPA_API_POD_COMPARE int spa_pod_compare(const struct spa_pod *pod1,
const struct spa_pod *pod2)
{
@ -149,12 +156,8 @@ SPA_API_POD_COMPARE int spa_pod_compare(const struct spa_pod *pod1,
break;
}
case SPA_TYPE_Array:
{
if (pod1->size != pod2->size)
return -EINVAL;
res = memcmp(SPA_POD_BODY(pod1), SPA_POD_BODY(pod2), pod2->size);
res = spa_pod_memcmp(pod1, pod2);
break;
}
default:
if (pod1->size != pod2->size)
return -EINVAL;

View file

@ -70,8 +70,10 @@ SPA_API_POD_DYNAMIC void spa_pod_dynamic_builder_continue(struct spa_pod_dynamic
SPA_API_POD_DYNAMIC void spa_pod_dynamic_builder_clean(struct spa_pod_dynamic_builder *builder)
{
if (builder->data != builder->b.data)
if (builder->data != builder->b.data) {
free(builder->b.data);
builder->b.data = NULL;
}
}
SPA_DEFINE_AUTO_CLEANUP(spa_pod_dynamic_builder, struct spa_pod_dynamic_builder, {

View file

@ -82,7 +82,7 @@ spa_pod_filter_prop(struct spa_pod_builder *b,
v1 = spa_pod_get_values(&p1->value, &nalt1, &p1c);
v2 = spa_pod_get_values(&p2->value, &nalt2, &p2c);
/* empty choices */
/* empty/invalid choices */
if (nalt1 < 1 || nalt2 < 1)
return -EINVAL;
@ -95,8 +95,6 @@ spa_pod_filter_prop(struct spa_pod_builder *b,
/* incompatible property types */
if (type != v2->type || size != v2->size || p1->key != p2->key)
return -EINVAL;
if (size < spa_pod_type_size(type))
return -EINVAL;
/* start with copying the property */
spa_pod_builder_prop(b, p1->key, p1->flags & p2->flags);
@ -341,9 +339,7 @@ SPA_API_POD_FILTER int spa_pod_filter_part(struct spa_pod_builder *b,
default:
if (pf != NULL) {
if (pp->size != pf->size)
return -EINVAL;
if (memcmp(pp, pf, pp->size) != 0)
if (spa_pod_memcmp(pp, pf) != 0)
return -EINVAL;
do_advance = true;
}
@ -406,7 +402,7 @@ SPA_API_POD_FILTER int spa_pod_filter_object_make(struct spa_pod_object *pod)
struct spa_pod *v = spa_pod_get_values(&res->value, &nvals, &choice);
const void *vals = SPA_POD_BODY(v);
if (v->size < spa_pod_type_size(v->type))
if (nvals < 1)
continue;
if (spa_pod_compare_is_valid_choice(v->type, v->size,

View file

@ -228,7 +228,7 @@ SPA_API_POD_ITER struct spa_pod *spa_pod_get_values(const struct spa_pod *pod,
spa_pod_choice_body_get_values(p, SPA_POD_BODY_CONST(p), n_vals, choice, &size, &type);
return (struct spa_pod*)&p->body.child;
} else {
*n_vals = 1;
*n_vals = pod->size < spa_pod_type_size(pod->type) ? 0 : 1;
*choice = SPA_CHOICE_None;
return (struct spa_pod*)pod;
}

View file

@ -90,13 +90,14 @@ spa_pod_parser_read_header(struct spa_pod_parser *parser, uint32_t offset, uint3
/* Cast to uint64_t to avoid wraparound. */
const uint64_t long_offset = (uint64_t)offset + header_size;
if (long_offset <= size && (offset & 7) == 0) {
struct spa_pod *pod;
/* a barrier around the memcpy to make sure it is not moved around or
* duplicated after the size check below. We need to to work on shared
* memory while there could be updates happening while we read. */
* duplicated after the size check below. We need to work on shared
* memory and so there could be updates happening while we read. */
SPA_BARRIER;
memcpy(header, SPA_PTROFF(parser->data, offset, void), header_size);
SPA_BARRIER;
struct spa_pod *pod = SPA_PTROFF(header, pod_offset, struct spa_pod);
pod = SPA_PTROFF(header, pod_offset, struct spa_pod);
/* Check that the size (rounded to the next multiple of 8) is in bounds. */
if (long_offset + SPA_ROUND_UP_N((uint64_t)pod->size, SPA_POD_ALIGN) <= size) {
*body = SPA_PTROFF(parser->data, long_offset, void);

View file

@ -28,7 +28,7 @@ extern "C" {
#define SPA_CHOICE_RANGE(def,min,max) 3,(def),(min),(max)
#define SPA_CHOICE_STEP(def,min,max,step) 4,(def),(min),(max),(step)
#define SPA_CHOICE_ENUM(n_vals,...) (n_vals),##__VA_ARGS__
#define SPA_CHOICE_ENUM(n_vals,def,alt1,...) (n_vals),(def),(alt1),##__VA_ARGS__
#define SPA_CHOICE_FLAGS(flags) 1, (flags)
#define SPA_CHOICE_FEATURES(features) 1, (features)
#define SPA_CHOICE_BOOL(def) 3,(def),(def),!(def)

View file

@ -117,6 +117,8 @@ SPA_API_THREAD int spa_thread_utils_drop_rt(struct spa_thread_utils *o,
#define SPA_KEY_THREAD_STACK_SIZE "thread.stack-size" /* the stack size of the thread */
#define SPA_KEY_THREAD_AFFINITY "thread.affinity" /* array of CPUs for this thread */
#define SPA_KEY_THREAD_CREATOR "thread.creator" /* platform specific thread creator function */
#define SPA_KEY_THREAD_RESET_ON_FORK "thread.reset-on-fork" /* reset priority and policy for real-time threads
on fork. Default true */
/**
* \}

View file

@ -46,9 +46,13 @@ __extension__ ({ \
/* ========================================================================== */
#if defined(__has_attribute) && __has_attribute(__cleanup__)
#ifdef __has_attribute
#if __has_attribute(__cleanup__)
#define spa_cleanup(func) __attribute__((__cleanup__(func)))
#endif
#endif
#ifdef spa_cleanup
#define SPA_DEFINE_AUTO_CLEANUP(name, type, ...) \
typedef __typeof__(type) _spa_auto_cleanup_type_ ## name; \

View file

@ -54,6 +54,15 @@ SPA_API_JSON void spa_json_init(struct spa_json * iter, const char *data, size_t
{
*iter = SPA_JSON_INIT(data, size);
}
#define SPA_JSON_INIT_RELAX(type,data,size) \
((struct spa_json) { (data), (data)+(size), NULL, (uint32_t)((type) == '[' ? 0x10 : 0x0), 0 })
SPA_API_JSON void spa_json_init_relax(struct spa_json * iter, char type, const char *data, size_t size)
{
*iter = SPA_JSON_INIT_RELAX(type, data, size);
}
#define SPA_JSON_ENTER(iter) ((struct spa_json) { (iter)->cur, (iter)->end, (iter), (iter)->state & 0xff0, 0 })
SPA_API_JSON void spa_json_enter(struct spa_json * iter, struct spa_json * sub)

View file

@ -105,7 +105,7 @@ SPA_API_JSON_UTILS int spa_json_begin_container(struct spa_json * iter,
spa_json_init(iter, data, size);
res = spa_json_enter_container(iter, iter, type);
if (res == -EPROTO && relax)
spa_json_init(iter, data, size);
spa_json_init_relax(iter, type, data, size);
else if (res <= 0)
return res;
return 1;

View file

@ -10,6 +10,7 @@
#include <errno.h>
#include <stdlib.h>
#include <locale.h>
#include <sys/types.h>
#include <spa/utils/defs.h>

View file

@ -5,6 +5,7 @@
#ifndef SPA_TYPE_INFO_H
#define SPA_TYPE_INFO_H
#include <spa/utils/defs.h>
#include <spa/utils/type.h>
#include <spa/utils/enum-types.h>
@ -78,6 +79,8 @@ static const struct spa_type_info spa_types[] = {
{ SPA_TYPE_OBJECT_ParamLatency, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Latency, spa_type_param_latency },
{ SPA_TYPE_OBJECT_ParamProcessLatency, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_ProcessLatency, spa_type_param_process_latency },
{ SPA_TYPE_OBJECT_ParamTag, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Tag, spa_type_param_tag },
{ SPA_TYPE_OBJECT_PeerParam, SPA_TYPE_Object, SPA_TYPE_INFO_PeerParam, spa_type_peer_param },
{ SPA_TYPE_OBJECT_ParamDict, SPA_TYPE_Object, SPA_TYPE_INFO_PARAM_Dict, spa_type_param_dict },
{ 0, 0, NULL, NULL }
};

View file

@ -88,6 +88,8 @@ enum {
SPA_TYPE_OBJECT_ParamLatency,
SPA_TYPE_OBJECT_ParamProcessLatency,
SPA_TYPE_OBJECT_ParamTag,
SPA_TYPE_OBJECT_PeerParam,
SPA_TYPE_OBJECT_ParamDict,
_SPA_TYPE_OBJECT_LAST, /**< not part of ABI */
/* vendor extensions */

View file

@ -1,4 +1,6 @@
#undef SPA_AUDIO_MAX_CHANNELS
#define SPA_API_IMPL SPA_EXPORT
#include <spa/utils/defs.h>
#include <spa/buffer/alloc.h>
@ -61,6 +63,7 @@
#include <spa/param/audio/iec958-types.h>
#include <spa/param/audio/iec958-utils.h>
#include <spa/param/audio/layout.h>
#include <spa/param/audio/layout-types.h>
#include <spa/param/audio/mp3.h>
#include <spa/param/audio/mp3-types.h>
#include <spa/param/audio/mp3-utils.h>
@ -165,9 +168,3 @@
#include <spa/utils/string.h>
#include <spa/utils/type.h>
#include <spa/utils/type-info.h>

View file

@ -245,7 +245,7 @@ static void init_device(pa_card *impl, pa_alsa_device *dev, pa_alsa_direction_t
pa_proplist_update(dev->proplist, PA_UPDATE_REPLACE, m->input_proplist);
}
if (m->split) {
char pos[2048];
char pos[PA_CHANNELS_MAX*8];
struct spa_strbuf b;
int i;
@ -1137,51 +1137,56 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask)
}
old_position = pa_proplist_gets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED);
if (eld.speakers == 0) {
if (eld.speakers == 0 || eld.lpcm_channels == 0) {
changed |= old_position != NULL;
pa_proplist_unset(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED);
} else {
uint32_t positions[eld.lpcm_channels];
char position[64];
int i = 0, pos = 0;
char position[eld.lpcm_channels * 8];
struct spa_strbuf b;
int i = 0;
#define _ADD_CHANNEL_POSITION(pos) \
{ \
if (i < eld.lpcm_channels) \
positions[i++] = pos; \
}
if (eld.speakers & 0x01) {
positions[i++] = ACP_CHANNEL_FL;
positions[i++] = ACP_CHANNEL_FR;
_ADD_CHANNEL_POSITION(ACP_CHANNEL_FL);
_ADD_CHANNEL_POSITION(ACP_CHANNEL_FR);
}
if (eld.speakers & 0x02) {
positions[i++] = ACP_CHANNEL_LFE;
_ADD_CHANNEL_POSITION(ACP_CHANNEL_LFE);
}
if (eld.speakers & 0x04) {
positions[i++] = ACP_CHANNEL_FC;
_ADD_CHANNEL_POSITION(ACP_CHANNEL_FC);
}
if (eld.speakers & 0x08) {
positions[i++] = ACP_CHANNEL_RL;
positions[i++] = ACP_CHANNEL_RR;
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RL);
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RR);
}
/* The rest are out of order in order of what channels we would prefer to use/expose first */
if (eld.speakers & 0x40) {
/* Use SL/SR instead of RLC/RRC */
positions[i++] = ACP_CHANNEL_SL;
positions[i++] = ACP_CHANNEL_SR;
_ADD_CHANNEL_POSITION(ACP_CHANNEL_SL);
_ADD_CHANNEL_POSITION(ACP_CHANNEL_SR);
}
if (eld.speakers & 0x20) {
positions[i++] = ACP_CHANNEL_RLC;
positions[i++] = ACP_CHANNEL_RRC;
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RLC);
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RRC);
}
if (eld.speakers & 0x10) {
positions[i++] = ACP_CHANNEL_RC;
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RC);
}
while (i < eld.lpcm_channels)
positions[i++] = ACP_CHANNEL_UNKNOWN;
for (i = 0, pos = 0; i < eld.lpcm_channels; i++) {
pos += snprintf(&position[pos], sizeof(position) - pos, "%s,", channel_names[positions[i]]);
}
/* Overwrite trailing , */
position[pos - 1] = 0;
spa_strbuf_init(&b, position, sizeof(position));
spa_strbuf_append(&b, "[");
for (i = 0; i < eld.lpcm_channels; i++)
spa_strbuf_append(&b, "%s%s", i ? "," : "", channel_names[positions[i]]);
spa_strbuf_append(&b, "]");
changed |= (old_position == NULL) || (!spa_streq(old_position, position));
pa_proplist_sets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED, position);

View file

@ -27,7 +27,11 @@
extern "C" {
#endif
#ifdef SPA_AUDIO_MAX_CHANNELS
#define PA_CHANNELS_MAX ((int)SPA_AUDIO_MAX_CHANNELS)
#else
#define PA_CHANNELS_MAX 64
#endif
#define PA_CHANNEL_MAP_SNPRINT_MAX (PA_CHANNELS_MAX * 32)
@ -451,7 +455,6 @@ static inline int pa_channel_map_equal(const pa_channel_map *a, const pa_channel
static inline char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map) {
unsigned channel;
bool first = true;
char *e;
if (!pa_channel_map_valid(map)) {
pa_snprintf(s, l, "%s", _("(invalid)"));
@ -460,12 +463,10 @@ static inline char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_m
*(e = s) = 0;
for (channel = 0; channel < map->channels && l > 1; channel++) {
l -= pa_snprintf(e, l, "%s%s",
first ? "" : ",",
channel == 0 ? "" : ",",
pa_channel_position_to_string(map->map[channel]));
e = strchr(e, 0);
first = false;
}
return s;
}

View file

@ -38,6 +38,7 @@
extern struct spa_i18n *acp_i18n;
#define MAX_POLL 16
#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS
#define DEFAULT_DEVICE "hw:0"
#define DEFAULT_AUTO_PROFILE true
@ -155,12 +156,13 @@ static int emit_node(struct impl *this, struct acp_device *dev)
const struct acp_dict_item *it;
uint32_t n_items, i;
char device_name[128], path[210], channels[16], ch[12], routes[16];
char card_index[16], card_name[64], *p;
char positions[SPA_AUDIO_MAX_CHANNELS * 12];
char card_index[16], card_name[64];
char positions[MAX_CHANNELS * 12];
char codecs[512];
struct spa_device_object_info info;
struct acp_card *card = this->card;
const char *stream, *card_id, *bus;
struct spa_strbuf b;
info = SPA_DEVICE_OBJECT_INFO_INIT();
info.type = SPA_TYPE_INTERFACE_Node;
@ -199,11 +201,13 @@ static int emit_node(struct impl *this, struct acp_device *dev)
snprintf(channels, sizeof(channels), "%d", dev->format.channels);
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_CHANNELS, channels);
p = positions;
spa_strbuf_init(&b, positions, sizeof(positions));
spa_strbuf_append(&b, "[");
for (i = 0; i < dev->format.channels; i++) {
p += snprintf(p, 12, "%s%s", i == 0 ? "" : ",",
spa_strbuf_append(&b, "%s%s", i == 0 ? " " : ", ",
acp_channel_str(ch, sizeof(ch), dev->format.map[i]));
}
spa_strbuf_append(&b, " ]");
items[n_items++] = SPA_DICT_ITEM_INIT(SPA_KEY_AUDIO_POSITION, positions);
if (dev->n_codecs > 0) {
@ -673,8 +677,8 @@ static int apply_device_props(struct impl *this, struct acp_device *dev, struct
struct spa_pod_prop *prop;
struct spa_pod_object *obj = (struct spa_pod_object *) props;
int changed = 0;
float volumes[SPA_AUDIO_MAX_CHANNELS];
uint32_t channels[SPA_AUDIO_MAX_CHANNELS];
float volumes[MAX_CHANNELS];
uint32_t channels[MAX_CHANNELS];
uint32_t n_volumes = 0;
if (!spa_pod_is_object_type(props, SPA_TYPE_OBJECT_Props))
@ -696,13 +700,13 @@ static int apply_device_props(struct impl *this, struct acp_device *dev, struct
break;
case SPA_PROP_channelVolumes:
if ((n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float,
volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) {
volumes, SPA_N_ELEMENTS(volumes))) > 0) {
changed++;
}
break;
case SPA_PROP_channelMap:
if (spa_pod_copy_array(&prop->value, SPA_TYPE_Id,
channels, SPA_AUDIO_MAX_CHANNELS) > 0) {
channels, SPA_N_ELEMENTS(channels)) > 0) {
changed++;
}
break;

View file

@ -163,10 +163,10 @@ static int alsa_set_param(struct state *state, const char *k, const char *s)
int fmt_change = 0;
if (spa_streq(k, SPA_KEY_AUDIO_CHANNELS)) {
state->default_channels = atoi(s);
if (state->default_channels > SPA_AUDIO_MAX_CHANNELS) {
if (state->default_channels > MAX_CHANNELS) {
spa_log_warn(state->log, "%p: %s: %s > %d, clamping",
state, k, s, SPA_AUDIO_MAX_CHANNELS);
state->default_channels = SPA_AUDIO_MAX_CHANNELS;
state, k, s, MAX_CHANNELS);
state->default_channels = MAX_CHANNELS;
}
fmt_change++;
} else if (spa_streq(k, SPA_KEY_AUDIO_RATE)) {
@ -240,35 +240,33 @@ static int alsa_set_param(struct state *state, const char *k, const char *s)
static int position_to_string(struct channel_map *map, char *val, size_t len)
{
uint32_t i, o = 0;
int r;
o += snprintf(val, len, "[ ");
for (i = 0; i < map->channels; i++) {
r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ",
spa_debug_type_find_short_name(spa_type_audio_channel,
map->pos[i]));
if (r < 0 || o + r >= len)
return -ENOSPC;
o += r;
uint32_t i;
char pos[8];
struct spa_strbuf b;
spa_strbuf_init(&b, val, len);
spa_strbuf_append(&b, "[");
for (i = 0; i < map->n_pos; i++) {
spa_strbuf_append(&b, "%s%s", i == 0 ? " " : ", ",
spa_type_audio_channel_make_short_name(map->pos[i],
pos, sizeof(pos), "UNK"));
}
if (len > o)
o += snprintf(val+o, len-o, " ]");
if (spa_strbuf_append(&b, " ]") < 2)
return -ENOSPC;
return 0;
}
static int uint32_array_to_string(uint32_t *vals, uint32_t n_vals, char *val, size_t len)
{
uint32_t i, o = 0;
int r;
o += snprintf(val, len, "[ ");
for (i = 0; i < n_vals; i++) {
r = snprintf(val+o, len-o, "%s%d", i == 0 ? "" : ", ", vals[i]);
if (r < 0 || o + r >= len)
return -ENOSPC;
o += r;
}
if (len > o)
o += snprintf(val+o, len-o, " ]");
uint32_t i;
struct spa_strbuf b;
spa_strbuf_init(&b, val, len);
spa_strbuf_append(&b, "[");
for (i = 0; i < n_vals; i++)
spa_strbuf_append(&b, "%s%d", i == 0 ? " " : ", ", vals[i]);
if (spa_strbuf_append(&b, " ]") < 2)
return -ENOSPC;
return 0;
}
@ -775,7 +773,7 @@ static void bind_ctl_event(struct spa_source *source)
snd_ctl_elem_id_alloca(&bound_id);
snd_ctl_elem_value_alloca(&old_value);
while ((err = snd_ctl_read(state->ctl, ev) > 0)) {
while ((err = snd_ctl_read(state->ctl, ev)) > 0) {
bool changed = false;
if (snd_ctl_event_get_type(ev) != SND_CTL_EVENT_ELEM)
@ -1584,8 +1582,8 @@ static int add_channels(struct state *state, bool all, uint32_t index, uint32_t
spa_log_debug(state->log, "channels (%d %d) default:%d all:%d",
min, max, state->default_channels, all);
min = SPA_MIN(min, SPA_AUDIO_MAX_CHANNELS);
max = SPA_MIN(max, SPA_AUDIO_MAX_CHANNELS);
min = SPA_MIN(min, MAX_CHANNELS);
max = SPA_MIN(max, MAX_CHANNELS);
if (state->default_channels != 0 && !all) {
if (min > state->default_channels ||
@ -1645,7 +1643,7 @@ skip_channels:
} else {
const struct channel_map *map = NULL;
spa_pod_builder_int(b, min);
if (state->default_pos.channels == min) {
if (state->default_pos.n_pos == min) {
map = &state->default_pos;
spa_log_debug(state->log, "%p: using provided default", state);
} else if (min <= 8) {
@ -1655,7 +1653,7 @@ skip_channels:
if (map) {
spa_pod_builder_prop(b, SPA_FORMAT_AUDIO_position, 0);
spa_pod_builder_push_array(b, &f[0]);
for (i = 0; i < map->channels; i++) {
for (i = 0; i < map->n_pos; i++) {
spa_log_debug(state->log, "%p: position %zd %d", state, i, map->pos[i]);
spa_pod_builder_id(b, map->pos[i]);
}
@ -2038,7 +2036,9 @@ static void recalc_headroom(struct state *state)
uint32_t latency;
uint32_t rate = 0;
if (state->position != NULL)
if (state->force_quantum && !state->following)
rate = state->rate;
else if (state->position != NULL)
rate = state->position->clock.target_rate.denom;
if (state->use_period_size_min_as_headroom)
@ -2065,8 +2065,6 @@ static void recalc_headroom(struct state *state)
state->headroom = 0;
latency = SPA_MAX(state->min_delay, SPA_MIN(state->max_delay, state->headroom));
if (rate != 0 && state->rate != 0)
latency = SPA_SCALE32_UP(latency, rate, state->rate);
if (state->is_firewire) {
/* XXX: For ALSA FireWire drivers, unlike for other ALSA drivers, buffer size
@ -2074,6 +2072,8 @@ static void recalc_headroom(struct state *state)
*/
latency += state->buffer_frames;
}
if (rate != 0 && state->rate != 0)
latency = SPA_SCALE32_UP(latency, rate, state->rate);
state->latency[state->port_direction].min_rate =
state->latency[state->port_direction].max_rate = latency;

View file

@ -36,6 +36,7 @@ extern "C" {
#endif
#define MAX_RATES 16
#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS
#define DEFAULT_PERIOD 1024u
#define DEFAULT_RATE 48000u
@ -71,8 +72,8 @@ struct buffer {
#define BW_PERIOD (3 * SPA_NSEC_PER_SEC)
struct channel_map {
uint32_t channels;
uint32_t pos[SPA_AUDIO_MAX_CHANNELS];
uint32_t n_pos;
uint32_t pos[MAX_CHANNELS];
};
struct card {
@ -314,7 +315,7 @@ void spa_alsa_emit_port_info(struct state *state, bool full);
static inline void spa_alsa_parse_position(struct channel_map *map, const char *val, size_t len)
{
spa_audio_parse_position(val, len, map->pos, &map->channels);
spa_audio_parse_position_n(val, len, map->pos, SPA_N_ELEMENTS(map->pos), &map->n_pos);
}
static inline uint32_t spa_alsa_parse_rates(uint32_t *rates, uint32_t max, const char *val, size_t len)

View file

@ -808,6 +808,7 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
if (spa_format_audio_parse(param, &info) < 0) {
spa_log_error(this->log, "%p: cannot set Format param: "
"parsing the POD failed", this);
spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 0, NULL, param);
return -EINVAL;
}
if (info.media_subtype != SPA_MEDIA_SUBTYPE_raw) {
@ -841,6 +842,7 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
SPA_PARAM_PORT_CONFIG_format, SPA_POD_OPT_Pod(&format)) < 0) {
spa_log_error(this->log, "%p: cannot set PortConfig param: "
"parsing the POD failed", this);
spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 0, NULL, param);
return -EINVAL;
}
@ -848,8 +850,12 @@ static int impl_node_set_param(void *object, uint32_t id, uint32_t flags,
struct spa_audio_info info;
spa_zero(info);
if ((res = spa_format_audio_parse(format, &info)) < 0)
if ((res = spa_format_audio_parse(format, &info)) < 0) {
spa_log_error(this->log, "%p: cannot set PortConfig param: "
"parsing format failed: %s", this, spa_strerror(res));
spa_debug_log_pod(this->log, SPA_LOG_LEVEL_ERROR, 0, NULL, format);
return res;
}
if (info.media_subtype == SPA_MEDIA_SUBTYPE_raw) {
info.info.raw.rate = 0;
@ -1213,6 +1219,9 @@ static void follower_convert_port_info(void *data,
case SPA_PARAM_Tag:
idx = IDX_Tag;
break;
case SPA_PARAM_EnumFormat:
idx = IDX_EnumFormat;
break;
default:
continue;
}
@ -1240,6 +1249,11 @@ static void follower_convert_port_info(void *data,
spa_log_debug(this->log, "tag: %d (%s)", res,
spa_strerror(res));
}
if (idx == IDX_EnumFormat) {
spa_log_info(this->log, "new EnumFormat from converter");
/* we will renegotiate when restarting */
this->recheck_format = true;
}
spa_log_debug(this->log, "param %d changed", info->params[i].id);
}
}
@ -1438,7 +1452,7 @@ static void follower_port_info(void *data,
spa_strerror(res));
}
if (idx == IDX_EnumFormat) {
spa_log_debug(this->log, "new formats");
spa_log_debug(this->log, "new EnumFormat from follower");
/* we will renegotiate when restarting */
this->recheck_format = true;
}
@ -2046,11 +2060,12 @@ static int do_auto_port_config(struct impl *this, const char *str)
return -ENOENT;
if (format.media_subtype == SPA_MEDIA_SUBTYPE_raw) {
uint32_t n_pos = SPA_MIN(SPA_N_ELEMENTS(format.info.raw.position), format.info.raw.channels);
if (position == POSITION_AUX) {
for (i = 0; i < format.info.raw.channels; i++)
for (i = 0; i < n_pos; i++)
format.info.raw.position[i] = SPA_AUDIO_CHANNEL_START_Aux + i;
} else if (position == POSITION_UNKNOWN) {
for (i = 0; i < format.info.raw.channels; i++)
for (i = 0; i < n_pos; i++)
format.info.raw.position[i] = SPA_AUDIO_CHANNEL_UNKNOWN;
}
}

View file

@ -47,10 +47,11 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.audioconvert");
#define DEFAULT_RATE 48000
#define DEFAULT_CHANNELS 2
#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS
#define MAX_ALIGN FMT_OPS_MAX_ALIGN
#define MAX_BUFFERS 32
#define MAX_DATAS SPA_AUDIO_MAX_CHANNELS
#define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1)
#define MAX_DATAS MAX_CHANNELS
#define MAX_PORTS (MAX_CHANNELS+1)
#define MAX_STAGES 64
#define MAX_GRAPH 9 /* 8 active + 1 replacement slot */
@ -62,7 +63,7 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.audioconvert");
struct volumes {
bool mute;
uint32_t n_volumes;
float volumes[SPA_AUDIO_MAX_CHANNELS];
float volumes[MAX_CHANNELS];
};
static void init_volumes(struct volumes *vol)
@ -70,7 +71,7 @@ static void init_volumes(struct volumes *vol)
uint32_t i;
vol->mute = DEFAULT_MUTE;
vol->n_volumes = 0;
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++)
for (i = 0; i < MAX_CHANNELS; i++)
vol->volumes[i] = DEFAULT_VOLUME;
}
@ -91,7 +92,7 @@ struct props {
float max_volume;
float prev_volume;
uint32_t n_channels;
uint32_t channel_map[SPA_AUDIO_MAX_CHANNELS];
uint32_t channel_map[MAX_CHANNELS];
struct volumes channel;
struct volumes soft;
struct volumes monitor;
@ -99,6 +100,7 @@ struct props {
unsigned int mix_disabled:1;
unsigned int resample_disabled:1;
unsigned int resample_quality;
struct resample_config resample_config;
double rate;
char wav_path[512];
unsigned int lock_volumes:1;
@ -112,7 +114,7 @@ static void props_reset(struct props *props)
props->min_volume = DEFAULT_MIN_VOLUME;
props->max_volume = DEFAULT_MAX_VOLUME;
props->n_channels = 0;
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++)
for (i = 0; i < MAX_CHANNELS; i++)
props->channel_map[i] = SPA_AUDIO_CHANNEL_UNKNOWN;
init_volumes(&props->channel);
init_volumes(&props->soft);
@ -121,6 +123,7 @@ static void props_reset(struct props *props)
props->mix_disabled = false;
props->resample_disabled = false;
props->resample_quality = RESAMPLE_DEFAULT_QUALITY;
spa_zero(props->resample_config);
props->rate = 1.0;
spa_zero(props->wav_path);
props->lock_volumes = false;
@ -241,9 +244,9 @@ struct filter_graph {
struct spa_filter_graph *graph;
struct spa_hook listener;
uint32_t n_inputs;
uint32_t inputs_position[SPA_AUDIO_MAX_CHANNELS];
uint32_t inputs_position[MAX_CHANNELS];
uint32_t n_outputs;
uint32_t outputs_position[SPA_AUDIO_MAX_CHANNELS];
uint32_t outputs_position[MAX_CHANNELS];
uint32_t latency;
bool removing;
bool setup;
@ -281,6 +284,7 @@ struct impl {
struct props props;
struct spa_io_clock *io_clock;
struct spa_io_position *io_position;
struct spa_io_rate_match *io_rate_match;
@ -423,7 +427,6 @@ static int init_port(struct impl *this, enum spa_direction direction, uint32_t p
uint32_t position, bool is_dsp, bool is_monitor, bool is_control)
{
struct port *port = GET_PORT(this, direction, port_id);
const char *name;
spa_assert(port_id < MAX_PORTS);
@ -438,8 +441,7 @@ static int init_port(struct impl *this, enum spa_direction direction, uint32_t p
port->latency[SPA_DIRECTION_INPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_INPUT);
port->latency[SPA_DIRECTION_OUTPUT] = SPA_LATENCY_INFO(SPA_DIRECTION_OUTPUT);
name = spa_debug_type_find_short_name(spa_type_audio_channel, position);
snprintf(port->position, sizeof(port->position), "%s", name ? name : "UNK");
spa_type_audio_channel_make_short_name(position, port->position, sizeof(port->position), "UNK");
port->info = SPA_PORT_INFO_INIT();
port->info.change_mask = port->info_all = SPA_PORT_CHANGE_MASK_FLAGS |
@ -1006,12 +1008,43 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
spa_log_debug(this->log, "%p: io %d %p/%zd", this, id, data, size);
switch (id) {
case SPA_IO_Position:
this->io_position = data;
case SPA_IO_Clock:
this->io_clock = data;
break;
case SPA_IO_Position:
{
struct port *p;
uint32_t i;
this->io_position = data;
if (this->io_position && this->io_clock &&
this->io_position->clock.target_rate.denom != this->io_clock->target_rate.denom &&
!this->props.resample_disabled) {
spa_log_debug(this->log, "driver %d changed rate:%u -> %u", this->io_position->clock.id,
this->io_clock->target_rate.denom,
this->io_position->clock.target_rate.denom);
this->io_clock->target_rate = this->io_position->clock.target_rate;
for (i = 0; i < this->dir[SPA_DIRECTION_INPUT].n_ports; i++) {
if ((p = GET_IN_PORT(this, i)) && p->valid && !p->is_dsp && !p->is_control) {
p->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
p->params[IDX_EnumFormat].user++;
}
}
for (i = 0; i < this->dir[SPA_DIRECTION_OUTPUT].n_ports; i++) {
if ((p = GET_OUT_PORT(this, i)) && p->valid && !p->is_dsp && !p->is_control) {
p->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
p->params[IDX_EnumFormat].user++;
}
}
}
break;
}
default:
return -ENOENT;
}
emit_info(this, false);
return 0;
}
@ -1103,11 +1136,11 @@ static void graph_info(void *object, const struct spa_filter_graph_info *info)
else if (spa_streq(k, "n_outputs"))
spa_atou32(s, &g->n_outputs, 0);
else if (spa_streq(k, "inputs.audio.position"))
spa_audio_parse_position(s, strlen(s),
g->inputs_position, &g->n_inputs);
spa_audio_parse_position_n(s, strlen(s), g->inputs_position,
SPA_N_ELEMENTS(g->inputs_position), &g->n_inputs);
else if (spa_streq(k, "outputs.audio.position"))
spa_audio_parse_position(s, strlen(s),
g->outputs_position, &g->n_outputs);
spa_audio_parse_position_n(s, strlen(s), g->outputs_position,
SPA_N_ELEMENTS(g->outputs_position), &g->n_outputs);
else if (spa_streq(k, "latency")) {
double latency;
if (spa_atod(s, &latency))
@ -1311,6 +1344,14 @@ static int do_sync_filter_graph(struct spa_loop *loop, bool async, uint32_t seq,
return 0;
}
static void sync_filter_graph(struct impl *impl)
{
if (impl->data_loop)
spa_loop_locked(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, impl);
else
do_sync_filter_graph(NULL, false, 0, NULL, 0, impl);
}
static void clean_filter_handles(struct impl *impl, bool force)
{
struct filter_graph *g, *t;
@ -1398,7 +1439,7 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order)
if (impl->setup)
res = setup_filter_graphs(impl, false);
spa_loop_locked(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, impl);
sync_filter_graph(impl);
if (impl->in_filter_props == 0)
clean_filter_handles(impl, false);
@ -1447,6 +1488,16 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
this->props.resample_quality = atoi(s);
else if (spa_streq(k, "resample.disable"))
this->props.resample_disabled = spa_atob(s);
else if (spa_streq(k, "resample.window"))
this->props.resample_config.window = resample_window_from_label(s);
else if (spa_streq(k, "resample.cutoff"))
spa_atod(s, &this->props.resample_config.cutoff);
else if (spa_streq(k, "resample.n-taps"))
spa_atou32(s, &this->props.resample_config.n_taps, 0);
else if (spa_strstartswith(k, "resample.param.")) {
uint32_t idx = resample_param_from_label(k+strlen("resample.param."));
spa_atod(s, &this->props.resample_config.params[idx]);
}
else if (spa_streq(k, "dither.noise"))
spa_atou32(s, &this->dir[1].conv.noise_bits, 0);
else if (spa_streq(k, "dither.method"))
@ -1457,6 +1508,10 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
}
else if (spa_streq(k, "channelmix.lock-volumes"))
this->props.lock_volumes = spa_atob(s);
else if (spa_streq(k, "audioconvert.filter-graph.disable")) {
if (!*disable_filter)
*disable_filter = spa_atob(s);
}
else if (spa_strstartswith(k, "audioconvert.filter-graph.")) {
int order = atoi(k + strlen("audioconvert.filter-graph."));
if ((res = load_filter_graph(this, s, order)) < 0) {
@ -1464,10 +1519,6 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
order, spa_strerror(res));
}
}
else if (spa_streq(k, "audioconvert.filter-graph.disable")) {
if (!*disable_filter)
*disable_filter = spa_atob(s);
}
else
return 0;
return 1;
@ -1539,8 +1590,6 @@ static int get_ramp_samples(struct impl *this, struct volume_ramp_params *vrp)
samples = (vrp->volume_ramp_time * vrp->rate) / 1000;
spa_log_info(this->log, "volume ramp samples calculated from time is %d", samples);
}
if (!samples)
samples = -1;
return samples;
}
@ -1551,12 +1600,10 @@ static int get_ramp_step_samples(struct impl *this, struct volume_ramp_params *v
if (vrp->volume_ramp_step_samples)
samples = vrp->volume_ramp_step_samples;
else if (vrp->volume_ramp_step_time) {
/* convert the step time which is in nano seconds to seconds */
samples = (vrp->volume_ramp_step_time/1000) * (vrp->rate/1000);
/* convert the step time which is in nano seconds to seconds, round up */
samples = SPA_MAX(1u, vrp->volume_ramp_step_time/1000) * (vrp->rate/1000);
spa_log_debug(this->log, "volume ramp step samples calculated from time is %d", samples);
}
if (!samples)
samples = -1;
return samples;
}
@ -1569,76 +1616,52 @@ static float get_volume_at_scale(struct volume_ramp_params *vrp, float value)
return 0.0;
}
static struct spa_pod *generate_ramp_up_seq(struct impl *this, struct volume_ramp_params *vrp,
static struct spa_pod *generate_ramp_seq(struct impl *this, struct volume_ramp_params *vrp,
void *buffer, size_t size)
{
struct spa_pod_dynamic_builder b;
struct spa_pod_frame f[1];
float start = vrp->start, end = vrp->end, volume_accum = start;
int ramp_samples = get_ramp_samples(this, vrp);
int ramp_step_samples = get_ramp_step_samples(this, vrp);
float volume_step = ((end - start) / (ramp_samples / ramp_step_samples));
uint32_t volume_offs = 0;
float start = vrp->start, end = vrp->end;
int samples = get_ramp_samples(this, vrp);
int step = get_ramp_step_samples(this, vrp);
int offs = 0;
if (samples < 0 || step < 0 || (samples > 0 && step == 0))
return NULL;
spa_pod_dynamic_builder_init(&b, buffer, size, 4096);
spa_pod_builder_push_sequence(&b.b, &f[0], 0);
spa_log_info(this->log, "generating ramp up sequence from %f to %f with a"
" step value %f at scale %d", start, end, volume_step, vrp->scale);
do {
float vas = get_volume_at_scale(vrp, volume_accum);
spa_log_trace(this->log, "volume accum %f", vas);
spa_pod_builder_control(&b.b, volume_offs, SPA_CONTROL_Properties);
spa_pod_builder_add_object(&b.b,
SPA_TYPE_OBJECT_Props, 0,
SPA_PROP_volume, SPA_POD_Float(vas));
volume_accum += volume_step;
volume_offs += ramp_step_samples;
} while (volume_accum < end);
return spa_pod_builder_pop(&b.b, &f[0]);
}
spa_log_info(this->log, "generating ramp sequence from %f to %f with "
"step %d/%d at scale %d", start, end, step, samples, vrp->scale);
static struct spa_pod *generate_ramp_down_seq(struct impl *this, struct volume_ramp_params *vrp,
void *buffer, size_t size)
{
struct spa_pod_dynamic_builder b;
struct spa_pod_frame f[1];
int ramp_samples = get_ramp_samples(this, vrp);
int ramp_step_samples = get_ramp_step_samples(this, vrp);
float start = vrp->start, end = vrp->end, volume_accum = start;
float volume_step = ((start - end) / (ramp_samples / ramp_step_samples));
uint32_t volume_offs = 0;
while (1) {
float pos = (samples == 0) ? end :
SPA_CLAMP(start + (end - start) * offs / samples,
SPA_MIN(start, end), SPA_MAX(start, end));
float vas = get_volume_at_scale(vrp, pos);
spa_pod_dynamic_builder_init(&b, buffer, size, 4096);
spa_pod_builder_push_sequence(&b.b, &f[0], 0);
spa_log_info(this->log, "generating ramp down sequence from %f to %f with a"
" step value %f at scale %d", start, end, volume_step, vrp->scale);
do {
float vas = get_volume_at_scale(vrp, volume_accum);
spa_log_trace(this->log, "volume accum %f", vas);
spa_pod_builder_control(&b.b, volume_offs, SPA_CONTROL_Properties);
spa_log_trace(this->log, "volume %d accum %f", offs, vas);
spa_pod_builder_control(&b.b, offs, SPA_CONTROL_Properties);
spa_pod_builder_add_object(&b.b,
SPA_TYPE_OBJECT_Props, 0,
SPA_PROP_volume, SPA_POD_Float(vas));
volume_accum -= volume_step;
volume_offs += ramp_step_samples;
} while (volume_accum > end);
if (offs >= samples)
break;
offs = SPA_MIN(samples, offs + step);
}
return spa_pod_builder_pop(&b.b, &f[0]);
}
static void generate_volume_ramp(struct impl *this, struct volume_ramp_params *vrp,
void *buffer, size_t size)
{
void *sequence = NULL;
if (vrp->start == vrp->end)
spa_log_error(this->log, "no change in volume, cannot ramp volume");
else if (vrp->end > vrp->start)
sequence = generate_ramp_up_seq(this, vrp, buffer, size);
else
sequence = generate_ramp_down_seq(this, vrp, buffer, size);
void *sequence;
sequence = generate_ramp_seq(this, vrp, buffer, size);
if (!sequence)
spa_log_error(this->log, "unable to generate sequence");
@ -1748,7 +1771,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
case SPA_PROP_channelVolumes:
if (!p->lock_volumes &&
(n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float,
p->channel.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) {
p->channel.volumes, SPA_N_ELEMENTS(p->channel.volumes))) > 0) {
have_channel_volume = true;
p->channel.n_volumes = n;
changed++;
@ -1756,7 +1779,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
break;
case SPA_PROP_channelMap:
if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Id,
p->channel_map, SPA_AUDIO_MAX_CHANNELS)) > 0) {
p->channel_map, SPA_N_ELEMENTS(p->channel_map))) > 0) {
p->n_channels = n;
changed++;
}
@ -1771,7 +1794,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
case SPA_PROP_softVolumes:
if (!p->lock_volumes &&
(n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float,
p->soft.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) {
p->soft.volumes, SPA_N_ELEMENTS(p->soft.volumes))) > 0) {
have_soft_volume = true;
p->soft.n_volumes = n;
changed++;
@ -1783,7 +1806,7 @@ static int apply_props(struct impl *this, const struct spa_pod *param)
break;
case SPA_PROP_monitorVolumes:
if ((n = spa_pod_copy_array(&prop->value, SPA_TYPE_Float,
p->monitor.volumes, SPA_AUDIO_MAX_CHANNELS)) > 0) {
p->monitor.volumes, SPA_N_ELEMENTS(p->monitor.volumes))) > 0) {
p->monitor.n_volumes = n;
changed++;
}
@ -1895,10 +1918,11 @@ static int reconfigure_mode(struct impl *this, enum spa_param_port_config_mode m
this->dir[SPA_DIRECTION_OUTPUT].n_ports = dir->n_ports + 1;
for (i = 0; i < dir->n_ports; i++) {
init_port(this, direction, i, info->info.raw.position[i], true, false, false);
uint32_t pos = info->info.raw.position[i];
init_port(this, direction, i, pos, true, false, false);
if (this->monitor && direction == SPA_DIRECTION_INPUT)
init_port(this, SPA_DIRECTION_OUTPUT, i+1,
info->info.raw.position[i], true, true, false);
pos, true, true, false);
}
break;
}
@ -1966,7 +1990,7 @@ static int node_set_param_port_config(struct impl *this, uint32_t flags,
return -EINVAL;
if (info.info.raw.channels == 0 ||
info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS)
info.info.raw.channels > MAX_CHANNELS)
return -EINVAL;
infop = &info;
@ -2056,22 +2080,24 @@ static int setup_in_convert(struct impl *this)
dst_info.info.raw.rate);
qsort(dst_info.info.raw.position, dst_info.info.raw.channels,
sizeof(uint32_t), int32_cmp);
sizeof(uint32_t), int32_cmp);
for (i = 0; i < src_info.info.raw.channels; i++) {
for (j = 0; j < dst_info.info.raw.channels; j++) {
if (src_info.info.raw.position[i] !=
dst_info.info.raw.position[j])
uint32_t pi, pj;
char b1[8], b2[8];
pi = src_info.info.raw.position[i];
pj = dst_info.info.raw.position[j];
if (pi != pj)
continue;
in->remap[i] = j;
if (i != j)
remap = true;
spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this,
i, in->remap[i], j,
spa_debug_type_find_short_name(spa_type_audio_channel,
src_info.info.raw.position[i]),
spa_debug_type_find_short_name(spa_type_audio_channel,
dst_info.info.raw.position[j]));
spa_type_audio_channel_make_short_name(pi, b1, 8, "UNK"),
spa_type_audio_channel_make_short_name(pj, b2, 8, "UNK"));
dst_info.info.raw.position[j] = -1;
break;
}
@ -2120,9 +2146,10 @@ static int remap_volumes(struct impl *this, const struct spa_audio_info *info)
for (i = 0; i < p->n_channels; i++) {
for (j = i; j < target; j++) {
uint32_t pj = info->info.raw.position[j];
spa_log_debug(this->log, "%d %d: %d <-> %d", i, j,
p->channel_map[i], info->info.raw.position[j]);
if (p->channel_map[i] != info->info.raw.position[j])
p->channel_map[i], pj);
if (p->channel_map[i] != pj)
continue;
if (i != j) {
SPA_SWAP(p->channel_map[i], p->channel_map[j]);
@ -2153,7 +2180,7 @@ static void set_volume(struct impl *this)
{
struct volumes *vol;
uint32_t i;
float volumes[SPA_AUDIO_MAX_CHANNELS];
float volumes[MAX_CHANNELS];
struct dir *dir = &this->dir[this->direction];
spa_log_debug(this->log, "%p set volume %f have_format:%d", this, this->props.volume, dir->have_format);
@ -2184,10 +2211,11 @@ static void set_volume(struct impl *this)
static char *format_position(char *str, size_t len, uint32_t channels, uint32_t *position)
{
uint32_t i, idx = 0;
char buf[8];
for (i = 0; i < channels; i++)
idx += snprintf(str + idx, len - idx, "%s%s", i == 0 ? "" : " ",
spa_debug_type_find_short_name(spa_type_audio_channel,
position[i]));
spa_type_audio_channel_make_short_name(position[i],
buf, sizeof(buf), "UNK"));
return str;
}
@ -2281,6 +2309,7 @@ static int setup_resample(struct impl *this)
this->resample.o_rate = out->format.info.raw.rate;
this->resample.log = this->log;
this->resample.quality = this->props.resample_quality;
this->resample.config = this->props.resample_config;
this->resample.cpu_flags = this->cpu_flags;
this->rate_adjust = this->props.rate != 1.0;
@ -2344,12 +2373,16 @@ static int setup_out_convert(struct impl *this)
dst_info.info.raw.rate);
qsort(src_info.info.raw.position, src_info.info.raw.channels,
sizeof(uint32_t), int32_cmp);
sizeof(uint32_t), int32_cmp);
for (i = 0; i < src_info.info.raw.channels; i++) {
for (j = 0; j < dst_info.info.raw.channels; j++) {
if (src_info.info.raw.position[i] !=
dst_info.info.raw.position[j])
uint32_t pi, pj;
char b1[8], b2[8];
pi = src_info.info.raw.position[i];
pj = dst_info.info.raw.position[j];
if (pi != pj)
continue;
out->remap[i] = j;
if (i != j)
@ -2357,10 +2390,9 @@ static int setup_out_convert(struct impl *this)
spa_log_debug(this->log, "%p: channel %d (%d) -> %d (%s -> %s)", this,
i, out->remap[i], j,
spa_debug_type_find_short_name(spa_type_audio_channel,
src_info.info.raw.position[i]),
spa_debug_type_find_short_name(spa_type_audio_channel,
dst_info.info.raw.position[j]));
spa_type_audio_channel_make_short_name(pi, b1, 8, "UNK"),
spa_type_audio_channel_make_short_name(pj, b2, 8, "UNK"));
dst_info.info.raw.position[j] = -1;
break;
}
@ -2521,6 +2553,8 @@ static int setup_convert(struct impl *this)
this->setup = true;
this->recalc = true;
sync_filter_graph(this);
return 0;
}
@ -2537,6 +2571,7 @@ static void reset_node(struct impl *this)
resample_reset(&this->resample);
this->in_offset = 0;
this->out_offset = 0;
this->setup = false;
}
static int impl_node_send_command(void *object, const struct spa_command *command)
@ -2557,7 +2592,6 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
break;
case SPA_NODE_COMMAND_Suspend:
reset_node(this);
this->setup = false;
SPA_FALLTHROUGH;
case SPA_NODE_COMMAND_Pause:
this->started = false;
@ -2671,7 +2705,7 @@ static int port_param_enum_formats(struct impl *impl, struct port *port, uint32_
}
spa_pod_builder_add(b,
SPA_FORMAT_AUDIO_channels, SPA_POD_CHOICE_RANGE_Int(
DEFAULT_CHANNELS, 1, SPA_AUDIO_MAX_CHANNELS),
DEFAULT_CHANNELS, 1, MAX_CHANNELS),
0);
*param = spa_pod_builder_pop(b, &f[0]);
}
@ -3064,7 +3098,7 @@ static int port_set_format(void *object,
if (info.info.raw.format == 0 ||
(!this->props.resample_disabled && info.info.raw.rate == 0) ||
info.info.raw.channels == 0 ||
info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS) {
info.info.raw.channels > MAX_CHANNELS) {
spa_log_error(this->log, "invalid format:%d rate:%d channels:%d",
info.info.raw.format, info.info.raw.rate,
info.info.raw.channels);
@ -4295,9 +4329,18 @@ impl_init(const struct spa_handle_factory *factory,
this->direction = SPA_DIRECTION_INPUT;
}
else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) {
if (s != NULL)
spa_audio_parse_position(s, strlen(s), this->props.channel_map,
&this->props.n_channels);
if (s == NULL)
continue;
spa_audio_parse_position_n(s, strlen(s),
this->props.channel_map, SPA_N_ELEMENTS(this->props.channel_map),
&this->props.n_channels);
}
else if (spa_streq(k, SPA_KEY_AUDIO_LAYOUT)) {
if (s == NULL)
continue;
spa_audio_parse_layout(s,
this->props.channel_map, SPA_N_ELEMENTS(this->props.channel_map),
&this->props.n_channels);
}
else if (spa_streq(k, SPA_KEY_PORT_IGNORE_LATENCY))
this->port_ignore_latency = spa_atob(s);

View file

@ -142,29 +142,29 @@ static uint32_t mask_to_ch(struct channelmix *mix, uint64_t mask)
}
static void distribute_mix(struct channelmix *mix,
float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS],
float matrix[MAX_CHANNELS][MAX_CHANNELS],
uint64_t mask)
{
uint32_t i, ch = mask_to_ch(mix, mask);
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++)
for (i = 0; i < MAX_CHANNELS; i++)
matrix[i][ch]= 1.0f;
}
static void average_mix(struct channelmix *mix,
float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS],
float matrix[MAX_CHANNELS][MAX_CHANNELS],
uint64_t mask)
{
uint32_t i, ch = mask_to_ch(mix, mask);
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++)
for (i = 0; i < MAX_CHANNELS; i++)
matrix[ch][i]= 1.0f;
}
static void pair_mix(float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS])
static void pair_mix(float matrix[MAX_CHANNELS][MAX_CHANNELS])
{
uint32_t i;
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++)
for (i = 0; i < MAX_CHANNELS; i++)
matrix[i][i]= 1.0f;
}
static bool match_mix(struct channelmix *mix,
float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS],
float matrix[MAX_CHANNELS][MAX_CHANNELS],
uint64_t src_mask, uint64_t dst_mask)
{
bool matched = false;
@ -181,7 +181,7 @@ static bool match_mix(struct channelmix *mix,
static int make_matrix(struct channelmix *mix)
{
float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS] = {{ 0.0f }};
float matrix[MAX_CHANNELS][MAX_CHANNELS] = {{ 0.0f }};
uint64_t src_mask = mix->src_mask, src_paired;
uint64_t dst_mask = mix->dst_mask, dst_paired;
uint32_t src_chan = mix->src_chan;
@ -293,7 +293,7 @@ static int make_matrix(struct channelmix *mix)
keep &= ~STEREO;
} else if (dst_mask & _MASK(MONO)){
spa_log_info(mix->log, "assign FC to MONO (%f)", 1.0f);
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++)
for (i = 0; i < MAX_CHANNELS; i++)
matrix[i][_CH(FC)]= 1.0f;
normalize = true;
} else {
@ -313,7 +313,7 @@ static int make_matrix(struct channelmix *mix)
keep &= ~FRONT;
} else if ((dst_mask & _MASK(MONO))){
spa_log_info(mix->log, "assign STEREO to MONO (%f)", 1.0f);
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) {
for (i = 0; i < MAX_CHANNELS; i++) {
matrix[i][_CH(FL)]= 1.0f;
matrix[i][_CH(FR)]= 1.0f;
}
@ -352,7 +352,7 @@ static int make_matrix(struct channelmix *mix)
_MATRIX(FC,RC) += slev * SQRT1_2;
} else if (dst_mask & _MASK(MONO)){
spa_log_info(mix->log, "assign RC to MONO (%f)", 1.0f);
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++)
for (i = 0; i < MAX_CHANNELS; i++)
matrix[i][_CH(RC)]= 1.0f;
normalize = true;
} else {
@ -398,7 +398,7 @@ static int make_matrix(struct channelmix *mix)
_MATRIX(FC,RR)+= slev * SQRT1_2;
} else if (dst_mask & _MASK(MONO)){
spa_log_info(mix->log, "assign RL+RR to MONO (%f)", 1.0f);
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) {
for (i = 0; i < MAX_CHANNELS; i++) {
matrix[i][_CH(RL)]= 1.0f;
matrix[i][_CH(RR)]= 1.0f;
}
@ -450,7 +450,7 @@ static int make_matrix(struct channelmix *mix)
_MATRIX(FC,SR) += slev * SQRT1_2;
} else if (dst_mask & _MASK(MONO)){
spa_log_info(mix->log, "assign SL+SR to MONO (%f)", 1.0f);
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) {
for (i = 0; i < MAX_CHANNELS; i++) {
matrix[i][_CH(SL)]= 1.0f;
matrix[i][_CH(SR)]= 1.0f;
}
@ -471,7 +471,7 @@ static int make_matrix(struct channelmix *mix)
_MATRIX(FC,FRC)+= SQRT1_2;
} else if (dst_mask & _MASK(MONO)){
spa_log_info(mix->log, "assign FLC+FRC to MONO (%f)", 1.0f);
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) {
for (i = 0; i < MAX_CHANNELS; i++) {
matrix[i][_CH(FLC)]= 1.0f;
matrix[i][_CH(FRC)]= 1.0f;
}
@ -492,7 +492,7 @@ static int make_matrix(struct channelmix *mix)
_MATRIX(FR,LFE) += llev * SQRT1_2;
} else if ((dst_mask & _MASK(MONO))){
spa_log_info(mix->log, "assign LFE to MONO (%f)", 1.0f);
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++)
for (i = 0; i < MAX_CHANNELS; i++)
matrix[i][_CH(LFE)]= 1.0f;
normalize = true;
} else {
@ -690,7 +690,7 @@ done:
static void impl_channelmix_set_volume(struct channelmix *mix, float volume, bool mute,
uint32_t n_channel_volumes, float *channel_volumes)
{
float volumes[SPA_AUDIO_MAX_CHANNELS];
float volumes[MAX_CHANNELS];
float vol = mute ? 0.0f : volume, t;
uint32_t i, j;
uint32_t src_chan = mix->src_chan;
@ -760,8 +760,8 @@ int channelmix_init(struct channelmix *mix)
{
const struct channelmix_info *info;
if (mix->src_chan > SPA_AUDIO_MAX_CHANNELS ||
mix->dst_chan > SPA_AUDIO_MAX_CHANNELS)
if (mix->src_chan > MAX_CHANNELS ||
mix->dst_chan > MAX_CHANNELS)
return -EINVAL;
info = find_channelmix_info(mix->src_chan, mix->src_mask, mix->dst_chan, mix->dst_mask,

View file

@ -24,6 +24,7 @@
#define BUFFER_SIZE 4096
#define MAX_TAPS 255u
#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS
#define CHANNELMIX_OPS_MAX_ALIGN 16
@ -50,8 +51,8 @@ struct channelmix {
#define CHANNELMIX_FLAG_EQUAL (1<<2) /**< all values are equal */
#define CHANNELMIX_FLAG_COPY (1<<3) /**< 1 on diagonal, can be nxm */
uint32_t flags;
float matrix_orig[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS];
float matrix[SPA_AUDIO_MAX_CHANNELS][SPA_AUDIO_MAX_CHANNELS];
float matrix_orig[MAX_CHANNELS][MAX_CHANNELS];
float matrix[MAX_CHANNELS][MAX_CHANNELS];
float freq; /* sample frequency */
float lfe_cutoff; /* in Hz, 0 is disabled */
@ -59,7 +60,7 @@ struct channelmix {
float rear_delay; /* in ms, 0 is disabled */
float widen; /* stereo widen. 0 is disabled */
uint32_t hilbert_taps; /* to phase shift, 0 disabled */
struct lr4 lr4[SPA_AUDIO_MAX_CHANNELS];
struct lr4 lr4[MAX_CHANNELS];
float buffer_mem[2 * BUFFER_SIZE*2 + CHANNELMIX_OPS_MAX_ALIGN/4];
float *buffer[2];

View file

@ -0,0 +1,147 @@
/* Copyright(C) 1996 Takuya OOURA
You may use, copy, modify this code for any purpose and
without fee.
Package home: http://www.kurims.kyoto-u.ac.jp/~ooura/bessel.html
*/
/* Bessel I_0(x) function in double precision */
#include <math.h>
static double
dbesi0 (double x)
{
int k;
double w, t, y;
static double a[65] = {
8.5246820682016865877e-11, 2.5966600546497407288e-9,
7.9689994568640180274e-8, 1.9906710409667748239e-6,
4.0312469446528002532e-5, 6.4499871606224265421e-4,
0.0079012345761930579108, 0.071111111109207045212,
0.444444444444724909, 1.7777777777777532045,
4.0000000000000011182, 3.99999999999999998,
1.0000000000000000001,
1.1520919130377195927e-10, 2.2287613013610985225e-9,
8.1903951930694585113e-8, 1.9821560631611544984e-6,
4.0335461940910133184e-5, 6.4495330974432203401e-4,
0.0079013012611467520626, 0.071111038160875566622,
0.44444450319062699316, 1.7777777439146450067,
4.0000000132337935071, 3.9999999968569015366,
1.0000000003426703174,
1.5476870780515238488e-10, 1.2685004214732975355e-9,
9.2776861851114223267e-8, 1.9063070109379044378e-6,
4.0698004389917945832e-5, 6.4370447244298070713e-4,
0.0079044749458444976958, 0.071105052411749363882,
0.44445280640924755082, 1.7777694934432109713,
4.0000055808824003386, 3.9999977081165740932,
1.0000004333949319118,
2.0675200625006793075e-10, -6.1689554705125681442e-10,
1.2436765915401571654e-7, 1.5830429403520613423e-6,
4.2947227560776583326e-5, 6.3249861665073441312e-4,
0.0079454472840953930811, 0.070994327785661860575,
0.44467219586283000332, 1.7774588182255374745,
4.0003038986252717972, 3.9998233869142057195,
1.0000472932961288324,
2.7475684794982708655e-10, -3.8991472076521332023e-9,
1.9730170483976049388e-7, 5.9651531561967674521e-7,
5.1992971474748995357e-5, 5.7327338675433770752e-4,
0.0082293143836530412024, 0.069990934858728039037,
0.44726764292723985087, 1.7726685170014087784,
4.0062907863712704432, 3.9952750700487845355,
1.0016354346654179322
};
static double b[70] = {
6.7852367144945531383e-8, 4.6266061382821826854e-7,
6.9703135812354071774e-6, 7.6637663462953234134e-5,
7.9113515222612691636e-4, 0.0073401204731103808981,
0.060677114958668837046, 0.43994941411651569622,
2.7420017097661750609, 14.289661921740860534,
59.820609640320710779, 188.78998681199150629,
399.8731367825601118, 427.56411572180478514,
1.8042097874891098754e-7, 1.2277164312044637357e-6,
1.8484393221474274861e-5, 2.0293995900091309208e-4,
0.0020918539850246207459, 0.019375315654033949297,
0.15985869016767185908, 1.1565260527420641724,
7.1896341224206072113, 37.354773811947484532,
155.80993164266268457, 489.5211371158540918,
1030.9147225169564806, 1093.5883545113746958,
4.8017305613187493564e-7, 3.261317843912380074e-6,
4.9073137508166159639e-5, 5.3806506676487583755e-4,
0.0055387918291051866561, 0.051223717488786549025,
0.42190298621367914765, 3.0463625987357355872,
18.895299447327733204, 97.915189029455461554,
407.13940115493494659, 1274.3088990480582632,
2670.9883037012547506, 2815.7166284662544712,
1.2789926338424623394e-6, 8.6718263067604918916e-6,
1.3041508821299929489e-4, 0.001428224737372747892,
0.014684070635768789378, 0.13561403190404185755,
1.1152592585977393953, 8.0387088559465389038,
49.761318895895479206, 257.2684232313529138,
1066.8543146269566231, 3328.3874581009636362,
6948.8586598121634874, 7288.4893398212481055,
3.409350368197032893e-6, 2.3079025203103376076e-5,
3.4691373283901830239e-4, 0.003794994977222908545,
0.038974209677945602145, 0.3594948380414878371,
2.9522878893539528226, 21.246564609514287056,
131.28727387146173141, 677.38107093296675421,
2802.3724744545046518, 8718.5731420798254081,
18141.348781638832286, 18948.925349296308859
};
static double c[45] = {
2.5568678676452702768e-15, 3.0393953792305924324e-14,
6.3343751991094840009e-13, 1.5041298011833009649e-11,
4.4569436918556541414e-10, 1.746393051427167951e-8,
1.0059224011079852317e-6, 1.0729838945088577089e-4,
0.05150322693642527738,
5.2527963991711562216e-15, 7.202118481421005641e-15,
7.2561421229904797156e-13, 1.482312146673104251e-11,
4.4602670450376245434e-10, 1.7463600061788679671e-8,
1.005922609132234756e-6, 1.0729838937545111487e-4,
0.051503226936437300716,
1.3365917359358069908e-14, -1.2932643065888544835e-13,
1.7450199447905602915e-12, 1.0419051209056979788e-11,
4.58047881980598326e-10, 1.7442405450073548966e-8,
1.0059461453281292278e-6, 1.0729837434500161228e-4,
0.051503226940658446941,
5.3771611477352308649e-14, -1.1396193006413731702e-12,
1.2858641335221653409e-11, -5.9802086004570057703e-11,
7.3666894305929510222e-10, 1.6731837150730356448e-8,
1.0070831435812128922e-6, 1.0729733111203704813e-4,
0.051503227360726294675,
3.7819492084858931093e-14, -4.8600496888588034879e-13,
1.6898350504817224909e-12, 4.5884624327524255865e-11,
1.2521615963377513729e-10, 1.8959658437754727957e-8,
1.0020716710561353622e-6, 1.073037119856927559e-4,
0.05150322383300230775
};
w = fabs (x);
if (w < 8.5) {
t = w * w * 0.0625;
k = 13 * ((int) t);
y = (((((((((((a[k] * t + a[k + 1]) * t +
a[k + 2]) * t + a[k + 3]) * t +
a[k + 4]) * t + a[k + 5]) * t + a[k +
6]) * t + a[k + 7]) * t + a[k + 8]) * t + a[k +
9]) * t + a[k + 10]) * t + a[k + 11]) * t + a[k + 12];
} else if (w < 12.5) {
k = (int) w;
t = w - k;
k = 14 * (k - 8);
y = ((((((((((((b[k] * t + b[k + 1]) * t + b[k + 2]) * t + b[k + 3]) * t +
b[k + 4]) * t + b[k + 5]) * t + b[k +
6]) * t + b[k + 7]) * t + b[k + 8]) * t +
b[k + 9]) * t + b[k + 10]) * t + b[k + 11]) * t + b[k +
12]) * t + b[k + 13];
} else {
t = 60 / w;
k = 9 * ((int) t);
y = ((((((((c[k] * t + c[k + 1]) * t +
c[k + 2]) * t + c[k + 3]) * t + c[k + 4]) * t +
c[k + 5]) * t + c[k + 6]) * t + c[k + 7]) * t +
c[k + 8]) * sqrt (t) * exp (w);
}
return y;
}

View file

@ -284,7 +284,8 @@ conv_s24_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA
}
}
static void
// Non static, referenced by `fmt-ops-sse41.c`.
void
conv_s24_to_f32d_2s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
uint32_t n_channels, uint32_t n_samples)
{

View file

@ -6,7 +6,8 @@
#include <tmmintrin.h>
static void
// Non static, referenced by `fmt-ops-sse41.c`.
void
conv_s24_to_f32d_4s_ssse3(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
uint32_t n_channels, uint32_t n_samples)
{

View file

@ -551,7 +551,7 @@ int convert_init(struct convert *conv)
const struct dither_info *dinfo;
const struct noise_info *ninfo;
const struct clear_info *cinfo;
uint32_t i, conv_flags, data_size[3];
uint32_t i, conv_flags, data_size[4];
/* we generate int32 bits of random values. With this scale
* factor, we bring this in the [-1.0, 1.0] range */
@ -615,15 +615,17 @@ int convert_init(struct convert *conv)
data_size[0] = SPA_ROUND_UP(conv->noise_size * sizeof(float), FMT_OPS_MAX_ALIGN);
data_size[1] = SPA_ROUND_UP(RANDOM_SIZE * sizeof(uint32_t), FMT_OPS_MAX_ALIGN);
data_size[2] = SPA_ROUND_UP(RANDOM_SIZE * sizeof(int32_t), FMT_OPS_MAX_ALIGN);
data_size[3] = SPA_ROUND_UP(conv->n_channels * sizeof(struct shaper), FMT_OPS_MAX_ALIGN);
conv->data = calloc(FMT_OPS_MAX_ALIGN +
data_size[0] + data_size[1] + data_size[2], 1);
data_size[0] + data_size[1] + data_size[2] + data_size[3], 1);
if (conv->data == NULL)
return -errno;
conv->noise = SPA_PTR_ALIGN(conv->data, FMT_OPS_MAX_ALIGN, float);
conv->random = SPA_PTROFF(conv->noise, data_size[0], uint32_t);
conv->prev = SPA_PTROFF(conv->random, data_size[1], int32_t);
conv->shaper = SPA_PTROFF(conv->prev, data_size[2], struct shaper);
for (i = 0; i < RANDOM_SIZE; i++)
conv->random[i] = random();

View file

@ -236,7 +236,7 @@ struct convert {
uint32_t noise_size;
const float *ns;
uint32_t n_ns;
struct shaper shaper[64];
struct shaper *shaper;
void (*update_noise) (struct convert *conv, float *noise, uint32_t n_samples);
void (*process) (struct convert *conv, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src[],

View file

@ -125,7 +125,7 @@ sparesampledumpcoeffs_sources = [
sparesampledumpcoeffs = executable(
'spa-resample-dump-coeffs',
sparesampledumpcoeffs_sources,
c_args : [ cc_flags_native, '-DRESAMPLE_DISABLE_PRECOMP' ],
c_args : [ '-DRESAMPLE_DISABLE_PRECOMP' ],
dependencies : [ spa_dep, mathlib_native ],
install : false,
native : true,

View file

@ -13,72 +13,186 @@
SPA_LOG_TOPIC_DEFINE(resample_log_topic, "spa.resample");
#define INHERIT_PARAM(c,q,p) if ((c)->params[p] == 0.0) (c)->params[p] = (q)->params[p];
struct quality {
uint32_t n_taps;
double cutoff;
double cutoff_up; /* when upsampling */
double cutoff_down; /* for downsampling */
double params[RESAMPLE_MAX_PARAMS];
};
static const struct quality window_qualities[] = {
{ 8, 0.53, },
{ 16, 0.67, },
{ 24, 0.75, },
{ 32, 0.80, },
{ 48, 0.85, }, /* default */
{ 64, 0.88, },
{ 80, 0.895, },
{ 96, 0.910, },
{ 128, 0.936, },
{ 144, 0.945, },
{ 160, 0.950, },
{ 192, 0.960, },
{ 256, 0.970, },
{ 896, 0.990, },
{ 1024, 0.995, },
struct window_info {
uint32_t window;
void (*func) (struct resample *r, double *w, double t, uint32_t n_taps);
uint32_t n_qualities;
const struct quality *qualities;
void (*config) (struct resample *r);
};
struct window_info window_info[];
static const struct quality blackman_qualities[] = {
{ 8, 0.58, 0.58, { 0.16, }},
{ 16, 0.70, 0.70, { 0.20, }},
{ 24, 0.77, 0.77, { 0.16, }},
{ 32, 0.82, 0.82, { 0.16, }},
{ 48, 0.87, 0.87, { 0.16, }}, /* default */
{ 64, 0.895, 0.895, { 0.16, }},
{ 80, 0.910, 0.910, { 0.16, }},
{ 96, 0.925, 0.925, { 0.16, }},
{ 128, 0.942, 0.942, { 0.16, }},
{ 144, 0.950, 0.950, { 0.16, }},
{ 160, 0.958, 0.958, { 0.16, }},
{ 192, 0.966, 0.966, { 0.16, }},
{ 256, 0.975, 0.975, { 0.16, }},
{ 896, 0.988, 0.988, { 0.16, }},
{ 1024, 0.990, 0.990, { 0.16, }},
};
static inline double sinc(double x)
static inline void blackman_window(struct resample *r, double *w, double t, uint32_t n_taps)
{
if (x < 1e-6) return 1.0;
double x, alpha = r->config.params[RESAMPLE_PARAM_BLACKMAN_ALPHA];
uint32_t i, n_taps12 = n_taps/2;
for (i = 0; i < n_taps12; i++, t += 1.0) {
x = 2.0 * M_PI * t / n_taps;
w[i] = (1.0 - alpha) / 2.0 + (1.0 / 2.0) * cos(x) +
(alpha / 2.0) * cos(2.0 * x);
}
}
static inline void blackman_config(struct resample *r)
{
const struct quality *q = &window_info[r->config.window].qualities[r->quality];
INHERIT_PARAM(&r->config, q, RESAMPLE_PARAM_BLACKMAN_ALPHA);
}
static const struct quality exp_qualities[] = {
{ 8, 0.58, 0.58, { 16.97789, }},
{ 16, 0.70, 0.70, { 16.97789, }},
{ 24, 0.77, 0.77, { 16.97789, }},
{ 32, 0.82, 0.82, { 16.97789, }},
{ 48, 0.87, 0.87, { 16.97789, }}, /* default */
{ 64, 0.895, 0.895, { 16.97789, }},
{ 80, 0.910, 0.910, { 16.97789, }},
{ 96, 0.925, 0.925, { 16.97789, }},
{ 128, 0.942, 0.942, { 16.97789, }},
{ 144, 0.950, 0.950, { 16.97789, }},
{ 160, 0.958, 0.958, { 16.97789, }},
{ 192, 0.966, 0.966, { 16.97789, }},
{ 256, 0.975, 0.975, { 16.97789, }},
{ 896, 0.988, 0.988, { 16.97789, }},
{ 1024, 0.990, 0.990, { 16.97789, }},
};
static inline void exp_window(struct resample *r, double *w, double t, uint32_t n_taps)
{
double x, A = r->config.params[RESAMPLE_PARAM_EXP_A];
uint32_t i, n_taps12 = n_taps/2;
for (i = 0; i < n_taps12; i++, t += 1.0) {
x = 2.0 * t / n_taps;
/* doi:10.1109/RME.2008.4595727 with tweak */
w[i] = (exp(A * sqrt(fmax(0.0, 1.0 - x*x))) - 1) / (exp(A) - 1);
}
}
static inline void exp_config(struct resample *r)
{
const struct quality *q = &window_info[r->config.window].qualities[r->quality];
INHERIT_PARAM(&r->config, q, RESAMPLE_PARAM_EXP_A);
}
#include "dbesi0.c"
static const struct quality kaiser_qualities[] = {
{ 8, 0.620000, 0.620000, { 3.553376, 110.000000, 0.888064 }},
{ 16, 0.780000, 0.780000, { 3.553376, 110.000000, 0.444032 }},
{ 24, 0.820000, 0.820000, { 3.904154, 120.000000, 0.325043 }},
{ 32, 0.865000, 0.865000, { 4.254931, 130.000000, 0.265548 }},
{ 48, 0.895000, 0.895000, { 4.254931, 130.000000, 0.177032 }},
{ 64, 0.915000, 0.915000, { 4.254931, 130.000000, 0.132774 }},
{ 80, 0.928000, 0.928000, { 4.254931, 130.000000, 0.106219 }},
{ 96, 0.942000, 0.942000, { 4.254931, 130.000000, 0.088516 }},
{ 128, 0.952000, 0.952000, { 4.254931, 130.000000, 0.066387 }},
{ 160, 0.960000, 0.960000, { 4.254931, 130.000000, 0.053110 }},
{ 192, 0.968000, 0.968000, { 4.254931, 130.000000, 0.044258 }},
{ 256, 0.976000, 0.976000, { 4.605709, 140.000000, 0.035914 }},
{ 512, 0.985000, 0.985000, { 4.781097, 145.000000, 0.018637 }},
{ 768, 0.990000, 0.990000, { 4.956486, 150.000000, 0.012878 }},
{ 1024, 0.993000, 0.993000, { 5.131875, 155.000000, 0.009999 }},
};
static inline void kaiser_window(struct resample *r, double *w, double t, uint32_t n_taps)
{
double x, beta = r->config.params[RESAMPLE_PARAM_KAISER_ALPHA] * M_PI;
double den = dbesi0(beta);
uint32_t i, n_taps12 = n_taps/2;
for (i = 0; i < n_taps12; i++, t += 1.0) {
x = 2.0 * t / n_taps;
w[i] = dbesi0(beta * sqrt(fmax(0.0, 1.0 - x*x))) / den;
}
}
static inline void kaiser_config(struct resample *r)
{
double A, B, dw, tr_bw, alpha;
uint32_t n;
const struct quality *q = &window_info[r->config.window].qualities[r->quality];
if ((A = r->config.params[RESAMPLE_PARAM_KAISER_SB_ATT]) == 0.0)
A = q->params[RESAMPLE_PARAM_KAISER_SB_ATT];
if ((tr_bw = r->config.params[RESAMPLE_PARAM_KAISER_TR_BW]) == 0.0)
tr_bw = q->params[RESAMPLE_PARAM_KAISER_TR_BW];
if ((alpha = r->config.params[RESAMPLE_PARAM_KAISER_ALPHA]) == 0.0) {
/* calculate Beta and alpha */
if (A > 50)
B = 0.1102 * (A - 8.7);
else if (A >= 21)
B = 0.5842 * pow (A - 21, 0.4) + 0.07886 * (A - 21);
else
B = 0.0;
r->config.params[RESAMPLE_PARAM_KAISER_ALPHA] = B / M_PI;
}
if (r->config.n_taps == 0) {
/* calculate transition width in radians */
dw = 2 * M_PI * (tr_bw);
/* order of the filter */
n = (uint32_t)((A - 8.0) / (2.285 * dw));
r->config.n_taps = n + 1;
}
}
struct window_info window_info[] = {
[RESAMPLE_WINDOW_EXP] = { RESAMPLE_WINDOW_EXP, exp_window,
SPA_N_ELEMENTS(exp_qualities), exp_qualities, exp_config },
[RESAMPLE_WINDOW_BLACKMAN] = { RESAMPLE_WINDOW_BLACKMAN, blackman_window,
SPA_N_ELEMENTS(blackman_qualities), blackman_qualities, blackman_config },
[RESAMPLE_WINDOW_KAISER] = { RESAMPLE_WINDOW_KAISER, kaiser_window,
SPA_N_ELEMENTS(kaiser_qualities), kaiser_qualities, kaiser_config },
};
static inline double sinc(double x, double cutoff)
{
if (x < 1e-6) return cutoff;
x *= M_PI;
return sin(x) / x;
return sin(x * cutoff) / x;
}
static inline double window_blackman(double x, double n_taps)
{
double alpha = 0.232, r;
x = 2.0 * M_PI * x / n_taps;
r = (1.0 - alpha) / 2.0 + (1.0 / 2.0) * cos(x) +
(alpha / 2.0) * cos(2.0 * x);
return r;
}
static inline double window_cosh(double x, double n_taps)
{
double r;
double A = 16.97789;
double x2;
x = 2.0 * x / n_taps;
x2 = x * x;
if (x2 >= 1.0)
return 0.0;
/* doi:10.1109/RME.2008.4595727 with tweak */
r = (exp(A * sqrt(1 - x2)) - 1) / (exp(A) - 1);
return r;
}
#define window (1 ? window_cosh : window_blackman)
static int build_filter(float *taps, uint32_t stride, uint32_t n_taps, uint32_t n_phases, double cutoff)
static int build_filter(struct resample *r, float *taps, uint32_t stride, uint32_t n_taps,
uint32_t n_phases, double cutoff)
{
uint32_t i, j, n_taps12 = n_taps/2;
double window[n_taps12+1];
for (i = 0; i <= n_phases; i++) {
double t = (double) i / (double) n_phases;
window_info[r->config.window].func(r, window, t, n_taps);
for (j = 0; j < n_taps12; j++, t += 1.0) {
/* exploit symmetry in filter taps */
taps[(n_phases - i) * stride + n_taps12 + j] =
taps[i * stride + (n_taps12 - j - 1)] = (float)
(cutoff * sinc(t * cutoff) * window(t, n_taps));
(sinc(t, cutoff) * window[j]);
}
}
return 0;
@ -352,11 +466,18 @@ int resample_native_init(struct resample *r)
{
struct native_data *d;
const struct quality *q;
double scale;
uint32_t c, n_taps, n_phases, filter_size, in_rate, out_rate, gcd, filter_stride;
double scale, cutoff;
uint32_t i, n_taps, n_phases, filter_size, in_rate, out_rate, gcd, filter_stride;
uint32_t history_stride, history_size, oversample;
struct resample_config *c = &r->config;
#ifndef RESAMPLE_DISABLE_PRECOMP
struct resample_config def = { 0 };
bool default_config;
r->quality = SPA_CLAMP(r->quality, 0, (int) SPA_N_ELEMENTS(window_qualities) - 1);
default_config = memcmp(c, &def, sizeof(def)) == 0;
#endif
c->window = SPA_CLAMP(c->window, 0u, SPA_N_ELEMENTS(window_info)-1);
r->quality = SPA_CLAMP(r->quality, 0, (int)(window_info[c->window].n_qualities - 1));
r->free = impl_native_free;
r->update_rate = impl_native_update_rate;
r->in_len = impl_native_in_len;
@ -366,17 +487,22 @@ int resample_native_init(struct resample *r)
r->delay = impl_native_delay;
r->phase = impl_native_phase;
q = &window_qualities[r->quality];
window_info[c->window].config(r);
q = &window_info[c->window].qualities[r->quality];
cutoff = r->o_rate < r->i_rate ? q->cutoff_down : q->cutoff_up;
c->cutoff = c->cutoff <= 0.0 ? cutoff: c->cutoff;
n_taps = c->n_taps == 0 ? q->n_taps : c->n_taps;
gcd = calc_gcd(r->i_rate, r->o_rate);
in_rate = r->i_rate / gcd;
out_rate = r->o_rate / gcd;
scale = SPA_MIN(q->cutoff * out_rate / in_rate, q->cutoff);
scale = SPA_MIN(c->cutoff * out_rate / in_rate, c->cutoff);
/* multiple of 8 taps to ease simd optimizations */
n_taps = SPA_ROUND_UP_N((uint32_t)ceil(q->n_taps / scale), 8);
n_taps = SPA_ROUND_UP_N((uint32_t)ceil(n_taps / scale), 8);
n_taps = SPA_MIN(n_taps, 1u << 18);
/* try to get at least 256 phases so that interpolation is
@ -400,7 +526,7 @@ int resample_native_init(struct resample *r)
return -errno;
r->data = d;
d->n_taps = n_taps;
c->n_taps = d->n_taps = n_taps;
d->n_phases = n_phases;
d->in_rate = UINT32_TO_FIXP(in_rate);
d->out_rate = out_rate;
@ -411,25 +537,26 @@ int resample_native_init(struct resample *r)
d->history = SPA_PTROFF(d->hist_mem, history_size, float*);
d->filter_stride = filter_stride / sizeof(float);
d->filter_stride_os = d->filter_stride * oversample;
for (c = 0; c < r->channels; c++)
d->history[c] = SPA_PTROFF(d->hist_mem, c * history_stride, float);
for (i = 0; i < r->channels; i++)
d->history[i] = SPA_PTROFF(d->hist_mem, i * history_stride, float);
#ifndef RESAMPLE_DISABLE_PRECOMP
/* See if we have precomputed coefficients */
for (c = 0; precomp_coeffs[c].filter; c++) {
if (precomp_coeffs[c].in_rate == r->i_rate &&
precomp_coeffs[c].out_rate == r->o_rate &&
precomp_coeffs[c].quality == r->quality)
for (i = 0; precomp_coeffs[i].filter; i++) {
if (default_config &&
precomp_coeffs[i].in_rate == r->i_rate &&
precomp_coeffs[i].out_rate == r->o_rate &&
precomp_coeffs[i].quality == r->quality)
break;
}
if (precomp_coeffs[c].filter) {
spa_log_debug(r->log, "using precomputed filter for %u->%u(%u)",
if (precomp_coeffs[i].filter) {
spa_log_info(r->log, "using precomputed filter for %u->%u(%u)",
r->i_rate, r->o_rate, r->quality);
spa_memcpy(d->filter, precomp_coeffs[c].filter, filter_size);
spa_memcpy(d->filter, precomp_coeffs[i].filter, filter_size);
} else {
#endif
build_filter(d->filter, d->filter_stride, n_taps, n_phases, scale);
build_filter(r, d->filter, d->filter_stride, n_taps, n_phases, scale);
#ifndef RESAMPLE_DISABLE_PRECOMP
}
#endif
@ -440,8 +567,8 @@ int resample_native_init(struct resample *r)
return -ENOTSUP;
}
spa_log_debug(r->log, "native %p: q:%d in:%d out:%d gcd:%d n_taps:%d n_phases:%d features:%08x:%08x",
r, r->quality, r->i_rate, r->o_rate, gcd, n_taps, n_phases,
spa_log_info(r->log, "native %p: c:%f q:%d w:%d in:%d out:%d gcd:%d n_taps:%d n_phases:%d features:%08x:%08x",
r, c->cutoff, r->quality, c->window, r->i_rate, r->o_rate, gcd, n_taps, n_phases,
r->cpu_flags, d->info->cpu_flags);
r->cpu_flags = d->info->cpu_flags;

View file

@ -8,7 +8,30 @@
#include <spa/support/cpu.h>
#include <spa/support/log.h>
#ifndef RESAMPLE_DEFAULT_QUALITY
#define RESAMPLE_DEFAULT_QUALITY 4
#endif
#define RESAMPLE_WINDOW_DEFAULT RESAMPLE_WINDOW_EXP
#define RESAMPLE_MAX_PARAMS 16
struct resample_config {
#define RESAMPLE_WINDOW_EXP 0
#define RESAMPLE_WINDOW_BLACKMAN 1
#define RESAMPLE_WINDOW_KAISER 2
uint32_t window;
double cutoff;
uint32_t n_taps;
#define RESAMPLE_PARAM_EXP_A 0
#define RESAMPLE_PARAM_BLACKMAN_ALPHA 0
#define RESAMPLE_PARAM_KAISER_ALPHA 0
#define RESAMPLE_PARAM_KAISER_SB_ATT 1 /* stopband attenuation */
#define RESAMPLE_PARAM_KAISER_TR_BW 2 /* transition bandwidth */
#define RESAMPLE_PARAM_INVALID (RESAMPLE_MAX_PARAMS-1)
double params[RESAMPLE_MAX_PARAMS];
};
struct resample {
struct spa_log *log;
@ -23,6 +46,8 @@ struct resample {
double rate;
int quality;
struct resample_config config; /* set to all 0 for defaults */
void (*free) (struct resample *r);
void (*update_rate) (struct resample *r, double rate);
uint32_t (*in_len) (struct resample *r, uint32_t out_len);
@ -49,6 +74,56 @@ struct resample {
#define resample_phase(r) (r)->phase(r)
int resample_native_init(struct resample *r);
int resample_native_init_config(struct resample *r, struct resample_config *conf);
int resample_peaks_init(struct resample *r);
static const struct resample_window_info {
uint32_t window;
const char *label;
const char *description;
uint32_t n_params;
} resample_window_info[] = {
[RESAMPLE_WINDOW_EXP] = { RESAMPLE_WINDOW_EXP,
"exp", "Exponential window", 1 },
[RESAMPLE_WINDOW_BLACKMAN] = { RESAMPLE_WINDOW_BLACKMAN,
"blackman", "Blackman window", 1 },
[RESAMPLE_WINDOW_KAISER] = { RESAMPLE_WINDOW_KAISER,
"kaiser", "Kaiser window", 3 },
};
static inline uint32_t resample_window_from_label(const char *label)
{
SPA_FOR_EACH_ELEMENT_VAR(resample_window_info, i) {
if (spa_streq(i->label, label))
return i->window;
}
return RESAMPLE_WINDOW_EXP;
}
static inline const char *resample_window_name(uint32_t idx)
{
return resample_window_info[SPA_CLAMP(idx, 0u, SPA_N_ELEMENTS(resample_window_info)-1)].label;
}
static const struct resample_param_info {
uint32_t window;
uint32_t idx;
const char *label;
} resample_param_info[] = {
{ RESAMPLE_WINDOW_EXP, RESAMPLE_PARAM_EXP_A, "exp.A" },
{ RESAMPLE_WINDOW_BLACKMAN, RESAMPLE_PARAM_BLACKMAN_ALPHA, "blackman.alpha" },
{ RESAMPLE_WINDOW_KAISER, RESAMPLE_PARAM_KAISER_ALPHA, "kaiser.alpha" },
{ RESAMPLE_WINDOW_KAISER, RESAMPLE_PARAM_KAISER_SB_ATT, "kaiser.stopband-attenuation" },
{ RESAMPLE_WINDOW_KAISER, RESAMPLE_PARAM_KAISER_TR_BW, "kaiser.transition-bandwidth" },
};
static inline uint32_t resample_param_from_label(const char *label)
{
SPA_FOR_EACH_ELEMENT_VAR(resample_param_info, i) {
if (spa_streq(i->label, label))
return i->idx;
}
return RESAMPLE_PARAM_INVALID;
}
#endif /* RESAMPLE_H */

View file

@ -29,7 +29,9 @@ struct data {
bool verbose;
int rate;
int format;
uint32_t window;
int quality;
struct resample_config config;
int cpu_flags;
const char *iname;
@ -43,15 +45,19 @@ struct data {
#define STR_FMTS "(s8|s16|s32|f32|f64)"
#define OPTIONS "hvr:f:q:c:"
#define OPTIONS "hvc:r:f:w:q:u:t:p:"
static const struct option long_options[] = {
{ "help", no_argument, NULL, 'h'},
{ "verbose", no_argument, NULL, 'v'},
{ "cpuflags", required_argument, NULL, 'c' },
{ "rate", required_argument, NULL, 'r' },
{ "format", required_argument, NULL, 'f' },
{ "window", required_argument, NULL, 'w' },
{ "quality", required_argument, NULL, 'q' },
{ "cpuflags", required_argument, NULL, 'c' },
{ "cutoff", required_argument, NULL, 'u' },
{ "taps", required_argument, NULL, 't' },
{ "param", required_argument, NULL, 'p' },
{ NULL, 0, NULL, 0 }
};
@ -59,6 +65,7 @@ static const struct option long_options[] = {
static void show_usage(const char *name, bool is_error)
{
FILE *fp;
uint32_t i;
fp = is_error ? stderr : stdout;
@ -66,14 +73,31 @@ static void show_usage(const char *name, bool is_error)
fprintf(fp,
" -h, --help Show this help\n"
" -v --verbose Be verbose\n"
" -c --cpuflags CPU flags (default 0)\n"
"\n");
fprintf(fp,
" -r --rate Output sample rate (default as input)\n"
" -f --format Output sample format %s (default as input)\n"
" -f --format Output sample format %s (default as input)\n\n"
" -w --window Window function (default %s)\n",
STR_FMTS, resample_window_name(RESAMPLE_WINDOW_DEFAULT));
for (i = 0; i < SPA_N_ELEMENTS(resample_window_info); i++) {
fprintf(fp,
" %s: %s\n",
resample_window_info[i].label,
resample_window_info[i].description);
}
fprintf(fp,
" -q --quality Resampler quality (default %u)\n"
" -c --cpuflags CPU flags (default 0)\n"
"\n",
STR_FMTS, DEFAULT_QUALITY);
" -u --cutoff Cutoff frequency [0.0..1.0] (default from quality)\n"
" -t --taps Resampler taps (default from quality)\n"
" -p --param Resampler param <name>=<value> (default from quality)\n",
DEFAULT_QUALITY);
for (i = 0; i < SPA_N_ELEMENTS(resample_param_info); i++) {
fprintf(fp,
" %s\n",
resample_param_info[i].label);
}
fprintf(fp, "\n");
}
static inline const char *
@ -163,6 +187,8 @@ static int open_files(struct data *d)
d->oname, sf_strerror(NULL));
return -EIO;
}
sf_command(d->ofile, SFC_SET_CLIPPING, NULL, 1);
if (d->verbose) {
fprintf(stdout, "input '%s': channels:%d rate:%d format:%s\n",
d->iname, d->iinfo.channels, d->iinfo.samplerate,
@ -207,10 +233,23 @@ static int do_conversion(struct data *d)
r.i_rate = d->iinfo.samplerate;
r.o_rate = d->oinfo.samplerate;
r.quality = d->quality < 0 ? DEFAULT_QUALITY : d->quality;
r.config = d->config;
if ((res = resample_native_init(&r)) < 0) {
fprintf(stderr, "can't init converter: %s\n", spa_strerror(res));
return res;
}
if (d->verbose) {
fprintf(stdout, "window:%s cutoff:%f n_taps:%u\n",
resample_window_name(r.config.window),
r.config.cutoff, r.config.n_taps);
for (i = 0; i < SPA_N_ELEMENTS(resample_param_info); i++) {
if (resample_param_info[i].window != r.config.window)
continue;
fprintf(stdout, " param:%s %f\n",
resample_param_info[i].label,
r.config.params[resample_param_info[i].idx]);
}
}
for (j = 0; j < channels; j++)
src[j] = &in[MAX_SAMPLES * j];
@ -259,9 +298,8 @@ static int do_conversion(struct data *d)
if (pout_len > 0) {
for (k = 0, i = 0; i < pout_len; i++) {
for (j = 0; j < channels; j++) {
for (j = 0; j < channels; j++)
obuf[k++] = out[MAX_SAMPLES * j + i];
}
}
pout_len = sf_writef_float(d->ofile, obuf, pout_len);
@ -320,6 +358,27 @@ int main(int argc, char *argv[])
case 'c':
data.cpu_flags = strtol(optarg, NULL, 0);
break;
case 'u':
data.config.cutoff = strtod(optarg, NULL);
fprintf(stderr, "%f\n", data.config.cutoff);
break;
case 'w':
data.config.window = resample_window_from_label(optarg);
break;
case 'p':
{
char *eq;
if ((eq = strchr(optarg, '=')) != NULL) {
uint32_t idx;
*eq = 0;
idx = resample_param_from_label(optarg);
data.config.params[idx] = atof(eq+1);
}
break;
}
case 't':
data.config.n_taps = atoi(optarg);
break;
default:
fprintf(stderr, "error: unknown option '%c'\n", c);
goto error_usage;

View file

@ -25,7 +25,8 @@ SPA_LOG_IMPL(logger);
extern const struct spa_handle_factory test_source_factory;
#define MAX_PORTS (SPA_AUDIO_MAX_CHANNELS+1)
#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS
#define MAX_PORTS (MAX_CHANNELS+1)
struct context {
struct spa_handle *convert_handle;

View file

@ -790,6 +790,7 @@ static int impl_node_process(void *object)
uint32_t n_buffers, maxsize;
struct buffer **buffers;
struct buffer *outb;
struct spa_data *d;
const void **datas;
uint32_t cycle = this->position->clock.cycle & 1;
@ -856,12 +857,11 @@ static int impl_node_process(void *object)
outport->n_buffers);
return -EPIPE;
}
d = outb->buf.datas;
if (n_buffers == 1) {
if (n_buffers == 1 && SPA_FLAG_IS_SET(d[0].flags, SPA_DATA_FLAG_DYNAMIC)) {
*outb->buffer = *buffers[0]->buffer;
} else {
struct spa_data *d = outb->buf.datas;
*outb->buffer = outb->buf;
maxsize = SPA_MIN(maxsize, d[0].maxsize);

View file

@ -113,6 +113,7 @@ struct impl {
struct props props;
struct spa_io_clock *clock;
struct spa_io_position *position;
bool following;
struct spa_hook_list hooks;
struct spa_callbacks callbacks;
@ -306,6 +307,8 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
default:
return -ENOENT;
}
this->following = this->position && this->clock && this->position->clock.id != this->clock->id;
return 0;
}
@ -422,7 +425,8 @@ static int make_buffer(struct impl *this)
this->sample_count += n_samples;
this->elapsed_time = SAMPLES_TO_TIME(this, this->sample_count);
set_timer(this, true);
if (!this->following)
set_timer(this, true);
io->buffer_id = b->id;
io->status = SPA_STATUS_HAVE_DATA;
@ -475,7 +479,8 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
this->elapsed_time = 0;
this->started = true;
set_timer(this, true);
if (!this->following)
set_timer(this, true);
break;
}
case SPA_NODE_COMMAND_Suspend:
@ -891,7 +896,7 @@ static inline void reuse_buffer(struct impl *this, struct port *port, uint32_t i
b->outstanding = false;
spa_list_append(&port->empty, &b->link);
if (!this->props.live)
if (!this->props.live && !this->following)
set_timer(this, true);
}
@ -966,7 +971,7 @@ static int impl_node_process(void *object)
io->buffer_id = SPA_ID_INVALID;
}
if (!this->props.live)
if (!this->props.live || this->following)
return make_buffer(this);
else
return SPA_STATUS_OK;

View file

@ -40,7 +40,13 @@ static int avb_set_param(struct state *state, const char *k, const char *s)
state->default_format = spa_type_audio_format_from_short_name(s);
fmt_change++;
} else if (spa_streq(k, SPA_KEY_AUDIO_POSITION)) {
spa_audio_parse_position(s, strlen(s), state->default_pos.pos,
spa_audio_parse_position_n(s, strlen(s), state->default_pos.pos,
SPA_N_ELEMENTS(state->default_pos.pos),
&state->default_pos.channels);
fmt_change++;
} else if (spa_streq(k, SPA_KEY_AUDIO_LAYOUT)) {
spa_audio_parse_layout(s, state->default_pos.pos,
SPA_N_ELEMENTS(state->default_pos.pos),
&state->default_pos.channels);
fmt_change++;
} else if (spa_streq(k, SPA_KEY_AUDIO_ALLOWED_RATES)) {
@ -85,11 +91,12 @@ static int position_to_string(struct channel_map *map, char *val, size_t len)
{
uint32_t i, o = 0;
int r;
char pos[8];
o += snprintf(val, len, "[ ");
for (i = 0; i < map->channels; i++) {
r = snprintf(val+o, len-o, "%s%s", i == 0 ? "" : ", ",
spa_debug_type_find_short_name(spa_type_audio_channel,
map->pos[i]));
spa_type_audio_channel_make_short_name(map->pos[i],
pos, sizeof(pos), "UNK"));
if (r < 0 || o + r >= len)
return -ENOSPC;
o += r;

View file

@ -109,6 +109,7 @@ static inline char *format_streamid(char *str, size_t size, const uint64_t strea
return str;
}
#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS
#define MAX_BUFFERS 32
struct buffer {
@ -127,7 +128,7 @@ struct buffer {
struct channel_map {
uint32_t channels;
uint32_t pos[SPA_AUDIO_MAX_CHANNELS];
uint32_t pos[MAX_CHANNELS];
};
struct port {

View file

@ -139,7 +139,8 @@ static int get_valid_aac_bitrate(a2dp_aac_t *conf)
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data)
{
a2dp_aac_t conf;
int i;
@ -200,7 +201,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
a2dp_aac_t conf;
struct spa_pod_frame f[2];
struct spa_pod_choice *choice;
uint32_t position[SPA_AUDIO_MAX_CHANNELS];
uint32_t position[2];
uint32_t i = 0;
if (caps_size < sizeof(conf))

View file

@ -110,7 +110,8 @@ aptx_frequencies[] = {
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data)
{
a2dp_aptx_t conf;
int i;
@ -146,7 +147,8 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags,
static int codec_select_config_ll(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data)
{
a2dp_aptx_ll_ext_t conf = { 0 };
size_t actual_conf_size;
@ -166,7 +168,7 @@ static int codec_select_config_ll(const struct media_codec *codec, uint32_t flag
if (codec->duplex_codec && !conf.base.bidirect_link)
return -ENOTSUP;
if ((res = codec_select_config(codec, flags, caps, caps_size, info, settings, config)) < 0)
if ((res = codec_select_config(codec, flags, caps, caps_size, info, settings, config, NULL)) < 0)
return res;
memcpy(&conf.base.aptx, config, sizeof(conf.base.aptx));
@ -205,7 +207,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
a2dp_aptx_t conf;
struct spa_pod_frame f[2];
struct spa_pod_choice *choice;
uint32_t position[SPA_AUDIO_MAX_CHANNELS];
uint32_t position[2];
uint32_t i = 0;
if (caps_size < sizeof(conf))

View file

@ -61,7 +61,8 @@ duplex_frequencies[] = {
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data)
{
a2dp_faststream_t conf;
int i;
@ -114,7 +115,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
a2dp_faststream_t conf;
struct spa_pod_frame f[2];
struct spa_pod_choice *choice;
uint32_t position[SPA_AUDIO_MAX_CHANNELS];
uint32_t position[2];
uint32_t i = 0;
if (caps_size < sizeof(conf))

View file

@ -83,7 +83,8 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data)
{
a2dp_lc3plus_hr_t conf;
@ -137,8 +138,8 @@ static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t f
int a, b;
/* Order selected configurations by preference */
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1);
res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2);
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1, NULL);
res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2, NULL);
#define PREFER_EXPR(expr) \
do { \
@ -175,7 +176,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
a2dp_lc3plus_hr_t conf;
struct spa_pod_frame f[2];
struct spa_pod_choice *choice;
uint32_t position[SPA_AUDIO_MAX_CHANNELS];
uint32_t position[2];
uint32_t i = 0;
if (caps_size < sizeof(conf))

View file

@ -115,7 +115,8 @@ ldac_channel_modes[] = {
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data)
{
a2dp_ldac_t conf;
int i;
@ -158,7 +159,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
struct spa_pod_frame f[2];
struct spa_pod_choice *choice;
uint32_t i = 0;
uint32_t position[SPA_AUDIO_MAX_CHANNELS];
uint32_t position[2];
if (caps_size < sizeof(conf))
return -EINVAL;

View file

@ -74,7 +74,8 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data)
{
a2dp_opus_g_t conf;
int frequency, duration, channels;
@ -126,8 +127,8 @@ static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t f
int a, b;
/* Order selected configurations by preference */
res1 = codec->select_config(codec, flags, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1);
res2 = codec->select_config(codec, flags, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2);
res1 = codec->select_config(codec, flags, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1, NULL);
res2 = codec->select_config(codec, flags, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2, NULL);
#define PREFER_EXPR(expr) \
do { \
@ -164,7 +165,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
{
a2dp_opus_g_t conf;
struct spa_pod_frame f[1];
uint32_t position[SPA_AUDIO_MAX_CHANNELS];
uint32_t position[2];
int channels;
if (caps_size < sizeof(conf))

View file

@ -58,6 +58,8 @@ static struct spa_log *log;
#define BITRATE_DUPLEX_BIDI 160000
#define MAX_CHANNELS SPA_AUDIO_MAX_CHANNELS
#define OPUS_05_MAX_BYTES (15 * 1024)
struct props {
@ -251,14 +253,8 @@ static const struct surround_encoder_mapping surround_encoders[] = {
static uint32_t bt_channel_from_name(const char *name)
{
size_t i;
enum spa_audio_channel position = SPA_AUDIO_CHANNEL_UNKNOWN;
enum spa_audio_channel position = spa_type_audio_channel_from_short_name(name);
for (i = 0; spa_type_audio_channel[i].name; i++) {
if (spa_streq(name, spa_debug_type_short_name(spa_type_audio_channel[i].name))) {
position = spa_type_audio_channel[i].type;
break;
}
}
for (i = 0; i < SPA_N_ELEMENTS(audio_locations); i++) {
if (position == audio_locations[i].position)
return audio_locations[i].mask;
@ -313,14 +309,14 @@ static void parse_settings(struct props *props, const struct spa_dict *settings)
return;
if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.channels"), &v, 0))
props->channels = SPA_CLAMP(v, 1u, SPA_AUDIO_MAX_CHANNELS);
props->channels = SPA_CLAMP(v, 1u, MAX_CHANNELS);
if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.max-bitrate"), &v, 0))
props->max_bitrate = SPA_MAX(v, (uint32_t)BITRATE_MIN);
if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.coupled-streams"), &v, 0))
props->coupled_streams = SPA_CLAMP(v, 0u, props->channels / 2);
if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.channels"), &v, 0))
props->bidi_channels = SPA_CLAMP(v, 0u, SPA_AUDIO_MAX_CHANNELS);
props->bidi_channels = SPA_CLAMP(v, 0u, MAX_CHANNELS);
if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.max-bitrate"), &v, 0))
props->bidi_max_bitrate = SPA_MAX(v, (uint32_t)BITRATE_MIN);
if (spa_atou32(spa_dict_lookup(settings, "bluez5.a2dp.opus.pro.bidi.coupled-streams"), &v, 0))
@ -503,7 +499,7 @@ static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direc
const uint8_t *permutation = NULL;
size_t i, j;
if (channels > SPA_AUDIO_MAX_CHANNELS)
if (channels > MAX_CHANNELS)
return -EINVAL;
if (2 * coupled_streams > channels)
return -EINVAL;
@ -542,10 +538,9 @@ static int get_mapping(const struct media_codec *codec, const a2dp_opus_05_direc
const struct audio_location loc = audio_locations[i];
if (location & loc.mask) {
if (permutation)
positions[permutation[j++]] = loc.position;
else
positions[j++] = loc.position;
uint32_t idx = permutation ? permutation[j] : j;
positions[idx] = loc.position;
j++;
}
}
for (i = SPA_AUDIO_CHANNEL_START_Aux; j < channels; ++i, ++j)
@ -561,7 +556,7 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
a2dp_opus_05_t a2dp_opus_05 = {
.info = codec->vendor,
.main = {
.channels = SPA_MIN(255u, SPA_AUDIO_MAX_CHANNELS),
.channels = SPA_MIN(255u, MAX_CHANNELS),
.frame_duration = (OPUS_05_FRAME_DURATION_25 |
OPUS_05_FRAME_DURATION_50 |
OPUS_05_FRAME_DURATION_100 |
@ -571,7 +566,7 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
OPUS_05_INIT_BITRATE(0)
},
.bidi = {
.channels = SPA_MIN(255u, SPA_AUDIO_MAX_CHANNELS),
.channels = SPA_MIN(255u, MAX_CHANNELS),
.frame_duration = (OPUS_05_FRAME_DURATION_25 |
OPUS_05_FRAME_DURATION_50 |
OPUS_05_FRAME_DURATION_100 |
@ -595,7 +590,8 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data)
{
struct props props;
a2dp_opus_05_t conf;
@ -704,8 +700,8 @@ static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t f
int a, b;
/* Order selected configurations by preference */
res1 = codec->select_config(codec, flags, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1);
res2 = codec->select_config(codec, flags, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2);
res1 = codec->select_config(codec, flags, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1, NULL);
res2 = codec->select_config(codec, flags, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2, NULL);
#define PREFER_EXPR(expr) \
do { \
@ -771,7 +767,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
a2dp_opus_05_t conf;
a2dp_opus_05_direction_t *dir;
struct spa_pod_frame f[1];
uint32_t position[SPA_AUDIO_MAX_CHANNELS];
uint32_t position[MAX_CHANNELS];
if (caps_size < sizeof(conf))
return -EINVAL;
@ -835,7 +831,8 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags
}
info->info.raw.channels = dir1->channels;
if (get_mapping(codec, dir1, surround_encoder, NULL, NULL, NULL, info->info.raw.position) < 0)
if (get_mapping(codec, dir1, surround_encoder, NULL, NULL, NULL,
info->info.raw.position) < 0)
return -EINVAL;
if (get_mapping(codec, dir2, surround_encoder, NULL, NULL, NULL, NULL) < 0)
return -EINVAL;

View file

@ -135,7 +135,8 @@ sbc_xq_channel_modes[] = {
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data)
{
a2dp_sbc_t conf;
int bitpool, i;
@ -221,8 +222,8 @@ static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t f
bool xq = (spa_streq(codec->name, "sbc_xq"));
/* Order selected configurations by preference */
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1);
res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2);
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, NULL, (uint8_t *)&conf1, NULL);
res2 = codec->select_config(codec, 0, caps2, caps2_size, info , NULL, (uint8_t *)&conf2, NULL);
#define PREFER_EXPR(expr) \
do { \
@ -346,7 +347,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
struct spa_pod_frame f[2];
struct spa_pod_choice *choice;
uint32_t i = 0;
uint32_t position[SPA_AUDIO_MAX_CHANNELS];
uint32_t position[2];
if (caps_size < sizeof(conf))
return -EINVAL;

View file

@ -125,6 +125,7 @@ struct impl {
struct spa_source *ring_timer;
void *upower;
struct spa_bt_telephony *telephony;
bool pts;
};
struct transport_data {
@ -852,7 +853,7 @@ done:
if (cfg)
libusb_free_config_descriptor(cfg);
if (devices)
libusb_free_device_list(devices, 0);
libusb_free_device_list(devices, true);
if (ctx)
libusb_exit(ctx);
return ok;
@ -1010,6 +1011,7 @@ static void process_hfp_hf_indicator(struct rfcomm *rfcomm, unsigned int indicat
switch (indicator) {
case SPA_BT_HFP_HF_INDICATOR_ENHANCED_SAFETY:
rfcomm_send_error(rfcomm, CMEE_AG_FAILURE);
break;
case SPA_BT_HFP_HF_INDICATOR_BATTERY_LEVEL:
// Battery level is reported in range 0-100
@ -1018,12 +1020,15 @@ static void process_hfp_hf_indicator(struct rfcomm *rfcomm, unsigned int indicat
if (value <= 100) {
// TODO: report without Battery Provider (using props)
spa_bt_device_report_battery_level(rfcomm->device, value);
rfcomm_send_reply(rfcomm, "OK");
} else {
spa_log_warn(backend->log, "battery HF indicator %u outside of range [0, 100]: %u", indicator, value);
rfcomm_send_error(rfcomm, CMEE_AG_FAILURE);
}
break;
default:
spa_log_warn(backend->log, "unknown HF indicator:%u value:%u", indicator, value);
rfcomm_send_error(rfcomm, CMEE_AG_FAILURE);
break;
}
}
@ -1307,7 +1312,7 @@ next_indicator:
type = INTERNATIONAL_NUMBER;
else
type = NATIONAL_NUMBER;
rfcomm_send_reply(rfcomm, "+CNUM: ,\"%s\",%u", backend->modem.own_number, type);
rfcomm_send_reply(rfcomm, "+CNUM: ,\"%s\",%u,,4", backend->modem.own_number, type);
}
rfcomm_send_reply(rfcomm, "OK");
} else if (spa_strstartswith(buf, "AT+COPS=")) {
@ -1350,6 +1355,7 @@ next_indicator:
rfcomm_send_reply(rfcomm, "+BIND: (2)");
rfcomm_send_reply(rfcomm, "OK");
} else if (spa_strstartswith(buf, "AT+BIND?")) {
rfcomm_send_reply(rfcomm, "+BIND: 1,0");
rfcomm_send_reply(rfcomm, "+BIND: 2,1");
rfcomm_send_reply(rfcomm, "OK");
} else if (spa_strstartswith(buf, "AT+BIND=")) {
@ -1359,7 +1365,6 @@ next_indicator:
rfcomm_send_reply(rfcomm, "OK");
} else if (sscanf(buf, "AT+BIEV=%u,%u", &indicator, &indicator_value) == 2) {
process_hfp_hf_indicator(rfcomm, indicator, indicator_value);
rfcomm_send_reply(rfcomm, "OK");
} else if (sscanf(buf, "AT+XAPL=%04x-%04x-%*[^,],%u", &xapl_vendor, &xapl_product, &xapl_features) == 3) {
if (xapl_features & SPA_BT_HFP_HF_XAPL_FEATURE_BATTERY_REPORTING) {
/* claim, that we support battery status reports */
@ -1434,6 +1439,15 @@ next_indicator:
rfcomm_send_error(rfcomm, error);
return true;
}
} else if (spa_strstartswith(buf, "AT+BLDN") && backend->pts) {
enum cmee_error error;
/* For PTS tests HFP/AG/OCL/BV-01-C and HFP/AG/OCL/BV-02-C, fake last dial
* number by calling first memory */
if (!mm_do_call(backend->modemmanager, ">1", rfcomm, &error)) {
rfcomm_send_error(rfcomm, error);
return true;
}
} else if (spa_strstartswith(buf, "AT+CHUP")) {
enum cmee_error error;
@ -2759,7 +2773,8 @@ static int sco_acquire_cb(void *data, bool optional)
goto fail;
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
rfcomm_hfp_ag_set_cind(td->rfcomm, true);
if (!td->rfcomm->device->disable_dummy_call)
rfcomm_hfp_ag_set_cind(td->rfcomm, true);
#endif
t->fd = sock;
@ -2813,7 +2828,8 @@ static int sco_release_cb(void *data)
spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_IDLE);
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
rfcomm_hfp_ag_set_cind(td->rfcomm, false);
if (!td->rfcomm->device->disable_dummy_call)
rfcomm_hfp_ag_set_cind(td->rfcomm, false);
#endif
sco_destroy_cb(t);
@ -2926,7 +2942,11 @@ static void sco_listen_event(struct spa_source *source)
/* Find transport for local and remote address */
spa_list_for_each(rfcomm, &backend->rfcomm_list, link) {
if ((rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) &&
/* Audio connection is allowed from both side with legacy peer, i.e. HSP or codec negotion not supported
* (except if PTS workaround has been enabled in which case audio coonection is allowed as for HSP),
* or only from the HFP Audio Gateway. */
if ((((!rfcomm->codec_negotiation_supported || backend->pts) && (rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO)) ||
(rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY)) &&
rfcomm->transport &&
spa_streq(rfcomm->device->address, remote_address) &&
spa_streq(rfcomm->device->adapter->address, local_address)) {
@ -2940,7 +2960,7 @@ static void sco_listen_event(struct spa_source *source)
return;
}
spa_assert(t->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY);
spa_assert(t->profile & SPA_BT_PROFILE_HEADSET_AUDIO);
if (rfcomm->telephony_ag && rfcomm->telephony_ag->transport.rejectSCO) {
spa_log_info(backend->log, "rejecting SCO, AudioGatewayTransport1.RejectSCO=true");
@ -3381,6 +3401,43 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag
}
spa_bt_device_add_profile(d, profile);
/* Prevent to connect HSP/HFP in both directions, i.e. HS->AG and AG->HS.
* This may only occur when connecting to a device which provides both
* HS and AG which should not be the case with headsets and phones. */
spa_list_for_each(rfcomm, &backend->rfcomm_list, link) {
if (spa_streq(rfcomm->device->address, d->address) &&
spa_streq(rfcomm->device->adapter->address, d->adapter->address)) {
bool connected = false;
switch (profile) {
case SPA_BT_PROFILE_HFP_HF:
if (rfcomm->profile == SPA_BT_PROFILE_HFP_AG)
connected = true;
break;
case SPA_BT_PROFILE_HFP_AG:
if (rfcomm->profile == SPA_BT_PROFILE_HFP_HF)
connected = true;
break;
case SPA_BT_PROFILE_HSP_HS:
if (rfcomm->profile == SPA_BT_PROFILE_HSP_AG)
connected = true;
break;
case SPA_BT_PROFILE_HSP_AG:
if (rfcomm->profile == SPA_BT_PROFILE_HSP_HS)
connected = true;
break;
default:
spa_log_warn(backend->log, "Unsupported profile: %s", handler);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
if (connected) {
spa_log_debug(backend->log, "Already connected in the opposite direction");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
}
}
dbus_message_iter_next(&it);
dbus_message_iter_get_basic(&it, &fd);
@ -4011,6 +4068,16 @@ static void parse_hfp_disable_nrec(struct impl *backend, const struct spa_dict *
backend->hfp_disable_nrec = false;
}
static void parse_hfp_pts(struct impl *backend, const struct spa_dict *info)
{
const char *str;
if ((str = spa_dict_lookup(info, "bluez5.hfphsp-backend-native-pts")) != NULL)
backend->pts = spa_atob(str);
else
backend->pts = false;
}
static void parse_hfp_default_volumes(struct impl *backend, const struct spa_dict *info)
{
const char *str;
@ -4095,6 +4162,7 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
parse_hfp_disable_nrec(backend, info);
parse_hfp_default_volumes(backend, info);
parse_hfp_pts(backend, info);
#ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE
if (!dbus_connection_register_object_path(backend->conn,

View file

@ -82,6 +82,22 @@
#define LC3_MAX_CHANNELS 28
/* Metadata types */
#define BAP_META_TYPE_PREFERRED_CONTEXT 0x01
#define BAP_META_TYPE_STREAMING_CONTEXT 0x02
#define BAP_META_TYPE_PROGRAM_INFO 0x03
#define BAP_META_TYPE_LANGUAGE 0x04
#define BAP_META_TYPE_CCID_LIST 0x05
#define BAP_META_TYPE_PARENTAL_RATING 0x06
#define BAP_META_TYPE_PROGRAM_INFO_URI 0x07
#define BAP_META_TYPE_AUDIO_ACTIVE_STATE 0x08
#define BAP_META_TYPE_BCAST_IMMEDIATE 0x09
#define BAP_META_TYPE_ASSISTED_LISTENING 0x0a
#define BAP_META_TYPE_BCAST_NAME 0x0b
#define BAP_META_TYPE_EXTENDED 0xfe
#define BAP_META_TYPE_VENDOR 0xff
#define BAP_CHANNEL_MONO 0x00000000 /* mono */
#define BAP_CHANNEL_FL 0x00000001 /* front left */
#define BAP_CHANNEL_FR 0x00000002 /* front right */
@ -137,11 +153,68 @@
#define BT_ISO_QOS_TARGET_LATENCY_BALANCED 0x02
#define BT_ISO_QOS_TARGET_LATENCY_RELIABILITY 0x03
struct __attribute__((packed)) ltv {
uint8_t len;
uint8_t type;
uint8_t value[];
};
#define BT_TMAP_UUID "00001855-0000-1000-8000-00805f9b34fb"
#define BT_TMAP_ROLE_CG_STR "cg"
#define BT_TMAP_ROLE_CT_STR "ct"
#define BT_TMAP_ROLE_UMS_STR "ums"
#define BT_TMAP_ROLE_UMR_STR "umr"
#define BT_TMAP_ROLE_BMS_STR "bms"
#define BT_TMAP_ROLE_BMR_STR "bmr"
#define BT_GMAP_ROLE_UGG_STR "ugg"
#define BT_GMAP_ROLE_UGT_STR "ugt"
#define BT_GMAP_ROLE_BGS_STR "bgs"
#define BT_GMAP_ROLE_BGR_STR "bgr"
#define BT_TMAP_ROLE_LIST(role) \
role(BT_TMAP_ROLE_CG) \
role(BT_TMAP_ROLE_CT) \
role(BT_TMAP_ROLE_UMS) \
role(BT_TMAP_ROLE_UMR) \
role(BT_TMAP_ROLE_BMS) \
role(BT_TMAP_ROLE_BMR)
#define BT_GMAP_UUID "00001858-0000-1000-8000-00805f9b34fb"
#define BT_GMAP_UGG_MULTIPLEX_STR "ugg-multiplex"
#define BT_GMAP_UGG_96KBPS_SOURCE_STR "ugg-96kbps-source"
#define BT_GMAP_UGG_MULTISINK_STR "ugg-multisink"
#define BT_GMAP_UGT_SOURCE_STR "ugt-source"
#define BT_GMAP_UGT_80KBPS_SOURCE_STR "ugt-80kbps-source"
#define BT_GMAP_UGT_SINK_STR "ugt-sink"
#define BT_GMAP_UGT_64KBPS_SINK_STR "ugt-64kbps-sink"
#define BT_GMAP_UGT_MULTIPLEX_STR "ugt-multiplex"
#define BT_GMAP_UGT_MULTISINK_STR "ugt-multisink"
#define BT_GMAP_UGT_MULTISOURCE_STR "ugt-multisource"
#define BT_GMAP_BGS_96KBPS_STR "bgs-96kbps"
#define BT_GMAP_BGR_MULTISINK_STR "bgr-multisink"
#define BT_GMAP_BGR_MULTIPLEX_STR "bgr-multiplex"
#define BT_GMAP_ROLE_LIST(role) \
role(BT_GMAP_ROLE_UGG) \
role(BT_GMAP_ROLE_UGT) \
role(BT_GMAP_ROLE_BGS) \
role(BT_GMAP_ROLE_BGR)
#define BT_GMAP_FEATURE_LIST(feature) \
feature(BT_GMAP_UGG_MULTIPLEX) \
feature(BT_GMAP_UGG_96KBPS_SOURCE) \
feature(BT_GMAP_UGG_MULTISINK) \
feature(BT_GMAP_UGT_SOURCE) \
feature(BT_GMAP_UGT_80KBPS_SOURCE) \
feature(BT_GMAP_UGT_SINK) \
feature(BT_GMAP_UGT_64KBPS_SINK) \
feature(BT_GMAP_UGT_MULTIPLEX) \
feature(BT_GMAP_UGT_MULTISINK) \
feature(BT_GMAP_UGT_MULTISOURCE) \
feature(BT_GMAP_BGS_96KBPS) \
feature(BT_GMAP_BGR_MULTISINK) \
feature(BT_GMAP_BGR_MULTIPLEX)
struct bap_endpoint_qos {
uint8_t framing;

View file

@ -16,6 +16,7 @@
#include <spa/param/audio/format-utils.h>
#include <spa/utils/string.h>
#include <spa/utils/json.h>
#include <spa/utils/cleanup.h>
#include <spa/debug/log.h>
#include <lc3.h>
@ -45,9 +46,11 @@ struct impl {
struct settings {
uint32_t locations;
uint32_t channel_allocation;
uint16_t supported_context;
uint16_t available_context;
bool sink;
bool duplex;
const char *qos_name;
char *qos_name;
int retransmission;
int latency;
int64_t delay;
@ -55,8 +58,10 @@ struct settings {
};
struct pac_data {
const uint8_t *data;
const void *data;
size_t size;
const void *metadata;
size_t metadata_size;
int index;
const struct settings *settings;
};
@ -81,9 +86,16 @@ typedef struct {
uint8_t n_blks;
bool sink;
bool duplex;
uint16_t preferred_context;
unsigned int priority;
} bap_lc3_t;
struct config_data {
bap_lc3_t conf;
int pac_index;
struct settings settings;
};
#define BAP_QOS(name_, rate_, duration_, framing_, framelen_, rtn_, latency_, delay_, priority_) \
((struct bap_qos){ .name = (name_), .rate = (rate_), .frame_duration = (duration_), .framing = (framing_), \
.framelen = (framelen_), .retransmission = (rtn_), .latency = (latency_), \
@ -191,32 +203,6 @@ static unsigned int get_duration_mask(uint8_t rate) {
return 0;
}
static int write_ltv(uint8_t *dest, uint8_t type, void* value, size_t len)
{
struct ltv *ltv = (struct ltv *)dest;
ltv->len = len + 1;
ltv->type = type;
memcpy(ltv->value, value, len);
return len + 2;
}
static int write_ltv_uint8(uint8_t *dest, uint8_t type, uint8_t value)
{
return write_ltv(dest, type, &value, sizeof(value));
}
static int write_ltv_uint16(uint8_t *dest, uint8_t type, uint16_t value)
{
return write_ltv(dest, type, &value, sizeof(value));
}
static int write_ltv_uint32(uint8_t *dest, uint8_t type, uint32_t value)
{
return write_ltv(dest, type, &value, sizeof(value));
}
static uint16_t parse_rates(const char *str)
{
struct spa_json it;
@ -319,7 +305,6 @@ static uint8_t parse_channel_counts(const char *str)
static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
const struct spa_dict *settings, uint8_t caps[A2DP_MAX_CAPS_SIZE])
{
uint8_t *data = caps;
const char *str;
uint16_t framelen[2];
uint16_t rate_mask = LC3_FREQ_48KHZ | LC3_FREQ_44KHZ | LC3_FREQ_32KHZ | \
@ -330,6 +315,7 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
uint16_t framelen_max = LC3_MAX_FRAME_BYTES;
uint8_t max_frames = 2;
uint32_t value;
struct ltv_writer writer = LTV_WRITER(caps, A2DP_MAX_CAPS_SIZE);
if (settings && (str = spa_dict_lookup(settings, "bluez5.bap-server-capabilities.rates")))
rate_mask = parse_rates(str);
@ -355,17 +341,17 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
framelen[0] = htobs(framelen_min);
framelen[1] = htobs(framelen_max);
data += write_ltv_uint16(data, LC3_TYPE_FREQ, htobs(rate_mask));
data += write_ltv_uint8(data, LC3_TYPE_DUR, duration_mask);
data += write_ltv_uint8(data, LC3_TYPE_CHAN, channel_counts);
data += write_ltv(data, LC3_TYPE_FRAMELEN, framelen, sizeof(framelen));
ltv_writer_uint16(&writer, LC3_TYPE_FREQ, rate_mask);
ltv_writer_uint8(&writer, LC3_TYPE_DUR, duration_mask);
ltv_writer_uint8(&writer, LC3_TYPE_CHAN, channel_counts);
ltv_writer_data(&writer, LC3_TYPE_FRAMELEN, framelen, sizeof(framelen));
/* XXX: we support only one frame block -> max 2 frames per SDU */
if (max_frames > 2)
max_frames = 2;
data += write_ltv_uint8(data, LC3_TYPE_BLKS, max_frames);
ltv_writer_uint8(&writer, LC3_TYPE_BLKS, max_frames);
return data - caps;
return ltv_writer_end(&writer);
}
static void debugc_ltv(struct spa_debug_context *debug_ctx, int pac, struct ltv *ltv)
@ -391,7 +377,7 @@ static void debugc_ltv(struct spa_debug_context *debug_ctx, int pac, struct ltv
}
}
static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_data pacs[MAX_PACS],
static int parse_bluez_pacs_data(const uint8_t *data, size_t data_size, struct pac_data pacs[MAX_PACS],
struct spa_debug_context *debug_ctx)
{
/*
@ -400,7 +386,7 @@ static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_da
*/
int pac = 0;
pacs[pac] = (struct pac_data){ data, 0 };
pacs[pac] = (struct pac_data){ .data = data };
while (data_size > 0) {
struct ltv *ltv = (struct ltv *)data;
@ -411,7 +397,7 @@ static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_da
break;
++pac;
pacs[pac] = (struct pac_data){ data + 1, 0, pac };
pacs[pac] = (struct pac_data){ .data = data + 1, .index = pac };
} else if (ltv->len >= data_size) {
return -EINVAL;
} else {
@ -425,6 +411,28 @@ static int parse_bluez_pacs(const uint8_t *data, size_t data_size, struct pac_da
return pac + 1;
}
static int parse_bluez_pacs(const uint8_t *data, size_t data_size,
const uint8_t *metadata, size_t metadata_size, struct pac_data pacs[MAX_PACS],
struct spa_debug_context *debug_ctx)
{
struct pac_data meta[MAX_PACS];
int pac_count, meta_count;
pac_count = parse_bluez_pacs_data(data, data_size, pacs, debug_ctx);
if (pac_count < 0)
return pac_count;
meta_count = parse_bluez_pacs_data(metadata, metadata_size, meta, debug_ctx);
if (meta_count == pac_count) {
for (int i = 0; i < pac_count; ++i) {
pacs[i].metadata = meta[i].data;
pacs[i].metadata_size = meta[i].size;
}
}
return pac_count;
}
static uint8_t get_channel_count(uint32_t channels)
{
uint8_t num;
@ -549,7 +557,7 @@ static int select_channels(uint8_t channel_counts, uint32_t locations, uint32_t
static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct spa_debug_context *debug_ctx)
{
const uint8_t *data = pac->data;
const void *data = pac->data;
size_t data_size = pac->size;
uint16_t framelen_min = 0, framelen_max = 0;
int max_frames = -1;
@ -557,9 +565,11 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp
uint8_t max_channels = 0;
uint8_t duration_mask = 0;
uint16_t rate_mask = 0;
uint16_t preferred_context = 0;
struct bap_qos bap_qos;
unsigned int i;
bool found = false;
const struct ltv *ltv;
if (!data_size)
return false;
@ -571,14 +581,7 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp
/* XXX: we always use one frame block */
conf->n_blks = 1;
while (data_size > 0) {
struct ltv *ltv = (struct ltv *)data;
if (ltv->len < sizeof(struct ltv) || ltv->len >= data_size) {
spa_debugc(debug_ctx, "invalid LTV data");
return false;
}
while ((ltv = ltv_next(&data, &data_size))) {
switch (ltv->type) {
case LC3_TYPE_FREQ:
spa_return_val_if_fail(ltv->len == 3, false);
@ -605,9 +608,25 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp
spa_debugc(debug_ctx, "unknown LTV type: 0x%02x", ltv->type);
break;
}
data_size -= ltv->len + 1;
data += ltv->len + 1;
}
if (data) {
spa_debugc(debug_ctx, "invalid LTV data");
return false;
}
data = pac->metadata;
data_size = pac->metadata_size;
while ((ltv = ltv_next(&data, &data_size))) {
switch (ltv->type) {
case BAP_META_TYPE_PREFERRED_CONTEXT:
if (ltv->len != 3)
break;
preferred_context = ltv->value[0] + (ltv->value[1] << 8);
break;
}
}
if (data)
spa_debugc(debug_ctx, "malformed metadata");
for (i = 0; i < 8; ++i)
if (channel_counts & (1u << i))
@ -646,8 +665,8 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp
* and reassemble SDUs as needed.
*/
if (pac->settings->duplex) {
/* 16KHz input is mandatory in BAP v1.0.1 Table 3.5, so prefer
* it or 32kHz for now for input rate in duplex configuration.
/* 16KHz input is mandatory in BAP v1.0.1 Table 3.5, and 32KHz in TMAP,
* so prefer those for now for input rate in duplex configuration.
*
* It appears few devices support 48kHz out + input, so in duplex mode
* try 32 kHz or 16 kHz also for output direction.
@ -670,12 +689,15 @@ static bool select_config(bap_lc3_t *conf, const struct pac_data *pac, struct sp
conf->frame_duration = bap_qos.frame_duration;
conf->framelen = bap_qos.framelen;
conf->priority = bap_qos.priority;
conf->preferred_context = preferred_context;
return true;
}
static bool parse_conf(bap_lc3_t *conf, const uint8_t *data, size_t data_size)
static bool parse_conf(bap_lc3_t *conf, const void *data, size_t data_size)
{
const struct ltv *ltv;
if (!data_size)
return false;
memset(conf, 0, sizeof(*conf));
@ -685,12 +707,7 @@ static bool parse_conf(bap_lc3_t *conf, const uint8_t *data, size_t data_size)
/* Absent Codec_Frame_Blocks_Per_SDU means 0x1 (BAP v1.0.1 Sec 4.3.2) */
conf->n_blks = 1;
while (data_size > 0) {
struct ltv *ltv = (struct ltv *)data;
if (ltv->len < sizeof(struct ltv) || ltv->len >= data_size)
return false;
while ((ltv = ltv_next(&data, &data_size))) {
switch (ltv->type) {
case LC3_TYPE_FREQ:
spa_return_val_if_fail(ltv->len == 2, false);
@ -718,9 +735,9 @@ static bool parse_conf(bap_lc3_t *conf, const uint8_t *data, size_t data_size)
default:
return false;
}
data_size -= ltv->len + 1;
data += ltv->len + 1;
}
if (data)
return false;
if (conf->frame_duration == 0xFF || !conf->rate)
return false;
@ -728,7 +745,32 @@ static bool parse_conf(bap_lc3_t *conf, const uint8_t *data, size_t data_size)
return true;
}
static int conf_cmp(const bap_lc3_t *conf1, int res1, const bap_lc3_t *conf2, int res2)
static uint16_t get_wanted_context(const struct settings *settings)
{
uint16_t context;
/* Stick with contexts specified in TMAP. Anything else is probably crapshoot due
* to interesting device firmware behavior.
*
* Eg. some Earfun firmwares fail if we set MEDIA | CONVERSATIONAL, other versions
* disconnect if LIVE is set, Samsung Galaxy Buds fail on microphone Enable if
* CONVERSATIONAL is not set.
*/
if (settings->sink || settings->duplex)
context = BAP_CONTEXT_CONVERSATIONAL;
else
context = BAP_CONTEXT_MEDIA;
/* CAP v1.0.1 Sec 7.3.1.2.1: drop contexts if not available, otherwise unspecified */
context &= settings->available_context;
if (!context)
context = BAP_CONTEXT_UNSPECIFIED & settings->available_context;
return context;
}
static int conf_cmp(const bap_lc3_t *conf1, int res1, const bap_lc3_t *conf2, int res2,
const struct settings *settings)
{
const bap_lc3_t *conf;
int a, b;
@ -753,6 +795,9 @@ static int conf_cmp(const bap_lc3_t *conf1, int res1, const bap_lc3_t *conf2, in
PREFER_EXPR(conf->priority == UINT_MAX);
/* CAP v1.0.1 Sec 7.3.1.2.4: should use PAC preferred context if possible */
PREFER_BOOL(conf->preferred_context & get_wanted_context(settings));
PREFER_BOOL(conf->channels & LC3_CHAN_2);
PREFER_BOOL(conf->channels & LC3_CHAN_1);
@ -778,7 +823,7 @@ static int pac_cmp(const void *p1, const void *p2)
res1 = select_config(&conf1, pac1, &debug_ctx.ctx) ? (int)sizeof(bap_lc3_t) : -EINVAL;
res2 = select_config(&conf2, pac2, &debug_ctx.ctx) ? (int)sizeof(bap_lc3_t) : -EINVAL;
return conf_cmp(&conf1, res1, &conf2, res2);
return conf_cmp(&conf1, res1, &conf2, res2, pac1->settings);
}
static void parse_settings(struct settings *s, const struct spa_dict *settings,
@ -797,7 +842,7 @@ static void parse_settings(struct settings *s, const struct spa_dict *settings,
return;
if ((str = spa_dict_lookup(settings, "bluez5.bap.preset")))
s->qos_name = str;
s->qos_name = strdup(str);
if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.rtn"), &value, 0))
s->retransmission = value;
@ -817,12 +862,18 @@ static void parse_settings(struct settings *s, const struct spa_dict *settings,
if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.channel-allocation"), &value, 0))
s->channel_allocation = value;
if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.supported-context"), &value, 0))
s->supported_context = value;
if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.available-context"), &value, 0))
s->available_context = value;
if (spa_atob(spa_dict_lookup(settings, "bluez5.bap.debug")))
*debug_ctx = SPA_LOG_DEBUG_INIT(log_, SPA_LOG_LEVEL_DEBUG);
else
*debug_ctx = SPA_LOG_DEBUG_INIT(NULL, SPA_LOG_LEVEL_TRACE);
/* Is remote endpoint sink or source */
/* Is local endpoint sink or source */
s->sink = spa_atob(spa_dict_lookup(settings, "bluez5.bap.sink"));
/* Is remote endpoint duplex */
@ -837,26 +888,48 @@ static void parse_settings(struct settings *s, const struct spa_dict *settings,
(int)s->sink, (int)s->duplex);
}
static void free_config_data(struct config_data *d)
{
if (!d)
return;
free(d->settings.qos_name);
free(d);
}
SPA_DEFINE_AUTOPTR_CLEANUP(config_data, struct config_data, { spa_clear_ptr(*thing, free_config_data); });
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data)
{
struct pac_data pacs[MAX_PACS];
int npacs;
bap_lc3_t conf;
uint8_t *data = config;
struct spa_debug_log_ctx debug_ctx;
struct settings s;
int i;
spa_autoptr(config_data) d = NULL;
int i, ret;
struct ltv_writer writer = LTV_WRITER(config, A2DP_MAX_CAPS_SIZE);
const void *metadata = NULL;
uint32_t metadata_len = 0;
if (caps == NULL)
return -EINVAL;
parse_settings(&s, settings, &debug_ctx);
d = calloc(1, sizeof(*d));
if (!d)
return -ENOMEM;
parse_settings(&d->settings, settings, &debug_ctx);
if (spa_atou32(spa_dict_lookup(settings, "bluez5.bap.metadata"), &metadata_len, 0))
metadata = spa_dict_lookup(settings, "bluez5.bap.metadata");
if (!metadata)
metadata_len = 0;
/* Select best conf from those possible */
npacs = parse_bluez_pacs(caps, caps_size, pacs, &debug_ctx.ctx);
npacs = parse_bluez_pacs(caps, caps_size, metadata, metadata_len, pacs, &debug_ctx.ctx);
if (npacs < 0) {
spa_debugc(&debug_ctx.ctx, "malformed PACS");
return npacs;
@ -866,7 +939,7 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags,
}
for (i = 0; i < npacs; ++i)
pacs[i].settings = &s;
pacs[i].settings = &d->settings;
qsort(pacs, npacs, sizeof(struct pac_data), pac_cmp);
@ -875,38 +948,55 @@ static int codec_select_config(const struct media_codec *codec, uint32_t flags,
if (!select_config(&conf, &pacs[0], &debug_ctx.ctx))
return -ENOTSUP;
data += write_ltv_uint8(data, LC3_TYPE_FREQ, conf.rate);
data += write_ltv_uint8(data, LC3_TYPE_DUR, conf.frame_duration);
d->conf = conf;
d->pac_index = pacs[0].index;
ltv_writer_uint8(&writer, LC3_TYPE_FREQ, conf.rate);
ltv_writer_uint8(&writer, LC3_TYPE_DUR, conf.frame_duration);
/* Indicate MONO with absent Audio_Channel_Allocation (BAP v1.0.1 Sec. 4.3.2) */
if (conf.channels != 0)
data += write_ltv_uint32(data, LC3_TYPE_CHAN, htobl(conf.channels));
ltv_writer_uint32(&writer, LC3_TYPE_CHAN, conf.channels);
data += write_ltv_uint16(data, LC3_TYPE_FRAMELEN, htobs(conf.framelen));
data += write_ltv_uint8(data, LC3_TYPE_BLKS, conf.n_blks);
ltv_writer_uint16(&writer, LC3_TYPE_FRAMELEN, conf.framelen);
ltv_writer_uint8(&writer, LC3_TYPE_BLKS, conf.n_blks);
return data - config;
ret = ltv_writer_end(&writer);
if (ret >= 0 && config_data)
*config_data = spa_steal_ptr(d);
return ret;
}
static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t flags, const void *caps1, size_t caps1_size,
const void *caps2, size_t caps2_size, const struct media_codec_audio_info *info, const struct spa_dict *global_settings)
{
bap_lc3_t conf1, conf2;
int res1, res2;
int res1, res2, res;
void *data1 = NULL;
void *data2 = NULL;
const struct config_data *d;
/* Order selected configurations by preference */
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1);
res2 = codec->select_config(codec, 0, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2);
res1 = codec->select_config(codec, 0, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1, &data1);
res2 = codec->select_config(codec, 0, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2, &data2);
return conf_cmp(&conf1, res1, &conf2, res2);
d = data1 ? data1 : data2;
res = conf_cmp(&conf1, res1, &conf2, res2, d ? &d->settings : NULL);
codec->free_config_data(codec, data1);
codec->free_config_data(codec, data2);
return res;
}
static uint8_t channels_to_positions(uint32_t channels, uint32_t *position)
static uint8_t channels_to_positions(uint32_t channels, uint32_t *position, uint32_t max_position)
{
uint32_t n_channels = get_channel_count(channels);
uint8_t n_positions = 0;
spa_assert(n_channels <= SPA_AUDIO_MAX_CHANNELS);
spa_assert(n_channels <= max_position);
if (channels == 0) {
position[0] = SPA_AUDIO_CHANNEL_MONO;
@ -932,7 +1022,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
bap_lc3_t conf;
struct spa_pod_frame f[2];
struct spa_pod_choice *choice;
uint32_t position[SPA_AUDIO_MAX_CHANNELS];
uint32_t position[LC3_MAX_CHANNELS];
uint32_t i = 0;
uint8_t res;
@ -990,7 +1080,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
if (i == 0)
return -EINVAL;
res = channels_to_positions(conf.channels, position);
res = channels_to_positions(conf.channels, position, SPA_N_ELEMENTS(position));
if (res == 0)
return -EINVAL;
spa_pod_builder_add(b,
@ -1044,7 +1134,8 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags
return -EINVAL;
}
res = channels_to_positions(conf.channels, info->info.raw.position);
res = channels_to_positions(conf.channels, info->info.raw.position,
SPA_N_ELEMENTS(info->info.raw.position));
if (res == 0)
return -EINVAL;
info->info.raw.channels = res;
@ -1061,24 +1152,23 @@ static int codec_validate_config(const struct media_codec *codec, uint32_t flags
}
static int codec_get_qos(const struct media_codec *codec,
const void *config, size_t config_size,
const struct bap_endpoint_qos *endpoint_qos,
struct bap_codec_qos *qos, const struct spa_dict *settings)
const void *config_data,
struct bap_codec_qos *qos)
{
struct bap_qos bap_qos;
bap_lc3_t conf;
bool found = false;
struct settings s;
struct spa_debug_log_ctx debug_ctx;
const struct config_data *d = config_data;
spa_zero(*qos);
if (!parse_conf(&conf, config, config_size))
if (!d)
return -EINVAL;
parse_settings(&s, settings, &debug_ctx);
conf = d->conf;
found = select_bap_qos(&bap_qos, &s, get_rate_mask(conf.rate), get_duration_mask(conf.frame_duration),
found = select_bap_qos(&bap_qos, &d->settings, get_rate_mask(conf.rate), get_duration_mask(conf.frame_duration),
conf.framelen, conf.framelen);
if (!found) {
/* shouldn't happen: select_config should pick existing one */
@ -1121,6 +1211,27 @@ static int codec_get_qos(const struct media_codec *codec,
return 0;
}
static int codec_get_metadata(const struct media_codec *codec, const void *config_data,
uint8_t *meta, size_t meta_max_size)
{
const struct config_data *d = config_data;
struct ltv_writer writer = LTV_WRITER(meta, meta_max_size);
uint16_t ctx;
ctx = get_wanted_context(&d->settings);
if (!ctx)
ctx = BAP_CONTEXT_UNSPECIFIED;
ltv_writer_uint16(&writer, BAP_META_TYPE_STREAMING_CONTEXT, ctx);
return ltv_writer_end(&writer);
}
static void codec_free_config_data(const struct media_codec *codec, void *config_data)
{
free_config_data(config_data);
}
static void *codec_init(const struct media_codec *codec, uint32_t flags,
void *config, size_t config_len, const struct spa_audio_info *info,
void *props, size_t mtu)
@ -1377,80 +1488,60 @@ static int codec_get_bis_config(const struct media_codec *codec, uint8_t *caps,
uint8_t *caps_size, struct spa_dict *settings,
struct bap_codec_qos *qos)
{
int index = 0x0;
bool preset_found = false;
const char *preset = NULL;
const char *preset_name = NULL;
int channel_allocation = 0;
uint8_t *data = caps;
int i, ret;
struct ltv_writer writer = LTV_WRITER(caps, *caps_size);
const struct bap_qos *preset = NULL;
*caps_size = 0;
int i;
if (settings) {
for (i = 0; i < (int)settings->n_items; ++i) {
if (spa_streq(settings->items[i].key, "channel_allocation"))
sscanf(settings->items[i].value, "%"PRIu32, &channel_allocation);
if (spa_streq(settings->items[i].key, "preset"))
preset = spa_dict_lookup(settings, "preset");
preset_name = settings->items[i].value;
}
}
if (preset == NULL)
if (preset_name == NULL)
return -EINVAL;
SPA_FOR_EACH_ELEMENT_VAR(bap_bcast_qos_configs, c) {
if (spa_streq(c->name, preset)) {
preset_found = true;
if (spa_streq(c->name, preset_name)) {
preset = c;
break;
}
index++;
}
if (!preset_found)
if (!preset)
return -EINVAL;
switch (bap_bcast_qos_configs[index].rate) {
case LC3_CONFIG_FREQ_48KHZ:
data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_48KHZ);
break;
case LC3_CONFIG_FREQ_44KHZ:
data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_44KHZ);
break;
case LC3_CONFIG_FREQ_32KHZ:
data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_32KHZ);
break;
case LC3_CONFIG_FREQ_24KHZ:
data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_24KHZ);
break;
case LC3_CONFIG_FREQ_16KHZ:
data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_16KHZ);
break;
case LC3_CONFIG_FREQ_8KHZ:
data += write_ltv_uint8(data, LC3_TYPE_FREQ, LC3_CONFIG_FREQ_8KHZ);
break;
default:
return -EINVAL;
}
*caps_size += 3;
ltv_writer_uint8(&writer, LC3_TYPE_FREQ, preset->rate);
ltv_writer_uint16(&writer, LC3_TYPE_FRAMELEN, preset->framelen);
ltv_writer_uint8(&writer, LC3_TYPE_DUR, preset->frame_duration);
ltv_writer_uint32(&writer, LC3_TYPE_CHAN, channel_allocation);
data += write_ltv_uint16(data, LC3_TYPE_FRAMELEN, htobs(bap_bcast_qos_configs[index].framelen));
*caps_size += 4;
data += write_ltv_uint8(data, LC3_TYPE_DUR, bap_bcast_qos_configs[index].frame_duration);
*caps_size += 3;
data += write_ltv_uint32(data, LC3_TYPE_CHAN, htobl(channel_allocation));
*caps_size += 6;
if(bap_bcast_qos_configs[index].framing)
if (preset->framing)
qos->framing = 1;
else
qos->framing = 0;
qos->sdu = bap_bcast_qos_configs[index].framelen * get_channel_count(channel_allocation);
qos->retransmission = bap_bcast_qos_configs[index].retransmission;
qos->latency = bap_bcast_qos_configs[index].latency;
qos->delay = bap_bcast_qos_configs[index].delay;
qos->sdu = preset->framelen * get_channel_count(channel_allocation);
qos->retransmission = preset->retransmission;
qos->latency = preset->latency;
qos->delay = preset->delay;
qos->phy = 2;
qos->interval = (bap_bcast_qos_configs[index].frame_duration == LC3_CONFIG_DURATION_7_5 ? 7500 : 10000);
qos->interval = (preset->frame_duration == LC3_CONFIG_DURATION_7_5 ? 7500 : 10000);
return true;
ret = ltv_writer_end(&writer);
if (ret < 0)
return ret;
if (ret > UINT8_MAX)
return -ENOSPC;
*caps_size = ret;
return 0;
}
const struct media_codec bap_codec_lc3 = {
@ -1464,6 +1555,8 @@ const struct media_codec bap_codec_lc3 = {
.enum_config = codec_enum_config,
.validate_config = codec_validate_config,
.get_qos = codec_get_qos,
.get_metadata = codec_get_metadata,
.free_config_data = codec_free_config_data,
.caps_preference_cmp = codec_caps_preference_cmp,
.init = codec_init,
.deinit = codec_deinit,

View file

@ -35,6 +35,7 @@
#include <spa/utils/string.h>
#include <spa/utils/json.h>
#include <spa-private/dbus-helpers.h>
#include <spa/param/audio/raw-utils.h>
#include <spa/param/audio/raw-json.h>
#include "codec-loader.h"
@ -76,6 +77,11 @@ enum backend_selection {
#define TRANSPORT_ERROR_TIMEOUT (2*BLUEZ_ACTION_RATE_MSEC*SPA_NSEC_PER_MSEC)
struct bap_features {
struct spa_dict dict;
struct spa_dict_item items[32];
};
struct spa_bt_monitor {
struct spa_handle handle;
struct spa_device device;
@ -130,6 +136,8 @@ struct spa_bt_monitor {
uint32_t bap_source_contexts;
uint32_t bap_source_supported_contexts;
struct bap_features bap_features;
struct spa_bt_quirks *quirks;
#define MAX_SETTINGS 128
@ -151,13 +159,17 @@ struct spa_bt_remote_endpoint {
char *uuid;
unsigned int codec;
struct spa_bt_device *device;
uint8_t capabilities[A2DP_MAX_CAPS_SIZE];
int capabilities_len;
uint8_t *capabilities;
size_t capabilities_len;
uint8_t *metadata;
size_t metadata_len;
bool delay_reporting;
bool acceptor;
struct bap_endpoint_qos qos;
struct bap_features bap_features;
bool asha_right_side;
uint64_t hisyncid;
};
@ -655,6 +667,77 @@ static bool endpoint_should_be_registered(struct spa_bt_monitor *monitor,
codec->fill_caps;
}
static bool bap_features_add(struct bap_features *feat, const char *uuid, const char *name)
{
#define TMAP_ITEM(item) { BT_TMAP_UUID, item ##_STR, BT_TMAP_UUID ":" item ##_STR },
#define GMAP_ITEM(item) { BT_GMAP_UUID, item ##_STR, BT_GMAP_UUID ":" item ##_STR },
static const struct {
const char *const uuid;
const char *const name;
const char *const key;
} values[] = {
BT_TMAP_ROLE_LIST(TMAP_ITEM)
BT_GMAP_ROLE_LIST(GMAP_ITEM)
BT_GMAP_FEATURE_LIST(GMAP_ITEM)
{ NULL, NULL, NULL }
};
SPA_STATIC_ASSERT(SPA_N_ELEMENTS(feat->items) >= SPA_N_ELEMENTS(values));
size_t n_items = feat->dict.n_items;
size_t i;
/* Accept only listed features */
for (i = 0; values[i].uuid; ++i)
if (spa_streq(values[i].uuid, uuid) && spa_streq(values[i].name, name))
break;
if (!values[i].uuid)
return false;
if (spa_dict_lookup(&feat->dict, values[i].key))
return false;
spa_assert(n_items < SPA_N_ELEMENTS(feat->items));
/* Add */
feat->items[n_items].key = values[i].key;
feat->items[n_items].value = values[i].uuid;
n_items++;
feat->dict = SPA_DICT(feat->items, n_items);
return true;
}
/** Get feature uuid at \a i */
static const char *bap_features_get_uuid(struct bap_features *feat, size_t i)
{
if (!SPA_FLAG_IS_SET(feat->dict.flags, SPA_DICT_FLAG_SORTED))
spa_dict_qsort(&feat->dict);
if (i >= feat->dict.n_items)
return NULL;
return feat->dict.items[i].value;
}
/** Get feature name at \a i, or NULL if uuid doesn't match */
static const char *bap_features_get_name(struct bap_features *feat, size_t i, const char *uuid)
{
char *pos;
if (i >= feat->dict.n_items)
return NULL;
if (!spa_streq(feat->dict.items[i].value, uuid))
return NULL;
pos = strchr(feat->dict.items[i].key, ':');
if (!pos)
return NULL;
return pos + 1;
}
static void bap_features_clear(struct bap_features *feat)
{
spa_zero(*feat);
}
static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata)
{
struct spa_bt_monitor *monitor = userdata;
@ -692,7 +775,7 @@ static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBu
* by codec switching.
*/
res = codec->select_config(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, cap, size, &monitor->default_audio_info,
&monitor->global_settings, config);
&monitor->global_settings, config, NULL);
else
res = -ENOTSUP;
@ -877,8 +960,9 @@ static void parse_endpoint_qos(struct spa_bt_monitor *monitor, DBusMessageIter *
}
static int parse_endpoint_props(struct spa_bt_monitor *monitor, DBusMessageIter *iter,
uint8_t caps[A2DP_MAX_CAPS_SIZE], int *caps_size, const char **endpoint_path,
struct bap_endpoint_qos *qos)
uint8_t **caps, size_t *caps_size,
uint8_t **meta, size_t *meta_size,
const char **endpoint_path, struct bap_endpoint_qos *qos)
{
DBusMessageIter dict_iter = *iter;
const char *key = NULL;
@ -899,29 +983,46 @@ static int parse_endpoint_props(struct spa_bt_monitor *monitor, DBusMessageIter
type = dbus_message_iter_get_arg_type(&it[1]);
if (spa_streq(key, "Capabilities")) {
uint8_t *buf;
if (spa_streq(key, "Capabilities") || spa_streq(key, "Metadata")) {
uint8_t **dest;
size_t *size;
uint8_t *data, *buf;
int n;
if (!caps)
if (spa_streq(key, "Capabilities")) {
dest = caps;
size = caps_size;
} else {
dest = meta;
size = meta_size;
}
if (!dest)
goto next;
if (type != DBUS_TYPE_ARRAY)
spa_assert(dest && size);
if (!check_iter_signature(&it[1], "ay"))
goto bad_property;
dbus_message_iter_recurse(&it[1], &it[2]);
type = dbus_message_iter_get_arg_type(&it[2]);
if (type != DBUS_TYPE_BYTE)
goto bad_property;
dbus_message_iter_get_fixed_array(&it[2], &data, &n);
dbus_message_iter_get_fixed_array(&it[2], &buf, caps_size);
if (*caps_size > A2DP_MAX_CAPS_SIZE) {
spa_log_error(monitor->log, "%s size:%d too large", key, (int)*caps_size);
return -EINVAL;
if (n) {
buf = malloc(n);
if (!buf)
return -ENOMEM;
memcpy(buf, data, n);
} else {
buf = NULL;
}
memcpy(caps, buf, *caps_size);
spa_log_info(monitor->log, "%p: %s size:%d", monitor, key, *caps_size);
spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', caps, (size_t)*caps_size);
free(*dest);
*dest = buf;
*size = n;
spa_log_info(monitor->log, "%p: %s size:%zu", monitor, key, *size);
spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', *dest, *size);
} else if (spa_streq(key, "Endpoint")) {
if (!endpoint_path)
goto next;
@ -1011,8 +1112,12 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
const char *endpoint_path = NULL;
uint8_t config[A2DP_MAX_CAPS_SIZE];
void *config_data = NULL;
char locations[64] = {0};
char channel_allocation[64] = {0};
char supported_context[64] = {0};
char available_context[64] = {0};
char metadata_len[64] = {0};
int conf_size;
DBusMessageIter dict;
@ -1027,6 +1132,9 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
path = dbus_message_get_path(m);
if ((r = dbus_message_new_method_return(m)) == NULL)
return DBUS_HANDLER_RESULT_NEED_MEMORY;
/* TODO: for codecs with shared endpoint, this currently always picks the default
* one. However, currently we don't have BAP codecs with shared endpoint, so
* this does not matter, but in case they are needed later we should pick the
@ -1042,7 +1150,7 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
/* Find endpoint */
iter = props;
if (parse_endpoint_props(monitor, &iter, NULL, NULL, &endpoint_path, NULL) < 0)
if (parse_endpoint_props(monitor, &iter, NULL, NULL, NULL, NULL, &endpoint_path, NULL) < 0)
goto error_invalid;
ep = remote_endpoint_find(monitor, endpoint_path);
@ -1058,7 +1166,8 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
/* Parse endpoint properties */
iter = props;
if (parse_endpoint_props(monitor, &iter, ep->capabilities, &ep->capabilities_len, NULL, &ep->qos) < 0)
if (parse_endpoint_props(monitor, &iter, &ep->capabilities, &ep->capabilities_len,
&ep->metadata, &ep->metadata_len, NULL, &ep->qos) < 0)
goto error_invalid;
if (ep->qos.locations)
@ -1066,6 +1175,10 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
if (ep->qos.channel_allocation)
spa_scnprintf(channel_allocation, sizeof(channel_allocation), "%"PRIu32, ep->qos.channel_allocation);
spa_scnprintf(supported_context, sizeof(supported_context), "%"PRIu16, ep->qos.supported_context);
spa_scnprintf(available_context, sizeof(available_context), "%"PRIu16, ep->qos.context);
spa_scnprintf(metadata_len, sizeof(metadata_len), "%zu", ep->metadata_len);
if (!ep->device->preferred_profiles)
ep->device->preferred_profiles = ep->device->profiles;
@ -1074,16 +1187,22 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
i = 0;
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.locations", locations);
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.channel-allocation", channel_allocation);
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.supported-context", supported_context);
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.available-context", available_context);
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.sink", sink ? "true" : "false");
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.duplex", duplex ? "true" : "false");
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.debug", "true");
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.metadata", (void *)ep->metadata);
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.metadata-len", metadata_len);
for (j = 0; j < ep->bap_features.dict.n_items && i < SPA_N_ELEMENTS(setting_items); ++i, ++j)
setting_items[i] = ep->bap_features.dict.items[j];
if (ep->device->settings)
for (j = 0; j < ep->device->settings->n_items && i < SPA_N_ELEMENTS(setting_items); ++i, ++j)
setting_items[i] = ep->device->settings->items[j];
settings = SPA_DICT_INIT(setting_items, i);
conf_size = codec->select_config(codec, 0, ep->capabilities, ep->capabilities_len,
&monitor->default_audio_info, &settings, config);
&monitor->default_audio_info, &settings, config, &config_data);
if (conf_size < 0) {
spa_log_error(monitor->log, "can't select config: %d (%s)",
conf_size, spa_strerror(conf_size));
@ -1092,8 +1211,6 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
spa_log_info(monitor->log, "%p: selected conf %d", monitor, conf_size);
spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', (uint8_t *)config, (size_t)conf_size);
if ((r = dbus_message_new_method_return(m)) == NULL)
return DBUS_HANDLER_RESULT_NEED_MEMORY;
dbus_message_iter_init_append(r, &iter);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
@ -1112,7 +1229,7 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
spa_zero(qos);
res = codec->get_qos(codec, config, conf_size, &ep->qos, &qos, &settings);
res = codec->get_qos(codec, &ep->qos, config_data, &qos);
if (res < 0) {
spa_log_error(monitor->log, "can't select QOS config: %d (%s)",
res, spa_strerror(res));
@ -1160,8 +1277,30 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
dbus_message_iter_close_container(&dict, &entry);
}
if (codec->get_metadata) {
uint8_t meta[4096] = {};
size_t meta_size;
meta_size = res = codec->get_metadata(codec, config_data, meta, sizeof(meta));
if (res < 0) {
spa_log_error(monitor->log, "can't select metadata config: %d (%s)",
res, spa_strerror(res));
goto error_invalid;
}
if (meta_size) {
spa_log_info(monitor->log, "%p: selected metadata %d", monitor, (int)meta_size);
spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', meta, meta_size);
append_basic_array_variant_dict_entry(&dict, "Metadata", "ay", "y", DBUS_TYPE_BYTE, &meta, meta_size);
}
}
dbus_message_iter_close_container(&iter, &dict);
if (config_data && codec->free_config_data)
codec->free_config_data(codec, config_data);
if (!dbus_connection_send(conn, r, NULL))
return DBUS_HANDLER_RESULT_NEED_MEMORY;
@ -1172,6 +1311,9 @@ error_invalid:
goto error;
error:
if (config_data && codec->free_config_data)
codec->free_config_data(codec, config_data);
if (!reply_with_error(conn, m, "org.bluez.Error.InvalidArguments", err_msg))
return DBUS_HANDLER_RESULT_NEED_MEMORY;
return DBUS_HANDLER_RESULT_HANDLED;
@ -2796,6 +2938,38 @@ static struct spa_bt_device *create_bcast_device(struct spa_bt_monitor *monitor,
static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, struct spa_bt_monitor *monitor);
static void parse_supported_features(struct spa_bt_monitor *monitor,
DBusMessageIter *dict, struct bap_features *features)
{
while (dbus_message_iter_get_arg_type(dict) == DBUS_TYPE_DICT_ENTRY) {
DBusMessageIter entry, variant, array;
const char *key;
dbus_message_iter_recurse(dict, &entry);
dbus_message_iter_get_basic(&entry, &key);
dbus_message_iter_next(&entry);
dbus_message_iter_recurse(&entry, &variant);
if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY)
goto next;
dbus_message_iter_recurse(&variant, &array);
while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
const char *name;
dbus_message_iter_get_basic(&array, &name);
if (bap_features_add(features, key, name))
spa_log_debug(monitor->log, "remote_endpoint: BAP feature %s %s", key, name);
dbus_message_iter_next(&array);
}
next:
dbus_message_iter_next(dict);
}
return;
}
static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_endpoint,
DBusMessageIter *props_iter,
DBusMessageIter *invalidated_iter)
@ -2804,8 +2978,9 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en
DBusMessageIter copy_iter = *props_iter;
parse_endpoint_props(monitor, &copy_iter,
remote_endpoint->capabilities, &remote_endpoint->capabilities_len, NULL,
&remote_endpoint->qos);
&remote_endpoint->capabilities, &remote_endpoint->capabilities_len,
&remote_endpoint->metadata, &remote_endpoint->metadata_len,
NULL, &remote_endpoint->qos);
while (dbus_message_iter_get_arg_type(props_iter) != DBUS_TYPE_INVALID) {
DBusMessageIter it[2];
@ -2819,7 +2994,8 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en
type = dbus_message_iter_get_arg_type(&it[1]);
if (spa_streq(key, "Capabilities") || spa_streq(key, "Locations") ||
if (spa_streq(key, "Capabilities") || spa_streq(key, "Metadata") ||
spa_streq(key, "Locations") ||
spa_streq(key, "QoS") || spa_streq(key, "Context") ||
spa_streq(key, "SupportedContext")) {
/* parsed by parse_endpoint_props */
@ -2929,8 +3105,13 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en
remote_endpoint->hisyncid = *(uint64_t *)value;
spa_log_debug(monitor->log, "remote_endpoint %p: %s=%"PRIu64, remote_endpoint, key, remote_endpoint->hisyncid);
}
else {
} else if (spa_streq(key, "SupportedFeatures")) {
if (!check_iter_signature(&it[1], "a{sv}"))
goto next;
dbus_message_iter_recurse(&it[1], &it[2]);
parse_supported_features(monitor, &it[2], &remote_endpoint->bap_features);
} else {
unhandled:
spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key);
}
@ -2985,10 +3166,14 @@ static void remote_endpoint_free(struct spa_bt_remote_endpoint *remote_endpoint)
if (remote_endpoint->device)
spa_list_remove(&remote_endpoint->device_link);
bap_features_clear(&remote_endpoint->bap_features);
spa_list_remove(&remote_endpoint->link);
free(remote_endpoint->path);
free(remote_endpoint->transport_path);
free(remote_endpoint->uuid);
free(remote_endpoint->capabilities);
free(remote_endpoint->metadata);
free(remote_endpoint);
}
@ -3586,6 +3771,11 @@ static int transport_update_props(struct spa_bt_transport *transport,
free(transport->configuration);
transport->configuration_len = 0;
if (!len) {
transport->configuration = NULL;
goto next;
}
transport->configuration = malloc(len);
if (transport->configuration) {
memcpy(transport->configuration, value, len);
@ -4086,13 +4276,22 @@ static int transport_acquire(void *data, bool optional)
return do_transport_acquire(data);
}
static int do_transport_release(struct spa_bt_transport *transport)
struct pending_release {
struct spa_list link;
DBusPendingCall *pending;
struct spa_bt_transport *transport;
bool is_idle;
};
static struct pending_release *do_transport_release(struct spa_bt_transport *transport)
{
struct spa_bt_monitor *monitor = transport->monitor;
spa_autoptr(DBusMessage) m = NULL, r = NULL;
spa_autoptr(DBusMessage) m = NULL;
struct spa_bt_transport *t_linked;
bool is_idle = (transport->state == SPA_BT_TRANSPORT_STATE_IDLE);
bool linked = false;
struct pending_release *pending;
DBusPendingCall *p;
spa_log_debug(monitor->log, "transport %p: Release %s",
transport, transport->path);
@ -4129,7 +4328,7 @@ static int do_transport_release(struct spa_bt_transport *transport)
if (linked) {
spa_log_info(monitor->log, "Linked transport %s released", transport->path);
transport->fd = -1;
return 0;
return NULL;
}
release:
@ -4145,46 +4344,39 @@ release:
BLUEZ_MEDIA_TRANSPORT_INTERFACE,
"Release");
if (m == NULL)
return -ENOMEM;
return NULL;
spa_auto(DBusError) err = DBUS_ERROR_INIT;
r = dbus_connection_send_with_reply_and_block(monitor->conn, m, -1, &err);
if (r == NULL) {
if (is_idle) {
/* XXX: The fd always needs to be closed. However, Release()
* XXX: apparently doesn't need to be called on idle transports
* XXX: and fails. We call it just to be sure (e.g. in case
* XXX: there's a race with updating the property), but tone down the error.
*/
spa_log_debug(monitor->log, "Failed to release idle transport %s: %s",
transport->path, err.message);
} else if (spa_streq(err.name, DBUS_ERROR_UNKNOWN_METHOD) ||
spa_streq(err.name, DBUS_ERROR_UNKNOWN_OBJECT)) {
/* Transport disappeared */
spa_log_debug(monitor->log, "Failed to release (gone) transport %s: %s",
transport->path, err.message);
} else {
spa_log_error(monitor->log, "Failed to release transport %s: %s",
transport->path, err.message);
}
} else {
spa_log_info(monitor->log, "Transport %s released", transport->path);
p = send_with_reply(monitor->conn, m, NULL, NULL);
if (!p)
return NULL;
pending = calloc(1, sizeof(*pending));
if (!pending) {
dbus_pending_call_block(p);
dbus_pending_call_unref(p);
return NULL;
}
return 0;
pending->pending = p;
pending->transport = transport;
pending->is_idle = is_idle;
return pending;
}
static int transport_release(void *data)
{
struct spa_bt_transport *transport = data;
struct spa_bt_monitor *monitor = transport->monitor;
struct spa_bt_transport *t;
struct spa_list pending = SPA_LIST_INIT(&pending);
struct pending_release *item;
/*
* XXX: When as BAP Central, release CIS in a CIG when the last transport
* XXX: goes away.
*/
if (transport->bap_initiator) {
struct spa_bt_transport *t;
/* Check if another transport is alive */
if (another_cig_transport_active(transport)) {
spa_log_debug(monitor->log, "Releasing %s: wait for CIG %d",
@ -4200,15 +4392,61 @@ static int transport_release(void *data)
spa_log_debug(monitor->log, "Release CIG %d: transport %s",
transport->bap_cig, t->path);
if (t->fd >= 0)
do_transport_release(t);
if (t->fd >= 0) {
item = do_transport_release(t);
if (item)
spa_list_append(&pending, &item->link);
}
}
spa_log_debug(monitor->log, "Release CIG %d: transport %s",
transport->bap_cig, transport->path);
}
return do_transport_release(data);
item = do_transport_release(transport);
if (item)
spa_list_append(&pending, &item->link);
spa_list_consume(item, &pending, link) {
struct spa_bt_transport *t = item->transport;
bool is_idle = item->is_idle;
DBusPendingCall *p = item->pending;
spa_autoptr(DBusMessage) r = NULL;
spa_auto(DBusError) err = DBUS_ERROR_INIT;
spa_list_remove(&item->link);
free(item);
if (!p)
continue;
dbus_pending_call_block(p);
r = steal_reply_and_unref(&p);
if (r == NULL) {
if (is_idle) {
/* XXX: The fd always needs to be closed. However, Release()
* XXX: apparently doesn't need to be called on idle transports
* XXX: and fails. We call it just to be sure (e.g. in case
* XXX: there's a race with updating the property), but tone down the error.
*/
spa_log_debug(monitor->log, "Failed to release idle transport %s: %s",
t->path, err.message);
} else if (spa_streq(err.name, DBUS_ERROR_UNKNOWN_METHOD) ||
spa_streq(err.name, DBUS_ERROR_UNKNOWN_OBJECT)) {
/* Transport disappeared */
spa_log_debug(monitor->log, "Failed to release (gone) transport %s: %s",
t->path, err.message);
} else {
spa_log_error(monitor->log, "Failed to release transport %s: %s",
t->path, err.message);
}
} else {
spa_log_info(monitor->log, "Transport %s released", t->path);
}
}
return 0;
}
static int transport_set_delay(void *data, int64_t delay_nsec)
@ -4480,7 +4718,7 @@ static bool codec_switch_configure_a2dp(struct spa_bt_codec_switch *sw, const ch
}
res = codec->select_config(codec, sink ? MEDIA_CODEC_FLAG_SINK : 0, ep->capabilities, ep->capabilities_len,
&monitor->default_audio_info, &monitor->global_settings, config);
&monitor->default_audio_info, &monitor->global_settings, config, NULL);
if (res < 0) {
spa_log_error(monitor->log, "media codec switch %p: incompatible capabilities (%d)",
sw, res);
@ -5037,6 +5275,10 @@ static DBusHandlerResult endpoint_set_configuration(DBusConnection *conn,
spa_log_error(monitor->log, "invalid transport configuration");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
if (info.info.raw.channels > MAX_CHANNELS) {
spa_log_error(monitor->log, "too many channels in transport");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
transport->n_channels = info.info.raw.channels;
memcpy(transport->channels, info.info.raw.position,
transport->n_channels * sizeof(uint32_t));
@ -5301,6 +5543,42 @@ out:
return err;
}
static void append_supported_features(DBusMessageIter *dict, struct bap_features *features)
{
const char *key = "SupportedFeatures";
DBusMessageIter dict_entry, dict_variant, value_dict;
DBusMessageIter entry, variant, array;
const char *uuid, *name;
size_t i;
dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry);
dbus_message_iter_append_basic(&dict_entry, DBUS_TYPE_STRING, &key);
dbus_message_iter_open_container(&dict_entry, DBUS_TYPE_VARIANT, "a{sv}", &dict_variant);
dbus_message_iter_open_container(&dict_variant, DBUS_TYPE_ARRAY, "{sv}", &value_dict);
i = 0;
while ((uuid = bap_features_get_uuid(features, i))) {
dbus_message_iter_open_container(&value_dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry);
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &uuid);
dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "as", &variant);
dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "s", &array);
while ((name = bap_features_get_name(features, i, uuid))) {
dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &name);
++i;
}
dbus_message_iter_close_container(&variant, &array);
dbus_message_iter_close_container(&entry, &variant);
dbus_message_iter_close_container(&value_dict, &entry);
}
dbus_message_iter_close_container(&dict_variant, &value_dict);
dbus_message_iter_close_container(&dict_entry, &dict_variant);
dbus_message_iter_close_container(dict, &dict_entry);
}
static void append_media_object(struct spa_bt_monitor *monitor, DBusMessageIter *iter, const char *endpoint,
const char *uuid, uint8_t codec_id, uint8_t *caps, size_t caps_size)
{
@ -5348,6 +5626,9 @@ static void append_media_object(struct spa_bt_monitor *monitor, DBusMessageIter
append_basic_variant_dict_entry(&dict, "SupportedContext", DBUS_TYPE_UINT16, "q", &supported_contexts);
}
if (spa_bt_profile_from_uuid(uuid) & SPA_BT_PROFILE_BAP_AUDIO)
append_supported_features(&dict, &monitor->bap_features);
dbus_message_iter_close_container(&entry, &dict);
dbus_message_iter_close_container(&array, &entry);
dbus_message_iter_close_container(&object, &array);
@ -5577,10 +5858,10 @@ static int register_media_endpoint(struct spa_bt_monitor *monitor,
static int register_media_application(struct spa_bt_monitor * monitor)
{
const struct media_codec * const * const media_codecs = monitor->media_codecs;
const DBusObjectPathVTable vtable_object_manager_a2dp = {
static const DBusObjectPathVTable vtable_object_manager_a2dp = {
.message_function = object_manager_handler_a2dp,
};
const DBusObjectPathVTable vtable_object_manager_bap = {
static const DBusObjectPathVTable vtable_object_manager_bap = {
.message_function = object_manager_handler_bap,
};
@ -5811,6 +6092,7 @@ static void configure_bis(struct spa_bt_monitor *monitor,
int sync_cte_type = 0;
int sync_timeout = 2000;
int timeout = 2000;
int ret;
/* Configure each BIS from a BIG */
spa_list_for_each(metadata_entry, &bis->metadata_list, link) {
@ -5834,7 +6116,12 @@ static void configure_bis(struct spa_bt_monitor *monitor,
setting_items[1] = SPA_DICT_ITEM_INIT("preset", bis->qos_preset);
settings = SPA_DICT_INIT(setting_items, 2);
codec->get_bis_config(codec, caps, &caps_size, &settings, &qos);
caps_size = sizeof(caps);
ret = codec->get_bis_config(codec, caps, &caps_size, &settings, &qos);
if (ret < 0) {
spa_log_warn(monitor->log, "Getting BIS config failed");
return;
}
msg = dbus_message_new_method_call(BLUEZ_SERVICE,
object_path,
@ -6563,6 +6850,8 @@ static int impl_clear(struct spa_handle *handle)
monitor->backend = NULL;
monitor->backend_selection = BACKEND_NATIVE;
bap_features_clear(&monitor->bap_features);
spa_bt_quirks_destroy(monitor->quirks);
free_media_codecs(monitor->media_codecs);
@ -6850,7 +7139,7 @@ static void parse_bap_locations(struct spa_bt_monitor *this, const struct spa_di
const char *key, uint32_t *value)
{
const char *str;
uint32_t position[SPA_AUDIO_MAX_CHANNELS];
uint32_t position[MAX_CHANNELS];
uint32_t n_channels;
uint32_t locations;
unsigned int i, j;
@ -6861,7 +7150,8 @@ static void parse_bap_locations(struct spa_bt_monitor *this, const struct spa_di
if (spa_atou32(str, value, 0))
return;
if (!spa_audio_parse_position(str, strlen(str), position, &n_channels)) {
if (!spa_audio_parse_position_n(str, strlen(str), position,
SPA_N_ELEMENTS(position), &n_channels)) {
spa_log_error(this->log, "property %s '%s' is not valid position array", key, str);
return;
}
@ -6875,10 +7165,36 @@ static void parse_bap_locations(struct spa_bt_monitor *this, const struct spa_di
*value = locations;
}
static void bap_feature_parse(struct spa_bt_monitor *this, const char *uuid, const char *str)
{
struct spa_json it;
char name[64];
if (!str)
return;
if (spa_json_begin_array_relax(&it, str, strlen(str)) < 0)
return;
while (spa_json_get_string(&it, name, sizeof(name)) > 0) {
if (bap_features_add(&this->bap_features, uuid, name))
spa_log_debug(this->log, "advertise BAP feature %s %s", uuid, name);
}
}
static void parse_bap_features(struct spa_bt_monitor *this, const struct spa_dict *info)
{
static const char *const tmap_uuid = "00001855-0000-1000-8000-00805f9b34fb";
static const char *const gmap_uuid = "00001858-0000-1000-8000-00805f9b34fb";
bap_feature_parse(this, tmap_uuid, spa_dict_lookup(info, "bluez5.bap-server-tmap-features"));
bap_feature_parse(this, gmap_uuid, spa_dict_lookup(info, "bluez5.bap-server-gmap-features"));
}
static void parse_bap_server(struct spa_bt_monitor *this, const struct spa_dict *info)
{
this->bap_sink_locations = BAP_CHANNEL_ALL;
this->bap_source_locations = BAP_CHANNEL_ALL;
this->bap_sink_locations = BAP_CHANNEL_FL | BAP_CHANNEL_FR;
this->bap_source_locations = BAP_CHANNEL_FL | BAP_CHANNEL_FR;
this->bap_sink_contexts = this->bap_sink_supported_contexts = BAP_CONTEXT_ALL;
this->bap_source_contexts = this->bap_source_supported_contexts = (BAP_CONTEXT_UNSPECIFIED | BAP_CONTEXT_CONVERSATIONAL |
BAP_CONTEXT_MEDIA | BAP_CONTEXT_GAME);
@ -6893,6 +7209,8 @@ static void parse_bap_server(struct spa_bt_monitor *this, const struct spa_dict
parse_bap_locations(this, info, "bluez5.bap-server-capabilities.source.locations", &this->bap_source_locations);
spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.source.contexts"), &this->bap_source_contexts, 0);
spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.source.supported-contexts"), &this->bap_source_supported_contexts, 0);
parse_bap_features(this, info);
}
static void get_global_settings(struct spa_bt_monitor *this, const struct spa_dict *dict)

View file

@ -12,6 +12,7 @@
#include <spa/support/log.h>
#include <spa/utils/type.h>
#include <spa/utils/json.h>
#include <spa/utils/keys.h>
#include <spa/utils/names.h>
#include <spa/utils/string.h>
@ -26,6 +27,7 @@
#include <spa/pod/parser.h>
#include <spa/param/param.h>
#include <spa/param/audio/raw.h>
#include <spa/param/audio/raw-utils.h>
#include <spa/param/bluetooth/audio.h>
#include <spa/param/bluetooth/type-info.h>
#include <spa/debug/pod.h>
@ -38,7 +40,7 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.device");
#undef SPA_LOG_TOPIC_DEFAULT
#define SPA_LOG_TOPIC_DEFAULT &log_topic
#define MAX_NODES (2*SPA_AUDIO_MAX_CHANNELS)
#define MAX_NODES (2*MAX_CHANNELS)
#define DEVICE_ID_SOURCE 0
#define DEVICE_ID_SINK 1
@ -99,9 +101,9 @@ struct node {
unsigned int offload_acquired:1;
uint32_t n_channels;
int64_t latency_offset;
uint32_t channels[SPA_AUDIO_MAX_CHANNELS];
float volumes[SPA_AUDIO_MAX_CHANNELS];
float soft_volumes[SPA_AUDIO_MAX_CHANNELS];
uint32_t channels[MAX_CHANNELS];
float volumes[MAX_CHANNELS];
float soft_volumes[MAX_CHANNELS];
};
struct dynamic_node
@ -129,8 +131,8 @@ struct device_set {
bool leader;
uint32_t sinks;
uint32_t sources;
struct device_set_member sink[SPA_AUDIO_MAX_CHANNELS];
struct device_set_member source[SPA_AUDIO_MAX_CHANNELS];
struct device_set_member sink[MAX_CHANNELS];
struct device_set_member source[MAX_CHANNELS];
};
struct impl {
@ -182,7 +184,7 @@ static void init_node(struct impl *this, struct node *node, uint32_t id)
spa_zero(*node);
node->id = id;
for (i = 0; i < SPA_AUDIO_MAX_CHANNELS; i++) {
for (i = 0; i < MAX_CHANNELS; i++) {
node->volumes[i] = 1.0f;
node->soft_volumes[i] = 1.0f;
}
@ -476,6 +478,7 @@ static void get_channels(struct spa_bt_transport *t, bool a2dp_duplex, uint32_t
*n_channels = info.info.raw.channels;
memcpy(channels, info.info.raw.position,
info.info.raw.channels * sizeof(uint32_t));
}
static const char *get_channel_name(uint32_t channel)
@ -546,7 +549,7 @@ static void emit_device_set_node(struct impl *this, uint32_t id)
if (node->channels[k] == t->channels[j])
break;
}
if (k == node->n_channels && node->n_channels < SPA_AUDIO_MAX_CHANNELS)
if (k == node->n_channels && node->n_channels < MAX_CHANNELS)
node->channels[node->n_channels++] = t->channels[j];
}
}
@ -2413,7 +2416,7 @@ static struct spa_pod *build_route(struct impl *this, struct spa_pod_builder *b,
default:
name_prefix = "bluetooth";
description = _("Bluetooth");
hfp_description = _("Bluetooth (HFP)");
hfp_description = _("Bluetooth Handsfree");
port_type = "bluetooth";
break;
}
@ -2674,11 +2677,24 @@ static struct spa_pod *build_prop_info_codec(struct impl *this, struct spa_pod_b
static struct spa_pod *build_props(struct impl *this, struct spa_pod_builder *b, uint32_t id)
{
struct props *p = &this->props;
struct spa_pod_frame f[2];
struct spa_pod *param;
return spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_Props, id,
SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(p->codec),
SPA_PROP_bluetoothOffloadActive, SPA_POD_Bool(p->offload_active));
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Props, id);
spa_pod_builder_add(b,
SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(p->codec),
SPA_PROP_bluetoothOffloadActive, SPA_POD_Bool(p->offload_active),
0);
spa_pod_builder_prop(b, SPA_PROP_params, 0);
spa_pod_builder_push_struct(b, &f[1]);
spa_pod_builder_string(b, "bluez5.disable-dummy-call");
spa_pod_builder_bool(b, this->bt_dev->disable_dummy_call);
spa_pod_builder_pop(b, &f[1]);
param = spa_pod_builder_pop(b, &f[0]);
return param;
}
static int impl_enum_params(void *object, int seq,
@ -2946,8 +2962,8 @@ static int apply_device_props(struct impl *this, struct node *node, struct spa_p
struct spa_pod_prop *prop;
struct spa_pod_object *obj = (struct spa_pod_object *) props;
int changed = 0;
float volumes[SPA_AUDIO_MAX_CHANNELS];
uint32_t channels[SPA_AUDIO_MAX_CHANNELS];
float volumes[MAX_CHANNELS];
uint32_t channels[MAX_CHANNELS];
uint32_t n_volumes = 0, SPA_UNUSED n_channels = 0;
int64_t latency_offset = 0;
@ -2972,11 +2988,11 @@ static int apply_device_props(struct impl *this, struct node *node, struct spa_p
break;
case SPA_PROP_channelVolumes:
n_volumes = spa_pod_copy_array(&prop->value, SPA_TYPE_Float,
volumes, SPA_AUDIO_MAX_CHANNELS);
volumes, SPA_N_ELEMENTS(volumes));
break;
case SPA_PROP_channelMap:
n_channels = spa_pod_copy_array(&prop->value, SPA_TYPE_Id,
channels, SPA_AUDIO_MAX_CHANNELS);
channels, SPA_N_ELEMENTS(channels));
break;
case SPA_PROP_latencyOffsetNsec:
if (spa_pod_get_long(&prop->value, &latency_offset) == 0) {
@ -3015,6 +3031,47 @@ static void apply_prop_offload_active(struct impl *this, bool active)
}
}
static int parse_prop_params(struct impl *this, struct spa_pod *params)
{
struct spa_pod_parser prs;
struct spa_pod_frame f;
int changed = 0;
if (params == NULL)
return 0;
spa_pod_parser_pod(&prs, params);
if (spa_pod_parser_push_struct(&prs, &f) < 0)
return 0;
while (true) {
const char *name;
struct spa_pod *pod;
if (spa_pod_parser_get_string(&prs, &name) < 0)
break;
if (spa_pod_parser_get_pod(&prs, &pod) < 0)
break;
if (spa_streq(name, "bluez5.disable-dummy-call") && spa_pod_is_bool(pod)) {
bool disable_dummy_call = SPA_POD_VALUE(struct spa_pod_bool, pod);
spa_log_info(this->log, "key:'%s' val:'%u'", name, disable_dummy_call);
this->bt_dev->disable_dummy_call = disable_dummy_call;
} else
continue;
changed++;
}
if (changed > 0) {
this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS;
this->params[IDX_Props].user++;
}
return changed;
}
static int impl_set_param(void *object,
uint32_t id, uint32_t flags,
const struct spa_pod *param)
@ -3091,6 +3148,7 @@ static int impl_set_param(void *object,
{
uint32_t codec_id = SPA_ID_INVALID;
bool offload_active = this->props.offload_active;
struct spa_pod *params = NULL;
if (param == NULL)
return 0;
@ -3098,7 +3156,8 @@ static int impl_set_param(void *object,
if ((res = spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL,
SPA_PROP_bluetoothAudioCodec, SPA_POD_OPT_Id(&codec_id),
SPA_PROP_bluetoothOffloadActive, SPA_POD_OPT_Bool(&offload_active))) < 0) {
SPA_PROP_bluetoothOffloadActive, SPA_POD_OPT_Bool(&offload_active),
SPA_PROP_params, SPA_POD_OPT_Pod(&params))) < 0) {
spa_log_warn(this->log, "can't parse props");
spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param);
return res;
@ -3106,10 +3165,15 @@ static int impl_set_param(void *object,
spa_log_debug(this->log, "setting props codec:%d offload:%d", (int)codec_id, (int)offload_active);
parse_prop_params(this, params);
apply_prop_offload_active(this, offload_active);
if (codec_id == SPA_ID_INVALID)
if (codec_id == SPA_ID_INVALID) {
this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL;
emit_info(this, false);
return 0;
}
if (this->profile == DEVICE_PROFILE_A2DP || profile_is_bap(this->profile) ||
this->profile == DEVICE_PROFILE_ASHA || this->profile == DEVICE_PROFILE_HSP_HFP) {
@ -3247,6 +3311,13 @@ impl_init(const struct spa_handle_factory *factory,
if ((profiles = spa_bt_profiles_from_json_array(str)) >= 0)
this->bt_dev->hw_volume_profiles = profiles;
}
if ((str = spa_dict_lookup(info, "bluez5.disable-dummy-call")) != NULL) {
bool value;
if (spa_json_parse_bool(str, strlen(str), &value) > 0)
this->bt_dev->disable_dummy_call = value;
}
}
this->device.iface = SPA_INTERFACE_INIT(

View file

@ -157,6 +157,8 @@ extern "C" {
#define SPA_BT_NO_BATTERY ((uint8_t)255)
#define MAX_CHANNELS (SPA_AUDIO_MAX_CHANNELS)
enum spa_bt_media_direction {
SPA_BT_MEDIA_SOURCE,
SPA_BT_MEDIA_SINK,
@ -571,6 +573,8 @@ struct spa_bt_device {
const struct media_codec *preferred_codec;
uint32_t preferred_profiles;
bool disable_dummy_call;
};
struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path);
@ -678,7 +682,7 @@ struct spa_bt_transport {
struct spa_list bap_transport_linked;
uint32_t n_channels;
uint32_t channels[SPA_AUDIO_MAX_CHANNELS];
uint32_t channels[MAX_CHANNELS];
struct spa_bt_transport_volume volumes[SPA_BT_VOLUME_ID_TERM];

View file

@ -34,7 +34,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
struct spa_pod_builder *b, struct spa_pod **param)
{
struct spa_pod_frame f[1];
const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO };
const uint32_t position[1] = { SPA_AUDIO_CHANNEL_MONO };
const int channels = 1;
spa_assert(caps == NULL && caps_size == 0);
@ -47,7 +47,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16_LE),
SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_ENUM_Int(1, 8000),
SPA_FORMAT_AUDIO_rate, SPA_POD_Int(8000),
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels),
SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t),
SPA_TYPE_Id, channels, position),

View file

@ -39,7 +39,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
struct spa_pod_builder *b, struct spa_pod **param)
{
struct spa_pod_frame f[1];
const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO };
const uint32_t position[1] = { SPA_AUDIO_CHANNEL_MONO };
const int channels = 1;
spa_assert(caps == NULL && caps_size == 0);
@ -52,7 +52,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32),
SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_ENUM_Int(1, 24000),
SPA_FORMAT_AUDIO_rate, SPA_POD_Int(24000),
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels),
SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t),
SPA_TYPE_Id, channels, position),

View file

@ -42,7 +42,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
struct spa_pod_builder *b, struct spa_pod **param)
{
struct spa_pod_frame f[1];
const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO };
const uint32_t position[1] = { SPA_AUDIO_CHANNEL_MONO };
const int channels = 1;
spa_assert(caps == NULL && caps_size == 0);
@ -55,7 +55,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_F32),
SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_ENUM_Int(1, 32000),
SPA_FORMAT_AUDIO_rate, SPA_POD_Int(32000),
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels),
SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t),
SPA_TYPE_Id, channels, position),

View file

@ -49,7 +49,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
struct spa_pod_builder *b, struct spa_pod **param)
{
struct spa_pod_frame f[1];
const uint32_t position[SPA_AUDIO_MAX_CHANNELS] = { SPA_AUDIO_CHANNEL_MONO };
const uint32_t position[1] = { SPA_AUDIO_CHANNEL_MONO };
const int channels = 1;
spa_assert(caps == NULL && caps_size == 0);
@ -62,7 +62,7 @@ static int codec_enum_config(const struct media_codec *codec, uint32_t flags,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_audio),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
SPA_FORMAT_AUDIO_format, SPA_POD_Id(SPA_AUDIO_FORMAT_S16),
SPA_FORMAT_AUDIO_rate, SPA_POD_CHOICE_ENUM_Int(1, 16000),
SPA_FORMAT_AUDIO_rate, SPA_POD_Int(16000),
SPA_FORMAT_AUDIO_channels, SPA_POD_Int(channels),
SPA_FORMAT_AUDIO_position, SPA_POD_Array(sizeof(uint32_t),
SPA_TYPE_Id, channels, position),

View file

@ -309,6 +309,7 @@ static void group_on_timeout(struct spa_source *source)
if (stream->this.size == 0) {
spa_log_debug(group->log, "%p: ISO group:%u miss fd:%d",
group, group->id, stream->fd);
stream->this.resync = true;
if (stream_silence(stream) < 0) {
fail = true;
continue;
@ -625,6 +626,17 @@ int spa_bt_iso_io_recv_errqueue(struct spa_bt_iso_io *this)
struct stream *stream = SPA_CONTAINER_OF(this, struct stream, this);
struct group *group = stream->group;
if (!stream->sink) {
struct stream *s;
spa_list_for_each(s, &group->streams, link) {
if (s->sink && s->fd == stream->fd) {
stream = s;
break;
}
}
}
return spa_bt_latency_recv_errqueue(&stream->tx_latency, stream->fd, group->log);
}

View file

@ -8,6 +8,8 @@
*
*/
#include <bluetooth/bluetooth.h>
#include <spa/utils/string.h>
#include <spa/utils/cleanup.h>
@ -90,7 +92,7 @@ bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_
if (caps == NULL)
return false;
res = codec->select_config(codec, 0, caps, caps_size, info, global_settings, config);
res = codec->select_config(codec, 0, caps, caps_size, info, global_settings, config, NULL);
if (res < 0)
return false;
@ -100,6 +102,52 @@ bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_
return ((size_t)res == caps_size);
}
void ltv_writer_data(struct ltv_writer *w, uint8_t type, void* value, size_t len)
{
struct ltv *ltv;
size_t sz = (size_t)w->size + sizeof(struct ltv) + len;
if (!w->buf || sz > w->max_size || (uint16_t)sz != sz) {
w->buf = NULL;
return;
}
ltv = SPA_PTROFF(w->buf, w->size, struct ltv);
ltv->len = len + 1;
ltv->type = type;
memcpy(ltv->value, value, len);
w->size = sz;
}
void ltv_writer_uint8(struct ltv_writer *w, uint8_t type, uint8_t v)
{
ltv_writer_data(w, type, &v, sizeof(v));
}
void ltv_writer_uint16(struct ltv_writer *w, uint8_t type, uint16_t value)
{
uint16_t v = htobs(value);
ltv_writer_data(w, type, &v, sizeof(v));
}
void ltv_writer_uint32(struct ltv_writer *w, uint8_t type, uint32_t value)
{
uint32_t v = htobl(value);
ltv_writer_data(w, type, &v, sizeof(v));
}
int ltv_writer_end(struct ltv_writer *w)
{
if (!w->buf)
return -ENOSPC;
w->buf = NULL;
return w->size;
}
#ifdef CODEC_PLUGIN
struct impl {

View file

@ -15,6 +15,7 @@
#include <spa/pod/pod.h>
#include <spa/pod/builder.h>
#include <spa/support/log.h>
#include <spa/debug/log.h>
#include "a2dp-codec-caps.h"
#include "bap-codec-caps.h"
@ -26,7 +27,7 @@
#define SPA_TYPE_INTERFACE_Bluez5CodecMedia SPA_TYPE_INFO_INTERFACE_BASE "Bluez5:Codec:Media:Private"
#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 15
#define SPA_VERSION_BLUEZ5_CODEC_MEDIA 16
struct spa_bluez5_codec_a2dp {
struct spa_interface iface;
@ -93,7 +94,7 @@ struct media_codec {
* called again to parse the remaining data. */
int (*get_bis_config)(const struct media_codec *codec, uint8_t *caps,
uint8_t *caps_size, struct spa_dict *settings,
uint8_t *caps_size, struct spa_dict *settings,
struct bap_codec_qos *qos);
/** If fill_caps is NULL, no endpoint is registered (for sharing with another codec). */
@ -103,7 +104,8 @@ struct media_codec {
int (*select_config) (const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE]);
const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data);
int (*enum_config) (const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size, uint32_t id, uint32_t idx,
struct spa_pod_builder *builder, struct spa_pod **param);
@ -111,9 +113,12 @@ struct media_codec {
const void *caps, size_t caps_size,
struct spa_audio_info *info);
int (*get_qos)(const struct media_codec *codec,
const void *config, size_t config_size,
const struct bap_endpoint_qos *endpoint_qos,
struct bap_codec_qos *qos, const struct spa_dict *settings);
const void *config_data,
struct bap_codec_qos *qos);
int (*get_metadata)(const struct media_codec *codec, const void *config_data,
uint8_t *meta, size_t meta_max_size);
void (*free_config_data)(const struct media_codec *codec, void *config_data);
/** qsort comparison sorting caps in order of preference for the codec.
* Used in codec switching to select best remote endpoints.
@ -264,4 +269,44 @@ bool media_codec_check_caps(const struct media_codec *codec, unsigned int codec_
const void *caps, size_t caps_size, const struct media_codec_audio_info *info,
const struct spa_dict *global_settings);
struct __attribute__((packed)) ltv {
uint8_t len;
uint8_t type;
uint8_t value[];
};
struct ltv_writer {
void *buf;
uint16_t size;
size_t max_size;
};
#define LTV_WRITER(ptr, max) ((struct ltv_writer) { .buf = (ptr), .max_size = (max) })
void ltv_writer_data(struct ltv_writer *w, uint8_t type, void* value, size_t len);
void ltv_writer_uint8(struct ltv_writer *w, uint8_t type, uint8_t v);
void ltv_writer_uint16(struct ltv_writer *w, uint8_t type, uint16_t value);
void ltv_writer_uint32(struct ltv_writer *w, uint8_t type, uint32_t value);
int ltv_writer_end(struct ltv_writer *w);
static inline const struct ltv *ltv_next(const void **data, size_t *size)
{
const struct ltv *ltv;
if (*size == 0) {
*data = NULL;
return NULL;
}
if (*size < sizeof(struct ltv))
return NULL;
ltv = *data;
if (ltv->len >= *size)
return NULL;
*data = SPA_PTROFF(*data, ltv->len + 1, void);
*size -= ltv->len + 1;
return ltv;
}
#endif

View file

@ -2139,7 +2139,7 @@ static int port_set_format(struct impl *this, struct port *port,
if (info.info.raw.rate == 0 ||
info.info.raw.channels == 0 ||
info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS)
info.info.raw.channels > MAX_CHANNELS)
return -EINVAL;
if (this->transport && this->transport->iso_io) {

View file

@ -613,6 +613,10 @@ static void handle_errqueue(struct impl *this)
{
int res;
if (this->transport && this->transport->iso_io)
if (spa_bt_iso_io_recv_errqueue(this->transport->iso_io) == 0)
return;
/* iso-io/media-sink use these for TX latency.
* Someone else should be reading them, so drop
* only after yielding.
@ -1439,7 +1443,7 @@ static int port_set_format(struct impl *this, struct port *port,
if (info.info.raw.rate == 0 ||
info.info.raw.channels == 0 ||
info.info.raw.channels > SPA_AUDIO_MAX_CHANNELS)
info.info.raw.channels > MAX_CHANNELS)
return -EINVAL;
port->frame_size = info.info.raw.channels;

View file

@ -10,6 +10,10 @@
#include "modemmanager.h"
SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.modemmanager");
#undef SPA_LOG_TOPIC_DEFAULT
#define SPA_LOG_TOPIC_DEFAULT &log_topic
#define DBUS_INTERFACE_OBJECTMANAGER "org.freedesktop.DBus.ObjectManager"
struct modem {
@ -32,6 +36,8 @@ struct impl {
struct modem modem;
struct spa_list call_list;
bool pts;
};
struct dbus_cmd_data {
@ -412,6 +418,26 @@ static void mm_get_managed_objects_reply(DBusPendingCall *pending, void *user_da
}
}
static bool mm_get_managed_objects(struct impl *this)
{
spa_autoptr(DBusMessage) m = dbus_message_new_method_call(MM_DBUS_SERVICE,
"/org/freedesktop/ModemManager1",
DBUS_INTERFACE_OBJECTMANAGER,
"GetManagedObjects");
if (m == NULL)
return false;
dbus_message_set_auto_start(m, false);
this->pending = send_with_reply(this->conn, m, mm_get_managed_objects_reply, this);
if (!this->pending) {
spa_log_error(this->log, "dbus call failure");
return false;
}
return true;
}
static void call_free(struct call *call)
{
spa_list_remove(&call->link);
@ -488,8 +514,12 @@ static DBusHandlerResult mm_filter_cb(DBusConnection *bus, DBusMessage *m, void
mm_clean_modem(this);
}
if (new_owner && *new_owner)
if (new_owner && *new_owner) {
spa_log_debug(this->log, "ModemManager daemon appeared (%s)", new_owner);
if (!mm_get_managed_objects(this))
goto finish;
}
}
} else if (dbus_message_is_signal(m, DBUS_INTERFACE_OBJECTMANAGER, DBUS_SIGNAL_INTERFACES_ADDED)) {
DBusMessageIter arg_i;
@ -916,8 +946,13 @@ bool mm_do_call(void *modemmanager, const char* number, void *user_data, enum cm
spa_autofree struct dbus_cmd_data *data = NULL;
spa_autoptr(DBusMessage) m = NULL;
DBusMessageIter iter, dict;
size_t i = 0;
for (size_t i = 0; number[i]; i++) {
/* Allow memory dial for PTS tests HFP/AG/OCM/BV-01-C and HFP/AG/OCM/BV-02-C */
if (this->pts && number[0] == '>')
i++;
for (; number[i]; i++) {
if (!is_valid_dial_string_char(number[i])) {
spa_log_warn(this->log, "Call creation canceled, invalid character found in dial string: %c", number[i]);
if (error)
@ -1050,6 +1085,8 @@ void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_d
{
const char *modem_device_str = NULL;
bool modem_device_found = false;
const char *pts_str = NULL;
bool pts = false;
spa_assert(log);
spa_assert(dbus_connection);
@ -1059,6 +1096,9 @@ void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_d
if (!spa_streq(modem_device_str, "none"))
modem_device_found = true;
}
if ((pts_str = spa_dict_lookup(info, "bluez5.hfphsp-backend-native-pts")) != NULL) {
pts = spa_atob(pts_str);
}
}
if (!modem_device_found) {
spa_log_info(log, "No modem allowed, doesn't link to ModemManager");
@ -1076,25 +1116,14 @@ void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_d
if (modem_device_str && !spa_streq(modem_device_str, "any"))
this->allowed_modem_device = strdup(modem_device_str);
spa_list_init(&this->call_list);
this->pts = pts;
if (add_filters(this) < 0)
return NULL;
spa_autoptr(DBusMessage) m = dbus_message_new_method_call(MM_DBUS_SERVICE,
"/org/freedesktop/ModemManager1",
DBUS_INTERFACE_OBJECTMANAGER,
"GetManagedObjects");
if (m == NULL)
if (!mm_get_managed_objects(this))
return NULL;
dbus_message_set_auto_start(m, false);
this->pending = send_with_reply(this->conn, m, mm_get_managed_objects_reply, this);
if (!this->pending) {
spa_log_error(this->log, "dbus call failure");
return NULL;
}
return spa_steal_ptr(this);
}

View file

@ -247,11 +247,12 @@ static void update_properties(struct impl *impl, bool send_signal)
struct spa_bt_player *spa_bt_player_new(void *dbus_connection, struct spa_log *log)
{
struct impl *impl;
const DBusObjectPathVTable vtable = {
static const DBusObjectPathVTable vtable = {
.message_function = player_handler,
};
struct impl *impl;
spa_log_topic_init(log, &log_topic);
impl = calloc(1, sizeof(struct impl));

Some files were not shown because too many files have changed in this diff Show more