Compare commits

..

157 commits

Author SHA1 Message Date
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
133 changed files with 7556 additions and 1544 deletions

121
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
@ -196,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

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

@ -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.
@ -1018,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
@ -1137,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',

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"

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-11-04 15:35+0000\n"
"PO-Revision-Date: 2025-11-05 07:47+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"
@ -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:1096
#: 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:1103
#: 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:1121
#: 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:1141
#: src/tools/pw-cat.c:1133
msgid ""
" -p, --playback Playback mode\n"
" -r, --record Recording mode\n"

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_N_ELEMENTS(info->position)))
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

@ -75,7 +75,7 @@ static const struct spa_type_audio_layout_info {
{ "7.1", { SPA_AUDIO_LAYOUT_7_1 } },
{ "7.1W", { SPA_AUDIO_LAYOUT_7_1W } },
{ "7.1WR", { SPA_AUDIO_LAYOUT_7_1WR } },
{ NULL, },
{ NULL, { 0, { SPA_AUDIO_CHANNEL_UNKNOWN } } },
};
SPA_API_AUDIO_LAYOUT_TYPES int

View file

@ -48,8 +48,10 @@ spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_in
if (info->channels > max_position)
return -ECHRNG;
if (position == NULL ||
spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->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

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

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

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

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

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

@ -1137,7 +1137,7 @@ 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 {
@ -1146,32 +1146,38 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask)
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;

View file

@ -2036,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)
@ -2063,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
@ -2072,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

@ -100,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;
@ -122,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;
@ -1019,7 +1021,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
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_warn(this->log, "driver %d changed rate:%u -> %u", this->io_position->clock.id,
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);
@ -1342,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;
@ -1429,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);
@ -1478,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"))
@ -1488,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) {
@ -1495,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;
@ -2289,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;
@ -2532,6 +2553,8 @@ static int setup_convert(struct impl *this)
this->setup = true;
this->recalc = true;
sync_filter_graph(this);
return 0;
}
@ -2548,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)
@ -2568,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;

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

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

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

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

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));

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;

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 { \

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;

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 { \

View file

@ -590,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;
@ -699,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 { \

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 { \

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,30 +948,47 @@ 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, uint32_t max_position)
@ -1062,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 */
@ -1122,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)
@ -1378,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 = {
@ -1465,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

@ -77,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;
@ -131,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
@ -152,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;
};
@ -656,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;
@ -693,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;
@ -878,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;
@ -900,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;
@ -1012,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;
@ -1028,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
@ -1043,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);
@ -1059,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)
@ -1067,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;
@ -1075,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));
@ -1093,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,
@ -1113,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));
@ -1161,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;
@ -1173,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;
@ -2797,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)
@ -2805,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];
@ -2820,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 */
@ -2930,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);
}
@ -2986,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);
}
@ -3587,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);
@ -4087,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);
@ -4130,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:
@ -4146,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",
@ -4201,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)
@ -4481,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);
@ -5306,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)
{
@ -5353,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);
@ -5582,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,
};
@ -5816,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) {
@ -5839,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,
@ -6568,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);
@ -6881,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);
@ -6899,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>
@ -2676,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,
@ -3017,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)
@ -3093,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;
@ -3100,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;
@ -3108,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) {
@ -3249,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

@ -573,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);

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

@ -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.

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));

View file

@ -1148,20 +1148,23 @@ int telephony_ag_register(struct spa_bt_telephony_ag *ag)
{
struct agimpl *agimpl = SPA_CONTAINER_OF(ag, struct agimpl, this);
struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this);
char *path;
const DBusObjectPathVTable vtable = {
static const DBusObjectPathVTable vtable = {
.message_function = ag_handler,
};
path = spa_aprintf (PW_TELEPHONY_OBJECT_PATH "/ag%d", agimpl->this.id);
if (agimpl->path)
return -EBUSY;
spa_autofree char *path = spa_aprintf(PW_TELEPHONY_OBJECT_PATH "/ag%d", agimpl->this.id);
/* register object */
if (!dbus_connection_register_object_path(impl->conn, path, &vtable, agimpl)) {
spa_log_error(impl->log, "failed to register %s", path);
return -EIO;
}
agimpl->path = strdup(path);
agimpl->path = spa_steal_ptr(path);
/* notify on ObjectManager of the Manager object */
{
@ -1174,7 +1177,7 @@ int telephony_ag_register(struct spa_bt_telephony_ag *ag)
dbus_iter_append_ag_interfaces(&iter, ag);
if (!dbus_connection_send(impl->conn, msg, NULL)) {
spa_log_error(impl->log, "failed to send InterfacesAdded for %s", path);
spa_log_error(impl->log, "failed to send InterfacesAdded for %s", agimpl->path);
telephony_ag_unregister(ag);
return -EIO;
}
@ -1188,18 +1191,18 @@ int telephony_ag_register(struct spa_bt_telephony_ag *ag)
msg = dbus_message_new_signal(impl->path, OFONO_MANAGER_IFACE,
"ModemAdded");
dbus_message_iter_init_append(msg, &iter);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &agimpl->path);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sv}", &props_dict);
dbus_message_iter_close_container(&iter, &props_dict);
if (!dbus_connection_send(impl->conn, msg, NULL)) {
spa_log_error(impl->log, "failed to send ModemAdded for %s", path);
spa_log_error(impl->log, "failed to send ModemAdded for %s", agimpl->path);
telephony_ag_unregister(ag);
return -EIO;
}
}
spa_log_debug(impl->log, "registered AudioGateway: %s", path);
spa_log_debug(impl->log, "registered AudioGateway: %s", agimpl->path);
return 0;
}
@ -1646,20 +1649,23 @@ int telephony_call_register(struct spa_bt_telephony_call *call)
struct callimpl *callimpl = SPA_CONTAINER_OF(call, struct callimpl, this);
struct agimpl *agimpl = SPA_CONTAINER_OF(callimpl->this.ag, struct agimpl, this);
struct impl *impl = SPA_CONTAINER_OF(agimpl->this.telephony, struct impl, this);
char *path;
const DBusObjectPathVTable vtable = {
static const DBusObjectPathVTable vtable = {
.message_function = call_handler,
};
path = spa_aprintf ("%s/call%d", agimpl->path, callimpl->this.id);
if (callimpl->path)
return -EBUSY;
spa_autofree char *path = spa_aprintf("%s/call%d", agimpl->path, callimpl->this.id);
/* register object */
if (!dbus_connection_register_object_path(impl->conn, path, &vtable, callimpl)) {
spa_log_error(impl->log, "failed to register %s", path);
return -EIO;
}
callimpl->path = strdup(path);
callimpl->path = spa_steal_ptr(path);
/* notify on ObjectManager of the AudioGateway object */
{
@ -1671,7 +1677,7 @@ int telephony_call_register(struct spa_bt_telephony_call *call)
DBUS_INTERFACE_OBJECT_MANAGER,
"InterfacesAdded");
dbus_message_iter_init_append(msg, &iter);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &callimpl->path);
dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sa{sv}}", &dict);
dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry);
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &interface);
@ -1680,7 +1686,7 @@ int telephony_call_register(struct spa_bt_telephony_call *call)
dbus_message_iter_close_container(&iter, &dict);
if (!dbus_connection_send(impl->conn, msg, NULL)) {
spa_log_error(impl->log, "failed to send InterfacesAdded for %s", path);
spa_log_error(impl->log, "failed to send InterfacesAdded for %s", callimpl->path);
telephony_call_unregister(call);
return -EIO;
}
@ -1695,11 +1701,11 @@ int telephony_call_register(struct spa_bt_telephony_call *call)
OFONO_VOICE_CALL_MANAGER_IFACE,
"CallAdded");
dbus_message_iter_init_append(msg, &iter);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &path);
dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH, &callimpl->path);
dbus_iter_append_call_properties(&iter, call, true);
if (!dbus_connection_send(impl->conn, msg, NULL)) {
spa_log_error(impl->log, "failed to send CallAdded for %s", path);
spa_log_error(impl->log, "failed to send CallAdded for %s", callimpl->path);
telephony_call_unregister(call);
return -EIO;
}
@ -1707,7 +1713,7 @@ int telephony_call_register(struct spa_bt_telephony_call *call)
telephony_call_commit_properties(call);
spa_log_debug(impl->log, "registered Call: %s", path);
spa_log_debug(impl->log, "registered Call: %s", callimpl->path);
return 0;
}

View file

@ -205,9 +205,10 @@ void dsp_linear_c(void *obj, float * dst,
void dsp_delay_c(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer,
uint32_t delay, float *dst, const float *src, uint32_t n_samples)
uint32_t delay, float *dst, const float *src, uint32_t n_samples,
float fb, float ff)
{
if (delay == 0) {
if (delay == 0 && fb == 0.0f && ff == 0.0f) {
dsp_copy_c(obj, dst, src, n_samples);
} else {
uint32_t w, o, i;
@ -215,10 +216,20 @@ void dsp_delay_c(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer,
w = *pos;
o = n_buffer - delay;
for (i = 0; i < n_samples; i++) {
buffer[w] = buffer[w + n_buffer] = src[i];
dst[i] = buffer[w + o];
w = w + 1 >= n_buffer ? 0 : w + 1;
if (fb == 0.0f && ff == 0.0f) {
for (i = 0; i < n_samples; i++) {
buffer[w] = buffer[w + n_buffer] = src[i];
dst[i] = buffer[w + o];
w = w + 1 >= n_buffer ? 0 : w + 1;
}
} else {
for (i = 0; i < n_samples; i++) {
float d = buffer[w + o];
float s = src[i];
buffer[w] = buffer[w + n_buffer] = s + d * fb;
dst[i] = ff * s + d;
w = w + 1 >= n_buffer ? 0 : w + 1;
}
}
*pos = w;
}

View file

@ -32,7 +32,7 @@ void dsp_biquad_run_##arch (void *obj, struct biquad *bq, uint32_t n_bq, uint32_
float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], uint32_t n_src, uint32_t n_samples)
#define MAKE_DELAY_FUNC(arch) \
void dsp_delay_##arch (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, \
uint32_t delay, float *dst, const float *src, uint32_t n_samples)
uint32_t delay, float *dst, const float *src, uint32_t n_samples, float fb, float ff)
#define MAKE_FFT_NEW_FUNC(arch) \
void *dsp_fft_new_##arch(void *obj, uint32_t size, bool real)

View file

@ -614,34 +614,70 @@ void dsp_biquad_run_sse(void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq
}
void dsp_delay_sse(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay,
float *dst, const float *src, uint32_t n_samples)
float *dst, const float *src, uint32_t n_samples, float fb, float ff)
{
__m128 t[1];
__m128 t[4];
uint32_t w = *pos;
uint32_t o = n_buffer - delay;
uint32_t n, unrolled;
if (SPA_IS_ALIGNED(src, 16) &&
SPA_IS_ALIGNED(dst, 16))
unrolled = n_samples & ~3;
else
unrolled = 0;
if (fb == 0.0f && ff == 0.0f) {
if (SPA_IS_ALIGNED(src, 16) &&
SPA_IS_ALIGNED(dst, 16) && delay >= 4)
unrolled = n_samples & ~3;
else
unrolled = 0;
for(n = 0; n < unrolled; n += 4) {
t[0] = _mm_load_ps(&src[n]);
_mm_storeu_ps(&buffer[w], t[0]);
_mm_storeu_ps(&buffer[w+n_buffer], t[0]);
t[0] = _mm_loadu_ps(&buffer[w+o]);
_mm_store_ps(&dst[n], t[0]);
w = w + 4 >= n_buffer ? 0 : w + 4;
}
for(; n < n_samples; n++) {
t[0] = _mm_load_ss(&src[n]);
_mm_store_ss(&buffer[w], t[0]);
_mm_store_ss(&buffer[w+n_buffer], t[0]);
t[0] = _mm_load_ss(&buffer[w+o]);
_mm_store_ss(&dst[n], t[0]);
w = w + 1 >= n_buffer ? 0 : w + 1;
for(n = 0; n < unrolled; n += 4) {
t[0] = _mm_load_ps(&src[n]);
_mm_storeu_ps(&buffer[w], t[0]);
_mm_storeu_ps(&buffer[w+n_buffer], t[0]);
t[0] = _mm_loadu_ps(&buffer[w+o]);
_mm_store_ps(&dst[n], t[0]);
w = w + 4 >= n_buffer ? 0 : w + 4;
}
for(; n < n_samples; n++) {
t[0] = _mm_load_ss(&src[n]);
_mm_store_ss(&buffer[w], t[0]);
_mm_store_ss(&buffer[w+n_buffer], t[0]);
t[0] = _mm_load_ss(&buffer[w+o]);
_mm_store_ss(&dst[n], t[0]);
w = w + 1 >= n_buffer ? 0 : w + 1;
}
} else {
__m128 fb0 = _mm_set1_ps(fb);
__m128 ff0 = _mm_set1_ps(ff);
if (SPA_IS_ALIGNED(src, 16) &&
SPA_IS_ALIGNED(dst, 16) && delay >= 4)
unrolled = n_samples & ~3;
else
unrolled = 0;
for(n = 0; n < unrolled; n += 4) {
t[0] = _mm_loadu_ps(&buffer[w+o]);
t[1] = _mm_load_ps(&src[n]);
t[2] = _mm_mul_ps(t[0], fb0);
t[2] = _mm_add_ps(t[2], t[1]);
_mm_storeu_ps(&buffer[w], t[2]);
_mm_storeu_ps(&buffer[w+n_buffer], t[2]);
t[2] = _mm_mul_ps(t[1], ff0);
t[2] = _mm_add_ps(t[2], t[0]);
_mm_store_ps(&dst[n], t[2]);
w = w + 4 >= n_buffer ? 0 : w + 4;
}
for(; n < n_samples; n++) {
t[0] = _mm_load_ss(&buffer[w+o]);
t[1] = _mm_load_ss(&src[n]);
t[2] = _mm_mul_ss(t[0], fb0);
t[2] = _mm_add_ss(t[2], t[1]);
_mm_store_ss(&buffer[w], t[2]);
_mm_store_ss(&buffer[w+n_buffer], t[2]);
t[2] = _mm_mul_ps(t[1], ff0);
t[2] = _mm_add_ps(t[2], t[0]);
_mm_store_ss(&dst[n], t[2]);
w = w + 1 >= n_buffer ? 0 : w + 1;
}
}
*pos = w;
}

View file

@ -58,7 +58,8 @@ struct spa_fga_dsp_methods {
float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[],
uint32_t n_src, uint32_t n_samples);
void (*delay) (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay,
float *dst, const float *src, uint32_t n_samples);
float *dst, const float *src, uint32_t n_samples,
float fb, float ff);
};
static inline void spa_fga_dsp_clear(struct spa_fga_dsp *obj, float * SPA_RESTRICT dst, uint32_t n_samples)
@ -159,10 +160,11 @@ static inline void spa_fga_dsp_biquad_run(struct spa_fga_dsp *obj,
}
static inline void spa_fga_dsp_delay(struct spa_fga_dsp *obj,
float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay,
float *dst, const float *src, uint32_t n_samples)
float *dst, const float *src, uint32_t n_samples,
float fb, float ff)
{
spa_api_method_v(spa_fga_dsp, &obj->iface, delay, 0,
buffer, pos, n_buffer, delay, dst, src, n_samples);
buffer, pos, n_buffer, delay, dst, src, n_samples, fb, ff);
}
#endif /* SPA_FGA_DSP_H */

View file

