Compare commits

...

100 commits

Author SHA1 Message Date
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
75 changed files with 4819 additions and 725 deletions

View file

@ -547,11 +547,49 @@ Below is an explanation of the options that can be tuned in the sample converter
\parblock \parblock
The quality of the resampler. from 0 to 14, the default is 4. 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 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 (much) more CPU consumption and more ringing. The default quality of 4 has been selected
between quality and performance with no artifacts that are well below the audible range. 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. 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 \endparblock
@PAR@ node-prop resample.disable = false @PAR@ node-prop resample.disable = false
@ -559,39 +597,86 @@ Disable the resampler entirely. The node will only be able to negotiate with the
when the samplerates are compatible. when the samplerates are compatible.
@PAR@ node-prop resample.window = exp @PAR@ node-prop resample.window = exp
\parblock
The resampler window function to use. By default an exponential window function is used The resampler window function to use. By default an exponential window function is used
that gives a good balance between complexitiy and quality. that gives a good balance between complexitiy and quality.
You can also specify a blackman or kaiser window, both with different tradeoffs. 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 @PAR@ node-prop resample.cutoff = 0.0
The resampler cutoff frequency. A value of 0.0 will use a predefined value based on \parblock
the resampler quality. 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 @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 number of taps. A value of 0 will use a predefined value based on
the resampler quality or other window function parameters. 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 @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 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. 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 @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 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. 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 @PAR@ node-prop resample.param.kaiser.stopband-attenuation = 0.0
The kaiser window stopband attenuation parameter. A default value of 0.0 will use a \parblock
The kaiser window stopband attenuation parameter in dB. A default value of 0.0 will use a
predefined value based on the quality. 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 @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 The kaiser window transition bandwidth parameter. A default value of 0.0 will use a
predefined value based on the quality. 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 @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 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. 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 ## Channel Mixer Parameters
@ -1053,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-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 @PAR@ monitor-prop bluez5.dummy-avrcp player # boolean
Register dummy AVRCP player. Some devices have wrongly functioning Register dummy AVRCP player. Some devices have wrongly functioning
volume or playback controls if this is not enabled. Default: false volume or playback controls if this is not enabled. Default: false
@ -1172,6 +1266,18 @@ Available source contexts PACS bitmask of the the server.
@PAR@ monitor-prop bluez5.bap-server-capabilities.source.supported-contexts # integer @PAR@ monitor-prop bluez5.bap-server-capabilities.source.supported-contexts # integer
Supported source contexts PACS bitmask of the the server. 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 ## Device properties
@PAR@ device-prop bluez5.auto-connect # boolean @PAR@ device-prop bluez5.auto-connect # boolean

View file

@ -9,8 +9,8 @@ msgstr ""
"Project-Id-Version: PipeWire master\n" "Project-Id-Version: PipeWire master\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/" "Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/"
"issues\n" "issues\n"
"POT-Creation-Date: 2025-11-04 03:33+0000\n" "POT-Creation-Date: 2025-12-04 15:34+0000\n"
"PO-Revision-Date: 2025-11-08 15:23+0100\n" "PO-Revision-Date: 2025-12-07 08:53+0100\n"
"Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n" "Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n"
"Language-Team: Slovenian <gnome-si@googlegroups.com>\n" "Language-Team: Slovenian <gnome-si@googlegroups.com>\n"
"Language: sl\n" "Language: sl\n"
@ -76,7 +76,7 @@ msgstr "%s na %s@%s"
msgid "%s on %s" msgid "%s on %s"
msgstr "%s na %s" msgstr "%s na %s"
#: src/tools/pw-cat.c:1096 #: src/tools/pw-cat.c:1103
#, c-format #, c-format
msgid "" msgid ""
"%s [options] [<file>|-]\n" "%s [options] [<file>|-]\n"
@ -92,7 +92,7 @@ msgstr ""
"\n" "\n"
"</file>\n" "</file>\n"
#: src/tools/pw-cat.c:1103 #: src/tools/pw-cat.c:1110
#, c-format #, c-format
msgid "" msgid ""
" -R, --remote Remote daemon name\n" " -R, --remote Remote daemon name\n"
@ -129,7 +129,7 @@ msgstr ""
" -P --properties Nastavi lastnosti vozlišča\n" " -P --properties Nastavi lastnosti vozlišča\n"
"\n" "\n"
#: src/tools/pw-cat.c:1121 #: src/tools/pw-cat.c:1128
#, c-format #, c-format
msgid "" msgid ""
" --rate Sample rate (req. for rec) (default " " --rate Sample rate (req. for rec) (default "
@ -137,8 +137,8 @@ msgid ""
" --channels Number of channels (req. for rec) " " --channels Number of channels (req. for rec) "
"(default %u)\n" "(default %u)\n"
" --channel-map Channel map\n" " --channel-map Channel map\n"
" one of: \"stereo\", " " one of: \"Stereo\", \"5.1\",... "
"\"surround-51\",... or\n" "or\n"
" comma separated list of channel " " comma separated list of channel "
"names: eg. \"FL,FR\"\n" "names: eg. \"FL,FR\"\n"
" --format Sample format %s (req. for rec) " " --format Sample format %s (req. for rec) "
@ -157,8 +157,8 @@ msgstr ""
" --channels Število kanalov (zaht. za snemanje) " " --channels Število kanalov (zaht. za snemanje) "
"(privzeto %u)\n" "(privzeto %u)\n"
" --channel-map Preslikava kanalov\n" " --channel-map Preslikava kanalov\n"
" Ena izmed: \"stereo\", " " Ena izmed: \"Stereo\", "
"\"surround-51\",... ali\n" "\"5.1\",... ali\n"
" seznam imen kanalov, ločen z " " seznam imen kanalov, ločen z "
"vejico: npr. \"FL,FR\"\n" "vejico: npr. \"FL,FR\"\n"
" --format Vzorčne oblike zapisa %s (zahtevano " " --format Vzorčne oblike zapisa %s (zahtevano "
@ -172,7 +172,7 @@ msgstr ""
" -n, --sample-count ŠTEVEC Ustavi po ŠTEVEC vzorcih\n" " -n, --sample-count ŠTEVEC Ustavi po ŠTEVEC vzorcih\n"
"\n" "\n"
#: src/tools/pw-cat.c:1141 #: src/tools/pw-cat.c:1148
msgid "" msgid ""
" -p, --playback Playback mode\n" " -p, --playback Playback mode\n"
" -r, --record Recording mode\n" " -r, --record Recording mode\n"
@ -353,7 +353,7 @@ msgstr "Mono izhod slušalk"
#: spa/plugins/alsa/acp/alsa-mixer.c:2823 #: spa/plugins/alsa/acp/alsa-mixer.c:2823
msgid "Line Out" msgid "Line Out"
msgstr "Linijsk izhod" msgstr "Linijski izhod"
#: spa/plugins/alsa/acp/alsa-mixer.c:2824 #: spa/plugins/alsa/acp/alsa-mixer.c:2824
msgid "Analog Mono Output" msgid "Analog Mono Output"
@ -361,7 +361,7 @@ msgstr "Analogni mono izhod"
#: spa/plugins/alsa/acp/alsa-mixer.c:2825 #: spa/plugins/alsa/acp/alsa-mixer.c:2825
msgid "Speakers" msgid "Speakers"
msgstr "Govorniki" msgstr "Zvočniki"
#: spa/plugins/alsa/acp/alsa-mixer.c:2826 #: spa/plugins/alsa/acp/alsa-mixer.c:2826
msgid "HDMI / DisplayPort" msgid "HDMI / DisplayPort"

View file

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

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); 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; changed |= old_position != NULL;
pa_proplist_unset(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED); pa_proplist_unset(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED);
} else { } else {
@ -1146,32 +1146,38 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask)
struct spa_strbuf b; struct spa_strbuf b;
int i = 0; int i = 0;
#define _ADD_CHANNEL_POSITION(pos) \
{ \
if (i < eld.lpcm_channels) \
positions[i++] = pos; \
}
if (eld.speakers & 0x01) { if (eld.speakers & 0x01) {
positions[i++] = ACP_CHANNEL_FL; _ADD_CHANNEL_POSITION(ACP_CHANNEL_FL);
positions[i++] = ACP_CHANNEL_FR; _ADD_CHANNEL_POSITION(ACP_CHANNEL_FR);
} }
if (eld.speakers & 0x02) { if (eld.speakers & 0x02) {
positions[i++] = ACP_CHANNEL_LFE; _ADD_CHANNEL_POSITION(ACP_CHANNEL_LFE);
} }
if (eld.speakers & 0x04) { if (eld.speakers & 0x04) {
positions[i++] = ACP_CHANNEL_FC; _ADD_CHANNEL_POSITION(ACP_CHANNEL_FC);
} }
if (eld.speakers & 0x08) { if (eld.speakers & 0x08) {
positions[i++] = ACP_CHANNEL_RL; _ADD_CHANNEL_POSITION(ACP_CHANNEL_RL);
positions[i++] = ACP_CHANNEL_RR; _ADD_CHANNEL_POSITION(ACP_CHANNEL_RR);
} }
/* The rest are out of order in order of what channels we would prefer to use/expose first */ /* The rest are out of order in order of what channels we would prefer to use/expose first */
if (eld.speakers & 0x40) { if (eld.speakers & 0x40) {
/* Use SL/SR instead of RLC/RRC */ /* Use SL/SR instead of RLC/RRC */
positions[i++] = ACP_CHANNEL_SL; _ADD_CHANNEL_POSITION(ACP_CHANNEL_SL);
positions[i++] = ACP_CHANNEL_SR; _ADD_CHANNEL_POSITION(ACP_CHANNEL_SR);
} }
if (eld.speakers & 0x20) { if (eld.speakers & 0x20) {
positions[i++] = ACP_CHANNEL_RLC; _ADD_CHANNEL_POSITION(ACP_CHANNEL_RLC);
positions[i++] = ACP_CHANNEL_RRC; _ADD_CHANNEL_POSITION(ACP_CHANNEL_RRC);
} }
if (eld.speakers & 0x10) { if (eld.speakers & 0x10) {
positions[i++] = ACP_CHANNEL_RC; _ADD_CHANNEL_POSITION(ACP_CHANNEL_RC);
} }
while (i < eld.lpcm_channels) while (i < eld.lpcm_channels)
positions[i++] = ACP_CHANNEL_UNKNOWN; positions[i++] = ACP_CHANNEL_UNKNOWN;

View file

@ -2036,7 +2036,9 @@ static void recalc_headroom(struct state *state)
uint32_t latency; uint32_t latency;
uint32_t rate = 0; 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; rate = state->position->clock.target_rate.denom;
if (state->use_period_size_min_as_headroom) if (state->use_period_size_min_as_headroom)
@ -2063,8 +2065,6 @@ static void recalc_headroom(struct state *state)
state->headroom = 0; state->headroom = 0;
latency = SPA_MAX(state->min_delay, SPA_MIN(state->max_delay, state->headroom)); 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) { if (state->is_firewire) {
/* XXX: For ALSA FireWire drivers, unlike for other ALSA drivers, buffer size /* 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; 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].min_rate =
state->latency[state->port_direction].max_rate = latency; state->latency[state->port_direction].max_rate = latency;

View file

@ -1344,6 +1344,14 @@ static int do_sync_filter_graph(struct spa_loop *loop, bool async, uint32_t seq,
return 0; 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) static void clean_filter_handles(struct impl *impl, bool force)
{ {
struct filter_graph *g, *t; struct filter_graph *g, *t;
@ -1431,7 +1439,7 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order)
if (impl->setup) if (impl->setup)
res = setup_filter_graphs(impl, false); 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) if (impl->in_filter_props == 0)
clean_filter_handles(impl, false); clean_filter_handles(impl, false);
@ -1500,6 +1508,10 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
} }
else if (spa_streq(k, "channelmix.lock-volumes")) else if (spa_streq(k, "channelmix.lock-volumes"))
this->props.lock_volumes = spa_atob(s); 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.")) { else if (spa_strstartswith(k, "audioconvert.filter-graph.")) {
int order = atoi(k + strlen("audioconvert.filter-graph.")); int order = atoi(k + strlen("audioconvert.filter-graph."));
if ((res = load_filter_graph(this, s, order)) < 0) { if ((res = load_filter_graph(this, s, order)) < 0) {
@ -1507,10 +1519,6 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
order, spa_strerror(res)); order, spa_strerror(res));
} }
} }
else if (spa_streq(k, "audioconvert.filter-graph.disable")) {
if (!*disable_filter)
*disable_filter = spa_atob(s);
}
else else
return 0; return 0;
return 1; return 1;
@ -2545,6 +2553,8 @@ static int setup_convert(struct impl *this)
this->setup = true; this->setup = true;
this->recalc = true; this->recalc = true;
sync_filter_graph(this);
return 0; return 0;
} }
@ -2561,6 +2571,7 @@ static void reset_node(struct impl *this)
resample_reset(&this->resample); resample_reset(&this->resample);
this->in_offset = 0; this->in_offset = 0;
this->out_offset = 0; this->out_offset = 0;
this->setup = false;
} }
static int impl_node_send_command(void *object, const struct spa_command *command) static int impl_node_send_command(void *object, const struct spa_command *command)
@ -2581,7 +2592,6 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
break; break;
case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Suspend:
reset_node(this); reset_node(this);
this->setup = false;
SPA_FALLTHROUGH; SPA_FALLTHROUGH;
case SPA_NODE_COMMAND_Pause: case SPA_NODE_COMMAND_Pause:
this->started = false; this->started = false;

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, 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) uint32_t n_channels, uint32_t n_samples)
{ {

View file

@ -6,7 +6,8 @@
#include <tmmintrin.h> #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, 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) uint32_t n_channels, uint32_t n_samples)
{ {

View file

@ -8,7 +8,10 @@
#include <spa/support/cpu.h> #include <spa/support/cpu.h>
#include <spa/support/log.h> #include <spa/support/log.h>
#ifndef RESAMPLE_DEFAULT_QUALITY
#define RESAMPLE_DEFAULT_QUALITY 4 #define RESAMPLE_DEFAULT_QUALITY 4
#endif
#define RESAMPLE_WINDOW_DEFAULT RESAMPLE_WINDOW_EXP #define RESAMPLE_WINDOW_DEFAULT RESAMPLE_WINDOW_EXP
#define RESAMPLE_MAX_PARAMS 16 #define RESAMPLE_MAX_PARAMS 16

View file

@ -187,6 +187,8 @@ static int open_files(struct data *d)
d->oname, sf_strerror(NULL)); d->oname, sf_strerror(NULL));
return -EIO; return -EIO;
} }
sf_command(d->ofile, SFC_SET_CLIPPING, NULL, 1);
if (d->verbose) { if (d->verbose) {
fprintf(stdout, "input '%s': channels:%d rate:%d format:%s\n", fprintf(stdout, "input '%s': channels:%d rate:%d format:%s\n",
d->iname, d->iinfo.channels, d->iinfo.samplerate, d->iname, d->iinfo.channels, d->iinfo.samplerate,
@ -296,10 +298,9 @@ static int do_conversion(struct data *d)
if (pout_len > 0) { if (pout_len > 0) {
for (k = 0, i = 0; i < pout_len; i++) { 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]; obuf[k++] = out[MAX_SAMPLES * j + i];
} }
}
pout_len = sf_writef_float(d->ofile, obuf, pout_len); pout_len = sf_writef_float(d->ofile, obuf, pout_len);
written += pout_len; written += pout_len;

View file

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

View file

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

View file

@ -125,6 +125,7 @@ struct impl {
struct spa_source *ring_timer; struct spa_source *ring_timer;
void *upower; void *upower;
struct spa_bt_telephony *telephony; struct spa_bt_telephony *telephony;
bool pts;
}; };
struct transport_data { struct transport_data {
@ -1438,6 +1439,15 @@ next_indicator:
rfcomm_send_error(rfcomm, error); rfcomm_send_error(rfcomm, error);
return true; 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")) { } else if (spa_strstartswith(buf, "AT+CHUP")) {
enum cmee_error error; enum cmee_error error;
@ -2763,7 +2773,7 @@ static int sco_acquire_cb(void *data, bool optional)
goto fail; goto fail;
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
if (!mm_is_available(backend->modemmanager)) if (!td->rfcomm->device->disable_dummy_call)
rfcomm_hfp_ag_set_cind(td->rfcomm, true); rfcomm_hfp_ag_set_cind(td->rfcomm, true);
#endif #endif
@ -2818,7 +2828,7 @@ static int sco_release_cb(void *data)
spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_IDLE); spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_IDLE);
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE #ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
if (!mm_is_available(backend->modemmanager)) if (!td->rfcomm->device->disable_dummy_call)
rfcomm_hfp_ag_set_cind(td->rfcomm, false); rfcomm_hfp_ag_set_cind(td->rfcomm, false);
#endif #endif
@ -2932,7 +2942,11 @@ static void sco_listen_event(struct spa_source *source)
/* Find transport for local and remote address */ /* Find transport for local and remote address */
spa_list_for_each(rfcomm, &backend->rfcomm_list, link) { 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 && rfcomm->transport &&
spa_streq(rfcomm->device->address, remote_address) && spa_streq(rfcomm->device->address, remote_address) &&
spa_streq(rfcomm->device->adapter->address, local_address)) { spa_streq(rfcomm->device->adapter->address, local_address)) {
@ -2946,7 +2960,7 @@ static void sco_listen_event(struct spa_source *source)
return; 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) { if (rfcomm->telephony_ag && rfcomm->telephony_ag->transport.rejectSCO) {
spa_log_info(backend->log, "rejecting SCO, AudioGatewayTransport1.RejectSCO=true"); spa_log_info(backend->log, "rejecting SCO, AudioGatewayTransport1.RejectSCO=true");
@ -3387,6 +3401,43 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag
} }
spa_bt_device_add_profile(d, profile); 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_next(&it);
dbus_message_iter_get_basic(&it, &fd); dbus_message_iter_get_basic(&it, &fd);
@ -4017,6 +4068,16 @@ static void parse_hfp_disable_nrec(struct impl *backend, const struct spa_dict *
backend->hfp_disable_nrec = false; 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) static void parse_hfp_default_volumes(struct impl *backend, const struct spa_dict *info)
{ {
const char *str; const char *str;
@ -4101,6 +4162,7 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
parse_hfp_disable_nrec(backend, info); parse_hfp_disable_nrec(backend, info);
parse_hfp_default_volumes(backend, info); parse_hfp_default_volumes(backend, info);
parse_hfp_pts(backend, info);
#ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE #ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE
if (!dbus_connection_register_object_path(backend->conn, if (!dbus_connection_register_object_path(backend->conn,

View file

@ -153,6 +153,69 @@
#define BT_ISO_QOS_TARGET_LATENCY_BALANCED 0x02 #define BT_ISO_QOS_TARGET_LATENCY_BALANCED 0x02
#define BT_ISO_QOS_TARGET_LATENCY_RELIABILITY 0x03 #define BT_ISO_QOS_TARGET_LATENCY_RELIABILITY 0x03
#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 { struct bap_endpoint_qos {
uint8_t framing; uint8_t framing;
uint8_t phy; uint8_t phy;

View file

@ -77,6 +77,11 @@ enum backend_selection {
#define TRANSPORT_ERROR_TIMEOUT (2*BLUEZ_ACTION_RATE_MSEC*SPA_NSEC_PER_MSEC) #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_bt_monitor {
struct spa_handle handle; struct spa_handle handle;
struct spa_device device; struct spa_device device;
@ -131,6 +136,8 @@ struct spa_bt_monitor {
uint32_t bap_source_contexts; uint32_t bap_source_contexts;
uint32_t bap_source_supported_contexts; uint32_t bap_source_supported_contexts;
struct bap_features bap_features;
struct spa_bt_quirks *quirks; struct spa_bt_quirks *quirks;
#define MAX_SETTINGS 128 #define MAX_SETTINGS 128
@ -161,6 +168,8 @@ struct spa_bt_remote_endpoint {
struct bap_endpoint_qos qos; struct bap_endpoint_qos qos;
struct bap_features bap_features;
bool asha_right_side; bool asha_right_side;
uint64_t hisyncid; uint64_t hisyncid;
}; };
@ -658,6 +667,77 @@ static bool endpoint_should_be_registered(struct spa_bt_monitor *monitor,
codec->fill_caps; 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) static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata)
{ {
struct spa_bt_monitor *monitor = userdata; struct spa_bt_monitor *monitor = userdata;
@ -922,24 +1002,24 @@ static int parse_endpoint_props(struct spa_bt_monitor *monitor, DBusMessageIter
spa_assert(dest && size); spa_assert(dest && size);
if (type != DBUS_TYPE_ARRAY) if (!check_iter_signature(&it[1], "ay"))
goto bad_property; goto bad_property;
dbus_message_iter_recurse(&it[1], &it[2]); 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], &data, &n);
if (n) {
buf = malloc(n); buf = malloc(n);
if (!buf) if (!buf)
return -ENOMEM; return -ENOMEM;
memcpy(buf, data, n);
} else {
buf = NULL;
}
free(*dest); free(*dest);
*dest = buf; *dest = buf;
*size = n; *size = n;
memcpy(buf, data, n);
spa_log_info(monitor->log, "%p: %s size:%zu", monitor, key, *size); spa_log_info(monitor->log, "%p: %s size:%zu", monitor, key, *size);
spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', *dest, *size); spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', *dest, *size);
@ -1114,6 +1194,8 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.debug", "true"); 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", (void *)ep->metadata);
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.metadata-len", metadata_len); 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) if (ep->device->settings)
for (j = 0; j < ep->device->settings->n_items && i < SPA_N_ELEMENTS(setting_items); ++i, ++j) 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]; setting_items[i] = ep->device->settings->items[j];
@ -2856,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 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, static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_endpoint,
DBusMessageIter *props_iter, DBusMessageIter *props_iter,
DBusMessageIter *invalidated_iter) DBusMessageIter *invalidated_iter)
@ -2991,8 +3105,13 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en
remote_endpoint->hisyncid = *(uint64_t *)value; remote_endpoint->hisyncid = *(uint64_t *)value;
spa_log_debug(monitor->log, "remote_endpoint %p: %s=%"PRIu64, remote_endpoint, key, remote_endpoint->hisyncid); spa_log_debug(monitor->log, "remote_endpoint %p: %s=%"PRIu64, remote_endpoint, key, remote_endpoint->hisyncid);
} } else if (spa_streq(key, "SupportedFeatures")) {
else { if (!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: unhandled:
spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key); spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key);
} }
@ -3047,6 +3166,8 @@ static void remote_endpoint_free(struct spa_bt_remote_endpoint *remote_endpoint)
if (remote_endpoint->device) if (remote_endpoint->device)
spa_list_remove(&remote_endpoint->device_link); spa_list_remove(&remote_endpoint->device_link);
bap_features_clear(&remote_endpoint->bap_features);
spa_list_remove(&remote_endpoint->link); spa_list_remove(&remote_endpoint->link);
free(remote_endpoint->path); free(remote_endpoint->path);
free(remote_endpoint->transport_path); free(remote_endpoint->transport_path);
@ -3650,6 +3771,11 @@ static int transport_update_props(struct spa_bt_transport *transport,
free(transport->configuration); free(transport->configuration);
transport->configuration_len = 0; transport->configuration_len = 0;
if (!len) {
transport->configuration = NULL;
goto next;
}
transport->configuration = malloc(len); transport->configuration = malloc(len);
if (transport->configuration) { if (transport->configuration) {
memcpy(transport->configuration, value, len); memcpy(transport->configuration, value, len);
@ -5417,6 +5543,42 @@ out:
return err; 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, 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) const char *uuid, uint8_t codec_id, uint8_t *caps, size_t caps_size)
{ {
@ -5464,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); 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(&entry, &dict);
dbus_message_iter_close_container(&array, &entry); dbus_message_iter_close_container(&array, &entry);
dbus_message_iter_close_container(&object, &array); dbus_message_iter_close_container(&object, &array);
@ -6685,6 +6850,8 @@ static int impl_clear(struct spa_handle *handle)
monitor->backend = NULL; monitor->backend = NULL;
monitor->backend_selection = BACKEND_NATIVE; monitor->backend_selection = BACKEND_NATIVE;
bap_features_clear(&monitor->bap_features);
spa_bt_quirks_destroy(monitor->quirks); spa_bt_quirks_destroy(monitor->quirks);
free_media_codecs(monitor->media_codecs); free_media_codecs(monitor->media_codecs);
@ -6998,6 +7165,32 @@ static void parse_bap_locations(struct spa_bt_monitor *this, const struct spa_di
*value = locations; *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) static void parse_bap_server(struct spa_bt_monitor *this, const struct spa_dict *info)
{ {
this->bap_sink_locations = BAP_CHANNEL_FL | BAP_CHANNEL_FR; this->bap_sink_locations = BAP_CHANNEL_FL | BAP_CHANNEL_FR;
@ -7016,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); 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.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); 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) 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/support/log.h>
#include <spa/utils/type.h> #include <spa/utils/type.h>
#include <spa/utils/json.h>
#include <spa/utils/keys.h> #include <spa/utils/keys.h>
#include <spa/utils/names.h> #include <spa/utils/names.h>
#include <spa/utils/string.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) static struct spa_pod *build_props(struct impl *this, struct spa_pod_builder *b, uint32_t id)
{ {
struct props *p = &this->props; struct props *p = &this->props;
struct spa_pod_frame f[2];
struct spa_pod *param;
return spa_pod_builder_add_object(b, spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Props, id);
SPA_TYPE_OBJECT_Props, id, spa_pod_builder_add(b,
SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(p->codec), SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(p->codec),
SPA_PROP_bluetoothOffloadActive, SPA_POD_Bool(p->offload_active)); 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, 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, static int impl_set_param(void *object,
uint32_t id, uint32_t flags, uint32_t id, uint32_t flags,
const struct spa_pod *param) const struct spa_pod *param)
@ -3093,6 +3148,7 @@ static int impl_set_param(void *object,
{ {
uint32_t codec_id = SPA_ID_INVALID; uint32_t codec_id = SPA_ID_INVALID;
bool offload_active = this->props.offload_active; bool offload_active = this->props.offload_active;
struct spa_pod *params = NULL;
if (param == NULL) if (param == NULL)
return 0; return 0;
@ -3100,7 +3156,8 @@ static int impl_set_param(void *object,
if ((res = spa_pod_parse_object(param, if ((res = spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL, SPA_TYPE_OBJECT_Props, NULL,
SPA_PROP_bluetoothAudioCodec, SPA_POD_OPT_Id(&codec_id), 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_log_warn(this->log, "can't parse props");
spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param); spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param);
return res; 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); 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); 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; return 0;
}
if (this->profile == DEVICE_PROFILE_A2DP || profile_is_bap(this->profile) || if (this->profile == DEVICE_PROFILE_A2DP || profile_is_bap(this->profile) ||
this->profile == DEVICE_PROFILE_ASHA || this->profile == DEVICE_PROFILE_HSP_HFP) { 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) if ((profiles = spa_bt_profiles_from_json_array(str)) >= 0)
this->bt_dev->hw_volume_profiles = profiles; 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( this->device.iface = SPA_INTERFACE_INIT(

View file

@ -573,6 +573,8 @@ struct spa_bt_device {
const struct media_codec *preferred_codec; const struct media_codec *preferred_codec;
uint32_t preferred_profiles; uint32_t preferred_profiles;
bool disable_dummy_call;
}; };
struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path); struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path);

View file

@ -36,6 +36,8 @@ struct impl {
struct modem modem; struct modem modem;
struct spa_list call_list; struct spa_list call_list;
bool pts;
}; };
struct dbus_cmd_data { struct dbus_cmd_data {
@ -944,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_autofree struct dbus_cmd_data *data = NULL;
spa_autoptr(DBusMessage) m = NULL; spa_autoptr(DBusMessage) m = NULL;
DBusMessageIter iter, dict; 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])) { 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]); spa_log_warn(this->log, "Call creation canceled, invalid character found in dial string: %c", number[i]);
if (error) if (error)
@ -1078,6 +1085,8 @@ void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_d
{ {
const char *modem_device_str = NULL; const char *modem_device_str = NULL;
bool modem_device_found = false; bool modem_device_found = false;
const char *pts_str = NULL;
bool pts = false;
spa_assert(log); spa_assert(log);
spa_assert(dbus_connection); spa_assert(dbus_connection);
@ -1087,6 +1096,9 @@ void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_d
if (!spa_streq(modem_device_str, "none")) if (!spa_streq(modem_device_str, "none"))
modem_device_found = true; 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) { if (!modem_device_found) {
spa_log_info(log, "No modem allowed, doesn't link to ModemManager"); spa_log_info(log, "No modem allowed, doesn't link to ModemManager");
@ -1104,6 +1116,7 @@ void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_d
if (modem_device_str && !spa_streq(modem_device_str, "any")) if (modem_device_str && !spa_streq(modem_device_str, "any"))
this->allowed_modem_device = strdup(modem_device_str); this->allowed_modem_device = strdup(modem_device_str);
spa_list_init(&this->call_list); spa_list_init(&this->call_list);
this->pts = pts;
if (add_filters(this) < 0) if (add_filters(this) < 0)
return NULL; return NULL;

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, 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); dsp_copy_c(obj, dst, src, n_samples);
} else { } else {
uint32_t w, o, i; uint32_t w, o, i;
@ -215,11 +216,21 @@ void dsp_delay_c(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer,
w = *pos; w = *pos;
o = n_buffer - delay; o = n_buffer - delay;
if (fb == 0.0f && ff == 0.0f) {
for (i = 0; i < n_samples; i++) { for (i = 0; i < n_samples; i++) {
buffer[w] = buffer[w + n_buffer] = src[i]; buffer[w] = buffer[w + n_buffer] = src[i];
dst[i] = buffer[w + o]; dst[i] = buffer[w + o];
w = w + 1 >= n_buffer ? 0 : w + 1; 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; *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) float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], uint32_t n_src, uint32_t n_samples)
#define MAKE_DELAY_FUNC(arch) \ #define MAKE_DELAY_FUNC(arch) \
void dsp_delay_##arch (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, \ 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) \ #define MAKE_FFT_NEW_FUNC(arch) \
void *dsp_fft_new_##arch(void *obj, uint32_t size, bool real) void *dsp_fft_new_##arch(void *obj, uint32_t size, bool real)

View file

@ -614,15 +614,16 @@ 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, 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 w = *pos;
uint32_t o = n_buffer - delay; uint32_t o = n_buffer - delay;
uint32_t n, unrolled; uint32_t n, unrolled;
if (fb == 0.0f && ff == 0.0f) {
if (SPA_IS_ALIGNED(src, 16) && if (SPA_IS_ALIGNED(src, 16) &&
SPA_IS_ALIGNED(dst, 16)) SPA_IS_ALIGNED(dst, 16) && delay >= 4)
unrolled = n_samples & ~3; unrolled = n_samples & ~3;
else else
unrolled = 0; unrolled = 0;
@ -643,6 +644,41 @@ void dsp_delay_sse(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, u
_mm_store_ss(&dst[n], t[0]); _mm_store_ss(&dst[n], t[0]);
w = w + 1 >= n_buffer ? 0 : w + 1; 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; *pos = w;
} }

View file

@ -58,7 +58,8 @@ struct spa_fga_dsp_methods {
float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[],
uint32_t n_src, uint32_t n_samples); uint32_t n_src, uint32_t n_samples);
void (*delay) (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay, 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) 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, 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 *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, 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 */ #endif /* SPA_FGA_DSP_H */

View file

@ -1228,7 +1228,7 @@ struct delay_impl {
struct spa_log *log; struct spa_log *log;
unsigned long rate; unsigned long rate;
float *port[4]; float *port[6];
float delay; float delay;
uint32_t delay_samples; uint32_t delay_samples;
@ -1334,7 +1334,8 @@ static void delay_run(void * Instance, unsigned long SampleCount)
} }
if (in != NULL && out != NULL) { if (in != NULL && out != NULL) {
spa_fga_dsp_delay(impl->dsp, impl->buffer, &impl->ptr, impl->buffer_samples, 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) if (impl->port[3] != NULL)
impl->port[3][0] = impl->latency; impl->port[3][0] = impl->latency;
@ -1359,6 +1360,16 @@ static struct spa_fga_port delay_ports[] = {
.hint = SPA_FGA_HINT_LATENCY, .hint = SPA_FGA_HINT_LATENCY,
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL, .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 = { static const struct spa_fga_descriptor delay_desc = {

View file

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

View file

@ -1056,6 +1056,44 @@ wait_started (GstPipeWireSrc *this)
return state; 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 static gboolean
gst_pipewire_src_negotiate (GstBaseSrc * basesrc) gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
{ {
@ -1067,7 +1105,6 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
g_autoptr (GPtrArray) possible = NULL; g_autoptr (GPtrArray) possible = NULL;
gboolean result = FALSE; gboolean result = FALSE;
const char *error = NULL; const char *error = NULL;
struct timespec abstime;
uint32_t target_id; uint32_t target_id;
/* first see what is possible on our source pad */ /* first see what is possible on our source pad */
@ -1177,27 +1214,9 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
(const struct spa_pod **)possible->pdata, (const struct spa_pod **)possible->pdata,
possible->len); possible->len);
pw_thread_loop_get_time (pwsrc->stream->core->loop, &abstime, if (wait_negotiated(pwsrc) == PW_STREAM_STATE_ERROR)
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; 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);
}
}
negotiated_caps = g_steal_pointer (&pwsrc->caps); negotiated_caps = g_steal_pointer (&pwsrc->caps);
pw_thread_loop_unlock (pwsrc->stream->core->loop); pw_thread_loop_unlock (pwsrc->stream->core->loop);
@ -1724,12 +1743,21 @@ gst_pipewire_src_change_state (GstElement * element, GstStateChange transition)
break; break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING: case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
/* uncork and start recording */ /* uncork and start recording */
GST_DEBUG_OBJECT (this, "activating stream");
pw_thread_loop_lock (this->stream->core->loop); pw_thread_loop_lock (this->stream->core->loop);
pw_stream_set_active (this->stream->pwstream, true); 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); pw_thread_loop_unlock (this->stream->core->loop);
break; break;
case GST_STATE_CHANGE_PLAYING_TO_PAUSED: case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
/* stop recording ASAP by corking */ /* stop recording ASAP by corking */
GST_DEBUG_OBJECT (this, "in-activating stream");
pw_thread_loop_lock (this->stream->core->loop); pw_thread_loop_lock (this->stream->core->loop);
pw_stream_set_active (this->stream->pwstream, false); pw_stream_set_active (this->stream->pwstream, false);
pw_thread_loop_unlock (this->stream->core->loop); pw_thread_loop_unlock (this->stream->core->loop);

View file

@ -736,8 +736,21 @@ if build_module_avb
'module-avb/acmp.c', 'module-avb/acmp.c',
'module-avb/aecp.c', 'module-avb/aecp.c',
'module-avb/aecp-aem.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/es-builder.c',
'module-avb/avdecc.c', 'module-avb/avdecc.c',
'module-avb/descriptors.c',
'module-avb/maap.c', 'module-avb/maap.c',
'module-avb/mmrp.c', 'module-avb/mmrp.c',
'module-avb/mrp.c', 'module-avb/mrp.c',

View file

@ -11,6 +11,8 @@
#include "msrp.h" #include "msrp.h"
#include "internal.h" #include "internal.h"
#include "stream.h" #include "stream.h"
#include "aecp-aem-descriptors.h"
#include "aecp-aem-state.h"
static const uint8_t mac[6] = AVB_BROADCAST_MAC; 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); 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 { struct msg_info {
uint16_t type; uint16_t type;
const char *name; const char *name;
int (*handle) (struct acmp *acmp, uint64_t now, const void *m, int len); 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) static int reply_not_supported(struct acmp *acmp, uint8_t type, const void *m, int len)
{ {
struct server *server = acmp->server; 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; return 0;
memcpy(buf, m, len); memcpy(buf, m, len);
stream = server_find_stream(server, SPA_DIRECTION_OUTPUT, stream = find_stream(server, SPA_DIRECTION_OUTPUT, ntohs(reply->talker_unique_id));
reply->talker_unique_id);
if (stream == NULL) { if (stream == NULL) {
status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX; status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX;
goto done; 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); AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE);
reply->stream_id = htobe64(stream->id); 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); memcpy(reply->stream_dest_mac, stream->addr, 6);
reply->connection_count = htons(1); 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); reply->sequence_id = htons(pending->old_sequence_id);
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE); AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE);
stream = server_find_stream(server, SPA_DIRECTION_INPUT, stream = find_stream(server, SPA_DIRECTION_INPUT, ntohs(reply->listener_unique_id));
ntohs(reply->listener_unique_id));
if (stream == NULL) if (stream == NULL)
return 0; return 0;
stream->peer_id = be64toh(reply->stream_id); stream->peer_id = be64toh(reply->stream_id);
memcpy(stream->addr, reply->stream_dest_mac, 6); 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); 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; return 0;
memcpy(buf, m, len); memcpy(buf, m, len);
stream = server_find_stream(server, SPA_DIRECTION_OUTPUT, stream = find_stream(server, SPA_DIRECTION_OUTPUT, ntohs(reply->talker_unique_id));
reply->talker_unique_id);
if (stream == NULL) { if (stream == NULL) {
status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX; status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX;
goto done; 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); reply->sequence_id = htons(pending->old_sequence_id);
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE); AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE);
stream = server_find_stream(server, SPA_DIRECTION_INPUT, stream = find_stream(server, SPA_DIRECTION_INPUT, ntohs(reply->listener_unique_id));
reply->listener_unique_id);
if (stream == NULL) if (stream == NULL)
return 0; return 0;
@ -369,6 +419,7 @@ static void acmp_destroy(void *data)
{ {
struct acmp *acmp = data; struct acmp *acmp = data;
spa_hook_remove(&acmp->server_listener); spa_hook_remove(&acmp->server_listener);
pending_destroy(acmp);
free(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) static void adp_destroy(void *data)
{ {
struct adp *adp = data; struct adp *adp = data;
struct entity *e, *t;
spa_hook_remove(&adp->server_listener); spa_hook_remove(&adp->server_listener);
spa_list_for_each_safe(e, t, &adp->entities, link) {
entity_free(e);
}
free(adp); 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

@ -6,8 +6,11 @@
#ifndef AVB_AECP_AEM_DESCRIPTORS_H #ifndef AVB_AECP_AEM_DESCRIPTORS_H
#define 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_ENTITY 0x0000
#define AVB_AEM_DESC_CONFIGURATION 0x0001 #define AVB_AEM_DESC_CONFIGURATION 0x0001
#define AVB_AEM_DESC_AUDIO_UNIT 0x0002 #define AVB_AEM_DESC_AUDIO_UNIT 0x0002
@ -50,6 +53,13 @@
#define AVB_AEM_DESC_LAST_RESERVED_17221 0x0029 #define AVB_AEM_DESC_LAST_RESERVED_17221 0x0029
#define AVB_AEM_DESC_INVALID 0xffff #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 { struct avb_aem_desc_entity {
uint64_t entity_id; uint64_t entity_id;
uint64_t entity_model_id; uint64_t entity_model_id;
@ -130,6 +140,42 @@ struct avb_aem_desc_audio_unit {
struct avb_aem_desc_sampling_rate sampling_rates[0]; struct avb_aem_desc_sampling_rate sampling_rates[0];
} __attribute__ ((__packed__)); } __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_SYNC_SOURCE (1u<<0)
#define AVB_AEM_DESC_STREAM_FLAG_CLASS_A (1u<<1) #define AVB_AEM_DESC_STREAM_FLAG_CLASS_A (1u<<1)
#define AVB_AEM_DESC_STREAM_FLAG_CLASS_B (1u<<2) #define AVB_AEM_DESC_STREAM_FLAG_CLASS_B (1u<<2)
@ -200,6 +246,15 @@ struct avb_aem_desc_clock_source {
uint16_t clock_source_location_index; uint16_t clock_source_location_index;
} __attribute__ ((__packed__)); } __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 { struct avb_aem_desc_locale {
char locale_identifier[64]; char locale_identifier[64];
uint16_t number_of_strings; uint16_t number_of_strings;
@ -227,4 +282,34 @@ struct avb_aem_desc_stream_port {
uint16_t base_map; uint16_t base_map;
} __attribute__ ((__packed__)); } __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 */ #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 */ /* AVB support */
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */ /* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki */
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "aecp-aem.h" #include "aecp-aem.h"
#include "aecp-aem-descriptors.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) /* The headers including the command and response of the system */
{ #include "aecp-aem-cmds-resps/cmd-available.h"
struct server *server = aecp->server; #include "aecp-aem-cmds-resps/cmd-get-set-configuration.h"
uint8_t buf[len]; #include "aecp-aem-cmds-resps/cmd-lock-entity.h"
struct avb_ethernet_header *h = (void*)buf; #include "aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.h"
struct avb_packet_aecp_header *reply = SPA_PTROFF(h, sizeof(*h), void); #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 */ /* 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; struct server *server = aecp->server;
const struct avb_packet_aecp_aem *p = m; 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); desc = server_find_descriptor(server, desc_type, desc_id);
if (desc == NULL) 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) if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0)
return reply_not_implemented(aecp, m, len); 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 */ /* 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; struct server *server = aecp->server;
const struct avb_packet_aecp_aem *p = m; 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 */ /* 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; struct server *server = aecp->server;
const struct avb_ethernet_header *h = m; 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 */ /* 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; struct server *server = aecp->server;
const struct avb_ethernet_header *h = m; 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); 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 */ /* AEM_COMMAND */
struct cmd_info { struct cmd_info {
uint16_t type; /**
const char *name; * \brief Is Readonly is a hint used to decide whether or not the
int (*handle) (struct aecp *aecp, const void *p, int len); * 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[] = { #define AECP_AEM_HANDLE_CMD(cmd, readonly_desc, handle_exec) \
{ AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, "acquire-entity", handle_acquire_entity, }, [cmd] = { \
{ AVB_AECP_AEM_CMD_LOCK_ENTITY, "lock-entity", handle_lock_entity, }, .is_readonly = readonly_desc, \
{ AVB_AECP_AEM_CMD_ENTITY_AVAILABLE, "entity-available", NULL, }, .handle_command = handle_exec \
{ 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, }, #define AECP_AEM_HANDLE_RESP(cmd, handle_cmd, handle_exec_unsol) \
{ AVB_AECP_AEM_CMD_GET_CONFIGURATION, "get-configuration", NULL, }, [cmd] = { \
{ AVB_AECP_AEM_CMD_SET_STREAM_FORMAT, "set-stream-format", NULL, }, .name = name_str, \
{ AVB_AECP_AEM_CMD_GET_STREAM_FORMAT, "get-stream-format", NULL, }, .is_readonly = false, \
{ AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT, "set-video-format", NULL, }, .handle_response = handle_cmd \
{ 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, }, #define AECP_AEM_CMD_RESP_AND_UNSOL(cmd, readonly_desc, handle_exec, \
{ AVB_AECP_AEM_CMD_SET_STREAM_INFO, "set-stream-info", NULL, }, handle_exec_unsol) \
{ AVB_AECP_AEM_CMD_GET_STREAM_INFO, "get-stream-info", NULL, }, [cmd] = { \
{ AVB_AECP_AEM_CMD_SET_NAME, "set-name", NULL, }, .name = name_str, \
{ AVB_AECP_AEM_CMD_GET_NAME, "get-name", NULL, }, .is_readonly = readonly_desc, \
{ AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID, "set-association-id", NULL, }, .handle = handle_exec, \
{ AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID, "get-association-id", NULL, }, .handle_unsol = handle_exec_unsol \
{ 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, }, static const struct cmd_info cmd_info_avb_legacy[] = {
{ AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE, "get-clock-source", NULL, }, AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, true,
{ AVB_AECP_AEM_CMD_SET_CONTROL, "set-control", NULL, }, handle_acquire_entity_avb_legacy),
{ AVB_AECP_AEM_CMD_GET_CONTROL, "get-control", NULL, },
{ AVB_AECP_AEM_CMD_INCREMENT_CONTROL, "increment-control", NULL, }, AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_LOCK_ENTITY, true,
{ AVB_AECP_AEM_CMD_DECREMENT_CONTROL, "decrement-control", NULL, }, handle_lock_entity_avb_legacy),
{ AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR, "set-signal-selector", NULL, },
{ AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR, "get-signal-selector", NULL, }, AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_CONFIGURATION, false,
{ AVB_AECP_AEM_CMD_SET_MIXER, "set-mixer", NULL, }, handle_cmd_get_configuration_common),
{ AVB_AECP_AEM_CMD_GET_MIXER, "get-mixer", NULL, },
{ AVB_AECP_AEM_CMD_SET_MATRIX, "set-matrix", NULL, }, AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_READ_DESCRIPTOR, true,
{ AVB_AECP_AEM_CMD_GET_MATRIX, "get-matrix", NULL, }, handle_read_descriptor_common),
{ AVB_AECP_AEM_CMD_START_STREAMING, "start-streaming", NULL, },
{ AVB_AECP_AEM_CMD_STOP_STREAMING, "stop-streaming", NULL, }, AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_AVB_INFO, true,
{ AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION, "register-unsolicited-notification", NULL, }, handle_get_avb_info_common),
{ 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) static const struct cmd_info cmd_info_milan_v12[] = {
{ /** Milan V1.2 should not implement acquire */
SPA_FOR_EACH_ELEMENT_VAR(cmd_info, i) { AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, true,
if ((name == NULL && type == i->type) || direct_reply_not_supported),
(name != NULL && spa_streq(name, i->name)))
return i; AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_LOCK_ENTITY, false,
} handle_cmd_lock_entity_milan_v12),
return NULL;
} 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) int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len)
{ {
const struct avb_ethernet_header *h = m; const struct avb_ethernet_header *h = m;
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void); const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
uint16_t cmd_type; uint16_t cmd_type;
struct server *server = aecp->server;
const struct cmd_info *info; const struct cmd_info *info;
struct timespec ts_now = {0};
int64_t now;
cmd_type = AVB_PACKET_AEM_GET_COMMAND_TYPE(p); cmd_type = AVB_PACKET_AEM_GET_COMMAND_TYPE(p);
info = find_cmd_info(cmd_type, NULL); pw_log_info("mode: %s aem command %s",
if (info == NULL) 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); return reply_not_implemented(aecp, m, len);
pw_log_info("aem command %s", info->name);
if (info->handle == NULL) if (clock_gettime(CLOCK_TAI, &ts_now)) {
return reply_not_implemented(aecp, m, len); 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) int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len)

View file

@ -6,99 +6,7 @@
#define AVB_AEM_H #define AVB_AEM_H
#include "aecp.h" #include "aecp.h"
#include "aecp-aem-types.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)
struct avb_packet_aecp_aem_acquire { struct avb_packet_aecp_aem_acquire {
uint32_t flags; uint32_t flags;
@ -114,6 +22,12 @@ struct avb_packet_aecp_aem_lock {
uint16_t descriptor_id; uint16_t descriptor_id;
} __attribute__ ((__packed__)); } __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 { struct avb_packet_aecp_aem_read_descriptor {
uint16_t configuration; uint16_t configuration;
uint8_t reserved[2]; uint8_t reserved[2];
@ -315,6 +229,8 @@ struct avb_packet_aecp_aem {
uint8_t payload[0]; uint8_t payload[0];
} __attribute__ ((__packed__)); } __attribute__ ((__packed__));
#define AVB_PACKET_CONTROL_DATA_OFFSET (12U)
#define AVB_PACKET_AEM_SET_COMMAND_TYPE(p,v) ((p)->cmd1 = ((v) >> 8),(p)->cmd2 = (v)) #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) #define AVB_PACKET_AEM_GET_COMMAND_TYPE(p) ((p)->cmd1 << 8 | (p)->cmd2)

View file

@ -40,6 +40,12 @@
#define server_emit_periodic(s,n) server_emit(s, periodic, 0, n) #define server_emit_periodic(s,n) server_emit(s, periodic, 0, n)
#define server_emit_command(s,n,c,a,f) server_emit(s, command, 0, n, c, a, f) #define server_emit_command(s,n,c,a,f) server_emit(s, command, 0, n, c, a, f)
static const char *avb_mode_str[] = {
[AVB_MODE_LEGACY] = "AVB Legacy",
[AVB_MODE_MILAN_V12] = "Milan V1.2",
};
static void on_timer_event(void *data) static void on_timer_event(void *data)
{ {
struct server *server = data; struct server *server = data;
@ -252,16 +258,20 @@ struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props)
spa_list_append(&impl->servers, &server->link); spa_list_append(&impl->servers, &server->link);
str = spa_dict_lookup(props, "ifname"); str = spa_dict_lookup(props, "ifname");
server->ifname = str ? strdup(str) : NULL; server->ifname = str ? strdup(str) : NULL;
if ((str = spa_dict_lookup(props, "milan")) != NULL && spa_atob(str))
server->avb_mode = AVB_MODE_MILAN_V12;
else
server->avb_mode = AVB_MODE_LEGACY;
spa_hook_list_init(&server->listener_list); spa_hook_list_init(&server->listener_list);
spa_list_init(&server->descriptors); spa_list_init(&server->descriptors);
spa_list_init(&server->streams);
server->debug_messages = false; server->debug_messages = false;
if ((res = setup_socket(server)) < 0) if ((res = setup_socket(server)) < 0)
goto error_free; goto error_free;
init_descriptors(server);
server->mrp = avb_mrp_new(server); server->mrp = avb_mrp_new(server);
if (server->mrp == NULL) if (server->mrp == NULL)
@ -284,11 +294,10 @@ struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props)
avb_mrp_attribute_begin(server->domain_attr->mrp, 0); avb_mrp_attribute_begin(server->domain_attr->mrp, 0);
avb_mrp_attribute_join(server->domain_attr->mrp, 0, true); avb_mrp_attribute_join(server->domain_attr->mrp, 0, true);
server_create_stream(server, SPA_DIRECTION_INPUT, 0);
server_create_stream(server, SPA_DIRECTION_OUTPUT, 0);
avb_maap_reserve(server->maap, 1); avb_maap_reserve(server->maap, 1);
init_descriptors(server);
return server; return server;
error_free: error_free:
@ -308,10 +317,17 @@ void avdecc_server_free(struct server *server)
{ {
struct impl *impl = server->impl; struct impl *impl = server->impl;
server_destroy_descriptors(server);
spa_list_remove(&server->link); spa_list_remove(&server->link);
if (server->source) if (server->source)
pw_loop_destroy_source(impl->loop, server->source); pw_loop_destroy_source(impl->loop, server->source);
pw_timer_queue_cancel(&server->timer); pw_timer_queue_cancel(&server->timer);
spa_hook_list_clean(&server->listener_list); spa_hook_list_clean(&server->listener_list);
free(server->ifname);
free(server); free(server);
} }
const char *get_avb_mode_str(enum avb_mode mode)
{
return avb_mode_str[mode];
}

View file

@ -0,0 +1,824 @@
/* PipeWire */
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
/* 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 */
#include "adp.h"
#include "aecp-aem.h"
#include "aecp-aem-types.h"
#include "aecp-aem-descriptors.h"
#include "aecp-aem-controls.h"
#include "es-builder.h"
#include "entity-model-milan-v12.h"
static void init_descriptor_legacy_avb(struct server *server)
{
es_builder_add_descriptor(server, AVB_AEM_DESC_STRINGS, 0,
sizeof(struct avb_aem_desc_strings),
&(struct avb_aem_desc_strings)
{
.string_0 = "PipeWire",
.string_1 = "Configuration 1",
.string_2 = "Wim Taymans",
});
es_builder_add_descriptor(server, AVB_AEM_DESC_LOCALE, 0,
sizeof(struct avb_aem_desc_locale),
&(struct avb_aem_desc_locale)
{
.locale_identifier = "en-EN",
.number_of_strings = htons(1),
.base_strings = htons(0)
});
es_builder_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0,
sizeof(struct avb_aem_desc_entity),
&(struct avb_aem_desc_entity)
{
.entity_id = htobe64(server->entity_id),
.entity_model_id = htobe64(0),
.entity_capabilities = htonl(
AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED |
AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED |
AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED |
AVB_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID |
AVB_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID),
.talker_stream_sources = htons(8),
.talker_capabilities = htons(
AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED |
AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE),
.listener_stream_sinks = htons(8),
.listener_capabilities = htons(
AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED |
AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK),
.controller_capabilities = htons(0),
.available_index = htonl(0),
.association_id = htobe64(0),
.entity_name = "PipeWire",
.vendor_name_string = htons(2),
.model_name_string = htons(0),
.firmware_version = "0.3.48",
.group_name = "",
.serial_number = "",
.configurations_count = htons(1),
.current_configuration = htons(0)
});
struct {
struct avb_aem_desc_configuration desc;
struct avb_aem_desc_descriptor_count descriptor_counts[8];
} __attribute__ ((__packed__)) config =
{
{
.object_name = "Configuration 1",
.localized_description = htons(1),
.descriptor_counts_count = htons(8),
.descriptor_counts_offset = htons(
4 + sizeof(struct avb_aem_desc_configuration)),
},
.descriptor_counts = {
{ htons(AVB_AEM_DESC_AUDIO_UNIT), htons(1) },
{ htons(AVB_AEM_DESC_STREAM_INPUT), htons(1) },
{ htons(AVB_AEM_DESC_STREAM_OUTPUT), htons(1) },
{ htons(AVB_AEM_DESC_AVB_INTERFACE), htons(1) },
{ htons(AVB_AEM_DESC_CLOCK_SOURCE), htons(1) },
{ htons(AVB_AEM_DESC_CONTROL), htons(2) },
{ htons(AVB_AEM_DESC_LOCALE), htons(1) },
{ htons(AVB_AEM_DESC_CLOCK_DOMAIN), htons(1) }
}
};
es_builder_add_descriptor(server, AVB_AEM_DESC_CONFIGURATION, 0,
sizeof(config), &config);
struct {
struct avb_aem_desc_audio_unit desc;
struct avb_aem_desc_sampling_rate sampling_rates[6];
} __attribute__ ((__packed__)) audio_unit =
{
{
.object_name = "PipeWire",
.localized_description = htons(0),
.clock_domain_index = htons(0),
.number_of_stream_input_ports = htons(1),
.base_stream_input_port = htons(0),
.number_of_stream_output_ports = htons(1),
.base_stream_output_port = htons(0),
.number_of_external_input_ports = htons(8),
.base_external_input_port = htons(0),
.number_of_external_output_ports = htons(8),
.base_external_output_port = htons(0),
.number_of_internal_input_ports = htons(0),
.base_internal_input_port = htons(0),
.number_of_internal_output_ports = htons(0),
.base_internal_output_port = htons(0),
.number_of_controls = htons(0),
.base_control = htons(0),
.number_of_signal_selectors = htons(0),
.base_signal_selector = htons(0),
.number_of_mixers = htons(0),
.base_mixer = htons(0),
.number_of_matrices = htons(0),
.base_matrix = htons(0),
.number_of_splitters = htons(0),
.base_splitter = htons(0),
.number_of_combiners = htons(0),
.base_combiner = htons(0),
.number_of_demultiplexers = htons(0),
.base_demultiplexer = htons(0),
.number_of_multiplexers = htons(0),
.base_multiplexer = htons(0),
.number_of_transcoders = htons(0),
.base_transcoder = htons(0),
.number_of_control_blocks = htons(0),
.base_control_block = htons(0),
.current_sampling_rate = htonl(48000),
.sampling_rates_offset = htons(
4 + sizeof(struct avb_aem_desc_audio_unit)),
.sampling_rates_count = htons(6),
},
.sampling_rates = {
{ .pull_frequency = htonl(44100) },
{ .pull_frequency = htonl(48000) },
{ .pull_frequency = htonl(88200) },
{ .pull_frequency = htonl(96000) },
{ .pull_frequency = htonl(176400) },
{ .pull_frequency = htonl(192000) },
}
};
es_builder_add_descriptor(server, AVB_AEM_DESC_AUDIO_UNIT, 0,
sizeof(audio_unit), &audio_unit);
struct {
struct avb_aem_desc_stream desc;
uint64_t stream_formats[6];
} __attribute__ ((__packed__)) stream_input_0 =
{
{
.object_name = "Stream Input 1",
.localized_description = htons(0xffff),
.clock_domain_index = htons(0),
.stream_flags = htons(
AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE |
AVB_AEM_DESC_STREAM_FLAG_CLASS_A),
.current_format = htobe64(0x00a0020840000800ULL),
.formats_offset = htons(
4 + sizeof(struct avb_aem_desc_stream)),
.number_of_formats = htons(6),
.backup_talker_entity_id_0 = htobe64(0),
.backup_talker_unique_id_0 = htons(0),
.backup_talker_entity_id_1 = htobe64(0),
.backup_talker_unique_id_1 = htons(0),
.backup_talker_entity_id_2 = htobe64(0),
.backup_talker_unique_id_2 = htons(0),
.backedup_talker_entity_id = htobe64(0),
.backedup_talker_unique = htons(0),
.avb_interface_index = htons(0),
.buffer_length = htons(8)
},
.stream_formats = {
htobe64(0x00a0010860000800ULL),
htobe64(0x00a0020860000800ULL),
htobe64(0x00a0030860000800ULL),
htobe64(0x00a0040860000800ULL),
htobe64(0x00a0050860000800ULL),
htobe64(0x00a0060860000800ULL),
},
};
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_INPUT, 0,
sizeof(stream_input_0), &stream_input_0);
struct {
struct avb_aem_desc_stream desc;
uint64_t stream_formats[6];
} __attribute__ ((__packed__)) stream_output_0 =
{
{
.object_name = "Stream Output 1",
.localized_description = htons(0xffff),
.clock_domain_index = htons(0),
.stream_flags = htons(
AVB_AEM_DESC_STREAM_FLAG_CLASS_A),
.current_format = htobe64(0x00a0020840000800ULL),
.formats_offset = htons(
4 + sizeof(struct avb_aem_desc_stream)),
.number_of_formats = htons(6),
.backup_talker_entity_id_0 = htobe64(0),
.backup_talker_unique_id_0 = htons(0),
.backup_talker_entity_id_1 = htobe64(0),
.backup_talker_unique_id_1 = htons(0),
.backup_talker_entity_id_2 = htobe64(0),
.backup_talker_unique_id_2 = htons(0),
.backedup_talker_entity_id = htobe64(0),
.backedup_talker_unique = htons(0),
.avb_interface_index = htons(0),
.buffer_length = htons(8)
},
.stream_formats = {
htobe64(0x00a0010860000800ULL),
htobe64(0x00a0020860000800ULL),
htobe64(0x00a0030860000800ULL),
htobe64(0x00a0040860000800ULL),
htobe64(0x00a0050860000800ULL),
htobe64(0x00a0060860000800ULL),
},
};
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_OUTPUT, 0,
sizeof(stream_output_0), &stream_output_0);
struct avb_aem_desc_avb_interface avb_interface = {
.localized_description = htons(0xffff),
.interface_flags = htons(
AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED),
.clock_identity = htobe64(0),
.priority1 = 0,
.clock_class = 0,
.offset_scaled_log_variance = htons(0),
.clock_accuracy = 0,
.priority2 = 0,
.domain_number = 0,
.log_sync_interval = 0,
.log_announce_interval = 0,
.log_pdelay_interval = 0,
.port_number = 0,
};
strncpy(avb_interface.object_name, server->ifname, 63);
memcpy(avb_interface.mac_address, server->mac_addr, 6);
es_builder_add_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0,
sizeof(avb_interface), &avb_interface);
struct avb_aem_desc_clock_source clock_source = {
.object_name = "Stream Clock",
.localized_description = htons(0xffff),
.clock_source_flags = htons(0),
.clock_source_type = htons(
AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM),
.clock_source_identifier = htobe64(0),
.clock_source_location_type = htons(AVB_AEM_DESC_STREAM_INPUT),
.clock_source_location_index = htons(0),
};
es_builder_add_descriptor(server, AVB_AEM_DESC_CLOCK_SOURCE, 0,
sizeof(clock_source), &clock_source);
}
static void init_descriptor_milan_v12(struct server *server)
{
// TODO PERSISTENCE: retrieve the saved buffers.
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.12 - STRINGS Descriptor
* Up to 7 localized strings
*/
es_builder_add_descriptor(server, AVB_AEM_DESC_STRINGS, 0,
sizeof(struct avb_aem_desc_strings),
&(struct avb_aem_desc_strings)
{
.string_0 = DSC_STRINGS_0_DEVICE_NAME,
.string_1 = DSC_STRINGS_1_CONFIGURATION_NAME,
.string_2 = DSC_STRINGS_2_MANUFACTURER_NAME,
.string_3 = DSC_STRINGS_3_GROUP_NAME,
.string_4 = DSC_STRINGS_4_MAINTAINER_0,
.string_5 = DSC_STRINGS_4_MAINTAINER_1,
}
);
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.11 - LOCALE Descriptor */
es_builder_add_descriptor(server, AVB_AEM_DESC_LOCALE, 0,
sizeof(struct avb_aem_desc_locale),
&(struct avb_aem_desc_locale)
{
.locale_identifier = DSC_LOCALE_LANGUAGE_CODE,
.number_of_strings = htons(DSC_LOCALE_NO_OF_STRINGS),
.base_strings = htons(DSC_LOCALE_BASE_STRINGS)
});
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.1 - ENTITY Descriptor */
/* Milan v1.2, Sec. 5.3.3.1 */
es_builder_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0,
sizeof(struct avb_aem_desc_entity),
&(struct avb_aem_desc_entity)
{
.entity_id = htobe64(DSC_ENTITY_MODEL_ENTITY_ID),
.entity_model_id = htobe64(DSC_ENTITY_MODEL_ID),
.entity_capabilities = htonl(DSC_ENTITY_MODEL_ENTITY_CAPABILITIES),
.talker_stream_sources = htons(DSC_ENTITY_MODEL_TALKER_STREAM_SOURCES),
.talker_capabilities = htons(DSC_ENTITY_MODEL_TALKER_CAPABILITIES),
.listener_stream_sinks = htons(DSC_ENTITY_MODEL_LISTENER_STREAM_SINKS),
.listener_capabilities = htons(DSC_ENTITY_MODEL_LISTENER_CAPABILITIES),
.controller_capabilities = htons(DSC_ENTITY_MODEL_CONTROLLER_CAPABILITIES),
.available_index = htonl(DSC_ENTITY_MODEL_AVAILABLE_INDEX),
.association_id = htobe64(DSC_ENTITY_MODEL_ASSOCIATION_ID),
.entity_name = DSC_ENTITY_MODEL_ENTITY_NAME,
.vendor_name_string = htons(DSC_ENTITY_MODEL_VENDOR_NAME_STRING),
.model_name_string = htons(DSC_ENTITY_MODEL_MODEL_NAME_STRING),
.firmware_version = DSC_ENTITY_MODEL_FIRMWARE_VERSION,
.group_name = DSC_ENTITY_MODEL_GROUP_NAME,
.serial_number = DSC_ENTITY_MODEL_SERIAL_NUMBER,
.configurations_count = htons(DSC_ENTITY_MODEL_CONFIGURATIONS_COUNT),
.current_configuration = htons(DSC_ENTITY_MODEL_CURRENT_CONFIGURATION)
});
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.2 - CONFIGURATION Descriptor*/
/* Milan v1.2, Sec. 5.3.3.2 */
struct {
struct avb_aem_desc_configuration desc;
struct avb_aem_desc_descriptor_count descriptor_counts[DSC_CONFIGURATION_DESCRIPTOR_COUNTS_COUNT];
} __attribute__ ((__packed__)) config =
{
{
.object_name = DSC_CONFIGURATION_OBJECT_NAME,
.localized_description = htons(DSC_CONFIGURATION_LOCALIZED_DESCRIPTION),
.descriptor_counts_count = htons(DSC_CONFIGURATION_DESCRIPTOR_COUNTS_COUNT),
// TODO: Does it work? Was the commented out lines, now replaced with hard coded value from IEEE.
// .descriptor_counts_offset = htons(
// 4 + sizeof(struct avb_aem_desc_configuration)),
// },
.descriptor_counts_offset = htons(DSC_CONFIGURATION_DESCRIPTOR_COUNTS_OFFSET),
},
.descriptor_counts = {
{ htons(AVB_AEM_DESC_AUDIO_UNIT), htons(DSC_CONFIGURATION_NO_OF_AUDIO_UNITS) },
{ htons(AVB_AEM_DESC_STREAM_INPUT), htons(DSC_CONFIGURATION_NO_OF_STREAM_INPUTS) },
{ htons(AVB_AEM_DESC_STREAM_OUTPUT), htons(DSC_CONFIGURATION_NO_OF_STREAM_OUTPUTS) },
{ htons(AVB_AEM_DESC_AVB_INTERFACE), htons(DSC_CONFIGURATION_NO_OF_AVB_INTERFACES) },
{ htons(AVB_AEM_DESC_CLOCK_DOMAIN), htons(DSC_CONFIGURATION_NO_OF_CLOCK_DOMAINS) },
{ htons(AVB_AEM_DESC_CLOCK_SOURCE), htons(DSC_CONFIGURATION_NO_OF_CLOCK_SOURCES) },
{ htons(AVB_AEM_DESC_CONTROL), htons(DSC_CONFIGURATION_NO_OF_CONTROLS) },
{ htons(AVB_AEM_DESC_LOCALE), htons(DSC_CONFIGURATION_NO_OF_LOCALES) },
}
};
es_builder_add_descriptor(server, AVB_AEM_DESC_CONFIGURATION, 0,
sizeof(config), &config);
/* Second configuration for tests*/
struct {
struct avb_aem_desc_configuration desc;
struct avb_aem_desc_descriptor_count descriptor_counts[8];
} __attribute__ ((__packed__)) config1 =
{
{
.object_name = "Non - redundant - 96kHz",
.localized_description = htons(1),
.descriptor_counts_count = htons(8),
.descriptor_counts_offset = htons(
4 + sizeof(struct avb_aem_desc_configuration)),
},
.descriptor_counts = {
{ htons(AVB_AEM_DESC_AUDIO_UNIT), htons(1) },
{ htons(AVB_AEM_DESC_STREAM_INPUT), htons(2) },
{ htons(AVB_AEM_DESC_STREAM_OUTPUT), htons(1) },
{ htons(AVB_AEM_DESC_AVB_INTERFACE), htons(1) },
{ htons(AVB_AEM_DESC_CLOCK_DOMAIN), htons(1) },
{ htons(AVB_AEM_DESC_CLOCK_SOURCE), htons(3) },
{ htons(AVB_AEM_DESC_CONTROL), htons(1) },
{ htons(AVB_AEM_DESC_LOCALE), htons(1) },
}
};
es_builder_add_descriptor(server, AVB_AEM_DESC_CONFIGURATION, 1,
sizeof(config), &config1);
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.22 CONTROL Descriptor*/
/* Milan v1.2, Sec. 5.3.3.10 */
struct {
struct avb_aem_desc_control desc;
struct avb_aem_desc_value_format value_inf;
} __attribute__ ((__packed__)) ctrl =
{
{
.object_name = DSC_CONTROL_OBJECT_NAME,
.localized_description = htons(DSC_CONTROL_LOCALIZED_DESCRIPTION),
.block_latency = htons(DSC_CONTROL_BLOCK_LATENCY),
.control_latency = htons(DSC_CONTROL_CONTROL_LATENCY),
.control_domain = htons(DSC_CONTROL_CONTROL_DOMAIN),
.control_value_type = htons(DSC_CONTROL_CONTROL_VALUE_TYPE),
.control_type = htobe64(DSC_CONTROL_CONTROL_TYPE),
.reset_time = htonl(DSC_CONTROL_RESET_TIME),
// TODO: This is not specified in Table 7-38
.descriptor_counts_offset = htons(
4 + sizeof(struct avb_aem_desc_control)),
.number_of_values = htons(1),
.signal_type = htons(0xffff),
.signal_index = htons(0),
.signal_output = htons(0),
},
{
.minimum = DSC_CONTROL_IDENTIFY_MIN,
.maximum = DSC_CONTROL_IDENTIFY_MAX,
.step = DSC_CONTROL_IDENTIFY_STEP,
.default_value = DSC_CONTROL_IDENTIFY_DEFAULT_VALUE,
.current_value = DSC_CONTROL_IDENTIFY_CURRENT_VALUE,
.localized_description = htons(DSC_CONTROL_LOCALIZED_DESCRIPTION),
}
};
es_builder_add_descriptor(server, AVB_AEM_DESC_CONTROL, 0,
sizeof(ctrl), &ctrl);
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.19 AUDIO_MAP Descriptor */
/* Milan v1.2, Sec. 5.3.3.9 */
struct {
struct avb_aem_desc_audio_map desc;
struct avb_aem_audio_mapping_format maps[DSC_AUDIO_MAPS_NO_OF_MAPPINGS];
} __attribute__((__packed__)) maps_input = {
.desc = {
.mapping_offset = htons(AVB_AEM_AUDIO_MAPPING_FORMAT_OFFSET),
.number_of_mappings = htons(DSC_AUDIO_MAPS_NO_OF_MAPPINGS),
},
};
for (uint32_t map_idx = 0; map_idx < DSC_AUDIO_MAPS_NO_OF_MAPPINGS; map_idx++) {
maps_input.maps[map_idx].mapping_stream_index = htons(DSC_AUDIO_MAPS_MAPPING_STREAM_INDEX);
maps_input.maps[map_idx].mapping_cluster_channel = htons(DSC_AUDIO_MAPS_MAPPING_CLUSTER_CHANNEL);
maps_input.maps[map_idx].mapping_cluster_offset = htons(map_idx);
maps_input.maps[map_idx].mapping_stream_channel = htons(map_idx);
}
es_builder_add_descriptor(server, AVB_AEM_DESC_AUDIO_MAP, 0,
sizeof(maps_input), &maps_input);
struct {
struct avb_aem_desc_audio_map desc;
struct avb_aem_audio_mapping_format maps[DSC_AUDIO_MAPS_NO_OF_MAPPINGS];
} __attribute__((__packed__)) maps_output= {
.desc = {
.mapping_offset = htons(AVB_AEM_AUDIO_MAPPING_FORMAT_OFFSET),
.number_of_mappings = htons(DSC_AUDIO_MAPS_NO_OF_MAPPINGS),
},
};
for (uint32_t map_idx = 0; map_idx < DSC_AUDIO_MAPS_NO_OF_MAPPINGS; map_idx++) {
maps_output.maps[map_idx].mapping_stream_index = htons(DSC_AUDIO_MAPS_MAPPING_STREAM_INDEX);
maps_output.maps[map_idx].mapping_cluster_channel = htons(DSC_AUDIO_MAPS_MAPPING_CLUSTER_CHANNEL);
maps_output.maps[map_idx].mapping_cluster_offset = htons(DSC_AUDIO_MAPS_NO_OF_MAPPINGS+map_idx);
maps_output.maps[map_idx].mapping_stream_channel = htons(DSC_AUDIO_MAPS_NO_OF_MAPPINGS+map_idx);
}
es_builder_add_descriptor(server, AVB_AEM_DESC_AUDIO_MAP, 1,
sizeof(maps_output), &maps_output);
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.16 AUDIO_CLUSTER Descriptor */
/* Milan v1.2, Sec. 5.3.3.8 */
struct avb_aem_desc_audio_cluster clusters[DSC_AUDIO_CLUSTER_NO_OF_CLUSTERS];
for (uint32_t cluster_idx = 0; cluster_idx < DSC_AUDIO_CLUSTER_NO_OF_CLUSTERS; cluster_idx++) {
memset(clusters[cluster_idx].object_name, 0,
sizeof(clusters[cluster_idx].object_name));
// TODO: Make this scale automatically
if (cluster_idx < 8) {
snprintf(clusters[cluster_idx].object_name, DSC_AUDIO_CLUSTER_OBJECT_NAME_LEN_IN_OCTET-1,
"Input %2u", cluster_idx);
} else {
snprintf(clusters[cluster_idx].object_name, DSC_AUDIO_CLUSTER_OBJECT_NAME_LEN_IN_OCTET-1,
"Output %2u", cluster_idx);
}
clusters[cluster_idx].localized_description = htons(DSC_AUDIO_CLUSTER_LOCALIZED_DESCRIPTION);
clusters[cluster_idx].signal_type = htons(DSC_AUDIO_CLUSTER_SIGNAL_TYPE);
clusters[cluster_idx].signal_index = htons(DSC_AUDIO_CLUSTER_SIGNAL_INDEX);
clusters[cluster_idx].signal_output = htons(DSC_AUDIO_CLUSTER_SIGNAL_OUTPUT);
clusters[cluster_idx].path_latency = htonl(DSC_AUDIO_CLUSTER_PATH_LATENCY_IN_NS);
clusters[cluster_idx].block_latency = htonl(DSC_AUDIO_CLUSTER_BLOCK_LATENCY_IN_NS);
clusters[cluster_idx].channel_count = htons(DSC_AUDIO_CLUSTER_CHANNEL_COUNT);
clusters[cluster_idx].format = DSC_AUDIO_CLUSTER_FORMAT;
clusters[cluster_idx].aes3_data_type_ref = DSC_AUDIO_CLUSTER_AES3_DATA_TYPE_REF;
clusters[cluster_idx].aes3_data_type = htons(DSC_AUDIO_CLUSTER_AES3_DATA_TYPE);
es_builder_add_descriptor(server, AVB_AEM_DESC_AUDIO_CLUSTER, cluster_idx,
sizeof(clusters[0]), &clusters[cluster_idx]);
}
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.13 STREAM_PORT_INPUT Descriptor */
/* Milan v1.2, Sec. 5.3.3.7 */
struct avb_aem_desc_stream_port stream_port_input0 = {
.clock_domain_index = htons(DSC_STREAM_PORT_INPUT_CLOCK_DOMAIN_INDEX),
.port_flags = htons(DSC_STREAM_PORT_INPUT_PORT_FLAGS),
.number_of_controls = htons(DSC_STREAM_PORT_INPUT_NUMBER_OF_CONTROLS),
.base_control = htons(DSC_STREAM_PORT_INPUT_BASE_CONTROL),
.number_of_clusters = htons(DSC_STREAM_PORT_INPUT_NUMBER_OF_CLUSTERS),
.base_cluster = htons(DSC_STREAM_PORT_INPUT_BASE_CLUSTER),
.number_of_maps = htons(DSC_STREAM_PORT_INPUT_NUMBER_OF_MAPS),
.base_map = htons(DSC_STREAM_PORT_INPUT_BASE_MAP),
};
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_PORT_INPUT, 0,
sizeof(stream_port_input0), &stream_port_input0);
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.13 STREAM_PORT_OUTPUT Descriptor */
/* Milan v1.2, Sec. 5.3.3.7 */
struct avb_aem_desc_stream_port stream_port_output0 = {
.clock_domain_index = htons(DSC_STREAM_PORT_OUTPUT_CLOCK_DOMAIN_INDEX),
.port_flags = htons(DSC_STREAM_PORT_OUTPUT_PORT_FLAGS),
.number_of_controls = htons(DSC_STREAM_PORT_OUTPUT_NUMBER_OF_CONTROLS),
.base_control = htons(DSC_STREAM_PORT_OUTPUT_BASE_CONTROL),
.number_of_clusters = htons(DSC_STREAM_PORT_OUTPUT_NUMBER_OF_CLUSTERS),
.base_cluster = htons(DSC_STREAM_PORT_OUTPUT_BASE_CLUSTER),
.number_of_maps = htons(DSC_STREAM_PORT_OUTPUT_NUMBER_OF_MAPS),
.base_map = htons(DSC_STREAM_PORT_OUTPUT_BASE_MAP),
};
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_PORT_OUTPUT, 0,
sizeof(stream_port_output0), &stream_port_output0);
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.3 AUDIO_UNIT Descriptor */
/* Milan v1.2, Sec. 5.3.3.3 */
struct {
struct avb_aem_desc_audio_unit desc;
struct avb_aem_desc_sampling_rate sampling_rates[DSC_AUDIO_UNIT_SUPPORTED_SAMPLING_RATE_COUNT];
} __attribute__ ((__packed__)) audio_unit =
{
{
.object_name = DSC_AUDIO_UNIT_OBJECT_NAME,
.localized_description = htons(DSC_AUDIO_UNIT_LOCALIZED_DESCRIPTION),
.clock_domain_index = htons(DSC_AUDIO_UNIT_CLOCK_DOMAIN_INDEX),
.number_of_stream_input_ports = htons(DSC_AUDIO_UNIT_NUMBER_OF_STREAM_INPUT_PORTS),
.base_stream_input_port = htons(DSC_AUDIO_UNIT_BASE_STREAM_INPUT_PORT),
.number_of_stream_output_ports = htons(DSC_AUDIO_UNIT_NUMBER_OF_STREAM_OUTPUT_PORTS),
.base_stream_output_port = htons(DSC_AUDIO_UNIT_BASE_STREAM_OUTPUT_PORT),
.number_of_external_input_ports = htons(DSC_AUDIO_UNIT_NUMBER_OF_EXTERNAL_INPUT_PORTS),
.base_external_input_port = htons(DSC_AUDIO_UNIT_BASE_EXTERNAL_INPUT_PORT),
.number_of_external_output_ports = htons(DSC_AUDIO_UNIT_NUMBER_OF_EXTERNAL_OUTPUT_PORTS),
.base_external_output_port = htons(DSC_AUDIO_UNIT_BASE_EXTERNAL_OUTPUT_PORT),
.number_of_internal_input_ports = htons(DSC_AUDIO_UNIT_NUMBER_OF_INTERNAL_INPUT_PORTS),
.base_internal_input_port = htons(DSC_AUDIO_UNIT_BASE_INTERNAL_INPUT_PORT),
.number_of_internal_output_ports = htons(DSC_AUDIO_UNIT_NUMBER_OF_INTERNAL_OUTPUT_PORTS),
.base_internal_output_port = htons(DSC_AUDIO_UNIT_BASE_INTERNAL_OUTPUT_PORT),
.number_of_controls = htons(DSC_AUDIO_UNIT_NUMBER_OF_CONTROLS),
.base_control = htons(DSC_AUDIO_UNIT_BASE_CONTROL),
.number_of_signal_selectors = htons(DSC_AUDIO_UNIT_NUMBER_OF_SIGNAL_SELECTORS),
.base_signal_selector = htons(DSC_AUDIO_UNIT_BASE_SIGNAL_SELECTOR),
.number_of_mixers = htons(DSC_AUDIO_UNIT_NUMBER_OF_MIXERS),
.base_mixer = htons(DSC_AUDIO_UNIT_BASE_MIXER),
.number_of_matrices = htons(DSC_AUDIO_UNIT_NUMBER_OF_MATRICES),
.base_matrix = htons(DSC_AUDIO_UNIT_BASE_MATRIX),
.number_of_splitters = htons(DSC_AUDIO_UNIT_NUMBER_OF_SPLITTERS),
.base_splitter = htons(DSC_AUDIO_UNIT_BASE_SPLITTER),
.number_of_combiners = htons(DSC_AUDIO_UNIT_NUMBER_OF_COMBINERS),
.base_combiner = htons(DSC_AUDIO_UNIT_BASE_COMBINER),
.number_of_demultiplexers = htons(DSC_AUDIO_UNIT_NUMBER_OF_DEMULTIPLEXERS),
.base_demultiplexer = htons(DSC_AUDIO_UNIT_BASE_DEMULTIPLEXER),
.number_of_multiplexers = htons(DSC_AUDIO_UNIT_NUMBER_OF_MULTIPLEXERS),
.base_multiplexer = htons(DSC_AUDIO_UNIT_BASE_MULTIPLEXER),
.number_of_transcoders = htons(DSC_AUDIO_UNIT_NUMBER_OF_TRANSCODERS),
.base_transcoder = htons(DSC_AUDIO_UNIT_BASE_TRANSCODER),
.number_of_control_blocks = htons(DSC_AUDIO_UNIT_NUMBER_OF_CONTROL_BLOCKS),
.base_control_block = htons(DSC_AUDIO_UNIT_BASE_CONTROL_BLOCK),
.current_sampling_rate = htonl(DSC_AUDIO_UNIT_CURRENT_SAMPLING_RATE_IN_HZ),
.sampling_rates_offset = htons(DSC_AUDIO_UNIT_SAMPLING_RATES_OFFSET),
.sampling_rates_count = htons(DSC_AUDIO_UNIT_SUPPORTED_SAMPLING_RATE_COUNT),
},
.sampling_rates = {
// Set the list of supported audio unit sample rate
{ .pull_frequency = htonl(DSC_AUDIO_UNIT_SUPPORTED_SAMPLING_RATE_IN_HZ_0) },
}
};
es_builder_add_descriptor(server, AVB_AEM_DESC_AUDIO_UNIT, 0,
sizeof(audio_unit), &audio_unit);
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.6 STREAM_INPUT Descriptor */
/* Milan v1.2, Sec. 5.3.3.4 */
// TODO: 1722.1 lists redundant parameters that are not mentioned here.
struct {
struct avb_aem_desc_stream desc;
uint64_t stream_formats[DSC_STREAM_INPUT_NUMBER_OF_FORMATS];
} __attribute__ ((__packed__)) stream_input_0 =
{
{
.object_name = DSC_STREAM_INPUT_OBJECT_NAME,
.localized_description = htons(DSC_STREAM_INPUT_LOCALIZED_DESCRIPTION),
.clock_domain_index = htons(DSC_STREAM_INPUT_CLOCK_DOMAIN_INDEX),
.stream_flags = htons(DSC_STREAM_INPUT_STREAM_FLAGS),
.current_format = htobe64(DSC_STREAM_INPUT_CURRENT_FORMAT),
.formats_offset = htons(DSC_STREAM_INPUT_FORMATS_OFFSET),
.number_of_formats = htons(DSC_STREAM_INPUT_NUMBER_OF_FORMATS),
.backup_talker_entity_id_0 = htobe64(DSC_STREAM_INPUT_BACKUP_TALKER_ENTITY_ID_0),
.backup_talker_unique_id_0 = htons(DSC_STREAM_INPUT_BACKUP_TALKER_UNIQUE_ID_0),
.backup_talker_entity_id_1 = htobe64(DSC_STREAM_INPUT_BACKUP_TALKER_ENTITY_ID_1),
.backup_talker_unique_id_1 = htons(DSC_STREAM_INPUT_BACKUP_TALKER_UNIQUE_ID_1),
.backup_talker_entity_id_2 = htobe64(DSC_STREAM_INPUT_BACKUP_TALKER_ENTITY_ID_2),
.backup_talker_unique_id_2 = htons(DSC_STREAM_INPUT_BACKUP_TALKER_UNIQUE_ID_2),
.backedup_talker_entity_id = htobe64(DSC_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_ID),
.backedup_talker_unique = htons(DSC_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_ID),
.avb_interface_index = htons(DSC_STREAM_INPUT_AVB_INTERFACE_INDEX),
.buffer_length = htonl(DSC_STREAM_INPUT_BUFFER_LENGTH_IN_NS)
},
.stream_formats = {
htobe64(DSC_STREAM_INPUT_FORMATS_0),
htobe64(DSC_STREAM_INPUT_FORMATS_1),
htobe64(DSC_STREAM_INPUT_FORMATS_2),
htobe64(DSC_STREAM_INPUT_FORMATS_3),
htobe64(DSC_STREAM_INPUT_FORMATS_4),
},
};
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_INPUT, 0,
sizeof(stream_input_0), &stream_input_0);
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.6 STREAM_INPUT Descriptor */
/* Milan v1.2, Sec. 5.3.3.4 */
struct {
struct avb_aem_desc_stream desc;
uint64_t stream_formats[DSC_STREAM_INPUT_CRF_NUMBER_OF_FORMATS];
} __attribute__ ((__packed__)) stream_input_crf_0 =
{
{
.object_name = DSC_STREAM_INPUT_CRF_OBJECT_NAME,
.localized_description = htons(DSC_STREAM_INPUT_CRF_LOCALIZED_DESCRIPTION),
.clock_domain_index = htons(DSC_STREAM_INPUT_CRF_CLOCK_DOMAIN_INDEX),
.stream_flags = htons(DSC_STREAM_INPUT_CRF_STREAM_FLAGS),
.current_format = htobe64(DSC_STREAM_INPUT_CRF_CURRENT_FORMAT),
.formats_offset = htons(DSC_STREAM_INPUT_CRF_FORMATS_OFFSET),
.number_of_formats = htons(DSC_STREAM_INPUT_CRF_NUMBER_OF_FORMATS),
.backup_talker_entity_id_0 = htobe64(DSC_STREAM_INPUT_CRF_BACKUP_TALKER_ENTITY_ID_0),
.backup_talker_unique_id_0 = htons(DSC_STREAM_INPUT_CRF_BACKUP_TALKER_UNIQUE_ID_0),
.backup_talker_entity_id_1 = htobe64(DSC_STREAM_INPUT_CRF_BACKUP_TALKER_ENTITY_ID_1),
.backup_talker_unique_id_1 = htons(DSC_STREAM_INPUT_CRF_BACKUP_TALKER_UNIQUE_ID_1),
.backup_talker_entity_id_2 = htobe64(DSC_STREAM_INPUT_CRF_BACKUP_TALKER_ENTITY_ID_2),
.backup_talker_unique_id_2 = htons(DSC_STREAM_INPUT_CRF_BACKUP_TALKER_UNIQUE_ID_2),
.backedup_talker_entity_id = htobe64(DSC_STREAM_INPUT_CRF_BACKEDUP_TALKER_ENTITY_ID),
.backedup_talker_unique = htons(DSC_STREAM_INPUT_CRF_BACKEDUP_TALKER_UNIQUE_ID),
.avb_interface_index = htons(DSC_STREAM_INPUT_CRF_AVB_INTERFACE_INDEX),
.buffer_length = htonl(DSC_STREAM_INPUT_CRF_BUFFER_LENGTH_IN_NS)
},
.stream_formats = {
htobe64(DSC_STREAM_INPUT_CRF_FORMATS_0),
},
};
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_INPUT, 1,
sizeof(stream_input_crf_0), &stream_input_crf_0);
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.6 STREAM_OUTPUT Descriptor */
/* Milan v1.2, Sec. 5.3.3.4 */
struct {
struct avb_aem_desc_stream desc;
uint64_t stream_formats[DSC_STREAM_OUTPUT_NUMBER_OF_FORMATS];
} __attribute__ ((__packed__)) stream_output_0 =
{
{
.object_name = DSC_STREAM_OUTPUT_OBJECT_NAME,
.localized_description = htons(DSC_STREAM_OUTPUT_LOCALIZED_DESCRIPTION),
.clock_domain_index = htons(DSC_STREAM_OUTPUT_CLOCK_DOMAIN_INDEX),
.stream_flags = htons(DSC_STREAM_OUTPUT_STREAM_FLAGS),
.current_format = htobe64(DSC_STREAM_OUTPUT_CURRENT_FORMAT),
.formats_offset = htons(DSC_STREAM_OUTPUT_FORMATS_OFFSET),
.number_of_formats = htons(DSC_STREAM_OUTPUT_NUMBER_OF_FORMATS),
.backup_talker_entity_id_0 = htobe64(DSC_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_ID_0),
.backup_talker_unique_id_0 = htons(DSC_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_ID_0),
.backup_talker_entity_id_1 = htobe64(DSC_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_ID_1),
.backup_talker_unique_id_1 = htons(DSC_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_ID_1),
.backup_talker_entity_id_2 = htobe64(DSC_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_ID_2),
.backup_talker_unique_id_2 = htons(DSC_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_ID_2),
.backedup_talker_entity_id = htobe64(DSC_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_ID),
.backedup_talker_unique = htons(DSC_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_ID),
.avb_interface_index = htons(DSC_STREAM_OUTPUT_AVB_INTERFACE_INDEX),
.buffer_length = htons(DSC_STREAM_OUTPUT_BUFFER_LENGTH_IN_NS)
},
.stream_formats = {
htobe64(DSC_STREAM_OUTPUT_FORMATS_0),
htobe64(DSC_STREAM_OUTPUT_FORMATS_1),
htobe64(DSC_STREAM_OUTPUT_FORMATS_2),
htobe64(DSC_STREAM_OUTPUT_FORMATS_3),
htobe64(DSC_STREAM_OUTPUT_FORMATS_4),
},
};
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_OUTPUT, 0,
sizeof(stream_output_0), &stream_output_0);
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.8 AVB Interface Descriptor */
/* Milan v1.2, Sec. 5.3.3.5 */
struct avb_aem_desc_avb_interface avb_interface = {
.localized_description = htons(DSC_AVB_INTERFACE_LOCALIZED_DESCRIPTION),
.interface_flags = htons(DSC_AVB_INTERFACE_INTERFACE_FLAGS),
.clock_identity = htobe64(DSC_AVB_INTERFACE_CLOCK_IDENTITY),
.priority1 = DSC_AVB_INTERFACE_PRIORITY1,
.clock_class = DSC_AVB_INTERFACE_CLOCK_CLASS,
.offset_scaled_log_variance = htons(DSC_AVB_INTERFACE_OFFSET_SCALED_LOG_VARIANCE),
.clock_accuracy = DSC_AVB_INTERFACE_CLOCK_ACCURACY,
.priority2 = DSC_AVB_INTERFACE_PRIORITY2,
.domain_number = DSC_AVB_INTERFACE_DOMAIN_NUMBER,
.log_sync_interval = DSC_AVB_INTERFACE_LOG_SYNC_INTERVAL,
.log_announce_interval = DSC_AVB_INTERFACE_LOG_ANNOUNCE_INTERVAL,
.log_pdelay_interval = DSC_AVB_INTERFACE_PDELAY_INTERVAL,
.port_number = DSC_AVB_INTERFACE_PORT_NUMBER,
};
memset(avb_interface.object_name, 0, sizeof(avb_interface.object_name));
strncpy(avb_interface.object_name, "", 63);
memcpy(avb_interface.mac_address, server->mac_addr, 6);
es_builder_add_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0,
sizeof(avb_interface), &avb_interface);
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.9 CLOCK_SOURCE Descriptor */
/* Milan v1.2, Sec. 5.3.3.6 */
// Internal Clock Descriptor
struct avb_aem_desc_clock_source clock_source_internal = {
.object_name = DSC_CLOCK_SOURCE_INTERNAL_OBJECT_NAME,
.localized_description = htons(DSC_CLOCK_SOURCE_INTERNAL_LOCALIZED_DESCRIPTION),
.clock_source_flags = htons(DSC_CLOCK_SOURCE_INTERNAL_FLAGS),
.clock_source_type = htons(DSC_CLOCK_SOURCE_INTERNAL_TYPE),
.clock_source_identifier = htobe64(DSC_CLOCK_SOURCE_INTERNAL_IDENTIFIER),
.clock_source_location_type = htons(DSC_CLOCK_SOURCE_INTERNAL_LOCATION_TYPE),
.clock_source_location_index = htons(DSC_CLOCK_SOURCE_INTERNAL_LOCATION_INDEX),
};
es_builder_add_descriptor(server, AVB_AEM_DESC_CLOCK_SOURCE, 0,
sizeof(clock_source_internal), &clock_source_internal);
// AAF Clock Descriptor
struct avb_aem_desc_clock_source clock_source_aaf = {
.object_name = DSC_CLOCK_SOURCE_AAF_OBJECT_NAME,
.localized_description = htons(DSC_CLOCK_SOURCE_AAF_LOCALIZED_DESCRIPTION),
.clock_source_flags = htons(DSC_CLOCK_SOURCE_AAF_FLAGS),
.clock_source_type = htons(DSC_CLOCK_SOURCE_AAF_TYPE),
.clock_source_identifier = htobe64(DSC_CLOCK_SOURCE_AAF_IDENTIFIER),
.clock_source_location_type = htons(DSC_CLOCK_SOURCE_AAF_LOCATION_TYPE),
.clock_source_location_index = htons(DSC_CLOCK_SOURCE_AAF_LOCATION_INDEX),
};
es_builder_add_descriptor(server, AVB_AEM_DESC_CLOCK_SOURCE, 1,
sizeof(clock_source_aaf), &clock_source_aaf);
// CRF Clock Descriptor
struct avb_aem_desc_clock_source clock_source_crf = {
.object_name = DSC_CLOCK_SOURCE_CRF_OBJECT_NAME,
.localized_description = htons(DSC_CLOCK_SOURCE_CRF_LOCALIZED_DESCRIPTION),
.clock_source_flags = htons(DSC_CLOCK_SOURCE_CRF_FLAGS),
.clock_source_type = htons(DSC_CLOCK_SOURCE_CRF_TYPE),
.clock_source_identifier = htobe64(DSC_CLOCK_SOURCE_CRF_IDENTIFIER),
.clock_source_location_type = htons(DSC_CLOCK_SOURCE_CRF_LOCATION_TYPE),
.clock_source_location_index = htons(DSC_CLOCK_SOURCE_CRF_LOCATION_INDEX),
};
es_builder_add_descriptor(server, AVB_AEM_DESC_CLOCK_SOURCE, 2,
sizeof(clock_source_crf), &clock_source_crf);
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.32 CLOCK_DOMAIN Descriptor */
/* Milan v1.2, Sec. 5.3.3.11 */
struct {
struct avb_aem_desc_clock_domain desc;
uint16_t clock_sources_idx[DSC_CLOCK_DOMAIN_CLOCK_SOURCES_COUNT];
} __attribute__ ((__packed__)) clock_domain = {
.desc = {
.object_name = DSC_CLOCK_DOMAIN_OBJECT_NAME,
.localized_description = htons(DSC_CLOCK_DOMAIN_LOCALIZED_DESCRIPTION),
.clock_source_index = htons(DSC_CLOCK_DOMAIN_CLOCK_SOURCE_INDEX),
.descriptor_counts_offset = htons(DSC_CLOCK_DOMAIN_DESCRIPTOR_COUNTS_OFFSET),
.clock_sources_count = htons(DSC_CLOCK_DOMAIN_CLOCK_SOURCES_COUNT),
},
.clock_sources_idx = {
htons(DSC_CLOCK_DOMAIN_SOURCES_0),
htons(DSC_CLOCK_DOMAIN_SOURCES_1),
htons(DSC_CLOCK_DOMAIN_SOURCES_2),
},
};
es_builder_add_descriptor(server, AVB_AEM_DESC_CLOCK_DOMAIN, 0,
sizeof(clock_domain), &clock_domain);
}
void init_descriptors(struct server *server)
{
if (!server) {
pw_log_error("No server");
spa_assert(0);
}
switch (server->avb_mode) {
case AVB_MODE_LEGACY:
init_descriptor_legacy_avb(server);
break;
case AVB_MODE_MILAN_V12:
init_descriptor_milan_v12(server);
break;
default:
pw_log_error("Unknown AVB mode");
break;
}
}

