mirror of
https://gitlab.freedesktop.org/pipewire/pipewire.git
synced 2025-12-20 09:10:03 +01:00
Compare commits
100 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
165bd7b219 | ||
|
|
e15e50c5ee | ||
|
|
f468529084 | ||
|
|
385161b12a | ||
|
|
8c7890eb52 | ||
|
|
e2262617aa | ||
|
|
12b2e5d67c | ||
|
|
ee42b18226 | ||
|
|
d89d1668dc | ||
|
|
9a48bbaa36 | ||
|
|
04cf29f7cd | ||
|
|
bb564d5eb6 | ||
|
|
f03021edd1 | ||
|
|
c7ebc66e64 | ||
|
|
a6f8e209ac | ||
|
|
6f1938d501 | ||
|
|
bb1ef8ea5e | ||
|
|
b22e442b10 | ||
|
|
43bf1b8f7c | ||
|
|
ba8c6154a0 | ||
|
|
548f26882f | ||
|
|
63abd4e71c | ||
|
|
c2ada3175e | ||
|
|
8153efc6ed | ||
|
|
40aa6fbb64 | ||
|
|
34122b4bf3 | ||
|
|
d9fa0629f6 | ||
|
|
25a6fdcdb1 | ||
|
|
7aa8d8d628 | ||
|
|
8e6945c496 | ||
|
|
e1392cec0e | ||
|
|
13def13f01 | ||
|
|
ea653a52e3 | ||
|
|
6054c1a12b | ||
|
|
b43d915e71 | ||
|
|
4f8f7980f0 | ||
|
|
a6d7e98db3 | ||
|
|
ad43eba25c | ||
|
|
a97abf10ab | ||
|
|
86168ab1e2 | ||
|
|
2f83c5dab5 | ||
|
|
63a37e4947 | ||
|
|
a1a33141d7 | ||
|
|
82fe584f51 | ||
|
|
b90bd2c528 | ||
|
|
43448f147c | ||
|
|
1b39e7836d | ||
|
|
034e8683c8 | ||
|
|
2942bae034 | ||
|
|
c623886625 | ||
|
|
f65d5654d3 | ||
|
|
198f4a92f5 | ||
|
|
57e589f2e1 | ||
|
|
e30ee9c846 | ||
|
|
b68698a086 | ||
|
|
0d7cb9b39f | ||
|
|
c81ee31c3b | ||
|
|
a172bf0f55 | ||
|
|
4f39329ca9 | ||
|
|
4152c5d292 | ||
|
|
52f2137397 | ||
|
|
6619aba582 | ||
|
|
1aacf8d15a | ||
|
|
93b59609a8 | ||
|
|
e7c7b5058d | ||
|
|
986254f56f | ||
|
|
32ceb47937 | ||
|
|
354006a699 | ||
|
|
17812c33cc | ||
|
|
2673558a52 | ||
|
|
a1b829997e | ||
|
|
bcf6b185d7 | ||
|
|
8e870c809c | ||
|
|
5eea411a3c | ||
|
|
8e135c1015 | ||
|
|
cdf1ebe861 | ||
|
|
99a131a91d | ||
|
|
929ac1f09f | ||
|
|
f3d0642994 | ||
|
|
9f1c11ac34 | ||
|
|
c48e835d0c | ||
|
|
af62143327 | ||
|
|
b60623df4d | ||
|
|
a3ce0f3e28 | ||
|
|
c10f869836 | ||
|
|
c1dbba1a31 | ||
|
|
8d99bf66bd | ||
|
|
2ff45313de | ||
|
|
52ec847cbd | ||
|
|
933ac4be43 | ||
|
|
8c698366b8 | ||
|
|
21bb281c75 | ||
|
|
831357ee88 | ||
|
|
f30a0c1864 | ||
|
|
76f8ebb1f2 | ||
|
|
a13d5eeccb | ||
|
|
875dd91bc2 | ||
|
|
546dafa0b0 | ||
|
|
a88b4bfecd | ||
|
|
8593235571 |
75 changed files with 4819 additions and 725 deletions
|
|
@ -547,11 +547,49 @@ Below is an explanation of the options that can be tuned in the sample converter
|
||||||
\parblock
|
\parblock
|
||||||
The quality of the resampler. from 0 to 14, the default is 4.
|
The quality of the resampler. from 0 to 14, the default is 4.
|
||||||
|
|
||||||
|
The quality of a resampler depends on multiple factors:
|
||||||
|
|
||||||
|
1. Anti-Aliasing, how well are unwanted frequencies filtered out. Poor anti-aliasing
|
||||||
|
will make the original inaudible frequencies audible as distortion and noise.
|
||||||
|
2. Cutoff frequence. At what frequency the transition band will start. This is the
|
||||||
|
frequency where the signal will start to fade out. A too low cutoff might remove too
|
||||||
|
much of the high frequencies and make the sound dull. A too high cutoff might cause
|
||||||
|
aliasing. The cutoff frequency is usually expressed as a ratio of the Nyquist
|
||||||
|
frequency, 1.0 being the Nyquist frequency (frequency/2).
|
||||||
|
3. Transition band length. How quickly the unwanted frequencies are filtered out. A
|
||||||
|
shorter transition band requires longer filters with more CPU and latency but
|
||||||
|
causes less aliasing. The transition band length is expressed as a ratio of the
|
||||||
|
Nyquist frequency.
|
||||||
|
4. Stopband attenuation. How well the unwanted frequencies are filtered out. This is
|
||||||
|
usually measured in dB. 96dB is below audidle on CD quality audio, 150dB is below
|
||||||
|
the precision of floating point values.
|
||||||
|
5. CPU usage. Better anti-aliasing needs longer filters and is therefore more CPU
|
||||||
|
intensive.
|
||||||
|
6. Latency. Longer filters have a higher Latency. In real-time application the latency
|
||||||
|
should be kept as low as possible.
|
||||||
|
7. Ringing. A too short transition band length might cause ringing because of how the
|
||||||
|
sinc filters work. This can sound like flutter on sharp attacks in the audio signal.
|
||||||
|
|
||||||
Increasing the quality will result in better cutoff and less aliasing at the expense of
|
Increasing the quality will result in better cutoff and less aliasing at the expense of
|
||||||
(much) more CPU consumption. The default quality of 4 has been selected as a good compromise
|
(much) more CPU consumption and more ringing. The default quality of 4 has been selected
|
||||||
between quality and performance with no artifacts that are well below the audible range.
|
as a good compromise between quality and performance with no artifacts that are well
|
||||||
|
below the audible range.
|
||||||
|
|
||||||
|
The default resampler quality for the exp window results in a cutoff of 0.87 and a
|
||||||
|
filter size of about 48 taps. It has a Stopband attenuation of about 150 dB.
|
||||||
|
|
||||||
See [Infinite Wave](https://src.infinitewave.ca/) for a comparison of the performance.
|
See [Infinite Wave](https://src.infinitewave.ca/) for a comparison of the performance.
|
||||||
|
|
||||||
|
You can tune the resampler in a variaty of ways:
|
||||||
|
|
||||||
|
* Tune the cutoff frequency. Increase the cutoff to preserve more high frequencies at the
|
||||||
|
expense of more aliasing.
|
||||||
|
* Tune the transition band. Reduce the transition band to better filter out the frequencies
|
||||||
|
around the cutoff frequency and get less aliasing at the expense of more ringing, CPU and
|
||||||
|
Latency. This can be done by increasing the number of taps.
|
||||||
|
* Tune the stopband attenuation. Increase the attenuation to reduce aliasing at the expense
|
||||||
|
of a wider transition band. This can only be done on the kaiser window, the exp and
|
||||||
|
blackman window have 150dB and 96dB attenuation respecively.
|
||||||
\endparblock
|
\endparblock
|
||||||
|
|
||||||
@PAR@ node-prop resample.disable = false
|
@PAR@ node-prop resample.disable = false
|
||||||
|
|
@ -559,39 +597,86 @@ Disable the resampler entirely. The node will only be able to negotiate with the
|
||||||
when the samplerates are compatible.
|
when the samplerates are compatible.
|
||||||
|
|
||||||
@PAR@ node-prop resample.window = exp
|
@PAR@ node-prop resample.window = exp
|
||||||
|
\parblock
|
||||||
The resampler window function to use. By default an exponential window function is used
|
The resampler window function to use. By default an exponential window function is used
|
||||||
that gives a good balance between complexitiy and quality.
|
that gives a good balance between complexitiy and quality.
|
||||||
|
|
||||||
You can also specify a blackman or kaiser window, both with different tradeoffs.
|
You can also specify a blackman or kaiser window, both with different tradeoffs. The
|
||||||
|
kaiser window has some extra tunable parameters for the specific use cases.
|
||||||
|
\endparblock
|
||||||
|
|
||||||
@PAR@ node-prop resample.cutoff = 0.0
|
@PAR@ node-prop resample.cutoff = 0.0
|
||||||
The resampler cutoff frequency. A value of 0.0 will use a predefined value based on
|
\parblock
|
||||||
the resampler quality.
|
The resampler cutoff frequency. This is a value between 0.0 and 1.0. A value of 0.0 will
|
||||||
|
use a predefined value based on the resampler quality.
|
||||||
|
|
||||||
|
A higher cutoff value will preserve more high frequency content but depending on the
|
||||||
|
size of the transition band will cause more aliasing.
|
||||||
|
|
||||||
|
The default quality 4 setting for all windows is 0.87.
|
||||||
|
\endparblock
|
||||||
|
|
||||||
@PAR@ node-prop resample.n-taps = 0
|
@PAR@ node-prop resample.n-taps = 0
|
||||||
|
\parblock
|
||||||
The resampler number of taps. A value of 0 will use a predefined value based on
|
The resampler number of taps. A value of 0 will use a predefined value based on
|
||||||
the resampler quality or other window function parameters.
|
the resampler quality or other window function parameters.
|
||||||
|
|
||||||
|
A higher number of taps will use more CPU, Latency and cause more ringing but will
|
||||||
|
reduce aliasing.
|
||||||
|
|
||||||
|
The default quality setting for the exp window is 48.
|
||||||
|
\endparblock
|
||||||
|
|
||||||
@PAR@ node-prop resample.param.exp.A = 0.0
|
@PAR@ node-prop resample.param.exp.A = 0.0
|
||||||
|
\parblock
|
||||||
The A parameter for the exponential window function. A value of 0.0 will use a predefined
|
The A parameter for the exponential window function. A value of 0.0 will use a predefined
|
||||||
value based on the quality when the exp window is in use.
|
value based on the quality when the exp window is in use.
|
||||||
|
|
||||||
|
The default setting for the exp window is 16.97789.
|
||||||
|
\endparblock
|
||||||
|
|
||||||
@PAR@ node-prop resample.param.blackman.alpha = 0.0
|
@PAR@ node-prop resample.param.blackman.alpha = 0.0
|
||||||
|
\parblock
|
||||||
The alpha value of the blackman function. A value of 0.0 will use a predefined
|
The alpha value of the blackman function. A value of 0.0 will use a predefined
|
||||||
value based on the quality when the blackman window is in use.
|
value based on the quality when the blackman window is in use.
|
||||||
|
|
||||||
|
The default quality setting for the blackman window is 0.16.
|
||||||
|
\endparblock
|
||||||
|
|
||||||
@PAR@ node-prop resample.param.kaiser.stopband-attenuation = 0.0
|
@PAR@ node-prop resample.param.kaiser.stopband-attenuation = 0.0
|
||||||
The kaiser window stopband attenuation parameter. A default value of 0.0 will use a
|
\parblock
|
||||||
|
The kaiser window stopband attenuation parameter in dB. A default value of 0.0 will use a
|
||||||
predefined value based on the quality.
|
predefined value based on the quality.
|
||||||
|
|
||||||
|
A higher value will filter out more of the unwanted frequencies and reduce aliasing at the
|
||||||
|
expense of a larger transition band. A value of 96dB is below the dynamic range of CD quality
|
||||||
|
audio. 150dB is the limit of the precision of the resampler.
|
||||||
|
|
||||||
|
The default quality setting for the kaiser window is 130.000000.
|
||||||
|
\endparblock
|
||||||
|
|
||||||
@PAR@ node-prop resample.param.kaiser.transition-bandwidth = 0.0
|
@PAR@ node-prop resample.param.kaiser.transition-bandwidth = 0.0
|
||||||
|
\parblock
|
||||||
The kaiser window transition bandwidth parameter. A default value of 0.0 will use a
|
The kaiser window transition bandwidth parameter. A default value of 0.0 will use a
|
||||||
predefined value based on the quality.
|
predefined value based on the quality.
|
||||||
|
|
||||||
|
A smaller transition band will cause a steeper cutoff with less unwanted frequencies
|
||||||
|
in the final signal at the expense of more a larger filter and more CPU usage and
|
||||||
|
latency. A smaller transition band can also cause more ringing.
|
||||||
|
|
||||||
|
The default quality setting for the kaiser window is 0.177032
|
||||||
|
\endparblock
|
||||||
|
|
||||||
@PAR@ node-prop resample.param.kaiser.alpha = 0.0
|
@PAR@ node-prop resample.param.kaiser.alpha = 0.0
|
||||||
|
\parblock
|
||||||
The kaiser window alpha parameter. A default value of 0.0 will calculate an alpha value
|
The kaiser window alpha parameter. A default value of 0.0 will calculate an alpha value
|
||||||
based on the stopband-attenuation and transition-bandwidth parameters.
|
based on the stopband-attenuation and transition-bandwidth parameters.
|
||||||
|
|
||||||
|
This value is usually calculated from the other parameters but can be set explicitly
|
||||||
|
with this property.
|
||||||
|
|
||||||
|
The default quality setting for the kaiser window is 4.254931.
|
||||||
|
\endparblock
|
||||||
|
|
||||||
## Channel Mixer Parameters
|
## Channel Mixer Parameters
|
||||||
|
|
||||||
|
|
@ -1053,6 +1138,15 @@ HFP/HSP backend (default: native). Available values: any, none, hsphfpd, ofono,
|
||||||
|
|
||||||
@PAR@ monitor-prop bluez5.hfphsp-backend-native-modem # string
|
@PAR@ monitor-prop bluez5.hfphsp-backend-native-modem # string
|
||||||
|
|
||||||
|
@PAR@ monitor-prop bluez5.hfphsp-backend-native-pts # boolean
|
||||||
|
Enable specific workarounds for Bluetooth qualification.
|
||||||
|
|
||||||
|
@PAR@ monitor-prop bluez5.disable-dummy-call # boolean
|
||||||
|
By default a call status event is sent on audio stream connection/disconnection to
|
||||||
|
workaround some headset timeout disconnection when the HFP HF is used by another
|
||||||
|
application than telephony one, e.g. a conference application/website.
|
||||||
|
This prevent to send this event.
|
||||||
|
|
||||||
@PAR@ monitor-prop bluez5.dummy-avrcp player # boolean
|
@PAR@ monitor-prop bluez5.dummy-avrcp player # boolean
|
||||||
Register dummy AVRCP player. Some devices have wrongly functioning
|
Register dummy AVRCP player. Some devices have wrongly functioning
|
||||||
volume or playback controls if this is not enabled. Default: false
|
volume or playback controls if this is not enabled. Default: false
|
||||||
|
|
@ -1172,6 +1266,18 @@ Available source contexts PACS bitmask of the the server.
|
||||||
@PAR@ monitor-prop bluez5.bap-server-capabilities.source.supported-contexts # integer
|
@PAR@ monitor-prop bluez5.bap-server-capabilities.source.supported-contexts # integer
|
||||||
Supported source contexts PACS bitmask of the the server.
|
Supported source contexts PACS bitmask of the the server.
|
||||||
|
|
||||||
|
@PAR@ monitor-prop bluez5.bap-server-tmap-features = null # array of string
|
||||||
|
Override advertised TMAP service features. See TMAP specification for their meaning.
|
||||||
|
Possible values: "cg", "ct", "ums", "umr", "bms", "bmr".
|
||||||
|
Default: none.
|
||||||
|
|
||||||
|
@PAR@ monitor-prop bluez5.bap-server-gmap-features = null # array of string
|
||||||
|
Override advertised GMAP service features. See GMAP specification for their meaning.
|
||||||
|
Possible values: "ugg", "ugt", "bgs", "bgr", "ugg-multiplex", "ugg-96kbps-source", "ugg-multisink",
|
||||||
|
"ugt-source", "ugt-80kbps-source", "ugt-sink", "ugt-64kbps-sink", "ugt-multiplex", "ugt-multisink",
|
||||||
|
"ugt-multisource", "bgs-96kbps", "bgr-multisink", "bgr-multiplex".
|
||||||
|
Default: none.
|
||||||
|
|
||||||
## Device properties
|
## Device properties
|
||||||
|
|
||||||
@PAR@ device-prop bluez5.auto-connect # boolean
|
@PAR@ device-prop bluez5.auto-connect # boolean
|
||||||
|
|
|
||||||
24
po/sl.po
24
po/sl.po
|
|
@ -9,8 +9,8 @@ msgstr ""
|
||||||
"Project-Id-Version: PipeWire master\n"
|
"Project-Id-Version: PipeWire master\n"
|
||||||
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/"
|
"Report-Msgid-Bugs-To: https://gitlab.freedesktop.org/pipewire/pipewire/-/"
|
||||||
"issues\n"
|
"issues\n"
|
||||||
"POT-Creation-Date: 2025-11-04 03:33+0000\n"
|
"POT-Creation-Date: 2025-12-04 15:34+0000\n"
|
||||||
"PO-Revision-Date: 2025-11-08 15:23+0100\n"
|
"PO-Revision-Date: 2025-12-07 08:53+0100\n"
|
||||||
"Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n"
|
"Last-Translator: Martin Srebotnjak <miles@filmsi.net>\n"
|
||||||
"Language-Team: Slovenian <gnome-si@googlegroups.com>\n"
|
"Language-Team: Slovenian <gnome-si@googlegroups.com>\n"
|
||||||
"Language: sl\n"
|
"Language: sl\n"
|
||||||
|
|
@ -76,7 +76,7 @@ msgstr "%s na %s@%s"
|
||||||
msgid "%s on %s"
|
msgid "%s on %s"
|
||||||
msgstr "%s na %s"
|
msgstr "%s na %s"
|
||||||
|
|
||||||
#: src/tools/pw-cat.c:1096
|
#: src/tools/pw-cat.c:1103
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"%s [options] [<file>|-]\n"
|
"%s [options] [<file>|-]\n"
|
||||||
|
|
@ -92,7 +92,7 @@ msgstr ""
|
||||||
"\n"
|
"\n"
|
||||||
"</file>\n"
|
"</file>\n"
|
||||||
|
|
||||||
#: src/tools/pw-cat.c:1103
|
#: src/tools/pw-cat.c:1110
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
" -R, --remote Remote daemon name\n"
|
" -R, --remote Remote daemon name\n"
|
||||||
|
|
@ -129,7 +129,7 @@ msgstr ""
|
||||||
" -P --properties Nastavi lastnosti vozlišča\n"
|
" -P --properties Nastavi lastnosti vozlišča\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
|
||||||
#: src/tools/pw-cat.c:1121
|
#: src/tools/pw-cat.c:1128
|
||||||
#, c-format
|
#, c-format
|
||||||
msgid ""
|
msgid ""
|
||||||
" --rate Sample rate (req. for rec) (default "
|
" --rate Sample rate (req. for rec) (default "
|
||||||
|
|
@ -137,8 +137,8 @@ msgid ""
|
||||||
" --channels Number of channels (req. for rec) "
|
" --channels Number of channels (req. for rec) "
|
||||||
"(default %u)\n"
|
"(default %u)\n"
|
||||||
" --channel-map Channel map\n"
|
" --channel-map Channel map\n"
|
||||||
" one of: \"stereo\", "
|
" one of: \"Stereo\", \"5.1\",... "
|
||||||
"\"surround-51\",... or\n"
|
"or\n"
|
||||||
" comma separated list of channel "
|
" comma separated list of channel "
|
||||||
"names: eg. \"FL,FR\"\n"
|
"names: eg. \"FL,FR\"\n"
|
||||||
" --format Sample format %s (req. for rec) "
|
" --format Sample format %s (req. for rec) "
|
||||||
|
|
@ -157,8 +157,8 @@ msgstr ""
|
||||||
" --channels Število kanalov (zaht. za snemanje) "
|
" --channels Število kanalov (zaht. za snemanje) "
|
||||||
"(privzeto %u)\n"
|
"(privzeto %u)\n"
|
||||||
" --channel-map Preslikava kanalov\n"
|
" --channel-map Preslikava kanalov\n"
|
||||||
" Ena izmed: \"stereo\", "
|
" Ena izmed: \"Stereo\", "
|
||||||
"\"surround-51\",... ali\n"
|
"\"5.1\",... ali\n"
|
||||||
" seznam imen kanalov, ločen z "
|
" seznam imen kanalov, ločen z "
|
||||||
"vejico: npr. \"FL,FR\"\n"
|
"vejico: npr. \"FL,FR\"\n"
|
||||||
" --format Vzorčne oblike zapisa %s (zahtevano "
|
" --format Vzorčne oblike zapisa %s (zahtevano "
|
||||||
|
|
@ -172,7 +172,7 @@ msgstr ""
|
||||||
" -n, --sample-count ŠTEVEC Ustavi po ŠTEVEC vzorcih\n"
|
" -n, --sample-count ŠTEVEC Ustavi po ŠTEVEC vzorcih\n"
|
||||||
"\n"
|
"\n"
|
||||||
|
|
||||||
#: src/tools/pw-cat.c:1141
|
#: src/tools/pw-cat.c:1148
|
||||||
msgid ""
|
msgid ""
|
||||||
" -p, --playback Playback mode\n"
|
" -p, --playback Playback mode\n"
|
||||||
" -r, --record Recording mode\n"
|
" -r, --record Recording mode\n"
|
||||||
|
|
@ -353,7 +353,7 @@ msgstr "Mono izhod slušalk"
|
||||||
|
|
||||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2823
|
#: spa/plugins/alsa/acp/alsa-mixer.c:2823
|
||||||
msgid "Line Out"
|
msgid "Line Out"
|
||||||
msgstr "Linijsk izhod"
|
msgstr "Linijski izhod"
|
||||||
|
|
||||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2824
|
#: spa/plugins/alsa/acp/alsa-mixer.c:2824
|
||||||
msgid "Analog Mono Output"
|
msgid "Analog Mono Output"
|
||||||
|
|
@ -361,7 +361,7 @@ msgstr "Analogni mono izhod"
|
||||||
|
|
||||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2825
|
#: spa/plugins/alsa/acp/alsa-mixer.c:2825
|
||||||
msgid "Speakers"
|
msgid "Speakers"
|
||||||
msgstr "Govorniki"
|
msgstr "Zvočniki"
|
||||||
|
|
||||||
#: spa/plugins/alsa/acp/alsa-mixer.c:2826
|
#: spa/plugins/alsa/acp/alsa-mixer.c:2826
|
||||||
msgid "HDMI / DisplayPort"
|
msgid "HDMI / DisplayPort"
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ static const struct spa_type_audio_layout_info {
|
||||||
{ "7.1", { SPA_AUDIO_LAYOUT_7_1 } },
|
{ "7.1", { SPA_AUDIO_LAYOUT_7_1 } },
|
||||||
{ "7.1W", { SPA_AUDIO_LAYOUT_7_1W } },
|
{ "7.1W", { SPA_AUDIO_LAYOUT_7_1W } },
|
||||||
{ "7.1WR", { SPA_AUDIO_LAYOUT_7_1WR } },
|
{ "7.1WR", { SPA_AUDIO_LAYOUT_7_1WR } },
|
||||||
{ NULL, },
|
{ NULL, { 0, { SPA_AUDIO_CHANNEL_UNKNOWN } } },
|
||||||
};
|
};
|
||||||
|
|
||||||
SPA_API_AUDIO_LAYOUT_TYPES int
|
SPA_API_AUDIO_LAYOUT_TYPES int
|
||||||
|
|
|
||||||
|
|
@ -1137,7 +1137,7 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask)
|
||||||
}
|
}
|
||||||
|
|
||||||
old_position = pa_proplist_gets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED);
|
old_position = pa_proplist_gets(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED);
|
||||||
if (eld.speakers == 0) {
|
if (eld.speakers == 0 || eld.lpcm_channels == 0) {
|
||||||
changed |= old_position != NULL;
|
changed |= old_position != NULL;
|
||||||
pa_proplist_unset(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED);
|
pa_proplist_unset(p->proplist, ACP_KEY_AUDIO_POSITION_DETECTED);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1146,32 +1146,38 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask)
|
||||||
struct spa_strbuf b;
|
struct spa_strbuf b;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
|
#define _ADD_CHANNEL_POSITION(pos) \
|
||||||
|
{ \
|
||||||
|
if (i < eld.lpcm_channels) \
|
||||||
|
positions[i++] = pos; \
|
||||||
|
}
|
||||||
|
|
||||||
if (eld.speakers & 0x01) {
|
if (eld.speakers & 0x01) {
|
||||||
positions[i++] = ACP_CHANNEL_FL;
|
_ADD_CHANNEL_POSITION(ACP_CHANNEL_FL);
|
||||||
positions[i++] = ACP_CHANNEL_FR;
|
_ADD_CHANNEL_POSITION(ACP_CHANNEL_FR);
|
||||||
}
|
}
|
||||||
if (eld.speakers & 0x02) {
|
if (eld.speakers & 0x02) {
|
||||||
positions[i++] = ACP_CHANNEL_LFE;
|
_ADD_CHANNEL_POSITION(ACP_CHANNEL_LFE);
|
||||||
}
|
}
|
||||||
if (eld.speakers & 0x04) {
|
if (eld.speakers & 0x04) {
|
||||||
positions[i++] = ACP_CHANNEL_FC;
|
_ADD_CHANNEL_POSITION(ACP_CHANNEL_FC);
|
||||||
}
|
}
|
||||||
if (eld.speakers & 0x08) {
|
if (eld.speakers & 0x08) {
|
||||||
positions[i++] = ACP_CHANNEL_RL;
|
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RL);
|
||||||
positions[i++] = ACP_CHANNEL_RR;
|
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RR);
|
||||||
}
|
}
|
||||||
/* The rest are out of order in order of what channels we would prefer to use/expose first */
|
/* The rest are out of order in order of what channels we would prefer to use/expose first */
|
||||||
if (eld.speakers & 0x40) {
|
if (eld.speakers & 0x40) {
|
||||||
/* Use SL/SR instead of RLC/RRC */
|
/* Use SL/SR instead of RLC/RRC */
|
||||||
positions[i++] = ACP_CHANNEL_SL;
|
_ADD_CHANNEL_POSITION(ACP_CHANNEL_SL);
|
||||||
positions[i++] = ACP_CHANNEL_SR;
|
_ADD_CHANNEL_POSITION(ACP_CHANNEL_SR);
|
||||||
}
|
}
|
||||||
if (eld.speakers & 0x20) {
|
if (eld.speakers & 0x20) {
|
||||||
positions[i++] = ACP_CHANNEL_RLC;
|
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RLC);
|
||||||
positions[i++] = ACP_CHANNEL_RRC;
|
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RRC);
|
||||||
}
|
}
|
||||||
if (eld.speakers & 0x10) {
|
if (eld.speakers & 0x10) {
|
||||||
positions[i++] = ACP_CHANNEL_RC;
|
_ADD_CHANNEL_POSITION(ACP_CHANNEL_RC);
|
||||||
}
|
}
|
||||||
while (i < eld.lpcm_channels)
|
while (i < eld.lpcm_channels)
|
||||||
positions[i++] = ACP_CHANNEL_UNKNOWN;
|
positions[i++] = ACP_CHANNEL_UNKNOWN;
|
||||||
|
|
|
||||||
|
|
@ -2036,7 +2036,9 @@ static void recalc_headroom(struct state *state)
|
||||||
uint32_t latency;
|
uint32_t latency;
|
||||||
uint32_t rate = 0;
|
uint32_t rate = 0;
|
||||||
|
|
||||||
if (state->position != NULL)
|
if (state->force_quantum && !state->following)
|
||||||
|
rate = state->rate;
|
||||||
|
else if (state->position != NULL)
|
||||||
rate = state->position->clock.target_rate.denom;
|
rate = state->position->clock.target_rate.denom;
|
||||||
|
|
||||||
if (state->use_period_size_min_as_headroom)
|
if (state->use_period_size_min_as_headroom)
|
||||||
|
|
@ -2063,8 +2065,6 @@ static void recalc_headroom(struct state *state)
|
||||||
state->headroom = 0;
|
state->headroom = 0;
|
||||||
|
|
||||||
latency = SPA_MAX(state->min_delay, SPA_MIN(state->max_delay, state->headroom));
|
latency = SPA_MAX(state->min_delay, SPA_MIN(state->max_delay, state->headroom));
|
||||||
if (rate != 0 && state->rate != 0)
|
|
||||||
latency = SPA_SCALE32_UP(latency, rate, state->rate);
|
|
||||||
|
|
||||||
if (state->is_firewire) {
|
if (state->is_firewire) {
|
||||||
/* XXX: For ALSA FireWire drivers, unlike for other ALSA drivers, buffer size
|
/* XXX: For ALSA FireWire drivers, unlike for other ALSA drivers, buffer size
|
||||||
|
|
@ -2072,6 +2072,8 @@ static void recalc_headroom(struct state *state)
|
||||||
*/
|
*/
|
||||||
latency += state->buffer_frames;
|
latency += state->buffer_frames;
|
||||||
}
|
}
|
||||||
|
if (rate != 0 && state->rate != 0)
|
||||||
|
latency = SPA_SCALE32_UP(latency, rate, state->rate);
|
||||||
|
|
||||||
state->latency[state->port_direction].min_rate =
|
state->latency[state->port_direction].min_rate =
|
||||||
state->latency[state->port_direction].max_rate = latency;
|
state->latency[state->port_direction].max_rate = latency;
|
||||||
|
|
|
||||||
|
|
@ -1344,6 +1344,14 @@ static int do_sync_filter_graph(struct spa_loop *loop, bool async, uint32_t seq,
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sync_filter_graph(struct impl *impl)
|
||||||
|
{
|
||||||
|
if (impl->data_loop)
|
||||||
|
spa_loop_locked(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, impl);
|
||||||
|
else
|
||||||
|
do_sync_filter_graph(NULL, false, 0, NULL, 0, impl);
|
||||||
|
}
|
||||||
|
|
||||||
static void clean_filter_handles(struct impl *impl, bool force)
|
static void clean_filter_handles(struct impl *impl, bool force)
|
||||||
{
|
{
|
||||||
struct filter_graph *g, *t;
|
struct filter_graph *g, *t;
|
||||||
|
|
@ -1431,7 +1439,7 @@ static int load_filter_graph(struct impl *impl, const char *graph, int order)
|
||||||
if (impl->setup)
|
if (impl->setup)
|
||||||
res = setup_filter_graphs(impl, false);
|
res = setup_filter_graphs(impl, false);
|
||||||
|
|
||||||
spa_loop_locked(impl->data_loop, do_sync_filter_graph, 0, NULL, 0, impl);
|
sync_filter_graph(impl);
|
||||||
|
|
||||||
if (impl->in_filter_props == 0)
|
if (impl->in_filter_props == 0)
|
||||||
clean_filter_handles(impl, false);
|
clean_filter_handles(impl, false);
|
||||||
|
|
@ -1500,6 +1508,10 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
|
||||||
}
|
}
|
||||||
else if (spa_streq(k, "channelmix.lock-volumes"))
|
else if (spa_streq(k, "channelmix.lock-volumes"))
|
||||||
this->props.lock_volumes = spa_atob(s);
|
this->props.lock_volumes = spa_atob(s);
|
||||||
|
else if (spa_streq(k, "audioconvert.filter-graph.disable")) {
|
||||||
|
if (!*disable_filter)
|
||||||
|
*disable_filter = spa_atob(s);
|
||||||
|
}
|
||||||
else if (spa_strstartswith(k, "audioconvert.filter-graph.")) {
|
else if (spa_strstartswith(k, "audioconvert.filter-graph.")) {
|
||||||
int order = atoi(k + strlen("audioconvert.filter-graph."));
|
int order = atoi(k + strlen("audioconvert.filter-graph."));
|
||||||
if ((res = load_filter_graph(this, s, order)) < 0) {
|
if ((res = load_filter_graph(this, s, order)) < 0) {
|
||||||
|
|
@ -1507,10 +1519,6 @@ static int audioconvert_set_param(struct impl *this, const char *k, const char *
|
||||||
order, spa_strerror(res));
|
order, spa_strerror(res));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (spa_streq(k, "audioconvert.filter-graph.disable")) {
|
|
||||||
if (!*disable_filter)
|
|
||||||
*disable_filter = spa_atob(s);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
return 0;
|
return 0;
|
||||||
return 1;
|
return 1;
|
||||||
|
|
@ -2545,6 +2553,8 @@ static int setup_convert(struct impl *this)
|
||||||
this->setup = true;
|
this->setup = true;
|
||||||
this->recalc = true;
|
this->recalc = true;
|
||||||
|
|
||||||
|
sync_filter_graph(this);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2561,6 +2571,7 @@ static void reset_node(struct impl *this)
|
||||||
resample_reset(&this->resample);
|
resample_reset(&this->resample);
|
||||||
this->in_offset = 0;
|
this->in_offset = 0;
|
||||||
this->out_offset = 0;
|
this->out_offset = 0;
|
||||||
|
this->setup = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int impl_node_send_command(void *object, const struct spa_command *command)
|
static int impl_node_send_command(void *object, const struct spa_command *command)
|
||||||
|
|
@ -2581,7 +2592,6 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
|
||||||
break;
|
break;
|
||||||
case SPA_NODE_COMMAND_Suspend:
|
case SPA_NODE_COMMAND_Suspend:
|
||||||
reset_node(this);
|
reset_node(this);
|
||||||
this->setup = false;
|
|
||||||
SPA_FALLTHROUGH;
|
SPA_FALLTHROUGH;
|
||||||
case SPA_NODE_COMMAND_Pause:
|
case SPA_NODE_COMMAND_Pause:
|
||||||
this->started = false;
|
this->started = false;
|
||||||
|
|
|
||||||
|
|
@ -284,7 +284,8 @@ conv_s24_to_f32d_1s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
// Non static, referenced by `fmt-ops-sse41.c`.
|
||||||
|
void
|
||||||
conv_s24_to_f32d_2s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
|
conv_s24_to_f32d_2s_sse2(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
|
||||||
uint32_t n_channels, uint32_t n_samples)
|
uint32_t n_channels, uint32_t n_samples)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@
|
||||||
|
|
||||||
#include <tmmintrin.h>
|
#include <tmmintrin.h>
|
||||||
|
|
||||||
static void
|
// Non static, referenced by `fmt-ops-sse41.c`.
|
||||||
|
void
|
||||||
conv_s24_to_f32d_4s_ssse3(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
|
conv_s24_to_f32d_4s_ssse3(void *data, void * SPA_RESTRICT dst[], const void * SPA_RESTRICT src,
|
||||||
uint32_t n_channels, uint32_t n_samples)
|
uint32_t n_channels, uint32_t n_samples)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,10 @@
|
||||||
#include <spa/support/cpu.h>
|
#include <spa/support/cpu.h>
|
||||||
#include <spa/support/log.h>
|
#include <spa/support/log.h>
|
||||||
|
|
||||||
|
#ifndef RESAMPLE_DEFAULT_QUALITY
|
||||||
#define RESAMPLE_DEFAULT_QUALITY 4
|
#define RESAMPLE_DEFAULT_QUALITY 4
|
||||||
|
#endif
|
||||||
|
|
||||||
#define RESAMPLE_WINDOW_DEFAULT RESAMPLE_WINDOW_EXP
|
#define RESAMPLE_WINDOW_DEFAULT RESAMPLE_WINDOW_EXP
|
||||||
#define RESAMPLE_MAX_PARAMS 16
|
#define RESAMPLE_MAX_PARAMS 16
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,8 @@ static int open_files(struct data *d)
|
||||||
d->oname, sf_strerror(NULL));
|
d->oname, sf_strerror(NULL));
|
||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
sf_command(d->ofile, SFC_SET_CLIPPING, NULL, 1);
|
||||||
|
|
||||||
if (d->verbose) {
|
if (d->verbose) {
|
||||||
fprintf(stdout, "input '%s': channels:%d rate:%d format:%s\n",
|
fprintf(stdout, "input '%s': channels:%d rate:%d format:%s\n",
|
||||||
d->iname, d->iinfo.channels, d->iinfo.samplerate,
|
d->iname, d->iinfo.channels, d->iinfo.samplerate,
|
||||||
|
|
@ -296,10 +298,9 @@ static int do_conversion(struct data *d)
|
||||||
|
|
||||||
if (pout_len > 0) {
|
if (pout_len > 0) {
|
||||||
for (k = 0, i = 0; i < pout_len; i++) {
|
for (k = 0, i = 0; i < pout_len; i++) {
|
||||||
for (j = 0; j < channels; j++) {
|
for (j = 0; j < channels; j++)
|
||||||
obuf[k++] = out[MAX_SAMPLES * j + i];
|
obuf[k++] = out[MAX_SAMPLES * j + i];
|
||||||
}
|
}
|
||||||
}
|
|
||||||
pout_len = sf_writef_float(d->ofile, obuf, pout_len);
|
pout_len = sf_writef_float(d->ofile, obuf, pout_len);
|
||||||
|
|
||||||
written += pout_len;
|
written += pout_len;
|
||||||
|
|
|
||||||
|
|
@ -790,6 +790,7 @@ static int impl_node_process(void *object)
|
||||||
uint32_t n_buffers, maxsize;
|
uint32_t n_buffers, maxsize;
|
||||||
struct buffer **buffers;
|
struct buffer **buffers;
|
||||||
struct buffer *outb;
|
struct buffer *outb;
|
||||||
|
struct spa_data *d;
|
||||||
const void **datas;
|
const void **datas;
|
||||||
uint32_t cycle = this->position->clock.cycle & 1;
|
uint32_t cycle = this->position->clock.cycle & 1;
|
||||||
|
|
||||||
|
|
@ -856,12 +857,11 @@ static int impl_node_process(void *object)
|
||||||
outport->n_buffers);
|
outport->n_buffers);
|
||||||
return -EPIPE;
|
return -EPIPE;
|
||||||
}
|
}
|
||||||
|
d = outb->buf.datas;
|
||||||
|
|
||||||
if (n_buffers == 1) {
|
if (n_buffers == 1 && SPA_FLAG_IS_SET(d[0].flags, SPA_DATA_FLAG_DYNAMIC)) {
|
||||||
*outb->buffer = *buffers[0]->buffer;
|
*outb->buffer = *buffers[0]->buffer;
|
||||||
} else {
|
} else {
|
||||||
struct spa_data *d = outb->buf.datas;
|
|
||||||
|
|
||||||
*outb->buffer = outb->buf;
|
*outb->buffer = outb->buf;
|
||||||
|
|
||||||
maxsize = SPA_MIN(maxsize, d[0].maxsize);
|
maxsize = SPA_MIN(maxsize, d[0].maxsize);
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,7 @@ struct impl {
|
||||||
struct props props;
|
struct props props;
|
||||||
struct spa_io_clock *clock;
|
struct spa_io_clock *clock;
|
||||||
struct spa_io_position *position;
|
struct spa_io_position *position;
|
||||||
|
bool following;
|
||||||
|
|
||||||
struct spa_hook_list hooks;
|
struct spa_hook_list hooks;
|
||||||
struct spa_callbacks callbacks;
|
struct spa_callbacks callbacks;
|
||||||
|
|
@ -306,6 +307,8 @@ static int impl_node_set_io(void *object, uint32_t id, void *data, size_t size)
|
||||||
default:
|
default:
|
||||||
return -ENOENT;
|
return -ENOENT;
|
||||||
}
|
}
|
||||||
|
this->following = this->position && this->clock && this->position->clock.id != this->clock->id;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -422,6 +425,7 @@ static int make_buffer(struct impl *this)
|
||||||
|
|
||||||
this->sample_count += n_samples;
|
this->sample_count += n_samples;
|
||||||
this->elapsed_time = SAMPLES_TO_TIME(this, this->sample_count);
|
this->elapsed_time = SAMPLES_TO_TIME(this, this->sample_count);
|
||||||
|
if (!this->following)
|
||||||
set_timer(this, true);
|
set_timer(this, true);
|
||||||
|
|
||||||
io->buffer_id = b->id;
|
io->buffer_id = b->id;
|
||||||
|
|
@ -475,6 +479,7 @@ static int impl_node_send_command(void *object, const struct spa_command *comman
|
||||||
this->elapsed_time = 0;
|
this->elapsed_time = 0;
|
||||||
|
|
||||||
this->started = true;
|
this->started = true;
|
||||||
|
if (!this->following)
|
||||||
set_timer(this, true);
|
set_timer(this, true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -891,7 +896,7 @@ static inline void reuse_buffer(struct impl *this, struct port *port, uint32_t i
|
||||||
b->outstanding = false;
|
b->outstanding = false;
|
||||||
spa_list_append(&port->empty, &b->link);
|
spa_list_append(&port->empty, &b->link);
|
||||||
|
|
||||||
if (!this->props.live)
|
if (!this->props.live && !this->following)
|
||||||
set_timer(this, true);
|
set_timer(this, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -966,7 +971,7 @@ static int impl_node_process(void *object)
|
||||||
io->buffer_id = SPA_ID_INVALID;
|
io->buffer_id = SPA_ID_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->props.live)
|
if (!this->props.live || this->following)
|
||||||
return make_buffer(this);
|
return make_buffer(this);
|
||||||
else
|
else
|
||||||
return SPA_STATUS_OK;
|
return SPA_STATUS_OK;
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ struct impl {
|
||||||
struct spa_source *ring_timer;
|
struct spa_source *ring_timer;
|
||||||
void *upower;
|
void *upower;
|
||||||
struct spa_bt_telephony *telephony;
|
struct spa_bt_telephony *telephony;
|
||||||
|
bool pts;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct transport_data {
|
struct transport_data {
|
||||||
|
|
@ -1438,6 +1439,15 @@ next_indicator:
|
||||||
rfcomm_send_error(rfcomm, error);
|
rfcomm_send_error(rfcomm, error);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
} else if (spa_strstartswith(buf, "AT+BLDN") && backend->pts) {
|
||||||
|
enum cmee_error error;
|
||||||
|
|
||||||
|
/* For PTS tests HFP/AG/OCL/BV-01-C and HFP/AG/OCL/BV-02-C, fake last dial
|
||||||
|
* number by calling first memory */
|
||||||
|
if (!mm_do_call(backend->modemmanager, ">1", rfcomm, &error)) {
|
||||||
|
rfcomm_send_error(rfcomm, error);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
} else if (spa_strstartswith(buf, "AT+CHUP")) {
|
} else if (spa_strstartswith(buf, "AT+CHUP")) {
|
||||||
enum cmee_error error;
|
enum cmee_error error;
|
||||||
|
|
||||||
|
|
@ -2763,7 +2773,7 @@ static int sco_acquire_cb(void *data, bool optional)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||||
if (!mm_is_available(backend->modemmanager))
|
if (!td->rfcomm->device->disable_dummy_call)
|
||||||
rfcomm_hfp_ag_set_cind(td->rfcomm, true);
|
rfcomm_hfp_ag_set_cind(td->rfcomm, true);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -2818,7 +2828,7 @@ static int sco_release_cb(void *data)
|
||||||
spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_IDLE);
|
spa_bt_transport_set_state(t, SPA_BT_TRANSPORT_STATE_IDLE);
|
||||||
|
|
||||||
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
#ifdef HAVE_BLUEZ_5_BACKEND_HFP_NATIVE
|
||||||
if (!mm_is_available(backend->modemmanager))
|
if (!td->rfcomm->device->disable_dummy_call)
|
||||||
rfcomm_hfp_ag_set_cind(td->rfcomm, false);
|
rfcomm_hfp_ag_set_cind(td->rfcomm, false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -2932,7 +2942,11 @@ static void sco_listen_event(struct spa_source *source)
|
||||||
|
|
||||||
/* Find transport for local and remote address */
|
/* Find transport for local and remote address */
|
||||||
spa_list_for_each(rfcomm, &backend->rfcomm_list, link) {
|
spa_list_for_each(rfcomm, &backend->rfcomm_list, link) {
|
||||||
if ((rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY) &&
|
/* Audio connection is allowed from both side with legacy peer, i.e. HSP or codec negotion not supported
|
||||||
|
* (except if PTS workaround has been enabled in which case audio coonection is allowed as for HSP),
|
||||||
|
* or only from the HFP Audio Gateway. */
|
||||||
|
if ((((!rfcomm->codec_negotiation_supported || backend->pts) && (rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO)) ||
|
||||||
|
(rfcomm->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY)) &&
|
||||||
rfcomm->transport &&
|
rfcomm->transport &&
|
||||||
spa_streq(rfcomm->device->address, remote_address) &&
|
spa_streq(rfcomm->device->address, remote_address) &&
|
||||||
spa_streq(rfcomm->device->adapter->address, local_address)) {
|
spa_streq(rfcomm->device->adapter->address, local_address)) {
|
||||||
|
|
@ -2946,7 +2960,7 @@ static void sco_listen_event(struct spa_source *source)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
spa_assert(t->profile & SPA_BT_PROFILE_HEADSET_AUDIO_GATEWAY);
|
spa_assert(t->profile & SPA_BT_PROFILE_HEADSET_AUDIO);
|
||||||
|
|
||||||
if (rfcomm->telephony_ag && rfcomm->telephony_ag->transport.rejectSCO) {
|
if (rfcomm->telephony_ag && rfcomm->telephony_ag->transport.rejectSCO) {
|
||||||
spa_log_info(backend->log, "rejecting SCO, AudioGatewayTransport1.RejectSCO=true");
|
spa_log_info(backend->log, "rejecting SCO, AudioGatewayTransport1.RejectSCO=true");
|
||||||
|
|
@ -3387,6 +3401,43 @@ static DBusHandlerResult profile_new_connection(DBusConnection *conn, DBusMessag
|
||||||
}
|
}
|
||||||
spa_bt_device_add_profile(d, profile);
|
spa_bt_device_add_profile(d, profile);
|
||||||
|
|
||||||
|
/* Prevent to connect HSP/HFP in both directions, i.e. HS->AG and AG->HS.
|
||||||
|
* This may only occur when connecting to a device which provides both
|
||||||
|
* HS and AG which should not be the case with headsets and phones. */
|
||||||
|
spa_list_for_each(rfcomm, &backend->rfcomm_list, link) {
|
||||||
|
if (spa_streq(rfcomm->device->address, d->address) &&
|
||||||
|
spa_streq(rfcomm->device->adapter->address, d->adapter->address)) {
|
||||||
|
bool connected = false;
|
||||||
|
|
||||||
|
switch (profile) {
|
||||||
|
case SPA_BT_PROFILE_HFP_HF:
|
||||||
|
if (rfcomm->profile == SPA_BT_PROFILE_HFP_AG)
|
||||||
|
connected = true;
|
||||||
|
break;
|
||||||
|
case SPA_BT_PROFILE_HFP_AG:
|
||||||
|
if (rfcomm->profile == SPA_BT_PROFILE_HFP_HF)
|
||||||
|
connected = true;
|
||||||
|
break;
|
||||||
|
case SPA_BT_PROFILE_HSP_HS:
|
||||||
|
if (rfcomm->profile == SPA_BT_PROFILE_HSP_AG)
|
||||||
|
connected = true;
|
||||||
|
break;
|
||||||
|
case SPA_BT_PROFILE_HSP_AG:
|
||||||
|
if (rfcomm->profile == SPA_BT_PROFILE_HSP_HS)
|
||||||
|
connected = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
spa_log_warn(backend->log, "Unsupported profile: %s", handler);
|
||||||
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connected) {
|
||||||
|
spa_log_debug(backend->log, "Already connected in the opposite direction");
|
||||||
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dbus_message_iter_next(&it);
|
dbus_message_iter_next(&it);
|
||||||
dbus_message_iter_get_basic(&it, &fd);
|
dbus_message_iter_get_basic(&it, &fd);
|
||||||
|
|
||||||
|
|
@ -4017,6 +4068,16 @@ static void parse_hfp_disable_nrec(struct impl *backend, const struct spa_dict *
|
||||||
backend->hfp_disable_nrec = false;
|
backend->hfp_disable_nrec = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void parse_hfp_pts(struct impl *backend, const struct spa_dict *info)
|
||||||
|
{
|
||||||
|
const char *str;
|
||||||
|
|
||||||
|
if ((str = spa_dict_lookup(info, "bluez5.hfphsp-backend-native-pts")) != NULL)
|
||||||
|
backend->pts = spa_atob(str);
|
||||||
|
else
|
||||||
|
backend->pts = false;
|
||||||
|
}
|
||||||
|
|
||||||
static void parse_hfp_default_volumes(struct impl *backend, const struct spa_dict *info)
|
static void parse_hfp_default_volumes(struct impl *backend, const struct spa_dict *info)
|
||||||
{
|
{
|
||||||
const char *str;
|
const char *str;
|
||||||
|
|
@ -4101,6 +4162,7 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
|
||||||
|
|
||||||
parse_hfp_disable_nrec(backend, info);
|
parse_hfp_disable_nrec(backend, info);
|
||||||
parse_hfp_default_volumes(backend, info);
|
parse_hfp_default_volumes(backend, info);
|
||||||
|
parse_hfp_pts(backend, info);
|
||||||
|
|
||||||
#ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE
|
#ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE
|
||||||
if (!dbus_connection_register_object_path(backend->conn,
|
if (!dbus_connection_register_object_path(backend->conn,
|
||||||
|
|
|
||||||
|
|
@ -153,6 +153,69 @@
|
||||||
#define BT_ISO_QOS_TARGET_LATENCY_BALANCED 0x02
|
#define BT_ISO_QOS_TARGET_LATENCY_BALANCED 0x02
|
||||||
#define BT_ISO_QOS_TARGET_LATENCY_RELIABILITY 0x03
|
#define BT_ISO_QOS_TARGET_LATENCY_RELIABILITY 0x03
|
||||||
|
|
||||||
|
|
||||||
|
#define BT_TMAP_UUID "00001855-0000-1000-8000-00805f9b34fb"
|
||||||
|
|
||||||
|
#define BT_TMAP_ROLE_CG_STR "cg"
|
||||||
|
#define BT_TMAP_ROLE_CT_STR "ct"
|
||||||
|
#define BT_TMAP_ROLE_UMS_STR "ums"
|
||||||
|
#define BT_TMAP_ROLE_UMR_STR "umr"
|
||||||
|
#define BT_TMAP_ROLE_BMS_STR "bms"
|
||||||
|
#define BT_TMAP_ROLE_BMR_STR "bmr"
|
||||||
|
|
||||||
|
#define BT_GMAP_ROLE_UGG_STR "ugg"
|
||||||
|
#define BT_GMAP_ROLE_UGT_STR "ugt"
|
||||||
|
#define BT_GMAP_ROLE_BGS_STR "bgs"
|
||||||
|
#define BT_GMAP_ROLE_BGR_STR "bgr"
|
||||||
|
|
||||||
|
#define BT_TMAP_ROLE_LIST(role) \
|
||||||
|
role(BT_TMAP_ROLE_CG) \
|
||||||
|
role(BT_TMAP_ROLE_CT) \
|
||||||
|
role(BT_TMAP_ROLE_UMS) \
|
||||||
|
role(BT_TMAP_ROLE_UMR) \
|
||||||
|
role(BT_TMAP_ROLE_BMS) \
|
||||||
|
role(BT_TMAP_ROLE_BMR)
|
||||||
|
|
||||||
|
#define BT_GMAP_UUID "00001858-0000-1000-8000-00805f9b34fb"
|
||||||
|
|
||||||
|
#define BT_GMAP_UGG_MULTIPLEX_STR "ugg-multiplex"
|
||||||
|
#define BT_GMAP_UGG_96KBPS_SOURCE_STR "ugg-96kbps-source"
|
||||||
|
#define BT_GMAP_UGG_MULTISINK_STR "ugg-multisink"
|
||||||
|
|
||||||
|
#define BT_GMAP_UGT_SOURCE_STR "ugt-source"
|
||||||
|
#define BT_GMAP_UGT_80KBPS_SOURCE_STR "ugt-80kbps-source"
|
||||||
|
#define BT_GMAP_UGT_SINK_STR "ugt-sink"
|
||||||
|
#define BT_GMAP_UGT_64KBPS_SINK_STR "ugt-64kbps-sink"
|
||||||
|
#define BT_GMAP_UGT_MULTIPLEX_STR "ugt-multiplex"
|
||||||
|
#define BT_GMAP_UGT_MULTISINK_STR "ugt-multisink"
|
||||||
|
#define BT_GMAP_UGT_MULTISOURCE_STR "ugt-multisource"
|
||||||
|
|
||||||
|
#define BT_GMAP_BGS_96KBPS_STR "bgs-96kbps"
|
||||||
|
|
||||||
|
#define BT_GMAP_BGR_MULTISINK_STR "bgr-multisink"
|
||||||
|
#define BT_GMAP_BGR_MULTIPLEX_STR "bgr-multiplex"
|
||||||
|
|
||||||
|
#define BT_GMAP_ROLE_LIST(role) \
|
||||||
|
role(BT_GMAP_ROLE_UGG) \
|
||||||
|
role(BT_GMAP_ROLE_UGT) \
|
||||||
|
role(BT_GMAP_ROLE_BGS) \
|
||||||
|
role(BT_GMAP_ROLE_BGR)
|
||||||
|
|
||||||
|
#define BT_GMAP_FEATURE_LIST(feature) \
|
||||||
|
feature(BT_GMAP_UGG_MULTIPLEX) \
|
||||||
|
feature(BT_GMAP_UGG_96KBPS_SOURCE) \
|
||||||
|
feature(BT_GMAP_UGG_MULTISINK) \
|
||||||
|
feature(BT_GMAP_UGT_SOURCE) \
|
||||||
|
feature(BT_GMAP_UGT_80KBPS_SOURCE) \
|
||||||
|
feature(BT_GMAP_UGT_SINK) \
|
||||||
|
feature(BT_GMAP_UGT_64KBPS_SINK) \
|
||||||
|
feature(BT_GMAP_UGT_MULTIPLEX) \
|
||||||
|
feature(BT_GMAP_UGT_MULTISINK) \
|
||||||
|
feature(BT_GMAP_UGT_MULTISOURCE) \
|
||||||
|
feature(BT_GMAP_BGS_96KBPS) \
|
||||||
|
feature(BT_GMAP_BGR_MULTISINK) \
|
||||||
|
feature(BT_GMAP_BGR_MULTIPLEX)
|
||||||
|
|
||||||
struct bap_endpoint_qos {
|
struct bap_endpoint_qos {
|
||||||
uint8_t framing;
|
uint8_t framing;
|
||||||
uint8_t phy;
|
uint8_t phy;
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,11 @@ enum backend_selection {
|
||||||
#define TRANSPORT_ERROR_TIMEOUT (2*BLUEZ_ACTION_RATE_MSEC*SPA_NSEC_PER_MSEC)
|
#define TRANSPORT_ERROR_TIMEOUT (2*BLUEZ_ACTION_RATE_MSEC*SPA_NSEC_PER_MSEC)
|
||||||
|
|
||||||
|
|
||||||
|
struct bap_features {
|
||||||
|
struct spa_dict dict;
|
||||||
|
struct spa_dict_item items[32];
|
||||||
|
};
|
||||||
|
|
||||||
struct spa_bt_monitor {
|
struct spa_bt_monitor {
|
||||||
struct spa_handle handle;
|
struct spa_handle handle;
|
||||||
struct spa_device device;
|
struct spa_device device;
|
||||||
|
|
@ -131,6 +136,8 @@ struct spa_bt_monitor {
|
||||||
uint32_t bap_source_contexts;
|
uint32_t bap_source_contexts;
|
||||||
uint32_t bap_source_supported_contexts;
|
uint32_t bap_source_supported_contexts;
|
||||||
|
|
||||||
|
struct bap_features bap_features;
|
||||||
|
|
||||||
struct spa_bt_quirks *quirks;
|
struct spa_bt_quirks *quirks;
|
||||||
|
|
||||||
#define MAX_SETTINGS 128
|
#define MAX_SETTINGS 128
|
||||||
|
|
@ -161,6 +168,8 @@ struct spa_bt_remote_endpoint {
|
||||||
|
|
||||||
struct bap_endpoint_qos qos;
|
struct bap_endpoint_qos qos;
|
||||||
|
|
||||||
|
struct bap_features bap_features;
|
||||||
|
|
||||||
bool asha_right_side;
|
bool asha_right_side;
|
||||||
uint64_t hisyncid;
|
uint64_t hisyncid;
|
||||||
};
|
};
|
||||||
|
|
@ -658,6 +667,77 @@ static bool endpoint_should_be_registered(struct spa_bt_monitor *monitor,
|
||||||
codec->fill_caps;
|
codec->fill_caps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool bap_features_add(struct bap_features *feat, const char *uuid, const char *name)
|
||||||
|
{
|
||||||
|
#define TMAP_ITEM(item) { BT_TMAP_UUID, item ##_STR, BT_TMAP_UUID ":" item ##_STR },
|
||||||
|
#define GMAP_ITEM(item) { BT_GMAP_UUID, item ##_STR, BT_GMAP_UUID ":" item ##_STR },
|
||||||
|
static const struct {
|
||||||
|
const char *const uuid;
|
||||||
|
const char *const name;
|
||||||
|
const char *const key;
|
||||||
|
} values[] = {
|
||||||
|
BT_TMAP_ROLE_LIST(TMAP_ITEM)
|
||||||
|
BT_GMAP_ROLE_LIST(GMAP_ITEM)
|
||||||
|
BT_GMAP_FEATURE_LIST(GMAP_ITEM)
|
||||||
|
{ NULL, NULL, NULL }
|
||||||
|
};
|
||||||
|
SPA_STATIC_ASSERT(SPA_N_ELEMENTS(feat->items) >= SPA_N_ELEMENTS(values));
|
||||||
|
size_t n_items = feat->dict.n_items;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
/* Accept only listed features */
|
||||||
|
for (i = 0; values[i].uuid; ++i)
|
||||||
|
if (spa_streq(values[i].uuid, uuid) && spa_streq(values[i].name, name))
|
||||||
|
break;
|
||||||
|
if (!values[i].uuid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (spa_dict_lookup(&feat->dict, values[i].key))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
spa_assert(n_items < SPA_N_ELEMENTS(feat->items));
|
||||||
|
|
||||||
|
/* Add */
|
||||||
|
feat->items[n_items].key = values[i].key;
|
||||||
|
feat->items[n_items].value = values[i].uuid;
|
||||||
|
n_items++;
|
||||||
|
|
||||||
|
feat->dict = SPA_DICT(feat->items, n_items);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get feature uuid at \a i */
|
||||||
|
static const char *bap_features_get_uuid(struct bap_features *feat, size_t i)
|
||||||
|
{
|
||||||
|
if (!SPA_FLAG_IS_SET(feat->dict.flags, SPA_DICT_FLAG_SORTED))
|
||||||
|
spa_dict_qsort(&feat->dict);
|
||||||
|
|
||||||
|
if (i >= feat->dict.n_items)
|
||||||
|
return NULL;
|
||||||
|
return feat->dict.items[i].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Get feature name at \a i, or NULL if uuid doesn't match */
|
||||||
|
static const char *bap_features_get_name(struct bap_features *feat, size_t i, const char *uuid)
|
||||||
|
{
|
||||||
|
char *pos;
|
||||||
|
|
||||||
|
if (i >= feat->dict.n_items)
|
||||||
|
return NULL;
|
||||||
|
if (!spa_streq(feat->dict.items[i].value, uuid))
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
pos = strchr(feat->dict.items[i].key, ':');
|
||||||
|
if (!pos)
|
||||||
|
return NULL;
|
||||||
|
return pos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void bap_features_clear(struct bap_features *feat)
|
||||||
|
{
|
||||||
|
spa_zero(*feat);
|
||||||
|
}
|
||||||
|
|
||||||
static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata)
|
static DBusHandlerResult endpoint_select_configuration(DBusConnection *conn, DBusMessage *m, void *userdata)
|
||||||
{
|
{
|
||||||
struct spa_bt_monitor *monitor = userdata;
|
struct spa_bt_monitor *monitor = userdata;
|
||||||
|
|
@ -922,24 +1002,24 @@ static int parse_endpoint_props(struct spa_bt_monitor *monitor, DBusMessageIter
|
||||||
|
|
||||||
spa_assert(dest && size);
|
spa_assert(dest && size);
|
||||||
|
|
||||||
if (type != DBUS_TYPE_ARRAY)
|
if (!check_iter_signature(&it[1], "ay"))
|
||||||
goto bad_property;
|
goto bad_property;
|
||||||
|
|
||||||
dbus_message_iter_recurse(&it[1], &it[2]);
|
dbus_message_iter_recurse(&it[1], &it[2]);
|
||||||
type = dbus_message_iter_get_arg_type(&it[2]);
|
|
||||||
if (type != DBUS_TYPE_BYTE)
|
|
||||||
goto bad_property;
|
|
||||||
|
|
||||||
dbus_message_iter_get_fixed_array(&it[2], &data, &n);
|
dbus_message_iter_get_fixed_array(&it[2], &data, &n);
|
||||||
|
|
||||||
|
if (n) {
|
||||||
buf = malloc(n);
|
buf = malloc(n);
|
||||||
if (!buf)
|
if (!buf)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
memcpy(buf, data, n);
|
||||||
|
} else {
|
||||||
|
buf = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
free(*dest);
|
free(*dest);
|
||||||
*dest = buf;
|
*dest = buf;
|
||||||
*size = n;
|
*size = n;
|
||||||
memcpy(buf, data, n);
|
|
||||||
|
|
||||||
spa_log_info(monitor->log, "%p: %s size:%zu", monitor, key, *size);
|
spa_log_info(monitor->log, "%p: %s size:%zu", monitor, key, *size);
|
||||||
spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', *dest, *size);
|
spa_debug_log_mem(monitor->log, SPA_LOG_LEVEL_DEBUG, ' ', *dest, *size);
|
||||||
|
|
@ -1114,6 +1194,8 @@ static DBusHandlerResult endpoint_select_properties(DBusConnection *conn, DBusMe
|
||||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.debug", "true");
|
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.debug", "true");
|
||||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.metadata", (void *)ep->metadata);
|
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.metadata", (void *)ep->metadata);
|
||||||
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.metadata-len", metadata_len);
|
setting_items[i++] = SPA_DICT_ITEM_INIT("bluez5.bap.metadata-len", metadata_len);
|
||||||
|
for (j = 0; j < ep->bap_features.dict.n_items && i < SPA_N_ELEMENTS(setting_items); ++i, ++j)
|
||||||
|
setting_items[i] = ep->bap_features.dict.items[j];
|
||||||
if (ep->device->settings)
|
if (ep->device->settings)
|
||||||
for (j = 0; j < ep->device->settings->n_items && i < SPA_N_ELEMENTS(setting_items); ++i, ++j)
|
for (j = 0; j < ep->device->settings->n_items && i < SPA_N_ELEMENTS(setting_items); ++i, ++j)
|
||||||
setting_items[i] = ep->device->settings->items[j];
|
setting_items[i] = ep->device->settings->items[j];
|
||||||
|
|
@ -2856,6 +2938,38 @@ static struct spa_bt_device *create_bcast_device(struct spa_bt_monitor *monitor,
|
||||||
|
|
||||||
static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, struct spa_bt_monitor *monitor);
|
static int setup_asha_transport(struct spa_bt_remote_endpoint *remote_endpoint, struct spa_bt_monitor *monitor);
|
||||||
|
|
||||||
|
static void parse_supported_features(struct spa_bt_monitor *monitor,
|
||||||
|
DBusMessageIter *dict, struct bap_features *features)
|
||||||
|
{
|
||||||
|
while (dbus_message_iter_get_arg_type(dict) == DBUS_TYPE_DICT_ENTRY) {
|
||||||
|
DBusMessageIter entry, variant, array;
|
||||||
|
const char *key;
|
||||||
|
|
||||||
|
dbus_message_iter_recurse(dict, &entry);
|
||||||
|
dbus_message_iter_get_basic(&entry, &key);
|
||||||
|
dbus_message_iter_next(&entry);
|
||||||
|
dbus_message_iter_recurse(&entry, &variant);
|
||||||
|
|
||||||
|
if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY)
|
||||||
|
goto next;
|
||||||
|
|
||||||
|
dbus_message_iter_recurse(&variant, &array);
|
||||||
|
|
||||||
|
while (dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRING) {
|
||||||
|
const char *name;
|
||||||
|
|
||||||
|
dbus_message_iter_get_basic(&array, &name);
|
||||||
|
if (bap_features_add(features, key, name))
|
||||||
|
spa_log_debug(monitor->log, "remote_endpoint: BAP feature %s %s", key, name);
|
||||||
|
dbus_message_iter_next(&array);
|
||||||
|
}
|
||||||
|
|
||||||
|
next:
|
||||||
|
dbus_message_iter_next(dict);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_endpoint,
|
static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_endpoint,
|
||||||
DBusMessageIter *props_iter,
|
DBusMessageIter *props_iter,
|
||||||
DBusMessageIter *invalidated_iter)
|
DBusMessageIter *invalidated_iter)
|
||||||
|
|
@ -2991,8 +3105,13 @@ static int remote_endpoint_update_props(struct spa_bt_remote_endpoint *remote_en
|
||||||
remote_endpoint->hisyncid = *(uint64_t *)value;
|
remote_endpoint->hisyncid = *(uint64_t *)value;
|
||||||
|
|
||||||
spa_log_debug(monitor->log, "remote_endpoint %p: %s=%"PRIu64, remote_endpoint, key, remote_endpoint->hisyncid);
|
spa_log_debug(monitor->log, "remote_endpoint %p: %s=%"PRIu64, remote_endpoint, key, remote_endpoint->hisyncid);
|
||||||
}
|
} else if (spa_streq(key, "SupportedFeatures")) {
|
||||||
else {
|
if (!check_iter_signature(&it[1], "a{sv}"))
|
||||||
|
goto next;
|
||||||
|
|
||||||
|
dbus_message_iter_recurse(&it[1], &it[2]);
|
||||||
|
parse_supported_features(monitor, &it[2], &remote_endpoint->bap_features);
|
||||||
|
} else {
|
||||||
unhandled:
|
unhandled:
|
||||||
spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key);
|
spa_log_debug(monitor->log, "remote_endpoint %p: unhandled key %s", remote_endpoint, key);
|
||||||
}
|
}
|
||||||
|
|
@ -3047,6 +3166,8 @@ static void remote_endpoint_free(struct spa_bt_remote_endpoint *remote_endpoint)
|
||||||
if (remote_endpoint->device)
|
if (remote_endpoint->device)
|
||||||
spa_list_remove(&remote_endpoint->device_link);
|
spa_list_remove(&remote_endpoint->device_link);
|
||||||
|
|
||||||
|
bap_features_clear(&remote_endpoint->bap_features);
|
||||||
|
|
||||||
spa_list_remove(&remote_endpoint->link);
|
spa_list_remove(&remote_endpoint->link);
|
||||||
free(remote_endpoint->path);
|
free(remote_endpoint->path);
|
||||||
free(remote_endpoint->transport_path);
|
free(remote_endpoint->transport_path);
|
||||||
|
|
@ -3650,6 +3771,11 @@ static int transport_update_props(struct spa_bt_transport *transport,
|
||||||
free(transport->configuration);
|
free(transport->configuration);
|
||||||
transport->configuration_len = 0;
|
transport->configuration_len = 0;
|
||||||
|
|
||||||
|
if (!len) {
|
||||||
|
transport->configuration = NULL;
|
||||||
|
goto next;
|
||||||
|
}
|
||||||
|
|
||||||
transport->configuration = malloc(len);
|
transport->configuration = malloc(len);
|
||||||
if (transport->configuration) {
|
if (transport->configuration) {
|
||||||
memcpy(transport->configuration, value, len);
|
memcpy(transport->configuration, value, len);
|
||||||
|
|
@ -5417,6 +5543,42 @@ out:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void append_supported_features(DBusMessageIter *dict, struct bap_features *features)
|
||||||
|
{
|
||||||
|
const char *key = "SupportedFeatures";
|
||||||
|
DBusMessageIter dict_entry, dict_variant, value_dict;
|
||||||
|
DBusMessageIter entry, variant, array;
|
||||||
|
const char *uuid, *name;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
dbus_message_iter_open_container(dict, DBUS_TYPE_DICT_ENTRY, NULL, &dict_entry);
|
||||||
|
dbus_message_iter_append_basic(&dict_entry, DBUS_TYPE_STRING, &key);
|
||||||
|
dbus_message_iter_open_container(&dict_entry, DBUS_TYPE_VARIANT, "a{sv}", &dict_variant);
|
||||||
|
|
||||||
|
dbus_message_iter_open_container(&dict_variant, DBUS_TYPE_ARRAY, "{sv}", &value_dict);
|
||||||
|
|
||||||
|
i = 0;
|
||||||
|
while ((uuid = bap_features_get_uuid(features, i))) {
|
||||||
|
dbus_message_iter_open_container(&value_dict, DBUS_TYPE_DICT_ENTRY, NULL, &entry);
|
||||||
|
dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &uuid);
|
||||||
|
dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT, "as", &variant);
|
||||||
|
dbus_message_iter_open_container(&variant, DBUS_TYPE_ARRAY, "s", &array);
|
||||||
|
|
||||||
|
while ((name = bap_features_get_name(features, i, uuid))) {
|
||||||
|
dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &name);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbus_message_iter_close_container(&variant, &array);
|
||||||
|
dbus_message_iter_close_container(&entry, &variant);
|
||||||
|
dbus_message_iter_close_container(&value_dict, &entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
dbus_message_iter_close_container(&dict_variant, &value_dict);
|
||||||
|
dbus_message_iter_close_container(&dict_entry, &dict_variant);
|
||||||
|
dbus_message_iter_close_container(dict, &dict_entry);
|
||||||
|
}
|
||||||
|
|
||||||
static void append_media_object(struct spa_bt_monitor *monitor, DBusMessageIter *iter, const char *endpoint,
|
static void append_media_object(struct spa_bt_monitor *monitor, DBusMessageIter *iter, const char *endpoint,
|
||||||
const char *uuid, uint8_t codec_id, uint8_t *caps, size_t caps_size)
|
const char *uuid, uint8_t codec_id, uint8_t *caps, size_t caps_size)
|
||||||
{
|
{
|
||||||
|
|
@ -5464,6 +5626,9 @@ static void append_media_object(struct spa_bt_monitor *monitor, DBusMessageIter
|
||||||
append_basic_variant_dict_entry(&dict, "SupportedContext", DBUS_TYPE_UINT16, "q", &supported_contexts);
|
append_basic_variant_dict_entry(&dict, "SupportedContext", DBUS_TYPE_UINT16, "q", &supported_contexts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (spa_bt_profile_from_uuid(uuid) & SPA_BT_PROFILE_BAP_AUDIO)
|
||||||
|
append_supported_features(&dict, &monitor->bap_features);
|
||||||
|
|
||||||
dbus_message_iter_close_container(&entry, &dict);
|
dbus_message_iter_close_container(&entry, &dict);
|
||||||
dbus_message_iter_close_container(&array, &entry);
|
dbus_message_iter_close_container(&array, &entry);
|
||||||
dbus_message_iter_close_container(&object, &array);
|
dbus_message_iter_close_container(&object, &array);
|
||||||
|
|
@ -6685,6 +6850,8 @@ static int impl_clear(struct spa_handle *handle)
|
||||||
monitor->backend = NULL;
|
monitor->backend = NULL;
|
||||||
monitor->backend_selection = BACKEND_NATIVE;
|
monitor->backend_selection = BACKEND_NATIVE;
|
||||||
|
|
||||||
|
bap_features_clear(&monitor->bap_features);
|
||||||
|
|
||||||
spa_bt_quirks_destroy(monitor->quirks);
|
spa_bt_quirks_destroy(monitor->quirks);
|
||||||
|
|
||||||
free_media_codecs(monitor->media_codecs);
|
free_media_codecs(monitor->media_codecs);
|
||||||
|
|
@ -6998,6 +7165,32 @@ static void parse_bap_locations(struct spa_bt_monitor *this, const struct spa_di
|
||||||
*value = locations;
|
*value = locations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void bap_feature_parse(struct spa_bt_monitor *this, const char *uuid, const char *str)
|
||||||
|
{
|
||||||
|
struct spa_json it;
|
||||||
|
char name[64];
|
||||||
|
|
||||||
|
if (!str)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (spa_json_begin_array_relax(&it, str, strlen(str)) < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (spa_json_get_string(&it, name, sizeof(name)) > 0) {
|
||||||
|
if (bap_features_add(&this->bap_features, uuid, name))
|
||||||
|
spa_log_debug(this->log, "advertise BAP feature %s %s", uuid, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parse_bap_features(struct spa_bt_monitor *this, const struct spa_dict *info)
|
||||||
|
{
|
||||||
|
static const char *const tmap_uuid = "00001855-0000-1000-8000-00805f9b34fb";
|
||||||
|
static const char *const gmap_uuid = "00001858-0000-1000-8000-00805f9b34fb";
|
||||||
|
|
||||||
|
bap_feature_parse(this, tmap_uuid, spa_dict_lookup(info, "bluez5.bap-server-tmap-features"));
|
||||||
|
bap_feature_parse(this, gmap_uuid, spa_dict_lookup(info, "bluez5.bap-server-gmap-features"));
|
||||||
|
}
|
||||||
|
|
||||||
static void parse_bap_server(struct spa_bt_monitor *this, const struct spa_dict *info)
|
static void parse_bap_server(struct spa_bt_monitor *this, const struct spa_dict *info)
|
||||||
{
|
{
|
||||||
this->bap_sink_locations = BAP_CHANNEL_FL | BAP_CHANNEL_FR;
|
this->bap_sink_locations = BAP_CHANNEL_FL | BAP_CHANNEL_FR;
|
||||||
|
|
@ -7016,6 +7209,8 @@ static void parse_bap_server(struct spa_bt_monitor *this, const struct spa_dict
|
||||||
parse_bap_locations(this, info, "bluez5.bap-server-capabilities.source.locations", &this->bap_source_locations);
|
parse_bap_locations(this, info, "bluez5.bap-server-capabilities.source.locations", &this->bap_source_locations);
|
||||||
spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.source.contexts"), &this->bap_source_contexts, 0);
|
spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.source.contexts"), &this->bap_source_contexts, 0);
|
||||||
spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.source.supported-contexts"), &this->bap_source_supported_contexts, 0);
|
spa_atou32(spa_dict_lookup(info, "bluez5.bap-server-capabilities.source.supported-contexts"), &this->bap_source_supported_contexts, 0);
|
||||||
|
|
||||||
|
parse_bap_features(this, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void get_global_settings(struct spa_bt_monitor *this, const struct spa_dict *dict)
|
static void get_global_settings(struct spa_bt_monitor *this, const struct spa_dict *dict)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include <spa/support/log.h>
|
#include <spa/support/log.h>
|
||||||
#include <spa/utils/type.h>
|
#include <spa/utils/type.h>
|
||||||
|
#include <spa/utils/json.h>
|
||||||
#include <spa/utils/keys.h>
|
#include <spa/utils/keys.h>
|
||||||
#include <spa/utils/names.h>
|
#include <spa/utils/names.h>
|
||||||
#include <spa/utils/string.h>
|
#include <spa/utils/string.h>
|
||||||
|
|
@ -2676,11 +2677,24 @@ static struct spa_pod *build_prop_info_codec(struct impl *this, struct spa_pod_b
|
||||||
static struct spa_pod *build_props(struct impl *this, struct spa_pod_builder *b, uint32_t id)
|
static struct spa_pod *build_props(struct impl *this, struct spa_pod_builder *b, uint32_t id)
|
||||||
{
|
{
|
||||||
struct props *p = &this->props;
|
struct props *p = &this->props;
|
||||||
|
struct spa_pod_frame f[2];
|
||||||
|
struct spa_pod *param;
|
||||||
|
|
||||||
return spa_pod_builder_add_object(b,
|
spa_pod_builder_push_object(b, &f[0], SPA_TYPE_OBJECT_Props, id);
|
||||||
SPA_TYPE_OBJECT_Props, id,
|
spa_pod_builder_add(b,
|
||||||
SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(p->codec),
|
SPA_PROP_bluetoothAudioCodec, SPA_POD_Id(p->codec),
|
||||||
SPA_PROP_bluetoothOffloadActive, SPA_POD_Bool(p->offload_active));
|
SPA_PROP_bluetoothOffloadActive, SPA_POD_Bool(p->offload_active),
|
||||||
|
0);
|
||||||
|
|
||||||
|
spa_pod_builder_prop(b, SPA_PROP_params, 0);
|
||||||
|
spa_pod_builder_push_struct(b, &f[1]);
|
||||||
|
spa_pod_builder_string(b, "bluez5.disable-dummy-call");
|
||||||
|
spa_pod_builder_bool(b, this->bt_dev->disable_dummy_call);
|
||||||
|
spa_pod_builder_pop(b, &f[1]);
|
||||||
|
|
||||||
|
param = spa_pod_builder_pop(b, &f[0]);
|
||||||
|
|
||||||
|
return param;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int impl_enum_params(void *object, int seq,
|
static int impl_enum_params(void *object, int seq,
|
||||||
|
|
@ -3017,6 +3031,47 @@ static void apply_prop_offload_active(struct impl *this, bool active)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int parse_prop_params(struct impl *this, struct spa_pod *params)
|
||||||
|
{
|
||||||
|
struct spa_pod_parser prs;
|
||||||
|
struct spa_pod_frame f;
|
||||||
|
int changed = 0;
|
||||||
|
|
||||||
|
if (params == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
spa_pod_parser_pod(&prs, params);
|
||||||
|
if (spa_pod_parser_push_struct(&prs, &f) < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const char *name;
|
||||||
|
struct spa_pod *pod;
|
||||||
|
|
||||||
|
if (spa_pod_parser_get_string(&prs, &name) < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (spa_pod_parser_get_pod(&prs, &pod) < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (spa_streq(name, "bluez5.disable-dummy-call") && spa_pod_is_bool(pod)) {
|
||||||
|
bool disable_dummy_call = SPA_POD_VALUE(struct spa_pod_bool, pod);
|
||||||
|
spa_log_info(this->log, "key:'%s' val:'%u'", name, disable_dummy_call);
|
||||||
|
this->bt_dev->disable_dummy_call = disable_dummy_call;
|
||||||
|
} else
|
||||||
|
continue;
|
||||||
|
|
||||||
|
changed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed > 0) {
|
||||||
|
this->info.change_mask |= SPA_NODE_CHANGE_MASK_PARAMS;
|
||||||
|
this->params[IDX_Props].user++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
static int impl_set_param(void *object,
|
static int impl_set_param(void *object,
|
||||||
uint32_t id, uint32_t flags,
|
uint32_t id, uint32_t flags,
|
||||||
const struct spa_pod *param)
|
const struct spa_pod *param)
|
||||||
|
|
@ -3093,6 +3148,7 @@ static int impl_set_param(void *object,
|
||||||
{
|
{
|
||||||
uint32_t codec_id = SPA_ID_INVALID;
|
uint32_t codec_id = SPA_ID_INVALID;
|
||||||
bool offload_active = this->props.offload_active;
|
bool offload_active = this->props.offload_active;
|
||||||
|
struct spa_pod *params = NULL;
|
||||||
|
|
||||||
if (param == NULL)
|
if (param == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -3100,7 +3156,8 @@ static int impl_set_param(void *object,
|
||||||
if ((res = spa_pod_parse_object(param,
|
if ((res = spa_pod_parse_object(param,
|
||||||
SPA_TYPE_OBJECT_Props, NULL,
|
SPA_TYPE_OBJECT_Props, NULL,
|
||||||
SPA_PROP_bluetoothAudioCodec, SPA_POD_OPT_Id(&codec_id),
|
SPA_PROP_bluetoothAudioCodec, SPA_POD_OPT_Id(&codec_id),
|
||||||
SPA_PROP_bluetoothOffloadActive, SPA_POD_OPT_Bool(&offload_active))) < 0) {
|
SPA_PROP_bluetoothOffloadActive, SPA_POD_OPT_Bool(&offload_active),
|
||||||
|
SPA_PROP_params, SPA_POD_OPT_Pod(¶ms))) < 0) {
|
||||||
spa_log_warn(this->log, "can't parse props");
|
spa_log_warn(this->log, "can't parse props");
|
||||||
spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param);
|
spa_debug_log_pod(this->log, SPA_LOG_LEVEL_DEBUG, 0, NULL, param);
|
||||||
return res;
|
return res;
|
||||||
|
|
@ -3108,10 +3165,15 @@ static int impl_set_param(void *object,
|
||||||
|
|
||||||
spa_log_debug(this->log, "setting props codec:%d offload:%d", (int)codec_id, (int)offload_active);
|
spa_log_debug(this->log, "setting props codec:%d offload:%d", (int)codec_id, (int)offload_active);
|
||||||
|
|
||||||
|
parse_prop_params(this, params);
|
||||||
|
|
||||||
apply_prop_offload_active(this, offload_active);
|
apply_prop_offload_active(this, offload_active);
|
||||||
|
|
||||||
if (codec_id == SPA_ID_INVALID)
|
if (codec_id == SPA_ID_INVALID) {
|
||||||
|
this->params[IDX_Props].flags ^= SPA_PARAM_INFO_SERIAL;
|
||||||
|
emit_info(this, false);
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (this->profile == DEVICE_PROFILE_A2DP || profile_is_bap(this->profile) ||
|
if (this->profile == DEVICE_PROFILE_A2DP || profile_is_bap(this->profile) ||
|
||||||
this->profile == DEVICE_PROFILE_ASHA || this->profile == DEVICE_PROFILE_HSP_HFP) {
|
this->profile == DEVICE_PROFILE_ASHA || this->profile == DEVICE_PROFILE_HSP_HFP) {
|
||||||
|
|
@ -3249,6 +3311,13 @@ impl_init(const struct spa_handle_factory *factory,
|
||||||
if ((profiles = spa_bt_profiles_from_json_array(str)) >= 0)
|
if ((profiles = spa_bt_profiles_from_json_array(str)) >= 0)
|
||||||
this->bt_dev->hw_volume_profiles = profiles;
|
this->bt_dev->hw_volume_profiles = profiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((str = spa_dict_lookup(info, "bluez5.disable-dummy-call")) != NULL) {
|
||||||
|
bool value;
|
||||||
|
|
||||||
|
if (spa_json_parse_bool(str, strlen(str), &value) > 0)
|
||||||
|
this->bt_dev->disable_dummy_call = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this->device.iface = SPA_INTERFACE_INIT(
|
this->device.iface = SPA_INTERFACE_INIT(
|
||||||
|
|
|
||||||
|
|
@ -573,6 +573,8 @@ struct spa_bt_device {
|
||||||
|
|
||||||
const struct media_codec *preferred_codec;
|
const struct media_codec *preferred_codec;
|
||||||
uint32_t preferred_profiles;
|
uint32_t preferred_profiles;
|
||||||
|
|
||||||
|
bool disable_dummy_call;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path);
|
struct spa_bt_device *spa_bt_device_find(struct spa_bt_monitor *monitor, const char *path);
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ struct impl {
|
||||||
|
|
||||||
struct modem modem;
|
struct modem modem;
|
||||||
struct spa_list call_list;
|
struct spa_list call_list;
|
||||||
|
|
||||||
|
bool pts;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct dbus_cmd_data {
|
struct dbus_cmd_data {
|
||||||
|
|
@ -944,8 +946,13 @@ bool mm_do_call(void *modemmanager, const char* number, void *user_data, enum cm
|
||||||
spa_autofree struct dbus_cmd_data *data = NULL;
|
spa_autofree struct dbus_cmd_data *data = NULL;
|
||||||
spa_autoptr(DBusMessage) m = NULL;
|
spa_autoptr(DBusMessage) m = NULL;
|
||||||
DBusMessageIter iter, dict;
|
DBusMessageIter iter, dict;
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
for (size_t i = 0; number[i]; i++) {
|
/* Allow memory dial for PTS tests HFP/AG/OCM/BV-01-C and HFP/AG/OCM/BV-02-C */
|
||||||
|
if (this->pts && number[0] == '>')
|
||||||
|
i++;
|
||||||
|
|
||||||
|
for (; number[i]; i++) {
|
||||||
if (!is_valid_dial_string_char(number[i])) {
|
if (!is_valid_dial_string_char(number[i])) {
|
||||||
spa_log_warn(this->log, "Call creation canceled, invalid character found in dial string: %c", number[i]);
|
spa_log_warn(this->log, "Call creation canceled, invalid character found in dial string: %c", number[i]);
|
||||||
if (error)
|
if (error)
|
||||||
|
|
@ -1078,6 +1085,8 @@ void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_d
|
||||||
{
|
{
|
||||||
const char *modem_device_str = NULL;
|
const char *modem_device_str = NULL;
|
||||||
bool modem_device_found = false;
|
bool modem_device_found = false;
|
||||||
|
const char *pts_str = NULL;
|
||||||
|
bool pts = false;
|
||||||
|
|
||||||
spa_assert(log);
|
spa_assert(log);
|
||||||
spa_assert(dbus_connection);
|
spa_assert(dbus_connection);
|
||||||
|
|
@ -1087,6 +1096,9 @@ void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_d
|
||||||
if (!spa_streq(modem_device_str, "none"))
|
if (!spa_streq(modem_device_str, "none"))
|
||||||
modem_device_found = true;
|
modem_device_found = true;
|
||||||
}
|
}
|
||||||
|
if ((pts_str = spa_dict_lookup(info, "bluez5.hfphsp-backend-native-pts")) != NULL) {
|
||||||
|
pts = spa_atob(pts_str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!modem_device_found) {
|
if (!modem_device_found) {
|
||||||
spa_log_info(log, "No modem allowed, doesn't link to ModemManager");
|
spa_log_info(log, "No modem allowed, doesn't link to ModemManager");
|
||||||
|
|
@ -1104,6 +1116,7 @@ void *mm_register(struct spa_log *log, void *dbus_connection, const struct spa_d
|
||||||
if (modem_device_str && !spa_streq(modem_device_str, "any"))
|
if (modem_device_str && !spa_streq(modem_device_str, "any"))
|
||||||
this->allowed_modem_device = strdup(modem_device_str);
|
this->allowed_modem_device = strdup(modem_device_str);
|
||||||
spa_list_init(&this->call_list);
|
spa_list_init(&this->call_list);
|
||||||
|
this->pts = pts;
|
||||||
|
|
||||||
if (add_filters(this) < 0)
|
if (add_filters(this) < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
|
||||||
|
|
@ -205,9 +205,10 @@ void dsp_linear_c(void *obj, float * dst,
|
||||||
|
|
||||||
|
|
||||||
void dsp_delay_c(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer,
|
void dsp_delay_c(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer,
|
||||||
uint32_t delay, float *dst, const float *src, uint32_t n_samples)
|
uint32_t delay, float *dst, const float *src, uint32_t n_samples,
|
||||||
|
float fb, float ff)
|
||||||
{
|
{
|
||||||
if (delay == 0) {
|
if (delay == 0 && fb == 0.0f && ff == 0.0f) {
|
||||||
dsp_copy_c(obj, dst, src, n_samples);
|
dsp_copy_c(obj, dst, src, n_samples);
|
||||||
} else {
|
} else {
|
||||||
uint32_t w, o, i;
|
uint32_t w, o, i;
|
||||||
|
|
@ -215,11 +216,21 @@ void dsp_delay_c(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer,
|
||||||
w = *pos;
|
w = *pos;
|
||||||
o = n_buffer - delay;
|
o = n_buffer - delay;
|
||||||
|
|
||||||
|
if (fb == 0.0f && ff == 0.0f) {
|
||||||
for (i = 0; i < n_samples; i++) {
|
for (i = 0; i < n_samples; i++) {
|
||||||
buffer[w] = buffer[w + n_buffer] = src[i];
|
buffer[w] = buffer[w + n_buffer] = src[i];
|
||||||
dst[i] = buffer[w + o];
|
dst[i] = buffer[w + o];
|
||||||
w = w + 1 >= n_buffer ? 0 : w + 1;
|
w = w + 1 >= n_buffer ? 0 : w + 1;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
for (i = 0; i < n_samples; i++) {
|
||||||
|
float d = buffer[w + o];
|
||||||
|
float s = src[i];
|
||||||
|
buffer[w] = buffer[w + n_buffer] = s + d * fb;
|
||||||
|
dst[i] = ff * s + d;
|
||||||
|
w = w + 1 >= n_buffer ? 0 : w + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
*pos = w;
|
*pos = w;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ void dsp_biquad_run_##arch (void *obj, struct biquad *bq, uint32_t n_bq, uint32_
|
||||||
float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], uint32_t n_src, uint32_t n_samples)
|
float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[], uint32_t n_src, uint32_t n_samples)
|
||||||
#define MAKE_DELAY_FUNC(arch) \
|
#define MAKE_DELAY_FUNC(arch) \
|
||||||
void dsp_delay_##arch (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, \
|
void dsp_delay_##arch (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, \
|
||||||
uint32_t delay, float *dst, const float *src, uint32_t n_samples)
|
uint32_t delay, float *dst, const float *src, uint32_t n_samples, float fb, float ff)
|
||||||
|
|
||||||
#define MAKE_FFT_NEW_FUNC(arch) \
|
#define MAKE_FFT_NEW_FUNC(arch) \
|
||||||
void *dsp_fft_new_##arch(void *obj, uint32_t size, bool real)
|
void *dsp_fft_new_##arch(void *obj, uint32_t size, bool real)
|
||||||
|
|
|
||||||
|
|
@ -614,15 +614,16 @@ void dsp_biquad_run_sse(void *obj, struct biquad *bq, uint32_t n_bq, uint32_t bq
|
||||||
}
|
}
|
||||||
|
|
||||||
void dsp_delay_sse(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay,
|
void dsp_delay_sse(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay,
|
||||||
float *dst, const float *src, uint32_t n_samples)
|
float *dst, const float *src, uint32_t n_samples, float fb, float ff)
|
||||||
{
|
{
|
||||||
__m128 t[1];
|
__m128 t[4];
|
||||||
uint32_t w = *pos;
|
uint32_t w = *pos;
|
||||||
uint32_t o = n_buffer - delay;
|
uint32_t o = n_buffer - delay;
|
||||||
uint32_t n, unrolled;
|
uint32_t n, unrolled;
|
||||||
|
|
||||||
|
if (fb == 0.0f && ff == 0.0f) {
|
||||||
if (SPA_IS_ALIGNED(src, 16) &&
|
if (SPA_IS_ALIGNED(src, 16) &&
|
||||||
SPA_IS_ALIGNED(dst, 16))
|
SPA_IS_ALIGNED(dst, 16) && delay >= 4)
|
||||||
unrolled = n_samples & ~3;
|
unrolled = n_samples & ~3;
|
||||||
else
|
else
|
||||||
unrolled = 0;
|
unrolled = 0;
|
||||||
|
|
@ -643,6 +644,41 @@ void dsp_delay_sse(void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, u
|
||||||
_mm_store_ss(&dst[n], t[0]);
|
_mm_store_ss(&dst[n], t[0]);
|
||||||
w = w + 1 >= n_buffer ? 0 : w + 1;
|
w = w + 1 >= n_buffer ? 0 : w + 1;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
__m128 fb0 = _mm_set1_ps(fb);
|
||||||
|
__m128 ff0 = _mm_set1_ps(ff);
|
||||||
|
|
||||||
|
if (SPA_IS_ALIGNED(src, 16) &&
|
||||||
|
SPA_IS_ALIGNED(dst, 16) && delay >= 4)
|
||||||
|
unrolled = n_samples & ~3;
|
||||||
|
else
|
||||||
|
unrolled = 0;
|
||||||
|
|
||||||
|
for(n = 0; n < unrolled; n += 4) {
|
||||||
|
t[0] = _mm_loadu_ps(&buffer[w+o]);
|
||||||
|
t[1] = _mm_load_ps(&src[n]);
|
||||||
|
t[2] = _mm_mul_ps(t[0], fb0);
|
||||||
|
t[2] = _mm_add_ps(t[2], t[1]);
|
||||||
|
_mm_storeu_ps(&buffer[w], t[2]);
|
||||||
|
_mm_storeu_ps(&buffer[w+n_buffer], t[2]);
|
||||||
|
t[2] = _mm_mul_ps(t[1], ff0);
|
||||||
|
t[2] = _mm_add_ps(t[2], t[0]);
|
||||||
|
_mm_store_ps(&dst[n], t[2]);
|
||||||
|
w = w + 4 >= n_buffer ? 0 : w + 4;
|
||||||
|
}
|
||||||
|
for(; n < n_samples; n++) {
|
||||||
|
t[0] = _mm_load_ss(&buffer[w+o]);
|
||||||
|
t[1] = _mm_load_ss(&src[n]);
|
||||||
|
t[2] = _mm_mul_ss(t[0], fb0);
|
||||||
|
t[2] = _mm_add_ss(t[2], t[1]);
|
||||||
|
_mm_store_ss(&buffer[w], t[2]);
|
||||||
|
_mm_store_ss(&buffer[w+n_buffer], t[2]);
|
||||||
|
t[2] = _mm_mul_ps(t[1], ff0);
|
||||||
|
t[2] = _mm_add_ps(t[2], t[0]);
|
||||||
|
_mm_store_ss(&dst[n], t[2]);
|
||||||
|
w = w + 1 >= n_buffer ? 0 : w + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
*pos = w;
|
*pos = w;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,8 @@ struct spa_fga_dsp_methods {
|
||||||
float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[],
|
float * SPA_RESTRICT out[], const float * SPA_RESTRICT in[],
|
||||||
uint32_t n_src, uint32_t n_samples);
|
uint32_t n_src, uint32_t n_samples);
|
||||||
void (*delay) (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay,
|
void (*delay) (void *obj, float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay,
|
||||||
float *dst, const float *src, uint32_t n_samples);
|
float *dst, const float *src, uint32_t n_samples,
|
||||||
|
float fb, float ff);
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline void spa_fga_dsp_clear(struct spa_fga_dsp *obj, float * SPA_RESTRICT dst, uint32_t n_samples)
|
static inline void spa_fga_dsp_clear(struct spa_fga_dsp *obj, float * SPA_RESTRICT dst, uint32_t n_samples)
|
||||||
|
|
@ -159,10 +160,11 @@ static inline void spa_fga_dsp_biquad_run(struct spa_fga_dsp *obj,
|
||||||
}
|
}
|
||||||
static inline void spa_fga_dsp_delay(struct spa_fga_dsp *obj,
|
static inline void spa_fga_dsp_delay(struct spa_fga_dsp *obj,
|
||||||
float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay,
|
float *buffer, uint32_t *pos, uint32_t n_buffer, uint32_t delay,
|
||||||
float *dst, const float *src, uint32_t n_samples)
|
float *dst, const float *src, uint32_t n_samples,
|
||||||
|
float fb, float ff)
|
||||||
{
|
{
|
||||||
spa_api_method_v(spa_fga_dsp, &obj->iface, delay, 0,
|
spa_api_method_v(spa_fga_dsp, &obj->iface, delay, 0,
|
||||||
buffer, pos, n_buffer, delay, dst, src, n_samples);
|
buffer, pos, n_buffer, delay, dst, src, n_samples, fb, ff);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* SPA_FGA_DSP_H */
|
#endif /* SPA_FGA_DSP_H */
|
||||||
|
|
|
||||||
|
|
@ -1228,7 +1228,7 @@ struct delay_impl {
|
||||||
struct spa_log *log;
|
struct spa_log *log;
|
||||||
|
|
||||||
unsigned long rate;
|
unsigned long rate;
|
||||||
float *port[4];
|
float *port[6];
|
||||||
|
|
||||||
float delay;
|
float delay;
|
||||||
uint32_t delay_samples;
|
uint32_t delay_samples;
|
||||||
|
|
@ -1334,7 +1334,8 @@ static void delay_run(void * Instance, unsigned long SampleCount)
|
||||||
}
|
}
|
||||||
if (in != NULL && out != NULL) {
|
if (in != NULL && out != NULL) {
|
||||||
spa_fga_dsp_delay(impl->dsp, impl->buffer, &impl->ptr, impl->buffer_samples,
|
spa_fga_dsp_delay(impl->dsp, impl->buffer, &impl->ptr, impl->buffer_samples,
|
||||||
impl->delay_samples, out, in, SampleCount);
|
impl->delay_samples, out, in, SampleCount,
|
||||||
|
impl->port[4][0], impl->port[5][0]);
|
||||||
}
|
}
|
||||||
if (impl->port[3] != NULL)
|
if (impl->port[3] != NULL)
|
||||||
impl->port[3][0] = impl->latency;
|
impl->port[3][0] = impl->latency;
|
||||||
|
|
@ -1359,6 +1360,16 @@ static struct spa_fga_port delay_ports[] = {
|
||||||
.hint = SPA_FGA_HINT_LATENCY,
|
.hint = SPA_FGA_HINT_LATENCY,
|
||||||
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL,
|
.flags = SPA_FGA_PORT_OUTPUT | SPA_FGA_PORT_CONTROL,
|
||||||
},
|
},
|
||||||
|
{ .index = 4,
|
||||||
|
.name = "Feedback",
|
||||||
|
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
|
||||||
|
.def = 0.0f, .min = -10.0f, .max = 10.0f
|
||||||
|
},
|
||||||
|
{ .index = 5,
|
||||||
|
.name = "Feedforward",
|
||||||
|
.flags = SPA_FGA_PORT_INPUT | SPA_FGA_PORT_CONTROL,
|
||||||
|
.def = 0.0f, .min = -10.0f, .max = 10.0f
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct spa_fga_descriptor delay_desc = {
|
static const struct spa_fga_descriptor delay_desc = {
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ avb.properties = {
|
||||||
# the addresses this server listens on
|
# the addresses this server listens on
|
||||||
#ifname = "eth0.2"
|
#ifname = "eth0.2"
|
||||||
ifname = "enp3s0"
|
ifname = "enp3s0"
|
||||||
|
milan = false
|
||||||
}
|
}
|
||||||
|
|
||||||
avb.properties.rules = [
|
avb.properties.rules = [
|
||||||
|
|
|
||||||
|
|
@ -1056,6 +1056,44 @@ wait_started (GstPipeWireSrc *this)
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static enum pw_stream_state
|
||||||
|
wait_negotiated (GstPipeWireSrc *this)
|
||||||
|
{
|
||||||
|
enum pw_stream_state state;
|
||||||
|
const char *error = NULL;
|
||||||
|
struct timespec abstime;
|
||||||
|
|
||||||
|
pw_thread_loop_get_time (this->stream->core->loop, &abstime,
|
||||||
|
GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC);
|
||||||
|
|
||||||
|
while (TRUE) {
|
||||||
|
state = pw_stream_get_state (this->stream->pwstream, &error);
|
||||||
|
|
||||||
|
GST_DEBUG_OBJECT (this, "waiting for NEGOTIATED, now %s", pw_stream_state_as_string (state));
|
||||||
|
if (state == PW_STREAM_STATE_ERROR)
|
||||||
|
break;
|
||||||
|
if (this->flushing) {
|
||||||
|
state = PW_STREAM_STATE_ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this->negotiated)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (this->autoconnect) {
|
||||||
|
if (pw_thread_loop_timed_wait_full (this->stream->core->loop, &abstime) < 0) {
|
||||||
|
state = PW_STREAM_STATE_ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pw_thread_loop_wait (this->stream->core->loop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
GST_DEBUG_OBJECT (this, state != PW_STREAM_STATE_ERROR ? "got negotiated signal" : "error during negotiation");
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
|
gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
|
||||||
{
|
{
|
||||||
|
|
@ -1067,7 +1105,6 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
|
||||||
g_autoptr (GPtrArray) possible = NULL;
|
g_autoptr (GPtrArray) possible = NULL;
|
||||||
gboolean result = FALSE;
|
gboolean result = FALSE;
|
||||||
const char *error = NULL;
|
const char *error = NULL;
|
||||||
struct timespec abstime;
|
|
||||||
uint32_t target_id;
|
uint32_t target_id;
|
||||||
|
|
||||||
/* first see what is possible on our source pad */
|
/* first see what is possible on our source pad */
|
||||||
|
|
@ -1177,27 +1214,9 @@ gst_pipewire_src_negotiate (GstBaseSrc * basesrc)
|
||||||
(const struct spa_pod **)possible->pdata,
|
(const struct spa_pod **)possible->pdata,
|
||||||
possible->len);
|
possible->len);
|
||||||
|
|
||||||
pw_thread_loop_get_time (pwsrc->stream->core->loop, &abstime,
|
if (wait_negotiated(pwsrc) == PW_STREAM_STATE_ERROR)
|
||||||
GST_PIPEWIRE_DEFAULT_TIMEOUT * SPA_NSEC_PER_SEC);
|
|
||||||
|
|
||||||
while (TRUE) {
|
|
||||||
enum pw_stream_state state = pw_stream_get_state (pwsrc->stream->pwstream, &error);
|
|
||||||
|
|
||||||
GST_DEBUG_OBJECT (basesrc, "waiting for NEGOTIATED, now %s", pw_stream_state_as_string (state));
|
|
||||||
if (state == PW_STREAM_STATE_ERROR || pwsrc->flushing)
|
|
||||||
goto connect_error;
|
goto connect_error;
|
||||||
|
|
||||||
if (pwsrc->negotiated)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (pwsrc->autoconnect) {
|
|
||||||
if (pw_thread_loop_timed_wait_full (pwsrc->stream->core->loop, &abstime) < 0)
|
|
||||||
goto connect_error;
|
|
||||||
} else {
|
|
||||||
pw_thread_loop_wait (pwsrc->stream->core->loop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
negotiated_caps = g_steal_pointer (&pwsrc->caps);
|
negotiated_caps = g_steal_pointer (&pwsrc->caps);
|
||||||
pw_thread_loop_unlock (pwsrc->stream->core->loop);
|
pw_thread_loop_unlock (pwsrc->stream->core->loop);
|
||||||
|
|
||||||
|
|
@ -1724,12 +1743,21 @@ gst_pipewire_src_change_state (GstElement * element, GstStateChange transition)
|
||||||
break;
|
break;
|
||||||
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
|
||||||
/* uncork and start recording */
|
/* uncork and start recording */
|
||||||
|
GST_DEBUG_OBJECT (this, "activating stream");
|
||||||
|
|
||||||
pw_thread_loop_lock (this->stream->core->loop);
|
pw_thread_loop_lock (this->stream->core->loop);
|
||||||
pw_stream_set_active (this->stream->pwstream, true);
|
pw_stream_set_active (this->stream->pwstream, true);
|
||||||
|
/* if state have been paused for longer time, the underlying node might
|
||||||
|
* be moved from idle to suspended, which would mean format cleared via
|
||||||
|
* handle_format_change. Wait for new format to avoid basesrc calling
|
||||||
|
* create() and get not-negotiated error as response. */
|
||||||
|
if (wait_negotiated(this) == PW_STREAM_STATE_ERROR)
|
||||||
|
goto open_failed;
|
||||||
pw_thread_loop_unlock (this->stream->core->loop);
|
pw_thread_loop_unlock (this->stream->core->loop);
|
||||||
break;
|
break;
|
||||||
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
|
||||||
/* stop recording ASAP by corking */
|
/* stop recording ASAP by corking */
|
||||||
|
GST_DEBUG_OBJECT (this, "in-activating stream");
|
||||||
pw_thread_loop_lock (this->stream->core->loop);
|
pw_thread_loop_lock (this->stream->core->loop);
|
||||||
pw_stream_set_active (this->stream->pwstream, false);
|
pw_stream_set_active (this->stream->pwstream, false);
|
||||||
pw_thread_loop_unlock (this->stream->core->loop);
|
pw_thread_loop_unlock (this->stream->core->loop);
|
||||||
|
|
|
||||||
|
|
@ -736,8 +736,21 @@ if build_module_avb
|
||||||
'module-avb/acmp.c',
|
'module-avb/acmp.c',
|
||||||
'module-avb/aecp.c',
|
'module-avb/aecp.c',
|
||||||
'module-avb/aecp-aem.c',
|
'module-avb/aecp-aem.c',
|
||||||
|
'module-avb/aecp-aem-cmds-resps/cmd-available.c',
|
||||||
|
'module-avb/aecp-aem-cmds-resps/cmd-get-set-name.c',
|
||||||
|
'module-avb/aecp-aem-cmds-resps/cmd-get-set-clock-source.c',
|
||||||
|
'module-avb/aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.c',
|
||||||
|
'module-avb/aecp-aem-cmds-resps/cmd-deregister-unsolicited-notifications.c',
|
||||||
|
'module-avb/aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.c',
|
||||||
|
'module-avb/aecp-aem-cmds-resps/cmd-get-set-stream-format.c',
|
||||||
|
'module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c',
|
||||||
|
'module-avb/aecp-aem-cmds-resps/cmd-get-set-configuration.c',
|
||||||
|
'module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c',
|
||||||
|
'module-avb/aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.c',
|
||||||
|
'module-avb/aecp-aem-cmds-resps/reply-unsol-helpers.c',
|
||||||
'module-avb/es-builder.c',
|
'module-avb/es-builder.c',
|
||||||
'module-avb/avdecc.c',
|
'module-avb/avdecc.c',
|
||||||
|
'module-avb/descriptors.c',
|
||||||
'module-avb/maap.c',
|
'module-avb/maap.c',
|
||||||
'module-avb/mmrp.c',
|
'module-avb/mmrp.c',
|
||||||
'module-avb/mrp.c',
|
'module-avb/mrp.c',
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,8 @@
|
||||||
#include "msrp.h"
|
#include "msrp.h"
|
||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
|
#include "aecp-aem-descriptors.h"
|
||||||
|
#include "aecp-aem-state.h"
|
||||||
|
|
||||||
static const uint8_t mac[6] = AVB_BROADCAST_MAC;
|
static const uint8_t mac[6] = AVB_BROADCAST_MAC;
|
||||||
|
|
||||||
|
|
@ -77,12 +79,64 @@ static void pending_free(struct acmp *acmp, struct pending *p)
|
||||||
free(p);
|
free(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void pending_destroy(struct acmp *acmp)
|
||||||
|
{
|
||||||
|
struct pending *p, *t;
|
||||||
|
for (uint32_t list_id = 0; list_id < PENDING_CONTROLLER; list_id++) {
|
||||||
|
spa_list_for_each_safe(p, t, &acmp->pending[list_id], link) {
|
||||||
|
pending_free(acmp, p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct msg_info {
|
struct msg_info {
|
||||||
uint16_t type;
|
uint16_t type;
|
||||||
const char *name;
|
const char *name;
|
||||||
int (*handle) (struct acmp *acmp, uint64_t now, const void *m, int len);
|
int (*handle) (struct acmp *acmp, uint64_t now, const void *m, int len);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static struct stream *find_stream(struct server *server, enum spa_direction direction,
|
||||||
|
uint16_t index)
|
||||||
|
{
|
||||||
|
uint16_t type;
|
||||||
|
struct descriptor *desc;
|
||||||
|
struct stream *stream;
|
||||||
|
|
||||||
|
switch (direction) {
|
||||||
|
case SPA_DIRECTION_INPUT:
|
||||||
|
type = AVB_AEM_DESC_STREAM_INPUT;
|
||||||
|
break;
|
||||||
|
case SPA_DIRECTION_OUTPUT:
|
||||||
|
type = AVB_AEM_DESC_STREAM_OUTPUT;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pw_log_error("Unkown direction\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
desc = server_find_descriptor(server, type, index);
|
||||||
|
if (!desc) {
|
||||||
|
pw_log_error("Could not find stream type %u index %u\n",
|
||||||
|
type, index);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (direction) {
|
||||||
|
case SPA_DIRECTION_INPUT:
|
||||||
|
struct aecp_aem_stream_input_state *stream_in;
|
||||||
|
stream_in = desc->ptr;
|
||||||
|
stream = &stream_in->stream;
|
||||||
|
break;
|
||||||
|
case SPA_DIRECTION_OUTPUT:
|
||||||
|
struct aecp_aem_stream_output_state *stream_out;
|
||||||
|
stream_out = desc->ptr;
|
||||||
|
stream = &stream_out->stream;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
static int reply_not_supported(struct acmp *acmp, uint8_t type, const void *m, int len)
|
static int reply_not_supported(struct acmp *acmp, uint8_t type, const void *m, int len)
|
||||||
{
|
{
|
||||||
struct server *server = acmp->server;
|
struct server *server = acmp->server;
|
||||||
|
|
@ -120,8 +174,7 @@ static int handle_connect_tx_command(struct acmp *acmp, uint64_t now, const void
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
memcpy(buf, m, len);
|
memcpy(buf, m, len);
|
||||||
stream = server_find_stream(server, SPA_DIRECTION_OUTPUT,
|
stream = find_stream(server, SPA_DIRECTION_OUTPUT, ntohs(reply->talker_unique_id));
|
||||||
reply->talker_unique_id);
|
|
||||||
if (stream == NULL) {
|
if (stream == NULL) {
|
||||||
status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX;
|
status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX;
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -130,7 +183,7 @@ static int handle_connect_tx_command(struct acmp *acmp, uint64_t now, const void
|
||||||
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE);
|
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_TX_RESPONSE);
|
||||||
reply->stream_id = htobe64(stream->id);
|
reply->stream_id = htobe64(stream->id);
|
||||||
|
|
||||||
stream_activate(stream, now);
|
stream_activate(stream, ntohs(reply->talker_unique_id), now);
|
||||||
|
|
||||||
memcpy(reply->stream_dest_mac, stream->addr, 6);
|
memcpy(reply->stream_dest_mac, stream->addr, 6);
|
||||||
reply->connection_count = htons(1);
|
reply->connection_count = htons(1);
|
||||||
|
|
@ -169,14 +222,13 @@ static int handle_connect_tx_response(struct acmp *acmp, uint64_t now, const voi
|
||||||
reply->sequence_id = htons(pending->old_sequence_id);
|
reply->sequence_id = htons(pending->old_sequence_id);
|
||||||
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE);
|
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_CONNECT_RX_RESPONSE);
|
||||||
|
|
||||||
stream = server_find_stream(server, SPA_DIRECTION_INPUT,
|
stream = find_stream(server, SPA_DIRECTION_INPUT, ntohs(reply->listener_unique_id));
|
||||||
ntohs(reply->listener_unique_id));
|
|
||||||
if (stream == NULL)
|
if (stream == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
stream->peer_id = be64toh(reply->stream_id);
|
stream->peer_id = be64toh(reply->stream_id);
|
||||||
memcpy(stream->addr, reply->stream_dest_mac, 6);
|
memcpy(stream->addr, reply->stream_dest_mac, 6);
|
||||||
stream_activate(stream, now);
|
stream_activate(stream, ntohs(reply->listener_unique_id), now);
|
||||||
|
|
||||||
res = avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, pending->size);
|
res = avb_server_send_packet(server, h->dest, AVB_TSN_ETH, h, pending->size);
|
||||||
|
|
||||||
|
|
@ -199,8 +251,7 @@ static int handle_disconnect_tx_command(struct acmp *acmp, uint64_t now, const v
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
memcpy(buf, m, len);
|
memcpy(buf, m, len);
|
||||||
stream = server_find_stream(server, SPA_DIRECTION_OUTPUT,
|
stream = find_stream(server, SPA_DIRECTION_OUTPUT, ntohs(reply->talker_unique_id));
|
||||||
reply->talker_unique_id);
|
|
||||||
if (stream == NULL) {
|
if (stream == NULL) {
|
||||||
status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX;
|
status = AVB_ACMP_STATUS_TALKER_NO_STREAM_INDEX;
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -243,8 +294,7 @@ static int handle_disconnect_tx_response(struct acmp *acmp, uint64_t now, const
|
||||||
reply->sequence_id = htons(pending->old_sequence_id);
|
reply->sequence_id = htons(pending->old_sequence_id);
|
||||||
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE);
|
AVB_PACKET_ACMP_SET_MESSAGE_TYPE(reply, AVB_ACMP_MESSAGE_TYPE_DISCONNECT_RX_RESPONSE);
|
||||||
|
|
||||||
stream = server_find_stream(server, SPA_DIRECTION_INPUT,
|
stream = find_stream(server, SPA_DIRECTION_INPUT, ntohs(reply->listener_unique_id));
|
||||||
reply->listener_unique_id);
|
|
||||||
if (stream == NULL)
|
if (stream == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
|
@ -369,6 +419,7 @@ static void acmp_destroy(void *data)
|
||||||
{
|
{
|
||||||
struct acmp *acmp = data;
|
struct acmp *acmp = data;
|
||||||
spa_hook_remove(&acmp->server_listener);
|
spa_hook_remove(&acmp->server_listener);
|
||||||
|
pending_destroy(acmp);
|
||||||
free(acmp);
|
free(acmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,7 +156,14 @@ static int adp_message(void *data, uint64_t now, const void *message, int len)
|
||||||
static void adp_destroy(void *data)
|
static void adp_destroy(void *data)
|
||||||
{
|
{
|
||||||
struct adp *adp = data;
|
struct adp *adp = data;
|
||||||
|
struct entity *e, *t;
|
||||||
|
|
||||||
spa_hook_remove(&adp->server_listener);
|
spa_hook_remove(&adp->server_listener);
|
||||||
|
|
||||||
|
spa_list_for_each_safe(e, t, &adp->entities, link) {
|
||||||
|
entity_free(e);
|
||||||
|
}
|
||||||
|
|
||||||
free(adp);
|
free(adp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
68
src/modules/module-avb/aecp-aem-cmds-resps/cmd-available.c
Normal file
68
src/modules/module-avb/aecp-aem-cmds-resps/cmd-available.c
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "../aecp-aem.h"
|
||||||
|
#include "../aecp-aem-state.h"
|
||||||
|
#include "../aecp-aem-descriptors.h"
|
||||||
|
#include "../aecp-aem-types.h"
|
||||||
|
|
||||||
|
#include "cmd-resp-helpers.h"
|
||||||
|
#include "cmd-available.h"
|
||||||
|
|
||||||
|
/* ENTITY AVAILABLE according to the locking state */
|
||||||
|
#define AECP_AEM_AVAIL_ENTITY_ACQUIRED (1<<0)
|
||||||
|
#define AECP_AEM_AVAIL_ENTITY_LOCKED (1<<1)
|
||||||
|
#define AECP_AEM_AVAIL_SUBENTITY_ACQUIRED (1<<2)
|
||||||
|
#define AECP_AEM_AVAIL_SUBENTITY_LOCKED (1<<3)
|
||||||
|
|
||||||
|
int handle_cmd_entity_available_milan_v12(struct aecp *aecp, int64_t now, const void *m,
|
||||||
|
int len)
|
||||||
|
{
|
||||||
|
uint8_t buf[512];
|
||||||
|
|
||||||
|
/* Commnand received specific */
|
||||||
|
struct server *server = aecp->server;
|
||||||
|
const struct avb_ethernet_header *h = m;
|
||||||
|
struct avb_ethernet_header *h_reply;
|
||||||
|
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
|
||||||
|
/* Reply specific */
|
||||||
|
struct avb_packet_aecp_aem *p_reply;
|
||||||
|
struct avb_packet_aecp_aem_available *avail_reply;
|
||||||
|
|
||||||
|
/* Entity specific */
|
||||||
|
struct descriptor *desc;
|
||||||
|
struct aecp_aem_entity_milan_state *entity_state;
|
||||||
|
struct aecp_aem_lock_state *lock;
|
||||||
|
|
||||||
|
desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0);
|
||||||
|
if (desc == NULL) {
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
entity_state = desc->ptr;
|
||||||
|
lock = &entity_state->state.lock_state;
|
||||||
|
|
||||||
|
/* Forge the response for the entity that is locking the device */
|
||||||
|
memcpy(buf, m, len);
|
||||||
|
h_reply = (struct avb_ethernet_header *) buf;
|
||||||
|
p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void);
|
||||||
|
avail_reply = (struct avb_packet_aecp_aem_available*)p_reply->payload;
|
||||||
|
|
||||||
|
avail_reply->acquired_controller_guid = 0;
|
||||||
|
|
||||||
|
if ((lock->base_info.expire_timeout < now) || !lock->is_locked) {
|
||||||
|
avail_reply->flags = 0;
|
||||||
|
} else if (lock->is_locked) {
|
||||||
|
avail_reply->lock_controller_guid = htobe64(lock->locked_id);
|
||||||
|
avail_reply->flags = htonl(AECP_AEM_AVAIL_ENTITY_LOCKED);
|
||||||
|
}
|
||||||
|
|
||||||
|
return reply_success(aecp, buf, len);
|
||||||
|
}
|
||||||
16
src/modules/module-avb/aecp-aem-cmds-resps/cmd-available.h
Normal file
16
src/modules/module-avb/aecp-aem-cmds-resps/cmd-available.h
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
#ifndef __AVB_AECP_AEM_AVAILABLE_H__
|
||||||
|
#define __AVB_AECP_AEM_AVAILABLE_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Milan V1.2 implementation to handle available command.
|
||||||
|
*/
|
||||||
|
int handle_cmd_entity_available_milan_v12(struct aecp *aecp, int64_t now, const void *m,
|
||||||
|
int len);
|
||||||
|
|
||||||
|
#endif //__AVB_AECP_AEM_AVAILABLE_H__
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#include <pipewire/log.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "../aecp-aem.h"
|
||||||
|
#include "../aecp-aem-state.h"
|
||||||
|
#include "../aecp-aem-milan.h"
|
||||||
|
|
||||||
|
#include "cmd-deregister-unsolicited-notifications.h"
|
||||||
|
#include "cmd-resp-helpers.h"
|
||||||
|
|
||||||
|
int handle_cmd_deregister_unsol_notif_milan_v12(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
const struct avb_ethernet_header *h = m;
|
||||||
|
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
struct descriptor *desc;
|
||||||
|
struct aecp_aem_entity_milan_state *entity_state;
|
||||||
|
struct aecp_aem_unsol_notification_state *unsol;
|
||||||
|
uint64_t controller_id = htobe64(p->aecp.controller_guid);
|
||||||
|
const uint32_t ctrler_max = AECP_AEM_MILAN_MAX_CONTROLLER;
|
||||||
|
uint16_t index;
|
||||||
|
|
||||||
|
desc = server_find_descriptor(aecp->server, AVB_AEM_DESC_ENTITY, 0);
|
||||||
|
if (desc == NULL)
|
||||||
|
return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
|
||||||
|
|
||||||
|
entity_state = (struct aecp_aem_entity_milan_state *) desc->ptr;
|
||||||
|
unsol = entity_state->unsol_notif_state;
|
||||||
|
|
||||||
|
/** First check if the controller was already registered */
|
||||||
|
for (index = 0; index < ctrler_max; index++) {
|
||||||
|
uint64_t ctrl_eid = unsol[index].ctrler_entity_id;
|
||||||
|
bool ctrler_reged = unsol[index].is_registered;
|
||||||
|
|
||||||
|
if ((ctrl_eid == controller_id) && ctrler_reged) {
|
||||||
|
pw_log_debug("controller 0x%"PRIx64", already registered",
|
||||||
|
controller_id);
|
||||||
|
return reply_success(aecp, m, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** When one slot is in the array is available use it */
|
||||||
|
for (index = 0; index < ctrler_max; index++) {
|
||||||
|
if (!unsol[index].is_registered) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reach the maximum controller allocated */
|
||||||
|
if (index == ctrler_max) {
|
||||||
|
return reply_no_resources(aecp, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsol[index].ctrler_entity_id = controller_id;
|
||||||
|
memcpy(unsol[index].ctrler_mac_addr, h->src, sizeof(h->src));
|
||||||
|
unsol[index].is_registered = true;
|
||||||
|
unsol[index].port_id = 0;
|
||||||
|
unsol[index].next_seq_id = 0;
|
||||||
|
|
||||||
|
pw_log_info("Unsol registration for 0x%"PRIx64, controller_id);
|
||||||
|
return reply_success(aecp, m, len);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#ifndef __AVB_DEREGISTER_UNSOLICITED_NOTIFICATIONS_H__
|
||||||
|
#define __AVB_DEREGISTER_UNSOLICITED_NOTIFICATIONS_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Command handling will generate the response to the
|
||||||
|
* received command when deregistering the a controller from
|
||||||
|
* the list of subscribed controller entities.
|
||||||
|
*
|
||||||
|
* @see IEEE1722.1-2021 Section 7.4.38 .
|
||||||
|
* @see Milan V1.2 Section 5.4.2.22.
|
||||||
|
*/
|
||||||
|
int handle_cmd_deregister_unsol_notif_milan_v12(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len);
|
||||||
|
|
||||||
|
#endif // __AVB_DEREGISTER_UNSOLICITED_NOTIFICATIONS_H__
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "../aecp-aem.h"
|
||||||
|
#include "../aecp-aem-state.h"
|
||||||
|
#include "../descriptors.h"
|
||||||
|
#include "../aecp-aem-types.h"
|
||||||
|
|
||||||
|
#include "cmd-resp-helpers.h"
|
||||||
|
#include "reply-unsol-helpers.h"
|
||||||
|
#include "cmd-get-set-clock-source.h"
|
||||||
|
|
||||||
|
static int reply_invalid_clock_source(struct aecp *aecp,
|
||||||
|
struct avb_aem_desc_clock_domain *desc, const void *m, int len)
|
||||||
|
{
|
||||||
|
uint8_t buf[128];
|
||||||
|
struct avb_ethernet_header *h = (struct avb_ethernet_header *)buf;
|
||||||
|
struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
struct avb_packet_aecp_aem_setget_clock_source *sclk_source;
|
||||||
|
|
||||||
|
memcpy(buf, m, len);
|
||||||
|
sclk_source =
|
||||||
|
(struct avb_packet_aecp_aem_setget_clock_source *) p->payload;
|
||||||
|
|
||||||
|
/** The descriptor keep the network endianess */
|
||||||
|
sclk_source->clock_source_index = desc->clock_source_index;
|
||||||
|
|
||||||
|
// Reply success with the old value which is the current if it fails.
|
||||||
|
return reply_success(aecp, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_unsol_set_clock_source(struct aecp *aecp, struct descriptor *desc,
|
||||||
|
const void *m, int len, uint64_t ctrler_id)
|
||||||
|
{
|
||||||
|
uint8_t buf[128];
|
||||||
|
struct aecp_aem_base_info bi = { 0 };
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
memcpy(buf, m, len);
|
||||||
|
bi.controller_entity_id = htobe64(ctrler_id);
|
||||||
|
bi.expire_timeout = INT64_MAX;
|
||||||
|
|
||||||
|
rc = reply_unsolicited_notifications(aecp, &bi, buf, len, false);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \see IEEE 1722.1-2021, 7.4.24. SET_CLOCK_SOURCE Command
|
||||||
|
* \see Milan V1.2 5.4.2.15
|
||||||
|
* \todo verify if this is valid for AVB
|
||||||
|
*/
|
||||||
|
int handle_cmd_get_clock_source_milan_v12(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
uint8_t buf[128];
|
||||||
|
struct avb_ethernet_header *h = (struct avb_ethernet_header *) buf;
|
||||||
|
struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
struct avb_packet_aecp_aem_setget_clock_source *sclk_source;
|
||||||
|
struct avb_aem_desc_clock_domain* dclk_domain;
|
||||||
|
struct descriptor *desc;
|
||||||
|
uint16_t desc_index;
|
||||||
|
uint16_t desc_type;
|
||||||
|
|
||||||
|
memcpy(buf, m, len);
|
||||||
|
sclk_source =
|
||||||
|
(struct avb_packet_aecp_aem_setget_clock_source *) p->payload;
|
||||||
|
|
||||||
|
desc_index = htons(sclk_source->descriptor_id);
|
||||||
|
desc_type = htons(sclk_source->descriptor_id);
|
||||||
|
|
||||||
|
desc = server_find_descriptor(aecp->server, desc_type, desc_index);
|
||||||
|
if (desc == NULL)
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
|
||||||
|
|
||||||
|
dclk_domain = (struct avb_aem_desc_clock_domain*) desc->ptr;
|
||||||
|
|
||||||
|
/** Descriptors always keep the network endianness */
|
||||||
|
sclk_source->clock_source_index = dclk_domain->clock_source_index;
|
||||||
|
|
||||||
|
len = sizeof(*p) + sizeof(*sclk_source) + sizeof(*h);
|
||||||
|
return reply_success(aecp, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \see IEEE 1722.1-2021, 7.4.23. SET_CLOCK_SOURCE Command
|
||||||
|
* \see Milan V1.2 5.4.2.15
|
||||||
|
* \todo verify if this is valid for AVB
|
||||||
|
*/
|
||||||
|
int handle_cmd_set_clock_source_milan_v12(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
int rc;
|
||||||
|
struct server *server = aecp->server;
|
||||||
|
const struct avb_ethernet_header *h = m;
|
||||||
|
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
struct avb_packet_aecp_aem_setget_clock_source *sclk_source;
|
||||||
|
/** Information in the packet */
|
||||||
|
uint16_t desc_type;
|
||||||
|
uint16_t desc_index;
|
||||||
|
uint16_t clock_src_index;
|
||||||
|
uint64_t ctrlr_id;
|
||||||
|
|
||||||
|
/*Information about the system */
|
||||||
|
struct descriptor *desc;
|
||||||
|
struct avb_aem_desc_clock_domain* dclk_domain;
|
||||||
|
|
||||||
|
sclk_source =
|
||||||
|
(struct avb_packet_aecp_aem_setget_clock_source *) p->payload;
|
||||||
|
|
||||||
|
desc_type = ntohs(sclk_source->descriptor_type);
|
||||||
|
desc_index = ntohs(sclk_source->descriptor_id);
|
||||||
|
clock_src_index = ntohs(sclk_source->clock_source_index);
|
||||||
|
ctrlr_id = htobe64(p->aecp.controller_guid);
|
||||||
|
|
||||||
|
/** Retrieve the descriptor */
|
||||||
|
desc = server_find_descriptor(server, desc_type, desc_index);
|
||||||
|
if (desc == NULL)
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
|
||||||
|
|
||||||
|
dclk_domain = (struct avb_aem_desc_clock_domain *) desc->ptr;
|
||||||
|
if (clock_src_index >= dclk_domain->clock_sources_count) {
|
||||||
|
return reply_invalid_clock_source(aecp, dclk_domain, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Descriptor always keep the network endianness */
|
||||||
|
dclk_domain->clock_source_index = htons(clock_src_index);
|
||||||
|
rc = reply_success(aecp, m, len);
|
||||||
|
if (rc) {
|
||||||
|
pw_log_error("Reply failed for set_clock_source\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle_unsol_set_clock_source(aecp, desc, m, len, ctrlr_id);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#ifndef __AVB_AECP_AEM_CMD_GET_SET_CLOCK_SOURCE_H__
|
||||||
|
#define __AVB_AECP_AEM_CMD_GET_SET_CLOCK_SOURCE_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
int handle_cmd_set_clock_source_milan_v12(struct aecp *aecp, int64_t now, const void *m, int len);
|
||||||
|
int handle_cmd_get_clock_source_milan_v12(struct aecp *aecp, int64_t now, const void *m, int len);
|
||||||
|
|
||||||
|
#endif /* __AVB_AECP_AEM_CMD_GET_SET_CLOCK_SOURCE_H__ */
|
||||||
|
|
@ -0,0 +1,194 @@
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "../aecp-aem.h"
|
||||||
|
#include "../aecp-aem-state.h"
|
||||||
|
#include "../aecp-aem-descriptors.h"
|
||||||
|
#include "../aecp-aem-types.h"
|
||||||
|
|
||||||
|
#include "cmd-get-set-configuration.h"
|
||||||
|
#include "cmd-resp-helpers.h"
|
||||||
|
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
static int handle_unsol_set_configuration_milan_v12(struct aecp *aecp, struct descriptor *desc,
|
||||||
|
uint64_t ctrler_id)
|
||||||
|
{
|
||||||
|
/* Reply */
|
||||||
|
uint8_t buf[512];
|
||||||
|
void *m = buf;
|
||||||
|
struct avb_aem_desc_entity *entity_desc;
|
||||||
|
struct avb_ethernet_header *h = m;
|
||||||
|
struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
struct avb_packet_aecp_aem_setget_configuration *cfg;
|
||||||
|
size_t len = sizeof (*h) + sizeof(*p) + sizeof(*cfg);
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
entity_desc = (struct avb_aem_desc_entity*) desc->ptr;
|
||||||
|
cfg = (struct avb_packet_aecp_aem_setget_configuration *) p->payload;
|
||||||
|
cfg->configuration_index = htons(entity_desc->current_configuration);
|
||||||
|
p->aecp.target_guid = htobe64(aecp->server->entity_id);
|
||||||
|
|
||||||
|
AVB_PACKET_AEM_SET_COMMAND_TYPE(p, AVB_AECP_AEM_CMD_SET_CONFIGURATION);
|
||||||
|
rc = reply_unsolicited_notifications_ctrler_id(aecp, ctrler_id,
|
||||||
|
buf, len, false);
|
||||||
|
|
||||||
|
if (rc) {
|
||||||
|
pw_log_error("unsol notif failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common handler for SET_CONFIGURATION command
|
||||||
|
*
|
||||||
|
* Milan v1.2, Sec. 5.4.2.5
|
||||||
|
* IEEE 1722.1-2021, Sec. 7.4.7
|
||||||
|
*/
|
||||||
|
int handle_cmd_set_configuration_milan_v12(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
uint8_t buf[2048];
|
||||||
|
struct server *server = aecp->server;
|
||||||
|
const struct avb_ethernet_header *h = m;
|
||||||
|
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
/* Reply */
|
||||||
|
struct avb_ethernet_header *h_reply;
|
||||||
|
struct avb_packet_aecp_aem *p_reply;
|
||||||
|
struct avb_packet_aecp_aem_setget_configuration *cfg;
|
||||||
|
|
||||||
|
/* Information about the current entity */
|
||||||
|
struct avb_aem_desc_entity *entity_desc;
|
||||||
|
uint16_t req_cfg_id, cur_cfg_id, cfg_count;
|
||||||
|
struct descriptor *desc;
|
||||||
|
int rc;
|
||||||
|
bool has_failed;
|
||||||
|
|
||||||
|
/* FIXME ACMP: IMPORTANT!!!! find the stream connection information
|
||||||
|
* whether they are running or not. */
|
||||||
|
|
||||||
|
/* Milan v1.2, Sec. 5.4.2.5
|
||||||
|
* The PAAD-AE shall not accept a SET_CONFIGURATION command if one of
|
||||||
|
* the Stream Input is bound or one of the Stream Output is streaming.
|
||||||
|
* In this case, the STREAM_IS_RUNNING error code shall be returned.
|
||||||
|
*
|
||||||
|
* If the PAAD-AE is locked by a controller, it shall not accept a
|
||||||
|
* SET_CONFIGURATION command from a different controller, and it shall
|
||||||
|
* also not change its current configuration by non-ATDECC
|
||||||
|
* means (proprietary remote control software, front-panel, ...).
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** WARNING! Milan forces only one entity */
|
||||||
|
desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0);
|
||||||
|
if (desc == NULL)
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
|
||||||
|
|
||||||
|
// TODO maybe avoid copy here
|
||||||
|
memcpy(buf, m, len);
|
||||||
|
h_reply = (struct avb_ethernet_header *)buf;
|
||||||
|
p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void);
|
||||||
|
|
||||||
|
cfg = (struct avb_packet_aecp_aem_setget_configuration*) p_reply->payload;
|
||||||
|
entity_desc = (struct avb_aem_desc_entity*) desc->ptr;
|
||||||
|
cur_cfg_id = ntohs(entity_desc->current_configuration);
|
||||||
|
req_cfg_id = ntohs(cfg->configuration_index);
|
||||||
|
cfg_count = ntohs(entity_desc->configurations_count);
|
||||||
|
|
||||||
|
if (entity_desc->entity_id != p->aecp.target_guid) {
|
||||||
|
pw_log_error("Invalid entity id");
|
||||||
|
has_failed = true;
|
||||||
|
/* TODO: req_cfg_id is zero based, cfg_count is not.
|
||||||
|
* Should be req_cfg_id >= cfg_count */
|
||||||
|
} else if (req_cfg_id >= cfg_count) {
|
||||||
|
pw_log_error("Requested %u, but has max %u id",
|
||||||
|
req_cfg_id, cfg_count);
|
||||||
|
has_failed = true;
|
||||||
|
} else if (req_cfg_id == cur_cfg_id) {
|
||||||
|
pw_log_warn("requested %u and same current %u id", req_cfg_id,
|
||||||
|
cur_cfg_id);
|
||||||
|
has_failed = true;
|
||||||
|
} else {
|
||||||
|
entity_desc->current_configuration = cfg->configuration_index;
|
||||||
|
has_failed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Always contains the current value,
|
||||||
|
* that is itcontains the new value if the command succeeds or the old
|
||||||
|
* value if it fails.
|
||||||
|
*/
|
||||||
|
if (has_failed) {
|
||||||
|
cfg->configuration_index = entity_desc->current_configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = reply_success(aecp, buf, len);
|
||||||
|
if (rc) {
|
||||||
|
pw_log_error("Reply Failed");
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
if(!has_failed) {
|
||||||
|
return handle_unsol_set_configuration_milan_v12(aecp, desc,
|
||||||
|
tobe64(p->aecp.controller_guid));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common handler for GET_CONFIGURATION command
|
||||||
|
* Milan v1.2, Sec. 5.4.2.6
|
||||||
|
* IEEE 1722.1-2021, Sec. 7.4.8
|
||||||
|
*/
|
||||||
|
int handle_cmd_get_configuration_common(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
uint8_t buf[2048];
|
||||||
|
struct server *server = aecp->server;
|
||||||
|
const struct avb_ethernet_header *h = m;
|
||||||
|
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
/* Reply */
|
||||||
|
struct avb_ethernet_header *h_reply;
|
||||||
|
struct avb_packet_aecp_aem *p_reply;
|
||||||
|
struct avb_packet_aecp_aem_setget_configuration *cfg;
|
||||||
|
|
||||||
|
/* Information about the current entity */
|
||||||
|
struct avb_aem_desc_entity *entity_desc;
|
||||||
|
struct descriptor *desc;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
desc = server_find_descriptor(server, AVB_AEM_DESC_ENTITY, 0);
|
||||||
|
if (desc == NULL)
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
|
||||||
|
|
||||||
|
memcpy(buf, m, len);
|
||||||
|
h_reply = (struct avb_ethernet_header *)buf;
|
||||||
|
p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void);
|
||||||
|
|
||||||
|
cfg = (struct avb_packet_aecp_aem_setget_configuration*) p_reply->payload;
|
||||||
|
entity_desc = (struct avb_aem_desc_entity*) desc->ptr;
|
||||||
|
|
||||||
|
if (entity_desc->entity_id != p->aecp.target_guid) {
|
||||||
|
pw_log_error("Invalid entity id");
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg->configuration_index = entity_desc->current_configuration;
|
||||||
|
|
||||||
|
rc = reply_success(aecp, buf, len);
|
||||||
|
if (rc) {
|
||||||
|
pw_log_error("Reply Failed");
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
#ifndef __AECP_AEM_CMD_GET_SET_CONFIGURATION_H__
|
||||||
|
#define __AECP_AEM_CMD_GET_SET_CONFIGURATION_H__
|
||||||
|
|
||||||
|
int handle_cmd_set_configuration_milan_v12(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len);
|
||||||
|
|
||||||
|
int handle_cmd_get_configuration_common(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len);
|
||||||
|
|
||||||
|
#endif //__AECP_AEM_CMD_GET_SET_CONFIGURATION_H__
|
||||||
183
src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-name.c
Normal file
183
src/modules/module-avb/aecp-aem-cmds-resps/cmd-get-set-name.c
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include "../aecp.h"
|
||||||
|
#include "../aecp-aem.h"
|
||||||
|
#include "../aecp-aem-state.h"
|
||||||
|
#include "../aecp-aem-descriptors.h"
|
||||||
|
|
||||||
|
|
||||||
|
#include "cmd-get-set-name.h"
|
||||||
|
#include "cmd-resp-helpers.h"
|
||||||
|
#include "reply-unsol-helpers.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Different descriptor hold different position for a name
|
||||||
|
* Therefore different descriptors should be handled diffierently.
|
||||||
|
*/
|
||||||
|
static char *get_name_ptr(uint16_t desc_type, void *ptr, uint16_t name_index)
|
||||||
|
{
|
||||||
|
/* Handle Entity specifically due to multiple name fields */
|
||||||
|
if (desc_type == AVB_AEM_DESC_ENTITY) {
|
||||||
|
struct avb_aem_desc_entity *d = ptr;
|
||||||
|
/*
|
||||||
|
* IEEE 1722.1-2021 Table 7-38:
|
||||||
|
* 0: entity_name
|
||||||
|
* 1: group_name
|
||||||
|
* 2: serial_number
|
||||||
|
*/
|
||||||
|
if (name_index == 0) return d->entity_name;
|
||||||
|
if (name_index == 1) return d->group_name;
|
||||||
|
if (name_index == 2) return d->serial_number;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle Strings descriptor */
|
||||||
|
if (desc_type == AVB_AEM_DESC_STRINGS) {
|
||||||
|
if (name_index > 6)
|
||||||
|
return NULL;
|
||||||
|
return (char *)ptr + (name_index * 64);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Case when the name index should be forcibly 0 */
|
||||||
|
if (name_index != 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Exclude descriptors that do not start with object_name */
|
||||||
|
switch (desc_type) {
|
||||||
|
case AVB_AEM_DESC_STREAM_PORT_INPUT:
|
||||||
|
case AVB_AEM_DESC_STREAM_PORT_OUTPUT:
|
||||||
|
case AVB_AEM_DESC_EXTERNAL_PORT_INPUT:
|
||||||
|
case AVB_AEM_DESC_EXTERNAL_PORT_OUTPUT:
|
||||||
|
case AVB_AEM_DESC_INTERNAL_PORT_INPUT:
|
||||||
|
case AVB_AEM_DESC_INTERNAL_PORT_OUTPUT:
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Most others (Configuration, Audio Unit, Stream Input/Output, AVB Interface,
|
||||||
|
* Clock Source, etc.) start with object_name[64] at offset 0.
|
||||||
|
*/
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int send_unsol_name(struct aecp *aecp,
|
||||||
|
const struct avb_packet_aecp_aem *p, const void *msg, int len)
|
||||||
|
{
|
||||||
|
uint8_t unsol_buf[512];
|
||||||
|
struct avb_ethernet_header *h_unsol = (void*)unsol_buf;
|
||||||
|
struct aecp_aem_base_info info = { 0 };
|
||||||
|
|
||||||
|
memcpy(unsol_buf, msg, len);
|
||||||
|
info.controller_entity_id = htobe64(p->aecp.controller_guid);
|
||||||
|
info.expire_timeout = INT64_MAX;
|
||||||
|
|
||||||
|
return reply_unsolicited_notifications(aecp, &info, unsol_buf, len, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IEEE 1722.1-2021 7.4.18 GET_NAME
|
||||||
|
* For now this is not handling UTF characters, only ASCII
|
||||||
|
*/
|
||||||
|
int handle_cmd_get_name_common(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
uint8_t buf[512];
|
||||||
|
struct server *server = aecp->server;
|
||||||
|
const struct avb_ethernet_header *h = m;
|
||||||
|
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
const struct avb_packet_aecp_aem_setget_name *cmd;
|
||||||
|
struct avb_ethernet_header *h_reply;
|
||||||
|
struct avb_packet_aecp_aem *p_reply;
|
||||||
|
struct avb_packet_aecp_aem_setget_name *reply;
|
||||||
|
struct descriptor *desc;
|
||||||
|
uint16_t desc_type, desc_id, name_index;
|
||||||
|
char *name_ptr;
|
||||||
|
|
||||||
|
cmd = (const struct avb_packet_aecp_aem_setget_name *)p->payload;
|
||||||
|
desc_type = ntohs(cmd->descriptor_type);
|
||||||
|
desc_id = ntohs(cmd->descriptor_index);
|
||||||
|
name_index = ntohs(cmd->name_index);
|
||||||
|
|
||||||
|
desc = server_find_descriptor(server, desc_type, desc_id);
|
||||||
|
if (desc == NULL)
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
|
||||||
|
|
||||||
|
name_ptr = get_name_ptr(desc_type, desc->ptr, name_index);
|
||||||
|
if (name_ptr == NULL)
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_BAD_ARGUMENTS, m, len);
|
||||||
|
|
||||||
|
memcpy(buf, m, len);
|
||||||
|
h_reply = (struct avb_ethernet_header *)buf;
|
||||||
|
p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void);
|
||||||
|
reply = (struct avb_packet_aecp_aem_setget_name *)p_reply->payload;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IEEE 1722.1-2021: 7.4.17.1: The name does not contain a trailing NULL
|
||||||
|
* but if the name is less than 64 bytes in length then it is zero
|
||||||
|
* padded
|
||||||
|
*/
|
||||||
|
memcpy(reply->name, name_ptr, 64);
|
||||||
|
|
||||||
|
return reply_success(aecp, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IEEE 1722.1-2021 7.4.17 SET_NAME
|
||||||
|
* For now this is not handling UTF characters, only ASCII
|
||||||
|
*/
|
||||||
|
int handle_cmd_set_name_common(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
struct server *server = aecp->server;
|
||||||
|
const struct avb_ethernet_header *h = m;
|
||||||
|
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
const struct avb_packet_aecp_aem_setget_name *cmd;
|
||||||
|
struct descriptor *desc;
|
||||||
|
uint16_t desc_type, desc_id, name_index;
|
||||||
|
char *name_ptr;
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
cmd = (const struct avb_packet_aecp_aem_setget_name *)p->payload;
|
||||||
|
desc_type = ntohs(cmd->descriptor_type);
|
||||||
|
desc_id = ntohs(cmd->descriptor_index);
|
||||||
|
name_index = ntohs(cmd->name_index);
|
||||||
|
|
||||||
|
desc = server_find_descriptor(server, desc_type, desc_id);
|
||||||
|
if (desc == NULL)
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
|
||||||
|
|
||||||
|
name_ptr = get_name_ptr(desc_type, desc->ptr, name_index);
|
||||||
|
if (name_ptr == NULL)
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_BAD_ARGUMENTS, m, len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IEEE 1722.1-2021: 7.4.17.1: The name does not contain a trailing NULL
|
||||||
|
* but if the name is less than 64 bytes in length then it is zero
|
||||||
|
* padded
|
||||||
|
*/
|
||||||
|
memcpy(name_ptr, cmd->name, 64);
|
||||||
|
|
||||||
|
/** TODO: According to the specification, the string should alwasy be 0
|
||||||
|
* terminated, the goal would be to check whether a string is UTF-8 and
|
||||||
|
* that it is correctly zero terminitaed if less than 64 char, if not
|
||||||
|
* then a simple memcpy is enough */
|
||||||
|
|
||||||
|
rc = reply_success(aecp, m, len);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
return send_unsol_name(aecp, p, m, len);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#ifndef __AVB_AECP_AEM_CMD_GET_SET_NAME_H__
|
||||||
|
#define __AVB_AECP_AEM_CMD_GET_SET_NAME_H__
|
||||||
|
|
||||||
|
#include "../aecp.h"
|
||||||
|
|
||||||
|
int handle_cmd_set_name_common(struct aecp *aecp, int64_t now, const void *m, int len);
|
||||||
|
int handle_cmd_get_name_common(struct aecp *aecp, int64_t now, const void *m, int len);
|
||||||
|
|
||||||
|
#endif /* __AVB_AECP_AEM_CMD_GET_SET_NAME_H__ */
|
||||||
|
|
@ -0,0 +1,154 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "../aecp.h"
|
||||||
|
#include "../aecp-aem.h"
|
||||||
|
#include "../aecp-aem-state.h"
|
||||||
|
#include "../aecp-aem-descriptors.h"
|
||||||
|
#include "../aecp-aem-types.h"
|
||||||
|
|
||||||
|
#include "cmd-get-set-stream-format.h"
|
||||||
|
#include "cmd-resp-helpers.h"
|
||||||
|
#include "reply-unsol-helpers.h"
|
||||||
|
|
||||||
|
static int send_unsol_stream_format(struct aecp *aecp, void *msg, int len)
|
||||||
|
{
|
||||||
|
struct avb_ethernet_header *h_unsol = (void*)msg;
|
||||||
|
struct avb_packet_aecp_aem *p_unsol = SPA_PTROFF(h_unsol, sizeof(*h_unsol), void);
|
||||||
|
struct aecp_aem_base_info info = { 0 };
|
||||||
|
|
||||||
|
/* Set the originator controller ID to avoid echo */
|
||||||
|
info.controller_entity_id = htobe64(p_unsol->aecp.controller_guid);
|
||||||
|
info.expire_timeout = INT64_MAX;
|
||||||
|
|
||||||
|
return reply_unsolicited_notifications(aecp, &info, msg, len, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \see IEEE 1722.1-2021 7.4.10
|
||||||
|
* \see Milan V1.2 5.4.2.8 GET_STREAM_FORMAT
|
||||||
|
*/
|
||||||
|
int handle_cmd_get_stream_format_milan_v12(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
uint8_t buf[2048];
|
||||||
|
struct server *server = aecp->server;
|
||||||
|
const struct avb_ethernet_header *h = m;
|
||||||
|
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
const struct avb_packet_aecp_aem_setget_stream_format *get_cmd;
|
||||||
|
struct avb_ethernet_header *h_reply;
|
||||||
|
struct avb_packet_aecp_aem *p_reply;
|
||||||
|
struct avb_packet_aecp_aem_setget_stream_format *get_reply;
|
||||||
|
struct descriptor *desc;
|
||||||
|
struct avb_aem_desc_stream *stream_desc;
|
||||||
|
uint16_t desc_type, desc_id;
|
||||||
|
|
||||||
|
get_cmd = (const struct avb_packet_aecp_aem_setget_stream_format *)p->payload;
|
||||||
|
desc_type = ntohs(get_cmd->descriptor_type);
|
||||||
|
desc_id = ntohs(get_cmd->descriptor_id);
|
||||||
|
|
||||||
|
desc = server_find_descriptor(server, desc_type, desc_id);
|
||||||
|
if (desc == NULL)
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
|
||||||
|
|
||||||
|
if (desc_type != AVB_AEM_DESC_STREAM_INPUT &&
|
||||||
|
desc_type != AVB_AEM_DESC_STREAM_OUTPUT)
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_BAD_ARGUMENTS, m, len);
|
||||||
|
|
||||||
|
stream_desc = (struct avb_aem_desc_stream *)desc->ptr;
|
||||||
|
|
||||||
|
memcpy(buf, m, len);
|
||||||
|
h_reply = (struct avb_ethernet_header *)buf;
|
||||||
|
p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void);
|
||||||
|
get_reply = (struct avb_packet_aecp_aem_setget_stream_format *)p_reply->payload;
|
||||||
|
|
||||||
|
get_reply->stream_format = stream_desc->current_format;
|
||||||
|
|
||||||
|
return reply_success(aecp, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \see IEEE 1722.1-2021 7.4.9
|
||||||
|
* \see Milan V1.2 5.4.2.7 SET_STREAM_FORMAT
|
||||||
|
*/
|
||||||
|
int handle_cmd_set_stream_format_milan_v12(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
uint8_t buf[2048];
|
||||||
|
struct server *server = aecp->server;
|
||||||
|
const struct avb_ethernet_header *h = m;
|
||||||
|
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
const struct avb_packet_aecp_aem_setget_stream_format *set_cmd;
|
||||||
|
struct avb_ethernet_header *h_reply;
|
||||||
|
struct avb_packet_aecp_aem *p_reply;
|
||||||
|
struct avb_packet_aecp_aem_setget_stream_format *set_reply;
|
||||||
|
struct descriptor *desc;
|
||||||
|
struct avb_aem_desc_stream *stream_desc;
|
||||||
|
uint16_t desc_type, desc_id;
|
||||||
|
uint64_t new_format;
|
||||||
|
int i;
|
||||||
|
int rc;
|
||||||
|
bool found = false;
|
||||||
|
void *stream;
|
||||||
|
|
||||||
|
set_cmd = (const struct avb_packet_aecp_aem_setget_stream_format *)p->payload;
|
||||||
|
desc_type = ntohs(set_cmd->descriptor_type);
|
||||||
|
desc_id = ntohs(set_cmd->descriptor_id);
|
||||||
|
new_format = set_cmd->stream_format;
|
||||||
|
|
||||||
|
desc = server_find_descriptor(server, desc_type, desc_id);
|
||||||
|
if (desc == NULL)
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, m, len);
|
||||||
|
|
||||||
|
if (desc_type == AVB_AEM_DESC_STREAM_INPUT) {
|
||||||
|
struct aecp_aem_stream_input_state *state =
|
||||||
|
(struct aecp_aem_stream_input_state *)desc->ptr;
|
||||||
|
stream = &state->stream;
|
||||||
|
// TODO check if the stream is bound
|
||||||
|
} else if (desc_type == AVB_AEM_DESC_STREAM_OUTPUT) {
|
||||||
|
struct aecp_aem_stream_output_state *state =
|
||||||
|
(struct aecp_aem_stream_output_state *)desc->ptr;
|
||||||
|
stream = &state->stream;
|
||||||
|
// TODO check if the stream is STREAM_RUNNING
|
||||||
|
} else {
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_BAD_ARGUMENTS, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
(void)stream;
|
||||||
|
stream_desc = (struct avb_aem_desc_stream *)desc->ptr;
|
||||||
|
for (i = 0; i < ntohs(stream_desc->number_of_formats); i++) {
|
||||||
|
if (stream_desc->stream_formats[i] == new_format) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
memcpy(buf, m, len);
|
||||||
|
h_reply = (struct avb_ethernet_header *)buf;
|
||||||
|
p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void);
|
||||||
|
set_reply = (struct avb_packet_aecp_aem_setget_stream_format*)p_reply->payload;
|
||||||
|
|
||||||
|
/** If this not found, return the current format */
|
||||||
|
if (!found) {
|
||||||
|
set_reply->stream_format = stream_desc->current_format;
|
||||||
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_BAD_ARGUMENTS, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_desc->current_format = new_format;
|
||||||
|
rc = reply_success(aecp, buf, len);
|
||||||
|
if (rc < 0)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
return send_unsol_stream_format(aecp, buf, len);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#ifndef __AVB_AECP_AEM_CMD_GET_SET_STREAM_FORMAT_H__
|
||||||
|
#define __AVB_AECP_AEM_CMD_GET_SET_STREAM_FORMAT_H__
|
||||||
|
|
||||||
|
#include "../aecp-aem.h"
|
||||||
|
|
||||||
|
int handle_cmd_set_stream_format_milan_v12(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len);
|
||||||
|
|
||||||
|
int handle_cmd_get_stream_format_milan_v12(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len);
|
||||||
|
|
||||||
|
#endif //__AVB_AECP_AEM_CMD_GET_SET_STREAM_FORMAT_H__
|
||||||
180
src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c
Normal file
180
src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.c
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "../aecp.h"
|
||||||
|
#include "../aecp-aem.h"
|
||||||
|
#include "../aecp-aem-state.h"
|
||||||
|
#include "../aecp-aem-descriptors.h"
|
||||||
|
#include "../aecp-aem-types.h"
|
||||||
|
|
||||||
|
#include "cmd-lock-entity.h"
|
||||||
|
#include "cmd-resp-helpers.h"
|
||||||
|
|
||||||
|
#include "reply-unsol-helpers.h"
|
||||||
|
|
||||||
|
static int handle_unsol_lock_common(struct aecp *aecp,
|
||||||
|
struct aecp_aem_lock_state *lock, bool internal)
|
||||||
|
{
|
||||||
|
uint8_t buf[512];
|
||||||
|
void *m = buf;
|
||||||
|
// struct aecp_aem_regis_unsols
|
||||||
|
struct avb_ethernet_header *h = m;
|
||||||
|
struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
struct avb_packet_aecp_aem_lock *ae;
|
||||||
|
size_t len = sizeof(*h) + sizeof(*p) + sizeof(*ae);
|
||||||
|
int rc;
|
||||||
|
|
||||||
|
memset(buf, 0, sizeof(buf));
|
||||||
|
ae = (struct avb_packet_aecp_aem_lock*)p->payload;
|
||||||
|
|
||||||
|
if (!lock->is_locked) {
|
||||||
|
ae->locked_guid = 0;
|
||||||
|
ae->flags = htonl(AECP_AEM_LOCK_ENTITY_FLAG_UNLOCK);
|
||||||
|
lock->is_locked = false;
|
||||||
|
lock->base_info.expire_timeout = LONG_MAX;
|
||||||
|
} else {
|
||||||
|
ae->locked_guid = htobe64(lock->locked_id);
|
||||||
|
ae->flags = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVB_PACKET_AEM_SET_COMMAND_TYPE(p, AVB_AECP_AEM_CMD_LOCK_ENTITY);
|
||||||
|
|
||||||
|
/** Setup the packet for the unsolicited notification*/
|
||||||
|
rc = reply_unsolicited_notifications(aecp, &lock->base_info, buf, len, internal);
|
||||||
|
if (rc) {
|
||||||
|
pw_log_error("Unsolicited notification failed \n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int handle_unsol_lock_entity_milanv12(struct aecp *aecp, struct descriptor *desc,
|
||||||
|
uint64_t ctrler_id)
|
||||||
|
{
|
||||||
|
int rc = -1;
|
||||||
|
struct aecp_aem_entity_milan_state *entity_state;
|
||||||
|
struct aecp_aem_lock_state *lock;
|
||||||
|
|
||||||
|
entity_state = desc->ptr;
|
||||||
|
lock = &entity_state->state.lock_state;
|
||||||
|
lock->base_info.controller_entity_id = ctrler_id;
|
||||||
|
rc = handle_unsol_lock_common(aecp, lock, false);
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* LOCK_ENTITY */
|
||||||
|
/* Milan v1.2, Sec. 5.4.2.2; IEEE 1722.1-2021, Sec. 7.4.2*/
|
||||||
|
int handle_cmd_lock_entity_milan_v12(struct aecp *aecp, int64_t now, const void *m, int len)
|
||||||
|
{
|
||||||
|
uint8_t buf[512];
|
||||||
|
struct server *server = aecp->server;
|
||||||
|
const struct avb_ethernet_header *h = m;
|
||||||
|
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
const struct avb_packet_aecp_aem_lock *ae;
|
||||||
|
|
||||||
|
struct avb_ethernet_header *h_reply;
|
||||||
|
struct avb_packet_aecp_aem *p_reply;
|
||||||
|
struct avb_packet_aecp_aem_lock *ae_reply;
|
||||||
|
|
||||||
|
struct descriptor *desc;
|
||||||
|
struct aecp_aem_entity_milan_state *entity_state;
|
||||||
|
struct aecp_aem_lock_state *lock;
|
||||||
|
uint16_t desc_type, desc_id;
|
||||||
|
bool reply_locked = false;
|
||||||
|
uint64_t ctrler_id;
|
||||||
|
|
||||||
|
ae = (const struct avb_packet_aecp_aem_lock*)p->payload;
|
||||||
|
desc_type = ntohs(ae->descriptor_type);
|
||||||
|
desc_id = ntohs(ae->descriptor_id);
|
||||||
|
ctrler_id = htobe64(p->aecp.controller_guid) ;
|
||||||
|
|
||||||
|
desc = server_find_descriptor(server, desc_type, desc_id);
|
||||||
|
if (desc == NULL)
|
||||||
|
return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
|
||||||
|
|
||||||
|
entity_state = desc->ptr;
|
||||||
|
lock = &entity_state->state.lock_state;
|
||||||
|
|
||||||
|
if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0) {
|
||||||
|
/*
|
||||||
|
* Milan v1.2: The PAAD-AE shall not allow locking another descriptor
|
||||||
|
* than the ENTITY descriptor (NOT_SUPPORTED shall be returned in
|
||||||
|
* this case).
|
||||||
|
*/
|
||||||
|
return reply_not_supported(aecp, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ae->flags & htonl(AECP_AEM_LOCK_ENTITY_FLAG_UNLOCK)) {
|
||||||
|
/* Entity is not locked */
|
||||||
|
if (!lock->is_locked) {
|
||||||
|
return reply_success(aecp, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unlocking by the controller which locked */
|
||||||
|
if (ctrler_id == lock->locked_id) {
|
||||||
|
pw_log_debug("Unlocking\n");
|
||||||
|
lock->is_locked = false;
|
||||||
|
lock->locked_id = 0;
|
||||||
|
} else {
|
||||||
|
/* Unlocking by a controller that did not lock?*/
|
||||||
|
if (ctrler_id != lock->locked_id) {
|
||||||
|
pw_log_debug("Already unlocked by %" PRIx64, lock->locked_id);
|
||||||
|
reply_locked = true;
|
||||||
|
} else {
|
||||||
|
// TODO: Can this statement be reached?
|
||||||
|
pw_log_error("Invalid state\n");
|
||||||
|
spa_assert(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Is it really locked?
|
||||||
|
if (!lock->is_locked ||
|
||||||
|
lock->base_info.expire_timeout < now) {
|
||||||
|
|
||||||
|
lock->base_info.expire_timeout = now +
|
||||||
|
AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND * SPA_NSEC_PER_SEC;
|
||||||
|
lock->is_locked = true;
|
||||||
|
lock->locked_id = ctrler_id;
|
||||||
|
} else {
|
||||||
|
// If the lock is taken again by device
|
||||||
|
if (ctrler_id == lock->locked_id) {
|
||||||
|
lock->base_info.expire_timeout +=
|
||||||
|
AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND;
|
||||||
|
|
||||||
|
lock->is_locked = true;
|
||||||
|
} else {
|
||||||
|
// Cannot lock because already locked
|
||||||
|
pw_log_debug("but the device is locked by %" PRIx64, lock->locked_id);
|
||||||
|
reply_locked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forge the response for the entity that is locking the device */
|
||||||
|
memcpy(buf, m, len);
|
||||||
|
h_reply = (struct avb_ethernet_header *) buf;
|
||||||
|
p_reply = SPA_PTROFF(h_reply, sizeof(*h_reply), void);
|
||||||
|
ae_reply = (struct avb_packet_aecp_aem_lock*)p_reply->payload;
|
||||||
|
ae_reply->locked_guid = htobe64(lock->locked_id);
|
||||||
|
|
||||||
|
if (reply_locked) {
|
||||||
|
return reply_entity_locked(aecp, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reply_success(aecp, buf, len)) {
|
||||||
|
pw_log_debug("Failed sending success reply\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Then update the state using the system */
|
||||||
|
return handle_unsol_lock_entity_milanv12(aecp, desc, ctrler_id);
|
||||||
|
|
||||||
|
}
|
||||||
21
src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.h
Normal file
21
src/modules/module-avb/aecp-aem-cmds-resps/cmd-lock-entity.h
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#ifndef __AVB_AECP_AEM_LOCK_H__
|
||||||
|
#define __AVB_AECP_AEM_LOCK_H__
|
||||||
|
|
||||||
|
#define AECP_AEM_LOCK_ENTITY_EXPIRE_TIMEOUT_SECOND (60UL)
|
||||||
|
#define AECP_AEM_LOCK_ENTITY_FLAG_UNLOCK (1)
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Command handling will generate the response for the lock command
|
||||||
|
*/
|
||||||
|
int handle_cmd_lock_entity_milan_v12(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len);
|
||||||
|
|
||||||
|
#endif //__AVB_AECP_AEM_LOCK_H__
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#include <pipewire/log.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "../aecp-aem.h"
|
||||||
|
#include "../aecp-aem-state.h"
|
||||||
|
#include "../aecp-aem-milan.h"
|
||||||
|
|
||||||
|
#include "cmd-register-unsolicited-notifications.h"
|
||||||
|
#include "cmd-resp-helpers.h"
|
||||||
|
|
||||||
|
int handle_cmd_register_unsol_notif_milan_v12(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
const struct avb_ethernet_header *h = m;
|
||||||
|
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
struct descriptor *desc;
|
||||||
|
struct aecp_aem_entity_milan_state *entity_state;
|
||||||
|
struct aecp_aem_unsol_notification_state *unsol;
|
||||||
|
uint64_t controller_id = htobe64(p->aecp.controller_guid);
|
||||||
|
const uint32_t ctrler_max = AECP_AEM_MILAN_MAX_CONTROLLER;
|
||||||
|
uint16_t index;
|
||||||
|
|
||||||
|
desc = server_find_descriptor(aecp->server, AVB_AEM_DESC_ENTITY, 0);
|
||||||
|
if (desc == NULL)
|
||||||
|
return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
|
||||||
|
|
||||||
|
entity_state = (struct aecp_aem_entity_milan_state *) desc->ptr;
|
||||||
|
unsol = entity_state->unsol_notif_state;
|
||||||
|
|
||||||
|
/** First check if the controller was already registered */
|
||||||
|
for (index = 0; index < ctrler_max; index++) {
|
||||||
|
uint64_t ctrl_eid = unsol[index].ctrler_entity_id;
|
||||||
|
bool ctrler_reged = unsol[index].is_registered;
|
||||||
|
|
||||||
|
if ((ctrl_eid == controller_id) && ctrler_reged) {
|
||||||
|
pw_log_debug("controller 0x%"PRIx64", already registered",
|
||||||
|
controller_id);
|
||||||
|
return reply_success(aecp, m, len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** When one slot is in the array is available use it */
|
||||||
|
for (index = 0; index < ctrler_max; index++) {
|
||||||
|
if (!unsol[index].is_registered) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Reach the maximum controller allocated */
|
||||||
|
if (index == ctrler_max) {
|
||||||
|
return reply_no_resources(aecp, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsol[index].ctrler_entity_id = controller_id;
|
||||||
|
memcpy(unsol[index].ctrler_mac_addr, h->src, sizeof(h->src));
|
||||||
|
unsol[index].is_registered = true;
|
||||||
|
unsol[index].port_id = 0;
|
||||||
|
unsol[index].next_seq_id = 0;
|
||||||
|
|
||||||
|
pw_log_info("Unsol registration for 0x%"PRIx64, controller_id);
|
||||||
|
return reply_success(aecp, m, len);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#ifndef __AVB_REGISTER_UNSOLICITED_NOTIFICATIONS_H__
|
||||||
|
#define __AVB_REGISTER_UNSOLICITED_NOTIFICATIONS_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Command handling will generate the response to the
|
||||||
|
* received command when registering the a controller from
|
||||||
|
* the list of subscribed entities.
|
||||||
|
*
|
||||||
|
* @see IEEE1722.1-2021 Section 7.4.37 .
|
||||||
|
* @see Milan V1.2 Section 5.4.2.21 .
|
||||||
|
*/
|
||||||
|
int handle_cmd_register_unsol_notif_milan_v12(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len);
|
||||||
|
|
||||||
|
#endif // __AVB_REGISTER_UNSOLICITED_NOTIFICATIONS_H__
|
||||||
111
src/modules/module-avb/aecp-aem-cmds-resps/cmd-resp-helpers.h
Normal file
111
src/modules/module-avb/aecp-aem-cmds-resps/cmd-resp-helpers.h
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#ifndef __AVB_AECP_AEM_HELPERS_H__
|
||||||
|
#define __AVB_AECP_AEM_HELPERS_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <spa/utils/defs.h>
|
||||||
|
#include <pipewire/log.h>
|
||||||
|
|
||||||
|
#include "../aecp.h"
|
||||||
|
|
||||||
|
static inline int reply_status(struct aecp *aecp, int status, const void *m, int len)
|
||||||
|
{
|
||||||
|
uint8_t buf[2048];
|
||||||
|
struct server *server = aecp->server;
|
||||||
|
struct avb_ethernet_header *h = (void*)buf;
|
||||||
|
struct avb_packet_aecp_header *reply = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
|
||||||
|
memcpy(buf, m, len);
|
||||||
|
|
||||||
|
pw_log_debug("status 0x%x\n", status);
|
||||||
|
|
||||||
|
AVB_PACKET_AECP_SET_MESSAGE_TYPE(reply, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
|
||||||
|
AVB_PACKET_AECP_SET_STATUS(reply, status);
|
||||||
|
|
||||||
|
return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int reply_entity_locked(struct aecp *aecp, const void *m, int len)
|
||||||
|
{
|
||||||
|
pw_log_warn("reply entity locked");
|
||||||
|
return reply_status(aecp, AVB_AECP_AEM_STATUS_ENTITY_LOCKED, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \brief The function is be directly hooked with the cmd_info structure */
|
||||||
|
static inline int direct_reply_entiy_locked(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
return reply_entity_locked(aecp, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int reply_not_implemented(struct aecp *aecp, const void *m, int len)
|
||||||
|
{
|
||||||
|
pw_log_warn("reply not implementing");
|
||||||
|
return reply_status(aecp, AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \brief The function is be directly hooked with the cmd_info structure */
|
||||||
|
static inline int direct_reply_not_implemented(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
return reply_not_implemented(aecp, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int reply_not_supported(struct aecp *aecp, const void *m, int len)
|
||||||
|
{
|
||||||
|
pw_log_warn("reply not supported");
|
||||||
|
return reply_status(aecp, AVB_AECP_AEM_STATUS_NOT_SUPPORTED, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \brief The function is be directly hooked with the cmd_info structure */
|
||||||
|
static inline int direct_reply_not_supported(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
return reply_not_supported(aecp, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int reply_no_resources(struct aecp *aecp, const void *m, int len)
|
||||||
|
{
|
||||||
|
pw_log_warn("reply no resources");
|
||||||
|
return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_RESOURCES, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \brief The function is be directly hooked with the cmd_info structure */
|
||||||
|
static inline int direct_reply_no_resources(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
return reply_no_resources(aecp, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int reply_bad_arguments(struct aecp *aecp, const void *m, int len)
|
||||||
|
{
|
||||||
|
pw_log_warn("reply bad arguments");
|
||||||
|
return reply_status(aecp, AVB_AECP_AEM_STATUS_BAD_ARGUMENTS, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \brief The function is be directly hooked with the cmd_info structure */
|
||||||
|
static inline int direct_reply_bad_arguments(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
return reply_bad_arguments(aecp, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int reply_success(struct aecp *aecp, const void *m, int len)
|
||||||
|
{
|
||||||
|
return reply_status(aecp, AVB_AECP_AEM_STATUS_SUCCESS, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** \brief The function is be directly hooked with the cmd_info structure */
|
||||||
|
static inline int direct_reply_success(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
|
{
|
||||||
|
return reply_success(aecp, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //__AVB_AECP_AEM_HELPERS_H__
|
||||||
153
src/modules/module-avb/aecp-aem-cmds-resps/reply-unsol-helpers.c
Normal file
153
src/modules/module-avb/aecp-aem-cmds-resps/reply-unsol-helpers.c
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#include "../aecp-aem.h"
|
||||||
|
#include "../aecp-aem-state.h"
|
||||||
|
#include "../internal.h"
|
||||||
|
|
||||||
|
#include "reply-unsol-helpers.h"
|
||||||
|
|
||||||
|
#include <pipewire/log.h>
|
||||||
|
|
||||||
|
#define AECP_UNSOL_BUFFER_SIZE (128U)
|
||||||
|
#define AECP_AEM_MIN_PACKET_LENGTH (64U)
|
||||||
|
|
||||||
|
static int reply_unsol_get_specific_info(struct aecp *aecp, struct descriptor *desc,
|
||||||
|
struct aecp_aem_unsol_notification_state **unsol_state, size_t *count)
|
||||||
|
{
|
||||||
|
enum avb_mode mode = aecp->server->avb_mode;
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case AVB_MODE_LEGACY:
|
||||||
|
pw_log_error("Not implemented\n");
|
||||||
|
break;
|
||||||
|
case AVB_MODE_MILAN_V12:
|
||||||
|
struct aecp_aem_entity_milan_state *entity_state;
|
||||||
|
|
||||||
|
entity_state = desc->ptr;
|
||||||
|
*unsol_state = entity_state->unsol_notif_state;
|
||||||
|
*count = AECP_AEM_MILAN_MAX_CONTROLLER;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
pw_log_error("Invalid avb_mode %d\n", mode);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int reply_unsol_send(struct aecp *aecp, uint64_t controller_id,
|
||||||
|
void *packet, size_t len, bool internal)
|
||||||
|
{
|
||||||
|
size_t ctrler_index;
|
||||||
|
int rc = 0;
|
||||||
|
struct avb_ethernet_header *h;
|
||||||
|
struct avb_packet_aecp_aem *p;
|
||||||
|
struct aecp_aem_unsol_notification_state *unsol_state;
|
||||||
|
struct descriptor *desc;
|
||||||
|
size_t max_ctrler;
|
||||||
|
|
||||||
|
desc = server_find_descriptor(aecp->server, AVB_AEM_DESC_ENTITY, 0);
|
||||||
|
if (desc == NULL) {
|
||||||
|
pw_log_error("Could not find the ENTITY descriptor 0");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = reply_unsol_get_specific_info(aecp, desc, &unsol_state, &max_ctrler);
|
||||||
|
if (rc) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
h = (struct avb_ethernet_header*) packet;
|
||||||
|
p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
|
||||||
|
/* Loop through all the unsol entities. */
|
||||||
|
for (ctrler_index = 0; ctrler_index < max_ctrler; ctrler_index++)
|
||||||
|
{
|
||||||
|
if (!unsol_state[ctrler_index].is_registered) {
|
||||||
|
pw_log_debug("Not registered %zu", ctrler_index);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((controller_id == unsol_state[ctrler_index].ctrler_entity_id)
|
||||||
|
&& !internal) {
|
||||||
|
/* Do not send unsolicited if that the one triggering
|
||||||
|
changes this is not a timeout. */
|
||||||
|
pw_log_debug("Do not send twice of %"PRIx64" %"PRIx64,
|
||||||
|
controller_id,
|
||||||
|
unsol_state[ctrler_index].ctrler_entity_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
p->aecp.controller_guid =
|
||||||
|
htobe64(unsol_state[ctrler_index].ctrler_entity_id);
|
||||||
|
|
||||||
|
p->aecp.sequence_id = htons(unsol_state[ctrler_index].next_seq_id);
|
||||||
|
|
||||||
|
unsol_state[ctrler_index].next_seq_id++;
|
||||||
|
rc = avb_server_send_packet(aecp->server,
|
||||||
|
unsol_state[ctrler_index].ctrler_mac_addr,
|
||||||
|
AVB_TSN_ETH, packet, len);
|
||||||
|
|
||||||
|
if (rc) {
|
||||||
|
pw_log_error("while sending packet to %"PRIx64,
|
||||||
|
unsol_state[ctrler_index].ctrler_entity_id);
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void reply_unsol_notifications_prepare(struct aecp *aecp,
|
||||||
|
uint8_t *buf, void *packet, size_t len)
|
||||||
|
{
|
||||||
|
struct avb_ethernet_header *h;
|
||||||
|
struct avb_packet_aecp_aem *p;
|
||||||
|
size_t ctrl_data_length;
|
||||||
|
|
||||||
|
/* Here the value of 12 is the delta between the target_entity_id and
|
||||||
|
start of the AECP message specific data. */
|
||||||
|
ctrl_data_length = len - (sizeof(*h) + sizeof(*p)) +
|
||||||
|
AVB_PACKET_CONTROL_DATA_OFFSET;
|
||||||
|
|
||||||
|
h = (struct avb_ethernet_header*) packet;
|
||||||
|
p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
|
|
||||||
|
p->aecp.hdr.subtype = AVB_SUBTYPE_AECP;
|
||||||
|
AVB_PACKET_AECP_SET_MESSAGE_TYPE(&p->aecp, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
|
||||||
|
AVB_PACKET_SET_VERSION(&p->aecp.hdr, 0);
|
||||||
|
AVB_PACKET_AECP_SET_STATUS(&p->aecp, AVB_AECP_AEM_STATUS_SUCCESS);
|
||||||
|
AVB_PACKET_SET_LENGTH(&p->aecp.hdr, ctrl_data_length);
|
||||||
|
p->u = 1;
|
||||||
|
p->aecp.target_guid = htobe64(aecp->server->entity_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sends unsolicited notifications. Does not sends information unless to
|
||||||
|
* the controller id unless an internal change has happenned (timeout, action
|
||||||
|
* etc)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int reply_unsolicited_notifications(struct aecp *aecp,
|
||||||
|
struct aecp_aem_base_info *b_state, void *packet, size_t len,
|
||||||
|
bool internal)
|
||||||
|
{
|
||||||
|
uint8_t buf[AECP_UNSOL_BUFFER_SIZE];
|
||||||
|
|
||||||
|
if (len < AECP_AEM_MIN_PACKET_LENGTH) {
|
||||||
|
memset(buf, 0, AECP_AEM_MIN_PACKET_LENGTH);
|
||||||
|
memcpy(buf, packet, len);
|
||||||
|
len = AECP_AEM_MIN_PACKET_LENGTH;
|
||||||
|
packet = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Retrieve the entity descriptor */
|
||||||
|
reply_unsol_notifications_prepare(aecp, buf, packet, len);
|
||||||
|
|
||||||
|
return reply_unsol_send(aecp, b_state->controller_entity_id, packet, len,
|
||||||
|
internal);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#ifndef __AVB_REPLY_UNSOL_HELPER_H__
|
||||||
|
#define __AVB_REPLY_UNSOL_HELPER_H__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <pipewire/log.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sends unsolicited notifications. Does not sends information unless to
|
||||||
|
* the controller id unless an internal change has happenned (timeout, action
|
||||||
|
* etc)
|
||||||
|
* @see Milan V1.2 Section 5.4.2.21
|
||||||
|
* @see IEEE 1722.1-2021 7.5.2 ( Unsolicited Notifications )
|
||||||
|
*/
|
||||||
|
int reply_unsolicited_notifications(struct aecp *aecp,
|
||||||
|
struct aecp_aem_base_info *b_state, void *packet, size_t len,
|
||||||
|
bool internal);
|
||||||
|
|
||||||
|
#endif // __AVB_REPLY_UNSOL_HELPER_H__
|
||||||
171
src/modules/module-avb/aecp-aem-controls.h
Normal file
171
src/modules/module-avb/aecp-aem-controls.h
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#ifndef __AECP_AEM_CONTROLS_H__
|
||||||
|
#define __AECP_AEM_CONTROLS_H__
|
||||||
|
|
||||||
|
// TODO, When all the AVB needs to be supported then addition needs to be don here
|
||||||
|
/* IEEE 1722.1-2021, Table 7-121 - Value Types*/
|
||||||
|
#define AECP_AEM_CTRL_LINEAR_INT8 0x0000
|
||||||
|
#define AECP_AEM_CTRL_LINEAR_UINT8 0x0001
|
||||||
|
#define AECP_AEM_CTRL_LINEAR_INT16 0x0002
|
||||||
|
#define AECP_AEM_CTRL_LINEAR_UINT16 0x0003
|
||||||
|
#define AECP_AEM_CTRL_LINEAR_INT32 0x0004
|
||||||
|
#define AECP_AEM_CTRL_LINEAR_UINT32 0x0005
|
||||||
|
#define AECP_AEM_CTRL_LINEAR_INT64 0x0006
|
||||||
|
#define AECP_AEM_CTRL_LINEAR_UINT64 0x0007
|
||||||
|
#define AECP_AEM_CTRL_LINEAR_FLOAT 0x0008
|
||||||
|
#define AECP_AEM_CTRL_LINEAR_DOUBLE 0x0009
|
||||||
|
|
||||||
|
#define AECP_AEM_CTRL_SELECTOR_INT8 0x000a
|
||||||
|
#define AECP_AEM_CTRL_SELECTOR_UINT8 0x000b
|
||||||
|
#define AECP_AEM_CTRL_SELECTOR_INT16 0x000c
|
||||||
|
#define AECP_AEM_CTRL_SELECTOR_UINT16 0x000d
|
||||||
|
#define AECP_AEM_CTRL_SELECTOR_INT32 0x000e
|
||||||
|
#define AECP_AEM_CTRL_SELECTOR_UINT32 0x000f
|
||||||
|
#define AECP_AEM_CTRL_SELECTOR_INT64 0x0010
|
||||||
|
#define AECP_AEM_CTRL_SELECTOR_UINT64 0x0011
|
||||||
|
#define AECP_AEM_CTRL_SELECTOR_FLOAT 0x0012
|
||||||
|
#define AECP_AEM_CTRL_SELECTOR_DOUBLE 0x0013
|
||||||
|
#define AECP_AEM_CTRL_SELECTOR_STRING 0x0014
|
||||||
|
|
||||||
|
#define AEPC_AEM_CTRL_ARRAY_INT8 0x0015
|
||||||
|
#define AEPC_AEM_CTRL_ARRAY_UINT8 0x0016
|
||||||
|
#define AEPC_AEM_CTRL_ARRAY_INT16 0x0017
|
||||||
|
#define AEPC_AEM_CTRL_ARRAY_UINT16 0x0018
|
||||||
|
#define AEPC_AEM_CTRL_ARRAY_INT32 0x0019
|
||||||
|
#define AEPC_AEM_CTRL_ARRAY_UINT32 0x001a
|
||||||
|
#define AEPC_AEM_CTRL_ARRAY_INT64 0x001b
|
||||||
|
#define AEPC_AEM_CTRL_ARRAY_UINT64 0x001c
|
||||||
|
#define AEPC_AEM_CTRL_ARRAY_FLOAT 0x001d
|
||||||
|
#define AEPC_AEM_CTRL_ARRAY_DOUBLE 0x001e
|
||||||
|
|
||||||
|
#define AECP_AEM_CTRL_UTF8 0x001f
|
||||||
|
#define AECP_AEM_CTRL_BODE_PLOT 0x0020
|
||||||
|
#define AECP_AEM_CTRL_SMPTE_TIME 0x0021
|
||||||
|
#define AECP_AEM_CTRL_SAMPLE_RATE 0x0022
|
||||||
|
#define AECP_AEM_CTRL_GPTP_TIME 0x0023
|
||||||
|
|
||||||
|
#define AECP_AEM_CTRL_CTRL_VENDOR 0x3ffe
|
||||||
|
|
||||||
|
/* Definition of the UNIT codes */
|
||||||
|
/* IEEE 1722.1-2021, Table 7-75 - Codes for Unitless quantities*/
|
||||||
|
#define AECP_AEM_CTRL_UNIT_CODE_UNITLESS (0)
|
||||||
|
#define AECP_AEM_CTRL_UNIT_CODE_COUNT (1)
|
||||||
|
#define AECP_AEM_CTRL_UNIT_CODE_PERCENT (2)
|
||||||
|
#define AECP_AEM_CTRL_UNIT_CODE_FSTOP (3)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#define AECP_AEM_CTRL_FORMAT_VENDOR (0)
|
||||||
|
#define AECP_AEM_CTRL_FORMAT_AVDECC (1)
|
||||||
|
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.3.5 Control Types */
|
||||||
|
#define AEM_CTRL_TYPE_ENABLE 0x90E0F00000000000ULL
|
||||||
|
#define AEM_CTRL_TYPE_IDENTIFY 0x90E0F00000000001ULL
|
||||||
|
#define AEM_CTRL_TYPE_MUTE 0x90E0F00000000002ULL
|
||||||
|
#define AEM_CTRL_TYPE_INVERT 0x90E0F00000000003ULL
|
||||||
|
#define AEM_CTRL_TYPE_GAIN 0x90E0F00000000004ULL
|
||||||
|
#define AEM_CTRL_TYPE_ATTENUATE 0x90E0F00000000005ULL
|
||||||
|
#define AEM_CTRL_TYPE_DELAY 0x90E0F00000000006ULL
|
||||||
|
#define AEM_CTRL_TYPE_SRC_MODE 0x90E0F00000000007ULL
|
||||||
|
#define AEM_CTRL_TYPE_SNAPSHOT 0x90E0F00000000008ULL
|
||||||
|
#define AEM_CTRL_TYPE_POW_LINE_FREQ 0x90E0F00000000009ULL
|
||||||
|
#define AEM_CTRL_TYPE_POWER_STATUS 0x90E0F0000000000AULL
|
||||||
|
#define AEM_CTRL_TYPE_FAN_STATUS 0x90E0F0000000000BULL
|
||||||
|
#define AEM_CTRL_TYPE_TEMPERATURE 0x90E0F0000000000CULL
|
||||||
|
#define AEM_CTRL_TYPE_ALTITUDE 0x90E0F0000000000DULL
|
||||||
|
#define AEM_CTRL_TYPE_ABSOLUTE_HUMIDITY 0x90E0F0000000000EULL
|
||||||
|
#define AEM_CTRL_TYPE_RELATIVE_HUMIDITY 0x90E0F0000000000FULL
|
||||||
|
#define AEM_CTRL_TYPE_ORIENTATION 0x90E0F00000000010ULL
|
||||||
|
#define AEM_CTRL_TYPE_VELOCITY 0x90E0F00000000011ULL
|
||||||
|
#define AEM_CTRL_TYPE_ACCELERATION 0x90E0F00000000012ULL
|
||||||
|
#define AEM_CTRL_TYPE_FILTER_RESPONSE 0x90E0F00000000013ULL
|
||||||
|
#define AEM_CTRL_TYPE_BAROMETRIC_PRESSURE 0x90E0F00000000014ULL
|
||||||
|
#define AEM_CTRL_TYPE_MANUFACTURER_URL 0x90E0F00000000015ULL
|
||||||
|
#define AEM_CTRL_TYPE_ENTITY_URL 0x90E0F00000000016ULL
|
||||||
|
#define AEM_CTRL_TYPE_CONFIGURATION_URL 0x90E0F00000000017ULL
|
||||||
|
#define AEM_CTRL_TYPE_GENERIC_URL 0x90E0F00000000018ULL
|
||||||
|
#define AEM_CTRL_TYPE_FAULT 0x90E0F00000000019ULL
|
||||||
|
#define AEM_CTRL_TYPE_CONTROLLER_TARGET_ENTITY 0x90E0F0000000001AULL
|
||||||
|
#define AEM_CTRL_TYPE_CONTROLLER_TARGET_OBJECT 0x90E0F0000000001BULL
|
||||||
|
#define AEM_CTRL_TYPE_LATENCY_COMPENSATION 0x90E0F0000000001CULL
|
||||||
|
|
||||||
|
#define AEM_CTRL_TYPE_PANPOT 0x90E0F00000010000ULL
|
||||||
|
#define AEM_CTRL_TYPE_PHANTOM 0x90E0F00000010001ULL
|
||||||
|
#define AEM_CTRL_TYPE_AUDIO_SCALE 0x90E0F00000010002ULL
|
||||||
|
#define AEM_CTRL_TYPE_AUDIO_METERS 0x90E0F00000010003ULL
|
||||||
|
#define AEM_CTRL_TYPE_AUDIO_SPECTRUM 0x90E0F00000010004ULL
|
||||||
|
|
||||||
|
#define AEM_CTRL_TYPE_SCANNING_MODE 0x90E0F00000020000ULL
|
||||||
|
#define AEM_CTRL_TYPE_AUTO_EXP_MODE 0x90E0F00000020001ULL
|
||||||
|
#define AEM_CTRL_TYPE_AUTO_EXP_PRIO 0x90E0F00000020002ULL
|
||||||
|
#define AEM_CTRL_TYPE_EXP_TIME 0x90E0F00000020003ULL
|
||||||
|
#define AEM_CTRL_TYPE_FOCUS 0x90E0F00000020004ULL
|
||||||
|
#define AEM_CTRL_TYPE_FOCUS_AUTO 0x90E0F00000020005ULL
|
||||||
|
#define AEM_CTRL_TYPE_IRIS 0x90E0F00000020006ULL
|
||||||
|
#define AEM_CTRL_TYPE_ZOOM 0x90E0F00000020007ULL
|
||||||
|
#define AEM_CTRL_TYPE_PRIVACY 0x90E0F00000020008ULL
|
||||||
|
#define AEM_CTRL_TYPE_BACKLIGHT 0x90E0F00000020009ULL
|
||||||
|
#define AEM_CTRL_TYPE_BRIGHTNESS 0x90E0F0000002000AULL
|
||||||
|
#define AEM_CTRL_TYPE_CONTRAST 0x90E0F0000002000BULL
|
||||||
|
#define AEM_CTRL_TYPE_HUE 0x90E0F0000002000CULL
|
||||||
|
#define AEM_CTRL_TYPE_SATURATION 0x90E0F0000002000DULL
|
||||||
|
#define AEM_CTRL_TYPE_SHARPNESS 0x90E0F0000002000EULL
|
||||||
|
#define AEM_CTRL_TYPE_GAMMA 0x90E0F0000002000FULL
|
||||||
|
#define AEM_CTRL_TYPE_WHITE_BAL_TEMP 0x90E0F00000020010ULL
|
||||||
|
#define AEM_CTRL_TYPE_WHITE_BAL_TEMP_AUTO 0x90E0F00000020011ULL
|
||||||
|
#define AEM_CTRL_TYPE_WHITE_BAL_COMP 0x90E0F00000020012ULL
|
||||||
|
#define AEM_CTRL_TYPE_WHITE_BAL_COMP_AUTO 0x90E0F00000020013ULL
|
||||||
|
#define AEM_CTRL_TYPE_DIGITAL_ZOOM 0x90E0F00000020014ULL
|
||||||
|
|
||||||
|
#define AEM_CTRL_TYPE_MEDIA_PLAYLIST 0x90E0F00000030000ULL
|
||||||
|
#define AEM_CTRL_TYPE_MEDIA_PLAYLIST_NAME 0x90E0F00000030001ULL
|
||||||
|
#define AEM_CTRL_TYPE_MEDIA_DISK 0x90E0F00000030002ULL
|
||||||
|
#define AEM_CTRL_TYPE_MEIDA_DISK_NAME 0x90E0F00000030003ULL
|
||||||
|
#define AEM_CTRL_TYPE_TRACK 0x90E0F00000030004ULL
|
||||||
|
#define AEM_CTRL_TYPE_TRACK_NAME 0x90E0F00000030005ULL
|
||||||
|
#define AEM_CTRL_TYPE_SPEED 0x90E0F00000030006ULL
|
||||||
|
#define AEM_CTRL_TYPE_MEDIA_SAMPLE_POSITION 0x90E0F00000030007ULL
|
||||||
|
#define AEM_CTRL_TYPE_MEDIA_PLAYBACK_TRANSPORT 0x90E0F00000030008ULL
|
||||||
|
#define AEM_CTRL_TYPE_MEDIA_RECORD_TRANSPORT 0x90E0F00000030009ULL
|
||||||
|
|
||||||
|
#define AEM_CTRL_TYPE_FREQUENCY 0x90E0F00000040000ULL
|
||||||
|
#define AEM_CTRL_TYPE_MODULATION 0x90E0F00000040001ULL
|
||||||
|
#define AEM_CTRL_TYPE_POLARIZATION 0x90E0F00000040002ULL
|
||||||
|
|
||||||
|
#define AEM_CTRL_TYPE_BAUD_RATE 0x90E0F00000050000ULL
|
||||||
|
#define AEM_CTRL_TYPE_BIT_WIDTH 0x90E0F00000050001ULL
|
||||||
|
#define AEM_CTRL_TYPE_PARITY 0x90E0F00000050002ULL
|
||||||
|
#define AEM_CTRL_TYPE_STOP_BITS 0x90E0F00000050003ULL
|
||||||
|
|
||||||
|
#define AEM_CTRL_TYPE_INTERFACE_OPERATIONAL 0x90E0F00000060000ULL
|
||||||
|
#define AEM_CTRL_TYPE_INTERFACE_MEDIA_OPTIONS 0x90E0F00000060001ULL
|
||||||
|
#define AEM_CTRL_TYPE_INTERFACE_MEDIA_STATUS 0x90E0F00000060002ULL
|
||||||
|
#define AEM_CTRL_TYPE_INTERFACE_NETWORK_NAME 0x90E0F00000060003ULL
|
||||||
|
#define AEM_CTRL_TYPE_FQTSS_DELTA_BANDWIDTH 0x90E0F00000060004ULL
|
||||||
|
#define AEM_CTRL_TYPE_FQTSS_ADMIN_IDLE_SLOPE 0x90E0F00000060005ULL
|
||||||
|
#define AEM_CTRL_TYPE_FQTSS_OPER_IDLE_SLOPE 0x90E0F00000060006ULL
|
||||||
|
#define AEM_CTRL_TYPE_FQTSS_PORT_TRANSMIT_RATE 0x90E0F00000060007ULL
|
||||||
|
#define AEM_CTRL_TYPE_FQTSS_CLASS_MEASUREMENT_INTERVAL 0x90E0F00000060008ULL
|
||||||
|
#define AEM_CTRL_TYPE_FQTSS_LOCK_CLASS_BANDWIDTH 0x90E0F00000060009ULL
|
||||||
|
|
||||||
|
/* Identify
|
||||||
|
* IEEE 1722.1, Sec. 7.3.5.2 - Identify Control (IDENTIFY)
|
||||||
|
* Milan v1.2, Sec. 5.4.5.4 - Identification notification
|
||||||
|
*/
|
||||||
|
#define AECP_AEM_CTRL_IDENTIFY_UNIT_MULTIPLY 0
|
||||||
|
#define AECP_AEM_CTRL_IDENTIFY_UNIT_CODE AECP_AEM_CTRL_UNIT_CODE_UNITLESS
|
||||||
|
#define AECP_AEM_CTRL_IDENTIFY_STEP (255)
|
||||||
|
#define AECP_AEM_CTRL_IDENTIFY_MINIMUM (0)
|
||||||
|
#define AECP_AEM_CTRL_IDENTIFY_MAXIMUM (255)
|
||||||
|
|
||||||
|
|
||||||
|
#define BASE_CTRL_TYPE_MAC { 0x90, 0xe0, 0xf0, 0x01, 0x00, 0x00 };
|
||||||
|
// 1722.1-2021, Annex B Table B.1
|
||||||
|
#define BASE_CTRL_IDENTIFY_MAC { 0x90, 0xe0, 0xf0, 0x00, 0x00, 0x01 };
|
||||||
|
|
||||||
|
#endif //__AECP_AEM_CONTROLS_H__
|
||||||
|
|
@ -6,8 +6,11 @@
|
||||||
#ifndef AVB_AECP_AEM_DESCRIPTORS_H
|
#ifndef AVB_AECP_AEM_DESCRIPTORS_H
|
||||||
#define AVB_AECP_AEM_DESCRIPTORS_H
|
#define AVB_AECP_AEM_DESCRIPTORS_H
|
||||||
|
|
||||||
#include "internal.h"
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IEEE 1722.1-2021, Table 7-1 - Descriptor Types
|
||||||
|
*/
|
||||||
#define AVB_AEM_DESC_ENTITY 0x0000
|
#define AVB_AEM_DESC_ENTITY 0x0000
|
||||||
#define AVB_AEM_DESC_CONFIGURATION 0x0001
|
#define AVB_AEM_DESC_CONFIGURATION 0x0001
|
||||||
#define AVB_AEM_DESC_AUDIO_UNIT 0x0002
|
#define AVB_AEM_DESC_AUDIO_UNIT 0x0002
|
||||||
|
|
@ -50,6 +53,13 @@
|
||||||
#define AVB_AEM_DESC_LAST_RESERVED_17221 0x0029
|
#define AVB_AEM_DESC_LAST_RESERVED_17221 0x0029
|
||||||
#define AVB_AEM_DESC_INVALID 0xffff
|
#define AVB_AEM_DESC_INVALID 0xffff
|
||||||
|
|
||||||
|
/* IEEE 1722.1-2021, Table 7-24 - Port Flags */
|
||||||
|
// No flag is not defined in table
|
||||||
|
#define AVB_AEM_PORT_FLAG_NO_FLAG 0x0000
|
||||||
|
#define AVB_AEM_PORT_FLAG_CLOCK_SYNC_SOURCE 0x0001
|
||||||
|
#define AVB_AEM_PORT_FLAG_ASYNC_SAMPLE_RATE_CONV 0x0002
|
||||||
|
#define AVB_AEM_PORT_FLAG_SYNC_SAMPLE_RATE_CONV 0x0004
|
||||||
|
|
||||||
struct avb_aem_desc_entity {
|
struct avb_aem_desc_entity {
|
||||||
uint64_t entity_id;
|
uint64_t entity_id;
|
||||||
uint64_t entity_model_id;
|
uint64_t entity_model_id;
|
||||||
|
|
@ -130,6 +140,42 @@ struct avb_aem_desc_audio_unit {
|
||||||
struct avb_aem_desc_sampling_rate sampling_rates[0];
|
struct avb_aem_desc_sampling_rate sampling_rates[0];
|
||||||
} __attribute__ ((__packed__));
|
} __attribute__ ((__packed__));
|
||||||
|
|
||||||
|
/* IEEE 1722.1-2021, Table 7-28 - AUDIO_CLUSTER format values */
|
||||||
|
#define AVB_AEM_AUDIO_CLUSTER_TYPE_IEC60958 0x00
|
||||||
|
#define AVB_AEM_AUDIO_CLUSTER_TYPE_MBLA 0x40
|
||||||
|
#define AVB_AEM_AUDIO_CLUSTER_TYPE_MIDI 0x80
|
||||||
|
#define AVB_AEM_AUDIO_CLUSTER_TYPE_SMPTE 0x88
|
||||||
|
|
||||||
|
struct avb_aem_desc_audio_cluster {
|
||||||
|
char object_name[64];
|
||||||
|
uint16_t localized_description;
|
||||||
|
|
||||||
|
uint16_t signal_type;
|
||||||
|
uint16_t signal_index;
|
||||||
|
uint16_t signal_output;
|
||||||
|
uint32_t path_latency;
|
||||||
|
uint32_t block_latency;
|
||||||
|
uint16_t channel_count;
|
||||||
|
uint8_t format;
|
||||||
|
uint8_t aes3_data_type_ref;
|
||||||
|
uint16_t aes3_data_type;
|
||||||
|
} __attribute__ ((__packed__));
|
||||||
|
|
||||||
|
#define AVB_AEM_AUDIO_MAPPING_FORMAT_OFFSET (8)
|
||||||
|
|
||||||
|
struct avb_aem_audio_mapping_format {
|
||||||
|
uint16_t mapping_stream_index;
|
||||||
|
uint16_t mapping_stream_channel;
|
||||||
|
uint16_t mapping_cluster_offset;
|
||||||
|
uint16_t mapping_cluster_channel;
|
||||||
|
} __attribute__ ((__packed__));
|
||||||
|
|
||||||
|
struct avb_aem_desc_audio_map {
|
||||||
|
uint16_t mapping_offset;
|
||||||
|
uint16_t number_of_mappings;
|
||||||
|
struct avb_aem_audio_mapping_format mappings[0];
|
||||||
|
} __attribute__ ((__packed__));
|
||||||
|
|
||||||
#define AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE (1u<<0)
|
#define AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE (1u<<0)
|
||||||
#define AVB_AEM_DESC_STREAM_FLAG_CLASS_A (1u<<1)
|
#define AVB_AEM_DESC_STREAM_FLAG_CLASS_A (1u<<1)
|
||||||
#define AVB_AEM_DESC_STREAM_FLAG_CLASS_B (1u<<2)
|
#define AVB_AEM_DESC_STREAM_FLAG_CLASS_B (1u<<2)
|
||||||
|
|
@ -200,6 +246,15 @@ struct avb_aem_desc_clock_source {
|
||||||
uint16_t clock_source_location_index;
|
uint16_t clock_source_location_index;
|
||||||
} __attribute__ ((__packed__));
|
} __attribute__ ((__packed__));
|
||||||
|
|
||||||
|
struct avb_aem_desc_clock_domain {
|
||||||
|
char object_name[64];
|
||||||
|
uint16_t localized_description;
|
||||||
|
uint16_t clock_source_index;
|
||||||
|
uint16_t descriptor_counts_offset;
|
||||||
|
uint16_t clock_sources_count;
|
||||||
|
uint16_t clock_sources[0];
|
||||||
|
} __attribute__ ((__packed__));
|
||||||
|
|
||||||
struct avb_aem_desc_locale {
|
struct avb_aem_desc_locale {
|
||||||
char locale_identifier[64];
|
char locale_identifier[64];
|
||||||
uint16_t number_of_strings;
|
uint16_t number_of_strings;
|
||||||
|
|
@ -227,4 +282,34 @@ struct avb_aem_desc_stream_port {
|
||||||
uint16_t base_map;
|
uint16_t base_map;
|
||||||
} __attribute__ ((__packed__));
|
} __attribute__ ((__packed__));
|
||||||
|
|
||||||
|
struct avb_aem_desc_value_format {
|
||||||
|
uint8_t minimum;
|
||||||
|
uint8_t maximum;
|
||||||
|
uint8_t step;
|
||||||
|
uint8_t default_value;
|
||||||
|
uint8_t current_value;
|
||||||
|
uint16_t unit;
|
||||||
|
uint16_t localized_description;
|
||||||
|
} __attribute__ ((__packed__));
|
||||||
|
|
||||||
|
struct avb_aem_desc_control {
|
||||||
|
char object_name[64];
|
||||||
|
uint16_t localized_description;
|
||||||
|
|
||||||
|
uint32_t block_latency;
|
||||||
|
uint32_t control_latency;
|
||||||
|
uint16_t control_domain;
|
||||||
|
uint16_t control_value_type;
|
||||||
|
uint64_t control_type;
|
||||||
|
uint32_t reset_time;
|
||||||
|
|
||||||
|
uint16_t descriptor_counts_offset;
|
||||||
|
|
||||||
|
uint16_t number_of_values;
|
||||||
|
uint16_t signal_type;
|
||||||
|
uint16_t signal_index;
|
||||||
|
uint16_t signal_output;
|
||||||
|
struct avb_aem_desc_value_format value_format[0];
|
||||||
|
} __attribute__ ((__packed__));
|
||||||
|
|
||||||
#endif /* AVB_AECP_AEM_DESCRIPTORS_H */
|
#endif /* AVB_AECP_AEM_DESCRIPTORS_H */
|
||||||
|
|
|
||||||
6
src/modules/module-avb/aecp-aem-milan.h
Normal file
6
src/modules/module-avb/aecp-aem-milan.h
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#ifndef __AVB_AECP_AEM_MILAN_H__
|
||||||
|
#define __AVB_AECP_AEM_MILAN_H__
|
||||||
|
|
||||||
|
#define AECP_AEM_MILAN_MAX_CONTROLLER 16
|
||||||
|
|
||||||
|
#endif //__AVB_AECP_AEM_MILAN_H__
|
||||||
180
src/modules/module-avb/aecp-aem-state.h
Normal file
180
src/modules/module-avb/aecp-aem-state.h
Normal file
|
|
@ -0,0 +1,180 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#ifndef AVB_AECP_AEM_STATE_H
|
||||||
|
#define AVB_AECP_AEM_STATE_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include "aecp-aem-descriptors.h"
|
||||||
|
#include "aecp-aem-milan.h"
|
||||||
|
#include "stream.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The way structure are organised in a "derived" manner.
|
||||||
|
* Each of the state structure must directly "castable" into the descriptor
|
||||||
|
* that the state variable relies upon.
|
||||||
|
*
|
||||||
|
* For instance, if a stream_input descriptor is created, the state structure
|
||||||
|
* stream_input_state needs to be created as follow:
|
||||||
|
*
|
||||||
|
* struct stream_input_state {
|
||||||
|
* struct stream_input_desc ...
|
||||||
|
* ...
|
||||||
|
* This way it's possible to get directly from the AEM command the descriptor
|
||||||
|
* and the state without having to create a mechanism for this.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief The base information would be required
|
||||||
|
* for descriptor that needs to udpate for unsollictied
|
||||||
|
* notificaction.
|
||||||
|
*/
|
||||||
|
struct aecp_aem_state_base {
|
||||||
|
/**
|
||||||
|
* Originator of the control
|
||||||
|
* This is needed so the unsoolictied notification does not send back SUCCESS
|
||||||
|
* to the originator of of the unsolicited notification
|
||||||
|
*/
|
||||||
|
uint64_t controller_entity_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To avoid sending on every change for unsol notifications, only once a
|
||||||
|
* second
|
||||||
|
*/
|
||||||
|
int64_t last_update;
|
||||||
|
|
||||||
|
/** timeout absolute time*/
|
||||||
|
int64_t expire_timeout;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief the structure keeps track of the registered controller entities
|
||||||
|
*/
|
||||||
|
struct aecp_aem_unsol_notification_state {
|
||||||
|
/**
|
||||||
|
* The controller is that is locking this system
|
||||||
|
*/
|
||||||
|
uint64_t ctrler_entity_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mac Address of the controller
|
||||||
|
*/
|
||||||
|
uint8_t ctrler_mac_addr[6];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Port where the registeration originated from
|
||||||
|
*/
|
||||||
|
uint8_t port_id;
|
||||||
|
|
||||||
|
/***
|
||||||
|
* The sequence ID of the next unsolicited notification
|
||||||
|
*/
|
||||||
|
|
||||||
|
uint16_t next_seq_id;
|
||||||
|
/**
|
||||||
|
* Actual value of the lock, get removed when unregistere or expired.
|
||||||
|
*/
|
||||||
|
bool is_registered;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
struct aecp_aem_base_info {
|
||||||
|
/** Originator of the control
|
||||||
|
* This is needed so the unsoolictied notification does not send back SUCCESS
|
||||||
|
* to the originator of of the unsolicited notification */
|
||||||
|
uint64_t controller_entity_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To avoid sending on every change for unsol notifications, only once a
|
||||||
|
* a second
|
||||||
|
* */
|
||||||
|
int64_t last_update;
|
||||||
|
|
||||||
|
/** timeout absolute time*/
|
||||||
|
int64_t expire_timeout;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct aecp_aem_lock_state {
|
||||||
|
struct aecp_aem_base_info base_info;
|
||||||
|
/**
|
||||||
|
* the entity id that is locking this system
|
||||||
|
*/
|
||||||
|
uint64_t locked_id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* actual value of the lock
|
||||||
|
*/
|
||||||
|
bool is_locked;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief the generic entity state common for all flavor of AVB
|
||||||
|
*/
|
||||||
|
struct aecp_aem_entity_state {
|
||||||
|
struct avb_aem_desc_entity desc;
|
||||||
|
struct aecp_aem_lock_state lock_state;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Milan implementation of the entity
|
||||||
|
*/
|
||||||
|
struct aecp_aem_entity_milan_state {
|
||||||
|
struct aecp_aem_entity_state state;
|
||||||
|
struct aecp_aem_unsol_notification_state unsol_notif_state[AECP_AEM_MILAN_MAX_CONTROLLER];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Legacy AVB implementation of the entity
|
||||||
|
*/
|
||||||
|
struct aecp_aem_entity_legacy_avb_state {
|
||||||
|
struct aecp_aem_entity_state state;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief The stream inputs are specified as in the IEEE-1722.1-2021
|
||||||
|
* Table 7-156
|
||||||
|
*/
|
||||||
|
struct aecp_aem_stream_input_counters {
|
||||||
|
struct aecp_aem_state_base base_state;
|
||||||
|
|
||||||
|
uint32_t media_locked;
|
||||||
|
uint32_t media_unlocked;
|
||||||
|
uint32_t stream_interrupted;
|
||||||
|
uint32_t seq_mistmatch;
|
||||||
|
uint32_t media_reset;
|
||||||
|
/** Timestamp Uncertain */
|
||||||
|
uint32_t tu;
|
||||||
|
uint32_t unsupported_format;
|
||||||
|
uint32_t late_timestamp;
|
||||||
|
uint32_t early_timestamp;
|
||||||
|
uint32_t frame_rx;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct aecp_aem_stream_input_state {
|
||||||
|
struct avb_aem_desc_stream desc;
|
||||||
|
|
||||||
|
struct aecp_aem_stream_input_counters counters;
|
||||||
|
struct stream stream;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct aecp_aem_stream_output_counters {
|
||||||
|
struct aecp_aem_state_base base_state;
|
||||||
|
|
||||||
|
uint32_t stream_start;
|
||||||
|
uint32_t stream_stop;
|
||||||
|
uint32_t media_reset;
|
||||||
|
uint32_t tu;
|
||||||
|
uint32_t frame_tx;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct aecp_aem_stream_output_state {
|
||||||
|
struct avb_aem_desc_stream desc;
|
||||||
|
|
||||||
|
struct aecp_aem_stream_output_counters counters;
|
||||||
|
struct stream stream;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // AVB_AECP_AEM_STATE_H
|
||||||
112
src/modules/module-avb/aecp-aem-types.h
Normal file
112
src/modules/module-avb/aecp-aem-types.h
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
/* AVB support */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alex Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#ifndef __AVB_AECP_AEM_TYPES_H__
|
||||||
|
#define __AVB_AECP_AEM_TYPES_H__
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IEEE 1722.1-2021, Table 7-141 - status field
|
||||||
|
*/
|
||||||
|
#define AVB_AECP_AEM_STATUS_SUCCESS 0
|
||||||
|
#define AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED 1
|
||||||
|
#define AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR 2
|
||||||
|
#define AVB_AECP_AEM_STATUS_ENTITY_LOCKED 3
|
||||||
|
#define AVB_AECP_AEM_STATUS_ENTITY_ACQUIRED 4
|
||||||
|
#define AVB_AECP_AEM_STATUS_NOT_AUTHENTICATED 5
|
||||||
|
#define AVB_AECP_AEM_STATUS_AUTHENTICATION_DISABLED 6
|
||||||
|
#define AVB_AECP_AEM_STATUS_BAD_ARGUMENTS 7
|
||||||
|
#define AVB_AECP_AEM_STATUS_NO_RESOURCES 8
|
||||||
|
#define AVB_AECP_AEM_STATUS_IN_PROGRESS 9
|
||||||
|
#define AVB_AECP_AEM_STATUS_ENTITY_MISBEHAVING 10
|
||||||
|
#define AVB_AECP_AEM_STATUS_NOT_SUPPORTED 11
|
||||||
|
#define AVB_AECP_AEM_STATUS_STREAM_IS_RUNNING 12
|
||||||
|
|
||||||
|
#define AECP_AEM_STRLEN_MAX (64)
|
||||||
|
|
||||||
|
/** IEEE 1722.1-2021, Table 7-140 - Command Codes */
|
||||||
|
// TODO: Update me
|
||||||
|
#define AVB_AECP_AEM_CMD_ACQUIRE_ENTITY 0x0000
|
||||||
|
#define AVB_AECP_AEM_CMD_LOCK_ENTITY 0x0001
|
||||||
|
#define AVB_AECP_AEM_CMD_ENTITY_AVAILABLE 0x0002
|
||||||
|
#define AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE 0x0003
|
||||||
|
#define AVB_AECP_AEM_CMD_READ_DESCRIPTOR 0x0004
|
||||||
|
#define AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR 0x0005
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_CONFIGURATION 0x0006
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_CONFIGURATION 0x0007
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_STREAM_FORMAT 0x0008
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_STREAM_FORMAT 0x0009
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT 0x000a
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT 0x000b
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT 0x000c
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT 0x000d
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_STREAM_INFO 0x000e
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_STREAM_INFO 0x000f
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_NAME 0x0010
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_NAME 0x0011
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID 0x0012
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID 0x0013
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_SAMPLING_RATE 0x0014
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_SAMPLING_RATE 0x0015
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE 0x0016
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE 0x0017
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_CONTROL 0x0018
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_CONTROL 0x0019
|
||||||
|
#define AVB_AECP_AEM_CMD_INCREMENT_CONTROL 0x001a
|
||||||
|
#define AVB_AECP_AEM_CMD_DECREMENT_CONTROL 0x001b
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR 0x001c
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR 0x001d
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_MIXER 0x001e
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_MIXER 0x001f
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_MATRIX 0x0020
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_MATRIX 0x0021
|
||||||
|
#define AVB_AECP_AEM_CMD_START_STREAMING 0x0022
|
||||||
|
#define AVB_AECP_AEM_CMD_STOP_STREAMING 0x0023
|
||||||
|
#define AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION 0x0024
|
||||||
|
#define AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION 0x0025
|
||||||
|
#define AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION 0x0026
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_AVB_INFO 0x0027
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_AS_PATH 0x0028
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_COUNTERS 0x0029
|
||||||
|
#define AVB_AECP_AEM_CMD_REBOOT 0x002a
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_AUDIO_MAP 0x002b
|
||||||
|
#define AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS 0x002c
|
||||||
|
#define AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS 0x002d
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_VIDEO_MAP 0x002e
|
||||||
|
#define AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS 0x002f
|
||||||
|
#define AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS 0x0030
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_SENSOR_MAP 0x0031
|
||||||
|
#define AVB_AECP_AEM_CMD_ADD_SENSOR_MAPPINGS 0x0032
|
||||||
|
#define AVB_AECP_AEM_CMD_REMOVE_SENSOR_MAPPINGS 0x0033
|
||||||
|
#define AVB_AECP_AEM_CMD_START_OPERATION 0x0034
|
||||||
|
#define AVB_AECP_AEM_CMD_ABORT_OPERATION 0x0035
|
||||||
|
#define AVB_AECP_AEM_CMD_OPERATION_STATUS 0x0036
|
||||||
|
#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY 0x0037
|
||||||
|
#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY 0x0038
|
||||||
|
#define AVB_AECP_AEM_CMD_AUTH_GET_KEY_LIST 0x0039
|
||||||
|
#define AVB_AECP_AEM_CMD_AUTH_GET_KEY 0x003a
|
||||||
|
#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY_TO_CHAIN 0x003b
|
||||||
|
#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY_FROM_CHAIN 0x003c
|
||||||
|
#define AVB_AECP_AEM_CMD_AUTH_GET_KEYCHAIN_LIST 0x003d
|
||||||
|
#define AVB_AECP_AEM_CMD_AUTH_GET_IDENTITY 0x003e
|
||||||
|
#define AVB_AECP_AEM_CMD_AUTH_ADD_TOKEN 0x003f
|
||||||
|
#define AVB_AECP_AEM_CMD_AUTH_DELETE_TOKEN 0x0040
|
||||||
|
#define AVB_AECP_AEM_CMD_AUTHENTICATE 0x0041
|
||||||
|
#define AVB_AECP_AEM_CMD_DEAUTHENTICATE 0x0042
|
||||||
|
#define AVB_AECP_AEM_CMD_ENABLE_TRANSPORT_SECURITY 0x0043
|
||||||
|
#define AVB_AECP_AEM_CMD_DISABLE_TRANSPORT_SECURITY 0x0044
|
||||||
|
#define AVB_AECP_AEM_CMD_ENABLE_STREAM_ENCRYPTION 0x0045
|
||||||
|
#define AVB_AECP_AEM_CMD_DISABLE_STREAM_ENCRYPTION 0x0046
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_MEMORY_OBJECT_LENGTH 0x0047
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_MEMORY_OBJECT_LENGTH 0x0048
|
||||||
|
#define AVB_AECP_AEM_CMD_SET_STREAM_BACKUP 0x0049
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_STREAM_BACKUP 0x004a
|
||||||
|
#define AVB_AECP_AEM_CMD_GET_DYNAMIC_INFO 0x004b
|
||||||
|
#define AVB_AECP_AEM_CMD_EXPANSION 0x7fff
|
||||||
|
|
||||||
|
#define AVB_AEM_ACQUIRE_ENTITY_PERSISTENT_FLAG (1<<0)
|
||||||
|
|
||||||
|
#endif //__AVB_AECP_AEM_TYPES_H__
|
||||||
|
|
@ -1,36 +1,28 @@
|
||||||
/* AVB support */
|
/* AVB support */
|
||||||
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
|
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki */
|
||||||
/* SPDX-License-Identifier: MIT */
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
#include "aecp-aem.h"
|
#include "aecp-aem.h"
|
||||||
#include "aecp-aem-descriptors.h"
|
#include "aecp-aem-descriptors.h"
|
||||||
|
#include "aecp-aem-cmds-resps/cmd-resp-helpers.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
static int reply_status(struct aecp *aecp, int status, const void *m, int len)
|
/* The headers including the command and response of the system */
|
||||||
{
|
#include "aecp-aem-cmds-resps/cmd-available.h"
|
||||||
struct server *server = aecp->server;
|
#include "aecp-aem-cmds-resps/cmd-get-set-configuration.h"
|
||||||
uint8_t buf[len];
|
#include "aecp-aem-cmds-resps/cmd-lock-entity.h"
|
||||||
struct avb_ethernet_header *h = (void*)buf;
|
#include "aecp-aem-cmds-resps/cmd-register-unsolicited-notifications.h"
|
||||||
struct avb_packet_aecp_header *reply = SPA_PTROFF(h, sizeof(*h), void);
|
#include "aecp-aem-cmds-resps/cmd-deregister-unsolicited-notifications.h"
|
||||||
|
#include "aecp-aem-cmds-resps/cmd-get-set-name.h"
|
||||||
|
#include "aecp-aem-cmds-resps/cmd-get-set-stream-format.h"
|
||||||
|
#include "aecp-aem-cmds-resps/cmd-get-set-clock-source.h"
|
||||||
|
#include "aecp-aem-cmds-resps/cmd-lock-entity.h"
|
||||||
|
|
||||||
memcpy(buf, m, len);
|
|
||||||
AVB_PACKET_AECP_SET_MESSAGE_TYPE(reply, AVB_AECP_MESSAGE_TYPE_AEM_RESPONSE);
|
|
||||||
AVB_PACKET_AECP_SET_STATUS(reply, status);
|
|
||||||
|
|
||||||
return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int reply_not_implemented(struct aecp *aecp, const void *m, int len)
|
|
||||||
{
|
|
||||||
return reply_status(aecp, AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED, m, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int reply_success(struct aecp *aecp, const void *m, int len)
|
|
||||||
{
|
|
||||||
return reply_status(aecp, AVB_AECP_AEM_STATUS_SUCCESS, m, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ACQUIRE_ENTITY */
|
/* ACQUIRE_ENTITY */
|
||||||
static int handle_acquire_entity(struct aecp *aecp, const void *m, int len)
|
static int handle_acquire_entity_avb_legacy(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
{
|
{
|
||||||
struct server *server = aecp->server;
|
struct server *server = aecp->server;
|
||||||
const struct avb_packet_aecp_aem *p = m;
|
const struct avb_packet_aecp_aem *p = m;
|
||||||
|
|
@ -45,7 +37,8 @@ static int handle_acquire_entity(struct aecp *aecp, const void *m, int len)
|
||||||
|
|
||||||
desc = server_find_descriptor(server, desc_type, desc_id);
|
desc = server_find_descriptor(server, desc_type, desc_id);
|
||||||
if (desc == NULL)
|
if (desc == NULL)
|
||||||
return reply_status(aecp, AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
|
return reply_status(aecp,
|
||||||
|
AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR, p, len);
|
||||||
|
|
||||||
if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0)
|
if (desc_type != AVB_AEM_DESC_ENTITY || desc_id != 0)
|
||||||
return reply_not_implemented(aecp, m, len);
|
return reply_not_implemented(aecp, m, len);
|
||||||
|
|
@ -54,7 +47,8 @@ static int handle_acquire_entity(struct aecp *aecp, const void *m, int len)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* LOCK_ENTITY */
|
/* LOCK_ENTITY */
|
||||||
static int handle_lock_entity(struct aecp *aecp, const void *m, int len)
|
static int handle_lock_entity_avb_legacy(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
{
|
{
|
||||||
struct server *server = aecp->server;
|
struct server *server = aecp->server;
|
||||||
const struct avb_packet_aecp_aem *p = m;
|
const struct avb_packet_aecp_aem *p = m;
|
||||||
|
|
@ -78,7 +72,7 @@ static int handle_lock_entity(struct aecp *aecp, const void *m, int len)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* READ_DESCRIPTOR */
|
/* READ_DESCRIPTOR */
|
||||||
static int handle_read_descriptor(struct aecp *aecp, const void *m, int len)
|
static int handle_read_descriptor_common(struct aecp *aecp, int64_t now, const void *m, int len)
|
||||||
{
|
{
|
||||||
struct server *server = aecp->server;
|
struct server *server = aecp->server;
|
||||||
const struct avb_ethernet_header *h = m;
|
const struct avb_ethernet_header *h = m;
|
||||||
|
|
@ -120,7 +114,8 @@ static int handle_read_descriptor(struct aecp *aecp, const void *m, int len)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* GET_AVB_INFO */
|
/* GET_AVB_INFO */
|
||||||
static int handle_get_avb_info(struct aecp *aecp, const void *m, int len)
|
static int handle_get_avb_info_common(struct aecp *aecp, int64_t now,
|
||||||
|
const void *m, int len)
|
||||||
{
|
{
|
||||||
struct server *server = aecp->server;
|
struct server *server = aecp->server;
|
||||||
const struct avb_ethernet_header *h = m;
|
const struct avb_ethernet_header *h = m;
|
||||||
|
|
@ -168,95 +163,227 @@ static int handle_get_avb_info(struct aecp *aecp, const void *m, int len)
|
||||||
return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size);
|
return avb_server_send_packet(server, h->src, AVB_TSN_ETH, buf, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* AEM_COMMAND */
|
||||||
|
/* TODO in the case the AVB mode allows you to modifiy a Milan readonly
|
||||||
|
descriptor, then create a array of is_readonly depending on the mode used */
|
||||||
|
static const char * const cmd_names[] = {
|
||||||
|
[AVB_AECP_AEM_CMD_ACQUIRE_ENTITY] = "acquire-entity",
|
||||||
|
[AVB_AECP_AEM_CMD_LOCK_ENTITY] = "lock-entity",
|
||||||
|
[AVB_AECP_AEM_CMD_ENTITY_AVAILABLE] = "entity-available",
|
||||||
|
[AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE] = "controller-available",
|
||||||
|
[AVB_AECP_AEM_CMD_READ_DESCRIPTOR] = "read-descriptor",
|
||||||
|
[AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR] = "write-descriptor",
|
||||||
|
[AVB_AECP_AEM_CMD_SET_CONFIGURATION] = "set-configuration",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_CONFIGURATION] = "get-configuration",
|
||||||
|
[AVB_AECP_AEM_CMD_SET_STREAM_FORMAT] = "set-stream-format",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_STREAM_FORMAT] = "get-stream-format",
|
||||||
|
[AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT] = "set-video-format",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT] = "get-video-format",
|
||||||
|
[AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT] = "set-sensor-format",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT] = "get-sensor-format",
|
||||||
|
[AVB_AECP_AEM_CMD_SET_STREAM_INFO] = "set-stream-info",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_STREAM_INFO] = "get-stream-info",
|
||||||
|
[AVB_AECP_AEM_CMD_SET_NAME] = "set-name",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_NAME] = "get-name",
|
||||||
|
[AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID] = "set-association-id",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID] = "get-association-id",
|
||||||
|
[AVB_AECP_AEM_CMD_SET_SAMPLING_RATE] = "set-sampling-rate",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_SAMPLING_RATE] = "get-sampling-rate",
|
||||||
|
[AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE] = "set-clock-source",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE] = "get-clock-source",
|
||||||
|
[AVB_AECP_AEM_CMD_SET_CONTROL] = "set-control",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_CONTROL] = "get-control",
|
||||||
|
[AVB_AECP_AEM_CMD_INCREMENT_CONTROL] = "increment-control",
|
||||||
|
[AVB_AECP_AEM_CMD_DECREMENT_CONTROL] = "decrement-control",
|
||||||
|
[AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR] = "set-signal-selector",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR] = "get-signal-selector",
|
||||||
|
[AVB_AECP_AEM_CMD_SET_MIXER] = "set-mixer",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_MIXER] = "get-mixer",
|
||||||
|
[AVB_AECP_AEM_CMD_SET_MATRIX] = "set-matrix",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_MATRIX] = "get-matrix",
|
||||||
|
[AVB_AECP_AEM_CMD_START_STREAMING] = "start-streaming",
|
||||||
|
[AVB_AECP_AEM_CMD_STOP_STREAMING] = "stop-streaming",
|
||||||
|
[AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION] = "register-unsolicited-notification",
|
||||||
|
[AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION] = "deregister-unsolicited-notification",
|
||||||
|
[AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION] = "identify-notification",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_AVB_INFO] = "get-avb-info",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_AS_PATH] = "get-as-path",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_COUNTERS] = "get-counters",
|
||||||
|
[AVB_AECP_AEM_CMD_REBOOT] = "reboot",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_AUDIO_MAP] = "get-audio-map",
|
||||||
|
[AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS] = "add-audio-mappings",
|
||||||
|
[AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS] = "remove-audio-mappings",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_VIDEO_MAP] = "get-video-map",
|
||||||
|
[AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS] = "add-video-mappings",
|
||||||
|
[AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS] = "remove-video-mappings",
|
||||||
|
[AVB_AECP_AEM_CMD_GET_SENSOR_MAP] = "get-sensor-map"
|
||||||
|
};
|
||||||
|
|
||||||
/* AEM_COMMAND */
|
/* AEM_COMMAND */
|
||||||
struct cmd_info {
|
struct cmd_info {
|
||||||
uint16_t type;
|
/**
|
||||||
const char *name;
|
* \brief Is Readonly is a hint used to decide whether or not the
|
||||||
int (*handle) (struct aecp *aecp, const void *p, int len);
|
* unsollocited notifications is to be sent for this descriptor or not
|
||||||
|
*/
|
||||||
|
const bool is_readonly;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief handle a command for a specific descriptor
|
||||||
|
*/
|
||||||
|
int (*handle_command) (struct aecp *aecp, int64_t now, const void *p,
|
||||||
|
int len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Response are sent upon changes that occure internally
|
||||||
|
* and that are then propagated to the network and are not
|
||||||
|
* unsollicited notifications
|
||||||
|
*/
|
||||||
|
int (*handle_response) (struct aecp *aecp, int64_t now, const void *p,
|
||||||
|
int len);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Handling of the unsolicited notification that are used
|
||||||
|
* to inform subscribed controller about the change of status of
|
||||||
|
* a specific descriptor or the counter associted with it
|
||||||
|
*/
|
||||||
|
int (*handle_unsol_timer) (struct aecp *aecp, int64_t now);
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct cmd_info cmd_info[] = {
|
#define AECP_AEM_HANDLE_CMD(cmd, readonly_desc, handle_exec) \
|
||||||
{ AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, "acquire-entity", handle_acquire_entity, },
|
[cmd] = { \
|
||||||
{ AVB_AECP_AEM_CMD_LOCK_ENTITY, "lock-entity", handle_lock_entity, },
|
.is_readonly = readonly_desc, \
|
||||||
{ AVB_AECP_AEM_CMD_ENTITY_AVAILABLE, "entity-available", NULL, },
|
.handle_command = handle_exec \
|
||||||
{ AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE, "controller-available", NULL, },
|
}
|
||||||
{ AVB_AECP_AEM_CMD_READ_DESCRIPTOR, "read-descriptor", handle_read_descriptor, },
|
|
||||||
{ AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR, "write-descriptor", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_SET_CONFIGURATION, "set-configuration", NULL, },
|
#define AECP_AEM_HANDLE_RESP(cmd, handle_cmd, handle_exec_unsol) \
|
||||||
{ AVB_AECP_AEM_CMD_GET_CONFIGURATION, "get-configuration", NULL, },
|
[cmd] = { \
|
||||||
{ AVB_AECP_AEM_CMD_SET_STREAM_FORMAT, "set-stream-format", NULL, },
|
.name = name_str, \
|
||||||
{ AVB_AECP_AEM_CMD_GET_STREAM_FORMAT, "get-stream-format", NULL, },
|
.is_readonly = false, \
|
||||||
{ AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT, "set-video-format", NULL, },
|
.handle_response = handle_cmd \
|
||||||
{ AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT, "get-video-format", NULL, },
|
}
|
||||||
{ AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT, "set-sensor-format", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT, "get-sensor-format", NULL, },
|
#define AECP_AEM_CMD_RESP_AND_UNSOL(cmd, readonly_desc, handle_exec, \
|
||||||
{ AVB_AECP_AEM_CMD_SET_STREAM_INFO, "set-stream-info", NULL, },
|
handle_exec_unsol) \
|
||||||
{ AVB_AECP_AEM_CMD_GET_STREAM_INFO, "get-stream-info", NULL, },
|
[cmd] = { \
|
||||||
{ AVB_AECP_AEM_CMD_SET_NAME, "set-name", NULL, },
|
.name = name_str, \
|
||||||
{ AVB_AECP_AEM_CMD_GET_NAME, "get-name", NULL, },
|
.is_readonly = readonly_desc, \
|
||||||
{ AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID, "set-association-id", NULL, },
|
.handle = handle_exec, \
|
||||||
{ AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID, "get-association-id", NULL, },
|
.handle_unsol = handle_exec_unsol \
|
||||||
{ AVB_AECP_AEM_CMD_SET_SAMPLING_RATE, "set-sampling-rate", NULL, },
|
}
|
||||||
{ AVB_AECP_AEM_CMD_GET_SAMPLING_RATE, "get-sampling-rate", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE, "set-clock-source", NULL, },
|
static const struct cmd_info cmd_info_avb_legacy[] = {
|
||||||
{ AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE, "get-clock-source", NULL, },
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, true,
|
||||||
{ AVB_AECP_AEM_CMD_SET_CONTROL, "set-control", NULL, },
|
handle_acquire_entity_avb_legacy),
|
||||||
{ AVB_AECP_AEM_CMD_GET_CONTROL, "get-control", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_INCREMENT_CONTROL, "increment-control", NULL, },
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_LOCK_ENTITY, true,
|
||||||
{ AVB_AECP_AEM_CMD_DECREMENT_CONTROL, "decrement-control", NULL, },
|
handle_lock_entity_avb_legacy),
|
||||||
{ AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR, "set-signal-selector", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR, "get-signal-selector", NULL, },
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_CONFIGURATION, false,
|
||||||
{ AVB_AECP_AEM_CMD_SET_MIXER, "set-mixer", NULL, },
|
handle_cmd_get_configuration_common),
|
||||||
{ AVB_AECP_AEM_CMD_GET_MIXER, "get-mixer", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_SET_MATRIX, "set-matrix", NULL, },
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_READ_DESCRIPTOR, true,
|
||||||
{ AVB_AECP_AEM_CMD_GET_MATRIX, "get-matrix", NULL, },
|
handle_read_descriptor_common),
|
||||||
{ AVB_AECP_AEM_CMD_START_STREAMING, "start-streaming", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_STOP_STREAMING, "stop-streaming", NULL, },
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_AVB_INFO, true,
|
||||||
{ AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION, "register-unsolicited-notification", NULL, },
|
handle_get_avb_info_common),
|
||||||
{ AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION, "deregister-unsolicited-notification", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION, "identify-notification", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_GET_AVB_INFO, "get-avb-info", handle_get_avb_info, },
|
|
||||||
{ AVB_AECP_AEM_CMD_GET_AS_PATH, "get-as-path", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_GET_COUNTERS, "get-counters", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_REBOOT, "reboot", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_GET_AUDIO_MAP, "get-audio-map", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS, "add-audio-mappings", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS, "remove-audio-mappings", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_GET_VIDEO_MAP, "get-video-map", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS, "add-video-mappings", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS, "remove-video-mappings", NULL, },
|
|
||||||
{ AVB_AECP_AEM_CMD_GET_SENSOR_MAP, "get-sensor-map", NULL, }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline const struct cmd_info *find_cmd_info(uint16_t type, const char *name)
|
static const struct cmd_info cmd_info_milan_v12[] = {
|
||||||
{
|
/** Milan V1.2 should not implement acquire */
|
||||||
SPA_FOR_EACH_ELEMENT_VAR(cmd_info, i) {
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ACQUIRE_ENTITY, true,
|
||||||
if ((name == NULL && type == i->type) ||
|
direct_reply_not_supported),
|
||||||
(name != NULL && spa_streq(name, i->name)))
|
|
||||||
return i;
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_LOCK_ENTITY, false,
|
||||||
}
|
handle_cmd_lock_entity_milan_v12),
|
||||||
return NULL;
|
|
||||||
}
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_ENTITY_AVAILABLE, true,
|
||||||
|
handle_cmd_entity_available_milan_v12),
|
||||||
|
|
||||||
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_STREAM_FORMAT, false,
|
||||||
|
handle_cmd_set_stream_format_milan_v12),
|
||||||
|
|
||||||
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_STREAM_FORMAT, true,
|
||||||
|
handle_cmd_get_stream_format_milan_v12),
|
||||||
|
|
||||||
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_CONFIGURATION, false,
|
||||||
|
handle_cmd_set_configuration_milan_v12),
|
||||||
|
|
||||||
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_CONFIGURATION, false,
|
||||||
|
handle_cmd_get_configuration_common),
|
||||||
|
|
||||||
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_READ_DESCRIPTOR, true,
|
||||||
|
handle_read_descriptor_common),
|
||||||
|
|
||||||
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION,
|
||||||
|
false, handle_cmd_register_unsol_notif_milan_v12),
|
||||||
|
|
||||||
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION,
|
||||||
|
false, handle_cmd_deregister_unsol_notif_milan_v12),
|
||||||
|
|
||||||
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_AVB_INFO, true,
|
||||||
|
handle_get_avb_info_common),
|
||||||
|
|
||||||
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_NAME, false,
|
||||||
|
handle_cmd_set_name_common),
|
||||||
|
|
||||||
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_NAME, true,
|
||||||
|
handle_cmd_get_name_common),
|
||||||
|
|
||||||
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE, false,
|
||||||
|
handle_cmd_set_clock_source_milan_v12),
|
||||||
|
|
||||||
|
AECP_AEM_HANDLE_CMD(AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE, true,
|
||||||
|
handle_cmd_get_clock_source_milan_v12),
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct {
|
||||||
|
const struct cmd_info *cmd_info;
|
||||||
|
size_t count;
|
||||||
|
} cmd_info_modes[AVB_MODE_MAX] = {
|
||||||
|
[AVB_MODE_LEGACY] = {
|
||||||
|
.cmd_info = cmd_info_avb_legacy,
|
||||||
|
.count = SPA_N_ELEMENTS(cmd_info_avb_legacy),
|
||||||
|
},
|
||||||
|
[AVB_MODE_MILAN_V12] = {
|
||||||
|
.cmd_info = cmd_info_milan_v12,
|
||||||
|
.count = SPA_N_ELEMENTS(cmd_info_milan_v12),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len)
|
int avb_aecp_aem_handle_command(struct aecp *aecp, const void *m, int len)
|
||||||
{
|
{
|
||||||
const struct avb_ethernet_header *h = m;
|
const struct avb_ethernet_header *h = m;
|
||||||
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
const struct avb_packet_aecp_aem *p = SPA_PTROFF(h, sizeof(*h), void);
|
||||||
uint16_t cmd_type;
|
uint16_t cmd_type;
|
||||||
|
struct server *server = aecp->server;
|
||||||
const struct cmd_info *info;
|
const struct cmd_info *info;
|
||||||
|
struct timespec ts_now = {0};
|
||||||
|
int64_t now;
|
||||||
|
|
||||||
cmd_type = AVB_PACKET_AEM_GET_COMMAND_TYPE(p);
|
cmd_type = AVB_PACKET_AEM_GET_COMMAND_TYPE(p);
|
||||||
|
|
||||||
info = find_cmd_info(cmd_type, NULL);
|
pw_log_info("mode: %s aem command %s",
|
||||||
if (info == NULL)
|
get_avb_mode_str(server->avb_mode), cmd_names[cmd_type]);
|
||||||
|
|
||||||
|
if (cmd_info_modes[server->avb_mode].count <= cmd_type) {
|
||||||
|
pw_log_warn("Too many %d vs exp. %zu\n", cmd_type,
|
||||||
|
cmd_info_modes[server->avb_mode].count);
|
||||||
|
return reply_not_implemented(aecp, m, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
info = &cmd_info_modes[server->avb_mode].cmd_info[cmd_type];
|
||||||
|
if (!info || !info->handle_command )
|
||||||
return reply_not_implemented(aecp, m, len);
|
return reply_not_implemented(aecp, m, len);
|
||||||
|
|
||||||
pw_log_info("aem command %s", info->name);
|
|
||||||
|
|
||||||
if (info->handle == NULL)
|
if (clock_gettime(CLOCK_TAI, &ts_now)) {
|
||||||
return reply_not_implemented(aecp, m, len);
|
pw_log_warn("clock_gettime(CLOCK_TAI): %m\n");
|
||||||
|
}
|
||||||
|
|
||||||
return info->handle(aecp, m, len);
|
now = SPA_TIMESPEC_TO_NSEC(&ts_now);
|
||||||
|
|
||||||
|
return info->handle_command(aecp, now, m, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len)
|
int avb_aecp_aem_handle_response(struct aecp *aecp, const void *m, int len)
|
||||||
|
|
|
||||||
|
|
@ -6,99 +6,7 @@
|
||||||
#define AVB_AEM_H
|
#define AVB_AEM_H
|
||||||
|
|
||||||
#include "aecp.h"
|
#include "aecp.h"
|
||||||
|
#include "aecp-aem-types.h"
|
||||||
#define AVB_AECP_AEM_STATUS_SUCCESS 0
|
|
||||||
#define AVB_AECP_AEM_STATUS_NOT_IMPLEMENTED 1
|
|
||||||
#define AVB_AECP_AEM_STATUS_NO_SUCH_DESCRIPTOR 2
|
|
||||||
#define AVB_AECP_AEM_STATUS_ENTITY_LOCKED 3
|
|
||||||
#define AVB_AECP_AEM_STATUS_ENTITY_ACQUIRED 4
|
|
||||||
#define AVB_AECP_AEM_STATUS_NOT_AUTHENTICATED 5
|
|
||||||
#define AVB_AECP_AEM_STATUS_AUTHENTICATION_DISABLED 6
|
|
||||||
#define AVB_AECP_AEM_STATUS_BAD_ARGUMENTS 7
|
|
||||||
#define AVB_AECP_AEM_STATUS_NO_RESOURCES 8
|
|
||||||
#define AVB_AECP_AEM_STATUS_IN_PROGRESS 9
|
|
||||||
#define AVB_AECP_AEM_STATUS_ENTITY_MISBEHAVING 10
|
|
||||||
#define AVB_AECP_AEM_STATUS_NOT_SUPPORTED 11
|
|
||||||
#define AVB_AECP_AEM_STATUS_STREAM_IS_RUNNING 12
|
|
||||||
|
|
||||||
#define AVB_AECP_AEM_CMD_ACQUIRE_ENTITY 0x0000
|
|
||||||
#define AVB_AECP_AEM_CMD_LOCK_ENTITY 0x0001
|
|
||||||
#define AVB_AECP_AEM_CMD_ENTITY_AVAILABLE 0x0002
|
|
||||||
#define AVB_AECP_AEM_CMD_CONTROLLER_AVAILABLE 0x0003
|
|
||||||
#define AVB_AECP_AEM_CMD_READ_DESCRIPTOR 0x0004
|
|
||||||
#define AVB_AECP_AEM_CMD_WRITE_DESCRIPTOR 0x0005
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_CONFIGURATION 0x0006
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_CONFIGURATION 0x0007
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_STREAM_FORMAT 0x0008
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_STREAM_FORMAT 0x0009
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_VIDEO_FORMAT 0x000a
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_VIDEO_FORMAT 0x000b
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_SENSOR_FORMAT 0x000c
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_SENSOR_FORMAT 0x000d
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_STREAM_INFO 0x000e
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_STREAM_INFO 0x000f
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_NAME 0x0010
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_NAME 0x0011
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_ASSOCIATION_ID 0x0012
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_ASSOCIATION_ID 0x0013
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_SAMPLING_RATE 0x0014
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_SAMPLING_RATE 0x0015
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_CLOCK_SOURCE 0x0016
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_CLOCK_SOURCE 0x0017
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_CONTROL 0x0018
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_CONTROL 0x0019
|
|
||||||
#define AVB_AECP_AEM_CMD_INCREMENT_CONTROL 0x001a
|
|
||||||
#define AVB_AECP_AEM_CMD_DECREMENT_CONTROL 0x001b
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_SIGNAL_SELECTOR 0x001c
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_SIGNAL_SELECTOR 0x001d
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_MIXER 0x001e
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_MIXER 0x001f
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_MATRIX 0x0020
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_MATRIX 0x0021
|
|
||||||
#define AVB_AECP_AEM_CMD_START_STREAMING 0x0022
|
|
||||||
#define AVB_AECP_AEM_CMD_STOP_STREAMING 0x0023
|
|
||||||
#define AVB_AECP_AEM_CMD_REGISTER_UNSOLICITED_NOTIFICATION 0x0024
|
|
||||||
#define AVB_AECP_AEM_CMD_DEREGISTER_UNSOLICITED_NOTIFICATION 0x0025
|
|
||||||
#define AVB_AECP_AEM_CMD_IDENTIFY_NOTIFICATION 0x0026
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_AVB_INFO 0x0027
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_AS_PATH 0x0028
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_COUNTERS 0x0029
|
|
||||||
#define AVB_AECP_AEM_CMD_REBOOT 0x002a
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_AUDIO_MAP 0x002b
|
|
||||||
#define AVB_AECP_AEM_CMD_ADD_AUDIO_MAPPINGS 0x002c
|
|
||||||
#define AVB_AECP_AEM_CMD_REMOVE_AUDIO_MAPPINGS 0x002d
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_VIDEO_MAP 0x002e
|
|
||||||
#define AVB_AECP_AEM_CMD_ADD_VIDEO_MAPPINGS 0x002f
|
|
||||||
#define AVB_AECP_AEM_CMD_REMOVE_VIDEO_MAPPINGS 0x0030
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_SENSOR_MAP 0x0031
|
|
||||||
#define AVB_AECP_AEM_CMD_ADD_SENSOR_MAPPINGS 0x0032
|
|
||||||
#define AVB_AECP_AEM_CMD_REMOVE_SENSOR_MAPPINGS 0x0033
|
|
||||||
#define AVB_AECP_AEM_CMD_START_OPERATION 0x0034
|
|
||||||
#define AVB_AECP_AEM_CMD_ABORT_OPERATION 0x0035
|
|
||||||
#define AVB_AECP_AEM_CMD_OPERATION_STATUS 0x0036
|
|
||||||
#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY 0x0037
|
|
||||||
#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY 0x0038
|
|
||||||
#define AVB_AECP_AEM_CMD_AUTH_GET_KEY_LIST 0x0039
|
|
||||||
#define AVB_AECP_AEM_CMD_AUTH_GET_KEY 0x003a
|
|
||||||
#define AVB_AECP_AEM_CMD_AUTH_ADD_KEY_TO_CHAIN 0x003b
|
|
||||||
#define AVB_AECP_AEM_CMD_AUTH_DELETE_KEY_FROM_CHAIN 0x003c
|
|
||||||
#define AVB_AECP_AEM_CMD_AUTH_GET_KEYCHAIN_LIST 0x003d
|
|
||||||
#define AVB_AECP_AEM_CMD_AUTH_GET_IDENTITY 0x003e
|
|
||||||
#define AVB_AECP_AEM_CMD_AUTH_ADD_TOKEN 0x003f
|
|
||||||
#define AVB_AECP_AEM_CMD_AUTH_DELETE_TOKEN 0x0040
|
|
||||||
#define AVB_AECP_AEM_CMD_AUTHENTICATE 0x0041
|
|
||||||
#define AVB_AECP_AEM_CMD_DEAUTHENTICATE 0x0042
|
|
||||||
#define AVB_AECP_AEM_CMD_ENABLE_TRANSPORT_SECURITY 0x0043
|
|
||||||
#define AVB_AECP_AEM_CMD_DISABLE_TRANSPORT_SECURITY 0x0044
|
|
||||||
#define AVB_AECP_AEM_CMD_ENABLE_STREAM_ENCRYPTION 0x0045
|
|
||||||
#define AVB_AECP_AEM_CMD_DISABLE_STREAM_ENCRYPTION 0x0046
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_MEMORY_OBJECT_LENGTH 0x0047
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_MEMORY_OBJECT_LENGTH 0x0048
|
|
||||||
#define AVB_AECP_AEM_CMD_SET_STREAM_BACKUP 0x0049
|
|
||||||
#define AVB_AECP_AEM_CMD_GET_STREAM_BACKUP 0x004a
|
|
||||||
#define AVB_AECP_AEM_CMD_EXPANSION 0x7fff
|
|
||||||
|
|
||||||
#define AVB_AEM_ACQUIRE_ENTITY_PERSISTENT_FLAG (1<<0)
|
|
||||||
|
|
||||||
struct avb_packet_aecp_aem_acquire {
|
struct avb_packet_aecp_aem_acquire {
|
||||||
uint32_t flags;
|
uint32_t flags;
|
||||||
|
|
@ -114,6 +22,12 @@ struct avb_packet_aecp_aem_lock {
|
||||||
uint16_t descriptor_id;
|
uint16_t descriptor_id;
|
||||||
} __attribute__ ((__packed__));
|
} __attribute__ ((__packed__));
|
||||||
|
|
||||||
|
struct avb_packet_aecp_aem_available {
|
||||||
|
uint32_t flags;
|
||||||
|
uint64_t acquired_controller_guid;
|
||||||
|
uint64_t lock_controller_guid;
|
||||||
|
} __attribute__ ((__packed__));
|
||||||
|
|
||||||
struct avb_packet_aecp_aem_read_descriptor {
|
struct avb_packet_aecp_aem_read_descriptor {
|
||||||
uint16_t configuration;
|
uint16_t configuration;
|
||||||
uint8_t reserved[2];
|
uint8_t reserved[2];
|
||||||
|
|
@ -315,6 +229,8 @@ struct avb_packet_aecp_aem {
|
||||||
uint8_t payload[0];
|
uint8_t payload[0];
|
||||||
} __attribute__ ((__packed__));
|
} __attribute__ ((__packed__));
|
||||||
|
|
||||||
|
#define AVB_PACKET_CONTROL_DATA_OFFSET (12U)
|
||||||
|
|
||||||
#define AVB_PACKET_AEM_SET_COMMAND_TYPE(p,v) ((p)->cmd1 = ((v) >> 8),(p)->cmd2 = (v))
|
#define AVB_PACKET_AEM_SET_COMMAND_TYPE(p,v) ((p)->cmd1 = ((v) >> 8),(p)->cmd2 = (v))
|
||||||
|
|
||||||
#define AVB_PACKET_AEM_GET_COMMAND_TYPE(p) ((p)->cmd1 << 8 | (p)->cmd2)
|
#define AVB_PACKET_AEM_GET_COMMAND_TYPE(p) ((p)->cmd1 << 8 | (p)->cmd2)
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,12 @@
|
||||||
#define server_emit_periodic(s,n) server_emit(s, periodic, 0, n)
|
#define server_emit_periodic(s,n) server_emit(s, periodic, 0, n)
|
||||||
#define server_emit_command(s,n,c,a,f) server_emit(s, command, 0, n, c, a, f)
|
#define server_emit_command(s,n,c,a,f) server_emit(s, command, 0, n, c, a, f)
|
||||||
|
|
||||||
|
|
||||||
|
static const char *avb_mode_str[] = {
|
||||||
|
[AVB_MODE_LEGACY] = "AVB Legacy",
|
||||||
|
[AVB_MODE_MILAN_V12] = "Milan V1.2",
|
||||||
|
};
|
||||||
|
|
||||||
static void on_timer_event(void *data)
|
static void on_timer_event(void *data)
|
||||||
{
|
{
|
||||||
struct server *server = data;
|
struct server *server = data;
|
||||||
|
|
@ -252,16 +258,20 @@ struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props)
|
||||||
spa_list_append(&impl->servers, &server->link);
|
spa_list_append(&impl->servers, &server->link);
|
||||||
str = spa_dict_lookup(props, "ifname");
|
str = spa_dict_lookup(props, "ifname");
|
||||||
server->ifname = str ? strdup(str) : NULL;
|
server->ifname = str ? strdup(str) : NULL;
|
||||||
|
|
||||||
|
if ((str = spa_dict_lookup(props, "milan")) != NULL && spa_atob(str))
|
||||||
|
server->avb_mode = AVB_MODE_MILAN_V12;
|
||||||
|
else
|
||||||
|
server->avb_mode = AVB_MODE_LEGACY;
|
||||||
|
|
||||||
spa_hook_list_init(&server->listener_list);
|
spa_hook_list_init(&server->listener_list);
|
||||||
spa_list_init(&server->descriptors);
|
spa_list_init(&server->descriptors);
|
||||||
spa_list_init(&server->streams);
|
|
||||||
|
|
||||||
server->debug_messages = false;
|
server->debug_messages = false;
|
||||||
|
|
||||||
if ((res = setup_socket(server)) < 0)
|
if ((res = setup_socket(server)) < 0)
|
||||||
goto error_free;
|
goto error_free;
|
||||||
|
|
||||||
init_descriptors(server);
|
|
||||||
|
|
||||||
server->mrp = avb_mrp_new(server);
|
server->mrp = avb_mrp_new(server);
|
||||||
if (server->mrp == NULL)
|
if (server->mrp == NULL)
|
||||||
|
|
@ -284,11 +294,10 @@ struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props)
|
||||||
avb_mrp_attribute_begin(server->domain_attr->mrp, 0);
|
avb_mrp_attribute_begin(server->domain_attr->mrp, 0);
|
||||||
avb_mrp_attribute_join(server->domain_attr->mrp, 0, true);
|
avb_mrp_attribute_join(server->domain_attr->mrp, 0, true);
|
||||||
|
|
||||||
server_create_stream(server, SPA_DIRECTION_INPUT, 0);
|
|
||||||
server_create_stream(server, SPA_DIRECTION_OUTPUT, 0);
|
|
||||||
|
|
||||||
avb_maap_reserve(server->maap, 1);
|
avb_maap_reserve(server->maap, 1);
|
||||||
|
|
||||||
|
init_descriptors(server);
|
||||||
|
|
||||||
return server;
|
return server;
|
||||||
|
|
||||||
error_free:
|
error_free:
|
||||||
|
|
@ -308,10 +317,17 @@ void avdecc_server_free(struct server *server)
|
||||||
{
|
{
|
||||||
struct impl *impl = server->impl;
|
struct impl *impl = server->impl;
|
||||||
|
|
||||||
|
server_destroy_descriptors(server);
|
||||||
spa_list_remove(&server->link);
|
spa_list_remove(&server->link);
|
||||||
if (server->source)
|
if (server->source)
|
||||||
pw_loop_destroy_source(impl->loop, server->source);
|
pw_loop_destroy_source(impl->loop, server->source);
|
||||||
pw_timer_queue_cancel(&server->timer);
|
pw_timer_queue_cancel(&server->timer);
|
||||||
spa_hook_list_clean(&server->listener_list);
|
spa_hook_list_clean(&server->listener_list);
|
||||||
|
free(server->ifname);
|
||||||
free(server);
|
free(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char *get_avb_mode_str(enum avb_mode mode)
|
||||||
|
{
|
||||||
|
return avb_mode_str[mode];
|
||||||
|
}
|
||||||
|
|
|
||||||
824
src/modules/module-avb/descriptors.c
Normal file
824
src/modules/module-avb/descriptors.c
Normal file
|
|
@ -0,0 +1,824 @@
|
||||||
|
/* PipeWire */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2022 Wim Taymans */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki (alexandre.malki@kebag-logic.com) */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#include "adp.h"
|
||||||
|
#include "aecp-aem.h"
|
||||||
|
#include "aecp-aem-types.h"
|
||||||
|
#include "aecp-aem-descriptors.h"
|
||||||
|
#include "aecp-aem-controls.h"
|
||||||
|
#include "es-builder.h"
|
||||||
|
|
||||||
|
#include "entity-model-milan-v12.h"
|
||||||
|
|
||||||
|
static void init_descriptor_legacy_avb(struct server *server)
|
||||||
|
{
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_STRINGS, 0,
|
||||||
|
sizeof(struct avb_aem_desc_strings),
|
||||||
|
&(struct avb_aem_desc_strings)
|
||||||
|
{
|
||||||
|
.string_0 = "PipeWire",
|
||||||
|
.string_1 = "Configuration 1",
|
||||||
|
.string_2 = "Wim Taymans",
|
||||||
|
});
|
||||||
|
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_LOCALE, 0,
|
||||||
|
sizeof(struct avb_aem_desc_locale),
|
||||||
|
&(struct avb_aem_desc_locale)
|
||||||
|
{
|
||||||
|
.locale_identifier = "en-EN",
|
||||||
|
.number_of_strings = htons(1),
|
||||||
|
.base_strings = htons(0)
|
||||||
|
});
|
||||||
|
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0,
|
||||||
|
sizeof(struct avb_aem_desc_entity),
|
||||||
|
&(struct avb_aem_desc_entity)
|
||||||
|
{
|
||||||
|
.entity_id = htobe64(server->entity_id),
|
||||||
|
.entity_model_id = htobe64(0),
|
||||||
|
.entity_capabilities = htonl(
|
||||||
|
AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED |
|
||||||
|
AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED |
|
||||||
|
AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED |
|
||||||
|
AVB_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID |
|
||||||
|
AVB_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID),
|
||||||
|
|
||||||
|
.talker_stream_sources = htons(8),
|
||||||
|
.talker_capabilities = htons(
|
||||||
|
AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED |
|
||||||
|
AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE),
|
||||||
|
.listener_stream_sinks = htons(8),
|
||||||
|
.listener_capabilities = htons(
|
||||||
|
AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED |
|
||||||
|
AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK),
|
||||||
|
.controller_capabilities = htons(0),
|
||||||
|
.available_index = htonl(0),
|
||||||
|
.association_id = htobe64(0),
|
||||||
|
.entity_name = "PipeWire",
|
||||||
|
.vendor_name_string = htons(2),
|
||||||
|
.model_name_string = htons(0),
|
||||||
|
.firmware_version = "0.3.48",
|
||||||
|
.group_name = "",
|
||||||
|
.serial_number = "",
|
||||||
|
.configurations_count = htons(1),
|
||||||
|
.current_configuration = htons(0)
|
||||||
|
});
|
||||||
|
struct {
|
||||||
|
struct avb_aem_desc_configuration desc;
|
||||||
|
struct avb_aem_desc_descriptor_count descriptor_counts[8];
|
||||||
|
} __attribute__ ((__packed__)) config =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.object_name = "Configuration 1",
|
||||||
|
.localized_description = htons(1),
|
||||||
|
.descriptor_counts_count = htons(8),
|
||||||
|
.descriptor_counts_offset = htons(
|
||||||
|
4 + sizeof(struct avb_aem_desc_configuration)),
|
||||||
|
},
|
||||||
|
.descriptor_counts = {
|
||||||
|
{ htons(AVB_AEM_DESC_AUDIO_UNIT), htons(1) },
|
||||||
|
{ htons(AVB_AEM_DESC_STREAM_INPUT), htons(1) },
|
||||||
|
{ htons(AVB_AEM_DESC_STREAM_OUTPUT), htons(1) },
|
||||||
|
{ htons(AVB_AEM_DESC_AVB_INTERFACE), htons(1) },
|
||||||
|
{ htons(AVB_AEM_DESC_CLOCK_SOURCE), htons(1) },
|
||||||
|
{ htons(AVB_AEM_DESC_CONTROL), htons(2) },
|
||||||
|
{ htons(AVB_AEM_DESC_LOCALE), htons(1) },
|
||||||
|
{ htons(AVB_AEM_DESC_CLOCK_DOMAIN), htons(1) }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_CONFIGURATION, 0,
|
||||||
|
sizeof(config), &config);
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct avb_aem_desc_audio_unit desc;
|
||||||
|
struct avb_aem_desc_sampling_rate sampling_rates[6];
|
||||||
|
} __attribute__ ((__packed__)) audio_unit =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.object_name = "PipeWire",
|
||||||
|
.localized_description = htons(0),
|
||||||
|
.clock_domain_index = htons(0),
|
||||||
|
.number_of_stream_input_ports = htons(1),
|
||||||
|
.base_stream_input_port = htons(0),
|
||||||
|
.number_of_stream_output_ports = htons(1),
|
||||||
|
.base_stream_output_port = htons(0),
|
||||||
|
.number_of_external_input_ports = htons(8),
|
||||||
|
.base_external_input_port = htons(0),
|
||||||
|
.number_of_external_output_ports = htons(8),
|
||||||
|
.base_external_output_port = htons(0),
|
||||||
|
.number_of_internal_input_ports = htons(0),
|
||||||
|
.base_internal_input_port = htons(0),
|
||||||
|
.number_of_internal_output_ports = htons(0),
|
||||||
|
.base_internal_output_port = htons(0),
|
||||||
|
.number_of_controls = htons(0),
|
||||||
|
.base_control = htons(0),
|
||||||
|
.number_of_signal_selectors = htons(0),
|
||||||
|
.base_signal_selector = htons(0),
|
||||||
|
.number_of_mixers = htons(0),
|
||||||
|
.base_mixer = htons(0),
|
||||||
|
.number_of_matrices = htons(0),
|
||||||
|
.base_matrix = htons(0),
|
||||||
|
.number_of_splitters = htons(0),
|
||||||
|
.base_splitter = htons(0),
|
||||||
|
.number_of_combiners = htons(0),
|
||||||
|
.base_combiner = htons(0),
|
||||||
|
.number_of_demultiplexers = htons(0),
|
||||||
|
.base_demultiplexer = htons(0),
|
||||||
|
.number_of_multiplexers = htons(0),
|
||||||
|
.base_multiplexer = htons(0),
|
||||||
|
.number_of_transcoders = htons(0),
|
||||||
|
.base_transcoder = htons(0),
|
||||||
|
.number_of_control_blocks = htons(0),
|
||||||
|
.base_control_block = htons(0),
|
||||||
|
.current_sampling_rate = htonl(48000),
|
||||||
|
.sampling_rates_offset = htons(
|
||||||
|
4 + sizeof(struct avb_aem_desc_audio_unit)),
|
||||||
|
.sampling_rates_count = htons(6),
|
||||||
|
},
|
||||||
|
.sampling_rates = {
|
||||||
|
{ .pull_frequency = htonl(44100) },
|
||||||
|
{ .pull_frequency = htonl(48000) },
|
||||||
|
{ .pull_frequency = htonl(88200) },
|
||||||
|
{ .pull_frequency = htonl(96000) },
|
||||||
|
{ .pull_frequency = htonl(176400) },
|
||||||
|
{ .pull_frequency = htonl(192000) },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_AUDIO_UNIT, 0,
|
||||||
|
sizeof(audio_unit), &audio_unit);
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct avb_aem_desc_stream desc;
|
||||||
|
uint64_t stream_formats[6];
|
||||||
|
} __attribute__ ((__packed__)) stream_input_0 =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.object_name = "Stream Input 1",
|
||||||
|
.localized_description = htons(0xffff),
|
||||||
|
.clock_domain_index = htons(0),
|
||||||
|
.stream_flags = htons(
|
||||||
|
AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE |
|
||||||
|
AVB_AEM_DESC_STREAM_FLAG_CLASS_A),
|
||||||
|
.current_format = htobe64(0x00a0020840000800ULL),
|
||||||
|
.formats_offset = htons(
|
||||||
|
4 + sizeof(struct avb_aem_desc_stream)),
|
||||||
|
.number_of_formats = htons(6),
|
||||||
|
.backup_talker_entity_id_0 = htobe64(0),
|
||||||
|
.backup_talker_unique_id_0 = htons(0),
|
||||||
|
.backup_talker_entity_id_1 = htobe64(0),
|
||||||
|
.backup_talker_unique_id_1 = htons(0),
|
||||||
|
.backup_talker_entity_id_2 = htobe64(0),
|
||||||
|
.backup_talker_unique_id_2 = htons(0),
|
||||||
|
.backedup_talker_entity_id = htobe64(0),
|
||||||
|
.backedup_talker_unique = htons(0),
|
||||||
|
.avb_interface_index = htons(0),
|
||||||
|
.buffer_length = htons(8)
|
||||||
|
},
|
||||||
|
.stream_formats = {
|
||||||
|
htobe64(0x00a0010860000800ULL),
|
||||||
|
htobe64(0x00a0020860000800ULL),
|
||||||
|
htobe64(0x00a0030860000800ULL),
|
||||||
|
htobe64(0x00a0040860000800ULL),
|
||||||
|
htobe64(0x00a0050860000800ULL),
|
||||||
|
htobe64(0x00a0060860000800ULL),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_INPUT, 0,
|
||||||
|
sizeof(stream_input_0), &stream_input_0);
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct avb_aem_desc_stream desc;
|
||||||
|
uint64_t stream_formats[6];
|
||||||
|
} __attribute__ ((__packed__)) stream_output_0 =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.object_name = "Stream Output 1",
|
||||||
|
.localized_description = htons(0xffff),
|
||||||
|
.clock_domain_index = htons(0),
|
||||||
|
.stream_flags = htons(
|
||||||
|
AVB_AEM_DESC_STREAM_FLAG_CLASS_A),
|
||||||
|
.current_format = htobe64(0x00a0020840000800ULL),
|
||||||
|
.formats_offset = htons(
|
||||||
|
4 + sizeof(struct avb_aem_desc_stream)),
|
||||||
|
.number_of_formats = htons(6),
|
||||||
|
.backup_talker_entity_id_0 = htobe64(0),
|
||||||
|
.backup_talker_unique_id_0 = htons(0),
|
||||||
|
.backup_talker_entity_id_1 = htobe64(0),
|
||||||
|
.backup_talker_unique_id_1 = htons(0),
|
||||||
|
.backup_talker_entity_id_2 = htobe64(0),
|
||||||
|
.backup_talker_unique_id_2 = htons(0),
|
||||||
|
.backedup_talker_entity_id = htobe64(0),
|
||||||
|
.backedup_talker_unique = htons(0),
|
||||||
|
.avb_interface_index = htons(0),
|
||||||
|
.buffer_length = htons(8)
|
||||||
|
},
|
||||||
|
.stream_formats = {
|
||||||
|
htobe64(0x00a0010860000800ULL),
|
||||||
|
htobe64(0x00a0020860000800ULL),
|
||||||
|
htobe64(0x00a0030860000800ULL),
|
||||||
|
htobe64(0x00a0040860000800ULL),
|
||||||
|
htobe64(0x00a0050860000800ULL),
|
||||||
|
htobe64(0x00a0060860000800ULL),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_OUTPUT, 0,
|
||||||
|
sizeof(stream_output_0), &stream_output_0);
|
||||||
|
|
||||||
|
struct avb_aem_desc_avb_interface avb_interface = {
|
||||||
|
.localized_description = htons(0xffff),
|
||||||
|
.interface_flags = htons(
|
||||||
|
AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED),
|
||||||
|
.clock_identity = htobe64(0),
|
||||||
|
.priority1 = 0,
|
||||||
|
.clock_class = 0,
|
||||||
|
.offset_scaled_log_variance = htons(0),
|
||||||
|
.clock_accuracy = 0,
|
||||||
|
.priority2 = 0,
|
||||||
|
.domain_number = 0,
|
||||||
|
.log_sync_interval = 0,
|
||||||
|
.log_announce_interval = 0,
|
||||||
|
.log_pdelay_interval = 0,
|
||||||
|
.port_number = 0,
|
||||||
|
};
|
||||||
|
strncpy(avb_interface.object_name, server->ifname, 63);
|
||||||
|
memcpy(avb_interface.mac_address, server->mac_addr, 6);
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0,
|
||||||
|
sizeof(avb_interface), &avb_interface);
|
||||||
|
|
||||||
|
struct avb_aem_desc_clock_source clock_source = {
|
||||||
|
.object_name = "Stream Clock",
|
||||||
|
.localized_description = htons(0xffff),
|
||||||
|
.clock_source_flags = htons(0),
|
||||||
|
.clock_source_type = htons(
|
||||||
|
AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM),
|
||||||
|
.clock_source_identifier = htobe64(0),
|
||||||
|
.clock_source_location_type = htons(AVB_AEM_DESC_STREAM_INPUT),
|
||||||
|
.clock_source_location_index = htons(0),
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_CLOCK_SOURCE, 0,
|
||||||
|
sizeof(clock_source), &clock_source);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_descriptor_milan_v12(struct server *server)
|
||||||
|
{
|
||||||
|
// TODO PERSISTENCE: retrieve the saved buffers.
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.12 - STRINGS Descriptor
|
||||||
|
* Up to 7 localized strings
|
||||||
|
*/
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_STRINGS, 0,
|
||||||
|
sizeof(struct avb_aem_desc_strings),
|
||||||
|
&(struct avb_aem_desc_strings)
|
||||||
|
{
|
||||||
|
.string_0 = DSC_STRINGS_0_DEVICE_NAME,
|
||||||
|
.string_1 = DSC_STRINGS_1_CONFIGURATION_NAME,
|
||||||
|
.string_2 = DSC_STRINGS_2_MANUFACTURER_NAME,
|
||||||
|
.string_3 = DSC_STRINGS_3_GROUP_NAME,
|
||||||
|
.string_4 = DSC_STRINGS_4_MAINTAINER_0,
|
||||||
|
.string_5 = DSC_STRINGS_4_MAINTAINER_1,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.11 - LOCALE Descriptor */
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_LOCALE, 0,
|
||||||
|
sizeof(struct avb_aem_desc_locale),
|
||||||
|
&(struct avb_aem_desc_locale)
|
||||||
|
{
|
||||||
|
.locale_identifier = DSC_LOCALE_LANGUAGE_CODE,
|
||||||
|
.number_of_strings = htons(DSC_LOCALE_NO_OF_STRINGS),
|
||||||
|
.base_strings = htons(DSC_LOCALE_BASE_STRINGS)
|
||||||
|
});
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.1 - ENTITY Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.1 */
|
||||||
|
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0,
|
||||||
|
sizeof(struct avb_aem_desc_entity),
|
||||||
|
&(struct avb_aem_desc_entity)
|
||||||
|
{
|
||||||
|
.entity_id = htobe64(DSC_ENTITY_MODEL_ENTITY_ID),
|
||||||
|
.entity_model_id = htobe64(DSC_ENTITY_MODEL_ID),
|
||||||
|
.entity_capabilities = htonl(DSC_ENTITY_MODEL_ENTITY_CAPABILITIES),
|
||||||
|
|
||||||
|
.talker_stream_sources = htons(DSC_ENTITY_MODEL_TALKER_STREAM_SOURCES),
|
||||||
|
.talker_capabilities = htons(DSC_ENTITY_MODEL_TALKER_CAPABILITIES),
|
||||||
|
|
||||||
|
.listener_stream_sinks = htons(DSC_ENTITY_MODEL_LISTENER_STREAM_SINKS),
|
||||||
|
.listener_capabilities = htons(DSC_ENTITY_MODEL_LISTENER_CAPABILITIES),
|
||||||
|
|
||||||
|
.controller_capabilities = htons(DSC_ENTITY_MODEL_CONTROLLER_CAPABILITIES),
|
||||||
|
|
||||||
|
.available_index = htonl(DSC_ENTITY_MODEL_AVAILABLE_INDEX),
|
||||||
|
.association_id = htobe64(DSC_ENTITY_MODEL_ASSOCIATION_ID),
|
||||||
|
|
||||||
|
.entity_name = DSC_ENTITY_MODEL_ENTITY_NAME,
|
||||||
|
.vendor_name_string = htons(DSC_ENTITY_MODEL_VENDOR_NAME_STRING),
|
||||||
|
.model_name_string = htons(DSC_ENTITY_MODEL_MODEL_NAME_STRING),
|
||||||
|
.firmware_version = DSC_ENTITY_MODEL_FIRMWARE_VERSION,
|
||||||
|
.group_name = DSC_ENTITY_MODEL_GROUP_NAME,
|
||||||
|
.serial_number = DSC_ENTITY_MODEL_SERIAL_NUMBER,
|
||||||
|
.configurations_count = htons(DSC_ENTITY_MODEL_CONFIGURATIONS_COUNT),
|
||||||
|
.current_configuration = htons(DSC_ENTITY_MODEL_CURRENT_CONFIGURATION)
|
||||||
|
});
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.2 - CONFIGURATION Descriptor*/
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.2 */
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct avb_aem_desc_configuration desc;
|
||||||
|
struct avb_aem_desc_descriptor_count descriptor_counts[DSC_CONFIGURATION_DESCRIPTOR_COUNTS_COUNT];
|
||||||
|
} __attribute__ ((__packed__)) config =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.object_name = DSC_CONFIGURATION_OBJECT_NAME,
|
||||||
|
.localized_description = htons(DSC_CONFIGURATION_LOCALIZED_DESCRIPTION),
|
||||||
|
.descriptor_counts_count = htons(DSC_CONFIGURATION_DESCRIPTOR_COUNTS_COUNT),
|
||||||
|
// TODO: Does it work? Was the commented out lines, now replaced with hard coded value from IEEE.
|
||||||
|
// .descriptor_counts_offset = htons(
|
||||||
|
// 4 + sizeof(struct avb_aem_desc_configuration)),
|
||||||
|
// },
|
||||||
|
.descriptor_counts_offset = htons(DSC_CONFIGURATION_DESCRIPTOR_COUNTS_OFFSET),
|
||||||
|
},
|
||||||
|
.descriptor_counts = {
|
||||||
|
{ htons(AVB_AEM_DESC_AUDIO_UNIT), htons(DSC_CONFIGURATION_NO_OF_AUDIO_UNITS) },
|
||||||
|
{ htons(AVB_AEM_DESC_STREAM_INPUT), htons(DSC_CONFIGURATION_NO_OF_STREAM_INPUTS) },
|
||||||
|
{ htons(AVB_AEM_DESC_STREAM_OUTPUT), htons(DSC_CONFIGURATION_NO_OF_STREAM_OUTPUTS) },
|
||||||
|
{ htons(AVB_AEM_DESC_AVB_INTERFACE), htons(DSC_CONFIGURATION_NO_OF_AVB_INTERFACES) },
|
||||||
|
{ htons(AVB_AEM_DESC_CLOCK_DOMAIN), htons(DSC_CONFIGURATION_NO_OF_CLOCK_DOMAINS) },
|
||||||
|
{ htons(AVB_AEM_DESC_CLOCK_SOURCE), htons(DSC_CONFIGURATION_NO_OF_CLOCK_SOURCES) },
|
||||||
|
{ htons(AVB_AEM_DESC_CONTROL), htons(DSC_CONFIGURATION_NO_OF_CONTROLS) },
|
||||||
|
{ htons(AVB_AEM_DESC_LOCALE), htons(DSC_CONFIGURATION_NO_OF_LOCALES) },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_CONFIGURATION, 0,
|
||||||
|
sizeof(config), &config);
|
||||||
|
|
||||||
|
/* Second configuration for tests*/
|
||||||
|
struct {
|
||||||
|
struct avb_aem_desc_configuration desc;
|
||||||
|
struct avb_aem_desc_descriptor_count descriptor_counts[8];
|
||||||
|
} __attribute__ ((__packed__)) config1 =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.object_name = "Non - redundant - 96kHz",
|
||||||
|
.localized_description = htons(1),
|
||||||
|
.descriptor_counts_count = htons(8),
|
||||||
|
.descriptor_counts_offset = htons(
|
||||||
|
4 + sizeof(struct avb_aem_desc_configuration)),
|
||||||
|
},
|
||||||
|
.descriptor_counts = {
|
||||||
|
{ htons(AVB_AEM_DESC_AUDIO_UNIT), htons(1) },
|
||||||
|
{ htons(AVB_AEM_DESC_STREAM_INPUT), htons(2) },
|
||||||
|
{ htons(AVB_AEM_DESC_STREAM_OUTPUT), htons(1) },
|
||||||
|
{ htons(AVB_AEM_DESC_AVB_INTERFACE), htons(1) },
|
||||||
|
{ htons(AVB_AEM_DESC_CLOCK_DOMAIN), htons(1) },
|
||||||
|
{ htons(AVB_AEM_DESC_CLOCK_SOURCE), htons(3) },
|
||||||
|
{ htons(AVB_AEM_DESC_CONTROL), htons(1) },
|
||||||
|
{ htons(AVB_AEM_DESC_LOCALE), htons(1) },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_CONFIGURATION, 1,
|
||||||
|
sizeof(config), &config1);
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.22 CONTROL Descriptor*/
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.10 */
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct avb_aem_desc_control desc;
|
||||||
|
struct avb_aem_desc_value_format value_inf;
|
||||||
|
} __attribute__ ((__packed__)) ctrl =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.object_name = DSC_CONTROL_OBJECT_NAME,
|
||||||
|
.localized_description = htons(DSC_CONTROL_LOCALIZED_DESCRIPTION),
|
||||||
|
|
||||||
|
.block_latency = htons(DSC_CONTROL_BLOCK_LATENCY),
|
||||||
|
.control_latency = htons(DSC_CONTROL_CONTROL_LATENCY),
|
||||||
|
.control_domain = htons(DSC_CONTROL_CONTROL_DOMAIN),
|
||||||
|
.control_value_type = htons(DSC_CONTROL_CONTROL_VALUE_TYPE),
|
||||||
|
.control_type = htobe64(DSC_CONTROL_CONTROL_TYPE),
|
||||||
|
.reset_time = htonl(DSC_CONTROL_RESET_TIME),
|
||||||
|
// TODO: This is not specified in Table 7-38
|
||||||
|
.descriptor_counts_offset = htons(
|
||||||
|
4 + sizeof(struct avb_aem_desc_control)),
|
||||||
|
.number_of_values = htons(1),
|
||||||
|
.signal_type = htons(0xffff),
|
||||||
|
.signal_index = htons(0),
|
||||||
|
.signal_output = htons(0),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
.minimum = DSC_CONTROL_IDENTIFY_MIN,
|
||||||
|
.maximum = DSC_CONTROL_IDENTIFY_MAX,
|
||||||
|
.step = DSC_CONTROL_IDENTIFY_STEP,
|
||||||
|
.default_value = DSC_CONTROL_IDENTIFY_DEFAULT_VALUE,
|
||||||
|
.current_value = DSC_CONTROL_IDENTIFY_CURRENT_VALUE,
|
||||||
|
.localized_description = htons(DSC_CONTROL_LOCALIZED_DESCRIPTION),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_CONTROL, 0,
|
||||||
|
sizeof(ctrl), &ctrl);
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.19 AUDIO_MAP Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.9 */
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct avb_aem_desc_audio_map desc;
|
||||||
|
struct avb_aem_audio_mapping_format maps[DSC_AUDIO_MAPS_NO_OF_MAPPINGS];
|
||||||
|
} __attribute__((__packed__)) maps_input = {
|
||||||
|
.desc = {
|
||||||
|
.mapping_offset = htons(AVB_AEM_AUDIO_MAPPING_FORMAT_OFFSET),
|
||||||
|
.number_of_mappings = htons(DSC_AUDIO_MAPS_NO_OF_MAPPINGS),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (uint32_t map_idx = 0; map_idx < DSC_AUDIO_MAPS_NO_OF_MAPPINGS; map_idx++) {
|
||||||
|
maps_input.maps[map_idx].mapping_stream_index = htons(DSC_AUDIO_MAPS_MAPPING_STREAM_INDEX);
|
||||||
|
maps_input.maps[map_idx].mapping_cluster_channel = htons(DSC_AUDIO_MAPS_MAPPING_CLUSTER_CHANNEL);
|
||||||
|
maps_input.maps[map_idx].mapping_cluster_offset = htons(map_idx);
|
||||||
|
maps_input.maps[map_idx].mapping_stream_channel = htons(map_idx);
|
||||||
|
}
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_AUDIO_MAP, 0,
|
||||||
|
sizeof(maps_input), &maps_input);
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct avb_aem_desc_audio_map desc;
|
||||||
|
struct avb_aem_audio_mapping_format maps[DSC_AUDIO_MAPS_NO_OF_MAPPINGS];
|
||||||
|
} __attribute__((__packed__)) maps_output= {
|
||||||
|
.desc = {
|
||||||
|
.mapping_offset = htons(AVB_AEM_AUDIO_MAPPING_FORMAT_OFFSET),
|
||||||
|
.number_of_mappings = htons(DSC_AUDIO_MAPS_NO_OF_MAPPINGS),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (uint32_t map_idx = 0; map_idx < DSC_AUDIO_MAPS_NO_OF_MAPPINGS; map_idx++) {
|
||||||
|
maps_output.maps[map_idx].mapping_stream_index = htons(DSC_AUDIO_MAPS_MAPPING_STREAM_INDEX);
|
||||||
|
maps_output.maps[map_idx].mapping_cluster_channel = htons(DSC_AUDIO_MAPS_MAPPING_CLUSTER_CHANNEL);
|
||||||
|
maps_output.maps[map_idx].mapping_cluster_offset = htons(DSC_AUDIO_MAPS_NO_OF_MAPPINGS+map_idx);
|
||||||
|
maps_output.maps[map_idx].mapping_stream_channel = htons(DSC_AUDIO_MAPS_NO_OF_MAPPINGS+map_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_AUDIO_MAP, 1,
|
||||||
|
sizeof(maps_output), &maps_output);
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.16 AUDIO_CLUSTER Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.8 */
|
||||||
|
|
||||||
|
struct avb_aem_desc_audio_cluster clusters[DSC_AUDIO_CLUSTER_NO_OF_CLUSTERS];
|
||||||
|
|
||||||
|
for (uint32_t cluster_idx = 0; cluster_idx < DSC_AUDIO_CLUSTER_NO_OF_CLUSTERS; cluster_idx++) {
|
||||||
|
memset(clusters[cluster_idx].object_name, 0,
|
||||||
|
sizeof(clusters[cluster_idx].object_name));
|
||||||
|
// TODO: Make this scale automatically
|
||||||
|
if (cluster_idx < 8) {
|
||||||
|
snprintf(clusters[cluster_idx].object_name, DSC_AUDIO_CLUSTER_OBJECT_NAME_LEN_IN_OCTET-1,
|
||||||
|
"Input %2u", cluster_idx);
|
||||||
|
} else {
|
||||||
|
snprintf(clusters[cluster_idx].object_name, DSC_AUDIO_CLUSTER_OBJECT_NAME_LEN_IN_OCTET-1,
|
||||||
|
"Output %2u", cluster_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
clusters[cluster_idx].localized_description = htons(DSC_AUDIO_CLUSTER_LOCALIZED_DESCRIPTION);
|
||||||
|
clusters[cluster_idx].signal_type = htons(DSC_AUDIO_CLUSTER_SIGNAL_TYPE);
|
||||||
|
clusters[cluster_idx].signal_index = htons(DSC_AUDIO_CLUSTER_SIGNAL_INDEX);
|
||||||
|
clusters[cluster_idx].signal_output = htons(DSC_AUDIO_CLUSTER_SIGNAL_OUTPUT);
|
||||||
|
clusters[cluster_idx].path_latency = htonl(DSC_AUDIO_CLUSTER_PATH_LATENCY_IN_NS);
|
||||||
|
clusters[cluster_idx].block_latency = htonl(DSC_AUDIO_CLUSTER_BLOCK_LATENCY_IN_NS);
|
||||||
|
clusters[cluster_idx].channel_count = htons(DSC_AUDIO_CLUSTER_CHANNEL_COUNT);
|
||||||
|
clusters[cluster_idx].format = DSC_AUDIO_CLUSTER_FORMAT;
|
||||||
|
clusters[cluster_idx].aes3_data_type_ref = DSC_AUDIO_CLUSTER_AES3_DATA_TYPE_REF;
|
||||||
|
clusters[cluster_idx].aes3_data_type = htons(DSC_AUDIO_CLUSTER_AES3_DATA_TYPE);
|
||||||
|
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_AUDIO_CLUSTER, cluster_idx,
|
||||||
|
sizeof(clusters[0]), &clusters[cluster_idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.13 STREAM_PORT_INPUT Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.7 */
|
||||||
|
|
||||||
|
struct avb_aem_desc_stream_port stream_port_input0 = {
|
||||||
|
.clock_domain_index = htons(DSC_STREAM_PORT_INPUT_CLOCK_DOMAIN_INDEX),
|
||||||
|
.port_flags = htons(DSC_STREAM_PORT_INPUT_PORT_FLAGS),
|
||||||
|
.number_of_controls = htons(DSC_STREAM_PORT_INPUT_NUMBER_OF_CONTROLS),
|
||||||
|
.base_control = htons(DSC_STREAM_PORT_INPUT_BASE_CONTROL),
|
||||||
|
.number_of_clusters = htons(DSC_STREAM_PORT_INPUT_NUMBER_OF_CLUSTERS),
|
||||||
|
.base_cluster = htons(DSC_STREAM_PORT_INPUT_BASE_CLUSTER),
|
||||||
|
.number_of_maps = htons(DSC_STREAM_PORT_INPUT_NUMBER_OF_MAPS),
|
||||||
|
.base_map = htons(DSC_STREAM_PORT_INPUT_BASE_MAP),
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_PORT_INPUT, 0,
|
||||||
|
sizeof(stream_port_input0), &stream_port_input0);
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.13 STREAM_PORT_OUTPUT Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.7 */
|
||||||
|
struct avb_aem_desc_stream_port stream_port_output0 = {
|
||||||
|
.clock_domain_index = htons(DSC_STREAM_PORT_OUTPUT_CLOCK_DOMAIN_INDEX),
|
||||||
|
.port_flags = htons(DSC_STREAM_PORT_OUTPUT_PORT_FLAGS),
|
||||||
|
.number_of_controls = htons(DSC_STREAM_PORT_OUTPUT_NUMBER_OF_CONTROLS),
|
||||||
|
.base_control = htons(DSC_STREAM_PORT_OUTPUT_BASE_CONTROL),
|
||||||
|
.number_of_clusters = htons(DSC_STREAM_PORT_OUTPUT_NUMBER_OF_CLUSTERS),
|
||||||
|
.base_cluster = htons(DSC_STREAM_PORT_OUTPUT_BASE_CLUSTER),
|
||||||
|
.number_of_maps = htons(DSC_STREAM_PORT_OUTPUT_NUMBER_OF_MAPS),
|
||||||
|
.base_map = htons(DSC_STREAM_PORT_OUTPUT_BASE_MAP),
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_PORT_OUTPUT, 0,
|
||||||
|
sizeof(stream_port_output0), &stream_port_output0);
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.3 AUDIO_UNIT Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.3 */
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct avb_aem_desc_audio_unit desc;
|
||||||
|
struct avb_aem_desc_sampling_rate sampling_rates[DSC_AUDIO_UNIT_SUPPORTED_SAMPLING_RATE_COUNT];
|
||||||
|
} __attribute__ ((__packed__)) audio_unit =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.object_name = DSC_AUDIO_UNIT_OBJECT_NAME,
|
||||||
|
.localized_description = htons(DSC_AUDIO_UNIT_LOCALIZED_DESCRIPTION),
|
||||||
|
.clock_domain_index = htons(DSC_AUDIO_UNIT_CLOCK_DOMAIN_INDEX),
|
||||||
|
.number_of_stream_input_ports = htons(DSC_AUDIO_UNIT_NUMBER_OF_STREAM_INPUT_PORTS),
|
||||||
|
.base_stream_input_port = htons(DSC_AUDIO_UNIT_BASE_STREAM_INPUT_PORT),
|
||||||
|
.number_of_stream_output_ports = htons(DSC_AUDIO_UNIT_NUMBER_OF_STREAM_OUTPUT_PORTS),
|
||||||
|
.base_stream_output_port = htons(DSC_AUDIO_UNIT_BASE_STREAM_OUTPUT_PORT),
|
||||||
|
.number_of_external_input_ports = htons(DSC_AUDIO_UNIT_NUMBER_OF_EXTERNAL_INPUT_PORTS),
|
||||||
|
.base_external_input_port = htons(DSC_AUDIO_UNIT_BASE_EXTERNAL_INPUT_PORT),
|
||||||
|
.number_of_external_output_ports = htons(DSC_AUDIO_UNIT_NUMBER_OF_EXTERNAL_OUTPUT_PORTS),
|
||||||
|
.base_external_output_port = htons(DSC_AUDIO_UNIT_BASE_EXTERNAL_OUTPUT_PORT),
|
||||||
|
.number_of_internal_input_ports = htons(DSC_AUDIO_UNIT_NUMBER_OF_INTERNAL_INPUT_PORTS),
|
||||||
|
.base_internal_input_port = htons(DSC_AUDIO_UNIT_BASE_INTERNAL_INPUT_PORT),
|
||||||
|
.number_of_internal_output_ports = htons(DSC_AUDIO_UNIT_NUMBER_OF_INTERNAL_OUTPUT_PORTS),
|
||||||
|
.base_internal_output_port = htons(DSC_AUDIO_UNIT_BASE_INTERNAL_OUTPUT_PORT),
|
||||||
|
.number_of_controls = htons(DSC_AUDIO_UNIT_NUMBER_OF_CONTROLS),
|
||||||
|
.base_control = htons(DSC_AUDIO_UNIT_BASE_CONTROL),
|
||||||
|
.number_of_signal_selectors = htons(DSC_AUDIO_UNIT_NUMBER_OF_SIGNAL_SELECTORS),
|
||||||
|
.base_signal_selector = htons(DSC_AUDIO_UNIT_BASE_SIGNAL_SELECTOR),
|
||||||
|
.number_of_mixers = htons(DSC_AUDIO_UNIT_NUMBER_OF_MIXERS),
|
||||||
|
.base_mixer = htons(DSC_AUDIO_UNIT_BASE_MIXER),
|
||||||
|
.number_of_matrices = htons(DSC_AUDIO_UNIT_NUMBER_OF_MATRICES),
|
||||||
|
.base_matrix = htons(DSC_AUDIO_UNIT_BASE_MATRIX),
|
||||||
|
.number_of_splitters = htons(DSC_AUDIO_UNIT_NUMBER_OF_SPLITTERS),
|
||||||
|
.base_splitter = htons(DSC_AUDIO_UNIT_BASE_SPLITTER),
|
||||||
|
.number_of_combiners = htons(DSC_AUDIO_UNIT_NUMBER_OF_COMBINERS),
|
||||||
|
.base_combiner = htons(DSC_AUDIO_UNIT_BASE_COMBINER),
|
||||||
|
.number_of_demultiplexers = htons(DSC_AUDIO_UNIT_NUMBER_OF_DEMULTIPLEXERS),
|
||||||
|
.base_demultiplexer = htons(DSC_AUDIO_UNIT_BASE_DEMULTIPLEXER),
|
||||||
|
.number_of_multiplexers = htons(DSC_AUDIO_UNIT_NUMBER_OF_MULTIPLEXERS),
|
||||||
|
.base_multiplexer = htons(DSC_AUDIO_UNIT_BASE_MULTIPLEXER),
|
||||||
|
.number_of_transcoders = htons(DSC_AUDIO_UNIT_NUMBER_OF_TRANSCODERS),
|
||||||
|
.base_transcoder = htons(DSC_AUDIO_UNIT_BASE_TRANSCODER),
|
||||||
|
.number_of_control_blocks = htons(DSC_AUDIO_UNIT_NUMBER_OF_CONTROL_BLOCKS),
|
||||||
|
.base_control_block = htons(DSC_AUDIO_UNIT_BASE_CONTROL_BLOCK),
|
||||||
|
.current_sampling_rate = htonl(DSC_AUDIO_UNIT_CURRENT_SAMPLING_RATE_IN_HZ),
|
||||||
|
.sampling_rates_offset = htons(DSC_AUDIO_UNIT_SAMPLING_RATES_OFFSET),
|
||||||
|
.sampling_rates_count = htons(DSC_AUDIO_UNIT_SUPPORTED_SAMPLING_RATE_COUNT),
|
||||||
|
},
|
||||||
|
.sampling_rates = {
|
||||||
|
// Set the list of supported audio unit sample rate
|
||||||
|
{ .pull_frequency = htonl(DSC_AUDIO_UNIT_SUPPORTED_SAMPLING_RATE_IN_HZ_0) },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_AUDIO_UNIT, 0,
|
||||||
|
sizeof(audio_unit), &audio_unit);
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.6 STREAM_INPUT Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.4 */
|
||||||
|
|
||||||
|
// TODO: 1722.1 lists redundant parameters that are not mentioned here.
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct avb_aem_desc_stream desc;
|
||||||
|
uint64_t stream_formats[DSC_STREAM_INPUT_NUMBER_OF_FORMATS];
|
||||||
|
} __attribute__ ((__packed__)) stream_input_0 =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.object_name = DSC_STREAM_INPUT_OBJECT_NAME,
|
||||||
|
.localized_description = htons(DSC_STREAM_INPUT_LOCALIZED_DESCRIPTION),
|
||||||
|
.clock_domain_index = htons(DSC_STREAM_INPUT_CLOCK_DOMAIN_INDEX),
|
||||||
|
.stream_flags = htons(DSC_STREAM_INPUT_STREAM_FLAGS),
|
||||||
|
.current_format = htobe64(DSC_STREAM_INPUT_CURRENT_FORMAT),
|
||||||
|
.formats_offset = htons(DSC_STREAM_INPUT_FORMATS_OFFSET),
|
||||||
|
.number_of_formats = htons(DSC_STREAM_INPUT_NUMBER_OF_FORMATS),
|
||||||
|
.backup_talker_entity_id_0 = htobe64(DSC_STREAM_INPUT_BACKUP_TALKER_ENTITY_ID_0),
|
||||||
|
.backup_talker_unique_id_0 = htons(DSC_STREAM_INPUT_BACKUP_TALKER_UNIQUE_ID_0),
|
||||||
|
.backup_talker_entity_id_1 = htobe64(DSC_STREAM_INPUT_BACKUP_TALKER_ENTITY_ID_1),
|
||||||
|
.backup_talker_unique_id_1 = htons(DSC_STREAM_INPUT_BACKUP_TALKER_UNIQUE_ID_1),
|
||||||
|
.backup_talker_entity_id_2 = htobe64(DSC_STREAM_INPUT_BACKUP_TALKER_ENTITY_ID_2),
|
||||||
|
.backup_talker_unique_id_2 = htons(DSC_STREAM_INPUT_BACKUP_TALKER_UNIQUE_ID_2),
|
||||||
|
.backedup_talker_entity_id = htobe64(DSC_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_ID),
|
||||||
|
.backedup_talker_unique = htons(DSC_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_ID),
|
||||||
|
.avb_interface_index = htons(DSC_STREAM_INPUT_AVB_INTERFACE_INDEX),
|
||||||
|
.buffer_length = htonl(DSC_STREAM_INPUT_BUFFER_LENGTH_IN_NS)
|
||||||
|
},
|
||||||
|
.stream_formats = {
|
||||||
|
htobe64(DSC_STREAM_INPUT_FORMATS_0),
|
||||||
|
htobe64(DSC_STREAM_INPUT_FORMATS_1),
|
||||||
|
htobe64(DSC_STREAM_INPUT_FORMATS_2),
|
||||||
|
htobe64(DSC_STREAM_INPUT_FORMATS_3),
|
||||||
|
htobe64(DSC_STREAM_INPUT_FORMATS_4),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_INPUT, 0,
|
||||||
|
sizeof(stream_input_0), &stream_input_0);
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.6 STREAM_INPUT Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.4 */
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct avb_aem_desc_stream desc;
|
||||||
|
uint64_t stream_formats[DSC_STREAM_INPUT_CRF_NUMBER_OF_FORMATS];
|
||||||
|
} __attribute__ ((__packed__)) stream_input_crf_0 =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.object_name = DSC_STREAM_INPUT_CRF_OBJECT_NAME,
|
||||||
|
.localized_description = htons(DSC_STREAM_INPUT_CRF_LOCALIZED_DESCRIPTION),
|
||||||
|
.clock_domain_index = htons(DSC_STREAM_INPUT_CRF_CLOCK_DOMAIN_INDEX),
|
||||||
|
.stream_flags = htons(DSC_STREAM_INPUT_CRF_STREAM_FLAGS),
|
||||||
|
.current_format = htobe64(DSC_STREAM_INPUT_CRF_CURRENT_FORMAT),
|
||||||
|
.formats_offset = htons(DSC_STREAM_INPUT_CRF_FORMATS_OFFSET),
|
||||||
|
.number_of_formats = htons(DSC_STREAM_INPUT_CRF_NUMBER_OF_FORMATS),
|
||||||
|
.backup_talker_entity_id_0 = htobe64(DSC_STREAM_INPUT_CRF_BACKUP_TALKER_ENTITY_ID_0),
|
||||||
|
.backup_talker_unique_id_0 = htons(DSC_STREAM_INPUT_CRF_BACKUP_TALKER_UNIQUE_ID_0),
|
||||||
|
.backup_talker_entity_id_1 = htobe64(DSC_STREAM_INPUT_CRF_BACKUP_TALKER_ENTITY_ID_1),
|
||||||
|
.backup_talker_unique_id_1 = htons(DSC_STREAM_INPUT_CRF_BACKUP_TALKER_UNIQUE_ID_1),
|
||||||
|
.backup_talker_entity_id_2 = htobe64(DSC_STREAM_INPUT_CRF_BACKUP_TALKER_ENTITY_ID_2),
|
||||||
|
.backup_talker_unique_id_2 = htons(DSC_STREAM_INPUT_CRF_BACKUP_TALKER_UNIQUE_ID_2),
|
||||||
|
.backedup_talker_entity_id = htobe64(DSC_STREAM_INPUT_CRF_BACKEDUP_TALKER_ENTITY_ID),
|
||||||
|
.backedup_talker_unique = htons(DSC_STREAM_INPUT_CRF_BACKEDUP_TALKER_UNIQUE_ID),
|
||||||
|
.avb_interface_index = htons(DSC_STREAM_INPUT_CRF_AVB_INTERFACE_INDEX),
|
||||||
|
.buffer_length = htonl(DSC_STREAM_INPUT_CRF_BUFFER_LENGTH_IN_NS)
|
||||||
|
},
|
||||||
|
.stream_formats = {
|
||||||
|
htobe64(DSC_STREAM_INPUT_CRF_FORMATS_0),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_INPUT, 1,
|
||||||
|
sizeof(stream_input_crf_0), &stream_input_crf_0);
|
||||||
|
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.6 STREAM_OUTPUT Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.4 */
|
||||||
|
struct {
|
||||||
|
struct avb_aem_desc_stream desc;
|
||||||
|
uint64_t stream_formats[DSC_STREAM_OUTPUT_NUMBER_OF_FORMATS];
|
||||||
|
} __attribute__ ((__packed__)) stream_output_0 =
|
||||||
|
{
|
||||||
|
{
|
||||||
|
.object_name = DSC_STREAM_OUTPUT_OBJECT_NAME,
|
||||||
|
.localized_description = htons(DSC_STREAM_OUTPUT_LOCALIZED_DESCRIPTION),
|
||||||
|
.clock_domain_index = htons(DSC_STREAM_OUTPUT_CLOCK_DOMAIN_INDEX),
|
||||||
|
.stream_flags = htons(DSC_STREAM_OUTPUT_STREAM_FLAGS),
|
||||||
|
.current_format = htobe64(DSC_STREAM_OUTPUT_CURRENT_FORMAT),
|
||||||
|
.formats_offset = htons(DSC_STREAM_OUTPUT_FORMATS_OFFSET),
|
||||||
|
.number_of_formats = htons(DSC_STREAM_OUTPUT_NUMBER_OF_FORMATS),
|
||||||
|
.backup_talker_entity_id_0 = htobe64(DSC_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_ID_0),
|
||||||
|
.backup_talker_unique_id_0 = htons(DSC_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_ID_0),
|
||||||
|
.backup_talker_entity_id_1 = htobe64(DSC_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_ID_1),
|
||||||
|
.backup_talker_unique_id_1 = htons(DSC_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_ID_1),
|
||||||
|
.backup_talker_entity_id_2 = htobe64(DSC_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_ID_2),
|
||||||
|
.backup_talker_unique_id_2 = htons(DSC_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_ID_2),
|
||||||
|
.backedup_talker_entity_id = htobe64(DSC_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_ID),
|
||||||
|
.backedup_talker_unique = htons(DSC_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_ID),
|
||||||
|
.avb_interface_index = htons(DSC_STREAM_OUTPUT_AVB_INTERFACE_INDEX),
|
||||||
|
.buffer_length = htons(DSC_STREAM_OUTPUT_BUFFER_LENGTH_IN_NS)
|
||||||
|
},
|
||||||
|
.stream_formats = {
|
||||||
|
htobe64(DSC_STREAM_OUTPUT_FORMATS_0),
|
||||||
|
htobe64(DSC_STREAM_OUTPUT_FORMATS_1),
|
||||||
|
htobe64(DSC_STREAM_OUTPUT_FORMATS_2),
|
||||||
|
htobe64(DSC_STREAM_OUTPUT_FORMATS_3),
|
||||||
|
htobe64(DSC_STREAM_OUTPUT_FORMATS_4),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_OUTPUT, 0,
|
||||||
|
sizeof(stream_output_0), &stream_output_0);
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.8 AVB Interface Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.5 */
|
||||||
|
|
||||||
|
struct avb_aem_desc_avb_interface avb_interface = {
|
||||||
|
.localized_description = htons(DSC_AVB_INTERFACE_LOCALIZED_DESCRIPTION),
|
||||||
|
.interface_flags = htons(DSC_AVB_INTERFACE_INTERFACE_FLAGS),
|
||||||
|
.clock_identity = htobe64(DSC_AVB_INTERFACE_CLOCK_IDENTITY),
|
||||||
|
.priority1 = DSC_AVB_INTERFACE_PRIORITY1,
|
||||||
|
.clock_class = DSC_AVB_INTERFACE_CLOCK_CLASS,
|
||||||
|
.offset_scaled_log_variance = htons(DSC_AVB_INTERFACE_OFFSET_SCALED_LOG_VARIANCE),
|
||||||
|
.clock_accuracy = DSC_AVB_INTERFACE_CLOCK_ACCURACY,
|
||||||
|
.priority2 = DSC_AVB_INTERFACE_PRIORITY2,
|
||||||
|
.domain_number = DSC_AVB_INTERFACE_DOMAIN_NUMBER,
|
||||||
|
.log_sync_interval = DSC_AVB_INTERFACE_LOG_SYNC_INTERVAL,
|
||||||
|
.log_announce_interval = DSC_AVB_INTERFACE_LOG_ANNOUNCE_INTERVAL,
|
||||||
|
.log_pdelay_interval = DSC_AVB_INTERFACE_PDELAY_INTERVAL,
|
||||||
|
.port_number = DSC_AVB_INTERFACE_PORT_NUMBER,
|
||||||
|
};
|
||||||
|
|
||||||
|
memset(avb_interface.object_name, 0, sizeof(avb_interface.object_name));
|
||||||
|
strncpy(avb_interface.object_name, "", 63);
|
||||||
|
memcpy(avb_interface.mac_address, server->mac_addr, 6);
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0,
|
||||||
|
sizeof(avb_interface), &avb_interface);
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.9 CLOCK_SOURCE Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.6 */
|
||||||
|
|
||||||
|
// Internal Clock Descriptor
|
||||||
|
struct avb_aem_desc_clock_source clock_source_internal = {
|
||||||
|
.object_name = DSC_CLOCK_SOURCE_INTERNAL_OBJECT_NAME,
|
||||||
|
.localized_description = htons(DSC_CLOCK_SOURCE_INTERNAL_LOCALIZED_DESCRIPTION),
|
||||||
|
.clock_source_flags = htons(DSC_CLOCK_SOURCE_INTERNAL_FLAGS),
|
||||||
|
.clock_source_type = htons(DSC_CLOCK_SOURCE_INTERNAL_TYPE),
|
||||||
|
.clock_source_identifier = htobe64(DSC_CLOCK_SOURCE_INTERNAL_IDENTIFIER),
|
||||||
|
.clock_source_location_type = htons(DSC_CLOCK_SOURCE_INTERNAL_LOCATION_TYPE),
|
||||||
|
.clock_source_location_index = htons(DSC_CLOCK_SOURCE_INTERNAL_LOCATION_INDEX),
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_CLOCK_SOURCE, 0,
|
||||||
|
sizeof(clock_source_internal), &clock_source_internal);
|
||||||
|
|
||||||
|
// AAF Clock Descriptor
|
||||||
|
struct avb_aem_desc_clock_source clock_source_aaf = {
|
||||||
|
.object_name = DSC_CLOCK_SOURCE_AAF_OBJECT_NAME,
|
||||||
|
.localized_description = htons(DSC_CLOCK_SOURCE_AAF_LOCALIZED_DESCRIPTION),
|
||||||
|
.clock_source_flags = htons(DSC_CLOCK_SOURCE_AAF_FLAGS),
|
||||||
|
.clock_source_type = htons(DSC_CLOCK_SOURCE_AAF_TYPE),
|
||||||
|
.clock_source_identifier = htobe64(DSC_CLOCK_SOURCE_AAF_IDENTIFIER),
|
||||||
|
.clock_source_location_type = htons(DSC_CLOCK_SOURCE_AAF_LOCATION_TYPE),
|
||||||
|
.clock_source_location_index = htons(DSC_CLOCK_SOURCE_AAF_LOCATION_INDEX),
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_CLOCK_SOURCE, 1,
|
||||||
|
sizeof(clock_source_aaf), &clock_source_aaf);
|
||||||
|
|
||||||
|
// CRF Clock Descriptor
|
||||||
|
struct avb_aem_desc_clock_source clock_source_crf = {
|
||||||
|
.object_name = DSC_CLOCK_SOURCE_CRF_OBJECT_NAME,
|
||||||
|
.localized_description = htons(DSC_CLOCK_SOURCE_CRF_LOCALIZED_DESCRIPTION),
|
||||||
|
.clock_source_flags = htons(DSC_CLOCK_SOURCE_CRF_FLAGS),
|
||||||
|
.clock_source_type = htons(DSC_CLOCK_SOURCE_CRF_TYPE),
|
||||||
|
.clock_source_identifier = htobe64(DSC_CLOCK_SOURCE_CRF_IDENTIFIER),
|
||||||
|
.clock_source_location_type = htons(DSC_CLOCK_SOURCE_CRF_LOCATION_TYPE),
|
||||||
|
.clock_source_location_index = htons(DSC_CLOCK_SOURCE_CRF_LOCATION_INDEX),
|
||||||
|
};
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_CLOCK_SOURCE, 2,
|
||||||
|
sizeof(clock_source_crf), &clock_source_crf);
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.32 CLOCK_DOMAIN Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.11 */
|
||||||
|
|
||||||
|
struct {
|
||||||
|
struct avb_aem_desc_clock_domain desc;
|
||||||
|
uint16_t clock_sources_idx[DSC_CLOCK_DOMAIN_CLOCK_SOURCES_COUNT];
|
||||||
|
} __attribute__ ((__packed__)) clock_domain = {
|
||||||
|
.desc = {
|
||||||
|
.object_name = DSC_CLOCK_DOMAIN_OBJECT_NAME,
|
||||||
|
.localized_description = htons(DSC_CLOCK_DOMAIN_LOCALIZED_DESCRIPTION),
|
||||||
|
.clock_source_index = htons(DSC_CLOCK_DOMAIN_CLOCK_SOURCE_INDEX),
|
||||||
|
.descriptor_counts_offset = htons(DSC_CLOCK_DOMAIN_DESCRIPTOR_COUNTS_OFFSET),
|
||||||
|
.clock_sources_count = htons(DSC_CLOCK_DOMAIN_CLOCK_SOURCES_COUNT),
|
||||||
|
},
|
||||||
|
.clock_sources_idx = {
|
||||||
|
htons(DSC_CLOCK_DOMAIN_SOURCES_0),
|
||||||
|
htons(DSC_CLOCK_DOMAIN_SOURCES_1),
|
||||||
|
htons(DSC_CLOCK_DOMAIN_SOURCES_2),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
es_builder_add_descriptor(server, AVB_AEM_DESC_CLOCK_DOMAIN, 0,
|
||||||
|
sizeof(clock_domain), &clock_domain);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_descriptors(struct server *server)
|
||||||
|
{
|
||||||
|
if (!server) {
|
||||||
|
pw_log_error("No server");
|
||||||
|
spa_assert(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (server->avb_mode) {
|
||||||
|
case AVB_MODE_LEGACY:
|
||||||
|
init_descriptor_legacy_avb(server);
|
||||||
|
break;
|
||||||
|
case AVB_MODE_MILAN_V12:
|
||||||
|
init_descriptor_milan_v12(server);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pw_log_error("Unknown AVB mode");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,12 +3,6 @@
|
||||||
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki (alexandre.malki@kebag-logic.com) */
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki (alexandre.malki@kebag-logic.com) */
|
||||||
/* SPDX-License-Identifier: MIT */
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
#include "adp.h"
|
|
||||||
#include "aecp-aem.h"
|
|
||||||
#include "aecp-aem-descriptors.h"
|
|
||||||
#include "es-builder.h"
|
|
||||||
#include "internal.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \todo This whole code needs to be re-factore,
|
* \todo This whole code needs to be re-factore,
|
||||||
* configuring the entity using such a "HARDCODED"
|
* configuring the entity using such a "HARDCODED"
|
||||||
|
|
@ -25,249 +19,4 @@
|
||||||
* Having the YANG would allow directly to know the
|
* Having the YANG would allow directly to know the
|
||||||
* capabilites/limits of the protocol
|
* capabilites/limits of the protocol
|
||||||
*/
|
*/
|
||||||
static inline void init_descriptors(struct server *server)
|
void init_descriptors(struct server *server);
|
||||||
{
|
|
||||||
es_builder_add_descriptor(server, AVB_AEM_DESC_STRINGS, 0,
|
|
||||||
sizeof(struct avb_aem_desc_strings),
|
|
||||||
&(struct avb_aem_desc_strings)
|
|
||||||
{
|
|
||||||
.string_0 = "PipeWire",
|
|
||||||
.string_1 = "Configuration 1",
|
|
||||||
.string_2 = "Wim Taymans",
|
|
||||||
});
|
|
||||||
es_builder_add_descriptor(server, AVB_AEM_DESC_LOCALE, 0,
|
|
||||||
sizeof(struct avb_aem_desc_locale),
|
|
||||||
&(struct avb_aem_desc_locale)
|
|
||||||
{
|
|
||||||
.locale_identifier = "en-EN",
|
|
||||||
.number_of_strings = htons(1),
|
|
||||||
.base_strings = htons(0)
|
|
||||||
});
|
|
||||||
es_builder_add_descriptor(server, AVB_AEM_DESC_ENTITY, 0,
|
|
||||||
sizeof(struct avb_aem_desc_entity),
|
|
||||||
&(struct avb_aem_desc_entity)
|
|
||||||
{
|
|
||||||
.entity_id = htobe64(server->entity_id),
|
|
||||||
.entity_model_id = htobe64(0),
|
|
||||||
.entity_capabilities = htonl(
|
|
||||||
AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED |
|
|
||||||
AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED |
|
|
||||||
AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED |
|
|
||||||
AVB_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID |
|
|
||||||
AVB_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID),
|
|
||||||
|
|
||||||
.talker_stream_sources = htons(8),
|
|
||||||
.talker_capabilities = htons(
|
|
||||||
AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED |
|
|
||||||
AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE),
|
|
||||||
.listener_stream_sinks = htons(8),
|
|
||||||
.listener_capabilities = htons(
|
|
||||||
AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED |
|
|
||||||
AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK),
|
|
||||||
.controller_capabilities = htons(0),
|
|
||||||
.available_index = htonl(0),
|
|
||||||
.association_id = htobe64(0),
|
|
||||||
.entity_name = "PipeWire",
|
|
||||||
.vendor_name_string = htons(2),
|
|
||||||
.model_name_string = htons(0),
|
|
||||||
.firmware_version = "0.3.48",
|
|
||||||
.group_name = "",
|
|
||||||
.serial_number = "",
|
|
||||||
.configurations_count = htons(1),
|
|
||||||
.current_configuration = htons(0)
|
|
||||||
});
|
|
||||||
struct {
|
|
||||||
struct avb_aem_desc_configuration desc;
|
|
||||||
struct avb_aem_desc_descriptor_count descriptor_counts[8];
|
|
||||||
} __attribute__ ((__packed__)) config =
|
|
||||||
{
|
|
||||||
{
|
|
||||||
.object_name = "Configuration 1",
|
|
||||||
.localized_description = htons(1),
|
|
||||||
.descriptor_counts_count = htons(8),
|
|
||||||
.descriptor_counts_offset = htons(
|
|
||||||
4 + sizeof(struct avb_aem_desc_configuration)),
|
|
||||||
},
|
|
||||||
.descriptor_counts = {
|
|
||||||
{ htons(AVB_AEM_DESC_AUDIO_UNIT), htons(1) },
|
|
||||||
{ htons(AVB_AEM_DESC_STREAM_INPUT), htons(1) },
|
|
||||||
{ htons(AVB_AEM_DESC_STREAM_OUTPUT), htons(1) },
|
|
||||||
{ htons(AVB_AEM_DESC_AVB_INTERFACE), htons(1) },
|
|
||||||
{ htons(AVB_AEM_DESC_CLOCK_SOURCE), htons(1) },
|
|
||||||
{ htons(AVB_AEM_DESC_CONTROL), htons(2) },
|
|
||||||
{ htons(AVB_AEM_DESC_LOCALE), htons(1) },
|
|
||||||
{ htons(AVB_AEM_DESC_CLOCK_DOMAIN), htons(1) }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
es_builder_add_descriptor(server, AVB_AEM_DESC_CONFIGURATION, 0,
|
|
||||||
sizeof(config), &config);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
struct avb_aem_desc_audio_unit desc;
|
|
||||||
struct avb_aem_desc_sampling_rate sampling_rates[6];
|
|
||||||
} __attribute__ ((__packed__)) audio_unit =
|
|
||||||
{
|
|
||||||
{
|
|
||||||
.object_name = "PipeWire",
|
|
||||||
.localized_description = htons(0),
|
|
||||||
.clock_domain_index = htons(0),
|
|
||||||
.number_of_stream_input_ports = htons(1),
|
|
||||||
.base_stream_input_port = htons(0),
|
|
||||||
.number_of_stream_output_ports = htons(1),
|
|
||||||
.base_stream_output_port = htons(0),
|
|
||||||
.number_of_external_input_ports = htons(8),
|
|
||||||
.base_external_input_port = htons(0),
|
|
||||||
.number_of_external_output_ports = htons(8),
|
|
||||||
.base_external_output_port = htons(0),
|
|
||||||
.number_of_internal_input_ports = htons(0),
|
|
||||||
.base_internal_input_port = htons(0),
|
|
||||||
.number_of_internal_output_ports = htons(0),
|
|
||||||
.base_internal_output_port = htons(0),
|
|
||||||
.number_of_controls = htons(0),
|
|
||||||
.base_control = htons(0),
|
|
||||||
.number_of_signal_selectors = htons(0),
|
|
||||||
.base_signal_selector = htons(0),
|
|
||||||
.number_of_mixers = htons(0),
|
|
||||||
.base_mixer = htons(0),
|
|
||||||
.number_of_matrices = htons(0),
|
|
||||||
.base_matrix = htons(0),
|
|
||||||
.number_of_splitters = htons(0),
|
|
||||||
.base_splitter = htons(0),
|
|
||||||
.number_of_combiners = htons(0),
|
|
||||||
.base_combiner = htons(0),
|
|
||||||
.number_of_demultiplexers = htons(0),
|
|
||||||
.base_demultiplexer = htons(0),
|
|
||||||
.number_of_multiplexers = htons(0),
|
|
||||||
.base_multiplexer = htons(0),
|
|
||||||
.number_of_transcoders = htons(0),
|
|
||||||
.base_transcoder = htons(0),
|
|
||||||
.number_of_control_blocks = htons(0),
|
|
||||||
.base_control_block = htons(0),
|
|
||||||
.current_sampling_rate = htonl(48000),
|
|
||||||
.sampling_rates_offset = htons(
|
|
||||||
4 + sizeof(struct avb_aem_desc_audio_unit)),
|
|
||||||
.sampling_rates_count = htons(6),
|
|
||||||
},
|
|
||||||
.sampling_rates = {
|
|
||||||
{ .pull_frequency = htonl(44100) },
|
|
||||||
{ .pull_frequency = htonl(48000) },
|
|
||||||
{ .pull_frequency = htonl(88200) },
|
|
||||||
{ .pull_frequency = htonl(96000) },
|
|
||||||
{ .pull_frequency = htonl(176400) },
|
|
||||||
{ .pull_frequency = htonl(192000) },
|
|
||||||
}
|
|
||||||
};
|
|
||||||
es_builder_add_descriptor(server, AVB_AEM_DESC_AUDIO_UNIT, 0,
|
|
||||||
sizeof(audio_unit), &audio_unit);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
struct avb_aem_desc_stream desc;
|
|
||||||
uint64_t stream_formats[6];
|
|
||||||
} __attribute__ ((__packed__)) stream_input_0 =
|
|
||||||
{
|
|
||||||
{
|
|
||||||
.object_name = "Stream Input 1",
|
|
||||||
.localized_description = htons(0xffff),
|
|
||||||
.clock_domain_index = htons(0),
|
|
||||||
.stream_flags = htons(
|
|
||||||
AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE |
|
|
||||||
AVB_AEM_DESC_STREAM_FLAG_CLASS_A),
|
|
||||||
.current_format = htobe64(0x00a0020840000800ULL),
|
|
||||||
.formats_offset = htons(
|
|
||||||
4 + sizeof(struct avb_aem_desc_stream)),
|
|
||||||
.number_of_formats = htons(6),
|
|
||||||
.backup_talker_entity_id_0 = htobe64(0),
|
|
||||||
.backup_talker_unique_id_0 = htons(0),
|
|
||||||
.backup_talker_entity_id_1 = htobe64(0),
|
|
||||||
.backup_talker_unique_id_1 = htons(0),
|
|
||||||
.backup_talker_entity_id_2 = htobe64(0),
|
|
||||||
.backup_talker_unique_id_2 = htons(0),
|
|
||||||
.backedup_talker_entity_id = htobe64(0),
|
|
||||||
.backedup_talker_unique = htons(0),
|
|
||||||
.avb_interface_index = htons(0),
|
|
||||||
.buffer_length = htons(8)
|
|
||||||
},
|
|
||||||
.stream_formats = {
|
|
||||||
htobe64(0x00a0010860000800ULL),
|
|
||||||
htobe64(0x00a0020860000800ULL),
|
|
||||||
htobe64(0x00a0030860000800ULL),
|
|
||||||
htobe64(0x00a0040860000800ULL),
|
|
||||||
htobe64(0x00a0050860000800ULL),
|
|
||||||
htobe64(0x00a0060860000800ULL),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_INPUT, 0,
|
|
||||||
sizeof(stream_input_0), &stream_input_0);
|
|
||||||
|
|
||||||
struct {
|
|
||||||
struct avb_aem_desc_stream desc;
|
|
||||||
uint64_t stream_formats[6];
|
|
||||||
} __attribute__ ((__packed__)) stream_output_0 =
|
|
||||||
{
|
|
||||||
{
|
|
||||||
.object_name = "Stream Output 1",
|
|
||||||
.localized_description = htons(0xffff),
|
|
||||||
.clock_domain_index = htons(0),
|
|
||||||
.stream_flags = htons(
|
|
||||||
AVB_AEM_DESC_STREAM_FLAG_CLASS_A),
|
|
||||||
.current_format = htobe64(0x00a0020840000800ULL),
|
|
||||||
.formats_offset = htons(
|
|
||||||
4 + sizeof(struct avb_aem_desc_stream)),
|
|
||||||
.number_of_formats = htons(6),
|
|
||||||
.backup_talker_entity_id_0 = htobe64(0),
|
|
||||||
.backup_talker_unique_id_0 = htons(0),
|
|
||||||
.backup_talker_entity_id_1 = htobe64(0),
|
|
||||||
.backup_talker_unique_id_1 = htons(0),
|
|
||||||
.backup_talker_entity_id_2 = htobe64(0),
|
|
||||||
.backup_talker_unique_id_2 = htons(0),
|
|
||||||
.backedup_talker_entity_id = htobe64(0),
|
|
||||||
.backedup_talker_unique = htons(0),
|
|
||||||
.avb_interface_index = htons(0),
|
|
||||||
.buffer_length = htons(8)
|
|
||||||
},
|
|
||||||
.stream_formats = {
|
|
||||||
htobe64(0x00a0010860000800ULL),
|
|
||||||
htobe64(0x00a0020860000800ULL),
|
|
||||||
htobe64(0x00a0030860000800ULL),
|
|
||||||
htobe64(0x00a0040860000800ULL),
|
|
||||||
htobe64(0x00a0050860000800ULL),
|
|
||||||
htobe64(0x00a0060860000800ULL),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
es_builder_add_descriptor(server, AVB_AEM_DESC_STREAM_OUTPUT, 0,
|
|
||||||
sizeof(stream_output_0), &stream_output_0);
|
|
||||||
|
|
||||||
struct avb_aem_desc_avb_interface avb_interface = {
|
|
||||||
.localized_description = htons(0xffff),
|
|
||||||
.interface_flags = htons(
|
|
||||||
AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED),
|
|
||||||
.clock_identity = htobe64(0),
|
|
||||||
.priority1 = 0,
|
|
||||||
.clock_class = 0,
|
|
||||||
.offset_scaled_log_variance = htons(0),
|
|
||||||
.clock_accuracy = 0,
|
|
||||||
.priority2 = 0,
|
|
||||||
.domain_number = 0,
|
|
||||||
.log_sync_interval = 0,
|
|
||||||
.log_announce_interval = 0,
|
|
||||||
.log_pdelay_interval = 0,
|
|
||||||
.port_number = 0,
|
|
||||||
};
|
|
||||||
strncpy(avb_interface.object_name, server->ifname, 63);
|
|
||||||
memcpy(avb_interface.mac_address, server->mac_addr, 6);
|
|
||||||
es_builder_add_descriptor(server, AVB_AEM_DESC_AVB_INTERFACE, 0,
|
|
||||||
sizeof(avb_interface), &avb_interface);
|
|
||||||
|
|
||||||
struct avb_aem_desc_clock_source clock_source = {
|
|
||||||
.object_name = "Stream Clock",
|
|
||||||
.localized_description = htons(0xffff),
|
|
||||||
.clock_source_flags = htons(0),
|
|
||||||
.clock_source_type = htons(
|
|
||||||
AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM),
|
|
||||||
.clock_source_identifier = htobe64(0),
|
|
||||||
.clock_source_location_type = htons(AVB_AEM_DESC_STREAM_INPUT),
|
|
||||||
.clock_source_location_index = htons(0),
|
|
||||||
};
|
|
||||||
es_builder_add_descriptor(server, AVB_AEM_DESC_CLOCK_SOURCE, 0,
|
|
||||||
sizeof(clock_source), &clock_source);
|
|
||||||
}
|
|
||||||
|
|
|
||||||
477
src/modules/module-avb/entity-model-milan-v12.h
Normal file
477
src/modules/module-avb/entity-model-milan-v12.h
Normal file
|
|
@ -0,0 +1,477 @@
|
||||||
|
/* PipeWire */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Kebag-Logic */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Simon Gapp <simon.gapp@kebag-logic.com> */
|
||||||
|
/* SPDX-FileCopyrightText: Copyright © 2025 Alexandre Malki <alexandre.malki@kebag-logic.com> */
|
||||||
|
/* SPDX-License-Identifier: MIT */
|
||||||
|
|
||||||
|
#ifndef __DESCRIPTOR_ENTITY_MODEL_MILAN_H__
|
||||||
|
#define __DESCRIPTOR_ENTITY_MODEL_MILAN_H__
|
||||||
|
|
||||||
|
#define TALKER_ENABLE 1
|
||||||
|
|
||||||
|
// TODO: Make defines as long as specified length
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.12 - STRINGS Descriptor
|
||||||
|
* Up to 7 localized strings
|
||||||
|
*/
|
||||||
|
#define DSC_STRINGS_0_DEVICE_NAME "PipeWire"
|
||||||
|
#define DSC_STRINGS_1_CONFIGURATION_NAME "NON - redundant - 48kHz"
|
||||||
|
#define DSC_STRINGS_2_MANUFACTURER_NAME "Kebag Logic"
|
||||||
|
#define DSC_STRINGS_3_GROUP_NAME "Kebag Logic"
|
||||||
|
#define DSC_STRINGS_4_MAINTAINER_0 "Alexandre Malki"
|
||||||
|
#define DSC_STRINGS_4_MAINTAINER_1 "Simon Gapp"
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.11 - LOCALE Descriptor */
|
||||||
|
#define DSC_LOCALE_LANGUAGE_CODE "en-EN"
|
||||||
|
#define DSC_LOCALE_NO_OF_STRINGS 1
|
||||||
|
#define DSC_LOCALE_BASE_STRINGS 0
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.1 - ENTITY Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.1 */
|
||||||
|
|
||||||
|
#define DSC_ENTITY_MODEL_ENTITY_ID 0xDEAD00BEEF00FEED
|
||||||
|
#define DSC_ENTITY_MODEL_ID 0
|
||||||
|
#define DSC_ENTITY_MODEL_ENTITY_CAPABILITIES (AVB_ADP_ENTITY_CAPABILITY_AEM_SUPPORTED | \
|
||||||
|
AVB_ADP_ENTITY_CAPABILITY_CLASS_A_SUPPORTED | \
|
||||||
|
AVB_ADP_ENTITY_CAPABILITY_GPTP_SUPPORTED | \
|
||||||
|
AVB_ADP_ENTITY_CAPABILITY_AEM_IDENTIFY_CONTROL_INDEX_VALID | \
|
||||||
|
AVB_ADP_ENTITY_CAPABILITY_AEM_INTERFACE_INDEX_VALID)
|
||||||
|
/* IEEE 1722.1-2021, Table 7-2 - ENTITY Descriptor
|
||||||
|
* This is the maximum number of STREAM_OUTPUT
|
||||||
|
* descriptors the ATDECC Entity has for
|
||||||
|
* Output Streams in any of its Configurations */
|
||||||
|
|
||||||
|
#if TALKER_ENABLE
|
||||||
|
#define DSC_ENTITY_MODEL_TALKER_STREAM_SOURCES 8
|
||||||
|
#define DSC_ENTITY_MODEL_TALKER_CAPABILITIES (AVB_ADP_TALKER_CAPABILITY_IMPLEMENTED | \
|
||||||
|
AVB_ADP_TALKER_CAPABILITY_AUDIO_SOURCE)
|
||||||
|
#else
|
||||||
|
#define DSC_ENTITY_MODEL_TALKER_STREAM_SOURCES 0
|
||||||
|
#define DSC_ENTITY_MODEL_TALKER_CAPABILITIES 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#define DSC_ENTITY_MODEL_LISTENER_STREAM_SINKS 8
|
||||||
|
#define DSC_ENTITY_MODEL_LISTENER_CAPABILITIES (AVB_ADP_LISTENER_CAPABILITY_IMPLEMENTED | \
|
||||||
|
AVB_ADP_LISTENER_CAPABILITY_AUDIO_SINK)
|
||||||
|
#define DSC_ENTITY_MODEL_CONTROLLER_CAPABILITIES 0
|
||||||
|
/* IEEE 1722.1-2021, Table 7-2 ENTITY Descriptor
|
||||||
|
* The available index of the ATDECC Entity.
|
||||||
|
* This is the same as the available_index field
|
||||||
|
* in ATDECC Discovery Protocol.*/
|
||||||
|
#define DSC_ENTITY_MODEL_AVAILABLE_INDEX 0
|
||||||
|
/* IEEE 1722.1-2021, Table 7-2 ENTITY Descriptor
|
||||||
|
* The association ID for the ATDECC Entity.
|
||||||
|
* This is the same as association_id field
|
||||||
|
* in ATDECC Discovery Protocol*/
|
||||||
|
#define DSC_ENTITY_MODEL_ASSOCIATION_ID 0
|
||||||
|
#define DSC_ENTITY_MODEL_ENTITY_NAME DSC_STRINGS_0_DEVICE_NAME
|
||||||
|
/* IEEE 1722.1-2021, Table 7-2 - ENTITY Descriptor
|
||||||
|
* The localized string reference pointing to the
|
||||||
|
* localized vendor name. See 7.3.7. */
|
||||||
|
#define DSC_ENTITY_MODEL_VENDOR_NAME_STRING 2
|
||||||
|
/* IEEE 1722.1-2021, Table 7-2 - ENTITY Descriptor
|
||||||
|
* The localized string reference pointing to the
|
||||||
|
* localized model name. See 7.3.7. */
|
||||||
|
#define DSC_ENTITY_MODEL_MODEL_NAME_STRING 0
|
||||||
|
#define DSC_ENTITY_MODEL_FIRMWARE_VERSION "0.3.48"
|
||||||
|
#define DSC_ENTITY_MODEL_GROUP_NAME DSC_STRINGS_3_GROUP_NAME
|
||||||
|
#define DSC_ENTITY_MODEL_SERIAL_NUMBER "0xBEBEDEAD"
|
||||||
|
#define DSC_ENTITY_MODEL_CONFIGURATIONS_COUNT 2
|
||||||
|
#define DSC_ENTITY_MODEL_CURRENT_CONFIGURATION 0
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.2 - CONFIGURATION Descriptor*/
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.2 */
|
||||||
|
#define DSC_CONFIGURATION_DESCRIPTOR_COUNTS_COUNT 8
|
||||||
|
#define DSC_CONFIGURATION_OBJECT_NAME DSC_STRINGS_1_CONFIGURATION_NAME
|
||||||
|
/* IEEE 1722.1-2021, Table 7-3 CONFIGURATION Descriptor
|
||||||
|
* The localized string reference pointing to the
|
||||||
|
* localized Configuration name. */
|
||||||
|
#define DSC_CONFIGURATION_LOCALIZED_DESCRIPTION 1
|
||||||
|
/* IEEE 1722.1-2021, Table 7-3 CONFIGURATION Descriptor
|
||||||
|
* The offset to the descriptor_counts field from the
|
||||||
|
* start of the descriptor. This field is set to 74 for
|
||||||
|
* this version of AEM. */
|
||||||
|
#define DSC_CONFIGURATION_DESCRIPTOR_COUNTS_OFFSET 74
|
||||||
|
|
||||||
|
#define DSC_CONFIGURATION_NO_OF_AUDIO_UNITS 1
|
||||||
|
#define DSC_CONFIGURATION_NO_OF_STREAM_INPUTS 2
|
||||||
|
|
||||||
|
#define DSC_CONFIGURATION_NO_OF_STREAM_OUTPUTS 1
|
||||||
|
|
||||||
|
#define DSC_CONFIGURATION_NO_OF_AVB_INTERFACES 1
|
||||||
|
#define DSC_CONFIGURATION_NO_OF_CLOCK_DOMAINS 1
|
||||||
|
#define DSC_CONFIGURATION_NO_OF_CLOCK_SOURCES 3
|
||||||
|
#define DSC_CONFIGURATION_NO_OF_CONTROLS 1
|
||||||
|
#define DSC_CONFIGURATION_NO_OF_LOCALES 1
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.22 CONTROL Descriptor*/
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.10 */
|
||||||
|
|
||||||
|
#define DSC_CONTROL_OBJECT_NAME "Identify"
|
||||||
|
#define DSC_CONTROL_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
|
||||||
|
#define DSC_CONTROL_BLOCK_LATENCY 500
|
||||||
|
#define DSC_CONTROL_CONTROL_LATENCY 500
|
||||||
|
#define DSC_CONTROL_CONTROL_DOMAIN 0
|
||||||
|
#define DSC_CONTROL_CONTROL_VALUE_TYPE AECP_AEM_CTRL_LINEAR_UINT8
|
||||||
|
#define DSC_CONTROL_CONTROL_TYPE AEM_CTRL_TYPE_IDENTIFY
|
||||||
|
/* IEEE 1722.1-2021, Table 7-39 - CONTROL Descriptor
|
||||||
|
* The time period in microseconds from when a control
|
||||||
|
* is set with the SET_CONTROL command till it automatically
|
||||||
|
* resets to its default values.
|
||||||
|
* When this is set to zero (0) automatic resets do not happen. */
|
||||||
|
// TODO: Milan v1.2: The PAAD remains in identification mode until the value of the “IDENTIFY” CONTROL descriptor is set back to 0.
|
||||||
|
#define DSC_CONTROL_RESET_TIME 3
|
||||||
|
#define DSC_CONTROL_NUMBER_OF_VALUES 1
|
||||||
|
#define DSC_CONTROL_SIGNAL_TYPE AVB_AEM_DESC_INVALID
|
||||||
|
#define DSC_CONTROL_SIGNAL_INDEX 0
|
||||||
|
#define DSC_CONTROL_SIGNAL_OUTPUT 0
|
||||||
|
|
||||||
|
#define DSC_CONTROL_IDENTIFY_MIN 0
|
||||||
|
#define DSC_CONTROL_IDENTIFY_MAX 255
|
||||||
|
#define DSC_CONTROL_IDENTIFY_STEP 255
|
||||||
|
#define DSC_CONTROL_IDENTIFY_DEFAULT_VALUE 0
|
||||||
|
#define DSC_CONTROL_IDENTIFY_CURRENT_VALUE 0
|
||||||
|
#define DSC_CONTROL_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.19 AUDIO_MAP Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.9 */
|
||||||
|
|
||||||
|
// TODO: Prepared for for loop over total number of audio maps
|
||||||
|
#define DSC_AUDIO_MAPS_TOTAL_NO_OF_MAPS 2
|
||||||
|
|
||||||
|
#define DSC_AUDIO_MAPS_NO_OF_MAPPINGS 8
|
||||||
|
#define DSC_AUDIO_MAPS_MAPPING_STREAM_INDEX 0
|
||||||
|
#define DSC_AUDIO_MAPS_MAPPING_CLUSTER_CHANNEL 0
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.16 AUDIO_CLUSTER Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.8 */
|
||||||
|
#define DSC_AUDIO_CLUSTER_NO_OF_CLUSTERS 16
|
||||||
|
#define DSC_AUDIO_CLUSTER_OBJECT_NAME_LEN_IN_OCTET 64
|
||||||
|
#define DSC_AUDIO_CLUSTER_OBJECT_NAME_INPUT "Input"
|
||||||
|
#define DSC_AUDIO_CLUSTER_OBJECT_NAME_OUTPUT "Output"
|
||||||
|
|
||||||
|
#define DSC_AUDIO_CLUSTER_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
|
||||||
|
|
||||||
|
/* The signal_type and signal_index fields indicate the
|
||||||
|
* object providing the signal destined for the channels
|
||||||
|
* of the streams mapped to the port. For a signal which
|
||||||
|
* is sourced internally from the Unit, the signal_type
|
||||||
|
* is set to AUDIO_UNIT and signal_index is set to the
|
||||||
|
* index of the Unit. For a Cluster attached to a
|
||||||
|
* STREAM_PORT_INPUT the signal_type and signal_index
|
||||||
|
* fields is set to INVALID and zero (0) respectively. */
|
||||||
|
#define DSC_AUDIO_CLUSTER_SIGNAL_TYPE 0
|
||||||
|
#define DSC_AUDIO_CLUSTER_SIGNAL_INDEX 0
|
||||||
|
/* The index of the output of the signal source of the
|
||||||
|
* cluster. For a signal_type of SIGNAL_SPLITTER or
|
||||||
|
* SIGNAL_DEMULTIPLEXER this is which output of the
|
||||||
|
* object it is being sourced from, for a signal_type
|
||||||
|
* of MATRIX this is the column the signal is from
|
||||||
|
* and for any other signal_type this is zero (0). */
|
||||||
|
#define DSC_AUDIO_CLUSTER_SIGNAL_OUTPUT 0
|
||||||
|
#define DSC_AUDIO_CLUSTER_PATH_LATENCY_IN_NS 500
|
||||||
|
#define DSC_AUDIO_CLUSTER_BLOCK_LATENCY_IN_NS 500
|
||||||
|
#define DSC_AUDIO_CLUSTER_CHANNEL_COUNT 1
|
||||||
|
#define DSC_AUDIO_CLUSTER_FORMAT AVB_AEM_AUDIO_CLUSTER_TYPE_MBLA
|
||||||
|
#define DSC_AUDIO_CLUSTER_AES3_DATA_TYPE_REF 0
|
||||||
|
#define DSC_AUDIO_CLUSTER_AES3_DATA_TYPE 0
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.13 STREAM_PORT_INPUT Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.7 */
|
||||||
|
|
||||||
|
#define DSC_STREAM_PORT_INPUT_CLOCK_DOMAIN_INDEX 0x0000
|
||||||
|
#define DSC_STREAM_PORT_INPUT_PORT_FLAGS AVB_AEM_PORT_FLAG_CLOCK_SYNC_SOURCE
|
||||||
|
/* The number of clusters within the Port. This corresponds to the number of
|
||||||
|
* AUDIO_CLUSTER, VIDEO_CLUSTER or SENSOR_CLUSTER descriptors which represent
|
||||||
|
* these clusters. */
|
||||||
|
// TODO: Validate value
|
||||||
|
#define DSC_STREAM_PORT_INPUT_NUMBER_OF_CONTROLS 0
|
||||||
|
#define DSC_STREAM_PORT_INPUT_BASE_CONTROL 0
|
||||||
|
// TODO: Validate value
|
||||||
|
#define DSC_STREAM_PORT_INPUT_NUMBER_OF_CLUSTERS 8
|
||||||
|
#define DSC_STREAM_PORT_INPUT_BASE_CLUSTER 0
|
||||||
|
#define DSC_STREAM_PORT_INPUT_NUMBER_OF_MAPS 1
|
||||||
|
#define DSC_STREAM_PORT_INPUT_BASE_MAP 0
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.13 STREAM_PORT_OUTPUT Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.7 */
|
||||||
|
|
||||||
|
#define DSC_STREAM_PORT_OUTPUT_CLOCK_DOMAIN_INDEX 0
|
||||||
|
#define DSC_STREAM_PORT_OUTPUT_PORT_FLAGS AVB_AEM_PORT_FLAG_NO_FLAG
|
||||||
|
#define DSC_STREAM_PORT_OUTPUT_NUMBER_OF_CONTROLS 0
|
||||||
|
#define DSC_STREAM_PORT_OUTPUT_BASE_CONTROL 0
|
||||||
|
// TODO: Verify
|
||||||
|
#define DSC_STREAM_PORT_OUTPUT_NUMBER_OF_CLUSTERS 8
|
||||||
|
#define DSC_STREAM_PORT_OUTPUT_BASE_CLUSTER 8
|
||||||
|
#define DSC_STREAM_PORT_OUTPUT_NUMBER_OF_MAPS 1
|
||||||
|
#define DSC_STREAM_PORT_OUTPUT_BASE_MAP 1
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.3 AUDIO_UNIT Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.3 */
|
||||||
|
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.3.1
|
||||||
|
* A sampling rate consists of a 3 bit pull field
|
||||||
|
* representing a multiplier and a 29 bit
|
||||||
|
* base_frequency in hertz, as detailed in Figure 7-2.
|
||||||
|
* The pull field specifies the multiplier modifier
|
||||||
|
* of the base_frequency field which is required to
|
||||||
|
* calculate the appropriate nominal sampling rate.
|
||||||
|
* The pull field may have one of the values defined
|
||||||
|
* in Table 7-70:
|
||||||
|
* The base_frequency field defines the nominal base
|
||||||
|
* sampling rate in Hz, from 1 Hz to 536 870 911 Hz.
|
||||||
|
* The value of this field is augmented by the
|
||||||
|
* pull field value.*/
|
||||||
|
#define BUILD_SAMPLING_RATE(pull, base_freq_hz) \
|
||||||
|
(((uint32_t)(pull) << 29) | ((uint32_t)(base_freq_hz) & 0x1FFFFFFF))
|
||||||
|
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_OBJECT_NAME ""
|
||||||
|
#define DSC_AUDIO_UNIT_LOCALIZED_DESCRIPTION 0xFFFF
|
||||||
|
#define DSC_AUDIO_UNIT_CLOCK_DOMAIN_INDEX 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_STREAM_INPUT_PORTS 0x0001
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_STREAM_INPUT_PORT 0x0000
|
||||||
|
|
||||||
|
#if TALKER_ENABLE
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_STREAM_OUTPUT_PORTS 0x0001
|
||||||
|
#else
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_STREAM_OUTPUT_PORTS 0x0000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_STREAM_OUTPUT_PORT 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_EXTERNAL_INPUT_PORTS 0x0008
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_EXTERNAL_INPUT_PORT 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_EXTERNAL_OUTPUT_PORTS 0x0008
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_EXTERNAL_OUTPUT_PORT 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_INTERNAL_INPUT_PORTS 0x0000
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_INTERNAL_INPUT_PORT 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_INTERNAL_OUTPUT_PORTS 0x0000
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_INTERNAL_OUTPUT_PORT 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_CONTROLS 0x0000
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_CONTROL 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_SIGNAL_SELECTORS 0x0000
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_SIGNAL_SELECTOR 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_MIXERS 0x0000
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_MIXER 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_MATRICES 0x0000
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_MATRIX 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_SPLITTERS 0x0000
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_SPLITTER 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_COMBINERS 0x0000
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_COMBINER 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_DEMULTIPLEXERS 0x0000
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_DEMULTIPLEXER 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_MULTIPLEXERS 0x0000
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_MULTIPLEXER 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_TRANSCODERS 0x0000
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_TRANSCODER 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_NUMBER_OF_CONTROL_BLOCKS 0x0000
|
||||||
|
#define DSC_AUDIO_UNIT_BASE_CONTROL_BLOCK 0x0000
|
||||||
|
|
||||||
|
#define DSC_AUDIO_UNIT_SAMPLING_RATE_PULL 0
|
||||||
|
#define DSC_AUDIO_UNIT_SAMPLING_RATE_BASE_FREQ_IN_HZ 48000
|
||||||
|
#define DSC_AUDIO_UNIT_CURRENT_SAMPLING_RATE_IN_HZ \
|
||||||
|
BUILD_SAMPLING_RATE(DSC_AUDIO_UNIT_SAMPLING_RATE_PULL, DSC_AUDIO_UNIT_SAMPLING_RATE_BASE_FREQ_IN_HZ)
|
||||||
|
/* The offset to the sample_rates field from the start of the descriptor.
|
||||||
|
* This field is 144 for this version of AEM.*/
|
||||||
|
#define DSC_AUDIO_UNIT_SAMPLING_RATES_OFFSET 144
|
||||||
|
#define DSC_AUDIO_UNIT_SUPPORTED_SAMPLING_RATE_COUNT 0x0001
|
||||||
|
#define DSC_AUDIO_UNIT_SUPPORTED_SAMPLING_RATE_IN_HZ_0 \
|
||||||
|
BUILD_SAMPLING_RATE(DSC_AUDIO_UNIT_SAMPLING_RATE_PULL, DSC_AUDIO_UNIT_SAMPLING_RATE_BASE_FREQ_IN_HZ)
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.6 STREAM_INPUT Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.4 */
|
||||||
|
|
||||||
|
// TODO: 1722.1 lists redundant parameters that are not mentioned here.
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_OBJECT_NAME "Stream 1"
|
||||||
|
#define DSC_STREAM_INPUT_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
|
||||||
|
#define DSC_STREAM_INPUT_CLOCK_DOMAIN_INDEX 0
|
||||||
|
#define DSC_STREAM_INPUT_STREAM_FLAGS (AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE | AVB_AEM_DESC_STREAM_FLAG_CLASS_A)
|
||||||
|
// To match my talker
|
||||||
|
// TODO: Define based on AUDIO_UNIT etc.
|
||||||
|
#define DSC_STREAM_INPUT_CURRENT_FORMAT 0x0205022001006000ULL
|
||||||
|
|
||||||
|
// TODO: Is 132 here, should be 138 according to spec
|
||||||
|
#define DSC_STREAM_INPUT_FORMATS_OFFSET (4 + sizeof(struct avb_aem_desc_stream))
|
||||||
|
#define DSC_STREAM_INPUT_NUMBER_OF_FORMATS 5
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_BACKUP_TALKER_ENTITY_ID_0 0
|
||||||
|
#define DSC_STREAM_INPUT_BACKUP_TALKER_UNIQUE_ID_0 0
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_BACKUP_TALKER_ENTITY_ID_1 0
|
||||||
|
#define DSC_STREAM_INPUT_BACKUP_TALKER_UNIQUE_ID_1 0
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_BACKUP_TALKER_ENTITY_ID_2 0
|
||||||
|
#define DSC_STREAM_INPUT_BACKUP_TALKER_UNIQUE_ID_2 0
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_BACKEDUP_TALKER_ENTITY_ID 0
|
||||||
|
#define DSC_STREAM_INPUT_BACKEDUP_TALKER_UNIQUE_ID 0
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_AVB_INTERFACE_INDEX 0
|
||||||
|
#define DSC_STREAM_INPUT_BUFFER_LENGTH_IN_NS 2126000
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_FORMATS_0 DSC_STREAM_INPUT_CURRENT_FORMAT
|
||||||
|
#define DSC_STREAM_INPUT_FORMATS_1 0x0205022000406000ULL
|
||||||
|
#define DSC_STREAM_INPUT_FORMATS_2 0x0205022000806000ULL
|
||||||
|
#define DSC_STREAM_INPUT_FORMATS_3 0x0205022001806000ULL
|
||||||
|
#define DSC_STREAM_INPUT_FORMATS_4 0x0205022002006000ULL
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.6 STREAM_INPUT Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.4 */
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_CRF_OBJECT_NAME "CRF"
|
||||||
|
#define DSC_STREAM_INPUT_CRF_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
|
||||||
|
#define DSC_STREAM_INPUT_CRF_CLOCK_DOMAIN_INDEX 0
|
||||||
|
#define DSC_STREAM_INPUT_CRF_STREAM_FLAGS (AVB_AEM_DESC_STREAM_FLAG_SYNC_SOURCE | AVB_AEM_DESC_STREAM_FLAG_CLASS_A)
|
||||||
|
#define DSC_STREAM_INPUT_CRF_CURRENT_FORMAT 0x041060010000BB80ULL
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_CRF_FORMATS_OFFSET (4 + sizeof(struct avb_aem_desc_stream))
|
||||||
|
#define DSC_STREAM_INPUT_CRF_NUMBER_OF_FORMATS 1
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_CRF_BACKUP_TALKER_ENTITY_ID_0 0
|
||||||
|
#define DSC_STREAM_INPUT_CRF_BACKUP_TALKER_UNIQUE_ID_0 0
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_CRF_BACKUP_TALKER_ENTITY_ID_1 0
|
||||||
|
#define DSC_STREAM_INPUT_CRF_BACKUP_TALKER_UNIQUE_ID_1 0
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_CRF_BACKUP_TALKER_ENTITY_ID_2 0
|
||||||
|
#define DSC_STREAM_INPUT_CRF_BACKUP_TALKER_UNIQUE_ID_2 0
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_CRF_BACKEDUP_TALKER_ENTITY_ID 0
|
||||||
|
#define DSC_STREAM_INPUT_CRF_BACKEDUP_TALKER_UNIQUE_ID 0
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_CRF_AVB_INTERFACE_INDEX 0
|
||||||
|
#define DSC_STREAM_INPUT_CRF_BUFFER_LENGTH_IN_NS 2126000
|
||||||
|
|
||||||
|
#define DSC_STREAM_INPUT_CRF_FORMATS_0 DSC_STREAM_INPUT_CRF_CURRENT_FORMAT
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.6 STREAM_OUTPUT Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.4 */
|
||||||
|
|
||||||
|
#define DSC_STREAM_OUTPUT_OBJECT_NAME "Stream output 1"
|
||||||
|
#define DSC_STREAM_OUTPUT_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
|
||||||
|
#define DSC_STREAM_OUTPUT_CLOCK_DOMAIN_INDEX 0
|
||||||
|
#define DSC_STREAM_OUTPUT_STREAM_FLAGS (AVB_AEM_DESC_STREAM_FLAG_CLASS_A)
|
||||||
|
#define DSC_STREAM_OUTPUT_CURRENT_FORMAT 0x0205022002006000ULL
|
||||||
|
|
||||||
|
#define DSC_STREAM_OUTPUT_FORMATS_OFFSET (4 + sizeof(struct avb_aem_desc_stream))
|
||||||
|
#define DSC_STREAM_OUTPUT_NUMBER_OF_FORMATS 5
|
||||||
|
|
||||||
|
#define DSC_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_ID_0 0
|
||||||
|
#define DSC_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_ID_0 0
|
||||||
|
|
||||||
|
#define DSC_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_ID_1 0
|
||||||
|
#define DSC_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_ID_1 0
|
||||||
|
|
||||||
|
#define DSC_STREAM_OUTPUT_BACKUP_TALKER_ENTITY_ID_2 0
|
||||||
|
#define DSC_STREAM_OUTPUT_BACKUP_TALKER_UNIQUE_ID_2 0
|
||||||
|
|
||||||
|
#define DSC_STREAM_OUTPUT_BACKEDUP_TALKER_ENTITY_ID 0
|
||||||
|
#define DSC_STREAM_OUTPUT_BACKEDUP_TALKER_UNIQUE_ID 0
|
||||||
|
|
||||||
|
#define DSC_STREAM_OUTPUT_AVB_INTERFACE_INDEX 0
|
||||||
|
#define DSC_STREAM_OUTPUT_BUFFER_LENGTH_IN_NS 8
|
||||||
|
|
||||||
|
// TODO: No hardcoded values!
|
||||||
|
#define DSC_STREAM_OUTPUT_FORMATS_0 0x0205022000406000ULL
|
||||||
|
#define DSC_STREAM_OUTPUT_FORMATS_1 0x0205022000806000ULL
|
||||||
|
#define DSC_STREAM_OUTPUT_FORMATS_2 0x0205022001006000ULL
|
||||||
|
#define DSC_STREAM_OUTPUT_FORMATS_3 0x0205022001806000ULL
|
||||||
|
#define DSC_STREAM_OUTPUT_FORMATS_4 DSC_STREAM_OUTPUT_CURRENT_FORMAT
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.8 AVB Interface Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.5 */
|
||||||
|
|
||||||
|
#define DSC_AVB_INTERFACE_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
|
||||||
|
#define DSC_AVB_INTERFACE_INTERFACE_FLAGS (AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_GRANDMASTER_SUPPORTED | \
|
||||||
|
AVB_AEM_DESC_AVB_INTERFACE_FLAG_GPTP_SUPPORTED | \
|
||||||
|
AVB_AEM_DESC_AVB_INTERFACE_FLAG_SRP_SUPPORTED)
|
||||||
|
// TODO: This is a dynamic parameter
|
||||||
|
#define DSC_AVB_INTERFACE_CLOCK_IDENTITY 0x3cc0c6FFFE0002CB
|
||||||
|
#define DSC_AVB_INTERFACE_PRIORITY1 0xF8
|
||||||
|
#define DSC_AVB_INTERFACE_CLOCK_CLASS 0xF8
|
||||||
|
#define DSC_AVB_INTERFACE_OFFSET_SCALED_LOG_VARIANCE 0x436A
|
||||||
|
#define DSC_AVB_INTERFACE_CLOCK_ACCURACY 0x21
|
||||||
|
#define DSC_AVB_INTERFACE_PRIORITY2 0xF8
|
||||||
|
#define DSC_AVB_INTERFACE_DOMAIN_NUMBER 0
|
||||||
|
#define DSC_AVB_INTERFACE_LOG_SYNC_INTERVAL 0
|
||||||
|
#define DSC_AVB_INTERFACE_LOG_ANNOUNCE_INTERVAL 0
|
||||||
|
#define DSC_AVB_INTERFACE_PDELAY_INTERVAL 0
|
||||||
|
#define DSC_AVB_INTERFACE_PORT_NUMBER 0
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.9 CLOCK_SOURCE Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.6 */
|
||||||
|
|
||||||
|
// Internal Clock Source
|
||||||
|
#define DSC_CLOCK_SOURCE_INTERNAL_OBJECT_NAME "Internal"
|
||||||
|
#define DSC_CLOCK_SOURCE_INTERNAL_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
|
||||||
|
#define DSC_CLOCK_SOURCE_INTERNAL_FLAGS 0x0002
|
||||||
|
#define DSC_CLOCK_SOURCE_INTERNAL_TYPE AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INTERNAL
|
||||||
|
#define DSC_CLOCK_SOURCE_INTERNAL_IDENTIFIER 0
|
||||||
|
#define DSC_CLOCK_SOURCE_INTERNAL_LOCATION_TYPE AVB_AEM_DESC_CLOCK_SOURCE
|
||||||
|
#define DSC_CLOCK_SOURCE_INTERNAL_LOCATION_INDEX 0
|
||||||
|
|
||||||
|
// AAF Stream Clock Source
|
||||||
|
#define DSC_CLOCK_SOURCE_AAF_OBJECT_NAME "Stream Clock"
|
||||||
|
#define DSC_CLOCK_SOURCE_AAF_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
|
||||||
|
#define DSC_CLOCK_SOURCE_AAF_FLAGS 0x0002
|
||||||
|
#define DSC_CLOCK_SOURCE_AAF_TYPE AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM
|
||||||
|
#define DSC_CLOCK_SOURCE_AAF_IDENTIFIER 0
|
||||||
|
#define DSC_CLOCK_SOURCE_AAF_LOCATION_TYPE AVB_AEM_DESC_STREAM_INPUT
|
||||||
|
#define DSC_CLOCK_SOURCE_AAF_LOCATION_INDEX 0
|
||||||
|
|
||||||
|
// CRF Clock Source
|
||||||
|
#define DSC_CLOCK_SOURCE_CRF_OBJECT_NAME "CRF Clock"
|
||||||
|
#define DSC_CLOCK_SOURCE_CRF_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
|
||||||
|
#define DSC_CLOCK_SOURCE_CRF_FLAGS 0x0002
|
||||||
|
#define DSC_CLOCK_SOURCE_CRF_TYPE AVB_AEM_DESC_CLOCK_SOURCE_TYPE_INPUT_STREAM
|
||||||
|
#define DSC_CLOCK_SOURCE_CRF_IDENTIFIER 0
|
||||||
|
#define DSC_CLOCK_SOURCE_CRF_LOCATION_TYPE AVB_AEM_DESC_STREAM_INPUT
|
||||||
|
#define DSC_CLOCK_SOURCE_CRF_LOCATION_INDEX 1
|
||||||
|
|
||||||
|
/**************************************************************************************/
|
||||||
|
/* IEEE 1722.1-2021, Sec. 7.2.32 CLOCK_DOMAIN Descriptor */
|
||||||
|
/* Milan v1.2, Sec. 5.3.3.11 */
|
||||||
|
|
||||||
|
#define DSC_CLOCK_DOMAIN_OBJECT_NAME "Clock Reference Format"
|
||||||
|
#define DSC_CLOCK_DOMAIN_LOCALIZED_DESCRIPTION AVB_AEM_DESC_INVALID
|
||||||
|
#define DSC_CLOCK_DOMAIN_CLOCK_SOURCE_INDEX 0
|
||||||
|
#define DSC_CLOCK_DOMAIN_DESCRIPTOR_COUNTS_OFFSET (4 + sizeof(struct avb_aem_desc_clock_domain))
|
||||||
|
#define DSC_CLOCK_DOMAIN_CLOCK_SOURCES_COUNT 3
|
||||||
|
|
||||||
|
#define DSC_CLOCK_DOMAIN_SOURCES_0 0 // Internal
|
||||||
|
#define DSC_CLOCK_DOMAIN_SOURCES_1 1 // AAF
|
||||||
|
#define DSC_CLOCK_DOMAIN_SOURCES_2 2 // CRF
|
||||||
|
|
||||||
|
#endif // __DESCRIPTOR_ENTITY_MODEL_MILAN_H__
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
|
|
||||||
#include "es-builder.h"
|
#include "es-builder.h"
|
||||||
#include "aecp-aem-descriptors.h"
|
#include "aecp-aem-state.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief The goal of this modules is to create a an entity and
|
* \brief The goal of this modules is to create a an entity and
|
||||||
|
|
@ -31,9 +32,124 @@ struct es_builder_st {
|
||||||
es_builder_cb_t build_descriptor_cb;
|
es_builder_cb_t build_descriptor_cb;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** All callback that needs a status information */
|
/*
|
||||||
static const struct es_builder_st es_builder[AVB_AEM_DESC_LAST_RESERVED_17221] =
|
* \brief The Entity keeps track of multiple things, the locks the current
|
||||||
|
* configuration use for instance. That tragets the Milan V1.2 mode only
|
||||||
|
*/
|
||||||
|
static void *es_builder_desc_entity_milan_v12(struct server *server,
|
||||||
|
uint16_t type, uint16_t index, size_t size, void *ptr)
|
||||||
{
|
{
|
||||||
|
struct aecp_aem_entity_milan_state entity_state = {0};
|
||||||
|
void *ptr_alloc;
|
||||||
|
struct aecp_aem_entity_state *state =
|
||||||
|
(struct aecp_aem_entity_state *) &entity_state;
|
||||||
|
|
||||||
|
memcpy(&state->desc, ptr, size);
|
||||||
|
|
||||||
|
ptr_alloc = server_add_descriptor(server, type, index, sizeof(entity_state),
|
||||||
|
&entity_state);
|
||||||
|
|
||||||
|
if (!ptr_alloc) {
|
||||||
|
pw_log_error("Error durring allocation\n");
|
||||||
|
spa_assert(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr_alloc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A generic function to avoid code duplicate for the streams */
|
||||||
|
static void *es_buidler_desc_stream_general_prepare(struct server *server,
|
||||||
|
uint16_t type, uint16_t index, size_t size, void *ptr)
|
||||||
|
{
|
||||||
|
void *ptr_alloc;
|
||||||
|
struct stream *stream;
|
||||||
|
enum spa_direction direction;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case AVB_AEM_DESC_STREAM_INPUT:
|
||||||
|
struct aecp_aem_stream_input_state *pstream_input;
|
||||||
|
struct aecp_aem_stream_input_state stream_input = { 0 };
|
||||||
|
|
||||||
|
memcpy(&stream_input.desc, ptr, size);
|
||||||
|
ptr_alloc = server_add_descriptor(server, type, index,
|
||||||
|
sizeof(stream_input), &stream_input);
|
||||||
|
if (!ptr_alloc) {
|
||||||
|
pw_log_error("Allocation failed\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pstream_input = ptr_alloc;
|
||||||
|
stream = &pstream_input->stream;
|
||||||
|
direction = SPA_DIRECTION_INPUT;
|
||||||
|
break;
|
||||||
|
case AVB_AEM_DESC_STREAM_OUTPUT:
|
||||||
|
struct aecp_aem_stream_output_state *pstream_output;
|
||||||
|
struct aecp_aem_stream_output_state stream_output = { 0 };
|
||||||
|
|
||||||
|
memcpy(&stream_output.desc, ptr, size);
|
||||||
|
ptr_alloc = server_add_descriptor(server, type, index,
|
||||||
|
sizeof(stream_output), &stream_output);
|
||||||
|
if (!ptr_alloc) {
|
||||||
|
pw_log_error("Allocation failed\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pstream_output = ptr_alloc;
|
||||||
|
stream = &pstream_output->stream;
|
||||||
|
direction = SPA_DIRECTION_OUTPUT;
|
||||||
|
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pw_log_error("Only STREAM_INPUT and STREAM_OUTPUT\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!server_create_stream(server, stream, direction, index)) {
|
||||||
|
pw_log_error("Could not create/initialize a stream");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ptr_alloc;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Assign a ID to an specific builder
|
||||||
|
#define HELPER_ES_BUIDLER(type, callback) \
|
||||||
|
[type] = { .build_descriptor_cb = callback }
|
||||||
|
|
||||||
|
/** All callback that needs a status information for the AVB/Milan V1.2 */
|
||||||
|
static const struct es_builder_st es_builder_milan_v12[] =
|
||||||
|
{
|
||||||
|
HELPER_ES_BUIDLER(AVB_AEM_DESC_ENTITY, es_builder_desc_entity_milan_v12),
|
||||||
|
HELPER_ES_BUIDLER(AVB_AEM_DESC_STREAM_OUTPUT, es_buidler_desc_stream_general_prepare),
|
||||||
|
HELPER_ES_BUIDLER(AVB_AEM_DESC_STREAM_INPUT, es_buidler_desc_stream_general_prepare),
|
||||||
|
};
|
||||||
|
|
||||||
|
/** All callback that needs a status information for Legacy AVB*/
|
||||||
|
static const struct es_builder_st es_builder_legacy_avb[] =
|
||||||
|
{
|
||||||
|
HELPER_ES_BUIDLER(AVB_AEM_DESC_STREAM_OUTPUT, es_buidler_desc_stream_general_prepare),
|
||||||
|
HELPER_ES_BUIDLER(AVB_AEM_DESC_STREAM_INPUT, es_buidler_desc_stream_general_prepare),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief keep the list of the supported avb flavors here
|
||||||
|
*/
|
||||||
|
static const struct {
|
||||||
|
const struct es_builder_st *es_builder;
|
||||||
|
/** Number of elements in the es_builder */
|
||||||
|
size_t count;
|
||||||
|
} es_builders[] = {
|
||||||
|
[AVB_MODE_LEGACY] = {
|
||||||
|
.es_builder = es_builder_legacy_avb,
|
||||||
|
.count = ARRAY_SIZE(es_builder_legacy_avb),
|
||||||
|
},
|
||||||
|
|
||||||
|
[AVB_MODE_MILAN_V12] = {
|
||||||
|
.es_builder = es_builder_milan_v12,
|
||||||
|
.count = ARRAY_SIZE(es_builder_milan_v12),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -44,21 +160,34 @@ static const struct es_builder_st es_builder[AVB_AEM_DESC_LAST_RESERVED_17221] =
|
||||||
void es_builder_add_descriptor(struct server *server, uint16_t type,
|
void es_builder_add_descriptor(struct server *server, uint16_t type,
|
||||||
uint16_t index, size_t size, void *ptr_aem)
|
uint16_t index, size_t size, void *ptr_aem)
|
||||||
{
|
{
|
||||||
|
const struct es_builder_st *es_builder;
|
||||||
void *desc_ptr;
|
void *desc_ptr;
|
||||||
struct descriptor *d;
|
struct descriptor *d;
|
||||||
|
enum avb_mode avb_mode;
|
||||||
|
bool std_processing = false;
|
||||||
|
|
||||||
if (!server) {
|
if (!server) {
|
||||||
pw_log_error("Invalid server, it is empty %p\n", server);
|
pw_log_error("Invalid server, it is empty %p\n", server);
|
||||||
spa_assert(0);
|
spa_assert(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type >= AVB_AEM_DESC_LAST_RESERVED_17221) {
|
avb_mode = server->avb_mode;
|
||||||
pw_log_error("Invalid Type %u\n", type);
|
if (avb_mode >= AVB_MODE_MAX) {
|
||||||
|
pw_log_error("AVB mode is not valid received %d\n", avb_mode);
|
||||||
spa_assert(0);
|
spa_assert(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Look if the descriptor has a callback to attach more status data */
|
|
||||||
|
es_builder = es_builders[avb_mode].es_builder;
|
||||||
|
if (type > es_builders[avb_mode].count) {
|
||||||
|
std_processing = true;
|
||||||
|
} else {
|
||||||
if (!es_builder[type].build_descriptor_cb) {
|
if (!es_builder[type].build_descriptor_cb) {
|
||||||
|
std_processing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std_processing) {
|
||||||
if (!server_add_descriptor(server, type, index, size, ptr_aem)) {
|
if (!server_add_descriptor(server, type, index, size, ptr_aem)) {
|
||||||
pw_log_error("Could not allocate descriptor %u at "
|
pw_log_error("Could not allocate descriptor %u at "
|
||||||
"index %u the avb aem type\n", type, index);
|
"index %u the avb aem type\n", type, index);
|
||||||
|
|
@ -75,6 +204,7 @@ void es_builder_add_descriptor(struct server *server, uint16_t type,
|
||||||
|
|
||||||
spa_assert(0);
|
spa_assert(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
d = (struct descriptor *) desc_ptr;
|
d = (struct descriptor *) desc_ptr;
|
||||||
d->size = size;
|
d->size = size;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,27 @@ struct descriptor {
|
||||||
void *ptr;
|
void *ptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
enum avb_mode {
|
||||||
|
/** The legacy AVB Mode */
|
||||||
|
AVB_MODE_LEGACY,
|
||||||
|
/**
|
||||||
|
* \brief Milan version 1.2, which subset of the AVB,
|
||||||
|
* \see Milan Specifications https://avnu.org/resource/milan-specification/
|
||||||
|
*/
|
||||||
|
AVB_MODE_MILAN_V12,
|
||||||
|
|
||||||
|
/** Future AVB mode will be added here if necessary */
|
||||||
|
AVB_MODE_MAX
|
||||||
|
};
|
||||||
|
|
||||||
struct server {
|
struct server {
|
||||||
struct spa_list link;
|
struct spa_list link;
|
||||||
struct impl *impl;
|
struct impl *impl;
|
||||||
|
|
||||||
char *ifname;
|
char *ifname;
|
||||||
|
/** Parsed from the configuration pipewire-avb.conf */
|
||||||
|
enum avb_mode avb_mode;
|
||||||
uint8_t mac_addr[6];
|
uint8_t mac_addr[6];
|
||||||
uint64_t entity_id;
|
uint64_t entity_id;
|
||||||
int ifindex;
|
int ifindex;
|
||||||
|
|
@ -67,7 +83,6 @@ struct server {
|
||||||
struct spa_hook_list listener_list;
|
struct spa_hook_list listener_list;
|
||||||
|
|
||||||
struct spa_list descriptors;
|
struct spa_list descriptors;
|
||||||
struct spa_list streams;
|
|
||||||
|
|
||||||
unsigned debug_messages:1;
|
unsigned debug_messages:1;
|
||||||
|
|
||||||
|
|
@ -82,7 +97,18 @@ struct server {
|
||||||
|
|
||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
|
|
||||||
static inline const struct descriptor *server_find_descriptor(struct server *server,
|
static inline void server_destroy_descriptors(struct server *server)
|
||||||
|
{
|
||||||
|
struct descriptor *d, *t;
|
||||||
|
|
||||||
|
spa_list_for_each_safe(d, t, &server->descriptors, link) {
|
||||||
|
free(d->ptr);
|
||||||
|
spa_list_remove(&d->link);
|
||||||
|
free(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct descriptor *server_find_descriptor(struct server *server,
|
||||||
uint16_t type, uint16_t index)
|
uint16_t type, uint16_t index)
|
||||||
{
|
{
|
||||||
struct descriptor *d;
|
struct descriptor *d;
|
||||||
|
|
@ -108,20 +134,10 @@ static inline void *server_add_descriptor(struct server *server,
|
||||||
if (ptr)
|
if (ptr)
|
||||||
memcpy(d->ptr, ptr, size);
|
memcpy(d->ptr, ptr, size);
|
||||||
spa_list_append(&server->descriptors, &d->link);
|
spa_list_append(&server->descriptors, &d->link);
|
||||||
return d->ptr;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline struct stream *server_find_stream(struct server *server,
|
const char *get_avb_mode_str(enum avb_mode mode);
|
||||||
enum spa_direction direction, uint16_t index)
|
|
||||||
{
|
|
||||||
struct stream *s;
|
|
||||||
spa_list_for_each(s, &server->streams, link) {
|
|
||||||
if (s->direction == direction &&
|
|
||||||
s->index == index)
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props);
|
struct server *avdecc_server_new(struct impl *impl, struct spa_dict *props);
|
||||||
void avdecc_server_free(struct server *server);
|
void avdecc_server_free(struct server *server);
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,14 @@ struct mrp {
|
||||||
static void mrp_destroy(void *data)
|
static void mrp_destroy(void *data)
|
||||||
{
|
{
|
||||||
struct mrp *mrp = data;
|
struct mrp *mrp = data;
|
||||||
|
struct attribute *a, *t;
|
||||||
spa_hook_remove(&mrp->server_listener);
|
spa_hook_remove(&mrp->server_listener);
|
||||||
|
|
||||||
|
spa_list_for_each_safe(a, t, &mrp->attributes, link) {
|
||||||
|
spa_list_remove(&a->link);
|
||||||
|
free(a);
|
||||||
|
}
|
||||||
|
|
||||||
free(mrp);
|
free(mrp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@
|
||||||
|
|
||||||
#include "iec61883.h"
|
#include "iec61883.h"
|
||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
|
#include "aecp-aem-state.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "aecp-aem-descriptors.h"
|
|
||||||
|
|
||||||
static void on_stream_destroy(void *d)
|
static void on_stream_destroy(void *d)
|
||||||
{
|
{
|
||||||
|
|
@ -235,34 +235,17 @@ static const struct pw_stream_events sink_stream_events = {
|
||||||
.process = on_sink_stream_process
|
.process = on_sink_stream_process
|
||||||
};
|
};
|
||||||
|
|
||||||
struct stream *server_create_stream(struct server *server,
|
struct stream *server_create_stream(struct server *server, struct stream *stream,
|
||||||
enum spa_direction direction, uint16_t index)
|
enum spa_direction direction, uint16_t index)
|
||||||
{
|
{
|
||||||
struct stream *stream;
|
|
||||||
const struct descriptor *desc;
|
|
||||||
uint32_t n_params;
|
uint32_t n_params;
|
||||||
const struct spa_pod *params[1];
|
const struct spa_pod *params[1];
|
||||||
uint8_t buffer[1024];
|
uint8_t buffer[1024];
|
||||||
struct spa_pod_builder b;
|
struct spa_pod_builder b;
|
||||||
int res;
|
int res;
|
||||||
|
|
||||||
desc = server_find_descriptor(server,
|
|
||||||
direction == SPA_DIRECTION_INPUT ?
|
|
||||||
AVB_AEM_DESC_STREAM_INPUT :
|
|
||||||
AVB_AEM_DESC_STREAM_OUTPUT, index);
|
|
||||||
if (desc == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
stream = calloc(1, sizeof(*stream));
|
|
||||||
if (stream == NULL)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
stream->server = server;
|
stream->server = server;
|
||||||
stream->direction = direction;
|
stream->direction = direction;
|
||||||
stream->index = index;
|
|
||||||
stream->desc = desc;
|
|
||||||
spa_list_append(&server->streams, &stream->link);
|
|
||||||
|
|
||||||
stream->prio = AVB_MSRP_PRIORITY_DEFAULT;
|
stream->prio = AVB_MSRP_PRIORITY_DEFAULT;
|
||||||
stream->vlan_id = AVB_DEFAULT_VLAN;
|
stream->vlan_id = AVB_DEFAULT_VLAN;
|
||||||
|
|
||||||
|
|
@ -361,8 +344,6 @@ error_free:
|
||||||
void stream_destroy(struct stream *stream)
|
void stream_destroy(struct stream *stream)
|
||||||
{
|
{
|
||||||
avb_mrp_attribute_destroy(stream->listener_attr->mrp);
|
avb_mrp_attribute_destroy(stream->listener_attr->mrp);
|
||||||
spa_list_remove(&stream->link);
|
|
||||||
free(stream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int setup_socket(struct stream *stream)
|
static int setup_socket(struct stream *stream)
|
||||||
|
|
@ -496,7 +477,7 @@ static void on_socket_data(void *data, int fd, uint32_t mask)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int stream_activate(struct stream *stream, uint64_t now)
|
int stream_activate(struct stream *stream, uint16_t index, uint64_t now)
|
||||||
{
|
{
|
||||||
struct server *server = stream->server;
|
struct server *server = stream->server;
|
||||||
struct avb_frame_header *h = (void*)stream->pdu;
|
struct avb_frame_header *h = (void*)stream->pdu;
|
||||||
|
|
@ -528,7 +509,7 @@ int stream_activate(struct stream *stream, uint64_t now)
|
||||||
stream->talker_attr->attr.talker.stream_id = htobe64(stream->peer_id);
|
stream->talker_attr->attr.talker.stream_id = htobe64(stream->peer_id);
|
||||||
avb_mrp_attribute_begin(stream->talker_attr->mrp, now);
|
avb_mrp_attribute_begin(stream->talker_attr->mrp, now);
|
||||||
} else {
|
} else {
|
||||||
if ((res = avb_maap_get_address(server->maap, stream->addr, stream->index)) < 0)
|
if ((res = avb_maap_get_address(server->maap, stream->addr, index)) < 0)
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
stream->listener_attr->attr.listener.stream_id = htobe64(stream->id);
|
stream->listener_attr->attr.listener.stream_id = htobe64(stream->id);
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,6 @@ struct stream {
|
||||||
struct server *server;
|
struct server *server;
|
||||||
|
|
||||||
uint16_t direction;
|
uint16_t direction;
|
||||||
uint16_t index;
|
|
||||||
const struct descriptor *desc;
|
|
||||||
uint64_t id;
|
uint64_t id;
|
||||||
uint64_t peer_id;
|
uint64_t peer_id;
|
||||||
|
|
||||||
|
|
@ -73,12 +71,12 @@ struct stream {
|
||||||
#include "mvrp.h"
|
#include "mvrp.h"
|
||||||
#include "maap.h"
|
#include "maap.h"
|
||||||
|
|
||||||
struct stream *server_create_stream(struct server *server,
|
struct stream *server_create_stream(struct server *server, struct stream *stream,
|
||||||
enum spa_direction direction, uint16_t index);
|
enum spa_direction direction, uint16_t index);
|
||||||
|
|
||||||
void stream_destroy(struct stream *stream);
|
void stream_destroy(struct stream *stream);
|
||||||
|
|
||||||
int stream_activate(struct stream *stream, uint64_t now);
|
int stream_activate(struct stream *stream, uint16_t index, uint64_t now);
|
||||||
int stream_deactivate(struct stream *stream, uint64_t now);
|
int stream_deactivate(struct stream *stream, uint64_t now);
|
||||||
|
|
||||||
#endif /* AVB_STREAM_H */
|
#endif /* AVB_STREAM_H */
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
|
|
||||||
#include "internal.h"
|
#include "internal.h"
|
||||||
|
|
||||||
|
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
|
||||||
|
|
||||||
static inline char *avb_utils_format_id(char *str, size_t size, const uint64_t id)
|
static inline char *avb_utils_format_id(char *str, size_t size, const uint64_t id)
|
||||||
{
|
{
|
||||||
snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x:%04x",
|
snprintf(str, size, "%02x:%02x:%02x:%02x:%02x:%02x:%04x",
|
||||||
|
|
|
||||||
|
|
@ -894,6 +894,7 @@ do_port_use_buffers(struct impl *impl,
|
||||||
case SPA_DATA_MemPtr:
|
case SPA_DATA_MemPtr:
|
||||||
spa_log_debug(impl->log, "mem %d %zd", j, SPA_PTRDIFF(d->data, baseptr));
|
spa_log_debug(impl->log, "mem %d %zd", j, SPA_PTRDIFF(d->data, baseptr));
|
||||||
b->datas[j].data = SPA_INT_TO_PTR(SPA_PTRDIFF(d->data, baseptr));
|
b->datas[j].data = SPA_INT_TO_PTR(SPA_PTRDIFF(d->data, baseptr));
|
||||||
|
SPA_FLAG_CLEAR(b->datas[j].flags, SPA_DATA_FLAG_MAPPABLE);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
b->datas[j].type = SPA_ID_INVALID;
|
b->datas[j].type = SPA_ID_INVALID;
|
||||||
|
|
|
||||||
|
|
@ -735,7 +735,7 @@ error_exit:
|
||||||
|
|
||||||
static int
|
static int
|
||||||
client_node_port_set_io(void *_data,
|
client_node_port_set_io(void *_data,
|
||||||
uint32_t direction,
|
enum spa_direction direction,
|
||||||
uint32_t port_id,
|
uint32_t port_id,
|
||||||
uint32_t mix_id,
|
uint32_t mix_id,
|
||||||
uint32_t id,
|
uint32_t id,
|
||||||
|
|
|
||||||
|
|
@ -420,11 +420,12 @@ extern struct spa_handle_factory spa_filter_graph_factory;
|
||||||
*
|
*
|
||||||
* ### Delay
|
* ### Delay
|
||||||
*
|
*
|
||||||
* The delay can be used to delay a signal in time.
|
* The delay can be used to delay a signal in time. With the Feedback and Feedforward
|
||||||
|
* controls it can also be used as a comb and an allpass filter.
|
||||||
*
|
*
|
||||||
* The delay has an input port "In" and an output port "Out". It also has
|
* The delay has an input port "In" and an output port "Out". It also has
|
||||||
* a "Delay (s)" control port. It requires a config section in the node declaration
|
* a "Delay (s)" control port and a "Feedback" and "Feedforward" port. It requires a
|
||||||
* in this format:
|
* config section in the node declaration in this format:
|
||||||
*
|
*
|
||||||
*\code{.unparsed}
|
*\code{.unparsed}
|
||||||
* filter.graph = {
|
* filter.graph = {
|
||||||
|
|
@ -439,6 +440,8 @@ extern struct spa_handle_factory spa_filter_graph_factory;
|
||||||
* }
|
* }
|
||||||
* control = {
|
* control = {
|
||||||
* "Delay (s)" = ...
|
* "Delay (s)" = ...
|
||||||
|
* "Feedback" = ...
|
||||||
|
* "Feedforward" = ...
|
||||||
* }
|
* }
|
||||||
* ...
|
* ...
|
||||||
* }
|
* }
|
||||||
|
|
@ -452,6 +455,10 @@ extern struct spa_handle_factory spa_filter_graph_factory;
|
||||||
* - `latency` the latency in seconds. This is 0 by default but in some cases
|
* - `latency` the latency in seconds. This is 0 by default but in some cases
|
||||||
* the delay can be used to introduce latency with this option.
|
* the delay can be used to introduce latency with this option.
|
||||||
*
|
*
|
||||||
|
* With the "Feedback" port one can create a comb filter. With the "Feedback"
|
||||||
|
* port and "Feedforward" port set to A and -A respectively, one can create
|
||||||
|
* an allpass filter. These settings can be used to create custom reverb units.
|
||||||
|
*
|
||||||
* ### Invert
|
* ### Invert
|
||||||
*
|
*
|
||||||
* The invert plugin can be used to invert the phase of the signal.
|
* The invert plugin can be used to invert the phase of the signal.
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,11 @@ struct client {
|
||||||
struct pw_manager_object *metadata_routes;
|
struct pw_manager_object *metadata_routes;
|
||||||
struct pw_properties *routes;
|
struct pw_properties *routes;
|
||||||
|
|
||||||
|
struct pw_manager_object *metadata_schema_sm_settings;
|
||||||
|
bool have_force_mono_audio;
|
||||||
|
struct pw_manager_object *metadata_sm_settings;
|
||||||
|
bool force_mono_audio;
|
||||||
|
|
||||||
uint32_t connect_tag;
|
uint32_t connect_tag;
|
||||||
|
|
||||||
uint32_t in_index;
|
uint32_t in_index;
|
||||||
|
|
|
||||||
|
|
@ -323,5 +323,6 @@ static inline uint32_t port_type_value(const char *port_type)
|
||||||
#define METADATA_CONFIG_DEFAULT_SOURCE "default.configured.audio.source"
|
#define METADATA_CONFIG_DEFAULT_SOURCE "default.configured.audio.source"
|
||||||
#define METADATA_TARGET_NODE "target.node"
|
#define METADATA_TARGET_NODE "target.node"
|
||||||
#define METADATA_TARGET_OBJECT "target.object"
|
#define METADATA_TARGET_OBJECT "target.object"
|
||||||
|
#define METADATA_FEATURES_AUDIO_MONO "node.features.audio.mono"
|
||||||
|
|
||||||
#endif /* PULSE_SERVER_DEFS_H */
|
#endif /* PULSE_SERVER_DEFS_H */
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
#include "collect.h"
|
#include "collect.h"
|
||||||
|
#include "defs.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "manager.h"
|
#include "manager.h"
|
||||||
#include "module.h"
|
#include "module.h"
|
||||||
|
|
@ -89,6 +90,46 @@ static int bluez_card_object_message_handler(struct client *client, struct pw_ma
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int core_object_force_mono_output(struct client *client, const char *params, FILE *response)
|
||||||
|
{
|
||||||
|
if (!client->have_force_mono_audio) {
|
||||||
|
/* Not supported, return a null value to indicate that */
|
||||||
|
fprintf(response, "null");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!params || params[0] == '\0') {
|
||||||
|
/* No parameter => query the current value */
|
||||||
|
fprintf(response, "%s", client->force_mono_audio ? "true" : "false");
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
/* The caller is trying to set a value or clear with a null */
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (spa_streq(params, "true")) {
|
||||||
|
ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE,
|
||||||
|
METADATA_FEATURES_AUDIO_MONO, "Spa:String:JSON", "true");
|
||||||
|
} else if (spa_streq(params, "false")) {
|
||||||
|
ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE,
|
||||||
|
METADATA_FEATURES_AUDIO_MONO, "Spa:String:JSON", "false");
|
||||||
|
} else if (spa_streq(params, "null")) {
|
||||||
|
ret = pw_manager_set_metadata(client->manager, client->metadata_sm_settings, PW_ID_CORE,
|
||||||
|
METADATA_FEATURES_AUDIO_MONO, NULL, NULL);
|
||||||
|
} else {
|
||||||
|
fprintf(response, "Value must be true, false, or clear");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
fprintf(response, "Could not set metadata: %s", spa_strerror(ret));
|
||||||
|
else
|
||||||
|
fprintf(response, "%s", params);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int core_object_message_handler(struct client *client, struct pw_manager_object *o, const char *message, const char *params, FILE *response)
|
static int core_object_message_handler(struct client *client, struct pw_manager_object *o, const char *message, const char *params, FILE *response)
|
||||||
{
|
{
|
||||||
pw_log_debug(": core %p object message:'%s' params:'%s'", o, message, params);
|
pw_log_debug(": core %p object message:'%s' params:'%s'", o, message, params);
|
||||||
|
|
@ -103,7 +144,8 @@ static int core_object_message_handler(struct client *client, struct pw_manager_
|
||||||
" pipewire-pulse:malloc-trim run malloc_trim\n"
|
" pipewire-pulse:malloc-trim run malloc_trim\n"
|
||||||
" pipewire-pulse:log-level update log level with <params>\n"
|
" pipewire-pulse:log-level update log level with <params>\n"
|
||||||
" pipewire-pulse:list-modules list all module names\n"
|
" pipewire-pulse:list-modules list all module names\n"
|
||||||
" pipewire-pulse:describe-module describe module info for <params>"
|
" pipewire-pulse:describe-module describe module info for <params>\n"
|
||||||
|
" pipewire-pulse:force-mono-output force mono mixdown on all hardware outputs"
|
||||||
);
|
);
|
||||||
} else if (spa_streq(message, "list-handlers")) {
|
} else if (spa_streq(message, "list-handlers")) {
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
@ -164,6 +206,8 @@ static int core_object_message_handler(struct client *client, struct pw_manager_
|
||||||
} else {
|
} else {
|
||||||
fprintf(response, "Failed to open module.\n");
|
fprintf(response, "Failed to open module.\n");
|
||||||
}
|
}
|
||||||
|
} else if (spa_streq(message, "pipewire-pulse:force-mono-output")) {
|
||||||
|
return core_object_force_mono_output(client, params, response);
|
||||||
} else {
|
} else {
|
||||||
return -ENOSYS;
|
return -ENOSYS;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -404,6 +404,14 @@ static void handle_metadata(struct client *client, struct pw_manager_object *old
|
||||||
if (client->metadata_routes == old)
|
if (client->metadata_routes == old)
|
||||||
client->metadata_routes = new;
|
client->metadata_routes = new;
|
||||||
}
|
}
|
||||||
|
else if (spa_streq(name, "sm-settings")) {
|
||||||
|
if (client->metadata_sm_settings == old)
|
||||||
|
client->metadata_sm_settings = new;
|
||||||
|
}
|
||||||
|
else if (spa_streq(name, "schema-sm-settings")) {
|
||||||
|
if (client->metadata_schema_sm_settings == old)
|
||||||
|
client->metadata_schema_sm_settings = new;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static uint32_t frac_to_bytes_round_up(struct spa_fraction val, const struct sample_spec *ss)
|
static uint32_t frac_to_bytes_round_up(struct spa_fraction val, const struct sample_spec *ss)
|
||||||
|
|
@ -964,6 +972,14 @@ static void manager_metadata(void *data, struct pw_manager_object *o,
|
||||||
}
|
}
|
||||||
if (subject == PW_ID_CORE && o == client->metadata_routes)
|
if (subject == PW_ID_CORE && o == client->metadata_routes)
|
||||||
client_update_routes(client, key, value);
|
client_update_routes(client, key, value);
|
||||||
|
if (subject == PW_ID_CORE && o == client->metadata_schema_sm_settings) {
|
||||||
|
if (spa_streq(key, METADATA_FEATURES_AUDIO_MONO))
|
||||||
|
client->have_force_mono_audio = true;
|
||||||
|
}
|
||||||
|
if (subject == PW_ID_CORE && o == client->metadata_sm_settings) {
|
||||||
|
if (spa_streq(key, METADATA_FEATURES_AUDIO_MONO))
|
||||||
|
client->force_mono_audio = spa_streq(value, "true");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1132,7 +1132,7 @@ int pipewire__module_init(struct pw_impl_module *module, const char *args)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IS_VALID_NICE_LEVEL(impl->nice_level)) {
|
if (IS_VALID_NICE_LEVEL(impl->nice_level)) {
|
||||||
if (set_nice(impl, impl->nice_level, !can_use_rtkit) < 0)
|
if (set_nice(impl, impl->nice_level, !use_rtkit) < 0)
|
||||||
use_rtkit = can_use_rtkit;
|
use_rtkit = can_use_rtkit;
|
||||||
}
|
}
|
||||||
if (!use_rtkit)
|
if (!use_rtkit)
|
||||||
|
|
|
||||||
|
|
@ -507,8 +507,6 @@ static int impl_send_command(void *object, const struct spa_command *command)
|
||||||
case SPA_NODE_COMMAND_Suspend:
|
case SPA_NODE_COMMAND_Suspend:
|
||||||
case SPA_NODE_COMMAND_Flush:
|
case SPA_NODE_COMMAND_Flush:
|
||||||
case SPA_NODE_COMMAND_Pause:
|
case SPA_NODE_COMMAND_Pause:
|
||||||
pw_loop_invoke(impl->main_loop,
|
|
||||||
NULL, 0, NULL, 0, false, impl);
|
|
||||||
if (filter->state == PW_FILTER_STATE_STREAMING && id != SPA_NODE_COMMAND_Flush) {
|
if (filter->state == PW_FILTER_STATE_STREAMING && id != SPA_NODE_COMMAND_Flush) {
|
||||||
pw_log_debug("%p: pause", filter);
|
pw_log_debug("%p: pause", filter);
|
||||||
filter_set_state(filter, PW_FILTER_STATE_PAUSED, 0, NULL);
|
filter_set_state(filter, PW_FILTER_STATE_PAUSED, 0, NULL);
|
||||||
|
|
@ -1389,6 +1387,28 @@ static void free_port(struct filter *impl, struct port *port)
|
||||||
free(port);
|
free(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void filter_free(struct pw_filter *filter)
|
||||||
|
{
|
||||||
|
struct filter *impl = SPA_CONTAINER_OF(filter, struct filter, this);
|
||||||
|
|
||||||
|
pw_log_debug("%p: free", filter);
|
||||||
|
clear_params(impl, NULL, SPA_ID_INVALID);
|
||||||
|
|
||||||
|
free(filter->error);
|
||||||
|
|
||||||
|
pw_properties_free(filter->properties);
|
||||||
|
|
||||||
|
pw_map_clear(&impl->ports[SPA_DIRECTION_INPUT]);
|
||||||
|
pw_map_clear(&impl->ports[SPA_DIRECTION_OUTPUT]);
|
||||||
|
|
||||||
|
free(filter->name);
|
||||||
|
|
||||||
|
if (impl->data.context)
|
||||||
|
pw_context_destroy(impl->data.context);
|
||||||
|
|
||||||
|
free(impl);
|
||||||
|
}
|
||||||
|
|
||||||
SPA_EXPORT
|
SPA_EXPORT
|
||||||
void pw_filter_destroy(struct pw_filter *filter)
|
void pw_filter_destroy(struct pw_filter *filter)
|
||||||
{
|
{
|
||||||
|
|
@ -1411,26 +1431,13 @@ void pw_filter_destroy(struct pw_filter *filter)
|
||||||
spa_hook_remove(&filter->core_listener);
|
spa_hook_remove(&filter->core_listener);
|
||||||
spa_list_remove(&filter->link);
|
spa_list_remove(&filter->link);
|
||||||
}
|
}
|
||||||
|
|
||||||
clear_params(impl, NULL, SPA_ID_INVALID);
|
|
||||||
|
|
||||||
pw_log_debug("%p: free", filter);
|
|
||||||
free(filter->error);
|
|
||||||
|
|
||||||
pw_properties_free(filter->properties);
|
|
||||||
|
|
||||||
spa_hook_list_clean(&impl->hooks);
|
spa_hook_list_clean(&impl->hooks);
|
||||||
spa_hook_list_clean(&filter->listener_list);
|
spa_hook_list_clean(&filter->listener_list);
|
||||||
|
|
||||||
pw_map_clear(&impl->ports[SPA_DIRECTION_INPUT]);
|
/* Make sure there are no queued invokes from us anymore */
|
||||||
pw_map_clear(&impl->ports[SPA_DIRECTION_OUTPUT]);
|
pw_loop_invoke(impl->main_loop, NULL, 0, NULL, 0, false, impl);
|
||||||
|
|
||||||
free(filter->name);
|
filter_free(filter);
|
||||||
|
|
||||||
if (impl->data.context)
|
|
||||||
pw_context_destroy(impl->data.context);
|
|
||||||
|
|
||||||
free(impl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
|
|
||||||
|
|
@ -715,8 +715,6 @@ static int impl_send_command(void *object, const struct spa_command *command)
|
||||||
case SPA_NODE_COMMAND_Suspend:
|
case SPA_NODE_COMMAND_Suspend:
|
||||||
case SPA_NODE_COMMAND_Flush:
|
case SPA_NODE_COMMAND_Flush:
|
||||||
case SPA_NODE_COMMAND_Pause:
|
case SPA_NODE_COMMAND_Pause:
|
||||||
pw_loop_invoke(impl->main_loop,
|
|
||||||
NULL, 0, NULL, 0, false, impl);
|
|
||||||
if (stream->state == PW_STREAM_STATE_STREAMING && id != SPA_NODE_COMMAND_Flush) {
|
if (stream->state == PW_STREAM_STATE_STREAMING && id != SPA_NODE_COMMAND_Flush) {
|
||||||
pw_log_debug("%p: pause", stream);
|
pw_log_debug("%p: pause", stream);
|
||||||
stream_set_state(stream, PW_STREAM_STATE_PAUSED, 0, NULL);
|
stream_set_state(stream, PW_STREAM_STATE_PAUSED, 0, NULL);
|
||||||
|
|
@ -1748,11 +1746,35 @@ static int stream_disconnect(struct stream *impl)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void stream_free(struct pw_stream *stream)
|
||||||
|
{
|
||||||
|
struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
|
||||||
|
struct control *c;
|
||||||
|
|
||||||
|
pw_log_debug("%p: free", stream);
|
||||||
|
clear_params(impl, SPA_ID_INVALID, 0);
|
||||||
|
|
||||||
|
free(stream->error);
|
||||||
|
|
||||||
|
pw_properties_free(stream->properties);
|
||||||
|
|
||||||
|
free(stream->name);
|
||||||
|
|
||||||
|
spa_list_consume(c, &stream->controls, link) {
|
||||||
|
spa_list_remove(&c->link);
|
||||||
|
free(c);
|
||||||
|
}
|
||||||
|
if (impl->data.context)
|
||||||
|
pw_context_destroy(impl->data.context);
|
||||||
|
|
||||||
|
pw_properties_free(impl->port_props);
|
||||||
|
free(impl);
|
||||||
|
}
|
||||||
|
|
||||||
SPA_EXPORT
|
SPA_EXPORT
|
||||||
void pw_stream_destroy(struct pw_stream *stream)
|
void pw_stream_destroy(struct pw_stream *stream)
|
||||||
{
|
{
|
||||||
struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
|
struct stream *impl = SPA_CONTAINER_OF(stream, struct stream, this);
|
||||||
struct control *c;
|
|
||||||
|
|
||||||
ensure_loop(impl->main_loop, return);
|
ensure_loop(impl->main_loop, return);
|
||||||
|
|
||||||
|
|
@ -1768,29 +1790,13 @@ void pw_stream_destroy(struct pw_stream *stream)
|
||||||
spa_list_remove(&stream->link);
|
spa_list_remove(&stream->link);
|
||||||
stream->core = NULL;
|
stream->core = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
clear_params(impl, SPA_ID_INVALID, 0);
|
|
||||||
|
|
||||||
pw_log_debug("%p: free", stream);
|
|
||||||
free(stream->error);
|
|
||||||
|
|
||||||
pw_properties_free(stream->properties);
|
|
||||||
|
|
||||||
free(stream->name);
|
|
||||||
|
|
||||||
spa_list_consume(c, &stream->controls, link) {
|
|
||||||
spa_list_remove(&c->link);
|
|
||||||
free(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
spa_hook_list_clean(&impl->hooks);
|
spa_hook_list_clean(&impl->hooks);
|
||||||
spa_hook_list_clean(&stream->listener_list);
|
spa_hook_list_clean(&stream->listener_list);
|
||||||
|
|
||||||
if (impl->data.context)
|
/* Make sure there are no queued invokes from us anymore */
|
||||||
pw_context_destroy(impl->data.context);
|
pw_loop_invoke(impl->main_loop, NULL, 0, NULL, 0, false, impl);
|
||||||
|
|
||||||
pw_properties_free(impl->port_props);
|
stream_free(stream);
|
||||||
free(impl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
|
|
@ -2475,8 +2481,9 @@ int pw_stream_get_time_n(struct pw_stream *stream, struct pw_time *time, size_t
|
||||||
|
|
||||||
time->delay += (int64_t)(((latency->min_quantum + latency->max_quantum) / 2.0f) * quantum);
|
time->delay += (int64_t)(((latency->min_quantum + latency->max_quantum) / 2.0f) * quantum);
|
||||||
time->delay += (latency->min_rate + latency->max_rate) / 2;
|
time->delay += (latency->min_rate + latency->max_rate) / 2;
|
||||||
|
if (time->rate.num != 0)
|
||||||
time->delay += ((latency->min_ns + latency->max_ns) / 2) *
|
time->delay += ((latency->min_ns + latency->max_ns) / 2) *
|
||||||
(int64_t)time->rate.denom / (int64_t)SPA_NSEC_PER_SEC;
|
(int64_t)time->rate.denom / ((int64_t)SPA_NSEC_PER_SEC * time->rate.num);
|
||||||
|
|
||||||
avail_buffers = spa_ringbuffer_get_read_index(&impl->dequeued.ring, &index);
|
avail_buffers = spa_ringbuffer_get_read_index(&impl->dequeued.ring, &index);
|
||||||
avail_buffers = SPA_CLAMP(avail_buffers, 0, (int32_t)impl->n_buffers);
|
avail_buffers = SPA_CLAMP(avail_buffers, 0, (int32_t)impl->n_buffers);
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ struct data {
|
||||||
#define TYPE_SYSEX 4
|
#define TYPE_SYSEX 4
|
||||||
#define TYPE_MIDI2 5
|
#define TYPE_MIDI2 5
|
||||||
int data_type;
|
int data_type;
|
||||||
bool raw;
|
bool rawfile;
|
||||||
const char *remote_name;
|
const char *remote_name;
|
||||||
const char *media_type;
|
const char *media_type;
|
||||||
const char *media_category;
|
const char *media_category;
|
||||||
|
|
@ -125,7 +125,6 @@ struct data {
|
||||||
struct pw_properties *props;
|
struct pw_properties *props;
|
||||||
|
|
||||||
const char *filename;
|
const char *filename;
|
||||||
SNDFILE *file;
|
|
||||||
|
|
||||||
unsigned int bitrate;
|
unsigned int bitrate;
|
||||||
unsigned int rate;
|
unsigned int rate;
|
||||||
|
|
@ -147,6 +146,9 @@ struct data {
|
||||||
bool drained;
|
bool drained;
|
||||||
uint64_t clock_time;
|
uint64_t clock_time;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
SNDFILE *file;
|
||||||
|
} sndfile;
|
||||||
struct {
|
struct {
|
||||||
struct midi_file *file;
|
struct midi_file *file;
|
||||||
struct midi_file_info info;
|
struct midi_file_info info;
|
||||||
|
|
@ -181,7 +183,12 @@ struct data {
|
||||||
#endif
|
#endif
|
||||||
struct {
|
struct {
|
||||||
FILE *file;
|
FILE *file;
|
||||||
|
bool close;
|
||||||
} sysex;
|
} sysex;
|
||||||
|
struct {
|
||||||
|
FILE *file;
|
||||||
|
bool close;
|
||||||
|
} raw;
|
||||||
|
|
||||||
uint64_t sample_limit; /* 0 means unlimited */
|
uint64_t sample_limit; /* 0 means unlimited */
|
||||||
uint64_t samples_processed;
|
uint64_t samples_processed;
|
||||||
|
|
@ -227,7 +234,7 @@ static int sf_playback_fill_x8(struct data *d, void *dest, unsigned int n_frames
|
||||||
{
|
{
|
||||||
sf_count_t rn;
|
sf_count_t rn;
|
||||||
|
|
||||||
rn = sf_read_raw(d->file, dest, n_frames * d->stride);
|
rn = sf_read_raw(d->sndfile.file, dest, n_frames * d->stride);
|
||||||
return (int)rn / d->stride;
|
return (int)rn / d->stride;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,7 +243,7 @@ static int sf_playback_fill_s16(struct data *d, void *dest, unsigned int n_frame
|
||||||
sf_count_t rn;
|
sf_count_t rn;
|
||||||
|
|
||||||
assert(sizeof(short) == sizeof(int16_t));
|
assert(sizeof(short) == sizeof(int16_t));
|
||||||
rn = sf_readf_short(d->file, dest, n_frames);
|
rn = sf_readf_short(d->sndfile.file, dest, n_frames);
|
||||||
return (int)rn;
|
return (int)rn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -245,7 +252,7 @@ static int sf_playback_fill_s32(struct data *d, void *dest, unsigned int n_frame
|
||||||
sf_count_t rn;
|
sf_count_t rn;
|
||||||
|
|
||||||
assert(sizeof(int) == sizeof(int32_t));
|
assert(sizeof(int) == sizeof(int32_t));
|
||||||
rn = sf_readf_int(d->file, dest, n_frames);
|
rn = sf_readf_int(d->sndfile.file, dest, n_frames);
|
||||||
return (int)rn;
|
return (int)rn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,7 +261,7 @@ static int sf_playback_fill_f32(struct data *d, void *dest, unsigned int n_frame
|
||||||
sf_count_t rn;
|
sf_count_t rn;
|
||||||
|
|
||||||
assert(sizeof(float) == 4);
|
assert(sizeof(float) == 4);
|
||||||
rn = sf_readf_float(d->file, dest, n_frames);
|
rn = sf_readf_float(d->sndfile.file, dest, n_frames);
|
||||||
return (int)rn;
|
return (int)rn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -263,7 +270,7 @@ static int sf_playback_fill_f64(struct data *d, void *dest, unsigned int n_frame
|
||||||
sf_count_t rn;
|
sf_count_t rn;
|
||||||
|
|
||||||
assert(sizeof(double) == 8);
|
assert(sizeof(double) == 8);
|
||||||
rn = sf_readf_double(d->file, dest, n_frames);
|
rn = sf_readf_double(d->sndfile.file, dest, n_frames);
|
||||||
return (int)rn;
|
return (int)rn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -550,7 +557,7 @@ static int sf_record_fill_x8(struct data *d, void *src, unsigned int n_frames, b
|
||||||
{
|
{
|
||||||
sf_count_t rn;
|
sf_count_t rn;
|
||||||
|
|
||||||
rn = sf_write_raw(d->file, src, n_frames * d->stride);
|
rn = sf_write_raw(d->sndfile.file, src, n_frames * d->stride);
|
||||||
return (int)rn / d->stride;
|
return (int)rn / d->stride;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -559,7 +566,7 @@ static int sf_record_fill_s16(struct data *d, void *src, unsigned int n_frames,
|
||||||
sf_count_t rn;
|
sf_count_t rn;
|
||||||
|
|
||||||
assert(sizeof(short) == sizeof(int16_t));
|
assert(sizeof(short) == sizeof(int16_t));
|
||||||
rn = sf_writef_short(d->file, src, n_frames);
|
rn = sf_writef_short(d->sndfile.file, src, n_frames);
|
||||||
return (int)rn;
|
return (int)rn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -568,7 +575,7 @@ static int sf_record_fill_s32(struct data *d, void *src, unsigned int n_frames,
|
||||||
sf_count_t rn;
|
sf_count_t rn;
|
||||||
|
|
||||||
assert(sizeof(int) == sizeof(int32_t));
|
assert(sizeof(int) == sizeof(int32_t));
|
||||||
rn = sf_writef_int(d->file, src, n_frames);
|
rn = sf_writef_int(d->sndfile.file, src, n_frames);
|
||||||
return (int)rn;
|
return (int)rn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -577,7 +584,7 @@ static int sf_record_fill_f32(struct data *d, void *src, unsigned int n_frames,
|
||||||
sf_count_t rn;
|
sf_count_t rn;
|
||||||
|
|
||||||
assert(sizeof(float) == 4);
|
assert(sizeof(float) == 4);
|
||||||
rn = sf_writef_float(d->file, src, n_frames);
|
rn = sf_writef_float(d->sndfile.file, src, n_frames);
|
||||||
return (int)rn;
|
return (int)rn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -586,7 +593,7 @@ static int sf_record_fill_f64(struct data *d, void *src, unsigned int n_frames,
|
||||||
sf_count_t rn;
|
sf_count_t rn;
|
||||||
|
|
||||||
assert(sizeof(double) == 8);
|
assert(sizeof(double) == 8);
|
||||||
rn = sf_writef_double(d->file, src, n_frames);
|
rn = sf_writef_double(d->sndfile.file, src, n_frames);
|
||||||
return (int)rn;
|
return (int)rn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1480,11 +1487,17 @@ static int setup_sysex(struct data *data)
|
||||||
if (data->mode == mode_record)
|
if (data->mode == mode_record)
|
||||||
return -ENOTSUP;
|
return -ENOTSUP;
|
||||||
|
|
||||||
|
if (spa_streq(data->filename, "-")) {
|
||||||
|
data->sysex.file = stdin;
|
||||||
|
data->sysex.close = false;
|
||||||
|
} else {
|
||||||
data->sysex.file = fopen(data->filename, "r");
|
data->sysex.file = fopen(data->filename, "r");
|
||||||
if (data->sysex.file == NULL) {
|
if (data->sysex.file == NULL) {
|
||||||
fprintf(stderr, "sysex: can't read file '%s': %m\n", data->filename);
|
fprintf(stderr, "sysex: can't read file '%s': %m\n", data->filename);
|
||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
|
data->sysex.close = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (data->verbose)
|
if (data->verbose)
|
||||||
fprintf(stderr, "sysex: opened file \"%s\"\n", data->filename);
|
fprintf(stderr, "sysex: opened file \"%s\"\n", data->filename);
|
||||||
|
|
@ -1557,17 +1570,17 @@ static int setup_dsdfile(struct data *data)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int stdout_record(struct data *d, void *src, unsigned int n_frames, bool *null_frame)
|
static int raw_record(struct data *d, void *src, unsigned int n_frames, bool *null_frame)
|
||||||
{
|
{
|
||||||
return fwrite(src, d->stride, n_frames, stdout);
|
return fwrite(src, d->stride, n_frames, d->raw.file);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int stdin_play(struct data *d, void *src, unsigned int n_frames, bool *null_frame)
|
static int raw_play(struct data *d, void *src, unsigned int n_frames, bool *null_frame)
|
||||||
{
|
{
|
||||||
return fread(src, d->stride, n_frames, stdin);
|
return fread(src, d->stride, n_frames, d->raw.file);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int setup_pipe(struct data *data)
|
static int setup_raw(struct data *data)
|
||||||
{
|
{
|
||||||
const struct format_info *info;
|
const struct format_info *info;
|
||||||
|
|
||||||
|
|
@ -1586,10 +1599,23 @@ static int setup_pipe(struct data *data)
|
||||||
|
|
||||||
data->spa_format = info->spa_format;
|
data->spa_format = info->spa_format;
|
||||||
data->stride = info->width * data->channels;
|
data->stride = info->width * data->channels;
|
||||||
data->fill = data->mode == mode_playback ? stdin_play : stdout_record;
|
data->fill = data->mode == mode_playback ? raw_play : raw_record;
|
||||||
|
|
||||||
|
if (spa_streq(data->filename, "-")) {
|
||||||
|
data->raw.file = data->mode == mode_playback ? stdin : stdout;
|
||||||
|
data->raw.close = false;
|
||||||
|
} else {
|
||||||
|
data->raw.file = fopen(data->filename,
|
||||||
|
data->mode == mode_playback ? "r" : "w");
|
||||||
|
if (data->raw.file == NULL) {
|
||||||
|
fprintf(stderr, "raw: can't open file '%s': %m\n", data->filename);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
data->raw.close = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (data->verbose)
|
if (data->verbose)
|
||||||
fprintf(stderr, "PIPE: rate=%u channels=%u fmt=%s samplesize=%u stride=%u\n",
|
fprintf(stderr, "raw: rate=%u channels=%u fmt=%s samplesize=%u stride=%u\n",
|
||||||
data->rate, data->channels,
|
data->rate, data->channels,
|
||||||
info->name, info->width, data->stride);
|
info->name, info->width, data->stride);
|
||||||
|
|
||||||
|
|
@ -1618,7 +1644,7 @@ static int fill_properties(struct data *data)
|
||||||
if (table[c] == NULL)
|
if (table[c] == NULL)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if ((s = sf_get_string(data->file, c)) == NULL ||
|
if ((s = sf_get_string(data->sndfile.file, c)) == NULL ||
|
||||||
*s == '\0')
|
*s == '\0')
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
@ -1627,14 +1653,14 @@ static int fill_properties(struct data *data)
|
||||||
}
|
}
|
||||||
|
|
||||||
spa_zero(sfi);
|
spa_zero(sfi);
|
||||||
if ((res = sf_command(data->file, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)))) {
|
if ((res = sf_command(data->sndfile.file, SFC_GET_CURRENT_SF_INFO, &sfi, sizeof(sfi)))) {
|
||||||
pw_log_error("sndfile: %s", sf_error_number(res));
|
pw_log_error("sndfile: %s", sf_error_number(res));
|
||||||
return -EIO;
|
return -EIO;
|
||||||
}
|
}
|
||||||
|
|
||||||
spa_zero(fi);
|
spa_zero(fi);
|
||||||
fi.format = sfi.format;
|
fi.format = sfi.format;
|
||||||
if (sf_command(data->file, SFC_GET_FORMAT_INFO, &fi, sizeof(fi)) == 0 && fi.name)
|
if (sf_command(data->sndfile.file, SFC_GET_FORMAT_INFO, &fi, sizeof(fi)) == 0 && fi.name)
|
||||||
if (pw_properties_get(data->props, PW_KEY_MEDIA_FORMAT) == NULL)
|
if (pw_properties_get(data->props, PW_KEY_MEDIA_FORMAT) == NULL)
|
||||||
pw_properties_set(data->props, PW_KEY_MEDIA_FORMAT, fi.name);
|
pw_properties_set(data->props, PW_KEY_MEDIA_FORMAT, fi.name);
|
||||||
|
|
||||||
|
|
@ -1811,10 +1837,10 @@ static int setup_sndfile(struct data *data)
|
||||||
format_from_filename(&info, data->filename);
|
format_from_filename(&info, data->filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
data->file = sf_open(data->filename,
|
data->sndfile.file = sf_open(data->filename,
|
||||||
data->mode == mode_playback ? SFM_READ : SFM_WRITE,
|
data->mode == mode_playback ? SFM_READ : SFM_WRITE,
|
||||||
&info);
|
&info);
|
||||||
if (!data->file) {
|
if (!data->sndfile.file) {
|
||||||
fprintf(stderr, "sndfile: failed to open audio file \"%s\": %s\n",
|
fprintf(stderr, "sndfile: failed to open audio file \"%s\": %s\n",
|
||||||
data->filename, sf_strerror(NULL));
|
data->filename, sf_strerror(NULL));
|
||||||
if (data->verbose) {
|
if (data->verbose) {
|
||||||
|
|
@ -1853,7 +1879,7 @@ static int setup_sndfile(struct data *data)
|
||||||
if (data->channelmap.n_channels == 0) {
|
if (data->channelmap.n_channels == 0) {
|
||||||
bool def = false;
|
bool def = false;
|
||||||
|
|
||||||
if (sf_command(data->file, SFC_GET_CHANNEL_MAP_INFO,
|
if (sf_command(data->sndfile.file, SFC_GET_CHANNEL_MAP_INFO,
|
||||||
data->channelmap.position,
|
data->channelmap.position,
|
||||||
sizeof(data->channelmap.position[0]) * data->channels)) {
|
sizeof(data->channelmap.position[0]) * data->channels)) {
|
||||||
data->channelmap.n_channels = data->channels;
|
data->channelmap.n_channels = data->channels;
|
||||||
|
|
@ -2097,7 +2123,7 @@ int main(int argc, char *argv[])
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'a':
|
case 'a':
|
||||||
data.raw = true;
|
data.rawfile = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
|
|
@ -2264,8 +2290,8 @@ int main(int argc, char *argv[])
|
||||||
}
|
}
|
||||||
pw_core_add_listener(data.core, &data.core_listener, &core_events, &data);
|
pw_core_add_listener(data.core, &data.core_listener, &core_events, &data);
|
||||||
|
|
||||||
if (data.raw) {
|
if (data.rawfile) {
|
||||||
ret = setup_pipe(&data);
|
ret = setup_raw(&data);
|
||||||
} else {
|
} else {
|
||||||
switch (data.data_type) {
|
switch (data.data_type) {
|
||||||
case TYPE_PCM:
|
case TYPE_PCM:
|
||||||
|
|
@ -2500,8 +2526,8 @@ error_no_context:
|
||||||
error_no_props:
|
error_no_props:
|
||||||
error_no_main_loop:
|
error_no_main_loop:
|
||||||
pw_properties_free(data.props);
|
pw_properties_free(data.props);
|
||||||
if (data.file)
|
if (data.sndfile.file)
|
||||||
sf_close(data.file);
|
sf_close(data.sndfile.file);
|
||||||
if (data.midi.file)
|
if (data.midi.file)
|
||||||
midi_file_close(data.midi.file);
|
midi_file_close(data.midi.file);
|
||||||
if (data.clip.file)
|
if (data.clip.file)
|
||||||
|
|
@ -2510,6 +2536,10 @@ error_no_main_loop:
|
||||||
dsf_file_close(data.dsf.file);
|
dsf_file_close(data.dsf.file);
|
||||||
if (data.dff.file)
|
if (data.dff.file)
|
||||||
dff_file_close(data.dff.file);
|
dff_file_close(data.dff.file);
|
||||||
|
if (data.sysex.file && data.sysex.close)
|
||||||
|
fclose(data.sysex.file);
|
||||||
|
if (data.raw.file && data.raw.close)
|
||||||
|
fclose(data.raw.file);
|
||||||
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
|
#ifdef HAVE_PW_CAT_FFMPEG_INTEGRATION
|
||||||
if (data.encoded.packet)
|
if (data.encoded.packet)
|
||||||
av_packet_free(&data.encoded.packet);
|
av_packet_free(&data.encoded.packet);
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue