Compare commits

..

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

133 changed files with 1544 additions and 7556 deletions

121
NEWS
View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
project('pipewire', ['c' ],
version : '1.5.84',
version : '1.5.81',
license : [ 'MIT', 'LGPL-2.1-or-later', 'GPL-2.0-only' ],
meson_version : '>= 0.61.1',
default_options : [ 'warning_level=3',

304
po/sl.po
View file

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

View file

@ -13,8 +13,8 @@ msgstr ""
"Project-Id-Version: pipewire.master-tx\n"
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/"
"issues\n"
"POT-Creation-Date: 2025-11-25 15:35+0000\n"
"PO-Revision-Date: 2025-11-26 10:19+0800\n"
"POT-Creation-Date: 2025-11-04 15:35+0000\n"
"PO-Revision-Date: 2025-11-05 07:47+0800\n"
"Last-Translator: lumingzh <lumingzh@qq.com>\n"
"Language-Team: Chinese (China) <i18n-zh@googlegroups.com>\n"
"Language: zh_CN\n"
@ -79,7 +79,7 @@ msgstr "%2$s@%3$s 上的 %1$s"
msgid "%s on %s"
msgstr "%2$s 上的 %1$s"
#: src/tools/pw-cat.c:1088
#: src/tools/pw-cat.c:1096
#, c-format
msgid ""
"%s [options] [<file>|-]\n"
@ -94,7 +94,7 @@ msgstr ""
" -v, --verbose 输出详细操作\n"
"\n"
#: src/tools/pw-cat.c:1095
#: src/tools/pw-cat.c:1103
#, c-format
msgid ""
" -R, --remote Remote daemon name\n"
@ -126,7 +126,7 @@ msgstr ""
" -P --properties 设置节点属性\n"
"\n"
#: src/tools/pw-cat.c:1113
#: src/tools/pw-cat.c:1121
#, c-format
msgid ""
" --rate Sample rate (req. for rec) (default "
@ -134,8 +134,8 @@ msgid ""
" --channels Number of channels (req. for rec) "
"(default %u)\n"
" --channel-map Channel map\n"
" one of: \"Stereo\", \"5.1\",... "
"or\n"
" one of: \"stereo\", "
"\"surround-51\",... or\n"
" comma separated list of channel "
"names: eg. \"FL,FR\"\n"
" --format Sample format %s (req. for rec) "
@ -152,9 +152,9 @@ msgstr ""
" --rate 采样率 (录制模式需要) (默认 %u)\n"
" --channels 通道数 (录制模式需要) (默认 %u)\n"
" --channel-map 通道映射\n"
" \"stereo\", \"5.1\",... 中的其一"
"或\n"
" 以英文逗号分隔的通道名列表: 如 "
" \"stereo\", \"surround-51\",... "
"中的其一或\n"
" 以\",\"分隔的通道名列表: 如 "
"\"FL,FR\"\n"
" --format 采样格式 %s (录制模式需要) (默认 "
"%s)\n"
@ -166,7 +166,7 @@ msgstr ""
" -n, --sample-count COUNT 计数采样后停止\n"
"\n"
#: src/tools/pw-cat.c:1133
#: src/tools/pw-cat.c:1141
msgid ""
" -p, --playback Playback mode\n"
" -r, --record Recording mode\n"

View file

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

View file

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

View file

@ -48,10 +48,8 @@ spa_format_audio_raw_ext_parse(const struct spa_pod *format, struct spa_audio_in
if (info->channels > max_position)
return -ECHRNG;
if (position == NULL ||
spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels) {
spa_pod_copy_array(position, SPA_TYPE_Id, info->position, max_position) != info->channels)
SPA_FLAG_SET(info->flags, SPA_AUDIO_FLAG_UNPOSITIONED);
spa_memzero(info->position, max_position * sizeof(info->position[0]));
}
return res;
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -339,7 +339,9 @@ SPA_API_POD_FILTER int spa_pod_filter_part(struct spa_pod_builder *b,
default:
if (pf != NULL) {
if (spa_pod_memcmp(pp, pf) != 0)
if (pp->size != pf->size)
return -EINVAL;
if (memcmp(pp, pf, pp->size) != 0)
return -EINVAL;
do_advance = true;
}

View file

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

View file

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

View file

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

View file

@ -2036,9 +2036,7 @@ static void recalc_headroom(struct state *state)
uint32_t latency;
uint32_t rate = 0;
if (state->force_quantum && !state->following)
rate = state->rate;
else if (state->position != NULL)
if (state->position != NULL)
rate = state->position->clock.target_rate.denom;
if (state->use_period_size_min_as_headroom)
@ -2065,6 +2063,8 @@ static void recalc_headroom(struct state *state)
state->headroom = 0;
latency = SPA_MAX(state->min_delay, SPA_MIN(state->max_delay, state->headroom));
if (rate != 0 && state->rate != 0)
latency = SPA_SCALE32_UP(latency, rate, state->rate);
if (state->is_firewire) {
/* XXX: For ALSA FireWire drivers, unlike for other ALSA drivers, buffer size
@ -2072,8 +2072,6 @@ static void recalc_headroom(struct state *state)
*/
latency += state->buffer_frames;
}
if (rate != 0 && state->rate != 0)
latency = SPA_SCALE32_UP(latency, rate, state->rate);
state->latency[state->port_direction].min_rate =
state->latency[state->port_direction].max_rate = latency;

View file

@ -100,7 +100,6 @@ struct props {
unsigned int mix_disabled:1;
unsigned int resample_disabled:1;
unsigned int resample_quality;
struct resample_config resample_config;
double rate;
char wav_path[512];
unsigned int lock_volumes:1;
@ -123,7 +122,6 @@ static void props_reset(struct props *props)
props->mix_disabled = false;
props->resample_disabled = false;
props->resample_quality = RESAMPLE_DEFAULT_QUALITY;
spa_zero(props->resample_config);
props->rate = 1.0;
spa_zero(props->wav_path);
props->lock_volumes = false;
@ -1021,7 +1019,7 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
if (this->io_position && this->io_clock &&
this->io_position->clock.target_rate.denom != this->io_clock->target_rate.denom &&
!this->props.resample_disabled) {
spa_log_debug(this->log, "driver %d changed rate:%u -> %u", this->io_position->clock.id,
spa_log_warn(this->log, "driver %d changed rate:%u -> %u", this->io_position->clock.id,
this->io_clock->target_rate.denom,
this->io_position->clock.target_rate.denom);
@ -1344,14 +1342,6 @@ static int do_sync_filter_graph(struct spa_loop *loop, bool async, uint32_t seq,
return 0;
}
static void sync_filter_graph(struct impl *impl)
{
if (impl->data_loop)
spa_loop_locked(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, impl);
else
do_sync_filter_graph(NULL, false, 0, NULL, 0, impl);
}
static void clean_filter_handles(struct impl *impl, bool force)
{
struct filter_graph *g, *t;
@ -1439,7 +1429,7 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order)
if (impl->setup)
res = setup_filter_graphs(impl, false);
sync_filter_graph(impl);
spa_loop_locked(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, impl);
if (impl->in_filter_props == 0)
clean_filter_handles(impl, false);
@ -1488,16 +1478,6 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
this->props.resample_quality = atoi(s);
else if (spa_streq(k, "resample.disable"))
this->props.resample_disabled = spa_atob(s);
else if (spa_streq(k, "resample.window"))
this->props.resample_config.window = resample_window_from_label(s);
else if (spa_streq(k, "resample.cutoff"))
spa_atod(s, &this->props.resample_config.cutoff);
else if (spa_streq(k, "resample.n-taps"))
spa_atou32(s, &this->props.resample_config.n_taps, 0);
else if (spa_strstartswith(k, "resample.param.")) {
uint32_t idx = resample_param_from_label(k+strlen("resample.param."));
spa_atod(s, &this->props.resample_config.params[idx]);
}
else if (spa_streq(k, "dither.noise"))
spa_atou32(s, &this->dir[1].conv.noise_bits, 0);
else if (spa_streq(k, "dither.method"))
@ -1508,10 +1488,6 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
}
else if (spa_streq(k, "channelmix.lock-volumes"))
this->props.lock_volumes = spa_atob(s);
else if (spa_streq(k, "audioconvert.filter-graph.disable")) {
if (!*disable_filter)
*disable_filter = spa_atob(s);
}
else if (spa_strstartswith(k, "audioconvert.filter-graph.")) {
int order = atoi(k + strlen("audioconvert.filter-graph."));
if ((res = load_filter_graph(this, s, order)) < 0) {
@ -1519,6 +1495,10 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
order, spa_strerror(res));
}
}
else if (spa_streq(k, "audioconvert.filter-graph.disable")) {
if (!*disable_filter)
*disable_filter = spa_atob(s);
}
else
return 0;
return 1;
@ -2309,7 +2289,6 @@ static int setup_resample(struct impl *this)
this->resample.o_rate = out->format.info.raw.rate;
this->resample.log = this->log;
this->resample.quality = this->props.resample_quality;
this->resample.config = this->props.resample_config;
this->resample.cpu_flags = this->cpu_flags;
this->rate_adjust = this->props.rate != 1.0;
@ -2553,8 +2532,6 @@ static int setup_convert(struct impl *this)
this->setup = true;
this->recalc = true;
sync_filter_graph(this);
return 0;
}
@ -2571,7 +2548,6 @@ static void reset_node(struct impl *this)
resample_reset(&this->resample);
this->in_offset = 0;
this->out_offset = 0;
this->setup = false;
}
static int impl_node_send_command(void *object, const struct spa_command *command)
@ -2592,6 +2568,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
break;
case SPA_NODE_COMMAND_Suspend:
reset_node(this);
this->setup = false;
SPA_FALLTHROUGH;
case SPA_NODE_COMMAND_Pause:
this->started = false;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -139,8 +139,7 @@ static int get_valid_aac_bitrate(a2dp_aac_t *conf)
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data)
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
{
a2dp_aac_t conf;
int i;

View file

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

View file

@ -61,8 +61,7 @@ duplex_frequencies[] = {
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data)
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
{
a2dp_faststream_t conf;
int i;

View file

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

View file

@ -115,8 +115,7 @@ ldac_channel_modes[] = {
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data)
const struct spa_dict *settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
{
a2dp_ldac_t conf;
int i;

View file

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

View file

@ -590,8 +590,7 @@ static int codec_fill_caps(const struct media_codec *codec, uint32_t flags,
static int codec_select_config(const struct media_codec *codec, uint32_t flags,
const void *caps, size_t caps_size,
const struct media_codec_audio_info *info,
const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE],
void **config_data)
const struct spa_dict *global_settings, uint8_t config[A2DP_MAX_CAPS_SIZE])
{
struct props props;
a2dp_opus_05_t conf;
@ -700,8 +699,8 @@ static int codec_caps_preference_cmp(const struct media_codec *codec, uint32_t f
int a, b;
/* Order selected configurations by preference */
res1 = codec->select_config(codec, flags, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1, NULL);
res2 = codec->select_config(codec, flags, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2, NULL);
res1 = codec->select_config(codec, flags, caps1, caps1_size, info, global_settings, (uint8_t *)&conf1);
res2 = codec->select_config(codec, flags, caps2, caps2_size, info, global_settings, (uint8_t *)&conf2);
#define PREFER_EXPR(expr) \
do { \

View file

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

View file

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

View file

@ -82,22 +82,6 @@
#define LC3_MAX_CHANNELS 28
/* Metadata types */
#define BAP_META_TYPE_PREFERRED_CONTEXT 0x01
#define BAP_META_TYPE_STREAMING_CONTEXT 0x02
#define BAP_META_TYPE_PROGRAM_INFO 0x03
#define BAP_META_TYPE_LANGUAGE 0x04
#define BAP_META_TYPE_CCID_LIST 0x05
#define BAP_META_TYPE_PARENTAL_RATING 0x06
#define BAP_META_TYPE_PROGRAM_INFO_URI 0x07
#define BAP_META_TYPE_AUDIO_ACTIVE_STATE 0x08
#define BAP_META_TYPE_BCAST_IMMEDIATE 0x09
#define BAP_META_TYPE_ASSISTED_LISTENING 0x0a
#define BAP_META_TYPE_BCAST_NAME 0x0b
#define BAP_META_TYPE_EXTENDED 0xfe
#define BAP_META_TYPE_VENDOR 0xff
#define BAP_CHANNEL_MONO 0x00000000 /* mono */
#define BAP_CHANNEL_FL 0x00000001 /* front left */
#define BAP_CHANNEL_FR 0x00000002 /* front right */
@ -153,68 +137,11 @@
#define BT_ISO_QOS_TARGET_LATENCY_BALANCED 0x02
#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 __attribute__((packed)) ltv {
uint8_t len;
uint8_t type;
uint8_t value[];
};
struct bap_endpoint_qos {
uint8_t framing;

View file

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

View file

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

View file

@ -12,7 +12,6 @@
#include <spa/support/log.h>
#include <spa/utils/type.h>
#include <spa/utils/json.h>
#include <spa/utils/keys.h>
#include <spa/utils/names.h>
#include <spa/utils/string.h>
@ -2677,24 +2676,11 @@ static struct spa_pod *build_prop_info_codec(struct impl *this, struct spa_pod_b
static struct spa_pod *build_props(struct impl *this, struct spa_pod_builder *b, uint32_t id)
{
struct props *p = &this->props;
struct spa_pod_frame f[2];
struct spa_pod *param;
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Props, id);
spa_pod_builder_add(b,
SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(p->codec),
SPA_PROP_bluetoothOffloadActive, SPA_POD_Bool(p->offload_active),
0);
spa_pod_builder_prop(b, SPA_PROP_params, 0);
spa_pod_builder_push_struct(b, &f[1]);
spa_pod_builder_string(b, "bluez5.disable-dummy-call");
spa_pod_builder_bool(b, this->bt_dev->disable_dummy_call);
spa_pod_builder_pop(b, &f[1]);
param = spa_pod_builder_pop(b, &f[0]);
return param;
return spa_pod_builder_add_object(b,
SPA_TYPE_OBJECT_Props, id,
SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(p->codec),
SPA_PROP_bluetoothOffloadActive, SPA_POD_Bool(p->offload_active));
}
static int impl_enum_params(void *object, int seq,
@ -3031,47 +3017,6 @@ static void apply_prop_offload_active(struct impl *this, bool active)
}
}
static int parse_prop_params(struct impl *this, struct spa_pod *params)
{
struct spa_pod_parser prs;
struct spa_pod_frame f;
int changed = 0;
if (params == NULL)
return 0;
spa_pod_parser_pod(&prs, params);
if (spa_pod_parser_push_struct(&prs, &f) < 0)
return 0;
while (true) {
const char *name;
struct spa_pod *pod;
if (spa_pod_parser_get_string(&prs, &name) < 0)
break;
if (spa_pod_parser_get_pod(&prs, &pod) < 0)
break;
if (spa_streq(name, "bluez5.disable-dummy-call") && spa_pod_is_bool(pod)) {
bool disable_dummy_call = SPA_POD_VALUE(struct spa_pod_bool, pod);
spa_log_info(this->log, "key:'%s' val:'%u'", name, disable_dummy_call);
this->bt_dev->disable_dummy_call = disable_dummy_call;
} else
continue;
changed++;
}
if (changed > 0) {
this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS;
this->params[IDX_Props].user++;
}
return changed;
}
static int impl_set_param(void *object,
uint32_t id, uint32_t flags,
const struct spa_pod *param)
@ -3148,7 +3093,6 @@ static int impl_set_param(void *object,
{
uint32_t codec_id = SPA_ID_INVALID;
bool offload_active = this->props.offload_active;
struct spa_pod *params = NULL;
if (param == NULL)
return 0;
@ -3156,8 +3100,7 @@ static int impl_set_param(void *object,
if ((res = spa_pod_parse_object(param,
SPA_TYPE_OBJECT_Props, NULL,
SPA_PROP_bluetoothAudioCodec, SPA_POD_OPT_Id(&codec_id),
SPA_PROP_bluetoothOffloadActive, SPA_POD_OPT_Bool(&offload_active),
SPA_PROP_params, SPA_POD_OPT_Pod(&params))) < 0) {
SPA_PROP_bluetoothOffloadActive, SPA_POD_OPT_Bool(&offload_active))) < 0) {
spa_log_warn(this->log, "can't parse props");
spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param);
return res;
@ -3165,15 +3108,10 @@ static int impl_set_param(void *object,
spa_log_debug(this->log, "setting props codec:%d offload:%d", (int)codec_id, (int)offload_active);
parse_prop_params(this, params);
apply_prop_offload_active(this, offload_active);
if (codec_id == SPA_ID_INVALID) {
this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL;
emit_info(this, false);
if (codec_id == SPA_ID_INVALID)
return 0;
}
if (this->profile == DEVICE_PROFILE_A2DP || profile_is_bap(this->profile) ||
this->profile == DEVICE_PROFILE_ASHA || this->profile == DEVICE_PROFILE_HSP_HFP) {
@ -3311,13 +3249,6 @@ impl_init(const struct spa_handle_factory *factory,
if ((profiles = spa_bt_profiles_from_json_array(str)) >= 0)
this->bt_dev->hw_volume_profiles = profiles;
}
if ((str = spa_dict_lookup(info, "bluez5.disable-dummy-call")) != NULL) {
bool value;
if (spa_json_parse_bool(str, strlen(str), &value) > 0)
this->bt_dev->disable_dummy_call = value;
}
}
this->device.iface = SPA_INTERFACE_INIT(

View file

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

View file

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

View file

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

View file

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

View file

@ -613,10 +613,6 @@ static void handle_errqueue(struct impl *this)
{
int res;
if (this->transport && this->transport->iso_io)
if (spa_bt_iso_io_recv_errqueue(this->transport->iso_io) == 0)
return;
/* iso-io/media-sink use these for TX latency.
* Someone else should be reading them, so drop
* only after yielding.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,68 +0,0 @@
/* 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

@ -1,16 +0,0 @@
/* 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

@ -1,68 +0,0 @@
/* 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

@ -1,23 +0,0 @@
/* 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

@ -1,142 +0,0 @@
/* 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

@ -1,14 +0,0 @@
/* 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

@ -1,194 +0,0 @@
#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

@ -1,10 +0,0 @@
#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

@ -1,183 +0,0 @@
/* 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

@ -1,13 +0,0 @@
/* 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

@ -1,154 +0,0 @@
/* 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

@ -1,16 +0,0 @@
/* 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

@ -1,180 +0,0 @@
/* 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

@ -1,21 +0,0 @@
/* 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

@ -1,68 +0,0 @@
/* 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

@ -1,23 +0,0 @@
/* 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

@ -1,111 +0,0 @@
/* 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

@ -1,153 +0,0 @@
/* 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

@ -1,23 +0,0 @@
/* 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

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

View file

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

View file

@ -1,6 +0,0 @@
#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

@ -1,180 +0,0 @@
/* 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

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

View file

@ -6,7 +6,99 @@
#define AVB_AEM_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 {
uint32_t flags;
@ -22,12 +114,6 @@ struct avb_packet_aecp_aem_lock {
uint16_t descriptor_id;
} __attribute__ ((__packed__));
struct avb_packet_aecp_aem_available {
uint32_t flags;
uint64_t acquired_controller_guid;
uint64_t lock_controller_guid;
} __attribute__ ((__packed__));
struct avb_packet_aecp_aem_read_descriptor {
uint16_t configuration;
uint8_t reserved[2];
@ -229,11 +315,9 @@ struct avb_packet_aecp_aem {
uint8_t payload[0];
} __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)
int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len);
int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len);

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