View file

@ -3,12 +3,6 @@
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki (alexandre.malki@kebag-logic.com) */ /* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki (alexandre.malki@kebag-logic.com) */
/* SPDX-License-Identifier: MIT */ /* SPDX-License-Identifier: MIT */
#include "adp.h"
#include "aecp-aem.h"
#include "aecp-aem-descriptors.h"
#include "es-builder.h"
#include "internal.h"
/** /**
* \todo This whole code needs to be re-factore, * \todo This whole code needs to be re-factore,
* configuring the entity using such a "HARDCODED" * configuring the entity using such a "HARDCODED"
@ -25,249 +19,4 @@
* Having the YANG would allow directly to know the * Having the YANG would allow directly to know the
* capabilites/limits of the protocol * capabilites/limits of the protocol
*/ */
static inline void init_descriptors(struct server *server) void init_descriptors(struct server *server);
{
es_builder_add_descriptor(server, AVB_AEM_DESC_STRINGS, 0,
sizeof(struct avb_aem_desc_strings),
&(struct avb_aem_desc_strings)
{
.string_0 = "PipeWire",
.string_1 = "Configuration 1",
.string_2 = "Wim Taymans",
});
es_builder_add_descriptor(server, AVB_AEM_DESC_LOCALE, 0,
sizeof(struct avb_aem_desc_locale),
&(struct avb_aem_desc_locale)
{
.locale_identifier = "en-EN",
.number_of_strings = htons(1),
.base_strings = htons(0)
});
es_builder_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0,
sizeof(struct avb_aem_desc_entity),
&(struct avb_aem_desc_entity)
{
.entity_id = htobe64(server->entity_id),
.entity_model_id = htobe64(0),
.entity_capabilities = htonl(
AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED |
AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED |
AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED |
AVB_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID |
AVB_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID),
.talker_stream_sources = htons(8),
.talker_capabilities = htons(
AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED |
AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE),
.listener_stream_sinks = htons(8),
.listener_capabilities = htons(
AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED |
AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK),
.controller_capabilities = htons(0),
.available_index = htonl(0),
.association_id = htobe64(0),
.entity_name = "PipeWire",
.vendor_name_string = htons(2),
.model_name_string = htons(0),
.firmware_version = "0.3.48",
.group_name = "",
.serial_number = "",
.configurations_count = htons(1),
.current_configuration = htons(0)
});
struct {
struct avb_aem_desc_configuration desc;
struct avb_aem_desc_descriptor_count descriptor_counts[8];
} __attribute__ ((__packed__)) config =
{
{
.object_name = "Configuration 1",
.localized_description = htons(1),
.descriptor_counts_count = htons(8),
.descriptor_counts_offset = htons(
4 + sizeof(struct avb_aem_desc_configuration)),
},
.descriptor_counts = {
{ htons(AVB_AEM_DESC_AUDIO_UNIT), htons(1) },
{ htons(AVB_AEM_DESC_STREAM_INPUT), htons(1) },
{ htons(AVB_AEM_DESC_STREAM_OUTPUT), htons(1) },
{ htons(AVB_AEM_DESC_AVB_INTERFACE), htons(1) },
{ htons(AVB_AEM_DESC_CLOCK_SOURCE), htons(1) },
{ htons(AVB_AEM_DESC_CONTROL), htons(2) },
{ htons(AVB_AEM_DESC_LOCALE), htons(1) },
{ htons(AVB_AEM_DESC_CLOCK_DOMAIN), htons(1) }
}
};
es_builder_add_descriptor(server, AVB_AEM_DESC_CONFIGURATION, 0,
sizeof(config), &config);
struct {
struct avb_aem_desc_audio_unit desc;
struct avb_aem_desc_sampling_rate sampling_rates[6];
} __attribute__ ((__packed__)) audio_unit =
{
{
.object_name = "PipeWire",
.localized_description = htons(0),
.clock_domain_index = htons(0),
.number_of_stream_input_ports = htons(1),
.base_stream_input_port = htons(0),
.number_of_stream_output_ports = htons(1),
.base_stream_output_port = htons(0),
.number_of_external_input_ports = htons(8),
.base_external_input_port = htons(0),
.number_of_external_output_ports = htons(8),
.base_external_output_port = htons(0),
.number_of_internal_input_ports = htons(0),
.base_internal_input_port = htons(0),
.number_of_internal_output_ports = htons(0),
.base_internal_output_port = htons(0),
.number_of_controls = htons(0),
.base_control = htons(0),
.number_of_signal_selectors = htons(0),
.base_signal_selector = htons(0),
.number_of_mixers = htons(0),
.base_mixer = htons(0),
.number_of_matrices = htons(0),
.base_matrix = htons(0),
.number_of_splitters = htons(0),
.base_splitter = htons(0),
.number_of_combiners = htons(0),
.base_combiner = htons(0),
.number_of_demultiplexers = htons(0),
.base_demultiplexer = htons(0),
.number_of_multiplexers = htons(0),
.base_multiplexer = htons(0),
.number_of_transcoders = htons(0),
.base_transcoder = htons(0),
.number_of_control_blocks = htons(0),
.base_control_block = htons(0),
.current_sampling_rate = htonl(48000),
.sampling_rates_offset = htons(
4 + sizeof(struct avb_aem_desc_audio_unit)),
.sampling_rates_count = htons(6),
},
.sampling_rates = {
{ .pull_frequency = htonl(44100) },
{ .pull_frequency = htonl(48000) },
{ .pull_frequency = htonl(88200) },
{ .pull_frequency = htonl(96000) },
{ .pull_frequency = htonl(176400) },
{ .pull_frequency = htonl(192000) },
}
};
es_builder_add_descriptor(server, AVB_AEM_DESC_AUDIO_UNIT, 0,
sizeof(audio_unit), &audio_unit);
struct {
struct avb_aem_desc_stream desc;
uint64_t stream_formats[6];
} __attribute__ ((__packed__)) stream_input_0 =
{
{
.object_name = "Stream Input 1",
.localized_description = htons(0xffff),
.clock_domain_index = htons(0),
.stream_flags = htons(
AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE |
AVB_AEM_DESC_STREAM_FLAG_CLASS_A),
.current_format = htobe64(0x00a0020840000800ULL),
.formats_offset = htons(
4 + sizeof(struct avb_aem_desc_stream)),
.number_of_formats = htons(6),
.backup_talker_entity_id_0 = htobe64(0),
.backup_talker_unique_id_0 = htons(0),
.backup_talker_entity_id_1 = htobe64(0),
.backup_talker_unique_id_1 = htons(0),
.backup_talker_entity_id_2 = htobe64(0),
.backup_talker_unique_id_2 = htons(0),
.backedup_talker_entity_id = htobe64(0),
.backedup_talker_unique = htons(0),
.avb_interface_index = htons(0),
.buffer_length = htons(8)
},
.stream_formats = {
htobe64(0x00a0010860000800ULL),
htobe64(0x00a0020860000800ULL),
htobe64(0x00a0030860000800ULL),
htobe64(0x00a0040860000800ULL),
htobe64(0x00a0050860000800ULL),
htobe64(0x00a0060860000800ULL),
},
};
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_INPUT, 0,
sizeof(stream_input_0), &stream_input_0);
struct {
struct avb_aem_desc_stream desc;
uint64_t stream_formats[6];
} __attribute__ ((__packed__)) stream_output_0 =
{
{
.object_name = "Stream Output 1",
.localized_description = htons(0xffff),
.clock_domain_index = htons(0),
.stream_flags = htons(
AVB_AEM_DESC_STREAM_FLAG_CLASS_A),
.current_format = htobe64(0x00a0020840000800ULL),
.formats_offset = htons(
4 + sizeof(struct avb_aem_desc_stream)),
.number_of_formats = htons(6),
.backup_talker_entity_id_0 = htobe64(0),
.backup_talker_unique_id_0 = htons(0),
.backup_talker_entity_id_1 = htobe64(0),
.backup_talker_unique_id_1 = htons(0),
.backup_talker_entity_id_2 = htobe64(0),
.backup_talker_unique_id_2 = htons(0),
.backedup_talker_entity_id = htobe64(0),
.backedup_talker_unique = htons(0),
.avb_interface_index = htons(0),
.buffer_length = htons(8)
},
.stream_formats = {
htobe64(0x00a0010860000800ULL),
htobe64(0x00a0020860000800ULL),
htobe64(0x00a0030860000800ULL),
htobe64(0x00a0040860000800ULL),
htobe64(0x00a0050860000800ULL),
htobe64(0x00a0060860000800ULL),
},
};
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_OUTPUT, 0,
sizeof(stream_output_0), &stream_output_0);
struct avb_aem_desc_avb_interface avb_interface = {
.localized_description = htons(0xffff),
.interface_flags = htons(
AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED),
.clock_identity = htobe64(0),
.priority1 = 0,
.clock_class = 0,
.offset_scaled_log_variance = htons(0),
.clock_accuracy = 0,
.priority2 = 0,
.domain_number = 0,
.log_sync_interval = 0,
.log_announce_interval = 0,
.log_pdelay_interval = 0,
.port_number = 0,
};
strncpy(avb_interface.object_name, server->ifname, 63);
memcpy(avb_interface.mac_address, server->mac_addr, 6);
es_builder_add_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0,
sizeof(avb_interface), &avb_interface);
struct avb_aem_desc_clock_source clock_source = {
.object_name = "Stream Clock",
.localized_description = htons(0xffff),
.clock_source_flags = htons(0),
.clock_source_type = htons(
AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM),
.clock_source_identifier = htobe64(0),
.clock_source_location_type = htons(AVB_AEM_DESC_STREAM_INPUT),
.clock_source_location_index = htons(0),
};
es_builder_add_descriptor(server, AVB_AEM_DESC_CLOCK_SOURCE, 0,
sizeof(clock_source), &clock_source);
}