@ -704,16 +704,93 @@ struct convolver_impl {
struct convolver *conv;
};
struct finfo {
#define TYPE_INVALID 0
#define TYPE_SNDFILE 1
#define TYPE_HILBERT 2
#define TYPE_DIRAC 3
#define TYPE_IR 4
uint32_t type;
const char *filename;
#ifdef HAVE_SNDFILE
static float *read_samples_from_sf(SNDFILE *f, const SF_INFO *info, float gain, int delay,
int offset, int length, int channel, long unsigned *rate, int *n_samples) {
float *samples;
int i, n;
SF_INFO info;
SNDFILE *fs;
#endif
int channels;
int def_frames;
int max_frames;
float latency; /* latency relative to number of samples */
uint32_t rate;
const char *error;
};
static int finfo_open(const char *filename, struct finfo *info, int rate)
{
info->filename = filename;
if (spa_strstartswith(filename, "/hilbert")) {
info->channels = 1;
info->rate = rate;
info->def_frames = 64;
info->max_frames = INT_MAX;
info->type = TYPE_HILBERT;
info->latency = 0.5f;
}
else if (spa_strstartswith(filename, "/dirac")) {
info->channels = 1;
info->def_frames = 1;
info->max_frames = 1;
info->rate = rate;
info->type = TYPE_DIRAC;
info->latency = 0.0f;
}
else if (spa_strstartswith(filename, "/ir:")) {
struct spa_json it[1];
float v;
int rate;
info->channels = 1;
info->type = TYPE_IR;
info->def_frames = 0;
if (spa_json_begin_array_relax(&it[0], filename+4, strlen(filename+4)) <= 0)
return -EINVAL;
if (spa_json_get_int(&it[0], &rate) <= 0)
return -EINVAL;
info->rate = rate;
while (spa_json_get_float(&it[0], &v) > 0)
info->def_frames++;
info->max_frames = info->def_frames;
info->latency = 0.0f;
} else {
#ifdef HAVE_SNDFILE
info->fs = sf_open(filename, SFM_READ, &info->info);
if (info->fs == NULL) {
info->error = sf_strerror(NULL);
return -ENOENT;
}
info->channels = info->info.channels;
info->def_frames = info->info.frames;
info->max_frames = info->def_frames;
info->rate = info->info.samplerate;
info->type = TYPE_SNDFILE;
info->latency = 0.0f;
#else
info->error = "compiled without sndfile support, can't load samples";
return -ENOTSUP;
#endif
}
return 0;
}
static float *finfo_read_samples(struct plugin *pl, struct finfo *info, float gain, int delay,
int offset, int length, int channel, long unsigned *rate, int *n_samples, int *latency)
{
float *samples, v;
int i, n, h;
if (length <= 0)
length = info->frames;
length = info->def_frames;
else
length = SPA_MIN(length, info->frames);
length = SPA_MIN(length, info->max_frames);
length -= SPA_MIN(offset, length);
@ -725,128 +802,107 @@ static float *read_samples_from_sf(SNDFILE *f, const SF_INFO *info, float gain,
if (samples == NULL)
return NULL;
if (offset > 0)
sf_seek(f, offset, SEEK_SET);
sf_readf_float(f, samples + (delay * info->channels), length);
channel = channel % info->channels;
for (i = 0; i < n; i++)
samples[i] = samples[info->channels * i + channel] * gain;
switch (info->type) {
case TYPE_SNDFILE:
#ifdef HAVE_SNDFILE
if (offset > 0)
sf_seek(info->fs, offset, SEEK_SET);
sf_readf_float(info->fs, samples + (delay * info->channels), length);
for (i = 0; i < n; i++)
samples[i] = samples[info->channels * i + channel] * gain;
#endif
break;
case TYPE_HILBERT:
gain *= 2 / (float)M_PI;
h = length / 2;
for (i = 1; i < h; i += 2) {
v = (gain / i) * (0.43f + 0.57f * cosf(i * (float)M_PI / h));
samples[delay + h + i] = -v;
samples[delay + h - i] = v;
}
spa_log_info(pl->log, "created hilbert function length %d", length);
break;
case TYPE_DIRAC:
samples[delay] = gain;
spa_log_info(pl->log, "created dirac function");
break;
case TYPE_IR:
{
struct spa_json it[1];
float v;
if (spa_json_begin_array_relax(&it[0], info->filename+4, strlen(info->filename+4)) <= 0)
return NULL;
if (spa_json_get_int(&it[0], &h) <= 0)
return NULL;
info->rate = h;
i = 0;
while (spa_json_get_float(&it[0], &v) > 0) {
samples[delay + i] = v * gain;
i++;
}
break;
}
}
*n_samples = n;
*rate = info->samplerate;
*rate = info->rate;
*latency = (int) (n * info->latency);
return samples;
}
#endif
static float *read_closest(struct plugin *pl, char **filenames, float gain, float delay_sec, int offset,
int length, int channel, long unsigned *rate, int *n_samples)
static void finfo_close(struct finfo *info)
{
#ifdef HAVE_SNDFILE
SF_INFO infos[MAX_RATES];
SNDFILE *fs[MAX_RATES];
if (info->type == TYPE_SNDFILE && info->fs != NULL)
sf_close(info->fs);
#endif
}
spa_zero(infos);
spa_zero(fs);
int diff = INT_MAX;
uint32_t best = 0, i;
static float *read_closest(struct plugin *pl, char **filenames, float gain, float delay_sec, int offset,
int length, int channel, long unsigned *rate, int *n_samples, int *latency)
{
struct finfo finfo[MAX_RATES];
int res, diff = INT_MAX;
uint32_t best = SPA_ID_INVALID, i;
float *samples = NULL;
spa_zero(finfo);
for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) {
fs[i] = sf_open(filenames[i], SFM_READ, &infos[i]);
if (fs[i] == NULL)
res = finfo_open(filenames[i], &finfo[i], *rate);
if (res < 0)
continue;
if (labs((long)infos[i].samplerate - (long)*rate) < diff) {
if (labs((long)finfo[i].rate - (long)*rate) < diff) {
best = i;
diff = labs((long)infos[i].samplerate - (long)*rate);
spa_log_debug(pl->log, "new closest match: %d", infos[i].samplerate);
diff = labs((long)finfo[i].rate - (long)*rate);
spa_log_debug(pl->log, "new closest match: %d", finfo[i].rate);
}
}
if (fs[best] != NULL) {
spa_log_info(pl->log, "loading best rate:%u %s", infos[best].samplerate, filenames[best]);
samples = read_samples_from_sf(fs[best], &infos[best], gain,
(int) (delay_sec * infos[best].samplerate), offset, length,
channel, rate, n_samples);
if (best != SPA_ID_INVALID) {
spa_log_info(pl->log, "loading best rate:%u %s", finfo[best].rate, filenames[best]);
samples = finfo_read_samples(pl, &finfo[best], gain,
(int) (delay_sec * finfo[best].rate), offset, length,
channel, rate, n_samples, latency);
} else {
char buf[PATH_MAX];
spa_log_error(pl->log, "Can't open any sample file (CWD %s):",
getcwd(buf, sizeof(buf)));
for (i = 0; i < MAX_RATES && filenames[i] && filenames[i][0]; i++) {
fs[i] = sf_open(filenames[i], SFM_READ, &infos[i]);
if (fs[i] == NULL)
spa_log_error(pl->log, " failed file %s: %s", filenames[i], sf_strerror(fs[i]));
res = finfo_open(filenames[i], &finfo[i], *rate);
if (res < 0)
spa_log_error(pl->log, " failed file %s: %s", filenames[i], finfo[i].error);
else
spa_log_warn(pl->log, " unexpectedly opened file %s", filenames[i]);
}
}
for (i = 0; i < MAX_RATES; i++)
if (fs[i] != NULL)
sf_close(fs[i]);
finfo_close(&finfo[i]);
return samples;
#else
spa_log_error(pl->log, "compiled without sndfile support, can't load samples: "
"using dirac impulse");
float *samples = calloc(1, sizeof(float));
samples[0] = gain;
*n_samples = 1;
return samples;
#endif
}
static float *create_hilbert(struct plugin *pl, const char *filename, float gain, int rate, float delay_sec, int offset,
int length, int *n_samples)
{
float *samples, v;
int i, n, h;
int delay = (int) (delay_sec * rate);
if (length <= 0)
length = 64;
length -= SPA_MIN(offset, length);
n = delay + length;
if (n == 0)
return NULL;
samples = calloc(n, sizeof(float));
if (samples == NULL)
return NULL;
gain *= 2 / (float)M_PI;
h = length / 2;
for (i = 1; i < h; i += 2) {
v = (gain / i) * (0.43f + 0.57f * cosf(i * (float)M_PI / h));
samples[delay + h + i] = -v;
samples[delay + h - i] = v;
}
*n_samples = n;
spa_log_info(pl->log, "created hilbert function");
return samples;
}
static float *create_dirac(struct plugin *pl, const char *filename, float gain, int rate, float delay_sec, int offset,
int length, int *n_samples)
{
float *samples;
int delay = (int) (delay_sec * rate);
int n;
n = delay + 1;
samples = calloc(n, sizeof(float));
if (samples == NULL)
return NULL;
samples[delay] = gain;
spa_log_info(pl->log, "created dirac function");
*n_samples = n;
return samples;
}
static float *resample_buffer(struct plugin *pl, float *samples, int *n_samples,
@ -936,10 +992,10 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
uint32_t i = 0;
struct spa_json it[2];
const char *val;
char key[256], v[256];
char key[256];
char *filenames[MAX_RATES] = { 0 };
int blocksize = 0, tailsize = 0;
int resample_quality = RESAMPLE_DEFAULT_QUALITY;
int resample_quality = RESAMPLE_DEFAULT_QUALITY, def_latency;
float gain = 1.0f, delay = 0.0f, latency = -1.0f;
unsigned long rate;
@ -985,17 +1041,20 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
else if (spa_streq(key, "filename")) {
if (spa_json_is_array(val, len)) {
spa_json_enter(&it[0], &it[1]);
while (spa_json_get_string(&it[1], v, sizeof(v)) > 0 &&
while ((len = spa_json_next(&it[1], &val)) > 0 &&
i < SPA_N_ELEMENTS(filenames)) {
filenames[i] = strdup(v);
filenames[i] = malloc(len+1);
if (filenames[i] == NULL)
return NULL;
spa_json_parse_stringn(val, len, filenames[i], len+1);
i++;
}
}
else if (spa_json_parse_stringn(val, len, v, sizeof(v)) <= 0) {
spa_log_error(pl->log, "convolver:filename requires a string or an array");
return NULL;
} else {
filenames[0] = strdup(v);
else {
filenames[0] = malloc(len+1);
if (filenames[0] == NULL)
return NULL;
spa_json_parse_stringn(val, len, filenames[0], len+1);
}
}
else if (spa_streq(key, "offset")) {
@ -1042,21 +1101,12 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
if (offset < 0)
offset = 0;
if (spa_streq(filenames[0], "/hilbert")) {
samples = create_hilbert(pl, filenames[0], gain, SampleRate, delay, offset,
length, &n_samples);
} else if (spa_streq(filenames[0], "/dirac")) {
samples = create_dirac(pl, filenames[0], gain, SampleRate, delay, offset,
length, &n_samples);
} else {
rate = SampleRate;
samples = read_closest(pl, filenames, gain, delay, offset,
length, channel, &rate, &n_samples);
if (samples != NULL && rate != SampleRate) {
samples = resample_buffer(pl, samples, &n_samples,
rate, SampleRate, resample_quality);
}
}
rate = SampleRate;
samples = read_closest(pl, filenames, gain, delay, offset,
length, channel, &rate, &n_samples, &def_latency);
if (samples != NULL && rate != SampleRate)
samples = resample_buffer(pl, samples, &n_samples,
rate, SampleRate, resample_quality);
for (i = 0; i < MAX_RATES; i++)
if (filenames[i])
@ -1072,8 +1122,8 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
if (tailsize <= 0)
tailsize = SPA_CLAMP(4096, blocksize, 32768);
spa_log_info(pl->log, "using n_samples:%u %d:%d blocksize delay:%f", n_samples,
blocksize, tailsize, delay);
spa_log_info(pl->log, "using n_samples:%u %d:%d blocksize delay:%f def-latency:%d", n_samples,
blocksize, tailsize, delay, def_latency);
impl = calloc(1, sizeof(*impl));
if (impl == NULL)
@ -1089,7 +1139,7 @@ static void * convolver_instantiate(const struct spa_fga_plugin *plugin, const s
goto error;
if (latency < 0.0f)
impl->latency = n_samples;
impl->latency = def_latency;
else
impl->latency = latency * impl->rate;
@ -1178,7 +1228,7 @@ struct delay_impl {
struct spa_log *log;
unsigned long rate;
float *port[4];
float *port[6];
float delay;
uint32_t delay_samples;
@ -1284,7 +1334,8 @@ static void delay_run(void * Instance, unsigned long SampleCount)
}
if (in != NULL && out != NULL) {
spa_fga_dsp_delay(impl->dsp, impl->buffer, &impl->ptr, impl->buffer_samples,
impl->delay_samples, out, in, SampleCount);
impl->delay_samples, out, in, SampleCount,
impl->port[4][0], impl->port[5][0]);
}
if (impl->port[3] != NULL)
impl->port[3][0] = impl->latency;
@ -1309,6 +1360,16 @@ static struct spa_fga_port delay_ports[] = {
.hint = SPA_FGA_HINT_LATENCY,
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL,
},
{ .index = 4,
.name = "Feedback",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
.def = 0.0f, .min = -10.0f, .max = 10.0f
},
{ .index = 5,
.name = "Feedforward",
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
.def = 0.0f, .min = -10.0f, .max = 10.0f
},
};
static const struct spa_fga_descriptor delay_desc = {

View file

@ -71,7 +71,6 @@ struct port {
struct spa_fraction rate = {};
StreamConfiguration streamConfig;
spa_data_type memtype = SPA_DATA_Invalid;
uint32_t buffers_blocks = 1;
struct buffer buffers[MAX_BUFFERS];
@ -449,8 +448,7 @@ const struct format_info *find_format_info_by_media_type(
uint32_t type, uint32_t subtype, uint32_t format)
{
for (const auto& f : format_info) {
if (f.media_type == type && f.media_subtype == subtype
&& (f.format == SPA_VIDEO_FORMAT_UNKNOWN || f.format == format))
if (f.media_type == type && f.media_subtype == subtype && f.format == format)
return &f;
}
@ -667,7 +665,6 @@ int spa_libcamera_set_format(struct impl *impl, struct port *port,
const struct format_info *info = nullptr;
uint32_t video_format;
struct spa_rectangle *size = nullptr;
struct spa_fraction *framerate = nullptr;
CameraConfiguration::Status validation;
int res;
@ -675,18 +672,15 @@ int spa_libcamera_set_format(struct impl *impl, struct port *port,
case SPA_MEDIA_SUBTYPE_raw:
video_format = format->info.raw.format;
size = &format->info.raw.size;
framerate = &format->info.raw.framerate;
break;
case SPA_MEDIA_SUBTYPE_mjpg:
case SPA_MEDIA_SUBTYPE_jpeg:
video_format = SPA_VIDEO_FORMAT_ENCODED;
size = &format->info.mjpg.size;
framerate = &format->info.mjpg.framerate;
break;
case SPA_MEDIA_SUBTYPE_h264:
video_format = SPA_VIDEO_FORMAT_ENCODED;
size = &format->info.h264.size;
framerate = &format->info.h264.framerate;
break;
default:
video_format = SPA_VIDEO_FORMAT_ENCODED;
@ -695,7 +689,7 @@ int spa_libcamera_set_format(struct impl *impl, struct port *port,
info = find_format_info_by_media_type(format->media_type,
format->media_subtype, video_format);
if (info == nullptr || size == nullptr || framerate == nullptr) {
if (info == nullptr || size == nullptr) {
spa_log_error(impl->log, "unknown media type %d %d %d", format->media_type,
format->media_subtype, video_format);
return -EINVAL;
@ -1210,21 +1204,17 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port,
const std::vector<std::unique_ptr<FrameBuffer>> &bufs =
impl->allocator.buffers(stream);
if (n_buffers > 0) {
if (bufs.size() != n_buffers)
return -EINVAL;
if (n_buffers > 0 && bufs.size() != n_buffers)
return -EINVAL;
spa_data *d = buffers[0]->datas;
const auto choose_memtype = [](uint32_t t) {
if (t != SPA_ID_INVALID && t & (1u << SPA_DATA_DmaBuf))
return SPA_DATA_DmaBuf;
if (t & (1u << SPA_DATA_MemFd))
return SPA_DATA_MemFd;
if (d[0].type != SPA_ID_INVALID && d[0].type & (1u << SPA_DATA_DmaBuf)) {
port->memtype = SPA_DATA_DmaBuf;
} else if (d[0].type & (1u << SPA_DATA_MemFd)) {
port->memtype = SPA_DATA_MemFd;
} else {
spa_log_error(impl->log, "can't use buffers of type %d", d[0].type);
return -EINVAL;
}
}
return SPA_DATA_Invalid;
};
for (uint32_t i = 0; i < n_buffers; i++) {
struct buffer *b;
@ -1254,9 +1244,17 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port,
spa_data *d = buffers[i]->datas;
for(uint32_t j = 0; j < buffers[i]->n_datas; ++j) {
d[j].type = port->memtype;
const auto memtype = choose_memtype(d[j].type);
if (memtype == SPA_DATA_Invalid) {
spa_log_error(impl->log, "can't use buffers of type %" PRIu32, d[j].type);
return -EINVAL;
}
d[j].type = memtype;
d[j].flags = SPA_DATA_FLAG_READABLE;
d[j].fd = -1;
d[j].mapoffset = 0;
d[j].data = nullptr;
d[j].chunk->stride = port->streamConfig.stride;
d[j].chunk->flags = 0;
/* Update parameters according to the plane information */
@ -1286,15 +1284,16 @@ spa_libcamera_alloc_buffers(struct impl *impl, struct port *port,
d[j].chunk->size = port->streamConfig.frameSize;
}
if (port->memtype == SPA_DATA_DmaBuf ||
port->memtype == SPA_DATA_MemFd) {
switch (memtype) {
case SPA_DATA_DmaBuf:
case SPA_DATA_MemFd:
d[j].flags |= SPA_DATA_FLAG_MAPPABLE;
d[j].fd = planes[j].fd.get();
spa_log_debug(impl->log, "Got fd = %" PRId64 " for buffer: #%d", d[j].fd, i);
d[j].data = nullptr;
} else {
spa_log_error(impl->log, "invalid buffer type");
return -EIO;
break;
default:
spa_assert_not_reached();
break;
}
}
}
@ -1420,20 +1419,17 @@ int port_get_format(struct impl *impl, struct port *port,
spa_pod_builder_add(builder,
SPA_FORMAT_VIDEO_format, SPA_POD_Id(port->current_format->info.raw.format),
SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.raw.size),
SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format->info.raw.framerate),
0);
break;
case SPA_MEDIA_SUBTYPE_mjpg:
case SPA_MEDIA_SUBTYPE_jpeg:
spa_pod_builder_add(builder,
SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.mjpg.size),
SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format->info.mjpg.framerate),
0);
break;
case SPA_MEDIA_SUBTYPE_h264:
spa_pod_builder_add(builder,
SPA_FORMAT_VIDEO_size, SPA_POD_Rectangle(&port->current_format->info.h264.size),
SPA_FORMAT_VIDEO_framerate, SPA_POD_Fraction(&port->current_format->info.h264.framerate),
0);
break;
default:

View file

@ -9,7 +9,6 @@
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <threads.h>
#include <stdatomic.h>
#include <spa/support/loop.h>

View file

@ -25,6 +25,7 @@
#include <spa/param/video/format-utils.h>
#include <spa/param/latency-utils.h>
#include <spa/param/tag-utils.h>
#include <spa/param/peer-utils.h>
#include <spa/debug/format.h>
#include <spa/debug/pod.h>
#include <spa/debug/log.h>
@ -900,7 +901,6 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
switch (id) {
case SPA_IO_Position:
this->io_position = data;
this->recheck_format = true;
break;
default:
break;
@ -920,7 +920,7 @@ static int update_param_peer_formats(struct impl *impl)
uint8_t buffer[4096];
spa_auto(spa_pod_dynamic_builder) b = { 0 };
uint32_t state = 0;
struct spa_pod *param;
struct spa_pod *param, *p, *str;
struct spa_pod_frame f;
int res;
@ -934,7 +934,6 @@ static int update_param_peer_formats(struct impl *impl)
spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
spa_pod_builder_push_struct(&b.b, &f);
while (true) {
res = node_port_enum_params_sync(impl, impl->follower,
impl->direction, 0,
@ -951,10 +950,24 @@ static int update_param_peer_formats(struct impl *impl)
spa_pod_simplify(&b.b, &param, param);
spa_debug_log_pod(impl->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param);
str = spa_pod_copy(param);
spa_pod_dynamic_builder_clean(&b);
spa_pod_dynamic_builder_init(&b, buffer, sizeof(buffer), 4096);
spa_peer_param_build_start(&b.b, &f, SPA_PARAM_PeerEnumFormat);
SPA_POD_STRUCT_FOREACH(str, p) {
spa_debug_log_pod(impl->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, p);
spa_peer_param_build_add_param(&b.b, 1, p);
}
param = spa_peer_param_build_end(&b.b, &f);
spa_debug_log_pod(impl->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param);
res = spa_node_port_set_param(impl->target,
SPA_DIRECTION_REVERSE(impl->direction), 0,
SPA_PARAM_PeerFormats, 0, param);
SPA_PARAM_PeerEnumFormat, 0, param);
free(str);
impl->recheck_format = false;
spa_log_debug(impl->log, "done updating peer formats: %d", res);

View file

@ -31,6 +31,7 @@
#include <spa/param/param.h>
#include <spa/param/latency-utils.h>
#include <spa/param/tag-utils.h>
#include <spa/param/peer-utils.h>
#include <spa/pod/filter.h>
#include <spa/pod/dynamic.h>
#include <spa/debug/types.h>
@ -77,15 +78,16 @@ struct port {
uint64_t info_all;
struct spa_port_info info;
#define IDX_EnumFormat 0
#define IDX_Meta 1
#define IDX_IO 2
#define IDX_Format 3
#define IDX_Buffers 4
#define IDX_Latency 5
#define IDX_Tag 6
#define IDX_PeerFormats 7
#define N_PORT_PARAMS 8
#define IDX_EnumFormat 0
#define IDX_Meta 1
#define IDX_IO 2
#define IDX_Format 3
#define IDX_Buffers 4
#define IDX_Latency 5
#define IDX_Tag 6
#define IDX_PeerEnumFormat 7
#define IDX_PeerCapability 8
#define N_PORT_PARAMS 9
struct spa_param_info params[N_PORT_PARAMS];
struct spa_pod *peer_format_pod;
@ -485,7 +487,8 @@ static int init_port(struct impl *this, enum spa_direction direction, uint32_t p
port->params[IDX_Buffers] = SPA_PARAM_INFO(SPA_PARAM_Buffers, 0);
port->params[IDX_Latency] = SPA_PARAM_INFO(SPA_PARAM_Latency, SPA_PARAM_INFO_READWRITE);
port->params[IDX_Tag] = SPA_PARAM_INFO(SPA_PARAM_Tag, SPA_PARAM_INFO_READWRITE);
port->params[IDX_PeerFormats] = SPA_PARAM_INFO(SPA_PARAM_PeerFormats, SPA_PARAM_INFO_WRITE);
port->params[IDX_PeerEnumFormat] = SPA_PARAM_INFO(SPA_PARAM_PeerEnumFormat, SPA_PARAM_INFO_WRITE);
port->params[IDX_PeerCapability] = SPA_PARAM_INFO(SPA_PARAM_PeerCapability, SPA_PARAM_INFO_WRITE);
port->info.params = port->params;
port->info.n_params = N_PORT_PARAMS;
@ -1599,7 +1602,7 @@ static int port_param_tag(struct impl *this, struct port *port, uint32_t id,
return 0;
}
static int port_param_peer_formats(struct impl *this, struct port *port, uint32_t index,
static int port_param_peer_enum_format(struct impl *this, struct port *port, uint32_t index,
const struct spa_pod **param, struct spa_pod_builder *b)
{
if (index >= port->n_peer_formats)
@ -1664,8 +1667,8 @@ impl_node_port_enum_params(void *object, int seq,
case SPA_PARAM_Tag:
res = port_param_tag(this, port, id, result.index, &param, &b);
break;
case SPA_PARAM_PeerFormats:
res = port_param_peer_formats(this, port, result.index, &param, &b);
case SPA_PARAM_PeerEnumFormat:
res = port_param_peer_enum_format(this, port, result.index, &param, &b);
break;
default:
return -ENOENT;
@ -1980,7 +1983,7 @@ static int port_set_format(void *object,
}
static int port_set_peer_formats(void *object,
static int port_set_peer_enum_format(void *object,
enum spa_direction direction,
uint32_t port_id,
uint32_t flags,
@ -1990,14 +1993,15 @@ static int port_set_peer_formats(void *object,
struct port *port, *oport;
int res = 0;
uint32_t i;
const struct spa_pod *format;
enum spa_direction other = SPA_DIRECTION_REVERSE(direction);
static uint32_t subtypes[] = {
SPA_MEDIA_SUBTYPE_raw,
SPA_MEDIA_SUBTYPE_mjpg,
SPA_MEDIA_SUBTYPE_h264 };
struct spa_peer_param_info info;
void *state = NULL;
spa_return_val_if_fail(spa_pod_is_struct(formats), -EINVAL);
spa_return_val_if_fail(spa_pod_is_object(formats), -EINVAL);
port = GET_PORT(this, direction, port_id);
oport = GET_PORT(this, other, port_id);
@ -2013,9 +2017,10 @@ static int port_set_peer_formats(void *object,
port->peer_format_pod = spa_pod_copy(formats);
for (i = 0; i < SPA_N_ELEMENTS(subtypes); i++) {
SPA_POD_STRUCT_FOREACH(port->peer_format_pod, format) {
state = NULL;
while (spa_peer_param_parse(formats, &info, sizeof(info), &state) > 0) {
uint32_t media_type, media_subtype;
if (!spa_format_parse(format, &media_type, &media_subtype) ||
if (!spa_format_parse(info.param, &media_type, &media_subtype) ||
media_type != SPA_MEDIA_TYPE_video ||
media_subtype != subtypes[i])
continue;
@ -2024,13 +2029,14 @@ static int port_set_peer_formats(void *object,
}
port->peer_formats = calloc(count, sizeof(struct spa_pod *));
for (i = 0; i < SPA_N_ELEMENTS(subtypes); i++) {
SPA_POD_STRUCT_FOREACH(port->peer_format_pod, format) {
state = NULL;
while (spa_peer_param_parse(port->peer_format_pod, &info, sizeof(info), &state) > 0) {
uint32_t media_type, media_subtype;
if (!spa_format_parse(format, &media_type, &media_subtype) ||
if (!spa_format_parse(info.param, &media_type, &media_subtype) ||
media_type != SPA_MEDIA_TYPE_video ||
media_subtype != subtypes[i])
continue;
port->peer_formats[port->n_peer_formats++] = format;
port->peer_formats[port->n_peer_formats++] = info.param;
}
}
}
@ -2038,7 +2044,7 @@ static int port_set_peer_formats(void *object,
oport->params[IDX_EnumFormat].user++;
port->info.change_mask |= SPA_PORT_CHANGE_MASK_PARAMS;
port->params[IDX_EnumFormat].user++;
port->params[IDX_PeerFormats].user++;
port->params[IDX_PeerEnumFormat].user++;
return res;
}
@ -2068,8 +2074,9 @@ impl_node_port_set_param(void *object,
case SPA_PARAM_Format:
res = port_set_format(this, direction, port_id, flags, param);
break;
case SPA_PARAM_PeerFormats:
res = port_set_peer_formats(this, direction, port_id, flags, param);
case SPA_PARAM_PeerEnumFormat:
res = port_set_peer_enum_format(this, direction, port_id, flags, param);
break;
break;
default:
return -ENOENT;

View file

@ -67,6 +67,7 @@ avb.properties = {
# the addresses this server listens on
#ifname = "eth0.2"
ifname = "enp3s0"
milan = false
}
avb.properties.rules = [

View file

@ -17,6 +17,7 @@ Requires=pipewire-pulse.socket
ConditionUser=!root
Wants=pipewire.service pipewire-session-manager.service
After=pipewire.service pipewire-session-manager.service
BindsTo=pipewire.service
Conflicts=pulseaudio.service
[Service]

View file

@ -15,6 +15,8 @@
#include <spa/utils/result.h>
#include <spa/param/video/format-utils.h>
#include <spa/param/tag-utils.h>
#include <spa/param/dict-utils.h>
#include <spa/param/peer-utils.h>
#include <spa/param/props.h>
#include <spa/param/latency-utils.h>
#include <spa/debug/format.h>
@ -270,6 +272,34 @@ on_stream_io_changed(void *_data, uint32_t id, void *area, uint32_t size)
}
}
static void parse_peer_capability(struct data *data, const struct spa_pod *param)
{
struct spa_peer_param_info info;
void *state = NULL;
fprintf(stderr, "peer capability\n");
while (spa_peer_param_parse(param, &info, sizeof(info), &state) == 1) {
struct spa_param_dict_info di;
if (spa_param_dict_parse(info.param, &di, sizeof(di)) > 0) {
struct spa_dict dict;
struct spa_dict_item *items;
const struct spa_dict_item *it;
if (spa_param_dict_info_parse(&di, sizeof(di), &dict, NULL) < 0)
return;
items = alloca(sizeof(struct spa_dict_item) * dict.n_items);
if (spa_param_dict_info_parse(&di, sizeof(di), &dict, items) < 0)
return;
spa_dict_for_each(it, &dict)
fprintf(stderr, "peer:%u %s: %s\n", info.peer_id, it->key, it->value);
}
}
}
/* Be notified when the stream param changes. We're only looking at the
* format changes.
*
@ -294,8 +324,11 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param)
void *d;
int32_t mult, size, blocks;
if (param != NULL && id == SPA_PARAM_Tag) {
spa_debug_pod(0, NULL, param);
if (param != NULL && (id == SPA_PARAM_Tag || id == SPA_PARAM_PeerCapability)) {
if (id == SPA_PARAM_PeerCapability)
parse_peer_capability(data, param);
else
spa_debug_pod(0, NULL, param);
return;
}
if (param != NULL && id == SPA_PARAM_Latency) {

View file

@ -15,6 +15,7 @@
#include <spa/param/video/format-utils.h>
#include <spa/param/tag-utils.h>
#include <spa/param/dict-utils.h>
#include <spa/debug/pod.h>
#include <spa/debug/format.h>
@ -219,7 +220,7 @@ on_stream_param_changed(void *_data, uint32_t id, const struct spa_pod *param)
const struct spa_pod *params[5];
uint32_t n_params = 0;
if (param != NULL && id == SPA_PARAM_Tag) {
if (param != NULL && (id == SPA_PARAM_Tag || id == SPA_PARAM_PeerCapability)) {
spa_debug_pod(0, NULL, param);
return;
}
@ -290,7 +291,8 @@ static void do_quit(void *userdata, int signal_number)
int main(int argc, char *argv[])
{
struct data data = { 0, };
const struct spa_pod *params[2];
const struct spa_pod *params[3];
uint32_t n_params = 0;
uint8_t buffer[1024];
struct spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
@ -318,7 +320,7 @@ int main(int argc, char *argv[])
PW_KEY_NODE_SUPPORTS_REQUEST, "1",
NULL));
params[0] = spa_pod_builder_add_object(&b,
params[n_params++] = spa_pod_builder_add_object(&b,
SPA_TYPE_OBJECT_Format, SPA_PARAM_EnumFormat,
SPA_FORMAT_mediaType, SPA_POD_Id(SPA_MEDIA_TYPE_video),
SPA_FORMAT_mediaSubtype, SPA_POD_Id(SPA_MEDIA_SUBTYPE_raw),
@ -336,7 +338,13 @@ int main(int argc, char *argv[])
spa_tag_build_add_dict(&b,
&SPA_DICT_ITEMS(
SPA_DICT_ITEM("my-tag-key", "my-special-tag-value")));
params[1] = spa_tag_build_end(&b, &f);
params[n_params++] = spa_tag_build_end(&b, &f);
}
{
params[n_params++] = spa_param_dict_build_dict(&b, SPA_PARAM_Capability,
&SPA_DICT_ITEMS(
SPA_DICT_ITEM("my-capability-key", "my-capability-value")));
}
pw_stream_add_listener(data.stream,
@ -349,7 +357,7 @@ int main(int argc, char *argv[])
PW_ID_ANY,
PW_STREAM_FLAG_DRIVER |
PW_STREAM_FLAG_MAP_BUFFERS,
params, 2);
params, n_params);
pw_main_loop_run(data.loop);

View file

@ -1056,6 +1056,44 @@ wait_started (GstPipeWireSrc *this)
return state;
}
static enum pw_stream_state
wait_negotiated (GstPipeWireSrc *this)
{
enum pw_stream_state state;
const char *error = NULL;
struct timespec abstime;
pw_thread_loop_get_time (this->stream->core->loop, &abstime,
GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC);
while (TRUE) {
state = pw_stream_get_state (this->stream->pwstream, &error);
GST_DEBUG_OBJECT (this, "waiting for NEGOTIATED, now %s", pw_stream_state_as_string (state));
if (state == PW_STREAM_STATE_ERROR)
break;
if (this->flushing) {
state = PW_STREAM_STATE_ERROR;
break;
}
if (this->negotiated)
break;
if (this->autoconnect) {
if (pw_thread_loop_timed_wait_full (this->stream->core->loop, &abstime) < 0) {
state = PW_STREAM_STATE_ERROR;
break;
}
} else {
pw_thread_loop_wait (this->stream->core->loop);
}
}
GST_DEBUG_OBJECT (this, state != PW_STREAM_STATE_ERROR ? "got negotiated signal" : "error during negotiation");
return state;
}
static gboolean
gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
{
@ -1067,7 +1105,6 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
g_autoptr (GPtrArray) possible = NULL;
gboolean result = FALSE;
const char *error = NULL;
struct timespec abstime;
uint32_t target_id;
/* first see what is possible on our source pad */
@ -1177,26 +1214,8 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
(const struct spa_pod **)possible->pdata,
possible->len);
pw_thread_loop_get_time (pwsrc->stream->core->loop, &abstime,
GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC);
while (TRUE) {
enum pw_stream_state state = pw_stream_get_state (pwsrc->stream->pwstream, &error);
GST_DEBUG_OBJECT (basesrc, "waiting for NEGOTIATED, now %s", pw_stream_state_as_string (state));
if (state == PW_STREAM_STATE_ERROR || pwsrc->flushing)
goto connect_error;
if (pwsrc->negotiated)
break;
if (pwsrc->autoconnect) {
if (pw_thread_loop_timed_wait_full (pwsrc->stream->core->loop, &abstime) < 0)
goto connect_error;
} else {
pw_thread_loop_wait (pwsrc->stream->core->loop);
}
}
if (wait_negotiated(pwsrc) == PW_STREAM_STATE_ERROR)
goto connect_error;
negotiated_caps = g_steal_pointer (&pwsrc->caps);
pw_thread_loop_unlock (pwsrc->stream->core->loop);
@ -1724,12 +1743,21 @@ gst_pipewire_src_change_state (GstElement * element, GstStateChange transition)
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
/* uncork and start recording */
GST_DEBUG_OBJECT (this, "activating stream");
pw_thread_loop_lock (this->stream->core->loop);
pw_stream_set_active (this->stream->pwstream, true);
/* if state have been paused for longer time, the underlying node might
* be moved from idle to suspended, which would mean format cleared via
* handle_format_change. Wait for new format to avoid basesrc calling
* create() and get not-negotiated error as response. */
if (wait_negotiated(this) == PW_STREAM_STATE_ERROR)
goto open_failed;
pw_thread_loop_unlock (this->stream->core->loop);
break;
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
/* stop recording ASAP by corking */
GST_DEBUG_OBJECT (this, "in-activating stream");
pw_thread_loop_lock (this->stream->core->loop);
pw_stream_set_active (this->stream->pwstream, false);
pw_thread_loop_unlock (this->stream->core->loop);

View file

@ -736,7 +736,21 @@ if build_module_avb
'module-avb/acmp.c',
'module-avb/aecp.c',
'module-avb/aecp-aem.c',
'module-avb/aecp-aem-cmds-resps/cmd-available.c',
'module-avb/aecp-aem-cmds-resps/cmd-get-set-name.c',
'module-avb/aecp-aem-cmds-resps/cmd-get-set-clock-source.c',
'module-avb/aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.c',
'module-avb/aecp-aem-cmds-resps/cmd-deregister-unsolicited-notifications.c',
'module-avb/aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.c',
'module-avb/aecp-aem-cmds-resps/cmd-get-set-stream-format.c',
'module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c',
'module-avb/aecp-aem-cmds-resps/cmd-get-set-configuration.c',
'module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c',
'module-avb/aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.c',
'module-avb/aecp-aem-cmds-resps/reply-unsol-helpers.c',
'module-avb/es-builder.c',
'module-avb/avdecc.c',
'module-avb/descriptors.c',
'module-avb/maap.c',
'module-avb/mmrp.c',
'module-avb/mrp.c',

View file

@ -11,6 +11,8 @@
#include "msrp.h"
#include "internal.h"
#include "stream.h"
#include "aecp-aem-descriptors.h"
#include "aecp-aem-state.h"
static const uint8_t mac[6] = AVB_BROADCAST_MAC;
@ -77,12 +79,64 @@ static void pending_free(struct acmp *acmp, struct pending *p)
free(p);
}
static void pending_destroy(struct acmp *acmp)
{
struct pending *p, *t;
for (uint32_t list_id = 0; list_id < PENDING_CONTROLLER; list_id++) {
spa_list_for_each_safe(p, t, &acmp->pending[list_id], link) {
pending_free(acmp, p);
}
}
}
struct msg_info {
uint16_t type;
const char *name;
int (*handle) (struct acmp *acmp, uint64_t now, const void *m, int len);
};
static struct stream *find_stream(struct server *server, enum spa_direction direction,
uint16_t index)
{
uint16_t type;
struct descriptor *desc;
struct stream *stream;
switch (direction) {
case SPA_DIRECTION_INPUT:
type = AVB_AEM_DESC_STREAM_INPUT;
break;
case SPA_DIRECTION_OUTPUT:
type = AVB_AEM_DESC_STREAM_OUTPUT;
break;
default:
pw_log_error("Unkown direction\n");
return NULL;
}
desc = server_find_descriptor(server, type, index);
if (!desc) {
pw_log_error("Could not find stream type %u index %u\n",
type, index);
return NULL;
}
switch (direction) {
case SPA_DIRECTION_INPUT:
struct aecp_aem_stream_input_state *stream_in;
stream_in = desc->ptr;
stream = &stream_in->stream;
break;
case SPA_DIRECTION_OUTPUT:
struct aecp_aem_stream_output_state *stream_out;
stream_out = desc->ptr;
stream = &stream_out->stream;
break;
}
return stream;
}
static int reply_not_supported(struct acmp *acmp, uint8_t type, const void *m, int len)
{
struct server *server = acmp->server;
@ -120,8 +174,7 @@ static int handle_connect_tx_command(struct acmp *acmp, uint64_t now, const void
return 0;
memcpy(buf, m, len);
stream = server_find_stream(server, SPA_DIRECTION_OUTPUT,
reply->talker_unique_id);
stream = find_stream(server, SPA_DIRECTION_OUTPUT, ntohs(reply->talker_unique_id));
if (stream == NULL) {
status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX;
goto done;
@ -130,7 +183,7 @@ static int handle_connect_tx_command(struct acmp *acmp, uint64_t now, const void
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE);
reply->stream_id = htobe64(stream->id);
stream_activate(stream, now);
stream_activate(stream, ntohs(reply->talker_unique_id), now);
memcpy(reply->stream_dest_mac, stream->addr, 6);
reply->connection_count = htons(1);
@ -169,14 +222,13 @@ static int handle_connect_tx_response(struct acmp *acmp, uint64_t now, const voi
reply->sequence_id = htons(pending->old_sequence_id);
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE);
stream = server_find_stream(server, SPA_DIRECTION_INPUT,
ntohs(reply->listener_unique_id));
stream = find_stream(server, SPA_DIRECTION_INPUT, ntohs(reply->listener_unique_id));
if (stream == NULL)
return 0;
stream->peer_id = be64toh(reply->stream_id);
memcpy(stream->addr, reply->stream_dest_mac, 6);
stream_activate(stream, now);
stream_activate(stream, ntohs(reply->listener_unique_id), now);
res = avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, pending->size);
@ -199,8 +251,7 @@ static int handle_disconnect_tx_command(struct acmp *acmp, uint64_t now, const v
return 0;
memcpy(buf, m, len);
stream = server_find_stream(server, SPA_DIRECTION_OUTPUT,
reply->talker_unique_id);
stream = find_stream(server, SPA_DIRECTION_OUTPUT, ntohs(reply->talker_unique_id));
if (stream == NULL) {
status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX;
goto done;
@ -243,8 +294,7 @@ static int handle_disconnect_tx_response(struct acmp *acmp, uint64_t now, const
reply->sequence_id = htons(pending->old_sequence_id);
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE);
stream = server_find_stream(server, SPA_DIRECTION_INPUT,
reply->listener_unique_id);
stream = find_stream(server, SPA_DIRECTION_INPUT, ntohs(reply->listener_unique_id));
if (stream == NULL)
return 0;
@ -369,6 +419,7 @@ static void acmp_destroy(void *data)
{
struct acmp *acmp = data;
spa_hook_remove(&acmp->server_listener);
pending_destroy(acmp);
free(acmp);
}

View file

@ -156,7 +156,14 @@ static int adp_message(void *data, uint64_t now, const void *message, int len)
static void adp_destroy(void *data)
{
struct adp *adp = data;
struct entity *e, *t;
spa_hook_remove(&adp->server_listener);
spa_list_for_each_safe(e, t, &adp->entities, link) {
entity_free(e);
}
free(adp);
}

View file

@ -0,0 +1,68 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#include <stdbool.h>
#include <stdint.h>
#include "../aecp-aem.h"
#include "../aecp-aem-state.h"
#include "../aecp-aem-descriptors.h"
#include "../aecp-aem-types.h"
#include "cmd-resp-helpers.h"
#include "cmd-available.h"
/* ENTITY AVAILABLE according to the locking state */
#define AECP_AEM_AVAIL_ENTITY_ACQUIRED (1<<0)
#define AECP_AEM_AVAIL_ENTITY_LOCKED (1<<1)
#define AECP_AEM_AVAIL_SUBENTITY_ACQUIRED (1<<2)
#define AECP_AEM_AVAIL_SUBENTITY_LOCKED (1<<3)
int handle_cmd_entity_available_milan_v12(struct aecp *aecp, int64_t now, const void *m,
int len)
{
uint8_t buf[512];
/* Commnand received specific */
struct server *server = aecp->server;
const struct avb_ethernet_header *h = m;
struct avb_ethernet_header *h_reply;
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
/* Reply specific */
struct avb_packet_aecp_aem *p_reply;
struct avb_packet_aecp_aem_available *avail_reply;
/* Entity specific */
struct descriptor *desc;
struct aecp_aem_entity_milan_state *entity_state;
struct aecp_aem_lock_state *lock;
desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0);
if (desc == NULL) {
return reply_status(aecp,
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
}
entity_state = desc->ptr;
lock = &entity_state->state.lock_state;
/* Forge the response for the entity that is locking the device */
memcpy(buf, m, len);
h_reply = (struct avb_ethernet_header *) buf;
p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void);
avail_reply = (struct avb_packet_aecp_aem_available*)p_reply->payload;
avail_reply->acquired_controller_guid = 0;
if ((lock->base_info.expire_timeout < now) || !lock->is_locked) {
avail_reply->flags = 0;
} else if (lock->is_locked) {
avail_reply->lock_controller_guid = htobe64(lock->locked_id);
avail_reply->flags = htonl(AECP_AEM_AVAIL_ENTITY_LOCKED);
}
return reply_success(aecp, buf, len);
}

View file

@ -0,0 +1,16 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#ifndef __AVB_AECP_AEM_AVAILABLE_H__
#define __AVB_AECP_AEM_AVAILABLE_H__
#include <stdint.h>
/**
* \brief Milan V1.2 implementation to handle available command.
*/
int handle_cmd_entity_available_milan_v12(struct aecp *aecp, int64_t now, const void *m,
int len);
#endif //__AVB_AECP_AEM_AVAILABLE_H__

View file

@ -0,0 +1,68 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#include <pipewire/log.h>
#include <inttypes.h>
#include "../aecp-aem.h"
#include "../aecp-aem-state.h"
#include "../aecp-aem-milan.h"
#include "cmd-deregister-unsolicited-notifications.h"
#include "cmd-resp-helpers.h"
int handle_cmd_deregister_unsol_notif_milan_v12(struct aecp *aecp, int64_t now,
const void *m, int len)
{
const struct avb_ethernet_header *h = m;
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
struct descriptor *desc;
struct aecp_aem_entity_milan_state *entity_state;
struct aecp_aem_unsol_notification_state *unsol;
uint64_t controller_id = htobe64(p->aecp.controller_guid);
const uint32_t ctrler_max = AECP_AEM_MILAN_MAX_CONTROLLER;
uint16_t index;
desc = server_find_descriptor(aecp->server, AVB_AEM_DESC_ENTITY, 0);
if (desc == NULL)
return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
entity_state = (struct aecp_aem_entity_milan_state *) desc->ptr;
unsol = entity_state->unsol_notif_state;
/** First check if the controller was already registered */
for (index = 0; index < ctrler_max; index++) {
uint64_t ctrl_eid = unsol[index].ctrler_entity_id;
bool ctrler_reged = unsol[index].is_registered;
if ((ctrl_eid == controller_id) && ctrler_reged) {
pw_log_debug("controller 0x%"PRIx64", already registered",
controller_id);
return reply_success(aecp, m, len);
}
}
/** When one slot is in the array is available use it */
for (index = 0; index < ctrler_max; index++) {
if (!unsol[index].is_registered) {
break;
}
}
/** Reach the maximum controller allocated */
if (index == ctrler_max) {
return reply_no_resources(aecp, m, len);
}
unsol[index].ctrler_entity_id = controller_id;
memcpy(unsol[index].ctrler_mac_addr, h->src, sizeof(h->src));
unsol[index].is_registered = true;
unsol[index].port_id = 0;
unsol[index].next_seq_id = 0;
pw_log_info("Unsol registration for 0x%"PRIx64, controller_id);
return reply_success(aecp, m, len);
}

View file

@ -0,0 +1,23 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#ifndef __AVB_DEREGISTER_UNSOLICITED_NOTIFICATIONS_H__
#define __AVB_DEREGISTER_UNSOLICITED_NOTIFICATIONS_H__
#include <stdint.h>
/**
* @brief Command handling will generate the response to the
* received command when deregistering the a controller from
* the list of subscribed controller entities.
*
* @see IEEE1722.1-2021 Section 7.4.38 .
* @see Milan V1.2 Section 5.4.2.22.
*/
int handle_cmd_deregister_unsol_notif_milan_v12(struct aecp *aecp, int64_t now,
const void *m, int len);
#endif // __AVB_DEREGISTER_UNSOLICITED_NOTIFICATIONS_H__

View file

@ -0,0 +1,142 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#include <stdint.h>
#include <stdbool.h>
#include "../aecp-aem.h"
#include "../aecp-aem-state.h"
#include "../descriptors.h"
#include "../aecp-aem-types.h"
#include "cmd-resp-helpers.h"
#include "reply-unsol-helpers.h"
#include "cmd-get-set-clock-source.h"
static int reply_invalid_clock_source(struct aecp *aecp,
struct avb_aem_desc_clock_domain *desc, const void *m, int len)
{
uint8_t buf[128];
struct avb_ethernet_header *h = (struct avb_ethernet_header *)buf;
struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
struct avb_packet_aecp_aem_setget_clock_source *sclk_source;
memcpy(buf, m, len);
sclk_source =
(struct avb_packet_aecp_aem_setget_clock_source *) p->payload;
/** The descriptor keep the network endianess */
sclk_source->clock_source_index = desc->clock_source_index;
// Reply success with the old value which is the current if it fails.
return reply_success(aecp, buf, len);
}
static int handle_unsol_set_clock_source(struct aecp *aecp, struct descriptor *desc,
const void *m, int len, uint64_t ctrler_id)
{
uint8_t buf[128];
struct aecp_aem_base_info bi = { 0 };
int rc;
memcpy(buf, m, len);
bi.controller_entity_id = htobe64(ctrler_id);
bi.expire_timeout = INT64_MAX;
rc = reply_unsolicited_notifications(aecp, &bi, buf, len, false);
return rc;
}
/**
* \see IEEE 1722.1-2021, 7.4.24. SET_CLOCK_SOURCE Command
* \see Milan V1.2 5.4.2.15
* \todo verify if this is valid for AVB
*/
int handle_cmd_get_clock_source_milan_v12(struct aecp *aecp, int64_t now,
const void *m, int len)
{
uint8_t buf[128];
struct avb_ethernet_header *h = (struct avb_ethernet_header *) buf;
struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
struct avb_packet_aecp_aem_setget_clock_source *sclk_source;
struct avb_aem_desc_clock_domain* dclk_domain;
struct descriptor *desc;
uint16_t desc_index;
uint16_t desc_type;
memcpy(buf, m, len);
sclk_source =
(struct avb_packet_aecp_aem_setget_clock_source *) p->payload;
desc_index = htons(sclk_source->descriptor_id);
desc_type = htons(sclk_source->descriptor_id);
desc = server_find_descriptor(aecp->server, desc_type, desc_index);
if (desc == NULL)
return reply_status(aecp,
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
dclk_domain = (struct avb_aem_desc_clock_domain*) desc->ptr;
/** Descriptors always keep the network endianness */
sclk_source->clock_source_index = dclk_domain->clock_source_index;
len = sizeof(*p) + sizeof(*sclk_source) + sizeof(*h);
return reply_success(aecp, m, len);
}
/**
* \see IEEE 1722.1-2021, 7.4.23. SET_CLOCK_SOURCE Command
* \see Milan V1.2 5.4.2.15
* \todo verify if this is valid for AVB
*/
int handle_cmd_set_clock_source_milan_v12(struct aecp *aecp, int64_t now,
const void *m, int len)
{
int rc;
struct server *server = aecp->server;
const struct avb_ethernet_header *h = m;
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
struct avb_packet_aecp_aem_setget_clock_source *sclk_source;
/** Information in the packet */
uint16_t desc_type;
uint16_t desc_index;
uint16_t clock_src_index;
uint64_t ctrlr_id;
/*Information about the system */
struct descriptor *desc;
struct avb_aem_desc_clock_domain* dclk_domain;
sclk_source =
(struct avb_packet_aecp_aem_setget_clock_source *) p->payload;
desc_type = ntohs(sclk_source->descriptor_type);
desc_index = ntohs(sclk_source->descriptor_id);
clock_src_index = ntohs(sclk_source->clock_source_index);
ctrlr_id = htobe64(p->aecp.controller_guid);
/** Retrieve the descriptor */
desc = server_find_descriptor(server, desc_type, desc_index);
if (desc == NULL)
return reply_status(aecp,
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
dclk_domain = (struct avb_aem_desc_clock_domain *) desc->ptr;
if (clock_src_index >= dclk_domain->clock_sources_count) {
return reply_invalid_clock_source(aecp, dclk_domain, m, len);
}
/** Descriptor always keep the network endianness */
dclk_domain->clock_source_index = htons(clock_src_index);
rc = reply_success(aecp, m, len);
if (rc) {
pw_log_error("Reply failed for set_clock_source\n");
return -1;
}
return handle_unsol_set_clock_source(aecp, desc, m, len, ctrlr_id);
}

View file

@ -0,0 +1,14 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#ifndef __AVB_AECP_AEM_CMD_GET_SET_CLOCK_SOURCE_H__
#define __AVB_AECP_AEM_CMD_GET_SET_CLOCK_SOURCE_H__
#include <stdint.h>
int handle_cmd_set_clock_source_milan_v12(struct aecp *aecp, int64_t now, const void *m, int len);
int handle_cmd_get_clock_source_milan_v12(struct aecp *aecp, int64_t now, const void *m, int len);
#endif /* __AVB_AECP_AEM_CMD_GET_SET_CLOCK_SOURCE_H__ */

View file

@ -0,0 +1,194 @@
#include <stdint.h>
#include <stdbool.h>
#include "../aecp-aem.h"
#include "../aecp-aem-state.h"
#include "../aecp-aem-descriptors.h"
#include "../aecp-aem-types.h"
#include "cmd-get-set-configuration.h"
#include "cmd-resp-helpers.h"
#if 0
static int handle_unsol_set_configuration_milan_v12(struct aecp *aecp, struct descriptor *desc,
uint64_t ctrler_id)
{
/* Reply */
uint8_t buf[512];
void *m = buf;
struct avb_aem_desc_entity *entity_desc;
struct avb_ethernet_header *h = m;
struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
struct avb_packet_aecp_aem_setget_configuration *cfg;
size_t len = sizeof (*h) + sizeof(*p) + sizeof(*cfg);
int rc;
memset(buf, 0, sizeof(buf));
entity_desc = (struct avb_aem_desc_entity*) desc->ptr;
cfg = (struct avb_packet_aecp_aem_setget_configuration *) p->payload;
cfg->configuration_index = htons(entity_desc->current_configuration);
p->aecp.target_guid = htobe64(aecp->server->entity_id);
AVB_PACKET_AEM_SET_COMMAND_TYPE(p, AVB_AECP_AEM_CMD_SET_CONFIGURATION);
rc = reply_unsolicited_notifications_ctrler_id(aecp, ctrler_id,
buf, len, false);
if (rc) {
pw_log_error("unsol notif failed");
}
return rc;
}
#endif
/**
* Common handler for SET_CONFIGURATION command
*
* Milan v1.2, Sec. 5.4.2.5
* IEEE 1722.1-2021, Sec. 7.4.7
*/
int handle_cmd_set_configuration_milan_v12(struct aecp *aecp, int64_t now,
const void *m, int len)
{
uint8_t buf[2048];
struct server *server = aecp->server;
const struct avb_ethernet_header *h = m;
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
/* Reply */
struct avb_ethernet_header *h_reply;
struct avb_packet_aecp_aem *p_reply;
struct avb_packet_aecp_aem_setget_configuration *cfg;
/* Information about the current entity */
struct avb_aem_desc_entity *entity_desc;
uint16_t req_cfg_id, cur_cfg_id, cfg_count;
struct descriptor *desc;
int rc;
bool has_failed;
/* FIXME ACMP: IMPORTANT!!!! find the stream connection information
* whether they are running or not. */
/* Milan v1.2, Sec. 5.4.2.5
* The PAAD-AE shall not accept a SET_CONFIGURATION command if one of
* the Stream Input is bound or one of the Stream Output is streaming.
* In this case, the STREAM_IS_RUNNING error code shall be returned.
*
* If the PAAD-AE is locked by a controller, it shall not accept a
* SET_CONFIGURATION command from a different controller, and it shall
* also not change its current configuration by non-ATDECC
* means (proprietary remote control software, front-panel, ...).
*/
/** WARNING! Milan forces only one entity */
desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0);
if (desc == NULL)
return reply_status(aecp,
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
// TODO maybe avoid copy here
memcpy(buf, m, len);
h_reply = (struct avb_ethernet_header *)buf;
p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void);
cfg = (struct avb_packet_aecp_aem_setget_configuration*) p_reply->payload;
entity_desc = (struct avb_aem_desc_entity*) desc->ptr;
cur_cfg_id = ntohs(entity_desc->current_configuration);
req_cfg_id = ntohs(cfg->configuration_index);
cfg_count = ntohs(entity_desc->configurations_count);
if (entity_desc->entity_id != p->aecp.target_guid) {
pw_log_error("Invalid entity id");
has_failed = true;
/* TODO: req_cfg_id is zero based, cfg_count is not.
* Should be req_cfg_id >= cfg_count */
} else if (req_cfg_id >= cfg_count) {
pw_log_error("Requested %u, but has max %u id",
req_cfg_id, cfg_count);
has_failed = true;
} else if (req_cfg_id == cur_cfg_id) {
pw_log_warn("requested %u and same current %u id", req_cfg_id,
cur_cfg_id);
has_failed = true;
} else {
entity_desc->current_configuration = cfg->configuration_index;
has_failed = false;
}
/*
* Always contains the current value,
* that is itcontains the new value if the command succeeds or the old
* value if it fails.
*/
if (has_failed) {
cfg->configuration_index = entity_desc->current_configuration;
}
rc = reply_success(aecp, buf, len);
if (rc) {
pw_log_error("Reply Failed");
return rc;
}
#if 0
if(!has_failed) {
return handle_unsol_set_configuration_milan_v12(aecp, desc,
tobe64(p->aecp.controller_guid));
}
#endif
return 0;
}
/**
* Common handler for GET_CONFIGURATION command
* Milan v1.2, Sec. 5.4.2.6
* IEEE 1722.1-2021, Sec. 7.4.8
*/
int handle_cmd_get_configuration_common(struct aecp *aecp, int64_t now,
const void *m, int len)
{
uint8_t buf[2048];
struct server *server = aecp->server;
const struct avb_ethernet_header *h = m;
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
/* Reply */
struct avb_ethernet_header *h_reply;
struct avb_packet_aecp_aem *p_reply;
struct avb_packet_aecp_aem_setget_configuration *cfg;
/* Information about the current entity */
struct avb_aem_desc_entity *entity_desc;
struct descriptor *desc;
int rc;
desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0);
if (desc == NULL)
return reply_status(aecp,
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
memcpy(buf, m, len);
h_reply = (struct avb_ethernet_header *)buf;
p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void);
cfg = (struct avb_packet_aecp_aem_setget_configuration*) p_reply->payload;
entity_desc = (struct avb_aem_desc_entity*) desc->ptr;
if (entity_desc->entity_id != p->aecp.target_guid) {
pw_log_error("Invalid entity id");
return reply_status(aecp,
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
}
cfg->configuration_index = entity_desc->current_configuration;
rc = reply_success(aecp, buf, len);
if (rc) {
pw_log_error("Reply Failed");
return rc;
}
return 0;
}

View file

@ -0,0 +1,10 @@
#ifndef __AECP_AEM_CMD_GET_SET_CONFIGURATION_H__
#define __AECP_AEM_CMD_GET_SET_CONFIGURATION_H__
int handle_cmd_set_configuration_milan_v12(struct aecp *aecp, int64_t now,
const void *m, int len);
int handle_cmd_get_configuration_common(struct aecp *aecp, int64_t now,
const void *m, int len);
#endif //__AECP_AEM_CMD_GET_SET_CONFIGURATION_H__

View file

@ -0,0 +1,183 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#include <limits.h>
#include <stdbool.h>
#include <inttypes.h>
#include <stdio.h>
#include "../aecp.h"
#include "../aecp-aem.h"
#include "../aecp-aem-state.h"
#include "../aecp-aem-descriptors.h"
#include "cmd-get-set-name.h"
#include "cmd-resp-helpers.h"
#include "reply-unsol-helpers.h"
/**
* \brief Different descriptor hold different position for a name
* Therefore different descriptors should be handled diffierently.
*/
static char *get_name_ptr(uint16_t desc_type, void *ptr, uint16_t name_index)
{
/* Handle Entity specifically due to multiple name fields */
if (desc_type == AVB_AEM_DESC_ENTITY) {
struct avb_aem_desc_entity *d = ptr;
/*
* IEEE 1722.1-2021 Table 7-38:
* 0: entity_name
* 1: group_name
* 2: serial_number
*/
if (name_index == 0) return d->entity_name;
if (name_index == 1) return d->group_name;
if (name_index == 2) return d->serial_number;
return NULL;
}
/* Handle Strings descriptor */
if (desc_type == AVB_AEM_DESC_STRINGS) {
if (name_index > 6)
return NULL;
return (char *)ptr + (name_index * 64);
}
/* Case when the name index should be forcibly 0 */
if (name_index != 0)
return NULL;
/* Exclude descriptors that do not start with object_name */
switch (desc_type) {
case AVB_AEM_DESC_STREAM_PORT_INPUT:
case AVB_AEM_DESC_STREAM_PORT_OUTPUT:
case AVB_AEM_DESC_EXTERNAL_PORT_INPUT:
case AVB_AEM_DESC_EXTERNAL_PORT_OUTPUT:
case AVB_AEM_DESC_INTERNAL_PORT_INPUT:
case AVB_AEM_DESC_INTERNAL_PORT_OUTPUT:
return NULL;
}
/*
* Most others (Configuration, Audio Unit, Stream Input/Output, AVB Interface,
* Clock Source, etc.) start with object_name[64] at offset 0.
*/
return ptr;
}
static int send_unsol_name(struct aecp *aecp,
const struct avb_packet_aecp_aem *p, const void *msg, int len)
{
uint8_t unsol_buf[512];
struct avb_ethernet_header *h_unsol = (void*)unsol_buf;
struct aecp_aem_base_info info = { 0 };
memcpy(unsol_buf, msg, len);
info.controller_entity_id = htobe64(p->aecp.controller_guid);
info.expire_timeout = INT64_MAX;
return reply_unsolicited_notifications(aecp, &info, unsol_buf, len, false);
}
/**
* IEEE 1722.1-2021 7.4.18 GET_NAME
* For now this is not handling UTF characters, only ASCII
*/
int handle_cmd_get_name_common(struct aecp *aecp, int64_t now,
const void *m, int len)
{
uint8_t buf[512];
struct server *server = aecp->server;
const struct avb_ethernet_header *h = m;
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
const struct avb_packet_aecp_aem_setget_name *cmd;
struct avb_ethernet_header *h_reply;
struct avb_packet_aecp_aem *p_reply;
struct avb_packet_aecp_aem_setget_name *reply;
struct descriptor *desc;
uint16_t desc_type, desc_id, name_index;
char *name_ptr;
cmd = (const struct avb_packet_aecp_aem_setget_name *)p->payload;
desc_type = ntohs(cmd->descriptor_type);
desc_id = ntohs(cmd->descriptor_index);
name_index = ntohs(cmd->name_index);
desc = server_find_descriptor(server, desc_type, desc_id);
if (desc == NULL)
return reply_status(aecp,
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
name_ptr = get_name_ptr(desc_type, desc->ptr, name_index);
if (name_ptr == NULL)
return reply_status(aecp,
AVB_AECP_AEM_STATUS_BAD_ARGUMENTS, m, len);
memcpy(buf, m, len);
h_reply = (struct avb_ethernet_header *)buf;
p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void);
reply = (struct avb_packet_aecp_aem_setget_name *)p_reply->payload;
/**
* IEEE 1722.1-2021: 7.4.17.1: The name does not contain a trailing NULL
* but if the name is less than 64 bytes in length then it is zero
* padded
*/
memcpy(reply->name, name_ptr, 64);
return reply_success(aecp, buf, len);
}
/**
* IEEE 1722.1-2021 7.4.17 SET_NAME
* For now this is not handling UTF characters, only ASCII
*/
int handle_cmd_set_name_common(struct aecp *aecp, int64_t now,
const void *m, int len)
{
struct server *server = aecp->server;
const struct avb_ethernet_header *h = m;
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
const struct avb_packet_aecp_aem_setget_name *cmd;
struct descriptor *desc;
uint16_t desc_type, desc_id, name_index;
char *name_ptr;
int rc;
cmd = (const struct avb_packet_aecp_aem_setget_name *)p->payload;
desc_type = ntohs(cmd->descriptor_type);
desc_id = ntohs(cmd->descriptor_index);
name_index = ntohs(cmd->name_index);
desc = server_find_descriptor(server, desc_type, desc_id);
if (desc == NULL)
return reply_status(aecp,
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
name_ptr = get_name_ptr(desc_type, desc->ptr, name_index);
if (name_ptr == NULL)
return reply_status(aecp,
AVB_AECP_AEM_STATUS_BAD_ARGUMENTS, m, len);
/**
* IEEE 1722.1-2021: 7.4.17.1: The name does not contain a trailing NULL
* but if the name is less than 64 bytes in length then it is zero
* padded
*/
memcpy(name_ptr, cmd->name, 64);
/** TODO: According to the specification, the string should alwasy be 0
* terminated, the goal would be to check whether a string is UTF-8 and
* that it is correctly zero terminitaed if less than 64 char, if not
* then a simple memcpy is enough */
rc = reply_success(aecp, m, len);
if (rc < 0)
return rc;
return send_unsol_name(aecp, p, m, len);
}

View file

@ -0,0 +1,13 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-License-Identifier: MIT */
#ifndef __AVB_AECP_AEM_CMD_GET_SET_NAME_H__
#define __AVB_AECP_AEM_CMD_GET_SET_NAME_H__
#include "../aecp.h"
int handle_cmd_set_name_common(struct aecp *aecp, int64_t now, const void *m, int len);
int handle_cmd_get_name_common(struct aecp *aecp, int64_t now, const void *m, int len);
#endif /* __AVB_AECP_AEM_CMD_GET_SET_NAME_H__ */

View file

@ -0,0 +1,154 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#include <limits.h>
#include <stdbool.h>
#include <inttypes.h>
#include "../aecp.h"
#include "../aecp-aem.h"
#include "../aecp-aem-state.h"
#include "../aecp-aem-descriptors.h"
#include "../aecp-aem-types.h"
#include "cmd-get-set-stream-format.h"
#include "cmd-resp-helpers.h"
#include "reply-unsol-helpers.h"
static int send_unsol_stream_format(struct aecp *aecp, void *msg, int len)
{
struct avb_ethernet_header *h_unsol = (void*)msg;
struct avb_packet_aecp_aem *p_unsol = SPA_PTROFF(h_unsol, sizeof(*h_unsol), void);
struct aecp_aem_base_info info = { 0 };
/* Set the originator controller ID to avoid echo */
info.controller_entity_id = htobe64(p_unsol->aecp.controller_guid);
info.expire_timeout = INT64_MAX;
return reply_unsolicited_notifications(aecp, &info, msg, len, false);
}
/**
* \see IEEE 1722.1-2021 7.4.10
* \see Milan V1.2 5.4.2.8 GET_STREAM_FORMAT
*/
int handle_cmd_get_stream_format_milan_v12(struct aecp *aecp, int64_t now,
const void *m, int len)
{
uint8_t buf[2048];
struct server *server = aecp->server;
const struct avb_ethernet_header *h = m;
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
const struct avb_packet_aecp_aem_setget_stream_format *get_cmd;
struct avb_ethernet_header *h_reply;
struct avb_packet_aecp_aem *p_reply;
struct avb_packet_aecp_aem_setget_stream_format *get_reply;
struct descriptor *desc;
struct avb_aem_desc_stream *stream_desc;
uint16_t desc_type, desc_id;
get_cmd = (const struct avb_packet_aecp_aem_setget_stream_format *)p->payload;
desc_type = ntohs(get_cmd->descriptor_type);
desc_id = ntohs(get_cmd->descriptor_id);
desc = server_find_descriptor(server, desc_type, desc_id);
if (desc == NULL)
return reply_status(aecp,
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
if (desc_type != AVB_AEM_DESC_STREAM_INPUT &&
desc_type != AVB_AEM_DESC_STREAM_OUTPUT)
return reply_status(aecp,
AVB_AECP_AEM_STATUS_BAD_ARGUMENTS, m, len);
stream_desc = (struct avb_aem_desc_stream *)desc->ptr;
memcpy(buf, m, len);
h_reply = (struct avb_ethernet_header *)buf;
p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void);
get_reply = (struct avb_packet_aecp_aem_setget_stream_format *)p_reply->payload;
get_reply->stream_format = stream_desc->current_format;
return reply_success(aecp, buf, len);
}
/**
* \see IEEE 1722.1-2021 7.4.9
* \see Milan V1.2 5.4.2.7 SET_STREAM_FORMAT
*/
int handle_cmd_set_stream_format_milan_v12(struct aecp *aecp, int64_t now,
const void *m, int len)
{
uint8_t buf[2048];
struct server *server = aecp->server;
const struct avb_ethernet_header *h = m;
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
const struct avb_packet_aecp_aem_setget_stream_format *set_cmd;
struct avb_ethernet_header *h_reply;
struct avb_packet_aecp_aem *p_reply;
struct avb_packet_aecp_aem_setget_stream_format *set_reply;
struct descriptor *desc;
struct avb_aem_desc_stream *stream_desc;
uint16_t desc_type, desc_id;
uint64_t new_format;
int i;
int rc;
bool found = false;
void *stream;
set_cmd = (const struct avb_packet_aecp_aem_setget_stream_format *)p->payload;
desc_type = ntohs(set_cmd->descriptor_type);
desc_id = ntohs(set_cmd->descriptor_id);
new_format = set_cmd->stream_format;
desc = server_find_descriptor(server, desc_type, desc_id);
if (desc == NULL)
return reply_status(aecp,
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
if (desc_type == AVB_AEM_DESC_STREAM_INPUT) {
struct aecp_aem_stream_input_state *state =
(struct aecp_aem_stream_input_state *)desc->ptr;
stream = &state->stream;
// TODO check if the stream is bound
} else if (desc_type == AVB_AEM_DESC_STREAM_OUTPUT) {
struct aecp_aem_stream_output_state *state =
(struct aecp_aem_stream_output_state *)desc->ptr;
stream = &state->stream;
// TODO check if the stream is STREAM_RUNNING
} else {
return reply_status(aecp,
AVB_AECP_AEM_STATUS_BAD_ARGUMENTS, m, len);
}
(void)stream;
stream_desc = (struct avb_aem_desc_stream *)desc->ptr;
for (i = 0; i < ntohs(stream_desc->number_of_formats); i++) {
if (stream_desc->stream_formats[i] == new_format) {
found = true;
break;
}
}
memcpy(buf, m, len);
h_reply = (struct avb_ethernet_header *)buf;
p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void);
set_reply = (struct avb_packet_aecp_aem_setget_stream_format*)p_reply->payload;
/** If this not found, return the current format */
if (!found) {
set_reply->stream_format = stream_desc->current_format;
return reply_status(aecp,
AVB_AECP_AEM_STATUS_BAD_ARGUMENTS, m, len);
}
stream_desc->current_format = new_format;
rc = reply_success(aecp, buf, len);
if (rc < 0)
return rc;
return send_unsol_stream_format(aecp, buf, len);
}

View file

@ -0,0 +1,16 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#ifndef __AVB_AECP_AEM_CMD_GET_SET_STREAM_FORMAT_H__
#define __AVB_AECP_AEM_CMD_GET_SET_STREAM_FORMAT_H__
#include "../aecp-aem.h"
int handle_cmd_set_stream_format_milan_v12(struct aecp *aecp, int64_t now,
const void *m, int len);
int handle_cmd_get_stream_format_milan_v12(struct aecp *aecp, int64_t now,
const void *m, int len);
#endif //__AVB_AECP_AEM_CMD_GET_SET_STREAM_FORMAT_H__

View file

@ -0,0 +1,180 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#include <limits.h>
#include <stdbool.h>
#include <inttypes.h>
#include "../aecp.h"
#include "../aecp-aem.h"
#include "../aecp-aem-state.h"
#include "../aecp-aem-descriptors.h"
#include "../aecp-aem-types.h"
#include "cmd-lock-entity.h"
#include "cmd-resp-helpers.h"
#include "reply-unsol-helpers.h"
static int handle_unsol_lock_common(struct aecp *aecp,
struct aecp_aem_lock_state *lock, bool internal)
{
uint8_t buf[512];
void *m = buf;
// struct aecp_aem_regis_unsols
struct avb_ethernet_header *h = m;
struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
struct avb_packet_aecp_aem_lock *ae;
size_t len = sizeof(*h) + sizeof(*p) + sizeof(*ae);
int rc;
memset(buf, 0, sizeof(buf));
ae = (struct avb_packet_aecp_aem_lock*)p->payload;
if (!lock->is_locked) {
ae->locked_guid = 0;
ae->flags = htonl(AECP_AEM_LOCK_ENTITY_FLAG_UNLOCK);
lock->is_locked = false;
lock->base_info.expire_timeout = LONG_MAX;
} else {
ae->locked_guid = htobe64(lock->locked_id);
ae->flags = 0;
}
AVB_PACKET_AEM_SET_COMMAND_TYPE(p, AVB_AECP_AEM_CMD_LOCK_ENTITY);
/** Setup the packet for the unsolicited notification*/
rc = reply_unsolicited_notifications(aecp, &lock->base_info, buf, len, internal);
if (rc) {
pw_log_error("Unsolicited notification failed \n");
}
return rc;
}
static int handle_unsol_lock_entity_milanv12(struct aecp *aecp, struct descriptor *desc,
uint64_t ctrler_id)
{
int rc = -1;
struct aecp_aem_entity_milan_state *entity_state;
struct aecp_aem_lock_state *lock;
entity_state = desc->ptr;
lock = &entity_state->state.lock_state;
lock->base_info.controller_entity_id = ctrler_id;
rc = handle_unsol_lock_common(aecp, lock, false);
return rc;
}
/* LOCK_ENTITY */
/* Milan v1.2, Sec. 5.4.2.2; IEEE 1722.1-2021, Sec. 7.4.2*/
int handle_cmd_lock_entity_milan_v12(struct aecp *aecp, int64_t now, const void *m, int len)
{
uint8_t buf[512];
struct server *server = aecp->server;
const struct avb_ethernet_header *h = m;
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
const struct avb_packet_aecp_aem_lock *ae;
struct avb_ethernet_header *h_reply;
struct avb_packet_aecp_aem *p_reply;
struct avb_packet_aecp_aem_lock *ae_reply;
struct descriptor *desc;
struct aecp_aem_entity_milan_state *entity_state;
struct aecp_aem_lock_state *lock;
uint16_t desc_type, desc_id;
bool reply_locked = false;
uint64_t ctrler_id;
ae = (const struct avb_packet_aecp_aem_lock*)p->payload;
desc_type = ntohs(ae->descriptor_type);
desc_id = ntohs(ae->descriptor_id);
ctrler_id = htobe64(p->aecp.controller_guid) ;
desc = server_find_descriptor(server, desc_type, desc_id);
if (desc == NULL)
return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
entity_state = desc->ptr;
lock = &entity_state->state.lock_state;
if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0) {
/*
* Milan v1.2: The PAAD-AE shall not allow locking another descriptor
* than the ENTITY descriptor (NOT_SUPPORTED shall be returned in
* this case).
*/
return reply_not_supported(aecp, m, len);
}
if (ae->flags & htonl(AECP_AEM_LOCK_ENTITY_FLAG_UNLOCK)) {
/* Entity is not locked */
if (!lock->is_locked) {
return reply_success(aecp, m, len);
}
/* Unlocking by the controller which locked */
if (ctrler_id == lock->locked_id) {
pw_log_debug("Unlocking\n");
lock->is_locked = false;
lock->locked_id = 0;
} else {
/* Unlocking by a controller that did not lock?*/
if (ctrler_id != lock->locked_id) {
pw_log_debug("Already unlocked by %" PRIx64, lock->locked_id);
reply_locked = true;
} else {
// TODO: Can this statement be reached?
pw_log_error("Invalid state\n");
spa_assert(0);
}
}
} else {
// Is it really locked?
if (!lock->is_locked ||
lock->base_info.expire_timeout < now) {
lock->base_info.expire_timeout = now +
AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND * SPA_NSEC_PER_SEC;
lock->is_locked = true;
lock->locked_id = ctrler_id;
} else {
// If the lock is taken again by device
if (ctrler_id == lock->locked_id) {
lock->base_info.expire_timeout +=
AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND;
lock->is_locked = true;
} else {
// Cannot lock because already locked
pw_log_debug("but the device is locked by %" PRIx64, lock->locked_id);
reply_locked = true;
}
}
}
/* Forge the response for the entity that is locking the device */
memcpy(buf, m, len);
h_reply = (struct avb_ethernet_header *) buf;
p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void);
ae_reply = (struct avb_packet_aecp_aem_lock*)p_reply->payload;
ae_reply->locked_guid = htobe64(lock->locked_id);
if (reply_locked) {
return reply_entity_locked(aecp, buf, len);
}
if (reply_success(aecp, buf, len)) {
pw_log_debug("Failed sending success reply\n");
}
/* Then update the state using the system */
return handle_unsol_lock_entity_milanv12(aecp, desc, ctrler_id);
}

View file

@ -0,0 +1,21 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#ifndef __AVB_AECP_AEM_LOCK_H__
#define __AVB_AECP_AEM_LOCK_H__
#define AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND (60UL)
#define AECP_AEM_LOCK_ENTITY_FLAG_UNLOCK (1)
#include <stdint.h>
/**
* @brief Command handling will generate the response for the lock command
*/
int handle_cmd_lock_entity_milan_v12(struct aecp *aecp, int64_t now,
const void *m, int len);
#endif //__AVB_AECP_AEM_LOCK_H__

View file

@ -0,0 +1,68 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#include <pipewire/log.h>
#include <inttypes.h>
#include "../aecp-aem.h"
#include "../aecp-aem-state.h"
#include "../aecp-aem-milan.h"
#include "cmd-register-unsolicited-notifications.h"
#include "cmd-resp-helpers.h"
int handle_cmd_register_unsol_notif_milan_v12(struct aecp *aecp, int64_t now,
const void *m, int len)
{
const struct avb_ethernet_header *h = m;
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
struct descriptor *desc;
struct aecp_aem_entity_milan_state *entity_state;
struct aecp_aem_unsol_notification_state *unsol;
uint64_t controller_id = htobe64(p->aecp.controller_guid);
const uint32_t ctrler_max = AECP_AEM_MILAN_MAX_CONTROLLER;
uint16_t index;
desc = server_find_descriptor(aecp->server, AVB_AEM_DESC_ENTITY, 0);
if (desc == NULL)
return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
entity_state = (struct aecp_aem_entity_milan_state *) desc->ptr;
unsol = entity_state->unsol_notif_state;
/** First check if the controller was already registered */
for (index = 0; index < ctrler_max; index++) {
uint64_t ctrl_eid = unsol[index].ctrler_entity_id;
bool ctrler_reged = unsol[index].is_registered;
if ((ctrl_eid == controller_id) && ctrler_reged) {
pw_log_debug("controller 0x%"PRIx64", already registered",
controller_id);
return reply_success(aecp, m, len);
}
}
/** When one slot is in the array is available use it */
for (index = 0; index < ctrler_max; index++) {
if (!unsol[index].is_registered) {
break;
}
}
/** Reach the maximum controller allocated */
if (index == ctrler_max) {
return reply_no_resources(aecp, m, len);
}
unsol[index].ctrler_entity_id = controller_id;
memcpy(unsol[index].ctrler_mac_addr, h->src, sizeof(h->src));
unsol[index].is_registered = true;
unsol[index].port_id = 0;
unsol[index].next_seq_id = 0;
pw_log_info("Unsol registration for 0x%"PRIx64, controller_id);
return reply_success(aecp, m, len);
}

View file

@ -0,0 +1,23 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#ifndef __AVB_REGISTER_UNSOLICITED_NOTIFICATIONS_H__
#define __AVB_REGISTER_UNSOLICITED_NOTIFICATIONS_H__
#include <stdint.h>
/**
* @brief Command handling will generate the response to the
* received command when registering the a controller from
* the list of subscribed entities.
*
* @see IEEE1722.1-2021 Section 7.4.37 .
* @see Milan V1.2 Section 5.4.2.21 .
*/
int handle_cmd_register_unsol_notif_milan_v12(struct aecp *aecp, int64_t now,
const void *m, int len);
#endif // __AVB_REGISTER_UNSOLICITED_NOTIFICATIONS_H__

View file

@ -0,0 +1,111 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#ifndef __AVB_AECP_AEM_HELPERS_H__
#define __AVB_AECP_AEM_HELPERS_H__
#include <stdint.h>
#include <string.h>
#include <spa/utils/defs.h>
#include <pipewire/log.h>
#include "../aecp.h"
static inline int reply_status(struct aecp *aecp, int status, const void *m, int len)
{
uint8_t buf[2048];
struct server *server = aecp->server;
struct avb_ethernet_header *h = (void*)buf;
struct avb_packet_aecp_header *reply = SPA_PTROFF(h, sizeof(*h), void);
memcpy(buf, m, len);
pw_log_debug("status 0x%x\n", status);
AVB_PACKET_AECP_SET_MESSAGE_TYPE(reply, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
AVB_PACKET_AECP_SET_STATUS(reply, status);
return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len);
}
static inline int reply_entity_locked(struct aecp *aecp, const void *m, int len)
{
pw_log_warn("reply entity locked");
return reply_status(aecp, AVB_AECP_AEM_STATUS_ENTITY_LOCKED, m, len);
}
/** \brief The function is be directly hooked with the cmd_info structure */
static inline int direct_reply_entiy_locked(struct aecp *aecp, int64_t now,
const void *m, int len)
{
return reply_entity_locked(aecp, m, len);
}
static inline int reply_not_implemented(struct aecp *aecp, const void *m, int len)
{
pw_log_warn("reply not implementing");
return reply_status(aecp, AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED, m, len);
}
/** \brief The function is be directly hooked with the cmd_info structure */
static inline int direct_reply_not_implemented(struct aecp *aecp, int64_t now,
const void *m, int len)
{
return reply_not_implemented(aecp, m, len);
}
static inline int reply_not_supported(struct aecp *aecp, const void *m, int len)
{
pw_log_warn("reply not supported");
return reply_status(aecp, AVB_AECP_AEM_STATUS_NOT_SUPPORTED, m, len);
}
/** \brief The function is be directly hooked with the cmd_info structure */
static inline int direct_reply_not_supported(struct aecp *aecp, int64_t now,
const void *m, int len)
{
return reply_not_supported(aecp, m, len);
}
static inline int reply_no_resources(struct aecp *aecp, const void *m, int len)
{
pw_log_warn("reply no resources");
return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_RESOURCES, m, len);
}
/** \brief The function is be directly hooked with the cmd_info structure */
static inline int direct_reply_no_resources(struct aecp *aecp, int64_t now,
const void *m, int len)
{
return reply_no_resources(aecp, m, len);
}
static inline int reply_bad_arguments(struct aecp *aecp, const void *m, int len)
{
pw_log_warn("reply bad arguments");
return reply_status(aecp, AVB_AECP_AEM_STATUS_BAD_ARGUMENTS, m, len);
}
/** \brief The function is be directly hooked with the cmd_info structure */
static inline int direct_reply_bad_arguments(struct aecp *aecp, int64_t now,
const void *m, int len)
{
return reply_bad_arguments(aecp, m, len);
}
static inline int reply_success(struct aecp *aecp, const void *m, int len)
{
return reply_status(aecp, AVB_AECP_AEM_STATUS_SUCCESS, m, len);
}
/** \brief The function is be directly hooked with the cmd_info structure */
static inline int direct_reply_success(struct aecp *aecp, int64_t now,
const void *m, int len)
{
return reply_success(aecp, m, len);
}
#endif //__AVB_AECP_AEM_HELPERS_H__

View file

@ -0,0 +1,153 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#include "../aecp-aem.h"
#include "../aecp-aem-state.h"
#include "../internal.h"
#include "reply-unsol-helpers.h"
#include <pipewire/log.h>
#define AECP_UNSOL_BUFFER_SIZE (128U)
#define AECP_AEM_MIN_PACKET_LENGTH (64U)
static int reply_unsol_get_specific_info(struct aecp *aecp, struct descriptor *desc,
struct aecp_aem_unsol_notification_state **unsol_state, size_t *count)
{
enum avb_mode mode = aecp->server->avb_mode;
switch (mode) {
case AVB_MODE_LEGACY:
pw_log_error("Not implemented\n");
break;
case AVB_MODE_MILAN_V12:
struct aecp_aem_entity_milan_state *entity_state;
entity_state = desc->ptr;
*unsol_state = entity_state->unsol_notif_state;
*count = AECP_AEM_MILAN_MAX_CONTROLLER;
return 0;
default:
pw_log_error("Invalid avb_mode %d\n", mode);
break;
}
return -1;
}
static int reply_unsol_send(struct aecp *aecp, uint64_t controller_id,
void *packet, size_t len, bool internal)
{
size_t ctrler_index;
int rc = 0;
struct avb_ethernet_header *h;
struct avb_packet_aecp_aem *p;
struct aecp_aem_unsol_notification_state *unsol_state;
struct descriptor *desc;
size_t max_ctrler;
desc = server_find_descriptor(aecp->server, AVB_AEM_DESC_ENTITY, 0);
if (desc == NULL) {
pw_log_error("Could not find the ENTITY descriptor 0");
return -EINVAL;
}
rc = reply_unsol_get_specific_info(aecp, desc, &unsol_state, &max_ctrler);
if (rc) {
return -EINVAL;
}
h = (struct avb_ethernet_header*) packet;
p = SPA_PTROFF(h, sizeof(*h), void);
/* Loop through all the unsol entities. */
for (ctrler_index = 0; ctrler_index < max_ctrler; ctrler_index++)
{
if (!unsol_state[ctrler_index].is_registered) {
pw_log_debug("Not registered %zu", ctrler_index);
continue;
}
if ((controller_id == unsol_state[ctrler_index].ctrler_entity_id)
&& !internal) {
/* Do not send unsolicited if that the one triggering
changes this is not a timeout. */
pw_log_debug("Do not send twice of %"PRIx64" %"PRIx64,
controller_id,
unsol_state[ctrler_index].ctrler_entity_id);
continue;
}
p->aecp.controller_guid =
htobe64(unsol_state[ctrler_index].ctrler_entity_id);
p->aecp.sequence_id = htons(unsol_state[ctrler_index].next_seq_id);
unsol_state[ctrler_index].next_seq_id++;
rc = avb_server_send_packet(aecp->server,
unsol_state[ctrler_index].ctrler_mac_addr,
AVB_TSN_ETH, packet, len);
if (rc) {
pw_log_error("while sending packet to %"PRIx64,
unsol_state[ctrler_index].ctrler_entity_id);
return rc;
}
}
return rc;
}
static void reply_unsol_notifications_prepare(struct aecp *aecp,
uint8_t *buf, void *packet, size_t len)
{
struct avb_ethernet_header *h;
struct avb_packet_aecp_aem *p;
size_t ctrl_data_length;
/* Here the value of 12 is the delta between the target_entity_id and
start of the AECP message specific data. */
ctrl_data_length = len - (sizeof(*h) + sizeof(*p)) +
AVB_PACKET_CONTROL_DATA_OFFSET;
h = (struct avb_ethernet_header*) packet;
p = SPA_PTROFF(h, sizeof(*h), void);
p->aecp.hdr.subtype = AVB_SUBTYPE_AECP;
AVB_PACKET_AECP_SET_MESSAGE_TYPE(&p->aecp, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
AVB_PACKET_SET_VERSION(&p->aecp.hdr, 0);
AVB_PACKET_AECP_SET_STATUS(&p->aecp, AVB_AECP_AEM_STATUS_SUCCESS);
AVB_PACKET_SET_LENGTH(&p->aecp.hdr, ctrl_data_length);
p->u = 1;
p->aecp.target_guid = htobe64(aecp->server->entity_id);
}
/**
* @brief Sends unsolicited notifications. Does not sends information unless to
* the controller id unless an internal change has happenned (timeout, action
* etc)
*
*/
int reply_unsolicited_notifications(struct aecp *aecp,
struct aecp_aem_base_info *b_state, void *packet, size_t len,
bool internal)
{
uint8_t buf[AECP_UNSOL_BUFFER_SIZE];
if (len < AECP_AEM_MIN_PACKET_LENGTH) {
memset(buf, 0, AECP_AEM_MIN_PACKET_LENGTH);
memcpy(buf, packet, len);
len = AECP_AEM_MIN_PACKET_LENGTH;
packet = buf;
}
/** Retrieve the entity descriptor */
reply_unsol_notifications_prepare(aecp, buf, packet, len);
return reply_unsol_send(aecp, b_state->controller_entity_id, packet, len,
internal);
}

View file

@ -0,0 +1,23 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#ifndef __AVB_REPLY_UNSOL_HELPER_H__
#define __AVB_REPLY_UNSOL_HELPER_H__
#include <stdint.h>
#include <pipewire/log.h>
/**
* @brief Sends unsolicited notifications. Does not sends information unless to
* the controller id unless an internal change has happenned (timeout, action
* etc)
* @see Milan V1.2 Section 5.4.2.21
* @see IEEE 1722.1-2021 7.5.2 ( Unsolicited Notifications )
*/
int reply_unsolicited_notifications(struct aecp *aecp,
struct aecp_aem_base_info *b_state, void *packet, size_t len,
bool internal);
#endif // __AVB_REPLY_UNSOL_HELPER_H__

View file

@ -0,0 +1,171 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#ifndef __AECP_AEM_CONTROLS_H__
#define __AECP_AEM_CONTROLS_H__
// TODO, When all the AVB needs to be supported then addition needs to be don here
/* IEEE 1722.1-2021, Table 7-121 - Value Types*/
#define AECP_AEM_CTRL_LINEAR_INT8 0x0000
#define AECP_AEM_CTRL_LINEAR_UINT8 0x0001
#define AECP_AEM_CTRL_LINEAR_INT16 0x0002
#define AECP_AEM_CTRL_LINEAR_UINT16 0x0003
#define AECP_AEM_CTRL_LINEAR_INT32 0x0004
#define AECP_AEM_CTRL_LINEAR_UINT32 0x0005
#define AECP_AEM_CTRL_LINEAR_INT64 0x0006
#define AECP_AEM_CTRL_LINEAR_UINT64 0x0007
#define AECP_AEM_CTRL_LINEAR_FLOAT 0x0008
#define AECP_AEM_CTRL_LINEAR_DOUBLE 0x0009
#define AECP_AEM_CTRL_SELECTOR_INT8 0x000a
#define AECP_AEM_CTRL_SELECTOR_UINT8 0x000b
#define AECP_AEM_CTRL_SELECTOR_INT16 0x000c
#define AECP_AEM_CTRL_SELECTOR_UINT16 0x000d
#define AECP_AEM_CTRL_SELECTOR_INT32 0x000e
#define AECP_AEM_CTRL_SELECTOR_UINT32 0x000f
#define AECP_AEM_CTRL_SELECTOR_INT64 0x0010
#define AECP_AEM_CTRL_SELECTOR_UINT64 0x0011
#define AECP_AEM_CTRL_SELECTOR_FLOAT 0x0012
#define AECP_AEM_CTRL_SELECTOR_DOUBLE 0x0013
#define AECP_AEM_CTRL_SELECTOR_STRING 0x0014
#define AEPC_AEM_CTRL_ARRAY_INT8 0x0015
#define AEPC_AEM_CTRL_ARRAY_UINT8 0x0016
#define AEPC_AEM_CTRL_ARRAY_INT16 0x0017
#define AEPC_AEM_CTRL_ARRAY_UINT16 0x0018
#define AEPC_AEM_CTRL_ARRAY_INT32 0x0019
#define AEPC_AEM_CTRL_ARRAY_UINT32 0x001a
#define AEPC_AEM_CTRL_ARRAY_INT64 0x001b
#define AEPC_AEM_CTRL_ARRAY_UINT64 0x001c
#define AEPC_AEM_CTRL_ARRAY_FLOAT 0x001d
#define AEPC_AEM_CTRL_ARRAY_DOUBLE 0x001e
#define AECP_AEM_CTRL_UTF8 0x001f
#define AECP_AEM_CTRL_BODE_PLOT 0x0020
#define AECP_AEM_CTRL_SMPTE_TIME 0x0021
#define AECP_AEM_CTRL_SAMPLE_RATE 0x0022
#define AECP_AEM_CTRL_GPTP_TIME 0x0023
#define AECP_AEM_CTRL_CTRL_VENDOR 0x3ffe
/* Definition of the UNIT codes */
/* IEEE 1722.1-2021, Table 7-75 - Codes for Unitless quantities*/
#define AECP_AEM_CTRL_UNIT_CODE_UNITLESS (0)
#define AECP_AEM_CTRL_UNIT_CODE_COUNT (1)
#define AECP_AEM_CTRL_UNIT_CODE_PERCENT (2)
#define AECP_AEM_CTRL_UNIT_CODE_FSTOP (3)
#define AECP_AEM_CTRL_FORMAT_VENDOR (0)
#define AECP_AEM_CTRL_FORMAT_AVDECC (1)
/* IEEE 1722.1-2021, Sec. 7.3.5 Control Types */
#define AEM_CTRL_TYPE_ENABLE 0x90E0F00000000000ULL
#define AEM_CTRL_TYPE_IDENTIFY 0x90E0F00000000001ULL
#define AEM_CTRL_TYPE_MUTE 0x90E0F00000000002ULL
#define AEM_CTRL_TYPE_INVERT 0x90E0F00000000003ULL
#define AEM_CTRL_TYPE_GAIN 0x90E0F00000000004ULL
#define AEM_CTRL_TYPE_ATTENUATE 0x90E0F00000000005ULL
#define AEM_CTRL_TYPE_DELAY 0x90E0F00000000006ULL
#define AEM_CTRL_TYPE_SRC_MODE 0x90E0F00000000007ULL
#define AEM_CTRL_TYPE_SNAPSHOT 0x90E0F00000000008ULL
#define AEM_CTRL_TYPE_POW_LINE_FREQ 0x90E0F00000000009ULL
#define AEM_CTRL_TYPE_POWER_STATUS 0x90E0F0000000000AULL
#define AEM_CTRL_TYPE_FAN_STATUS 0x90E0F0000000000BULL
#define AEM_CTRL_TYPE_TEMPERATURE 0x90E0F0000000000CULL
#define AEM_CTRL_TYPE_ALTITUDE 0x90E0F0000000000DULL
#define AEM_CTRL_TYPE_ABSOLUTE_HUMIDITY 0x90E0F0000000000EULL
#define AEM_CTRL_TYPE_RELATIVE_HUMIDITY 0x90E0F0000000000FULL
#define AEM_CTRL_TYPE_ORIENTATION 0x90E0F00000000010ULL
#define AEM_CTRL_TYPE_VELOCITY 0x90E0F00000000011ULL
#define AEM_CTRL_TYPE_ACCELERATION 0x90E0F00000000012ULL
#define AEM_CTRL_TYPE_FILTER_RESPONSE 0x90E0F00000000013ULL
#define AEM_CTRL_TYPE_BAROMETRIC_PRESSURE 0x90E0F00000000014ULL
#define AEM_CTRL_TYPE_MANUFACTURER_URL 0x90E0F00000000015ULL
#define AEM_CTRL_TYPE_ENTITY_URL 0x90E0F00000000016ULL
#define AEM_CTRL_TYPE_CONFIGURATION_URL 0x90E0F00000000017ULL
#define AEM_CTRL_TYPE_GENERIC_URL 0x90E0F00000000018ULL
#define AEM_CTRL_TYPE_FAULT 0x90E0F00000000019ULL
#define AEM_CTRL_TYPE_CONTROLLER_TARGET_ENTITY 0x90E0F0000000001AULL
#define AEM_CTRL_TYPE_CONTROLLER_TARGET_OBJECT 0x90E0F0000000001BULL
#define AEM_CTRL_TYPE_LATENCY_COMPENSATION 0x90E0F0000000001CULL
#define AEM_CTRL_TYPE_PANPOT 0x90E0F00000010000ULL
#define AEM_CTRL_TYPE_PHANTOM 0x90E0F00000010001ULL
#define AEM_CTRL_TYPE_AUDIO_SCALE 0x90E0F00000010002ULL
#define AEM_CTRL_TYPE_AUDIO_METERS 0x90E0F00000010003ULL
#define AEM_CTRL_TYPE_AUDIO_SPECTRUM 0x90E0F00000010004ULL
#define AEM_CTRL_TYPE_SCANNING_MODE 0x90E0F00000020000ULL
#define AEM_CTRL_TYPE_AUTO_EXP_MODE 0x90E0F00000020001ULL
#define AEM_CTRL_TYPE_AUTO_EXP_PRIO 0x90E0F00000020002ULL
#define AEM_CTRL_TYPE_EXP_TIME 0x90E0F00000020003ULL
#define AEM_CTRL_TYPE_FOCUS 0x90E0F00000020004ULL
#define AEM_CTRL_TYPE_FOCUS_AUTO 0x90E0F00000020005ULL
#define AEM_CTRL_TYPE_IRIS 0x90E0F00000020006ULL
#define AEM_CTRL_TYPE_ZOOM 0x90E0F00000020007ULL
#define AEM_CTRL_TYPE_PRIVACY 0x90E0F00000020008ULL
#define AEM_CTRL_TYPE_BACKLIGHT 0x90E0F00000020009ULL
#define AEM_CTRL_TYPE_BRIGHTNESS 0x90E0F0000002000AULL
#define AEM_CTRL_TYPE_CONTRAST 0x90E0F0000002000BULL
#define AEM_CTRL_TYPE_HUE 0x90E0F0000002000CULL
#define AEM_CTRL_TYPE_SATURATION 0x90E0F0000002000DULL
#define AEM_CTRL_TYPE_SHARPNESS 0x90E0F0000002000EULL
#define AEM_CTRL_TYPE_GAMMA 0x90E0F0000002000FULL
#define AEM_CTRL_TYPE_WHITE_BAL_TEMP 0x90E0F00000020010ULL
#define AEM_CTRL_TYPE_WHITE_BAL_TEMP_AUTO 0x90E0F00000020011ULL
#define AEM_CTRL_TYPE_WHITE_BAL_COMP 0x90E0F00000020012ULL
#define AEM_CTRL_TYPE_WHITE_BAL_COMP_AUTO 0x90E0F00000020013ULL
#define AEM_CTRL_TYPE_DIGITAL_ZOOM 0x90E0F00000020014ULL
#define AEM_CTRL_TYPE_MEDIA_PLAYLIST 0x90E0F00000030000ULL
#define AEM_CTRL_TYPE_MEDIA_PLAYLIST_NAME 0x90E0F00000030001ULL
#define AEM_CTRL_TYPE_MEDIA_DISK 0x90E0F00000030002ULL
#define AEM_CTRL_TYPE_MEIDA_DISK_NAME 0x90E0F00000030003ULL
#define AEM_CTRL_TYPE_TRACK 0x90E0F00000030004ULL
#define AEM_CTRL_TYPE_TRACK_NAME 0x90E0F00000030005ULL
#define AEM_CTRL_TYPE_SPEED 0x90E0F00000030006ULL
#define AEM_CTRL_TYPE_MEDIA_SAMPLE_POSITION 0x90E0F00000030007ULL
#define AEM_CTRL_TYPE_MEDIA_PLAYBACK_TRANSPORT 0x90E0F00000030008ULL
#define AEM_CTRL_TYPE_MEDIA_RECORD_TRANSPORT 0x90E0F00000030009ULL
#define AEM_CTRL_TYPE_FREQUENCY 0x90E0F00000040000ULL
#define AEM_CTRL_TYPE_MODULATION 0x90E0F00000040001ULL
#define AEM_CTRL_TYPE_POLARIZATION 0x90E0F00000040002ULL
#define AEM_CTRL_TYPE_BAUD_RATE 0x90E0F00000050000ULL
#define AEM_CTRL_TYPE_BIT_WIDTH 0x90E0F00000050001ULL
#define AEM_CTRL_TYPE_PARITY 0x90E0F00000050002ULL
#define AEM_CTRL_TYPE_STOP_BITS 0x90E0F00000050003ULL
#define AEM_CTRL_TYPE_INTERFACE_OPERATIONAL 0x90E0F00000060000ULL
#define AEM_CTRL_TYPE_INTERFACE_MEDIA_OPTIONS 0x90E0F00000060001ULL
#define AEM_CTRL_TYPE_INTERFACE_MEDIA_STATUS 0x90E0F00000060002ULL
#define AEM_CTRL_TYPE_INTERFACE_NETWORK_NAME 0x90E0F00000060003ULL
#define AEM_CTRL_TYPE_FQTSS_DELTA_BANDWIDTH 0x90E0F00000060004ULL
#define AEM_CTRL_TYPE_FQTSS_ADMIN_IDLE_SLOPE 0x90E0F00000060005ULL
#define AEM_CTRL_TYPE_FQTSS_OPER_IDLE_SLOPE 0x90E0F00000060006ULL
#define AEM_CTRL_TYPE_FQTSS_PORT_TRANSMIT_RATE 0x90E0F00000060007ULL
#define AEM_CTRL_TYPE_FQTSS_CLASS_MEASUREMENT_INTERVAL 0x90E0F00000060008ULL
#define AEM_CTRL_TYPE_FQTSS_LOCK_CLASS_BANDWIDTH 0x90E0F00000060009ULL
/* Identify
* IEEE 1722.1, Sec. 7.3.5.2 - Identify Control (IDENTIFY)
* Milan v1.2, Sec. 5.4.5.4 - Identification notification
*/
#define AECP_AEM_CTRL_IDENTIFY_UNIT_MULTIPLY 0
#define AECP_AEM_CTRL_IDENTIFY_UNIT_CODE AECP_AEM_CTRL_UNIT_CODE_UNITLESS
#define AECP_AEM_CTRL_IDENTIFY_STEP (255)
#define AECP_AEM_CTRL_IDENTIFY_MINIMUM (0)
#define AECP_AEM_CTRL_IDENTIFY_MAXIMUM (255)
#define BASE_CTRL_TYPE_MAC { 0x90, 0xe0, 0xf0, 0x01, 0x00, 0x00 };
// 1722.1-2021, Annex B Table B.1
#define BASE_CTRL_IDENTIFY_MAC { 0x90, 0xe0, 0xf0, 0x00, 0x00, 0x01 };
#endif //__AECP_AEM_CONTROLS_H__

View file

@ -1,12 +1,16 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki (alexandre.malki@kebag-logic.com) */
/* SPDX-License-Identifier: MIT */
#ifndef AVB_AECP_AEM_DESCRIPTORS_H
#define AVB_AECP_AEM_DESCRIPTORS_H
#include "internal.h"
#include <stdint.h>
/*
* IEEE 1722.1-2021, Table 7-1 - Descriptor Types
*/
#define AVB_AEM_DESC_ENTITY 0x0000
#define AVB_AEM_DESC_CONFIGURATION 0x0001
#define AVB_AEM_DESC_AUDIO_UNIT 0x0002
@ -45,8 +49,17 @@
#define AVB_AEM_DESC_SIGNAL_TRANSCODER 0x0023
#define AVB_AEM_DESC_CLOCK_DOMAIN 0x0024
#define AVB_AEM_DESC_CONTROL_BLOCK 0x0025
/** IEEE 1722.1-2021 Table-7 has up to descriptor 0x0029, reserved for future */
#define AVB_AEM_DESC_LAST_RESERVED_17221 0x0029
#define AVB_AEM_DESC_INVALID 0xffff
/* IEEE 1722.1-2021, Table 7-24 - Port Flags */
// No flag is not defined in table
#define AVB_AEM_PORT_FLAG_NO_FLAG 0x0000
#define AVB_AEM_PORT_FLAG_CLOCK_SYNC_SOURCE 0x0001
#define AVB_AEM_PORT_FLAG_ASYNC_SAMPLE_RATE_CONV 0x0002
#define AVB_AEM_PORT_FLAG_SYNC_SAMPLE_RATE_CONV 0x0004
struct avb_aem_desc_entity {
uint64_t entity_id;
uint64_t entity_model_id;
@ -127,6 +140,42 @@ struct avb_aem_desc_audio_unit {
struct avb_aem_desc_sampling_rate sampling_rates[0];
} __attribute__ ((__packed__));
/* IEEE 1722.1-2021, Table 7-28 - AUDIO_CLUSTER format values */
#define AVB_AEM_AUDIO_CLUSTER_TYPE_IEC60958 0x00
#define AVB_AEM_AUDIO_CLUSTER_TYPE_MBLA 0x40
#define AVB_AEM_AUDIO_CLUSTER_TYPE_MIDI 0x80
#define AVB_AEM_AUDIO_CLUSTER_TYPE_SMPTE 0x88
struct avb_aem_desc_audio_cluster {
char object_name[64];
uint16_t localized_description;
uint16_t signal_type;
uint16_t signal_index;
uint16_t signal_output;
uint32_t path_latency;
uint32_t block_latency;
uint16_t channel_count;
uint8_t format;
uint8_t aes3_data_type_ref;
uint16_t aes3_data_type;
} __attribute__ ((__packed__));
#define AVB_AEM_AUDIO_MAPPING_FORMAT_OFFSET (8)
struct avb_aem_audio_mapping_format {
uint16_t mapping_stream_index;
uint16_t mapping_stream_channel;
uint16_t mapping_cluster_offset;
uint16_t mapping_cluster_channel;
} __attribute__ ((__packed__));
struct avb_aem_desc_audio_map {
uint16_t mapping_offset;
uint16_t number_of_mappings;
struct avb_aem_audio_mapping_format mappings[0];
} __attribute__ ((__packed__));
#define AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE (1u<<0)
#define AVB_AEM_DESC_STREAM_FLAG_CLASS_A (1u<<1)
#define AVB_AEM_DESC_STREAM_FLAG_CLASS_B (1u<<2)
@ -197,6 +246,15 @@ struct avb_aem_desc_clock_source {
uint16_t clock_source_location_index;
} __attribute__ ((__packed__));
struct avb_aem_desc_clock_domain {
char object_name[64];
uint16_t localized_description;
uint16_t clock_source_index;
uint16_t descriptor_counts_offset;
uint16_t clock_sources_count;
uint16_t clock_sources[0];
} __attribute__ ((__packed__));
struct avb_aem_desc_locale {
char locale_identifier[64];
uint16_t number_of_strings;
@ -224,4 +282,34 @@ struct avb_aem_desc_stream_port {
uint16_t base_map;
} __attribute__ ((__packed__));
struct avb_aem_desc_value_format {
uint8_t minimum;
uint8_t maximum;
uint8_t step;
uint8_t default_value;
uint8_t current_value;
uint16_t unit;
uint16_t localized_description;
} __attribute__ ((__packed__));
struct avb_aem_desc_control {
char object_name[64];
uint16_t localized_description;
uint32_t block_latency;
uint32_t control_latency;
uint16_t control_domain;
uint16_t control_value_type;
uint64_t control_type;
uint32_t reset_time;
uint16_t descriptor_counts_offset;
uint16_t number_of_values;
uint16_t signal_type;
uint16_t signal_index;
uint16_t signal_output;
struct avb_aem_desc_value_format value_format[0];
} __attribute__ ((__packed__));
#endif /* AVB_AECP_AEM_DESCRIPTORS_H */

View file

@ -0,0 +1,6 @@
#ifndef __AVB_AECP_AEM_MILAN_H__
#define __AVB_AECP_AEM_MILAN_H__
#define AECP_AEM_MILAN_MAX_CONTROLLER 16
#endif //__AVB_AECP_AEM_MILAN_H__

View file

@ -0,0 +1,180 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#ifndef AVB_AECP_AEM_STATE_H
#define AVB_AECP_AEM_STATE_H
#include <stdint.h>
#include <stdbool.h>
#include "aecp-aem-descriptors.h"
#include "aecp-aem-milan.h"
#include "stream.h"
/**
* The way structure are organised in a "derived" manner.
* Each of the state structure must directly "castable" into the descriptor
* that the state variable relies upon.
*
* For instance, if a stream_input descriptor is created, the state structure
* stream_input_state needs to be created as follow:
*
* struct stream_input_state {
* struct stream_input_desc ...
* ...
* This way it's possible to get directly from the AEM command the descriptor
* and the state without having to create a mechanism for this.
*/
/**
* \brief The base information would be required
* for descriptor that needs to udpate for unsollictied
* notificaction.
*/
struct aecp_aem_state_base {
/**
* Originator of the control
* This is needed so the unsoolictied notification does not send back SUCCESS
* to the originator of of the unsolicited notification
*/
uint64_t controller_entity_id;
/**
* To avoid sending on every change for unsol notifications, only once a
* second
*/
int64_t last_update;
/** timeout absolute time*/
int64_t expire_timeout;
};
/**
* \brief the structure keeps track of the registered controller entities
*/
struct aecp_aem_unsol_notification_state {
/**
* The controller is that is locking this system
*/
uint64_t ctrler_entity_id;
/**
* mac Address of the controller
*/
uint8_t ctrler_mac_addr[6];
/**
* Port where the registeration originated from
*/
uint8_t port_id;
/***
* The sequence ID of the next unsolicited notification
*/
uint16_t next_seq_id;
/**
* Actual value of the lock, get removed when unregistere or expired.
*/
bool is_registered;
};
struct aecp_aem_base_info {
/** Originator of the control
* This is needed so the unsoolictied notification does not send back SUCCESS
* to the originator of of the unsolicited notification */
uint64_t controller_entity_id;
/**
* To avoid sending on every change for unsol notifications, only once a
* a second
* */
int64_t last_update;
/** timeout absolute time*/
int64_t expire_timeout;
};
struct aecp_aem_lock_state {
struct aecp_aem_base_info base_info;
/**
* the entity id that is locking this system
*/
uint64_t locked_id;
/**
* actual value of the lock
*/
bool is_locked;
};
/**
* \brief the generic entity state common for all flavor of AVB
*/
struct aecp_aem_entity_state {
struct avb_aem_desc_entity desc;
struct aecp_aem_lock_state lock_state;
};
/**
* \brief Milan implementation of the entity
*/
struct aecp_aem_entity_milan_state {
struct aecp_aem_entity_state state;
struct aecp_aem_unsol_notification_state unsol_notif_state[AECP_AEM_MILAN_MAX_CONTROLLER];
};
/**
* \brief Legacy AVB implementation of the entity
*/
struct aecp_aem_entity_legacy_avb_state {
struct aecp_aem_entity_state state;
};
/**
* \brief The stream inputs are specified as in the IEEE-1722.1-2021
* Table 7-156
*/
struct aecp_aem_stream_input_counters {
struct aecp_aem_state_base base_state;
uint32_t media_locked;
uint32_t media_unlocked;
uint32_t stream_interrupted;
uint32_t seq_mistmatch;
uint32_t media_reset;
/** Timestamp Uncertain */
uint32_t tu;
uint32_t unsupported_format;
uint32_t late_timestamp;
uint32_t early_timestamp;
uint32_t frame_rx;
};
struct aecp_aem_stream_input_state {
struct avb_aem_desc_stream desc;
struct aecp_aem_stream_input_counters counters;
struct stream stream;
};
struct aecp_aem_stream_output_counters {
struct aecp_aem_state_base base_state;
uint32_t stream_start;
uint32_t stream_stop;
uint32_t media_reset;
uint32_t tu;
uint32_t frame_tx;
};
struct aecp_aem_stream_output_state {
struct avb_aem_desc_stream desc;
struct aecp_aem_stream_output_counters counters;
struct stream stream;
};
#endif // AVB_AECP_AEM_STATE_H

View file

@ -0,0 +1,112 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#ifndef __AVB_AECP_AEM_TYPES_H__
#define __AVB_AECP_AEM_TYPES_H__
/*
* IEEE 1722.1-2021, Table 7-141 - status field
*/
#define AVB_AECP_AEM_STATUS_SUCCESS 0
#define AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED 1
#define AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR 2
#define AVB_AECP_AEM_STATUS_ENTITY_LOCKED 3
#define AVB_AECP_AEM_STATUS_ENTITY_ACQUIRED 4
#define AVB_AECP_AEM_STATUS_NOT_AUTHENTICATED 5
#define AVB_AECP_AEM_STATUS_AUTHENTICATION_DISABLED 6
#define AVB_AECP_AEM_STATUS_BAD_ARGUMENTS 7
#define AVB_AECP_AEM_STATUS_NO_RESOURCES 8
#define AVB_AECP_AEM_STATUS_IN_PROGRESS 9
#define AVB_AECP_AEM_STATUS_ENTITY_MISBEHAVING 10
#define AVB_AECP_AEM_STATUS_NOT_SUPPORTED 11
#define AVB_AECP_AEM_STATUS_STREAM_IS_RUNNING 12
#define AECP_AEM_STRLEN_MAX (64)
/** IEEE 1722.1-2021, Table 7-140 - Command Codes */
// TODO: Update me
#define AVB_AECP_AEM_CMD_ACQUIRE_ENTITY 0x0000
#define AVB_AECP_AEM_CMD_LOCK_ENTITY 0x0001
#define AVB_AECP_AEM_CMD_ENTITY_AVAILABLE 0x0002
#define AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE 0x0003
#define AVB_AECP_AEM_CMD_READ_DESCRIPTOR 0x0004
#define AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR 0x0005
#define AVB_AECP_AEM_CMD_SET_CONFIGURATION 0x0006
#define AVB_AECP_AEM_CMD_GET_CONFIGURATION 0x0007
#define AVB_AECP_AEM_CMD_SET_STREAM_FORMAT 0x0008
#define AVB_AECP_AEM_CMD_GET_STREAM_FORMAT 0x0009
#define AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT 0x000a
#define AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT 0x000b
#define AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT 0x000c
#define AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT 0x000d
#define AVB_AECP_AEM_CMD_SET_STREAM_INFO 0x000e
#define AVB_AECP_AEM_CMD_GET_STREAM_INFO 0x000f
#define AVB_AECP_AEM_CMD_SET_NAME 0x0010
#define AVB_AECP_AEM_CMD_GET_NAME 0x0011
#define AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID 0x0012
#define AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID 0x0013
#define AVB_AECP_AEM_CMD_SET_SAMPLING_RATE 0x0014
#define AVB_AECP_AEM_CMD_GET_SAMPLING_RATE 0x0015
#define AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE 0x0016
#define AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE 0x0017
#define AVB_AECP_AEM_CMD_SET_CONTROL 0x0018
#define AVB_AECP_AEM_CMD_GET_CONTROL 0x0019
#define AVB_AECP_AEM_CMD_INCREMENT_CONTROL 0x001a
#define AVB_AECP_AEM_CMD_DECREMENT_CONTROL 0x001b
#define AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR 0x001c
#define AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR 0x001d
#define AVB_AECP_AEM_CMD_SET_MIXER 0x001e
#define AVB_AECP_AEM_CMD_GET_MIXER 0x001f
#define AVB_AECP_AEM_CMD_SET_MATRIX 0x0020
#define AVB_AECP_AEM_CMD_GET_MATRIX 0x0021
#define AVB_AECP_AEM_CMD_START_STREAMING 0x0022
#define AVB_AECP_AEM_CMD_STOP_STREAMING 0x0023
#define AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION 0x0024
#define AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION 0x0025
#define AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION 0x0026
#define AVB_AECP_AEM_CMD_GET_AVB_INFO 0x0027
#define AVB_AECP_AEM_CMD_GET_AS_PATH 0x0028
#define AVB_AECP_AEM_CMD_GET_COUNTERS 0x0029
#define AVB_AECP_AEM_CMD_REBOOT 0x002a
#define AVB_AECP_AEM_CMD_GET_AUDIO_MAP 0x002b
#define AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS 0x002c
#define AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS 0x002d
#define AVB_AECP_AEM_CMD_GET_VIDEO_MAP 0x002e
#define AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS 0x002f
#define AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS 0x0030
#define AVB_AECP_AEM_CMD_GET_SENSOR_MAP 0x0031
#define AVB_AECP_AEM_CMD_ADD_SENSOR_MAPPINGS 0x0032
#define AVB_AECP_AEM_CMD_REMOVE_SENSOR_MAPPINGS 0x0033
#define AVB_AECP_AEM_CMD_START_OPERATION 0x0034
#define AVB_AECP_AEM_CMD_ABORT_OPERATION 0x0035
#define AVB_AECP_AEM_CMD_OPERATION_STATUS 0x0036
#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY 0x0037
#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY 0x0038
#define AVB_AECP_AEM_CMD_AUTH_GET_KEY_LIST 0x0039
#define AVB_AECP_AEM_CMD_AUTH_GET_KEY 0x003a
#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY_TO_CHAIN 0x003b
#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY_FROM_CHAIN 0x003c
#define AVB_AECP_AEM_CMD_AUTH_GET_KEYCHAIN_LIST 0x003d
#define AVB_AECP_AEM_CMD_AUTH_GET_IDENTITY 0x003e
#define AVB_AECP_AEM_CMD_AUTH_ADD_TOKEN 0x003f
#define AVB_AECP_AEM_CMD_AUTH_DELETE_TOKEN 0x0040
#define AVB_AECP_AEM_CMD_AUTHENTICATE 0x0041
#define AVB_AECP_AEM_CMD_DEAUTHENTICATE 0x0042
#define AVB_AECP_AEM_CMD_ENABLE_TRANSPORT_SECURITY 0x0043
#define AVB_AECP_AEM_CMD_DISABLE_TRANSPORT_SECURITY 0x0044
#define AVB_AECP_AEM_CMD_ENABLE_STREAM_ENCRYPTION 0x0045
#define AVB_AECP_AEM_CMD_DISABLE_STREAM_ENCRYPTION 0x0046
#define AVB_AECP_AEM_CMD_SET_MEMORY_OBJECT_LENGTH 0x0047
#define AVB_AECP_AEM_CMD_GET_MEMORY_OBJECT_LENGTH 0x0048
#define AVB_AECP_AEM_CMD_SET_STREAM_BACKUP 0x0049
#define AVB_AECP_AEM_CMD_GET_STREAM_BACKUP 0x004a
#define AVB_AECP_AEM_CMD_GET_DYNAMIC_INFO 0x004b
#define AVB_AECP_AEM_CMD_EXPANSION 0x7fff
#define AVB_AEM_ACQUIRE_ENTITY_PERSISTENT_FLAG (1<<0)
#endif //__AVB_AECP_AEM_TYPES_H__

View file

@ -1,36 +1,28 @@
/* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki */
/* SPDX-License-Identifier: MIT */
#include "aecp-aem.h"
#include "aecp-aem-descriptors.h"
#include "aecp-aem-cmds-resps/cmd-resp-helpers.h"
#include "utils.h"
static int reply_status(struct aecp *aecp, int status, const void *m, int len)
{
struct server *server = aecp->server;
uint8_t buf[len];
struct avb_ethernet_header *h = (void*)buf;
struct avb_packet_aecp_header *reply = SPA_PTROFF(h, sizeof(*h), void);
/* The headers including the command and response of the system */
#include "aecp-aem-cmds-resps/cmd-available.h"
#include "aecp-aem-cmds-resps/cmd-get-set-configuration.h"
#include "aecp-aem-cmds-resps/cmd-lock-entity.h"
#include "aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.h"
#include "aecp-aem-cmds-resps/cmd-deregister-unsolicited-notifications.h"
#include "aecp-aem-cmds-resps/cmd-get-set-name.h"
#include "aecp-aem-cmds-resps/cmd-get-set-stream-format.h"
#include "aecp-aem-cmds-resps/cmd-get-set-clock-source.h"
#include "aecp-aem-cmds-resps/cmd-lock-entity.h"
memcpy(buf, m, len);
AVB_PACKET_AECP_SET_MESSAGE_TYPE(reply, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
AVB_PACKET_AECP_SET_STATUS(reply, status);
return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len);
}
static int reply_not_implemented(struct aecp *aecp, const void *m, int len)
{
return reply_status(aecp, AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED, m, len);
}
static int reply_success(struct aecp *aecp, const void *m, int len)
{
return reply_status(aecp, AVB_AECP_AEM_STATUS_SUCCESS, m, len);
}
/* ACQUIRE_ENTITY */
static int handle_acquire_entity(struct aecp *aecp, const void *m, int len)
static int handle_acquire_entity_avb_legacy(struct aecp *aecp, int64_t now,
const void *m, int len)
{
struct server *server = aecp->server;
const struct avb_packet_aecp_aem *p = m;
@ -45,7 +37,8 @@ static int handle_acquire_entity(struct aecp *aecp, const void *m, int len)
desc = server_find_descriptor(server, desc_type, desc_id);
if (desc == NULL)
return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
return reply_status(aecp,
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0)
return reply_not_implemented(aecp, m, len);
@ -54,7 +47,8 @@ static int handle_acquire_entity(struct aecp *aecp, const void *m, int len)
}
/* LOCK_ENTITY */
static int handle_lock_entity(struct aecp *aecp, const void *m, int len)
static int handle_lock_entity_avb_legacy(struct aecp *aecp, int64_t now,
const void *m, int len)
{
struct server *server = aecp->server;
const struct avb_packet_aecp_aem *p = m;
@ -78,7 +72,7 @@ static int handle_lock_entity(struct aecp *aecp, const void *m, int len)
}
/* READ_DESCRIPTOR */
static int handle_read_descriptor(struct aecp *aecp, const void *m, int len)
static int handle_read_descriptor_common(struct aecp *aecp, int64_t now, const void *m, int len)
{
struct server *server = aecp->server;
const struct avb_ethernet_header *h = m;
@ -120,7 +114,8 @@ static int handle_read_descriptor(struct aecp *aecp, const void *m, int len)
}
/* GET_AVB_INFO */
static int handle_get_avb_info(struct aecp *aecp, const void *m, int len)
static int handle_get_avb_info_common(struct aecp *aecp, int64_t now,
const void *m, int len)
{
struct server *server = aecp->server;
const struct avb_ethernet_header *h = m;
@ -168,95 +163,227 @@ static int handle_get_avb_info(struct aecp *aecp, const void *m, int len)
return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size);
}
/* AEM_COMMAND */
/* TODO in the case the AVB mode allows you to modifiy a Milan readonly
descriptor, then create a array of is_readonly depending on the mode used */
static const char * const cmd_names[] = {
[AVB_AECP_AEM_CMD_ACQUIRE_ENTITY] = "acquire-entity",
[AVB_AECP_AEM_CMD_LOCK_ENTITY] = "lock-entity",
[AVB_AECP_AEM_CMD_ENTITY_AVAILABLE] = "entity-available",
[AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE] = "controller-available",
[AVB_AECP_AEM_CMD_READ_DESCRIPTOR] = "read-descriptor",
[AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR] = "write-descriptor",
[AVB_AECP_AEM_CMD_SET_CONFIGURATION] = "set-configuration",
[AVB_AECP_AEM_CMD_GET_CONFIGURATION] = "get-configuration",
[AVB_AECP_AEM_CMD_SET_STREAM_FORMAT] = "set-stream-format",
[AVB_AECP_AEM_CMD_GET_STREAM_FORMAT] = "get-stream-format",
[AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT] = "set-video-format",
[AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT] = "get-video-format",
[AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT] = "set-sensor-format",
[AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT] = "get-sensor-format",
[AVB_AECP_AEM_CMD_SET_STREAM_INFO] = "set-stream-info",
[AVB_AECP_AEM_CMD_GET_STREAM_INFO] = "get-stream-info",
[AVB_AECP_AEM_CMD_SET_NAME] = "set-name",
[AVB_AECP_AEM_CMD_GET_NAME] = "get-name",
[AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID] = "set-association-id",
[AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID] = "get-association-id",
[AVB_AECP_AEM_CMD_SET_SAMPLING_RATE] = "set-sampling-rate",
[AVB_AECP_AEM_CMD_GET_SAMPLING_RATE] = "get-sampling-rate",
[AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE] = "set-clock-source",
[AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE] = "get-clock-source",
[AVB_AECP_AEM_CMD_SET_CONTROL] = "set-control",
[AVB_AECP_AEM_CMD_GET_CONTROL] = "get-control",
[AVB_AECP_AEM_CMD_INCREMENT_CONTROL] = "increment-control",
[AVB_AECP_AEM_CMD_DECREMENT_CONTROL] = "decrement-control",
[AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR] = "set-signal-selector",
[AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR] = "get-signal-selector",
[AVB_AECP_AEM_CMD_SET_MIXER] = "set-mixer",
[AVB_AECP_AEM_CMD_GET_MIXER] = "get-mixer",
[AVB_AECP_AEM_CMD_SET_MATRIX] = "set-matrix",
[AVB_AECP_AEM_CMD_GET_MATRIX] = "get-matrix",
[AVB_AECP_AEM_CMD_START_STREAMING] = "start-streaming",
[AVB_AECP_AEM_CMD_STOP_STREAMING] = "stop-streaming",
[AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION] = "register-unsolicited-notification",
[AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION] = "deregister-unsolicited-notification",
[AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION] = "identify-notification",
[AVB_AECP_AEM_CMD_GET_AVB_INFO] = "get-avb-info",
[AVB_AECP_AEM_CMD_GET_AS_PATH] = "get-as-path",
[AVB_AECP_AEM_CMD_GET_COUNTERS] = "get-counters",
[AVB_AECP_AEM_CMD_REBOOT] = "reboot",
[AVB_AECP_AEM_CMD_GET_AUDIO_MAP] = "get-audio-map",
[AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS] = "add-audio-mappings",
[AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS] = "remove-audio-mappings",
[AVB_AECP_AEM_CMD_GET_VIDEO_MAP] = "get-video-map",
[AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS] = "add-video-mappings",
[AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS] = "remove-video-mappings",
[AVB_AECP_AEM_CMD_GET_SENSOR_MAP] = "get-sensor-map"
};
/* AEM_COMMAND */
struct cmd_info {
uint16_t type;
const char *name;
int (*handle) (struct aecp *aecp, const void *p, int len);
/**
* \brief Is Readonly is a hint used to decide whether or not the
* unsollocited notifications is to be sent for this descriptor or not
*/
const bool is_readonly;
/**
* \brief handle a command for a specific descriptor
*/
int (*handle_command) (struct aecp *aecp, int64_t now, const void *p,
int len);
/**
* \brief Response are sent upon changes that occure internally
* and that are then propagated to the network and are not
* unsollicited notifications
*/
int (*handle_response) (struct aecp *aecp, int64_t now, const void *p,
int len);
/**
* \brief Handling of the unsolicited notification that are used
* to inform subscribed controller about the change of status of
* a specific descriptor or the counter associted with it
*/
int (*handle_unsol_timer) (struct aecp *aecp, int64_t now);
};
static const struct cmd_info cmd_info[] = {
{ AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, "acquire-entity", handle_acquire_entity, },
{ AVB_AECP_AEM_CMD_LOCK_ENTITY, "lock-entity", handle_lock_entity, },
{ AVB_AECP_AEM_CMD_ENTITY_AVAILABLE, "entity-available", NULL, },
{ AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE, "controller-available", NULL, },
{ AVB_AECP_AEM_CMD_READ_DESCRIPTOR, "read-descriptor", handle_read_descriptor, },
{ AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR, "write-descriptor", NULL, },
{ AVB_AECP_AEM_CMD_SET_CONFIGURATION, "set-configuration", NULL, },
{ AVB_AECP_AEM_CMD_GET_CONFIGURATION, "get-configuration", NULL, },
{ AVB_AECP_AEM_CMD_SET_STREAM_FORMAT, "set-stream-format", NULL, },
{ AVB_AECP_AEM_CMD_GET_STREAM_FORMAT, "get-stream-format", NULL, },
{ AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT, "set-video-format", NULL, },
{ AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT, "get-video-format", NULL, },
{ AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT, "set-sensor-format", NULL, },
{ AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT, "get-sensor-format", NULL, },
{ AVB_AECP_AEM_CMD_SET_STREAM_INFO, "set-stream-info", NULL, },
{ AVB_AECP_AEM_CMD_GET_STREAM_INFO, "get-stream-info", NULL, },
{ AVB_AECP_AEM_CMD_SET_NAME, "set-name", NULL, },
{ AVB_AECP_AEM_CMD_GET_NAME, "get-name", NULL, },
{ AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID, "set-association-id", NULL, },
{ AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID, "get-association-id", NULL, },
{ AVB_AECP_AEM_CMD_SET_SAMPLING_RATE, "set-sampling-rate", NULL, },
{ AVB_AECP_AEM_CMD_GET_SAMPLING_RATE, "get-sampling-rate", NULL, },
{ AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE, "set-clock-source", NULL, },
{ AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE, "get-clock-source", NULL, },
{ AVB_AECP_AEM_CMD_SET_CONTROL, "set-control", NULL, },
{ AVB_AECP_AEM_CMD_GET_CONTROL, "get-control", NULL, },
{ AVB_AECP_AEM_CMD_INCREMENT_CONTROL, "increment-control", NULL, },
{ AVB_AECP_AEM_CMD_DECREMENT_CONTROL, "decrement-control", NULL, },
{ AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR, "set-signal-selector", NULL, },
{ AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR, "get-signal-selector", NULL, },
{ AVB_AECP_AEM_CMD_SET_MIXER, "set-mixer", NULL, },
{ AVB_AECP_AEM_CMD_GET_MIXER, "get-mixer", NULL, },
{ AVB_AECP_AEM_CMD_SET_MATRIX, "set-matrix", NULL, },
{ AVB_AECP_AEM_CMD_GET_MATRIX, "get-matrix", NULL, },
{ AVB_AECP_AEM_CMD_START_STREAMING, "start-streaming", NULL, },
{ AVB_AECP_AEM_CMD_STOP_STREAMING, "stop-streaming", NULL, },
{ AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION, "register-unsolicited-notification", NULL, },
{ AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION, "deregister-unsolicited-notification", NULL, },
{ AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION, "identify-notification", NULL, },
{ AVB_AECP_AEM_CMD_GET_AVB_INFO, "get-avb-info", handle_get_avb_info, },
{ AVB_AECP_AEM_CMD_GET_AS_PATH, "get-as-path", NULL, },
{ AVB_AECP_AEM_CMD_GET_COUNTERS, "get-counters", NULL, },
{ AVB_AECP_AEM_CMD_REBOOT, "reboot", NULL, },
{ AVB_AECP_AEM_CMD_GET_AUDIO_MAP, "get-audio-map", NULL, },
{ AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS, "add-audio-mappings", NULL, },
{ AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS, "remove-audio-mappings", NULL, },
{ AVB_AECP_AEM_CMD_GET_VIDEO_MAP, "get-video-map", NULL, },
{ AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS, "add-video-mappings", NULL, },
{ AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS, "remove-video-mappings", NULL, },
{ AVB_AECP_AEM_CMD_GET_SENSOR_MAP, "get-sensor-map", NULL, }
};
static inline const struct cmd_info *find_cmd_info(uint16_t type, const char *name)
{
SPA_FOR_EACH_ELEMENT_VAR(cmd_info, i) {
if ((name == NULL && type == i->type) ||
(name != NULL && spa_streq(name, i->name)))
return i;
#define AECP_AEM_HANDLE_CMD(cmd, readonly_desc, handle_exec) \
[cmd] = { \
.is_readonly = readonly_desc, \
.handle_command = handle_exec \
}
return NULL;
}
#define AECP_AEM_HANDLE_RESP(cmd, handle_cmd, handle_exec_unsol) \
[cmd] = { \
.name = name_str, \
.is_readonly = false, \
.handle_response = handle_cmd \
}
#define AECP_AEM_CMD_RESP_AND_UNSOL(cmd, readonly_desc, handle_exec, \
handle_exec_unsol) \
[cmd] = { \
.name = name_str, \
.is_readonly = readonly_desc, \
.handle = handle_exec, \
.handle_unsol = handle_exec_unsol \
}
static const struct cmd_info cmd_info_avb_legacy[] = {
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, true,
handle_acquire_entity_avb_legacy),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_LOCK_ENTITY, true,
handle_lock_entity_avb_legacy),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_CONFIGURATION, false,
handle_cmd_get_configuration_common),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_READ_DESCRIPTOR, true,
handle_read_descriptor_common),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_AVB_INFO, true,
handle_get_avb_info_common),
};
static const struct cmd_info cmd_info_milan_v12[] = {
/** Milan V1.2 should not implement acquire */
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, true,
direct_reply_not_supported),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_LOCK_ENTITY, false,
handle_cmd_lock_entity_milan_v12),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ENTITY_AVAILABLE, true,
handle_cmd_entity_available_milan_v12),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_STREAM_FORMAT, false,
handle_cmd_set_stream_format_milan_v12),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_STREAM_FORMAT, true,
handle_cmd_get_stream_format_milan_v12),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_CONFIGURATION, false,
handle_cmd_set_configuration_milan_v12),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_CONFIGURATION, false,
handle_cmd_get_configuration_common),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_READ_DESCRIPTOR, true,
handle_read_descriptor_common),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION,
false, handle_cmd_register_unsol_notif_milan_v12),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION,
false, handle_cmd_deregister_unsol_notif_milan_v12),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_AVB_INFO, true,
handle_get_avb_info_common),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_NAME, false,
handle_cmd_set_name_common),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_NAME, true,
handle_cmd_get_name_common),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE, false,
handle_cmd_set_clock_source_milan_v12),
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE, true,
handle_cmd_get_clock_source_milan_v12),
};
static const struct {
const struct cmd_info *cmd_info;
size_t count;
} cmd_info_modes[AVB_MODE_MAX] = {
[AVB_MODE_LEGACY] = {
.cmd_info = cmd_info_avb_legacy,
.count = SPA_N_ELEMENTS(cmd_info_avb_legacy),
},
[AVB_MODE_MILAN_V12] = {
.cmd_info = cmd_info_milan_v12,
.count = SPA_N_ELEMENTS(cmd_info_milan_v12),
},
};
int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len)
{
const struct avb_ethernet_header *h = m;
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
uint16_t cmd_type;
struct server *server = aecp->server;
const struct cmd_info *info;
struct timespec ts_now = {0};
int64_t now;
cmd_type = AVB_PACKET_AEM_GET_COMMAND_TYPE(p);
info = find_cmd_info(cmd_type, NULL);
if (info == NULL)
pw_log_info("mode: %s aem command %s",
get_avb_mode_str(server->avb_mode), cmd_names[cmd_type]);
if (cmd_info_modes[server->avb_mode].count <= cmd_type) {
pw_log_warn("Too many %d vs exp. %zu\n", cmd_type,
cmd_info_modes[server->avb_mode].count);
return reply_not_implemented(aecp, m, len);
}
info = &cmd_info_modes[server->avb_mode].cmd_info[cmd_type];
if (!info || !info->handle_command )
return reply_not_implemented(aecp, m, len);
pw_log_info("aem command %s", info->name);
if (info->handle == NULL)
return reply_not_implemented(aecp, m, len);
if (clock_gettime(CLOCK_TAI, &ts_now)) {
pw_log_warn("clock_gettime(CLOCK_TAI): %m\n");
}
return info->handle(aecp, m, len);
now = SPA_TIMESPEC_TO_NSEC(&ts_now);
return info->handle_command(aecp, now, m, len);
}
int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len)

View file

@ -6,99 +6,7 @@
#define AVB_AEM_H
#include "aecp.h"
#define AVB_AECP_AEM_STATUS_SUCCESS 0
#define AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED 1
#define AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR 2
#define AVB_AECP_AEM_STATUS_ENTITY_LOCKED 3
#define AVB_AECP_AEM_STATUS_ENTITY_ACQUIRED 4
#define AVB_AECP_AEM_STATUS_NOT_AUTHENTICATED 5
#define AVB_AECP_AEM_STATUS_AUTHENTICATION_DISABLED 6
#define AVB_AECP_AEM_STATUS_BAD_ARGUMENTS 7
#define AVB_AECP_AEM_STATUS_NO_RESOURCES 8
#define AVB_AECP_AEM_STATUS_IN_PROGRESS 9
#define AVB_AECP_AEM_STATUS_ENTITY_MISBEHAVING 10
#define AVB_AECP_AEM_STATUS_NOT_SUPPORTED 11
#define AVB_AECP_AEM_STATUS_STREAM_IS_RUNNING 12
#define AVB_AECP_AEM_CMD_ACQUIRE_ENTITY 0x0000
#define AVB_AECP_AEM_CMD_LOCK_ENTITY 0x0001
#define AVB_AECP_AEM_CMD_ENTITY_AVAILABLE 0x0002
#define AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE 0x0003
#define AVB_AECP_AEM_CMD_READ_DESCRIPTOR 0x0004
#define AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR 0x0005
#define AVB_AECP_AEM_CMD_SET_CONFIGURATION 0x0006
#define AVB_AECP_AEM_CMD_GET_CONFIGURATION 0x0007
#define AVB_AECP_AEM_CMD_SET_STREAM_FORMAT 0x0008
#define AVB_AECP_AEM_CMD_GET_STREAM_FORMAT 0x0009
#define AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT 0x000a
#define AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT 0x000b
#define AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT 0x000c
#define AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT 0x000d
#define AVB_AECP_AEM_CMD_SET_STREAM_INFO 0x000e
#define AVB_AECP_AEM_CMD_GET_STREAM_INFO 0x000f
#define AVB_AECP_AEM_CMD_SET_NAME 0x0010
#define AVB_AECP_AEM_CMD_GET_NAME 0x0011
#define AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID 0x0012
#define AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID 0x0013
#define AVB_AECP_AEM_CMD_SET_SAMPLING_RATE 0x0014
#define AVB_AECP_AEM_CMD_GET_SAMPLING_RATE 0x0015
#define AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE 0x0016
#define AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE 0x0017
#define AVB_AECP_AEM_CMD_SET_CONTROL 0x0018
#define AVB_AECP_AEM_CMD_GET_CONTROL 0x0019
#define AVB_AECP_AEM_CMD_INCREMENT_CONTROL 0x001a
#define AVB_AECP_AEM_CMD_DECREMENT_CONTROL 0x001b
#define AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR 0x001c
#define AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR 0x001d
#define AVB_AECP_AEM_CMD_SET_MIXER 0x001e
#define AVB_AECP_AEM_CMD_GET_MIXER 0x001f
#define AVB_AECP_AEM_CMD_SET_MATRIX 0x0020
#define AVB_AECP_AEM_CMD_GET_MATRIX 0x0021
#define AVB_AECP_AEM_CMD_START_STREAMING 0x0022
#define AVB_AECP_AEM_CMD_STOP_STREAMING 0x0023
#define AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION 0x0024
#define AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION 0x0025
#define AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION 0x0026
#define AVB_AECP_AEM_CMD_GET_AVB_INFO 0x0027
#define AVB_AECP_AEM_CMD_GET_AS_PATH 0x0028
#define AVB_AECP_AEM_CMD_GET_COUNTERS 0x0029
#define AVB_AECP_AEM_CMD_REBOOT 0x002a
#define AVB_AECP_AEM_CMD_GET_AUDIO_MAP 0x002b
#define AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS 0x002c
#define AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS 0x002d
#define AVB_AECP_AEM_CMD_GET_VIDEO_MAP 0x002e
#define AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS 0x002f
#define AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS 0x0030
#define AVB_AECP_AEM_CMD_GET_SENSOR_MAP 0x0031
#define AVB_AECP_AEM_CMD_ADD_SENSOR_MAPPINGS 0x0032
#define AVB_AECP_AEM_CMD_REMOVE_SENSOR_MAPPINGS 0x0033
#define AVB_AECP_AEM_CMD_START_OPERATION 0x0034
#define AVB_AECP_AEM_CMD_ABORT_OPERATION 0x0035
#define AVB_AECP_AEM_CMD_OPERATION_STATUS 0x0036
#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY 0x0037
#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY 0x0038
#define AVB_AECP_AEM_CMD_AUTH_GET_KEY_LIST 0x0039
#define AVB_AECP_AEM_CMD_AUTH_GET_KEY 0x003a
#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY_TO_CHAIN 0x003b
#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY_FROM_CHAIN 0x003c
#define AVB_AECP_AEM_CMD_AUTH_GET_KEYCHAIN_LIST 0x003d
#define AVB_AECP_AEM_CMD_AUTH_GET_IDENTITY 0x003e
#define AVB_AECP_AEM_CMD_AUTH_ADD_TOKEN 0x003f
#define AVB_AECP_AEM_CMD_AUTH_DELETE_TOKEN 0x0040
#define AVB_AECP_AEM_CMD_AUTHENTICATE 0x0041
#define AVB_AECP_AEM_CMD_DEAUTHENTICATE 0x0042
#define AVB_AECP_AEM_CMD_ENABLE_TRANSPORT_SECURITY 0x0043
#define AVB_AECP_AEM_CMD_DISABLE_TRANSPORT_SECURITY 0x0044
#define AVB_AECP_AEM_CMD_ENABLE_STREAM_ENCRYPTION 0x0045
#define AVB_AECP_AEM_CMD_DISABLE_STREAM_ENCRYPTION 0x0046
#define AVB_AECP_AEM_CMD_SET_MEMORY_OBJECT_LENGTH 0x0047
#define AVB_AECP_AEM_CMD_GET_MEMORY_OBJECT_LENGTH 0x0048
#define AVB_AECP_AEM_CMD_SET_STREAM_BACKUP 0x0049
#define AVB_AECP_AEM_CMD_GET_STREAM_BACKUP 0x004a
#define AVB_AECP_AEM_CMD_EXPANSION 0x7fff
#define AVB_AEM_ACQUIRE_ENTITY_PERSISTENT_FLAG (1<<0)
#include "aecp-aem-types.h"
struct avb_packet_aecp_aem_acquire {
uint32_t flags;
@ -114,6 +22,12 @@ struct avb_packet_aecp_aem_lock {
uint16_t descriptor_id;
} __attribute__ ((__packed__));
struct avb_packet_aecp_aem_available {
uint32_t flags;
uint64_t acquired_controller_guid;
uint64_t lock_controller_guid;
} __attribute__ ((__packed__));
struct avb_packet_aecp_aem_read_descriptor {
uint16_t configuration;
uint8_t reserved[2];
@ -315,9 +229,11 @@ struct avb_packet_aecp_aem {
uint8_t payload[0];
} __attribute__ ((__packed__));
#define AVB_PACKET_AEM_SET_COMMAND_TYPE(p,v) ((p)->cmd1 = ((v) >> 8),(p)->cmd2 = (v))
#define AVB_PACKET_CONTROL_DATA_OFFSET (12U)
#define AVB_PACKET_AEM_GET_COMMAND_TYPE(p) ((p)->cmd1 << 8 | (p)->cmd2)
#define AVB_PACKET_AEM_SET_COMMAND_TYPE(p,v) ((p)->cmd1 = ((v) >> 8),(p)->cmd2 = (v))
#define AVB_PACKET_AEM_GET_COMMAND_TYPE(p) ((p)->cmd1 << 8 | (p)->cmd2)
int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len);
int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len);

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