View file

@ -0,0 +1,477 @@
/* PipeWire */
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
/* SPDX-License-Identifier: MIT */
#ifndef __DESCRIPTOR_ENTITY_MODEL_MILAN_H__
#define __DESCRIPTOR_ENTITY_MODEL_MILAN_H__
#define TALKER_ENABLE 1
// TODO: Make defines as long as specified length
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.12 - STRINGS Descriptor
* Up to 7 localized strings
*/
#define DSC_STRINGS_0_DEVICE_NAME "PipeWire"
#define DSC_STRINGS_1_CONFIGURATION_NAME "NON - redundant - 48kHz"
#define DSC_STRINGS_2_MANUFACTURER_NAME "Kebag Logic"
#define DSC_STRINGS_3_GROUP_NAME "Kebag Logic"
#define DSC_STRINGS_4_MAINTAINER_0 "Alexandre Malki"
#define DSC_STRINGS_4_MAINTAINER_1 "Simon Gapp"
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.11 - LOCALE Descriptor */
#define DSC_LOCALE_LANGUAGE_CODE "en-EN"
#define DSC_LOCALE_NO_OF_STRINGS 1
#define DSC_LOCALE_BASE_STRINGS 0
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.1 - ENTITY Descriptor */
/* Milan v1.2, Sec. 5.3.3.1 */
#define DSC_ENTITY_MODEL_ENTITY_ID 0xDEAD00BEEF00FEED
#define DSC_ENTITY_MODEL_ID 0
#define DSC_ENTITY_MODEL_ENTITY_CAPABILITIES (AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED | \
AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED | \
AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED | \
AVB_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID | \
AVB_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID)
/* IEEE 1722.1-2021, Table 7-2 - ENTITY Descriptor
* This is the maximum number of STREAM_OUTPUT
* descriptors the ATDECC Entity has for
* Output Streams in any of its Configurations */
#if TALKER_ENABLE
#define DSC_ENTITY_MODEL_TALKER_STREAM_SOURCES 8
#define DSC_ENTITY_MODEL_TALKER_CAPABILITIES (AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED | \
AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE)
#else
#define DSC_ENTITY_MODEL_TALKER_STREAM_SOURCES 0
#define DSC_ENTITY_MODEL_TALKER_CAPABILITIES 0
#endif
#define DSC_ENTITY_MODEL_LISTENER_STREAM_SINKS 8
#define DSC_ENTITY_MODEL_LISTENER_CAPABILITIES (AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED | \
AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK)
#define DSC_ENTITY_MODEL_CONTROLLER_CAPABILITIES 0
/* IEEE 1722.1-2021, Table 7-2 ENTITY Descriptor
* The available index of the ATDECC Entity.
* This is the same as the available_index field
* in ATDECC Discovery Protocol.*/
#define DSC_ENTITY_MODEL_AVAILABLE_INDEX 0
/* IEEE 1722.1-2021, Table 7-2 ENTITY Descriptor
* The association ID for the ATDECC Entity.
* This is the same as association_id field
* in ATDECC Discovery Protocol*/
#define DSC_ENTITY_MODEL_ASSOCIATION_ID 0
#define DSC_ENTITY_MODEL_ENTITY_NAME DSC_STRINGS_0_DEVICE_NAME
/* IEEE 1722.1-2021, Table 7-2 - ENTITY Descriptor
* The localized string reference pointing to the
* localized vendor name. See 7.3.7. */
#define DSC_ENTITY_MODEL_VENDOR_NAME_STRING 2
/* IEEE 1722.1-2021, Table 7-2 - ENTITY Descriptor
* The localized string reference pointing to the
* localized model name. See 7.3.7. */
#define DSC_ENTITY_MODEL_MODEL_NAME_STRING 0
#define DSC_ENTITY_MODEL_FIRMWARE_VERSION "0.3.48"
#define DSC_ENTITY_MODEL_GROUP_NAME DSC_STRINGS_3_GROUP_NAME
#define DSC_ENTITY_MODEL_SERIAL_NUMBER "0xBEBEDEAD"
#define DSC_ENTITY_MODEL_CONFIGURATIONS_COUNT 2
#define DSC_ENTITY_MODEL_CURRENT_CONFIGURATION 0
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.2 - CONFIGURATION Descriptor*/
/* Milan v1.2, Sec. 5.3.3.2 */
#define DSC_CONFIGURATION_DESCRIPTOR_COUNTS_COUNT 8
#define DSC_CONFIGURATION_OBJECT_NAME DSC_STRINGS_1_CONFIGURATION_NAME
/* IEEE 1722.1-2021, Table 7-3 CONFIGURATION Descriptor
* The localized string reference pointing to the
* localized Configuration name. */
#define DSC_CONFIGURATION_LOCALIZED_DESCRIPTION 1
/* IEEE 1722.1-2021, Table 7-3 CONFIGURATION Descriptor
* The offset to the descriptor_counts field from the
* start of the descriptor. This field is set to 74 for
* this version of AEM. */
#define DSC_CONFIGURATION_DESCRIPTOR_COUNTS_OFFSET 74
#define DSC_CONFIGURATION_NO_OF_AUDIO_UNITS 1
#define DSC_CONFIGURATION_NO_OF_STREAM_INPUTS 2
#define DSC_CONFIGURATION_NO_OF_STREAM_OUTPUTS 1
#define DSC_CONFIGURATION_NO_OF_AVB_INTERFACES 1
#define DSC_CONFIGURATION_NO_OF_CLOCK_DOMAINS 1
#define DSC_CONFIGURATION_NO_OF_CLOCK_SOURCES 3
#define DSC_CONFIGURATION_NO_OF_CONTROLS 1
#define DSC_CONFIGURATION_NO_OF_LOCALES 1
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.22 CONTROL Descriptor*/
/* Milan v1.2, Sec. 5.3.3.10 */
#define DSC_CONTROL_OBJECT_NAME "Identify"
#define DSC_CONTROL_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
#define DSC_CONTROL_BLOCK_LATENCY 500
#define DSC_CONTROL_CONTROL_LATENCY 500
#define DSC_CONTROL_CONTROL_DOMAIN 0
#define DSC_CONTROL_CONTROL_VALUE_TYPE AECP_AEM_CTRL_LINEAR_UINT8
#define DSC_CONTROL_CONTROL_TYPE AEM_CTRL_TYPE_IDENTIFY
/* IEEE 1722.1-2021, Table 7-39 - CONTROL Descriptor
* The time period in microseconds from when a control
* is set with the SET_CONTROL command till it automatically
* resets to its default values.
* When this is set to zero (0) automatic resets do not happen. */
// TODO: Milan v1.2: The PAAD remains in identification mode until the value of the “IDENTIFY” CONTROL descriptor is set back to 0.
#define DSC_CONTROL_RESET_TIME 3
#define DSC_CONTROL_NUMBER_OF_VALUES 1
#define DSC_CONTROL_SIGNAL_TYPE AVB_AEM_DESC_INVALID
#define DSC_CONTROL_SIGNAL_INDEX 0
#define DSC_CONTROL_SIGNAL_OUTPUT 0
#define DSC_CONTROL_IDENTIFY_MIN 0
#define DSC_CONTROL_IDENTIFY_MAX 255
#define DSC_CONTROL_IDENTIFY_STEP 255
#define DSC_CONTROL_IDENTIFY_DEFAULT_VALUE 0
#define DSC_CONTROL_IDENTIFY_CURRENT_VALUE 0
#define DSC_CONTROL_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.19 AUDIO_MAP Descriptor */
/* Milan v1.2, Sec. 5.3.3.9 */
// TODO: Prepared for for loop over total number of audio maps
#define DSC_AUDIO_MAPS_TOTAL_NO_OF_MAPS 2
#define DSC_AUDIO_MAPS_NO_OF_MAPPINGS 8
#define DSC_AUDIO_MAPS_MAPPING_STREAM_INDEX 0
#define DSC_AUDIO_MAPS_MAPPING_CLUSTER_CHANNEL 0
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.16 AUDIO_CLUSTER Descriptor */
/* Milan v1.2, Sec. 5.3.3.8 */
#define DSC_AUDIO_CLUSTER_NO_OF_CLUSTERS 16
#define DSC_AUDIO_CLUSTER_OBJECT_NAME_LEN_IN_OCTET 64
#define DSC_AUDIO_CLUSTER_OBJECT_NAME_INPUT "Input"
#define DSC_AUDIO_CLUSTER_OBJECT_NAME_OUTPUT "Output"
#define DSC_AUDIO_CLUSTER_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
/* The signal_type and signal_index fields indicate the
* object providing the signal destined for the channels
* of the streams mapped to the port. For a signal which
* is sourced internally from the Unit, the signal_type
* is set to AUDIO_UNIT and signal_index is set to the
* index of the Unit. For a Cluster attached to a
* STREAM_PORT_INPUT the signal_type and signal_index
* fields is set to INVALID and zero (0) respectively. */
#define DSC_AUDIO_CLUSTER_SIGNAL_TYPE 0
#define DSC_AUDIO_CLUSTER_SIGNAL_INDEX 0
/* The index of the output of the signal source of the
* cluster. For a signal_type of SIGNAL_SPLITTER or
* SIGNAL_DEMULTIPLEXER this is which output of the
* object it is being sourced from, for a signal_type
* of MATRIX this is the column the signal is from
* and for any other signal_type this is zero (0). */
#define DSC_AUDIO_CLUSTER_SIGNAL_OUTPUT 0
#define DSC_AUDIO_CLUSTER_PATH_LATENCY_IN_NS 500
#define DSC_AUDIO_CLUSTER_BLOCK_LATENCY_IN_NS 500
#define DSC_AUDIO_CLUSTER_CHANNEL_COUNT 1
#define DSC_AUDIO_CLUSTER_FORMAT AVB_AEM_AUDIO_CLUSTER_TYPE_MBLA
#define DSC_AUDIO_CLUSTER_AES3_DATA_TYPE_REF 0
#define DSC_AUDIO_CLUSTER_AES3_DATA_TYPE 0
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.13 STREAM_PORT_INPUT Descriptor */
/* Milan v1.2, Sec. 5.3.3.7 */
#define DSC_STREAM_PORT_INPUT_CLOCK_DOMAIN_INDEX 0x0000
#define DSC_STREAM_PORT_INPUT_PORT_FLAGS AVB_AEM_PORT_FLAG_CLOCK_SYNC_SOURCE
/* The number of clusters within the Port. This corresponds to the number of
* AUDIO_CLUSTER, VIDEO_CLUSTER or SENSOR_CLUSTER descriptors which represent
* these clusters. */
// TODO: Validate value
#define DSC_STREAM_PORT_INPUT_NUMBER_OF_CONTROLS 0
#define DSC_STREAM_PORT_INPUT_BASE_CONTROL 0
// TODO: Validate value
#define DSC_STREAM_PORT_INPUT_NUMBER_OF_CLUSTERS 8
#define DSC_STREAM_PORT_INPUT_BASE_CLUSTER 0
#define DSC_STREAM_PORT_INPUT_NUMBER_OF_MAPS 1
#define DSC_STREAM_PORT_INPUT_BASE_MAP 0
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.13 STREAM_PORT_OUTPUT Descriptor */
/* Milan v1.2, Sec. 5.3.3.7 */
#define DSC_STREAM_PORT_OUTPUT_CLOCK_DOMAIN_INDEX 0
#define DSC_STREAM_PORT_OUTPUT_PORT_FLAGS AVB_AEM_PORT_FLAG_NO_FLAG
#define DSC_STREAM_PORT_OUTPUT_NUMBER_OF_CONTROLS 0
#define DSC_STREAM_PORT_OUTPUT_BASE_CONTROL 0
// TODO: Verify
#define DSC_STREAM_PORT_OUTPUT_NUMBER_OF_CLUSTERS 8
#define DSC_STREAM_PORT_OUTPUT_BASE_CLUSTER 8
#define DSC_STREAM_PORT_OUTPUT_NUMBER_OF_MAPS 1
#define DSC_STREAM_PORT_OUTPUT_BASE_MAP 1
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.3 AUDIO_UNIT Descriptor */
/* Milan v1.2, Sec. 5.3.3.3 */
/* IEEE 1722.1-2021, Sec. 7.3.1
* A sampling rate consists of a 3 bit pull field
* representing a multiplier and a 29 bit
* base_frequency in hertz, as detailed in Figure 7-2.
* The pull field specifies the multiplier modifier
* of the base_frequency field which is required to
* calculate the appropriate nominal sampling rate.
* The pull field may have one of the values defined
* in Table 7-70:
* The base_frequency field defines the nominal base
* sampling rate in Hz, from 1 Hz to 536 870 911 Hz.
* The value of this field is augmented by the
* pull field value.*/
#define BUILD_SAMPLING_RATE(pull, base_freq_hz) \
(((uint32_t)(pull) << 29) | ((uint32_t)(base_freq_hz) & 0x1FFFFFFF))
#define DSC_AUDIO_UNIT_OBJECT_NAME ""
#define DSC_AUDIO_UNIT_LOCALIZED_DESCRIPTION 0xFFFF
#define DSC_AUDIO_UNIT_CLOCK_DOMAIN_INDEX 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_STREAM_INPUT_PORTS 0x0001
#define DSC_AUDIO_UNIT_BASE_STREAM_INPUT_PORT 0x0000
#if TALKER_ENABLE
#define DSC_AUDIO_UNIT_NUMBER_OF_STREAM_OUTPUT_PORTS 0x0001
#else
#define DSC_AUDIO_UNIT_NUMBER_OF_STREAM_OUTPUT_PORTS 0x0000
#endif
#define DSC_AUDIO_UNIT_BASE_STREAM_OUTPUT_PORT 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_EXTERNAL_INPUT_PORTS 0x0008
#define DSC_AUDIO_UNIT_BASE_EXTERNAL_INPUT_PORT 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_EXTERNAL_OUTPUT_PORTS 0x0008
#define DSC_AUDIO_UNIT_BASE_EXTERNAL_OUTPUT_PORT 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_INTERNAL_INPUT_PORTS 0x0000
#define DSC_AUDIO_UNIT_BASE_INTERNAL_INPUT_PORT 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_INTERNAL_OUTPUT_PORTS 0x0000
#define DSC_AUDIO_UNIT_BASE_INTERNAL_OUTPUT_PORT 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_CONTROLS 0x0000
#define DSC_AUDIO_UNIT_BASE_CONTROL 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_SIGNAL_SELECTORS 0x0000
#define DSC_AUDIO_UNIT_BASE_SIGNAL_SELECTOR 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_MIXERS 0x0000
#define DSC_AUDIO_UNIT_BASE_MIXER 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_MATRICES 0x0000
#define DSC_AUDIO_UNIT_BASE_MATRIX 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_SPLITTERS 0x0000
#define DSC_AUDIO_UNIT_BASE_SPLITTER 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_COMBINERS 0x0000
#define DSC_AUDIO_UNIT_BASE_COMBINER 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_DEMULTIPLEXERS 0x0000
#define DSC_AUDIO_UNIT_BASE_DEMULTIPLEXER 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_MULTIPLEXERS 0x0000
#define DSC_AUDIO_UNIT_BASE_MULTIPLEXER 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_TRANSCODERS 0x0000
#define DSC_AUDIO_UNIT_BASE_TRANSCODER 0x0000
#define DSC_AUDIO_UNIT_NUMBER_OF_CONTROL_BLOCKS 0x0000
#define DSC_AUDIO_UNIT_BASE_CONTROL_BLOCK 0x0000
#define DSC_AUDIO_UNIT_SAMPLING_RATE_PULL 0
#define DSC_AUDIO_UNIT_SAMPLING_RATE_BASE_FREQ_IN_HZ 48000
#define DSC_AUDIO_UNIT_CURRENT_SAMPLING_RATE_IN_HZ \
BUILD_SAMPLING_RATE(DSC_AUDIO_UNIT_SAMPLING_RATE_PULL, DSC_AUDIO_UNIT_SAMPLING_RATE_BASE_FREQ_IN_HZ)
/* The offset to the sample_rates field from the start of the descriptor.
* This field is 144 for this version of AEM.*/
#define DSC_AUDIO_UNIT_SAMPLING_RATES_OFFSET 144
#define DSC_AUDIO_UNIT_SUPPORTED_SAMPLING_RATE_COUNT 0x0001
#define DSC_AUDIO_UNIT_SUPPORTED_SAMPLING_RATE_IN_HZ_0 \
BUILD_SAMPLING_RATE(DSC_AUDIO_UNIT_SAMPLING_RATE_PULL, DSC_AUDIO_UNIT_SAMPLING_RATE_BASE_FREQ_IN_HZ)
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.6 STREAM_INPUT Descriptor */
/* Milan v1.2, Sec. 5.3.3.4 */
// TODO: 1722.1 lists redundant parameters that are not mentioned here.
#define DSC_STREAM_INPUT_OBJECT_NAME "Stream 1"
#define DSC_STREAM_INPUT_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
#define DSC_STREAM_INPUT_CLOCK_DOMAIN_INDEX 0
#define DSC_STREAM_INPUT_STREAM_FLAGS (AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE | AVB_AEM_DESC_STREAM_FLAG_CLASS_A)
// To match my talker
// TODO: Define based on AUDIO_UNIT etc.
#define DSC_STREAM_INPUT_CURRENT_FORMAT 0x0205022001006000ULL
// TODO: Is 132 here, should be 138 according to spec
#define DSC_STREAM_INPUT_FORMATS_OFFSET (4 + sizeof(struct avb_aem_desc_stream))
#define DSC_STREAM_INPUT_NUMBER_OF_FORMATS 5
#define DSC_STREAM_INPUT_BACKUP_TALKER_ENTITY_ID_0 0
#define DSC_STREAM_INPUT_BACKUP_TALKER_UNIQUE_ID_0 0
#define DSC_STREAM_INPUT_BACKUP_TALKER_ENTITY_ID_1 0
#define DSC_STREAM_INPUT_BACKUP_TALKER_UNIQUE_ID_1 0
#define DSC_STREAM_INPUT_BACKUP_TALKER_ENTITY_ID_2 0
#define DSC_STREAM_INPUT_BACKUP_TALKER_UNIQUE_ID_2 0
#define DSC_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_ID 0
#define DSC_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_ID 0
#define DSC_STREAM_INPUT_AVB_INTERFACE_INDEX 0
#define DSC_STREAM_INPUT_BUFFER_LENGTH_IN_NS 2126000
#define DSC_STREAM_INPUT_FORMATS_0 DSC_STREAM_INPUT_CURRENT_FORMAT
#define DSC_STREAM_INPUT_FORMATS_1 0x0205022000406000ULL
#define DSC_STREAM_INPUT_FORMATS_2 0x0205022000806000ULL
#define DSC_STREAM_INPUT_FORMATS_3 0x0205022001806000ULL
#define DSC_STREAM_INPUT_FORMATS_4 0x0205022002006000ULL
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.6 STREAM_INPUT Descriptor */
/* Milan v1.2, Sec. 5.3.3.4 */
#define DSC_STREAM_INPUT_CRF_OBJECT_NAME "CRF"
#define DSC_STREAM_INPUT_CRF_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
#define DSC_STREAM_INPUT_CRF_CLOCK_DOMAIN_INDEX 0
#define DSC_STREAM_INPUT_CRF_STREAM_FLAGS (AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE | AVB_AEM_DESC_STREAM_FLAG_CLASS_A)
#define DSC_STREAM_INPUT_CRF_CURRENT_FORMAT 0x041060010000BB80ULL
#define DSC_STREAM_INPUT_CRF_FORMATS_OFFSET (4 + sizeof(struct avb_aem_desc_stream))
#define DSC_STREAM_INPUT_CRF_NUMBER_OF_FORMATS 1
#define DSC_STREAM_INPUT_CRF_BACKUP_TALKER_ENTITY_ID_0 0
#define DSC_STREAM_INPUT_CRF_BACKUP_TALKER_UNIQUE_ID_0 0
#define DSC_STREAM_INPUT_CRF_BACKUP_TALKER_ENTITY_ID_1 0
#define DSC_STREAM_INPUT_CRF_BACKUP_TALKER_UNIQUE_ID_1 0
#define DSC_STREAM_INPUT_CRF_BACKUP_TALKER_ENTITY_ID_2 0
#define DSC_STREAM_INPUT_CRF_BACKUP_TALKER_UNIQUE_ID_2 0
#define DSC_STREAM_INPUT_CRF_BACKEDUP_TALKER_ENTITY_ID 0
#define DSC_STREAM_INPUT_CRF_BACKEDUP_TALKER_UNIQUE_ID 0
#define DSC_STREAM_INPUT_CRF_AVB_INTERFACE_INDEX 0
#define DSC_STREAM_INPUT_CRF_BUFFER_LENGTH_IN_NS 2126000
#define DSC_STREAM_INPUT_CRF_FORMATS_0 DSC_STREAM_INPUT_CRF_CURRENT_FORMAT
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.6 STREAM_OUTPUT Descriptor */
/* Milan v1.2, Sec. 5.3.3.4 */
#define DSC_STREAM_OUTPUT_OBJECT_NAME "Stream output 1"
#define DSC_STREAM_OUTPUT_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
#define DSC_STREAM_OUTPUT_CLOCK_DOMAIN_INDEX 0
#define DSC_STREAM_OUTPUT_STREAM_FLAGS (AVB_AEM_DESC_STREAM_FLAG_CLASS_A)
#define DSC_STREAM_OUTPUT_CURRENT_FORMAT 0x0205022002006000ULL
#define DSC_STREAM_OUTPUT_FORMATS_OFFSET (4 + sizeof(struct avb_aem_desc_stream))
#define DSC_STREAM_OUTPUT_NUMBER_OF_FORMATS 5
#define DSC_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_ID_0 0
#define DSC_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_ID_0 0
#define DSC_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_ID_1 0
#define DSC_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_ID_1 0
#define DSC_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_ID_2 0
#define DSC_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_ID_2 0
#define DSC_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_ID 0
#define DSC_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_ID 0
#define DSC_STREAM_OUTPUT_AVB_INTERFACE_INDEX 0
#define DSC_STREAM_OUTPUT_BUFFER_LENGTH_IN_NS 8
// TODO: No hardcoded values!
#define DSC_STREAM_OUTPUT_FORMATS_0 0x0205022000406000ULL
#define DSC_STREAM_OUTPUT_FORMATS_1 0x0205022000806000ULL
#define DSC_STREAM_OUTPUT_FORMATS_2 0x0205022001006000ULL
#define DSC_STREAM_OUTPUT_FORMATS_3 0x0205022001806000ULL
#define DSC_STREAM_OUTPUT_FORMATS_4 DSC_STREAM_OUTPUT_CURRENT_FORMAT
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.8 AVB Interface Descriptor */
/* Milan v1.2, Sec. 5.3.3.5 */
#define DSC_AVB_INTERFACE_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
#define DSC_AVB_INTERFACE_INTERFACE_FLAGS (AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED | \
AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_SUPPORTED | \
AVB_AEM_DESC_AVB_INTERFACE_FLAG_SRP_SUPPORTED)
// TODO: This is a dynamic parameter
#define DSC_AVB_INTERFACE_CLOCK_IDENTITY 0x3cc0c6FFFE0002CB
#define DSC_AVB_INTERFACE_PRIORITY1 0xF8
#define DSC_AVB_INTERFACE_CLOCK_CLASS 0xF8
#define DSC_AVB_INTERFACE_OFFSET_SCALED_LOG_VARIANCE 0x436A
#define DSC_AVB_INTERFACE_CLOCK_ACCURACY 0x21
#define DSC_AVB_INTERFACE_PRIORITY2 0xF8
#define DSC_AVB_INTERFACE_DOMAIN_NUMBER 0
#define DSC_AVB_INTERFACE_LOG_SYNC_INTERVAL 0
#define DSC_AVB_INTERFACE_LOG_ANNOUNCE_INTERVAL 0
#define DSC_AVB_INTERFACE_PDELAY_INTERVAL 0
#define DSC_AVB_INTERFACE_PORT_NUMBER 0
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.9 CLOCK_SOURCE Descriptor */
/* Milan v1.2, Sec. 5.3.3.6 */
// Internal Clock Source
#define DSC_CLOCK_SOURCE_INTERNAL_OBJECT_NAME "Internal"
#define DSC_CLOCK_SOURCE_INTERNAL_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
#define DSC_CLOCK_SOURCE_INTERNAL_FLAGS 0x0002
#define DSC_CLOCK_SOURCE_INTERNAL_TYPE AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INTERNAL
#define DSC_CLOCK_SOURCE_INTERNAL_IDENTIFIER 0
#define DSC_CLOCK_SOURCE_INTERNAL_LOCATION_TYPE AVB_AEM_DESC_CLOCK_SOURCE
#define DSC_CLOCK_SOURCE_INTERNAL_LOCATION_INDEX 0
// AAF Stream Clock Source
#define DSC_CLOCK_SOURCE_AAF_OBJECT_NAME "Stream Clock"
#define DSC_CLOCK_SOURCE_AAF_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
#define DSC_CLOCK_SOURCE_AAF_FLAGS 0x0002
#define DSC_CLOCK_SOURCE_AAF_TYPE AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM
#define DSC_CLOCK_SOURCE_AAF_IDENTIFIER 0
#define DSC_CLOCK_SOURCE_AAF_LOCATION_TYPE AVB_AEM_DESC_STREAM_INPUT
#define DSC_CLOCK_SOURCE_AAF_LOCATION_INDEX 0
// CRF Clock Source
#define DSC_CLOCK_SOURCE_CRF_OBJECT_NAME "CRF Clock"
#define DSC_CLOCK_SOURCE_CRF_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
#define DSC_CLOCK_SOURCE_CRF_FLAGS 0x0002
#define DSC_CLOCK_SOURCE_CRF_TYPE AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM
#define DSC_CLOCK_SOURCE_CRF_IDENTIFIER 0
#define DSC_CLOCK_SOURCE_CRF_LOCATION_TYPE AVB_AEM_DESC_STREAM_INPUT
#define DSC_CLOCK_SOURCE_CRF_LOCATION_INDEX 1
/**************************************************************************************/
/* IEEE 1722.1-2021, Sec. 7.2.32 CLOCK_DOMAIN Descriptor */
/* Milan v1.2, Sec. 5.3.3.11 */
#define DSC_CLOCK_DOMAIN_OBJECT_NAME "Clock Reference Format"
#define DSC_CLOCK_DOMAIN_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
#define DSC_CLOCK_DOMAIN_CLOCK_SOURCE_INDEX 0
#define DSC_CLOCK_DOMAIN_DESCRIPTOR_COUNTS_OFFSET (4 + sizeof(struct avb_aem_desc_clock_domain))
#define DSC_CLOCK_DOMAIN_CLOCK_SOURCES_COUNT 3
#define DSC_CLOCK_DOMAIN_SOURCES_0 0 // Internal
#define DSC_CLOCK_DOMAIN_SOURCES_1 1 // AAF
#define DSC_CLOCK_DOMAIN_SOURCES_2 2 // CRF
#endif // __DESCRIPTOR_ENTITY_MODEL_MILAN_H__

View file

@ -4,7 +4,8 @@
#include "es-builder.h" #include "es-builder.h"
#include "aecp-aem-descriptors.h" #include "aecp-aem-state.h"
#include "utils.h"
/** /**
* \brief The goal of this modules is to create a an entity and * \brief The goal of this modules is to create a an entity and
@ -31,9 +32,124 @@ struct es_builder_st {
es_builder_cb_t build_descriptor_cb; es_builder_cb_t build_descriptor_cb;
}; };
/** All callback that needs a status information */ /*
static const struct es_builder_st es_builder[AVB_AEM_DESC_LAST_RESERVED_17221] = * \brief The Entity keeps track of multiple things, the locks the current
* configuration use for instance. That tragets the Milan V1.2 mode only
*/
static void *es_builder_desc_entity_milan_v12(struct server *server,
uint16_t type, uint16_t index, size_t size, void *ptr)
{ {
struct aecp_aem_entity_milan_state entity_state = {0};
void *ptr_alloc;
struct aecp_aem_entity_state *state =
(struct aecp_aem_entity_state *) &entity_state;
memcpy(&state->desc, ptr, size);
ptr_alloc = server_add_descriptor(server, type, index, sizeof(entity_state),
&entity_state);
if (!ptr_alloc) {
pw_log_error("Error durring allocation\n");
spa_assert(0);
}
return ptr_alloc;
}
/**
* \brief A generic function to avoid code duplicate for the streams */
static void *es_buidler_desc_stream_general_prepare(struct server *server,
uint16_t type, uint16_t index, size_t size, void *ptr)
{
void *ptr_alloc;
struct stream *stream;
enum spa_direction direction;
switch (type) {
case AVB_AEM_DESC_STREAM_INPUT:
struct aecp_aem_stream_input_state *pstream_input;
struct aecp_aem_stream_input_state stream_input = { 0 };
memcpy(&stream_input.desc, ptr, size);
ptr_alloc = server_add_descriptor(server, type, index,
sizeof(stream_input), &stream_input);
if (!ptr_alloc) {
pw_log_error("Allocation failed\n");
return NULL;
}
pstream_input = ptr_alloc;
stream = &pstream_input->stream;
direction = SPA_DIRECTION_INPUT;
break;
case AVB_AEM_DESC_STREAM_OUTPUT:
struct aecp_aem_stream_output_state *pstream_output;
struct aecp_aem_stream_output_state stream_output = { 0 };
memcpy(&stream_output.desc, ptr, size);
ptr_alloc = server_add_descriptor(server, type, index,
sizeof(stream_output), &stream_output);
if (!ptr_alloc) {
pw_log_error("Allocation failed\n");
return NULL;
}
pstream_output = ptr_alloc;
stream = &pstream_output->stream;
direction = SPA_DIRECTION_OUTPUT;
break;
default:
pw_log_error("Only STREAM_INPUT and STREAM_OUTPUT\n");
return NULL;
}
if (!server_create_stream(server, stream, direction, index)) {
pw_log_error("Could not create/initialize a stream");
return NULL;
}
return ptr_alloc;
}
// Assign a ID to an specific builder
#define HELPER_ES_BUIDLER(type, callback) \
[type] = { .build_descriptor_cb = callback }
/** All callback that needs a status information for the AVB/Milan V1.2 */
static const struct es_builder_st es_builder_milan_v12[] =
{
HELPER_ES_BUIDLER(AVB_AEM_DESC_ENTITY, es_builder_desc_entity_milan_v12),
HELPER_ES_BUIDLER(AVB_AEM_DESC_STREAM_OUTPUT, es_buidler_desc_stream_general_prepare),
HELPER_ES_BUIDLER(AVB_AEM_DESC_STREAM_INPUT, es_buidler_desc_stream_general_prepare),
};
/** All callback that needs a status information for Legacy AVB*/
static const struct es_builder_st es_builder_legacy_avb[] =
{
HELPER_ES_BUIDLER(AVB_AEM_DESC_STREAM_OUTPUT, es_buidler_desc_stream_general_prepare),
HELPER_ES_BUIDLER(AVB_AEM_DESC_STREAM_INPUT, es_buidler_desc_stream_general_prepare),
};
/**
* \brief keep the list of the supported avb flavors here
*/
static const struct {
const struct es_builder_st *es_builder;
/** Number of elements in the es_builder */
size_t count;
} es_builders[] = {
[AVB_MODE_LEGACY] = {
.es_builder = es_builder_legacy_avb,
.count = ARRAY_SIZE(es_builder_legacy_avb),
},
[AVB_MODE_MILAN_V12] = {
.es_builder = es_builder_milan_v12,
.count = ARRAY_SIZE(es_builder_milan_v12),
},
}; };
/** /**
@ -44,21 +160,34 @@ static const struct es_builder_st es_builder[AVB_AEM_DESC_LAST_RESERVED_17221] =
void es_builder_add_descriptor(struct server *server, uint16_t type, void es_builder_add_descriptor(struct server *server, uint16_t type,
uint16_t index, size_t size, void *ptr_aem) uint16_t index, size_t size, void *ptr_aem)
{ {
const struct es_builder_st *es_builder;
void *desc_ptr; void *desc_ptr;
struct descriptor *d; struct descriptor *d;
enum avb_mode avb_mode;
bool std_processing = false;
if (!server) { if (!server) {
pw_log_error("Invalid server, it is empty %p\n", server); pw_log_error("Invalid server, it is empty %p\n", server);
spa_assert(0); spa_assert(0);
} }
if (type >= AVB_AEM_DESC_LAST_RESERVED_17221) { avb_mode = server->avb_mode;
pw_log_error("Invalid Type %u\n", type); if (avb_mode >= AVB_MODE_MAX) {
pw_log_error("AVB mode is not valid received %d\n", avb_mode);
spa_assert(0); spa_assert(0);
} }
/* Look if the descriptor has a callback to attach more status data */
es_builder = es_builders[avb_mode].es_builder;
if (type > es_builders[avb_mode].count) {
std_processing = true;
} else {
if (!es_builder[type].build_descriptor_cb) { if (!es_builder[type].build_descriptor_cb) {
std_processing = true;
}
}
if (std_processing) {
if (!server_add_descriptor(server, type, index, size, ptr_aem)) { if (!server_add_descriptor(server, type, index, size, ptr_aem)) {
pw_log_error("Could not allocate descriptor %u at " pw_log_error("Could not allocate descriptor %u at "
"index %u the avb aem type\n", type, index); "index %u the avb aem type\n", type, index);
@ -75,6 +204,7 @@ void es_builder_add_descriptor(struct server *server, uint16_t type,
spa_assert(0); spa_assert(0);
} }
d = (struct descriptor *) desc_ptr; d = (struct descriptor *) desc_ptr;
d->size = size; d->size = size;
} }

View file

@ -52,11 +52,27 @@ struct descriptor {
void *ptr; void *ptr;
}; };
enum avb_mode {
/** The legacy AVB Mode */
AVB_MODE_LEGACY,
/**
* \brief Milan version 1.2, which subset of the AVB,
* \see Milan Specifications https://avnu.org/resource/milan-specification/
*/
AVB_MODE_MILAN_V12,
/** Future AVB mode will be added here if necessary */
AVB_MODE_MAX
};
struct server { struct server {
struct spa_list link; struct spa_list link;
struct impl *impl; struct impl *impl;
char *ifname; char *ifname;
/** Parsed from the configuration pipewire-avb.conf */
enum avb_mode avb_mode;
uint8_t mac_addr[6]; uint8_t mac_addr[6];
uint64_t entity_id; uint64_t entity_id;
int ifindex; int ifindex;
@ -67,7 +83,6 @@ struct server {
struct spa_hook_list listener_list; struct spa_hook_list listener_list;
struct spa_list descriptors; struct spa_list descriptors;
struct spa_list streams;
unsigned debug_messages:1; unsigned debug_messages:1;
@ -82,7 +97,18 @@ struct server {
#include "stream.h" #include "stream.h"
static inline const struct descriptor *server_find_descriptor(struct server *server, static inline void server_destroy_descriptors(struct server *server)
{
struct descriptor *d, *t;
spa_list_for_each_safe(d, t, &server->descriptors, link) {
free(d->ptr);
spa_list_remove(&d->link);
free(d);
}
}
static inline struct descriptor *server_find_descriptor(struct server *server,
uint16_t type, uint16_t index) uint16_t type, uint16_t index)
{ {
struct descriptor *d; struct descriptor *d;
@ -108,20 +134,10 @@ static inline void *server_add_descriptor(struct server *server,
if (ptr) if (ptr)
memcpy(d->ptr, ptr, size); memcpy(d->ptr, ptr, size);
spa_list_append(&server->descriptors, &d->link); spa_list_append(&server->descriptors, &d->link);
return d->ptr; return d;
} }
static inline struct stream *server_find_stream(struct server *server, const char *get_avb_mode_str(enum avb_mode mode);
enum spa_direction direction, uint16_t index)
{
struct stream *s;
spa_list_for_each(s, &server->streams, link) {
if (s->direction == direction &&
s->index == index)
return s;
}
return NULL;
}
struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props); struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props);
void avdecc_server_free(struct server *server); void avdecc_server_free(struct server *server);

View file

@ -60,7 +60,14 @@ struct mrp {
static void mrp_destroy(void *data) static void mrp_destroy(void *data)
{ {
struct mrp *mrp = data; struct mrp *mrp = data;
struct attribute *a, *t;
spa_hook_remove(&mrp->server_listener); spa_hook_remove(&mrp->server_listener);
spa_list_for_each_safe(a, t, &mrp->attributes, link) {
spa_list_remove(&a->link);
free(a);
}
free(mrp); free(mrp);
} }

View file

@ -15,8 +15,8 @@
#include "iec61883.h" #include "iec61883.h"
#include "stream.h" #include "stream.h"
#include "aecp-aem-state.h"
#include "utils.h" #include "utils.h"
#include "aecp-aem-descriptors.h"
static void on_stream_destroy(void *d) static void on_stream_destroy(void *d)
{ {
@ -235,34 +235,17 @@ static const struct pw_stream_events sink_stream_events = {
.process = on_sink_stream_process .process = on_sink_stream_process
}; };
struct stream *server_create_stream(struct server *server, struct stream *server_create_stream(struct server *server, struct stream *stream,
enum spa_direction direction, uint16_t index) enum spa_direction direction, uint16_t index)
{ {
struct stream *stream;
const struct descriptor *desc;
uint32_t n_params; uint32_t n_params;
const struct spa_pod *params[1]; const struct spa_pod *params[1];
uint8_t buffer[1024]; uint8_t buffer[1024];
struct spa_pod_builder b; struct spa_pod_builder b;
int res; int res;
desc = server_find_descriptor(server,
direction == SPA_DIRECTION_INPUT ?
AVB_AEM_DESC_STREAM_INPUT :
AVB_AEM_DESC_STREAM_OUTPUT, index);
if (desc == NULL)
return NULL;
stream = calloc(1, sizeof(*stream));
if (stream == NULL)
return NULL;
stream->server = server; stream->server = server;
stream->direction = direction; stream->direction = direction;
stream->index = index;
stream->desc = desc;
spa_list_append(&server->streams, &stream->link);
stream->prio = AVB_MSRP_PRIORITY_DEFAULT; stream->prio = AVB_MSRP_PRIORITY_DEFAULT;
stream->vlan_id = AVB_DEFAULT_VLAN; stream->vlan_id = AVB_DEFAULT_VLAN;
@ -361,8 +344,6 @@ error_free:
void stream_destroy(struct stream *stream) void stream_destroy(struct stream *stream)
{ {
avb_mrp_attribute_destroy(stream->listener_attr->mrp); avb_mrp_attribute_destroy(stream->listener_attr->mrp);
spa_list_remove(&stream->link);
free(stream);
} }
static int setup_socket(struct stream *stream) static int setup_socket(struct stream *stream)
@ -496,7 +477,7 @@ static void on_socket_data(void *data, int fd, uint32_t mask)
} }
} }
int stream_activate(struct stream *stream, uint64_t now) int stream_activate(struct stream *stream, uint16_t index, uint64_t now)
{ {
struct server *server = stream->server; struct server *server = stream->server;
struct avb_frame_header *h = (void*)stream->pdu; struct avb_frame_header *h = (void*)stream->pdu;
@ -528,7 +509,7 @@ int stream_activate(struct stream *stream, uint64_t now)
stream->talker_attr->attr.talker.stream_id = htobe64(stream->peer_id); stream->talker_attr->attr.talker.stream_id = htobe64(stream->peer_id);
avb_mrp_attribute_begin(stream->talker_attr->mrp, now); avb_mrp_attribute_begin(stream->talker_attr->mrp, now);
} else { } else {
if ((res = avb_maap_get_address(server->maap, stream->addr, stream->index)) < 0) if ((res = avb_maap_get_address(server->maap, stream->addr, index)) < 0)
return res; return res;
stream->listener_attr->attr.listener.stream_id = htobe64(stream->id); stream->listener_attr->attr.listener.stream_id = htobe64(stream->id);

View file

@ -24,8 +24,6 @@ struct stream {
struct server *server; struct server *server;
uint16_t direction; uint16_t direction;
uint16_t index;
const struct descriptor *desc;
uint64_t id; uint64_t id;
uint64_t peer_id; uint64_t peer_id;
@ -73,12 +71,12 @@ struct stream {
#include "mvrp.h" #include "mvrp.h"
#include "maap.h" #include "maap.h"
struct stream *server_create_stream(struct server *server, struct stream *server_create_stream(struct server *server, struct stream *stream,
enum spa_direction direction, uint16_t index); enum spa_direction direction, uint16_t index);
void stream_destroy(struct stream *stream); void stream_destroy(struct stream *stream);
int stream_activate(struct stream *stream, uint64_t now); int stream_activate(struct stream *stream, uint16_t index, uint64_t now);
int stream_deactivate(struct stream *stream, uint64_t now); int stream_deactivate(struct stream *stream, uint64_t now);
#endif /* AVB_STREAM_H */ #endif /* AVB_STREAM_H */

View file

@ -9,6 +9,8 @@
#include "internal.h" #include "internal.h"
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
static inline char *avb_utils_format_id(char *str, size_t size, const uint64_t id) static inline char *avb_utils_format_id(char *str, size_t size, const uint64_t id)
{ {
snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x:%04x", snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x:%04x",

View file

@ -894,6 +894,7 @@ do_port_use_buffers(struct impl *impl,
case SPA_DATA_MemPtr: case SPA_DATA_MemPtr:
spa_log_debug(impl->log, "mem %d %zd", j, SPA_PTRDIFF(d->data, baseptr)); spa_log_debug(impl->log, "mem %d %zd", j, SPA_PTRDIFF(d->data, baseptr));
b->datas[j].data = SPA_INT_TO_PTR(SPA_PTRDIFF(d->data, baseptr)); b->datas[j].data = SPA_INT_TO_PTR(SPA_PTRDIFF(d->data, baseptr));
SPA_FLAG_CLEAR(b->datas[j].flags, SPA_DATA_FLAG_MAPPABLE);
break; break;
default: default:
b->datas[j].type = SPA_ID_INVALID; b->datas[j].type = SPA_ID_INVALID;

View file

@ -735,7 +735,7 @@ error_exit:
static int static int
client_node_port_set_io(void *_data, client_node_port_set_io(void *_data,
uint32_t direction, enum spa_direction direction,
uint32_t port_id, uint32_t port_id,
uint32_t mix_id, uint32_t mix_id,
uint32_t id, uint32_t id,

View file

@ -420,11 +420,12 @@ extern struct spa_handle_factory spa_filter_graph_factory;
* *
* ### Delay * ### Delay
* *
* The delay can be used to delay a signal in time. * The delay can be used to delay a signal in time. With the Feedback and Feedforward
* controls it can also be used as a comb and an allpass filter.
* *
* The delay has an input port "In" and an output port "Out". It also has * The delay has an input port "In" and an output port "Out". It also has
* a "Delay (s)" control port. It requires a config section in the node declaration * a "Delay (s)" control port and a "Feedback" and "Feedforward" port. It requires a
* in this format: * config section in the node declaration in this format:
* *
*\code{.unparsed} *\code{.unparsed}
* filter.graph = { * filter.graph = {
@ -439,6 +440,8 @@ extern struct spa_handle_factory spa_filter_graph_factory;
* } * }
* control = { * control = {
* "Delay (s)" = ... * "Delay (s)" = ...
* "Feedback" = ...
* "Feedforward" = ...
* } * }
* ... * ...
* } * }
@ -452,6 +455,10 @@ extern struct spa_handle_factory spa_filter_graph_factory;
* - `latency` the latency in seconds. This is 0 by default but in some cases * - `latency` the latency in seconds. This is 0 by default but in some cases
* the delay can be used to introduce latency with this option. * the delay can be used to introduce latency with this option.
* *
* With the "Feedback" port one can create a comb filter. With the "Feedback"
* port and "Feedforward" port set to A and -A respectively, one can create
* an allpass filter. These settings can be used to create custom reverb units.
*
* ### Invert * ### Invert
* *
* The invert plugin can be used to invert the phase of the signal. * The invert plugin can be used to invert the phase of the signal.

View file

@ -60,6 +60,11 @@ struct client {
struct pw_manager_object *metadata_routes; struct pw_manager_object *metadata_routes;
struct pw_properties *routes; struct pw_properties *routes;
struct pw_manager_object *metadata_schema_sm_settings;
bool have_force_mono_audio;
struct pw_manager_object *metadata_sm_settings;
bool force_mono_audio;
uint32_t connect_tag; uint32_t connect_tag;
uint32_t in_index; uint32_t in_index;

View file

@ -323,5 +323,6 @@ static inline uint32_t port_type_value(const char *port_type)
#define METADATA_CONFIG_DEFAULT_SOURCE "default.configured.audio.source" #define METADATA_CONFIG_DEFAULT_SOURCE "default.configured.audio.source"
#define METADATA_TARGET_NODE "target.node" #define METADATA_TARGET_NODE "target.node"
#define METADATA_TARGET_OBJECT "target.object" #define METADATA_TARGET_OBJECT "target.object"
#define METADATA_FEATURES_AUDIO_MONO "node.features.audio.mono"
#endif /* PULSE_SERVER_DEFS_H */ #endif /* PULSE_SERVER_DEFS_H */

View file

@ -19,6 +19,7 @@
#include "client.h" #include "client.h"
#include "collect.h" #include "collect.h"
#include "defs.h"
#include "log.h" #include "log.h"
#include "manager.h" #include "manager.h"
#include "module.h" #include "module.h"
@ -89,6 +90,46 @@ static int bluez_card_object_message_handler(struct client *client, struct pw_ma
return 0; return 0;
} }
static int core_object_force_mono_output(struct client *client, const char *params, FILE *response)
{
if (!client->have_force_mono_audio) {
/* Not supported, return a null value to indicate that */
fprintf(response, "null");
return 0;
}
if (!params || params[0] == '\0') {
/* No parameter => query the current value */
fprintf(response, "%s", client->force_mono_audio ? "true" : "false");
return 0;
} else {
/* The caller is trying to set a value or clear with a null */
int ret;
if (spa_streq(params, "true")) {
ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE,
METADATA_FEATURES_AUDIO_MONO, "Spa:String:JSON", "true");
} else if (spa_streq(params, "false")) {
ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE,
METADATA_FEATURES_AUDIO_MONO, "Spa:String:JSON", "false");
} else if (spa_streq(params, "null")) {
ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE,
METADATA_FEATURES_AUDIO_MONO, NULL, NULL);
} else {
fprintf(response, "Value must be true, false, or clear");
return -EINVAL;
}
if (ret < 0)
fprintf(response, "Could not set metadata: %s", spa_strerror(ret));
else
fprintf(response, "%s", params);
return ret;
}
}
static int core_object_message_handler(struct client *client, struct pw_manager_object *o, const char *message, const char *params, FILE *response) static int core_object_message_handler(struct client *client, struct pw_manager_object *o, const char *message, const char *params, FILE *response)
{ {
pw_log_debug(": core %p object message:'%s' params:'%s'", o, message, params); pw_log_debug(": core %p object message:'%s' params:'%s'", o, message, params);
@ -103,7 +144,8 @@ static int core_object_message_handler(struct client *client, struct pw_manager_
" pipewire-pulse:malloc-trim run malloc_trim\n" " pipewire-pulse:malloc-trim run malloc_trim\n"
" pipewire-pulse:log-level update log level with <params>\n" " pipewire-pulse:log-level update log level with <params>\n"
" pipewire-pulse:list-modules list all module names\n" " pipewire-pulse:list-modules list all module names\n"
" pipewire-pulse:describe-module describe module info for <params>" " pipewire-pulse:describe-module describe module info for <params>\n"
" pipewire-pulse:force-mono-output force mono mixdown on all hardware outputs"
); );
} else if (spa_streq(message, "list-handlers")) { } else if (spa_streq(message, "list-handlers")) {
bool first = true; bool first = true;
@ -164,6 +206,8 @@ static int core_object_message_handler(struct client *client, struct pw_manager_
} else { } else {
fprintf(response, "Failed to open module.\n"); fprintf(response, "Failed to open module.\n");
} }
} else if (spa_streq(message, "pipewire-pulse:force-mono-output")) {
return core_object_force_mono_output(client, params, response);
} else { } else {
return -ENOSYS; return -ENOSYS;
} }

View file

@ -404,6 +404,14 @@ static void handle_metadata(struct client *client, struct pw_manager_object *old
if (client->metadata_routes == old) if (client->metadata_routes == old)
client->metadata_routes = new; client->metadata_routes = new;
} }
else if (spa_streq(name, "sm-settings")) {
if (client->metadata_sm_settings == old)
client->metadata_sm_settings = new;
}
else if (spa_streq(name, "schema-sm-settings")) {
if (client->metadata_schema_sm_settings == old)
client->metadata_schema_sm_settings = new;
}
} }
static uint32_t frac_to_bytes_round_up(struct spa_fraction val, const struct sample_spec *ss) static uint32_t frac_to_bytes_round_up(struct spa_fraction val, const struct sample_spec *ss)
@ -964,6 +972,14 @@ static void manager_metadata(void *data, struct pw_manager_object *o,
} }
if (subject == PW_ID_CORE && o == client->metadata_routes) if (subject == PW_ID_CORE && o == client->metadata_routes)
client_update_routes(client, key, value); client_update_routes(client, key, value);
if (subject == PW_ID_CORE && o == client->metadata_schema_sm_settings) {
if (spa_streq(key, METADATA_FEATURES_AUDIO_MONO))
client->have_force_mono_audio = true;
}
if (subject == PW_ID_CORE && o == client->metadata_sm_settings) {
if (spa_streq(key, METADATA_FEATURES_AUDIO_MONO))
client->force_mono_audio = spa_streq(value, "true");
}
} }

View file

@ -1132,7 +1132,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
} }
if (IS_VALID_NICE_LEVEL(impl->nice_level)) { if (IS_VALID_NICE_LEVEL(impl->nice_level)) {
if (set_nice(impl, impl->nice_level, !can_use_rtkit) < 0) if (set_nice(impl, impl->nice_level, !use_rtkit) < 0)
use_rtkit = can_use_rtkit; use_rtkit = can_use_rtkit;
} }
if (!use_rtkit) if (!use_rtkit)

View file

@ -507,8 +507,6 @@ static int impl_send_command(void *object, const struct spa_command *command)
case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Suspend:
case SPA_NODE_COMMAND_Flush: case SPA_NODE_COMMAND_Flush:
case SPA_NODE_COMMAND_Pause: case SPA_NODE_COMMAND_Pause:
pw_loop_invoke(impl->main_loop,
NULL, 0, NULL, 0, false, impl);
if (filter->state == PW_FILTER_STATE_STREAMING && id != SPA_NODE_COMMAND_Flush) { if (filter->state == PW_FILTER_STATE_STREAMING && id != SPA_NODE_COMMAND_Flush) {
pw_log_debug("%p: pause", filter); pw_log_debug("%p: pause", filter);
filter_set_state(filter, PW_FILTER_STATE_PAUSED, 0, NULL); filter_set_state(filter, PW_FILTER_STATE_PAUSED, 0, NULL);
@ -1389,6 +1387,28 @@ static void free_port(struct filter *impl, struct port *port)
free(port); free(port);
} }
static void filter_free(struct pw_filter *filter)
{
struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this);
pw_log_debug("%p: free", filter);
clear_params(impl, NULL, SPA_ID_INVALID);
free(filter->error);
pw_properties_free(filter->properties);
pw_map_clear(&impl->ports[SPA_DIRECTION_INPUT]);
pw_map_clear(&impl->ports[SPA_DIRECTION_OUTPUT]);
free(filter->name);
if (impl->data.context)
pw_context_destroy(impl->data.context);
free(impl);
}
SPA_EXPORT SPA_EXPORT
void pw_filter_destroy(struct pw_filter *filter) void pw_filter_destroy(struct pw_filter *filter)
{ {
@ -1411,26 +1431,13 @@ void pw_filter_destroy(struct pw_filter *filter)
spa_hook_remove(&filter->core_listener); spa_hook_remove(&filter->core_listener);
spa_list_remove(&filter->link); spa_list_remove(&filter->link);
} }
clear_params(impl, NULL, SPA_ID_INVALID);
pw_log_debug("%p: free", filter);
free(filter->error);
pw_properties_free(filter->properties);
spa_hook_list_clean(&impl->hooks); spa_hook_list_clean(&impl->hooks);
spa_hook_list_clean(&filter->listener_list); spa_hook_list_clean(&filter->listener_list);
pw_map_clear(&impl->ports[SPA_DIRECTION_INPUT]); /* Make sure there are no queued invokes from us anymore */
pw_map_clear(&impl->ports[SPA_DIRECTION_OUTPUT]); pw_loop_invoke(impl->main_loop, NULL, 0, NULL, 0, false, impl);
free(filter->name); filter_free(filter);
if (impl->data.context)
pw_context_destroy(impl->data.context);
free(impl);
} }
static int static int

View file

@ -715,8 +715,6 @@ static int impl_send_command(void *object, const struct spa_command *command)
case SPA_NODE_COMMAND_Suspend: case SPA_NODE_COMMAND_Suspend:
case SPA_NODE_COMMAND_Flush: case SPA_NODE_COMMAND_Flush:
case SPA_NODE_COMMAND_Pause: case SPA_NODE_COMMAND_Pause:
pw_loop_invoke(impl->main_loop,
NULL, 0, NULL, 0, false, impl);
if (stream->state == PW_STREAM_STATE_STREAMING && id != SPA_NODE_COMMAND_Flush) { if (stream->state == PW_STREAM_STATE_STREAMING && id != SPA_NODE_COMMAND_Flush) {
pw_log_debug("%p: pause", stream); pw_log_debug("%p: pause", stream);
stream_set_state(stream, PW_STREAM_STATE_PAUSED, 0, NULL); stream_set_state(stream, PW_STREAM_STATE_PAUSED, 0, NULL);
@ -1748,11 +1746,35 @@ static int stream_disconnect(struct stream *impl)
return 0; return 0;
} }
static void stream_free(struct pw_stream *stream)
{
struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
struct control *c;
pw_log_debug("%p: free", stream);
clear_params(impl, SPA_ID_INVALID, 0);
free(stream->error);
pw_properties_free(stream->properties);
free(stream->name);
spa_list_consume(c, &stream->controls, link) {
spa_list_remove(&c->link);
free(c);
}
if (impl->data.context)
pw_context_destroy(impl->data.context);
pw_properties_free(impl->port_props);
free(impl);
}
SPA_EXPORT SPA_EXPORT
void pw_stream_destroy(struct pw_stream *stream) void pw_stream_destroy(struct pw_stream *stream)
{ {
struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this); struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
struct control *c;
ensure_loop(impl->main_loop, return); ensure_loop(impl->main_loop, return);
@ -1768,29 +1790,13 @@ void pw_stream_destroy(struct pw_stream *stream)
spa_list_remove(&stream->link); spa_list_remove(&stream->link);
stream->core = NULL; stream->core = NULL;
} }
clear_params(impl, SPA_ID_INVALID, 0);
pw_log_debug("%p: free", stream);
free(stream->error);
pw_properties_free(stream->properties);
free(stream->name);
spa_list_consume(c, &stream->controls, link) {
spa_list_remove(&c->link);
free(c);
}
spa_hook_list_clean(&impl->hooks); spa_hook_list_clean(&impl->hooks);
spa_hook_list_clean(&stream->listener_list); spa_hook_list_clean(&stream->listener_list);
if (impl->data.context) /* Make sure there are no queued invokes from us anymore */
pw_context_destroy(impl->data.context); pw_loop_invoke(impl->main_loop, NULL, 0, NULL, 0, false, impl);
pw_properties_free(impl->port_props); stream_free(stream);
free(impl);
} }
static int static int
@ -2475,8 +2481,9 @@ int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t
time->delay += (int64_t)(((latency->min_quantum + latency->max_quantum) / 2.0f) * quantum); time->delay += (int64_t)(((latency->min_quantum + latency->max_quantum) / 2.0f) * quantum);
time->delay += (latency->min_rate + latency->max_rate) / 2; time->delay += (latency->min_rate + latency->max_rate) / 2;
if (time->rate.num != 0)
time->delay += ((latency->min_ns + latency->max_ns) / 2) * time->delay += ((latency->min_ns + latency->max_ns) / 2) *
(int64_t)time->rate.denom / (int64_t)SPA_NSEC_PER_SEC; (int64_t)time->rate.denom / ((int64_t)SPA_NSEC_PER_SEC * time->rate.num);
avail_buffers = spa_ringbuffer_get_read_index(&impl->dequeued.ring, &index); avail_buffers = spa_ringbuffer_get_read_index(&impl->dequeued.ring, &index);
avail_buffers = SPA_CLAMP(avail_buffers, 0, (int32_t)impl->n_buffers); avail_buffers = SPA_CLAMP(avail_buffers, 0, (int32_t)impl->n_buffers);

View file

@ -113,7 +113,7 @@ struct data {
#define TYPE_SYSEX 4 #define TYPE_SYSEX 4
#define TYPE_MIDI2 5 #define TYPE_MIDI2 5
int data_type; int data_type;
bool raw; bool rawfile;
const char *remote_name; const char *remote_name;
const char *media_type; const char *media_type;
const char *media_category; const char *media_category;
@ -125,7 +125,6 @@ struct data {
struct pw_properties *props; struct pw_properties *props;
const char *filename; const char *filename;
SNDFILE *file;
unsigned int bitrate; unsigned int bitrate;
unsigned int rate; unsigned int rate;
@ -147,6 +146,9 @@ struct data {
bool drained; bool drained;
uint64_t clock_time; uint64_t clock_time;
struct {
SNDFILE *file;
} sndfile;
struct { struct {
struct midi_file *file; struct midi_file *file;
struct midi_file_info info; struct midi_file_info info;
@ -181,7 +183,12 @@ struct data {
#endif #endif
struct { struct {
FILE *file; FILE *file;
bool close;
} sysex; } sysex;
struct {
FILE *file;
bool close;
} raw;
uint64_t sample_limit; /* 0 means unlimited */ uint64_t sample_limit; /* 0 means unlimited */
uint64_t samples_processed; uint64_t samples_processed;
@ -227,7 +234,7 @@ static int sf_playback_fill_x8(struct data *d, void *dest, unsigned int n_frames
{ {
sf_count_t rn; sf_count_t rn;
rn = sf_read_raw(d->file, dest, n_frames * d->stride); rn = sf_read_raw(d->sndfile.file, dest, n_frames * d->stride);
return (int)rn / d->stride; return (int)rn / d->stride;
} }
@ -236,7 +243,7 @@ static int sf_playback_fill_s16(struct data *d, void *dest, unsigned int n_frame
sf_count_t rn; sf_count_t rn;
assert(sizeof(short) == sizeof(int16_t)); assert(sizeof(short) == sizeof(int16_t));
rn = sf_readf_short(d->file, dest, n_frames); rn = sf_readf_short(d->sndfile.file, dest, n_frames);
return (int)rn; return (int)rn;
} }
@ -245,7 +252,7 @@ static int sf_playback_fill_s32(struct data *d, void *dest, unsigned int n_frame
sf_count_t rn; sf_count_t rn;
assert(sizeof(int) == sizeof(int32_t)); assert(sizeof(int) == sizeof(int32_t));
rn = sf_readf_int(d->file, dest, n_frames); rn = sf_readf_int(d->sndfile.file, dest, n_frames);
return (int)rn; return (int)rn;
} }
@ -254,7 +261,7 @@ static int sf_playback_fill_f32(struct data *d, void *dest, unsigned int n_frame
sf_count_t rn; sf_count_t rn;
assert(sizeof(float) == 4); assert(sizeof(float) == 4);
rn = sf_readf_float(d->file, dest, n_frames); rn = sf_readf_float(d->sndfile.file, dest, n_frames);
return (int)rn; return (int)rn;
} }
@ -263,7 +270,7 @@ static int sf_playback_fill_f64(struct data *d, void *dest, unsigned int n_frame
sf_count_t rn; sf_count_t rn;
assert(sizeof(double) == 8); assert(sizeof(double) == 8);
rn = sf_readf_double(d->file, dest, n_frames); rn = sf_readf_double(d->sndfile.file, dest, n_frames);
return (int)rn; return (int)rn;
} }
@ -550,7 +557,7 @@ static int sf_record_fill_x8(struct data *d, void *src, unsigned int n_frames, b
{ {
sf_count_t rn; sf_count_t rn;
rn = sf_write_raw(d->file, src, n_frames * d->stride); rn = sf_write_raw(d->sndfile.file, src, n_frames * d->stride);
return (int)rn / d->stride; return (int)rn / d->stride;
} }
@ -559,7 +566,7 @@ static int sf_record_fill_s16(struct data *d, void *src, unsigned int n_frames,
sf_count_t rn; sf_count_t rn;
assert(sizeof(short) == sizeof(int16_t)); assert(sizeof(short) == sizeof(int16_t));
rn = sf_writef_short(d->file, src, n_frames); rn = sf_writef_short(d->sndfile.file, src, n_frames);
return (int)rn; return (int)rn;
} }
@ -568,7 +575,7 @@ static int sf_record_fill_s32(struct data *d, void *src, unsigned int n_frames,
sf_count_t rn; sf_count_t rn;
assert(sizeof(int) == sizeof(int32_t)); assert(sizeof(int) == sizeof(int32_t));
rn = sf_writef_int(d->file, src, n_frames); rn = sf_writef_int(d->sndfile.file, src, n_frames);
return (int)rn; return (int)rn;
} }
@ -577,7 +584,7 @@ static int sf_record_fill_f32(struct data *d, void *src, unsigned int n_frames,
sf_count_t rn; sf_count_t rn;
assert(sizeof(float) == 4); assert(sizeof(float) == 4);
rn = sf_writef_float(d->file, src, n_frames); rn = sf_writef_float(d->sndfile.file, src, n_frames);
return (int)rn; return (int)rn;
} }
@ -586,7 +593,7 @@ static int sf_record_fill_f64(struct data *d, void *src, unsigned int n_frames,
sf_count_t rn; sf_count_t rn;
assert(sizeof(double) == 8); assert(sizeof(double) == 8);
rn = sf_writef_double(d->file, src, n_frames); rn = sf_writef_double(d->sndfile.file, src, n_frames);
return (int)rn; return (int)rn;
} }
@ -1480,11 +1487,17 @@ static int setup_sysex(struct data *data)
if (data->mode == mode_record) if (data->mode == mode_record)
return -ENOTSUP; return -ENOTSUP;
if (spa_streq(data->filename, "-")) {
data->sysex.file = stdin;
data->sysex.close = false;
} else {
data->sysex.file = fopen(data->filename, "r"); data->sysex.file = fopen(data->filename, "r");
if (data->sysex.file == NULL) { if (data->sysex.file == NULL) {
fprintf(stderr, "sysex: can't read file '%s': %m\n", data->filename); fprintf(stderr, "sysex: can't read file '%s': %m\n", data->filename);
return -errno; return -errno;
} }
data->sysex.close = false;
}
if (data->verbose) if (data->verbose)
fprintf(stderr, "sysex: opened file \"%s\"\n", data->filename); fprintf(stderr, "sysex: opened file \"%s\"\n", data->filename);
@ -1557,17 +1570,17 @@ static int setup_dsdfile(struct data *data)
return 0; return 0;
} }
static int stdout_record(struct data *d, void *src, unsigned int n_frames, bool *null_frame) static int raw_record(struct data *d, void *src, unsigned int n_frames, bool *null_frame)
{ {
return fwrite(src, d->stride, n_frames, stdout); return fwrite(src, d->stride, n_frames, d->raw.file);
} }
static int stdin_play(struct data *d, void *src, unsigned int n_frames, bool *null_frame) static int raw_play(struct data *d, void *src, unsigned int n_frames, bool *null_frame)
{ {
return fread(src, d->stride, n_frames, stdin); return fread(src, d->stride, n_frames, d->raw.file);
} }
static int setup_pipe(struct data *data) static int setup_raw(struct data *data)
{ {
const struct format_info *info; const struct format_info *info;
@ -1586,10 +1599,23 @@ static int setup_pipe(struct data *data)
data->spa_format = info->spa_format; data->spa_format = info->spa_format;
data->stride = info->width * data->channels; data->stride = info->width * data->channels;
data->fill = data->mode == mode_playback ? stdin_play : stdout_record; data->fill = data->mode == mode_playback ? raw_play : raw_record;
if (spa_streq(data->filename, "-")) {
data->raw.file = data->mode == mode_playback ? stdin : stdout;
data->raw.close = false;
} else {
data->raw.file = fopen(data->filename,
data->mode == mode_playback ? "r" : "w");
if (data->raw.file == NULL) {
fprintf(stderr, "raw: can't open file '%s': %m\n", data->filename);
return -errno;
}
data->raw.close = true;
}
if (data->verbose) if (data->verbose)
fprintf(stderr, "PIPE: rate=%u channels=%u fmt=%s samplesize=%u stride=%u\n", fprintf(stderr, "raw: rate=%u channels=%u fmt=%s samplesize=%u stride=%u\n",
data->rate, data->channels, data->rate, data->channels,
info->name, info->width, data->stride); info->name, info->width, data->stride);
@ -1618,7 +1644,7 @@ static int fill_properties(struct data *data)
if (table[c] == NULL) if (table[c] == NULL)
continue; continue;
if ((s = sf_get_string(data->file, c)) == NULL || if ((s = sf_get_string(data->sndfile.file, c)) == NULL ||
*s == '\0') *s == '\0')
continue; continue;
@ -1627,14 +1653,14 @@ static int fill_properties(struct data *data)
} }
spa_zero(sfi); spa_zero(sfi);
if ((res = sf_command(data->file, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)))) { if ((res = sf_command(data->sndfile.file, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)))) {
pw_log_error("sndfile: %s", sf_error_number(res)); pw_log_error("sndfile: %s", sf_error_number(res));
return -EIO; return -EIO;
} }
spa_zero(fi); spa_zero(fi);
fi.format = sfi.format; fi.format = sfi.format;
if (sf_command(data->file, SFC_GET_FORMAT_INFO, &fi, sizeof(fi)) == 0 && fi.name) if (sf_command(data->sndfile.file, SFC_GET_FORMAT_INFO, &fi, sizeof(fi)) == 0 && fi.name)
if (pw_properties_get(data->props, PW_KEY_MEDIA_FORMAT) == NULL) if (pw_properties_get(data->props, PW_KEY_MEDIA_FORMAT) == NULL)
pw_properties_set(data->props, PW_KEY_MEDIA_FORMAT, fi.name); pw_properties_set(data->props, PW_KEY_MEDIA_FORMAT, fi.name);
@ -1811,10 +1837,10 @@ static int setup_sndfile(struct data *data)
format_from_filename(&info, data->filename); format_from_filename(&info, data->filename);
} }
data->file = sf_open(data->filename, data->sndfile.file = sf_open(data->filename,
data->mode == mode_playback ? SFM_READ : SFM_WRITE, data->mode == mode_playback ? SFM_READ : SFM_WRITE,
&info); &info);
if (!data->file) { if (!data->sndfile.file) {
fprintf(stderr, "sndfile: failed to open audio file \"%s\": %s\n", fprintf(stderr, "sndfile: failed to open audio file \"%s\": %s\n",
data->filename, sf_strerror(NULL)); data->filename, sf_strerror(NULL));
if (data->verbose) { if (data->verbose) {
@ -1853,7 +1879,7 @@ static int setup_sndfile(struct data *data)
if (data->channelmap.n_channels == 0) { if (data->channelmap.n_channels == 0) {
bool def = false; bool def = false;
if (sf_command(data->file, SFC_GET_CHANNEL_MAP_INFO, if (sf_command(data->sndfile.file, SFC_GET_CHANNEL_MAP_INFO,
data->channelmap.position, data->channelmap.position,
sizeof(data->channelmap.position[0]) * data->channels)) { sizeof(data->channelmap.position[0]) * data->channels)) {
data->channelmap.n_channels = data->channels; data->channelmap.n_channels = data->channels;
@ -2097,7 +2123,7 @@ int main(int argc, char *argv[])
break; break;
case 'a': case 'a':
data.raw = true; data.rawfile = true;
break; break;
case 'M': case 'M':
@ -2264,8 +2290,8 @@ int main(int argc, char *argv[])
} }
pw_core_add_listener(data.core, &data.core_listener, &core_events, &data); pw_core_add_listener(data.core, &data.core_listener, &core_events, &data);
if (data.raw) { if (data.rawfile) {
ret = setup_pipe(&data); ret = setup_raw(&data);
} else { } else {
switch (data.data_type) { switch (data.data_type) {
case TYPE_PCM: case TYPE_PCM:
@ -2500,8 +2526,8 @@ error_no_context:
error_no_props: error_no_props:
error_no_main_loop: error_no_main_loop:
pw_properties_free(data.props); pw_properties_free(data.props);
if (data.file) if (data.sndfile.file)
sf_close(data.file); sf_close(data.sndfile.file);
if (data.midi.file) if (data.midi.file)
midi_file_close(data.midi.file); midi_file_close(data.midi.file);
if (data.clip.file) if (data.clip.file)
@ -2510,6 +2536,10 @@ error_no_main_loop:
dsf_file_close(data.dsf.file); dsf_file_close(data.dsf.file);
if (data.dff.file) if (data.dff.file)
dff_file_close(data.dff.file); dff_file_close(data.dff.file);
if (data.sysex.file && data.sysex.close)
fclose(data.sysex.file);
if (data.raw.file && data.raw.close)
fclose(data.raw.file);
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION #ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
if (data.encoded.packet) if (data.encoded.packet)
av_packet_free(&data.encoded.packet); av_packet_free(&data.encoded.packet);